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