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;