export const MODEL_VERSION='1.0.0';export const DEFAULT_PARAMETERS=Object.freeze({viscosity:.5,diameter:.000990,tubeDiameter:.02,distance:.2,entryDistance:.02,sphereDensity:7800,liquidDensity:950,gravity:9.8});export const PARAMETER_BOUNDS=Object.freeze({viscosity:[.2,1.2],diameter:[.0005,.002],tubeDiameter:[.02,.04],distance:[.1,.3],entryDistance:[0,.05],sphereDensity:[7800,7800],liquidDensity:[950,950],gravity:[9.8,9.8]}); function base(p){const wall=1+2.4*p.diameter/p.tubeDiameter,speed=(p.sphereDensity-p.liquidDensity)*p.gravity*p.diameter**2/(18*p.viscosity*wall),tau=p.sphereDensity*p.diameter**2/(18*p.viscosity*wall),reynolds=p.liquidDensity*speed*p.diameter/p.viscosity;return{wall,speed,tau,reynolds};} export function validateParameters(p){for(const[k,[lo,hi]]of Object.entries(PARAMETER_BOUNDS))if(!Number.isFinite(p[k])||p[k]hi)throw new RangeError(`${k} outside [${lo},${hi}]`);if(p.diameter/p.tubeDiameter>.1+1e-12||base(p).reynolds>.1)throw new RangeError('教学Stokes域要求d/D≤0.1、Re≤0.1;请提高黏度或减小球径。');return p;} export function properties(p){validateParameters(p);return base(p);} export function motion(p,t){const q=properties(p);if(!Number.isFinite(t)||t<0)throw new RangeError('invalid time');return{position:q.speed*(t+q.tau*Math.expm1(-t/q.tau)),velocity:q.speed*(-Math.expm1(-t/q.tau))};} export function timeAtDistance(p,x){const q=properties(p);if(!Number.isFinite(x)||x<0)throw new RangeError('invalid distance');if(x===0)return 0;let lo=0,hi=x/q.speed+q.tau*2;for(let i=0;i<70;i++){const mid=(lo+hi)/2;if(motion(p,mid).position>x)hi=mid;else lo=mid;}return(lo+hi)/2;} export function fallInterval(p){return timeAtDistance(p,p.entryDistance+p.distance)-timeAtDistance(p,p.entryDistance);} export function inferViscosity(p,elapsed){validateParameters(p);if(!Number.isFinite(elapsed)||elapsed<=0)throw new RangeError('计时须为正有限数');const viscosity=(p.sphereDensity-p.liquidDensity)*p.gravity*p.diameter**2*elapsed/(18*p.distance*base(p).wall),speed=p.distance/elapsed,reynolds=p.liquidDensity*speed*p.diameter/viscosity;return{viscosity,speed,reynolds};} export function measurement(p,elapsed,source,startTime=null,stopTime=null){validateParameters(p);if(!['ideal-model','manual-timer'].includes(source))throw new RangeError('unknown source');return{experiment:'course-02/liquid-viscosity',modelVersion:MODEL_VERSION,source,parameters:{...p},raw:{elapsed,startTime,stopTime},derived:inferViscosity(p,elapsed)};}