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)