diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md
index f5d2031b22b..b78d3e83759 100644
--- a/.github/CHANGELOG.md
+++ b/.github/CHANGELOG.md
@@ -48,6 +48,10 @@
Bug fixes
+* A bug which resulted in `qml.adjoint()` and `qml.inv()` failing to work with
+ templates has been fixed.
+ [(#1243)](https://github.com/PennyLaneAI/pennylane/pull/1243)
+
Documentation
* Updated the order of the parameters to the `GaussianState` operation to match
@@ -58,7 +62,7 @@
This release contains contributions from (in alphabetical order):
-Thomas Bromley, Diego Guala, Anthony Hayes, Antal Száva
+Thomas Bromley, Diego Guala, Anthony Hayes, Josh Izaac, Antal Száva
# Release 0.15.0 (current release)
diff --git a/pennylane/tape/tape.py b/pennylane/tape/tape.py
index 3c1585c00c9..a557370c5fa 100644
--- a/pennylane/tape/tape.py
+++ b/pennylane/tape/tape.py
@@ -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)()
+
+ # 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()
+
+ return new_tape
+
# ========================================================
# Parameter handling
# ========================================================
diff --git a/pennylane/templates/embeddings/amplitude.py b/pennylane/templates/embeddings/amplitude.py
index f85c18dae7c..4c750c8de74 100644
--- a/pennylane/templates/embeddings/amplitude.py
+++ b/pennylane/templates/embeddings/amplitude.py
@@ -143,6 +143,11 @@ def __init__(self, features, wires, pad_with=None, normalize=False, pad=None, do
features = self._preprocess(features, wires, pad_with, normalize)
super().__init__(features, wires=wires, do_queue=do_queue)
+ def adjoint(self): # pylint: disable=arguments-differ
+ return qml.adjoint(qml.templates.MottonenStatePreparation)(
+ self.parameters[0], wires=self.wires
+ )
+
def expand(self):
with qml.tape.QuantumTape() as tape:
diff --git a/pennylane/templates/layers/particle_conserving_u1.py b/pennylane/templates/layers/particle_conserving_u1.py
index 03008d6be01..097f1bfd5e9 100644
--- a/pennylane/templates/layers/particle_conserving_u1.py
+++ b/pennylane/templates/layers/particle_conserving_u1.py
@@ -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)
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
diff --git a/pennylane/templates/layers/particle_conserving_u2.py b/pennylane/templates/layers/particle_conserving_u2.py
index 940e0c32089..e0ac00a8a17 100644
--- a/pennylane/templates/layers/particle_conserving_u2.py
+++ b/pennylane/templates/layers/particle_conserving_u2.py
@@ -183,7 +183,7 @@ 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)
for l in range(self.n_layers):
diff --git a/pennylane/transforms/adjoint.py b/pennylane/transforms/adjoint.py
index ea8908c2b94..fbdb6e578f8 100644
--- a/pennylane/transforms/adjoint.py
+++ b/pennylane/transforms/adjoint.py
@@ -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.
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])
.. 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)()
return wrapper
diff --git a/pennylane/utils.py b/pennylane/utils.py
index a2b15e094f4..258b7f6bf06 100644
--- a/pennylane/utils.py
+++ b/pennylane/utils.py
@@ -305,8 +305,9 @@ def circuit2():
"Please use inv on the function including its arguments, as in inv(template(args))."
)
elif isinstance(operation_list, qml.tape.QuantumTape):
- operation_list.inv()
- return operation_list
+ new_tape = operation_list.adjoint()
+ return new_tape
+
elif not isinstance(operation_list, Iterable):
raise ValueError("The provided operation_list is not iterable.")
@@ -334,13 +335,13 @@ def circuit2():
# exist on the queuing context
pass
- with qml.tape.QuantumTape() as tape:
+ def qfunc():
for o in operation_list:
o.queue()
- if o.inverse:
- o.inv()
- tape.inv()
+ with qml.tape.QuantumTape() as tape:
+ qml.adjoint(qfunc)()
+
return tape
diff --git a/tests/devices/test_default_qubit.py b/tests/devices/test_default_qubit.py
index 2f83dd7df94..6db6fd81885 100644
--- a/tests/devices/test_default_qubit.py
+++ b/tests/devices/test_default_qubit.py
@@ -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]
expected = np.array([1.0, -1.0j]) / np.sqrt(2)
assert np.allclose(dev.state, expected, atol=tol, rtol=0)
diff --git a/tests/tape/test_tape.py b/tests/tape/test_tape.py
index d39f4b87f42..5b78ea4da9c 100644
--- a/tests/tape/test_tape.py
+++ b/tests/tape/test_tape.py
@@ -604,12 +604,10 @@ def test_inverse(self):
tape.inv()
# check that operation order is reversed
- assert tape.operations == [prep] + ops[::-1]
+ assert [o.name for o in tape.operations] == ["BasisState", "CNOT", "Rot", "RX"]
# check that operations are inverted
- assert ops[0].inverse
- assert not ops[1].inverse
- assert ops[2].inverse
+ assert np.allclose(tape.operations[2].parameters, -np.array(p[-1:0:-1]))
# check that parameter order has reversed
assert tape.get_parameters() == [init_state, p[1], p[2], p[3], p[0]]
@@ -636,7 +634,7 @@ def test_parameter_transforms(self):
tape.inv()
assert tape.trainable_params == {1, 2}
assert tape.get_parameters() == [p[0], p[1]]
- assert tape._ops == ops
+ assert [o.name for o in tape._ops] == ["RX", "Rot", "CNOT"]
class TestExpand:
diff --git a/tests/templates/test_layers/test_particle_conserving_u1.py b/tests/templates/test_layers/test_particle_conserving_u1.py
index a7d64976dcf..d65936b1836 100644
--- a/tests/templates/test_layers/test_particle_conserving_u1.py
+++ b/tests/templates/test_layers/test_particle_conserving_u1.py
@@ -84,7 +84,7 @@ def test_operations(self):
assert gate_count == len(queue)
# check initialization of the qubit register
- assert isinstance(queue[0], qml.BasisState)
+ assert isinstance(queue[0], qml.templates.BasisEmbedding)
# check all quantum operations
idx_CRot = 8
diff --git a/tests/templates/test_layers/test_particle_conserving_u2.py b/tests/templates/test_layers/test_particle_conserving_u2.py
index f741956a521..3a05f8e2d37 100644
--- a/tests/templates/test_layers/test_particle_conserving_u2.py
+++ b/tests/templates/test_layers/test_particle_conserving_u2.py
@@ -50,7 +50,7 @@ def test_operations(self, layers, qubits, init_state):
assert len(queue) == n_gates
# initialization
- assert isinstance(queue[0], qml.BasisState)
+ assert isinstance(queue[0], qml.templates.BasisEmbedding)
# order of gates
for op1, op2 in zip(queue[1:], exp_gates):
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 4e48a46018e..ab886069f21 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -463,8 +463,8 @@ def inverted_dummy_template_operations(wires):
ops = []
for wire in reversed(wires):
- ops.append(qml.RY(-1, wires=[wire]).inv())
- ops.append(qml.RX(1, wires=[wire]).inv())
+ ops.append(qml.RY(1, wires=[wire]))
+ ops.append(qml.RX(-1, wires=[wire]))
return ops
@@ -475,7 +475,7 @@ class TestInv:
def test_inversion_without_context(self):
"""Test that a sequence of operations is properly inverted."""
op_queue = [qml.PauliX(0), qml.PauliY(0).inv(), qml.PauliZ(0)]
- inv_queue = [qml.PauliZ(0).inv(), qml.PauliY(0), qml.PauliX(0).inv()]
+ inv_queue = [qml.PauliZ(0), qml.PauliY(0), qml.PauliX(0)]
inv_ops = pu.inv(op_queue).operations
@@ -527,9 +527,9 @@ def test_inversion_with_context(self):
inv_queue = [
qml.Hadamard(wires=[0]),
qml.CNOT(wires=[0, 1]),
- qml.RZ(3, wires=[0]).inv(),
- qml.RY(2, wires=[0]).inv(),
- qml.RX(1, wires=[0]).inv(),
+ qml.RZ(-3, wires=[0]),
+ qml.RY(-2, wires=[0]),
+ qml.RX(-1, wires=[0]),
qml.CNOT(wires=[0, 1]),
qml.Hadamard(wires=[0]),
]
@@ -554,9 +554,9 @@ def test_non_queued_inversion_with_context(self):
inv_queue = [
qml.Hadamard(wires=[0]),
qml.CNOT(wires=[0, 1]),
- qml.RZ(3, wires=[0]).inv(),
- qml.RY(2, wires=[0]).inv(),
- qml.RX(1, wires=[0]).inv(),
+ qml.RZ(-3, wires=[0]),
+ qml.RY(-2, wires=[0]),
+ qml.RX(-1, wires=[0]),
qml.CNOT(wires=[0, 1]),
qml.Hadamard(wires=[0]),
]
@@ -582,10 +582,10 @@ def test_mixed_inversion_with_context(self):
inv_queue = [
qml.Hadamard(wires=[0]),
qml.CNOT(wires=[0, 1]),
- qml.RY(2, wires=[0]).inv(),
- qml.PauliZ(0).inv(),
- qml.RX(1, wires=[0]).inv(),
- qml.PauliX(0).inv(),
+ qml.RY(-2, wires=[0]),
+ qml.PauliZ(0),
+ qml.RX(-1, wires=[0]),
+ qml.PauliX(0),
qml.CNOT(wires=[0, 1]),
qml.Hadamard(wires=[0]),
]
@@ -612,10 +612,10 @@ def test_mixed_inversion_with_nested_context(self):
inv_queue = [
qml.Hadamard(wires=[0]),
qml.CNOT(wires=[0, 1]),
- qml.RY(2, wires=[0]).inv(),
- qml.PauliZ(0).inv(),
- qml.RX(1, wires=[0]).inv(),
- qml.PauliX(0).inv(),
+ qml.RY(-2, wires=[0]),
+ qml.PauliZ(0),
+ qml.RX(-1, wires=[0]),
+ qml.PauliX(0),
qml.CNOT(wires=[0, 1]),
qml.Hadamard(wires=[0]),
]
@@ -669,9 +669,9 @@ def qfunc():
inv_queue = [
qml.Hadamard(wires=[0]),
qml.CNOT(wires=[0, 1]),
- qml.RZ(3, wires=[0]).inv(),
- qml.RY(2, wires=[0]).inv(),
- qml.RX(1, wires=[0]).inv(),
+ qml.RZ(-3, wires=[0]),
+ qml.RY(-2, wires=[0]),
+ qml.RX(-1, wires=[0]),
qml.CNOT(wires=[0, 1]),
qml.Hadamard(wires=[0]),
]
@@ -703,9 +703,9 @@ def qfunc():
inv_queue = [
qml.Hadamard(wires=[0]),
qml.CNOT(wires=[0, 1]),
- qml.RZ(3, wires=[0]).inv(),
- qml.RY(2, wires=[0]).inv(),
- qml.RX(1, wires=[0]).inv(),
+ qml.RZ(-3, wires=[0]),
+ qml.RY(-2, wires=[0]),
+ qml.RX(-1, wires=[0]),
qml.CNOT(wires=[0, 1]),
qml.Hadamard(wires=[0]),
]
@@ -737,10 +737,10 @@ def qfunc():
inv_queue = [
qml.Hadamard(wires=[0]),
qml.CNOT(wires=[0, 1]),
- qml.RY(2, wires=[0]).inv(),
- qml.PauliZ(0).inv(),
- qml.RX(1, wires=[0]).inv(),
- qml.PauliX(0).inv(),
+ qml.RY(-2, wires=[0]),
+ qml.PauliZ(0),
+ qml.RX(-1, wires=[0]),
+ qml.PauliX(0),
qml.CNOT(wires=[0, 1]),
qml.Hadamard(wires=[0]),
]
@@ -783,8 +783,8 @@ def qfunc():
def test_argument_wrapping(self):
"""Test that a single operation can be given to inv and is properly inverted."""
- op = qml.PauliX(0)
- exp_op = qml.PauliX(0).inv()
+ op = qml.RX(0.1, wires=0)
+ exp_op = qml.RX(-0.1, wires=0)
inv_ops = pu.inv(op).operations
diff --git a/tests/transforms/test_adjoint.py b/tests/transforms/test_adjoint.py
index 00156dc83f2..a111df0ba93 100644
--- a/tests/transforms/test_adjoint.py
+++ b/tests/transforms/test_adjoint.py
@@ -11,7 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-
+import pytest
import numpy as np
import pennylane as qml
@@ -85,3 +85,292 @@ def my_circuit():
np.testing.assert_allclose(
my_circuit(), np.array([-0.995707, 0.068644 + 6.209710e-02j]), atol=1e-6, rtol=1e-6
)
+
+
+test_functions = [
+ lambda fn, *args, **kwargs: adjoint(fn)(*args, **kwargs),
+ lambda fn, *args, **kwargs: qml.inv(fn(*args, **kwargs)),
+]
+
+
+@pytest.mark.parametrize("fn", test_functions)
+class TestTemplateIntegration:
+ """Test that templates work correctly with the adjoint transform"""
+
+ def test_angle_embedding(self, fn):
+ """Test that the adjoint correctly inverts angle embedding"""
+ dev = qml.device("default.qubit", wires=3)
+ template = qml.templates.AngleEmbedding
+
+ @qml.qnode(dev)
+ def circuit(weights):
+ template(features=weights, wires=[0, 1, 2])
+ fn(template, features=weights, wires=[0, 1, 2])
+ return qml.state()
+
+ weights = np.array([0.2, 0.5, 0.8])
+ res = circuit(weights)
+ assert len(np.nonzero(res)) == 1
+
+ def test_amplitude_embedding(self, fn):
+ """Test that the adjoint correctly inverts amplitude embedding"""
+ dev = qml.device("default.qubit", wires=3)
+ template = qml.templates.AmplitudeEmbedding
+
+ @qml.qnode(dev)
+ def circuit(weights):
+ template(features=weights, wires=[0, 1, 2])
+ fn(template, features=weights, wires=[0, 1, 2])
+ return qml.state()
+
+ weights = np.array([0.2, 0.5, 0.8, 0.6, 0.1, 0.6, 0.1, 0.5]) / np.sqrt(1.92)
+ res = circuit(weights)
+ assert len(np.nonzero(res)) == 1
+
+ def test_basis_embedding(self, fn):
+ """Test that the adjoint correctly inverts basis embedding"""
+ dev = qml.device("default.qubit", wires=3)
+ template = qml.templates.BasisEmbedding
+
+ @qml.qnode(dev)
+ def circuit(weights):
+ template(features=weights, wires=[0, 1, 2])
+ fn(template, features=weights, wires=[0, 1, 2])
+ return qml.state()
+
+ weights = np.array([1, 0, 1])
+ res = circuit(weights)
+ expected = np.zeros([2 ** 3])
+ expected[0] = 1.0
+ assert np.allclose(res, expected)
+
+ def test_displacement_embedding(self, fn):
+ """Test that the adjoint correctly inverts displacement embedding"""
+ dev = qml.device("default.gaussian", wires=3)
+ template = qml.templates.DisplacementEmbedding
+
+ @qml.qnode(dev)
+ def circuit(weights):
+ template(features=weights, wires=[0, 1, 2])
+ fn(template, features=weights, wires=[0, 1, 2])
+ return qml.expval(qml.NumberOperator(0))
+
+ weights = np.array([0.6, 0.2, 0.1])
+ res = circuit(weights)
+ assert np.allclose(res, 0.0)
+
+ def test_squeezing_embedding(self, fn):
+ """Test that the adjoint correctly inverts squeezing embedding"""
+ dev = qml.device("default.gaussian", wires=3)
+ template = qml.templates.SqueezingEmbedding
+
+ @qml.qnode(dev)
+ def circuit(weights):
+ template(features=weights, wires=[0, 1, 2])
+ fn(template, features=weights, wires=[0, 1, 2])
+ return qml.expval(qml.NumberOperator(0))
+
+ weights = np.array([0.6, 0.2, 0.1])
+ res = circuit(weights)
+ assert np.allclose(res, 0.0)
+
+ def test_qaoa_embedding(self, fn):
+ """Test that the adjoint correctly inverts qaoa embedding"""
+ dev = qml.device("default.qubit", wires=3)
+ template = qml.templates.QAOAEmbedding
+
+ @qml.qnode(dev)
+ def circuit(features, weights):
+ template(features=features, weights=weights, wires=[0, 1, 2])
+ fn(template, features=features, weights=weights, wires=[0, 1, 2])
+ return qml.state()
+
+ features = np.array([1.0, 2.0, 3.0])
+ weights = np.random.random(template.shape(2, 3))
+
+ res = circuit(features, weights)
+ expected = np.zeros([2 ** 3])
+ expected[0] = 1.0
+
+ assert np.allclose(res, expected)
+
+ def test_iqp_embedding(self, fn):
+ """Test that the adjoint correctly inverts iqp embedding"""
+ dev = qml.device("default.qubit", wires=3)
+ template = qml.templates.IQPEmbedding
+
+ @qml.qnode(dev)
+ def circuit(features):
+ template(features=features, wires=[0, 1, 2])
+ fn(template, features=features, wires=[0, 1, 2])
+ return qml.state()
+
+ features = np.array([1.0, 2.0, 3.0])
+ res = circuit(features)
+ expected = np.zeros([2 ** 3])
+ expected[0] = 1.0
+
+ assert np.allclose(res, expected)
+
+ @pytest.mark.parametrize(
+ "template",
+ [
+ qml.templates.BasicEntanglerLayers,
+ qml.templates.StronglyEntanglingLayers,
+ qml.templates.RandomLayers,
+ ],
+ )
+ def test_layers(self, fn, template):
+ """Test that the adjoint correctly inverts layers"""
+ dev = qml.device("default.qubit", wires=3)
+
+ @qml.qnode(dev)
+ def circuit(weights):
+ template(weights=weights, wires=[0, 1, 2])
+ fn(template, weights=weights, wires=[0, 1, 2])
+ return qml.state()
+
+ weights = np.random.random(template.shape(2, 3))
+ res = circuit(weights)
+ expected = np.zeros([2 ** 3])
+ expected[0] = 1.0
+
+ assert np.allclose(res, expected)
+
+ @pytest.mark.parametrize(
+ "template",
+ [
+ qml.templates.ParticleConservingU1,
+ qml.templates.ParticleConservingU2,
+ ],
+ )
+ def test_particle_conserving(self, fn, template):
+ """Test that the adjoint correctly inverts particle conserving layers"""
+ dev = qml.device("default.qubit", wires=3)
+ init_state = np.array([0, 1, 1])
+
+ @qml.qnode(dev)
+ def circuit(weights):
+ template(weights=weights, init_state=init_state, wires=[0, 1, 2])
+ fn(template, weights=weights, init_state=init_state, wires=[0, 1, 2])
+ return qml.state()
+
+ weights = np.random.random(template.shape(2, 3))
+ res = circuit(weights)
+ expected = np.zeros([2 ** 3])
+ expected[0] = 1.0
+
+ assert np.allclose(res, expected)
+
+ def test_simplified_two_design(self, fn):
+ """Test that the adjoint correctly inverts the simplified two design"""
+ dev = qml.device("default.qubit", wires=3)
+ template = qml.templates.SimplifiedTwoDesign
+
+ @qml.qnode(dev)
+ def circuit(data, weights):
+ template(initial_layer_weights=data, weights=weights, wires=[0, 1, 2])
+ fn(template, initial_layer_weights=data, weights=weights, wires=[0, 1, 2])
+ return qml.state()
+
+ weights = [np.random.random(s) for s in template.shape(2, 3)]
+ res = circuit(weights[0], *weights[1:])
+ expected = np.zeros([2 ** 3])
+ expected[0] = 1.0
+
+ assert np.allclose(res, expected)
+
+ def test_approx_time_evolution(self, fn):
+ """Test that the adjoint correctly inverts the approx time evolution"""
+ dev = qml.device("default.qubit", wires=3)
+ template = qml.templates.ApproxTimeEvolution
+
+ coeffs = [1, 1]
+ obs = [qml.PauliX(0), qml.PauliX(1)]
+ H = qml.Hamiltonian(coeffs, obs)
+
+ @qml.qnode(dev)
+ def circuit(t):
+ template(H, t, 1)
+ fn(template, H, t, 1)
+ return qml.state()
+
+ res = circuit(0.5)
+ expected = np.zeros([2 ** 3])
+ expected[0] = 1.0
+ assert np.allclose(res, expected)
+
+ def test_arbitrary_unitary(self, fn):
+ """Test that the adjoint correctly inverts the arbitrary unitary"""
+ dev = qml.device("default.qubit", wires=3)
+ template = qml.templates.ArbitraryUnitary
+
+ @qml.qnode(dev)
+ def circuit(weights):
+ template(weights=weights, wires=[0, 1, 2])
+ fn(template, weights=weights, wires=[0, 1, 2])
+ return qml.state()
+
+ weights = np.random.random(template.shape(3))
+ res = circuit(weights)
+ expected = np.zeros([2 ** 3])
+ expected[0] = 1.0
+
+ assert np.allclose(res, expected)
+
+ def test_single_excitation(self, fn):
+ """Test that the adjoint correctly inverts the single excitation unitary"""
+ dev = qml.device("default.qubit", wires=3)
+ template = qml.templates.SingleExcitationUnitary
+
+ @qml.qnode(dev)
+ def circuit(weights):
+ template(weight=weights, wires=[0, 1, 2])
+ fn(template, weight=weights, wires=[0, 1, 2])
+ return qml.state()
+
+ res = circuit(0.6)
+ expected = np.zeros([2 ** 3])
+ expected[0] = 1.0
+
+ assert np.allclose(res, expected)
+
+ def test_double_excitation(self, fn):
+ """Test that the adjoint correctly inverts the double excitation unitary"""
+ dev = qml.device("default.qubit", wires=4)
+ template = qml.templates.DoubleExcitationUnitary
+
+ @qml.qnode(dev)
+ def circuit(weights):
+ template(weight=weights, wires1=[0, 1], wires2=[2, 3])
+ fn(template, weight=weights, wires1=[0, 1], wires2=[2, 3])
+ return qml.state()
+
+ res = circuit(0.6)
+ expected = np.zeros([2 ** 4])
+ expected[0] = 1.0
+
+ assert np.allclose(res, expected)
+
+ def test_interferometer(self, fn):
+ """Test that the adjoint correctly inverts squeezing embedding"""
+ dev = qml.device("default.gaussian", wires=3)
+ template = qml.templates.Interferometer
+ r = 1.5
+
+ @qml.qnode(dev)
+ def circuit(weights):
+ qml.Squeezing(r, 0, wires=0)
+ qml.Squeezing(r, 0, wires=1)
+ qml.Squeezing(r, 0, wires=2)
+ template(*weights, wires=[0, 1, 2])
+ fn(template, *weights, wires=[0, 1, 2])
+ return qml.expval(qml.NumberOperator(0))
+
+ weights = [
+ np.random.random([3 * (3 - 1) // 2]),
+ np.random.random([3 * (3 - 1) // 2]),
+ np.random.random([3]),
+ ]
+ res = circuit(weights)
+ assert np.allclose(res, np.sinh(r) ** 2)