Add Logic.and; fix formula generation

We weren’t generating formulas when they were used by other formulas.  When we generate a clause for a formula, like “A v B v -$or1”, we need to only suppress formula generation for the -$or1 term, not the entire clause.  This matters when A and B are formula vars (eg “$and1 v $and2 v -$or1”).
This commit is contained in:
David Greenspan
2014-12-23 14:32:46 -08:00
parent 5d48ec2487
commit 3281ac7d9e
2 changed files with 147 additions and 54 deletions

View File

@@ -46,8 +46,13 @@ Logic.TRUE = "$T";
Logic.FALSE = "$F";
Logic.Formula = function () {};
// Returns a list of clauses that together require the
// Formula to be true. (Does not add them to the solver.)
// Returns a list of Clauses that together require the Formula to be
// true. A single Clause may also be returned. The implementation
// should call makeClause(formulaOrArray, ...) on the Formulas and
// Terms of the clause in order to construct Clause objects to return.
// makeClause is provided by the caller (the Solver) and returns a
// Clause object that uses variable numbers specific to that Solver
// instance.
Logic.Formula.prototype._genTrue = function (makeClause) { return []; };
// Returns a list of clauses that together require the
// Formula to be false.
@@ -184,64 +189,69 @@ Logic.Solver.prototype.toNameTerm = function (t) {
}
};
Logic.Solver.prototype._addClause = function (cls, _internal) {
Logic.Solver.prototype._addClause = function (cls, _extraTerms) {
var self = this;
check(cls, Logic.Clause);
if (! _internal) {
var usedF = false;
var usedT = false;
_.each(cls.terms, function (t) {
var v = (t < 0) ? -t : t;
if (v === 1) {
usedF = true;
} else if (v === 2) {
usedT = true;
} else if (v < 1 || v >= self._num2name.length) {
throw new Error("Bad variable number: " + v);
} else if (_.has(self._ungeneratedFormulas, v)) {
// using a Formula's var, maybe have to generate clauses
// for the Formula
var formula = self._ungeneratedFormulas[v];
var info = self._getFormulaInfo(formula);
var positive = t > 0;
if (positive && ! info.occursPositively) {
// generate clauses for the formula.
// Eg, if we use variable `X` which represents the formula
// `A v B`, add the clause `A v B v -X`.
info.occursPositively = true;
var clauses = formula._genTrue(_.bind(self._makeClause, self));
self._addClauses(_.map(clauses, function (clause) {
return clause.append(-v);
}), true);
} else if ((! positive) && ! info.occursNegatively) {
// Eg, if we have the term `-X` where `X` represents the
// formula `A v B`, add the clauses `-A v X` and `-B v X`.
info.occursNegatively = true;
var clauses = formula._genFalse(_.bind(self._makeClause, self));
self._addClauses(_.map(clauses, function (clause) {
return clause.append(v);
}), true);
}
if (info.occursPositively && info.occursNegatively) {
delete self._ungeneratedFormulas[v];
}
}
});
this._F_used = (this._F_used || usedF);
this._T_used = (this._T_used || usedT);
var extraTerms = null;
if (_extraTerms) {
extraTerms = _extraTerms;
check(extraTerms, [Logic.Term]);
}
var usedF = false;
var usedT = false;
var numRealTerms = cls.terms.length;
if (extraTerms) {
cls = cls.append(extraTerms);
}
_.each(cls.terms, function (t, i) {
var v = (t < 0) ? -t : t;
if (v === 1) {
usedF = true;
} else if (v === 2) {
usedT = true;
} else if (v < 1 || v >= self._num2name.length) {
throw new Error("Bad variable number: " + v);
} else if ((i < numRealTerms) && _.has(self._ungeneratedFormulas, v)) {
// using a Formula's var; maybe have to generate clauses
// for the Formula
var formula = self._ungeneratedFormulas[v];
var info = self._getFormulaInfo(formula);
var positive = t > 0;
if (positive && ! info.occursPositively) {
// generate clauses for the formula.
// Eg, if we use variable `X` which represents the formula
// `A v B`, add the clause `A v B v -X`.
info.occursPositively = true;
var clauses = self._callGenTrue(formula);
self._addClauses(clauses, [-v]);
} else if ((! positive) && ! info.occursNegatively) {
// Eg, if we have the term `-X` where `X` represents the
// formula `A v B`, add the clauses `-A v X` and `-B v X`.
info.occursNegatively = true;
var clauses = self._callGenFalse(formula);
self._addClauses(clauses, [v]);
}
if (info.occursPositively && info.occursNegatively) {
delete self._ungeneratedFormulas[v];
}
}
});
this._F_used = (this._F_used || usedF);
this._T_used = (this._T_used || usedT);
this.clauses.push(cls);
};
Logic.Solver.prototype._addClauses = function (array, _internal) {
Logic.Solver.prototype._addClauses = function (array, _extraTerms) {
check(array, [Logic.Clause]);
var self = this;
_.each(array, function (cls) { self._addClause(cls, _internal); });
_.each(array, function (cls) { self._addClause(cls, _extraTerms); });
};
Logic.Solver.prototype.require = function (formulaOrArray/*, ...*/) {
@@ -256,7 +266,7 @@ Logic.Solver.prototype.require = function (formulaOrArray/*, ...*/) {
if (info.varNum !== null) {
self._addClause(new Logic.Clause(info.varNum));
} else {
self._addClauses(f._genTrue(_.bind(self._makeClause, self)));
self._addClauses(self._callGenTrue(f));
}
} else if (Match.test(f, Logic.Term)) {
self._addClause(new Logic.Clause(self.toNumTerm(f)));
@@ -276,7 +286,7 @@ Logic.Solver.prototype.forbid = function (formulaOrArray/*, ...*/) {
if (info.varNum !== null) {
self._addClause(new Logic.Clause(-info.varNum));
} else {
self._addClauses(f._genFalse(_.bind(self._makeClause, self)));
self._addClauses(self._callGenFalse(f));
}
} else if (Match.test(f, Logic.Term)) {
self._addClause(new Logic.Clause(-self.toNumTerm(f)));
@@ -284,6 +294,16 @@ Logic.Solver.prototype.forbid = function (formulaOrArray/*, ...*/) {
});
};
// Helpers for invoking _genTrue and _genFalse on a Formula.
Logic.Solver.prototype._callGenTrue = function (formula) {
var ret = formula._genTrue(_.bind(this._makeClause, this));
return _.isArray(ret) ? ret : [ret];
};
Logic.Solver.prototype._callGenFalse = function (formula) {
var ret = formula._genFalse(_.bind(this._makeClause, this));
return _.isArray(ret) ? ret : [ret];
};
// Get clause data as an array of arrays of integers,
// for testing and debugging purposes.
Logic.Solver.prototype._clauseData = function () {
@@ -405,12 +425,12 @@ Logic.OrFormula = function (operands) {
Logic._defineFormula(Logic.OrFormula, 'or', {
_genTrue: function (makeClause) {
// eg A v B v C
return [makeClause(this.operands)];
return makeClause(this.operands);
},
_genFalse: function (makeClause) {
// eg -A; -B; -C
return _.map(this.operands, function (formula) {
return makeClause(Logic.not(formula));
return _.map(this.operands, function (o) {
return makeClause(Logic.not(o));
});
}
});
@@ -428,3 +448,25 @@ Logic._defineFormula(Logic.NotFormula, 'not', {
throw new Error("'not' is special and doesn't gen");
}
});
Logic.and = function (termOrArray/*, ...*/) {
return new Logic.AndFormula(_.flatten(arguments));
};
Logic.AndFormula = function (operands) {
check(operands, [Logic.FormulaOrTerm]);
this.operands = operands;
};
Logic._defineFormula(Logic.AndFormula, 'and', {
_genTrue: function (makeClause) {
// eg A; B; C
return _.map(this.operands, function (o) {
return makeClause(o);
});
},
_genFalse: function (makeClause) {
// eg -A v -B v -C
return makeClause(_.map(this.operands, Logic.not));
}
});

View File

@@ -223,3 +223,54 @@ Tinytest.add("logic-solver - Require/forbid after formula gen", function (test)
["-A v $or1","-B v $or1","-$or1 v C","A v B v -$or1","$or1"]
]);
});
Tinytest.add("logic-solver - Logic.and", function (test) {
runClauseTests(test, [
function (s) {
s.require(Logic.and('A', 'B'));
},
["A", "B"],
function (s) {
s.require(Logic.and(['A', 'B']));
},
["A", "B"],
function (s) {
s.require(Logic.and(['A'], ['-B']));
},
["A", "-B"],
function (s) {
s.forbid(Logic.and('A', '-B'));
},
["-A v B"],
function (s) {
s.forbid(Logic.and());
},
[""],
function (s) {
s.require(Logic.and());
},
[],
function (s) {
s.require(Logic.or(Logic.and(Logic.or("A", "B"),
Logic.or("-A", "C")),
"-D"));
},
["A v B v -$or1",
"$or1 v -$and1",
"-A v C v -$or2",
"$or2 v -$and1",
"$and1 v -D"],
function (s) {
s.require(Logic.or(Logic.not(Logic.and(Logic.or("A", "B"),
Logic.or("-A", "C"))),
"-D"));
},
["-A v $or1",
"-B v $or1",
"A v $or2",
"-C v $or2",
"-$or1 v -$or2 v $and1",
"-$and1 v -D"]
]);
});