UNPKG

javascript-lp-solver

Version:

Easy to use, JSON oriented Linear Programming and Mixed Int. Programming Solver

277 lines (223 loc) 8.49 kB
/*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); } };