meander/
lib.rs

1//! This crate provides a way to show slow change in several variables at once.
2//! This change is done in such a way that should explore the value space fairly
3//! well while also appearing natural and random.
4//! 
5//! One place this might be useful is in code that demonstrates how changing certain
6//! parameters changes a model.
7//! 
8//! The variables yielded by this crate will all have values between 0 and 1, so you
9//! should scale them to suit your purposes.
10//!
11//! # How it Works
12//!
13//! For each variable, there is a separate function that determines its motion.
14//! This function is given by the average of three sinusoidal functions.
15//! 
16//! ```no_run
17//! use meander::rand;
18//! use meander::typenum::U3;
19//! 
20//! use meander::Meander;
21//! 
22//! struct Color {
23//!     r: u8,
24//!     g: u8,
25//!     b: u8,
26//! }
27//!
28//! fn random_colors() -> impl Iterator<Item=Color> {
29//!     rand::random::<Meander<U3>>()
30//!         .into_time_steps(0.01).map(|a| {
31//!             match a.as_slice() {
32//!                 // The variables yielded by `Meander` are floats between 0 and 1,
33//!                 // so we multiply by 256 and cast to `u8` to get the range we want.
34//!                 &[r, g, b] => Color {
35//!                     r: (r*256.0) as u8,
36//!                     g: (g*256.0) as u8,
37//!                     b: (b*256.0) as u8,
38//!                 },
39//!                 _ => unreachable!()
40//!             }
41//!         })
42//! }
43//! ```
44
45#![deny(missing_docs)]
46
47pub use rand;
48pub use generic_array;
49pub use generic_array::typenum;
50
51use generic_array::{GenericArray, ArrayLength};
52use generic_array::functional::FunctionalSequence;
53use generic_array::sequence::GenericSequence;
54
55use rand::Rng;
56use rand::distributions::{Distribution, Standard};
57
58const PI2: f64 = 2.0 * std::f64::consts::PI;
59
60/// Represents a sinusoid that varies between 0 and 1.
61///
62/// This can be generated randomly using `rand::random()`.
63#[derive(Clone, Copy, Debug)]
64pub struct UnitSinusoid {
65    /// The number of cycles the function makes per unit time.
66    pub frequency: f64,
67    /// The location in the cycle the function is `t = 0`.
68    pub phase: f64,
69}
70
71impl Distribution<UnitSinusoid> for Standard {
72    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> UnitSinusoid {
73        let frequency: f64 = rng.gen_range(1.0, 10.0);
74        let phase = rng.gen_range(0.0, frequency.recip());
75        UnitSinusoid { frequency, phase }
76    }
77}
78
79impl UnitSinusoid {
80    fn haversin(theta: f64) -> f64 {
81        (1.0 - theta.cos()) / 2.0
82    }
83    /// Find the value of the sinusoid at a given point in time.
84    pub fn evaluate(self, t: f64) -> f64 {
85        Self::haversin(PI2 * self.frequency * (t + self.phase))
86    }
87}
88
89/// Represents a curve that meanders through 1-dimensional space. Consists of 3
90/// sinusoids whose values are averaged.
91///
92/// This can be generated randomly using `rand::random()`.
93#[derive(Clone, Copy, Debug)]
94pub struct Meander1D(pub UnitSinusoid, pub UnitSinusoid, pub UnitSinusoid);
95
96impl Meander1D {
97    /// Find the value of the curve at a given point in time.
98    pub fn evaluate(self, t: f64) -> f64 {
99        ( (self.0).evaluate(t)
100        + (self.1).evaluate(t)
101        + (self.2).evaluate(t)
102        ) / 3.0
103    }
104}
105
106impl Distribution<Meander1D> for Standard {
107    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Meander1D {
108        Meander1D(rng.gen(), rng.gen(), rng.gen())
109    }
110}
111
112/// Represents a curve that meanders through `D`-dimensional space.
113///
114/// This can be generated randomly using `rand::random()`.
115#[derive(Clone, Debug)]
116pub struct Meander<D: ArrayLength<Meander1D>> {
117    /// Each variable is controlled by a separate 1-dimensional function defined here.
118    pub curves: GenericArray<Meander1D, D>,
119}
120
121impl<D: ArrayLength<Meander1D>> Distribution<Meander<D>> for Standard {
122    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Meander<D> {
123        Meander {
124            curves: <GenericArray<_, _> as GenericSequence<_>>::generate(|_| rng.gen()),
125        }
126    }
127}
128
129impl<D: ArrayLength<Meander1D> + ArrayLength<f64>> Meander<D> {
130    /// Find the value of each of the variables at a particular point in time.
131    pub fn evaluate(&self, t: f64) -> GenericArray<f64, D> {
132        (&self).curves.clone().map(|c| c.evaluate(t))
133    }
134    /// Return an iterator yielding the values of the variables at intervals of `dt`.
135    pub fn time_steps<'a>(&'a self, dt: f64) -> impl Iterator<Item=GenericArray<f64, D>> + 'a {
136        (0..).map(move |i| self.evaluate(i as f64 * dt))
137    }
138    /// Return an iterator yielding the values of the variables at intervals of `dt`.
139    /// Consumes `self`.
140    pub fn into_time_steps(self, dt: f64) -> impl Iterator<Item=GenericArray<f64, D>> {
141        (0..).map(move |i| self.evaluate(i as f64 * dt))
142    }
143}