1use crate::error::{IntegrateError, IntegrateResult};
7use scirs2_core::ndarray::{Array1, Array2, ArrayView1};
8#[allow(unused_imports)]
9use std::f64::consts::{PI, SQRT_2};
10
11type InvariantFn = Box<dyn Fn(&ArrayView1<f64>) -> f64>;
13
14pub trait DivergenceFreeFlow {
16 fn dim(&self) -> usize;
18
19 fn evaluate(&self, x: &ArrayView1<f64>, t: f64) -> Array1<f64>;
21
22 fn verify_divergence_free(&self, x: &ArrayView1<f64>, t: f64, h: f64) -> f64 {
24 let n = self.dim();
25 let mut div = 0.0;
26
27 for i in 0..n {
28 let mut x_plus = x.to_owned();
29 let mut x_minus = x.to_owned();
30 x_plus[i] += h;
31 x_minus[i] -= h;
32
33 let f_plus = self.evaluate(&x_plus.view(), t);
34 let f_minus = self.evaluate(&x_minus.view(), t);
35
36 div += (f_plus[i] - f_minus[i]) / (2.0 * h);
37 }
38
39 div
40 }
41}
42
43pub struct VolumePreservingIntegrator {
45 pub dt: f64,
47 pub method: VolumePreservingMethod,
49 pub tol: f64,
51 pub max_iter: usize,
53}
54
55#[derive(Debug, Clone, Copy)]
57pub enum VolumePreservingMethod {
58 ExplicitMidpoint,
60 ImplicitMidpoint,
62 SplittingMethod,
64 ProjectionMethod,
66 CompositionMethod,
68}
69
70impl VolumePreservingIntegrator {
71 pub fn new(dt: f64, method: VolumePreservingMethod) -> Self {
73 Self {
74 dt,
75 method,
76 tol: 1e-10,
77 max_iter: 100,
78 }
79 }
80
81 pub fn with_tolerance(mut self, tol: f64) -> Self {
83 self.tol = tol;
84 self
85 }
86
87 pub fn step<F>(&self, x: &ArrayView1<f64>, t: f64, flow: &F) -> IntegrateResult<Array1<f64>>
89 where
90 F: DivergenceFreeFlow,
91 {
92 match self.method {
93 VolumePreservingMethod::ExplicitMidpoint => self.explicit_midpoint_step(x, t, flow),
94 VolumePreservingMethod::ImplicitMidpoint => self.implicit_midpoint_step(x, t, flow),
95 VolumePreservingMethod::SplittingMethod => self.splitting_step(x, t, flow),
96 VolumePreservingMethod::ProjectionMethod => self.projection_step(x, t, flow),
97 VolumePreservingMethod::CompositionMethod => self.composition_step(x, t, flow),
98 }
99 }
100
101 fn explicit_midpoint_step<F>(
103 &self,
104 x: &ArrayView1<f64>,
105 t: f64,
106 flow: &F,
107 ) -> IntegrateResult<Array1<f64>>
108 where
109 F: DivergenceFreeFlow,
110 {
111 let f0 = flow.evaluate(x, t);
112 let x_mid = x + &f0 * (self.dt / 2.0);
113 let f_mid = flow.evaluate(&x_mid.view(), t + self.dt / 2.0);
114
115 Ok(x + &f_mid * self.dt)
116 }
117
118 fn implicit_midpoint_step<F>(
120 &self,
121 x: &ArrayView1<f64>,
122 t: f64,
123 flow: &F,
124 ) -> IntegrateResult<Array1<f64>>
125 where
126 F: DivergenceFreeFlow,
127 {
128 let mut x_new = x.to_owned();
129 let t_mid = t + self.dt / 2.0;
130
131 for _ in 0..self.max_iter {
133 let x_mid = (&x.to_owned() + &x_new) / 2.0;
134 let f_mid = flow.evaluate(&x_mid.view(), t_mid);
135 let x_next = x + &f_mid * self.dt;
136
137 let error = (&x_next - &x_new).mapv(f64::abs).sum();
138 x_new = x_next;
139
140 if error < self.tol {
141 return Ok(x_new);
142 }
143 }
144
145 Err(IntegrateError::ConvergenceError(
146 "Implicit midpoint method failed to converge".to_string(),
147 ))
148 }
149
150 fn splitting_step<F>(
152 &self,
153 x: &ArrayView1<f64>,
154 t: f64,
155 flow: &F,
156 ) -> IntegrateResult<Array1<f64>>
157 where
158 F: DivergenceFreeFlow,
159 {
160 self.composition_step(x, t, flow)
162 }
163
164 fn projection_step<F>(
166 &self,
167 x: &ArrayView1<f64>,
168 t: f64,
169 flow: &F,
170 ) -> IntegrateResult<Array1<f64>>
171 where
172 F: DivergenceFreeFlow,
173 {
174 let f = flow.evaluate(x, t);
176 let _x_euler = x + &f * self.dt;
177
178 self.explicit_midpoint_step(x, t, flow)
181 }
182
183 fn composition_step<F>(
185 &self,
186 x: &ArrayView1<f64>,
187 t: f64,
188 flow: &F,
189 ) -> IntegrateResult<Array1<f64>>
190 where
191 F: DivergenceFreeFlow,
192 {
193 let gamma = 1.0 / (2.0 - 2.0_f64.powf(1.0 / 3.0));
195 let c1 = gamma / 2.0;
196 let c2 = (1.0 - gamma) / 2.0;
197 let c3 = c2;
198 let c4 = c1;
199
200 let d1 = gamma;
201 let d2 = -gamma * 2.0_f64.powf(1.0 / 3.0);
202 let d3 = gamma;
203
204 let mut x_current = x.to_owned();
206 let mut t_current = t;
207
208 let substep = Self::new(c1 * self.dt, VolumePreservingMethod::ExplicitMidpoint);
210 x_current = substep.step(&x_current.view(), t_current, flow)?;
211 t_current += c1 * self.dt;
212
213 let substep = Self::new(d1 * self.dt, VolumePreservingMethod::ExplicitMidpoint);
215 x_current = substep.step(&x_current.view(), t_current, flow)?;
216 t_current += d1 * self.dt;
217
218 let substep = Self::new(c2 * self.dt, VolumePreservingMethod::ExplicitMidpoint);
220 x_current = substep.step(&x_current.view(), t_current, flow)?;
221 t_current += c2 * self.dt;
222
223 let substep = Self::new(d2 * self.dt, VolumePreservingMethod::ExplicitMidpoint);
225 x_current = substep.step(&x_current.view(), t_current, flow)?;
226 t_current += d2 * self.dt;
227
228 let substep = Self::new(c3 * self.dt, VolumePreservingMethod::ExplicitMidpoint);
230 x_current = substep.step(&x_current.view(), t_current, flow)?;
231 t_current += c3 * self.dt;
232
233 let substep = Self::new(d3 * self.dt, VolumePreservingMethod::ExplicitMidpoint);
235 x_current = substep.step(&x_current.view(), t_current, flow)?;
236 t_current += d3 * self.dt;
237
238 let substep = Self::new(c4 * self.dt, VolumePreservingMethod::ExplicitMidpoint);
240 x_current = substep.step(&x_current.view(), t_current, flow)?;
241
242 Ok(x_current)
243 }
244
245 pub fn integrate<F>(
247 &self,
248 x0: &ArrayView1<f64>,
249 t0: f64,
250 t_final: f64,
251 flow: &F,
252 ) -> IntegrateResult<Vec<(f64, Array1<f64>)>>
253 where
254 F: DivergenceFreeFlow,
255 {
256 let n_steps = ((t_final - t0) / self.dt).ceil() as usize;
257 let mut trajectory = vec![(t0, x0.to_owned())];
258
259 let mut x_current = x0.to_owned();
260 let mut t_current = t0;
261
262 for _ in 0..n_steps {
263 let dt_actual = (t_final - t_current).min(self.dt);
264
265 if dt_actual != self.dt {
266 let integrator = Self::new(dt_actual, self.method);
268 x_current = integrator.step(&x_current.view(), t_current, flow)?;
269 } else {
270 x_current = self.step(&x_current.view(), t_current, flow)?;
271 }
272
273 t_current += dt_actual;
274 trajectory.push((t_current, x_current.clone()));
275
276 if t_current >= t_final - 1e-10 {
277 break;
278 }
279 }
280
281 Ok(trajectory)
282 }
283}
284
285pub struct IncompressibleFlow;
287
288impl IncompressibleFlow {
289 pub fn circular_2d(&self) -> CircularFlow2D {
291 CircularFlow2D { omega: 1.0 }
292 }
293
294 pub fn abc_flow(a: f64, b: f64, c: f64) -> ABCFlow {
296 ABCFlow { a, b, c }
297 }
298
299 pub fn double_gyre(a: f64, epsilon: f64, omega: f64) -> DoubleGyre {
301 DoubleGyre { a, epsilon, omega }
302 }
303}
304
305pub struct CircularFlow2D {
307 omega: f64,
308}
309
310impl DivergenceFreeFlow for CircularFlow2D {
311 fn dim(&self) -> usize {
312 2
313 }
314
315 fn evaluate(&self, x: &ArrayView1<f64>, t: f64) -> Array1<f64> {
316 Array1::from_vec(vec![-self.omega * x[1], self.omega * x[0]])
317 }
318}
319
320pub struct ABCFlow {
322 a: f64,
323 b: f64,
324 c: f64,
325}
326
327impl DivergenceFreeFlow for ABCFlow {
328 fn dim(&self) -> usize {
329 3
330 }
331
332 fn evaluate(&self, x: &ArrayView1<f64>, t: f64) -> Array1<f64> {
333 Array1::from_vec(vec![
334 self.a * x[1].sin() + self.c * x[2].cos(),
335 self.b * x[2].sin() + self.a * x[0].cos(),
336 self.c * x[0].sin() + self.b * x[1].cos(),
337 ])
338 }
339}
340
341pub struct DoubleGyre {
343 a: f64,
344 epsilon: f64,
345 omega: f64,
346}
347
348impl DivergenceFreeFlow for DoubleGyre {
349 fn dim(&self) -> usize {
350 2
351 }
352
353 fn evaluate(&self, x: &ArrayView1<f64>, t: f64) -> Array1<f64> {
354 let a_t = self.epsilon * (self.omega * t).sin();
355 let b_t = 1.0 - 2.0 * self.epsilon * (self.omega * t).sin();
356
357 let f = a_t * x[0].powi(2) + b_t * x[0];
358 let df_dx = 2.0 * a_t * x[0] + b_t;
359
360 Array1::from_vec(vec![
361 -PI * self.a * (PI * f).sin() * (PI * x[1]).cos(),
362 PI * self.a * (PI * f).cos() * df_dx * (PI * x[1]).sin(),
363 ])
364 }
365}
366
367pub trait StreamFunction {
369 fn psi(&self, x: f64, y: f64, t: f64) -> f64;
371
372 fn velocity(&self, x: f64, y: f64, t: f64) -> (f64, f64) {
374 let h = 1e-8;
375
376 let u = (self.psi(x, y + h, t) - self.psi(x, y - h, t)) / (2.0 * h);
378
379 let v = -(self.psi(x + h, y, t) - self.psi(x - h, y, t)) / (2.0 * h);
381
382 (u, v)
383 }
384}
385
386pub struct StuartVortex {
388 pub alpha: f64,
390 pub k: f64,
392}
393
394impl StreamFunction for StuartVortex {
395 fn psi(&self, x: f64, y: f64, t: f64) -> f64 {
396 -self.alpha.ln() * y.cos() + self.alpha * (self.k * x).cos() * y.sin()
397 }
398}
399
400impl DivergenceFreeFlow for StuartVortex {
401 fn dim(&self) -> usize {
402 2
403 }
404
405 fn evaluate(&self, x: &ArrayView1<f64>, t: f64) -> Array1<f64> {
406 let (u, v) = self.velocity(x[0], x[1], t);
407 Array1::from_vec(vec![u, v])
408 }
409}
410
411pub struct TaylorGreenVortex {
413 pub nu: f64,
415}
416
417impl StreamFunction for TaylorGreenVortex {
418 fn psi(&self, x: f64, y: f64, t: f64) -> f64 {
419 let decay = (-2.0 * self.nu * t).exp();
420 decay * x.sin() * y.sin()
421 }
422}
423
424impl DivergenceFreeFlow for TaylorGreenVortex {
425 fn dim(&self) -> usize {
426 2
427 }
428
429 fn evaluate(&self, x: &ArrayView1<f64>, t: f64) -> Array1<f64> {
430 let (u, v) = self.velocity(x[0], x[1], t);
431 Array1::from_vec(vec![u, v])
432 }
433}
434
435pub struct HamiltonianFlow<H>
437where
438 H: Fn(&ArrayView1<f64>) -> f64,
439{
440 pub hamiltonian: H,
442 pub dim: usize,
444}
445
446impl<H> DivergenceFreeFlow for HamiltonianFlow<H>
447where
448 H: Fn(&ArrayView1<f64>) -> f64,
449{
450 fn dim(&self) -> usize {
451 self.dim
452 }
453
454 fn evaluate(&self, x: &ArrayView1<f64>, t: f64) -> Array1<f64> {
455 let n = self.dim / 2;
456 let h = 1e-8;
457 let mut dx = Array1::zeros(self.dim);
458
459 let mut grad_h = Array1::zeros(self.dim);
461 for i in 0..self.dim {
462 let mut x_plus = x.to_owned();
463 let mut x_minus = x.to_owned();
464 x_plus[i] += h;
465 x_minus[i] -= h;
466
467 grad_h[i] = ((self.hamiltonian)(&x_plus.view()) - (self.hamiltonian)(&x_minus.view()))
468 / (2.0 * h);
469 }
470
471 for i in 0..n {
473 dx[i] = grad_h[n + i]; dx[n + i] = -grad_h[i]; }
476
477 dx
478 }
479}
480
481pub struct ModifiedMidpointIntegrator {
483 base: VolumePreservingIntegrator,
485 correction_factor: f64,
487}
488
489impl ModifiedMidpointIntegrator {
490 pub fn new(_dt: f64, correctionfactor: f64) -> Self {
492 Self {
493 base: VolumePreservingIntegrator::new(_dt, VolumePreservingMethod::ImplicitMidpoint),
494 correction_factor: correctionfactor,
495 }
496 }
497
498 pub fn step_with_correction<F>(
500 &self,
501 x: &ArrayView1<f64>,
502 t: f64,
503 flow: &F,
504 ) -> IntegrateResult<Array1<f64>>
505 where
506 F: DivergenceFreeFlow,
507 {
508 let x_new = self.base.step(x, t, flow)?;
510
511 let x_mid = (&x.to_owned() + &x_new) / 2.0;
513 let div = flow.verify_divergence_free(&x_mid.view(), t + self.base.dt / 2.0, 1e-8);
514
515 if div.abs() > 1e-10 {
517 let correction = -self.correction_factor * div * self.base.dt;
518 let n = x.len();
519 let corrected = &x_new * (1.0 + correction / n as f64);
520 Ok(corrected)
521 } else {
522 Ok(x_new)
523 }
524 }
525}
526
527pub struct VariationalIntegrator {
529 dt: f64,
531 n_quad: usize,
533}
534
535impl VariationalIntegrator {
536 pub fn new(dt: f64, nquad: usize) -> Self {
538 Self { dt, n_quad: nquad }
539 }
540
541 pub fn discrete_lagrangian<F>(
543 &self,
544 x0: &ArrayView1<f64>,
545 x1: &ArrayView1<f64>,
546 t: f64,
547 flow: &F,
548 ) -> IntegrateResult<f64>
549 where
550 F: DivergenceFreeFlow,
551 {
552 let (weights, nodes) = self.gauss_legendre_quadrature()?;
554
555 let mut l_d = 0.0;
556
557 for i in 0..self.n_quad {
558 let tau = nodes[i];
559 let x_tau = x0 * (1.0 - tau) + x1 * tau;
560 let t_tau = t + self.dt * tau;
561
562 let f = flow.evaluate(&x_tau.view(), t_tau);
563 let v = (x1 - x0) / self.dt;
564
565 let l = 0.5 * v.dot(&v) - v.dot(&f);
567 l_d += weights[i] * l;
568 }
569
570 Ok(l_d * self.dt)
571 }
572
573 fn gauss_legendre_quadrature(&self) -> IntegrateResult<(Vec<f64>, Vec<f64>)> {
575 match self.n_quad {
576 1 => Ok((vec![1.0], vec![0.5])),
577 2 => Ok((
578 vec![0.5, 0.5],
579 vec![0.5 - 0.5 / 3.0_f64.sqrt(), 0.5 + 0.5 / 3.0_f64.sqrt()],
580 )),
581 3 => Ok((
582 vec![5.0 / 18.0, 8.0 / 18.0, 5.0 / 18.0],
583 vec![
584 0.5 - 0.5 * (0.6_f64).sqrt(),
585 0.5,
586 0.5 + 0.5 * (0.6_f64).sqrt(),
587 ],
588 )),
589 _ => Err(IntegrateError::ValueError(format!(
590 "Quadrature order {} not implemented",
591 self.n_quad
592 ))),
593 }
594 }
595}
596
597pub struct DiscreteGradientIntegrator {
599 #[allow(dead_code)]
601 dt: f64,
602 #[allow(dead_code)]
604 invariants: Vec<InvariantFn>,
605}
606
607impl DiscreteGradientIntegrator {
608 pub fn new(dt: f64) -> Self {
610 Self {
611 dt,
612 invariants: Vec::new(),
613 }
614 }
615
616 pub fn add_invariant<I>(&mut self, invariant: I) -> &mut Self
618 where
619 I: Fn(&ArrayView1<f64>) -> f64 + 'static,
620 {
621 self.invariants.push(Box::new(invariant));
622 self
623 }
624
625 pub fn discrete_gradient(
627 &self,
628 x0: &ArrayView1<f64>,
629 x1: &ArrayView1<f64>,
630 invariantidx: usize,
631 ) -> Array1<f64> {
632 let h = &self.invariants[invariantidx];
633 let h0 = h(x0);
634 let h1 = h(x1);
635
636 if (x1 - x0).mapv(|x| x.abs()).sum() < 1e-14 {
637 self.gradient(x0, invariantidx)
639 } else {
640 let g0 = self.gradient(x0, invariantidx);
642 let g1 = self.gradient(x1, invariantidx);
643 let g_avg = (&g0 + &g1) / 2.0;
644
645 let dx = x1 - x0;
647 let correction = (h1 - h0 - g_avg.dot(&dx)) / dx.dot(&dx) * &dx;
648
649 g_avg + correction
650 }
651 }
652
653 fn gradient(&self, x: &ArrayView1<f64>, invariantidx: usize) -> Array1<f64> {
655 let h = &self.invariants[invariantidx];
656 let eps = 1e-8;
657 let n = x.len();
658 let mut grad = Array1::zeros(n);
659
660 for i in 0..n {
661 let mut x_plus = x.to_owned();
662 let mut x_minus = x.to_owned();
663 x_plus[i] += eps;
664 x_minus[i] -= eps;
665
666 grad[i] = (h(&x_plus.view()) - h(&x_minus.view())) / (2.0 * eps);
667 }
668
669 grad
670 }
671}
672
673pub struct VolumeChecker;
675
676impl VolumeChecker {
677 pub fn check_volume_preservation<F>(
679 points: &Array2<f64>,
680 integrator: &VolumePreservingIntegrator,
681 flow: &F,
682 t0: f64,
683 t_final: f64,
684 ) -> IntegrateResult<f64>
685 where
686 F: DivergenceFreeFlow,
687 {
688 let npoints = points.nrows();
689 let dim = points.ncols();
690
691 let initial_volume = Self::estimate_volume(points)?;
693
694 let mut evolvedpoints = Array2::zeros((npoints, dim));
696 for i in 0..npoints {
697 let x0 = points.row(i);
698 let trajectory = integrator.integrate(&x0, t0, t_final, flow)?;
699 let (_, x_final) = trajectory.last().unwrap();
700 evolvedpoints.row_mut(i).assign(x_final);
701 }
702
703 let final_volume = Self::estimate_volume(&evolvedpoints)?;
705
706 Ok((final_volume - initial_volume).abs() / initial_volume)
708 }
709
710 fn estimate_volume(points: &Array2<f64>) -> IntegrateResult<f64> {
712 if points.nrows() == 0 {
713 return Ok(0.0);
714 }
715
716 let dim = points.ncols();
717 let mut min_coords = points.row(0).to_owned();
718 let mut max_coords = points.row(0).to_owned();
719
720 for i in 1..points.nrows() {
721 for j in 0..dim {
722 min_coords[j] = min_coords[j].min(points[[i, j]]);
723 max_coords[j] = max_coords[j].max(points[[i, j]]);
724 }
725 }
726
727 let mut volume = 1.0;
728 for j in 0..dim {
729 volume *= max_coords[j] - min_coords[j];
730 }
731
732 Ok(volume)
733 }
734}
735
736#[cfg(test)]
737mod tests {
738 use super::*;
739 use approx::assert_relative_eq;
740
741 #[test]
742 fn test_circular_flow_volume_preservation() {
743 let flow = CircularFlow2D { omega: 1.0 };
744 let integrator =
745 VolumePreservingIntegrator::new(0.1, VolumePreservingMethod::ExplicitMidpoint);
746
747 let mut points = Array2::zeros((4, 2));
749 points[[0, 0]] = 1.0;
750 points[[0, 1]] = 0.0;
751 points[[1, 0]] = 0.0;
752 points[[1, 1]] = 1.0;
753 points[[2, 0]] = -1.0;
754 points[[2, 1]] = 0.0;
755 points[[3, 0]] = 0.0;
756 points[[3, 1]] = -1.0;
757
758 let volume_change =
759 VolumeChecker::check_volume_preservation(&points, &integrator, &flow, 0.0, 2.0 * PI)
760 .unwrap();
761
762 assert!(
763 volume_change < 0.01,
764 "Volume not preserved: {volume_change}"
765 );
766 }
767
768 #[test]
769 fn test_divergence_free_verification() {
770 let flow = ABCFlow {
771 a: 1.0,
772 b: SQRT_2,
773 c: PI / 2.0,
774 };
775 let x = Array1::from_vec(vec![0.5, 0.5, 0.5]);
776
777 let div = flow.verify_divergence_free(&x.view(), 0.0, 1e-6);
778 assert!(div.abs() < 1e-8, "Flow not divergence-free: {div}");
779 }
780
781 #[test]
782 fn test_implicit_midpoint_convergence() {
783 let flow = CircularFlow2D { omega: 1.0 };
784 let dt = 0.1;
785
786 let integrator =
787 VolumePreservingIntegrator::new(dt, VolumePreservingMethod::ImplicitMidpoint);
788 let x0 = Array1::from_vec(vec![1.0, 0.0]);
789
790 let x1 = integrator.step(&x0.view(), 0.0, &flow).unwrap();
791
792 assert_relative_eq!(x1[0], dt.cos(), epsilon = 1e-3);
794 assert_relative_eq!(x1[1], dt.sin(), epsilon = 1e-3);
795 }
796}