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