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