code wiki / (root) / nx_psychrometrics_test.nx

nx_psychrometrics_test.nx source

↩ module page · 107 lines · 5228 B

1// nx_psychrometrics_test.nx -- KAT gate for the no-float psychrometric floor. 2// 3// EXTERNAL-COMP (sensor-gap census axis-4): the saturation-pressure nodes are 4// checked against NIST/ASME steam-table / ASHRAE reference values, and the 5// derived humidity ratio + enthalpy against ASHRAE Fundamentals reference 6// states -- an OUTSIDE oracle, not self-grading. 7// 8// Coverage: 9// K1 LUT nodes == NIST reference Psat (0/20/25/30 degC) 10// K2 interpolated Psat within band of an independent NIST point (22 degC) 11// K3 Pw = RH*Psat exact 12// K4 humidity ratio W at 25C/60%RH and 20C/50%RH (ASHRAE reference states) 13// K5 moist-air enthalpy h at the same two states (ASHRAE ~55.5 / ~38.5 kJ/kg) 14// INV monotonicity invariants: Psat up in T, W up in RH, h up in T and in W 15// RT dewpoint round-trip: Psat(Td(Pw)) ~= Pw (inverse-consistency liar-kill) 16// LK1 infeasible input is LOUD: Pw>=P -> INFEASIBLE, never a fabricated W 17// LK2 wrong-formula liar-kill: the latent term provably contributes to h 18// 19// expect_exit: 0 20// license_tier: ORIGINAL 21 22import "nx_psychrometrics.nx" 23 24func nx_psy_abs(x: i64) -> i64 { 25 if x < 0 { return 0 - x } 26 return x 27} 28 29func main() -> i64 { 30 // ===== K1: saturation-pressure LUT nodes == NIST reference ====== 31 if nx_psy_psat_node(0) != 611 { return 1 } // 0 degC 32 if nx_psy_psat_node(4) != 2339 { return 2 } // 20 degC 33 if nx_psy_psat_node(5) != 3170 { return 3 } // 25 degC 34 if nx_psy_psat_node(6) != 4246 { return 4 } // 30 degC 35 if nx_psy_psat_node(12) != 19946 { return 5 } // 60 degC 36 37 // exact-node interpolation returns the node value 38 if nx_psy_psat_pa(20000) != 2339 { return 6 } 39 if nx_psy_psat_pa(25000) != 3170 { return 7 } 40 41 // ===== K2: interpolated Psat within band of independent NIST 22C = 42 // NIST Psat(22 degC) ~= 2645 Pa; our linear interp gives 2671 Pa. 43 let ps22: i64 = nx_psy_psat_pa(22000) 44 if ps22 != 2671 { return 8 } // deterministic impl value 45 if nx_psy_abs(ps22 - 2645) > 80 { return 9 } // external-comp band 46 47 // ===== K3: Pw = RH * Psat exact ================================= 48 if nx_psy_pw_pa(25000, 600) != 1902 { return 10 } // 25C, 60% RH 49 if nx_psy_pw_pa(20000, 500) != 1169 { return 11 } // 20C, 50% RH 50 51 // ===== K4: humidity ratio W (mg/kg dry air) ===================== 52 // ASHRAE: 25C/60%RH -> ~0.0119 kg/kg; 20C/50%RH -> ~0.00726 kg/kg. 53 let w25: i64 = nx_psy_humratio_mg(1902, NX_PSY_P_ATM_PA) 54 if w25 != 11898 { return 12 } 55 if nx_psy_abs(w25 - 11900) > 200 { return 13 } // ASHRAE band 56 let w20: i64 = nx_psy_humratio_mg(1169, NX_PSY_P_ATM_PA) 57 if w20 != 7259 { return 14 } 58 if nx_psy_abs(w20 - 7260) > 200 { return 15 } 59 60 // ===== K5: moist-air enthalpy h (J/kg dry air) ================== 61 // ASHRAE reference: 25C/60%RH -> ~55.5 kJ/kg; 20C/50%RH -> ~38.5 kJ/kg. 62 let h25: i64 = nx_psy_enthalpy_from_rh(25000, 600, NX_PSY_P_ATM_PA) 63 if h25 != 55460 { return 16 } 64 if nx_psy_abs(h25 - 55500) > 800 { return 17 } // ASHRAE band 65 let h20: i64 = nx_psy_enthalpy_from_rh(20000, 500, NX_PSY_P_ATM_PA) 66 if h20 != 38544 { return 18 } 67 if nx_psy_abs(h20 - 38500) > 800 { return 19 } 68 69 // ===== INV: physical monotonicity invariants ==================== 70 // Psat strictly increases with temperature. 71 if nx_psy_psat_pa(10000) >= nx_psy_psat_pa(20000) { return 20 } 72 if nx_psy_psat_pa(20000) >= nx_psy_psat_pa(30000) { return 21 } 73 // W increases with RH at fixed T. 74 let wlo: i64 = nx_psy_humratio_mg(nx_psy_pw_pa(25000, 300), NX_PSY_P_ATM_PA) 75 if wlo >= w25 { return 22 } 76 // h increases with T at fixed RH. 77 let h26: i64 = nx_psy_enthalpy_from_rh(26000, 600, NX_PSY_P_ATM_PA) 78 if h26 <= h25 { return 23 } 79 // h increases with RH (humidity) at fixed T. 80 let h25hi: i64 = nx_psy_enthalpy_from_rh(25000, 700, NX_PSY_P_ATM_PA) 81 if h25hi <= h25 { return 24 } 82 83 // ===== RT: dewpoint round-trip inversion (liar-kill) ============ 84 // Td(Pw)=16548 mC (~16.5C, ASHRAE ~16.7C for 25C/60%RH). 85 let td: i64 = nx_psy_dewpoint_mC(1902) 86 if td != 16548 { return 25 } 87 if nx_psy_abs(td - 16700) > 400 { return 26 } // ASHRAE band 88 // Psat at the dewpoint must recover the original Pw (inverse-consistency). 89 let pw_back: i64 = nx_psy_psat_pa(td) 90 if nx_psy_abs(pw_back - 1902) > 20 { return 27 } 91 92 // ===== LK1: infeasible input is LOUD ============================ 93 // Pw >= P is physically impossible -> INFEASIBLE, never a fabricated W. 94 if nx_psy_humratio_mg(NX_PSY_P_ATM_PA, NX_PSY_P_ATM_PA) != NX_PSY_INFEASIBLE { return 30 } 95 if nx_psy_humratio_mg(200000, NX_PSY_P_ATM_PA) != NX_PSY_INFEASIBLE { return 31 } 96 97 // ===== LK2: wrong-formula liar-kill ============================= 98 // The latent term must actually contribute. h(dry-air-only) omits it; 99 // the difference must equal the latent enthalpy of the moisture. 100 let h_moist: i64 = nx_psy_enthalpy_j(25000, 11898) 101 let h_dry: i64 = nx_psy_enthalpy_j(25000, 0) 102 if h_moist != 55460 { return 32 } 103 if h_dry != 25150 { return 33 } 104 if (h_moist - h_dry) != 30310 { return 34 } // latent is real, not dropped 105 106 return 0 107}