aima.core.logic.fol.inference.FOLModelElimination Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aima-core Show documentation
Show all versions of aima-core Show documentation
AIMA-Java Core Algorithms from the book Artificial Intelligence a Modern Approach 3rd Ed.
The newest version!
package aima.core.logic.fol.inference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import aima.core.logic.fol.Connectors;
import aima.core.logic.fol.StandardizeApartInPlace;
import aima.core.logic.fol.SubstVisitor;
import aima.core.logic.fol.SubsumptionElimination;
import aima.core.logic.fol.Unifier;
import aima.core.logic.fol.inference.proof.Proof;
import aima.core.logic.fol.inference.proof.ProofFinal;
import aima.core.logic.fol.inference.proof.ProofStepChainCancellation;
import aima.core.logic.fol.inference.proof.ProofStepChainDropped;
import aima.core.logic.fol.inference.proof.ProofStepChainFromClause;
import aima.core.logic.fol.inference.proof.ProofStepChainReduction;
import aima.core.logic.fol.inference.proof.ProofStepGoal;
import aima.core.logic.fol.inference.trace.FOLModelEliminationTracer;
import aima.core.logic.fol.kb.FOLKnowledgeBase;
import aima.core.logic.fol.kb.data.Chain;
import aima.core.logic.fol.kb.data.Clause;
import aima.core.logic.fol.kb.data.Literal;
import aima.core.logic.fol.kb.data.ReducedLiteral;
import aima.core.logic.fol.parsing.ast.AtomicSentence;
import aima.core.logic.fol.parsing.ast.ConnectedSentence;
import aima.core.logic.fol.parsing.ast.NotSentence;
import aima.core.logic.fol.parsing.ast.Sentence;
import aima.core.logic.fol.parsing.ast.Term;
import aima.core.logic.fol.parsing.ast.Variable;
/**
* Based on lecture notes from:
*
* http://logic.stanford.edu/classes/cs157/2008/lectures/lecture13.pdf
*
* @author Ciaran O'Reilly
*
*/
public class FOLModelElimination implements InferenceProcedure {
// Ten seconds is default maximum query time permitted
private long maxQueryTime = 10 * 1000;
//
private FOLModelEliminationTracer tracer = null;
//
private Unifier unifier = new Unifier();
private SubstVisitor substVisitor = new SubstVisitor();
public FOLModelElimination() {
}
public FOLModelElimination(long maxQueryTime) {
setMaxQueryTime(maxQueryTime);
}
public FOLModelElimination(FOLModelEliminationTracer tracer) {
this.tracer = tracer;
}
public FOLModelElimination(FOLModelEliminationTracer tracer,
long maxQueryTime) {
this.tracer = tracer;
setMaxQueryTime(maxQueryTime);
}
public long getMaxQueryTime() {
return maxQueryTime;
}
public void setMaxQueryTime(long maxQueryTime) {
this.maxQueryTime = maxQueryTime;
}
//
// START-InferenceProcedure
public InferenceResult ask(FOLKnowledgeBase kb, Sentence query) {
//
// Get the background knowledge - are assuming this is satisfiable
// as using Set of Support strategy.
Set bgClauses = new LinkedHashSet(kb.getAllClauses());
bgClauses.removeAll(SubsumptionElimination
.findSubsumedClauses(bgClauses));
List background = createChainsFromClauses(bgClauses);
// Collect the information necessary for constructing
// an answer (supports use of answer literals).
AnswerHandler ansHandler = new AnswerHandler(kb, query, maxQueryTime);
IndexedFarParents ifps = new IndexedFarParents(
ansHandler.getSetOfSupport(), background);
// Iterative deepening to be used
for (int maxDepth = 1; maxDepth < Integer.MAX_VALUE; maxDepth++) {
// Track the depth actually reached
ansHandler.resetMaxDepthReached();
if (null != tracer) {
tracer.reset();
}
for (Chain nearParent : ansHandler.getSetOfSupport()) {
recursiveDLS(maxDepth, 0, nearParent, ifps, ansHandler);
if (ansHandler.isComplete()) {
return ansHandler;
}
}
// This means the search tree
// has bottomed out (i.e. finite).
// Return what I know based on exploring everything.
if (ansHandler.getMaxDepthReached() < maxDepth) {
return ansHandler;
}
}
return ansHandler;
}
// END-InferenceProcedure
//
//
// PRIVATE METHODS
//
private List createChainsFromClauses(Set clauses) {
List chains = new ArrayList();
for (Clause c : clauses) {
Chain chn = new Chain(c.getLiterals());
chn.setProofStep(new ProofStepChainFromClause(chn, c));
chains.add(chn);
chains.addAll(chn.getContrapositives());
}
return chains;
}
// Recursive Depth Limited Search
private void recursiveDLS(int maxDepth, int currentDepth, Chain nearParent,
IndexedFarParents indexedFarParents, AnswerHandler ansHandler) {
// Keep track of the maximum depth reached.
ansHandler.updateMaxDepthReached(currentDepth);
if (currentDepth == maxDepth) {
return;
}
int noCandidateFarParents = indexedFarParents
.getNumberCandidateFarParents(nearParent);
if (null != tracer) {
tracer.increment(currentDepth, noCandidateFarParents);
}
indexedFarParents.standardizeApart(nearParent);
for (int farParentIdx = 0; farParentIdx < noCandidateFarParents; farParentIdx++) {
// If have a complete answer, don't keep
// checking candidate far parents
if (ansHandler.isComplete()) {
break;
}
// Reduction
Chain nextNearParent = indexedFarParents.attemptReduction(
nearParent, farParentIdx);
if (null == nextNearParent) {
// Unable to remove the head via reduction
continue;
}
// Handle Canceling and Dropping
boolean cancelled = false;
boolean dropped = false;
do {
cancelled = false;
Chain nextParent = null;
while (nextNearParent != (nextParent = tryCancellation(nextNearParent))) {
nextNearParent = nextParent;
cancelled = true;
}
dropped = false;
while (nextNearParent != (nextParent = tryDropping(nextNearParent))) {
nextNearParent = nextParent;
dropped = true;
}
} while (dropped || cancelled);
// Check if have answer before
// going to the next level
if (!ansHandler.isAnswer(nextNearParent)) {
// Keep track of the current # of
// far parents that are possible for the next near parent.
int noNextFarParents = indexedFarParents
.getNumberFarParents(nextNearParent);
// Add to indexed far parents
nextNearParent = indexedFarParents.addToIndex(nextNearParent);
// Check the next level
recursiveDLS(maxDepth, currentDepth + 1, nextNearParent,
indexedFarParents, ansHandler);
// Reset the number of far parents possible
// when recursing back up.
indexedFarParents.resetNumberFarParentsTo(nextNearParent,
noNextFarParents);
}
}
}
// Returns c if no cancellation occurred
private Chain tryCancellation(Chain c) {
Literal head = c.getHead();
if (null != head && !(head instanceof ReducedLiteral)) {
for (Literal l : c.getTail()) {
if (l instanceof ReducedLiteral) {
// if they can be resolved
if (head.isNegativeLiteral() != l.isNegativeLiteral()) {
Map subst = unifier
.unify(head.getAtomicSentence(),
l.getAtomicSentence());
if (null != subst) {
// I have a cancellation
// Need to apply subst to all of the
// literals in the cancellation
List cancLits = new ArrayList();
for (Literal lfc : c.getTail()) {
AtomicSentence a = (AtomicSentence) substVisitor
.subst(subst, lfc.getAtomicSentence());
cancLits.add(lfc.newInstance(a));
}
Chain cancellation = new Chain(cancLits);
cancellation
.setProofStep(new ProofStepChainCancellation(
cancellation, c, subst));
return cancellation;
}
}
}
}
}
return c;
}
// Returns c if no dropping occurred
private Chain tryDropping(Chain c) {
Literal head = c.getHead();
if (null != head && (head instanceof ReducedLiteral)) {
Chain dropped = new Chain(c.getTail());
dropped.setProofStep(new ProofStepChainDropped(dropped, c));
return dropped;
}
return c;
}
class AnswerHandler implements InferenceResult {
private Chain answerChain = new Chain();
private Set answerLiteralVariables;
private List sos = null;
private boolean complete = false;
private long finishTime = 0L;
private int maxDepthReached = 0;
private List proofs = new ArrayList();
private boolean timedOut = false;
public AnswerHandler(FOLKnowledgeBase kb, Sentence query,
long maxQueryTime) {
finishTime = System.currentTimeMillis() + maxQueryTime;
Sentence refutationQuery = new NotSentence(query);
// Want to use an answer literal to pull
// query variables where necessary
Literal answerLiteral = kb.createAnswerLiteral(refutationQuery);
answerLiteralVariables = kb.collectAllVariables(answerLiteral
.getAtomicSentence());
// Create the Set of Support based on the Query.
if (answerLiteralVariables.size() > 0) {
Sentence refutationQueryWithAnswer = new ConnectedSentence(
Connectors.OR, refutationQuery, answerLiteral
.getAtomicSentence().copy());
sos = createChainsFromClauses(kb
.convertToClauses(refutationQueryWithAnswer));
answerChain.addLiteral(answerLiteral);
} else {
sos = createChainsFromClauses(kb
.convertToClauses(refutationQuery));
}
for (Chain s : sos) {
s.setProofStep(new ProofStepGoal(s));
}
}
//
// START-InferenceResult
public boolean isPossiblyFalse() {
return !timedOut && proofs.size() == 0;
}
public boolean isTrue() {
return proofs.size() > 0;
}
public boolean isUnknownDueToTimeout() {
return timedOut && proofs.size() == 0;
}
public boolean isPartialResultDueToTimeout() {
return timedOut && proofs.size() > 0;
}
public List getProofs() {
return proofs;
}
// END-InferenceResult
//
public List getSetOfSupport() {
return sos;
}
public boolean isComplete() {
return complete;
}
public void resetMaxDepthReached() {
maxDepthReached = 0;
}
public int getMaxDepthReached() {
return maxDepthReached;
}
public void updateMaxDepthReached(int depth) {
if (depth > maxDepthReached) {
maxDepthReached = depth;
}
}
public boolean isAnswer(Chain nearParent) {
boolean isAns = false;
if (answerChain.isEmpty()) {
if (nearParent.isEmpty()) {
proofs.add(new ProofFinal(nearParent.getProofStep(),
new HashMap()));
complete = true;
isAns = true;
}
} else {
if (nearParent.isEmpty()) {
// This should not happen
// as added an answer literal to sos, which
// implies the database (i.e. premises) are
// unsatisfiable to begin with.
throw new IllegalStateException(
"Generated an empty chain while looking for an answer, implies original KB is unsatisfiable");
}
if (1 == nearParent.getNumberLiterals()
&& nearParent
.getHead()
.getAtomicSentence()
.getSymbolicName()
.equals(answerChain.getHead()
.getAtomicSentence().getSymbolicName())) {
Map answerBindings = new HashMap();
List answerTerms = nearParent.getHead()
.getAtomicSentence().getArgs();
int idx = 0;
for (Variable v : answerLiteralVariables) {
answerBindings.put(v, answerTerms.get(idx));
idx++;
}
boolean addNewAnswer = true;
for (Proof p : proofs) {
if (p.getAnswerBindings().equals(answerBindings)) {
addNewAnswer = false;
break;
}
}
if (addNewAnswer) {
proofs.add(new ProofFinal(nearParent.getProofStep(),
answerBindings));
}
isAns = true;
}
}
if (System.currentTimeMillis() > finishTime) {
complete = true;
// Indicate that I have run out of query time
timedOut = true;
}
return isAns;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("isComplete=" + complete);
sb.append("\n");
sb.append("result=" + proofs);
return sb.toString();
}
}
}
class IndexedFarParents {
//
private int saIdx = 0;
private Unifier unifier = new Unifier();
private SubstVisitor substVisitor = new SubstVisitor();
//
private Map> posHeads = new LinkedHashMap>();
private Map> negHeads = new LinkedHashMap>();
public IndexedFarParents(List sos, List background) {
constructInternalDataStructures(sos, background);
}
public int getNumberFarParents(Chain farParent) {
Literal head = farParent.getHead();
Map> heads = null;
if (head.isPositiveLiteral()) {
heads = posHeads;
} else {
heads = negHeads;
}
String headKey = head.getAtomicSentence().getSymbolicName();
List farParents = heads.get(headKey);
if (null != farParents) {
return farParents.size();
}
return 0;
}
public void resetNumberFarParentsTo(Chain farParent, int toSize) {
Literal head = farParent.getHead();
Map> heads = null;
if (head.isPositiveLiteral()) {
heads = posHeads;
} else {
heads = negHeads;
}
String key = head.getAtomicSentence().getSymbolicName();
List farParents = heads.get(key);
while (farParents.size() > toSize) {
farParents.remove(farParents.size() - 1);
}
}
public int getNumberCandidateFarParents(Chain nearParent) {
Literal nearestHead = nearParent.getHead();
Map> candidateHeads = null;
if (nearestHead.isPositiveLiteral()) {
candidateHeads = negHeads;
} else {
candidateHeads = posHeads;
}
String nearestKey = nearestHead.getAtomicSentence().getSymbolicName();
List farParents = candidateHeads.get(nearestKey);
if (null != farParents) {
return farParents.size();
}
return 0;
}
public Chain attemptReduction(Chain nearParent, int farParentIndex) {
Chain nnpc = null;
Literal nearLiteral = nearParent.getHead();
Map> candidateHeads = null;
if (nearLiteral.isPositiveLiteral()) {
candidateHeads = negHeads;
} else {
candidateHeads = posHeads;
}
AtomicSentence nearAtom = nearLiteral.getAtomicSentence();
String nearestKey = nearAtom.getSymbolicName();
List farParents = candidateHeads.get(nearestKey);
if (null != farParents) {
Chain farParent = farParents.get(farParentIndex);
standardizeApart(farParent);
Literal farLiteral = farParent.getHead();
AtomicSentence farAtom = farLiteral.getAtomicSentence();
Map subst = unifier.unify(nearAtom, farAtom);
// If I was able to unify with one
// of the far heads
if (null != subst) {
// Want to always apply reduction uniformly
Chain topChain = farParent;
Literal botLit = nearLiteral;
Chain botChain = nearParent;
// Need to apply subst to all of the
// literals in the reduction
List reduction = new ArrayList();
for (Literal l : topChain.getTail()) {
AtomicSentence atom = (AtomicSentence) substVisitor.subst(
subst, l.getAtomicSentence());
reduction.add(l.newInstance(atom));
}
reduction.add(new ReducedLiteral((AtomicSentence) substVisitor
.subst(subst, botLit.getAtomicSentence()), botLit
.isNegativeLiteral()));
for (Literal l : botChain.getTail()) {
AtomicSentence atom = (AtomicSentence) substVisitor.subst(
subst, l.getAtomicSentence());
reduction.add(l.newInstance(atom));
}
nnpc = new Chain(reduction);
nnpc.setProofStep(new ProofStepChainReduction(nnpc, nearParent,
farParent, subst));
}
}
return nnpc;
}
public Chain addToIndex(Chain c) {
Chain added = null;
Literal head = c.getHead();
if (null != head) {
Map> toAddTo = null;
if (head.isPositiveLiteral()) {
toAddTo = posHeads;
} else {
toAddTo = negHeads;
}
String key = head.getAtomicSentence().getSymbolicName();
List farParents = toAddTo.get(key);
if (null == farParents) {
farParents = new ArrayList();
toAddTo.put(key, farParents);
}
added = c;
farParents.add(added);
}
return added;
}
public void standardizeApart(Chain c) {
saIdx = StandardizeApartInPlace.standardizeApart(c, saIdx);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("#");
sb.append(posHeads.size());
for (String key : posHeads.keySet()) {
sb.append(",");
sb.append(posHeads.get(key).size());
}
sb.append(" posHeads=");
sb.append(posHeads.toString());
sb.append("\n");
sb.append("#");
sb.append(negHeads.size());
for (String key : negHeads.keySet()) {
sb.append(",");
sb.append(negHeads.get(key).size());
}
sb.append(" negHeads=");
sb.append(negHeads.toString());
return sb.toString();
}
//
// PRIVATE METHODS
//
private void constructInternalDataStructures(List sos,
List background) {
List toIndex = new ArrayList();
toIndex.addAll(sos);
toIndex.addAll(background);
for (Chain c : toIndex) {
addToIndex(c);
}
}
}