javascript-lp-solver
Version:
Easy to use, JSON oriented Linear Programming and Mixed Int. Programming Solver
305 lines (254 loc) • 10.2 kB
JavaScript
/*global require*/
/*global console*/
var Tableau = require("./Tableau.js");
//-------------------------------------------------------------------
//-------------------------------------------------------------------
Tableau.prototype._putInBase = function (varIndex) {
// Is varIndex in the base?
var r = this.rowByVarIndex[varIndex];
if (r === -1) {
// Outside the base
// pivoting to take it out
var c = this.colByVarIndex[varIndex];
// Selecting pivot row
// (Any row with coefficient different from 0)
for (var r1 = 1; r1 < this.height; r1 += 1) {
var coefficient = this.matrix[r1][c];
if (coefficient < -this.precision || this.precision < coefficient) {
r = r1;
break;
}
}
this.pivot(r, c);
}
return r;
};
Tableau.prototype._takeOutOfBase = function (varIndex) {
// Is varIndex in the base?
var c = this.colByVarIndex[varIndex];
if (c === -1) {
// Inside the base
// pivoting to take it out
var r = this.rowByVarIndex[varIndex];
// Selecting pivot column
// (Any column with coefficient different from 0)
var pivotRow = this.matrix[r];
for (var c1 = 1; c1 < this.height; c1 += 1) {
var coefficient = pivotRow[c1];
if (coefficient < -this.precision || this.precision < coefficient) {
c = c1;
break;
}
}
this.pivot(r, c);
}
return c;
};
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.rowByVarIndex[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 + Number.EPSILON) * roundingCoeff) / roundingCoeff;
}
}
};
Tableau.prototype.updateRightHandSide = function (constraint, difference) {
// Updates RHS of given constraint
var lastRow = this.height - 1;
var constraintRow = this.rowByVarIndex[constraint.index];
if (constraintRow === -1) {
// Slack is not in base
var slackColumn = this.colByVarIndex[constraint.index];
// Upading all the RHS values
for (var r = 0; r <= lastRow; r += 1) {
var row = this.matrix[r];
row[this.rhsColumn] -= difference * row[slackColumn];
}
var nOptionalObjectives = this.optionalObjectives.length;
if (nOptionalObjectives > 0) {
for (var o = 0; o < nOptionalObjectives; o += 1) {
var reducedCosts = this.optionalObjectives[o].reducedCosts;
reducedCosts[this.rhsColumn] -= difference * reducedCosts[slackColumn];
}
}
} else {
// Slack variable of constraint is in base
// Updating RHS with the difference between the old and the new one
this.matrix[constraintRow][this.rhsColumn] -= difference;
}
};
Tableau.prototype.updateConstraintCoefficient = function (constraint, variable, difference) {
// Updates variable coefficient within a constraint
if (constraint.index === variable.index) {
throw new Error("[Tableau.updateConstraintCoefficient] constraint index should not be equal to variable index !");
}
var r = this._putInBase(constraint.index);
var colVar = this.colByVarIndex[variable.index];
if (colVar === -1) {
var rowVar = this.rowByVarIndex[variable.index];
for (var c = 0; c < this.width; c += 1){
this.matrix[r][c] += difference * this.matrix[rowVar][c];
}
} else {
this.matrix[r][colVar] -= difference;
}
};
Tableau.prototype.updateCost = function (variable, difference) {
// Updates variable coefficient within the objective function
var varIndex = variable.index;
var lastColumn = this.width - 1;
var varColumn = this.colByVarIndex[varIndex];
if (varColumn === -1) {
// Variable is in base
var variableRow = this.matrix[this.rowByVarIndex[varIndex]];
var c;
if (variable.priority === 0) {
var costRow = this.matrix[0];
// Upading all the reduced costs
for (c = 0; c <= lastColumn; c += 1) {
costRow[c] += difference * variableRow[c];
}
} else {
var reducedCosts = this.objectivesByPriority[variable.priority].reducedCosts;
for (c = 0; c <= lastColumn; c += 1) {
reducedCosts[c] += difference * variableRow[c];
}
}
} else {
// Variable is not in the base
// Updating coefficient with difference
this.matrix[0][varColumn] -= difference;
}
};
Tableau.prototype.addConstraint = function (constraint) {
// Adds a constraint to the tableau
var sign = constraint.isUpperBound ? 1 : -1;
var lastRow = this.height;
var constraintRow = this.matrix[lastRow];
if (constraintRow === undefined) {
constraintRow = this.matrix[0].slice();
this.matrix[lastRow] = constraintRow;
}
// Setting all row cells to 0
var lastColumn = this.width - 1;
for (var c = 0; c <= lastColumn; c += 1) {
constraintRow[c] = 0;
}
// Initializing RHS
constraintRow[this.rhsColumn] = sign * constraint.rhs;
var terms = constraint.terms;
var nTerms = terms.length;
for (var t = 0; t < nTerms; t += 1) {
var term = terms[t];
var coefficient = term.coefficient;
var varIndex = term.variable.index;
var varRowIndex = this.rowByVarIndex[varIndex];
if (varRowIndex === -1) {
// Variable is non basic
constraintRow[this.colByVarIndex[varIndex]] += sign * coefficient;
} else {
// Variable is basic
var varRow = this.matrix[varRowIndex];
var varValue = varRow[this.rhsColumn];
for (c = 0; c <= lastColumn; c += 1) {
constraintRow[c] -= sign * coefficient * varRow[c];
}
}
}
// Creating slack variable
var slackIndex = constraint.index;
this.varIndexByRow[lastRow] = slackIndex;
this.rowByVarIndex[slackIndex] = lastRow;
this.colByVarIndex[slackIndex] = -1;
this.height += 1;
};
Tableau.prototype.removeConstraint = function (constraint) {
var slackIndex = constraint.index;
var lastRow = this.height - 1;
// Putting the constraint's slack in the base
var r = this._putInBase(slackIndex);
// Removing constraint
// by putting the corresponding row at the bottom of the matrix
// and virtually reducing the height of the matrix by 1
var tmpRow = this.matrix[lastRow];
this.matrix[lastRow] = this.matrix[r];
this.matrix[r] = tmpRow;
// Removing associated slack variable from basic variables
this.varIndexByRow[r] = this.varIndexByRow[lastRow];
this.varIndexByRow[lastRow] = -1;
this.rowByVarIndex[slackIndex] = -1;
// Putting associated slack variable index in index manager
this.availableIndexes[this.availableIndexes.length] = slackIndex;
constraint.slack.index = -1;
this.height -= 1;
};
Tableau.prototype.addVariable = function (variable) {
// Adds a variable to the tableau
// var sign = constraint.isUpperBound ? 1 : -1;
var lastRow = this.height - 1;
var lastColumn = this.width;
var cost = this.model.isMinimization === true ? -variable.cost : variable.cost;
var priority = variable.priority;
// Setting reduced costs
var nOptionalObjectives = this.optionalObjectives.length;
if (nOptionalObjectives > 0) {
for (var o = 0; o < nOptionalObjectives; o += 1) {
this.optionalObjectives[o].reducedCosts[lastColumn] = 0;
}
}
if (priority === 0) {
this.matrix[0][lastColumn] = cost;
} else {
this.setOptionalObjective(priority, lastColumn, cost);
this.matrix[0][lastColumn] = 0;
}
// Setting all other column cells to 0
for (var r = 1; r <= lastRow; r += 1) {
this.matrix[r][lastColumn] = 0;
}
// Adding variable to trackers
var varIndex = variable.index;
this.varIndexByCol[lastColumn] = varIndex;
this.rowByVarIndex[varIndex] = -1;
this.colByVarIndex[varIndex] = lastColumn;
this.width += 1;
};
Tableau.prototype.removeVariable = function (variable) {
var varIndex = variable.index;
// Putting the variable out of the base
var c = this._takeOutOfBase(varIndex);
var lastColumn = this.width - 1;
if (c !== lastColumn) {
var lastRow = this.height - 1;
for (var r = 0; r <= lastRow; r += 1) {
var row = this.matrix[r];
row[c] = row[lastColumn];
}
var nOptionalObjectives = this.optionalObjectives.length;
if (nOptionalObjectives > 0) {
for (var o = 0; o < nOptionalObjectives; o += 1) {
var reducedCosts = this.optionalObjectives[o].reducedCosts;
reducedCosts[c] = reducedCosts[lastColumn];
}
}
var switchVarIndex = this.varIndexByCol[lastColumn];
this.varIndexByCol[c] = switchVarIndex;
this.colByVarIndex[switchVarIndex] = c;
}
// Removing variable from non basic variables
this.varIndexByCol[lastColumn] = -1;
this.colByVarIndex[varIndex] = -1;
// Adding index into index manager
this.availableIndexes[this.availableIndexes.length] = varIndex;
variable.index = -1;
this.width -= 1;
};