#![cfg_attr(not(test), no_std)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![warn(clippy::cargo)]
#![warn(clippy::complexity)]
#![warn(clippy::correctness)]
#![warn(clippy::perf)]
#![warn(clippy::style)]
#![warn(clippy::suspicious)]
#![warn(missing_docs)]
#![warn(missing_copy_implementations)]
#![warn(missing_debug_implementations)]
#![warn(clippy::missing_panics_doc)]
#![warn(clippy::missing_const_for_fn)]
#![allow(clippy::unused_unit)]
extern crate alloc;
use core::cmp::Ordering;
pub mod generators;
pub mod modifiers;
pub mod pdf;
pub mod reconstruction;
mod schedule;
#[cfg(feature = "terminal-viz")]
#[cfg_attr(docsrs, doc(cfg(feature = "terminal-viz")))]
pub mod terminal_viz;
use rand::Rng;
use rand_chacha::ChaCha12Rng;
use rustfft::num_complex::{Complex, ComplexFloat};
pub use schedule::*;
pub use rustfft;
pub use ndarray;
#[derive(Clone, Copy, Debug, Default)]
pub enum DisplayMode {
#[default]
Abs,
RealPart,
}
impl DisplayMode {
pub fn magnitude(self, complex: Complex<f64>) -> f64 {
match self {
DisplayMode::Abs => complex.abs(),
DisplayMode::RealPart => complex.re().abs(),
}
}
pub fn threshold<T: ComplexSequence + ?Sized>(self, sequence: &mut T, threshold: f64) {
sequence.apply(|v| {
let mag = self.magnitude(v);
if mag > threshold {
v * (mag - threshold) / mag
} else {
Complex::new(0., 0.)
}
})
}
pub fn maybe_real_part<T: ComplexSequence + ?Sized>(self, sequence: &mut T) {
if let Self::RealPart = self {
sequence.apply(|v| Complex::new(v.re(), 0.));
}
}
}
fn partition<T>(rng: &mut ChaCha12Rng, slice: &mut [T], by: &impl Fn(&T, &T) -> Ordering) -> usize {
slice.swap(0, rng.random_range(0..slice.len()));
let mut i = 1;
let mut j = slice.len() - 1;
loop {
while i < slice.len() && !matches!(by(&slice[i], &slice[0]), Ordering::Less) {
i += 1;
}
while matches!(by(&slice[j], &slice[0]), Ordering::Less) {
j -= 1;
}
if i > j {
slice.swap(0, j);
return j;
}
slice.swap(i, j);
i += 1;
}
}
pub(crate) fn quickselect<T>(
rng: &mut ChaCha12Rng,
mut slice: &mut [T],
by: impl Fn(&T, &T) -> Ordering,
mut find_spot: usize,
) {
loop {
let len = slice.len();
if len < 2 {
return;
}
let spot_found = partition(rng, slice, &by);
match find_spot.cmp(&spot_found) {
Ordering::Less => slice = &mut slice[0..spot_found],
Ordering::Equal => return,
Ordering::Greater => {
slice = &mut slice[spot_found + 1..len];
find_spot = find_spot - spot_found - 1;
}
}
}
}
pub trait ComplexSequence {
fn apply(&mut self, f: impl FnMut(Complex<f64>) -> Complex<f64>);
fn apply_into<T>(&self, out: &mut [T], f: impl FnMut(Complex<f64>) -> T);
fn phase(&mut self, θ: f64) {
let rotator = Complex::new(0., θ).exp();
self.apply(|v| v * rotator);
}
fn re(&self, out: &mut [f64]) {
self.apply_into(out, |v| v.re());
}
}
impl ComplexSequence for [Complex<f64>] {
fn apply(&mut self, mut f: impl FnMut(Complex<f64>) -> Complex<f64>) {
for v in self {
*v = f(*v);
}
}
fn apply_into<T>(&self, out: &mut [T], mut f: impl FnMut(Complex<f64>) -> T) {
assert_eq!(self.len(), out.len());
out.iter_mut()
.zip(self.iter())
.for_each(|(out_v, complex)| *out_v = f(*complex));
}
}
impl<V: AsMut<[Complex<f64>]> + AsRef<[Complex<f64>]>> ComplexSequence for V {
fn apply(&mut self, f: impl FnMut(Complex<f64>) -> Complex<f64>) {
self.as_mut().apply(f);
}
fn apply_into<T>(&self, out: &mut [T], f: impl FnMut(Complex<f64>) -> T) {
self.as_ref().apply_into(out, f);
}
}
#[derive(Clone, Copy, Debug)]
enum Monotonicity {
Increasing,
Decreasing,
}
#[derive(Clone, Copy, Debug)]
struct InitialState {
start: f64,
min: f64,
max: f64,
}
impl InitialState {
pub const fn new(start: f64, min: f64, max: f64) -> Self {
Self { start, min, max }
}
}
type BinsearchPoint<T> = (f64, (f64, T));
enum Precision<'a, T> {
#[allow(dead_code)]
Preimage(f64),
Image {
amount: f64,
debug: &'a dyn Fn(BinsearchPoint<T>, BinsearchPoint<T>),
},
}
fn find_zero<T>(
monotonicity: Monotonicity,
initial_state: InitialState,
precision: Precision<'_, T>,
f: impl Fn(f64) -> (f64, T),
) -> (f64, T) {
let mut min = initial_state.min;
let mut current = initial_state.start;
let mut max = initial_state.max;
loop {
let (v, ret) = f(current);
if let Precision::Image { amount, debug: _ } = precision {
if v.abs() < amount {
return (current, ret);
}
}
match (v.total_cmp(&0.), monotonicity) {
(Ordering::Less, Monotonicity::Increasing)
| (Ordering::Greater, Monotonicity::Decreasing) => min = current,
(Ordering::Equal, _) => return (current, ret),
(Ordering::Greater, Monotonicity::Increasing)
| (Ordering::Less, Monotonicity::Decreasing) => max = current,
}
if max != f64::INFINITY {
let new_current = min / 2. + max / 2.;
match precision {
Precision::Preimage(amount) => {
if max - min < amount || new_current == current {
return (current, ret);
}
}
Precision::Image { amount: _, debug } => {
if new_current == current {
debug((min, f(min)), (max, f(max)));
return (current, ret);
}
}
}
current = new_current;
} else {
current *= 2.;
}
}
}
#[cfg(test)]
mod tests {
use alloc::borrow::ToOwned;
use core::cell::OnceCell;
use rand_chacha::ChaCha12Rng;
use alloc::vec::Vec;
use rand::{Rng, SeedableRng};
use rustfft::num_complex::{Complex, ComplexFloat};
use crate::{DisplayMode, InitialState, Monotonicity, find_zero, quickselect};
#[test]
fn test_display_mode() {
assert_eq!(DisplayMode::Abs.magnitude(Complex::new(4., 3.)), 5.);
assert_eq!(DisplayMode::RealPart.magnitude(Complex::new(4., 3.)), 4.);
let mut seq = [Complex::new(6., 8.), Complex::new(1., 1.)];
DisplayMode::Abs.threshold(&mut seq[..], 5.);
DisplayMode::Abs.maybe_real_part(&mut seq[..]);
assert!((seq[0] - Complex::new(3., 4.)).abs() < 0.000000001);
assert_eq!(seq[1], Complex::ZERO);
seq = [Complex::new(2., 9.), Complex::new(0.5, 1000.)];
DisplayMode::RealPart.threshold(&mut seq[..], 1.);
assert!((seq[0] - Complex::new(1., 4.5)).abs() < 0.000000001);
assert_eq!(seq[1], Complex::ZERO);
DisplayMode::RealPart.maybe_real_part(&mut seq[..]);
assert!((seq[0] - Complex::new(1., 0.)).abs() < 0.000000001);
let mut rng = ChaCha12Rng::from_seed(*b"Not all plants spread seeds, and");
for mode in [DisplayMode::Abs, DisplayMode::RealPart] {
let seq = (0..1000)
.map(|_| Complex::new(rng.random::<f64>() - 0.5, rng.random::<f64>() - 0.5) * 128.)
.collect::<Vec<_>>();
let mut seq_new = seq.to_owned();
mode.threshold(&mut *seq_new, 32.);
seq.iter().zip(seq_new.iter()).for_each(|(before, after)| {
if mode.magnitude(*before) < 32. {
assert_eq!(*after, Complex::ZERO);
} else {
assert!(
(mode.magnitude(*before) - mode.magnitude(*after) - 32.).abs() < 0.00000001,
"{before} {after} {mode:?}"
);
assert!(
(before.arg() - after.arg()).abs() < 0.00000001,
"{before} {after} {mode:?}"
);
match mode {
DisplayMode::Abs => {}
DisplayMode::RealPart => {
assert_eq!(
before.re().signum(),
after.re().signum(),
"{before} {after} {mode:?}"
);
}
}
}
});
let mut seq_maybe_re = seq_new.to_owned();
mode.maybe_real_part(&mut *seq_maybe_re);
seq_new
.iter()
.zip(seq_maybe_re.iter())
.for_each(|(before, after)| match mode {
DisplayMode::Abs => assert_eq!(before, after),
DisplayMode::RealPart => {
assert_eq!(after.im(), 0.);
}
});
}
}
#[test]
fn test_binsearch() {
let v = find_zero(
Monotonicity::Increasing,
InitialState {
start: 1.,
min: 0.,
max: f64::INFINITY,
},
crate::Precision::Preimage(0.1),
|v| (-5. + v, ()),
)
.0;
assert!(v > 5. - 0.1 && v < 5. + 0.1);
let v = find_zero(
Monotonicity::Decreasing,
InitialState {
start: 1.,
min: 0.,
max: f64::INFINITY,
},
crate::Precision::Preimage(0.1),
|v| (4. - 0.1 * v, ()),
)
.0;
assert!(v > 40. - 0.1 && v < 40. + 0.1, "v = {v}");
let v = find_zero(
Monotonicity::Increasing,
InitialState {
start: 1.,
min: 0.,
max: f64::INFINITY,
},
crate::Precision::Image {
amount: 0.1,
debug: (&|_, _| -> () { panic!("Debug should not have been called!") })
as &dyn Fn(_, _),
},
|v| (-3. + 10. * v, ()),
)
.0;
assert!(v > 0.3 - 0.01 && v < 0.3 + 0.01, "v = {v}");
let v = find_zero(
Monotonicity::Decreasing,
InitialState {
start: 1.,
min: 0.,
max: f64::INFINITY,
},
crate::Precision::Image {
amount: 0.1,
debug: (&|_, _| -> () { panic!("Debug should not have been called!") })
as &dyn Fn(_, _),
},
|v| (5. - 5. * v, ()),
)
.0;
assert!(v > 1. - 0.02 && v < 1. + 0.02, "v = {v}");
let debug_called = OnceCell::new();
find_zero(
Monotonicity::Decreasing,
InitialState {
start: 1.,
min: 0.,
max: f64::INFINITY,
},
crate::Precision::Image {
amount: 0.1,
debug: (&|_, _| {
debug_called.set(true).unwrap();
}) as &dyn Fn(_, _),
},
|v| (if v > 5. { 1. } else { -1. }, ()),
);
assert!(debug_called.get().is_some());
}
#[test]
fn test_quickselect() {
fn verify(rng: &mut ChaCha12Rng, pos: usize, slice: &[f64]) {
let mut slice = slice
.iter()
.enumerate()
.map(|(a, b)| (*b, a))
.collect::<Vec<_>>();
quickselect(rng, &mut slice, |a, b| a.0.total_cmp(&b.0), pos);
for i in 0..pos {
assert!(
slice[i].0 >= slice[pos].0,
"Pos: {pos}, Index: {i} - {slice:?}"
);
}
for i in pos + 1..slice.len() {
assert!(
slice[i].0 <= slice[pos].0,
"Pos: {pos}, Index: {i} - {slice:?}"
);
}
let v = slice[pos];
slice.sort_by(|a, b| b.0.total_cmp(&a.0));
assert_eq!(slice[pos].0, v.0);
}
let mut rng = ChaCha12Rng::from_seed(*b"Not all seeds plant plants, some");
verify(&mut rng, 2, &[5., 4., 3., 2., 1.]);
verify(&mut rng, 2, &[1., 2., 3., 4., 5.]);
verify(&mut rng, 3, &[1., 2., 1., 4., 3.]);
for i in 0..100 {
let pos = rng.random_range(0..i + 1);
let data = (0..i + 1).map(|_| rng.random()).collect::<Vec<_>>();
verify(&mut rng, pos, &data);
}
}
}