package aima.core.logic.propositional.inference;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import aima.core.logic.propositional.kb.KnowledgeBase;
import aima.core.logic.propositional.kb.data.Clause;
import aima.core.logic.propositional.parsing.ast.PropositionSymbol;
import aima.core.logic.propositional.visitors.ConvertToConjunctionOfClauses;
import aima.core.logic.propositional.visitors.SymbolCollector;
/**
* Artificial Intelligence A Modern Approach (3rd Edition): page 258.<br>
* <br>
*
* <pre>
* <code>
* function PL-FC-ENTAILS?(KB, q) returns true or false
* inputs: KB, the knowledge base, a set of propositional definite clauses
* q, the query, a proposition symbol
* count ← a table, where count[c] is the number of symbols in c's premise
* inferred ← a table, where inferred[s] is initially false for all symbols
* agenda ← a queue of symbols, initially symbols known to be true in KB
*
* while agenda is not empty do
* p ← Pop(agenda)
* if p = q then return true
* if inferred[p] = false then
* inferred[p] ← true
* for each clause c in KB where p is in c.PREMISE do
* decrement count[c]
* if count[c] = 0 then add c.CONCLUSION to agenda
* return false
* </code>
* </pre>
*
* Figure 7.15 the forward-chaining algorithm for propositional logic. The
* <i>agenda</i> keeps track of symbols known to be true but not yet
* "processed". The <i>count</i> table keeps track of how many premises of each
* implication are as yet unknown. Whenever a new symbol p from the agenda is
* processed, the count is reduced by one for each implication in whose premise
* p appears (easily identified in constant time with appropriate indexing.) If
* a count reaches zero, all the premises of the implication are known, so its
* conclusion can be added to the agenda. Finally, we need to keep track of
* which symbols have been processed; a symbol that is already in the set of
* inferred symbols need not be added to the agenda again. This avoids redundant
* work and prevents loops caused by implications such as P ⇒ Q and Q
* ⇒ P.
*
* @author Ciaran O'Reilly
* @author Ravi Mohan
* @author Mike Stampone
*/
public class PLFCEntails {
/**
* PL-FC-ENTAILS?(KB, q)<br>
* The forward-chaining algorithm for propositional logic.
*
* @param kb
* the knowledge base, a set of propositional definite clauses.
* @param q
* q, the query, a proposition symbol
* @return true if KB |= q, false otherwise.
* @throws IllegalArgumentException
* if KB contains any non-definite clauses.
*/
public boolean plfcEntails(KnowledgeBase kb, PropositionSymbol q) {
// count <- a table, where count[c] is the number of symbols in c's
// premise
Map<Clause, Integer> count = initializeCount(kb);
// inferred <- a table, where inferred[s] is initially false for all
// symbols
Map<PropositionSymbol, Boolean> inferred = initializeInferred(kb);
// agenda <- a queue of symbols, initially symbols known to be true in
// KB
Queue<PropositionSymbol> agenda = initializeAgenda(count);
// Note: an index for p to the clauses where p appears in the premise
Map<PropositionSymbol, Set<Clause>> pToClausesWithPInPremise = initializeIndex(
count, inferred);
// while agenda is not empty do
while (!agenda.isEmpty()) {
// p <- Pop(agenda)
PropositionSymbol p = agenda.remove();
// if p = q then return true
if (p.equals(q)) {
return true;
}
// if inferred[p] = false then
if (inferred.get(p).equals(Boolean.FALSE)) {
// inferred[p] <- true
inferred.put(p, true);
// for each clause c in KB where p is in c.PREMISE do
for (Clause c : pToClausesWithPInPremise.get(p)) {
// decrement count[c]
decrement(count, c);
// if count[c] = 0 then add c.CONCLUSION to agenda
if (count.get(c) == 0) {
agenda.add(conclusion(c));
}
}
}
}
// return false
return false;
}
//
// SUPPORTING CODE
//
//
// PROTECTED
//
protected Map<Clause, Integer> initializeCount(KnowledgeBase kb) {
// count <- a table, where count[c] is the number of symbols in c's
// premise
Map<Clause, Integer> count = new HashMap<Clause, Integer>();
Set<Clause> clauses = ConvertToConjunctionOfClauses.convert(
kb.asSentence()).getClauses();
for (Clause c : clauses) {
if (!c.isDefiniteClause()) {
throw new IllegalArgumentException(
"Knowledge Base contains non-definite clauses:" + c);
}
// Note: # of negative literals is equivalent to the number of
// symbols in c's premise
count.put(c, c.getNumberNegativeLiterals());
}
return count;
}
protected Map<PropositionSymbol, Boolean> initializeInferred(KnowledgeBase kb) {
// inferred <- a table, where inferred[s] is initially false for all
// symbols
Map<PropositionSymbol, Boolean> inferred = new HashMap<PropositionSymbol, Boolean>();
for (PropositionSymbol p : SymbolCollector.getSymbolsFrom(kb
.asSentence())) {
inferred.put(p, false);
}
return inferred;
}
// Note: at the point of calling this routine, count will contain all the
// clauses in KB.
protected Queue<PropositionSymbol> initializeAgenda(Map<Clause, Integer> count) {
// agenda <- a queue of symbols, initially symbols known to be true in
// KB
Queue<PropositionSymbol> agenda = new LinkedList<PropositionSymbol>();
for (Clause c : count.keySet()) {
// No premise just a conclusion, then we know its true
if (c.getNumberNegativeLiterals() == 0) {
agenda.add(conclusion(c));
}
}
return agenda;
}
// Note: at the point of calling this routine, count will contain all the
// clauses in KB while inferred will contain all the proposition symbols.
protected Map<PropositionSymbol, Set<Clause>> initializeIndex(
Map<Clause, Integer> count, Map<PropositionSymbol, Boolean> inferred) {
Map<PropositionSymbol, Set<Clause>> pToClausesWithPInPremise = new HashMap<PropositionSymbol, Set<Clause>>();
for (PropositionSymbol p : inferred.keySet()) {
Set<Clause> clausesWithPInPremise = new HashSet<Clause>();
for (Clause c : count.keySet()) {
// Note: The negative symbols comprise the premise
if (c.getNegativeSymbols().contains(p)) {
clausesWithPInPremise.add(c);
}
}
pToClausesWithPInPremise.put(p, clausesWithPInPremise);
}
return pToClausesWithPInPremise;
}
protected void decrement(Map<Clause, Integer> count, Clause c) {
int currentCount = count.get(c);
// Note: a definite clause can just be a fact (i.e. 1 positive literal)
// However, we only decrement those where the symbol is in the premise
// so we don't need to worry about going < 0.
count.put(c, currentCount - 1);
}
protected PropositionSymbol conclusion(Clause c) {
// Note: the conclusion is from the single positive
// literal in the definite clause (which we are
// restricted to).
return c.getPositiveSymbols().iterator().next();
}
}