Configuration and Usage#
This notebook describes the basic concepts and configuration of Pulla.
Basics#
import os
from pprint import pprint
from qiskit import QuantumCircuit
from qiskit.compiler import transpile
from iqm.qiskit_iqm import IQMProvider
from iqm.qiskit_iqm.iqm_transpilation import optimize_single_qubit_gates
from iqm.pulla.utils_qiskit import qiskit_to_pulla
Now let’s create a Pulla object.
A Pulla object is conceptually an IQM quantum computer client for fetching calibration data and constructing a circuit-to-pulse compiler.. It consists of:
methods for fetching calibration sets from the server
methods for fetching metadata about the QC from the server
method for executing pulse-level instruction schedules (e.g. ones created by the compiler)
A Compiler object defines a particular circuit-to-pulse compilation logic. It consists of:
single calibration set
schedule builder based on the calibration set
circuit compilation options
information about the QC (chip topology, channel properties, etc.)
compilation stages
set of available circuit-level quantum operations (“native operations”) (including user-defined operations)
set of implementations for each native operation (including user-defined implementations)
methods for manipulating the calibration, operations, and implementations
Pulla can construct a standard compiler equivalent to the one used by the server side (CoCoS). You can also construct a Compiler manually.
To create an instance of Pulla, you need to provide the URL of Station Control. Upon successful initialization, some configuration data is printed (the verbosity of such messages will be controlled by a debug level value).
from iqm.pulla.pulla import Pulla
station_control_url = os.environ['PULLA_STATION_CONTROL_URL'] # or set the URL directly here
p = Pulla(station_control_url)
You can access various things like channel properties of the connected station, chip topology, or the current calibration. The initial calibration is the latest calibration of the connected station.
pprint(p.get_channel_properties()[0]['QB1__drive.awg'])
ChannelProperties(sample_rate=2000000000,
instruction_duration_granularity=8,
instruction_duration_min=8,
compatible_instructions=(),
is_iq=True,
is_virtual=False,
blocks_component=True)
p.get_chip_topology().get_coupler_for('QB1', 'QB3')
'TC-1-3'
Call get_standard_compiler()
method to get an instance of Compiler
. It will be pre-populated with the latest default calibration set and standard compilation stages. This standard compiler is the same one that CoCoS uses on the server side when processing circuits.
compiler = p.get_standard_compiler()
# get_calibration() returns the entire calibration set
# here we filter the data by 'QB1'
pprint({k: v for k, v in compiler.get_calibration().items() if k.startswith('QB1')})
{'QB1.drive.awg.trigger_delay': 5e-07,
'QB1.drive.frequency': 4125545769.6098022,
'QB1.flux.voltage': -0.19278427456300698,
'QB1.readout.average_response_e': 0.006816367663293804,
'QB1.readout.average_response_g': -0.0011533715161316659,
'QB1.readout.average_response_phase': -0.8662044568763143}
get_standard_compiler()
fetches the latest calibration set from the server. This network request takes a few moments. You may want to create many Compiler instances without such delay.
It may also be possible that, due to human error, the latest calibration set stored on the server is invalid (or incompatible with your version of Pulla or IQM Pulse). In that case get_standard_compiler()
will fail.
To avoid this, you can pass the calibration set manually, and Pulla will construct the compiler with it instead of getting one from the server. If you want to reuse the calibration of an existing compiler, call Compiler.get_calibration()
, e.g.:
compiler_new = p.get_standard_compiler(calibration_set=compiler_old.get_calibration())
You can also get a specific calibration set from the server if you know its UUID by calling Pulla.fetch_calibration_set_by_id()
, e.g.:
specific_cal_set = p.fetch_calibration_set_by_id('fe026208-19aa-4906-93ab-06ba3c86100f')
compiler = p.get_standard_compiler(calibration_set=specific_cal_set)
The compiler initializes with the following default configuration options:
circuit_boundary_mode='all'
measurement_mode='all'
heralding_mode='none'
dd_mode='none'
You can change them by changing the corresponding attributes of compiler.options
.
Complex readout#
For the constant
implementation of the measure
operation, the readout type is controlled by the acquisition_type
parameter. By default, it’s set to "threshold"
. Let’s change it to "complex"
. The full key in the calibration set dictionary is gates.measure.constant.QUBIT.acquisition_type
, where QUBIT
is the physical qubit name.
Note that we call get_calibration()
to get a copy of the compiler’s current calibration set, make changes to the copy, then replace the compiler’s set with the copy. The compiler always contains a single calibration set only.
cocos_url = os.environ['PULLA_COCOS_URL'] # or set the URL directly here
provider = IQMProvider(cocos_url)
backend = provider.get_backend()
shots = 10
qc = QuantumCircuit(3, 3)
qc.h(0)
qc.cx(0, 1)
qc.cx(0, 2)
qc.measure_all()
qc_transpiled = transpile(qc, backend=backend, layout_method='sabre', optimization_level=3)
qc_optimized = optimize_single_qubit_gates(qc_transpiled)
circuits, compiler = qiskit_to_pulla(p, backend, qc_optimized)
updated_cal_set = compiler.get_calibration()
for qubit in compiler.component_mapping.values():
updated_cal_set[f'gates.measure.constant.{qubit}.acquisition_type'] = 'complex'
compiler.set_calibration(updated_cal_set)
playlist, context = compiler.compile(circuits)
settings, context = compiler.build_settings(context, shots=shots)
response_data = p.execute(playlist, context, settings, verbose=False)
# execute() returns a StationControlResult object; the measurements are in StationControlResult.result
# in addition, by default execute() prints the measurement results; disable it with verbose=False
print(f"Raw results:\n{response_data.result}\n")
[10-31 18:58:18;I] Submitted sweep with ID: 50e6a9b0-b47c-4ef0-ab80-541b0b02eeba
[10-31 18:58:18;I] Created task in queue with ID: 7d93b241-9303-483a-ba57-23ee4fb012cc
[10-31 18:58:18;I] Sweep link: http://xld11-s2.xld11.iqm.fi/station/sweeps/50e6a9b0-b47c-4ef0-ab80-541b0b02eeba
[10-31 18:58:18;I] Task link: http://xld11-s2.xld11.iqm.fi/station/tasks/7d93b241-9303-483a-ba57-23ee4fb012cc
[10-31 18:58:18;I] Waiting for the sweep to finish...
[10-31 18:58:20;I] Sweep status: SweepStatus.SUCCESS
Raw results:
[{'meas_3_1_2': [[(0.017560029610991477+0.001747127801179886j)], [(0.020133029192686083+0.0008924271911382675j)], [(0.017855847001075745+0.0019635492861270905j)], [(0.0017019706815481186-0.0004723154604434967j)], [(-0.005224970668554306-0.00043420156836509706j)], [(0.0007658885419368744-0.0012639837712049484j)], [(0.0028541987389326096-0.0008985150754451752j)], [(0.017014767691493033+0.0010119934976100921j)], [(0.017034811615943907+0.0012639050036668778j)], [(0.015309534698724748-0.002708324134349823j)]], 'meas_3_1_1': [[(0.019376286864280702-0.00210743710398674j)], [(0.018009518772363665-0.0030516235083341598j)], [(0.018922824278473853-0.003862631008028984j)], [(0.0014579930305480958-0.004029262945055962j)], [(-0.0010851595550775528-0.005570073902606964j)], [(0.002359690770506859-0.006554983094334602j)], [(0.0009728277027606964-0.004944017618894577j)], [(0.018303910166025162-0.0023298319429159164j)], [(0.012900233909487725-0.0029298262149095537j)], [(0.0015736334770917893-0.00441809307038784j)]], 'meas_3_1_0': [[(0.0027815798074007033+0.0021424723714590075j)], [(0.012645499736070633-0.0006962650120258332j)], [(0.013543942973017692-0.002404656171798706j)], [(-0.0006662311404943466+0.00016822126507759095j)], [(3.840082883834839e-05-0.002238329127430916j)], [(0.0014284905046224594-0.003383114218711853j)], [(0.0003444172292947769+0.0008650016337633133j)], [(0.011626350373029708-0.0031556818485260012j)], [(0.01110577204823494-0.003783987507224083j)], [(0.01471160139143467-0.0015116431415081023j)]]}]