001    package aima.logic.fol.inference;
002    
003    import java.util.ArrayList;
004    import java.util.HashMap;
005    import java.util.HashSet;
006    import java.util.LinkedHashSet;
007    import java.util.List;
008    import java.util.Map;
009    import java.util.Set;
010    
011    import aima.logic.fol.Connectors;
012    import aima.logic.fol.SubsumptionElimination;
013    import aima.logic.fol.inference.otter.ClauseFilter;
014    import aima.logic.fol.inference.otter.ClauseSimplifier;
015    import aima.logic.fol.inference.otter.LightestClauseHeuristic;
016    import aima.logic.fol.inference.otter.defaultimpl.DefaultClauseFilter;
017    import aima.logic.fol.inference.otter.defaultimpl.DefaultClauseSimplifier;
018    import aima.logic.fol.inference.otter.defaultimpl.DefaultLightestClauseHeuristic;
019    import aima.logic.fol.inference.proof.Proof;
020    import aima.logic.fol.inference.proof.ProofFinal;
021    import aima.logic.fol.inference.proof.ProofStepGoal;
022    import aima.logic.fol.kb.FOLKnowledgeBase;
023    import aima.logic.fol.kb.data.Clause;
024    import aima.logic.fol.kb.data.Literal;
025    import aima.logic.fol.parsing.ast.ConnectedSentence;
026    import aima.logic.fol.parsing.ast.NotSentence;
027    import aima.logic.fol.parsing.ast.Sentence;
028    import aima.logic.fol.parsing.ast.Term;
029    import aima.logic.fol.parsing.ast.TermEquality;
030    import aima.logic.fol.parsing.ast.Variable;
031    
032    /**
033     * Artificial Intelligence A Modern Approach (2nd Edition): Figure 9.14, page 307.
034     * 
035     * <pre>
036     * procedure OTTER(sos, usable)
037     *   inputs: sos, a set of support-clauses defining the problem (a global variable)
038     *   usable, background knowledge potentially relevant to the problem
039     *   
040     *   repeat
041     *      clause <- the lightest member of sos
042     *      move clause from sos to usable
043     *      PROCESS(INFER(clause, usable), sos)
044     *   until sos = [] or a refutation has been found
045     * 
046     * --------------------------------------------------------------------------------
047     * 
048     * function INFER(clause, usable) returns clauses
049     *   
050     *   resolve clause with each member of usable
051     *   return the resulting clauses after applying filter
052     *   
053     * --------------------------------------------------------------------------------
054     * 
055     * procedure PROCESS(clauses, sos)
056     * 
057     *   for each clause in clauses do
058     *       clause <- SIMPLIFY(clause)
059     *       merge identical literals
060     *       discard clause if it is a tautology
061     *       sos <- [clause | sos]
062     *       if clause has no literals then a refutation has been found
063     *       if clause has one literal then look for unit refutation
064     * </pre>
065     * 
066     * Figure 9.14 Sketch of the OTTER theorem prover. Heuristic control is applied in the
067     * selection of the "lightest" clause and in the FILTER function that eliminates uninteresting
068     * clauses from consideration.
069     */
070    
071    // Note: The original implementation of OTTER has been retired 
072    // but its successor, Prover9, can be found at:
073    // http://www.prover9.org/
074    // or
075    // http://www.cs.unm.edu/~mccune/mace4/
076    // Should you wish to play with a mature implementation of a theorem prover :-)
077    // For lots of interesting problems to play with, see
078    // 'The TPTP Problem Library for Automated Theorem Proving':
079    // http://www.cs.miami.edu/~tptp/
080    /**
081     * @author Ciaran O'Reilly
082     * 
083     */
084    public class FOLOTTERLikeTheoremProver implements InferenceProcedure {
085            //
086            // Ten seconds is default maximum query time permitted
087            private long maxQueryTime = 10 * 1000;
088            private boolean useParamodulation = true;
089            private LightestClauseHeuristic lightestClauseHeuristic = new DefaultLightestClauseHeuristic();
090            private ClauseFilter clauseFilter = new DefaultClauseFilter();
091            private ClauseSimplifier clauseSimplifier = new DefaultClauseSimplifier();
092            //
093            private Paramodulation paramodulation = new Paramodulation();
094    
095            public FOLOTTERLikeTheoremProver() {
096    
097            }
098    
099            public FOLOTTERLikeTheoremProver(long maxQueryTime) {
100                    setMaxQueryTime(maxQueryTime);
101            }
102    
103            public FOLOTTERLikeTheoremProver(boolean useParamodulation) {
104                    setUseParamodulation(useParamodulation);
105            }
106    
107            public FOLOTTERLikeTheoremProver(long maxQueryTime,
108                            boolean useParamodulation) {
109                    setMaxQueryTime(maxQueryTime);
110                    setUseParamodulation(useParamodulation);
111            }
112    
113            public long getMaxQueryTime() {
114                    return maxQueryTime;
115            }
116    
117            public void setMaxQueryTime(long maxQueryTime) {
118                    this.maxQueryTime = maxQueryTime;
119            }
120    
121            public boolean isUseParamodulation() {
122                    return useParamodulation;
123            }
124    
125            public void setUseParamodulation(boolean useParamodulation) {
126                    this.useParamodulation = useParamodulation;
127            }
128    
129            public LightestClauseHeuristic getLightestClauseHeuristic() {
130                    return lightestClauseHeuristic;
131            }
132    
133            public void setLightestClauseHeuristic(
134                            LightestClauseHeuristic lightestClauseHeuristic) {
135                    this.lightestClauseHeuristic = lightestClauseHeuristic;
136            }
137    
138            public ClauseFilter getClauseFilter() {
139                    return clauseFilter;
140            }
141    
142            public void setClauseFilter(ClauseFilter clauseFilter) {
143                    this.clauseFilter = clauseFilter;
144            }
145    
146            public ClauseSimplifier getClauseSimplifier() {
147                    return clauseSimplifier;
148            }
149    
150            public void setClauseSimplifier(ClauseSimplifier clauseSimplifier) {
151                    this.clauseSimplifier = clauseSimplifier;
152            }
153    
154            //
155            // START-InferenceProcedure
156            public InferenceResult ask(FOLKnowledgeBase KB, Sentence alpha) {
157                    Set<Clause> sos = new HashSet<Clause>();
158                    Set<Clause> usable = new HashSet<Clause>();
159    
160                    // Usable set will be the set of clauses in the KB,
161                    // are assuming this is satisfiable as using the
162                    // Set of Support strategy.
163                    for (Clause c : KB.getAllClauses()) {
164                            c = KB.standardizeApart(c);
165                            c.setStandardizedApartCheckNotRequired();
166                            usable.addAll(c.getFactors());
167                    }
168    
169                    // Ensure reflexivity axiom is added to usable if using paramodulation.
170                    if (isUseParamodulation()) {
171                            // Reflexivity Axiom: x = x
172                            TermEquality reflexivityAxiom = new TermEquality(new Variable("x"),
173                                            new Variable("x"));
174                            Clause reflexivityClause = new Clause();
175                            reflexivityClause.addLiteral(new Literal(reflexivityAxiom));
176                            reflexivityClause = KB.standardizeApart(reflexivityClause);
177                            reflexivityClause.setStandardizedApartCheckNotRequired();
178                            usable.add(reflexivityClause);
179                    }
180    
181                    Sentence notAlpha = new NotSentence(alpha);
182                    // Want to use an answer literal to pull
183                    // query variables where necessary
184                    Literal answerLiteral = KB.createAnswerLiteral(notAlpha);
185                    Set<Variable> answerLiteralVariables = KB
186                                    .collectAllVariables(answerLiteral.getAtomicSentence());
187                    Clause answerClause = new Clause();
188    
189                    if (answerLiteralVariables.size() > 0) {
190                            Sentence notAlphaWithAnswer = new ConnectedSentence(Connectors.OR,
191                                            notAlpha, answerLiteral.getAtomicSentence());
192                            for (Clause c : KB.convertToClauses(notAlphaWithAnswer)) {
193                                    c = KB.standardizeApart(c);
194                                    c.setProofStep(new ProofStepGoal(c));
195                                    c.setStandardizedApartCheckNotRequired();
196                                    sos.addAll(c.getFactors());
197                            }
198    
199                            answerClause.addLiteral(answerLiteral);
200                    } else {
201                            for (Clause c : KB.convertToClauses(notAlpha)) {
202                                    c = KB.standardizeApart(c);
203                                    c.setProofStep(new ProofStepGoal(c));
204                                    c.setStandardizedApartCheckNotRequired();
205                                    sos.addAll(c.getFactors());
206                            }
207                    }
208    
209                    // Ensure all subsumed clauses are removed
210                    usable.removeAll(SubsumptionElimination.findSubsumedClauses(usable));
211                    sos.removeAll(SubsumptionElimination.findSubsumedClauses(sos));
212    
213                    OTTERAnswerHandler ansHandler = new OTTERAnswerHandler(answerLiteral,
214                                    answerLiteralVariables, answerClause, maxQueryTime);
215    
216                    IndexedClauses idxdClauses = new IndexedClauses(
217                                    getLightestClauseHeuristic(), sos, usable);
218    
219                    return otter(ansHandler, idxdClauses, sos, usable);
220            }
221    
222            // END-InferenceProcedure
223            //
224    
225            /**
226             * <pre>
227             * procedure OTTER(sos, usable) 
228             *   inputs: sos, a set of support-clauses defining the problem (a global variable) 
229             *   usable, background knowledge potentially relevant to the problem
230             * </pre>
231             */
232            private InferenceResult otter(OTTERAnswerHandler ansHandler,
233                            IndexedClauses idxdClauses, Set<Clause> sos, Set<Clause> usable) {
234    
235                    getLightestClauseHeuristic().initialSOS(sos);
236    
237                    // * repeat
238                    do {
239                            // * clause <- the lightest member of sos
240                            Clause clause = getLightestClauseHeuristic().getLightestClause();
241                            if (null != clause) {
242                                    // * move clause from sos to usable
243                                    sos.remove(clause);
244                                    getLightestClauseHeuristic().removedClauseFromSOS(clause);
245                                    usable.add(clause);
246                                    // * PROCESS(INFER(clause, usable), sos)
247                                    process(ansHandler, idxdClauses, infer(clause, usable), sos,
248                                                    usable);
249                            }
250    
251                            // * until sos = [] or a refutation has been found
252                    } while (sos.size() != 0 && !ansHandler.isComplete());
253    
254                    return ansHandler;
255            }
256    
257            /**
258             * <pre>
259             * function INFER(clause, usable) returns clauses
260             */
261            private Set<Clause> infer(Clause clause, Set<Clause> usable) {
262                    Set<Clause> resultingClauses = new LinkedHashSet<Clause>();
263    
264                    // * resolve clause with each member of usable
265                    for (Clause c : usable) {
266                            Set<Clause> resolvents = clause.binaryResolvents(c);
267                            for (Clause rc : resolvents) {
268                                    resultingClauses.add(rc);
269                            }
270    
271                            // if using paramodulation to handle equality
272                            if (isUseParamodulation()) {
273                                    Set<Clause> paras = paramodulation.apply(clause, c, true);
274                                    for (Clause p : paras) {
275                                            resultingClauses.add(p);
276                                    }
277                            }
278                    }
279    
280                    // * return the resulting clauses after applying filter
281                    return getClauseFilter().filter(resultingClauses);
282            }
283    
284            // procedure PROCESS(clauses, sos)
285            private void process(OTTERAnswerHandler ansHandler,
286                            IndexedClauses idxdClauses, Set<Clause> clauses, Set<Clause> sos,
287                            Set<Clause> usable) {
288    
289                    // * for each clause in clauses do
290                    for (Clause clause : clauses) {
291                            // * clause <- SIMPLIFY(clause)
292                            clause = getClauseSimplifier().simplify(clause);
293    
294                            // * merge identical literals
295                            // Note: Not required as handled by Clause Implementation
296                            // which keeps literals within a Set, so no duplicates
297                            // will exist.
298    
299                            // * discard clause if it is a tautology
300                            if (clause.isTautology()) {
301                                    continue;
302                            }
303    
304                            // * if clause has no literals then a refutation has been found
305                            // or if it just contains the answer literal.
306                            if (!ansHandler.isAnswer(clause)) {
307                                    // * sos <- [clause | sos]
308                                    // This check ensure duplicate clauses are not
309                                    // introduced which will cause the
310                                    // LightestClauseHeuristic to loop continuously
311                                    // on the same pair of objects.
312                                    if (!sos.contains(clause) && !usable.contains(clause)) {
313                                            for (Clause ac : clause.getFactors()) {
314                                                    if (!sos.contains(ac) && !usable.contains(ac)) {
315                                                            idxdClauses.addClause(ac, sos, usable);
316    
317                                                            // * if clause has one literal then look for unit
318                                                            // refutation
319                                                            lookForUnitRefutation(ansHandler, idxdClauses, ac,
320                                                                            sos, usable);
321                                                    }
322                                            }
323                                    }
324                            }
325    
326                            if (ansHandler.isComplete()) {
327                                    break;
328                            }
329                    }
330            }
331    
332            private void lookForUnitRefutation(OTTERAnswerHandler ansHandler,
333                            IndexedClauses idxdClauses, Clause clause, Set<Clause> sos,
334                            Set<Clause> usable) {
335    
336                    Set<Clause> toCheck = new LinkedHashSet<Clause>();
337    
338                    if (ansHandler.isCheckForUnitRefutation(clause)) {
339                            for (Clause s : sos) {
340                                    if (s.isUnitClause()) {
341                                            toCheck.add(s);
342                                    }
343                            }
344                            for (Clause u : usable) {
345                                    if (u.isUnitClause()) {
346                                            toCheck.add(u);
347                                    }
348                            }
349                    }
350    
351                    if (toCheck.size() > 0) {
352                            toCheck = infer(clause, toCheck);
353                            for (Clause t : toCheck) {
354                                    // * clause <- SIMPLIFY(clause)
355                                    t = getClauseSimplifier().simplify(t);
356    
357                                    // * discard clause if it is a tautology
358                                    if (t.isTautology()) {
359                                            continue;
360                                    }
361    
362                                    // * if clause has no literals then a refutation has been found
363                                    // or if it just contains the answer literal.
364                                    if (!ansHandler.isAnswer(t)) {
365                                            // * sos <- [clause | sos]
366                                            // This check ensure duplicate clauses are not
367                                            // introduced which will cause the
368                                            // LightestClauseHeuristic to loop continuously
369                                            // on the same pair of objects.
370                                            if (!sos.contains(t) && !usable.contains(t)) {
371                                                    idxdClauses.addClause(t, sos, usable);
372                                            }
373                                    }
374    
375                                    if (ansHandler.isComplete()) {
376                                            break;
377                                    }
378                            }
379                    }
380            }
381    
382            // This is a simple indexing on the clauses to support
383            // more efficient forward and backward subsumption testing.
384            class IndexedClauses {
385                    private LightestClauseHeuristic lightestClauseHeuristic = null;
386                    // Group the clauses by their # of literals.
387                    private Map<Integer, Set<Clause>> clausesGroupedBySize = new HashMap<Integer, Set<Clause>>();
388                    // Keep track of the min and max # of literals.
389                    private int minNoLiterals = Integer.MAX_VALUE;
390                    private int maxNoLiterals = 0;
391    
392                    public IndexedClauses(LightestClauseHeuristic lightestClauseHeuristic,
393                                    Set<Clause> sos, Set<Clause> usable) {
394                            this.lightestClauseHeuristic = lightestClauseHeuristic;
395                            for (Clause c : sos) {
396                                    indexClause(c);
397                            }
398                            for (Clause c : usable) {
399                                    indexClause(c);
400                            }
401                    }
402    
403                    public void addClause(Clause c, Set<Clause> sos, Set<Clause> usable) {
404                            // Perform forward subsumption elimination
405                            boolean addToSOS = true;
406                            for (int i = minNoLiterals; i < c.getNumberLiterals(); i++) {
407                                    Set<Clause> fs = clausesGroupedBySize.get(i);
408                                    if (null != fs) {
409                                            for (Clause s : fs) {
410                                                    if (s.subsumes(c)) {
411                                                            addToSOS = false;
412                                                            break;
413                                                    }
414                                            }
415                                    }
416                                    if (!addToSOS) {
417                                            break;
418                                    }
419                            }
420    
421                            if (addToSOS) {
422                                    sos.add(c);
423                                    lightestClauseHeuristic.addedClauseToSOS(c);
424                                    indexClause(c);
425                                    // Have added clause, therefore
426                                    // perform backward subsumption elimination
427                                    Set<Clause> subsumed = new HashSet<Clause>();
428                                    for (int i = c.getNumberLiterals() + 1; i <= maxNoLiterals; i++) {
429                                            subsumed.clear();
430                                            Set<Clause> bs = clausesGroupedBySize.get(i);
431                                            if (null != bs) {
432                                                    for (Clause s : bs) {
433                                                            if (c.subsumes(s)) {
434                                                                    subsumed.add(s);
435                                                                    if (sos.contains(s)) {
436                                                                            sos.remove(s);
437                                                                            lightestClauseHeuristic
438                                                                                            .removedClauseFromSOS(s);
439                                                                    }
440                                                                    usable.remove(s);
441                                                            }
442                                                    }
443                                                    bs.removeAll(subsumed);
444                                            }                                       
445                                    }
446                            }
447                    }
448    
449                    //
450                    // PRIVATE METHODS
451                    //
452                    private void indexClause(Clause c) {
453                            int size = c.getNumberLiterals();
454                            if (size < minNoLiterals) {
455                                    minNoLiterals = size;
456                            }
457                            if (size > maxNoLiterals) {
458                                    maxNoLiterals = size;
459                            }
460                            Set<Clause> cforsize = clausesGroupedBySize.get(size);
461                            if (null == cforsize) {
462                                    cforsize = new HashSet<Clause>();
463                                    clausesGroupedBySize.put(size, cforsize);
464                            }
465                            cforsize.add(c);
466                    }
467            }
468    
469            class OTTERAnswerHandler implements InferenceResult {
470                    private Literal answerLiteral = null;
471                    private Set<Variable> answerLiteralVariables = null;
472                    private Clause answerClause = null;
473                    private long finishTime = 0L;
474                    private boolean complete = false;
475                    private List<Proof> proofs = new ArrayList<Proof>();
476                    private boolean timedOut = false;
477    
478                    public OTTERAnswerHandler(Literal answerLiteral,
479                                    Set<Variable> answerLiteralVariables, Clause answerClause,
480                                    long maxQueryTime) {
481                            this.answerLiteral = answerLiteral;
482                            this.answerLiteralVariables = answerLiteralVariables;
483                            this.answerClause = answerClause;
484                            //
485                            this.finishTime = System.currentTimeMillis() + maxQueryTime;
486                    }
487    
488                    //
489                    // START-InferenceResult
490                    public boolean isPossiblyFalse() {
491                            return !timedOut && proofs.size() == 0;
492                    }
493    
494                    public boolean isTrue() {
495                            return proofs.size() > 0;
496                    }
497    
498                    public boolean isUnknownDueToTimeout() {
499                            return timedOut && proofs.size() == 0;
500                    }
501    
502                    public boolean isPartialResultDueToTimeout() {
503                            return timedOut && proofs.size() > 0;
504                    }
505    
506                    public List<Proof> getProofs() {
507                            return proofs;
508                    }
509    
510                    // END-InferenceResult
511                    //
512    
513                    public boolean isComplete() {
514                            return complete;
515                    }
516    
517                    public boolean isLookingForAnswerLiteral() {
518                            return !answerClause.isEmpty();
519                    }
520    
521                    public boolean isCheckForUnitRefutation(Clause clause) {
522    
523                            if (isLookingForAnswerLiteral()) {
524                                    if (2 == clause.getNumberLiterals()) {
525                                            for (Literal t : clause.getLiterals()) {
526                                                    if (t.getAtomicSentence().getSymbolicName().equals(
527                                                                    answerLiteral.getAtomicSentence()
528                                                                                    .getSymbolicName())) {
529                                                            return true;
530                                                    }
531                                            }
532                                    }
533                            } else {
534                                    return clause.isUnitClause();
535                            }
536    
537                            return false;
538                    }
539    
540                    public boolean isAnswer(Clause aClause) {
541                            boolean isAns = false;
542    
543                            if (answerClause.isEmpty()) {
544                                    if (aClause.isEmpty()) {
545                                            proofs.add(new ProofFinal(aClause.getProofStep(),
546                                                            new HashMap<Variable, Term>()));
547                                            complete = true;
548                                            isAns = true;
549                                    }
550                            } else {
551                                    if (aClause.isEmpty()) {
552                                            // This should not happen
553                                            // as added an answer literal to sos, which
554                                            // implies the database (i.e. premises) are
555                                            // unsatisfiable to begin with.
556                                            throw new IllegalStateException(
557                                                            "Generated an empty clause while looking for an answer, implies original KB or usable is unsatisfiable");
558                                    }
559    
560                                    if (aClause.isUnitClause()
561                                                    && aClause.isDefiniteClause()
562                                                    && aClause.getPositiveLiterals().get(0)
563                                                                    .getAtomicSentence().getSymbolicName().equals(
564                                                                                    answerLiteral.getAtomicSentence()
565                                                                                                    .getSymbolicName())) {
566                                            Map<Variable, Term> answerBindings = new HashMap<Variable, Term>();
567                                            List<Term> answerTerms = aClause.getPositiveLiterals().get(
568                                                            0).getAtomicSentence().getArgs();
569                                            int idx = 0;
570                                            for (Variable v : answerLiteralVariables) {
571                                                    answerBindings.put(v, answerTerms.get(idx));
572                                                    idx++;
573                                            }
574                                            boolean addNewAnswer = true;
575                                            for (Proof p : proofs) {
576                                                    if (p.getAnswerBindings().equals(answerBindings)) {
577                                                            addNewAnswer = false;
578                                                            break;
579                                                    }
580                                            }
581                                            if (addNewAnswer) {
582                                                    proofs.add(new ProofFinal(aClause.getProofStep(),
583                                                                    answerBindings));
584                                            }
585                                            isAns = true;
586                                    }
587                            }
588    
589                            if (System.currentTimeMillis() > finishTime) {
590                                    complete = true;
591                                    // Indicate that I have run out of query time
592                                    timedOut = true;
593                            }
594    
595                            return isAns;
596                    }
597    
598                    public String toString() {
599                            StringBuilder sb = new StringBuilder();
600                            sb.append("isComplete=" + complete);
601                            sb.append("\n");
602                            sb.append("result=" + proofs);
603                            return sb.toString();
604                    }
605            }
606    }