openqasm/source/language/pulses.rst

272 lines
10 KiB
ReStructuredText

.. role:: raw-latex(raw)
:format: latex
.. _pulse-gates:
Pulse-level descriptions of gates and measurement
=================================================
To induce the quantum gates and measurements of a circuit, qubits are
manipulated with classically-controlled stimulus fields. The details of
these stimuli are typically unique per-qubit and may vary over time due
to instabilities in the underlying systems. Furthermore, there is
significant interest in applying optimal control methodologies to the
construction of these controls in order to optimize gate and circuit
performance. As a consequence, we desire to connect gate-level
instructions to the underlying microcoded
:cite:`wilkesBestWayDesign1989` stimulus programs emitted by
the controllers to implement each operation. In OpenQASM we expose
access to this level of control with pulse-level definitions of gates
and measurement with a user-selectable pulse grammar.
The entry point to such gate and measurement definitions is the ``defcal`` keyword
analogous to the ``gate`` keyword, but where the ``defcal`` body specifies a pulse-level
instruction sequence on *physical* qubits, e.g.
.. code-block::
defcal rz(angle[20] theta) $0 { ... }
defcal measure $0 -> bit { ... }
defcal measure_iq q -> complex[float[32]] { ... }
We distinguish gate and measurement definitions by the presence of a
return value type in the latter case, analogous to the subroutine syntax
defined earlier. Furthermore, the return value type does not need to return a
classified value but can return some lower level data.
The reference to *physical* rather than *virtual*
qubits is critical because quantum registers are no longer
interchangeable at the pulse level. Due to varying physical qubit
properties a microcode definition of a gate on one qubit will not
perform the equivalent operation on another qubit. To meaningfully
describe gates as pulses we must bind operations to specific qubits.
QASM achieves this by prefixing qubit references with ``$`` to indicate
a specific qubit on the device, e.g. ``$2`` would refer to physical
qubit 2.
One can define a `defcal` using a regular identifier for a qubit, which
causes that calibration definition to be valid for all physical qubits.
This is most likely to be useful for gates that are implemented virtually.
For instance, to define an equivalent `rz` calibration on qubits 0 and 1, we could write
.. code-block::
defcal rz(angle[20] theta) q { ... }
// we've defined ``rz`` on arbitrary physical qubits, so we can do:
rz(3.14) $0;
rz(3.14) $1;
As a consequence of the need for specialization of operations on
particular qubits, the same symbol may be defined multiple
times, e.g.
.. code-block::
defcal h $0 { ... }
defcal h $1 { ... }
and so forth. Some operations require further specialization on
parameter values, so we also allow multiple declarations on the same
physical qubits with different parameter values, e.g.
.. code-block::
defcal rx(pi) $0 { ... }
defcal rx(pi / 2) $0 { ... }
Given multiple definitions of the same symbol, the compiler will match
the most specific definition found for a given operation. Thus, given,
#. ``defcal rx(angle[20] theta) q ...``
#. ``defcal rx(angle[20] theta) $0 ...``
#. ``defcal rx(pi / 2) $0 ...``
the operation ``rx(pi/2) $0`` would match to (3), ``rx(pi) $0`` would
match (2), ``rx(pi/2) $1`` would match (1).
Users specify the grammar used inside ``defcal`` blocks with a
``defcalgrammar "name"`` declaration. One such grammar is a
`textual representation of OpenPulse <openpulse.html>`_ specified by:
.. code-block::
defcalgrammar "openpulse";
Note that ``defcal`` and ``gate`` communicate orthogonal information to the compiler. ``gate``\s
define unitary transformation rules to the compiler. The compiler may
freely invoke such rules on operations while preserving the structure of
a circuit as a collection of ``gate``\s and ``subroutine``\s. The ``defcal`` declarations instead define
elements of a symbol lookup table. As soon as the compiler replaces a ``gate``
with a ``defcal`` definition, we have changed the fundamental structure of the
circuit. Most of the time symbols in the ``defcal`` table will also have
corresponding ``gate`` definitions. However, if a user provides a ``defcal`` for a symbol
without a corresponding ``gate``, then we treat such operations like the ``opaque`` gates
of prior versions of OpenQASM.
Inline calibration blocks
~~~~~~~~~~~~~~~~~~~~~~~~~
As calibration grammars may require the ability to insert top-level configuration information, perform program setup, or make inline calls
to calibration-level instructions, OpenQASM supports the ability to declare a ``cal`` block. Within the ``cal`` block the
semantics of the selected ``defcalgrammar`` are valid. The ``cal`` block is of the same scope level as the enclosing block. The
calibration grammar may choose to allow capturing values (with chosen syntax) from within the ``cal``
block that were declared within the containing parent scope.
Values declared within the ``cal`` block are only referenceable from other ``cal`` blocks or ``defcal`` declarations
that may observe that scope as defined by the calibration grammar. Values may not leak back to the block's enclosing scope.
In practice, calibration grammars such as OpenPulse may apply
a global scope to all identifiers in order to declare values shared across all ``defcal`` calls thereby linking them together.
.. code-block::
OPENQASM 3;
defcalgrammar "openpulse";
const float original_freq = 5.9e9;
cal {
// Defined within `cal`, so it may not leak back out to the enclosing blocks scope
float new_freq = 5.2e9;
// declare global port
extern port d0;
// reference `freq` variable from enclosing blocks scope
frame d0f = newframe(d0, freq, 0.0);
}
defcal x $0 {
waveform xp = gaussian(1.0, 160t, 40dt);
// References frame and `new_freq` declared in top-level cal block
play(d0f, xp);
set_frequency(d0f, new_freq);
play(d0f, xp);
}
Restrictions on defcal bodies
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The contents of ``defcal`` bodies are subject to the restriction they must have a definite duration
known at compile time, regardless of the parameters passed in or the state of the system when
called. This allows the compiler to properly resolve ``durationof(...)`` calls and
allows for optimizations. If there is to be control flow in the ``defcal``, each branch of the
control flow must have definite and equivalent duration resolvable at compile time. Similarly, loops
must be have a resolvable definite duration at compile time.
For example, consider the case of a ``reset`` gate. The ``defcal`` for a
``reset`` gate can be composed of a single if statement, provided each branch
of the if statement has definite and equivalent duration.
.. code-block::
defcal reset $0 {
bit res = // measure qubit $0
if (res == 1) {
// flip the qubit
} else {
// delay for an equivalent amount of time
}
}
Calibrations in practice
~~~~~~~~~~~~~~~~~~~~~~~~
By their very nature calibrations are transient and unique to a target system.
They are typically generated by automatic calibration routines that are periodically
run on the target system, that are in turn bootstrapped from previous calibrations.
The majority of OpenQASM users will use the default calibrations, however,
for those that want more control, but do not want to bootstrap calibrations for an entire
system. It is expected that the target system provider will provide an include
file to the user. This will contain the declaration of the ``defcalgrammar``, constants,
``defcal``\s and other grammar and system specific components such as ``port``\s,
``waveform``\s and ``frame``\s in the `OpenPulse defcalgrammar <openpulse.html>`. The user
may then plugin to the existing calibrations by defining new calibrations, or overwriting
existing ones by using the same ``port``\s and ``frame``\s.
The example below demonstrates this in practice for a two-qubit,
cross-resonance device using a ``backend.inc`` include file.
The name ``backend.inc`` is arbitrary - it's just a file to be included using the
existing ``include`` mechanism.
.. code-block::
// backend.inc for openpulse two-qubit device
defcalgrammar "openpulse";
const float q0_freq = 5.0e9;
const float q1_freq = 5.1e9;
cal {
extern drag(complex[size] amp, duration l, duration sigma, float[size] beta) -> waveform;
extern gaussian_square(complex[size] amp, duration l, duration square_width, duration sigma) -> waveform;
extern port q0;
extern port q1;
frame q0_frame = newframe(q0, q0_freq, 0);
frame q1_frame = newframe(q1, q1_freq, 0);
}
defcal rz(angle theta) $0 {
shift_phase(q0_frame, theta);
}
defcal rz(angle theta) $1 {
shift_phase(q1_frame, theta);
}
defcal sx $0 {
waveform sx_wf = drag(0.2+0.1im, 160dt, 40dt, 0.05);
play(q0_frame, sx_wf);
}
defcal sx $1 {
waveform sx_wf = drag(0.1+0.05im, 160dt, 40dt, 0.1);
play(q1_frame, sx_wf);
}
defcal cx $1, $0 {
waveform CR90p = gaussian_square(0.2+0.05im, 560dt, 240dt, 40dt);
waveform CR90m = gaussian_square(-0.2-0.05im, 560dt, 240dt, 40dt);
rz(pi/2) $0; rz(-pi/2) $1;
sx $0; sx $1;
barrier $0, $1;
play(q0_frame, CR90p);
barrier $0, $1;
sx $0;
sx $0;
barrier $0, $1;
rz(-pi/2) $0; rz(pi/2) $1;
sx $0; sx $1;
play(q0_frame, CR90m);
}
The user would then include the ``backend.inc`` in their own program and use them as demonstrated below
.. code-block::
OPENQASM 3.0;
include "backend.inc"
// Defcal using frames from backend.inc enabling the calibration
// to "plugin" to the existing calibrations.
defcal Y90p $0 {
waveform y90p = drag(0.1-0.2im, 160dt, 40dt, 0.05);
play(q0_frame, y90p);
}
// Teach the compiler what the unitary of a Y90p is
gate Y90p q {
rz(-pi/2) q;
sx q;
}
// Use this defcal explicitly
Y90p $0;
cx $1, $0;