mod apply;
use crate::chp_decompositions::ChpOperation;
use crate::linalg::{extend_one_to_n, extend_two_to_n, zeros_like};
use crate::processes::ProcessData::{KrausDecomposition, MixedPauli, Unitary};
use crate::NoiseModel;
use crate::QubitSized;
use crate::C64;
use crate::{AsUnitary, Pauli};
use itertools::Itertools;
use ndarray::{Array, Array2, Array3, Axis, NewAxis};
use num_complex::Complex;
use num_traits::{One, Zero};
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
use std::ops::Add;
use std::ops::Mul;
pub type Process = QubitSized<ProcessData>;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ProcessData {
MixedPauli(Vec<(f64, Vec<Pauli>)>),
Unitary(Array2<C64>),
KrausDecomposition(Array3<C64>),
Sequence(Vec<Process>),
ChpDecomposition(Vec<ChpOperation>),
Unsupported,
}
impl Process {
pub fn new_pauli_channel<T: IntoPauliMixture>(data: T) -> Self {
let data = data.into_pauli_mixture();
let n_qubits = data[0].1.len();
Process {
n_qubits,
data: MixedPauli(data),
}
}
pub fn as_json(&self) -> String {
serde_json::to_string(&self).unwrap()
}
pub fn extend_one_to_n(&self, idx_qubit: usize, n_qubits: usize) -> Process {
assert_eq!(self.n_qubits, 1);
Process {
n_qubits,
data: match &self.data {
Unitary(u) => Unitary(extend_one_to_n(u.view(), idx_qubit, n_qubits)),
KrausDecomposition(ks) => {
let new_dim = 2usize.pow(n_qubits.try_into().unwrap());
let n_kraus = ks.shape()[0];
let mut extended: Array3<C64> = Array::zeros((n_kraus, new_dim, new_dim));
for (idx_kraus, kraus) in ks.axis_iter(Axis(0)).enumerate() {
let mut target = extended.index_axis_mut(Axis(0), idx_kraus);
let big_kraus = extend_one_to_n(kraus.view(), idx_qubit, n_qubits);
target.assign(&big_kraus);
}
KrausDecomposition(extended)
},
MixedPauli(paulis) => MixedPauli(
paulis.iter()
.map(|(pr, pauli)| {
if pauli.len() != 1 {
panic!("Pauli channel acts on more than one qubit, cannot extend to 𝑛 qubits.");
}
let p = pauli[0];
let mut extended = std::iter::repeat(Pauli::I).take(n_qubits).collect_vec();
extended[idx_qubit] = p;
(*pr, extended)
})
.collect_vec()
),
ProcessData::Unsupported => ProcessData::Unsupported,
ProcessData::Sequence(processes) => ProcessData::Sequence(
processes.iter().map(|p| p.extend_one_to_n(idx_qubit, n_qubits)).collect()
),
ProcessData::ChpDecomposition(_) => todo!(),
},
}
}
pub fn extend_two_to_n(
&self,
idx_qubit1: usize,
idx_qubit2: usize,
n_qubits: usize,
) -> Process {
assert_eq!(self.n_qubits, 2);
Process {
n_qubits,
data: match &self.data {
Unitary(u) => Unitary(extend_two_to_n(u.view(), idx_qubit1, idx_qubit2, n_qubits)),
KrausDecomposition(ks) => {
let new_dim = 2usize.pow(n_qubits.try_into().unwrap());
let n_kraus = ks.shape()[0];
let mut extended: Array3<C64> = Array::zeros((n_kraus, new_dim, new_dim));
for (idx_kraus, kraus) in ks.axis_iter(Axis(0)).enumerate() {
let mut target = extended.index_axis_mut(Axis(0), idx_kraus);
let big_kraus = extend_two_to_n(kraus, idx_qubit1, idx_qubit2, n_qubits);
target.assign(&big_kraus);
}
KrausDecomposition(extended)
},
MixedPauli(paulis) => MixedPauli(
paulis.iter()
.map(|(pr, pauli)| {
if pauli.len() != 2 {
panic!("Pauli channel acts on more than one qubit, cannot extend to 𝑛 qubits.");
}
let p = (pauli[0], pauli[1]);
let mut extended = std::iter::repeat(Pauli::I).take(n_qubits).collect_vec();
extended[idx_qubit1] = p.0;
extended[idx_qubit2] = p.1;
(*pr, extended)
})
.collect_vec()
),
ProcessData::Unsupported => ProcessData::Unsupported,
ProcessData::Sequence(processes) => ProcessData::Sequence(
processes.iter().map(|p| p.extend_two_to_n(idx_qubit1, idx_qubit2, n_qubits)).collect()
),
ProcessData::ChpDecomposition(_) => todo!(),
},
}
}
}
impl Mul<&Process> for C64 {
type Output = Process;
fn mul(self, channel: &Process) -> Self::Output {
Process {
n_qubits: channel.n_qubits,
data: match &channel.data {
Unitary(u) => KrausDecomposition({
let mut ks = Array3::<C64>::zeros((1, u.shape()[0], u.shape()[1]));
ks.index_axis_mut(Axis(0), 0).assign(&(self.sqrt() * u));
ks
}),
KrausDecomposition(ks) => KrausDecomposition(self.sqrt() * ks),
MixedPauli(paulis) => (self * promote_pauli_channel(paulis)).data,
ProcessData::Unsupported => ProcessData::Unsupported,
ProcessData::Sequence(processes) => {
ProcessData::Sequence(processes.iter().map(|p| self * p).collect())
}
ProcessData::ChpDecomposition(_) => todo!(),
},
}
}
}
impl Mul<Process> for C64 {
type Output = Process;
fn mul(self, channel: Process) -> Self::Output {
self * (&channel)
}
}
impl Mul<&Process> for f64 {
type Output = Process;
fn mul(self, chanel: &Process) -> Self::Output {
C64::new(self, 0f64) * chanel
}
}
impl Mul<Process> for f64 {
type Output = Process;
fn mul(self, channel: Process) -> Self::Output {
self * (&channel)
}
}
impl Mul<&Process> for &Process {
type Output = Process;
fn mul(self, rhs: &Process) -> Self::Output {
assert_eq!(self.n_qubits, rhs.n_qubits);
Process {
n_qubits: self.n_qubits,
data: match (&self.data, &rhs.data) {
(Unitary(u), Unitary(v)) => Unitary(u.dot(v)),
(Unitary(u), KrausDecomposition(ks)) => {
let mut post = zeros_like(ks);
for (idx_kraus, kraus) in ks.axis_iter(Axis(0)).enumerate() {
post.index_axis_mut(Axis(0), idx_kraus)
.assign(&u.dot(&kraus));
}
KrausDecomposition(post)
}
_ => todo!(),
},
}
}
}
impl Add<&Process> for &Process {
type Output = Process;
fn add(self, rhs: &Process) -> Self::Output {
assert_eq!(self.n_qubits, rhs.n_qubits);
Process {
n_qubits: self.n_qubits,
data: match (&self.data, &rhs.data) {
(KrausDecomposition(ks1), KrausDecomposition(ks2)) => {
let mut sum = Array::zeros([
ks1.shape()[0] + ks2.shape()[0],
ks1.shape()[1],
ks1.shape()[2],
]);
for (idx_kraus, kraus) in ks1.axis_iter(Axis(0)).enumerate() {
sum.index_axis_mut(Axis(0), idx_kraus).assign(&kraus);
}
for (idx_kraus, kraus) in ks2.axis_iter(Axis(0)).enumerate() {
sum.index_axis_mut(Axis(0), ks1.shape()[0] + idx_kraus)
.assign(&kraus);
}
KrausDecomposition(sum)
}
_ => todo!(),
},
}
}
}
impl Mul<Process> for &Process {
type Output = Process;
fn mul(self, rhs: Process) -> Self::Output {
self * &rhs
}
}
impl Mul<&Process> for Process {
type Output = Process;
fn mul(self, rhs: &Process) -> Self::Output {
&self * rhs
}
}
impl Mul<Process> for Process {
type Output = Process;
fn mul(self, rhs: Process) -> Self::Output {
&self * &rhs
}
}
impl Add<Process> for &Process {
type Output = Process;
fn add(self, rhs: Process) -> Self::Output {
self + &rhs
}
}
impl Add<&Process> for Process {
type Output = Process;
fn add(self, rhs: &Process) -> Self::Output {
&self + rhs
}
}
impl Add<Process> for Process {
type Output = Process;
fn add(self, rhs: Process) -> Self::Output {
&self + &rhs
}
}
pub fn depolarizing_channel(p: f64) -> Process {
let ideal = NoiseModel::ideal();
(1.0 - p) * ideal.i + p / 3.0 * ideal.x + p / 3.0 * ideal.y + p / 3.0 * ideal.z
}
pub fn amplitude_damping_channel(gamma: f64) -> Process {
Process {
n_qubits: 1,
data: KrausDecomposition(array![
[
[C64::one(), C64::zero()],
[C64::zero(), C64::one() * (1.0 - gamma).sqrt()]
],
[
[C64::zero(), C64::one() * gamma.sqrt()],
[C64::zero(), C64::zero()]
]
]),
}
}
pub trait IntoPauliMixture {
fn into_pauli_mixture(self) -> Vec<(f64, Vec<Pauli>)>;
}
impl IntoPauliMixture for Vec<(f64, Vec<Pauli>)> {
fn into_pauli_mixture(self) -> Vec<(f64, Vec<Pauli>)> {
self
}
}
impl IntoPauliMixture for Vec<Pauli> {
fn into_pauli_mixture(self) -> Vec<(f64, Vec<Pauli>)> {
vec![(1.0, self)]
}
}
impl IntoPauliMixture for Vec<(f64, Pauli)> {
fn into_pauli_mixture(self) -> Vec<(f64, Vec<Pauli>)> {
self.iter().map(|(pr, p)| (*pr, vec![*p])).collect_vec()
}
}
impl IntoPauliMixture for Pauli {
fn into_pauli_mixture(self) -> Vec<(f64, Vec<Pauli>)> {
vec![(1.0, vec![self])]
}
}
fn promote_pauli_channel(paulis: &[(f64, Vec<Pauli>)]) -> Process {
if paulis.len() == 1 {
let (_, pauli) = &paulis[0];
Process {
n_qubits: pauli.len(),
data: Unitary(pauli.as_unitary()),
}
} else {
let matrices = paulis
.iter()
.map(|p| {
p.1.as_unitary()
* Complex {
re: p.0.sqrt(),
im: 0.0f64,
}
})
.map(|u| {
let x = u.slice(s![NewAxis, .., ..]);
x.to_owned()
})
.collect_vec();
Process {
n_qubits: paulis[0].1.len(),
data: KrausDecomposition(
ndarray::concatenate(
Axis(0),
matrices.iter().map(|u| u.view()).collect_vec().as_slice(),
)
.unwrap(),
),
}
}
}