use crate::calculus::integrals::Integration;
use crate::calculus::pde::types::InitialCondition;
use crate::core::{Expression, Symbol};
use crate::expr;
pub fn compute_fourier_coefficients(
initial_condition: &InitialCondition,
eigenfunctions: &[Expression],
domain: &(Expression, Expression),
variable: &Symbol,
) -> Result<Vec<Expression>, String> {
let f = match initial_condition {
InitialCondition::Value { function } => function,
InitialCondition::Derivative { .. } => {
return Err(
"Fourier coefficient computation from derivative IC not yet implemented".to_owned(),
);
}
};
let mut coefficients = Vec::new();
for x_n in eigenfunctions {
let coefficient = compute_single_coefficient(f, x_n, domain, variable)?;
coefficients.push(coefficient);
}
Ok(coefficients)
}
fn compute_single_coefficient(
f: &Expression,
x_n: &Expression,
domain: &(Expression, Expression),
variable: &Symbol,
) -> Result<Expression, String> {
let (_a, _b) = domain;
let numerator_integrand = Expression::mul(vec![f.clone(), x_n.clone()]);
let denominator_integrand = Expression::mul(vec![x_n.clone(), x_n.clone()]);
let numerator = numerator_integrand.integrate(variable.clone(), 0);
let denominator = denominator_integrand.integrate(variable.clone(), 0);
if denominator == expr!(0) {
return Err("Eigenfunction has zero norm".to_owned());
}
Ok(Expression::mul(vec![
numerator,
Expression::pow(denominator, Expression::integer(-1)),
]))
}
pub fn compute_normalization(
eigenfunction: &Expression,
domain: &(Expression, Expression),
variable: &Symbol,
) -> Result<Expression, String> {
let (_a, _b) = domain;
let integrand = Expression::mul(vec![eigenfunction.clone(), eigenfunction.clone()]);
let norm_squared = integrand.integrate(variable.clone(), 0);
Ok(Expression::function("sqrt", vec![norm_squared]))
}
pub fn compute_coefficients_analytical(
initial_condition: &InitialCondition,
eigenfunctions: &[Expression],
domain: &(Expression, Expression),
variable: &Symbol,
) -> Result<Option<Vec<Expression>>, String> {
let f = match initial_condition {
InitialCondition::Value { function } => function,
_ => return Ok(None),
};
if is_constant(f) {
return compute_constant_coefficients(f, eigenfunctions, domain, variable).map(Some);
}
if is_single_mode(f, eigenfunctions) {
return compute_single_mode_coefficients(f, eigenfunctions).map(Some);
}
Ok(None)
}
fn is_constant(expr: &Expression) -> bool {
matches!(expr, Expression::Number(_))
}
fn is_single_mode(f: &Expression, eigenfunctions: &[Expression]) -> bool {
eigenfunctions.iter().any(|x_n| {
if let Expression::Mul(terms) = f {
terms.iter().any(|term| term == x_n)
} else {
f == x_n
}
})
}
fn compute_constant_coefficients(
constant: &Expression,
eigenfunctions: &[Expression],
domain: &(Expression, Expression),
variable: &Symbol,
) -> Result<Vec<Expression>, String> {
let mut coefficients = Vec::new();
for x_n in eigenfunctions {
let (_a, _b) = domain;
let integrand = x_n.clone();
let numerator = integrand.integrate(variable.clone(), 0);
let norm_integrand = Expression::mul(vec![x_n.clone(), x_n.clone()]);
let denominator = norm_integrand.integrate(variable.clone(), 0);
let coefficient = Expression::mul(vec![
constant.clone(),
numerator,
Expression::pow(denominator, Expression::integer(-1)),
]);
coefficients.push(coefficient);
}
Ok(coefficients)
}
fn compute_single_mode_coefficients(
f: &Expression,
eigenfunctions: &[Expression],
) -> Result<Vec<Expression>, String> {
let mut coefficients = Vec::new();
for x_n in eigenfunctions {
if f == x_n {
coefficients.push(expr!(1));
} else if let Expression::Mul(terms) = f {
if terms.iter().any(|term| term == x_n) {
let const_part: Vec<Expression> =
terms.iter().filter(|term| *term != x_n).cloned().collect();
if const_part.len() == 1 {
coefficients.push(const_part[0].clone());
} else {
coefficients.push(Expression::mul(const_part));
}
} else {
coefficients.push(expr!(0));
}
} else {
coefficients.push(expr!(0));
}
}
Ok(coefficients)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{expr, symbol};
#[test]
fn test_is_constant() {
assert!(is_constant(&expr!(5)));
assert!(!is_constant(&symbol!(x).into()));
}
#[test]
fn test_is_single_mode_exact_match() {
let x = symbol!(x);
let sin_x = Expression::function("sin", vec![Expression::symbol(x)]);
let eigenfunctions = vec![sin_x.clone()];
assert!(is_single_mode(&sin_x, &eigenfunctions));
}
#[test]
fn test_is_single_mode_with_coefficient() {
let x = symbol!(x);
let sin_x = Expression::function("sin", vec![Expression::symbol(x)]);
let f = Expression::mul(vec![Expression::integer(2), sin_x.clone()]);
let eigenfunctions = vec![sin_x];
assert!(is_single_mode(&f, &eigenfunctions));
}
#[test]
fn test_is_single_mode_no_match() {
let x = symbol!(x);
let sin_x = Expression::function("sin", vec![Expression::symbol(x.clone())]);
let cos_x = Expression::function("cos", vec![Expression::symbol(x)]);
let eigenfunctions = vec![sin_x];
assert!(!is_single_mode(&cos_x, &eigenfunctions));
}
#[test]
fn test_compute_single_mode_coefficients_exact() {
let x = symbol!(x);
let sin_x = Expression::function("sin", vec![Expression::symbol(x)]);
let eigenfunctions = vec![sin_x.clone(), Expression::function("cos", vec![])];
let result = compute_single_mode_coefficients(&sin_x, &eigenfunctions);
assert!(result.is_ok());
let coefficients = result.unwrap();
assert_eq!(coefficients.len(), 2);
assert_eq!(coefficients[0], expr!(1));
assert_eq!(coefficients[1], expr!(0));
}
#[test]
fn test_compute_single_mode_coefficients_with_constant() {
let x = symbol!(x);
let sin_x = Expression::function("sin", vec![Expression::symbol(x)]);
let f = Expression::mul(vec![Expression::integer(3), sin_x.clone()]);
let eigenfunctions = vec![sin_x];
let result = compute_single_mode_coefficients(&f, &eigenfunctions);
assert!(result.is_ok());
let coefficients = result.unwrap();
assert_eq!(coefficients.len(), 1);
assert_eq!(coefficients[0], expr!(3));
}
#[test]
fn test_compute_fourier_coefficients_single_mode() {
let x = symbol!(x);
let sin_x = Expression::function("sin", vec![Expression::symbol(x.clone())]);
let ic = InitialCondition::value(sin_x.clone());
let eigenfunctions = vec![sin_x];
let domain = (expr!(0), expr!(pi));
let result = compute_fourier_coefficients(&ic, &eigenfunctions, &domain, &x);
assert!(result.is_ok());
}
#[test]
fn test_compute_fourier_coefficients_derivative_ic_error() {
let x = symbol!(x);
let sin_x = Expression::function("sin", vec![Expression::symbol(x.clone())]);
let ic = InitialCondition::derivative(sin_x.clone());
let eigenfunctions = vec![sin_x];
let domain = (expr!(0), expr!(pi));
let result = compute_fourier_coefficients(&ic, &eigenfunctions, &domain, &x);
assert!(result.is_err());
}
}