STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
feed_pressure_model.py
Go to the documentation of this file.
1"""Feed tank/manifold pressure vs time — blowdown segments vs dome-regulated (Phys §6.2).
2
3The dome-regulated curve is the regulator's setpoint plus its supply-pressure effect (SPE): a dome
4regulator's outlet rises as its inlet falls, by a ratio that belongs to the regulator, not to the
5code: the Aqua 1092 datasheet gives 10 psi per 1000 psi of inlet drop, the 2026-09-25 config audit
6reads TB 1031 at 17. So the ratio is a field with its source, and the inlet is the COPV's own
7blowdown -- never a fraction of the setpoint.
8
9What this module used to add and no longer does: a ±9 psi two-sine "ripple" (no measured spectrum
10exists; test T6 would provide one), an inlet running from 1.2x the setpoint down to 0.84x (a
11regulator delivering its setpoint from an inlet below it), a fixed 10 psi/1000 psi SPE labelled
12as one regulator's, and a "lockup droop" that lockup -- a no-flow condition -- does not produce.
13"""
14
15from __future__ import annotations
16
17from dataclasses import dataclass
18from typing import Any, Optional, Sequence, Tuple
19import numpy as np
20
21_PA_PER_PSI = 6894.757
22
23
24@dataclass(frozen=True)
26 """A dome regulator as the tank-pressure curve sees it.
27
28 supply_pressure_effect : outlet rise per unit inlet drop [-] (psi per psi). 0 = ideal.
29 source : where that number came from (datasheet, test); "" when unsourced.
30 min_differential_pa : inlet minus outlet below which the regulator cannot hold its outlet
31 (drops out). 0 enforces only that an outlet never exceeds its inlet.
32 """
33 supply_pressure_effect: float = 0.0
34 source: str = ""
35 min_differential_pa: float = 0.0
36
37
38class RegulatorDropout(ValueError):
39 """The inlet history falls below setpoint + minimum differential: the regulator cannot deliver
40 the curve being asked of it. Raised rather than drawing a flat line through it."""
41
42
43def get_feed_pressure_model(config: Any) -> str:
44 """Return ``blowdown`` or ``dome_regulated`` from design requirements."""
45 dr = getattr(config, "design_requirements", None)
46 if dr is None:
47 return "blowdown"
48 model = getattr(dr, "feed_pressure_model", None) or "blowdown"
49 return str(model).strip().lower()
50
51
52def regulator_from_config(config: Any) -> RegulatorModel:
53 """RegulatorModel from ``design_requirements.regulator_*``; unset fields are recorded
54 assumptions (ideal regulator), never silent constants."""
55 from engine.pipeline.assumptions import assume
56 dr = getattr(config, "design_requirements", None)
57 spe = getattr(dr, "regulator_supply_pressure_effect", None)
58 src = getattr(dr, "regulator_supply_pressure_effect_source", None) or ""
59 dmin_psi = getattr(dr, "regulator_min_differential_psi", None)
60 if spe is None:
61 # The regulator itself is unconfirmed (audit operator question 6). The stability design
62 # record names the Aqua 1092, whose datasheet gives 10 psi / 1000 psi; TB 1031 reads 17.
63 spe = assume("feed.regulator_supply_pressure_effect", 0.010, unit="psi/psi",
64 reason="design_requirements.regulator_supply_pressure_effect unset: Aqua 1092 "
65 "datasheet value (docs/stability/combustion_stability_physics.md ref 16); "
66 "the regulator is unconfirmed and TB 1031 reads 0.017")
67 if dmin_psi is None:
68 dmin_psi = assume("feed.regulator_min_differential_psi", 0.0, unit="psi",
69 reason="design_requirements.regulator_min_differential_psi unset: only "
70 "outlet <= inlet is enforced")
71 return RegulatorModel(float(spe), str(src), float(dmin_psi) * _PA_PER_PSI)
72
73
75 P_set_pa: float,
76 *,
77 burn_time_s: float,
78 n_points: int = 200,
79 P_inlet_pa: Optional[Sequence[float]] = None,
80 P_inlet_0_pa: Optional[float] = None,
81 P_inlet_end_pa: Optional[float] = None,
82 regulator: Optional[RegulatorModel] = None,
83) -> Tuple[np.ndarray, np.ndarray]:
84 """Eq. (6.2): regulated outlet = setpoint + SPE x (inlet drop since t = 0).
85
86 The inlet history is ``P_inlet_pa`` (one value per output point, e.g. the COPV blowdown trace)
87 or, failing that, a straight line from ``P_inlet_0_pa`` to ``P_inlet_end_pa``. With no inlet
88 history there is nothing for the SPE to act on and the curve is the flat setpoint.
89
90 Raises RegulatorDropout when the inlet falls below outlet + ``regulator.min_differential_pa``.
91 Returns (time_s, P_tank_pa).
92 """
93 n_points = max(2, int(n_points))
94 t = np.linspace(0.0, max(burn_time_s, 1e-6), n_points)
95 P_set = float(P_set_pa)
96 reg = regulator or RegulatorModel()
97
98 if P_inlet_pa is not None:
99 P_in = np.asarray(P_inlet_pa, dtype=float)
100 if P_in.shape != t.shape:
101 raise ValueError(f"P_inlet_pa has {P_in.size} points; the curve has {t.size}")
102 elif P_inlet_0_pa is not None and P_inlet_end_pa is not None:
103 P_in = float(P_inlet_0_pa) + (float(P_inlet_end_pa) - float(P_inlet_0_pa)) * (t / t[-1])
104 else:
105 return t, np.full_like(t, P_set)
106
107 P = P_set + float(reg.supply_pressure_effect) * (P_in[0] - P_in)
108 short = P_in - P < float(reg.min_differential_pa)
109 if np.any(short):
110 k = int(np.argmax(short))
111 raise RegulatorDropout(
112 f"regulator inlet {P_in[k] / _PA_PER_PSI:.0f} psia at t = {t[k]:.2f} s is below outlet "
113 f"{P[k] / _PA_PER_PSI:.0f} psia + minimum differential "
114 f"{reg.min_differential_pa / _PA_PER_PSI:.0f} psi: regulation is lost")
115 return t, P
116
117
119 initial_lox_pa: float,
120 initial_fuel_pa: float,
121 burn_time_s: float,
122 n_points: int = 200,
123 *,
124 copv_pressure_pa: Optional[Sequence[float]] = None,
125 copv_initial_pa: Optional[float] = None,
126 copv_end_pa: Optional[float] = None,
127 regulator: Optional[RegulatorModel] = None,
128) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
129 """LOX and fuel regulated manifold curves for Layer 2 / the time solver.
130
131 Both tanks hang off the same COPV, so both see the same inlet history: ``copv_pressure_pa``
132 (a trace, one value per point) or the ``copv_initial_pa``/``copv_end_pa`` pair. Without either
133 the curves are the flat setpoints.
134 """
135 kw = dict(burn_time_s=burn_time_s, n_points=n_points, P_inlet_pa=copv_pressure_pa,
136 P_inlet_0_pa=copv_initial_pa, P_inlet_end_pa=copv_end_pa, regulator=regulator)
137 t, P_O = generate_dome_regulated_pressure_curve(initial_lox_pa, **kw)
138 _, P_F = generate_dome_regulated_pressure_curve(initial_fuel_pa, **kw)
139 return t, P_O, P_F
#define max(a, b)
Definition Arduino.h:43
Tuple[np.ndarray, np.ndarray, np.ndarray] dome_regulated_tank_pair(float initial_lox_pa, float initial_fuel_pa, float burn_time_s, int n_points=200, *, Optional[Sequence[float]] copv_pressure_pa=None, Optional[float] copv_initial_pa=None, Optional[float] copv_end_pa=None, Optional[RegulatorModel] regulator=None)
RegulatorModel regulator_from_config(Any config)
Tuple[np.ndarray, np.ndarray] generate_dome_regulated_pressure_curve(float P_set_pa, *, float burn_time_s, int n_points=200, Optional[Sequence[float]] P_inlet_pa=None, Optional[float] P_inlet_0_pa=None, Optional[float] P_inlet_end_pa=None, Optional[RegulatorModel] regulator=None)