Skip to main content

proof_engine/number_theory/
collatz.rs

1//! Collatz conjecture: sequences, stopping times, and tree visualization.
2
3use glam::{Vec3, Vec4};
4
5/// Compute the Collatz sequence starting from n until it reaches 1.
6pub fn collatz_sequence(n: u64) -> Vec<u64> {
7    if n == 0 {
8        return vec![0];
9    }
10    let mut seq = vec![n];
11    let mut current = n;
12    while current != 1 {
13        current = if current % 2 == 0 {
14            current / 2
15        } else {
16            3 * current + 1
17        };
18        seq.push(current);
19    }
20    seq
21}
22
23/// Number of steps for n to reach 1.
24pub fn collatz_stopping_time(n: u64) -> u32 {
25    if n <= 1 {
26        return 0;
27    }
28    let mut current = n;
29    let mut steps = 0u32;
30    while current != 1 {
31        current = if current % 2 == 0 {
32            current / 2
33        } else {
34            3 * current + 1
35        };
36        steps += 1;
37    }
38    steps
39}
40
41/// A node in the reverse Collatz tree.
42#[derive(Debug, Clone)]
43pub struct TreeNode {
44    pub value: u64,
45    pub children: Vec<TreeNode>,
46}
47
48/// The full reverse Collatz tree: starting from 1, build upward.
49/// Each node n has child 2n (always), and child (n-1)/3 if n ≡ 1 mod 3 and (n-1)/3 is odd and > 0.
50pub struct CollatzTree {
51    pub root: TreeNode,
52    pub limit: u64,
53}
54
55impl CollatzTree {
56    /// Build the reverse Collatz tree up to a given limit on node values.
57    pub fn build(limit: u64) -> Self {
58        let root = Self::build_node(1, limit, 0, 40);
59        CollatzTree { root, limit }
60    }
61
62    fn build_node(value: u64, limit: u64, depth: usize, max_depth: usize) -> TreeNode {
63        if depth >= max_depth {
64            return TreeNode { value, children: vec![] };
65        }
66        let mut children = Vec::new();
67
68        // Child via n -> 2n (reverse of n/2)
69        let child_even = 2 * value;
70        if child_even <= limit {
71            children.push(Self::build_node(child_even, limit, depth + 1, max_depth));
72        }
73
74        // Child via n -> (n-1)/3 reversed: if value came from 3k+1, then k = (value-1)/3
75        // Reverse: if some odd k maps to value via 3k+1 = value, then k = (value-1)/3
76        // We want k to be a valid predecessor: k must be odd and (value-1) divisible by 3
77        if value > 4 && (value - 1) % 3 == 0 {
78            let k = (value - 1) / 3;
79            if k > 0 && k % 2 == 1 && k != value {
80                // k is odd and would map to value
81                if k <= limit {
82                    children.push(Self::build_node(k, limit, depth + 1, max_depth));
83                }
84            }
85        }
86
87        TreeNode { value, children }
88    }
89
90    /// Flatten the tree into a list of (value, depth) pairs.
91    pub fn flatten(&self) -> Vec<(u64, usize)> {
92        let mut result = Vec::new();
93        Self::flatten_node(&self.root, 0, &mut result);
94        result
95    }
96
97    fn flatten_node(node: &TreeNode, depth: usize, result: &mut Vec<(u64, usize)>) {
98        result.push((node.value, depth));
99        for child in &node.children {
100            Self::flatten_node(child, depth + 1, result);
101        }
102    }
103
104    /// Count total nodes in the tree.
105    pub fn node_count(&self) -> usize {
106        Self::count_nodes(&self.root)
107    }
108
109    fn count_nodes(node: &TreeNode) -> usize {
110        1 + node.children.iter().map(|c| Self::count_nodes(c)).sum::<usize>()
111    }
112}
113
114// ─── Renderer ───────────────────────────────────────────────────────────────
115
116/// Layout and render the Collatz tree with branch colors based on path length.
117pub struct CollatzTreeRenderer {
118    pub origin: Vec3,
119    pub scale: f32,
120    pub horizontal_spread: f32,
121}
122
123pub struct CollatzGlyph {
124    pub value: u64,
125    pub depth: usize,
126    pub position: Vec3,
127    pub color: Vec4,
128    pub character: char,
129}
130
131impl CollatzTreeRenderer {
132    pub fn new(origin: Vec3, scale: f32, horizontal_spread: f32) -> Self {
133        Self { origin, scale, horizontal_spread }
134    }
135
136    /// Render a Collatz tree.
137    pub fn render(&self, tree: &CollatzTree) -> Vec<CollatzGlyph> {
138        let mut glyphs = Vec::new();
139        let mut counter = 0usize;
140        self.render_node(&tree.root, 0, &mut counter, &mut glyphs);
141        glyphs
142    }
143
144    fn render_node(
145        &self,
146        node: &TreeNode,
147        depth: usize,
148        counter: &mut usize,
149        glyphs: &mut Vec<CollatzGlyph>,
150    ) {
151        let x = *counter as f32 * self.horizontal_spread;
152        let y = -(depth as f32) * self.scale;
153        let stopping = collatz_stopping_time(node.value);
154        let t = (stopping as f32 / 50.0).min(1.0);
155        glyphs.push(CollatzGlyph {
156            value: node.value,
157            depth,
158            position: self.origin + Vec3::new(x, y, 0.0),
159            color: Vec4::new(t, 0.5, 1.0 - t, 1.0),
160            character: if node.value % 2 == 0 { 'E' } else { 'O' },
161        });
162        *counter += 1;
163        for child in &node.children {
164            self.render_node(child, depth + 1, counter, glyphs);
165        }
166    }
167
168    /// Render a single Collatz sequence as a path of glyphs.
169    pub fn render_sequence(&self, n: u64) -> Vec<CollatzGlyph> {
170        let seq = collatz_sequence(n);
171        let max_val = *seq.iter().max().unwrap_or(&1) as f32;
172        seq.iter()
173            .enumerate()
174            .map(|(i, &v)| {
175                let t = i as f32 / seq.len().max(1) as f32;
176                let height = v as f32 / max_val;
177                CollatzGlyph {
178                    value: v,
179                    depth: i,
180                    position: self.origin + Vec3::new(i as f32 * self.scale, height * self.scale * 5.0, 0.0),
181                    color: Vec4::new(t, height, 1.0 - t, 1.0),
182                    character: if v % 2 == 0 { 'v' } else { '^' },
183                }
184            })
185            .collect()
186    }
187}
188
189// ─── Tests ──────────────────────────────────────────────────────────────────
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194
195    #[test]
196    fn sequence_basic() {
197        let seq = collatz_sequence(6);
198        assert_eq!(seq, vec![6, 3, 10, 5, 16, 8, 4, 2, 1]);
199    }
200
201    #[test]
202    fn sequence_one() {
203        assert_eq!(collatz_sequence(1), vec![1]);
204    }
205
206    #[test]
207    fn sequence_27() {
208        let seq = collatz_sequence(27);
209        assert_eq!(*seq.last().unwrap(), 1);
210        // 27 is famous for having a long sequence (111 steps)
211        assert_eq!(seq.len(), 112); // 111 steps + initial value
212    }
213
214    #[test]
215    fn stopping_time() {
216        assert_eq!(collatz_stopping_time(1), 0);
217        assert_eq!(collatz_stopping_time(2), 1);
218        assert_eq!(collatz_stopping_time(6), 8);
219        assert_eq!(collatz_stopping_time(27), 111);
220    }
221
222    #[test]
223    fn tree_build() {
224        let tree = CollatzTree::build(32);
225        // Root should be 1
226        assert_eq!(tree.root.value, 1);
227        // 1 -> 2 is always a child
228        assert!(tree.root.children.iter().any(|c| c.value == 2));
229    }
230
231    #[test]
232    fn tree_contains_powers_of_2() {
233        let tree = CollatzTree::build(64);
234        let flat = tree.flatten();
235        let values: Vec<u64> = flat.iter().map(|&(v, _)| v).collect();
236        // All powers of 2 up to 64 should be in the tree
237        for &p in &[1, 2, 4, 8, 16, 32, 64] {
238            assert!(values.contains(&p), "tree should contain {}", p);
239        }
240    }
241
242    #[test]
243    fn tree_node_count() {
244        let tree = CollatzTree::build(16);
245        assert!(tree.node_count() >= 5); // at least 1,2,4,8,16
246    }
247
248    #[test]
249    fn renderer_sequence() {
250        let r = CollatzTreeRenderer::new(Vec3::ZERO, 1.0, 2.0);
251        let glyphs = r.render_sequence(27);
252        assert_eq!(glyphs.len(), 112);
253        assert_eq!(glyphs[0].value, 27);
254        assert_eq!(glyphs.last().unwrap().value, 1);
255    }
256
257    #[test]
258    fn renderer_tree() {
259        let tree = CollatzTree::build(32);
260        let r = CollatzTreeRenderer::new(Vec3::ZERO, 1.0, 2.0);
261        let glyphs = r.render(&tree);
262        assert!(!glyphs.is_empty());
263        assert_eq!(glyphs[0].value, 1); // root
264    }
265
266    #[test]
267    fn all_reach_one() {
268        // Verify Collatz for all n up to 1000
269        for n in 1..=1000 {
270            let seq = collatz_sequence(n);
271            assert_eq!(*seq.last().unwrap(), 1, "Collatz failed for {}", n);
272        }
273    }
274}