1use super::{AnalyticalSolution, Point, SPEED_OF_SOUND};
7use num_complex::Complex64;
8use spec_math::Bessel;
9use std::f64::consts::PI;
10
11pub fn cylinder_scattering_2d(
54 wave_number: f64,
55 radius: f64,
56 num_terms: usize,
57 r_points: Vec<f64>,
58 theta_points: Vec<f64>,
59) -> AnalyticalSolution {
60 let ka = wave_number * radius;
61
62 let coefficients = compute_rigid_cylinder_coefficients(ka, num_terms);
64
65 let mut positions = Vec::new();
67 let mut pressure = Vec::new();
68
69 for &r in &r_points {
70 let kr = wave_number * r;
71
72 let hankels: Vec<Complex64> = (0..num_terms)
74 .map(|n| {
75 let n_i64 = n as i64;
76 Complex64::new(bessel_j(n_i64, kr), bessel_y(n_i64, kr))
77 })
78 .collect();
79
80 for &theta in &theta_points {
81 positions.push(Point::from_polar(r, theta));
82
83 let incident = Complex64::new((kr * theta.cos()).cos(), (kr * theta.cos()).sin());
85
86 let mut scattered = Complex64::new(0.0, 0.0);
88
89 for n in 0..num_terms {
90 let epsilon_n = if n == 0 { 1.0 } else { 2.0 };
91 let cos_term = (n as f64 * theta).cos();
92 let contribution = coefficients[n] * hankels[n];
93 scattered += contribution * (epsilon_n * cos_term);
94 }
95
96 pressure.push(incident + scattered);
97 }
98 }
99
100 let frequency = wave_number * SPEED_OF_SOUND / (2.0 * PI);
101
102 AnalyticalSolution {
103 name: format!("2D Cylinder Scattering (ka={:.2})", ka),
104 dimensions: 2,
105 positions,
106 pressure,
107 wave_number,
108 frequency,
109 metadata: serde_json::json!({
110 "radius": radius,
111 "ka": ka,
112 "num_terms": num_terms,
113 "boundary_condition": "rigid",
114 "r_points": r_points,
115 "theta_range": [theta_points.first(), theta_points.last()],
116 "regime": classify_regime_2d(ka),
117 }),
118 }
119}
120
121fn classify_regime_2d(ka: f64) -> &'static str {
123 if ka < 0.3 {
124 "Rayleigh (ka << 1)"
125 } else if ka < 3.0 {
126 "Resonance (ka ~ 1)"
127 } else {
128 "Geometric (ka >> 1)"
129 }
130}
131
132fn compute_rigid_cylinder_coefficients(ka: f64, num_terms: usize) -> Vec<Complex64> {
139 let mut coefficients = Vec::with_capacity(num_terms);
140
141 for n in 0..num_terms {
142 let n_i64 = n as i64;
143 let n_f64 = n as f64;
144
145 let jn = bessel_j(n_i64, ka);
147 let jn_minus_1 = if n > 0 {
148 bessel_j(n_i64 - 1, ka)
149 } else {
150 -bessel_j(1, ka) };
152 let jn_prime = jn_minus_1 - n_f64 / ka * jn;
153
154 let yn = bessel_y(n_i64, ka);
156 let yn_minus_1 = if n > 0 {
157 bessel_y(n_i64 - 1, ka)
158 } else {
159 -bessel_y(1, ka)
160 };
161 let yn_prime = yn_minus_1 - n_f64 / ka * yn;
162
163 let hankel_prime = Complex64::new(jn_prime, yn_prime);
164
165 let i_power_n = Complex64::new((n_f64 * PI / 2.0).cos(), (n_f64 * PI / 2.0).sin());
167
168 let a_n = -jn_prime / hankel_prime * i_power_n;
169
170 coefficients.push(a_n);
171 }
172
173 coefficients
174}
175
176fn bessel_j(n: i64, x: f64) -> f64 {
178 x.bessel_jv(n as f64)
179}
180
181fn bessel_y(n: i64, x: f64) -> f64 {
183 x.bessel_yv(n as f64)
184}
185
186pub fn cylinder_directivity_2d(
195 wave_number: f64,
196 radius: f64,
197 num_terms: usize,
198 theta_points: Vec<f64>,
199) -> Vec<Complex64> {
200 let ka = wave_number * radius;
201 let coefficients = compute_rigid_cylinder_coefficients(ka, num_terms);
202
203 theta_points
204 .iter()
205 .map(|&theta| {
206 let mut directivity = Complex64::new(0.0, 0.0);
207
208 for (n, coeff) in coefficients.iter().enumerate().take(num_terms) {
209 let epsilon_n = if n == 0 { 1.0 } else { 2.0 };
210 let cos_term = (n as f64 * theta).cos();
211 directivity += coeff * (epsilon_n * cos_term);
212 }
213
214 directivity
215 })
216 .collect()
217}
218
219pub fn cylinder_scattering_cross_section_2d(
228 wave_number: f64,
229 radius: f64,
230 num_terms: usize,
231) -> f64 {
232 let ka = wave_number * radius;
233 let coefficients = compute_rigid_cylinder_coefficients(ka, num_terms);
234
235 let mut sum_sq = 0.0;
236 for (n, a_n) in coefficients.iter().enumerate() {
237 let epsilon_n = if n == 0 { 1.0 } else { 2.0 };
238 sum_sq += epsilon_n * a_n.norm_sqr();
239 }
240
241 4.0 / wave_number * sum_sq
242}
243
244pub fn plane_wave_2d(
255 wave_number: f64,
256 direction: f64,
257 x_points: Vec<f64>,
258 y_points: Vec<f64>,
259) -> AnalyticalSolution {
260 let cos_theta = direction.cos();
261 let sin_theta = direction.sin();
262
263 let mut positions = Vec::new();
264 let mut pressure = Vec::new();
265
266 for &x in &x_points {
267 for &y in &y_points {
268 positions.push(Point::new_2d(x, y));
269
270 let phase = wave_number * (x * cos_theta + y * sin_theta);
271 pressure.push(Complex64::new(phase.cos(), phase.sin()));
272 }
273 }
274
275 let frequency = wave_number * SPEED_OF_SOUND / (2.0 * PI);
276
277 AnalyticalSolution {
278 name: format!("2D Plane Wave (k={}, θ={:.2})", wave_number, direction),
279 dimensions: 2,
280 positions,
281 pressure,
282 wave_number,
283 frequency,
284 metadata: serde_json::json!({
285 "direction": direction,
286 "direction_vector": [cos_theta, sin_theta],
287 "wavelength": 2.0 * PI / wave_number,
288 }),
289 }
290}
291
292#[cfg(test)]
293mod tests {
294 use super::*;
295 use approx::assert_abs_diff_eq;
296
297 #[test]
298 fn test_cylinder_low_frequency() {
299 let k = 0.1;
301 let a = 1.0;
302
303 let solution = cylinder_scattering_2d(k, a, 10, vec![2.0], vec![0.0, PI / 2.0, PI]);
304
305 for p in &solution.pressure {
308 assert!(p.norm() > 0.5); assert!(p.norm() < 2.0); }
311 }
312
313 #[test]
314 fn test_cylinder_boundary_condition() {
315 let k = 2.0;
317 let a = 1.0;
318
319 let solution = cylinder_scattering_2d(
320 k,
321 a,
322 30,
323 vec![a], (0..36).map(|i| i as f64 * 2.0 * PI / 36.0).collect(),
325 );
326
327 for p in &solution.pressure {
329 assert!(p.norm().is_finite());
330 }
331 }
332
333 #[test]
334 fn test_symmetry() {
335 let k = 1.0;
337 let a = 1.0;
338
339 let solution = cylinder_scattering_2d(k, a, 20, vec![2.0], vec![PI / 4.0, -PI / 4.0]);
340
341 assert_abs_diff_eq!(
343 solution.pressure[0].norm(),
344 solution.pressure[1].norm(),
345 epsilon = 1e-6
346 );
347 }
348
349 #[test]
350 fn test_bessel_functions() {
351 let x = 1.0;
353
354 assert_abs_diff_eq!(bessel_j(0, x), 0.7651976865579666, epsilon = 1e-10);
356
357 assert_abs_diff_eq!(bessel_j(1, x), 0.4400505857449335, epsilon = 1e-10);
359 }
360
361 #[test]
362 fn test_scattering_cross_section() {
363 let k = 1.0;
365 let a = 1.0;
366
367 let sigma = cylinder_scattering_cross_section_2d(k, a, 30);
368 assert!(sigma > 0.0);
369 assert!(sigma.is_finite());
370 }
371
372 #[test]
373 fn test_plane_wave_2d() {
374 let k = 1.0;
375 let direction = 0.0; let solution = plane_wave_2d(k, direction, vec![0.0, 1.0, 2.0], vec![0.0]);
378
379 assert_abs_diff_eq!(solution.pressure[0].re, 1.0, epsilon = 1e-10);
381
382 for p in &solution.pressure {
384 assert_abs_diff_eq!(p.norm(), 1.0, epsilon = 1e-10);
385 }
386 }
387}