export const MODEL_VERSION='1.0.0',EXPERIMENT_ID='course-04/polarization';export const DEFAULTS=Object.freeze({amplitude_ma:1.2,background_ma:.02,plate_axis_deg:45,retardance_deg:0,refractive_index:1.5}); export function validateParameters(p){for(const[k,l,h]of[['amplitude_ma',.1,2],['background_ma',0,.1],['plate_axis_deg',0,90],['retardance_deg',0,90],['refractive_index',1.2,1.8]])if(!Number.isFinite(p[k])||p[k]h)throw new RangeError(`${k}须在${l}–${h}范围`);if(![0,90].includes(p.retardance_deg))throw new RangeError('仅开放无波片和理想四分之一波片');return{...p};} export function jones(p){validateParameters(p);const phi=p.plate_axis_deg*Math.PI/180,delta=p.retardance_deg*Math.PI/180,c=Math.cos(phi),s=Math.sin(phi),co=Math.cos(delta),si=Math.sin(delta);return{ex:[c*c+s*s*co,s*s*si],ey:[c*s*(1-co),-c*s*si]};} export function transmission(angle_deg,p){validateParameters(p);if(!Number.isFinite(angle_deg)||angle_deg<0||angle_deg>180)throw new RangeError('检偏器角须在0–180°');const a=angle_deg*Math.PI/180,j=jones(p),re=j.ex[0]*Math.cos(a)+j.ey[0]*Math.sin(a),im=j.ex[1]*Math.cos(a)+j.ey[1]*Math.sin(a);return p.amplitude_ma*(re*re+im*im)+p.background_ma;} export function reflection(angle_deg,p){validateParameters(p);if(!Number.isFinite(angle_deg)||angle_deg<0||angle_deg>85)throw new RangeError('入射角须在0–85°');const a=angle_deg*Math.PI/180,b=Math.asin(Math.sin(a)/p.refractive_index),ci=Math.cos(a),ct=Math.cos(b),n=p.refractive_index,rs=(ci-n*ct)/(ci+n*ct),rp=(n*ci-ct)/(n*ci+ct);return{rs:rs*rs,rp:rp*rp,transmitted_angle_deg:b*180/Math.PI,brewster_deg:Math.atan(n)*180/Math.PI};} export function measurement(angle_deg,p,source='manual-angle',kind='transmission'){validateParameters(p);if(!['manual-angle','ideal-model'].includes(source))throw new RangeError('记录身份无效');if(!['transmission','reflection'].includes(kind))throw new RangeError('记录类型无效');return{experiment_id:EXPERIMENT_ID,model_version:MODEL_VERSION,parameters:{...p},source,kind,angle_deg,...(kind==='transmission'?{current_ma:transmission(angle_deg,p)}:reflection(angle_deg,p))};} export function sameParameters(a,b){return Object.keys(DEFAULTS).every(k=>a?.[k]===b?.[k]);} export function fitMalus(records){if(records.length<3)throw new RangeError('马吕斯拟合需至少3组读数');const p=records[0].parameters;if(p.retardance_deg!==0)throw new RangeError('马吕斯拟合使用未插入波片的线偏振记录');if(!records.every(r=>r.kind==='transmission'&&sameParameters(r.parameters,p)&&r.source===records[0].source))throw new RangeError('记录参数及身份须一致');const xs=records.map(r=>Math.cos(r.angle_deg*Math.PI/180)**2),ys=records.map(r=>r.current_ma),n=xs.length,mx=xs.reduce((a,b)=>a+b)/n,my=ys.reduce((a,b)=>a+b)/n,sxx=xs.reduce((s,v)=>s+(v-mx)**2,0);if(sxx<1e-15)throw new RangeError('需至少两个不同cos²θ值');const amplitude=xs.reduce((s,v,i)=>s+(v-mx)*(ys[i]-my),0)/sxx,background=my-amplitude*mx;return{amplitude_ma:amplitude,background_ma:background,residuals:ys.map((v,i)=>v-amplitude*xs[i]-background)};}