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