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 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    /// # Arguments
322    ///
323    /// * `params` - A slice of floating-point parameters (e.g., rotation angles).
324    ///
325    /// # Returns
326    ///
327    /// - `Some(Cow<Array2>)`: The unitary matrix for `Standard` and `Extended` gates.
328    /// - `None`: If the instruction is non-unitary (e.g., `Measure`, `Barrier`, `Reset`,
329    ///   or classical control).
330    pub fn matrix(&self, params: &[f64]) -> Option<Cow<'_, Array2<Complex64>>> {
331        match self {
332            Instruction::Standard(g) => g.matrix(params).ok(),
333            Instruction::McGate(g) => g.matrix(params).ok(),
334            Instruction::UnitaryGate(g) => g.matrix_for_params(params).ok(),
335            Instruction::CircuitGate(g) => circuit_to_matrix(&g.circuit.circuit, None)
336                .ok()
337                .map(Cow::Owned),
338            Instruction::Directive(_) => None,
339            Instruction::ClassicalData(_) => None,
340            Instruction::Delay => None,
341            Instruction::ClassicalControl(_) => None,
342        }
343    }
344
345    /// Computes the inverse (Hermitian conjugate) of the instruction.
346    ///
347    /// # Arguments
348    ///
349    /// * `params` - The parameters of the instruction instance.
350    ///
351    /// # Returns
352    ///
353    /// - `Some(...)`: The inverse instruction and its transformed parameters.
354    /// - `None`: If the instruction is non-invertible (e.g., `Measure` or classical control)
355    ///   or if the inverse cannot be symbolically determined (e.g., some custom unitaries).
356    pub fn inverse(&self, params: &[Parameter]) -> Option<(Instruction, SmallVec<[Parameter; 3]>)> {
357        match self {
358            Instruction::Standard(g) => {
359                if let Some((gate, ps)) = g.inverse(params) {
360                    Some((Self::Standard(gate), ps))
361                } else {
362                    None
363                }
364            }
365            Instruction::McGate(g) => {
366                if let Some((gate, ps)) = g.inverse(params) {
367                    Some((Self::McGate(Box::new(gate)), ps))
368                } else {
369                    None
370                }
371            }
372            Instruction::UnitaryGate(g) => {
373                // Try to invert via circuit representation first
374                if let Some(c) = g.circuit().as_ref() {
375                    // Invert the internal circuit
376                    if let Ok(c_inv) = c.circuit.inverse() {
377                        // Create frozen circuit from inverted circuit
378                        let frozen_inv = FrozenCircuit::new(c_inv);
379                        // Create new UnitaryGate with inverted circuit
380                        let u_inv = UnitaryGate::new(
381                            format!("{}_dg", g.label()).as_str(),
382                            g.num_qubits(),
383                            g.num_params(),
384                        )
385                        .with_circuit(Arc::new(frozen_inv))
386                        .ok()?;
387                        return Some((Self::UnitaryGate(Box::new(u_inv)), SmallVec::new()));
388                    }
389                }
390                None
391            }
392            Instruction::CircuitGate(circuit_gate) => {
393                if let Ok(inv_gate) = circuit_gate.inverse() {
394                    Some((
395                        Instruction::CircuitGate(Box::new(inv_gate)),
396                        params.iter().cloned().collect(),
397                    ))
398                } else {
399                    None
400                }
401            }
402            Instruction::Directive(d) => match d {
403                Directive::Barrier => Some((Self::Directive(Directive::Barrier), SmallVec::new())),
404                _ => None,
405            },
406            Instruction::ClassicalData(_) => None,
407            Instruction::Delay => Some((Self::Delay, SmallVec::new())),
408            Instruction::ClassicalControl(_) => None,
409        }
410    }
411
412    /// Returns a new instruction that applies the current operation conditioned on extra control qubits.
413    ///
414    /// This method employs a **canonicalization strategy** to return the most optimized gate representation:
415    ///
416    /// 1. **Promotion**: If adding controls to a `StandardGate` results in another `StandardGate`
417    ///    (e.g., $X \xrightarrow{+1} CX \xrightarrow{+1} CCX$), it returns the upgraded standard gate.
418    /// 2. **Fallback**: If no standard equivalent exists (e.g., $C^3-X$), it returns
419    ///    an [`Instruction::McGate`].
420    /// 3. **Aggregation**: If the instruction is already an `MCGate`, the new controls are merged into it.
421    ///
422    /// # Arguments
423    ///
424    /// * `num_new_ctrls` - The number of additional control qubits to add.
425    ///
426    /// # Returns
427    ///
428    /// - `Some(Instruction)`: The new controlled instruction.
429    /// - `None`: If the instruction cannot be controlled (e.g., `Barrier`, `Measure`,
430    ///   or classical control).
431    pub fn control(&self, num_new_ctrls: usize) -> Option<Instruction> {
432        if num_new_ctrls == 0 {
433            return Some(self.clone());
434        }
435
436        match self {
437            Instruction::Standard(g) => {
438                let (base, curr_ctrls) = Self::decompose_standard_control(*g);
439                let total_ctrls = curr_ctrls + num_new_ctrls;
440
441                if let Some(std) = Self::compose_standard_control(base, total_ctrls) {
442                    Some(Instruction::Standard(std))
443                } else {
444                    Some(Instruction::McGate(Box::from(MCGate::new(
445                        total_ctrls as u8,
446                        base,
447                    ))))
448                }
449            }
450            Instruction::McGate(mc) => {
451                let total_ctrls = mc.num_ctrl_qubits() + num_new_ctrls;
452                let (base, _) = Self::decompose_standard_control(mc.base_gate().to_owned());
453
454                if let Some(std) = Self::compose_standard_control(base, total_ctrls) {
455                    Some(Instruction::Standard(std))
456                } else {
457                    Some(Instruction::McGate(Box::from(MCGate::new(
458                        total_ctrls as u8,
459                        base,
460                    ))))
461                }
462            }
463            Instruction::UnitaryGate(uni) => {
464                let mut g = UnitaryGate::new(
465                    format!("ctl_{}_{}", num_new_ctrls, uni.label()).as_str(),
466                    uni.num_qubits() + num_new_ctrls as u16,
467                    uni.num_params(),
468                );
469                if let Some(m) = uni.matrix() {
470                    let controlled = gate_matrix::control_matrix(m, num_new_ctrls);
471                    // Handle possible matrix dimension error, return None on failure
472                    g = match g.with_matrix(controlled) {
473                        Ok(gate) => gate,
474                        Err(_) => return None,
475                    };
476                } else if let Some(m) = uni.symbolic_matrix() {
477                    let controlled =
478                        crate::circuit::symbolic_matrix::gate::control_matrix(m, num_new_ctrls);
479                    let params = uni.matrix_params().unwrap_or(&[]);
480                    g = match g.with_symbolic_matrix(params.iter().cloned(), controlled) {
481                        Ok(gate) => gate,
482                        Err(_) => return None,
483                    };
484                }
485                // Copy circuit field if present
486                if let Some(c) = uni.circuit() {
487                    g = match g.with_circuit(c.clone()) {
488                        Ok(gate) => gate,
489                        Err(_) => return None,
490                    };
491                }
492
493                Some(Instruction::UnitaryGate(Box::from(g)))
494            }
495            Instruction::CircuitGate(_) => None, // CircuitGate does not support control yet
496            Instruction::Directive(_) => None,
497            Instruction::ClassicalData(_) => None,
498            Instruction::Delay => None,
499            Instruction::ClassicalControl(_) => None,
500        }
501    }
502}
503
504impl fmt::Display for Instruction {
505    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
506        f.write_str(&self.name())
507    }
508}
509
510impl From<StandardGate> for Instruction {
511    fn from(g: StandardGate) -> Self {
512        Self::Standard(g)
513    }
514}
515
516impl From<Directive> for Instruction {
517    fn from(d: Directive) -> Self {
518        Self::Directive(d)
519    }
520}
521
522impl From<ClassicalControlOp> for Instruction {
523    fn from(cf: ClassicalControlOp) -> Self {
524        Self::ClassicalControl(cf)
525    }
526}
527
528impl From<ClassicalDataOp> for Instruction {
529    fn from(op: ClassicalDataOp) -> Self {
530        Self::ClassicalData(op)
531    }
532}
533
534#[cfg(test)]
535#[path = "instruction_test.rs"]
536mod instruction_test;