Repository: PennyLaneAI/pennylane
Pull request #1933 (2021-11-24 11:46:02)

Multi amplitude embedding with transform
**Context:** In certain situations it may be convenient to be able to use more than one `AmplitudeEmbedding` in the same circuit. A first approximation has been made but it has been decided to use circuit transformations to merge these gates. [(#1890)](https://github.com/PennyLaneAI/pennylane/pull/1890)

**Description of the Change:** A `merge_amplitude_embedding` transformation has been created, which takes a quantum function and returns it transformed by joining all gates of this type together.

**Example:**

```python
import pennylane as qml
from pennylane.transforms import merge_amplitude_embedding

@merge_amplitude_embedding
def qfunc():
    
    qml.AmplitudeEmbedding([0,1,0,0], wires = [0,1])
    qml.AmplitudeEmbedding([0,1], wires = 2)
    
    return qml.expval(qml.PauliZ(wires = 0))

dev = qml.device("default.qubit", wires = 3)
qnode = qml.QNode(qfunc, dev)
print(qml.draw(qnode)())
```

 0: ──╭AmplitudeEmbedding(M0)──┤ ⟨Z⟩ 
 1: ──├AmplitudeEmbedding(M0)──┤     
 2: ──╰AmplitudeEmbedding(M0)──┤     
M0 =
[0.+0.j 0.+0.j 0.+0.j 1.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j]

**Benefits:**

Convenience when programming some routines such as the calculation of the inner product through the swap test.

**Possible Drawbacks:**

**Related GitHub Issues:**
[(#1140)](https://github.com/PennyLaneAI/pennylane/issues/1140)


github-actions[bot]: Hello. You may have forgotten to update the changelog!
Please edit [`doc/releases/changelog-dev.md`](/PennyLaneAI/pennylane/blob/master/doc/releases/changelog-dev.md) with:
* A one-to-two sentence description of the change. You may include a small working example for new features.
* A link back to this PR.
* Your name (or GitHub username) in the contributors section.
codecov[bot]: # [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1933?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
> Merging [#1933](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1933?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (1420b98) into [master](https://codecov.io/gh/PennyLaneAI/pennylane/commit/d705f0e3cc51bd876c8b910cd25b85bfc08694b0?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (d705f0e) will **increase** coverage by `0.00%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1933/graphs/tree.svg?width=650&height=150&src=pr&token=9S3YzlUgGk&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1933?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)

```diff
@@           Coverage Diff           @@
##           master    #1933   +/-   ##
=======================================
  Coverage   98.84%   98.84%           
=======================================
  Files         224      225    +1     
  Lines       17101    17136   +35     
=======================================
+ Hits        16903    16938   +35     
  Misses        198      198           
```


| [Impacted Files](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1933?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | Coverage Δ | |
|---|---|---|
| [pennylane/transforms/\_\_init\_\_.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1933/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3RyYW5zZm9ybXMvX19pbml0X18ucHk=) | `100.00% <ø> (ø)` | |
| [pennylane/transforms/optimization/\_\_init\_\_.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1933/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3RyYW5zZm9ybXMvb3B0aW1pemF0aW9uL19faW5pdF9fLnB5) | `100.00% <100.00%> (ø)` | |
| [...ansforms/optimization/merge\_amplitude\_embedding.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1933/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3RyYW5zZm9ybXMvb3B0aW1pemF0aW9uL21lcmdlX2FtcGxpdHVkZV9lbWJlZGRpbmcucHk=) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1933?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1933?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI). Last update [d705f0e...1420b98](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1933?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).

KetpuntoG: [sc-11240]

Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index b056245d9..06994237a 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -281,6 +281,9 @@
 
 * Added density matrix initialization gate for mixed state simulation. [(#1686)](https://github.com/PennyLaneAI/pennylane/issues/1686)
 
+* The `merge_amplitude_embedding` transformation has been created to automatically merge all gates of this type into one.
+  [(#1933)](https://github.com/PennyLaneAI/pennylane/pull/1933)
+
 <h3>Improvements</h3>
 
 * Tests do not loop over automatically imported and instantiated operations any more,
diff --git a/pennylane/transforms/__init__.py b/pennylane/transforms/__init__.py
index e930a7e22..b3908df52 100644
--- a/pennylane/transforms/__init__.py
+++ b/pennylane/transforms/__init__.py
@@ -129,6 +129,8 @@ from .optimization import (
     commute_controlled,
     merge_rotations,
     single_qubit_fusion,
+    merge_amplitude_embedding,
+    remove_barrier,
 )
 from .specs import specs
 from .qmc import apply_controlled_Q, quantum_monte_carlo
diff --git a/pennylane/transforms/optimization/__init__.py b/pennylane/transforms/optimization/__init__.py
index 434f46fd8..d1d5a93be 100644
--- a/pennylane/transforms/optimization/__init__.py
+++ b/pennylane/transforms/optimization/__init__.py
@@ -19,4 +19,5 @@ from .remove_barrier import remove_barrier
 from .cancel_inverses import cancel_inverses
 from .commute_controlled import commute_controlled
 from .merge_rotations import merge_rotations
+from .merge_amplitude_embedding import merge_amplitude_embedding
 from .single_qubit_fusion import single_qubit_fusion
diff --git a/pennylane/transforms/optimization/merge_amplitude_embedding.py b/pennylane/transforms/optimization/merge_amplitude_embedding.py
new file mode 100644
index 000000000..1f2b84ecb
--- /dev/null
+++ b/pennylane/transforms/optimization/merge_amplitude_embedding.py
@@ -0,0 +1,103 @@
+# Copyright 2018-2021 Xanadu Quantum Technologies Inc.
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+#     http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# 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.
+"""Transform for merging AmplitudeEmbedding gates in a quantum circuit."""
+from pennylane import apply
+from pennylane.transforms import qfunc_transform
+
+from pennylane import AmplitudeEmbedding
+from pennylane._device import DeviceError
+from pennylane.math import kron
+
+
+@qfunc_transform
+def merge_amplitude_embedding(tape):
+    r"""Quantum function transform to combine amplitude embedding templates that act on different qubits.
+
+    Args:
+        qfunc (function): A quantum function.
+
+    Returns:
+        function: the transformed quantum function
+
+    **Example**
+
+    Consider the following quantum function.
+
+    .. code-block:: python
+
+        def qfunc():
+            qml.CNOT(wires = [0,1])
+            qml.AmplitudeEmbedding([0,1], wires = 2)
+            qml.AmplitudeEmbedding([0,1], wires = 3)
+            return qml.state()
+
+    The circuit before compilation will not work because of using two amplitude embedding.
+
+    Using the transformation we can join the different amplitude embedding into a single one:
+
+    >>> dev = qml.device('default.qubit', wires=4)
+    >>> optimized_qfunc = qml.transforms.merge_amplitude_embedding(qfunc)
+    >>> optimized_qnode = qml.QNode(optimized_qfunc, dev)
+    >>> print(qml.draw(optimized_qnode)())
+
+      0: ──╭C───────────────────────╭┤ State
+      1: ──╰X───────────────────────├┤ State
+      2: ──╭AmplitudeEmbedding(M0)──├┤ State
+      3: ──╰AmplitudeEmbedding(M0)──╰┤ State
+      M0 = [0.+0.j 0.+0.j 0.+0.j 1.+0.j]
+
+    """
+    # Make a working copy of the list to traverse
+    list_copy = tape.operations.copy()
+    not_amplitude_embedding = []
+    visited_wires = set()
+    input_wires, input_vectors = [], []
+    while len(list_copy) > 0:
+        current_gate = list_copy[0]
+        wires_set = set(current_gate.wires)
+
+        # Check if the current gate is an AmplitudeEmbedding.
+        if not isinstance(current_gate, AmplitudeEmbedding):
+            not_amplitude_embedding.append(current_gate)
+            list_copy.pop(0)
+            visited_wires = visited_wires.union(wires_set)
+            continue
+
+        # Check the qubits have not been used.
+        if len(visited_wires.intersection(wires_set)) > 0:
+            raise DeviceError(
+                f"Operation {current_gate.name} cannot be used after other Operation applied in the same qubit "
+            )
+        input_wires.append(current_gate.wires)
+        input_vectors.append(current_gate.parameters[0])
+        list_copy.pop(0)
+        visited_wires = visited_wires.union(wires_set)
+
+    if len(input_wires) > 0:
+        final_wires = input_wires[0]
+        final_vector = input_vectors[0]
+
+        # Merge all parameters and qubits into a single one.
+        for w, v in zip(input_wires[1:], input_vectors[1:]):
+            final_vector = kron(final_vector, v)
+            final_wires = final_wires + w
+
+        AmplitudeEmbedding(final_vector, wires=final_wires)
+
+    for gate in not_amplitude_embedding:
+        apply(gate)
+
+    # Queue the measurements normally
+    for m in tape.measurements:
+        apply(m)


Test patch:
diff --git a/tests/transforms/test_optimization/test_merge_amplitude_embedding.py b/tests/transforms/test_optimization/test_merge_amplitude_embedding.py
new file mode 100644
index 000000000..139e4f0d4
--- /dev/null
+++ b/tests/transforms/test_optimization/test_merge_amplitude_embedding.py
@@ -0,0 +1,149 @@
+# Copyright 2018-2021 Xanadu Quantum Technologies Inc.
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+#     http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# 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
+from pennylane import numpy as np
+
+import pennylane as qml
+from pennylane.transforms.optimization import merge_amplitude_embedding
+from pennylane._device import DeviceError
+
+
+class TestMergeAmplitudeEmbedding:
+    """Test that amplitude embedding gates are combined into a single."""
+
+    def test_multi_amplitude_embedding(self):
+        """Test that the transformation is working correctly by joining two AmplitudeEmbedding."""
+
+        def qfunc():
+            qml.AmplitudeEmbedding([0.0, 1.0], wires=0)
+            qml.AmplitudeEmbedding([0.0, 1.0], wires=1)
+            qml.Hadamard(wires=0)
+            qml.Hadamard(wires=0)
+            qml.state()
+
+        transformed_qfunc = merge_amplitude_embedding(qfunc)
+        ops = qml.transforms.make_tape(transformed_qfunc)().operations
+
+        assert len(ops) == 3
+
+        # Check that the solution is as expected.
+        dev = qml.device("default.qubit", wires=2)
+        assert np.allclose(qml.QNode(transformed_qfunc, dev)()[-1], 1)
+
+    def test_repeated_qubit(self):
+        """Check that AmplitudeEmbedding cannot be applied if the qubit has already been used."""
+
+        def qfunc():
+            qml.CNOT(wires=[0.0, 1.0])
+            qml.AmplitudeEmbedding([0.0, 1.0], wires=1)
+
+        transformed_qfunc = merge_amplitude_embedding(qfunc)
+
+        dev = qml.device("default.qubit", wires=2)
+        qnode = qml.QNode(transformed_qfunc, dev)
+
+        with pytest.raises(DeviceError, match="applied in the same qubit"):
+            qnode()
+
+    def test_decorator(self):
+        """Check that the decorator works."""
+
+        @merge_amplitude_embedding
+        def qfunc():
+            qml.AmplitudeEmbedding([0, 1, 0, 0], wires=[0, 1])
+            qml.AmplitudeEmbedding([0, 1], wires=2)
+
+            return qml.state()
+
+        dev = qml.device("default.qubit", wires=3)
+        qnode = qml.QNode(qfunc, dev)
+        assert qnode()[3] == 1.0
+
+
+class TestMergeAmplitudeEmbeddingInterfaces:
+    """Test that merging amplitude embedding operations works in all interfaces."""
+
+    def test_merge_amplitude_embedding_autograd(self):
+        """Test QNode in autograd interface."""
+
+        def qfunc(amplitude):
+            qml.AmplitudeEmbedding(amplitude, wires=0)
+            qml.AmplitudeEmbedding(amplitude, wires=1)
+            return qml.state()
+
+        dev = qml.device("default.qubit", wires=2)
+        optimized_qfunc = qml.transforms.merge_amplitude_embedding(qfunc)
+        optimized_qnode = qml.QNode(optimized_qfunc, dev)
+
+        amplitude = np.array([0.0, 1.0], requires_grad=True)
+        # Check the state |11> is being generated.
+        assert optimized_qnode(amplitude)[-1] == 1
+
+    def test_merge_amplitude_embedding_torch(self):
+        """Test QNode in torch interface."""
+        torch = pytest.importorskip("torch", minversion="1.8")
+
+        def qfunc(amplitude):
+            qml.AmplitudeEmbedding(amplitude, wires=0)
+            qml.AmplitudeEmbedding(amplitude, wires=1)
+            return qml.state()
+
+        dev = qml.device("default.qubit", wires=2)
+        optimized_qfunc = qml.transforms.merge_amplitude_embedding(qfunc)
+        optimized_qnode = qml.QNode(optimized_qfunc, dev, interface="torch")
+
+        amplitude = torch.tensor([0.0, 1.0], requires_grad=True)
+        # Check the state |11> is being generated.
+        assert optimized_qnode(amplitude)[-1] == 1
+
+    def test_merge_amplitude_embedding_tf(self):
+        """Test QNode in tensorflow interface."""
+        tf = pytest.importorskip("tensorflow")
+
+        def qfunc(amplitude):
+            qml.AmplitudeEmbedding(amplitude, wires=0)
+            qml.AmplitudeEmbedding(amplitude, wires=1)
+            return qml.state()
+
+        dev = qml.device("default.qubit", wires=2)
+        optimized_qfunc = qml.transforms.merge_amplitude_embedding(qfunc)
+        optimized_qnode = qml.QNode(optimized_qfunc, dev, interface="tf")
+
+        amplitude = tf.Variable([0.0, 1.0])
+        # Check the state |11> is being generated.
+        assert optimized_qnode(amplitude)[-1] == 1
+
+    def test_merge_amplitude_embedding_jax(self):
+        """Test QNode in JAX interface."""
+        jax = pytest.importorskip("jax")
+        from jax import numpy as jnp
+
+        from jax.config import config
+
+        remember = config.read("jax_enable_x64")
+        config.update("jax_enable_x64", True)
+
+        def qfunc(amplitude):
+            qml.AmplitudeEmbedding(amplitude, wires=0)
+            qml.AmplitudeEmbedding(amplitude, wires=1)
+            return qml.state()
+
+        dev = qml.device("default.qubit", wires=2)
+        optimized_qfunc = qml.transforms.merge_amplitude_embedding(qfunc)
+        optimized_qnode = qml.QNode(optimized_qfunc, dev, interface="jax")
+
+        amplitude = jnp.array([0.0, 1.0], dtype=jnp.float64)
+        # Check the state |11> is being generated.
+        assert optimized_qnode(amplitude)[-1] == 1