{
const W = 400, H = 360, CX = W/2, CY = H/2, SCALE = 70;
const svgNS = "http://www.w3.org/2000/svg";
const funcData = {
"1/(1+z²)": {
poles: [{re:0,im:1,res:{re:0,im:-0.5},label:"i"},{re:0,im:-1,res:{re:0,im:0.5},label:"−i"}],
realIntegral: "π",
realValue: Math.PI,
eval: (re,im) => {
const dr=re, di=im+1, d=dr*dr+di*di;
if(d<1e-8) return [NaN,NaN];
const dr2=re, di2=im-1, d2=dr2*dr2+di2*di2;
// 1/(z²+1) = 1/((z+i)(z-i))
// just colour: use |f|
// f = 1/(1+z^2): real = (1-y^2+x^2-2x^2)/... let's compute directly
const r2 = 1 + re*re - im*im; const i2 = 2*re*im;
const den = r2*r2 + i2*i2;
if(den<1e-10) return [NaN,NaN];
return [r2/den, -i2/den];
}
},
"1/((z²+1)(z²+4))": {
poles: [
{re:0,im:1,res:{re:0,im:-1/6},label:"i"},
{re:0,im:-1,res:{re:0,im:1/6},label:"−i"},
{re:0,im:2,res:{re:0,im:1/12},label:"2i"},
{re:0,im:-2,res:{re:0,im:-1/12},label:"−2i"}
],
realIntegral: "π/2",
realValue: Math.PI/2,
eval: (re,im) => {
// f = 1/((z^2+1)(z^2+4))
const a1=re*re-im*im+1, b1=2*re*im, a2=re*re-im*im+4, b2=2*re*im;
const ra=a1*a2-b1*b2, ia=a1*b2+a2*b1;
const d=ra*ra+ia*ia; if(d<1e-10) return [NaN,NaN];
return [ra/d,-ia/d];
}
},
"z²/((z²−1)(z+2))": {
poles: [
{re:1,im:0,res:{re:1/3,im:0},label:"1"},
{re:-1,im:0,res:{re:1/3,im:0},label:"−1"},
{re:-2,im:0,res:{re:4/3,im:0},label:"−2"}
],
realIntegral: "(sum of residues)",
realValue: null,
eval: (re,im) => {
const z2r=re*re-im*im, z2i=2*re*im;
const d1r=z2r-1,d1i=z2i;
const d2r=re+2,d2i=im;
const d12r=d1r*d2r-d1i*d2i, d12i=d1r*d2i+d1i*d2r;
const nd=d12r*d12r+d12i*d12i; if(nd<1e-10) return [NaN,NaN];
return [(z2r*d12r+z2i*d12i)/nd,(z2i*d12r-z2r*d12i)/nd];
}
}
};
const funcSelector = Inputs.select(Object.keys(funcData), {
value: "1/(1+z²)", label: "f(z)"
});
const radiusSlider = Inputs.range([0.1, 4.5], {value: 1.5, step: 0.05, label: "Contour radius R"});
function hslToRgb(h,s,l) {
h=((h%1)+1)%1;
const c=(1-Math.abs(2*l-1))*s,x=c*(1-Math.abs((h*6)%2-1)),m=l-c/2;
let r=0,g=0,b=0;
if(h<1/6){r=c;g=x;}else if(h<2/6){r=x;g=c;}else if(h<3/6){g=c;b=x;}
else if(h<4/6){g=x;b=c;}else if(h<5/6){r=x;b=c;}else{r=c;b=x;}
return [Math.round((r+m)*255),Math.round((g+m)*255),Math.round((b+m)*255)];
}
function toScreen(re,im) { return [CX+re*SCALE, CY-im*SCALE]; }
function render3(fname, R) {
const fd = funcData[fname];
const canvas = document.createElement("canvas");
canvas.width=W; canvas.height=H;
const ctx=canvas.getContext("2d");
const img=ctx.createImageData(W,H); const data=img.data;
for (let px=0;px<W;px++) {
for (let py=0;py<H;py++) {
const re=(px-CX)/SCALE, im=-(py-CY)/SCALE;
const [wr,wi]=fd.eval(re,im);
if(!isFinite(wr)||!isFinite(wi)) {
const idx=(py*W+px)*4;
data[idx]=255;data[idx+1]=255;data[idx+2]=255;data[idx+3]=255;
continue;
}
const arg=Math.atan2(wi,wr), mag=Math.sqrt(wr*wr+wi*wi);
const hue=(arg/(2*Math.PI)+1)%1;
const logmag=Math.log(mag+0.001);
const brightness=0.5+0.35*Math.tanh(logmag/2);
const [r,g,b]=hslToRgb(hue,0.75,brightness);
const idx=(py*W+px)*4;
data[idx]=r;data[idx+1]=g;data[idx+2]=b;data[idx+3]=255;
}
}
ctx.putImageData(img,0,0);
// Contour
const [cx0,cy0]=toScreen(0,0);
ctx.strokeStyle="rgba(255,255,255,0.9)"; ctx.lineWidth=2;
ctx.beginPath(); ctx.arc(cx0,cy0,R*SCALE,0,2*Math.PI); ctx.stroke();
ctx.fillStyle="rgba(255,255,255,0.8)"; ctx.font="11px sans-serif";
ctx.fillText(`C: |z| = ${R.toFixed(2)}`,5,14);
// Pole markers and labels
const enclosed = [];
fd.poles.forEach(p => {
const [psx,psy]=toScreen(p.re,p.im);
const inC = Math.sqrt(p.re*p.re+p.im*p.im) < R;
if(inC) enclosed.push(p);
ctx.strokeStyle=inC?"#fbbf24":"rgba(255,255,255,0.7)";
ctx.lineWidth=2;
ctx.beginPath(); ctx.moveTo(psx-7,psy-7); ctx.lineTo(psx+7,psy+7); ctx.stroke();
ctx.beginPath(); ctx.moveTo(psx+7,psy-7); ctx.lineTo(psx-7,psy+7); ctx.stroke();
ctx.fillStyle=inC?"#fbbf24":"rgba(255,255,255,0.7)";
ctx.font="10px sans-serif";
ctx.fillText(p.label,psx+9,psy+4);
});
// Residue sum
let sumRe=0, sumIm=0;
enclosed.forEach(p => { sumRe+=p.res.re; sumIm+=p.res.im; });
const integral2piRe = -2*Math.PI*sumIm;
const integral2piIm = 2*Math.PI*sumRe;
const infoDiv=document.createElement("div");
infoDiv.style.cssText="margin-top:0.5rem; font-family:sans-serif; font-size:0.85em; background:#f0f9ff; border-radius:4px; padding:0.5rem;";
const encStr=enclosed.length===0?"none":enclosed.map(p=>p.label).join(", ");
const resStr=enclosed.length===0?"0":(`${sumRe.toFixed(4)}${sumIm>=0?"+":""}${sumIm.toFixed(4)}i`);
const intStr=enclosed.length===0?"0":`${integral2piRe.toFixed(4)} + ${integral2piIm.toFixed(4)}i`;
const realStr=(fd.realValue!==null&&enclosed.some(p=>p.im>0))
?`<div style="color:#059669">Real integral = 2\u03c0 \xd7 Im(upper-half sum) = ${(2*Math.PI*sumRe).toFixed(4)} [predicted: ${fd.realIntegral}]</div>`
:"";
infoDiv.innerHTML=`
<div><strong>Enclosed poles:</strong> ${encStr}</div>
<div><strong>Sum of residues:</strong> ${resStr}</div>
<div><strong>\u222e<sub>C</sub> f\u202fdz = 2\u03c0i \xd7 \u03a3Res = </strong><span style="color:#1e40af">${intStr}</span></div>
${realStr}
`;
const wrap=document.createElement("div");
wrap.appendChild(canvas);
wrap.appendChild(infoDiv);
return wrap;
}
const container=document.createElement("div");
container.style.cssText="border:1px solid #e5e7eb; border-radius:8px; padding:1rem; margin:1rem 0;";
const title=document.createElement("div");
title.style.cssText="font-weight:600; margin-bottom:0.5rem; font-family:sans-serif;";
title.textContent="Residue theorem: sweeping poles with the contour";
container.appendChild(title);
container.appendChild(funcSelector);
container.appendChild(radiusSlider);
const vizDiv3=document.createElement("div");
container.appendChild(vizDiv3);
const note=document.createElement("div");
note.style.cssText="margin-top:0.5rem; font-size:0.82em; color:#6b7280; font-style:italic;";
note.textContent="Domain colouring shows f(z): poles appear as white blooms. Gold \u00d7 marks = poles inside contour. As R increases past each pole, the running residue sum updates and the integral changes. Each pole contributes 2\u03c0i times its residue.";
container.appendChild(note);
function update3() {
vizDiv3.innerHTML="";
vizDiv3.appendChild(render3(funcSelector.value, radiusSlider.value));
}
funcSelector.addEventListener("input", update3);
radiusSlider.addEventListener("input", update3);
update3();
return container;
}