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

Add documentation on OpenQASM 2 phase conventions #10801

Merged
merged 3 commits into from
Sep 28, 2023
Merged
Changes from all commits
Commits
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
98 changes: 86 additions & 12 deletions qiskit/qasm2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

r"""
r'''
================================
OpenQASM 2 (:mod:`qiskit.qasm2`)
================================
Expand Down Expand Up @@ -61,6 +61,9 @@

.. autoclass:: CustomInstruction

This can be particularly useful when trying to resolve ambiguities in the global-phase conventions
of an OpenQASM 2 program. See :ref:`qasm2-phase-conventions` for more details.

.. _qasm2-custom-classical:

Specifying custom classical functions
Expand Down Expand Up @@ -183,7 +186,7 @@
.. code-block:: python

import qiskit.qasm2
program = '''
program = """
OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
Expand All @@ -193,7 +196,7 @@
cx q[0], q[1];

measure q -> c;
'''
"""
circuit = qiskit.qasm2.loads(program)
circuit.draw()

Expand Down Expand Up @@ -222,10 +225,10 @@
.. code-block:: python

import qiskit.qasm2
program = '''
program = """
include "other.qasm";
// ... and so on
'''
"""
circuit = qiskit.qasm2.loads(program, include_path=("/path/to/a", "/path/to/b", "."))

For :func:`load` only, there is an extra argument ``include_input_directory``, which can be used to
Expand Down Expand Up @@ -269,12 +272,12 @@ class Builtin(Gate):
def __init__(self):
super().__init__("builtin", 1, [])

program = '''
program = """
opaque my(theta) q1, q2;
qreg q[2];
my(0.5) q[0], q[1];
builtin q[0];
'''
"""
customs = [
CustomInstruction(name="my", num_params=1, num_qubits=2, constructor=MyGate),
# Setting 'builtin=True' means the instruction doesn't require a declaration to be usable.
Expand All @@ -286,20 +289,20 @@ def __init__(self):
Similarly, you can add new classical functions used during the description of arguments to gates,
both in the main body of the program (which come out constant-folded) and within the bodies of
defined gates (which are computed on demand). Here we provide a Python version of ``atan2(y, x)``,
which mathematically is :math:`\atan(y/x)` but correctly handling angle quadrants and infinities,
which mathematically is :math:`\arctan(y/x)` but correctly handling angle quadrants and infinities,
and a custom ``add_one`` function:

.. code-block:: python

import math
from qiskit.qasm2 import loads, CustomClassical

program = '''
program = """
include "qelib1.inc";
qreg q[2];
rx(atan2(pi, 3 + add_one(0.2))) q[0];
cx q[0], q[1];
'''
"""

def add_one(x):
return x + 1
Expand All @@ -313,6 +316,77 @@ def add_one(x):
circuit = loads(program, custom_classical=customs)


.. _qasm2-phase-conventions:

OpenQASM 2 Phase Conventions
============================

As a language, OpenQASM 2 does not have a way to specify the global phase of a complete program, nor
of particular gate definitions. This means that parsers of the language may interpret particular
gates with a different global phase than what you might expect. For example, the *de facto*
standard library of OpenQASM 2 ``qelib1.inc`` contains definitions of ``u1`` and ``rz`` as follows:

.. code-block:: text

gate u1(lambda) q {
U(0, 0, lambda) q;
}

gate rz(phi) a {
u1(phi) a;
}

In other words, ``rz`` appears to be a direct alias for ``u1``. However, the interpretation of
``u1`` is specified in `equation (3) of the paper describing the language
<https://arxiv.org/abs/1707.03429>`__ as

.. math::

u_1(\lambda) = \operatorname{diag}\bigl(1, e^{i\lambda}\bigr) \sim R_z(\lambda)

where the :math:`\sim` symbol denotes equivalence only up to a global phase. When parsing OpenQASM
2, we need to choose how to handle a distinction between such gates; ``u1`` is defined in the prose
to be different by a phase to ``rz``, but the language is not designed to represent this.

Qiskit's default position is to interpret a usage of the standard-library ``rz`` using
:class:`.RZGate`, and a usage of ``u1`` as using the phase-distinct :class:`.U1Gate`. If you wish
to use the phase conventions more implied by a direct interpretation of the ``gate`` statements in
the header file, you can use :class:`CustomInstruction` to override how Qiskit builds the circuit.

For the standard ``qelib1.inc`` include there is only one point of difference, and so the override
needed to switch its phase convention is:

.. code-block:: python

from qiskit import qasm2
from qiskit.circuit.library import PhaseGate
from qiskit.quantum_info import Operator

program = """
OPENQASM 2.0;
include "qelib1.inc";
qreg q[1];
rz(pi / 2) q[0];
"""

custom = [
qasm2.CustomInstruction("rz", 1, 1, PhaseGate),
]

This will use Qiskit's :class:`.PhaseGate` class to represent the ``rz`` instruction, which is
equal (including the phase) to :class:`.U1Gate`:

.. code-block:: python

Operator(qasm2.loads(program, custom_instructions=custom))

.. code-block:: text

Operator([[1.000000e+00+0.j, 0.000000e+00+0.j],
[0.000000e+00+0.j, 6.123234e-17+1.j]],
input_dims=(2,), output_dims=(2,))


.. _qasm2-legacy-compatibility:

Legacy Compatibility
Expand Down Expand Up @@ -418,7 +492,7 @@ def from_qasm_str(qasm_str: str):

.. note::

Circuits imported with :func:`load` and :func:`loads` with the above legacy-compability settings
Circuits imported with :func:`load` and :func:`loads` with the above legacy-compatibility settings
should compare equal to those created by Qiskit's legacy importer, provided no non-``qelib1.inc``
user gates are defined. User-defined gates are handled slightly differently in the new importer,
and while they should have equivalent :attr:`~.Instruction.definition` fields on inspection, this
Expand All @@ -444,7 +518,7 @@ def from_qasm_str(qasm_str: str):
OpenQASM 2 is not a suitable serialization language for Qiskit's :class:`.QuantumCircuit`. This
module is provided for interoperability purposes, not as a general serialization format. If that is
what you need, consider using :mod:`qiskit.qpy` instead.
"""
'''

__all__ = [
"load",
Expand Down