|
STAR
Cal STAR — firmware, DAQ server, and engine design
|
Classes | |
| class | Regulator |
| class | ChugStream |
| class | ChugChamber |
Functions | |
| complex | chug_open_loop (complex s, List[ChugStream] streams, ChugChamber chamber, *, bool with_regulator=True) |
| np.ndarray | _open_loop_grid (np.ndarray omega, List[ChugStream] streams, ChugChamber chamber, *, bool with_regulator=True) |
| complex | chug_characteristic (complex s, List[ChugStream] streams, ChugChamber chamber, *, bool with_regulator=True) |
| np.ndarray | _freq_grid_cached (float f_lo, float f_hi, int n) |
| np.ndarray | _freq_grid (float f_lo=2.0, float f_hi=2000.0, int n=200) |
| Tuple[np.ndarray, np.ndarray] | _negative_axis_crossings (np.ndarray omega, np.ndarray L) |
| Dict[str, float] | chug_margin_fast (List[ChugStream] streams, ChugChamber chamber, *, bool with_regulator=True, float f_lo=2.0, float f_hi=2000.0) |
| Tuple[float, float, float] | _solve_dominant_root (List[ChugStream] streams, ChugChamber chamber, *, bool with_regulator, float omega_seed) |
| Tuple[float, float, float] | _dominant_root (List[ChugStream] streams, ChugChamber chamber, Dict[str, float] fast_res, *, bool with_regulator=True) |
| str | _dominant_driver (List[ChugStream] streams, complex s) |
| float | damping_ratio (float sigma, float omega) |
| Tuple[float, float, float] | _solve_root_near (List[ChugStream] streams, ChugChamber chamber, complex s0, *, bool with_regulator=True) |
| float | mean_eta (List[ChugStream] streams) |
| List[Dict[str, float]] | chug_root_locus (List[ChugStream] streams, ChugChamber chamber, *, Optional[np.ndarray] eta_values=None, bool with_regulator=True, bool scale_design=False) |
| Dict[str, float] | chug_growth_rate (List[ChugStream] streams, ChugChamber chamber, *, bool with_regulator=True) |
Low-frequency (chug) stability: lumped feed <-> chamber <-> combustion model.
Implements the impedance-form characteristic equation [Phys §3.2, eq 3.3]:
F(s) = 1 + Y_ch(s) * sum_k exp(-s*tau_conv_k) / Z_feed_k(s) = 0
Y_ch(s) = K_c / (theta_c*s + 1) chamber transfer function (K_c = c*/A_t)
Z_feed_k = Z_reg_k(s) + I_k*s + R_k + 1/G_inj_k series feed+injector+regulator impedance
All quantities use the **mass-flow** through-variable convention (impedances in Pa·s/kg). Pure numeric
module — params come in as dataclasses; config extraction lives in the integration layer (analysis.py).
Two tiers (plan A2):
* ``chug_margin_fast`` — gain/phase-margin proxy from a 1-D frequency scan (no transcendental
root-find). For the Layer-1 inner loop. Calibrated to the **sign** of alpha.
* ``chug_growth_rate`` — complex root-find of (3.3) for the dominant (alpha, omega). For the report.
The regulator enters in **two separate roles** [Phys §3.2 / §6.1]:
* impedance ``Z_reg(s)`` inside ``Z_feed`` — shifts the poles / alpha (homogeneous stability);
* forcing bound ``max_excursion_pa`` — disturbance amplitude (reported separately, NOT a pole-shifter).
|
protected |
Heuristic: which feed term dominates |Z_feed| at the root -> the limiting physics.
Definition at line 324 of file chug.py.
References max.
Referenced by chug_growth_rate().
|
protected |
The most unstable root of F(s) = 0: seeded at every negative-real-axis crossing of L(iw) (``fast_res``, from ``chug_margin_fast``), the largest alpha kept. (alpha, omega, |F|).
Definition at line 308 of file chug.py.
References _solve_dominant_root().
Referenced by chug_growth_rate(), and chug_root_locus().
|
protected |
Definition at line 187 of file chug.py.
References _freq_grid_cached().
Referenced by chug_margin_fast().
|
protected |
Definition at line 181 of file chug.py.
Referenced by _freq_grid().
|
protected |
(omega_c, |L|_c) where L(iw) crosses the negative real axis: Im L changes sign with Re L < 0. Every such crossing is a phase of -pi - 2*pi*k. Testing the unwrapped phase against -pi alone (the old rule) misses k >= 1, where a slow second stream can put the loop's largest gain.
Definition at line 195 of file chug.py.
Referenced by chug_margin_fast().
|
protected |
Vectorised L(iw) over a whole frequency grid. Same computation as calling chug_open_loop once per point: every operation in Z_feed and Y_ch is element-wise in s, and the per-stream primitives (G_inj, inertance, resistance, regulator corner) do not depend on s at all. The per-point version recomputed all of them at each of the 200 grid points, which made this the dominant per-eval stability cost. Term order is kept identical to Z_feed/chug_open_loop deliberately, since floating-point addition is not associative. Agreement is ~1 ULP rather than bit-for-bit: numpy's complex exp takes a different code path on arrays than on scalars, which is far below any tolerance here but is not exactly zero.
Definition at line 138 of file chug.py.
References max.
Referenced by chug_margin_fast().
|
protected |
Newton/fsolve for the dominant complex root of F(s)=0 near omega_seed. Returns (alpha, omega, |F|).
Definition at line 277 of file chug.py.
References chug_characteristic().
Referenced by _dominant_root().
|
protected |
Single-seed Newton solve for the root of F(s)=0 nearest ``s0``. (alpha, omega, |F|). The continuation step of the root locus: each point seeds from its predecessor, so one fsolve per point is enough. ``_solve_dominant_root`` fans out over nine seeds because it has no predecessor to start from; doing that per locus point would cost ~40x for no extra accuracy.
Definition at line 351 of file chug.py.
References chug_characteristic().
Referenced by chug_root_locus().
| complex engine.pipeline.stability.chug.chug_characteristic | ( | complex | s, |
| List[ChugStream] | streams, | ||
| ChugChamber | chamber, | ||
| * | , | ||
| bool | with_regulator = True ) |
F(s) = 1 + L(s). Roots s = alpha + i*omega give chug frequency/growth rate.
Definition at line 170 of file chug.py.
References chug_open_loop().
Referenced by _solve_dominant_root(), and _solve_root_near().
| Dict[str, float] engine.pipeline.stability.chug.chug_growth_rate | ( | List[ChugStream] | streams, |
| ChugChamber | chamber, | ||
| * | , | ||
| bool | with_regulator = True ) |
Rich chug analysis: dominant growth rate alpha and frequency from root-find of (3.3). The root-find is seeded at EVERY negative-real-axis crossing of L(iw) and keeps the root with the largest alpha; seeding only at the -pi crossing found a stable root while a -3pi one grew. Returns dict: ``alpha`` [1/s], ``f_chug_hz``, ``zeta`` (= -alpha/|s|), ``margin`` (= 1+zeta), ``stable``, ``regulator_status``, ``alpha_no_reg`` (only when a regulator is modelled; None otherwise), ``driver``, ``residual``.
Definition at line 464 of file chug.py.
References _dominant_driver(), _dominant_root(), chug_margin_fast(), and damping_ratio().
| Dict[str, float] engine.pipeline.stability.chug.chug_margin_fast | ( | List[ChugStream] | streams, |
| ChugChamber | chamber, | ||
| * | , | ||
| bool | with_regulator = True, | ||
| float | f_lo = 2.0, | ||
| float | f_hi = 2000.0 ) |
Fast chug margin via the Nyquist gain margin of L(iw). No root-find. [Phys §3.2 fast form] Char. eq 1+L=0 => instability when L(iw) encircles -1. L is open-loop stable (chamber and feed poles are real and negative; delays add none), so the loop is stable when L stays inside the unit circle wherever it crosses the negative real axis: GM = 1 / max |L| over ALL those crossings (phase -pi, -3pi, ...), and GM < 1 means encirclement. Returns dict: ``gain_margin`` (>1 stable), ``stable`` (bool), ``f_chug_hz`` (frequency of the worst crossing, the chug-frequency estimate), ``crossings_hz`` (every negative-axis crossing, for seeding the root-find), ``phase_margin_deg``, ``margin`` (= gain_margin).
Definition at line 215 of file chug.py.
References _freq_grid(), _negative_axis_crossings(), _open_loop_grid(), and max.
Referenced by chug_growth_rate(), and chug_root_locus().
| complex engine.pipeline.stability.chug.chug_open_loop | ( | complex | s, |
| List[ChugStream] | streams, | ||
| ChugChamber | chamber, | ||
| * | , | ||
| bool | with_regulator = True ) |
Open-loop transfer L(s) = Y_ch(s) * sum_k exp(-s*tau_k)/Z_feed_k(s). Char. eq is 1 + L(s) = 0.
Definition at line 126 of file chug.py.
Referenced by chug_characteristic().
| List[Dict[str, float]] engine.pipeline.stability.chug.chug_root_locus | ( | List[ChugStream] | streams, |
| ChugChamber | chamber, | ||
| * | , | ||
| Optional[np.ndarray] | eta_values = None, | ||
| bool | with_regulator = True, | ||
| bool | scale_design = False ) |
Track the dominant chug pole through the s-plane as injector stiffness sweeps. This is a root locus in the textbook sense: ``eta_inj = dP_inj/Pc`` is the swept gain, and each returned point is the eigenvalue ``s = sigma + j*omega`` of the closed-loop characteristic equation ``1 + L(s) = 0`` at that gain. The imaginary axis is the stability boundary — the branch crosses it where the loop goes neutrally stable, and the crossing frequency is the chug frequency the engine would ring at. Solved by continuation from the softest injector upward, each point seeded on its predecessor, which is the standard way to follow a branch rather than re-discover it. Honest caveat: on every case tried so far (lags 0.8-9 ms, feed runs 0.08-1.5 m, eta from 0.02 to 1.2) a single fixed seed found the same branch, so the continuation is insurance against branch-hopping rather than a demonstrated fix for it. ``test_locus_is_continuous_in_frequency`` checks the OUTPUT is a branch; it does not, and cannot currently, distinguish the two seeding strategies. ``scale_design``: each value is the streams' mass-weighted mean stiffness, reached by scaling every stream's OWN drop by one factor, so the design (unequal drops and all) sits exactly on the branch at its own mean. Off, every stream is set to the same eta. Returns points in ascending ``eta`` with keys ``eta``, ``real``, ``imag``, ``f_hz``, ``zeta``. Points where the branch could not be followed are dropped, so the caller gets a clean polyline.
Definition at line 385 of file chug.py.
References _dominant_root(), _solve_root_near(), chug_margin_fast(), damping_ratio(), and mean_eta().
| float engine.pipeline.stability.chug.damping_ratio | ( | float | sigma, |
| float | omega ) |
Damping ratio of a complex pole ``s = sigma + j*omega``: ``zeta = -sigma / |s|``. This is the standard definition — the cosine of the pole's angle from the negative real axis — and it is what a constant-zeta ray on a root locus means. (An earlier version of this module reported ``-sigma/omega``, which is ``zeta/sqrt(1-zeta^2)``: indistinguishable below zeta ~ 0.1 and 15 % off by zeta = 0.5, so a pole plotted against its own reported zeta did not sit on the ray. Fixed here so the diagram and the number agree.)
Definition at line 336 of file chug.py.
Referenced by chug_growth_rate(), and chug_root_locus().
| float engine.pipeline.stability.chug.mean_eta | ( | List[ChugStream] | streams | ) |
Mass-flow-weighted injector stiffness of the streams.
Definition at line 379 of file chug.py.
References max.
Referenced by chug_root_locus().