Package org.ggp.base.util.gdl.model

Source Code of org.ggp.base.util.gdl.model.SentenceDomainModelOptimizer

package org.ggp.base.util.gdl.model;

import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.ggp.base.util.concurrency.ConcurrencyUtils;
import org.ggp.base.util.gdl.GdlUtils;
import org.ggp.base.util.gdl.GdlVisitor;
import org.ggp.base.util.gdl.GdlVisitors;
import org.ggp.base.util.gdl.grammar.Gdl;
import org.ggp.base.util.gdl.grammar.GdlConstant;
import org.ggp.base.util.gdl.grammar.GdlDistinct;
import org.ggp.base.util.gdl.grammar.GdlLiteral;
import org.ggp.base.util.gdl.grammar.GdlNot;
import org.ggp.base.util.gdl.grammar.GdlOr;
import org.ggp.base.util.gdl.grammar.GdlPool;
import org.ggp.base.util.gdl.grammar.GdlRule;
import org.ggp.base.util.gdl.grammar.GdlSentence;
import org.ggp.base.util.gdl.grammar.GdlTerm;
import org.ggp.base.util.gdl.grammar.GdlVariable;
import org.ggp.base.util.gdl.model.SentenceDomainModels.VarDomainOpts;
import org.ggp.base.util.gdl.transforms.VariableConstrainer;

import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;

public class SentenceDomainModelOptimizer {
  /**
   * Given a SentenceDomainModel, returns an ImmutableSentenceDomainModel
   * with Cartesian domains that tries to minimize the domains of sentence
   * forms without impacting the game rules. In particular, when sentences
   * are restricted to these domains, the answers to queries about terminal,
   * legal, goal, next, and init sentences will not change.
   *
   * Note that if a sentence form is not used in a meaningful way by the
   * game, it may end up with an empty domain.
   *
   * The description for the game must have had the {@link VariableConstrainer}
   * applied to it.
   */
  public static ImmutableSentenceDomainModel restrictDomainsToUsefulValues(SentenceDomainModel oldModel) throws InterruptedException {
    // Start with everything from the current domain model.
    Map<SentenceForm, SetMultimap<Integer, GdlConstant>> neededAndPossibleConstantsByForm = Maps.newHashMap();
    for (SentenceForm form : oldModel.getSentenceForms()) {
      neededAndPossibleConstantsByForm.put(form, HashMultimap.<Integer, GdlConstant>create());
      addDomain(neededAndPossibleConstantsByForm.get(form), oldModel.getDomain(form), form);
    }

    /*
     * To minimize the contents of the domains, we repeatedly go through two processes to reduce
     * the domain:
     *
     * 1) We remove unneeded constants from the domain. These are constants which (in their
     *    position) do not contribute to any sentences with a GDL keyword as its name; that
     *    is, it never matters whether a sentence with that constant in that position is
     *    true or false.
     * 2) We remove impossible constants from the domain. These are constants which cannot
     *    end up in their position via any rule or sentence in the game description, given
     *    the current domain.
     *
     * Constants removed because of one type of pass or the other may cause other constants
     * in other sentence forms to become unneeded or impossible, so we make multiple passes
     * until everything is stable.
     */
    boolean somethingChanged = true;
    while (somethingChanged) {
      somethingChanged = removeUnneededConstants(neededAndPossibleConstantsByForm, oldModel);
      somethingChanged |= removeImpossibleConstants(neededAndPossibleConstantsByForm, oldModel);
    }

    return toSentenceDomainModel(neededAndPossibleConstantsByForm, oldModel);
  }

  private static void addDomain(
      SetMultimap<Integer, GdlConstant> setMultimap,
      SentenceFormDomain domain,
      SentenceForm form) {
    for (int i = 0; i < form.getTupleSize(); i++) {
      setMultimap.putAll(i, domain.getDomainForSlot(i));
    }
  }

  private static boolean removeImpossibleConstants(
      Map<SentenceForm, SetMultimap<Integer, GdlConstant>> curDomains,
      SentenceFormModel model) throws InterruptedException {
    Map<SentenceForm, SetMultimap<Integer, GdlConstant>> newPossibleConstantsByForm = Maps.newHashMap();
    for (SentenceForm form : curDomains.keySet()) {
      newPossibleConstantsByForm.put(form, HashMultimap.<Integer, GdlConstant>create());
    }
    populateInitialPossibleConstants(newPossibleConstantsByForm, curDomains, model);

    boolean somethingChanged = true;
    while (somethingChanged) {
      somethingChanged = propagatePossibleConstants(newPossibleConstantsByForm, curDomains, model);
    }

    return retainNewDomains(curDomains, newPossibleConstantsByForm);
  }

  private static void populateInitialPossibleConstants(
      Map<SentenceForm, SetMultimap<Integer, GdlConstant>> newPossibleConstantsByForm,
      Map<SentenceForm, SetMultimap<Integer, GdlConstant>> curDomains,
      SentenceFormModel model) throws InterruptedException {
    //Add anything in the head of a rule...
    for (GdlRule rule : getRules(model.getDescription())) {
      GdlSentence head = rule.getHead();

      addConstantsFromSentenceIfInOldDomain(newPossibleConstantsByForm, curDomains, model, head);
    }
    //... and any true sentences
    for (SentenceForm form : model.getSentenceForms()) {
      for (GdlSentence sentence : model.getSentencesListedAsTrue(form)) {
        addConstantsFromSentenceIfInOldDomain(newPossibleConstantsByForm, curDomains, model, sentence);
      }
    }
  }

  private static boolean propagatePossibleConstants(
      Map<SentenceForm, SetMultimap<Integer, GdlConstant>> newPossibleConstantsByForm,
      Map<SentenceForm, SetMultimap<Integer, GdlConstant>> curDomain,
      SentenceFormModel model) throws InterruptedException {
    //Injection: Go from the intersections of variable values in rules to the
    //values in their heads
    boolean somethingChanged = false;

    for (GdlRule rule : getRules(model.getDescription())) {
      GdlSentence head = rule.getHead();

      Map<GdlVariable, Set<GdlConstant>> domainsOfHeadVars = Maps.newHashMap();
      for (GdlVariable varInHead : ImmutableSet.copyOf(GdlUtils.getVariables(rule.getHead()))) {
        Set<GdlConstant> domain = getVarDomainInRuleBody(varInHead, rule, newPossibleConstantsByForm, curDomain, model);
        domainsOfHeadVars.put(varInHead, domain);
        somethingChanged |= addPossibleValuesToSentence(domain, head, varInHead, newPossibleConstantsByForm, model);
      }
    }

    //Language-based injections
    somethingChanged |= applyLanguageBasedInjections(GdlPool.INIT, GdlPool.TRUE, newPossibleConstantsByForm);
    somethingChanged |= applyLanguageBasedInjections(GdlPool.NEXT, GdlPool.TRUE, newPossibleConstantsByForm);
    somethingChanged |= applyLanguageBasedInjections(GdlPool.LEGAL, GdlPool.DOES, newPossibleConstantsByForm);

    return somethingChanged;
  }

  private static boolean applyLanguageBasedInjections(
      GdlConstant curName,
      GdlConstant resultingName,
      Map<SentenceForm, SetMultimap<Integer, GdlConstant>> newPossibleConstantsByForm) throws InterruptedException {
    boolean somethingChanged = false;
    for (SentenceForm form : newPossibleConstantsByForm.keySet()) {
      ConcurrencyUtils.checkForInterruption();
      if (form.getName() == curName) {
        SentenceForm resultingForm = form.withName(resultingName);

        SetMultimap<Integer, GdlConstant> curFormDomain = newPossibleConstantsByForm.get(form);
        SetMultimap<Integer, GdlConstant> resultingFormDomain = newPossibleConstantsByForm.get(resultingForm);

        somethingChanged |= resultingFormDomain.putAll(curFormDomain);
      }
    }
    return somethingChanged;
  }

  private static Set<GdlConstant> getVarDomainInRuleBody(
      GdlVariable varInHead,
      GdlRule rule,
      Map<SentenceForm, SetMultimap<Integer, GdlConstant>> newPossibleConstantsByForm,
      Map<SentenceForm, SetMultimap<Integer, GdlConstant>> curDomain,
      SentenceFormModel model) {
    try {
      List<Set<GdlConstant>> domains = Lists.newArrayList();
      for (GdlSentence conjunct : getPositiveConjuncts(rule.getBody())) {
        if (GdlUtils.getVariables(conjunct).contains(varInHead)) {
          domains.add(getVarDomainInSentence(varInHead, conjunct, newPossibleConstantsByForm, curDomain, model));
        }
      }
      return getIntersection(domains);
    } catch (RuntimeException e) {
      throw new RuntimeException("Error in rule " + rule + " for variable " + varInHead, e);
    }
  }

  private static Set<GdlConstant> getVarDomainInSentence(
      GdlVariable var,
      GdlSentence conjunct,
      Map<SentenceForm, SetMultimap<Integer, GdlConstant>> newPossibleConstantsByForm,
      Map<SentenceForm, SetMultimap<Integer, GdlConstant>> curDomain,
      SentenceFormModel model) {
    SentenceForm form = model.getSentenceForm(conjunct);
    List<GdlTerm> tuple = GdlUtils.getTupleFromSentence(conjunct);

    List<Set<GdlConstant>> domains = Lists.newArrayList();
    for (int i = 0; i < tuple.size(); i++) {
      if (tuple.get(i) == var) {
        domains.add(newPossibleConstantsByForm.get(form).get(i));
        domains.add(curDomain.get(form).get(i));
      }
    }
    return getIntersection(domains);
  }

  private static Set<GdlConstant> getIntersection(
      List<Set<GdlConstant>> domains) {
    if (domains.isEmpty()) {
      throw new IllegalArgumentException("Unsafe rule has no positive conjuncts");
    }
    Set<GdlConstant> intersection = Sets.newHashSet(domains.get(0));
    for (int i = 1; i < domains.size(); i++) {
      Set<GdlConstant> curDomain = domains.get(i);
      intersection.retainAll(curDomain);
    }
    return intersection;
  }

  private static boolean removeUnneededConstants(
      Map<SentenceForm, SetMultimap<Integer, GdlConstant>> curDomains,
      SentenceFormModel model) throws InterruptedException {
    Map<SentenceForm, SetMultimap<Integer, GdlConstant>> newNeededConstantsByForm = Maps.newHashMap();
    for (SentenceForm form : curDomains.keySet()) {
      newNeededConstantsByForm.put(form, HashMultimap.<Integer, GdlConstant>create());
    }
    populateInitialNeededConstants(newNeededConstantsByForm, curDomains, model);

    boolean somethingChanged = true;
    while (somethingChanged) {
      somethingChanged = propagateNeededConstants(newNeededConstantsByForm, curDomains, model);
    }

    return retainNewDomains(curDomains, newNeededConstantsByForm);
  }

  private static boolean retainNewDomains(
      Map<SentenceForm, SetMultimap<Integer, GdlConstant>> curDomains,
      Map<SentenceForm, SetMultimap<Integer, GdlConstant>> newDomains) {
    boolean somethingChanged = false;
    for (SentenceForm form : curDomains.keySet()) {
      SetMultimap<Integer, GdlConstant> newDomain = newDomains.get(form);
      somethingChanged |= curDomains.get(form).entries().retainAll(newDomain.entries());
    }
    return somethingChanged;
  }

  private static boolean propagateNeededConstants(
      Map<SentenceForm, SetMultimap<Integer, GdlConstant>> neededConstantsByForm,
      Map<SentenceForm, SetMultimap<Integer, GdlConstant>> curDomains,
      SentenceFormModel model) throws InterruptedException {
    boolean somethingChanged = false;

    somethingChanged |= applyRuleHeadPropagation(neededConstantsByForm, curDomains, model);
    somethingChanged |= applyRuleBodyOnlyPropagation(neededConstantsByForm, curDomains, model);

    return somethingChanged;
  }


  private static boolean applyRuleBodyOnlyPropagation(
      Map<SentenceForm, SetMultimap<Integer, GdlConstant>> neededConstantsByForm,
      Map<SentenceForm, SetMultimap<Integer, GdlConstant>> curDomains,
      SentenceFormModel model) throws InterruptedException {
    boolean somethingChanged = false;
    //If a variable does not appear in the head of a variable,
    //then all the values that are in the intersections of all the
    //domains from the positive conjuncts containing the variable
    //become needed.

    for (GdlRule rule : getRules(model.getDescription())) {
      GdlSentence head = rule.getHead();
      Set<GdlVariable> varsInHead = ImmutableSet.copyOf(GdlUtils.getVariables(head));

      Map<GdlVariable, Set<GdlConstant>> varDomains = getVarDomains(rule, curDomains, model);
      for (GdlVariable var : ImmutableSet.copyOf(GdlUtils.getVariables(rule))) {
        if (!varsInHead.contains(var)) {
          Set<GdlConstant> neededConstants = varDomains.get(var);
          if (neededConstants == null) {
            throw new IllegalStateException("var is " + var + ";\nvarDomains key set is " + varDomains.keySet() + ";\nvarsInHead is " + varsInHead +
              ";\nrule is " + rule);
          }
          for (GdlLiteral conjunct : rule.getBody()) {
            somethingChanged |=
                addPossibleValuesToConjunct(neededConstants, conjunct, var, neededConstantsByForm, model);
          }
        }
      }
    }
    return somethingChanged;
  }

  private static Map<GdlVariable, Set<GdlConstant>> getVarDomains(
      GdlRule rule,
      final Map<SentenceForm, SetMultimap<Integer, GdlConstant>> curDomains,
      final SentenceFormModel model) {
    return SentenceDomainModels.getVarDomains(rule, new AbstractSentenceDomainModel(model) {
      @Override
      public SentenceFormDomain getDomain(final SentenceForm form) {
        return new SentenceFormDomain() {
          @Override
          public SentenceForm getForm() {
            return form;
          }

          @Override
          public Iterator<GdlSentence> iterator() {
            throw new UnsupportedOperationException();
          }

          @Override
          public Set<GdlConstant> getDomainForSlot(int slotIndex) {
            if (!curDomains.containsKey(form)) {
              return ImmutableSet.of();
            }
            return curDomains.get(form).get(slotIndex);
          }
        };
      }}, VarDomainOpts.INCLUDE_HEAD);
  }

  private static boolean applyRuleHeadPropagation(
      Map<SentenceForm, SetMultimap<Integer, GdlConstant>> neededConstantsByForm,
      Map<SentenceForm, SetMultimap<Integer, GdlConstant>> curDomains,
      SentenceFormModel model) throws InterruptedException {
    boolean somethingChanged = false;
    //If a term that is a variable in the head of a rule needs a
    //particular value, AND that variable is possible (i.e. in the
    //current domain) in every appearance of the variable in
    //positive conjuncts in the rule's body, then the value is
    //needed in every appearance of the variable in the rule
    //(positive or negative).
    for (GdlRule rule : getRules(model.getDescription())) {
      GdlSentence head = rule.getHead();
      SentenceForm headForm = model.getSentenceForm(head);
      List<GdlTerm> headTuple = GdlUtils.getTupleFromSentence(head);

      Map<GdlVariable, Set<GdlConstant>> varDomains = getVarDomains(rule, curDomains, model);

      for (int i = 0; i < headTuple.size(); i++) {
        ConcurrencyUtils.checkForInterruption();
        if (headTuple.get(i) instanceof GdlVariable) {
          GdlVariable curVar = (GdlVariable) headTuple.get(i);
          Set<GdlConstant> neededConstants = neededConstantsByForm.get(headForm).get(i);

          //Whittle these down based on what's possible throughout the rule
          Set<GdlConstant> neededAndPossibleConstants = Sets.newHashSet(neededConstants);
          neededAndPossibleConstants.retainAll(varDomains.get(curVar));
          //Relay those values back to the conjuncts in the rule body
          for (GdlLiteral conjunct : rule.getBody()) {
            somethingChanged |= addPossibleValuesToConjunct(neededAndPossibleConstants, conjunct, curVar, neededConstantsByForm, model);
          }
        }
      }
    }
    return somethingChanged;
  }

  private static boolean addPossibleValuesToConjunct(
      Set<GdlConstant> neededAndPossibleConstants,
      GdlLiteral conjunct,
      GdlVariable curVar,
      Map<SentenceForm, SetMultimap<Integer, GdlConstant>> neededConstantsByForm,
      SentenceFormModel model) throws InterruptedException {
    if (conjunct instanceof GdlSentence) {
      return addPossibleValuesToSentence(neededAndPossibleConstants, (GdlSentence) conjunct, curVar, neededConstantsByForm, model);
    } else if (conjunct instanceof GdlNot) {
      GdlSentence innerSentence = (GdlSentence) ((GdlNot) conjunct).getBody();
      return addPossibleValuesToSentence(neededAndPossibleConstants, innerSentence, curVar, neededConstantsByForm, model);
    } else if (conjunct instanceof GdlOr) {
      throw new IllegalArgumentException("The SentenceDomainModelOptimizer is not designed for game descriptions with OR. Use the DeORer.");
    } else if (conjunct instanceof GdlDistinct) {
      return false;
    } else {
      throw new IllegalArgumentException("Unexpected literal type " + conjunct.getClass() + " for literal " + conjunct);
    }
  }

  private static boolean addPossibleValuesToSentence(
      Set<GdlConstant> neededAndPossibleConstants,
      GdlSentence sentence,
      GdlVariable curVar,
      Map<SentenceForm, SetMultimap<Integer, GdlConstant>> neededConstantsByForm,
      SentenceFormModel model) throws InterruptedException {
    ConcurrencyUtils.checkForInterruption();
    boolean somethingChanged = false;

    SentenceForm form = model.getSentenceForm(sentence);
    List<GdlTerm> tuple = GdlUtils.getTupleFromSentence(sentence);
    Preconditions.checkArgument(form.getTupleSize() == tuple.size());

    for (int i = 0; i < tuple.size(); i++) {
      if (tuple.get(i) == curVar) {
        Preconditions.checkNotNull(neededConstantsByForm.get(form));
        Preconditions.checkNotNull(neededAndPossibleConstants);
        somethingChanged |= neededConstantsByForm.get(form).putAll(i, neededAndPossibleConstants);
      }
    }
    return somethingChanged;
  }

  private static Iterable<GdlSentence> getPositiveConjuncts(List<GdlLiteral> body) {
    return Iterables.transform(Iterables.filter(body, new Predicate<GdlLiteral>() {
      @Override
      public boolean apply(GdlLiteral input) {
        return input instanceof GdlSentence;
      }
    }), new Function<GdlLiteral, GdlSentence>() {
      @Override
      public GdlSentence apply(GdlLiteral input) {
        return (GdlSentence) input;
      }
    });
  }

  // Unlike getPositiveConjuncts, this also returns sentences inside NOT literals.
  private static List<GdlSentence> getAllSentencesInBody(List<GdlLiteral> body) {
    final List<GdlSentence> sentences = Lists.newArrayList();
    GdlVisitors.visitAll(body, new GdlVisitor() {
      @Override
      public void visitSentence(GdlSentence sentence) {
        sentences.add(sentence);
      }
    });
    return sentences;
  }

  private static Iterable<GdlRule> getRules(List<Gdl> description) {
    return Iterables.transform(Iterables.filter(description, new Predicate<Gdl>() {
      @Override
      public boolean apply(Gdl input) {
        return input instanceof GdlRule;
      }
    }), new Function<Gdl, GdlRule>() {
      @Override
      public GdlRule apply(Gdl input) {
        return (GdlRule) input;
      }
    });
  }

  private static final ImmutableSet<GdlConstant> ALWAYS_NEEDED_SENTENCE_NAMES = ImmutableSet.of(
      GdlPool.NEXT,
      GdlPool.GOAL,
      GdlPool.LEGAL,
      GdlPool.INIT,
      GdlPool.ROLE,
      GdlPool.BASE,
      GdlPool.INPUT,
      GdlPool.TRUE,
      GdlPool.DOES);
  private static void populateInitialNeededConstants(
      Map<SentenceForm, SetMultimap<Integer, GdlConstant>> newNeededConstantsByForm,
      Map<SentenceForm, SetMultimap<Integer, GdlConstant>> curDomains,
      SentenceFormModel model) throws InterruptedException {
    // If the term model is part of a keyword-named sentence,
    // then it is needed. This includes base and init.
    for (SentenceForm form : model.getSentenceForms()) {
      ConcurrencyUtils.checkForInterruption();

      GdlConstant name = form.getName();
      if (ALWAYS_NEEDED_SENTENCE_NAMES.contains(name)) {
        newNeededConstantsByForm.get(form).putAll(curDomains.get(form));
      }
    }

    // If the term has a constant value in some sentence in the
    // BODY of a rule, then it is needed.
    for (GdlRule rule : getRules(model.getDescription())) {
      for (GdlSentence sentence : getAllSentencesInBody(rule.getBody())) {
        addConstantsFromSentenceIfInOldDomain(newNeededConstantsByForm, curDomains, model, sentence);
      }
    }
  }

  private static void addConstantsFromSentenceIfInOldDomain(
      Map<SentenceForm, SetMultimap<Integer, GdlConstant>> newConstantsByForm,
      Map<SentenceForm, SetMultimap<Integer, GdlConstant>> oldDomain,
      SentenceFormModel model, GdlSentence sentence) throws InterruptedException {
    SentenceForm form = model.getSentenceForm(sentence);
    List<GdlTerm> tuple = GdlUtils.getTupleFromSentence(sentence);
    if (tuple.size() != form.getTupleSize()) {
      throw new IllegalStateException();
    }

    for (int i = 0; i < form.getTupleSize(); i++) {
      ConcurrencyUtils.checkForInterruption();

      GdlTerm term = tuple.get(i);
      if (term instanceof GdlConstant) {
        Set<GdlConstant> oldDomainForTerm = oldDomain.get(form).get(i);
        if (oldDomainForTerm.contains(term)) {
          newConstantsByForm.get(form).put(i, (GdlConstant) term);
        }
      }
    }
  }

  private static ImmutableSentenceDomainModel toSentenceDomainModel(
      Map<SentenceForm, SetMultimap<Integer, GdlConstant>> neededAndPossibleConstantsByForm,
      SentenceFormModel formModel) throws InterruptedException {
    Map<SentenceForm, SentenceFormDomain> domains = Maps.newHashMap();
    for (SentenceForm form : formModel.getSentenceForms()) {
      ConcurrencyUtils.checkForInterruption();
      domains.put(form, CartesianSentenceFormDomain.create(form,
          neededAndPossibleConstantsByForm.get(form)));
    }

    return ImmutableSentenceDomainModel.create(formModel, domains);
  }
}
TOP

Related Classes of org.ggp.base.util.gdl.model.SentenceDomainModelOptimizer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.