javascript-lp-solver
Version:
Easy to use, JSON oriented Linear Programming and Mixed Int. Programming Solver
271 lines (227 loc) • 9.5 kB
JavaScript
/*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
//-------------------------------------------------------------------
function sortByEvaluation(a, b) {
return b.relaxedEvaluation - a.relaxedEvaluation;
}
//-------------------------------------------------------------------
// Applying cuts on a tableau and resolving
//-------------------------------------------------------------------
Tableau.prototype.applyCuts = function (branchingCuts){
// Restoring initial solution
this.restore();
this.addCutConstraints(branchingCuts);
this.simplex();
// Adding MIR cuts
if (this.model.useMIRCuts){
var fractionalVolumeImproved = true;
while(fractionalVolumeImproved){
var fractionalVolumeBefore = this.computeFractionalVolume(true);
this.applyMIRCuts();
this.simplex();
var fractionalVolumeAfter = this.computeFractionalVolume(true);
// If the new fractional volume is bigger than 90% of the previous one
// we assume there is no improvement from the MIR cuts
if(fractionalVolumeAfter >= 0.9 * fractionalVolumeBefore){
fractionalVolumeImproved = false;
}
}
}
};
//-------------------------------------------------------------------
// Function: MILP
// Detail: Main function, my attempt at a mixed integer linear programming
// solver
//-------------------------------------------------------------------
Tableau.prototype.branchAndCut = function () {
var branches = [];
var iterations = 0;
var tolerance = this.model.tolerance;
var toleranceFlag = true;
var terminalTime = 1e99;
//
// Set Start Time on model...
// Let's build out a way to *gracefully* quit
// after {{time}} milliseconds
//
// 1.) Check to see if there's a timeout on the model
//
if(this.model.timeout){
// 2.) Hooray! There is!
// Calculate the final date
//
terminalTime = Date.now() + this.model.timeout;
}
// This is the default result
// If nothing is both *integral* and *feasible*
var bestEvaluation = Infinity;
var bestBranch = null;
var bestOptionalObjectivesEvaluations = [];
for (var oInit = 0; oInit < this.optionalObjectives.length; oInit += 1){
bestOptionalObjectivesEvaluations.push(Infinity);
}
// And here...we...go!
// 1.) Load a model into the queue
var branch = new Branch(-Infinity, []);
var acceptableThreshold;
branches.push(branch);
// If all branches have been exhausted terminate the loop
while (branches.length > 0 && toleranceFlag === true && Date.now() < terminalTime) {
if(this.model.isMinimization){
acceptableThreshold = this.bestPossibleEval * (1 + tolerance);
} else {
acceptableThreshold = this.bestPossibleEval * (1 - tolerance);
}
// Abort while loop if termination tolerance is both specified and condition is met
if (tolerance > 0) {
if (bestEvaluation < acceptableThreshold) {
toleranceFlag = false;
}
}
// Get a model from the queue
branch = branches.pop();
if (branch.relaxedEvaluation > bestEvaluation) {
continue;
}
// Solving from initial relaxed solution
// with additional cut constraints
// Adding cut constraints
var cuts = branch.cuts;
this.applyCuts(cuts);
iterations++;
if (this.feasible === false) {
continue;
}
var evaluation = this.evaluation;
if (evaluation > bestEvaluation) {
// This branch does not contain the optimal solution
continue;
}
// To deal with the optional objectives
if (evaluation === bestEvaluation){
var isCurrentEvaluationWorse = true;
for (var o = 0; o < this.optionalObjectives.length; o += 1){
if (this.optionalObjectives[o].reducedCosts[0] > bestOptionalObjectivesEvaluations[o]){
break;
} else if (this.optionalObjectives[o].reducedCosts[0] < bestOptionalObjectivesEvaluations[o]) {
isCurrentEvaluationWorse = false;
break;
}
}
if (isCurrentEvaluationWorse){
continue;
}
}
// Is the model both integral and feasible?
if (this.isIntegral() === true) {
//
// Store the fact that we are integral
//
this.__isIntegral = true;
if (iterations === 1) {
this.branchAndCutIterations = iterations;
return;
}
// Store the solution as the bestSolution
bestBranch = branch;
bestEvaluation = evaluation;
for (var oCopy = 0; oCopy < this.optionalObjectives.length; oCopy += 1){
bestOptionalObjectivesEvaluations[oCopy] = this.optionalObjectives[oCopy].reducedCosts[0];
}
} else {
if (iterations === 1) {
// Saving the first iteration
// TODO: implement a better strategy for saving the tableau?
this.save();
}
// 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 = this.getMostFractionalVar();
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);
}
}
// Adding cut constraints for the optimal solution
if (bestBranch !== null) {
// The model is feasible
this.applyCuts(bestBranch.cuts);
}
this.branchAndCutIterations = iterations;
};