export const MODEL_VERSION='1.0.0';export const DEFAULT_PARAMETERS=Object.freeze({latentHeat:2250000,waterMass:.11976,steamMass:.0078,initialTemperature:12.65,steamTemperature:100,innerMass:.02952,stirrerMass:.00384,waterHeat:4180,innerHeat:897,stirrerHeat:390,moistureFraction:0,heatLossFraction:0});export const PARAMETER_BOUNDS=Object.freeze({latentHeat:[2e6,2.5e6],waterMass:[.08,.2],steamMass:[.001,.015],initialTemperature:[0,30],steamTemperature:[100,100],innerMass:[.02952,.02952],stirrerMass:[.00384,.00384],waterHeat:[4180,4180],innerHeat:[897,897],stirrerHeat:[390,390],moistureFraction:[0,.1],heatLossFraction:[0,.1]}); function result(p){const heatCapacity=p.waterMass*p.waterHeat+p.innerMass*p.innerHeat+p.stirrerMass*p.stirrerHeat,latent=p.steamMass*(1-p.moistureFraction)*p.latentHeat,heatLoss=p.steamMass*p.heatLossFraction*p.latentHeat,finalTemperature=(heatCapacity*p.initialTemperature+latent+p.steamMass*p.waterHeat*p.steamTemperature-heatLoss)/(heatCapacity+p.steamMass*p.waterHeat),sensible=p.steamMass*p.waterHeat*(p.steamTemperature-finalTemperature),absorbed=heatCapacity*(finalTemperature-p.initialTemperature);return{heatCapacity,finalTemperature,latent,sensible,heatLoss,absorbed,initialMass:p.waterMass+p.innerMass,finalMass:p.waterMass+p.innerMass+p.steamMass,initialVoltage:p.initialTemperature+273.15,finalVoltage:finalTemperature+273.15};} 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(result(p).finalTemperature>=p.steamTemperature-1)throw new RangeError('预计末温接近沸点,需减少凝结质量或增加冷水;当前模型要求蒸汽完全凝结。');return p;} export function properties(p){validateParameters(p);return result(p);} export function inferLatent(p,raw,extraFinalStirrer=false){validateParameters(p);if(![raw.initialMass,raw.finalMass,raw.initialVoltage,raw.finalVoltage].every(Number.isFinite))throw new RangeError('称量与电压须为有限数');const waterMass=raw.initialMass-p.innerMass,steamMass=raw.finalMass-raw.initialMass-(extraFinalStirrer?p.stirrerMass:0),T1=raw.initialVoltage-273.15,T2=raw.finalVoltage-273.15;if(waterMass<=0||steamMass<=0||T2<=T1||T2>=100)throw new RangeError('称量构成或温度条件不满足混合量热反演');const C=waterMass*p.waterHeat+p.innerMass*p.innerHeat+p.stirrerMass*p.stirrerHeat,absorbed=C*(T2-T1),sensible=steamMass*p.waterHeat*(100-T2);return{latentHeat:(absorbed-sensible)/steamMass,steamMass,waterMass,heatCapacity:C,absorbed,sensible};} export function measurement(p,raw,source){validateParameters(p);if(!['ideal-model','manual-readings'].includes(source))throw new RangeError('unknown source');let same=null,extra=null;try{same=inferLatent(p,raw);}catch{}try{extra=inferLatent(p,raw,true);}catch{}if(!same&&!extra)throw new RangeError('无可行称量构成;请核对原始质量与温度');return{experiment:'course-01/vaporization-heat',modelVersion:MODEL_VERSION,source,parameters:{...p},raw:{...raw},derived:{sameComposition:same,extraFinalStirrer:extra}};} export function idealReadings(p,extraFinalStirrer=false){const q=properties(p);return{initialMass:q.initialMass,finalMass:q.finalMass+(extraFinalStirrer?p.stirrerMass:0),initialVoltage:q.initialVoltage,finalVoltage:q.finalVoltage,actualExtraFinalStirrer:extraFinalStirrer};}