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;
}