/aima-core/src/main/java/aima/core/logic/fol/inference/FOLOTTERLikeTheoremProver.java
Java | 614 lines | 403 code | 76 blank | 135 comment | 78 complexity | c11f21361de79c1eba5aca9e4dd1e6fb MD5 | raw file
Possible License(s): GPL-3.0, Apache-2.0
- 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();
- }
- }
- }