Source code for qiskit_addon_cutting.utils.observable_terms
# This code is a Qiskit project.
# (C) Copyright IBM 2024.
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
# Reminder: update the RST file in docs/apidocs when adding new interfaces.
r"""Utilities for working with the unique terms of a collection of :class:`~qiskit.quantum_info.SparsePauliOp`\ s."""
from __future__ import annotations
from typing import Sequence, Iterable, Mapping
from qiskit.quantum_info import Pauli, PauliList, SparsePauliOp
from .iteration import strict_zip
[docs]
def gather_unique_observable_terms(
observables: Sequence[Pauli | SparsePauliOp] | PauliList,
) -> PauliList:
"""Inspect the contents of each observable to find and return the unique Pauli terms."""
if not observables:
if isinstance(observables, PauliList):
return observables.copy()
raise ValueError("observables list cannot be empty")
pauli_list: list[Pauli] = []
pauli_set: set[Pauli] = set()
for observable in observables:
if isinstance(observable, Pauli):
observable = SparsePauliOp(observable)
for pauli, coeff in strict_zip(observable.paulis, observable.coeffs):
assert pauli.phase == 0 # SparsePauliOp should do this for us
if coeff == 0:
continue
if pauli not in pauli_set:
pauli_list.append(pauli)
pauli_set.add(pauli)
if not pauli_list:
# Handle this special case, which can happen if all coeffs are zero.
# We create an empty PauliList with the correct number of qubits. See
# https://github.com/Qiskit/qiskit/pull/9770 for a proposal to make
# this simpler in Qiskit.
return PauliList(["I" * observables[0].num_qubits])[:0]
try:
return PauliList(pauli_list)
except ValueError as ex:
raise ValueError(
"Cannot construct PauliList. Do provided observables all have "
"the same number of qubits?"
) from ex
def _reconstruct_observable_expval_from_terms(
observable: Pauli | SparsePauliOp,
term_expvals: Mapping[Pauli, float | complex],
) -> complex:
if isinstance(observable, Pauli):
observable = SparsePauliOp(observable)
rv = 0.0j
for pauli, coeff in strict_zip(observable.paulis, observable.coeffs):
assert pauli.phase == 0 # SparsePauliOp should do this for us
if coeff == 0:
continue
try:
term_expval = term_expvals[pauli]
except KeyError as ex:
raise ValueError(
"An observable contains a Pauli term whose expectation value "
"was not provided."
) from ex
rv += coeff * term_expval
return rv
[docs]
def reconstruct_observable_expvals_from_terms(
observables: Iterable[Pauli | SparsePauliOp] | PauliList,
term_expvals: Mapping[Pauli, float | complex],
) -> list[complex]:
"""Reconstruct the expectation values given the expectation value of each unique term."""
return [
_reconstruct_observable_expval_from_terms(observable, term_expvals)
for observable in observables
]