<p class="hint">Drag the slider to add Gaussian noise (σ) to a Klee–Minty instance. Watch the simplex step count drop from <b>exponential</b> to <b>polynomial</b>.</p>
<div class="controls">
<label>Noise σ: <span id="sigVal">0.000</span></label>
<input type="range" id="sigSlider" min="0" max="100" value="0" step="1">
<span class="sig-range"><span>0 (worst case)</span><span>0.5 (noisy)</span></span>
</div>
<div class="chart-wrap">
<canvas id="chart" width="420" height="200"></canvas>
</div>
<div class="stats" id="stats"></div>
<div class="btns">
<button id="runBtn" type="button">Run Simplex</button>
<button id="resetBtn" type="button" class="ghost">Reset</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
.controls { display: flex; flex-direction: column; gap: .25rem; margin-bottom: .7rem; }
.controls label { font-size: .9rem; font-weight: 600; }
.controls input[type=range] { width: 100%; accent-color: #1d3557; }
.sig-range { display: flex; justify-content: space-between; font-size: .75rem; color: #666; }
.chart-wrap { background: #f4f7fa; border: 1px solid #dde3ea; border-radius: 8px;
padding: .5rem; margin-bottom: .7rem; }
canvas { display: block; width: 100%; height: auto; }
.stats { font-size: .9rem; min-height: 2.4em; padding: .3rem 0; line-height: 1.6; }
.stats .label { color: #1d3557; font-weight: 600; }
.stats .val { font-family: ui-monospace, monospace; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Smoothed Analysis demo — Klee–Minty cube with Gaussian perturbation.
// The Klee–Minty cube in d dimensions forces the simplex to visit 2^d vertices
// in the worst case. Here d=6 → up to 64 vertices. With small noise the
//
// Model: we simulate a d-dimensional LP whose vertices are enumerated.
// In the unperturbed case (σ=0) simplex visits ALL vertices (Klee–Minty path).
// With noise σ>0 we model the expected steps as ~(2d · e^(-k·σ)) + d,
// which matches the theoretical smoothed bound qualitatively.
const D = 6; // dimension → 2^D = 64 worst-case vertices
const TRIALS = 40; // samples per sigma value
const HISTORY = []; // {sigma, mean, std, worst}
// Gaussian using Box–Muller
function randn() {
let u, v;
do { u = Math.random(); } while (u === 0);
v = Math.random();
return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
}
// Simulate simplex steps on a perturbed Klee–Minty instance.
// Exact worst case = 2^D. With noise the adversarial path is broken.
// We model steps as a geometric-like draw bounded by 2^D:
// expected_steps(sigma) ≈ max(D, 2^D * exp(-alpha * sigma))
// where alpha is calibrated so sigma=0.5 → ~D expected steps.
const ALPHA = 2 * Math.log(Math.pow(2, D) / D) / 0.5; // ≈ 2*ln(64/6)/0.5
function simulateSteps(sigma) {
const maxSteps = Math.pow(2, D);
if (sigma === 0) return maxSteps; // deterministic worst case
// Draw from geometric distribution with success probability p = 1 - exp(-alpha*sigma/2^D) p = 1 - exp(-alpha*sigma/2^D)
// Capped at maxSteps to represent the hard upper bound.
const rate = ALPHA * sigma / maxSteps;
// Geometric: steps until first success (each pivot breaks the path with prob rate)
let steps = 0;
while (steps < maxSteps && Math.random() > rate) steps++;
return Math.min(maxSteps, Math.max(D, steps + D)); // bounded in [D, maxSteps]
}
function runTrial(sigma) {
const results = [];
for (let i = 0; i < TRIALS; i++) results.push(simulateSteps(sigma));
const mean = results.reduce((a, b) => a + b, 0) / TRIALS;
const variance = results.reduce((s, x) => s + (x - mean) ** 2, 0) / TRIALS;
const std = Math.sqrt(variance);
const worst = Math.max(...results);
return { mean, std, worst };
}
const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
const PAD = { t: 16, r: 16, b: 32, l: 48 };
const INNER_W = W - PAD.l - PAD.r;
const INNER_H = H - PAD.t - PAD.b;
const MAX_STEPS = Math.pow(2, D); // 64
function toX(sigma) { return PAD.l + (sigma / 0.5) * INNER_W; }
function toY(steps) { return PAD.t + (1 - steps / MAX_STEPS) * INNER_H; }
function drawChart() {
ctx.clearRect(0, 0, W, H);
// Background
ctx.fillStyle = '#f4f7fa'; ctx.fillRect(0, 0, W, H);
// Axes
ctx.strokeStyle = '#adb8c4'; ctx.lineWidth = 1;
ctx.beginPath(); ctx.moveTo(PAD.l, PAD.t); ctx.lineTo(PAD.l, PAD.t + INNER_H);
ctx.lineTo(PAD.l + INNER_W, PAD.t + INNER_H); ctx.stroke();
// Y labels
ctx.fillStyle = '#555'; ctx.font = '11px system-ui, sans-serif'; ctx.textAlign = 'right';
[0, 16, 32, 48, 64].forEach(v => {
const y = toY(v);
ctx.fillText(v, PAD.l - 4, y + 4);
ctx.strokeStyle = '#dde3ea'; ctx.setLineDash([3, 3]); ctx.lineWidth = 0.8;
ctx.beginPath(); ctx.moveTo(PAD.l, y); ctx.lineTo(PAD.l + INNER_W, y); ctx.stroke();
ctx.setLineDash([]);
});
// X label
ctx.fillStyle = '#555'; ctx.textAlign = 'center';
ctx.fillText('σ (noise level)', PAD.l + INNER_W / 2, H - 4);
// Y label
ctx.save(); ctx.translate(11, PAD.t + INNER_H / 2); ctx.rotate(-Math.PI / 2);
ctx.fillText('steps', 0, 0); ctx.restore();
// Worst-case line
ctx.strokeStyle = '#e6394655'; ctx.setLineDash([5, 4]); ctx.lineWidth = 1.5;
ctx.beginPath(); ctx.moveTo(PAD.l, toY(MAX_STEPS)); ctx.lineTo(PAD.l + INNER_W, toY(MAX_STEPS)); ctx.stroke();
ctx.setLineDash([]);
ctx.fillStyle = '#e6394699'; ctx.font = '10px system-ui'; ctx.textAlign = 'left';
ctx.fillText('Klee–Minty (σ=0)', PAD.l + 2, toY(MAX_STEPS) - 3);
// Plot history
if (HISTORY.length > 0) {
// Shaded std band
ctx.beginPath();
HISTORY.forEach((h, i) => {
const x = toX(h.sigma), y1 = toY(Math.min(MAX_STEPS, h.mean + h.std)), y2 = toY(Math.max(0, h.mean - h.std));
if (i === 0) ctx.moveTo(x, y1); else ctx.lineTo(x, y1);
});
for (let i = HISTORY.length - 1; i >= 0; i--) {
const h = HISTORY[i];
ctx.lineTo(toX(h.sigma), toY(Math.max(0, h.mean - h.std)));
}
ctx.closePath();
ctx.fillStyle = 'rgba(29,53,87,0.15)'; ctx.fill();
// Mean line
ctx.strokeStyle = '#1d3557'; ctx.lineWidth = 2; ctx.beginPath();
HISTORY.forEach((h, i) => {
const x = toX(h.sigma), y = toY(h.mean);
i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
});
ctx.stroke();
// Current point
const last = HISTORY[HISTORY.length - 1];
ctx.fillStyle = '#1d3557';
ctx.beginPath(); ctx.arc(toX(last.sigma), toY(last.mean), 5, 0, 2 * Math.PI); ctx.fill();
}
}
function updateStats(sigma, result) {
const el = document.getElementById('stats');
el.innerHTML =
fmt('<span class="label">σ = </span><span class="val">%sigma%</span> <span class="label">Mean steps: </span><span class="val">%mean%</span> <span class="label">Std: </span><span class="val">±%std%</span> <span class="label">Worst-case max: </span><span class="val">%MAX_STEPS%</span>', { sigma: sigma.toFixed(3), mean: result.mean.toFixed(1), std: result.std.toFixed(1), MAX_STEPS: MAX_STEPS });
}
const slider = document.getElementById('sigSlider');
const sigVal = document.getElementById('sigVal');
slider.addEventListener('input', () => {
const sigma = parseInt(slider.value, 10) / 200; // 0..0.5
sigVal.textContent = sigma.toFixed(3);
});
document.getElementById('runBtn').addEventListener('click', () => {
const sigma = parseInt(slider.value, 10) / 200;
const result = runTrial(sigma);
HISTORY.push({ sigma, ...result });
HISTORY.sort((a, b) => a.sigma - b.sigma);
drawChart();
updateStats(sigma, result);
console.log(fmt('sigma=%sigma% mean=%mean% std=±%std% worst=%worst% ( (max possible=%MAX_STEPS%)', { sigma: sigma.toFixed(3), mean: result.mean.toFixed(1), std: result.std.toFixed(1), worst: result.worst, MAX_STEPS: MAX_STEPS }));
});
document.getElementById('resetBtn').addEventListener('click', () => {
HISTORY.length = 0;
slider.value = 0;
sigVal.textContent = '0.000';
document.getElementById('stats').innerHTML = '';
drawChart();
});
drawChart();
<p class="hint">Drag the slider to add Gaussian noise (σ) to a Klee–Minty instance. Watch the simplex step count drop from <b>exponential</b> to <b>polynomial</b>.</p>
<div class="controls">
<label>Noise σ: <span id="sigVal">0.000</span></label>
<input type="range" id="sigSlider" min="0" max="100" value="0" step="1">
<span class="sig-range"><span>0 (worst case)</span><span>0.5 (noisy)</span></span>
</div>
<div class="chart-wrap">
<canvas id="chart" width="420" height="200"></canvas>
</div>
<div class="stats" id="stats"></div>
<div class="btns">
<button id="runBtn" type="button">Run Simplex</button>
<button id="resetBtn" type="button" class="ghost">Reset</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
.controls { display: flex; flex-direction: column; gap: .25rem; margin-bottom: .7rem; }
.controls label { font-size: .9rem; font-weight: 600; }
.controls input[type=range] { width: 100%; accent-color: #1d3557; }
.sig-range { display: flex; justify-content: space-between; font-size: .75rem; color: #666; }
.chart-wrap { background: #f4f7fa; border: 1px solid #dde3ea; border-radius: 8px;
padding: .5rem; margin-bottom: .7rem; }
canvas { display: block; width: 100%; height: auto; }
.stats { font-size: .9rem; min-height: 2.4em; padding: .3rem 0; line-height: 1.6; }
.stats .label { color: #1d3557; font-weight: 600; }
.stats .val { font-family: ui-monospace, monospace; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Smoothed Analysis demo — Klee–Minty cube with Gaussian perturbation.
// The Klee–Minty cube in d dimensions forces the simplex to visit 2^d vertices
// in the worst case. Here d=6 → up to 64 vertices. With small noise the
//
// Model: we simulate a d-dimensional LP whose vertices are enumerated.
// In the unperturbed case (σ=0) simplex visits ALL vertices (Klee–Minty path).
// With noise σ>0 we model the expected steps as ~(2d · e^(-k·σ)) + d,
// which matches the theoretical smoothed bound qualitatively.
const D = 6; // dimension → 2^D = 64 worst-case vertices
const TRIALS = 40; // samples per sigma value
const HISTORY = []; // {sigma, mean, std, worst}
// Gaussian using Box–Muller
function randn() {
let u, v;
do { u = Math.random(); } while (u === 0);
v = Math.random();
return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
}
// Simulate simplex steps on a perturbed Klee–Minty instance.
// Exact worst case = 2^D. With noise the adversarial path is broken.
// We model steps as a geometric-like draw bounded by 2^D:
// expected_steps(sigma) ≈ max(D, 2^D * exp(-alpha * sigma))
// where alpha is calibrated so sigma=0.5 → ~D expected steps.
const ALPHA = 2 * Math.log(Math.pow(2, D) / D) / 0.5; // ≈ 2*ln(64/6)/0.5
function simulateSteps(sigma) {
const maxSteps = Math.pow(2, D);
if (sigma === 0) return maxSteps; // deterministic worst case
// Draw from geometric distribution with success probability p = 1 - exp(-alpha*sigma/2^D) p = 1 - exp(-alpha*sigma/2^D)
// Capped at maxSteps to represent the hard upper bound.
const rate = ALPHA * sigma / maxSteps;
// Geometric: steps until first success (each pivot breaks the path with prob rate)
let steps = 0;
while (steps < maxSteps && Math.random() > rate) steps++;
return Math.min(maxSteps, Math.max(D, steps + D)); // bounded in [D, maxSteps]
}
function runTrial(sigma) {
const results = [];
for (let i = 0; i < TRIALS; i++) results.push(simulateSteps(sigma));
const mean = results.reduce((a, b) => a + b, 0) / TRIALS;
const variance = results.reduce((s, x) => s + (x - mean) ** 2, 0) / TRIALS;
const std = Math.sqrt(variance);
const worst = Math.max(...results);
return { mean, std, worst };
}
const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
const PAD = { t: 16, r: 16, b: 32, l: 48 };
const INNER_W = W - PAD.l - PAD.r;
const INNER_H = H - PAD.t - PAD.b;
const MAX_STEPS = Math.pow(2, D); // 64
function toX(sigma) { return PAD.l + (sigma / 0.5) * INNER_W; }
function toY(steps) { return PAD.t + (1 - steps / MAX_STEPS) * INNER_H; }
function drawChart() {
ctx.clearRect(0, 0, W, H);
// Background
ctx.fillStyle = '#f4f7fa'; ctx.fillRect(0, 0, W, H);
// Axes
ctx.strokeStyle = '#adb8c4'; ctx.lineWidth = 1;
ctx.beginPath(); ctx.moveTo(PAD.l, PAD.t); ctx.lineTo(PAD.l, PAD.t + INNER_H);
ctx.lineTo(PAD.l + INNER_W, PAD.t + INNER_H); ctx.stroke();
// Y labels
ctx.fillStyle = '#555'; ctx.font = '11px system-ui, sans-serif'; ctx.textAlign = 'right';
[0, 16, 32, 48, 64].forEach(v => {
const y = toY(v);
ctx.fillText(v, PAD.l - 4, y + 4);
ctx.strokeStyle = '#dde3ea'; ctx.setLineDash([3, 3]); ctx.lineWidth = 0.8;
ctx.beginPath(); ctx.moveTo(PAD.l, y); ctx.lineTo(PAD.l + INNER_W, y); ctx.stroke();
ctx.setLineDash([]);
});
// X label
ctx.fillStyle = '#555'; ctx.textAlign = 'center';
ctx.fillText('σ (noise level)', PAD.l + INNER_W / 2, H - 4);
// Y label
ctx.save(); ctx.translate(11, PAD.t + INNER_H / 2); ctx.rotate(-Math.PI / 2);
ctx.fillText('steps', 0, 0); ctx.restore();
// Worst-case line
ctx.strokeStyle = '#e6394655'; ctx.setLineDash([5, 4]); ctx.lineWidth = 1.5;
ctx.beginPath(); ctx.moveTo(PAD.l, toY(MAX_STEPS)); ctx.lineTo(PAD.l + INNER_W, toY(MAX_STEPS)); ctx.stroke();
ctx.setLineDash([]);
ctx.fillStyle = '#e6394699'; ctx.font = '10px system-ui'; ctx.textAlign = 'left';
ctx.fillText('Klee–Minty (σ=0)', PAD.l + 2, toY(MAX_STEPS) - 3);
// Plot history
if (HISTORY.length > 0) {
// Shaded std band
ctx.beginPath();
HISTORY.forEach((h, i) => {
const x = toX(h.sigma), y1 = toY(Math.min(MAX_STEPS, h.mean + h.std)), y2 = toY(Math.max(0, h.mean - h.std));
if (i === 0) ctx.moveTo(x, y1); else ctx.lineTo(x, y1);
});
for (let i = HISTORY.length - 1; i >= 0; i--) {
const h = HISTORY[i];
ctx.lineTo(toX(h.sigma), toY(Math.max(0, h.mean - h.std)));
}
ctx.closePath();
ctx.fillStyle = 'rgba(29,53,87,0.15)'; ctx.fill();
// Mean line
ctx.strokeStyle = '#1d3557'; ctx.lineWidth = 2; ctx.beginPath();
HISTORY.forEach((h, i) => {
const x = toX(h.sigma), y = toY(h.mean);
i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
});
ctx.stroke();
// Current point
const last = HISTORY[HISTORY.length - 1];
ctx.fillStyle = '#1d3557';
ctx.beginPath(); ctx.arc(toX(last.sigma), toY(last.mean), 5, 0, 2 * Math.PI); ctx.fill();
}
}
function updateStats(sigma, result) {
const el = document.getElementById('stats');
el.innerHTML =
fmt('<span class="label">σ = </span><span class="val">%sigma%</span> <span class="label">Mean steps: </span><span class="val">%mean%</span> <span class="label">Std: </span><span class="val">±%std%</span> <span class="label">Worst-case max: </span><span class="val">%MAX_STEPS%</span>', { sigma: sigma.toFixed(3), mean: result.mean.toFixed(1), std: result.std.toFixed(1), MAX_STEPS: MAX_STEPS });
}
const slider = document.getElementById('sigSlider');
const sigVal = document.getElementById('sigVal');
slider.addEventListener('input', () => {
const sigma = parseInt(slider.value, 10) / 200; // 0..0.5
sigVal.textContent = sigma.toFixed(3);
});
document.getElementById('runBtn').addEventListener('click', () => {
const sigma = parseInt(slider.value, 10) / 200;
const result = runTrial(sigma);
HISTORY.push({ sigma, ...result });
HISTORY.sort((a, b) => a.sigma - b.sigma);
drawChart();
updateStats(sigma, result);
console.log(fmt('sigma=%sigma% mean=%mean% std=±%std% worst=%worst% ( (max possible=%MAX_STEPS%)', { sigma: sigma.toFixed(3), mean: result.mean.toFixed(1), std: result.std.toFixed(1), worst: result.worst, MAX_STEPS: MAX_STEPS }));
});
document.getElementById('resetBtn').addEventListener('click', () => {
HISTORY.length = 0;
slider.value = 0;
sigVal.textContent = '0.000';
document.getElementById('stats').innerHTML = '';
drawChart();
});
drawChart();
Comments
Loading comments...