code wiki / (root) / nx_ice_scale.nx

nx_ice_scale.nx source

↩ module page · 299 lines · 13904 B

1// nx_ice_scale.nx -- PERSONAL TO INDUSTRIAL: what changes when you make more, 2// and the direction that surprises people. 3// 4// ===== THE FINDING THIS ORGAN EXISTS TO CARRY ====================== 5// 6// **SCALING UP IMPROVES ICE CRYSTAL SIZE. The artisan premium story is 7// backwards on this axis, and the mechanism is not subtle.** 8// 9// A batch freezer holds the mix in the barrel for five to fifteen minutes 10// against a barrel wall only a little colder than the draw temperature. A 11// continuous freezer pushes it through in about thirty seconds against a 12// wall at -26 to -28 C. Nucleation rate rises steeply with undercooling and 13// growth time is what lets crystals coarsen, so the industrial machine 14// produces MORE and SMALLER crystals, and the small-batch machine produces 15// fewer and larger ones. Cold extrusion pushes this further, drawing to 16// -15 to -18 C with crystals and air cells two to three times smaller again. 17// 18// So the quality-versus-scale trade that a formulator expects does not exist 19// here. It runs the other way, and a business plan that promises a superior 20// product BECAUSE it is made in small batches is promising the opposite of 21// what the freezer does. 22// 23// ===== WHERE THE SMALL PRODUCER ACTUALLY COMPETES ================== 24// 25// Not at the freezer. At the COLD CHAIN. nx_ice_recrystal established that 26// ripening is driven by temperature FLUCTUATION rather than mean temperature, 27// and nx_ice_stabiliser established that stabilisers act during STORAGE and 28// not during freezing. Both point the same way: what happens after the 29// product leaves the barrel dominates what happened inside it. 30// 31// A producer selling within days, through a freezer that is never opened to 32// a warm room, holds an advantage that no continuous freezer can take back -- 33// and a national brand shipping through open-faced retail cabinets that reach 34// -9 C is carrying damage the plant cannot prevent. That is the real axis, 35// and it is the opposite of the one the marketing uses. 36// 37// ===== WHAT IS DERIVED AND WHAT IS SUPPLIED ======================== 38// 39// **The thermodynamics is DERIVED.** Freezing energy per kilogram follows 40// from the latent heat of fusion, the water fraction, the frozen fraction and 41// two specific heats. Nothing about it is a quoted figure. 42// 43// **The money is SUPPLIED BY THE CALLER and flagged indicative.** This organ 44// holds no equipment prices, no ingredient prices and no labour rates, 45// because it has no verified source for any of them. It computes the SHAPE 46// of the economics from costs the caller provides, and isc_cost_is_indicative 47// reports that provenance so no downstream model can present a computed 48// margin as a quotation. A cost model built on invented prices produces 49// confident nonsense, which is the failure this lane exists to refuse. 50// 51// All INTEGER. _q1 = x10, _q2 = x100, _mc = milli-Celsius, _permil = x1000. 52// 53// Grounding (cited): 54// cook_hartel_2010_ice_cream_freezing_draw_and_hardening 55// goff_guelph_continuous_freezer_residence_and_barrel_wall 56// giudici_2021_foods_10_334_batch_freezer_larger_crystals 57// windhab_wildmoser_cold_extrusion_crystal_and_cell_size 58// latent_heat_fusion_water_334_kj_per_kg 59// 60// genealogy_id: frozen_dessert_science + nishi_food_science_suite 61 62import "nx_syscalls.nx" 63const ISC_MAGIC_1200: i64 = 1200 64const ISC_MAGIC_18000: i64 = 18000 65const ISC_MAGIC_22000: i64 = 22000 66const ISC_MAGIC_27000: i64 = 27000 67const ISC_MAGIC_5000: i64 = 5000 68const ISC_MAGIC_6000: i64 = 6000 69const ISC_MAGIC_5500: i64 = 5500 70const ISC_MAGIC_16000: i64 = 16000 71const ISC_MAGIC_35000: i64 = 35000 72const ISC_MAGIC_40000: i64 = 40000 73const ISC_MAGIC_3600: i64 = 3600 74const ISC_MAGIC_1000000: i64 = 1000000 75 76const ISC_INVALID: i64 = 0 - 1 77const ISC_UNKNOWN: i64 = 0 - 2 78 79// ===== Freezer classes ============================================== 80 81const ISC_BATCH_HOME: i64 = 0 82const ISC_BATCH_COMMERCIAL: i64 = 1 83const ISC_CONTINUOUS: i64 = 2 84const ISC_COLD_EXTRUSION: i64 = 3 85 86// ===== Thermophysical constants ===================================== 87 88// Latent heat of fusion of water, J/kg. 89const ISC_LATENT_FUSION_J: i64 = 334000 90 91// Specific heats, J/(kg*K): mix above its freezing point, product below it. 92const ISC_CP_UNFROZEN_J: i64 = 3300 93const ISC_CP_FROZEN_J: i64 = 2000 94 95const ISC_J_PER_WH: i64 = 3600 96 97// Retail open-faced cabinets reach this, which is the abuse baseline a 98// national cold chain actually operates against. 99const ISC_RETAIL_CABINET_MC: i64 = 0 - 9000 100 101// ===== Freezer behaviour, from the cited process literature ========== 102 103// Residence time in the barrel, seconds. 104func isc_residence_s(class: i64) -> i64 { 105 if class == ISC_BATCH_HOME { return ISC_MAGIC_1200 } 106 if class == ISC_BATCH_COMMERCIAL { return 480 } 107 if class == ISC_CONTINUOUS { return 30 } 108 if class == ISC_COLD_EXTRUSION { return 30 } 109 return ISC_INVALID 110} 111 112// Barrel wall temperature, milli-Celsius. The undercooling that drives 113// nucleation is the gap between this and the draw temperature. 114func isc_barrel_wall_mc(class: i64) -> i64 { 115 if class == ISC_BATCH_HOME { return 0 - ISC_MAGIC_18000 } 116 if class == ISC_BATCH_COMMERCIAL { return 0 - ISC_MAGIC_22000 } 117 if class == ISC_CONTINUOUS { return 0 - ISC_MAGIC_27000 } 118 if class == ISC_COLD_EXTRUSION { return 0 - ISC_MAGIC_27000 } 119 return ISC_INVALID 120} 121 122// Draw temperature out of the barrel, milli-Celsius. 123func isc_draw_mc(class: i64) -> i64 { 124 if class == ISC_BATCH_HOME { return 0 - ISC_MAGIC_5000 } 125 if class == ISC_BATCH_COMMERCIAL { return 0 - ISC_MAGIC_6000 } 126 if class == ISC_CONTINUOUS { return 0 - ISC_MAGIC_5500 } 127 if class == ISC_COLD_EXTRUSION { return 0 - ISC_MAGIC_16000 } 128 return ISC_INVALID 129} 130 131// Undercooling at the wall: how hard the machine is driving nucleation. 132func isc_undercooling_mc(class: i64) -> i64 { 133 let wall: i64 = isc_barrel_wall_mc(class) 134 let draw: i64 = isc_draw_mc(class) 135 if wall == ISC_INVALID { return ISC_INVALID } 136 return draw - wall 137} 138 139// Mean crystal diameter at draw, tenths of a micrometre. 140// 141// !!THE BATCH FIGURE IS DELIBERATELY ABSENT. The source establishes that a 142// batch freezer yields LARGER crystals than a continuous one; it does not 143// give a number. Reporting the ORDERING as measured and the MAGNITUDE as 144// unknown is the honest split -- inventing a batch diameter would let a 145// caller compute a false quality gap to three digits. 146func isc_crystal_at_draw_q1(class: i64) -> i64 { 147 if class == ISC_CONTINUOUS { return 325 } 148 if class == ISC_COLD_EXTRUSION { return 162 } 149 if class == ISC_BATCH_HOME { return ISC_UNKNOWN } 150 if class == ISC_BATCH_COMMERCIAL { return ISC_UNKNOWN } 151 return ISC_INVALID 152} 153 154// The ordering IS measured, and it is the claim that matters. 155func isc_batch_makes_larger_crystals(batch: i64, continuous: i64) -> i64 { 156 if batch == ISC_BATCH_HOME { if continuous == ISC_CONTINUOUS { return 1 } } 157 if batch == ISC_BATCH_HOME { if continuous == ISC_COLD_EXTRUSION { return 1 } } 158 if batch == ISC_BATCH_COMMERCIAL { if continuous == ISC_CONTINUOUS { return 1 } } 159 if batch == ISC_BATCH_COMMERCIAL { if continuous == ISC_COLD_EXTRUSION { return 1 } } 160 return 0 161} 162 163// **The headline, as a function so a planner cannot route around it. 164func isc_scaling_improves_crystal_size() -> i64 { return 1 } 165func isc_small_batch_is_finer() -> i64 { return 0 } 166 167// Hardening: air temperature and the time to reach the >=80% frozen endpoint. 168func isc_hardening_air_mc(class: i64) -> i64 { 169 if class == ISC_BATCH_HOME { return 0 - ISC_MAGIC_18000 } 170 if class == ISC_BATCH_COMMERCIAL { return 0 - ISC_MAGIC_35000 } 171 if class == ISC_CONTINUOUS { return 0 - ISC_MAGIC_40000 } 172 if class == ISC_COLD_EXTRUSION { return 0 - ISC_MAGIC_40000 } 173 return ISC_INVALID 174} 175 176func isc_hardening_minutes(class: i64) -> i64 { 177 if class == ISC_BATCH_HOME { return 720 } 178 if class == ISC_BATCH_COMMERCIAL { return 120 } 179 if class == ISC_CONTINUOUS { return 25 } 180 if class == ISC_COLD_EXTRUSION { return 18 } 181 return ISC_INVALID 182} 183 184// !!A HOME FREEZER IS NOT A HARDENING TUNNEL. At -18 C it takes about half a 185// day to reach the endpoint a blast tunnel reaches in twenty minutes, and 186// every hour spent between the draw temperature and the endpoint is time the 187// crystals spend growing. This is the one place where the home tier is 188// genuinely and unavoidably worse. 189func isc_has_true_hardening(class: i64) -> i64 { 190 if class == ISC_BATCH_HOME { return 0 } 191 if class == ISC_INVALID { return 0 } 192 return 1 193} 194 195// ===== Throughput ==================================================== 196 197func isc_throughput_l_per_h(class: i64, barrel_l: i64) -> i64 { 198 if barrel_l <= 0 { return ISC_INVALID } 199 let res: i64 = isc_residence_s(class) 200 if res == ISC_INVALID { return ISC_INVALID } 201 if res <= 0 { return ISC_INVALID } 202 return barrel_l * ISC_MAGIC_3600 / res 203} 204 205// ===== DERIVED: the energy it takes to freeze a kilogram ============== 206// 207// E = cp_unfrozen * (T_in - T_fp) sensible, above freezing 208// + water * frozen * L latent, the dominant term 209// + cp_frozen * (T_fp - T_final) sensible, below freezing 210// 211// frozen_permil should come from ic_frozen_water_permil so the freezing 212// curve of the ACTUAL mix drives the answer rather than a typical value. 213func isc_freeze_energy_j_per_kg(t_in_mc: i64, t_fp_mc: i64, t_final_mc: i64, water_permil: i64, frozen_permil: i64) -> i64 { 214 if water_permil < 0 { return ISC_INVALID } 215 if water_permil > 1000 { return ISC_INVALID } 216 if frozen_permil < 0 { return ISC_INVALID } 217 if frozen_permil > 1000 { return ISC_INVALID } 218 if t_in_mc <= t_fp_mc { return ISC_INVALID } 219 if t_final_mc >= t_fp_mc { return ISC_INVALID } 220 let sens_above: i64 = ISC_CP_UNFROZEN_J * (t_in_mc - t_fp_mc) / 1000 221 let latent: i64 = water_permil * frozen_permil * ISC_LATENT_FUSION_J / ISC_MAGIC_1000000 222 let sens_below: i64 = ISC_CP_FROZEN_J * (t_fp_mc - t_final_mc) / 1000 223 return sens_above + latent + sens_below 224} 225 226// **THE LATENT TERM DOMINATES, AND THAT IS THE DESIGN FACT. Most of the 227// energy is spent changing phase, not changing temperature, so a plant's 228// refrigeration is sized by how much WATER it freezes -- which is why raising 229// total solids lowers the energy bill as well as the freezing point. 230func isc_latent_share_permil(t_in_mc: i64, t_fp_mc: i64, t_final_mc: i64, water_permil: i64, frozen_permil: i64) -> i64 { 231 let total: i64 = isc_freeze_energy_j_per_kg(t_in_mc, t_fp_mc, t_final_mc, water_permil, frozen_permil) 232 if total == ISC_INVALID { return ISC_INVALID } 233 if total <= 0 { return ISC_INVALID } 234 let latent: i64 = water_permil * frozen_permil * ISC_LATENT_FUSION_J / ISC_MAGIC_1000000 235 return latent * 1000 / total 236} 237 238// Electrical energy, given a coefficient of performance x100. A refrigeration 239// plant lifting heat from -30 C runs a COP near 1.5 to 2.0, so the electrical 240// draw is materially larger than the thermal load. 241func isc_electrical_wh_per_kg(thermal_j: i64, cop_q2: i64) -> i64 { 242 if thermal_j <= 0 { return ISC_INVALID } 243 if cop_q2 <= 0 { return ISC_INVALID } 244 return thermal_j * 100 / (cop_q2 * ISC_J_PER_WH) 245} 246 247// ===== Cost STRUCTURE, on caller-supplied prices ===================== 248// 249// !!EVERY PRICE HERE ARRIVES FROM THE CALLER. This organ has no verified 250// source for ingredient, energy, labour or packaging cost, so it holds none. 251 252func isc_cogs_per_l_cents(ingredient_c: i64, energy_c: i64, labour_c: i64, packaging_c: i64) -> i64 { 253 if ingredient_c < 0 { return ISC_INVALID } 254 if energy_c < 0 { return ISC_INVALID } 255 if labour_c < 0 { return ISC_INVALID } 256 if packaging_c < 0 { return ISC_INVALID } 257 return ingredient_c + energy_c + labour_c + packaging_c 258} 259 260// Energy cost per litre of MIX, from the derived thermal load and a supplied 261// tariff in cents per kilowatt-hour. mix_density_q3 converts per-kg to 262// per-litre, so a denser mix costs more per litre to freeze. 263func isc_energy_cost_per_l_cents_q2(wh_per_kg: i64, mix_density_q3: i64, tariff_c_per_kwh: i64) -> i64 { 264 if wh_per_kg <= 0 { return ISC_INVALID } 265 if mix_density_q3 <= 0 { return ISC_INVALID } 266 if tariff_c_per_kwh < 0 { return ISC_INVALID } 267 let wh_per_l: i64 = wh_per_kg * mix_density_q3 / 1000 268 return wh_per_l * tariff_c_per_kwh / 10 269} 270 271// **LABOUR PER LITRE IS WHERE SCALE ACTUALLY PAYS, and it is arithmetic, not 272// a learning curve: one operator supervising a machine that makes ten times 273// as much per hour costs a tenth as much per litre. Wright's law in 274// nx_market covers the unit COST of a manufactured good over cumulative 275// volume; this is the simpler and larger effect for a process plant. 276func isc_labour_per_l_cents_q2(operator_c_per_h: i64, operators: i64, throughput_l_per_h: i64) -> i64 { 277 if operator_c_per_h < 0 { return ISC_INVALID } 278 if operators <= 0 { return ISC_INVALID } 279 if throughput_l_per_h <= 0 { return ISC_INVALID } 280 return operator_c_per_h * operators * 100 / throughput_l_per_h 281} 282 283func isc_cost_is_indicative() -> i64 { return 1 } 284func isc_holds_any_verified_price() -> i64 { return 0 } 285 286// ===== The cold chain, which is the axis that actually decides quality === 287 288// A retail cabinet reaching -9 C is warmer than the -18 C storage target, so 289// a national distribution chain operates in permanent partial abuse. 290func isc_retail_cabinet_is_abuse(storage_target_mc: i64) -> i64 { 291 if ISC_RETAIL_CABINET_MC > storage_target_mc { return 1 } 292 return 0 293} 294 295// **WHERE THE SMALL PRODUCER WINS. Short chain, few handoffs, no open-faced 296// cabinet. Since ripening is driven by fluctuation rather than mean 297// temperature, fewer excursions beats a colder average. 298func isc_short_chain_beats_plant_freezer() -> i64 { return 1 } 299func isc_quality_advantage_is_at_the_freezer() -> i64 { return 0 }