""" Cryogenic Pump Cycle Analysis - break_coolprop Version Replaces ALL CoolProp calls in the hot loop with Numba-JIT Helmholtz EOS evaluations. CoolProp is only used in pre-loop setup (one-time cost). HOT LOOP CHANGES (vs cycle2mdot_fast.py): - cv: AS.update(DmassT)+AS.cvmass() → h2_props.cv_td() [Helmholtz direct] - State update: 2×AS.update(DmassT) → 2×h2_props.state_td() [Helmholtz direct] - ICV flow: _flow_AS() → h2_props.flow_calc() [Helmholtz PT + PS] - Blowby flow: _flow_AS() → h2_props.flow_calc() [Helmholtz PT + PS] - DCV reverse flow: _flow_AS() → h2_props.flow_calc() [Helmholtz PT + PS] - DCV forward flow: 1D LUT (unchanged) - Convection: CoolProp via refprop (unchanged, called rarely) WHAT DID NOT CHANGE: - Energy balance equation (sacred) - Adaptive timestep logic - Valve dynamics, geometry, all physics - Output format and history arrays """ import numpy as np import math from typing import Optional, Tuple, List, Dict, Any import time import CoolProp from CoolProp.CoolProp import PropsSI from numba import njit # Import non-hot-loop functions from the original from cryosim.engine.helpers import ( refprop, coolprop_fluid_name, subcool_K, kv_from_Cd_and_RO_dia, fluid_phase_qual, mixture_pump_prop, flow_RF_kgpm, composite_thermal_conductivity, mean_free_path_meter, molecular_cond_WpmK, free_conv_2cyl_Wpm, minmax, max_dt_s, _get_cached_fluid_props, _build_ps_table_1d, print_results, PI, STEFAN_BOLTZMANN, RU, ) # break_coolprop: Numba-JIT Helmholtz property evaluations from cryosim.engine.helmholtz.h2_props import init as h2_init, cv_td, state_td, flow_calc # ── Module-level PS table cache (avoids 10k CoolProp calls on repeat runs) ── _PS_TABLE_CACHE = {} def _build_ps_table_1d_cached(s_fixed, P_range_MPa, fluid, n=2000): """Cached wrapper around _build_ps_table_1d. Cache key uses rounded values to handle floating-point comparison safely. The table only changes when exit conditions (entropy, pressure range, fluid) change. """ key = (round(s_fixed, 6), round(P_range_MPa[0], 6), round(P_range_MPa[1], 6), fluid, n) if key in _PS_TABLE_CACHE: return _PS_TABLE_CACHE[key] result = _build_ps_table_1d(s_fixed, P_range_MPa, fluid, n) _PS_TABLE_CACHE[key] = result return result # ── SS304 thermal conductivity (NIST Cryogenic Materials Database) ─────────── _SS304_T = [4, 6, 8, 10, 15, 20, 30, 40, 50, 60, 77, 100, 150, 200, 250, 300] _SS304_K = [0.3, 0.5, 0.75, 1.0, 1.8, 2.6, 3.9, 5.0, 6.0, 6.9, 8.0, 9.5, 11.5, 13.0, 14.5, 15.5] def tc_ss304(T_K: float) -> float: """Thermal conductivity of SS304 stainless steel [W/m/K]. Piecewise linear interpolation of NIST cryogenic data. Valid range: 4–300 K. Clamps to endpoints outside range. """ return float(np.interp(T_K, _SS304_T, _SS304_K)) # ── Phase B: @njit helper functions ────────────────────────────────────────── _PI = 3.141592653589793 _SIGMA = 5.670374419e-8 # Stefan-Boltzmann constant [W/m²/K⁴] @njit(cache=True) def _kv_from_dia(d_mm, Cd=0.61): """Kv from area-equivalent diameter [m³/hr]. Replaces kv_from_Cd_and_RO_dia.""" area = _PI / 4.0 * (d_mm / 1000.0) ** 2 return 3.6e4 * area * (2.0 * Cd * Cd) ** 0.5 @njit(cache=True) def _interp1d(x, xg, yg): """Linear interpolation on sorted 1D grid with binary search.""" n = len(xg) if x <= xg[0]: return yg[0] if x >= xg[n - 1]: return yg[n - 1] lo, hi = 0, n - 1 while lo < hi - 1: mid = (lo + hi) >> 1 if xg[mid] <= x: lo = mid else: hi = mid t = (x - xg[lo]) / (xg[lo + 1] - xg[lo]) return yg[lo] + t * (yg[lo + 1] - yg[lo]) @njit(cache=True) def _dcv_flow_1d_njit(p1_barg, p2_barg, h1_J, Kv, cf, P_grid, h_tbl, d_tbl): """DCV flow via 1D PS table [kg/min]. Replaces _cached_flow_1d + _flow_core.""" if abs(p1_barg - p2_barg) < 0.0001: return 0.0 Phigha = max(p1_barg, p2_barg) + 1.01325 Plowa = min(p1_barg, p2_barg) + 1.01325 p2hat_MPa = max(Plowa / 10.0, Phigha * cf / 10.0) d2hat = _interp1d(p2hat_MPa, P_grid, d_tbl) h2hat_kJ = _interp1d(p2hat_MPa, P_grid, h_tbl) sg = 1.0 if p2_barg > p1_barg: sg = -1.0 h2hat_J = h2hat_kJ * 1000.0 dh = h1_J - h2hat_J if dh < 0.0: dh = 0.0 mdot_kghr = Kv * d2hat * 0.1 * math.sqrt(dh) return sg * mdot_kghr / 60.0 @njit(cache=True) def _ICV_loop_njit( # Time / geometry tcycle, tstroke, dt0, dtmax, slp, icp, Vdisp, dvf, vm_piston, bore_mm, ChamberLen_mm, HousingOD_mm, # Pressures Pexit_barg, Ptank_barg, Pbbexit_barg, # DCV valve DCVmass, DCVSC_Npm, DCVtravel, DCVdpArea, DCVFs_N, DCVleakKv, DCVport_mm, # ICV valve ICVmass, ICVSC_Npm, ICVtravel, ICVdpArea, ICVFs_N, ICVleakKv, ICVport_mm, K_bulk, # Thermal / process Tamb_K, htc_amb, bot, Ffric, fric2chamber, tmass, Kv_BB, cf, # Inlet / outlet h_in, h_out, Tin_K, # Initial chamber state mc_init, mc0, hc_init, uc_init, pc_init, Tc_K_init, den_init, vc_init, # Initial valve / leak state leak_rate_DCV_init, leak_rate_ICV_init, # Initial heat terms (from pre-loop CoolProp, preserves Phase A bit-identity) Qig_init, Qf_init, Qtmass_init, dm_bb_init, h_bb_init, # DCV 1D table dcv1d_P, dcv1d_h, dcv1d_d, h1_exit_J, # PS 2D LUT Pg, Sg, ps_d_tbl, ps_h_tbl, # Convection LUT conv_Tc, conv_Q, # Pre-allocated history arrays (modified in-place) hist_t, hist_pc, hist_den, hist_yp, hist_mc, hist_Tc, hist_hc, hist_dcvof, hist_dcvlk, hist_icvof, hist_icvlk, hist_dmtot, ): """Compiled main integration loop. Returns (j, m_in, m_out, kWh_retract, kWh_extend, Fmax_ICV, Fmax_DCV, Fmax_ICV_close, Vmax_ICVopen, ICVmax_frac, DCV_ct, DCV_ot, ICV_openst, ICV_ct).""" # ── initialise mutable state from passed-in values ── mc = mc_init hc = hc_init uc = uc_init pc = pc_init Tc_K = Tc_K_init den = den_init vc = vc_init yp = 0.0 leak_rate_prev_DCV = leak_rate_DCV_init leak_rate_prev_ICV = leak_rate_ICV_init Qig = Qig_init Qf = Qf_init Qtmass = Qtmass_init dm_bb = dm_bb_init h_bb = h_bb_init pc_last = pc vc_prev = vc del_pc = 0.0 # Valve positions xp = 0.0; vp = 0.0 xip = 0.0; vip = 0.0 Fs_DCV = DCVFs_N # Accumulators Fmax_ICV = 0.0 Fmax_DCV = 0.0 ICVmax_frac = 0.0 Fmax_ICV_close = 0.0 Vmax_ICVopen = 0.0 m_in = 0.0 m_out = 0.0 kWh_retract = 0.0 kWh_extend = 0.0 # Phase flags retract = True Extend = False DCVmoving = True ICVmoving = False DCV_ct = 0.0 DCV_ot = tcycle ICV_openst = 0.0 ICV_ct = tcycle t = 0.0 j = 1 while t < tcycle and j < 50000: if t > tstroke and not Extend: retract = False Extend = True # ── adaptive dt ── ICVopenfr = ICV_openst / tstroke if tstroke > 0.0 else 0.0 ICVmovfr = 1.0 - ICVopenfr n_seg = 5 df = ICVmovfr / n_seg if ICVmovfr > 0.0 else 0.2 xn = (yp - ICVopenfr) / df if df > 0.0 else 0.0 if DCVmoving: dt = max(dt0, min(dtmax, slp * Fs_DCV + icp)) elif ICVmoving: dt = dt0 * (50.0 ** xn) dt = max(dt0, min(dtmax * 2.0, dt)) elif (pc - Ptank_barg < 2.0) and not ICVmoving: dt = dt0 elif (Pexit_barg - pc < abs(del_pc)) and not DCVmoving: dt = dt0 else: dt = dtmax t += dt # ── chamber heat / mass inputs ── dm_DCV = leak_rate_prev_DCV * dt dm_ICV = leak_rate_prev_ICV * dt dm_tot = dm_ICV + dm_DCV + dm_bb m_out += dm_DCV m_in += dm_ICV mc_prev = mc mc = max(mc + dm_tot, mc0 / 1000.0) dh_DCV = (h_out if leak_rate_prev_DCV > 0.0 else hc) * dm_DCV dh_ICV = (h_in if leak_rate_prev_ICV > 0.0 else hc) * dm_ICV dh_bb = (h_bb if dm_bb > 0.0 else hc) * dm_bb work = (-pc * vc + pc_last * vc_prev) * 100.0 if retract: kWh_retract += work else: kWh_extend += work uc_prev_step = uc # ── cv via Helmholtz ── cv_kJkgK = cv_td(Tc_K, den) # ── SACRED energy balance ── uc = (uc * mc_prev + (dh_DCV + dh_ICV + dh_bb + Qig + Qf + Qtmass) + work) / mc # ── piston position ── yp = 0.5 * (1.0 - math.cos(2.0 * _PI * t / tcycle)) vc_prev = vc vc = Vdisp * (dvf + yp) den = mc / vc # ── state update via Helmholtz (two-pass refinement) ── dT_est = (uc - uc_prev_step) / cv_kJkgK T_est = max(Tc_K + dT_est, 14.0) pc_new, hc_new = state_td(T_est, den) p_new_Pa = (pc_new + 1.01325) * 1e5 uc_new_ref = hc_new - p_new_Pa / den / 1000.0 dT_ref = (uc - uc_new_ref + (uc - uc_prev_step)) / cv_kJkgK T_ref = max(Tc_K + dT_ref, 14.0) pc_new, hc_new = state_td(T_ref, den) del_pc = pc_new - pc_last pc_last = pc_new pc = pc_new hc = hc_new Tc_K = T_ref uc = hc - (pc + 1.01325) * 1e5 / den / 1000.0 # ── DCV motion ── Fs_DCV = DCVFs_N - DCVSC_Npm * xp Fdp_DCV = (Pexit_barg - pc) * 1e5 * DCVdpArea Fmax_DCV = max(Fmax_DCV, Fs_DCV + Fdp_DCV) ap = (Fs_DCV + Fdp_DCV) / DCVmass vp += ap * dt if (xp == 0.0 and vp < 0.0) or (xp == DCVtravel and vp > 0.0): vp = 0.0 xp = xp + vp * dt xp = max(0.0, min(DCVtravel, xp)) x_frac = max(0.0, min(1.0, xp / DCVtravel)) if retract and x_frac >= 1.0 and DCVmoving: DCVmoving = False DCV_ct = t elif Extend and x_frac < 1.0 and not DCVmoving: DCVmoving = True DCV_ot = t kv_DCV = DCVleakKv + _kv_from_dia(DCVport_mm * (1.0 - x_frac)) if Pexit_barg > pc: leak_rate_DCV = _dcv_flow_1d_njit( Pexit_barg, pc, h1_exit_J, kv_DCV, cf, dcv1d_P, dcv1d_h, dcv1d_d) / 60.0 else: leak_rate_DCV = flow_calc( Pexit_barg, pc, Tc_K, kv_DCV, cf, Pg, Sg, ps_d_tbl, ps_h_tbl) / 60.0 leak_rate_prev_DCV = leak_rate_DCV # ── ICV motion ── Fs_ICV = ICVFs_N - ICVSC_Npm * (ICVtravel - xip) Fdp_ICV = (Ptank_barg - pc) * 1e5 * ICVdpArea vwave = math.sqrt(K_bulk * 1e6 / max(den, 1e-6)) v_piston = vm_piston * math.sin(2.0 * _PI * t / tcycle) WHdp = (1.0 if retract else -1.0) * den * v_piston * vwave * ICVdpArea Ftot_ICV = Fdp_ICV - Fs_ICV + WHdp Fmax_ICV = max(Fmax_ICV, Ftot_ICV) Fmax_ICV_close = min(Fmax_ICV_close, Ftot_ICV) aip = Ftot_ICV / ICVmass vip += aip * dt if (xip == 0.0 and vip < 0.0) or (xip == ICVtravel and vip > 0.0): vip = 0.0 Vmax_ICVopen = max(Vmax_ICVopen, vip) xip = xip + vip * dt xip = max(0.0, min(ICVtravel, xip)) xi_frac = xip / ICVtravel if retract and xip > 0.0 and not ICVmoving: ICVmoving = True ICV_openst = t elif Extend and xi_frac == 0.0 and ICVmoving: ICVmoving = False ICV_ct = t ICVmax_frac = max(ICVmax_frac, xi_frac) kv_ICV = ICVleakKv + _kv_from_dia(ICVport_mm * xi_frac) T_upstream_ICV = Tc_K if pc > Ptank_barg else Tin_K leak_rate_ICV = flow_calc( Ptank_barg, pc, T_upstream_ICV, kv_ICV, cf, Pg, Sg, ps_d_tbl, ps_h_tbl) / 60.0 leak_rate_prev_ICV = leak_rate_ICV # ── heat ingress, friction, blowby ── Qrad = (_PI * bore_mm * ChamberLen_mm * _SIGMA * (Tamb_K ** 4 - Tc_K ** 4) / (2.0 * bot) / 1e6) _Qconv_base = _interp1d(Tc_K, conv_Tc, conv_Q) Qconv = -_Qconv_base * (ChamberLen_mm / 1000.0) Qconv = Qconv + 2.0 * _PI / 4.0 * HousingOD_mm ** 2 * htc_amb * (Tamb_K - Tc_K) / 1e6 Qig = (Qrad + Qconv) * dt / 1000.0 Qfric = Ffric * abs(v_piston) * fric2chamber Qf = Qfric * dt / 1000.0 Qtmass = (1.0 if retract else -1.0) * tmass * dt dm_bb = flow_calc( Pbbexit_barg, pc, Tc_K, Kv_BB, cf, Pg, Sg, ps_d_tbl, ps_h_tbl) / 60.0 * dt h_bb = hc # ── record history ── hist_t[j - 1] = t * 360.0 / tcycle hist_pc[j - 1] = pc hist_den[j - 1] = den hist_yp[j - 1] = yp hist_mc[j - 1] = mc * 1000.0 hist_Tc[j - 1] = Tc_K hist_hc[j - 1] = hc hist_dcvof[j - 1] = 1.0 - x_frac hist_dcvlk[j - 1] = leak_rate_DCV * 60.0 hist_icvof[j - 1] = xi_frac hist_icvlk[j - 1] = leak_rate_ICV * 60.0 hist_dmtot[j - 1] = dm_tot / dt if dt > 0.0 else 0.0 j += 1 return (j - 1, m_in, m_out, kWh_retract, kWh_extend, Fmax_ICV, Fmax_DCV, Fmax_ICV_close, Vmax_ICVopen, ICVmax_frac, DCV_ct, DCV_ot, ICV_openst, ICV_ct) def ICV_open(Pexit_barg: float, speed_f: float, Ptank_barg: float, Psat_barg: float, ICVparam: List[float], DCVparam: List[float], pump_geom: List[float], proc_param: List[float], fluid: str = "h2", prtMode: int = 0, flash_eff: float = 0.0 ) -> Tuple[np.ndarray, Dict[str, Any]]: """AbstractState-accelerated pump cycle simulation. Identical interface, output, and numerical values to cycle2mdot_cached.ICV_open() when flash_eff=0.0. Uses CoolProp's low-level AbstractState API to batch property queries. Parameters added (VBA Pack 3): flash_eff : float Film boiling heat transfer efficiency for thermal mass effect (0–1). Default 0.0 disables thermal mass for backward compatibility. Typical value: 0.02 (2%). """ t_start = time.time() # Initialize Numba Helmholtz + PS LUT (replaces AbstractState) _Pg, _Sg, _d_tbl, _h_tbl = h2_init() fluid_cp = coolprop_fluid_name(fluid) # ------------------------------------------------------------------ unpack ICVport_mm = ICVparam[0] ICVmass_g = ICVparam[1] ICVtravel_mm = ICVparam[2] ICVdpArea_mm2 = ICVparam[3] ICVFs_N = ICVparam[4] ICVSC_Npmm = ICVparam[5] ICVleakKv = ICVparam[6] ICVcomp_eff = ICVparam[7] ICV_Npts = ICVparam[8] DCVport_mm = DCVparam[0] DCVmass_g = DCVparam[1] DCVtravel_mm = DCVparam[2] DCVdpArea_mm2 = DCVparam[3] DCVFs_N = DCVparam[4] DCVSC_Npmm = DCVparam[5] DCVleakKv = DCVparam[6] DCVcomp_eff = DCVparam[7] DCV_Npts = DCVparam[8] bore_mm = pump_geom[0] stroke_mm = pump_geom[1] HousingOD_mm = pump_geom[2] ChamberLen_mm = pump_geom[3] em_housing = pump_geom[4] em_shield = pump_geom[5] kvoid = pump_geom[6] khousing = pump_geom[7] Vfvoid = pump_geom[8] Vacuum_micron = pump_geom[9] design_cpm = pump_geom[10] dvf = pump_geom[11] Tamb_K = proc_param[0] htc_amb = proc_param[1] NetDriveCouplerForce_kgf = proc_param[2] F_multiplier = proc_param[3] Kv_BB = proc_param[4] Pbbexit_barg = proc_param[5] fric2chamber = proc_param[6] Exp_eff = proc_param[7] # ---------------------------------------------------------------- geometry stroke = stroke_mm / 1000.0 bore = bore_mm / 1000.0 Vdisp = PI / 4.0 * bore**2 * stroke V_dead = dvf * Vdisp pump_cpm = design_cpm * speed_f tcycle = 60.0 / pump_cpm tstroke = tcycle / 2.0 vm_piston = PI * stroke * pump_cpm / 60.0 # --------------------------------------------------------- thermodynamics # Pre-loop: use refprop (one-time cost, matches original exactly) PsMPa = Psat_barg / 10.0 + 0.101325 PtMPa = Ptank_barg / 10.0 + 0.101325 PeMPa = Pexit_barg / 10.0 + 0.101325 Tin_K = refprop("t", fluid, "pq", "si", PsMPa, 0.0) den_in = refprop("d", fluid, "pt", "si", PtMPa, Tin_K) h_in = refprop("h", fluid, "pt", "si", PtMPa, Tin_K) den_out = mixture_pump_prop("d", Ptank_barg, Pexit_barg, 0.0, DCVcomp_eff, fluid, Tin_K) h_out = mixture_pump_prop("h", Ptank_barg, Pexit_barg, 0.0, DCVcomp_eff, fluid, Tin_K) Tout_K = mixture_pump_prop("t", Ptank_barg, Pexit_barg, 0.0, DCVcomp_eff, fluid, Tin_K) Tout_C = Tout_K - 273.15 # ------------------------------------------------ property-level P,S flash cache _props = _get_cached_fluid_props(fluid) _cf = _props["cf"] _s_exit = refprop("s", fluid, "pt", "si", PeMPa, Tout_K) _h1_exit_J = refprop("h", fluid, "pt", "si", PeMPa, Tout_K) * 1000.0 _s_tank = refprop("s", fluid, "pt", "si", PtMPa, Tin_K) _h1_tank_J = refprop("h", fluid, "pt", "si", PtMPa, Tin_K) * 1000.0 # 1D tables for DCV (unchanged from original) _p2hat_lo = 0.101325 _p2hat_hi = PeMPa + 1.0 _dcv1d_P, _dcv1d_h, _dcv1d_d = _build_ps_table_1d_cached( s_fixed=_s_exit, P_range_MPa=(_p2hat_lo, _p2hat_hi), fluid=fluid, n=2000, ) # ---------------------------------------------------- DCV physical params DCVmass = DCVmass_g / 1000.0 DCVSC_Npm = DCVSC_Npmm * 1000.0 DCVtravel = DCVtravel_mm / 1000.0 DCVdpArea = DCVdpArea_mm2 / 1e6 tc_Fs_DCV = np.sqrt(2.0 * DCVmass * DCVtravel / max(DCVFs_N, 1e-9)) # ---------------------------------------------------- ICV physical params ICVmass = ICVmass_g / 1000.0 ICVSC_Npm = ICVSC_Npmm * 1000.0 ICVtravel = ICVtravel_mm / 1000.0 ICVdpArea = ICVdpArea_mm2 / 1e6 K_bulk = refprop("bs", fluid, "pt", "si", PtMPa, Tin_K) tc_Fs_ICV = np.sqrt(2.0 * ICVmass * ICVtravel / max(ICVFs_N, 1e-9)) # -------------------------------------------------- chamber initial state mc = V_dead * den_out hc = h_out mc0 = mc vc = Vdisp * dvf den = den_out pc = Pexit_barg yp = 0.0 kv_DCV = DCVleakKv + kv_from_Cd_and_RO_dia(DCVport_mm) leak_rate_DCV = _dcv_flow_1d_njit(Pexit_barg, pc, _h1_exit_J, kv_DCV, _cf, _dcv1d_P, _dcv1d_h, _dcv1d_d) / 60.0 Pexit_MPa = (Pexit_barg + 1.01325) / 10.0 Tc_K = refprop("t", fluid, "ph", "si", Pexit_MPa, hc) p_init_Pa = (pc + 1.01325) * 1e5 uc = hc - p_init_Pa / den / 1000 # -------------------------------------------------- ICV initial leak rate kv_ICV = ICVleakKv TupstreamC = (Tout_K if pc > Ptank_barg else Tin_K) - 273.15 leak_rate_ICV = flow_RF_kgpm(Ptank_barg, pc, TupstreamC, kv_ICV, fluid) / 60.0 # -------------------------------------------------- adaptive time-step dt0 = max(tc_Fs_ICV / max(ICV_Npts, 1), tc_Fs_DCV / max(DCV_Npts, 1)) / 10.0 kv1 = kv_from_Cd_and_RO_dia(ICVport_mm) dp_icv = ICVFs_N / ICVdpArea / 1e5 dtmax_ICV = max_dt_s(Ptank_barg, kv1, dp_icv, Ptank_barg, Psat_barg, 1, Vdisp, fluid) / 2.0 kv1 = kv_from_Cd_and_RO_dia(DCVport_mm) dp_dcv = DCVFs_N / DCVdpArea / 1e5 dtmax_DCV = max_dt_s(Pexit_barg, kv1, dp_dcv, Ptank_barg, Psat_barg, 0, Vdisp, fluid) / 2.0 dtmax = 5e-6 DCVFs_min = DCVFs_N - DCVSC_Npm * DCVtravel if (DCVFs_N - DCVFs_min) != 0: slp = (dt0 - dtmax) / (DCVFs_N - DCVFs_min) icp = dtmax - slp * DCVFs_min else: slp = 0.0 icp = dt0 # -------------------------------------------------- radiation/convection geometry bot = 1.0 / em_housing + (1.0 - em_housing) / em_housing * (bore_mm / HousingOD_mm) ds = (bore_mm + HousingOD_mm) / 2.0 bot = bot + 2.0 * (1.0 - em_shield) / em_shield * (bore_mm / ds) keff = composite_thermal_conductivity(1, kvoid, Vfvoid, khousing) htc_all = 1.0 / (1.0 / htc_amb + (HousingOD_mm - bore_mm) / 2.0 / 1000.0 / keff) pa = Vacuum_micron / 1000.0 * (101325.0 / 760.0) Ffric = NetDriveCouplerForce_kgf * F_multiplier * 9.80665 Qfric = Ffric * vm_piston * fric2chamber Qrad = PI * bore_mm * ChamberLen_mm * STEFAN_BOLTZMANN * (Tamb_K**4 - Tc_K**4) / (2.0 * bot) / 1e6 _Qconv_base = free_conv_2cyl_Wpm(bore_mm / 1000.0, Tc_K, HousingOD_mm / 1000.0, Tamb_K, pa, "air") Qconv = -_Qconv_base * (ChamberLen_mm / 1000.0) Qconv = Qconv + 2.0 * PI / 4.0 * HousingOD_mm**2 * htc_amb * (Tamb_K - Tc_K) / 1e6 Qig = (Qrad + Qconv) * dt0 / 1000.0 Qf = Qfric * dt0 / 1000.0 # -------------------------------------------------- thermal mass effect (VBA Pack 3) # Models cyclic heat storage/release in the SS304 chamber wall. # During retract: wall releases stored heat into cold chamber (+Qtmass) # During extend: hot gas heats the wall (−Qtmass) if flash_eff > 0.0: _theta_max = Tout_K - Tin_K # K, max temp amplitude _theta_avg = 0.215 * _theta_max # K, avg (21.5% from transient diffusion) _tc_wall = tc_ss304(Tin_K + _theta_avg) # W/m/K, SS304 at avg T _rho_wall = 7800.0 # kg/m3 _cp_wall = 500.0 # J/kg/K _kappa = _tc_wall / _rho_wall / _cp_wall # m2/s, thermal diffusivity _delta = 2.6 * math.sqrt(_kappa * tcycle) # m, thermal penetration depth _a_chamber = 2.0 * (PI / 4.0 * bore**2) + PI * bore * stroke # m2, chamber surface tmass = (flash_eff * _rho_wall * _cp_wall * _a_chamber * _delta * _theta_avg / tstroke / 1000.0) # kJ/s else: tmass = 0.0 Qtmass = tmass * dt0 # kJ (initial, retract→positive) dm_bb = flow_RF_kgpm(Pbbexit_barg, pc, Tc_K - 273.15, Kv_BB, fluid, 1) / 60.0 * dt0 h_bb = hc # ------------------------------------------- convection LUT (Phase B) # Pre-compute free_conv_2cyl_Wpm at 100 Tc values to replace in-loop CoolProp _N_CONV = 100 conv_Tc = np.linspace(15.0, 350.0, _N_CONV) conv_Q = np.empty(_N_CONV) for _ci in range(_N_CONV): conv_Q[_ci] = free_conv_2cyl_Wpm( bore_mm / 1000.0, conv_Tc[_ci], HousingOD_mm / 1000.0, Tamb_K, pa, "air") # ------------------------------------------- pre-allocated history arrays _MAX_STEPS = 50000 hist_t = np.zeros(_MAX_STEPS) hist_pc = np.zeros(_MAX_STEPS) hist_den = np.zeros(_MAX_STEPS) hist_yp = np.zeros(_MAX_STEPS) hist_mc = np.zeros(_MAX_STEPS) hist_Tc = np.zeros(_MAX_STEPS) hist_hc = np.zeros(_MAX_STEPS) hist_dcvof = np.zeros(_MAX_STEPS) hist_dcvlk = np.zeros(_MAX_STEPS) hist_icvof = np.zeros(_MAX_STEPS) hist_icvlk = np.zeros(_MAX_STEPS) hist_dmtot = np.zeros(_MAX_STEPS) # ============================= @njit loop kernel ========================== (j, m_in, m_out, kWh_retract, kWh_extend, Fmax_ICV, Fmax_DCV, Fmax_ICV_close, Vmax_ICVopen, ICVmax_frac, DCV_ct, DCV_ot, ICV_openst, ICV_ct) = _ICV_loop_njit( # Time / geometry tcycle, tstroke, dt0, dtmax, slp, icp, Vdisp, dvf, vm_piston, bore_mm, ChamberLen_mm, HousingOD_mm, # Pressures Pexit_barg, Ptank_barg, Pbbexit_barg, # DCV valve DCVmass, DCVSC_Npm, DCVtravel, DCVdpArea, DCVFs_N, DCVleakKv, DCVport_mm, # ICV valve ICVmass, ICVSC_Npm, ICVtravel, ICVdpArea, ICVFs_N, ICVleakKv, ICVport_mm, K_bulk, # Thermal / process Tamb_K, htc_amb, bot, Ffric, fric2chamber, tmass, Kv_BB, _cf, # Inlet / outlet h_in, h_out, Tin_K, # Initial chamber state mc, mc0, hc, uc, pc, Tc_K, den, vc, # Initial valve / leak state leak_rate_DCV, leak_rate_ICV, # Initial heat terms Qig, Qf, Qtmass, dm_bb, hc, # h_bb = hc at init # DCV 1D table _dcv1d_P, _dcv1d_h, _dcv1d_d, _h1_exit_J, # PS 2D LUT _Pg, _Sg, _d_tbl, _h_tbl, # Convection LUT conv_Tc, conv_Q, # Pre-allocated history arrays hist_t, hist_pc, hist_den, hist_yp, hist_mc, hist_Tc, hist_hc, hist_dcvof, hist_dcvlk, hist_icvof, hist_icvlk, hist_dmtot, ) # ============================ post-processing ============================ mass_eff = m_in / (Vdisp * den_in) m_out = -m_out m_out_eff = m_out / (Vdisp * den_in) mdot_out = m_out / tcycle * 60.0 # Per-stroke kWh normalization (VBA Pack 3) kWh_retract_out = kWh_retract / 3600.0 / m_out if m_out > 0 else 0.0 kWh_extend_out = kWh_extend / 3600.0 / m_out if m_out > 0 else 0.0 t_end = time.time() out = np.zeros((15, 2), dtype=object) out[0, 0] = t_end - t_start; out[0, 1] = "s, cpu time" out[1, 0] = mass_eff; out[1, 1] = ", mass inflow efficiency" out[2, 0] = DCV_ct; out[2, 1] = "s, DCV closure time" out[3, 0] = ICV_openst / tstroke; out[3, 1] = ", stroke fraction ICV starts to open" out[4, 0] = ICVmax_frac; out[4, 1] = ", max ICV open fraction" out[5, 0] = Fmax_DCV; out[5, 1] = "N, max closure force on DCV" out[6, 0] = Fmax_ICV; out[6, 1] = "N, max open force on ICV" out[7, 0] = Fmax_ICV_close; out[7, 1] = "N, max closure force on ICV" out[8, 0] = Vmax_ICVopen; out[8, 1] = "m/s, max ICV opening velocity" out[9, 0] = m_out; out[9, 1] = "kg, total discharged mass per cycle" out[10, 0] = m_out_eff; out[10, 1] = ", mass efficiency of cycle" out[11, 0] = DCV_ot / tstroke - 1.0; out[11, 1] = ", stroke fraction DCV starts to open" out[12, 0] = ICV_ct - tstroke; out[12, 1] = "s, ICV closure time after extend start" out[13, 0] = kWh_retract_out; out[13, 1] = "kWh/kg, retract pV work" out[14, 0] = kWh_extend_out; out[14, 1] = "kWh/kg, extend pV work" history = { 'angle_deg': hist_t[:j], 'pc': hist_pc[:j], 'den': hist_den[:j], 'yp': hist_yp[:j], 'mc_g': hist_mc[:j], 'Tc_K': hist_Tc[:j], 'hc': hist_hc[:j], 'DCV_open_frac': hist_dcvof[:j], 'DCV_leak_kgpm': hist_dcvlk[:j], 'ICV_open_frac': hist_icvof[:j], 'ICV_leak_kgpm': hist_icvlk[:j], 'dm_tot_kgps': hist_dmtot[:j], 'mdot_kgpm': mdot_out, 'tcycle_s': tcycle, 'tstroke_s': tstroke, 'steps': j, 'kWh_retract': kWh_retract_out, 'kWh_extend': kWh_extend_out, } return out, history if __name__ == "__main__": print("Cryogenic Pump Cycle Analysis - break_coolprop (Numba Helmholtz)") print("-" * 60) # MVP 1.0 Simplex defaults ICVparam = [20.5, 48, 5, 779.3, 33.4, 3.75, 0.0000736, 1.0, 100] DCVparam = [8.3, 25, 3.79, 78.54, 7.8, 1.053, 0.0000026, 0.8, 200] pump_geom = [40.3, 60, 500, 0.01, 120, 300, 0.8, 0.8, 0.026, 15, 37, 760000] proc_param = [300, 10, 4, 30, 0.5, 0.006, 0, 0.2] # Note: pump_geom order for ICV_open is: # [bore, stroke, HousingOD, ChamberLen, em_housing, em_shield, kvoid, khousing, Vfvoid, Vacuum, design_cpm, dvf] pump_geom = [40.3, 60.0, 120.0, 300.0, 0.8, 0.8, 0.026, 15.0, 37.0, 760000, 500.0, 0.01] proc_param = [300.0, 10.0, 4.0, 30.0, 0.006, 0.0, 0.5, 0.2] print("Running break_coolprop simulation...") t0 = time.time() out, hist = ICV_open(900.0, 0.8, 7.0, 2.0, ICVparam, DCVparam, pump_geom, proc_param) wall = time.time() - t0 print(f"Wall time: {wall:.2f}s Steps: {hist['steps']} mdot: {hist['mdot_kgpm']:.4f} kg/min") print_results(out)