Skip to main content

cqlib_core/circuit/
operation.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//! # Circuit Operation Module
14//!
15//! This module defines the [`Operation`] struct, which represents a single, concrete execution step
16//! within a quantum circuit.
17//!
18//! ## Role in Architecture
19//!
20//! While [`Instruction`] defines *what* operation to perform (e.g., "apply a Hadamard gate"),
21//! [`Operation`] defines *where* and *how* to apply it. It binds an abstract instruction to:
22//! - Specific qubits (Topology).
23//! - Specific parameters (Context).
24//!
25//! ## Memory Optimization
26//!
27//! Since a circuit may contain millions of operations, this struct is heavily optimized for memory compactness:
28//! - **SmallVec**: Uses `SmallVec` for qubits and parameters to store data inline on the stack for common cases
29//!   (e.g., 1-2 qubit gates, 0-1 parameters), avoiding heap allocation overhead.
30//! - **CircuitParam**: Uses a lightweight enum to store parameters, supporting both immediate float values
31//!   and references to the circuit's global parameter table (interning).
32
33use crate::circuit::bit::Qubit;
34use crate::circuit::circuit_param::{CircuitParam, ParameterValue};
35use crate::circuit::error::CircuitError;
36use crate::circuit::gate::{StandardGate, instruction::Instruction};
37use crate::circuit::value_instruction::ValueInstruction;
38use alloc::borrow::Cow;
39use ndarray::Array2;
40use num_complex::Complex64;
41use smallvec::{SmallVec, smallvec};
42use std::fmt;
43
44/// A circuit-local operation in the compact storage IR.
45///
46/// An `Operation` combines a gate (instruction) with the specific qubits it acts upon and its
47/// parameters. Fixed parameters are stored inline, while symbolic parameters may be represented
48/// by indices into the owning [`Circuit`](crate::circuit::Circuit)'s parameter table. An
49/// `Operation` therefore cannot always be interpreted without its owning circuit.
50///
51/// # Fields
52///
53/// * `instruction` - The type of operation (e.g., `StandardGate::H`, `Directive::Measure`).
54/// * `qubits` - The ordered list of qubits involved in this operation.
55///   - For controlled gates, control qubits usually come first, followed by target qubits.
56///   - Implementation uses `SmallVec<[Qubit; 3]>` to optimize for gates acting on ≤3 qubits (covering almost all standard gates).
57/// * `params` - The parameters for the operation.
58///   - Implementation uses `SmallVec<[CircuitParam; 1]>` to optimize for single-parameter gates (e.g., `RX`, `RZ`).
59/// * `label` - An optional human-readable label or tag for this specific operation instance.
60#[derive(Debug, Clone)]
61pub struct Operation {
62    /// The abstract instruction definition (what to do).
63    pub instruction: Instruction,
64    /// The specific qubits this operation applies to (where to do it).
65    pub qubits: SmallVec<[Qubit; 3]>,
66    /// The concrete or symbolic parameters for this operation (how to do it).
67    pub params: SmallVec<[CircuitParam; 1]>,
68    /// Optional metadata label.
69    pub label: Option<Box<str>>,
70}
71
72impl PartialEq for Operation {
73    /// Compares the storage-level structure of two operations.
74    ///
75    /// Parameters compare in their circuit-local storage form (see
76    /// [`CircuitParam`]), and classical handles embedded in
77    /// [`Instruction::ClassicalData`]/[`Instruction::ClassicalControl`]
78    /// carry the owning circuit's process-local identity, so operations
79    /// originating from different circuits are generally unequal unless
80    /// remapped first (as [`Circuit`](crate::circuit::Circuit)'s own
81    /// equality does internally).
82    fn eq(&self, other: &Self) -> bool {
83        self.instruction == other.instruction
84            && self.qubits == other.qubits
85            && self.params == other.params
86            && self.label == other.label
87    }
88}
89
90impl Operation {
91    /// Returns the standard gate represented directly by this operation.
92    pub fn standard_gate(&self) -> Option<StandardGate> {
93        self.instruction.standard_gate()
94    }
95
96    /// Computes the numerical unitary matrix for this specific operation.
97    ///
98    /// This method accepts only parameters already stored as fixed values and delegates
99    /// matrix generation to the underlying [`Instruction`]. It cannot resolve
100    /// circuit-local parameter indices without access to the owning circuit.
101    ///
102    /// # Returns
103    ///
104    /// * `Ok(Cow<Array2>)` - The unitary matrix. It may be borrowed (static) or owned (computed).
105    ///   All gate matrices put the first qubit argument at the most-significant
106    ///   local basis bit, including circuit-backed UnitaryGate and CircuitGate
107    ///   definitions. Full circuit matrices instead use an LSB-first basis.
108    /// * `Err(CircuitError)` - If the operation is non-unitary or contains an
109    ///   unresolved symbolic parameter.
110    ///
111    /// # Errors
112    ///
113    /// Returns [`CircuitError::SymbolicParameterError`] when the operation
114    /// contains an indexed symbolic parameter. Resolve parameters through the
115    /// owning [`Circuit`](crate::circuit::Circuit) before requesting a numeric
116    /// matrix. Returns [`CircuitError::NoMatrixRepresentation`] for non-unitary
117    /// instructions such as measurement, reset, and barriers.
118    pub fn matrix(&self) -> Result<Cow<'_, Array2<Complex64>>, CircuitError> {
119        let mut ps: SmallVec<[f64; 4]> = smallvec![];
120        for p in self.params.iter() {
121            match p {
122                CircuitParam::Fixed(val) => {
123                    ps.push(*val);
124                }
125                CircuitParam::Index(_index) => {
126                    return Err(CircuitError::SymbolicParameterError);
127                }
128            }
129        }
130        self.instruction
131            .matrix(&ps)
132            .ok_or(CircuitError::NoMatrixRepresentation)
133    }
134}
135
136/// A value-level operation independent of a circuit parameter table.
137///
138/// Unlike [`Operation`], symbolic parameters are stored directly as
139/// [`ParameterValue`] values rather than circuit-local table indices. This form
140/// is used before insertion into a circuit and when returning a resolved
141/// operation from [`Circuit::index`](crate::circuit::Circuit::index).
142#[derive(Debug, Clone)]
143pub struct ValueOperation {
144    /// The abstract instruction definition (what to do).
145    pub instruction: ValueInstruction,
146    /// The specific qubits this operation applies to (where to do it).
147    pub qubits: SmallVec<[Qubit; 3]>,
148    /// The concrete or symbolic parameters for this operation (how to do it).
149    pub params: SmallVec<[ParameterValue; 1]>,
150    /// Optional metadata label.
151    pub label: Option<Box<str>>,
152}
153
154impl PartialEq for ValueOperation {
155    /// Compares the construction-level structure of two operations.
156    ///
157    /// Parameters compare in their construction form (see
158    /// [`ParameterValue`]), and classical handles embedded in
159    /// [`Instruction::ClassicalData`]/[`Instruction::ClassicalControl`]
160    /// carry the owning circuit's process-local identity, so operations
161    /// originating from different circuits are generally unequal unless
162    /// remapped first (as [`Circuit`](crate::circuit::Circuit)'s own
163    /// equality does internally).
164    fn eq(&self, other: &Self) -> bool {
165        self.instruction == other.instruction
166            && self.qubits == other.qubits
167            && self.params == other.params
168            && self.label == other.label
169    }
170}
171
172impl ValueOperation {
173    /// Creates a standard gate operation.
174    pub fn from_standard(
175        gate: StandardGate,
176        qubits: impl IntoIterator<Item = Qubit>,
177        params: impl IntoIterator<Item = ParameterValue>,
178    ) -> Self {
179        Self {
180            instruction: ValueInstruction::from_instruction(Instruction::Standard(gate)),
181            qubits: qubits.into_iter().collect(),
182            params: params.into_iter().collect(),
183            label: None,
184        }
185    }
186
187    /// Returns the human-readable instruction name.
188    pub fn name(&self) -> String {
189        self.instruction.name()
190    }
191
192    /// Returns the number of qubits used by this operation instance.
193    pub fn num_qubits(&self) -> usize {
194        self.qubits.len()
195    }
196
197    /// Returns the number of parameters carried by this operation instance.
198    pub fn num_params(&self) -> usize {
199        self.params.len()
200    }
201
202    /// Returns a stable category name for this operation's instruction.
203    pub fn instruction_type(&self) -> &'static str {
204        self.instruction.instruction_type()
205    }
206
207    /// Returns `true` if this operation uses a standard-gate instruction.
208    pub fn is_standard(&self) -> bool {
209        self.instruction.is_standard()
210    }
211
212    /// Returns `true` if this operation uses a multi-controlled-gate instruction.
213    pub fn is_mcgate(&self) -> bool {
214        self.instruction.is_mcgate()
215    }
216
217    /// Returns `true` if this operation uses a user-defined unitary instruction.
218    pub fn is_unitary(&self) -> bool {
219        self.instruction.is_unitary()
220    }
221
222    /// Returns `true` if this operation uses a circuit-backed gate instruction.
223    pub fn is_circuit_gate(&self) -> bool {
224        self.instruction.is_circuit_gate()
225    }
226
227    /// Returns `true` if this operation uses a directive instruction.
228    pub fn is_directive(&self) -> bool {
229        self.instruction.is_directive()
230    }
231
232    /// Returns `true` if this operation uses a classical-data instruction.
233    pub fn is_classical_data(&self) -> bool {
234        self.instruction.is_classical_data()
235    }
236
237    /// Returns `true` if this operation uses a classical-control instruction.
238    pub fn is_classical_control(&self) -> bool {
239        self.instruction.is_classical_control()
240    }
241
242    /// Returns `true` if this operation uses a delay instruction.
243    pub fn is_delay(&self) -> bool {
244        self.instruction.is_delay()
245    }
246}
247
248impl fmt::Display for ValueOperation {
249    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
250        write!(f, "{}", self.instruction)?;
251        if !self.params.is_empty() {
252            write!(f, "(")?;
253            for (i, param) in self.params.iter().enumerate() {
254                if i > 0 {
255                    write!(f, ", ")?;
256                }
257                write!(f, "{}", crate::circuit::Parameter::from(param))?;
258            }
259            write!(f, ")")?;
260        }
261        for qubit in &self.qubits {
262            write!(f, " {}", qubit)?;
263        }
264        if let Some(ref label) = self.label {
265            write!(f, " [{}]", label)?;
266        }
267        Ok(())
268    }
269}
270
271#[cfg(test)]
272mod tests {
273    use super::*;
274
275    #[test]
276    fn operation_standard_gate_query_delegates_to_instruction() {
277        let standard = Operation {
278            instruction: Instruction::Standard(StandardGate::H),
279            qubits: smallvec![Qubit::new(0)],
280            params: smallvec![],
281            label: None,
282        };
283        let directive = Operation {
284            instruction: Instruction::Directive(crate::circuit::Directive::Barrier),
285            qubits: smallvec![],
286            params: smallvec![],
287            label: None,
288        };
289
290        assert_eq!(standard.standard_gate(), Some(StandardGate::H));
291        assert_eq!(directive.standard_gate(), None);
292    }
293
294    #[test]
295    fn value_operation_exposes_instance_queries() {
296        let operation = ValueOperation::from_standard(
297            StandardGate::RX,
298            [Qubit::new(0)],
299            [ParameterValue::Fixed(0.5)],
300        );
301
302        assert_eq!(operation.name(), "RX");
303        assert_eq!(operation.num_qubits(), 1);
304        assert_eq!(operation.num_params(), 1);
305        assert_eq!(operation.instruction_type(), "standard");
306        assert!(operation.is_standard());
307        assert!(!operation.is_directive());
308        assert!(!operation.is_classical_control());
309    }
310
311    #[test]
312    fn value_operation_compares_structurally() {
313        let make = || {
314            ValueOperation::from_standard(
315                StandardGate::RX,
316                [Qubit::new(0)],
317                [ParameterValue::Fixed(0.5)],
318            )
319        };
320        assert_eq!(make(), make());
321
322        let different_param = ValueOperation::from_standard(
323            StandardGate::RX,
324            [Qubit::new(0)],
325            [ParameterValue::Fixed(1.5)],
326        );
327        assert_ne!(make(), different_param);
328
329        let different_qubit = ValueOperation::from_standard(
330            StandardGate::RX,
331            [Qubit::new(1)],
332            [ParameterValue::Fixed(0.5)],
333        );
334        assert_ne!(make(), different_qubit);
335
336        let labeled = ValueOperation {
337            label: Some("tagged".into()),
338            ..make()
339        };
340        assert_ne!(make(), labeled);
341    }
342
343    #[test]
344    fn value_operation_parameter_forms_do_not_cross_compare() {
345        let symbolic = ValueOperation::from_standard(
346            StandardGate::RX,
347            [Qubit::new(0)],
348            [ParameterValue::Param(crate::circuit::Parameter::symbol(
349                "theta",
350            ))],
351        );
352        let symbolic_again = ValueOperation::from_standard(
353            StandardGate::RX,
354            [Qubit::new(0)],
355            [ParameterValue::Param(crate::circuit::Parameter::symbol(
356                "theta",
357            ))],
358        );
359        let fixed = ValueOperation::from_standard(
360            StandardGate::RX,
361            [Qubit::new(0)],
362            [ParameterValue::Fixed(0.5)],
363        );
364
365        assert_eq!(symbolic, symbolic_again);
366        assert_ne!(symbolic, fixed);
367    }
368}