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 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) => summed(lanes[c].as_ref(), (from, to), grid),
349        // Each group's sum from +0, times its factor.
350        Row::Grouped(lanes) => {
351            let mut out = vec![0.0; n];
352            for g in &lanes[c] {
353                let (a, b) = active::meet((from, to), g.live);
354                for (m, summed) in (a..b).zip(summed(g.direct.as_ref(), (a, b), grid)) {
355                    out[(m - from) as usize] += summed
356                        * point::eval_atom_on(&g.factor, grid.instant(m), Some((grid, m)))?.re;
357                }
358            }
359            out
360        }
361        Row::Sweep {
362            sum,
363            spans,
364            intervals,
365        } => {
366            let mut out = vec![0.0; n];
367            for (a, b) in inside(spans[c].as_deref(), (from, to)) {
368                let at = (a - from) as usize..(b - from) as usize;
369                active::sweep(&sum.lanes[c], &intervals[c], (a, b), grid, &mut out[at])?;
370            }
371            out
372        }
373        Row::Point {
374            written,
375            intervals,
376            programs,
377            ..
378        } => {
379            let summed = addends::summed(&written.body).is_some();
380            point_values(&programs[c], summed.then_some(intervals), (from, to), grid)?
381        }
382        Row::Added(parts) => {
383            let mut sum = vec![0.0; n];
384            for (part, held, reach) in parts {
385                if let (Some(lane), (a, b)) = (lane(*held, c), active::meet((from, to), *reach))
386                    && a < b
387                {
388                    let at = (a - from) as usize;
389                    for (out, v) in sum[at..].iter_mut().zip(values(part, lane, (a, b), grid)?) {
390                        *out += v;
391                    }
392                }
393            }
394            sum
395        }
396    })
397}
398
399/// Lanes a written form is evaluated over at once.
400const CHUNK: i64 = 256;
401
402/// A lane's lines summed from +0 at each sample of `[from, to)`.
403fn summed(direct: Option<&Direct>, (from, to): SampleInterval, grid: Grid) -> Vec<f64> {
404    let Some(direct) = direct else {
405        return vec![0.0; (to - from).max(0) as usize];
406    };
407    let mut out = Vec::with_capacity((to - from).max(0) as usize);
408    for at in (from..to).step_by(CHUNK as usize) {
409        let t: Vec<f64> = (at..(at + CHUNK).min(to))
410            .map(|n| grid.instant(n))
411            .collect();
412        out.extend(direct.each(&t).into_iter().map(|v| 0.0 + v));
413    }
414    out
415}
416
417/// Samples `[from, to)` of a written form's component, from the whole form, or, given each
418/// addend's interval, summed from +0 in order over the addends live at each sample: one left
419/// out is exactly zero there. The first sample refusing refuses them all.
420fn point_values(
421    program: &Program,
422    intervals: Option<&Vec<SampleInterval>>,
423    (from, to): SampleInterval,
424    grid: Grid,
425) -> Result<Vec<f64>, CollapseError> {
426    let mut out = vec![0.0; (to - from).max(0) as usize];
427    let mut columns = program.columns(CHUNK.min(to - from).max(0) as usize);
428    columns.grid = grid;
429    let mut at = from;
430    while at < to {
431        let end = (at + CHUNK).min(to);
432        let lanes = (end - at) as usize;
433        columns.again();
434        for (i, n) in (at..end).enumerate() {
435            columns.t[i] = grid.instant(n);
436            columns.on[i] = Some(n);
437        }
438        let written = &mut out[(at - from) as usize..][..lanes];
439        match intervals {
440            None => {
441                let whole = columns.root(0, (0, lanes));
442                for (i, v) in written.iter_mut().enumerate() {
443                    *v = whole.get(i).map_err(Clone::clone)?.re;
444                }
445            }
446            Some(intervals) => {
447                let mut sums: Vec<Result<sva_formula::C64, CollapseError>> =
448                    vec![Ok(sva_formula::C64::ZERO); lanes];
449                for (k, live) in intervals.iter().enumerate() {
450                    let (lo, hi) = active::meet((at, end), *live);
451                    let (lo, hi) = ((lo - at).max(0) as usize, (hi - at).max(0) as usize);
452                    if lo >= hi {
453                        continue;
454                    }
455                    let addend = columns.root(k + 1, (lo, hi));
456                    for (i, sum) in sums.iter_mut().enumerate().take(hi).skip(lo) {
457                        if let Some(held) = sum.as_ref().ok().copied() {
458                            *sum = addend.get(i).map(|v| held + v).map_err(Clone::clone);
459                        }
460                    }
461                }
462                for (v, sum) in written.iter_mut().zip(sums) {
463                    *v = match sum? {
464                        sum if sum.is_finite() => sum.re,
465                        _ => return Err(column::INFINITE),
466                    };
467                }
468            }
469        }
470        at = end;
471    }
472    Ok(out)
473}
474
475/// `values` at an instant between samples, where no interval of samples skips a term.
476fn between(row: &Row, c: usize, t: f64) -> Result<f64, CollapseError> {
477    Ok(match row {
478        Row::Lines(lanes) => {
479            let mut held = 0.0;
480            if let Some(direct) = &lanes[c] {
481                held += direct.at(t);
482            }
483            held
484        }
485        Row::Grouped(lanes) => {
486            let mut out = 0.0;
487            for g in &lanes[c] {
488                let summed = g.direct.as_ref().map_or(0.0, |d| 0.0 + d.at(t));
489                out += summed * point::eval_atom_on(&g.factor, t, None)?.re;
490            }
491            out
492        }
493        Row::Sweep { sum, .. } => point::eval_spectral_sum(sum, c, t)?.re,
494        Row::Point { programs, .. } => {
495            let mut columns = programs[c].columns(1);
496            columns.t[0] = t;
497            columns.root(0, (0, 1)).get(0).map_err(Clone::clone)?.re
498        }
499        Row::Added(parts) => {
500            let mut sum = 0.0;
501            for (part, held, _) in parts {
502                if let Some(lane) = lane(*held, c) {
503                    sum += between(part, lane, t)?;
504                }
505            }
506            sum
507        }
508    })
509}
510
511/// `[from, to)` met with a lane's spans, where every atom of it is windowed.
512fn inside(spans: Option<&[SampleInterval]>, (from, to): SampleInterval) -> Vec<SampleInterval> {
513    match spans {
514        None => vec![(from, to)],
515        Some(spans) => spans
516            .iter()
517            .map(|(a, b)| (from.max(*a), to.min(*b)))
518            .filter(|(a, b)| a < b)
519            .collect(),
520    }
521}
522
523/// One component broadcasts.
524fn lane(width: usize, c: usize) -> Option<usize> {
525    let lane = if width == 1 { 0 } else { c };
526    (lane < width).then_some(lane)
527}
528
529/// A form no atom sum reaches still has a value at every instant. A nesting past the bound
530/// is not one of those: it has a form, and too many terms to expand.
531fn reaches_no_atom(e: &CollapseError) -> bool {
532    matches!(e, CollapseError::NotEvaluable(_))
533}