Below is a simple stabilizer-circuit simulator. Each row is a qubit starting in ∣0⟩. Click the gate buttons to add H, S, or CNOT gates, then press Simulate to run the classical stabilizer algorithm and display the resulting Pauli stabilizers — the compact classical description of the quantum state.
<!-- Gottesman-Knill stabilizer circuit demo — 2-qubit Clifford simulator -->
<p class="hint">Add gates to build a 2-qubit Clifford circuit (H, S, CNOT), then click <b>Simulate</b> to see the stabilizer tableau — the classical description that tracks the quantum state in just O(n²) bits.</p>
<div class="qubit-section">
<div class="qubit-label-row">
<span class="ql">q0</span>
<span class="ql">q1</span>
</div>
<div class="gate-buttons">
<button type="button" id="btn-h0" data-gate="H" data-qubit="0">H q0</button>
<button type="button" id="btn-h1" data-gate="H" data-qubit="1">H q1</button>
<button type="button" id="btn-s0" data-gate="S" data-qubit="0">S q0</button>
<button type="button" id="btn-s1" data-gate="S" data-qubit="1">S q1</button>
<button type="button" id="btn-cnot" data-gate="CNOT" data-control="0" data-target="1">CNOT q0→q1</button>
</div>
</div>
<div class="circuit-row" id="circuit-display">
<span class="circuit-label">Circuit:</span>
<span id="circuit-ops" class="ops-list">(empty)</span>
</div>
<div class="btns">
<button type="button" id="btn-simulate">Simulate</button>
<button type="button" id="btn-reset" class="ghost">Clear</button>
</div>
<div id="result-area" class="result-area" aria-live="polite"></div>
/* Styles for the Gottesman-Knill stabilizer demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; font-size: 15px; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.qubit-section { margin-bottom: .6rem; }
.qubit-label-row { display: flex; gap: .4rem; margin-bottom: .3rem; }
.ql { font-weight: 700; font-size: .85rem; color: #1d3557; min-width: 2rem; }
.gate-buttons { display: flex; gap: .4rem; flex-wrap: wrap; }
.gate-buttons button { font: 600 13px system-ui; padding: .35rem .7rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 6px; cursor: pointer; }
.gate-buttons button:hover { background: #274d7a; }
.circuit-row { margin: .6rem 0; font-size: .88rem; color: #333; }
.circuit-label { font-weight: 600; margin-right: .4rem; color: #1d3557; }
.ops-list { font-family: ui-monospace, monospace; color: #555; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .7rem; }
button { font: 600 14px system-ui; padding: .45rem .9rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.result-area { min-height: 2em; }
.stab-title { font-weight: 700; color: #1d3557; margin: .4rem 0 .2rem; font-size: .95rem; }
.stab-table { border-collapse: collapse; margin-bottom: .5rem; font-size: .88rem; }
.stab-table th { background: #e8eef3; color: #1d3557; padding: .25rem .5rem;
border: 1px solid #cdd9e3; font-family: ui-monospace, monospace; }
.stab-table td { padding: .2rem .5rem; border: 1px solid #cdd9e3;
font-family: ui-monospace, monospace; }
.stab-table tr:nth-child(even) td { background: #f5f8fb; }
.note { font-size: .83rem; color: #555; margin-top: .4rem; line-height: 1.45; }
.ok { color: #0a7d33; font-weight: 600; }
.bad { color: #c92f3c; font-weight: 600; }
// Gottesman-Knill stabilizer tableau simulator (2 qubits).
// Tracks the stabilizer group using the Aaronson-Gottesman tableau method.
const N = 2; // number of qubits
// Stabilizer tableau: each row i is a Pauli generator g_i of the stabilizer group.
// Row i encodes the generator as bit vectors x[i][q] (X part) and z[i][q] (Z part)
// plus a sign bit r[i] (false = +1, true = -1).
function initTableau() {
// Initial state |00>: stabilized by +ZI and +IZ.
// Generator 0 -> +ZI (Z on q0, I on q1)
return {
x: [[false, false], [false, false]], // X part: no X in either generator
z: [[true, false], [false, true ]], // Z part: g0 has Z on q0, g1 has Z on q1
r: [false, false] // sign: both generators have sign +1
};
}
// Hadamard: swaps X <-> Z, flips sign if both X and Z were set (Y -> -Y).
function applyH(tab, q) {
for (let i = 0; i < N; i++) {
const xOld = tab.x[i][q];
const zOld = tab.z[i][q];
if (xOld && zOld) tab.r[i] = !tab.r[i]; // H maps Y -> -Y, so flip sign when X=Z=1
tab.x[i][q] = zOld;
tab.z[i][q] = xOld;
}
}
// Phase gate S: maps X -> Y (i.e. X -> iXZ), Z unchanged.
function applyS(tab, q) {
for (let i = 0; i < N; i++) {
if (tab.x[i][q] && tab.z[i][q]) tab.r[i] = !tab.r[i]; // S maps Y -> -X, flip sign when X=Z=1
tab.z[i][q] = tab.z[i][q] !== tab.x[i][q]; // S maps X -> Y = iXZ, so z[q] ^= x[q]
}
}
// CNOT: ctrl=control qubit, tgt=target qubit.
function applyCNOT(tab, ctrl, tgt) {
for (let i = 0; i < N; i++) {
// Sign flip rule for CNOT (from Aaronson-Gottesman Table 1)
const rFlip = tab.x[i][ctrl] && tab.z[i][tgt] &&
(tab.x[i][tgt] !== tab.z[i][ctrl]);
if (rFlip) tab.r[i] = !tab.r[i];
tab.x[i][tgt] = tab.x[i][tgt] !== tab.x[i][ctrl]; // CNOT propagates X from control to target
tab.z[i][ctrl] = tab.z[i][ctrl] !== tab.z[i][tgt]; // CNOT propagates Z from target to control
}
}
// Convert tableau row i to a human-readable Pauli string like '+XZ' or '-YI'.
function pauliStr(tab, i) {
let s = tab.r[i] ? '-' : '+';
for (let q = 0; q < N; q++) {
const hasX = tab.x[i][q], hasZ = tab.z[i][q];
if (hasX && hasZ) s += 'Y';
else if (hasX) s += 'X';
else if (hasZ) s += 'Z';
else s += 'I';
}
return s;
}
const ops = [];
function addOp(gate, q1, q2) {
ops.push({ gate, q1, q2 });
const el = document.getElementById('circuit-ops');
el.textContent = ops.map(o =>
o.gate === 'CNOT' ? `CNOT(${o.q1},${o.q2})` : `${o.gate}(${o.q1})`
).join(' → ');
}
document.getElementById('btn-h0').addEventListener('click', () => addOp('H', 0, null));
document.getElementById('btn-h1').addEventListener('click', () => addOp('H', 1, null));
document.getElementById('btn-s0').addEventListener('click', () => addOp('S', 0, null));
document.getElementById('btn-s1').addEventListener('click', () => addOp('S', 1, null));
document.getElementById('btn-cnot').addEventListener('click', () => addOp('CNOT', 0, 1));
document.getElementById('btn-reset').addEventListener('click', () => {
ops.length = 0;
document.getElementById('circuit-ops').textContent = '(empty)';
document.getElementById('result-area').innerHTML = '';
});
document.getElementById('btn-simulate').addEventListener('click', () => {
const tab = initTableau();
for (const op of ops) {
if (op.gate === 'H') applyH(tab, op.q1);
else if (op.gate === 'S') applyS(tab, op.q1);
else if (op.gate === 'CNOT') applyCNOT(tab, op.q1, op.q2);
}
const stabs = Array.from({ length: N }, (_, i) => pauliStr(tab, i));
const area = document.getElementById('result-area');
let html = '<div class="stab-title">Stabilizer generators after simulation:</div>';
html += '<table class="stab-table"><tr><th>Generator</th><th>Pauli string</th></tr>';
for (let i = 0; i < N; i++) {
html += `<tr><td>g${i + 1}</td><td>${stabs[i]}</td></tr>`;
}
html += `</table>`;
// Check for well-known special states to show an informative message
const isBell = stabs[0] === '+XX' && stabs[1] === '+ZZ';
const isZZ = stabs[0] === '+ZI' && stabs[1] === '+IZ';
const isPP = stabs[0] === '+XI' && stabs[1] === '+IX';
if (isBell) {
html += '<p class="note ok">This is the Bell state |Φ⁺⟩ — maximally entangled! Its stabilizers are +XX and +ZZ.</p>';
} else if (isZZ) {
html += '<p class="note ok">Both qubits are in |0⟩: stabilizers +ZI and +IZ describe a separable state.</p>';
} else if (isPP) {
html += '<p class="note ok">Both qubits are in |+⟩ (equal superposition): stabilizers +XI and +IX.</p>';
} else {
html += '<p class="note">The two Pauli strings above fully describe the quantum state — no exponential list of amplitudes needed.</p>';
}
html += '<p class="note">No matter how many Clifford gates you add, this description stays compact: 2 Pauli strings for 2 qubits, 2n strings for n qubits. That is the Gottesman-Knill theorem.</p>';
area.innerHTML = html;
console.log('Stabilizers:', stabs.join(', '), '| gates applied:', ops.length);
});
<!-- Gottesman-Knill stabilizer circuit demo — 2-qubit Clifford simulator -->
<p class="hint">Add gates to build a 2-qubit Clifford circuit (H, S, CNOT), then click <b>Simulate</b> to see the stabilizer tableau — the classical description that tracks the quantum state in just O(n²) bits.</p>
<div class="qubit-section">
<div class="qubit-label-row">
<span class="ql">q0</span>
<span class="ql">q1</span>
</div>
<div class="gate-buttons">
<button type="button" id="btn-h0" data-gate="H" data-qubit="0">H q0</button>
<button type="button" id="btn-h1" data-gate="H" data-qubit="1">H q1</button>
<button type="button" id="btn-s0" data-gate="S" data-qubit="0">S q0</button>
<button type="button" id="btn-s1" data-gate="S" data-qubit="1">S q1</button>
<button type="button" id="btn-cnot" data-gate="CNOT" data-control="0" data-target="1">CNOT q0→q1</button>
</div>
</div>
<div class="circuit-row" id="circuit-display">
<span class="circuit-label">Circuit:</span>
<span id="circuit-ops" class="ops-list">(empty)</span>
</div>
<div class="btns">
<button type="button" id="btn-simulate">Simulate</button>
<button type="button" id="btn-reset" class="ghost">Clear</button>
</div>
<div id="result-area" class="result-area" aria-live="polite"></div>
/* Styles for the Gottesman-Knill stabilizer demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; font-size: 15px; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.qubit-section { margin-bottom: .6rem; }
.qubit-label-row { display: flex; gap: .4rem; margin-bottom: .3rem; }
.ql { font-weight: 700; font-size: .85rem; color: #1d3557; min-width: 2rem; }
.gate-buttons { display: flex; gap: .4rem; flex-wrap: wrap; }
.gate-buttons button { font: 600 13px system-ui; padding: .35rem .7rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 6px; cursor: pointer; }
.gate-buttons button:hover { background: #274d7a; }
.circuit-row { margin: .6rem 0; font-size: .88rem; color: #333; }
.circuit-label { font-weight: 600; margin-right: .4rem; color: #1d3557; }
.ops-list { font-family: ui-monospace, monospace; color: #555; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .7rem; }
button { font: 600 14px system-ui; padding: .45rem .9rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.result-area { min-height: 2em; }
.stab-title { font-weight: 700; color: #1d3557; margin: .4rem 0 .2rem; font-size: .95rem; }
.stab-table { border-collapse: collapse; margin-bottom: .5rem; font-size: .88rem; }
.stab-table th { background: #e8eef3; color: #1d3557; padding: .25rem .5rem;
border: 1px solid #cdd9e3; font-family: ui-monospace, monospace; }
.stab-table td { padding: .2rem .5rem; border: 1px solid #cdd9e3;
font-family: ui-monospace, monospace; }
.stab-table tr:nth-child(even) td { background: #f5f8fb; }
.note { font-size: .83rem; color: #555; margin-top: .4rem; line-height: 1.45; }
.ok { color: #0a7d33; font-weight: 600; }
.bad { color: #c92f3c; font-weight: 600; }
// Gottesman-Knill stabilizer tableau simulator (2 qubits).
// Tracks the stabilizer group using the Aaronson-Gottesman tableau method.
const N = 2; // number of qubits
// Stabilizer tableau: each row i is a Pauli generator g_i of the stabilizer group.
// Row i encodes the generator as bit vectors x[i][q] (X part) and z[i][q] (Z part)
// plus a sign bit r[i] (false = +1, true = -1).
function initTableau() {
// Initial state |00>: stabilized by +ZI and +IZ.
// Generator 0 -> +ZI (Z on q0, I on q1)
return {
x: [[false, false], [false, false]], // X part: no X in either generator
z: [[true, false], [false, true ]], // Z part: g0 has Z on q0, g1 has Z on q1
r: [false, false] // sign: both generators have sign +1
};
}
// Hadamard: swaps X <-> Z, flips sign if both X and Z were set (Y -> -Y).
function applyH(tab, q) {
for (let i = 0; i < N; i++) {
const xOld = tab.x[i][q];
const zOld = tab.z[i][q];
if (xOld && zOld) tab.r[i] = !tab.r[i]; // H maps Y -> -Y, so flip sign when X=Z=1
tab.x[i][q] = zOld;
tab.z[i][q] = xOld;
}
}
// Phase gate S: maps X -> Y (i.e. X -> iXZ), Z unchanged.
function applyS(tab, q) {
for (let i = 0; i < N; i++) {
if (tab.x[i][q] && tab.z[i][q]) tab.r[i] = !tab.r[i]; // S maps Y -> -X, flip sign when X=Z=1
tab.z[i][q] = tab.z[i][q] !== tab.x[i][q]; // S maps X -> Y = iXZ, so z[q] ^= x[q]
}
}
// CNOT: ctrl=control qubit, tgt=target qubit.
function applyCNOT(tab, ctrl, tgt) {
for (let i = 0; i < N; i++) {
// Sign flip rule for CNOT (from Aaronson-Gottesman Table 1)
const rFlip = tab.x[i][ctrl] && tab.z[i][tgt] &&
(tab.x[i][tgt] !== tab.z[i][ctrl]);
if (rFlip) tab.r[i] = !tab.r[i];
tab.x[i][tgt] = tab.x[i][tgt] !== tab.x[i][ctrl]; // CNOT propagates X from control to target
tab.z[i][ctrl] = tab.z[i][ctrl] !== tab.z[i][tgt]; // CNOT propagates Z from target to control
}
}
// Convert tableau row i to a human-readable Pauli string like '+XZ' or '-YI'.
function pauliStr(tab, i) {
let s = tab.r[i] ? '-' : '+';
for (let q = 0; q < N; q++) {
const hasX = tab.x[i][q], hasZ = tab.z[i][q];
if (hasX && hasZ) s += 'Y';
else if (hasX) s += 'X';
else if (hasZ) s += 'Z';
else s += 'I';
}
return s;
}
const ops = [];
function addOp(gate, q1, q2) {
ops.push({ gate, q1, q2 });
const el = document.getElementById('circuit-ops');
el.textContent = ops.map(o =>
o.gate === 'CNOT' ? `CNOT(${o.q1},${o.q2})` : `${o.gate}(${o.q1})`
).join(' → ');
}
document.getElementById('btn-h0').addEventListener('click', () => addOp('H', 0, null));
document.getElementById('btn-h1').addEventListener('click', () => addOp('H', 1, null));
document.getElementById('btn-s0').addEventListener('click', () => addOp('S', 0, null));
document.getElementById('btn-s1').addEventListener('click', () => addOp('S', 1, null));
document.getElementById('btn-cnot').addEventListener('click', () => addOp('CNOT', 0, 1));
document.getElementById('btn-reset').addEventListener('click', () => {
ops.length = 0;
document.getElementById('circuit-ops').textContent = '(empty)';
document.getElementById('result-area').innerHTML = '';
});
document.getElementById('btn-simulate').addEventListener('click', () => {
const tab = initTableau();
for (const op of ops) {
if (op.gate === 'H') applyH(tab, op.q1);
else if (op.gate === 'S') applyS(tab, op.q1);
else if (op.gate === 'CNOT') applyCNOT(tab, op.q1, op.q2);
}
const stabs = Array.from({ length: N }, (_, i) => pauliStr(tab, i));
const area = document.getElementById('result-area');
let html = '<div class="stab-title">Stabilizer generators after simulation:</div>';
html += '<table class="stab-table"><tr><th>Generator</th><th>Pauli string</th></tr>';
for (let i = 0; i < N; i++) {
html += `<tr><td>g${i + 1}</td><td>${stabs[i]}</td></tr>`;
}
html += `</table>`;
// Check for well-known special states to show an informative message
const isBell = stabs[0] === '+XX' && stabs[1] === '+ZZ';
const isZZ = stabs[0] === '+ZI' && stabs[1] === '+IZ';
const isPP = stabs[0] === '+XI' && stabs[1] === '+IX';
if (isBell) {
html += '<p class="note ok">This is the Bell state |Φ⁺⟩ — maximally entangled! Its stabilizers are +XX and +ZZ.</p>';
} else if (isZZ) {
html += '<p class="note ok">Both qubits are in |0⟩: stabilizers +ZI and +IZ describe a separable state.</p>';
} else if (isPP) {
html += '<p class="note ok">Both qubits are in |+⟩ (equal superposition): stabilizers +XI and +IX.</p>';
} else {
html += '<p class="note">The two Pauli strings above fully describe the quantum state — no exponential list of amplitudes needed.</p>';
}
html += '<p class="note">No matter how many Clifford gates you add, this description stays compact: 2 Pauli strings for 2 qubits, 2n strings for n qubits. That is the Gottesman-Knill theorem.</p>';
area.innerHTML = html;
console.log('Stabilizers:', stabs.join(', '), '| gates applied:', ops.length);
});
Notice that no matter how many gates you add, the simulation stays fast and the description stays compact. The stabilizer tableau tracks the state with just O(n2) bits — exponentially smaller than the 2n amplitudes a general quantum state would need. This is the Gottesman-Knill theorem in action: Clifford circuits never escape polynomial classical description.
Comments
Loading comments...