package aima.core.logic.fol.inference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
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.SubsumptionElimination;
import aima.core.logic.fol.inference.otter.ClauseFilter;
import aima.core.logic.fol.inference.otter.ClauseSimplifier;
import aima.core.logic.fol.inference.otter.LightestClauseHeuristic;
import aima.core.logic.fol.inference.otter.defaultimpl.DefaultClauseFilter;
import aima.core.logic.fol.inference.otter.defaultimpl.DefaultClauseSimplifier;
import aima.core.logic.fol.inference.otter.defaultimpl.DefaultLightestClauseHeuristic;
import aima.core.logic.fol.inference.proof.Proof;
import aima.core.logic.fol.inference.proof.ProofFinal;
import aima.core.logic.fol.inference.proof.ProofStepGoal;
import aima.core.logic.fol.kb.FOLKnowledgeBase;
import aima.core.logic.fol.kb.data.Clause;
import aima.core.logic.fol.kb.data.Literal;
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.TermEquality;
import aima.core.logic.fol.parsing.ast.Variable;
/**
* Artificial Intelligence A Modern Approach (2nd Edition): Figure 9.14, page
* 307.<br>
* <br>
*
* <pre>
* procedure OTTER(sos, usable)
* inputs: sos, a set of support-clauses defining the problem (a global variable)
* usable, background knowledge potentially relevant to the problem
*
* repeat
* clause <- the lightest member of sos
* move clause from sos to usable
* PROCESS(INFER(clause, usable), sos)
* until sos = [] or a refutation has been found
*
* --------------------------------------------------------------------------------
*
* function INFER(clause, usable) returns clauses
*
* resolve clause with each member of usable
* return the resulting clauses after applying filter
*
* --------------------------------------------------------------------------------
*
* procedure PROCESS(clauses, sos)
*
* for each clause in clauses do
* clause <- SIMPLIFY(clause)
* merge identical literals
* discard clause if it is a tautology
* sos <- [clause | sos]
* if clause has no literals then a refutation has been found
* if clause has one literal then look for unit refutation
* </pre>
*
* Figure 9.14 Sketch of the OTTER theorem prover. Heuristic control is applied
* in the selection of the "lightest" clause and in the FILTER function that
* eliminates uninteresting clauses from consideration.<br>
* <br>
* <b>Note:</b> The original implementation of OTTER has been retired but its
* successor, <b>Prover9</b>, can be found at:<br>
* <a href="http://www.prover9.org/">http://www.prover9.org/</a><br>
* or<br>
* <a href="http://www.cs.unm.edu/~mccune/mace4/">http://www.cs.unm.edu/~mccune/
* mace4/</a><br>
* Should you wish to play with a mature implementation of a theorem prover :-)<br>
* <br>
* For lots of interesting problems to play with, see <b>The TPTP Problem
* Library for Automated Theorem Proving</b>:<br>
* <a href="http://www.cs.miami.edu/~tptp/">http://www.cs.miami.edu/~tptp/</a><br>
*
* @author Ciaran O'Reilly
*
*/
public class FOLOTTERLikeTheoremProver implements InferenceProcedure {
//
// Ten seconds is default maximum query time permitted
private long maxQueryTime = 10 * 1000;
private boolean useParamodulation = true;
private LightestClauseHeuristic lightestClauseHeuristic = new DefaultLightestClauseHeuristic();
private ClauseFilter clauseFilter = new DefaultClauseFilter();
private ClauseSimplifier clauseSimplifier = new DefaultClauseSimplifier();
//
private Paramodulation paramodulation = new Paramodulation();
public FOLOTTERLikeTheoremProver() {
}
public FOLOTTERLikeTheoremProver(long maxQueryTime) {
setMaxQueryTime(maxQueryTime);
}
public FOLOTTERLikeTheoremProver(boolean useParamodulation) {
setUseParamodulation(useParamodulation);
}
public FOLOTTERLikeTheoremProver(long maxQueryTime,
boolean useParamodulation) {
setMaxQueryTime(maxQueryTime);
setUseParamodulation(useParamodulation);
}
public long getMaxQueryTime() {
return maxQueryTime;
}
public void setMaxQueryTime(long maxQueryTime) {
this.maxQueryTime = maxQueryTime;
}
public boolean isUseParamodulation() {
return useParamodulation;
}
public void setUseParamodulation(boolean useParamodulation) {
this.useParamodulation = useParamodulation;
}
public LightestClauseHeuristic getLightestClauseHeuristic() {
return lightestClauseHeuristic;
}
public void setLightestClauseHeuristic(
LightestClauseHeuristic lightestClauseHeuristic) {
this.lightestClauseHeuristic = lightestClauseHeuristic;
}
public ClauseFilter getClauseFilter() {
return clauseFilter;
}
public void setClauseFilter(ClauseFilter clauseFilter) {
this.clauseFilter = clauseFilter;
}
public ClauseSimplifier getClauseSimplifier() {
return clauseSimplifier;
}
public void setClauseSimplifier(ClauseSimplifier clauseSimplifier) {
this.clauseSimplifier = clauseSimplifier;
}
//
// START-InferenceProcedure
public InferenceResult ask(FOLKnowledgeBase KB, Sentence alpha) {
Set<Clause> sos = new HashSet<Clause>();
Set<Clause> usable = new HashSet<Clause>();
// Usable set will be the set of clauses in the KB,
// are assuming this is satisfiable as using the
// Set of Support strategy.
for (Clause c : KB.getAllClauses()) {
c = KB.standardizeApart(c);
c.setStandardizedApartCheckNotRequired();
usable.addAll(c.getFactors());
}
// Ensure reflexivity axiom is added to usable if using paramodulation.
if (isUseParamodulation()) {
// Reflexivity Axiom: x = x
TermEquality reflexivityAxiom = new TermEquality(new Variable("x"),
new Variable("x"));
Clause reflexivityClause = new Clause();
reflexivityClause.addLiteral(new Literal(reflexivityAxiom));
reflexivityClause = KB.standardizeApart(reflexivityClause);
reflexivityClause.setStandardizedApartCheckNotRequired();
usable.add(reflexivityClause);
}
Sentence notAlpha = new NotSentence(alpha);
// Want to use an answer literal to pull
// query variables where necessary
Literal answerLiteral = KB.createAnswerLiteral(notAlpha);
Set<Variable> answerLiteralVariables = KB
.collectAllVariables(answerLiteral.getAtomicSentence());
Clause answerClause = new Clause();
if (answerLiteralVariables.size() > 0) {
Sentence notAlphaWithAnswer = new ConnectedSentence(Connectors.OR,
notAlpha, answerLiteral.getAtomicSentence());
for (Clause c : KB.convertToClauses(notAlphaWithAnswer)) {
c = KB.standardizeApart(c);
c.setProofStep(new ProofStepGoal(c));
c.setStandardizedApartCheckNotRequired();
sos.addAll(c.getFactors());
}
answerClause.addLiteral(answerLiteral);
} else {
for (Clause c : KB.convertToClauses(notAlpha)) {
c = KB.standardizeApart(c);
c.setProofStep(new ProofStepGoal(c));
c.setStandardizedApartCheckNotRequired();
sos.addAll(c.getFactors());
}
}
// Ensure all subsumed clauses are removed
usable.removeAll(SubsumptionElimination.findSubsumedClauses(usable));
sos.removeAll(SubsumptionElimination.findSubsumedClauses(sos));
OTTERAnswerHandler ansHandler = new OTTERAnswerHandler(answerLiteral,
answerLiteralVariables, answerClause, maxQueryTime);
IndexedClauses idxdClauses = new IndexedClauses(
getLightestClauseHeuristic(), sos, usable);
return otter(ansHandler, idxdClauses, sos, usable);
}
// END-InferenceProcedure
//
/**
* <pre>
* procedure OTTER(sos, usable)
* inputs: sos, a set of support-clauses defining the problem (a global variable)
* usable, background knowledge potentially relevant to the problem
* </pre>
*/
private InferenceResult otter(OTTERAnswerHandler ansHandler,
IndexedClauses idxdClauses, Set<Clause> sos, Set<Clause> usable) {
getLightestClauseHeuristic().initialSOS(sos);
// * repeat
do {
// * clause <- the lightest member of sos
Clause clause = getLightestClauseHeuristic().getLightestClause();
if (null != clause) {
// * move clause from sos to usable
sos.remove(clause);
getLightestClauseHeuristic().removedClauseFromSOS(clause);
usable.add(clause);
// * PROCESS(INFER(clause, usable), sos)
process(ansHandler, idxdClauses, infer(clause, usable), sos,
usable);
}
// * until sos = [] or a refutation has been found
} while (sos.size() != 0 && !ansHandler.isComplete());
return ansHandler;
}
/**
* <pre>
* function INFER(clause, usable) returns clauses
*/
private Set<Clause> infer(Clause clause, Set<Clause> usable) {
Set<Clause> resultingClauses = new LinkedHashSet<Clause>();
// * resolve clause with each member of usable
for (Clause c : usable) {
Set<Clause> resolvents = clause.binaryResolvents(c);
for (Clause rc : resolvents) {
resultingClauses.add(rc);
}
// if using paramodulation to handle equality
if (isUseParamodulation()) {
Set<Clause> paras = paramodulation.apply(clause, c, true);
for (Clause p : paras) {
resultingClauses.add(p);
}
}
}
// * return the resulting clauses after applying filter
return getClauseFilter().filter(resultingClauses);
}
// procedure PROCESS(clauses, sos)
private void process(OTTERAnswerHandler ansHandler,
IndexedClauses idxdClauses, Set<Clause> clauses, Set<Clause> sos,
Set<Clause> usable) {
// * for each clause in clauses do
for (Clause clause : clauses) {
// * clause <- SIMPLIFY(clause)
clause = getClauseSimplifier().simplify(clause);
// * merge identical literals
// Note: Not required as handled by Clause Implementation
// which keeps literals within a Set, so no duplicates
// will exist.
// * discard clause if it is a tautology
if (clause.isTautology()) {
continue;
}
// * if clause has no literals then a refutation has been found
// or if it just contains the answer literal.
if (!ansHandler.isAnswer(clause)) {
// * sos <- [clause | sos]
// This check ensure duplicate clauses are not
// introduced which will cause the
// LightestClauseHeuristic to loop continuously
// on the same pair of objects.
if (!sos.contains(clause) && !usable.contains(clause)) {
for (Clause ac : clause.getFactors()) {
if (!sos.contains(ac) && !usable.contains(ac)) {
idxdClauses.addClause(ac, sos, usable);
// * if clause has one literal then look for unit
// refutation
lookForUnitRefutation(ansHandler, idxdClauses, ac,
sos, usable);
}
}
}
}
if (ansHandler.isComplete()) {
break;
}
}
}
private void lookForUnitRefutation(OTTERAnswerHandler ansHandler,
IndexedClauses idxdClauses, Clause clause, Set<Clause> sos,
Set<Clause> usable) {
Set<Clause> toCheck = new LinkedHashSet<Clause>();
if (ansHandler.isCheckForUnitRefutation(clause)) {
for (Clause s : sos) {
if (s.isUnitClause()) {
toCheck.add(s);
}
}
for (Clause u : usable) {
if (u.isUnitClause()) {
toCheck.add(u);
}
}
}
if (toCheck.size() > 0) {
toCheck = infer(clause, toCheck);
for (Clause t : toCheck) {
// * clause <- SIMPLIFY(clause)
t = getClauseSimplifier().simplify(t);
// * discard clause if it is a tautology
if (t.isTautology()) {
continue;
}
// * if clause has no literals then a refutation has been found
// or if it just contains the answer literal.
if (!ansHandler.isAnswer(t)) {
// * sos <- [clause | sos]
// This check ensure duplicate clauses are not
// introduced which will cause the
// LightestClauseHeuristic to loop continuously
// on the same pair of objects.
if (!sos.contains(t) && !usable.contains(t)) {
idxdClauses.addClause(t, sos, usable);
}
}
if (ansHandler.isComplete()) {
break;
}
}
}
}
// This is a simple indexing on the clauses to support
// more efficient forward and backward subsumption testing.
class IndexedClauses {
private LightestClauseHeuristic lightestClauseHeuristic = null;
// Group the clauses by their # of literals.
private Map<Integer, Set<Clause>> clausesGroupedBySize = new HashMap<Integer, Set<Clause>>();
// Keep track of the min and max # of literals.
private int minNoLiterals = Integer.MAX_VALUE;
private int maxNoLiterals = 0;
public IndexedClauses(LightestClauseHeuristic lightestClauseHeuristic,
Set<Clause> sos, Set<Clause> usable) {
this.lightestClauseHeuristic = lightestClauseHeuristic;
for (Clause c : sos) {
indexClause(c);
}
for (Clause c : usable) {
indexClause(c);
}
}
public void addClause(Clause c, Set<Clause> sos, Set<Clause> usable) {
// Perform forward subsumption elimination
boolean addToSOS = true;
for (int i = minNoLiterals; i < c.getNumberLiterals(); i++) {
Set<Clause> fs = clausesGroupedBySize.get(i);
if (null != fs) {
for (Clause s : fs) {
if (s.subsumes(c)) {
addToSOS = false;
break;
}
}
}
if (!addToSOS) {
break;
}
}
if (addToSOS) {
sos.add(c);
lightestClauseHeuristic.addedClauseToSOS(c);
indexClause(c);
// Have added clause, therefore
// perform backward subsumption elimination
Set<Clause> subsumed = new HashSet<Clause>();
for (int i = c.getNumberLiterals() + 1; i <= maxNoLiterals; i++) {
subsumed.clear();
Set<Clause> bs = clausesGroupedBySize.get(i);
if (null != bs) {
for (Clause s : bs) {
if (c.subsumes(s)) {
subsumed.add(s);
if (sos.contains(s)) {
sos.remove(s);
lightestClauseHeuristic
.removedClauseFromSOS(s);
}
usable.remove(s);
}
}
bs.removeAll(subsumed);
}
}
}
}
//
// PRIVATE METHODS
//
private void indexClause(Clause c) {
int size = c.getNumberLiterals();
if (size < minNoLiterals) {
minNoLiterals = size;
}
if (size > maxNoLiterals) {
maxNoLiterals = size;
}
Set<Clause> cforsize = clausesGroupedBySize.get(size);
if (null == cforsize) {
cforsize = new HashSet<Clause>();
clausesGroupedBySize.put(size, cforsize);
}
cforsize.add(c);
}
}
class OTTERAnswerHandler implements InferenceResult {
private Literal answerLiteral = null;
private Set<Variable> answerLiteralVariables = null;
private Clause answerClause = null;
private long finishTime = 0L;
private boolean complete = false;
private List<Proof> proofs = new ArrayList<Proof>();
private boolean timedOut = false;
public OTTERAnswerHandler(Literal answerLiteral,
Set<Variable> answerLiteralVariables, Clause answerClause,
long maxQueryTime) {
this.answerLiteral = answerLiteral;
this.answerLiteralVariables = answerLiteralVariables;
this.answerClause = answerClause;
//
this.finishTime = System.currentTimeMillis() + maxQueryTime;
}
//
// 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<Proof> getProofs() {
return proofs;
}
// END-InferenceResult
//
public boolean isComplete() {
return complete;
}
public boolean isLookingForAnswerLiteral() {
return !answerClause.isEmpty();
}
public boolean isCheckForUnitRefutation(Clause clause) {
if (isLookingForAnswerLiteral()) {
if (2 == clause.getNumberLiterals()) {
for (Literal t : clause.getLiterals()) {
if (t.getAtomicSentence()
.getSymbolicName()
.equals(answerLiteral.getAtomicSentence()
.getSymbolicName())) {
return true;
}
}
}
} else {
return clause.isUnitClause();
}
return false;
}
public boolean isAnswer(Clause clause) {
boolean isAns = false;
if (answerClause.isEmpty()) {
if (clause.isEmpty()) {
proofs.add(new ProofFinal(clause.getProofStep(),
new HashMap<Variable, Term>()));
complete = true;
isAns = true;
}
} else {
if (clause.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 clause while looking for an answer, implies original KB or usable is unsatisfiable");
}
if (clause.isUnitClause()
&& clause.isDefiniteClause()
&& clause
.getPositiveLiterals()
.get(0)
.getAtomicSentence()
.getSymbolicName()
.equals(answerLiteral.getAtomicSentence()
.getSymbolicName())) {
Map<Variable, Term> answerBindings = new HashMap<Variable, Term>();
List<Term> answerTerms = clause.getPositiveLiterals()
.get(0).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(clause.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();
}
}
}