Skip to content

Update docstrings for PauliSum. #5596

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 7, 2022
Merged
Changes from 3 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
115 changes: 110 additions & 5 deletions cirq-core/cirq/ops/linear_combinations.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,17 +360,69 @@ def _pauli_string_from_unit(unit: UnitPauliStringT, coefficient: Union[int, floa
class PauliSum:
"""Represents operator defined by linear combination of PauliStrings.

Since PauliStrings store their own coefficients, this class
does not implement the LinearDict interface. Instead, you can
Since `cirq.PauliString`s store their own coefficients, this class
does not implement the `cirq.LinearDict` interface. Instead, you can
add and subtract terms and then iterate over the resulting
(simplified) expression.

Under the hood, this class is backed by a LinearDict with coefficient-less
PauliStrings as keys. PauliStrings are reconstructed on-the-fly during
iteration.

PauliSums can be constructed explicitly:


>>> a, b = cirq.GridQubit.rect(1, 2)
>>> sum = cirq.PauliSum()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Can we show an example which constructs a non-empty pauli sum? Maybe using the .from_pauli_strings method, since that's the recommended way?



or implicitly:


>>> a, b = cirq.GridQubit.rect(1, 2)
>>> psum = cirq.X(a) * cirq.X(b) + 3.0 * cirq.Y(a)
>>> psum
cirq.PauliSum(
cirq.LinearDict({
frozenset({
(cirq.GridQubit(0, 0), cirq.X), (cirq.GridQubit(0, 1), cirq.X)}): (1+0j),
frozenset({
(cirq.GridQubit(0, 0), cirq.Y)}): (3+0j)}
)
)


basic arithmetic and expectation operations are supported as well:


>>> a, b = cirq.GridQubit.rect(1, 2)
>>> psum = cirq.X(a) * cirq.X(b) + 3.0 * cirq.Y(a)
>>> two_psum = 2 * psum
>>> four_psum = two_psum + two_psum
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a print(four_sum) statement before computing the expectation, for clarity.

>>> expectation = four_psum.expectation_from_state_vector(
... np.array([0.707106, 0, 0, 0.707106], dtype=complex),
... qubit_map={a: 0, b: 1}
... )
>>> expectation
4.0


"""

def __init__(self, linear_dict: Optional[value.LinearDict[UnitPauliStringT]] = None):
"""Construct a PauliSum from a linear dictionary.

Note, the preferred method of constructing PauliSum objects is either implicitly
or via the `from_pauli_strings` function.

Args:
linear_dict: Set of (`cirq.Qid`, `cirq.Pauli`) tuples to construct the sum
from.

Raises:
ValueError: If structure of `linear_dict` contains tuples other than the
form (`cirq.Qid`, `cirq.Pauli`).
"""
if linear_dict is None:
linear_dict = value.LinearDict()
if not _is_linear_dict_of_unit_pauli_string(linear_dict):
Expand All @@ -386,12 +438,39 @@ def _value_equality_values_(self):

@staticmethod
def wrap(val: PauliSumLike) -> 'PauliSum':
"""Convert a `cirq.PauliSumLike` object to a PauliSum

Attemps to convert an existing int, float, complex, `cirq.PauliString`,
`cirq.PauliSum` or `cirq.SingleQubitPauliStringGateOperation` into
a `cirq.PauliSum` object. For example:


>>> my_psum = cirq.PauliSum.wrap(2.345)
>>> my_psum
cirq.PauliSum(cirq.LinearDict({frozenset(): (2.345+0j)}))


Args:
`cirq.PauliSumLike` to convert to PauliSum.

Returns:
PauliSum representation of `val`.
"""
if isinstance(val, PauliSum):
return val
return PauliSum() + val

@classmethod
def from_pauli_strings(cls, terms: Union[PauliString, List[PauliString]]) -> 'PauliSum':
"""Returns a PauliSum by combining `cirq.PauliString` terms.

Args:
terms: `cirq.PauliString` or List of `cirq.PauliString`s to use inside
of this PauliSum object.
Returns:
PauliSum object representing the addition of all the `cirq.PauliString`
terms in `terms`.
"""
if isinstance(terms, PauliString):
terms = [terms]
termdict: DefaultDict[UnitPauliStringT, value.Scalar] = defaultdict(lambda: 0)
Expand All @@ -414,7 +493,7 @@ def from_boolean_expression(
qubit_map: map of string (boolean variable name) to qubit.

Return:
The PauliString that represents the Boolean expression.
The PauliSum that represents the Boolean expression.

Raises:
ValueError: If `boolean_expr` is of an unsupported type.
Expand Down Expand Up @@ -455,10 +534,25 @@ def from_boolean_expression(

@property
def qubits(self) -> Tuple[raw_types.Qid, ...]:
"""The sorted list of qubits used in this PauliSum."""
qs = {q for k in self._linear_dict.keys() for q, _ in k}
return tuple(sorted(qs))

def with_qubits(self, *new_qubits: 'cirq.Qid') -> 'PauliSum':
"""Return a new PauliSum on `new_qubits`.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can use some example here / highlight that self.qubits will give a sorted list of all qubits used in the pauli sum; and new qubits will be mapped 1:1 with the sorted qubits.

The fact that pauli sum will always return a sorted list of qubits is tricky and could be helpful to provide some examples.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Were you thinking maybe a code snippet ?

Args:
*new_qubits: `cirq.Qid` objects to replace existing
qubit objects in this PauliSum.

Returns:
PauliSum with new_qubits replacing the previous
qubits.

Raises:
ValueError: If len(new_qubits) != len(self.qubits).

"""
qubits = self.qubits
if len(new_qubits) != len(qubits):
raise ValueError('Incorrect number of qubits for PauliSum.')
Expand All @@ -469,12 +563,23 @@ def with_qubits(self, *new_qubits: 'cirq.Qid') -> 'PauliSum':
return PauliSum.from_pauli_strings(new_pauli_strings)

def copy(self) -> 'PauliSum':
"""Return a copy of this PauliSum.

Returns: A copy of this PauliSum.
"""
factory = type(self)
return factory(self._linear_dict.copy())

def matrix(self, qubits: Optional[Iterable[raw_types.Qid]] = None) -> np.ndarray:
"""Reconstructs matrix of self from underlying Pauli operations in
computational basis of qubits.
"""Returns the matrix of this PauliSum in computational basis of qubits.

Args:
qubits: Iterable of qubits, ordering to determine the computational
basis of qubits. If none is provided the default ordering of
`self.qubits` is used.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
qubits: Iterable of qubits, ordering to determine the computational
basis of qubits. If none is provided the default ordering of
`self.qubits` is used.
qubits: Ordered collection of qubits that determine the subspace
in which the matrix representation of the Pauli sum is to
be computed. If none is provided the default ordering of
`self.qubits` is used. Qubits present in `qubits` but absent from
`self.qubits` are acted on by the identity.


Returns:
np.ndarray representing the matrix of this PauliSum expression.

Raises:
TypeError: if any of the gates in self does not provide a unitary.
Expand Down