code wiki / (root) / nx_icecream_balance.nx

nx_icecream_balance.nx

buildroot/runtime/nx_icecream_balance.nx

8926 B198 linesdepth 3pulls 3 transitivereach 1 importersview sourcekind librarytopic icecream
docsdependenciesstructsconstsfunctions

about

nx_icecream_balance.nx -- FOOD-SCIENCE SUITE / MIX BALANCING rung. nx_icecream ANALYSES a mix that already exists. This SOLVES for the mix: given targets, it returns the grams of each ingredient to weigh out. That inversion is the formulator's actual job, and it is the difference between a calculator and a design tool. THREE SOLVERS, EACH EXACT -- no iteration, no search, no float. 1. THE DAIRY BALANCE is a TRIANGULAR system, so it solves in one pass. Fat comes only from cream, so cream is determined first; cream drags MSNF in with it, so the powder only makes up the shortfall; sugar and stabiliser are direct; added water is whatever is left. Solving it as a general matrix would be wrong-headed -- the dependency order IS the dairy chemistry. 2. THE SUGAR BLEND is a 2x2 with a closed form. Two sugars, two constraints (total mass, target PAC), so g_lo = (PAC_hi * S - P) / (PAC_hi - PAC_lo) and the PAC figures come from ic_pac_from_mw, not from constants, so the solver works for ANY sugar pair rather than the one it was written against. 3. THE SERVE-TEMPERATURE INVERSION runs the freezing curve backwards: pick the temperature you want to scoop at and the ice fraction you want there, and it returns the grams of sweetener that produce it. This is where the dextrose lever becomes a number instead of an opinion -- scooping at -12 C takes ~248 g of sucrose or ~130 g of dextrose per kg, because PAC is inverse in molar mass. EVERY SOLVER REFUSES RATHER THAN RETURNS A BAD MIX. Cream that already overshoots the MSNF target, a PAC target outside what the two sugars can reach, negative added water -- all set feasible = 0. A formulation that cannot exist must not come back as numbers a person would then weigh out. ROUNDING IS TO NEAREST, NOT TRUNCATED. Truncation cost a whole PAC unit in nx_icecream (dextrose 190.009 read as 189); here it would silently miss the MSNF target by a gram per stage. Grounding (cited; researcher-groundable): goff_hartel_ice_cream_mix_calculation_serum_point

dependencies 2 imports · 1 importers

nx_syscalls.nx nx_icecream.nx nx_icecream_balance.nx nx_icecream_balance_test.nx

imports: nx_syscalls.nxnx_icecream.nx

imported by: nx_icecream_balance_test.nx

structs

66struct NxMixSolution

consts

51const ICB_CREAM_FAT_PERMIL: i64 = 400 // 40% heavy cream
52const ICB_CREAM_MSNF_PERMIL: i64 = 54 // 5.4% MSNF rides along with it
53const ICB_SMP_MSNF_PERMIL: i64 = 960 // 96% skim milk powder
54const ICB_MILK_FAT_PERMIL: i64 = 35 // 3.5% whole milk
55const ICB_MILK_MSNF_PERMIL: i64 = 87
57const ICB_INFEASIBLE: i64 = 0 - 1

functions

75func nx_mix_solution_new() -> *NxMixSolution
called by 1: icb_solve_dairy calls 1: sys_mmap
87func icb_div_round(num: i64, den: i64) -> i64
95func icb_solve_dairy(batch_g: i64, fat_permil: i64, msnf_permil: i64, sugar_permil: i64, stab_g: i64) -> *NxMixSolution
120func icb_solution_to_mix(s: *NxMixSolution, batch_g: i64, sugar_mw_q2: i64, sugar_pod: i64, density_q3: i64) -> *NxIceMix
called by 1: main calls 2: nx_ice_mix_newicb_div_round
143func icb_pac_index_of(sugar_g: i64, batch_g: i64, mw_q2: i64) -> i64
called by 1: main calls 1: ic_pac_from_mw
149func icb_solve_sugar_blend(total_sugar_g: i64, batch_g: i64, pac_target: i64, mw_lo_q2: i64, mw_hi_q2: i64) -> i64
called by 1: main calls 2: ic_pac_from_mwicb_div_round
174func icb_fpd_needed_milli(serve_temp_milli: i64, target_frozen_permil: i64) -> i64
183func icb_sugar_for_serve_temp(msnf_g: i64, water_g: i64, sugar_mw_q2: i64, serve_temp_milli: i64, target_frozen_permil: i64) -> i64