Skip to content

Instantly share code, notes, and snippets.

@globalpolicy
Created May 30, 2019 04:53
Show Gist options
  • Save globalpolicy/56ee2deca654f6656b74fb14ea7d6e5d to your computer and use it in GitHub Desktop.
Save globalpolicy/56ee2deca654f6656b74fb14ea7d6e5d to your computer and use it in GitHub Desktop.
Standard-form Linear Programming solver using Simplex method
<!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>
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