The |cloud|_ quantum cloud service provides access to a simulator that enables
you to test gate-model circuits intended to be executed on a dual-rail quantum
processing unit (QPU). You describe your circuits using the dwave-gate
package's quantum circuit description language (QCDL), described here.
Important
Features for real-time control, which are being phased into dual-rail quantum computing systems, are already available on the simulator in the |cloud|_ service for prototyping and learning.
To construct :term:`QCDL` programs and submit to the dual-rail simulator in the |cloud|_ service you need the following:
- A |cloud|_ account that has been invited to beta test the dual-rail simulator.
- A development environment with the :ref:`index_ocean_sdk`.
If you are already using Ocean software for an existing |cloud|_ service account, see the :ref:`qcdl_onboarding_previous_users` section for working with another project.
If you have accepted an invitation to the |cloud|_ service for the first time to use the dual-rail simulator, the following documentation gets you started with submitting your programs:
The :ref:`index_leap_sapi` section.
This section describes the |cloud|_ service: the dashboard where you can see your access to :term:`solver`s such as the dual-rail simulator, the API token you need to submit programs to the simulator, whitelisting information if required by your organization, and more.
The :ref:`ocean_index_get_started` section.
This section explains how to start using the :ref:`index_ocean_sdk`, which lets you write :term:`QCDL` programs and submit them to the simulator.
Note
Installing the SDK is recommended. If you chose to install only the :ref:`index_gate` package, see the installation instructions here.
To submit programs to the dual-rail simulator in the |cloud|_ service, you must accept the emailed invitation to a new project. You use the API token from this project to access and send jobs to the simulator.
The :ref:`ocean_leap_authorization` section describes how to work with multiple projects (see the "Multiple Leap Projects" tab).
The following are two simple ways to use the beta-tester project's API token from your existing development environment.
Add a section to your
dwave.conffile.You can see your
dwave.conffile using the methods described in the :ref:`cloud_configuration` or using the :ref:`D-Wave CLI <ocean_dwave_cli>` section.For example, add a
betasection:[defaults] token = ABC-123456789123456789123456789 [beta] token = BETA-123456789123456789123456789
You can then set
profile="beta"to use the beta-tester project's API token when accessing the simulator.>>> from dwave.gate.leap import LeapQCDLSimulator ... >>> simulator = LeapQCDLSimulator(profile="beta") # doctest: +SKIP
Set the
DWAVE_API_TOKENenvironment variable.You can set this environment variable for a Unix operating system with a Bash command such as,
export DWAVE_API_TOKEN="BETA-123456789123456789123456789", for example, or for a Windows system with a command such asset DWAVE_API_TOKEN=BETA-123456789123456789123456789.Remember to delete that environment variable when you return to your work on your previous project.
QCDL is an embedded Domain Specific Language (DSL) that uses Python as the host language. If you have some experience coding in Python, you can understand the structure of QCDL programs.
You use the :class:`~dwave.gate.qcdl.qcdl` Python decorator[1] to mark the entry point to your QCDL circuit. This decorator converts an otherwise standard Python function into one that generates QCDL programs when executed.
The decorator can optionally indicate the number of qubits in the program.
This example creates a Bell state.
.. testcode::
from dwave.gate.qcdl import qcdl
from dwave.gate.qcdl.operations import cx, h, measure
@qcdl(2)
def main(q0, q1):
h(q0)
cx(q0, q1)
measure(q0)
measure(q1)
qcdl_program = main()
In the code above, the @qcdl decorator specifies that the entry point
accepts two qubits, the arguments q0 and q1 of main(). The decorated
function main() returns a
Pydantic model
that you can submit to a compiler or simulator in the |cloud|_ service, as
described in the :ref:`qcdl_submitting_programs` section.
The :func:`~dwave.gate.utils.display.print_qcdl` function can visualize this structure as readable text, and if run in a Jupyter notebook, as a display object.
.. testcode::
from dwave.gate.utils.display import print_qcdl
print_qcdl(qcdl_program)
The code above displays the following QCDL program.
.. testcode::
:hide:
print(print_qcdl(qcdl_program))
.. testoutput::
:options: +NORMALIZE_WHITESPACE
begin quantum
h([q0], q0)
cx([q0, q1], q0, q1)
measure([q0], q0, log=True)
measure([q1], q1, log=True)
end quantum
If :func:`~dwave.gate.utils.display.print_qcdl` displays poorly, you can output a
string by setting the function's to_Display=False parameter.
| [1] | Python decorators are described in the Decorators section of the Wikipedia article on Python syntax and multiple internet tutorials. |
.. seealso::
:func:`~dwave.gate.qcdl.qcdl` decorator
The gates dwave-gate supports match the method names in a Qiskit
QuantumCircuit (e.g., :func:`~dwave.gate.qcdl.operations.h`,
:func:`~dwave.gate.qcdl.operations.sx`, :func:`~dwave.gate.qcdl.operations.rz`,
:func:`~dwave.gate.qcdl.operations.cz`, etc).
.. testcode::
from dwave.gate.qcdl import qcdl
from dwave.gate.qcdl.operations import h, cz
@qcdl(2)
def simple_gate_example(q0, q1):
h(q0)
cz(control_qubit=q0, target_qubit=q1)
In this example, cz(control_qubit=q0, target_qubit=q1) is similar to the
Qiskit method call cz(q0, q1).
Note
For gates that take angles as an argument, dwave-gate lists qubits
before angles, whereas Qiskit follows the reverse order.
Note
For parameterizable gates, angles are in units of radians when passed as literals. When a :class:`~dwave.gate.qcdl.registers.FixedPointRegister` is passed as the angle for a gate, its value must be in units of π (see the :ref:`qcdl_basic_registers_arithmetic` section for more information).
When you submit your QCDL to a QPU in the |cloud|_ service, a transpiler rewrites the circuit to use the QPU's supported basis gates and topology, as described in the :ref:`qcdl_basic_transpilation` section. For most algorithms, any implementation is acceptable but if you are studying fidelity or yield characterization, you can prevent the transpiler from combining certain gates. The :func:`~dwave.gate.qcdl.operations.barrier` instruction signals to the transpiler to not combine gates across your barrier.[2]
For example, if you do not set :func:`~dwave.gate.qcdl.operations.barrier` instructions on a randomized benchmarking circuit, where the net mathematical effect is an identity operation, transpilation collapses your QCDL.
.. testcode::
from dwave.gate.qcdl import qcdl
from dwave.gate.qcdl.operations import barrier, x
@qcdl(1)
def barrier_example(q0):
x(q0)
barrier(q0)
x(q0)
| [2] | When the transpiler is not used, the :func:`~dwave.gate.qcdl.operations.barrier` instruction might affect some circuit modifications. |
QCDL supports integer and fixed-point registers with the :class:`~dwave.gate.qcdl.registers.Register` and :class:`~dwave.gate.qcdl.registers.FixedPointRegister` classes. You can use these registers for simple classical expressions: negation, addition, subtraction, multiplication, AND, OR, XOR, right-shift, and all six comparisons.
You can use the outputs of these calculations for :ref:`conditional statements <qcdl_advanced_conditionals>` within complex real-time classical-quantum logic.
When you store a :class:`~dwave.gate.qcdl.operations.measure` result to a register,
numerical values 0 and 1 are used to represent the logical
projective measurements and -1 for a * state (a measurement that was
out of the code space and declared "erased"). For more information, see the
:class:`~dwave.gate.qcdl.LogicalOutcomeToInteger` class.
Tip
The simulator is able to detect register overflow or underflow problems. For
this among many other reasons, you should validate programs with the
simulator before running your application on the QPU. Configure simulator
option use_registers=True to either warn or raise an exception on such
conditions.
.. testcode::
from dwave.gate.qcdl import qcdl, Scope
@qcdl(2)
def register_example(q0, q1):
sc = Scope(q0, q1) # Scope facilitates control flow
r1 = sc.Register(2, name="r1") # naming facilitates debugging
r2 = sc.Register()
r2 <<= 1 # set r2 to 1
r2 <<= 2 * r1 # set r2 to 2*r1 = 4
Attention!
Registers are not implicitly re-assigned with every shot. Instead, they carry the value they ended with from one shot to the next. Typically, for most registers, you prefer each shot to be independent, and so should re-assign your registers before using them.
Note
- A register is associated with a qubit. For QCDL programs with some complexity, your program must ensure the information in any qubit's register is visible to other qubits. The :ref:`qcdl_advanced_registers_mirroring` section provides more information.
- If you pass a :class:`~dwave.gate.qcdl.registers.FixedPointRegister` object to a gate as an angle, use units of π instead of radians. For example, a value of 1 is equivalent to π.
A logical :func:`~dwave.gate.qcdl.operations.measure` operation on a dual-rail gate-model quantum computer produces one of three outcomes:
- 0, 1 represent logical projective measurements.
*(which has numerical representation -1), sometimes informally referred to as a "splat", represents that a measured qubit was determined to be out of the code space and is thereby declared to be "erased".
You may place these "end of the line"-measurement instructions anywhere in your program. You can measure qubits multiple times in a given shot (usually resetting the qubit(s) in between).
Tip
Using the :attr:`~dwave.gate.results.Result.tags` property is the recommended way to organize measurement data.
Measurement outcomes are handled in three different ways:
.. todo:: Update below for Ocean
- If
log=True(the default) the outcome is appended to the array associated with the qubit on which it was measured. Along with the arrays from the other qubits, this data is returned to you in a 3D array (pertag) with shape "number of measurements per shot, number of shots, number of qubits". This data structure may be retrieved usingResult.get_memory. For circuits with a deterministic number of measurements per shot consistent for all qubits, this data structure may be converted into a counts dictionary withResult.get_counts(get_countscallsget_memory). - The outcome may be saved to a register. When doing so, even if the register
is defined on multiple qubits, only the register copy on the qubit measured
is assigned. This data could be returned with
append_table_row(see the :ref:`qcdl_basic_result_records` section). - Each qubit implicitly stores its most recent measurement outcome and this value may be used in conditional statements.
.. testcode::
from dwave.gate.qcdl.operations import measure
@qcdl(1)
def measurement_example1(q0):
measure(q0)
.. testcode::
from dwave.gate.qcdl import qcdl
from dwave.gate.qcdl.operations import measure
@qcdl(1)
def measurement_example2(q0):
register = q0.Register()
measure(q0, register=register, log=False)
Warning
The 3D array of logged measurements is unlikely to be useful if measurement data is generated non-deterministically. Unless there is a deterministic number of measurements per shot, you cannot relate measurement outcomes with the generating instruction.
By default, the :meth:`~dwave.gate.results.Result.get_counts` method returns all data,
including erasures. To return only results without the *, thereby
post-selecting on the detected errors, use the post_select=True flag.
You can non-destructively inspect a dual-rail qubit to detect if it is out of the code space ("leaked") with the :func:`~dwave.gate.qcdl.operations.mced` operation. If the test is positive, the qubit is declared erased.
.. testcode::
from dwave.gate.qcdl import qcdl
from dwave.gate.qcdl.operations import mced
@qcdl(1)
def mced_example(q0):
register = q0.Register()
mced(q0, register=register)
Results are a Python dictionary where keys are set by the :meth:`~dwave.gate.qcdl.QCDLModuleContainer.append_table_row` method and values are tables formatted as a Polars DataFrame.
The :meth:`~dwave.gate.qcdl.QCDLModuleContainer.append_table_row` method retrieves the values of registers in runtime. When you invoke the method, register data is written to a set of tables that your application can retrieve. In addition to using this functionality in algorithms, you can use it for troubleshooting, as though it were a cross between a print statement and a breakpoint.
.. todo:: update for Ocean
If your QCDL uses the :meth:`~dwave.gate.qcdl.QCDLModuleContainer.append_table_row` method, the :class:`~dwave.gate.results.Result` output contains records that you may retrieve with the :attr:`~dwave.gate.results.Result.records` property.
.. testcode::
:skipif: True
import pandas as pd
from dwave.gate.qcdl import qcdl
from aqumen import Aqumen # Replace with Leap service's class
@qcdl(1)
def main(q0):
r = q0.Register(name="some_classical_data")
r <<= 13
q0.append_table_row(r, table_name="my_table")
aq = Aqumen("simulator", simulate=True)
results = await aq.execute(program=main(), shots=10)
df : pl.DataFrame = res.get_records()["q0"]["my_table"]
The result is a DataFrame containing 1 column named some_classical_data
with 10 rows, each of which have a value of 13.
The :class:`~dwave.gate.results.YieldHandling` class provides a general way of handling result distributions. It supports options for renormalizing distributions, ignoring erasures, and others.
.. testcode::
from dwave.gate.results import YieldHandling
half_splats = {"00": 100, "0*": 100}
assert YieldHandling.only_post_selected_counts.apply(half_splats) == ({"00": 100}, 0.5)
A significant feature of the D-Wave simulator is that it flags detected errors
by returning * as a third measurement outcome in addition to 0 and
1, as described in the :ref:`qcdl_basic_measurements` section. Qiskit
does not handle these values so you must remove individual shots containing a
* when passing information to Qiskit. Consequently, fewer shots are likely
to be returned than the number of shots you requested.[3]
.. todo:: update for Ocean
.. testcode::
:skipif: True
from dwave.gate.results import YieldHandling
provider = AqumenProvider(yield_handling=YieldHandling.renormalize_distribution)
simulator_noisy_backend = provider.simulator_noisy
shots = 1000
job: AqumenJob = simulator_noisy_backend.run(qc, shots=shots)
result: AqumenQiskitResult = job.result()
# no splats here!
counts: dict[str, float] = result.get_counts()
assert abs(sum(counts.values()) - shots) < 1e-8
The code above divides the values in the counts dict by the yield, trading
statistical accuracy for convenience.
Alternatively, a YieldHandling option may be passed to get_counts.
| [3] | If an application you use, for example, in computing statistical errors, is not robust to results containing fewer shots than requested, you can use the :class:`~dwave.gate.results.YieldHandling` class as a workaround temporarily and with caution. |
At the beginning of every shot, the QPU initializes all of the qubits used by your circuit. You can also explicitly use the :func:`~dwave.gate.qcdl.operations.initialize` operation in your QCDL.
.. testcode::
from dwave.gate.qcdl import qcdl
from dwave.gate.qcdl.operations import initialize
@qcdl(4)
def initialize_example(q0, q1, q2, q3):
initialize(q0, q1, q2, q3)
The operation is more effective on the QPU than any you are able to implement otherwise in QCDL code.
You can also reset qubits individually.
.. testcode::
from dwave.gate.qcdl import qcdl
from dwave.gate.qcdl.operations import initialize
@qcdl(1)
def reset_example(q0):
q0.reset()
While QCDL programs support any single- or two-qubit gate that is supported by a Qiskit QuantumCircuit, D-Wave QPUs (and simulator noise models) do not support all gates. The set of quantum gates that are compatible with a QPU is called its basis gates. A QCDL program must be transpiled to replace any unsupported gates with these basis gates.
Transpilation handles the change of gates for you. You may use any gates you wish to in your QCDL, knowing that the operations executed on the solver might differ in this way from your code. However, if you want your program executed verbatim (or an error raised), you can configure compilation and simulation to not transpile (see the :ref:`qcdl_submitting_programs` section).
| Basis Gates | Description | Availability |
|---|---|---|
| :func:`~dwave.gate.qcdl.operations.sx`, :func:`~dwave.gate.qcdl.operations.x` | Single qubit rotation around the X-axis by π/2 and π respectively. | All operational qubits. |
| :func:`~dwave.gate.qcdl.operations.rz` | Single qubit, parameterizable rotation around the Z-axis. | All operational qubits. |
| :func:`~dwave.gate.qcdl.operations.cz` | Two qubit rotation by π/2 around the ZZ-axis. | Connected, operational qubits. |
- Transpilation is a non-deterministic optimization algorithm based on Qiskit. Optimality is not guaranteed.
- Depending on topology and your circuit, the transpiler may add qubits and gates to the executed program that were not in your QCDL. You might be able to prevent this through careful placement of input gates.
- Returned logged measurements are organized according to the name of the qubit used in the :func:`~dwave.gate.qcdl.operations.measure` instruction.
QCDL supports procedures on qubits. A procedure is a subroutine that is
called from another procedure (the :ref:`entrypoint <qcdl_basic_entrypoint>`,
marked with the @qcdl decorator, is the outermost procedure).
Procedures are useful for:
- Potentially conserving instruction memory on the QPU
- Organizing code for visualization purposes
- Constraining transpilation
A procedure is marked with the :class:`~dwave.gate.qcdl.procedure` decorator.
.. testcode::
from dwave.gate.qcdl import procedure, qcdl
from dwave.gate.qcdl.operations import rx, ry
@procedure
def my_procedure(qa, qb, increment):
rx(qa, increment)
ry(qb, increment)
@qcdl(2)
def procedure_example(q0, q1):
for _ in range(10):
my_procedure(q0, q1, 0.3)
my_procedure(q1, q0, 0.5)
A QCDL program calls the procedure just as it would any other Python function.
In the preceding example, if you remove the @procedure decorator, the
program inlines all the gates into the main procedure.
The :class:`~dwave.gate.qcdl.Scope` class enables you to define a set of operations you can consistently reuse on multiple qubits, which is especially beneficial for for classical and control-flow instructions.
This class is a client-side convenience feature used to generate qubit-level instructions---it is not represented in the generated QCDL. You may declare any number of scopes with arbitrary overlaps.
The example below defines a scope containing all the qubits used in the
program. At the end of each shot, all qubits have 1 in their register
if q0 is measured to be 1. This is a good example for how one might
:ref:`mirror <qcdl_advanced_registers_mirroring>` the same register across
qubits.
.. testcode::
from dwave.gate.qcdl import qcdl, Scope
from dwave.gate.qcdl.operations import h, measure
@qcdl(3)
def main(q0, q1, q2):
sc = Scope(q0, q1, q2)
is_1 = sc.Register()
is_1 <<= 0
h(q0)
measure(q0)
with sc.If(condition=q0):
is_1 += 1
In order to run on a QPU, quantum programs must have all of their gate operations scheduled, with the start time of each instruction precisely determined relative to the preceding instruction. Typically, you leave that to the compiler. For some programs, however, you might need to ensure that certain operations execute sequentially instead of concurrently; for example, to complete a measurement on one qubit before another qubit uses that measurement as a condition.
You can explicitly control such scheduling with the :meth:`~dwave.gate.qcdl.QCDLModuleContainer.sync` instruction. You may apply this instruction to any number of qubits to indicate that all operations before the :meth:`~dwave.gate.qcdl.QCDLModuleContainer.sync` instruction must be completed before any operations after the instruction are started.
.. testcode::
from dwave.gate.qcdl import qcdl
from dwave.gate.qcdl.operations import x
@qcdl(2)
def sync_example(q0, q1):
x(q0)
q0.sync(q1)
x(q1)
The example above ensures that the :func:`~dwave.gate.qcdl.operations.x` on q1
is scheduled to start after the :func:`~dwave.gate.qcdl.operations.x` on q0 has
completed.
- Compilation inserts implicit :meth:`~dwave.gate.qcdl.QCDLModuleContainer.sync` instructions before and after all multi-qubit operations such as gates, procedures, and control-flow operations (including shots).
- The :meth:`~dwave.gate.qcdl.QCDLModuleContainer.sync` instruction is not sensitive to the ordering of the qubits.
- An explicit :meth:`~dwave.gate.qcdl.QCDLModuleContainer.sync` instruction in the program is treated as a :func:`~dwave.gate.qcdl.operations.barrier` instruction by the transpiler.
- Scheduling is performed at compile-time and there is no support for "runtime" re-synchronization. If qubits are ever desynchronized, the output is meaningless. This means that non-deterministic operations must include all qubits in your program.
- Conditional statements themselves are deterministically scheduled by ensuring that an idle is inserted into either the true or false branch so that both branches are exactly the same duration.
Attention!
The simulator does not model runtime concurrency; it simply executes instructions sequentially regardless of which qubit the instruction uses. Therefore the :meth:`~dwave.gate.qcdl.QCDLModuleContainer.sync` instruction does not affect execution order of operations.
Consider carefully the positioning of any :meth:`~dwave.gate.qcdl.QCDLModuleContainer.sync` and cautiously validate when executing on a QPU (for example, by using a :meth:`~dwave.gate.qcdl.QCDLModuleContainer.append_table_row` instruction).
Programs may execute operations subject to a condition. You accomplish this in two discrete steps:
- Use classical logic to compute one bit of information and store it in a register. This is the branch condition.
- Create a true branch statement, and optionally a false branch statement. If the branch condition evaluates to 1, your true branch is executed; otherwise, the false branch (or the default idle) is executed.
QCDL supports several ways of setting a branch condition and branching from an instruction.
.. testcode::
from dwave.gate.qcdl import qcdl
from dwave.gate.qcdl.operations import measure, x
@qcdl(2)
def branch_example1(q0, q1):
measure(q1)
with q0.If(condition=q1):
x(q0)
In the preceding example, the :func:`~dwave.gate.qcdl.operations.x` gate is executed
if the most recent measurement of q1 was a 1. Here, the unspecified
false branch---executed if the most recent measurement of q1 was a 0
or a *---is an idle of equal duration to the true branch.
The next example specifies a false branch. A :func:`~dwave.gate.qcdl.operations.y`
gate is executed in the case of a 0 or a * instead of the default
idle.
.. testcode::
from dwave.gate.qcdl import qcdl
from dwave.gate.qcdl.operations import measure, x, y
@qcdl(2)
def branch_example2(q0, q1):
measure(q1)
with q0.If(condition=q1) as Else:
x(q0)
with Else():
y(q0)
| Condition | Description | Notes |
|---|---|---|
q{N} |
The Nth qubit's most recent measurement. Branches if the last measurement for the qubit is 1. | Volatile and may change at the next measurement. |
| Expressions | A classical expression evaluated at runtime (e.g. reg2 < 5). |
See the :ref:`qcdl_basic_registers_arithmetic` section for details. |
| Signal | Specify q{N}.signal to branch off of the bit that q{N} is
currently signaling. |
See the :ref:`qcdl_advanced_signals` section for details. |
None |
You can separate the steps of evaluating the branch condition and
branching by assigning the branch condition before the statement
(e.g., :meth:`~dwave.gate.qcdl.QCDLModuleContainer.If`), with
that branch condition persisting if None is specified. The
branch condition might be set, for example, by a preceding
:meth:`~dwave.gate.qcdl.QCDLModuleContainer.all_to_all` or
:meth:`~dwave.gate.qcdl.QCDLModule.one_to_all` call, which
places a signal from one qubit onto the branch condition of each
recipient qubit. |
See the :ref:`qcdl_advanced_signals` section. |
True or False |
Python Boolean that deterministically selects a branch taken by all qubits. | For troubleshooting. |
- You can nest conditional statements arbitrarily deep.
- Place the :meth:`~dwave.gate.qcdl.QCDLModuleContainer.If` statement and its true and false branches in the same procedure.
- Use the :class:`~dwave.gate.qcdl.Scope` class to include an arbitrary number of qubits in a condition. If your condition value is an expression, take care that it evaluates to the same outcome for all qubits.
- Compilation does not guarantee that a qubit has been measured before it is used in a conditional. Use with caution.
- Since a condition can be a Boolean, if you do not intend that, be careful that your Python code does not inadvertently cast the condition to a Boolean. (You may find the output of :func:`~dwave.gate.utils.display.print_qcdl` helpful for this.)
- Your true and false branches must not contain operations on qubits that are not a part of the conditional branch.
This example detects and resets a qubit if it has been erased.
.. testcode::
from dwave.gate.qcdl import qcdl
from dwave.gate.qcdl.operations import mced
@qcdl(1)
def detect_erasure_example(q0):
erased = q0.Register(name="erased")
erased <<= 0
mced(q0, register=erased)
with q0.If(erased == 1):
q0.reset()
This example conditions on a classical register.
.. testcode::
from dwave.gate.qcdl import qcdl, Scope
from dwave.gate.qcdl.operations import mced, x
@qcdl(2)
def classical_condition_example(q0, q1):
sc = Scope(q0, q1)
c0 = sc.Register(2, name="c0")
c1 = sc.Register()
# operations that update these registers
with q1.If( c0 | c1 == 1 ):
x(q1)
This example updates all registers with the outcome of a particular measurement.
.. testcode::
from dwave.gate.qcdl import qcdl, Scope
from dwave.gate.qcdl.operations import mced, x
@qcdl(2)
def classical_condition_example(q0, q1):
sc = Scope(q0, q1)
register = sc.Register(name="register")
measure(q0)
with sc.If(q0):
register += 1
A :ref:`register <qcdl_basic_registers_arithmetic>` is associated with a qubit. Your QCDL must ensure the information in any qubit's register is visible to all qubits (:ref:`mirror <qcdl_advanced_registers_mirroring>` the information) in order, for example, to select the same branch to execute for a :ref:`conditional statement <qcdl_advanced_conditionals>`.
This requires that you pass some information from the memory associated with one qubit to that of another, in particular results of a measurement on one qubit that condition operations on other qubits.
For any qubit, you can set the :attr:`~dwave.gate.qcdl.QCDLModule.signal` property (the signal) to a Boolean value for use, in realtime, as a :ref:`branch condition <qcdl_advanced_conditionals>` by other qubits.
The following example results in the bitstring being either 11 or
00 (assuming no noise). The
:meth:`~dwave.gate.qcdl.QCDLModuleContainer.sync` operation prevents the
receiver qubit conditioning off of the signal value before the sender
qubit sets it after its measurement. The :ref:`qcdl_advanced_conditionals`
section describes the condition value used in the If statement.
.. todo:: Amos is updating the simulator for use of signal (see simulator ticket 503)
.. testcode::
from dwave.gate.qcdl import qcdl
from dwave.gate.qcdl.operations import h, measure, x
@qcdl(2)
def signal_example(q0, q1):
receiver = q1
sender = q0
h(q0)
send_register = q0.Register()
measure(q0, register=send_register)
sender.master(signal=send_register == 1)
sender.sync(receiver)
with receiver.If(sender.signal):
x(receiver)
measure(receiver)
Note
If more than one qubit is branching off a signal, it is likely more efficient to use the :meth:`~dwave.gate.qcdl.QCDLModule.one_to_all` method.
The :meth:`~dwave.gate.qcdl.QCDLModule.one_to_all` method signals a Boolean value from one qubit to a set of other qubits that can use it as a :ref:`branch condition <qcdl_advanced_conditionals>` to conditionally execute a branch of operations.
You accomplish this by specifying the following: (1) The origin qubit, (2) an expression for setting the Boolean value, and (3) the set of qubits that use the signal for a branch condition
This example results in the bitstring being either 000 or 111 (absent
noise). The :ref:`qcdl_advanced_conditionals` section describes the condition
value used in the If statement.
.. testcode::
from dwave.gate.qcdl import qcdl
from dwave.gate.qcdl.operations import h, measure, x
@qcdl(3)
def one_to_all_example(q0, q1, q2):
h(q0)
send_register = q0.Register()
measure(q0, register=send_register)
sc = Scope(q1, q2)
q0.one_to_all(sc.qcdl_modules, send_register == 1)
with sc.If(None):
x(q1)
x(q2)
measure(q1)
measure(q2)
The more-general :meth:`~dwave.gate.qcdl.QCDLModuleContainer.all_to_all` method signals a Boolean value from all qubits to a set of participating qubits to use as a :ref:`branch condition <qcdl_advanced_conditionals>`.
You accomplish this by specifying the following: (1) A set of qubits that all contribute one bit to the signal, (2) a reduction operator used to compute a Boolean value from those bits. The resulting Boolean value conditions all participating qubits.
This example results in the bitstring being either 000 or 111
(absent noise). The :ref:`qcdl_advanced_conditionals` section describes the
condition value used in the If statement here and in subsequent examples.
.. testcode::
from dwave.gate.qcdl import qcdl
from dwave.gate.qcdl.operations import h, measure, x
@qcdl(3)
def all_to_all_example(q0, q1, q2):
sc = Scope(q0, q1, q2)
h(q0)
name = "bit"
# all qubits have a copy of the same register:
send_register = sc.Register(name=name)
# set the register on q0 to 0 or 1
measure(q0, register=q0.Register(name=name))
# if any of the copies of the register are equal to 1, then all
# will receive a condition of True.
sc.all_to_all(send_register == 1, reduce_op="|")
with sc.If(None):
x(q1)
x(q2)
measure(q1)
measure(q2)
In the next example, if any register has value 1, update all the registers to be 1, otherwise, set them to 0.
.. testcode::
from dwave.gate.qcdl import qcdl, Scope
@qcdl(3)
def all_to_all_example2(q0, q1, q2):
sc = Scope(q0, q1, q2)
register = sc.Register(name="register")
# operations
sc.all_to_all(register == 1, reduce_op="|")
with sc.If(None) as Else:
register <<= 1
with Else():
register <<= 0
The next example loops until a qubit has been erased. The :ref:`qcdl_advanced_control_flow` section describes QCDL control-flow methods.
.. testcode::
from dwave.gate.qcdl import qcdl, Scope
from dwave.gate.qcdl.operations import mced
@qcdl(2)
def all_to_all_example3(q0, q1):
sc = Scope(q0, q1)
erased = sc.Register(name="erased")
with sc.DoWhile(None):
for q in [q0, q1]:
mced(q, register=erased)
sc.all_to_all(erased == 0, reduce_op="&")
The next example demonstrates an active reset, looping until all qubits are in a \ket 0 state.
.. todo:: The next example needs to be fixed
.. testcode::
:skipif: True
from dwave.gate.qcdl import qcdl, Register, Scope
from dwave.gate.qcdl.operations import mced, measure, x
@qcdl(2)
def all_to_all_example4(q0, q1):
name = "in_0"
sc = Scope(q0, q1)
in_0 = sc.Register(name=name)
with sc.DoWhile(None):
in_0 <<= 1
for q in sc.qubits:
measure(q)
with q.If(q):
x(q)
Register(q, name=name) <<= 0
# if any were not in 0, iterate again
sc.all_to_all(send=in_0==0, reduce_op="|")
QCDL programs support several control-flow mechanisms.[4]
| Method | Purpose |
|---|---|
| :meth:`~dwave.gate.qcdl.QCDLModuleContainer.Repeat` | Repeats the body of the context manager for a specified number of iterations. |
| :meth:`~dwave.gate.qcdl.QCDLModuleContainer.While` | Repeats the body of the context manager as long as the condition is true. |
| :meth:`~dwave.gate.qcdl.QCDLModuleContainer.DoWhile` | Unconditionally executes a first iteration of the context manager and then, similar to the :meth:`~dwave.gate.qcdl.QCDLModuleContainer.While` method, repeats the body of the context manager as long as the condition is true. Useful if the condition is evaluated only within the loop. |
| :meth:`~dwave.gate.qcdl.QCDLModuleContainer.For` | Repeats the body of the context manager as long as the condition is true, similar to the :meth:`~dwave.gate.qcdl.QCDLModuleContainer.While` method, but, similarly to a C-style loop, also provides some convenience mechanisms for initializing and updating a register. |
| :meth:`~dwave.gate.qcdl.QCDLModuleContainer.Break` | Breaks out of a loop structure. Useful for preventing infinite loops. |
| :meth:`~dwave.gate.qcdl.QCDLModuleContainer.Continue` | Skips the remainder of the body of the context manager and jumps to the conditional. |
| :meth:`~dwave.gate.qcdl.QCDLModuleContainer.Label`/:meth:`~dwave.gate.qcdl.QCDLModuleContainer.Goto` | A :meth:`~dwave.gate.qcdl.QCDLModuleContainer.Goto` instruction unconditionally jumps to the location marked by the corresponding :meth:`~dwave.gate.qcdl.QCDLModuleContainer.Label` instruction. |
| :meth:`~dwave.gate.qcdl.QCDLModuleContainer.Return` | Exits a procedure early. |
| [4] | These are all abstractions built on top of an underlying goto and label mechanism. |
Attention!
If your QCDL lets only a subset of qubit branches execute a jump, these powerful control-flow expressions risk desychronizing operations on qubits, as noted in the :ref:`qcdl_advanced_synchronization` section. These control-flow operations are recommended only in a :meth:`~dwave.gate.qcdl.Scope` that includes all of the qubits.
.. testcode::
from dwave.gate.qcdl import procedure, qcdl, Scope
from dwave.gate.qcdl.operations import rx, ry
@procedure
def rotate(q0, q1, increment):
rx(q0, increment)
ry(q1, increment)
@qcdl(2)
def repeat_example(q0, q1):
sc = Scope(q0, q1)
num_iterations = 5
with sc.Repeat(num_iterations):
rotate(q0, q0, 0.1)
The following is also an example for how a :meth:`~dwave.gate.qcdl.QCDLModuleContainer.Repeat` instruction could be implemented.
.. testcode::
from dwave.gate.qcdl import procedure, qcdl, Scope
@qcdl(2)
def dowhile_example(q0, q1):
sc = Scope(q0, q1)
counter = sc.Register(name="counter")
num_iterations = 5
counter <<= num_iterations
with sc.DoWhile(counter > 0):
counter -= 1
A register is associated with a qubit. When you create a :class:`~dwave.gate.qcdl.registers.Register` object for the qubits of a :class:`~dwave.gate.qcdl.Scope` class, it is implemented as a collection of registers for the qubits in the scope. And when you assign that :class:`~dwave.gate.qcdl.registers.Register` to a :func:`~dwave.gate.qcdl.operations.measure` or :func:`~dwave.gate.qcdl.operations.mced` operation, only the register associated with the measured qubit is updated (the measurement outcome is immediately written to that register).
Your QCDL must ensure the information in any qubit's register is visible to all qubits in the :class:`~dwave.gate.qcdl.Scope` or :class:`~dwave.gate.qcdl.registers.Register` object (mirror the information). This is needed, for example, to select the same branch to execute for a :ref:`conditional statement <qcdl_advanced_conditionals>`.
Mirroring requires an extra communication step to ensure that registers associated with all other qubits of the :class:`~dwave.gate.qcdl.registers.Register` object are also updated.
For simplicity, you can use the following two cooperative techniques to implement mirroring.
- Execute classical calculations redundantly when possible; for example, use a :ref:`scope <qcdl_advanced_scope>` to instantiate registers for all qubits or, for each qubit, give its register the same name and execute the same operations.
- Communicate non indentical information (see the :ref:`qcdl_advanced_signals` section). With the :class:`~dwave.gate.qcdl.Scope` class, the only information you must update across registers at runtime are (non-deterministic) measurement/MCED results.
- Expressions between registers in different scopes are not supported.
- Use the
mirrorparameter in the :func:`~dwave.gate.qcdl.operations.mced` or :func:`~dwave.gate.qcdl.operations.measure` operations to propagate measurements among registers. - Always use the same scope for conditional statements and register instantiation. A good practice is to instantiate one :class:`~dwave.gate.qcdl.Scope` object at the start of your QCDL that contains all qubits and use that scope for registers and loops, making other scopes only for small tasks.
- It is technically possible to compose a conditional expression using registers such that some qubits go to a true branch and others to a false branch. This is not recommended and the simulator raises an exception.
.. seealso::
:func:`~dwave.gate.implementations.mirror_bool_register` and
:func:`~dwave.gate.implementations.mirror_measurement_register`
functions
the |cloud|_ service provides a Monte Carlo simulator of QCDL programs. This is built on top of Qiskit's AerStatevector.
This simulator closely models the classical and quantum operation of the QPU with varying approximations. It instantiates a "state" representing both classical and quantum components of the hardware and then executes your QCDL instructions one at a time to update that state. As a Monte Carlo simulator, it is significantly slower than a "sampling" simulator and scales linearly with the number of shots; however, its operation is embarrassingly parallel.
Tip
Accuracy bears a simulation cost and error handling increases circuit complexity. It is advisable to start circuit development against the ideal simulator and then introduce error modeling.
The simulator in the |cloud|_ service supports two modes of simulations. The following table compares these two simulation modes.
| Characteristic | Statevector Simulation | Dual-Rail Erasure Simulation |
|---|---|---|
| Noise model. | Solver parameter :ref:`parameter_drsim_noise_model` set to
Useful during initial testing of QCDL programs before introducing noise. |
Solver parameter :ref:`parameter_drsim_noise_model` set to
This simulation is useful for exploring the impact of erasures on QCDL programs. It operates by randomly applying Pauli errors, leakages, and seepages after quantum gates and idles. |
| Runtime. | Scales as O(s*g*2^n) where n is the number of qubits, s the number of shots, and g the number of gates. | Slower but same scaling. |
| Supported gates. | All gates available in Qiskit (no transpilation required). | Subset of gates (transpilation required). |
| Support for errors. | No support. (Returns 0 for mced, signifying no leak.) |
Supports the mced instruction to detect if the qubit has been
erased, and the leak and seep instructions to simulate
leakage and seepage errors. |
The :ref:`QPU simulator <qcdl_simulator>` in the |cloud|_ service is intended to simulate :ref:`gate-model quantum computers <qpu_gate_model_intro>` by executing programs formulated as QCDL.
The following documentation describes how to work with the |cloud|_ service:
- The :ref:`index_leap_sapi` section describes the |cloud|_ service.
- The :ref:`ocean_leap_authorization` section walks you through authorizing your Ocean client to access the simulator in the |cloud|_ service.
- The :ref:`ocean_install` section explains how to install Ocean software.
Descriptions of the supported parameters and simulator properties are provided in the :ref:`qcdl_simulator_parameters` and :ref:`qcdl_simulator_properties` sections.
The example below submits the following Bell state QCDL program.
.. testcode::
from dwave.gate.qcdl import qcdl
from dwave.gate.qcdl.operations import cx, h, measure
@qcdl(2)
def bell_program(q0, q1):
h(q0)
cx(q0, q1)
measure(q0)
measure(q1)
simulator_job_submission = bell_program()
Submit the program above to a simulator for a dual-rail QPU with 17 qubits,
DRsim_17qubits, in the |cloud|_ service.
>>> from dwave.gate.leap import LeapQCDLSimulator
...
>>> simulator = LeapQCDLSimulator() # doctest: +SKIP
>>> future = simulator.run( # doctest: +SKIP
... simulator_job_submission,
... qpu='DRsim_17qubits')
>>> result = future.result().result # doctest: +SKIPThe returned result is a 3D array of (measurements per shot, shots, qubits).
>>> print(result.get_memory().shape) # doctest: +SKIP
(1, 1000, 2).. todo:: describe the results
The examples in this section submit the QCDL program defined in the :ref:`qcdl_submitting_programs_example` section.
Boolean flag that applies a noise model.
noise_model=True: Apply a noise model.noise_model=False: Do not apply a noise model (simulate an ideal QPU, as described in the :ref:`qcdl_simulator` section).
The default value is specified by the :ref:`property_drsim_default_noise_model` property.
This example applies a noise model for the program submitted to the simulator.
>>> from dwave.gate.leap import LeapQCDLSimulator
...
>>> simulator = LeapQCDLSimulator() # doctest: +SKIP
>>> future = simulator.run( # doctest: +SKIP
... simulator_job_submission,
... noise_model=True)
>>> result = future.result().result # doctest: +SKIPThe QPU to simulate, formatted as a string.
The :ref:`property_drsim_supported_qpu_strings` property lists the supported values. The default QPU to simulate is specified by the :ref:`property_drsim_default_qpu` property.
This example submits a QCDL program to a dual-rail QPU simulator with 21 qubits,
DRsim_21qubits .
>>> from dwave.gate.leap import LeapQCDLSimulator
...
>>> simulator = LeapQCDLSimulator() # doctest: +SKIP
>>> future = simulator.run( # doctest: +SKIP
... simulator_job_submission,
... qpu='DRsim_21qubits')
>>> result = future.result().result # doctest: +SKIPBoolean flag to run the circuit repeatedly until a target number of non-erased measurements are accumulated.
Running a circuit might return, under noisy conditions, measurements that are declared to be “erased”, as described in the :ref:`qcdl_basic_measurements` section. To try to achieve the required number of non-erasure measurements indicted by the :ref:`parameter_drsim_shots` parameter, as counted after post-selection to remove "splats" (see the :ref:`qcdl_basic_result_records` section), you can select to repeatedly run the circuit. With yield defined as the percentage of shots without erasures, the required number of executions (and runtime) is proportional to the value of the :ref:`parameter_drsim_shots` parameter and the reciprocal of the yield, and grows exponentially with increased noise.
repeat_until_shots_requested=True: Repeatedly run the circuit until the requested number of non-erasure measurements, indicated by the :ref:`parameter_drsim_shots` parameter, is accumulated withpost_select=True. Under noisy conditions the circuit might be executed a greater number of times than set by the :ref:`parameter_drsim_shots` parameter.repeat_until_shots_requested=False: Run the circuit the number of times set by the :ref:`parameter_drsim_shots` parameter. Under noisy conditions, fewer non-erasure measurements than indicated by the :ref:`parameter_drsim_shots` parameter might be accumulated withpost_select=True.
The default value is set by the :ref:`property_drsim_default_repeat_until_shots_requested` property.
If runtime exceeds the value you specified in the :ref:`parameter_drsim_time_limit` parameter (or the default value of the :ref:`property_drsim_default_time_limit_s` property), execution terminates.
This example repeatedly executes the circuit, under noisy conditions, to accumulate 10 non-erasure measurements.
>>> from dwave.gate.leap import LeapQCDLSimulator
...
>>> simulator = LeapQCDLSimulator() # doctest: +SKIP
>>> future = simulator.run( # doctest: +SKIP
... simulator_job_submission,
... shots=10,
... noise_model=True,
... repeat_until_shots_requested=True)
>>> result = future.result().result # doctest: +SKIPThe sum of non-splat states in the returned results is the requested number of shots:
>>> print(sum(result.get_counts(post_select=True)[0].values())) # doctest: +SKIP
10The number of measurements to run, formatted as an integer.
Your QCDL program is executed once for each requested measurement.
The specified value must not exceed the value of the :ref:`property_drsim_maximum_shots` property. Execution time is limited by the value you specified in the :ref:`parameter_drsim_time_limit` parameter (or the default value of the :ref:`property_drsim_default_time_limit_s` property).
The default value is to measure the number of times specified by the :ref:`property_drsim_default_shots` property.
This example executes the circuit 1000 times.
>>> from dwave.gate.leap import LeapQCDLSimulator
...
>>> simulator = LeapQCDLSimulator() # doctest: +SKIP
>>> future = simulator.run( # doctest: +SKIP
... simulator_job_submission,
... shots=1000)
>>> result = future.result().result # doctest: +SKIPSpecifies the maximum runtime, in seconds, the solver is allowed to work on the given program. Can be a float or integer.
The specified time must be between the values of the :ref:`property_drsim_maximum_time_limit_s` and :ref:`property_drsim_minimum_time_limit_s` properties.
The default runtime limit is specified by the :ref:`property_drsim_default_time_limit_s` property.
This example sets a maximum runtime of 10 minutes.
>>> from dwave.gate.leap import LeapQCDLSimulator
...
>>> simulator = LeapQCDLSimulator() # doctest: +SKIP
>>> future = simulator.run( # doctest: +SKIP
... simulator_job_submission,
... time_limit=10*60)
>>> result = future.result().result # doctest: +SKIPBoolean flag to rewrite the submitted QCDL circuit to use the QPU's supported basis gates and topology, as described in the :ref:`qcdl_basic_transpilation` section.
transpile=True: Transpile the circuit.transpile=False: Run the circuit exactly as specified in the submitted QCDL or return an error.
The default value is specified by the :ref:`property_drsim_default_transpile` property.
This example requires that the QCDL circuit be submitted as written to the simulator.
>>> from dwave.gate.leap import LeapQCDLSimulator
...
>>> simulator = LeapQCDLSimulator() # doctest: +SKIP
>>> future = simulator.run( # doctest: +SKIP
... simulator_job_submission,
... noise_model=True,
... transpile=False)
>>> result = future.result().result # doctest: +SKIPType of solver, as a string.
software-gate: Gate-model simulator.
>>> from dwave.gate.leap import LeapQCDLSimulator
...
>>> simulator = LeapQCDLSimulator() # doctest: +SKIP
>>> simulator.properties["category"] # doctest: +SKIP
'software-gate'Default setting for the application of a noise model, as a Boolean.
True: A noise model is applied.False: Simulates an ideal QPU, as described in the :ref:`qcdl_simulator` section.
>>> from dwave.gate.leap import LeapQCDLSimulator
...
>>> simulator = LeapQCDLSimulator() # doctest: +SKIP
>>> simulator.properties["default_noise_model"] # doctest: +SKIP
FalseDefault selection of the QPU to simulate, as a string.
Supported QPUs are listed in the :ref:`property_drsim_supported_qpu_strings` property.
>>> from dwave.gate.leap import LeapQCDLSimulator
...
>>> simulator = LeapQCDLSimulator() # doctest: +SKIP
>>> simulator.properties["default_qpu"] # doctest: +SKIP
'DRsim_21qubits'Default setting, as a Boolean, for rerunning the circuit until the requested number of measurements is accumulated, where the accumulated measurements do not include erasures (see the :ref:`qcdl_basic_result_records` section).
True: Repeatedly rerun the circuit.False: Run the circuit the number of times set by the :ref:`parameter_drsim_shots` parameter.
>>> from dwave.gate.leap import LeapQCDLSimulator
...
>>> simulator = LeapQCDLSimulator() # doctest: +SKIP
>>> simulator.properties["default_repeat_until_shots_requested"] # doctest: +SKIP
FalseDefault setting for the number of measurements to run (times to execute your QCDL circuit), as an integer. With dual-rail QPUs, a measurement result can be a "splat" (see the :ref:`qcdl_basic_measurements` section).
>>> from dwave.gate.leap import LeapQCDLSimulator
...
>>> simulator = LeapQCDLSimulator() # doctest: +SKIP
>>> simulator.properties["default_shots"] # doctest: +SKIP
1000Default maximum runtime, in seconds, the solver is allowed to work on the given program, as a float.
>>> from dwave.gate.leap import LeapQCDLSimulator
...
>>> simulator = LeapQCDLSimulator() # doctest: +SKIP
>>> simulator.properties["default_time_limit_s"] # doctest: +SKIP
2700Default setting, as a Boolean, for :ref:`transpiling <qcdl_basic_transpilation>` the submitted QCDL program.
True: Transpile the program.False: Run the circuit exactly as specified in the submitted QCDL or return an error.
>>> from dwave.gate.leap import LeapQCDLSimulator
...
>>> simulator = LeapQCDLSimulator() # doctest: +SKIP
>>> simulator.properties["default_transpile"] # doctest: +SKIP
TrueMaximum number of qubits for QCDL circuits, as an integer.
Note
:ref:`Transpilation <qcdl_basic_transpilation>` can add and remove qubits in your QCDL.
>>> from dwave.gate.leap import LeapQCDLSimulator
...
>>> simulator = LeapQCDLSimulator() # doctest: +SKIP
>>> simulator.properties["maximum_num_qubits"] # doctest: +SKIP
21Maximum value of the :ref:`parameter_drsim_shots` you can specify, as an integer.
>>> from dwave.gate.leap import LeapQCDLSimulator
...
>>> simulator = LeapQCDLSimulator() # doctest: +SKIP
>>> simulator.properties["maximum_shots"] # doctest: +SKIP
1000000Maximum time, in seconds as a float, that your submitted circuit can run.
This value limits the range of values you can set on the :ref:`parameter_drsim_time_limit` parameter.
>>> from dwave.gate.leap import LeapQCDLSimulator
...
>>> simulator = LeapQCDLSimulator() # doctest: +SKIP
>>> simulator.properties["maximum_time_limit_s"] # doctest: +SKIP
2700Minimum number of times the circuit can be executed, as an integer.
>>> from dwave.gate.leap import LeapQCDLSimulator
...
>>> simulator = LeapQCDLSimulator() # doctest: +SKIP
>>> simulator.properties["minimum_shots"] # doctest: +SKIP
1Minimum time, in seconds as a float, you can specify for the runtime limit (the :ref:`parameter_drsim_time_limit` parameter) on your submitted circuit.
>>> from dwave.gate.leap import LeapQCDLSimulator
...
>>> simulator = LeapQCDLSimulator() # doctest: +SKIP
>>> simulator.properties["minimum_time_limit_s"] # doctest: +SKIP
1Rate at which user or project quota is consumed for the solver as a ratio to QPU solver usage. Different solver types may consume quota at different rates.
Time is deducted from your quota according to:
\frac{num\_seconds}{quota\_conversion\_rate}
See the :ref:`leap_hybrid_usage_charges` section for more information.
>>> from dwave.gate.leap import LeapQCDLSimulator
...
>>> simulator = LeapQCDLSimulator() # doctest: +SKIP
>>> simulator.properties["quota_conversion_rate"] # doctest: +SKIP
1Names of supported simulators, as a list of strings.
Available QPUs are the following:
DRsim_17qubits: Dual-rail QPU with 17 qubits.DRsim_21qubits: Dual-rail QPU with 21 qubits.
>>> from dwave.gate.leap import LeapQCDLSimulator
...
>>> simulator = LeapQCDLSimulator() # doctest: +SKIP
>>> simulator.properties["supported_qpu_strings"] # doctest: +SKIP
['DRsim_17qubits', 'DRsim_21qubits']