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}