Skip to main content

sva_samples/collapse/
plan.rs

1// Concern: which row of the collapse table a form takes, what it costs and its direct sums' bounds | Non-concern: running the row | IO: (&SpectralSum, rate, Extent) -> Plan, flops, bounds
2
3use 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
17/// Both routes place a line exactly, so cost decides; the direct one hands a lone line back
18/// unchanged.
19pub 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
31/// FORMAT 9.2's window route. Outside `live` the factor zeroes a sum proven finite.
32pub 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
40/// `atoms` times `samples` chose the route before a sweep skipped dead atoms, so the choice
41/// keeps its bits; `evaluated` is what it now runs.
42pub 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
60/// The row of FORMAT 9.1 that runs, decided once for the collapse and the count alike.
61pub enum Plan {
62    Spectrum(Box<SpectralSum>),
63    Lines(Box<LinePlan>),
64    Sampled(Box<Sampled>),
65    /// Row 4 over the written closed form: every node the truncation leaves, at each instant
66    /// it is walked.
67    Point {
68        written: Box<ClosedForm>,
69        width: usize,
70    },
71    /// A sum whose addends name different rows, each taking its own.
72    Added(Vec<Plan>),
73}
74
75/// The rows in FORMAT 9.1's order; the first guard that holds decides.
76pub 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
117/// The row a closed form with no spectral sum takes: one per addend where they differ, else 4.
118pub 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    // Addends that all name row 4 are one row 4 over the whole sum, measured once.
136    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
142/// A row the addend alone cannot take refuses nothing; the written row still stands for it.
143/// A nesting past the bound is the one exception, refused wherever it is written.
144fn 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
191/// Each of these is linear, so over a sum it is the sum of itself over every addend.
192fn 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
214/// The nodes every component's evaluation walks over the samples `[from, to)` of `grid`.
215pub 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
220/// One component's `(nodes, waves)` over the samples `[from, to)` of `grid`, as
221/// `point::eval_body` walks them: a run is priced by its Horner steps and turns its lines,
222/// every other node is one, and a crop's operand counts only at the instants its window holds.
223pub 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
238/// A written sum's addends in order, a left-nested `a + b + c` as one list: `point::eval_body`
239/// folds either from +0 to the same bits, as no partial sum from +0 is -0.
240pub(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
258/// Outside its own window an addend is exact zero.
259pub(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
270/// A crop's open window, met through products and shifts, as `point::eval_body` zeroes them.
271fn 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/// The instant a subterm is read at, from the grid's index: `None` once a warp moves it.
283#[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    /// The terms a banded series sums over the span, each instant's own count; where a warp
308    /// moves the instants, the most any instant sums.
309    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    /// Where a crop's gain is not zero: inside `[l, r)` less the instants a shoulder opens at.
335    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        // `point::product` walks no factor past one a shut crop zeroed.
388        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
408/// Each direct sum a row may take over this form's lines, as its rounding bound and the
409/// factor it is read under: none on a line row, the group's own on a windowed one.
410pub 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
437/// Rows one and two: every atom a line, none of them windowed.
438fn 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
465/// Each lane's lines under the ceiling and over it, before any extent places them.
466pub(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    // A form with no line at all is silence, not a band every line sat above.
513    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
569/// Outside it the factor zeroes its lines' sum, where that sum, `bins` times over, is finite.
570pub(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
583/// `n*log2(n)` butterflies; a length that is no power of two is Bluestein's three
584/// transforms over the next one past `2n-1`.
585pub 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    /// Outside it every sample is +0.0 and inside each is its own instant's, so a run over it
660    /// alone holds the same bits; a row reading the whole extent at once answers all of it.
661    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    /// What two extents cut to one may still differ in.
708    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    /// The multiple a reading names need not be the label's `ALIAS_OVERSAMPLE`. Every component
755    /// is scored, so every component's reference is paid for.
756    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}