Skip to main content

oxilean_std/harmonic_analysis/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::functions::*;
6use oxilean_kernel::{BinderInfo, Declaration, Environment, Expr, Level, Name};
7
8/// Fourier multiplier operator with a given symbol.
9pub struct MultiplierOperator {
10    /// Symbol m(ξ) of the operator.
11    pub symbol: String,
12    /// Whether the operator is Lᵖ-bounded (p ≠ 2).
13    pub is_bounded: bool,
14}
15impl MultiplierOperator {
16    /// Create a new MultiplierOperator.
17    pub fn new(symbol: impl Into<String>, is_bounded: bool) -> Self {
18        Self {
19            symbol: symbol.into(),
20            is_bounded,
21        }
22    }
23    /// Lᵖ boundedness of the multiplier operator.
24    pub fn lp_boundedness(&self) -> bool {
25        self.is_bounded
26    }
27    /// Hörmander–Mikhlin multiplier condition (sufficient for Lᵖ bounds).
28    pub fn hormander_condition(&self) -> bool {
29        self.is_bounded
30    }
31}
32/// Real-variable Hardy space Hᵖ.
33pub struct HardySpace {
34    /// The integrability exponent p (0 < p ≤ ∞).
35    pub p: f64,
36    /// Whether this is the real-variable (not holomorphic) Hardy space.
37    pub is_real_variable: bool,
38}
39impl HardySpace {
40    /// Create a new HardySpace.
41    pub fn new(p: f64, is_real_variable: bool) -> Self {
42        Self {
43            p,
44            is_real_variable,
45        }
46    }
47    /// Atomic decomposition of H¹: every f ∈ H¹ is a sum of atoms.
48    pub fn atomic_decomposition(&self) -> bool {
49        self.p <= 1.0
50    }
51    /// Duality: (H¹)* = BMO (Fefferman-Stein).
52    pub fn duality_with_bmo(&self) -> bool {
53        (self.p - 1.0).abs() < 1e-12
54    }
55}
56/// Abstract harmonic analysis on a group.
57pub struct GroupHarmonic {
58    /// Type of group (e.g., "compact", "locally_compact", "abelian").
59    pub group_type: String,
60}
61impl GroupHarmonic {
62    /// Create a new GroupHarmonic object.
63    pub fn new(group_type: impl Into<String>) -> Self {
64        Self {
65            group_type: group_type.into(),
66        }
67    }
68    /// Connection to unitary representation theory of the group.
69    pub fn representation_theory_connection(&self) -> bool {
70        true
71    }
72    /// Peter-Weyl theorem: L²(G) decomposes into finite-dim unitary reps (compact G).
73    pub fn peter_weyl(&self) -> bool {
74        self.group_type.to_lowercase().contains("compact")
75    }
76}
77/// A discrete H¹ atom checker over a finite signal.
78///
79/// An H¹-atom supported on an interval I = [lo, hi] satisfies:
80/// 1. supp(a) ⊆ I
81/// 2. ‖a‖_{L∞} ≤ 1/|I|
82/// 3. ∫ a = 0  (zero-mean / cancellation condition)
83#[derive(Debug, Clone)]
84pub struct HardySpaceAtom {
85    /// Support interval [lo, hi] (index range).
86    pub lo: usize,
87    /// Support interval hi (inclusive).
88    pub hi: usize,
89    /// Values of the atom (length = full signal length; zero outside support).
90    pub values: Vec<f64>,
91}
92impl HardySpaceAtom {
93    /// Create a new H¹ atom with the given support and values.
94    pub fn new(lo: usize, hi: usize, values: Vec<f64>) -> Self {
95        HardySpaceAtom { lo, hi, values }
96    }
97    /// Length of the support interval.
98    pub fn support_length(&self) -> usize {
99        if self.hi >= self.lo {
100            self.hi - self.lo + 1
101        } else {
102            0
103        }
104    }
105    /// Check that all values outside [lo, hi] are zero.
106    pub fn has_compact_support(&self) -> bool {
107        self.values
108            .iter()
109            .enumerate()
110            .filter(|&(i, _)| i < self.lo || i > self.hi)
111            .all(|(_, &v)| v.abs() < 1e-12)
112    }
113    /// Check the L∞ bound: ‖a‖_{L∞} ≤ 1/|I|.
114    pub fn satisfies_linfty_bound(&self) -> bool {
115        let len = self.support_length();
116        if len == 0 {
117            return true;
118        }
119        let bound = 1.0 / len as f64;
120        self.values.iter().all(|&v| v.abs() <= bound + 1e-12)
121    }
122    /// Check the cancellation condition: ∫ a = 0.
123    pub fn has_zero_mean(&self) -> bool {
124        let sum: f64 = self.values.iter().sum();
125        sum.abs() < 1e-10
126    }
127    /// Verify all three H¹ atom conditions.
128    pub fn is_valid_atom(&self) -> bool {
129        self.has_compact_support() && self.satisfies_linfty_bound() && self.has_zero_mean()
130    }
131    /// Construct a canonical atom supported on [lo, hi] with +1/|I| on first half,
132    /// -1/|I| on second half (or ±1/|I| for |I|=1 → zero atom).
133    pub fn canonical(signal_len: usize, lo: usize, hi: usize) -> Self {
134        let mut values = vec![0.0f64; signal_len];
135        let len = if hi >= lo { hi - lo + 1 } else { 0 };
136        if len < 2 {
137            return HardySpaceAtom { lo, hi, values };
138        }
139        let weight = 1.0 / len as f64;
140        let mid = lo + len / 2;
141        for i in lo..mid {
142            if i < signal_len {
143                values[i] = weight;
144            }
145        }
146        for i in mid..=hi {
147            if i < signal_len {
148                values[i] = -weight;
149            }
150        }
151        HardySpaceAtom { lo, hi, values }
152    }
153}
154/// Discrete Littlewood-Paley square function S(f) = (Σⱼ |Δⱼf|²)^{1/2}.
155///
156/// Uses DFT-based dyadic frequency decomposition.
157#[derive(Debug, Clone)]
158pub struct LittlewoodPaleySquare {
159    /// The original signal.
160    pub signal: Vec<f64>,
161}
162impl LittlewoodPaleySquare {
163    /// Create from signal.
164    pub fn new(signal: Vec<f64>) -> Self {
165        LittlewoodPaleySquare { signal }
166    }
167    /// Forward DFT of the signal.
168    fn dft(signal: &[f64]) -> Vec<(f64, f64)> {
169        let n = signal.len();
170        if n == 0 {
171            return vec![];
172        }
173        let two_pi_over_n = 2.0 * std::f64::consts::PI / n as f64;
174        (0..n)
175            .map(|k| {
176                signal
177                    .iter()
178                    .enumerate()
179                    .fold((0.0, 0.0), |(re, im), (j, &x)| {
180                        let angle = two_pi_over_n * (k * j) as f64;
181                        (re + x * angle.cos(), im - x * angle.sin())
182                    })
183            })
184            .collect()
185    }
186    /// Inverse DFT.
187    fn idft(spectrum: &[(f64, f64)]) -> Vec<f64> {
188        let n = spectrum.len();
189        if n == 0 {
190            return vec![];
191        }
192        let two_pi_over_n = 2.0 * std::f64::consts::PI / n as f64;
193        let n_f = n as f64;
194        (0..n)
195            .map(|j| {
196                spectrum
197                    .iter()
198                    .enumerate()
199                    .map(|(k, &(re, im))| {
200                        let angle = two_pi_over_n * (k * j) as f64;
201                        (re * angle.cos() - im * angle.sin()) / n_f
202                    })
203                    .sum()
204            })
205            .collect()
206    }
207    /// Compute the pointwise square function S(f)(i) = (Σⱼ |Δⱼf(i)|²)^{1/2}.
208    pub fn square_function_pointwise(&self) -> Vec<f64> {
209        let n = self.signal.len();
210        if n == 0 {
211            return vec![];
212        }
213        let spectrum = Self::dft(&self.signal);
214        let mut sum_sq = vec![0.0f64; n];
215        let mut j = 1usize;
216        while j < n {
217            let lo = j;
218            let hi = (2 * j).min(n);
219            let mut block_spectrum: Vec<(f64, f64)> = vec![(0.0, 0.0); n];
220            for k in lo..hi {
221                block_spectrum[k] = spectrum[k];
222                let mirror = n - k;
223                if mirror < n && mirror != k {
224                    block_spectrum[mirror] = spectrum[mirror];
225                }
226            }
227            let block_signal = Self::idft(&block_spectrum);
228            for i in 0..n {
229                sum_sq[i] += block_signal[i] * block_signal[i];
230            }
231            j = hi;
232            if hi >= n {
233                break;
234            }
235        }
236        sum_sq.iter().map(|&s| s.sqrt()).collect()
237    }
238    /// L² norm of the square function.
239    pub fn l2_norm_squared(&self) -> f64 {
240        self.square_function_pointwise()
241            .iter()
242            .map(|&v| v * v)
243            .sum()
244    }
245    /// L² norm of the original signal.
246    pub fn signal_l2_norm_squared(&self) -> f64 {
247        self.signal.iter().map(|&x| x * x).sum()
248    }
249    /// Verify the Littlewood-Paley inequality: ‖S(f)‖₂ ≈ ‖f‖₂.
250    ///
251    /// Returns (‖S(f)‖₂², ‖f‖₂², ratio ≈ 1).
252    pub fn verify_lp_inequality(&self) -> (f64, f64, f64) {
253        let sq_norm = self.l2_norm_squared();
254        let sig_norm = self.signal_l2_norm_squared();
255        let ratio = if sig_norm > 1e-15 {
256            sq_norm / sig_norm
257        } else {
258            1.0
259        };
260        (sq_norm, sig_norm, ratio)
261    }
262}
263/// A discrete Fourier multiplier operator M_m.
264///
265/// Given a multiplier symbol m: {0, …, N-1} → ℝ, applies the operator
266/// (M_m f)^(k) = m(k) · f̂(k), i.e., pointwise multiplication in frequency domain.
267#[derive(Debug, Clone)]
268pub struct FourierMultiplierOp {
269    /// Multiplier symbol m(k) for k = 0, …, N-1.
270    pub symbol: Vec<f64>,
271}
272impl FourierMultiplierOp {
273    /// Create a new Fourier multiplier with given symbol.
274    pub fn new(symbol: Vec<f64>) -> Self {
275        FourierMultiplierOp { symbol }
276    }
277    /// Create the Hilbert transform multiplier: m(k) = -i·sign(k).
278    /// For real signals: m(0)=0, m(k)=1 for k>0, m(k)=-1 for k<0 (as real multiplier).
279    pub fn hilbert_multiplier(n: usize) -> Self {
280        let mut symbol = vec![0.0f64; n];
281        for k in 1..n / 2 {
282            symbol[k] = 1.0;
283        }
284        for k in n / 2..n {
285            symbol[k] = -1.0;
286        }
287        FourierMultiplierOp { symbol }
288    }
289    /// Create a low-pass filter: m(k) = 1 for |k| ≤ cutoff, 0 otherwise.
290    pub fn low_pass(n: usize, cutoff: usize) -> Self {
291        let mut symbol = vec![0.0f64; n];
292        for k in 0..=cutoff.min(n - 1) {
293            symbol[k] = 1.0;
294        }
295        for k in (n - cutoff).min(n)..n {
296            symbol[k] = 1.0;
297        }
298        FourierMultiplierOp { symbol }
299    }
300    /// Apply the multiplier to a real signal (via DFT).
301    ///
302    /// Computes M_m f = IDFT(m · DFT(f)).
303    pub fn apply(&self, signal: &[f64]) -> Vec<f64> {
304        let n = signal.len();
305        assert_eq!(n, self.symbol.len());
306        if n == 0 {
307            return vec![];
308        }
309        let two_pi_over_n = 2.0 * std::f64::consts::PI / n as f64;
310        let spectrum: Vec<(f64, f64)> = (0..n)
311            .map(|k| {
312                signal
313                    .iter()
314                    .enumerate()
315                    .fold((0.0, 0.0), |(re, im), (j, &x)| {
316                        let angle = two_pi_over_n * (k * j) as f64;
317                        (re + x * angle.cos(), im - x * angle.sin())
318                    })
319            })
320            .collect();
321        let filtered: Vec<(f64, f64)> = spectrum
322            .iter()
323            .zip(self.symbol.iter())
324            .map(|(&(re, im), &m)| (re * m, im * m))
325            .collect();
326        let n_f = n as f64;
327        (0..n)
328            .map(|j| {
329                filtered
330                    .iter()
331                    .enumerate()
332                    .map(|(k, &(re, im))| {
333                        let angle = two_pi_over_n * (k * j) as f64;
334                        (re * angle.cos() - im * angle.sin()) / n_f
335                    })
336                    .sum()
337            })
338            .collect()
339    }
340    /// Compute the operator norm (sup over unit vectors) approximately via power iteration.
341    ///
342    /// For a multiplier, the L² norm is just ‖m‖_{L∞}.
343    pub fn l2_operator_norm(&self) -> f64 {
344        self.symbol.iter().fold(0.0_f64, |acc, &m| acc.max(m.abs()))
345    }
346    /// Verify L² boundedness: M_m is L²-bounded iff m ∈ L∞.
347    pub fn is_l2_bounded(&self) -> bool {
348        self.l2_operator_norm().is_finite()
349    }
350}
351/// Represents a Calderón-Zygmund operator.
352#[allow(dead_code)]
353#[derive(Debug, Clone)]
354pub struct CalderonZygmundOperator {
355    /// Name of the operator.
356    pub name: String,
357    /// Whether it is bounded on L^2.
358    pub l2_bounded: bool,
359    /// Order of the kernel singularity.
360    pub kernel_order: f64,
361    /// Dimension n.
362    pub dimension: usize,
363}
364#[allow(dead_code)]
365impl CalderonZygmundOperator {
366    /// Creates a CZ operator.
367    pub fn new(name: &str, dim: usize) -> Self {
368        CalderonZygmundOperator {
369            name: name.to_string(),
370            l2_bounded: true,
371            kernel_order: 0.0,
372            dimension: dim,
373        }
374    }
375    /// Creates the Hilbert transform (1D CZ operator).
376    pub fn hilbert_transform() -> Self {
377        CalderonZygmundOperator::new("H", 1)
378    }
379    /// Creates the Riesz transform R_j in R^n.
380    pub fn riesz_transform(j: usize, n: usize) -> Self {
381        CalderonZygmundOperator::new(&format!("R_{j}"), n)
382    }
383    /// CZ theorem: bounded on L^p for 1 < p < ∞.
384    pub fn lp_boundedness(&self, p: f64) -> bool {
385        self.l2_bounded && p > 1.0 && p < f64::INFINITY
386    }
387    /// Weak-type (1,1) bound: ||Tf||_{1,∞} <= C ||f||_1.
388    pub fn weak_type_one_one(&self) -> bool {
389        self.l2_bounded
390    }
391    /// Cotlar-Stein almost orthogonality estimate (symbolic).
392    pub fn cotlar_stein_description(&self) -> String {
393        format!(
394            "Cotlar-Stein for {}: almost orthogonal sum bounded on L^2",
395            self.name
396        )
397    }
398    /// T(1) theorem condition: T bounded on L^2 iff T(1) ∈ BMO and T^*(1) ∈ BMO.
399    pub fn t1_theorem_condition(&self) -> String {
400        format!(
401            "T(1) theorem: {} bounded on L^2 iff T(1), T^*(1) ∈ BMO",
402            self.name
403        )
404    }
405}
406/// Discrete Littlewood-Paley square function for a 1D signal of length N = 2^m.
407///
408/// Partitions the DFT spectrum into dyadic blocks [2ʲ, 2^{j+1}) and computes
409/// S(f)(i) = (Σⱼ |Δⱼf(i)|²)^{1/2} via inverse DFT of each block.
410pub struct LPSquareFunction {
411    /// The original signal.
412    pub signal: Vec<f64>,
413}
414impl LPSquareFunction {
415    /// Create from signal.
416    pub fn new(signal: Vec<f64>) -> Self {
417        Self { signal }
418    }
419    /// Compute the square function value at position i.
420    pub fn square_function_pointwise(&self) -> Vec<f64> {
421        let n = self.signal.len();
422        if n == 0 {
423            return vec![];
424        }
425        let spectrum = dft(&self.signal);
426        let mut sum_sq = vec![0.0f64; n];
427        let mut j = 1usize;
428        while j < n {
429            let lo = j;
430            let hi = (2 * j).min(n);
431            let mut block_spectrum: Vec<(f64, f64)> = vec![(0.0, 0.0); n];
432            for k in lo..hi {
433                block_spectrum[k] = spectrum[k];
434                let mirror = n - k;
435                if mirror < n && mirror != k {
436                    block_spectrum[mirror] = spectrum[mirror];
437                }
438            }
439            let block_signal = idft(&block_spectrum);
440            for i in 0..n {
441                sum_sq[i] += block_signal[i] * block_signal[i];
442            }
443            j = hi;
444            if hi >= n {
445                break;
446            }
447        }
448        sum_sq.iter().map(|&s| s.sqrt()).collect()
449    }
450    /// L² norm of the square function (should ≈ L² norm of signal by LP inequality).
451    pub fn l2_norm_squared(&self) -> f64 {
452        self.square_function_pointwise()
453            .iter()
454            .map(|&v| v * v)
455            .sum()
456    }
457}
458/// Calderón-Zygmund singular integral operator.
459pub struct CalderonZygmund {
460    /// Description of the CZ kernel.
461    pub kernel: String,
462    /// Whether the kernel is a singular (non-integrable) kernel.
463    pub is_singular: bool,
464}
465impl CalderonZygmund {
466    /// Create a new CalderonZygmund operator.
467    pub fn new(kernel: impl Into<String>, is_singular: bool) -> Self {
468        Self {
469            kernel: kernel.into(),
470            is_singular,
471        }
472    }
473    /// Lᵖ estimate: CZ operators are bounded on Lᵖ for 1 < p < ∞.
474    pub fn lp_estimate(&self) -> bool {
475        true
476    }
477    /// Endpoint L¹ → weak-L¹ bound.
478    pub fn endpoint_l1(&self) -> bool {
479        true
480    }
481}
482/// Data for oscillatory integral estimation.
483#[allow(dead_code)]
484#[derive(Debug, Clone)]
485pub struct OscillatoryIntegralData {
486    /// Phase function description.
487    pub phase: String,
488    /// Amplitude function description.
489    pub amplitude: String,
490    /// Frequency parameter λ.
491    pub frequency: f64,
492    /// Dimension.
493    pub dim: usize,
494}
495#[allow(dead_code)]
496impl OscillatoryIntegralData {
497    /// Creates oscillatory integral data.
498    pub fn new(phase: &str, amplitude: &str, lambda: f64, dim: usize) -> Self {
499        OscillatoryIntegralData {
500            phase: phase.to_string(),
501            amplitude: amplitude.to_string(),
502            frequency: lambda,
503            dim,
504        }
505    }
506    /// Stationary phase estimate: |I(λ)| ~ λ^{-n/2} as λ → ∞.
507    pub fn stationary_phase_decay(&self) -> f64 {
508        self.frequency.powf(-(self.dim as f64) / 2.0)
509    }
510    /// Van der Corput lemma: if |φ'| >= 1, then |I(λ)| <= C λ^{-1/k} for k-th order derivative.
511    pub fn van_der_corput_bound(&self, k: usize) -> f64 {
512        self.frequency.powf(-1.0 / k as f64)
513    }
514    /// Returns the Strichartz estimate description.
515    pub fn strichartz_description(&self) -> String {
516        format!(
517            "Strichartz: ||e^{{it∆}}f||_{{L^p_t L^q_x}} <= C ||f||_{{L^2}} (n={})",
518            self.dim
519        )
520    }
521}
522/// Data for the BMO (Bounded Mean Oscillation) space.
523#[allow(dead_code)]
524#[derive(Debug, Clone)]
525pub struct BMOData {
526    /// Samples of a function.
527    pub values: Vec<f64>,
528    /// BMO seminorm estimate.
529    pub bmo_seminorm: f64,
530}
531#[allow(dead_code)]
532impl BMOData {
533    /// Creates BMO data.
534    pub fn new(values: Vec<f64>) -> Self {
535        let seminorm = Self::compute_bmo_seminorm(&values);
536        BMOData {
537            values,
538            bmo_seminorm: seminorm,
539        }
540    }
541    /// Computes the BMO seminorm: sup_Q (1/|Q|) ∫_Q |f - f_Q|.
542    /// Simplified: use standard deviation over the whole sample.
543    fn compute_bmo_seminorm(values: &[f64]) -> f64 {
544        if values.is_empty() {
545            return 0.0;
546        }
547        let mean: f64 = values.iter().sum::<f64>() / values.len() as f64;
548        let dev: f64 = values.iter().map(|&x| (x - mean).abs()).sum::<f64>() / values.len() as f64;
549        dev
550    }
551    /// John-Nirenberg inequality: f ∈ BMO iff exp(c|f|/||f||_BMO) is locally integrable.
552    pub fn john_nirenberg_constant(&self) -> f64 {
553        if self.bmo_seminorm < 1e-14 {
554            0.0
555        } else {
556            1.0 / self.bmo_seminorm
557        }
558    }
559    /// Checks if this function is in VMO (vanishing mean oscillation).
560    pub fn is_vmo_approx(&self, tol: f64) -> bool {
561        self.bmo_seminorm < tol
562    }
563}
564/// Fourier series on an interval of given period.
565pub struct FourierSeries {
566    /// Period of the underlying function.
567    pub period: f64,
568    /// Cosine and sine coefficients: (aₙ, bₙ) for n = 0, 1, 2, …
569    pub coefficients: Vec<(f64, f64)>,
570}
571impl FourierSeries {
572    /// Create a new FourierSeries.
573    pub fn new(period: f64, coefficients: Vec<(f64, f64)>) -> Self {
574        Self {
575            period,
576            coefficients,
577        }
578    }
579    /// Evaluate the N-th partial sum at x.
580    ///
581    /// Sₙ(x) = a₀/2 + Σₖ₌₁ⁿ [aₖ cos(2πkx/T) + bₖ sin(2πkx/T)]
582    pub fn partial_sum(&self, n: usize, x: f64) -> f64 {
583        let t = self.period;
584        let (a0, _b0) = self.coefficients.first().copied().unwrap_or((0.0, 0.0));
585        let mut s = a0 / 2.0;
586        let limit = n.min(self.coefficients.len().saturating_sub(1));
587        for k in 1..=limit {
588            let (ak, bk) = self.coefficients[k];
589            let arg = 2.0 * std::f64::consts::PI * (k as f64) * x / t;
590            s += ak * arg.cos() + bk * arg.sin();
591        }
592        s
593    }
594    /// Parseval's identity: (1/T)∫|f|² = a₀²/4 + (1/2)Σ(aₙ²+bₙ²).
595    pub fn parseval_identity(&self) -> f64 {
596        let (a0, _) = self.coefficients.first().copied().unwrap_or((0.0, 0.0));
597        let mut s = a0 * a0 / 4.0;
598        for &(ak, bk) in self.coefficients.iter().skip(1) {
599            s += 0.5 * (ak * ak + bk * bk);
600        }
601        s
602    }
603    /// Dirichlet kernel value Dₙ(x) = sin((n+½)x) / sin(x/2).
604    pub fn dirichlet_kernel(&self) -> f64 {
605        let n = self.coefficients.len();
606        let x = 0.1_f64;
607        let num = ((n as f64 + 0.5) * x).sin();
608        let den = (x / 2.0).sin();
609        if den.abs() < 1e-15 {
610            (2 * n + 1) as f64
611        } else {
612            num / den
613        }
614    }
615}
616/// Data for the Fourier restriction problem.
617#[allow(dead_code)]
618#[derive(Debug, Clone)]
619pub struct FourierRestrictionData {
620    /// Manifold/hypersurface.
621    pub manifold: String,
622    /// Dimension n.
623    pub dimension: usize,
624    /// Stein-Tomas exponent p_0 = 2(n+1)/(n-1).
625    pub stein_tomas_p: f64,
626}
627#[allow(dead_code)]
628impl FourierRestrictionData {
629    /// Creates restriction data.
630    pub fn new(manifold: &str, dim: usize) -> Self {
631        let n = dim as f64;
632        let p0 = 2.0 * (n + 1.0) / (n - 1.0);
633        FourierRestrictionData {
634            manifold: manifold.to_string(),
635            dimension: dim,
636            stein_tomas_p: p0,
637        }
638    }
639    /// Stein-Tomas theorem: restriction R_S: L^{p'} → L^2(S, dσ) bounded for p' <= p_0.
640    pub fn stein_tomas_statement(&self) -> String {
641        format!(
642            "Stein-Tomas: R_{{{}}}: L^{{p'}} → L^2(S) for p' <= {:.3}",
643            self.manifold, self.stein_tomas_p
644        )
645    }
646    /// Decoupling theorem connection (Bourgain-Demeter).
647    pub fn decoupling_description(&self) -> String {
648        format!(
649            "Bourgain-Demeter decoupling for {} in R^{}",
650            self.manifold, self.dimension
651        )
652    }
653    /// Checks if the endpoint Stein-Tomas holds.
654    pub fn endpoint_holds(&self) -> bool {
655        false
656    }
657}
658/// Fourier transform — continuous or discrete variant.
659pub struct FourierTransform {
660    /// Whether this is the continuous Fourier transform.
661    pub is_continuous: bool,
662}
663impl FourierTransform {
664    /// Create a new FourierTransform.
665    pub fn new(is_continuous: bool) -> Self {
666        Self { is_continuous }
667    }
668    /// The Fourier inversion theorem: f = ℱ⁻¹(ℱ(f)).
669    pub fn fourier_inversion(&self) -> bool {
670        true
671    }
672    /// Plancherel theorem: ‖ℱf‖₂ = ‖f‖₂.
673    pub fn plancherel_theorem(&self) -> bool {
674        true
675    }
676    /// Riemann-Lebesgue lemma: ℱf(ξ) → 0 as |ξ| → ∞ for f ∈ L¹.
677    pub fn riemann_lebesgue(&self) -> bool {
678        self.is_continuous
679    }
680}
681/// Data for multilinear Calderón-Zygmund operators.
682#[allow(dead_code)]
683#[derive(Debug, Clone)]
684pub struct MultilinearCZData {
685    /// Number of inputs m.
686    pub m: usize,
687    /// Name.
688    pub name: String,
689    /// Whether the operator is bounded.
690    pub is_bounded: bool,
691}
692#[allow(dead_code)]
693impl MultilinearCZData {
694    /// Creates multilinear CZ data.
695    pub fn new(name: &str, m: usize) -> Self {
696        MultilinearCZData {
697            m,
698            name: name.to_string(),
699            is_bounded: true,
700        }
701    }
702    /// Hölder exponent: 1/r = 1/p_1 + ... + 1/p_m, 1 < p_j <= ∞.
703    pub fn holder_exponent(exponents: &[f64]) -> f64 {
704        exponents.iter().map(|&p| 1.0 / p).sum()
705    }
706    /// Returns the Leibniz rule description.
707    pub fn leibniz_rule(&self) -> String {
708        format!("Product rule for {}: fractional differentiation", self.name)
709    }
710}
711/// Convolution of two named functions f and g.
712pub struct Convolution {
713    /// Name of the first function.
714    pub f: String,
715    /// Name of the second function.
716    pub g: String,
717}
718impl Convolution {
719    /// Create a new Convolution.
720    pub fn new(f: impl Into<String>, g: impl Into<String>) -> Self {
721        Self {
722            f: f.into(),
723            g: g.into(),
724        }
725    }
726    /// Convolution is commutative: f * g = g * f.
727    pub fn is_commutative(&self) -> bool {
728        true
729    }
730    /// Convolution theorem: ℱ(f * g) = ℱ(f) · ℱ(g).
731    pub fn convolution_theorem(&self) -> bool {
732        true
733    }
734    /// Young's inequality: ‖f * g‖_r ≤ ‖f‖_p ‖g‖_q  (1/r = 1/p + 1/q − 1).
735    pub fn young_inequality(&self) -> bool {
736        true
737    }
738}
739/// Calderón-Zygmund decomposition of a discrete signal at height α.
740///
741/// Given a non-negative signal f and α > 0, decomposes f = g + b where:
742/// - g is the "good" part (bounded by 2^n α a.e.)
743/// - b is the "bad" part supported on a union of dyadic intervals where the
744///   average of f exceeds α
745#[derive(Debug, Clone)]
746pub struct CalderonZygmundDecomp {
747    /// Original signal values.
748    pub signal: Vec<f64>,
749    /// Decomposition height α.
750    pub alpha: f64,
751}
752impl CalderonZygmundDecomp {
753    /// Create a new CZ decomposition.
754    pub fn new(signal: Vec<f64>, alpha: f64) -> Self {
755        assert!(alpha > 0.0, "alpha must be positive");
756        CalderonZygmundDecomp { signal, alpha }
757    }
758    /// Find the "bad" intervals: maximal dyadic intervals where the average > α.
759    ///
760    /// Uses a greedy partition into dyadic intervals [k·2ʲ, (k+1)·2ʲ).
761    pub fn bad_intervals(&self) -> Vec<(usize, usize)> {
762        let n = self.signal.len();
763        if n == 0 {
764            return vec![];
765        }
766        let mut bad = Vec::new();
767        let mut i = 0;
768        while i < n {
769            let mut len = 1;
770            let mut found = false;
771            while i + len <= n {
772                let avg: f64 = self.signal[i..i + len].iter().sum::<f64>() / len as f64;
773                if avg > self.alpha {
774                    bad.push((i, i + len - 1));
775                    found = true;
776                    break;
777                }
778                len += 1;
779            }
780            if !found {
781                i += 1;
782            } else {
783                i += len;
784            }
785        }
786        bad
787    }
788    /// Compute the "good" part g: set f to α on bad intervals, keep f elsewhere.
789    pub fn good_part(&self) -> Vec<f64> {
790        let bad = self.bad_intervals();
791        self.signal
792            .iter()
793            .enumerate()
794            .map(|(i, &fi)| {
795                if bad.iter().any(|&(lo, hi)| i >= lo && i <= hi) {
796                    self.alpha
797                } else {
798                    fi
799                }
800            })
801            .collect()
802    }
803    /// Compute the "bad" part b = f - g.
804    pub fn bad_part(&self) -> Vec<f64> {
805        let g = self.good_part();
806        self.signal
807            .iter()
808            .zip(g.iter())
809            .map(|(&f, &gv)| f - gv)
810            .collect()
811    }
812    /// Verify decomposition: f = g + b pointwise.
813    pub fn verify_decomposition(&self) -> bool {
814        let g = self.good_part();
815        let b = self.bad_part();
816        self.signal
817            .iter()
818            .zip(g.iter().zip(b.iter()))
819            .all(|(&f, (&gv, &bv))| (f - gv - bv).abs() < 1e-12)
820    }
821    /// Check that the good part satisfies ‖g‖_{L∞} ≤ 2α.
822    pub fn good_part_bounded(&self) -> bool {
823        let g = self.good_part();
824        g.iter().all(|&v| v.abs() <= 2.0 * self.alpha + 1e-12)
825    }
826}
827/// Littlewood-Paley theory with dyadic frequency decomposition.
828pub struct LittlewoodPaley {
829    /// Labels of the dyadic blocks Δⱼ.
830    pub dyadic_blocks: Vec<String>,
831}
832impl LittlewoodPaley {
833    /// Create a new LittlewoodPaley decomposition.
834    pub fn new(dyadic_blocks: Vec<String>) -> Self {
835        Self { dyadic_blocks }
836    }
837    /// Lᵖ equivalence: ‖f‖_p ~ ‖(Σⱼ |Δⱼf|²)^{1/2}‖_p for 1 < p < ∞.
838    pub fn lp_equivalence(&self) -> bool {
839        !self.dyadic_blocks.is_empty()
840    }
841    /// The Littlewood-Paley square function S(f) = (Σ|Δⱼf|²)^{1/2}.
842    pub fn square_function(&self) -> usize {
843        self.dyadic_blocks.len()
844    }
845}
846/// Wavelet transform with a given mother wavelet.
847pub struct WaveletTransform {
848    /// Name or formula of the mother wavelet ψ.
849    pub mother_wavelet: String,
850    /// Number of decomposition levels.
851    pub num_levels: usize,
852}
853impl WaveletTransform {
854    /// Create a new WaveletTransform.
855    pub fn new(mother_wavelet: impl Into<String>, num_levels: usize) -> Self {
856        Self {
857            mother_wavelet: mother_wavelet.into(),
858            num_levels,
859        }
860    }
861    /// Continuous wavelet transform: Wf(a,b) = ∫ f(t) ψ_{a,b}(t) dt.
862    pub fn continuous_wavelet(&self) -> bool {
863        true
864    }
865    /// Discrete wavelet transform (dyadic subsampling).
866    pub fn discrete_wavelet(&self) -> Vec<usize> {
867        (0..self.num_levels).collect()
868    }
869    /// Haar wavelet: the simplest piecewise-constant orthonormal wavelet.
870    pub fn haar_wavelet(&self) -> bool {
871        self.mother_wavelet.to_lowercase().contains("haar")
872    }
873}
874/// Weighted Lᵖ norm with Muckenhoupt Aₚ weight.
875pub struct WeightedNorm {
876    /// Description of the weight function w.
877    pub weight: String,
878    /// Whether w satisfies the Aₚ condition.
879    pub ap_condition: bool,
880}
881impl WeightedNorm {
882    /// Create a new WeightedNorm.
883    pub fn new(weight: impl Into<String>, ap_condition: bool) -> Self {
884        Self {
885            weight: weight.into(),
886            ap_condition,
887        }
888    }
889    /// Muckenhoupt Aₚ condition characterises weights for which M is bounded.
890    pub fn muckenhoupt_ap(&self) -> bool {
891        self.ap_condition
892    }
893    /// Reverse Hölder inequality: Aₚ weights satisfy a reverse Hölder estimate.
894    pub fn reverse_holder(&self) -> bool {
895        self.ap_condition
896    }
897}
898/// Hardy-Littlewood maximal function data.
899#[allow(dead_code)]
900#[derive(Debug, Clone)]
901pub struct MaximalFunctionData {
902    /// Dimension.
903    pub dimension: usize,
904    /// Input samples (as finite sequence approximation).
905    pub samples: Vec<f64>,
906}
907#[allow(dead_code)]
908impl MaximalFunctionData {
909    /// Creates maximal function data.
910    pub fn new(dimension: usize, samples: Vec<f64>) -> Self {
911        MaximalFunctionData { dimension, samples }
912    }
913    /// Approximate Hardy-Littlewood maximal function Mf(x) at index i.
914    /// Uses average over samples[max(0,i-r)..=min(n-1,i+r)] for r=1.
915    pub fn hl_maximal_at(&self, i: usize, r: usize) -> f64 {
916        if self.samples.is_empty() {
917            return 0.0;
918        }
919        let lo = i.saturating_sub(r);
920        let hi = (i + r).min(self.samples.len() - 1);
921        let window = &self.samples[lo..=hi];
922        window.iter().copied().fold(f64::NEG_INFINITY, f64::max)
923    }
924    /// Checks weak-type (1,1) bound: |{Mf > λ}| <= C/λ ||f||_1.
925    pub fn weak_type_bound_approx(&self, lambda: f64) -> f64 {
926        if lambda <= 0.0 {
927            return self.samples.len() as f64;
928        }
929        let l1_norm: f64 = self.samples.iter().map(|&x| x.abs()).sum();
930        l1_norm / lambda
931    }
932    /// L^p norm estimate for p > 1: ||Mf||_p <= C_p ||f||_p.
933    pub fn lp_norm_estimate(&self, p: f64) -> f64 {
934        if p <= 1.0 {
935            return f64::INFINITY;
936        }
937        let lp_norm: f64 = self
938            .samples
939            .iter()
940            .map(|&x| x.abs().powf(p))
941            .sum::<f64>()
942            .powf(1.0 / p);
943        let cp = p / (p - 1.0);
944        cp * lp_norm
945    }
946}