repos

darkfurrow.com-rust

mirror archived upstream

A living almanac of seasons, soil, and the quiet knowledge that used to be common. Rust axum with minijinja and Vite.

agriculturealmanacaxumfolk-knowledgegardeningminijinjarustseasonalvite

2.4 KB · 84 lines · Rust Raw History
 1/// mulberry32 with javascript Math.imul / signed-32 semantics. matches the
 2/// python implementation in the original almanac.py exactly so seasonal
 3/// picks are stable across the python and rust versions.
 4
 5pub struct Mulberry32 {
 6    state: i32,
 7}
 8
 9impl Mulberry32 {
10    pub fn new(seed: i64) -> Self {
11        Mulberry32 { state: seed as i32 }
12    }
13
14    pub fn next(&mut self) -> f64 {
15        // s = to_signed32(s + 0x6D2B79F5)
16        self.state = self.state.wrapping_add(0x6D2B79F5_u32 as i32);
17        // t = imul(s ^ (s >>> 15), 1 | s)
18        let s = self.state as u32;
19        let mut t = imul(s ^ (s >> 15), 1u32 | s) as i32;
20        // t = to_signed32(t + to_signed32(imul(t ^ (t >>> 7), 61 | t)))
21        let tu = t as u32;
22        let inner = imul(tu ^ (tu >> 7), 61u32 | tu) as i32;
23        t = t.wrapping_add(inner);
24        // t = t ^ (t >>> 14)
25        let tu = t as u32;
26        let final_u = tu ^ (tu >> 14);
27        final_u as f64 / 4294967296.0
28    }
29}
30
31#[inline]
32fn imul(a: u32, b: u32) -> u32 {
33    a.wrapping_mul(b)
34}
35
36pub fn pick_items<T: Clone>(items: &[T], count: usize, rng: &mut Mulberry32) -> Vec<T> {
37    if items.len() <= count {
38        return items.to_vec();
39    }
40    let mut copy: Vec<T> = items.to_vec();
41    let mut out = Vec::with_capacity(count);
42    for _ in 0..count {
43        let idx = (rng.next() * copy.len() as f64) as usize;
44        out.push(copy.remove(idx));
45    }
46    out
47}
48
49pub fn day_hash(date: chrono::DateTime<chrono_tz::Tz>) -> i64 {
50    use chrono::Datelike;
51    let doy = date.ordinal() as i64;
52    date.year() as i64 * 1000 + doy
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58
59    #[test]
60    fn matches_python_seed_2026126() {
61        // Captured live from python: seeded_random(2026126) for 10 calls.
62        let expected = [
63            0.9106675076764077,
64            0.03512798482552171,
65            0.27484832773916423,
66            0.24498416110873222,
67            0.6522165813948959,
68            0.8708663478028029,
69            0.8258189295884222,
70            0.6256361070554703,
71            0.3541836692020297,
72            0.3059297795407474,
73        ];
74        let mut rng = Mulberry32::new(2026126);
75        for (i, &want) in expected.iter().enumerate() {
76            let got = rng.next();
77            assert!(
78                (got - want).abs() < 1e-12,
79                "iter {i}: got {got}, want {want}"
80            );
81        }
82    }
83}