Skip to content
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

Update docstrings for PauliSum. #5596

Merged
merged 5 commits into from
Jul 7, 2022
Merged
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
127 changes: 122 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,79 @@ 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
MichaelBroughton marked this conversation as resolved.
Show resolved Hide resolved
does not implement the `cirq.LinearDict` interface. Instead, you can
add and subtract terms and then iterate over the resulting
(simplified) expression.

MichaelBroughton marked this conversation as resolved.
Show resolved Hide resolved
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)
>>> psum = cirq.PauliSum.from_pauli_strings([
... cirq.PauliString(-1, cirq.X(a), cirq.Y(b)),
... cirq.PauliString(2, cirq.Z(a), cirq.Z(b)),
... cirq.PauliString(0.5, cirq.Y(a), cirq.Y(b))
... ])
>>> print(psum)
-1.000*X(q(0))*Y(q(1))+2.000*Z(q(0))*Z(q(1))+0.500*Y(q(0))*Y(q(1))


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.

>>> print(four_psum)
4.000*X(q(0, 0))*X(q(0, 1))+12.000*Y(q(0, 0))


>>> 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 +448,39 @@ def _value_equality_values_(self):

@staticmethod
def wrap(val: PauliSumLike) -> 'PauliSum':
"""Convert a `cirq.PauliSumLike` object to a PauliSum
MichaelBroughton marked this conversation as resolved.
Show resolved Hide resolved

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 +503,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 +544,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))
MichaelBroughton marked this conversation as resolved.
Show resolved Hide resolved
MichaelBroughton marked this conversation as resolved.
Show resolved Hide resolved

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 +573,25 @@ 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: 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