-
Notifications
You must be signed in to change notification settings - Fork 48
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
More improvements to the Controlled infrastructure #879
More improvements to the Controlled infrastructure #879
Conversation
def controlled( | ||
self, | ||
num_controls: Optional[int] = None, | ||
num_controls: Union[Optional[int], 'CtrlSpec'] = None, | ||
control_values=None, | ||
control_qid_shape: Optional[Tuple[int, ...]] = None, | ||
) -> 'cirq.Gate': | ||
from qualtran.cirq_interop import BloqAsCirqGate | ||
|
||
controlled_gate = cirq.ControlledGate( | ||
self, | ||
num_controls=num_controls, | ||
control_values=control_values, | ||
control_qid_shape=control_qid_shape, | ||
) | ||
ctrl_spec = CtrlSpec.from_cirq_cv(controlled_gate.control_values) | ||
return BloqAsCirqGate(Controlled(self, ctrl_spec)) | ||
) -> 'GateWithRegisters': |
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.
Would it be useful to provide overloaded type hints?
# pylint: disable=arguments-renamed
@overload
def controlled(
self,
num_controls: Optional[int] = None,
control_values=None,
control_qid_shape: Optional[Tuple[int, ...]] = None,
) -> 'GateWithRegisters':
...
# pylint: disable=signature-differs
@overload
def controlled(self, ctrl_spec: 'CtrlSpec') -> 'GateWithRegisters':
...
# pylint: disable=arguments-renamed
def controlled(self, num_controls=None, control_values=None, control_qid_shape=None):
Right now it's difficult to understand the usage without looking at the function body.
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, I think this needs a big docstring that describes the two modes of operation
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.
Added a detailed docstring and the overloads. Also added tests to verify the different cases raise errors.
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.
looks good. approval but please fix the issues raised
qualtran/_infra/adjoint.py
Outdated
@@ -150,6 +150,13 @@ def decompose_from_registers( | |||
return cirq.inverse(self.subbloq.decompose_from_registers(context=context, **quregs)) | |||
return super().decompose_from_registers(context=context, **quregs) | |||
|
|||
def _circuit_diagram_info_(self, args: 'cirq.CircuitDiagramInfoArgs'): |
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.
return type annotation
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.
done
qualtran/_infra/controlled.py
Outdated
cirq.ControlledGate(self.subbloq, control_values=self.ctrl_spec.to_cirq_cv()) | ||
) | ||
if all(reg.side == Side.THRU for reg in self.subbloq.signature): | ||
# subbloq has only THRU registers, so the unitary is well defined. |
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.
pycharm tells me this is supposed to be "well-defined"
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.
reworded
def controlled( | ||
self, | ||
num_controls: Optional[int] = None, | ||
num_controls: Union[Optional[int], 'CtrlSpec'] = None, | ||
control_values=None, | ||
control_qid_shape: Optional[Tuple[int, ...]] = None, | ||
) -> 'cirq.Gate': | ||
from qualtran.cirq_interop import BloqAsCirqGate | ||
|
||
controlled_gate = cirq.ControlledGate( | ||
self, | ||
num_controls=num_controls, | ||
control_values=control_values, | ||
control_qid_shape=control_qid_shape, | ||
) | ||
ctrl_spec = CtrlSpec.from_cirq_cv(controlled_gate.control_values) | ||
return BloqAsCirqGate(Controlled(self, ctrl_spec)) | ||
) -> 'GateWithRegisters': |
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, I think this needs a big docstring that describes the two modes of operation
|
||
yield self.ctrl_state_prep.on(*phase_qubits) | ||
for i, qbit in enumerate(phase_qubits[::-1]): | ||
yield cirq.pow(unitary_op.controlled_by(qbit), 2**i) | ||
yield Power(self.unitary.controlled(), 2**i).on(qbit, *target_qubits) | ||
# yield cirq.pow(unitary_op.controlled_by(qbit), 2**i) |
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.
remove old code
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.
Reverted the file so now there are no changes and the original implementation uses bloqs instead of cirq gates. See added tests in gate_with_registers_test.py
for clarity.
num_inverse_applications -= 1 | ||
else: | ||
# apply C[0]-U | ||
yield self.U.on_registers(**quregs).controlled_by(signal_qubit, control_values=[0]) | ||
yield self.U.controlled(control_values=[0]).on(signal_qubit, *flat_qubits_for_u) |
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.
Should we default to using qualtran style controls CtrlSpec(cvs=0)
instead of cirq style control_values=[0]
?
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.
Reverted this change because self.U.on_registers(**quregs).controlled_by(signal_qubit)
now uses the Controlled
bloq instead of cirq gate. The op.controlled_by
still expects the cirq-style API since we don't have a custom GWROperation
where we can override the controlled_by
to expect the bloq-style API as well. But I think it's fine right now to do things this way
@mpharrigan I'm ready to merge this but feel free to take another look since I've made some minor upgrades. Using Tl;Dr - the discrepancy in usage from a users perspective that I described in Qualtran sync today is now gone and users don't need to worry about the intricacies when using the Cirq or Bloq style APIs. |
where is the logic for |
No override is needed because of the way Cirq and Qualtran works. This was true today morning as well, I just realized it when making these changes and thus added tests to |
Merging this now |
Fixes #865
Fixes #686
The improvements are -
GateWithRegisters.controlled()
now returnsControlled
bloqcirq_gate_to_bloq
now maps acirq.Controlled
gates toControlled
bloq.Controlled
bloq is now aGateWithRegisters
. This is required because we want to support cirq style simulations for circuits containingControlled(Rx())
bloqs andBloqAsCirqGate(Controlled())
is not sufficient because we need special logic to derive the unitary matrix for controlled bloqs (eg: when subbloq is a GateWithRegisters but doesn't have only THRU registers).After this PR, we should never need to invoke the
cirq.Controlled
gates in either cirq-style or bloq-style API in Qualtran and we should always useControlled
bloq in all cases. All existing uses ofcirq.Controlled
gates would be updated toControlled
bloq when invoking the bloq-style API using thecirq_gate_to_bloq
mapping.This is the final PR in my series of PRs to improve the controlled infrastructure. cc @mpharrigan