Skip to content

Commit

Permalink
Add documentation on OpenQASM 2 phase conventions
Browse files Browse the repository at this point in the history
OpenQASM 2 as a language does not provide a way to specify global
phases, either of operations or the entire program.  The prose of the
defining paper talks about the `u1` and `rz` instructions from
`qelib1.inc` as having different global phases, however since the
language cannot represent this, the header file defines `rz` apparently
as a direct alias to `u1`.

Qiskit's position has always been that we interpret `rz` as using the
phase convention of `RZGate`, and `u1` uses the different convention of
`PhaseGate`.  This commit adds documentation to the pages on OpenQASM 2
about this difference, and includes an example of how to override
Qiskit's default if the generating code (e.g. pyZX) wants to use an
alternative phase convention.
  • Loading branch information
jakelishman committed Sep 8, 2023
1 parent cd507ad commit 7da5e5f
Showing 1 changed file with 85 additions and 12 deletions.
97 changes: 85 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,76 @@ 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, the override needed to do this 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 +491,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 +517,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

0 comments on commit 7da5e5f

Please sign in to comment.