• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Qiskit / rustworkx / 15528744044

09 Jun 2025 06:57AM UTC coverage: 95.281% (-0.001%) from 95.282%
15528744044

Pull #1463

github

web-flow
Merge 7905e7dfa into ac1082ca9
Pull Request #1463: token_swapper to return its swaps as (G::NodeId, G::NodeId) rather than NodeIndex

20 of 21 new or added lines in 1 file covered. (95.24%)

5 existing lines in 1 file now uncovered.

18961 of 19900 relevant lines covered (95.28%)

1059916.57 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.99
/rustworkx-core/src/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

13
use rand::prelude::*;
14
use rand_distr::{StandardUniform, Uniform};
15
use rand_pcg::Pcg64;
16
use std::error::Error;
17
use std::fmt;
18
use std::hash::Hash;
19

20
use hashbrown::HashMap;
21
use petgraph::stable_graph::{NodeIndex, StableGraph};
22
use petgraph::visit::{
23
    EdgeCount, GraphBase, IntoEdges, IntoNeighborsDirected, IntoNodeIdentifiers, NodeCount,
24
    NodeIndexable, Visitable,
25
};
26
use petgraph::Directed;
27
use petgraph::Direction::{Incoming, Outgoing};
28
use rayon::prelude::*;
29
use rayon_cond::CondIterator;
30

31
use crate::connectivity::find_cycle;
32
use crate::dictmap::*;
33
use crate::shortest_path::dijkstra;
34
use crate::traversal::dfs_edges;
35

36
type Swap = (NodeIndex, NodeIndex);
37
type Edge = (NodeIndex, NodeIndex);
38
type SwapList<N> = Vec<(N, N)>;
39
type SwapResult<N> = Result<SwapList<N>, MapNotPossible>;
40

41
/// Error returned by token swapper if the request mapping
42
/// is impossible
43
#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Copy, Clone)]
44
pub struct MapNotPossible;
45

46
impl Error for MapNotPossible {}
47
impl fmt::Display for MapNotPossible {
48
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
×
49
        write!(f, "No mapping possible.")
×
50
    }
×
51
}
52

53
struct TokenSwapper<G: GraphBase>
54
where
55
    G::NodeId: Eq + Hash,
56
{
57
    // The input graph
58
    graph: G,
59
    // The user-supplied mapping to use for swapping tokens
60
    mapping: HashMap<G::NodeId, G::NodeId>,
61
    // Number of trials
62
    trials: usize,
63
    // Seed for random selection of a node for a trial
64
    seed: Option<u64>,
65
    // Threshold for how many nodes will trigger parallel iterator
66
    parallel_threshold: usize,
67
}
68

69
impl<G> TokenSwapper<G>
70
where
71
    G: NodeCount
72
        + EdgeCount
73
        + IntoEdges
74
        + Visitable
75
        + NodeIndexable
76
        + IntoNeighborsDirected
77
        + IntoNodeIdentifiers
78
        + Send
79
        + Sync,
80
    G::NodeId: Hash + Eq + Send + Sync,
81
{
82
    fn new(
18✔
83
        graph: G,
18✔
84
        mapping: HashMap<G::NodeId, G::NodeId>,
18✔
85
        trials: Option<usize>,
18✔
86
        seed: Option<u64>,
18✔
87
        parallel_threshold: Option<usize>,
18✔
88
    ) -> Self {
18✔
89
        TokenSwapper {
18✔
90
            graph,
18✔
91
            mapping,
18✔
92
            trials: trials.unwrap_or(4),
18✔
93
            seed,
18✔
94
            parallel_threshold: parallel_threshold.unwrap_or(50),
18✔
95
        }
18✔
96
    }
18✔
97

98
    fn map(&mut self) -> SwapResult<G::NodeId> {
18✔
99
        let num_nodes = self.graph.node_bound();
18✔
100
        let num_edges = self.graph.edge_count();
18✔
101

18✔
102
        // Directed graph with nodes matching ``graph`` and
18✔
103
        // edges for neighbors closer than nodes
18✔
104
        let mut digraph = StableGraph::with_capacity(num_nodes, num_edges);
18✔
105

18✔
106
        // First fill the digraph with nodes. Then since it's a stable graph,
18✔
107
        // must go through and remove nodes that were removed in original graph
18✔
108
        for _ in 0..self.graph.node_bound() {
280✔
109
            digraph.add_node(());
280✔
110
        }
280✔
111
        let mut count: usize = 0;
18✔
112
        for gnode in self.graph.node_identifiers() {
278✔
113
            let gidx = self.graph.to_index(gnode);
278✔
114
            if gidx != count {
278✔
115
                for idx in count..gidx {
2✔
116
                    digraph.remove_node(NodeIndex::new(idx));
2✔
117
                }
2✔
118
                count = gidx;
2✔
119
            }
276✔
120
            count += 1;
278✔
121
        }
122

123
        // sub will become same as digraph but with no self edges in add_token_edges
124
        let mut sub_digraph = digraph.clone();
18✔
125

18✔
126
        let mut tokens: HashMap<NodeIndex, NodeIndex> = self
18✔
127
            .mapping
18✔
128
            .iter()
18✔
129
            .map(|(k, v)| {
260✔
130
                let k_idx = NodeIndex::new(self.graph.to_index(*k));
260✔
131
                let v_idx = NodeIndex::new(self.graph.to_index(*v));
260✔
132
                (k_idx, v_idx)
260✔
133
            })
260✔
134
            .collect();
18✔
135

18✔
136
        // todo_nodes are all the mapping entries where left != right
18✔
137
        let mut todo_nodes: Vec<NodeIndex> = tokens
18✔
138
            .iter()
18✔
139
            .filter_map(|(node, dest)| if node != dest { Some(*node) } else { None })
260✔
140
            .collect();
18✔
141
        todo_nodes.par_sort();
18✔
142

143
        // Add initial edges to the digraph/sub_digraph
144
        for node in self.graph.node_identifiers() {
272✔
145
            let node_idx = NodeIndex::new(self.graph.to_index(node));
272✔
146
            self.add_token_edges(node_idx, &mut digraph, &mut sub_digraph, &mut tokens)?;
272✔
147
        }
148
        // First collect the self.trial number of random numbers
149
        // into a Vec based on the given seed
150
        let outer_rng: Pcg64 = match self.seed {
16✔
151
            Some(rng_seed) => Pcg64::seed_from_u64(rng_seed),
16✔
NEW
152
            None => Pcg64::from_os_rng(),
×
153
        };
154
        let trial_seeds_vec: Vec<u64> = outer_rng
16✔
155
            .sample_iter(&StandardUniform)
16✔
156
            .take(self.trials)
16✔
157
            .collect();
16✔
158

16✔
159
        CondIterator::new(
16✔
160
            trial_seeds_vec,
16✔
161
            self.graph.node_count() >= self.parallel_threshold,
16✔
162
        )
16✔
163
        .map(|trial_seed| {
76✔
164
            self.trial_map(
76✔
165
                digraph.clone(),
76✔
166
                sub_digraph.clone(),
76✔
167
                tokens.clone(),
76✔
168
                todo_nodes.clone(),
76✔
169
                trial_seed,
76✔
170
            )
76✔
171
        })
76✔
172
        .min_by_key(|result| match result {
76✔
173
            Ok(res) => Ok(res.len()),
76✔
UNCOV
174
            Err(e) => Err(*e),
×
175
        })
76✔
176
        .unwrap()
16✔
177
    }
18✔
178

179
    fn add_token_edges(
3,368✔
180
        &self,
3,368✔
181
        node: NodeIndex,
3,368✔
182
        digraph: &mut StableGraph<(), (), Directed>,
3,368✔
183
        sub_digraph: &mut StableGraph<(), (), Directed>,
3,368✔
184
        tokens: &mut HashMap<NodeIndex, NodeIndex>,
3,368✔
185
    ) -> Result<(), MapNotPossible> {
3,368✔
186
        // Adds an edge to digraph if distance from the token to a neighbor is
3,368✔
187
        // less than distance from token to node. sub_digraph is same except
3,368✔
188
        // for self-edges.
3,368✔
189
        if !tokens.contains_key(&node) {
3,368✔
190
            return Ok(());
90✔
191
        }
3,278✔
192
        if tokens[&node] == node {
3,278✔
193
            digraph.update_edge(node, node, ());
1,192✔
194
            return Ok(());
1,192✔
195
        }
2,086✔
196
        let id_node = self.graph.from_index(node.index());
2,086✔
197
        let id_token = self.graph.from_index(tokens[&node].index());
2,086✔
198

2,086✔
199
        if self.graph.neighbors(id_node).next().is_none() {
2,086✔
UNCOV
200
            return Err(MapNotPossible {});
×
201
        }
2,086✔
202

203
        for id_neighbor in self.graph.neighbors(id_node) {
37,816✔
204
            let neighbor = NodeIndex::new(self.graph.to_index(id_neighbor));
37,816✔
205
            let dist_neighbor: DictMap<G::NodeId, usize> = dijkstra(
37,816✔
206
                &self.graph,
37,816✔
207
                id_neighbor,
37,816✔
208
                Some(id_token),
37,816✔
209
                |_| Ok::<usize, &str>(1),
26,947,386✔
210
                None,
37,816✔
211
            )
37,816✔
212
            .unwrap();
37,816✔
213

37,816✔
214
            let dist_node: DictMap<G::NodeId, usize> = dijkstra(
37,816✔
215
                &self.graph,
37,816✔
216
                id_node,
37,816✔
217
                Some(id_token),
37,816✔
218
                |_| Ok::<usize, &str>(1),
18,979,370✔
219
                None,
37,816✔
220
            )
37,816✔
221
            .unwrap();
37,816✔
222
            let neigh_dist = dist_neighbor.get(&id_token);
37,816✔
223
            let node_dist = dist_node.get(&id_token);
37,816✔
224
            if neigh_dist.is_none() {
37,816✔
225
                return Err(MapNotPossible {});
2✔
226
            }
37,814✔
227
            if node_dist.is_none() {
37,814✔
UNCOV
228
                return Err(MapNotPossible {});
×
229
            }
37,814✔
230

37,814✔
231
            if neigh_dist < node_dist {
37,814✔
232
                digraph.update_edge(node, neighbor, ());
4,850✔
233
                sub_digraph.update_edge(node, neighbor, ());
4,850✔
234
            }
32,964✔
235
        }
236
        Ok(())
2,084✔
237
    }
3,368✔
238

239
    fn trial_map(
76✔
240
        &self,
76✔
241
        mut digraph: StableGraph<(), (), Directed>,
76✔
242
        mut sub_digraph: StableGraph<(), (), Directed>,
76✔
243
        mut tokens: HashMap<NodeIndex, NodeIndex>,
76✔
244
        mut todo_nodes: Vec<NodeIndex>,
76✔
245
        trial_seed: u64,
76✔
246
    ) -> SwapResult<G::NodeId> {
76✔
247
        // Create a random trial list of swaps to move tokens to optimal positions
76✔
248
        let mut steps = 0;
76✔
249
        let mut swap_edges: Vec<Swap> = vec![];
76✔
250
        let mut rng_seed: Pcg64 = Pcg64::seed_from_u64(trial_seed);
76✔
251
        while !todo_nodes.is_empty() && steps <= 4 * digraph.node_count().pow(2) {
864✔
252
            // Choose a random todo_node
253
            let between = Uniform::new(0, todo_nodes.len()).map_err(|_| MapNotPossible {})?;
788✔
254
            let random: usize = between.sample(&mut rng_seed);
788✔
255
            let todo_node = todo_nodes[random];
788✔
256

788✔
257
            // If there's a cycle in sub_digraph, add it to swap_edges and do swap
788✔
258
            let cycle = find_cycle(&sub_digraph, Some(todo_node));
788✔
259
            if !cycle.is_empty() {
788✔
260
                for edge in cycle[1..].iter().rev() {
1,304✔
261
                    swap_edges.push(*edge);
1,304✔
262
                    self.swap(
1,304✔
263
                        edge.0,
1,304✔
264
                        edge.1,
1,304✔
265
                        &mut digraph,
1,304✔
266
                        &mut sub_digraph,
1,304✔
267
                        &mut tokens,
1,304✔
268
                        &mut todo_nodes,
1,304✔
269
                    )?;
1,304✔
270
                }
271
                steps += cycle.len() - 1;
544✔
272
            // If there's no cycle, see if there's an edge target that matches a token key.
273
            // If so, add to swap_edges and do swap
274
            } else {
275
                let mut found = false;
244✔
276
                let sub2 = &sub_digraph.clone();
244✔
277
                for edge in dfs_edges(sub2, Some(todo_node)) {
752✔
278
                    let new_edge = (NodeIndex::new(edge.0), NodeIndex::new(edge.1));
752✔
279
                    if !tokens.contains_key(&new_edge.1) {
752✔
280
                        swap_edges.push(new_edge);
72✔
281
                        self.swap(
72✔
282
                            new_edge.0,
72✔
283
                            new_edge.1,
72✔
284
                            &mut digraph,
72✔
285
                            &mut sub_digraph,
72✔
286
                            &mut tokens,
72✔
287
                            &mut todo_nodes,
72✔
288
                        )?;
72✔
289
                        steps += 1;
72✔
290
                        found = true;
72✔
291
                        break;
72✔
292
                    }
680✔
293
                }
294
                // If none found, look for cycle in digraph which will result in
295
                // an unhappy node. Look for a predecessor and add node and pred
296
                // to swap_edges and do swap
297
                if !found {
244✔
298
                    let cycle: Vec<Edge> = find_cycle(&digraph, Some(todo_node));
172✔
299
                    let unhappy_node = cycle[0].0;
172✔
300
                    let mut found = false;
172✔
301
                    let di2 = &mut digraph.clone();
172✔
302
                    for predecessor in di2.neighbors_directed(unhappy_node, Incoming) {
268✔
303
                        if predecessor != unhappy_node {
268✔
304
                            swap_edges.push((unhappy_node, predecessor));
172✔
305
                            self.swap(
172✔
306
                                unhappy_node,
172✔
307
                                predecessor,
172✔
308
                                &mut digraph,
172✔
309
                                &mut sub_digraph,
172✔
310
                                &mut tokens,
172✔
311
                                &mut todo_nodes,
172✔
312
                            )?;
172✔
313
                            steps += 1;
172✔
314
                            found = true;
172✔
315
                            break;
172✔
316
                        }
96✔
317
                    }
318
                    assert!(
172✔
319
                        found,
172✔
UNCOV
320
                        "The token swap process has ended unexpectedly, this points to a bug in rustworkx, please open an issue."
×
321
                    );
322
                }
72✔
323
            }
324
        }
325
        assert!(
76✔
326
            todo_nodes.is_empty(),
76✔
UNCOV
327
            "The output final swap map is incomplete, this points to a bug in rustworkx, please open an issue."
×
328
        );
329
        let result: Vec<(G::NodeId, G::NodeId)> = swap_edges
76✔
330
            .into_iter()
76✔
331
            .map(|(ni1, ni2)| {
1,548✔
332
                let id1 = self.graph.from_index(ni1.index());
1,548✔
333
                let id2 = self.graph.from_index(ni2.index());
1,548✔
334
                (id1, id2)
1,548✔
335
            })
1,548✔
336
            .collect();
76✔
337
        Ok(result)
76✔
338
    }
76✔
339

340
    fn swap(
1,548✔
341
        &self,
1,548✔
342
        node1: NodeIndex,
1,548✔
343
        node2: NodeIndex,
1,548✔
344
        digraph: &mut StableGraph<(), (), Directed>,
1,548✔
345
        sub_digraph: &mut StableGraph<(), (), Directed>,
1,548✔
346
        tokens: &mut HashMap<NodeIndex, NodeIndex>,
1,548✔
347
        todo_nodes: &mut Vec<NodeIndex>,
1,548✔
348
    ) -> Result<(), MapNotPossible> {
1,548✔
349
        // Get token values for the 2 nodes and remove them
1,548✔
350
        let token1 = tokens.remove(&node1);
1,548✔
351
        let token2 = tokens.remove(&node2);
1,548✔
352

353
        // Swap the token edge values
354
        if let Some(t2) = token2 {
1,548✔
355
            tokens.insert(node1, t2);
1,476✔
356
        }
1,476✔
357
        if let Some(t1) = token1 {
1,548✔
358
            tokens.insert(node2, t1);
1,548✔
359
        }
1,548✔
360
        // For each node, remove the (node, successor) from digraph and
361
        // sub_digraph. Then add new token edges back in.
362
        for node in [node1, node2] {
3,096✔
363
            let edge_nodes: Vec<(NodeIndex, NodeIndex)> = digraph
3,096✔
364
                .neighbors_directed(node, Outgoing)
3,096✔
365
                .map(|successor| (node, successor))
7,264✔
366
                .collect();
3,096✔
367
            for (edge_node1, edge_node2) in edge_nodes {
10,360✔
368
                let edge = digraph.find_edge(edge_node1, edge_node2).unwrap();
7,264✔
369
                digraph.remove_edge(edge);
7,264✔
370
            }
7,264✔
371
            let edge_nodes: Vec<(NodeIndex, NodeIndex)> = sub_digraph
3,096✔
372
                .neighbors_directed(node, Outgoing)
3,096✔
373
                .map(|successor| (node, successor))
7,092✔
374
                .collect();
3,096✔
375
            for (edge_node1, edge_node2) in edge_nodes {
10,188✔
376
                let edge = sub_digraph.find_edge(edge_node1, edge_node2).unwrap();
7,092✔
377
                sub_digraph.remove_edge(edge);
7,092✔
378
            }
7,092✔
379
            self.add_token_edges(node, digraph, sub_digraph, tokens)?;
3,096✔
380

381
            // If a node is a token key and not equal to the value, add it to todo_nodes
382
            if tokens.contains_key(&node) && tokens[&node] != node {
3,096✔
383
                if !todo_nodes.contains(&node) {
1,844✔
384
                    todo_nodes.push(node);
204✔
385
                }
1,640✔
386
            // Otherwise if node is in todo_nodes, remove it
387
            } else if todo_nodes.contains(&node) {
1,252✔
388
                todo_nodes.swap_remove(todo_nodes.iter().position(|x| *x == node).unwrap());
21,456✔
389
            }
1,212✔
390
        }
391
        Ok(())
1,548✔
392
    }
1,548✔
393
}
394

395
/// Module to perform an approximately optimal Token Swapping algorithm. Supports partial
396
/// mappings (i.e. not-permutations) for graphs with missing tokens.
397
///
398
/// Based on the paper: Approximation and Hardness for Token Swapping by Miltzow et al. (2016)
399
/// ArXiV: <https://arxiv.org/abs/1602.05150>
400
///
401
/// Arguments:
402
///
403
/// * `graph` - The graph on which to perform the token swapping.
404
/// * `mapping` - A partial mapping to be implemented in swaps.
405
/// * `trials` - Optional number of trials. If None, defaults to 4.
406
/// * `seed` - Optional integer seed. If None, the internal rng will be initialized from system entropy.
407
/// * `parallel_threshold` - Optional integer for the number of nodes in the graph that will
408
///   trigger the use of parallel threads. If the number of nodes in the graph is less than this value
409
///   it will run in a single thread. The default value is 50.
410
///
411
/// It returns a list of tuples representing the swaps to perform, where each tuple contains
412
/// node identifiers of type `(G::NodeId, G::NodeId)`. The result will be an
413
/// `Err(MapNotPossible)` if the `token_swapper()` function can't find a mapping.
414
///
415
/// This function is multithreaded and will launch a thread pool with threads equal to
416
/// the number of CPUs by default. You can tune the number of threads with
417
/// the ``RAYON_NUM_THREADS`` environment variable. For example, setting ``RAYON_NUM_THREADS=4``
418
/// would limit the thread pool to 4 threads.
419
///
420
/// # Example
421
/// ```rust
422
/// use hashbrown::HashMap;
423
/// use rustworkx_core::petgraph;
424
/// use rustworkx_core::token_swapper::token_swapper;
425
/// use rustworkx_core::petgraph::graph::NodeIndex;
426
///
427
///  let g = petgraph::graph::UnGraph::<(), ()>::from_edges(&[(0, 1), (1, 2), (2, 3)]);
428
///  let mapping = HashMap::from([
429
///   (NodeIndex::new(0), NodeIndex::new(0)),
430
///   (NodeIndex::new(1), NodeIndex::new(3)),
431
///   (NodeIndex::new(3), NodeIndex::new(1)),
432
///   (NodeIndex::new(2), NodeIndex::new(2)),
433
///  ]);
434
///  // Do the token swap
435
///  let output = token_swapper(&g, mapping, Some(4), Some(4), Some(50)).expect("Swap mapping failed.");
436
///  assert_eq!(3, output.len());
437
///
438
/// ```
439
pub fn token_swapper<G>(
18✔
440
    graph: G,
18✔
441
    mapping: HashMap<G::NodeId, G::NodeId>,
18✔
442
    trials: Option<usize>,
18✔
443
    seed: Option<u64>,
18✔
444
    parallel_threshold: Option<usize>,
18✔
445
) -> SwapResult<G::NodeId>
18✔
446
where
18✔
447
    G: NodeCount
18✔
448
        + EdgeCount
18✔
449
        + IntoEdges
18✔
450
        + Visitable
18✔
451
        + NodeIndexable
18✔
452
        + IntoNeighborsDirected
18✔
453
        + IntoNodeIdentifiers
18✔
454
        + Send
18✔
455
        + Sync,
18✔
456
    G::NodeId: Hash + Eq + Send + Sync,
18✔
457
{
18✔
458
    let mut swapper = TokenSwapper::new(graph, mapping, trials, seed, parallel_threshold);
18✔
459
    swapper.map()
18✔
460
}
18✔
461

462
#[cfg(test)]
463
mod test_token_swapper {
464

465
    use crate::petgraph;
466
    use crate::token_swapper::token_swapper;
467
    use hashbrown::HashMap;
468
    use petgraph::graph::NodeIndex;
469

470
    fn do_swap(mapping: &mut HashMap<NodeIndex, NodeIndex>, swaps: &Vec<(NodeIndex, NodeIndex)>) {
471
        // Apply the swaps to the mapping to get final result
472
        for (swap1, swap2) in swaps {
473
            //Need to create temp nodes in case of partial mapping
474
            let mut temp_node1: Option<NodeIndex> = None;
475
            let mut temp_node2: Option<NodeIndex> = None;
476
            if mapping.contains_key(swap1) {
477
                temp_node1 = Some(mapping[swap1]);
478
                mapping.remove(swap1);
479
            }
480
            if mapping.contains_key(swap2) {
481
                temp_node2 = Some(mapping[swap2]);
482
                mapping.remove(swap2);
483
            }
484
            if let Some(t1) = temp_node1 {
485
                mapping.insert(*swap2, t1);
486
            }
487
            if let Some(t2) = temp_node2 {
488
                mapping.insert(*swap1, t2);
489
            }
490
        }
491
    }
492

493
    #[test]
494
    fn test_simple_swap() {
495
        // Simple arbitrary swap
496
        let g = petgraph::graph::UnGraph::<(), ()>::from_edges([(0, 1), (1, 2), (2, 3)]);
497
        let mapping = HashMap::from([
498
            (NodeIndex::new(0), NodeIndex::new(0)),
499
            (NodeIndex::new(1), NodeIndex::new(3)),
500
            (NodeIndex::new(3), NodeIndex::new(1)),
501
            (NodeIndex::new(2), NodeIndex::new(2)),
502
        ]);
503
        let swaps = token_swapper(&g, mapping, Some(4), Some(4), Some(50));
504
        assert_eq!(3, swaps.expect("swap mapping errored").len());
505
    }
506

507
    #[test]
508
    fn test_small_swap() {
509
        // Reverse all small swap
510
        let g = petgraph::graph::UnGraph::<(), ()>::from_edges([
511
            (0, 1),
512
            (1, 2),
513
            (2, 3),
514
            (3, 4),
515
            (4, 5),
516
            (5, 6),
517
            (6, 7),
518
        ]);
519
        let mut mapping = HashMap::with_capacity(8);
520
        for i in 0..8 {
521
            mapping.insert(NodeIndex::new(i), NodeIndex::new(7 - i));
522
        }
523
        // Do the token swap
524
        let mut new_map = mapping.clone();
525
        let swaps =
526
            token_swapper(&g, mapping, Some(4), Some(4), Some(50)).expect("swap mapping errored");
527
        do_swap(&mut new_map, &swaps);
528
        let mut expected = HashMap::with_capacity(8);
529
        for i in 0..8 {
530
            expected.insert(NodeIndex::new(i), NodeIndex::new(i));
531
        }
532
        assert_eq!(expected, new_map);
533
    }
534

535
    #[test]
536
    fn test_happy_swap_chain() {
537
        // Reverse all happy swap chain > 2
538
        let g = petgraph::graph::UnGraph::<(), ()>::from_edges([
539
            (0, 1),
540
            (0, 2),
541
            (0, 3),
542
            (0, 4),
543
            (1, 2),
544
            (1, 3),
545
            (1, 4),
546
            (2, 3),
547
            (2, 4),
548
            (3, 4),
549
            (3, 6),
550
        ]);
551
        let mapping = HashMap::from([
552
            (NodeIndex::new(0), NodeIndex::new(4)),
553
            (NodeIndex::new(1), NodeIndex::new(0)),
554
            (NodeIndex::new(2), NodeIndex::new(3)),
555
            (NodeIndex::new(3), NodeIndex::new(6)),
556
            (NodeIndex::new(4), NodeIndex::new(2)),
557
            (NodeIndex::new(6), NodeIndex::new(1)),
558
        ]);
559
        // Do the token swap
560
        let mut new_map = mapping.clone();
561
        let swaps =
562
            token_swapper(&g, mapping, Some(4), Some(4), Some(50)).expect("swap mapping errored");
563
        do_swap(&mut new_map, &swaps);
564
        let mut expected = HashMap::with_capacity(6);
565
        for i in (0..5).chain(6..7) {
566
            expected.insert(NodeIndex::new(i), NodeIndex::new(i));
567
        }
568
        assert_eq!(expected, new_map);
569
    }
570

571
    #[test]
572
    fn test_partial_simple() {
573
        // Simple partial swap
574
        let g = petgraph::graph::UnGraph::<(), ()>::from_edges([(0, 1), (1, 2), (2, 3)]);
575
        let mapping = HashMap::from([(NodeIndex::new(0), NodeIndex::new(3))]);
576
        let mut new_map = mapping.clone();
577
        let swaps =
578
            token_swapper(&g, mapping, Some(4), Some(4), Some(1)).expect("swap mapping errored");
579
        do_swap(&mut new_map, &swaps);
580
        let mut expected = HashMap::with_capacity(4);
581
        expected.insert(NodeIndex::new(3), NodeIndex::new(3));
582
        assert_eq!(expected, new_map);
583
    }
584

585
    #[test]
586
    fn test_partial_simple_remove_node() {
587
        // Simple partial swap
588
        let mut g =
589
            petgraph::graph::UnGraph::<(), ()>::from_edges([(0, 1), (1, 2), (2, 3), (3, 4)]);
590
        let mapping = HashMap::from([(NodeIndex::new(0), NodeIndex::new(3))]);
591
        g.remove_node(NodeIndex::new(2));
592
        g.add_edge(NodeIndex::new(1), NodeIndex::new(3), ());
593
        let mut new_map = mapping.clone();
594
        let swaps =
595
            token_swapper(&g, mapping, Some(4), Some(4), Some(1)).expect("swap mapping errored");
596
        do_swap(&mut new_map, &swaps);
597
        let mut expected = HashMap::with_capacity(4);
598
        expected.insert(NodeIndex::new(3), NodeIndex::new(3));
599
        assert_eq!(expected, new_map);
600
    }
601

602
    #[test]
603
    fn test_partial_small() {
604
        // Partial inverting on small path graph
605
        let g = petgraph::graph::UnGraph::<(), ()>::from_edges([(0, 1), (1, 2), (2, 3)]);
606
        let mapping = HashMap::from([
607
            (NodeIndex::new(0), NodeIndex::new(3)),
608
            (NodeIndex::new(1), NodeIndex::new(2)),
609
        ]);
610
        let mut new_map = mapping.clone();
611
        let swaps =
612
            token_swapper(&g, mapping, Some(4), Some(4), Some(50)).expect("swap mapping errored");
613
        do_swap(&mut new_map, &swaps);
614
        let expected = HashMap::from([
615
            (NodeIndex::new(2), NodeIndex::new(2)),
616
            (NodeIndex::new(3), NodeIndex::new(3)),
617
        ]);
618
        assert_eq!(5, swaps.len());
619
        assert_eq!(expected, new_map);
620
    }
621

622
    #[test]
623
    fn test_large_partial_random() {
624
        // Test a random (partial) mapping on a large randomly generated graph
625
        use crate::generators::gnm_random_graph;
626
        use rand::prelude::*;
627
        use rand_pcg::Pcg64;
628
        use std::iter::zip;
629

630
        let mut rng: Pcg64 = Pcg64::seed_from_u64(4);
631

632
        // Note that graph may have "gaps" in the node counts, i.e. the numbering is noncontiguous.
633
        let size = 100;
634
        let mut g: petgraph::stable_graph::StableGraph<(), ()> =
635
            gnm_random_graph(size, size.pow(2) / 10, Some(4), || (), || ()).unwrap();
636

637
        // Remove self-loops
638
        let nodes: Vec<_> = g.node_indices().collect();
639
        for node in nodes {
640
            if let Some(edge) = g.find_edge(node, node) {
641
                g.remove_edge(edge);
642
            }
643
        }
644
        // Make sure the graph is connected by adding C_n
645
        for i in 0..(g.node_count() - 1) {
646
            g.add_edge(NodeIndex::new(i), NodeIndex::new(i + 1), ());
647
        }
648

649
        // Get node indices and randomly shuffle
650
        let mut mapped_nodes: Vec<usize> = g.node_indices().map(|node| node.index()).collect();
651
        let nodes = mapped_nodes.clone();
652
        mapped_nodes.shuffle(&mut rng);
653

654
        // Zip nodes and shuffled nodes and remove every other one
655
        let mut mapping: Vec<(usize, usize)> = zip(nodes, mapped_nodes).collect();
656
        mapping.retain(|(a, _)| a % 2 == 0);
657

658
        // Convert mapping to HashMap of NodeIndex's
659
        let mapping: HashMap<NodeIndex, NodeIndex> = mapping
660
            .into_iter()
661
            .map(|(a, b)| (NodeIndex::new(a), NodeIndex::new(b)))
662
            .collect();
663
        let mut new_map = mapping.clone();
664
        let expected: HashMap<NodeIndex, NodeIndex> =
665
            mapping.values().map(|val| (*val, *val)).collect();
666

667
        let swaps =
668
            token_swapper(&g, mapping, Some(4), Some(4), Some(50)).expect("swap mapping errored");
669
        do_swap(&mut new_map, &swaps);
670
        assert_eq!(expected, new_map)
671
    }
672

673
    #[test]
674
    fn test_disjoint_graph_works() {
675
        let g = petgraph::graph::UnGraph::<(), ()>::from_edges([(0, 1), (2, 3)]);
676
        let mapping = HashMap::from([
677
            (NodeIndex::new(1), NodeIndex::new(0)),
678
            (NodeIndex::new(0), NodeIndex::new(1)),
679
            (NodeIndex::new(2), NodeIndex::new(3)),
680
            (NodeIndex::new(3), NodeIndex::new(2)),
681
        ]);
682
        let mut new_map = mapping.clone();
683
        let swaps =
684
            token_swapper(&g, mapping, Some(10), Some(4), Some(50)).expect("swap mapping errored");
685
        do_swap(&mut new_map, &swaps);
686
        let expected = HashMap::from([
687
            (NodeIndex::new(2), NodeIndex::new(2)),
688
            (NodeIndex::new(3), NodeIndex::new(3)),
689
            (NodeIndex::new(1), NodeIndex::new(1)),
690
            (NodeIndex::new(0), NodeIndex::new(0)),
691
        ]);
692
        assert_eq!(2, swaps.len());
693
        assert_eq!(expected, new_map);
694
    }
695

696
    #[test]
697
    fn test_disjoint_graph_fails() {
698
        let g = petgraph::graph::UnGraph::<(), ()>::from_edges([(0, 1), (2, 3)]);
699
        let mapping = HashMap::from([
700
            (NodeIndex::new(2), NodeIndex::new(0)),
701
            (NodeIndex::new(1), NodeIndex::new(1)),
702
            (NodeIndex::new(0), NodeIndex::new(2)),
703
            (NodeIndex::new(3), NodeIndex::new(3)),
704
        ]);
705
        assert!(
706
            token_swapper(&g, mapping, Some(10), Some(4), Some(50)).is_err(),
707
            "This should error"
708
        );
709
    }
710

711
    #[test]
712
    fn test_edgeless_graph_fails() {
713
        let mut g = petgraph::graph::UnGraph::<(), ()>::new_undirected();
714
        let a = g.add_node(());
715
        let b = g.add_node(());
716
        let c = g.add_node(());
717
        let d = g.add_node(());
718
        g.add_edge(c, d, ());
719
        let mapping = HashMap::from([(a, b), (b, a)]);
720
        assert!(
721
            token_swapper(&g, mapping, Some(10), Some(4), Some(50)).is_err(),
722
            "This should error"
723
        );
724
    }
725
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc