001    package aima.logic.fol.kb;
002    
003    import java.util.ArrayList;
004    import java.util.Collections;
005    import java.util.HashMap;
006    import java.util.LinkedHashMap;
007    import java.util.LinkedHashSet;
008    import java.util.List;
009    import java.util.Map;
010    import java.util.Set;
011    
012    import aima.logic.fol.CNFConverter;
013    import aima.logic.fol.StandardizeApart;
014    import aima.logic.fol.StandardizeApartIndexical;
015    import aima.logic.fol.StandardizeApartIndexicalFactory;
016    import aima.logic.fol.StandardizeApartResult;
017    import aima.logic.fol.SubstVisitor;
018    import aima.logic.fol.Unifier;
019    import aima.logic.fol.VariableCollector;
020    import aima.logic.fol.domain.FOLDomain;
021    import aima.logic.fol.inference.FOLOTTERLikeTheoremProver;
022    import aima.logic.fol.inference.InferenceProcedure;
023    import aima.logic.fol.inference.InferenceResult;
024    import aima.logic.fol.inference.proof.Proof;
025    import aima.logic.fol.inference.proof.ProofStepClauseClausifySentence;
026    import aima.logic.fol.kb.data.CNF;
027    import aima.logic.fol.kb.data.Chain;
028    import aima.logic.fol.kb.data.Clause;
029    import aima.logic.fol.kb.data.Literal;
030    import aima.logic.fol.parsing.FOLParser;
031    import aima.logic.fol.parsing.ast.FOLNode;
032    import aima.logic.fol.parsing.ast.Predicate;
033    import aima.logic.fol.parsing.ast.Sentence;
034    import aima.logic.fol.parsing.ast.Term;
035    import aima.logic.fol.parsing.ast.Variable;
036    
037    /**
038     * A First Order Logic (FOL) Knowledge Base.
039     */
040    
041    /**
042     * @author Ciaran O'Reilly
043     * 
044     */
045    public class FOLKnowledgeBase {
046    
047            private FOLParser parser;
048            private InferenceProcedure inferenceProcedure;
049            private Unifier unifier;
050            private SubstVisitor substVisitor;
051            private VariableCollector variableCollector;
052            private StandardizeApart standardizeApart;
053            private CNFConverter cnfConverter;
054            //
055            // Persistent data structures
056            //
057            // Keeps track of the Sentences in their original form as added to the
058            // Knowledge base.
059            private List<Sentence> originalSentences = new ArrayList<Sentence>();
060            // The KB in clause form
061            private Set<Clause> clauses = new LinkedHashSet<Clause>();
062            // Keep track of all of the definite clauses in the database
063            // along with those that represent implications.
064            private List<Clause> allDefiniteClauses = new ArrayList<Clause>();
065            private List<Clause> implicationDefiniteClauses = new ArrayList<Clause>();
066            // All the facts in the KB indexed by Atomic Sentence name (Note: pg. 279)
067            private Map<String, List<Literal>> indexFacts = new HashMap<String, List<Literal>>();
068            // Keep track of indexical keys for uniquely standardizing apart sentences
069            private StandardizeApartIndexical variableIndexical = StandardizeApartIndexicalFactory
070                            .newStandardizeApartIndexical('v');
071            private StandardizeApartIndexical queryIndexical = StandardizeApartIndexicalFactory
072                            .newStandardizeApartIndexical('q');
073    
074            //
075            // PUBLIC METHODS
076            //
077            public FOLKnowledgeBase(FOLDomain domain) {
078                    // Default to Full Resolution if not set.
079                    this(domain, new FOLOTTERLikeTheoremProver());
080            }
081    
082            public FOLKnowledgeBase(FOLDomain domain,
083                            InferenceProcedure inferenceProcedure) {
084                    this(domain, inferenceProcedure, new Unifier());
085            }
086    
087            public FOLKnowledgeBase(FOLDomain domain,
088                            InferenceProcedure inferenceProcedure, Unifier unifier) {
089                    this.parser = new FOLParser(new FOLDomain(domain));
090                    this.inferenceProcedure = inferenceProcedure;
091                    this.unifier = unifier;
092                    //
093                    this.substVisitor = new SubstVisitor();
094                    this.variableCollector = new VariableCollector();
095                    this.standardizeApart = new StandardizeApart(variableCollector,
096                                    substVisitor);
097                    this.cnfConverter = new CNFConverter(parser);
098            }
099            
100            public void clear() {
101                    this.originalSentences.clear();
102                    this.clauses.clear();
103                    this.allDefiniteClauses.clear();
104                    this.implicationDefiniteClauses.clear();
105                    this.indexFacts.clear();
106            }
107    
108            public InferenceProcedure getInferenceProcedure() {
109                    return inferenceProcedure;
110            }
111    
112            public void setInferenceProcedure(InferenceProcedure inferenceProcedure) {
113                    if (null != inferenceProcedure) {
114                            this.inferenceProcedure = inferenceProcedure;
115                    }
116            }
117    
118            public Sentence tell(String aSentence) {
119                    Sentence s = parser.parse(aSentence);
120                    tell(s);
121                    return s;
122            }
123    
124            public void tell(List<? extends Sentence> sentences) {
125                    for (Sentence s : sentences) {
126                            tell(s);
127                    }
128            }
129    
130            public void tell(Sentence aSentence) {
131                    store(aSentence);
132            }
133    
134            /**
135             * 
136             * @param aQuerySentence
137             * @return an InferenceResult.
138             */
139            public InferenceResult ask(String aQuerySentence) {
140                    return ask(parser.parse(aQuerySentence));
141            }
142    
143            public InferenceResult ask(Sentence aQuery) {
144                    // Want to standardize apart the query to ensure
145                    // it does not clash with any of the sentences
146                    // in the database
147                    StandardizeApartResult saResult = standardizeApart.standardizeApart(
148                                    aQuery, queryIndexical);
149    
150                    // Need to map the result variables (as they are standardized apart)
151                    // to the original queries variables so that the caller can easily
152                    // understand and use the returned set of substitutions
153                    InferenceResult infResult = getInferenceProcedure().ask(
154                                    this, saResult.getStandardized());
155                    for (Proof p : infResult.getProofs()) {
156                            Map<Variable, Term> im = p.getAnswerBindings();
157                            Map<Variable, Term> em = new LinkedHashMap<Variable, Term>();
158                            for (Variable rev : saResult.getReverseSubstitution().keySet()) {
159                                            em.put((Variable) saResult.getReverseSubstitution()
160                                                            .get(rev), im.get(rev));
161                            }
162                            p.replaceAnswerBindings(em);
163                    }
164    
165                    return infResult;
166            }
167    
168            public int getNumberFacts() {
169                    return allDefiniteClauses.size() - implicationDefiniteClauses.size();
170            }
171    
172            public int getNumberRules() {
173                    return clauses.size() - getNumberFacts();
174            }
175    
176            public List<Sentence> getOriginalSentences() {
177                    return Collections.unmodifiableList(originalSentences);
178            }
179    
180            public List<Clause> getAllDefiniteClauses() {
181                    return Collections.unmodifiableList(allDefiniteClauses);
182            }
183    
184            public List<Clause> getAllDefiniteClauseImplications() {
185                    return Collections.unmodifiableList(implicationDefiniteClauses);
186            }
187    
188            public Set<Clause> getAllClauses() {
189                    return Collections.unmodifiableSet(clauses);
190            }
191    
192            // Note: pg 278, FETCH(q) concept.
193            public synchronized Set<Map<Variable, Term>> fetch(Literal l) {
194                    // Get all of the substitutions in the KB that p unifies with
195                    Set<Map<Variable, Term>> allUnifiers = new LinkedHashSet<Map<Variable, Term>>();
196    
197                    List<Literal> matchingFacts = fetchMatchingFacts(l);
198                    if (null != matchingFacts) {
199                            for (Literal fact : matchingFacts) {
200                                    Map<Variable, Term> substitution = unifier.unify(l
201                                                    .getAtomicSentence(), fact.getAtomicSentence());
202                                    if (null != substitution) {
203                                            allUnifiers.add(substitution);
204                                    }
205                            }
206                    }
207    
208                    return allUnifiers;
209            }
210    
211            // Note: To support FOL-FC-Ask
212            public Set<Map<Variable, Term>> fetch(List<Literal> literals) {
213                    Set<Map<Variable, Term>> possibleSubstitutions = new LinkedHashSet<Map<Variable, Term>>();
214    
215                    if (literals.size() > 0) {
216                            Literal first = literals.get(0);
217                            List<Literal> rest = literals.subList(1, literals.size());
218    
219                            recursiveFetch(new LinkedHashMap<Variable, Term>(), first, rest,
220                                            possibleSubstitutions);
221                    }
222    
223                    return possibleSubstitutions;
224            }
225    
226            public Map<Variable, Term> unify(FOLNode x, FOLNode y) {
227                    return unifier.unify(x, y);
228            }
229    
230            public Sentence subst(Map<Variable, Term> theta, Sentence aSentence) {
231                    return substVisitor.subst(theta, aSentence);
232            }
233            
234            public Literal subst(Map<Variable, Term> theta, Literal l) {
235                    return substVisitor.subst(theta, l);
236            }
237            
238            public Term subst(Map<Variable, Term> theta, Term aTerm) {
239                    return substVisitor.subst(theta, aTerm);
240            }
241    
242            // Note: see page 277.
243            public Sentence standardizeApart(Sentence aSentence) {
244                    return standardizeApart.standardizeApart(aSentence, variableIndexical)
245                                    .getStandardized();
246            }
247            
248            public Clause standardizeApart(Clause aClause) {
249                    return standardizeApart.standardizeApart(aClause, variableIndexical);
250            }
251            
252            public Chain standardizeApart(Chain aChain) {
253                    return standardizeApart.standardizeApart(aChain, variableIndexical);
254            }
255            
256            public Set<Variable> collectAllVariables(Sentence aSentence) {
257                    return variableCollector.collectAllVariables(aSentence);
258            }
259    
260            public CNF convertToCNF(Sentence aSentence) {
261                    return cnfConverter.convertToCNF(aSentence);
262            }
263            
264            public Set<Clause> convertToClauses(Sentence aSentence) {
265                    CNF cnf = cnfConverter.convertToCNF(aSentence);
266                    
267                    return new LinkedHashSet<Clause>(cnf.getConjunctionOfClauses());
268            }
269            
270            public Literal createAnswerLiteral(Sentence forQuery) {
271                    String alName = parser.getFOLDomain().addAnswerLiteral();
272                    List<Term> terms = new ArrayList<Term>();
273                    
274                    Set<Variable> vars = variableCollector.collectAllVariables(forQuery);
275                    for (Variable v : vars) {
276                            // Ensure copies of the variables are used.
277                            terms.add(v.copy());
278                    }
279                    
280                    return new Literal(new Predicate(alName, terms));
281            }
282    
283            // Note: see pg. 281
284            public boolean isRenaming(Literal l) {
285                    List<Literal> possibleMatches = fetchMatchingFacts(l);
286                    if (null != possibleMatches) {
287                            return isRenaming(l, possibleMatches);
288                    }
289    
290                    return false;
291            }
292    
293            // Note: see pg. 281
294            public boolean isRenaming(Literal l, List<Literal> possibleMatches) {
295    
296                    for (Literal q : possibleMatches) {
297                            if (l.isPositiveLiteral() != q.isPositiveLiteral()) {
298                                    continue;
299                            }
300                            Map<Variable, Term> subst = unifier.unify(l.getAtomicSentence(), q
301                                            .getAtomicSentence());
302                            if (null != subst) {
303                                    int cntVarTerms = 0;
304                                    for (Term t : subst.values()) {
305                                            if (t instanceof Variable) {
306                                                    cntVarTerms++;
307                                            }
308                                    }
309                                    // If all the substitutions, even if none, map to Variables
310                                    // then this is a renaming
311                                    if (subst.size() == cntVarTerms) {
312                                            return true;
313                                    }
314                            }
315                    }
316    
317                    return false;
318            }
319            
320            public String toString() {
321                    StringBuilder sb = new StringBuilder();
322                    for (Sentence s : originalSentences) {
323                            sb.append(s.toString());
324                            sb.append("\n");
325                    }
326                    return sb.toString();
327            }
328    
329            //
330            // PROTECTED METHODS
331            //
332    
333            protected FOLParser getParser() {
334                    return parser;
335            }
336    
337            //
338            // PRIVATE METHODS
339            //
340    
341            // Note: pg 278, STORE(s) concept.
342            private synchronized void store(Sentence aSentence) {
343                    originalSentences.add(aSentence);
344                    
345                    // Convert the sentence to CNF
346                    CNF cnfOfOrig = cnfConverter.convertToCNF(aSentence);
347                    for (Clause c : cnfOfOrig.getConjunctionOfClauses()) {
348                            c.setProofStep(new ProofStepClauseClausifySentence(c, aSentence));
349                            if (c.isEmpty()) {
350                                    // This should not happen, if so the user
351                                    // is trying to add an unsatisfiable sentence
352                                    // to the KB.
353                                    throw new IllegalArgumentException(
354                                                    "Attempted to add unsatisfiable sentence to KB, orig=["
355                                                                    + aSentence + "] CNF=" + cnfOfOrig);
356                            }
357                            
358                            // Ensure all clauses added to the KB are Standardized Apart.
359                            c = standardizeApart.standardizeApart(c, variableIndexical);
360                            
361                            // Will make all clauses immutable
362                            // so that they cannot be modified externally.
363                            c.setImmutable();
364                            if (clauses.add(c)) {
365                                    // If added keep track of special types of
366                                    // clauses, as useful for query purposes
367                                    if (c.isDefiniteClause()) {
368                                            allDefiniteClauses.add(c);
369                                    }
370                                    if (c.isImplicationDefiniteClause()) {
371                                            implicationDefiniteClauses.add(c);
372                                    }
373                                    if (c.isUnitClause()) {
374                                            indexFact(c.getLiterals().iterator().next());
375                                    }
376                            }
377                    }
378            }
379    
380            // Only if it is a unit clause does it get indexed as a fact
381            // see pg. 279 for general idea.
382            private void indexFact(Literal fact) {
383                    String factKey = getFactKey(fact);
384                    if (!indexFacts.containsKey(factKey)) {
385                            indexFacts.put(factKey, new ArrayList<Literal>());
386                    }
387    
388                    indexFacts.get(factKey).add(fact);
389            }
390    
391            private void recursiveFetch(Map<Variable, Term> theta, Literal l,
392                            List<Literal> remainingLiterals,
393                            Set<Map<Variable, Term>> possibleSubstitutions) {
394    
395                    // Find all substitutions for current predicate based on the
396                    // substitutions of prior predicates in the list (i.e. SUBST with
397                    // theta).
398                    Set<Map<Variable, Term>> pSubsts = fetch(subst(theta, l));
399    
400                    // No substitutions, therefore cannot continue
401                    if (null == pSubsts) {
402                            return;
403                    }
404    
405                    for (Map<Variable, Term> psubst : pSubsts) {
406                            // Ensure all prior substitution information is maintained
407                            // along the chain of predicates (i.e. for shared variables
408                            // across the predicates).
409                            psubst.putAll(theta);
410                            if (remainingLiterals.size() == 0) {
411                                    // This means I am at the end of the chain of predicates
412                                    // and have found a valid substitution.
413                                    possibleSubstitutions.add(psubst);
414                            } else {
415                                    // Need to move to the next link in the chain of substitutions
416                                    Literal first = remainingLiterals.get(0);
417                                    List<Literal> rest = remainingLiterals.subList(1,
418                                                    remainingLiterals.size());
419    
420                                    recursiveFetch(psubst, first, rest, possibleSubstitutions);
421                            }
422                    }
423            }
424            
425            private List<Literal> fetchMatchingFacts(Literal l) {
426                    return indexFacts.get(getFactKey(l));
427            }
428    
429            private String getFactKey(Literal l) {
430                    StringBuilder key = new StringBuilder();
431                    if (l.isPositiveLiteral()) {
432                            key.append("+");
433                    } else {
434                            key.append("-");
435                    }
436                    key.append(l.getAtomicSentence().getSymbolicName());
437    
438                    return key.toString();
439            }
440    }