org.ggp.base.util.gdl.transforms.VariableConstrainer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of alloy-ggp-base Show documentation
Show all versions of alloy-ggp-base Show documentation
A modified version of the GGP-Base library for Alloy.
The newest version!
package org.ggp.base.util.gdl.transforms;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
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.GdlFunction;
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.ImmutableSentenceFormModel;
import org.ggp.base.util.gdl.model.SentenceForm;
import org.ggp.base.util.gdl.model.SentenceFormModel;
import org.ggp.base.util.gdl.model.SentenceFormModelFactory;
import org.ggp.base.util.gdl.model.SimpleSentenceForm;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Sets;
public class VariableConstrainer {
private VariableConstrainer() {
}
/**
* Modifies a GDL description by replacing all rules in which variables could be bound to
* functions, so that the new rules will only bind constants to variables. Also automatically
* removes GdlOrs from the rules using the DeORer.
*
* Not guaranteed to work if the GDL is written strangely, such as when they include rules
* in which certain conjuncts are never or always true. Not guaranteed to work when rules
* are unsafe, i.e., they contain variables only appearing in the head, a negated literal,
* and/or a distinct literal. (In fact, this can be a good way to test for GDL errors, which
* often result in exceptions.)
*
* Not guaranteed to finish in a reasonable amount of time in pathological cases, where the
* number of possible functional structures is prohibitively large.
*
* @param description A GDL game description.
* @return A modified version of the same game.
*/
public static List replaceFunctionValuedVariables(List description) throws InterruptedException {
description = GdlCleaner.run(description);
description = DeORer.run(description);
SentenceFormModel model = SentenceFormModelFactory.create(description);
// Find "ambiguities" between sentence rules: "If we have sentence form X
// with variables in slots [...], it could be aliased to sentence form Y instead"
ListMultimap ambiguitiesByOriginalForm = getAmbiguitiesByOriginalForm(model);
if (ambiguitiesByOriginalForm.isEmpty()) {
return description;
}
List expandedRules = applyAmbiguitiesToRules(description, ambiguitiesByOriginalForm, model);
return removeDuplicates(cleanUpIrrelevantRules(expandedRules));
}
private static List removeDuplicates(List rules) {
Set alreadyInRules = Sets.newHashSet();
List newRules = Lists.newArrayList();
for (Gdl rule : rules) {
if (alreadyInRules.contains(rule)) {
continue;
}
newRules.add(rule);
alreadyInRules.add(rule);
}
return newRules;
}
/**
* An ambiguity represents a particular relationship between two
* sentence forms. It says that if sentence form "original" appears
* in a rule and has GdlVariables in particular slots, it could be
* equivalent to the sentence form "replacement" if functions are
* assigned to its variables.
*
* The goal of this transformation is to make it possible for users
* of the game description to treat it as if functions could not be
* assigned to variables. This requires adding or modifying rules to
* account for the extra cases.
*/
private static class Ambiguity {
private final SentenceForm original;
private final ImmutableMap replacementsByOriginalTupleIndex;
private final SentenceForm replacement;
private Ambiguity(SentenceForm original,
ImmutableMap replacementsByOriginalTupleIndex,
SentenceForm replacement) {
Preconditions.checkNotNull(original);
Preconditions.checkNotNull(replacementsByOriginalTupleIndex);
Preconditions.checkArgument(!replacementsByOriginalTupleIndex.isEmpty());
Preconditions.checkNotNull(replacement);
for (int varIndex : replacementsByOriginalTupleIndex.keySet()) {
Preconditions.checkElementIndex(varIndex, original.getTupleSize());
}
this.original = original;
this.replacementsByOriginalTupleIndex = replacementsByOriginalTupleIndex;
this.replacement = replacement;
}
public static Ambiguity create(SentenceForm original,
Map replacementsByOriginalTupleIndex,
SentenceForm replacement) {
return new Ambiguity(original,
ImmutableMap.copyOf(replacementsByOriginalTupleIndex),
replacement);
}
public SentenceForm getOriginal() {
return original;
}
public SentenceForm getReplacement() {
return replacement;
}
@Override
public String toString() {
return "Ambiguity [original=" + original
+ ", replacementsByOriginalTupleIndex="
+ replacementsByOriginalTupleIndex + ", replacement="
+ replacement + "]";
}
/**
* Returns true iff the given sentence could correspond to a
* sentence of the replacement form, for some variable assignment.
*/
public boolean applies(GdlSentence sentence) {
if (!original.matches(sentence)) {
return false;
}
List tuple = GdlUtils.getTupleFromSentence(sentence);
for (int varIndex : replacementsByOriginalTupleIndex.keySet()) {
if (!(tuple.get(varIndex) instanceof GdlVariable)) {
return false;
}
}
return true;
}
public Map getReplacementAssignment(GdlSentence sentence, UnusedVariableGenerator varGen) {
Preconditions.checkArgument(applies(sentence));
Map assignment = Maps.newHashMap();
List tuple = GdlUtils.getTupleFromSentence(sentence);
for (int varIndex : replacementsByOriginalTupleIndex.keySet()) {
GdlFunction function = replacementsByOriginalTupleIndex.get(varIndex);
GdlFunction replacementFunction = varGen.replaceVariablesAndConstants(function);
assignment.put((GdlVariable) tuple.get(varIndex), replacementFunction);
}
return assignment;
}
}
private static ListMultimap getAmbiguitiesByOriginalForm(
SentenceFormModel model) throws InterruptedException {
ListMultimap result = ArrayListMultimap.create();
ListMultimap formsByName = getFormsByName(model);
for (GdlConstant name : formsByName.keySet()) {
List forms = formsByName.get(name);
for (SentenceForm form : forms) {
result.putAll(form, getAmbiguities(form, forms));
}
}
Set allForms = ImmutableSet.copyOf(formsByName.values());
for (Ambiguity ambiguity : result.values()) {
Preconditions.checkState(allForms.contains(ambiguity.getOriginal()));
Preconditions.checkState(allForms.contains(ambiguity.getReplacement()));
}
return result;
}
private static ListMultimap getFormsByName(
SentenceFormModel model) {
return Multimaps.index(getAllSentenceForms(model), new Function() {
@Override
public GdlConstant apply(SentenceForm input) {
return input.getName();
}
});
}
private static Set getAllSentenceForms(SentenceFormModel model) {
//The model may only have sentence forms for sentences that can actually be
//true. It may be missing sentence forms that are used in the rules only,
//with no actual corresponding sentences. We want to make sure these are
//included.
final Set forms = Sets.newHashSet(model.getSentenceForms());
GdlVisitors.visitAll(model.getDescription(), new GdlVisitor() {
@Override
public void visitSentence(GdlSentence sentence) {
forms.add(SimpleSentenceForm.create(sentence));
}
});
return forms;
}
private static List getAmbiguities(
SentenceForm original, List forms) throws InterruptedException {
List result = Lists.newArrayList();
for (SentenceForm form : forms) {
if (form == original) {
continue;
}
Optional ambiguity = findAmbiguity(original, form);
if (ambiguity.isPresent()) {
result.add(ambiguity.get());
}
}
return result;
}
private static Optional findAmbiguity(SentenceForm original,
SentenceForm replacement) throws InterruptedException {
Preconditions.checkArgument(original.getName() == replacement.getName());
Preconditions.checkArgument(!original.equals(replacement));
ConcurrencyUtils.checkForInterruption();
Map replacementsByOriginalTupleIndex = Maps.newHashMap();
//Make the arguments ?v0, ?v1, ?v2, ... so we can find the tuple indices easily
GdlSentence originalSentence =
original.getSentenceFromTuple(getNumberedTuple(original.getTupleSize()));
GdlSentence replacementSentence =
replacement.getSentenceFromTuple(getNumberedTuple(replacement.getTupleSize()));
boolean success = findAmbiguity(originalSentence.getBody(), replacementSentence.getBody(),
replacementsByOriginalTupleIndex);
if (success) {
return Optional.of(Ambiguity.create(original, replacementsByOriginalTupleIndex, replacement));
} else {
return Optional.absent();
}
}
private static boolean findAmbiguity(List originalBody,
List replacementBody, Map replacementsByOriginalTupleIndex) throws InterruptedException {
if (originalBody.size() != replacementBody.size()) {
return false;
}
for (int i = 0; i < originalBody.size(); i++) {
ConcurrencyUtils.checkForInterruption();
GdlTerm originalTerm = originalBody.get(i);
GdlTerm replacementTerm = replacementBody.get(i);
if (replacementTerm instanceof GdlVariable) {
if (!(originalTerm instanceof GdlVariable)) {
return false;
}
} else if (replacementTerm instanceof GdlFunction) {
if (originalTerm instanceof GdlVariable) {
int varIndex = Integer.valueOf(originalTerm.toString().replace("?v", ""));
replacementsByOriginalTupleIndex.put(varIndex, (GdlFunction) replacementTerm);
} else if (originalTerm instanceof GdlFunction) {
GdlFunction originalFunction = (GdlFunction) originalTerm;
GdlFunction replacementFunction = (GdlFunction) replacementTerm;
if (originalFunction.getName() != replacementFunction.getName()) {
return false;
}
boolean successSoFar = findAmbiguity(originalFunction.getBody(),
replacementFunction.getBody(),
replacementsByOriginalTupleIndex);
if (!successSoFar) {
return false;
}
} else {
throw new RuntimeException();
}
} else {
throw new RuntimeException();
}
}
return true;
}
private static List getNumberedTuple(int tupleSize) {
List result = Lists.newArrayList();
for (int i = 0; i < tupleSize; i++) {
result.add(GdlPool.getVariable("?v" + Integer.toString(i)));
}
return result;
}
private static List applyAmbiguitiesToRules(List description,
ListMultimap ambiguitiesByOriginalForm,
SentenceFormModel model) throws InterruptedException {
ImmutableList.Builder result = ImmutableList.builder();
for (Gdl gdl : description) {
if (gdl instanceof GdlRule) {
result.addAll(applyAmbiguities((GdlRule) gdl, ambiguitiesByOriginalForm, model));
} else {
result.add(gdl);
}
}
return result.build();
}
private static List applyAmbiguities(GdlRule originalRule,
ListMultimap ambiguitiesByOriginalForm,
SentenceFormModel model) throws InterruptedException {
List rules = Lists.newArrayList(originalRule);
//Each literal can potentially multiply the number of rules we have, so
//we apply each literal separately to the entire list of rules so far.
for (GdlLiteral literal : Iterables.concat(ImmutableSet.of(originalRule.getHead()),
originalRule.getBody())) {
List newRules = Lists.newArrayList();
for (GdlRule rule : rules) {
Preconditions.checkArgument(originalRule.arity() == rule.arity());
newRules.addAll(applyAmbiguitiesForLiteral(literal, rule, ambiguitiesByOriginalForm, model));
}
rules = newRules;
}
return rules;
}
private static List applyAmbiguitiesForLiteral(
GdlLiteral literal, GdlRule rule,
ListMultimap ambiguitiesByOriginalForm,
SentenceFormModel model) throws InterruptedException {
ConcurrencyUtils.checkForInterruption();
List results = Lists.newArrayList(rule);
UnusedVariableGenerator varGen = getVariableGenerator(rule);
if (literal instanceof GdlSentence) {
GdlSentence sentence = (GdlSentence) literal;
SentenceForm form = model.getSentenceForm(sentence);
for (Ambiguity ambiguity : ambiguitiesByOriginalForm.get(form)) {
ConcurrencyUtils.checkForInterruption();
if (ambiguity.applies(sentence)) {
Map replacementAssignment = ambiguity.getReplacementAssignment(sentence, varGen);
GdlRule newRule = CommonTransforms.replaceVariables(rule, replacementAssignment);
results.add(newRule);
}
}
} else if (literal instanceof GdlNot) {
// Do nothing. Variables must appear in a positive literal in the
// rule, and will be handled there.
} else if (literal instanceof GdlOr) {
throw new RuntimeException("ORs should have been removed");
} else if (literal instanceof GdlDistinct) {
// Do nothing
}
return results;
}
private abstract static class UnusedVariableGenerator {
public GdlFunction replaceVariablesAndConstants(GdlFunction function) {
Map assignment = Maps.newHashMap();
final Set termsToReplace = Sets.newHashSet();
GdlVisitors.visitAll(function, new GdlVisitor() {
@Override
public void visitConstant(GdlConstant constant) {
termsToReplace.add(constant);
}
@Override
public void visitVariable(GdlVariable variable) {
termsToReplace.add(variable);
}
});
for (GdlVariable var : GdlUtils.getVariables(function)) {
assignment.put(var, getUnusedVariable());
}
return (GdlFunction) CommonTransforms.replaceVariables(function, assignment);
}
protected abstract GdlVariable getUnusedVariable();
}
private static UnusedVariableGenerator getVariableGenerator(final GdlRule rule) {
//Not thread-safe
return new UnusedVariableGenerator() {
private int count = 1;
private final Set originalVarsFromRule =
ImmutableSet.copyOf(GdlUtils.getVariables(rule));
@Override
public GdlVariable getUnusedVariable() {
GdlVariable curVar = GdlPool.getVariable("?a" + count);
count++;
while (originalVarsFromRule.contains(curVar)) {
curVar = GdlPool.getVariable("?a" + count);
count++;
}
return curVar;
}
};
}
/**
* Removes rules with sentences with empty domains. These simply won't have
* sentence forms in the generated sentence model, so this is fairly easy.
* @throws InterruptedException
*/
private static List cleanUpIrrelevantRules(List expandedRules) throws InterruptedException {
final ImmutableSentenceFormModel model = SentenceFormModelFactory.create(expandedRules);
return ImmutableList.copyOf(Collections2.filter(expandedRules, new Predicate() {
@Override
public boolean apply(Gdl input) {
if (!(input instanceof GdlRule)) {
// If it's not a rule, leave it in
return true;
}
GdlRule rule = (GdlRule) input;
// Used just as a boolean we can change from the inner class
final AtomicBoolean shouldRemove = new AtomicBoolean(false);
GdlVisitors.visitAll(rule, new GdlVisitor() {
@Override
public void visitSentence(GdlSentence sentence) {
SentenceForm form = model.getSentenceForm(sentence);
if (!model.getSentenceForms().contains(form)) {
shouldRemove.set(true);
}
}
});
return !shouldRemove.get();
}
}));
}
}