STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
contour_checks.py
Go to the documentation of this file.
1"""Verification helpers for the P1 contour->mesh path (supplementary to the paper's
2V1-V6 ladder; these guard the *geometry* machinery specifically, before the V4
3cross-code benchmark exists).
4
5Two independent references are built here:
6
71. ``webster_modes`` — Webster's horn equation, the 1-D limit of the thermoacoustic
8 Helmholtz equation (paper Eq. 6, passive, uniform c) in a duct of slowly varying
9 cross-section A(x). Derivation: assume p_hat uniform over each cross-section
10 (valid when the wall slope is small and the frequency is below the first
11 transverse cutoff), integrate Eq. 6 over the cross-section, and use the rigid-wall
12 condition to drop the wall flux:
13
14 d/dx ( A(x) c^2 dp_hat/dx ) + omega^2 A(x) p_hat = 0, p_hat'(0)=p_hat'(L)=0.
15
16 This is NOT exact for our 2-D domains — its error is O(wall slope^2) — so it is
17 used as a cross-check on gently tapered contours (10 deg cone: slope^2 ~ 0.03),
18 where sub-1% agreement is expected, and deliberately NOT on the 45-deg hardware
19 contour (slope^2 = 1), where self-convergence is the right check instead.
20 Discretized by 1-D P1 FEM with A(x) linear per element (exact element integrals),
21 on a grid fine enough (thousands of elements) that its own discretization error is
22 negligible against the model error being tolerated.
23
242. ``revolved_volume`` — the exact volume of the revolved contour by adaptive
25 quadrature of pi * r_wall(x)^2 dx (piecewise-analytic integrand, split at the
26 segment breaks). The FEM mesh must reproduce this through the identity
27 2*pi*sum_ij(M2_ij) = 2*pi*int r dOmega = Volume (rows of M2 sum shape functions
28 to 1) — a direct test that the mapped mesh covers exactly the intended solid of
29 revolution, independent of any eigenvalue.
30"""
31
32from __future__ import annotations
33
34from typing import List
35
36import numpy as np
37from scipy import linalg as sla
38from scipy.integrate import quad
39
40from engine.stability_hifi.acoustics.contour import ChamberAcousticContour
41
42
43def webster_modes(contour: ChamberAcousticContour, c_sound: float, n_modes: int,
44 n_elements: int = 4000) -> List[float]:
45 """Lowest ``n_modes`` longitudinal frequencies [Hz] of the Webster horn equation
46 on [0, x_end] with rigid ends, A(x) = pi * r_wall(x)^2.
47
48 1-D P1 FEM: with A linear over each element (endpoint values A1, A2, length h),
49
50 K_e = c^2 * (A1+A2)/2 / h * [[1, -1], [-1, 1]] (A linear, p' const: exact)
51 M_e = h/12 * [[3*A1 + A2, A1 + A2], [A1 + A2, A1 + 3*A2]] (cubic integrand: exact)
52
53 giving the symmetric GEVP K p = omega^2 M p; rigid ends are natural (no boundary
54 term). Dense solve — a 1-D problem of a few thousand nodes is trivial.
55 """
56 xs = np.linspace(0.0, contour.x_end, n_elements + 1)
57 A = np.pi * contour.r_wall(xs) ** 2
58 n = len(xs)
59 K = np.zeros((n, n))
60 M = np.zeros((n, n))
61 for k in range(n_elements):
62 h = xs[k + 1] - xs[k]
63 A1, A2 = A[k], A[k + 1]
64 Ke = c_sound ** 2 * 0.5 * (A1 + A2) / h * np.array([[1.0, -1.0], [-1.0, 1.0]])
65 Me = h / 12.0 * np.array([[3 * A1 + A2, A1 + A2], [A1 + A2, A1 + 3 * A2]])
66 K[k:k + 2, k:k + 2] += Ke
67 M[k:k + 2, k:k + 2] += Me
68 w2 = sla.eigh(K, M, eigvals_only=True)
69 w2 = np.clip(w2, 0.0, None)
70 freqs = np.sqrt(w2) / (2.0 * np.pi)
71 # The all-Neumann problem has one exact zero eigenvalue (uniform pressure — same
72 # trivial mode V1 excludes); discard everything far below the 1L scale c/(2L).
73 freqs = freqs[freqs > 0.05 * c_sound / (2.0 * contour.x_end)]
74 return [float(f) for f in freqs[:n_modes]]
75
76
77def revolved_volume(contour: ChamberAcousticContour) -> float:
78 """Exact volume [m^3] of the revolved acoustic domain, by adaptive quadrature of
79 pi * r_wall^2 dx split at the segment breaks (integrand is analytic per segment).
80 """
81 breaks = sorted({0.0, min(contour.L_cyl, contour.x_end),
82 min(contour.x_cone_end, contour.x_end), contour.x_end})
83 total = 0.0
84 for a, b in zip(breaks[:-1], breaks[1:]):
85 if b <= a:
86 continue
87 val, _err = quad(lambda x: np.pi * float(contour.r_wall(x)) ** 2, a, b, limit=200)
88 total += val
89 return float(total)
#define min(a, b)
Definition Arduino.h:42
float revolved_volume(ChamberAcousticContour contour)
List[float] webster_modes(ChamberAcousticContour contour, float c_sound, int n_modes, int n_elements=4000)