Repository: PennyLaneAI/pennylane
Pull request #4804 (2023-11-07 23:56:19)

Adding functionality `iterative_qpe`
Adding functionality `iterative_qpe`


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://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4804?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Comparison is base [(`2c7c0c1`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/2c7c0c19d8e6accbdc93d87350c9cd8426bc3f3d?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) 99.64% compared to head [(`c36d09a`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4804?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) 99.63%.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #4804      +/-   ##
==========================================
- Coverage   99.64%   99.63%   -0.01%     
==========================================
  Files         381      382       +1     
  Lines       34324    34078     -246     
==========================================
- Hits        34202    33955     -247     
- Misses        122      123       +1     
```


| [Files](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4804?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | Coverage Δ | |
|---|---|---|
| [pennylane/\_\_init\_\_.py](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4804?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL19faW5pdF9fLnB5) | `100.00% <ø> (ø)` | |
| [pennylane/ops/functions/\_\_init\_\_.py](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4804?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wcy9mdW5jdGlvbnMvX19pbml0X18ucHk=) | `100.00% <100.00%> (ø)` | |
| [pennylane/ops/functions/iterative\_qpe.py](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4804?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wcy9mdW5jdGlvbnMvaXRlcmF0aXZlX3FwZS5weQ==) | `100.00% <100.00%> (ø)` | |

... and [43 files with indirect coverage changes](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4804/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4804?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).

KetpuntoG: I've added test for `tensorflow` and `torch`.
It has been identified a bug, so the tests will not pass until it is solved:
This is the issue:

https://github.com/PennyLaneAI/pennylane/issues/4825

Jaybsoni: [sc-45848]

Patch:
diff --git a/doc/introduction/operations.rst b/doc/introduction/operations.rst
index 3e9e83a41..aebaaba86 100644
--- a/doc/introduction/operations.rst
+++ b/doc/introduction/operations.rst
@@ -60,6 +60,7 @@ Operator to Operator functions
     ~pennylane.map_wires
     ~pennylane.dot
     ~pennylane.evolve
+    ~pennylane.iterative_qpe
 
 These operator functions act on operators to produce new operators.
 
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index c424d8afe..c319b1b07 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -7,6 +7,36 @@
 * Approximate Quantum Fourier Transform (AQFT) is now available from `qml.AQFT`.
   [(#4656)](https://github.com/PennyLaneAI/pennylane/pull/4656)
 
+* Iterative Quantum Phase Estimation is now available from `qml.iterative_qpe`.
+  [(#4804)](https://github.com/PennyLaneAI/pennylane/pull/4804)
+
+  The subroutine can be used similarly to mid-circuit measurements: 
+
+  ```python
+  
+  import pennylane as qml
+  
+  dev = qml.device("default.qubit", shots = 5)
+
+  @qml.qnode(dev)
+  def circuit():
+
+    # Initial state
+    qml.PauliX(wires = [0])
+
+    # Iterative QPE
+    measurements = qml.iterative_qpe(qml.RZ(2., wires = [0]), ancilla = [1], iters = 3)
+
+    return [qml.sample(op = meas) for meas in measurements]
+  ```
+  
+  ```pycon
+  >>> print(circuit())
+  [array([0, 0, 0, 0, 0]), array([1, 0, 0, 0, 0]), array([0, 1, 1, 1, 1])]
+  ```
+  
+  The i-th element in the list refers to the 5 samples generated by the i-th measurement of the algorithm.
+
 <h3>Improvements 🛠</h3>
 
 * `default.qubit` no longer uses a dense matrix for `MultiControlledX` for more than 8 operation wires.
@@ -158,6 +188,7 @@
 
 This release contains contributions from (in alphabetical order):
 
+Guillermo Alonso,
 Amintor Dusko,
 Lillian Frederiksen,
 Emiliano Godinez Ramirez,
diff --git a/pennylane/__init__.py b/pennylane/__init__.py
index 610825baf..a501e523a 100644
--- a/pennylane/__init__.py
+++ b/pennylane/__init__.py
@@ -113,6 +113,7 @@ from pennylane.ops.functions import (
     map_wires,
     matrix,
     simplify,
+    iterative_qpe,
 )
 from pennylane.optimize import *
 from pennylane.vqe import ExpvalCost
diff --git a/pennylane/ops/functions/__init__.py b/pennylane/ops/functions/__init__.py
index 292e338ea..e879b1c36 100644
--- a/pennylane/ops/functions/__init__.py
+++ b/pennylane/ops/functions/__init__.py
@@ -32,6 +32,7 @@ This module contains functions that act on operators and tapes.
     ~map_wires
     ~matrix
     ~simplify
+    ~iterative_qpe
 
 """
 from .bind_new_parameters import bind_new_parameters
@@ -46,4 +47,5 @@ from .is_unitary import is_unitary
 from .map_wires import map_wires
 from .matrix import matrix
 from .simplify import simplify
+from .iterative_qpe import iterative_qpe
 from .assert_valid import assert_valid
diff --git a/pennylane/ops/functions/iterative_qpe.py b/pennylane/ops/functions/iterative_qpe.py
new file mode 100644
index 000000000..b0136ccaa
--- /dev/null
+++ b/pennylane/ops/functions/iterative_qpe.py
@@ -0,0 +1,84 @@
+# Copyright 2018-2023 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.
+"""
+This module contains the qml.iterative_qpe function.
+"""
+
+import numpy as np
+import pennylane as qml
+
+
+def iterative_qpe(base, ancilla, iters):
+    r"""Performs the `iterative quantum phase estimation <https://arxiv.org/pdf/quant-ph/0610214.pdf>`_ circuit.
+
+    Given a unitary :math:`U`, this function applies the circuit for iterative quantum phase
+    estimation and returns a list of mid-circuit measurements with qubit reset.
+
+    Args:
+      base (Operator): the phase estimation unitary, specified as an :class:`~.Operator`
+      ancilla (Union[Wires, int, str]): the wire to be used for the estimation
+      iters (int): the number of measurements to be performed
+
+    Returns:
+      list[MidMeasureMP]: the list of measurements performed
+
+    .. seealso:: :class:`~.QuantumPhaseEstimation`, :func:`~.measure`
+
+    **Example**
+
+    .. code-block:: python
+
+        dev = qml.device("default.qubit", shots = 5)
+
+        @qml.qnode(dev)
+        def circuit():
+
+          # Initial state
+          qml.PauliX(wires = [0])
+
+          # Iterative QPE
+          measurements = qml.iterative_qpe(qml.RZ(2., wires = [0]), ancilla = 1, iters = 3)
+
+          return [qml.sample(op = meas) for meas in measurements]
+
+    .. code-block:: pycon
+
+        >>> print(circuit())
+        [array([0, 0, 0, 0, 0]), array([1, 0, 0, 0, 0]), array([0, 1, 1, 1, 1])]
+
+    The output is an array of size ``(number of iterations, number of shots)``.
+
+    .. code-block:: pycon
+
+        >>> print(qml.draw(circuit)())
+
+        1: ──H─╭●────────────H──┤↗│  │0⟩──H─╭●────────────Rϕ(-1.57)──H──┤↗│  │0⟩──H─╭●────────────Rϕ(-1.57)──Rϕ(-0.79)──H──┤↗│  │0⟩─┤  Sample  Sample  Sample
+        0: ──X─╰RZ(2.00)⁴⋅⁰──────║──────────╰RZ(2.00)²⋅⁰──║──────────────║──────────╰RZ(2.00)¹⋅⁰──║──────────║──────────────────────┤
+                                 ╚════════════════════════╝══════════════║════════════════════════║══════════╝
+                                                                         ╚════════════════════════╝
+    """
+
+    measurements = []
+
+    for i in range(iters):
+        qml.Hadamard(wires=ancilla)
+        qml.ctrl(qml.pow(base, z=2 ** (iters - i - 1)), control=ancilla)
+
+        for ind, meas in enumerate(measurements):
+            qml.cond(meas, qml.PhaseShift)(-2.0 * np.pi / 2 ** (ind + 2), wires=ancilla)
+
+        qml.Hadamard(wires=ancilla)
+        measurements.insert(0, qml.measure(wires=ancilla, reset=True))
+
+    return measurements


Test patch:
diff --git a/tests/ops/functions/test_iterative_qpe.py b/tests/ops/functions/test_iterative_qpe.py
new file mode 100644
index 000000000..92b458852
--- /dev/null
+++ b/tests/ops/functions/test_iterative_qpe.py
@@ -0,0 +1,242 @@
+# Copyright 2018-2023 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.
+"""
+Unit tests for the iterative_qpe function
+"""
+import pytest
+
+import numpy as np
+import pennylane as qml
+
+
+class TestIQPE:
+    """Test to check that the iterative quantum phase estimation function works as expected."""
+
+    @pytest.mark.parametrize("phi", (1.0, 2.0, 3.0))
+    def test_compare_qpe(self, phi):
+        """Test to check that the results obtained are equivalent to those of QuantumPhaseEstimation"""
+
+        # TODO: When we have general statistics on measurements we can calculate it exactly with qml.probs
+        dev = qml.device("default.qubit", shots=10000000)
+
+        @qml.qnode(dev)
+        def circuit_iterative():
+            # Initial state
+            qml.PauliX(wires=[0])
+
+            # Iterative QPE
+            measurements = qml.iterative_qpe(qml.RZ(phi, wires=[0]), ancilla=[1], iters=3)
+
+            return [qml.sample(op=meas) for meas in measurements]
+
+        sample_list = np.array(circuit_iterative())
+        sample_list = sample_list.T
+        output = qml.probs().process_samples(np.array(sample_list), wire_order=[0, 1, 2])
+
+        @qml.qnode(dev)
+        def circuit_qpe():
+            # Initial state
+            qml.PauliX(wires=[0])
+
+            # Iterative QPE
+            qml.QuantumPhaseEstimation(qml.RZ(phi, wires=[0]), estimation_wires=[1, 2, 3])
+
+            return qml.probs(wires=[1, 2, 3])
+
+        assert np.allclose(np.round(output, 2), np.round(circuit_qpe(), 2))
+
+    @pytest.mark.jax
+    def test_check_gradients_jax(self):
+        """Test to check that the gradients are correct comparing with the expanded circuit using JAX"""
+
+        import jax
+
+        dev = qml.device("default.qubit")
+
+        @qml.qnode(dev)
+        def circuit(theta):
+            meas = qml.iterative_qpe(qml.RZ(theta, wires=[0]), [1], iters=2)
+            return qml.expval(meas[0])
+
+        @qml.qnode(dev)
+        def manual_circuit(phi):
+            qml.Hadamard(wires=[1])
+            qml.ctrl(qml.RZ(phi, wires=[0]) ** 2, control=[1])
+            qml.Hadamard(wires=[1])
+            qml.CNOT(wires=[1, 2])
+            qml.CNOT(wires=[2, 1])
+            qml.Hadamard(wires=[1])
+            qml.ctrl(qml.RZ(phi, wires=[0]), control=[1])
+            qml.ctrl(qml.PhaseShift(-np.pi / 2, wires=[1]), control=[2])
+            qml.Hadamard(wires=[1])
+            qml.CNOT(wires=[1, 3])
+            qml.CNOT(wires=[3, 1])
+
+            return qml.expval(qml.Hermitian([[0, 0], [0, 1]], wires=3))
+
+        phi = jax.numpy.array(1.0)
+        assert jax.numpy.isclose(jax.grad(circuit)(phi), jax.grad(manual_circuit)(phi))
+
+    @pytest.mark.torch
+    def test_check_gradients_torch(self):
+        """Test to check that the gradients are correct comparing with the expanded circuit using PyTorch"""
+
+        import torch
+
+        dev = qml.device("default.qubit")
+
+        @qml.qnode(dev)
+        def circuit(theta):
+            meas = qml.iterative_qpe(qml.RZ(theta, wires=[0]), [1], iters=2)
+            return qml.expval(meas[0])
+
+        @qml.qnode(dev)
+        def manual_circuit(phi):
+            qml.Hadamard(wires=[1])
+            qml.ctrl(qml.RZ(phi, wires=[0]) ** 2, control=[1])
+            qml.Hadamard(wires=[1])
+            qml.CNOT(wires=[1, 2])
+            qml.CNOT(wires=[2, 1])
+            qml.Hadamard(wires=[1])
+            qml.ctrl(qml.RZ(phi, wires=[0]), control=[1])
+            qml.ctrl(qml.PhaseShift(-np.pi / 2, wires=[1]), control=[2])
+            qml.Hadamard(wires=[1])
+            qml.CNOT(wires=[1, 3])
+            qml.CNOT(wires=[3, 1])
+
+            return qml.expval(qml.Hermitian([[0, 0], [0, 1]], wires=3))
+
+        phi = torch.tensor(1.0, requires_grad=True)
+        assert torch.isclose(torch.func.grad(circuit)(phi), torch.func.grad(manual_circuit)(phi))
+
+    @pytest.mark.xfail(reason="See https://github.com/PennyLaneAI/pennylane/issues/4825")
+    @pytest.mark.tf
+    def test_check_gradients_tf(self):
+        """Test to check that the gradients are correct comparing with the expanded circuit using TensorFlow"""
+
+        import tensorflow as tf
+
+        def grad(f):
+            def wrapper(x):
+                with tf.GradientTape() as tape:
+                    y = f(x)
+
+                return tape.gradient(y, x)
+
+            return wrapper
+
+        dev = qml.device("default.qubit")
+
+        @qml.qnode(dev)
+        def circuit(theta):
+            meas = qml.iterative_qpe(qml.RZ(theta, wires=[0]), [1], iters=2)
+            return qml.expval(meas[0])
+
+        @qml.qnode(dev)
+        def manual_circuit(phi):
+            qml.Hadamard(wires=[1])
+            qml.ctrl(qml.RZ(phi, wires=[0]) ** 2, control=[1])
+            qml.Hadamard(wires=[1])
+            qml.CNOT(wires=[1, 2])
+            qml.CNOT(wires=[2, 1])
+            qml.Hadamard(wires=[1])
+            qml.ctrl(qml.RZ(phi, wires=[0]), control=[1])
+            qml.ctrl(qml.PhaseShift(-np.pi / 2, wires=[1]), control=[2])
+            qml.Hadamard(wires=[1])
+            qml.CNOT(wires=[1, 3])
+            qml.CNOT(wires=[3, 1])
+
+            return qml.expval(qml.Hermitian([[0, 0], [0, 1]], wires=3))
+
+        phi = tf.Variable(1.0)
+        assert np.isclose(grad(circuit)(phi), grad(manual_circuit)(phi))
+
+    @pytest.mark.parametrize("iters", (1, 2, 3, 4))
+    def test_size_return(self, iters):
+        """Test to check that the size of the returned list is correct"""
+
+        dev = qml.device("default.qubit", shots=1)
+
+        @qml.qnode(dev)
+        def circuit():
+            m = qml.iterative_qpe(qml.RZ(1.0, wires=[0]), [1], iters=iters)
+            return [qml.sample(op=meas) for meas in m]
+
+        assert len(circuit()) == iters
+
+    @pytest.mark.parametrize("wire", (1, "a", "abc", 6))
+    def test_wires_args(self, wire):
+        """Test to check that all types of wires are working"""
+
+        with qml.tape.QuantumTape() as tape:
+            qml.iterative_qpe(qml.RZ(1.0, wires=[0]), wire, iters=3)
+
+        assert wire in tape.wires
+
+    @pytest.mark.parametrize("phi", (1.2, 2.3, 3.4))
+    def test_measurement_processes_probs(self, phi):
+        """Test to check that the measurement process prob works correctly"""
+
+        dev = qml.device("default.qubit")
+
+        @qml.qnode(dev)
+        def circuit_qpe():
+            # Initial state
+            qml.PauliX(wires=[0])
+
+            # Iterative QPE
+            qml.QuantumPhaseEstimation(qml.RZ(phi, wires=[0]), estimation_wires=[1, 2, 3])
+
+            return [qml.probs(wires=i) for i in [1, 2, 3]]
+
+        @qml.qnode(dev)
+        def circuit_iterative():
+            # Initial state
+            qml.PauliX(wires=[0])
+
+            # Iterative QPE
+            measurements = qml.iterative_qpe(qml.RZ(phi, wires=[0]), ancilla=[1], iters=3)
+
+            return [qml.probs(op=i) for i in measurements]
+
+        assert np.allclose(circuit_qpe(), circuit_iterative())
+
+    @pytest.mark.parametrize("phi", (1.2, 2.3, 3.4))
+    def test_measurement_processes_expval(self, phi):
+        """Test to check that the measurement process expval works correctly"""
+
+        dev = qml.device("default.qubit")
+
+        @qml.qnode(dev)
+        def circuit_qpe():
+            # Initial state
+            qml.PauliX(wires=[0])
+
+            # Iterative QPE
+            qml.QuantumPhaseEstimation(qml.RZ(phi, wires=[0]), estimation_wires=[1, 2, 3])
+
+            # We will use the projector as an observable
+            return [qml.expval(qml.Hermitian([[0, 0], [0, 1]], wires=i)) for i in [1, 2, 3]]
+
+        @qml.qnode(dev)
+        def circuit_iterative():
+            # Initial state
+            qml.PauliX(wires=[0])
+
+            # Iterative QPE
+            measurements = qml.iterative_qpe(qml.RZ(phi, wires=[0]), ancilla=[1], iters=3)
+
+            return [qml.expval(op=i) for i in measurements]
+
+        assert np.allclose(circuit_qpe(), circuit_iterative())