Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions paulimer/bindings/python/paulimer.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,30 @@ class DensePauli:
"""
...

def indexed_anti_commutators_of(self, others: Iterable["DensePauli" | "SparsePauli"]) -> list[int]:
"""Return the indices of operators in ``others`` that anticommute with this operator.

Args:
others: An iterable of Pauli operators to test against.

Returns:
A list of integer indices into ``others`` for operators that
anticommute with this operator.
"""
...

def indexed_commutators_of(self, others: Iterable["DensePauli" | "SparsePauli"]) -> list[int]:
"""Return the indices of operators in ``others`` that commute with this operator.

Args:
others: An iterable of Pauli operators to test against.

Returns:
A list of integer indices into ``others`` for operators that
commute with this operator.
"""
...

def copy(self) -> "DensePauli": ...
def __eq__(self, other: object) -> bool: ...
def __ne__(self, other: object) -> bool: ...
Expand Down Expand Up @@ -277,6 +301,30 @@ class SparsePauli:
"""Check if this operator commutes with another or collection of operators."""
...

def indexed_anti_commutators_of(self, others: Iterable["DensePauli" | "SparsePauli"]) -> list[int]:
"""Return the indices of operators in ``others`` that anticommute with this operator.

Args:
others: An iterable of Pauli operators to test against.

Returns:
A list of integer indices into ``others`` for operators that
anticommute with this operator.
"""
...

def indexed_commutators_of(self, others: Iterable["DensePauli" | "SparsePauli"]) -> list[int]:
"""Return the indices of operators in ``others`` that commute with this operator.

Args:
others: An iterable of Pauli operators to test against.

Returns:
A list of integer indices into ``others`` for operators that
commute with this operator.
"""
...

def copy(self) -> "SparsePauli": ...
def __eq__(self, other: object) -> bool: ...
def __ne__(self, other: object) -> bool: ...
Expand Down
48 changes: 46 additions & 2 deletions paulimer/bindings/python/src/py_clifford.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ use paulimer::clifford::{
group_encoding_clifford_of, split_phased_css, split_qubit_cliffords_and_css, Clifford, CliffordMutable,
CliffordUnitary, XOrZ,
};
use paulimer::pauli::{as_sparse, DensePauli, SparsePauli};
use paulimer::pauli::{anti_commutes_with, as_sparse, dense_from, DensePauli, Pauli, SparsePauli};
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use pyo3::types::PySliceIndices;
use pyo3::types::{PyIterator, PySliceIndices};

use binar::{vec::AlignedBitVec, BitwisePair, IndexSet};

use crate::enums::PyUnitaryOp;
use crate::format_spec::parse_format_spec;
Expand All @@ -25,6 +27,48 @@ impl PyPauliInput {
PyPauliInput::Sparse(sparse) => sparse.clone(),
}
}

pub fn to_dense(&self, qubit_count: usize) -> DensePauli {
match self {
PyPauliInput::Dense(dense) => dense_from(dense, qubit_count),
PyPauliInput::Sparse(sparse) => dense_from(sparse, qubit_count),
}
}

pub fn anti_commutes_with<P: Pauli>(&self, other: &P) -> bool
where
P::Bits: BitwisePair<AlignedBitVec> + BitwisePair<IndexSet>,
{
match self {
PyPauliInput::Dense(ref d) => anti_commutes_with(other, d),
PyPauliInput::Sparse(ref s) => anti_commutes_with(other, s),
}
}

pub fn commutes_with<P: Pauli>(&self, other: &P) -> bool
where
P::Bits: BitwisePair<AlignedBitVec> + BitwisePair<IndexSet>,
{
!self.anti_commutes_with(other)
}
}

pub(crate) fn indexes_of_paulis_where<P: Pauli>(
observable: &P,
paulis: &Bound<'_, PyAny>,
predicate: fn(&PyPauliInput, &P) -> bool,
) -> PyResult<Vec<usize>>
where
P::Bits: BitwisePair<AlignedBitVec> + BitwisePair<IndexSet>,
{
let iter = PyIterator::from_object(paulis)?;
let mut result = Vec::new();
for (i, item) in iter.enumerate() {
if predicate(&item?.extract::<PyPauliInput>()?, observable) {
result.push(i);
}
}
Ok(result)
}

impl<'a, 'py> FromPyObject<'a, 'py> for PyPauliInput {
Expand Down
13 changes: 13 additions & 0 deletions paulimer/bindings/python/src/py_dense_pauli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use pyo3::{
};

use crate::format_spec::parse_format_spec;
use crate::py_clifford::{indexes_of_paulis_where, PyPauliInput};
use crate::py_sparse_pauli::PySparsePauli;

#[derive(Clone, Deref, DerefMut, From, Into)]
Expand Down Expand Up @@ -162,6 +163,18 @@ impl PyDensePauli {
Ok(true)
}

/// # Errors
/// Will return an error if the extraction of Pauli(s) fails.
pub fn indexed_anti_commutators_of(&self, others: &Bound<'_, PyAny>) -> PyResult<Vec<usize>> {
indexes_of_paulis_where(&self.inner, others, PyPauliInput::anti_commutes_with)
}

/// # Errors
/// Will return an error if the extraction of Pauli(s) fails.
pub fn indexed_commutators_of(&self, others: &Bound<'_, PyAny>) -> PyResult<Vec<usize>> {
indexes_of_paulis_where(&self.inner, others, PyPauliInput::commutes_with)
}

/// # Errors
///
/// Will return error for >,>=,<,<=
Expand Down
13 changes: 13 additions & 0 deletions paulimer/bindings/python/src/py_sparse_pauli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use std::collections::HashMap;
use std::hash::{DefaultHasher, Hash, Hasher};

use crate::format_spec::parse_format_spec;
use crate::py_clifford::{indexes_of_paulis_where, PyPauliInput};
use crate::py_dense_pauli::PyDensePauli;

#[must_use]
Expand Down Expand Up @@ -184,6 +185,18 @@ impl PySparsePauli {
Ok(true)
}

/// # Errors
/// Will return an error if the extraction of Pauli(s) fails.
pub fn indexed_anti_commutators_of(&self, others: &Bound<'_, PyAny>) -> PyResult<Vec<usize>> {
indexes_of_paulis_where(&self.inner, others, PyPauliInput::anti_commutes_with)
}

/// # Errors
/// Will return an error if the extraction of Pauli(s) fails.
pub fn indexed_commutators_of(&self, others: &Bound<'_, PyAny>) -> PyResult<Vec<usize>> {
indexes_of_paulis_where(&self.inner, others, PyPauliInput::commutes_with)
}

/// # Errors
///
/// Will return for >, <, >=, <= comparisons
Expand Down
93 changes: 93 additions & 0 deletions paulimer/bindings/python/tests/commutation_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
from paulimer import DensePauli, SparsePauli


def test_dense_indexed_anti_commutators_of():
x = DensePauli("X")
z = DensePauli("Z")
y = DensePauli("Y")
i = DensePauli("I")
result = x.indexed_anti_commutators_of([x, z, y, i])
assert sorted(result) == [1, 2]


def test_dense_indexed_anti_commutators_of_all_commute():
x = DensePauli("X")
result = x.indexed_anti_commutators_of([x, DensePauli("I")])
assert result == []


def test_dense_indexed_anti_commutators_of_empty():
x = DensePauli("X")
result = x.indexed_anti_commutators_of([])
assert result == []


def test_dense_indexed_commutators_of():
x = DensePauli("X")
z = DensePauli("Z")
y = DensePauli("Y")
i = DensePauli("I")
result = x.indexed_commutators_of([x, z, y, i])
assert sorted(result) == [0, 3]


def test_dense_indexed_commutators_of_all_anticommute():
x = DensePauli("X")
z = DensePauli("Z")
y = DensePauli("Y")
result = x.indexed_commutators_of([z, y])
assert result == []


def test_dense_indexed_commutators_of_empty():
x = DensePauli("X")
result = x.indexed_commutators_of([])
assert result == []


def test_sparse_indexed_anti_commutators_of():
x = SparsePauli("X_0")
z = SparsePauli("Z_0")
i = SparsePauli("I")
result = x.indexed_anti_commutators_of([z, i, x])
assert result == [0]


def test_sparse_indexed_commutators_of():
x = SparsePauli("X_0")
z = SparsePauli("Z_0")
i = SparsePauli("I")
result = x.indexed_commutators_of([z, i, x])
assert sorted(result) == [1, 2]


def test_dense_with_sparse_args_indexed_commutators_of():
x = DensePauli("X")
z = SparsePauli("Z_0")
i = SparsePauli("I")
result = x.indexed_commutators_of([z, i])
assert result == [1]


def test_dense_with_sparse_args_indexed_anti_commutators_of():
x = DensePauli("X")
z = SparsePauli("Z_0")
i = SparsePauli("I")
result = x.indexed_anti_commutators_of([z, i])
assert result == [0]


def test_sparse_with_dense_args_indexed_commutators_of():
x = SparsePauli("X_0")
z = DensePauli("Z")
i = DensePauli("I")
result = x.indexed_commutators_of([z, i])
assert result == [1]


def test_sparse_with_dense_args_indexed_anti_commutators_of():
x = SparsePauli("X_0")
z = DensePauli("Z")
i = DensePauli("I")
result = x.indexed_anti_commutators_of([z, i])
assert result == [0]
3 changes: 2 additions & 1 deletion paulimer/src/pauli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ pub use sparse::{SparsePauli, SparsePauliProjective, as_sparse, as_sparse_projec
mod algorithms;
pub use algorithms::{
apply_pauli_exponent, apply_root_x, apply_root_y, apply_root_z, are_mutually_commuting,
are_the_same_group_up_to_phases, complete_to_full_pauli_basis, paulis_qubit_count,
are_the_same_group_up_to_phases, complete_to_full_pauli_basis, indexed_anti_commutators_of, indexed_commutators_of,
paulis_qubit_count,
};

use binar::{Bitwise, BitwiseMut, BitwisePair, BitwisePairMut};
Expand Down
42 changes: 40 additions & 2 deletions paulimer/src/pauli/algorithms.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::{DensePauli, Pauli, PauliBinaryOps, anti_commutes_with};
use super::{DensePauli, Pauli, PauliBinaryOps, anti_commutes_with, commutes_with};
use crate::setwise::complement;
use crate::traits::NeutralElement;
use binar::{BitMatrix, Bitwise};
use binar::{BitMatrix, Bitwise, BitwisePair, IndexSet};

/// # Panics
/// Will panic if the input `paulis` are not independent
Expand Down Expand Up @@ -70,6 +70,44 @@ pub fn are_the_same_group_up_to_phases<PauliLike1: Pauli, PauliLike2: Pauli>(
}
}

fn indexes_of_paulis_where<Observable: Pauli, PauliLike: Pauli>(
observable: &Observable,
paulis: &[PauliLike],
predicate: fn(&Observable, &PauliLike) -> bool,
) -> IndexSet
where
Observable::Bits: BitwisePair<PauliLike::Bits>,
{
paulis
.iter()
.enumerate()
.filter(|(_, p)| predicate(observable, *p))
.map(|(i, _)| i)
.collect()
}

/// Returns the indices of Pauli operators in `paulis` that anticommute with `observable`.
pub fn indexed_anti_commutators_of<Observable: Pauli, PauliLike: Pauli>(
observable: &Observable,
paulis: &[PauliLike],
) -> IndexSet
where
Observable::Bits: BitwisePair<PauliLike::Bits>,
{
indexes_of_paulis_where(observable, paulis, anti_commutes_with)
}

/// Returns the indices of Pauli operators in `paulis` that commute with `observable`.
pub fn indexed_commutators_of<Observable: Pauli, PauliLike: Pauli>(
observable: &Observable,
paulis: &[PauliLike],
) -> IndexSet
where
Observable::Bits: BitwisePair<PauliLike::Bits>,
{
indexes_of_paulis_where(observable, paulis, commutes_with)
}

pub fn are_mutually_commuting<PauliLike: Pauli>(paulis: &[PauliLike]) -> bool {
for i in 0..paulis.len() {
for j in 0..i {
Expand Down
Loading
Loading