ruvector_math/spectral/
chebyshev.rs1use std::f64::consts::PI;
7
8#[derive(Debug, Clone)]
10pub struct ChebyshevPolynomial {
11 pub degree: usize,
13}
14
15impl ChebyshevPolynomial {
16 pub fn new(degree: usize) -> Self {
18 Self { degree }
19 }
20
21 pub fn eval(&self, x: f64) -> f64 {
24 if self.degree == 0 {
25 return 1.0;
26 }
27 if self.degree == 1 {
28 return x;
29 }
30
31 let mut t_prev = 1.0;
32 let mut t_curr = x;
33
34 for _ in 2..=self.degree {
35 let t_next = 2.0 * x * t_curr - t_prev;
36 t_prev = t_curr;
37 t_curr = t_next;
38 }
39
40 t_curr
41 }
42
43 pub fn eval_all(x: f64, max_degree: usize) -> Vec<f64> {
45 if max_degree == 0 {
46 return vec![1.0];
47 }
48
49 let mut result = Vec::with_capacity(max_degree + 1);
50 result.push(1.0);
51 result.push(x);
52
53 for k in 2..=max_degree {
54 let t_k = 2.0 * x * result[k - 1] - result[k - 2];
55 result.push(t_k);
56 }
57
58 result
59 }
60
61 pub fn nodes(n: usize) -> Vec<f64> {
63 (0..n)
64 .map(|k| ((2 * k + 1) as f64 * PI / (2 * n) as f64).cos())
65 .collect()
66 }
67
68 pub fn derivative(&self, x: f64) -> f64 {
70 if self.degree == 0 {
71 return 0.0;
72 }
73 if self.degree == 1 {
74 return 1.0;
75 }
76
77 let n = self.degree;
80 let mut u_prev = 1.0;
81 let mut u_curr = 2.0 * x;
82
83 for _ in 2..n {
84 let u_next = 2.0 * x * u_curr - u_prev;
85 u_prev = u_curr;
86 u_curr = u_next;
87 }
88
89 n as f64 * if n == 1 { u_prev } else { u_curr }
90 }
91}
92
93#[derive(Debug, Clone)]
96pub struct ChebyshevExpansion {
97 pub coefficients: Vec<f64>,
99}
100
101impl ChebyshevExpansion {
102 pub fn new(coefficients: Vec<f64>) -> Self {
104 Self { coefficients }
105 }
106
107 pub fn from_function<F: Fn(f64) -> f64>(f: F, degree: usize) -> Self {
109 let n = degree + 1;
110 let nodes = ChebyshevPolynomial::nodes(n);
111
112 let f_values: Vec<f64> = nodes.iter().map(|&x| f(x)).collect();
114
115 let mut coefficients = Vec::with_capacity(n);
117
118 for k in 0..n {
119 let mut c_k = 0.0;
120 for (j, &f_j) in f_values.iter().enumerate() {
121 let t_k_at_node = ChebyshevPolynomial::new(k).eval(nodes[j]);
122 c_k += f_j * t_k_at_node;
123 }
124 c_k *= 2.0 / n as f64;
125 if k == 0 {
126 c_k *= 0.5;
127 }
128 coefficients.push(c_k);
129 }
130
131 Self { coefficients }
132 }
133
134 pub fn heat_kernel(t: f64, degree: usize) -> Self {
137 Self::from_function(
138 |x| {
139 let exponent = -t * (x + 1.0);
140 let clamped = exponent.clamp(-700.0, 700.0);
142 clamped.exp()
143 },
144 degree,
145 )
146 }
147
148 pub fn low_pass(cutoff: f64, degree: usize) -> Self {
151 let steepness = 10.0 / cutoff.max(0.1);
152 Self::from_function(
153 |x| {
154 let lambda = (x + 1.0) / 2.0 * 2.0; let exponent = steepness * (lambda - cutoff);
156 let clamped = exponent.clamp(-700.0, 700.0);
158 1.0 / (1.0 + clamped.exp())
159 },
160 degree,
161 )
162 }
163
164 pub fn eval(&self, x: f64) -> f64 {
167 if self.coefficients.is_empty() {
168 return 0.0;
169 }
170 if self.coefficients.len() == 1 {
171 return self.coefficients[0];
172 }
173
174 let n = self.coefficients.len();
176 let mut b_next = 0.0;
177 let mut b_curr = 0.0;
178
179 for k in (1..n).rev() {
180 let b_prev = 2.0 * x * b_curr - b_next + self.coefficients[k];
181 b_next = b_curr;
182 b_curr = b_prev;
183 }
184
185 self.coefficients[0] + x * b_curr - b_next
186 }
187
188 pub fn eval_vector(&self, x: &[f64]) -> Vec<f64> {
190 x.iter().map(|&xi| self.eval(xi)).collect()
191 }
192
193 pub fn degree(&self) -> usize {
195 self.coefficients.len().saturating_sub(1)
196 }
197
198 pub fn truncate(&self, new_degree: usize) -> Self {
200 let n = (new_degree + 1).min(self.coefficients.len());
201 Self {
202 coefficients: self.coefficients[..n].to_vec(),
203 }
204 }
205
206 pub fn add(&self, other: &Self) -> Self {
208 let max_len = self.coefficients.len().max(other.coefficients.len());
209 let mut coefficients = vec![0.0; max_len];
210
211 for (i, &c) in self.coefficients.iter().enumerate() {
212 coefficients[i] += c;
213 }
214 for (i, &c) in other.coefficients.iter().enumerate() {
215 coefficients[i] += c;
216 }
217
218 Self { coefficients }
219 }
220
221 pub fn scale(&self, s: f64) -> Self {
223 Self {
224 coefficients: self.coefficients.iter().map(|&c| c * s).collect(),
225 }
226 }
227
228 pub fn derivative(&self) -> Self {
231 let n = self.coefficients.len();
232 if n <= 1 {
233 return Self::new(vec![0.0]);
234 }
235
236 let mut d_coeffs = vec![0.0; n - 1];
237
238 for k in (0..n - 1).rev() {
240 d_coeffs[k] = 2.0 * (k + 1) as f64 * self.coefficients[k + 1];
241 if k + 2 < n {
242 d_coeffs[k] += if k == 0 { 0.0 } else { d_coeffs[k + 2] };
243 }
244 }
245
246 if !d_coeffs.is_empty() {
248 d_coeffs[0] *= 0.5;
249 }
250
251 Self {
252 coefficients: d_coeffs,
253 }
254 }
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260
261 #[test]
262 fn test_chebyshev_polynomial() {
263 assert!((ChebyshevPolynomial::new(0).eval(0.5) - 1.0).abs() < 1e-10);
265
266 assert!((ChebyshevPolynomial::new(1).eval(0.5) - 0.5).abs() < 1e-10);
268
269 let t2_at_half = 2.0 * 0.5 * 0.5 - 1.0;
271 assert!((ChebyshevPolynomial::new(2).eval(0.5) - t2_at_half).abs() < 1e-10);
272
273 let t3_at_half = 4.0 * 0.5_f64.powi(3) - 3.0 * 0.5;
275 assert!((ChebyshevPolynomial::new(3).eval(0.5) - t3_at_half).abs() < 1e-10);
276 }
277
278 #[test]
279 fn test_eval_all() {
280 let x = 0.5;
281 let all = ChebyshevPolynomial::eval_all(x, 5);
282
283 assert_eq!(all.len(), 6);
284 for (k, &t_k) in all.iter().enumerate() {
285 let expected = ChebyshevPolynomial::new(k).eval(x);
286 assert!((t_k - expected).abs() < 1e-10);
287 }
288 }
289
290 #[test]
291 fn test_chebyshev_nodes() {
292 let nodes = ChebyshevPolynomial::nodes(4);
293 assert_eq!(nodes.len(), 4);
294
295 for &x in &nodes {
297 assert!(x >= -1.0 && x <= 1.0);
298 }
299 }
300
301 #[test]
302 fn test_expansion_constant() {
303 let expansion = ChebyshevExpansion::from_function(|_| 5.0, 3);
304
305 for x in [-0.9, -0.5, 0.0, 0.5, 0.9] {
307 assert!((expansion.eval(x) - 5.0).abs() < 0.1);
308 }
309 }
310
311 #[test]
312 fn test_expansion_linear() {
313 let expansion = ChebyshevExpansion::from_function(|x| 2.0 * x + 1.0, 5);
314
315 for x in [-0.8, -0.3, 0.0, 0.4, 0.7] {
316 let expected = 2.0 * x + 1.0;
317 assert!(
318 (expansion.eval(x) - expected).abs() < 0.1,
319 "x={}, expected={}, got={}",
320 x,
321 expected,
322 expansion.eval(x)
323 );
324 }
325 }
326
327 #[test]
328 fn test_heat_kernel() {
329 let heat = ChebyshevExpansion::heat_kernel(1.0, 10);
330
331 let at_zero = heat.eval(-1.0);
333 assert!((at_zero - 1.0).abs() < 0.1);
334
335 let at_two = heat.eval(1.0);
337 assert!((at_two - (-2.0_f64).exp()).abs() < 0.1);
338 }
339
340 #[test]
341 fn test_clenshaw_stability() {
342 let expansion = ChebyshevExpansion::from_function(|x| x.sin(), 20);
344
345 for x in [-0.9, 0.0, 0.9] {
346 let approx = expansion.eval(x);
347 let exact = x.sin();
348 assert!(
349 (approx - exact).abs() < 0.01,
350 "x={}, approx={}, exact={}",
351 x,
352 approx,
353 exact
354 );
355 }
356 }
357}