-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Simplify decomposition of controlled eigengates with global phase #7291
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,6 +33,7 @@ | |
control_values as cv, | ||
controlled_operation as cop, | ||
diagonal_gate as dg, | ||
eigen_gate, | ||
global_phase_op as gp, | ||
op_tree, | ||
raw_types, | ||
|
@@ -159,6 +160,12 @@ def _decompose_with_context_( | |
self, qubits: Tuple['cirq.Qid', ...], context: Optional['cirq.DecompositionContext'] = None | ||
) -> Union[None, NotImplementedType, 'cirq.OP_TREE']: | ||
control_qubits = list(qubits[: self.num_controls()]) | ||
# If the subgate is an EigenGate with non-zero phase, try to decompose it | ||
# into a phase-free gate and a global phase gate. | ||
if isinstance(self.sub_gate, eigen_gate.EigenGate) and self.sub_gate.global_shift != 0: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any way you can get rid of this condition? Like try decomposing the subgate first, then only enter the branch if the subgate has decomposed? A big goal is to reduce the amount of type checking needed here. If this works, then the duplicate function call at the end of this function can be removed, and the function can just return NotImplemented if it gets to that point. |
||
result = self._decompose_sub_gate_with_controls(qubits, context) | ||
if result is not NotImplemented: | ||
return result | ||
if ( | ||
protocols.has_unitary(self.sub_gate) | ||
and protocols.num_qubits(self.sub_gate) == 1 | ||
|
@@ -219,6 +226,11 @@ def _decompose_with_context_( | |
control_qid_shape=self.control_qid_shape, | ||
).on(*control_qubits) | ||
return [result, controlled_phase_op] | ||
return self._decompose_sub_gate_with_controls(qubits, context) | ||
|
||
def _decompose_sub_gate_with_controls( | ||
self, qubits: Tuple['cirq.Qid', ...], context: Optional['cirq.DecompositionContext'] = None | ||
) -> Union[None, NotImplementedType, 'cirq.OP_TREE']: | ||
result = protocols.decompose_once_with_qubits( | ||
self.sub_gate, | ||
qubits[self.num_controls() :], | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,7 +34,7 @@ | |
import sympy | ||
|
||
from cirq import protocols, value | ||
from cirq.ops import raw_types | ||
from cirq.ops import global_phase_op, raw_types | ||
|
||
if TYPE_CHECKING: | ||
import cirq | ||
|
@@ -375,6 +375,24 @@ def _json_dict_(self) -> Dict[str, Any]: | |
def _measurement_key_objs_(self): | ||
return frozenset() | ||
|
||
def _decompose_( | ||
self, qubits: Tuple['cirq.Qid', ...] | ||
) -> Union[NotImplementedType, 'cirq.OP_TREE']: | ||
"""Attempts to decompose the gate into a phase-free gate and a global phase gate. | ||
|
||
Returns: | ||
NotImplemented, if global phase or exponent are 0. Otherwise a phase-free gate | ||
applied to the qubits followed by a global phase gate. | ||
""" | ||
if self.global_shift == 0 or self.exponent == 0: | ||
return NotImplemented | ||
self_without_phase = self._with_exponent(self.exponent) | ||
# This doesn't work for gates that fix global_shift, such as Rx. These gates must define | ||
# their own _decompose_ method. | ||
self_without_phase._global_shift = 0 | ||
global_phase = 1j ** (2 * self.global_shift * self.exponent) | ||
return [self_without_phase.on(*qubits), global_phase_op.GlobalPhaseGate(global_phase)()] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe check that the remaining phase isn't zero, and drop it if it is. This could be the case if shift==0.5 and exponent==4 for instance. The decomposition would factor out the shift, leaving, say, X**4 and an identity phase gate that there's no reason to keep. |
||
|
||
|
||
def _lcm(vals: Iterable[int]) -> int: | ||
t = 1 | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, so here's where we probably need to have an opt in flag on the decompose context object. Having the basic gates suddenly decompose to multiple gates by default probably would be too likely to break something. While generally it seems that changing details around complex decompositions have been approved, this change seems a little too fundamental.