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