<p class="hint">The gray curve is the exact function f(x) = -ln(1-x), which has a real singularity at x = 1. The red curve is its degree-4 Taylor polynomial; the green curve is a Padé[2/2] approximant — built from the very same 5 coefficients, just arranged as a ratio.</p>
<div class="plotwrap">
<canvas id="plot" width="640" height="320"></canvas>
</div>
<div class="controls">
<label for="xslider">Move x toward the pole:</label>
<input type="range" id="xslider" min="0" max="99" value="30" step="1" aria-label="Move x toward the pole:">
<span id="xval" class="xval">x = 0.30</span>
</div>
<div class="readout" id="readout"></div>
<div class="btns">
<button id="reset" type="button" class="ghost">Reset</button>
</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 .7rem; line-height: 1.45; }
.plotwrap { background: #f7f9fb; border: 1px solid #dbe3ea; border-radius: 10px; padding: .5rem; }
canvas { width: 100%; height: auto; display: block; }
.controls { display: flex; align-items: center; gap: .6rem; margin: .7rem 0 .3rem; flex-wrap: wrap; }
.controls label { font-size: .85rem; font-weight: 600; color: #1d3557; }
input[type="range"] { flex: 1 1 200px; accent-color: #1d3557; }
.xval { font: 700 14px ui-monospace, monospace; color: #1d3557; min-width: 5.5em; }
.readout { font-size: .88rem; line-height: 1.6; margin: .3rem 0 .6rem; }
.readout .row { display: flex; justify-content: space-between; gap: .8rem; padding: .15rem 0; border-bottom: 1px dashed #e1e6ea; }
.readout .row:last-child { border-bottom: none; }
.readout .swatch { display: inline-block; width: .7em; height: .7em; border-radius: 2px; margin-right: .4em; }
.sw-exact { background: #444; }
.sw-taylor { background: #e63946; }
.sw-pade { background: #0a7d33; }
.readout .err { font-family: ui-monospace, monospace; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
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; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Target function with a real singularity at x = 1: f(x) = -ln(1-x).
function f(x) { return -Math.log(1 - x); }
// Taylor coefficients of f around x = 0: c_n = 1/n for n >= 1, c_0 = 0.
function taylorCoeffs(n) { const c = [0]; for (let i = 1; i <= n; i++) c.push(1 / i); return c; }
// General Pade[L/M] builder: solve an M x M linear system for the denominator, then read off the numerator by substitution.
function padeLM(c, L, M) {
const A = [], b = [];
for (let j = 1; j <= M; j++) {
const row = [];
for (let k = 1; k <= M; k++) {
const idx = L + j - k;
row.push(idx >= 0 && idx < c.length ? c[idx] : 0);
}
A.push(row);
const idx = L + j;
b.push(-(idx < c.length ? c[idx] : 0));
}
const n = M;
for (let i = 0; i < n; i++) {
let piv = i;
for (let k = i + 1; k < n; k++) if (Math.abs(A[k][i]) > Math.abs(A[piv][i])) piv = k;
[A[i], A[piv]] = [A[piv], A[i]];
[b[i], b[piv]] = [b[piv], b[i]];
for (let k = i + 1; k < n; k++) {
const fac = A[k][i] / A[i][i];
for (let j = i; j < n; j++) A[k][j] -= fac * A[i][j];
b[k] -= fac * b[i];
}
}
const q = new Array(n).fill(0);
for (let i = n - 1; i >= 0; i--) {
let s = b[i];
for (let j = i + 1; j < n; j++) s -= A[i][j] * q[j];
q[i] = s / A[i][i];
}
const qfull = [1, ...q];
const p = [];
for (let i = 0; i <= L; i++) {
let s = 0;
for (let k = 0; k <= Math.min(i, M); k++) s += qfull[k] * (c[i - k] !== undefined ? c[i - k] : 0);
p.push(s);
}
return { p, q: qfull };
}
function evalPoly(coef, x) { let s = 0; for (let i = 0; i < coef.length; i++) s += coef[i] * Math.pow(x, i); return s; }
const DEG = 4; // total number of matched Taylor coefficients used by both curves (c_0..c_4)
const coeffs = taylorCoeffs(DEG);
function taylorEval(x) { return evalPoly(coeffs.slice(0, DEG + 1), x); }
const { p: padeP, q: padeQ } = padeLM(coeffs, 2, 2);
function padeEval(x) { return evalPoly(padeP, x) / evalPoly(padeQ, x); }
const slider = document.getElementById('xslider');
const xval = document.getElementById('xval');
const readout = document.getElementById('readout');
const canvas = document.getElementById('plot');
const ctx = canvas.getContext('2d');
const XMAX = 0.99; // stop just short of the true pole at x = 1 to avoid dividing by zero
const W = canvas.width, H = canvas.height, PAD = 36;
function toPx(x, y, yMin, yMax) {
const px = PAD + (x / XMAX) * (W - 2 * PAD);
const py = H - PAD - ((y - yMin) / (yMax - yMin)) * (H - 2 * PAD);
return [px, py];
}
function draw(curX) {
ctx.clearRect(0, 0, W, H);
const yMin = -0.5, yMax = 6; // clip the y-range so the plot stays readable as curves diverge
// axes
ctx.strokeStyle = '#c7d0d8'; ctx.lineWidth = 1;
ctx.beginPath();
const [ox, oy] = toPx(0, 0, yMin, yMax);
ctx.moveTo(PAD, oy); ctx.lineTo(W - PAD, oy);
ctx.moveTo(ox, PAD); ctx.lineTo(ox, H - PAD);
ctx.stroke();
function plot(fn, color, clamp) {
ctx.strokeStyle = color; ctx.lineWidth = 2.2; ctx.beginPath();
let started = false;
for (let i = 0; i <= 400; i++) {
const x = (i / 400) * XMAX;
let y = fn(x);
if (clamp) y = Math.max(yMin, Math.min(yMax, y));
const [px, py] = toPx(x, y, yMin, yMax);
if (!started) { ctx.moveTo(px, py); started = true; } else ctx.lineTo(px, py);
}
ctx.stroke();
}
plot(f, '#444', true);
plot(taylorEval, '#e63946', true);
plot(padeEval, '#0a7d33', true);
// cursor
const [cx] = toPx(curX, 0, yMin, yMax);
ctx.strokeStyle = '#1d3557'; ctx.setLineDash([4, 3]); ctx.lineWidth = 1.4;
ctx.beginPath(); ctx.moveTo(cx, PAD); ctx.lineTo(cx, H - PAD); ctx.stroke();
ctx.setLineDash([]);
}
function update() {
const x = Math.min(0.99, slider.value / 100);
xval.textContent = i18nFmt('x = %x%', { x: x.toFixed(2) });
const exact = f(x);
const tay = taylorEval(x);
const pade = padeEval(x);
const errTay = Math.abs(exact - tay);
const errPade = Math.abs(exact - pade);
readout.innerHTML =
'<div class="row"><span><span class="swatch sw-exact"></span>' + 'Exact f(x)' + '</span><span>' + exact.toFixed(4) + '</span></div>' +
'<div class="row"><span><span class="swatch sw-taylor"></span>' + 'Taylor (degree 4)' + '</span><span>' + tay.toFixed(4) + ' <span class="err">(' + i18nFmt('error %e%', { e: errTay.toExponential(2) }) + ')</span></span></div>' +
'<div class="row"><span><span class="swatch sw-pade"></span>' + 'Padé [2/2]' + '</span><span>' + pade.toFixed(4) + ' <span class="err">(' + i18nFmt('error %e%', { e: errPade.toExponential(2) }) + ')</span></span></div>';
draw(x);
console.log(i18nFmt('At x=%x%: Taylor error %et%, Padé error %ep%.', { x: x.toFixed(2), et: errTay.toExponential(2), ep: errPade.toExponential(2) }));
}
slider.addEventListener('input', update);
document.getElementById('reset').addEventListener('click', () => { slider.value = 30; update(); });
update();
<p class="hint">The gray curve is the exact function f(x) = -ln(1-x), which has a real singularity at x = 1. The red curve is its degree-4 Taylor polynomial; the green curve is a Padé[2/2] approximant — built from the very same 5 coefficients, just arranged as a ratio.</p>
<div class="plotwrap">
<canvas id="plot" width="640" height="320"></canvas>
</div>
<div class="controls">
<label for="xslider">Move x toward the pole:</label>
<input type="range" id="xslider" min="0" max="99" value="30" step="1" aria-label="Move x toward the pole:">
<span id="xval" class="xval">x = 0.30</span>
</div>
<div class="readout" id="readout"></div>
<div class="btns">
<button id="reset" type="button" class="ghost">Reset</button>
</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 .7rem; line-height: 1.45; }
.plotwrap { background: #f7f9fb; border: 1px solid #dbe3ea; border-radius: 10px; padding: .5rem; }
canvas { width: 100%; height: auto; display: block; }
.controls { display: flex; align-items: center; gap: .6rem; margin: .7rem 0 .3rem; flex-wrap: wrap; }
.controls label { font-size: .85rem; font-weight: 600; color: #1d3557; }
input[type="range"] { flex: 1 1 200px; accent-color: #1d3557; }
.xval { font: 700 14px ui-monospace, monospace; color: #1d3557; min-width: 5.5em; }
.readout { font-size: .88rem; line-height: 1.6; margin: .3rem 0 .6rem; }
.readout .row { display: flex; justify-content: space-between; gap: .8rem; padding: .15rem 0; border-bottom: 1px dashed #e1e6ea; }
.readout .row:last-child { border-bottom: none; }
.readout .swatch { display: inline-block; width: .7em; height: .7em; border-radius: 2px; margin-right: .4em; }
.sw-exact { background: #444; }
.sw-taylor { background: #e63946; }
.sw-pade { background: #0a7d33; }
.readout .err { font-family: ui-monospace, monospace; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
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; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Target function with a real singularity at x = 1: f(x) = -ln(1-x).
function f(x) { return -Math.log(1 - x); }
// Taylor coefficients of f around x = 0: c_n = 1/n for n >= 1, c_0 = 0.
function taylorCoeffs(n) { const c = [0]; for (let i = 1; i <= n; i++) c.push(1 / i); return c; }
// General Pade[L/M] builder: solve an M x M linear system for the denominator, then read off the numerator by substitution.
function padeLM(c, L, M) {
const A = [], b = [];
for (let j = 1; j <= M; j++) {
const row = [];
for (let k = 1; k <= M; k++) {
const idx = L + j - k;
row.push(idx >= 0 && idx < c.length ? c[idx] : 0);
}
A.push(row);
const idx = L + j;
b.push(-(idx < c.length ? c[idx] : 0));
}
const n = M;
for (let i = 0; i < n; i++) {
let piv = i;
for (let k = i + 1; k < n; k++) if (Math.abs(A[k][i]) > Math.abs(A[piv][i])) piv = k;
[A[i], A[piv]] = [A[piv], A[i]];
[b[i], b[piv]] = [b[piv], b[i]];
for (let k = i + 1; k < n; k++) {
const fac = A[k][i] / A[i][i];
for (let j = i; j < n; j++) A[k][j] -= fac * A[i][j];
b[k] -= fac * b[i];
}
}
const q = new Array(n).fill(0);
for (let i = n - 1; i >= 0; i--) {
let s = b[i];
for (let j = i + 1; j < n; j++) s -= A[i][j] * q[j];
q[i] = s / A[i][i];
}
const qfull = [1, ...q];
const p = [];
for (let i = 0; i <= L; i++) {
let s = 0;
for (let k = 0; k <= Math.min(i, M); k++) s += qfull[k] * (c[i - k] !== undefined ? c[i - k] : 0);
p.push(s);
}
return { p, q: qfull };
}
function evalPoly(coef, x) { let s = 0; for (let i = 0; i < coef.length; i++) s += coef[i] * Math.pow(x, i); return s; }
const DEG = 4; // total number of matched Taylor coefficients used by both curves (c_0..c_4)
const coeffs = taylorCoeffs(DEG);
function taylorEval(x) { return evalPoly(coeffs.slice(0, DEG + 1), x); }
const { p: padeP, q: padeQ } = padeLM(coeffs, 2, 2);
function padeEval(x) { return evalPoly(padeP, x) / evalPoly(padeQ, x); }
const slider = document.getElementById('xslider');
const xval = document.getElementById('xval');
const readout = document.getElementById('readout');
const canvas = document.getElementById('plot');
const ctx = canvas.getContext('2d');
const XMAX = 0.99; // stop just short of the true pole at x = 1 to avoid dividing by zero
const W = canvas.width, H = canvas.height, PAD = 36;
function toPx(x, y, yMin, yMax) {
const px = PAD + (x / XMAX) * (W - 2 * PAD);
const py = H - PAD - ((y - yMin) / (yMax - yMin)) * (H - 2 * PAD);
return [px, py];
}
function draw(curX) {
ctx.clearRect(0, 0, W, H);
const yMin = -0.5, yMax = 6; // clip the y-range so the plot stays readable as curves diverge
// axes
ctx.strokeStyle = '#c7d0d8'; ctx.lineWidth = 1;
ctx.beginPath();
const [ox, oy] = toPx(0, 0, yMin, yMax);
ctx.moveTo(PAD, oy); ctx.lineTo(W - PAD, oy);
ctx.moveTo(ox, PAD); ctx.lineTo(ox, H - PAD);
ctx.stroke();
function plot(fn, color, clamp) {
ctx.strokeStyle = color; ctx.lineWidth = 2.2; ctx.beginPath();
let started = false;
for (let i = 0; i <= 400; i++) {
const x = (i / 400) * XMAX;
let y = fn(x);
if (clamp) y = Math.max(yMin, Math.min(yMax, y));
const [px, py] = toPx(x, y, yMin, yMax);
if (!started) { ctx.moveTo(px, py); started = true; } else ctx.lineTo(px, py);
}
ctx.stroke();
}
plot(f, '#444', true);
plot(taylorEval, '#e63946', true);
plot(padeEval, '#0a7d33', true);
// cursor
const [cx] = toPx(curX, 0, yMin, yMax);
ctx.strokeStyle = '#1d3557'; ctx.setLineDash([4, 3]); ctx.lineWidth = 1.4;
ctx.beginPath(); ctx.moveTo(cx, PAD); ctx.lineTo(cx, H - PAD); ctx.stroke();
ctx.setLineDash([]);
}
function update() {
const x = Math.min(0.99, slider.value / 100);
xval.textContent = i18nFmt('x = %x%', { x: x.toFixed(2) });
const exact = f(x);
const tay = taylorEval(x);
const pade = padeEval(x);
const errTay = Math.abs(exact - tay);
const errPade = Math.abs(exact - pade);
readout.innerHTML =
'<div class="row"><span><span class="swatch sw-exact"></span>' + 'Exact f(x)' + '</span><span>' + exact.toFixed(4) + '</span></div>' +
'<div class="row"><span><span class="swatch sw-taylor"></span>' + 'Taylor (degree 4)' + '</span><span>' + tay.toFixed(4) + ' <span class="err">(' + i18nFmt('error %e%', { e: errTay.toExponential(2) }) + ')</span></span></div>' +
'<div class="row"><span><span class="swatch sw-pade"></span>' + 'Padé [2/2]' + '</span><span>' + pade.toFixed(4) + ' <span class="err">(' + i18nFmt('error %e%', { e: errPade.toExponential(2) }) + ')</span></span></div>';
draw(x);
console.log(i18nFmt('At x=%x%: Taylor error %et%, Padé error %ep%.', { x: x.toFixed(2), et: errTay.toExponential(2), ep: errPade.toExponential(2) }));
}
slider.addEventListener('input', update);
document.getElementById('reset').addEventListener('click', () => { slider.value = 30; update(); });
update();
Comments
Loading comments...