aima.core.logic.fol.inference.FOLOTTERLikeTheoremProver 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.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.
*
*
*
* 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
*
*
* 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.
*
* Note: The original implementation of OTTER has been retired but its
* successor, Prover9, can be found at:
* http://www.prover9.org/
* or
* http://www.cs.unm.edu/~mccune/
* mace4/
* Should you wish to play with a mature implementation of a theorem prover :-)
*
* For lots of interesting problems to play with, see The TPTP Problem
* Library for Automated Theorem Proving:
* http://www.cs.miami.edu/~tptp/
*
* @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 sos = new HashSet();
Set usable = new HashSet();
// 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 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
//
/**
*
* 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
*
*/
private InferenceResult otter(OTTERAnswerHandler ansHandler,
IndexedClauses idxdClauses, Set sos, Set 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;
}
/**
*
* function INFER(clause, usable) returns clauses
*/
private Set infer(Clause clause, Set usable) {
Set resultingClauses = new LinkedHashSet();
// * resolve clause with each member of usable
for (Clause c : usable) {
Set resolvents = clause.binaryResolvents(c);
for (Clause rc : resolvents) {
resultingClauses.add(rc);
}
// if using paramodulation to handle equality
if (isUseParamodulation()) {
Set 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 clauses, Set sos,
Set 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 sos,
Set usable) {
Set toCheck = new LinkedHashSet();
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> clausesGroupedBySize = new HashMap>();
// Keep track of the min and max # of literals.
private int minNoLiterals = Integer.MAX_VALUE;
private int maxNoLiterals = 0;
public IndexedClauses(LightestClauseHeuristic lightestClauseHeuristic,
Set sos, Set usable) {
this.lightestClauseHeuristic = lightestClauseHeuristic;
for (Clause c : sos) {
indexClause(c);
}
for (Clause c : usable) {
indexClause(c);
}
}
public void addClause(Clause c, Set sos, Set usable) {
// Perform forward subsumption elimination
boolean addToSOS = true;
for (int i = minNoLiterals; i < c.getNumberLiterals(); i++) {
Set 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 subsumed = new HashSet();
for (int i = c.getNumberLiterals() + 1; i <= maxNoLiterals; i++) {
subsumed.clear();
Set 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 cforsize = clausesGroupedBySize.get(size);
if (null == cforsize) {
cforsize = new HashSet();
clausesGroupedBySize.put(size, cforsize);
}
cforsize.add(c);
}
}
class OTTERAnswerHandler implements InferenceResult {
private Literal answerLiteral = null;
private Set answerLiteralVariables = null;
private Clause answerClause = null;
private long finishTime = 0L;
private boolean complete = false;
private List proofs = new ArrayList();
private boolean timedOut = false;
public OTTERAnswerHandler(Literal answerLiteral,
Set 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 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()));
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 answerBindings = new HashMap();
List 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();
}
}
}