1use super::{AnalyticalSolution, Point};
6use num_complex::Complex64;
7use std::f64::consts::PI;
8
9pub const SPEED_OF_SOUND: f64 = 343.0;
11
12pub fn plane_wave_1d(
35 wave_number: f64,
36 x_min: f64,
37 x_max: f64,
38 num_points: usize,
39) -> AnalyticalSolution {
40 assert!(num_points >= 2, "Need at least 2 points");
41
42 let dx = (x_max - x_min) / (num_points - 1) as f64;
43
44 let positions: Vec<Point> = (0..num_points)
45 .map(|i| Point::new_1d(x_min + i as f64 * dx))
46 .collect();
47
48 let pressure: Vec<Complex64> = positions
49 .iter()
50 .map(|p| {
51 let kx = wave_number * p.x;
52 Complex64::new(kx.cos(), kx.sin())
53 })
54 .collect();
55
56 let frequency = wave_number * SPEED_OF_SOUND / (2.0 * PI);
57
58 AnalyticalSolution {
59 name: format!("1D Plane Wave (k={})", wave_number),
60 dimensions: 1,
61 positions,
62 pressure,
63 wave_number,
64 frequency,
65 metadata: serde_json::json!({
66 "x_min": x_min,
67 "x_max": x_max,
68 "num_points": num_points,
69 "speed_of_sound": SPEED_OF_SOUND,
70 "wavelength": 2.0 * PI / wave_number,
71 }),
72 }
73}
74
75pub fn standing_wave_1d(
98 wave_number: f64,
99 x_min: f64,
100 x_max: f64,
101 num_points: usize,
102) -> AnalyticalSolution {
103 assert!(num_points >= 2, "Need at least 2 points");
104
105 let dx = (x_max - x_min) / (num_points - 1) as f64;
106
107 let positions: Vec<Point> = (0..num_points)
108 .map(|i| Point::new_1d(x_min + i as f64 * dx))
109 .collect();
110
111 let pressure: Vec<Complex64> = positions
112 .iter()
113 .map(|p| {
114 let kx = wave_number * p.x;
115 Complex64::new(0.0, kx.sin())
117 })
118 .collect();
119
120 let frequency = wave_number * SPEED_OF_SOUND / (2.0 * PI);
121
122 AnalyticalSolution {
123 name: format!("1D Standing Wave (k={})", wave_number),
124 dimensions: 1,
125 positions,
126 pressure,
127 wave_number,
128 frequency,
129 metadata: serde_json::json!({
130 "x_min": x_min,
131 "x_max": x_max,
132 "num_points": num_points,
133 "wavelength": 2.0 * PI / wave_number,
134 "node_spacing": PI / wave_number,
135 }),
136 }
137}
138
139pub fn damped_wave_1d(
163 wave_number: f64,
164 absorption: f64,
165 x_min: f64,
166 x_max: f64,
167 num_points: usize,
168) -> AnalyticalSolution {
169 assert!(num_points >= 2, "Need at least 2 points");
170 assert!(absorption >= 0.0, "Absorption must be non-negative");
171
172 let dx = (x_max - x_min) / (num_points - 1) as f64;
173
174 let positions: Vec<Point> = (0..num_points)
175 .map(|i| Point::new_1d(x_min + i as f64 * dx))
176 .collect();
177
178 let pressure: Vec<Complex64> = positions
179 .iter()
180 .map(|p| {
181 let damping = (-absorption * p.x).exp();
183 let wave = Complex64::new((wave_number * p.x).cos(), (wave_number * p.x).sin());
184 damping * wave
185 })
186 .collect();
187
188 let frequency = wave_number * SPEED_OF_SOUND / (2.0 * PI);
189
190 AnalyticalSolution {
191 name: format!("1D Damped Wave (k={}, α={})", wave_number, absorption),
192 dimensions: 1,
193 positions,
194 pressure,
195 wave_number,
196 frequency,
197 metadata: serde_json::json!({
198 "x_min": x_min,
199 "x_max": x_max,
200 "absorption": absorption,
201 "penetration_depth": if absorption > 0.0 { 1.0 / absorption } else { f64::INFINITY },
202 "quality_factor": wave_number / (2.0 * absorption),
203 }),
204 }
205}
206
207pub fn helmholtz_1d_mode(
222 wave_number: f64,
223 length: f64,
224 mode_number: usize,
225 num_points: usize,
226) -> AnalyticalSolution {
227 assert!(num_points >= 2, "Need at least 2 points");
228 assert!(mode_number >= 1, "Mode number must be >= 1");
229
230 let n = mode_number as f64;
231 let kn = n * PI / length;
232
233 let denom = wave_number * wave_number - kn * kn;
235 assert!(
236 denom.abs() > 1e-10,
237 "Resonance condition: k ≈ nπ/L, solution unbounded"
238 );
239
240 let dx = length / (num_points - 1) as f64;
241
242 let positions: Vec<Point> = (0..num_points)
243 .map(|i| Point::new_1d(i as f64 * dx))
244 .collect();
245
246 let pressure: Vec<Complex64> = positions
247 .iter()
248 .map(|p| {
249 let u = (n * PI * p.x / length).sin() / denom;
250 Complex64::new(u, 0.0)
251 })
252 .collect();
253
254 let frequency = wave_number * SPEED_OF_SOUND / (2.0 * PI);
255
256 AnalyticalSolution {
257 name: format!("1D Helmholtz Mode (k={}, n={})", wave_number, mode_number),
258 dimensions: 1,
259 positions,
260 pressure,
261 wave_number,
262 frequency,
263 metadata: serde_json::json!({
264 "length": length,
265 "mode_number": mode_number,
266 "resonant_wavenumber": kn,
267 "detuning": wave_number - kn,
268 }),
269 }
270}
271
272#[cfg(test)]
273mod tests {
274 use super::*;
275 use approx::assert_abs_diff_eq;
276
277 #[test]
278 fn test_plane_wave_1d() {
279 let k = 1.0;
280 let solution = plane_wave_1d(k, 0.0, 2.0 * PI, 100);
281
282 assert_abs_diff_eq!(solution.pressure[0].re, 1.0, epsilon = 1e-10);
284 assert_abs_diff_eq!(solution.pressure[0].im, 0.0, epsilon = 1e-10);
285
286 let last_idx = solution.pressure.len() - 1;
288 assert_abs_diff_eq!(solution.pressure[last_idx].re, 1.0, epsilon = 1e-6);
289 assert_abs_diff_eq!(solution.pressure[last_idx].im, 0.0, epsilon = 1e-6);
290 }
291
292 #[test]
293 fn test_plane_wave_unit_magnitude() {
294 let k = 2.5;
295 let solution = plane_wave_1d(k, 0.0, 10.0, 50);
296
297 for p in &solution.pressure {
299 assert_abs_diff_eq!(p.norm(), 1.0, epsilon = 1e-10);
300 }
301 }
302
303 #[test]
304 fn test_standing_wave_nodes() {
305 let k = 1.0;
306 let solution = standing_wave_1d(k, 0.0, PI, 200);
307
308 assert_abs_diff_eq!(solution.pressure[0].im, 0.0, epsilon = 1e-10);
310
311 let dx = PI / 199.0;
313 let target_idx = ((PI / 2.0) / dx).round() as usize;
314 assert_abs_diff_eq!(solution.pressure[target_idx].im, 1.0, epsilon = 1e-4);
315 }
316
317 #[test]
318 fn test_damped_wave_decay() {
319 let k = 1.0;
320 let alpha = 0.1;
321 let solution = damped_wave_1d(k, alpha, 0.0, 10.0, 100);
322
323 let mag_start = solution.pressure[0].norm();
325 let mag_end = solution.pressure[solution.pressure.len() - 1].norm();
326
327 let expected_ratio = (-alpha * 10.0).exp();
328 assert_abs_diff_eq!(mag_end / mag_start, expected_ratio, epsilon = 1e-6);
329 }
330
331 #[test]
332 fn test_wavelength() {
333 let k = 2.0;
334 let wavelength = 2.0 * PI / k;
335
336 let solution = plane_wave_1d(k, 0.0, wavelength, 100);
337
338 let p0 = solution.pressure[0];
340 let p_end = solution.pressure[solution.pressure.len() - 1];
341
342 assert_abs_diff_eq!(p0.re, p_end.re, epsilon = 1e-6);
343 assert_abs_diff_eq!(p0.im, p_end.im, epsilon = 1e-6);
344 }
345
346 #[test]
347 fn test_helmholtz_1d_mode() {
348 let k = 1.0;
349 let l = PI; let n = 2; let solution = helmholtz_1d_mode(k, l, n, 100);
353
354 assert_abs_diff_eq!(solution.pressure[0].re, 0.0, epsilon = 1e-10);
356 let last = solution.pressure.len() - 1;
357 assert_abs_diff_eq!(solution.pressure[last].re, 0.0, epsilon = 1e-6);
358
359 let mid = solution.pressure.len() / 4; assert!(solution.pressure[mid].norm() > 1e-10);
362 }
363
364 #[test]
365 #[should_panic(expected = "Resonance")]
366 fn test_helmholtz_resonance() {
367 let l = PI;
368 let k = 1.0; helmholtz_1d_mode(k, l, 1, 100);
370 }
371}