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