JavaScript: proof by resolution refutation

Show that a knowledge base entails a query by deriving a contradiction from the knowledge base plus the negated query.

JavaScript
const show = (clause) => (clause.size === 0 ? "empty" : [...clause].sort().join(" v "));

function resolvents(c1, c2) {
  const out = [];
  for (const literal of c1) {
    const negation = literal.startsWith("~") ? literal.slice(1) : "~" + literal;
    if (c2.has(negation)) {
      const merged = new Set([...c1, ...c2]);
      merged.delete(literal);
      merged.delete(negation);
      out.push(merged);
    }
  }
  return out;
}

const clauses = [
  new Set(["A", "B"]),
  new Set(["~A", "C"]),
  new Set(["~B", "C"]),
  new Set(["~C"]),
];
const known = new Set(clauses.map(show));
let proved = false;

for (let i = 0; i < clauses.length && !proved; i++) {
  for (let j = 0; j < i && !proved; j++) {
    for (const r of resolvents(clauses[i], clauses[j])) {
      if (known.has(show(r))) continue;
      known.add(show(r));
      clauses.push(r);
      console.log("derived " + show(r));
      if (r.size === 0) proved = true;
    }
  }
}
console.log("KB entails C: " + proved);
Output
derived B v C
derived A v C
derived ~A
derived ~B
derived C
derived B
derived A
derived empty
KB entails C: true

Explanation

Resolution is an inference rule for clauses, which are disjunctions of literals. From (A or B) and (not A or C) it derives (B or C), because whichever way A is resolved one of the other literals must hold. A knowledge base entails a query if adding the negation of the query makes the clauses contradictory, which means the empty clause can be derived.

The knowledge base says A or B, A implies C (not A or C), and B implies C (not B or C). To prove C, the program adds not-C and resolves pairs of clauses until it finds the empty clause. Each new clause is kept only once. Because the number of possible clauses is finite, the process terminates, with the empty clause if the query is entailed.

Explore the dry run
Guided walkthroughReady
1const show = (clause) => (clause.size === 0 ? "empty" : [...clause].sort().join(" v "));
2
3function resolvents(c1, c2) {
4 const out = [];
5 for (const literal of c1) {
6 const negation = literal.startsWith("~") ? literal.slice(1) : "~" + literal;
7 if (c2.has(negation)) {
8 const merged = new Set([...c1, ...c2]);
9 merged.delete(literal);
10 merged.delete(negation);
11 out.push(merged);
12 }
13 }
14 return out;
15}
16
17const clauses = [
18 new Set(["A", "B"]),
19 new Set(["~A", "C"]),
20 new Set(["~B", "C"]),
21 new Set(["~C"]),
22];
23const known = new Set(clauses.map(show));
24let proved = false;
25
26for (let i = 0; i < clauses.length && !proved; i++) {
27 for (let j = 0; j < i && !proved; j++) {
28 for (const r of resolvents(clauses[i], clauses[j])) {
29 if (known.has(show(r))) continue;
30 known.add(show(r));
31 clauses.push(r);
32 console.log("derived " + show(r));
33 if (r.size === 0) proved = true;
34 }
35 }
36}
37console.log("KB entails C: " + proved);

Follow the example

Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.

View the complete dry-run table
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput so far
Find the complementary literal; a clause containing it can be resolved with this one.—
—
The negated query is added to the knowledge base.—
—
Skip clauses that were already derived.—
—
Each new resolvent is printed. B v C and A v C come from the knowledge base alone, ~A and ~B come from resolving with the negated query ~C, and the chain ends when C meets ~C to give the empty clause.—
derived B v C
derived A v C
derived ~A
derived ~B
derived C
derived B
derived A
derived empty
The empty clause was derived, so the knowledge base entails C.—
derived B v C
derived A v C
derived ~A
derived ~B
derived C
derived B
derived A
derived empty
KB entails C: true

Cost and common mistakes

Exponential in the worst case, since the number of possible clauses is 3^n for n symbols.

  • Resolution works on clauses, so sentences must first be converted to conjunctive normal form.
  • The search can generate many useless clauses. Practical provers use strategies such as unit preference and set of support.
  • A resolvent that contains both p and not p is a tautology and should be discarded in a full implementation.

Try it yourself

What happens if the query is A instead of C?

Reveal answer and reasoning

The empty clause is never derived, because the knowledge base has a model where A is false (B and C true), so A is not entailed.

Further reading