pandeydigant31's picture
Upload folder using huggingface_hub
8a169a0 verified
Raw
History Blame Contribute Delete
28.6 kB
"""
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)