Skip to content

Commit

Permalink
allow observable alphabet as well
Browse files Browse the repository at this point in the history
  • Loading branch information
Cryoris committed Jan 30, 2025
1 parent 875ea23 commit 4220023
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 19 deletions.
56 changes: 41 additions & 15 deletions crates/accelerate/src/sparse_observable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2396,24 +2396,50 @@ impl PySparseObservable {
Ok(inner.into())
}

#[pyo3(signature = ())]
fn to_sparse_list(&self, py: Python) -> PyResult<Py<PyList>> {
/// Express the observable in terms of a sparse list format.
///
/// This is the counter-operation of :meth:`.SparseObservable.from_sparse_list`.
///
/// Args:
/// only_pauli: If ``True``, express the observable only in terms of non-identity Paulis,
/// :math:`X`, :math:`Y`, and :math:`Z`. Beware that this will use at least :math:`2^n`
/// terms if there are :math:`n` single-qubit projectors present, which can lead
/// to an exponentially expensive representation. Defaults to ``False``.
///
/// Examples:
///
/// >>> obs = SparseObservable.from_list([("IIXIZ", 2j), ("IIZIX", 2j)])
/// >>> reconstructed = SparseObservable.from_sparse_list(obs.to_sparse_list(), obs.num_qubits)
/// >>> assert obs == reconstructed
#[pyo3(signature = (only_paulis=false))]
fn to_sparse_list(&self, py: Python, only_paulis: bool) -> PyResult<Py<PyList>> {
let inner = self.inner.read().map_err(|_| InnerReadError)?;

let sparse_list = inner
.to_paulis()
.map(move |(bits, indices, coeff)| {
let mut pauli_string = String::new();
for bit in bits {
pauli_string.push_str(bit.py_label());
}
let py_string = PyString::new(py, &pauli_string).unbind();
let py_indices = PyList::new(py, indices)?.unbind();
let py_coeff = coeff.into_py_any(py)?;
// turn a 3-tuple of (bit terms, indices, coeff) into a Python tuple
let to_py_tuple = |bits: &[BitTerm], indices: &[u32], coeff: Complex64| {
let mut pauli_string = String::new();
for bit in bits {
pauli_string.push_str(bit.py_label());
}
let py_string = PyString::new(py, &pauli_string).unbind();
let py_indices = PyList::new(py, indices)?.unbind();
let py_coeff = coeff.into_py_any(py)?;

PyTuple::new(py, vec![py_string.as_any(), py_indices.as_any(), &py_coeff])
})
.collect::<PyResult<Vec<_>>>()?;
PyTuple::new(py, vec![py_string.as_any(), py_indices.as_any(), &py_coeff])
};

// to map onto a Pauli list, we first have to expand all projectors, otherwise
// we can just directly iterate over the view
let sparse_list = match only_paulis {
false => inner
.iter()
.map(|view| to_py_tuple(view.bit_terms, view.indices, view.coeff))
.collect::<PyResult<Vec<_>>>()?,
true => inner
.to_paulis()
.map(move |(bits, indices, coeff)| to_py_tuple(&bits, &indices, coeff))
.collect::<PyResult<Vec<_>>>()?,
};

let out = PyList::new(py, sparse_list)?;
Ok(out.unbind())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -967,7 +967,7 @@ def from_sparse_observable(
category=RuntimeWarning,
)

as_sparse_list = obs.to_sparse_list()
as_sparse_list = obs.to_sparse_list(only_paulis=True)
return SparsePauliOp.from_sparse_list(as_sparse_list, obs.num_qubits)

def to_list(self, array: bool = False):
Expand Down
34 changes: 31 additions & 3 deletions test/python/quantum_info/test_sparse_observable.py
Original file line number Diff line number Diff line change
Expand Up @@ -2028,8 +2028,22 @@ def test_to_sparse_list(self):
self.assertEqual("ZYX", sparse_list[0][0])
self.assertListEqual([0, 1, 2], sparse_list[0][1])

obs = SparseObservable("lrI0")
obs = SparseObservable.from_list([("lrI0", 0.5), ("YYIZ", -1j)])
sparse_list = obs.to_sparse_list()
with self.subTest(msg="multiple"):
self.assertEqual(2, len(sparse_list))

self.assertEqual("0rl", sparse_list[0][0])
self.assertEqual([0, 2, 3], sparse_list[0][1])
self.assertAlmostEqual(0.5, sparse_list[0][2])

self.assertEqual("ZYY", sparse_list[1][0])
self.assertEqual([0, 2, 3], sparse_list[1][1])
self.assertAlmostEqual(-1j, sparse_list[1][2])

def test_to_sparse_pauli_list(self):
obs = SparseObservable("lrI0")
sparse_list = obs.to_sparse_list(only_paulis=True)

as_spo = SparsePauliOp.from_sparse_list(sparse_list, 4)
expect = SparsePauliOp.from_sparse_list(
Expand All @@ -2045,6 +2059,20 @@ def test_to_sparse_list(self):
],
4,
)
self.assertEqual(Operator(expect), Operator(as_spo))

def test_sparse_list_roundtrip(self):
"""Test dumping into a sparse list and constructing from one."""
obs = SparseObservable.from_list(
[
("IIXIZ", 2j),
("IIZIX", 2j),
("++III", -1.5),
("--III", -1.5),
("IrIlI", 0.5),
("IIrIl", 0.5),
]
)

with self.subTest(msg="lrI0"):
self.assertEqual(Operator(expect), Operator(as_spo))
reconstructed = SparseObservable.from_sparse_list(obs.to_sparse_list(), obs.num_qubits)
self.assertEqual(obs, reconstructed)

0 comments on commit 4220023

Please sign in to comment.