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}