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 active;
4mod atoms;
5mod blocks;
6mod inverse;
7mod lines;
8pub mod plan;
9mod point;
10mod reading;
11pub mod run;
12mod span;
13mod sum;
14mod tail;
15mod truncate;
16
17use sva_formula::{Body, C64, ClosedForm, SpectralSum, Var, normalize_closed_form};
18
19use crate::buffer::Buffer;
20use crate::error::CollapseError;
21use crate::label::{Detail, Label, Rule, Source};
22use crate::profile::Profile;
23
24pub use blocks::Rows;
25pub use plan::transform_flops;
26
27use plan::Plan;
28pub(crate) use point::NoRefs;
29pub use point::{Refs, crop_gain, lane_of, shoulders, unary};
30pub use truncate::{Audible, spectral_sum as truncate_spectral_sum, written as truncate_written};
31
32/// One closed form's value at one instant, for a caller that already holds the spectral sum.
33pub fn eval_spectral_sum_at(
34    sum: &SpectralSum,
35    component: usize,
36    t: f64,
37) -> Result<C64, CollapseError> {
38    point::eval_spectral_sum(sum, component, t)
39}
40
41/// One written closed form's value at one instant, every `Body::Node` in it answered by the
42/// caller holding the graph.
43pub fn eval_written_at(
44    body: &Body,
45    component: usize,
46    t: f64,
47    refs: &dyn Refs,
48) -> Result<C64, CollapseError> {
49    point::eval_body(body, component, t, refs)
50}
51
52/// Samples `[start, end)` of the one grid every node is read on, whose sample 0 is t = 0.
53#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
54pub struct Extent {
55    pub start: i64,
56    pub end: i64,
57}
58
59impl Extent {
60    /// Every sample of the grid: `i64::MIN` and `i64::MAX` stand for no edge at all.
61    pub const EVERYWHERE: Extent = Extent {
62        start: i64::MIN,
63        end: i64::MAX,
64    };
65
66    pub const NOWHERE: Extent = Extent { start: 0, end: 0 };
67
68    pub fn new(start: i64, end: i64) -> Extent {
69        assert!(start <= end, "an extent [{start}, {end}) runs backwards");
70        Extent { start, end }
71    }
72
73    pub fn from(start: i64) -> Extent {
74        Extent::new(start, i64::MAX)
75    }
76
77    pub fn is_bounded(&self) -> bool {
78        self.start != i64::MIN && self.end != i64::MAX
79    }
80
81    pub fn contains(&self, n: i64) -> bool {
82        self.start <= n && n < self.end
83    }
84
85    pub fn intersect(self, other: Extent) -> Extent {
86        let start = self.start.max(other.start);
87        let end = self.end.min(other.end);
88        match start < end {
89            true => Extent { start, end },
90            false => Extent::NOWHERE,
91        }
92    }
93
94    pub fn hull(self, other: Extent) -> Extent {
95        match (self.is_empty(), other.is_empty()) {
96            (true, _) => other,
97            (_, true) => self,
98            _ => Extent {
99                start: self.start.min(other.start),
100                end: self.end.max(other.end),
101            },
102        }
103    }
104
105    /// Every sample moved `by` later; an unbounded edge stays unbounded.
106    pub fn shifted(self, by: i64) -> Extent {
107        if self.is_empty() {
108            return self;
109        }
110        let edge = |n: i64| match n {
111            i64::MIN | i64::MAX => n,
112            n => n.saturating_add(by).clamp(i64::MIN + 1, i64::MAX - 1),
113        };
114        Extent {
115            start: edge(self.start),
116            end: edge(self.end),
117        }
118    }
119
120    pub fn secs(rate: u32, start_secs: f64, end_secs: f64) -> Extent {
121        let at = |secs: f64| (secs * f64::from(rate)).round() as i64;
122        Extent::new(at(start_secs), at(end_secs))
123    }
124
125    pub fn len(&self) -> usize {
126        debug_assert!(self.is_bounded(), "an unbounded extent has no length");
127        (self.end - self.start) as usize
128    }
129
130    pub fn is_empty(&self) -> bool {
131        self.end == self.start
132    }
133
134    pub fn start_secs(&self, rate: u32) -> f64 {
135        self.start as f64 / f64::from(rate)
136    }
137
138    pub fn span_secs(&self, rate: u32) -> f64 {
139        self.len() as f64 / f64::from(rate)
140    }
141
142    /// Sample `i` read `scale` times finer: the grid's own index times the step, so every
143    /// span of the grid reads one instant alike.
144    pub fn instant(&self, i: usize, scale: usize, step: f64) -> f64 {
145        (self.start * scale as i64 + i as i64) as f64 * step
146    }
147}
148
149/// The oversampling a point-sampled collapse's own alias score is measured against.
150pub const ALIAS_OVERSAMPLE: usize = 4;
151
152/// The reference is the same closed form at `ALIAS_OVERSAMPLE` times the rate, so only a reading
153/// that reads the score pays for one.
154#[derive(Clone, Copy, Debug, PartialEq, Eq)]
155pub enum AliasScore {
156    Asked,
157    NotAsked,
158}
159
160/// Every row of FORMAT 9.1's table, R0 to R6, by the shape the form names.
161pub fn render(
162    form: &ClosedForm,
163    rate: u32,
164    extent: Extent,
165    profile: &Profile,
166    score: AliasScore,
167) -> Result<(Buffer, Label), CollapseError> {
168    match normalize_closed_form(form) {
169        Ok(sum) => of_spectral_sum_or_point(&sum, Some(form), rate, extent, profile, score),
170        Err(_) if form.var == Var::T => render_written(form, rate, extent, profile, score),
171        Err(left) => Err(CollapseError::LeftAlgebra(left.reason.clause())),
172    }
173}
174
175/// FORMAT 9.1's row 4, or one row per addend where a sum's addends name different ones.
176pub fn render_written(
177    form: &ClosedForm,
178    rate: u32,
179    extent: Extent,
180    profile: &Profile,
181    score: AliasScore,
182) -> Result<(Buffer, Label), CollapseError> {
183    let len = extent.len();
184    run(
185        plan::of_written(form, rate, extent, profile, len)?,
186        rate,
187        extent,
188        profile,
189        len,
190        score,
191    )
192}
193
194/// FORMAT 9.1 row 4 over one written closed form: point sampling, measured against its own alias.
195fn point_row(
196    written: &ClosedForm,
197    width: usize,
198    rate: u32,
199    extent: Extent,
200    profile: &Profile,
201    len: usize,
202    score: AliasScore,
203) -> Result<(Buffer, Label), CollapseError> {
204    let planes = (0..width)
205        .map(|c| reading::sampled_body(written, c, rate, extent, len, 1))
206        .collect::<Result<Vec<_>, _>>()?;
207    let alias_db = match score {
208        AliasScore::Asked => Some(formula_alias(written, rate, extent, len, &planes)?),
209        AliasScore::NotAsked => None,
210    };
211    Ok(measured(
212        planes,
213        rate,
214        extent,
215        profile,
216        Detail::Point {
217            rule: Rule::PointSampled,
218            alias_db,
219        },
220    ))
221}
222
223/// The six rows over a spectral sum, falling to the point-sampled row over the written closed
224/// form where no atom sum bounds the series.
225pub fn of_spectral_sum_or_point(
226    sum: &SpectralSum,
227    written: Option<&ClosedForm>,
228    rate: u32,
229    extent: Extent,
230    profile: &Profile,
231    score: AliasScore,
232) -> Result<(Buffer, Label), CollapseError> {
233    match of_spectral_sum(sum, rate, extent, profile, score) {
234        Err(e) if reaches_no_atom(&e) => match written.filter(|t| t.var == Var::T) {
235            Some(form) => render_written(form, rate, extent, profile, score),
236            None => Err(e),
237        },
238        other => other,
239    }
240}
241
242/// A collapse decided for one extent and not yet run: each row `of_spectral_sum_or_point` and
243/// `render` would take, run over that extent or over its `nonzero` part.
244pub struct Planned(Plan);
245
246pub fn planned(
247    sum: Option<&SpectralSum>,
248    written: Option<&ClosedForm>,
249    rate: u32,
250    extent: Extent,
251    profile: &Profile,
252) -> Result<Planned, CollapseError> {
253    let len = extent.len();
254    let normalized;
255    let sum = match (sum, written) {
256        (Some(sum), _) => sum,
257        (None, Some(form)) => match normalize_closed_form(form) {
258            Ok(held) => {
259                normalized = held;
260                &normalized
261            }
262            Err(_) if form.var == Var::T => {
263                return plan::of_written(form, rate, extent, profile, len).map(Planned);
264            }
265            Err(left) => return Err(CollapseError::LeftAlgebra(left.reason.clause())),
266        },
267        (None, None) => return Err(CollapseError::NotEvaluable("a form with neither view")),
268    };
269    match plan::of(sum, rate, extent, profile, len) {
270        Err(e) if reaches_no_atom(&e) => match written.filter(|t| t.var == Var::T) {
271            Some(form) => plan::of_written(form, rate, extent, profile, len).map(Planned),
272            None => Err(e),
273        },
274        other => other.map(Planned),
275    }
276}
277
278impl Planned {
279    pub fn nonzero(&self, rate: u32, extent: Extent) -> Extent {
280        self.0.nonzero(rate, extent)
281    }
282
283    pub fn route(&self) -> Vec<u64> {
284        self.0.route()
285    }
286
287    pub fn run(
288        self,
289        rate: u32,
290        extent: Extent,
291        profile: &Profile,
292        score: AliasScore,
293    ) -> Result<(Buffer, Label), CollapseError> {
294        run(self.0, rate, extent, profile, extent.len(), score)
295    }
296}
297
298/// A form no atom sum reaches still has a value at every instant. A nesting past the bound
299/// is not one of those: it has a form, priced, and too many terms to expand.
300pub(crate) fn reaches_no_atom(e: &CollapseError) -> bool {
301    matches!(e, CollapseError::NotEvaluable(_))
302}
303
304/// The same six rows, for a caller whose refs are already composed in.
305pub fn of_spectral_sum(
306    sum: &SpectralSum,
307    rate: u32,
308    extent: Extent,
309    profile: &Profile,
310    score: AliasScore,
311) -> Result<(Buffer, Label), CollapseError> {
312    let len = extent.len();
313    of_sum(sum, rate, extent, profile, len, score)
314}
315
316/// The row `plan` named, run: one decision, and this is the half that makes samples.
317fn of_sum(
318    sum: &SpectralSum,
319    rate: u32,
320    extent: Extent,
321    profile: &Profile,
322    len: usize,
323    score: AliasScore,
324) -> Result<(Buffer, Label), CollapseError> {
325    run(
326        plan::of(sum, rate, extent, profile, len)?,
327        rate,
328        extent,
329        profile,
330        len,
331        score,
332    )
333}
334
335fn run(
336    plan: Plan,
337    rate: u32,
338    extent: Extent,
339    profile: &Profile,
340    len: usize,
341    score: AliasScore,
342) -> Result<(Buffer, Label), CollapseError> {
343    match plan {
344        Plan::Spectrum(sum) => spectrum_row(&sum, rate, extent, profile, len),
345        Plan::Lines(found) => Ok(line_row(&found, rate, extent, profile, len)),
346        Plan::Sampled(held) => sampled_row(&held, rate, extent, profile, len, score),
347        Plan::Point { written, width, .. } => {
348            point_row(&written, width, rate, extent, profile, len, score)
349        }
350        Plan::Added(parts) => sum::added(parts, rate, extent, profile, len, score),
351    }
352}
353
354fn line_row(
355    found: &plan::LinePlan,
356    rate: u32,
357    extent: Extent,
358    profile: &Profile,
359    len: usize,
360) -> (Buffer, Label) {
361    let planes: Vec<Vec<f64>> = found
362        .placed
363        .iter()
364        .zip(&found.summed)
365        .map(|(placed, summed)| {
366            let mut plane = lines::transformed(placed, extent, found.bins, rate, len);
367            lines::add_direct(&mut plane, summed, extent, rate);
368            plane
369        })
370        .collect();
371    let (list, more) = lines::dropped_list(&found.dropped);
372    let (placed, summed) = (
373        lines::distinct(&found.placed),
374        lines::distinct(&found.summed),
375    );
376    let detail = Detail::Lines {
377        rule: found.rule(),
378        placed,
379        summed,
380        dropped: list,
381        dropped_more: more,
382        terms: Some(placed + summed),
383        tail_db: found.tail_db,
384    };
385    exact(planes, rate, extent, profile, detail)
386}
387
388fn sampled_row(
389    held: &plan::Sampled,
390    rate: u32,
391    extent: Extent,
392    profile: &Profile,
393    len: usize,
394    score: AliasScore,
395) -> Result<(Buffer, Label), CollapseError> {
396    let (sum, rule) = (&held.sum, held.rule);
397    let planes = reading::sampled_spectral_sum(sum, &held.lanes, rate, extent, len)?;
398    match rule {
399        Rule::BandLimited => Ok(exact(
400            planes,
401            rate,
402            extent,
403            profile,
404            Detail::Continuous { rule },
405        )),
406        Rule::CroppedPair => Ok(measured(
407            planes,
408            rate,
409            extent,
410            profile,
411            Detail::Cropped {
412                rule,
413                tail_db: tail::tail_db(sum, profile.ceiling(rate)),
414            },
415        )),
416        _ => {
417            let alias_db = match score {
418                AliasScore::Asked => Some(spectral_sum_alias(sum, rate, extent, len, &planes)?),
419                AliasScore::NotAsked => None,
420            };
421            Ok(measured(
422                planes,
423                rate,
424                extent,
425                profile,
426                Detail::Point { rule, alias_db },
427            ))
428        }
429    }
430}
431
432fn spectrum_row(
433    sum: &SpectralSum,
434    rate: u32,
435    extent: Extent,
436    profile: &Profile,
437    len: usize,
438) -> Result<(Buffer, Label), CollapseError> {
439    let mut planes = Vec::with_capacity(sum.lanes.len());
440    for c in 0..sum.lanes.len() {
441        planes.push(inverse::collapse_lane(
442            sum,
443            c,
444            extent.start_secs(rate),
445            extent.span_secs(rate),
446            rate,
447            len,
448        )?);
449    }
450    let wrap_db = inverse::wrap_db(sum, extent.span_secs(rate), rate)?;
451    Ok(measured(
452        planes,
453        rate,
454        extent,
455        profile,
456        Detail::Spectrum {
457            rule: Rule::InverseSpectrum,
458            wrap_db,
459        },
460    ))
461}
462
463/// What the grid could not hold is the residual `measure/alias.rs` already knows how to score.
464fn formula_alias(
465    form: &ClosedForm,
466    rate: u32,
467    extent: Extent,
468    len: usize,
469    planes: &[Vec<f64>],
470) -> Result<f64, CollapseError> {
471    let mut scored = Vec::with_capacity(planes.len());
472    for (c, base) in planes.iter().enumerate() {
473        let high = reading::sampled_body(
474            form,
475            c,
476            rate,
477            extent,
478            len * ALIAS_OVERSAMPLE,
479            ALIAS_OVERSAMPLE,
480        )?;
481        scored.push(one_component(base, &high, rate, extent));
482    }
483    Ok(worst_of(scored))
484}
485
486fn spectral_sum_alias(
487    sum: &SpectralSum,
488    rate: u32,
489    extent: Extent,
490    len: usize,
491    planes: &[Vec<f64>],
492) -> Result<f64, CollapseError> {
493    let step = 1.0 / (f64::from(rate) * ALIAS_OVERSAMPLE as f64);
494    let mut scored = Vec::with_capacity(planes.len());
495    for (c, base) in planes.iter().enumerate() {
496        let high: Vec<f64> = (0..len * ALIAS_OVERSAMPLE)
497            .map(|i| {
498                point::eval_spectral_sum(sum, c, extent.instant(i, ALIAS_OVERSAMPLE, step))
499                    .map(|v| v.re)
500            })
501            .collect::<Result<_, _>>()?;
502        scored.push(one_component(base, &high, rate, extent));
503    }
504    Ok(worst_of(scored))
505}
506
507fn one_component(
508    base: &[f64],
509    high: &[f64],
510    rate: u32,
511    extent: Extent,
512) -> crate::measure::alias::Alias {
513    crate::measure::alias::measure_alias(
514        base,
515        high,
516        ALIAS_OVERSAMPLE,
517        f64::from(rate),
518        extent.start_secs(rate),
519    )
520}
521
522/// The label carries one number for the node, and `worst` says which component it comes from.
523fn worst_of(scored: Vec<crate::measure::alias::Alias>) -> f64 {
524    crate::measure::alias::worst(scored).map_or(f64::NEG_INFINITY, |held| held.asr_db)
525}
526
527fn exact(
528    planes: Vec<Vec<f64>>,
529    rate: u32,
530    extent: Extent,
531    profile: &Profile,
532    detail: Detail,
533) -> (Buffer, Label) {
534    labelled(Source::Exact, planes, rate, extent, profile, detail)
535}
536
537fn measured(
538    planes: Vec<Vec<f64>>,
539    rate: u32,
540    extent: Extent,
541    profile: &Profile,
542    detail: Detail,
543) -> (Buffer, Label) {
544    labelled(Source::Measured, planes, rate, extent, profile, detail)
545}
546
547fn labelled(
548    source: Source,
549    planes: Vec<Vec<f64>>,
550    rate: u32,
551    extent: Extent,
552    profile: &Profile,
553    detail: Detail,
554) -> (Buffer, Label) {
555    let mut buffer = Buffer::of_planes(rate, planes);
556    buffer.start = extent.start;
557    (buffer, Label::new(source, profile.name, rate, detail))
558}