Below is a faithful classical simulation of QPE applied to a single-qubit phase gate U with U∣1⟩=e2πiφ∣1⟩. The eigenstate is ∣1⟩ and the eigenphase is φ.
Choose a target phase φ (as a fraction of a full turn) and a number of ancilla qubits n. The algorithm will estimate φ to n binary digits of precision.
<p class="hint">QPE applies a phase gate U where U|1⟩ = e<sup>2πiφ</sup>|1⟩ and reads φ as a binary fraction using n ancilla qubits and the quantum Fourier transform. Choose φ and n, then run the algorithm.</p>
<div class="controls">
<label>Phase φ = <span id="phi-val">0.375</span>
<input id="phi-slider" type="range" min="0" max="100" value="37" step="1">
</label>
<label>Ancilla qubits n = <span id="n-val">3</span>
<input id="n-slider" type="range" min="1" max="6" value="3" step="1">
</label>
</div>
<div id="register" class="register"></div>
<div id="result" class="result"></div>
<div class="btns">
<button id="run" type="button">Run QPE</button>
<button id="reset" 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: .5rem; margin-bottom: .9rem; }
label { font-size: .9rem; display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
input[type=range] { width: 180px; accent-color: #1d3557; }
span { font-weight: 700; color: #1d3557; min-width: 3.5ch; display: inline-block; }
.register { display: flex; gap: 6px; flex-wrap: wrap; margin: .5rem 0; min-height: 58px; align-items: center; }
.qubit { width: 50px; height: 50px; border-radius: 10px; border: 1.5px solid #adb1b8;
background: #e8eef3; display: flex; flex-direction: column; align-items: center;
justify-content: center; font-size: .72rem; color: #444; transition: all .3s; }
.qubit .bit { font: 700 18px ui-monospace, monospace; color: #1d3557; }
.qubit.active { background: #1d3557; border-color: #1d3557; }
.qubit.active .bit { color: #fff; }
.qubit.active { color: #9fb2c8; }
.result { background: #f0f4f8; border-radius: 10px; padding: .7rem 1rem; font-size: .92rem;
line-height: 1.6; min-height: 3.5em; margin: .5rem 0; }
.result .exact { color: #0a7d33; font-weight: 700; }
.result .approx { color: #c97000; font-weight: 700; }
.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; }
.bar-row { display: flex; align-items: center; gap: 6px; margin-top: .4rem; }
.bar-label { font-size: .75rem; color: #555; min-width: 5ch; text-align: right; }
.bar-bg { flex: 1; height: 10px; background: #dde3e9; border-radius: 5px; overflow: hidden; }
.bar-fill { height: 100%; background: #1d3557; border-radius: 5px; transition: width .4s; }
.bar-fill.peak { background: #e63946; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Quantum Phase Estimation simulation
// U is a 1-qubit phase gate: U|0⟩=|0⟩, U|1⟩=e^(2πiφ)|1⟩
// eigenstate = |1⟩, eigenphase = φ
//
// Algorithm (n ancilla qubits):
// 1. Prepare ancilla in |+⟩^n = H^n |0...0⟩
// 2. Apply controlled-U^(2^k) for k=0..n-1 (phase kickback: ancilla qubit k picks up e^(2πi·2^k·φ))
// 3. Apply inverse QFT to ancilla register
// 4. Measure: result is the n-bit binary fraction closest to φ
//
// Classical simulation: the state after step 2 is
// (1/√2ⁿ) Σ_{j=0}^{2ⁿ-1} e^(2πi·j·φ) |j⟩
// QFT†: state becomes Σ_k A_k |k⟩ where
// A_k = (1/2ⁿ) Σ_j e^(2πij(φ - k/2ⁿ))
// |A_k|² = (1/2^(2n)) |Σ_j e^(2πij(φ-k/2ⁿ))|²
// For φ = m/2ⁿ exactly: |A_m|² = 1, all others 0.
const phiSlider = document.getElementById('phi-slider');
const nSlider = document.getElementById('n-slider');
const phiVal = document.getElementById('phi-val');
const nVal = document.getElementById('n-val');
const regDiv = document.getElementById('register');
const resultDiv = document.getElementById('result');
let phi = 0.375, n = 3;
function updateLabels() {
phi = parseFloat(phiSlider.value) / 100;
n = parseInt(nSlider.value);
phiVal.textContent = phi.toFixed(3);
nVal.textContent = n;
}
phiSlider.addEventListener('input', () => { updateLabels(); renderRegister(); resultDiv.innerHTML = ''; });
nSlider.addEventListener('input', () => { updateLabels(); renderRegister(); resultDiv.innerHTML = ''; });
function renderRegister() {
regDiv.innerHTML = '';
for (let k = 0; k < n; k++) {
const d = document.createElement('div');
d.className = 'qubit';
d.id = 'q' + k;
d.innerHTML = '<span class="bit">?</span><span>q' + k + '</span>';
regDiv.appendChild(d);
}
}
// Compute QPE probability distribution over 2^n outcomes
function qpeDist(phi, n) {
const N = 1 << n; // 2^n
const probs = new Float64Array(N);
for (let k = 0; k < N; k++) {
// |A_k|^2 = (1/N^2) * |sum_{j=0}^{N-1} e^{2πij(φ - k/N)}|^2
const delta = phi - k / N;
if (Math.abs(delta % 1) < 1e-12 || Math.abs((delta % 1) - 1) < 1e-12) {
probs[k] = (Math.abs(delta) < 1e-9 || Math.abs(delta - Math.round(delta)) < 1e-9) ? 1.0 : 0.0;
} else {
// geometric sum: (1 - e^{2πiN·delta}) / (1 - e^{2πi·delta})
const arg = 2 * Math.PI * delta;
const argN = arg * N;
// |sin(Nπδ) / sin(πδ)|^2 / N^2
const num = Math.sin(N * Math.PI * delta);
const den = Math.sin(Math.PI * delta);
probs[k] = (num * num) / (N * N * den * den);
}
}
return probs;
}
function runQPE() {
updateLabels();
const N = 1 << n;
const probs = qpeDist(phi, n);
// Find peak outcome
let peakIdx = 0;
for (let k = 1; k < N; k++) if (probs[k] > probs[peakIdx]) peakIdx = k;
// Estimated phase = peakIdx / 2^n
const estPhi = peakIdx / N;
const error = Math.abs(phi - estPhi);
const maxErr = 1 / N;
// Animate qubits with the binary bits of peakIdx
for (let k = 0; k < n; k++) {
const bit = (peakIdx >> (n - 1 - k)) & 1;
const qDiv = document.getElementById('q' + k);
if (qDiv) {
qDiv.querySelector('.bit').textContent = bit;
qDiv.className = 'qubit' + (bit ? ' active' : '');
}
}
// Build binary fraction string
let binFrac = '0.';
for (let k = 0; k < n; k++) binFrac += ((peakIdx >> (n - 1 - k)) & 1);
const isExact = error < 1e-9;
const successProb = probs[peakIdx].toFixed(4);
const cls = isExact ? 'exact' : 'approx';
const label = isExact ? `Exact!` : `Nearest representable`;
// Build probability bars (top outcomes)
const indexed = Array.from(probs).map((p, i) => ({ i, p })).sort((a, b) => b.p - a.p);
const top = indexed.slice(0, Math.min(4, N));
let bars = '';
for (const { i, p } of top) {
if (p < 0.001) break;
const pct = (p * 100).toFixed(1);
const isPeak = i === peakIdx;
const bf = '0.' + i.toString(2).padStart(n, '0');
bars += `<div class="bar-row">
<span class="bar-label">${bf}</span>
<div class="bar-bg"><div class="bar-fill${isPeak ? ' peak' : ''}" style="width:${pct}%"></div></div>
<span style="min-width:4ch;font-size:.75rem">${pct}%</span>
</div>`;
}
resultDiv.innerHTML =
fmt(`QPE reads binary register <b>%binFrac%</b> = <b>%estPhi%</b>
<span class="%cls%">[%label%]</span><br>
True φ = %phi% | Error ≤ 1/2<sup>n</sup> = 1/2<sup>%n%</sup> = %maxErr%<br>
Peak probability: %v%%<br>
%bars%`, { binFrac: binFrac, estPhi: estPhi.toFixed(6), cls: cls, label: label, phi: phi.toFixed(6), n: n, maxErr: maxErr.toFixed(6), v: (parseFloat(successProb)*100).toFixed(1), bars: bars });
console.log(fmt(`QPE result: peak=%peakIdx% estPhi=%estPhi% error=%error% prob=%successProb%`, { peakIdx: peakIdx, estPhi: estPhi, error: error, successProb: successProb }));
}
document.getElementById('run').addEventListener('click', runQPE);
document.getElementById('reset').addEventListener('click', () => {
phiSlider.value = 37; nSlider.value = 3;
updateLabels(); renderRegister(); resultDiv.innerHTML = '';
});
updateLabels();
renderRegister();
<p class="hint">QPE applies a phase gate U where U|1⟩ = e<sup>2πiφ</sup>|1⟩ and reads φ as a binary fraction using n ancilla qubits and the quantum Fourier transform. Choose φ and n, then run the algorithm.</p>
<div class="controls">
<label>Phase φ = <span id="phi-val">0.375</span>
<input id="phi-slider" type="range" min="0" max="100" value="37" step="1">
</label>
<label>Ancilla qubits n = <span id="n-val">3</span>
<input id="n-slider" type="range" min="1" max="6" value="3" step="1">
</label>
</div>
<div id="register" class="register"></div>
<div id="result" class="result"></div>
<div class="btns">
<button id="run" type="button">Run QPE</button>
<button id="reset" 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: .5rem; margin-bottom: .9rem; }
label { font-size: .9rem; display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
input[type=range] { width: 180px; accent-color: #1d3557; }
span { font-weight: 700; color: #1d3557; min-width: 3.5ch; display: inline-block; }
.register { display: flex; gap: 6px; flex-wrap: wrap; margin: .5rem 0; min-height: 58px; align-items: center; }
.qubit { width: 50px; height: 50px; border-radius: 10px; border: 1.5px solid #adb1b8;
background: #e8eef3; display: flex; flex-direction: column; align-items: center;
justify-content: center; font-size: .72rem; color: #444; transition: all .3s; }
.qubit .bit { font: 700 18px ui-monospace, monospace; color: #1d3557; }
.qubit.active { background: #1d3557; border-color: #1d3557; }
.qubit.active .bit { color: #fff; }
.qubit.active { color: #9fb2c8; }
.result { background: #f0f4f8; border-radius: 10px; padding: .7rem 1rem; font-size: .92rem;
line-height: 1.6; min-height: 3.5em; margin: .5rem 0; }
.result .exact { color: #0a7d33; font-weight: 700; }
.result .approx { color: #c97000; font-weight: 700; }
.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; }
.bar-row { display: flex; align-items: center; gap: 6px; margin-top: .4rem; }
.bar-label { font-size: .75rem; color: #555; min-width: 5ch; text-align: right; }
.bar-bg { flex: 1; height: 10px; background: #dde3e9; border-radius: 5px; overflow: hidden; }
.bar-fill { height: 100%; background: #1d3557; border-radius: 5px; transition: width .4s; }
.bar-fill.peak { background: #e63946; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Quantum Phase Estimation simulation
// U is a 1-qubit phase gate: U|0⟩=|0⟩, U|1⟩=e^(2πiφ)|1⟩
// eigenstate = |1⟩, eigenphase = φ
//
// Algorithm (n ancilla qubits):
// 1. Prepare ancilla in |+⟩^n = H^n |0...0⟩
// 2. Apply controlled-U^(2^k) for k=0..n-1 (phase kickback: ancilla qubit k picks up e^(2πi·2^k·φ))
// 3. Apply inverse QFT to ancilla register
// 4. Measure: result is the n-bit binary fraction closest to φ
//
// Classical simulation: the state after step 2 is
// (1/√2ⁿ) Σ_{j=0}^{2ⁿ-1} e^(2πi·j·φ) |j⟩
// QFT†: state becomes Σ_k A_k |k⟩ where
// A_k = (1/2ⁿ) Σ_j e^(2πij(φ - k/2ⁿ))
// |A_k|² = (1/2^(2n)) |Σ_j e^(2πij(φ-k/2ⁿ))|²
// For φ = m/2ⁿ exactly: |A_m|² = 1, all others 0.
const phiSlider = document.getElementById('phi-slider');
const nSlider = document.getElementById('n-slider');
const phiVal = document.getElementById('phi-val');
const nVal = document.getElementById('n-val');
const regDiv = document.getElementById('register');
const resultDiv = document.getElementById('result');
let phi = 0.375, n = 3;
function updateLabels() {
phi = parseFloat(phiSlider.value) / 100;
n = parseInt(nSlider.value);
phiVal.textContent = phi.toFixed(3);
nVal.textContent = n;
}
phiSlider.addEventListener('input', () => { updateLabels(); renderRegister(); resultDiv.innerHTML = ''; });
nSlider.addEventListener('input', () => { updateLabels(); renderRegister(); resultDiv.innerHTML = ''; });
function renderRegister() {
regDiv.innerHTML = '';
for (let k = 0; k < n; k++) {
const d = document.createElement('div');
d.className = 'qubit';
d.id = 'q' + k;
d.innerHTML = '<span class="bit">?</span><span>q' + k + '</span>';
regDiv.appendChild(d);
}
}
// Compute QPE probability distribution over 2^n outcomes
function qpeDist(phi, n) {
const N = 1 << n; // 2^n
const probs = new Float64Array(N);
for (let k = 0; k < N; k++) {
// |A_k|^2 = (1/N^2) * |sum_{j=0}^{N-1} e^{2πij(φ - k/N)}|^2
const delta = phi - k / N;
if (Math.abs(delta % 1) < 1e-12 || Math.abs((delta % 1) - 1) < 1e-12) {
probs[k] = (Math.abs(delta) < 1e-9 || Math.abs(delta - Math.round(delta)) < 1e-9) ? 1.0 : 0.0;
} else {
// geometric sum: (1 - e^{2πiN·delta}) / (1 - e^{2πi·delta})
const arg = 2 * Math.PI * delta;
const argN = arg * N;
// |sin(Nπδ) / sin(πδ)|^2 / N^2
const num = Math.sin(N * Math.PI * delta);
const den = Math.sin(Math.PI * delta);
probs[k] = (num * num) / (N * N * den * den);
}
}
return probs;
}
function runQPE() {
updateLabels();
const N = 1 << n;
const probs = qpeDist(phi, n);
// Find peak outcome
let peakIdx = 0;
for (let k = 1; k < N; k++) if (probs[k] > probs[peakIdx]) peakIdx = k;
// Estimated phase = peakIdx / 2^n
const estPhi = peakIdx / N;
const error = Math.abs(phi - estPhi);
const maxErr = 1 / N;
// Animate qubits with the binary bits of peakIdx
for (let k = 0; k < n; k++) {
const bit = (peakIdx >> (n - 1 - k)) & 1;
const qDiv = document.getElementById('q' + k);
if (qDiv) {
qDiv.querySelector('.bit').textContent = bit;
qDiv.className = 'qubit' + (bit ? ' active' : '');
}
}
// Build binary fraction string
let binFrac = '0.';
for (let k = 0; k < n; k++) binFrac += ((peakIdx >> (n - 1 - k)) & 1);
const isExact = error < 1e-9;
const successProb = probs[peakIdx].toFixed(4);
const cls = isExact ? 'exact' : 'approx';
const label = isExact ? `Exact!` : `Nearest representable`;
// Build probability bars (top outcomes)
const indexed = Array.from(probs).map((p, i) => ({ i, p })).sort((a, b) => b.p - a.p);
const top = indexed.slice(0, Math.min(4, N));
let bars = '';
for (const { i, p } of top) {
if (p < 0.001) break;
const pct = (p * 100).toFixed(1);
const isPeak = i === peakIdx;
const bf = '0.' + i.toString(2).padStart(n, '0');
bars += `<div class="bar-row">
<span class="bar-label">${bf}</span>
<div class="bar-bg"><div class="bar-fill${isPeak ? ' peak' : ''}" style="width:${pct}%"></div></div>
<span style="min-width:4ch;font-size:.75rem">${pct}%</span>
</div>`;
}
resultDiv.innerHTML =
fmt(`QPE reads binary register <b>%binFrac%</b> = <b>%estPhi%</b>
<span class="%cls%">[%label%]</span><br>
True φ = %phi% | Error ≤ 1/2<sup>n</sup> = 1/2<sup>%n%</sup> = %maxErr%<br>
Peak probability: %v%%<br>
%bars%`, { binFrac: binFrac, estPhi: estPhi.toFixed(6), cls: cls, label: label, phi: phi.toFixed(6), n: n, maxErr: maxErr.toFixed(6), v: (parseFloat(successProb)*100).toFixed(1), bars: bars });
console.log(fmt(`QPE result: peak=%peakIdx% estPhi=%estPhi% error=%error% prob=%successProb%`, { peakIdx: peakIdx, estPhi: estPhi, error: error, successProb: successProb }));
}
document.getElementById('run').addEventListener('click', runQPE);
document.getElementById('reset').addEventListener('click', () => {
phiSlider.value = 37; nSlider.value = 3;
updateLabels(); renderRegister(); resultDiv.innerHTML = '';
});
updateLabels();
renderRegister();
Notice how the estimate snaps to the exact value whenever φ is an exact n-bit binary fraction (like 0.375 = 3/8 with n = 3). For other phases the algorithm returns the nearest representable fraction. More ancilla qubits mean finer resolution — the error halves with each extra qubit, giving exponential precision growth with register size.
Comments
Loading comments...