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