Skip to main content

sva_samples/collapse/
blocks.rs

1// Concern: reads a closed form onto any span of the grid by rows no extent chooses, priced | Non-concern: rows a whole render fits to its extent | IO: (form, rate) -> Rows; (Tape, to) -> samples, work
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, Window};
7use super::lines::{self, Direct};
8use super::truncate::{self, Audible};
9use super::{atoms, plan, point, reaches_no_atom, reading, span, tail};
10use crate::Grid;
11use crate::error::CollapseError;
12use crate::label::{Detail, Label, Rule, Source};
13use crate::machine::tape::Tape;
14use crate::profile::Profile;
15
16/// Rows chosen from the form, the rate 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    rate: u32,
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        windows: Vec<Vec<Window>>,
34    },
35    /// A written sum's addends each read only inside its own window.
36    Point {
37        written: Box<ClosedForm>,
38        width: usize,
39        windows: Vec<Window>,
40    },
41    /// Each addend's row, its width, and the window outside which it writes +0.
42    Added(Vec<(Row, usize, Window)>),
43}
44
45impl Rows {
46    /// `collapse::render`'s dispatch.
47    pub fn of(form: &ClosedForm, rate: u32, profile: &Profile) -> Result<Rows, CollapseError> {
48        match normalize_closed_form(form) {
49            Ok(sum) => Rows::of_spectral_sum_or_point(&sum, Some(form), (rate, profile), &Opaque),
50            Err(_) if form.var == Var::T => Ok(Rows::held(of_written(form, rate, profile)?, rate)),
51            Err(left) => Err(CollapseError::LeftAlgebra(left.reason.clause())),
52        }
53    }
54
55    /// Each ref a series term holds read as the form `reads` names; `written` reads none.
56    pub fn of_spectral_sum_or_point(
57        sum: &SpectralSum,
58        written: Option<&ClosedForm>,
59        (rate, profile): (u32, &Profile),
60        reads: &dyn Reads,
61    ) -> Result<Rows, CollapseError> {
62        let row = match of_sum(sum, rate, profile, reads) {
63            Err(e) if reaches_no_atom(&e) => match written.filter(|t| t.var == Var::T) {
64                Some(form) => of_written(form, rate, profile),
65                None => Err(e),
66            },
67            other => other,
68        }?;
69        Ok(Rows::held(row, rate))
70    }
71
72    fn held((row, label): (Row, (Source, Detail)), rate: u32) -> Rows {
73        Rows {
74            width: width(&row),
75            row,
76            rate,
77            label,
78        }
79    }
80
81    pub fn width(&self) -> usize {
82        self.width
83    }
84
85    /// How exact these rows are, stated once for every sample they write.
86    pub fn label(&self, profile: &Profile) -> Label {
87        let (source, detail) = self.label.clone();
88        Label::new(source, profile.name, self.rate, detail)
89    }
90
91    /// The lines a row of lines sums, each lane's, as hertz; `None` for any other row.
92    pub fn lines(&self) -> Option<Vec<f64>> {
93        let Row::Lines(lanes) = &self.row else {
94            return None;
95        };
96        Some(lanes.iter().flatten().flat_map(Direct::hz).collect())
97    }
98
99    /// `[from, to)` of every component, on the grid's own index.
100    pub fn planes(&self, from: i64, to: i64) -> Result<Vec<Vec<f64>>, CollapseError> {
101        (0..self.width)
102            .map(|c| values(&self.row, c, (from, to), self.rate))
103            .collect()
104    }
105
106    /// What writing `[from, to)` of every component takes, as `(priced flops, waves)`.
107    pub fn work(&self, from: i64, to: i64) -> (u128, u128) {
108        (0..self.width).fold((0, 0), |held, c| {
109            let (priced, waves) = worked(&self.row, c, from, to, Grid::of(self.rate));
110            (held.0 + priced, held.1 + waves)
111        })
112    }
113
114    /// Appends `[tape.end(), to)` of every component, on the grid's own index.
115    pub fn extend(&self, to: i64, tape: &mut Tape) -> Result<(), CollapseError> {
116        let from = tape.end();
117        if to <= from {
118            return Ok(());
119        }
120        let planes = (0..self.width)
121            .map(|c| values(&self.row, c, (from, to), self.rate))
122            .collect::<Result<Vec<_>, _>>()?;
123        for i in 0..(to - from) as usize {
124            for (c, plane) in planes.iter().enumerate() {
125                tape.push(c, plane[i]);
126            }
127        }
128        Ok(())
129    }
130}
131
132/// `(priced flops, waves)` one component's row takes over `[from, to)`: a line, a node walked
133/// or an atom inside its spans, a sample each, as a whole render prices them.
134fn worked(row: &Row, c: usize, from: i64, to: i64, grid: Grid) -> (u128, u128) {
135    let n = (to - from) as u128;
136    let times = |(priced, waves): (usize, usize), n: u128| (priced as u128 * n, waves as u128 * n);
137    match row {
138        Row::Lines(lanes) => lanes[c]
139            .as_ref()
140            .map_or((0, 0), |d| times(d.lines_priced_and_turned(), n)),
141        Row::Grouped(lanes) => lanes[c].iter().fold((0, 0), |held, g| {
142            let (from, to) = active::meet((from, to), g.live);
143            let (priced, waves) = g
144                .direct
145                .as_ref()
146                .map_or((0, 0), Direct::lines_priced_and_turned);
147            let (priced, waves) = times((priced + 1, waves), (to - from).max(0) as u128);
148            (held.0 + priced, held.1 + waves)
149        }),
150        Row::Sweep { spans, windows, .. } => {
151            let evaluated =
152                active::evaluated(&windows[c], &inside(spans[c].as_deref(), (from, to)));
153            (evaluated, evaluated)
154        }
155        Row::Point { written, .. } => plan::point_work(&written.body, c, grid, (from, to)),
156        Row::Added(parts) => parts.iter().fold((0, 0), |held, (part, width, reach)| {
157            match (lane(*width, c), active::meet((from, to), *reach)) {
158                (Some(lane), (a, b)) if a < b => {
159                    let (priced, waves) = worked(part, lane, a, b, grid);
160                    (held.0 + priced, held.1 + waves)
161                }
162                _ => held,
163            }
164        }),
165    }
166}
167
168type Labelled = (Row, (Source, Detail));
169
170/// One factor, the window outside which it zeroes a finite sum, and its lines.
171struct Group {
172    factor: SpectralAtom,
173    live: Window,
174    direct: Option<Direct>,
175}
176
177/// `plan::LanePlan::Grouped` with every line summed: `None` where a lane is no line sum
178/// under common factors.
179fn grouped(
180    sum: &SpectralSum,
181    (band, grid): (Audible, Grid),
182    reads: &dyn Reads,
183) -> Option<Vec<Vec<Group>>> {
184    let lane = |lane: &Lane| {
185        let groups = lines::line_groups(lane, band, reads)?.into_iter();
186        Some(
187            groups
188                .map(|(factor, held)| Group {
189                    live: plan::group_window(&factor, &held, 1, grid),
190                    direct: Direct::of(&held),
191                    factor,
192                })
193                .collect(),
194        )
195    };
196    sum.lanes.iter().map(lane).collect()
197}
198
199/// `plan::of` with each row an extent picks by cost replaced by the one summed per instant.
200fn of_sum(
201    sum: &SpectralSum,
202    rate: u32,
203    profile: &Profile,
204    reads: &dyn Reads,
205) -> Result<Labelled, CollapseError> {
206    if sum.var == Var::F {
207        return Err(CollapseError::NoBlockRow);
208    }
209    let ceiling = profile.ceiling(rate);
210    if let Some(found) = plan::kept_lines(sum, profile, ceiling, reads)? {
211        let (dropped, dropped_more) = lines::dropped_list(found.dropped());
212        let summed = lines::distinct(&found.kept);
213        let detail = Detail::Lines {
214            rule: Rule::LineSpectrumSummed,
215            placed: 0,
216            summed,
217            dropped,
218            dropped_more,
219            terms: Some(summed),
220            tail_db: found.tail(),
221        };
222        let row = Row::Lines(found.kept.iter().map(|kept| Direct::of(kept)).collect());
223        return Ok((row, (Source::Exact, detail)));
224    }
225    let truncated = truncate::spectral_sum_read(sum, Audible::of(profile, rate), reads)?;
226    let label = match () {
227        () if atoms::band_limited(&truncated, ceiling, profile) => (
228            Source::Exact,
229            Detail::Continuous {
230                rule: Rule::BandLimited,
231            },
232        ),
233        () if atoms::windowed(&truncated) => (
234            Source::Measured,
235            Detail::Cropped {
236                rule: Rule::CroppedPair,
237                tail_db: tail::tail_db(&truncated, ceiling),
238            },
239        ),
240        () => (
241            Source::Measured,
242            Detail::Point {
243                rule: Rule::PointSampled,
244                alias_db: None,
245                tail_db: None,
246            },
247        ),
248    };
249    let grid = Grid::of(rate);
250    if let Some(lanes) = grouped(sum, (Audible::of(profile, rate), grid), reads) {
251        return Ok((Row::Grouped(lanes), label));
252    }
253    let spans = truncated
254        .lanes
255        .iter()
256        .map(|lane| span::windows(lane, rate))
257        .collect();
258    let windows = truncated
259        .lanes
260        .iter()
261        .map(|lane| active::windows(lane, grid))
262        .collect();
263    let row = Row::Sweep {
264        sum: Box::new(truncated),
265        spans,
266        windows,
267    };
268    Ok((row, label))
269}
270
271/// `plan::of_written`'s split into addends.
272fn of_written(form: &ClosedForm, rate: u32, profile: &Profile) -> Result<Labelled, CollapseError> {
273    let Some(addends) = plan::addends(form) else {
274        return point(form, rate, profile);
275    };
276    let mut parts = Vec::with_capacity(addends.len());
277    let mut labels: Vec<(Source, Detail)> = Vec::new();
278    for addend in &addends {
279        match of_term(addend, rate, profile)? {
280            (Row::Added(inner), (source, Detail::Added { parts: details })) => {
281                parts.extend(inner);
282                labels.extend(details.into_iter().map(|d| (source, d)));
283            }
284            (part, (source, detail)) => {
285                let (held, reach) = (width(&part), reach(&part, rate));
286                parts.push((part, held, reach));
287                labels.push((source, detail));
288            }
289        }
290    }
291    if parts
292        .iter()
293        .all(|(part, ..)| matches!(part, Row::Point { .. }))
294    {
295        return point(form, rate, profile);
296    }
297    let source = match labels.iter().all(|(s, _)| *s == Source::Exact) {
298        true => Source::Exact,
299        false => Source::Measured,
300    };
301    let detail = Detail::Added {
302        parts: labels.into_iter().map(|(_, d)| d).collect(),
303    };
304    Ok((Row::Added(parts), (source, detail)))
305}
306
307fn of_term(form: &ClosedForm, rate: u32, profile: &Profile) -> Result<Labelled, CollapseError> {
308    let Ok(sum) = normalize_closed_form(form) else {
309        return of_written(form, rate, profile);
310    };
311    match of_sum(&sum, rate, profile, &Opaque) {
312        Err(nested @ CollapseError::NestedSeries { .. }) => Err(nested),
313        Err(_) => of_written(form, rate, profile),
314        held => held,
315    }
316}
317
318fn point(form: &ClosedForm, rate: u32, profile: &Profile) -> Result<Labelled, CollapseError> {
319    let written = ClosedForm {
320        body: truncate::written(&form.body, Audible::of(profile, rate))?,
321        ..form.clone()
322    };
323    let tail_db = truncate::dropped_db(&written.body);
324    let width = point::width_of(&written.body, &point::NoRefs).max(1);
325    let windows = plan::summed(&written.body).map_or_else(Vec::new, |parts| {
326        plan::addend_windows(&parts, Grid::of(rate))
327    });
328    let row = Row::Point {
329        written: Box::new(written),
330        width,
331        windows,
332    };
333    let detail = Detail::Point {
334        rule: Rule::PointSampled,
335        alias_db: None,
336        tail_db,
337    };
338    Ok((row, (Source::Measured, detail)))
339}
340
341fn width(row: &Row) -> usize {
342    match row {
343        Row::Lines(lanes) => lanes.len(),
344        Row::Grouped(lanes) => lanes.len(),
345        Row::Sweep { sum, .. } => sum.lanes.len(),
346        Row::Point { width, .. } => *width,
347        Row::Added(parts) => parts.iter().map(|(_, w, _)| *w).max().unwrap_or(1),
348    }
349}
350
351/// Outside it a row writes +0 at every sample: a line never ends, a sweep ends with its spans
352/// where every atom is windowed, a written form with its addends' crops.
353fn reach(row: &Row, rate: u32) -> Window {
354    let hull = |spans: &mut dyn Iterator<Item = Window>| {
355        spans
356            .filter(|(a, b)| a < b)
357            .reduce(|x, y| (x.0.min(y.0), x.1.max(y.1)))
358            .unwrap_or((0, 0))
359    };
360    match row {
361        Row::Lines(_) => active::OPEN,
362        Row::Grouped(lanes) => hull(&mut lanes.iter().flatten().map(|g| g.live)),
363        Row::Sweep { spans, .. } if spans.iter().all(Option::is_some) => {
364            hull(&mut spans.iter().flatten().flatten().copied())
365        }
366        Row::Sweep { .. } => active::OPEN,
367        Row::Point {
368            written, windows, ..
369        } => match plan::summed(&written.body) {
370            Some(_) => hull(&mut windows.iter().copied()),
371            None => plan::live_window(&written.body, Grid::of(rate)),
372        },
373        Row::Added(parts) => hull(&mut parts.iter().map(|(.., reach)| *reach)),
374    }
375}
376
377/// Each row's arithmetic over `[from, to)`, in its whole-render row's order, so the bits agree.
378fn values(row: &Row, c: usize, (from, to): Window, rate: u32) -> Result<Vec<f64>, CollapseError> {
379    let grid = Grid::of(rate);
380    let n = (to - from) as usize;
381    Ok(match row {
382        Row::Lines(lanes) => (from..to)
383            .map(|i| {
384                let mut held = 0.0;
385                if let Some(direct) = &lanes[c] {
386                    held += direct.at(i as f64 / f64::from(rate));
387                }
388                held
389            })
390            .collect(),
391        // `reading::under_a_window`'s order: each group's sum from +0, times its factor.
392        Row::Grouped(lanes) => {
393            let mut out = vec![0.0; n];
394            for g in &lanes[c] {
395                let (a, b) = active::meet((from, to), g.live);
396                for m in a..b {
397                    let summed = g
398                        .direct
399                        .as_ref()
400                        .map_or(0.0, |d| 0.0 + d.at(m as f64 / f64::from(rate)));
401                    out[(m - from) as usize] +=
402                        summed * point::eval_atom(&g.factor, grid.instant(m))?.re;
403                }
404            }
405            out
406        }
407        Row::Sweep {
408            sum,
409            spans,
410            windows,
411        } => {
412            let mut out = vec![0.0; n];
413            for (a, b) in inside(spans[c].as_deref(), (from, to)) {
414                let at = (a - from) as usize..(b - from) as usize;
415                active::sweep(&sum.lanes[c], &windows[c], (a, b), grid, &mut out[at])?;
416            }
417            out
418        }
419        Row::Point {
420            written, windows, ..
421        } => reading::written(&written.body, windows, c, (from, to), grid)?,
422        Row::Added(parts) => {
423            let mut sum = vec![0.0; n];
424            for (part, held, reach) in parts {
425                if let (Some(lane), (a, b)) = (lane(*held, c), active::meet((from, to), *reach))
426                    && a < b
427                {
428                    let at = (a - from) as usize;
429                    for (out, v) in sum[at..].iter_mut().zip(values(part, lane, (a, b), rate)?) {
430                        *out += v;
431                    }
432                }
433            }
434            sum
435        }
436    })
437}
438
439/// `[from, to)` met with a lane's spans, where every atom of it is windowed.
440fn inside(spans: Option<&[(i64, i64)]>, (from, to): Window) -> Vec<Window> {
441    match spans {
442        None => vec![(from, to)],
443        Some(spans) => spans
444            .iter()
445            .map(|(a, b)| (from.max(*a), to.min(*b)))
446            .filter(|(a, b)| a < b)
447            .collect(),
448    }
449}
450
451/// One component broadcasts.
452fn lane(width: usize, c: usize) -> Option<usize> {
453    let lane = if width == 1 { 0 } else { c };
454    (lane < width).then_some(lane)
455}