org.ggp.base.util.prover.aima.AimaProver 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.prover.aima;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.ggp.base.util.gdl.GdlUtils;
import org.ggp.base.util.gdl.grammar.Gdl;
import org.ggp.base.util.gdl.grammar.GdlConstant;
import org.ggp.base.util.gdl.grammar.GdlDistinct;
import org.ggp.base.util.gdl.grammar.GdlLiteral;
import org.ggp.base.util.gdl.grammar.GdlNot;
import org.ggp.base.util.gdl.grammar.GdlOr;
import org.ggp.base.util.gdl.grammar.GdlPool;
import org.ggp.base.util.gdl.grammar.GdlRule;
import org.ggp.base.util.gdl.grammar.GdlSentence;
import org.ggp.base.util.gdl.grammar.GdlVariable;
import org.ggp.base.util.gdl.transforms.DistinctAndNotMover;
import org.ggp.base.util.prover.Prover;
import org.ggp.base.util.prover.aima.cache.ProverCache;
import org.ggp.base.util.prover.aima.knowledge.KnowledgeBase;
import org.ggp.base.util.prover.aima.renamer.VariableRenamer;
import org.ggp.base.util.prover.aima.substituter.Substituter;
import org.ggp.base.util.prover.aima.substitution.Substitution;
import org.ggp.base.util.prover.aima.unifier.Unifier;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
public final class AimaProver implements Prover
{
private final KnowledgeBase knowledgeBase;
private final ProverCache fixedAnswerCache = ProverCache.createMultiThreadedCache();
private AimaProver(List description) {
this.knowledgeBase = new KnowledgeBase(Sets.newHashSet(description));
}
/**
* This is how AimaProvers should usually be constructed.
*/
public static AimaProver create(List description) {
description = DistinctAndNotMover.run(description);
return new AimaProver(description);
}
/**
* This can be used to create an AimaProver with no automatic GDL pre-processing.
* You should generally only use this if you intend to do GDL pre-processing
* yourself.
*/
public static AimaProver createWithoutPreprocessing(List description) {
return new AimaProver(description);
}
private Set ask(GdlSentence query, Set context, boolean askOne)
{
LinkedList goals = new LinkedList();
goals.add(query);
Set answers = new HashSet();
ask(goals, new KnowledgeBase(context), new Substitution(), ProverCache.createSingleThreadedCache(),
new VariableRenamer(), askOne, answers, new RecursionHandler(), new IsConstant());
Set results = new HashSet();
for (Substitution theta : answers)
{
results.add(Substituter.substitute(query, theta));
}
return results;
}
private void ask(LinkedList goals, KnowledgeBase context, Substitution theta, ProverCache cache, VariableRenamer renamer, boolean askOne, Set results, RecursionHandler recursionHandler, IsConstant isConstant)
{
if (goals.isEmpty())
{
results.add(theta);
isConstant.value = true;
return;
}
else
{
GdlLiteral literal = goals.removeFirst();
GdlLiteral qPrime = Substituter.substitute(literal, theta);
if (qPrime instanceof GdlDistinct)
{
GdlDistinct distinct = (GdlDistinct) qPrime;
askDistinct(distinct, goals, context, theta, cache, renamer, askOne, results, recursionHandler, isConstant);
}
else if (qPrime instanceof GdlNot)
{
GdlNot not = (GdlNot) qPrime;
askNot(not, goals, context, theta, cache, renamer, askOne, results, recursionHandler, isConstant);
}
else if (qPrime instanceof GdlOr)
{
GdlOr or = (GdlOr) qPrime;
askOr(or, goals, context, theta, cache, renamer, askOne, results, recursionHandler, isConstant);
}
else
{
GdlSentence sentence = (GdlSentence) qPrime;
askSentence(sentence, goals, context, theta, cache, renamer, askOne, results, recursionHandler, isConstant);
}
goals.addFirst(literal);
}
}
@Override
public Set askAll(GdlSentence query, Set context)
{
return ask(query, context, false);
}
private void askDistinct(GdlDistinct distinct, LinkedList goals, KnowledgeBase context, Substitution theta, ProverCache cache, VariableRenamer renamer, boolean askOne, Set results, RecursionHandler recursionHandler, IsConstant isConstant)
{
if (!distinct.getArg1().equals(distinct.getArg2()))
{
ask(goals, context, theta, cache, renamer, askOne, results, recursionHandler, isConstant);
} else {
isConstant.value = true;
}
}
private void askNot(GdlNot not, LinkedList goals, KnowledgeBase context, Substitution theta, ProverCache cache, VariableRenamer renamer, boolean askOne, Set results, RecursionHandler recursionHandler, IsConstant isConstantRet)
{
LinkedList notGoals = new LinkedList();
notGoals.add(not.getBody());
Set notResults = new HashSet();
boolean isConstant = true;
ask(notGoals, context, theta, cache, renamer, true, notResults, recursionHandler, isConstantRet);
isConstant &= isConstantRet.value;
if (notResults.isEmpty())
{
ask(goals, context, theta, cache, renamer, askOne, results, recursionHandler, isConstantRet);
isConstant &= isConstantRet.value;
}
isConstantRet.value = isConstant;
}
@Override
public GdlSentence askOne(GdlSentence query, Set context)
{
Set results = ask(query, context, true);
return (!results.isEmpty()) ? results.iterator().next() : null;
}
private void askOr(GdlOr or, LinkedList goals, KnowledgeBase context, Substitution theta, ProverCache cache, VariableRenamer renamer, boolean askOne, Set results, RecursionHandler recursionHandler, IsConstant isConstantRet)
{
boolean isConstant = true;
for (int i = 0; i < or.arity(); i++)
{
goals.addFirst(or.get(i));
ask(goals, context, theta, cache, renamer, askOne, results, recursionHandler, isConstantRet);
isConstant &= isConstantRet.value;
goals.removeFirst();
if (askOne && (!results.isEmpty()))
{
break;
}
}
isConstantRet.value = isConstant;
}
private void askSentence(GdlSentence sentence, LinkedList goals, KnowledgeBase context, Substitution theta, ProverCache cache, VariableRenamer renamer, boolean askOne, Set results, RecursionHandler recursionHandler,
IsConstant isConstantRet) {
Collection sentenceResults = findSentenceResults(sentence,
context, theta, cache, renamer, recursionHandler, isConstantRet);
boolean isConstant = isConstantRet.value;
for (Substitution thetaPrime : sentenceResults)
{
ask(goals, context, theta.compose(thetaPrime), cache, renamer, askOne, results, recursionHandler, isConstantRet);
isConstant &= isConstantRet.value;
if (askOne && (!results.isEmpty()))
{
break;
}
}
isConstantRet.value = isConstant;
}
private Collection findSentenceResults(GdlSentence sentence,
KnowledgeBase context, Substitution theta,
ProverCache cache, VariableRenamer renamer, RecursionHandler recursionHandler,
IsConstant isConstantRet) {
GdlSentence varRenamedSentence = new VariableRenamer().rename(sentence);
if (!fixedAnswerCache.contains(varRenamedSentence) && !cache.contains(varRenamedSentence))
{
if (recursionHandler.alreadyAsking.contains(varRenamedSentence)) {
//Mark that we're in recursive mode and shouldn't cache results
recursionHandler.calledRecursively.add(varRenamedSentence);
//Return stuff that we've seen as an answer for this before
Collection previousResults = recursionHandler.previousResults.get(varRenamedSentence);
List results = Lists.newArrayListWithCapacity(previousResults.size());
for (GdlSentence knownResult : previousResults) {
results.add(Unifier.unify(sentence, knownResult));
}
return results;
}
recursionHandler.alreadyAsking.add(varRenamedSentence);
List candidates = new ArrayList();
candidates.addAll(knowledgeBase.fetch(sentence));
candidates.addAll(context.fetch(sentence));
boolean isConstant = !isTrueOrDoesSentence(sentence);
Set sentenceResults = new HashSet();
for (GdlRule rule : candidates)
{
GdlRule r = renamer.rename(rule);
Substitution thetaPrime = Unifier.unify(r.getHead(), sentence);
if (thetaPrime != null)
{
LinkedList sentenceGoals = new LinkedList();
for (int i = 0; i < r.arity(); i++)
{
sentenceGoals.add(r.get(i));
}
ask(sentenceGoals, context, theta.compose(thetaPrime), cache, renamer, false, sentenceResults, recursionHandler, isConstantRet);
isConstant &= isConstantRet.value;
}
}
if (recursionHandler.calledRecursively.contains(varRenamedSentence)) {
Set sentencesFromResults = Sets.newHashSet();
for (Substitution result : sentenceResults) {
sentencesFromResults.add(Substituter.substitute(sentence, result));
}
while (sentencesFromResults.size() > recursionHandler.previousResults.get(varRenamedSentence).size()) {
recursionHandler.calledRecursively.remove(varRenamedSentence);
recursionHandler.previousResults.putAll(varRenamedSentence, sentencesFromResults);
sentenceResults = Sets.newHashSet();
for (GdlRule rule : candidates) {
GdlRule r = renamer.rename(rule);
Substitution thetaPrime = Unifier.unify(r.getHead(), sentence);
if (thetaPrime != null) {
LinkedList sentenceGoals = new LinkedList();
for (int i = 0; i < r.arity(); i++) {
sentenceGoals.add(r.get(i));
}
ask(sentenceGoals, context, theta.compose(thetaPrime), cache, renamer, false, sentenceResults, recursionHandler, isConstantRet);
isConstant &= isConstantRet.value;
}
}
sentencesFromResults = Sets.newHashSet();
for (Substitution result : sentenceResults) {
sentencesFromResults.add(Substituter.substitute(sentence, result));
}
}
recursionHandler.calledRecursively.remove(varRenamedSentence);
}
recursionHandler.alreadyAsking.remove(varRenamedSentence);
recursionHandler.previousResults.removeAll(varRenamedSentence);
isConstantRet.value = isConstant;
if (recursionHandler.calledRecursively.isEmpty()) {
if (isConstant) {
fixedAnswerCache.put(sentence, varRenamedSentence, sentenceResults);
} else {
cache.put(sentence, varRenamedSentence, sentenceResults);
}
}
/*
* We filter results because they normally contain entries for all the variables
* encountered in subqueries, not just the sentence we're interested in. For some
* games (e.g. ruleDepthExponential) this is very expensive.
*/
return filterSentenceResults(sentence, sentenceResults);
}
List cachedResults = fixedAnswerCache.get(sentence, varRenamedSentence);
isConstantRet.value = (cachedResults != null);
if (cachedResults == null) {
cachedResults = cache.get(sentence, varRenamedSentence);
}
return cachedResults;
}
private Collection filterSentenceResults(
GdlSentence sentence, Set sentenceResults) {
Set varsInSentence = GdlUtils.getVariablesSet(sentence);
List results = Lists.newArrayListWithCapacity(sentenceResults.size());
for (Substitution result : sentenceResults) {
Substitution fixedResult = new Substitution();
for (GdlVariable var : varsInSentence) {
fixedResult.put(var, result.get(var));
}
results.add(fixedResult);
}
return results;
}
private boolean isTrueOrDoesSentence(GdlSentence sentence) {
GdlConstant name = sentence.getName();
return name == GdlPool.TRUE || name == GdlPool.DOES;
}
@Override
public boolean prove(GdlSentence query, Set context)
{
return askOne(query, context) != null;
}
/*
* Mutable value holder; gets modified by methods it's passed to, as a kind of
* additional return value. Tracks whether queries involve "true" or "does" sentences;
* if not, their answers can be added to the fixedAnswerCache and reused across queries.
*/
private static class IsConstant {
public boolean value = true;
}
/*
* Contains some mutable values used by the recursion implementation, to reduce
* the number of arguments being passed around.
*
* The general approach to handle recursion is to check for cases where we're
* querying a sentence we're already in the middle of querying. In that case, we
* return all the sentences we've found so far in the recursive query (initially
* the empty set), and we re-run the outer query until the number of sentences
* returned stops growing. (GDL restricts recursion in such a way that for a valid
* game description, no sentences will stop being true because we added a new sentence.)
* We also stop the caching of intermediate results while running a recursive query.
*
* This is not necessarily the most efficient approach, but it gives correct results.
*/
private static class RecursionHandler {
public Set alreadyAsking = Sets.newHashSet();
public Set calledRecursively = Sets.newHashSet();
public Multimap previousResults = HashMultimap.create();
}
}