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