import{readVerniers,dualDifference,wrap}from'./angles.mjs';export{readVerniers,dualDifference,wrap};export const MODEL_VERSION='1.0.0',EXPERIMENT_ID='course-04/transmission-grating';export const DEFAULTS=Object.freeze({spacing_um:2,wavelength_nm:546.1,order:1,slit_count:20,open_fraction:.25,zero_deg:10,eccentricity_arcmin:5}); export function validateParameters(p){for(const[k,l,h]of[['spacing_um',1,4],['wavelength_nm',400,700],['order',1,3],['slit_count',10,50],['open_fraction',.1,.5],['zero_deg',0,360],['eccentricity_arcmin',0,10]])if(!Number.isFinite(p[k])||p[k]h)throw new RangeError(`${k}须在${l}–${h}`);if(!Number.isInteger(p.order)||!Number.isInteger(p.slit_count))throw new RangeError('级次与缝数须为整数');return{...p};} export function diffractionIntensity(angle_deg,p){validateParameters(p);if(!Number.isFinite(angle_deg)||Math.abs(angle_deg)>=90)throw new RangeError('角度须在±90°内');const s=Math.sin(angle_deg*Math.PI/180),q=Math.PI*p.spacing_um*1e3*s/p.wavelength_nm,beta=p.open_fraction*q,sinc=Math.abs(beta)<1e-8?1-beta*beta/6:Math.sin(beta)/beta;const qResidual=q-Math.round(q/Math.PI)*Math.PI,grating=Math.abs(qResidual)<1e-8?1:Math.sin(p.slit_count*qResidual)/(p.slit_count*Math.sin(qResidual));return sinc*sinc*grating*grating;} export function forward(p){validateParameters(p);const ratio=p.order*p.wavelength_nm/(p.spacing_um*1e3);if(ratio>=1)throw new RangeError('该级无可传播主极大,请减小级次或增加光栅间距');const theta=Math.asin(ratio)*180/Math.PI;return{reference_deg:-theta,target_deg:theta,true_value:p.wavelength_nm,unit:'nm',theta_deg:theta};} export function inverse(delta,p){validateParameters(p);if(!Number.isFinite(delta)||Math.abs(delta)>=180)throw new RangeError('双侧角差须小于180°');return p.spacing_um*1e3*Math.sin(Math.abs(delta)*Math.PI/360)/p.order;} export function measurement(p,source='ideal-model',start=null,end=null){validateParameters(p);if(!['ideal-model','manual-telescope'].includes(source))throw new RangeError('记录身份无效');const f=forward(p),a=source==='ideal-model'?readVerniers(f.reference_deg,p):start,b=source==='ideal-model'?readVerniers(f.target_deg,p):end;if(!a||!b)throw new RangeError('先保存左右谱线方向');const diff=dualDifference(a,b);return{experiment_id:EXPERIMENT_ID,model_version:MODEL_VERSION,source,parameters:{...p},start:a,end:b,...diff,estimate:inverse(diff.average_difference_deg,p),expected:f.true_value,unit:f.unit};}