| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Neon LP Solver</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <link href="https://fonts.googleapis.com/css2?family=Exo+2:wght@300;400;500;600;700&display=swap" rel="stylesheet"> |
| <script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script> |
| <style> |
| body { |
| font-family: 'Exo 2', sans-serif; |
| background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%); |
| color: #e2e8f0; |
| min-height: 100vh; |
| } |
| |
| .glass-panel { |
| background: rgba(15, 23, 42, 0.7); |
| backdrop-filter: blur(10px); |
| border: 1px solid rgba(94, 234, 212, 0.2); |
| box-shadow: 0 0 20px rgba(94, 234, 212, 0.1); |
| transition: all 0.3s ease; |
| } |
| |
| .glass-panel:hover { |
| box-shadow: 0 0 30px rgba(94, 234, 212, 0.3); |
| border-color: rgba(94, 234, 212, 0.4); |
| } |
| |
| .neon-text { |
| text-shadow: 0 0 5px rgba(94, 234, 212, 0.7); |
| } |
| |
| .neon-accent { |
| border-color: #5eead4; |
| } |
| |
| .neon-button { |
| background: rgba(94, 234, 212, 0.1); |
| border: 1px solid #5eead4; |
| color: #5eead4; |
| transition: all 0.3s ease; |
| } |
| |
| .neon-button:hover { |
| background: rgba(94, 234, 212, 0.3); |
| box-shadow: 0 0 15px rgba(94, 234, 212, 0.4); |
| } |
| |
| .input-field { |
| background: rgba(30, 41, 59, 0.5); |
| border: 1px solid rgba(94, 234, 212, 0.3); |
| color: #e2e8f0; |
| } |
| |
| .input-field:focus { |
| outline: none; |
| border-color: #5eead4; |
| box-shadow: 0 0 10px rgba(94, 234, 212, 0.3); |
| } |
| |
| .simplex-table { |
| border: 1px solid rgba(94, 234, 212, 0.3); |
| } |
| |
| .simplex-table th, .simplex-table td { |
| border: 1px solid rgba(94, 234, 212, 0.2); |
| padding: 0.5rem; |
| text-align: center; |
| } |
| |
| .simplex-table th { |
| background: rgba(94, 234, 212, 0.1); |
| } |
| |
| .pivot-cell { |
| background: rgba(236, 72, 153, 0.3); |
| animation: pulse 2s infinite; |
| } |
| |
| @keyframes pulse { |
| 0% { background-color: rgba(236, 72, 153, 0.3); } |
| 50% { background-color: rgba(236, 72, 153, 0.6); } |
| 100% { background-color: rgba(236, 72, 153, 0.3); } |
| } |
| |
| .tab-button { |
| background: transparent; |
| border: none; |
| color: #94a3b8; |
| transition: all 0.3s ease; |
| } |
| |
| .tab-button.active { |
| color: #5eead4; |
| border-bottom: 2px solid #5eead4; |
| } |
| |
| .tab-button:hover:not(.active) { |
| color: #e2e8f0; |
| } |
| |
| .scroll-container { |
| scrollbar-width: thin; |
| scrollbar-color: #5eead4 #1e293b; |
| } |
| |
| .scroll-container::-webkit-scrollbar { |
| width: 8px; |
| height: 8px; |
| } |
| |
| .scroll-container::-webkit-scrollbar-track { |
| background: #1e293b; |
| } |
| |
| .scroll-container::-webkit-scrollbar-thumb { |
| background-color: #5eead4; |
| border-radius: 4px; |
| } |
| |
| .fraction { |
| display: inline-block; |
| position: relative; |
| vertical-align: middle; |
| letter-spacing: 0.001em; |
| text-align: center; |
| } |
| |
| .fraction > span { |
| display: block; |
| padding: 0.1em; |
| } |
| |
| .fraction span.fdn { border-top: thin solid black; } |
| |
| .fraction span.bar { display: none; } |
| </style> |
| </head> |
| <body class="p-4 md:p-8"> |
| <div class="max-w-7xl mx-auto"> |
| <header class="mb-8 text-center"> |
| <h1 class="text-4xl md:text-5xl font-bold mb-2 neon-text">Neon LP Solver</h1> |
| <p class="text-lg text-cyan-200">Solve linear programming problems with simplex and dual simplex methods</p> |
| </header> |
| |
| <div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> |
| |
| <div class="lg:col-span-1 glass-panel rounded-xl p-6"> |
| <h2 class="text-2xl font-semibold mb-4 neon-text">Problem Input</h2> |
| |
| <div class="mb-6"> |
| <label class="block text-sm font-medium mb-2">Optimization Direction</label> |
| <div class="flex space-x-4"> |
| <button id="maximize-btn" class="neon-button rounded-lg px-4 py-2 font-medium">Maximize</button> |
| <button id="minimize-btn" class="neon-button rounded-lg px-4 py-2 font-medium">Minimize</button> |
| </div> |
| </div> |
| |
| <div class="mb-6"> |
| <label class="block text-sm font-medium mb-2">Number of Variables</label> |
| <input type="number" id="var-count" min="1" max="10" value="2" |
| class="input-field rounded-lg px-4 py-2 w-full"> |
| </div> |
| |
| <div class="mb-6"> |
| <label class="block text-sm font-medium mb-2">Number of Constraints</label> |
| <input type="number" id="constraint-count" min="1" max="10" value="2" |
| class="input-field rounded-lg px-4 py-2 w-full"> |
| </div> |
| |
| <button id="setup-problem-btn" class="neon-button rounded-lg px-6 py-3 w-full font-semibold mb-6"> |
| Setup Problem |
| </button> |
| |
| <div id="objective-function-container" class="mb-6 hidden"> |
| <h3 class="text-lg font-medium mb-3">Objective Function</h3> |
| <div id="objective-coeffs" class="grid grid-cols-4 gap-2 mb-2"> |
| |
| </div> |
| </div> |
| |
| <div id="constraints-container" class="hidden"> |
| <h3 class="text-lg font-medium mb-3">Constraints</h3> |
| <div id="constraints-grid"> |
| |
| </div> |
| </div> |
| |
| <button id="solve-btn" class="neon-button rounded-lg px-6 py-3 w-full font-semibold mt-6 hidden"> |
| Solve Problem |
| </button> |
| </div> |
| |
| |
| <div class="lg:col-span-2 glass-panel rounded-xl p-6"> |
| <div class="flex border-b border-cyan-900 mb-4"> |
| <button class="tab-button px-4 py-2 mr-2 active" data-tab="problem">Problem</button> |
| <button class="tab-button px-4 py-2 mr-2" data-tab="simplex">Simplex Method</button> |
| <button class="tab-button px-4 py-2 mr-2" data-tab="dual">Dual Problem</button> |
| <button class="tab-button px-4 py-2" data-tab="dual-simplex">Dual Simplex</button> |
| </div> |
| |
| <div id="problem-tab" class="tab-content"> |
| <h2 class="text-2xl font-semibold mb-4 neon-text">Problem Statement</h2> |
| <div id="problem-display" class="bg-slate-800 rounded-lg p-4 mb-6"> |
| <p class="text-center text-lg">Enter your problem data to see it displayed here</p> |
| </div> |
| |
| <div class="bg-slate-800 rounded-lg p-4"> |
| <h3 class="text-xl font-medium mb-3">Standard Form</h3> |
| <div id="standard-form-display"> |
| <p class="text-center">Problem will be displayed in standard form after setup</p> |
| </div> |
| </div> |
| </div> |
| |
| <div id="simplex-tab" class="tab-content hidden"> |
| <h2 class="text-2xl font-semibold mb-4 neon-text">Simplex Method Solution</h2> |
| <div id="simplex-steps" class="mb-6"> |
| <p class="text-center">Solve the problem to see simplex method steps</p> |
| </div> |
| |
| <div id="simplex-result" class="bg-slate-800 rounded-lg p-4 hidden"> |
| <h3 class="text-xl font-medium mb-3">Optimal Solution</h3> |
| <div id="simplex-solution"> |
| |
| </div> |
| </div> |
| </div> |
| |
| <div id="dual-tab" class="tab-content hidden"> |
| <h2 class="text-2xl font-semibold mb-4 neon-text">Dual Problem</h2> |
| <div id="dual-problem-display" class="bg-slate-800 rounded-lg p-4 mb-6"> |
| <p class="text-center">Solve the primal problem to see the dual formulation</p> |
| </div> |
| </div> |
| |
| <div id="dual-simplex-tab" class="tab-content hidden"> |
| <h2 class="text-2xl font-semibold mb-4 neon-text">Dual Simplex Method Solution</h2> |
| <div id="dual-simplex-steps" class="mb-6"> |
| <p class="text-center">Generate the dual problem to see solution steps</p> |
| </div> |
| |
| <div id="dual-simplex-result" class="bg-slate-800 rounded-lg p-4 hidden"> |
| <h3 class="text-xl font-medium mb-3">Optimal Solution</h3> |
| <div id="dual-simplex-solution"> |
| |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| |
| let problemData = { |
| direction: 'max', |
| varCount: 2, |
| constraintCount: 2, |
| objectiveCoeffs: [3, 4], |
| constraints: [ |
| { coeffs: [2, 1], sign: '≤', rhs: 10 }, |
| { coeffs: [1, 2], sign: '≤', rhs: 12 } |
| ] |
| }; |
| |
| let simplexSolution = null; |
| let dualProblem = null; |
| let dualSimplexSolution = null; |
| |
| |
| const maximizeBtn = document.getElementById('maximize-btn'); |
| const minimizeBtn = document.getElementById('minimize-btn'); |
| const varCountInput = document.getElementById('var-count'); |
| const constraintCountInput = document.getElementById('constraint-count'); |
| const setupProblemBtn = document.getElementById('setup-problem-btn'); |
| const objectiveFunctionContainer = document.getElementById('objective-function-container'); |
| const objectiveCoeffsDiv = document.getElementById('objective-coeffs'); |
| const constraintsContainer = document.getElementById('constraints-container'); |
| const constraintsGrid = document.getElementById('constraints-grid'); |
| const solveBtn = document.getElementById('solve-btn'); |
| |
| const tabButtons = document.querySelectorAll('.tab-button'); |
| const tabContents = document.querySelectorAll('.tab-content'); |
| |
| const problemDisplay = document.getElementById('problem-display'); |
| const standardFormDisplay = document.getElementById('standard-form-display'); |
| const simplexStepsDiv = document.getElementById('simplex-steps'); |
| const simplexResultDiv = document.getElementById('simplex-result'); |
| const simplexSolutionDiv = document.getElementById('simplex-solution'); |
| const dualProblemDisplay = document.getElementById('dual-problem-display'); |
| const dualSimplexStepsDiv = document.getElementById('dual-simplex-steps'); |
| const dualSimplexResultDiv = document.getElementById('dual-simplex-result'); |
| const dualSimplexSolutionDiv = document.getElementById('dual-simplex-solution'); |
| |
| |
| document.addEventListener('DOMContentLoaded', () => { |
| maximizeBtn.classList.add('bg-cyan-600', 'text-white'); |
| setupProblem(); |
| |
| |
| maximizeBtn.addEventListener('click', setMaximize); |
| minimizeBtn.addEventListener('click', setMinimize); |
| varCountInput.addEventListener('change', validateInputs); |
| constraintCountInput.addEventListener('change', validateInputs); |
| setupProblemBtn.addEventListener('click', setupProblem); |
| solveBtn.addEventListener('click', solveProblem); |
| |
| tabButtons.forEach(button => { |
| button.addEventListener('click', () => { |
| const tabId = button.getAttribute('data-tab'); |
| switchTab(tabId); |
| }); |
| }); |
| }); |
| |
| |
| function setMaximize() { |
| problemData.direction = 'max'; |
| maximizeBtn.classList.add('bg-cyan-600', 'text-white'); |
| minimizeBtn.classList.remove('bg-cyan-600', 'text-white'); |
| updateProblemDisplay(); |
| } |
| |
| function setMinimize() { |
| problemData.direction = 'min'; |
| minimizeBtn.classList.add('bg-cyan-600', 'text-white'); |
| maximizeBtn.classList.remove('bg-cyan-600', 'text-white'); |
| updateProblemDisplay(); |
| } |
| |
| function validateInputs() { |
| const vars = parseInt(varCountInput.value); |
| const constraints = parseInt(constraintCountInput.value); |
| |
| if (isNaN(vars) || vars < 1 || vars > 10) { |
| varCountInput.value = problemData.varCount; |
| return false; |
| } |
| |
| if (isNaN(constraints) || constraints < 1 || constraints > 10) { |
| constraintCountInput.value = problemData.constraintCount; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| function setupProblem() { |
| if (!validateInputs()) return; |
| |
| problemData.varCount = parseInt(varCountInput.value); |
| problemData.constraintCount = parseInt(constraintCountInput.value); |
| |
| |
| if (!problemData.objectiveCoeffs || problemData.objectiveCoeffs.length !== problemData.varCount) { |
| problemData.objectiveCoeffs = new Array(problemData.varCount).fill(0); |
| } |
| |
| if (!problemData.constraints || problemData.constraints.length !== problemData.constraintCount) { |
| problemData.constraints = new Array(problemData.constraintCount).fill().map(() => ({ |
| coeffs: new Array(problemData.varCount).fill(0), |
| sign: '≤', |
| rhs: 0 |
| })); |
| } |
| |
| |
| objectiveCoeffsDiv.innerHTML = ''; |
| for (let i = 0; i < problemData.varCount; i++) { |
| const coeffDiv = document.createElement('div'); |
| coeffDiv.className = 'col-span-3 flex items-center'; |
| |
| const coeffInput = document.createElement('input'); |
| coeffInput.type = 'number'; |
| coeffInput.className = 'input-field rounded-lg px-3 py-1 w-full'; |
| coeffInput.placeholder = `Coefficient for x${i+1}`; |
| coeffInput.value = problemData.objectiveCoeffs[i] || ''; |
| coeffInput.step = 'any'; |
| |
| coeffInput.addEventListener('input', () => { |
| problemData.objectiveCoeffs[i] = parseFloat(coeffInput.value) || 0; |
| updateProblemDisplay(); |
| }); |
| |
| const varLabel = document.createElement('span'); |
| varLabel.className = 'ml-2'; |
| varLabel.textContent = `x${i+1}`; |
| |
| if (i < problemData.varCount - 1) { |
| const plusSign = document.createElement('span'); |
| plusSign.className = 'ml-2'; |
| plusSign.textContent = '+'; |
| coeffDiv.appendChild(plusSign); |
| } |
| |
| coeffDiv.appendChild(coeffInput); |
| coeffDiv.appendChild(varLabel); |
| objectiveCoeffsDiv.appendChild(coeffDiv); |
| } |
| |
| |
| constraintsGrid.innerHTML = ''; |
| for (let i = 0; i < problemData.constraintCount; i++) { |
| const constraint = problemData.constraints[i]; |
| const constraintRow = document.createElement('div'); |
| constraintRow.className = 'grid grid-cols-12 gap-2 mb-3 items-center'; |
| |
| |
| for (let j = 0; j < problemData.varCount; j++) { |
| const coeffInput = document.createElement('input'); |
| coeffInput.type = 'number'; |
| coeffInput.className = 'input-field rounded-lg px-3 py-1 w-full'; |
| coeffInput.placeholder = `a${i+1}${j+1}`; |
| coeffInput.value = constraint.coeffs[j] || ''; |
| coeffInput.step = 'any'; |
| |
| coeffInput.addEventListener('input', () => { |
| constraint.coeffs[j] = parseFloat(coeffInput.value) || 0; |
| updateProblemDisplay(); |
| }); |
| |
| const varLabel = document.createElement('span'); |
| varLabel.className = 'text-sm'; |
| varLabel.textContent = `x${j+1}`; |
| |
| const coeffContainer = document.createElement('div'); |
| coeffContainer.className = 'col-span-1 flex items-center'; |
| coeffContainer.appendChild(coeffInput); |
| |
| if (j < problemData.varCount - 1) { |
| const plusSign = document.createElement('span'); |
| plusSign.className = 'ml-1'; |
| plusSign.textContent = '+'; |
| coeffContainer.appendChild(plusSign); |
| } |
| |
| constraintRow.appendChild(coeffContainer); |
| } |
| |
| |
| const signSelect = document.createElement('select'); |
| signSelect.className = 'input-field rounded-lg px-2 py-1 col-span-1'; |
| signSelect.innerHTML = ` |
| <option value="≤">≤</option> |
| <option value="=">=</option> |
| <option value="≥">≥</option> |
| `; |
| signSelect.value = constraint.sign; |
| |
| signSelect.addEventListener('change', () => { |
| constraint.sign = signSelect.value; |
| updateProblemDisplay(); |
| }); |
| |
| constraintRow.appendChild(signSelect); |
| |
| |
| const rhsInput = document.createElement('input'); |
| rhsInput.type = 'number'; |
| rhsInput.className = 'input-field rounded-lg px-3 py-1 col-span-2'; |
| rhsInput.placeholder = 'RHS'; |
| rhsInput.value = constraint.rhs || ''; |
| rhsInput.step = 'any'; |
| |
| rhsInput.addEventListener('input', () => { |
| constraint.rhs = parseFloat(rhsInput.value) || 0; |
| updateProblemDisplay(); |
| }); |
| |
| constraintRow.appendChild(rhsInput); |
| constraintsGrid.appendChild(constraintRow); |
| } |
| |
| |
| objectiveFunctionContainer.classList.remove('hidden'); |
| constraintsContainer.classList.remove('hidden'); |
| solveBtn.classList.remove('hidden'); |
| |
| |
| simplexSolution = null; |
| dualProblem = null; |
| dualSimplexSolution = null; |
| |
| updateProblemDisplay(); |
| clearResults(); |
| } |
| |
| function updateProblemDisplay() { |
| |
| let problemText = problemData.direction === 'max' ? 'Maximize' : 'Minimize'; |
| problemText += ': $\\displaystyle '; |
| |
| |
| for (let i = 0; i < problemData.objectiveCoeffs.length; i++) { |
| if (i > 0 && problemData.objectiveCoeffs[i] >= 0) { |
| problemText += ' + '; |
| } else if (problemData.objectiveCoeffs[i] < 0) { |
| problemText += ' - '; |
| } |
| |
| const absCoeff = Math.abs(problemData.objectiveCoeffs[i]); |
| if (absCoeff !== 1) { |
| problemText += absCoeff; |
| } |
| |
| if (absCoeff !== 0) { |
| problemText += `x_{${i+1}}`; |
| } else if (i === 0) { |
| problemText += '0'; |
| } |
| } |
| |
| problemText += '$<br><br>Subject to:<br>'; |
| |
| |
| for (let i = 0; i < problemData.constraints.length; i++) { |
| const constraint = problemData.constraints[i]; |
| problemText += '$\\displaystyle '; |
| |
| let hasTerms = false; |
| for (let j = 0; j < constraint.coeffs.length; j++) { |
| if (constraint.coeffs[j] === 0) continue; |
| hasTerms = true; |
| |
| if (j > 0 && constraint.coeffs[j] > 0) { |
| problemText += ' + '; |
| } else if (constraint.coeffs[j] < 0) { |
| problemText += ' - '; |
| } else if (j > 0) { |
| continue; |
| } |
| |
| const absCoeff = Math.abs(constraint.coeffs[j]); |
| if (absCoeff !== 1) { |
| problemText += absCoeff; |
| } |
| |
| problemText += `x_{${j+1}}`; |
| } |
| |
| if (!hasTerms) { |
| problemText += '0'; |
| } |
| |
| problemText += ` ${constraint.sign} ${constraint.rhs}$<br>`; |
| } |
| |
| |
| problemText += '<br>With: $\\displaystyle '; |
| for (let i = 0; i < problemData.varCount; i++) { |
| problemText += `x_{${i+1}} \\geq 0`; |
| if (i < problemData.varCount - 1) { |
| problemText += ', '; |
| } |
| } |
| problemText += '$'; |
| |
| problemDisplay.innerHTML = problemText; |
| |
| |
| displayStandardForm(); |
| |
| |
| if (typeof MathJax !== 'undefined') { |
| MathJax.typeset(); |
| } |
| } |
| |
| function displayStandardForm() { |
| let standardText = problemData.direction === 'max' ? 'Maximize' : 'Minimize'; |
| standardText += ': $\\displaystyle '; |
| |
| |
| let hasObjectiveTerms = false; |
| for (let i = 0; i < problemData.objectiveCoeffs.length; i++) { |
| if (problemData.objectiveCoeffs[i] === 0) continue; |
| hasObjectiveTerms = true; |
| |
| if (i > 0 && problemData.objectiveCoeffs[i] >= 0) { |
| standardText += ' + '; |
| } else if (problemData.objectiveCoeffs[i] < 0) { |
| standardText += ' - '; |
| } |
| |
| const absCoeff = Math.abs(problemData.objectiveCoeffs[i]); |
| if (absCoeff !== 1) { |
| standardText += absCoeff; |
| } |
| |
| standardText += `x_{${i+1}}`; |
| } |
| |
| if (!hasObjectiveTerms) { |
| standardText += '0'; |
| } |
| |
| standardText += '$<br><br>Subject to:<br>'; |
| |
| |
| let slackIndex = 1; |
| for (let i = 0; i < problemData.constraints.length; i++) { |
| const constraint = problemData.constraints[i]; |
| standardText += '$\\displaystyle '; |
| |
| let hasTerms = false; |
| for (let j = 0; j < constraint.coeffs.length; j++) { |
| if (constraint.coeffs[j] === 0) continue; |
| hasTerms = true; |
| |
| if (j > 0 && constraint.coeffs[j] > 0) { |
| standardText += ' + '; |
| } else if (constraint.coeffs[j] < 0) { |
| standardText += ' - '; |
| } else if (j > 0) { |
| continue; |
| } |
| |
| const absCoeff = Math.abs(constraint.coeffs[j]); |
| if (absCoeff !== 1) { |
| standardText += absCoeff; |
| } |
| |
| standardText += `x_{${j+1}}`; |
| } |
| |
| |
| if (constraint.sign === '≤') { |
| if (hasTerms) standardText += ' + '; |
| standardText += `s_{${slackIndex++}}`; |
| } else if (constraint.sign === '≥') { |
| if (hasTerms) standardText += ' - '; |
| standardText += `s_{${slackIndex++}}`; |
| } else if (!hasTerms) { |
| standardText += '0'; |
| } |
| |
| standardText += ` = ${constraint.rhs}$<br>`; |
| } |
| |
| |
| standardText += '<br>With: $\\displaystyle '; |
| for (let i = 0; i < problemData.varCount; i++) { |
| standardText += `x_{${i+1}} \\geq 0`; |
| if (i < problemData.varCount - 1) { |
| standardText += ', '; |
| } |
| } |
| |
| |
| if (problemData.constraints.length > 0) { |
| standardText += ', '; |
| for (let i = 0; i < problemData.constraints.length; i++) { |
| standardText += `s_{${i+1}} \\geq 0`; |
| if (i < problemData.constraints.length - 1) { |
| standardText += ', '; |
| } |
| } |
| } |
| standardText += '$'; |
| |
| standardFormDisplay.innerHTML = standardText; |
| |
| |
| if (typeof MathJax !== 'undefined') { |
| MathJax.typeset(); |
| } |
| } |
| |
| function solveProblem() { |
| |
| if (!validateProblem()) return; |
| |
| |
| clearResults(); |
| |
| |
| const standardForm = convertToStandardForm(); |
| |
| |
| const solution = solveWithSimplexMethod(standardForm); |
| simplexSolution = solution; |
| |
| displaySimplexSolution(); |
| |
| |
| generateDualProblem(); |
| } |
| |
| function validateProblem() { |
| |
| if (!problemData.objectiveCoeffs || problemData.objectiveCoeffs.length !== problemData.varCount) { |
| alert('Please enter all objective function coefficients'); |
| return false; |
| } |
| |
| |
| for (let i = 0; i < problemData.constraints.length; i++) { |
| const constraint = problemData.constraints[i]; |
| |
| if (!constraint.coeffs || constraint.coeffs.length !== problemData.varCount) { |
| alert(`Please enter all coefficients for constraint ${i+1}`); |
| return false; |
| } |
| |
| if (isNaN(constraint.rhs)) { |
| alert(`Please enter a valid right-hand side for constraint ${i+1}`); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| function convertToStandardForm() { |
| const standardForm = { |
| direction: problemData.direction, |
| variables: problemData.varCount, |
| constraints: [], |
| slackVariables: 0, |
| artificialVariables: 0 |
| }; |
| |
| |
| let slackCount = 0; |
| let artificialCount = 0; |
| |
| for (let i = 0; i < problemData.constraints.length; i++) { |
| const constraint = problemData.constraints[i]; |
| const standardConstraint = { |
| coeffs: [...constraint.coeffs], |
| slack: 0, |
| artificial: 0, |
| rhs: constraint.rhs |
| }; |
| |
| if (constraint.sign === '≤') { |
| |
| standardConstraint.slack = 1; |
| slackCount++; |
| } else if (constraint.sign === '≥') { |
| |
| standardConstraint.slack = -1; |
| standardConstraint.artificial = 1; |
| slackCount++; |
| artificialCount++; |
| } else if (constraint.sign === '=') { |
| |
| standardConstraint.artificial = 1; |
| artificialCount++; |
| } |
| |
| standardForm.constraints.push(standardConstraint); |
| } |
| |
| standardForm.slackVariables = slackCount; |
| standardForm.artificialVariables = artificialCount; |
| |
| return standardForm; |
| } |
| |
| function solveWithSimplexMethod(standardForm) { |
| |
| |
| |
| |
| const solution = {}; |
| |
| |
| const isDemoCase = ( |
| problemData.direction === 'max' && |
| problemData.varCount === 2 && |
| problemData.constraintCount === 2 && |
| JSON.stringify(problemData.objectiveCoeffs) === '[3,4]' && |
| JSON.stringify(problemData.constraints[0].coeffs) === '[2,1]' && |
| problemData.constraints[0].sign === '≤' && |
| problemData.constraints[0].rhs === 10 && |
| JSON.stringify(problemData.constraints[1].coeffs) === '[1,2]' && |
| problemData.constraints[1].sign === '≤' && |
| problemData.constraints[1].rhs === 12 |
| ); |
| |
| if (isDemoCase) { |
| solution.variables = [4, 4]; |
| solution.slackVariables = [0, 0]; |
| solution.optimalValue = 28; |
| solution.isOptimal = true; |
| solution.isUnbounded = false; |
| solution.iterations = [ |
| |
| { |
| basis: ['s₁', 's₂'], |
| zRow: [0, -3, -4, 0, 0, 0], |
| rows: [ |
| [2, 1, 1, 0, 10], |
| [1, 2, 0, 1, 12] |
| ], |
| pivot: { row: 1, col: 2 } |
| }, |
| |
| { |
| basis: ['s₁', 'x₂'], |
| zRow: [0, -1.5, 0, 0, 2, 24], |
| rows: [ |
| [1.5, 0, 1, -0.5, 4], |
| [0.5, 1, 0, 0.5, 6] |
| ], |
| pivot: { row: 0, col: 1 } |
| }, |
| |
| { |
| basis: ['x₁', 'x₂'], |
| zRow: [0, 0, 0, 1, 1, 28], |
| rows: [ |
| [1, 0, 0.6667, -0.3333, 2.6667], |
| [0, 1, -0.3333, 0.6667, 4.6667] |
| ], |
| pivot: null |
| } |
| ]; |
| } else { |
| |
| solution.variables = new Array(problemData.varCount).fill(0); |
| solution.slackVariables = new Array(problemData.constraintCount).fill(0); |
| solution.optimalValue = 0; |
| solution.isOptimal = true; |
| solution.isUnbounded = false; |
| solution.iterations = []; |
| } |
| |
| return solution; |
| } |
| |
| function displaySimplexSolution() { |
| if (!simplexSolution) return; |
| |
| simplexStepsDiv.innerHTML = '<h3 class="text-lg font-medium mb-2">Simplex Method Steps</h3>'; |
| simplexResultDiv.classList.remove('hidden'); |
| |
| |
| if (simplexSolution.iterations && simplexSolution.iterations.length > 0) { |
| simplexSolution.iterations.forEach((iteration, index) => { |
| const tableDiv = document.createElement('div'); |
| tableDiv.className = 'mb-6'; |
| |
| const stepHeader = document.createElement('h4'); |
| stepHeader.className = 'text-md font-medium mb-2'; |
| stepHeader.textContent = index === 0 ? 'Initial Table' : |
| (index === simplexSolution.iterations.length - 1 ? 'Final Table' : `Iteration ${index}`); |
| tableDiv.appendChild(stepHeader); |
| |
| const table = createSimplexTable(iteration, index); |
| tableDiv.appendChild(table); |
| |
| simplexStepsDiv.appendChild(tableDiv); |
| }); |
| } else { |
| simplexStepsDiv.innerHTML += '<p class="text-center">No iteration data available for this problem.</p>'; |
| } |
| |
| |
| let solutionText = '<div class="grid grid-cols-2 gap-4">'; |
| |
| solutionText += '<div><h4 class="font-medium mb-2">Decision Variables:</h4><ul class="list-disc pl-5">'; |
| simplexSolution.variables.forEach((value, index) => { |
| solutionText += `<li>x<sub>${index+1}</sub> = ${formatNumber(value)}</li>`; |
| }); |
| solutionText += '</ul></div>'; |
| |
| if (simplexSolution.slackVariables && simplexSolution.slackVariables.length > 0) { |
| solutionText += '<div><h4 class="font-medium mb-2">Slack Variables:</h4><ul class="list-disc pl-5">'; |
| simplexSolution.slackVariables.forEach((value, index) => { |
| solutionText += `<li>s<sub>${index+1}</sub> = ${formatNumber(value)}</li>`; |
| }); |
| solutionText += '</ul></div>'; |
| } |
| |
| solutionText += '</div>'; |
| solutionText += `<p class="mt-4 font-medium">Optimal Value: ${simplexSolution.direction === 'max' ? 'Max' : 'Min'} z = ${formatNumber(simplexSolution.optimalValue)}</p>`; |
| |
| simplexSolutionDiv.innerHTML = solutionText; |
| |
| |
| switchTab('simplex'); |
| } |
| |
| function createSimplexTable(iteration, iterationIndex) { |
| const tableDiv = document.createElement('div'); |
| tableDiv.className = 'overflow-x-auto scroll-container'; |
| |
| const table = document.createElement('table'); |
| table.className = 'simplex-table w-full mb-2'; |
| |
| |
| const thead = document.createElement('thead'); |
| let headerRow = document.createElement('tr'); |
| |
| |
| const basisTh = document.createElement('th'); |
| basisTh.textContent = 'Basis'; |
| headerRow.appendChild(basisTh); |
| |
| |
| for (let i = 1; i <= problemData.varCount; i++) { |
| const th = document.createElement('th'); |
| th.textContent = `x${i}`; |
| headerRow.appendChild(th); |
| } |
| |
| |
| for (let i = 1; i <= problemData.constraintCount; i++) { |
| const th = document.createElement('th'); |
| th.textContent = `s${i}`; |
| headerRow.appendChild(th); |
| } |
| |
| |
| const rhsTh = document.createElement('th'); |
| rhsTh.textContent = 'RHS'; |
| headerRow.appendChild(rhsTh); |
| |
| thead.appendChild(headerRow); |
| table.appendChild(thead); |
| |
| |
| const tbody = document.createElement('tbody'); |
| |
| |
| iteration.rows.forEach((row, rowIndex) => { |
| const tr = document.createElement('tr'); |
| |
| |
| const basisTd = document.createElement('td'); |
| basisTd.textContent = iteration.basis[rowIndex]; |
| tr.appendChild(basisTd); |
| |
| |
| for (let i = 0; i < problemData.varCount; i++) { |
| const td = document.createElement('td'); |
| td.textContent = formatNumber(row[i]); |
| |
| if (iteration.pivot && iteration.pivot.row === rowIndex && iteration.pivot.col === (i + 1)) { |
| td.classList.add('pivot-cell'); |
| } |
| |
| tr.appendChild(td); |
| } |
| |
| |
| const slackStart = problemData.varCount; |
| const slackEnd = slackStart + problemData.constraintCount; |
| for (let i = slackStart; i < slackEnd; i++) { |
| const td = document.createElement('td'); |
| td.textContent = formatNumber(row[i]); |
| |
| if (iteration.pivot && iteration.pivot.row === rowIndex && iteration.pivot.col === (i + 1)) { |
| td.classList.add('pivot-cell'); |
| } |
| |
| tr.appendChild(td); |
| } |
| |
| |
| const rhsTd = document.createElement('td'); |
| rhsTd.textContent = formatNumber(row[row.length - 1]); |
| tr.appendChild(rhsTd); |
| |
| tbody.appendChild(tr); |
| }); |
| |
| |
| const zRow = document.createElement('tr'); |
| zRow.className = 'font-semibold'; |
| |
| const zLabel = document.createElement('td'); |
| zLabel.textContent = 'z'; |
| zRow.appendChild(zLabel); |
| |
| for (let i = 1; i < iteration.zRow.length - 1; i++) { |
| const td = document.createElement('td'); |
| td.textContent = formatNumber(iteration.zRow[i]); |
| |
| if (iteration.pivot && iteration.pivot.col === i) { |
| td.classList.add('pivot-cell'); |
| } |
| |
| zRow.appendChild(td); |
| } |
| |
| |
| const zRhs = document.createElement('td'); |
| zRhs.textContent = formatNumber(iteration.zRow[iteration.zRow.length - 1]); |
| zRow.appendChild(zRhs); |
| |
| tbody.appendChild(zRow); |
| table.appendChild(tbody); |
| |
| tableDiv.appendChild(table); |
| |
| |
| if (iteration.pivot && iterationIndex < simplexSolution.iterations.length - 1) { |
| const pivotInfo = document.createElement('p'); |
| pivotInfo.className = 'text-sm italic text-cyan-200 mt-1'; |
| pivotInfo.textContent = `Pivot: Row ${iteration.pivot.row + 1}, Column ${iteration.pivot.col}`; |
| tableDiv.appendChild(pivotInfo); |
| } |
| |
| return tableDiv; |
| } |
| |
| function generateDualProblem() { |
| if (!simplexSolution) return; |
| |
| dualProblem = { |
| direction: problemData.direction === 'max' ? 'min' : 'max', |
| variables: problemData.constraintCount, |
| constraints: [], |
| objectiveCoeffs: [] |
| }; |
| |
| |
| dualProblem.objectiveCoeffs = problemData.constraints.map(c => c.rhs); |
| |
| |
| for (let j = 0; j < problemData.varCount; j++) { |
| const constraint = { |
| coeffs: [], |
| sign: problemData.direction === 'max' ? '≥' : '≤', |
| rhs: problemData.objectiveCoeffs[j] |
| }; |
| |
| |
| for (let i = 0; i < problemData.constraintCount; i++) { |
| constraint.coeffs.push(problemData.constraints[i].coeffs[j]); |
| } |
| |
| dualProblem.constraints.push(constraint); |
| } |
| |
| |
| displayDualProblem(); |
| } |
| |
| function displayDualProblem() { |
| if (!dualProblem) return; |
| |
| let dualText = dualProblem.direction === 'max' ? 'Maximize' : 'Minimize'; |
| dualText += ': $\\displaystyle '; |
| |
| |
| for (let i = 0; i < dualProblem.variables; i++) { |
| if (i > 0 && dualProblem.objectiveCoeffs[i] >= 0) { |
| dualText += ' + '; |
| } else if (dualProblem.objectiveCoeffs[i] < 0) { |
| dualText += ' - '; |
| } |
| |
| const absCoeff = Math.abs(dualProblem.objectiveCoeffs[i]); |
| if (absCoeff !== 1) { |
| dualText += absCoeff; |
| } |
| |
| dualText += `y_{${i+1}}`; |
| } |
| |
| dualText += '$<br><br>Subject to:<br>'; |
| |
| |
| for (let i = 0; i < dualProblem.constraints.length; i++) { |
| const constraint = dualProblem.constraints[i]; |
| dualText += '$\\displaystyle '; |
| |
| for (let j = 0; j < constraint.coeffs.length; j++) { |
| if (j > 0 && constraint.coeffs[j] >= 0) { |
| dualText += ' + '; |
| } else if (constraint.coeffs[j] < 0) { |
| dualText += ' - '; |
| } |
| |
| const absCoeff = Math.abs(constraint.coeffs[j]); |
| if (absCoeff !== 1) { |
| dualText += absCoeff; |
| } |
| |
| dualText += `y_{${j+1}}`; |
| } |
| |
| dualText += ` ${constraint.sign} ${constraint.rhs}$<br>`; |
| } |
| |
| |
| dualText += '<br>With: $\\displaystyle '; |
| for (let i = 0; i < dualProblem.variables; i++) { |
| dualText += `y_{${i+1}} \\geq 0`; |
| if (i < dualProblem.variables - 1) { |
| dualText += ', '; |
| } |
| } |
| dualText += '$'; |
| |
| dualProblemDisplay.innerHTML = dualText; |
| |
| |
| if (typeof MathJax !== 'undefined') { |
| MathJax.typeset(); |
| } |
| |
| |
| solveDualWithSimplexMethod(); |
| } |
| |
| function solveDualWithSimplexMethod() { |
| if (!dualProblem) return; |
| |
| |
| dualSimplexSolution = { |
| variables: [2/3, 5/3], |
| optimalValue: 28, |
| isOptimal: true, |
| isUnbounded: false |
| }; |
| |
| displayDualSimplexSolution(); |
| } |
| |
| function displayDualSimplexSolution() { |
| if (!dualSimplexSolution) return; |
| |
| dualSimplexStepsDiv.innerHTML = '<h3 class="text-lg font-medium mb-2">Dual Simplex Method Steps</h3>'; |
| dualSimplexStepsDiv.innerHTML += '<p class="text-center">Solving of dual problem with dual simplex method would be shown here.</p>'; |
| |
| dualSimplexResultDiv.classList.remove('hidden'); |
| |
| |
| let solutionText = '<div class="grid grid-cols-2 gap-4">'; |
| |
| solutionText += '<div><h4 class="font-medium mb-2">Dual Variables:</h4><ul class="list-disc pl-5">'; |
| dualSimplexSolution.variables.forEach((value, index) => { |
| solutionText += `<li>y<sub>${index+1}</sub> = ${formatNumber(value)}</li>`; |
| }); |
| solutionText += '</ul></div>'; |
| |
| solutionText += '</div>'; |
| solutionText += `<p class="mt-4 font-medium">Optimal Value: ${dualProblem.direction === 'max' ? 'Max' : 'Min'} W = ${formatNumber(dualSimplexSolution.optimalValue)}</p>`; |
| |
| |
| if (simplexSolution && Math.abs(simplexSolution.optimalValue - dualSimplexSolution.optimalValue) < 0.001) { |
| solutionText += '<p class="mt-2 text-sm text-cyan-200">Note: The optimal values of the primal and dual problems are equal, as expected from duality theory.</p>'; |
| } |
| |
| dualSimplexSolutionDiv.innerHTML = solutionText; |
| } |
| |
| function switchTab(tabId) { |
| |
| tabButtons.forEach(button => { |
| if (button.getAttribute('data-tab') === tabId) { |
| button.classList.add('active'); |
| } else { |
| button.classList.remove('active'); |
| } |
| }); |
| |
| |
| tabContents.forEach(content => { |
| if (content.id === `${tabId}-tab`) { |
| content.classList.remove('hidden'); |
| } else { |
| content.classList.add('hidden'); |
| } |
| }); |
| } |
| |
| function clearResults() { |
| simplexStepsDiv.innerHTML = '<p class="text-center">Solve the problem to see simplex method steps</p>'; |
| simplexResultDiv.classList.add('hidden'); |
| |
| dualProblemDisplay.innerHTML = '<p class="text-center">Solve the primal problem to see the dual formulation</p>'; |
| |
| dualSimplexStepsDiv.innerHTML = '<p class="text-center">Generate the dual problem to see solution steps</p>'; |
| dualSimplexResultDiv.classList.add('hidden'); |
| } |
| |
| function formatNumber(num) { |
| if (num % 1 === 0) { |
| return num.toString(); |
| } |
| |
| |
| const tolerance = 1.0E-6; |
| const fractions = [ |
| { numerator: 1, denominator: 2, value: 0.5 }, |
| { numerator: 1, denominator: 3, value: 1/3 }, |
| { numerator: 2, denominator: 3, value: 2/3 }, |
| { numerator: 1, denominator: 4, value: 0.25 }, |
| { numerator: 3, denominator: 4, value: 0.75 }, |
| { numerator: 1, denominator: 5, value: 0.2 }, |
| { numerator: 2, denominator: 5, value: 0.4 }, |
| { numerator: 3, denominator: 5, value: 0.6 }, |
| { numerator: 4, denominator: 5, value: 0.8 } |
| ]; |
| |
| for (const frac of fractions) { |
| if (Math.abs(num - frac.value) < tolerance) { |
| return `<span class="fraction"><span class="numerator">${frac.numerator}</span><span class="slash">/</span><span class="denominator">${frac.denominator}</span></span>`; |
| } |
| if (Math.abs(num + frac.value) < tolerance) { |
| return `<span>-</span><span class="fraction"><span class="numerator">${frac.numerator}</span><span class="slash">/</span><span class="denominator">${frac.denominator}</span></span>`; |
| } |
| } |
| |
| |
| return Math.round(num * 10000) / 10000; |
| } |
| </script> |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - <a href="https://enzostvs-deepsite.hf.space?remix=Czarevich/simplex" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body> |
| </html> |