@uandi/javascript-lp-solver
Version:
Easy to use, JSON oriented Linear Programming and Mixed Int. Programming Solver
1,578 lines (1,309 loc) • 83.6 kB
JavaScript
(function(){if (typeof exports === "object") {module.exports = require("./main");}})();
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/*global describe*/
/*global require*/
/*global module*/
/*global it*/
/*global console*/
/*global process*/
//-------------------------------------------------------------------
//-------------------------------------------------------------------
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
//-------------------------------------------------------------------
function sortByEvaluation(a, b) {
return b.relaxedEvaluation - a.relaxedEvaluation;
}
//-------------------------------------------------------------------
// Function: MILP
// Detail: Main function, my attempt at a mixed integer linear programming
// solver
//-------------------------------------------------------------------
function MILP(model) {
var branches = [];
var iterations = 0;
var tableau = model.tableau;
// This is the default result
// If nothing is both *integral* and *feasible*
var bestEvaluation = Infinity;
var bestBranch = null;
// And here...we...go!
// Restoring initial solution
tableau.restore();
// Running solver a first time to obtain an initial solution
tableau.solve();
// Saving initial solution
tableau.save();
// 1.) Load a model into the queue
var branch = new Branch(-Infinity, []);
branches.push(branch);
// If all branches have been exhausted terminate the loop
while (branches.length > 0) {
// Get a model from the queue
branch = branches.pop();
if (branch.relaxedEvaluation >= bestEvaluation) {
continue;
}
// Solving from initial relaxed solution
// with additional cut constraints
// Restoring initial solution
tableau.restore();
// Adding cut constraints
var cuts = branch.cuts;
tableau.addCutConstraints(cuts);
// Solving
tableau.solve();
// Keep Track of how many cycles
// we've gone through
iterations++;
if (tableau.feasible === false) {
continue;
}
var evaluation = tableau.evaluation;
// Is the model both integral and feasible?
if (tableau.isIntegral() === true) {
// Is the new result the bestSolution that we've ever had?
if (evaluation < bestEvaluation) {
// Store the solution as the bestSolution
bestBranch = branch;
bestEvaluation = evaluation;
}
// The solution is feasible and interagal;
// But it is worse than the current solution;
// Ignore it.
} else if (evaluation < bestEvaluation) {
// If the solution is
// a. Feasible
// b. Better than the current solution
// c. but *NOT* integral
// So the solution isn't integral? How do we solve this.
// We create 2 new models, that are mirror images of the prior
// model, with 1 exception.
// Say we're trying to solve some stupid problem requiring you get
// animals for your daughter's kindergarten petting zoo party
// and you have to choose how many ducks, goats, and lambs to get.
// Say that the optimal solution to this problem if we didn't have
// to make it integral was {duck: 8, lambs: 3.5}
//
// To keep from traumatizing your daughter and the other children
// you're going to want to have whole animals
// What we would do is find the most fractional variable (lambs)
// and create new models from the old models, but with a new constraint
// on apples. The constraints on the low model would look like:
// constraints: {...
// lamb: {max: 3}
// ...
// }
//
// while the constraints on the high model would look like:
//
// constraints: {...
// lamb: {min: 4}
// ...
// }
// If neither of these models is feasible because of this constraint,
// the model is not integral at this point, and fails.
// Find out where we want to split the solution
var variable = tableau.getMostFractionalVar();
// var variable = tableau.getFractionalVarWithLowestCost();
var varIndex = variable.index;
var cutsHigh = [];
var cutsLow = [];
var nCuts = cuts.length;
for (var c = 0; c < nCuts; c += 1) {
var cut = cuts[c];
if (cut.varIndex === varIndex) {
if (cut.type === "min") {
cutsLow.push(cut);
} else {
cutsHigh.push(cut);
}
} else {
cutsHigh.push(cut);
cutsLow.push(cut);
}
}
var min = Math.ceil(variable.value);
var max = Math.floor(variable.value);
var cutHigh = new Cut("min", varIndex, min);
cutsHigh.push(cutHigh);
var cutLow = new Cut("max", varIndex, max);
cutsLow.push(cutLow);
branches.push(new Branch(evaluation, cutsHigh));
branches.push(new Branch(evaluation, cutsLow));
// Sorting branches
// Branches with the most promising lower bounds
// will be picked first
branches.sort(sortByEvaluation);
}
}
// Restoring initial solution
tableau.restore();
// Adding cut constraints for the optimal solution
tableau.addCutConstraints(bestBranch.cuts);
// Solving a last time
var bestSolution = tableau.solve().getSolution();
tableau.updateVariableValues();
bestSolution.iter = iterations;
return bestSolution;
}
module.exports = MILP;
},{}],2:[function(require,module,exports){
/*global describe*/
/*global require*/
/*global module*/
/*global it*/
/*global console*/
/*global process*/
var Tableau = require("./Tableau.js");
var branchAndCut = require("./branchAndCut.js");
var expressions = require("./expressions.js");
var Constraint = expressions.Constraint;
var Equality = expressions.Equality;
var Variable = expressions.Variable;
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.variableIds = [];
this.integerVariables = [];
this.unrestrictedVariables = {};
this.constraints = [];
this.nConstraints = 0;
this.nVariables = 0;
this.isMinimization = true;
this.availableIndexes = [];
this.lastElementIndex = 0;
this.tableauInitialized = false;
}
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) {
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._getNewElementIndex(), this);
this._addConstraint(constraint);
return constraint;
};
Model.prototype.greaterThan = function (rhs) {
var constraint = new Constraint(rhs, false, this._getNewElementIndex(), this);
this._addConstraint(constraint);
return constraint;
};
Model.prototype.equal = function (rhs) {
var constraintUpper = new Constraint(rhs, true, this._getNewElementIndex(), this);
this._addConstraint(constraintUpper);
var constraintLower = new Constraint(rhs, false, this._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._getNewElementIndex();
if (!id) { // could be null, undefined or empty string
id = "v" + varIndex;
}
if (!cost) { // could be null, undefined or already 0
cost = 0;
}
if (!priority) { // could be null, undefined or already 0
priority = 0;
}
var variable = new Variable(id, cost, varIndex, priority);
this.variables.push(variable);
this.variableIds[varIndex] = id;
if (isInteger) {
this.integerVariables.push(variable);
}
if (isUnrestricted) {
this.unrestrictedVariables[varIndex] = true;
}
this.nVariables += 1;
if (this.tableauInitialized === true) {
this.tableau.addVariable(variable);
}
return variable;
};
//-------------------------------------------------------------------
// For dynamic model modification
//-------------------------------------------------------------------
Model.prototype.removeConstraint = function (constraint) {
var idx = this.constraints.indexOf(constraint);
if (idx === -1) {
console.warn("[Model.removeConstraint] Constraint not present in model");
return;
}
if (this.tableauInitialized === true) {
if (constraint instanceof Equality === true) {
this.tableau.removeConstraint(constraint.upperBound);
this.tableau.removeConstraint(constraint.lowerBound);
} else {
this.tableau.removeConstraint(constraint);
}
}
this.constraints.splice(idx, 1);
this.nConstraints -= 1;
this.availableIndexes.push(constraint.index);
if (constraint.relaxation) {
this.removeVariable(constraint.relaxation);
}
return this;
};
Model.prototype.removeVariable = function (variable) {
// TODO ? remove variable term from every constraint?
// How: every variable should reference the constraints it appears in
var idx = this.variables.indexOf(variable);
if (idx === -1) {
console.warn("[Model.removeVariable] Variable not present in model");
return;
}
if (this.tableauInitialized === true) {
this.tableau.removeVariable(variable);
}
this.availableIndexes.push(variable.index);
variable.index = -1;
this.variables.splice(idx, 1);
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 min = (equal === undefined) ? constraint.min : equal;
if (min !== undefined) {
constraintsMin[constraintId] = this.greaterThan(min);
}
var max = (equal === undefined) ? constraint.max : equal;
if (max !== undefined) {
constraintsMax[constraintId] = this.smallerThan(max);
}
if (equal !== undefined) {
var equality = new Equality(constraintsMin[constraintId], constraintsMax[constraintId]);
if (constraint.weight !== undefined) {
equality.relax(constraint.weight);
}
continue;
}
}
var variableIds = Object.keys(variables);
var nVariables = variableIds.length;
var integerVarIds = jsonModel.ints || {};
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 isInteger = !!integerVarIds[variableId];
var isUnrestricted = !!unrestrictedVarIds[variableId];
var variable = this.addVariable(cost, variableId, isInteger, isUnrestricted);
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;
}
if (this.getNumberOfIntegerVariables() > 0) {
return MILP(this);
} else {
var solution = this.tableau.solve().getSolution();
this.tableau.updateVariableValues();
return solution;
}
};
Model.prototype.compileSolution = function () {
return this.tableau.compileSolution();
};
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.log = function (message) {
return this.tableau.log(message);
};
},{"./MILP.js":1,"./Tableau.js":5,"./expressions.js":7}],3:[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
};
};
},{}],4:[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_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,
"get_num": /(\-|\+){0,1}(\W|^)\d+\.{0,}\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_constraint.test(tmp)){
// Pull apart lhs
ary = tmp.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 type
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
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,"_");
}
}
// Add some closure to our line thing
output += ";\n";
// And now... to iterate over the constraints
for(x in model.constraints){
for(var y in model.constraints[x]){
for(var z in model.variables){
// Does our Constraint exist here?
if(model.variables[z][x]){
output += " " + model.variables[z][x] + " " + z.replace(rxClean,"_");
}
}
// Add the constraint type and value...
output += " " + lookup[y] + " " + model.constraints[x][y];
output += ";\n";
}
}
// Are there any ints?
if(model.ints){
output += "\n\n";
for(x in model.ints){
output += "int " + x.replace(rxClean,"_") + ";\n";
}
}
// Are there any unrestricted?
if(model.unrestricted){
output += "\n\n";
for(x in model.unrestricted){
output += "unrestricted " + x.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);
}
};
},{}],5:[function(require,module,exports){
/*global describe*/
/*global require*/
/*global module*/
/*global it*/
/*global console*/
/*global process*/
/*************************************************************
* 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.variableIds = null;
this.unrestrictedVars = null;
// Solution attributes
this.feasible = true; // until proven guilty
this.evaluation = 0;
this.basicIndexes = null;
this.nonBasicIndexes = null;
this.rows = null;
this.cols = null;
this.precision = precision || 1e-8;
this.optionalObjectives = [];
this.objectivesByPriority = {};
this.savedState = null;
}
module.exports = Tableau;
//-------------------------------------------------------------------
//-------------------------------------------------------------------
Tableau.prototype.initialize = function (width, height, variables, variableIds, unrestrictedVars) {
this.variables = variables;
this.variableIds = variableIds;
this.unrestrictedVars = unrestrictedVars;
this.width = width;
this.height = height;
// 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();
}
this.basicIndexes = new Array(this.height);
this.nonBasicIndexes = new Array(this.width);
this.basicIndexes[0] = -1;
this.nonBasicIndexes[0] = -1;
this.nVars = width + height - 2;
this.rows = new Array(this.nVars);
this.cols = new Array(this.nVars);
};
//-------------------------------------------------------------------
// Function: solve
// Detail: Main function, linear programming solver
//-------------------------------------------------------------------
Tableau.prototype.solve = function () {
// Execute Phase 1 to obtain a Basic Feasible Solution (BFS)
this.phase1();
// Execute Phase 2
if (this.feasible === true) {
// Running simplex on Initial Basic Feasible Solution (BFS)
// N.B current solution is feasible
this.phase2();
}
return this;
};
function Solution(tableau, evaluation, feasible) {
this.feasible = feasible;
this.evaluation = evaluation;
this._tableau = tableau;
}
Solution.prototype.generateSolutionSet = function () {
var solutionSet = {};
var tableau = this._tableau;
var basicIndexes = tableau.basicIndexes;
var variableIds = tableau.variableIds;
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 = basicIndexes[r];
var variableId = variableIds[varIndex];
if (variableId !== undefined) {
var varValue = matrix[r][rhsColumn];
solutionSet[variableId] =
Math.round(varValue * roundingCoeff) / roundingCoeff;
}
}
return solutionSet;
};
//-------------------------------------------------------------------
//-------------------------------------------------------------------
Tableau.prototype.updateVariableValues = function () {
var nVars = this.variables.length;
var roundingCoeff = Math.round(1 / this.precision);
for (var v = 0; v < nVars; v += 1) {
var variable = this.variables[v];
var varIndex = variable.index;
var r = this.rows[varIndex];
if (r === -1) {
// Variable is non basic
variable.value = 0;
} else {
// Variable is basic
var varValue = this.matrix[r][this.rhsColumn];
variable.value = Math.round(varValue * roundingCoeff) / roundingCoeff;
}
}
};
//-------------------------------------------------------------------
//-------------------------------------------------------------------
Tableau.prototype.getSolution = function () {
var evaluation = (this.model.isMinimization === true) ?
this.evaluation : -this.evaluation;
return new Solution(this, evaluation, this.feasible);
};
//-------------------------------------------------------------------
//-------------------------------------------------------------------
Tableau.prototype.isIntegral = function () {
var integerVariables = this.model.integerVariables;
var nIntegerVars = integerVariables.length;
for (var v = 0; v < nIntegerVars; v++) {
var varRow = this.rows[integerVariables[v].index];
if (varRow === -1) {
continue;
}
var varValue = this.matrix[varRow][this.rhsColumn];
if (Math.abs(varValue - Math.round(varValue)) > this.precision) {
return false;
}
}
return true;
};
function VariableData(index, value) {
this.index = index;
this.value = value;
}
//-------------------------------------------------------------------
//-------------------------------------------------------------------
Tableau.prototype.getMostFractionalVar = function () {
var biggestFraction = 0;
var selectedVarIndex = null;
var selectedVarValue = null;
var mid = 0.5;
var integerVariables = this.model.integerVariables;
var nIntegerVars = integerVariables.length;
for (var v = 0; v < nIntegerVars; v++) {
var varIndex = integerVariables[v].index;
var varRow = this.rows[varIndex];
if (varRow === -1) {
continue;
}
var varValue = this.matrix[varRow][this.rhsColumn];
var fraction = Math.abs(varValue - Math.round(varValue));
if (biggestFraction < fraction) {
biggestFraction = fraction;
selectedVarIndex = varIndex;
selectedVarValue = varValue;
}
}
return new VariableData(selectedVarIndex, selectedVarValue);
};
//-------------------------------------------------------------------
//-------------------------------------------------------------------
Tableau.prototype.getFractionalVarWithLowestCost = function () {
var highestCost = Infinity;
var selectedVarIndex = null;
var selectedVarValue = null;
var integerVariables = this.model.integerVariables;
var nIntegerVars = integerVariables.length;
for (var v = 0; v < nIntegerVars; v++) {
var variable = integerVariables[v];
var varIndex = variable.index;
var varRow = this.rows[varIndex];
if (varRow === -1) {
// Variable value is non basic
// its value is 0
continue;
}
var varValue = this.matrix[varRow][this.rhsColumn];
if (Math.abs(varValue - Math.round(varValue)) > this.precision) {
var cost = variable.cost;
if (highestCost > cost) {
highestCost = cost;
selectedVarIndex = varIndex;
selectedVarValue = varValue;
}
}
}
return new VariableData(selectedVarIndex, selectedVarValue);
};
//-------------------------------------------------------------------
//-------------------------------------------------------------------
Tableau.prototype.setEvaluation = function () {
// Rounding objective value
var roundingCoeff = Math.round(1 / this.precision);
var evaluation = this.matrix[this.costRowIndex][this.rhsColumn];
this.evaluation =
Math.round(evaluation * roundingCoeff) / roundingCoeff;
};
//-------------------------------------------------------------------
// Description: Convert a non standard form tableau
// to a standard form tableau by eliminating
// all negative values in the Right Hand Side (RHS)
// This results in a Basic Feasible Solution (BFS)
//
//-------------------------------------------------------------------
Tableau.prototype.phase1 = function () {
var matrix = this.matrix;
var rhsColumn = this.rhsColumn;
var lastColumn = this.width - 1;
var lastRow = this.height - 1;
var unrestricted;
var iterations = 0;
while (true) {
// Selecting leaving variable (feasibility condition):
// Basic variable with most negative value
var leavingRowIndex = 0;
var rhsValue = -this.precision;
for (var r = 1; r <= lastRow; r++) {
unrestricted = this.unrestrictedVars[this.basicIndexes[r]] === true;
if (unrestricted) {
continue;
}
var value = matrix[r][rhsColumn];
if (value < rhsValue) {
rhsValue = value;
leavingRowIndex = r;
}
}
// If nothing is strictly smaller than 0; we're done with phase 1.
if (leavingRowIndex === 0) {
// Feasible, champagne!
this.feasible = true;
return iterations;
}
// Selecting entering variable
var enteringColumn = 0;
var maxQuotient = -Infinity;
var costRow = matrix[0];
var leavingRow = matrix[leavingRowIndex];
for (var c = 1; c <= lastColumn; c++) {
var reducedCost = leavingRow[c];
if (-this.precision < reducedCost && reducedCost < this.precision) {
continue;
}
unrestricted = this.unrestrictedVars[this.nonBasicIndexes[c]] === true;
if (unrestricted || reducedCost < -this.precision) {
var quotient = -costRow[c] / reducedCost;
if (maxQuotient < quotient) {
maxQuotient = quotient;
enteringColumn = c;
}
}
}
if (enteringColumn === 0) {
// Not feasible
this.feasible = false;
return iterations;
}
this.pivot(leavingRowIndex, enteringColumn);
iterations += 1;
}
};
//-------------------------------------------------------------------
// Description: Apply simplex to obtain optimal soltuion
// used as phase2 of the simplex
//
//-------------------------------------------------------------------
Tableau.prototype.phase2 = function () {
var matrix = this.matrix;
var rhsColumn = this.rhsColumn;
var lastColumn = this.width - 1;
var lastRow = this.height - 1;
var precision = this.precision;
var nOptionalObjectives = this.optionalObjectives.length;
var optionalCostsColumns = null;
var iterations = 0;
var reducedCost, unrestricted;
while (true) {
var costRow = matrix[this.costRowIndex];
// Selecting entering variable (optimality condition)
if (nOptionalObjectives > 0) {
optionalCostsColumns = [];
}
var enteringColumn = 0;
var enteringValue = this.precision;
var isReducedCostNegative = false;
for (var c = 1; c <= lastColumn; c++) {
reducedCost = costRow[c];
unrestricted = this.unrestrictedVars[this.nonBasicIndexes[c]] === true;
if (nOptionalObjectives > 0 && -this.precision < reducedCost && reducedCost < this.precision) {
optionalCostsColumns.push(c);
continue;
}
if (unrestricted && reducedCost < 0) {
if (-reducedCost > enteringValue) {
enteringValue = -reducedCost;
enteringColumn = c;
isReducedCostNegative = true;
}
continue;
}
if (reducedCost > enteringValue) {
enteringValue = reducedCost;
enteringColumn = c;
isReducedCostNegative = false;
}
}
if (nOptionalObjectives > 0) {
// There exist optional improvable objectives
var o = 0;
while (enteringColumn === 0 && optionalCostsColumns.length > 0 && o < nOptionalObjectives) {
var optionalCostsColumns2 = [];
var reducedCosts = this.optionalObjectives[o].reducedCosts;
for (var i = 0; i <= optionalCostsColumns.length; i++) {
c = optionalCostsColumns[i];
reducedCost = reducedCosts[c];
unrestricted = this.unrestrictedVars[this.nonBasicIndexes[c]] === true;
if (-this.precision < reducedCost && reducedCost < this.precision) {
optionalCostsColumns2.push(c);
continue;
}
if (unrestricted && reducedCost < 0) {
if (-reducedCost > enteringValue) {
enteringValue = -reducedCost;
enteringColumn = c;
isReducedCostNegative = true;
}
continue;
}
if (reducedCost > enteringValue) {
enteringValue = reducedCost;
enteringColumn = c;
isReducedCostNegative = false;
}
}
optionalCostsColumns = optionalCostsColumns2;
o += 1;
}
}
// If no entering column could be found we're done with phase 2.
if (enteringColumn === 0) {
this.setEvaluation();
return;
}
// Selecting leaving variable
var leavingRow = 0;
var minQuotient = Infinity;
for (var r = 1; r <= lastRow; r++) {
var row = matrix[r];
var rhsValue = row[rhsColumn];
var colValue = row[enteringColumn];
if (-precision < colValue && colValue < precision) {
continue;
}
if (colValue > 0 && precision > rhsValue && rhsValue > -precision) {
minQuotient = 0;
leavingRow = r;
break;
}
var quotient = isReducedCostNegative ? -rhsValue / colValue : rhsValue / colValue;
if (quotient > 0 && minQuotient > quotient) {
minQuotient = quotient;
leavingRow = r;
}
}
if (minQuotient === Infinity) {
// TODO: solution is not bounded
// optimal value is -Infinity
this.evaluation = -Infinity;
return;
}
this.pivot(leavingRow, enteringColumn, true);
iterations += 1;
}
};
// Array holding the column indexes for which the value is not null
// on the pivot row
// Shared by all tableaux for smaller overhead and lower memory usage
var nonZeroColumns = [];
//-------------------------------------------------------------------
// Description: Execute pivot operations over a 2d array,
// on a given row, and column
//
//-------------------------------------------------------------------
Tableau.prototype.pivot = function (pivotRowIndex, pivotColumnIndex, debug) {
var matrix = this.matrix;
var quotient = matrix[pivotRowIndex][pivotColumnIndex];
var lastRow = this.height - 1;
var lastColumn = this.width - 1;
var leavingBasicIndex = this.basicIndexes[pivotRowIndex];
var enteringBasicIndex = this.nonBasicIndexes[pivotColumnIndex];
this.basicIndexes[pivotRowIndex] = enteringBasicIndex;
this.nonBasicIndexes[pivotColumnIndex] = leavingBasicIndex;
this.rows[enteringBasicIndex] = pivotRowIndex;
this.rows[leavingBasicIndex] = -1;
this.cols[enteringBasicIndex] = -1;
this.cols[leavingBasicIndex] = pivotColumnIndex;
// Divide everything in the target row by the element @
// the target column
var pivotRow = matrix[pivotRowIndex];
var nNonZeroColumns = 0;
for (var c = 0; c <= lastColumn; c++) {
if (pivotRow[c] !== 0) {
pivotRow[c] /= quotient;
nonZeroColumns[nNonZeroColumns] = c;
nNonZeroColumns += 1;
}
}
pivotRow[pivotColumnIndex] = 1 / quotient;
// for every row EXCEPT the pivot row,
// set the value in the pivot column = 0 by
// multiplying the value of all elements in the objective
// row by ... yuck... just look below; better explanation later
var coefficient, i, v0;
var precision = this.precision;
for (var r = 0; r <= lastRow; r++) {
var row = matrix[r];
if (r !== pivotRowIndex) {
coefficient = row[pivotColumnIndex];
// No point Burning Cycles if
// Zero to the thing
if (coefficient !== 0) {
for (i = 0; i < nNonZeroColumns; i++) {
c = nonZeroColumns[i];
// No point in doing math if you're just adding
// Zero to the thing
v0 = pivotRow[c];
if (v0 !== 0) {
row[c] = row[c] - coefficient * v0;
}
}
row[pivotColumnIndex] = -coefficient / quotient;
}
}
}
var nOptionalObjectives = this.optionalObjectives.length;
if (nOptionalObjectives > 0) {
for (var o = 0; o < nOptionalObjectives; o += 1) {
var reducedCosts = this.optionalObjectives[o].reducedCosts;
coefficient = reducedCosts[pivotColumnIndex];
if (coefficient !== 0) {
for (i = 0; i < nNonZeroColumns; i++) {
c = nonZeroColumns[i];
v0 = pivotRow[c];
if (v0 !== 0) {
red