All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.ggp.base.util.gdl.model.SentenceDomainModelOptimizer Maven / Gradle / Ivy

There is a newer version: 0.0.15
Show newest version
package org.ggp.base.util.gdl.model;

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

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.ConstantChecker;
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 {
    private 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> neededAndPossibleConstantsByForm = Maps.newHashMap();
        for (SentenceForm form : oldModel.getSentenceForms()) {
            neededAndPossibleConstantsByForm.put(form, HashMultimap.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 setMultimap,
            SentenceFormDomain domain,
            SentenceForm form) {
        for (int i = 0; i < form.getTupleSize(); i++) {
            setMultimap.putAll(i, domain.getDomainForSlot(i));
        }
    }

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

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

        return retainNewDomains(curDomains, newPossibleConstantsByForm);
    }

    private static void populateInitialPossibleConstants(
            Map> newPossibleConstantsByForm,
            Map> 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> newPossibleConstantsByForm,
            Map> 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> domainsOfHeadVars = Maps.newHashMap();
            for (GdlVariable varInHead : ImmutableSet.copyOf(GdlUtils.getVariables(rule.getHead()))) {
                Set 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> newPossibleConstantsByForm) throws InterruptedException {
        boolean somethingChanged = false;
        for (SentenceForm form : newPossibleConstantsByForm.keySet()) {
            ConcurrencyUtils.checkForInterruption();
            if (form.getName() == curName) {
                SentenceForm resultingForm = form.withName(resultingName);

                SetMultimap curFormDomain = newPossibleConstantsByForm.get(form);
                SetMultimap resultingFormDomain = newPossibleConstantsByForm.get(resultingForm);

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

    private static Set getVarDomainInRuleBody(
            GdlVariable varInHead,
            GdlRule rule,
            Map> newPossibleConstantsByForm,
            Map> curDomain,
            SentenceFormModel model) {
        try {
            List> 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 getVarDomainInSentence(
            GdlVariable var,
            GdlSentence conjunct,
            Map> newPossibleConstantsByForm,
            Map> curDomain,
            SentenceFormModel model) {
        SentenceForm form = model.getSentenceForm(conjunct);
        List tuple = GdlUtils.getTupleFromSentence(conjunct);

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

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

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

        return retainNewDomains(curDomains, newNeededConstantsByForm);
    }

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

    private static boolean propagateNeededConstants(
            Map> neededConstantsByForm,
            Map> 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> neededConstantsByForm,
            Map> 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 varsInHead = ImmutableSet.copyOf(GdlUtils.getVariables(head));

            Map> varDomains = getVarDomains(rule, curDomains, model);
            for (GdlVariable var : ImmutableSet.copyOf(GdlUtils.getVariables(rule))) {
                if (!varsInHead.contains(var)) {
                    Set 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> getVarDomains(
            GdlRule rule,
            final Map> 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 iterator() {
                        throw new UnsupportedOperationException();
                    }

                    @Override
                    public Set getDomainForSlot(int slotIndex) {
                        if (!curDomains.containsKey(form)) {
                            return ImmutableSet.of();
                        }
                        return curDomains.get(form).get(slotIndex);
                    }

                    @Override
                    public Map> getDomainsForSlotGivenValuesOfOtherSlot(
                            int slotOfInterest, int inputSlot) {
                        return CartesianSentenceFormDomain.getCartesianMapForDomains(
                                getDomainForSlot(inputSlot),
                                getDomainForSlot(slotOfInterest));
                    }

                    @Override
                    public int getDomainSize() {
                        throw new UnsupportedOperationException();
                    }
                };
            }}, VarDomainOpts.INCLUDE_HEAD);
    }

    private static boolean applyRuleHeadPropagation(
            Map> neededConstantsByForm,
            Map> 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 headTuple = GdlUtils.getTupleFromSentence(head);

            Map> 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 neededConstants = neededConstantsByForm.get(headForm).get(i);

                    //Whittle these down based on what's possible throughout the rule
                    Set 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 neededAndPossibleConstants,
            GdlLiteral conjunct,
            GdlVariable curVar,
            Map> 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 neededAndPossibleConstants,
            GdlSentence sentence,
            GdlVariable curVar,
            Map> neededConstantsByForm,
            SentenceFormModel model) throws InterruptedException {
        ConcurrencyUtils.checkForInterruption();
        boolean somethingChanged = false;

        SentenceForm form = model.getSentenceForm(sentence);
        List 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), "missing form is " + form +
                        "; forms are: " + neededConstantsByForm.keySet());
                Preconditions.checkNotNull(neededAndPossibleConstants);
                somethingChanged |= neededConstantsByForm.get(form).putAll(i, neededAndPossibleConstants);
            }
        }
        return somethingChanged;
    }

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

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

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

    private static final ImmutableSet 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> newNeededConstantsByForm,
            Map> 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> newConstantsByForm,
            Map> oldDomain,
            SentenceFormModel model, GdlSentence sentence) throws InterruptedException {
        SentenceForm form = model.getSentenceForm(sentence);
        List 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 oldDomainForTerm = oldDomain.get(form).get(i);
                if (oldDomainForTerm.contains(term)) {
                    newConstantsByForm.get(form).put(i, (GdlConstant) term);
                }
            }
        }
    }

    private static ImmutableSentenceDomainModel toSentenceDomainModel(
            Map> neededAndPossibleConstantsByForm,
            SentenceDomainModel oldModel) throws InterruptedException {
        Map domains = Maps.newHashMap();
        for (SentenceForm form : oldModel.getSentenceForms()) {
            ConcurrencyUtils.checkForInterruption();
            SentenceFormDomain cartesianDomain = CartesianSentenceFormDomain.create(form,
                    neededAndPossibleConstantsByForm.get(form));
            domains.put(form, reconcileDomains(oldModel.getDomain(form), cartesianDomain));
        }

        return ImmutableSentenceDomainModel.create(oldModel, domains);
    }

    private static SentenceFormDomain reconcileDomains(
            SentenceFormDomain domain, SentenceFormDomain cartesianDomain) {
        int oldSize = domain.getDomainSize();
        int newSize = cartesianDomain.getDomainSize();
        if (newSize <= oldSize) {
            return cartesianDomain;
        } else {
            //TODO: Remove entries that contradict the Cartesian domain?
            return domain;
        }
    }

    public static SentenceDomainModel restrictDomainsUsingBasesAndInputs(
            SentenceDomainModel domainModel, ConstantChecker constantChecker) {
        Map domainsToUpdate = Maps.newHashMap();
        if (hasBasesDefined(domainModel)) {
            domainsToUpdate.putAll(getBaseDefinedDomains(constantChecker));
        }
        if (hasInputsDefined(domainModel)) {
            domainsToUpdate.putAll(getInputDefinedDomains(constantChecker));
        }
        return replaceSomeDomains(domainModel, domainsToUpdate);
    }

    private static SentenceDomainModel replaceSomeDomains(
            SentenceDomainModel domainModel,
            Map domainsToUpdate) {
        Map domains = Maps.newHashMap();
        for (SentenceForm form : domainModel.getSentenceForms()) {
            if (domainsToUpdate.containsKey(form)) {
                domains.put(form, domainsToUpdate.get(form));
            } else {
                domains.put(form, domainModel.getDomain(form));
            }
        }

        return ImmutableSentenceDomainModel.create(domainModel, domains);
    }

    private static Map getBaseDefinedDomains(
            ConstantChecker constantChecker) {
        Map baseDefinedDomains = Maps.newHashMap();
        for (SentenceForm form : constantChecker.getConstantSentenceForms()) {
            if (form.getName() == GdlPool.BASE) {
                Set baseSentences = constantChecker.getTrueSentences(form);
                addDomainWithNewName(GdlPool.TRUE, baseDefinedDomains, form, baseSentences);
                addDomainWithNewName(GdlPool.NEXT, baseDefinedDomains, form, baseSentences);
            }
        }
        return baseDefinedDomains;
    }

    public static void addDomainWithNewName(GdlConstant newName,
            Map baseDefinedDomains,
            SentenceForm form, Set baseSentences) {
        SentenceForm newNameForm = form.withName(newName);
        Set newNameSentences = baseSentences.stream()
                .map(s -> s.withName(newName))
                .collect(Collectors.toSet());
        FullSentenceFormDomain newNameDomain = FullSentenceFormDomain.create(newNameForm, newNameSentences);
        baseDefinedDomains.put(newNameForm, newNameDomain);
    }

    private static Map getInputDefinedDomains(
            ConstantChecker constantChecker) {
        Map inputDefinedDomains = Maps.newHashMap();
        for (SentenceForm form : constantChecker.getConstantSentenceForms()) {
            if (form.getName() == GdlPool.INPUT) {
                Set inputSentences = constantChecker.getTrueSentences(form);
                addDomainWithNewName(GdlPool.DOES, inputDefinedDomains, form, inputSentences);
                addDomainWithNewName(GdlPool.LEGAL, inputDefinedDomains, form, inputSentences);
            }
        }
        return inputDefinedDomains;
    }

    private static boolean hasBasesDefined(SentenceDomainModel domainModel) {
        for (SentenceForm form : domainModel.getConstantSentenceForms()) {
            if (form.getName() == GdlPool.BASE) {
                return true;
            }
        }
        return false;
    }

    private static boolean hasInputsDefined(SentenceDomainModel domainModel) {
        for (SentenceForm form : domainModel.getConstantSentenceForms()) {
            if (form.getName() == GdlPool.INPUT) {
                return true;
            }
        }
        return false;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy