Spaces:
Sleeping
Sleeping
| """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 | |
| 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() | |
| 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__", | |
| ] | |