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;