-
Notifications
You must be signed in to change notification settings - Fork 617
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
Fix inversion of templates #1243
Changes from 15 commits
fb4bb48
272d32f
43b38ee
afcfef7
f2092a5
48150ab
ee955d7
bcfaa85
5d7cedc
84c8943
caa4ca2
d7de2a5
3210ee9
cb75162
16fb9dc
89d2891
6245c71
c6cecb9
9ee8f73
0f65b61
a9bfc8b
f6dce73
ffa9b20
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 |
---|---|---|
|
@@ -648,11 +648,40 @@ def inv(self): | |
self.trainable_params = {parameter_mapping[i] for i in self.trainable_params} | ||
self._par_info = {parameter_mapping[k]: v for k, v in self._par_info.items()} | ||
|
||
for op in self._ops: | ||
op.inverse = not op.inverse | ||
for idx, op in enumerate(self._ops): | ||
try: | ||
self._ops[idx] = op.adjoint() | ||
except NotImplementedError: | ||
op.inverse = not op.inverse | ||
|
||
self._ops = list(reversed(self._ops)) | ||
|
||
def adjoint(self): | ||
"""Create a tape that is the adjoint of this one. | ||
|
||
Adjointed tapes are the conjugated and transposed version of the | ||
original tapes. Adjointed ops are equivalent to the inverted operation for unitary | ||
gates. | ||
|
||
Returns: | ||
~.QuantumTape: the adjointed tape | ||
""" | ||
new_tape = self.copy(copy_operations=True) | ||
qml.transforms.invisible(new_tape.inv)() | ||
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. For my understanding: do we now have And 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. Yep! 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. Interesting, invisible makes sure it is not queued? so it is a transform transform :) 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. Yep. Under the hood, it looks like this: with tape.stop_recording():
# things you don't want queued go here However, @Thenerdstation pointed out that with PL's functional UI, it also makes sense to have a functional 'entry point' to this functionality. Nathan is not wild about the name though, so I'm trying to brainstorm better names 😆 How about 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. Note that |
||
|
||
# the current implementation of the adjoint | ||
# transform requires that the returned inverted object | ||
# is automatically queued. | ||
QuantumTape._lock.acquire() | ||
try: | ||
QueuingContext.append(new_tape) | ||
except Exception as _: | ||
QuantumTape._lock.release() | ||
raise | ||
QuantumTape._lock.release() | ||
mariaschuld marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
return new_tape | ||
|
||
# ======================================================== | ||
# Parameter handling | ||
# ======================================================== | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -266,13 +266,14 @@ def expand(self): | |
|
||
with qml.tape.QuantumTape() as tape: | ||
|
||
qml.BasisState(self.init_state, wires=self.wires) | ||
qml.templates.BasisEmbedding(self.init_state, wires=self.wires) | ||
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. This ensures that this layer structure is invertible |
||
|
||
for l in range(self.n_layers): | ||
for i, wires_ in enumerate(nm_wires): | ||
u1_ex_gate( | ||
self.parameters[0][l, i, 0], self.parameters[0][l, i, 1], wires=wires_ | ||
) | ||
|
||
return tape | ||
|
||
@staticmethod | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,34 +18,51 @@ | |
|
||
|
||
def adjoint(fn): | ||
"""Create a function that applies the adjoint of the provided operation or template. | ||
"""Create a function that applies the adjoint (inverse) of the provided operation or template. | ||
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. @Thenerdstation I updated this docstring after discussion with a user that was having trouble |
||
|
||
This transform can be used to apply the adjoint of an arbitrary sequence of operations. | ||
|
||
Args: | ||
fn (function): Any python function that applies pennylane operations. | ||
fn (function): A quantum function that applies quantum operations. | ||
|
||
Returns: | ||
function: A new function that will apply the same operations but adjointed and in reverse order. | ||
|
||
**Example** | ||
|
||
The adjoint transforms can be used within a QNode to apply the adjoint of | ||
any quantum function. Consider the following quantum function, that applies two | ||
operations: | ||
|
||
.. code-block:: python3 | ||
|
||
def my_ops(): | ||
qml.RX(0.123, wires=0) | ||
qml.RY(0.456, wires=0) | ||
def my_ops(a, b, wire): | ||
qml.RX(a, wires=wire) | ||
qml.RY(b, wires=wire) | ||
|
||
We can create a QNode that applies this quantum function, | ||
followed by the adjoint of this function: | ||
|
||
.. code-block:: python3 | ||
|
||
dev = qml.device('default.qubit', wires=1) | ||
|
||
@qml.qnode(dev) | ||
def circuit(a, b): | ||
my_ops(a, b, wire=0) | ||
qml.adjoint(my_ops)(a, b, wire=0) | ||
return qml.expval(qml.PauliZ(0)) | ||
|
||
Printing this out, we can see that the inverse quantum | ||
function has indeed been applied: | ||
|
||
with qml.tape.QuantumTape() as tape: | ||
my_ops() | ||
>>> print(qml.draw(circuit)(0.2, 0.5)) | ||
0: ──RX(0.2)──RY(0.5)──RY(-0.5)──RX(-0.2)──┤ ⟨Z⟩ | ||
|
||
with qml.tape.QuantumTape() as tape_adj: | ||
qml.adjoint(my_ops)() | ||
The adjoint function can also be applied directly to templates and operations: | ||
|
||
>>> print(tape.operations) | ||
[RX(0.123, wires=[0]), RY(0.456, wires=[0])] | ||
>>> print(tape_adj.operatioins) | ||
[RY(-0.456, wires=[0]), RX(-0.123, wires=[0])] | ||
>>> qml.adjoint(qml.RX)(0.123, wires=0) | ||
>>> qml.adjoint(qml.templates.StronglyEntanglingLayers)(weights, wires=[0, 1]) | ||
josh146 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
.. UsageDetails:: | ||
|
||
|
@@ -102,9 +119,10 @@ def wrapper(*args, **kwargs): | |
try: | ||
op.adjoint() | ||
except NotImplementedError: | ||
# Decompose the operation and adjoint the result. | ||
# Expand the operation and adjoint the result. | ||
# We do not do anything with the output since | ||
# decomposition will automatically queue the new operations. | ||
adjoint(op.decomposition)(wires=op.wires) | ||
# calling adjoint on the expansion will automatically | ||
# queue the new operations. | ||
adjoint(op.expand)() | ||
josh146 marked this conversation as resolved.
Show resolved
Hide resolved
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. Much cleaner, because we do not need to know a signature of |
||
|
||
return wrapper |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2054,7 +2054,7 @@ def test_s_inverse(): | |
test_s_inverse() | ||
operations = test_s_inverse.qtape.operations | ||
assert "S.inv" not in [i.name for i in operations] | ||
assert "PhaseShift.inv" in [i.name for i in operations] | ||
assert "PhaseShift" in [i.name for i in operations] | ||
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. How come this was changed? (and line 2056 stayed as is) 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. With this change, The only exception are gates where the inverse is not known. 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. Oh! That makes sense. thank you! 🙂 |
||
|
||
expected = np.array([1.0, -1.0j]) / np.sqrt(2) | ||
assert np.allclose(dev.state, expected, atol=tol, rtol=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.
We need to add an
adjoint()
method to the tape, so that theqml.adjoint()
function knows what to do when it recursively hits a tape.