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.clone().into_shape_with_order(nx * ny).unwrap();
193
194 let x_grid_closure = x_grid.clone();
196 let y_grid_closure = y_grid.clone();
197
198 let x_grid_apply = x_grid.clone();
200 let y_grid_apply = y_grid.clone();
201
202 let ode_options = ODEOptions {
204 method: self.options.ode_method,
205 rtol: self.options.rtol,
206 atol: self.options.atol,
207 max_steps: self.options.max_steps.unwrap_or(500),
208 h0: None,
209 max_step: None,
210 min_step: None,
211 dense_output: true,
212 max_order: None,
213 jac: None,
214 use_banded_jacobian: false,
215 ml: None,
216 mu: None,
217 mass_matrix: None,
218 jacobian_strategy: None,
219 };
220
221 let time_range = self.time_range;
222 let boundary_conditions = self.boundary_conditions.clone();
223
224 let solver = self;
226
227 let ode_func = move |t: f64, u_flat: ArrayView1<f64>| -> Array1<f64> {
229 let u = u_flat.into_shape_with_order((ny, nx)).unwrap();
231 let mut dudt = Array2::zeros((ny, nx));
232
233 for j in 1..ny - 1 {
235 for i in 1..nx - 1 {
236 let y = y_grid[j];
237 let x = x_grid[i];
238 let u_val = u[[j, i]];
239
240 let d2u_dx2 = (u[[j, i + 1]] - 2.0 * u[[j, i]] + u[[j, i - 1]]) / (dx * dx);
242 let d2u_dy2 = (u[[j + 1, i]] - 2.0 * u[[j, i]] + u[[j - 1, i]]) / (dy * dy);
243
244 let diffusion_term_x = (solver.diffusion_x)(x, y, t, u_val) * d2u_dx2;
245 let diffusion_term_y = (solver.diffusion_y)(x, y, t, u_val) * d2u_dy2;
246
247 let advection_term_x = if let Some(advection_x) = &solver.advection_x {
249 let du_dx = (u[[j, i + 1]] - u[[j, i - 1]]) / (2.0 * dx);
250 advection_x(x, y, t, u_val) * du_dx
251 } else {
252 0.0
253 };
254
255 let advection_term_y = if let Some(advection_y) = &solver.advection_y {
256 let du_dy = (u[[j + 1, i]] - u[[j - 1, i]]) / (2.0 * dy);
257 advection_y(x, y, t, u_val) * du_dy
258 } else {
259 0.0
260 };
261
262 let reaction_val = if let Some(reaction) = &solver.reaction_term {
264 reaction(x, y, t, u_val)
265 } else {
266 0.0
267 };
268
269 dudt[[j, i]] = diffusion_term_x
270 + diffusion_term_y
271 + advection_term_x
272 + advection_term_y
273 + reaction_val;
274 }
275 }
276
277 for bc in &solver.boundary_conditions {
279 match (bc.dimension, bc.location) {
280 (0, BoundaryLocation::Lower) => {
282 apply_boundary_condition_2d(
284 &mut dudt,
285 &u,
286 &x_grid_closure,
287 &y_grid_closure,
288 bc,
289 dx,
290 dy,
291 Some(0),
292 None,
293 None,
294 Some(ny),
295 solver,
296 );
297 }
298 (0, BoundaryLocation::Upper) => {
299 apply_boundary_condition_2d(
301 &mut dudt,
302 &u,
303 &x_grid_closure,
304 &y_grid_closure,
305 bc,
306 dx,
307 dy,
308 Some(nx - 1),
309 None,
310 None,
311 Some(ny),
312 solver,
313 );
314 }
315 (1, BoundaryLocation::Lower) => {
317 apply_boundary_condition_2d(
319 &mut dudt,
320 &u,
321 &x_grid_closure,
322 &y_grid_closure,
323 bc,
324 dx,
325 dy,
326 None,
327 Some(0),
328 Some(nx),
329 None,
330 solver,
331 );
332 }
333 (1, BoundaryLocation::Upper) => {
334 apply_boundary_condition_2d(
336 &mut dudt,
337 &u,
338 &x_grid_closure,
339 &y_grid_closure,
340 bc,
341 dx,
342 dy,
343 None,
344 Some(ny - 1),
345 Some(nx),
346 None,
347 solver,
348 );
349 }
350 _ => {
351 }
354 }
355 }
356
357 dudt.into_shape_with_order(nx * ny).unwrap()
359 };
360
361 apply_dirichlet_conditions_to_initial(
363 &mut u0,
364 &boundary_conditions,
365 &x_grid_apply,
366 &y_grid_apply,
367 );
368
369 let u0_flat = u0.clone().into_shape_with_order(nx * ny).unwrap();
370
371 let ode_result = solve_ivp(ode_func, time_range, u0_flat, Some(ode_options))?;
373
374 let computation_time = start_time.elapsed().as_secs_f64();
376
377 let t = ode_result.t.clone();
379 let nt = t.len();
380
381 let mut u_3d = Array3::zeros((nt, ny, nx));
383
384 for (time_idx, y_flat) in ode_result.y.iter().enumerate() {
385 let u_2d = y_flat.clone().into_shape_with_order((ny, nx)).unwrap();
386 for j in 0..ny {
387 for i in 0..nx {
388 u_3d[[time_idx, j, i]] = u_2d[[j, i]];
389 }
390 }
391 }
392
393 let ode_info = Some(format!(
394 "ODE steps: {}, function evaluations: {}, successful steps: {}",
395 ode_result.n_steps, ode_result.n_eval, ode_result.n_accepted,
396 ));
397
398 Ok(MOL2DResult {
399 t: ode_result.t.into(),
400 u: u_3d,
401 ode_info,
402 computation_time,
403 })
404 }
405}
406
407#[allow(clippy::too_many_arguments)]
409#[allow(dead_code)]
410fn apply_boundary_condition_2d(
411 dudt: &mut Array2<f64>,
412 u: &ArrayView2<f64>,
413 x_grid: &Array1<f64>,
414 y_grid: &Array1<f64>,
415 bc: &BoundaryCondition<f64>,
416 dx: f64,
417 dy: f64,
418 i_fixed: Option<usize>,
419 j_fixed: Option<usize>,
420 i_range: Option<usize>,
421 j_range: Option<usize>,
422 solver: &MOLParabolicSolver2D,
423) {
424 let nx = x_grid.len();
425 let ny = y_grid.len();
426
427 let i_range = i_range.unwrap_or(1);
428 let j_range = j_range.unwrap_or(1);
429
430 match bc.bc_type {
431 BoundaryConditionType::Dirichlet => {
432 if let Some(i) = i_fixed {
435 for j in 0..j_range {
436 dudt[[j, i]] = 0.0;
437 }
438 } else if let Some(j) = j_fixed {
439 for i in 0..i_range {
440 dudt[[j, i]] = 0.0;
441 }
442 }
443 }
444 BoundaryConditionType::Neumann => {
445 if let Some(i) = i_fixed {
447 for j in 1..ny - 1 {
449 let y = y_grid[j];
450 let x = x_grid[i];
451 let u_val = u[[j, i]];
452 let t = 0.0; let du_dn = bc.value; let u_ghost = if i == 0 {
460 u[[j, i]] - dx * du_dn
461 } else {
462 u[[j, i]] + dx * du_dn
463 };
464
465 let d2u_dx2 = if i == 0 {
467 (u[[j, i + 1]] - 2.0 * u[[j, i]] + u_ghost) / (dx * dx)
468 } else {
469 (u_ghost - 2.0 * u[[j, i]] + u[[j, i - 1]]) / (dx * dx)
470 };
471
472 let d2u_dy2 = (u[[j + 1, i]] - 2.0 * u[[j, i]] + u[[j - 1, i]]) / (dy * dy);
474
475 let diffusion_term_x = (solver.diffusion_x)(x, y, t, u_val) * d2u_dx2;
476 let diffusion_term_y = (solver.diffusion_y)(x, y, t, u_val) * d2u_dy2;
477
478 let advection_term_x = if let Some(advection_x) = &solver.advection_x {
480 let du_dx = if i == 0 {
482 (u[[j, i + 1]] - u[[j, i]]) / dx
483 } else {
484 (u[[j, i]] - u[[j, i - 1]]) / dx
485 };
486 advection_x(x, y, t, u_val) * du_dx
487 } else {
488 0.0
489 };
490
491 let advection_term_y = if let Some(advection_y) = &solver.advection_y {
492 let du_dy = (u[[j + 1, i]] - u[[j - 1, i]]) / (2.0 * dy);
494 advection_y(x, y, t, u_val) * du_dy
495 } else {
496 0.0
497 };
498
499 let reaction_term = if let Some(reaction) = &solver.reaction_term {
501 reaction(x, y, t, u_val)
502 } else {
503 0.0
504 };
505
506 dudt[[j, i]] = diffusion_term_x
507 + diffusion_term_y
508 + advection_term_x
509 + advection_term_y
510 + reaction_term;
511 }
512 } else if let Some(j) = j_fixed {
513 for i in 1..nx - 1 {
515 let y = y_grid[j];
516 let x = x_grid[i];
517 let u_val = u[[j, i]];
518 let t = 0.0; let du_dn = bc.value; let u_ghost = if j == 0 {
526 u[[j, i]] - dy * du_dn
527 } else {
528 u[[j, i]] + dy * du_dn
529 };
530
531 let d2u_dy2 = if j == 0 {
533 (u[[j + 1, i]] - 2.0 * u[[j, i]] + u_ghost) / (dy * dy)
534 } else {
535 (u_ghost - 2.0 * u[[j, i]] + u[[j - 1, i]]) / (dy * dy)
536 };
537
538 let d2u_dx2 = (u[[j, i + 1]] - 2.0 * u[[j, i]] + u[[j, i - 1]]) / (dx * dx);
540
541 let diffusion_term_x = (solver.diffusion_x)(x, y, t, u_val) * d2u_dx2;
542 let diffusion_term_y = (solver.diffusion_y)(x, y, t, u_val) * d2u_dy2;
543
544 let advection_term_x = if let Some(advection_x) = &solver.advection_x {
546 let du_dx = (u[[j, i + 1]] - u[[j, i - 1]]) / (2.0 * dx);
548 advection_x(x, y, t, u_val) * du_dx
549 } else {
550 0.0
551 };
552
553 let advection_term_y = if let Some(advection_y) = &solver.advection_y {
554 let du_dy = if j == 0 {
556 (u[[j + 1, i]] - u[[j, i]]) / dy
557 } else {
558 (u[[j, i]] - u[[j - 1, i]]) / dy
559 };
560 advection_y(x, y, t, u_val) * du_dy
561 } else {
562 0.0
563 };
564
565 let reaction_term = if let Some(reaction) = &solver.reaction_term {
567 reaction(x, y, t, u_val)
568 } else {
569 0.0
570 };
571
572 dudt[[j, i]] = diffusion_term_x
573 + diffusion_term_y
574 + advection_term_x
575 + advection_term_y
576 + reaction_term;
577 }
578 }
579 }
580 BoundaryConditionType::Robin => {
581 if let Some([a, b, c]) = bc.coefficients {
583 if let Some(i) = i_fixed {
584 for j in 1..ny - 1 {
586 let y = y_grid[j];
587 let x = x_grid[i];
588 let u_val = u[[j, i]];
589 let t = 0.0; let du_dn = (c - a * u_val) / b;
593
594 let u_ghost = if i == 0 {
597 u[[j, i]] - dx * du_dn
598 } else {
599 u[[j, i]] + dx * du_dn
600 };
601
602 let d2u_dx2 = if i == 0 {
604 (u[[j, i + 1]] - 2.0 * u[[j, i]] + u_ghost) / (dx * dx)
605 } else {
606 (u_ghost - 2.0 * u[[j, i]] + u[[j, i - 1]]) / (dx * dx)
607 };
608
609 let d2u_dy2 = (u[[j + 1, i]] - 2.0 * u[[j, i]] + u[[j - 1, i]]) / (dy * dy);
611
612 let diffusion_term_x = (solver.diffusion_x)(x, y, t, u_val) * d2u_dx2;
613 let diffusion_term_y = (solver.diffusion_y)(x, y, t, u_val) * d2u_dy2;
614
615 let advection_term_x = if let Some(advection_x) = &solver.advection_x {
617 let du_dx = if i == 0 {
619 (u[[j, i + 1]] - u[[j, i]]) / dx
620 } else {
621 (u[[j, i]] - u[[j, i - 1]]) / dx
622 };
623 advection_x(x, y, t, u_val) * du_dx
624 } else {
625 0.0
626 };
627
628 let advection_term_y = if let Some(advection_y) = &solver.advection_y {
629 let du_dy = (u[[j + 1, i]] - u[[j - 1, i]]) / (2.0 * dy);
631 advection_y(x, y, t, u_val) * du_dy
632 } else {
633 0.0
634 };
635
636 let reaction_term = if let Some(reaction) = &solver.reaction_term {
638 reaction(x, y, t, u_val)
639 } else {
640 0.0
641 };
642
643 dudt[[j, i]] = diffusion_term_x
644 + diffusion_term_y
645 + advection_term_x
646 + advection_term_y
647 + reaction_term;
648 }
649 } else if let Some(j) = j_fixed {
650 for i in 1..nx - 1 {
652 let y = y_grid[j];
653 let x = x_grid[i];
654 let u_val = u[[j, i]];
655 let t = 0.0; let du_dn = (c - a * u_val) / b;
659
660 let u_ghost = if j == 0 {
663 u[[j, i]] - dy * du_dn
664 } else {
665 u[[j, i]] + dy * du_dn
666 };
667
668 let d2u_dy2 = if j == 0 {
670 (u[[j + 1, i]] - 2.0 * u[[j, i]] + u_ghost) / (dy * dy)
671 } else {
672 (u_ghost - 2.0 * u[[j, i]] + u[[j - 1, i]]) / (dy * dy)
673 };
674
675 let d2u_dx2 = (u[[j, i + 1]] - 2.0 * u[[j, i]] + u[[j, i - 1]]) / (dx * dx);
677
678 let diffusion_term_x = (solver.diffusion_x)(x, y, t, u_val) * d2u_dx2;
679 let diffusion_term_y = (solver.diffusion_y)(x, y, t, u_val) * d2u_dy2;
680
681 let advection_term_x = if let Some(advection_x) = &solver.advection_x {
683 let du_dx = (u[[j, i + 1]] - u[[j, i - 1]]) / (2.0 * dx);
685 advection_x(x, y, t, u_val) * du_dx
686 } else {
687 0.0
688 };
689
690 let advection_term_y = if let Some(advection_y) = &solver.advection_y {
691 let du_dy = if j == 0 {
693 (u[[j + 1, i]] - u[[j, i]]) / dy
694 } else {
695 (u[[j, i]] - u[[j - 1, i]]) / dy
696 };
697 advection_y(x, y, t, u_val) * du_dy
698 } else {
699 0.0
700 };
701
702 let reaction_term = if let Some(reaction) = &solver.reaction_term {
704 reaction(x, y, t, u_val)
705 } else {
706 0.0
707 };
708
709 dudt[[j, i]] = diffusion_term_x
710 + diffusion_term_y
711 + advection_term_x
712 + advection_term_y
713 + reaction_term;
714 }
715 }
716 }
717 }
718 BoundaryConditionType::Periodic => {
719 if let Some(i) = i_fixed {
723 let _opposite_i = if i == 0 { nx - 1 } else { 0 };
725
726 for j in 0..ny {
727 let y = y_grid[j];
728 let x = x_grid[i];
729 let u_val = u[[j, i]];
730 let t = 0.0; let left_val = if i == 0 {
734 u[[j, nx - 1]]
735 } else {
736 u[[j, i - 1]]
737 };
738 let right_val = if i == nx - 1 {
739 u[[j, 0]]
740 } else {
741 u[[j, i + 1]]
742 };
743 let top_val = if j == ny - 1 {
744 u[[0, i]]
745 } else {
746 u[[j + 1, i]]
747 };
748 let bottom_val = if j == 0 {
749 u[[ny - 1, i]]
750 } else {
751 u[[j - 1, i]]
752 };
753
754 let d_coeff_x = (solver.diffusion_x)(x, y, t, u_val);
756 let d2u_dx2 = (right_val - 2.0 * u_val + left_val) / (dx * dx);
757 let diffusion_term_x = d_coeff_x * d2u_dx2;
758
759 let d_coeff_y = (solver.diffusion_y)(x, y, t, u_val);
760 let d2u_dy2 = (top_val - 2.0 * u_val + bottom_val) / (dy * dy);
761 let diffusion_term_y = d_coeff_y * d2u_dy2;
762
763 let advection_term_x = if let Some(advection_x) = &solver.advection_x {
765 let a_coeff = advection_x(x, y, t, u_val);
766 let du_dx = (right_val - left_val) / (2.0 * dx);
767 a_coeff * du_dx
768 } else {
769 0.0
770 };
771
772 let advection_term_y = if let Some(advection_y) = &solver.advection_y {
773 let a_coeff = advection_y(x, y, t, u_val);
774 let du_dy = (top_val - bottom_val) / (2.0 * dy);
775 a_coeff * du_dy
776 } else {
777 0.0
778 };
779
780 let reaction_term = if let Some(reaction) = &solver.reaction_term {
782 reaction(x, y, t, u_val)
783 } else {
784 0.0
785 };
786
787 dudt[[j, i]] = diffusion_term_x
788 + diffusion_term_y
789 + advection_term_x
790 + advection_term_y
791 + reaction_term;
792 }
793 } else if let Some(j) = j_fixed {
794 let _opposite_j = if j == 0 { ny - 1 } else { 0 };
796
797 for i in 0..nx {
798 let y = y_grid[j];
799 let x = x_grid[i];
800 let u_val = u[[j, i]];
801 let t = 0.0; let left_val = if i == 0 {
805 u[[j, nx - 1]]
806 } else {
807 u[[j, i - 1]]
808 };
809 let right_val = if i == nx - 1 {
810 u[[j, 0]]
811 } else {
812 u[[j, i + 1]]
813 };
814 let top_val = if j == ny - 1 {
815 u[[0, i]]
816 } else {
817 u[[j + 1, i]]
818 };
819 let bottom_val = if j == 0 {
820 u[[ny - 1, i]]
821 } else {
822 u[[j - 1, i]]
823 };
824
825 let d_coeff_x = (solver.diffusion_x)(x, y, t, u_val);
827 let d2u_dx2 = (right_val - 2.0 * u_val + left_val) / (dx * dx);
828 let diffusion_term_x = d_coeff_x * d2u_dx2;
829
830 let d_coeff_y = (solver.diffusion_y)(x, y, t, u_val);
831 let d2u_dy2 = (top_val - 2.0 * u_val + bottom_val) / (dy * dy);
832 let diffusion_term_y = d_coeff_y * d2u_dy2;
833
834 let advection_term_x = if let Some(advection_x) = &solver.advection_x {
836 let a_coeff = advection_x(x, y, t, u_val);
837 let du_dx = (right_val - left_val) / (2.0 * dx);
838 a_coeff * du_dx
839 } else {
840 0.0
841 };
842
843 let advection_term_y = if let Some(advection_y) = &solver.advection_y {
844 let a_coeff = advection_y(x, y, t, u_val);
845 let du_dy = (top_val - bottom_val) / (2.0 * dy);
846 a_coeff * du_dy
847 } else {
848 0.0
849 };
850
851 let reaction_term = if let Some(reaction) = &solver.reaction_term {
853 reaction(x, y, t, u_val)
854 } else {
855 0.0
856 };
857
858 dudt[[j, i]] = diffusion_term_x
859 + diffusion_term_y
860 + advection_term_x
861 + advection_term_y
862 + reaction_term;
863 }
864 }
865 }
866 }
867}
868
869#[allow(dead_code)]
871fn apply_dirichlet_conditions_to_initial(
872 u0: &mut Array2<f64>,
873 boundary_conditions: &[BoundaryCondition<f64>],
874 x_grid: &Array1<f64>,
875 y_grid: &Array1<f64>,
876) {
877 let nx = x_grid.len();
878 let ny = y_grid.len();
879
880 for bc in boundary_conditions {
881 if bc.bc_type == BoundaryConditionType::Dirichlet {
882 match (bc.dimension, bc.location) {
883 (0, BoundaryLocation::Lower) => {
884 for j in 0..ny {
886 u0[[j, 0]] = bc.value;
887 }
888 }
889 (0, BoundaryLocation::Upper) => {
890 for j in 0..ny {
892 u0[[j, nx - 1]] = bc.value;
893 }
894 }
895 (1, BoundaryLocation::Lower) => {
896 for i in 0..nx {
898 u0[[0, i]] = bc.value;
899 }
900 }
901 (1, BoundaryLocation::Upper) => {
902 for i in 0..nx {
904 u0[[ny - 1, i]] = bc.value;
905 }
906 }
907 _ => {
908 }
911 }
912 }
913 }
914}
915
916impl From<MOL2DResult> for PDESolution<f64> {
918 fn from(result: MOL2DResult) -> Self {
919 let mut grids = Vec::new();
920
921 grids.push(result.t.clone());
923
924 let nt = result.t.len();
926 let ny = result.u.shape()[1];
927 let nx = result.u.shape()[2];
928
929 let y_grid = Array1::linspace(0.0, 1.0, ny);
931 let x_grid = Array1::linspace(0.0, 1.0, nx);
932 grids.push(y_grid);
933 grids.push(x_grid);
934
935 let mut values = Vec::new();
937 for t_idx in 0..nt {
938 let time_slice = result.u.slice(s![t_idx, .., ..]).to_owned();
939 values.push(time_slice);
940 }
941
942 let info = PDESolverInfo {
944 num_iterations: 0, computation_time: result.computation_time,
946 residual_norm: None,
947 convergence_history: None,
948 method: "Method of Lines (2D)".to_string(),
949 };
950
951 PDESolution {
952 grids,
953 values,
954 error_estimate: None,
955 info,
956 }
957 }
958}