diff --git a/doc/source/tutorials/calibration.rst b/doc/source/tutorials/calibration.rst
index 2da46cd39..e4ed3f8bf 100644
--- a/doc/source/tutorials/calibration.rst
+++ b/doc/source/tutorials/calibration.rst
@@ -26,62 +26,7 @@ We start by initializing the platform, that reads the information written in the
respective runcard, a sequence composed of only a measurement and a sweeper
around the pre-defined frequency.
-.. testcode:: python
-
- import numpy as np
- from qibolab import create_platform
- from qibolab.pulses import PulseSequence
- from qibolab.sweeper import Sweeper, SweeperType, Parameter
- from qibolab.execution_parameters import (
- ExecutionParameters,
- AveragingMode,
- AcquisitionType,
- )
-
- # allocate platform
- platform = create_platform("dummy")
-
- # create pulse sequence and add pulse
- sequence = PulseSequence()
- readout_pulse = platform.create_MZ_pulse(qubit=0, start=0)
- sequence.add(readout_pulse)
-
- # allocate frequency sweeper
- sweeper = Sweeper(
- parameter=Parameter.frequency,
- values=np.arange(-2e8, +2e8, 1e6),
- pulses=[readout_pulse],
- type=SweeperType.OFFSET,
- )
-
-We then define the execution parameters and launch the experiment.
-
-.. testcode:: python
-
- options = ExecutionParameters(
- nshots=1000,
- relaxation_time=50,
- averaging_mode=AveragingMode.CYCLIC,
- acquisition_type=AcquisitionType.INTEGRATION,
- )
-
- results = platform.sweep(sequence, options, sweeper)
-
-In few seconds, the experiment will be finished and we can proceed to plot it.
-
-.. testcode:: python
-
- import matplotlib.pyplot as plt
-
- amplitudes = results[readout_pulse.serial].magnitude
- frequencies = np.arange(-2e8, +2e8, 1e6) + readout_pulse.frequency
-
- plt.title("Resonator Spectroscopy")
- plt.xlabel("Frequencies [Hz]")
- plt.ylabel("Amplitudes [a.u.]")
-
- plt.plot(frequencies, amplitudes)
- plt.show()
+.. literalinclude:: ./includes/calibration/calibration0.py
.. image:: resonator_spectroscopy_light.svg
:class: only-light
@@ -105,64 +50,7 @@ typical qubit spectroscopy experiment is as follows:
So, mainly, the difference that this experiment introduces is a slightly more
complex pulse sequence. Therefore with start with that:
-.. testcode:: python
-
- import numpy as np
- import matplotlib.pyplot as plt
- from qibolab import create_platform
- from qibolab.pulses import PulseSequence
- from qibolab.sweeper import Sweeper, SweeperType, Parameter
- from qibolab.execution_parameters import (
- ExecutionParameters,
- AveragingMode,
- AcquisitionType,
- )
-
- # allocate platform
- platform = create_platform("dummy")
-
- # create pulse sequence and add pulses
- sequence = PulseSequence()
- drive_pulse = platform.create_RX_pulse(qubit=0, start=0)
- drive_pulse.duration = 2000
- drive_pulse.amplitude = 0.01
- readout_pulse = platform.create_MZ_pulse(qubit=0, start=drive_pulse.finish)
- sequence.add(drive_pulse)
- sequence.add(readout_pulse)
-
- # allocate frequency sweeper
- sweeper = Sweeper(
- parameter=Parameter.frequency,
- values=np.arange(-2e8, +2e8, 1e6),
- pulses=[drive_pulse],
- type=SweeperType.OFFSET,
- )
-
-Note that the drive pulse has been changed to match the characteristics required
-for the experiment.
-
-We can now proceed to launch on hardware:
-
-.. testcode:: python
-
- options = ExecutionParameters(
- nshots=1000,
- relaxation_time=50,
- averaging_mode=AveragingMode.CYCLIC,
- acquisition_type=AcquisitionType.INTEGRATION,
- )
-
- results = platform.sweep(sequence, options, sweeper)
-
- amplitudes = results[readout_pulse.serial].magnitude
- frequencies = np.arange(-2e8, +2e8, 1e6) + drive_pulse.frequency
-
- plt.title("Resonator Spectroscopy")
- plt.xlabel("Frequencies [Hz]")
- plt.ylabel("Amplitudes [a.u.]")
-
- plt.plot(frequencies, amplitudes)
- plt.show()
+.. literalinclude:: ./includes/calibration/calibration1.py
.. image:: qubit_spectroscopy_light.svg
:class: only-light
@@ -200,58 +88,7 @@ two states.
This experiment serves to assess the effectiveness of single-qubit calibration
and its impact on qubit states in the IQ plane.
-.. testcode:: python
-
- import numpy as np
- import matplotlib.pyplot as plt
- from qibolab import create_platform
- from qibolab.pulses import PulseSequence
- from qibolab.sweeper import Sweeper, SweeperType, Parameter
- from qibolab.execution_parameters import (
- ExecutionParameters,
- AveragingMode,
- AcquisitionType,
- )
-
- # allocate platform
- platform = create_platform("dummy")
-
- # create pulse sequence 1 and add pulses
- one_sequence = PulseSequence()
- drive_pulse = platform.create_RX_pulse(qubit=0, start=0)
- readout_pulse1 = platform.create_MZ_pulse(qubit=0, start=drive_pulse.finish)
- one_sequence.add(drive_pulse)
- one_sequence.add(readout_pulse1)
-
- # create pulse sequence 2 and add pulses
- zero_sequence = PulseSequence()
- readout_pulse2 = platform.create_MZ_pulse(qubit=0, start=0)
- zero_sequence.add(readout_pulse2)
-
- options = ExecutionParameters(
- nshots=1000,
- relaxation_time=50_000,
- averaging_mode=AveragingMode.SINGLESHOT,
- acquisition_type=AcquisitionType.INTEGRATION,
- )
-
- results_one = platform.execute_pulse_sequence(one_sequence, options)
- results_zero = platform.execute_pulse_sequence(zero_sequence, options)
-
- plt.title("Single shot classification")
- plt.xlabel("I [a.u.]")
- plt.ylabel("Q [a.u.]")
- plt.scatter(
- results_one[readout_pulse1.serial].voltage_i,
- results_one[readout_pulse1.serial].voltage_q,
- label="One state",
- )
- plt.scatter(
- results_zero[readout_pulse2.serial].voltage_i,
- results_zero[readout_pulse2.serial].voltage_q,
- label="Zero state",
- )
- plt.show()
+.. literalinclude:: ./includes/calibration/calibration2.py
.. image:: classification_light.svg
:class: only-light
diff --git a/doc/source/tutorials/circuits.rst b/doc/source/tutorials/circuits.rst
index 7982e8696..ebe5aacea 100644
--- a/doc/source/tutorials/circuits.rst
+++ b/doc/source/tutorials/circuits.rst
@@ -3,54 +3,17 @@
Circuit execution
=================
-Qibolab can be used as a ``qibo`` backend for executing executions. The purpose
+Qibolab can be used as a ``qibo`` backend for executing circuits. The purpose
of this section is to show how to do it, without entering into the details of
circuits definition that we leave to the `Qibo
`_ documentation.
-.. testcode:: python
-
- import numpy as np
- import qibo
- from qibo import Circuit, gates
-
- np.random.seed(0)
-
- # create a single qubit circuit
- circuit = Circuit(1)
-
- # attach Hadamard gate and a measurement
- circuit.add(gates.GPI2(0, phi=np.pi / 2))
- circuit.add(gates.M(0))
-
- # execute on quantum hardware
- qibo.set_backend("qibolab", platform="dummy")
- hardware_result = circuit(nshots=5000)
-
- # retrieve measured probabilities
- freq = hardware_result.frequencies()
- p0 = freq["0"] / 5000 if "0" in freq else 0
- p1 = freq["1"] / 5000 if "1" in freq else 0
- hardware = [p0, p1]
-
- # execute with classical quantum simulation
- qibo.set_backend("numpy")
- simulation_result = circuit(nshots=5000)
-
- simulation = simulation_result.probabilities(qubits=(0,))
-
+.. literalinclude:: ./includes/circuits/circuits0.py
In this snippet, we first define a single-qubit circuit containing a single Hadamard gate and a measurement.
We then proceed to define the qibo backend as ``qibolab`` using the ``tii1q_b1`` platform.
Finally, we change the backend to ``numpy``, a simulation one, to compare the results with ideality.
-After executing the script we can print our results that will appear more or less as:
-
-.. testcode:: python
-
- print(f"Qibolab: P(0) = {hardware[0]:.2f}\tP(1) = {hardware[1]:.2f}")
- print(f"Numpy: P(0) = {simulation[0]:.2f}\tP(1) = {simulation[1]:.2f}")
-
-Returns:
+At the end of the script we can inpsect our printed our results:
.. testoutput:: python
:options: +NORMALIZE_WHITESPACE
@@ -58,68 +21,21 @@ Returns:
Qibolab: P(0) = 0.49 P(1) = 0.51
Numpy: P(0) = 0.50 P(1) = 0.50
+..
+ TODO: See https://github.com/qiboteam/qibolab/issues/828
+ and https://github.com/qiboteam/qibolab/pull/807
+
Clearly, we do not expect the results to be exactly equal due to the non
ideality of current NISQ devices.
.. note::
- Qibo circuits and gates are backend agnostic. The same circuit can be executed on multiple backends, including simulation and quantum platforms.
+ Qibo circuits and gates are backend agnostic. The same circuit can be
+ executed on multiple backends, including simulation and quantum platforms.
A slightly more complex circuit, a variable rotation, will produce similar
results:
-.. testcode:: python
-
- import matplotlib.pyplot as plt
- import numpy as np
- import qibo
- from qibo import Circuit, gates
-
-
- def execute_rotation():
- # create single qubit circuit
- circuit = Circuit(1)
-
- # attach Rotation on X-Pauli with angle = 0
- circuit.add(gates.GPI2(0, phi=0))
- circuit.add(gates.M(0))
-
- # define range of angles from [0, 2pi]
- exp_angles = np.arange(0, 2 * np.pi, np.pi / 16)
-
- res = []
- for angle in exp_angles:
- # update circuit's rotation angle
- circuit.set_parameters([angle])
-
- # execute circuit
- result = circuit.execute(nshots=4000)
- freq = result.frequencies()
- p0 = freq['0'] / 4000 if '0' in freq else 0
- p1 = freq['1'] / 4000 if '1' in freq else 0
-
- # store probability in state |1>
- res.append(p1)
-
- return res
-
-
- # execute on quantum hardware
- qibo.set_backend("qibolab", platform="dummy")
- hardware = execute_rotation()
-
- # execute with classical quantum simulation
- qibo.set_backend("numpy")
- simulation = execute_rotation()
-
- # plot results
- exp_angles = np.arange(0, 2 * np.pi, np.pi / 16)
- plt.plot(exp_angles, hardware, label="qibolab hardware")
- plt.plot(exp_angles, simulation, label="numpy")
-
- plt.legend()
- plt.ylabel("P(1)")
- plt.xlabel("Rotation [rad]")
- plt.show()
+.. literalinclude:: ./includes/circuits/circuits1.py
Returns the following plot:
@@ -137,6 +53,9 @@ QASM Execution
Qibolab also supports the execution of circuits starting from a QASM string. The QASM circuit:
+..
+ TODO: The OpenQASM code below does not correspond to the code above, I think.
+
.. testcode::
circuit = """// Generated by QIBO 0.2.4
@@ -158,6 +77,8 @@ can be executed by passing it together with the platform name to the :func:`qibo
result = execute_qasm(circuit, platform="dummy")
+The return type of :func:`qibolab.execute_qasm` is `qibo.result.MeasurementOutcome`.
+See `Qibo API `_ for more details.
C-API
-----
diff --git a/doc/source/tutorials/compiler.rst b/doc/source/tutorials/compiler.rst
index a445e8e7f..c5b689861 100644
--- a/doc/source/tutorials/compiler.rst
+++ b/doc/source/tutorials/compiler.rst
@@ -4,6 +4,7 @@ How to modify the default circuit compilation
=============================================
A Qibolab platform can execute pulse sequences.
+
As shown in :ref:`tutorials_circuits`, Qibo circuits can be executed by invoking the :class:`qibolab.backends.QibolabBackend`, which is the object integrating Qibolab to Qibo.
When a Qibo circuit is executed, the backend will automatically compile it to a pulse sequence, which will be sent to the platform for execution.
The default compiler outlined in the :ref:`main_doc_compiler` section will be used in this process.
@@ -38,24 +39,7 @@ It is possible to perform compiler manipulation in both approaches.
When using ``circuit(nshots=1000)``, Qibo is automatically initializing a ``GlobalBackend()`` singleton that is used to execute the circuit.
Therefore the previous manipulations can be done as follows:
-.. testcode:: python
-
- import qibo
- from qibo import gates
- from qibo.models import Circuit
- from qibo.backends import GlobalBackend
-
- # define circuit
- circuit = Circuit(1)
- circuit.add(gates.U3(0, 0.1, 0.2, 0.3))
- circuit.add(gates.M(0))
-
- # set backend to qibolab
- qibo.set_backend("qibolab", platform="dummy")
-
- # execute circuit
- result = circuit(nshots=1000)
-
+.. literalinclude:: ./includes/compiler/compiler0.py
Defining custom compiler rules
==============================
@@ -66,36 +50,7 @@ and returns the pulse sequence implementing this gate.
The following example shows how to modify the compiler in order to execute a circuit containing a Pauli X gate using a single pi-pulse:
-.. testcode:: python
-
- from qibo import gates
- from qibo.models import Circuit
- from qibolab.backends import QibolabBackend
- from qibolab.pulses import PulseSequence
-
- # define the circuit
- circuit = Circuit(1)
- circuit.add(gates.X(0))
- circuit.add(gates.M(0))
-
-
- # define a compiler rule that translates X to the pi-pulse
- def x_rule(gate, platform):
- """X gate applied with a single pi-pulse."""
- qubit = gate.target_qubits[0]
- sequence = PulseSequence()
- sequence.add(platform.create_RX_pulse(qubit, start=0))
- return sequence, {}
-
-
- # the empty dictionary is needed because the X gate does not require any virtual Z-phases
-
- backend = QibolabBackend(platform="dummy")
- # register the new X rule in the compiler
- backend.compiler[gates.X] = x_rule
-
- # execute the circuit
- result = backend.execute_circuit(circuit, nshots=1000)
+.. literalinclude:: ./includes/compiler/compiler1.py
The default set of compiler rules is defined in :py:mod:`qibolab.compilers.default`.
diff --git a/doc/source/tutorials/includes/calibration/calibration0.py b/doc/source/tutorials/includes/calibration/calibration0.py
new file mode 100644
index 000000000..37420af4e
--- /dev/null
+++ b/doc/source/tutorials/includes/calibration/calibration0.py
@@ -0,0 +1,50 @@
+import matplotlib.pyplot as plt
+import numpy as np
+
+from qibolab import create_platform
+from qibolab.execution_parameters import (
+ AcquisitionType,
+ AveragingMode,
+ ExecutionParameters,
+)
+from qibolab.pulses import PulseSequence
+from qibolab.sweeper import Parameter, Sweeper, SweeperType
+
+# allocate platform
+platform = create_platform("dummy")
+
+# create pulse sequence and add pulse
+sequence = PulseSequence()
+readout_pulse = platform.create_MZ_pulse(qubit=0, start=0)
+sequence.add(readout_pulse)
+
+# allocate frequency sweeper
+sweeper = Sweeper(
+ parameter=Parameter.frequency,
+ values=np.arange(-2e8, +2e8, 1e6),
+ pulses=[readout_pulse],
+ type=SweeperType.OFFSET,
+)
+
+## We then define the execution parameters and launch the experiment.
+
+options = ExecutionParameters(
+ nshots=1000,
+ relaxation_time=50,
+ averaging_mode=AveragingMode.CYCLIC,
+ acquisition_type=AcquisitionType.INTEGRATION,
+)
+
+results = platform.sweep(sequence, options, sweeper)
+
+## In few seconds, the experiment will be finished and we can proceed to plot it.
+
+amplitudes = results[readout_pulse.serial].magnitude
+frequencies = np.arange(-2e8, +2e8, 1e6) + readout_pulse.frequency
+
+plt.title("Resonator Spectroscopy")
+plt.xlabel("Frequencies [Hz]")
+plt.ylabel("Amplitudes [a.u.]")
+
+plt.plot(frequencies, amplitudes)
+plt.show()
diff --git a/doc/source/tutorials/includes/calibration/calibration1.py b/doc/source/tutorials/includes/calibration/calibration1.py
new file mode 100644
index 000000000..4fbc684fd
--- /dev/null
+++ b/doc/source/tutorials/includes/calibration/calibration1.py
@@ -0,0 +1,55 @@
+import matplotlib.pyplot as plt
+import numpy as np
+
+from qibolab import create_platform
+from qibolab.execution_parameters import (
+ AcquisitionType,
+ AveragingMode,
+ ExecutionParameters,
+)
+from qibolab.pulses import PulseSequence
+from qibolab.sweeper import Parameter, Sweeper, SweeperType
+
+# allocate platform
+platform = create_platform("dummy")
+
+# create pulse sequence and add pulses
+sequence = PulseSequence()
+drive_pulse = platform.create_RX_pulse(qubit=0, start=0)
+drive_pulse.duration = 2000
+drive_pulse.amplitude = 0.01
+readout_pulse = platform.create_MZ_pulse(qubit=0, start=drive_pulse.finish)
+sequence.add(drive_pulse)
+sequence.add(readout_pulse)
+
+# allocate frequency sweeper
+sweeper = Sweeper(
+ parameter=Parameter.frequency,
+ values=np.arange(-2e8, +2e8, 1e6),
+ pulses=[drive_pulse],
+ type=SweeperType.OFFSET,
+)
+
+## Note that the drive pulse has been changed to match the characteristics required
+## for the experiment.
+
+## We can now proceed to launch on hardware:
+
+options = ExecutionParameters(
+ nshots=1000,
+ relaxation_time=50,
+ averaging_mode=AveragingMode.CYCLIC,
+ acquisition_type=AcquisitionType.INTEGRATION,
+)
+
+results = platform.sweep(sequence, options, sweeper)
+
+amplitudes = results[readout_pulse.serial].magnitude
+frequencies = np.arange(-2e8, +2e8, 1e6) + drive_pulse.frequency
+
+plt.title("Qubit Spectroscopy")
+plt.xlabel("Frequencies [Hz]")
+plt.ylabel("Amplitudes [a.u.]")
+
+plt.plot(frequencies, amplitudes)
+plt.show()
diff --git a/doc/source/tutorials/includes/calibration/calibration2.py b/doc/source/tutorials/includes/calibration/calibration2.py
new file mode 100644
index 000000000..1c05bf8af
--- /dev/null
+++ b/doc/source/tutorials/includes/calibration/calibration2.py
@@ -0,0 +1,49 @@
+import matplotlib.pyplot as plt
+
+from qibolab import create_platform
+from qibolab.execution_parameters import (
+ AcquisitionType,
+ AveragingMode,
+ ExecutionParameters,
+)
+from qibolab.pulses import PulseSequence
+
+# allocate platform
+platform = create_platform("dummy")
+
+# create pulse sequence 1 and add pulses
+one_sequence = PulseSequence()
+drive_pulse = platform.create_RX_pulse(qubit=0, start=0)
+readout_pulse1 = platform.create_MZ_pulse(qubit=0, start=drive_pulse.finish)
+one_sequence.add(drive_pulse)
+one_sequence.add(readout_pulse1)
+
+# create pulse sequence 2 and add pulses
+zero_sequence = PulseSequence()
+readout_pulse2 = platform.create_MZ_pulse(qubit=0, start=0)
+zero_sequence.add(readout_pulse2)
+
+options = ExecutionParameters(
+ nshots=1000,
+ relaxation_time=50_000,
+ averaging_mode=AveragingMode.SINGLESHOT,
+ acquisition_type=AcquisitionType.INTEGRATION,
+)
+
+results_one = platform.execute_pulse_sequence(one_sequence, options)
+results_zero = platform.execute_pulse_sequence(zero_sequence, options)
+
+plt.title("Single shot classification")
+plt.xlabel("I [a.u.]")
+plt.ylabel("Q [a.u.]")
+plt.scatter(
+ results_one[readout_pulse1.serial].voltage_i,
+ results_one[readout_pulse1.serial].voltage_q,
+ label="One state",
+)
+plt.scatter(
+ results_zero[readout_pulse2.serial].voltage_i,
+ results_zero[readout_pulse2.serial].voltage_q,
+ label="Zero state",
+)
+plt.show()
diff --git a/doc/source/tutorials/includes/circuits/circuits0.py b/doc/source/tutorials/includes/circuits/circuits0.py
new file mode 100644
index 000000000..7469757f4
--- /dev/null
+++ b/doc/source/tutorials/includes/circuits/circuits0.py
@@ -0,0 +1,32 @@
+import numpy as np
+import qibo
+from qibo import Circuit, gates
+
+np.random.seed(0)
+
+# create a single qubit circuit
+circuit = Circuit(1)
+
+# attach Hadamard gate and a measurement
+circuit.add(gates.GPI2(0, phi=np.pi / 2))
+circuit.add(gates.M(0))
+
+# execute on quantum hardware
+qibo.set_backend("qibolab", platform="dummy")
+hardware_result = circuit(nshots=5000)
+
+# retrieve measured probabilities
+freq = hardware_result.frequencies()
+p0 = freq["0"] / 5000 if "0" in freq else 0
+p1 = freq["1"] / 5000 if "1" in freq else 0
+hardware = [p0, p1]
+
+# execute with classical quantum simulation
+qibo.set_backend("numpy")
+simulation_result = circuit(nshots=5000)
+
+simulation = simulation_result.probabilities(qubits=(0,))
+
+# print results
+print(f"Qibolab: P(0) = {hardware[0]:.2f}\tP(1) = {hardware[1]:.2f}")
+print(f"Numpy: P(0) = {simulation[0]:.2f}\tP(1) = {simulation[1]:.2f}")
diff --git a/doc/source/tutorials/includes/circuits/circuits1.py b/doc/source/tutorials/includes/circuits/circuits1.py
new file mode 100644
index 000000000..f414240a8
--- /dev/null
+++ b/doc/source/tutorials/includes/circuits/circuits1.py
@@ -0,0 +1,51 @@
+import matplotlib.pyplot as plt
+import numpy as np
+import qibo
+from qibo import Circuit, gates
+
+
+def execute_rotation():
+ # create single qubit circuit
+ circuit = Circuit(1)
+
+ # attach Rotation on X-Pauli with angle = 0
+ circuit.add(gates.GPI2(0, phi=0))
+ circuit.add(gates.M(0))
+
+ # define range of angles from [0, 2pi]
+ exp_angles = np.arange(0, 2 * np.pi, np.pi / 16)
+
+ res = []
+ for angle in exp_angles:
+ # update circuit's rotation angle
+ circuit.set_parameters([angle])
+
+ # execute circuit
+ result = circuit.execute(nshots=4000)
+ freq = result.frequencies()
+ p0 = freq["0"] / 4000 if "0" in freq else 0
+ p1 = freq["1"] / 4000 if "1" in freq else 0
+
+ # store probability in state |1>
+ res.append(p1)
+
+ return res
+
+
+# execute on quantum hardware
+qibo.set_backend("qibolab", platform="dummy")
+hardware = execute_rotation()
+
+# execute with classical quantum simulation
+qibo.set_backend("numpy")
+simulation = execute_rotation()
+
+# plot results
+exp_angles = np.arange(0, 2 * np.pi, np.pi / 16)
+plt.plot(exp_angles, hardware, label="qibolab hardware")
+plt.plot(exp_angles, simulation, label="numpy")
+
+plt.legend()
+plt.ylabel("P(1)")
+plt.xlabel("Rotation [rad]")
+plt.show()
diff --git a/doc/source/tutorials/includes/circuits/circuits1.qasm b/doc/source/tutorials/includes/circuits/circuits1.qasm
new file mode 100644
index 000000000..8f0c637e2
--- /dev/null
+++ b/doc/source/tutorials/includes/circuits/circuits1.qasm
@@ -0,0 +1,10 @@
+// Generated by QIBO 0.2.4
+OPENQASM 2.0;
+include "qelib1.inc";
+qreg q[3];
+creg a[2];
+cz q[0],q[2];
+gpi2(0.3) q[1];
+cz q[1],q[2];
+measure q[0] -> a[0];
+measure q[2] -> a[1];
diff --git a/doc/source/tutorials/includes/circuits/circuits2.py b/doc/source/tutorials/includes/circuits/circuits2.py
new file mode 100644
index 000000000..33c3bbed4
--- /dev/null
+++ b/doc/source/tutorials/includes/circuits/circuits2.py
@@ -0,0 +1,46 @@
+import matplotlib.pyplot as plt
+import numpy as np
+
+from qibolab import execute_qasm
+
+# define the circuit in OpenQASM
+circuit = """// Generated by QIBO 0.2.4
+OPENQASM 2.0;
+include "qelib1.inc";
+qreg q[3];
+creg a[2];
+cz q[0],q[2];
+gpi2(0.3) q[1];
+cz q[1],q[2];
+measure q[0] -> a[0];
+measure q[2] -> a[1];"""
+
+## or read in an assembly-file
+
+# set load_circuit to 'True' to overwrite the circuit above
+load_circuit = False
+if load_circuit:
+ try:
+ with open("./circuits1.qasm" "r") as qasm_file:
+ circuit = qasm_file.read()
+ except FileNotFoundError:
+ print("File not found!")
+ except Exception as e:
+ print(f"An error occurred: {e}")
+
+measurement = execute_qasm(circuit, platform="dummy", nshots=4000)
+
+# FIXME: This crashes - understand the MeasurementObject
+freq = measurement.frequencies()
+p0 = freq["0"] / 4000 if "0" in freq else 0
+p1 = freq["1"] / 4000 if "1" in freq else 0
+hardware = [p0, p1]
+
+# plot results
+exp_angles = np.arange(0, 2 * np.pi, np.pi / 16)
+plt.plot(exp_angles, hardware, label="qibolab hardware")
+
+plt.legend()
+plt.ylabel("P(1)")
+plt.xlabel("Rotation [rad]")
+plt.show()
diff --git a/doc/source/tutorials/includes/compiler/compiler0.py b/doc/source/tutorials/includes/compiler/compiler0.py
new file mode 100644
index 000000000..6fb628155
--- /dev/null
+++ b/doc/source/tutorials/includes/compiler/compiler0.py
@@ -0,0 +1,18 @@
+import qibo
+from qibo import gates
+from qibo.backends import GlobalBackend
+from qibo.models import Circuit
+
+# define circuit
+circuit = Circuit(1)
+circuit.add(gates.U3(0, 0.1, 0.2, 0.3))
+circuit.add(gates.M(0))
+
+# set backend to qibolab
+qibo.set_backend("qibolab", platform="dummy")
+
+# disable the transpiler
+GlobalBackend().transpiler = None
+
+# execute circuit
+result = circuit(nshots=1000)
diff --git a/doc/source/tutorials/includes/compiler/compiler1.py b/doc/source/tutorials/includes/compiler/compiler1.py
new file mode 100644
index 000000000..0fc34bad9
--- /dev/null
+++ b/doc/source/tutorials/includes/compiler/compiler1.py
@@ -0,0 +1,31 @@
+from qibo import gates
+from qibo.models import Circuit
+
+from qibolab.backends import QibolabBackend
+from qibolab.pulses import PulseSequence
+
+# define the circuit
+circuit = Circuit(1)
+circuit.add(gates.X(0))
+circuit.add(gates.M(0))
+
+
+# define a compiler rule that translates X to the pi-pulse
+def x_rule(gate, platform):
+ """X gate applied with a single pi-pulse."""
+ qubit = gate.target_qubits[0]
+ sequence = PulseSequence()
+ sequence.add(platform.create_RX_pulse(qubit, start=0))
+ return sequence, {}
+
+
+# the empty dictionary is needed because the X gate does not require any virtual Z-phases
+
+backend = QibolabBackend(platform="dummy")
+# disable the transpiler
+backend.transpiler = None
+# register the new X rule in the compiler
+backend.compiler[gates.X] = x_rule
+
+# execute the circuit
+result = backend.execute_circuit(circuit, nshots=1000)
diff --git a/doc/source/tutorials/includes/instrument/instrument0.py b/doc/source/tutorials/includes/instrument/instrument0.py
new file mode 100644
index 000000000..a08d3b8b6
--- /dev/null
+++ b/doc/source/tutorials/includes/instrument/instrument0.py
@@ -0,0 +1,42 @@
+# let's suppose that there is already avaiable a base driver for connection
+# and control of the device
+from proprietary_instruments import BiaserDriver
+
+from qibolab.instruments.abstract import Instrument
+
+
+class Biaser(Instrument):
+ """An instrument that delivers constant biases."""
+
+ def __init__(self, name, address, min_value=-1, max_value=1):
+ super().__init__(name, address)
+ self.max_value: float = (
+ max_value # attribute example, maximum value of voltage allowed
+ )
+ self.min_value: float = (
+ min_value # attribute example, minimum value of voltage allowed
+ )
+ self.bias: float = 0
+
+ self.device = BiaserDriver(address)
+
+ def connect(self):
+ """Check if a connection is avaiable."""
+ if not self.device.is_connected:
+ raise ConnectionError("Biaser not connected")
+
+ def disconnect(self):
+ """Method not used."""
+
+ # FIXME:: *args, **kwargs are not passed on
+ def setup(self):
+ """Set biaser parameters."""
+ self.device.set_range(self.min_value, self.max_value)
+
+ def start(self):
+ """Start biasing."""
+ self.device.on(bias)
+
+ def stop(self):
+ """Stop biasing."""
+ self.device.off(bias)
diff --git a/doc/source/tutorials/includes/instrument/instrument1.py b/doc/source/tutorials/includes/instrument/instrument1.py
new file mode 100644
index 000000000..dfe5f5cb7
--- /dev/null
+++ b/doc/source/tutorials/includes/instrument/instrument1.py
@@ -0,0 +1,79 @@
+from typing import Union
+
+from proprietary_instruments import ControllerDriver
+
+from qibolab.execution_parameters import ExecutionParameters
+from qibolab.instruments.abstract import Controller
+from qibolab.pulses import PulseSequence
+from qibolab.qubits import Qubit
+from qibolab.result import IntegratedResults, SampleResults
+from qibolab.sweeper import Sweeper
+
+
+class MyController(Controller):
+ def __init__(self, name, address):
+ self.device = ControllerDriver(address)
+ super().__init__(name, address)
+
+ def connect(self):
+ """Empty method to comply with Instrument interface."""
+
+ def start(self):
+ """Empty method to comply with Instrument interface."""
+
+ def stop(self):
+ """Empty method to comply with Instrument interface."""
+
+ def disconnect(self):
+ """Empty method to comply with Instrument interface."""
+
+ # FIXME:: *args, **kwargs are not passed on
+ def setup(self):
+ """Empty method to comply with Instrument interface."""
+
+ # FIXME:: this seems to be incompatbile with the ABC, too
+ def play(
+ self,
+ qubits: dict[int, Qubit],
+ sequence: PulseSequence,
+ execution_parameters: ExecutionParameters,
+ ) -> dict[str, Union[IntegratedResults, SampleResults]]:
+ """Executes a PulseSequence."""
+
+ # usually, some modification on the qubit objects, sequences or
+ # parameters is needed so that the qibolab interface comply with the one
+ # of the device here these are equal
+ results = self.device.run_experiment(qubits, sequence, execution_parameters)
+
+ # also the results are, in qibolab, specific objects that need some kind
+ # of conversion. Refer to the results section in the documentation.
+ return results
+
+ # FIXME:: this seems to be incompatbile with the ABC, too
+ def sweep(
+ self,
+ qubits: dict[int, Qubit],
+ sequence: PulseSequence,
+ execution_parameters: ExecutionParameters,
+ *sweepers: Sweeper,
+ ) -> dict[str, Union[IntegratedResults, SampleResults]]:
+ # usually, some modification on the qubit objects, sequences or
+ # parameters is needed so that the qibolab interface comply with the one
+ # of the device here these are equal
+ results = self.device.run_scan(qubits, sequence, sweepers, execution_parameters)
+
+ # also the results are, in qibolab, specific objects that need some kind
+ # of conversion. Refer to the results section in the documentation.
+ return results
+
+ def play_sequences(
+ self,
+ qubits: dict[int, Qubit],
+ sequences: list[PulseSequence],
+ execution_parameters: ExecutionParameters,
+ ) -> dict[str, Union[IntegratedResults, SampleResults]]:
+ """This method is used for sequence unrolling sweeps.
+
+ Here not implemented.
+ """
+ raise NotImplementedError
diff --git a/doc/source/tutorials/includes/instrument/proprietary_instruments.py b/doc/source/tutorials/includes/instrument/proprietary_instruments.py
new file mode 100644
index 000000000..4b905d3b3
--- /dev/null
+++ b/doc/source/tutorials/includes/instrument/proprietary_instruments.py
@@ -0,0 +1,54 @@
+"""Dummy class to provide a device driver for example in instrument0.py."""
+
+from enum import Enum
+
+
+class State(Enum):
+ OFF = 0
+ ON = 1
+
+
+class BiaserDriver:
+
+ def __init__(self, address):
+ self.address = address
+ self.bias = 0
+ self.state = State.OFF
+
+ def is_connected(self):
+ return True
+
+ def set_range(self, min_value=0, max_value=65536):
+ self.min_value = min_value
+ self.max_value = max_value
+
+ def on(self, bias=0):
+ self.bias = bias
+ self.state = State.ON
+
+ def off(self, bias=0):
+ self.bias = bias
+ self.state = State.OFF
+
+
+class ControllerDriver:
+
+ def __init__(self, address):
+ self.address = address
+ self.bias = 0
+ self.state = State.OFF
+
+ def is_connected(self):
+ return True
+
+ def set_range(self, min_value=0, max_value=65536):
+ self.min_value = min_value
+ self.max_value = max_value
+
+ def on(self, bias=0):
+ self.bias = bias
+ self.state = State.ON
+
+ def off(self, bias=0):
+ self.bias = bias
+ self.state = State.OFF
diff --git a/doc/source/tutorials/includes/lab/lab0.py b/doc/source/tutorials/includes/lab/lab0.py
new file mode 100644
index 000000000..d71d78b72
--- /dev/null
+++ b/doc/source/tutorials/includes/lab/lab0.py
@@ -0,0 +1,55 @@
+from qibolab import Platform
+from qibolab.channels import Channel, ChannelMap
+from qibolab.instruments.dummy import DummyInstrument
+from qibolab.native import NativePulse, SingleQubitNatives
+from qibolab.pulses import PulseType
+from qibolab.qubits import Qubit
+
+
+def create():
+ # Create a controller instrument
+ instrument = DummyInstrument("my_instrument", "0.0.0.0:0")
+
+ # Create channel objects and assign to them the controller ports
+ channels = ChannelMap()
+ channels |= Channel("ch1out", port=instrument["o1"])
+ channels |= Channel("ch2", port=instrument["o2"])
+ channels |= Channel("ch1in", port=instrument["i1"])
+
+ # create the qubit object
+ qubit = Qubit(0)
+
+ # assign native gates to the qubit
+ qubit.native_gates = SingleQubitNatives(
+ RX=NativePulse(
+ name="RX",
+ duration=40,
+ amplitude=0.05,
+ shape="Gaussian(5)",
+ pulse_type=PulseType.DRIVE,
+ qubit=qubit,
+ frequency=int(4.5e9),
+ ),
+ MZ=NativePulse(
+ name="MZ",
+ duration=1000,
+ amplitude=0.005,
+ shape="Rectangular()",
+ pulse_type=PulseType.READOUT,
+ qubit=qubit,
+ frequency=int(7e9),
+ ),
+ )
+
+ # assign channels to the qubit
+ qubit.readout = channels["ch1out"]
+ qubit.feedback = channels["ch1in"]
+ qubit.drive = channels["ch2"]
+
+ # create dictionaries of the different objects
+ qubits = {qubit.name: qubit}
+ pairs = {} # empty as for single qubit we have no qubit pairs
+ instruments = {instrument.name: instrument}
+
+ # allocate and return Platform object
+ return Platform("my_platform", qubits, pairs, instruments, resonator_type="3D")
diff --git a/doc/source/tutorials/includes/lab/lab1.py b/doc/source/tutorials/includes/lab/lab1.py
new file mode 100644
index 000000000..35ee6a462
--- /dev/null
+++ b/doc/source/tutorials/includes/lab/lab1.py
@@ -0,0 +1,72 @@
+from qibolab.native import (
+ NativePulse,
+ NativeSequence,
+ SingleQubitNatives,
+ TwoQubitNatives,
+)
+from qibolab.pulses import PulseType
+from qibolab.qubits import Qubit, QubitPair
+
+# create the qubit objects
+qubit0 = Qubit(0)
+qubit1 = Qubit(1)
+
+# assign single-qubit native gates to each qubit
+qubit0.native_gates = SingleQubitNatives(
+ RX=NativePulse(
+ name="RX",
+ duration=40,
+ amplitude=0.05,
+ shape="Gaussian(5)",
+ pulse_type=PulseType.DRIVE,
+ qubit=qubit0,
+ frequency=int(4.7e9),
+ ),
+ MZ=NativePulse(
+ name="MZ",
+ duration=1000,
+ amplitude=0.005,
+ shape="Rectangular()",
+ pulse_type=PulseType.READOUT,
+ qubit=qubit0,
+ frequency=int(7e9),
+ ),
+)
+qubit1.native_gates = SingleQubitNatives(
+ RX=NativePulse(
+ name="RX",
+ duration=40,
+ amplitude=0.05,
+ shape="Gaussian(5)",
+ pulse_type=PulseType.DRIVE,
+ qubit=qubit1,
+ frequency=int(5.1e9),
+ ),
+ MZ=NativePulse(
+ name="MZ",
+ duration=1000,
+ amplitude=0.005,
+ shape="Rectangular()",
+ pulse_type=PulseType.READOUT,
+ qubit=qubit1,
+ frequency=int(7.5e9),
+ ),
+)
+
+# define the pair of qubits
+pair = QubitPair(qubit0, qubit1)
+pair.native_gates = TwoQubitNatives(
+ CZ=NativeSequence(
+ name="CZ",
+ pulses=[
+ NativePulse(
+ name="CZ1",
+ duration=30,
+ amplitude=0.005,
+ shape="Rectangular()",
+ pulse_type=PulseType.FLUX,
+ qubit=qubit1,
+ )
+ ],
+ )
+)
diff --git a/doc/source/tutorials/includes/lab/lab2.py b/doc/source/tutorials/includes/lab/lab2.py
new file mode 100644
index 000000000..390d7c3a5
--- /dev/null
+++ b/doc/source/tutorials/includes/lab/lab2.py
@@ -0,0 +1,30 @@
+from qibolab.couplers import Coupler
+from qibolab.native import NativePulse, NativeSequence, TwoQubitNatives
+from qibolab.pulses import PulseType
+from qibolab.qubits import Qubit, QubitPair
+
+# create the qubit and coupler objects
+qubit0 = Qubit(0)
+qubit1 = Qubit(1)
+coupler_01 = Coupler(0)
+
+# assign single-qubit native gates to each qubit
+# Look above example
+
+# define the pair of qubits
+pair = QubitPair(qubit0, qubit1, coupler_01)
+pair.native_gates = TwoQubitNatives(
+ CZ=NativeSequence(
+ name="CZ",
+ pulses=[
+ NativePulse(
+ name="CZ1",
+ duration=30,
+ amplitude=0.005,
+ shape="Rectangular()",
+ pulse_type=PulseType.FLUX,
+ qubit=qubit1,
+ )
+ ],
+ )
+)
diff --git a/doc/source/tutorials/includes/pulses/pulses0.py b/doc/source/tutorials/includes/pulses/pulses0.py
new file mode 100644
index 000000000..cf9fb9722
--- /dev/null
+++ b/doc/source/tutorials/includes/pulses/pulses0.py
@@ -0,0 +1,36 @@
+# pulses0.py
+
+from qibolab.pulses import (
+ DrivePulse,
+ Gaussian,
+ PulseSequence,
+ ReadoutPulse,
+ Rectangular,
+)
+
+# Define PulseSequence
+sequence = PulseSequence()
+
+# Add some pulses to the pulse sequence
+sequence.add(
+ DrivePulse(
+ start=0,
+ frequency=200000000,
+ amplitude=0.3,
+ duration=60,
+ relative_phase=0,
+ shape=Gaussian(5),
+ qubit=0,
+ )
+)
+sequence.add(
+ ReadoutPulse(
+ start=70,
+ frequency=20000000.0,
+ amplitude=0.5,
+ duration=3000,
+ relative_phase=0,
+ shape=Rectangular(),
+ qubit=0,
+ )
+)
diff --git a/doc/source/tutorials/includes/pulses/pulses1.py b/doc/source/tutorials/includes/pulses/pulses1.py
new file mode 100644
index 000000000..1bf950dd2
--- /dev/null
+++ b/doc/source/tutorials/includes/pulses/pulses1.py
@@ -0,0 +1,17 @@
+from pulses0 import sequence
+
+from qibolab import create_platform
+from qibolab.execution_parameters import ExecutionParameters
+
+# Define platform and load specific runcard
+platform = create_platform("dummy")
+
+# Connects to lab instruments using the details specified in the calibration settings.
+platform.connect()
+
+# Executes a pulse sequence.
+options = ExecutionParameters(nshots=1000, relaxation_time=100)
+results = platform.execute_pulse_sequence(sequence, options=options)
+
+# Disconnect from the instruments
+platform.disconnect()
diff --git a/doc/source/tutorials/instrument.rst b/doc/source/tutorials/instrument.rst
index db30ce641..015d036e8 100644
--- a/doc/source/tutorials/instrument.rst
+++ b/doc/source/tutorials/instrument.rst
@@ -48,50 +48,7 @@ A good example of a instrument driver is the
Here, let's write a basic example of instrument whose job is to deliver a fixed bias for the duration of the experiment:
-.. code-block:: python
-
- from qibolab.instruments.abstract import Instrument
-
- # let's suppose that there is already avaiable a base driver for connection
- # and control of the device
- from proprietary_instruments import biaser_driver
-
-
- class Biaser(Instrument):
- """An instrument that delivers constand biases."""
-
- def __init__(self, name, address, min_value=-1, max_value=1):
- super().__init__(name, address)
- self.max_value: float = (
- max_value # attribute example, maximum value of voltage allowed
- )
- self.min_value: float = (
- min_value # attribute example, minimum value of voltage allowed
- )
- self.bias: float = 0
-
- self.device = biaser_driver(address)
-
- def connect(self):
- """Check if a connection is avaiable."""
- if not self.device.is_connected:
- raise ConnectionError("Biaser not connected")
-
- def disconnect(self):
- """Method not used."""
-
- def setup(self):
- """Set biaser parameters."""
- self.device.set_range(self.min_value, self.max_value)
-
- def start(self):
- """Start biasing."""
- self.device.on(bias)
-
- def stop(self):
- """Stop biasing."""
- self.device.off(bias)
-
+.. literalinclude:: ./includes/instrument/instrument0.py
Add a controller
----------------
@@ -111,73 +68,7 @@ complex than the local oscillator ones.
Let's see a minimal example:
-.. code-block:: python
-
- from qibolab.instruments.abstract import Controller
- from proprietary_instruments import controller_driver
-
-
- class MyController(Controller):
- def __init__(self, name, address):
- self.device = controller_driver(address)
- super().__init__(name, address)
-
- def connect(self):
- """Empty method to comply with Instrument interface."""
-
- def start(self):
- """Empty method to comply with Instrument interface."""
-
- def stop(self):
- """Empty method to comply with Instrument interface."""
-
- def disconnect(self):
- """Empty method to comply with Instrument interface."""
-
- def setup(self):
- """Empty method to comply with Instrument interface."""
-
- def play(
- self,
- qubits: dict[int, Qubit],
- sequence: PulseSequence,
- execution_parameters: ExecutionParameters,
- ) -> dict[str, Union[IntegratedResults, SampleResults]]:
- """Executes a PulseSequence."""
-
- # usually, some modification on the qubit objects, sequences or
- # parameters is needed so that the qibolab interface comply with the one
- # of the device here these are equal
- results = self.device.run_experiment(qubits, sequence, execution_parameters)
-
- # also the results are, in qibolab, specific objects that need some kind
- # of conversion. Refer to the results section in the documentation.
- return results
-
- def sweep(
- self,
- qubits: dict[int, Qubit],
- sequence: PulseSequence,
- execution_parameters: ExecutionParameters,
- *sweepers: Sweeper,
- ) -> dict[str, Union[IntegratedResults, SampleResults]]:
- # usually, some modification on the qubit objects, sequences or
- # parameters is needed so that the qibolab interface comply with the one
- # of the device here these are equal
- results = self.device.run_scan(qubits, sequence, sweepers, execution_parameters)
-
- # also the results are, in qibolab, specific objects that need some kind
- # of conversion. Refer to the results section in the documentation.
- return results
-
- def play_sequences(
- self,
- qubits: dict[int, Qubit],
- sequences: List[PulseSequence],
- execution_parameters: ExecutionParameters,
- ) -> dict[str, Union[IntegratedResults, SampleResults]]:
- """This method is used for sequence unrolling sweeps. Here not implemented."""
- raise NotImplementedError
+.. literalinclude:: ./includes/instrument/instrument1.py
As we saw in :doc:`lab`, to instantiate a platform at some point you have to
write something like this:
diff --git a/doc/source/tutorials/lab.rst b/doc/source/tutorials/lab.rst
index ffb52ad53..ac4a04331 100644
--- a/doc/source/tutorials/lab.rst
+++ b/doc/source/tutorials/lab.rst
@@ -20,64 +20,7 @@ instrumentation.
The following cell shows how to define a single qubit platform from scratch,
using different Qibolab primitives.
-.. testcode:: python
-
- from qibolab import Platform
- from qibolab.qubits import Qubit
- from qibolab.pulses import PulseType
- from qibolab.channels import ChannelMap, Channel
- from qibolab.native import NativePulse, SingleQubitNatives
- from qibolab.instruments.dummy import DummyInstrument
-
-
- def create():
- # Create a controller instrument
- instrument = DummyInstrument("my_instrument", "0.0.0.0:0")
-
- # Create channel objects and assign to them the controller ports
- channels = ChannelMap()
- channels |= Channel("ch1out", port=instrument["o1"])
- channels |= Channel("ch2", port=instrument["o2"])
- channels |= Channel("ch1in", port=instrument["i1"])
-
- # create the qubit object
- qubit = Qubit(0)
-
- # assign native gates to the qubit
- qubit.native_gates = SingleQubitNatives(
- RX=NativePulse(
- name="RX",
- duration=40,
- amplitude=0.05,
- shape="Gaussian(5)",
- pulse_type=PulseType.DRIVE,
- qubit=qubit,
- frequency=int(4.5e9),
- ),
- MZ=NativePulse(
- name="MZ",
- duration=1000,
- amplitude=0.005,
- shape="Rectangular()",
- pulse_type=PulseType.READOUT,
- qubit=qubit,
- frequency=int(7e9),
- ),
- )
-
- # assign channels to the qubit
- qubit.readout = channels["ch1out"]
- qubit.feedback = channels["ch1in"]
- qubit.drive = channels["ch2"]
-
- # create dictionaries of the different objects
- qubits = {qubit.name: qubit}
- pairs = {} # empty as for single qubit we have no qubit pairs
- instruments = {instrument.name: instrument}
-
- # allocate and return Platform object
- return Platform("my_platform", qubits, pairs, instruments, resonator_type="3D")
-
+.. literalinclude:: ./includes/lab/lab0.py
This code creates a platform with a single qubit that is controlled by the
:class:`qibolab.instruments.dummy.DummyInstrument`. In real applications, if
@@ -96,80 +39,7 @@ that two-qubit gates can be applied. For such connected pairs of qubits one
needs to additionally define :class:`qibolab.qubits.QubitPair` objects, which
hold the parameters of the two-qubit gates.
-.. testcode:: python
-
- from qibolab.qubits import Qubit, QubitPair
- from qibolab.pulses import PulseType
- from qibolab.native import (
- NativePulse,
- NativeSequence,
- SingleQubitNatives,
- TwoQubitNatives,
- )
-
- # create the qubit objects
- qubit0 = Qubit(0)
- qubit1 = Qubit(1)
-
- # assign single-qubit native gates to each qubit
- qubit0.native_gates = SingleQubitNatives(
- RX=NativePulse(
- name="RX",
- duration=40,
- amplitude=0.05,
- shape="Gaussian(5)",
- pulse_type=PulseType.DRIVE,
- qubit=qubit0,
- frequency=int(4.7e9),
- ),
- MZ=NativePulse(
- name="MZ",
- duration=1000,
- amplitude=0.005,
- shape="Rectangular()",
- pulse_type=PulseType.READOUT,
- qubit=qubit0,
- frequency=int(7e9),
- ),
- )
- qubit1.native_gates = SingleQubitNatives(
- RX=NativePulse(
- name="RX",
- duration=40,
- amplitude=0.05,
- shape="Gaussian(5)",
- pulse_type=PulseType.DRIVE,
- qubit=qubit1,
- frequency=int(5.1e9),
- ),
- MZ=NativePulse(
- name="MZ",
- duration=1000,
- amplitude=0.005,
- shape="Rectangular()",
- pulse_type=PulseType.READOUT,
- qubit=qubit1,
- frequency=int(7.5e9),
- ),
- )
-
- # define the pair of qubits
- pair = QubitPair(qubit0, qubit1)
- pair.native_gates = TwoQubitNatives(
- CZ=NativeSequence(
- name="CZ",
- pulses=[
- NativePulse(
- name="CZ1",
- duration=30,
- amplitude=0.005,
- shape="Rectangular()",
- pulse_type=PulseType.FLUX,
- qubit=qubit1,
- )
- ],
- )
- )
+.. literalinclude:: ./includes/lab1.py
Some architectures may also have coupler qubits that mediate the interactions.
We can also interact with them defining the :class:`qibolab.couplers.Coupler` objects.
@@ -177,44 +47,7 @@ Then we add them to their corresponding :class:`qibolab.qubits.QubitPair` object
to the chip topology. We neglected characterization parameters associated to the
coupler but qibolab will take them into account when calling :class:`qibolab.native.TwoQubitNatives`.
-
-.. testcode:: python
-
- from qibolab.couplers import Coupler
- from qibolab.qubits import Qubit, QubitPair
- from qibolab.pulses import PulseType
- from qibolab.native import (
- NativePulse,
- NativeSequence,
- SingleQubitNatives,
- TwoQubitNatives,
- )
-
- # create the qubit and coupler objects
- qubit0 = Qubit(0)
- qubit1 = Qubit(1)
- coupler_01 = Coupler(0)
-
- # assign single-qubit native gates to each qubit
- # Look above example
-
- # define the pair of qubits
- pair = QubitPair(qubit0, qubit1, coupler_01)
- pair.native_gates = TwoQubitNatives(
- CZ=NativeSequence(
- name="CZ",
- pulses=[
- NativePulse(
- name="CZ1",
- duration=30,
- amplitude=0.005,
- shape="Rectangular()",
- pulse_type=PulseType.FLUX,
- qubit=qubit1,
- )
- ],
- )
- )
+.. literalinclude:: ./includes/lab2.py
The platform automatically creates the connectivity graph of the given chip
using the dictionary of :class:`qibolab.qubits.QubitPair` objects.
diff --git a/doc/source/tutorials/pulses.rst b/doc/source/tutorials/pulses.rst
index 51fa61d76..443c6348a 100644
--- a/doc/source/tutorials/pulses.rst
+++ b/doc/source/tutorials/pulses.rst
@@ -6,42 +6,7 @@ defining a :class:`qibolab.pulses.PulseSequence` object and adding different
pulses (:class:`qibolab.pulses.Pulse`) through the
:func:`qibolab.pulses.PulseSequence.add()` method:
-.. testcode:: python
-
- from qibolab.pulses import (
- DrivePulse,
- ReadoutPulse,
- PulseSequence,
- Rectangular,
- Gaussian,
- )
-
- # Define PulseSequence
- sequence = PulseSequence()
-
- # Add some pulses to the pulse sequence
- sequence.add(
- DrivePulse(
- start=0,
- frequency=200000000,
- amplitude=0.3,
- duration=60,
- relative_phase=0,
- shape=Gaussian(5),
- qubit=0,
- )
- )
- sequence.add(
- ReadoutPulse(
- start=70,
- frequency=20000000.0,
- amplitude=0.5,
- duration=3000,
- relative_phase=0,
- shape=Rectangular(),
- qubit=0,
- )
- )
+.. literalinclude:: ./includes/pulses/pulses0.py
The next step consists in connecting to a specific lab in which the pulse
sequence will be executed. In order to do this we allocate a platform object
@@ -55,23 +20,7 @@ After connecting and setting up the platform's instruments using the
local oscillators and the ``execute`` method will execute the previous defined
pulse sequence according to the number of shots ``nshots`` specified.
-.. testcode:: python
-
- from qibolab import create_platform
- from qibolab.execution_parameters import ExecutionParameters
-
- # Define platform and load specific runcard
- platform = create_platform("dummy")
-
- # Connects to lab instruments using the details specified in the calibration settings.
- platform.connect()
-
- # Executes a pulse sequence.
- options = ExecutionParameters(nshots=1000, relaxation_time=100)
- results = platform.execute_pulse_sequence(sequence, options=options)
-
- # Disconnect from the instruments
- platform.disconnect()
+.. literalinclude:: ./includes/pulses/pulses1.py
Remember to turn off the instruments and disconnect from the lab using the
``stop()`` and ``disconnect()`` methods of the platform.
diff --git a/pyproject.toml b/pyproject.toml
index c14b3af68..7412027f0 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -67,6 +67,8 @@ qibosoq = ">=0.1.2,<0.2"
qualang-tools = "^0.15.0"
laboneq = "==2.25.0"
qutip = "^4.7.5"
+# to run code examples from tutorial
+matplotlib = "^3.8.2"
[tool.poetry.group.tests]
optional = true