Skip to main content

cqlib_core/circuit/gate/
instruction.rs

1// This code is part of Cqlib.
2//
3// (C) Copyright China Telecom Quantum Group 2026
4//
5// This code is licensed under the Apache License, Version 2.0. You may
6// obtain a copy of this license in the LICENSE.txt file in the root directory
7// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8//
9// Any modifications or derivative works of this code must retain this
10// copyright notice, and modified files need to carry a notice indicating
11// that they have been altered from the originals.
12
13//! Unified Quantum Instruction Type
14//!
15//! This module defines the [`Instruction`] enum, which serves as the universal wrapper for all
16//! operations that can be placed in a quantum circuit. It unifies:
17//! - **Standard Gates**: Highly optimized, commonly used gates (e.g., H, CX).
18//! - **Extended Gates**: Multi-controlled operations, custom unitaries, and circuit-backed gates.
19//! - **Directives**: Non-unitary operations such as measurement, barrier, and reset.
20//! - **Classical Control**: Structured dynamic-circuit control flow driven by typed classical
21//!   expressions.
22//!
23//! By using `Instruction`, the circuit can store a heterogeneous list of operations in a single vector.
24
25use crate::circuit::gate::circuit_gate::{CircuitGate, FrozenCircuit};
26use crate::circuit::gate::directive::Directive;
27use crate::circuit::gate::standard_gate::StandardGate;
28use crate::circuit::gate::{ClassicalDataOp, MCGate, UnitaryGate, gate_matrix};
29use crate::circuit::{ClassicalControlOp, ClassicalValue, Parameter};
30use alloc::borrow::Cow;
31use ndarray::Array2;
32use num_complex::Complex64;
33use smallvec::SmallVec;
34use std::fmt;
35use std::sync::Arc;
36
37/// A unified representation of any operation in a quantum circuit.
38///
39/// This enum acts as a sum type for all possible instructions.
40/// - Prefer [`Instruction::Standard`] for common gates to leverage simulator optimizations.
41/// - Use [`Instruction::McGate`] for generalized controls over standard gates.
42/// - Use [`Instruction::UnitaryGate`] or [`Instruction::CircuitGate`] for custom unitary behavior.
43/// - Use [`Instruction::Directive`] for non-reversible actions such as measurement and reset.
44/// - Use [`Instruction::ClassicalControl`] for expression-based dynamic control flow.
45#[derive(Debug, Clone, PartialEq)]
46pub enum Instruction {
47    /// A standard, natively supported quantum gate (e.g., `H`, `CX`).
48    Standard(StandardGate),
49    /// An extended gate, such as a multi-controlled gate or a user-defined unitary.
50    McGate(Box<MCGate>),
51    /// User-defined unitary gate backed by a matrix or circuit definition.
52    UnitaryGate(Box<UnitaryGate>),
53    /// Reusable gate defined by a frozen circuit body.
54    CircuitGate(Box<CircuitGate>),
55    /// A non-unitary operation, such as `Measure`, `Barrier`, or `Reset`.
56    Directive(Directive),
57
58    /// Runtime classical data operation such as assignment or measurement
59    /// into typed classical storage.
60    ClassicalData(ClassicalDataOp),
61
62    /// Structured classical control flow.
63    ///
64    /// This variant carries expression-based IR such as `if`, `while`, `for`,
65    /// `switch`, `break`, and `continue`. It is a runtime control instruction,
66    /// not a unitary gate: it has no fixed arity, cannot be represented as a
67    /// matrix, has no static inverse, and cannot be promoted to a
68    /// quantum-controlled instruction.
69    ClassicalControl(ClassicalControlOp),
70    /// I gate in QCIS, represented here as Delay, unit is 0.5ns
71    Delay,
72}
73
74impl Instruction {
75    /// Returns the stable concrete operation name or user-defined gate label.
76    ///
77    /// Unlike [`Self::instruction_type`], this distinguishes individual gates.
78    /// It is independent of `Debug` and of presentation-only formatting.
79    pub fn name(&self) -> String {
80        match self {
81            Self::Standard(gate) => gate.name().to_string(),
82            Self::McGate(gate) => gate.name(),
83            Self::UnitaryGate(gate) => gate.label().to_string(),
84            Self::CircuitGate(gate) => gate.name().to_string(),
85            Self::Directive(directive) => directive.name().to_string(),
86            Self::ClassicalData(operation) => operation.name().to_string(),
87            Self::ClassicalControl(operation) => operation.name().to_string(),
88            Self::Delay => "delay".to_string(),
89        }
90    }
91
92    /// Returns a stable category name for this instruction.
93    pub const fn instruction_type(&self) -> &'static str {
94        match self {
95            Self::Standard(_) => "standard",
96            Self::McGate(_) => "mcgate",
97            Self::UnitaryGate(_) => "unitary",
98            Self::CircuitGate(_) => "circuit",
99            Self::Directive(_) => "directive",
100            Self::ClassicalData(_) => "classical_data",
101            Self::ClassicalControl(_) => "classical_control",
102            Self::Delay => "delay",
103        }
104    }
105
106    pub const fn is_standard(&self) -> bool {
107        matches!(self, Self::Standard(_))
108    }
109
110    pub const fn is_mcgate(&self) -> bool {
111        matches!(self, Self::McGate(_))
112    }
113
114    pub const fn is_unitary(&self) -> bool {
115        matches!(self, Self::UnitaryGate(_))
116    }
117
118    pub const fn is_circuit_gate(&self) -> bool {
119        matches!(self, Self::CircuitGate(_))
120    }
121
122    pub const fn is_directive(&self) -> bool {
123        matches!(self, Self::Directive(_))
124    }
125
126    pub const fn is_classical_data(&self) -> bool {
127        matches!(self, Self::ClassicalData(_))
128    }
129
130    pub const fn is_classical_control(&self) -> bool {
131        matches!(self, Self::ClassicalControl(_))
132    }
133
134    pub const fn is_delay(&self) -> bool {
135        matches!(self, Self::Delay)
136    }
137
138    /// Returns whether this instruction is a unitary quantum gate.
139    pub const fn is_quantum_gate(&self) -> bool {
140        match self {
141            Self::Standard(_) | Self::McGate(_) | Self::UnitaryGate(_) | Self::CircuitGate(_) => {
142                true
143            }
144            Self::Directive(_)
145            | Self::ClassicalData(_)
146            | Self::ClassicalControl(_)
147            | Self::Delay => false,
148        }
149    }
150
151    /// Returns the standard gate represented directly by this instruction.
152    pub fn standard_gate(&self) -> Option<StandardGate> {
153        match self {
154            Self::Standard(gate) => Some(*gate),
155            _ => None,
156        }
157    }
158
159    /// Returns the multi-controlled gate when this is an `McGate` instruction.
160    pub fn mc_gate(&self) -> Option<&MCGate> {
161        match self {
162            Self::McGate(gate) => Some(gate),
163            _ => None,
164        }
165    }
166
167    /// Returns the user-defined unitary gate when this is a `UnitaryGate` instruction.
168    pub fn unitary_gate(&self) -> Option<&UnitaryGate> {
169        match self {
170            Self::UnitaryGate(gate) => Some(gate),
171            _ => None,
172        }
173    }
174
175    /// Returns the circuit-backed gate when this is a `CircuitGate` instruction.
176    pub fn circuit_gate(&self) -> Option<&CircuitGate> {
177        match self {
178            Self::CircuitGate(gate) => Some(gate),
179            _ => None,
180        }
181    }
182
183    /// Returns the directive when this is a `Directive` instruction.
184    pub fn directive(&self) -> Option<Directive> {
185        match self {
186            Self::Directive(directive) => Some(*directive),
187            _ => None,
188        }
189    }
190
191    /// Returns the classical data operation when this is a `ClassicalData` instruction.
192    pub fn classical_data(&self) -> Option<&ClassicalDataOp> {
193        match self {
194            Self::ClassicalData(operation) => Some(operation),
195            _ => None,
196        }
197    }
198
199    /// Returns the classical control operation when this is a `ClassicalControl` instruction.
200    pub fn classical_control(&self) -> Option<&ClassicalControlOp> {
201        match self {
202            Self::ClassicalControl(operation) => Some(operation),
203            _ => None,
204        }
205    }
206
207    /// Returns true when this instruction directly or recursively contains a measurement.
208    pub fn has_measurement(&self) -> bool {
209        match self {
210            Self::Directive(Directive::Measure)
211            | Self::ClassicalData(ClassicalDataOp::MeasureBit { .. })
212            | Self::ClassicalData(ClassicalDataOp::MeasureBits { .. }) => true,
213            Self::ClassicalControl(control) => control.has_measurement(),
214            _ => false,
215        }
216    }
217
218    /// Returns true when this instruction directly or recursively reads `value`.
219    pub fn reads_value(&self, value: ClassicalValue) -> bool {
220        match self {
221            Self::ClassicalData(ClassicalDataOp::Store {
222                value: expression, ..
223            }) => expression.values().contains(&value),
224            Self::ClassicalControl(control) => control.reads_value(value),
225            _ => false,
226        }
227    }
228
229    /// Breaks a standard gate into its canonical base gate plus explicit
230    /// control count.
231    ///
232    /// This helper centralizes the standard-gate promotion table so
233    /// instruction-form canonicalization and `control()` share exactly the
234    /// same notion of which multi-controlled instructions deserve a dedicated
235    /// `StandardGate` representation.
236    fn decompose_standard_control(g: StandardGate) -> (StandardGate, usize) {
237        match g {
238            StandardGate::CX => (StandardGate::X, 1),
239            StandardGate::CCX => (StandardGate::X, 2),
240            StandardGate::CY => (StandardGate::Y, 1),
241            StandardGate::CZ => (StandardGate::Z, 1),
242            StandardGate::CRX => (StandardGate::RX, 1),
243            StandardGate::CRY => (StandardGate::RY, 1),
244            StandardGate::CRZ => (StandardGate::RZ, 1),
245            _ => (g, 0),
246        }
247    }
248
249    /// Attempts to recompose a base gate and explicit control count into the
250    /// most specific `StandardGate` form supported by the current IR.
251    fn compose_standard_control(base: StandardGate, total_controls: usize) -> Option<StandardGate> {
252        match (base, total_controls) {
253            (StandardGate::X, 1) => Some(StandardGate::CX),
254            (StandardGate::X, 2) => Some(StandardGate::CCX),
255            (StandardGate::Y, 1) => Some(StandardGate::CY),
256            (StandardGate::Z, 1) => Some(StandardGate::CZ),
257            (StandardGate::RX, 1) => Some(StandardGate::CRX),
258            (StandardGate::RY, 1) => Some(StandardGate::CRY),
259            (StandardGate::RZ, 1) => Some(StandardGate::CRZ),
260            (gate, 0) => Some(gate),
261            _ => None,
262        }
263    }
264
265    /// Returns the canonical instruction-form representation for this
266    /// instruction without changing its semantics.
267    ///
268    /// This only collapses `McGate` values back to dedicated `StandardGate`
269    /// variants when such a representation already exists in the IR. It uses
270    /// the same control-promotion table as [`Instruction::control`], preserving
271    /// the total control count, target convention, and parameter arity of the
272    /// base gate. Operation qubit order is not changed here; callers that
273    /// rewrite an operation keep the original qubit list attached to the new
274    /// instruction.
275    ///
276    /// It intentionally does not perform decomposition, target-aware lowering,
277    /// or matrix-based equivalence detection.
278    pub(crate) fn canonicalize_form(&self) -> Instruction {
279        match self {
280            Instruction::McGate(mc) => {
281                let total_ctrls = mc.num_ctrl_qubits();
282                let (base, _) = Self::decompose_standard_control(mc.base_gate().to_owned());
283
284                if let Some(std) = Self::compose_standard_control(base, total_ctrls) {
285                    Instruction::Standard(std)
286                } else {
287                    self.clone()
288                }
289            }
290            _ => self.clone(),
291        }
292    }
293
294    /// Returns the fixed `(qubit_count, parameter_count)` for gate-like instructions.
295    ///
296    /// This method describes arity that is intrinsic to the instruction itself.
297    /// It returns `None` for instructions whose qubit count is variable or
298    /// context-dependent, such as barriers and classical control operations.
299    /// Callers that need the qubits used by a classical control body should use
300    /// [`ClassicalControlOp::used_qubits`] instead.
301    pub fn gate_arity(&self) -> Option<(usize, usize)> {
302        match self {
303            Instruction::Standard(gate) => Some((gate.num_qubits(), gate.num_params())),
304            Instruction::McGate(gate) => Some((gate.num_qubits(), gate.num_params())),
305            Instruction::UnitaryGate(gate) => {
306                Some((gate.num_qubits() as usize, gate.num_params() as usize))
307            }
308            Instruction::CircuitGate(gate) => Some((gate.num_qubits(), gate.num_params())),
309            Instruction::Directive(Directive::Measure | Directive::Reset) => Some((1, 0)),
310            Instruction::ClassicalData(ClassicalDataOp::Store { .. }) => Some((0, 0)),
311            Instruction::ClassicalData(ClassicalDataOp::MeasureBit { .. }) => Some((1, 0)),
312            Instruction::Delay => Some((1, 1)),
313            Instruction::Directive(Directive::Barrier)
314            | Instruction::ClassicalData(ClassicalDataOp::MeasureBits { .. })
315            | Instruction::ClassicalControl(_) => None,
316        }
317    }
318
319    /// Computes the unitary matrix representation of the instruction.
320    ///
321    /// The first qubit argument is the most-significant bit of the local
322    /// matrix for every supported gate, including circuit-backed UnitaryGate
323    /// definitions. Full circuit matrices instead use an LSB-first basis.
324    ///
325    /// # Arguments
326    ///
327    /// * `params` - A slice of floating-point parameters (e.g., rotation angles).
328    ///   CircuitGate values bind positionally to its declared signature.
329    ///
330    /// # Returns
331    ///
332    /// - `Some(Cow<Array2>)`: A numeric matrix for a supported gate instruction.
333    /// - `None`: If parameters are invalid or unresolved, or the instruction has
334    ///   no matrix (e.g., `Measure`, `Barrier`, `Reset`, or classical control).
335    pub fn matrix(&self, params: &[f64]) -> Option<Cow<'_, Array2<Complex64>>> {
336        match self {
337            Instruction::Standard(g) => g.matrix(params).ok(),
338            Instruction::McGate(g) => g.matrix(params).ok(),
339            Instruction::UnitaryGate(g) => g.matrix_for_params_in_gate_order(params).ok(),
340            Instruction::CircuitGate(g) => g.matrix_for_params(params).ok().map(Cow::Owned),
341            Instruction::Directive(_) => None,
342            Instruction::ClassicalData(_) => None,
343            Instruction::Delay => None,
344            Instruction::ClassicalControl(_) => None,
345        }
346    }
347
348    /// Computes the inverse (Hermitian conjugate) of the instruction.
349    ///
350    /// # Arguments
351    ///
352    /// * `params` - The parameters of the instruction instance.
353    ///
354    /// # Returns
355    ///
356    /// - `Some(...)`: The inverse instruction and its transformed parameters.
357    /// - `None`: If the instruction is non-invertible (e.g., `Measure` or classical control)
358    ///   or if the inverse cannot be symbolically determined (e.g., some custom unitaries).
359    pub fn inverse(&self, params: &[Parameter]) -> Option<(Instruction, SmallVec<[Parameter; 3]>)> {
360        match self {
361            Instruction::Standard(g) => {
362                if let Some((gate, ps)) = g.inverse(params) {
363                    Some((Self::Standard(gate), ps))
364                } else {
365                    None
366                }
367            }
368            Instruction::McGate(g) => {
369                if let Some((gate, ps)) = g.inverse(params) {
370                    Some((Self::McGate(Box::new(gate)), ps))
371                } else {
372                    None
373                }
374            }
375            Instruction::UnitaryGate(g) => {
376                // Try to invert via circuit representation first
377                if let Some(c) = g.circuit().as_ref() {
378                    // Invert the internal circuit
379                    if let Ok(c_inv) = c.circuit.inverse() {
380                        // Create frozen circuit from inverted circuit
381                        let frozen_inv = FrozenCircuit::new(c_inv);
382                        // Create new UnitaryGate with inverted circuit
383                        let u_inv = UnitaryGate::new(
384                            format!("{}_dg", g.label()).as_str(),
385                            g.num_qubits(),
386                            g.num_params(),
387                        )
388                        .with_circuit(Arc::new(frozen_inv))
389                        .ok()?;
390                        return Some((Self::UnitaryGate(Box::new(u_inv)), SmallVec::new()));
391                    }
392                }
393                None
394            }
395            Instruction::CircuitGate(circuit_gate) => {
396                if let Ok(inv_gate) = circuit_gate.inverse() {
397                    Some((
398                        Instruction::CircuitGate(Box::new(inv_gate)),
399                        params.iter().cloned().collect(),
400                    ))
401                } else {
402                    None
403                }
404            }
405            Instruction::Directive(d) => match d {
406                Directive::Barrier => Some((Self::Directive(Directive::Barrier), SmallVec::new())),
407                _ => None,
408            },
409            Instruction::ClassicalData(_) => None,
410            Instruction::Delay => Some((Self::Delay, SmallVec::new())),
411            Instruction::ClassicalControl(_) => None,
412        }
413    }
414
415    /// Returns a new instruction that applies the current operation conditioned on extra control qubits.
416    ///
417    /// This method employs a **canonicalization strategy** to return the most optimized gate representation:
418    ///
419    /// 1. **Promotion**: If adding controls to a `StandardGate` results in another `StandardGate`
420    ///    (e.g., $X \xrightarrow{+1} CX \xrightarrow{+1} CCX$), it returns the upgraded standard gate.
421    /// 2. **Fallback**: If no standard equivalent exists (e.g., $C^3-X$), it returns
422    ///    an [`Instruction::McGate`].
423    /// 3. **Aggregation**: If the instruction is already an `MCGate`, the new controls are merged into it.
424    ///
425    /// # Arguments
426    ///
427    /// * `num_new_ctrls` - The number of additional control qubits to add.
428    ///
429    /// # Returns
430    ///
431    /// - `Some(Instruction)`: The new controlled instruction.
432    /// - `None`: If the instruction cannot be controlled (e.g., `Barrier`, `Measure`,
433    ///   or classical control).
434    pub fn control(&self, num_new_ctrls: usize) -> Option<Instruction> {
435        if num_new_ctrls == 0 {
436            return Some(self.clone());
437        }
438
439        match self {
440            Instruction::Standard(g) => {
441                let (base, curr_ctrls) = Self::decompose_standard_control(*g);
442                let total_ctrls = curr_ctrls + num_new_ctrls;
443
444                if let Some(std) = Self::compose_standard_control(base, total_ctrls) {
445                    Some(Instruction::Standard(std))
446                } else {
447                    Some(Instruction::McGate(Box::from(MCGate::new(
448                        total_ctrls as u8,
449                        base,
450                    ))))
451                }
452            }
453            Instruction::McGate(mc) => {
454                let total_ctrls = mc.num_ctrl_qubits() + num_new_ctrls;
455                let (base, _) = Self::decompose_standard_control(mc.base_gate().to_owned());
456
457                if let Some(std) = Self::compose_standard_control(base, total_ctrls) {
458                    Some(Instruction::Standard(std))
459                } else {
460                    Some(Instruction::McGate(Box::from(MCGate::new(
461                        total_ctrls as u8,
462                        base,
463                    ))))
464                }
465            }
466            Instruction::UnitaryGate(uni) => {
467                let mut g = UnitaryGate::new(
468                    format!("ctl_{}_{}", num_new_ctrls, uni.label()).as_str(),
469                    uni.num_qubits() + num_new_ctrls as u16,
470                    uni.num_params(),
471                );
472                if let Some(m) = uni.matrix() {
473                    let controlled = gate_matrix::control_matrix(m, num_new_ctrls);
474                    // Handle possible matrix dimension error, return None on failure
475                    g = match g.with_matrix(controlled) {
476                        Ok(gate) => gate,
477                        Err(_) => return None,
478                    };
479                } else if let Some(m) = uni.symbolic_matrix() {
480                    let controlled =
481                        crate::circuit::symbolic_matrix::gate::control_matrix(m, num_new_ctrls);
482                    let params = uni.matrix_params().unwrap_or(&[]);
483                    g = match g.with_symbolic_matrix(params.iter().cloned(), controlled) {
484                        Ok(gate) => gate,
485                        Err(_) => return None,
486                    };
487                }
488                // Copy circuit field if present
489                if let Some(c) = uni.circuit() {
490                    g = match g.with_circuit(c.clone()) {
491                        Ok(gate) => gate,
492                        Err(_) => return None,
493                    };
494                }
495
496                Some(Instruction::UnitaryGate(Box::from(g)))
497            }
498            Instruction::CircuitGate(_) => None, // CircuitGate does not support control yet
499            Instruction::Directive(_) => None,
500            Instruction::ClassicalData(_) => None,
501            Instruction::Delay => None,
502            Instruction::ClassicalControl(_) => None,
503        }
504    }
505}
506
507impl fmt::Display for Instruction {
508    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
509        f.write_str(&self.name())
510    }
511}
512
513impl From<StandardGate> for Instruction {
514    fn from(g: StandardGate) -> Self {
515        Self::Standard(g)
516    }
517}
518
519impl From<Directive> for Instruction {
520    fn from(d: Directive) -> Self {
521        Self::Directive(d)
522    }
523}
524
525impl From<ClassicalControlOp> for Instruction {
526    fn from(cf: ClassicalControlOp) -> Self {
527        Self::ClassicalControl(cf)
528    }
529}
530
531impl From<ClassicalDataOp> for Instruction {
532    fn from(op: ClassicalDataOp) -> Self {
533        Self::ClassicalData(op)
534    }
535}
536
537#[cfg(test)]
538#[path = "instruction_test.rs"]
539mod instruction_test;