from pathlib import Path
import json,csv,hashlib,sys
import numpy as np
OUT=Path(__file__).resolve().parent
SEED=27092026;N=1000
noise=np.random.default_rng(SEED).standard_normal((N,4))
def run(spread,sigma=.1,distance=100):
 angles=np.deg2rad(np.linspace(-spread/2,spread/2,4));landmarks=distance*np.stack([np.cos(angles),np.sin(angles)],axis=1)
 theta=angles+np.deg2rad(sigma)*noise
 normals=np.stack([-np.sin(theta),np.cos(theta)],axis=2)
 A=np.einsum('nki,nkj->nij',normals,normals)
 b=np.einsum('nki,nkj,kj->ni',normals,normals,landmarks)
 valid=np.linalg.det(A)>1e-12
 positions=np.linalg.solve(A[valid],b[valid,:,None])[:,:,0];errors=np.linalg.norm(positions,axis=1)
 summary={'spread_deg':spread,'bearing_noise_sd_deg':sigma,'range_m':distance,'trials':N,'failed':int(N-valid.sum()),'median_error_m':float(np.median(errors)) if len(errors) else None,'p95_error_m':float(np.percentile(errors,95)) if len(errors) else None}
 return summary,positions,errors,np.flatnonzero(valid)
spreads=[5,10,20,30,45,60,90,120,160];summaries=[];trials=[]
for spread in spreads:
 summary,positions,errors,indices=run(spread);summaries.append(summary)
 assert len(indices)==N
 for idx,pos,error in zip(indices,positions,errors):trials.append(dict(spread_deg=spread,trial_id=int(idx)+1,x_m=float(pos[0]),y_m=float(pos[1]),error_m=float(error)))
reference=json.loads((OUT/'reference-study.json').read_text());maxerror=0
for actual,stored in zip(summaries,reference['cases']):
 for key,value in actual.items():maxerror=max(maxerror,abs(value-stored[key]));assert abs(value-stored[key])<1e-12
assert len(trials)==9000 and run(90,0)[0]['p95_error_m']<1e-10 and run(0,0)[0]['failed']==N
for spread in spreads:assert abs(run(spread,distance=200)[0]['median_error_m']-2*run(spread)[0]['median_error_m'])<1e-9
for name,rows in [('trials.csv',trials),('summary.csv',summaries)]:
 with(OUT/name).open('w',newline='',encoding='utf8')as f:w=csv.DictWriter(f,fieldnames=list(rows[0]));w.writeheader();w.writerows(rows)
report=dict(all_pass=True,trial_rows=9000,summary_rows=9,max_stored_summary_difference=maxerror,comparison_absolute_tolerance=1e-12,no_failed_exported_cases=True,zero_noise_and_singular_checks=True,range_scaling=True,runtime=dict(python=sys.version,numpy=np.__version__),generator_sha256=hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),reference_sha256=hashlib.sha256((OUT/'reference-study.json').read_bytes()).hexdigest())
(OUT/'validation.json').write_text(json.dumps(report,indent=2)+'\n');print(json.dumps(report))
