The demo below uses a simple 3-query Hadamard-style LDC over a 4-bit message. The encoder spreads each bit into a codeword of length 16. You can flip codeword bits to simulate corruption, then pick any message bit to decode.
<!-- Locally Decodable Codes demo — Hadamard LDC over a 4-bit message -->
<p class="hint">A <b>Hadamard LDC</b> encodes a 4-bit message into 16 codeword bits. Click any codeword bit to flip it (simulate corruption). Then choose which message bit to decode and press Decode — the algorithm reads only 3 pairs of positions.</p>
<div class="panels">
<div class="panel">
<div class="panel-title">Original message (4 bits)</div>
<div id="msg-bits" class="bit-row"></div>
</div>
<div class="panel">
<div class="panel-title">Codeword <span class="small">(16 bits)</span></div>
<div id="cw-bits" class="bit-row cw"></div>
<div class="cw-hint">Click any bit to flip it (simulate corruption).</div>
</div>
</div>
<div class="decode-row">
<label class="decode-label">Decode message bit:</label>
<div id="bit-sel" class="bit-sel"></div>
</div>
<div class="btns">
<button id="btn-decode" type="button">Decode</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
<div id="status" class="status"></div>
<div id="log" class="log"></div>
/* Styles for the LDC 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; }
.panels { display: flex; flex-direction: column; gap: .6rem; margin-bottom: .7rem; }
.panel { background: #f4f6f8; border-radius: 8px; padding: .55rem .7rem; }
.panel-title { font-size: .78rem; font-weight: 700; color: #555; text-transform: uppercase; letter-spacing: .04em; margin-bottom: .4rem; }
.bit-row { display: flex; flex-wrap: wrap; gap: 3px; }
.bit { width: 28px; height: 28px; display: flex; align-items: center; justify-content: center;
font: 700 13px ui-monospace, monospace; border-radius: 5px; user-select: none; }
/* Message bits (fixed, shown in navy) */
.bit.msg { background: #1d3557; color: #fff; }
/* Codeword bits: normal, flipped (red), or queried (orange outline) */
.bit.cw-bit { background: #d0e4f7; color: #1d3557; border: 1px solid #aac5e0; }
.bit.cw-bit.flipped { background: #e63946; color: #fff; border-color: #c92f3c; }
.bit.cw-bit.queried { outline: 2px solid #f4a261; outline-offset: 1px; }
.bit.cw-bit { cursor: pointer; }
.cw-hint { font-size: .75rem; color: #888; margin-top: .3rem; }
.small { font-weight: 400; font-size: .75rem; }
.decode-row { display: flex; align-items: center; gap: .6rem; margin-bottom: .6rem; flex-wrap: wrap; }
.decode-label { font-size: .88rem; font-weight: 600; }
.bit-sel { display: flex; gap: 4px; }
.bit-sel .bit { cursor: pointer; background: #e8eef3; color: #1d3557; border: 2px solid transparent; }
.bit-sel .bit.active { border-color: #1d3557; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .5rem; }
button { font: 600 14px system-ui, sans-serif; padding: .42rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.status { font-size: 1rem; font-weight: 700; min-height: 1.4em; margin-bottom: .3rem; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.log { font: .8rem ui-monospace, monospace; color: #555; line-height: 1.6; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Locally Decodable Codes — Hadamard-based 3-query LDC demo
// The Hadamard code: codeword[x] = inner product of message and x over GF(2)
// Fixed 4-bit message for demonstration
const MSG = [1, 0, 1, 1]; // The 4-bit message: bits 0-3
const K = MSG.length; // message length in bits
const N = 1 << K; // codeword length = 2^K = 16
// Hadamard encoder: codeword[x] = sum_i (msg[i] * x_i) mod 2
// x iterates over all K-bit strings; cw[x] is the inner product with msg
function encode(msg) {
const cw = new Array(N);
for (let x = 0; x < N; x++) {
let dot = 0;
for (let i = 0; i < K; i++) dot ^= (msg[i] & ((x >> i) & 1));
cw[x] = dot;
}
return cw;
}
let codeword = encode(MSG); // the encoded codeword (may be corrupted by flipping)
let flipped = new Array(N).fill(false); // tracks which positions the user has flipped
let selectedBit = 0; // which message bit the user wants to decode
// 3-query decoder for message bit i: pick random r, query r and r XOR e_i
// codeword[r] XOR codeword[r XOR e_i] = msg[i] with probability 1 (no errors)
function decode3(cw, i) {
// e_i = unit vector with 1 in position i
const e_i = 1 << i;
// take 3 independent queries and majority-vote
let votes = 0;
const queries = [];
for (let t = 0; t < 3; t++) {
// random vector r
const r = Math.floor(Math.random() * N);
const s = r ^ e_i; // s = r XOR e_i so cw[r] XOR cw[s] = msg[i]
queries.push(r, s);
votes += cw[r] ^ cw[s]; // each XOR gives one vote for msg[i]
}
// majority vote: 2 or more votes out of 3 wins
return { bit: votes >= 2 ? 1 : 0, queries };
}
function renderMsg() {
const el = document.getElementById('msg-bits');
el.innerHTML = '';
MSG.forEach((b, i) => {
const d = document.createElement('div');
d.className = 'bit msg';
d.textContent = b;
d.title = fmt(`Message bit %i%`, { i: i });
el.appendChild(d);
});
}
function renderCW() {
const el = document.getElementById('cw-bits');
el.innerHTML = '';
const effective = codeword.map((b, i) => flipped[i] ? b ^ 1 : b);
for (let i = 0; i < N; i++) {
const d = document.createElement('div');
d.className = 'bit cw-bit' + (flipped[i] ? ' flipped' : '');
d.textContent = effective[i];
d.title = fmt(`Codeword position %i%%v%`, { i: i, v: flipped[i] ? ' (flipped)' : '' });
d.addEventListener('click', () => { flipped[i] = !flipped[i]; renderCW(); clearResult(); });
el.appendChild(d);
}
}
function renderBitSel() {
const el = document.getElementById('bit-sel');
el.innerHTML = '';
for (let i = 0; i < K; i++) {
const d = document.createElement('div');
d.className = 'bit' + (i === selectedBit ? ' active' : '');
d.textContent = i;
d.title = fmt(`Decode bit %i%`, { i: i });
d.addEventListener('click', () => { selectedBit = i; renderBitSel(); clearResult(); });
el.appendChild(d);
}
}
function clearResult() {
document.getElementById('status').textContent = '';
document.getElementById('status').className = 'status';
document.getElementById('log').textContent = '';
// remove highlighted query markers from previous decode
document.querySelectorAll('.cw-bit').forEach(d => d.classList.remove('queried'));
}
function doDecode() {
const effective = codeword.map((b, i) => flipped[i] ? b ^ 1 : b);
const { bit, queries } = decode3(effective, selectedBit);
const correct = MSG[selectedBit];
const ok = bit === correct;
// highlight the queried positions in the codeword display
const cwEls = document.querySelectorAll('.cw-bit');
cwEls.forEach(d => d.classList.remove('queried'));
queries.forEach(q => { if (cwEls[q]) cwEls[q].classList.add('queried'); });
const statusEl = document.getElementById('status');
statusEl.textContent = ok
? fmt(`✓ Decoded bit = %bit% (correct!)`, { bit: bit })
: fmt(`✗ Decoded bit = %bit% (wrong! True value = %correct%)`, { bit: bit, correct: correct });
statusEl.className = 'status ' + (ok ? 'ok' : 'bad');
// show the individual query pairs and votes in the log
const logEl = document.getElementById('log');
const pairs = [];
for (let t = 0; t < 3; t++) {
const r = queries[2 * t], s = queries[2 * t + 1];
pairs.push('(cw[' + r + ']=' + effective[r] + ' ⊕ cw[' + s + ']=' + effective[s] + ')=' + (effective[r] ^ effective[s]));
}
logEl.textContent = fmt(`Queries: %pairs%
Votes: %reduce%/3 → Result: %bit%`, { pairs: pairs.join(', '), reduce: queries.reduce((a, q, t) => t % 2 === 0 ? a + (effective[queries[t]] ^ effective[queries[t + 1]]) : a, 0), bit: bit });
}
document.getElementById('btn-decode').addEventListener('click', doDecode);
document.getElementById('btn-reset').addEventListener('click', () => {
flipped.fill(false);
selectedBit = 0;
renderCW();
renderBitSel();
clearResult();
});
renderMsg();
renderCW();
renderBitSel();
<!-- Locally Decodable Codes demo — Hadamard LDC over a 4-bit message -->
<p class="hint">A <b>Hadamard LDC</b> encodes a 4-bit message into 16 codeword bits. Click any codeword bit to flip it (simulate corruption). Then choose which message bit to decode and press Decode — the algorithm reads only 3 pairs of positions.</p>
<div class="panels">
<div class="panel">
<div class="panel-title">Original message (4 bits)</div>
<div id="msg-bits" class="bit-row"></div>
</div>
<div class="panel">
<div class="panel-title">Codeword <span class="small">(16 bits)</span></div>
<div id="cw-bits" class="bit-row cw"></div>
<div class="cw-hint">Click any bit to flip it (simulate corruption).</div>
</div>
</div>
<div class="decode-row">
<label class="decode-label">Decode message bit:</label>
<div id="bit-sel" class="bit-sel"></div>
</div>
<div class="btns">
<button id="btn-decode" type="button">Decode</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
<div id="status" class="status"></div>
<div id="log" class="log"></div>
/* Styles for the LDC 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; }
.panels { display: flex; flex-direction: column; gap: .6rem; margin-bottom: .7rem; }
.panel { background: #f4f6f8; border-radius: 8px; padding: .55rem .7rem; }
.panel-title { font-size: .78rem; font-weight: 700; color: #555; text-transform: uppercase; letter-spacing: .04em; margin-bottom: .4rem; }
.bit-row { display: flex; flex-wrap: wrap; gap: 3px; }
.bit { width: 28px; height: 28px; display: flex; align-items: center; justify-content: center;
font: 700 13px ui-monospace, monospace; border-radius: 5px; user-select: none; }
/* Message bits (fixed, shown in navy) */
.bit.msg { background: #1d3557; color: #fff; }
/* Codeword bits: normal, flipped (red), or queried (orange outline) */
.bit.cw-bit { background: #d0e4f7; color: #1d3557; border: 1px solid #aac5e0; }
.bit.cw-bit.flipped { background: #e63946; color: #fff; border-color: #c92f3c; }
.bit.cw-bit.queried { outline: 2px solid #f4a261; outline-offset: 1px; }
.bit.cw-bit { cursor: pointer; }
.cw-hint { font-size: .75rem; color: #888; margin-top: .3rem; }
.small { font-weight: 400; font-size: .75rem; }
.decode-row { display: flex; align-items: center; gap: .6rem; margin-bottom: .6rem; flex-wrap: wrap; }
.decode-label { font-size: .88rem; font-weight: 600; }
.bit-sel { display: flex; gap: 4px; }
.bit-sel .bit { cursor: pointer; background: #e8eef3; color: #1d3557; border: 2px solid transparent; }
.bit-sel .bit.active { border-color: #1d3557; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .5rem; }
button { font: 600 14px system-ui, sans-serif; padding: .42rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.status { font-size: 1rem; font-weight: 700; min-height: 1.4em; margin-bottom: .3rem; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.log { font: .8rem ui-monospace, monospace; color: #555; line-height: 1.6; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Locally Decodable Codes — Hadamard-based 3-query LDC demo
// The Hadamard code: codeword[x] = inner product of message and x over GF(2)
// Fixed 4-bit message for demonstration
const MSG = [1, 0, 1, 1]; // The 4-bit message: bits 0-3
const K = MSG.length; // message length in bits
const N = 1 << K; // codeword length = 2^K = 16
// Hadamard encoder: codeword[x] = sum_i (msg[i] * x_i) mod 2
// x iterates over all K-bit strings; cw[x] is the inner product with msg
function encode(msg) {
const cw = new Array(N);
for (let x = 0; x < N; x++) {
let dot = 0;
for (let i = 0; i < K; i++) dot ^= (msg[i] & ((x >> i) & 1));
cw[x] = dot;
}
return cw;
}
let codeword = encode(MSG); // the encoded codeword (may be corrupted by flipping)
let flipped = new Array(N).fill(false); // tracks which positions the user has flipped
let selectedBit = 0; // which message bit the user wants to decode
// 3-query decoder for message bit i: pick random r, query r and r XOR e_i
// codeword[r] XOR codeword[r XOR e_i] = msg[i] with probability 1 (no errors)
function decode3(cw, i) {
// e_i = unit vector with 1 in position i
const e_i = 1 << i;
// take 3 independent queries and majority-vote
let votes = 0;
const queries = [];
for (let t = 0; t < 3; t++) {
// random vector r
const r = Math.floor(Math.random() * N);
const s = r ^ e_i; // s = r XOR e_i so cw[r] XOR cw[s] = msg[i]
queries.push(r, s);
votes += cw[r] ^ cw[s]; // each XOR gives one vote for msg[i]
}
// majority vote: 2 or more votes out of 3 wins
return { bit: votes >= 2 ? 1 : 0, queries };
}
function renderMsg() {
const el = document.getElementById('msg-bits');
el.innerHTML = '';
MSG.forEach((b, i) => {
const d = document.createElement('div');
d.className = 'bit msg';
d.textContent = b;
d.title = fmt(`Message bit %i%`, { i: i });
el.appendChild(d);
});
}
function renderCW() {
const el = document.getElementById('cw-bits');
el.innerHTML = '';
const effective = codeword.map((b, i) => flipped[i] ? b ^ 1 : b);
for (let i = 0; i < N; i++) {
const d = document.createElement('div');
d.className = 'bit cw-bit' + (flipped[i] ? ' flipped' : '');
d.textContent = effective[i];
d.title = fmt(`Codeword position %i%%v%`, { i: i, v: flipped[i] ? ' (flipped)' : '' });
d.addEventListener('click', () => { flipped[i] = !flipped[i]; renderCW(); clearResult(); });
el.appendChild(d);
}
}
function renderBitSel() {
const el = document.getElementById('bit-sel');
el.innerHTML = '';
for (let i = 0; i < K; i++) {
const d = document.createElement('div');
d.className = 'bit' + (i === selectedBit ? ' active' : '');
d.textContent = i;
d.title = fmt(`Decode bit %i%`, { i: i });
d.addEventListener('click', () => { selectedBit = i; renderBitSel(); clearResult(); });
el.appendChild(d);
}
}
function clearResult() {
document.getElementById('status').textContent = '';
document.getElementById('status').className = 'status';
document.getElementById('log').textContent = '';
// remove highlighted query markers from previous decode
document.querySelectorAll('.cw-bit').forEach(d => d.classList.remove('queried'));
}
function doDecode() {
const effective = codeword.map((b, i) => flipped[i] ? b ^ 1 : b);
const { bit, queries } = decode3(effective, selectedBit);
const correct = MSG[selectedBit];
const ok = bit === correct;
// highlight the queried positions in the codeword display
const cwEls = document.querySelectorAll('.cw-bit');
cwEls.forEach(d => d.classList.remove('queried'));
queries.forEach(q => { if (cwEls[q]) cwEls[q].classList.add('queried'); });
const statusEl = document.getElementById('status');
statusEl.textContent = ok
? fmt(`✓ Decoded bit = %bit% (correct!)`, { bit: bit })
: fmt(`✗ Decoded bit = %bit% (wrong! True value = %correct%)`, { bit: bit, correct: correct });
statusEl.className = 'status ' + (ok ? 'ok' : 'bad');
// show the individual query pairs and votes in the log
const logEl = document.getElementById('log');
const pairs = [];
for (let t = 0; t < 3; t++) {
const r = queries[2 * t], s = queries[2 * t + 1];
pairs.push('(cw[' + r + ']=' + effective[r] + ' ⊕ cw[' + s + ']=' + effective[s] + ')=' + (effective[r] ^ effective[s]));
}
logEl.textContent = fmt(`Queries: %pairs%
Votes: %reduce%/3 → Result: %bit%`, { pairs: pairs.join(', '), reduce: queries.reduce((a, q, t) => t % 2 === 0 ? a + (effective[queries[t]] ^ effective[queries[t + 1]]) : a, 0), bit: bit });
}
document.getElementById('btn-decode').addEventListener('click', doDecode);
document.getElementById('btn-reset').addEventListener('click', () => {
flipped.fill(false);
selectedBit = 0;
renderCW();
renderBitSel();
clearResult();
});
renderMsg();
renderCW();
renderBitSel();
Each decode picks 3 random positions whose XOR is structurally tied to the requested bit. The majority vote of those 3 reads corrects any single corrupted query position. Notice that even with several flipped bits the decoder usually finds the right answer — but flip enough and it eventually fails.
Comments
Loading comments...