1#![cfg_attr(not(test), no_std)]
48#![cfg_attr(docsrs, feature(doc_cfg))]
49#![warn(clippy::cargo)]
50#![warn(clippy::complexity)]
51#![warn(clippy::correctness)]
52#![warn(clippy::perf)]
53#![warn(clippy::style)]
54#![warn(clippy::suspicious)]
55#![warn(missing_docs)]
56#![warn(missing_copy_implementations)]
57#![warn(missing_debug_implementations)]
58#![warn(clippy::missing_panics_doc)]
59#![warn(clippy::missing_const_for_fn)]
60#![allow(clippy::unused_unit)]
62
63extern crate alloc;
64
65use core::cmp::Ordering;
66
67pub mod generators;
68pub mod modifiers;
69pub mod pdf;
70pub mod reconstruction;
71mod schedule;
72#[cfg(feature = "terminal-viz")]
73#[cfg_attr(docsrs, doc(cfg(feature = "terminal-viz")))]
74pub mod terminal_viz;
75
76use rand::Rng;
77use rand_chacha::ChaCha12Rng;
78use rustfft::num_complex::{Complex, ComplexFloat};
79pub use schedule::*;
80
81pub use rustfft;
82
83pub use ndarray;
84
85#[derive(Clone, Copy, Debug, Default)]
95pub enum DisplayMode {
96 #[default]
98 Abs,
99 RealPart,
101}
102
103impl DisplayMode {
104 pub fn magnitude(self, complex: Complex<f64>) -> f64 {
106 match self {
107 DisplayMode::Abs => complex.abs(),
108 DisplayMode::RealPart => complex.re().abs(),
109 }
110 }
111
112 pub fn threshold<T: ComplexSequence + ?Sized>(self, sequence: &mut T, threshold: f64) {
114 sequence.apply(|v| {
115 let mag = self.magnitude(v);
116 if mag > threshold {
117 v * (mag - threshold) / mag
118 } else {
119 Complex::new(0., 0.)
120 }
121 })
122 }
123
124 pub fn maybe_real_part<T: ComplexSequence + ?Sized>(self, sequence: &mut T) {
126 if let Self::RealPart = self {
127 sequence.apply(|v| Complex::new(v.re(), 0.));
128 }
129 }
130}
131
132fn partition<T>(rng: &mut ChaCha12Rng, slice: &mut [T], by: &impl Fn(&T, &T) -> Ordering) -> usize {
133 slice.swap(0, rng.random_range(0..slice.len()));
134
135 let mut i = 1;
136 let mut j = slice.len() - 1;
137
138 loop {
139 while i < slice.len() && !matches!(by(&slice[i], &slice[0]), Ordering::Less) {
140 i += 1;
141 }
142
143 while matches!(by(&slice[j], &slice[0]), Ordering::Less) {
144 j -= 1;
145 }
146
147 if i > j {
149 slice.swap(0, j);
150 return j;
151 }
152
153 slice.swap(i, j);
155 i += 1;
156 }
157}
158
159pub(crate) fn quickselect<T>(
164 rng: &mut ChaCha12Rng,
165 mut slice: &mut [T],
166 by: impl Fn(&T, &T) -> Ordering,
167 mut find_spot: usize,
168) {
169 loop {
170 let len = slice.len();
171
172 if len < 2 {
173 return;
174 }
175
176 let spot_found = partition(rng, slice, &by);
177
178 match find_spot.cmp(&spot_found) {
179 Ordering::Less => slice = &mut slice[0..spot_found],
180 Ordering::Equal => return,
181 Ordering::Greater => {
182 slice = &mut slice[spot_found + 1..len];
183 find_spot = find_spot - spot_found - 1;
184 }
185 }
186 }
187}
188
189pub trait ComplexSequence {
191 fn apply(&mut self, f: impl FnMut(Complex<f64>) -> Complex<f64>);
193
194 fn apply_into<T>(&self, out: &mut [T], f: impl FnMut(Complex<f64>) -> T);
200
201 fn phase(&mut self, θ: f64) {
203 let rotator = Complex::new(0., θ).exp();
204 self.apply(|v| v * rotator);
205 }
206
207 fn re(&self, out: &mut [f64]) {
213 self.apply_into(out, |v| v.re());
214 }
215}
216
217impl ComplexSequence for [Complex<f64>] {
218 fn apply(&mut self, mut f: impl FnMut(Complex<f64>) -> Complex<f64>) {
219 for v in self {
220 *v = f(*v);
221 }
222 }
223
224 fn apply_into<T>(&self, out: &mut [T], mut f: impl FnMut(Complex<f64>) -> T) {
225 assert_eq!(self.len(), out.len());
226
227 out.iter_mut()
228 .zip(self.iter())
229 .for_each(|(out_v, complex)| *out_v = f(*complex));
230 }
231}
232
233impl<V: AsMut<[Complex<f64>]> + AsRef<[Complex<f64>]>> ComplexSequence for V {
234 fn apply(&mut self, f: impl FnMut(Complex<f64>) -> Complex<f64>) {
235 self.as_mut().apply(f);
236 }
237
238 fn apply_into<T>(&self, out: &mut [T], f: impl FnMut(Complex<f64>) -> T) {
239 self.as_ref().apply_into(out, f);
240 }
241}
242
243#[derive(Clone, Copy, Debug)]
245enum Monotonicity {
246 Increasing,
248 Decreasing,
250}
251
252#[derive(Clone, Copy, Debug)]
254struct InitialState {
255 start: f64,
256 min: f64,
257 max: f64,
258}
259
260impl InitialState {
261 pub const fn new(start: f64, min: f64, max: f64) -> Self {
265 Self { start, min, max }
266 }
267}
268
269type BinsearchPoint<T> = (f64, (f64, T));
270
271enum Precision<'a, T> {
273 #[allow(dead_code)]
274 Preimage(f64),
276 Image {
277 amount: f64,
278 debug: &'a dyn Fn(BinsearchPoint<T>, BinsearchPoint<T>),
280 },
281}
282
283fn find_zero<T>(
294 monotonicity: Monotonicity,
295 initial_state: InitialState,
296 precision: Precision<'_, T>,
297 f: impl Fn(f64) -> (f64, T),
298) -> (f64, T) {
299 let mut min = initial_state.min;
300 let mut current = initial_state.start;
301 let mut max = initial_state.max;
302
303 loop {
304 let (v, ret) = f(current);
305
306 if let Precision::Image { amount, debug: _ } = precision {
307 if v.abs() < amount {
308 return (current, ret);
309 }
310 }
311
312 match (v.total_cmp(&0.), monotonicity) {
313 (Ordering::Less, Monotonicity::Increasing)
314 | (Ordering::Greater, Monotonicity::Decreasing) => min = current,
315 (Ordering::Equal, _) => return (current, ret),
316 (Ordering::Greater, Monotonicity::Increasing)
317 | (Ordering::Less, Monotonicity::Decreasing) => max = current,
318 }
319
320 if max != f64::INFINITY {
321 let new_current = min / 2. + max / 2.;
322
323 match precision {
324 Precision::Preimage(amount) => {
325 if max - min < amount || new_current == current {
326 return (current, ret);
327 }
328 }
329 Precision::Image { amount: _, debug } => {
330 if new_current == current {
331 debug((min, f(min)), (max, f(max)));
332 return (current, ret);
333 }
334 }
335 }
336
337 current = new_current;
338 } else {
339 current *= 2.;
340 }
341 }
342}
343
344#[cfg(test)]
345mod tests {
346 use alloc::borrow::ToOwned;
347 use core::cell::OnceCell;
348 use rand_chacha::ChaCha12Rng;
349
350 use alloc::vec::Vec;
351 use rand::{Rng, SeedableRng};
352 use rustfft::num_complex::{Complex, ComplexFloat};
353
354 use crate::{DisplayMode, InitialState, Monotonicity, find_zero, quickselect};
355
356 #[test]
357 fn test_display_mode() {
358 assert_eq!(DisplayMode::Abs.magnitude(Complex::new(4., 3.)), 5.);
359 assert_eq!(DisplayMode::RealPart.magnitude(Complex::new(4., 3.)), 4.);
360
361 let mut seq = [Complex::new(6., 8.), Complex::new(1., 1.)];
362
363 DisplayMode::Abs.threshold(&mut seq[..], 5.);
364 DisplayMode::Abs.maybe_real_part(&mut seq[..]);
365 assert!((seq[0] - Complex::new(3., 4.)).abs() < 0.000000001);
366 assert_eq!(seq[1], Complex::ZERO);
367
368 seq = [Complex::new(2., 9.), Complex::new(0.5, 1000.)];
369
370 DisplayMode::RealPart.threshold(&mut seq[..], 1.);
371
372 assert!((seq[0] - Complex::new(1., 4.5)).abs() < 0.000000001);
373 assert_eq!(seq[1], Complex::ZERO);
374
375 DisplayMode::RealPart.maybe_real_part(&mut seq[..]);
376 assert!((seq[0] - Complex::new(1., 0.)).abs() < 0.000000001);
377
378 let mut rng = ChaCha12Rng::from_seed(*b"Not all plants spread seeds, and");
379
380 for mode in [DisplayMode::Abs, DisplayMode::RealPart] {
381 let seq = (0..1000)
382 .map(|_| Complex::new(rng.random::<f64>() - 0.5, rng.random::<f64>() - 0.5) * 128.)
383 .collect::<Vec<_>>();
384
385 let mut seq_new = seq.to_owned();
386
387 mode.threshold(&mut *seq_new, 32.);
388
389 seq.iter().zip(seq_new.iter()).for_each(|(before, after)| {
390 if mode.magnitude(*before) < 32. {
391 assert_eq!(*after, Complex::ZERO);
392 } else {
393 assert!(
394 (mode.magnitude(*before) - mode.magnitude(*after) - 32.).abs() < 0.00000001,
395 "{before} {after} {mode:?}"
396 );
397
398 assert!(
399 (before.arg() - after.arg()).abs() < 0.00000001,
400 "{before} {after} {mode:?}"
401 );
402
403 match mode {
404 DisplayMode::Abs => {}
405 DisplayMode::RealPart => {
406 assert_eq!(
407 before.re().signum(),
408 after.re().signum(),
409 "{before} {after} {mode:?}"
410 );
411 }
412 }
413 }
414 });
415
416 let mut seq_maybe_re = seq_new.to_owned();
417
418 mode.maybe_real_part(&mut *seq_maybe_re);
419
420 seq_new
421 .iter()
422 .zip(seq_maybe_re.iter())
423 .for_each(|(before, after)| match mode {
424 DisplayMode::Abs => assert_eq!(before, after),
425 DisplayMode::RealPart => {
426 assert_eq!(after.im(), 0.);
427 }
428 });
429 }
430 }
431
432 #[test]
433 fn test_binsearch() {
434 let v = find_zero(
435 Monotonicity::Increasing,
436 InitialState {
437 start: 1.,
438 min: 0.,
439 max: f64::INFINITY,
440 },
441 crate::Precision::Preimage(0.1),
442 |v| (-5. + v, ()),
443 )
444 .0;
445
446 assert!(v > 5. - 0.1 && v < 5. + 0.1);
447
448 let v = find_zero(
449 Monotonicity::Decreasing,
450 InitialState {
451 start: 1.,
452 min: 0.,
453 max: f64::INFINITY,
454 },
455 crate::Precision::Preimage(0.1),
456 |v| (4. - 0.1 * v, ()),
457 )
458 .0;
459
460 assert!(v > 40. - 0.1 && v < 40. + 0.1, "v = {v}");
461
462 let v = find_zero(
463 Monotonicity::Increasing,
464 InitialState {
465 start: 1.,
466 min: 0.,
467 max: f64::INFINITY,
468 },
469 crate::Precision::Image {
470 amount: 0.1,
471 debug: (&|_, _| -> () { panic!("Debug should not have been called!") })
472 as &dyn Fn(_, _),
473 },
474 |v| (-3. + 10. * v, ()),
475 )
476 .0;
477
478 assert!(v > 0.3 - 0.01 && v < 0.3 + 0.01, "v = {v}");
479
480 let v = find_zero(
481 Monotonicity::Decreasing,
482 InitialState {
483 start: 1.,
484 min: 0.,
485 max: f64::INFINITY,
486 },
487 crate::Precision::Image {
488 amount: 0.1,
489 debug: (&|_, _| -> () { panic!("Debug should not have been called!") })
490 as &dyn Fn(_, _),
491 },
492 |v| (5. - 5. * v, ()),
493 )
494 .0;
495
496 assert!(v > 1. - 0.02 && v < 1. + 0.02, "v = {v}");
497
498 let debug_called = OnceCell::new();
499
500 find_zero(
501 Monotonicity::Decreasing,
502 InitialState {
503 start: 1.,
504 min: 0.,
505 max: f64::INFINITY,
506 },
507 crate::Precision::Image {
508 amount: 0.1,
509 debug: (&|_, _| {
510 debug_called.set(true).unwrap();
511 }) as &dyn Fn(_, _),
512 },
513 |v| (if v > 5. { 1. } else { -1. }, ()),
514 );
515
516 assert!(debug_called.get().is_some());
517 }
518
519 #[test]
520 fn test_quickselect() {
521 fn verify(rng: &mut ChaCha12Rng, pos: usize, slice: &[f64]) {
522 let mut slice = slice
523 .iter()
524 .enumerate()
525 .map(|(a, b)| (*b, a))
526 .collect::<Vec<_>>();
527
528 quickselect(rng, &mut slice, |a, b| a.0.total_cmp(&b.0), pos);
529
530 for i in 0..pos {
531 assert!(
532 slice[i].0 >= slice[pos].0,
533 "Pos: {pos}, Index: {i} - {slice:?}"
534 );
535 }
536
537 for i in pos + 1..slice.len() {
538 assert!(
539 slice[i].0 <= slice[pos].0,
540 "Pos: {pos}, Index: {i} - {slice:?}"
541 );
542 }
543
544 let v = slice[pos];
545
546 slice.sort_by(|a, b| b.0.total_cmp(&a.0));
547
548 assert_eq!(slice[pos].0, v.0);
549 }
550
551 let mut rng = ChaCha12Rng::from_seed(*b"Not all seeds plant plants, some");
552
553 verify(&mut rng, 2, &[5., 4., 3., 2., 1.]);
554 verify(&mut rng, 2, &[1., 2., 3., 4., 5.]);
555 verify(&mut rng, 3, &[1., 2., 1., 4., 3.]);
556
557 for i in 0..100 {
558 let pos = rng.random_range(0..i + 1);
559 let data = (0..i + 1).map(|_| rng.random()).collect::<Vec<_>>();
560 verify(&mut rng, pos, &data);
561 }
562 }
563}