// Educational double-precision calculations, not a verified numerical certifier.
// Counts are hypothetical; this module does not run an image classifier.
const assert=require('node:assert/strict');
function counts(k,n){assert(Number.isInteger(n)&&n>=1&&n<=10000,'n must be 1..10000');assert(Number.isInteger(k)&&k>=0&&k<=n,'k must be 0..n');}
function normalCDF(z){assert(Number.isFinite(z)&&Math.abs(z)<=8);let term=z,sum=z;for(let i=1;i<400;i++){term*=z*z/(2*i+1);sum+=term;if(Math.abs(term)<1e-16*Math.abs(sum)||term===0)break;}return .5+Math.exp(-z*z/2)*sum/Math.sqrt(2*Math.PI);}
function normalQuantile(p){assert(Number.isFinite(p)&&p>=1e-6&&p<=1-1e-6,'Quantile supported for p in [1e-6,1-1e-6]');let lo=-8,hi=8;for(let i=0;i<60;i++){const mid=(lo+hi)/2;if(normalCDF(mid)
=0&&p<=1);if(k===0||p===1)return 1;if(p===0)return 0;
// Log PMF recurrence, then log-sum-exp: no factorial overflow.
let logChoose=0;for(let j=1;j<=k;j++)logChoose+=Math.log(n-j+1)-Math.log(j);
let logTerm=logChoose+k*Math.log(p)+(n-k)*Math.log1p(-p),max=logTerm,sum=1;
for(let j=k;jmax){sum=sum*Math.exp(max-logTerm)+1;max=logTerm;}else sum+=Math.exp(logTerm-max);}
return Math.min(1,Math.exp(max)*sum);
}
function lowerBound(k,n,alpha){counts(k,n);assert(Number.isFinite(alpha)&&alpha>=1e-6&&alpha<=.1,'alpha must be 1e-6..0.1');if(k===0)return 0;if(k===n)return Math.exp(Math.log(alpha)/n);
// Solve P_p[K >= k] = alpha. Returning the lower bisection endpoint does
// not constitute a proof that floating-point evaluation is outward-rounded.
let lo=0,hi=k/n;for(let i=0;i<60;i++){const mid=(lo+hi)/2;if(binomialTail(k,n,mid)=2&&selection.length<=20);assert(Array.isArray(estimation)&&estimation.length===selection.length);
for(const a of [selection,estimation]){assert(a.every(x=>Number.isInteger(x)&&x>=0));counts(0,a.reduce((s,x)=>s+x,0));}
assert(Number.isFinite(sigma)&&sigma>0&&sigma<=10,'sigma must be positive and at most 10');
const chosen=selection.indexOf(Math.max(...selection)),n=estimation.reduce((s,x)=>s+x,0),k=estimation[chosen],pLower=lowerBound(k,n,alpha),abstain=pLower<=.5;
return {chosen,n,k,alpha,sigma,pLower,abstain,radius:abstain?0:sigma*normalQuantile(pLower)};
}
const examples=[
{id:'clear',selection:[8,2],estimation:[90,10]},
{id:'weak-majority',selection:[8,2],estimation:[60,40]},
{id:'selection-miss',selection:[8,2],estimation:[10,90]},
{id:'unanimous',selection:[8,2],estimation:[100,0]}
];
module.exports={normalCDF,normalQuantile,binomialTail,lowerBound,certify,examples};