algorithmbox
Version:
A metaheuristic algorithm development framework for solving discrete optimization problem
273 lines (216 loc) • 7.51 kB
JavaScript
var util = require('util');
var assert = require('assert');
var defineClass = require('simple-cls').defineClass;
var Class = require('simple-cls').Class;
var Matrix = require('sylvester').Matrix;
var Vector = require('sylvester').Vector;
var fisher_yates_permute = require('../../util/permute.js');
var OProblem = require('../../core/OProblem.js');
var SATSolution = require('./Solution.js');
/**
The Propositional Satisfiability Problem
Given a propositional logic in CNF (conjuntion normal form), find a variable value assignment a (x1,...xk) such that
the logic evaluates to true
SAT is satifiable iff there exist at least a value assignment evaluating it to true.
e.g.
f = ( c1 | c2 ) & ( !c3 | c4 )
**/
var SAT = defineClass({
name : "SAT",
extend : OProblem,
construct : function(data){
if(data instanceof Array) data = $M(data);
OProblem.call(this,data,false); // a maximization problem - more positive clauses, better
},
methods : {
/**
a valid solution should mach number of variables, and contains only [1|0] for true/false
**/
valid: function(candidate) {
assert.ok(Class.isInstanceOfClass(candidate,"SATSolution"));
if(candidate.dimension()!=this.variables()) return false;
for(var i=0;i<candidate.dimension();i++)
if(!(candidate.value(i)==-1 || candidate.value(i)==1)) return false;
return true;
},
/**
f = total # of clauses that evaluates to true for the given solution
Time Complexity O(MN)
**/
fitness: function(candidate) {
var M = this.clauses();
var N = this.variables();
var trueclauses = 0;
//a literal is true if
// - solution's variable value is true (1), and the clause literal is present and positive (1)
// - solution's variable value is false (-1), and the clause literal is present and negative (-1)
for(var i=0;i<M;i++){
var clause = this.data.elements[i];
var trueeval = false;
for(var j=0;j<N;j++){
if( clause[j] * candidate.value(j) == 1){
trueeval = true;
break;
}
}
if(trueeval) trueclauses += 1;
}
return trueclauses;
},
/**
a random variable assignemnt
- each var randomly takes either true or false
**/
randsol : function(){
var vals = [-1,1]; //a solution takes either 1(true) or -1(false)
var sol = new Array(this.variables());
for(var i=0;i<sol.length;i++)
sol[i] = (Math.random() > 0.5) ? 1 : -1;
var ret = new SATSolution($V(sol));
ret.fitness = this.fitness(ret);
return ret;
},
//CNF clause count
clauses : function(){
return this.data.elements.length;
},
//number of variables
variables : function(){
return this.data.elements[0].length;
}
},
statics : {
/**
takes in a sat problem in CNF form, and convert it to internal matrix representation
SAT problem format using te following
http://www.cs.ubc.ca/~hoos/SATLIB/benchm.html
it uses the DIMACS CNF Format form
**/
parseData : function(raw){
assert(typeof(raw) == "string", "raw is " + raw);
var M = 0; //number of clauses
var N = 0; //number of vars
//read preambles
var lines = raw.trim().split('\n');
var curl = 0 ;// current line number
for(curl=0;curl<lines.length;curl++){
var line = lines[curl].trim();
//comment
if(line[0]=="c"){
//ignore comments
}
//problem def
else if(line[0]=="p"){
var words = line.split(/[\s]+/);
assert.ok(words.length==4);
assert.ok(words[0]=="p");
assert.ok(words[1]=="cnf");
N = parseInt(words[2]);
M = parseInt(words[3]);
assert.ok(!isNaN(N) && !isNaN(M));
assert.ok(M >= 1 && N >= 1);
}
else{
break;
}
}
//initlaize the data with M x N array
var data = new Array(M);
for(var i=0;i<M;i++){
data[i] = new Array(N);
for(var j=0;j<N;j++)
data[i][j] = 0; //default a literal not present
}
//read body
var body = lines.slice(curl).join(' ');
var clauses = body.trim().split(/[\s\t\n]+0[\s\t\n]+/); //split at 0 with trailing space/newline on sides
assert.ok(clauses.length == M + 1); //an ending %
//for each clause
for(var i=0;i<M;i++){
var clause = clauses[i].trim();
var literals = clause.split(/[\s]+/);
assert.ok(literals.length >= 1); //t least one literal
for(var j=0;j<literals.length;j++){
var literal = parseInt(literals[j]);
assert.ok(!isNaN(literal));
assert.ok(literal != 0 && literal >= -N && literal <= N); //range
//-1 to map to the actual index of the variable!!
data[i][Math.abs(literal)-1] = (literal > 0) ? 1 : -1;
}
}
return $M(data);
},
/**
SAT Data is a matrix representing propositional logic in CNF of dimention N x K (# of clauses x # of variables)
- each row is a clause representing a disjuntion of literals
- each col represent a single literal
- each cell (i,j) represent a literal xj's form in clause i
0 - literal not exist in the clause
1 - literal in positive form
-1 - literal in negative form
**/
validData : function(data){
assert.ok(data instanceof Matrix);
var N = data.elements.length;
if(N < 1) return false;
var K = data.elements[0].length;
for(var r=0; r<N;r++){
var sum = 0;
if(data.elements[r].length != K){
console.log("length for row %d is %d",r,data.elements[r].length );
return false;
}
for(var c=0;c<K;c++){
var val = data.elements[r][c];
if(!(val==1 || val==-1 || val==0))
{
console.log("invalid val="+val);
return false;
}
sum += val;
}
if(sum==0){
console.log("all clauses not present");
return false; //cannot be an empty clause
}
}
return true;
},
/**
Using Uniform Random k-SAT (fixed clause-length model)
given M clauses, N variables, fixed clause length K (each clause only include K variables)
k literals are chosen independtly and uniformly at random for the set of 2N possible literals (positive + negative)
- exclude clauses containing duplicate literals
- exclude clauses containing both a literal and its negation at the same time (which will always evaluate to true)
run it M times to produce M clauses
TODO : clauses shall not duplicate
**/
randomSAT : function(M, N, K){
var clauses = [];
for(var m=0;m<M;m++){
var clause = {}; //each key is the variable index (or its negation). Starting with 1
for(var i=0;i<K;i++){
//randomly pick a literal and include
//each refers to the ith literal, with positive value indicating its negative form
var literals = [];
for(var j=1;j<=N;j++){
if(clause[j] || clause[-j]) continue; //ignore already included clause
literals.push(j);
literals.push(-j);
}
fisher_yates_permute(literals); //randomize it and simply take the first k literals
clause[literals[0]] = 1; //just pick the first one which is randomly picked after permute
}
var row = new Array(N);
for(var i=0;i<N;i++) row[i] = 0;
for(var key in clause){
var literal = parseInt(key);
row[Math.abs(literal)-1] = (literal > 0) ? 1 : -1;
}
clauses.push(row);
}
return new SAT($M(clauses));
}
}
});
module.exports = exports = SAT;