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, circuit_to_matrix};
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 true when this instruction directly or recursively contains a measurement.
160    pub fn has_measurement(&self) -> bool {
161        match self {
162            Self::Directive(Directive::Measure)
163            | Self::ClassicalData(ClassicalDataOp::MeasureBit { .. })
164            | Self::ClassicalData(ClassicalDataOp::MeasureBits { .. }) => true,
165            Self::ClassicalControl(control) => control.has_measurement(),
166            _ => false,
167        }
168    }
169
170    /// Returns true when this instruction directly or recursively reads `value`.
171    pub fn reads_value(&self, value: ClassicalValue) -> bool {
172        match self {
173            Self::ClassicalData(ClassicalDataOp::Store {
174                value: expression, ..
175            }) => expression.values().contains(&value),
176            Self::ClassicalControl(control) => control.reads_value(value),
177            _ => false,
178        }
179    }
180
181    /// Breaks a standard gate into its canonical base gate plus explicit
182    /// control count.
183    ///
184    /// This helper centralizes the standard-gate promotion table so
185    /// instruction-form canonicalization and `control()` share exactly the
186    /// same notion of which multi-controlled instructions deserve a dedicated
187    /// `StandardGate` representation.
188    fn decompose_standard_control(g: StandardGate) -> (StandardGate, usize) {
189        match g {
190            StandardGate::CX => (StandardGate::X, 1),
191            StandardGate::CCX => (StandardGate::X, 2),
192            StandardGate::CY => (StandardGate::Y, 1),
193            StandardGate::CZ => (StandardGate::Z, 1),
194            StandardGate::CRX => (StandardGate::RX, 1),
195            StandardGate::CRY => (StandardGate::RY, 1),
196            StandardGate::CRZ => (StandardGate::RZ, 1),
197            _ => (g, 0),
198        }
199    }
200
201    /// Attempts to recompose a base gate and explicit control count into the
202    /// most specific `StandardGate` form supported by the current IR.
203    fn compose_standard_control(base: StandardGate, total_controls: usize) -> Option<StandardGate> {
204        match (base, total_controls) {
205            (StandardGate::X, 1) => Some(StandardGate::CX),
206            (StandardGate::X, 2) => Some(StandardGate::CCX),
207            (StandardGate::Y, 1) => Some(StandardGate::CY),
208            (StandardGate::Z, 1) => Some(StandardGate::CZ),
209            (StandardGate::RX, 1) => Some(StandardGate::CRX),
210            (StandardGate::RY, 1) => Some(StandardGate::CRY),
211            (StandardGate::RZ, 1) => Some(StandardGate::CRZ),
212            (gate, 0) => Some(gate),
213            _ => None,
214        }
215    }
216
217    /// Returns the canonical instruction-form representation for this
218    /// instruction without changing its semantics.
219    ///
220    /// This only collapses `McGate` values back to dedicated `StandardGate`
221    /// variants when such a representation already exists in the IR. It uses
222    /// the same control-promotion table as [`Instruction::control`], preserving
223    /// the total control count, target convention, and parameter arity of the
224    /// base gate. Operation qubit order is not changed here; callers that
225    /// rewrite an operation keep the original qubit list attached to the new
226    /// instruction.
227    ///
228    /// It intentionally does not perform decomposition, target-aware lowering,
229    /// or matrix-based equivalence detection.
230    pub(crate) fn canonicalize_form(&self) -> Instruction {
231        match self {
232            Instruction::McGate(mc) => {
233                let total_ctrls = mc.num_ctrl_qubits();
234                let (base, _) = Self::decompose_standard_control(mc.base_gate().to_owned());
235
236                if let Some(std) = Self::compose_standard_control(base, total_ctrls) {
237                    Instruction::Standard(std)
238                } else {
239                    self.clone()
240                }
241            }
242            _ => self.clone(),
243        }
244    }
245
246    /// Returns the fixed `(qubit_count, parameter_count)` for gate-like instructions.
247    ///
248    /// This method describes arity that is intrinsic to the instruction itself.
249    /// It returns `None` for instructions whose qubit count is variable or
250    /// context-dependent, such as barriers and classical control operations.
251    /// Callers that need the qubits used by a classical control body should use
252    /// [`ClassicalControlOp::used_qubits`] instead.
253    pub fn gate_arity(&self) -> Option<(usize, usize)> {
254        match self {
255            Instruction::Standard(gate) => Some((gate.num_qubits(), gate.num_params())),
256            Instruction::McGate(gate) => Some((gate.num_qubits(), gate.num_params())),
257            Instruction::UnitaryGate(gate) => {
258                Some((gate.num_qubits() as usize, gate.num_params() as usize))
259            }
260            Instruction::CircuitGate(gate) => Some((gate.num_qubits(), gate.num_params())),
261            Instruction::Directive(Directive::Measure | Directive::Reset) => Some((1, 0)),
262            Instruction::ClassicalData(ClassicalDataOp::Store { .. }) => Some((0, 0)),
263            Instruction::ClassicalData(ClassicalDataOp::MeasureBit { .. }) => Some((1, 0)),
264            Instruction::Delay => Some((1, 1)),
265            Instruction::Directive(Directive::Barrier)
266            | Instruction::ClassicalData(ClassicalDataOp::MeasureBits { .. })
267            | Instruction::ClassicalControl(_) => None,
268        }
269    }
270
271    /// Computes the unitary matrix representation of the instruction.
272    ///
273    /// # Arguments
274    ///
275    /// * `params` - A slice of floating-point parameters (e.g., rotation angles).
276    ///
277    /// # Returns
278    ///
279    /// - `Some(Cow<Array2>)`: The unitary matrix for `Standard` and `Extended` gates.
280    /// - `None`: If the instruction is non-unitary (e.g., `Measure`, `Barrier`, `Reset`,
281    ///   or classical control).
282    pub fn matrix(&self, params: &[f64]) -> Option<Cow<'_, Array2<Complex64>>> {
283        match self {
284            Instruction::Standard(g) => g.matrix(params).ok(),
285            Instruction::McGate(g) => g.matrix(params).ok(),
286            Instruction::UnitaryGate(g) => g.matrix_for_params(params).ok(),
287            Instruction::CircuitGate(g) => circuit_to_matrix(&g.circuit.circuit, None)
288                .ok()
289                .map(Cow::Owned),
290            Instruction::Directive(_) => None,
291            Instruction::ClassicalData(_) => None,
292            Instruction::Delay => None,
293            Instruction::ClassicalControl(_) => None,
294        }
295    }
296
297    /// Computes the inverse (Hermitian conjugate) of the instruction.
298    ///
299    /// # Arguments
300    ///
301    /// * `params` - The parameters of the instruction instance.
302    ///
303    /// # Returns
304    ///
305    /// - `Some(...)`: The inverse instruction and its transformed parameters.
306    /// - `None`: If the instruction is non-invertible (e.g., `Measure` or classical control)
307    ///   or if the inverse cannot be symbolically determined (e.g., some custom unitaries).
308    pub fn inverse(&self, params: &[Parameter]) -> Option<(Instruction, SmallVec<[Parameter; 3]>)> {
309        match self {
310            Instruction::Standard(g) => {
311                if let Some((gate, ps)) = g.inverse(params) {
312                    Some((Self::Standard(gate), ps))
313                } else {
314                    None
315                }
316            }
317            Instruction::McGate(g) => {
318                if let Some((gate, ps)) = g.inverse(params) {
319                    Some((Self::McGate(Box::new(gate)), ps))
320                } else {
321                    None
322                }
323            }
324            Instruction::UnitaryGate(g) => {
325                // Try to invert via circuit representation first
326                if let Some(c) = g.circuit().as_ref() {
327                    // Invert the internal circuit
328                    if let Ok(c_inv) = c.circuit.inverse() {
329                        // Create frozen circuit from inverted circuit
330                        let frozen_inv = FrozenCircuit::new(c_inv);
331                        // Create new UnitaryGate with inverted circuit
332                        let u_inv = UnitaryGate::new(
333                            format!("{}_dg", g.label()).as_str(),
334                            g.num_qubits(),
335                            g.num_params(),
336                        )
337                        .with_circuit(Arc::new(frozen_inv))
338                        .ok()?;
339                        return Some((Self::UnitaryGate(Box::new(u_inv)), SmallVec::new()));
340                    }
341                }
342                None
343            }
344            Instruction::CircuitGate(circuit_gate) => {
345                if let Ok(inv_gate) = circuit_gate.inverse() {
346                    Some((
347                        Instruction::CircuitGate(Box::new(inv_gate)),
348                        params.iter().cloned().collect(),
349                    ))
350                } else {
351                    None
352                }
353            }
354            Instruction::Directive(d) => match d {
355                Directive::Barrier => Some((Self::Directive(Directive::Barrier), SmallVec::new())),
356                _ => None,
357            },
358            Instruction::ClassicalData(_) => None,
359            Instruction::Delay => Some((Self::Delay, SmallVec::new())),
360            Instruction::ClassicalControl(_) => None,
361        }
362    }
363
364    /// Returns a new instruction that applies the current operation conditioned on extra control qubits.
365    ///
366    /// This method employs a **canonicalization strategy** to return the most optimized gate representation:
367    ///
368    /// 1. **Promotion**: If adding controls to a `StandardGate` results in another `StandardGate`
369    ///    (e.g., $X \xrightarrow{+1} CX \xrightarrow{+1} CCX$), it returns the upgraded standard gate.
370    /// 2. **Fallback**: If no standard equivalent exists (e.g., $C^3-X$), it returns
371    ///    an [`Instruction::McGate`].
372    /// 3. **Aggregation**: If the instruction is already an `MCGate`, the new controls are merged into it.
373    ///
374    /// # Arguments
375    ///
376    /// * `num_new_ctrls` - The number of additional control qubits to add.
377    ///
378    /// # Returns
379    ///
380    /// - `Some(Instruction)`: The new controlled instruction.
381    /// - `None`: If the instruction cannot be controlled (e.g., `Barrier`, `Measure`,
382    ///   or classical control).
383    pub fn control(&self, num_new_ctrls: usize) -> Option<Instruction> {
384        if num_new_ctrls == 0 {
385            return Some(self.clone());
386        }
387
388        match self {
389            Instruction::Standard(g) => {
390                let (base, curr_ctrls) = Self::decompose_standard_control(*g);
391                let total_ctrls = curr_ctrls + num_new_ctrls;
392
393                if let Some(std) = Self::compose_standard_control(base, total_ctrls) {
394                    Some(Instruction::Standard(std))
395                } else {
396                    Some(Instruction::McGate(Box::from(MCGate::new(
397                        total_ctrls as u8,
398                        base,
399                    ))))
400                }
401            }
402            Instruction::McGate(mc) => {
403                let total_ctrls = mc.num_ctrl_qubits() + num_new_ctrls;
404                let (base, _) = Self::decompose_standard_control(mc.base_gate().to_owned());
405
406                if let Some(std) = Self::compose_standard_control(base, total_ctrls) {
407                    Some(Instruction::Standard(std))
408                } else {
409                    Some(Instruction::McGate(Box::from(MCGate::new(
410                        total_ctrls as u8,
411                        base,
412                    ))))
413                }
414            }
415            Instruction::UnitaryGate(uni) => {
416                let mut g = UnitaryGate::new(
417                    format!("ctl_{}_{}", num_new_ctrls, uni.label()).as_str(),
418                    uni.num_qubits() + num_new_ctrls as u16,
419                    uni.num_params(),
420                );
421                if let Some(m) = uni.matrix() {
422                    let controlled = gate_matrix::control_matrix(m, num_new_ctrls);
423                    // Handle possible matrix dimension error, return None on failure
424                    g = match g.with_matrix(controlled) {
425                        Ok(gate) => gate,
426                        Err(_) => return None,
427                    };
428                } else if let Some(m) = uni.symbolic_matrix() {
429                    let controlled =
430                        crate::circuit::symbolic_matrix::gate::control_matrix(m, num_new_ctrls);
431                    let params = uni.matrix_params().unwrap_or(&[]);
432                    g = match g.with_symbolic_matrix(params.iter().cloned(), controlled) {
433                        Ok(gate) => gate,
434                        Err(_) => return None,
435                    };
436                }
437                // Copy circuit field if present
438                if let Some(c) = uni.circuit() {
439                    g = match g.with_circuit(c.clone()) {
440                        Ok(gate) => gate,
441                        Err(_) => return None,
442                    };
443                }
444
445                Some(Instruction::UnitaryGate(Box::from(g)))
446            }
447            Instruction::CircuitGate(_) => None, // CircuitGate does not support control yet
448            Instruction::Directive(_) => None,
449            Instruction::ClassicalData(_) => None,
450            Instruction::Delay => None,
451            Instruction::ClassicalControl(_) => None,
452        }
453    }
454}
455
456impl fmt::Display for Instruction {
457    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
458        f.write_str(&self.name())
459    }
460}
461
462impl From<StandardGate> for Instruction {
463    fn from(g: StandardGate) -> Self {
464        Self::Standard(g)
465    }
466}
467
468impl From<Directive> for Instruction {
469    fn from(d: Directive) -> Self {
470        Self::Directive(d)
471    }
472}
473
474impl From<ClassicalControlOp> for Instruction {
475    fn from(cf: ClassicalControlOp) -> Self {
476        Self::ClassicalControl(cf)
477    }
478}
479
480impl From<ClassicalDataOp> for Instruction {
481    fn from(op: ClassicalDataOp) -> Self {
482        Self::ClassicalData(op)
483    }
484}
485
486#[cfg(test)]
487#[path = "instruction_test.rs"]
488mod instruction_test;