<p class="hint">Blue is f(x) = x² + bx + 2b, red is the fixed g(x) = x² - 5x + 6. The chart on the right plots the resultant Res(f,g) as you change b — pure arithmetic on the coefficients, no root-finding.</p>
<div class="controls">
<label for="bslider">b =</label>
<input type="range" id="bslider" min="-3" max="1" step="0.01" value="0.3">
<span id="bval" class="bval">b = 0.30</span>
</div>
<div class="panels">
<div class="panel">
<canvas id="curves" width="320" height="220"></canvas>
<div class="caption">f (blue) and g (red)</div>
</div>
<div class="panel">
<canvas id="reschart" width="320" height="220"></canvas>
<div class="caption">Resultant Res(f,g) vs. b</div>
</div>
</div>
<div class="status" id="status">Drag the slider to change b.</div>
<div class="btns">
<button id="snap1" type="button">Snap to shared root x=2</button>
<button id="snap2" type="button">Snap to shared root x=3</button>
<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; }
.controls { display: flex; align-items: center; gap: .6rem; margin-bottom: .6rem; flex-wrap: wrap; }
.controls label { font-size: .85rem; font-weight: 600; color: #1d3557; }
.controls input[type="range"] { flex: 1; min-width: 160px; accent-color: #1d3557; }
.bval { font: 700 14px ui-monospace, monospace; color: #1d3557; min-width: 80px; }
.panels { display: flex; gap: .8rem; flex-wrap: wrap; margin-bottom: .6rem; }
.panel { flex: 1; min-width: 260px; }
canvas { width: 100%; max-width: 320px; height: auto; background: #f7f9fb; border: 1px solid #cdd9e3; border-radius: 8px; display: block; }
.caption { font-size: .78rem; color: #555; text-align: center; margin-top: .3rem; }
.status { font-size: 1rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #444; }
.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 fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Demo: f(x) = x^2 + b*x + 2b (slider controls b), g(x) = x^2 - 5x + 6 fixed.
// g factors as (x-2)(x-3), so its roots are always x=2 and x=3.
// The resultant Res(f,g) is the determinant of their 4x4 Sylvester matrix.
const G = [1, -5, 6]; // coefficients of the fixed polynomial g(x) = x^2 - 5x + 6
const ROOT_TOL = 0.03;
// coefficients of f(x) = x^2 + b*x + 2b for the current slider value
function fCoeffs(b){ return [1, b, 2 * b]; }
// generic 4x4 determinant via cofactor expansion along the first row
function det3(a){
return a[0][0]*(a[1][1]*a[2][2]-a[1][2]*a[2][1])
- a[0][1]*(a[1][0]*a[2][2]-a[1][2]*a[2][0])
+ a[0][2]*(a[1][0]*a[2][1]-a[1][1]*a[2][0]);
}
function det4(m){
let result = 0;
for(let col=0; col<4; col++){
const minor = [];
for(let r=1;r<4;r++){ const row=[]; for(let c=0;c<4;c++) if(c!==col) row.push(m[r][c]); minor.push(row); }
const sign = (col % 2 === 0) ? 1 : -1;
result += sign * m[0][col] * det3(minor);
}
return result;
}
// build the 4x4 Sylvester matrix from f and g's coefficients and take its determinant
function resultant(b){
const [a0,a1,a2] = fCoeffs(b);
const [b0,b1,b2] = G;
const M = [
[a0,a1,a2,0],
[0,a0,a1,a2],
[b0,b1,b2,0],
[0,b0,b1,b2]
];
return det4(M);
}
function fVal(b,x){ const [a0,a1,a2]=fCoeffs(b); return a0*x*x + a1*x + a2; }
function gVal(x){ const [b0,b1,b2]=G; return b0*x*x + b1*x + b2; }
const slider = document.getElementById('bslider');
const bvalEl = document.getElementById('bval');
const statusEl = document.getElementById('status');
const curvesCv = document.getElementById('curves');
const resCv = document.getElementById('reschart');
const cctx = curvesCv.getContext('2d');
const rctx = resCv.getContext('2d');
// the two values of b where f and g share a root (solved analytically: f(2)=0 at b=-1, f(3)=0 at b=-1.8)
const ZEROS = [-1, -1.8]; // verified offline: resultant(-1) = 0 and resultant(-1.8) = 0
function drawCurves(b){
const W = curvesCv.width, H = curvesCv.height;
cctx.clearRect(0,0,W,H);
const xmin=-1, xmax=7, ymin=-6, ymax=14;
const sx = x => (x - xmin) / (xmax - xmin) * W;
const sy = y => H - (y - ymin) / (ymax - ymin) * H;
// draw x and y axes
cctx.strokeStyle = '#cdd9e3'; cctx.lineWidth = 1;
cctx.beginPath(); cctx.moveTo(0, sy(0)); cctx.lineTo(W, sy(0)); cctx.stroke();
cctx.beginPath(); cctx.moveTo(sx(0), 0); cctx.lineTo(sx(0), H); cctx.stroke();
// g(x) fixed
cctx.strokeStyle = '#e63946'; cctx.lineWidth = 2;
cctx.beginPath();
for(let px=0; px<=W; px++){ const x = xmin + (px/W)*(xmax-xmin); const y = gVal(x);
const py = sy(y); if(px===0) cctx.moveTo(px,py); else cctx.lineTo(px,py); }
cctx.stroke();
// f(x;b)
cctx.strokeStyle = '#1d3557'; cctx.lineWidth = 2;
cctx.beginPath();
for(let px=0; px<=W; px++){ const x = xmin + (px/W)*(xmax-xmin); const y = fVal(b,x);
const py = sy(y); if(px===0) cctx.moveTo(px,py); else cctx.lineTo(px,py); }
cctx.stroke();
// highlight the touching point in green when b is within tolerance of a zero crossing
const nearZero = ZEROS.some(z => Math.abs(b - z) < ROOT_TOL);
if(nearZero){
const z = ZEROS.find(zz => Math.abs(b - zz) < ROOT_TOL);
const rx = z === -1 ? 2 : 3;
cctx.fillStyle = '#0a7d33';
cctx.beginPath(); cctx.arc(sx(rx), sy(gVal(rx)), 6, 0, Math.PI*2); cctx.fill();
}
}
function drawResChart(b){
const W = resCv.width, H = resCv.height;
rctx.clearRect(0,0,W,H);
const bmin=-3, bmax=1, rmin=-20, rmax=70;
const sx = bb => (bb - bmin) / (bmax - bmin) * W;
const sy = rr => H - (rr - rmin) / (rmax - rmin) * H;
rctx.strokeStyle = '#cdd9e3'; rctx.lineWidth = 1;
rctx.beginPath(); rctx.moveTo(0, sy(0)); rctx.lineTo(W, sy(0)); rctx.stroke();
rctx.strokeStyle = '#457b9d'; rctx.lineWidth = 2;
rctx.beginPath();
for(let px=0; px<=W; px++){ const bb = bmin + (px/W)*(bmax-bmin); const rr = resultant(bb);
const py = sy(Math.max(rmin, Math.min(rmax, rr))); if(px===0) rctx.moveTo(px,py); else rctx.lineTo(px,py); }
rctx.stroke();
// mark the true zero crossings of the resultant curve in green
rctx.fillStyle = '#0a7d33';
ZEROS.forEach(z => { rctx.beginPath(); rctx.arc(sx(z), sy(0), 5, 0, Math.PI*2); rctx.fill(); });
// current b marker
rctx.fillStyle = '#e63946';
rctx.beginPath(); rctx.arc(sx(b), sy(Math.max(rmin, Math.min(rmax, resultant(b)))), 4, 0, Math.PI*2); rctx.fill();
}
function render(){
const b = parseFloat(slider.value);
bvalEl.textContent = fmt('b = %b%', { b: b.toFixed(2) });
drawCurves(b);
drawResChart(b);
const r = resultant(b);
const nearZero = ZEROS.some(z => Math.abs(b - z) < ROOT_TOL);
if(nearZero){
statusEl.textContent = fmt('Resultant = %r% (~0) — the curves touch at a common root!', { r: r.toFixed(2) });
statusEl.className = 'status ok';
} else {
statusEl.textContent = fmt('Resultant = %r% (nonzero) — no shared root right now.', { r: r.toFixed(2) });
statusEl.className = 'status bad';
}
}
slider.addEventListener('input', render);
document.getElementById('snap1').addEventListener('click', () => { slider.value = '-1'; render(); });
document.getElementById('snap2').addEventListener('click', () => { slider.value = '-1.8'; render(); });
document.getElementById('reset').addEventListener('click', () => { slider.value = '0.3'; render(); });
render();
<p class="hint">Blue is f(x) = x² + bx + 2b, red is the fixed g(x) = x² - 5x + 6. The chart on the right plots the resultant Res(f,g) as you change b — pure arithmetic on the coefficients, no root-finding.</p>
<div class="controls">
<label for="bslider">b =</label>
<input type="range" id="bslider" min="-3" max="1" step="0.01" value="0.3">
<span id="bval" class="bval">b = 0.30</span>
</div>
<div class="panels">
<div class="panel">
<canvas id="curves" width="320" height="220"></canvas>
<div class="caption">f (blue) and g (red)</div>
</div>
<div class="panel">
<canvas id="reschart" width="320" height="220"></canvas>
<div class="caption">Resultant Res(f,g) vs. b</div>
</div>
</div>
<div class="status" id="status">Drag the slider to change b.</div>
<div class="btns">
<button id="snap1" type="button">Snap to shared root x=2</button>
<button id="snap2" type="button">Snap to shared root x=3</button>
<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; }
.controls { display: flex; align-items: center; gap: .6rem; margin-bottom: .6rem; flex-wrap: wrap; }
.controls label { font-size: .85rem; font-weight: 600; color: #1d3557; }
.controls input[type="range"] { flex: 1; min-width: 160px; accent-color: #1d3557; }
.bval { font: 700 14px ui-monospace, monospace; color: #1d3557; min-width: 80px; }
.panels { display: flex; gap: .8rem; flex-wrap: wrap; margin-bottom: .6rem; }
.panel { flex: 1; min-width: 260px; }
canvas { width: 100%; max-width: 320px; height: auto; background: #f7f9fb; border: 1px solid #cdd9e3; border-radius: 8px; display: block; }
.caption { font-size: .78rem; color: #555; text-align: center; margin-top: .3rem; }
.status { font-size: 1rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #444; }
.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 fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Demo: f(x) = x^2 + b*x + 2b (slider controls b), g(x) = x^2 - 5x + 6 fixed.
// g factors as (x-2)(x-3), so its roots are always x=2 and x=3.
// The resultant Res(f,g) is the determinant of their 4x4 Sylvester matrix.
const G = [1, -5, 6]; // coefficients of the fixed polynomial g(x) = x^2 - 5x + 6
const ROOT_TOL = 0.03;
// coefficients of f(x) = x^2 + b*x + 2b for the current slider value
function fCoeffs(b){ return [1, b, 2 * b]; }
// generic 4x4 determinant via cofactor expansion along the first row
function det3(a){
return a[0][0]*(a[1][1]*a[2][2]-a[1][2]*a[2][1])
- a[0][1]*(a[1][0]*a[2][2]-a[1][2]*a[2][0])
+ a[0][2]*(a[1][0]*a[2][1]-a[1][1]*a[2][0]);
}
function det4(m){
let result = 0;
for(let col=0; col<4; col++){
const minor = [];
for(let r=1;r<4;r++){ const row=[]; for(let c=0;c<4;c++) if(c!==col) row.push(m[r][c]); minor.push(row); }
const sign = (col % 2 === 0) ? 1 : -1;
result += sign * m[0][col] * det3(minor);
}
return result;
}
// build the 4x4 Sylvester matrix from f and g's coefficients and take its determinant
function resultant(b){
const [a0,a1,a2] = fCoeffs(b);
const [b0,b1,b2] = G;
const M = [
[a0,a1,a2,0],
[0,a0,a1,a2],
[b0,b1,b2,0],
[0,b0,b1,b2]
];
return det4(M);
}
function fVal(b,x){ const [a0,a1,a2]=fCoeffs(b); return a0*x*x + a1*x + a2; }
function gVal(x){ const [b0,b1,b2]=G; return b0*x*x + b1*x + b2; }
const slider = document.getElementById('bslider');
const bvalEl = document.getElementById('bval');
const statusEl = document.getElementById('status');
const curvesCv = document.getElementById('curves');
const resCv = document.getElementById('reschart');
const cctx = curvesCv.getContext('2d');
const rctx = resCv.getContext('2d');
// the two values of b where f and g share a root (solved analytically: f(2)=0 at b=-1, f(3)=0 at b=-1.8)
const ZEROS = [-1, -1.8]; // verified offline: resultant(-1) = 0 and resultant(-1.8) = 0
function drawCurves(b){
const W = curvesCv.width, H = curvesCv.height;
cctx.clearRect(0,0,W,H);
const xmin=-1, xmax=7, ymin=-6, ymax=14;
const sx = x => (x - xmin) / (xmax - xmin) * W;
const sy = y => H - (y - ymin) / (ymax - ymin) * H;
// draw x and y axes
cctx.strokeStyle = '#cdd9e3'; cctx.lineWidth = 1;
cctx.beginPath(); cctx.moveTo(0, sy(0)); cctx.lineTo(W, sy(0)); cctx.stroke();
cctx.beginPath(); cctx.moveTo(sx(0), 0); cctx.lineTo(sx(0), H); cctx.stroke();
// g(x) fixed
cctx.strokeStyle = '#e63946'; cctx.lineWidth = 2;
cctx.beginPath();
for(let px=0; px<=W; px++){ const x = xmin + (px/W)*(xmax-xmin); const y = gVal(x);
const py = sy(y); if(px===0) cctx.moveTo(px,py); else cctx.lineTo(px,py); }
cctx.stroke();
// f(x;b)
cctx.strokeStyle = '#1d3557'; cctx.lineWidth = 2;
cctx.beginPath();
for(let px=0; px<=W; px++){ const x = xmin + (px/W)*(xmax-xmin); const y = fVal(b,x);
const py = sy(y); if(px===0) cctx.moveTo(px,py); else cctx.lineTo(px,py); }
cctx.stroke();
// highlight the touching point in green when b is within tolerance of a zero crossing
const nearZero = ZEROS.some(z => Math.abs(b - z) < ROOT_TOL);
if(nearZero){
const z = ZEROS.find(zz => Math.abs(b - zz) < ROOT_TOL);
const rx = z === -1 ? 2 : 3;
cctx.fillStyle = '#0a7d33';
cctx.beginPath(); cctx.arc(sx(rx), sy(gVal(rx)), 6, 0, Math.PI*2); cctx.fill();
}
}
function drawResChart(b){
const W = resCv.width, H = resCv.height;
rctx.clearRect(0,0,W,H);
const bmin=-3, bmax=1, rmin=-20, rmax=70;
const sx = bb => (bb - bmin) / (bmax - bmin) * W;
const sy = rr => H - (rr - rmin) / (rmax - rmin) * H;
rctx.strokeStyle = '#cdd9e3'; rctx.lineWidth = 1;
rctx.beginPath(); rctx.moveTo(0, sy(0)); rctx.lineTo(W, sy(0)); rctx.stroke();
rctx.strokeStyle = '#457b9d'; rctx.lineWidth = 2;
rctx.beginPath();
for(let px=0; px<=W; px++){ const bb = bmin + (px/W)*(bmax-bmin); const rr = resultant(bb);
const py = sy(Math.max(rmin, Math.min(rmax, rr))); if(px===0) rctx.moveTo(px,py); else rctx.lineTo(px,py); }
rctx.stroke();
// mark the true zero crossings of the resultant curve in green
rctx.fillStyle = '#0a7d33';
ZEROS.forEach(z => { rctx.beginPath(); rctx.arc(sx(z), sy(0), 5, 0, Math.PI*2); rctx.fill(); });
// current b marker
rctx.fillStyle = '#e63946';
rctx.beginPath(); rctx.arc(sx(b), sy(Math.max(rmin, Math.min(rmax, resultant(b)))), 4, 0, Math.PI*2); rctx.fill();
}
function render(){
const b = parseFloat(slider.value);
bvalEl.textContent = fmt('b = %b%', { b: b.toFixed(2) });
drawCurves(b);
drawResChart(b);
const r = resultant(b);
const nearZero = ZEROS.some(z => Math.abs(b - z) < ROOT_TOL);
if(nearZero){
statusEl.textContent = fmt('Resultant = %r% (~0) — the curves touch at a common root!', { r: r.toFixed(2) });
statusEl.className = 'status ok';
} else {
statusEl.textContent = fmt('Resultant = %r% (nonzero) — no shared root right now.', { r: r.toFixed(2) });
statusEl.className = 'status bad';
}
}
slider.addEventListener('input', render);
document.getElementById('snap1').addEventListener('click', () => { slider.value = '-1'; render(); });
document.getElementById('snap2').addEventListener('click', () => { slider.value = '-1.8'; render(); });
document.getElementById('reset').addEventListener('click', () => { slider.value = '0.3'; render(); });
render();
Drag the slider slowly. Watch the resultant curve and the two parabolas at the same time: every time the resultant crosses zero, the two curves are touching at a common point. The determinant "feels" the tangency algebraically, before you ever look at the picture.
Comments
Loading comments...