<!-- Controls: coin-flip simulation parameters -->
<div class="controls">
<label>Flips per trial (n): <strong id="nVal">50</strong>
<input type="range" id="nSlider" min="10" max="500" step="10" value="50">
</label>
<label>Deviation ε: <strong id="epsVal">0.10</strong>
<input type="range" id="epsSlider" min="1" max="30" step="1" value="10">
</label>
<label>Number of trials: <strong id="trialsVal">500</strong>
<input type="range" id="trialsSlider" min="100" max="2000" step="100" value="500">
</label>
</div>
<canvas id="chart" width="540" height="240"></canvas>
<div class="legend">
<span class="leg-item"><span class="swatch empirical"></span>Empirical tail</span>
<span class="leg-item"><span class="swatch markov"></span>Markov bound</span>
<span class="leg-item"><span class="swatch chebyshev"></span>Chebyshev bound</span>
<span class="leg-item"><span class="swatch hoeffding"></span>Hoeffding bound</span>
</div>
<div id="stats" class="stats"></div>
<button id="runBtn" type="button">Run simulation</button>
/* Layout and color palette for the concentration demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; }
.controls { display: flex; flex-direction: column; gap: .45rem; margin-bottom: .7rem; }
label { font-size: .85rem; display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
input[type=range] { flex: 1; min-width: 120px; max-width: 260px; }
canvas { display: block; border: 1px solid #cdd9e3; border-radius: 8px; background: #f7fafc; width: 100%; max-width: 540px; }
.legend { display: flex; flex-wrap: wrap; gap: .5rem 1rem; margin: .5rem 0 .3rem; font-size: .8rem; }
.leg-item { display: flex; align-items: center; gap: .35rem; }
.swatch { width: 18px; height: 4px; border-radius: 2px; display: inline-block; }
.swatch.empirical { background: #4a90d9; }
.swatch.markov { background: #e8a838; }
.swatch.chebyshev { background: #d05c5c; }
.swatch.hoeffding { background: #3aaa6a; }
.stats { font-size: .82rem; color: #444; min-height: 2.5em; margin-bottom: .4rem; line-height: 1.5; }
button { font: 600 14px system-ui; padding: .45rem 1rem; background: #1d3557; color: #fff; border: none; border-radius: 8px; cursor: pointer; }
button:hover { background: #274e7a; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Concentration inequalities demo: simulate coin flips and compare
// theoretical tail bounds (Markov, Chebyshev, Hoeffding) to empirical results
const nSlider = document.getElementById('nSlider');
const epsSlider = document.getElementById('epsSlider');
const trialsSlider = document.getElementById('trialsSlider');
const nVal = document.getElementById('nVal');
const epsVal = document.getElementById('epsVal');
const trialsVal = document.getElementById('trialsVal');
const statsEl = document.getElementById('stats');
const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');
// Update displayed values when sliders change
function updateLabels() {
nVal.textContent = nSlider.value;
epsVal.textContent = (epsSlider.value / 100).toFixed(2);
trialsVal.textContent = trialsSlider.value;
}
nSlider.addEventListener('input', updateLabels);
epsSlider.addEventListener('input', updateLabels);
trialsSlider.addEventListener('input', updateLabels);
// Markov's inequality: Pr[X >= mu + eps] <= mu / (mu + eps) for non-negative X
function markovBound(eps) {
// For fraction of heads with mu=0.5, bound on upper tail only
return Math.min(1, 0.5 / (0.5 + eps));
}
// Chebyshev: Pr[|X - mu| >= eps] <= Var(X) / eps^2
function chebyshevBound(n, eps) {
// Variance of average of n Bernoulli(0.5) = 0.25/n
const sigma2 = 0.25 / n;
return Math.min(1, sigma2 / (eps * eps));
}
// Hoeffding: two-sided tail, variables in [0,1], sum of ranges^2 = n
function hoeffdingBound(n, eps) {
// Two-sided: 2 * exp(-2 * n * eps^2)
return 2 * Math.exp(-2 * n * eps * eps);
}
// Run trials coin-flip experiments, return fraction of heads each time
function simulate(n, trials) {
const fracs = [];
for (let t = 0; t < trials; t++) {
let heads = 0;
for (let i = 0; i < n; i++) heads += Math.random() < 0.5 ? 1 : 0;
fracs.push(heads / n);
}
return fracs;
}
// Bin fractions into a histogram with given number of bins
function buildHistogram(fracs, bins) {
const counts = new Array(bins).fill(0);
for (const f of fracs) {
const b = Math.min(bins - 1, Math.floor(f * bins));
counts[b]++;
}
return counts;
}
function run() {
const n = parseInt(nSlider.value);
const eps = parseInt(epsSlider.value) / 100;
const trials = parseInt(trialsSlider.value);
const fracs = simulate(n, trials);
const BINS = 50;
const counts = buildHistogram(fracs, BINS);
const maxCount = Math.max(...counts);
// Draw the histogram and bounds on the canvas
const W = canvas.width;
const H = canvas.height;
const PAD = { top: 10, right: 10, bottom: 28, left: 36 };
const cw = W - PAD.left - PAD.right;
const ch = H - PAD.top - PAD.bottom;
ctx.clearRect(0, 0, W, H);
// Shade the central region [0.5-eps, 0.5+eps] in green
const lo = Math.max(0, 0.5 - eps);
const hi = Math.min(1, 0.5 + eps);
const xLo = PAD.left + lo * cw;
const xHi = PAD.left + hi * cw;
ctx.fillStyle = 'rgba(58,170,106,0.12)';
ctx.fillRect(xLo, PAD.top, xHi - xLo, ch);
// Draw histogram bars
const barW = cw / BINS;
for (let b = 0; b < BINS; b++) {
const h = (counts[b] / maxCount) * ch;
const x = PAD.left + b * barW;
const y = PAD.top + ch - h;
ctx.fillStyle = '#4a90d9';
ctx.fillRect(x + 1, y, barW - 2, h);
}
// Compute and draw the three theoretical bounds as horizontal lines
const mBound = markovBound(eps);
const cBound = chebyshevBound(n, eps);
const hBound = hoeffdingBound(n, eps);
function drawHLine(prob, color, label) {
// Draw a horizontal dashed line at a given tail-probability level
const yp = PAD.top + ch * (1 - Math.min(prob, 1));
ctx.strokeStyle = color;
ctx.lineWidth = 2;
ctx.setLineDash([5, 3]);
ctx.beginPath();
ctx.moveTo(PAD.left, yp);
ctx.lineTo(PAD.left + cw, yp);
ctx.stroke();
ctx.setLineDash([]);
ctx.fillStyle = color;
ctx.font = '11px system-ui';
ctx.fillText(label, PAD.left + cw - 4, yp - 3);
}
// Draw axes
ctx.strokeStyle = '#bbb';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(PAD.left, PAD.top);
ctx.lineTo(PAD.left, PAD.top + ch);
ctx.lineTo(PAD.left + cw, PAD.top + ch);
ctx.stroke();
// x-axis labels: fraction of heads from 0 to 1
ctx.fillStyle = '#666';
ctx.font = '10px system-ui';
for (let v = 0; v <= 1; v += 0.25) {
const x = PAD.left + v * cw;
ctx.fillText(v.toFixed(2), x - 8, PAD.top + ch + 16);
}
// y-axis labels: probability density (relative)
for (let p = 0; p <= 1; p += 0.25) {
const y = PAD.top + ch * (1 - p);
ctx.fillStyle = '#999';
ctx.font = '9px system-ui';
ctx.textAlign = 'right';
ctx.fillText(p.toFixed(2), PAD.left - 3, y + 3);
}
ctx.textAlign = 'left';
drawHLine(mBound, '#e8a838', 'M');
drawHLine(cBound, '#d05c5c', 'C');
drawHLine(hBound, '#3aaa6a', 'H');
// Count how many trials fell outside the epsilon band
const outside = fracs.filter(f => Math.abs(f - 0.5) > eps).length;
const empTail = outside / trials;
statsEl.innerHTML =
fmt('<b>n=%n%, ε=%eps%, trials=%trials%</b><br>Empirical outside: <b>%v%%</b> | Markov: <b>%v2%%</b> | Chebyshev: <b>%v3%%</b> | Hoeffding: <b>%v4%%</b>', { n: n, eps: eps.toFixed(2), trials: trials, v: (empTail * 100).toFixed(1), v2: (mBound * 100).toFixed(1), v3: (cBound * 100).toFixed(1), v4: (hBound * 100).toFixed(1) });
console.log('Simulation results:', { n, eps, trials, empTail: empTail.toFixed(4), markov: mBound.toFixed(4), chebyshev: cBound.toFixed(4), hoeffding: hBound.toFixed(4) });
}
document.getElementById('runBtn').addEventListener('click', run);
run();
<!-- Controls: coin-flip simulation parameters -->
<div class="controls">
<label>Flips per trial (n): <strong id="nVal">50</strong>
<input type="range" id="nSlider" min="10" max="500" step="10" value="50">
</label>
<label>Deviation ε: <strong id="epsVal">0.10</strong>
<input type="range" id="epsSlider" min="1" max="30" step="1" value="10">
</label>
<label>Number of trials: <strong id="trialsVal">500</strong>
<input type="range" id="trialsSlider" min="100" max="2000" step="100" value="500">
</label>
</div>
<canvas id="chart" width="540" height="240"></canvas>
<div class="legend">
<span class="leg-item"><span class="swatch empirical"></span>Empirical tail</span>
<span class="leg-item"><span class="swatch markov"></span>Markov bound</span>
<span class="leg-item"><span class="swatch chebyshev"></span>Chebyshev bound</span>
<span class="leg-item"><span class="swatch hoeffding"></span>Hoeffding bound</span>
</div>
<div id="stats" class="stats"></div>
<button id="runBtn" type="button">Run simulation</button>
/* Layout and color palette for the concentration demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; }
.controls { display: flex; flex-direction: column; gap: .45rem; margin-bottom: .7rem; }
label { font-size: .85rem; display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
input[type=range] { flex: 1; min-width: 120px; max-width: 260px; }
canvas { display: block; border: 1px solid #cdd9e3; border-radius: 8px; background: #f7fafc; width: 100%; max-width: 540px; }
.legend { display: flex; flex-wrap: wrap; gap: .5rem 1rem; margin: .5rem 0 .3rem; font-size: .8rem; }
.leg-item { display: flex; align-items: center; gap: .35rem; }
.swatch { width: 18px; height: 4px; border-radius: 2px; display: inline-block; }
.swatch.empirical { background: #4a90d9; }
.swatch.markov { background: #e8a838; }
.swatch.chebyshev { background: #d05c5c; }
.swatch.hoeffding { background: #3aaa6a; }
.stats { font-size: .82rem; color: #444; min-height: 2.5em; margin-bottom: .4rem; line-height: 1.5; }
button { font: 600 14px system-ui; padding: .45rem 1rem; background: #1d3557; color: #fff; border: none; border-radius: 8px; cursor: pointer; }
button:hover { background: #274e7a; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Concentration inequalities demo: simulate coin flips and compare
// theoretical tail bounds (Markov, Chebyshev, Hoeffding) to empirical results
const nSlider = document.getElementById('nSlider');
const epsSlider = document.getElementById('epsSlider');
const trialsSlider = document.getElementById('trialsSlider');
const nVal = document.getElementById('nVal');
const epsVal = document.getElementById('epsVal');
const trialsVal = document.getElementById('trialsVal');
const statsEl = document.getElementById('stats');
const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');
// Update displayed values when sliders change
function updateLabels() {
nVal.textContent = nSlider.value;
epsVal.textContent = (epsSlider.value / 100).toFixed(2);
trialsVal.textContent = trialsSlider.value;
}
nSlider.addEventListener('input', updateLabels);
epsSlider.addEventListener('input', updateLabels);
trialsSlider.addEventListener('input', updateLabels);
// Markov's inequality: Pr[X >= mu + eps] <= mu / (mu + eps) for non-negative X
function markovBound(eps) {
// For fraction of heads with mu=0.5, bound on upper tail only
return Math.min(1, 0.5 / (0.5 + eps));
}
// Chebyshev: Pr[|X - mu| >= eps] <= Var(X) / eps^2
function chebyshevBound(n, eps) {
// Variance of average of n Bernoulli(0.5) = 0.25/n
const sigma2 = 0.25 / n;
return Math.min(1, sigma2 / (eps * eps));
}
// Hoeffding: two-sided tail, variables in [0,1], sum of ranges^2 = n
function hoeffdingBound(n, eps) {
// Two-sided: 2 * exp(-2 * n * eps^2)
return 2 * Math.exp(-2 * n * eps * eps);
}
// Run trials coin-flip experiments, return fraction of heads each time
function simulate(n, trials) {
const fracs = [];
for (let t = 0; t < trials; t++) {
let heads = 0;
for (let i = 0; i < n; i++) heads += Math.random() < 0.5 ? 1 : 0;
fracs.push(heads / n);
}
return fracs;
}
// Bin fractions into a histogram with given number of bins
function buildHistogram(fracs, bins) {
const counts = new Array(bins).fill(0);
for (const f of fracs) {
const b = Math.min(bins - 1, Math.floor(f * bins));
counts[b]++;
}
return counts;
}
function run() {
const n = parseInt(nSlider.value);
const eps = parseInt(epsSlider.value) / 100;
const trials = parseInt(trialsSlider.value);
const fracs = simulate(n, trials);
const BINS = 50;
const counts = buildHistogram(fracs, BINS);
const maxCount = Math.max(...counts);
// Draw the histogram and bounds on the canvas
const W = canvas.width;
const H = canvas.height;
const PAD = { top: 10, right: 10, bottom: 28, left: 36 };
const cw = W - PAD.left - PAD.right;
const ch = H - PAD.top - PAD.bottom;
ctx.clearRect(0, 0, W, H);
// Shade the central region [0.5-eps, 0.5+eps] in green
const lo = Math.max(0, 0.5 - eps);
const hi = Math.min(1, 0.5 + eps);
const xLo = PAD.left + lo * cw;
const xHi = PAD.left + hi * cw;
ctx.fillStyle = 'rgba(58,170,106,0.12)';
ctx.fillRect(xLo, PAD.top, xHi - xLo, ch);
// Draw histogram bars
const barW = cw / BINS;
for (let b = 0; b < BINS; b++) {
const h = (counts[b] / maxCount) * ch;
const x = PAD.left + b * barW;
const y = PAD.top + ch - h;
ctx.fillStyle = '#4a90d9';
ctx.fillRect(x + 1, y, barW - 2, h);
}
// Compute and draw the three theoretical bounds as horizontal lines
const mBound = markovBound(eps);
const cBound = chebyshevBound(n, eps);
const hBound = hoeffdingBound(n, eps);
function drawHLine(prob, color, label) {
// Draw a horizontal dashed line at a given tail-probability level
const yp = PAD.top + ch * (1 - Math.min(prob, 1));
ctx.strokeStyle = color;
ctx.lineWidth = 2;
ctx.setLineDash([5, 3]);
ctx.beginPath();
ctx.moveTo(PAD.left, yp);
ctx.lineTo(PAD.left + cw, yp);
ctx.stroke();
ctx.setLineDash([]);
ctx.fillStyle = color;
ctx.font = '11px system-ui';
ctx.fillText(label, PAD.left + cw - 4, yp - 3);
}
// Draw axes
ctx.strokeStyle = '#bbb';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(PAD.left, PAD.top);
ctx.lineTo(PAD.left, PAD.top + ch);
ctx.lineTo(PAD.left + cw, PAD.top + ch);
ctx.stroke();
// x-axis labels: fraction of heads from 0 to 1
ctx.fillStyle = '#666';
ctx.font = '10px system-ui';
for (let v = 0; v <= 1; v += 0.25) {
const x = PAD.left + v * cw;
ctx.fillText(v.toFixed(2), x - 8, PAD.top + ch + 16);
}
// y-axis labels: probability density (relative)
for (let p = 0; p <= 1; p += 0.25) {
const y = PAD.top + ch * (1 - p);
ctx.fillStyle = '#999';
ctx.font = '9px system-ui';
ctx.textAlign = 'right';
ctx.fillText(p.toFixed(2), PAD.left - 3, y + 3);
}
ctx.textAlign = 'left';
drawHLine(mBound, '#e8a838', 'M');
drawHLine(cBound, '#d05c5c', 'C');
drawHLine(hBound, '#3aaa6a', 'H');
// Count how many trials fell outside the epsilon band
const outside = fracs.filter(f => Math.abs(f - 0.5) > eps).length;
const empTail = outside / trials;
statsEl.innerHTML =
fmt('<b>n=%n%, ε=%eps%, trials=%trials%</b><br>Empirical outside: <b>%v%%</b> | Markov: <b>%v2%%</b> | Chebyshev: <b>%v3%%</b> | Hoeffding: <b>%v4%%</b>', { n: n, eps: eps.toFixed(2), trials: trials, v: (empTail * 100).toFixed(1), v2: (mBound * 100).toFixed(1), v3: (cBound * 100).toFixed(1), v4: (hBound * 100).toFixed(1) });
console.log('Simulation results:', { n, eps, trials, empTail: empTail.toFixed(4), markov: mBound.toFixed(4), chebyshev: cBound.toFixed(4), hoeffding: hBound.toFixed(4) });
}
document.getElementById('runBtn').addEventListener('click', run);
run();
Comments
Loading comments...