package util.gdl.model; import java.util.ArrayList; import java.util.Collection; 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 java.util.Stack; import java.util.Map.Entry; import util.gdl.grammar.Gdl; import util.gdl.grammar.GdlConstant; import util.gdl.grammar.GdlDistinct; import util.gdl.grammar.GdlFunction; import util.gdl.grammar.GdlLiteral; import util.gdl.grammar.GdlNot; import util.gdl.grammar.GdlOr; import util.gdl.grammar.GdlPool; import util.gdl.grammar.GdlProposition; import util.gdl.grammar.GdlRelation; import util.gdl.grammar.GdlRule; import util.gdl.grammar.GdlSentence; import util.gdl.grammar.GdlTerm; import util.gdl.grammar.GdlVariable; /** * A model of all the sentences that can be formed (loosely speaking) in a particular game. * For each relation and function, this model contains the values (both constants and functions) * that each of its terms may have, according to some simple analysis of the rules. The values * found are a superset of the actual values that will be found in the game; not all of these * values are necessarily reachable in the actual game. * * @author Alex Landau */ public class SentenceModel { List<Gdl> description; private Map<String, List<TermModel>> sentences = new HashMap<String, List<TermModel>>(); //int maxArity = 0; boolean ignoreLanguageRules; public SentenceModel(List<Gdl> description, boolean ignoreLanguageRules) { this.description = description; this.ignoreLanguageRules = ignoreLanguageRules; List<GdlRule> rules = new ArrayList<GdlRule>(); //First, get all the constants (non-rules) into the model for(Gdl gdl : description) { if(gdl instanceof GdlRelation) { GdlRelation relation = (GdlRelation) gdl; addToModel(relation); } else if(gdl instanceof GdlRule) { rules.add((GdlRule) gdl); } else { throw new RuntimeException("Description contains non-relation, non-rule Gdl " + gdl + " of class " + gdl.getClass()); } } //For the purposes of the language-based injections, we need //these to be included in the model at this point if(!ignoreLanguageRules) { if(!sentences.containsKey("legal")) { List<TermModel> termsList = new ArrayList<TermModel>(2); termsList.add(new TermModel()); termsList.add(new TermModel()); sentences.put("legal", termsList); } if(!sentences.containsKey("does")) { List<TermModel> termsList = new ArrayList<TermModel>(2); termsList.add(new TermModel()); termsList.add(new TermModel()); sentences.put("does", termsList); } if(!sentences.containsKey("init")) { List<TermModel> termsList = new ArrayList<TermModel>(1); termsList.add(new TermModel()); sentences.put("init", termsList); } if(!sentences.containsKey("next")) { List<TermModel> termsList = new ArrayList<TermModel>(1); termsList.add(new TermModel()); sentences.put("next", termsList); } if(!sentences.containsKey("true")) { List<TermModel> termsList = new ArrayList<TermModel>(1); termsList.add(new TermModel()); sentences.put("true", termsList); } } //Now, apply injections through rules until we've gotten absolutely everything //The brute-force method is to repeatedly apply injection at every rule until nothing new is added //GDL descriptions are generally small enough that this should work boolean somethingChanged = true; while(somethingChanged) { somethingChanged = false; if(!ignoreLanguageRules && applyLanguageInjections()) somethingChanged = true; for(GdlRule rule : rules) { //We apply the injection, and note if it changes the model if(applyInjection(rule)) somethingChanged = true; } } //for(List<TermModel> body : sentences.values()) //if(maxArity < body.size()) //maxArity = body.size(); } public SentenceModel(List<Gdl> description) { this(description, false); } /** * Constructs a deep copy of the given model. */ public SentenceModel(SentenceModel other) { //Part of the reason this exists is to allow malleable descriptions. //For most objects, this will mean new data structures sharing the //same immutable objects. description = new ArrayList<Gdl>(other.description); //TermModels are not immutable. sentences = new HashMap<String, List<TermModel>>(); for(Entry<String, List<TermModel>> entry : other.sentences.entrySet()) sentences.put(entry.getKey(), copy(entry.getValue())); //maxArity = other.maxArity; ignoreLanguageRules = other.ignoreLanguageRules; if(other.dependencyGraph != null) dependencyGraph = new HashMap<SentenceForm, Set<SentenceForm>>(other.dependencyGraph); //SentenceForms are immutable. if(other.constantSentenceForms != null) constantSentenceForms = new HashSet<SentenceForm>(other.constantSentenceForms); if(other.independentSentenceForms != null) independentSentenceForms = new HashSet<SentenceForm>(other.independentSentenceForms); if(other.dependentSentenceForms != null) dependentSentenceForms = new HashSet<SentenceForm>(other.dependentSentenceForms); if(other.sentenceForms != null) sentenceForms = new HashSet<SentenceForm>(other.sentenceForms); if(other.sentenceFormsByName != null) sentenceFormsByName = new HashMap<GdlConstant, Set<SentenceForm>>(other.sentenceFormsByName); relationsByForm.putAll(other.relationsByForm); rulesByForm.putAll(other.rulesByForm); } private boolean applyLanguageInjections() { //Injects init and next to true, and legal to does. boolean somethingChanged = false; List<TermModel> legalBody, doesBody, initBody, nextBody, trueBody; legalBody = sentences.get("legal"); doesBody = sentences.get("does"); initBody = sentences.get("init"); nextBody = sentences.get("next"); trueBody = sentences.get("true"); if(doesBody.get(0).inject(legalBody.get(0))) somethingChanged = true; if(doesBody.get(1).inject(legalBody.get(1))) somethingChanged = true; if(trueBody.get(0).inject(initBody.get(0))) somethingChanged = true; if(trueBody.get(0).inject(nextBody.get(0))) somethingChanged = true; return somethingChanged; } private boolean applyInjection(GdlRule rule) { boolean result = false; boolean somethingChanged = true; while(somethingChanged) { somethingChanged = false; //For each variable in the LHS of the rule, we want //to get all the possible values implied by each //positive conjunct on the RHS. GdlSentence head = rule.getHead(); //This preliminary pass handles cases with even no variables if(injectIntoHead(head, null, null)) somethingChanged = true; List<String> variablesInHead = new ArrayList<String>(); addVariableNames(variablesInHead, head); for(GdlLiteral literal : rule.getBody()) { if(!(literal instanceof GdlNot)) { if(injectVariable(head, literal, variablesInHead)) somethingChanged = true; } } if(somethingChanged) result = true; } return result; } private boolean injectVariable(GdlSentence head, GdlLiteral literal, List<String> vars) { //Convert this into a sentence if(literal instanceof GdlSentence) { return injectVariableFromSentence(head, (GdlSentence)literal, vars); } else if(literal instanceof GdlOr) { boolean changed = false; GdlOr or = (GdlOr) literal; for(int i = 0; i < or.arity(); i++) if(injectVariable(head, or.get(i), vars)) changed = true; return changed; } else if((literal instanceof GdlDistinct) || (literal instanceof GdlNot)) { //Contains no useful information return false; } else { throw new RuntimeException("Unforeseen literal type " + literal.getClass() + " encountered during variable injection"); } } private boolean injectVariableFromSentence(GdlSentence head, GdlSentence sentence, List<String> vars) { //Crawl the sentence and its model together, looking //for where the variable refers String sentenceName = sentence.getName().getValue(); if(sentences.containsKey(sentenceName)) { List<GdlTerm> body; try { body = sentence.getBody(); } catch(RuntimeException e) {body = Collections.emptyList();} List<TermModel> bodyModel = sentences.get(sentenceName); //Look for the variable in the body return crawlBody(head, body, bodyModel, vars); } return false; } private boolean crawlBody(GdlSentence head, List<GdlTerm> body, List<TermModel> bodyModel, List<String> vars) { boolean changedSomething = false; //Look for the variable in the body for(int i = 0; i < body.size(); i++) { GdlTerm term = body.get(i); TermModel termModel = bodyModel.get(i); if(term instanceof GdlVariable) { if(vars.contains(term.toString())) { if(injectIntoHead(head, termModel, term.toString())) changedSomething = true; } } else if(term instanceof GdlFunction) { //Crawl the function, too GdlFunction function = (GdlFunction) term; String functionName = function.getName().getValue(); if(termModel.containsFunction(functionName)) { //crawl function body List<GdlTerm> functionBody = function.getBody(); List<TermModel> functionBodyModel = termModel.getFunction(functionName); if(crawlBody(head, functionBody, functionBodyModel, vars)) changedSomething = true; } } } return changedSomething; } private boolean injectIntoHead(GdlSentence head, TermModel termModel, String varName) { boolean changedSomething = false; //Since this is a head, we construct as we crawl the model, //rather than ignoring cases where the model is incomplete. //This is even true for random constants we encounter. String headKey = head.getName().getValue(); if(!sentences.containsKey(headKey)) { List<TermModel> termsList = new ArrayList<TermModel>(head.arity()); for(int i = 0; i < head.arity(); i++) termsList.add(new TermModel()); sentences.put(headKey, termsList); changedSomething = true; } List<GdlTerm> headBody; try{ headBody = head.getBody(); } catch(RuntimeException e) {headBody = Collections.emptyList();} List<TermModel> headBodyModel = sentences.get(headKey); if(buildBodyModel(headBody, headBodyModel, termModel, varName)) changedSomething = true; return changedSomething; } private boolean buildBodyModel(List<GdlTerm> body, List<TermModel> bodyModel, TermModel termToInject, String varName) { boolean changedSomething = false; for(int i = 0; i < body.size(); i++) { GdlTerm term = body.get(i); TermModel termModel = bodyModel.get(i); if(term instanceof GdlConstant) { if(!termModel.containsConstant((GdlConstant)term)) { termModel.addConstant((GdlConstant)term); changedSomething = true; } } else if(term instanceof GdlVariable) { if(((GdlVariable)term).getName().equals(varName)) { if(termModel.inject(termToInject)) changedSomething = true; } } else if(term instanceof GdlFunction) { //Get the function, then the function body GdlFunction function = (GdlFunction) term; String functionName = function.getName().getValue(); if(!termModel.containsFunction(functionName)) { termModel.addFunction(function.getName().getValue(), function.arity()); changedSomething = true; } List<GdlTerm> functionBody = function.getBody(); List<TermModel> functionBodyModel = termModel.getFunction(functionName); if(buildBodyModel(functionBody, functionBodyModel, termToInject, varName)) changedSomething = true; } else { throw new RuntimeException("Unforeseen term type " + term.getClass()); } } return changedSomething; } public void restrictDomainsToUsefulValues() { //We want to restrict the domains of sentence forms to those that will be //useful looking forward; that is, they must eventually be passed into //some statement with a next/legal/goal/init __, or else be __, or //used in a not or distinct statement. Also, any domain that ends up //being reduced should be maintained. //The motivator for this is the set of chinese checkers games, and //specifically the "count" sentence. //Basically, for non-special sentences, the domain is the union of what //we find from the following, as parts of rule bodies: //1) If it's part of a variable with no equivalent in the // head, keep all of the domain. //2) If it's a constant in the sentence (as used in the domain), keep // that constant. //I guess if it's any //TODO: We need to expand this to handle, e.g., mummymaze1p. Here, we use //a different set of rules: //The domain is the union of what we find from the following: //1) If it's part of a variable with no equivalent in the head, keep // the intersection of all appearances of the variable in positive // conjuncts in the rule body. //2) If it's a constant in the sentence, keep that constant. //Can we get a more formal/useful definition? //We want to restrict term models. We currently have some superset of the //minimal domain for each term model; we want to cut down that size. //We want to go through all the rules and find SUPPORT for the inclusion //of each variable in each term model. (I suppose functions, too, but we'll //ignore those for now.) That means for each term model, we set up a set //of variables that have support so far. //Then we iterate over the functions and find the support that each function //provides to each of the relevant term models. //This gets slightly complicated in that we want to prune term models through //various layers, i.e. when the head's domain changes. Maybe repeated iteration? //I'm wondering if this could get ugly if we ignore the ways functions //are used in the term models. Let's say we have (legal ?player ?move) //deriving from something; the term models in the function called by //?move might get ignored, even though this grants them full support. //If we had function-valued variables removed, this would be easier. //So, we should have a new domain for each term model, I guess... Map<TermModel, Set<GdlConstant>> newDomains = new HashMap<TermModel, Set<GdlConstant>>(); //We go through each rule body //Don't forget the special ones... for(Gdl gdl : description) { if(gdl instanceof GdlRule) { GdlRule rule = (GdlRule) gdl; for(GdlLiteral literal : rule.getBody()) { addToRestrictedDomains(literal, newDomains); } } } //Now, we restrict all the domains we found for(Entry<TermModel, Set<GdlConstant>> entry : newDomains.entrySet()) { entry.getKey().setConstants(entry.getValue()); } } private void addToRestrictedDomains(GdlLiteral literal, Map<TermModel, Set<GdlConstant>> newDomains) { if(literal instanceof GdlRelation) { GdlRelation relation = (GdlRelation) literal; //There are certain domains we don't want to restrict. String relationName = relation.getName().getValue(); if(relationName.equalsIgnoreCase("does") || relationName.equalsIgnoreCase("legal") || relationName.equalsIgnoreCase("goal") || relationName.equalsIgnoreCase("init") || relationName.equalsIgnoreCase("true") || relationName.equalsIgnoreCase("next") || relationName.equalsIgnoreCase("base") || relationName.equalsIgnoreCase("input")) return; //Get the list of terms and the list of term models List<GdlTerm> body = relation.getBody(); List<TermModel> bodyModel = getBody(relation.getName().getValue()); addToRestrictedDomains(body, bodyModel, newDomains); } else if(literal instanceof GdlNot) { GdlNot not = (GdlNot) literal; addToRestrictedDomains(not.getBody(), newDomains); } else if(literal instanceof GdlOr) { GdlOr or = (GdlOr) literal; for(int i = 0; i < or.arity(); i++) { addToRestrictedDomains(or.get(i), newDomains); } } } private void addToRestrictedDomains(List<GdlTerm> body, List<TermModel> bodyModel, Map<TermModel, Set<GdlConstant>> newDomains) { for(int i = 0; i < body.size(); i++) { GdlTerm term = body.get(i); TermModel termModel = bodyModel.get(i); if(term instanceof GdlConstant) { if(!newDomains.containsKey(termModel)) newDomains.put(termModel, new HashSet<GdlConstant>()); newDomains.get(termModel).add((GdlConstant)term); } else if(term instanceof GdlVariable) { //Can't really rule anything out of the domain newDomains.put(termModel, termModel.getConstants()); } else if(term instanceof GdlFunction) { GdlFunction function = (GdlFunction) term; List<GdlTerm> functionBody = function.getBody(); List<TermModel> functionBodyModel = termModel.getFunction(function); addToRestrictedDomains(functionBody, functionBodyModel, newDomains); } } } //This could probably be factored out into some utility class. public static List<GdlVariable> getVariables(GdlRule rule) { List<String> variableNames = getVariableNames(rule); List<GdlVariable> variables = new ArrayList<GdlVariable>(); for(String name : variableNames) variables.add(GdlPool.getVariable(name)); return variables; } public static List<GdlVariable> getVariables(GdlLiteral literal) { List<String> variableNames = getVariableNames(literal); List<GdlVariable> variables = new ArrayList<GdlVariable>(); for(String name : variableNames) variables.add(GdlPool.getVariable(name)); return variables; } //I happened to have written the name-finding code first; the other way around would //probably be more efficient. The use of a list over a set is also for historical //reasons, though some applications could make use of the consistent ordering. public static List<String> getVariableNames(GdlLiteral literal) { List<String> varNames = new ArrayList<String>(); addVariableNames(varNames, literal); return varNames; } public static List<String> getVariableNames(GdlRule rule) { List<String> varNames = new ArrayList<String>(); addVariableNames(varNames, rule.getHead()); for(GdlLiteral conjunct : rule.getBody()) addVariableNames(varNames, conjunct); return varNames; } private static void addVariableNames(List<String> variables, GdlLiteral literal) { if(literal instanceof GdlRelation) { GdlSentence sentence = (GdlSentence) literal; addVariableNames(variables, sentence.getBody()); } else if(literal instanceof GdlOr) { GdlOr or = (GdlOr) literal; for(int i = 0; i < or.arity(); i++) addVariableNames(variables, or.get(i)); } else if(literal instanceof GdlNot) { GdlNot not = (GdlNot) literal; addVariableNames(variables, not.getBody()); } else if(literal instanceof GdlDistinct) { GdlDistinct distinct = (GdlDistinct) literal; List<GdlTerm> pair = new ArrayList<GdlTerm>(2); pair.add(distinct.getArg1()); pair.add(distinct.getArg2()); addVariableNames(variables, pair); } else if(literal instanceof GdlProposition) { //No variables } else { throw new RuntimeException("Unforeseen literal type"); } } private static void addVariableNames(List<String> variables, List<GdlTerm> body) { for(GdlTerm term : body) { if(term instanceof GdlVariable) { GdlVariable var = (GdlVariable) term; if(!variables.contains(var.getName())) variables.add(var.getName()); } else if(term instanceof GdlFunction) { GdlFunction function = (GdlFunction) term; addVariableNames(variables, function.getBody()); } } } @Override public String toString() { String result = ""; result += "{\n "; for(Entry<String, List<TermModel>> relType : sentences.entrySet()) { result += " (" + relType.getKey(); for(TermModel m : relType.getValue()) { result += " " + m; } result += ")\n"; } result += "}\n"; return result; } private void addToModel(GdlRelation relation) { //Add a nice, simple relation with no symbols to the model String name = relation.getName().getValue(); int arity = relation.arity(); updateTermsListOfName(name, arity, relation); } private void updateTermsListOfName(String name, int arity, GdlRelation relation) { if(!sentences.containsKey(name)) { List<TermModel> termsList = new ArrayList<TermModel>(arity); for(int i = 0; i < arity; i++) termsList.add(new TermModel()); sentences.put(name, termsList); } List<TermModel> terms = sentences.get(name); //For each slot in the body model, add the current term for(int i = 0; i < arity; i++) { terms.get(i).addTerm(relation.get(i)); } } public static class TermModel { Set<GdlConstant> constantValues = new HashSet<GdlConstant>(); Map<String, List<TermModel>> functionalValues = new HashMap<String, List<TermModel>>(); public TermModel () { } void setConstants(Set<GdlConstant> constants) { this.constantValues = constants; } public TermModel(TermModel other) { //Copy other term model without sharing any memory constantValues.addAll(other.constantValues); for(String key : other.functionalValues.keySet()) { functionalValues.put(key, copy(other.functionalValues.get(key))); } } public void addTerm(GdlTerm term) { if(term instanceof GdlConstant) { constantValues.add((GdlConstant) term); } else if(term instanceof GdlFunction) { GdlFunction function = (GdlFunction) term; //Add the function //Do we already have a function of this name and arity? int arity = function.arity(); String name = function.getName().getValue(); //If not, add it to the list if(!functionalValues.containsKey(name)) { List<TermModel> terms = new ArrayList<TermModel>(arity); for(int i = 0; i < arity; i++) terms.add(new TermModel()); functionalValues.put(name, terms); } List<TermModel> terms = functionalValues.get(name); //Add to each term model its value for(int i = 0; i < arity; i++) { terms.get(i).addTerm(function.get(i)); } } else { throw new RuntimeException("Description contains non-constant, non-function term " + term + " in a supposedly constant relation"); } } public boolean inject(TermModel other) { //Return true if we add anything new boolean changedSomething = false; if(constantValues.addAll(other.constantValues)) changedSomething = true; for(Entry<String, List<TermModel>> function : other.functionalValues.entrySet()) { //If we don't have the function, add it and a copy of its contents String functionKey = function.getKey(); List<TermModel> functionBody = function.getValue(); if(!functionalValues.containsKey(functionKey)) { functionalValues.put(functionKey, copy(functionBody)); changedSomething = true; } else { List<TermModel> ourFunction = functionalValues.get(functionKey); //Inject all the inner terms? for(int i = 0; i < ourFunction.size(); i++) { if(ourFunction.get(i).inject(functionBody.get(i))) changedSomething = true; } } } return changedSomething; } public boolean containsConstant(GdlConstant constant) { return constantValues.contains(constant); } public void addConstant(GdlConstant constant) { constantValues.add(constant); } public boolean containsFunction(String functionKey) { return functionalValues.containsKey(functionKey); } public List<TermModel> getFunction(String functionKey) { return functionalValues.get(functionKey); } public void addFunction(String functionName, int arity) { if(functionalValues.containsKey(functionName)) throw new RuntimeException("Trying to add already-existing function name " + functionName); List<TermModel> termsList = new ArrayList<TermModel>(arity); for(int i = 0; i < arity; i++) termsList.add(new TermModel()); functionalValues.put(functionName, termsList); } @Override public String toString() { String result = "{"; for(GdlConstant c : constantValues) { result += c.getValue() + ", "; } for(Entry<String, List<TermModel>> fn : functionalValues.entrySet()) { result += "(" + fn.getKey(); for(TermModel term : fn.getValue()) result += " " + term; result += "), "; } result += "}"; return result; } public boolean hasFunctions() { return !functionalValues.isEmpty(); } public List<TermModel> getFunction(GdlFunction function) { return getFunction(function.getName().getValue()); } public Set<GdlConstant> getConstants() { return constantValues; } public Map<String, List<TermModel>> getFunctions() { return functionalValues; } public static TermModel getIntersection(Collection<TermModel> list) { if(list.isEmpty()) throw new RuntimeException("Looking for the intersection of an empty set of TermModels"); Iterator<TermModel> itr = list.iterator(); TermModel intersection = new TermModel(itr.next()); while(itr.hasNext()) { TermModel cur = itr.next(); //Intersect cur into the current intersection intersection.intersect(cur); } return intersection; } private void intersect(TermModel other) { //Keep only the constants that are in both. //Keep a function only if it appears in both; //intersect each of the term models in the models constantValues.retainAll(other.constantValues); Set<String> keys = new HashSet<String>(functionalValues.keySet()); for(String key : keys) { if(!other.containsFunction(key)) { functionalValues.remove(key); } else { //Intersect each term of the function's body with the other function List<TermModel> ourBody = functionalValues.get(key); List<TermModel> theirBody = other.functionalValues.get(key); for(int i = 0; i < ourBody.size(); i++) { ourBody.get(i).intersect(theirBody.get(i)); } } } } } public static List<TermModel> copy(List<TermModel> body) { List<TermModel> copy = new ArrayList<TermModel>(); for(TermModel t : body) { copy.add(new TermModel(t)); } return copy; } public List<TermModel> getBodyForSentence(GdlSentence sentence) { //This is some relation... //We just look at its name return sentences.get(sentence.getName().getValue()); } /** * Returns the set of all constants used in the game. Does not include * relation constants (i.e. constants used only as the names of domains). */ public Set<GdlConstant> getUniversalDomain() { Set<GdlConstant> domain = new HashSet<GdlConstant>(); for(List<TermModel> body : sentences.values()) { addConstantsToDomain(body, domain); } return domain; } private void addConstantsToDomain(List<TermModel> body, Set<GdlConstant> domain) { for(TermModel term : body) { domain.addAll(term.getConstants()); for(List<TermModel> functionBody : term.getFunctions().values()) { addConstantsToDomain(functionBody, domain); } } } private Map<SentenceForm, Set<SentenceForm>> dependencyGraph = null; /** * Each key in the graph depends on those sentence forms in the associated set. */ public Map<SentenceForm, Set<SentenceForm>> getDependencyGraph() { if(dependencyGraph == null) { //Build graph from rules dependencyGraph = new HashMap<SentenceForm, Set<SentenceForm>>(); for(Gdl gdl : description) { if(gdl instanceof GdlRule) { GdlRule rule = (GdlRule) gdl; SentenceForm head = new SentenceForm(rule.getHead()); if(!dependencyGraph.containsKey(head)) dependencyGraph.put(head, new HashSet<SentenceForm>()); for(GdlLiteral bodyLiteral : rule.getBody()) { dependencyGraph.get(head).addAll(extractSentenceForms(bodyLiteral)); } } } } return dependencyGraph; } /** * Independent sentence forms are those whose sentences do * not depend on players' states; they may depend on which * turn it is, however. For example, a sentence form (true (step _)) * implementing a step counter would be independent, but * not constant. All constant sentence forms are independent. */ public Set<SentenceForm> getIndependentSentenceForms() { if(independentSentenceForms == null) setDependentAndIndependentSentenceForms(); return independentSentenceForms; } private Set<SentenceForm> independentSentenceForms = null; /** * Constant sentence forms are those whose sentences are * either true in all states or false in all states. */ public Set<SentenceForm> getConstantSentenceForms() { if(constantSentenceForms == null) { constantSentenceForms = new HashSet<SentenceForm>(); constantSentenceForms.addAll(getSentenceForms()); constantSentenceForms.removeAll(getChangingSentences(true)); constantSentenceForms = Collections.unmodifiableSet(constantSentenceForms); } return constantSentenceForms; } private Set<SentenceForm> constantSentenceForms = null; /** * Dependent sentence forms are those with at least some * sentences whose truth values can vary based on the players' * choices of states. */ public Set<SentenceForm> getDependentSentenceForms() { if(dependentSentenceForms == null) setDependentAndIndependentSentenceForms(); return dependentSentenceForms; } private Set<SentenceForm> dependentSentenceForms = null; private void setDependentAndIndependentSentenceForms() { //We'll need to go over the graph... dependentSentenceForms = getChangingSentences(false); independentSentenceForms = new HashSet<SentenceForm>(); independentSentenceForms.addAll(getSentenceForms()); independentSentenceForms.removeAll(dependentSentenceForms); dependentSentenceForms = Collections.unmodifiableSet(dependentSentenceForms); independentSentenceForms = Collections.unmodifiableSet(independentSentenceForms); } private Set<SentenceForm> getChangingSentences(boolean includeIndependents) { Set<SentenceForm> allForms = getSentenceForms(); Map<SentenceForm, Set<SentenceForm>> dependencies = getDependencyGraph(); Queue<SentenceForm> unfollowed = new LinkedList<SentenceForm>(); Set<SentenceForm> changing = new HashSet<SentenceForm>(); for(SentenceForm form : allForms) { if(form.getName().getValue().equalsIgnoreCase("does")) { unfollowed.add(form); changing.add(form); } //Do this next part if we want to include things that change //over time, but always function the same way (e.g. step counters) if(includeIndependents) { if(form.getName().getValue().equalsIgnoreCase("true")) { unfollowed.add(form); changing.add(form); } } } //Now, we propagate the unfollowed set until we have everything //that follows from a does clause //We must be careful when we encounter a "next" sentence form while(!unfollowed.isEmpty()) { SentenceForm toFollow = unfollowed.remove(); for(SentenceForm candidate : allForms) { if(changing.contains(candidate)) continue; if(dependencies.get(candidate) != null && dependencies.get(candidate).contains(toFollow)) { //The candidate is dependent on a changing form changing.add(candidate); unfollowed.add(candidate); if(candidate.getName().getValue().equalsIgnoreCase("next")) { SentenceForm trueForm = candidate.getCopyWithName("true"); changing.add(trueForm); unfollowed.add(trueForm); } } } } return changing; } private Set<SentenceForm> extractSentenceForms(GdlRule rule) { Set<SentenceForm> forms = new HashSet<SentenceForm>(); extractSentenceForms(forms, rule.getHead()); for(GdlLiteral literal : rule.getBody()) extractSentenceForms(forms, literal); return forms; } private Set<SentenceForm> extractSentenceForms(GdlLiteral literal) { Set<SentenceForm> forms = new HashSet<SentenceForm>(); extractSentenceForms(forms, literal); return forms; } private void extractSentenceForms(Collection<SentenceForm> forms, GdlLiteral literal) { if(literal instanceof GdlSentence) { forms.add(new SentenceForm((GdlSentence)literal)); } else if(literal instanceof GdlNot) { extractSentenceForms(forms, ((GdlNot) literal).getBody()); } else if(literal instanceof GdlOr) { GdlOr or = (GdlOr) literal; for(int i = 0; i < or.arity(); i++) extractSentenceForms(forms, or.get(i)); } //Distincts are unnecessary to record } public class SentenceForm implements Iterable<GdlSentence> { public SentenceForm(GdlSentence sentence) { sentenceName = sentence.getName(); tupleSize = 0; //Gets filled in as we go if(sentence instanceof GdlProposition) return; GdlRelation relation = (GdlRelation) sentence; fillElementsWithBody(relation.getBody(), 0); } public SentenceForm(SentenceForm original, String name) { //Construct a SentenceForm based on another, but with a different relation name sentenceName = GdlPool.getConstant(name); //These are immutable, so we can just reference the originals functionalElements = original.functionalElements; arities = original.arities; tupleSize = original.tupleSize; } private int fillElementsWithBody(List<GdlTerm> body, int termNumber) { for(GdlTerm term : body) { if(term instanceof GdlConstant || term instanceof GdlVariable) { functionalElements.add(null); arities.add(0); termNumber++; tupleSize++; } else if(term instanceof GdlFunction) { GdlFunction function = (GdlFunction) term; functionalElements.add(function.getName()); arities.add(function.arity()); termNumber++; termNumber = fillElementsWithBody(function.getBody(), termNumber); } } return termNumber; } //The sentence form is a selection of a relation constant and any //functions it contains. That is, two relations with the same //sentence form may differ only in their constants and variables. private GdlConstant sentenceName; private List<GdlConstant> functionalElements = new ArrayList<GdlConstant>(); private List<Integer> arities = new ArrayList<Integer>(); private int tupleSize = 0; @Override public int hashCode() { final int prime = 31; int result = 1; //result = prime * result + getOuterType().hashCode(); result = prime * result + ((arities == null) ? 0 : arities.hashCode()); result = prime * result + ((functionalElements == null) ? 0 : functionalElements .hashCode()); result = prime * result + ((sentenceName == null) ? 0 : sentenceName.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; SentenceForm other = (SentenceForm) obj; //if (!getOuterType().equals(other.getOuterType())) // return false; if (arities == null) { if (other.arities != null) return false; } else if (!arities.equals(other.arities)) return false; if (functionalElements == null) { if (other.functionalElements != null) return false; } else if (!functionalElements.equals(other.functionalElements)) return false; if (sentenceName == null) { if (other.sentenceName != null) return false; } else if (!sentenceName.equals(other.sentenceName)) return false; return true; } //private SentenceModel getOuterType() { // return SentenceModel.this; //} @Override public String toString() { StringBuilder b = new StringBuilder(); b.append("(").append(sentenceName); Stack<Integer> arityStack = new Stack<Integer>(); for(int i = 0; i < functionalElements.size(); i++) { GdlConstant functionName = functionalElements.get(i); int arity = arities.get(i); if(functionName == null) { b.append(" _"); if(!arityStack.isEmpty()) { int curArity = arityStack.pop(); if(curArity == 1) b.append(")"); else arityStack.push(curArity - 1); } } else { //functionName != null b.append(" (").append(functionName); arityStack.push(arity); } } b.append(")"); return b.toString(); } public GdlConstant getName() { return sentenceName; } public SentenceForm getCopyWithName(String name) { return new SentenceForm(this, name); } public boolean matches(GdlSentence sentence) { //Does the sentence fit the form? Hmm... if(sentence.getName() != sentenceName) return false; if(sentence instanceof GdlProposition) return functionalElements.isEmpty(); GdlRelation relation = (GdlRelation) sentence; Iterator<GdlConstant> elementItr = functionalElements.iterator(); Iterator<Integer> arityItr = arities.iterator(); return elementsMatchBody(relation.getBody(), elementItr, arityItr); } private boolean elementsMatchBody(List<GdlTerm> body, Iterator<GdlConstant> elementItr, Iterator<Integer> arityItr) { //Go through the body, increase the index as we go for(GdlTerm term : body) { if(term instanceof GdlFunction) { GdlFunction function = (GdlFunction) term; if(elementItr.next() != function.getName()) { return false; } if(arityItr.next() != function.arity()) { return false; } //Check the function body if(!elementsMatchBody(function.getBody(), elementItr, arityItr)) { return false; } } else { if(elementItr.next() != null) return false; arityItr.next(); } } return true; } @Override public Iterator<GdlSentence> iterator() { if(functionalElements.isEmpty()) return new SentenceFormPropositionIterator(); else return new SentenceFormRelationIterator(); } private class SentenceFormPropositionIterator implements Iterator<GdlSentence> { boolean used = false; @Override public boolean hasNext() { return !used; } @Override public GdlSentence next() { used = true; return GdlPool.getProposition(sentenceName); } @Override public void remove() { //Not implemented } } private class SentenceFormRelationIterator implements Iterator<GdlSentence> { List<Set<GdlConstant>> bodyDomains = new ArrayList<Set<GdlConstant>>(functionalElements.size()); List<Iterator<GdlConstant>> bodyIterators = new ArrayList<Iterator<GdlConstant>>(functionalElements.size()); List<GdlConstant> currentTuple = new ArrayList<GdlConstant>(functionalElements.size()); int initIndex = 0; //used only in initialization public SentenceFormRelationIterator() { //bodyIterators: initialize //need to go over the domains we've established List<TermModel> models = sentences.get(sentenceName.getValue()); addDomainsOfModels(models); //Now, set the iterators according to the domains for(Set<GdlConstant> domain : bodyDomains) { bodyIterators.add(domain.iterator()); } //We wait for the current tuple... } private void addDomainsOfModels(List<TermModel> models) { for(TermModel model : models) { GdlConstant functionName = functionalElements.get(initIndex); if(functionName == null) { bodyDomains.add(model.getConstants()); initIndex++; } else { //Recurse over the function body List<TermModel> functionBody = model.getFunction(functionName.getValue()); initIndex++; addDomainsOfModels(functionBody); } } } @Override public boolean hasNext() { for(Iterator<GdlConstant> itr : bodyIterators) if(itr.hasNext()) return true; return false; } @Override public GdlSentence next() { if(currentTuple.isEmpty()) { //Fill it for the first time for(Iterator<GdlConstant> itr : bodyIterators) currentTuple.add(itr.next()); return getSentenceFromTuple(currentTuple); } //Starting at the end, find the first non-expired slot int i; for(i = bodyIterators.size() - 1; i >= 0; i--) { if(bodyIterators.get(i).hasNext()) break; } //Update the constant at i currentTuple.set(i, bodyIterators.get(i).next()); //Replace all the iterators after i for(int j = i + 1; j < bodyIterators.size(); j++) { bodyIterators.set(j, bodyDomains.get(j).iterator()); currentTuple.set(j, bodyIterators.get(j).next()); } //Now the current tuple is updated return getSentenceFromTuple(currentTuple); } @Override public void remove() { //Not applicable } } public GdlSentence getSentenceFromTuple(List<GdlConstant> tuple) { /*if(tuple.size() != functionalElements.size()) throw new RuntimeException("Tried to get sentence from tuple of size "+tuple.size() +"; need size " + functionalElements.size());*/ if(tuple.size() == 0) return GdlPool.getProposition(sentenceName); //Make the GdlRelation corresponding to this as a tuple Iterator<GdlConstant> elementItr = functionalElements.iterator(); Iterator<Integer> arityItr = arities.iterator(); Iterator<GdlConstant> tupleItr = tuple.iterator(); List<GdlTerm> body = new ArrayList<GdlTerm>(); //we could bootstrap the arity... int arity = arities.size(); for(int a : arities) arity -= a; fillBody(body, elementItr, arityItr, tupleItr, arity); return GdlPool.getRelation(sentenceName, body); } private void fillBody(List<GdlTerm> body, Iterator<GdlConstant> elementItr, Iterator<Integer> arityItr, Iterator<GdlConstant> tupleItr, int arity) { for(int i = 0; i < arity; i++) { //fill in the ith element of the body //starting with the index1th element of functionalElements //constants filled by the index2th element of tuple GdlConstant functionName = elementItr.next(); int a = arityItr.next(); if(functionName == null) { body.add(tupleItr.next()); } else { //add the function List<GdlTerm> functionBody = new ArrayList<GdlTerm>(a); fillBody(functionBody, elementItr, arityItr, tupleItr, a); body.add(GdlPool.getFunction(functionName, functionBody)); } } } public int getTupleSize() { return tupleSize; } private GdlSentence sampleSentence = null; public GdlSentence getSampleSentence() { if(sampleSentence == null) { //TODO: Fill in values from actual domain? List<GdlConstant> tuple = new ArrayList<GdlConstant>(tupleSize); for(int i = 0; i < tupleSize; i++) tuple.add(GdlPool.getConstant("0")); sampleSentence = getSentenceFromTuple(tuple); } return sampleSentence; } } public static boolean inSentenceFormGroup(GdlSentence sentence, Set<SentenceForm> forms) { for(SentenceForm form : forms) if(form.matches(sentence)) return true; return false; } public static List<GdlTerm> getTupleFromSentence( GdlSentence sentence) { if(sentence instanceof GdlProposition) return Collections.emptyList(); //A simple crawl through the sentence. List<GdlTerm> tuple = new ArrayList<GdlTerm>(); try { addBodyToTuple(sentence.getBody(), tuple); } catch(RuntimeException e) { throw new RuntimeException(e.getMessage() + "\nSentence was " + sentence); } return tuple; } private static void addBodyToTuple(List<GdlTerm> body, List<GdlTerm> tuple) { for(GdlTerm term : body) { if(term instanceof GdlConstant) { tuple.add(term); } else if(term instanceof GdlVariable) { tuple.add(term); } else if(term instanceof GdlFunction){ GdlFunction function = (GdlFunction) term; addBodyToTuple(function.getBody(), tuple); } else { throw new RuntimeException("Unforeseen Gdl tupe in SentenceModel.addBodyToTuple()"); } } } public static List<GdlConstant> getTupleFromGroundSentence( GdlSentence sentence) { if(sentence instanceof GdlProposition) return Collections.emptyList(); //A simple crawl through the sentence. List<GdlConstant> tuple = new ArrayList<GdlConstant>(); try { addBodyToGroundTuple(sentence.getBody(), tuple); } catch(RuntimeException e) { throw new RuntimeException(e.getMessage() + "\nSentence was " + sentence); } return tuple; } private static void addBodyToGroundTuple(List<GdlTerm> body, List<GdlConstant> tuple) { for(GdlTerm term : body) { if(term instanceof GdlConstant) { tuple.add((GdlConstant) term); } else if(term instanceof GdlVariable) { throw new RuntimeException("Asking for a ground tuple of a non-ground sentence"); } else if(term instanceof GdlFunction){ GdlFunction function = (GdlFunction) term; addBodyToGroundTuple(function.getBody(), tuple); } else { throw new RuntimeException("Unforeseen Gdl tupe in SentenceModel.addBodyToTuple()"); } } } public Set<String> getSentenceNames() { //return sentences.keySet(); Set<String> sentenceNames = new HashSet<String>(); for(String taggedName : sentences.keySet()) { sentenceNames.add(taggedName); } return sentenceNames; } public List<TermModel> getBody(String relationName) { return sentences.get(relationName); } public void addSentence(String newName, String sentenceToCopy) { //System.out.println(newName + ", " + sentenceToCopy); //System.out.println("stc: " + sentences.get(sentenceToCopy)); if(!sentences.containsKey(newName)) sentences.put(newName, copy(sentences.get(sentenceToCopy))); else injectIntoSameSentenceForm(sentences.get(newName), sentences.get(sentenceToCopy)); } private void injectIntoSameSentenceForm(List<TermModel> newBody, List<TermModel> oldBody) { if(newBody.size() != oldBody.size()) throw new RuntimeException(); //A one-to-one injection for(int i = 0; i < oldBody.size(); i++) { newBody.get(i).inject(oldBody.get(i)); } } private Set<SentenceForm> sentenceForms = null; public Set<SentenceForm> getSentenceForms() { if(sentenceForms == null) { sentenceForms = new HashSet<SentenceForm>(); for(Gdl gdl : description) { if(gdl instanceof GdlRelation) { extractSentenceForms(sentenceForms, (GdlRelation) gdl); } else if(gdl instanceof GdlRule) { GdlRule rule = (GdlRule) gdl; extractSentenceForms(sentenceForms, rule.getHead()); for(GdlLiteral literal : rule.getBody()) extractSentenceForms(sentenceForms, literal); } } if(!ignoreLanguageRules) addImpliedSentenceForms(sentenceForms); sentenceForms = Collections.unmodifiableSet(sentenceForms); } return sentenceForms; } private void addImpliedSentenceForms(Set<SentenceForm> forms) { Set<SentenceForm> formsToAdd = new HashSet<SentenceForm>(); for(SentenceForm form : forms) { if(form.getName().getValue().equalsIgnoreCase("next")) formsToAdd.add(form.getCopyWithName("true")); else if(form.getName().getValue().equalsIgnoreCase("init")) formsToAdd.add(form.getCopyWithName("true")); else if(form.getName().getValue().equalsIgnoreCase("legal")) formsToAdd.add(form.getCopyWithName("does")); } forms.addAll(formsToAdd); } private Map<SentenceForm, Set<GdlRelation>> relationsByForm = new HashMap<SentenceForm, Set<GdlRelation>>(); public Set<GdlRelation> getRelations(SentenceForm form) { if(relationsByForm.get(form) == null) { Set<GdlRelation> relations = new HashSet<GdlRelation>(); //build it... for(Gdl gdl : description) { if(gdl instanceof GdlRelation) { GdlRelation relation = (GdlRelation) gdl; if(form.matches(relation)) relations.add(relation); } } relationsByForm.put(form, relations); } return relationsByForm.get(form); } private Map<SentenceForm, Set<GdlRule>> rulesByForm = new HashMap<SentenceForm, Set<GdlRule>>(); /** * Returns the rules that GENERATE the sentence form, not necessarily * all the rules that contain it. * * Note that if functions can be assigned to variables, this might not * find all the rules capable of generating sentences of the given form. */ public Set<GdlRule> getRules(SentenceForm form) { if(rulesByForm.get(form) == null) { Set<GdlRule> rules = new HashSet<GdlRule>(); for(Gdl gdl : description) { if(gdl instanceof GdlRule) { GdlRule rule = (GdlRule) gdl; if(form.matches(rule.getHead())) rules.add(rule); } } rulesByForm.put(form, rules); } return rulesByForm.get(form); } public SentenceForm getSentenceForm(GdlSentence sentence) { Set<SentenceForm> forms = getSentenceForms(); for(SentenceForm form : forms) if(form.matches(sentence)) return form; return null; } private Map<GdlConstant, Set<SentenceForm>> sentenceFormsByName = null; public Set<SentenceForm> getSentenceFormsWithName(GdlConstant name) { if(sentenceFormsByName == null) { //Build it sentenceFormsByName = new HashMap<GdlConstant, Set<SentenceForm>>(); for(SentenceForm form : getSentenceForms()) { GdlConstant formName = form.getName(); if(!sentenceFormsByName.containsKey(formName)) sentenceFormsByName.put(formName, new HashSet<SentenceForm>()); sentenceFormsByName.get(formName).add(form); } } return sentenceFormsByName.get(name); } public List<Gdl> getDescription() { return Collections.unmodifiableList(description); //return description; } /** * Replaces the given rules with a set of new rules that must be in * some sense equivalent to the old rules. * * Comparing the old and new descriptions, these properties must hold: * - In every state, the sentences of those sentence forms defined in * the original description have the same truth value in each description. * - There may be a new sentence form or multiple new sentence forms in * the new description; these must not be sentence forms with a name that * has special meaning in GDL (e.g. true, legal, goal, base, etc.). * * This allows us to efficiently update the model by simply adding the * new sentence form and leaving the rest of the model intact. If more * changes are needed, consider creating a new SentenceModel with the * full description instead. * * @param oldRules Rules to be removed from the model. * @param newRules Semantically equivalent rules to replace the old rules. */ public void replaceRules(List<GdlRule> oldRules, List<GdlRule> newRules) { //Step 1: Identify the new sentence forms Set<SentenceForm> newForms = new HashSet<SentenceForm>(); for(GdlRule newRule : newRules) newForms.addAll(extractSentenceForms(newRule)); newForms.removeAll(sentenceForms); //Step 2: Replace the rules description.removeAll(oldRules); description.addAll(newRules); //Step 3: Add the new sentence forms (and rules) everywhere needed //Need to update: //sentences //We use the same injection method (and infrastructure) we used earlier. boolean somethingChanged = true; while(somethingChanged) { somethingChanged = false; for(GdlRule newRule : newRules) { somethingChanged |= applyInjection(newRule); } //Continue until nothing changes in any rule } //maxArity: unneeded? //TODO: Better method for dependencyGraph dependencyGraph = null; //TODO: Better methods for these //independentSentenceForms independentSentenceForms = null; //constantSentenceForms constantSentenceForms = null; //dependentSentenceForms dependentSentenceForms = null; //sentenceForms //if(sentenceForms != null) // sentenceForms.addAll(newForms); //We're storing this as an unmodifiable set, so replace instead sentenceForms = null; //relationsByForm: unchanged //rulesByForm for(GdlRule oldRule : oldRules) { SentenceForm headForm = getSentenceForm(oldRule.getHead()); if(rulesByForm.get(headForm) != null) { rulesByForm.get(headForm).remove(oldRule); } } for(GdlRule newRule : newRules) { SentenceForm headForm = getSentenceForm(newRule.getHead()); //This should be an uncommon occurrence... if(rulesByForm.get(headForm) != null) rulesByForm.get(headForm).add(newRule); } //sentenceFormsByName if(sentenceFormsByName != null) { for(SentenceForm form : newForms) { GdlConstant name = form.getName(); if(!sentenceFormsByName.containsKey(name)) sentenceFormsByName.put(name, new HashSet<SentenceForm>()); sentenceFormsByName.get(name).add(form); } } } public Set<GdlConstant> getDomainOfVarInRelation(GdlVariable var, GdlRelation relation) { //Find the intersection of all the places it appears List<TermModel> termModels = getMatchingTermModels(var, relation); Set<GdlConstant> domain = new HashSet<GdlConstant>(); if(termModels.isEmpty()) { //Not in the relation System.err.println("Error: Tried to find the domain of " + var + " in relation " + relation); return Collections.emptySet(); } for(TermModel termModel : termModels) { if(domain.isEmpty()) { domain.addAll(termModel.getConstants()); } else { domain.retainAll(termModel.getConstants()); if(domain.isEmpty()) return domain; } } return domain; } /** * Finds all the instances of a particular term (variable or * constant) in a given sentence and returns the term models * associated with them. Used to find the domain of a variable * in a sentence. * * @param term * @param relation * @return */ private List<TermModel> getMatchingTermModels(GdlTerm term, GdlRelation relation) { List<TermModel> matches = new ArrayList<TermModel>(); //Walk through the form and relation together String sentenceName = relation.getName().getValue(); List<TermModel> bodyModel = sentences.get(sentenceName); List<GdlTerm> body = relation.getBody(); getMatchingTermModels(body, bodyModel, term, matches); return matches; } private void getMatchingTermModels(List<GdlTerm> body, List<TermModel> bodyModel, GdlTerm toMatch, List<TermModel> matches) { for(int i = 0; i < body.size(); i++) { GdlTerm term = body.get(i); TermModel termModel = bodyModel.get(i); if(term instanceof GdlFunction) { GdlFunction function = (GdlFunction) term; List<GdlTerm> functionBody = function.getBody(); List<TermModel> functionBodyModel = termModel.getFunction(function); getMatchingTermModels(functionBody, functionBodyModel, toMatch, matches); } else { if(term.equals(toMatch)) { matches.add(termModel); } } } } }