Below is an elliptic curve plotted over the real numbers. The highlighted dots are its rational points — points whose coordinates are honest fractions. Choose a curve and watch how many appear. Then use the group law: pick two rational points, draw the line through them, and the third place it crosses the curve (reflected over the x-axis) is another rational point.
<p class="hint">An elliptic curve <b>y² = x³ + ax + b</b> over the rationals. The dots are <b>rational points</b>. Pick a curve, then press <b>Add the generator (P + P + …)</b> to use the group law: each step produces a brand-new rational point. A rank-0 curve runs out fast; a rank-1 curve never does.</p>
<div class="curves">
<button data-c="0" class="csel active" type="button">y² = x³ − x (rank 0)</button>
<button data-c="1" class="csel" type="button">y² = x³ + 1 (rank 0)</button>
<button data-c="2" class="csel" type="button">y² = x³ − 2 (rank 1)</button>
</div>
<canvas id="plot" width="360" height="300"></canvas>
<div class="status" id="status">Choose a curve and add its generator.</div>
<div class="btns">
<button id="step" type="button">Add the generator (P + P + …)</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; }
.curves { display: flex; gap: .4rem; flex-wrap: wrap; margin-bottom: .5rem; }
.csel { font: 600 13px system-ui, sans-serif; padding: .35rem .6rem; border: 1px solid #cdd9e3;
background: #e8eef3; color: #1d3557; border-radius: 8px; cursor: pointer; }
.csel.active { background: #1d3557; color: #fff; border-color: #1d3557; }
canvas { background: #fbfcfe; border: 1px solid #cdd9e3; border-radius: 8px; display: block; margin: .3rem 0; }
.status { font-size: .95rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; line-height: 1.4; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.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] ?? ''));
// Birch & Swinnerton-Dyer demo: an elliptic curve y^2 = x^3 + a x + b over Q.
// We start from a known generator P and repeatedly compute P, 2P, 3P, ... with the
// exact group law in rational arithmetic. A rank-1 curve yields endless new rational points; a rank-0 curve only has finitely many (torsion), so the orbit cycles / hits O.
//
// The conjecture: rank == order of vanishing of L(E,s) at s=1 (open Millennium problem).
// --- exact rational arithmetic with BigInt fractions ---
function gcd(a,b){ a=a<0n?-a:a; b=b<0n?-b:b; while(b){ [a,b]=[b,a%b]; } return a; }
function fr(n,d){ if(d<0n){n=-n;d=-d;} const g=gcd(n,d)||1n; return [n/g, d/g]; }
function add(p,q){ return fr(p[0]*q[1]+q[0]*p[1], p[1]*q[1]); }
function sub(p,q){ return fr(p[0]*q[1]-q[0]*p[1], p[1]*q[1]); }
function mul(p,q){ return fr(p[0]*q[0], p[1]*q[1]); }
function div(p,q){ return fr(p[0]*q[1], p[1]*q[0]); }
function eq(p,q){ return p[0]===q[0] && p[1]===q[1]; }
function isZero(p){ return p[0]===0n; }
function toNum(p){ return Number(p[0]) / Number(p[1]); }
function show(p){ return p[1]===1n ? p[0].toString() : p[0].toString()+'/'+p[1].toString(); }
// Curves: a, b (integers), and a starting generator P (or null = no nontrivial point).
const Z = (n)=>[BigInt(n),1n];
const CURVES = [
// y^2 = x^3 - x : rank 0. Only torsion: (0,0),(1,0),(-1,0) and O. 2P = O for each.
{ a:-1n, b:0n, gen:[Z(0), Z(0)], rank:0 },
// y^2 = x^3 + 1 : rank 0. Torsion of order 6, generator (2,3). Orbit is finite.
{ a:0n, b:1n, gen:[Z(2), Z(3)], rank:0 },
// y^2 = x^3 - 2 : rank 1. Generator (3,5): 3^3-2 = 25 = 5^2. Endless new points.
{ a:0n, b:-2n, gen:[Z(3), Z(5)], rank:1 },
];
let curve = CURVES[0];
let P = null; // running multiple kP, as {x:fr,y:fr} or null = O
let k = 0; // how many times we've added the generator
let history = []; // list of rational points found
function onCurve(x,y){ // y^2 == x^3 + a x + b (exact)
const lhs = mul(y,y);
const rhs = add(add(mul(mul(x,x),x), mul([curve.a,1n], x)), [curve.b,1n]);
return eq(lhs, rhs);
}
// Group law: returns {x,y} or null (= point at infinity O).
function ecAdd(Pp, Qp){
if(Pp===null) return Qp;
if(Qp===null) return Pp;
// P + (-P) = O
if(eq(Pp.x,Qp.x) && eq(Pp.y, fr(-Qp.y[0],Qp.y[1]))) return null;
let m;
if(eq(Pp.x,Qp.x) && eq(Pp.y,Qp.y)){
if(isZero(Pp.y)) return null; // vertical tangent -> O (2-torsion)
// m = (3x^2 + a) / (2y)
m = div(add(mul(Z(3), mul(Pp.x,Pp.x)), [curve.a,1n]), mul(Z(2), Pp.y));
} else {
m = div(sub(Qp.y,Pp.y), sub(Qp.x,Pp.x)); // chord slope
}
const x3 = sub(sub(mul(m,m), Pp.x), Qp.x);
const y3 = sub(mul(m, sub(Pp.x, x3)), Pp.y);
return { x:x3, y:y3 };
}
const cv = document.getElementById('plot');
const ctx = cv.getContext('2d');
const W = cv.width, H = cv.height, SCALE = 28, OX = W/2, OY = H/2;
const sx = (x)=> OX + x*SCALE;
const sy = (y)=> OY - y*SCALE;
function drawAxes(){
ctx.clearRect(0,0,W,H);
ctx.strokeStyle = '#dce5ed'; ctx.lineWidth = 1;
for(let gx=-6;gx<=6;gx++){ ctx.beginPath(); ctx.moveTo(sx(gx),0); ctx.lineTo(sx(gx),H); ctx.stroke(); }
for(let gy=-5;gy<=5;gy++){ ctx.beginPath(); ctx.moveTo(0,sy(gy)); ctx.lineTo(W,sy(gy)); ctx.stroke(); }
ctx.strokeStyle = '#9fb2c8'; ctx.lineWidth = 1.4;
ctx.beginPath(); ctx.moveTo(0,sy(0)); ctx.lineTo(W,sy(0)); ctx.stroke();
ctx.beginPath(); ctx.moveTo(sx(0),0); ctx.lineTo(sx(0),H); ctx.stroke();
}
function drawCurve(){
const a = Number(curve.a), b = Number(curve.b);
ctx.strokeStyle = '#1d3557'; ctx.lineWidth = 2;
for(const sign of [1,-1]){
ctx.beginPath(); let started=false;
for(let px=0; px<=W; px++){
const x = (px-OX)/SCALE; const r = x*x*x + a*x + b;
if(r<0){ started=false; continue; }
const y = sign*Math.sqrt(r); const py = sy(y);
if(!started){ ctx.moveTo(px,py); started=true; } else ctx.lineTo(px,py);
}
ctx.stroke();
}
}
function dot(x,y,color,rad){
ctx.beginPath(); ctx.arc(sx(x),sy(y),rad,0,7); ctx.fillStyle=color; ctx.fill();
ctx.strokeStyle='#fff'; ctx.lineWidth=1.2; ctx.stroke();
}
function redraw(){
drawAxes(); drawCurve();
history.forEach((pt,i)=>{ if(pt) dot(toNum(pt.x),toNum(pt.y), i===history.length-1?'#e63946':'#0a7d33', i===history.length-1?6:4.5); });
}
function setStatus(t,cls){ const s=document.getElementById('status'); s.innerHTML=t; s.className='status '+(cls||''); }
function selectCurve(idx){
curve = CURVES[idx]; P=null; k=0; history=[];
document.querySelectorAll('.csel').forEach((bn,i)=>bn.classList.toggle('active', i===idx));
redraw();
setStatus('Curve loaded. Press the button to add its generator.', '');
}
function step(){
const g = { x:curve.gen[0], y:curve.gen[1] };
if(!onCurve(g.x,g.y)){ setStatus('Generator off curve (bug).','bad'); return; }
P = ecAdd(P, g); k++;
if(P===null){
history.push(null); redraw();
setStatus(fmt('%k%·P = O (the point at infinity). This curve is <b>rank 0</b> — its rational points are a finite torsion set. Reset to try a rank-1 curve.', { k: k }),'bad');
return;
}
// duplicate? (torsion cycle)
const dup = history.some(h=>h && eq(h.x,P.x) && eq(h.y,P.y));
history.push({x:P.x,y:P.y});
redraw();
const label = k+'·P = ( '+show(P.x)+' , '+show(P.y)+' )';
if(dup){
setStatus(fmt('%label% — already seen. The orbit <b>cycles</b>: finitely many rational points, <b>rank 0</b>.', { label: label }),'bad');
} else if(curve.rank===0){
setStatus(fmt('%label% (still torsion; will close up soon).', { label: label }), '');
} else {
setStatus(fmt('%label% — a <b>new</b> rational point. Numerators and denominators keep growing: <b>rank 1</b>, infinitely many points.', { label: label }),'ok');
}
console.log(label);
}
document.querySelectorAll('.csel').forEach((bn)=> bn.addEventListener('click',()=>selectCurve(+bn.dataset.c)));
document.getElementById('step').addEventListener('click', step);
document.getElementById('reset').addEventListener('click', ()=>selectCurve(CURVES.indexOf(curve)));
selectCurve(0);
<p class="hint">An elliptic curve <b>y² = x³ + ax + b</b> over the rationals. The dots are <b>rational points</b>. Pick a curve, then press <b>Add the generator (P + P + …)</b> to use the group law: each step produces a brand-new rational point. A rank-0 curve runs out fast; a rank-1 curve never does.</p>
<div class="curves">
<button data-c="0" class="csel active" type="button">y² = x³ − x (rank 0)</button>
<button data-c="1" class="csel" type="button">y² = x³ + 1 (rank 0)</button>
<button data-c="2" class="csel" type="button">y² = x³ − 2 (rank 1)</button>
</div>
<canvas id="plot" width="360" height="300"></canvas>
<div class="status" id="status">Choose a curve and add its generator.</div>
<div class="btns">
<button id="step" type="button">Add the generator (P + P + …)</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; }
.curves { display: flex; gap: .4rem; flex-wrap: wrap; margin-bottom: .5rem; }
.csel { font: 600 13px system-ui, sans-serif; padding: .35rem .6rem; border: 1px solid #cdd9e3;
background: #e8eef3; color: #1d3557; border-radius: 8px; cursor: pointer; }
.csel.active { background: #1d3557; color: #fff; border-color: #1d3557; }
canvas { background: #fbfcfe; border: 1px solid #cdd9e3; border-radius: 8px; display: block; margin: .3rem 0; }
.status { font-size: .95rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; line-height: 1.4; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.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] ?? ''));
// Birch & Swinnerton-Dyer demo: an elliptic curve y^2 = x^3 + a x + b over Q.
// We start from a known generator P and repeatedly compute P, 2P, 3P, ... with the
// exact group law in rational arithmetic. A rank-1 curve yields endless new rational points; a rank-0 curve only has finitely many (torsion), so the orbit cycles / hits O.
//
// The conjecture: rank == order of vanishing of L(E,s) at s=1 (open Millennium problem).
// --- exact rational arithmetic with BigInt fractions ---
function gcd(a,b){ a=a<0n?-a:a; b=b<0n?-b:b; while(b){ [a,b]=[b,a%b]; } return a; }
function fr(n,d){ if(d<0n){n=-n;d=-d;} const g=gcd(n,d)||1n; return [n/g, d/g]; }
function add(p,q){ return fr(p[0]*q[1]+q[0]*p[1], p[1]*q[1]); }
function sub(p,q){ return fr(p[0]*q[1]-q[0]*p[1], p[1]*q[1]); }
function mul(p,q){ return fr(p[0]*q[0], p[1]*q[1]); }
function div(p,q){ return fr(p[0]*q[1], p[1]*q[0]); }
function eq(p,q){ return p[0]===q[0] && p[1]===q[1]; }
function isZero(p){ return p[0]===0n; }
function toNum(p){ return Number(p[0]) / Number(p[1]); }
function show(p){ return p[1]===1n ? p[0].toString() : p[0].toString()+'/'+p[1].toString(); }
// Curves: a, b (integers), and a starting generator P (or null = no nontrivial point).
const Z = (n)=>[BigInt(n),1n];
const CURVES = [
// y^2 = x^3 - x : rank 0. Only torsion: (0,0),(1,0),(-1,0) and O. 2P = O for each.
{ a:-1n, b:0n, gen:[Z(0), Z(0)], rank:0 },
// y^2 = x^3 + 1 : rank 0. Torsion of order 6, generator (2,3). Orbit is finite.
{ a:0n, b:1n, gen:[Z(2), Z(3)], rank:0 },
// y^2 = x^3 - 2 : rank 1. Generator (3,5): 3^3-2 = 25 = 5^2. Endless new points.
{ a:0n, b:-2n, gen:[Z(3), Z(5)], rank:1 },
];
let curve = CURVES[0];
let P = null; // running multiple kP, as {x:fr,y:fr} or null = O
let k = 0; // how many times we've added the generator
let history = []; // list of rational points found
function onCurve(x,y){ // y^2 == x^3 + a x + b (exact)
const lhs = mul(y,y);
const rhs = add(add(mul(mul(x,x),x), mul([curve.a,1n], x)), [curve.b,1n]);
return eq(lhs, rhs);
}
// Group law: returns {x,y} or null (= point at infinity O).
function ecAdd(Pp, Qp){
if(Pp===null) return Qp;
if(Qp===null) return Pp;
// P + (-P) = O
if(eq(Pp.x,Qp.x) && eq(Pp.y, fr(-Qp.y[0],Qp.y[1]))) return null;
let m;
if(eq(Pp.x,Qp.x) && eq(Pp.y,Qp.y)){
if(isZero(Pp.y)) return null; // vertical tangent -> O (2-torsion)
// m = (3x^2 + a) / (2y)
m = div(add(mul(Z(3), mul(Pp.x,Pp.x)), [curve.a,1n]), mul(Z(2), Pp.y));
} else {
m = div(sub(Qp.y,Pp.y), sub(Qp.x,Pp.x)); // chord slope
}
const x3 = sub(sub(mul(m,m), Pp.x), Qp.x);
const y3 = sub(mul(m, sub(Pp.x, x3)), Pp.y);
return { x:x3, y:y3 };
}
const cv = document.getElementById('plot');
const ctx = cv.getContext('2d');
const W = cv.width, H = cv.height, SCALE = 28, OX = W/2, OY = H/2;
const sx = (x)=> OX + x*SCALE;
const sy = (y)=> OY - y*SCALE;
function drawAxes(){
ctx.clearRect(0,0,W,H);
ctx.strokeStyle = '#dce5ed'; ctx.lineWidth = 1;
for(let gx=-6;gx<=6;gx++){ ctx.beginPath(); ctx.moveTo(sx(gx),0); ctx.lineTo(sx(gx),H); ctx.stroke(); }
for(let gy=-5;gy<=5;gy++){ ctx.beginPath(); ctx.moveTo(0,sy(gy)); ctx.lineTo(W,sy(gy)); ctx.stroke(); }
ctx.strokeStyle = '#9fb2c8'; ctx.lineWidth = 1.4;
ctx.beginPath(); ctx.moveTo(0,sy(0)); ctx.lineTo(W,sy(0)); ctx.stroke();
ctx.beginPath(); ctx.moveTo(sx(0),0); ctx.lineTo(sx(0),H); ctx.stroke();
}
function drawCurve(){
const a = Number(curve.a), b = Number(curve.b);
ctx.strokeStyle = '#1d3557'; ctx.lineWidth = 2;
for(const sign of [1,-1]){
ctx.beginPath(); let started=false;
for(let px=0; px<=W; px++){
const x = (px-OX)/SCALE; const r = x*x*x + a*x + b;
if(r<0){ started=false; continue; }
const y = sign*Math.sqrt(r); const py = sy(y);
if(!started){ ctx.moveTo(px,py); started=true; } else ctx.lineTo(px,py);
}
ctx.stroke();
}
}
function dot(x,y,color,rad){
ctx.beginPath(); ctx.arc(sx(x),sy(y),rad,0,7); ctx.fillStyle=color; ctx.fill();
ctx.strokeStyle='#fff'; ctx.lineWidth=1.2; ctx.stroke();
}
function redraw(){
drawAxes(); drawCurve();
history.forEach((pt,i)=>{ if(pt) dot(toNum(pt.x),toNum(pt.y), i===history.length-1?'#e63946':'#0a7d33', i===history.length-1?6:4.5); });
}
function setStatus(t,cls){ const s=document.getElementById('status'); s.innerHTML=t; s.className='status '+(cls||''); }
function selectCurve(idx){
curve = CURVES[idx]; P=null; k=0; history=[];
document.querySelectorAll('.csel').forEach((bn,i)=>bn.classList.toggle('active', i===idx));
redraw();
setStatus('Curve loaded. Press the button to add its generator.', '');
}
function step(){
const g = { x:curve.gen[0], y:curve.gen[1] };
if(!onCurve(g.x,g.y)){ setStatus('Generator off curve (bug).','bad'); return; }
P = ecAdd(P, g); k++;
if(P===null){
history.push(null); redraw();
setStatus(fmt('%k%·P = O (the point at infinity). This curve is <b>rank 0</b> — its rational points are a finite torsion set. Reset to try a rank-1 curve.', { k: k }),'bad');
return;
}
// duplicate? (torsion cycle)
const dup = history.some(h=>h && eq(h.x,P.x) && eq(h.y,P.y));
history.push({x:P.x,y:P.y});
redraw();
const label = k+'·P = ( '+show(P.x)+' , '+show(P.y)+' )';
if(dup){
setStatus(fmt('%label% — already seen. The orbit <b>cycles</b>: finitely many rational points, <b>rank 0</b>.', { label: label }),'bad');
} else if(curve.rank===0){
setStatus(fmt('%label% (still torsion; will close up soon).', { label: label }), '');
} else {
setStatus(fmt('%label% — a <b>new</b> rational point. Numerators and denominators keep growing: <b>rank 1</b>, infinitely many points.', { label: label }),'ok');
}
console.log(label);
}
document.querySelectorAll('.csel').forEach((bn)=> bn.addEventListener('click',()=>selectCurve(+bn.dataset.c)));
document.getElementById('step').addEventListener('click', step);
document.getElementById('reset').addEventListener('click', ()=>selectCurve(CURVES.indexOf(curve)));
selectCurve(0);
Comments
Loading comments...