Usage as a CLI tool#
Using PyQBench as a library allows one to conduct, in principle, arbitrary two-qubit von Neumann measurement experiment. However, as discussed in the previous guide, it requires some amount of boilerplate code.
However, for a Fourier parametrized family of measurements, PyQBench offers a simplified way of performing the experiment using a Command Line Interface (CLI).
Overview of CLI workflow#
Experiments that can be run using qbench cli tool are separate into stages. This is a design
choice that ensures a reasonable level of fault-tolerance. If at any stage fails (e.g. due to
network error), it can be repeated without running everything from the beginning.
The workflow is best describe as a list of steps:
You prepare configuration files describing backend and the experiment scenario.
Submit/run experiments. Depending on the experiment scenario, execution can be synchronous, or asynchronous.
(optional) If the execution is asynchronous, you can check status of the submitted jobs.
For asynchronous jobs, you need to resolve jobs into actual measurements.
Obtained measurements are used for computing discrimination probabilities and outputting the resulting table.
Defining the experiment#
Experiments are defined in YAML files. An example experiment definition looks as follows.
type: discrimination-fourier
qubits:
- target: 0
ancilla: 1
- target: 2
ancilla: 3
angles:
start: 0
stop: 2 * pi
num_steps: 50
gateset: ibmq
method: direct_sum
num_shots: 100
Let’s break it down:
The
typedescribes type of the experiment. Currently, the only option fortypeisdiscrimination-fourier. In the future, other benchmarks may be added, in which keys thetypeskey should contain their name.The
qubitslist describes pair of qubits on which the experiment should be run. In our example, the benchmark will run on qubits 0, 1 and 2, 3. Notice that we distinguish between ancilla and target qubit, so e.g.qubits: - target: 0 ancilla: 1
is not the same as
qubits: - ancilla: 0 target: 1
The
angleskey describe range of angles for Fourier parametrized family. The range is always uniform, starts at thestart, ends atstopand containsnum_stepspoints, including bothstartandstop. The start and stop can be arithmetic expressions usingpiliteral. For instance range defined as:angles: start: 0 stop: 2 * pi num_steps: 3
would contain three angles, 0, \(\pi\) and \(2\pi\).
The
gatesetkey informs what gates can be used in the experiment. The possible choices are the same as forFourierComponents.The
methodcan take one of two values:direct_sumorpostselection, and as the name suggests, defines which method is used to implement the experiment.The
num_shotsdefines how many shots are performed in the experiment for particular angle, qubit pair and circuit.
For the purpose of calculating possible costs, this is how you compute total number of shots and
executed circuits. Let \(N\) be the number of qubit pairs in qubits key and let \(M\) be the number
of steps defined in the angles key. Then number of executed circuits is \(2MN\) for the
direct_sum method and \(4MN\) for the postselection method. Each of those circuits is executed
num_shots times.
Observe that the experiment file does not specify what backend to use. This way, the same experiment file can be used on multiple backends.
Defining the backend#
The backend is also specified in YAML file. But here’s where things get complicated. Different
backends may require different information to be used. For instance, IBMQ backends might require
hub, group and project, whereas backends from the Aer package do not. Hence, the exact format of
the YAML file depends on what backend one wants to use. Below are the available options.
IBMQ backends#
The description of IBMQ backend looks as follows, with the obvious meaning of most of the parameters:
name: ibmq_quito
asynchronous: true
provider:
hub: ibm-q
group: open
project: main
The only key that requires the explanation is asynchronous which determines whether
experiments will be run asynchronously or not. We recommend setting it to true, unless your
experiment is really small (several circuits total).
IBMQ backends typically require and access token to IBM Quantum Experience. It would be unsafe
to store them in plain text, and therefore the token is configured separately. Before running
the experiment, you should place your token in the IBMQ_TOKEN environmental variable.
Backends obtainable from simple providers#
In many cases, backend can be created using Provider.get_backend() method. For such cases,
PyQBench provides a simple description. For instance:
provider: qiskit_braket_provider:AWSBraketProvider
name: SV1
asynchronous: true
This backend will be created using code similar to the one below:
from qiskit_braket_provider import AWSBraketProvider
provider = AWSBraketProvider()
backend = provider.get_backend("SV1")
For this to work, the following conditions have to be satisfied:
providerkey has to contain full Python path of the provider class, where the class itself is separated from the module with:.The provider class needs to have parameter-less initializer.
The asynchronous key, as in other backends, determines if the backend will be used
asynchronously (true) or synchronously (false).
Note
If you are using AWSBraketProvider, you need to have the AWS CLI configured on your machine.
Backends constructed from a simple function call#
If your backend can be obtained by calling a simple function (without a need for creating a provider or other objects), you can define it as follows:
factory: mypackage.my_module:create_backend
asynchronous: false
args:
- backend_name
kwargs:
foo: bar
run_options:
baz: 123
This backend would be constructed using code similar to the one below:
from mypackage.my_module import create_backend
backend = create_backend("backend_name", foo="bar")
Whenever the circuit is run using this backend, an addition baz=123 keyword parameter would be
passed to backend.run method.
Except from the factory, other parameters are optional (e.g. you don’t have to provide args or
kwargs if your create_backend function does not have parameters).
As a practical example, this is how you would define local Braket backend:
factory: qiskit_braket_provider:BraketLocalBackend
args:
- "braket_sv"
Notes on using the asynchronous flag#
Some backends may not support asynchronous execution. This might be especially the case with local simulators. For asynchronous execution to work, the following conditions have to be met:
Jobs returned by the backend have unique
job_idJob object is retrievable from the backend using
backend.retrieve_jobmethod, even from another process (e.g. if the original process running the experiment has finished).
Since PyQBench cannot determine if the job retrieval works for given backend, your are responsible for checking it yourself.
Running the experiment and collecting measurements data#
Starting the experiment#
Running the experiment is done by using the following command line invocation:
qbench disc-fourier benchmark experiment_file.yaml backend_file.yaml
The output file will be printed to stdout. Optionally, the --output OUTPUT flag might be
provided to write the output to the OUTPUT file instead.
The result of running the above command can be twofold:
If backend is asynchronous, the output will contain intermediate data containing, amongst others, job _ids correlated with the circuit they correspond to.
If the backend is synchronous, the output will contain measurement data (bitstrings) for each of the circuits run.
Note
Synchronous benchmarks can take quite a lot of time. For your convenience, the progress bar will be displayed to provide an estimate on how long the benchmark will take. However, please remember that the estimate might be quite a bit off for backends that use external APIs or queues. This is because it is impossible to anticipate the future load of the external resources.
For asynchronous experiments, the output looks similar to the one below:
metadata:
experiment:
type: discrimination-fourier
qubits:
- {target: 0, ancilla: 1}
angles: {start: 0.0, stop: 6.283185307179586, num_steps: 3}
gateset: ibmq
method: postselection
num_shots: 100
backend_description:
provider: qiskit_braket_provider:AWSBraketProvider
name: SV1
run_options: {}
asynchronous: true
results:
- job: {aws_job_id: 'arn:aws:braket:eu-west-2:673402117850:quantum-task/e22f4792-a082-4ae1-af53-0f024792fd72;arn:aws:braket:eu-west-2:673402117850:quantum-task/79b23b9d-e48e-4249-867c-15dd889afac7;arn:aws:braket:eu-west-2:673402117850:quantum-task/dfa4f61a-9ae3-42d8-a1ba-93d4f126d1e4;arn:aws:braket:eu-west-2:673402117850:quantum-task/193adaba-7364-4947-962a-01c220105912;arn:aws:braket:eu-west-2:673402117850:quantum-task/07c89ced-08ab-4102-92eb-fb50f0a68b9f;arn:aws:braket:eu-west-2:673402117850:quantum-task/e0c0b9a5-8f18-4b49-aeba-540ccdebc56d;arn:aws:braket:eu-west-2:673402117850:quantum-task/688f1c6e-ef5d-4c16-b4f8-4c32051d6fe2;arn:aws:braket:eu-west-2:673402117850:quantum-task/70b9eb68-e739-4431-adaf-f3840058aef2;arn:aws:braket:eu-west-2:673402117850:quantum-task/2fc41fa7-114b-4690-bb59-744885ae7b01;arn:aws:braket:eu-west-2:673402117850:quantum-task/8a8cd36c-cabd-48e1-bd06-e8645dd5810c;arn:aws:braket:eu-west-2:673402117850:quantum-task/eb34e000-9410-428e-9fae-72a731841bb6;arn:aws:braket:eu-west-2:673402117850:quantum-task/25e5ed04-1386-4846-9b7b-ae7da5d23334'}
keys:
- [0, 1, id_v0, 0.0]
- [0, 1, id_v1, 0.0]
- [0, 1, u_v0, 0.0]
- [0, 1, u_v1, 0.0]
- [0, 1, id_v0, 3.141592653589793]
- [0, 1, id_v1, 3.141592653589793]
- [0, 1, u_v0, 3.141592653589793]
- [0, 1, u_v1, 3.141592653589793]
- [0, 1, id_v0, 6.283185307179586]
- [0, 1, id_v1, 6.283185307179586]
- [0, 1, u_v0, 6.283185307179586]
- [0, 1, u_v1, 6.283185307179586]
For synchronous experiment, this looks as follows (the file is truncated and showing only
several entries in the data section:
metadata:
experiment:
type: discrimination-fourier
qubits:
- target: 0
ancilla: 1
- target: 1
ancilla: 2
- target: 1
ancilla: 3
angles:
start: 0.0
stop: 6.283185307179586
num_steps: 21
gateset: ibmq
method: direct_sum
num_shots: 10000
backend_description:
name: ibmq_belem
asynchronous: true
provider:
group: open
hub: ibm-q
project: main
data:
- target: 0
ancilla: 1
phi: 0.0
results_per_circuit:
- name: id
histogram:
'00': 2727
'01': 2320
'10': 2701
'11': 2252
- name: u
histogram:
'00': 2583
'01': 2482
'10': 2639
'11': 2296
- target: 0
ancilla: 1
phi: 0.3141592653589793
results_per_circuit:
- name: id
histogram:
'00': 2303
'01': 2023
'10': 3134
'11': 2540
# Truncated here
As you can see, the output is rather verbose, but don’t worry, we only describe it here for completeness, but you will rarely (probably never) have to inspect the file by yourself.
(Optional) Getting status of asynchronous jobs#
The whole point of running the asynchronous jobs is to be able to retrieve the results at a later point in time. But how do you know that the jobs you submitted to the backend have completed or not? You can of course, inspect the file manually and check e.g. IBMQ dashboard to see the statuses of the jobs. However, PyQBench provides a helper command that will fetch the statuses for you. The command is:
qbench disc-fourier status aysnc-results.yml
and it will display dictionary with histogram of statuses.
Resolving asynchronous jobs#
Before we can compute the discrimination probabilities, we have to obtain measurements from the submitted jobs. This is done using the following command:
qbench disc-fourier resolve async-results.yml resolved.yml
The resolved results, stored in resolved.yml, would look just like the experiment was run
synchronously. In other words, at this step you should have a file containing measurement
histograms, no matter if you run a synchronous or asynchronous experiment. We are ready to move
on to computing probabilities.
Computing probabilities#
To construct a table with discrimination probabilities, you can use the following command
qbench disc-fourier tabulate resolved.yml results.csv
The resulting csv will similarly to the one below:
target,ancilla,phi,disc_prob,mit_disc_prob
0,1,0.0,0.5009,0.5009414225941422
0,1,0.3141592653589793,0.56505,0.5680439330543934
0,1,0.6283185307179586,0.62695,0.6327928870292887
0,1,0.9424777960769379,0.69045,0.6992154811715481
0,1,1.2566370614359172,0.76165,0.7736924686192469
0,1,1.5707963267948966,0.81305,0.8274581589958159
As you can see, it contains a single row for each tuple of (target, ancilla, phi). The
disc_prob column will contain the discrimination probability for given configuration. The
mit_disc_prob column contains discrimination probability if Mthree mitigation was applied.
Note
The MThree mitigation requires knowledge of the stochastic matrix describing the measurement errors.
Currently, only the IBMQ backends provide this data, and other backends would require running
calibration circuits to obtain the matrix. Hence, the qbench CLI performs mitigation only for
IBMQ backends. If you wish to perform MThree mitigation with other backend, you need to use
PyQBench as a library (see our User guide to learn how to do this).