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