1use sva_formula::spectral_sum::atom::SpectralAtom;
4use sva_formula::{ClosedForm, Lane, Opaque, Reads, SpectralSum, Var, normalize_closed_form};
5
6use super::active::{self, SampleInterval};
7use super::column::{self, Program};
8use super::lines::{self, Direct};
9use super::truncate::{self, Audible};
10use super::{addends, atoms, point, tail};
11use crate::error::CollapseError;
12use crate::grid::{Grid, Round};
13use crate::label::{Detail, Label, Rule, Source};
14use crate::profile::Profile;
15
16pub struct Rows {
19 row: Row,
20 grid: Grid,
21 width: usize,
22 label: (Source, Detail),
23}
24
25enum Row {
26 Lines(Vec<Option<Direct>>),
28 Grouped(Vec<Vec<Group>>),
30 Sweep {
31 sum: Box<SpectralSum>,
32 spans: Vec<Option<Vec<(i64, i64)>>>,
33 intervals: Vec<Vec<SampleInterval>>,
34 },
35 Point {
38 written: Box<ClosedForm>,
39 width: usize,
40 intervals: Vec<SampleInterval>,
41 programs: Vec<Program>,
42 },
43 Added(Vec<(Row, usize, SampleInterval)>),
45}
46
47impl std::fmt::Debug for Rows {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 f.debug_struct("Rows")
50 .field("grid", &self.grid)
51 .field("width", &self.width)
52 .field("label", &self.label)
53 .finish_non_exhaustive()
54 }
55}
56
57impl Rows {
58 pub fn of(form: &ClosedForm, grid: Grid, profile: &Profile) -> Result<Rows, CollapseError> {
59 match normalize_closed_form(form) {
60 Ok(sum) => Rows::of_spectral_sum_or_point(&sum, Some(form), (grid, profile), &Opaque),
61 Err(_) if form.var == Var::T => Ok(Rows::held(of_written(form, grid, profile)?, grid)),
62 Err(left) => Err(CollapseError::LeftAlgebra(left.reason.clause())),
63 }
64 }
65
66 pub fn of_spectral_sum_or_point(
68 sum: &SpectralSum,
69 written: Option<&ClosedForm>,
70 (grid, profile): (Grid, &Profile),
71 reads: &dyn Reads,
72 ) -> Result<Rows, CollapseError> {
73 let row = match of_sum(sum, grid, profile, reads) {
74 Err(e) if reaches_no_atom(&e) => match written.filter(|t| t.var == Var::T) {
75 Some(form) => of_written(form, grid, profile),
76 None => Err(e),
77 },
78 other => other,
79 }?;
80 Ok(Rows::held(row, grid))
81 }
82
83 fn held((row, label): (Row, (Source, Detail)), grid: Grid) -> Rows {
84 Rows {
85 width: width(&row),
86 row,
87 grid,
88 label,
89 }
90 }
91
92 pub fn width(&self) -> usize {
93 self.width
94 }
95
96 pub fn label(&self, profile: &Profile) -> Label {
98 let (source, detail) = self.label.clone();
99 Label::new(source, profile.name, self.grid.rate, detail)
100 }
101
102 pub fn lines(&self) -> Option<Vec<f64>> {
104 let Row::Lines(lanes) = &self.row else {
105 return None;
106 };
107 Some(lanes.iter().flatten().flat_map(Direct::hz).collect())
108 }
109
110 pub fn planes(&self, from: i64, to: i64) -> Result<Vec<Vec<f64>>, CollapseError> {
112 (0..self.width)
113 .map(|c| values(&self.row, c, (from, to), self.grid))
114 .collect()
115 }
116
117 pub fn at(&self, c: usize, t: f64) -> Result<f64, CollapseError> {
119 let c =
120 lane(self.width, c).ok_or(CollapseError::NotEvaluable("a component past the width"))?;
121 let landed = self.grid.step_at(t, Round::Even);
122 match landed.filter(|n| self.grid.instant(*n) == t) {
123 Some(n) => Ok(values(&self.row, c, (n, n + 1), self.grid)?[0]),
124 None => between(&self.row, c, t),
125 }
126 }
127}
128
129type Labelled = (Row, (Source, Detail));
130
131struct Group {
133 factor: SpectralAtom,
134 live: SampleInterval,
135 direct: Option<Direct>,
136}
137
138fn grouped(
141 sum: &SpectralSum,
142 (band, grid): (Audible, Grid),
143 reads: &dyn Reads,
144) -> Option<Vec<Vec<Group>>> {
145 let lane = |lane: &Lane| {
146 let groups = lines::line_groups(lane, band, reads)?.into_iter();
147 Some(
148 groups
149 .map(|(factor, held)| Group {
150 live: lines::group_interval(&factor, &held, grid),
151 direct: Direct::of(&held),
152 factor,
153 })
154 .collect(),
155 )
156 };
157 sum.lanes.iter().map(lane).collect()
158}
159
160fn of_sum(
162 sum: &SpectralSum,
163 grid: Grid,
164 profile: &Profile,
165 reads: &dyn Reads,
166) -> Result<Labelled, CollapseError> {
167 if sum.var == Var::F {
168 return Err(CollapseError::NoBlockRow);
169 }
170 let band = Audible::on(profile, grid);
171 let ceiling = band.ceiling();
172 if let Some(found) = lines::kept_lines(sum, profile, ceiling, reads)? {
173 let (dropped, dropped_more) = lines::dropped_list(found.dropped());
174 let summed = lines::distinct(&found.kept);
175 let detail = Detail::Lines {
176 rule: Rule::LineSpectrumSummed,
177 summed,
178 dropped,
179 dropped_more,
180 tail_db: found.tail(),
181 };
182 let row = Row::Lines(found.kept.iter().map(|kept| Direct::of(kept)).collect());
183 return Ok((row, (Source::Exact, detail)));
184 }
185 let truncated = truncate::spectral_sum_read(sum, band, reads)?;
186 let label = match () {
187 () if atoms::band_limited(&truncated, ceiling, profile) => (
188 Source::Exact,
189 Detail::Continuous {
190 rule: Rule::BandLimited,
191 },
192 ),
193 () if atoms::windowed(&truncated) => (
194 Source::Measured,
195 Detail::Cropped {
196 rule: Rule::CroppedPair,
197 tail_db: tail::tail_db(&truncated, ceiling),
198 },
199 ),
200 () => (
201 Source::Measured,
202 Detail::Point {
203 rule: Rule::PointSampled,
204 alias_db: None,
205 tail_db: None,
206 },
207 ),
208 };
209 if let Some(lanes) = grouped(sum, (band, grid), reads) {
210 return Ok((Row::Grouped(lanes), label));
211 }
212 let spans = truncated
213 .lanes
214 .iter()
215 .map(|lane| active::spans(lane, grid))
216 .collect();
217 let intervals = truncated
218 .lanes
219 .iter()
220 .map(|lane| active::nonzero_intervals(lane, grid))
221 .collect();
222 let row = Row::Sweep {
223 sum: Box::new(truncated),
224 spans,
225 intervals,
226 };
227 Ok((row, label))
228}
229
230fn of_written(form: &ClosedForm, grid: Grid, profile: &Profile) -> Result<Labelled, CollapseError> {
232 let Some(addends) = addends::addends(form) else {
233 return point(form, grid, profile);
234 };
235 let mut parts = Vec::with_capacity(addends.len());
236 let mut labels: Vec<(Source, Detail)> = Vec::new();
237 for addend in &addends {
238 match of_term(addend, grid, profile)? {
239 (Row::Added(inner), (source, Detail::Added { parts: details })) => {
240 parts.extend(inner);
241 labels.extend(details.into_iter().map(|d| (source, d)));
242 }
243 (part, (source, detail)) => {
244 let (held, reach) = (width(&part), reach(&part, grid));
245 parts.push((part, held, reach));
246 labels.push((source, detail));
247 }
248 }
249 }
250 if parts
251 .iter()
252 .all(|(part, ..)| matches!(part, Row::Point { .. }))
253 {
254 return point(form, grid, profile);
255 }
256 let source = match labels.iter().all(|(s, _)| *s == Source::Exact) {
257 true => Source::Exact,
258 false => Source::Measured,
259 };
260 let detail = Detail::Added {
261 parts: labels.into_iter().map(|(_, d)| d).collect(),
262 };
263 Ok((Row::Added(parts), (source, detail)))
264}
265
266fn of_term(form: &ClosedForm, grid: Grid, profile: &Profile) -> Result<Labelled, CollapseError> {
267 let Ok(sum) = normalize_closed_form(form) else {
268 return of_written(form, grid, profile);
269 };
270 match of_sum(&sum, grid, profile, &Opaque) {
271 Err(nested @ CollapseError::NestedSeries { .. }) => Err(nested),
272 Err(_) => of_written(form, grid, profile),
273 held => held,
274 }
275}
276
277fn point(form: &ClosedForm, grid: Grid, profile: &Profile) -> Result<Labelled, CollapseError> {
278 let written = ClosedForm {
279 body: truncate::written(&form.body, Audible::on(profile, grid))?,
280 ..form.clone()
281 };
282 let tail_db = truncate::dropped_db(&written.body);
283 let width = column::width_of(&written.body, &[]).max(1);
284 let parts = addends::summed(&written.body).unwrap_or_default();
285 let intervals = addends::addend_intervals(&parts, grid);
286 let roots: Vec<&sva_formula::Body> = std::iter::once(&written.body)
287 .chain(parts.iter().map(|p| &*p.body))
288 .collect();
289 let programs = (0..width).map(|c| Program::of(&roots, &[], c)).collect();
290 let row = Row::Point {
291 written: Box::new(written),
292 width,
293 intervals,
294 programs,
295 };
296 let detail = Detail::Point {
297 rule: Rule::PointSampled,
298 alias_db: None,
299 tail_db,
300 };
301 Ok((row, (Source::Measured, detail)))
302}
303
304fn width(row: &Row) -> usize {
305 match row {
306 Row::Lines(lanes) => lanes.len(),
307 Row::Grouped(lanes) => lanes.len(),
308 Row::Sweep { sum, .. } => sum.lanes.len(),
309 Row::Point { width, .. } => *width,
310 Row::Added(parts) => parts.iter().map(|(_, w, _)| *w).max().unwrap_or(1),
311 }
312}
313
314fn reach(row: &Row, grid: Grid) -> SampleInterval {
317 let hull = |spans: &mut dyn Iterator<Item = SampleInterval>| {
318 spans
319 .filter(|(a, b)| a < b)
320 .reduce(|x, y| (x.0.min(y.0), x.1.max(y.1)))
321 .unwrap_or((0, 0))
322 };
323 match row {
324 Row::Lines(_) => active::OPEN,
325 Row::Grouped(lanes) => hull(&mut lanes.iter().flatten().map(|g| g.live)),
326 Row::Sweep { spans, .. } if spans.iter().all(Option::is_some) => {
327 hull(&mut spans.iter().flatten().flatten().copied())
328 }
329 Row::Sweep { .. } => active::OPEN,
330 Row::Point {
331 written, intervals, ..
332 } => match addends::summed(&written.body) {
333 Some(_) => hull(&mut intervals.iter().copied()),
334 None => addends::live_interval(&written.body, grid),
335 },
336 Row::Added(parts) => hull(&mut parts.iter().map(|(.., reach)| *reach)),
337 }
338}
339
340fn values(
341 row: &Row,
342 c: usize,
343 (from, to): SampleInterval,
344 grid: Grid,
345) -> Result<Vec<f64>, CollapseError> {
346 let n = (to - from) as usize;
347 Ok(match row {
348 Row::Lines(lanes) => summed(lanes[c].as_ref(), (from, to), grid),
349 Row::Grouped(lanes) => {
351 let mut out = vec![0.0; n];
352 for g in &lanes[c] {
353 let (a, b) = active::meet((from, to), g.live);
354 for (m, summed) in (a..b).zip(summed(g.direct.as_ref(), (a, b), grid)) {
355 out[(m - from) as usize] += summed
356 * point::eval_atom_on(&g.factor, grid.instant(m), Some((grid, m)))?.re;
357 }
358 }
359 out
360 }
361 Row::Sweep {
362 sum,
363 spans,
364 intervals,
365 } => {
366 let mut out = vec![0.0; n];
367 for (a, b) in inside(spans[c].as_deref(), (from, to)) {
368 let at = (a - from) as usize..(b - from) as usize;
369 active::sweep(&sum.lanes[c], &intervals[c], (a, b), grid, &mut out[at])?;
370 }
371 out
372 }
373 Row::Point {
374 written,
375 intervals,
376 programs,
377 ..
378 } => {
379 let summed = addends::summed(&written.body).is_some();
380 point_values(&programs[c], summed.then_some(intervals), (from, to), grid)?
381 }
382 Row::Added(parts) => {
383 let mut sum = vec![0.0; n];
384 for (part, held, reach) in parts {
385 if let (Some(lane), (a, b)) = (lane(*held, c), active::meet((from, to), *reach))
386 && a < b
387 {
388 let at = (a - from) as usize;
389 for (out, v) in sum[at..].iter_mut().zip(values(part, lane, (a, b), grid)?) {
390 *out += v;
391 }
392 }
393 }
394 sum
395 }
396 })
397}
398
399const CHUNK: i64 = 256;
401
402fn summed(direct: Option<&Direct>, (from, to): SampleInterval, grid: Grid) -> Vec<f64> {
404 let Some(direct) = direct else {
405 return vec![0.0; (to - from).max(0) as usize];
406 };
407 let mut out = Vec::with_capacity((to - from).max(0) as usize);
408 for at in (from..to).step_by(CHUNK as usize) {
409 let t: Vec<f64> = (at..(at + CHUNK).min(to))
410 .map(|n| grid.instant(n))
411 .collect();
412 out.extend(direct.each(&t).into_iter().map(|v| 0.0 + v));
413 }
414 out
415}
416
417fn point_values(
421 program: &Program,
422 intervals: Option<&Vec<SampleInterval>>,
423 (from, to): SampleInterval,
424 grid: Grid,
425) -> Result<Vec<f64>, CollapseError> {
426 let mut out = vec![0.0; (to - from).max(0) as usize];
427 let mut columns = program.columns(CHUNK.min(to - from).max(0) as usize);
428 columns.grid = grid;
429 let mut at = from;
430 while at < to {
431 let end = (at + CHUNK).min(to);
432 let lanes = (end - at) as usize;
433 columns.again();
434 for (i, n) in (at..end).enumerate() {
435 columns.t[i] = grid.instant(n);
436 columns.on[i] = Some(n);
437 }
438 let written = &mut out[(at - from) as usize..][..lanes];
439 match intervals {
440 None => {
441 let whole = columns.root(0, (0, lanes));
442 for (i, v) in written.iter_mut().enumerate() {
443 *v = whole.get(i).map_err(Clone::clone)?.re;
444 }
445 }
446 Some(intervals) => {
447 let mut sums: Vec<Result<sva_formula::C64, CollapseError>> =
448 vec![Ok(sva_formula::C64::ZERO); lanes];
449 for (k, live) in intervals.iter().enumerate() {
450 let (lo, hi) = active::meet((at, end), *live);
451 let (lo, hi) = ((lo - at).max(0) as usize, (hi - at).max(0) as usize);
452 if lo >= hi {
453 continue;
454 }
455 let addend = columns.root(k + 1, (lo, hi));
456 for (i, sum) in sums.iter_mut().enumerate().take(hi).skip(lo) {
457 if let Some(held) = sum.as_ref().ok().copied() {
458 *sum = addend.get(i).map(|v| held + v).map_err(Clone::clone);
459 }
460 }
461 }
462 for (v, sum) in written.iter_mut().zip(sums) {
463 *v = match sum? {
464 sum if sum.is_finite() => sum.re,
465 _ => return Err(column::INFINITE),
466 };
467 }
468 }
469 }
470 at = end;
471 }
472 Ok(out)
473}
474
475fn between(row: &Row, c: usize, t: f64) -> Result<f64, CollapseError> {
477 Ok(match row {
478 Row::Lines(lanes) => {
479 let mut held = 0.0;
480 if let Some(direct) = &lanes[c] {
481 held += direct.at(t);
482 }
483 held
484 }
485 Row::Grouped(lanes) => {
486 let mut out = 0.0;
487 for g in &lanes[c] {
488 let summed = g.direct.as_ref().map_or(0.0, |d| 0.0 + d.at(t));
489 out += summed * point::eval_atom_on(&g.factor, t, None)?.re;
490 }
491 out
492 }
493 Row::Sweep { sum, .. } => point::eval_spectral_sum(sum, c, t)?.re,
494 Row::Point { programs, .. } => {
495 let mut columns = programs[c].columns(1);
496 columns.t[0] = t;
497 columns.root(0, (0, 1)).get(0).map_err(Clone::clone)?.re
498 }
499 Row::Added(parts) => {
500 let mut sum = 0.0;
501 for (part, held, _) in parts {
502 if let Some(lane) = lane(*held, c) {
503 sum += between(part, lane, t)?;
504 }
505 }
506 sum
507 }
508 })
509}
510
511fn inside(spans: Option<&[SampleInterval]>, (from, to): SampleInterval) -> Vec<SampleInterval> {
513 match spans {
514 None => vec![(from, to)],
515 Some(spans) => spans
516 .iter()
517 .map(|(a, b)| (from.max(*a), to.min(*b)))
518 .filter(|(a, b)| a < b)
519 .collect(),
520 }
521}
522
523fn lane(width: usize, c: usize) -> Option<usize> {
525 let lane = if width == 1 { 0 } else { c };
526 (lane < width).then_some(lane)
527}
528
529fn reaches_no_atom(e: &CollapseError) -> bool {
532 matches!(e, CollapseError::NotEvaluable(_))
533}