1mod atoms;
4mod blocks;
5mod inverse;
6mod lines;
7pub mod plan;
8mod point;
9mod reading;
10mod span;
11mod sum;
12mod tail;
13mod truncate;
14
15use sva_formula::{Body, C64, ClosedForm, SpectralSum, Var, normalize_closed_form};
16
17use crate::buffer::Buffer;
18use crate::error::CollapseError;
19use crate::label::{Detail, Label, Rule, Source};
20use crate::profile::Profile;
21
22pub use blocks::Rows;
23pub use plan::transform_flops;
24
25use plan::Plan;
26pub use point::{Refs, crop_gain, lane_of, unary};
27pub use truncate::{Audible, spectral_sum as truncate_spectral_sum, written as truncate_written};
28
29pub fn eval_spectral_sum_at(
31 sum: &SpectralSum,
32 component: usize,
33 t: f64,
34) -> Result<C64, CollapseError> {
35 point::eval_spectral_sum(sum, component, t)
36}
37
38pub fn eval_written_at(
41 body: &Body,
42 component: usize,
43 t: f64,
44 refs: &dyn Refs,
45) -> Result<C64, CollapseError> {
46 point::eval_body(body, component, t, refs)
47}
48
49#[derive(Clone, Copy, Debug, PartialEq)]
50pub struct Horizon {
51 pub start_secs: f64,
52 pub end_secs: f64,
53}
54
55impl Horizon {
56 pub fn secs(start_secs: f64, end_secs: f64) -> Horizon {
57 Horizon {
58 start_secs,
59 end_secs,
60 }
61 }
62
63 pub fn span(&self) -> f64 {
64 self.end_secs - self.start_secs
65 }
66
67 pub fn len(&self, rate: u32) -> Result<usize, CollapseError> {
70 let span = self.span();
71 if !span.is_finite() || span <= 0.0 {
72 return Err(CollapseError::NoHorizon);
73 }
74 Ok(((span * f64::from(rate)).round() as usize).max(1))
75 }
76}
77
78pub const ALIAS_OVERSAMPLE: usize = 4;
80
81#[derive(Clone, Copy, Debug, PartialEq, Eq)]
84pub enum AliasScore {
85 Asked,
86 NotAsked,
87}
88
89pub fn render(
91 form: &ClosedForm,
92 rate: u32,
93 horizon: Horizon,
94 profile: &Profile,
95 score: AliasScore,
96) -> Result<(Buffer, Label), CollapseError> {
97 match normalize_closed_form(form) {
98 Ok(sum) => of_spectral_sum_or_point(&sum, Some(form), rate, horizon, profile, score),
99 Err(_) if form.var == Var::T => render_written(form, rate, horizon, profile, score),
100 Err(left) => Err(CollapseError::LeftAlgebra(left.reason.clause())),
101 }
102}
103
104pub fn render_written(
106 form: &ClosedForm,
107 rate: u32,
108 horizon: Horizon,
109 profile: &Profile,
110 score: AliasScore,
111) -> Result<(Buffer, Label), CollapseError> {
112 let len = horizon.len(rate)?;
113 run(
114 plan::of_written(form, rate, horizon, profile, len)?,
115 rate,
116 horizon,
117 profile,
118 len,
119 score,
120 )
121}
122
123fn point_row(
125 written: &ClosedForm,
126 width: usize,
127 rate: u32,
128 horizon: Horizon,
129 profile: &Profile,
130 len: usize,
131 score: AliasScore,
132) -> Result<(Buffer, Label), CollapseError> {
133 let planes = (0..width)
134 .map(|c| reading::sampled_body(written, c, rate, horizon, len, 1.0))
135 .collect::<Result<Vec<_>, _>>()?;
136 let alias_db = match score {
137 AliasScore::Asked => Some(formula_alias(written, rate, horizon, len, &planes)?),
138 AliasScore::NotAsked => None,
139 };
140 Ok(measured(
141 planes,
142 rate,
143 horizon,
144 profile,
145 Detail::Point {
146 rule: Rule::PointSampled,
147 alias_db,
148 },
149 ))
150}
151
152pub fn of_spectral_sum_or_point(
155 sum: &SpectralSum,
156 written: Option<&ClosedForm>,
157 rate: u32,
158 horizon: Horizon,
159 profile: &Profile,
160 score: AliasScore,
161) -> Result<(Buffer, Label), CollapseError> {
162 match of_spectral_sum(sum, rate, horizon, profile, score) {
163 Err(e) if reaches_no_atom(&e) => match written.filter(|t| t.var == Var::T) {
164 Some(form) => render_written(form, rate, horizon, profile, score),
165 None => Err(e),
166 },
167 other => other,
168 }
169}
170
171pub(crate) fn reaches_no_atom(e: &CollapseError) -> bool {
174 matches!(e, CollapseError::NotEvaluable(_))
175}
176
177pub fn of_spectral_sum(
179 sum: &SpectralSum,
180 rate: u32,
181 horizon: Horizon,
182 profile: &Profile,
183 score: AliasScore,
184) -> Result<(Buffer, Label), CollapseError> {
185 let len = horizon.len(rate)?;
186 of_sum(sum, rate, horizon, profile, len, score)
187}
188
189fn of_sum(
191 sum: &SpectralSum,
192 rate: u32,
193 horizon: Horizon,
194 profile: &Profile,
195 len: usize,
196 score: AliasScore,
197) -> Result<(Buffer, Label), CollapseError> {
198 run(
199 plan::of(sum, rate, horizon, profile, len)?,
200 rate,
201 horizon,
202 profile,
203 len,
204 score,
205 )
206}
207
208fn run(
209 plan: Plan,
210 rate: u32,
211 horizon: Horizon,
212 profile: &Profile,
213 len: usize,
214 score: AliasScore,
215) -> Result<(Buffer, Label), CollapseError> {
216 match plan {
217 Plan::Spectrum(sum) => spectrum_row(&sum, rate, horizon, profile, len),
218 Plan::Lines(found) => Ok(line_row(&found, rate, horizon, profile, len)),
219 Plan::Sampled(held) => sampled_row(&held, rate, horizon, profile, len, score),
220 Plan::Point { written, width, .. } => {
221 point_row(&written, width, rate, horizon, profile, len, score)
222 }
223 Plan::Added(parts) => sum::added(parts, rate, horizon, profile, len, score),
224 }
225}
226
227fn line_row(
228 found: &plan::LinePlan,
229 rate: u32,
230 horizon: Horizon,
231 profile: &Profile,
232 len: usize,
233) -> (Buffer, Label) {
234 let planes: Vec<Vec<f64>> = found
235 .placed
236 .iter()
237 .zip(&found.summed)
238 .map(|(placed, summed)| {
239 let mut plane = lines::transformed(placed, horizon.start_secs, found.bins, rate, len);
240 lines::add_direct(&mut plane, summed, horizon.start_secs, rate);
241 plane
242 })
243 .collect();
244 let (list, more) = lines::dropped_list(&found.dropped);
245 let (placed, summed) = (
246 lines::distinct(&found.placed),
247 lines::distinct(&found.summed),
248 );
249 let detail = Detail::Lines {
250 rule: found.rule(),
251 placed,
252 summed,
253 dropped: list,
254 dropped_more: more,
255 terms: Some(placed + summed),
256 tail_db: found.tail_db,
257 };
258 exact(planes, rate, horizon, profile, detail)
259}
260
261fn sampled_row(
262 held: &plan::Sampled,
263 rate: u32,
264 horizon: Horizon,
265 profile: &Profile,
266 len: usize,
267 score: AliasScore,
268) -> Result<(Buffer, Label), CollapseError> {
269 let (sum, rule) = (&held.sum, held.rule);
270 let planes = reading::sampled_spectral_sum(sum, &held.lanes, rate, horizon, len)?;
271 match rule {
272 Rule::BandLimited => Ok(exact(
273 planes,
274 rate,
275 horizon,
276 profile,
277 Detail::Continuous { rule },
278 )),
279 Rule::CroppedPair => Ok(measured(
280 planes,
281 rate,
282 horizon,
283 profile,
284 Detail::Cropped {
285 rule,
286 tail_db: tail::tail_db(sum, profile.ceiling(rate)),
287 },
288 )),
289 _ => {
290 let alias_db = match score {
291 AliasScore::Asked => Some(spectral_sum_alias(sum, rate, horizon, len, &planes)?),
292 AliasScore::NotAsked => None,
293 };
294 Ok(measured(
295 planes,
296 rate,
297 horizon,
298 profile,
299 Detail::Point { rule, alias_db },
300 ))
301 }
302 }
303}
304
305fn spectrum_row(
306 sum: &SpectralSum,
307 rate: u32,
308 horizon: Horizon,
309 profile: &Profile,
310 len: usize,
311) -> Result<(Buffer, Label), CollapseError> {
312 let mut planes = Vec::with_capacity(sum.lanes.len());
313 for c in 0..sum.lanes.len() {
314 planes.push(inverse::collapse_lane(
315 sum,
316 c,
317 horizon.start_secs,
318 horizon.span(),
319 rate,
320 len,
321 )?);
322 }
323 let wrap_db = inverse::wrap_db(sum, horizon.span(), rate)?;
324 Ok(measured(
325 planes,
326 rate,
327 horizon,
328 profile,
329 Detail::Spectrum {
330 rule: Rule::InverseSpectrum,
331 wrap_db,
332 },
333 ))
334}
335
336fn formula_alias(
338 form: &ClosedForm,
339 rate: u32,
340 horizon: Horizon,
341 len: usize,
342 planes: &[Vec<f64>],
343) -> Result<f64, CollapseError> {
344 let mut scored = Vec::with_capacity(planes.len());
345 for (c, base) in planes.iter().enumerate() {
346 let high = reading::sampled_body(
347 form,
348 c,
349 rate,
350 horizon,
351 len * ALIAS_OVERSAMPLE,
352 ALIAS_OVERSAMPLE as f64,
353 )?;
354 scored.push(one_component(base, &high, rate, horizon));
355 }
356 Ok(worst_of(scored))
357}
358
359fn spectral_sum_alias(
360 sum: &SpectralSum,
361 rate: u32,
362 horizon: Horizon,
363 len: usize,
364 planes: &[Vec<f64>],
365) -> Result<f64, CollapseError> {
366 let step = 1.0 / (f64::from(rate) * ALIAS_OVERSAMPLE as f64);
367 let mut scored = Vec::with_capacity(planes.len());
368 for (c, base) in planes.iter().enumerate() {
369 let high: Vec<f64> = (0..len * ALIAS_OVERSAMPLE)
370 .map(|i| {
371 point::eval_spectral_sum(sum, c, horizon.start_secs + i as f64 * step).map(|v| v.re)
372 })
373 .collect::<Result<_, _>>()?;
374 scored.push(one_component(base, &high, rate, horizon));
375 }
376 Ok(worst_of(scored))
377}
378
379fn one_component(
380 base: &[f64],
381 high: &[f64],
382 rate: u32,
383 horizon: Horizon,
384) -> crate::measure::alias::Alias {
385 crate::measure::alias::measure_alias(
386 base,
387 high,
388 ALIAS_OVERSAMPLE,
389 f64::from(rate),
390 horizon.start_secs,
391 )
392}
393
394fn worst_of(scored: Vec<crate::measure::alias::Alias>) -> f64 {
396 crate::measure::alias::worst(scored).map_or(f64::NEG_INFINITY, |held| held.asr_db)
397}
398
399fn exact(
400 planes: Vec<Vec<f64>>,
401 rate: u32,
402 horizon: Horizon,
403 profile: &Profile,
404 detail: Detail,
405) -> (Buffer, Label) {
406 labelled(Source::Exact, planes, rate, horizon, profile, detail)
407}
408
409fn measured(
410 planes: Vec<Vec<f64>>,
411 rate: u32,
412 horizon: Horizon,
413 profile: &Profile,
414 detail: Detail,
415) -> (Buffer, Label) {
416 labelled(Source::Measured, planes, rate, horizon, profile, detail)
417}
418
419fn labelled(
420 source: Source,
421 planes: Vec<Vec<f64>>,
422 rate: u32,
423 horizon: Horizon,
424 profile: &Profile,
425 detail: Detail,
426) -> (Buffer, Label) {
427 let mut buffer = Buffer::of_planes(rate, planes);
428 buffer.origin_secs = horizon.start_secs;
429 (buffer, Label::new(source, profile.name, rate, detail))
430}