1#![allow(clippy::needless_range_loop)]
6pub fn thomas_algorithm(a: &[f64], b: &[f64], c: &[f64], d: &[f64]) -> Vec<f64> {
16 let n = b.len();
17 assert!(n >= 2, "system must have at least 2 equations");
18 assert_eq!(a.len(), n);
19 assert_eq!(c.len(), n);
20 assert_eq!(d.len(), n);
21 let mut c_prime = vec![0.0_f64; n];
22 let mut d_prime = vec![0.0_f64; n];
23 let mut x = vec![0.0_f64; n];
24 c_prime[0] = c[0] / b[0];
25 d_prime[0] = d[0] / b[0];
26 for i in 1..n {
27 let m = b[i] - a[i] * c_prime[i - 1];
28 c_prime[i] = if i < n - 1 { c[i] / m } else { 0.0 };
29 d_prime[i] = (d[i] - a[i] * d_prime[i - 1]) / m;
30 }
31 x[n - 1] = d_prime[n - 1];
32 for i in (0..n - 1).rev() {
33 x[i] = d_prime[i] - c_prime[i] * x[i + 1];
34 }
35 x
36}
37pub fn fdm_gradient(u: &[f64], dx: f64) -> Vec<f64> {
41 let n = u.len();
42 let mut grad = vec![0.0_f64; n];
43 if n == 0 {
44 return grad;
45 }
46 if n == 1 {
47 return grad;
48 }
49 grad[0] = (u[1] - u[0]) / dx;
50 for i in 1..n - 1 {
51 grad[i] = (u[i + 1] - u[i - 1]) / (2.0 * dx);
52 }
53 grad[n - 1] = (u[n - 1] - u[n - 2]) / dx;
54 grad
55}
56pub fn fdm_laplacian(u: &[f64], dx: f64) -> Vec<f64> {
60 let n = u.len();
61 let mut lap = vec![0.0_f64; n];
62 if n < 3 {
63 return lap;
64 }
65 let dx2 = dx * dx;
66 for i in 1..n - 1 {
67 lap[i] = (u[i - 1] - 2.0 * u[i] + u[i + 1]) / dx2;
68 }
69 lap
70}
71pub fn bilinear_interp(grid: &[f64], nx: usize, ny: usize, x: f64, y: f64) -> f64 {
78 let x0 = x.floor() as usize;
79 let y0 = y.floor() as usize;
80 let x0 = x0.min(nx - 2);
81 let y0 = y0.min(ny - 2);
82 let x1 = x0 + 1;
83 let y1 = y0 + 1;
84 let tx = x - x0 as f64;
85 let ty = y - y0 as f64;
86 let f00 = grid[y0 * nx + x0];
87 let f10 = grid[y0 * nx + x1];
88 let f01 = grid[y1 * nx + x0];
89 let f11 = grid[y1 * nx + x1];
90 f00 * (1.0 - tx) * (1.0 - ty) + f10 * tx * (1.0 - ty) + f01 * (1.0 - tx) * ty + f11 * tx * ty
91}
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use crate::pde::AdiMethod2D;
96 use crate::pde::BoundaryCondition;
97 use crate::pde::BurgersShock;
98 use crate::pde::Dct1D;
99 use crate::pde::FftDiff1D;
100 use crate::pde::FiniteDiffOps1D;
101 use crate::pde::FiniteDiffOps2D;
102 use crate::pde::FiniteDiffOps3D;
103 use crate::pde::FiniteDifference1D;
104 use crate::pde::FiniteDifference2D;
105 use crate::pde::FiniteVolume1D;
106 use crate::pde::FitzHughNagumo;
107 use crate::pde::GrayScottSystem;
108 use crate::pde::HeatEquation1D;
109 use crate::pde::HeatEquation3D;
110 use crate::pde::LevelSet2D;
111 use crate::pde::MethodOfLines;
112 use crate::pde::NsBurgers1D;
113 use crate::pde::PhaseField2D;
114 use crate::pde::PoissonSolver;
115 use crate::pde::SpectralMethod;
116 use crate::pde::WaveEq2ndOrder;
117 use crate::pde::WaveEquation1D;
118 use std::f64::consts::PI;
119 #[test]
120 fn test_thomas_algorithm_basic() {
121 let a = vec![0.0, -1.0, -1.0];
122 let b = vec![2.0, 2.0, 2.0];
123 let c = vec![-1.0, -1.0, 0.0];
124 let d = vec![0.0, 0.0, 0.0];
125 let x = thomas_algorithm(&a, &b, &c, &d);
126 assert_eq!(x.len(), 3);
127 for xi in &x {
128 assert!(xi.abs() < 1e-12);
129 }
130 }
131 #[test]
132 fn test_thomas_algorithm_nontrivial() {
133 let a = vec![0.0, -1.0];
134 let b = vec![2.0, 2.0];
135 let c = vec![-1.0, 0.0];
136 let d = vec![1.0, 1.0];
137 let x = thomas_algorithm(&a, &b, &c, &d);
138 assert!((x[0] - 1.0).abs() < 1e-10);
139 assert!((x[1] - 1.0).abs() < 1e-10);
140 }
141 #[test]
142 fn test_fdm_gradient_constant() {
143 let u = vec![3.0; 10];
144 let grad = fdm_gradient(&u, 0.1);
145 for g in &grad {
146 assert!(g.abs() < 1e-12);
147 }
148 }
149 #[test]
150 fn test_fdm_gradient_linear() {
151 let dx = 0.1;
152 let u: Vec<f64> = (0..10).map(|i| i as f64 * dx).collect();
153 let grad = fdm_gradient(&u, dx);
154 for i in 1..9 {
155 assert!((grad[i] - 1.0).abs() < 1e-10);
156 }
157 }
158 #[test]
159 fn test_fdm_laplacian_linear() {
160 let dx = 0.1;
161 let u: Vec<f64> = (0..10).map(|i| i as f64 * dx).collect();
162 let lap = fdm_laplacian(&u, dx);
163 for i in 1..9 {
164 assert!(lap[i].abs() < 1e-10);
165 }
166 }
167 #[test]
168 fn test_fdm_laplacian_quadratic() {
169 let dx = 0.1;
170 let u: Vec<f64> = (0..20).map(|i| (i as f64 * dx).powi(2)).collect();
171 let lap = fdm_laplacian(&u, dx);
172 for i in 1..19 {
173 assert!((lap[i] - 2.0).abs() < 1e-6, "lap[{}] = {}", i, lap[i]);
174 }
175 }
176 #[test]
177 fn test_bilinear_interp_at_nodes() {
178 let grid = vec![1.0, 2.0, 3.0, 4.0];
179 assert!((bilinear_interp(&grid, 2, 2, 0.0, 0.0) - 1.0).abs() < 1e-12);
180 assert!((bilinear_interp(&grid, 2, 2, 1.0, 0.0) - 2.0).abs() < 1e-12);
181 assert!((bilinear_interp(&grid, 2, 2, 0.0, 1.0) - 3.0).abs() < 1e-12);
182 assert!((bilinear_interp(&grid, 2, 2, 1.0, 1.0) - 4.0).abs() < 1e-12);
183 }
184 #[test]
185 fn test_bilinear_interp_midpoint() {
186 let grid = vec![1.0, 2.0, 3.0, 4.0];
187 let v = bilinear_interp(&grid, 2, 2, 0.5, 0.5);
188 assert!((v - 2.5).abs() < 1e-12);
189 }
190 #[test]
191 fn test_fd1d_diffusion_number() {
192 let fd = FiniteDifference1D::new(
193 10,
194 0.1,
195 0.01,
196 0.5,
197 0.0,
198 BoundaryCondition::Dirichlet(0.0),
199 BoundaryCondition::Dirichlet(0.0),
200 );
201 let r = fd.diffusion_number();
202 assert!((r - 0.5).abs() < 1e-12);
203 }
204 #[test]
205 fn test_fd1d_stability_check() {
206 let fd_stable = FiniteDifference1D::new(
207 10,
208 0.1,
209 0.001,
210 0.1,
211 0.0,
212 BoundaryCondition::Dirichlet(0.0),
213 BoundaryCondition::Dirichlet(0.0),
214 );
215 assert!(fd_stable.is_stable_explicit());
216 let fd_unstable = FiniteDifference1D::new(
217 10,
218 0.1,
219 0.1,
220 1.0,
221 0.0,
222 BoundaryCondition::Dirichlet(0.0),
223 BoundaryCondition::Dirichlet(0.0),
224 );
225 assert!(!fd_unstable.is_stable_explicit());
226 }
227 #[test]
228 fn test_fd1d_explicit_diffusion_preserves_bc() {
229 let n = 10;
230 let fd = FiniteDifference1D::new(
231 n,
232 0.1,
233 0.001,
234 0.1,
235 0.0,
236 BoundaryCondition::Dirichlet(0.0),
237 BoundaryCondition::Dirichlet(1.0),
238 );
239 let u: Vec<f64> = (0..n).map(|i| i as f64 / (n - 1) as f64).collect();
240 let u_new = fd.step_diffusion_explicit(&u);
241 assert!((u_new[0] - 0.0).abs() < 1e-12);
242 assert!((u_new[n - 1] - 1.0).abs() < 1e-12);
243 }
244 #[test]
245 fn test_fd1d_implicit_diffusion_preserves_bc() {
246 let n = 10;
247 let fd = FiniteDifference1D::new(
248 n,
249 0.1,
250 0.01,
251 0.1,
252 0.0,
253 BoundaryCondition::Dirichlet(0.0),
254 BoundaryCondition::Dirichlet(1.0),
255 );
256 let u: Vec<f64> = (0..n).map(|i| i as f64 / (n - 1) as f64).collect();
257 let u_new = fd.step_diffusion_implicit(&u);
258 assert!((u_new[0] - 0.0).abs() < 1e-12);
259 assert!((u_new[n - 1] - 1.0).abs() < 1e-12);
260 }
261 #[test]
262 fn test_fd1d_cn_diffusion_preserves_bc() {
263 let n = 10;
264 let fd = FiniteDifference1D::new(
265 n,
266 0.1,
267 0.01,
268 0.1,
269 0.0,
270 BoundaryCondition::Dirichlet(0.0),
271 BoundaryCondition::Dirichlet(1.0),
272 );
273 let u: Vec<f64> = (0..n).map(|i| i as f64 / (n - 1) as f64).collect();
274 let u_new = fd.step_diffusion_cn(&u);
275 assert!((u_new[0] - 0.0).abs() < 1e-12);
276 assert!((u_new[n - 1] - 1.0).abs() < 1e-12);
277 }
278 #[test]
279 fn test_fd1d_advection_upwind_conservation() {
280 let n = 20;
281 let fd = FiniteDifference1D::new(
282 n,
283 0.05,
284 0.01,
285 0.0,
286 1.0,
287 BoundaryCondition::Periodic,
288 BoundaryCondition::Periodic,
289 );
290 let u: Vec<f64> = (0..n)
291 .map(|i| (2.0 * PI * i as f64 / n as f64).sin())
292 .collect();
293 let u_new = fd.step_advection_upwind(&u);
294 let sum_old: f64 = u.iter().sum();
295 let sum_new: f64 = u_new.iter().sum();
296 assert!((sum_old - sum_new).abs() < 1.0);
297 }
298 #[test]
299 fn test_fd2d_laplacian_5pt_zero_for_linear() {
300 let nx = 5;
301 let ny = 5;
302 let fd2d = FiniteDifference2D::new(nx, ny, 0.1, 0.1, 0.001, 0.1);
303 let u: Vec<f64> = (0..ny)
304 .flat_map(|j| (0..nx).map(move |i| i as f64 * 0.1 + j as f64 * 0.1))
305 .collect();
306 let lap = fd2d.laplacian_5pt(&u);
307 for j in 1..ny - 1 {
308 for i in 1..nx - 1 {
309 assert!(lap[j * nx + i].abs() < 1e-10);
310 }
311 }
312 }
313 #[test]
314 fn test_wave1d_courant_number() {
315 let w = WaveEquation1D::new(100, 0.01, 0.005, 1.0);
316 assert!((w.courant_number() - 0.5).abs() < 1e-12);
317 }
318 #[test]
319 fn test_wave1d_lax_wendroff_stable() {
320 let n = 50;
321 let w = WaveEquation1D::new(n, 0.02, 0.01, 1.0);
322 let u: Vec<f64> = (0..n)
323 .map(|i| (2.0 * PI * i as f64 / n as f64).sin())
324 .collect();
325 let u_prev = u.clone();
326 let u_new = w.step_lax_wendroff(&u, &u_prev);
327 let max_val = u_new.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
328 assert!(max_val.abs() <= 2.0);
329 }
330 #[test]
331 fn test_poisson_vcycle_converges() {
332 let n = 17;
333 let dx = 1.0 / (n - 1) as f64;
334 let solver = PoissonSolver::new(n, dx, 4, 10);
335 let f: Vec<f64> = (0..n).map(|i| -(PI * i as f64 * dx).sin()).collect();
336 let u0 = vec![0.0_f64; n];
337 let u = solver.vcycle(&u0, &f, 3);
338 let exact: Vec<f64> = (0..n)
339 .map(|i| (PI * i as f64 * dx).sin() / (PI * PI))
340 .collect();
341 let err = (0..n)
342 .map(|i| (u[i] - exact[i]).powi(2))
343 .sum::<f64>()
344 .sqrt()
345 / n as f64;
346 assert!(err < 0.1, "Error too large: {}", err);
347 }
348 #[test]
349 fn test_burgers_step_no_blowup() {
350 let n = 50;
351 let b = NsBurgers1D::new(n, 0.02, 0.001, 0.01);
352 let u: Vec<f64> = (0..n)
353 .map(|i| (2.0 * PI * i as f64 / n as f64).sin())
354 .collect();
355 let u_new = b.step(&u);
356 let max_val = u_new.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
357 assert!(max_val.abs() < 10.0);
358 }
359 #[test]
360 fn test_spectral_chebyshev_nodes_count() {
361 let sp = SpectralMethod::new(8);
362 let nodes = sp.gauss_lobatto_nodes();
363 assert_eq!(nodes.len(), 8);
364 assert!((nodes[0].abs() - 1.0).abs() < 1e-12);
365 assert!((nodes[7].abs() - 1.0).abs() < 1e-12);
366 }
367 #[test]
368 fn test_spectral_differentiation_matrix_size() {
369 let sp = SpectralMethod::new(5);
370 let d = sp.differentiation_matrix();
371 assert_eq!(d.len(), 25);
372 }
373 #[test]
374 fn test_dct_roundtrip() {
375 let dct = Dct1D::new(8);
376 let x = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
377 let xk = dct.dct2_forward(&x);
378 let x_rec = dct.dct2_inverse(&xk);
379 for (orig, rec) in x.iter().zip(x_rec.iter()) {
380 assert!((orig - rec).abs() < 1e-10, "orig={} rec={}", orig, rec);
381 }
382 }
383 #[test]
384 fn test_fv1d_minmod() {
385 assert_eq!(FiniteVolume1D::minmod(2.0, 3.0), 2.0);
386 assert_eq!(FiniteVolume1D::minmod(-2.0, -3.0), -2.0);
387 assert_eq!(FiniteVolume1D::minmod(2.0, -3.0), 0.0);
388 }
389 #[test]
390 fn test_fv1d_superbee() {
391 let s = FiniteVolume1D::superbee(1.0, 1.0);
392 assert!(s >= 0.0);
393 }
394 #[test]
395 fn test_fv1d_van_leer() {
396 let s = FiniteVolume1D::van_leer(1.0, 2.0);
397 assert!((s - 4.0 / 3.0).abs() < 1e-12);
398 }
399 #[test]
400 fn test_fv1d_step_conservation() {
401 let n = 30;
402 let fv = FiniteVolume1D::new(n, 0.033, 0.005);
403 let u: Vec<f64> = (0..n)
404 .map(|i| (2.0 * PI * i as f64 / n as f64).sin())
405 .collect();
406 let u_new = fv.step(&u);
407 assert_eq!(u_new.len(), n);
408 }
409 #[test]
410 fn test_levelset2d_creation() {
411 let phi: Vec<f64> = (0..25).map(|i| i as f64 - 12.0).collect();
412 let ls = LevelSet2D::new(5, 5, 0.1, phi);
413 assert_eq!(ls.phi.len(), 25);
414 }
415 #[test]
416 fn test_levelset2d_normal_boundary() {
417 let nx = 5;
418 let ny = 5;
419 let phi: Vec<f64> = (0..nx * ny).map(|i| (i % nx) as f64 * 0.1 - 0.2).collect();
420 let ls = LevelSet2D::new(nx, ny, 0.1, phi);
421 let (nx_vec, _ny_vec) = ls.normal();
422 assert_eq!(nx_vec.len(), nx * ny);
423 }
424 #[test]
425 fn test_phasefield2d_allen_cahn_energy_decrease() {
426 let nx = 10;
427 let ny = 10;
428 let phi0: Vec<f64> = (0..nx * ny)
429 .map(|i| if i < nx * ny / 2 { -1.0 } else { 1.0 })
430 .collect();
431 let mut pf = PhaseField2D::new(nx, ny, 0.1, 0.001, 0.1, 1.0, phi0);
432 let e0 = pf.interface_energy();
433 pf.step_allen_cahn();
434 let e1 = pf.interface_energy();
435 assert!(e1 <= e0 * 2.0);
436 }
437 #[test]
438 fn test_method_of_lines_rk4() {
439 let mol = MethodOfLines::new(3, 0.1);
440 let u0 = vec![1.0, 0.0, 0.0];
441 let u_final = mol.integrate_rk4(&u0, 0.01, 100, |_t, u| u.iter().map(|&x| -x).collect());
442 assert!(u_final[0] < 1.0);
443 assert!(u_final[0] > 0.0);
444 }
445 #[test]
446 fn test_adi_2d_step() {
447 let nx = 5;
448 let ny = 5;
449 let fd2d = FiniteDifference2D::new(nx, ny, 0.1, 0.1, 0.001, 0.1);
450 let u: Vec<f64> = vec![1.0; nx * ny];
451 let u_new = fd2d.adi_step(&u);
452 assert_eq!(u_new.len(), nx * ny);
453 }
454 #[test]
455 fn test_heat3d_explicit_step() {
456 let nx = 4;
457 let ny = 4;
458 let nz = 4;
459 let heat = HeatEquation3D::new(
460 nx,
461 ny,
462 nz,
463 0.1,
464 0.0001,
465 0.01,
466 BoundaryCondition::Dirichlet(0.0),
467 );
468 let u: Vec<f64> = vec![1.0; nx * ny * nz];
469 let u_new = heat.step_explicit(&u);
470 assert_eq!(u_new.len(), nx * ny * nz);
471 }
472 #[test]
473 fn test_thomas_three_by_three() {
474 let a = vec![0.0, 1.0, 1.0];
475 let b = vec![4.0, 4.0, 4.0];
476 let c = vec![1.0, 1.0, 0.0];
477 let d = vec![5.0, 6.0, 5.0];
478 let x = thomas_algorithm(&a, &b, &c, &d);
479 let r0 = 4.0 * x[0] + 1.0 * x[1];
480 let r1 = 1.0 * x[0] + 4.0 * x[1] + 1.0 * x[2];
481 let r2 = 1.0 * x[1] + 4.0 * x[2];
482 assert!((r0 - 5.0).abs() < 1e-10);
483 assert!((r1 - 6.0).abs() < 1e-10);
484 assert!((r2 - 5.0).abs() < 1e-10);
485 }
486 #[test]
487 fn test_levelset2d_reinitialize() {
488 let nx = 7;
489 let ny = 7;
490 let phi0: Vec<f64> = (0..nx * ny)
491 .map(|i| {
492 let x = (i % nx) as f64 * 0.1 - 0.3;
493 let y = (i / nx) as f64 * 0.1 - 0.3;
494 x * x + y * y - 0.09
495 })
496 .collect();
497 let mut ls = LevelSet2D::new(nx, ny, 0.1, phi0);
498 ls.reinitialize(5, 0.01);
499 assert_eq!(ls.phi.len(), nx * ny);
500 }
501 #[test]
502 fn test_phasefield2d_cahn_hilliard_conserves_mass() {
503 let nx = 8;
504 let ny = 8;
505 let phi0: Vec<f64> = (0..nx * ny)
506 .map(|i| if i % 2 == 0 { 0.5 } else { -0.5 })
507 .collect();
508 let mut pf = PhaseField2D::new(nx, ny, 0.1, 0.001, 0.1, 0.1, phi0);
509 let mass_before: f64 = pf.phi.iter().sum();
510 pf.step_cahn_hilliard();
511 let mass_after: f64 = pf.phi.iter().sum();
512 assert!((mass_before - mass_after).abs() < 1.0);
513 }
514 #[test]
515 fn test_fd1d_absorbing_bc() {
516 let n = 10;
517 let fd = FiniteDifference1D::new(
518 n,
519 0.1,
520 0.001,
521 0.1,
522 0.0,
523 BoundaryCondition::Absorbing,
524 BoundaryCondition::Absorbing,
525 );
526 let u: Vec<f64> = (0..n).map(|i| i as f64).collect();
527 let u_new = fd.step_diffusion_explicit(&u);
528 assert!((u_new[0] - u_new[1]).abs() < 1e-10);
529 assert!((u_new[n - 1] - u_new[n - 2]).abs() < 1e-10);
530 }
531 #[test]
532 fn test_wave1d_absorbing_bc() {
533 let n = 20;
534 let w = WaveEquation1D::new(n, 0.05, 0.01, 1.0);
535 let mut u = vec![1.0_f64; n];
536 w.apply_absorbing_bc(&mut u);
537 assert!((u[0] - u[1]).abs() < 1e-12);
538 assert!((u[n - 1] - u[n - 2]).abs() < 1e-12);
539 }
540 #[test]
541 fn test_fft_poisson_periodicity() {
542 let n = 8;
543 let dx = 2.0 * PI / n as f64;
544 let solver = PoissonSolver::new(n, dx, 3, 5);
545 let f: Vec<f64> = (0..n).map(|i| -(i as f64 * dx).sin()).collect();
546 let u = solver.solve_fft_periodic(&f);
547 assert_eq!(u.len(), n);
548 }
549 #[test]
550 fn test_laplacian1d_constant_zero() {
551 let ops = FiniteDiffOps1D::new(10, 0.1);
552 let u = vec![5.0_f64; 10];
553 let lap = ops.laplacian(&u);
554 for i in 1..9 {
555 assert!(lap[i].abs() < 1e-10);
556 }
557 }
558 #[test]
559 fn test_laplacian2d_linear_zero() {
560 let ops = FiniteDiffOps2D::new(5, 5, 0.1, 0.1);
561 let u: Vec<f64> = (0..25).map(|k| (k % 5) as f64 + (k / 5) as f64).collect();
562 let lap = ops.laplacian(&u);
563 for j in 1..4 {
564 for i in 1..4 {
565 assert!(lap[j * 5 + i].abs() < 1e-9);
566 }
567 }
568 }
569 #[test]
570 fn test_gradient2d_constant_zero() {
571 let ops = FiniteDiffOps2D::new(5, 5, 0.1, 0.1);
572 let u = vec![3.0_f64; 25];
573 let (gx, gy) = ops.gradient(&u);
574 for i in 1..4 {
575 for j in 1..4 {
576 let idx = j * 5 + i;
577 assert!(gx[idx].abs() < 1e-10);
578 assert!(gy[idx].abs() < 1e-10);
579 }
580 }
581 }
582 #[test]
583 fn test_divergence2d_result_size() {
584 let ops = FiniteDiffOps2D::new(5, 5, 0.1, 0.1);
585 let fx = vec![1.0_f64; 25];
586 let fy = vec![0.0_f64; 25];
587 let div = ops.divergence(&fx, &fy);
588 assert_eq!(div.len(), 25);
589 }
590 #[test]
591 fn test_curl2d_constant_zero() {
592 let ops = FiniteDiffOps2D::new(5, 5, 0.1, 0.1);
593 let ux = vec![2.0_f64; 25];
594 let uy = vec![2.0_f64; 25];
595 let curl = ops.curl(&ux, &uy);
596 for j in 1..4 {
597 for i in 1..4 {
598 assert!(curl[j * 5 + i].abs() < 1e-9);
599 }
600 }
601 }
602 #[test]
603 fn test_laplacian3d_result_size() {
604 let ops = FiniteDiffOps3D::new(4, 4, 4, 0.1);
605 let u = vec![1.0_f64; 64];
606 let lap = ops.laplacian(&u);
607 assert_eq!(lap.len(), 64);
608 }
609 #[test]
610 fn test_mol_diffusion_rk4_decay() {
611 let n = 20;
612 let dx = 1.0 / (n - 1) as f64;
613 let mol = MethodOfLines::new(n, dx);
614 let u0: Vec<f64> = (0..n).map(|i| (PI * i as f64 * dx).sin()).collect();
615 let d = 0.01_f64;
616 let u_final = mol.integrate_rk4(&u0, 0.001, 50, |_t, u| {
617 fdm_laplacian(u, dx).iter().map(|&v| d * v).collect()
618 });
619 let amp0: f64 = u0.iter().cloned().fold(0.0_f64, f64::max);
620 let amp1: f64 = u_final.iter().cloned().fold(0.0_f64, f64::max);
621 assert!(amp1 <= amp0 + 1e-10);
622 }
623 #[test]
624 fn test_spectral_fft_diff_sine() {
625 let n = 16;
626 let dx = 2.0 * PI / n as f64;
627 let fft = FftDiff1D::new(n, dx);
628 let u: Vec<f64> = (0..n).map(|i| (i as f64 * dx).sin()).collect();
629 let du = fft.differentiate(&u);
630 for i in 2..n - 2 {
631 let x = i as f64 * dx;
632 assert!(
633 (du[i] - x.cos()).abs() < 0.05,
634 "i={} du={} cos={}",
635 i,
636 du[i],
637 x.cos()
638 );
639 }
640 }
641 #[test]
642 fn test_gray_scott_step_size() {
643 let nx = 8;
644 let ny = 8;
645 let (u0, v0) = GrayScottSystem::initial_conditions(nx, ny);
646 let mut gs = GrayScottSystem::new(nx, ny, 0.1, 0.001, 0.04, 0.06);
647 gs.u = u0;
648 gs.v = v0;
649 gs.step();
650 assert_eq!(gs.u.len(), nx * ny);
651 assert_eq!(gs.v.len(), nx * ny);
652 }
653 #[test]
654 fn test_fitzhugh_nagumo_step() {
655 let mut fhn = FitzHughNagumo::new(10, 0.1, 0.001);
656 fhn.step();
657 assert_eq!(fhn.v.len(), 10);
658 assert_eq!(fhn.w.len(), 10);
659 }
660 #[test]
661 fn test_wave_eq_2nd_order_size() {
662 let n = 20;
663 let w = WaveEq2ndOrder::new(n, 0.05, 0.01, 1.0);
664 let u = vec![0.0_f64; n];
665 let u_prev = vec![0.0_f64; n];
666 let u_new = w.step(&u, &u_prev);
667 assert_eq!(u_new.len(), n);
668 }
669 #[test]
670 fn test_burgers_shock_formation() {
671 let n = 50;
672 let b = BurgersShock::new(n, 0.02, 0.001, 0.005);
673 let u: Vec<f64> = (0..n).map(|i| (PI * i as f64 / n as f64).sin()).collect();
674 let u_new = b.step(&u);
675 assert_eq!(u_new.len(), n);
676 let max_abs = u_new.iter().cloned().fold(0.0_f64, |a, v| a.max(v.abs()));
677 assert!(max_abs < 5.0);
678 }
679 #[test]
680 fn test_heat_eq_explicit_implicit_cn_size() {
681 let n = 10;
682 let he = HeatEquation1D::new(n, 0.1, 0.001, 0.1);
683 let u = vec![1.0_f64; n];
684 assert_eq!(he.step_explicit(&u).len(), n);
685 assert_eq!(he.step_implicit(&u).len(), n);
686 assert_eq!(he.step_cn(&u).len(), n);
687 }
688 #[test]
689 fn test_poisson_multigrid_restrict_prolongate_roundtrip() {
690 let ps = PoissonSolver::new(8, 0.1, 3, 5);
691 let f = vec![1.0_f64, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
692 let coarse = ps.restrict(&f);
693 assert_eq!(coarse.len(), 4);
694 let fine = ps.prolongate(&coarse, 8);
695 assert_eq!(fine.len(), 8);
696 }
697 #[test]
698 fn test_adi_method_2d_size() {
699 let adi = AdiMethod2D::new(6, 6, 0.1, 0.1, 0.001, 0.1);
700 let u = vec![1.0_f64; 36];
701 let u_new = adi.step(&u);
702 assert_eq!(u_new.len(), 36);
703 }
704 #[test]
705 fn test_levelset_advection_preserves_size() {
706 let nx = 7;
707 let ny = 7;
708 let phi0: Vec<f64> = (0..nx * ny).map(|i| i as f64 - 24.0).collect();
709 let mut ls = LevelSet2D::new(nx, ny, 0.1, phi0);
710 let vel_u = vec![0.1_f64; nx * ny];
711 let vel_v = vec![0.0_f64; nx * ny];
712 ls.advect(&vel_u, &vel_v, 0.01);
713 assert_eq!(ls.phi.len(), nx * ny);
714 }
715}