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