javascript-lp-solver
Version:
Easy to use, JSON oriented Linear Programming and Mixed Int. Programming Solver
1,645 lines (1,329 loc) • 120 kB
JavaScript
(function(){if (typeof exports === "object") {module.exports = require("./main");}})();
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
},{}],2:[function(require,module,exports){
/*global describe*/
/*global require*/
/*global module*/
/*global it*/
/*global console*/
/*global process*/
/*jshint -W083 */
/*************************************************************
* Method: to_JSON
* Scope: Public:
* Agruments: input: Whatever the user gives us
* Purpose: Convert an unfriendly formatted LP
* into something that our library can
* work with
**************************************************************/
function to_JSON(input){
var rxo = {
/* jshint ignore:start */
"is_blank": /^\W{0,}$/,
"is_objective": /(max|min)(imize){0,}\:/i,
"is_int": /^(?!\/\*)\W{0,}int/i,
"is_bin": /^(?!\/\*)\W{0,}bin/i,
"is_constraint": /(\>|\<){0,}\=/i,
"is_unrestricted": /^\S{0,}unrestricted/i,
"parse_lhs": /(\-|\+){0,1}\s{0,1}\d{0,}\.{0,}\d{0,}\s{0,}[A-Za-z]\S{0,}/gi,
"parse_rhs": /(\-|\+){0,1}\d{1,}\.{0,}\d{0,}\W{0,}\;{0,1}$/i,
"parse_dir": /(\>|\<){0,}\=/gi,
"parse_int": /[^\s|^\,]+/gi,
"parse_bin": /[^\s|^\,]+/gi,
"get_num": /(\-|\+){0,1}(\W|^)\d+\.{0,1}\d{0,}/g,
"get_word": /[A-Za-z].*/
/* jshint ignore:end */
},
model = {
"opType": "",
"optimize": "_obj",
"constraints": {},
"variables": {}
},
constraints = {
">=": "min",
"<=": "max",
"=": "equal"
},
tmp = "", tst = 0, ary = null, hldr = "", hldr2 = "",
constraint = "", rhs = 0;
// Handle input if its coming
// to us as a hard string
// instead of as an array of
// strings
if(typeof input === "string"){
input = input.split("\n");
}
// Start iterating over the rows
// to see what all we have
for(var i = 0; i < input.length; i++){
constraint = "__" + i;
// Get the string we're working with
tmp = input[i];
// Set the test = 0
tst = 0;
// Reset the array
ary = null;
// Test to see if we're the objective
if(rxo.is_objective.test(tmp)){
// Set up in model the opType
model.opType = tmp.match(/(max|min)/gi)[0];
// Pull apart lhs
ary = tmp.match(rxo.parse_lhs).map(function(d){
return d.replace(/\s+/,"");
}).slice(1);
// *** STEP 1 *** ///
// Get the variables out
ary.forEach(function(d){
// Get the number if its there
hldr = d.match(rxo.get_num);
// If it isn't a number, it might
// be a standalone variable
if(hldr === null){
if(d.substr(0,1) === "-"){
hldr = -1;
} else {
hldr = 1;
}
} else {
hldr = hldr[0];
}
hldr = parseFloat(hldr);
// Get the variable type
hldr2 = d.match(rxo.get_word)[0].replace(/\;$/,"");
// Make sure the variable is in the model
model.variables[hldr2] = model.variables[hldr2] || {};
model.variables[hldr2]._obj = hldr;
});
////////////////////////////////////
}else if(rxo.is_int.test(tmp)){
// Get the array of ints
ary = tmp.match(rxo.parse_int).slice(1);
// Since we have an int, our model should too
model.ints = model.ints || {};
ary.forEach(function(d){
d = d.replace(";","");
model.ints[d] = 1;
});
////////////////////////////////////
} else if(rxo.is_bin.test(tmp)){
// Get the array of bins
ary = tmp.match(rxo.parse_bin).slice(1);
// Since we have an binary, our model should too
model.binaries = model.binaries || {};
ary.forEach(function(d){
d = d.replace(";","");
model.binaries[d] = 1;
});
////////////////////////////////////
} else if(rxo.is_constraint.test(tmp)){
var separatorIndex = tmp.indexOf(":");
var constraintExpression = (separatorIndex === -1) ? tmp : tmp.slice(separatorIndex + 1);
// Pull apart lhs
ary = constraintExpression.match(rxo.parse_lhs).map(function(d){
return d.replace(/\s+/,"");
});
// *** STEP 1 *** ///
// Get the variables out
ary.forEach(function(d){
// Get the number if its there
hldr = d.match(rxo.get_num);
if(hldr === null){
if(d.substr(0,1) === "-"){
hldr = -1;
} else {
hldr = 1;
}
} else {
hldr = hldr[0];
}
hldr = parseFloat(hldr);
// Get the variable name
hldr2 = d.match(rxo.get_word)[0];
// Make sure the variable is in the model
model.variables[hldr2] = model.variables[hldr2] || {};
model.variables[hldr2][constraint] = hldr;
});
// *** STEP 2 *** ///
// Get the RHS out
rhs = parseFloat(tmp.match(rxo.parse_rhs)[0]);
// *** STEP 3 *** ///
// Get the Constrainer out
tmp = constraints[tmp.match(rxo.parse_dir)[0]];
model.constraints[constraint] = model.constraints[constraint] || {};
model.constraints[constraint][tmp] = rhs;
////////////////////////////////////
} else if(rxo.is_unrestricted.test(tmp)){
// Get the array of unrestricted
ary = tmp.match(rxo.parse_int).slice(1);
// Since we have an int, our model should too
model.unrestricted = model.unrestricted || {};
ary.forEach(function(d){
d = d.replace(";","");
model.unrestricted[d] = 1;
});
}
}
return model;
}
/*************************************************************
* Method: from_JSON
* Scope: Public:
* Agruments: model: The model we want solver to operate on
* Purpose: Convert a friendly JSON model into a model for a
* real solving library...in this case
* lp_solver
**************************************************************/
function from_JSON(model){
// Make sure we at least have a model
if (!model) {
throw new Error("Solver requires a model to operate on");
}
var output = "",
ary = [],
norm = 1,
lookup = {
"max": "<=",
"min": ">=",
"equal": "="
},
rxClean = new RegExp("[^A-Za-z0-9_\[\{\}\/\.\&\#\$\%\~\'\@\^]", "gi");
// Build the objective statement
if(model.opType){
output += model.opType + ":";
// Iterate over the variables
for(var x in model.variables){
// Give each variable a self of 1 unless
// it exists already
model.variables[x][x] = model.variables[x][x] ? model.variables[x][x] : 1;
// Does our objective exist here?
if(model.variables[x][model.optimize]){
output += " " + model.variables[x][model.optimize] + " " + x.replace(rxClean,"_");
}
}
} else {
output += "max:";
}
// Add some closure to our line thing
output += ";\n\n";
// And now... to iterate over the constraints
for(var xx in model.constraints){
for(var y in model.constraints[xx]){
if(typeof lookup[y] !== "undefined"){
for(var z in model.variables){
// Does our Constraint exist here?
if(typeof model.variables[z][xx] !== "undefined"){
output += " " + model.variables[z][xx] + " " + z.replace(rxClean,"_");
}
}
// Add the constraint type and value...
output += " " + lookup[y] + " " + model.constraints[xx][y];
output += ";\n";
}
}
}
// Are there any ints?
if(model.ints){
output += "\n\n";
for(var xxx in model.ints){
output += "int " + xxx.replace(rxClean,"_") + ";\n";
}
}
// Are there any unrestricted?
if(model.unrestricted){
output += "\n\n";
for(var xxxx in model.unrestricted){
output += "unrestricted " + xxxx.replace(rxClean,"_") + ";\n";
}
}
// And kick the string back
return output;
}
module.exports = function (model) {
// If the user is giving us an array
// or a string, convert it to a JSON Model
// otherwise, spit it out as a string
if(model.length){
return to_JSON(model);
} else {
return from_JSON(model);
}
};
},{}],3:[function(require,module,exports){
/*global describe*/
/*global require*/
/*global it*/
/*global console*/
/*global process*/
/*global exports*/
/*global Promise*/
// LP SOLVE CLI REFERENCE:
// http://lpsolve.sourceforge.net/5.5/lp_solve.htm
//
//
// var reformat = require("./Reformat.js");
exports.reformat = require("./Reformat.js");
function clean_data(data){
//
// Clean Up
// And Reformatting...
//
data = data.replace("\\r\\n","\r\n");
data = data.split("\r\n");
data = data.filter(function(x){
var rx;
//
// Test 1
rx = new RegExp(" 0$","gi");
if(rx.test(x) === true){
return false;
}
//
// Test 2
rx = new RegExp("\\d$","gi");
if(rx.test(x) === false){
return false;
}
return true;
})
.map(function(x){
return x.split(/\:{0,1} +(?=\d)/);
})
.reduce(function(o,k,i){
o[k[0]] = k[1];
return o;
},{});
return data;
}
exports.solve = function(model){
//
return new Promise(function(res, rej){
//
// Exit if we're in the browser...
//
if(typeof window !== "undefined"){
rej("Function Not Available in Browser");
}
//
// Convert JSON model to lp_solve format
//
var data = require("./Reformat.js")(model);
if(!model.external){
rej("Data for this function must be contained in the 'external' attribute. Not seeing anything there.");
}
//
// In the args, they *SHALL* have provided an executable
// path to the solver they're piping the data into
//
if(!model.external.binPath){
rej("No Executable | Binary path provided in arguments as 'binPath'");
}
//
// They also need to provide an arg_array
//
if(!model.external.args){
rej("No arguments array for cli | bash provided on 'args' attribute");
}
//
// They also need a tempName so we know where to store
// the temp file we're creating...
//
if(!model.external.tempName){
rej("No 'tempName' given. This is necessary to produce a staging file for the solver to operate on");
}
//
// To my knowledge, in Windows, you cannot directly pipe text into
// an exe...
//
// Thus, our process looks like this...
//
// 1.) Convert a model to something an external solver can use
// 2.) Save the results from step 1 as a temp-text file
// 3.) Pump the results into an exe | whatever-linux-uses
// 4.)
//
//
var fs = require("fs");
fs.writeFile(model.external.tempName, data, function(fe, fd){
if(fe){
rej(fe);
} else {
//
// So it looks like we wrote to a file and closed it.
// Neat.
//
// Now we need to execute our CLI...
var exec = require("child_process").execFile;
//
// Put the temp file name in the args array...
//
model.external.args.push(model.external.tempName);
exec(model.external.binPath, model.external.args, function(e,data){
if(e){
if(e.code === 1){
res(clean_data(data));
} else {
var codes = {
"-2": "Out of Memory",
"1": "SUBOPTIMAL",
"2": "INFEASIBLE",
"3": "UNBOUNDED",
"4": "DEGENERATE",
"5": "NUMFAILURE",
"6": "USER-ABORT",
"7": "TIMEOUT",
"9": "PRESOLVED",
"25": "ACCURACY ERROR",
"255": "FILE-ERROR"
};
var ret_obj = {
"code": e.code,
"meaning": codes[e.code],
"data": data
};
rej(ret_obj);
}
} else {
// And finally...return it.
res(clean_data(data));
}
});
}
});
});
};
/*
model.external = {
"binPath": "C:/lpsolve/lp_solve.exe",
"tempName": "C:/temp/out.txt",
"args": [
"-S2"
]
}
*/
},{"./Reformat.js":2,"child_process":1,"fs":1}],4:[function(require,module,exports){
/*global describe*/
/*global require*/
/*global it*/
/*global console*/
/*global process*/
/*global exports*/
/*global Promise*/
/*global module*/
module.exports = {
"lpsolve": require("./lpsolve/main.js")
};
},{"./lpsolve/main.js":3}],5:[function(require,module,exports){
/*global describe*/
/*global require*/
/*global module*/
/*global it*/
/*global console*/
/*global process*/
var Tableau = require("./Tableau/Tableau.js");
var branchAndCut = require("./Tableau/branchAndCut.js");
var expressions = require("./expressions.js");
var Constraint = expressions.Constraint;
var Equality = expressions.Equality;
var Variable = expressions.Variable;
var IntegerVariable = expressions.IntegerVariable;
var Term = expressions.Term;
/*************************************************************
* Class: Model
* Description: Holds the model of a linear optimisation problem
**************************************************************/
function Model(precision, name) {
this.tableau = new Tableau(precision);
this.name = name;
this.variables = [];
this.integerVariables = [];
this.unrestrictedVariables = {};
this.constraints = [];
this.nConstraints = 0;
this.nVariables = 0;
this.isMinimization = true;
this.tableauInitialized = false;
this.relaxationIndex = 1;
this.useMIRCuts = false;
this.checkForCycles = true;
//
// Quick and dirty way to leave useful information
// for the end user without hitting the console
// or modifying the primary return object...
//
this.messages = [];
}
module.exports = Model;
Model.prototype.minimize = function () {
this.isMinimization = true;
return this;
};
Model.prototype.maximize = function () {
this.isMinimization = false;
return this;
};
// Model.prototype.addConstraint = function (constraint) {
// // TODO: make sure that the constraint does not belong do another model
// // and make
// this.constraints.push(constraint);
// return this;
// };
Model.prototype._getNewElementIndex = function () {
if (this.availableIndexes.length > 0) {
return this.availableIndexes.pop();
}
var index = this.lastElementIndex;
this.lastElementIndex += 1;
return index;
};
Model.prototype._addConstraint = function (constraint) {
var slackVariable = constraint.slack;
this.tableau.variablesPerIndex[slackVariable.index] = slackVariable;
this.constraints.push(constraint);
this.nConstraints += 1;
if (this.tableauInitialized === true) {
this.tableau.addConstraint(constraint);
}
};
Model.prototype.smallerThan = function (rhs) {
var constraint = new Constraint(rhs, true, this.tableau.getNewElementIndex(), this);
this._addConstraint(constraint);
return constraint;
};
Model.prototype.greaterThan = function (rhs) {
var constraint = new Constraint(rhs, false, this.tableau.getNewElementIndex(), this);
this._addConstraint(constraint);
return constraint;
};
Model.prototype.equal = function (rhs) {
var constraintUpper = new Constraint(rhs, true, this.tableau.getNewElementIndex(), this);
this._addConstraint(constraintUpper);
var constraintLower = new Constraint(rhs, false, this.tableau.getNewElementIndex(), this);
this._addConstraint(constraintLower);
return new Equality(constraintUpper, constraintLower);
};
Model.prototype.addVariable = function (cost, id, isInteger, isUnrestricted, priority) {
if (typeof priority === "string") {
switch (priority) {
case "required":
priority = 0;
break;
case "strong":
priority = 1;
break;
case "medium":
priority = 2;
break;
case "weak":
priority = 3;
break;
default:
priority = 0;
break;
}
}
var varIndex = this.tableau.getNewElementIndex();
if (id === null || id === undefined) {
id = "v" + varIndex;
}
if (cost === null || cost === undefined) {
cost = 0;
}
if (priority === null || priority === undefined) {
priority = 0;
}
var variable;
if (isInteger) {
variable = new IntegerVariable(id, cost, varIndex, priority);
this.integerVariables.push(variable);
} else {
variable = new Variable(id, cost, varIndex, priority);
}
this.variables.push(variable);
this.tableau.variablesPerIndex[varIndex] = variable;
if (isUnrestricted) {
this.unrestrictedVariables[varIndex] = true;
}
this.nVariables += 1;
if (this.tableauInitialized === true) {
this.tableau.addVariable(variable);
}
return variable;
};
Model.prototype._removeConstraint = function (constraint) {
var idx = this.constraints.indexOf(constraint);
if (idx === -1) {
console.warn("[Model.removeConstraint] Constraint not present in model");
return;
}
this.constraints.splice(idx, 1);
this.nConstraints -= 1;
if (this.tableauInitialized === true) {
this.tableau.removeConstraint(constraint);
}
if (constraint.relaxation) {
this.removeVariable(constraint.relaxation);
}
};
//-------------------------------------------------------------------
// For dynamic model modification
//-------------------------------------------------------------------
Model.prototype.removeConstraint = function (constraint) {
if (constraint.isEquality) {
this._removeConstraint(constraint.upperBound);
this._removeConstraint(constraint.lowerBound);
} else {
this._removeConstraint(constraint);
}
return this;
};
Model.prototype.removeVariable = function (variable) {
var idx = this.variables.indexOf(variable);
if (idx === -1) {
console.warn("[Model.removeVariable] Variable not present in model");
return;
}
this.variables.splice(idx, 1);
if (this.tableauInitialized === true) {
this.tableau.removeVariable(variable);
}
return this;
};
Model.prototype.updateRightHandSide = function (constraint, difference) {
if (this.tableauInitialized === true) {
this.tableau.updateRightHandSide(constraint, difference);
}
return this;
};
Model.prototype.updateConstraintCoefficient = function (constraint, variable, difference) {
if (this.tableauInitialized === true) {
this.tableau.updateConstraintCoefficient(constraint, variable, difference);
}
return this;
};
Model.prototype.setCost = function (cost, variable) {
var difference = cost - variable.cost;
if (this.isMinimization === false) {
difference = -difference;
}
variable.cost = cost;
this.tableau.updateCost(variable, difference);
return this;
};
//-------------------------------------------------------------------
//-------------------------------------------------------------------
Model.prototype.loadJson = function (jsonModel) {
this.isMinimization = (jsonModel.opType !== "max");
var variables = jsonModel.variables;
var constraints = jsonModel.constraints;
var constraintsMin = {};
var constraintsMax = {};
// Instantiating constraints
var constraintIds = Object.keys(constraints);
var nConstraintIds = constraintIds.length;
for (var c = 0; c < nConstraintIds; c += 1) {
var constraintId = constraintIds[c];
var constraint = constraints[constraintId];
var equal = constraint.equal;
var weight = constraint.weight;
var priority = constraint.priority;
var relaxed = weight !== undefined || priority !== undefined;
var lowerBound, upperBound;
if (equal === undefined) {
var min = constraint.min;
if (min !== undefined) {
lowerBound = this.greaterThan(min);
constraintsMin[constraintId] = lowerBound;
if (relaxed) { lowerBound.relax(weight, priority); }
}
var max = constraint.max;
if (max !== undefined) {
upperBound = this.smallerThan(max);
constraintsMax[constraintId] = upperBound;
if (relaxed) { upperBound.relax(weight, priority); }
}
} else {
lowerBound = this.greaterThan(equal);
constraintsMin[constraintId] = lowerBound;
upperBound = this.smallerThan(equal);
constraintsMax[constraintId] = upperBound;
var equality = new Equality(lowerBound, upperBound);
if (relaxed) { equality.relax(weight, priority); }
}
}
var variableIds = Object.keys(variables);
var nVariables = variableIds.length;
//
//
// *** OPTIONS ***
//
//
this.tolerance = jsonModel.tolerance || 0;
if(jsonModel.timeout){
this.timeout = jsonModel.timeout;
}
//
//
// The model is getting too sloppy with options added to it...
// mebe it needs an "options" option...?
//
// YES! IT DOES!
// DO IT!
// NOW!
// HERE!!!
//
if(jsonModel.options){
//
// TIMEOUT
//
if(jsonModel.options.timeout){
this.timeout = jsonModel.options.timeout;
}
//
// TOLERANCE
//
if(this.tolerance === 0){
this.tolerance = jsonModel.options.tolerance || 0;
}
//
// MIR CUTS - (NOT WORKING)
//
if(jsonModel.options.useMIRCuts){
this.useMIRCuts = jsonModel.options.useMIRCuts;
}
//
// CYCLE CHECK...tricky because it defaults to false
//
//
// This should maybe be on by default...
//
if(typeof jsonModel.options.exitOnCycles === "undefined"){
this.checkForCycles = true;
} else {
this.checkForCycles = jsonModel.options.exitOnCycles;
}
}
//
//
// /// OPTIONS \\\
//
//
var integerVarIds = jsonModel.ints || {};
var binaryVarIds = jsonModel.binaries || {};
var unrestrictedVarIds = jsonModel.unrestricted || {};
// Instantiating variables and constraint terms
var objectiveName = jsonModel.optimize;
for (var v = 0; v < nVariables; v += 1) {
// Creation of the variables
var variableId = variableIds[v];
var variableConstraints = variables[variableId];
var cost = variableConstraints[objectiveName] || 0;
var isBinary = !!binaryVarIds[variableId];
var isInteger = !!integerVarIds[variableId] || isBinary;
var isUnrestricted = !!unrestrictedVarIds[variableId];
var variable = this.addVariable(cost, variableId, isInteger, isUnrestricted);
if (isBinary) {
// Creating an upperbound constraint for this variable
this.smallerThan(1).addTerm(1, variable);
}
var constraintNames = Object.keys(variableConstraints);
for (c = 0; c < constraintNames.length; c += 1) {
var constraintName = constraintNames[c];
if (constraintName === objectiveName) {
continue;
}
var coefficient = variableConstraints[constraintName];
var constraintMin = constraintsMin[constraintName];
if (constraintMin !== undefined) {
constraintMin.addTerm(coefficient, variable);
}
var constraintMax = constraintsMax[constraintName];
if (constraintMax !== undefined) {
constraintMax.addTerm(coefficient, variable);
}
}
}
return this;
};
//-------------------------------------------------------------------
//-------------------------------------------------------------------
Model.prototype.getNumberOfIntegerVariables = function () {
return this.integerVariables.length;
};
Model.prototype.solve = function () {
// Setting tableau if not done
if (this.tableauInitialized === false) {
this.tableau.setModel(this);
this.tableauInitialized = true;
}
return this.tableau.solve();
};
Model.prototype.isFeasible = function () {
return this.tableau.feasible;
};
Model.prototype.save = function () {
return this.tableau.save();
};
Model.prototype.restore = function () {
return this.tableau.restore();
};
Model.prototype.activateMIRCuts = function (useMIRCuts) {
this.useMIRCuts = useMIRCuts;
};
Model.prototype.debug = function (debugCheckForCycles) {
this.checkForCycles = debugCheckForCycles;
};
Model.prototype.log = function (message) {
return this.tableau.log(message);
};
},{"./Tableau/Tableau.js":9,"./Tableau/branchAndCut.js":11,"./expressions.js":20}],6:[function(require,module,exports){
/*global describe*/
/*global require*/
/*global module*/
/*global it*/
/*global console*/
/*global process*/
/***************************************************************
* Method: polyopt
* Scope: private
* Agruments:
* model: The model we want solver to operate on.
Because we're in here, we're assuming that
we're solving a multi-objective optimization
problem. Poly-Optimization. polyopt.
This model has to be formed a little differently
because it has multiple objective functions.
Normally, a model has 2 attributes: opType (string,
"max" or "min"), and optimize (string, whatever
attribute we're optimizing.
Now, there is no opType attribute on the model,
and optimize is an object of attributes to be
optimized, and how they're to be optimized.
For example:
...
"optimize": {
"pancakes": "max",
"cost": "minimize"
}
...
**************************************************************/
module.exports = function(solver, model){
// I have no idea if this is actually works, or what,
// but here is my algorithm to solve linear programs
// with multiple objective functions
// 1. Optimize for each constraint
// 2. The results for each solution is a vector
// representing a vertex on the polytope we're creating
// 3. The results for all solutions describes the shape
// of the polytope (would be nice to have the equation
// representing this)
// 4. Find the mid-point between all vertices by doing the
// following (a_1 + a_2 ... a_n) / n;
var objectives = model.optimize,
new_constraints = JSON.parse(JSON.stringify(model.optimize)),
keys = Object.keys(model.optimize),
tmp,
counter = 0,
vectors = {},
vector_key = "",
obj = {},
pareto = [],
i,j,x,y,z;
// Delete the optimize object from the model
delete model.optimize;
// Iterate and Clear
for(i = 0; i < keys.length; i++){
// Clean up the new_constraints
new_constraints[keys[i]] = 0;
}
// Solve and add
for(i = 0; i < keys.length; i++){
// Prep the model
model.optimize = keys[i];
model.opType = objectives[keys[i]];
// solve the model
tmp = solver.Solve(model, undefined, undefined, true);
// Only the variables make it into the solution;
// not the attributes.
//
// Because of this, we have to add the attributes
// back onto the solution so we can do math with
// them later...
// Loop over the keys
for(y in keys){
// We're only worried about attributes, not variables
if(!model.variables[keys[y]]){
// Create space for the attribute in the tmp object
tmp[keys[y]] = tmp[keys[y]] ? tmp[keys[y]] : 0;
// Go over each of the variables
for(x in model.variables){
// Does the variable exist in tmp *and* does attribute exist in this model?
if(model.variables[x][keys[y]] && tmp[x]){
// Add it to tmp
tmp[keys[y]] += tmp[x] * model.variables[x][keys[y]];
}
}
}
}
// clear our key
vector_key = "base";
// this makes sure that if we get
// the same vector more than once,
// we only count it once when finding
// the midpoint
for(j = 0; j < keys.length; j++){
if(tmp[keys[j]]){
vector_key += "-" + ((tmp[keys[j]] * 1000) | 0) / 1000;
} else {
vector_key += "-0";
}
}
// Check here to ensure it doesn't exist
if(!vectors[vector_key]){
// Add the vector-key in
vectors[vector_key] = 1;
counter++;
// Iterate over the keys
// and update our new constraints
for(j = 0; j < keys.length; j++){
if(tmp[keys[j]]){
new_constraints[keys[j]] += tmp[keys[j]];
}
}
// Push the solution into the paretos
// array after cleaning it of some
// excess data markers
delete tmp.feasible;
delete tmp.result;
pareto.push(tmp);
}
}
// Trying to find the mid-point
// divide each constraint by the
// number of constraints
// *midpoint formula*
// (x1 + x2 + x3) / 3
for(i = 0; i < keys.length; i++){
model.constraints[keys[i]] = {"equal": new_constraints[keys[i]] / counter};
}
// Give the model a fake thing to optimize on
model.optimize = "cheater-" + Math.random();
model.opType = "max";
// And add the fake attribute to the variables
// in the model
for(i in model.variables){
model.variables[i].cheater = 1;
}
// Build out the object with all attributes
for(i in pareto){
for(x in pareto[i]){
obj[x] = obj[x] || {min: 1e99, max: -1e99};
}
}
// Give each pareto a full attribute list
// while getting the max and min values
// for each attribute
for(i in obj){
for(x in pareto){
if(pareto[x][i]){
if(pareto[x][i] > obj[i].max){
obj[i].max = pareto[x][i];
}
if(pareto[x][i] < obj[i].min){
obj[i].min = pareto[x][i];
}
} else {
pareto[x][i] = 0;
obj[i].min = 0;
}
}
}
// Solve the model for the midpoints
tmp = solver.Solve(model, undefined, undefined, true);
return {
midpoint: tmp,
vertices: pareto,
ranges: obj
};
};
},{}],7:[function(require,module,exports){
/*global module*/
/*global require*/
var Solution = require("./Solution.js");
function MilpSolution(tableau, evaluation, feasible, bounded, branchAndCutIterations) {
Solution.call(this, tableau, evaluation, feasible, bounded);
this.iter = branchAndCutIterations;
}
module.exports = MilpSolution;
MilpSolution.prototype = Object.create(Solution.prototype);
MilpSolution.constructor = MilpSolution;
},{"./Solution.js":8}],8:[function(require,module,exports){
/*global module*/
function Solution(tableau, evaluation, feasible, bounded) {
this.feasible = feasible;
this.evaluation = evaluation;
this.bounded = bounded;
this._tableau = tableau;
}
module.exports = Solution;
Solution.prototype.generateSolutionSet = function () {
var solutionSet = {};
var tableau = this._tableau;
var varIndexByRow = tableau.varIndexByRow;
var variablesPerIndex = tableau.variablesPerIndex;
var matrix = tableau.matrix;
var rhsColumn = tableau.rhsColumn;
var lastRow = tableau.height - 1;
var roundingCoeff = Math.round(1 / tableau.precision);
for (var r = 1; r <= lastRow; r += 1) {
var varIndex = varIndexByRow[r];
var variable = variablesPerIndex[varIndex];
if (variable === undefined || variable.isSlack === true) {
continue;
}
var varValue = matrix[r][rhsColumn];
solutionSet[variable.id] =
Math.round((Number.EPSILON + varValue) * roundingCoeff) / roundingCoeff;
}
return solutionSet;
};
},{}],9:[function(require,module,exports){
/*global describe*/
/*global require*/
/*global module*/
/*global it*/
/*global console*/
/*global process*/
var Solution = require("./Solution.js");
var MilpSolution = require("./MilpSolution.js");
/*************************************************************
* Class: Tableau
* Description: Simplex tableau, holding a the tableau matrix
* and all the information necessary to perform
* the simplex algorithm
* Agruments:
* precision: If we're solving a MILP, how tight
* do we want to define an integer, given
* that 20.000000000000001 is not an integer.
* (defaults to 1e-8)
**************************************************************/
function Tableau(precision) {
this.model = null;
this.matrix = null;
this.width = 0;
this.height = 0;
this.costRowIndex = 0;
this.rhsColumn = 0;
this.variablesPerIndex = [];
this.unrestrictedVars = null;
// Solution attributes
this.feasible = true; // until proven guilty
this.evaluation = 0;
this.simplexIters = 0;
this.varIndexByRow = null;
this.varIndexByCol = null;
this.rowByVarIndex = null;
this.colByVarIndex = null;
this.precision = precision || 1e-8;
this.optionalObjectives = [];
this.objectivesByPriority = {};
this.savedState = null;
this.availableIndexes = [];
this.lastElementIndex = 0;
this.variables = null;
this.nVars = 0;
this.bounded = true;
this.unboundedVarIndex = null;
this.branchAndCutIterations = 0;
}
module.exports = Tableau;
Tableau.prototype.solve = function () {
if (this.model.getNumberOfIntegerVariables() > 0) {
this.branchAndCut();
} else {
this.simplex();
}
this.updateVariableValues();
return this.getSolution();
};
function OptionalObjective(priority, nColumns) {
this.priority = priority;
this.reducedCosts = new Array(nColumns);
for (var c = 0; c < nColumns; c += 1) {
this.reducedCosts[c] = 0;
}
}
OptionalObjective.prototype.copy = function () {
var copy = new OptionalObjective(this.priority, this.reducedCosts.length);
copy.reducedCosts = this.reducedCosts.slice();
return copy;
};
Tableau.prototype.setOptionalObjective = function (priority, column, cost) {
var objectiveForPriority = this.objectivesByPriority[priority];
if (objectiveForPriority === undefined) {
var nColumns = Math.max(this.width, column + 1);
objectiveForPriority = new OptionalObjective(priority, nColumns);
this.objectivesByPriority[priority] = objectiveForPriority;
this.optionalObjectives.push(objectiveForPriority);
this.optionalObjectives.sort(function (a, b) {
return a.priority - b.priority;
});
}
objectiveForPriority.reducedCosts[column] = cost;
};
//-------------------------------------------------------------------
//-------------------------------------------------------------------
Tableau.prototype.initialize = function (width, height, variables, unrestrictedVars) {
this.variables = variables;
this.unrestrictedVars = unrestrictedVars;
this.width = width;
this.height = height;
// console.time("tableau_build");
// BUILD AN EMPTY ARRAY OF THAT WIDTH
var tmpRow = new Array(width);
for (var i = 0; i < width; i++) {
tmpRow[i] = 0;
}
// BUILD AN EMPTY TABLEAU
this.matrix = new Array(height);
for (var j = 0; j < height; j++) {
this.matrix[j] = tmpRow.slice();
}
//
// TODO: Benchmark This
//this.matrix = new Array(height).fill(0).map(() => new Array(width).fill(0));
// console.timeEnd("tableau_build");
// console.log("height",height);
// console.log("width",width);
// console.log("------");
// console.log("");
this.varIndexByRow = new Array(this.height);
this.varIndexByCol = new Array(this.width);
this.varIndexByRow[0] = -1;
this.varIndexByCol[0] = -1;
this.nVars = width + height - 2;
this.rowByVarIndex = new Array(this.nVars);
this.colByVarIndex = new Array(this.nVars);
this.lastElementIndex = this.nVars;
};
Tableau.prototype._resetMatrix = function () {
var variables = this.model.variables;
var constraints = this.model.constraints;
var nVars = variables.length;
var nConstraints = constraints.length;
var v, varIndex;
var costRow = this.matrix[0];
var coeff = (this.model.isMinimization === true) ? -1 : 1;
for (v = 0; v < nVars; v += 1) {
var variable = variables[v];
var priority = variable.priority;
var cost = coeff * variable.cost;
if (priority === 0) {
costRow[v + 1] = cost;
} else {
this.setOptionalObjective(priority, v + 1, cost);
}
varIndex = variables[v].index;
this.rowByVarIndex[varIndex] = -1;
this.colByVarIndex[varIndex] = v + 1;
this.varIndexByCol[v + 1] = varIndex;
}
var rowIndex = 1;
for (var c = 0; c < nConstraints; c += 1) {
var constraint = constraints[c];
var constraintIndex = constraint.index;
this.rowByVarIndex[constraintIndex] = rowIndex;
this.colByVarIndex[constraintIndex] = -1;
this.varIndexByRow[rowIndex] = constraintIndex;
var t, term, column;
var terms = constraint.terms;
var nTerms = terms.length;
var row = this.matrix[rowIndex++];
if (constraint.isUpperBound) {
for (t = 0; t < nTerms; t += 1) {
term = terms[t];
column = this.colByVarIndex[term.variable.index];
row[column] = term.coefficient;
}
row[0] = constraint.rhs;
} else {
for (t = 0; t < nTerms; t += 1) {
term = terms[t];
column = this.colByVarIndex[term.variable.index];
row[column] = -term.coefficient;
}
row[0] = -constraint.rhs;
}
}
};
//-------------------------------------------------------------------
//-------------------------------------------------------------------
Tableau.prototype.setModel = function (model) {
this.model = model;
var width = model.nVariables + 1;
var height = model.nConstraints + 1;
this.initialize(width, height, model.variables, model.unrestrictedVariables);
this._resetMatrix();
return this;
};
Tableau.prototype.getNewElementIndex = function () {
if (this.availableIndexes.length > 0) {
return this.availableIndexes.pop();
}
var index = this.lastElementIndex;
this.lastElementIndex += 1;
return index;
};
Tableau.prototype.density = function () {
var density = 0;
var matrix = this.matrix;
for (var r = 0; r < this.height; r++) {
var row = matrix[r];
for (var c = 0; c < this.width; c++) {
if (row[c] !== 0) {
density += 1;
}
}
}
return density / (this.height * this.width);
};
//-------------------------------------------------------------------
//-------------------------------------------------------------------
Tableau.prototype.setEvaluation = function () {
// Rounding objective value
var roundingCoeff = Math.round(1 / this.precision);
var evaluation = this.matrix[this.costRowIndex][this.rhsColumn];
var roundedEvaluation =
Math.round((Number.EPSILON + evaluation) * roundingCoeff) / roundingCoeff;
this.evaluation = roundedEvaluation;
if (this.simplexIters === 0) {
this.bestPossibleEval = roundedEvaluation;
}
};
//-------------------------------------------------------------------
//-------------------------------------------------------------------
Tableau.prototype.getSolution = function () {
var evaluation = (this.model.isMinimization === true) ?
this.evaluation : -this.evaluation;
if (this.model.getNumberOfIntegerVariables() > 0) {
return new MilpSolution(this, evaluation, this.feasible, this.bounded, this.branchAndCutIterations);
} else {
return new Solution(this, evaluation, this.feasible, this.bounded);
}
};
},{"./MilpSolution.js":7,"./Solution.js":8}],10:[function(require,module,exports){
/*global require*/
var Tableau = require("./Tableau.js");
Tableau.prototype.copy = function () {
var copy = new Tableau(this.precision);
copy.width = this.width;
copy.height = this.height;
copy.nVars = this.nVars;
copy.model = this.model;
// Making a shallow copy of integer variable indexes
// and variable ids
copy.variables = this.variables;
copy.variablesPerIndex = this.variablesPerIndex;
copy.unrestrictedVars = this.unrestrictedVars;
copy.lastElementIndex = this.lastElementIndex;
// All the other arrays are deep copied
copy.varIndexByRow = this.varIndexByRow.slice();
copy.varIndexByCol = this.varIndexByCol.slice();
copy.rowByVarIndex = this.rowByVarIndex.slice();
copy.colByVarIndex = this.colByVarIndex.slice();
copy.availableIndexes = this.availableIndexes.slice();
var optionalObjectivesCopy = [];
for(var o = 0; o < this.optionalObjectives.length; o++){
optionalObjectivesCopy[o] = this.optionalObjectives[o].copy();
}
copy.optionalObjectives = optionalObjectivesCopy;
var matrix = this.matrix;
var matrixCopy = new Array(this.height);
for (var r = 0; r < this.height; r++) {
matrixCopy[r] = matrix[r].slice();
}
copy.matrix = matrixCopy;
return copy;
};
Tableau.prototype.save = function () {
this.savedState = this.copy();
};
Tableau.prototype.restore = function () {
if (this.savedState === null) {
return;
}
var save = this.savedState;
var savedMatrix = save.matrix;
this.nVars = save.nVars;
this.model = save.model;
// Shallow restore
this.variables = save.variables;
this.variablesPerIndex = save.variablesPerIndex;
this.unrestrictedVars = save.unrestrictedVars;
this.lastElementIndex = save.lastElementIndex;
this.width = save.width;
this.height = save.height;
// Restoring matrix
var r, c;
for (r = 0; r < this.height; r += 1) {
var savedRow = savedMatrix[r];
var row = this.matrix[r];
for (c = 0; c < this.width; c += 1) {
row[c] = savedRow[c];
}
}
// Restoring all the other structures
var savedBasicIndexes = save.varIndexByRow;
for (c = 0; c < this.height; c += 1) {
this.varIndexByRow[c] = savedBasicIndexes[c];
}
while (this.varIndexByRow.length > this.height) {
this.varIndexByRow.pop();
}
var savedNonBasicIndexes = save.varIndexByCol;
for (r = 0; r < this.width; r += 1) {
this.varIndexByCol[r] = savedNonBasicIndexes[r];
}
while (this.varIndexByCol.length > this.width) {
this.varIndexByCol.pop();
}
var savedRows = save.rowByVarIndex;
var savedCols = save.colByVarIndex;
for (var v = 0; v < this.nVars; v += 1) {
this.rowByVarIndex[v] = savedRows[v];
this.colByVarIndex[v] = savedCols[v];
}
if (save.optionalObjectives.length > 0 && this.optionalObjectives.length > 0) {
this.optionalObjectives = [];
this.optionalObjectivePerPriority = {};
for(var o = 0; o < save.optionalObjectives.length; o++){
var optionalObjectiveCopy = save.optionalObjectives[o].copy();
this.optionalObjectives[o] = optionalObjectiveCopy;
this.optionalObjectivePerPriority[optionalObjectiveCopy.priority] = optionalObjectiveCopy;
}
}
};
},{"./Tableau.js":9}],11:[function(require,module,exports){
/*global describe*/
/*global require*/
/*global module*/
/*global it*/
/*global console*/
/*global process*/
var Tableau = require("./Tableau.js");
//-------------------------------------------------------------------
//-------------------------------------------------------------------
function Cut(type, varIndex, value) {
this.type = type;
this.varIndex = varIndex;
this.value = value;
}
//-------------------------------------------------------------------
//-------------------------------------------------------------------
function Branch(relaxedEvaluation, cuts) {
this.relaxedEvaluation = relaxedEvaluation;
this.cuts = cuts;
}
//-------------------------------------------------------------------
// Branch sorting strategies
//------------------------------------------------------