001    package aima.logic.fol.inference;
002    
003    import java.util.ArrayList;
004    import java.util.HashMap;
005    import java.util.LinkedHashSet;
006    import java.util.List;
007    import java.util.Map;
008    import java.util.Set;
009    
010    import aima.logic.fol.Connectors;
011    import aima.logic.fol.inference.proof.Proof;
012    import aima.logic.fol.inference.proof.ProofFinal;
013    import aima.logic.fol.inference.proof.ProofStepGoal;
014    import aima.logic.fol.inference.trace.FOLTFMResolutionTracer;
015    import aima.logic.fol.kb.FOLKnowledgeBase;
016    import aima.logic.fol.kb.data.Clause;
017    import aima.logic.fol.kb.data.Literal;
018    import aima.logic.fol.parsing.ast.ConnectedSentence;
019    import aima.logic.fol.parsing.ast.NotSentence;
020    import aima.logic.fol.parsing.ast.Sentence;
021    import aima.logic.fol.parsing.ast.Term;
022    import aima.logic.fol.parsing.ast.Variable;
023    
024    /**
025     * Artificial Intelligence A Modern Approach (2nd Edition): page 297.
026     * 
027     * The algorithmic approach is very close to the propositional case, described
028     * in Figure 7.12. However, this implementation will use the T)wo F)inger M)ethod 
029     * for looking for resolvents between clauses. 
030     * Note: very inefficient, 
031     * see: http://logic.stanford.edu/classes/cs157/2008/lectures/lecture04.pdf,
032     * slide 21 for the propositional case.  
033     * In addition, an Answer literal will be used so that queries with Variables 
034     * may be answered (see pg. 300 of AIMA).
035     * 
036     */
037    
038    /**
039     * @author Ciaran O'Reilly
040     * 
041     */
042    public class FOLTFMResolution implements InferenceProcedure {
043    
044            private long maxQueryTime = 10 * 1000;
045            
046            private FOLTFMResolutionTracer tracer = null;
047    
048            public FOLTFMResolution() {
049    
050            }
051    
052            public FOLTFMResolution(long maxQueryTime) {
053                    setMaxQueryTime(maxQueryTime);
054            }
055    
056            public FOLTFMResolution(FOLTFMResolutionTracer tracer) {
057                    setTracer(tracer);
058            }
059    
060            public long getMaxQueryTime() {
061                    return maxQueryTime;
062            }
063    
064            public void setMaxQueryTime(long maxQueryTime) {
065                    this.maxQueryTime = maxQueryTime;
066            }
067    
068            public FOLTFMResolutionTracer getTracer() {
069                    return tracer;
070            }
071    
072            public void setTracer(FOLTFMResolutionTracer tracer) {
073                    this.tracer = tracer;
074            }
075    
076            //
077            // START-InferenceProcedure
078            public InferenceResult ask(FOLKnowledgeBase KB, Sentence alpha) {
079    
080                    // clauses <- the set of clauses in CNF representation of KB ^ ~alpha
081                    Set<Clause> clauses = new LinkedHashSet<Clause>();
082                    for (Clause c : KB.getAllClauses()) {
083                            c = KB.standardizeApart(c);
084                            c.setStandardizedApartCheckNotRequired();
085                            clauses.addAll(c.getFactors());
086                    }
087                    Sentence notAlpha = new NotSentence(alpha);
088                    // Want to use an answer literal to pull
089                    // query variables where necessary
090                    Literal answerLiteral = KB.createAnswerLiteral(notAlpha);
091                    Set<Variable> answerLiteralVariables = KB
092                                    .collectAllVariables(answerLiteral.getAtomicSentence());
093                    Clause answerClause = new Clause();
094    
095                    if (answerLiteralVariables.size() > 0) {
096                            Sentence notAlphaWithAnswer = new ConnectedSentence(Connectors.OR,
097                                            notAlpha, answerLiteral.getAtomicSentence());
098                            for (Clause c : KB.convertToClauses(notAlphaWithAnswer)) {
099                                    c = KB.standardizeApart(c);
100                                    c.setProofStep(new ProofStepGoal(c));
101                                    c.setStandardizedApartCheckNotRequired();
102                                    clauses.addAll(c.getFactors());
103                            }
104    
105                            answerClause.addLiteral(answerLiteral);
106                    } else {
107                            for (Clause c : KB.convertToClauses(notAlpha)) {
108                                    c = KB.standardizeApart(c);
109                                    c.setProofStep(new ProofStepGoal(c));
110                                    c.setStandardizedApartCheckNotRequired();
111                                    clauses.addAll(c.getFactors());
112                            }
113                    }
114    
115                    TFMAnswerHandler ansHandler = new TFMAnswerHandler(answerLiteral,
116                                    answerLiteralVariables, answerClause, maxQueryTime);
117    
118                    // new <- {}
119                    Set<Clause> newClauses = new LinkedHashSet<Clause>();
120                    Set<Clause> toAdd = new LinkedHashSet<Clause>();
121                    // loop do
122                    int noOfPrevClauses = clauses.size();
123                    do {
124                            if (null != tracer) {
125                                    tracer.stepStartWhile(clauses, clauses.size(), newClauses
126                                                    .size());
127                            }
128    
129                            newClauses.clear();
130    
131                            // for each Ci, Cj in clauses do
132                            Clause[] clausesA = new Clause[clauses.size()];
133                            clauses.toArray(clausesA);
134                            // Basically, using the simple T)wo F)inger M)ethod here.
135                            for (int i = 0; i < clausesA.length; i++) {
136                                    Clause cI = clausesA[i];
137                                    if (null != tracer) {
138                                            tracer.stepOuterFor(cI);
139                                    }
140                                    for (int j = i; j < clausesA.length; j++) {
141                                            Clause cJ = clausesA[j];
142    
143                                            if (null != tracer) {
144                                                    tracer.stepInnerFor(cI, cJ);
145                                            }
146    
147                                            // resolvent <- FOL-RESOLVE(Ci, Cj)
148                                            Set<Clause> resolvents = cI.binaryResolvents(cJ);
149    
150                                            if (resolvents.size() > 0) {
151                                                    toAdd.clear();
152                                                    // new <- new <UNION> resolvent
153                                                    for (Clause rc : resolvents) {
154                                                            toAdd.addAll(rc.getFactors());
155                                                    }
156    
157                                                    if (null != tracer) {
158                                                            tracer.stepResolved(cI, cJ, toAdd);
159                                                    }
160                                                    
161                                                    ansHandler.checkForPossibleAnswers(toAdd);
162                                                    
163                                                    if (ansHandler.isComplete()) {
164                                                            break;
165                                                    }
166                                                    
167                                                    newClauses.addAll(toAdd);
168                                            }
169    
170                                            if (ansHandler.isComplete()) {
171                                                    break;
172                                            }
173                                    }
174                                    if (ansHandler.isComplete()) {
175                                            break;
176                                    }
177                            }
178    
179                            noOfPrevClauses = clauses.size();
180    
181                            // clauses <- clauses <UNION> new
182                            clauses.addAll(newClauses);
183    
184                            if (ansHandler.isComplete()) {
185                                    break;
186                            }
187    
188                            // if new is a <SUBSET> of clauses then finished
189                            // searching for an answer
190                            // (i.e. when they were added the # clauses
191                            // did not increase).
192                    } while (noOfPrevClauses < clauses.size());
193    
194                    if (null != tracer) {
195                            tracer.stepFinished(clauses, ansHandler);
196                    }
197    
198                    return ansHandler;
199            }
200    
201            // END-InferenceProcedure
202            // 
203    
204            //
205            // PRIVATE METHODS
206            //
207            class TFMAnswerHandler implements InferenceResult {
208                    private Literal answerLiteral = null;
209                    private Set<Variable> answerLiteralVariables = null;
210                    private Clause answerClause = null;
211                    private long finishTime = 0L;
212                    private boolean complete = false;
213                    private List<Proof> proofs = new ArrayList<Proof>();
214                    private boolean timedOut = false;
215    
216                    public TFMAnswerHandler(Literal answerLiteral,
217                                    Set<Variable> answerLiteralVariables, Clause answerClause,
218                                    long maxQueryTime) {
219                            this.answerLiteral = answerLiteral;
220                            this.answerLiteralVariables = answerLiteralVariables;
221                            this.answerClause = answerClause;
222                            //
223                            this.finishTime = System.currentTimeMillis() + maxQueryTime;
224                    }
225    
226                    //
227                    // START-InferenceResult
228                    public boolean isPossiblyFalse() {
229                            return !timedOut && proofs.size() == 0;
230                    }
231    
232                    public boolean isTrue() {
233                            return proofs.size() > 0;
234                    }
235    
236                    public boolean isUnknownDueToTimeout() {
237                            return timedOut && proofs.size() == 0;
238                    }
239    
240                    public boolean isPartialResultDueToTimeout() {
241                            return timedOut && proofs.size() > 0;
242                    }
243    
244                    public List<Proof> getProofs() {
245                            return proofs;
246                    }
247    
248                    // END-InferenceResult
249                    //
250    
251                    public boolean isComplete() {
252                            return complete;
253                    }
254                    
255                    private void checkForPossibleAnswers(Set<Clause> resolvents) {
256                            // If no bindings being looked for, then
257                            // is just a true false query.
258                            for (Clause aClause : resolvents) {
259                                    if (answerClause.isEmpty()) {
260                                            if (aClause.isEmpty()) {
261                                                    proofs.add(new ProofFinal(aClause.getProofStep(),
262                                                                    new HashMap<Variable, Term>()));
263                                                    complete = true;
264                                            }
265                                    } else {
266                                            if (aClause.isEmpty()) {
267                                                    // This should not happen
268                                                    // as added an answer literal, which
269                                                    // implies the database (i.e. premises) are
270                                                    // unsatisfiable to begin with.
271                                                    throw new IllegalStateException(
272                                                                    "Generated an empty clause while looking for an answer, implies original KB is unsatisfiable");
273                                            }
274    
275                                            if (aClause.isUnitClause()
276                                                            && aClause.isDefiniteClause()
277                                                            && aClause.getPositiveLiterals().get(0)
278                                                                            .getAtomicSentence().getSymbolicName()
279                                                                            .equals(
280                                                                                            answerLiteral.getAtomicSentence()
281                                                                                                            .getSymbolicName())) {
282                                                    Map<Variable, Term> answerBindings = new HashMap<Variable, Term>();
283                                                    List<Term> answerTerms = aClause.getPositiveLiterals()
284                                                                    .get(0).getAtomicSentence().getArgs();
285                                                    int idx = 0;
286                                                    for (Variable v : answerLiteralVariables) {
287                                                            answerBindings.put(v, answerTerms.get(idx));
288                                                            idx++;
289                                                    }
290                                                    boolean addNewAnswer = true;
291                                                    for (Proof p : proofs) {
292                                                            if (p.getAnswerBindings().equals(answerBindings)) {
293                                                                    addNewAnswer = false;
294                                                                    break;
295                                                            }
296                                                    }
297                                                    if (addNewAnswer) {
298                                                            proofs.add(new ProofFinal(aClause.getProofStep(),
299                                                                            answerBindings));
300                                                    }
301                                            }
302                                    }
303    
304                                    if (System.currentTimeMillis() > finishTime) {
305                                            complete = true;
306                                            // Indicate that I have run out of query time
307                                            timedOut = true;
308                                    }
309                            }
310                    }
311                    
312                    public String toString() {
313                            StringBuilder sb = new StringBuilder();
314                            sb.append("isComplete=" + complete);
315                            sb.append("\n");
316                            sb.append("result=" + proofs);
317                            return sb.toString();
318                    }
319            }
320    }