CnfFormula.js

A propositional logic formula in conjunctive normal form (CNF) - or more simply put:

an AND of ORs.

Let's build the following CNF formula:

(x1 ∨ ¬x2) ∧ (¬x1 ∨ x2 ∨ x3) ∧ (¬x1)

Or in plain english:

(x1 or not x2) and (not x1 or x2 or x3) and (not x1)

var CnfFormula = require('./CnfFormula');
var cnf = new CnfFormula();
var x1 = {}, x2 = {}, x3 = {};

cnf.openClause(x1).orNot(x2).close()
   .openClauseNot(x1).or(x2).or(x3).close()
   .openClauseNot(x1).close();

Definitions

Each chunk of or between () is called a clause.

x1, x2 and x3 are called variables. Their instances are irrevelant to the algorithm.

x1 and ¬x1 are called literals. A literal is either:

Implementation notes

The execution of the DPLL algorithm does not modify the CNFFormula. In other words: once built, a CNFFormula is immutable.

This implementation makes use of maps and sets to keep tracks of the clauses, variables and literals of this formula. If a variable implements the equals function it will be used instead of the === operator to determine variable equality.

More about...

Source code

import Map.js

var Map = require('./Map');

import Set.js

var Set = require('./Set');

import Clause.js

var Clause = require('./Clause');

Constructor - no argument.

function CnfFormula() {
    
    'use strict';

the array of Clauses of this formula.

    var clauses = [];

a map whose keys are variables and value the number of occurences in the formula.

    var variables = new Map();

a map whose keys are variables and values are associated positive literal.

    var positiveLiterals = new Map();

a map whose keys are variables and values are associated negative literal.

    var negativeLiterals = new Map();

adds all the variables of the specified array to this formula. This fills the variables attribute.

    function addVariables(someVars) {
        var it = someVars.iterator();
        while (it.hasNext()) {
            var someVar = it.next();
            var occ = variables.get(someVar);
            if (occ === undefined) {
                variables.put(someVar, 1);
            } else {
                variables.put(someVar, occ + 1);
            }
        }
    };

Returns a new clause with specified variable as first literal. This literal will be positive.

    this.openClause = function(variable) {
        return new Clause(variable, false, this);
    };

Returns a new clause with specified variable as first literal. This literal will be negative (negation of variable).

    this.openClauseNot = function(variable) {
        return new Clause(variable, true, this);
    };

Adds the specified clause to this formula.

    this.addClause = function(clause, vars, literals) {
        clauses.push(clause);
        addVariables(vars);
        var it = literals.iterator();
        while (it.hasNext()) {
            var l = it.next();
            if (l.isPositive()) {
                positiveLiterals.put(l.variable(), l);
            } else {
                negativeLiterals.put(l.variable(), l);
            }
        }
        return this;
    };

returns the variables attribute.

    this.variables = function() {
        return variables;
    };

Evaluate this formula against the specified valuation and returns

This method just loop through all the clauses of this formula calling in turn Clause#evaluate.

    this.evaluate = function(valuation) {
        var result = true;
        var length = clauses.length;
        for (var index = 0; index < length && result !== undefined && result; index++) {
            result = clauses[index].evaluate(valuation);
        }
        return result;
    };

Performs the unit propagation step. If a clause is a unit clause, i.e. it contains only a single unassigned literal, this clause can only be satisfied by assigning the necessary value to make this literal true. Thus, no choice is necessary. In practice, this often leads to deterministic cascades of units, thus avoiding a large part of the naive search space.

The specified valuation will be filled with computed variable truth assignments upon return.

This method just loop through all the clauses of this formula calling in turn Clause#evaluate.

    this.unitPropagate = function(valuation) {
        var length = clauses.length;
        for (var index = 0; index < length; index++) {
            clauses[index].unitPropagate(valuation);
        }
    };

Performs the pure literal elimination step. If a propositional variable occurs with only one polarity in the formula, it is called pure. Pure literals can always be assigned in a way that makes all clauses containing them true. Thus, these clauses do not constrain the search anymore and can be deleted.

The specified valuation will be filled with computed variable truth assignments upon return.

    this.pureLiteralAssign = function(valuation) {
        var it = valuation.unassigned().iterator();
        while (it.hasNext()) {
            var variable = it.next();

variable not assign, check if pure literal.

            var pLiteral = positiveLiterals.containsKey(variable);
            var nLiteral = negativeLiterals.containsKey(variable);
            if (pLiteral && !nLiteral) {
                valuation.putSolution(variable, true);
            } else if (nLiteral && !pLiteral) {
                valuation.putSolution(variable, false);
            }
        }
    };

}

expose API to Node.js

module.exports = CnfFormula;