The demo below works with RM(1, 3) — the first-order Reed-Muller code in m=3 variables. Every message is a 4-bit vector (a0,a1,a2,a3); the codeword evaluates the linear polynomial f(x1,x2,x3)=a0+a1x1+a2x2+a3x3 (all arithmetic mod 2) at all 23=8 points of {0,1}3.
Pick a message, introduce noise by clicking individual codeword bits, then press Decode to watch Reed's majority-logic algorithm vote the errors away.
<!-- Reed-Muller RM(1,3) interactive majority-logic decoder -->
<div class="rm-demo">
<div class="controls">
<div class="msg-row">
<span class="label">Message (4 bits):</span>
<div id="msg-bits" class="bit-row"></div>
<button id="rand-btn" type="button" class="ghost-btn">New message</button>
</div>
<div class="cw-row">
<span class="label">Codeword (8 bits):</span>
<div id="cw-bits" class="bit-row clickable" title="Click a codeword bit to flip it (introduce noise)"></div>
</div>
<div class="noise-row">
<span class="label">Errors introduced:</span>
<span id="error-count">0</span>
</div>
</div>
<div class="btn-row">
<button id="decode-btn" type="button">Decode</button>
<button id="reset-btn" type="button" class="ghost-btn">Reset noise</button>
</div>
<div id="result" class="result hidden"></div>
<div id="steps" class="steps hidden"></div>
<p class="hint-text">Click any green codeword bit to flip it and introduce noise. RM(1,3) can correct 1 error; try flipping 1, 2, or 3 bits and see how decoding fares.</p>
</div>
/* Styles for the Reed-Muller majority-logic decoder demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; }
.rm-demo { padding: .5rem; max-width: 480px; }
.controls { display: flex; flex-direction: column; gap: .6rem; margin-bottom: .8rem; }
.msg-row, .cw-row, .noise-row { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.label { font-size: .8rem; font-weight: 600; color: #555; min-width: 90px; }
.bit-row { display: flex; gap: 4px; flex-wrap: wrap; }
.bit {
width: 34px; height: 34px; display: flex; align-items: center; justify-content: center;
font: 700 15px ui-monospace, monospace; border-radius: 6px; user-select: none;
}
.bit.msg-bit { background: #dbeafe; color: #1e3a8a; border: 1px solid #93c5fd; }
.bit.cw-bit { background: #e8eef3; color: #1d3557; border: 1px solid #cdd9e3; }
.bit.cw-bit.flipped { background: #fee2e2; color: #991b1b; border-color: #fca5a5; }
.clickable .bit { cursor: pointer; transition: background .1s; }
.clickable .bit:hover { filter: brightness(.93); }
#error-count { font: 700 15px ui-monospace; color: #b45309; }
.btn-row { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .7rem; }
button {
font: 600 13px system-ui; padding: .4rem .85rem; border-radius: 7px; cursor: pointer;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
}
button.ghost-btn { background: #fff; color: #1d3557; }
.result {
font-size: .95rem; font-weight: 600; padding: .45rem .7rem; border-radius: 7px;
margin-bottom: .6rem;
}
.result.ok { background: #dcfce7; color: #166534; }
.result.fail { background: #fee2e2; color: #991b1b; }
.hidden { display: none; }
.steps { font-size: .8rem; color: #444; border-top: 1px solid #e5e7eb; padding-top: .5rem; }
.steps p { margin: .2rem 0; }
.hint-text { font-size: .78rem; color: #666; margin-top: .6rem; line-height: 1.5; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Reed-Muller RM(1,3) majority-logic decoder demo
// RM(1,3): length 8, dimension 4, min distance 4 — corrects 1 error, detects up to 3
// Encode: evaluate f(x1,x2,x3) = a0 + a1*x1 + a2*x2 + a3*x3 (mod 2) at all 8 binary points
// All 8 points in {0,1}^3, enumerated in binary order
const POINTS = [];
for (let i = 0; i < 8; i++) {
POINTS.push([(i>>2)&1, (i>>1)&1, i&1]);
}
// Encode a 4-bit message as a RM(1,3) codeword (length 8)
function encode(msg) {
// msg = [a0, a1, a2, a3]
// Evaluate the linear polynomial f = a0 + a1*x1 + a2*x2 + a3*x3 (mod 2)
return POINTS.map(([x1, x2, x3]) =>
(msg[0] ^ (msg[1] & x1) ^ (msg[2] & x2) ^ (msg[3] & x3)) & 1
);
}
// Majority-logic decode a (possibly noisy) received word back to a 4-bit message
// Reed's algorithm: decode each linear coefficient a1..a3 first, then a0
function decode(received) {
const log = [];
const est = [0, 0, 0, 0]; // estimated message bits
// Decode a1, a2, a3 using coordinate differences (XOR with standard basis vector)
// a1: majority over pairs (i, i XOR e1) where e1 = (0,0,1) -> index 1
// a2: majority over pairs (i, i XOR e2) where e2 = (0,1,0) -> index 2
// a3: majority over pairs (i, i XOR e3) where e3 = (1,0,0) -> index 4
const masks = [4, 2, 1]; // XOR masks: a1 toggles x1 (bit2 of i)->4, a2 toggles x2 (bit1)->2, a3 toggles x3 (bit0)->1
for (let bit = 1; bit <= 3; bit++) {
let votes0 = 0, votes1 = 0;
const mask = masks[bit - 1];
// For each pair of indices (i, j=i XOR mask), vote: received[i] XOR received[j] = a_bit
for (let i = 0; i < 8; i++) {
const j = i ^ mask;
if (i < j) { // each unordered pair counted once
const xorVal = received[i] ^ received[j];
if (xorVal === 0) votes0++;
else votes1++;
}
}
est[bit] = votes1 > votes0 ? 1 : 0;
log.push(fmt('a%bit%: %votes1% votes for 1 vs %votes0% votes for 0 → a%bit% = %v%', { bit: bit, votes1: votes1, votes0: votes0, v: est[bit] }));
}
// Decode a0: majority vote of (received[i] XOR linear_part)
// a0 = majority of (received[i] XOR a1*x1 XOR a2*x2 XOR a3*x3) over all i
let cnt0 = 0, cnt1 = 0;
for (let i = 0; i < 8; i++) {
const [x1, x2, x3] = POINTS[i];
const linear = (est[1] & x1) ^ (est[2] & x2) ^ (est[3] & x3);
const v = received[i] ^ linear;
if (v === 0) cnt0++; else cnt1++;
}
est[0] = cnt1 > cnt0 ? 1 : 0;
log.push(fmt('a0: %cnt1% votes for 1 vs %cnt0% votes for 0 → a0 = %v%', { cnt1: cnt1, cnt0: cnt0, v: est[0] }));
return { est, log };
}
// Application state: current message, its codeword, and the (possibly flipped) received word
let currentMsg = [0, 0, 0, 0];
let currentCW = encode(currentMsg);
let receivedCW = [...currentCW];
function countErrors() {
return receivedCW.reduce((s, b, i) => s + (b !== currentCW[i] ? 1 : 0), 0);
}
function renderMsg() {
const container = document.getElementById('msg-bits');
container.innerHTML = '';
currentMsg.forEach((b, i) => {
const d = document.createElement('div');
d.className = 'bit msg-bit';
d.textContent = b;
container.appendChild(d);
});
}
function renderCW() {
const container = document.getElementById('cw-bits');
container.innerHTML = '';
receivedCW.forEach((b, i) => {
const d = document.createElement('div');
d.className = 'bit cw-bit' + (b !== currentCW[i] ? ' flipped' : '');
d.textContent = b;
d.addEventListener('click', () => {
receivedCW[i] ^= 1;
document.getElementById('result').classList.add('hidden');
document.getElementById('steps').classList.add('hidden');
renderCW();
document.getElementById('error-count').textContent = countErrors();
});
container.appendChild(d);
});
}
function randomMsg() {
currentMsg = Array.from({length: 4}, () => Math.random() < 0.5 ? 0 : 1);
currentCW = encode(currentMsg);
receivedCW = [...currentCW];
document.getElementById('error-count').textContent = '0';
document.getElementById('result').classList.add('hidden');
document.getElementById('steps').classList.add('hidden');
renderMsg();
renderCW();
}
document.getElementById('rand-btn').addEventListener('click', randomMsg);
document.getElementById('decode-btn').addEventListener('click', () => {
const { est, log } = decode(receivedCW);
const ok = est.every((v, i) => v === currentMsg[i]);
const res = document.getElementById('result');
res.className = 'result ' + (ok ? 'ok' : 'fail');
res.textContent = ok
? fmt('Decoded: [%est%] — correct!', { est: est.join(', ') })
: fmt('Decoded: [%est%] — wrong! Original was [%currentMsg%]', { est: est.join(', '), currentMsg: currentMsg.join(', ') });
res.classList.remove('hidden');
const stepsEl = document.getElementById('steps');
stepsEl.innerHTML = fmt('<p><strong>Majority-logic voting steps:</strong></p>%map%', { map: log.map(l => `<p>${l}</p>`).join('') });
stepsEl.classList.remove('hidden');
});
document.getElementById('reset-btn').addEventListener('click', () => {
receivedCW = [...currentCW];
document.getElementById('error-count').textContent = '0';
document.getElementById('result').classList.add('hidden');
document.getElementById('steps').classList.add('hidden');
renderCW();
});
// Initialize with a random message on load
randomMsg();
<!-- Reed-Muller RM(1,3) interactive majority-logic decoder -->
<div class="rm-demo">
<div class="controls">
<div class="msg-row">
<span class="label">Message (4 bits):</span>
<div id="msg-bits" class="bit-row"></div>
<button id="rand-btn" type="button" class="ghost-btn">New message</button>
</div>
<div class="cw-row">
<span class="label">Codeword (8 bits):</span>
<div id="cw-bits" class="bit-row clickable" title="Click a codeword bit to flip it (introduce noise)"></div>
</div>
<div class="noise-row">
<span class="label">Errors introduced:</span>
<span id="error-count">0</span>
</div>
</div>
<div class="btn-row">
<button id="decode-btn" type="button">Decode</button>
<button id="reset-btn" type="button" class="ghost-btn">Reset noise</button>
</div>
<div id="result" class="result hidden"></div>
<div id="steps" class="steps hidden"></div>
<p class="hint-text">Click any green codeword bit to flip it and introduce noise. RM(1,3) can correct 1 error; try flipping 1, 2, or 3 bits and see how decoding fares.</p>
</div>
/* Styles for the Reed-Muller majority-logic decoder demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; }
.rm-demo { padding: .5rem; max-width: 480px; }
.controls { display: flex; flex-direction: column; gap: .6rem; margin-bottom: .8rem; }
.msg-row, .cw-row, .noise-row { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.label { font-size: .8rem; font-weight: 600; color: #555; min-width: 90px; }
.bit-row { display: flex; gap: 4px; flex-wrap: wrap; }
.bit {
width: 34px; height: 34px; display: flex; align-items: center; justify-content: center;
font: 700 15px ui-monospace, monospace; border-radius: 6px; user-select: none;
}
.bit.msg-bit { background: #dbeafe; color: #1e3a8a; border: 1px solid #93c5fd; }
.bit.cw-bit { background: #e8eef3; color: #1d3557; border: 1px solid #cdd9e3; }
.bit.cw-bit.flipped { background: #fee2e2; color: #991b1b; border-color: #fca5a5; }
.clickable .bit { cursor: pointer; transition: background .1s; }
.clickable .bit:hover { filter: brightness(.93); }
#error-count { font: 700 15px ui-monospace; color: #b45309; }
.btn-row { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .7rem; }
button {
font: 600 13px system-ui; padding: .4rem .85rem; border-radius: 7px; cursor: pointer;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
}
button.ghost-btn { background: #fff; color: #1d3557; }
.result {
font-size: .95rem; font-weight: 600; padding: .45rem .7rem; border-radius: 7px;
margin-bottom: .6rem;
}
.result.ok { background: #dcfce7; color: #166534; }
.result.fail { background: #fee2e2; color: #991b1b; }
.hidden { display: none; }
.steps { font-size: .8rem; color: #444; border-top: 1px solid #e5e7eb; padding-top: .5rem; }
.steps p { margin: .2rem 0; }
.hint-text { font-size: .78rem; color: #666; margin-top: .6rem; line-height: 1.5; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Reed-Muller RM(1,3) majority-logic decoder demo
// RM(1,3): length 8, dimension 4, min distance 4 — corrects 1 error, detects up to 3
// Encode: evaluate f(x1,x2,x3) = a0 + a1*x1 + a2*x2 + a3*x3 (mod 2) at all 8 binary points
// All 8 points in {0,1}^3, enumerated in binary order
const POINTS = [];
for (let i = 0; i < 8; i++) {
POINTS.push([(i>>2)&1, (i>>1)&1, i&1]);
}
// Encode a 4-bit message as a RM(1,3) codeword (length 8)
function encode(msg) {
// msg = [a0, a1, a2, a3]
// Evaluate the linear polynomial f = a0 + a1*x1 + a2*x2 + a3*x3 (mod 2)
return POINTS.map(([x1, x2, x3]) =>
(msg[0] ^ (msg[1] & x1) ^ (msg[2] & x2) ^ (msg[3] & x3)) & 1
);
}
// Majority-logic decode a (possibly noisy) received word back to a 4-bit message
// Reed's algorithm: decode each linear coefficient a1..a3 first, then a0
function decode(received) {
const log = [];
const est = [0, 0, 0, 0]; // estimated message bits
// Decode a1, a2, a3 using coordinate differences (XOR with standard basis vector)
// a1: majority over pairs (i, i XOR e1) where e1 = (0,0,1) -> index 1
// a2: majority over pairs (i, i XOR e2) where e2 = (0,1,0) -> index 2
// a3: majority over pairs (i, i XOR e3) where e3 = (1,0,0) -> index 4
const masks = [4, 2, 1]; // XOR masks: a1 toggles x1 (bit2 of i)->4, a2 toggles x2 (bit1)->2, a3 toggles x3 (bit0)->1
for (let bit = 1; bit <= 3; bit++) {
let votes0 = 0, votes1 = 0;
const mask = masks[bit - 1];
// For each pair of indices (i, j=i XOR mask), vote: received[i] XOR received[j] = a_bit
for (let i = 0; i < 8; i++) {
const j = i ^ mask;
if (i < j) { // each unordered pair counted once
const xorVal = received[i] ^ received[j];
if (xorVal === 0) votes0++;
else votes1++;
}
}
est[bit] = votes1 > votes0 ? 1 : 0;
log.push(fmt('a%bit%: %votes1% votes for 1 vs %votes0% votes for 0 → a%bit% = %v%', { bit: bit, votes1: votes1, votes0: votes0, v: est[bit] }));
}
// Decode a0: majority vote of (received[i] XOR linear_part)
// a0 = majority of (received[i] XOR a1*x1 XOR a2*x2 XOR a3*x3) over all i
let cnt0 = 0, cnt1 = 0;
for (let i = 0; i < 8; i++) {
const [x1, x2, x3] = POINTS[i];
const linear = (est[1] & x1) ^ (est[2] & x2) ^ (est[3] & x3);
const v = received[i] ^ linear;
if (v === 0) cnt0++; else cnt1++;
}
est[0] = cnt1 > cnt0 ? 1 : 0;
log.push(fmt('a0: %cnt1% votes for 1 vs %cnt0% votes for 0 → a0 = %v%', { cnt1: cnt1, cnt0: cnt0, v: est[0] }));
return { est, log };
}
// Application state: current message, its codeword, and the (possibly flipped) received word
let currentMsg = [0, 0, 0, 0];
let currentCW = encode(currentMsg);
let receivedCW = [...currentCW];
function countErrors() {
return receivedCW.reduce((s, b, i) => s + (b !== currentCW[i] ? 1 : 0), 0);
}
function renderMsg() {
const container = document.getElementById('msg-bits');
container.innerHTML = '';
currentMsg.forEach((b, i) => {
const d = document.createElement('div');
d.className = 'bit msg-bit';
d.textContent = b;
container.appendChild(d);
});
}
function renderCW() {
const container = document.getElementById('cw-bits');
container.innerHTML = '';
receivedCW.forEach((b, i) => {
const d = document.createElement('div');
d.className = 'bit cw-bit' + (b !== currentCW[i] ? ' flipped' : '');
d.textContent = b;
d.addEventListener('click', () => {
receivedCW[i] ^= 1;
document.getElementById('result').classList.add('hidden');
document.getElementById('steps').classList.add('hidden');
renderCW();
document.getElementById('error-count').textContent = countErrors();
});
container.appendChild(d);
});
}
function randomMsg() {
currentMsg = Array.from({length: 4}, () => Math.random() < 0.5 ? 0 : 1);
currentCW = encode(currentMsg);
receivedCW = [...currentCW];
document.getElementById('error-count').textContent = '0';
document.getElementById('result').classList.add('hidden');
document.getElementById('steps').classList.add('hidden');
renderMsg();
renderCW();
}
document.getElementById('rand-btn').addEventListener('click', randomMsg);
document.getElementById('decode-btn').addEventListener('click', () => {
const { est, log } = decode(receivedCW);
const ok = est.every((v, i) => v === currentMsg[i]);
const res = document.getElementById('result');
res.className = 'result ' + (ok ? 'ok' : 'fail');
res.textContent = ok
? fmt('Decoded: [%est%] — correct!', { est: est.join(', ') })
: fmt('Decoded: [%est%] — wrong! Original was [%currentMsg%]', { est: est.join(', '), currentMsg: currentMsg.join(', ') });
res.classList.remove('hidden');
const stepsEl = document.getElementById('steps');
stepsEl.innerHTML = fmt('<p><strong>Majority-logic voting steps:</strong></p>%map%', { map: log.map(l => `<p>${l}</p>`).join('') });
stepsEl.classList.remove('hidden');
});
document.getElementById('reset-btn').addEventListener('click', () => {
receivedCW = [...currentCW];
document.getElementById('error-count').textContent = '0';
document.getElementById('result').classList.add('hidden');
document.getElementById('steps').classList.add('hidden');
renderCW();
});
// Initialize with a random message on load
randomMsg();
Notice: the code is a [8,4,4]2 code — length 8, dimension 4, minimum distance 4 — so it can correct any single error (distance ⌊(4−1)/2⌋=1) and detect up to 3 errors. Flip two bits and it still decodes correctly; flip three and it may fail.
Comments
Loading comments...