mathhook_core/calculus/pde/standard/
laplace.rs

1//! Laplace equation solver
2//!
3//! Solves the Laplace equation: ∇²u = 0
4//!
5//! ⚠️ **CURRENT LIMITATION**: Returns solutions with symbolic Fourier coefficients
6//! (C₁, C₂, C₃, ...). Numerical evaluation of these coefficients requires symbolic
7//! integration, which is not yet implemented in MathHook.
8//!
9//! **What you get**: Correct solution structure `u(x,y) = Σ Cₙ sin(λₙx) sinh(λₙy)`
10//! where λₙ = nπ/a are correctly computed eigenvalues
11//!
12//! **What's missing**: Actual values of Cₙ computed from boundary conditions
13//!
14//! # Examples
15//!
16//! ```rust
17//! // This returns a solution with correctly computed eigenvalues
18//! // but symbolic coefficients C_1, C_2, C_3, ...
19//! # use mathhook_core::calculus::pde::standard::laplace::LaplaceEquationSolver;
20//! # use mathhook_core::calculus::pde::types::{Pde, BoundaryCondition, BoundaryLocation};
21//! # use mathhook_core::{symbol, expr};
22//! # let solver = LaplaceEquationSolver::new();
23//! # let u = symbol!(u);
24//! # let x = symbol!(x);
25//! # let y = symbol!(y);
26//! # let equation = expr!(u);
27//! # let pde = Pde::new(equation, u, vec![x.clone(), y.clone()]);
28//! # let bc1 = BoundaryCondition::dirichlet(expr!(0), BoundaryLocation::Simple { variable: x.clone(), value: expr!(0) });
29//! # let bc2 = BoundaryCondition::dirichlet(expr!(0), BoundaryLocation::Simple { variable: x, value: expr!(1) });
30//! let solution = solver.solve_laplace_equation_2d(&pde, &[bc1, bc2]);
31//! // solution.x_eigenvalues = [π, 2π, 3π, ...]  (correctly computed)
32//! // solution.coefficients = [C_1, C_2, C_3, ...]  (symbolic, not computed)
33//! ```
34
35use crate::calculus::pde::registry::{PDEError, PDEResult, PDESolver};
36use crate::calculus::pde::types::{BoundaryCondition, PDESolution, Pde, PdeType};
37use crate::core::{Expression, Symbol};
38use crate::expr;
39
40/// Solution to the Laplace equation
41#[derive(Debug, Clone, PartialEq)]
42pub struct LaplaceSolution {
43    pub solution: Expression,
44    pub x_eigenvalues: Vec<Expression>,
45    pub y_eigenvalues: Vec<Expression>,
46    pub coefficients: Vec<Expression>,
47}
48
49/// Laplace equation solver implementing PDESolver trait
50pub struct LaplaceEquationSolver {
51    max_terms: usize,
52}
53
54impl LaplaceEquationSolver {
55    pub fn new() -> Self {
56        Self { max_terms: 10 }
57    }
58
59    pub fn with_max_terms(max_terms: usize) -> Self {
60        Self { max_terms }
61    }
62
63    /// Solves the 2D Laplace equation on a rectangle
64    ///
65    /// For the Laplace equation: ∂²u/∂x² + ∂²u/∂y² = 0
66    /// on a rectangular domain `[0,a]` × `[0,b]` with Dirichlet boundary conditions
67    ///
68    /// # Mathematical Background
69    ///
70    /// Eigenvalues for rectangular domain with Dirichlet BCs:
71    /// - x-direction: λₙ = nπ/a for n = 1, 2, 3, ...
72    /// - y-direction: μₘ = mπ/b for m = 1, 2, 3, ...
73    ///
74    /// Solution form: u(x,y) = Σ Cₙ sin(λₙx) sinh(λₙy)
75    pub fn solve_laplace_equation_2d(
76        &self,
77        pde: &Pde,
78        boundary_conditions: &[BoundaryCondition],
79    ) -> Result<LaplaceSolution, PDEError> {
80        if pde.independent_vars.len() != 2 {
81            return Err(PDEError::InvalidForm {
82                reason: "Laplace equation solver requires exactly 2 independent variables"
83                    .to_owned(),
84            });
85        }
86
87        let x_var = &pde.independent_vars[0];
88        let y_var = &pde.independent_vars[1];
89
90        let (x_eigs, y_eigs) =
91            compute_eigenvalues(boundary_conditions, x_var, y_var, self.max_terms)?;
92        let coefficients = compute_coefficients(boundary_conditions, &x_eigs)?;
93
94        let solution =
95            construct_laplace_solution(&pde.independent_vars, &x_eigs, &y_eigs, &coefficients);
96
97        Ok(LaplaceSolution {
98            solution,
99            x_eigenvalues: x_eigs,
100            y_eigenvalues: y_eigs,
101            coefficients,
102        })
103    }
104}
105
106impl PDESolver for LaplaceEquationSolver {
107    fn solve(&self, pde: &Pde) -> PDEResult {
108        let result = self.solve_laplace_equation_2d(pde, &[])?;
109
110        Ok(PDESolution::laplace(
111            result.solution,
112            result.x_eigenvalues,
113            result.coefficients,
114        ))
115    }
116
117    fn can_solve(&self, pde_type: PdeType) -> bool {
118        matches!(pde_type, PdeType::Elliptic)
119    }
120
121    fn priority(&self) -> u8 {
122        100
123    }
124
125    fn name(&self) -> &'static str {
126        "Laplace Equation Solver"
127    }
128
129    fn description(&self) -> &'static str {
130        "Solves Laplace equation ∇²u = 0 on rectangular domains"
131    }
132}
133
134impl Default for LaplaceEquationSolver {
135    fn default() -> Self {
136        Self::new()
137    }
138}
139
140/// Computes eigenvalues for 2D Laplace equation
141///
142/// For rectangular domain `[0,a]` × `[0,b]` with Dirichlet BCs:
143/// - λₙ = nπ/a for x-direction (n = 1, 2, 3, ...)
144/// - μₘ = mπ/b for y-direction (m = 1, 2, 3, ...)
145///
146/// # Arguments
147/// * `boundary_conditions` - Boundary conditions (used to extract domain dimensions)
148/// * `x_var` - x spatial variable
149/// * `y_var` - y spatial variable
150/// * `max_terms` - Maximum number of terms to generate
151///
152/// # Returns
153/// Tuple of (x_eigenvalues, y_eigenvalues) as actual expressions λₙ = nπ/a, μₘ = mπ/b
154fn compute_eigenvalues(
155    boundary_conditions: &[BoundaryCondition],
156    x_var: &Symbol,
157    y_var: &Symbol,
158    max_terms: usize,
159) -> Result<(Vec<Expression>, Vec<Expression>), PDEError> {
160    use crate::calculus::pde::common::extract_domain_length;
161
162    let x_length = if boundary_conditions.is_empty() {
163        expr!(1)
164    } else {
165        extract_domain_length(boundary_conditions, x_var)?
166    };
167
168    let y_length = if boundary_conditions.is_empty() {
169        expr!(1)
170    } else {
171        extract_domain_length(boundary_conditions, y_var)?
172    };
173
174    let x_eigenvalues: Vec<_> = (1..=max_terms)
175        .map(|n| {
176            let n_expr = Expression::integer(n as i64);
177            let pi = Expression::pi();
178
179            Expression::mul(vec![
180                n_expr,
181                pi,
182                Expression::pow(x_length.clone(), Expression::integer(-1)),
183            ])
184        })
185        .collect();
186
187    let y_eigenvalues: Vec<_> = (1..=max_terms)
188        .map(|m| {
189            let m_expr = Expression::integer(m as i64);
190            let pi = Expression::pi();
191
192            Expression::mul(vec![
193                m_expr,
194                pi,
195                Expression::pow(y_length.clone(), Expression::integer(-1)),
196            ])
197        })
198        .collect();
199
200    Ok((x_eigenvalues, y_eigenvalues))
201}
202
203/// Computes Fourier coefficients for Laplace equation
204///
205/// Returns symbolic coefficients C₁, C₂, C₃, ... for boundary condition matching.
206///
207/// ⚠️ **LIMITATION**: Returns symbolic placeholders. Numerical values require
208/// symbolic integration of boundary data (not yet implemented).
209fn compute_coefficients(
210    _boundary_conditions: &[BoundaryCondition],
211    x_eigenvalues: &[Expression],
212) -> Result<Vec<Expression>, PDEError> {
213    let coefficients: Vec<_> = (0..x_eigenvalues.len())
214        .map(|i| {
215            let symbol = Symbol::new(format!("C_{}", i + 1));
216            Expression::symbol(symbol)
217        })
218        .collect();
219
220    Ok(coefficients)
221}
222
223/// Construct the general solution to the Laplace equation
224///
225/// Solution form: u(x,y) = Σ Cₙ sin(λₙx) sinh(λₙy)
226/// or u(x,y) = Σ Cₙ sin(λₙx) [sinh(λₙy) / sinh(λₙb)]
227/// depending on which boundary has non-homogeneous condition
228fn construct_laplace_solution(
229    vars: &[Symbol],
230    x_eigenvalues: &[Expression],
231    _y_eigenvalues: &[Expression],
232    coefficients: &[Expression],
233) -> Expression {
234    let x = &vars[0];
235    let y = &vars[1];
236
237    if x_eigenvalues.is_empty() || coefficients.is_empty() {
238        return Expression::integer(0);
239    }
240
241    let lambda = &x_eigenvalues[0];
242    let c = &coefficients[0];
243
244    let x_part = Expression::function(
245        "sin",
246        vec![Expression::mul(vec![
247            lambda.clone(),
248            Expression::symbol(x.clone()),
249        ])],
250    );
251
252    let y_arg = Expression::mul(vec![lambda.clone(), Expression::symbol(y.clone())]);
253    let y_part = Expression::function("sinh", vec![y_arg]);
254
255    Expression::mul(vec![c.clone(), x_part, y_part])
256}
257
258/// Legacy function for backward compatibility
259pub fn solve_laplace_2d(
260    pde: &Pde,
261    boundary_conditions: &[BoundaryCondition],
262) -> Result<LaplaceSolution, String> {
263    LaplaceEquationSolver::new()
264        .solve_laplace_equation_2d(pde, boundary_conditions)
265        .map_err(|e| format!("{:?}", e))
266}
267
268#[cfg(test)]
269mod tests {
270    use super::*;
271    use crate::calculus::pde::types::BoundaryLocation;
272    use crate::{expr, symbol};
273
274    #[test]
275    fn test_laplace_solver_creation() {
276        let solver = LaplaceEquationSolver::new();
277        assert_eq!(solver.name(), "Laplace Equation Solver");
278        assert_eq!(solver.priority(), 100);
279    }
280
281    #[test]
282    fn test_laplace_solver_can_solve() {
283        let solver = LaplaceEquationSolver::new();
284        assert!(solver.can_solve(PdeType::Elliptic));
285        assert!(!solver.can_solve(PdeType::Parabolic));
286        assert!(!solver.can_solve(PdeType::Hyperbolic));
287    }
288
289    #[test]
290    fn test_solve_laplace_2d_basic() {
291        let u = symbol!(u);
292        let x = symbol!(x);
293        let y = symbol!(y);
294        let equation = expr!(u);
295        let pde = Pde::new(equation, u, vec![x.clone(), y.clone()]);
296
297        let bc1 = BoundaryCondition::dirichlet(
298            expr!(0),
299            BoundaryLocation::Simple {
300                variable: x.clone(),
301                value: expr!(0),
302            },
303        );
304        let bc2 = BoundaryCondition::dirichlet(
305            expr!(0),
306            BoundaryLocation::Simple {
307                variable: x.clone(),
308                value: expr!(1),
309            },
310        );
311        let bc3 = BoundaryCondition::dirichlet(
312            expr!(0),
313            BoundaryLocation::Simple {
314                variable: y.clone(),
315                value: expr!(0),
316            },
317        );
318        let bc4 = BoundaryCondition::dirichlet(
319            Expression::function("f", vec![Expression::symbol(x)]),
320            BoundaryLocation::Simple {
321                variable: y,
322                value: expr!(1),
323            },
324        );
325
326        let result = solve_laplace_2d(&pde, &[bc1, bc2, bc3, bc4]);
327        assert!(result.is_ok());
328
329        let solution = result.unwrap();
330        assert!(!solution.x_eigenvalues.is_empty());
331        assert!(!solution.y_eigenvalues.is_empty());
332        assert!(!solution.coefficients.is_empty());
333    }
334
335    #[test]
336    fn test_solve_laplace_wrong_dimensions() {
337        let u = symbol!(u);
338        let x = symbol!(x);
339        let equation = expr!(u);
340        let pde = Pde::new(equation, u, vec![x.clone()]);
341
342        let bc = BoundaryCondition::dirichlet(
343            expr!(0),
344            BoundaryLocation::Simple {
345                variable: x,
346                value: expr!(0),
347            },
348        );
349
350        let result = solve_laplace_2d(&pde, &[bc]);
351        assert!(result.is_err());
352    }
353
354    #[test]
355    fn test_solve_laplace_insufficient_bc() {
356        let u = symbol!(u);
357        let x = symbol!(x);
358        let y = symbol!(y);
359        let equation = expr!(u);
360        let pde = Pde::new(equation, u, vec![x.clone(), y.clone()]);
361
362        let bc1 = BoundaryCondition::dirichlet(
363            expr!(0),
364            BoundaryLocation::Simple {
365                variable: x,
366                value: expr!(0),
367            },
368        );
369        let bc2 = BoundaryCondition::dirichlet(
370            expr!(0),
371            BoundaryLocation::Simple {
372                variable: y,
373                value: expr!(0),
374            },
375        );
376
377        let result = solve_laplace_2d(&pde, &[bc1, bc2]);
378        assert!(result.is_ok());
379    }
380
381    #[test]
382    fn test_construct_laplace_solution() {
383        let x = symbol!(x);
384        let y = symbol!(y);
385        let vars = vec![x, y];
386        let x_eigenvalues = vec![expr!(1)];
387        let y_eigenvalues = vec![expr!(1)];
388        let coefficients = vec![Expression::symbol(symbol!(C_0))];
389
390        let solution =
391            construct_laplace_solution(&vars, &x_eigenvalues, &y_eigenvalues, &coefficients);
392        match solution {
393            Expression::Mul(_) => (),
394            _ => panic!("Expected multiplication expression for Laplace solution"),
395        }
396    }
397
398    #[test]
399    fn test_laplace_solution_structure() {
400        let u = symbol!(u);
401        let x = symbol!(x);
402        let y = symbol!(y);
403        let equation = expr!(u);
404        let pde = Pde::new(equation, u, vec![x.clone(), y.clone()]);
405
406        let bc1 = BoundaryCondition::dirichlet(
407            expr!(0),
408            BoundaryLocation::Simple {
409                variable: x.clone(),
410                value: expr!(0),
411            },
412        );
413        let bc2 = BoundaryCondition::dirichlet(
414            expr!(0),
415            BoundaryLocation::Simple {
416                variable: x.clone(),
417                value: expr!(1),
418            },
419        );
420        let bc3 = BoundaryCondition::dirichlet(
421            expr!(0),
422            BoundaryLocation::Simple {
423                variable: y.clone(),
424                value: expr!(0),
425            },
426        );
427        let bc4 = BoundaryCondition::dirichlet(
428            Expression::function("f", vec![Expression::symbol(x)]),
429            BoundaryLocation::Simple {
430                variable: y,
431                value: expr!(1),
432            },
433        );
434
435        let result = solve_laplace_2d(&pde, &[bc1, bc2, bc3, bc4]);
436        assert!(result.is_ok());
437
438        let solution = result.unwrap();
439        assert_eq!(solution.x_eigenvalues.len(), solution.coefficients.len());
440    }
441
442    #[test]
443    fn test_laplace_eigenvalues_actual_values() {
444        let u = symbol!(u);
445        let x = symbol!(x);
446        let y = symbol!(y);
447        let equation = expr!(u);
448        let pde = Pde::new(equation, u, vec![x.clone(), y.clone()]);
449
450        let bc1 = BoundaryCondition::dirichlet(
451            expr!(0),
452            BoundaryLocation::Simple {
453                variable: x.clone(),
454                value: expr!(0),
455            },
456        );
457        let bc2 = BoundaryCondition::dirichlet(
458            expr!(0),
459            BoundaryLocation::Simple {
460                variable: x.clone(),
461                value: expr!(1),
462            },
463        );
464
465        let solver = LaplaceEquationSolver::with_max_terms(3);
466        let result = solver.solve_laplace_equation_2d(&pde, &[bc1, bc2]);
467        assert!(result.is_ok());
468
469        let solution = result.unwrap();
470
471        assert_eq!(solution.x_eigenvalues.len(), 3);
472        assert_eq!(solution.y_eigenvalues.len(), 3);
473    }
474
475    #[test]
476    fn test_laplace_solution_clone() {
477        let solution = LaplaceSolution {
478            solution: expr!(1),
479            x_eigenvalues: vec![expr!(1)],
480            y_eigenvalues: vec![expr!(1)],
481            coefficients: vec![expr!(1)],
482        };
483
484        let _cloned = solution.clone();
485    }
486
487    #[test]
488    fn test_pde_solver_trait() {
489        let solver = LaplaceEquationSolver::new();
490        let u = symbol!(u);
491        let x = symbol!(x);
492        let y = symbol!(y);
493        let equation = expr!(u);
494        let pde = Pde::new(equation, u, vec![x, y]);
495
496        let result = solver.solve(&pde);
497        assert!(result.is_ok());
498    }
499}