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 true when this instruction directly or recursively contains a measurement.
160 pub fn has_measurement(&self) -> bool {
161 match self {
162 Self::Directive(Directive::Measure)
163 | Self::ClassicalData(ClassicalDataOp::MeasureBit { .. })
164 | Self::ClassicalData(ClassicalDataOp::MeasureBits { .. }) => true,
165 Self::ClassicalControl(control) => control.has_measurement(),
166 _ => false,
167 }
168 }
169
170 /// Returns true when this instruction directly or recursively reads `value`.
171 pub fn reads_value(&self, value: ClassicalValue) -> bool {
172 match self {
173 Self::ClassicalData(ClassicalDataOp::Store {
174 value: expression, ..
175 }) => expression.values().contains(&value),
176 Self::ClassicalControl(control) => control.reads_value(value),
177 _ => false,
178 }
179 }
180
181 /// Breaks a standard gate into its canonical base gate plus explicit
182 /// control count.
183 ///
184 /// This helper centralizes the standard-gate promotion table so
185 /// instruction-form canonicalization and `control()` share exactly the
186 /// same notion of which multi-controlled instructions deserve a dedicated
187 /// `StandardGate` representation.
188 fn decompose_standard_control(g: StandardGate) -> (StandardGate, usize) {
189 match g {
190 StandardGate::CX => (StandardGate::X, 1),
191 StandardGate::CCX => (StandardGate::X, 2),
192 StandardGate::CY => (StandardGate::Y, 1),
193 StandardGate::CZ => (StandardGate::Z, 1),
194 StandardGate::CRX => (StandardGate::RX, 1),
195 StandardGate::CRY => (StandardGate::RY, 1),
196 StandardGate::CRZ => (StandardGate::RZ, 1),
197 _ => (g, 0),
198 }
199 }
200
201 /// Attempts to recompose a base gate and explicit control count into the
202 /// most specific `StandardGate` form supported by the current IR.
203 fn compose_standard_control(base: StandardGate, total_controls: usize) -> Option<StandardGate> {
204 match (base, total_controls) {
205 (StandardGate::X, 1) => Some(StandardGate::CX),
206 (StandardGate::X, 2) => Some(StandardGate::CCX),
207 (StandardGate::Y, 1) => Some(StandardGate::CY),
208 (StandardGate::Z, 1) => Some(StandardGate::CZ),
209 (StandardGate::RX, 1) => Some(StandardGate::CRX),
210 (StandardGate::RY, 1) => Some(StandardGate::CRY),
211 (StandardGate::RZ, 1) => Some(StandardGate::CRZ),
212 (gate, 0) => Some(gate),
213 _ => None,
214 }
215 }
216
217 /// Returns the canonical instruction-form representation for this
218 /// instruction without changing its semantics.
219 ///
220 /// This only collapses `McGate` values back to dedicated `StandardGate`
221 /// variants when such a representation already exists in the IR. It uses
222 /// the same control-promotion table as [`Instruction::control`], preserving
223 /// the total control count, target convention, and parameter arity of the
224 /// base gate. Operation qubit order is not changed here; callers that
225 /// rewrite an operation keep the original qubit list attached to the new
226 /// instruction.
227 ///
228 /// It intentionally does not perform decomposition, target-aware lowering,
229 /// or matrix-based equivalence detection.
230 pub(crate) fn canonicalize_form(&self) -> Instruction {
231 match self {
232 Instruction::McGate(mc) => {
233 let total_ctrls = mc.num_ctrl_qubits();
234 let (base, _) = Self::decompose_standard_control(mc.base_gate().to_owned());
235
236 if let Some(std) = Self::compose_standard_control(base, total_ctrls) {
237 Instruction::Standard(std)
238 } else {
239 self.clone()
240 }
241 }
242 _ => self.clone(),
243 }
244 }
245
246 /// Returns the fixed `(qubit_count, parameter_count)` for gate-like instructions.
247 ///
248 /// This method describes arity that is intrinsic to the instruction itself.
249 /// It returns `None` for instructions whose qubit count is variable or
250 /// context-dependent, such as barriers and classical control operations.
251 /// Callers that need the qubits used by a classical control body should use
252 /// [`ClassicalControlOp::used_qubits`] instead.
253 pub fn gate_arity(&self) -> Option<(usize, usize)> {
254 match self {
255 Instruction::Standard(gate) => Some((gate.num_qubits(), gate.num_params())),
256 Instruction::McGate(gate) => Some((gate.num_qubits(), gate.num_params())),
257 Instruction::UnitaryGate(gate) => {
258 Some((gate.num_qubits() as usize, gate.num_params() as usize))
259 }
260 Instruction::CircuitGate(gate) => Some((gate.num_qubits(), gate.num_params())),
261 Instruction::Directive(Directive::Measure | Directive::Reset) => Some((1, 0)),
262 Instruction::ClassicalData(ClassicalDataOp::Store { .. }) => Some((0, 0)),
263 Instruction::ClassicalData(ClassicalDataOp::MeasureBit { .. }) => Some((1, 0)),
264 Instruction::Delay => Some((1, 1)),
265 Instruction::Directive(Directive::Barrier)
266 | Instruction::ClassicalData(ClassicalDataOp::MeasureBits { .. })
267 | Instruction::ClassicalControl(_) => None,
268 }
269 }
270
271 /// Computes the unitary matrix representation of the instruction.
272 ///
273 /// # Arguments
274 ///
275 /// * `params` - A slice of floating-point parameters (e.g., rotation angles).
276 ///
277 /// # Returns
278 ///
279 /// - `Some(Cow<Array2>)`: The unitary matrix for `Standard` and `Extended` gates.
280 /// - `None`: If the instruction is non-unitary (e.g., `Measure`, `Barrier`, `Reset`,
281 /// or classical control).
282 pub fn matrix(&self, params: &[f64]) -> Option<Cow<'_, Array2<Complex64>>> {
283 match self {
284 Instruction::Standard(g) => g.matrix(params).ok(),
285 Instruction::McGate(g) => g.matrix(params).ok(),
286 Instruction::UnitaryGate(g) => g.matrix_for_params(params).ok(),
287 Instruction::CircuitGate(g) => circuit_to_matrix(&g.circuit.circuit, None)
288 .ok()
289 .map(Cow::Owned),
290 Instruction::Directive(_) => None,
291 Instruction::ClassicalData(_) => None,
292 Instruction::Delay => None,
293 Instruction::ClassicalControl(_) => None,
294 }
295 }
296
297 /// Computes the inverse (Hermitian conjugate) of the instruction.
298 ///
299 /// # Arguments
300 ///
301 /// * `params` - The parameters of the instruction instance.
302 ///
303 /// # Returns
304 ///
305 /// - `Some(...)`: The inverse instruction and its transformed parameters.
306 /// - `None`: If the instruction is non-invertible (e.g., `Measure` or classical control)
307 /// or if the inverse cannot be symbolically determined (e.g., some custom unitaries).
308 pub fn inverse(&self, params: &[Parameter]) -> Option<(Instruction, SmallVec<[Parameter; 3]>)> {
309 match self {
310 Instruction::Standard(g) => {
311 if let Some((gate, ps)) = g.inverse(params) {
312 Some((Self::Standard(gate), ps))
313 } else {
314 None
315 }
316 }
317 Instruction::McGate(g) => {
318 if let Some((gate, ps)) = g.inverse(params) {
319 Some((Self::McGate(Box::new(gate)), ps))
320 } else {
321 None
322 }
323 }
324 Instruction::UnitaryGate(g) => {
325 // Try to invert via circuit representation first
326 if let Some(c) = g.circuit().as_ref() {
327 // Invert the internal circuit
328 if let Ok(c_inv) = c.circuit.inverse() {
329 // Create frozen circuit from inverted circuit
330 let frozen_inv = FrozenCircuit::new(c_inv);
331 // Create new UnitaryGate with inverted circuit
332 let u_inv = UnitaryGate::new(
333 format!("{}_dg", g.label()).as_str(),
334 g.num_qubits(),
335 g.num_params(),
336 )
337 .with_circuit(Arc::new(frozen_inv))
338 .ok()?;
339 return Some((Self::UnitaryGate(Box::new(u_inv)), SmallVec::new()));
340 }
341 }
342 None
343 }
344 Instruction::CircuitGate(circuit_gate) => {
345 if let Ok(inv_gate) = circuit_gate.inverse() {
346 Some((
347 Instruction::CircuitGate(Box::new(inv_gate)),
348 params.iter().cloned().collect(),
349 ))
350 } else {
351 None
352 }
353 }
354 Instruction::Directive(d) => match d {
355 Directive::Barrier => Some((Self::Directive(Directive::Barrier), SmallVec::new())),
356 _ => None,
357 },
358 Instruction::ClassicalData(_) => None,
359 Instruction::Delay => Some((Self::Delay, SmallVec::new())),
360 Instruction::ClassicalControl(_) => None,
361 }
362 }
363
364 /// Returns a new instruction that applies the current operation conditioned on extra control qubits.
365 ///
366 /// This method employs a **canonicalization strategy** to return the most optimized gate representation:
367 ///
368 /// 1. **Promotion**: If adding controls to a `StandardGate` results in another `StandardGate`
369 /// (e.g., $X \xrightarrow{+1} CX \xrightarrow{+1} CCX$), it returns the upgraded standard gate.
370 /// 2. **Fallback**: If no standard equivalent exists (e.g., $C^3-X$), it returns
371 /// an [`Instruction::McGate`].
372 /// 3. **Aggregation**: If the instruction is already an `MCGate`, the new controls are merged into it.
373 ///
374 /// # Arguments
375 ///
376 /// * `num_new_ctrls` - The number of additional control qubits to add.
377 ///
378 /// # Returns
379 ///
380 /// - `Some(Instruction)`: The new controlled instruction.
381 /// - `None`: If the instruction cannot be controlled (e.g., `Barrier`, `Measure`,
382 /// or classical control).
383 pub fn control(&self, num_new_ctrls: usize) -> Option<Instruction> {
384 if num_new_ctrls == 0 {
385 return Some(self.clone());
386 }
387
388 match self {
389 Instruction::Standard(g) => {
390 let (base, curr_ctrls) = Self::decompose_standard_control(*g);
391 let total_ctrls = curr_ctrls + num_new_ctrls;
392
393 if let Some(std) = Self::compose_standard_control(base, total_ctrls) {
394 Some(Instruction::Standard(std))
395 } else {
396 Some(Instruction::McGate(Box::from(MCGate::new(
397 total_ctrls as u8,
398 base,
399 ))))
400 }
401 }
402 Instruction::McGate(mc) => {
403 let total_ctrls = mc.num_ctrl_qubits() + num_new_ctrls;
404 let (base, _) = Self::decompose_standard_control(mc.base_gate().to_owned());
405
406 if let Some(std) = Self::compose_standard_control(base, total_ctrls) {
407 Some(Instruction::Standard(std))
408 } else {
409 Some(Instruction::McGate(Box::from(MCGate::new(
410 total_ctrls as u8,
411 base,
412 ))))
413 }
414 }
415 Instruction::UnitaryGate(uni) => {
416 let mut g = UnitaryGate::new(
417 format!("ctl_{}_{}", num_new_ctrls, uni.label()).as_str(),
418 uni.num_qubits() + num_new_ctrls as u16,
419 uni.num_params(),
420 );
421 if let Some(m) = uni.matrix() {
422 let controlled = gate_matrix::control_matrix(m, num_new_ctrls);
423 // Handle possible matrix dimension error, return None on failure
424 g = match g.with_matrix(controlled) {
425 Ok(gate) => gate,
426 Err(_) => return None,
427 };
428 } else if let Some(m) = uni.symbolic_matrix() {
429 let controlled =
430 crate::circuit::symbolic_matrix::gate::control_matrix(m, num_new_ctrls);
431 let params = uni.matrix_params().unwrap_or(&[]);
432 g = match g.with_symbolic_matrix(params.iter().cloned(), controlled) {
433 Ok(gate) => gate,
434 Err(_) => return None,
435 };
436 }
437 // Copy circuit field if present
438 if let Some(c) = uni.circuit() {
439 g = match g.with_circuit(c.clone()) {
440 Ok(gate) => gate,
441 Err(_) => return None,
442 };
443 }
444
445 Some(Instruction::UnitaryGate(Box::from(g)))
446 }
447 Instruction::CircuitGate(_) => None, // CircuitGate does not support control yet
448 Instruction::Directive(_) => None,
449 Instruction::ClassicalData(_) => None,
450 Instruction::Delay => None,
451 Instruction::ClassicalControl(_) => None,
452 }
453 }
454}
455
456impl fmt::Display for Instruction {
457 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
458 f.write_str(&self.name())
459 }
460}
461
462impl From<StandardGate> for Instruction {
463 fn from(g: StandardGate) -> Self {
464 Self::Standard(g)
465 }
466}
467
468impl From<Directive> for Instruction {
469 fn from(d: Directive) -> Self {
470 Self::Directive(d)
471 }
472}
473
474impl From<ClassicalControlOp> for Instruction {
475 fn from(cf: ClassicalControlOp) -> Self {
476 Self::ClassicalControl(cf)
477 }
478}
479
480impl From<ClassicalDataOp> for Instruction {
481 fn from(op: ClassicalDataOp) -> Self {
482 Self::ClassicalData(op)
483 }
484}
485
486#[cfg(test)]
487#[path = "instruction_test.rs"]
488mod instruction_test;