Spaces:
Sleeping
Sleeping
| """ | |
| 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⁴] | |
| 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 | |
| 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]) | |
| 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 | |
| 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) | |