UNPKG

javascript-lp-solver

Version:

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

271 lines (227 loc) 9.5 kB
/*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; };