Linear Programming Problem Solver

About This Page

This Linear Programming Problem solver was coded from scratch, without external libraries (see script at the bottom of the page). It demonstrates my ability to implement complex algorithms from what I learned on my maths course. This page is useful to me. I use it frequently. If you want any additional features or food items adding Email, Text or Call.

There is a bug currently with my code where it will exclude a food item impacting the nutricianal information below, so there is the option of using the libary solver. Feel free to compare and put mine to the test. :) you can read more about creating this here.

Optimization Goal

Boundaries

Results

Food Data

Nutrient Cost Calories Fats Monounsaturated Polyunsaturated Saturated Carbohydrates Fibre Sugar Protein Calcium Phosphorus Magnesium Sodium Potassium Iron Zinc Copper Selenium Iodine Thiamin_B1 Riboflavin_B2 Niacin_B3 Pantothenic_acid_B5 Vitamin_B6 Biotin_B7 Folate_B9 Vitamin_B12 Vitamin_A Vitamin_C Vitamin_D Vitamin_E Vitamin_K Manganese Molybdenum Chromium Choline Creatine Weight
20% beef mince 0.49 282 20 8.5 0.7 7.9 0 0 0 23 18 198 21 66 318 2.2 4.3 0.1 17 0 0.05 0.2 5.4 0.6 0.3 0 6 2.4 0 0 0.1 0.3 1.2 0.01 0 2 65 0.3 0.1
chicken breast 0.65 165 3.6 1.24 0.77 1.01 0 0 0 31 6 241 32 47 343 0.49 0.94 0.04 31.9 23 0.1 0.19 9.47 1.59 0.94 1.2 3 0.2 10 0 0.06 0.33 4.3 0.01 0 0.5 117 0.4 0.1
carrot 0.06 41 0.24 0 0 0.03 9.58 2.8 4.74 0.93 33 35 12 69 320 0.3 0.24 0.045 0.1 1 0.066 0.058 0.983 0.273 0.138 0.25 19 0 835 5.9 0 0.66 13.2 0.143 1 1 8.8 0 0.1
broccoli 0.2 31 0.3 0 0 0 6 2.4 1.5 2.5 47 66 21 30 288 0.7 0.4 0.05 2.5 3 0.07 0.12 0.64 0.57 0.18 1.5 57 0 567 81.2 0 0.78 92.5 0.21 10 11.8 19 0 0.1
potato 0.08 77 0.1 0 0 0.02 21 2 1 2 7 57 23 4 535 1 0.2 0.1 0 30 0.1 0.03 1.2 0.35 0.2 4.6 13 0 0 11 0 0.01 2 0 9 0 0 0 0.1
sweet potato 0.12 86 0.1 0 0 0 20.1 3 4.2 1.6 50.8 50.8 19.8 306 259 0.7 0.3 0.36 0.9 1 0.08 0.06 0.56 0.35 0.34 5.7 7.44 0 823 12.8 0 0.26 5.1 0.43 0 0 14.4 0 0.1
Chia Seeds 0.833 486 30.74 2.309 23.665 3.33 42.12 34.4 0 16.54 631 860 335 16 407 7.72 4.58 0.924 55.2 0 0.62 0.17 8.83 0 0 0 49 0 0 1.6 0 0.5 0 2.723 0 0 0 0 0.1
Almonds 1.45 579 49.93 31.551 12.329 3.802 21.55 12.5 4.35 21.15 269 481 270 1 733 3.71 3.12 1.031 4.1 0 0.205 1.138 3.618 0.471 0.137 13.5 44 0 0 0 0 25.63 0 2.179 5 8 52.1 0 0.1
Brazilnuts 1.5 659 67.1 23.879 24.399 16.134 11.74 7.5 2.33 14.32 160 725 376 3 659 2.43 4.06 1.743 1917 0 0.617 0.035 0.295 0.184 0.101 9.2 22 0 0 0.7 0 5.65 0 1.223 5 16 28.8 0 0.1
Dry-Roasted Cashews 1.4 574 46.35 27.317 7.836 9.157 32.69 3 5.01 15.31 45 490 260 16 565 6 5.6 2.22 11.7 0 0.2 0.2 1.4 1.217 0.256 0 69 0 0 0 0 0.92 34.7 0.826 5 0 61 0 0.1
Olive Oil 0.4 884 100 72.961 10.523 13.808 0 0 0 0 1 0 0 2 1 0.56 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 14.35 60.2 0 0 6.8 0.3 0 0.1
Farmed Atlantic Salmon (Raw) 1.786 208 13.42 3.77 3.886 3.05 0 0 0 20.42 9 240 27 59 363 0.34 0.36 0.045 24 10 0.207 0.155 8.672 1.547 0.636 5 26 3.23 58 3.9 11 3.55 0.5 0.011 0 0 78.5 0.5 0.1
Wild Atlantic Salmon (Raw) 1.786 142 6.34 2.103 2.539 0.981 0 0 0 19.84 12 200 29 44 490 0.8 0.64 0.25 36.5 20 0.226 0.38 7.86 1.664 0.818 5 25 3.18 12 0 11 0 0 0.016 0 0 0 0.5 0.1
Mozzarella 0.552 299 22.14 6.573 0.765 13.9 2.4 0 0 22.17 505 354 20 486 76 0.44 2.92 0.011 17 0 0.03 0.283 0.104 0.141 0.037 0 7 2.28 179 0 0.4 0.19 2.3 0.03 0 0 15.4 1.65 0.1
Cheese Cheddar 0.5 404 33.31 9.246 1.421 18.867 3.09 0 0.48 22.87 710 455 27 653 76 0.14 3.64 0.03 28.5 42.9 0.029 0.428 0.059 0 0.066 0 27 1.1 330 0 0.6 0.71 2.4 0 0 0 16.5 1.64 0.1
White Rice (cooked) 0.017 130 0.28 0.088 0.076 0.077 28.17 0.4 0.05 2.69 10 43 12 1 35 1.2 0.49 0.069 7.5 0 0.163 0.013 1.476 0.39 0.093 0 58 0 0 0 0 0.04 0 0.472 1 0 2.1 0 0.1
Rice White Long-Grain uncooked Enriched 0.052 365 0.66 0.206 0.177 0.18 79.95 1.3 0.12 7.13 28 115 25 5 115 4.31 1.09 0.22 15.1 0 0.576 0.049 4.192 1.014 0.164 0 231 0 0 0 0 0.11 0.1 1.088 0 0 5.8 0 0.1
Chickpeas Canned Drained 0.188 147 3.25 0.74 1.468 0.34 22.48 7.3 4.32 8.2 58 133 45 220 240 2.05 1.15 0.255 3.3 0 0.051 0.024 0.206 0 0.749 0 40 0 2 0.2 0 0.32 3.7 0 5 0 38.5 0 0.1
Bananas 0.09 89 0.33 0.032 0.073 0.112 22.84 2.6 12.23 1.09 5 22 27 1 358 0.26 0.15 0.078 1 2 0.031 0.073 0.665 0.334 0.367 0.15 20 0 3 8.7 0 0.1 0.5 0.27 15 0 9.8 0 0.1
Oranges 0.25 47 0.12 0.023 0.025 0.015 11.75 2.4 9.35 0.94 40 14 10 0 181 0.1 0.07 0.045 0.5 0 0.087 0.04 0.282 0.015 0.06 0.05 30 0 11 53.2 0 0.18 0 0.02 0 1 8.4 0 0.1
Tangerines 0.25 53 0.31 0.06 0.065 0.039 13.34 1.8 10.58 0.81 37 20 12 2 166 0.15 0.07 0.042 0.1 0 0.058 0.036 0.376 0.25 0.078 0.4 16 0 34 26.7 0 0.2 0 0.025 0 0 10.2 0 0.1
Avocados 1.16 160 14.66 9.799 1.816 2.126 8.53 6.7 0.66 2 12 52 29 7 485 0.55 0.64 0.19 0.4 0 0.067 0.13 1.738 0.216 0.257 0.96 81 0 7 10 0 2.07 21 0.039 0 0 14.2 0 0.1

Simplex

function LPPsolver(problem) { //step 1: create tableau, tableau = createTwoPhaseTableau(problem); console.log('Tableau:', tableau); console.log('Tableau (stringified):', JSON.stringify(tableau, null, 2)); //step 2: normalise and attempt to solve auxilary problem //step 3: solve new tableau solverResult = solveTwoPhaseSimplexTableau(tableau); console.log('solverResult:', solverResult); console.log('solverResult (stringified):', JSON.stringify(solverResult, null, 2)); //step 4: use result as initial to solve //step 5: convert to format for output return formatSolutionOutput(solverResult, Object.keys(problem.variables)); }; function createTwoPhaseTableau(problem) { const variables = Object.keys(problem.variables); const constraints = Object.keys(problem.constraints); const objective = problem.optimize; const opType = problem.opType; // Count the number of actual constraints (could be up to 2 per constraint if both min and max are specified) let constraintCount = 0; let artCount = 0; // counts min therefore number of artificial variables constraints.forEach(constraint => { if (problem.constraints[constraint].min !== undefined && problem.constraints[constraint].min > 0) { constraintCount++; artCount++; } if (problem.constraints[constraint].max !== undefined) constraintCount++; }); console.log('constraintCount:', constraintCount) const tableau = []; // Objective function row (Phase 2) const objRow = new Array(variables.length + constraintCount + artCount + 1).fill(0); variables.forEach((variable, i) => { objRow[i] = problem.variables[variable][objective] * (opType === 'max' ? -1 : 1); }); tableau.push(objRow); // Artificial objective function row (Phase 1) const artObjRow = new Array(variables.length + constraintCount + artCount + 1).fill(0); for (let i = variables.length + constraintCount; i < variables.length + constraintCount + artCount; i++) { artObjRow[i] = 1; } tableau.push(artObjRow); // Constraint rows let slackIndex = variables.length; let artIndex = variables.length + constraintCount; constraints.forEach(constraint => { const minValue = problem.constraints[constraint].min; const maxValue = problem.constraints[constraint].max; if (minValue !== undefined && minValue > 0) { //LOOK AT THE >0 REQUIREMENT const row = new Array(variables.length + constraintCount + artCount + 1).fill(0); variables.forEach((variable, j) => { row[j] = problem.variables[variable][constraint]; }); row[slackIndex] = -1; // For >= constraints, we subtract the slack variable row[artIndex] = 1; // For >= constraints, we add artificial variable row[row.length - 1] = minValue; // RHS tableau.push(row); tableau[1] = tableau[1].map((value, index) => value - row[index]); //normalise table console.log('row',row) slackIndex++; artIndex++; } if (maxValue !== undefined) { const row = new Array(variables.length + constraintCount + artCount + 1).fill(0); variables.forEach((variable, j) => { row[j] = problem.variables[variable][constraint]; }); row[slackIndex] = 1; // For <= constraints, we add the slack variable row[row.length - 1] = maxValue; // RHS tableau.push(row); slackIndex++; } }); /* Non-negativity constraints for variables for (let i = 0; i < variables.length; i++) { const row = new Array(variables.length + constraintCount + artCount + 1).fill(0); row[i] = 1; row[row.length - 1] = 0; // RHS tableau.push(row); }*/ console.log('look at this one:', JSON.stringify(tableau, null, 2)); return tableau; } function solveTwoPhaseSimplexTableau(tableau) { // Helper function to find the pivot column function findPivotColumn(row) { let mostNegativeIndex = -1; let mostNegativeValue = 0; for (let i = 0; i < row.length - 1; i++) { if (row[i] < mostNegativeValue) { mostNegativeValue = row[i]; mostNegativeIndex = i; } } return mostNegativeIndex; } // Helper function to find the pivot row function findPivotRow(tableau, pivotColumn) { let minRatio = Infinity; let pivotRow = -1; for (let i = 2; i < tableau.length; i++) { if (tableau[i][pivotColumn] > 0) { const ratio = tableau[i][tableau[i].length - 1] / tableau[i][pivotColumn]; if (ratio < minRatio) { minRatio = ratio; pivotRow = i; } } } return pivotRow; } // Helper function to perform a pivot operation function pivot(tableau, pivotRow, pivotColumn) { const pivotValue = tableau[pivotRow][pivotColumn]; for (let i = 0; i < tableau.length; i++) { if (i !== pivotRow) { const factor = tableau[i][pivotColumn] / pivotValue; for (let j = 0; j < tableau[i].length; j++) { tableau[i][j] -= factor * tableau[pivotRow][j]; } } } // Normalize pivot row for (let j = 0; j < tableau[pivotRow].length; j++) { tableau[pivotRow][j] /= pivotValue; } } // Helper function to check if Phase 1 is complete function isPhase1Complete(tableau) { return tableau[1].every(value => value >= 0); } // Helper function to remove artificial variables function removeArtificialVariables(tableau, artCount) { // Remove artificial objective row console.log('pre removeArtificialVariables art Count:', artCount) console.log('pre removeArtificialVariables:', JSON.stringify(tableau, null, 2)); tableau.splice(1, 1); // Remove artificial variable columns (assuming they're the last columns before RHS) for (let i = 0; i < tableau.length; i++) { tableau[i].splice(tableau[i].length - artCount-1, artCount); } console.log('post removeArtificialVariables:', JSON.stringify(tableau, null, 2)); } // Helper function to check if optimal solution is reached function isOptimal(tableau) { return tableau[0].every(value => value >= 0); } function countNegativeOnes(tableau) { let count = 0; // Start from row 2 to skip rows 0 and 1 for (let i = 2; i < tableau.length; i++) { for (let j = 0; j < tableau[i].length - 1; j++) { if (tableau[i][j] === -1) { count++; } } } return count; } const artCount = countNegativeOnes(tableau); console.log("Number of artificial variables:", artCount); // Main solving loop let phase = 1; while (true) { console.log('Tableau (stringified):', JSON.stringify(tableau, null, 2)); let pivotColumn = findPivotColumn(tableau[phase]); console.log('pivotColumn:', pivotColumn) if (pivotColumn === -1) { if (phase === 1) { if (isPhase1Complete(tableau)) { removeArtificialVariables(tableau, artCount); phase = 0; //phase 2 continue; } else { return "No feasible solution exists."; } } else { break; // Optimal solution reached } } const pivotRow = findPivotRow(tableau, pivotColumn); console.log('pivotRow:', pivotRow) if (pivotRow === -1) { return "The problem is unbounded."; } pivot(tableau, pivotRow, pivotColumn); } // Extract solution const solution = new Array(tableau[0].length - 1).fill(0); for (let i = 2; i < tableau.length; i++) { const basicVar = tableau[i].findIndex(value => value === 1); if (basicVar !== -1 && basicVar < solution.length) { solution[basicVar] = tableau[i][tableau[i].length - 1]; } } return { tableau: tableau, solution: solution.slice(0, 6), // Assuming the first 6 variables are the original variables objectiveValue: -tableau[0][tableau[0].length - 1] // Negative because we minimized }; } function formatSolutionOutput(solverResult, variableNames) { const output = { feasible: true, result: 0, bounded: true }; if (typeof solverResult === 'string') { // Handle infeasible or unbounded cases output.feasible = false; output.bounded = !solverResult.includes('unbounded'); return output; } output.result = Number(solverResult.objectiveValue.toFixed(8)); // Add non-zero variables to the output solverResult.solution.forEach((value, index) => { if (value > 1e-8) { // Consider values greater than 1e-8 as non-zero output[variableNames[index]] = Number(value.toFixed(8)); } }); return output; }
Contact Image

Contact

Email: lucasmarcbeastall@gmail.com

Phone: 07725 715638

LinkedIn: www.linkedin.com/in/lucas-beastall-4a3025318