<!-- Gaussian Quadrature vs Trapezoidal Rule — interactive comparison -->
<div class="controls">
<label for="poly-select">Polynomial:</label>
<select id="poly-select">
<option value="cubic">x³ − x (degree 3)</option>
<option value="degree5" selected>x⁵ − 3x³ + 2x (degree 5)</option>
<option value="degree3b">2x³ − x² + x − 1 (degree 3)</option>
<option value="degree5b">x⁵ − x² + 1 (degree 5)</option>
</select>
</div>
<canvas id="chart" width="480" height="220"></canvas>
<div id="results" class="results"></div>
<div class="btns">
<button id="btn-gauss" type="button">Gauss (3 nodes)</button>
<button id="btn-trap" type="button" class="ghost">Trapezoidal (3 panels)</button>
<button id="btn-both" type="button" class="ghost">Both</button>
</div>
/* layout and typography */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: .4rem .5rem; }
.controls { display: flex; align-items: center; gap: .5rem; margin-bottom: .5rem; flex-wrap: wrap; }
label { font-size: .85rem; font-weight: 600; color: #444; }
select { font-size: .85rem; padding: .25rem .4rem; border: 1px solid #cdd9e3; border-radius: 6px; }
canvas { display: block; width: 100%; max-width: 480px; border: 1px solid #e0e8ef; border-radius: 8px; background: #f9fbfd; }
.results { font-size: .85rem; margin: .5rem 0; line-height: 1.6; min-height: 3.5rem; }
.results table { border-collapse: collapse; width: 100%; }
.results td, .results th { padding: .18rem .4rem; border-bottom: 1px solid #e8eef3; }
.results th { font-weight: 700; color: #1d3557; text-align: left; }
.gauss-val { color: #0a7d33; font-weight: 700; }
.trap-val { color: #c92f3c; font-weight: 600; }
.exact-val { color: #555; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 13px system-ui, sans-serif; padding: .4rem .8rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button.active { background: #0a7d33; border-color: #0a7d33; color: #fff; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// 3-point Gauss-Legendre quadrature on [-1, 1]:
// nodes (roots of P_3, the degree-3 Legendre polynomial): x1 = -sqrt(3/5), x2 = 0, x3 = +sqrt(3/5)
// weights: w1 = 5/9, w2 = 8/9, w3 = 5/9
// exactness claim: any polynomial of degree <= 2*3-1 = 5
// Gauss-Legendre nodes and weights for n=3
const GAUSS_NODES = [-Math.sqrt(3/5), 0, Math.sqrt(3/5)];
const GAUSS_WEIGHTS = [5/9, 8/9, 5/9];
// polynomial library — exact integrals over [-1,1] computed analytically
const POLYS = {
cubic: { fn: x => x*x*x - x, exact: 0, label: 'x³ − x' },
degree5: { fn: x => x*x*x*x*x - 3*x*x*x + 2*x, exact: 0, label: 'x⁵ − 3x³ + 2x' },
degree3b:{ fn: x => 2*x*x*x - x*x + x - 1, exact: -8/3, label: '2x³ − x² + x − 1' },
degree5b:{ fn: x => x*x*x*x*x - x*x + 1, exact: 4/3, label: 'x⁵ − x² + 1' }
};
// Gaussian quadrature: sum w_i * f(x_i)
function gaussIntegrate(f) {
return GAUSS_NODES.reduce((sum, xi, i) => sum + GAUSS_WEIGHTS[i] * f(xi), 0);
}
// trapezoidal rule with 3 equal panels (3 panels = 4 points equally spaced on [-1,1])
function trapIntegrate(f) {
const pts = [-1, -1/3, 1/3, 1];
let s = 0;
for (let i = 0; i < pts.length - 1; i++) {
const h = pts[i+1] - pts[i];
s += h * (f(pts[i]) + f(pts[i+1])) / 2;
}
return s;
}
let activeMode = 'gauss'; // which method is currently highlighted
function round6(v) { return Math.round(v * 1e6) / 1e6; }
function currentPoly() {
return POLYS[document.getElementById('poly-select').value];
}
// draw the function curve and the chosen method's sample pattern
function drawCanvas(mode) {
const poly = currentPoly();
const f = poly.fn;
const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
const PAD = { top: 18, bottom: 28, left: 36, right: 12 };
const cW = W - PAD.left - PAD.right;
const cH = H - PAD.top - PAD.bottom;
// sample the curve for drawing
const xs = [];
for (let i = 0; i <= 200; i++) xs.push(-1 + 2*i/200);
const ys = xs.map(f);
const yMin = Math.min(...ys, 0);
const yMax = Math.max(...ys, 0);
const yRange = yMax - yMin || 1;
const toCanvasX = x => PAD.left + (x + 1) / 2 * cW;
const toCanvasY = y => PAD.top + (1 - (y - yMin) / yRange) * cH;
ctx.clearRect(0, 0, W, H);
// draw axes and domain boundaries
ctx.strokeStyle = '#cdd9e3'; ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(PAD.left, toCanvasY(0)); ctx.lineTo(W - PAD.right, toCanvasY(0));
ctx.moveTo(toCanvasX(-1), PAD.top); ctx.lineTo(toCanvasX(-1), H - PAD.bottom);
ctx.moveTo(toCanvasX(1), PAD.top); ctx.lineTo(toCanvasX(1), H - PAD.bottom);
ctx.stroke();
// draw the polynomial curve
ctx.strokeStyle = '#1d3557'; ctx.lineWidth = 2;
ctx.beginPath();
xs.forEach((x, i) => i === 0 ? ctx.moveTo(toCanvasX(x), toCanvasY(ys[i])) : ctx.lineTo(toCanvasX(x), toCanvasY(ys[i])));
ctx.stroke();
if (mode === 'gauss' || mode === 'both') {
// draw Gauss nodes as green filled circles with weight labels
GAUSS_NODES.forEach((xi, i) => {
const yi = f(xi);
const cx = toCanvasX(xi), cy = toCanvasY(yi);
const base = toCanvasY(0);
ctx.fillStyle = 'rgba(10,125,51,0.15)';
ctx.fillRect(cx - 1, Math.min(cy, base), 2, Math.abs(cy - base));
ctx.fillStyle = '#0a7d33';
ctx.beginPath(); ctx.arc(cx, cy, 5, 0, 2*Math.PI); ctx.fill();
ctx.fillStyle = '#0a7d33'; ctx.font = '10px system-ui';
ctx.fillText(`w=${round6(GAUSS_WEIGHTS[i])}`, cx + 6, cy - 3);
});
}
if (mode === 'trap' || mode === 'both') {
// draw trapezoidal panels as red shaded trapezoids
const panels = [[-1,-1/3],[-1/3,1/3],[1/3,1]];
panels.forEach(([a, b]) => {
const ya = f(a), yb = f(b);
ctx.fillStyle = 'rgba(201,47,60,0.12)';
ctx.beginPath();
ctx.moveTo(toCanvasX(a), toCanvasY(0));
ctx.lineTo(toCanvasX(a), toCanvasY(ya));
ctx.lineTo(toCanvasX(b), toCanvasY(yb));
ctx.lineTo(toCanvasX(b), toCanvasY(0));
ctx.closePath(); ctx.fill();
ctx.strokeStyle = '#c92f3c'; ctx.lineWidth = 1.5;
ctx.beginPath();
ctx.moveTo(toCanvasX(a), toCanvasY(ya));
ctx.lineTo(toCanvasX(b), toCanvasY(yb));
ctx.stroke();
[a, b].forEach(pt => {
ctx.fillStyle = '#c92f3c';
ctx.beginPath(); ctx.arc(toCanvasX(pt), toCanvasY(f(pt)), 4, 0, 2*Math.PI); ctx.fill();
});
});
}
// x-axis tick labels
ctx.fillStyle = '#888'; ctx.font = '10px system-ui'; ctx.textAlign = 'center';
[[-1,'-1'],[0,'0'],[1,'1']].forEach(([x,lbl]) => {
ctx.fillText(lbl, toCanvasX(x), H - PAD.bottom + 12);
});
}
// populate the results table
function showResults(mode) {
const poly = currentPoly();
const f = poly.fn;
const exact = poly.exact;
const gauss = gaussIntegrate(f);
const trap = trapIntegrate(f);
const gaussErr = Math.abs(gauss - exact);
const trapErr = Math.abs(trap - exact);
const res = document.getElementById('results');
res.innerHTML = `
<table>
<tr><th>Method</th><th>Result</th><th>Exact</th><th>Error</th></tr>
<tr>
<td>Gauss (3 pts)</td>
<td class="gauss-val">${round6(gauss)}</td>
<td class="exact-val">${exact}</td>
<td class="gauss-val">${gaussErr < 1e-9 ? '0 (exact!)' : round6(gaussErr)}</td>
</tr>
<tr>
<td>Trapezoidal</td>
<td class="trap-val">${round6(trap)}</td>
<td class="exact-val">${exact}</td>
<td class="trap-val">${round6(trapErr)}</td>
</tr>
</table>`;
}
function run(mode) {
activeMode = mode;
['btn-gauss','btn-trap','btn-both'].forEach(id => document.getElementById(id).classList.remove('active'));
document.getElementById('btn-' + mode).classList.add('active');
drawCanvas(mode);
showResults(mode);
}
document.getElementById('btn-gauss').addEventListener('click', () => run('gauss'));
document.getElementById('btn-trap').addEventListener('click', () => run('trap'));
document.getElementById('btn-both').addEventListener('click', () => run('both'));
document.getElementById('poly-select').addEventListener('change', () => run(activeMode));
// start in Gauss mode
run('gauss');
<!-- Gaussian Quadrature vs Trapezoidal Rule — interactive comparison -->
<div class="controls">
<label for="poly-select">Polynomial:</label>
<select id="poly-select">
<option value="cubic">x³ − x (degree 3)</option>
<option value="degree5" selected>x⁵ − 3x³ + 2x (degree 5)</option>
<option value="degree3b">2x³ − x² + x − 1 (degree 3)</option>
<option value="degree5b">x⁵ − x² + 1 (degree 5)</option>
</select>
</div>
<canvas id="chart" width="480" height="220"></canvas>
<div id="results" class="results"></div>
<div class="btns">
<button id="btn-gauss" type="button">Gauss (3 nodes)</button>
<button id="btn-trap" type="button" class="ghost">Trapezoidal (3 panels)</button>
<button id="btn-both" type="button" class="ghost">Both</button>
</div>
/* layout and typography */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: .4rem .5rem; }
.controls { display: flex; align-items: center; gap: .5rem; margin-bottom: .5rem; flex-wrap: wrap; }
label { font-size: .85rem; font-weight: 600; color: #444; }
select { font-size: .85rem; padding: .25rem .4rem; border: 1px solid #cdd9e3; border-radius: 6px; }
canvas { display: block; width: 100%; max-width: 480px; border: 1px solid #e0e8ef; border-radius: 8px; background: #f9fbfd; }
.results { font-size: .85rem; margin: .5rem 0; line-height: 1.6; min-height: 3.5rem; }
.results table { border-collapse: collapse; width: 100%; }
.results td, .results th { padding: .18rem .4rem; border-bottom: 1px solid #e8eef3; }
.results th { font-weight: 700; color: #1d3557; text-align: left; }
.gauss-val { color: #0a7d33; font-weight: 700; }
.trap-val { color: #c92f3c; font-weight: 600; }
.exact-val { color: #555; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 13px system-ui, sans-serif; padding: .4rem .8rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button.active { background: #0a7d33; border-color: #0a7d33; color: #fff; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// 3-point Gauss-Legendre quadrature on [-1, 1]:
// nodes (roots of P_3, the degree-3 Legendre polynomial): x1 = -sqrt(3/5), x2 = 0, x3 = +sqrt(3/5)
// weights: w1 = 5/9, w2 = 8/9, w3 = 5/9
// exactness claim: any polynomial of degree <= 2*3-1 = 5
// Gauss-Legendre nodes and weights for n=3
const GAUSS_NODES = [-Math.sqrt(3/5), 0, Math.sqrt(3/5)];
const GAUSS_WEIGHTS = [5/9, 8/9, 5/9];
// polynomial library — exact integrals over [-1,1] computed analytically
const POLYS = {
cubic: { fn: x => x*x*x - x, exact: 0, label: 'x³ − x' },
degree5: { fn: x => x*x*x*x*x - 3*x*x*x + 2*x, exact: 0, label: 'x⁵ − 3x³ + 2x' },
degree3b:{ fn: x => 2*x*x*x - x*x + x - 1, exact: -8/3, label: '2x³ − x² + x − 1' },
degree5b:{ fn: x => x*x*x*x*x - x*x + 1, exact: 4/3, label: 'x⁵ − x² + 1' }
};
// Gaussian quadrature: sum w_i * f(x_i)
function gaussIntegrate(f) {
return GAUSS_NODES.reduce((sum, xi, i) => sum + GAUSS_WEIGHTS[i] * f(xi), 0);
}
// trapezoidal rule with 3 equal panels (3 panels = 4 points equally spaced on [-1,1])
function trapIntegrate(f) {
const pts = [-1, -1/3, 1/3, 1];
let s = 0;
for (let i = 0; i < pts.length - 1; i++) {
const h = pts[i+1] - pts[i];
s += h * (f(pts[i]) + f(pts[i+1])) / 2;
}
return s;
}
let activeMode = 'gauss'; // which method is currently highlighted
function round6(v) { return Math.round(v * 1e6) / 1e6; }
function currentPoly() {
return POLYS[document.getElementById('poly-select').value];
}
// draw the function curve and the chosen method's sample pattern
function drawCanvas(mode) {
const poly = currentPoly();
const f = poly.fn;
const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
const PAD = { top: 18, bottom: 28, left: 36, right: 12 };
const cW = W - PAD.left - PAD.right;
const cH = H - PAD.top - PAD.bottom;
// sample the curve for drawing
const xs = [];
for (let i = 0; i <= 200; i++) xs.push(-1 + 2*i/200);
const ys = xs.map(f);
const yMin = Math.min(...ys, 0);
const yMax = Math.max(...ys, 0);
const yRange = yMax - yMin || 1;
const toCanvasX = x => PAD.left + (x + 1) / 2 * cW;
const toCanvasY = y => PAD.top + (1 - (y - yMin) / yRange) * cH;
ctx.clearRect(0, 0, W, H);
// draw axes and domain boundaries
ctx.strokeStyle = '#cdd9e3'; ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(PAD.left, toCanvasY(0)); ctx.lineTo(W - PAD.right, toCanvasY(0));
ctx.moveTo(toCanvasX(-1), PAD.top); ctx.lineTo(toCanvasX(-1), H - PAD.bottom);
ctx.moveTo(toCanvasX(1), PAD.top); ctx.lineTo(toCanvasX(1), H - PAD.bottom);
ctx.stroke();
// draw the polynomial curve
ctx.strokeStyle = '#1d3557'; ctx.lineWidth = 2;
ctx.beginPath();
xs.forEach((x, i) => i === 0 ? ctx.moveTo(toCanvasX(x), toCanvasY(ys[i])) : ctx.lineTo(toCanvasX(x), toCanvasY(ys[i])));
ctx.stroke();
if (mode === 'gauss' || mode === 'both') {
// draw Gauss nodes as green filled circles with weight labels
GAUSS_NODES.forEach((xi, i) => {
const yi = f(xi);
const cx = toCanvasX(xi), cy = toCanvasY(yi);
const base = toCanvasY(0);
ctx.fillStyle = 'rgba(10,125,51,0.15)';
ctx.fillRect(cx - 1, Math.min(cy, base), 2, Math.abs(cy - base));
ctx.fillStyle = '#0a7d33';
ctx.beginPath(); ctx.arc(cx, cy, 5, 0, 2*Math.PI); ctx.fill();
ctx.fillStyle = '#0a7d33'; ctx.font = '10px system-ui';
ctx.fillText(`w=${round6(GAUSS_WEIGHTS[i])}`, cx + 6, cy - 3);
});
}
if (mode === 'trap' || mode === 'both') {
// draw trapezoidal panels as red shaded trapezoids
const panels = [[-1,-1/3],[-1/3,1/3],[1/3,1]];
panels.forEach(([a, b]) => {
const ya = f(a), yb = f(b);
ctx.fillStyle = 'rgba(201,47,60,0.12)';
ctx.beginPath();
ctx.moveTo(toCanvasX(a), toCanvasY(0));
ctx.lineTo(toCanvasX(a), toCanvasY(ya));
ctx.lineTo(toCanvasX(b), toCanvasY(yb));
ctx.lineTo(toCanvasX(b), toCanvasY(0));
ctx.closePath(); ctx.fill();
ctx.strokeStyle = '#c92f3c'; ctx.lineWidth = 1.5;
ctx.beginPath();
ctx.moveTo(toCanvasX(a), toCanvasY(ya));
ctx.lineTo(toCanvasX(b), toCanvasY(yb));
ctx.stroke();
[a, b].forEach(pt => {
ctx.fillStyle = '#c92f3c';
ctx.beginPath(); ctx.arc(toCanvasX(pt), toCanvasY(f(pt)), 4, 0, 2*Math.PI); ctx.fill();
});
});
}
// x-axis tick labels
ctx.fillStyle = '#888'; ctx.font = '10px system-ui'; ctx.textAlign = 'center';
[[-1,'-1'],[0,'0'],[1,'1']].forEach(([x,lbl]) => {
ctx.fillText(lbl, toCanvasX(x), H - PAD.bottom + 12);
});
}
// populate the results table
function showResults(mode) {
const poly = currentPoly();
const f = poly.fn;
const exact = poly.exact;
const gauss = gaussIntegrate(f);
const trap = trapIntegrate(f);
const gaussErr = Math.abs(gauss - exact);
const trapErr = Math.abs(trap - exact);
const res = document.getElementById('results');
res.innerHTML = `
<table>
<tr><th>Method</th><th>Result</th><th>Exact</th><th>Error</th></tr>
<tr>
<td>Gauss (3 pts)</td>
<td class="gauss-val">${round6(gauss)}</td>
<td class="exact-val">${exact}</td>
<td class="gauss-val">${gaussErr < 1e-9 ? '0 (exact!)' : round6(gaussErr)}</td>
</tr>
<tr>
<td>Trapezoidal</td>
<td class="trap-val">${round6(trap)}</td>
<td class="exact-val">${exact}</td>
<td class="trap-val">${round6(trapErr)}</td>
</tr>
</table>`;
}
function run(mode) {
activeMode = mode;
['btn-gauss','btn-trap','btn-both'].forEach(id => document.getElementById(id).classList.remove('active'));
document.getElementById('btn-' + mode).classList.add('active');
drawCanvas(mode);
showResults(mode);
}
document.getElementById('btn-gauss').addEventListener('click', () => run('gauss'));
document.getElementById('btn-trap').addEventListener('click', () => run('trap'));
document.getElementById('btn-both').addEventListener('click', () => run('both'));
document.getElementById('poly-select').addEventListener('change', () => run(activeMode));
// start in Gauss mode
run('gauss');
Comments
Loading comments...