1use sva_formula::closed_form::{Part, map_children};
4use sva_formula::spectral_sum::atom::SpectralAtom;
5use sva_formula::{Body, ClosedForm, Lane, Line, SpectralSum, Var, normalize_closed_form};
6
7use super::active::{self, Window};
8use super::truncate::Audible;
9use super::{Extent, atoms, lines, point, truncate};
10use crate::Grid;
11use crate::error::CollapseError;
12use crate::label::Rule;
13use crate::profile::Profile;
14
15pub fn direct_is_cheaper(lines: usize, n: usize, len: usize) -> bool {
18 (lines as f64) * (len as f64) <= (n as f64) * (n as f64).log2()
19}
20
21pub struct LinePlan {
22 pub placed: Vec<Vec<Line>>,
23 pub summed: Vec<Vec<Line>>,
24 pub dropped: Vec<Vec<Line>>,
25 pub bins: usize,
26 pub tail_db: Option<f64>,
27}
28
29pub struct Group {
31 pub factor: SpectralAtom,
32 pub placed: Vec<Line>,
33 pub summed: Vec<Line>,
34 pub live: Window,
35 samples: usize,
36}
37
38pub enum LanePlan {
41 Grouped {
42 groups: Vec<Group>,
43 bins: usize,
44 },
45 Sweep {
46 atoms: usize,
47 samples: usize,
48 evaluated: u128,
49 },
50}
51
52pub struct Sampled {
53 pub sum: Box<SpectralSum>,
54 pub rule: Rule,
55 pub lanes: Vec<LanePlan>,
56}
57
58pub enum Plan {
60 Spectrum(Box<SpectralSum>),
61 Lines(Box<LinePlan>),
62 Sampled(Box<Sampled>),
63 Point {
66 written: Box<ClosedForm>,
67 width: usize,
68 },
69 Added(Vec<Plan>),
71}
72
73pub fn of(
75 sum: &SpectralSum,
76 rate: u32,
77 extent: Extent,
78 profile: &Profile,
79 len: usize,
80) -> Result<Plan, CollapseError> {
81 let ceiling = profile.ceiling(rate);
82 if sum.var == Var::F {
83 return Ok(Plan::Spectrum(Box::new(sum.clone())));
84 }
85 if let Some(found) = line_plan(sum, rate, extent, profile, len, ceiling)? {
86 return Ok(Plan::Lines(Box::new(found)));
87 }
88 let truncated = truncate::spectral_sum(sum, Audible::of(profile, rate))?;
89 let rule = match () {
90 () if atoms::band_limited(&truncated, ceiling, profile) => Rule::BandLimited,
91 () if atoms::windowed(&truncated) => Rule::CroppedPair,
92 () => Rule::PointSampled,
93 };
94 let lanes = truncated
95 .lanes
96 .iter()
97 .map(|lane| lane_plan(lane, rate, extent, len))
98 .collect();
99 Ok(Plan::Sampled(Box::new(Sampled {
100 sum: Box::new(truncated),
101 rule,
102 lanes,
103 })))
104}
105
106pub fn of_written(
108 form: &ClosedForm,
109 rate: u32,
110 extent: Extent,
111 profile: &Profile,
112 len: usize,
113) -> Result<Plan, CollapseError> {
114 let Some(addends) = addends(form) else {
115 return point_plan(form, rate, profile);
116 };
117 let mut parts = Vec::with_capacity(addends.len());
118 for addend in &addends {
119 match of_term(addend, rate, extent, profile, len)? {
120 Plan::Added(inner) => parts.extend(inner),
121 part => parts.push(part),
122 }
123 }
124 match parts.iter().all(|part| matches!(part, Plan::Point { .. })) {
126 true => point_plan(form, rate, profile),
127 false => Ok(Plan::Added(parts)),
128 }
129}
130
131fn of_term(
134 form: &ClosedForm,
135 rate: u32,
136 extent: Extent,
137 profile: &Profile,
138 len: usize,
139) -> Result<Plan, CollapseError> {
140 let Ok(sum) = normalize_closed_form(form) else {
141 return of_written(form, rate, extent, profile, len);
142 };
143 match of(&sum, rate, extent, profile, len) {
144 Err(nested @ CollapseError::NestedSeries { .. }) => Err(nested),
145 Err(_) => of_written(form, rate, extent, profile, len),
146 held => held,
147 }
148}
149
150pub(super) fn addends(form: &ClosedForm) -> Option<Vec<ClosedForm>> {
151 if let Body::Add(parts) = &form.body {
152 return Some(
153 parts
154 .iter()
155 .map(|part| ClosedForm {
156 var: form.var,
157 body: (*part.body).clone(),
158 origin: part.origin,
159 })
160 .collect(),
161 );
162 }
163 let under = linear_over(&form.body)?;
164 let held = addends(&ClosedForm {
165 body: under,
166 ..form.clone()
167 })?;
168 Some(
169 held.into_iter()
170 .map(|addend| ClosedForm {
171 body: map_children(&form.body, |_| {
172 Part::new(addend.origin, addend.body.clone())
173 }),
174 ..addend
175 })
176 .collect(),
177 )
178}
179
180fn linear_over(f: &Body) -> Option<Body> {
182 match f {
183 Body::Crop { of, .. }
184 | Body::Shift { of, .. }
185 | Body::Deriv { of, .. }
186 | Body::Channel(of, _) => Some((*of.body).clone()),
187 _ => None,
188 }
189}
190
191fn point_plan(form: &ClosedForm, rate: u32, profile: &Profile) -> Result<Plan, CollapseError> {
192 let written = ClosedForm {
193 body: truncate::written(&form.body, Audible::of(profile, rate))?,
194 ..form.clone()
195 };
196 let width = point::width_of(&written.body, &point::NoRefs).max(1);
197 Ok(Plan::Point {
198 written: Box::new(written),
199 width,
200 })
201}
202
203pub fn point_flops(f: &Body, grid: Grid, span: Window) -> u128 {
205 let width = point::width_of(f, &point::NoRefs).max(1);
206 (0..width).map(|c| point_work(f, c, grid, span).0).sum()
207}
208
209pub fn point_work(f: &Body, component: usize, grid: Grid, span: Window) -> (u128, u128) {
213 let clock = Clock::grid(grid);
214 let Some(parts) = summed(f) else {
215 return walked(f, component, &clock, span);
216 };
217 let n = (i128::from(span.1) - i128::from(span.0)).max(0) as u128;
218 parts
219 .iter()
220 .zip(addend_windows(&parts, grid))
221 .fold((n, 0), |held, (part, live)| {
222 let (priced, waves) = walked(&part.body, component, &clock, active::meet(span, live));
223 (held.0 + priced, held.1 + waves)
224 })
225}
226
227pub(super) fn summed(f: &Body) -> Option<Vec<&Part>> {
230 let Body::Add(parts) = f else {
231 return None;
232 };
233 let (mut head, mut tails) = (parts, Vec::new());
234 while let [first, rest @ ..] = head.as_slice()
235 && let Body::Add(inner) = &*first.body
236 {
237 tails.push(rest);
238 head = inner;
239 }
240 let mut out: Vec<&Part> = head.iter().collect();
241 for tail in tails.iter().rev() {
242 out.extend(tail.iter());
243 }
244 Some(out)
245}
246
247pub(super) fn addend_windows(parts: &[&Part], grid: Grid) -> Vec<Window> {
249 parts
250 .iter()
251 .map(|part| live_window(&part.body, grid))
252 .collect()
253}
254
255pub(super) fn live_window(f: &Body, grid: Grid) -> Window {
256 live(f, &Clock::grid(grid), active::OPEN)
257}
258
259fn live(f: &Body, clock: &Clock, span: Window) -> Window {
261 match f {
262 Body::Crop { of, .. } => live(&of.body, clock, clock.cropped(span, f)),
263 Body::Mul(parts) => parts
264 .iter()
265 .fold(span, |held, part| live(&part.body, clock, held)),
266 Body::Shift { by, of } => live(&of.body, &clock.shifted(*by), span),
267 _ => span,
268 }
269}
270
271#[derive(Clone)]
273struct Clock {
274 grid: Grid,
275 shifts: Option<Vec<f64>>,
276}
277
278impl Clock {
279 fn grid(grid: Grid) -> Clock {
280 Clock {
281 grid,
282 shifts: Some(Vec::new()),
283 }
284 }
285
286 fn shifted(&self, by: f64) -> Clock {
287 Clock {
288 grid: self.grid,
289 shifts: self
290 .shifts
291 .as_ref()
292 .map(|held| [held.as_slice(), &[by]].concat()),
293 }
294 }
295
296 fn warped(&self) -> Clock {
297 Clock {
298 grid: self.grid,
299 shifts: None,
300 }
301 }
302
303 fn cropped(&self, span: Window, crop: &Body) -> Window {
305 let (
306 Body::Crop {
307 l, r, rise, fall, ..
308 },
309 Some(shifts),
310 ) = (crop, &self.shifts)
311 else {
312 return span;
313 };
314 let at = |n: i64| shifts.iter().fold(self.grid.instant(n), |t, by| t - by);
315 let (l, r) = (l.value(), r.value());
316 let (mut from, mut to) = active::meet(span, active::between(l, r, at));
317 let shut = |n: i64| point::crop_gain(at(n), l, r, *rise, *fall) == 0.0;
318 while from < to && shut(from) {
319 from += 1;
320 }
321 while from < to && shut(to - 1) {
322 to -= 1;
323 }
324 (from, to)
325 }
326}
327
328fn walked(f: &Body, component: usize, clock: &Clock, span: Window) -> (u128, u128) {
329 let n = (i128::from(span.1) - i128::from(span.0)).max(0) as u128;
330 if n == 0 {
331 return (0, 0);
332 }
333 let add = |a: (u128, u128), b: (u128, u128)| (a.0 + b.0, a.1 + b.1);
334 let branch = |part: &Part, c: usize, clock: &Clock, span: Window| {
335 add((n, 0), walked(&part.body, c, clock, span))
336 };
337 match f {
338 Body::Run(run) => (super::run::steps(run) as u128 * n, run.len() as u128 * n),
339 Body::Join(parts) => {
340 let widths: Vec<usize> = parts
341 .iter()
342 .map(|p| point::width_of(&p.body, &point::NoRefs))
343 .collect();
344 match point::lane_of(&widths, component) {
345 Some((at, inner)) => branch(&parts[at], inner, clock, span),
346 None => (n, 0),
347 }
348 }
349 Body::Channel(of, k) => branch(of, usize::from(*k), clock, span),
350 Body::Crop { of, .. } => branch(of, component, clock, clock.cropped(span, f)),
351 Body::Mul(parts) => {
353 let mut live = span;
354 parts.iter().fold((n, 0), |held, part| {
355 let walked = walked(&part.body, component, clock, live);
356 live = clock.cropped(live, &part.body);
357 add(held, walked)
358 })
359 }
360 Body::Shift { by, of } => branch(of, component, &clock.shifted(*by), span),
361 Body::Warp { at, of } => add(
362 branch(at, component, clock, span),
363 walked(&of.body, component, &clock.warped(), span),
364 ),
365 other => sva_formula::closed_form::children(other)
366 .iter()
367 .map(|part| walked(&part.body, component, clock, span))
368 .fold((n, 0), add),
369 }
370}
371
372pub fn summed_bounds(
375 sum: &SpectralSum,
376 profile: &Profile,
377 rate: u32,
378) -> Result<Vec<(Option<SpectralAtom>, f64)>, CollapseError> {
379 if sum.var == Var::F {
380 return Ok(Vec::new());
381 }
382 if let Some(found) = kept_lines(sum, profile, profile.ceiling(rate))? {
383 let direct = found.kept.iter().filter_map(|kept| lines::Direct::of(kept));
384 return Ok(direct.map(|d| (None, d.bound())).collect());
385 }
386 let truncated = truncate::spectral_sum(sum, Audible::of(profile, rate))?;
387 let groups = truncated.lanes.iter().filter_map(lines::grouped).flatten();
388 Ok(groups
389 .filter_map(|(factor, held)| Some((Some(factor), lines::Direct::of(&held)?.bound())))
390 .collect())
391}
392
393fn line_plan(
395 sum: &SpectralSum,
396 rate: u32,
397 extent: Extent,
398 profile: &Profile,
399 len: usize,
400 ceiling: f64,
401) -> Result<Option<LinePlan>, CollapseError> {
402 let Some(found) = kept_lines(sum, profile, ceiling)? else {
403 return Ok(None);
404 };
405 let bins = lines::grid(&found.kept, &found.grids, rate, extent.span_secs(rate), len);
406 let split: Vec<(Vec<Line>, Vec<Line>)> = found
407 .kept
408 .iter()
409 .map(|k| match direct_is_cheaper(k.len(), bins, len) {
410 true => (Vec::new(), k.clone()),
411 false => lines::split(k, bins, rate),
412 })
413 .collect();
414 Ok(Some(LinePlan {
415 placed: split.iter().map(|(on, _)| on.clone()).collect(),
416 summed: split.into_iter().map(|(_, off)| off).collect(),
417 dropped: found.dropped,
418 bins,
419 tail_db: found.tail,
420 }))
421}
422
423pub(super) struct Kept {
425 pub(super) kept: Vec<Vec<Line>>,
426 dropped: Vec<Vec<Line>>,
427 grids: Vec<f64>,
428 tail: Option<f64>,
429}
430
431impl Kept {
432 pub(super) fn dropped(&self) -> &[Vec<Line>] {
433 &self.dropped
434 }
435
436 pub(super) fn tail(&self) -> Option<f64> {
437 self.tail
438 }
439}
440
441pub(super) fn kept_lines(
442 sum: &SpectralSum,
443 profile: &Profile,
444 ceiling: f64,
445) -> Result<Option<Kept>, CollapseError> {
446 let mut per_lane = Vec::with_capacity(sum.lanes.len());
447 let mut grids: Vec<f64> = Vec::new();
448 let mut tail: Option<f64> = None;
449 for lane in &sum.lanes {
450 match lines::of_lane(lane, ceiling, profile.floor(ceiling), profile.half_lsb()) {
451 Some(found) => {
452 if let Some(left) = found.tail_db {
453 tail = Some(tail.map_or(left, |held: f64| held.max(left)));
454 }
455 grids.extend(found.grids);
456 per_lane.push(found.lines);
457 }
458 None => return Ok(None),
459 }
460 }
461
462 let (mut kept, mut dropped): (Vec<Vec<Line>>, Vec<Vec<Line>>) = (Vec::new(), Vec::new());
463 for found in &per_lane {
464 let (here, gone): (Vec<Line>, Vec<Line>) = found.iter().partition(|l| l.hz.abs() < ceiling);
465 kept.push(here);
466 dropped.push(gone);
467 }
468 if kept.iter().all(Vec::is_empty) && !dropped.iter().all(Vec::is_empty) {
470 let lowest = dropped
471 .iter()
472 .flatten()
473 .map(|l| l.hz.abs())
474 .fold(f64::INFINITY, f64::min);
475 return Err(CollapseError::EmptyBand { ceiling, lowest });
476 }
477 Ok(Some(Kept {
478 kept,
479 dropped,
480 grids,
481 tail,
482 }))
483}
484
485fn lane_plan(lane: &Lane, rate: u32, extent: Extent, len: usize) -> LanePlan {
486 let bins = lines::bins(extent.span_secs(rate), rate);
487 let grid = Grid::of(rate);
488 let spans = super::span::absolute(lane, extent, rate);
489 let sweep = LanePlan::Sweep {
490 atoms: lane.atoms.len(),
491 samples: spans.iter().map(|(from, to)| (to - from) as usize).sum(),
492 evaluated: active::evaluated(&active::windows(lane, grid), &spans),
493 };
494 let Some(found) = lines::grouped(lane) else {
495 return sweep;
496 };
497 let groups: Vec<Group> = found
498 .into_iter()
499 .map(|(factor, held)| {
500 let (placed, summed) = match direct_is_cheaper(held.len(), bins, len) {
501 true => (Vec::new(), held),
502 false => lines::split(&held, bins, rate),
503 };
504 let live = match bounded(&placed, &summed, bins) {
505 true => active::window(&factor, grid),
506 false => active::OPEN,
507 };
508 Group {
509 samples: active::evaluated(&[live], &[(extent.start, extent.end)]) as usize,
510 factor,
511 placed,
512 summed,
513 live,
514 }
515 })
516 .collect();
517 let grouped = LanePlan::Grouped { groups, bins };
518 match grouped.chosen_by(len) <= sweep.chosen_by(len) {
519 true => grouped,
520 false => sweep,
521 }
522}
523
524fn bounded(placed: &[Line], summed: &[Line], bins: usize) -> bool {
525 let reach: f64 = placed
526 .iter()
527 .chain(summed)
528 .map(|l| l.amp.re.abs() + l.amp.im.abs())
529 .sum();
530 reach * (bins.max(1) as f64) < 1e300
531}
532
533pub fn transform_flops(n: usize) -> u128 {
536 let stages = |m: usize| m as u128 * (m.max(2).trailing_zeros() as u128);
537 match n.is_power_of_two() {
538 true => stages(n),
539 false => 3 * stages((2 * n - 1).next_power_of_two()),
540 }
541}
542
543impl LinePlan {
544 pub fn flops(&self, len: usize) -> u128 {
545 let transforms: u128 = self
546 .placed
547 .iter()
548 .filter(|p| !p.is_empty())
549 .map(|_| transform_flops(self.bins))
550 .sum();
551 let direct: u128 = self
552 .summed
553 .iter()
554 .map(|s| s.len() as u128 * len as u128)
555 .sum();
556 transforms + direct
557 }
558
559 pub fn rule(&self) -> Rule {
560 match (lines::distinct(&self.placed), lines::distinct(&self.summed)) {
561 (_, 0) => Rule::LineSpectrumExact,
562 (0, _) => Rule::LineSpectrumSummed,
563 _ => Rule::LineSpectrumMixed,
564 }
565 }
566}
567
568impl LanePlan {
569 pub fn flops(&self) -> u128 {
570 match self {
571 LanePlan::Grouped { groups, bins } => groups
572 .iter()
573 .map(|g| transformed(g, *bins) + (g.summed.len() as u128 + 1) * g.samples as u128)
574 .sum(),
575 LanePlan::Sweep { evaluated, .. } => *evaluated,
576 }
577 }
578
579 fn chosen_by(&self, len: usize) -> u128 {
580 match self {
581 LanePlan::Grouped { groups, bins } => groups
582 .iter()
583 .map(|g| transformed(g, *bins) + (g.summed.len() as u128 + 1) * len as u128)
584 .sum(),
585 LanePlan::Sweep { atoms, samples, .. } => *atoms as u128 * *samples as u128,
586 }
587 }
588
589 fn swept_flops(&self, len: u128) -> u128 {
590 let terms: u128 = match self {
591 LanePlan::Grouped { groups, .. } => groups
592 .iter()
593 .map(|g| (g.placed.len() + g.summed.len() + 1) as u128)
594 .sum(),
595 LanePlan::Sweep { atoms, .. } => *atoms as u128,
596 };
597 terms * len
598 }
599}
600
601fn transformed(g: &Group, bins: usize) -> u128 {
602 match g.placed.is_empty() {
603 true => 0,
604 false => transform_flops(bins),
605 }
606}
607
608impl Plan {
609 pub fn nonzero(&self, rate: u32, extent: Extent) -> Extent {
612 let grid = Grid::of(rate);
613 let lane = |lane: &Lane, taken: &LanePlan| -> Option<Option<Window>> {
614 match taken {
615 LanePlan::Sweep { .. } if lane.modal.is_empty() => Some(active::hull(
616 &active::windows(lane, grid),
617 &super::span::absolute(lane, extent, rate),
618 )),
619 LanePlan::Grouped { groups, .. } if groups.iter().all(|g| g.placed.is_empty()) => {
620 Some(active::hull(
621 &groups.iter().map(|g| g.live).collect::<Vec<_>>(),
622 &[(extent.start, extent.end)],
623 ))
624 }
625 _ => None,
626 }
627 };
628 let found = match self {
629 Plan::Sampled(held) => held
630 .sum
631 .lanes
632 .iter()
633 .zip(&held.lanes)
634 .map(|(l, taken)| lane(l, taken))
635 .collect::<Option<Vec<_>>>()
636 .map(|lanes| lanes.into_iter().flatten().collect::<Vec<_>>()),
637 Plan::Added(parts) => Some(
638 parts
639 .iter()
640 .map(|part| part.nonzero(rate, extent))
641 .filter(|part| !part.is_empty())
642 .map(|part| (part.start, part.end))
643 .collect(),
644 ),
645 _ => None,
646 };
647 match found {
648 None => extent,
649 Some(spans) => spans
650 .into_iter()
651 .map(|(a, b)| Extent::new(a, b))
652 .fold(Extent::NOWHERE, Extent::hull)
653 .intersect(extent),
654 }
655 }
656
657 pub fn route(&self) -> Vec<u64> {
659 let lines = |held: &[Vec<Line>]| held.iter().map(|l| l.len() as u64).collect::<Vec<_>>();
660 match self {
661 Plan::Spectrum(_) => vec![0],
662 Plan::Lines(found) => [vec![1], lines(&found.placed), lines(&found.summed)].concat(),
663 Plan::Sampled(held) => {
664 let mut out = vec![2, held.rule as u64];
665 for lane in &held.lanes {
666 match lane {
667 LanePlan::Sweep { .. } => out.push(0),
668 LanePlan::Grouped { groups, .. } => {
669 out.push(1 + groups.len() as u64);
670 for g in groups {
671 out.extend([g.placed.len() as u64, g.summed.len() as u64]);
672 }
673 }
674 }
675 }
676 out
677 }
678 Plan::Point { .. } => vec![3],
679 Plan::Added(parts) => {
680 let mut out = vec![4, parts.len() as u64];
681 parts.iter().for_each(|part| out.extend(part.route()));
682 out
683 }
684 }
685 }
686
687 pub fn flops(&self, rate: u32, extent: Extent) -> u128 {
688 let len = extent.len();
689 match self {
690 Plan::Spectrum(_) => transform_flops(len),
691 Plan::Lines(found) => found.flops(len),
692 Plan::Sampled(held) => held.lanes.iter().map(|lane| lane.flops()).sum(),
693 Plan::Point { written, .. } => {
694 point_flops(&written.body, Grid::of(rate), (extent.start, extent.end))
695 }
696 Plan::Added(parts) => parts.iter().map(|part| part.flops(rate, extent)).sum(),
697 }
698 }
699
700 pub fn alias_flops(&self, rate: u32, extent: Extent) -> u128 {
701 self.alias_flops_at(rate, extent, super::ALIAS_OVERSAMPLE)
702 }
703
704 pub fn alias_flops_at(&self, rate: u32, extent: Extent, oversample: usize) -> u128 {
707 let reference = (extent.len() * oversample) as u128;
708 match self {
709 Plan::Point { written, .. } => {
710 let finer = oversample as i64;
711 point_flops(
712 &written.body,
713 Grid::finer(rate, oversample),
714 (extent.start * finer, extent.end * finer),
715 )
716 }
717 Plan::Sampled(held) if held.rule == Rule::PointSampled => held
718 .lanes
719 .iter()
720 .map(|lane| lane.swept_flops(reference))
721 .sum(),
722 Plan::Added(parts) => parts
723 .iter()
724 .map(|part| part.alias_flops_at(rate, extent, oversample))
725 .sum(),
726 Plan::Spectrum(_) | Plan::Lines(_) | Plan::Sampled(_) => 0,
727 }
728 }
729
730 pub fn rule(&self) -> Rule {
731 match self {
732 Plan::Spectrum(_) => Rule::InverseSpectrum,
733 Plan::Lines(found) => found.rule(),
734 Plan::Sampled(held) => held.rule,
735 Plan::Point { .. } => Rule::PointSampled,
736 Plan::Added(_) => Rule::Added,
737 }
738 }
739}