-
Notifications
You must be signed in to change notification settings - Fork 1k
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 support for allocating qubits in decompose to cirq.unitary #6112
Add support for allocating qubits in decompose to cirq.unitary #6112
Conversation
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.
Can you add a test where a GateA
decomposes into a GateB
where GateB
allocates new qubits and acts on
a) <= 4 qubits
b) > 4 qubits ?
My guess is that Case-(b)
above would fail the test right now. The reason is as follows:
-
_try_decompose_into_operations_and_qubits
internally callscirq.decompose_once
for gates / operations etc. -
cirq.unitary
uses (1) to get a list of decomposed operations and then delegates the logic of applying the unitary effect of these decompositions toapply_unitaries
protocol.
result = apply_unitaries( -
apply_unitary
then acts on each operation in the first-level decomposition and then tries to "apply" it's unitary effect onto the given target tensor. To decide a strategy, it looks atlen(args.axes)
.
Cirq/cirq-core/cirq/protocols/apply_unitary_protocol.py
Lines 346 to 357 in 36d67c1
if len(args.axes) <= 4: strats = [ _strat_apply_unitary_from_apply_unitary, _strat_apply_unitary_from_unitary, _strat_apply_unitary_from_decompose, ] else: strats = [ _strat_apply_unitary_from_apply_unitary, _strat_apply_unitary_from_decompose, _strat_apply_unitary_from_unitary, ] -
Thus, if the unitary operators on <= 4 qubits, the recursive strategy will delegate to
cirq.unitary(OpForGateB)
to get its (narrow) unitary and thus be able to apply the effect of the unitary on the target tensor correctly. -
But, if it acts > 4 qubits, then it'll try the
_strat_apply_unitary_from_decompose
which will fail because it currently assumes that decomposition returns an op-tree that's acts only the returned set ofqubits
and thus we'd need to make similar changes to_strat_apply_unitary_from_decompose
as well.
I think a better strategy would be to change the return type of _try_decompose_into_operations_and_qubits
to return work_qubits
and ancilla_qubits
instead of only qubits
. But that would be a bigger change since it would trigger every call site that makes this assumption. We can leave a comment on the issue and track this as an independent improvements (I can also take care of it as part of restructuring the cirq.decompose
protocol).
it doesn't matter whether the gate acts on the issue isn't I'm working a fix for the apply unitary protocol, will update this PR soon. |
Well, what I meant was that most callsites of Cirq/cirq-core/cirq/sim/simulation_state.py Line 296 in ebec38b
Thus, changing the return signature of this method would be a good way to find and fix all such call-sites that assume that gates / operations cannot allocate new qubits as part of their decomposition. Although, I quick grep search shows the following call sites: cirq-core/cirq/sim/simulation_state.py:from cirq.protocols.decompose_protocol import _try_decompose_into_operations_and_qubits
cirq-core/cirq/sim/simulation_state.py: operations, qubits1, _ = _try_decompose_into_operations_and_qubits(val)
cirq-core/cirq/protocols/kraus_protocol.py:from cirq.protocols.decompose_protocol import _try_decompose_into_operations_and_qubits
cirq-core/cirq/protocols/kraus_protocol.py: operations, _, _ = _try_decompose_into_operations_and_qubits(val)
cirq-core/cirq/protocols/decompose_protocol.py:def _try_decompose_into_operations_and_qubits(
cirq-core/cirq/protocols/unitary_protocol.py:from cirq.protocols.decompose_protocol import _try_decompose_into_operations_and_qubits
cirq-core/cirq/protocols/unitary_protocol.py: operations, qubits, val_qid_shape = _try_decompose_into_operations_and_qubits(val)
cirq-core/cirq/protocols/has_unitary_protocol.py:from cirq.protocols.decompose_protocol import _try_decompose_into_operations_and_qubits
cirq-core/cirq/protocols/has_unitary_protocol.py: operations, _, _ = _try_decompose_into_operations_and_qubits(val)
cirq-core/cirq/protocols/apply_unitary_protocol.py:from cirq.protocols.decompose_protocol import _try_decompose_into_operations_and_qubits
cirq-core/cirq/protocols/apply_unitary_protocol.py: operations, qubits, _ = _try_decompose_into_operations_and_qubits(val)
cirq-core/cirq/protocols/mixture_protocol.py:from cirq.protocols.decompose_protocol import _try_decompose_into_operations_and_qubits
cirq-core/cirq/protocols/mixture_protocol.py: operations, _, _ = _try_decompose_into_operations_and_qubits(val) Out of these, the The |
@tanujkhattar got it. however fixing different places where it's called depends on the logic executed there. that's why the method is not the issue for this PR since we already know that these two protocols don't support ancillas. The cirq.unitary is essentially just convenience method for handling different cases with a fall back to To that end I have two ideas. but I don't like either of them. |
@NoureldinYosri I think you should also look at the approach suggested here: #6040 (comment) and implemented as part of #6081. The two issues seem related. |
@tanujkhattar the issue for me wasn't keeping track of the new qubits. I have a couple of ways for doing that. The issue is how not to sacrifice the efficiency of the protocol. The current implementation is not efficient in any way. but it will do the trick & unblock cirq-ft. later I'll improve its efficiency. btw currently there is no mention of the qubit manager. when we introduce the qubit manager abstraction into cirq we will need to add it here. There is a natural way of doing this by adding the qubit manager as a property of the For cirq-ft this is currently not a problem since all decompositions use the global qubit manager and there is no object to pass around. what do you think? |
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.
Looking good overall. Main suggestion is to extend the ApplyUnitaryArgs
class to provide methods to allocate new space for the ancillas and extract out the unitary corresponding to working qubits.
state = qis.eye_tensor(all_qid_shapes, dtype=np.complex128) | ||
buffer = np.empty_like(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 think we should create a kronecker_product
and factor
method on ApplyUnitaryArgs
; similar to SimulationState
; and then dispatch the logic of allocate new space and extracting out the unitary corresponding to a subspace using function calls to these methods.
I addressed the comment we had offline. I'll keep the code that factors out the unitary as it's for now and I'll add a general |
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 % minor refactoring nits
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.
One comment on simple_gates.py -- either we don't create a new file in this PR or we add tests to make sure the new file has coverage.
@tanujkhattar PTAL. I added |
…lso added docstrings
Here is a commit on top of this PR which suggests a few changes (described below) - tanujkhattar@14dd571 The tests for I've refactored the I've also fixed a few styling nits like added docstrings and modified function names. Can you please incorporate the changes from my commit into your PR and then we can go ahead and merge? |
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! Can you please fix the coverage failure so we can merge?
Thanks for all the help!
done. one thing I noticed is that the |
Hi, I set up my dev enviroment to start working on this issue and read through the the previous conversation and code. I'm gonna work on the 2nd question you raised #6101 2nd Question: Can it support the situtation where some of the operations in the decomposition are not unitary but the overall effect on the work qubits (not the ancillas) is unitary (e.g. erasing entanglement using measurement)? |
@shef4 sounds good, thanks for your help In the mean time I will soon add the consistancy and correctness checks. |
@NoureldinYosri sure, thing! I looked into the problem more and wanted to get some input before coding. From what I understood from the comments and the code files. I think it makes sense to add a simple test for "earsing entagled qubits using measurment" in apply_unitary_protocol_test.py that utilizes the apply_unitaries function. The apply_unitaries function currently terminates if one of the the given unitary values does not have a unitary effect ,but does not rollback changes from previous unitray values. A bruteforce approach could be to apply all unitary operations and check at the end if the work qubit is still unitary. This would probaly require the least amount of change to the code, but would make apply_unitaries less effecient. I could also add a separate function. Just to clearify, can the work qubit be non-unitary during the operation sequence as long as it is unitary at the end? |
@shef4 the product of unitaries is always a unitary. the problem we have is different. if some operation is not unitary and in that case there is no This gate can be implemented using And gate as class CCNOTUsingAnd(cirq_ft.GateWithRegisters):
@cached_property
def registers(self):
return cirq_ft.Registers.build(control=2, target=1)
def decompose_from_registers(
self, *, context: cirq.DecompositionContext, **quregs: Sequence[cirq.Qid]
) -> cirq.OP_TREE:
control, target = quregs['control'], quregs['target']
(ancilla,) = context.qubit_manager.qalloc(1)
yield cirq_ft.And().on(*control, ancilla)
yield cirq.CNOT(ancilla, t)
yield cirq_ft.And(adjoint=True)(c0, c1, ancilla) so task 2 is to support something like u = cirq.unitary(CCNOTUsingAnd) # Must succeeds to build the unitary.
assert np.allclose(u, cirq.unitary(cirq.CCNOT)) # Must be the same as the unitary of a CCNOT currently the line |
@NoureldinYosri Thanks for the clarification and code example, there was a typo in my brute-force solution comment. what I meant was that from my understanding,
I think i know where to go from here, thank you again. I'm gonna write a simple test for this example ( |
…umlib#6112) * Add support for allocating qubits in decompose to cirq.unitary * fixed apply_unitaries * fix mypy * refactored tests * addressing comments * added sample_gates_test.py * Improved sample_gates.py implementation and unitary_protocol tests. Also added docstrings * fixed lint * retrigger checks --------- Co-authored-by: Tanuj Khattar <tanujkhattar@google.com>
This is the simplest implementation to do this in order to unblock cirq-ft.
This implementation only satisfies the first requirement of #6101 (comment)
In followup PRs I will address other requirements.