From cb8fec8bae2afe1172ccd42b487a3fc15963310e Mon Sep 17 00:00:00 2001 From: Roland Siegbert Date: Sat, 20 Apr 2024 18:09:46 +0200 Subject: [PATCH 01/15] Add tutorials to examples incl. deps --- examples/tutorials/calibration0.py | 47 +++++++++++++++++++ examples/tutorials/calibration1.py | 50 +++++++++++++++++++++ examples/tutorials/calibration2.py | 49 ++++++++++++++++++++ examples/tutorials/circuits0.py | 31 +++++++++++++ examples/tutorials/circuits1.py | 51 +++++++++++++++++++++ examples/tutorials/lab0.py | 55 +++++++++++++++++++++++ examples/tutorials/lab1.py | 72 ++++++++++++++++++++++++++++++ examples/tutorials/lab2.py | 30 +++++++++++++ 8 files changed, 385 insertions(+) create mode 100644 examples/tutorials/calibration0.py create mode 100644 examples/tutorials/calibration1.py create mode 100644 examples/tutorials/calibration2.py create mode 100644 examples/tutorials/circuits0.py create mode 100644 examples/tutorials/circuits1.py create mode 100644 examples/tutorials/lab0.py create mode 100644 examples/tutorials/lab1.py create mode 100644 examples/tutorials/lab2.py diff --git a/examples/tutorials/calibration0.py b/examples/tutorials/calibration0.py new file mode 100644 index 0000000000..a072860e52 --- /dev/null +++ b/examples/tutorials/calibration0.py @@ -0,0 +1,47 @@ +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, +) + +options = ExecutionParameters( + nshots=1000, + relaxation_time=50, + averaging_mode=AveragingMode.CYCLIC, + acquisition_type=AcquisitionType.INTEGRATION, +) + +results = platform.sweep(sequence, options, sweeper) + +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() diff --git a/examples/tutorials/calibration1.py b/examples/tutorials/calibration1.py new file mode 100644 index 0000000000..add183196f --- /dev/null +++ b/examples/tutorials/calibration1.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 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, +) + +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() diff --git a/examples/tutorials/calibration2.py b/examples/tutorials/calibration2.py new file mode 100644 index 0000000000..1c05bf8aff --- /dev/null +++ b/examples/tutorials/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/examples/tutorials/circuits0.py b/examples/tutorials/circuits0.py new file mode 100644 index 0000000000..2f4fbfef73 --- /dev/null +++ b/examples/tutorials/circuits0.py @@ -0,0 +1,31 @@ +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.H(0)) +circuit.add(gates.M(0)) + +# execute on quantum hardware +qibo.set_backend("qibolab", "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(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/examples/tutorials/circuits1.py b/examples/tutorials/circuits1.py new file mode 100644 index 0000000000..f1cf39c5bd --- /dev/null +++ b/examples/tutorials/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.RX(0, theta=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", "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/examples/tutorials/lab0.py b/examples/tutorials/lab0.py new file mode 100644 index 0000000000..d71d78b728 --- /dev/null +++ b/examples/tutorials/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/examples/tutorials/lab1.py b/examples/tutorials/lab1.py new file mode 100644 index 0000000000..35ee6a462b --- /dev/null +++ b/examples/tutorials/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/examples/tutorials/lab2.py b/examples/tutorials/lab2.py new file mode 100644 index 0000000000..390d7c3a52 --- /dev/null +++ b/examples/tutorials/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, + ) + ], + ) +) From e9abfdd25e65a00ed984664e051909b46abe4b56 Mon Sep 17 00:00:00 2001 From: Roland Siegbert Date: Wed, 27 Mar 2024 08:33:18 +0100 Subject: [PATCH 02/15] Move code examples into doc/source/tutorials --- .../source/tutorials/examples}/calibration0.py | 0 .../source/tutorials/examples}/calibration1.py | 0 .../source/tutorials/examples}/calibration2.py | 0 .../source/tutorials/examples}/circuits0.py | 0 .../source/tutorials/examples}/circuits1.py | 0 .../source/tutorials/examples}/lab0.py | 8 ++++---- .../source/tutorials/examples}/lab1.py | 0 .../source/tutorials/examples}/lab2.py | 11 ++++++++--- 8 files changed, 12 insertions(+), 7 deletions(-) rename {examples/tutorials => doc/source/tutorials/examples}/calibration0.py (100%) rename {examples/tutorials => doc/source/tutorials/examples}/calibration1.py (100%) rename {examples/tutorials => doc/source/tutorials/examples}/calibration2.py (100%) rename {examples/tutorials => doc/source/tutorials/examples}/circuits0.py (100%) rename {examples/tutorials => doc/source/tutorials/examples}/circuits1.py (100%) rename {examples/tutorials => doc/source/tutorials/examples}/lab0.py (97%) rename {examples/tutorials => doc/source/tutorials/examples}/lab1.py (100%) rename {examples/tutorials => doc/source/tutorials/examples}/lab2.py (86%) diff --git a/examples/tutorials/calibration0.py b/doc/source/tutorials/examples/calibration0.py similarity index 100% rename from examples/tutorials/calibration0.py rename to doc/source/tutorials/examples/calibration0.py diff --git a/examples/tutorials/calibration1.py b/doc/source/tutorials/examples/calibration1.py similarity index 100% rename from examples/tutorials/calibration1.py rename to doc/source/tutorials/examples/calibration1.py diff --git a/examples/tutorials/calibration2.py b/doc/source/tutorials/examples/calibration2.py similarity index 100% rename from examples/tutorials/calibration2.py rename to doc/source/tutorials/examples/calibration2.py diff --git a/examples/tutorials/circuits0.py b/doc/source/tutorials/examples/circuits0.py similarity index 100% rename from examples/tutorials/circuits0.py rename to doc/source/tutorials/examples/circuits0.py diff --git a/examples/tutorials/circuits1.py b/doc/source/tutorials/examples/circuits1.py similarity index 100% rename from examples/tutorials/circuits1.py rename to doc/source/tutorials/examples/circuits1.py diff --git a/examples/tutorials/lab0.py b/doc/source/tutorials/examples/lab0.py similarity index 97% rename from examples/tutorials/lab0.py rename to doc/source/tutorials/examples/lab0.py index d71d78b728..23766c953c 100644 --- a/examples/tutorials/lab0.py +++ b/doc/source/tutorials/examples/lab0.py @@ -1,9 +1,9 @@ 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 +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(): diff --git a/examples/tutorials/lab1.py b/doc/source/tutorials/examples/lab1.py similarity index 100% rename from examples/tutorials/lab1.py rename to doc/source/tutorials/examples/lab1.py diff --git a/examples/tutorials/lab2.py b/doc/source/tutorials/examples/lab2.py similarity index 86% rename from examples/tutorials/lab2.py rename to doc/source/tutorials/examples/lab2.py index 390d7c3a52..35d4f7b279 100644 --- a/examples/tutorials/lab2.py +++ b/doc/source/tutorials/examples/lab2.py @@ -1,7 +1,12 @@ from qibolab.couplers import Coupler -from qibolab.native import NativePulse, NativeSequence, TwoQubitNatives -from qibolab.pulses import PulseType 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) @@ -27,4 +32,4 @@ ) ], ) -) +) \ No newline at end of file From 97e1209f7d32b2e252d57490e71810cb816ee5ca Mon Sep 17 00:00:00 2001 From: Roland Siegbert Date: Wed, 27 Mar 2024 08:40:51 +0100 Subject: [PATCH 03/15] Reorder tutorial example imports --- doc/source/tutorials/examples/lab0.py | 8 ++++---- doc/source/tutorials/examples/lab2.py | 11 +++-------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/doc/source/tutorials/examples/lab0.py b/doc/source/tutorials/examples/lab0.py index 23766c953c..d71d78b728 100644 --- a/doc/source/tutorials/examples/lab0.py +++ b/doc/source/tutorials/examples/lab0.py @@ -1,9 +1,9 @@ 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.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(): diff --git a/doc/source/tutorials/examples/lab2.py b/doc/source/tutorials/examples/lab2.py index 35d4f7b279..390d7c3a52 100644 --- a/doc/source/tutorials/examples/lab2.py +++ b/doc/source/tutorials/examples/lab2.py @@ -1,12 +1,7 @@ from qibolab.couplers import Coupler -from qibolab.qubits import Qubit, QubitPair +from qibolab.native import NativePulse, NativeSequence, TwoQubitNatives from qibolab.pulses import PulseType -from qibolab.native import ( - NativePulse, - NativeSequence, - SingleQubitNatives, - TwoQubitNatives, -) +from qibolab.qubits import Qubit, QubitPair # create the qubit and coupler objects qubit0 = Qubit(0) @@ -32,4 +27,4 @@ ) ], ) -) \ No newline at end of file +) From 58294264b688d07db2991a9b95f8bd8ac002311d Mon Sep 17 00:00:00 2001 From: Roland Siegbert Date: Wed, 27 Mar 2024 08:33:38 +0100 Subject: [PATCH 04/15] Replace code examples in lab.rst w/ literalincludes --- doc/source/tutorials/lab.rst | 172 +---------------------------------- 1 file changed, 3 insertions(+), 169 deletions(-) diff --git a/doc/source/tutorials/lab.rst b/doc/source/tutorials/lab.rst index ffb52ad53d..96fb462675 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:: examples/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:: examples/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. @@ -178,43 +48,7 @@ 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:: examples/lab2.py The platform automatically creates the connectivity graph of the given chip using the dictionary of :class:`qibolab.qubits.QubitPair` objects. From 954fc0868a0bce5670f70ce052639f59e38d36e9 Mon Sep 17 00:00:00 2001 From: Roland Siegbert Date: Sat, 20 Apr 2024 18:10:24 +0200 Subject: [PATCH 05/15] Move matplotlib into docs dep group --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index c14b3af688..7412027f06 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 From 93b7ffdf44504a0beea6f8fdd8e320ba93689ade Mon Sep 17 00:00:00 2001 From: Roland Siegbert Date: Sun, 21 Apr 2024 07:13:44 +0200 Subject: [PATCH 06/15] Move calibration code to .py and literalinclude into docs --- doc/source/tutorials/calibration.rst | 169 +----------------- doc/source/tutorials/examples/calibration0.py | 5 +- doc/source/tutorials/examples/calibration1.py | 7 +- 3 files changed, 13 insertions(+), 168 deletions(-) diff --git a/doc/source/tutorials/calibration.rst b/doc/source/tutorials/calibration.rst index 2da46cd398..afeee43541 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:: ./examples/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:: ./examples/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:: ./examples/calibration2.py .. image:: classification_light.svg :class: only-light diff --git a/doc/source/tutorials/examples/calibration0.py b/doc/source/tutorials/examples/calibration0.py index a072860e52..37420af4e3 100644 --- a/doc/source/tutorials/examples/calibration0.py +++ b/doc/source/tutorials/examples/calibration0.py @@ -1,3 +1,4 @@ +import matplotlib.pyplot as plt import numpy as np from qibolab import create_platform @@ -25,6 +26,8 @@ type=SweeperType.OFFSET, ) +## We then define the execution parameters and launch the experiment. + options = ExecutionParameters( nshots=1000, relaxation_time=50, @@ -34,7 +37,7 @@ results = platform.sweep(sequence, options, sweeper) -import matplotlib.pyplot as plt +## 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 diff --git a/doc/source/tutorials/examples/calibration1.py b/doc/source/tutorials/examples/calibration1.py index add183196f..4fbc684fdf 100644 --- a/doc/source/tutorials/examples/calibration1.py +++ b/doc/source/tutorials/examples/calibration1.py @@ -30,6 +30,11 @@ 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, @@ -42,7 +47,7 @@ amplitudes = results[readout_pulse.serial].magnitude frequencies = np.arange(-2e8, +2e8, 1e6) + drive_pulse.frequency -plt.title("Resonator Spectroscopy") +plt.title("Qubit Spectroscopy") plt.xlabel("Frequencies [Hz]") plt.ylabel("Amplitudes [a.u.]") From 8424bbb0de2ed5898f30f640087edfdb44a550de Mon Sep 17 00:00:00 2001 From: Roland Siegbert Date: Sun, 21 Apr 2024 08:01:51 +0200 Subject: [PATCH 07/15] Relocate literal includes in docs --- doc/source/tutorials/calibration.rst | 6 +++--- .../{examples => includes/calibration}/calibration0.py | 0 .../{examples => includes/calibration}/calibration1.py | 0 .../{examples => includes/calibration}/calibration2.py | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename doc/source/tutorials/{examples => includes/calibration}/calibration0.py (100%) rename doc/source/tutorials/{examples => includes/calibration}/calibration1.py (100%) rename doc/source/tutorials/{examples => includes/calibration}/calibration2.py (100%) diff --git a/doc/source/tutorials/calibration.rst b/doc/source/tutorials/calibration.rst index afeee43541..e4ed3f8bf3 100644 --- a/doc/source/tutorials/calibration.rst +++ b/doc/source/tutorials/calibration.rst @@ -26,7 +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. -.. literalinclude:: ./examples/calibration0.py +.. literalinclude:: ./includes/calibration/calibration0.py .. image:: resonator_spectroscopy_light.svg :class: only-light @@ -50,7 +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: -.. literalinclude:: ./examples/calibration1.py +.. literalinclude:: ./includes/calibration/calibration1.py .. image:: qubit_spectroscopy_light.svg :class: only-light @@ -88,7 +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. -.. literalinclude:: ./examples/calibration2.py +.. literalinclude:: ./includes/calibration/calibration2.py .. image:: classification_light.svg :class: only-light diff --git a/doc/source/tutorials/examples/calibration0.py b/doc/source/tutorials/includes/calibration/calibration0.py similarity index 100% rename from doc/source/tutorials/examples/calibration0.py rename to doc/source/tutorials/includes/calibration/calibration0.py diff --git a/doc/source/tutorials/examples/calibration1.py b/doc/source/tutorials/includes/calibration/calibration1.py similarity index 100% rename from doc/source/tutorials/examples/calibration1.py rename to doc/source/tutorials/includes/calibration/calibration1.py diff --git a/doc/source/tutorials/examples/calibration2.py b/doc/source/tutorials/includes/calibration/calibration2.py similarity index 100% rename from doc/source/tutorials/examples/calibration2.py rename to doc/source/tutorials/includes/calibration/calibration2.py From 8c26ddcc7e7fdd18c6958da521f6546290ea7ded Mon Sep 17 00:00:00 2001 From: Roland Siegbert Date: Sun, 21 Apr 2024 08:11:03 +0200 Subject: [PATCH 08/15] Move circuits code to .py and literlincludes in docs --- doc/source/tutorials/circuits.rst | 109 +++--------------- .../circuits}/circuits0.py | 5 +- .../circuits}/circuits1.py | 14 +-- 3 files changed, 25 insertions(+), 103 deletions(-) rename doc/source/tutorials/{examples => includes/circuits}/circuits0.py (88%) rename doc/source/tutorials/{examples => includes/circuits}/circuits1.py (77%) diff --git a/doc/source/tutorials/circuits.rst b/doc/source/tutorials/circuits.rst index 7982e86965..ebe5aaceaa 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/examples/circuits0.py b/doc/source/tutorials/includes/circuits/circuits0.py similarity index 88% rename from doc/source/tutorials/examples/circuits0.py rename to doc/source/tutorials/includes/circuits/circuits0.py index 2f4fbfef73..7469757f49 100644 --- a/doc/source/tutorials/examples/circuits0.py +++ b/doc/source/tutorials/includes/circuits/circuits0.py @@ -8,11 +8,11 @@ circuit = Circuit(1) # attach Hadamard gate and a measurement -circuit.add(gates.H(0)) +circuit.add(gates.GPI2(0, phi=np.pi / 2)) circuit.add(gates.M(0)) # execute on quantum hardware -qibo.set_backend("qibolab", "dummy") +qibo.set_backend("qibolab", platform="dummy") hardware_result = circuit(nshots=5000) # retrieve measured probabilities @@ -27,5 +27,6 @@ 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/examples/circuits1.py b/doc/source/tutorials/includes/circuits/circuits1.py similarity index 77% rename from doc/source/tutorials/examples/circuits1.py rename to doc/source/tutorials/includes/circuits/circuits1.py index f1cf39c5bd..f414240a8c 100644 --- a/doc/source/tutorials/examples/circuits1.py +++ b/doc/source/tutorials/includes/circuits/circuits1.py @@ -9,7 +9,7 @@ def execute_rotation(): circuit = Circuit(1) # attach Rotation on X-Pauli with angle = 0 - circuit.add(gates.RX(0, theta=0)) + circuit.add(gates.GPI2(0, phi=0)) circuit.add(gates.M(0)) # define range of angles from [0, 2pi] @@ -22,18 +22,18 @@ def execute_rotation(): # 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 + 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) + # store probability in state |1> + res.append(p1) return res # execute on quantum hardware -qibo.set_backend("qibolab", "dummy") +qibo.set_backend("qibolab", platform="dummy") hardware = execute_rotation() # execute with classical quantum simulation From 6a44d6033b0c6af7f9587ddbf9bf92a8161ec492 Mon Sep 17 00:00:00 2001 From: Roland Siegbert Date: Sun, 21 Apr 2024 21:16:39 +0200 Subject: [PATCH 09/15] Move lab code to .py and literalincludes into docs --- doc/source/tutorials/{examples => includes/lab}/lab0.py | 0 doc/source/tutorials/{examples => includes/lab}/lab1.py | 0 doc/source/tutorials/{examples => includes/lab}/lab2.py | 0 doc/source/tutorials/lab.rst | 7 +++---- 4 files changed, 3 insertions(+), 4 deletions(-) rename doc/source/tutorials/{examples => includes/lab}/lab0.py (100%) rename doc/source/tutorials/{examples => includes/lab}/lab1.py (100%) rename doc/source/tutorials/{examples => includes/lab}/lab2.py (100%) diff --git a/doc/source/tutorials/examples/lab0.py b/doc/source/tutorials/includes/lab/lab0.py similarity index 100% rename from doc/source/tutorials/examples/lab0.py rename to doc/source/tutorials/includes/lab/lab0.py diff --git a/doc/source/tutorials/examples/lab1.py b/doc/source/tutorials/includes/lab/lab1.py similarity index 100% rename from doc/source/tutorials/examples/lab1.py rename to doc/source/tutorials/includes/lab/lab1.py diff --git a/doc/source/tutorials/examples/lab2.py b/doc/source/tutorials/includes/lab/lab2.py similarity index 100% rename from doc/source/tutorials/examples/lab2.py rename to doc/source/tutorials/includes/lab/lab2.py diff --git a/doc/source/tutorials/lab.rst b/doc/source/tutorials/lab.rst index 96fb462675..ac4a04331c 100644 --- a/doc/source/tutorials/lab.rst +++ b/doc/source/tutorials/lab.rst @@ -20,7 +20,7 @@ instrumentation. The following cell shows how to define a single qubit platform from scratch, using different Qibolab primitives. -.. literalinclude:: examples/lab0.py +.. 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 @@ -39,7 +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. -.. literalinclude:: examples/lab1.py +.. 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. @@ -47,8 +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`. - -.. literalinclude:: examples/lab2.py +.. literalinclude:: ./includes/lab2.py The platform automatically creates the connectivity graph of the given chip using the dictionary of :class:`qibolab.qubits.QubitPair` objects. From 0b60d8e145af87e1baf686c390f124f508e72ce8 Mon Sep 17 00:00:00 2001 From: Roland Siegbert Date: Sun, 21 Apr 2024 21:17:52 +0200 Subject: [PATCH 10/15] Move compiler code to .py and literalincludes into docs --- doc/source/tutorials/compiler.rst | 51 ++----------------- .../tutorials/includes/compiler/compiler0.py | 18 +++++++ .../tutorials/includes/compiler/compiler1.py | 31 +++++++++++ 3 files changed, 52 insertions(+), 48 deletions(-) create mode 100644 doc/source/tutorials/includes/compiler/compiler0.py create mode 100644 doc/source/tutorials/includes/compiler/compiler1.py diff --git a/doc/source/tutorials/compiler.rst b/doc/source/tutorials/compiler.rst index a445e8e7f1..c5b6898610 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/compiler/compiler0.py b/doc/source/tutorials/includes/compiler/compiler0.py new file mode 100644 index 0000000000..6fb628155d --- /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 0000000000..0fc34bad94 --- /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) From f94276b11c73a8b82542e1ec10f0141a125d5ad7 Mon Sep 17 00:00:00 2001 From: Roland Siegbert Date: Sun, 21 Apr 2024 21:18:53 +0200 Subject: [PATCH 11/15] Move instruments to .py and literalincludes into docs --- .../includes/instrument/instrument0.py | 42 +++++++ .../includes/instrument/instrument1.py | 79 ++++++++++++ .../instrument/proprietary_instruments.py | 54 +++++++++ doc/source/tutorials/instrument.rst | 113 +----------------- 4 files changed, 177 insertions(+), 111 deletions(-) create mode 100644 doc/source/tutorials/includes/instrument/instrument0.py create mode 100644 doc/source/tutorials/includes/instrument/instrument1.py create mode 100644 doc/source/tutorials/includes/instrument/proprietary_instruments.py diff --git a/doc/source/tutorials/includes/instrument/instrument0.py b/doc/source/tutorials/includes/instrument/instrument0.py new file mode 100644 index 0000000000..a08d3b8b68 --- /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 0000000000..dfe5f5cb78 --- /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 0000000000..4b905d3b31 --- /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/instrument.rst b/doc/source/tutorials/instrument.rst index db30ce6416..015d036e80 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: From 14a86f6694ba94c79eca9c98917e1048d5925650 Mon Sep 17 00:00:00 2001 From: Roland Siegbert Date: Sun, 21 Apr 2024 21:20:21 +0200 Subject: [PATCH 12/15] Add additional circuit example for direct OpenQASM import --- .../includes/circuits/circuits1.qasm | 10 +++++ .../tutorials/includes/circuits/circuits2.py | 45 +++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 doc/source/tutorials/includes/circuits/circuits1.qasm create mode 100644 doc/source/tutorials/includes/circuits/circuits2.py diff --git a/doc/source/tutorials/includes/circuits/circuits1.qasm b/doc/source/tutorials/includes/circuits/circuits1.qasm new file mode 100644 index 0000000000..8f0c637e28 --- /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 0000000000..68e7600c84 --- /dev/null +++ b/doc/source/tutorials/includes/circuits/circuits2.py @@ -0,0 +1,45 @@ +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) + +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() From 56ce77195e719d0ed5fcc1b43d5a2d1ec474e5db Mon Sep 17 00:00:00 2001 From: Roland Siegbert Date: Sun, 21 Apr 2024 21:22:25 +0200 Subject: [PATCH 13/15] Add FIXME for additional OpenQASM example --- doc/source/tutorials/includes/circuits/circuits2.py | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/tutorials/includes/circuits/circuits2.py b/doc/source/tutorials/includes/circuits/circuits2.py index 68e7600c84..33c3bbed42 100644 --- a/doc/source/tutorials/includes/circuits/circuits2.py +++ b/doc/source/tutorials/includes/circuits/circuits2.py @@ -30,6 +30,7 @@ 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 From 446e6e188d9155f9b3f7d3dcb3fefee406816af1 Mon Sep 17 00:00:00 2001 From: Roland Siegbert Date: Tue, 2 Jul 2024 05:48:25 +0200 Subject: [PATCH 14/15] Add pulses --- .../tutorials/includes/pulses/pulses0.py | 36 ++++++++++++ .../tutorials/includes/pulses/pulses1.py | 16 ++++++ doc/source/tutorials/pulses.rst | 55 +------------------ 3 files changed, 54 insertions(+), 53 deletions(-) create mode 100644 doc/source/tutorials/includes/pulses/pulses0.py create mode 100644 doc/source/tutorials/includes/pulses/pulses1.py diff --git a/doc/source/tutorials/includes/pulses/pulses0.py b/doc/source/tutorials/includes/pulses/pulses0.py new file mode 100644 index 0000000000..9532f8aaf4 --- /dev/null +++ b/doc/source/tutorials/includes/pulses/pulses0.py @@ -0,0 +1,36 @@ +# pulses0.py + +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, + ) +) diff --git a/doc/source/tutorials/includes/pulses/pulses1.py b/doc/source/tutorials/includes/pulses/pulses1.py new file mode 100644 index 0000000000..1b69a5583e --- /dev/null +++ b/doc/source/tutorials/includes/pulses/pulses1.py @@ -0,0 +1,16 @@ +from qibolab import create_platform +from qibolab.execution_parameters import ExecutionParameters +from pulses0 import sequence + +# 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() \ No newline at end of file diff --git a/doc/source/tutorials/pulses.rst b/doc/source/tutorials/pulses.rst index 51fa61d763..443c6348a1 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. From 76e704cb10dc7f9e24d21b886b5ed2b6287c6f80 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 2 Jul 2024 03:57:17 +0000 Subject: [PATCH 15/15] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- doc/source/tutorials/includes/pulses/pulses0.py | 4 ++-- doc/source/tutorials/includes/pulses/pulses1.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/source/tutorials/includes/pulses/pulses0.py b/doc/source/tutorials/includes/pulses/pulses0.py index 9532f8aaf4..cf9fb97221 100644 --- a/doc/source/tutorials/includes/pulses/pulses0.py +++ b/doc/source/tutorials/includes/pulses/pulses0.py @@ -2,10 +2,10 @@ from qibolab.pulses import ( DrivePulse, - ReadoutPulse, + Gaussian, PulseSequence, + ReadoutPulse, Rectangular, - Gaussian, ) # Define PulseSequence diff --git a/doc/source/tutorials/includes/pulses/pulses1.py b/doc/source/tutorials/includes/pulses/pulses1.py index 1b69a5583e..1bf950dd27 100644 --- a/doc/source/tutorials/includes/pulses/pulses1.py +++ b/doc/source/tutorials/includes/pulses/pulses1.py @@ -1,6 +1,7 @@ +from pulses0 import sequence + from qibolab import create_platform from qibolab.execution_parameters import ExecutionParameters -from pulses0 import sequence # Define platform and load specific runcard platform = create_platform("dummy") @@ -13,4 +14,4 @@ results = platform.execute_pulse_sequence(sequence, options=options) # Disconnect from the instruments -platform.disconnect() \ No newline at end of file +platform.disconnect()