Skip to main content

dodopow/
lib.rs

1use std::collections::HashSet;
2
3pub use rand_core;
4
5/// DodoPoW graph storage.
6#[derive(Debug, Clone, PartialEq, Eq, Hash)]
7pub struct Graph(Box<[(u32, u32)]>);
8
9impl Graph {
10    /// Generate a new graph.
11    ///
12    /// - `rng` is used to randomly generate edges of the graph. It is expected
13    ///   that an RNG of good quality is provided.
14    /// - `n` specifies amount of nodes and edges in the graph: `N = 2^n` edges,
15    ///   `2N` nodes. `n` must be lower or equal to 32 due to internal graph
16    ///   storage structure.
17    pub fn new(rng: &mut impl rand_core::RngCore, n: u8) -> Self {
18        assert!(n <= 32, "graph n param must be lower or equal to 32");
19
20        let edges_number = 1_usize << n;
21
22        let mut nodes = Vec::with_capacity(edges_number);
23
24        for _ in 0..edges_number {
25            let from_node = rng.next_u32() % edges_number as u32;
26            let to_node = rng.next_u32() % edges_number as u32;
27
28            // Can (and will) contain duplicates.
29            nodes.push((from_node, to_node));
30        }
31
32        Self(nodes.into_boxed_slice())
33    }
34
35    /// Search for cycles of the graph.
36    ///
37    /// - `max_depth` specifies maximal length of a potential cycle.
38    /// - `handler` accepts all the found cycles, and if `true` is returned then
39    ///   search is stopped.
40    pub fn solve(
41        &self,
42        max_depth: usize,
43        mut handler: impl FnMut(&[u32]) -> bool
44    ) -> Option<Box<[u32]>> {
45        let edges_number = self.0.len();
46
47        // Build transition matrices.
48        let mut top_nodes = vec![vec![]; edges_number];
49        let mut bottom_nodes = vec![vec![]; edges_number];
50
51        for (top_node, bottom_node) in &self.0 {
52            if !top_nodes[*top_node as usize].contains(bottom_node) {
53                top_nodes[*top_node as usize].push(*bottom_node);
54            }
55
56            if !bottom_nodes[*bottom_node as usize].contains(top_node) {
57                bottom_nodes[*bottom_node as usize].push(*top_node);
58            }
59        }
60
61        // Prune top nodes with less than 2 edges.
62        #[allow(clippy::needless_range_loop)]
63        for top_node in 0..edges_number {
64            if top_nodes[top_node].len() < 2 {
65                for bottom_node in &top_nodes[top_node] {
66                    bottom_nodes[*bottom_node as usize].retain(|node| {
67                        node != &(top_node as u32)
68                    });
69                }
70
71                top_nodes[top_node].clear();
72            }
73        }
74
75        // Prune bottom nodes with less than 2 edges.
76        #[allow(clippy::needless_range_loop)]
77        for bottom_node in 0..edges_number {
78            if bottom_nodes[bottom_node].len() < 2 {
79                for top_node in &bottom_nodes[bottom_node] {
80                    top_nodes[*top_node as usize].retain(|node| {
81                        node != &(bottom_node as u32)
82                    });
83                }
84
85                bottom_nodes[bottom_node].clear();
86            }
87        }
88
89        // Run iterative DFS over the graph.
90        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
91        enum GraphSide {
92            Top(u32),
93            Bottom(u32)
94        }
95
96        for target_top_node in 0..edges_number as u32 {
97            if top_nodes[target_top_node as usize].is_empty() {
98                continue;
99            }
100
101            let mut stack: Vec<(GraphSide, Box<[u32]>)> = Vec::new();
102            let mut visited = HashSet::new();
103
104            stack.push((
105                GraphSide::Top(target_top_node),
106                Box::new([target_top_node])
107            ));
108
109            while let Some((node, path)) = stack.pop() {
110                if visited.insert(node) && path.len() < max_depth {
111                    match node {
112                        GraphSide::Top(top_node) => {
113                            for bottom_node in &top_nodes[top_node as usize] {
114                                let mut path = path.to_vec();
115
116                                path.push(*bottom_node);
117
118                                stack.push((
119                                    GraphSide::Bottom(*bottom_node),
120                                    path.into_boxed_slice()
121                                ));
122                            }
123                        }
124
125                        GraphSide::Bottom(bottom_node) => {
126                            for top_node in &bottom_nodes[bottom_node as usize] {
127                                let mut path = path.to_vec();
128
129                                path.push(*top_node);
130
131                                stack.push((
132                                    GraphSide::Top(*top_node),
133                                    path.into_boxed_slice()
134                                ));
135                            }
136                        }
137                    }
138                }
139
140                else if node == GraphSide::Top(target_top_node)
141                    && path.len() > 3
142                    && handler(&path)
143                {
144                    return Some(path);
145                }
146            }
147        }
148
149        None
150    }
151
152    /// Search for any cycle of length `diff`.
153    ///
154    /// The `diff` value must be odd due to the graph structure, and longer
155    /// than 3 to form a proper cycle, meaning `diff` is an odd number starting
156    /// from 5.
157    pub fn solve_for(&self, diff: usize)-> Option<Box<[u32]>> {
158        if diff % 2 == 0 || diff < 5 {
159            return None;
160        }
161
162        self.solve(diff, |cycle| cycle.len() == diff)
163    }
164
165    /// Verify the cycle.
166    pub fn verify(&self, cycle: &[u32]) -> bool {
167        let n = cycle.len();
168
169        // IQ test (cycle must be of odd length due to graph structure).
170        if n % 2 == 0 {
171            return false;
172        }
173
174        let mut i = 1;
175
176        while i < n {
177            let mut found = false;
178
179            for (top_node, bottom_node) in &self.0 {
180                if (i % 2 != 0 && top_node == &cycle[i - 1] && bottom_node == &cycle[i])
181                    || (i % 2 == 0 && bottom_node == &cycle[i - 1] && top_node == &cycle[i])
182                {
183                    found = true;
184
185                    break;
186                }
187            }
188
189            if !found {
190                return false;
191            }
192
193            i += 1;
194        }
195
196        true
197    }
198}
199
200#[test]
201fn test() {
202    use rand_core::SeedableRng;
203
204    let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(123);
205
206    let graph = Graph::new(&mut rng, 16);
207
208    assert!(graph.solve_for(9).is_some());
209
210    assert!(graph.verify(&[
211        1981,
212        19107,
213        3084,
214        24653,
215        6267,
216        46608,
217        34728,
218        11923,
219        1981
220    ]));
221}