/** Ideal equal-amplitude reflected Newton rings. All lengths explicitly in mm. */ export const MODEL_VERSION='1.0.0'; export const EXPERIMENT_ID='course-04/newton-rings'; export const LIMITS=Object.freeze({radius_mm:[300,1500],wavelength_nm:[400,700],gap_fraction:[0,.5],center_mm:[20,30]}); export const DEFAULTS=Object.freeze({radius_mm:800,wavelength_nm:589.3,gap_fraction:0,center_mm:25}); export function validateParameters(p){for(const [key,[lo,hi]]of Object.entries(LIMITS))if(!Number.isFinite(p[key])||p[key]hi)throw new RangeError(`${key} 必须在 ${lo}–${hi} 内`);return {...p};} export function intensity(x_mm,y_mm,p){validateParameters(p);if(!Number.isFinite(x_mm)||!Number.isFinite(y_mm))throw new RangeError('坐标必须有限');const lam=p.wavelength_nm*1e-6,t=p.gap_fraction*lam+(x_mm*x_mm+y_mm*y_mm)/(2*p.radius_mm);return Math.sin(2*Math.PI*t/lam)**2;} export function intensityField(p,extent_mm=5,size=501){validateParameters(p);if(!Number.isFinite(extent_mm)||extent_mm<=0||!Number.isInteger(size)||size<3||size>2001)throw new RangeError('光场网格无效');const pixels=new Uint8ClampedArray(size*size*4),lam=p.wavelength_nm*1e-6;for(let j=0;j100)throw new RangeError('暗环级数须为1–100的整数');const r2=p.radius_mm*p.wavelength_nm*1e-6*(order-2*p.gap_fraction);if(r2<=0)throw new RangeError('该级暗环收缩为中心点,请选择更高级次');return Math.sqrt(r2);} export function measurement(order,p,source='ideal-model',readings=null){validateParameters(p);if(!['ideal-model','manual-micrometer'].includes(source))throw new RangeError('记录来源无效');const r=source==='ideal-model'?darkRadius(order,p):null;if(!Number.isInteger(order)||order<1||order>100)throw new RangeError('暗环级数须为1–100的整数');const left=source==='ideal-model'?p.center_mm-r:readings?.left_mm,right=source==='ideal-model'?p.center_mm+r:readings?.right_mm;if(!Number.isFinite(left)||!Number.isFinite(right)||right<=left)throw new RangeError('右读数须大于左读数');return {experiment_id:EXPERIMENT_ID,model_version:MODEL_VERSION,source,parameters:{...p},order,left_mm:left,right_mm:right,diameter_mm:right-left,diameter_squared_mm2:(right-left)**2};} export function fitMeasurements(records,wavelength_nm){if(!Number.isFinite(wavelength_nm)||wavelength_nm<=0)throw new RangeError('波长必须为正');if(records.length<3)throw new RangeError('拟合需要至少3组记录');const x=records.map(r=>r.order),y=records.map(r=>(r.right_mm-r.left_mm)**2);if(x.some(v=>!Number.isInteger(v)||v<1)||y.some(v=>!Number.isFinite(v)||v<=0))throw new RangeError('记录包含无效读数');if(new Set(x).size!==x.length)throw new RangeError('每一级仅保留一组读数,重复读数请分别分析');const n=x.length,mx=x.reduce((a,b)=>a+b,0)/n,my=y.reduce((a,b)=>a+b,0)/n,sxx=x.reduce((a,b)=>a+(b-mx)**2,0),slope=x.reduce((a,b,i)=>a+(b-mx)*(y[i]-my),0)/sxx,intercept=my-slope*mx,residuals=x.map((v,i)=>y[i]-slope*v-intercept),sse=residuals.reduce((a,b)=>a+b*b,0);if(slope<=0)throw new RangeError('拟合斜率非正,请核对环级与左右读数');const lam=wavelength_nm*1e-6;return {count:n,slope_mm2:slope,intercept_mm2:intercept,radius_mm:slope/(4*lam),radius_residual_se_mm:Math.sqrt(sse/(n-2)/sxx)/(4*lam),gap_from_intercept_nm:-intercept/(8*(slope/(4*lam)))*1e6,residuals_mm2:residuals,r_squared:my===0?null:1-sse/y.reduce((a,b)=>a+(b-my)**2,0)};} export function pairDifferences(records,separation,wavelength_nm){if(!Number.isInteger(separation)||separation<1)throw new RangeError('级差须为正整数');if(!Number.isFinite(wavelength_nm)||wavelength_nm<=0)throw new RangeError('波长必须为正');if(new Set(records.map(r=>r.order)).size!==records.length)throw new RangeError('隔级差需要唯一环级');const lookup=new Map(records.map(r=>[r.order,r]));return [...lookup.keys()].sort((a,b)=>a-b).flatMap(m=>{const a=lookup.get(m),b=lookup.get(m+separation);return b?[{order:m,upper_order:m+separation,radius_mm:((b.right_mm-b.left_mm)**2-(a.right_mm-a.left_mm)**2)/(4*separation*wavelength_nm*1e-6)}]:[]});} export function wedgeThickness({intervals,fringe_length_mm,wedge_length_mm},wavelength_nm){if(!Number.isInteger(intervals)||intervals<1||![fringe_length_mm,wedge_length_mm,wavelength_nm].every(v=>Number.isFinite(v)&&v>0))throw new RangeError('劈尖参数须为正,间隔数须为整数');return intervals*wavelength_nm*1e-6*wedge_length_mm/(2*fringe_length_mm);} export function sameParameters(a,b){return Object.keys(DEFAULTS).every(k=>a?.[k]===b?.[k]);}