Skip to main content

sva_samples/collapse/
point.rs

1// Concern: evaluates a spectral sum's atoms at one instant, and the crop and lane rules every evaluator shares | Non-concern: a written form's tree | IO: (&SpectralSum, t, component) -> C64
2
3use sva_formula::closed_form::Unary;
4use sva_formula::spectral_sum::atom::{Singular, SpectralAtom};
5use sva_formula::{C64, Lane, SpectralSum};
6
7use crate::error::CollapseError;
8use crate::grid::Grid;
9
10/// Where a form is evaluated: a free instant, or a grid sample, whose edges the grid decides.
11#[derive(Clone, Copy, Debug, PartialEq)]
12pub enum At {
13    Free(f64),
14    Sample(Grid, i64),
15}
16
17impl At {
18    pub fn t(self) -> f64 {
19        match self {
20            At::Free(t) => t,
21            At::Sample(grid, n) => grid.instant(n),
22        }
23    }
24
25    pub(crate) fn sample(self) -> Option<(Grid, i64)> {
26        match self {
27            At::Free(_) => None,
28            At::Sample(grid, n) => Some((grid, n)),
29        }
30    }
31}
32
33type On = Option<(Grid, i64)>;
34
35/// The six atom factors in closed form. A delta has no ordinary value, and a principal
36/// value has none at its own pole.
37pub(crate) fn eval_atom_on(a: &SpectralAtom, t: f64, on: On) -> Result<C64, CollapseError> {
38    debug_assert!(
39        on.is_none_or(|(grid, n)| grid.instant(n) == t),
40        "a sample at its instant"
41    );
42    if let Singular::Delta { at, order } = a.sing {
43        return Err(CollapseError::SingularInCt {
44            at,
45            order: i32::from(order),
46        });
47    }
48    let held = match (a.ind, on) {
49        (Some(ind), Some((grid, n))) => match grid.inside(n, ind.l.value(), ind.r.value()) {
50            true => a.smooth_inside(t),
51            false => Some(C64::ZERO),
52        },
53        _ => a.smooth_at(t),
54    };
55    held.ok_or(CollapseError::SingularInCt {
56        at: a.pole.map_or(t, |p| p.at.re),
57        order: -1,
58    })
59}
60
61pub(crate) fn eval_lane_on(lane: &Lane, t: f64, on: On) -> Result<C64, CollapseError> {
62    let mut sum = C64::ZERO;
63    for a in &lane.atoms {
64        sum = sum + eval_atom_on(a, t, on)?;
65    }
66    with_modal(lane, sum, t, on)
67}
68
69pub(crate) fn eval_among(
70    lane: &Lane,
71    live: &[usize],
72    (grid, m): (Grid, i64),
73) -> Result<C64, CollapseError> {
74    let (t, on) = (grid.instant(m), Some((grid, m)));
75    let mut sum = C64::ZERO;
76    for &i in live {
77        sum = sum + eval_atom_on(&lane.atoms[i], t, on)?;
78    }
79    with_modal(lane, sum, t, on)
80}
81
82fn with_modal(lane: &Lane, mut sum: C64, t: f64, on: On) -> Result<C64, CollapseError> {
83    for bank in &lane.modal {
84        for a in sva_formula::modal::atoms(bank, sva_formula::Origin::UNKNOWN) {
85            sum = sum + eval_atom_on(&a, t, on)?;
86        }
87    }
88    Ok(sum)
89}
90
91pub(crate) fn eval_spectral_sum(n: &SpectralSum, c: usize, t: f64) -> Result<C64, CollapseError> {
92    eval_spectral_sum_on(n, c, At::Free(t))
93}
94
95pub(crate) fn eval_spectral_sum_on(
96    n: &SpectralSum,
97    c: usize,
98    at: At,
99) -> Result<C64, CollapseError> {
100    eval_lane_on(&n.lanes[c.min(n.lanes.len() - 1)], at.t(), at.sample())
101}
102
103/// FORMAT 15.6's window: one over the plateau, a raised cosine over each shoulder, zero out.
104pub fn crop_gain(t: f64, l: f64, r: f64, rise: f64, fall: f64) -> f64 {
105    if t < l || t >= r {
106        return 0.0;
107    }
108    shoulders(t, l, r, rise, fall)
109}
110
111/// The window's gain at an instant already known to lie inside it.
112pub fn shoulders(t: f64, l: f64, r: f64, rise: f64, fall: f64) -> f64 {
113    let opening = shoulder(t - l, rise);
114    let closing = shoulder(r - t, fall);
115    opening.min(closing)
116}
117
118fn shoulder(into: f64, span: f64) -> f64 {
119    if span <= 0.0 || into >= span {
120        return 1.0;
121    }
122    0.5 - 0.5 * (std::f64::consts::PI * into / span).cos()
123}
124
125/// Which operand of a `join` holds one component of the joined value, and which of its own
126/// components that is.
127pub fn lane_of(widths: &[usize], component: usize) -> Option<(usize, usize)> {
128    let mut left = component;
129    for (at, width) in widths.iter().enumerate() {
130        if left < *width {
131            return Some((at, left));
132        }
133        left -= width;
134    }
135    None
136}
137
138pub fn unary(op: Unary, x: C64) -> C64 {
139    match op {
140        Unary::Exp => x.exp(),
141        Unary::Sin => C64::new(x.re.sin() * x.im.cosh(), x.re.cos() * x.im.sinh()),
142        Unary::Cos => C64::new(x.re.cos() * x.im.cosh(), -x.re.sin() * x.im.sinh()),
143        Unary::Tanh => C64::real(x.re.tanh()),
144        Unary::Sat => C64::real(x.re.clamp(-1.0, 1.0)),
145        Unary::Abs => C64::real(x.abs()),
146        Unary::Log => C64::real(x.re.ln()),
147        Unary::Sqrt => C64::real(x.re.sqrt()),
148        Unary::Step => C64::real(sva_formula::affine::step(x.re)),
149    }
150}