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 band = Audible::of(profile, rate);
387 let groups = sum.lanes.iter().map(|lane| lines::line_groups(lane, band));
388 let Some(groups) = groups.collect::<Option<Vec<_>>>() else {
389 return Ok(Vec::new());
390 };
391 Ok(groups
392 .into_iter()
393 .flatten()
394 .filter_map(|(factor, held)| Some((Some(factor), lines::Direct::of(&held)?.bound())))
395 .collect())
396}
397
398fn line_plan(
400 sum: &SpectralSum,
401 rate: u32,
402 extent: Extent,
403 profile: &Profile,
404 len: usize,
405 ceiling: f64,
406) -> Result<Option<LinePlan>, CollapseError> {
407 let Some(found) = kept_lines(sum, profile, ceiling)? else {
408 return Ok(None);
409 };
410 let bins = lines::grid(&found.kept, &found.grids, rate, extent.span_secs(rate), len);
411 let split: Vec<(Vec<Line>, Vec<Line>)> = found
412 .kept
413 .iter()
414 .map(|k| match direct_is_cheaper(k.len(), bins, len) {
415 true => (Vec::new(), k.clone()),
416 false => lines::split(k, bins, rate),
417 })
418 .collect();
419 Ok(Some(LinePlan {
420 placed: split.iter().map(|(on, _)| on.clone()).collect(),
421 summed: split.into_iter().map(|(_, off)| off).collect(),
422 dropped: found.dropped,
423 bins,
424 tail_db: found.tail,
425 }))
426}
427
428pub(super) struct Kept {
430 pub(super) kept: Vec<Vec<Line>>,
431 dropped: Vec<Vec<Line>>,
432 grids: Vec<f64>,
433 tail: Option<f64>,
434}
435
436impl Kept {
437 pub(super) fn dropped(&self) -> &[Vec<Line>] {
438 &self.dropped
439 }
440
441 pub(super) fn tail(&self) -> Option<f64> {
442 self.tail
443 }
444}
445
446pub(super) fn kept_lines(
447 sum: &SpectralSum,
448 profile: &Profile,
449 ceiling: f64,
450) -> Result<Option<Kept>, CollapseError> {
451 let mut per_lane = Vec::with_capacity(sum.lanes.len());
452 let mut grids: Vec<f64> = Vec::new();
453 let mut tail: Option<f64> = None;
454 for lane in &sum.lanes {
455 match lines::of_lane(lane, ceiling, profile.floor(ceiling), profile.half_lsb()) {
456 Some(found) => {
457 if let Some(left) = found.tail_db {
458 tail = Some(tail.map_or(left, |held: f64| held.max(left)));
459 }
460 grids.extend(found.grids);
461 per_lane.push(found.lines);
462 }
463 None => return Ok(None),
464 }
465 }
466
467 let (mut kept, mut dropped): (Vec<Vec<Line>>, Vec<Vec<Line>>) = (Vec::new(), Vec::new());
468 for found in &per_lane {
469 let (here, gone): (Vec<Line>, Vec<Line>) = found.iter().partition(|l| l.hz.abs() < ceiling);
470 kept.push(here);
471 dropped.push(gone);
472 }
473 if kept.iter().all(Vec::is_empty) && !dropped.iter().all(Vec::is_empty) {
475 let lowest = dropped
476 .iter()
477 .flatten()
478 .map(|l| l.hz.abs())
479 .fold(f64::INFINITY, f64::min);
480 return Err(CollapseError::EmptyBand { ceiling, lowest });
481 }
482 Ok(Some(Kept {
483 kept,
484 dropped,
485 grids,
486 tail,
487 }))
488}
489
490fn lane_plan(lane: &Lane, rate: u32, extent: Extent, len: usize) -> LanePlan {
491 let bins = lines::bins(extent.span_secs(rate), rate);
492 let grid = Grid::of(rate);
493 let spans = super::span::absolute(lane, extent, rate);
494 let sweep = LanePlan::Sweep {
495 atoms: lane.atoms.len(),
496 samples: spans.iter().map(|(from, to)| (to - from) as usize).sum(),
497 evaluated: active::evaluated(&active::windows(lane, grid), &spans),
498 };
499 let Some(found) = lines::grouped(lane) else {
500 return sweep;
501 };
502 let groups: Vec<Group> = found
503 .into_iter()
504 .map(|(factor, held)| {
505 let (placed, summed) = match direct_is_cheaper(held.len(), bins, len) {
506 true => (Vec::new(), held),
507 false => lines::split(&held, bins, rate),
508 };
509 let live = group_window(&factor, &[placed.as_slice(), &summed].concat(), bins, grid);
510 Group {
511 samples: active::evaluated(&[live], &[(extent.start, extent.end)]) as usize,
512 factor,
513 placed,
514 summed,
515 live,
516 }
517 })
518 .collect();
519 let grouped = LanePlan::Grouped { groups, bins };
520 match grouped.chosen_by(len) <= sweep.chosen_by(len) {
521 true => grouped,
522 false => sweep,
523 }
524}
525
526pub(super) fn group_window(
528 factor: &SpectralAtom,
529 held: &[Line],
530 bins: usize,
531 grid: Grid,
532) -> Window {
533 let reach: f64 = held.iter().map(|l| l.amp.re.abs() + l.amp.im.abs()).sum();
534 match reach * (bins.max(1) as f64) < 1e300 {
535 true => active::window(factor, grid),
536 false => active::OPEN,
537 }
538}
539
540pub fn transform_flops(n: usize) -> u128 {
543 let stages = |m: usize| m as u128 * (m.max(2).trailing_zeros() as u128);
544 match n.is_power_of_two() {
545 true => stages(n),
546 false => 3 * stages((2 * n - 1).next_power_of_two()),
547 }
548}
549
550impl LinePlan {
551 pub fn flops(&self, len: usize) -> u128 {
552 let transforms: u128 = self
553 .placed
554 .iter()
555 .filter(|p| !p.is_empty())
556 .map(|_| transform_flops(self.bins))
557 .sum();
558 let direct: u128 = self
559 .summed
560 .iter()
561 .map(|s| s.len() as u128 * len as u128)
562 .sum();
563 transforms + direct
564 }
565
566 pub fn rule(&self) -> Rule {
567 match (lines::distinct(&self.placed), lines::distinct(&self.summed)) {
568 (_, 0) => Rule::LineSpectrumExact,
569 (0, _) => Rule::LineSpectrumSummed,
570 _ => Rule::LineSpectrumMixed,
571 }
572 }
573}
574
575impl LanePlan {
576 pub fn flops(&self) -> u128 {
577 match self {
578 LanePlan::Grouped { groups, bins } => groups
579 .iter()
580 .map(|g| transformed(g, *bins) + (g.summed.len() as u128 + 1) * g.samples as u128)
581 .sum(),
582 LanePlan::Sweep { evaluated, .. } => *evaluated,
583 }
584 }
585
586 fn chosen_by(&self, len: usize) -> u128 {
587 match self {
588 LanePlan::Grouped { groups, bins } => groups
589 .iter()
590 .map(|g| transformed(g, *bins) + (g.summed.len() as u128 + 1) * len as u128)
591 .sum(),
592 LanePlan::Sweep { atoms, samples, .. } => *atoms as u128 * *samples as u128,
593 }
594 }
595
596 fn swept_flops(&self, len: u128) -> u128 {
597 let terms: u128 = match self {
598 LanePlan::Grouped { groups, .. } => groups
599 .iter()
600 .map(|g| (g.placed.len() + g.summed.len() + 1) as u128)
601 .sum(),
602 LanePlan::Sweep { atoms, .. } => *atoms as u128,
603 };
604 terms * len
605 }
606}
607
608fn transformed(g: &Group, bins: usize) -> u128 {
609 match g.placed.is_empty() {
610 true => 0,
611 false => transform_flops(bins),
612 }
613}
614
615impl Plan {
616 pub fn nonzero(&self, rate: u32, extent: Extent) -> Extent {
619 let grid = Grid::of(rate);
620 let lane = |lane: &Lane, taken: &LanePlan| -> Option<Option<Window>> {
621 match taken {
622 LanePlan::Sweep { .. } if lane.modal.is_empty() => Some(active::hull(
623 &active::windows(lane, grid),
624 &super::span::absolute(lane, extent, rate),
625 )),
626 LanePlan::Grouped { groups, .. } if groups.iter().all(|g| g.placed.is_empty()) => {
627 Some(active::hull(
628 &groups.iter().map(|g| g.live).collect::<Vec<_>>(),
629 &[(extent.start, extent.end)],
630 ))
631 }
632 _ => None,
633 }
634 };
635 let found = match self {
636 Plan::Sampled(held) => held
637 .sum
638 .lanes
639 .iter()
640 .zip(&held.lanes)
641 .map(|(l, taken)| lane(l, taken))
642 .collect::<Option<Vec<_>>>()
643 .map(|lanes| lanes.into_iter().flatten().collect::<Vec<_>>()),
644 Plan::Added(parts) => Some(
645 parts
646 .iter()
647 .map(|part| part.nonzero(rate, extent))
648 .filter(|part| !part.is_empty())
649 .map(|part| (part.start, part.end))
650 .collect(),
651 ),
652 _ => None,
653 };
654 match found {
655 None => extent,
656 Some(spans) => spans
657 .into_iter()
658 .map(|(a, b)| Extent::new(a, b))
659 .fold(Extent::NOWHERE, Extent::hull)
660 .intersect(extent),
661 }
662 }
663
664 pub fn route(&self) -> Vec<u64> {
666 let lines = |held: &[Vec<Line>]| held.iter().map(|l| l.len() as u64).collect::<Vec<_>>();
667 match self {
668 Plan::Spectrum(_) => vec![0],
669 Plan::Lines(found) => [vec![1], lines(&found.placed), lines(&found.summed)].concat(),
670 Plan::Sampled(held) => {
671 let mut out = vec![2, held.rule as u64];
672 for lane in &held.lanes {
673 match lane {
674 LanePlan::Sweep { .. } => out.push(0),
675 LanePlan::Grouped { groups, .. } => {
676 out.push(1 + groups.len() as u64);
677 for g in groups {
678 out.extend([g.placed.len() as u64, g.summed.len() as u64]);
679 }
680 }
681 }
682 }
683 out
684 }
685 Plan::Point { .. } => vec![3],
686 Plan::Added(parts) => {
687 let mut out = vec![4, parts.len() as u64];
688 parts.iter().for_each(|part| out.extend(part.route()));
689 out
690 }
691 }
692 }
693
694 pub fn flops(&self, rate: u32, extent: Extent) -> u128 {
695 let len = extent.len();
696 match self {
697 Plan::Spectrum(_) => transform_flops(len),
698 Plan::Lines(found) => found.flops(len),
699 Plan::Sampled(held) => held.lanes.iter().map(|lane| lane.flops()).sum(),
700 Plan::Point { written, .. } => {
701 point_flops(&written.body, Grid::of(rate), (extent.start, extent.end))
702 }
703 Plan::Added(parts) => parts.iter().map(|part| part.flops(rate, extent)).sum(),
704 }
705 }
706
707 pub fn alias_flops(&self, rate: u32, extent: Extent) -> u128 {
708 self.alias_flops_at(rate, extent, super::ALIAS_OVERSAMPLE)
709 }
710
711 pub fn alias_flops_at(&self, rate: u32, extent: Extent, oversample: usize) -> u128 {
714 let reference = (extent.len() * oversample) as u128;
715 match self {
716 Plan::Point { written, .. } => {
717 let finer = oversample as i64;
718 point_flops(
719 &written.body,
720 Grid::finer(rate, oversample),
721 (extent.start * finer, extent.end * finer),
722 )
723 }
724 Plan::Sampled(held) if held.rule == Rule::PointSampled => held
725 .lanes
726 .iter()
727 .map(|lane| lane.swept_flops(reference))
728 .sum(),
729 Plan::Added(parts) => parts
730 .iter()
731 .map(|part| part.alias_flops_at(rate, extent, oversample))
732 .sum(),
733 Plan::Spectrum(_) | Plan::Lines(_) | Plan::Sampled(_) => 0,
734 }
735 }
736
737 pub fn rule(&self) -> Rule {
738 match self {
739 Plan::Spectrum(_) => Rule::InverseSpectrum,
740 Plan::Lines(found) => found.rule(),
741 Plan::Sampled(held) => held.rule,
742 Plan::Point { .. } => Rule::PointSampled,
743 Plan::Added(_) => Rule::Added,
744 }
745 }
746}