STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
spec.py
Go to the documentation of this file.
1"""The ``MeanFlowSpec`` interface (paper Section V.A).
2
3Every eigensolver input passes through this one container, so mean-flow fidelity
4(synthetic uniform field -> parametric generator -> warm-started SU2 RANS) can be
5swapped without touching the mesh, assembly, or eigensolver code. P0 only uses the
6synthetic generator at the bottom of this file.
7"""
8
9from __future__ import annotations
10
11from dataclasses import dataclass, field
12from typing import List, Optional
13
14import numpy as np
15
16from engine.stability_hifi.acoustics.mesh import MeridionalMesh, cylinder_mesh, two_zone_duct_mesh
17
18
19@dataclass
21 """One flame reference point/ring for the flame-response closure (Eq. 7).
22
23 Not used by the passive verification cases (V1-V2); populated once an active
24 flame closure is added (V3 onward).
25 """
26 x_ref: float
27 r_ref: float
28 n_gain: float # interaction index (n_p or n_u)
29 tau: float # time lag [s]
30 normal: tuple = (1.0, 0.0) # reference-plane normal, for velocity coupling (Eq. 7b)
31
32
33@dataclass
35 """Traceability metadata: every eigenvalue should be traceable to its mean flow."""
36 generator: str
37 propellants: Optional[str] = None
38 Pc: Optional[float] = None
39 MR: Optional[float] = None
40 notes: str = ""
41
42
43@dataclass
45 """Scalar (Helmholtz-tier) mean-flow fields on a meridional mesh (Eq. 9-10).
46
47 ``c`` is the paper's per-node sound-speed field (Section V.A) — the right
48 representation for any *smoothly varying* mean flow, which is what every real
49 generator (parametric or CFD) produces. ``c_element``, an EngineDesign-specific
50 addition not in the paper, is an escape hatch for synthetic verification cases that
51 need a genuinely *discontinuous* material property a shared node cannot represent
52 (verification case V2's temperature-jump duct is the only user of it in P0). When
53 present, ``assemble_passive`` should be called with ``c_element`` instead of ``c``;
54 ``c`` is still filled in (as a defensible smooth proxy) purely so the dataclass
55 contract — "every field the paper lists is always populated" — holds even here.
56 """
57 mesh: MeridionalMesh
58 rho: np.ndarray # (n_nodes,) mean density [kg/m^3]
59 c: np.ndarray # (n_nodes,) sound speed [m/s]
60 gamma: np.ndarray # (n_nodes,) specific heat ratio [-]
61 qbar: np.ndarray # (n_nodes,) mean volumetric heat release [W/m^3]
62 ubar: Optional[np.ndarray] = None # (n_nodes, 2), None at Helmholtz tier
63 refs: List[FlameReference] = field(default_factory=list)
64 meta: Optional[ProvenanceRecord] = None
65 c_element: Optional[np.ndarray] = None # (n_tri,), set only for discontinuous test cases (see above)
66
67
68def synthetic_uniform_cylinder(L: float, R: float, nx: int, nr: int,
69 *, c_sound: float, gamma: float = 1.2,
70 rho: float = 4.0) -> MeanFlowSpec:
71 """Uniform-property closed cylinder mean flow (verification case V1).
72
73 Constant sound speed / density / gamma everywhere, no heat release (passive) — the
74 exact configuration the analytic mode formula in Section VII.A (case V1) assumes.
75 """
76 mesh = cylinder_mesh(L, R, nx, nr)
77 n = mesh.n_nodes
78 return MeanFlowSpec(
79 mesh=mesh,
80 rho=np.full(n, rho),
81 c=np.full(n, c_sound),
82 gamma=np.full(n, gamma),
83 qbar=np.zeros(n),
84 ubar=None,
85 refs=[],
86 meta=ProvenanceRecord(generator="synthetic_uniform_cylinder",
87 notes="V1 analytic-cylinder verification case; no real propellant/Pc/MR"),
88 )
89
90
91def synthetic_two_zone_duct(L1: float, L2: float, R: float, nx1: int, nx2: int, nr: int,
92 *, c1: float, c2: float, gamma: float = 1.2,
93 rho: float = 4.0) -> MeanFlowSpec:
94 """Two-zone duct with a sound-speed (temperature) jump at x=L1 (verification case V2).
95
96 ``c1`` applies on ``[0, L1]``, ``c2`` on ``[L1, L1+L2]`` — a step discontinuity, not
97 a smooth profile, which is exactly why this uses ``c_element`` (see ``MeanFlowSpec``
98 docstring) rather than the ordinary per-node ``c`` field. ``rho`` is passed through
99 uniformly for now; density does jump physically alongside sound speed at a real
100 temperature interface (rho = p/(R*T), same p, different T), but the passive
101 Helmholtz operator (Eq. 10) only ever uses ``c``, so it plays no role in this case.
102 """
103 mesh, zone = two_zone_duct_mesh(L1, L2, R, nx1, nx2, nr)
104 n = mesh.n_nodes
105 c_element = np.where(zone == 1, c1, c2)
106 # Per-node c is not physically meaningful across the jump (see docstring); fill it
107 # with each node's "home" zone value (nodes exactly on the interface get c1) purely
108 # so the dataclass field is populated with *something* traceable, never used by the solve.
109 node_zone = np.ones(n, dtype=np.int64)
110 right_of_interface = mesh.nodes[:, 0] > L1 + 1e-9
111 node_zone[right_of_interface] = 2
112 c_node_proxy = np.where(node_zone == 1, c1, c2)
113 return MeanFlowSpec(
114 mesh=mesh,
115 rho=np.full(n, rho),
116 c=c_node_proxy,
117 c_element=c_element,
118 gamma=np.full(n, gamma),
119 qbar=np.zeros(n),
120 ubar=None,
121 refs=[],
122 meta=ProvenanceRecord(generator="synthetic_two_zone_duct",
123 notes=f"V2 temperature-jump verification case; c1={c1}, c2={c2}, interface at x={L1}"),
124 )
MeanFlowSpec synthetic_uniform_cylinder(float L, float R, int nx, int nr, *, float c_sound, float gamma=1.2, float rho=4.0)
Definition spec.py:70
MeanFlowSpec synthetic_two_zone_duct(float L1, float L2, float R, int nx1, int nx2, int nr, *, float c1, float c2, float gamma=1.2, float rho=4.0)
Definition spec.py:93