quantrs2_device/mapping_scirs2/
utils.rs

1//! Utility functions and test helpers for SciRS2 mapping
2
3use super::*;
4use crate::calibration::create_ideal_calibration as create_ideal_calibration_fn;
5use scirs2_core::random::prelude::*;
6
7/// Create a standard hardware topology for testing
8pub fn create_standard_topology(
9    topology_type: &str,
10    num_qubits: usize,
11) -> DeviceResult<HardwareTopology> {
12    match topology_type {
13        "linear" => Ok(create_linear_topology(num_qubits)),
14        "grid" => Ok(create_grid_topology(num_qubits)),
15        "star" => Ok(create_star_topology(num_qubits)),
16        "complete" => Ok(create_complete_topology(num_qubits)),
17        _ => Err(DeviceError::InvalidTopology(format!(
18            "Unknown topology: {}",
19            topology_type
20        ))),
21    }
22}
23
24/// Create a linear chain topology
25fn create_linear_topology(num_qubits: usize) -> HardwareTopology {
26    HardwareTopology::linear_topology(num_qubits)
27}
28
29/// Create a 2D grid topology (approximate square)
30fn create_grid_topology(num_qubits: usize) -> HardwareTopology {
31    let side_length = (num_qubits as f64).sqrt().ceil() as usize;
32    let rows = side_length;
33    let cols = num_qubits.div_ceil(side_length);
34    HardwareTopology::grid_topology(rows, cols)
35}
36
37/// Create a star topology (center connected to all other qubits)
38fn create_star_topology(num_qubits: usize) -> HardwareTopology {
39    use crate::topology::{GateProperties, QubitProperties};
40
41    let mut topology = HardwareTopology::new(num_qubits);
42
43    // First add qubits (nodes must exist before adding connections)
44    for i in 0..num_qubits {
45        topology.add_qubit(QubitProperties {
46            id: i as u32,
47            index: i as u32,
48            t1: 50.0,
49            t2: 70.0,
50            single_qubit_gate_error: 0.001,
51            gate_error_1q: 0.001,
52            readout_error: 0.01,
53            frequency: 5.0 + 0.1 * i as f64,
54        });
55    }
56
57    // Add star connections: center (0) connected to all others
58    for i in 1..num_qubits {
59        topology.add_connection(
60            0,
61            i as u32,
62            GateProperties {
63                error_rate: 0.01,
64                duration: 200.0,
65                gate_type: "CZ".to_string(),
66            },
67        );
68    }
69    topology
70}
71
72/// Create a complete graph topology
73fn create_complete_topology(num_qubits: usize) -> HardwareTopology {
74    use crate::topology::{GateProperties, QubitProperties};
75
76    // Complete topology: every qubit connected to every other
77    let mut topology = HardwareTopology::new(num_qubits);
78
79    // First add qubits (nodes must exist before adding connections)
80    for i in 0..num_qubits {
81        topology.add_qubit(QubitProperties {
82            id: i as u32,
83            index: i as u32,
84            t1: 50.0,
85            t2: 70.0,
86            single_qubit_gate_error: 0.001,
87            gate_error_1q: 0.001,
88            readout_error: 0.01,
89            frequency: 5.0 + 0.1 * i as f64,
90        });
91    }
92
93    // Add all connections
94    for i in 0..num_qubits {
95        for j in i + 1..num_qubits {
96            topology.add_connection(
97                i as u32,
98                j as u32,
99                GateProperties {
100                    error_rate: 0.01,
101                    duration: 200.0,
102                    gate_type: "CZ".to_string(),
103                },
104            );
105        }
106    }
107    topology
108}
109
110/// Create ideal calibration data for testing
111pub fn create_ideal_calibration(device_name: String, num_qubits: usize) -> DeviceCalibration {
112    create_ideal_calibration_fn(device_name, num_qubits)
113}
114
115/// Validate mapping consistency
116pub fn validate_mapping(
117    mapping: &HashMap<usize, usize>,
118    num_logical_qubits: usize,
119    num_physical_qubits: usize,
120) -> DeviceResult<()> {
121    // Check all logical qubits are mapped
122    for i in 0..num_logical_qubits {
123        if !mapping.contains_key(&i) {
124            return Err(DeviceError::InvalidMapping(format!(
125                "Logical qubit {} not mapped",
126                i
127            )));
128        }
129    }
130
131    // Check all physical qubits are valid
132    for &physical in mapping.values() {
133        if physical >= num_physical_qubits {
134            return Err(DeviceError::InvalidMapping(format!(
135                "Physical qubit {} exceeds device capacity",
136                physical
137            )));
138        }
139    }
140
141    // Check for duplicate mappings
142    let mut used_physical = HashSet::new();
143    for &physical in mapping.values() {
144        if !used_physical.insert(physical) {
145            return Err(DeviceError::InvalidMapping(format!(
146                "Physical qubit {} mapped multiple times",
147                physical
148            )));
149        }
150    }
151
152    Ok(())
153}
154
155/// Calculate mapping quality score
156pub fn calculate_mapping_quality(
157    mapping: &HashMap<usize, usize>,
158    logical_graph: &Graph<usize, f64>,
159    topology: &HardwareTopology,
160) -> DeviceResult<f64> {
161    let mut total_distance = 0.0;
162    let mut edge_count = 0;
163
164    for edge in logical_graph.edges() {
165        // Note: node_weight method not available in current scirs2-graph version
166        // Using edge source/target indices as usize directly
167        let source = edge.source;
168        let target = edge.target;
169
170        if let (Some(&phys_source), Some(&phys_target)) =
171            (mapping.get(&source), mapping.get(&target))
172        {
173            // Calculate shortest path distance in physical topology
174            let distance = topology
175                .shortest_path_distance(phys_source, phys_target)
176                .unwrap_or(f64::INFINITY);
177
178            total_distance += distance;
179            edge_count += 1;
180        }
181    }
182
183    if edge_count > 0 {
184        Ok(1.0 / (1.0 + total_distance / edge_count as f64))
185    } else {
186        Ok(1.0)
187    }
188}
189
190/// Generate random circuit for testing
191pub fn generate_random_circuit<const N: usize>(
192    gate_count: usize,
193    two_qubit_ratio: f64,
194) -> Circuit<N> {
195    let mut circuit = Circuit::<N>::new();
196    let mut rng = thread_rng();
197
198    for _ in 0..gate_count {
199        if rng.gen::<f64>() < two_qubit_ratio {
200            // Two-qubit gate
201            let q1 = rng.gen_range(0..N);
202            let mut q2 = rng.gen_range(0..N);
203            while q2 == q1 {
204                q2 = rng.gen_range(0..N);
205            }
206            let _ = circuit.cnot(QubitId(q1 as u32), QubitId(q2 as u32));
207        } else {
208            // Single-qubit gate
209            let q = rng.gen_range(0..N);
210            let _ = circuit.x(QubitId(q as u32));
211        }
212    }
213
214    circuit
215}
216
217#[cfg(test)]
218mod tests {
219    use super::*;
220
221    #[test]
222    fn test_linear_topology_creation() {
223        let topology = create_linear_topology(4);
224        assert_eq!(topology.num_qubits(), 4);
225        assert!(topology.are_connected(0, 1));
226        assert!(topology.are_connected(1, 2));
227        assert!(topology.are_connected(2, 3));
228        assert!(!topology.are_connected(0, 3));
229    }
230
231    #[test]
232    fn test_grid_topology_creation() {
233        let topology = create_grid_topology(4);
234        assert_eq!(topology.num_qubits(), 4);
235        // In a 2x2 grid: 0-1, 0-2, 1-3, 2-3
236        assert!(topology.are_connected(0, 1));
237        assert!(topology.are_connected(0, 2));
238    }
239
240    #[test]
241    fn test_star_topology_creation() {
242        let topology = create_star_topology(5);
243        assert_eq!(topology.num_qubits(), 5);
244        // Center (0) connected to all others
245        for i in 1..5 {
246            assert!(topology.are_connected(0, i));
247        }
248        // Non-center nodes not connected to each other
249        assert!(!topology.are_connected(1, 2));
250    }
251
252    #[test]
253    fn test_mapping_validation() {
254        let mut mapping = HashMap::new();
255        mapping.insert(0, 0);
256        mapping.insert(1, 1);
257        mapping.insert(2, 2);
258
259        assert!(validate_mapping(&mapping, 3, 4).is_ok());
260
261        // Test duplicate mapping
262        mapping.insert(3, 2); // Duplicate physical qubit 2
263        assert!(validate_mapping(&mapping, 4, 4).is_err());
264    }
265
266    #[test]
267    fn test_mapping_quality_calculation() {
268        let topology = create_linear_topology(4);
269        let mut graph = Graph::new();
270        let _nodes: Vec<_> = (0..4).map(|i| graph.add_node(i)).collect();
271
272        // Add edge between qubits 0 and 3 (distant in linear topology)
273        let _ = graph.add_edge(0, 3, 1.0);
274
275        let mut mapping = HashMap::new();
276        mapping.insert(0, 0);
277        mapping.insert(1, 1);
278        mapping.insert(2, 2);
279        mapping.insert(3, 3);
280
281        let quality = calculate_mapping_quality(&mapping, &graph, &topology)
282            .expect("should calculate mapping quality");
283        assert!(quality > 0.0 && quality <= 1.0);
284    }
285
286    #[test]
287    fn test_random_circuit_generation() {
288        let circuit = generate_random_circuit::<4>(10, 0.5);
289        assert!(circuit.gates().len() <= 10);
290    }
291
292    #[test]
293    fn test_standard_topology_creation() {
294        assert!(create_standard_topology("linear", 4).is_ok());
295        assert!(create_standard_topology("grid", 4).is_ok());
296        assert!(create_standard_topology("star", 4).is_ok());
297        assert!(create_standard_topology("complete", 4).is_ok());
298        assert!(create_standard_topology("invalid", 4).is_err());
299    }
300
301    #[test]
302    fn test_complete_topology() {
303        let topology = create_complete_topology(4);
304        assert_eq!(topology.num_qubits(), 4);
305
306        // All pairs should be connected
307        for i in 0..4 {
308            for j in i + 1..4 {
309                assert!(topology.are_connected(i, j));
310            }
311        }
312    }
313
314    #[test]
315    fn test_ideal_calibration_creation() {
316        let _calibration = create_ideal_calibration("test_device".to_string(), 4);
317        // DeviceCalibration doesn't expose device_name or num_qubits directly
318        // Just verify it creates without error
319    }
320
321    #[test]
322    fn test_mapping_validation_edge_cases() {
323        // Empty mapping
324        let mapping = HashMap::new();
325        assert!(validate_mapping(&mapping, 0, 4).is_ok());
326
327        // Physical qubit out of range
328        let mut mapping = HashMap::new();
329        mapping.insert(0, 10);
330        assert!(validate_mapping(&mapping, 1, 4).is_err());
331
332        // Missing logical qubit
333        let mut mapping = HashMap::new();
334        mapping.insert(0, 0);
335        assert!(validate_mapping(&mapping, 2, 4).is_err());
336    }
337}