1use scirs2_core::ndarray::{s, Array1, Array2, Array3, ArrayView1, ArrayView2};
7use std::sync::Arc;
8use std::time::Instant;
9
10use crate::ode::{solve_ivp, ODEOptions};
11use crate::pde::finite_difference::FiniteDifferenceScheme;
12use crate::pde::{
13 BoundaryCondition, BoundaryConditionType, BoundaryLocation, Domain, PDEError, PDEResult,
14 PDESolution, PDESolverInfo,
15};
16
17type CoeffFn2D = Arc<dyn Fn(f64, f64, f64, f64) -> f64 + Send + Sync>;
19
20pub struct MOL2DResult {
22 pub t: Array1<f64>,
24
25 pub u: Array3<f64>,
27
28 pub ode_info: Option<String>,
30
31 pub computation_time: f64,
33}
34
35#[derive(Clone)]
40pub struct MOLParabolicSolver2D {
41 domain: Domain,
43
44 time_range: [f64; 2],
46
47 diffusion_x: CoeffFn2D,
49
50 diffusion_y: CoeffFn2D,
52
53 advection_x: Option<CoeffFn2D>,
55
56 advection_y: Option<CoeffFn2D>,
58
59 reaction_term: Option<CoeffFn2D>,
61
62 initial_condition: Arc<dyn Fn(f64, f64) -> f64 + Send + Sync>,
64
65 boundary_conditions: Vec<BoundaryCondition<f64>>,
67
68 fd_scheme: FiniteDifferenceScheme,
70
71 options: super::MOLOptions,
73}
74
75impl MOLParabolicSolver2D {
76 #[allow(clippy::too_many_arguments)]
78 pub fn new(
79 domain: Domain,
80 time_range: [f64; 2],
81 diffusion_x: impl Fn(f64, f64, f64, f64) -> f64 + Send + Sync + 'static,
82 diffusion_y: impl Fn(f64, f64, f64, f64) -> f64 + Send + Sync + 'static,
83 initial_condition: impl Fn(f64, f64) -> f64 + Send + Sync + 'static,
84 boundary_conditions: Vec<BoundaryCondition<f64>>,
85 options: Option<super::MOLOptions>,
86 ) -> PDEResult<Self> {
87 if domain.dimensions() != 2 {
89 return Err(PDEError::DomainError(
90 "Domain must be 2-dimensional for 2D parabolic solver".to_string(),
91 ));
92 }
93
94 if time_range[0] >= time_range[1] {
96 return Err(PDEError::DomainError(
97 "Invalid time _range: start must be less than end".to_string(),
98 ));
99 }
100
101 if boundary_conditions.len() != 4 {
103 return Err(PDEError::BoundaryConditions(
104 "2D parabolic PDE requires exactly 4 boundary _conditions (one for each edge)"
105 .to_string(),
106 ));
107 }
108
109 let has_x_lower = boundary_conditions
111 .iter()
112 .any(|bc| bc.location == BoundaryLocation::Lower && bc.dimension == 0);
113 let has_x_upper = boundary_conditions
114 .iter()
115 .any(|bc| bc.location == BoundaryLocation::Upper && bc.dimension == 0);
116 let has_y_lower = boundary_conditions
117 .iter()
118 .any(|bc| bc.location == BoundaryLocation::Lower && bc.dimension == 1);
119 let has_y_upper = boundary_conditions
120 .iter()
121 .any(|bc| bc.location == BoundaryLocation::Upper && bc.dimension == 1);
122
123 if !has_x_lower || !has_x_upper || !has_y_lower || !has_y_upper {
124 return Err(PDEError::BoundaryConditions(
125 "2D parabolic PDE requires boundary _conditions for all edges of the domain"
126 .to_string(),
127 ));
128 }
129
130 Ok(MOLParabolicSolver2D {
131 domain,
132 time_range,
133 diffusion_x: Arc::new(diffusion_x),
134 diffusion_y: Arc::new(diffusion_y),
135 advection_x: None,
136 advection_y: None,
137 reaction_term: None,
138 initial_condition: Arc::new(initial_condition),
139 boundary_conditions,
140 fd_scheme: FiniteDifferenceScheme::CentralDifference,
141 options: options.unwrap_or_default(),
142 })
143 }
144
145 pub fn with_advection(
147 mut self,
148 advection_x: impl Fn(f64, f64, f64, f64) -> f64 + Send + Sync + 'static,
149 advection_y: impl Fn(f64, f64, f64, f64) -> f64 + Send + Sync + 'static,
150 ) -> Self {
151 self.advection_x = Some(Arc::new(advection_x));
152 self.advection_y = Some(Arc::new(advection_y));
153 self
154 }
155
156 pub fn with_reaction(
158 mut self,
159 reaction_term: impl Fn(f64, f64, f64, f64) -> f64 + Send + Sync + 'static,
160 ) -> Self {
161 self.reaction_term = Some(Arc::new(reaction_term));
162 self
163 }
164
165 pub fn with_fd_scheme(mut self, scheme: FiniteDifferenceScheme) -> Self {
167 self.fd_scheme = scheme;
168 self
169 }
170
171 pub fn solve(&self) -> PDEResult<MOL2DResult> {
173 let start_time = Instant::now();
174
175 let x_grid = self.domain.grid(0)?;
177 let y_grid = self.domain.grid(1)?;
178 let nx = x_grid.len();
179 let ny = y_grid.len();
180 let dx = self.domain.grid_spacing(0)?;
181 let dy = self.domain.grid_spacing(1)?;
182
183 let mut u0 = Array2::zeros((ny, nx));
185 for j in 0..ny {
186 for i in 0..nx {
187 u0[[j, i]] = (self.initial_condition)(x_grid[i], y_grid[j]);
188 }
189 }
190
191 let _u0_flat = u0
193 .clone()
194 .into_shape_with_order(nx * ny)
195 .expect("Operation failed");
196
197 let x_grid_closure = x_grid.clone();
199 let y_grid_closure = y_grid.clone();
200
201 let x_grid_apply = x_grid.clone();
203 let y_grid_apply = y_grid.clone();
204
205 let ode_options = ODEOptions {
207 method: self.options.ode_method,
208 rtol: self.options.rtol,
209 atol: self.options.atol,
210 max_steps: self.options.max_steps.unwrap_or(500),
211 h0: None,
212 max_step: None,
213 min_step: None,
214 dense_output: true,
215 max_order: None,
216 jac: None,
217 use_banded_jacobian: false,
218 ml: None,
219 mu: None,
220 mass_matrix: None,
221 jacobian_strategy: None,
222 };
223
224 let time_range = self.time_range;
225 let boundary_conditions = self.boundary_conditions.clone();
226
227 let solver = self;
229
230 let ode_func = move |t: f64, u_flat: ArrayView1<f64>| -> Array1<f64> {
232 let u = u_flat
234 .into_shape_with_order((ny, nx))
235 .expect("Operation failed");
236 let mut dudt = Array2::zeros((ny, nx));
237
238 for j in 1..ny - 1 {
240 for i in 1..nx - 1 {
241 let y = y_grid[j];
242 let x = x_grid[i];
243 let u_val = u[[j, i]];
244
245 let d2u_dx2 = (u[[j, i + 1]] - 2.0 * u[[j, i]] + u[[j, i - 1]]) / (dx * dx);
247 let d2u_dy2 = (u[[j + 1, i]] - 2.0 * u[[j, i]] + u[[j - 1, i]]) / (dy * dy);
248
249 let diffusion_term_x = (solver.diffusion_x)(x, y, t, u_val) * d2u_dx2;
250 let diffusion_term_y = (solver.diffusion_y)(x, y, t, u_val) * d2u_dy2;
251
252 let advection_term_x = if let Some(advection_x) = &solver.advection_x {
254 let du_dx = (u[[j, i + 1]] - u[[j, i - 1]]) / (2.0 * dx);
255 advection_x(x, y, t, u_val) * du_dx
256 } else {
257 0.0
258 };
259
260 let advection_term_y = if let Some(advection_y) = &solver.advection_y {
261 let du_dy = (u[[j + 1, i]] - u[[j - 1, i]]) / (2.0 * dy);
262 advection_y(x, y, t, u_val) * du_dy
263 } else {
264 0.0
265 };
266
267 let reaction_val = if let Some(reaction) = &solver.reaction_term {
269 reaction(x, y, t, u_val)
270 } else {
271 0.0
272 };
273
274 dudt[[j, i]] = diffusion_term_x
275 + diffusion_term_y
276 + advection_term_x
277 + advection_term_y
278 + reaction_val;
279 }
280 }
281
282 for bc in &solver.boundary_conditions {
284 match (bc.dimension, bc.location) {
285 (0, BoundaryLocation::Lower) => {
287 apply_boundary_condition_2d(
289 &mut dudt,
290 &u,
291 &x_grid_closure,
292 &y_grid_closure,
293 bc,
294 dx,
295 dy,
296 Some(0),
297 None,
298 None,
299 Some(ny),
300 solver,
301 );
302 }
303 (0, BoundaryLocation::Upper) => {
304 apply_boundary_condition_2d(
306 &mut dudt,
307 &u,
308 &x_grid_closure,
309 &y_grid_closure,
310 bc,
311 dx,
312 dy,
313 Some(nx - 1),
314 None,
315 None,
316 Some(ny),
317 solver,
318 );
319 }
320 (1, BoundaryLocation::Lower) => {
322 apply_boundary_condition_2d(
324 &mut dudt,
325 &u,
326 &x_grid_closure,
327 &y_grid_closure,
328 bc,
329 dx,
330 dy,
331 None,
332 Some(0),
333 Some(nx),
334 None,
335 solver,
336 );
337 }
338 (1, BoundaryLocation::Upper) => {
339 apply_boundary_condition_2d(
341 &mut dudt,
342 &u,
343 &x_grid_closure,
344 &y_grid_closure,
345 bc,
346 dx,
347 dy,
348 None,
349 Some(ny - 1),
350 Some(nx),
351 None,
352 solver,
353 );
354 }
355 _ => {
356 }
359 }
360 }
361
362 dudt.into_shape_with_order(nx * ny)
364 .expect("Operation failed")
365 };
366
367 apply_dirichlet_conditions_to_initial(
369 &mut u0,
370 &boundary_conditions,
371 &x_grid_apply,
372 &y_grid_apply,
373 );
374
375 let u0_flat = u0
376 .clone()
377 .into_shape_with_order(nx * ny)
378 .expect("Operation failed");
379
380 let ode_result = solve_ivp(ode_func, time_range, u0_flat, Some(ode_options))?;
382
383 let computation_time = start_time.elapsed().as_secs_f64();
385
386 let t = ode_result.t.clone();
388 let nt = t.len();
389
390 let mut u_3d = Array3::zeros((nt, ny, nx));
392
393 for (time_idx, y_flat) in ode_result.y.iter().enumerate() {
394 let u_2d = y_flat
395 .clone()
396 .into_shape_with_order((ny, nx))
397 .expect("Operation failed");
398 for j in 0..ny {
399 for i in 0..nx {
400 u_3d[[time_idx, j, i]] = u_2d[[j, i]];
401 }
402 }
403 }
404
405 let ode_info = Some(format!(
406 "ODE steps: {}, function evaluations: {}, successful steps: {}",
407 ode_result.n_steps, ode_result.n_eval, ode_result.n_accepted,
408 ));
409
410 Ok(MOL2DResult {
411 t: ode_result.t.into(),
412 u: u_3d,
413 ode_info,
414 computation_time,
415 })
416 }
417}
418
419#[allow(clippy::too_many_arguments)]
421#[allow(dead_code)]
422fn apply_boundary_condition_2d(
423 dudt: &mut Array2<f64>,
424 u: &ArrayView2<f64>,
425 x_grid: &Array1<f64>,
426 y_grid: &Array1<f64>,
427 bc: &BoundaryCondition<f64>,
428 dx: f64,
429 dy: f64,
430 i_fixed: Option<usize>,
431 j_fixed: Option<usize>,
432 i_range: Option<usize>,
433 j_range: Option<usize>,
434 solver: &MOLParabolicSolver2D,
435) {
436 let nx = x_grid.len();
437 let ny = y_grid.len();
438
439 let i_range = i_range.unwrap_or(1);
440 let j_range = j_range.unwrap_or(1);
441
442 match bc.bc_type {
443 BoundaryConditionType::Dirichlet => {
444 if let Some(i) = i_fixed {
447 for j in 0..j_range {
448 dudt[[j, i]] = 0.0;
449 }
450 } else if let Some(j) = j_fixed {
451 for i in 0..i_range {
452 dudt[[j, i]] = 0.0;
453 }
454 }
455 }
456 BoundaryConditionType::Neumann => {
457 if let Some(i) = i_fixed {
459 for j in 1..ny - 1 {
461 let y = y_grid[j];
462 let x = x_grid[i];
463 let u_val = u[[j, i]];
464 let t = 0.0; let du_dn = bc.value; let u_ghost = if i == 0 {
472 u[[j, i]] - dx * du_dn
473 } else {
474 u[[j, i]] + dx * du_dn
475 };
476
477 let d2u_dx2 = if i == 0 {
479 (u[[j, i + 1]] - 2.0 * u[[j, i]] + u_ghost) / (dx * dx)
480 } else {
481 (u_ghost - 2.0 * u[[j, i]] + u[[j, i - 1]]) / (dx * dx)
482 };
483
484 let d2u_dy2 = (u[[j + 1, i]] - 2.0 * u[[j, i]] + u[[j - 1, i]]) / (dy * dy);
486
487 let diffusion_term_x = (solver.diffusion_x)(x, y, t, u_val) * d2u_dx2;
488 let diffusion_term_y = (solver.diffusion_y)(x, y, t, u_val) * d2u_dy2;
489
490 let advection_term_x = if let Some(advection_x) = &solver.advection_x {
492 let du_dx = if i == 0 {
494 (u[[j, i + 1]] - u[[j, i]]) / dx
495 } else {
496 (u[[j, i]] - u[[j, i - 1]]) / dx
497 };
498 advection_x(x, y, t, u_val) * du_dx
499 } else {
500 0.0
501 };
502
503 let advection_term_y = if let Some(advection_y) = &solver.advection_y {
504 let du_dy = (u[[j + 1, i]] - u[[j - 1, i]]) / (2.0 * dy);
506 advection_y(x, y, t, u_val) * du_dy
507 } else {
508 0.0
509 };
510
511 let reaction_term = if let Some(reaction) = &solver.reaction_term {
513 reaction(x, y, t, u_val)
514 } else {
515 0.0
516 };
517
518 dudt[[j, i]] = diffusion_term_x
519 + diffusion_term_y
520 + advection_term_x
521 + advection_term_y
522 + reaction_term;
523 }
524 } else if let Some(j) = j_fixed {
525 for i in 1..nx - 1 {
527 let y = y_grid[j];
528 let x = x_grid[i];
529 let u_val = u[[j, i]];
530 let t = 0.0; let du_dn = bc.value; let u_ghost = if j == 0 {
538 u[[j, i]] - dy * du_dn
539 } else {
540 u[[j, i]] + dy * du_dn
541 };
542
543 let d2u_dy2 = if j == 0 {
545 (u[[j + 1, i]] - 2.0 * u[[j, i]] + u_ghost) / (dy * dy)
546 } else {
547 (u_ghost - 2.0 * u[[j, i]] + u[[j - 1, i]]) / (dy * dy)
548 };
549
550 let d2u_dx2 = (u[[j, i + 1]] - 2.0 * u[[j, i]] + u[[j, i - 1]]) / (dx * dx);
552
553 let diffusion_term_x = (solver.diffusion_x)(x, y, t, u_val) * d2u_dx2;
554 let diffusion_term_y = (solver.diffusion_y)(x, y, t, u_val) * d2u_dy2;
555
556 let advection_term_x = if let Some(advection_x) = &solver.advection_x {
558 let du_dx = (u[[j, i + 1]] - u[[j, i - 1]]) / (2.0 * dx);
560 advection_x(x, y, t, u_val) * du_dx
561 } else {
562 0.0
563 };
564
565 let advection_term_y = if let Some(advection_y) = &solver.advection_y {
566 let du_dy = if j == 0 {
568 (u[[j + 1, i]] - u[[j, i]]) / dy
569 } else {
570 (u[[j, i]] - u[[j - 1, i]]) / dy
571 };
572 advection_y(x, y, t, u_val) * du_dy
573 } else {
574 0.0
575 };
576
577 let reaction_term = if let Some(reaction) = &solver.reaction_term {
579 reaction(x, y, t, u_val)
580 } else {
581 0.0
582 };
583
584 dudt[[j, i]] = diffusion_term_x
585 + diffusion_term_y
586 + advection_term_x
587 + advection_term_y
588 + reaction_term;
589 }
590 }
591 }
592 BoundaryConditionType::Robin => {
593 if let Some([a, b, c]) = bc.coefficients {
595 if let Some(i) = i_fixed {
596 for j in 1..ny - 1 {
598 let y = y_grid[j];
599 let x = x_grid[i];
600 let u_val = u[[j, i]];
601 let t = 0.0; let du_dn = (c - a * u_val) / b;
605
606 let u_ghost = if i == 0 {
609 u[[j, i]] - dx * du_dn
610 } else {
611 u[[j, i]] + dx * du_dn
612 };
613
614 let d2u_dx2 = if i == 0 {
616 (u[[j, i + 1]] - 2.0 * u[[j, i]] + u_ghost) / (dx * dx)
617 } else {
618 (u_ghost - 2.0 * u[[j, i]] + u[[j, i - 1]]) / (dx * dx)
619 };
620
621 let d2u_dy2 = (u[[j + 1, i]] - 2.0 * u[[j, i]] + u[[j - 1, i]]) / (dy * dy);
623
624 let diffusion_term_x = (solver.diffusion_x)(x, y, t, u_val) * d2u_dx2;
625 let diffusion_term_y = (solver.diffusion_y)(x, y, t, u_val) * d2u_dy2;
626
627 let advection_term_x = if let Some(advection_x) = &solver.advection_x {
629 let du_dx = if i == 0 {
631 (u[[j, i + 1]] - u[[j, i]]) / dx
632 } else {
633 (u[[j, i]] - u[[j, i - 1]]) / dx
634 };
635 advection_x(x, y, t, u_val) * du_dx
636 } else {
637 0.0
638 };
639
640 let advection_term_y = if let Some(advection_y) = &solver.advection_y {
641 let du_dy = (u[[j + 1, i]] - u[[j - 1, i]]) / (2.0 * dy);
643 advection_y(x, y, t, u_val) * du_dy
644 } else {
645 0.0
646 };
647
648 let reaction_term = if let Some(reaction) = &solver.reaction_term {
650 reaction(x, y, t, u_val)
651 } else {
652 0.0
653 };
654
655 dudt[[j, i]] = diffusion_term_x
656 + diffusion_term_y
657 + advection_term_x
658 + advection_term_y
659 + reaction_term;
660 }
661 } else if let Some(j) = j_fixed {
662 for i in 1..nx - 1 {
664 let y = y_grid[j];
665 let x = x_grid[i];
666 let u_val = u[[j, i]];
667 let t = 0.0; let du_dn = (c - a * u_val) / b;
671
672 let u_ghost = if j == 0 {
675 u[[j, i]] - dy * du_dn
676 } else {
677 u[[j, i]] + dy * du_dn
678 };
679
680 let d2u_dy2 = if j == 0 {
682 (u[[j + 1, i]] - 2.0 * u[[j, i]] + u_ghost) / (dy * dy)
683 } else {
684 (u_ghost - 2.0 * u[[j, i]] + u[[j - 1, i]]) / (dy * dy)
685 };
686
687 let d2u_dx2 = (u[[j, i + 1]] - 2.0 * u[[j, i]] + u[[j, i - 1]]) / (dx * dx);
689
690 let diffusion_term_x = (solver.diffusion_x)(x, y, t, u_val) * d2u_dx2;
691 let diffusion_term_y = (solver.diffusion_y)(x, y, t, u_val) * d2u_dy2;
692
693 let advection_term_x = if let Some(advection_x) = &solver.advection_x {
695 let du_dx = (u[[j, i + 1]] - u[[j, i - 1]]) / (2.0 * dx);
697 advection_x(x, y, t, u_val) * du_dx
698 } else {
699 0.0
700 };
701
702 let advection_term_y = if let Some(advection_y) = &solver.advection_y {
703 let du_dy = if j == 0 {
705 (u[[j + 1, i]] - u[[j, i]]) / dy
706 } else {
707 (u[[j, i]] - u[[j - 1, i]]) / dy
708 };
709 advection_y(x, y, t, u_val) * du_dy
710 } else {
711 0.0
712 };
713
714 let reaction_term = if let Some(reaction) = &solver.reaction_term {
716 reaction(x, y, t, u_val)
717 } else {
718 0.0
719 };
720
721 dudt[[j, i]] = diffusion_term_x
722 + diffusion_term_y
723 + advection_term_x
724 + advection_term_y
725 + reaction_term;
726 }
727 }
728 }
729 }
730 BoundaryConditionType::Periodic => {
731 if let Some(i) = i_fixed {
735 let _opposite_i = if i == 0 { nx - 1 } else { 0 };
737
738 for j in 0..ny {
739 let y = y_grid[j];
740 let x = x_grid[i];
741 let u_val = u[[j, i]];
742 let t = 0.0; let left_val = if i == 0 {
746 u[[j, nx - 1]]
747 } else {
748 u[[j, i - 1]]
749 };
750 let right_val = if i == nx - 1 {
751 u[[j, 0]]
752 } else {
753 u[[j, i + 1]]
754 };
755 let top_val = if j == ny - 1 {
756 u[[0, i]]
757 } else {
758 u[[j + 1, i]]
759 };
760 let bottom_val = if j == 0 {
761 u[[ny - 1, i]]
762 } else {
763 u[[j - 1, i]]
764 };
765
766 let d_coeff_x = (solver.diffusion_x)(x, y, t, u_val);
768 let d2u_dx2 = (right_val - 2.0 * u_val + left_val) / (dx * dx);
769 let diffusion_term_x = d_coeff_x * d2u_dx2;
770
771 let d_coeff_y = (solver.diffusion_y)(x, y, t, u_val);
772 let d2u_dy2 = (top_val - 2.0 * u_val + bottom_val) / (dy * dy);
773 let diffusion_term_y = d_coeff_y * d2u_dy2;
774
775 let advection_term_x = if let Some(advection_x) = &solver.advection_x {
777 let a_coeff = advection_x(x, y, t, u_val);
778 let du_dx = (right_val - left_val) / (2.0 * dx);
779 a_coeff * du_dx
780 } else {
781 0.0
782 };
783
784 let advection_term_y = if let Some(advection_y) = &solver.advection_y {
785 let a_coeff = advection_y(x, y, t, u_val);
786 let du_dy = (top_val - bottom_val) / (2.0 * dy);
787 a_coeff * du_dy
788 } else {
789 0.0
790 };
791
792 let reaction_term = if let Some(reaction) = &solver.reaction_term {
794 reaction(x, y, t, u_val)
795 } else {
796 0.0
797 };
798
799 dudt[[j, i]] = diffusion_term_x
800 + diffusion_term_y
801 + advection_term_x
802 + advection_term_y
803 + reaction_term;
804 }
805 } else if let Some(j) = j_fixed {
806 let _opposite_j = if j == 0 { ny - 1 } else { 0 };
808
809 for i in 0..nx {
810 let y = y_grid[j];
811 let x = x_grid[i];
812 let u_val = u[[j, i]];
813 let t = 0.0; let left_val = if i == 0 {
817 u[[j, nx - 1]]
818 } else {
819 u[[j, i - 1]]
820 };
821 let right_val = if i == nx - 1 {
822 u[[j, 0]]
823 } else {
824 u[[j, i + 1]]
825 };
826 let top_val = if j == ny - 1 {
827 u[[0, i]]
828 } else {
829 u[[j + 1, i]]
830 };
831 let bottom_val = if j == 0 {
832 u[[ny - 1, i]]
833 } else {
834 u[[j - 1, i]]
835 };
836
837 let d_coeff_x = (solver.diffusion_x)(x, y, t, u_val);
839 let d2u_dx2 = (right_val - 2.0 * u_val + left_val) / (dx * dx);
840 let diffusion_term_x = d_coeff_x * d2u_dx2;
841
842 let d_coeff_y = (solver.diffusion_y)(x, y, t, u_val);
843 let d2u_dy2 = (top_val - 2.0 * u_val + bottom_val) / (dy * dy);
844 let diffusion_term_y = d_coeff_y * d2u_dy2;
845
846 let advection_term_x = if let Some(advection_x) = &solver.advection_x {
848 let a_coeff = advection_x(x, y, t, u_val);
849 let du_dx = (right_val - left_val) / (2.0 * dx);
850 a_coeff * du_dx
851 } else {
852 0.0
853 };
854
855 let advection_term_y = if let Some(advection_y) = &solver.advection_y {
856 let a_coeff = advection_y(x, y, t, u_val);
857 let du_dy = (top_val - bottom_val) / (2.0 * dy);
858 a_coeff * du_dy
859 } else {
860 0.0
861 };
862
863 let reaction_term = if let Some(reaction) = &solver.reaction_term {
865 reaction(x, y, t, u_val)
866 } else {
867 0.0
868 };
869
870 dudt[[j, i]] = diffusion_term_x
871 + diffusion_term_y
872 + advection_term_x
873 + advection_term_y
874 + reaction_term;
875 }
876 }
877 }
878 }
879}
880
881#[allow(dead_code)]
883fn apply_dirichlet_conditions_to_initial(
884 u0: &mut Array2<f64>,
885 boundary_conditions: &[BoundaryCondition<f64>],
886 x_grid: &Array1<f64>,
887 y_grid: &Array1<f64>,
888) {
889 let nx = x_grid.len();
890 let ny = y_grid.len();
891
892 for bc in boundary_conditions {
893 if bc.bc_type == BoundaryConditionType::Dirichlet {
894 match (bc.dimension, bc.location) {
895 (0, BoundaryLocation::Lower) => {
896 for j in 0..ny {
898 u0[[j, 0]] = bc.value;
899 }
900 }
901 (0, BoundaryLocation::Upper) => {
902 for j in 0..ny {
904 u0[[j, nx - 1]] = bc.value;
905 }
906 }
907 (1, BoundaryLocation::Lower) => {
908 for i in 0..nx {
910 u0[[0, i]] = bc.value;
911 }
912 }
913 (1, BoundaryLocation::Upper) => {
914 for i in 0..nx {
916 u0[[ny - 1, i]] = bc.value;
917 }
918 }
919 _ => {
920 }
923 }
924 }
925 }
926}
927
928impl From<MOL2DResult> for PDESolution<f64> {
930 fn from(result: MOL2DResult) -> Self {
931 let mut grids = Vec::new();
932
933 grids.push(result.t.clone());
935
936 let nt = result.t.len();
938 let ny = result.u.shape()[1];
939 let nx = result.u.shape()[2];
940
941 let y_grid = Array1::linspace(0.0, 1.0, ny);
943 let x_grid = Array1::linspace(0.0, 1.0, nx);
944 grids.push(y_grid);
945 grids.push(x_grid);
946
947 let mut values = Vec::new();
949 for t_idx in 0..nt {
950 let time_slice = result.u.slice(s![t_idx, .., ..]).to_owned();
951 values.push(time_slice);
952 }
953
954 let info = PDESolverInfo {
956 num_iterations: 0, computation_time: result.computation_time,
958 residual_norm: None,
959 convergence_history: None,
960 method: "Method of Lines (2D)".to_string(),
961 };
962
963 PDESolution {
964 grids,
965 values,
966 error_estimate: None,
967 info,
968 }
969 }
970}