Skip to main content

ket/ir/
gate.rs

1// SPDX-FileCopyrightText: 2024 Evandro Chagas Ribeiro da Rosa <evandro@quantuloop.com>
2//
3// SPDX-License-Identifier: Apache-2.0
4
5//! Intermediate representation (IR) for quantum gate instructions.
6//!
7//! This module defines the core data types used to represent individual quantum
8//! gates ([`QuantumGate`]), their numeric parameters ([`Param`]), and the
9//! composed gate-instruction type ([`GateInstruction`]) that carries a gate
10//! together with its target and control qubits.
11//!
12//! Gate decomposition: the process of rewriting a multi-controlled gate into
13//! a sequence of primitive one- and two-qubit gates: is handled by the
14//! methods on [`GateInstruction`].
15
16use itertools::{chain, Itertools};
17use serde::{Deserialize, Serialize};
18use std::{
19    collections::BTreeSet,
20    f64::consts::{FRAC_1_SQRT_2, FRAC_PI_2, FRAC_PI_4, FRAC_PI_8, PI},
21};
22
23use crate::{
24    decompose::{self, x::CXMode, Algorithm, AuxMode, DepthMode},
25    error::KetError,
26    ir::gate::QuantumGate::{PauliX, RotationZ},
27    matrix::{Cf64, Matrix},
28};
29
30/// A numeric parameter for rotation and phase gates.
31///
32/// A `Param` is either a concrete floating-point value ([`Param::Value`]) or a
33/// symbolic reference to an entry in a parameter vector ([`Param::Ref`]). The
34/// `Ref` variant enables deferred parameter binding for variational algorithms
35/// where the same circuit is evaluated at multiple parameter points.
36#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
37pub enum Param {
38    /// A concrete, pre-bound rotation angle (in radians).
39    Value(f64),
40    /// A symbolic reference to a parameter vector entry.
41    ///
42    /// The effective angle is `value * multiplier`. The `value` field stores
43    /// the base value at binding time; `multiplier` can be adjusted (e.g.,
44    /// negated for inversion) without touching the underlying vector.
45    Ref {
46        /// Zero-based index into the process parameter vector.
47        index: usize,
48        /// Scaling factor applied to the parameter value.
49        multiplier: f64,
50        /// The concrete value of the parameter at binding time.
51        value: f64,
52    },
53}
54
55impl Param {
56    /// Returns the mathematical inverse of the parameter (i.e., the negation).
57    ///
58    /// For `Value(v)` this returns `Value(-v)`; for `Ref` this negates the
59    /// `multiplier` field.
60    #[must_use]
61    pub fn inverse(&self) -> Self {
62        match self {
63            Self::Value(v) => Self::Value(-v),
64            Self::Ref {
65                index,
66                multiplier,
67                value,
68            } => Self::Ref {
69                index: *index,
70                multiplier: -multiplier,
71                value: *value,
72            },
73        }
74    }
75
76    /// Returns the current floating-point value of the parameter.
77    ///
78    /// For [`Param::Value`] this is a direct read. For [`Param::Ref`] the
79    /// effective value is `value * multiplier`.
80    #[must_use]
81    pub fn value(&self) -> f64 {
82        match self {
83            Self::Value(value) => *value,
84            Self::Ref {
85                multiplier, value, ..
86            } => *value * *multiplier,
87        }
88    }
89
90    /// Binds a symbolic [`Param::Ref`] to a concrete value from `parameters`.
91    ///
92    /// If `self` is already a [`Param::Value`] it is returned unchanged.
93    /// Otherwise the result is `Value(base_value * multiplier * parameters[index])`.
94    #[must_use]
95    pub fn set_parameter(&self, parameters: &[f64]) -> Self {
96        match self {
97            Self::Value(_) => *self,
98            Self::Ref {
99                multiplier,
100                value: _,
101                index,
102            } => Self::Value(*multiplier * parameters[*index]),
103        }
104    }
105
106    /// Attempts to add two parameters together.
107    ///
108    /// Returns `Some(Value(a + b))` only when **both** operands are
109    /// [`Param::Value`]. Any combination involving a [`Param::Ref`] returns
110    /// `None`, because symbolic parameters cannot be merged algebraically at
111    /// compile time.
112    #[must_use]
113    pub fn add(&self, other: &Self) -> Option<Self> {
114        let (Self::Value(a), Self::Value(b)) = (self, other) else {
115            return None;
116        };
117        Some(Self::Value(a + b))
118    }
119
120    /// Returns `true` if the parameter's effective rotation angle is within
121    /// `epsilon` of zero.
122    ///
123    /// For [`Param::Ref`] only the `multiplier` is inspected, since the actual
124    /// parameter value is not known at compile time.
125    #[must_use]
126    pub fn is_near_zero(&self, epsilon: f64) -> bool {
127        match self {
128            Self::Value(v) => v.abs() <= epsilon,
129            Self::Ref { multiplier, .. } => multiplier.abs() <= epsilon,
130        }
131    }
132}
133
134/// Classification of a gate's action on the computational basis.
135///
136/// Variants are ordered from least to most general so that the derived `Ord`
137/// implementation makes `Unitary > Permutation > Diagonal > Identity` hold.
138#[derive(Debug, Clone, Copy, PartialEq, Serialize, Default, Eq, PartialOrd, Ord)]
139pub enum GatePropriety {
140    /// The gate is the identity: it leaves all basis states unchanged.
141    #[default]
142    Identity,
143    /// The gate is diagonal in the computational basis (it only adds phases).
144    Diagonal,
145    /// The gate permutes computational-basis states without introducing superposition.
146    Permutation,
147    /// The gate is a general unitary that creates superposition.
148    Unitary,
149}
150
151impl GatePropriety {
152    pub fn restrict(&mut self, propriety: GatePropriety) {
153        *self = match (*self, propriety) {
154            (GatePropriety::Identity, other) | (other, GatePropriety::Identity) => other,
155            (GatePropriety::Diagonal, GatePropriety::Diagonal) => GatePropriety::Diagonal,
156            (_, GatePropriety::Unitary) | (GatePropriety::Unitary, _) => GatePropriety::Unitary,
157            _ => GatePropriety::Permutation,
158        };
159    }
160
161    pub fn broaden(&mut self, propriety: GatePropriety) {
162        *self = match (*self, propriety) {
163            (GatePropriety::Identity, _) | (_, GatePropriety::Identity) => GatePropriety::Identity,
164            (
165                GatePropriety::Diagonal | GatePropriety::Permutation | GatePropriety::Unitary,
166                GatePropriety::Diagonal,
167            )
168            | (GatePropriety::Diagonal, GatePropriety::Permutation) => GatePropriety::Diagonal,
169            (GatePropriety::Permutation | GatePropriety::Unitary, GatePropriety::Permutation) => {
170                GatePropriety::Permutation
171            }
172            (other, GatePropriety::Unitary) => other,
173        }
174    }
175}
176
177/// The fundamental set of single-qubit quantum gates supported by the compiler.
178///
179/// Each variant corresponds to a standard gate from the universal gate set.
180/// Rotation gates carry a [`Param`] that encodes the rotation angle in radians.
181#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
182pub enum QuantumGate {
183    /// The Pauli-X (bit-flip) gate.
184    PauliX,
185    /// The Pauli-Y gate.
186    PauliY,
187    /// The Pauli-Z (phase-flip) gate.
188    PauliZ,
189    /// Rotation about the X-axis by angle `θ`: `Rx(θ) = exp(-i θ/2 · X)`.
190    RotationX(Param),
191    /// Rotation about the Y-axis by angle `θ`: `Ry(θ) = exp(-i θ/2 · Y)`.
192    RotationY(Param),
193    /// Rotation about the Z-axis by angle `θ`: `Rz(θ) = exp(-i θ/2 · Z)`.
194    RotationZ(Param),
195    /// Phase gate: `P(λ) = diag(1, e^{iλ})`.
196    Phase(Param),
197    /// The Hadamard gate.
198    Hadamard,
199}
200
201impl QuantumGate {
202    /// Returns `true` if the gate acts as a permutation of computational-basis
203    /// states (including X-rotations by `±π`).
204    #[must_use]
205    pub fn is_permutation(&self) -> bool {
206        match self {
207            Self::RotationX(param) | Self::RotationY(param) => {
208                if let Param::Value(value) = param {
209                    (value.abs() - PI).abs() <= 1e-10
210                        || 2.0f64.mul_add(-PI, value.abs()).abs() <= 1e-10
211                } else {
212                    false
213                }
214            }
215            Self::Hadamard => false,
216            _ => true,
217        }
218    }
219
220    /// Returns the parameter index and multiplier if this gate holds a
221    /// [`Param::Ref`] (i.e., a symbolic variational parameter).
222    ///
223    /// Returns `Some((index, multiplier))` for `RotationX`, `RotationY`,
224    /// `RotationZ`, and `Phase` variants whose parameter is a [`Param::Ref`].
225    /// Returns `None` for all other gates or when the parameter is a
226    /// concrete [`Param::Value`].
227    ///
228    /// Used by the parameter-shift rule to identify which gate instructions
229    /// correspond to a given parameter index.
230    #[must_use]
231    pub fn param_index(&self) -> Option<(usize, f64)> {
232        match self {
233            Self::RotationX(Param::Ref {
234                index, multiplier, ..
235            })
236            | Self::RotationY(Param::Ref {
237                index, multiplier, ..
238            })
239            | Self::RotationZ(Param::Ref {
240                index, multiplier, ..
241            })
242            | Self::Phase(Param::Ref {
243                index, multiplier, ..
244            }) => Some((*index, *multiplier)),
245            _ => None,
246        }
247    }
248
249    /// Returns `true` if the gate is diagonal in the computational basis.
250    #[must_use]
251    pub const fn is_diagonal(&self) -> bool {
252        matches!(self, Self::PauliZ | Self::RotationZ(_) | Self::Phase(_))
253    }
254
255    /// Returns the adjoint (Hermitian conjugate / inverse) of this gate.
256    #[must_use]
257    pub fn inverse(&self) -> Self {
258        match self {
259            Self::PauliX => Self::PauliX,
260            Self::PauliY => Self::PauliY,
261            Self::PauliZ => Self::PauliZ,
262            Self::RotationX(p) => Self::RotationX(p.inverse()),
263            Self::RotationY(p) => Self::RotationY(p.inverse()),
264            Self::RotationZ(p) => Self::RotationZ(p.inverse()),
265            Self::Phase(p) => Self::Phase(p.inverse()),
266            Self::Hadamard => Self::Hadamard,
267        }
268    }
269
270    /// Classifies the gate according to its action on the computational basis.
271    #[must_use]
272    pub fn propriety(&self) -> GatePropriety {
273        match self {
274            Self::PauliX | Self::PauliY => GatePropriety::Permutation,
275            Self::RotationX(_) | Self::RotationY(_) => {
276                if self.is_permutation() {
277                    GatePropriety::Permutation
278                } else {
279                    GatePropriety::Unitary
280                }
281            }
282            Self::PauliZ | Self::RotationZ(_) | Self::Phase(_) => GatePropriety::Diagonal,
283            Self::Hadamard => GatePropriety::Unitary,
284        }
285    }
286
287    /// Constructs an S gate (`Phase(π/2)`).
288    #[must_use]
289    pub const fn s() -> Self {
290        Self::Phase(Param::Value(FRAC_PI_2))
291    }
292
293    /// Constructs an S† gate (`Phase(-π/2)`).
294    #[must_use]
295    pub fn sd() -> Self {
296        Self::Phase(Param::Value(-FRAC_PI_2))
297    }
298
299    /// Constructs a T gate (`Phase(π/4)`).
300    #[must_use]
301    pub const fn t() -> Self {
302        Self::Phase(Param::Value(FRAC_PI_4))
303    }
304
305    /// Constructs a T† gate (`Phase(-π/4)`).
306    #[must_use]
307    pub fn td() -> Self {
308        Self::Phase(Param::Value(-FRAC_PI_4))
309    }
310
311    /// Constructs a √T gate (`Phase(π/8)`).
312    #[must_use]
313    pub const fn sqrt_t() -> Self {
314        Self::Phase(Param::Value(FRAC_PI_8))
315    }
316
317    /// Constructs a √T† gate (`Phase(-π/8)`).
318    #[must_use]
319    pub fn sqrt_td() -> Self {
320        Self::Phase(Param::Value(-FRAC_PI_8))
321    }
322
323    /// Returns the 2×2 unitary matrix representation of the gate.
324    pub(crate) fn matrix(&self) -> Matrix {
325        match self {
326            Self::PauliX => [[0.0.into(), 1.0.into()], [1.0.into(), 0.0.into()]],
327            Self::PauliY => [[0.0.into(), -Cf64::i()], [Cf64::i(), 0.0.into()]],
328            Self::PauliZ => [[1.0.into(), 0.0.into()], [0.0.into(), (-1.0).into()]],
329            Self::RotationX(angle) => {
330                let angle = angle.value();
331                [
332                    [(angle / 2.0).cos().into(), -Cf64::i() * (angle / 2.0).sin()],
333                    [-Cf64::i() * (angle / 2.0).sin(), (angle / 2.0).cos().into()],
334                ]
335            }
336            Self::RotationY(angle) => {
337                let angle = angle.value();
338                [
339                    [(angle / 2.0).cos().into(), (-(angle / 2.0).sin()).into()],
340                    [(angle / 2.0).sin().into(), (angle / 2.0).cos().into()],
341                ]
342            }
343            Self::RotationZ(angle) => {
344                let angle = angle.value();
345                [
346                    [(-Cf64::i() * (angle / 2.0)).exp(), 0.0.into()],
347                    [0.0.into(), (Cf64::i() * (angle / 2.0)).exp()],
348                ]
349            }
350            Self::Phase(angle) => [
351                [1.0.into(), 0.0.into()],
352                [0.0.into(), (Cf64::i() * angle.value()).exp()],
353            ],
354            Self::Hadamard => [
355                [(1.0 / 2.0f64.sqrt()).into(), (1.0 / 2.0f64.sqrt()).into()],
356                [(1.0 / 2.0f64.sqrt()).into(), (-1.0 / 2.0f64.sqrt()).into()],
357            ],
358        }
359    }
360
361    /// Returns the SU(2) (determinant-1) matrix representative of the gate.
362    ///
363    /// Some gates (e.g. `PauliX`) have a natural matrix that is in U(2) but not
364    /// SU(2). This method returns the closest SU(2) representative, which is
365    /// needed for certain decomposition algorithms.
366    pub(crate) fn su2_matrix(&self) -> Matrix {
367        match self {
368            Self::PauliX => Self::RotationX(Param::Value(PI)).matrix(),
369            Self::PauliY => Self::RotationY(Param::Value(PI)).matrix(),
370            Self::PauliZ => Self::RotationZ(Param::Value(PI)).matrix(),
371            Self::Phase(angle) => Self::RotationZ(*angle).matrix(),
372            Self::Hadamard => [
373                [-Cf64::i() * FRAC_1_SQRT_2, -Cf64::i() * FRAC_1_SQRT_2],
374                [-Cf64::i() * FRAC_1_SQRT_2, Cf64::i() * FRAC_1_SQRT_2],
375            ],
376            _ => self.matrix(),
377        }
378    }
379
380    /// Attempts to merge two consecutive same-type rotation gates into one.
381    ///
382    /// Returns `Some(merged)` when both gates are the same rotation axis and
383    /// their parameters can be added (see [`Param::add`]), or `None` otherwise.
384    pub fn merge(&self, other: &Self) -> Option<Self> {
385        match (self, other) {
386            (Self::RotationX(a), Self::RotationX(b)) => a.add(b).map(QuantumGate::RotationX),
387            (Self::RotationY(a), Self::RotationY(b)) => a.add(b).map(QuantumGate::RotationY),
388            (Self::RotationZ(a), Self::RotationZ(b)) => a.add(b).map(QuantumGate::RotationZ),
389            (Self::Phase(a), Self::Phase(b)) => a.add(b).map(QuantumGate::Phase),
390            _ => None,
391        }
392    }
393
394    /// Returns `true` if the gate's rotation angle is within `epsilon` of zero,
395    /// making it effectively the identity.
396    ///
397    /// Only parametric gates (`Rotation*` and `Phase`) can be near zero;
398    /// non-parametric gates always return `false`.
399    #[must_use]
400    pub fn is_near_zero(&self, epsilon: f64) -> bool {
401        match self {
402            Self::RotationX(p) | Self::RotationY(p) | Self::RotationZ(p) | Self::Phase(p) => {
403                p.is_near_zero(epsilon)
404            }
405            _ => false,
406        }
407    }
408
409    /// Binds all symbolic parameters in this gate to their concrete values.
410    ///
411    /// For non-parametric gates (`PauliX`, `PauliY`, `PauliZ`, `Hadamard`)
412    /// this is a no-op and returns a copy of `self`. For parametric gates the
413    /// [`Param`] is resolved via [`Param::set_parameter`].
414    #[must_use]
415    pub fn set_parameter(&self, parameters: &[f64]) -> Self {
416        match self {
417            Self::PauliX | Self::PauliY | Self::PauliZ | Self::Hadamard => *self,
418            Self::RotationX(param) => Self::RotationX(param.set_parameter(parameters)),
419            Self::RotationY(param) => Self::RotationY(param.set_parameter(parameters)),
420            Self::RotationZ(param) => Self::RotationZ(param.set_parameter(parameters)),
421            Self::Phase(param) => Self::Phase(param.set_parameter(parameters)),
422        }
423    }
424}
425
426/// A primitive quantum gate resulting from the decomposition of a multi-controlled gate.
427///
428/// Multi-controlled gates are decomposed into a sequence of single-qubit gates
429/// ([`DecomposedGate::U`]) and two-qubit CNOT gates ([`DecomposedGate::CNOT`]).
430#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
431pub enum DecomposedGate {
432    /// A single-qubit gate instruction applied to a target qubit.
433    U(QuantumGate, usize),
434    /// A CNOT gate instruction applied to a control and a target qubit.
435    CNOT(usize, usize),
436}
437
438impl DecomposedGate {
439    #[must_use]
440    pub fn inverse(&self) -> Self {
441        match self {
442            DecomposedGate::U(gate, t) => DecomposedGate::U(gate.inverse(), *t),
443            DecomposedGate::CNOT(c, t) => DecomposedGate::CNOT(*c, *t),
444        }
445    }
446}
447
448impl From<(QuantumGate, usize)> for DecomposedGate {
449    fn from(value: (QuantumGate, usize)) -> Self {
450        let (gate, target) = value;
451        Self::U(gate, target)
452    }
453}
454
455/// A fully-specified gate instruction applied to one or more logical qubits.
456///
457/// A [`GateInstruction`] bundles a [`QuantumGate`] with its `target` qubit index
458/// and a set of `control` qubit indices. It also tracks whether new
459/// controls may still be added (`control_locked`), whether approximate
460/// decomposition algorithms may be used (`is_approximated`), and the optional
461/// pre-computed gate decomposition (`decomposed`).
462#[derive(Debug, Clone, PartialEq, Serialize)]
463pub struct GateInstruction {
464    /// The quantum gate to apply.
465    pub gate: QuantumGate,
466    /// The index of the target qubit.
467    pub target: usize,
468    /// The set of control qubit indices.
469    pub control: BTreeSet<usize>,
470    /// When `true`, no additional control qubits may be added to this instruction.
471    pub control_locked: bool,
472    /// When `true`, the gate decomposition may use relative-phase decompostion that
473    /// reduce the gate count.
474    pub is_approximated: bool,
475    /// The gate decomposed into primitive (CNOT + single-qubit) operations.
476    pub decomposed: Option<Vec<DecomposedGate>>,
477}
478
479impl From<&DecomposedGate> for GateInstruction {
480    fn from(value: &DecomposedGate) -> Self {
481        match value {
482            DecomposedGate::U(gate, target) => Self {
483                gate: *gate,
484                target: *target,
485                control: BTreeSet::new(),
486                control_locked: false,
487                is_approximated: false,
488                decomposed: None,
489            },
490            DecomposedGate::CNOT(control, target) => Self {
491                gate: PauliX,
492                target: *target,
493                control: BTreeSet::from([*control]),
494                control_locked: false,
495                is_approximated: false,
496                decomposed: None,
497            },
498        }
499    }
500}
501
502impl GateInstruction {
503    /// Creates a new uncontrolled gate instruction for `gate` applied to `target`.
504    #[must_use]
505    pub const fn new(gate: QuantumGate, target: usize) -> Self {
506        Self {
507            gate,
508            target,
509            control: BTreeSet::new(),
510            control_locked: false,
511            is_approximated: false,
512            decomposed: None,
513        }
514    }
515
516    /// Returns the adjoint (inverse) of this gate instruction.
517    ///
518    /// The control set and lock status are preserved. If the instruction has
519    /// already been decomposed, the decomposition is reversed and each
520    /// constituent gate is individually inverted.
521    #[must_use]
522    pub fn inverse(&self) -> Self {
523        Self {
524            gate: self.gate.inverse(),
525            target: self.target,
526            control: self.control.clone(),
527            control_locked: self.control_locked,
528            is_approximated: self.is_approximated,
529            decomposed: self
530                .decomposed
531                .as_ref()
532                .map(|gates| gates.iter().rev().map(DecomposedGate::inverse).collect()),
533        }
534    }
535
536    /// Adds `control_qubits` to the control set of this instruction.
537    ///
538    /// # Errors
539    /// - [`KetError::DuplicateControlQubit`]: one of `control_qubits` is
540    ///   already present in the control set.
541    /// - [`KetError::ControlTargetConflict`]: one of `control_qubits` is the
542    ///   same qubit as `self.target`.
543    ///
544    /// If the instruction's controls are locked ([`GateInstruction::control_locked`]
545    /// is `true`), the call is a no-op and `Ok(self.clone())` is returned.
546    pub fn control(&self, control_qubits: &[usize]) -> Result<Self, KetError> {
547        if self.control_locked {
548            return Ok(self.clone());
549        }
550
551        let mut control = self.control.clone();
552        control.extend(control_qubits.iter());
553
554        if self.gate.param_index().is_some() {
555            Err(KetError::ControlParameterizedGate)
556        } else if control.len() != (control_qubits.len() + self.control.len()) {
557            Err(KetError::DuplicateControlQubit)
558        } else if control.contains(&self.target) {
559            Err(KetError::ControlTargetConflict)
560        } else {
561            Ok(Self {
562                gate: self.gate,
563                target: self.target,
564                control,
565                control_locked: false,
566                is_approximated: false,
567                decomposed: None,
568            })
569        }
570    }
571
572    /// Enable approximate decomposition.
573    ///
574    /// When `is_approximated` is `true`, decomposition algorithms use a
575    /// smaller circuits are permitted.
576    pub fn enable_approximated_decomposition(&mut self) {
577        self.is_approximated = true;
578    }
579
580    /// Lock the control set.
581    ///
582    /// A locked instruction will silently ignore any subsequent calls to
583    /// [`GateInstruction::control`], returning a clone of itself unchanged.
584    pub fn lock_control(&mut self) {
585        self.control_locked = true;
586    }
587
588    /// Returns `true` if this instruction commutes with `other`.
589    ///
590    /// Two instructions commute when both are diagonal, or when they apply the
591    /// same gate to the same target.
592    #[must_use]
593    pub fn commutes_with(&self, other: &Self) -> bool {
594        self.gate.is_diagonal() && other.gate.is_diagonal()
595            || (self.gate == other.gate && self.target == other.target)
596    }
597
598    /// Decomposes a multi-controlled gate into primitive two-qubit operations.
599    ///
600    /// The decomposition is written into `self.decomposed`. If the instruction
601    /// has no control qubits, or if `decomposed` is already populated, this
602    /// method is a no-op.
603    ///
604    /// `clean_auxiliary_range` is a half-open range `(start, end)` of qubit
605    /// indices that are guaranteed to be in the |0⟩ state (clean auxiliary).
606    pub fn decompose(&mut self, clean_auxiliary_range: (usize, usize)) {
607        let control = self.control.iter().copied().collect_vec();
608        if !self.control.is_empty() && self.decomposed.is_none() {
609            self.decomposed = Some(match self.gate {
610                QuantumGate::PauliX => Self::decompose_x(
611                    self.target,
612                    &control,
613                    self.is_approximated,
614                    clean_auxiliary_range,
615                ),
616                QuantumGate::PauliY => Self::decompose_y(
617                    self.target,
618                    &control,
619                    self.is_approximated,
620                    clean_auxiliary_range,
621                ),
622                QuantumGate::PauliZ => Self::decompose_z(
623                    self.target,
624                    &control,
625                    self.is_approximated,
626                    clean_auxiliary_range,
627                ),
628                QuantumGate::RotationX(_)
629                | QuantumGate::RotationY(_)
630                | QuantumGate::RotationZ(_) => Self::decompose_r(
631                    self.gate,
632                    self.target,
633                    &control,
634                    self.is_approximated,
635                    clean_auxiliary_range,
636                ),
637                QuantumGate::Phase(angle) => Self::decompose_phase(
638                    angle.value(),
639                    self.target,
640                    &control,
641                    self.is_approximated,
642                    clean_auxiliary_range,
643                ),
644                QuantumGate::Hadamard => Self::decompose_hadamard(
645                    self.target,
646                    &control,
647                    self.is_approximated,
648                    clean_auxiliary_range,
649                ),
650            });
651        }
652    }
653
654    /// Decomposes a multi-controlled general rotation gate.
655    fn decompose_r(
656        gate: QuantumGate,
657        target: usize,
658        control: &[usize],
659        approximated: bool,
660        clean_axillary_range: (usize, usize),
661    ) -> Vec<DecomposedGate> {
662        if control.len() == 1 {
663            return decompose::u2::cu2(gate.matrix(), control[0], target);
664        }
665
666        let num_clean_aux = clean_axillary_range.1 - clean_axillary_range.0;
667        let network_num_aux = Algorithm::NetworkU2(CXMode::C3X).aux_needed(control.len());
668
669        if network_num_aux <= num_clean_aux {
670            let aux_qubits: Vec<_> =
671                (clean_axillary_range.0..clean_axillary_range.0 + network_num_aux).collect();
672
673            decompose::network::network(
674                gate,
675                control,
676                &aux_qubits,
677                target,
678                CXMode::C3X,
679                approximated,
680            )
681        } else {
682            decompose::su2::decompose(gate, control, target, DepthMode::Linear, approximated)
683        }
684    }
685
686    /// Decomposes a multi-controlled Pauli-X gate.
687    fn decompose_x(
688        target: usize,
689        control: &[usize],
690        approximated: bool,
691        clean_axillary_range: (usize, usize),
692    ) -> Vec<DecomposedGate> {
693        if control.len() <= 4 {
694            return decompose::x::c2to4x::c1to4x(control, target, approximated);
695        }
696
697        let num_clean_aux = clean_axillary_range.1 - clean_axillary_range.0;
698
699        let network_c2x_num_aux = Algorithm::NetworkPauli(CXMode::C2X).aux_needed(control.len());
700
701        if num_clean_aux >= network_c2x_num_aux {
702            let aux_qubits: Vec<_> =
703                (clean_axillary_range.0..clean_axillary_range.0 + network_c2x_num_aux).collect();
704
705            return decompose::network::network(
706                QuantumGate::PauliX,
707                control,
708                &aux_qubits,
709                target,
710                CXMode::C2X,
711                approximated,
712            );
713        }
714
715        let network_c3x_num_aux = Algorithm::NetworkPauli(CXMode::C3X).aux_needed(control.len());
716
717        if num_clean_aux >= network_c3x_num_aux {
718            let aux_qubits: Vec<_> =
719                (clean_axillary_range.0..clean_axillary_range.0 + network_c3x_num_aux).collect();
720
721            return decompose::network::network(
722                QuantumGate::PauliX,
723                control,
724                &aux_qubits,
725                target,
726                CXMode::C3X,
727                approximated,
728            );
729        }
730        let v_chain_c3x_clean_num_aux =
731            Algorithm::VChain(CXMode::C3X, AuxMode::Clean).aux_needed(control.len());
732
733        if num_clean_aux >= v_chain_c3x_clean_num_aux {
734            let aux_qubits: Vec<_> = (clean_axillary_range.0
735                ..clean_axillary_range.0 + v_chain_c3x_clean_num_aux)
736                .collect();
737
738            return decompose::x::v_chain::v_chain(
739                control,
740                &aux_qubits,
741                target,
742                AuxMode::Clean,
743                CXMode::C3X,
744                approximated,
745            );
746        }
747
748        let num_dirty_aux = clean_axillary_range.1 - 1 - control.len();
749
750        let v_chain_c2x_dirty_num_aux =
751            Algorithm::VChain(CXMode::C2X, AuxMode::Dirty).aux_needed(control.len());
752
753        if num_dirty_aux >= v_chain_c2x_dirty_num_aux {
754            let all_qubits = control.iter().copied().chain([target]).collect_vec();
755
756            let aux_qubits: Vec<_> = (0..v_chain_c3x_clean_num_aux + 1 + control.len())
757                .filter(|qubit| !all_qubits.contains(qubit))
758                .collect();
759
760            decompose::x::v_chain::v_chain(
761                control,
762                &aux_qubits,
763                target,
764                AuxMode::Dirty,
765                CXMode::C2X,
766                approximated,
767            )
768        } else if num_clean_aux >= 1 {
769            decompose::x::single_aux::decompose(
770                control,
771                clean_axillary_range.0,
772                target,
773                AuxMode::Clean,
774                DepthMode::Linear,
775                approximated,
776            )
777        } else {
778            decompose::u2::linear_depth(QuantumGate::PauliX, control, target)
779        }
780    }
781
782    /// Decomposes a multi-controlled Pauli-Y gate.
783    ///
784    /// Uses the identity CY = (S†⊗I) · CX · (S⊗I).
785    fn decompose_y(
786        target: usize,
787        control: &[usize],
788        approximated: bool,
789        clean_axillary_range: (usize, usize),
790    ) -> Vec<DecomposedGate> {
791        chain![
792            [(QuantumGate::sd(), target).into()],
793            Self::decompose_x(target, control, approximated, clean_axillary_range),
794            [(QuantumGate::s(), target).into()]
795        ]
796        .collect()
797    }
798
799    /// Decomposes a multi-controlled Pauli-Z gate.
800    ///
801    /// Uses the identity CZ = (H⊗I) · CX · (H⊗I).
802    fn decompose_z(
803        target: usize,
804        control: &[usize],
805        approximated: bool,
806        clean_axillary_range: (usize, usize),
807    ) -> Vec<DecomposedGate> {
808        chain![
809            [(QuantumGate::Hadamard, target).into()],
810            Self::decompose_x(target, control, approximated, clean_axillary_range),
811            [(QuantumGate::Hadamard, target).into()]
812        ]
813        .collect()
814    }
815
816    /// Decomposes a multi-controlled Phase gate.
817    fn decompose_phase(
818        angle: f64,
819        target: usize,
820        control: &[usize],
821        approximated: bool,
822        clean_axillary_range: (usize, usize),
823    ) -> Vec<DecomposedGate> {
824        let num_clean_aux = clean_axillary_range.1 - clean_axillary_range.0;
825        let network_c3x_num_aux = Algorithm::NetworkU2(CXMode::C3X).aux_needed(control.len());
826
827        if control.len() == 1 {
828            decompose::u2::cu2(
829                QuantumGate::Phase(Param::Value(angle)).matrix(),
830                control[0],
831                target,
832            )
833        } else if num_clean_aux >= network_c3x_num_aux {
834            let aux_qubits: Vec<_> =
835                (clean_axillary_range.0..clean_axillary_range.0 + network_c3x_num_aux).collect();
836
837            decompose::network::network(
838                QuantumGate::Phase(Param::Value(angle)),
839                control,
840                &aux_qubits,
841                target,
842                CXMode::C3X,
843                approximated,
844            )
845        } else if num_clean_aux >= 1 {
846            let control = control.iter().copied().chain([target]).collect_vec();
847            decompose::su2::decompose(
848                RotationZ(Param::Value(-2.0 * angle)),
849                &control,
850                clean_axillary_range.0,
851                DepthMode::Linear,
852                approximated,
853            )
854        } else {
855            decompose::u2::linear_depth(QuantumGate::Phase(Param::Value(angle)), control, target)
856        }
857    }
858
859    /// Decomposes a multi-controlled Hadamard gate.
860    fn decompose_hadamard(
861        target: usize,
862        control: &[usize],
863        approximated: bool,
864        clean_axillary_range: (usize, usize),
865    ) -> Vec<DecomposedGate> {
866        let num_clean_aux = clean_axillary_range.1 - clean_axillary_range.0;
867        let network_c3x_num_aux = Algorithm::NetworkU2(CXMode::C3X).aux_needed(control.len());
868
869        if control.len() == 1 {
870            decompose::u2::cu2(QuantumGate::Hadamard.matrix(), control[0], target)
871        } else if num_clean_aux >= network_c3x_num_aux {
872            let aux_qubits: Vec<_> =
873                (clean_axillary_range.0..clean_axillary_range.0 + network_c3x_num_aux).collect();
874
875            decompose::network::network(
876                QuantumGate::Hadamard,
877                control,
878                &aux_qubits,
879                target,
880                CXMode::C3X,
881                approximated,
882            )
883        } else if num_clean_aux >= 1 {
884            decompose::u2::su2_rewrite_hadamard(control, clean_axillary_range.0, target)
885        } else {
886            decompose::u2::linear_depth(QuantumGate::Hadamard, control, target)
887        }
888    }
889}