Skip to main content

oxilean_std/functional_analysis/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use oxilean_kernel::{BinderInfo, Declaration, Environment, Expr, Level, Name};
6
7use super::types::{
8    BoundedOp, Distribution, FredholmOperatorData, InterpolationData, InterpolationMethod,
9    L2Sequence, LuDecomposition, QrDecomposition, RnVector, SobolevSpaceData, WeakConvergenceData,
10};
11
12pub fn app(f: Expr, a: Expr) -> Expr {
13    Expr::App(Box::new(f), Box::new(a))
14}
15pub fn app2(f: Expr, a: Expr, b: Expr) -> Expr {
16    app(app(f, a), b)
17}
18pub fn cst(s: &str) -> Expr {
19    Expr::Const(Name::str(s), vec![])
20}
21pub fn prop() -> Expr {
22    Expr::Sort(Level::zero())
23}
24pub fn type0() -> Expr {
25    Expr::Sort(Level::succ(Level::zero()))
26}
27pub fn pi(bi: BinderInfo, name: &str, dom: Expr, body: Expr) -> Expr {
28    Expr::Pi(bi, Name::str(name), Box::new(dom), Box::new(body))
29}
30pub fn arrow(a: Expr, b: Expr) -> Expr {
31    pi(BinderInfo::Default, "_", a, b)
32}
33pub fn bvar(n: u32) -> Expr {
34    Expr::BVar(n)
35}
36pub fn nat_ty() -> Expr {
37    cst("Nat")
38}
39pub fn real_ty() -> Expr {
40    cst("Real")
41}
42pub fn impl_pi(name: &str, dom: Expr, body: Expr) -> Expr {
43    pi(BinderInfo::Implicit, name, dom, body)
44}
45pub fn _use_helpers() {
46    let _ = (
47        app(cst("a"), cst("b")),
48        app2(cst("a"), cst("b"), cst("c")),
49        prop(),
50        type0(),
51        arrow(prop(), prop()),
52        bvar(0),
53        nat_ty(),
54        real_ty(),
55        impl_pi("x", type0(), bvar(0)),
56    );
57}
58pub fn normed_space_ty() -> Expr {
59    arrow(type0(), prop())
60}
61pub fn banach_space_ty() -> Expr {
62    arrow(type0(), prop())
63}
64pub fn hilbert_space_ty() -> Expr {
65    arrow(type0(), prop())
66}
67pub fn bounded_linear_op_ty() -> Expr {
68    pi(
69        BinderInfo::Default,
70        "X",
71        type0(),
72        pi(BinderInfo::Default, "Y", type0(), type0()),
73    )
74}
75pub fn dual_space_ty() -> Expr {
76    arrow(type0(), type0())
77}
78pub fn spectrum_ty() -> Expr {
79    pi(
80        BinderInfo::Default,
81        "X",
82        type0(),
83        arrow(
84            app2(cst("BoundedLinearOp"), bvar(0), bvar(0)),
85            app(cst("Set"), real_ty()),
86        ),
87    )
88}
89pub fn fredholm_operator_ty() -> Expr {
90    pi(
91        BinderInfo::Default,
92        "X",
93        type0(),
94        pi(BinderInfo::Default, "Y", type0(), prop()),
95    )
96}
97pub fn fredholm_index_ty() -> Expr {
98    pi(
99        BinderInfo::Default,
100        "X",
101        type0(),
102        pi(
103            BinderInfo::Default,
104            "Y",
105            type0(),
106            arrow(app2(cst("BoundedLinearOp"), bvar(1), bvar(0)), cst("Int")),
107        ),
108    )
109}
110pub fn compact_operator_ty() -> Expr {
111    pi(
112        BinderInfo::Default,
113        "X",
114        type0(),
115        pi(
116            BinderInfo::Default,
117            "Y",
118            type0(),
119            arrow(app2(cst("BoundedLinearOp"), bvar(1), bvar(0)), prop()),
120        ),
121    )
122}
123pub fn hahn_banach_ty() -> Expr {
124    pi(
125        BinderInfo::Default,
126        "X",
127        type0(),
128        arrow(app(cst("NormedSpace"), bvar(0)), prop()),
129    )
130}
131pub fn open_mapping_ty() -> Expr {
132    pi(
133        BinderInfo::Default,
134        "X",
135        type0(),
136        pi(
137            BinderInfo::Default,
138            "Y",
139            type0(),
140            arrow(
141                app2(cst("BoundedLinearOp"), bvar(1), bvar(0)),
142                arrow(
143                    app(cst("BanachSpace"), bvar(2)),
144                    arrow(app(cst("BanachSpace"), bvar(1)), prop()),
145                ),
146            ),
147        ),
148    )
149}
150pub fn closed_graph_ty() -> Expr {
151    pi(
152        BinderInfo::Default,
153        "X",
154        type0(),
155        pi(
156            BinderInfo::Default,
157            "Y",
158            type0(),
159            arrow(
160                arrow(bvar(1), bvar(0)),
161                arrow(app(cst("BanachSpace"), bvar(2)), prop()),
162            ),
163        ),
164    )
165}
166pub fn uniform_boundedness_ty() -> Expr {
167    pi(
168        BinderInfo::Default,
169        "X",
170        type0(),
171        pi(
172            BinderInfo::Default,
173            "Y",
174            type0(),
175            arrow(
176                app(cst("BanachSpace"), bvar(1)),
177                arrow(app(cst("BanachSpace"), bvar(0)), prop()),
178            ),
179        ),
180    )
181}
182pub fn riesz_representation_ty() -> Expr {
183    pi(
184        BinderInfo::Default,
185        "H",
186        type0(),
187        arrow(app(cst("HilbertSpace"), bvar(0)), prop()),
188    )
189}
190pub fn spectral_theorem_ty() -> Expr {
191    pi(
192        BinderInfo::Default,
193        "H",
194        type0(),
195        arrow(
196            app(cst("HilbertSpace"), bvar(0)),
197            arrow(app2(cst("CompactSelfAdjoint"), bvar(1), bvar(0)), prop()),
198        ),
199    )
200}
201pub fn fredholm_alternative_ty() -> Expr {
202    pi(
203        BinderInfo::Default,
204        "X",
205        type0(),
206        arrow(
207            app(cst("BanachSpace"), bvar(0)),
208            arrow(app2(cst("BoundedLinearOp"), bvar(1), bvar(1)), prop()),
209        ),
210    )
211}
212pub fn banach_fixed_point_ty() -> Expr {
213    pi(
214        BinderInfo::Default,
215        "X",
216        type0(),
217        arrow(
218            app(cst("BanachSpace"), bvar(0)),
219            arrow(arrow(bvar(1), bvar(1)), prop()),
220        ),
221    )
222}
223pub fn build_functional_analysis_env(env: &mut Environment) {
224    let axioms: &[(&str, Expr)] = &[
225        ("NormedSpace", normed_space_ty()),
226        ("BanachSpace", banach_space_ty()),
227        ("HilbertSpace", hilbert_space_ty()),
228        ("BoundedLinearOp", bounded_linear_op_ty()),
229        ("DualSpace", dual_space_ty()),
230        ("Spectrum", spectrum_ty()),
231        ("FredholmOperator", fredholm_operator_ty()),
232        ("FredholmIndex", fredholm_index_ty()),
233        ("CompactOperator", compact_operator_ty()),
234        ("Set", arrow(type0(), type0())),
235        (
236            "CompactSelfAdjoint",
237            pi(BinderInfo::Default, "H", type0(), arrow(type0(), type0())),
238        ),
239        ("Int", type0()),
240        ("hahn_banach", hahn_banach_ty()),
241        ("open_mapping", open_mapping_ty()),
242        ("closed_graph", closed_graph_ty()),
243        ("uniform_boundedness", uniform_boundedness_ty()),
244        ("riesz_representation", riesz_representation_ty()),
245        ("spectral_theorem", spectral_theorem_ty()),
246        ("fredholm_alternative", fredholm_alternative_ty()),
247        ("banach_fixed_point", banach_fixed_point_ty()),
248    ];
249    for (name, ty) in axioms {
250        env.add(Declaration::Axiom {
251            name: Name::str(*name),
252            univ_params: vec![],
253            ty: ty.clone(),
254        })
255        .ok();
256    }
257}
258pub fn gram_schmidt(vectors: &[RnVector]) -> Vec<RnVector> {
259    let mut orthonormal = Vec::new();
260    for v in vectors {
261        let mut u = v.clone();
262        for q in &orthonormal {
263            let proj = u.project_onto(q);
264            u = u.sub(&proj);
265        }
266        let n = u.norm();
267        if n > 1e-12 {
268            orthonormal.push(u.scale(1.0 / n));
269        }
270    }
271    orthonormal
272}
273pub fn is_orthonormal(vectors: &[RnVector], tol: f64) -> bool {
274    for (i, vi) in vectors.iter().enumerate() {
275        if (vi.norm() - 1.0).abs() > tol {
276            return false;
277        }
278        for vj in vectors.iter().skip(i + 1) {
279            if vi.inner(vj).abs() > tol {
280                return false;
281            }
282        }
283    }
284    true
285}
286pub fn spans_full_space(vectors: &[RnVector], dim: usize) -> bool {
287    gram_schmidt(vectors).len() == dim
288}
289pub fn conjugate_gradient(a: &BoundedOp, b: &RnVector, max_iter: usize, tol: f64) -> RnVector {
290    let n = b.dim();
291    let mut x = RnVector::zero(n);
292    let mut r = b.sub(&a.apply(&x));
293    let mut p = r.clone();
294    let mut rs_old = r.inner(&r);
295    for _ in 0..max_iter {
296        if rs_old.sqrt() < tol {
297            break;
298        }
299        let ap = a.apply(&p);
300        let alpha = rs_old / p.inner(&ap);
301        x = x.add(&p.scale(alpha));
302        r = r.sub(&ap.scale(alpha));
303        let rs_new = r.inner(&r);
304        if rs_new.sqrt() < tol {
305            break;
306        }
307        p = r.add(&p.scale(rs_new / rs_old));
308        rs_old = rs_new;
309    }
310    x
311}
312pub fn l2_norm_approx(f: &dyn Fn(f64) -> f64, a: f64, b: f64, n: usize) -> f64 {
313    if n == 0 {
314        return 0.0;
315    }
316    let h = (b - a) / n as f64;
317    let mut sum = 0.5 * f(a).powi(2) + 0.5 * f(b).powi(2);
318    for i in 1..n {
319        sum += f(a + i as f64 * h).powi(2);
320    }
321    (sum * h).sqrt()
322}
323pub fn l2_inner_approx(
324    f: &dyn Fn(f64) -> f64,
325    g: &dyn Fn(f64) -> f64,
326    a: f64,
327    b: f64,
328    n: usize,
329) -> f64 {
330    if n == 0 {
331        return 0.0;
332    }
333    let h = (b - a) / n as f64;
334    let mut sum = 0.5 * f(a) * g(a) + 0.5 * f(b) * g(b);
335    for i in 1..n {
336        let x = a + i as f64 * h;
337        sum += f(x) * g(x);
338    }
339    sum * h
340}
341pub fn sup_norm_approx(f: &dyn Fn(f64) -> f64, a: f64, b: f64, n: usize) -> f64 {
342    if n == 0 {
343        return f(a).abs();
344    }
345    let h = (b - a) / n as f64;
346    let mut max_val = 0.0_f64;
347    for i in 0..=n {
348        max_val = max_val.max(f(a + i as f64 * h).abs());
349    }
350    max_val
351}
352pub fn banach_fixed_point_iter(
353    t: &dyn Fn(f64) -> f64,
354    x0: f64,
355    max_iter: usize,
356    tol: f64,
357) -> (f64, usize) {
358    let mut x = x0;
359    for i in 0..max_iter {
360        let x_next = t(x);
361        if (x_next - x).abs() < tol {
362            return (x_next, i + 1);
363        }
364        x = x_next;
365    }
366    (x, max_iter)
367}
368pub fn fourier_cosine_coefficients(
369    f: &dyn Fn(f64) -> f64,
370    period: f64,
371    num_coeffs: usize,
372    qp: usize,
373) -> Vec<f64> {
374    let h = period / qp as f64;
375    let mut coeffs = Vec::with_capacity(num_coeffs);
376    for k in 0..num_coeffs {
377        let mut sum =
378            0.5 * f(0.0) + 0.5 * f(period) * (2.0 * std::f64::consts::PI * k as f64).cos();
379        for i in 1..qp {
380            let x = i as f64 * h;
381            sum += f(x) * (2.0 * std::f64::consts::PI * k as f64 * x / period).cos();
382        }
383        let factor = if k == 0 { 1.0 / period } else { 2.0 / period };
384        coeffs.push(sum * h * factor);
385    }
386    coeffs
387}
388pub fn eval_fourier_cosine(coeffs: &[f64], period: f64, x: f64) -> f64 {
389    coeffs
390        .iter()
391        .enumerate()
392        .map(|(k, &c)| c * (2.0 * std::f64::consts::PI * k as f64 * x / period).cos())
393        .sum()
394}
395/// Compute QR decomposition of matrix A using modified Gram-Schmidt.
396pub fn qr_decomposition(a: &BoundedOp) -> Option<QrDecomposition> {
397    let (m, n) = (a.range_dim, a.domain_dim);
398    if m == 0 || n == 0 {
399        return None;
400    }
401    let mut cols: Vec<RnVector> = (0..n)
402        .map(|j| RnVector::new((0..m).map(|i| a.matrix[i][j]).collect()))
403        .collect();
404    let mut q_cols: Vec<RnVector> = Vec::new();
405    let mut r_matrix = vec![vec![0.0; n]; n.min(m)];
406    for j in 0..n.min(m) {
407        for k in 0..q_cols.len() {
408            let rk = cols[j].inner(&q_cols[k]);
409            r_matrix[k][j] = rk;
410            cols[j] = cols[j].sub(&q_cols[k].scale(rk));
411        }
412        let nrm = cols[j].norm();
413        if nrm < 1e-14 {
414            r_matrix[j][j] = 0.0;
415            q_cols.push(RnVector::zero(m));
416        } else {
417            r_matrix[j][j] = nrm;
418            q_cols.push(cols[j].scale(1.0 / nrm));
419        }
420    }
421    let q_n = q_cols.len();
422    let q_matrix: Vec<Vec<f64>> = (0..m)
423        .map(|i| (0..q_n).map(|j| q_cols[j].components[i]).collect())
424        .collect();
425    let q = BoundedOp {
426        matrix: q_matrix,
427        domain_dim: q_n,
428        range_dim: m,
429    };
430    let r = BoundedOp {
431        matrix: r_matrix,
432        domain_dim: n,
433        range_dim: q_n,
434    };
435    Some(QrDecomposition { q, r })
436}
437/// Compute LU decomposition with partial pivoting.
438pub fn lu_decomposition(a: &BoundedOp) -> Option<LuDecomposition> {
439    let n = a.domain_dim;
440    if n != a.range_dim || n == 0 {
441        return None;
442    }
443    let mut u = a.matrix.clone();
444    let mut l = vec![vec![0.0; n]; n];
445    let mut perm: Vec<usize> = (0..n).collect();
446    for k in 0..n {
447        let mut max_val = u[k][k].abs();
448        let mut max_row = k;
449        for row in (k + 1)..n {
450            if u[row][k].abs() > max_val {
451                max_val = u[row][k].abs();
452                max_row = row;
453            }
454        }
455        if max_val < 1e-15 {
456            return None;
457        }
458        if max_row != k {
459            u.swap(k, max_row);
460            l.swap(k, max_row);
461            perm.swap(k, max_row);
462        }
463        l[k][k] = 1.0;
464        for i in (k + 1)..n {
465            let factor = u[i][k] / u[k][k];
466            l[i][k] = factor;
467            for j in k..n {
468                let val = u[k][j];
469                u[i][j] -= factor * val;
470            }
471        }
472    }
473    Some(LuDecomposition {
474        l: BoundedOp {
475            matrix: l,
476            domain_dim: n,
477            range_dim: n,
478        },
479        u: BoundedOp {
480            matrix: u,
481            domain_dim: n,
482            range_dim: n,
483        },
484        permutation: perm,
485    })
486}
487/// Solve Ax = b using LU decomposition.
488pub fn lu_solve(lu: &LuDecomposition, b: &RnVector) -> RnVector {
489    let n = lu.l.domain_dim;
490    let mut pb = vec![0.0; n];
491    for (i, &pi) in lu.permutation.iter().enumerate() {
492        pb[i] = b.components[pi];
493    }
494    let mut y = vec![0.0; n];
495    for i in 0..n {
496        let mut sum = pb[i];
497        for j in 0..i {
498            sum -= lu.l.matrix[i][j] * y[j];
499        }
500        y[i] = sum;
501    }
502    let mut x = vec![0.0; n];
503    for i in (0..n).rev() {
504        let mut sum = y[i];
505        for j in (i + 1)..n {
506            sum -= lu.u.matrix[i][j] * x[j];
507        }
508        if lu.u.matrix[i][i].abs() < 1e-15 {
509            x[i] = 0.0;
510        } else {
511            x[i] = sum / lu.u.matrix[i][i];
512        }
513    }
514    RnVector::new(x)
515}
516/// Compute singular values of matrix A (sorted descending).
517pub fn singular_values(a: &BoundedOp, max_iter: usize) -> Vec<f64> {
518    let ata = match a.transpose().compose(a) {
519        Some(m) => m,
520        None => return vec![],
521    };
522    let n = ata.domain_dim;
523    if n == 0 {
524        return vec![];
525    }
526    let mut current = ata.clone();
527    for _ in 0..max_iter {
528        let qr = match qr_decomposition(&current) {
529            Some(qr) => qr,
530            None => break,
531        };
532        current = match qr.r.compose(&qr.q) {
533            Some(m) => m,
534            None => break,
535        };
536    }
537    let mut eigenvalues: Vec<f64> = (0..n).map(|i| current.matrix[i][i].max(0.0)).collect();
538    eigenvalues.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
539    eigenvalues.iter().map(|&ev| ev.sqrt()).collect()
540}
541/// Condition number of a matrix (ratio of largest to smallest singular value).
542pub fn condition_number(a: &BoundedOp, max_iter: usize) -> f64 {
543    let svs = singular_values(a, max_iter);
544    if svs.is_empty() {
545        return f64::INFINITY;
546    }
547    let max_sv = svs[0];
548    let min_sv = *svs
549        .last()
550        .expect("svs is non-empty: checked by early return");
551    if min_sv < 1e-15 {
552        f64::INFINITY
553    } else {
554        max_sv / min_sv
555    }
556}
557/// Approximate spectral radius using power iteration.
558pub fn spectral_radius(a: &BoundedOp, iterations: usize) -> f64 {
559    if a.domain_dim != a.range_dim || a.domain_dim == 0 {
560        return 0.0;
561    }
562    let n = a.domain_dim;
563    let mut v = RnVector::new(vec![1.0; n]);
564    let nrm = v.norm();
565    if nrm > 0.0 {
566        v = v.scale(1.0 / nrm);
567    }
568    let mut radius = 0.0;
569    for _ in 0..iterations {
570        let w = a.apply(&v);
571        let nrm = w.norm();
572        if nrm < 1e-15 {
573            return 0.0;
574        }
575        radius = nrm;
576        v = w.scale(1.0 / nrm);
577    }
578    radius
579}
580/// Approximate the H^1 Sobolev norm of f on [a,b]:
581/// ||f||_{H^1}^2 = ||f||_{L^2}^2 + ||f'||_{L^2}^2
582pub fn h1_norm_approx(f: &dyn Fn(f64) -> f64, a: f64, b: f64, n: usize) -> f64 {
583    let l2_sq = l2_norm_approx(f, a, b, n).powi(2);
584    if n < 2 {
585        return l2_sq.sqrt();
586    }
587    let h = (b - a) / n as f64;
588    let mut deriv_sq_sum = 0.0;
589    for i in 0..n {
590        let x = a + i as f64 * h;
591        let df = (f(x + h) - f(x)) / h;
592        deriv_sq_sum += df * df;
593    }
594    let deriv_l2_sq = deriv_sq_sum * h;
595    (l2_sq + deriv_l2_sq).sqrt()
596}
597/// Approximate the H^2 Sobolev norm (includes second derivative).
598pub fn h2_norm_approx(f: &dyn Fn(f64) -> f64, a: f64, b: f64, n: usize) -> f64 {
599    let h1_sq = h1_norm_approx(f, a, b, n).powi(2);
600    if n < 3 {
601        return h1_sq.sqrt();
602    }
603    let h = (b - a) / n as f64;
604    let mut d2_sq_sum = 0.0;
605    for i in 1..(n - 1) {
606        let x = a + i as f64 * h;
607        let d2f = (f(x + h) - 2.0 * f(x) + f(x - h)) / (h * h);
608        d2_sq_sum += d2f * d2f;
609    }
610    let d2_l2_sq = d2_sq_sum * h;
611    (h1_sq + d2_l2_sq).sqrt()
612}
613/// Simpson's rule for numerical integration.
614pub fn simpson_integrate(f: &dyn Fn(f64) -> f64, a: f64, b: f64, n: usize) -> f64 {
615    let n = if n % 2 == 0 { n } else { n + 1 };
616    if n == 0 {
617        return 0.0;
618    }
619    let h = (b - a) / n as f64;
620    let mut sum = f(a) + f(b);
621    for i in 1..n {
622        let x = a + i as f64 * h;
623        sum += if i % 2 == 0 { 2.0 * f(x) } else { 4.0 * f(x) };
624    }
625    sum * h / 3.0
626}
627/// Gauss-Legendre 3-point quadrature on [a,b].
628pub fn gauss_legendre_3(f: &dyn Fn(f64) -> f64, a: f64, b: f64) -> f64 {
629    let mid = (a + b) / 2.0;
630    let half = (b - a) / 2.0;
631    let nodes = [-0.7745966692414834, 0.0, 0.7745966692414834];
632    let weights = [0.5555555555555556, 0.8888888888888888, 0.5555555555555556];
633    let mut sum = 0.0;
634    for i in 0..3 {
635        sum += weights[i] * f(mid + half * nodes[i]);
636    }
637    sum * half
638}
639/// Composite Gauss-Legendre 3-point quadrature.
640pub fn gauss_legendre_composite(f: &dyn Fn(f64) -> f64, a: f64, b: f64, panels: usize) -> f64 {
641    if panels == 0 {
642        return 0.0;
643    }
644    let h = (b - a) / panels as f64;
645    let mut total = 0.0;
646    for i in 0..panels {
647        let lo = a + i as f64 * h;
648        let hi = lo + h;
649        total += gauss_legendre_3(f, lo, hi);
650    }
651    total
652}
653/// Matrix exponential via scaling and squaring with Pade approximation.
654pub fn matrix_exponential(a: &BoundedOp, terms: usize) -> BoundedOp {
655    let n = a.domain_dim;
656    if n != a.range_dim {
657        return BoundedOp::zero_op(n, n);
658    }
659    let mut result = BoundedOp::identity(n);
660    let mut term = BoundedOp::identity(n);
661    for k in 1..terms {
662        term = match term.compose(a) {
663            Some(m) => m.scalar_mul(1.0 / k as f64),
664            None => break,
665        };
666        result = match result.op_add(&term) {
667            Some(m) => m,
668            None => break,
669        };
670    }
671    result
672}
673/// Commutator [A, B] = AB - BA.
674pub fn commutator(a: &BoundedOp, b: &BoundedOp) -> Option<BoundedOp> {
675    let ab = a.compose(b)?;
676    let ba = b.compose(a)?;
677    let neg_ba = ba.scalar_mul(-1.0);
678    ab.op_add(&neg_ba)
679}
680/// Anti-commutator {A, B} = AB + BA.
681pub fn anti_commutator(a: &BoundedOp, b: &BoundedOp) -> Option<BoundedOp> {
682    let ab = a.compose(b)?;
683    let ba = b.compose(a)?;
684    ab.op_add(&ba)
685}
686/// Check if operator T = I - K is Fredholm by estimating kernel and cokernel dimensions.
687/// Returns (kernel_dim, cokernel_dim, index).
688pub fn fredholm_index_numerical(k: &BoundedOp) -> (usize, usize, i64) {
689    let n = k.domain_dim;
690    if n != k.range_dim {
691        return (0, 0, 0);
692    }
693    let i_minus_k = match BoundedOp::identity(n).op_add(&k.scalar_mul(-1.0)) {
694        Some(m) => m,
695        None => return (0, 0, 0),
696    };
697    let kernel_dim = i_minus_k.nullity();
698    let cokernel_dim = i_minus_k.transpose().nullity();
699    let index = kernel_dim as i64 - cokernel_dim as i64;
700    (kernel_dim, cokernel_dim, index)
701}
702/// Jacobi iterative method for solving Ax = b.
703pub fn jacobi_iteration(a: &BoundedOp, b: &RnVector, max_iter: usize, tol: f64) -> RnVector {
704    let n = a.domain_dim;
705    if n != a.range_dim || n != b.dim() {
706        return RnVector::zero(n);
707    }
708    let mut x = RnVector::zero(n);
709    for _ in 0..max_iter {
710        let mut x_new = vec![0.0; n];
711        for i in 0..n {
712            if a.matrix[i][i].abs() < 1e-15 {
713                x_new[i] = x.components[i];
714                continue;
715            }
716            let mut sum = b.components[i];
717            for j in 0..n {
718                if j != i {
719                    sum -= a.matrix[i][j] * x.components[j];
720                }
721            }
722            x_new[i] = sum / a.matrix[i][i];
723        }
724        let new_x = RnVector::new(x_new);
725        if new_x.sub(&x).norm() < tol {
726            return new_x;
727        }
728        x = new_x;
729    }
730    x
731}
732/// Gauss-Seidel iterative method for solving Ax = b.
733pub fn gauss_seidel_iteration(a: &BoundedOp, b: &RnVector, max_iter: usize, tol: f64) -> RnVector {
734    let n = a.domain_dim;
735    if n != a.range_dim || n != b.dim() {
736        return RnVector::zero(n);
737    }
738    let mut x = vec![0.0; n];
739    for _ in 0..max_iter {
740        let old_x = x.clone();
741        for i in 0..n {
742            if a.matrix[i][i].abs() < 1e-15 {
743                continue;
744            }
745            let mut sum = b.components[i];
746            for j in 0..n {
747                if j != i {
748                    sum -= a.matrix[i][j] * x[j];
749                }
750            }
751            x[i] = sum / a.matrix[i][i];
752        }
753        let diff: f64 = x
754            .iter()
755            .zip(old_x.iter())
756            .map(|(a, b)| (a - b).powi(2))
757            .sum::<f64>()
758            .sqrt();
759        if diff < tol {
760            break;
761        }
762    }
763    RnVector::new(x)
764}
765/// Compute Fourier sine coefficients on [0, L].
766pub fn fourier_sine_coefficients(
767    f: &dyn Fn(f64) -> f64,
768    period: f64,
769    num_coeffs: usize,
770    qp: usize,
771) -> Vec<f64> {
772    let h = period / qp as f64;
773    let mut coeffs = Vec::with_capacity(num_coeffs);
774    for k in 0..num_coeffs {
775        let mut sum = 0.0;
776        for i in 0..=qp {
777            let x = i as f64 * h;
778            let w = if i == 0 || i == qp { 0.5 } else { 1.0 };
779            sum += w * f(x) * (std::f64::consts::PI * (k + 1) as f64 * x / period).sin();
780        }
781        coeffs.push(sum * h * 2.0 / period);
782    }
783    coeffs
784}
785/// Evaluate Fourier sine series at a point.
786pub fn eval_fourier_sine(coeffs: &[f64], period: f64, x: f64) -> f64 {
787    coeffs
788        .iter()
789        .enumerate()
790        .map(|(k, &c)| c * (std::f64::consts::PI * (k + 1) as f64 * x / period).sin())
791        .sum()
792}
793/// Evaluate the n-th Chebyshev polynomial T_n(x) via recurrence.
794pub fn chebyshev_t(n: usize, x: f64) -> f64 {
795    if n == 0 {
796        return 1.0;
797    }
798    if n == 1 {
799        return x;
800    }
801    let (mut t0, mut t1) = (1.0, x);
802    for _ in 2..=n {
803        let t2 = 2.0 * x * t1 - t0;
804        t0 = t1;
805        t1 = t2;
806    }
807    t1
808}
809/// Chebyshev nodes on [a,b].
810pub fn chebyshev_nodes(n: usize, a: f64, b: f64) -> Vec<f64> {
811    (0..n)
812        .map(|k| {
813            let theta = std::f64::consts::PI * (2 * k + 1) as f64 / (2 * n) as f64;
814            (a + b) / 2.0 + (b - a) / 2.0 * theta.cos()
815        })
816        .collect()
817}
818/// Chebyshev interpolation coefficients for f on [a,b] at n nodes.
819pub fn chebyshev_coefficients(f: &dyn Fn(f64) -> f64, n: usize, a: f64, b: f64) -> Vec<f64> {
820    let nodes = chebyshev_nodes(n, a, b);
821    let values: Vec<f64> = nodes.iter().map(|&x| f(x)).collect();
822    let mut coeffs = vec![0.0; n];
823    for j in 0..n {
824        let mut sum = 0.0;
825        for (k, &val) in values.iter().enumerate() {
826            let xk = 2.0 * (nodes[k] - a) / (b - a) - 1.0;
827            sum += val * chebyshev_t(j, xk);
828        }
829        coeffs[j] = sum
830            * if j == 0 {
831                1.0 / n as f64
832            } else {
833                2.0 / n as f64
834            };
835    }
836    coeffs
837}
838/// Evaluate Chebyshev series at point x on [a,b].
839pub fn eval_chebyshev(coeffs: &[f64], a: f64, b: f64, x: f64) -> f64 {
840    let t = 2.0 * (x - a) / (b - a) - 1.0;
841    coeffs
842        .iter()
843        .enumerate()
844        .map(|(k, &c)| c * chebyshev_t(k, t))
845        .sum()
846}
847#[cfg(test)]
848mod tests {
849    use super::*;
850    #[test]
851    fn test_rn_vector_norm() {
852        let v = RnVector::new(vec![3.0, 4.0]);
853        assert!((v.norm() - 5.0).abs() < 1e-10);
854    }
855    #[test]
856    fn test_rn_vector_inner() {
857        let e1 = RnVector::new(vec![1.0, 0.0]);
858        let e2 = RnVector::new(vec![0.0, 1.0]);
859        assert!(e1.inner(&e2).abs() < 1e-10);
860        assert!((e1.inner(&e1) - 1.0).abs() < 1e-10);
861    }
862    #[test]
863    fn test_rn_vector_normalized() {
864        let v = RnVector::new(vec![3.0, 4.0]);
865        let u = v.normalized().expect("nonzero");
866        assert!((u.norm() - 1.0).abs() < 1e-10);
867        assert!(RnVector::zero(3).normalized().is_none());
868    }
869    #[test]
870    fn test_rn_vector_basis() {
871        let e0 = RnVector::basis(3, 0);
872        assert!((e0.components[0] - 1.0).abs() < 1e-10);
873        assert!(e0.components[1].abs() < 1e-10);
874    }
875    #[test]
876    fn test_rn_vector_sub() {
877        let c = RnVector::new(vec![5.0, 3.0]).sub(&RnVector::new(vec![2.0, 1.0]));
878        assert!((c.components[0] - 3.0).abs() < 1e-10);
879        assert!((c.components[1] - 2.0).abs() < 1e-10);
880    }
881    #[test]
882    fn test_rn_vector_cross() {
883        let k = RnVector::new(vec![1.0, 0.0, 0.0])
884            .cross(&RnVector::new(vec![0.0, 1.0, 0.0]))
885            .expect("cross should succeed");
886        assert!(k.components[0].abs() < 1e-10);
887        assert!(k.components[1].abs() < 1e-10);
888        assert!((k.components[2] - 1.0).abs() < 1e-10);
889    }
890    #[test]
891    fn test_rn_vector_project() {
892        let proj = RnVector::new(vec![3.0, 4.0]).project_onto(&RnVector::new(vec![1.0, 0.0]));
893        assert!((proj.components[0] - 3.0).abs() < 1e-10);
894        assert!(proj.components[1].abs() < 1e-10);
895    }
896    #[test]
897    fn test_gram_schmidt() {
898        let basis = gram_schmidt(&[
899            RnVector::new(vec![1.0, 1.0, 0.0]),
900            RnVector::new(vec![1.0, 0.0, 1.0]),
901            RnVector::new(vec![0.0, 1.0, 1.0]),
902        ]);
903        assert_eq!(basis.len(), 3);
904        assert!(is_orthonormal(&basis, 1e-10));
905    }
906    #[test]
907    fn test_gram_schmidt_dependent() {
908        let basis = gram_schmidt(&[
909            RnVector::new(vec![1.0, 0.0]),
910            RnVector::new(vec![2.0, 0.0]),
911            RnVector::new(vec![0.0, 1.0]),
912        ]);
913        assert_eq!(basis.len(), 2);
914        assert!(is_orthonormal(&basis, 1e-10));
915    }
916    #[test]
917    fn test_spans_full_space() {
918        assert!(spans_full_space(
919            &[RnVector::new(vec![1.0, 0.0]), RnVector::new(vec![0.0, 1.0])],
920            2
921        ));
922        assert!(!spans_full_space(
923            &[
924                RnVector::new(vec![1.0, 0.0, 0.0]),
925                RnVector::new(vec![0.0, 1.0, 0.0])
926            ],
927            3
928        ));
929    }
930    #[test]
931    fn test_bounded_op_apply() {
932        let id = BoundedOp::identity(3);
933        let v = RnVector::new(vec![1.0, 2.0, 3.0]);
934        let w = id.apply(&v);
935        for (a, b) in w.components.iter().zip(v.components.iter()) {
936            assert!((a - b).abs() < 1e-10);
937        }
938    }
939    #[test]
940    fn test_bounded_op_transpose() {
941        let a = BoundedOp::new(vec![vec![1.0, 2.0, 3.0], vec![4.0, 5.0, 6.0]]);
942        let at = a.transpose();
943        assert_eq!(at.range_dim, 3);
944        assert_eq!(at.domain_dim, 2);
945        let att = at.transpose();
946        for i in 0..2 {
947            for j in 0..3 {
948                assert!((att.matrix[i][j] - a.matrix[i][j]).abs() < 1e-10);
949            }
950        }
951    }
952    #[test]
953    fn test_bounded_op_symmetric() {
954        assert!(BoundedOp::new(vec![vec![2.0, 1.0], vec![1.0, 3.0]]).is_symmetric());
955        assert!(!BoundedOp::new(vec![vec![1.0, 2.0], vec![3.0, 4.0]]).is_symmetric());
956    }
957    #[test]
958    fn test_bounded_op_diagonal() {
959        let w = BoundedOp::diagonal(&[2.0, 3.0, 5.0]).apply(&RnVector::new(vec![1.0, 1.0, 1.0]));
960        assert!((w.components[0] - 2.0).abs() < 1e-10);
961        assert!((w.components[1] - 3.0).abs() < 1e-10);
962        assert!((w.components[2] - 5.0).abs() < 1e-10);
963    }
964    #[test]
965    fn test_bounded_op_add() {
966        let c = BoundedOp::identity(2)
967            .op_add(&BoundedOp::identity(2))
968            .expect("op_add should succeed");
969        assert!((c.matrix[0][0] - 2.0).abs() < 1e-10);
970    }
971    #[test]
972    fn test_bounded_op_determinant() {
973        assert!(
974            (BoundedOp::identity(3)
975                .determinant()
976                .expect("determinant should succeed")
977                - 1.0)
978                .abs()
979                < 1e-10
980        );
981        assert!(
982            (BoundedOp::new(vec![vec![1.0, 2.0], vec![3.0, 4.0]])
983                .determinant()
984                .expect("determinant should succeed")
985                - (-2.0))
986                .abs()
987                < 1e-10
988        );
989    }
990    #[test]
991    fn test_bounded_op_solve() {
992        let x = BoundedOp::new(vec![vec![2.0, 1.0], vec![1.0, 3.0]])
993            .solve(&RnVector::new(vec![5.0, 7.0]))
994            .expect("solve should succeed");
995        assert!((x.components[0] - 1.6).abs() < 1e-10);
996        assert!((x.components[1] - 1.8).abs() < 1e-10);
997    }
998    #[test]
999    fn test_bounded_op_rank_nullity() {
1000        let a = BoundedOp::new(vec![
1001            vec![1.0, 2.0, 3.0],
1002            vec![4.0, 5.0, 6.0],
1003            vec![7.0, 8.0, 9.0],
1004        ]);
1005        assert_eq!(a.rank(), 2);
1006        assert_eq!(a.nullity(), 1);
1007    }
1008    #[test]
1009    fn test_bounded_op_positive_definite() {
1010        assert!(BoundedOp::new(vec![vec![2.0, 1.0], vec![1.0, 2.0]]).is_positive_definite());
1011        assert!(!BoundedOp::new(vec![vec![1.0, 2.0], vec![2.0, 1.0]]).is_positive_definite());
1012    }
1013    #[test]
1014    fn test_power_iteration() {
1015        let (ev, _) = BoundedOp::diagonal(&[5.0, 1.0])
1016            .power_iteration(100)
1017            .expect("power_iteration should succeed");
1018        assert!((ev - 5.0).abs() < 1e-6, "got {ev}");
1019    }
1020    #[test]
1021    fn test_eigenvalues_2x2() {
1022        let (e1, e2) = BoundedOp::new(vec![vec![3.0, 1.0], vec![1.0, 3.0]])
1023            .eigenvalues_2x2()
1024            .expect("eigenvalues_2x2 should succeed");
1025        assert!((e1 - 4.0).abs() < 1e-10);
1026        assert!((e2 - 2.0).abs() < 1e-10);
1027    }
1028    #[test]
1029    fn test_conjugate_gradient() {
1030        let a = BoundedOp::new(vec![vec![4.0, 1.0], vec![1.0, 3.0]]);
1031        let x = conjugate_gradient(&a, &RnVector::new(vec![1.0, 2.0]), 100, 1e-12);
1032        let ax = a.apply(&x);
1033        assert!((ax.components[0] - 1.0).abs() < 1e-8);
1034        assert!((ax.components[1] - 2.0).abs() < 1e-8);
1035    }
1036    #[test]
1037    fn test_conjugate_gradient_identity() {
1038        let b = RnVector::new(vec![1.0, 2.0, 3.0]);
1039        let x = conjugate_gradient(&BoundedOp::identity(3), &b, 100, 1e-12);
1040        for i in 0..3 {
1041            assert!((x.components[i] - b.components[i]).abs() < 1e-8);
1042        }
1043    }
1044    #[test]
1045    fn test_l2_sequence_norm() {
1046        assert!((L2Sequence::new(vec![1.0, 0.0, 0.0]).l2_norm() - 1.0).abs() < 1e-10);
1047        let b = L2Sequence::new(vec![3.0, 4.0]);
1048        assert!((b.l2_norm() - 5.0).abs() < 1e-10);
1049        assert!(b.is_in_l2());
1050    }
1051    #[test]
1052    fn test_l2_sequence_shift() {
1053        let s = L2Sequence::new(vec![1.0, 2.0, 3.0]);
1054        assert_eq!(s.shift_left().terms, vec![2.0, 3.0]);
1055        assert_eq!(s.shift_right().terms, vec![0.0, 1.0, 2.0, 3.0]);
1056    }
1057    #[test]
1058    fn test_l2_sequence_convolve() {
1059        let c = L2Sequence::new(vec![1.0, 2.0]).convolve(&L2Sequence::new(vec![3.0, 4.0]));
1060        assert!((c.terms[0] - 3.0).abs() < 1e-10);
1061        assert!((c.terms[1] - 10.0).abs() < 1e-10);
1062        assert!((c.terms[2] - 8.0).abs() < 1e-10);
1063    }
1064    #[test]
1065    fn test_l2_sequence_parseval() {
1066        assert!(L2Sequence::new(vec![1.0, 2.0, 3.0, 4.0]).parseval_residual() < 1e-10);
1067    }
1068    #[test]
1069    fn test_l2_norm_approx() {
1070        assert!((l2_norm_approx(&|_| 1.0, 0.0, 1.0, 1000) - 1.0).abs() < 1e-3);
1071    }
1072    #[test]
1073    fn test_l2_inner_approx() {
1074        let ip = l2_inner_approx(
1075            &|x: f64| x.sin(),
1076            &|x: f64| x.cos(),
1077            0.0,
1078            2.0 * std::f64::consts::PI,
1079            10000,
1080        );
1081        assert!(ip.abs() < 1e-3, "got {ip}");
1082    }
1083    #[test]
1084    fn test_sup_norm_approx() {
1085        let n = sup_norm_approx(&|x: f64| x.sin(), 0.0, std::f64::consts::PI, 10000);
1086        assert!((n - 1.0).abs() < 1e-3, "got {n}");
1087    }
1088    #[test]
1089    fn test_banach_fixed_point_iter() {
1090        let (fp, iters) = banach_fixed_point_iter(&|x: f64| x.cos(), 0.5, 1000, 1e-10);
1091        assert!((fp - fp.cos()).abs() < 1e-8);
1092        assert!(iters < 1000);
1093    }
1094    #[test]
1095    fn test_fourier_cosine_coefficients() {
1096        let c = fourier_cosine_coefficients(&|_| 1.0, 1.0, 5, 1000);
1097        assert!((c[0] - 1.0).abs() < 1e-3);
1098        for k in 1..5 {
1099            assert!(c[k].abs() < 1e-3);
1100        }
1101    }
1102    #[test]
1103    fn test_eval_fourier_cosine() {
1104        assert!((eval_fourier_cosine(&[1.0, 0.0, 0.0], 1.0, 0.5) - 1.0).abs() < 1e-10);
1105    }
1106    #[test]
1107    fn test_bounded_op_operator_norm_power() {
1108        let n = BoundedOp::diagonal(&[3.0, 1.0, 2.0]).operator_norm_power_iter(200);
1109        assert!((n - 3.0).abs() < 1e-3, "got {n}");
1110    }
1111    #[test]
1112    fn test_bounded_op_frobenius_norm() {
1113        let frob = BoundedOp::identity(2).frobenius_norm();
1114        assert!((frob - 2.0_f64.sqrt()).abs() < 1e-10);
1115        assert!(BoundedOp::identity(2).operator_norm() > 0.0);
1116    }
1117    #[test]
1118    fn test_bounded_op_compose() {
1119        let a = BoundedOp::new(vec![vec![1.0, 2.0], vec![3.0, 4.0]]);
1120        let c = a
1121            .compose(&BoundedOp::identity(2))
1122            .expect("BoundedOp::new should succeed");
1123        for i in 0..2 {
1124            for j in 0..2 {
1125                assert!((c.matrix[i][j] - a.matrix[i][j]).abs() < 1e-10);
1126            }
1127        }
1128    }
1129    #[test]
1130    fn test_bounded_op_scalar_mul() {
1131        let b = BoundedOp::identity(2).scalar_mul(3.0);
1132        assert!((b.matrix[0][0] - 3.0).abs() < 1e-10);
1133    }
1134    #[test]
1135    fn test_rn_vector_lp_norms() {
1136        let v = RnVector::new(vec![3.0, 4.0]);
1137        assert!((v.norm_p(1.0) - 7.0).abs() < 1e-10);
1138        assert!((v.norm_p(f64::INFINITY) - 4.0).abs() < 1e-10);
1139    }
1140    #[test]
1141    fn test_build_functional_analysis_env() {
1142        let mut env = Environment::new();
1143        build_functional_analysis_env(&mut env);
1144        assert!(env.get(&Name::str("NormedSpace")).is_some());
1145        assert!(env.get(&Name::str("hahn_banach")).is_some());
1146        assert!(env.get(&Name::str("fredholm_alternative")).is_some());
1147        assert!(env.get(&Name::str("banach_fixed_point")).is_some());
1148    }
1149    #[test]
1150    fn test_bounded_op_injective_surjective() {
1151        assert!(BoundedOp::identity(3).is_injective());
1152        assert!(BoundedOp::identity(3).is_surjective());
1153        let s = BoundedOp::new(vec![vec![1.0, 0.0], vec![0.0, 0.0]]);
1154        assert!(!s.is_injective());
1155        assert!(!s.is_surjective());
1156    }
1157    #[test]
1158    fn test_rn_vector_angle() {
1159        let angle = RnVector::new(vec![1.0, 0.0]).angle_with(&RnVector::new(vec![0.0, 1.0]));
1160        assert!((angle - std::f64::consts::FRAC_PI_2).abs() < 1e-10);
1161    }
1162    #[test]
1163    fn test_qr_identity() {
1164        let qr = qr_decomposition(&BoundedOp::identity(3)).expect("operation should succeed");
1165        for i in 0..3 {
1166            for j in 0..3 {
1167                let expected = if i == j { 1.0 } else { 0.0 };
1168                assert!((qr.q.matrix[i][j] - expected).abs() < 1e-10);
1169                assert!((qr.r.matrix[i][j] - expected).abs() < 1e-10);
1170            }
1171        }
1172    }
1173    #[test]
1174    fn test_qr_reconstruction() {
1175        let a = BoundedOp::new(vec![vec![1.0, 2.0], vec![3.0, 4.0], vec![5.0, 6.0]]);
1176        let qr = qr_decomposition(&a).expect("BoundedOp::new should succeed");
1177        let prod = qr.q.compose(&qr.r).expect("compose should succeed");
1178        for i in 0..a.range_dim {
1179            for j in 0..a.domain_dim {
1180                assert!(
1181                    (prod.matrix[i][j] - a.matrix[i][j]).abs() < 1e-10,
1182                    "mismatch at ({i},{j}): {} vs {}",
1183                    prod.matrix[i][j],
1184                    a.matrix[i][j]
1185                );
1186            }
1187        }
1188    }
1189    #[test]
1190    fn test_qr_orthogonality() {
1191        let a = BoundedOp::new(vec![
1192            vec![12.0, -51.0, 4.0],
1193            vec![6.0, 167.0, -68.0],
1194            vec![-4.0, 24.0, -41.0],
1195        ]);
1196        let qr = qr_decomposition(&a).expect("operation should succeed");
1197        let qtq =
1198            qr.q.transpose()
1199                .compose(&qr.q)
1200                .expect("compose should succeed");
1201        for i in 0..3 {
1202            for j in 0..3 {
1203                let expected = if i == j { 1.0 } else { 0.0 };
1204                assert!((qtq.matrix[i][j] - expected).abs() < 1e-10);
1205            }
1206        }
1207    }
1208    #[test]
1209    fn test_lu_solve() {
1210        let a = BoundedOp::new(vec![
1211            vec![2.0, 1.0, 1.0],
1212            vec![4.0, 3.0, 3.0],
1213            vec![8.0, 7.0, 9.0],
1214        ]);
1215        let b = RnVector::new(vec![1.0, 1.0, 1.0]);
1216        let lu = lu_decomposition(&a).expect("RnVector::new should succeed");
1217        let x = lu_solve(&lu, &b);
1218        let ax = a.apply(&x);
1219        for i in 0..3 {
1220            assert!(
1221                (ax.components[i] - b.components[i]).abs() < 1e-8,
1222                "lu_solve error at {i}"
1223            );
1224        }
1225    }
1226    #[test]
1227    fn test_lu_identity() {
1228        let lu = lu_decomposition(&BoundedOp::identity(3)).expect("operation should succeed");
1229        for i in 0..3 {
1230            assert!((lu.l.matrix[i][i] - 1.0).abs() < 1e-10);
1231            assert!((lu.u.matrix[i][i] - 1.0).abs() < 1e-10);
1232        }
1233    }
1234    #[test]
1235    fn test_singular_values_diagonal() {
1236        let svs = singular_values(&BoundedOp::diagonal(&[3.0, 1.0, 5.0]), 50);
1237        assert!((svs[0] - 5.0).abs() < 0.1, "sv[0] = {}", svs[0]);
1238        assert!((svs[1] - 3.0).abs() < 0.1, "sv[1] = {}", svs[1]);
1239        assert!((svs[2] - 1.0).abs() < 0.1, "sv[2] = {}", svs[2]);
1240    }
1241    #[test]
1242    fn test_condition_number_identity() {
1243        let cn = condition_number(&BoundedOp::identity(3), 50);
1244        assert!((cn - 1.0).abs() < 0.1, "cond(I) = {cn}");
1245    }
1246    #[test]
1247    fn test_condition_number_ill_conditioned() {
1248        let cn = condition_number(&BoundedOp::diagonal(&[100.0, 0.01]), 50);
1249        assert!(cn > 1000.0, "expected ill-conditioned, got {cn}");
1250    }
1251    #[test]
1252    fn test_spectral_radius() {
1253        let rho = spectral_radius(&BoundedOp::diagonal(&[2.0, -3.0, 1.0]), 200);
1254        assert!((rho - 3.0).abs() < 0.1, "got {rho}");
1255    }
1256    #[test]
1257    fn test_h1_norm_constant() {
1258        let h1 = h1_norm_approx(&|_| 1.0, 0.0, 1.0, 1000);
1259        assert!((h1 - 1.0).abs() < 1e-2, "got {h1}");
1260    }
1261    #[test]
1262    fn test_h1_norm_linear() {
1263        let h1 = h1_norm_approx(&|x| x, 0.0, 1.0, 1000);
1264        let expected = (1.0 / 3.0 + 1.0_f64).sqrt();
1265        assert!(
1266            (h1 - expected).abs() < 0.05,
1267            "got {h1}, expected {expected}"
1268        );
1269    }
1270    #[test]
1271    fn test_h2_norm() {
1272        let h2 = h2_norm_approx(&|x: f64| x * x, 0.0, 1.0, 1000);
1273        assert!(h2 > 0.0);
1274    }
1275    #[test]
1276    fn test_simpson_integrate() {
1277        let result = simpson_integrate(&|x| x * x, 0.0, 1.0, 100);
1278        assert!((result - 1.0 / 3.0).abs() < 1e-6, "got {result}");
1279    }
1280    #[test]
1281    fn test_gauss_legendre_quadrature() {
1282        let result = gauss_legendre_composite(&|x| x * x, 0.0, 1.0, 10);
1283        assert!((result - 1.0 / 3.0).abs() < 1e-10, "got {result}");
1284    }
1285    #[test]
1286    fn test_matrix_exponential_zero() {
1287        let exp_zero = matrix_exponential(&BoundedOp::zero_op(3, 3), 20);
1288        for i in 0..3 {
1289            for j in 0..3 {
1290                let expected = if i == j { 1.0 } else { 0.0 };
1291                assert!((exp_zero.matrix[i][j] - expected).abs() < 1e-10);
1292            }
1293        }
1294    }
1295    #[test]
1296    fn test_matrix_exponential_identity() {
1297        let exp_i = matrix_exponential(&BoundedOp::identity(2), 20);
1298        let e = std::f64::consts::E;
1299        assert!(
1300            (exp_i.matrix[0][0] - e).abs() < 1e-6,
1301            "got {}",
1302            exp_i.matrix[0][0]
1303        );
1304        assert!((exp_i.matrix[1][1] - e).abs() < 1e-6);
1305        assert!(exp_i.matrix[0][1].abs() < 1e-6);
1306    }
1307    #[test]
1308    fn test_commutator_self() {
1309        let a = BoundedOp::new(vec![vec![1.0, 2.0], vec![3.0, 4.0]]);
1310        let c = commutator(&a, &a).expect("BoundedOp::new should succeed");
1311        for i in 0..2 {
1312            for j in 0..2 {
1313                assert!(c.matrix[i][j].abs() < 1e-10);
1314            }
1315        }
1316    }
1317    #[test]
1318    fn test_anti_commutator() {
1319        let a = BoundedOp::identity(2);
1320        let ac = anti_commutator(&a, &a).expect("BoundedOp::identity should succeed");
1321        assert!((ac.matrix[0][0] - 2.0).abs() < 1e-10);
1322        assert!((ac.matrix[1][1] - 2.0).abs() < 1e-10);
1323    }
1324    #[test]
1325    fn test_fredholm_index_zero() {
1326        let k = BoundedOp::zero_op(3, 3);
1327        let (kd, cd, idx) = fredholm_index_numerical(&k);
1328        assert_eq!(kd, 0);
1329        assert_eq!(cd, 0);
1330        assert_eq!(idx, 0);
1331    }
1332    #[test]
1333    fn test_fredholm_index_projection() {
1334        let k = BoundedOp::new(vec![vec![1.0, 0.0], vec![0.0, 0.0]]);
1335        let (kd, _cd, _idx) = fredholm_index_numerical(&k);
1336        assert_eq!(kd, 1);
1337    }
1338    #[test]
1339    fn test_jacobi_iteration() {
1340        let a = BoundedOp::new(vec![vec![4.0, 1.0], vec![1.0, 3.0]]);
1341        let b = RnVector::new(vec![1.0, 2.0]);
1342        let x = jacobi_iteration(&a, &b, 200, 1e-10);
1343        let ax = a.apply(&x);
1344        assert!((ax.components[0] - b.components[0]).abs() < 1e-6);
1345        assert!((ax.components[1] - b.components[1]).abs() < 1e-6);
1346    }
1347    #[test]
1348    fn test_gauss_seidel_iteration() {
1349        let a = BoundedOp::new(vec![vec![4.0, 1.0], vec![1.0, 3.0]]);
1350        let b = RnVector::new(vec![1.0, 2.0]);
1351        let x = gauss_seidel_iteration(&a, &b, 200, 1e-10);
1352        let ax = a.apply(&x);
1353        assert!((ax.components[0] - b.components[0]).abs() < 1e-6);
1354        assert!((ax.components[1] - b.components[1]).abs() < 1e-6);
1355    }
1356    #[test]
1357    fn test_fourier_sine_coefficients() {
1358        let coeffs =
1359            fourier_sine_coefficients(&|x: f64| (std::f64::consts::PI * x).sin(), 1.0, 5, 1000);
1360        assert!((coeffs[0] - 1.0).abs() < 1e-2, "got {}", coeffs[0]);
1361        for k in 1..5 {
1362            assert!(coeffs[k].abs() < 1e-2, "coeff[{k}] = {}", coeffs[k]);
1363        }
1364    }
1365    #[test]
1366    fn test_eval_fourier_sine() {
1367        assert!((eval_fourier_sine(&[1.0, 0.0, 0.0], 1.0, 0.5) - 1.0).abs() < 1e-10);
1368    }
1369    #[test]
1370    fn test_chebyshev_t_values() {
1371        assert!((chebyshev_t(0, 0.5) - 1.0).abs() < 1e-10);
1372        assert!((chebyshev_t(1, 0.5) - 0.5).abs() < 1e-10);
1373        assert!((chebyshev_t(2, 0.5) - (-0.5)).abs() < 1e-10);
1374    }
1375    #[test]
1376    fn test_chebyshev_nodes() {
1377        let nodes = chebyshev_nodes(4, -1.0, 1.0);
1378        assert_eq!(nodes.len(), 4);
1379        for &x in &nodes {
1380            assert!(x >= -1.0 && x <= 1.0);
1381        }
1382    }
1383    #[test]
1384    fn test_chebyshev_approximation() {
1385        let coeffs = chebyshev_coefficients(&|x: f64| x.sin(), 10, -1.0, 1.0);
1386        let approx = eval_chebyshev(&coeffs, -1.0, 1.0, 0.5);
1387        assert!(
1388            (approx - 0.5_f64.sin()).abs() < 0.1,
1389            "got {approx}, expected {}",
1390            0.5_f64.sin()
1391        );
1392    }
1393}
1394#[cfg(test)]
1395mod tests_functional_analysis_ext {
1396    use super::*;
1397    #[test]
1398    fn test_fredholm_operator() {
1399        let fo = FredholmOperatorData::new("D_A", 2, 1);
1400        assert_eq!(fo.index(), 1);
1401        assert!(!fo.is_isomorphism());
1402        assert!(fo.index_stable_under_compact());
1403        assert!(fo.atkinson_description().contains("index 1"));
1404    }
1405    #[test]
1406    fn test_fredholm_isomorphism() {
1407        let fo = FredholmOperatorData::new("I", 0, 0);
1408        assert_eq!(fo.index(), 0);
1409        assert!(fo.is_isomorphism());
1410    }
1411    #[test]
1412    fn test_sobolev_space() {
1413        let ss = SobolevSpaceData::new(1, 2.0, 3, "R^3");
1414        let p_star = ss
1415            .critical_sobolev_exponent()
1416            .expect("critical_sobolev_exponent should succeed");
1417        assert!((p_star - 6.0).abs() < 1e-10, "p* should be 6, got {p_star}");
1418        assert!(ss.rellich_kondrachov_compact());
1419        assert!(ss.trace_theorem().contains("Trace"));
1420    }
1421    #[test]
1422    fn test_sobolev_supercritical() {
1423        let ss = SobolevSpaceData::new(2, 2.0, 2, "R^2");
1424        assert!(ss.critical_sobolev_exponent().is_none());
1425    }
1426    #[test]
1427    fn test_interpolation() {
1428        let id = InterpolationData::new("L^1", "L^inf", 0.5, InterpolationMethod::Complex);
1429        let exp = id.lp_exponent(1.0, 1e10);
1430        assert!((exp - 0.5).abs() < 1e-9, "1/p should be ~0.5");
1431        let bound = id.riesz_thorin_bound(1.0, 1.0);
1432        assert!((bound - 1.0).abs() < 1e-10);
1433    }
1434}
1435#[cfg(test)]
1436mod tests_functional_analysis_ext2 {
1437    use super::*;
1438    #[test]
1439    fn test_weak_convergence() {
1440        let seq: Vec<Vec<f64>> = (1..=10).map(|n| vec![1.0 / n as f64, 0.0]).collect();
1441        let wcd = WeakConvergenceData::new(seq);
1442        assert!(wcd.is_bounded);
1443        assert!(wcd.banach_alaoglu_applies());
1444        assert!(wcd.check_weak_convergence(&[0.0, 0.0], 0.2));
1445    }
1446    #[test]
1447    fn test_distribution_dirac() {
1448        let delta = Distribution::dirac_delta(0.0);
1449        assert!(!delta.is_regular);
1450        assert_eq!(delta.order, Some(0));
1451        assert!(delta.is_tempered());
1452        let deriv = delta.differentiate();
1453        assert_eq!(deriv.order, Some(1));
1454    }
1455    #[test]
1456    fn test_distribution_regular() {
1457        let reg = Distribution::regular("f", "R").differentiate();
1458        assert!(!reg.is_regular);
1459        assert_eq!(reg.order, Some(1));
1460        assert!(reg
1461            .fourier_transform_description()
1462            .contains("distributional"));
1463    }
1464}