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

Add qubits to PauliStringPhasor #5565

Merged
merged 25 commits into from
Jun 24, 2022
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a4e977f
Add qubits to PauliStringPhasor
dabacon Jun 21, 2022
9f07b53
Merge branch 'master' into fixphaso
dabacon Jun 21, 2022
c3b5bf2
fix gate operation test
dabacon Jun 21, 2022
db7ffd1
Update cirq-core/cirq/ops/pauli_string_phasor.py
dabacon Jun 21, 2022
c714db1
Update cirq-core/cirq/ops/pauli_string_phasor.py
dabacon Jun 21, 2022
dafbf70
Update cirq-core/cirq/ops/pauli_string_phasor.py
dabacon Jun 21, 2022
58b7f4d
Update cirq-core/cirq/ops/pauli_string_phasor.py
dabacon Jun 21, 2022
11a8e00
review comments
dabacon Jun 22, 2022
5517103
Merge branch 'master' into fixphaso
dabacon Jun 22, 2022
0f3637f
Merge branch 'master' into fixphaso
dabacon Jun 22, 2022
a7e7cb0
Merge branch 'master' into fixphaso
dabacon Jun 22, 2022
e42f8b6
Merge branch 'master' into fixphaso
dabacon Jun 23, 2022
52ca61a
Merge branch 'master' into fixphaso
dabacon Jun 23, 2022
faac9e1
Update cirq-core/cirq/ops/pauli_string_phasor.py
dabacon Jun 23, 2022
b648751
Update cirq-core/cirq/ops/pauli_string_phasor.py
dabacon Jun 23, 2022
43a0095
review comments
dabacon Jun 23, 2022
118b285
fix nits
dabacon Jun 23, 2022
c943d05
Merge branch 'fixphaso' of github.com:dabacon/Cirq into fixphaso
dabacon Jun 23, 2022
d65eb71
Merge branch 'master' into fixphaso
dabacon Jun 23, 2022
b471857
Merge branch 'master' into fixphaso
dabacon Jun 24, 2022
f79e319
fix lint
dabacon Jun 24, 2022
a7b98a0
fix
dabacon Jun 24, 2022
798ba80
fix mypy
dabacon Jun 24, 2022
e80a822
Merge branch 'master' into fixphaso
dabacon Jun 24, 2022
6cef047
Merge branch 'master' into fixphaso
dabacon Jun 24, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 2 additions & 8 deletions cirq-core/cirq/ops/gate_operation_test.py
Expand Up @@ -488,6 +488,7 @@ def all_subclasses(cls):

skip_classes = {
# Abstract or private parent classes.
cirq.ArithmeticGate,
cirq.BaseDensePauliString,
cirq.EigenGate,
cirq.Pauli,
Expand All @@ -503,17 +504,10 @@ def all_subclasses(cls):
# Interop gates
cirq.interop.quirk.QuirkQubitPermutationGate,
cirq.interop.quirk.QuirkArithmeticGate,
# No reason given for missing json.
# TODO(#5353): Serialize these gates.
cirq.ArithmeticGate,
}

# Gates that do not satisfy the contract.
# TODO(#5167): Fix this case.
exceptions = {cirq.PauliStringPhasorGate}

skipped = set()
for gate_cls in gate_subclasses - exceptions:
for gate_cls in gate_subclasses:
filename = test_module_spec.test_data_path.joinpath(f"{gate_cls.__name__}.json")
if pathlib.Path(filename).is_file():
gates = cirq.read_json(filename)
Expand Down
2 changes: 1 addition & 1 deletion cirq-core/cirq/ops/pauli_string.py
Expand Up @@ -391,7 +391,7 @@ def _repr_pretty_(self, p: Any, cycle: bool) -> None:
p.text(str(self))

def __repr__(self) -> str:
ordered_qubits = sorted(self.qubits)
ordered_qubits = self.qubits
prefix = ''

factors = []
Expand Down
112 changes: 97 additions & 15 deletions cirq-core/cirq/ops/pauli_string_phasor.py
Expand Up @@ -12,7 +12,18 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import AbstractSet, cast, Dict, Iterable, Union, TYPE_CHECKING, Sequence, Iterator
from typing import (
AbstractSet,
cast,
Dict,
Iterable,
Iterator,
Optional,
Sequence,
TYPE_CHECKING,
Union,
)

import numbers

import sympy
Expand All @@ -35,16 +46,25 @@

@value.value_equality(approximate=True)
class PauliStringPhasor(gate_operation.GateOperation):
"""An operation that phases the eigenstates of a Pauli string.
r"""An operation that phases the eigenstates of a Pauli string.

This class takes `PauliString`, which is a sequence of non-identity
Pauli operators, potentially with a $\pm 1$ valued coefficient,
acting on qubits.

The -1 eigenstates of the Pauli string will have their amplitude multiplied
by e^(i pi exponent_neg) while +1 eigenstates of the Pauli string will have
their amplitude multiplied by e^(i pi exponent_pos).

The class also takes a list of qubits, which can be bigger than those
dabacon marked this conversation as resolved.
Show resolved Hide resolved
acted on by the provided `PauliString`. Those extra qubits are assumed to be
acted upon via identity.
"""

def __init__(
self,
pauli_string: ps.PauliString,
qubits: Optional[Sequence['cirq.Qid']] = None,
*,
exponent_neg: Union[int, float, sympy.Expr] = 1,
exponent_pos: Union[int, float, sympy.Expr] = 0,
Expand All @@ -54,20 +74,39 @@ def __init__(
Args:
pauli_string: The PauliString defining the positive and negative
eigenspaces that will be independently phased.
qubits: The qubits upon which the PauliStringPhasor acts. This
must be a superset of the qubits of `pauli_string`.
If None, it will use the qubits from `pauli_string`
The `pauli_string` contains only the non-identity component
of the phasor, while the qubits supplied here and not in
`pauli_string` are acted upon by identity. The order of
these qubits must match the order in `pauli_string`.
exponent_neg: How much to phase vectors in the negative eigenspace,
in the form of the t in (-1)**t = exp(i pi t).
exponent_pos: How much to phase vectors in the positive eigenspace,
in the form of the t in (-1)**t = exp(i pi t).

Raises:
ValueError: If coefficient is not 1 or -1.
ValueError: If coefficient is not 1 or -1 or the qubits of
`pauli_string` are not a subset of `qubits`.
"""
if qubits is not None:
it = iter(qubits)
if any(not any(q0 == q1 for q1 in it) for q0 in pauli_string.qubits):
raise ValueError(
f"PauliStringPhasor's pauli string qubits ({pauli_string.qubits}) "
f"are not an ordered subset of the explicit qubits ({qubits})."
)
dabacon marked this conversation as resolved.
Show resolved Hide resolved
else:
qubits = pauli_string.qubits
# Use qubits below instead of qubits or pauli_string.qubits
dabacon marked this conversation as resolved.
Show resolved Hide resolved
gate = PauliStringPhasorGate(
pauli_string.dense(pauli_string.qubits),
pauli_string.dense(qubits or pauli_string.qubits),
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
pauli_string.dense(qubits or pauli_string.qubits),
pauli_string.dense(qubits),

exponent_neg=exponent_neg,
exponent_pos=exponent_pos,
)
super().__init__(gate, pauli_string.qubits)
super().__init__(gate, qubits or pauli_string.qubits)
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
super().__init__(gate, qubits or pauli_string.qubits)
super().__init__(gate, qubits)

it = iter(self.qubits)
dabacon marked this conversation as resolved.
Show resolved Hide resolved
self._pauli_string = gate.dense_pauli_string.on(*self.qubits)

@property
Expand Down Expand Up @@ -96,41 +135,70 @@ def exponent_relative(self) -> Union[int, float, sympy.Expr]:
return self.gate.exponent_relative

def _value_equality_values_(self):
return (self.pauli_string, self.exponent_neg, self.exponent_pos)
return (self.pauli_string, self.qubits, self.exponent_neg, self.exponent_pos)

def equal_up_to_global_phase(self, other):
"""Checks equality of two PauliStringPhasors, up to global phase."""
if isinstance(other, PauliStringPhasor):
rel1 = self.exponent_relative
rel2 = other.exponent_relative
return rel1 == rel2 and self.pauli_string == other.pauli_string
return (
self.exponent_relative == other.exponent_relative
and self.pauli_string == other.pauli_string
and self.qubits == other.qubits
)
return False

def map_qubits(self, qubit_map: Dict[raw_types.Qid, raw_types.Qid]):
dabacon marked this conversation as resolved.
Show resolved Hide resolved
"""Maps the qubits inside the PauliString."""
"""Maps the qubits inside the PauliStringPhasor.

Args:
qubit_map: A map from the qubits in the phasor to new qubits.

Returns:
A new PauliStringPhasor with remapped qubits.

Raises:
ValueError: If the map does not contain an entry for all
the qubits in the phasor.
"""
if not set(self.qubits) <= qubit_map.keys():
raise ValueError(
"qubit_map must have a key for every qubit in the phasors qubits. "
f"keys: {qubit_map.keys()} phasor qubits: {self.qubits}"
)
return PauliStringPhasor(
self.pauli_string.map_qubits(qubit_map),
pauli_string=self.pauli_string.map_qubits(qubit_map),
qubits=[qubit_map[q] for q in self.qubits],
tanujkhattar marked this conversation as resolved.
Show resolved Hide resolved
exponent_neg=self.exponent_neg,
exponent_pos=self.exponent_pos,
)

def can_merge_with(self, op: 'PauliStringPhasor') -> bool:
"""Checks whether the underlying PauliStrings can be merged."""
return self.pauli_string.equal_up_to_coefficient(op.pauli_string)
return (
self.pauli_string.equal_up_to_coefficient(op.pauli_string) and self.qubits == op.qubits
)

def merged_with(self, op: 'PauliStringPhasor') -> 'PauliStringPhasor':
"""Merges two PauliStringPhasors."""
if not self.can_merge_with(op):
raise ValueError(f'Cannot merge operations: {self}, {op}')
pp = self.exponent_pos + op.exponent_pos
pn = self.exponent_neg + op.exponent_neg
return PauliStringPhasor(self.pauli_string, exponent_pos=pp, exponent_neg=pn)
return PauliStringPhasor(
self.pauli_string, qubits=self.qubits, exponent_pos=pp, exponent_neg=pn
)

def _circuit_diagram_info_(
self, args: 'cirq.CircuitDiagramInfoArgs'
) -> 'cirq.CircuitDiagramInfo':
qubits = self.qubits if args.known_qubits is None else args.known_qubits
syms = tuple(f'[{self.pauli_string[qubit]}]' for qubit in qubits)

def sym(qubit):
if qubit in self.pauli_string:
return f'[{self.pauli_string[qubit]}]'
return '[I]'

syms = tuple(sym(qubit) for qubit in qubits)
return protocols.CircuitDiagramInfo(wire_symbols=syms, exponent=self.exponent_relative)

def pass_operations_over(
Expand Down Expand Up @@ -170,6 +238,7 @@ def pass_operations_over(
def __repr__(self) -> str:
return (
f'cirq.PauliStringPhasor({self.pauli_string!r}, '
f'qubits={self.qubits!r}, '
f'exponent_neg={proper_repr(self.exponent_neg)}, '
f'exponent_pos={proper_repr(self.exponent_pos)})'
)
Expand All @@ -182,7 +251,19 @@ def __str__(self) -> str:
return f'({self.pauli_string})**{self.exponent_relative}'

def _json_dict_(self):
return protocols.obj_to_dict_helper(self, ['pauli_string', 'exponent_neg', 'exponent_pos'])
return protocols.obj_to_dict_helper(
self, ['pauli_string', 'qubits', 'exponent_neg', 'exponent_pos']
)

@classmethod
def _from_json_dict_(cls, pauli_string, exponent_neg, exponent_pos, **kwargs):
qubits = kwargs['qubits'] if 'qubits' in kwargs else None
return PauliStringPhasor(
pauli_string=pauli_string,
qubits=qubits,
exponent_neg=exponent_neg,
exponent_pos=exponent_pos,
)


@value.value_equality(approximate=True)
Expand Down Expand Up @@ -352,6 +433,7 @@ def on(self, *qubits: 'cirq.Qid') -> 'cirq.PauliStringPhasor':
"""Creates a PauliStringPhasor on the qubits."""
return PauliStringPhasor(
self.dense_pauli_string.on(*qubits),
qubits=qubits,
exponent_pos=self.exponent_pos,
exponent_neg=self.exponent_neg,
)
Expand Down