export const MODEL_VERSION='1.0.0',EXPERIMENT_ID='course-04/single-slit-diffraction'; export const DEFAULTS=Object.freeze({width_mm:.2,distance_mm:800,wavelength_nm:632.8}); export function validateParameters(p){for(const[k,lo,hi]of[['width_mm',.05,.2],['distance_mm',800,2000],['wavelength_nm',500,700]])if(!Number.isFinite(p[k])||p[k]hi)throw new RangeError(`${k}须在${lo}–${hi}范围`);return {...p};} export function sinc(z){return Math.abs(z)<1e-8?1-z*z/6:Math.sin(z)/z;} export function intensity(x_mm,p){validateParameters(p);if(!Number.isFinite(x_mm))throw new RangeError('位置须为有限数');return sinc(Math.PI*p.width_mm*x_mm/(Math.hypot(p.distance_mm,x_mm)*p.wavelength_nm*1e-6))**2;} export function darkPosition(order,p){validateParameters(p);if(!Number.isInteger(order)||order<1||order>20)throw new RangeError('级次须为1–20整数');const s=order*p.wavelength_nm*1e-6/p.width_mm;if(s>=1)throw new RangeError('此级暗纹没有传播方向');return p.distance_mm*s/Math.sqrt(1-s*s);} export function measurement(order,p,source='ideal-model',position=null){validateParameters(p);if(!Number.isInteger(order)||order<1||order>20)throw new RangeError('级次须为1–20整数');if(!['ideal-model','manual-probe'].includes(source))throw new RangeError('记录身份无效');const x=source==='ideal-model'?darkPosition(order,p):Math.abs(position);if(!Number.isFinite(x)||x<=0)throw new RangeError('暗纹距中心须大于零');const s=x/Math.hypot(x,p.distance_mm);return {experiment_id:EXPERIMENT_ID,model_version:MODEL_VERSION,source,parameters:{...p},order,position_mm:x,intensity:intensity(x,p),width_estimate_mm:order*p.wavelength_nm*1e-6/s};} export function fitDark(records){if(records.length<3||new Set(records.map(r=>r.order)).size!==records.length)throw new RangeError('拟合需至少3个唯一暗纹级次');const p=records[0].parameters;if(!records.every(r=>sameParameters(p,r.parameters)&&r.source===records[0].source))throw new RangeError('拟合参数和记录身份须一致');const n=records.length,x=records.map(r=>r.order),y=records.map(r=>r.position_mm/Math.hypot(r.position_mm,p.distance_mm)),xm=x.reduce((a,b)=>a+b)/n,ym=y.reduce((a,b)=>a+b)/n,sxx=x.reduce((s,v)=>s+(v-xm)**2,0),slope=x.reduce((s,v,i)=>s+(v-xm)*(y[i]-ym),0)/sxx,intercept=ym-slope*xm;if(slope<=0)throw new RangeError('斜率须为正');const residuals=y.map((v,i)=>v-slope*x[i]-intercept);return {slope,intercept,width_mm:p.wavelength_nm*1e-6/slope,residuals};} export function fresnelNumber(p){validateParameters(p);return p.width_mm**2/(p.wavelength_nm*1e-6*p.distance_mm);} export function sameParameters(a,b){return Object.keys(DEFAULTS).every(k=>a?.[k]===b?.[k]);}