Skip to main content

sva_samples/collapse/
blocks.rs

1// Concern: which row a closed form takes, read on any span of its grid or at any instant | Non-concern: a form's arithmetic | IO: (form, grid) -> Rows; (span, t) -> samples
2
3use sva_formula::spectral_sum::atom::SpectralAtom;
4use sva_formula::{ClosedForm, Lane, Opaque, Reads, SpectralSum, Var, normalize_closed_form};
5
6use super::active::{self, SampleInterval};
7use super::column::{self, Program};
8use super::lines::{self, Direct};
9use super::truncate::{self, Audible};
10use super::{addends, atoms, point, tail};
11use crate::error::CollapseError;
12use crate::grid::{Grid, Round};
13use crate::label::{Detail, Label, Rule, Source};
14use crate::profile::Profile;
15
16/// Rows chosen from the form, the grid and the profile alone: each sample is its own
17/// instant's value, so any span reads what one whole span reads there.
18pub struct Rows {
19    row: Row,
20    grid: Grid,
21    width: usize,
22    label: (Source, Detail),
23}
24
25enum Row {
26    /// Every kept line summed at each instant.
27    Lines(Vec<Option<Direct>>),
28    /// Each lane's lines summed as runs under each common factor, read once an instant.
29    Grouped(Vec<Vec<Group>>),
30    Sweep {
31        sum: Box<SpectralSum>,
32        spans: Vec<Option<Vec<(i64, i64)>>>,
33        intervals: Vec<Vec<SampleInterval>>,
34    },
35    /// A written sum's addends each read only inside its own interval. Each component's
36    /// program holds the whole form first, then each addend where it is a sum.
37    Point {
38        written: Box<ClosedForm>,
39        width: usize,
40        intervals: Vec<SampleInterval>,
41        programs: Vec<Program>,
42    },
43    /// Each addend's row, its width, and the interval outside which it writes +0.
44    Added(Vec<(Row, usize, SampleInterval)>),
45}
46
47impl std::fmt::Debug for Rows {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        f.debug_struct("Rows")
50            .field("grid", &self.grid)
51            .field("width", &self.width)
52            .field("label", &self.label)
53            .finish_non_exhaustive()
54    }
55}
56
57impl Rows {
58    pub fn of(form: &ClosedForm, grid: Grid, profile: &Profile) -> Result<Rows, CollapseError> {
59        match normalize_closed_form(form) {
60            Ok(sum) => Rows::of_spectral_sum_or_point(&sum, Some(form), (grid, profile), &Opaque),
61            Err(_) if form.var == Var::T => Ok(Rows::held(of_written(form, grid, profile)?, grid)),
62            Err(left) => Err(CollapseError::LeftAlgebra(left.reason.clause())),
63        }
64    }
65
66    /// Each ref a series term holds read as the form `reads` names; `written` reads none.
67    pub fn of_spectral_sum_or_point(
68        sum: &SpectralSum,
69        written: Option<&ClosedForm>,
70        (grid, profile): (Grid, &Profile),
71        reads: &dyn Reads,
72    ) -> Result<Rows, CollapseError> {
73        let row = match of_sum(sum, grid, profile, reads) {
74            Err(e) if reaches_no_atom(&e) => match written.filter(|t| t.var == Var::T) {
75                Some(form) => of_written(form, grid, profile),
76                None => Err(e),
77            },
78            other => other,
79        }?;
80        Ok(Rows::held(row, grid))
81    }
82
83    fn held((row, label): (Row, (Source, Detail)), grid: Grid) -> Rows {
84        Rows {
85            width: width(&row),
86            row,
87            grid,
88            label,
89        }
90    }
91
92    pub fn width(&self) -> usize {
93        self.width
94    }
95
96    /// How exact these rows are, stated once for every sample they write.
97    pub fn label(&self, profile: &Profile) -> Label {
98        let (source, detail) = self.label.clone();
99        Label::new(source, profile.name, self.grid.rate, detail)
100    }
101
102    /// The lines a row of lines sums, each lane's, as hertz; `None` for any other row.
103    pub fn lines(&self) -> Option<Vec<f64>> {
104        let Row::Lines(lanes) = &self.row else {
105            return None;
106        };
107        Some(lanes.iter().flatten().flat_map(Direct::hz).collect())
108    }
109
110    /// `[from, to)` of every component, on the grid's own index.
111    pub fn planes(&self, from: i64, to: i64) -> Result<Vec<Vec<f64>>, CollapseError> {
112        (0..self.width)
113            .map(|c| values(&self.row, c, (from, to), self.grid))
114            .collect()
115    }
116
117    /// One component at `t`, exactly a sample's value where `t` is that sample's instant.
118    pub fn at(&self, c: usize, t: f64) -> Result<f64, CollapseError> {
119        let c =
120            lane(self.width, c).ok_or(CollapseError::NotEvaluable("a component past the width"))?;
121        let landed = self.grid.step_at(t, Round::Even);
122        match landed.filter(|n| self.grid.instant(*n) == t) {
123            Some(n) => Ok(values(&self.row, c, (n, n + 1), self.grid)?[0]),
124            None => between(&self.row, c, t),
125        }
126    }
127}
128
129type Labelled = (Row, (Source, Detail));
130
131/// One factor, the interval outside which it zeroes a finite sum, and its lines.
132struct Group {
133    factor: SpectralAtom,
134    live: SampleInterval,
135    direct: Option<Direct>,
136}
137
138/// Each lane's lines under their common factors, summed: `None` where a lane is no line sum
139/// under common factors.
140fn grouped(
141    sum: &SpectralSum,
142    (band, grid): (Audible, Grid),
143    reads: &dyn Reads,
144) -> Option<Vec<Vec<Group>>> {
145    let lane = |lane: &Lane| {
146        let groups = lines::line_groups(lane, band, reads)?.into_iter();
147        Some(
148            groups
149                .map(|(factor, held)| Group {
150                    live: lines::group_interval(&factor, &held, grid),
151                    direct: Direct::of(&held),
152                    factor,
153                })
154                .collect(),
155        )
156    };
157    sum.lanes.iter().map(lane).collect()
158}
159
160/// The rows in FORMAT 9.1's order; the first guard that holds decides.
161fn of_sum(
162    sum: &SpectralSum,
163    grid: Grid,
164    profile: &Profile,
165    reads: &dyn Reads,
166) -> Result<Labelled, CollapseError> {
167    if sum.var == Var::F {
168        return Err(CollapseError::NoBlockRow);
169    }
170    let band = Audible::on(profile, grid);
171    let ceiling = band.ceiling();
172    if let Some(found) = lines::kept_lines(sum, profile, ceiling, reads)? {
173        let (dropped, dropped_more) = lines::dropped_list(found.dropped());
174        let summed = lines::distinct(&found.kept);
175        let detail = Detail::Lines {
176            rule: Rule::LineSpectrumSummed,
177            summed,
178            dropped,
179            dropped_more,
180            tail_db: found.tail(),
181        };
182        let row = Row::Lines(found.kept.iter().map(|kept| Direct::of(kept)).collect());
183        return Ok((row, (Source::Exact, detail)));
184    }
185    let truncated = truncate::spectral_sum_read(sum, band, reads)?;
186    let label = match () {
187        () if atoms::band_limited(&truncated, ceiling, profile) => (
188            Source::Exact,
189            Detail::Continuous {
190                rule: Rule::BandLimited,
191            },
192        ),
193        () if atoms::windowed(&truncated) => (
194            Source::Measured,
195            Detail::Cropped {
196                rule: Rule::CroppedPair,
197                tail_db: tail::tail_db(&truncated, ceiling),
198            },
199        ),
200        () => (
201            Source::Measured,
202            Detail::Point {
203                rule: Rule::PointSampled,
204                alias_db: None,
205                tail_db: None,
206            },
207        ),
208    };
209    if let Some(lanes) = grouped(sum, (band, grid), reads) {
210        return Ok((Row::Grouped(lanes), label));
211    }
212    let spans = truncated
213        .lanes
214        .iter()
215        .map(|lane| active::spans(lane, grid))
216        .collect();
217    let intervals = truncated
218        .lanes
219        .iter()
220        .map(|lane| active::nonzero_intervals(lane, grid))
221        .collect();
222    let row = Row::Sweep {
223        sum: Box::new(truncated),
224        spans,
225        intervals,
226    };
227    Ok((row, label))
228}
229
230/// The row a closed form with no spectral sum takes: one per addend where they differ, else 4.
231fn of_written(form: &ClosedForm, grid: Grid, profile: &Profile) -> Result<Labelled, CollapseError> {
232    let Some(addends) = addends::addends(form) else {
233        return point(form, grid, profile);
234    };
235    let mut parts = Vec::with_capacity(addends.len());
236    let mut labels: Vec<(Source, Detail)> = Vec::new();
237    for addend in &addends {
238        match of_term(addend, grid, profile)? {
239            (Row::Added(inner), (source, Detail::Added { parts: details })) => {
240                parts.extend(inner);
241                labels.extend(details.into_iter().map(|d| (source, d)));
242            }
243            (part, (source, detail)) => {
244                let (held, reach) = (width(&part), reach(&part, grid));
245                parts.push((part, held, reach));
246                labels.push((source, detail));
247            }
248        }
249    }
250    if parts
251        .iter()
252        .all(|(part, ..)| matches!(part, Row::Point { .. }))
253    {
254        return point(form, grid, profile);
255    }
256    let source = match labels.iter().all(|(s, _)| *s == Source::Exact) {
257        true => Source::Exact,
258        false => Source::Measured,
259    };
260    let detail = Detail::Added {
261        parts: labels.into_iter().map(|(_, d)| d).collect(),
262    };
263    Ok((Row::Added(parts), (source, detail)))
264}
265
266fn of_term(form: &ClosedForm, grid: Grid, profile: &Profile) -> Result<Labelled, CollapseError> {
267    let Ok(sum) = normalize_closed_form(form) else {
268        return of_written(form, grid, profile);
269    };
270    match of_sum(&sum, grid, profile, &Opaque) {
271        Err(nested @ CollapseError::NestedSeries { .. }) => Err(nested),
272        Err(_) => of_written(form, grid, profile),
273        held => held,
274    }
275}
276
277fn point(form: &ClosedForm, grid: Grid, profile: &Profile) -> Result<Labelled, CollapseError> {
278    let written = ClosedForm {
279        body: truncate::written(&form.body, Audible::on(profile, grid))?,
280        ..form.clone()
281    };
282    let tail_db = truncate::dropped_db(&written.body);
283    let width = column::width_of(&written.body, &[]).max(1);
284    let parts = addends::summed(&written.body).unwrap_or_default();
285    let intervals = addends::addend_intervals(&parts, grid);
286    let roots: Vec<&sva_formula::Body> = std::iter::once(&written.body)
287        .chain(parts.iter().map(|p| &*p.body))
288        .collect();
289    let programs = (0..width).map(|c| Program::of(&roots, &[], c)).collect();
290    let row = Row::Point {
291        written: Box::new(written),
292        width,
293        intervals,
294        programs,
295    };
296    let detail = Detail::Point {
297        rule: Rule::PointSampled,
298        alias_db: None,
299        tail_db,
300    };
301    Ok((row, (Source::Measured, detail)))
302}
303
304fn width(row: &Row) -> usize {
305    match row {
306        Row::Lines(lanes) => lanes.len(),
307        Row::Grouped(lanes) => lanes.len(),
308        Row::Sweep { sum, .. } => sum.lanes.len(),
309        Row::Point { width, .. } => *width,
310        Row::Added(parts) => parts.iter().map(|(_, w, _)| *w).max().unwrap_or(1),
311    }
312}
313
314/// Outside it a row writes +0 at every sample: a line never ends, a sweep ends with its spans
315/// where every atom is windowed, a written form with its addends' crops.
316fn reach(row: &Row, grid: Grid) -> SampleInterval {
317    let hull = |spans: &mut dyn Iterator<Item = SampleInterval>| {
318        spans
319            .filter(|(a, b)| a < b)
320            .reduce(|x, y| (x.0.min(y.0), x.1.max(y.1)))
321            .unwrap_or((0, 0))
322    };
323    match row {
324        Row::Lines(_) => active::OPEN,
325        Row::Grouped(lanes) => hull(&mut lanes.iter().flatten().map(|g| g.live)),
326        Row::Sweep { spans, .. } if spans.iter().all(Option::is_some) => {
327            hull(&mut spans.iter().flatten().flatten().copied())
328        }
329        Row::Sweep { .. } => active::OPEN,
330        Row::Point {
331            written, intervals, ..
332        } => match addends::summed(&written.body) {
333            Some(_) => hull(&mut intervals.iter().copied()),
334            None => addends::live_interval(&written.body, grid),
335        },
336        Row::Added(parts) => hull(&mut parts.iter().map(|(.., reach)| *reach)),
337    }
338}
339
340fn values(
341    row: &Row,
342    c: usize,
343    (from, to): SampleInterval,
344    grid: Grid,
345) -> Result<Vec<f64>, CollapseError> {
346    let n = (to - from) as usize;
347    Ok(match row {
348        Row::Lines(lanes) => (from..to)
349            .map(|i| {
350                let mut held = 0.0;
351                if let Some(direct) = &lanes[c] {
352                    held += direct.at(grid.instant(i));
353                }
354                held
355            })
356            .collect(),
357        // Each group's sum from +0, times its factor.
358        Row::Grouped(lanes) => {
359            let mut out = vec![0.0; n];
360            for g in &lanes[c] {
361                let (a, b) = active::meet((from, to), g.live);
362                for m in a..b {
363                    let summed = g
364                        .direct
365                        .as_ref()
366                        .map_or(0.0, |d| 0.0 + d.at(grid.instant(m)));
367                    out[(m - from) as usize] += summed
368                        * point::eval_atom_on(&g.factor, grid.instant(m), Some((grid, m)))?.re;
369                }
370            }
371            out
372        }
373        Row::Sweep {
374            sum,
375            spans,
376            intervals,
377        } => {
378            let mut out = vec![0.0; n];
379            for (a, b) in inside(spans[c].as_deref(), (from, to)) {
380                let at = (a - from) as usize..(b - from) as usize;
381                active::sweep(&sum.lanes[c], &intervals[c], (a, b), grid, &mut out[at])?;
382            }
383            out
384        }
385        Row::Point {
386            written,
387            intervals,
388            programs,
389            ..
390        } => {
391            let summed = addends::summed(&written.body).is_some();
392            point_values(&programs[c], summed.then_some(intervals), (from, to), grid)?
393        }
394        Row::Added(parts) => {
395            let mut sum = vec![0.0; n];
396            for (part, held, reach) in parts {
397                if let (Some(lane), (a, b)) = (lane(*held, c), active::meet((from, to), *reach))
398                    && a < b
399                {
400                    let at = (a - from) as usize;
401                    for (out, v) in sum[at..].iter_mut().zip(values(part, lane, (a, b), grid)?) {
402                        *out += v;
403                    }
404                }
405            }
406            sum
407        }
408    })
409}
410
411/// Lanes a written form is evaluated over at once.
412const CHUNK: i64 = 256;
413
414/// Samples `[from, to)` of a written form's component, from the whole form, or, given each
415/// addend's interval, summed from +0 in order over the addends live at each sample: one left
416/// out is exactly zero there. The first sample refusing refuses them all.
417fn point_values(
418    program: &Program,
419    intervals: Option<&Vec<SampleInterval>>,
420    (from, to): SampleInterval,
421    grid: Grid,
422) -> Result<Vec<f64>, CollapseError> {
423    let mut out = vec![0.0; (to - from).max(0) as usize];
424    let mut columns = program.columns(CHUNK.min(to - from).max(0) as usize);
425    columns.grid = grid;
426    let mut at = from;
427    while at < to {
428        let end = (at + CHUNK).min(to);
429        let lanes = (end - at) as usize;
430        columns.again();
431        for (i, n) in (at..end).enumerate() {
432            columns.t[i] = grid.instant(n);
433            columns.on[i] = Some(n);
434        }
435        let written = &mut out[(at - from) as usize..][..lanes];
436        match intervals {
437            None => {
438                let whole = columns.root(0, (0, lanes));
439                for (i, v) in written.iter_mut().enumerate() {
440                    *v = whole.get(i).map_err(Clone::clone)?.re;
441                }
442            }
443            Some(intervals) => {
444                let mut sums: Vec<Result<sva_formula::C64, CollapseError>> =
445                    vec![Ok(sva_formula::C64::ZERO); lanes];
446                for (k, live) in intervals.iter().enumerate() {
447                    let (lo, hi) = active::meet((at, end), *live);
448                    let (lo, hi) = ((lo - at).max(0) as usize, (hi - at).max(0) as usize);
449                    if lo >= hi {
450                        continue;
451                    }
452                    let addend = columns.root(k + 1, (lo, hi));
453                    for (i, sum) in sums.iter_mut().enumerate().take(hi).skip(lo) {
454                        if let Some(held) = sum.as_ref().ok().copied() {
455                            *sum = addend.get(i).map(|v| held + v).map_err(Clone::clone);
456                        }
457                    }
458                }
459                for (v, sum) in written.iter_mut().zip(sums) {
460                    *v = match sum? {
461                        sum if sum.is_finite() => sum.re,
462                        _ => return Err(column::INFINITE),
463                    };
464                }
465            }
466        }
467        at = end;
468    }
469    Ok(out)
470}
471
472/// `values` at an instant between samples, where no interval of samples skips a term.
473fn between(row: &Row, c: usize, t: f64) -> Result<f64, CollapseError> {
474    Ok(match row {
475        Row::Lines(lanes) => {
476            let mut held = 0.0;
477            if let Some(direct) = &lanes[c] {
478                held += direct.at(t);
479            }
480            held
481        }
482        Row::Grouped(lanes) => {
483            let mut out = 0.0;
484            for g in &lanes[c] {
485                let summed = g.direct.as_ref().map_or(0.0, |d| 0.0 + d.at(t));
486                out += summed * point::eval_atom_on(&g.factor, t, None)?.re;
487            }
488            out
489        }
490        Row::Sweep { sum, .. } => point::eval_spectral_sum(sum, c, t)?.re,
491        Row::Point { programs, .. } => {
492            let mut columns = programs[c].columns(1);
493            columns.t[0] = t;
494            columns.root(0, (0, 1)).get(0).map_err(Clone::clone)?.re
495        }
496        Row::Added(parts) => {
497            let mut sum = 0.0;
498            for (part, held, _) in parts {
499                if let Some(lane) = lane(*held, c) {
500                    sum += between(part, lane, t)?;
501                }
502            }
503            sum
504        }
505    })
506}
507
508/// `[from, to)` met with a lane's spans, where every atom of it is windowed.
509fn inside(spans: Option<&[SampleInterval]>, (from, to): SampleInterval) -> Vec<SampleInterval> {
510    match spans {
511        None => vec![(from, to)],
512        Some(spans) => spans
513            .iter()
514            .map(|(a, b)| (from.max(*a), to.min(*b)))
515            .filter(|(a, b)| a < b)
516            .collect(),
517    }
518}
519
520/// One component broadcasts.
521fn lane(width: usize, c: usize) -> Option<usize> {
522    let lane = if width == 1 { 0 } else { c };
523    (lane < width).then_some(lane)
524}
525
526/// A form no atom sum reaches still has a value at every instant. A nesting past the bound
527/// is not one of those: it has a form, and too many terms to expand.
528fn reaches_no_atom(e: &CollapseError) -> bool {
529    matches!(e, CollapseError::NotEvaluable(_))
530}