<p class="hint">
A <b>matchgate</b> is defined by a 2×2 matrix. Compose two matchgates <b>M</b> and <b>N</b> and sum over all internal states — some contribute positively, some negatively. Watch the cancellation leave the determinant of M·N.
</p>
<div class="controls">
<div class="preset-row">
<span class="label">Preset:</span>
<button type="button" id="btn-fkt" class="preset">FKT basis</button>
<button type="button" id="btn-hadamard" class="preset">Hadamard</button>
<button type="button" id="btn-custom" class="preset">Custom</button>
</div>
<div class="matrices">
<div class="mat-block">
<div class="mat-label">Matrix M</div>
<div class="mat-grid" id="mat-M">
<input type="number" id="m00" step="0.5" value="1">
<input type="number" id="m01" step="0.5" value="1">
<input type="number" id="m10" step="0.5" value="1">
<input type="number" id="m11" step="0.5" value="-1">
</div>
</div>
<div class="mat-block">
<div class="mat-label">Matrix N</div>
<div class="mat-grid" id="mat-N">
<input type="number" id="n00" step="0.5" value="1">
<input type="number" id="n01" step="0.5" value="1">
<input type="number" id="n10" step="0.5" value="1">
<input type="number" id="n11" step="0.5" value="-1">
</div>
</div>
</div>
<button type="button" id="btn-compute">Compute contributions</button>
</div>
<div id="result-area" class="result-area" style="display:none">
<div class="contributions" id="contributions"></div>
<div class="summary" id="summary"></div>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; font-size: 14px; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.controls { display: flex; flex-direction: column; gap: .7rem; margin-bottom: .8rem; }
.preset-row { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; }
.label { font-weight: 600; color: #1d3557; }
.preset { font-size: 13px; padding: .3rem .7rem; border: 1px solid #1d3557;
background: #fff; color: #1d3557; border-radius: 6px; cursor: pointer; }
.preset:hover { background: #e8f0f8; }
.matrices { display: flex; gap: 1.2rem; flex-wrap: wrap; }
.mat-block { display: flex; flex-direction: column; gap: .3rem; }
.mat-label { font-weight: 700; color: #1d3557; font-size: .9rem; }
.mat-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 4px; }
.mat-grid input { width: 56px; padding: .35rem; border: 1px solid #bcc0c6;
border-radius: 6px; font: 600 14px ui-monospace,monospace;
text-align: center; color: #1d3557; }
#btn-compute { align-self: flex-start; font: 600 14px system-ui,sans-serif;
padding: .45rem 1rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
#btn-compute:hover { background: #163250; }
.result-area { margin-top: .5rem; }
.contributions { display: flex; flex-direction: column; gap: .3rem; margin-bottom: .7rem; }
.term { display: flex; align-items: center; gap: .5rem; font-size: .88rem;
padding: .3rem .5rem; border-radius: 6px; }
.term.pos { background: #d4edda; color: #155724; }
.term.neg { background: #f8d7da; color: #721c24; }
.term.zero { background: #f5f5f5; color: #888; }
.term-label { font-family: ui-monospace, monospace; font-weight: 700; min-width: 60px; }
.term-value { font-family: ui-monospace, monospace; min-width: 52px; text-align: right; }
.term-bar-wrap { flex: 1; background: #e0e0e0; border-radius: 4px; height: 10px; overflow: hidden; min-width: 60px; }
.term-bar { height: 100%; border-radius: 4px; transition: width .4s; }
.bar-pos { background: #28a745; }
.bar-neg { background: #dc3545; }
.summary { border-top: 2px solid #1d3557; padding-top: .6rem; font-size: .95rem; }
.sum-row { display: flex; justify-content: space-between; margin: .15rem 0; }
.sum-label { color: #555; }
.sum-value { font-family: ui-monospace, monospace; font-weight: 700; color: #1d3557; }
.highlight { font-weight: 800; font-size: 1.05rem; color: #0a5c2a; }
.match-note { margin-top: .4rem; font-size: .83rem; color: #444; padding: .4rem .6rem;
background: #e8f0f8; border-radius: 6px; border-left: 3px solid #1d3557; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Holographic algorithm demo: matchgate composition and cancellation.
// Two 2x2 matchgate matrices M and N are composed.
// The "holographic" sum is: sum_{k in {0,1}} M[i][k] * N[k][j]
// for all (i,j) combinations, showing which terms are +, which are -.
// The total equals det(M*N) = det(M)*det(N) — the FKT-computable quantity.
function getMatrix(prefix) {
const v = (id) => parseFloat(document.getElementById(id).value) || 0;
return [
[v(prefix+'00'), v(prefix+'01')],
[v(prefix+'10'), v(prefix+'11')]
];
}
function setMatrix(prefix, mat) {
const ids = [[prefix+'00', prefix+'01'], [prefix+'10', prefix+'11']];
for (let r = 0; r < 2; r++) for (let c = 0; c < 2; c++)
document.getElementById(ids[r][c]).value = mat[r][c];
}
function det2(m) { return m[0][0]*m[1][1] - m[0][1]*m[1][0]; }
function matMul(A, B) {
return [
[A[0][0]*B[0][0]+A[0][1]*B[1][0], A[0][0]*B[0][1]+A[0][1]*B[1][1]],
[A[1][0]*B[0][0]+A[1][1]*B[1][0], A[1][0]*B[0][1]+A[1][1]*B[1][1]]
];
}
function fmt(v) {
if (Math.abs(v) < 1e-9) return '0';
if (Number.isInteger(v)) return String(v);
return v.toFixed(3).replace(/\.?0+$/, '');
}
const PRESETS = {
fkt: { M: [[1,1],[1,-1]], N: [[1,1],[1,-1]] },
hadamard: { M: [[1,1],[1,-1]], N: [[1,1],[-1,1]] },
custom: { M: [[2,1],[0,1]], N: [[1,0],[1,2]] }
};
function applyPreset(name) {
const p = PRESETS[name];
setMatrix('m', p.M);
setMatrix('n', p.N);
document.querySelectorAll('.preset').forEach(b => b.style.fontWeight = '');
document.getElementById('btn-'+name).style.fontWeight = '800';
}
document.getElementById('btn-fkt').addEventListener('click', () => applyPreset('fkt'));
document.getElementById('btn-hadamard').addEventListener('click', () => applyPreset('hadamard'));
document.getElementById('btn-custom').addEventListener('click', () => applyPreset('custom'));
document.getElementById('btn-compute').addEventListener('click', compute);
function compute() {
const M = getMatrix('m');
const N = getMatrix('n');
const MN = matMul(M, N);
const detM = det2(M);
const detN = det2(N);
const detMN = det2(MN);
// All (i, k, j) contributions: M[i][k] * N[k][j] M[i][k] * N[k][j] for i,j,k in {0,1}
// Group them by (i,j) output pair — each pair gets 2 terms (k=0, k=1)
const terms = [];
let totalSum = 0;
for (let i = 0; i < 2; i++) {
for (let j = 0; j < 2; j++) {
for (let k = 0; k < 2; k++) {
const val = M[i][k] * N[k][j];
totalSum += val;
terms.push({ i, j, k, val,
label: `(${i},${k})×(${k},${j})`
});
}
}
}
// Find max absolute value for bar scaling
const maxAbs = Math.max(...terms.map(t => Math.abs(t.val)), 1e-9);
const contDiv = document.getElementById('contributions');
contDiv.innerHTML = '';
// Group header
const header = document.createElement('div');
header.style.cssText = 'font-weight:700;font-size:.85rem;color:#555;margin-bottom:.2rem;padding:0 .5rem;';
header.textContent = `Contribution of each internal state (i,k)×(k,j) = M[i][k]·N[k][j]`;
contDiv.appendChild(header);
for (const t of terms) {
const div = document.createElement('div');
const cls = Math.abs(t.val) < 1e-9 ? 'zero' : (t.val > 0 ? 'pos' : 'neg');
div.className = 'term ' + cls;
const barPct = Math.abs(t.val) / maxAbs * 100;
const barCls = t.val >= 0 ? 'bar-pos' : 'bar-neg';
div.innerHTML = `
<span class="term-label">${t.label}</span>
<span class="term-value">${t.val > 0 ? '+' : ''}${fmt(t.val)}</span>
<div class="term-bar-wrap"><div class="term-bar ${barCls}" style="width:${barPct}%"></div></div>
`;
contDiv.appendChild(div);
}
const sumDiv = document.getElementById('summary');
const close = Math.abs(totalSum - detMN) < 1e-6;
sumDiv.innerHTML = "\n " + i18nFmt(`<div class="sum-row"><span class="sum-label">Sum of all contributions:</span><span class="sum-value">%fmt%</span></div>
<div class="sum-row"><span class="sum-label">det(M):</span><span class="sum-value">%fmt2%</span></div>
<div class="sum-row"><span class="sum-label">det(N):</span><span class="sum-value">%fmt3%</span></div>
<div class="sum-row"><span class="sum-label">det(M)·det(N) = det(M·N):</span><span class="sum-value highlight">%fmt4%</span></div>
%v%`, { fmt: fmt(totalSum), fmt2: fmt(detM), fmt3: fmt(detN), fmt4: fmt(detMN), v: close ? '<div class="match-note">✓ The contributions cancel to exactly det(M·N). The FKT algorithm computes this in polynomial time — no matter how large the planar graph.</div>' : '<div class="match-note">The sum equals the scalar sum of M·N entries, which differs from det(M·N) when the matchgate rules are not satisfied.</div>' }) + "\n ";
document.getElementById('result-area').style.display = 'block';
// Log for node verification
console.log(`M:`, JSON.stringify(M));
console.log(`N:`, JSON.stringify(N));
console.log(`det(M):`, detM, `det(N):`, detN, `det(MN):`, detMN);
console.log(`totalContributions:`, totalSum);
}
// Apply default preset on load
applyPreset('fkt');
<p class="hint">
A <b>matchgate</b> is defined by a 2×2 matrix. Compose two matchgates <b>M</b> and <b>N</b> and sum over all internal states — some contribute positively, some negatively. Watch the cancellation leave the determinant of M·N.
</p>
<div class="controls">
<div class="preset-row">
<span class="label">Preset:</span>
<button type="button" id="btn-fkt" class="preset">FKT basis</button>
<button type="button" id="btn-hadamard" class="preset">Hadamard</button>
<button type="button" id="btn-custom" class="preset">Custom</button>
</div>
<div class="matrices">
<div class="mat-block">
<div class="mat-label">Matrix M</div>
<div class="mat-grid" id="mat-M">
<input type="number" id="m00" step="0.5" value="1">
<input type="number" id="m01" step="0.5" value="1">
<input type="number" id="m10" step="0.5" value="1">
<input type="number" id="m11" step="0.5" value="-1">
</div>
</div>
<div class="mat-block">
<div class="mat-label">Matrix N</div>
<div class="mat-grid" id="mat-N">
<input type="number" id="n00" step="0.5" value="1">
<input type="number" id="n01" step="0.5" value="1">
<input type="number" id="n10" step="0.5" value="1">
<input type="number" id="n11" step="0.5" value="-1">
</div>
</div>
</div>
<button type="button" id="btn-compute">Compute contributions</button>
</div>
<div id="result-area" class="result-area" style="display:none">
<div class="contributions" id="contributions"></div>
<div class="summary" id="summary"></div>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; font-size: 14px; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.controls { display: flex; flex-direction: column; gap: .7rem; margin-bottom: .8rem; }
.preset-row { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; }
.label { font-weight: 600; color: #1d3557; }
.preset { font-size: 13px; padding: .3rem .7rem; border: 1px solid #1d3557;
background: #fff; color: #1d3557; border-radius: 6px; cursor: pointer; }
.preset:hover { background: #e8f0f8; }
.matrices { display: flex; gap: 1.2rem; flex-wrap: wrap; }
.mat-block { display: flex; flex-direction: column; gap: .3rem; }
.mat-label { font-weight: 700; color: #1d3557; font-size: .9rem; }
.mat-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 4px; }
.mat-grid input { width: 56px; padding: .35rem; border: 1px solid #bcc0c6;
border-radius: 6px; font: 600 14px ui-monospace,monospace;
text-align: center; color: #1d3557; }
#btn-compute { align-self: flex-start; font: 600 14px system-ui,sans-serif;
padding: .45rem 1rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
#btn-compute:hover { background: #163250; }
.result-area { margin-top: .5rem; }
.contributions { display: flex; flex-direction: column; gap: .3rem; margin-bottom: .7rem; }
.term { display: flex; align-items: center; gap: .5rem; font-size: .88rem;
padding: .3rem .5rem; border-radius: 6px; }
.term.pos { background: #d4edda; color: #155724; }
.term.neg { background: #f8d7da; color: #721c24; }
.term.zero { background: #f5f5f5; color: #888; }
.term-label { font-family: ui-monospace, monospace; font-weight: 700; min-width: 60px; }
.term-value { font-family: ui-monospace, monospace; min-width: 52px; text-align: right; }
.term-bar-wrap { flex: 1; background: #e0e0e0; border-radius: 4px; height: 10px; overflow: hidden; min-width: 60px; }
.term-bar { height: 100%; border-radius: 4px; transition: width .4s; }
.bar-pos { background: #28a745; }
.bar-neg { background: #dc3545; }
.summary { border-top: 2px solid #1d3557; padding-top: .6rem; font-size: .95rem; }
.sum-row { display: flex; justify-content: space-between; margin: .15rem 0; }
.sum-label { color: #555; }
.sum-value { font-family: ui-monospace, monospace; font-weight: 700; color: #1d3557; }
.highlight { font-weight: 800; font-size: 1.05rem; color: #0a5c2a; }
.match-note { margin-top: .4rem; font-size: .83rem; color: #444; padding: .4rem .6rem;
background: #e8f0f8; border-radius: 6px; border-left: 3px solid #1d3557; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Holographic algorithm demo: matchgate composition and cancellation.
// Two 2x2 matchgate matrices M and N are composed.
// The "holographic" sum is: sum_{k in {0,1}} M[i][k] * N[k][j]
// for all (i,j) combinations, showing which terms are +, which are -.
// The total equals det(M*N) = det(M)*det(N) — the FKT-computable quantity.
function getMatrix(prefix) {
const v = (id) => parseFloat(document.getElementById(id).value) || 0;
return [
[v(prefix+'00'), v(prefix+'01')],
[v(prefix+'10'), v(prefix+'11')]
];
}
function setMatrix(prefix, mat) {
const ids = [[prefix+'00', prefix+'01'], [prefix+'10', prefix+'11']];
for (let r = 0; r < 2; r++) for (let c = 0; c < 2; c++)
document.getElementById(ids[r][c]).value = mat[r][c];
}
function det2(m) { return m[0][0]*m[1][1] - m[0][1]*m[1][0]; }
function matMul(A, B) {
return [
[A[0][0]*B[0][0]+A[0][1]*B[1][0], A[0][0]*B[0][1]+A[0][1]*B[1][1]],
[A[1][0]*B[0][0]+A[1][1]*B[1][0], A[1][0]*B[0][1]+A[1][1]*B[1][1]]
];
}
function fmt(v) {
if (Math.abs(v) < 1e-9) return '0';
if (Number.isInteger(v)) return String(v);
return v.toFixed(3).replace(/\.?0+$/, '');
}
const PRESETS = {
fkt: { M: [[1,1],[1,-1]], N: [[1,1],[1,-1]] },
hadamard: { M: [[1,1],[1,-1]], N: [[1,1],[-1,1]] },
custom: { M: [[2,1],[0,1]], N: [[1,0],[1,2]] }
};
function applyPreset(name) {
const p = PRESETS[name];
setMatrix('m', p.M);
setMatrix('n', p.N);
document.querySelectorAll('.preset').forEach(b => b.style.fontWeight = '');
document.getElementById('btn-'+name).style.fontWeight = '800';
}
document.getElementById('btn-fkt').addEventListener('click', () => applyPreset('fkt'));
document.getElementById('btn-hadamard').addEventListener('click', () => applyPreset('hadamard'));
document.getElementById('btn-custom').addEventListener('click', () => applyPreset('custom'));
document.getElementById('btn-compute').addEventListener('click', compute);
function compute() {
const M = getMatrix('m');
const N = getMatrix('n');
const MN = matMul(M, N);
const detM = det2(M);
const detN = det2(N);
const detMN = det2(MN);
// All (i, k, j) contributions: M[i][k] * N[k][j] M[i][k] * N[k][j] for i,j,k in {0,1}
// Group them by (i,j) output pair — each pair gets 2 terms (k=0, k=1)
const terms = [];
let totalSum = 0;
for (let i = 0; i < 2; i++) {
for (let j = 0; j < 2; j++) {
for (let k = 0; k < 2; k++) {
const val = M[i][k] * N[k][j];
totalSum += val;
terms.push({ i, j, k, val,
label: `(${i},${k})×(${k},${j})`
});
}
}
}
// Find max absolute value for bar scaling
const maxAbs = Math.max(...terms.map(t => Math.abs(t.val)), 1e-9);
const contDiv = document.getElementById('contributions');
contDiv.innerHTML = '';
// Group header
const header = document.createElement('div');
header.style.cssText = 'font-weight:700;font-size:.85rem;color:#555;margin-bottom:.2rem;padding:0 .5rem;';
header.textContent = `Contribution of each internal state (i,k)×(k,j) = M[i][k]·N[k][j]`;
contDiv.appendChild(header);
for (const t of terms) {
const div = document.createElement('div');
const cls = Math.abs(t.val) < 1e-9 ? 'zero' : (t.val > 0 ? 'pos' : 'neg');
div.className = 'term ' + cls;
const barPct = Math.abs(t.val) / maxAbs * 100;
const barCls = t.val >= 0 ? 'bar-pos' : 'bar-neg';
div.innerHTML = `
<span class="term-label">${t.label}</span>
<span class="term-value">${t.val > 0 ? '+' : ''}${fmt(t.val)}</span>
<div class="term-bar-wrap"><div class="term-bar ${barCls}" style="width:${barPct}%"></div></div>
`;
contDiv.appendChild(div);
}
const sumDiv = document.getElementById('summary');
const close = Math.abs(totalSum - detMN) < 1e-6;
sumDiv.innerHTML = "\n " + i18nFmt(`<div class="sum-row"><span class="sum-label">Sum of all contributions:</span><span class="sum-value">%fmt%</span></div>
<div class="sum-row"><span class="sum-label">det(M):</span><span class="sum-value">%fmt2%</span></div>
<div class="sum-row"><span class="sum-label">det(N):</span><span class="sum-value">%fmt3%</span></div>
<div class="sum-row"><span class="sum-label">det(M)·det(N) = det(M·N):</span><span class="sum-value highlight">%fmt4%</span></div>
%v%`, { fmt: fmt(totalSum), fmt2: fmt(detM), fmt3: fmt(detN), fmt4: fmt(detMN), v: close ? '<div class="match-note">✓ The contributions cancel to exactly det(M·N). The FKT algorithm computes this in polynomial time — no matter how large the planar graph.</div>' : '<div class="match-note">The sum equals the scalar sum of M·N entries, which differs from det(M·N) when the matchgate rules are not satisfied.</div>' }) + "\n ";
document.getElementById('result-area').style.display = 'block';
// Log for node verification
console.log(`M:`, JSON.stringify(M));
console.log(`N:`, JSON.stringify(N));
console.log(`det(M):`, detM, `det(N):`, detN, `det(MN):`, detMN);
console.log(`totalContributions:`, totalSum);
}
// Apply default preset on load
applyPreset('fkt');
Comments
Loading comments...