<p class="hint">Two 2×2 matrices. The <b>schoolbook</b> method uses 8 scalar
multiplications; <b>Strassen</b> uses only 7. Run both — the answer is the
same, but the multiplication count differs. Then scale the matrices up and watch
the gap explode.</p>
<div class="grids">
<div class="mwrap"><span class="lab">A</span><div id="A" class="mat"></div></div>
<div class="times">×</div>
<div class="mwrap"><span class="lab">B</span><div id="B" class="mat"></div></div>
<div class="times">=</div>
<div class="mwrap"><span class="lab">C</span><div id="C" class="mat res"></div></div>
</div>
<div class="status" id="status">Press a method to multiply A × B.</div>
<div class="btns">
<button id="naive" type="button">Schoolbook (8×)</button>
<button id="strassen" type="button">Strassen (7×)</button>
<button id="rand" type="button" class="ghost">New numbers</button>
</div>
<div class="scale">
<button id="scaleup" type="button" class="ghost">Scale it up →</button>
<table id="scaletbl"><thead><tr><th>n</th><th>Schoolbook n³</th><th>Strassen n^2.807</th><th>Saved</th></tr></thead><tbody></tbody></table>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
.grids { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; margin: .4rem 0 .2rem; }
.mwrap { text-align: center; }
.lab { font: 700 13px ui-monospace, monospace; color: #1d3557; display: block; margin-bottom: 3px; }
.mat { display: grid; grid-template-columns: repeat(2, 44px); gap: 4px; }
.mat > div { width: 44px; height: 40px; display: flex; align-items: center; justify-content: center;
font: 700 15px ui-monospace, monospace; background: #e8eef3; color: #1d3557;
border: 1px solid #cdd9e3; border-radius: 7px; }
.mat.res > div { background: #f3f5f7; color: #555; }
.mat.res.done > div { background: #d6f0df; color: #0a7d33; border-color: #aedcc0; }
.times { font: 700 20px system-ui; color: #888; }
.status { font-size: .95rem; font-weight: 600; margin: .6rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: 1rem; }
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; }
.scale { border-top: 1px solid #e3e7ec; padding-top: .8rem; }
table { border-collapse: collapse; font: 13px ui-monospace, monospace; margin-top: .6rem; width: 100%; }
th, td { border: 1px solid #dfe4ea; padding: .35rem .5rem; text-align: right; }
th { background: #f1f4f7; color: #1d3557; }
td.saved { color: #0a7d33; font-weight: 700; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Matrix multiplication: checking a product is trivial, but how FEW scalar
// multiplications does it take? Schoolbook uses 8 for a 2x2 block; Strassen
// (1969) uses 7. Recursively that lowers the exponent from 3 to log2(7)~2.807.
// The true optimal exponent omega is an OPEN problem (only known: 2 <= omega).
let A = [[1,2],[3,4]], B = [[5,6],[7,8]];
function drawMat(id, m){ const el=document.getElementById(id); el.innerHTML='';
for(let r=0;r<2;r++)for(let c=0;c<2;c++){ const d=document.createElement('div'); d.textContent=m[r][c]; el.appendChild(d);} }
function drawRes(m, done){ const el=document.getElementById('C'); el.className='mat res'+(done?' done':''); el.innerHTML='';
for(let r=0;r<2;r++)for(let c=0;c<2;c++){ const d=document.createElement('div'); d.textContent=(m?m[r][c]:'?'); el.appendChild(d);} }
function setStatus(t,cls){ const s=document.getElementById('status'); s.textContent=t; s.className='status '+cls; }
// Schoolbook: 8 scalar multiplications for a 2x2 result.
function naive(A,B){ let mults=0; const C=[[0,0],[0,0]];
for(let i=0;i<2;i++)for(let j=0;j<2;j++){ let s=0;
for(let k=0;k<2;k++){ s+=A[i][k]*B[k][j]; mults++; } C[i][j]=s; }
return {C, mults}; }
// Strassen: 7 products (the m1..m7), recombined with adds/subtracts only.
function strassen(A,B){ const a=A[0][0],b=A[0][1],c=A[1][0],d=A[1][1];
const e=B[0][0],f=B[0][1],g=B[1][0],h=B[1][1];
const m1=(a+d)*(e+h), m2=(c+d)*e, m3=a*(f-h), m4=d*(g-e),
m5=(a+b)*h, m6=(c-a)*(e+f), m7=(b-d)*(g+h);
const C=[[m1+m4-m5+m7, m3+m5],[m2+m4, m1-m2+m3+m6]];
return {C, mults:7}; }
function runNaive(){ const {C,mults}=naive(A,B); drawRes(C,true);
setStatus(fmt('Schoolbook done with %mults% scalar multiplications.', { mults: mults }),'ok'); }
function runStrassen(){ const {C,mults}=strassen(A,B); drawRes(C,true);
setStatus(fmt('Strassen done with %mults% scalar multiplications — same answer, one fewer product.', { mults: mults }),'ok'); }
function newNums(){ const rnd=()=>Math.floor(Math.random()*9)+1;
A=[[rnd(),rnd()],[rnd(),rnd()]]; B=[[rnd(),rnd()],[rnd(),rnd()]];
drawMat('A',A); drawMat('B',B); drawRes(null,false); setStatus('Press a method to multiply A × B.',''); }
function scaleUp(){ const body=document.querySelector('#scaletbl tbody'); body.innerHTML='';
for(let p=1;p<=8;p++){ const n=Math.pow(2,p);
const naiveOps=Math.round(Math.pow(n,3));
const strOps=Math.round(Math.pow(7,p)); // exactly 7^log2(n) products, recursive Strassen
const tr=document.createElement('tr');
tr.innerHTML='<td>'+n+'</td><td>'+naiveOps.toLocaleString()+'</td><td>'+strOps.toLocaleString()+
'</td><td class="saved">'+(100*(1-strOps/naiveOps)).toFixed(0)+'%</td>';
body.appendChild(tr); } }
document.getElementById('naive').addEventListener('click',runNaive);
document.getElementById('strassen').addEventListener('click',runStrassen);
document.getElementById('rand').addEventListener('click',newNums);
document.getElementById('scaleup').addEventListener('click',scaleUp);
drawMat('A',A); drawMat('B',B); drawRes(null,false);
<p class="hint">Two 2×2 matrices. The <b>schoolbook</b> method uses 8 scalar
multiplications; <b>Strassen</b> uses only 7. Run both — the answer is the
same, but the multiplication count differs. Then scale the matrices up and watch
the gap explode.</p>
<div class="grids">
<div class="mwrap"><span class="lab">A</span><div id="A" class="mat"></div></div>
<div class="times">×</div>
<div class="mwrap"><span class="lab">B</span><div id="B" class="mat"></div></div>
<div class="times">=</div>
<div class="mwrap"><span class="lab">C</span><div id="C" class="mat res"></div></div>
</div>
<div class="status" id="status">Press a method to multiply A × B.</div>
<div class="btns">
<button id="naive" type="button">Schoolbook (8×)</button>
<button id="strassen" type="button">Strassen (7×)</button>
<button id="rand" type="button" class="ghost">New numbers</button>
</div>
<div class="scale">
<button id="scaleup" type="button" class="ghost">Scale it up →</button>
<table id="scaletbl"><thead><tr><th>n</th><th>Schoolbook n³</th><th>Strassen n^2.807</th><th>Saved</th></tr></thead><tbody></tbody></table>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
.grids { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; margin: .4rem 0 .2rem; }
.mwrap { text-align: center; }
.lab { font: 700 13px ui-monospace, monospace; color: #1d3557; display: block; margin-bottom: 3px; }
.mat { display: grid; grid-template-columns: repeat(2, 44px); gap: 4px; }
.mat > div { width: 44px; height: 40px; display: flex; align-items: center; justify-content: center;
font: 700 15px ui-monospace, monospace; background: #e8eef3; color: #1d3557;
border: 1px solid #cdd9e3; border-radius: 7px; }
.mat.res > div { background: #f3f5f7; color: #555; }
.mat.res.done > div { background: #d6f0df; color: #0a7d33; border-color: #aedcc0; }
.times { font: 700 20px system-ui; color: #888; }
.status { font-size: .95rem; font-weight: 600; margin: .6rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: 1rem; }
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; }
.scale { border-top: 1px solid #e3e7ec; padding-top: .8rem; }
table { border-collapse: collapse; font: 13px ui-monospace, monospace; margin-top: .6rem; width: 100%; }
th, td { border: 1px solid #dfe4ea; padding: .35rem .5rem; text-align: right; }
th { background: #f1f4f7; color: #1d3557; }
td.saved { color: #0a7d33; font-weight: 700; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Matrix multiplication: checking a product is trivial, but how FEW scalar
// multiplications does it take? Schoolbook uses 8 for a 2x2 block; Strassen
// (1969) uses 7. Recursively that lowers the exponent from 3 to log2(7)~2.807.
// The true optimal exponent omega is an OPEN problem (only known: 2 <= omega).
let A = [[1,2],[3,4]], B = [[5,6],[7,8]];
function drawMat(id, m){ const el=document.getElementById(id); el.innerHTML='';
for(let r=0;r<2;r++)for(let c=0;c<2;c++){ const d=document.createElement('div'); d.textContent=m[r][c]; el.appendChild(d);} }
function drawRes(m, done){ const el=document.getElementById('C'); el.className='mat res'+(done?' done':''); el.innerHTML='';
for(let r=0;r<2;r++)for(let c=0;c<2;c++){ const d=document.createElement('div'); d.textContent=(m?m[r][c]:'?'); el.appendChild(d);} }
function setStatus(t,cls){ const s=document.getElementById('status'); s.textContent=t; s.className='status '+cls; }
// Schoolbook: 8 scalar multiplications for a 2x2 result.
function naive(A,B){ let mults=0; const C=[[0,0],[0,0]];
for(let i=0;i<2;i++)for(let j=0;j<2;j++){ let s=0;
for(let k=0;k<2;k++){ s+=A[i][k]*B[k][j]; mults++; } C[i][j]=s; }
return {C, mults}; }
// Strassen: 7 products (the m1..m7), recombined with adds/subtracts only.
function strassen(A,B){ const a=A[0][0],b=A[0][1],c=A[1][0],d=A[1][1];
const e=B[0][0],f=B[0][1],g=B[1][0],h=B[1][1];
const m1=(a+d)*(e+h), m2=(c+d)*e, m3=a*(f-h), m4=d*(g-e),
m5=(a+b)*h, m6=(c-a)*(e+f), m7=(b-d)*(g+h);
const C=[[m1+m4-m5+m7, m3+m5],[m2+m4, m1-m2+m3+m6]];
return {C, mults:7}; }
function runNaive(){ const {C,mults}=naive(A,B); drawRes(C,true);
setStatus(fmt('Schoolbook done with %mults% scalar multiplications.', { mults: mults }),'ok'); }
function runStrassen(){ const {C,mults}=strassen(A,B); drawRes(C,true);
setStatus(fmt('Strassen done with %mults% scalar multiplications — same answer, one fewer product.', { mults: mults }),'ok'); }
function newNums(){ const rnd=()=>Math.floor(Math.random()*9)+1;
A=[[rnd(),rnd()],[rnd(),rnd()]]; B=[[rnd(),rnd()],[rnd(),rnd()]];
drawMat('A',A); drawMat('B',B); drawRes(null,false); setStatus('Press a method to multiply A × B.',''); }
function scaleUp(){ const body=document.querySelector('#scaletbl tbody'); body.innerHTML='';
for(let p=1;p<=8;p++){ const n=Math.pow(2,p);
const naiveOps=Math.round(Math.pow(n,3));
const strOps=Math.round(Math.pow(7,p)); // exactly 7^log2(n) products, recursive Strassen
const tr=document.createElement('tr');
tr.innerHTML='<td>'+n+'</td><td>'+naiveOps.toLocaleString()+'</td><td>'+strOps.toLocaleString()+
'</td><td class="saved">'+(100*(1-strOps/naiveOps)).toFixed(0)+'%</td>';
body.appendChild(tr); } }
document.getElementById('naive').addEventListener('click',runNaive);
document.getElementById('strassen').addEventListener('click',runStrassen);
document.getElementById('rand').addEventListener('click',newNums);
document.getElementById('scaleup').addEventListener('click',scaleUp);
drawMat('A',A); drawMat('B',B); drawRes(null,false);
One saved multiplication sounds trivial. But the trick is recursive: split big matrices into blocks and apply it again and again. Press Scale it up and watch the counts diverge — the naive method climbs as n3 while Strassen climbs as n2.807. Verifying that a result is correct is easy (just check the entries); finding the fewest possible multiplications is the hard, still-open part.
Comments
Loading comments...