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