use crate::Pauli;
use sprs::vec::{NnzEither, SparseIterTools, VectorIterator};
use sprs::CsVec;
use std::error::Error;
use std::fmt;
use std::ops::Mul;
use Pauli::{X, Z};
use serde::{Serialize, Deserialize};
#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
pub struct PauliOperator {
paulis: CsVec<Pauli>,
}
impl PauliOperator {
pub fn new(length: usize, positions: Vec<usize>, paulis: Vec<Pauli>) -> Self {
Self::try_new(length, positions, paulis).expect("invalid operator")
}
pub fn try_new(
length: usize,
positions: Vec<usize>,
paulis: Vec<Pauli>,
) -> Result<Self, PauliError> {
if positions.len() != paulis.len() {
Err(PauliError::IncompatibleLength(
positions.len(),
paulis.len(),
))
} else if let Some(pos) = positions.iter().find(|pos| **pos >= length) {
Err(PauliError::OutOfBound(*pos, length))
} else {
Ok(Self {
paulis: CsVec::new(length, positions, paulis),
})
}
}
pub fn empty() -> Self {
Self {
paulis: CsVec::empty(0),
}
}
pub fn commutes_with(&self, other: &Self) -> bool {
self.iter()
.nnz_zip(other.iter())
.filter(|(_, pauli, other_pauli)| pauli.anticommutes_with(**other_pauli))
.count()
% 2
== 0
}
pub fn anticommutes_with(&self, other: &Self) -> bool {
!self.commutes_with(other)
}
pub fn iter(&self) -> VectorIterator<Pauli, usize> {
self.paulis.iter()
}
pub fn get(&self, position: usize) -> Option<Pauli> {
self.paulis.get(position).cloned().or_else(|| {
if position < self.len() {
Some(Pauli::I)
} else {
None
}
})
}
pub fn len(&self) -> usize {
self.paulis.dim()
}
pub fn weight(&self) -> usize {
self.paulis.nnz()
}
pub fn non_trivial_positions(&self) -> &[usize] {
self.paulis.indices()
}
pub fn partition_x_and_z(&self) -> (Self, Self) {
(self.x_part(), self.z_part())
}
pub fn x_part(&self) -> Self {
let (positions, paulis) = self
.iter()
.filter_map(|(position, pauli)| {
if *pauli != Z {
Some((position, X))
} else {
None
}
})
.unzip();
Self::new(self.len(), positions, paulis)
}
pub fn z_part(&self) -> Self {
let (positions, paulis) = self
.iter()
.filter_map(|(position, pauli)| {
if *pauli != X {
Some((position, Z))
} else {
None
}
})
.unzip();
Self::new(self.len(), positions, paulis)
}
pub fn multiply_with(&self, other: &Self) -> Result<Self, PauliError> {
if self.len() != other.len() {
Err(PauliError::IncompatibleLength(self.len(), other.len()))
} else {
let (positions, paulis) = self
.iter()
.nnz_or_zip(other.iter())
.map(|values| match values {
NnzEither::Left((position, &pauli)) => (position, pauli),
NnzEither::Right((position, &pauli)) => (position, pauli),
NnzEither::Both((position, &p0, &p1)) => (position, p0 * p1),
})
.filter(|(_, pauli)| pauli.is_non_trivial())
.unzip();
Ok(PauliOperator::new(self.len(), positions, paulis))
}
}
pub fn into_raw_positions(self) -> Vec<usize> {
self.paulis.into_raw_storage().0
}
pub fn into_raw_paulis(self) -> Vec<Pauli> {
self.paulis.into_raw_storage().1
}
pub fn into_raw(self) -> (Vec<usize>, Vec<Pauli>) {
self.paulis.into_raw_storage()
}
}
impl<'a> Mul<&'a PauliOperator> for &'a PauliOperator {
type Output = PauliOperator;
fn mul(self, other: Self) -> Self::Output {
self.multiply_with(other).unwrap()
}
}
impl fmt::Display for PauliOperator {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[")?;
for (idx, (position, pauli)) in self.iter().enumerate() {
write!(f, "({}, {})", position, pauli)?;
if idx + 1 < self.weight() {
write!(f, ", ")?;
}
}
write!(f, "]")
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub enum PauliError {
IncompatibleLength(usize, usize),
OutOfBound(usize, usize),
}
impl fmt::Display for PauliError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::IncompatibleLength(l0, l1) => {
write!(f, "incompatible length {} and {}", l0, l1)
}
Self::OutOfBound(pos, len) => {
write!(f, "position {} is out of bound for length {}", pos, len)
}
}
}
}
impl Error for PauliError {}
#[cfg(test)]
mod test {
use super::*;
use Pauli::{X, Z};
#[test]
fn commutes_with_different_lengths() {
let long_operator = PauliOperator::new(10, vec![0, 2, 7, 9], vec![X, X, X, X]);
let short_operator = PauliOperator::new(4, vec![0, 1, 2], vec![Z, Z, Z]);
assert!(long_operator.commutes_with(&short_operator));
}
#[test]
fn anticommutes_with_different_lengths() {
let long_operator = PauliOperator::new(10, vec![0, 2, 7, 9], vec![X, Z, X, Z]);
let short_operator = PauliOperator::new(4, vec![0, 1, 2], vec![Z, Z, Z]);
assert!(long_operator.anticommutes_with(&short_operator));
}
}