mathhook_core/calculus/pde/
separation_of_variables.rs1use crate::calculus::pde::common::eigenvalue_problem::solve_sturm_liouville;
18use crate::calculus::pde::common::fourier_coefficients::compute_fourier_coefficients;
19use crate::calculus::pde::types::{BoundaryCondition, InitialCondition, Pde};
20use crate::core::{Expression, Symbol};
21use crate::expr;
22
23#[derive(Debug, Clone, PartialEq)]
25pub struct SeparatedSolution {
26 pub functions: Vec<Expression>,
28 pub constants: Vec<Expression>,
30 pub solution: Expression,
32 pub eigenvalues: Vec<Expression>,
34 pub eigenfunctions: Vec<Expression>,
36 pub coefficients: Vec<Expression>,
38}
39
40pub fn separate_variables(
83 pde: &Pde,
84 boundary_conditions: &[BoundaryCondition],
85 initial_conditions: &[InitialCondition],
86) -> Result<SeparatedSolution, String> {
87 let num_vars = pde.independent_vars.len();
88
89 if num_vars < 2 {
90 return Err("Separation of variables requires at least 2 independent variables".to_owned());
91 }
92
93 let functions = create_separated_functions(&pde.independent_vars);
94 let constants = create_separation_constants(num_vars - 1);
95 let solution = construct_product_solution(&functions);
96
97 if boundary_conditions.is_empty() {
98 return Ok(SeparatedSolution {
99 functions,
100 constants,
101 solution,
102 eigenvalues: Vec::new(),
103 eigenfunctions: Vec::new(),
104 coefficients: Vec::new(),
105 });
106 }
107
108 if boundary_conditions.len() != 2 {
109 return Err(format!(
110 "Expected exactly 2 boundary conditions, got {}",
111 boundary_conditions.len()
112 ));
113 }
114
115 let eigenvalue_solution =
116 solve_sturm_liouville(&boundary_conditions[0], &boundary_conditions[1], 10)?;
117
118 let coefficients = if initial_conditions.is_empty() {
119 Vec::new()
120 } else {
121 compute_fourier_coefficients(
122 &initial_conditions[0],
123 &eigenvalue_solution.eigenfunctions,
124 &eigenvalue_solution.domain,
125 &eigenvalue_solution.variable,
126 )?
127 };
128
129 Ok(SeparatedSolution {
130 functions,
131 constants,
132 solution,
133 eigenvalues: eigenvalue_solution.eigenvalues,
134 eigenfunctions: eigenvalue_solution.eigenfunctions,
135 coefficients,
136 })
137}
138
139pub fn construct_series_solution(
154 coefficients: &[Expression],
155 spatial_eigenfunctions: &[Expression],
156 temporal_solutions: &[Expression],
157 num_terms: usize,
158) -> Expression {
159 let mut terms = Vec::new();
160
161 let max_terms = num_terms.min(coefficients.len());
162
163 for n in 0..max_terms {
164 let c_n = &coefficients[n];
165 let x_n = &spatial_eigenfunctions[n];
166 let t_n = &temporal_solutions[n];
167
168 let term = Expression::mul(vec![c_n.clone(), x_n.clone(), t_n.clone()]);
169 terms.push(term);
170 }
171
172 if terms.is_empty() {
173 return expr!(0);
174 }
175
176 Expression::add(terms)
177}
178
179fn create_separated_functions(vars: &[Symbol]) -> Vec<Expression> {
181 vars.iter()
182 .map(|var| Expression::function("F", vec![Expression::symbol(var.clone())]))
183 .collect()
184}
185
186fn create_separation_constants(count: usize) -> Vec<Expression> {
188 (0..count)
189 .map(|i| {
190 let lambda = Symbol::new(format!("lambda_{}", i));
191 Expression::symbol(lambda)
192 })
193 .collect()
194}
195
196fn construct_product_solution(functions: &[Expression]) -> Expression {
198 if functions.is_empty() {
199 return expr!(1);
200 }
201
202 Expression::mul(functions.to_vec())
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208 use crate::{expr, symbol};
209 use std::slice::from_ref;
210
211 #[test]
212 fn test_separate_variables_basic_no_bc() {
213 let u = symbol!(u);
214 let x = symbol!(x);
215 let t = symbol!(t);
216 let equation = expr!(u);
217 let pde = Pde::new(equation, u, vec![x, t]);
218
219 let result = separate_variables(&pde, &[], &[]);
220 assert!(result.is_ok());
221
222 let solution = result.unwrap();
223 assert_eq!(solution.functions.len(), 2);
224 assert_eq!(solution.constants.len(), 1);
225 assert!(solution.eigenvalues.is_empty());
226 assert!(solution.eigenfunctions.is_empty());
227 }
228
229 #[test]
230 fn test_separate_variables_with_dirichlet_bc() {
231 let u = symbol!(u);
232 let x = symbol!(x);
233 let t = symbol!(t);
234 let equation = expr!(u);
235 let pde = Pde::new(equation, u, vec![x.clone(), t]);
236
237 let bc_left = BoundaryCondition::dirichlet_at(x.clone(), expr!(0), expr!(0));
238 let bc_right = BoundaryCondition::dirichlet_at(x, expr!(pi), expr!(0));
239 let bcs = vec![bc_left, bc_right];
240
241 let result = separate_variables(&pde, &bcs, &[]);
242 assert!(result.is_ok());
243
244 let solution = result.unwrap();
245 assert_eq!(solution.eigenvalues.len(), 10);
246 assert_eq!(solution.eigenfunctions.len(), 10);
247 }
248
249 #[test]
250 fn test_separate_variables_with_neumann_bc() {
251 let u = symbol!(u);
252 let x = symbol!(x);
253 let t = symbol!(t);
254 let equation = expr!(u);
255 let pde = Pde::new(equation, u, vec![x.clone(), t]);
256
257 let bc_left = BoundaryCondition::neumann_at(x.clone(), expr!(0), expr!(0));
258 let bc_right = BoundaryCondition::neumann_at(x, expr!(pi), expr!(0));
259 let bcs = vec![bc_left, bc_right];
260
261 let result = separate_variables(&pde, &bcs, &[]);
262 assert!(result.is_ok());
263
264 let solution = result.unwrap();
265 assert_eq!(solution.eigenvalues.len(), 10);
266 assert_eq!(solution.eigenfunctions.len(), 10);
267 }
268
269 #[test]
270 fn test_separate_variables_with_ic() {
271 let u = symbol!(u);
272 let x = symbol!(x);
273 let t = symbol!(t);
274 let equation = expr!(u);
275 let pde = Pde::new(equation, u, vec![x.clone(), t]);
276
277 let bc_left = BoundaryCondition::dirichlet_at(x.clone(), expr!(0), expr!(0));
278 let bc_right = BoundaryCondition::dirichlet_at(x.clone(), expr!(pi), expr!(0));
279 let bcs = vec![bc_left, bc_right];
280
281 let sin_x = Expression::function("sin", vec![Expression::symbol(x)]);
282 let ic = InitialCondition::value(sin_x);
283 let ics = vec![ic];
284
285 let result = separate_variables(&pde, &bcs, &ics);
286 assert!(result.is_ok());
287
288 let solution = result.unwrap();
289 assert_eq!(solution.coefficients.len(), 10);
290 }
291
292 #[test]
293 fn test_separate_variables_insufficient_bcs() {
294 let u = symbol!(u);
295 let x = symbol!(x);
296 let t = symbol!(t);
297 let equation = expr!(u);
298 let pde = Pde::new(equation, u, vec![x.clone(), t]);
299
300 let bc = BoundaryCondition::dirichlet_at(x, expr!(0), expr!(0));
301 let bcs = vec![bc];
302
303 let result = separate_variables(&pde, &bcs, &[]);
304 assert!(result.is_err());
305 }
306
307 #[test]
308 fn test_separate_variables_three_vars() {
309 let u = symbol!(u);
310 let x = symbol!(x);
311 let y = symbol!(y);
312 let t = symbol!(t);
313 let equation = expr!(u);
314 let pde = Pde::new(equation, u, vec![x, y, t]);
315
316 let result = separate_variables(&pde, &[], &[]);
317 assert!(result.is_ok());
318
319 let solution = result.unwrap();
320 assert_eq!(solution.functions.len(), 3);
321 assert_eq!(solution.constants.len(), 2);
322 }
323
324 #[test]
325 fn test_separate_variables_insufficient_vars() {
326 let u = symbol!(u);
327 let x = symbol!(x);
328 let equation = expr!(u);
329 let pde = Pde::new(equation, u, vec![x]);
330
331 let result = separate_variables(&pde, &[], &[]);
332 assert!(result.is_err());
333 }
334
335 #[test]
336 fn test_create_separated_functions() {
337 let x = symbol!(x);
338 let t = symbol!(t);
339 let vars = vec![x, t];
340
341 let functions = create_separated_functions(&vars);
342 assert_eq!(functions.len(), 2);
343 }
344
345 #[test]
346 fn test_create_separation_constants() {
347 let constants = create_separation_constants(2);
348 assert_eq!(constants.len(), 2);
349 }
350
351 #[test]
352 fn test_construct_product_solution_empty() {
353 let solution = construct_product_solution(&[]);
354 assert_eq!(solution, expr!(1));
355 }
356
357 #[test]
358 fn test_construct_product_solution_single() {
359 let x = symbol!(x);
360 let f = Expression::function("F", vec![Expression::symbol(x)]);
361 let solution = construct_product_solution(from_ref(&f));
362 assert_eq!(solution, f);
363 }
364
365 #[test]
366 fn test_construct_series_solution_single_term() {
367 let x = symbol!(x);
368 let _t = symbol!(t);
369
370 let coefficients = vec![expr!(1)];
371 let spatial = vec![Expression::function("sin", vec![Expression::symbol(x)])];
372 let temporal = vec![Expression::function("exp", vec![expr!(-t)])];
373
374 let solution = construct_series_solution(&coefficients, &spatial, &temporal, 1);
375
376 assert!(matches!(solution, Expression::Mul(_)));
377 }
378
379 #[test]
380 fn test_construct_series_solution_multiple_terms() {
381 let x = symbol!(x);
382 let t = symbol!(t);
383
384 let coefficients = vec![expr!(1), expr!(2)];
385 let spatial = vec![
386 Expression::function("sin", vec![Expression::symbol(x.clone())]),
387 Expression::function(
388 "sin",
389 vec![Expression::mul(vec![
390 Expression::integer(2),
391 Expression::symbol(x),
392 ])],
393 ),
394 ];
395 let temporal = vec![
396 Expression::function(
397 "exp",
398 vec![Expression::mul(vec![
399 Expression::integer(-1),
400 Expression::symbol(t.clone()),
401 ])],
402 ),
403 Expression::function(
404 "exp",
405 vec![Expression::mul(vec![
406 Expression::integer(-4),
407 Expression::symbol(t),
408 ])],
409 ),
410 ];
411
412 let solution = construct_series_solution(&coefficients, &spatial, &temporal, 2);
413
414 assert!(matches!(solution, Expression::Add(_)));
415 }
416
417 #[test]
418 fn test_construct_series_solution_empty() {
419 let solution = construct_series_solution(&[], &[], &[], 0);
420 assert_eq!(solution, expr!(0));
421 }
422
423 #[test]
424 fn test_separated_solution_clone() {
425 let u = symbol!(u);
426 let x = symbol!(x);
427 let t = symbol!(t);
428 let equation = expr!(u);
429 let pde = Pde::new(equation, u, vec![x, t]);
430
431 let result = separate_variables(&pde, &[], &[]);
432 assert!(result.is_ok());
433
434 let solution = result.unwrap();
435 let _cloned = solution.clone();
436 }
437}