1use 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#[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
49pub 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 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
140fn 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
203fn 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
223fn 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
258pub 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}