quantrs2_circuit/routing/
coupling_map.rs

1//! Coupling map representation for quantum devices
2//!
3//! This module defines the physical connectivity graph of quantum devices
4//! and provides utilities for working with device topologies.
5
6use serde::{Deserialize, Serialize};
7use std::collections::{HashMap, HashSet, VecDeque};
8
9/// Distance type for coupling maps
10pub type Distance = usize;
11
12/// Coupling map representing the connectivity graph of a quantum device
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct CouplingMap {
15    /// Number of physical qubits
16    num_qubits: usize,
17    /// Adjacency list representation
18    edges: Vec<Vec<usize>>,
19    /// Pre-computed distance matrix
20    distances: Option<Vec<Vec<Distance>>>,
21}
22
23impl CouplingMap {
24    /// Create a new coupling map with the specified number of qubits
25    #[must_use]
26    pub fn new(num_qubits: usize) -> Self {
27        Self {
28            num_qubits,
29            edges: vec![Vec::new(); num_qubits],
30            distances: None,
31        }
32    }
33
34    /// Add a bidirectional edge between two qubits
35    pub fn add_edge(&mut self, qubit1: usize, qubit2: usize) {
36        if qubit1 < self.num_qubits && qubit2 < self.num_qubits && qubit1 != qubit2 {
37            if !self.edges[qubit1].contains(&qubit2) {
38                self.edges[qubit1].push(qubit2);
39            }
40            if !self.edges[qubit2].contains(&qubit1) {
41                self.edges[qubit2].push(qubit1);
42            }
43            // Invalidate distance cache
44            self.distances = None;
45        }
46    }
47
48    /// Check if two qubits are directly connected
49    #[must_use]
50    pub fn are_connected(&self, qubit1: usize, qubit2: usize) -> bool {
51        if qubit1 < self.num_qubits && qubit2 < self.num_qubits {
52            self.edges[qubit1].contains(&qubit2)
53        } else {
54            false
55        }
56    }
57
58    /// Get the neighbors of a qubit
59    #[must_use]
60    pub fn neighbors(&self, qubit: usize) -> &[usize] {
61        if qubit < self.num_qubits {
62            &self.edges[qubit]
63        } else {
64            &[]
65        }
66    }
67
68    /// Get the number of qubits
69    #[must_use]
70    pub const fn num_qubits(&self) -> usize {
71        self.num_qubits
72    }
73
74    /// Get all edges as pairs
75    #[must_use]
76    pub fn edges(&self) -> Vec<(usize, usize)> {
77        let mut edges = Vec::new();
78        for (i, neighbors) in self.edges.iter().enumerate() {
79            for &j in neighbors {
80                if i < j {
81                    // Avoid duplicates
82                    edges.push((i, j));
83                }
84            }
85        }
86        edges
87    }
88
89    /// Compute the distance between two qubits using BFS
90    #[must_use]
91    pub fn distance(&self, qubit1: usize, qubit2: usize) -> Distance {
92        if qubit1 == qubit2 {
93            return 0;
94        }
95
96        if qubit1 >= self.num_qubits || qubit2 >= self.num_qubits {
97            return Distance::MAX;
98        }
99
100        // Use pre-computed distances if available
101        if let Some(ref distances) = self.distances {
102            return distances[qubit1][qubit2];
103        }
104
105        // BFS to find shortest path
106        let mut queue = VecDeque::new();
107        let mut visited = vec![false; self.num_qubits];
108        let mut distances = vec![Distance::MAX; self.num_qubits];
109
110        queue.push_back(qubit1);
111        visited[qubit1] = true;
112        distances[qubit1] = 0;
113
114        while let Some(current) = queue.pop_front() {
115            if current == qubit2 {
116                return distances[current];
117            }
118
119            for &neighbor in &self.edges[current] {
120                if !visited[neighbor] {
121                    visited[neighbor] = true;
122                    distances[neighbor] = distances[current] + 1;
123                    queue.push_back(neighbor);
124                }
125            }
126        }
127
128        Distance::MAX
129    }
130
131    /// Pre-compute all-pairs shortest distances
132    pub fn compute_distances(&mut self) {
133        let mut distances = vec![vec![Distance::MAX; self.num_qubits]; self.num_qubits];
134
135        // Initialize diagonal and direct edges
136        for i in 0..self.num_qubits {
137            distances[i][i] = 0;
138            for &j in &self.edges[i] {
139                distances[i][j] = 1;
140            }
141        }
142
143        // Floyd-Warshall algorithm
144        for k in 0..self.num_qubits {
145            for i in 0..self.num_qubits {
146                for j in 0..self.num_qubits {
147                    if distances[i][k] != Distance::MAX && distances[k][j] != Distance::MAX {
148                        let new_dist = distances[i][k] + distances[k][j];
149                        if new_dist < distances[i][j] {
150                            distances[i][j] = new_dist;
151                        }
152                    }
153                }
154            }
155        }
156
157        self.distances = Some(distances);
158    }
159
160    /// Get the shortest path between two qubits
161    #[must_use]
162    pub fn shortest_path(&self, start: usize, end: usize) -> Option<Vec<usize>> {
163        if start == end {
164            return Some(vec![start]);
165        }
166
167        if start >= self.num_qubits || end >= self.num_qubits {
168            return None;
169        }
170
171        // BFS to find path
172        let mut queue = VecDeque::new();
173        let mut visited = vec![false; self.num_qubits];
174        let mut parent = vec![None; self.num_qubits];
175
176        queue.push_back(start);
177        visited[start] = true;
178
179        while let Some(current) = queue.pop_front() {
180            if current == end {
181                // Reconstruct path
182                let mut path = Vec::new();
183                let mut node = Some(end);
184
185                while let Some(n) = node {
186                    path.push(n);
187                    node = parent[n];
188                }
189
190                path.reverse();
191                return Some(path);
192            }
193
194            for &neighbor in &self.edges[current] {
195                if !visited[neighbor] {
196                    visited[neighbor] = true;
197                    parent[neighbor] = Some(current);
198                    queue.push_back(neighbor);
199                }
200            }
201        }
202
203        None
204    }
205
206    /// Check if the graph is connected
207    #[must_use]
208    pub fn is_connected(&self) -> bool {
209        if self.num_qubits <= 1 {
210            return true;
211        }
212
213        let mut visited = vec![false; self.num_qubits];
214        let mut queue = VecDeque::new();
215
216        // Start from qubit 0
217        queue.push_back(0);
218        visited[0] = true;
219        let mut count = 1;
220
221        while let Some(current) = queue.pop_front() {
222            for &neighbor in &self.edges[current] {
223                if !visited[neighbor] {
224                    visited[neighbor] = true;
225                    queue.push_back(neighbor);
226                    count += 1;
227                }
228            }
229        }
230
231        count == self.num_qubits
232    }
233
234    /// Get the diameter of the graph (maximum distance between any two nodes)
235    #[must_use]
236    pub fn diameter(&self) -> Distance {
237        let mut max_distance = 0;
238
239        for i in 0..self.num_qubits {
240            for j in (i + 1)..self.num_qubits {
241                let dist = self.distance(i, j);
242                if dist != Distance::MAX {
243                    max_distance = max_distance.max(dist);
244                }
245            }
246        }
247
248        max_distance
249    }
250
251    /// Create common device topologies
252
253    /// Linear topology (1D chain)
254    #[must_use]
255    pub fn linear(num_qubits: usize) -> Self {
256        let mut coupling_map = Self::new(num_qubits);
257        for i in 0..(num_qubits.saturating_sub(1)) {
258            coupling_map.add_edge(i, i + 1);
259        }
260        coupling_map.compute_distances();
261        coupling_map
262    }
263
264    /// Ring topology (circular)
265    #[must_use]
266    pub fn ring(num_qubits: usize) -> Self {
267        let mut coupling_map = Self::linear(num_qubits);
268        if num_qubits > 2 {
269            coupling_map.add_edge(0, num_qubits - 1);
270        }
271        coupling_map.compute_distances();
272        coupling_map
273    }
274
275    /// Grid topology (2D)
276    #[must_use]
277    pub fn grid(rows: usize, cols: usize) -> Self {
278        let num_qubits = rows * cols;
279        let mut coupling_map = Self::new(num_qubits);
280
281        for row in 0..rows {
282            for col in 0..cols {
283                let qubit = row * cols + col;
284
285                // Connect to right neighbor
286                if col + 1 < cols {
287                    coupling_map.add_edge(qubit, qubit + 1);
288                }
289
290                // Connect to bottom neighbor
291                if row + 1 < rows {
292                    coupling_map.add_edge(qubit, qubit + cols);
293                }
294            }
295        }
296
297        coupling_map.compute_distances();
298        coupling_map
299    }
300
301    /// All-to-all topology (complete graph)
302    #[must_use]
303    pub fn all_to_all(num_qubits: usize) -> Self {
304        let mut coupling_map = Self::new(num_qubits);
305        for i in 0..num_qubits {
306            for j in (i + 1)..num_qubits {
307                coupling_map.add_edge(i, j);
308            }
309        }
310        coupling_map.compute_distances();
311        coupling_map
312    }
313
314    /// IBM Lagos device topology
315    #[must_use]
316    pub fn ibm_lagos() -> Self {
317        let mut coupling_map = Self::new(7);
318
319        // Lagos connectivity: heavy-hex layout
320        let edges = [(0, 1), (1, 2), (1, 4), (2, 3), (3, 6), (4, 5), (5, 6)];
321
322        for (q1, q2) in edges {
323            coupling_map.add_edge(q1, q2);
324        }
325
326        coupling_map.compute_distances();
327        coupling_map
328    }
329
330    /// IBM Nairobi device topology
331    #[must_use]
332    pub fn ibm_nairobi() -> Self {
333        let mut coupling_map = Self::new(7);
334
335        // Nairobi connectivity
336        let edges = [(0, 1), (1, 2), (1, 3), (3, 5), (4, 5), (5, 6)];
337
338        for (q1, q2) in edges {
339            coupling_map.add_edge(q1, q2);
340        }
341
342        coupling_map.compute_distances();
343        coupling_map
344    }
345
346    /// Google Sycamore-like device topology
347    #[must_use]
348    pub fn google_sycamore() -> Self {
349        let mut coupling_map = Self::new(12);
350
351        // Simplified Sycamore-like 2D grid with some missing connections
352        let edges = [
353            (0, 1),
354            (0, 4),
355            (1, 2),
356            (1, 5),
357            (2, 3),
358            (2, 6),
359            (3, 7),
360            (4, 5),
361            (4, 8),
362            (5, 6),
363            (5, 9),
364            (6, 7),
365            (6, 10),
366            (7, 11),
367            (8, 9),
368            (9, 10),
369            (10, 11),
370        ];
371
372        for (q1, q2) in edges {
373            coupling_map.add_edge(q1, q2);
374        }
375
376        coupling_map.compute_distances();
377        coupling_map
378    }
379
380    /// Load coupling map from adjacency list
381    #[must_use]
382    pub fn from_edges(num_qubits: usize, edges: &[(usize, usize)]) -> Self {
383        let mut coupling_map = Self::new(num_qubits);
384        for &(q1, q2) in edges {
385            coupling_map.add_edge(q1, q2);
386        }
387        coupling_map.compute_distances();
388        coupling_map
389    }
390}
391
392impl Default for CouplingMap {
393    fn default() -> Self {
394        Self::linear(5)
395    }
396}
397
398#[cfg(test)]
399mod tests {
400    use super::*;
401
402    #[test]
403    fn test_linear_coupling_map() {
404        let coupling_map = CouplingMap::linear(5);
405
406        assert_eq!(coupling_map.num_qubits(), 5);
407        assert!(coupling_map.are_connected(0, 1));
408        assert!(coupling_map.are_connected(1, 2));
409        assert!(!coupling_map.are_connected(0, 2));
410
411        assert_eq!(coupling_map.distance(0, 4), 4);
412        assert_eq!(coupling_map.distance(1, 3), 2);
413    }
414
415    #[test]
416    fn test_grid_coupling_map() {
417        let coupling_map = CouplingMap::grid(2, 3);
418
419        assert_eq!(coupling_map.num_qubits(), 6);
420        assert!(coupling_map.are_connected(0, 1));
421        assert!(coupling_map.are_connected(0, 3));
422        assert!(!coupling_map.are_connected(0, 2));
423    }
424
425    #[test]
426    fn test_shortest_path() {
427        let coupling_map = CouplingMap::linear(5);
428
429        let path = coupling_map
430            .shortest_path(0, 4)
431            .expect("shortest_path 0->4 should succeed");
432        assert_eq!(path, vec![0, 1, 2, 3, 4]);
433
434        let path = coupling_map
435            .shortest_path(2, 2)
436            .expect("shortest_path 2->2 should succeed");
437        assert_eq!(path, vec![2]);
438    }
439
440    #[test]
441    fn test_connectivity() {
442        let connected = CouplingMap::linear(5);
443        assert!(connected.is_connected());
444
445        let mut disconnected = CouplingMap::new(5);
446        disconnected.add_edge(0, 1);
447        disconnected.add_edge(2, 3);
448        assert!(!disconnected.is_connected());
449    }
450
451    #[test]
452    fn test_ibm_lagos() {
453        let coupling_map = CouplingMap::ibm_lagos();
454        assert_eq!(coupling_map.num_qubits(), 7);
455        assert!(coupling_map.is_connected());
456    }
457}