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();
public AimaProver(List<Gdl> description)
{
description = DistinctAndNotMover.run(description);
knowledgeBase = new KnowledgeBase(Sets.newHashSet(description));
}
private Set<GdlSentence> ask(GdlSentence query, Set<GdlSentence> context, boolean askOne)
{
LinkedList<GdlLiteral> goals = new LinkedList<GdlLiteral>();
goals.add(query);
Set<Substitution> answers = new HashSet<Substitution>();
ask(goals, new KnowledgeBase(context), new Substitution(), ProverCache.createSingleThreadedCache(),
new VariableRenamer(), askOne, answers, new RecursionHandler(), new IsConstant());
Set<GdlSentence> results = new HashSet<GdlSentence>();
for (Substitution theta : answers)
{
results.add(Substituter.substitute(query, theta));
}
return results;
}
private void ask(LinkedList<GdlLiteral> goals, KnowledgeBase context, Substitution theta, ProverCache cache, VariableRenamer renamer, boolean askOne, Set<Substitution> 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<GdlSentence> askAll(GdlSentence query, Set<GdlSentence> context)
{
return ask(query, context, false);
}
private void askDistinct(GdlDistinct distinct, LinkedList<GdlLiteral> goals, KnowledgeBase context, Substitution theta, ProverCache cache, VariableRenamer renamer, boolean askOne, Set<Substitution> 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<GdlLiteral> goals, KnowledgeBase context, Substitution theta, ProverCache cache, VariableRenamer renamer, boolean askOne, Set<Substitution> results, RecursionHandler recursionHandler, IsConstant isConstantRet)
{
LinkedList<GdlLiteral> notGoals = new LinkedList<GdlLiteral>();
notGoals.add(not.getBody());
Set<Substitution> notResults = new HashSet<Substitution>();
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<GdlSentence> context)
{
Set<GdlSentence> results = ask(query, context, true);
return (!results.isEmpty()) ? results.iterator().next() : null;
}
private void askOr(GdlOr or, LinkedList<GdlLiteral> goals, KnowledgeBase context, Substitution theta, ProverCache cache, VariableRenamer renamer, boolean askOne, Set<Substitution> 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<GdlLiteral> goals, KnowledgeBase context, Substitution theta, ProverCache cache, VariableRenamer renamer, boolean askOne, Set<Substitution> results, RecursionHandler recursionHandler,
IsConstant isConstantRet) {
Collection<Substitution> 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<Substitution> 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<GdlSentence> previousResults = recursionHandler.previousResults.get(varRenamedSentence);
List<Substitution> results = Lists.newArrayListWithCapacity(previousResults.size());
for (GdlSentence knownResult : previousResults) {
results.add(Unifier.unify(sentence, knownResult));
}
return results;
}
recursionHandler.alreadyAsking.add(varRenamedSentence);
List<GdlRule> candidates = new ArrayList<GdlRule>();
candidates.addAll(knowledgeBase.fetch(sentence));
candidates.addAll(context.fetch(sentence));
boolean isConstant = !isTrueOrDoesSentence(sentence);
Set<Substitution> sentenceResults = new HashSet<Substitution>();
for (GdlRule rule : candidates)
{
GdlRule r = renamer.rename(rule);
Substitution thetaPrime = Unifier.unify(r.getHead(), sentence);
if (thetaPrime != null)
{
LinkedList<GdlLiteral> sentenceGoals = new LinkedList<GdlLiteral>();
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<GdlSentence> 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<GdlLiteral> sentenceGoals = new LinkedList<GdlLiteral>();
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<Substitution> cachedResults = fixedAnswerCache.get(sentence, varRenamedSentence);
isConstantRet.value = (cachedResults != null);
if (cachedResults == null) {
cachedResults = cache.get(sentence, varRenamedSentence);
}
return cachedResults;
}
private Collection<Substitution> filterSentenceResults(
GdlSentence sentence, Set<Substitution> sentenceResults) {
Set<GdlVariable> varsInSentence = GdlUtils.getVariablesSet(sentence);
List<Substitution> 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<GdlSentence> 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<GdlSentence> alreadyAsking = Sets.newHashSet();
public Set<GdlSentence> calledRecursively = Sets.newHashSet();
public Multimap<GdlSentence, GdlSentence> previousResults = HashMultimap.create();
}
}