rustworkx_core/
token_swapper.rs

1// Licensed under the Apache License, Version 2.0 (the "License"); you may
2// not use this file except in compliance with the License. You may obtain
3// a copy of the License at
4//
5//     http://www.apache.org/licenses/LICENSE-2.0
6//
7// Unless required by applicable law or agreed to in writing, software
8// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10// License for the specific language governing permissions and limitations
11// under the License.
12
13use rand::prelude::*;
14use rand_distr::{StandardUniform, Uniform};
15use rand_pcg::Pcg64;
16use std::error::Error;
17use std::fmt;
18use std::hash::Hash;
19
20use hashbrown::HashMap;
21use petgraph::stable_graph::{NodeIndex, StableGraph};
22use petgraph::visit::{
23    EdgeCount, GraphBase, IntoEdges, IntoNeighborsDirected, IntoNodeIdentifiers, NodeCount,
24    NodeIndexable, Visitable,
25};
26use petgraph::Directed;
27use petgraph::Direction::{Incoming, Outgoing};
28use rayon::prelude::*;
29use rayon_cond::CondIterator;
30
31use crate::connectivity::find_cycle;
32use crate::dictmap::*;
33use crate::shortest_path::dijkstra;
34use crate::traversal::dfs_edges;
35
36type Swap = (NodeIndex, NodeIndex);
37type Edge = (NodeIndex, NodeIndex);
38
39/// Error returned by token swapper if the request mapping
40/// is impossible
41#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Copy, Clone)]
42pub struct MapNotPossible;
43
44impl Error for MapNotPossible {}
45impl fmt::Display for MapNotPossible {
46    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
47        write!(f, "No mapping possible.")
48    }
49}
50
51struct TokenSwapper<G: GraphBase>
52where
53    G::NodeId: Eq + Hash,
54{
55    // The input graph
56    graph: G,
57    // The user-supplied mapping to use for swapping tokens
58    mapping: HashMap<G::NodeId, G::NodeId>,
59    // Number of trials
60    trials: usize,
61    // Seed for random selection of a node for a trial
62    seed: Option<u64>,
63    // Threshold for how many nodes will trigger parallel iterator
64    parallel_threshold: usize,
65    // Map of NodeId to NodeIndex
66    node_map: HashMap<G::NodeId, NodeIndex>,
67    // Map of NodeIndex to NodeId
68    rev_node_map: HashMap<NodeIndex, G::NodeId>,
69}
70
71impl<G> TokenSwapper<G>
72where
73    G: NodeCount
74        + EdgeCount
75        + IntoEdges
76        + Visitable
77        + NodeIndexable
78        + IntoNeighborsDirected
79        + IntoNodeIdentifiers
80        + Send
81        + Sync,
82    G::NodeId: Hash + Eq + Send + Sync,
83{
84    fn new(
85        graph: G,
86        mapping: HashMap<G::NodeId, G::NodeId>,
87        trials: Option<usize>,
88        seed: Option<u64>,
89        parallel_threshold: Option<usize>,
90    ) -> Self {
91        TokenSwapper {
92            graph,
93            mapping,
94            trials: trials.unwrap_or(4),
95            seed,
96            parallel_threshold: parallel_threshold.unwrap_or(50),
97            node_map: HashMap::with_capacity(graph.node_count()),
98            rev_node_map: HashMap::with_capacity(graph.node_count()),
99        }
100    }
101
102    fn map(&mut self) -> Result<Vec<Swap>, MapNotPossible> {
103        let num_nodes = self.graph.node_bound();
104        let num_edges = self.graph.edge_count();
105
106        // Directed graph with nodes matching ``graph`` and
107        // edges for neighbors closer than nodes
108        let mut digraph = StableGraph::with_capacity(num_nodes, num_edges);
109
110        // First fill the digraph with nodes. Then since it's a stable graph,
111        // must go through and remove nodes that were removed in original graph
112        for _ in 0..self.graph.node_bound() {
113            digraph.add_node(());
114        }
115        let mut count: usize = 0;
116        for gnode in self.graph.node_identifiers() {
117            let gidx = self.graph.to_index(gnode);
118            if gidx != count {
119                for idx in count..gidx {
120                    digraph.remove_node(NodeIndex::new(idx));
121                }
122                count = gidx;
123            }
124            count += 1;
125        }
126
127        // Create maps between NodeId and NodeIndex
128        for node in self.graph.node_identifiers() {
129            self.node_map
130                .insert(node, NodeIndex::new(self.graph.to_index(node)));
131            self.rev_node_map
132                .insert(NodeIndex::new(self.graph.to_index(node)), node);
133        }
134        // sub will become same as digraph but with no self edges in add_token_edges
135        let mut sub_digraph = digraph.clone();
136
137        // The mapping in HashMap form using NodeIndex
138        let mut tokens: HashMap<NodeIndex, NodeIndex> = self
139            .mapping
140            .iter()
141            .map(|(k, v)| (self.node_map[k], self.node_map[v]))
142            .collect();
143
144        // todo_nodes are all the mapping entries where left != right
145        let mut todo_nodes: Vec<NodeIndex> = tokens
146            .iter()
147            .filter_map(|(node, dest)| if node != dest { Some(*node) } else { None })
148            .collect();
149        todo_nodes.par_sort();
150
151        // Add initial edges to the digraph/sub_digraph
152        for node in self.graph.node_identifiers() {
153            self.add_token_edges(
154                self.node_map[&node],
155                &mut digraph,
156                &mut sub_digraph,
157                &mut tokens,
158            )?;
159        }
160        // First collect the self.trial number of random numbers
161        // into a Vec based on the given seed
162        let outer_rng: Pcg64 = match self.seed {
163            Some(rng_seed) => Pcg64::seed_from_u64(rng_seed),
164            None => Pcg64::from_os_rng(),
165        };
166        let trial_seeds_vec: Vec<u64> = outer_rng
167            .sample_iter(&StandardUniform)
168            .take(self.trials)
169            .collect();
170
171        CondIterator::new(
172            trial_seeds_vec,
173            self.graph.node_count() >= self.parallel_threshold,
174        )
175        .map(|trial_seed| {
176            self.trial_map(
177                digraph.clone(),
178                sub_digraph.clone(),
179                tokens.clone(),
180                todo_nodes.clone(),
181                trial_seed,
182            )
183        })
184        .min_by_key(|result| match result {
185            Ok(res) => Ok(res.len()),
186            Err(e) => Err(*e),
187        })
188        .unwrap()
189    }
190
191    fn add_token_edges(
192        &self,
193        node: NodeIndex,
194        digraph: &mut StableGraph<(), (), Directed>,
195        sub_digraph: &mut StableGraph<(), (), Directed>,
196        tokens: &mut HashMap<NodeIndex, NodeIndex>,
197    ) -> Result<(), MapNotPossible> {
198        // Adds an edge to digraph if distance from the token to a neighbor is
199        // less than distance from token to node. sub_digraph is same except
200        // for self-edges.
201        if !tokens.contains_key(&node) {
202            return Ok(());
203        }
204        if tokens[&node] == node {
205            digraph.update_edge(node, node, ());
206            return Ok(());
207        }
208        let id_node = self.rev_node_map[&node];
209        let id_token = self.rev_node_map[&tokens[&node]];
210
211        if self.graph.neighbors(id_node).next().is_none() {
212            return Err(MapNotPossible {});
213        }
214
215        for id_neighbor in self.graph.neighbors(id_node) {
216            let neighbor = self.node_map[&id_neighbor];
217            let dist_neighbor: DictMap<G::NodeId, usize> = dijkstra(
218                &self.graph,
219                id_neighbor,
220                Some(id_token),
221                |_| Ok::<usize, &str>(1),
222                None,
223            )
224            .unwrap();
225
226            let dist_node: DictMap<G::NodeId, usize> = dijkstra(
227                &self.graph,
228                id_node,
229                Some(id_token),
230                |_| Ok::<usize, &str>(1),
231                None,
232            )
233            .unwrap();
234            let neigh_dist = dist_neighbor.get(&id_token);
235            let node_dist = dist_node.get(&id_token);
236            if neigh_dist.is_none() {
237                return Err(MapNotPossible {});
238            }
239            if node_dist.is_none() {
240                return Err(MapNotPossible {});
241            }
242
243            if neigh_dist < node_dist {
244                digraph.update_edge(node, neighbor, ());
245                sub_digraph.update_edge(node, neighbor, ());
246            }
247        }
248        Ok(())
249    }
250
251    fn trial_map(
252        &self,
253        mut digraph: StableGraph<(), (), Directed>,
254        mut sub_digraph: StableGraph<(), (), Directed>,
255        mut tokens: HashMap<NodeIndex, NodeIndex>,
256        mut todo_nodes: Vec<NodeIndex>,
257        trial_seed: u64,
258    ) -> Result<Vec<Swap>, MapNotPossible> {
259        // Create a random trial list of swaps to move tokens to optimal positions
260        let mut steps = 0;
261        let mut swap_edges: Vec<Swap> = vec![];
262        let mut rng_seed: Pcg64 = Pcg64::seed_from_u64(trial_seed);
263        while !todo_nodes.is_empty() && steps <= 4 * digraph.node_count().pow(2) {
264            // Choose a random todo_node
265            let between = Uniform::new(0, todo_nodes.len()).map_err(|_| MapNotPossible {})?;
266            let random: usize = between.sample(&mut rng_seed);
267            let todo_node = todo_nodes[random];
268
269            // If there's a cycle in sub_digraph, add it to swap_edges and do swap
270            let cycle = find_cycle(&sub_digraph, Some(todo_node));
271            if !cycle.is_empty() {
272                for edge in cycle[1..].iter().rev() {
273                    swap_edges.push(*edge);
274                    self.swap(
275                        edge.0,
276                        edge.1,
277                        &mut digraph,
278                        &mut sub_digraph,
279                        &mut tokens,
280                        &mut todo_nodes,
281                    )?;
282                }
283                steps += cycle.len() - 1;
284            // If there's no cycle, see if there's an edge target that matches a token key.
285            // If so, add to swap_edges and do swap
286            } else {
287                let mut found = false;
288                let sub2 = &sub_digraph.clone();
289                for edge in dfs_edges(sub2, Some(todo_node)) {
290                    let new_edge = (NodeIndex::new(edge.0), NodeIndex::new(edge.1));
291                    if !tokens.contains_key(&new_edge.1) {
292                        swap_edges.push(new_edge);
293                        self.swap(
294                            new_edge.0,
295                            new_edge.1,
296                            &mut digraph,
297                            &mut sub_digraph,
298                            &mut tokens,
299                            &mut todo_nodes,
300                        )?;
301                        steps += 1;
302                        found = true;
303                        break;
304                    }
305                }
306                // If none found, look for cycle in digraph which will result in
307                // an unhappy node. Look for a predecessor and add node and pred
308                // to swap_edges and do swap
309                if !found {
310                    let cycle: Vec<Edge> = find_cycle(&digraph, Some(todo_node));
311                    let unhappy_node = cycle[0].0;
312                    let mut found = false;
313                    let di2 = &mut digraph.clone();
314                    for predecessor in di2.neighbors_directed(unhappy_node, Incoming) {
315                        if predecessor != unhappy_node {
316                            swap_edges.push((unhappy_node, predecessor));
317                            self.swap(
318                                unhappy_node,
319                                predecessor,
320                                &mut digraph,
321                                &mut sub_digraph,
322                                &mut tokens,
323                                &mut todo_nodes,
324                            )?;
325                            steps += 1;
326                            found = true;
327                            break;
328                        }
329                    }
330                    assert!(
331                        found,
332                        "The token swap process has ended unexpectedly, this points to a bug in rustworkx, please open an issue."
333                    );
334                }
335            }
336        }
337        assert!(
338            todo_nodes.is_empty(),
339            "The output final swap map is incomplete, this points to a bug in rustworkx, please open an issue."
340        );
341        Ok(swap_edges)
342    }
343
344    fn swap(
345        &self,
346        node1: NodeIndex,
347        node2: NodeIndex,
348        digraph: &mut StableGraph<(), (), Directed>,
349        sub_digraph: &mut StableGraph<(), (), Directed>,
350        tokens: &mut HashMap<NodeIndex, NodeIndex>,
351        todo_nodes: &mut Vec<NodeIndex>,
352    ) -> Result<(), MapNotPossible> {
353        // Get token values for the 2 nodes and remove them
354        let token1 = tokens.remove(&node1);
355        let token2 = tokens.remove(&node2);
356
357        // Swap the token edge values
358        if let Some(t2) = token2 {
359            tokens.insert(node1, t2);
360        }
361        if let Some(t1) = token1 {
362            tokens.insert(node2, t1);
363        }
364        // For each node, remove the (node, successor) from digraph and
365        // sub_digraph. Then add new token edges back in.
366        for node in [node1, node2] {
367            let edge_nodes: Vec<(NodeIndex, NodeIndex)> = digraph
368                .neighbors_directed(node, Outgoing)
369                .map(|successor| (node, successor))
370                .collect();
371            for (edge_node1, edge_node2) in edge_nodes {
372                let edge = digraph.find_edge(edge_node1, edge_node2).unwrap();
373                digraph.remove_edge(edge);
374            }
375            let edge_nodes: Vec<(NodeIndex, NodeIndex)> = sub_digraph
376                .neighbors_directed(node, Outgoing)
377                .map(|successor| (node, successor))
378                .collect();
379            for (edge_node1, edge_node2) in edge_nodes {
380                let edge = sub_digraph.find_edge(edge_node1, edge_node2).unwrap();
381                sub_digraph.remove_edge(edge);
382            }
383            self.add_token_edges(node, digraph, sub_digraph, tokens)?;
384
385            // If a node is a token key and not equal to the value, add it to todo_nodes
386            if tokens.contains_key(&node) && tokens[&node] != node {
387                if !todo_nodes.contains(&node) {
388                    todo_nodes.push(node);
389                }
390            // Otherwise if node is in todo_nodes, remove it
391            } else if todo_nodes.contains(&node) {
392                todo_nodes.swap_remove(todo_nodes.iter().position(|x| *x == node).unwrap());
393            }
394        }
395        Ok(())
396    }
397}
398
399/// Module to perform an approximately optimal Token Swapping algorithm. Supports partial
400/// mappings (i.e. not-permutations) for graphs with missing tokens.
401///
402/// Based on the paper: Approximation and Hardness for Token Swapping by Miltzow et al. (2016)
403/// ArXiV: <https://arxiv.org/abs/1602.05150>
404///
405/// Arguments:
406///
407/// * `graph` - The graph on which to perform the token swapping.
408/// * `mapping` - A partial mapping to be implemented in swaps.
409/// * `trials` - Optional number of trials. If None, defaults to 4.
410/// * `seed` - Optional integer seed. If None, the internal rng will be initialized from system entropy.
411/// * `parallel_threshold` - Optional integer for the number of nodes in the graph that will
412///   trigger the use of parallel threads. If the number of nodes in the graph is less than this value
413///   it will run in a single thread. The default value is 50.
414///
415/// It returns a list of tuples representing the swaps to perform. The result will be an
416/// `Err(MapNotPossible)` if the `token_swapper()` function can't find a mapping.
417///
418/// This function is multithreaded and will launch a thread pool with threads equal to
419/// the number of CPUs by default. You can tune the number of threads with
420/// the ``RAYON_NUM_THREADS`` environment variable. For example, setting ``RAYON_NUM_THREADS=4``
421/// would limit the thread pool to 4 threads.
422///
423/// # Example
424/// ```rust
425/// use hashbrown::HashMap;
426/// use rustworkx_core::petgraph;
427/// use rustworkx_core::token_swapper::token_swapper;
428/// use rustworkx_core::petgraph::graph::NodeIndex;
429///
430///  let g = petgraph::graph::UnGraph::<(), ()>::from_edges(&[(0, 1), (1, 2), (2, 3)]);
431///  let mapping = HashMap::from([
432///   (NodeIndex::new(0), NodeIndex::new(0)),
433///   (NodeIndex::new(1), NodeIndex::new(3)),
434///   (NodeIndex::new(3), NodeIndex::new(1)),
435///   (NodeIndex::new(2), NodeIndex::new(2)),
436///  ]);
437///  // Do the token swap
438///  let output = token_swapper(&g, mapping, Some(4), Some(4), Some(50)).expect("Swap mapping failed.");
439///  assert_eq!(3, output.len());
440///
441/// ```
442pub fn token_swapper<G>(
443    graph: G,
444    mapping: HashMap<G::NodeId, G::NodeId>,
445    trials: Option<usize>,
446    seed: Option<u64>,
447    parallel_threshold: Option<usize>,
448) -> Result<Vec<Swap>, MapNotPossible>
449where
450    G: NodeCount
451        + EdgeCount
452        + IntoEdges
453        + Visitable
454        + NodeIndexable
455        + IntoNeighborsDirected
456        + IntoNodeIdentifiers
457        + Send
458        + Sync,
459    G::NodeId: Hash + Eq + Send + Sync,
460{
461    let mut swapper = TokenSwapper::new(graph, mapping, trials, seed, parallel_threshold);
462    swapper.map()
463}
464
465#[cfg(test)]
466mod test_token_swapper {
467
468    use crate::petgraph;
469    use crate::token_swapper::token_swapper;
470    use hashbrown::HashMap;
471    use petgraph::graph::NodeIndex;
472
473    fn do_swap(mapping: &mut HashMap<NodeIndex, NodeIndex>, swaps: &Vec<(NodeIndex, NodeIndex)>) {
474        // Apply the swaps to the mapping to get final result
475        for (swap1, swap2) in swaps {
476            //Need to create temp nodes in case of partial mapping
477            let mut temp_node1: Option<NodeIndex> = None;
478            let mut temp_node2: Option<NodeIndex> = None;
479            if mapping.contains_key(swap1) {
480                temp_node1 = Some(mapping[swap1]);
481                mapping.remove(swap1);
482            }
483            if mapping.contains_key(swap2) {
484                temp_node2 = Some(mapping[swap2]);
485                mapping.remove(swap2);
486            }
487            if let Some(t1) = temp_node1 {
488                mapping.insert(*swap2, t1);
489            }
490            if let Some(t2) = temp_node2 {
491                mapping.insert(*swap1, t2);
492            }
493        }
494    }
495
496    #[test]
497    fn test_simple_swap() {
498        // Simple arbitrary swap
499        let g = petgraph::graph::UnGraph::<(), ()>::from_edges([(0, 1), (1, 2), (2, 3)]);
500        let mapping = HashMap::from([
501            (NodeIndex::new(0), NodeIndex::new(0)),
502            (NodeIndex::new(1), NodeIndex::new(3)),
503            (NodeIndex::new(3), NodeIndex::new(1)),
504            (NodeIndex::new(2), NodeIndex::new(2)),
505        ]);
506        let swaps = token_swapper(&g, mapping, Some(4), Some(4), Some(50));
507        assert_eq!(3, swaps.expect("swap mapping errored").len());
508    }
509
510    #[test]
511    fn test_small_swap() {
512        // Reverse all small swap
513        let g = petgraph::graph::UnGraph::<(), ()>::from_edges([
514            (0, 1),
515            (1, 2),
516            (2, 3),
517            (3, 4),
518            (4, 5),
519            (5, 6),
520            (6, 7),
521        ]);
522        let mut mapping = HashMap::with_capacity(8);
523        for i in 0..8 {
524            mapping.insert(NodeIndex::new(i), NodeIndex::new(7 - i));
525        }
526        // Do the token swap
527        let mut new_map = mapping.clone();
528        let swaps =
529            token_swapper(&g, mapping, Some(4), Some(4), Some(50)).expect("swap mapping errored");
530        do_swap(&mut new_map, &swaps);
531        let mut expected = HashMap::with_capacity(8);
532        for i in 0..8 {
533            expected.insert(NodeIndex::new(i), NodeIndex::new(i));
534        }
535        assert_eq!(expected, new_map);
536    }
537
538    #[test]
539    fn test_happy_swap_chain() {
540        // Reverse all happy swap chain > 2
541        let g = petgraph::graph::UnGraph::<(), ()>::from_edges([
542            (0, 1),
543            (0, 2),
544            (0, 3),
545            (0, 4),
546            (1, 2),
547            (1, 3),
548            (1, 4),
549            (2, 3),
550            (2, 4),
551            (3, 4),
552            (3, 6),
553        ]);
554        let mapping = HashMap::from([
555            (NodeIndex::new(0), NodeIndex::new(4)),
556            (NodeIndex::new(1), NodeIndex::new(0)),
557            (NodeIndex::new(2), NodeIndex::new(3)),
558            (NodeIndex::new(3), NodeIndex::new(6)),
559            (NodeIndex::new(4), NodeIndex::new(2)),
560            (NodeIndex::new(6), NodeIndex::new(1)),
561        ]);
562        // Do the token swap
563        let mut new_map = mapping.clone();
564        let swaps =
565            token_swapper(&g, mapping, Some(4), Some(4), Some(50)).expect("swap mapping errored");
566        do_swap(&mut new_map, &swaps);
567        let mut expected = HashMap::with_capacity(6);
568        for i in (0..5).chain(6..7) {
569            expected.insert(NodeIndex::new(i), NodeIndex::new(i));
570        }
571        assert_eq!(expected, new_map);
572    }
573
574    #[test]
575    fn test_partial_simple() {
576        // Simple partial swap
577        let g = petgraph::graph::UnGraph::<(), ()>::from_edges([(0, 1), (1, 2), (2, 3)]);
578        let mapping = HashMap::from([(NodeIndex::new(0), NodeIndex::new(3))]);
579        let mut new_map = mapping.clone();
580        let swaps =
581            token_swapper(&g, mapping, Some(4), Some(4), Some(1)).expect("swap mapping errored");
582        do_swap(&mut new_map, &swaps);
583        let mut expected = HashMap::with_capacity(4);
584        expected.insert(NodeIndex::new(3), NodeIndex::new(3));
585        assert_eq!(expected, new_map);
586    }
587
588    #[test]
589    fn test_partial_simple_remove_node() {
590        // Simple partial swap
591        let mut g =
592            petgraph::graph::UnGraph::<(), ()>::from_edges([(0, 1), (1, 2), (2, 3), (3, 4)]);
593        let mapping = HashMap::from([(NodeIndex::new(0), NodeIndex::new(3))]);
594        g.remove_node(NodeIndex::new(2));
595        g.add_edge(NodeIndex::new(1), NodeIndex::new(3), ());
596        let mut new_map = mapping.clone();
597        let swaps =
598            token_swapper(&g, mapping, Some(4), Some(4), Some(1)).expect("swap mapping errored");
599        do_swap(&mut new_map, &swaps);
600        let mut expected = HashMap::with_capacity(4);
601        expected.insert(NodeIndex::new(3), NodeIndex::new(3));
602        assert_eq!(expected, new_map);
603    }
604
605    #[test]
606    fn test_partial_small() {
607        // Partial inverting on small path graph
608        let g = petgraph::graph::UnGraph::<(), ()>::from_edges([(0, 1), (1, 2), (2, 3)]);
609        let mapping = HashMap::from([
610            (NodeIndex::new(0), NodeIndex::new(3)),
611            (NodeIndex::new(1), NodeIndex::new(2)),
612        ]);
613        let mut new_map = mapping.clone();
614        let swaps =
615            token_swapper(&g, mapping, Some(4), Some(4), Some(50)).expect("swap mapping errored");
616        do_swap(&mut new_map, &swaps);
617        let expected = HashMap::from([
618            (NodeIndex::new(2), NodeIndex::new(2)),
619            (NodeIndex::new(3), NodeIndex::new(3)),
620        ]);
621        assert_eq!(5, swaps.len());
622        assert_eq!(expected, new_map);
623    }
624
625    #[test]
626    fn test_large_partial_random() {
627        // Test a random (partial) mapping on a large randomly generated graph
628        use crate::generators::gnm_random_graph;
629        use rand::prelude::*;
630        use rand_pcg::Pcg64;
631        use std::iter::zip;
632
633        let mut rng: Pcg64 = Pcg64::seed_from_u64(4);
634
635        // Note that graph may have "gaps" in the node counts, i.e. the numbering is noncontiguous.
636        let size = 100;
637        let mut g: petgraph::stable_graph::StableGraph<(), ()> =
638            gnm_random_graph(size, size.pow(2) / 10, Some(4), || (), || ()).unwrap();
639
640        // Remove self-loops
641        let nodes: Vec<_> = g.node_indices().collect();
642        for node in nodes {
643            if let Some(edge) = g.find_edge(node, node) {
644                g.remove_edge(edge);
645            }
646        }
647        // Make sure the graph is connected by adding C_n
648        for i in 0..(g.node_count() - 1) {
649            g.add_edge(NodeIndex::new(i), NodeIndex::new(i + 1), ());
650        }
651
652        // Get node indices and randomly shuffle
653        let mut mapped_nodes: Vec<usize> = g.node_indices().map(|node| node.index()).collect();
654        let nodes = mapped_nodes.clone();
655        mapped_nodes.shuffle(&mut rng);
656
657        // Zip nodes and shuffled nodes and remove every other one
658        let mut mapping: Vec<(usize, usize)> = zip(nodes, mapped_nodes).collect();
659        mapping.retain(|(a, _)| a % 2 == 0);
660
661        // Convert mapping to HashMap of NodeIndex's
662        let mapping: HashMap<NodeIndex, NodeIndex> = mapping
663            .into_iter()
664            .map(|(a, b)| (NodeIndex::new(a), NodeIndex::new(b)))
665            .collect();
666        let mut new_map = mapping.clone();
667        let expected: HashMap<NodeIndex, NodeIndex> =
668            mapping.values().map(|val| (*val, *val)).collect();
669
670        let swaps =
671            token_swapper(&g, mapping, Some(4), Some(4), Some(50)).expect("swap mapping errored");
672        do_swap(&mut new_map, &swaps);
673        assert_eq!(expected, new_map)
674    }
675
676    #[test]
677    fn test_disjoint_graph_works() {
678        let g = petgraph::graph::UnGraph::<(), ()>::from_edges([(0, 1), (2, 3)]);
679        let mapping = HashMap::from([
680            (NodeIndex::new(1), NodeIndex::new(0)),
681            (NodeIndex::new(0), NodeIndex::new(1)),
682            (NodeIndex::new(2), NodeIndex::new(3)),
683            (NodeIndex::new(3), NodeIndex::new(2)),
684        ]);
685        let mut new_map = mapping.clone();
686        let swaps =
687            token_swapper(&g, mapping, Some(10), Some(4), Some(50)).expect("swap mapping errored");
688        do_swap(&mut new_map, &swaps);
689        let expected = HashMap::from([
690            (NodeIndex::new(2), NodeIndex::new(2)),
691            (NodeIndex::new(3), NodeIndex::new(3)),
692            (NodeIndex::new(1), NodeIndex::new(1)),
693            (NodeIndex::new(0), NodeIndex::new(0)),
694        ]);
695        assert_eq!(2, swaps.len());
696        assert_eq!(expected, new_map);
697    }
698
699    #[test]
700    fn test_disjoint_graph_fails() {
701        let g = petgraph::graph::UnGraph::<(), ()>::from_edges([(0, 1), (2, 3)]);
702        let mapping = HashMap::from([
703            (NodeIndex::new(2), NodeIndex::new(0)),
704            (NodeIndex::new(1), NodeIndex::new(1)),
705            (NodeIndex::new(0), NodeIndex::new(2)),
706            (NodeIndex::new(3), NodeIndex::new(3)),
707        ]);
708        assert!(
709            token_swapper(&g, mapping, Some(10), Some(4), Some(50)).is_err(),
710            "This should error"
711        );
712    }
713
714    #[test]
715    fn test_edgeless_graph_fails() {
716        let mut g = petgraph::graph::UnGraph::<(), ()>::new_undirected();
717        let a = g.add_node(());
718        let b = g.add_node(());
719        let c = g.add_node(());
720        let d = g.add_node(());
721        g.add_edge(c, d, ());
722        let mapping = HashMap::from([(a, b), (b, a)]);
723        assert!(
724            token_swapper(&g, mapping, Some(10), Some(4), Some(50)).is_err(),
725            "This should error"
726        );
727    }
728}