mathhook_core/calculus/pde/common/
fourier_coefficients.rs

1//! Fourier coefficient computation for PDE solutions
2//!
3//! Computes coefficients cₙ for series solutions:
4//! u(x,t) = Σ cₙ Xₙ(x) Tₙ(t)
5//!
6//! Uses inner product: cₙ = ⟨f, Xₙ⟩ / ⟨Xₙ, Xₙ⟩
7//! Where ⟨f, g⟩ = ∫ f(x)g(x)dx
8
9use crate::calculus::integrals::Integration;
10use crate::calculus::pde::types::InitialCondition;
11use crate::core::{Expression, Symbol};
12use crate::expr;
13
14/// Compute Fourier coefficients from initial condition
15///
16/// Computes: cₙ = ∫ f(x)Xₙ(x)dx / ∫ Xₙ²(x)dx
17///
18/// # Arguments
19///
20/// * `initial_condition` - Initial condition u(x,0) = f(x)
21/// * `eigenfunctions` - Eigenfunctions Xₙ(x)
22/// * `domain` - Integration domain [a, b]
23/// * `variable` - Spatial variable (e.g., x)
24///
25/// # Returns
26///
27/// Vector of Fourier coefficients cₙ
28///
29/// # Examples
30///
31/// ```rust
32/// use mathhook_core::calculus::pde::common::fourier_coefficients::compute_fourier_coefficients;
33/// use mathhook_core::calculus::pde::types::InitialCondition;
34/// use mathhook_core::{symbol, expr};
35///
36/// let x = symbol!(x);
37/// let sin_x = expr!(sin(x));
38/// let ic = InitialCondition::value(sin_x.clone());
39/// let eigenfunctions = vec![sin_x];
40/// let coefficients = compute_fourier_coefficients(
41///     &ic,
42///     &eigenfunctions,
43///     &(expr!(0), expr!(pi)),
44///     &x
45/// );
46/// assert!(coefficients.is_ok());
47/// ```
48pub fn compute_fourier_coefficients(
49    initial_condition: &InitialCondition,
50    eigenfunctions: &[Expression],
51    domain: &(Expression, Expression),
52    variable: &Symbol,
53) -> Result<Vec<Expression>, String> {
54    let f = match initial_condition {
55        InitialCondition::Value { function } => function,
56        InitialCondition::Derivative { .. } => {
57            return Err(
58                "Fourier coefficient computation from derivative IC not yet implemented".to_owned(),
59            );
60        }
61    };
62
63    let mut coefficients = Vec::new();
64
65    for x_n in eigenfunctions {
66        let coefficient = compute_single_coefficient(f, x_n, domain, variable)?;
67        coefficients.push(coefficient);
68    }
69
70    Ok(coefficients)
71}
72
73/// Compute single Fourier coefficient cₙ = ⟨f, Xₙ⟩ / ⟨Xₙ, Xₙ⟩
74fn compute_single_coefficient(
75    f: &Expression,
76    x_n: &Expression,
77    domain: &(Expression, Expression),
78    variable: &Symbol,
79) -> Result<Expression, String> {
80    let (_a, _b) = domain;
81
82    let numerator_integrand = Expression::mul(vec![f.clone(), x_n.clone()]);
83    let denominator_integrand = Expression::mul(vec![x_n.clone(), x_n.clone()]);
84
85    let numerator = numerator_integrand.integrate(variable.clone(), 0);
86    let denominator = denominator_integrand.integrate(variable.clone(), 0);
87
88    if denominator == expr!(0) {
89        return Err("Eigenfunction has zero norm".to_owned());
90    }
91
92    Ok(Expression::mul(vec![
93        numerator,
94        Expression::pow(denominator, Expression::integer(-1)),
95    ]))
96}
97
98/// Compute normalization constant for an eigenfunction
99///
100/// Returns: √(∫ Xₙ²(x)dx)
101pub fn compute_normalization(
102    eigenfunction: &Expression,
103    domain: &(Expression, Expression),
104    variable: &Symbol,
105) -> Result<Expression, String> {
106    let (_a, _b) = domain;
107    let integrand = Expression::mul(vec![eigenfunction.clone(), eigenfunction.clone()]);
108
109    let norm_squared = integrand.integrate(variable.clone(), 0);
110
111    Ok(Expression::function("sqrt", vec![norm_squared]))
112}
113
114/// Simplified coefficient computation for common cases
115///
116/// Handles special cases with known analytical solutions:
117/// - f(x) = sin(nx), cos(nx)
118/// - f(x) = constant
119/// - f(x) = polynomial
120pub fn compute_coefficients_analytical(
121    initial_condition: &InitialCondition,
122    eigenfunctions: &[Expression],
123    domain: &(Expression, Expression),
124    variable: &Symbol,
125) -> Result<Option<Vec<Expression>>, String> {
126    let f = match initial_condition {
127        InitialCondition::Value { function } => function,
128        _ => return Ok(None),
129    };
130
131    if is_constant(f) {
132        return compute_constant_coefficients(f, eigenfunctions, domain, variable).map(Some);
133    }
134
135    if is_single_mode(f, eigenfunctions) {
136        return compute_single_mode_coefficients(f, eigenfunctions).map(Some);
137    }
138
139    Ok(None)
140}
141
142/// Check if expression is a constant
143fn is_constant(expr: &Expression) -> bool {
144    matches!(expr, Expression::Number(_))
145}
146
147/// Check if f(x) matches a single eigenfunction
148fn is_single_mode(f: &Expression, eigenfunctions: &[Expression]) -> bool {
149    eigenfunctions.iter().any(|x_n| {
150        if let Expression::Mul(terms) = f {
151            terms.iter().any(|term| term == x_n)
152        } else {
153            f == x_n
154        }
155    })
156}
157
158/// Compute coefficients for constant initial condition
159fn compute_constant_coefficients(
160    constant: &Expression,
161    eigenfunctions: &[Expression],
162    domain: &(Expression, Expression),
163    variable: &Symbol,
164) -> Result<Vec<Expression>, String> {
165    let mut coefficients = Vec::new();
166
167    for x_n in eigenfunctions {
168        let (_a, _b) = domain;
169        let integrand = x_n.clone();
170
171        let numerator = integrand.integrate(variable.clone(), 0);
172
173        let norm_integrand = Expression::mul(vec![x_n.clone(), x_n.clone()]);
174        let denominator = norm_integrand.integrate(variable.clone(), 0);
175
176        let coefficient = Expression::mul(vec![
177            constant.clone(),
178            numerator,
179            Expression::pow(denominator, Expression::integer(-1)),
180        ]);
181        coefficients.push(coefficient);
182    }
183
184    Ok(coefficients)
185}
186
187/// Compute coefficients when f(x) is a single eigenmode
188fn compute_single_mode_coefficients(
189    f: &Expression,
190    eigenfunctions: &[Expression],
191) -> Result<Vec<Expression>, String> {
192    let mut coefficients = Vec::new();
193
194    for x_n in eigenfunctions {
195        if f == x_n {
196            coefficients.push(expr!(1));
197        } else if let Expression::Mul(terms) = f {
198            if terms.iter().any(|term| term == x_n) {
199                let const_part: Vec<Expression> =
200                    terms.iter().filter(|term| *term != x_n).cloned().collect();
201                if const_part.len() == 1 {
202                    coefficients.push(const_part[0].clone());
203                } else {
204                    coefficients.push(Expression::mul(const_part));
205                }
206            } else {
207                coefficients.push(expr!(0));
208            }
209        } else {
210            coefficients.push(expr!(0));
211        }
212    }
213
214    Ok(coefficients)
215}
216
217#[cfg(test)]
218mod tests {
219    use super::*;
220    use crate::{expr, symbol};
221
222    #[test]
223    fn test_is_constant() {
224        assert!(is_constant(&expr!(5)));
225        assert!(!is_constant(&symbol!(x).into()));
226    }
227
228    #[test]
229    fn test_is_single_mode_exact_match() {
230        let x = symbol!(x);
231        let sin_x = Expression::function("sin", vec![Expression::symbol(x)]);
232        let eigenfunctions = vec![sin_x.clone()];
233
234        assert!(is_single_mode(&sin_x, &eigenfunctions));
235    }
236
237    #[test]
238    fn test_is_single_mode_with_coefficient() {
239        let x = symbol!(x);
240        let sin_x = Expression::function("sin", vec![Expression::symbol(x)]);
241        let f = Expression::mul(vec![Expression::integer(2), sin_x.clone()]);
242        let eigenfunctions = vec![sin_x];
243
244        assert!(is_single_mode(&f, &eigenfunctions));
245    }
246
247    #[test]
248    fn test_is_single_mode_no_match() {
249        let x = symbol!(x);
250        let sin_x = Expression::function("sin", vec![Expression::symbol(x.clone())]);
251        let cos_x = Expression::function("cos", vec![Expression::symbol(x)]);
252        let eigenfunctions = vec![sin_x];
253
254        assert!(!is_single_mode(&cos_x, &eigenfunctions));
255    }
256
257    #[test]
258    fn test_compute_single_mode_coefficients_exact() {
259        let x = symbol!(x);
260        let sin_x = Expression::function("sin", vec![Expression::symbol(x)]);
261        let eigenfunctions = vec![sin_x.clone(), Expression::function("cos", vec![])];
262
263        let result = compute_single_mode_coefficients(&sin_x, &eigenfunctions);
264        assert!(result.is_ok());
265
266        let coefficients = result.unwrap();
267        assert_eq!(coefficients.len(), 2);
268        assert_eq!(coefficients[0], expr!(1));
269        assert_eq!(coefficients[1], expr!(0));
270    }
271
272    #[test]
273    fn test_compute_single_mode_coefficients_with_constant() {
274        let x = symbol!(x);
275        let sin_x = Expression::function("sin", vec![Expression::symbol(x)]);
276        let f = Expression::mul(vec![Expression::integer(3), sin_x.clone()]);
277        let eigenfunctions = vec![sin_x];
278
279        let result = compute_single_mode_coefficients(&f, &eigenfunctions);
280        assert!(result.is_ok());
281
282        let coefficients = result.unwrap();
283        assert_eq!(coefficients.len(), 1);
284        assert_eq!(coefficients[0], expr!(3));
285    }
286
287    #[test]
288    fn test_compute_fourier_coefficients_single_mode() {
289        let x = symbol!(x);
290        let sin_x = Expression::function("sin", vec![Expression::symbol(x.clone())]);
291        let ic = InitialCondition::value(sin_x.clone());
292        let eigenfunctions = vec![sin_x];
293        let domain = (expr!(0), expr!(pi));
294
295        let result = compute_fourier_coefficients(&ic, &eigenfunctions, &domain, &x);
296        assert!(result.is_ok());
297    }
298
299    #[test]
300    fn test_compute_fourier_coefficients_derivative_ic_error() {
301        let x = symbol!(x);
302        let sin_x = Expression::function("sin", vec![Expression::symbol(x.clone())]);
303        let ic = InitialCondition::derivative(sin_x.clone());
304        let eigenfunctions = vec![sin_x];
305        let domain = (expr!(0), expr!(pi));
306
307        let result = compute_fourier_coefficients(&ic, &eigenfunctions, &domain, &x);
308        assert!(result.is_err());
309    }
310}