use itertools::{chain, Itertools};
use serde::{Deserialize, Serialize};
use std::{
collections::BTreeSet,
f64::consts::{FRAC_1_SQRT_2, FRAC_PI_2, FRAC_PI_4, FRAC_PI_8, PI},
};
use crate::{
decompose::{self, x::CXMode, Algorithm, AuxMode, DepthMode},
error::KetError,
ir::gate::QuantumGate::{PauliX, RotationZ},
matrix::{Cf64, Matrix},
};
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum Param {
Value(f64),
Ref {
index: usize,
multiplier: f64,
value: f64,
},
}
impl Param {
#[must_use]
pub fn inverse(&self) -> Self {
match self {
Self::Value(v) => Self::Value(-v),
Self::Ref {
index,
multiplier,
value,
} => Self::Ref {
index: *index,
multiplier: -multiplier,
value: *value,
},
}
}
#[must_use]
pub fn value(&self) -> f64 {
match self {
Self::Value(value) => *value,
Self::Ref {
multiplier, value, ..
} => *value * *multiplier,
}
}
#[must_use]
pub fn set_parameter(&self, parameters: &[f64]) -> Self {
match self {
Self::Value(_) => *self,
Self::Ref {
multiplier,
value: _,
index,
} => Self::Value(*multiplier * parameters[*index]),
}
}
#[must_use]
pub fn add(&self, other: &Self) -> Option<Self> {
let (Self::Value(a), Self::Value(b)) = (self, other) else {
return None;
};
Some(Self::Value(a + b))
}
#[must_use]
pub fn is_near_zero(&self, epsilon: f64) -> bool {
match self {
Self::Value(v) => v.abs() <= epsilon,
Self::Ref { multiplier, .. } => multiplier.abs() <= epsilon,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Default, Eq, PartialOrd, Ord)]
pub enum GatePropriety {
#[default]
Identity,
Diagonal,
Permutation,
Unitary,
}
impl GatePropriety {
pub fn restrict(&mut self, propriety: GatePropriety) {
*self = match (*self, propriety) {
(GatePropriety::Identity, other) | (other, GatePropriety::Identity) => other,
(GatePropriety::Diagonal, GatePropriety::Diagonal) => GatePropriety::Diagonal,
(_, GatePropriety::Unitary) | (GatePropriety::Unitary, _) => GatePropriety::Unitary,
_ => GatePropriety::Permutation,
};
}
pub fn broaden(&mut self, propriety: GatePropriety) {
*self = match (*self, propriety) {
(GatePropriety::Identity, _) | (_, GatePropriety::Identity) => GatePropriety::Identity,
(
GatePropriety::Diagonal | GatePropriety::Permutation | GatePropriety::Unitary,
GatePropriety::Diagonal,
)
| (GatePropriety::Diagonal, GatePropriety::Permutation) => GatePropriety::Diagonal,
(GatePropriety::Permutation | GatePropriety::Unitary, GatePropriety::Permutation) => {
GatePropriety::Permutation
}
(other, GatePropriety::Unitary) => other,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum QuantumGate {
PauliX,
PauliY,
PauliZ,
RotationX(Param),
RotationY(Param),
RotationZ(Param),
Phase(Param),
Hadamard,
}
impl QuantumGate {
#[must_use]
pub fn is_permutation(&self) -> bool {
match self {
Self::RotationX(param) | Self::RotationY(param) => {
if let Param::Value(value) = param {
(value.abs() - PI).abs() <= 1e-10
|| 2.0f64.mul_add(-PI, value.abs()).abs() <= 1e-10
} else {
false
}
}
Self::Hadamard => false,
_ => true,
}
}
#[must_use]
pub fn param_index(&self) -> Option<(usize, f64)> {
match self {
Self::RotationX(Param::Ref {
index, multiplier, ..
})
| Self::RotationY(Param::Ref {
index, multiplier, ..
})
| Self::RotationZ(Param::Ref {
index, multiplier, ..
})
| Self::Phase(Param::Ref {
index, multiplier, ..
}) => Some((*index, *multiplier)),
_ => None,
}
}
#[must_use]
pub const fn is_diagonal(&self) -> bool {
matches!(self, Self::PauliZ | Self::RotationZ(_) | Self::Phase(_))
}
#[must_use]
pub fn inverse(&self) -> Self {
match self {
Self::PauliX => Self::PauliX,
Self::PauliY => Self::PauliY,
Self::PauliZ => Self::PauliZ,
Self::RotationX(p) => Self::RotationX(p.inverse()),
Self::RotationY(p) => Self::RotationY(p.inverse()),
Self::RotationZ(p) => Self::RotationZ(p.inverse()),
Self::Phase(p) => Self::Phase(p.inverse()),
Self::Hadamard => Self::Hadamard,
}
}
#[must_use]
pub fn propriety(&self) -> GatePropriety {
match self {
Self::PauliX | Self::PauliY => GatePropriety::Permutation,
Self::RotationX(_) | Self::RotationY(_) => {
if self.is_permutation() {
GatePropriety::Permutation
} else {
GatePropriety::Unitary
}
}
Self::PauliZ | Self::RotationZ(_) | Self::Phase(_) => GatePropriety::Diagonal,
Self::Hadamard => GatePropriety::Unitary,
}
}
#[must_use]
pub const fn s() -> Self {
Self::Phase(Param::Value(FRAC_PI_2))
}
#[must_use]
pub fn sd() -> Self {
Self::Phase(Param::Value(-FRAC_PI_2))
}
#[must_use]
pub const fn t() -> Self {
Self::Phase(Param::Value(FRAC_PI_4))
}
#[must_use]
pub fn td() -> Self {
Self::Phase(Param::Value(-FRAC_PI_4))
}
#[must_use]
pub const fn sqrt_t() -> Self {
Self::Phase(Param::Value(FRAC_PI_8))
}
#[must_use]
pub fn sqrt_td() -> Self {
Self::Phase(Param::Value(-FRAC_PI_8))
}
pub(crate) fn matrix(&self) -> Matrix {
match self {
Self::PauliX => [[0.0.into(), 1.0.into()], [1.0.into(), 0.0.into()]],
Self::PauliY => [[0.0.into(), -Cf64::i()], [Cf64::i(), 0.0.into()]],
Self::PauliZ => [[1.0.into(), 0.0.into()], [0.0.into(), (-1.0).into()]],
Self::RotationX(angle) => {
let angle = angle.value();
[
[(angle / 2.0).cos().into(), -Cf64::i() * (angle / 2.0).sin()],
[-Cf64::i() * (angle / 2.0).sin(), (angle / 2.0).cos().into()],
]
}
Self::RotationY(angle) => {
let angle = angle.value();
[
[(angle / 2.0).cos().into(), (-(angle / 2.0).sin()).into()],
[(angle / 2.0).sin().into(), (angle / 2.0).cos().into()],
]
}
Self::RotationZ(angle) => {
let angle = angle.value();
[
[(-Cf64::i() * (angle / 2.0)).exp(), 0.0.into()],
[0.0.into(), (Cf64::i() * (angle / 2.0)).exp()],
]
}
Self::Phase(angle) => [
[1.0.into(), 0.0.into()],
[0.0.into(), (Cf64::i() * angle.value()).exp()],
],
Self::Hadamard => [
[(1.0 / 2.0f64.sqrt()).into(), (1.0 / 2.0f64.sqrt()).into()],
[(1.0 / 2.0f64.sqrt()).into(), (-1.0 / 2.0f64.sqrt()).into()],
],
}
}
pub(crate) fn su2_matrix(&self) -> Matrix {
match self {
Self::PauliX => Self::RotationX(Param::Value(PI)).matrix(),
Self::PauliY => Self::RotationY(Param::Value(PI)).matrix(),
Self::PauliZ => Self::RotationZ(Param::Value(PI)).matrix(),
Self::Phase(angle) => Self::RotationZ(*angle).matrix(),
Self::Hadamard => [
[-Cf64::i() * FRAC_1_SQRT_2, -Cf64::i() * FRAC_1_SQRT_2],
[-Cf64::i() * FRAC_1_SQRT_2, Cf64::i() * FRAC_1_SQRT_2],
],
_ => self.matrix(),
}
}
pub fn merge(&self, other: &Self) -> Option<Self> {
match (self, other) {
(Self::RotationX(a), Self::RotationX(b)) => a.add(b).map(QuantumGate::RotationX),
(Self::RotationY(a), Self::RotationY(b)) => a.add(b).map(QuantumGate::RotationY),
(Self::RotationZ(a), Self::RotationZ(b)) => a.add(b).map(QuantumGate::RotationZ),
(Self::Phase(a), Self::Phase(b)) => a.add(b).map(QuantumGate::Phase),
_ => None,
}
}
#[must_use]
pub fn is_near_zero(&self, epsilon: f64) -> bool {
match self {
Self::RotationX(p) | Self::RotationY(p) | Self::RotationZ(p) | Self::Phase(p) => {
p.is_near_zero(epsilon)
}
_ => false,
}
}
#[must_use]
pub fn set_parameter(&self, parameters: &[f64]) -> Self {
match self {
Self::PauliX | Self::PauliY | Self::PauliZ | Self::Hadamard => *self,
Self::RotationX(param) => Self::RotationX(param.set_parameter(parameters)),
Self::RotationY(param) => Self::RotationY(param.set_parameter(parameters)),
Self::RotationZ(param) => Self::RotationZ(param.set_parameter(parameters)),
Self::Phase(param) => Self::Phase(param.set_parameter(parameters)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
pub enum DecomposedGate {
U(QuantumGate, usize),
CNOT(usize, usize),
}
impl DecomposedGate {
#[must_use]
pub fn inverse(&self) -> Self {
match self {
DecomposedGate::U(gate, t) => DecomposedGate::U(gate.inverse(), *t),
DecomposedGate::CNOT(c, t) => DecomposedGate::CNOT(*c, *t),
}
}
}
impl From<(QuantumGate, usize)> for DecomposedGate {
fn from(value: (QuantumGate, usize)) -> Self {
let (gate, target) = value;
Self::U(gate, target)
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct GateInstruction {
pub gate: QuantumGate,
pub target: usize,
pub control: BTreeSet<usize>,
pub control_locked: bool,
pub is_approximated: bool,
pub decomposed: Option<Vec<DecomposedGate>>,
}
impl From<&DecomposedGate> for GateInstruction {
fn from(value: &DecomposedGate) -> Self {
match value {
DecomposedGate::U(gate, target) => Self {
gate: *gate,
target: *target,
control: BTreeSet::new(),
control_locked: false,
is_approximated: false,
decomposed: None,
},
DecomposedGate::CNOT(control, target) => Self {
gate: PauliX,
target: *target,
control: BTreeSet::from([*control]),
control_locked: false,
is_approximated: false,
decomposed: None,
},
}
}
}
impl GateInstruction {
#[must_use]
pub const fn new(gate: QuantumGate, target: usize) -> Self {
Self {
gate,
target,
control: BTreeSet::new(),
control_locked: false,
is_approximated: false,
decomposed: None,
}
}
#[must_use]
pub fn inverse(&self) -> Self {
Self {
gate: self.gate.inverse(),
target: self.target,
control: self.control.clone(),
control_locked: self.control_locked,
is_approximated: self.is_approximated,
decomposed: self
.decomposed
.as_ref()
.map(|gates| gates.iter().rev().map(DecomposedGate::inverse).collect()),
}
}
pub fn control(&self, control_qubits: &[usize]) -> Result<Self, KetError> {
if self.control_locked {
return Ok(self.clone());
}
let mut control = self.control.clone();
control.extend(control_qubits.iter());
if self.gate.param_index().is_some() {
Err(KetError::ControlParameterizedGate)
} else if control.len() != (control_qubits.len() + self.control.len()) {
Err(KetError::DuplicateControlQubit)
} else if control.contains(&self.target) {
Err(KetError::ControlTargetConflict)
} else {
Ok(Self {
gate: self.gate,
target: self.target,
control,
control_locked: false,
is_approximated: false,
decomposed: None,
})
}
}
pub fn enable_approximated_decomposition(&mut self) {
self.is_approximated = true;
}
pub fn lock_control(&mut self) {
self.control_locked = true;
}
#[must_use]
pub fn commutes_with(&self, other: &Self) -> bool {
self.gate.is_diagonal() && other.gate.is_diagonal()
|| (self.gate == other.gate && self.target == other.target)
}
pub fn decompose(&mut self, clean_auxiliary_range: (usize, usize)) {
let control = self.control.iter().copied().collect_vec();
if !self.control.is_empty() && self.decomposed.is_none() {
self.decomposed = Some(match self.gate {
QuantumGate::PauliX => Self::decompose_x(
self.target,
&control,
self.is_approximated,
clean_auxiliary_range,
),
QuantumGate::PauliY => Self::decompose_y(
self.target,
&control,
self.is_approximated,
clean_auxiliary_range,
),
QuantumGate::PauliZ => Self::decompose_z(
self.target,
&control,
self.is_approximated,
clean_auxiliary_range,
),
QuantumGate::RotationX(_)
| QuantumGate::RotationY(_)
| QuantumGate::RotationZ(_) => Self::decompose_r(
self.gate,
self.target,
&control,
self.is_approximated,
clean_auxiliary_range,
),
QuantumGate::Phase(angle) => Self::decompose_phase(
angle.value(),
self.target,
&control,
self.is_approximated,
clean_auxiliary_range,
),
QuantumGate::Hadamard => Self::decompose_hadamard(
self.target,
&control,
self.is_approximated,
clean_auxiliary_range,
),
});
}
}
fn decompose_r(
gate: QuantumGate,
target: usize,
control: &[usize],
approximated: bool,
clean_axillary_range: (usize, usize),
) -> Vec<DecomposedGate> {
if control.len() == 1 {
return decompose::u2::cu2(gate.matrix(), control[0], target);
}
let num_clean_aux = clean_axillary_range.1 - clean_axillary_range.0;
let network_num_aux = Algorithm::NetworkU2(CXMode::C3X).aux_needed(control.len());
if network_num_aux <= num_clean_aux {
let aux_qubits: Vec<_> =
(clean_axillary_range.0..clean_axillary_range.0 + network_num_aux).collect();
decompose::network::network(
gate,
control,
&aux_qubits,
target,
CXMode::C3X,
approximated,
)
} else {
decompose::su2::decompose(gate, control, target, DepthMode::Linear, approximated)
}
}
fn decompose_x(
target: usize,
control: &[usize],
approximated: bool,
clean_axillary_range: (usize, usize),
) -> Vec<DecomposedGate> {
if control.len() <= 4 {
return decompose::x::c2to4x::c1to4x(control, target, approximated);
}
let num_clean_aux = clean_axillary_range.1 - clean_axillary_range.0;
let network_c2x_num_aux = Algorithm::NetworkPauli(CXMode::C2X).aux_needed(control.len());
if num_clean_aux >= network_c2x_num_aux {
let aux_qubits: Vec<_> =
(clean_axillary_range.0..clean_axillary_range.0 + network_c2x_num_aux).collect();
return decompose::network::network(
QuantumGate::PauliX,
control,
&aux_qubits,
target,
CXMode::C2X,
approximated,
);
}
let network_c3x_num_aux = Algorithm::NetworkPauli(CXMode::C3X).aux_needed(control.len());
if num_clean_aux >= network_c3x_num_aux {
let aux_qubits: Vec<_> =
(clean_axillary_range.0..clean_axillary_range.0 + network_c3x_num_aux).collect();
return decompose::network::network(
QuantumGate::PauliX,
control,
&aux_qubits,
target,
CXMode::C3X,
approximated,
);
}
let v_chain_c3x_clean_num_aux =
Algorithm::VChain(CXMode::C3X, AuxMode::Clean).aux_needed(control.len());
if num_clean_aux >= v_chain_c3x_clean_num_aux {
let aux_qubits: Vec<_> = (clean_axillary_range.0
..clean_axillary_range.0 + v_chain_c3x_clean_num_aux)
.collect();
return decompose::x::v_chain::v_chain(
control,
&aux_qubits,
target,
AuxMode::Clean,
CXMode::C3X,
approximated,
);
}
let num_dirty_aux = clean_axillary_range.1 - 1 - control.len();
let v_chain_c2x_dirty_num_aux =
Algorithm::VChain(CXMode::C2X, AuxMode::Dirty).aux_needed(control.len());
if num_dirty_aux >= v_chain_c2x_dirty_num_aux {
let all_qubits = control.iter().copied().chain([target]).collect_vec();
let aux_qubits: Vec<_> = (0..v_chain_c3x_clean_num_aux + 1 + control.len())
.filter(|qubit| !all_qubits.contains(qubit))
.collect();
decompose::x::v_chain::v_chain(
control,
&aux_qubits,
target,
AuxMode::Dirty,
CXMode::C2X,
approximated,
)
} else if num_clean_aux >= 1 {
decompose::x::single_aux::decompose(
control,
clean_axillary_range.0,
target,
AuxMode::Clean,
DepthMode::Linear,
approximated,
)
} else {
decompose::u2::linear_depth(QuantumGate::PauliX, control, target)
}
}
fn decompose_y(
target: usize,
control: &[usize],
approximated: bool,
clean_axillary_range: (usize, usize),
) -> Vec<DecomposedGate> {
chain![
[(QuantumGate::sd(), target).into()],
Self::decompose_x(target, control, approximated, clean_axillary_range),
[(QuantumGate::s(), target).into()]
]
.collect()
}
fn decompose_z(
target: usize,
control: &[usize],
approximated: bool,
clean_axillary_range: (usize, usize),
) -> Vec<DecomposedGate> {
chain![
[(QuantumGate::Hadamard, target).into()],
Self::decompose_x(target, control, approximated, clean_axillary_range),
[(QuantumGate::Hadamard, target).into()]
]
.collect()
}
fn decompose_phase(
angle: f64,
target: usize,
control: &[usize],
approximated: bool,
clean_axillary_range: (usize, usize),
) -> Vec<DecomposedGate> {
let num_clean_aux = clean_axillary_range.1 - clean_axillary_range.0;
let network_c3x_num_aux = Algorithm::NetworkU2(CXMode::C3X).aux_needed(control.len());
if control.len() == 1 {
decompose::u2::cu2(
QuantumGate::Phase(Param::Value(angle)).matrix(),
control[0],
target,
)
} else if num_clean_aux >= network_c3x_num_aux {
let aux_qubits: Vec<_> =
(clean_axillary_range.0..clean_axillary_range.0 + network_c3x_num_aux).collect();
decompose::network::network(
QuantumGate::Phase(Param::Value(angle)),
control,
&aux_qubits,
target,
CXMode::C3X,
approximated,
)
} else if num_clean_aux >= 1 {
let control = control.iter().copied().chain([target]).collect_vec();
decompose::su2::decompose(
RotationZ(Param::Value(-2.0 * angle)),
&control,
clean_axillary_range.0,
DepthMode::Linear,
approximated,
)
} else {
decompose::u2::linear_depth(QuantumGate::Phase(Param::Value(angle)), control, target)
}
}
fn decompose_hadamard(
target: usize,
control: &[usize],
approximated: bool,
clean_axillary_range: (usize, usize),
) -> Vec<DecomposedGate> {
let num_clean_aux = clean_axillary_range.1 - clean_axillary_range.0;
let network_c3x_num_aux = Algorithm::NetworkU2(CXMode::C3X).aux_needed(control.len());
if control.len() == 1 {
decompose::u2::cu2(QuantumGate::Hadamard.matrix(), control[0], target)
} else if num_clean_aux >= network_c3x_num_aux {
let aux_qubits: Vec<_> =
(clean_axillary_range.0..clean_axillary_range.0 + network_c3x_num_aux).collect();
decompose::network::network(
QuantumGate::Hadamard,
control,
&aux_qubits,
target,
CXMode::C3X,
approximated,
)
} else if num_clean_aux >= 1 {
decompose::u2::su2_rewrite_hadamard(control, clean_axillary_range.0, target)
} else {
decompose::u2::linear_depth(QuantumGate::Hadamard, control, target)
}
}
}