quantrs2_circuit/optimization/
gate_properties.rs

1//! Gate properties and relationships
2//!
3//! This module defines properties of quantum gates that are used for optimization,
4//! including cost models, error rates, decomposition rules, and commutation relations.
5
6use quantrs2_core::gate::{multi, single, GateOp};
7use quantrs2_core::qubit::QubitId;
8use std::any::Any;
9use std::collections::HashMap;
10
11/// Gate cost information
12#[derive(Debug, Clone, Copy)]
13pub struct GateCost {
14    /// Time duration in nanoseconds
15    pub duration_ns: f64,
16    /// Gate count (e.g., 1 for native gates, >1 for decomposed gates)
17    pub gate_count: u32,
18    /// Energy or computational cost (arbitrary units)
19    pub computational_cost: f64,
20}
21
22impl GateCost {
23    /// Create a new gate cost
24    #[must_use]
25    pub const fn new(duration_ns: f64, gate_count: u32, computational_cost: f64) -> Self {
26        Self {
27            duration_ns,
28            gate_count,
29            computational_cost,
30        }
31    }
32
33    /// Total cost (weighted sum)
34    #[must_use]
35    pub fn total_cost(&self, time_weight: f64, count_weight: f64, comp_weight: f64) -> f64 {
36        comp_weight.mul_add(
37            self.computational_cost,
38            time_weight.mul_add(self.duration_ns, count_weight * f64::from(self.gate_count)),
39        )
40    }
41}
42
43/// Gate error information
44#[derive(Debug, Clone, Copy)]
45pub struct GateError {
46    /// Average gate fidelity
47    pub fidelity: f64,
48    /// Depolarizing error rate
49    pub error_rate: f64,
50    /// Coherent error contribution
51    pub coherent_error: f64,
52}
53
54impl GateError {
55    /// Create new gate error info
56    #[must_use]
57    pub const fn new(fidelity: f64, error_rate: f64, coherent_error: f64) -> Self {
58        Self {
59            fidelity,
60            error_rate,
61            coherent_error,
62        }
63    }
64
65    /// Combined error metric
66    #[must_use]
67    pub fn total_error(&self) -> f64 {
68        1.0 - self.fidelity + self.error_rate + self.coherent_error
69    }
70}
71
72/// Decomposition rule for gates
73#[derive(Debug, Clone)]
74pub struct DecompositionRule {
75    /// Name of the decomposition
76    pub name: String,
77    /// Target gate set for decomposition
78    pub target_gates: Vec<String>,
79    /// Cost of the decomposition
80    pub cost: GateCost,
81    /// Priority (lower is better)
82    pub priority: u32,
83}
84
85/// Properties of a quantum gate
86#[derive(Debug, Clone)]
87pub struct GateProperties {
88    /// Gate name
89    pub name: String,
90    /// Number of qubits
91    pub num_qubits: usize,
92    /// Is this a native gate on hardware?
93    pub is_native: bool,
94    /// Cost information
95    pub cost: GateCost,
96    /// Error information
97    pub error: GateError,
98    /// Available decomposition rules
99    pub decompositions: Vec<DecompositionRule>,
100    /// Gates that this gate commutes with
101    pub commutes_with: Vec<String>,
102    /// Is self-inverse (G·G = I)?
103    pub is_self_inverse: bool,
104    /// Is diagonal in computational basis?
105    pub is_diagonal: bool,
106    /// Is parameterized?
107    pub is_parameterized: bool,
108}
109
110impl GateProperties {
111    /// Create properties for a single-qubit gate
112    #[must_use]
113    pub fn single_qubit(name: &str) -> Self {
114        match name {
115            "H" => Self {
116                name: name.to_string(),
117                num_qubits: 1,
118                is_native: true,
119                cost: GateCost::new(20.0, 1, 1.0),
120                error: GateError::new(0.9999, 0.0001, 0.00001),
121                decompositions: vec![],
122                commutes_with: vec![],
123                is_self_inverse: true,
124                is_diagonal: false,
125                is_parameterized: false,
126            },
127            "X" | "Y" | "Z" => Self {
128                name: name.to_string(),
129                num_qubits: 1,
130                is_native: true,
131                cost: GateCost::new(20.0, 1, 1.0),
132                error: GateError::new(0.9999, 0.0001, 0.00001),
133                decompositions: vec![],
134                commutes_with: if name == "Z" {
135                    vec![
136                        "Z".to_string(),
137                        "RZ".to_string(),
138                        "S".to_string(),
139                        "T".to_string(),
140                    ]
141                } else {
142                    vec![]
143                },
144                is_self_inverse: true,
145                is_diagonal: name == "Z",
146                is_parameterized: false,
147            },
148            "S" => Self {
149                name: name.to_string(),
150                num_qubits: 1,
151                is_native: true,
152                cost: GateCost::new(20.0, 1, 1.0),
153                error: GateError::new(0.9999, 0.0001, 0.00001),
154                decompositions: vec![DecompositionRule {
155                    name: "Z-rotation".to_string(),
156                    target_gates: vec!["RZ".to_string()],
157                    cost: GateCost::new(20.0, 1, 1.0),
158                    priority: 1,
159                }],
160                commutes_with: vec![
161                    "Z".to_string(),
162                    "RZ".to_string(),
163                    "S".to_string(),
164                    "T".to_string(),
165                ],
166                is_self_inverse: false,
167                is_diagonal: true,
168                is_parameterized: false,
169            },
170            "T" => Self {
171                name: name.to_string(),
172                num_qubits: 1,
173                is_native: true,
174                cost: GateCost::new(20.0, 1, 1.0),
175                error: GateError::new(0.9999, 0.0001, 0.00001),
176                decompositions: vec![DecompositionRule {
177                    name: "Z-rotation".to_string(),
178                    target_gates: vec!["RZ".to_string()],
179                    cost: GateCost::new(20.0, 1, 1.0),
180                    priority: 1,
181                }],
182                commutes_with: vec![
183                    "Z".to_string(),
184                    "RZ".to_string(),
185                    "S".to_string(),
186                    "T".to_string(),
187                ],
188                is_self_inverse: false,
189                is_diagonal: true,
190                is_parameterized: false,
191            },
192            "RX" | "RY" | "RZ" => Self {
193                name: name.to_string(),
194                num_qubits: 1,
195                is_native: true,
196                cost: GateCost::new(40.0, 1, 1.5),
197                error: GateError::new(0.9998, 0.0002, 0.00002),
198                decompositions: vec![],
199                commutes_with: if name == "RZ" {
200                    vec![
201                        "Z".to_string(),
202                        "RZ".to_string(),
203                        "S".to_string(),
204                        "T".to_string(),
205                    ]
206                } else {
207                    vec![]
208                },
209                is_self_inverse: false,
210                is_diagonal: name == "RZ",
211                is_parameterized: true,
212            },
213            _ => Self::default_single_qubit(name),
214        }
215    }
216
217    /// Create properties for a two-qubit gate
218    #[must_use]
219    pub fn two_qubit(name: &str) -> Self {
220        match name {
221            "CNOT" => Self {
222                name: name.to_string(),
223                num_qubits: 2,
224                is_native: true,
225                cost: GateCost::new(300.0, 1, 3.0),
226                error: GateError::new(0.999, 0.001, 0.0001),
227                decompositions: vec![],
228                commutes_with: vec![],
229                is_self_inverse: true,
230                is_diagonal: false,
231                is_parameterized: false,
232            },
233            "CZ" => Self {
234                name: name.to_string(),
235                num_qubits: 2,
236                is_native: true,
237                cost: GateCost::new(200.0, 1, 2.5),
238                error: GateError::new(0.999, 0.001, 0.0001),
239                decompositions: vec![DecompositionRule {
240                    name: "H-CNOT-H".to_string(),
241                    target_gates: vec!["H".to_string(), "CNOT".to_string()],
242                    cost: GateCost::new(340.0, 3, 5.0),
243                    priority: 1,
244                }],
245                commutes_with: vec!["CZ".to_string()],
246                is_self_inverse: true,
247                is_diagonal: true,
248                is_parameterized: false,
249            },
250            "SWAP" => Self {
251                name: name.to_string(),
252                num_qubits: 2,
253                is_native: false,
254                cost: GateCost::new(900.0, 3, 9.0),
255                error: GateError::new(0.997, 0.003, 0.0003),
256                decompositions: vec![DecompositionRule {
257                    name: "3-CNOT".to_string(),
258                    target_gates: vec!["CNOT".to_string()],
259                    cost: GateCost::new(900.0, 3, 9.0),
260                    priority: 1,
261                }],
262                commutes_with: vec![],
263                is_self_inverse: true,
264                is_diagonal: false,
265                is_parameterized: false,
266            },
267            _ => Self::default_two_qubit(name),
268        }
269    }
270
271    /// Create properties for a multi-qubit gate
272    #[must_use]
273    pub fn multi_qubit(name: &str, num_qubits: usize) -> Self {
274        match name {
275            "Toffoli" => Self {
276                name: name.to_string(),
277                num_qubits: 3,
278                is_native: false,
279                cost: GateCost::new(2000.0, 15, 20.0),
280                error: GateError::new(0.99, 0.01, 0.001),
281                decompositions: vec![DecompositionRule {
282                    name: "Standard".to_string(),
283                    target_gates: vec![
284                        "H".to_string(),
285                        "CNOT".to_string(),
286                        "T".to_string(),
287                        "T†".to_string(),
288                    ],
289                    cost: GateCost::new(2000.0, 15, 20.0),
290                    priority: 1,
291                }],
292                commutes_with: vec![],
293                is_self_inverse: true,
294                is_diagonal: false,
295                is_parameterized: false,
296            },
297            _ => Self::default_multi_qubit(name, num_qubits),
298        }
299    }
300
301    fn default_single_qubit(name: &str) -> Self {
302        Self {
303            name: name.to_string(),
304            num_qubits: 1,
305            is_native: false,
306            cost: GateCost::new(50.0, 1, 2.0),
307            error: GateError::new(0.999, 0.001, 0.0001),
308            decompositions: vec![],
309            commutes_with: vec![],
310            is_self_inverse: false,
311            is_diagonal: false,
312            is_parameterized: false,
313        }
314    }
315
316    fn default_two_qubit(name: &str) -> Self {
317        Self {
318            name: name.to_string(),
319            num_qubits: 2,
320            is_native: false,
321            cost: GateCost::new(500.0, 2, 5.0),
322            error: GateError::new(0.995, 0.005, 0.0005),
323            decompositions: vec![],
324            commutes_with: vec![],
325            is_self_inverse: false,
326            is_diagonal: false,
327            is_parameterized: false,
328        }
329    }
330
331    fn default_multi_qubit(name: &str, num_qubits: usize) -> Self {
332        Self {
333            name: name.to_string(),
334            num_qubits,
335            is_native: false,
336            cost: GateCost::new(
337                1000.0 * num_qubits as f64,
338                num_qubits as u32 * 5,
339                10.0 * num_qubits as f64,
340            ),
341            error: GateError::new(
342                0.99 / num_qubits as f64,
343                0.01 * num_qubits as f64,
344                0.001 * num_qubits as f64,
345            ),
346            decompositions: vec![],
347            commutes_with: vec![],
348            is_self_inverse: false,
349            is_diagonal: false,
350            is_parameterized: false,
351        }
352    }
353}
354
355/// Commutation table for gates
356pub struct CommutationTable {
357    table: HashMap<(String, String), bool>,
358}
359
360impl CommutationTable {
361    /// Create a new commutation table
362    #[must_use]
363    pub fn new() -> Self {
364        let mut table = HashMap::new();
365
366        // Pauli commutation relations
367        table.insert(("X".to_string(), "X".to_string()), true);
368        table.insert(("Y".to_string(), "Y".to_string()), true);
369        table.insert(("Z".to_string(), "Z".to_string()), true);
370        table.insert(("X".to_string(), "Y".to_string()), false);
371        table.insert(("Y".to_string(), "X".to_string()), false);
372        table.insert(("X".to_string(), "Z".to_string()), false);
373        table.insert(("Z".to_string(), "X".to_string()), false);
374        table.insert(("Y".to_string(), "Z".to_string()), false);
375        table.insert(("Z".to_string(), "Y".to_string()), false);
376
377        // Diagonal gates commute
378        for diag1 in &["Z", "S", "T", "RZ"] {
379            for diag2 in &["Z", "S", "T", "RZ"] {
380                table.insert(((*diag1).to_string(), (*diag2).to_string()), true);
381            }
382        }
383
384        // CNOT commutation
385        table.insert(("CNOT".to_string(), "CNOT".to_string()), false); // In general
386
387        Self { table }
388    }
389
390    /// Check if two gates commute
391    #[must_use]
392    pub fn commutes(&self, gate1: &str, gate2: &str) -> bool {
393        if let Some(&result) = self.table.get(&(gate1.to_string(), gate2.to_string())) {
394            result
395        } else if let Some(&result) = self.table.get(&(gate2.to_string(), gate1.to_string())) {
396            result
397        } else {
398            false // Conservative default
399        }
400    }
401
402    /// Check if two gate operations commute (considering qubit indices)
403    pub fn gates_commute(&self, gate1: &dyn GateOp, gate2: &dyn GateOp) -> bool {
404        let qubits1 = gate1.qubits();
405        let qubits2 = gate2.qubits();
406
407        // Gates on disjoint qubits always commute
408        if qubits1.iter().all(|q| !qubits2.contains(q)) {
409            return true;
410        }
411
412        // Check specific commutation rules
413        self.commutes(gate1.name(), gate2.name())
414    }
415}
416
417impl Default for CommutationTable {
418    fn default() -> Self {
419        Self::new()
420    }
421}
422
423/// Get properties for a gate operation
424pub fn get_gate_properties(gate: &dyn GateOp) -> GateProperties {
425    let num_qubits = gate.num_qubits();
426
427    match num_qubits {
428        1 => GateProperties::single_qubit(gate.name()),
429        2 => GateProperties::two_qubit(gate.name()),
430        n => GateProperties::multi_qubit(gate.name(), n),
431    }
432}