code wiki / (root) / nx_icecream_balance_test.nx

nx_icecream_balance_test.nx source

↩ module page · 192 lines · 11360 B

1// nx_icecream_balance_test.nx -- gate for nx_icecream_balance. The core 2// tests are ROUND TRIPS: solve for a mix, hand the answer to nx_icecream, 3// and demand the analyser reports back the targets that were asked for. 4// Solver and analyser are independent code paths, so agreement is real 5// evidence rather than a restatement. 6// expect_exit: 0 license_tier: ORIGINAL 7import "nx_syscalls.nx" 8import "nx_icecream.nx" 9import "nx_icecream_balance.nx" 10 11func 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 } 12func 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 } 13 14func main() -> i64 { 15 var pass: i64 = 0 16 var total: i64 = 0 17 18 // --- T1 solve a standard 12/10/15 mix --- 19 total = total + 1 20 let s: *NxMixSolution = icb_solve_dairy(1000, 120, 100, 150, 5) 21 t_puts("T1 solve 12% fat /10% MSNF /15% sugar: cream=" as *u8); t_putn(s.cream_g); t_puts(" SMP=" as *u8); t_putn(s.smp_g); t_puts(" sugar=" as *u8); t_putn(s.sugar_g); t_puts(" stab=" as *u8); t_putn(s.stab_g); t_puts(" added-water=" as *u8); t_putn(s.added_water_g); t_puts(" feasible=" as *u8); t_putn(s.feasible); t_puts(": " as *u8) 22 var ok1: i64 = 1 23 if s.feasible != 1 { ok1 = 0 } 24 if s.cream_g != 300 { ok1 = 0 } 25 if s.sugar_g != 150 { ok1 = 0 } 26 let sum1: i64 = s.cream_g + s.smp_g + s.sugar_g + s.stab_g + s.added_water_g 27 if sum1 != 1000 { ok1 = 0 } 28 if ok1 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } 29 30 // --- T2 ROUND TRIP: the solved mix analyses back to the targets --- 31 total = total + 1 32 let m: *NxIceMix = icb_solution_to_mix(s, 1000, IC_MW_SUCROSE_Q2, 100, 1100) 33 let f: i64 = ic_fat_permil(m) 34 let ms: i64 = ic_msnf_permil(m) 35 let su: i64 = ic_sugar_permil(m) 36 t_puts("T2 analyser reports fat=" as *u8); t_putn(f); t_puts(" MSNF=" as *u8); t_putn(ms); t_puts(" sugar=" as *u8); t_putn(su); t_puts(" permil, targets 120/100/150 tol 2: " as *u8) 37 var ok2: i64 = 1 38 var df: i64 = f - 120 39 if df < 0 { df = 0 - df } 40 var dm: i64 = ms - 100 41 if dm < 0 { dm = 0 - dm } 42 var ds: i64 = su - 150 43 if ds < 0 { ds = 0 - ds } 44 if df > 2 { ok2 = 0 } 45 if dm > 2 { ok2 = 0 } 46 if ds > 2 { ok2 = 0 } 47 if ok2 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } 48 49 // --- T3 ADDED water is not TOTAL water (cream and powder carry their own) --- 50 total = total + 1 51 let total_water: i64 = ic_water_g(m) 52 t_puts("T3 added water=" as *u8); t_putn(s.added_water_g); t_puts(" but mix total water=" as *u8); t_putn(total_water); t_puts(" (must differ, cream/SMP carry water): " as *u8) 53 if total_water > s.added_water_g { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } 54 55 // --- T4 the solved mix is legally ice cream and not sandy --- 56 total = total + 1 57 let fda: i64 = ic_fda_is_ice_cream(m, 100) 58 let sandy: i64 = ic_sandy_risk(m) 59 t_puts("T4 solved mix FDA ice cream=" as *u8); t_putn(fda); t_puts(" sandy risk=" as *u8); t_putn(sandy); t_puts(" want 1/0: " as *u8) 60 if fda == 1 { if sandy == 0 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } } else { t_puts("FAIL\n" as *u8) } 61 62 // --- T5 REFUSAL: cream alone overshoots MSNF -> no solution --- 63 // 30% fat needs 750 g cream, which drags in 40 g MSNF; ask for 20 g and 64 // the target pair is unreachable. 65 total = total + 1 66 let bad1: *NxMixSolution = icb_solve_dairy(1000, 300, 20, 150, 5) 67 let bad2: *NxMixSolution = icb_solve_dairy(1000, 400, 100, 400, 5) 68 let bad3: *NxMixSolution = icb_solve_dairy(0, 120, 100, 150, 5) 69 t_puts("T5 MSNF-undershoot=" as *u8); t_putn(bad1.feasible); t_puts(" no-water-left=" as *u8); t_putn(bad2.feasible); t_puts(" zero-batch=" as *u8); t_putn(bad3.feasible); t_puts(" (all 0): " as *u8) 70 var ok5: i64 = 1 71 if bad1.feasible != 0 { ok5 = 0 } 72 if bad2.feasible != 0 { ok5 = 0 } 73 if bad3.feasible != 0 { ok5 = 0 } 74 if ok5 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } 75 76 // --- T6 sugar blend hits a PAC target, verified by the analyser --- 77 total = total + 1 78 let g_suc: i64 = icb_solve_sugar_blend(150, 1000, 200, IC_MW_SUCROSE_Q2, IC_MW_DEXTROSE_Q2) 79 let g_dex: i64 = 150 - g_suc 80 let pac_s: i64 = icb_pac_index_of(g_suc, 1000, IC_MW_SUCROSE_Q2) 81 let pac_d: i64 = icb_pac_index_of(g_dex, 1000, IC_MW_DEXTROSE_Q2) 82 let pac_tot: i64 = pac_s + pac_d 83 t_puts("T6 for PAC 200: sucrose=" as *u8); t_putn(g_suc); t_puts("g dextrose=" as *u8); t_putn(g_dex); t_puts("g -> achieved PAC=" as *u8); t_putn(pac_tot); t_puts(" tol 3: " as *u8) 84 var d6: i64 = pac_tot - 200 85 if d6 < 0 { d6 = 0 - d6 } 86 if d6 <= 3 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } 87 88 // --- T7 blend endpoints: all-sucrose and all-dextrose --- 89 total = total + 1 90 let all_suc: i64 = icb_solve_sugar_blend(150, 1000, 150, IC_MW_SUCROSE_Q2, IC_MW_DEXTROSE_Q2) 91 let all_dex: i64 = icb_solve_sugar_blend(150, 1000, 285, IC_MW_SUCROSE_Q2, IC_MW_DEXTROSE_Q2) 92 t_puts("T7 PAC150 -> sucrose=" as *u8); t_putn(all_suc); t_puts(" (all 150) PAC285 -> sucrose=" as *u8); t_putn(all_dex); t_puts(" (all 0): " as *u8) 93 var ok7: i64 = 1 94 if all_suc != 150 { ok7 = 0 } 95 if all_dex != 0 { ok7 = 0 } 96 if ok7 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } 97 98 // --- T8 REFUSAL: a PAC target the pair cannot reach --- 99 total = total + 1 100 let un_lo: i64 = icb_solve_sugar_blend(150, 1000, 100, IC_MW_SUCROSE_Q2, IC_MW_DEXTROSE_Q2) 101 let un_hi: i64 = icb_solve_sugar_blend(150, 1000, 400, IC_MW_SUCROSE_Q2, IC_MW_DEXTROSE_Q2) 102 let un_zero: i64 = icb_solve_sugar_blend(0, 1000, 200, IC_MW_SUCROSE_Q2, IC_MW_DEXTROSE_Q2) 103 t_puts("T8 below-reach=" as *u8); t_putn(un_lo); t_puts(" above-reach=" as *u8); t_putn(un_hi); t_puts(" no-sugar=" as *u8); t_putn(un_zero); t_puts(" (all -1, not clamped): " as *u8) 104 var ok8: i64 = 1 105 if un_lo != ICB_INFEASIBLE { ok8 = 0 } 106 if un_hi != ICB_INFEASIBLE { ok8 = 0 } 107 if un_zero != ICB_INFEASIBLE { ok8 = 0 } 108 if ok8 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } 109 110 // --- T9 THE DEXTROSE LEVER, QUANTIFIED: same target, half the sugar --- 111 total = total + 1 112 let need_suc: i64 = icb_sugar_for_serve_temp(100, 600, IC_MW_SUCROSE_Q2, 0 - 12000, 720) 113 let need_dex: i64 = icb_sugar_for_serve_temp(100, 600, IC_MW_DEXTROSE_Q2, 0 - 12000, 720) 114 t_puts("T9 to scoop at -12 C with 72% ice: sucrose=" as *u8); t_putn(need_suc); t_puts("g vs dextrose=" as *u8); t_putn(need_dex); t_puts("g per kg: " as *u8) 115 var ok9: i64 = 1 116 if need_suc < 230 { ok9 = 0 } 117 if need_suc > 265 { ok9 = 0 } 118 if need_dex < 120 { ok9 = 0 } 119 if need_dex > 140 { ok9 = 0 } 120 if need_dex >= need_suc { ok9 = 0 } 121 if ok9 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } 122 123 // --- T10 ROUND TRIP through the freezing curve: solve -> analyse --- 124 total = total + 1 125 let rt: *NxIceMix = nx_ice_mix_new() 126 rt.total_g = 1000 127 rt.sugar_mw_q2 = IC_MW_DEXTROSE_Q2 128 rt.sugar_pod = 70 129 rt.msnf_g = 100 130 rt.sugar_g = need_dex 131 let rest: i64 = 1000 - 600 - 100 - need_dex 132 rt.fat_g = rest 133 rt.other_solids_g = 0 134 let got_temp: i64 = ic_serve_temp_for_frozen_permil(rt, 720) 135 t_puts("T10 mix built from the solver serves 72% ice at " as *u8); t_putn(got_temp); t_puts(" milli-C, asked -12000 tol 200: " as *u8) 136 var d10: i64 = got_temp + 12000 137 if d10 < 0 { d10 = 0 - d10 } 138 if d10 <= 200 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } 139 140 // --- T11 colder target needs MORE sweetener (monotone, sane direction) --- 141 total = total + 1 142 let n8: i64 = icb_sugar_for_serve_temp(100, 600, IC_MW_SUCROSE_Q2, 0 - 8000, 720) 143 let n12: i64 = icb_sugar_for_serve_temp(100, 600, IC_MW_SUCROSE_Q2, 0 - 12000, 720) 144 let n16: i64 = icb_sugar_for_serve_temp(100, 600, IC_MW_SUCROSE_Q2, 0 - 16000, 720) 145 t_puts("T11 sucrose for -8/-12/-16 C = " as *u8); t_putn(n8); t_puts("/" as *u8); t_putn(n12); t_puts("/" as *u8); t_putn(n16); t_puts(" (increasing): " as *u8) 146 var ok11: i64 = 1 147 if n12 <= n8 { ok11 = 0 } 148 if n16 <= n12 { ok11 = 0 } 149 if ok11 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } 150 151 // --- T12 REFUSAL: milk solids alone already overshoot the target --- 152 // A warm target with heavy MSNF needs no sugar at all -- and saying 153 // "0 g" would be a lie, because the mix is already past the target. 154 total = total + 1 155 let over: i64 = icb_sugar_for_serve_temp(200, 300, IC_MW_SUCROSE_Q2, 0 - 3000, 720) 156 let nowater: i64 = icb_sugar_for_serve_temp(100, 0, IC_MW_SUCROSE_Q2, 0 - 12000, 720) 157 let badfrac: i64 = icb_sugar_for_serve_temp(100, 600, IC_MW_SUCROSE_Q2, 0 - 12000, 1000) 158 t_puts("T12 MSNF-overshoot=" as *u8); t_putn(over); t_puts(" no-water=" as *u8); t_putn(nowater); t_puts(" 100%-ice-target=" as *u8); t_putn(badfrac); t_puts(" (all -1): " as *u8) 159 var ok12: i64 = 1 160 if over != ICB_INFEASIBLE { ok12 = 0 } 161 if nowater != ICB_INFEASIBLE { ok12 = 0 } 162 if badfrac != ICB_INFEASIBLE { ok12 = 0 } 163 if ok12 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } 164 165 // --- T13 a gelato-style solve also round-trips --- 166 total = total + 1 167 let g: *NxMixSolution = icb_solve_dairy(1000, 70, 100, 180, 4) 168 let gm: *NxIceMix = icb_solution_to_mix(g, 1000, IC_MW_SUCROSE_Q2, 100, 1100) 169 let gf: i64 = ic_fat_permil(gm) 170 let gc: i64 = ic_classify(gm, 30) 171 t_puts("T13 gelato solve fat=" as *u8); t_putn(gf); t_puts(" permil (target 70) class@30% overrun=" as *u8); t_putn(gc); t_puts(" want 2=GELATO: " as *u8) 172 var dg: i64 = gf - 70 173 if dg < 0 { dg = 0 - dg } 174 var ok13: i64 = 1 175 if g.feasible != 1 { ok13 = 0 } 176 if dg > 2 { ok13 = 0 } 177 if gc != IC_STYLE_GELATO { ok13 = 0 } 178 if ok13 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } 179 180 // --- T14 an infeasible solution yields a mix with no solids, and the 181 // analyser refuses it rather than reporting a plausible one --- 182 total = total + 1 183 let im: *NxIceMix = icb_solution_to_mix(bad1, 1000, IC_MW_SUCROSE_Q2, 100, 1100) 184 let isolids: i64 = ic_total_solids_g(im) 185 let ifda: i64 = ic_fda_is_ice_cream(im, 100) 186 t_puts("T14 infeasible->mix solids=" as *u8); t_putn(isolids); t_puts(" FDA ice cream=" as *u8); t_putn(ifda); t_puts(" want 0/0: " as *u8) 187 if isolids == 0 { if ifda == 0 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } } else { t_puts("FAIL\n" as *u8) } 188 189 t_puts("ICECREAM-BALANCE-GATE passed " as *u8); t_putn(pass); t_puts("/" as *u8); t_putn(total) 190 if pass == total { t_puts(" verdict=GREEN\n" as *u8); sys_exit(0); return 0 } 191 t_puts(" verdict=RED\n" as *u8); sys_exit(1); return 1 192}