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}