Files
site/dial/index.html
T

198 lines
17 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>The Zeta Dial</title>
<meta name="description" content="Turn the ball about the ½–∞ strut: s − ½ ↦ e^{iθ}(s − ½). A quarter turn is the Wick rotation, a half turn the functional equation, a full turn is −1 on the odd sheet.">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,500;1,500&family=JetBrains+Mono:wght@400;500&display=swap">
<style>
:root{--bg:#0b0a12;--panel:#15131f;--line:#2a2638;--fg:#e9e4f2;--muted:#9a93ad;--up:#f0b44c;--down:#e2559a;--teal:#4fd1c5;
--display:"Cormorant Garamond",Georgia,serif;--mono:"JetBrains Mono",ui-monospace,monospace;color-scheme:dark}
html,body{margin:0}
body{background:var(--bg);color:var(--fg);font-family:var(--mono);font-size:13.5px;line-height:1.6;
padding:calc(28px + env(safe-area-inset-top,0px)) max(16px,env(safe-area-inset-right,0px)) calc(48px + env(safe-area-inset-bottom,0px)) max(16px,env(safe-area-inset-left,0px))}
main{max-width:1040px;margin:0 auto;display:flex;flex-direction:column;gap:18px}
.eyebrow{font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--muted)} .eyebrow a{color:var(--muted)}
h1{font-family:var(--display);font-weight:500;font-size:clamp(30px,5.5vw,46px);line-height:1.05;margin:.15em 0 0;text-wrap:balance} h1 i{color:var(--down)}
.lede{color:var(--muted);max-width:70ch;margin:0}
.bar{display:flex;flex-wrap:wrap;gap:10px 18px;align-items:center;padding:12px 14px;border:1px solid var(--line);border-radius:6px;background:var(--panel)}
.seg{display:inline-flex;border:1px solid var(--line);border-radius:5px;overflow:hidden}
.seg button{background:transparent;color:var(--fg);border:0;padding:6px 11px;font:inherit;cursor:pointer}
.seg button+button{border-left:1px solid var(--line)} .seg button[aria-pressed="true"]{background:var(--up);color:#1a1408}
label.k{color:var(--muted);font-size:11.5px;letter-spacing:.06em;text-transform:uppercase;margin-right:6px}
input[type=range]{accent-color:var(--up);width:260px;max-width:100%}
.panes{display:grid;grid-template-columns:1.2fr 1fr;gap:12px}
.pane{border:1px solid var(--line);border-radius:6px;background:var(--panel);overflow:hidden}
.pane h2{font-family:var(--display);font-weight:500;font-size:20px;margin:0;padding:8px 12px 0}
.pane p.sub{margin:0;padding:0 12px 6px;color:var(--muted);font-size:12px;min-height:2.6em}
.view{position:relative;aspect-ratio:1;background:#000} .view canvas{position:absolute;inset:0;width:100%;height:100%}
.busy{position:absolute;right:8px;top:6px;font-size:11px;color:var(--up);opacity:0;transition:opacity .2s} .busy.on{opacity:1}
.read{display:grid;grid-template-columns:auto 1fr;gap:4px 14px;padding:10px 12px 12px;font-variant-numeric:tabular-nums}
.read span.k{color:var(--muted)} .read .q{color:var(--up)}
.notes{display:grid;grid-template-columns:1.3fr 1fr;gap:14px}
.card{border:1px solid var(--line);border-radius:6px;background:var(--panel);padding:14px 16px;color:var(--muted);font-size:12.5px}
.card h3{font-family:var(--display);font-weight:500;font-size:19px;color:var(--fg);margin:0 0 6px} .card p{margin:0 0 8px} .card b{color:var(--fg);font-weight:500}
@media (max-width:760px){.panes,.notes{grid-template-columns:1fr}}
</style>
</head>
<body>
<main>
<header>
<div class="eyebrow">maths · <a href="../">two poles</a> · <a href="../zeta/">zeta ball</a> · <a href="../strut/">strut</a> · the dial</div>
<h1>The zeta <i>dial</i></h1>
</header>
<p class="lede">Turn the ball about the strut through ½ and ∞. On the plane that is a plain rotation about ½: <b>s − ½ ↦ e<sup>iθ</sup>(s − ½)</b>. A quarter turn carries the real line onto the critical line (the Wick rotation); a half turn is the functional equation s ↦ 1 − s; a full turn is the identity, with q = −1. The paint shows the function pulled back through the turn, so you watch cosh become cos.</p>
<div class="bar">
<span><label class="k">sheet</label>
<span class="seg"><button data-fn="0" aria-pressed="true">ξ(s) even</button><button data-fn="1" aria-pressed="false">(s−½)·ξ(s) odd</button><button data-fn="2" aria-pressed="false">F(u), u = (s−½)²</button></span></span>
<span><label class="k">θ</label><input type="range" id="th" min="0" max="720" step="1" value="0"> <span id="thRead">0°</span></span>
<span class="seg"><button data-stop="0">0</button><button data-stop="90">¼</button><button data-stop="180">½</button><button data-stop="270">¾</button><button data-stop="360">1</button><button data-stop="720">2</button></span>
<span><label class="k">height</label><input type="range" id="span" min="6" max="60" step="1" value="24" style="width:120px"> <span id="spanRead">±24</span></span>
</div>
<div class="panes">
<section class="pane">
<h2 id="title">ξ pulled back through the turn</h2>
<p class="sub" id="sub">white line: where the real axis has gone · gold: where the critical line has gone · dots: the zeros, carried along</p>
<div class="view"><canvas id="c"></canvas><canvas id="o"></canvas><span class="busy" id="b">painting</span></div>
</section>
<section class="pane">
<h2>The ball, from above the lamp</h2>
<p class="sub">looking down the ½–∞ strut: the equator is the rim, the real axis and the imaginary axis are the two diameters; the needle is θ</p>
<div class="view"><canvas id="d"></canvas></div>
<div class="read">
<span class="k">θ</span><span id="r1">0</span>
<span class="k">on the plane</span><span id="r2">s − ½ ↦ s − ½</span>
<span class="k">q = e^{iθ/2}</span><span class="q" id="r3">1</span>
<span class="k">even sheet sees</span><span id="r4">+1</span>
<span class="k">odd sheet sees</span><span id="r5">+1</span>
<span class="k">F sees</span><span id="r6">u ↦ u</span>
</div>
</section>
</div>
<section class="notes">
<div class="card">
<h3>Three sheets, one dial</h3>
<p><b>F(u).</b> Write v = s − ½. ξ(½ + v) = F(v²), and F has positive Taylor coefficients. F lives in the u = v² plane; the dial turns u at double speed, u ↦ e<sup>2iθ</sup>u. A half turn of the ball is a full turn of F. The zeros of ξ, at v = ±iγ, become the single points u = −γ² on F's negative real axis. RH, downstairs: every zero of F is real and negative.</p>
<p><b>ξ, even.</b> Take the square root: v = √u. Now the half turn exists as its own move, s ↦ 1 − s, and ξ doesn't notice it: ξ(1 − s) = ξ(s). The two roots ±iγ are the zero pair on the equator.</p>
<p><b>(s − ½)·ξ, odd.</b> Multiply by v. Same zeros, plus a new one at v = 0, the touching point ½. The half turn now shows up as a sign: this sheet sees s ↦ 1 − s as −1. A full turn is +1 again on both sheets; the −1 that a full turn carries in the quaternion reading, q = e<sup>iθ/2</sup>, is what the odd sheet shows at the half.</p>
<p>So the dial reads: 0 = nothing; ¼ = Wick, real axis onto the critical line, cosh onto cos; ½ = the functional equation (+1 even, −1 odd); 1 = home with q = −1; 2 = home with q = +1.</p>
</div>
<div class="card">
<h3>Reading the paint</h3>
<p>Hue is the argument of the value, brightness its size. At θ = 0 the real axis is a bright cosh ridge and the critical line a cos stripe with dark zeros. Drag θ: the ridge and the stripe rotate together about ½. At ¼ they have swapped places on the screen. At ½ the picture is back (even sheet) or back with every hue rotated by half a turn (odd sheet, a sign flip).</p>
<p>Zeros are the first 20 pairs on the critical line; on the F sheet they appear once each, on the negative real u-axis.</p>
</div>
</section>
</main>
<script type="text/plain" id="wsrc">
const TAU=2*Math.PI,LN2=Math.LN2,LNPI=Math.log(Math.PI),HALF_LN_TAU=0.5*Math.log(TAU);
const BC=[1/12,-1/720,1/30240,-1/1209600,1/47900160,-691/1307674368000,1/74724249600,-3617/10670622842880000,43867/5109094217170944000,-174611/802857662698291200000];
function zetaEM(sr,si,out){
const N=Math.min(560,Math.max(14,Math.ceil(Math.hypot(sr,si)/Math.PI)+12));let re=0,im=0;
for(let n=1;n<N;n++){const l=Math.log(n),m=Math.exp(-sr*l),t=-si*l;re+=m*Math.cos(t);im+=m*Math.sin(t);}
const lN=Math.log(N),m0=Math.exp(-sr*lN),t0=-si*lN,nr=m0*Math.cos(t0),ni=m0*Math.sin(t0);re+=nr/2;im+=ni/2;
const ar=N*nr,ai=N*ni,br=sr-1,bi=si,d=br*br+bi*bi;re+=(ar*br+ai*bi)/d;im+=(ai*br-ar*bi)/d;
let pr=sr,pi=si,sc=1/N;
for(let k=1;k<=BC.length;k++){const tr=pr*nr-pi*ni,ti=pr*ni+pi*nr,c=BC[k-1]*sc;re+=c*tr;im+=c*ti;
if(Math.abs(c)*Math.hypot(tr,ti)<1e-16*Math.hypot(re,im))break;
const xr=sr+2*k-1,yr=sr+2*k,qr=xr*yr-si*si,qi=si*(xr+yr);const npr=pr*qr-pi*qi;pi=pr*qi+pi*qr;pr=npr;sc/=N*N;}
out[0]=re;out[1]=im;}
function lnGamma(zr,zi,out){let accR=0,accI=0;while(zr<9){accR+=0.5*Math.log(zr*zr+zi*zi);accI+=Math.atan2(zi,zr);zr+=1;}
const lr=0.5*Math.log(zr*zr+zi*zi),li=Math.atan2(zi,zr);let r=(zr-0.5)*lr-zi*li-zr+HALF_LN_TAU,i=(zr-0.5)*li+zi*lr-zi;
const d=zr*zr+zi*zi,ir=zr/d,ii=-zi/d,i2r=ir*ir-ii*ii,i2i=2*ir*ii;let pr=ir,pi=ii;const cs=[1/12,-1/360,1/1260,-1/1680];
for(let j=0;j<4;j++){r+=cs[j]*pr;i+=cs[j]*pi;const t=pr*i2r-pi*i2i;pi=pr*i2i+pi*i2r;pr=t;}out[0]=r-accR;out[1]=i-accI;}
function logSin(x,y,out){if(y>15){out[0]=-LN2+y;out[1]=Math.PI/2-x;return;}if(y<-15){out[0]=-LN2-y;out[1]=x-Math.PI/2;return;}
const re=Math.sin(x)*Math.cosh(y),im=Math.cos(x)*Math.sinh(y);out[0]=0.5*Math.log(re*re+im*im);out[1]=Math.atan2(im,re);}
const T1=[0,0],T2=[0,0],T3=[0,0],T4=[0,0],T5=[0,0];
function logZeta(sr,si,out){if(sr>=0.5){zetaEM(sr,si,T1);out[0]=0.5*Math.log(T1[0]*T1[0]+T1[1]*T1[1]);out[1]=Math.atan2(T1[1],T1[0]);return;}
logSin(Math.PI*sr/2,Math.PI*si/2,T1);lnGamma(1-sr,-si,T2);zetaEM(1-sr,-si,T3);
out[0]=sr*LN2+(sr-1)*LNPI+T1[0]+T2[0]+0.5*Math.log(T3[0]*T3[0]+T3[1]*T3[1]);out[1]=si*(LN2+LNPI)+T1[1]+T2[1]+Math.atan2(T3[1],T3[0]);}
function logXi(sr,si,out){if(sr<0.5){sr=1-sr;si=-si;}const pr=0.5*(sr*(sr-1)-si*si),pi=0.5*si*(2*sr-1);
lnGamma(sr/2,si/2,T4);logZeta(sr,si,T5);out[0]=0.5*Math.log(pr*pr+pi*pi)-sr/2*LNPI+T4[0]+T5[0];out[1]=Math.atan2(pi,pr)-si/2*LNPI+T4[1]+T5[1];}
function hsv(h,s,v,buf,o){const i=Math.floor(h*6),f=h*6-i,p=v*(1-s),q=v*(1-f*s),t=v*(1-(1-f)*s);let r,g,b;
switch(((i%6)+6)%6){case 0:r=v;g=t;b=p;break;case 1:r=q;g=v;b=p;break;case 2:r=p;g=v;b=t;break;case 3:r=p;g=q;b=v;break;case 4:r=t;g=p;b=v;break;default:r=v;g=p;b=q;}
buf[o]=r*255;buf[o+1]=g*255;buf[o+2]=b*255;buf[o+3]=255;}
const L=[0,0];
// paint the chosen sheet at plane point (x,y) = v (sheets 0,1) or u (sheet 2), after the dial's pull-back
function paint(x,y,fn,th,buf,o){
let vr,vi;
if(fn===2){ // u-plane: u ↦ e^{2iθ}u, then v = sqrt(u)
const c=Math.cos(2*th),s=Math.sin(2*th),ur=x*c-y*s,ui=x*s+y*c,m=Math.hypot(ur,ui),a=Math.atan2(ui,ur)/2;
vr=Math.sqrt(m)*Math.cos(a);vi=Math.sqrt(m)*Math.sin(a);
}else{const c=Math.cos(th),s=Math.sin(th);vr=x*c-y*s;vi=x*s+y*c;}
if(Math.hypot(vr,vi)>400){buf[o]=buf[o+1]=buf[o+2]=40;buf[o+3]=255;return;}
logXi(0.5+vr,vi,L);
if(fn===1){L[0]+=0.5*Math.log(vr*vr+vi*vi+1e-300);L[1]+=Math.atan2(vi,vr);}
if(!isFinite(L[0])){const v=L[0]<0?0:255;buf[o]=buf[o+1]=buf[o+2]=v;buf[o+3]=255;return;}
let h=(L[1]/TAU)%1;if(h<0)h+=1;
const v=0.2+0.65*(0.5+Math.atan(0.18*L[0])/Math.PI);hsv(h,0.62,v,buf,o);}
onmessage=(e)=>{const m=e.data,n=m.n,buf=new Uint8ClampedArray(n*n*4);
for(let j=0;j<n;j++)for(let i=0;i<n;i++){const x=(i/(n-1)-0.5)*2*m.hw,y=-(j/(n-1)-0.5)*2*m.hw;paint(x,y,m.fn,m.th,buf,(j*n+i)*4);}
postMessage({id:m.id,n,buf},[buf.buffer]);};
</script>
<script>
(()=>{
const $=id=>document.getElementById(id);
const ZEROS=[14.134725,21.022040,25.010858,30.424876,32.935062,37.586178,40.918719,43.327073,48.005151,49.773832,52.970321,56.446248,59.347044,60.831779,65.112544,67.079811,69.546402,72.067158,75.704691,77.144840];
const st={fn:0,th:0,span:24};
const url=URL.createObjectURL(new Blob([$("wsrc").textContent],{type:"text/javascript"}));const W=new Worker(url);let id=0;
function draw(hi){const n=hi?400:150;const my=++id;$("b").classList.add("on");W.postMessage({id:my,n,fn:st.fn,th:st.th*Math.PI/180,hw:st.span});}
W.onmessage=(e)=>{if(e.data.id!==id)return;const c=$("c");c.width=c.height=e.data.n;c.getContext("2d").putImageData(new ImageData(e.data.buf,e.data.n,e.data.n),0,0);$("b").classList.remove("on");overlay();};
function toPx(x,y,S){return[(x/(2*st.span)+0.5)*S,(0.5-y/(2*st.span))*S];}
function overlay(){
const o=$("o"),S=o.clientWidth*devicePixelRatio;o.width=o.height=S;const g=o.getContext("2d");g.clearRect(0,0,S,S);
const th=-st.th*Math.PI/180; // the pull-back shows the axes rotated the other way
const rot=(x,y,a)=>[x*Math.cos(a)-y*Math.sin(a),x*Math.sin(a)+y*Math.cos(a)];
const line=(ax,ay,col)=>{g.strokeStyle=col;g.lineWidth=1.5*devicePixelRatio;g.beginPath();const[x1,y1]=toPx(-200*ax,-200*ay,S),[x2,y2]=toPx(200*ax,200*ay,S);g.moveTo(x1,y1);g.lineTo(x2,y2);g.stroke();};
if(st.fn===2){line(1,0,"rgba(230,230,230,.6)");line(0,1,"rgba(240,180,76,.85)");
g.fillStyle="#fff";for(const z of ZEROS){const[ux,uy]=rot(-z*z,0,2*th);const[x,y]=toPx(ux,uy,S);if(x>=0&&x<=S)g.beginPath(),g.arc(x,y,2.6*devicePixelRatio,0,7),g.fill();}
}else{
const[rx,ry]=rot(1,0,th),[cx,cy]=rot(0,1,th);line(rx,ry,"rgba(230,230,230,.6)");line(cx,cy,"rgba(240,180,76,.85)");
g.fillStyle="#fff";for(const z of ZEROS)for(const sg of[1,-1]){const[vx,vy]=rot(0,sg*z,th);const[x,y]=toPx(vx,vy,S);g.beginPath();g.arc(x,y,2.4*devicePixelRatio,0,7);g.fill();}
if(st.fn===1){const[x,y]=toPx(0,0,S);g.fillStyle="#e2559a";g.beginPath();g.arc(x,y,3.4*devicePixelRatio,0,7);g.fill();}
}
// the origin ½
const[x0,y0]=toPx(0,0,S);g.strokeStyle="#fff";g.lineWidth=1.2*devicePixelRatio;g.beginPath();g.arc(x0,y0,4*devicePixelRatio,0,7);g.stroke();
}
function dial(){
const c=$("d"),S=c.clientWidth*devicePixelRatio;c.width=c.height=S;const g=c.getContext("2d");g.clearRect(0,0,S,S);
const cx=S/2,cy=S/2,R=S*0.36,th=st.th*Math.PI/180;
g.strokeStyle="#f0b44c";g.lineWidth=3*devicePixelRatio;g.beginPath();g.arc(cx,cy,R,0,7);g.stroke(); // equator = rim
g.strokeStyle="rgba(230,230,230,.6)";g.lineWidth=1.5*devicePixelRatio;g.beginPath();g.moveTo(cx-R,cy);g.lineTo(cx+R,cy);g.stroke(); // real axis
g.strokeStyle="rgba(140,140,160,.6)";g.beginPath();g.moveTo(cx,cy-R);g.lineTo(cx,cy+R);g.stroke(); // imaginary axis
const dot=(x,y,col,r,fill=true)=>{g.beginPath();g.arc(x,y,r*devicePixelRatio,0,7);if(fill){g.fillStyle=col;g.fill();}else{g.strokeStyle=col;g.lineWidth=2*devicePixelRatio;g.stroke();}};
dot(cx+R,cy,"#000",7);dot(cx+R,cy,"#f0b44c",7,false);dot(cx-R,cy,"#fff",7); // ∞ (z=+1) and ½ (z=−1) on the rim
dot(cx,cy,"#4fd1c5",6); // the pole axis 0–∞ seen end-on
// needle: θ, and the double-speed hand for q
const nx=cx+0.92*R*Math.cos(-th),ny=cy+0.92*R*Math.sin(-th);
g.strokeStyle="#e2559a";g.lineWidth=2.5*devicePixelRatio;g.beginPath();g.moveTo(cx,cy);g.lineTo(nx,ny);g.stroke();
const qx=cx+0.5*R*Math.cos(-th/2),qy=cy+0.5*R*Math.sin(-th/2);
g.strokeStyle="#f0b44c";g.setLineDash([4*devicePixelRatio,3*devicePixelRatio]);g.beginPath();g.moveTo(cx,cy);g.lineTo(qx,qy);g.stroke();g.setLineDash([]);
g.fillStyle="#9a93ad";g.font=`${11*devicePixelRatio}px JetBrains Mono`;g.fillText("∞",cx+R+10*devicePixelRatio,cy+4*devicePixelRatio);g.fillText("½",cx-R-22*devicePixelRatio,cy+4*devicePixelRatio);
g.fillText("needle: θ · dashed: q = e^{iθ/2}",10*devicePixelRatio,S-10*devicePixelRatio);
const d=st.th;$("r1").textContent=d+"°";
$("r2").textContent=d%360===0?"s − ½ ↦ s − ½":d%360===180?"s − ½ ↦ −(s − ½) i.e. s ↦ 1 − s":d%360===90?"s − ½ ↦ i(s − ½) (Wick)":d%360===270?"s − ½ ↦ −i(s − ½)":"s − ½ ↦ e^{i·"+d+"°}(s − ½)";
const qr=Math.cos(th/2),qi=Math.sin(th/2);$("r3").textContent=(Math.abs(qr)<1e-9?"":qr.toFixed(3))+(Math.abs(qi)<1e-9?"":(qi>0&&Math.abs(qr)>=1e-9?" + ":qi<0?" − ":"")+Math.abs(qi).toFixed(3)+"i");
$("r4").textContent=d%360===0?"+1 (home)":d%360===180?"+1 (ξ(1−s) = ξ(s))":"—";
$("r5").textContent=d%360===0?"+1 (home)":d%360===180?"−1 (sign flip)":"—";
$("r6").textContent="u ↦ e^{i·"+(2*d)%360+"°}u"+((2*d)%360===0?" (home)":"");
}
function all(){draw(false);clearTimeout(all._t);all._t=setTimeout(()=>draw(true),220);dial();
$("title").textContent=st.fn===2?"F pulled back through the turn (u-plane)":st.fn===1?"(s − ½)·ξ pulled back through the turn":"ξ pulled back through the turn";
$("sub").textContent=st.fn===2?"white: the real u-axis · gold: the negative real axis, where F's zeros are (once each) · the dial turns u at double speed":"white line: where the real axis has gone · gold: where the critical line has gone · dots: the zeros, carried along"+(st.fn===1?" · pink: the new zero at ½":"");}
document.querySelectorAll("[data-fn]").forEach(b=>b.onclick=()=>{st.fn=+b.dataset.fn;document.querySelectorAll("[data-fn]").forEach(x=>x.setAttribute("aria-pressed",x===b));all();});
document.querySelectorAll("[data-stop]").forEach(b=>b.onclick=()=>{st.th=+b.dataset.stop;$("th").value=st.th;$("thRead").textContent=st.th+"°";all();});
$("th").oninput=e=>{st.th=+e.target.value;$("thRead").textContent=st.th+"°";dial();overlay();clearTimeout($("th")._t);$("th")._t=setTimeout(all,60);};
$("span").oninput=e=>{st.span=+e.target.value;$("spanRead").textContent="±"+st.span;clearTimeout($("span")._t);$("span")._t=setTimeout(all,120);};
addEventListener("resize",()=>{overlay();dial();});
all();
})();
</script>
</body>
</html>