1use std::cell::RefCell;
4use std::collections::HashMap;
5use std::f64::consts::TAU;
6
7use sva_formula::affine::{Axis, axis_read, exact_constant_at, exact_constant_read};
8use sva_formula::closed_form::{Bound, Series, children, map_children};
9use sva_formula::series::{falls, mentions, mentions_line_read, ratio, substitute};
10use sva_formula::spectral_sum::atom::{Exp, Factors, Singular, SpectralAtom};
11use sva_formula::spectral_sum::merge::simplify;
12use sva_formula::spectral_sum::sup::{Fate, fate, sup_from};
13use sva_formula::table::series::{Shape, read_with};
14use sva_formula::through::{Read, looked};
15use sva_formula::{
16 Banded, Body, C64, Codomain, Env, IndexId, Lane, NodeId, Opaque, ParamId, Part, Reads, Run,
17 SpectralSum, Ty, Unary, Var, d_dt, leading_read, lines_read, normalize_read,
18};
19
20use crate::error::CollapseError;
21use crate::profile::Profile;
22
23#[derive(Clone, Copy, Debug, PartialEq)]
26pub struct Audible {
27 ceiling: f64,
28 precision: f64,
29 floor_db: f64,
30}
31
32impl Audible {
33 pub fn of(profile: &Profile, rate: u32) -> Audible {
34 Audible::on(profile, crate::Grid::of(rate))
35 }
36
37 pub fn on(profile: &Profile, grid: crate::Grid) -> Audible {
38 let ceiling = profile.ceiling_hz.min(grid.sr() / 2.0);
39 Audible {
40 ceiling,
41 precision: profile.half_lsb(),
42 floor_db: profile.floor(ceiling),
43 }
44 }
45
46 pub fn key(self) -> [u64; 3] {
47 [self.ceiling, self.precision, self.floor_db].map(f64::to_bits)
48 }
49
50 fn read_at(self, at: &Body, reads: &dyn Reads) -> Audible {
52 match sva_formula::calculus::steepest_read(at, reads) {
53 Some(rate) if rate > 1.0 => Audible {
54 ceiling: self.ceiling / rate,
55 ..self
56 },
57 _ => self,
58 }
59 }
60}
61
62const MAX_EXPANDED_TERMS: usize = 1 << 13;
65
66const MOST_PER_INSTANT: i64 = 1 << 10;
70
71pub fn spectral_sum(n: &SpectralSum, band: Audible) -> Result<SpectralSum, CollapseError> {
74 spectral_sum_read(n, band, &Opaque)
75}
76
77pub fn spectral_sum_read(
78 n: &SpectralSum,
79 band: Audible,
80 reads: &dyn Reads,
81) -> Result<SpectralSum, CollapseError> {
82 Terms::new(reads).spectral_sum(n, band)
83}
84
85pub fn steady_read(n: &SpectralSum, band: Audible, reads: &dyn Reads) -> Option<f64> {
88 Terms::new(reads).steady(n, band)
89}
90
91pub fn written(f: &Body, band: Audible) -> Result<Body, CollapseError> {
93 written_with(f, band, &Opaque, &mut |id, _| Ok(id))
94}
95
96pub fn written_with(
99 f: &Body,
100 band: Audible,
101 reads: &dyn Reads,
102 named: &mut dyn FnMut(NodeId, Audible) -> Result<NodeId, CollapseError>,
103) -> Result<Body, CollapseError> {
104 Terms::new(reads).written(f, band, named)
105}
106
107pub fn dropped_db(f: &Body) -> Option<f64> {
109 let own = match f {
110 Body::Banded(b) => Some(b.dropped_db),
111 _ => None,
112 };
113 let inner = children(f).into_iter().filter_map(|p| dropped_db(&p.body));
114 own.into_iter().chain(inner).reduce(f64::max)
115}
116
117pub(super) fn windowed_lines(
118 s: &Series,
119 band: Audible,
120 reads: &dyn Reads,
121) -> Option<(Vec<sva_formula::Line>, Option<sva_formula::Indicator>)> {
122 Terms::new(reads).windowed_lines(s, band)
123}
124
125struct Terms<'a> {
127 reads: &'a dyn Reads,
128 bounds: PerBand<Option<Bounded>>,
129 prices: PerBand<Option<Price>>,
130 series: RefCell<HashMap<NodeId, bool>>,
131 summed: PerBand<NodeId>,
132}
133
134type PerBand<T> = RefCell<HashMap<(NodeId, [u64; 3]), T>>;
135
136type Price = (usize, Option<usize>);
138
139struct Cost {
141 depth: usize,
142 terms: Option<usize>,
143 own: usize,
144}
145
146#[derive(Clone)]
149struct Bounded {
150 body: Body,
151 exact: bool,
152}
153
154impl<'a> Terms<'a> {
155 fn new(reads: &'a dyn Reads) -> Terms<'a> {
156 Terms {
157 reads,
158 bounds: RefCell::default(),
159 prices: RefCell::default(),
160 series: RefCell::default(),
161 summed: RefCell::default(),
162 }
163 }
164
165 fn holds_series(&self, id: NodeId) -> bool {
166 if let Some(held) = self.series.borrow().get(&id) {
167 return *held;
168 }
169 let form = self.reads.view(id, &Read::As);
170 let found = form.is_some_and(|form| self.reaches_series(&form));
171 self.series.borrow_mut().insert(id, found);
172 found
173 }
174
175 fn reaches_series(&self, f: &Body) -> bool {
176 match f {
177 Body::Series(_) => true,
178 Body::Node(id) => self.holds_series(*id),
179 other => children(other).iter().any(|p| self.reaches_series(&p.body)),
180 }
181 }
182
183 fn summed(&self, id: NodeId, band: Audible) -> Result<NodeId, CollapseError> {
185 if !self.holds_series(id) {
186 return Ok(id);
187 }
188 let key = (id, band.key());
189 if let Some(held) = self.summed.borrow().get(&key) {
190 return Ok(*held);
191 }
192 let form = self
193 .reads
194 .view(id, &Read::As)
195 .expect("a ref holding a series names a form");
196 let body = self.written(&form, band, &mut |n, b| self.summed(n, b))?;
197 let stand = self
198 .reads
199 .stand_for(body)
200 .expect("a reader handing out forms stands one in");
201 self.summed.borrow_mut().insert(key, stand);
202 Ok(stand)
203 }
204
205 fn spectral_sum(&self, n: &SpectralSum, band: Audible) -> Result<SpectralSum, CollapseError> {
206 if n.lanes.iter().all(|l| l.series.is_empty()) {
207 return Ok(n.clone());
208 }
209 let mut lanes = Vec::with_capacity(n.lanes.len());
210 for lane in &n.lanes {
211 let mut held = Lane {
212 series: Vec::new(),
213 ..lane.clone()
214 };
215 for s in &lane.series {
216 held.atoms.extend(self.atoms(s, band)?);
217 }
218 lanes.push(held);
219 }
220 Ok(SpectralSum::of(n.var, lanes))
221 }
222
223 fn written(
224 &self,
225 f: &Body,
226 band: Audible,
227 named: &mut dyn FnMut(NodeId, Audible) -> Result<NodeId, CollapseError>,
228 ) -> Result<Body, CollapseError> {
229 match f {
230 Body::Node(id) => return Ok(Body::Node(named(*id, band)?)),
231 Body::Series(s) => return self.expanded(s, band, named),
232 Body::Warp { at, of } => {
233 let moved = band.read_at(&at.body, self.reads);
234 return Ok(Body::Warp {
235 at: Part::new(at.origin, self.written(&at.body, band, named)?),
236 of: Part::new(of.origin, self.written(&of.body, moved, named)?),
237 });
238 }
239 _ => {}
240 }
241 let mut found = None;
242 let out = map_children(f, |p| match self.written(&p.body, band, named) {
243 Ok(body) => Part::new(p.origin, body),
244 Err(e) => {
245 found = Some(e);
246 p.clone()
247 }
248 });
249 match found {
250 Some(e) => Err(e),
251 None => Ok(out),
252 }
253 }
254
255 fn steady(&self, n: &SpectralSum, band: Audible) -> Option<f64> {
257 let in_band =
258 |a: &SpectralAtom| (a.exp.map_or(0.0, |e| e.omega) / TAU).abs() < band.ceiling;
259 let mut most = 0.0f64;
260 for lane in &n.lanes {
261 let steady = lane
262 .atoms
263 .iter()
264 .filter(|a| fate(a) == Fate::Steady && in_band(a));
265 most = steady.map(|a| a.c.abs()).fold(most, f64::max);
266 for s in &lane.series {
267 let (body, window) = sva_formula::crop_peeled(&looked(&s.term.body, self.reads));
268 let bare = Series {
269 term: Part::new(s.term.origin, body),
270 ..s.clone()
271 };
272 let Some(Shape::Lines(_)) = read_with(&bare.term.body, self.reads) else {
273 return None;
274 };
275 let held = (band.ceiling, band.floor_db, band.precision);
276 let first = leading_read(&bare, held, self.reads)?;
277 if window.is_none() {
278 most = first.iter().map(|l| l.amp.abs()).fold(most, f64::max);
279 }
280 }
281 }
282 (most > 0.0).then_some(most)
283 }
284
285 fn windowed_lines(
286 &self,
287 s: &Series,
288 band: Audible,
289 ) -> Option<(Vec<sva_formula::Line>, Option<sva_formula::Indicator>)> {
290 let (body, window) = sva_formula::crop_peeled(&looked(&s.term.body, self.reads));
291 let bare = Series {
292 term: Part::new(s.term.origin, body),
293 ..s.clone()
294 };
295 let taken = self.enumerated(&bare, band).ok()?;
296 (!taken.is_empty()).then_some((taken, window))
297 }
298
299 fn atoms(&self, s: &Series, band: Audible) -> Result<Vec<SpectralAtom>, CollapseError> {
300 if let Some((taken, window)) = self.windowed_lines(s, band) {
301 return Ok(taken
302 .into_iter()
303 .map(|l| {
304 SpectralAtom::new(
305 l.amp,
306 Factors {
307 exp: Some(Exp::at(0.0, TAU * l.hz)),
308 ind: window,
309 ..Factors::NONE
310 },
311 Singular::Regular,
312 s.term.origin,
313 )
314 })
315 .collect());
316 }
317 if self.carried(s).is_some() && self.precise(s, band).is_none() {
318 return Err(CollapseError::NotEvaluable(
319 "a series summed instant by instant",
320 ));
321 }
322 let body = self.expanded(s, band, &mut |id, b| self.summed(id, b))?;
323 let held = normalize_read(&body, Var::T, self.reads)
324 .map_err(|_| CollapseError::NotEvaluable("a series term"))?;
325 let mut atoms = Vec::new();
326 for lane in held.lanes {
327 let mut summed = Lane::of(lane.atoms);
328 for inner in &lane.series {
329 summed.atoms.extend(self.atoms(inner, band)?);
330 }
331 if !lane.series.is_empty() {
332 simplify(&mut summed);
333 }
334 atoms.extend(summed.atoms);
335 }
336 Ok(atoms)
337 }
338
339 fn expanded(
341 &self,
342 s: &Series,
343 band: Audible,
344 named: &mut dyn FnMut(NodeId, Audible) -> Result<NodeId, CollapseError>,
345 ) -> Result<Body, CollapseError> {
346 let taken = self.enumerated(s, band)?;
347 if !taken.is_empty() {
348 let runs = Run::of(&taken).into_iter();
349 return Ok(sum(runs.map(|r| Body::Run(Box::new(r))).collect()));
350 }
351 if self.precise(s, band).is_none()
352 && let Some(banded) = self.banded(s, band, named)?
353 {
354 return Ok(Body::Banded(Box::new(banded)));
355 }
356 let count = self.terms(s, band)?;
357 let mut parts = Vec::with_capacity(count);
358 for i in 0..count {
359 let form = self.term(&s.term.body, s.index, (s.lo + i as i64) as f64);
360 parts.push(self.written(&form, band, named)?);
361 }
362 Ok(sum(parts))
363 }
364
365 fn term(&self, f: &Body, k: IndexId, value: f64) -> Body {
368 match f {
369 Body::Index(i) if *i == k => Body::Const(C64::real(value)),
370 Body::Warp { at, of } if mentions(&at.body, k) => {
371 let time = self.term(&at.body, k, value);
372 let moved = match &*of.body {
373 Body::Node(id) => self.reads.moved(*id, &time),
374 _ => None,
375 };
376 match moved {
377 Some(stand) => Body::Node(stand),
378 None => Body::Warp {
379 at: Part::new(at.origin, time),
380 of: Part::new(of.origin, self.term(&of.body, k, value)),
381 },
382 }
383 }
384 other => map_children(other, |p| Part::new(p.origin, self.term(&p.body, k, value))),
385 }
386 }
387
388 fn enumerated(
390 &self,
391 s: &Series,
392 band: Audible,
393 ) -> Result<Vec<sva_formula::Line>, CollapseError> {
394 match read_with(&s.term.body, self.reads) {
395 Some(Shape::Lines(_)) => {
396 let held = (band.ceiling, band.floor_db, band.precision);
397 lines_read(s, held, self.reads)
398 .map(|l| l.taken)
399 .ok_or(CollapseError::NotEvaluable(
400 "a series whose tail no bound sums",
401 ))
402 }
403 _ => Ok(Vec::new()),
404 }
405 }
406
407 fn terms(&self, s: &Series, band: Audible) -> Result<usize, CollapseError> {
409 let cost = self.cost(s, band).ok_or(CollapseError::NotEvaluable(
410 "a series whose term count no coefficient bounds",
411 ))?;
412 if cost.depth == 1 && cost.own > MAX_EXPANDED_TERMS {
413 return Err(CollapseError::NotEvaluable(
414 "a series of more terms than one instant expands",
415 ));
416 }
417 match cost.terms {
418 Some(terms) if terms <= MAX_EXPANDED_TERMS => Ok(cost.own),
419 terms => Err(CollapseError::NestedSeries {
420 depth: cost.depth,
421 terms,
422 bound: MAX_EXPANDED_TERMS,
423 }),
424 }
425 }
426
427 fn cost(&self, s: &Series, band: Audible) -> Option<Cost> {
428 let own = self.counted(s, band)?;
429 let inside = substitute(&s.term.body, s.index, s.lo as f64);
430 let (depth, inner) = self.price(&inside, band)?;
431 Some(Cost {
432 depth: depth + 1,
433 terms: inner.and_then(|inner| own.checked_mul(inner)),
434 own,
435 })
436 }
437
438 fn price(&self, f: &Body, band: Audible) -> Option<Price> {
440 match f {
441 Body::Series(s) => {
442 let cost = self.cost(s, band)?;
443 return Some((cost.depth, cost.terms));
444 }
445 Body::Node(id) => return self.node_price(*id, band),
446 _ => {}
447 }
448 let summed = matches!(f, Body::Add(_));
449 let mut depth = 0;
450 let mut terms: Option<usize> = Some(if summed { 0 } else { 1 });
451 for p in children(f) {
452 let (d, n) = self.price(&p.body, band)?;
453 if d == 0 {
454 continue;
455 }
456 depth = depth.max(d);
457 terms = terms.zip(n).and_then(|(terms, n)| match summed {
458 true => terms.checked_add(n),
459 false => terms.checked_mul(n),
460 });
461 }
462 Some((depth, terms.map(|n| n.max(1))))
463 }
464
465 fn node_price(&self, id: NodeId, band: Audible) -> Option<Price> {
466 let key = (id, band.key());
467 if let Some(held) = self.prices.borrow().get(&key) {
468 return *held;
469 }
470 let found = match self.reads.view(id, &Read::As) {
471 Some(form) => self.price(&form, band),
472 None => Some((0, Some(1))),
473 };
474 self.prices.borrow_mut().insert(key, found);
475 found
476 }
477
478 fn counted(&self, s: &Series, band: Audible) -> Option<usize> {
481 if let Bound::Finite(n) = s.hi {
482 return usize::try_from((n - s.lo + 1).max(0)).ok();
483 }
484 let precise = self.precise(s, band);
485 precise.or_else(|| self.carried(s).map(|_| MOST_PER_INSTANT as usize))
486 }
487
488 fn precise(&self, s: &Series, band: Audible) -> Option<usize> {
490 let bound = self.bound(&s.term.body, band)?;
491 let ratio = ratio(&bound.body, s.index).filter(|r| *r < 1.0 && bound.exact)?;
492 for i in 0..MAX_EXPANDED_TERMS {
493 let at = (s.lo + i as i64) as f64;
494 let held = exact_constant_at(&bound.body, s.index, at, self.reads)?.abs();
495 if held / (1.0 - ratio) <= band.precision {
496 return Some(i.max(1));
497 }
498 }
499 None
500 }
501
502 fn carried<'s>(&self, s: &'s Series) -> Option<&'s Body> {
505 if s.hi != Bound::Infinite {
506 return None;
507 }
508 let angle = carrier(&s.term.body, s.index, self.reads)?;
509 (degree(angle, s.index)? == 1 && self.real(angle)).then_some(angle)
510 }
511
512 fn banded(
515 &self,
516 s: &Series,
517 band: Audible,
518 named: &mut dyn FnMut(NodeId, Audible) -> Result<NodeId, CollapseError>,
519 ) -> Result<Option<Banded>, CollapseError> {
520 let Some(angle) = self.carried(s) else {
521 return Ok(None);
522 };
523 let at = |k: f64| substitute(angle, s.index, k);
524 let (still, once) = (at(0.0), at(1.0));
525 let per = Body::Add(vec![
526 Part::bare(once),
527 Part::bare(Body::Mul(vec![
528 Part::bare(Body::Const(C64::real(-1.0))),
529 Part::bare(still.clone()),
530 ])),
531 ]);
532 let turning = |f: &Body| {
533 let sum = normalize_read(f, Var::T, self.reads).ok()?;
534 let [lane] = sum.lanes.as_slice() else {
535 return None;
536 };
537 (lane.series.is_empty() && lane.modal.is_empty()).then(|| d_dt(&sum))
538 };
539 let (Some(slope), Some(offset)) = (turning(&per), turning(&still)) else {
540 return Ok(None);
541 };
542 let term = self.written(&s.term.body, band, named)?;
543 let weight = steady(&s.term.body, angle);
544 let mut banded = Banded {
545 series: Series {
546 term: Part::new(s.term.origin, term),
547 ..s.clone()
548 },
549 slope,
550 offset,
551 omega: TAU * band.ceiling,
552 most: MOST_PER_INSTANT,
553 widest: MOST_PER_INSTANT,
554 reach: f64::INFINITY,
555 dropped_db: f64::INFINITY,
556 };
557 self.bounded(&mut banded, &weight, band);
558 match banded.dropped_db.is_finite() {
559 true => Ok(Some(banded)),
560 false => Err(CollapseError::NotEvaluable(
561 "a series whose dropped terms no bound holds",
562 )),
563 }
564 }
565
566 fn bounded(&self, b: &mut Banded, weight: &Body, band: Audible) {
568 let (lo, k) = (b.series.lo, b.series.index);
569 let (fastest, drift) = (sup(&b.slope), sup(&b.offset));
570 let (slowest, still) = (least(&b.slope), least(&b.offset).max(0.0));
571 let count = |last: f64| (last.ceil().clamp(-9e18, 9e18) as i64 - lo).clamp(0, b.most);
572 let kept = match fastest > 0.0 {
573 true => count((b.omega - drift) / fastest),
574 false if drift < b.omega => b.most,
575 false => 0,
576 };
577 b.widest = match slowest > 0.0 {
578 true => count((b.omega + drift.max(still)) / slowest),
579 false => b.most,
580 };
581 let Some(bound) = self.bound(weight, band).filter(|w| w.exact) else {
582 return;
583 };
584 let at = |n: i64| Some(exact_constant_at(&bound.body, k, n as f64, self.reads)?.abs());
585 b.reach = (lo..lo + b.widest)
586 .try_fold(0.0, |held, n| Some(held + at(n)?))
587 .unwrap_or(f64::INFINITY);
588 if lo >= 1
589 && falls(&bound.body, k)
590 && let (Some(top), Some(gone)) = (at(lo), at(lo + kept))
591 {
592 b.dropped_db = 20.0 * (gone / top).log10();
593 }
594 }
595
596 fn bound(&self, f: &Body, band: Audible) -> Option<Bounded> {
597 let held = |body: Body, exact: bool| Some(Bounded { body, exact });
598 let under = |p: &Part| {
599 self.bound(&p.body, band)
600 .map(|b| (Part::new(p.origin, b.body), b.exact))
601 };
602 let all = |parts: &[Part], wrap: &dyn Fn(Part) -> Part| {
603 let mut exact = true;
604 let mut kept = Vec::with_capacity(parts.len());
605 for p in parts {
606 let (part, known) = under(p)?;
607 exact &= known;
608 kept.push(wrap(part));
609 }
610 Some((kept, exact))
611 };
612 let mentions_line = |f: &Body| mentions_line_read(f, self.reads);
613 match f {
614 Body::Series(s) => match self.total(s, band) {
615 Some(sum) => held(Body::Const(C64::real(sum)), true),
616 None => held(Body::Const(C64::ONE), false),
617 },
618 Body::Node(id) => self.node_bound(*id, band),
619 Body::Line => held(Body::Const(C64::ONE), false),
620 Body::Mul(parts) => {
621 let (kept, exact) = all(parts, &|p| p)?;
622 held(Body::Mul(kept), exact)
623 }
624 Body::Add(parts) => {
625 let (kept, exact) = all(parts, &|p| Part::bare(Body::Apply(Unary::Abs, p)))?;
626 held(Body::Add(kept), exact)
627 }
628 Body::Div(a, b) if !mentions_line(&b.body) => {
629 let (num, exact) = under(a)?;
630 held(Body::Div(num, b.clone()), exact)
631 }
632 Body::Div(a, b) => {
633 let ((num, _), (den, _)) = (under(a)?, under(b)?);
634 held(Body::Div(num, den), false)
635 }
636 Body::Apply(Unary::Sin | Unary::Cos | Unary::Tanh | Unary::Sat | Unary::Step, arg) => {
637 held(Body::Const(C64::ONE), self.real(&arg.body))
638 }
639 Body::Crop { of, .. } | Body::Shift { of, .. } | Body::Warp { of, .. } => {
640 self.bound(&of.body, band)
641 }
642 Body::Pow(base, n) => {
643 let (part, exact) = under(base)?;
644 held(Body::Pow(part, *n), exact && *n >= 0)
645 }
646 other if !mentions_line(other) => held(other.clone(), true),
647 _ => None,
648 }
649 }
650
651 fn node_bound(&self, id: NodeId, band: Audible) -> Option<Bounded> {
653 let key = (id, band.key());
654 if let Some(held) = self.bounds.borrow().get(&key) {
655 return held.clone();
656 }
657 let found = match self.reads.view(id, &Read::As) {
658 Some(form) => self.bound(&form, band).and_then(|b| {
659 let value = exact_constant_read(&b.body, self.reads)?;
660 Some(Bounded {
661 body: Body::Const(value),
662 exact: b.exact,
663 })
664 }),
665 None => Some(Bounded {
666 body: Body::Const(C64::ONE),
667 exact: false,
668 }),
669 };
670 self.bounds.borrow_mut().insert(key, found.clone());
671 found
672 }
673
674 fn total(&self, s: &Series, band: Audible) -> Option<f64> {
676 let bound = self.bound(&s.term.body, band).filter(|b| b.exact)?.body;
677 let at = |i: i64| Some(exact_constant_at(&bound, s.index, i as f64, self.reads)?.abs());
678 if let (Bound::Infinite, Some(r)) = (s.hi, ratio(&bound, s.index).filter(|r| *r < 1.0)) {
679 return Some(at(s.lo)? / (1.0 - r));
680 }
681 let kept =
682 i64::try_from(self.counted(s, band).filter(|n| *n <= MAX_EXPANDED_TERMS)?).ok()?;
683 (s.lo..s.lo + kept).try_fold(0.0, |held, i| Some(held + at(i)?))
684 }
685
686 fn real(&self, f: &Body) -> bool {
687 axis_read(f, &Unread, self.reads) == Axis::Real
688 }
689}
690
691fn sum(parts: Vec<Body>) -> Body {
692 match parts.len() {
693 0 => Body::Const(C64::ZERO),
694 1 => parts.into_iter().next().expect("one part"),
695 _ => Body::Add(parts.into_iter().map(Part::bare).collect()),
696 }
697}
698
699fn carrier<'f>(f: &'f Body, k: IndexId, reads: &dyn Reads) -> Option<&'f Body> {
702 let (mut found, mut clean) = (Vec::new(), true);
703 moving(f, k, reads, (&mut found, &mut clean));
704 match found.as_slice() {
705 [angle] if clean => Some(*angle),
706 _ => None,
707 }
708}
709
710fn moving<'f>(
711 f: &'f Body,
712 k: IndexId,
713 reads: &dyn Reads,
714 (found, clean): (&mut Vec<&'f Body>, &mut bool),
715) {
716 if !mentions(f, k) {
717 return;
718 }
719 match f {
720 Body::Apply(Unary::Sin | Unary::Cos, arg) => found.push(&arg.body),
721 Body::Apply(_, arg) if mentions_line_read(&arg.body, reads) => *clean = false,
722 Body::Warp { .. }
723 | Body::Series(_)
724 | Body::Banded(_)
725 | Body::Keyed { .. }
726 | Body::Delta { .. }
727 | Body::Pv(_)
728 | Body::Deriv { .. } => *clean = false,
729 other => {
730 for p in children(other) {
731 moving(&p.body, k, reads, (&mut *found, &mut *clean));
732 }
733 }
734 }
735}
736
737fn degree(f: &Body, k: IndexId) -> Option<u32> {
739 if !mentions(f, k) {
740 return Some(0);
741 }
742 match f {
743 Body::Index(i) if *i == k => Some(1),
744 Body::Add(parts) => parts
745 .iter()
746 .try_fold(0, |held, p| Some(held.max(degree(&p.body, k)?))),
747 Body::Mul(parts) => parts
748 .iter()
749 .try_fold(0, |held, p| Some(held + degree(&p.body, k)?)),
750 Body::Div(num, den) if !mentions(&den.body, k) => degree(&num.body, k),
751 Body::Pow(base, n) if *n >= 0 => Some(degree(&base.body, k)? * n.unsigned_abs()),
752 _ => None,
753 }
754}
755
756fn steady(term: &Body, angle: &Body) -> Body {
758 match term {
759 Body::Apply(Unary::Sin | Unary::Cos, arg) if std::ptr::eq(&*arg.body, angle) => {
760 Body::Const(C64::ONE)
761 }
762 other => map_children(other, |p| Part::new(p.origin, steady(&p.body, angle))),
763 }
764}
765
766fn sup(f: &SpectralSum) -> f64 {
768 let atoms = f.lanes.iter().flat_map(|lane| lane.atoms.iter());
769 atoms
770 .map(|a| sup_from(a, f64::NEG_INFINITY).unwrap_or(f64::INFINITY))
771 .sum()
772}
773
774fn least(f: &SpectralSum) -> f64 {
776 let atoms = f.lanes.iter().flat_map(|lane| lane.atoms.iter());
777 let (still, moving): (Vec<_>, Vec<_>) = atoms.partition(|a| a.is_bare());
778 let held = still
779 .iter()
780 .fold(C64::ZERO, |held, a: &&SpectralAtom| held + a.c)
781 .abs();
782 let moves: f64 = moving
783 .iter()
784 .map(|a| sup_from(a, f64::NEG_INFINITY).unwrap_or(f64::INFINITY))
785 .sum();
786 (held - moves).max(0.0)
787}
788
789struct Unread;
790
791impl Env for Unread {
792 fn node(&self, _: NodeId) -> Ty {
793 Ty::form(Var::T, false, Codomain::Complex)
794 }
795
796 fn param(&self, _: ParamId) -> Ty {
797 Ty::form(Var::T, false, Codomain::Complex)
798 }
799}