public static List<Expression> Run(IList<Expression> description) { // This class is not put together in any "optimal" way, so it's left in an unpolished state for // now. A better version would use estimates of the impact of breaking apart rules. (It also needs // to stop itself from making multiple new relations with the same meaning.) //This version will be rather advanced. In particular, it will try to incorporate //1) More thorough scanning for condensations; //2) Condensations that are only safe to perform because of mutexes. //TODO: Don't perform condensations on stuff like (Add _ _ _)... //In general, don't perform condensations where the headroom is huge? //Better yet... DON'T perform condensations on recursive functions! //As for headroom... maybe make sure that # of vars eliminated > # "kept" //Or make sure none are kept? Use directional connected components? //How do we define a condensation, and what needs to be true in it? //Definition: A condensation set is a set of conjuncts of a sentence. //Restrictions: //1) There must be some variable not in the head of the sentence that appears exclusively in the // condensation set. (This means we can easily find sets one of which must be a condensation set.) //2) For any variable appearing in a distinct or not conjunct in the set, there must be a positive // conjunct in the set also containing that variable. This does apply to variables found in the head. //3) There must be at least one non-distinct literal outside the condensation set. //How mutexes work: //Say we have a rule // (<= (r1 ?b) // (r2 ?a ?b ?c) // (r3 ?b ?c) // (r4 ?a) // (r5 ?c)) //If we wanted to factor out ?a, we'd normally have to do /* (<= (r6 ?b ?c) * (r2 ?a ?b ?c) * (r4 ?a)) * (<= (r1 ?b) * (r6 ?b ?c) * (r3 ?b ?c) * (r5 ?c)) * But if we know r2 is a mutex, instead we can do (notice r2 splitting): * (<= (r6 ?b) * (r2 ?a ?b ?c) * (r4 ?a)) * (<= (r1 ?b) * (r2 ?a ?b ?c) * (r6 ?b) * (r3 ?b ?c) * (r5 ?c)) * Which in turn becomes: * (<= (r6 ?b) * (r2 ?a ?b ?c) * (r4 ?a)) * (<= (r7 ?b) * (r2 ?a ?b ?c) * (r3 ?b ?c) * (r5 ?c)) * (<= (r1 ?b) * (r6 ?b) * (r7 ?b)) * Both r6 and r7 can be further condensed to ignore ?c and ?a, respectively. What just happened? * 1) The condensation set for ?a included the mutex r2. * 2) r2 (by itself) would have required ?c to be included as an argument passed back to the * original rule, which is undesirable. Instead, as it's a mutex, we leave a copy in the * original rule and don't include the ?c. * * So, what kind of algorithm can we find to solve this task? */ var newDescription = new List<Expression>(); var rulesToAdd = new Queue<Implication>(); foreach (Expression gdl in description) { var implication = gdl as Implication; if (implication == null) newDescription.Add(gdl); else rulesToAdd.Enqueue(implication); } //Don't use the model indiscriminately; it reflects the old description, not necessarily the new one ISentenceDomainModel model = SentenceDomainModelFactory.CreateWithCartesianDomains(description); model = SentenceDomainModelOptimizer.RestrictDomainsToUsefulValues(model); var sentenceNameSource = new UnusedSentenceNameSource(model); //TODO: ConstantChecker constantChecker = ConstantCheckerFactory.createWithForwardChaining(model); IConstantChecker constantChecker = ConstantCheckerFactory.CreateWithProver(model); ISet<ISentenceForm> constantForms = model.ConstantSentenceForms; ISentenceDomain condensorDomain = model; var curDescription = new List<Expression>(description); while (rulesToAdd.Any()) { Implication curRule = rulesToAdd.Dequeue(); if (IsRecursive(curRule)) { //Don't mess with it! newDescription.Add(curRule); continue; } Fact curRuleHead = curRule.Consequent; if (SentenceFormAdder.InSentenceFormGroup(curRuleHead, constantForms)) { newDescription.Add(curRule); continue; } ISet<Expression> condensationSet = GetCondensationSet(curRule, condensorDomain, constantChecker, sentenceNameSource); if (condensationSet != null) { List<Implication> newRules = ApplyCondensation(condensationSet, curRule, sentenceNameSource); newRules.ForEach(rulesToAdd.Enqueue); //Since we're making only small changes, we can readjust //the model as we go, instead of recomputing it var oldRules = new List<Expression> { curRule }; var replacementDescription = new List<Expression>(curDescription); replacementDescription.RemoveAll(oldRules.Contains); replacementDescription.AddRange(newRules); curDescription = replacementDescription; condensorDomain = AugmentModelWithNewForm(condensorDomain, newRules); } else newDescription.Add(curRule); } return newDescription; }
private static ISet<Expression> GetCondensationSet(Implication rule, ISentenceDomain model, IConstantChecker checker, UnusedSentenceNameSource sentenceNameSource) { //We use each variable as a starting point List<TermVariable> varsInRule = rule.VariablesOrEmpty.ToList(); List<TermVariable> varsInHead = rule.Consequent.VariablesOrEmpty.ToList(); var varsNotInHead = new List<TermVariable>(varsInRule); varsNotInHead.RemoveAll(varsInHead.Contains); foreach (TermVariable var in varsNotInHead) { var minSet = new HashSet<Expression>(); foreach (Expression literal in rule.Antecedents.Conjuncts) if (literal.VariablesOrEmpty.Contains(var)) minSet.Add(literal); //#1 is already done //Now we try #2 var varsNeeded = new HashSet<TermVariable>(); var varsSupplied = new HashSet<TermVariable>(); foreach (Expression literal in minSet) { if (literal is Negation) { foreach (var variable in literal.VariablesOrEmpty) varsNeeded.Add(variable); } else if (literal is Fact) { if (((Fact)literal).RelationName == GameContainer.Parser.TokDistinct) foreach (var variable in literal.VariablesOrEmpty) varsNeeded.Add(variable); else foreach (var variable in literal.VariablesOrEmpty) varsSupplied.Add(variable); } } varsNeeded.RemoveWhere(varsSupplied.Contains); if (varsNeeded.Any()) continue; var candidateSuppliersList = new List<ISet<Expression>>(); foreach (TermVariable varNeeded in varsNeeded) { var suppliers = new HashSet<Expression>(); foreach (Expression literal in rule.Antecedents.Conjuncts) if (literal is Fact) if (literal.VariablesOrEmpty.Contains(varNeeded)) suppliers.Add(literal); candidateSuppliersList.Add(suppliers); } //TODO: Now... I'm not sure if we want to minimize the number of literals added, or the number of variables added //Right now, I don't have time to worry about optimization. Currently, we pick one at random //TODO: Optimize this var literalsToAdd = new HashSet<Expression>(); foreach (ISet<Expression> suppliers in candidateSuppliersList) if (!suppliers.Intersect(literalsToAdd).Any()) literalsToAdd.Add(suppliers.First()); minSet.UnionWith(literalsToAdd); if (GoodCondensationSetByHeuristic(minSet, rule, model, checker, sentenceNameSource)) return minSet; } return null; }
private static bool GoodCondensationSetByHeuristic(ICollection<Expression> minSet, Implication rule, ISentenceDomain model, IConstantChecker checker, UnusedSentenceNameSource sentenceNameSource) { //We actually want the sentence model here so we can see the domains //also, if it's a constant, ... //Anyway... we want to compare the heuristic for the number of assignments //and/or links that will be generated with or without the condensation set //Heuristic for a rule is A*(L+1), where A is the number of assignments and //L is the number of literals, unless L = 1, in which case the heuristic is //just A. This roughly captures the number of links that would be generated //if this rule were to be generated. //Obviously, there are differing degrees of accuracy with which we can //represent A. //One way is taking the product of all the variables in all the domains. //However, we can do better by actually asking the Assignments class for //its own heuristic of how it would implement the rule as-is. //The only tricky aspect here is that we need an up-to-date SentenceModel, //and in some cases this could be expensive to compute. Might as well try //it, though... //Heuristic for the rule as-is: long assignments = AssignmentsImpl.GetNumAssignmentsEstimate(rule, SentenceDomainModels.GetVarDomains(rule, model, SentenceDomainModels.VarDomainOpts.IncludeHead), checker); int literals = rule.Consequent.Arity; if (literals > 1) literals++; //We have to "and" the literals together //Note that even though constants will be factored out, we're concerned here //with getting through them in a reasonable amount of time, so we do want to //count them. TODO: Not sure if they should be counted in L, though... long curRuleHeuristic = assignments * literals; //And if we split them up... List<Implication> newRules = ApplyCondensation(minSet, rule, sentenceNameSource); Implication r1 = newRules[0], r2 = newRules[1]; //Augment the model ISentenceDomain newModel = AugmentModelWithNewForm(model, newRules); long a1 = AssignmentsImpl.GetNumAssignmentsEstimate(r1, SentenceDomainModels.GetVarDomains(r1, newModel, SentenceDomainModels.VarDomainOpts.IncludeHead), checker); long a2 = AssignmentsImpl.GetNumAssignmentsEstimate(r2, SentenceDomainModels.GetVarDomains(r2, newModel, SentenceDomainModels.VarDomainOpts.IncludeHead), checker); int l1 = r1.Consequent.Arity; if (l1 > 1) l1++; int l2 = r2.Consequent.Arity; if (l2 > 1) l2++; //Whether we split or not depends on what the two heuristics say long newRulesHeuristic = a1 * l1 + a2 * l2; return newRulesHeuristic < curRuleHeuristic; }
private static List<Implication> ApplyCondensation(ICollection<Expression> condensationSet, Implication rule, UnusedSentenceNameSource sentenceNameSource) { var varsInCondensationSet = new HashSet<TermVariable>(); foreach (Expression literal in condensationSet) foreach (var variable in literal.VariablesOrEmpty) varsInCondensationSet.Add(variable); var varsToKeep = new HashSet<TermVariable>(); //Which vars do we "keep" (put in our new condensed literal)? //Vars that are both: //1) In the condensation set, in a non-mutex literal //2) Either in the head or somewhere else outside the condensation set foreach (Expression literal in condensationSet) foreach (var variable in literal.VariablesOrEmpty) varsToKeep.Add(variable); var varsToKeep2 = new HashSet<TermVariable>(); foreach (var variable in rule.Consequent.VariablesOrEmpty) varsToKeep2.Add(variable); foreach (Expression literal in rule.Antecedents.Conjuncts) if (!condensationSet.Contains(literal)) foreach (var variable in literal.VariablesOrEmpty) varsToKeep2.Add(variable); varsToKeep.IntersectWith(varsToKeep2); //Now we're ready to split it apart //Let's make the new rule var orderedVars = new List<Term>(varsToKeep); int condenserName = sentenceNameSource.GetNameWithPrefix(rule.Consequent.RelationName); //Make the rule head var condenserHead = new VariableFact(false, condenserName, orderedVars.ToArray()); var condenserBody = new List<Expression>(condensationSet); var condenserRule = new Implication(condenserHead, condenserBody.ToArray()); //TODO: Look for existing rules matching the new one var remainingLiterals = rule.Antecedents.Conjuncts.Where(literal => !condensationSet.Contains(literal)).ToList(); remainingLiterals.Add(condenserHead); var modifiedRule = new Implication(rule.Consequent, remainingLiterals.ToArray()); var newRules = new List<Implication>(2) { condenserRule, modifiedRule }; return newRules; }