org.ggp.base.util.gdl.model.assignments.IterationOrderCandidate 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.model.assignments;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.annotation.Nullable;
import org.ggp.base.util.Immutables;
import org.ggp.base.util.assignments.AssignmentStrategy;
import org.ggp.base.util.assignments.ComplexAssignmentIterationPlan;
import org.ggp.base.util.assignments.DependentAssignmentStrategy;
import org.ggp.base.util.assignments.NewAssignmentIterationPlan;
import org.ggp.base.util.assignments.SimpleAssignmentStrategy;
import org.ggp.base.util.gdl.GdlUtils;
import org.ggp.base.util.gdl.grammar.GdlConstant;
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.SentenceForm;
import org.ggp.base.util.gdl.transforms.ConstantChecker;
import com.google.common.base.Preconditions;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
//This class has a natural ordering that is inconsistent with equals.
public class IterationOrderCandidate implements Comparable {
//Information specific to this ordering
private List sourceConjunctIndices; //Which conjuncts are we using as sources, and in what order?
private List varOrdering; //In what order do we assign variables?
private List functionalConjunctIndices; //Same size as varOrdering
//Index of conjunct if functional, -1 otherwise
private List varSources; //Same size as varOrdering
//For each variable: Which source conjunct
//originally contributes it? -1 if none
//Becomes sourceResponsibleForVar
//Information shared by the orderings
//Presumably, this will also be used to construct the iterator to be used...
private List varsToAssign;
private List sourceConjunctCandidates;
private List sourceConjunctSizes; //same indexing as candidates
private List functionalSentences;
private List functionalSentencesInfo; //Indexing same as functionalSentences
private Map varDomainSizes;
/**
* This constructor is for creating the start node of the
* search. No part of the ordering is specified.
*/
public IterationOrderCandidate(
List varsToAssign,
List sourceConjunctCandidates,
List sourceConjunctSizes,
List functionalSentences,
List functionalSentencesInfo,
Map varDomainSizes) {
sourceConjunctIndices = new ArrayList();
varOrdering = new ArrayList();
functionalConjunctIndices = new ArrayList();
varSources = new ArrayList();
this.varsToAssign = varsToAssign;
this.sourceConjunctCandidates = sourceConjunctCandidates;
this.sourceConjunctSizes = sourceConjunctSizes;
this.functionalSentences = functionalSentences;
this.functionalSentencesInfo = functionalSentencesInfo;
this.varDomainSizes = varDomainSizes;
}
public List getFunctionalConjuncts() {
//Returns, for each var, the conjunct defining it (if any)
List functionalConjuncts = new ArrayList(functionalConjunctIndices.size());
for(int index : functionalConjunctIndices) {
if(index == -1)
functionalConjuncts.add(null);
else
functionalConjuncts.add(functionalSentences.get(index));
}
return functionalConjuncts;
}
public List getSourceConjuncts() {
//These are the selected source conjuncts, not just the candidates.
List sourceConjuncts = new ArrayList(sourceConjunctIndices.size());
for(int index : sourceConjunctIndices) {
sourceConjuncts.add(sourceConjunctCandidates.get(index));
}
return sourceConjuncts;
}
public List getVariableOrdering() {
return varOrdering;
}
/**
* This constructor is for "completing" the ordering by
* adding all remaining variables, in some arbitrary order.
* No source conjuncts or functions are added.
*/
public IterationOrderCandidate(
IterationOrderCandidate parent) {
//Shared rules
this.varsToAssign = parent.varsToAssign;
this.sourceConjunctCandidates = parent.sourceConjunctCandidates;
this.sourceConjunctSizes = parent.sourceConjunctSizes;
this.functionalSentences = parent.functionalSentences;
this.functionalSentencesInfo = parent.functionalSentencesInfo;
this.varDomainSizes = parent.varDomainSizes;
//Individual rules:
//We can share this because we won't be adding to it
sourceConjunctIndices = parent.sourceConjunctIndices;
//These others we'll be adding to
varOrdering = new ArrayList(parent.varOrdering);
functionalConjunctIndices = new ArrayList(parent.functionalConjunctIndices);
varSources = new ArrayList(parent.varSources);
//Fill out the ordering with all remaining variables: Easy enough
for(GdlVariable var : varsToAssign) {
if(!varOrdering.contains(var)) {
varOrdering.add(var);
functionalConjunctIndices.add(-1);
varSources.add(-1);
}
}
}
/**
* This constructor is for adding a source conjunct to an
* ordering.
* @param i The index of the source conjunct being added.
*/
public IterationOrderCandidate(
IterationOrderCandidate parent, int i) {
//Shared rules:
this.varsToAssign = parent.varsToAssign;
this.sourceConjunctCandidates = parent.sourceConjunctCandidates;
this.sourceConjunctSizes = parent.sourceConjunctSizes;
this.functionalSentences = parent.functionalSentences;
this.functionalSentencesInfo = parent.functionalSentencesInfo;
this.varDomainSizes = parent.varDomainSizes;
//Individual rules:
sourceConjunctIndices = new ArrayList(parent.sourceConjunctIndices);
varOrdering = new ArrayList(parent.varOrdering);
functionalConjunctIndices = new ArrayList(parent.functionalConjunctIndices);
varSources = new ArrayList(parent.varSources);
//Add the new source conjunct
sourceConjunctIndices.add(i);
GdlSentence sourceConjunctCandidate = sourceConjunctCandidates.get(i);
List varsFromConjunct = GdlUtils.getVariables(sourceConjunctCandidate);
//Ignore both previously added vars and duplicates
//Oh, but we need to be careful here, at some point.
//i.e., what if there are multiple of the same variable
//in a single statement?
//That should probably be handled later.
for(GdlVariable var : varsFromConjunct) {
if(!varOrdering.contains(var)) {
varOrdering.add(var);
varSources.add(i);
functionalConjunctIndices.add(-1);
}
}
}
/**
* This constructor is for adding a function to the ordering.
*/
public IterationOrderCandidate(
IterationOrderCandidate parent,
GdlSentence functionalSentence,
int functionalSentenceIndex, GdlVariable functionOutput) {
//Shared rules:
this.varsToAssign = parent.varsToAssign;
this.sourceConjunctCandidates = parent.sourceConjunctCandidates;
this.sourceConjunctSizes = parent.sourceConjunctSizes;
this.functionalSentences = parent.functionalSentences;
this.functionalSentencesInfo = parent.functionalSentencesInfo;
this.varDomainSizes = parent.varDomainSizes;
//Individual rules:
sourceConjunctIndices = new ArrayList(parent.sourceConjunctIndices);
varOrdering = new ArrayList(parent.varOrdering);
functionalConjunctIndices = new ArrayList(parent.functionalConjunctIndices);
varSources = new ArrayList(parent.varSources);
//And we add the function
List varsInFunction = GdlUtils.getVariables(functionalSentence);
//First, add the remaining arguments
for(GdlVariable var : varsInFunction) {
if(!varOrdering.contains(var) && !var.equals(functionOutput) && varsToAssign.contains(var)) {
varOrdering.add(var);
functionalConjunctIndices.add(-1);
varSources.add(-1);
}
}
//Then the output
varOrdering.add(functionOutput);
functionalConjunctIndices.add(functionalSentenceIndex);
varSources.add(-1);
}
public long getHeuristicValue() {
long heuristic = 1;
for(int sourceIndex : sourceConjunctIndices) {
heuristic *= sourceConjunctSizes.get(sourceIndex);
}
for(int v = 0; v < varOrdering.size(); v++) {
if(varSources.get(v) == -1 && functionalConjunctIndices.get(v) == -1) {
//It's not set by a source conjunct or a function
heuristic *= varDomainSizes.get(varOrdering.get(v));
}
}
//We want complete orderings to show up faster
//so we add a little incentive to pick them
//Add 1 to the value of non-complete orderings
if(varOrdering.size() < varsToAssign.size())
heuristic++;
// System.out.println("Heuristic value is " + heuristic + " with functionalConjunctIndices " + functionalConjunctIndices);
return heuristic;
}
public boolean isComplete() {
return varOrdering.containsAll(varsToAssign);
}
public List getChildren(boolean analyticFunctionOrdering) {
List allChildren = new ArrayList();
allChildren.addAll(getSourceConjunctChildren());
allChildren.addAll(getFunctionAddedChildren(analyticFunctionOrdering));
// System.out.println("Number of children being added: " + allChildren.size());
return allChildren;
}
private List getSourceConjunctChildren() {
List children = new ArrayList();
//If we are already using functions, short-circuit to cut off
//repetition of the search space
for(int index : functionalConjunctIndices) {
if(index != -1) {
return Collections.emptyList();
}
}
//This means we want a reference to the original list of conjuncts.
int lastSourceConjunctIndex = -1;
if(!sourceConjunctIndices.isEmpty())
lastSourceConjunctIndex = sourceConjunctIndices.get(sourceConjunctIndices.size() - 1);
for(int i = lastSourceConjunctIndex + 1; i < sourceConjunctCandidates.size(); i++) {
children.add(new IterationOrderCandidate(this, i));
}
return children;
}
private List getFunctionAddedChildren(boolean analyticFunctionOrdering) {
//We can't just add those functions that
//are "ready" to be added. We should be adding all those variables
//"leading up to" the functions and then applying the functions.
//We can even take this one step further by only adding one child
//per remaining constant function; we choose as our function output the
//variable that is a candidate for functionhood that has the
//largest domain, or one that is tied for largest.
//New criterion: Must also NOT be in preassignment.
List children = new ArrayList();
//It would be really nice here to just analytically choose
//the set of functions we're going to use.
//Here's one approach for doing that:
//For each variable, get a list of the functions that could
//potentially produce it.
//For all the variables with no functions, add them.
//Then repeatedly find the function with the fewest
//number of additional variables (hopefully 0!) needed to
//specify it and add it as a function.
//The goal here is not to be optimal, but to be efficient!
//Certain games (e.g. Pentago) break the old complete search method!
//TODO: Eventual possible optimization here:
//If something is dependent on a connected component that it is
//not part of, wait until the connected component is resolved
//(or something like that...)
if(analyticFunctionOrdering && functionalSentencesInfo.size() > 8) {
//For each variable, a list of functions
//(refer to functions by their indices)
//and the set of outstanding vars they depend on...
Map> functionsProducingVars = new HashMap>();
//We start by adding to the varOrdering the vars not produced by functions
//First, we have to find them
for(int i = 0; i < functionalSentencesInfo.size(); i++) {
GdlSentence functionalSentence = functionalSentences.get(i);
FunctionInfo functionInfo = functionalSentencesInfo.get(i);
Set producibleVars = functionInfo.getProducibleVars(functionalSentence);
for(GdlVariable producibleVar : producibleVars) {
if(!functionsProducingVars.containsKey(producibleVar))
functionsProducingVars.put(producibleVar, new HashSet());
functionsProducingVars.get(producibleVar).add(i);
}
}
//Non-producible vars get iterated over before we start
//deciding which functions to add
for(GdlVariable var : varsToAssign) {
if(!varOrdering.contains(var)) {
if(!functionsProducingVars.containsKey(var)) {
//Add var to the ordering
varOrdering.add(var);
functionalConjunctIndices.add(-1);
varSources.add(-1);
}
}
}
//Map is from potential set of dependencies to function indices
Map, Set> functionsHavingDependencies = new HashMap, Set>();
//Create this map...
for(int i = 0; i < functionalSentencesInfo.size(); i++) {
GdlSentence functionalSentence = functionalSentences.get(i);
FunctionInfo functionInfo = functionalSentencesInfo.get(i);
Set producibleVars = functionInfo.getProducibleVars(functionalSentence);
Set allVars = new HashSet(GdlUtils.getVariables(functionalSentence));
//Variables already in varOrdering don't go in dependents list
producibleVars.removeAll(varOrdering);
allVars.removeAll(varOrdering);
for(GdlVariable producibleVar : producibleVars) {
Set dependencies = new HashSet();
dependencies.addAll(allVars);
dependencies.remove(producibleVar);
if(!functionsHavingDependencies.containsKey(dependencies))
functionsHavingDependencies.put(dependencies, new HashSet());
functionsHavingDependencies.get(dependencies).add(i);
}
}
//Now, we can keep creating functions to generate the remaining variables
while(varOrdering.size() < varsToAssign.size()) {
if(functionsHavingDependencies.isEmpty())
throw new RuntimeException("We should not run out of functions we could use");
//Find the smallest set of dependencies
Set dependencySetToUse = null;
int smallestDependencySetSize = Integer.MAX_VALUE;
for(Set dependencySet : functionsHavingDependencies.keySet()) {
if(dependencySet.size() < smallestDependencySetSize) {
smallestDependencySetSize = dependencySet.size();
dependencySetToUse = dependencySet;
}
}
if (dependencySetToUse == null) {
dependencySetToUse = ImmutableSet.of();
}
//See if any of the functions are applicable
Set functions = functionsHavingDependencies.get(dependencySetToUse);
int functionToUse = -1;
GdlVariable varProduced = null;
for (int function : functions) {
GdlSentence functionalSentence = functionalSentences.get(function);
FunctionInfo functionInfo = functionalSentencesInfo.get(function);
Set producibleVars = functionInfo.getProducibleVars(functionalSentence);
producibleVars.removeAll(dependencySetToUse);
producibleVars.removeAll(varOrdering);
if(!producibleVars.isEmpty()) {
functionToUse = function;
varProduced = producibleVars.iterator().next();
break;
}
}
if(functionToUse == -1) {
//None of these functions were actually useful now?
//Dump the dependency set
functionsHavingDependencies.remove(dependencySetToUse);
} else {
//Apply the function
//1) Add the remaining dependencies as iterated variables
for(GdlVariable var : dependencySetToUse) {
varOrdering.add(var);
functionalConjunctIndices.add(-1);
varSources.add(-1);
}
//2) Add the function's produced variable (varProduced)
varOrdering.add(varProduced);
functionalConjunctIndices.add(functionToUse);
varSources.add(-1);
//3) Remove all vars added this way from all dependency sets
Set addedVars = new HashSet();
addedVars.addAll(dependencySetToUse);
addedVars.add(varProduced);
//Tricky, because we have to merge sets
//Easier to use a new map
Map, Set> newFunctionsHavingDependencies = new HashMap, Set>();
for(Entry, Set> entry : functionsHavingDependencies.entrySet()) {
Set newKey = new HashSet(entry.getKey());
newKey.removeAll(addedVars);
if(!newFunctionsHavingDependencies.containsKey(newKey))
newFunctionsHavingDependencies.put(newKey, new HashSet());
newFunctionsHavingDependencies.get(newKey).addAll(entry.getValue());
}
functionsHavingDependencies = newFunctionsHavingDependencies;
//4) Remove this function from the lists?
for(Set functionSet : functionsHavingDependencies.values())
functionSet.remove(functionToUse);
}
}
//Now we need to actually return the ordering in a list
//Here's the quick way to do that...
//(since we've added all the new stuff to ourself already)
return Collections.singletonList(new IterationOrderCandidate(this));
} else {
//Let's try a new technique for restricting the space of possibilities...
//We already have an ordering on the functions
//Let's try to constrain things to that order
//Namely, if i varsProducedByFunctions = new HashSet();
for (int i = 0; i < functionalConjunctIndices.size(); i++) {
if (functionalConjunctIndices.get(i) != -1) {
varsProducedByFunctions.add(varOrdering.get(i));
}
}
for (int i = 0; i < functionalSentencesInfo.size(); i++) {
GdlSentence functionalSentence = functionalSentences.get(i);
FunctionInfo functionInfo = functionalSentencesInfo.get(i);
if (i < lastFunctionUsedIndex) {
//We need to figure out whether i could use any of the
//vars we're producing with functions
//TODO: Try this with a finer grain
//i.e., see if i needs a var from a function that is after
//it, not one that might be before it
List varsInSentence = GdlUtils.getVariables(functionalSentence);
if (Collections.disjoint(varsInSentence, varsProducedByFunctions)) {
continue;
}
}
//What is the best variable to grab from this form, if there are any?
GdlVariable bestVariable = getBestVariable(functionalSentence, functionInfo);
if (bestVariable == null) {
continue;
}
IterationOrderCandidate newCandidate =
new IterationOrderCandidate(this, functionalSentence, i, bestVariable);
children.add(newCandidate);
}
//If there are no more functions to add, add the completed version
if (children.isEmpty()) {
children.add(new IterationOrderCandidate(this));
}
return children;
}
}
private GdlVariable getBestVariable(GdlSentence functionalSentence,
FunctionInfo functionInfo) {
//If all the variables that can be set by the functional sentence are in
//the varOrdering, we return null. Otherwise, we return one of
//those with the largest domain.
//The FunctionInfo is sentence-independent, so we need the context
//of the sentence (which has variables in it).
List tuple = GdlUtils.getTupleFromSentence(functionalSentence);
List dependentSlots = functionInfo.getDependentSlots();
if(tuple.size() != dependentSlots.size())
throw new RuntimeException("Mismatched sentence " + functionalSentence + " and constant form " + functionInfo);
//TODO: Should "candidateVars" here just be a call to getProducibleVars?
Set candidateVars = new HashSet();
for(int i = 0; i < tuple.size(); i++) {
GdlTerm term = tuple.get(i);
if(term instanceof GdlVariable && dependentSlots.get(i)
&& !varOrdering.contains(term)
&& varsToAssign.contains(term))
candidateVars.add((GdlVariable) term);
}
//TODO: Should we just generate the candidate vars with a call to getProducibleVars?
Set producibleVars = functionInfo.getProducibleVars(functionalSentence);
candidateVars.retainAll(producibleVars);
//Now we look at the domains, trying to find the largest
GdlVariable bestVar = null;
int bestDomainSize = 0;
for(GdlVariable var : candidateVars) {
int domainSize = varDomainSizes.get(var);
if(domainSize > bestDomainSize) {
bestVar = var;
bestDomainSize = domainSize;
}
}
return bestVar; //null if none are usable
}
//This class has a natural ordering that is inconsistent with equals.
@Override
public int compareTo(IterationOrderCandidate o) {
return Long.compare(getHeuristicValue(), o.getHeuristicValue());
}
@Override
public String toString() {
return varOrdering.toString() + " with sources " + getSourceConjuncts().toString() + "; functional?: " + functionalConjunctIndices + "; domain sizes are " + this.varDomainSizes;
}
public NewAssignmentIterationPlan toAssignmentIterationPlan(Map> varDomains,
ConstantChecker constantChecker) {
Preconditions.checkState(isComplete());
List assignmentOrder = varOrdering;
List strategies = Lists.newArrayList();
//What gets shared?
//getSourceConjuncts -> defined by sourceConjunctIndices
//getFunctionalConjuncts -> defined by functionalConjunctIndices
//What will our strategies look like?
//One per source conjunct
//One per function
//One per standard iteration
for (GdlSentence sourceConjunct : getSourceConjuncts()) {
strategies.add(toStrategyFromSource(sourceConjunct, constantChecker));
}
for (int i = 0; i < varOrdering.size(); i++) {
GdlVariable variable = varOrdering.get(i);
if (varSources.get(i) == -1) {
//Functional or no?
int functionalConjunctIndex = functionalConjunctIndices.get(i);
if (functionalConjunctIndex >= 0) {
//Create strategy for the functional index
// strategies.add(DependentAssignmentStrategy.create(
// dependentIndices,
// ImmutableList.of(i),
// contents));
strategies.add(toStrategyFromFunction(i, functionalConjunctIndex, constantChecker));
} else {
//Just a naive iteration strategy
strategies.add(SimpleAssignmentStrategy.create(
ImmutableList.of(i),
varDomains.get(variable).stream()
.map(ImmutableList::of)
.collect(Immutables.collectList())));
}
}
}
//TODO: Add validation that this will give us stuff in the right order...
return ComplexAssignmentIterationPlan.create(assignmentOrder, strategies);
}
private AssignmentStrategy toStrategyFromFunction(int varIndex,
int functionalConjunctIndex, ConstantChecker constantChecker) {
GdlSentence functionalSentence = functionalSentences.get(functionalConjunctIndex);
FunctionInfo functionInfo = functionalSentencesInfo.get(functionalConjunctIndex);
// functionInfo.getValueMap(varIndex); //These might need reordering...
List definedIndices = ImmutableList.of(varIndex);
List dependentIndices = GdlUtils.getVariables(functionalSentence).stream()
.map(varOrdering::indexOf)
.filter(i -> !i.equals(varIndex))
.sorted()
.distinct()
.collect(Immutables.collectList());
Preconditions.checkState(constantChecker.getConstantSentenceForms().contains(functionInfo.getSentenceForm()),
"Not implemented yet for non-constant sentence forms");
Set sentencesForForm = constantChecker.getTrueSentences(functionInfo.getSentenceForm());
ListMultimap, List> contents = getContents(
functionalSentence, dependentIndices, definedIndices, sentencesForForm);
return DependentAssignmentStrategy.create(dependentIndices, definedIndices, contents.asMap());
}
private AssignmentStrategy toStrategyFromSource(
GdlSentence sourceConjunct,
ConstantChecker constantChecker) {
int sourceIndex = sourceConjunctCandidates.indexOf(sourceConjunct);
List dependentIndices = Lists.newArrayList();
List definedIndices = Lists.newArrayList();
//... so we need to know which variable indices the conjunct is involved with
Set varsInSource = Sets.newHashSet(GdlUtils.getVariables(sourceConjunct));
for (int i = 0; i < varOrdering.size(); i++) {
if (varsInSource.contains(varOrdering.get(i))) {
if (varSources.get(i) == sourceIndex) {
definedIndices.add(i);
} else {
dependentIndices.add(i);
}
}
}
SentenceForm sourceConjunctForm = constantChecker.getSentenceFormModel().getSentenceForm(sourceConjunct);
Preconditions.checkState(constantChecker.getConstantSentenceForms().contains(sourceConjunctForm),
"Haven't yet implemented passing of non-constant sources");
Set sentencesForForm = constantChecker.getTrueSentences(sourceConjunctForm);
ListMultimap, List> contents = getContents(
sourceConjunct, dependentIndices, definedIndices,
sentencesForForm);
return DependentAssignmentStrategy.create(dependentIndices, definedIndices, contents.asMap());
}
private ListMultimap, List> getContents(
GdlSentence sourceConjunct, List dependentIndices,
List definedIndices, Set sentencesForForm) {
ListMultimap, List> contents = ArrayListMultimap.create();
for (GdlSentence sentence : sentencesForForm) {
@Nullable Map assignment = GdlUtils.getAssignmentMakingLeftIntoGroundRight(sourceConjunct, sentence);
if (assignment == null) {
continue;
}
List dependents = dependentIndices.stream()
.map(varOrdering::get)
.map(assignment::get)
.collect(Immutables.collectList());
List defined = definedIndices.stream()
.map(varOrdering::get)
.map(assignment::get)
.collect(Immutables.collectList());
contents.put(dependents, defined);
}
return contents;
}
}