1use 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#[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
35pub(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
103pub 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
111pub 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
125pub 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}