|
STAR
Cal STAR — firmware, DAQ server, and engine design
|
Classes | |
| class | V3Result |
Functions | |
| complex | dispersion_G (complex lam, *, float c, float L, float x_f, float y_noz, float beta, float tau) |
| complex | dispersion_root (complex lam_seed, *, float c, float L, float x_f, float y_noz, float beta, float tau, int iters=60, float max_step=20.0) |
| V3Result | run_case (*, float c, float L, float R, float x_f, float gamma, float Mbar_e, float flame_gain, float tau, int nx1, int nx2, int nr, bool rigid_end=False) |
Verification case V3: duct with a compact n-tau flame, closed/choked ends
(paper Section VII.A: "active flame term, delay nonlinearity, complex lambda,
all three solvers agree").
This is the first case where the eigenproblem is genuinely NONLINEAR in lambda
(the flame delay exp(-lambda*tau), Eq. 7a/11), so it is the first exercise of the
paper's Section IV solver hierarchy: the frozen-delay fixed point (Algorithm 1) and
the bordered Newton polish (Section IV.C). (The third solver of the hierarchy, Beyn
contour integration, is a completeness AUDIT rather than a mode solver and is deferred
to P3 per the paper's phasing — so "all three solvers" is, at P0, these two. Flagged
rather than silently reinterpreted.)
Configuration
-------------
A duct of length L, uniform sound speed c, rigid ("closed") end at x=0, compact
choked-nozzle admittance y_noz = (gamma-1)*Mbar_e/2 (Appendix C) at x=L, and a compact
(delta-in-x, uniform-in-r) pressure-coupled flame at x = x_f:
q_hat(x) = gain * exp(-lambda*tau) * p_hat(x_f) * delta(x - x_f)
i.e. Eq. (7a) lumped to a single reference point and a single flame sheet, with
``gain`` absorbing (gamma-1) * n_p * (qbar_dot/pbar) * (flame axial thickness)
[units m/s]. The flame is uniform across the cross-section, so for the r-independent
longitudinal modes compared here the 2-D axisymmetric FEM and the 1-D analytic model
below describe *identical* physics.
Discrete form solved (Eq. 11, rank-1 flame):
N(lambda) p = [K + lambda*C + lambda^2*M2 - lambda*gain*exp(-lambda*tau)*g b^T] p = 0
with b = point sample of p at (x_f, 0) and g = the r-weighted disk load at x_f
(two DIFFERENT vectors; see acoustics/assembly.py for why conflating them is a bug).
Analytic reference (derived, not from the paper)
------------------------------------------------
Zone solutions satisfying the end conditions, with s = lambda/c:
zone 1 (0..x_f): P(x) = cosh(s*x) [P'(0)=0, rigid]
zone 2 (x_f..L): Q(x) = r2*exp(s*(x-L)) + exp(-s*(x-L))
The admittance end condition p'(L) = -s*y_noz*p(L) (from Eq. 8 with y constant) gives
r2 = (1 - y_noz) / (1 + y_noz) [lambda-independent for a compact nozzle].
CAUTION (documented wrong turn): an earlier draft had r2 = (y-s')/(y+s') -> the
NEGATIVE of the correct value. The passive limit exposes it: with the wrong sign the
flame-off roots do not reproduce the independently verified tanh(s*L) = -y_noz
spectrum (sigma ~ -y*c/L, f ~ n*c/2L). Always check the passive limit of an
active-flame dispersion relation first.
At the flame, p is continuous and the slope jumps. The jump follows from the FEM's own
strong form (integrate lambda^2*p - d/dx(c^2 p') = lambda*gain*e^{-lambda*tau}
* p(x_f) * delta(x-x_f) across the sheet):
[p']_{x_f-}^{x_f+} = -lambda * beta * exp(-lambda*tau) * p(x_f),
beta = gain / c^2.
CAUTION (the bug that cost the most time): beta is gain/c^2, NOT gain*(R^2/2)/c^2.
The r-weighted disk load g sums to R^2/2, but the r dOmega measure multiplies every
OTHER matrix (K, M2) by the same R^2/2, so it cancels exactly in the 1-D reduction.
Double-counting it made the analytic flame coupling 1/(R^2/2) = 5000x too weak, which
presented as "FEM growth rate 5000x larger than analytic" — with frequencies agreeing
to 0.003%, the signature of a coupling-scale error rather than a discretization or
solver error. (The FEM answer was mesh-converged and confirmed independently by
first-order eigenvalue perturbation theory of the discrete system; the closed-form
sensitivity for the fundamental mode of the rigid-rigid case,
d(lambda)/d(gain) = exp(-lambda0*tau) * cos^2(pi*x_f/L) / L, in which R cancels
entirely, is re-checked in the test suite.)
Eliminating the two amplitudes with the two interface conditions gives the dispersion
relation, root-found in pole-free product form (V2's lesson):
G(lambda) = P'(x_f) Q(x_f) - Q'(x_f) P(x_f)
- lambda*beta*exp(-lambda*tau) * P(x_f) Q(x_f) = 0.
Phase convention worth knowing (physics finding, verified numerically three ways):
with the pure-delay coupling (7a), the Rayleigh driving of a mode goes as
+cos(omega*tau) — in-phase heat release drives, anti-phase damps — because the
cycle-averaged p'q' is |p_hat|^2 * cos(omega*tau): the REAL part of the transfer. The
lumped model (engine/pipeline/stability/acoustic.py) uses the DIFFERENCE form of the
Crocco response, n*[p'(t) - p'(t-tau)], whose transfer n*(1-e^{-i*omega*tau}) has real
part n*(1 - cos(omega*tau)) -- driving everywhere, most at omega*tau = pi. (It took the
imaginary part, n*sin, until 2026-09-26; that is the quadrature component and does no
work.) The two forms differ by the instantaneous n*p'(t) term, so their tau-bands are
not the same; comparisons between this framework and the lumped tier must translate
conventions first.
| complex engine.stability_hifi.validation.v3_ntau_duct.dispersion_G | ( | complex | lam, |
| * | , | ||
| float | c, | ||
| float | L, | ||
| float | x_f, | ||
| float | y_noz, | ||
| float | beta, | ||
| float | tau ) |
Pole-free dispersion residual G(lambda) (module docstring). Root <=> eigenvalue.
Definition at line 125 of file v3_ntau_duct.py.
Referenced by dispersion_root().
| complex engine.stability_hifi.validation.v3_ntau_duct.dispersion_root | ( | complex | lam_seed, |
| * | , | ||
| float | c, | ||
| float | L, | ||
| float | x_f, | ||
| float | y_noz, | ||
| float | beta, | ||
| float | tau, | ||
| int | iters = 60, | ||
| float | max_step = 20.0 ) |
Bounded-step complex Newton on G, seeded near the expected root. Deliberately LOCAL: we want the dispersion root nearest the seed (the FEM eigenvalue being cross-checked), and an unbounded/global root-finder can wander to a distant root and report success — scipy.fsolve did exactly that during development, converging to an unrelated real root thousands of rad/s away. Guaranteed-complete root surveys are the job of the Beyn audit tier (P3), not of a verification cross-check. The derivative is numerical (central difference, step h=1e-2 — G is smooth on O(1) lambda scales, so this is far below its variation).
Definition at line 138 of file v3_ntau_duct.py.
References dispersion_G().
Referenced by run_case().
| V3Result engine.stability_hifi.validation.v3_ntau_duct.run_case | ( | * | , |
| float | c, | ||
| float | L, | ||
| float | R, | ||
| float | x_f, | ||
| float | gamma, | ||
| float | Mbar_e, | ||
| float | flame_gain, | ||
| float | tau, | ||
| int | nx1, | ||
| int | nx2, | ||
| int | nr, | ||
| bool | rigid_end = False ) |
Full V3 run: FEM NLEVP (both solvers) vs the 1-D dispersion relation. ``rigid_end=True`` replaces the choked-nozzle admittance at x=L with a rigid wall (y_noz = 0) — the pure-flame sub-case, useful for isolating flame handling from boundary-damping handling when something disagrees. The mesh is built with ``two_zone_duct_mesh`` even though there is no material jump, purely to guarantee an exact node column at x = x_f for the disk load (see ``disk_load_vector``'s docstring).
Definition at line 164 of file v3_ntau_duct.py.
References dispersion_root().