A living almanac of seasons, soil, and the quiet knowledge that used to be common. Rust axum with minijinja and Vite.
agriculturealmanacaxumfolk-knowledgegardeningminijinjarustseasonalvite
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}