Skip to main content

sva_samples/collapse/
blocks.rs

1// Concern: reads a closed form onto any span of the grid by rows no horizon chooses, priced | Non-concern: rows a whole render fits to its horizon | IO: (form, rate) -> Rows; (Tape, to) -> samples, work
2
3use sva_formula::{ClosedForm, SpectralSum, Var, normalize_closed_form};
4
5use super::lines::Direct;
6use super::truncate::{self, Audible};
7use super::{plan, point, reaches_no_atom, span};
8use crate::error::CollapseError;
9use crate::machine::tape::Tape;
10use crate::profile::Profile;
11
12/// Rows chosen from the form, the rate and the profile alone: each sample is its own
13/// instant's value, so any span reads what one whole span reads there.
14pub struct Rows {
15    row: Row,
16    rate: u32,
17    width: usize,
18}
19
20enum Row {
21    /// Every kept line summed at each instant, as a whole render's direct route sums them.
22    Lines(Vec<Option<Direct>>),
23    Sweep {
24        sum: Box<SpectralSum>,
25        spans: Vec<Option<Vec<(usize, usize)>>>,
26    },
27    Point {
28        written: Box<ClosedForm>,
29        width: usize,
30    },
31    Added(Vec<(Row, usize)>),
32}
33
34impl Rows {
35    /// `collapse::render`'s dispatch.
36    pub fn of(form: &ClosedForm, rate: u32, profile: &Profile) -> Result<Rows, CollapseError> {
37        match normalize_closed_form(form) {
38            Ok(sum) => Rows::of_spectral_sum_or_point(&sum, Some(form), rate, profile),
39            Err(_) if form.var == Var::T => Ok(Rows::held(of_written(form, rate, profile)?, rate)),
40            Err(left) => Err(CollapseError::LeftAlgebra(left.reason.clause())),
41        }
42    }
43
44    pub fn of_spectral_sum_or_point(
45        sum: &SpectralSum,
46        written: Option<&ClosedForm>,
47        rate: u32,
48        profile: &Profile,
49    ) -> Result<Rows, CollapseError> {
50        let row = match of_sum(sum, rate, profile) {
51            Err(e) if reaches_no_atom(&e) => match written.filter(|t| t.var == Var::T) {
52                Some(form) => of_written(form, rate, profile),
53                None => Err(e),
54            },
55            other => other,
56        }?;
57        Ok(Rows::held(row, rate))
58    }
59
60    fn held(row: Row, rate: u32) -> Rows {
61        Rows {
62            width: width(&row),
63            row,
64            rate,
65        }
66    }
67
68    pub fn width(&self) -> usize {
69        self.width
70    }
71
72    /// What writing `[from, to)` of every component takes, as `(priced flops, waves)`.
73    pub fn work(&self, from: usize, to: usize) -> (u128, u128) {
74        (0..self.width).fold((0, 0), |held, c| {
75            let (priced, waves) = worked(&self.row, c, from, to);
76            (held.0 + priced, held.1 + waves)
77        })
78    }
79
80    /// Appends `[tape.end(), to)` of every component, counted from the grid's start.
81    pub fn extend(&self, to: usize, tape: &mut Tape) -> Result<(), CollapseError> {
82        let step = 1.0 / f64::from(self.rate);
83        for i in tape.end()..to {
84            for c in 0..self.width {
85                tape.push(c, value(&self.row, c, i, self.rate, step)?);
86            }
87        }
88        Ok(())
89    }
90}
91
92/// `(priced flops, waves)` one component's row takes over `[from, to)`: a line, a node walked
93/// or an atom inside its spans, a sample each, as a whole render prices them.
94fn worked(row: &Row, c: usize, from: usize, to: usize) -> (u128, u128) {
95    let n = (to - from) as u128;
96    let times = |(priced, waves): (usize, usize), n: u128| (priced as u128 * n, waves as u128 * n);
97    match row {
98        Row::Lines(lanes) => lanes[c]
99            .as_ref()
100            .map_or((0, 0), |d| times(d.lines_priced_and_turned(), n)),
101        Row::Sweep { sum, spans } => {
102            let inside = spans[c].as_ref().map_or(n, |spans| {
103                let held = spans
104                    .iter()
105                    .map(|(a, b)| to.min(*b).saturating_sub(from.max(*a)));
106                held.sum::<usize>() as u128
107            });
108            let atoms = sum.lanes[c].atoms.len();
109            times((atoms, atoms), inside)
110        }
111        Row::Point { written, .. } => times(plan::point_work(&written.body, c), n),
112        Row::Added(parts) => {
113            parts
114                .iter()
115                .fold((0, 0), |held, (part, width)| match lane(*width, c) {
116                    Some(lane) => {
117                        let (priced, waves) = worked(part, lane, from, to);
118                        (held.0 + priced, held.1 + waves)
119                    }
120                    None => held,
121                })
122        }
123    }
124}
125
126/// `plan::of` with each row a horizon picks by cost replaced by the one summed per instant.
127fn of_sum(sum: &SpectralSum, rate: u32, profile: &Profile) -> Result<Row, CollapseError> {
128    if sum.var == Var::F {
129        return Err(CollapseError::NoBlockRow);
130    }
131    if let Some(found) = plan::kept_lines(sum, profile, profile.ceiling(rate))? {
132        return Ok(Row::Lines(
133            found.kept.iter().map(|kept| Direct::of(kept)).collect(),
134        ));
135    }
136    let truncated = truncate::spectral_sum(sum, Audible::of(profile, rate))?;
137    let spans = truncated
138        .lanes
139        .iter()
140        .map(|lane| span::windows(lane, 0.0, rate, f64::INFINITY))
141        .collect();
142    Ok(Row::Sweep {
143        sum: Box::new(truncated),
144        spans,
145    })
146}
147
148/// `plan::of_written`'s split into addends.
149fn of_written(form: &ClosedForm, rate: u32, profile: &Profile) -> Result<Row, CollapseError> {
150    let Some(addends) = plan::addends(form) else {
151        return point(form, rate, profile);
152    };
153    let mut parts = Vec::with_capacity(addends.len());
154    for addend in &addends {
155        match of_term(addend, rate, profile)? {
156            Row::Added(inner) => parts.extend(inner),
157            part => {
158                let held = width(&part);
159                parts.push((part, held));
160            }
161        }
162    }
163    match parts
164        .iter()
165        .all(|(part, _)| matches!(part, Row::Point { .. }))
166    {
167        true => point(form, rate, profile),
168        false => Ok(Row::Added(parts)),
169    }
170}
171
172fn of_term(form: &ClosedForm, rate: u32, profile: &Profile) -> Result<Row, CollapseError> {
173    let Ok(sum) = normalize_closed_form(form) else {
174        return of_written(form, rate, profile);
175    };
176    match of_sum(&sum, rate, profile) {
177        Err(nested @ CollapseError::NestedSeries { .. }) => Err(nested),
178        Err(_) => of_written(form, rate, profile),
179        held => held,
180    }
181}
182
183fn point(form: &ClosedForm, rate: u32, profile: &Profile) -> Result<Row, CollapseError> {
184    let written = ClosedForm {
185        body: truncate::written(&form.body, Audible::of(profile, rate))?,
186        ..form.clone()
187    };
188    let width = point::width_of(&written.body, &point::NoRefs).max(1);
189    Ok(Row::Point {
190        written: Box::new(written),
191        width,
192    })
193}
194
195fn width(row: &Row) -> usize {
196    match row {
197        Row::Lines(lanes) => lanes.len(),
198        Row::Sweep { sum, .. } => sum.lanes.len(),
199        Row::Point { width, .. } => *width,
200        Row::Added(parts) => parts.iter().map(|(_, w)| *w).max().unwrap_or(1),
201    }
202}
203
204/// Each row's arithmetic, in its whole-render row's order, so the bits agree.
205fn value(row: &Row, c: usize, i: usize, rate: u32, step: f64) -> Result<f64, CollapseError> {
206    Ok(match row {
207        Row::Lines(lanes) => {
208            let mut held = 0.0;
209            if let Some(direct) = &lanes[c] {
210                held += direct.at(0.0 + i as f64 / f64::from(rate));
211            }
212            held
213        }
214        Row::Sweep { sum, spans } => {
215            let inside = spans[c]
216                .as_ref()
217                .is_none_or(|spans| spans.iter().any(|(from, to)| (*from..*to).contains(&i)));
218            match inside {
219                true => point::eval_lane(&sum.lanes[c], 0.0 + i as f64 * step)?.re,
220                false => 0.0,
221            }
222        }
223        Row::Point { written, .. } => {
224            point::eval_body(&written.body, c, 0.0 + i as f64 * step, &point::NoRefs)?.re
225        }
226        Row::Added(parts) => {
227            let mut sum = 0.0;
228            for (part, held) in parts {
229                if let Some(lane) = lane(*held, c) {
230                    sum += value(part, lane, i, rate, step)?;
231                }
232            }
233            sum
234        }
235    })
236}
237
238/// One component broadcasts.
239fn lane(width: usize, c: usize) -> Option<usize> {
240    let lane = if width == 1 { 0 } else { c };
241    (lane < width).then_some(lane)
242}