Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change default value of from_qasm to keep the measurements #5982

Merged
merged 21 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
e844f15
change default value of measurements in from_qasm, improve documentat…
EmilianoG-byte Jul 10, 2024
49a41b7
add entries to deprecations and changelog
EmilianoG-byte Jul 10, 2024
a3e3a71
add additional explanation to from_qasm docstring
EmilianoG-byte Jul 11, 2024
e253349
Merge branch 'master' into removals-0.38
EmilianoG-byte Jul 11, 2024
b363d33
Apply suggestions from code review
EmilianoG-byte Jul 12, 2024
5a22a80
create separate sections for examples in from_qasm
EmilianoG-byte Jul 12, 2024
a0bb593
fix sections separation
EmilianoG-byte Jul 12, 2024
e0ffff3
Merge branch 'master' into removals-0.38
EmilianoG-byte Jul 12, 2024
655cc8c
fix sphinx reference problem
EmilianoG-byte Jul 12, 2024
cf04f89
fix drawing
EmilianoG-byte Jul 12, 2024
cecd59b
fix identation, add example, and fix qasm code
EmilianoG-byte Jul 15, 2024
5f41895
add intentation to example
EmilianoG-byte Jul 15, 2024
517bd1a
Merge branch 'master' into removals-0.38
EmilianoG-byte Jul 15, 2024
d6963f6
add back backslashes on qasm example code
EmilianoG-byte Jul 15, 2024
3922eb0
erase import temporarily to make sphinx work
EmilianoG-byte Jul 15, 2024
0b498a4
bring back io module
EmilianoG-byte Jul 16, 2024
07dd414
Update pennylane/io.py
EmilianoG-byte Jul 16, 2024
99b64e3
Merge branch 'master' into removals-0.38
EmilianoG-byte Jul 16, 2024
da0f4fe
Merge branch 'master' into removals-0.38
EmilianoG-byte Jul 17, 2024
8831b10
Merge branch 'master' into removals-0.38
EmilianoG-byte Jul 17, 2024
eede54e
Merge branch 'master' into removals-0.38
EmilianoG-byte Jul 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 7 additions & 8 deletions doc/development/deprecations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,6 @@ Pending deprecations
- Deprecated in v0.37
- Will be removed in v0.39

* ``qml.from_qasm`` will no longer remove measurements from the QASM code. Calling ``qml.from_qasm``
on a circuit containing measurements without specifying ``measurements`` will raise a deprecation
warning in v0.37, and in v0.38, the default behaviour will be changed to keeping measurements. Use
``measurements=[]`` to remove measurements from the original circuit.

- Deprecated in v0.37
- Default behaviour will be changed in v0.38

New operator arithmetic deprecations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down Expand Up @@ -67,6 +59,13 @@ Other deprecations
Completed deprecation cycles
----------------------------


* ``qml.from_qasm`` no longer removes measurements from the QASM code. Use
``measurements=[]`` to remove measurements from the original circuit.

- Deprecated in v0.37
- Default behaviour changed in v0.38

* ``qml.transforms.map_batch_transform`` has been removed, since transforms can be applied directly to a batch of tapes.
See :func:`~.pennylane.transform` for more information.

Expand Down
5 changes: 5 additions & 0 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@

<h3>Breaking changes 💔</h3>

* ``qml.from_qasm`` no longer removes measurements from the QASM code. Use
``measurements=[]`` to remove measurements from the original circuit.
[(#5982)](https://github.com/PennyLaneAI/pennylane/pull/5982)

* ``qml.transforms.map_batch_transform`` has been removed, since transforms can be applied directly to a batch of tapes.
See :func:`~.pennylane.transform` for more information.
[(#5981)](https://github.com/PennyLaneAI/pennylane/pull/5981)
Expand Down Expand Up @@ -75,6 +79,7 @@ Astral Cai,
Yushao Chen,
Lillian M. A. Frederiksen,
Pietropaolo Frisoni,
Emiliano Godinez,
Christina Lee,
Austin Huang,
William Maxwell,
Expand Down
172 changes: 98 additions & 74 deletions pennylane/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,10 @@
This module contains functions to load circuits from other frameworks as
PennyLane templates.
"""
import warnings
from collections import defaultdict
from importlib import metadata
from sys import version_info

import pennylane as qml

# Error message to show when the PennyLane-Qiskit plugin is required but missing.
_MISSING_QISKIT_PLUGIN_MESSAGE = (
"Conversion from Qiskit requires the PennyLane-Qiskit plugin. "
Expand Down Expand Up @@ -410,113 +407,140 @@ def from_qiskit_op(qiskit_op, params=None, wires=None):
raise RuntimeError(_MISSING_QISKIT_PLUGIN_MESSAGE) from e


def from_qasm(quantum_circuit: str, measurements=False):
"""Loads quantum circuits from a QASM string using the converter in the
def from_qasm(quantum_circuit: str, measurements=None):
r"""
Loads quantum circuits from a QASM string using the converter in the
PennyLane-Qiskit plugin.

Args:
quantum_circuit (str): a QASM string containing a valid quantum circuit
measurements (None | MeasurementProcess | list[MeasurementProcess]): an optional PennyLane
measurement or list of PennyLane measurements that overrides any terminal measurements
that may be present in the input circuit. If set to ``None``, existing measurements
in the input circuit will be used.
measurement or list of PennyLane measurements that overrides the terminal measurements
that may be present in the input circuit. Defaults to ``None``, such that all existing measurements
in the input circuit are returned. See **Removing terminal measurements** for details.
EmilianoG-byte marked this conversation as resolved.
Show resolved Hide resolved

Returns:
function: the PennyLane template created based on the QASM string
function: the PennyLane quantum function created based on the QASM string. This function itself returns the mid-circuit measurements plus the terminal measurements by default (``measurements=None``), and returns **only** the measurements from the ``measurements`` argument otherwise.
EmilianoG-byte marked this conversation as resolved.
Show resolved Hide resolved
trbromley marked this conversation as resolved.
Show resolved Hide resolved

By default, ``from_qasm`` will remove any measurements that are present in the QASM code.
If the QASM code contains measurements, set ``measurements=None`` to keep them in the
output of ``from_qasm``.
**Example:**

.. warning::
.. code-block:: python

The current default behaviour of removing measurements in the QASM code is deprecated
and will be changed in a future release. Starting in version ``0.38``, ``from_qasm``
will keep the measurements from the QASM code by default. To remove all measurements,
set ``measurements=[]`` which overrides the existing measurements with an empty list.
qasm_code = 'OPENQASM 2.0;'\\
'include "qelib1.inc";'\\
'qreg q[2];'\\
'creg c[2];'\\
'h q[0];'\\
'measure q[0] -> c[0];'\\
EmilianoG-byte marked this conversation as resolved.
Show resolved Hide resolved
'rz(0.24) q[0];'\\
'cx q[0], q[1];'\\
'measure q -> c;'

**Example:**
loaded_circuit = qml.from_qasm(qasm_code)

.. code-block:: python
>>> print(qml.draw(loaded_circuit)())
0: ──H──┤↗├──RZ(0.24)─╭●──┤↗├─┤
1: ───────────────────╰X──┤↗├─┤

Calling the quantum function returns a tuple containing the mid-circuit measurements and the terminal measurements.

>>> loaded_circuit()
(MeasurementValue(wires=[0]),
MeasurementValue(wires=[0]),
MeasurementValue(wires=[1]))

A list of measurements can also be passed directly to ``from_qasm`` using the ``measurements`` argument, making it possible to create a PennyLane circuit with :class:`qml.QNode <pennylane.QNode>`.

.. code-block:: python

dev = qml.device("default.qubit")
measurements = [qml.var(qml.Y(0))]
circuit = qml.QNode(qml.from_qasm(qasm_code, measurements = measurements), dev)
EmilianoG-byte marked this conversation as resolved.
Show resolved Hide resolved

>>> hadamard_qasm = 'OPENQASM 2.0;' \\
... 'include "qelib1.inc";' \\
... 'qreg q[1];' \\
... 'creg c[1];' \\
... 'h q[0];'
>>> my_circuit = qml.from_qasm(hadamard_qasm)
>>> my_circuit()
>>> print(qml.draw(circuit)())
0: ──H──┤↗├──RZ(0.24)─╭●─┤ Var[Y]
1: ───────────────────╰X─┤

.. details::
:title: Removing terminal measurements

To remove all terminal measurements, set ``measurements=[]``. This removes the existing terminal measurements and keeps the mid-circuit measurements.

.. code-block:: python

loaded_circuit = qml.from_qasm(qasm_code, measurements=[])

>>> print(qml.draw(loaded_circuit)())
0: ──H──┤↗├──RZ(0.24)─╭●─┤
1: ───────────────────╰X─┤

Calling the quantum function returns the same empty list that we originally passed in.

>>> loaded_circuit()
[]

The measurements can also be passed directly to the function when creating the
quantum function, making it possible to create a PennyLane circuit with
:class:`qml.QNode <pennylane.QNode>`:
Note that mid-circuit measurements are always applied, but are only returned when ``measurements=None``.
lillian542 marked this conversation as resolved.
Show resolved Hide resolved

>>> measurements = [qml.var(qml.Y(0))]
>>> circuit = qml.QNode(qml.from_qasm(hadamard_qasm, measurements), dev)
>>> circuit()
[tensor(1., requires_grad=True)]
.. details::
:title: Using conditional operations

Mid-circuit measurements inside the QASM code can also be interpreted.
We can take advantage of the mid-circuit measurements inside the QASM code by calling the returned function within a :class:`qml.QNode <pennylane.QNode>`.

.. code-block:: python
.. code-block:: python

hadamard_qasm = 'OPENQASM 2.0;' \\
'include "qelib1.inc";' \\
'qreg q[2];' \\
'creg c[2];' \\
'h q[0];' \\
'measure q[0] -> c[0];' \\
'rz(0.24) q[0];' \\
'cx q[0], q[1];' \\
'measure q -> c;'
loaded_circuit = qml.from_qasm(qasm_code)

dev = qml.device("default.qubit")
loaded_circuit = qml.from_qasm(hadamard_qasm, measurements=None)
@qml.qnode(dev)
def circuit():
mid_measure, *_ = loaded_circuit()
qml.cond(mid_measure == 0, qml.RX)(np.pi / 2, 0)
return [qml.expval(qml.Z(0))]

@qml.qnode(dev)
def circuit():
mid_measure, m0, m1 = loaded_circuit()
qml.cond(mid_measure == 0, qml.RX)(np.pi / 2, 0)
return [qml.expval(qml.measure(0))]
>>> print(qml.draw(circuit)())
0: ──H──┤↗├──RZ(0.24)─╭●──┤↗├──RX(1.57)─┤ <Z>
1: ──────║────────────╰X──┤↗├──║────────┤
╚═════════════════════╝

>>> circuit()
[tensor(0.75, requires_grad=True)]
.. details::
:title: Importing from a QASM file

You can also load the contents of a QASM file:
We can also load the contents of a QASM file.

.. code-block:: python
.. code-block:: python

>>> with open("hadamard_circuit.qasm", "r") as f:
... my_circuit = qml.from_qasm(f.read())
# save the qasm code in a file
import locale
from pathlib import Path

The ``my_circuit`` template can now be used within QNodes, as a
two-wire quantum template.
filename = "circuit.qasm"
with Path(filename).open("w", encoding=locale.getpreferredencoding(False)) as f:
f.write(qasm_code)

>>> @qml.qnode(dev)
>>> def circuit(x):
>>> qml.RX(x, wires=1)
>>> my_circuit(wires=(1, 0))
>>> return qml.expval(qml.Z(0))
with open("circuit.qasm", "r") as f:
loaded_circuit = qml.from_qasm(f.read())

The ``loaded_circuit`` function can now be used within a :class:`qml.QNode <pennylane.QNode>` as a two-wire quantum template.

.. code-block:: python

@qml.qnode(dev)
def circuit(x):
qml.RX(x, wires=1)
loaded_circuit(wires=(0, 1))
return qml.expval(qml.Z(0))

>>> print(qml.draw(circuit)(1.23))
0: ──H─────────┤↗├──RZ(0.24)─╭●──┤↗├─┤ <Z>
1: ──RX(1.23)────────────────╰X──┤↗├─┤
"""

try:
plugin_converter = plugin_converters["qasm"].load()
except Exception as e: # pragma: no cover
raise RuntimeError( # pragma: no cover
"Failed to load the qasm plugin. Please ensure that the pennylane-qiskit package is installed."
) from e

if measurements is False:
measurements = []
if "measure" in quantum_circuit:
warnings.warn(
"The current default behaviour of removing measurements in the QASM code "
"is deprecated. Set measurements=None to keep existing measurements in the QASM "
"code or set measurements=[] to remove them from the returned circuit. Starting "
"in version 0.38, measurements=None will be the new default.",
qml.PennyLaneDeprecationWarning,
)
return plugin_converter(quantum_circuit, measurements=measurements)


Expand Down
10 changes: 0 additions & 10 deletions tests/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,16 +145,6 @@ def test_from_qasm(self, mock_plugin_converters):
if mock_plugin_converters[plugin_converter].called and plugin_converter != "qasm":
raise RuntimeError(f"The other plugin converter {plugin_converter} was called.")

def test_from_qasm_deprecated(self, mock_plugin_converters):
"""Tests that the current default behaviour of from_qasm is deprecated."""

with pytest.warns(qml.PennyLaneDeprecationWarning, match="The current default behaviour"):
qml.from_qasm("measure q[i] -> c[i];")

called_args, called_kwargs = mock_plugin_converters["qasm"].call_args
assert called_args == ("measure q[i] -> c[i];",)
assert called_kwargs == {"measurements": []}

@pytest.mark.parametrize(
"method, entry_point_name, args, kwargs",
[
Expand Down
Loading