-
-
Save globalpolicy/56ee2deca654f6656b74fb14ea7d6e5d to your computer and use it in GitHub Desktop.
Standard-form Linear Programming solver using Simplex method
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Simplex solver</title> | |
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" | |
integrity="sha256-pasqAKBDmFT4eHoN2ndd6lN370kFiGUFyTiUHWhU7k8=" crossorigin="anonymous"></script> | |
<style> | |
input[type=number] { | |
width: 80px; | |
margin: 5px; | |
} | |
input[type=button] { | |
margin: 5px | |
} | |
</style> | |
</head> | |
<body> | |
<script type="text/javascript" src="simplex.js"></script> | |
<div> | |
<label for="num_vars">Number of decision variables:</label> | |
<input type="number" id="num_vars"> | |
</div> | |
<div> | |
<label for="num_constraints">Number of constraints:</label> | |
<input type="number" id="num_constraints"> | |
</div> | |
<div> | |
<input type="button" id="btn_next" value="Next" onclick="onNextClicked()"> | |
</div> | |
<div id="maximize_func_div"> | |
</div> | |
<div id="constraints_div"> | |
</div> | |
<div id="optimize_btn_div"> | |
</div> | |
<div id="result_div"> | |
</div> | |
<script> | |
function onNextClicked() { | |
var num_constraints = parseInt(Number(document.getElementById("num_constraints").value)); | |
var num_vars = parseInt(Number(document.getElementById("num_vars").value)); | |
if (num_constraints < 1 && num_vars < 2) { | |
return; | |
} | |
$("div#maximize_func_div").empty(); | |
$("div#maximize_func_div").append("Maximize: Z="); | |
for (var i = 0; i < num_vars; i++) { | |
textboxText = "x" + (i + 1) + ((i == (num_vars - 1)) ? "" : "+"); | |
textboxId = "x" + (i); | |
$("div#maximize_func_div").append("<input type='number' id='" + textboxId + "'>" + | |
textboxText); | |
} | |
$("div#constraints_div").empty(); | |
$("div#constraints_div").append("Constraints:<br>"); | |
for (var j = 0; j < num_constraints; j++) { | |
for (var i = 0; i < num_vars; i++) { | |
textboxText = "x" + (i + 1) + ((i == (num_vars - 1)) ? "" : "+"); | |
textboxId = "c" + (j) + (i); | |
$("div#constraints_div").append("<input type='number' id='" + textboxId + "'>" + | |
textboxText); | |
} | |
$("div#constraints_div").append("<=<input type='number' id='c" + j + (i) + "'>"); | |
$("div#constraints_div").append("<br>") | |
} | |
for (var i = 0; i < num_vars; i++) { | |
$("div#constraints_div").append("x" + (i + 1) + ((i == num_vars - 1) ? " " : ", ")); | |
} | |
$("div#constraints_div").append(">=0"); | |
$("div#optimize_btn_div").empty(); | |
$("div#optimize_btn_div").append( | |
"<input type='button' id='btn_optimize' value='Optimize' onclick='onOptimizeClicked()'>"); | |
} | |
function onOptimizeClicked() { | |
var num_constraints = parseInt(Number(document.getElementById("num_constraints").value)); | |
var num_vars = parseInt(Number(document.getElementById("num_vars").value)); | |
var matrix = []; | |
for (var i = 0; i < num_constraints; i++) { | |
var row = []; | |
row.push(0); | |
for (var j = 0; j < num_vars; j++) { | |
var c_ij = parseInt(Number(document.getElementById("c" + (i) + (j)).value)); | |
row.push(c_ij); | |
} | |
for (var k = 0; k < num_constraints; k++) { | |
if (i == k) { | |
row.push(1); | |
} else { | |
row.push(0); | |
} | |
} | |
var c_ij_plus1 = parseInt(Number(document.getElementById("c" + (i) + (j)).value)); | |
row.push(c_ij_plus1); | |
matrix.push(row); | |
} | |
var z_row = []; | |
z_row.push(1); | |
for (var i = 0; i < num_vars; i++) { | |
var x_i = parseInt(Number(document.getElementById("x" + i).value)); | |
z_row.push(-x_i); | |
} | |
for (var i = num_vars; i < matrix[0].length; i++) { | |
z_row.push(0); | |
} | |
matrix.push(z_row); | |
var result = solveSimplex(matrix); | |
$("div#result_div").empty(); | |
if (!result['error']) { | |
$("div#result_div").append("Optimum solution:<br>"); | |
var basicvars = result['vars']; | |
var optimizedVars = {}; | |
for (var i = 0; i < basicvars.length; i++) { | |
if (basicvars[i].includes("x")) { | |
optimizedVars[basicvars[i]] = matrix[i][matrix[0].length - 1]; | |
} | |
} | |
var sortedOptimizedVars = {}; | |
for (var i = 0; i < num_vars; i++) { | |
if (!optimizedVars.hasOwnProperty("x" + (i + 1))) { | |
sortedOptimizedVars["x" + (i + 1)] = 0; | |
} else { | |
sortedOptimizedVars["x" + (i + 1)] = optimizedVars["x" + (i + 1)]; | |
} | |
} | |
for (var key of Object.keys(sortedOptimizedVars)) { | |
$("div#result_div").append(key + " : " + sortedOptimizedVars[key] + "<br>") | |
} | |
$("div#result_div").append("Z : " + matrix[num_constraints + 1 - 1][matrix[0].length - 1]); | |
} else { | |
$("div#result_div").append(result['message']); | |
} | |
} | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function solveSimplex(matrix) { | |
//By s0ft | |
//29th May, 2019 - 12:40AM | |
//Part of M.Sc. Water Resource Engineering - System Mathematics/Operations Research assignment | |
//the simplex table format is based on Hamdy A. Taha's book on Operations Research - Tenth Edition | |
//with one difference in that the z-row is kept as the last row of the table | |
/* | |
HA Taha - Page 106 - Reddy Mikks problem: | |
Maximize: | |
z = 5x1 + 4x2 | |
subject to: | |
6x1 + 4x2 <= 24 | |
x1 + 2x2 <= 6 | |
-x1 + x2 <=1 | |
x2 <= 2 | |
Answer for verification: x1=3,x2=1.5,z=21 for maximum | |
Matrix formulation: | |
matrix=[[0,6,4,1,0,0,0,24], | |
[0,1,2,0,1,0,0,6], | |
[0,-1,1,0,0,1,0,1], | |
[0,0,1,0,0,0,1,2], | |
[1,-5,-4,0,0,0,0,0]]; | |
*/ | |
numColumns = matrix[0].length; | |
numRows = matrix.length; | |
basicVarsNum = numRows - 1; | |
//populate basic variable strings | |
basicVars = []; | |
for (var i = 0; i < numRows - 1; i++) { | |
basicVars.push('s' + (i + 1)); | |
} | |
//populate column header strings | |
columnHeaderVars = []; | |
columnHeaderVars.push('z'); | |
for (var i = 1; i <= numColumns - 2 - (numRows - 1); i++) { | |
columnHeaderVars.push('x' + i); | |
} | |
columnHeaderVars.push(...basicVars); | |
//get pivot column index | |
pivotColumnIndex = getPivotColumnIndex(matrix); | |
while (pivotColumnIndex != null) { | |
//populate ratios column | |
ratios = getRatios(matrix, numColumns, pivotColumnIndex) | |
//get pivot row index | |
pivotRowIndex = getPivotRowIndex(ratios); | |
if (pivotRowIndex == null) { | |
return { | |
error: true, | |
message: "Problem is unbounded." | |
}; | |
} | |
//replace the 'leaving' basic variable with 'entering' variable | |
basicVars[pivotRowIndex] = columnHeaderVars[pivotColumnIndex]; | |
//update pivot row | |
pivotElement = matrix[pivotRowIndex][pivotColumnIndex]; | |
for (var i = 0; i < numColumns; i++) { | |
matrix[pivotRowIndex][i] /= pivotElement; | |
} | |
//update non-pivot rows | |
for (var i = 0; i < numRows; i++) { | |
oldrow = matrix[i].slice(); //copy by value for intra-row operations in the for-loop below | |
for (var j = 0; j < numColumns; j++) { | |
if (i != pivotRowIndex) { | |
matrix[i][j] = oldrow[j] - oldrow[pivotColumnIndex] * matrix[pivotRowIndex][j]; | |
} | |
} | |
} | |
//get pivot column index | |
pivotColumnIndex = getPivotColumnIndex(matrix); | |
} | |
return { | |
error: false, | |
vars: basicVars | |
}; | |
} | |
function getPivotColumnIndex(matrix) { | |
//check z-row for most negative element (and thus find pivot column index) | |
numRows = matrix.length; | |
zrow = matrix[numRows - 1]; | |
mostNegativeElement = 0 | |
pivotColumnIndex = null | |
for (var i = 0; i < zrow.length; i++) { | |
if (zrow[i] < mostNegativeElement) { | |
mostNegativeElement = zrow[i]; | |
pivotColumnIndex = i; | |
} | |
} | |
return pivotColumnIndex; | |
} | |
function getRatios(matrix, numColumns, pivotColumnIndex) { | |
//calculate ratios | |
ratios = []; | |
for (var i = 0; i < matrix.length - 1; i++) { //matrix.length-1 because we don't want ratio for z-row(last row) | |
row = matrix[i]; | |
rowSolution = row[numColumns - 1]; | |
if (row[pivotColumnIndex] == 0) { | |
ratios.push(-1); //prevents division by zero and renders it ignorable | |
} else { | |
ratios.push(rowSolution / row[pivotColumnIndex]) | |
} | |
} | |
return ratios; | |
} | |
function getPivotRowIndex(ratios) { | |
//check ratios column for smallest positive element (and thus find pivot row index) | |
smallestPositiveElement = Infinity; | |
pivotRowIndex = null; | |
for (var i = 0; i < ratios.length; i++) { | |
if (ratios[i] > 0) { //check if only positive | |
if (ratios[i] < smallestPositiveElement) { | |
smallestPositiveElement = ratios[i]; | |
pivotRowIndex = i; | |
} | |
} | |
} | |
return pivotRowIndex; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment