pandeydigant31's picture
Upload folder using huggingface_hub
8a169a0 verified
Raw
History Blame Contribute Delete
12.5 kB
"""CryoSim — Cryogenic Pump Hardware Prototyping Simulator.
Quick start:
from cryosim import predict
result = predict(hardware="old_icv", Pexit=350, speed=0.65)
print(result)
"""
__version__ = "0.1.0"
from dataclasses import dataclass
from typing import Optional, Dict, Any
import numpy as np
@dataclass
class CycleResult:
"""Result of a single pump cycle simulation."""
mdot_kgpm: float # Discharge flow rate [kg/min]
mass_eff: float # Mass inflow efficiency [0-1]
Tc_peak_K: float # Peak chamber temperature [K]
pc_peak_barg: float # Peak chamber pressure [barg]
DCV_ct_s: float # DCV closure time [s]
ICV_open_frac: float # Max ICV opening fraction [0-1]
cpu_s: float # Simulation wall time [s]
steps: int # Adaptive timesteps used
kWh_retract: float # Retract stroke work [kWh/kg]
kWh_extend: float # Extend stroke work [kWh/kg]
tcycle_s: float # Cycle period [s]
hardware_name: str # Config name used
Pexit_barg: float # Exit pressure [barg]
speed_f: float # Speed fraction [0-1]
backflow_warning: bool = False # True if raw mdot was negative (clamped to 0)
raw_out: Optional[np.ndarray] = None
raw_history: Optional[Dict[str, Any]] = None
def __repr__(self) -> str:
warn = " BACKFLOW!" if self.backflow_warning else ""
return (
f"CycleResult(mdot={self.mdot_kgpm:.4f} kg/min, "
f"eff={self.mass_eff:.1%}, "
f"Tc_peak={self.Tc_peak_K:.1f} K, "
f"pc_peak={self.pc_peak_barg:.1f} barg, "
f"steps={self.steps}, "
f"cpu={self.cpu_s:.2f}s{warn})"
)
def predict(
hardware: str,
Pexit: float,
speed: float,
Ptank: float = 7.0,
Psat: float = 2.0,
flash_eff: float = 0.0,
) -> CycleResult:
"""Run a single pump cycle simulation.
Args:
hardware: Config name ("old_icv", "new_icv", "calibrated_lh2")
or absolute path to a YAML file.
Pexit: Discharge pressure target [barg].
speed: Speed fraction [0-1].
Ptank: Inlet tank pressure [barg]. Default 7.0.
Psat: Saturation pressure [barg]. Default 2.0.
flash_eff: Thermal mass film boiling efficiency [0-1]. Default 0.0.
Returns:
CycleResult with all simulation outputs.
Raises:
ValueError: If inputs are outside valid physical ranges.
"""
if Pexit < 0:
raise ValueError(f"Pexit must be non-negative, got {Pexit}")
if not 0.0 < speed <= 1.5:
raise ValueError(f"speed must be in (0, 1.5], got {speed}")
if Ptank < 0:
raise ValueError(f"Ptank must be non-negative, got {Ptank}")
from cryosim.hardware.config import load_config
from cryosim.engine.fast import ICV_open
cfg = load_config(hardware)
engine_args = cfg.to_engine_args()
out, hist = ICV_open(
Pexit_barg=Pexit,
speed_f=speed,
Ptank_barg=Ptank,
Psat_barg=Psat,
flash_eff=flash_eff,
**engine_args,
)
raw_mdot = hist["mdot_kgpm"]
_backflow = raw_mdot < 0.0
mdot_clamped = max(0.0, raw_mdot)
return CycleResult(
mdot_kgpm=mdot_clamped,
mass_eff=float(out[1, 0]),
Tc_peak_K=float(np.max(hist["Tc_K"])),
pc_peak_barg=float(np.max(hist["pc"])),
DCV_ct_s=float(out[2, 0]),
ICV_open_frac=float(out[4, 0]),
cpu_s=float(out[0, 0]),
steps=hist["steps"],
kWh_retract=hist["kWh_retract"],
kWh_extend=hist["kWh_extend"],
tcycle_s=hist["tcycle_s"],
hardware_name=cfg.name,
Pexit_barg=Pexit,
speed_f=speed,
backflow_warning=_backflow,
raw_out=out,
raw_history=hist,
)
from cryosim.results import FillResult, FillTimeResult
from cryosim.optimization.optimizer import (
optimize, multi_start_optimize, differential_evolution_optimize,
)
from cryosim.optimization.design_optimizer import design_optimize
def fill(
hardware: str,
target_bar: float,
speed: float,
tank_volume_L: float = 50.0,
max_time_s: float = 1800.0,
Ptank: float = 7.0,
Psat: float = 2.0,
T_inlet_K: float = 55.0,
system_loss_kgmin: float = 0.02,
**kwargs,
) -> FillResult:
"""Simulate a complete fill cycle.
Args:
hardware: Config name ("old_icv", "new_icv", etc.).
target_bar: Target discharge pressure [bar].
speed: Speed fraction [0-1].
tank_volume_L: Receiver tank volume [liters].
max_time_s: Maximum simulation time [s].
Ptank: Inlet tank pressure [barg].
Psat: Saturation pressure [barg].
T_inlet_K: Inlet fluid temperature [K].
system_loss_kgmin: Combined system losses (blowby + heat leak + valve
leakage) expressed as equivalent mass flow [kg/min]. When net pump
flow drops below this threshold the fill is considered stalled,
because the pump can no longer overcome parasitic losses.
Default 0.02 kg/min.
**kwargs: Accepts deprecated ``stall_threshold_kgmin`` for backward
compatibility (mapped to ``system_loss_kgmin``).
Returns:
FillResult with fill profile, stall detection, timing.
Raises:
ValueError: If inputs are outside valid physical ranges.
"""
# Backward compatibility: accept old parameter name
if "stall_threshold_kgmin" in kwargs:
system_loss_kgmin = kwargs.pop("stall_threshold_kgmin")
if kwargs:
raise TypeError(f"Unexpected keyword arguments: {list(kwargs.keys())}")
if not 0.0 < speed <= 1.5:
raise ValueError(f"speed must be in (0, 1.5], got {speed}")
if Ptank < 0:
raise ValueError(f"Ptank must be non-negative, got {Ptank}")
if target_bar <= 0:
raise ValueError(f"target_bar must be positive, got {target_bar}")
if tank_volume_L <= 0:
raise ValueError(f"tank_volume_L must be positive, got {tank_volume_L}")
from cryosim.fill_sim.simulator import FillSimulator
sim = FillSimulator(
hardware=hardware, speed=speed, tank_volume_L=tank_volume_L,
Ptank=Ptank, Psat=Psat, T_inlet_K=T_inlet_K,
system_loss_kgmin=system_loss_kgmin,
)
return sim.run(target_bar=target_bar, max_time_s=max_time_s)
def stall_pressure(
hardware: str,
speed: float,
Ptank: float = 7.0,
Psat: float = 2.0,
threshold_kgmin: float = 0.05,
) -> float:
"""Find the maximum achievable discharge pressure for a hardware config.
Uses the pre-computed flow curve to find where flow drops below threshold.
Returns:
Stall pressure [barg].
"""
from cryosim.fill_sim.flow_curve import FlowCurve
fc = FlowCurve.build(
hardware=hardware, speed=speed, n_points=30,
P_min=50.0, P_max=950.0, Ptank=Ptank, Psat=Psat,
)
return fc.find_stall(threshold_kgmin=threshold_kgmin)
def cliff_pressure(
hardware: str,
speed: float,
Ptank: float = 7.0,
Psat: float = 2.0,
) -> Optional["CliffResult"]:
"""Detect the flow cliff for a hardware config at a given speed.
The flow cliff is the narrow pressure band (typically ~5 bar wide) where
pump flow drops by 50%+ due to the discharge check valve dynamics.
Knowing the cliff location is critical for fill strategy — the pump
transitions from bulk filling to trickle-charge above the cliff.
Args:
hardware: Config name ("old_icv", "new_icv", etc.).
speed: Speed fraction [0-1].
Ptank: Inlet tank pressure [barg].
Psat: Saturation pressure [barg].
Returns:
CliffResult with cliff boundaries and flow drop info,
or None if no significant cliff detected.
"""
from cryosim.fill_sim.flow_curve import FlowCurve, CliffResult # noqa: F811
fc = FlowCurve.build(
hardware=hardware, speed=speed, n_points=30,
P_min=50.0, P_max=950.0, Ptank=Ptank, Psat=Psat,
adaptive=True,
)
return fc.find_cliff()
def fill_time(
hardware: str,
target_bar: float,
speed: float,
tank_volume_L: float = 50.0,
Ptank: float = 7.0,
Psat: float = 2.0,
T_inlet_K: float = 55.0,
) -> 'FillTimeResult':
"""Estimate fill time to reach a target pressure.
Returns:
FillTimeResult with structured info about whether target was reached,
and why it wasn't if unreachable. Supports float() for backward compat.
"""
from cryosim.results import FillTimeResult as _FTR
result = fill(
hardware=hardware, target_bar=target_bar, speed=speed,
tank_volume_L=tank_volume_L, Ptank=Ptank, Psat=Psat,
T_inlet_K=T_inlet_K,
)
if result.reached_target:
return _FTR(
time_s=result.fill_time_s, reachable=True,
stall_pressure_bar=None, reason="Target reached")
elif result.stalled:
return _FTR(
time_s=None, reachable=False,
stall_pressure_bar=result.stall_pressure_bar,
reason=f"Pump stalled at {result.stall_pressure_bar:.0f} bar (flow below system loss threshold)")
else:
return _FTR(
time_s=None, reachable=False,
stall_pressure_bar=result.peak_pressure_bar,
reason=f"Timeout: reached {result.peak_pressure_bar:.0f} bar in {result.fill_time_s:.0f}s")
def calibrate(
hardware: str,
fills: list,
maxiter: int = 200,
):
"""Calibrate engine parameters against measured fill data.
Args:
hardware: Base hardware config to start from.
fills: List of fill dicts, each with keys:
Pexit_barg, speed_f, Ptank_barg, Psat_barg, measured_mdot_kgpm
maxiter: Maximum L-BFGS-B iterations.
Returns:
CalibrationResult with fitted params, RMSE, and export methods.
"""
from cryosim.calibration.engine import Calibrator
cal = Calibrator(hardware=hardware, fills=fills, maxiter=maxiter)
return cal.run()
@dataclass
class UncertainPrediction:
"""Prediction with uncertainty bounds."""
result: CycleResult # The point estimate
mdot_bounds: 'UncertaintyBands' # Bounds on mass flow
envelope_status: str # VALIDATED/EXTRAPOLATING/OUTSIDE_RANGE
def predict_with_uncertainty(
hardware: str,
Pexit: float,
speed: float,
Ptank: float = 7.0,
Psat: float = 2.0,
) -> UncertainPrediction:
"""Run prediction with uncertainty bounds.
Returns the standard CycleResult plus uncertainty bands
computed from calibration residuals and extrapolation penalties.
"""
from cryosim.validation.uncertainty import ResidualModel, UncertaintyBands
from cryosim.validation.envelope import check_envelope
result = predict(hardware=hardware, Pexit=Pexit, speed=speed, Ptank=Ptank, Psat=Psat)
model = ResidualModel()
bounds = model.predict_bounds(result.mdot_kgpm, Pexit, speed)
envelope = check_envelope(Pexit, speed)
return UncertainPrediction(
result=result,
mdot_bounds=bounds,
envelope_status=envelope.status,
)
def sensitivity(hardware="old_icv", speed=0.65, Pexit=350.0, **kwargs):
"""Run parameter sensitivity analysis. Returns SensitivityResult."""
from cryosim.optimization.sensitivity import run_sensitivity
return run_sensitivity(hardware=hardware, speed=speed, Pexit=Pexit, **kwargs)
def sweep(hardware, param_name, values, speed=0.65, Pexit=350.0, metric="mdot_kgpm", **kwargs):
"""Sweep one parameter across values, returning metric at each point."""
from cryosim.optimization.sweep import sweep_1d
return sweep_1d(hardware=hardware, param_name=param_name, values=values,
speed=speed, Pexit=Pexit, metric=metric, **kwargs)
def __getattr__(name):
if name == "CliffResult":
from cryosim.fill_sim.flow_curve import CliffResult
return CliffResult
raise AttributeError(f"module 'cryosim' has no attribute {name!r}")
__all__ = [
"predict", "CycleResult",
"predict_with_uncertainty", "UncertainPrediction",
"fill", "stall_pressure", "fill_time", "FillResult", "FillTimeResult",
"cliff_pressure", "CliffResult",
"calibrate",
"sensitivity", "sweep", "optimize",
"multi_start_optimize", "differential_evolution_optimize",
"design_optimize",
"__version__",
]