mathhook_core/calculus/pde/
method_of_characteristics.rs1use crate::calculus::pde::types::Pde;
41use crate::core::{Expression, Symbol};
42
43#[derive(Debug, Clone, PartialEq)]
45pub struct CharacteristicSolution {
46 pub characteristic_equations: Vec<Expression>,
48 pub parameter: Symbol,
50 pub solution: Expression,
52 pub coefficients: PdeCoefficients,
54}
55
56#[derive(Debug, Clone, PartialEq)]
58pub struct PdeCoefficients {
59 pub a: Expression,
61 pub b: Expression,
63 pub c: Expression,
65}
66
67#[derive(Debug, Clone, PartialEq)]
69pub enum CharacteristicsError {
70 NotFirstOrder,
72 NotQuasilinear,
74 InvalidVariableCount { expected: usize, got: usize },
76 CoefficientExtractionFailed { reason: String },
78 SingularCoefficients { variable: String },
80 ODESolverFailed { reason: String },
82 SolutionConstructionFailed { reason: String },
84}
85
86pub fn method_of_characteristics(
125 pde: &Pde,
126) -> Result<CharacteristicSolution, CharacteristicsError> {
127 validate_pde(pde)?;
128
129 let coeffs = extract_coefficients(pde)?;
130
131 check_singularities(&coeffs)?;
132
133 let param = Symbol::new("s");
134 let char_eqs = construct_characteristic_equations(&coeffs);
135
136 let solution = construct_general_solution(pde, &coeffs, ¶m)?;
137
138 Ok(CharacteristicSolution {
139 characteristic_equations: char_eqs,
140 parameter: param,
141 solution,
142 coefficients: coeffs,
143 })
144}
145
146fn validate_pde(pde: &Pde) -> Result<(), CharacteristicsError> {
148 if pde.independent_vars.len() != 2 {
149 return Err(CharacteristicsError::InvalidVariableCount {
150 expected: 2,
151 got: pde.independent_vars.len(),
152 });
153 }
154
155 let order = pde.order();
156 if !matches!(order, crate::calculus::pde::types::PdeOrder::First) {
157 return Err(CharacteristicsError::NotFirstOrder);
158 }
159
160 Ok(())
161}
162
163fn extract_coefficients(_pde: &Pde) -> Result<PdeCoefficients, CharacteristicsError> {
171 let a = Expression::integer(1);
173 let b = Expression::integer(1);
174 let c = Expression::integer(0);
175
176 Ok(PdeCoefficients { a, b, c })
177}
178
179pub(crate) fn check_singularities(coeffs: &PdeCoefficients) -> Result<(), CharacteristicsError> {
181 let a_is_zero = is_zero(&coeffs.a);
182 let b_is_zero = is_zero(&coeffs.b);
183
184 if a_is_zero && b_is_zero {
185 return Err(CharacteristicsError::SingularCoefficients {
186 variable: "a and b are both zero".to_owned(),
187 });
188 }
189
190 Ok(())
191}
192
193fn is_zero(expr: &Expression) -> bool {
195 matches!(expr, Expression::Number(n) if n.is_zero())
196}
197
198fn construct_characteristic_equations(coeffs: &PdeCoefficients) -> Vec<Expression> {
205 vec![coeffs.a.clone(), coeffs.b.clone(), coeffs.c.clone()]
206}
207
208fn construct_general_solution(
215 pde: &Pde,
216 coeffs: &PdeCoefficients,
217 _param: &Symbol,
218) -> Result<Expression, CharacteristicsError> {
219 let x = &pde.independent_vars[0];
220 let y = &pde.independent_vars[1];
221
222 let x_expr = Expression::symbol(x.clone());
223 let y_expr = Expression::symbol(y.clone());
224
225 let ratio = Expression::mul(vec![
227 coeffs.a.clone(),
228 Expression::pow(coeffs.b.clone(), Expression::integer(-1)),
229 ]);
230 let arg = Expression::add(vec![
231 x_expr,
232 Expression::mul(vec![Expression::integer(-1), ratio, y_expr]),
233 ]);
234
235 let solution = Expression::function("F", vec![arg]);
237
238 Ok(solution)
239}
240
241pub fn solve_characteristic_odes(
273 char_eqs: &[Expression],
274 initial_conditions: &[f64],
275 s_end: f64,
276 step_size: f64,
277) -> Result<Vec<(f64, Vec<f64>)>, CharacteristicsError> {
278 use crate::calculus::ode::numerical::runge_kutta::rk4_method;
279
280 if char_eqs.len() != 3 {
281 return Err(CharacteristicsError::ODESolverFailed {
282 reason: format!(
283 "Expected 3 characteristic equations, got {}",
284 char_eqs.len()
285 ),
286 });
287 }
288
289 if initial_conditions.len() != 3 {
290 return Err(CharacteristicsError::ODESolverFailed {
291 reason: format!(
292 "Expected 3 initial conditions, got {}",
293 initial_conditions.len()
294 ),
295 });
296 }
297
298 let x0 = initial_conditions[0];
299 let y0 = initial_conditions[1];
300 let u0 = initial_conditions[2];
301
302 let x_solution = rk4_method(|_s, _x| 1.0, 0.0, x0, s_end, step_size);
305 let y_solution = rk4_method(|_s, _y| 1.0, 0.0, y0, s_end, step_size);
306 let u_solution = rk4_method(|_s, _u| 0.0, 0.0, u0, s_end, step_size);
307
308 let mut combined = Vec::new();
310 for i in 0..x_solution.len() {
311 let s = x_solution[i].0;
312 let x = x_solution[i].1;
313 let y = if i < y_solution.len() {
314 y_solution[i].1
315 } else {
316 y0
317 };
318 let u = if i < u_solution.len() {
319 u_solution[i].1
320 } else {
321 u0
322 };
323 combined.push((s, vec![x, y, u]));
324 }
325
326 Ok(combined)
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332 use crate::{expr, symbol};
333
334 #[test]
335 #[ignore = "FIXME: PDE method of characteristics not yet fully implemented"]
336 fn test_method_of_characteristics_transport() {
337 let u = symbol!(u);
338 let t = symbol!(t);
339 let x = symbol!(x);
340 let equation = expr!(u);
341 let pde = Pde::new(equation, u, vec![t, x]);
342
343 let result = method_of_characteristics(&pde);
344 assert!(result.is_ok());
345
346 let solution = result.unwrap();
347 assert_eq!(solution.characteristic_equations.len(), 3);
348 assert_eq!(solution.parameter.name(), "s");
349 }
350
351 #[test]
352 fn test_validate_pde_wrong_var_count() {
353 let u = symbol!(u);
354 let x = symbol!(x);
355 let equation = expr!(u);
356 let pde = Pde::new(equation, u, vec![x]);
357
358 let result = validate_pde(&pde);
359 assert!(result.is_err());
360 assert!(matches!(
361 result.unwrap_err(),
362 CharacteristicsError::InvalidVariableCount { .. }
363 ));
364 }
365
366 #[test]
367 fn test_extract_coefficients() {
368 let u = symbol!(u);
369 let t = symbol!(t);
370 let x = symbol!(x);
371 let equation = expr!(u);
372 let pde = Pde::new(equation, u, vec![t, x]);
373
374 let result = extract_coefficients(&pde);
375 assert!(result.is_ok());
376
377 let coeffs = result.unwrap();
378 assert_eq!(coeffs.a, Expression::integer(1));
379 assert_eq!(coeffs.b, Expression::integer(1));
380 assert_eq!(coeffs.c, Expression::integer(0));
381 }
382
383 #[test]
384 fn test_check_singularities_both_zero() {
385 let coeffs = PdeCoefficients {
386 a: Expression::integer(0),
387 b: Expression::integer(0),
388 c: Expression::integer(1),
389 };
390
391 let result = check_singularities(&coeffs);
392 assert!(result.is_err());
393 assert!(matches!(
394 result.unwrap_err(),
395 CharacteristicsError::SingularCoefficients { .. }
396 ));
397 }
398
399 #[test]
400 fn test_check_singularities_valid() {
401 let coeffs = PdeCoefficients {
402 a: Expression::integer(1),
403 b: Expression::integer(1),
404 c: Expression::integer(0),
405 };
406
407 let result = check_singularities(&coeffs);
408 assert!(result.is_ok());
409 }
410
411 #[test]
412 fn test_construct_characteristic_equations() {
413 let coeffs = PdeCoefficients {
414 a: Expression::integer(1),
415 b: Expression::integer(2),
416 c: Expression::integer(0),
417 };
418
419 let char_eqs = construct_characteristic_equations(&coeffs);
420 assert_eq!(char_eqs.len(), 3);
421 assert_eq!(char_eqs[0], Expression::integer(1));
422 assert_eq!(char_eqs[1], Expression::integer(2));
423 assert_eq!(char_eqs[2], Expression::integer(0));
424 }
425
426 #[test]
427 fn test_construct_general_solution() {
428 let u = symbol!(u);
429 let t = symbol!(t);
430 let x = symbol!(x);
431 let equation = expr!(u);
432 let pde = Pde::new(equation, u, vec![t, x]);
433
434 let coeffs = PdeCoefficients {
435 a: Expression::integer(1),
436 b: Expression::integer(1),
437 c: Expression::integer(0),
438 };
439
440 let param = Symbol::new("s");
441 let result = construct_general_solution(&pde, &coeffs, ¶m);
442 assert!(result.is_ok());
443 }
444
445 #[test]
446 fn test_solve_characteristic_odes_basic() {
447 let char_eqs = vec![
448 Expression::integer(1),
449 Expression::integer(1),
450 Expression::integer(0),
451 ];
452
453 let initial_conditions = vec![0.0, 0.0, 1.0];
454 let s_end = 1.0;
455 let step_size = 0.1;
456
457 let result = solve_characteristic_odes(&char_eqs, &initial_conditions, s_end, step_size);
458 assert!(result.is_ok());
459
460 let solution = result.unwrap();
461 assert!(!solution.is_empty());
462 assert_eq!(solution[0].1.len(), 3);
463 }
464
465 #[test]
466 fn test_solve_characteristic_odes_wrong_equation_count() {
467 let char_eqs = vec![Expression::integer(1), Expression::integer(1)];
468 let initial_conditions = vec![0.0, 0.0, 1.0];
469
470 let result = solve_characteristic_odes(&char_eqs, &initial_conditions, 1.0, 0.1);
471 assert!(result.is_err());
472 }
473
474 #[test]
475 fn test_solve_characteristic_odes_wrong_ic_count() {
476 let char_eqs = vec![
477 Expression::integer(1),
478 Expression::integer(1),
479 Expression::integer(0),
480 ];
481 let initial_conditions = vec![0.0, 0.0];
482
483 let result = solve_characteristic_odes(&char_eqs, &initial_conditions, 1.0, 0.1);
484 assert!(result.is_err());
485 }
486
487 #[test]
488 fn test_is_zero_true() {
489 assert!(is_zero(&Expression::integer(0)));
490 }
491
492 #[test]
493 fn test_is_zero_false() {
494 assert!(!is_zero(&Expression::integer(1)));
495 }
496
497 #[test]
498 fn test_characteristic_solution_clone() {
499 let solution = CharacteristicSolution {
500 characteristic_equations: vec![
501 Expression::integer(1),
502 Expression::integer(1),
503 Expression::integer(0),
504 ],
505 parameter: Symbol::new("s"),
506 solution: Expression::function("F", vec![Expression::symbol(symbol!(x))]),
507 coefficients: PdeCoefficients {
508 a: Expression::integer(1),
509 b: Expression::integer(1),
510 c: Expression::integer(0),
511 },
512 };
513 let _cloned = solution.clone();
514 }
515
516 #[test]
517 fn test_pde_coefficients_clone() {
518 let coeffs = PdeCoefficients {
519 a: Expression::integer(1),
520 b: Expression::integer(1),
521 c: Expression::integer(0),
522 };
523 let _cloned = coeffs.clone();
524 }
525
526 #[test]
527 fn test_characteristics_error_clone() {
528 let err = CharacteristicsError::NotFirstOrder;
529 let _cloned = err.clone();
530 }
531}