Skip to main content

sva_samples/
collapse.rs

1// Concern: turns a closed form into samples by the rule its shape names | Non-concern: its spectral sum (sva-formula), reading the buffer (measure/) | IO: (&ClosedForm, rate) -> Buffer, Label
2
3mod atoms;
4mod blocks;
5mod inverse;
6mod lines;
7pub mod plan;
8mod point;
9mod reading;
10mod span;
11mod sum;
12mod tail;
13mod truncate;
14
15use sva_formula::{Body, C64, ClosedForm, SpectralSum, Var, normalize_closed_form};
16
17use crate::buffer::Buffer;
18use crate::error::CollapseError;
19use crate::label::{Detail, Label, Rule, Source};
20use crate::profile::Profile;
21
22pub use blocks::Rows;
23pub use plan::transform_flops;
24
25use plan::Plan;
26pub use point::{Refs, crop_gain, lane_of, unary};
27pub use truncate::{Audible, spectral_sum as truncate_spectral_sum, written as truncate_written};
28
29/// One closed form's value at one instant, for a caller that already holds the spectral sum.
30pub fn eval_spectral_sum_at(
31    sum: &SpectralSum,
32    component: usize,
33    t: f64,
34) -> Result<C64, CollapseError> {
35    point::eval_spectral_sum(sum, component, t)
36}
37
38/// One written closed form's value at one instant, every `Body::Node` in it answered by the
39/// caller holding the graph.
40pub fn eval_written_at(
41    body: &Body,
42    component: usize,
43    t: f64,
44    refs: &dyn Refs,
45) -> Result<C64, CollapseError> {
46    point::eval_body(body, component, t, refs)
47}
48
49#[derive(Clone, Copy, Debug, PartialEq)]
50pub struct Horizon {
51    pub start_secs: f64,
52    pub end_secs: f64,
53}
54
55impl Horizon {
56    pub fn secs(start_secs: f64, end_secs: f64) -> Horizon {
57        Horizon {
58            start_secs,
59            end_secs,
60        }
61    }
62
63    pub fn span(&self) -> f64 {
64        self.end_secs - self.start_secs
65    }
66
67    /// An observation that named no window has no horizon at all, and no collapse can run
68    /// against one.
69    pub fn len(&self, rate: u32) -> Result<usize, CollapseError> {
70        let span = self.span();
71        if !span.is_finite() || span <= 0.0 {
72            return Err(CollapseError::NoHorizon);
73        }
74        Ok(((span * f64::from(rate)).round() as usize).max(1))
75    }
76}
77
78/// The oversampling a point-sampled collapse's own alias score is measured against.
79pub const ALIAS_OVERSAMPLE: usize = 4;
80
81/// The reference is the same closed form at `ALIAS_OVERSAMPLE` times the rate, so only a reading
82/// that reads the score pays for one.
83#[derive(Clone, Copy, Debug, PartialEq, Eq)]
84pub enum AliasScore {
85    Asked,
86    NotAsked,
87}
88
89/// Every row of FORMAT 9.1's table, R0 to R6, by the shape the form names.
90pub fn render(
91    form: &ClosedForm,
92    rate: u32,
93    horizon: Horizon,
94    profile: &Profile,
95    score: AliasScore,
96) -> Result<(Buffer, Label), CollapseError> {
97    match normalize_closed_form(form) {
98        Ok(sum) => of_spectral_sum_or_point(&sum, Some(form), rate, horizon, profile, score),
99        Err(_) if form.var == Var::T => render_written(form, rate, horizon, profile, score),
100        Err(left) => Err(CollapseError::LeftAlgebra(left.reason.clause())),
101    }
102}
103
104/// FORMAT 9.1's row 4, or one row per addend where a sum's addends name different ones.
105pub fn render_written(
106    form: &ClosedForm,
107    rate: u32,
108    horizon: Horizon,
109    profile: &Profile,
110    score: AliasScore,
111) -> Result<(Buffer, Label), CollapseError> {
112    let len = horizon.len(rate)?;
113    run(
114        plan::of_written(form, rate, horizon, profile, len)?,
115        rate,
116        horizon,
117        profile,
118        len,
119        score,
120    )
121}
122
123/// FORMAT 9.1 row 4 over one written closed form: point sampling, measured against its own alias.
124fn point_row(
125    written: &ClosedForm,
126    width: usize,
127    rate: u32,
128    horizon: Horizon,
129    profile: &Profile,
130    len: usize,
131    score: AliasScore,
132) -> Result<(Buffer, Label), CollapseError> {
133    let planes = (0..width)
134        .map(|c| reading::sampled_body(written, c, rate, horizon, len, 1.0))
135        .collect::<Result<Vec<_>, _>>()?;
136    let alias_db = match score {
137        AliasScore::Asked => Some(formula_alias(written, rate, horizon, len, &planes)?),
138        AliasScore::NotAsked => None,
139    };
140    Ok(measured(
141        planes,
142        rate,
143        horizon,
144        profile,
145        Detail::Point {
146            rule: Rule::PointSampled,
147            alias_db,
148        },
149    ))
150}
151
152/// The six rows over a spectral sum, falling to the point-sampled row over the written closed
153/// form where no atom sum bounds the series.
154pub fn of_spectral_sum_or_point(
155    sum: &SpectralSum,
156    written: Option<&ClosedForm>,
157    rate: u32,
158    horizon: Horizon,
159    profile: &Profile,
160    score: AliasScore,
161) -> Result<(Buffer, Label), CollapseError> {
162    match of_spectral_sum(sum, rate, horizon, profile, score) {
163        Err(e) if reaches_no_atom(&e) => match written.filter(|t| t.var == Var::T) {
164            Some(form) => render_written(form, rate, horizon, profile, score),
165            None => Err(e),
166        },
167        other => other,
168    }
169}
170
171/// A form no atom sum reaches still has a value at every instant. A nesting past the bound
172/// is not one of those: it has a form, priced, and too many terms to expand.
173pub(crate) fn reaches_no_atom(e: &CollapseError) -> bool {
174    matches!(e, CollapseError::NotEvaluable(_))
175}
176
177/// The same six rows, for a caller whose refs are already composed in.
178pub fn of_spectral_sum(
179    sum: &SpectralSum,
180    rate: u32,
181    horizon: Horizon,
182    profile: &Profile,
183    score: AliasScore,
184) -> Result<(Buffer, Label), CollapseError> {
185    let len = horizon.len(rate)?;
186    of_sum(sum, rate, horizon, profile, len, score)
187}
188
189/// The row `plan` named, run: one decision, and this is the half that makes samples.
190fn of_sum(
191    sum: &SpectralSum,
192    rate: u32,
193    horizon: Horizon,
194    profile: &Profile,
195    len: usize,
196    score: AliasScore,
197) -> Result<(Buffer, Label), CollapseError> {
198    run(
199        plan::of(sum, rate, horizon, profile, len)?,
200        rate,
201        horizon,
202        profile,
203        len,
204        score,
205    )
206}
207
208fn run(
209    plan: Plan,
210    rate: u32,
211    horizon: Horizon,
212    profile: &Profile,
213    len: usize,
214    score: AliasScore,
215) -> Result<(Buffer, Label), CollapseError> {
216    match plan {
217        Plan::Spectrum(sum) => spectrum_row(&sum, rate, horizon, profile, len),
218        Plan::Lines(found) => Ok(line_row(&found, rate, horizon, profile, len)),
219        Plan::Sampled(held) => sampled_row(&held, rate, horizon, profile, len, score),
220        Plan::Point { written, width, .. } => {
221            point_row(&written, width, rate, horizon, profile, len, score)
222        }
223        Plan::Added(parts) => sum::added(parts, rate, horizon, profile, len, score),
224    }
225}
226
227fn line_row(
228    found: &plan::LinePlan,
229    rate: u32,
230    horizon: Horizon,
231    profile: &Profile,
232    len: usize,
233) -> (Buffer, Label) {
234    let planes: Vec<Vec<f64>> = found
235        .placed
236        .iter()
237        .zip(&found.summed)
238        .map(|(placed, summed)| {
239            let mut plane = lines::transformed(placed, horizon.start_secs, found.bins, rate, len);
240            lines::add_direct(&mut plane, summed, horizon.start_secs, rate);
241            plane
242        })
243        .collect();
244    let (list, more) = lines::dropped_list(&found.dropped);
245    let (placed, summed) = (
246        lines::distinct(&found.placed),
247        lines::distinct(&found.summed),
248    );
249    let detail = Detail::Lines {
250        rule: found.rule(),
251        placed,
252        summed,
253        dropped: list,
254        dropped_more: more,
255        terms: Some(placed + summed),
256        tail_db: found.tail_db,
257    };
258    exact(planes, rate, horizon, profile, detail)
259}
260
261fn sampled_row(
262    held: &plan::Sampled,
263    rate: u32,
264    horizon: Horizon,
265    profile: &Profile,
266    len: usize,
267    score: AliasScore,
268) -> Result<(Buffer, Label), CollapseError> {
269    let (sum, rule) = (&held.sum, held.rule);
270    let planes = reading::sampled_spectral_sum(sum, &held.lanes, rate, horizon, len)?;
271    match rule {
272        Rule::BandLimited => Ok(exact(
273            planes,
274            rate,
275            horizon,
276            profile,
277            Detail::Continuous { rule },
278        )),
279        Rule::CroppedPair => Ok(measured(
280            planes,
281            rate,
282            horizon,
283            profile,
284            Detail::Cropped {
285                rule,
286                tail_db: tail::tail_db(sum, profile.ceiling(rate)),
287            },
288        )),
289        _ => {
290            let alias_db = match score {
291                AliasScore::Asked => Some(spectral_sum_alias(sum, rate, horizon, len, &planes)?),
292                AliasScore::NotAsked => None,
293            };
294            Ok(measured(
295                planes,
296                rate,
297                horizon,
298                profile,
299                Detail::Point { rule, alias_db },
300            ))
301        }
302    }
303}
304
305fn spectrum_row(
306    sum: &SpectralSum,
307    rate: u32,
308    horizon: Horizon,
309    profile: &Profile,
310    len: usize,
311) -> Result<(Buffer, Label), CollapseError> {
312    let mut planes = Vec::with_capacity(sum.lanes.len());
313    for c in 0..sum.lanes.len() {
314        planes.push(inverse::collapse_lane(
315            sum,
316            c,
317            horizon.start_secs,
318            horizon.span(),
319            rate,
320            len,
321        )?);
322    }
323    let wrap_db = inverse::wrap_db(sum, horizon.span(), rate)?;
324    Ok(measured(
325        planes,
326        rate,
327        horizon,
328        profile,
329        Detail::Spectrum {
330            rule: Rule::InverseSpectrum,
331            wrap_db,
332        },
333    ))
334}
335
336/// What the grid could not hold is the residual `measure/alias.rs` already knows how to score.
337fn formula_alias(
338    form: &ClosedForm,
339    rate: u32,
340    horizon: Horizon,
341    len: usize,
342    planes: &[Vec<f64>],
343) -> Result<f64, CollapseError> {
344    let mut scored = Vec::with_capacity(planes.len());
345    for (c, base) in planes.iter().enumerate() {
346        let high = reading::sampled_body(
347            form,
348            c,
349            rate,
350            horizon,
351            len * ALIAS_OVERSAMPLE,
352            ALIAS_OVERSAMPLE as f64,
353        )?;
354        scored.push(one_component(base, &high, rate, horizon));
355    }
356    Ok(worst_of(scored))
357}
358
359fn spectral_sum_alias(
360    sum: &SpectralSum,
361    rate: u32,
362    horizon: Horizon,
363    len: usize,
364    planes: &[Vec<f64>],
365) -> Result<f64, CollapseError> {
366    let step = 1.0 / (f64::from(rate) * ALIAS_OVERSAMPLE as f64);
367    let mut scored = Vec::with_capacity(planes.len());
368    for (c, base) in planes.iter().enumerate() {
369        let high: Vec<f64> = (0..len * ALIAS_OVERSAMPLE)
370            .map(|i| {
371                point::eval_spectral_sum(sum, c, horizon.start_secs + i as f64 * step).map(|v| v.re)
372            })
373            .collect::<Result<_, _>>()?;
374        scored.push(one_component(base, &high, rate, horizon));
375    }
376    Ok(worst_of(scored))
377}
378
379fn one_component(
380    base: &[f64],
381    high: &[f64],
382    rate: u32,
383    horizon: Horizon,
384) -> crate::measure::alias::Alias {
385    crate::measure::alias::measure_alias(
386        base,
387        high,
388        ALIAS_OVERSAMPLE,
389        f64::from(rate),
390        horizon.start_secs,
391    )
392}
393
394/// The label carries one number for the node, and `worst` says which component it comes from.
395fn worst_of(scored: Vec<crate::measure::alias::Alias>) -> f64 {
396    crate::measure::alias::worst(scored).map_or(f64::NEG_INFINITY, |held| held.asr_db)
397}
398
399fn exact(
400    planes: Vec<Vec<f64>>,
401    rate: u32,
402    horizon: Horizon,
403    profile: &Profile,
404    detail: Detail,
405) -> (Buffer, Label) {
406    labelled(Source::Exact, planes, rate, horizon, profile, detail)
407}
408
409fn measured(
410    planes: Vec<Vec<f64>>,
411    rate: u32,
412    horizon: Horizon,
413    profile: &Profile,
414    detail: Detail,
415) -> (Buffer, Label) {
416    labelled(Source::Measured, planes, rate, horizon, profile, detail)
417}
418
419fn labelled(
420    source: Source,
421    planes: Vec<Vec<f64>>,
422    rate: u32,
423    horizon: Horizon,
424    profile: &Profile,
425    detail: Detail,
426) -> (Buffer, Label) {
427    let mut buffer = Buffer::of_planes(rate, planes);
428    buffer.origin_secs = horizon.start_secs;
429    (buffer, Label::new(source, profile.name, rate, detail))
430}