Below is a simulation of the quantum counting idea. Choose how many items are marked out of a database of N=64 items, then watch how the phase angle θ is estimated and converted back to a count M^.
<!-- Quantum Counting demo â phase estimation on the Grover operator -->
<p class="hint">Choose how many items are <b>marked</b> out of 64, then click <b>Estimate Count</b>. Phase estimation reads the Grover rotation angle θ where sin²θ = M/N, recovering M in O(âN) oracle calls instead of O(N).</p>
<div class="controls">
<label for="marked-slider">Marked items M = <strong id="marked-val">8</strong> / 64</label>
<input type="range" id="marked-slider" min="1" max="63" value="8" step="1">
</div>
<div class="controls">
<label for="ancilla-slider">Ancilla qubits t = <strong id="ancilla-val">6</strong></label>
<input type="range" id="ancilla-slider" min="3" max="8" value="6" step="1">
</div>
<button id="run-btn" type="button">Estimate Count</button>
<div id="result-area" class="result-area" aria-live="polite"></div>
<div id="chart-area" class="chart-area"></div>
/* Base layout and controls */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: .5rem; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.controls { display: flex; align-items: center; gap: .7rem; margin: .4rem 0; flex-wrap: wrap; }
.controls label { font-size: .9rem; min-width: 14rem; }
input[type=range] { flex: 1; min-width: 120px; accent-color: #1d3557; }
button { font: 600 14px system-ui; padding: .45rem 1rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; margin-top: .4rem; }
button:hover { background: #2b4d73; }
.result-area { margin: .7rem 0 .3rem; font-size: .95rem; min-height: 2.4em; }
.result-area .row { margin: .15rem 0; }
.ok { color: #0a7d33; font-weight: 600; }
.formula { font-family: ui-monospace, monospace; font-size: .85rem; color: #555; }
/* Bar chart comparing query counts */
.chart-area { margin-top: .5rem; overflow-x: auto; }
.chart-area svg { display: block; }
.bar-exact { fill: #1d3557; }
.bar-estimate { fill: #e63946; }
.axis { stroke: #bbb; stroke-width: 1; }
.tick-label { font-size: 10px; fill: #666; }
.legend { font-size: 11px; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Quantum Counting (BrassardâHøyerâTapp 1998):
// Run phase estimation on Grover operator G = -H I_0 H I_f.
// G has eigenphases ¹2θ where sin²(θ) = M/N.
// Measuring θ via QPE gives M in O(sqrt(N)/epsilon) queries.
const N = 64; // database size â fixed for this demo
// Compute the exact Grover rotation angle from M and N
function exactTheta(M, N) {
return Math.asin(Math.sqrt(M / N));
}
// Simulate QPE measurement: the ideal output is theta/pi as a t-bit fraction.
// We add small Gaussian-like noise to model finite precision.
function simulatePhaseEstimation(M, t) {
// Grover eigenphase
const theta = exactTheta(M, N);
// QPE measures the closest t-bit fraction to theta/pi
// phase eigenvalue of G is exp(Âą2i*theta); QPE measures k/2^t â theta/pi
const trueFrac = theta / Math.PI;
// Add finite-precision noise proportional to 1/2^t
const noise = (Math.random() - 0.5) * (0.6 / Math.pow(2, t));
const measFrac = Math.max(0, Math.min(0.5 - 1e-9, trueFrac + noise));
return measFrac;
}
// Convert measured fraction back to an integer count
function fracToM(frac, N) {
const theta = frac * Math.PI;
return Math.round(N * Math.sin(theta) * Math.sin(theta));
}
// Classical sample complexity to count with relative error eps
function classicalQueries(N, M, eps) {
// classical: O(N / (M * eps^2)) â proportional to N/M
return Math.ceil(N / (M * eps * eps));
}
// Quantum query complexity for counting
function quantumQueries(N, M, eps) {
// quantum: O(sqrt(N) / eps) â independent of M
return Math.ceil(Math.sqrt(N) / eps);
}
function drawChart(M, Mest) {
const area = document.getElementById('chart-area');
const W = Math.min(area.clientWidth || 400, 460);
const H = 110;
const pad = { l: 50, r: 10, t: 20, b: 30 };
const innerW = W - pad.l - pad.r;
// Build bar chart comparing quantum vs classical query counts
const eps = 0.1;
const qQ = quantumQueries(N, M, eps);
const cQ = classicalQueries(N, M, eps);
const maxQ = Math.max(qQ, cQ, 1);
const scale = innerW / maxQ;
let svg = `<svg width="${W}" height="${H}" viewBox="0 0 ${W} ${H}" xmlns="http://www.w3.org/2000/svg">`;
// Draw axes
svg += `<line class="axis" x1="${pad.l}" y1="${pad.t}" x2="${pad.l}" y2="${H - pad.b}"/>`;
svg += `<line class="axis" x1="${pad.l}" y1="${H - pad.b}" x2="${W - pad.r}" y2="${H - pad.b}"/>`;
// Quantum bar (blue)
const bH = 22, gap = 8;
const y1 = pad.t + 4;
svg += `<rect class="bar-exact" x="${pad.l + 1}" y="${y1}" width="${Math.max(2, qQ * scale)}" height="${bH}"/>`;
svg += fmt('<text class="tick-label" x="%v%" y="%v2%" dominant-baseline="middle">Quantum (%qQ%)</text>', { v: pad.l + Math.max(2, qQ * scale) + 4, v2: y1 + bH - 6, qQ: qQ });
// Classical bar (red)
const y2 = y1 + bH + gap;
svg += `<rect class="bar-estimate" x="${pad.l + 1}" y="${y2}" width="${Math.min(innerW, Math.max(2, cQ * scale))}" height="${bH}"/>`;
svg += fmt('<text class="tick-label" x="%v%" y="%v2%" dominant-baseline="middle">Classical (%cQ%)</text>', { v: pad.l + Math.min(innerW, Math.max(2, cQ * scale)) + 4, v2: y2 + bH - 6, cQ: cQ });
// Label x-axis
svg += `<text class="tick-label" x="${pad.l}" y="${H - 4}">0</text>`;
svg += fmt('<text class="tick-label" x="%v%" y="%v2%">queries â</text>', { v: W - pad.r - 30, v2: H - 4 });
svg += '</svg>';
area.innerHTML = svg;
}
function run() {
const M = parseInt(document.getElementById('marked-slider').value, 10);
const t = parseInt(document.getElementById('ancilla-slider').value, 10);
const frac = simulatePhaseEstimation(M, t);
const Mest = fracToM(frac, N);
const theta = frac * Math.PI;
const thetaDeg = (theta * 180 / Math.PI).toFixed(2);
const area = document.getElementById('result-area');
area.innerHTML =
fmt('<div class="row">True M: <strong>%M%</strong> | Estimated MĚ: <strong class="ok">%Mest%</strong></div><div class="row formula">θ â %thetaDeg%° (sin²θ = %v%, M/N = %v2%)</div><div class="row formula">Quantum oracle calls needed: ~%ceil% (classical would need ~%N%)</div>', { M: M, Mest: Mest, thetaDeg: thetaDeg, v: (Math.sin(theta) ** 2).toFixed(4), v2: (M / N).toFixed(4), ceil: Math.ceil(Math.sqrt(N)), N: N });
drawChart(M, Mest);
console.log('Quantum counting run:', { M, Mest, thetaDeg, t });
}
// Sync slider display values
document.getElementById('marked-slider').addEventListener('input', function() {
document.getElementById('marked-val').textContent = this.value;
});
document.getElementById('ancilla-slider').addEventListener('input', function() {
document.getElementById('ancilla-val').textContent = this.value;
});
document.getElementById('run-btn').addEventListener('click', run);
run(); // Run once on load
<!-- Quantum Counting demo â phase estimation on the Grover operator -->
<p class="hint">Choose how many items are <b>marked</b> out of 64, then click <b>Estimate Count</b>. Phase estimation reads the Grover rotation angle θ where sin²θ = M/N, recovering M in O(âN) oracle calls instead of O(N).</p>
<div class="controls">
<label for="marked-slider">Marked items M = <strong id="marked-val">8</strong> / 64</label>
<input type="range" id="marked-slider" min="1" max="63" value="8" step="1">
</div>
<div class="controls">
<label for="ancilla-slider">Ancilla qubits t = <strong id="ancilla-val">6</strong></label>
<input type="range" id="ancilla-slider" min="3" max="8" value="6" step="1">
</div>
<button id="run-btn" type="button">Estimate Count</button>
<div id="result-area" class="result-area" aria-live="polite"></div>
<div id="chart-area" class="chart-area"></div>
/* Base layout and controls */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: .5rem; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.controls { display: flex; align-items: center; gap: .7rem; margin: .4rem 0; flex-wrap: wrap; }
.controls label { font-size: .9rem; min-width: 14rem; }
input[type=range] { flex: 1; min-width: 120px; accent-color: #1d3557; }
button { font: 600 14px system-ui; padding: .45rem 1rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; margin-top: .4rem; }
button:hover { background: #2b4d73; }
.result-area { margin: .7rem 0 .3rem; font-size: .95rem; min-height: 2.4em; }
.result-area .row { margin: .15rem 0; }
.ok { color: #0a7d33; font-weight: 600; }
.formula { font-family: ui-monospace, monospace; font-size: .85rem; color: #555; }
/* Bar chart comparing query counts */
.chart-area { margin-top: .5rem; overflow-x: auto; }
.chart-area svg { display: block; }
.bar-exact { fill: #1d3557; }
.bar-estimate { fill: #e63946; }
.axis { stroke: #bbb; stroke-width: 1; }
.tick-label { font-size: 10px; fill: #666; }
.legend { font-size: 11px; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Quantum Counting (BrassardâHøyerâTapp 1998):
// Run phase estimation on Grover operator G = -H I_0 H I_f.
// G has eigenphases ¹2θ where sin²(θ) = M/N.
// Measuring θ via QPE gives M in O(sqrt(N)/epsilon) queries.
const N = 64; // database size â fixed for this demo
// Compute the exact Grover rotation angle from M and N
function exactTheta(M, N) {
return Math.asin(Math.sqrt(M / N));
}
// Simulate QPE measurement: the ideal output is theta/pi as a t-bit fraction.
// We add small Gaussian-like noise to model finite precision.
function simulatePhaseEstimation(M, t) {
// Grover eigenphase
const theta = exactTheta(M, N);
// QPE measures the closest t-bit fraction to theta/pi
// phase eigenvalue of G is exp(Âą2i*theta); QPE measures k/2^t â theta/pi
const trueFrac = theta / Math.PI;
// Add finite-precision noise proportional to 1/2^t
const noise = (Math.random() - 0.5) * (0.6 / Math.pow(2, t));
const measFrac = Math.max(0, Math.min(0.5 - 1e-9, trueFrac + noise));
return measFrac;
}
// Convert measured fraction back to an integer count
function fracToM(frac, N) {
const theta = frac * Math.PI;
return Math.round(N * Math.sin(theta) * Math.sin(theta));
}
// Classical sample complexity to count with relative error eps
function classicalQueries(N, M, eps) {
// classical: O(N / (M * eps^2)) â proportional to N/M
return Math.ceil(N / (M * eps * eps));
}
// Quantum query complexity for counting
function quantumQueries(N, M, eps) {
// quantum: O(sqrt(N) / eps) â independent of M
return Math.ceil(Math.sqrt(N) / eps);
}
function drawChart(M, Mest) {
const area = document.getElementById('chart-area');
const W = Math.min(area.clientWidth || 400, 460);
const H = 110;
const pad = { l: 50, r: 10, t: 20, b: 30 };
const innerW = W - pad.l - pad.r;
// Build bar chart comparing quantum vs classical query counts
const eps = 0.1;
const qQ = quantumQueries(N, M, eps);
const cQ = classicalQueries(N, M, eps);
const maxQ = Math.max(qQ, cQ, 1);
const scale = innerW / maxQ;
let svg = `<svg width="${W}" height="${H}" viewBox="0 0 ${W} ${H}" xmlns="http://www.w3.org/2000/svg">`;
// Draw axes
svg += `<line class="axis" x1="${pad.l}" y1="${pad.t}" x2="${pad.l}" y2="${H - pad.b}"/>`;
svg += `<line class="axis" x1="${pad.l}" y1="${H - pad.b}" x2="${W - pad.r}" y2="${H - pad.b}"/>`;
// Quantum bar (blue)
const bH = 22, gap = 8;
const y1 = pad.t + 4;
svg += `<rect class="bar-exact" x="${pad.l + 1}" y="${y1}" width="${Math.max(2, qQ * scale)}" height="${bH}"/>`;
svg += fmt('<text class="tick-label" x="%v%" y="%v2%" dominant-baseline="middle">Quantum (%qQ%)</text>', { v: pad.l + Math.max(2, qQ * scale) + 4, v2: y1 + bH - 6, qQ: qQ });
// Classical bar (red)
const y2 = y1 + bH + gap;
svg += `<rect class="bar-estimate" x="${pad.l + 1}" y="${y2}" width="${Math.min(innerW, Math.max(2, cQ * scale))}" height="${bH}"/>`;
svg += fmt('<text class="tick-label" x="%v%" y="%v2%" dominant-baseline="middle">Classical (%cQ%)</text>', { v: pad.l + Math.min(innerW, Math.max(2, cQ * scale)) + 4, v2: y2 + bH - 6, cQ: cQ });
// Label x-axis
svg += `<text class="tick-label" x="${pad.l}" y="${H - 4}">0</text>`;
svg += fmt('<text class="tick-label" x="%v%" y="%v2%">queries â</text>', { v: W - pad.r - 30, v2: H - 4 });
svg += '</svg>';
area.innerHTML = svg;
}
function run() {
const M = parseInt(document.getElementById('marked-slider').value, 10);
const t = parseInt(document.getElementById('ancilla-slider').value, 10);
const frac = simulatePhaseEstimation(M, t);
const Mest = fracToM(frac, N);
const theta = frac * Math.PI;
const thetaDeg = (theta * 180 / Math.PI).toFixed(2);
const area = document.getElementById('result-area');
area.innerHTML =
fmt('<div class="row">True M: <strong>%M%</strong> | Estimated MĚ: <strong class="ok">%Mest%</strong></div><div class="row formula">θ â %thetaDeg%° (sin²θ = %v%, M/N = %v2%)</div><div class="row formula">Quantum oracle calls needed: ~%ceil% (classical would need ~%N%)</div>', { M: M, Mest: Mest, thetaDeg: thetaDeg, v: (Math.sin(theta) ** 2).toFixed(4), v2: (M / N).toFixed(4), ceil: Math.ceil(Math.sqrt(N)), N: N });
drawChart(M, Mest);
console.log('Quantum counting run:', { M, Mest, thetaDeg, t });
}
// Sync slider display values
document.getElementById('marked-slider').addEventListener('input', function() {
document.getElementById('marked-val').textContent = this.value;
});
document.getElementById('ancilla-slider').addEventListener('input', function() {
document.getElementById('ancilla-val').textContent = this.value;
});
document.getElementById('run-btn').addEventListener('click', run);
run(); // Run once on load
Comments
Loading comments...