Skip to content

Commit

Permalink
Merge branch 'master' into add-pytest-benchmarks-CI
Browse files Browse the repository at this point in the history
  • Loading branch information
AmintorDusko authored Nov 22, 2023
2 parents 1babfb8 + c025b98 commit 083b2a9
Show file tree
Hide file tree
Showing 14 changed files with 481 additions and 38 deletions.
1 change: 1 addition & 0 deletions doc/introduction/operations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
37 changes: 37 additions & 0 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,41 @@
* 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` now can evolve already batched states with `ParametrizedEvolution`
[(#4863)](https://github.com/PennyLaneAI/pennylane/pull/4863)

* `default.qubit` no longer uses a dense matrix for `MultiControlledX` for more than 8 operation wires.
[(#4673)](https://github.com/PennyLaneAI/pennylane/pull/4673)

Expand Down Expand Up @@ -49,6 +82,9 @@
* Simplified the logic for re-arranging states before returning.
[(#4817)](https://github.com/PennyLaneAI/pennylane/pull/4817)

* `GlobalPhase` now decomposes to nothing, in case devices do not support global phases.
[(#4855)](https://github.com/PennyLaneAI/pennylane/pull/4855)

<h3>Breaking changes 💔</h3>

* Moved `qml.cond` and the `Conditional` operation from the `transforms` folder to the `ops/op_math` folder.
Expand Down Expand Up @@ -158,6 +194,7 @@

This release contains contributions from (in alphabetical order):

Guillermo Alonso,
Amintor Dusko,
Lillian Frederiksen,
Emiliano Godinez Ramirez,
Expand Down
1 change: 1 addition & 0 deletions pennylane/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@
map_wires,
matrix,
simplify,
iterative_qpe,
)
from pennylane.optimize import *
from pennylane.vqe import ExpvalCost
Expand Down
39 changes: 25 additions & 14 deletions pennylane/devices/qubit/apply_operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,23 +334,26 @@ def apply_parametrized_evolution(
):
"""Apply ParametrizedEvolution by evolving the state rather than the operator matrix
if we are operating on more than half of the subsystem"""
if is_state_batched and op.batch_size is None:
raise RuntimeError(
"ParameterizedEvolution does not support standard broadcasting, but received a batched state"
)

# shape(state) is static (not a tracer), we can use an if statement
num_wires = len(qml.math.shape(state))
num_wires = len(qml.math.shape(state)) - is_state_batched
state = qml.math.cast(state, complex)
if 2 * len(op.wires) > num_wires and not op.hyperparameters["complementary"]:
# the subsystem operated is more than half of the system based on the state vector --> evolve state
return _evolve_state_vector_under_parametrized_evolution(op, state, num_wires)
# otherwise --> evolve matrix
return _apply_operation_default(op, state, is_state_batched, debugger)
if (
2 * len(op.wires) <= num_wires
or op.hyperparameters["complementary"]
or (is_state_batched and op.hyperparameters["return_intermediate"])
):
# the subsystem operated on is half as big as the total system, or less
# or we want complementary time evolution
# or both the state and the operation have a batch dimension
# --> evolve matrix
return _apply_operation_default(op, state, is_state_batched, debugger)
# otherwise --> evolve state
return _evolve_state_vector_under_parametrized_evolution(op, state, num_wires, is_state_batched)


def _evolve_state_vector_under_parametrized_evolution(
operation: qml.pulse.ParametrizedEvolution, state, num_wires
operation: qml.pulse.ParametrizedEvolution, state, num_wires, is_state_batched
):
"""Uses an odeint solver to compute the evolution of the input ``state`` under the given
``ParametrizedEvolution`` operation.
Expand Down Expand Up @@ -385,7 +388,13 @@ def _evolve_state_vector_under_parametrized_evolution(
"You can update these values by calling the ParametrizedEvolution class: EV(params, t)."
)

state = state.flatten()
if is_state_batched:
batch_dim = state.shape[0]
state = qml.math.moveaxis(state.reshape((batch_dim, 2**num_wires)), 1, 0)
out_shape = [2] * num_wires + [batch_dim] # this shape is before moving the batch_dim back
else:
state = state.flatten()
out_shape = [2] * num_wires

with jax.ensure_compile_time_eval():
H_jax = ParametrizedHamiltonianPytree.from_hamiltonian( # pragma: no cover
Expand All @@ -399,7 +408,9 @@ def fun(y, t):
return (-1j * H_jax(operation.data, t=t)) @ y

result = odeint(fun, state, operation.t, **operation.odeint_kwargs)
out_shape = [2] * num_wires
if operation.hyperparameters["return_intermediate"]:
return qml.math.reshape(result, [-1] + out_shape)
return qml.math.reshape(result[-1], out_shape)
result = qml.math.reshape(result[-1], out_shape)
if is_state_batched:
return qml.math.moveaxis(result, -1, 0)
return result
2 changes: 2 additions & 0 deletions pennylane/ops/functions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
~map_wires
~matrix
~simplify
~iterative_qpe
"""
from .bind_new_parameters import bind_new_parameters
Expand All @@ -46,4 +47,5 @@
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
84 changes: 84 additions & 0 deletions pennylane/ops/functions/iterative_qpe.py
Original file line number Diff line number Diff line change
@@ -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
32 changes: 32 additions & 0 deletions pennylane/ops/identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,38 @@ def compute_diagonalizing_gates(
"""
return []

@staticmethod
def compute_decomposition(phi, wires=None): # pylint:disable=arguments-differ,unused-argument
r"""Representation of the operator as a product of other operators (static method).
.. note::
The ``GlobalPhase`` operation decomposes to an empty list of operations.
Support for global phase
was added in v0.33 and was ignored in earlier versions of PennyLane. Setting
global phase to decompose to nothing allows existing devices to maintain
current support for operations which now have ``GlobalPhase`` in the
decomposition pipeline.
.. math:: O = O_1 O_2 \dots O_n.
.. seealso:: :meth:`~.GlobalPhase.decomposition`.
Args:
phi (TensorLike): the global phase
wires (Iterable[Any] or Any): unused argument - the operator is applied to all wires
Returns:
list[Operator]: decomposition into lower level operations
**Example:**
>>> qml.GlobalPhase.compute_decomposition(1.23)
[]
"""
return []

def matrix(self, wire_order=None):
n_wires = len(wire_order) if wire_order else len(self.wires)
return self.compute_matrix(self.data[0], n_wires=n_wires)
Expand Down
7 changes: 7 additions & 0 deletions pennylane/ops/op_math/controlled.py
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,13 @@ def _decompose_no_control_values(op: "operation.Operator") -> List["operation.Op
return None

base_decomp = op.base.decomposition()
if len(base_decomp) == 0 and isinstance(op.base, qml.GlobalPhase):
warnings.warn(
"Controlled-GlobalPhase currently decomposes to nothing, and this will likely "
"produce incorrect results. Consider implementing your circuit with a different set "
"of operations, or use a device that natively supports GlobalPhase.",
UserWarning,
)

return [Controlled(newop, op.control_wires, work_wires=op.work_wires) for newop in base_decomp]

Expand Down
Loading

0 comments on commit 083b2a9

Please sign in to comment.