mathhook_core/calculus/pde/standard/
heat.rs1use crate::calculus::pde::common::{
41 compute_dirichlet_1d_eigenvalues, create_symbolic_coefficients,
42};
43use crate::calculus::pde::registry::{PDEError, PDEResult, PDESolver};
44use crate::calculus::pde::types::{BoundaryCondition, InitialCondition, PDESolution, Pde, PdeType};
45use crate::core::{Expression, Symbol};
46
47#[deprecated(since = "0.1.0", note = "Use PDESolution instead")]
49#[derive(Debug, Clone, PartialEq)]
50pub struct HeatSolution {
51 pub solution: Expression,
53 pub alpha: Expression,
55 pub eigenvalues: Vec<Expression>,
57 pub coefficients: Vec<Expression>,
59}
60
61pub struct HeatEquationSolver {
63 max_terms: usize,
64}
65
66impl HeatEquationSolver {
67 pub fn new() -> Self {
69 Self { max_terms: 10 }
70 }
71
72 pub fn with_max_terms(max_terms: usize) -> Self {
74 Self { max_terms }
75 }
76
77 #[allow(deprecated, unused_variables)]
99 pub fn solve_heat_equation_1d(
100 &self,
101 pde: &Pde,
102 alpha: &Expression,
103 boundary_conditions: &[BoundaryCondition],
104 initial_condition: &InitialCondition,
105 ) -> Result<HeatSolution, PDEError> {
106 if pde.independent_vars.len() != 2 {
107 return Err(PDEError::InvalidForm {
108 reason: "1D heat equation requires exactly 2 independent variables (x, t)"
109 .to_owned(),
110 });
111 }
112
113 let eigenvalues = compute_dirichlet_1d_eigenvalues(
114 boundary_conditions,
115 &pde.independent_vars[0],
116 self.max_terms,
117 )?;
118
119 let coefficients = create_symbolic_coefficients("A", eigenvalues.len())?;
120
121 let solution =
122 self.construct_heat_solution(&pde.independent_vars, alpha, &eigenvalues, &coefficients);
123
124 Ok(HeatSolution {
125 solution,
126 alpha: alpha.clone(),
127 eigenvalues,
128 coefficients,
129 })
130 }
131
132 fn construct_heat_solution(
133 &self,
134 vars: &[Symbol],
135 alpha: &Expression,
136 eigenvalues: &[Expression],
137 coefficients: &[Expression],
138 ) -> Expression {
139 let x = &vars[0];
140 let t = &vars[1];
141
142 if eigenvalues.is_empty() || coefficients.is_empty() {
143 return Expression::integer(0);
144 }
145
146 let mut terms = Vec::new();
147
148 for (lambda, a_n) in eigenvalues.iter().zip(coefficients.iter()) {
149 let spatial = Expression::function(
150 "sin",
151 vec![Expression::mul(vec![
152 Expression::pow(lambda.clone(), Expression::rational(1, 2)),
153 Expression::symbol(x.clone()),
154 ])],
155 );
156
157 let temporal = Expression::function(
158 "exp",
159 vec![Expression::mul(vec![
160 Expression::integer(-1),
161 lambda.clone(),
162 alpha.clone(),
163 Expression::symbol(t.clone()),
164 ])],
165 );
166
167 let term = Expression::mul(vec![a_n.clone(), spatial, temporal]);
168 terms.push(term);
169 }
170
171 Expression::add(terms)
172 }
173}
174
175impl PDESolver for HeatEquationSolver {
176 #[allow(deprecated)]
177 fn solve(&self, pde: &Pde) -> PDEResult {
178 use crate::expr;
179
180 let alpha = expr!(1);
181 let ic = InitialCondition::value(expr!(1));
182
183 let legacy_sol = self.solve_heat_equation_1d(pde, &alpha, &[], &ic)?;
184
185 Ok(PDESolution::heat(
186 legacy_sol.solution,
187 legacy_sol.alpha,
188 legacy_sol.eigenvalues,
189 legacy_sol.coefficients,
190 ))
191 }
192
193 fn can_solve(&self, pde_type: PdeType) -> bool {
194 matches!(pde_type, PdeType::Parabolic)
195 }
196
197 fn priority(&self) -> u8 {
198 100
199 }
200
201 fn name(&self) -> &'static str {
202 "Heat Equation Solver"
203 }
204
205 fn description(&self) -> &'static str {
206 "Solves heat equation ∂u/∂t = α∇²u using separation of variables and Fourier series"
207 }
208}
209
210impl Default for HeatEquationSolver {
211 fn default() -> Self {
212 Self::new()
213 }
214}
215
216#[allow(deprecated, unused_variables)]
217#[deprecated(
218 since = "0.1.0",
219 note = "Use HeatEquationSolver::new().solve() instead"
220)]
221pub fn solve_heat_equation_1d(
222 pde: &Pde,
223 alpha: &Expression,
224 boundary_conditions: &[BoundaryCondition],
225 initial_condition: &InitialCondition,
226) -> Result<HeatSolution, String> {
227 HeatEquationSolver::new()
228 .solve_heat_equation_1d(pde, alpha, boundary_conditions, initial_condition)
229 .map_err(|e| format!("{:?}", e))
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235 use crate::calculus::pde::types::BoundaryLocation;
236 use crate::{expr, symbol};
237
238 #[test]
239 fn test_heat_solver_creation() {
240 let solver = HeatEquationSolver::new();
241 assert_eq!(solver.name(), "Heat Equation Solver");
242 assert_eq!(solver.priority(), 100);
243 }
244
245 #[test]
246 fn test_heat_solver_can_solve() {
247 let solver = HeatEquationSolver::new();
248 assert!(solver.can_solve(PdeType::Parabolic));
249 assert!(!solver.can_solve(PdeType::Hyperbolic));
250 assert!(!solver.can_solve(PdeType::Elliptic));
251 }
252
253 #[test]
254 #[allow(deprecated)]
255 fn test_solve_heat_equation_1d_basic() {
256 let u = symbol!(u);
257 let x = symbol!(x);
258 let t = symbol!(t);
259 let equation = expr!(u);
260 let pde = Pde::new(equation, u, vec![x.clone(), t]);
261 let alpha = expr!(1);
262
263 let bc1 = BoundaryCondition::dirichlet(
264 expr!(0),
265 BoundaryLocation::Simple {
266 variable: x.clone(),
267 value: expr!(0),
268 },
269 );
270 let bc2 = BoundaryCondition::dirichlet(
271 expr!(0),
272 BoundaryLocation::Simple {
273 variable: x,
274 value: expr!(1),
275 },
276 );
277
278 let ic = InitialCondition::value(expr!(1));
279
280 let solver = HeatEquationSolver::new();
281 let result = solver.solve_heat_equation_1d(&pde, &alpha, &[bc1, bc2], &ic);
282 assert!(result.is_ok());
283
284 let solution = result.unwrap();
285 assert_eq!(solution.alpha, alpha);
286 assert!(!solution.eigenvalues.is_empty());
287 assert!(!solution.coefficients.is_empty());
288 }
289
290 #[test]
291 #[allow(deprecated)]
292 fn test_solve_heat_equation_wrong_dimensions() {
293 let u = symbol!(u);
294 let x = symbol!(x);
295 let equation = expr!(u);
296 let pde = Pde::new(equation, u, vec![x]);
297 let alpha = expr!(1);
298 let ic = InitialCondition::value(expr!(1));
299
300 let solver = HeatEquationSolver::new();
301 let result = solver.solve_heat_equation_1d(&pde, &alpha, &[], &ic);
302 assert!(result.is_err());
303 }
304
305 #[test]
306 #[allow(deprecated)]
307 fn test_heat_solution_structure() {
308 let u = symbol!(u);
309 let x = symbol!(x);
310 let t = symbol!(t);
311 let equation = expr!(u);
312 let pde = Pde::new(equation, u, vec![x.clone(), t]);
313 let alpha = expr!(1);
314
315 let bc = BoundaryCondition::dirichlet(
316 expr!(0),
317 BoundaryLocation::Simple {
318 variable: x,
319 value: expr!(0),
320 },
321 );
322 let ic = InitialCondition::value(expr!(1));
323
324 let solver = HeatEquationSolver::new();
325 let result = solver.solve_heat_equation_1d(&pde, &alpha, &[bc], &ic);
326 assert!(result.is_ok());
327
328 let solution = result.unwrap();
329 assert_eq!(solution.eigenvalues.len(), solution.coefficients.len());
330 }
331
332 #[test]
333 #[allow(deprecated)]
334 fn test_heat_solution_clone() {
335 let solution = HeatSolution {
336 solution: expr!(1),
337 alpha: expr!(1),
338 eigenvalues: vec![expr!(1)],
339 coefficients: vec![expr!(1)],
340 };
341
342 let _cloned = solution.clone();
343 }
344
345 #[test]
346 fn test_pde_solver_trait() {
347 let solver = HeatEquationSolver::new();
348 let u = symbol!(u);
349 let x = symbol!(x);
350 let t = symbol!(t);
351 let equation = expr!(u);
352 let pde = Pde::new(equation, u, vec![x, t]);
353
354 let result = solver.solve(&pde);
355 assert!(result.is_ok());
356
357 let solution = result.unwrap();
358 match solution.metadata {
359 crate::calculus::pde::types::SolutionMetadata::Heat {
360 alpha,
361 eigenvalues,
362 coefficients,
363 } => {
364 assert_eq!(alpha, expr!(1));
365 assert!(!eigenvalues.is_empty());
366 assert!(!coefficients.is_empty());
367 }
368 _ => panic!("Expected Heat metadata"),
369 }
370 }
371
372 #[test]
373 #[allow(deprecated)]
374 fn test_legacy_function() {
375 let u = symbol!(u);
376 let x = symbol!(x);
377 let t = symbol!(t);
378 let equation = expr!(u);
379 let pde = Pde::new(equation, u, vec![x, t]);
380 let alpha = expr!(1);
381 let ic = InitialCondition::value(expr!(1));
382
383 let result = solve_heat_equation_1d(&pde, &alpha, &[], &ic);
384 assert!(result.is_ok());
385 }
386}