-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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 gate.control()
to return AnnotatedOperation
#11433
Update gate.control()
to return AnnotatedOperation
#11433
Conversation
One or more of the the following people are requested to review this:
|
Pull Request Test Coverage Report for Build 7730318921Warning: This coverage report may be inaccurate.We've detected an issue with your CI configuration that might affect the accuracy of this pull request's coverage report.
💛 - Coveralls |
gate.control()
to return AnnotatedOperation
gate.control()
to return AnnotatedOperation
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.
Overall I think this looks good. I've only done a high level review, quick skim over the code so far. But before I dive in line by line I had a couple of high level questions:
- Do you think there is a time in the future where we want to have
annotated=True
by default? - For concrete gates that have a defined controlled variant (e.g. X -> CX) is there an advantage to calling
XGate().control(annotated=True)
? I assume this opens up opportunities for optimization by having things defined abstractly withAnnotatedOperation
.
I'm just wondering if there are potentially different defaults we want here as a breaking change for the 1.0.0 release? Like I'm wondering if doing the base Gate.control
default to annotated=True
makes more sense. From a backwards compatibility PoV what you have here is better, but I'm just curious of your thoughts on the longer term plan with this.
These are really great questions. Longer-term I would indeed like to see I have now run local tests to see what would break if we were to set
I am up to making this breaking change for Qiskit 1.0 provided that we can quickly resolve all the problems above. Regarding controlled gates with defined controlled variants. At the moment I can't think of a convincing use-case when having an Update: after thinking about the above, I think that always returning a |
I have taken a close look at all tests that would fail if we changed the default to As I wrote before, most of the failing tests pertain to testing controlled gates, as annotated operations do not have methods There are failing tests related to the There are some failing visualization tests: thanks to #11202 the annotated operations with control modifiers are printed very nicely and quite similarly to controlled gates, but still there are some minor differences in
used to print
and now it's exactly the same picture except that the first letter in The two remaining failing tests are
does not currently assign parameters to the base operation of an annotated 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.
Overall this LGTM, I think you're right that even with a potentially breaking change in qiskit 1.0 the limitations around AnnotatedOperation
(primarily the QPY support still needed) is enough of a reason to be conservative here and default to the existing behavior. We can revisit this for qiskit 2.0.0 if we want to potentially move everything to be a annotated operation.
I left a few inline comments but nothing major and I'm fine merging this as is.
num_ctrl_qubits: int = 1, | ||
label: str | None = None, | ||
ctrl_state: int | str | None = None, | ||
annotated: bool = False, |
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 this be?
annotated: bool = False, | |
annotated: bool = True, |
Not that it matters much, but we're always returning an annotated op here.
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.
Good point. I have also simplified the returned gate: instead of returning a control-annotated operation on top of the current annotated operation, we return an annotated operation with the current set of modifiers extended with the new control modifiers.
label: str | None = None, | ||
ctrl_state: int | str | None = None, | ||
annotated: bool = False, | ||
): |
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.
?
): | |
) -> AnnotatedOperation: |
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.
if not annotated: | ||
if ctrl_state is None: # TODO add ctrl state implementation by adding X gates | ||
gate = MCMT( | ||
self.gate, self.num_ctrl_qubits + num_ctrl_qubits, self.num_target_qubits | ||
) | ||
else: | ||
gate = super().control(num_ctrl_qubits, label, ctrl_state, annotated=annotated) | ||
else: | ||
gate = AnnotatedOperation( | ||
self, ControlModifier(num_ctrl_qubits=num_ctrl_qubits, ctrl_state=ctrl_state) | ||
) |
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.
I'd probably have done to save some code and reduce duplication:
if not annotated: | |
if ctrl_state is None: # TODO add ctrl state implementation by adding X gates | |
gate = MCMT( | |
self.gate, self.num_ctrl_qubits + num_ctrl_qubits, self.num_target_qubits | |
) | |
else: | |
gate = super().control(num_ctrl_qubits, label, ctrl_state, annotated=annotated) | |
else: | |
gate = AnnotatedOperation( | |
self, ControlModifier(num_ctrl_qubits=num_ctrl_qubits, ctrl_state=ctrl_state) | |
) | |
if not annotated and ctrl_state is None: | |
gate = MCMT( | |
self.gate, self.num_ctrl_qubits + num_ctrl_qubits, self.num_target_qubits | |
) | |
else: | |
gate = super().control(num_ctrl_qubits, label, ctrl_state, annotated=annotated) |
I think this applies pretty much everwhere.
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.
Oh, I absolutely love this suggestion. Not only this makes the code shorter and more readable, it also seems a more correct way to apply the control method. Changed across the board in 0783fbb.
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.
LGTM, thanks for the updates
Summary
Partially resolves #11082.
This PR adds a new argument
annotated
to all the "control" methods, that is toQuantumCircuit.control()
,Gate.control()
and all of the subclasses ofGate
, for example toSwapGate.control()
andUnitaryGate.control()
.When
annotated
isFalse
(which is the default), the behavior is unchanged, making the change backward-compatible. As an example,SwapGate().control(1, annotated=False)
returns an object of typeCSwapGate
(or, more precisely, a singleton version of that), whileSwapGate().control(2, annotated=False)
returns an object of typeControlledGate
.When
annotated
isTrue
, the control method is allowed to return anAnnotatedOperation
instead, leading to a more abstract representation that avoids eagerly constructing the gate's definition and allows potential "high-level" transpiler optimizations. I am saying "is allowed" because in practice there may be cases where we wouldn't want to introduce this abstraction, for example we may decide that a singly-controlledXGate
should always be aCXGate
and not an annotated operation. This will make even more sense when we try to similarly extend thepower
andinverse
methods: it makes no sense to say that the inverse of aCXGate
is an annotated operation and not just aCXGate
.The plans are to follow this PR in two directions.
First, we want to introduce similar capability for
inverse
andpower
methods, however each of these comes with its own set of headaches, so I have limited this PR only tocontrol
.Second, we want to identify where in Qiskit it's beneficial to call
control()
withanotated=True
. I believe it would be useful to do so in various circuit-constructing utilities (for example constructing controlled QFT-based adders), but there could be more opportunities.Feedback regarding the approach is welcome. I am explicitly tagging @jakelishman and @mtreinish (especially that this is based on Matthew's offline suggestion).