Below are N boxes. One hides the secret item; the rest are empty. You can play Classical — open boxes one at a time until you hit it — or run a Grover step, which nudges every box's amplitude at once so the secret box grows taller while the others shrink.
<p class="hint">One of the <b id="ncount">8</b> boxes hides the secret item. <b>Classical</b>
search opens boxes one by one (up to N tries). A <b>Grover step</b> nudges every
amplitude at once so the secret box grows — after about √N steps it is nearly
certain. Checking a box is instant; finding it classically is slow.</p>
<div id="bars" class="bars"></div>
<div class="status" id="status">Pick the secret box, then search.</div>
<div class="btns">
<button id="grover" type="button">Grover step</button>
<button id="classical" type="button">Classical open</button>
<button id="measure" type="button">Measure</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
<div class="meta" id="meta"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .7rem; line-height: 1.45; }
.bars { display: flex; align-items: flex-end; gap: 6px; height: 170px;
padding: 6px; background: #f4f7fa; border: 1px solid #dde5ec; border-radius: 8px; }
.bar { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
height: 100%; cursor: pointer; }
.fill { width: 100%; background: #6b87a3; border-radius: 4px 4px 0 0; transition: height .25s ease, background .2s; min-height: 2px; }
.bar.secret .fill { background: #2a9d4a; }
.bar.opened .fill { outline: 2px solid #c92f3c; }
.bar .lbl { font: 600 11px ui-monospace, monospace; color: #5a6b7c; margin-top: 3px; }
.status { font-size: 1rem; font-weight: 600; margin: .6rem 0 .4rem; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
.meta { font-size: .82rem; color: #5a6b7c; margin-top: .5rem; min-height: 1.2em; }
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] ?? ''));
// Grover's algorithm as amplitude amplification over N basis states.
// State = real amplitude per box; probability = amplitude^2. One marked (secret) box.
// A Grover iteration = oracle (flip the secret box's sign) then diffusion
// (reflect every amplitude about the mean). The marked amplitude grows for ~ (pi/4)*sqrt(N)
// steps, then OVERSHOOTS. Classical search instead opens boxes one at a time (up to N).
const N = 8;
const secret = Math.floor(Math.random() * N);
let amp = new Array(N).fill(1 / Math.sqrt(N)); // uniform superposition
let steps = 0, opened = new Set(), done = false;
const optimal = Math.round((Math.PI / 4) * Math.sqrt(N));
document.getElementById('ncount').textContent = N;
function oracle(a){ const b=a.slice(); b[secret] = -b[secret]; return b; }
function diffusion(a){ const mean=a.reduce((s,x)=>s+x,0)/a.length;
return a.map(x => 2*mean - x); } // reflect about the mean
function groverStep(){ amp = diffusion(oracle(amp)); steps++; }
function probs(){ return amp.map(x => x*x); }
function render(){
const wrap = document.getElementById('bars'); wrap.innerHTML='';
const p = probs();
for(let i=0;i<N;i++){
const bar=document.createElement('div');
bar.className='bar'+(opened.has(i)?' opened':'')+(done&&i===secret?' secret':'');
const fill=document.createElement('div'); fill.className='fill';
fill.style.height = Math.max(2, Math.round(p[i]*150)) + 'px';
const lbl=document.createElement('div'); lbl.className='lbl'; lbl.textContent='#'+i;
bar.appendChild(fill); bar.appendChild(lbl);
bar.addEventListener('click',()=>classicalOpen(i));
wrap.appendChild(bar);
}
const p2 = (probs()[secret]*100).toFixed(1);
document.getElementById('meta').textContent =
fmt('Grover steps: %steps% (optimal ≈ %optimal%) | secret-box probability: %p2%% | classical opens: %size%', { steps: steps, optimal: optimal, p2: p2, size: opened.size });
}
function setStatus(t,cls){ const s=document.getElementById('status'); s.textContent=t; s.className='status '+cls; }
function grover(){ if(done) return;
groverStep(); render();
const p = probs()[secret];
if(steps===optimal) setStatus(fmt('Near the sweet spot (%steps% steps). Measure now!', { steps: steps }),'ok');
else if(steps>optimal) setStatus('Overshot — the secret box is shrinking again. Reset to retry.','bad');
else setStatus(fmt('Step %steps%: secret amplitude is climbing.', { steps: steps }),'');
}
function classicalOpen(i){ if(done) return;
opened.add(i); render();
if(i===secret){ done=true; render();
setStatus(fmt('Classical hit box #%i% after %size% opens. Worst case is N=%N%.', { i: i, size: opened.size, N: N }),'ok'); }
else setStatus(fmt('Box #%i% is empty. Classical search keeps opening...', { i: i }),'bad');
}
function measure(){ if(done) return;
const p = probs(); let r=Math.random(), acc=0, pick=0;
for(let i=0;i<N;i++){ acc+=p[i]; if(r<=acc){ pick=i; break; } }
done=true; opened.add(pick); render();
if(pick===secret) setStatus(fmt('Measured box #%pick% — correct! (prob was %v%%)', { pick: pick, v: (p[secret]*100).toFixed(1) }),'ok');
else setStatus(fmt('Measured box #%pick% — wrong. More Grover steps raise the odds first.', { pick: pick }),'bad');
}
document.getElementById('grover').addEventListener('click',grover);
document.getElementById('classical').addEventListener('click',()=>{
// open the first not-yet-opened box, scanning left to right
for(let i=0;i<N;i++){ if(!opened.has(i)){ classicalOpen(i); return; } }
});
document.getElementById('measure').addEventListener('click',measure);
document.getElementById('reset').addEventListener('click',()=>{
amp=new Array(N).fill(1/Math.sqrt(N)); steps=0; opened=new Set(); done=false;
setStatus('Reset. New search ready.',''); render();
});
render();
<p class="hint">One of the <b id="ncount">8</b> boxes hides the secret item. <b>Classical</b>
search opens boxes one by one (up to N tries). A <b>Grover step</b> nudges every
amplitude at once so the secret box grows — after about √N steps it is nearly
certain. Checking a box is instant; finding it classically is slow.</p>
<div id="bars" class="bars"></div>
<div class="status" id="status">Pick the secret box, then search.</div>
<div class="btns">
<button id="grover" type="button">Grover step</button>
<button id="classical" type="button">Classical open</button>
<button id="measure" type="button">Measure</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
<div class="meta" id="meta"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .7rem; line-height: 1.45; }
.bars { display: flex; align-items: flex-end; gap: 6px; height: 170px;
padding: 6px; background: #f4f7fa; border: 1px solid #dde5ec; border-radius: 8px; }
.bar { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
height: 100%; cursor: pointer; }
.fill { width: 100%; background: #6b87a3; border-radius: 4px 4px 0 0; transition: height .25s ease, background .2s; min-height: 2px; }
.bar.secret .fill { background: #2a9d4a; }
.bar.opened .fill { outline: 2px solid #c92f3c; }
.bar .lbl { font: 600 11px ui-monospace, monospace; color: #5a6b7c; margin-top: 3px; }
.status { font-size: 1rem; font-weight: 600; margin: .6rem 0 .4rem; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
.meta { font-size: .82rem; color: #5a6b7c; margin-top: .5rem; min-height: 1.2em; }
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] ?? ''));
// Grover's algorithm as amplitude amplification over N basis states.
// State = real amplitude per box; probability = amplitude^2. One marked (secret) box.
// A Grover iteration = oracle (flip the secret box's sign) then diffusion
// (reflect every amplitude about the mean). The marked amplitude grows for ~ (pi/4)*sqrt(N)
// steps, then OVERSHOOTS. Classical search instead opens boxes one at a time (up to N).
const N = 8;
const secret = Math.floor(Math.random() * N);
let amp = new Array(N).fill(1 / Math.sqrt(N)); // uniform superposition
let steps = 0, opened = new Set(), done = false;
const optimal = Math.round((Math.PI / 4) * Math.sqrt(N));
document.getElementById('ncount').textContent = N;
function oracle(a){ const b=a.slice(); b[secret] = -b[secret]; return b; }
function diffusion(a){ const mean=a.reduce((s,x)=>s+x,0)/a.length;
return a.map(x => 2*mean - x); } // reflect about the mean
function groverStep(){ amp = diffusion(oracle(amp)); steps++; }
function probs(){ return amp.map(x => x*x); }
function render(){
const wrap = document.getElementById('bars'); wrap.innerHTML='';
const p = probs();
for(let i=0;i<N;i++){
const bar=document.createElement('div');
bar.className='bar'+(opened.has(i)?' opened':'')+(done&&i===secret?' secret':'');
const fill=document.createElement('div'); fill.className='fill';
fill.style.height = Math.max(2, Math.round(p[i]*150)) + 'px';
const lbl=document.createElement('div'); lbl.className='lbl'; lbl.textContent='#'+i;
bar.appendChild(fill); bar.appendChild(lbl);
bar.addEventListener('click',()=>classicalOpen(i));
wrap.appendChild(bar);
}
const p2 = (probs()[secret]*100).toFixed(1);
document.getElementById('meta').textContent =
fmt('Grover steps: %steps% (optimal ≈ %optimal%) | secret-box probability: %p2%% | classical opens: %size%', { steps: steps, optimal: optimal, p2: p2, size: opened.size });
}
function setStatus(t,cls){ const s=document.getElementById('status'); s.textContent=t; s.className='status '+cls; }
function grover(){ if(done) return;
groverStep(); render();
const p = probs()[secret];
if(steps===optimal) setStatus(fmt('Near the sweet spot (%steps% steps). Measure now!', { steps: steps }),'ok');
else if(steps>optimal) setStatus('Overshot — the secret box is shrinking again. Reset to retry.','bad');
else setStatus(fmt('Step %steps%: secret amplitude is climbing.', { steps: steps }),'');
}
function classicalOpen(i){ if(done) return;
opened.add(i); render();
if(i===secret){ done=true; render();
setStatus(fmt('Classical hit box #%i% after %size% opens. Worst case is N=%N%.', { i: i, size: opened.size, N: N }),'ok'); }
else setStatus(fmt('Box #%i% is empty. Classical search keeps opening...', { i: i }),'bad');
}
function measure(){ if(done) return;
const p = probs(); let r=Math.random(), acc=0, pick=0;
for(let i=0;i<N;i++){ acc+=p[i]; if(r<=acc){ pick=i; break; } }
done=true; opened.add(pick); render();
if(pick===secret) setStatus(fmt('Measured box #%pick% — correct! (prob was %v%%)', { pick: pick, v: (p[secret]*100).toFixed(1) }),'ok');
else setStatus(fmt('Measured box #%pick% — wrong. More Grover steps raise the odds first.', { pick: pick }),'bad');
}
document.getElementById('grover').addEventListener('click',grover);
document.getElementById('classical').addEventListener('click',()=>{
// open the first not-yet-opened box, scanning left to right
for(let i=0;i<N;i++){ if(!opened.has(i)){ classicalOpen(i); return; } }
});
document.getElementById('measure').addEventListener('click',measure);
document.getElementById('reset').addEventListener('click',()=>{
amp=new Array(N).fill(1/Math.sqrt(N)); steps=0; opened=new Set(); done=false;
setStatus('Reset. New search ready.',''); render();
});
render();
Notice the rhythm. Each Grover step flips the answer's sign (the oracle) and then reflects everything about the average (the diffusion), pumping probability into the right box. After about √N steps the secret box is nearly certain — measure now and you almost surely win. Push too far and it overshoots and shrinks again: timing is everything. Meanwhile checking a candidate is instant — the oracle just says yes or no — but finding it classically still costs up to N opens.
Comments
Loading comments...