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.
48 1-D P1 FEM: with A linear over each element (endpoint values A1, A2, length h),
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)
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.
56 xs = np.linspace(0.0, contour.x_end, n_elements + 1)
57 A = np.pi * contour.r_wall(xs) ** 2
61 for k
in range(n_elements):
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)
73 freqs = freqs[freqs > 0.05 * c_sound / (2.0 * contour.x_end)]
74 return [float(f)
for f
in freqs[:n_modes]]
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).
81 breaks = sorted({0.0,
min(contour.L_cyl, contour.x_end),
82 min(contour.x_cone_end, contour.x_end), contour.x_end})
84 for a, b
in zip(breaks[:-1], breaks[1:]):
87 val, _err = quad(
lambda x: np.pi * float(contour.r_wall(x)) ** 2, a, b, limit=200)