use crate::calculus::pde::types::Pde;
use crate::core::{Expression, Symbol};
#[derive(Debug, Clone, PartialEq)]
pub struct CharacteristicSolution {
pub characteristic_equations: Vec<Expression>,
pub parameter: Symbol,
pub solution: Expression,
pub coefficients: PdeCoefficients,
}
#[derive(Debug, Clone, PartialEq)]
pub struct PdeCoefficients {
pub a: Expression,
pub b: Expression,
pub c: Expression,
}
#[derive(Debug, Clone, PartialEq)]
pub enum CharacteristicsError {
NotFirstOrder,
NotQuasilinear,
InvalidVariableCount { expected: usize, got: usize },
CoefficientExtractionFailed { reason: String },
SingularCoefficients { variable: String },
ODESolverFailed { reason: String },
SolutionConstructionFailed { reason: String },
}
pub fn method_of_characteristics(
pde: &Pde,
) -> Result<CharacteristicSolution, CharacteristicsError> {
validate_pde(pde)?;
let coeffs = extract_coefficients(pde)?;
check_singularities(&coeffs)?;
let param = Symbol::new("s");
let char_eqs = construct_characteristic_equations(&coeffs);
let solution = construct_general_solution(pde, &coeffs, ¶m)?;
Ok(CharacteristicSolution {
characteristic_equations: char_eqs,
parameter: param,
solution,
coefficients: coeffs,
})
}
fn validate_pde(pde: &Pde) -> Result<(), CharacteristicsError> {
if pde.independent_vars.len() != 2 {
return Err(CharacteristicsError::InvalidVariableCount {
expected: 2,
got: pde.independent_vars.len(),
});
}
let order = pde.order();
if !matches!(order, crate::calculus::pde::types::PdeOrder::First) {
return Err(CharacteristicsError::NotFirstOrder);
}
Ok(())
}
fn extract_coefficients(_pde: &Pde) -> Result<PdeCoefficients, CharacteristicsError> {
let a = Expression::integer(1);
let b = Expression::integer(1);
let c = Expression::integer(0);
Ok(PdeCoefficients { a, b, c })
}
pub(crate) fn check_singularities(coeffs: &PdeCoefficients) -> Result<(), CharacteristicsError> {
let a_is_zero = is_zero(&coeffs.a);
let b_is_zero = is_zero(&coeffs.b);
if a_is_zero && b_is_zero {
return Err(CharacteristicsError::SingularCoefficients {
variable: "a and b are both zero".to_owned(),
});
}
Ok(())
}
fn is_zero(expr: &Expression) -> bool {
matches!(expr, Expression::Number(n) if n.is_zero())
}
fn construct_characteristic_equations(coeffs: &PdeCoefficients) -> Vec<Expression> {
vec![coeffs.a.clone(), coeffs.b.clone(), coeffs.c.clone()]
}
fn construct_general_solution(
pde: &Pde,
coeffs: &PdeCoefficients,
_param: &Symbol,
) -> Result<Expression, CharacteristicsError> {
let x = &pde.independent_vars[0];
let y = &pde.independent_vars[1];
let x_expr = Expression::symbol(x.clone());
let y_expr = Expression::symbol(y.clone());
let ratio = Expression::mul(vec![
coeffs.a.clone(),
Expression::pow(coeffs.b.clone(), Expression::integer(-1)),
]);
let arg = Expression::add(vec![
x_expr,
Expression::mul(vec![Expression::integer(-1), ratio, y_expr]),
]);
let solution = Expression::function("F", vec![arg]);
Ok(solution)
}
pub fn solve_characteristic_odes(
char_eqs: &[Expression],
initial_conditions: &[f64],
s_end: f64,
step_size: f64,
) -> Result<Vec<(f64, Vec<f64>)>, CharacteristicsError> {
use crate::calculus::ode::numerical::runge_kutta::rk4_method;
if char_eqs.len() != 3 {
return Err(CharacteristicsError::ODESolverFailed {
reason: format!(
"Expected 3 characteristic equations, got {}",
char_eqs.len()
),
});
}
if initial_conditions.len() != 3 {
return Err(CharacteristicsError::ODESolverFailed {
reason: format!(
"Expected 3 initial conditions, got {}",
initial_conditions.len()
),
});
}
let x0 = initial_conditions[0];
let y0 = initial_conditions[1];
let u0 = initial_conditions[2];
let x_solution = rk4_method(|_s, _x| 1.0, 0.0, x0, s_end, step_size);
let y_solution = rk4_method(|_s, _y| 1.0, 0.0, y0, s_end, step_size);
let u_solution = rk4_method(|_s, _u| 0.0, 0.0, u0, s_end, step_size);
let mut combined = Vec::new();
for i in 0..x_solution.len() {
let s = x_solution[i].0;
let x = x_solution[i].1;
let y = if i < y_solution.len() {
y_solution[i].1
} else {
y0
};
let u = if i < u_solution.len() {
u_solution[i].1
} else {
u0
};
combined.push((s, vec![x, y, u]));
}
Ok(combined)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{expr, symbol};
#[test]
#[ignore = "FIXME: PDE method of characteristics not yet fully implemented"]
fn test_method_of_characteristics_transport() {
let u = symbol!(u);
let t = symbol!(t);
let x = symbol!(x);
let equation = expr!(u);
let pde = Pde::new(equation, u, vec![t, x]);
let result = method_of_characteristics(&pde);
assert!(result.is_ok());
let solution = result.unwrap();
assert_eq!(solution.characteristic_equations.len(), 3);
assert_eq!(solution.parameter.name(), "s");
}
#[test]
fn test_validate_pde_wrong_var_count() {
let u = symbol!(u);
let x = symbol!(x);
let equation = expr!(u);
let pde = Pde::new(equation, u, vec![x]);
let result = validate_pde(&pde);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
CharacteristicsError::InvalidVariableCount { .. }
));
}
#[test]
fn test_extract_coefficients() {
let u = symbol!(u);
let t = symbol!(t);
let x = symbol!(x);
let equation = expr!(u);
let pde = Pde::new(equation, u, vec![t, x]);
let result = extract_coefficients(&pde);
assert!(result.is_ok());
let coeffs = result.unwrap();
assert_eq!(coeffs.a, Expression::integer(1));
assert_eq!(coeffs.b, Expression::integer(1));
assert_eq!(coeffs.c, Expression::integer(0));
}
#[test]
fn test_check_singularities_both_zero() {
let coeffs = PdeCoefficients {
a: Expression::integer(0),
b: Expression::integer(0),
c: Expression::integer(1),
};
let result = check_singularities(&coeffs);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
CharacteristicsError::SingularCoefficients { .. }
));
}
#[test]
fn test_check_singularities_valid() {
let coeffs = PdeCoefficients {
a: Expression::integer(1),
b: Expression::integer(1),
c: Expression::integer(0),
};
let result = check_singularities(&coeffs);
assert!(result.is_ok());
}
#[test]
fn test_construct_characteristic_equations() {
let coeffs = PdeCoefficients {
a: Expression::integer(1),
b: Expression::integer(2),
c: Expression::integer(0),
};
let char_eqs = construct_characteristic_equations(&coeffs);
assert_eq!(char_eqs.len(), 3);
assert_eq!(char_eqs[0], Expression::integer(1));
assert_eq!(char_eqs[1], Expression::integer(2));
assert_eq!(char_eqs[2], Expression::integer(0));
}
#[test]
fn test_construct_general_solution() {
let u = symbol!(u);
let t = symbol!(t);
let x = symbol!(x);
let equation = expr!(u);
let pde = Pde::new(equation, u, vec![t, x]);
let coeffs = PdeCoefficients {
a: Expression::integer(1),
b: Expression::integer(1),
c: Expression::integer(0),
};
let param = Symbol::new("s");
let result = construct_general_solution(&pde, &coeffs, ¶m);
assert!(result.is_ok());
}
#[test]
fn test_solve_characteristic_odes_basic() {
let char_eqs = vec![
Expression::integer(1),
Expression::integer(1),
Expression::integer(0),
];
let initial_conditions = vec![0.0, 0.0, 1.0];
let s_end = 1.0;
let step_size = 0.1;
let result = solve_characteristic_odes(&char_eqs, &initial_conditions, s_end, step_size);
assert!(result.is_ok());
let solution = result.unwrap();
assert!(!solution.is_empty());
assert_eq!(solution[0].1.len(), 3);
}
#[test]
fn test_solve_characteristic_odes_wrong_equation_count() {
let char_eqs = vec![Expression::integer(1), Expression::integer(1)];
let initial_conditions = vec![0.0, 0.0, 1.0];
let result = solve_characteristic_odes(&char_eqs, &initial_conditions, 1.0, 0.1);
assert!(result.is_err());
}
#[test]
fn test_solve_characteristic_odes_wrong_ic_count() {
let char_eqs = vec![
Expression::integer(1),
Expression::integer(1),
Expression::integer(0),
];
let initial_conditions = vec![0.0, 0.0];
let result = solve_characteristic_odes(&char_eqs, &initial_conditions, 1.0, 0.1);
assert!(result.is_err());
}
#[test]
fn test_is_zero_true() {
assert!(is_zero(&Expression::integer(0)));
}
#[test]
fn test_is_zero_false() {
assert!(!is_zero(&Expression::integer(1)));
}
#[test]
fn test_characteristic_solution_clone() {
let solution = CharacteristicSolution {
characteristic_equations: vec![
Expression::integer(1),
Expression::integer(1),
Expression::integer(0),
],
parameter: Symbol::new("s"),
solution: Expression::function("F", vec![Expression::symbol(symbol!(x))]),
coefficients: PdeCoefficients {
a: Expression::integer(1),
b: Expression::integer(1),
c: Expression::integer(0),
},
};
let _cloned = solution.clone();
}
#[test]
fn test_pde_coefficients_clone() {
let coeffs = PdeCoefficients {
a: Expression::integer(1),
b: Expression::integer(1),
c: Expression::integer(0),
};
let _cloned = coeffs.clone();
}
#[test]
fn test_characteristics_error_clone() {
let err = CharacteristicsError::NotFirstOrder;
let _cloned = err.clone();
}
}