<!-- Cholesky demo: show A and L side by side, step through the factorization -->
<div class="container">
<div class="section">
<h3>Matrix A (edit to try your own)</h3>
<p class="hint">Click any cell to change a value. A is kept symmetric automatically.</p>
<div class="matrix-wrap">
<table id="matA" class="matrix"></table>
</div>
</div>
<div class="section">
<h3>Cholesky factor L</h3>
<p class="hint">Built column by column. Yellow = current entry, green = done.</p>
<div class="matrix-wrap">
<table id="matL" class="matrix"></table>
</div>
</div>
</div>
<div class="btns">
<button id="btnStep" type="button">Next entry</button>
<button id="btnAll" type="button">Compute all</button>
<button id="btnReset" type="button" class="ghost">Reset</button>
</div>
<div id="status" class="status"></div>
<div id="formula" class="formula"></div>
/* styles for the Cholesky decomposition demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: .4rem; }
h3 { margin: 0 0 .3rem; font-size: 1rem; color: #1d3557; }
.container { display: flex; gap: 1.5rem; flex-wrap: wrap; margin-bottom: .6rem; }
.section { flex: 1 1 140px; }
.hint { font-size: .82rem; color: #555; margin: 0 0 .4rem; line-height: 1.4; }
.matrix-wrap { display: inline-block; border-left: 2px solid #1d3557; border-right: 2px solid #1d3557;
padding: 4px 8px; }
.matrix { border-collapse: separate; border-spacing: 4px; }
.matrix td { width: 44px; height: 36px; text-align: center; font: 600 14px ui-monospace, monospace;
background: #e8eef3; border-radius: 6px; border: 1px solid #cdd9e3; color: #1d3557;
transition: background .25s, color .25s; }
.matrix td.editable { background: #f0f4f8; cursor: text; }
.matrix td.zero { color: #aaa; }
.matrix td.active { background: #ffd166; border-color: #e0a800; color: #333; }
.matrix td.done { background: #c8f0d4; border-color: #5cb87a; }
.matrix td.empty { background: #f7f7f7; color: #bbb; border-color: #ddd; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .5rem; }
button { font: 600 14px system-ui; padding: .4rem .85rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button:disabled { opacity: .45; cursor: default; }
button.ghost { background: #fff; color: #1d3557; }
.status { font-size: .95rem; font-weight: 600; min-height: 1.3em; margin-bottom: .3rem; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.status.info { color: #1d3557; }
.formula { font-size: .83rem; color: #555; min-height: 1.2em; font-family: ui-monospace, monospace; }
/* Cholesky decomposition demo — build L column by column from A = LL^T */
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
/* Default symmetric positive-definite 3x3 matrix A */
const DEFAULT_A = [[4, 2, -2], [2, 5, 3], [-2, 3, 14]];
let A = DEFAULT_A.map(r => r.slice());
let L = [[0,0,0],[0,0,0],[0,0,0]];
const N = 3;
/* Current position in the column-by-column sweep */
let stepJ = 0; // current column being filled
let stepI = 0; // current row within that column
let done = false;
/* Render a 3x3 matrix into the given table element */
function renderMatrix(tableId, data, highlights, emptyBelow) {
const tbl = document.getElementById(tableId);
tbl.innerHTML = '';
for (let i = 0; i < N; i++) {
const tr = document.createElement('tr');
for (let j = 0; j < N; j++) {
const td = document.createElement('td');
const key = i + ',' + j;
if (emptyBelow && j > i) {
/* upper triangle of L is always zero */
td.textContent = '0';
td.className = 'zero';
} else if (highlights && highlights.active && highlights.active === key) {
td.textContent = fmt4(data[i][j]);
td.className = 'active';
} else if (highlights && highlights.done && highlights.done.has(key)) {
td.textContent = fmt4(data[i][j]);
td.className = 'done';
} else if (emptyBelow && i > 0 && data[i][j] === 0 && !isDone(i, j)) {
td.textContent = '?';
td.className = 'empty';
} else {
td.textContent = fmt4(data[i][j]);
td.className = data[i][j] === 0 ? 'zero' : '';
}
tr.appendChild(td);
}
tbl.appendChild(tr);
}
}
function isDone(i, j) {
/* Returns true if entry (i,j) has already been computed */
if (j < stepJ) return true;
if (j === stepJ && i < stepI) return true;
return false;
}
function fmt4(v) {
/* Format a number to at most 3 decimal places, stripping trailing zeros */
if (v === 0) return '0';
const s = v.toFixed(3).replace(/\.?0+$/, '');
return s;
}
function getDoneSet() {
/* Build the set of already-computed L entries */
const s = new Set();
for (let j = 0; j < stepJ; j++)
for (let i = j; i < N; i++) s.add(i + ',' + j);
for (let i = stepJ; i < stepI; i++) s.add(i + ',' + stepJ);
return s;
}
function renderAll() {
const doneSet = getDoneSet();
const activeKey = (stepJ < N && !done) ? stepI + ',' + stepJ : null;
renderMatrix('matA', A, null, false);
renderMatrix('matL', L, { active: activeKey, done: doneSet }, true);
}
/* Compute the next entry of L using the Cholesky recurrence */
function choleskyStep() {
if (done) return;
if (stepJ >= N) { done = true; return; }
const j = stepJ;
const i = stepI;
if (i === j) {
/* Diagonal entry: l_jj = sqrt(a_jj - sum_{k<j} l_jk^2) */
let sum = 0;
for (let k = 0; k < j; k++) sum += L[j][k] * L[j][k];
const val = A[j][j] - sum;
if (val <= 0) {
setStatus('Matrix is not positive-definite — square root of a non-positive number.', 'bad');
done = true;
return;
}
L[j][j] = Math.sqrt(val);
setFormula(i18nFmt('l[%i%][%i%] = sqrt(%val% - %sum%) = %res%', { i: j, val: A[j][j].toFixed(3), sum: sum.toFixed(3), res: L[j][j].toFixed(4) }));
} else {
/* Off-diagonal entry: l_ij = (a_ij - sum_{k<j} l_ik * l_jk) / l_jj */
let sum = 0;
for (let k = 0; k < j; k++) sum += L[i][k] * L[j][k];
L[i][j] = (A[i][j] - sum) / L[j][j];
setFormula(i18nFmt('l[%i%][%j%] = (%aij% - %sum%) / %ljj% = %res%', { i: i, j: j, aij: A[i][j].toFixed(3), sum: sum.toFixed(3), ljj: L[j][j].toFixed(4), res: L[i][j].toFixed(4) }));
}
/* Move to the next (i,j) in column-major order within the lower triangle */
stepI++;
if (stepI >= N) {
stepJ++;
stepI = stepJ;
}
if (stepJ >= N) {
done = true;
setStatus('Done! L is complete. Verify: L × Lᵀ = A.', 'ok');
document.getElementById('btnStep').disabled = true;
document.getElementById('btnAll').disabled = true;
} else {
setStatus(i18nFmt('Computing L[%i%][%j%]…', { j: stepJ, i: stepI }), 'info');
}
}
function setStatus(t, cls) {
const s = document.getElementById('status');
s.textContent = t; s.className = 'status ' + cls;
}
function setFormula(t) {
document.getElementById('formula').textContent = t;
}
/* Reset L and step counters to restart the factorization */
function reset() {
L = [[0,0,0],[0,0,0],[0,0,0]];
stepJ = 0; stepI = 0; done = false;
document.getElementById('btnStep').disabled = false;
document.getElementById('btnAll').disabled = false;
setStatus('Ready — click Next entry to begin.', 'info');
setFormula('');
renderAll();
}
/* Make the cells of A editable so the user can try different matrices */
function makeAEditable() {
const tbl = document.getElementById('matA');
for (let i = 0; i < N; i++) {
for (let j = 0; j < N; j++) {
const td = tbl.rows[i].cells[j];
td.contentEditable = 'true';
td.className += ' editable';
td.addEventListener('blur', () => {
const v = parseFloat(td.textContent);
if (!isNaN(v)) {
A[i][j] = v;
A[j][i] = v; // keep A symmetric when the user edits an entry
reset();
} else {
td.textContent = fmt4(A[i][j]);
}
});
}
}
}
document.getElementById('btnStep').addEventListener('click', () => { choleskyStep(); renderAll(); });
document.getElementById('btnAll').addEventListener('click', () => {
while (!done) choleskyStep();
renderAll();
});
document.getElementById('btnReset').addEventListener('click', () => { A = DEFAULT_A.map(r => r.slice()); reset(); makeAEditable(); });
reset();
makeAEditable();
<!-- Cholesky demo: show A and L side by side, step through the factorization -->
<div class="container">
<div class="section">
<h3>Matrix A (edit to try your own)</h3>
<p class="hint">Click any cell to change a value. A is kept symmetric automatically.</p>
<div class="matrix-wrap">
<table id="matA" class="matrix"></table>
</div>
</div>
<div class="section">
<h3>Cholesky factor L</h3>
<p class="hint">Built column by column. Yellow = current entry, green = done.</p>
<div class="matrix-wrap">
<table id="matL" class="matrix"></table>
</div>
</div>
</div>
<div class="btns">
<button id="btnStep" type="button">Next entry</button>
<button id="btnAll" type="button">Compute all</button>
<button id="btnReset" type="button" class="ghost">Reset</button>
</div>
<div id="status" class="status"></div>
<div id="formula" class="formula"></div>
/* styles for the Cholesky decomposition demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: .4rem; }
h3 { margin: 0 0 .3rem; font-size: 1rem; color: #1d3557; }
.container { display: flex; gap: 1.5rem; flex-wrap: wrap; margin-bottom: .6rem; }
.section { flex: 1 1 140px; }
.hint { font-size: .82rem; color: #555; margin: 0 0 .4rem; line-height: 1.4; }
.matrix-wrap { display: inline-block; border-left: 2px solid #1d3557; border-right: 2px solid #1d3557;
padding: 4px 8px; }
.matrix { border-collapse: separate; border-spacing: 4px; }
.matrix td { width: 44px; height: 36px; text-align: center; font: 600 14px ui-monospace, monospace;
background: #e8eef3; border-radius: 6px; border: 1px solid #cdd9e3; color: #1d3557;
transition: background .25s, color .25s; }
.matrix td.editable { background: #f0f4f8; cursor: text; }
.matrix td.zero { color: #aaa; }
.matrix td.active { background: #ffd166; border-color: #e0a800; color: #333; }
.matrix td.done { background: #c8f0d4; border-color: #5cb87a; }
.matrix td.empty { background: #f7f7f7; color: #bbb; border-color: #ddd; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .5rem; }
button { font: 600 14px system-ui; padding: .4rem .85rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button:disabled { opacity: .45; cursor: default; }
button.ghost { background: #fff; color: #1d3557; }
.status { font-size: .95rem; font-weight: 600; min-height: 1.3em; margin-bottom: .3rem; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.status.info { color: #1d3557; }
.formula { font-size: .83rem; color: #555; min-height: 1.2em; font-family: ui-monospace, monospace; }
/* Cholesky decomposition demo — build L column by column from A = LL^T */
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
/* Default symmetric positive-definite 3x3 matrix A */
const DEFAULT_A = [[4, 2, -2], [2, 5, 3], [-2, 3, 14]];
let A = DEFAULT_A.map(r => r.slice());
let L = [[0,0,0],[0,0,0],[0,0,0]];
const N = 3;
/* Current position in the column-by-column sweep */
let stepJ = 0; // current column being filled
let stepI = 0; // current row within that column
let done = false;
/* Render a 3x3 matrix into the given table element */
function renderMatrix(tableId, data, highlights, emptyBelow) {
const tbl = document.getElementById(tableId);
tbl.innerHTML = '';
for (let i = 0; i < N; i++) {
const tr = document.createElement('tr');
for (let j = 0; j < N; j++) {
const td = document.createElement('td');
const key = i + ',' + j;
if (emptyBelow && j > i) {
/* upper triangle of L is always zero */
td.textContent = '0';
td.className = 'zero';
} else if (highlights && highlights.active && highlights.active === key) {
td.textContent = fmt4(data[i][j]);
td.className = 'active';
} else if (highlights && highlights.done && highlights.done.has(key)) {
td.textContent = fmt4(data[i][j]);
td.className = 'done';
} else if (emptyBelow && i > 0 && data[i][j] === 0 && !isDone(i, j)) {
td.textContent = '?';
td.className = 'empty';
} else {
td.textContent = fmt4(data[i][j]);
td.className = data[i][j] === 0 ? 'zero' : '';
}
tr.appendChild(td);
}
tbl.appendChild(tr);
}
}
function isDone(i, j) {
/* Returns true if entry (i,j) has already been computed */
if (j < stepJ) return true;
if (j === stepJ && i < stepI) return true;
return false;
}
function fmt4(v) {
/* Format a number to at most 3 decimal places, stripping trailing zeros */
if (v === 0) return '0';
const s = v.toFixed(3).replace(/\.?0+$/, '');
return s;
}
function getDoneSet() {
/* Build the set of already-computed L entries */
const s = new Set();
for (let j = 0; j < stepJ; j++)
for (let i = j; i < N; i++) s.add(i + ',' + j);
for (let i = stepJ; i < stepI; i++) s.add(i + ',' + stepJ);
return s;
}
function renderAll() {
const doneSet = getDoneSet();
const activeKey = (stepJ < N && !done) ? stepI + ',' + stepJ : null;
renderMatrix('matA', A, null, false);
renderMatrix('matL', L, { active: activeKey, done: doneSet }, true);
}
/* Compute the next entry of L using the Cholesky recurrence */
function choleskyStep() {
if (done) return;
if (stepJ >= N) { done = true; return; }
const j = stepJ;
const i = stepI;
if (i === j) {
/* Diagonal entry: l_jj = sqrt(a_jj - sum_{k<j} l_jk^2) */
let sum = 0;
for (let k = 0; k < j; k++) sum += L[j][k] * L[j][k];
const val = A[j][j] - sum;
if (val <= 0) {
setStatus('Matrix is not positive-definite — square root of a non-positive number.', 'bad');
done = true;
return;
}
L[j][j] = Math.sqrt(val);
setFormula(i18nFmt('l[%i%][%i%] = sqrt(%val% - %sum%) = %res%', { i: j, val: A[j][j].toFixed(3), sum: sum.toFixed(3), res: L[j][j].toFixed(4) }));
} else {
/* Off-diagonal entry: l_ij = (a_ij - sum_{k<j} l_ik * l_jk) / l_jj */
let sum = 0;
for (let k = 0; k < j; k++) sum += L[i][k] * L[j][k];
L[i][j] = (A[i][j] - sum) / L[j][j];
setFormula(i18nFmt('l[%i%][%j%] = (%aij% - %sum%) / %ljj% = %res%', { i: i, j: j, aij: A[i][j].toFixed(3), sum: sum.toFixed(3), ljj: L[j][j].toFixed(4), res: L[i][j].toFixed(4) }));
}
/* Move to the next (i,j) in column-major order within the lower triangle */
stepI++;
if (stepI >= N) {
stepJ++;
stepI = stepJ;
}
if (stepJ >= N) {
done = true;
setStatus('Done! L is complete. Verify: L × Lᵀ = A.', 'ok');
document.getElementById('btnStep').disabled = true;
document.getElementById('btnAll').disabled = true;
} else {
setStatus(i18nFmt('Computing L[%i%][%j%]…', { j: stepJ, i: stepI }), 'info');
}
}
function setStatus(t, cls) {
const s = document.getElementById('status');
s.textContent = t; s.className = 'status ' + cls;
}
function setFormula(t) {
document.getElementById('formula').textContent = t;
}
/* Reset L and step counters to restart the factorization */
function reset() {
L = [[0,0,0],[0,0,0],[0,0,0]];
stepJ = 0; stepI = 0; done = false;
document.getElementById('btnStep').disabled = false;
document.getElementById('btnAll').disabled = false;
setStatus('Ready — click Next entry to begin.', 'info');
setFormula('');
renderAll();
}
/* Make the cells of A editable so the user can try different matrices */
function makeAEditable() {
const tbl = document.getElementById('matA');
for (let i = 0; i < N; i++) {
for (let j = 0; j < N; j++) {
const td = tbl.rows[i].cells[j];
td.contentEditable = 'true';
td.className += ' editable';
td.addEventListener('blur', () => {
const v = parseFloat(td.textContent);
if (!isNaN(v)) {
A[i][j] = v;
A[j][i] = v; // keep A symmetric when the user edits an entry
reset();
} else {
td.textContent = fmt4(A[i][j]);
}
});
}
}
}
document.getElementById('btnStep').addEventListener('click', () => { choleskyStep(); renderAll(); });
document.getElementById('btnAll').addEventListener('click', () => {
while (!done) choleskyStep();
renderAll();
});
document.getElementById('btnReset').addEventListener('click', () => { A = DEFAULT_A.map(r => r.slice()); reset(); makeAEditable(); });
reset();
makeAEditable();
Comments
Loading comments...