Every local Hamiltonian is a sum of terms, each touching only a few qubits. The total ground-state energy is a global property — but the QPCP conjecture asks whether local checks can witness it efficiently.
<p class="hint">A local Hamiltonian is a sum of interaction terms, each acting on a few qubits. Toggle terms below. The system tries to minimize its total energy — but some configurations are <b>frustrated</b>: no single assignment satisfies every term at once.</p>
<div id="qubit-row"></div>
<div id="term-grid"></div>
<div class="energy-bar-wrap">
<span class="e-label">Energy:</span>
<div class="energy-bar-track"><div class="energy-bar-fill" id="efill"></div></div>
<span class="e-val" id="eval">–</span>
</div>
<div class="status" id="status">Adjust terms to explore the energy landscape.</div>
<div class="btns">
<button id="btn-min" type="button">Find minimum energy</button>
<button id="btn-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.5; }
#qubit-row { display: flex; gap: 10px; margin-bottom: .8rem; flex-wrap: wrap; }
.qubit { width: 44px; height: 44px; border-radius: 50%; border: 2px solid #1d3557;
background: #e8eef3; display: flex; align-items: center; justify-content: center;
font: 700 14px ui-monospace,monospace; color: #1d3557; cursor: pointer;
user-select: none; transition: background .15s, color .15s; }
.qubit.up { background: #1d3557; color: #fff; }
#term-grid { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: .9rem; }
.term { display: flex; align-items: center; gap: 6px; padding: 5px 10px;
border: 1.5px solid #cdd9e3; border-radius: 8px; cursor: pointer;
background: #f4f7fa; font-size: .85rem; user-select: none; transition: all .15s; }
.term.active { background: #1d3557; color: #fff; border-color: #1d3557; }
.term.sat { background: #e6f4ec; border-color: #2a9d5c; color: #1a5c38; }
.term.unsat { background: #fceaea; border-color: #c92f3c; color: #8b1c24; }
.term-dot { width: 8px; height: 8px; border-radius: 50%; background: currentColor; flex-shrink: 0; }
.energy-bar-wrap { display: flex; align-items: center; gap: 10px; margin-bottom: .6rem; }
.e-label { font-size: .85rem; color: #555; white-space: nowrap; }
.energy-bar-track { flex: 1; height: 14px; background: #e8eef3; border-radius: 7px; overflow: hidden; }
.energy-bar-fill { height: 100%; background: #1d3557; border-radius: 7px; width: 0%; transition: width .3s, background .3s; }
.e-val { font: 700 14px ui-monospace,monospace; color: #1d3557; white-space: nowrap; min-width: 70px; }
.status { font-size: .95rem; font-weight: 600; margin: .4rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.status.info { color: #1d3557; }
.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; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Local Hamiltonian demo for the Quantum PCP Conjecture article.
// We model 5 spin-1/2 qubits (each in state |0> or |1>, represented as 0/1).
// The Hamiltonian H = sum of local 2-body terms. Each term has an "interaction type":
// FERRO (ferromagnetic): energy = -1 if both qubits same, +1 if different
// ANTIF (antiferromagnetic): energy = -1 if qubits differ, +1 if same
// The ground-state energy is min over all 2^5 assignments.
// A "frustrated" system has no assignment satisfying all active terms simultaneously.
const N = 5;
// 2-body terms: [qubit_i, qubit_j, type] type: 'F'=ferro, 'A'=antiferro
const ALL_TERMS = [
{i:0, j:1, t:'F', label:'q0–q1 ↑↑'},
{i:1, j:2, t:'A', label:'q1–q2 ↑↓'},
{i:2, j:3, t:'F', label:'q2–q3 ↑↑'},
{i:3, j:4, t:'A', label:'q3–q4 ↑↓'},
{i:0, j:2, t:'A', label:'q0–q2 ↑↓'},
{i:1, j:3, t:'F', label:'q1–q3 ↑↑'},
{i:2, j:4, t:'A', label:'q2–q4 ↑↓'},
];
// Terms active by default (indices into ALL_TERMS)
const DEFAULT_ACTIVE = new Set([0,1,2,3,4]);
let qubits = Array(N).fill(0); // current spin assignment
let activeTerms = new Set(DEFAULT_ACTIVE);
function termEnergy(term, spins) {
const si = spins[term.i], sj = spins[term.j];
if (term.t === 'F') return si === sj ? -1 : 1; // wants same
else return si !== sj ? -1 : 1; // wants different
}
function totalEnergy(spins) {
let e = 0;
for (const idx of activeTerms) e += termEnergy(ALL_TERMS[idx], spins);
return e;
}
function minEnergy() {
let best = Infinity, bestSpins = null;
for (let mask = 0; mask < (1 << N); mask++) {
const s = Array.from({length: N}, (_, k) => (mask >> k) & 1);
const e = totalEnergy(s);
if (e < best) { best = e; bestSpins = s; }
}
return {energy: best, spins: bestSpins};
}
function isSat(term, spins) {
return termEnergy(term, spins) === -1;
}
function maxPossible() { return activeTerms.size; }
function render() {
// Qubit row
const qrow = document.getElementById('qubit-row');
qrow.innerHTML = '';
qubits.forEach((v, i) => {
const d = document.createElement('div');
d.className = 'qubit' + (v ? ' up' : '');
d.textContent = v ? '|1⟩' : '|0⟩';
d.title = fmt('Click to flip qubit %i%', { i: i });
d.addEventListener('click', () => { qubits[i] ^= 1; render(); });
qrow.appendChild(d);
});
// Term grid
const grid = document.getElementById('term-grid');
grid.innerHTML = '';
ALL_TERMS.forEach((term, idx) => {
const isActive = activeTerms.has(idx);
const sat = isActive && isSat(term, qubits);
const unsat = isActive && !isSat(term, qubits);
const d = document.createElement('div');
d.className = 'term' + (isActive ? (sat ? ' sat' : ' unsat') : '');
d.innerHTML = '<span class="term-dot"></span>' + term.label +
' <small>(' + (term.t==='F'?'ferro':'antiferro') + ')</small>';
d.title = isActive ? 'Click to disable this term' : 'Click to enable this term';
d.addEventListener('click', () => {
if (activeTerms.has(idx)) activeTerms.delete(idx);
else activeTerms.add(idx);
render();
});
grid.appendChild(d);
});
// Energy bar
const e = totalEnergy(qubits);
const maxE = maxPossible();
const minE = -maxE;
const range = maxE - minE || 1;
const pct = ((e - minE) / range) * 100;
const fill = document.getElementById('efill');
fill.style.width = Math.max(0, Math.min(100, pct)) + '%';
fill.style.background = e <= -maxE + 1 ? '#0a7d33' : (e >= maxE - 1 ? '#c92f3c' : '#1d3557');
document.getElementById('eval').textContent = e.toFixed(0) + ' / ' + minE;
// Satisfaction check
const satCount = [...activeTerms].filter(idx => isSat(ALL_TERMS[idx], qubits)).length;
const statusEl = document.getElementById('status');
if (activeTerms.size === 0) {
statusEl.textContent = 'No active terms. Enable some terms to start.';
statusEl.className = 'status info';
} else if (satCount === activeTerms.size) {
statusEl.textContent = fmt('✓ All %size% active terms satisfied — no frustration!', { size: activeTerms.size });
statusEl.className = 'status ok';
} else {
statusEl.textContent = fmt('%satCount% of %size% terms satisfied. System is frustrated.', { satCount: satCount, size: activeTerms.size });
statusEl.className = 'status bad';
}
console.log('Energy:', e, '| Sat:', satCount, '/', activeTerms.size, '| Spins:', qubits.join(''));
}
document.getElementById('btn-min').addEventListener('click', () => {
if (activeTerms.size === 0) {
document.getElementById('status').textContent = 'Enable some terms first.';
document.getElementById('status').className = 'status info';
return;
}
const {energy, spins} = minEnergy();
qubits = spins;
render();
const maxE = -maxPossible();
const statusEl = document.getElementById('status');
if (energy === maxE) {
statusEl.textContent = fmt('✓ Ground state found! Energy = %energy% (all terms satisfied).', { energy: energy });
statusEl.className = 'status ok';
} else {
statusEl.textContent = fmt('Ground state found. Energy = %energy%. Some terms frustrated — gap above ideal minimum.', { energy: energy });
statusEl.className = 'status info';
}
console.log('Ground state energy:', energy, '| Spins:', spins.join(''));
});
document.getElementById('btn-reset').addEventListener('click', () => {
qubits = Array(N).fill(0);
activeTerms = new Set(DEFAULT_ACTIVE);
render();
document.getElementById('status').textContent = 'Adjust terms to explore the energy landscape.';
document.getElementById('status').className = 'status';
});
render();
<p class="hint">A local Hamiltonian is a sum of interaction terms, each acting on a few qubits. Toggle terms below. The system tries to minimize its total energy — but some configurations are <b>frustrated</b>: no single assignment satisfies every term at once.</p>
<div id="qubit-row"></div>
<div id="term-grid"></div>
<div class="energy-bar-wrap">
<span class="e-label">Energy:</span>
<div class="energy-bar-track"><div class="energy-bar-fill" id="efill"></div></div>
<span class="e-val" id="eval">–</span>
</div>
<div class="status" id="status">Adjust terms to explore the energy landscape.</div>
<div class="btns">
<button id="btn-min" type="button">Find minimum energy</button>
<button id="btn-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.5; }
#qubit-row { display: flex; gap: 10px; margin-bottom: .8rem; flex-wrap: wrap; }
.qubit { width: 44px; height: 44px; border-radius: 50%; border: 2px solid #1d3557;
background: #e8eef3; display: flex; align-items: center; justify-content: center;
font: 700 14px ui-monospace,monospace; color: #1d3557; cursor: pointer;
user-select: none; transition: background .15s, color .15s; }
.qubit.up { background: #1d3557; color: #fff; }
#term-grid { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: .9rem; }
.term { display: flex; align-items: center; gap: 6px; padding: 5px 10px;
border: 1.5px solid #cdd9e3; border-radius: 8px; cursor: pointer;
background: #f4f7fa; font-size: .85rem; user-select: none; transition: all .15s; }
.term.active { background: #1d3557; color: #fff; border-color: #1d3557; }
.term.sat { background: #e6f4ec; border-color: #2a9d5c; color: #1a5c38; }
.term.unsat { background: #fceaea; border-color: #c92f3c; color: #8b1c24; }
.term-dot { width: 8px; height: 8px; border-radius: 50%; background: currentColor; flex-shrink: 0; }
.energy-bar-wrap { display: flex; align-items: center; gap: 10px; margin-bottom: .6rem; }
.e-label { font-size: .85rem; color: #555; white-space: nowrap; }
.energy-bar-track { flex: 1; height: 14px; background: #e8eef3; border-radius: 7px; overflow: hidden; }
.energy-bar-fill { height: 100%; background: #1d3557; border-radius: 7px; width: 0%; transition: width .3s, background .3s; }
.e-val { font: 700 14px ui-monospace,monospace; color: #1d3557; white-space: nowrap; min-width: 70px; }
.status { font-size: .95rem; font-weight: 600; margin: .4rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.status.info { color: #1d3557; }
.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; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Local Hamiltonian demo for the Quantum PCP Conjecture article.
// We model 5 spin-1/2 qubits (each in state |0> or |1>, represented as 0/1).
// The Hamiltonian H = sum of local 2-body terms. Each term has an "interaction type":
// FERRO (ferromagnetic): energy = -1 if both qubits same, +1 if different
// ANTIF (antiferromagnetic): energy = -1 if qubits differ, +1 if same
// The ground-state energy is min over all 2^5 assignments.
// A "frustrated" system has no assignment satisfying all active terms simultaneously.
const N = 5;
// 2-body terms: [qubit_i, qubit_j, type] type: 'F'=ferro, 'A'=antiferro
const ALL_TERMS = [
{i:0, j:1, t:'F', label:'q0–q1 ↑↑'},
{i:1, j:2, t:'A', label:'q1–q2 ↑↓'},
{i:2, j:3, t:'F', label:'q2–q3 ↑↑'},
{i:3, j:4, t:'A', label:'q3–q4 ↑↓'},
{i:0, j:2, t:'A', label:'q0–q2 ↑↓'},
{i:1, j:3, t:'F', label:'q1–q3 ↑↑'},
{i:2, j:4, t:'A', label:'q2–q4 ↑↓'},
];
// Terms active by default (indices into ALL_TERMS)
const DEFAULT_ACTIVE = new Set([0,1,2,3,4]);
let qubits = Array(N).fill(0); // current spin assignment
let activeTerms = new Set(DEFAULT_ACTIVE);
function termEnergy(term, spins) {
const si = spins[term.i], sj = spins[term.j];
if (term.t === 'F') return si === sj ? -1 : 1; // wants same
else return si !== sj ? -1 : 1; // wants different
}
function totalEnergy(spins) {
let e = 0;
for (const idx of activeTerms) e += termEnergy(ALL_TERMS[idx], spins);
return e;
}
function minEnergy() {
let best = Infinity, bestSpins = null;
for (let mask = 0; mask < (1 << N); mask++) {
const s = Array.from({length: N}, (_, k) => (mask >> k) & 1);
const e = totalEnergy(s);
if (e < best) { best = e; bestSpins = s; }
}
return {energy: best, spins: bestSpins};
}
function isSat(term, spins) {
return termEnergy(term, spins) === -1;
}
function maxPossible() { return activeTerms.size; }
function render() {
// Qubit row
const qrow = document.getElementById('qubit-row');
qrow.innerHTML = '';
qubits.forEach((v, i) => {
const d = document.createElement('div');
d.className = 'qubit' + (v ? ' up' : '');
d.textContent = v ? '|1⟩' : '|0⟩';
d.title = fmt('Click to flip qubit %i%', { i: i });
d.addEventListener('click', () => { qubits[i] ^= 1; render(); });
qrow.appendChild(d);
});
// Term grid
const grid = document.getElementById('term-grid');
grid.innerHTML = '';
ALL_TERMS.forEach((term, idx) => {
const isActive = activeTerms.has(idx);
const sat = isActive && isSat(term, qubits);
const unsat = isActive && !isSat(term, qubits);
const d = document.createElement('div');
d.className = 'term' + (isActive ? (sat ? ' sat' : ' unsat') : '');
d.innerHTML = '<span class="term-dot"></span>' + term.label +
' <small>(' + (term.t==='F'?'ferro':'antiferro') + ')</small>';
d.title = isActive ? 'Click to disable this term' : 'Click to enable this term';
d.addEventListener('click', () => {
if (activeTerms.has(idx)) activeTerms.delete(idx);
else activeTerms.add(idx);
render();
});
grid.appendChild(d);
});
// Energy bar
const e = totalEnergy(qubits);
const maxE = maxPossible();
const minE = -maxE;
const range = maxE - minE || 1;
const pct = ((e - minE) / range) * 100;
const fill = document.getElementById('efill');
fill.style.width = Math.max(0, Math.min(100, pct)) + '%';
fill.style.background = e <= -maxE + 1 ? '#0a7d33' : (e >= maxE - 1 ? '#c92f3c' : '#1d3557');
document.getElementById('eval').textContent = e.toFixed(0) + ' / ' + minE;
// Satisfaction check
const satCount = [...activeTerms].filter(idx => isSat(ALL_TERMS[idx], qubits)).length;
const statusEl = document.getElementById('status');
if (activeTerms.size === 0) {
statusEl.textContent = 'No active terms. Enable some terms to start.';
statusEl.className = 'status info';
} else if (satCount === activeTerms.size) {
statusEl.textContent = fmt('✓ All %size% active terms satisfied — no frustration!', { size: activeTerms.size });
statusEl.className = 'status ok';
} else {
statusEl.textContent = fmt('%satCount% of %size% terms satisfied. System is frustrated.', { satCount: satCount, size: activeTerms.size });
statusEl.className = 'status bad';
}
console.log('Energy:', e, '| Sat:', satCount, '/', activeTerms.size, '| Spins:', qubits.join(''));
}
document.getElementById('btn-min').addEventListener('click', () => {
if (activeTerms.size === 0) {
document.getElementById('status').textContent = 'Enable some terms first.';
document.getElementById('status').className = 'status info';
return;
}
const {energy, spins} = minEnergy();
qubits = spins;
render();
const maxE = -maxPossible();
const statusEl = document.getElementById('status');
if (energy === maxE) {
statusEl.textContent = fmt('✓ Ground state found! Energy = %energy% (all terms satisfied).', { energy: energy });
statusEl.className = 'status ok';
} else {
statusEl.textContent = fmt('Ground state found. Energy = %energy%. Some terms frustrated — gap above ideal minimum.', { energy: energy });
statusEl.className = 'status info';
}
console.log('Ground state energy:', energy, '| Spins:', spins.join(''));
});
document.getElementById('btn-reset').addEventListener('click', () => {
qubits = Array(N).fill(0);
activeTerms = new Set(DEFAULT_ACTIVE);
render();
document.getElementById('status').textContent = 'Adjust terms to explore the energy landscape.';
document.getElementById('status').className = 'status';
});
render();
Toggle individual interaction terms on and off and watch how the minimum energy of the system changes. Notice that satisfying all terms simultaneously may be impossible (a frustrated system), and that the gap between the best achievable energy and the ideal minimum is exactly what the QPCP conjecture is about. Compare this to the classical PCP Theorem, where a constant-fraction error is always detectable locally.
Comments
Loading comments...