code wiki / (root) / nx_water_activity_test.nx

nx_water_activity_test.nx source

↩ module page · 201 lines · 10919 B

1// nx_water_activity_test.nx -- gate for nx_water_activity. The load-bearing 2// tests are T2 (a real mix lands on the published aw ~0.97 for ice cream mix) 3// and T8/T9, where the hurdle model must NAME the barrier and must refuse to 4// call an ice cream mix safe on water activity -- the reassuring wrong answer. 5// expect_exit: 0 license_tier: ORIGINAL 6import "nx_syscalls.nx" 7import "nx_icecream.nx" 8import "nx_chem_solution.nx" 9import "nx_thermal_process.nx" 10import "nx_water_activity.nx" 11 12func t_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 13func t_putn(v: i64) -> i64 { let bb: *u8 = sys_mmap(28); var m: i64 = v; if m < 0 { m = 0 - m; sys_write(1, "-" as *u8, 1) } let t: *u8 = sys_mmap(28); var k: i64 = 0; if m == 0 { t[0] = 48 as u8; k = 1 } while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } var i: i64 = 0; while i < k { bb[i] = t[k - 1 - i]; i = i + 1 } sys_write(1, bb, k); return 0 } 14 15func ref_mix() -> *NxIceMix { 16 let m: *NxIceMix = nx_ice_mix_new() 17 m.fat_g = 120 18 m.msnf_g = 100 19 m.sugar_g = 150 20 m.sugar_mw_q2 = IC_MW_SUCROSE_Q2 21 m.sugar_pod = 100 22 m.other_solids_g = 30 23 m.total_g = 1000 24 m.density_q3 = 1100 25 return m 26} 27 28func main() -> i64 { 29 var pass: i64 = 0 30 var total: i64 = 0 31 32 // --- T1 pure water is aw = 1.0000 exactly --- 33 total = total + 1 34 let pure: i64 = wa_from_solutes_q4(1000, 0) 35 t_puts("T1 pure water aw x10000 = " as *u8); t_putn(pure); t_puts(" want 10000: " as *u8) 36 if pure == 10000 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } 37 38 // --- T2 a real ice cream mix lands on the published ~0.97 --- 39 total = total + 1 40 let m: *NxIceMix = ref_mix() 41 let aw: i64 = wa_from_mix_q4(m) 42 t_puts("T2 ice cream mix aw = " as *u8); t_putn(aw); t_puts(" x10000, published ~0.97 (band 9650..9850): " as *u8) 43 var ok2: i64 = 1 44 if aw < 9650 { ok2 = 0 } 45 if aw > 9850 { ok2 = 0 } 46 if ok2 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } 47 48 // --- T3 aw and freezing point come from the SAME moles: more solute 49 // must both lower aw and deepen the depression --- 50 total = total + 1 51 let sweet: *NxIceMix = ref_mix() 52 sweet.sugar_g = 250 53 sweet.other_solids_g = 0 54 let aw_s: i64 = wa_from_mix_q4(sweet) 55 let fpd_ref: i64 = ic_fpd_milli_c(m) 56 let fpd_s: i64 = ic_fpd_milli_c(sweet) 57 t_puts("T3 more sugar: aw " as *u8); t_putn(aw); t_puts("->" as *u8); t_putn(aw_s); t_puts(" FPD " as *u8); t_putn(fpd_ref); t_puts("->" as *u8); t_putn(fpd_s); t_puts(" (aw down, FPD up): " as *u8) 58 var ok3: i64 = 1 59 if aw_s >= aw { ok3 = 0 } 60 if fpd_s <= fpd_ref { ok3 = 0 } 61 if ok3 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } 62 63 // --- T4 growth thresholds are ordered as the literature has them --- 64 total = total + 1 65 let sal: i64 = wa_min_growth_q4(WA_ORG_SALMONELLA) 66 let cbot: i64 = wa_min_growth_q4(WA_ORG_CBOT) 67 let staph: i64 = wa_min_growth_q4(WA_ORG_STAPH) 68 let mold: i64 = wa_min_growth_q4(WA_ORG_MOLD) 69 let xero: i64 = wa_min_growth_q4(WA_ORG_XEROPHILE) 70 t_puts("T4 min aw salmonella/cbot/staph/mold/xerophile = " as *u8); t_putn(sal); t_puts("/" as *u8); t_putn(cbot); t_puts("/" as *u8); t_putn(staph); t_puts("/" as *u8); t_putn(mold); t_puts("/" as *u8); t_putn(xero); t_puts(" (descending): " as *u8) 71 var ok4: i64 = 1 72 if cbot >= sal { ok4 = 0 } 73 if staph >= cbot { ok4 = 0 } 74 if mold >= staph { ok4 = 0 } 75 if xero >= mold { ok4 = 0 } 76 if ok4 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } 77 78 // --- T5 THE UNCOMFORTABLE TRUTH: the mix is NOT protected by water --- 79 total = total + 1 80 let grows_bot: i64 = wa_supports_growth(aw, WA_ORG_CBOT) 81 let grows_sal: i64 = wa_supports_growth(aw, WA_ORG_SALMONELLA) 82 let nsupp: i64 = wa_organisms_supported(aw) 83 let phf: i64 = wa_is_potentially_hazardous(aw) 84 t_puts("T5 mix aw supports C.bot=" as *u8); t_putn(grows_bot); t_puts(" salmonella=" as *u8); t_putn(grows_sal); t_puts(" organisms=" as *u8); t_putn(nsupp); t_puts("/6 PHF=" as *u8); t_putn(phf); t_puts(" want 1/1/6/1: " as *u8) 85 var ok5: i64 = 1 86 if grows_bot != 1 { ok5 = 0 } 87 if grows_sal != 1 { ok5 = 0 } 88 if nsupp != WA_ORG_N { ok5 = 0 } 89 if phf != 1 { ok5 = 0 } 90 if ok5 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } 91 92 // --- T6 a dry product (honey-like, aw 0.60) stops everything --- 93 total = total + 1 94 let dry: i64 = 6000 95 let d_bot: i64 = wa_supports_growth(dry, WA_ORG_CBOT) 96 let d_mold: i64 = wa_supports_growth(dry, WA_ORG_MOLD) 97 let d_xero: i64 = wa_supports_growth(dry, WA_ORG_XEROPHILE) 98 let d_n: i64 = wa_organisms_supported(dry) 99 t_puts("T6 aw 0.60: cbot=" as *u8); t_putn(d_bot); t_puts(" mold=" as *u8); t_putn(d_mold); t_puts(" xerophile=" as *u8); t_putn(d_xero); t_puts(" total=" as *u8); t_putn(d_n); t_puts(" want 0/0/0/0: " as *u8) 100 if d_n == 0 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } 101 102 // --- T7 FAIL-CLOSED: no water, bad solute, unknown organism --- 103 // An unknown hazard must read as ABLE to grow, never as absent. 104 total = total + 1 105 let nowater: i64 = wa_from_solutes_q4(0, 1000) 106 let negsol: i64 = wa_from_solutes_q4(100, 0 - 5) 107 let unk: i64 = wa_supports_growth(9000, 99) 108 let inv_grow: i64 = wa_supports_growth(WA_INVALID, WA_ORG_MOLD) 109 let inv_phf: i64 = wa_is_potentially_hazardous(WA_INVALID) 110 t_puts("T7 no-water=" as *u8); t_putn(nowater); t_puts(" neg-solute=" as *u8); t_putn(negsol); t_puts(" unknown-organism-grows=" as *u8); t_putn(unk); t_puts(" invalid-aw-grows=" as *u8); t_putn(inv_grow); t_puts(" invalid-PHF=" as *u8); t_putn(inv_phf); t_puts(": " as *u8) 111 var ok7: i64 = 1 112 if nowater != WA_INVALID { ok7 = 0 } 113 if negsol != WA_INVALID { ok7 = 0 } 114 if unk != 1 { ok7 = 0 } 115 if inv_grow != 1 { ok7 = 0 } 116 if inv_phf != 1 { ok7 = 0 } 117 if ok7 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } 118 119 // --- T8 THE HURDLE MODEL NAMES THE BARRIER --- 120 total = total + 1 121 let v_dry: i64 = wa_hurdle_verdict(6600, 6000, 0, 0, 1) 122 let v_acid: i64 = wa_hurdle_verdict(3500, 9800, 0, 1, 1) 123 let v_retort: i64 = wa_hurdle_verdict(6600, 9800, TP_F0_STANDARD_MS, 0, 1) 124 let v_cold: i64 = wa_hurdle_verdict(6600, 9800, 0, 1, 0) 125 t_puts("T8 verdicts dry/acid/retort/chilled = " as *u8); t_putn(v_dry); t_puts("/" as *u8); t_putn(v_acid); t_puts("/" as *u8); t_putn(v_retort); t_puts("/" as *u8); t_putn(v_cold); t_puts(" want 1/2/3/4: " as *u8) 126 var ok8: i64 = 1 127 if v_dry != WA_SAFE_BY_AW { ok8 = 0 } 128 if v_acid != WA_SAFE_BY_ACID { ok8 = 0 } 129 if v_retort != WA_SAFE_BY_THERMAL { ok8 = 0 } 130 if v_cold != WA_SAFE_BY_COLD_CHAIN { ok8 = 0 } 131 if ok8 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } 132 133 // --- T9 ICE CREAM MIX end to end: carried by the COLD CHAIN, and the 134 // model refuses every way of pretending otherwise --- 135 total = total + 1 136 let real_v: i64 = wa_hurdle_verdict(6600, aw, 0, 1, 0) 137 let no_past: i64 = wa_hurdle_verdict(6600, aw, 0, 0, 0) 138 let ambient: i64 = wa_hurdle_verdict(6600, aw, 0, 1, 1) 139 t_puts("T9 mix chilled+pasteurised=" as *u8); t_putn(real_v); t_puts(" unpasteurised=" as *u8); t_putn(no_past); t_puts(" left ambient=" as *u8); t_putn(ambient); t_puts(" want 4/0/0: " as *u8) 140 var ok9: i64 = 1 141 if real_v != WA_SAFE_BY_COLD_CHAIN { ok9 = 0 } 142 if no_past != WA_UNSAFE { ok9 = 0 } 143 if ambient != WA_UNSAFE { ok9 = 0 } 144 if ok9 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } 145 146 // --- T10 the aw hurdle OVERRIDES a low pH and no heat at all --- 147 // Milk powder: low-acid, never retorted, ambient -- safe purely on water. 148 total = total + 1 149 let powder: i64 = wa_hurdle_verdict(6600, 2000, 0, 0, 1) 150 let safe_p: i64 = wa_is_safe(6600, 2000, 0, 0, 1) 151 t_puts("T10 milk powder aw 0.20 low-acid ambient unheated: verdict=" as *u8); t_putn(powder); t_puts(" is_safe=" as *u8); t_putn(safe_p); t_puts(" want 1/1: " as *u8) 152 if powder == WA_SAFE_BY_AW { if safe_p == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } } else { t_puts("FAIL\n" as *u8) } 153 154 // --- T11 the PHF line is exactly 0.85, checked either side --- 155 total = total + 1 156 let under: i64 = wa_is_potentially_hazardous(8499) 157 let at: i64 = wa_is_potentially_hazardous(8500) 158 let vu: i64 = wa_hurdle_verdict(6600, 8499, 0, 0, 1) 159 let va: i64 = wa_hurdle_verdict(6600, 8500, 0, 0, 1) 160 t_puts("T11 aw 0.8499 PHF=" as *u8); t_putn(under); t_puts(" verdict=" as *u8); t_putn(vu); t_puts(" ; aw 0.8500 PHF=" as *u8); t_putn(at); t_puts(" verdict=" as *u8); t_putn(va); t_puts(" want 0/1 then 1/0: " as *u8) 161 var ok11: i64 = 1 162 if under != 0 { ok11 = 0 } 163 if at != 1 { ok11 = 0 } 164 if vu != WA_SAFE_BY_AW { ok11 = 0 } 165 if va != WA_UNSAFE { ok11 = 0 } 166 if ok11 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } 167 168 // --- T12 inversion: solute needed for a target aw, and the honest 169 // answer when the target is unreachable by adding solute --- 170 total = total + 1 171 let need: i64 = wa_solute_umol_for_target(600, 8500) 172 let rt: i64 = wa_from_solutes_q4(600, need) 173 let bad_hi: i64 = wa_solute_umol_for_target(600, 10000) 174 let bad_lo: i64 = wa_solute_umol_for_target(600, 0) 175 t_puts("T12 solute for aw 0.85 in 600g water = " as *u8); t_putn(need); t_puts(" umol, round-trips to aw=" as *u8); t_putn(rt); t_puts(" ; aw=1.0 -> " as *u8); t_putn(bad_hi); t_puts(" aw=0 -> " as *u8); t_putn(bad_lo); t_puts(": " as *u8) 176 var d12: i64 = rt - 8500 177 if d12 < 0 { d12 = 0 - d12 } 178 var ok12: i64 = 1 179 if d12 > 5 { ok12 = 0 } 180 if bad_hi != WA_INVALID { ok12 = 0 } 181 if bad_lo != WA_INVALID { ok12 = 0 } 182 if ok12 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } 183 184 // --- T13 aw is monotone decreasing in solute (no inversions) --- 185 total = total + 1 186 var s13: i64 = 0 187 var prev13: i64 = 10001 188 var ok13: i64 = 1 189 while s13 <= 20000000 { 190 let a: i64 = wa_from_solutes_q4(600, s13) 191 if a > prev13 { ok13 = 0 } 192 prev13 = a 193 s13 = s13 + 1000000 194 } 195 t_puts("T13 aw monotone non-increasing in solute (0..20 mol): " as *u8) 196 if ok13 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } 197 198 t_puts("WATER-ACTIVITY-GATE passed " as *u8); t_putn(pass); t_puts("/" as *u8); t_putn(total) 199 if pass == total { t_puts(" verdict=GREEN\n" as *u8); sys_exit(0); return 0 } 200 t_puts(" verdict=RED\n" as *u8); sys_exit(1); return 1 201}