package util.gdl.transforms;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import util.gdl.grammar.Gdl;
import util.gdl.grammar.GdlConstant;
import util.gdl.grammar.GdlDistinct;
import util.gdl.grammar.GdlLiteral;
import util.gdl.grammar.GdlNot;
import util.gdl.grammar.GdlProposition;
import util.gdl.grammar.GdlRelation;
import util.gdl.grammar.GdlRule;
import util.gdl.grammar.GdlSentence;
import util.gdl.grammar.GdlVariable;
import util.gdl.model.SentenceModel;
import util.gdl.model.SentenceModel.SentenceForm;
import util.propnet.factory.Assignments;
import util.propnet.factory.Assignments.AssignmentIterator;
import util.propnet.factory.Assignments.ConstantForm;
import util.statemachine.Role;
public class ConstantFinder {
/**
* Produces a new description of a game with time-invariant sentence forms
* removed, and an object for accessing the truth values of those sentences.
* This can be useful for state machines working with games that use
* sentences to describe things like the sum of two integers in the range
* [0, 100]. (This can crash many state machines.)
*/
public static ConstantChecker getConstants(List<Gdl> description) {
description = DeORer.run(description);
description = VariableConstrainer.replaceFunctionValuedVariables(description);
return new ConstantChecker(description);
}
public static class ConstantChecker {
List<Role> roles;
Map<SentenceForm, Set<GdlSentence>> sentencesByForm = new HashMap<SentenceForm, Set<GdlSentence>>();
SentenceModel model;
Map<SentenceForm, ConstantForm> constForms;
public ConstantChecker(List<Gdl> description) {
roles = Role.computeRoles(description);
model = new SentenceModel(description, true);
for(SentenceForm form : model.getConstantSentenceForms()) {
sentencesByForm.put(form, new HashSet<GdlSentence>());
}
//TODO: We want to restrict the domains in the model here to useful
//values, but it's going to be tricky because the "useful values"
//depend on what's needed in the remainder of the description
//See: direction-from constants in mummymaze1p
//Those take on values 0-50 when all they need are 0-8
model.restrictDomainsToUsefulValues();
//TODO: Now we need to actually use these restricted domains where applicable
List<SentenceForm> ordering = getTopologicalOrdering(model.getConstantSentenceForms(), model.getDependencyGraph());
constForms = new HashMap<SentenceForm, ConstantForm>();
for(SentenceForm form : ordering) {
Set<GdlRelation> relations = model.getRelations(form);
Set<GdlRule> rules = model.getRules(form);
addConstantSentenceForm(form, relations, rules);
constForms.put(form, new ConstantForm(form, this));
}
}
public ConstantChecker(ConstantChecker other) {
roles = other.roles;
sentencesByForm = new HashMap<SentenceForm, Set<GdlSentence>>();
for(SentenceForm form : other.sentencesByForm.keySet()) {
sentencesByForm.put(form, new HashSet<GdlSentence>(other.sentencesByForm.get(form)));
}
model = new SentenceModel(other.model);
constForms = new HashMap<SentenceForm, ConstantForm>(other.constForms);
}
private static List<SentenceForm> getTopologicalOrdering(
Set<SentenceForm> forms,
Map<SentenceForm, Set<SentenceForm>> dependencyGraph) {
//We want each form as a key of the dependency graph to
//follow all the forms in the dependency graph, except maybe itself
Queue<SentenceForm> queue = new LinkedList<SentenceForm>(forms);
List<SentenceForm> ordering = new ArrayList<SentenceForm>(forms.size());
Set<SentenceForm> alreadyOrdered = new HashSet<SentenceForm>();
while(!queue.isEmpty()) {
SentenceForm curForm = queue.remove();
boolean readyToAdd = true;
//Don't add if there are dependencies
if(dependencyGraph.get(curForm) != null) {
for(SentenceForm dependency : dependencyGraph.get(curForm)) {
if(!dependency.equals(curForm) && !alreadyOrdered.contains(dependency)) {
readyToAdd = false;
break;
}
}
}
//Add it
if(readyToAdd) {
ordering.add(curForm);
alreadyOrdered.add(curForm);
} else {
queue.add(curForm);
}
//TODO: Add check for an infinite loop here
//Or replace with code that does stratification of loops
}
return ordering;
}
public List<Role> getRoles() {
return roles;
}
public boolean isTrueConstant(GdlSentence sentence) {
SentenceForm form = model.getSentenceForm(sentence);
return sentencesByForm.get(form).contains(sentence);
}
public Iterator<GdlSentence> getTrueSentences(SentenceForm form) {
Set<GdlSentence> sentences = sentencesByForm.get(form);
if(sentences == null)
return null;
else
return sentences.iterator();
}
private boolean hasConstantForm(GdlSentence sentence) {
return SentenceModel.inSentenceFormGroup(sentence, model.getConstantSentenceForms());
}
public boolean isConstantForm(SentenceForm form) {
return sentencesByForm.containsKey(form);
}
public Iterator<List<GdlConstant>> getTrueTuples(SentenceForm form) {
final SentenceForm finalForm = form;
if(form == null)
System.out.println("form is null");
if(sentencesByForm.get(finalForm) == null)
System.out.println("No set found for form " + form);
return new Iterator<List<GdlConstant>>() {
Iterator<GdlSentence> sentenceItr = sentencesByForm.get(finalForm).iterator();
@Override
public boolean hasNext() {
return sentenceItr.hasNext();
}
@Override
public List<GdlConstant> next() {
return SentenceModel.getTupleFromGroundSentence(sentenceItr.next());
}
@Override
public void remove() {
//Unimplemented
}
};
}
@SuppressWarnings("unchecked")
private void addConstantSentenceForm(SentenceForm form,
Set<GdlRelation> relations, Set<GdlRule> rules) {
Set<GdlRule> nonRecursiveRules = new HashSet<GdlRule>();
Set<GdlRule> recursiveRules = new HashSet<GdlRule>();
for(GdlRule rule : rules) {
boolean containsItself = false;
for(GdlLiteral literal : rule.getBody())
if(literal instanceof GdlRelation)
if(form.matches((GdlSentence)literal))
containsItself = true;
if(containsItself)
recursiveRules.add(rule);
else
nonRecursiveRules.add(rule);
}
Set<GdlSentence> trueByNonRecursives = new HashSet<GdlSentence>();
trueByNonRecursives.addAll(relations);
for(GdlRule rule : nonRecursiveRules) {
Assignments assignments = Assignments.getAssignmentsForRule(rule, model, constForms, sentencesByForm);
GdlSentence head = rule.getHead();
List<GdlVariable> varsInHead = getVarsInConjunct(head);
AssignmentIterator asnItr = assignments.getIterator();
while(asnItr.hasNext()) {
Map<GdlVariable, GdlConstant> assignment = asnItr.next();
boolean isGoodAssignment = true;
for(GdlLiteral literal : rule.getBody()) {
if(literal instanceof GdlSentence) {
GdlSentence transformed = CommonTransforms.replaceVariables((GdlSentence)literal, assignment);
SentenceForm literalForm = model.getSentenceForm(transformed);
if(!sentencesByForm.get(literalForm).contains(transformed)) {
isGoodAssignment = false;
List<GdlVariable> varsToChange = getVarsInConjunct(literal);
asnItr.changeOneInNext(varsToChange, assignment);
}
} else if(literal instanceof GdlNot) {
GdlSentence internal = (GdlSentence) ((GdlNot) literal).getBody();
GdlSentence transformed = CommonTransforms.replaceVariables(internal, assignment);
SentenceForm internalForm = model.getSentenceForm(transformed);
if(sentencesByForm.get(internalForm).contains(transformed)) {
isGoodAssignment = false;
List<GdlVariable> varsToChange = getVarsInConjunct(literal);
asnItr.changeOneInNext(varsToChange, assignment);
}
} else if(literal instanceof GdlDistinct) {
//Do nothing, handled in Assignments
} else {
throw new RuntimeException("Bad GdlLiteral type, probably OR");
}
}
if(isGoodAssignment) {
//Add it to the "good" list
trueByNonRecursives.add(CommonTransforms.replaceVariables(head, assignment));
//The constant head is a proposition? It comes up sometimes...
//Try applying condensation isolation to Zhadu
if(!(head instanceof GdlProposition))
asnItr.changeOneInNext(varsInHead, assignment);
} else {
//Nothing to do, I guess...
}
}
}
Set<GdlSentence> allTrueSentences = new HashSet<GdlSentence>(trueByNonRecursives);
Set<GdlSentence> recentAdditions = new HashSet<GdlSentence>(trueByNonRecursives);
Set<GdlSentence> newlyTrue = new HashSet<GdlSentence>();
while(!recentAdditions.isEmpty()) {
//Da da da, do rules
for(GdlRule rule : recursiveRules) {
for(GdlSentence input : recentAdditions) {
//TODO: Lack of inputs here seems to be causing slowdown
//need constantForms, completedSentenceFormValues
Assignments assignments = Assignments.getAssignmentsWithRecursiveInput(rule, model, form, input, constForms, true, Collections.EMPTY_MAP/*TODO sentencesByForm*/);
GdlSentence head = rule.getHead();
List<GdlVariable> varsInHead = getVarsInConjunct(head);
AssignmentIterator asnItr = assignments.getIterator();
while(asnItr.hasNext()) {
Map<GdlVariable, GdlConstant> assignment = asnItr.next();
//System.out.println(assignment);
boolean isGoodAssignment = true;
GdlSentence transformedHead = CommonTransforms.replaceVariables(head, assignment);
if(allTrueSentences.contains(transformedHead)) {
asnItr.changeOneInNext(varsInHead, assignment);
isGoodAssignment = false;
}
for(GdlLiteral literal : rule.getBody()) {
if(literal instanceof GdlSentence) {
GdlSentence transformed = CommonTransforms.replaceVariables((GdlSentence)literal, assignment);
SentenceForm literalForm = model.getSentenceForm(transformed);
if(form.matches(transformed)) {
//Works slightly differently for the recursive sentence form
if(!allTrueSentences.contains(transformed)) {
isGoodAssignment = false;
List<GdlVariable> varsToChange = getVarsInConjunct(literal);
asnItr.changeOneInNext(varsToChange, assignment);
}
} else {
//Component conj = components.get(transformed);
if(!sentencesByForm.get(literalForm).contains(transformed)) {
isGoodAssignment = false;
List<GdlVariable> varsToChange = getVarsInConjunct(literal);
asnItr.changeOneInNext(varsToChange, assignment);
}
}
} else if(literal instanceof GdlNot) {
//We can't have the recursive case here, by GDL rules
GdlSentence internal = (GdlSentence) ((GdlNot) literal).getBody();
GdlSentence transformed = CommonTransforms.replaceVariables(internal, assignment);
SentenceForm internalForm = model.getSentenceForm(transformed);
//Component conj = components.get(transformed);
if(sentencesByForm.get(internalForm).contains(transformed)) {
isGoodAssignment = false;
List<GdlVariable> varsToChange = getVarsInConjunct(literal);
asnItr.changeOneInNext(varsToChange, assignment);
}
} else if(literal instanceof GdlDistinct) {
//Do nothing
} else {
throw new RuntimeException("Bad GdlLiteral type, probably OR");
}
}
if(isGoodAssignment) {
//Add it to the "good" list
newlyTrue.add(CommonTransforms.replaceVariables(head, assignment));
//asnItr.changeOneInNext(varsInHead, assignment);
break; //
} else {
//Nothing to do, I guess...
}
}
}
}
allTrueSentences.addAll(newlyTrue);
recentAdditions.clear();
recentAdditions.addAll(newlyTrue);
newlyTrue.clear();
}
if(sentencesByForm.get(form) == null)
sentencesByForm.put(form, new HashSet<GdlSentence>());
sentencesByForm.get(form).addAll(allTrueSentences);
}
public Set<SentenceForm> getSentenceForms() {
return sentencesByForm.keySet();
}
public Integer getNumTrueTuples(SentenceForm form) {
return sentencesByForm.get(form).size();
}
//Currently makes a lot of assumptions about how this will be used.
//Basically custom-designed for the CondensationIsolator right now.
@SuppressWarnings("unchecked")
public void replaceRules(List<GdlRule> oldRules, List<GdlRule> newRules) {
//See which of the new rules involve constants
outer : for(GdlRule rule : newRules) {
for(GdlLiteral literal : rule.getBody()) {
GdlSentence sentence;
if(literal instanceof GdlSentence) {
sentence = (GdlSentence) literal;
} else if(literal instanceof GdlNot) {
sentence = (GdlSentence) ((GdlNot) literal).getBody();
} else continue;
if(!hasConstantForm(sentence))
continue outer;
}
//It's a constant form
GdlSentence head = rule.getHead();
model.replaceRules(Collections.EMPTY_LIST, Collections.singletonList(rule));
SentenceForm headForm = model.getSentenceForm(head);
addConstantSentenceForm(headForm, Collections.EMPTY_SET,
Collections.singleton(rule));
constForms.put(headForm, new ConstantForm(headForm, this));
}
}
}
private static List<GdlVariable> getVarsInConjunct(GdlLiteral literal) {
return SentenceModel.getVariables(literal);
}
}