From 447178d068fc72bb9d9af9acf8519aede8795890 Mon Sep 17 00:00:00 2001 From: "jale.ipekoglu" Date: Fri, 5 Nov 2021 18:56:32 +0300 Subject: [PATCH 1/7] initial commit of the calibration tutorial --- docs/tutorials/calibration_experiment.rst | 65 +++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 docs/tutorials/calibration_experiment.rst diff --git a/docs/tutorials/calibration_experiment.rst b/docs/tutorials/calibration_experiment.rst new file mode 100644 index 0000000000..359766a583 --- /dev/null +++ b/docs/tutorials/calibration_experiment.rst @@ -0,0 +1,65 @@ +######################################### +Run a Single-Qubit Calibration Experiment +######################################### + +The calibration module in qiskit-experiments allows users to run calibration experiments to find the pulse shapes and parameter values that maximizes the fidelity of the resulting quantum operations. To produce high fidelity quantum operations, we want to be able to run good gates. Calibrations experiments encapsulates the internal processes and allow experimenters do calibration operations in a quicker way. Without the experiments module, we need to define pulse schedules and plot the resulting measurement data manually (see also `Qiskit textbook `_ for calibrating qubits with Qiskit Terra). + +.. jupyter-execute:: + + import numpy as np + + import qiskit.pulse as pulse + from qiskit.circuit import Parameter + + from qiskit_experiments.calibration_management.backend_calibrations import BackendCalibrations + from qiskit_experiments.library.calibration import Rabi + + from qiskit import IBMQ, schedule + +For this guide, we choose one of the publicly available and pulse-enabled backends. + +.. jupyter-execute:: + + IBMQ.load_account() + provider = IBMQ.get_provider(hub='ibm-q', group='open', project='main') + backend = provider.get_backend('ibmq_armonk') + +======================================================== +1. Calibrating the pulse amplitudes with Rabi experiment +======================================================== +We are going to run a sample Rabi experiment to calibrate rotations between the ground-state \|0\⟩ and the excited state \|1\⟩. We can think of this as a rotation by π radians around the x-axis of the Bloch sphere. Our goal is to seek the amplitude of the pulse needed to achieve this rotation. + +We create a new Rabi experiment instance by providing the qubit index to be calibrated. In the Rabi experiment we apply a pulse at the frequency of the qubit and scan its amplitude to find the amplitude that creates a rotation of a desired angle. + +.. jupyter-execute:: + + qubit = 0 + + rabi = Rabi(qubit) + +We can give custom amplitude values by providing a list to the ``amplitude`` parameter of ``set_experiment_options()`` method or run the experiment with default values. See `API reference `_ for the current default amplitude values. + +.. jupyter-execute:: + + rabi.set_experiment_options( + amplitudes=np.linspace(-0.95, 0.95, 51) + ) + +.. jupyter-execute:: + + #rabi_data = rabi.run(backend) + #rabi_data.block_for_results() # Block until our job and its post processing finish. + #print(rabi_data) + +In the analysis results, ``rabi_rate`` is the unit of frequency which our qubit completes a full cycle from ground-state \|0\⟩ to the excited state \|1\⟩ and back to ground-state \|0\⟩. Using this information we calculate one period, which is a full cycle by 2π radians around the x-axis of the Bloch sphere. However our goal was to seek the amplitude of the pulse needed to achieve a rotation by π radians which will take our qubit from ground-state \|0\⟩ to the excited state \|1\⟩. So we need to divide it by 2. + +.. jupyter-execute:: + + #pi_pulse_amplitude = (1/rabi_data.analysis_results("rabi_rate").value.value) / 2 + #print(pi_pulse_amplitude) + +Optionally we can save our experiment to load the analysis results at a later time. See `Saving Experiment Data to the Cloud `_ guide for further details on saving experiments. + +.. jupyter-execute:: + + #rabi_data.save() From 46ed103621577b1c8a817341d73c125f85ec7831 Mon Sep 17 00:00:00 2001 From: jaleipekoglu Date: Tue, 16 Nov 2021 17:33:48 +0300 Subject: [PATCH 2/7] finalized Rabi experiment and saving csv sections --- docs/tutorials/calibration_experiment.rst | 79 ++++++++++++++++++----- 1 file changed, 63 insertions(+), 16 deletions(-) diff --git a/docs/tutorials/calibration_experiment.rst b/docs/tutorials/calibration_experiment.rst index 359766a583..362db779b1 100644 --- a/docs/tutorials/calibration_experiment.rst +++ b/docs/tutorials/calibration_experiment.rst @@ -2,7 +2,7 @@ Run a Single-Qubit Calibration Experiment ######################################### -The calibration module in qiskit-experiments allows users to run calibration experiments to find the pulse shapes and parameter values that maximizes the fidelity of the resulting quantum operations. To produce high fidelity quantum operations, we want to be able to run good gates. Calibrations experiments encapsulates the internal processes and allow experimenters do calibration operations in a quicker way. Without the experiments module, we need to define pulse schedules and plot the resulting measurement data manually (see also `Qiskit textbook `_ for calibrating qubits with Qiskit Terra). +To produce high fidelity quantum operations, we want to be able to run good gates. The calibration module in qiskit-experiments allows users to run experiments to find the pulse shapes and parameter values that maximizes the fidelity of the resulting quantum operations. Calibrations experiments encapsulates the internal processes and allow experimenters do calibration operations in a quicker way. Without the experiments module, we would need to define pulse schedules and plot the resulting measurement data manually (see also `Qiskit textbook `_ for calibrating qubits with Qiskit Terra). .. jupyter-execute:: @@ -11,8 +11,9 @@ The calibration module in qiskit-experiments allows users to run calibration exp import qiskit.pulse as pulse from qiskit.circuit import Parameter - from qiskit_experiments.calibration_management.backend_calibrations import BackendCalibrations - from qiskit_experiments.library.calibration import Rabi + from qiskit_experiments.calibration_management import BackendCalibrations + + from qiskit.pulse import InstructionScheduleMap from qiskit import IBMQ, schedule @@ -31,35 +32,81 @@ We are going to run a sample Rabi experiment to calibrate rotations between the We create a new Rabi experiment instance by providing the qubit index to be calibrated. In the Rabi experiment we apply a pulse at the frequency of the qubit and scan its amplitude to find the amplitude that creates a rotation of a desired angle. -.. jupyter-execute:: +We do this with the calibration experiment RoughXAmplitudeCal. This is a specialization of the Rabi experiment that will update the calibrations for the X pulse automatically. - qubit = 0 +We first need to define template schedule to calibrate for `x` pulse. - rabi = Rabi(qubit) +.. jupyter-execute:: -We can give custom amplitude values by providing a list to the ``amplitude`` parameter of ``set_experiment_options()`` method or run the experiment with default values. See `API reference `_ for the current default amplitude values. + def setup_cals(backend) -> BackendCalibrations: + """A function to instantiate calibrations and add a couple of template schedules.""" + cals = BackendCalibrations(backend) + + dur = Parameter("dur") + amp = Parameter("amp") + sigma = Parameter("σ") + beta = Parameter("β") + drive = pulse.DriveChannel(Parameter("ch0")) + + # Define and add template schedules. + with pulse.build(name="x") as x: + pulse.play(pulse.Drag(dur, amp, sigma, beta), drive) + + cals.add_schedule(x, num_qubits=1) + + return cals + + def add_parameter_guesses(cals: BackendCalibrations): + + """Add guesses for the parameter values to the calibrations.""" + for sched in ["x"]: + print(sched) + cals.add_parameter_value(80, "σ", schedule=sched) + cals.add_parameter_value(0.5, "β", schedule=sched) + cals.add_parameter_value(320, "dur", schedule=sched) + cals.add_parameter_value(0.5, "amp", schedule=sched) + + cals = setup_cals(backend) + add_parameter_guesses(cals) .. jupyter-execute:: - rabi.set_experiment_options( - amplitudes=np.linspace(-0.95, 0.95, 51) - ) + from qiskit_experiments.library.calibration import RoughAmplitudeCal + + qubit = 0 + + rabi = RoughAmplitudeCal(qubit, cals) .. jupyter-execute:: - #rabi_data = rabi.run(backend) - #rabi_data.block_for_results() # Block until our job and its post processing finish. - #print(rabi_data) + rabi_data = rabi.run(backend) + rabi_data.block_for_results() # Block until our job and its post processing finish. + print(rabi_data) -In the analysis results, ``rabi_rate`` is the unit of frequency which our qubit completes a full cycle from ground-state \|0\⟩ to the excited state \|1\⟩ and back to ground-state \|0\⟩. Using this information we calculate one period, which is a full cycle by 2π radians around the x-axis of the Bloch sphere. However our goal was to seek the amplitude of the pulse needed to achieve a rotation by π radians which will take our qubit from ground-state \|0\⟩ to the excited state \|1\⟩. So we need to divide it by 2. +.. jupyter-execute:: + + rabi_data.figure(0) + +In the analysis results, ``rabi_rate`` is the unit of frequency which our qubit completes a full cycle by 2π radians around the x-axis of the Bloch sphere. Using this information we calculate one period. However our goal was to seek the amplitude of the pulse needed to achieve a rotation by π radians which will take our qubit from ground-state \|0\⟩ to the excited state \|1\⟩. So we need to divide it by 2. .. jupyter-execute:: #pi_pulse_amplitude = (1/rabi_data.analysis_results("rabi_rate").value.value) / 2 #print(pi_pulse_amplitude) -Optionally we can save our experiment to load the analysis results at a later time. See `Saving Experiment Data to the Cloud `_ guide for further details on saving experiments. +================================== +2. Saving and loading calibrations +================================== + +The values of the calibrated parameters can be saved to a .csv file and reloaded at a later point in time. + +.. jupyter-execute:: + + cals.save(file_type="csv", overwrite=True, file_prefix="Armonk") + +After saving the values of the parameters you may restart your kernel. If you do so, you will only need to run the following cell to recover the state of your calibrations. Since the schedules are currently not stored we need to call our `setup_cals` function to populate an instance of `Calibrations` with the template schedules. By contrast, the value of the parameters will be recovered from the file. .. jupyter-execute:: - #rabi_data.save() + cals = BackendCalibrations(backend, library) + cals.load_parameter_values(file_name="Armonkparameter_values.csv") \ No newline at end of file From 7888fd7c819827ca91df9ce8f5d25ff4e5e2f46f Mon Sep 17 00:00:00 2001 From: jaleipekoglu Date: Thu, 18 Nov 2021 17:09:27 +0300 Subject: [PATCH 3/7] use mock backend to fix IBM credentials error --- docs/tutorials/calibration_experiment.rst | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/docs/tutorials/calibration_experiment.rst b/docs/tutorials/calibration_experiment.rst index 362db779b1..88bf8851fb 100644 --- a/docs/tutorials/calibration_experiment.rst +++ b/docs/tutorials/calibration_experiment.rst @@ -15,15 +15,22 @@ To produce high fidelity quantum operations, we want to be able to run good gate from qiskit.pulse import InstructionScheduleMap - from qiskit import IBMQ, schedule + from qiskit import IBMQ, schedules -For this guide, we choose one of the publicly available and pulse-enabled backends. +On our own environment, we may use one of the pulse-enabled real backends like below. .. jupyter-execute:: - IBMQ.load_account() - provider = IBMQ.get_provider(hub='ibm-q', group='open', project='main') - backend = provider.get_backend('ibmq_armonk') + # IBMQ.load_account() + # provider = IBMQ.get_provider(hub='ibm-q', group='open', project='main') + # backend = provider.get_backend('ibmq_armonk') + +We can use a mock backend in case no IBM Quantum Experience credentials found. + +.. jupyter-execute:: + + from qiskit_experiments.test.mock_iq_backend import RabiBackend + backend = RabiBackend() ======================================================== 1. Calibrating the pulse amplitudes with Rabi experiment @@ -91,8 +98,8 @@ In the analysis results, ``rabi_rate`` is the unit of frequency which our qubit .. jupyter-execute:: - #pi_pulse_amplitude = (1/rabi_data.analysis_results("rabi_rate").value.value) / 2 - #print(pi_pulse_amplitude) + pi_pulse_amplitude = (1/rabi_data.analysis_results("rabi_rate").value.value) / 2 + print(pi_pulse_amplitude) ================================== 2. Saving and loading calibrations @@ -108,5 +115,5 @@ After saving the values of the parameters you may restart your kernel. If you do .. jupyter-execute:: - cals = BackendCalibrations(backend, library) + cals = BackendCalibrations(backend) cals.load_parameter_values(file_name="Armonkparameter_values.csv") \ No newline at end of file From 8bb28d3cf30af2bba498bdb2e466e5482e2ea525 Mon Sep 17 00:00:00 2001 From: jaleipekoglu Date: Sat, 20 Nov 2021 15:16:29 +0300 Subject: [PATCH 4/7] add Drag experiment --- docs/tutorials/calibration_experiment.rst | 100 +++++++++++++++++++--- 1 file changed, 87 insertions(+), 13 deletions(-) diff --git a/docs/tutorials/calibration_experiment.rst b/docs/tutorials/calibration_experiment.rst index 88bf8851fb..008129a170 100644 --- a/docs/tutorials/calibration_experiment.rst +++ b/docs/tutorials/calibration_experiment.rst @@ -15,8 +15,6 @@ To produce high fidelity quantum operations, we want to be able to run good gate from qiskit.pulse import InstructionScheduleMap - from qiskit import IBMQ, schedules - On our own environment, we may use one of the pulse-enabled real backends like below. .. jupyter-execute:: @@ -25,22 +23,22 @@ On our own environment, we may use one of the pulse-enabled real backends like b # provider = IBMQ.get_provider(hub='ibm-q', group='open', project='main') # backend = provider.get_backend('ibmq_armonk') -We can use a mock backend in case no IBM Quantum Experience credentials found. +We can use a mock backend in case no IBM Quantum Experience credentials found. For this tutorial, we will use mock backends suited to each experiment. .. jupyter-execute:: from qiskit_experiments.test.mock_iq_backend import RabiBackend backend = RabiBackend() +=================================== +1. Finding qubits with spectroscopy +=================================== + ======================================================== -1. Calibrating the pulse amplitudes with Rabi experiment +2. Calibrating the pulse amplitudes with Rabi experiment ======================================================== We are going to run a sample Rabi experiment to calibrate rotations between the ground-state \|0\⟩ and the excited state \|1\⟩. We can think of this as a rotation by π radians around the x-axis of the Bloch sphere. Our goal is to seek the amplitude of the pulse needed to achieve this rotation. -We create a new Rabi experiment instance by providing the qubit index to be calibrated. In the Rabi experiment we apply a pulse at the frequency of the qubit and scan its amplitude to find the amplitude that creates a rotation of a desired angle. - -We do this with the calibration experiment RoughXAmplitudeCal. This is a specialization of the Rabi experiment that will update the calibrations for the X pulse automatically. - We first need to define template schedule to calibrate for `x` pulse. .. jupyter-execute:: @@ -76,6 +74,12 @@ We first need to define template schedule to calibrate for `x` pulse. cals = setup_cals(backend) add_parameter_guesses(cals) +We create a new Rabi experiment instance by providing the qubit index to be calibrated. In the Rabi experiment we apply a pulse at the frequency of the qubit and scan its amplitude to find the amplitude that creates a rotation of a desired angle. + +We do this with the calibration experiment `RoughAmplitudeCal`. This is a calibration version of the Rabi experiment that will update the calibrations for the X pulse automatically. + +If we do not set any experiment options using `set_experiment_options()` method, experiment will use the default values. Default values can be seen `here `__ under `Experiment Options`. + .. jupyter-execute:: from qiskit_experiments.library.calibration import RoughAmplitudeCal @@ -84,6 +88,14 @@ We first need to define template schedule to calibrate for `x` pulse. rabi = RoughAmplitudeCal(qubit, cals) +The rough amplitude calibration is therefore a Rabi experiment in which each circuit contains a pulse with a gate. Different circuits correspond to pulses with different amplitudes. + +.. jupyter-execute:: + + rabi.circuits()[0].draw() + +After the experiment completes the value of the amplitudes in the calibrations will automatically be updated. This behaviour can be controlled using the `auto_update` argument given to the calibration experiment at initialization. + .. jupyter-execute:: rabi_data = rabi.run(backend) @@ -102,18 +114,80 @@ In the analysis results, ``rabi_rate`` is the unit of frequency which our qubit print(pi_pulse_amplitude) ================================== -2. Saving and loading calibrations +3. Saving and loading calibrations ================================== The values of the calibrated parameters can be saved to a .csv file and reloaded at a later point in time. +.. code-block:: python + + cals.save(file_type="csv", overwrite=True, file_prefix="RabiBackend") + +After saving the values of the parameters we may restart our kernel. If we do so, we will only need to run the following cell to recover the state of the calibrations. Since the schedules are currently not stored we need to call our `setup_cals` function to populate an instance of `Calibrations` with the template schedules. By contrast, the value of the parameters will be recovered from the file. + +.. code-block:: python + + from qiskit_experiments.test.mock_iq_backend import RabiBackend + rabi_backend = RabiBackend() + cals = BackendCalibrations(rabi_backend) + cals.load_parameter_values(file_name="RabiBackendparameter_values.csv") + +======================================================= +4. Using the Calibrated Amplitude in Another Experiment +======================================================= +------------------------------------------------------ +4.1. Calibrating the value of the DRAG coefficient +------------------------------------------------------ + +A Derivative Removal by Adiabatic Gate (DRAG) pulse is designed to minimize leakage +to a neighbouring transition. It is a standard pulse with an additional derivative +component. It is designed to reduce the frequency spectrum of a normal pulse near +the $|1\rangle$ - $|2\rangle$ transition, reducing the chance of leakage +to the $|2\rangle$ state. The optimal value of the DRAG parameter is chosen to +minimize both leakage and phase errors resulting from the AC Stark shift. +The pulse envelope is $f(t) = \Omega_x(t) + j \beta \frac{\rm d}{{\rm d }t} \Omega_x(t)$. +Here, $\Omega_x$ is the envelop of the in-phase component of the pulse and +$\beta$ is the strength of the quadrature which we refer to as the DRAG +parameter and seek to calibrate in this experiment. +The DRAG calibration will run +several series of circuits. In a given circuit a Rp(β) - Rm(β) block is repeated +$N$ times. Here, Rp is a rotation with a positive angle and Rm is the same rotation +with a negative amplitude. + +We use a mock backend in case no IBM credentials found. + .. jupyter-execute:: - cals.save(file_type="csv", overwrite=True, file_prefix="Armonk") + from qiskit_experiments.test.mock_iq_backend import DragBackend + drag_backend = DragBackend(gate_name="Drag(x)") -After saving the values of the parameters you may restart your kernel. If you do so, you will only need to run the following cell to recover the state of your calibrations. Since the schedules are currently not stored we need to call our `setup_cals` function to populate an instance of `Calibrations` with the template schedules. By contrast, the value of the parameters will be recovered from the file. +We define the template schedule for `x` pulse using previous methods. + +Note that, if we run the experiments on real backends, we wouldn't need to define template schedules again. .. jupyter-execute:: - cals = BackendCalibrations(backend) - cals.load_parameter_values(file_name="Armonkparameter_values.csv") \ No newline at end of file + cals = setup_cals(drag_backend) + add_parameter_guesses(cals) + +We create a calibration version of Drag experiment instance by providing the qubit index to be calibrated. We use the calibration version of Drag experiment `RoughDragCal`. This is a calibration version of the Rabi experiment that will update the calibrations for the X pulse automatically. + +If we do not set any experiment options using `set_experiment_options()` method, experiment will use the default values. Default values can be seen `here `__ under `Experiment Options`. + +.. jupyter-execute:: + + from qiskit_experiments.library import RoughDragCal + drag = RoughDragCal(qubit, cals) + +.. jupyter-execute:: + + drag_data = drag.run(drag_backend) + drag_data.block_for_results() + +.. jupyter-execute:: + + drag_data.figure(0) + +=============== +5. Failure Mode +=============== \ No newline at end of file From d3b54e0d7b1dd706096c9c39e4fb04b89993386d Mon Sep 17 00:00:00 2001 From: jaleipekoglu Date: Tue, 7 Dec 2021 18:09:59 +0300 Subject: [PATCH 5/7] add qubit spectroscopy and miscalibrations --- ...riment.rst => calibration_experiments.rst} | 145 +++++++++++++--- qiskit_experiments/test/base.py | 95 +++++++++++ .../test/test_qubit_spectroscopy.py | 161 ++++++++++++++++++ 3 files changed, 379 insertions(+), 22 deletions(-) rename docs/tutorials/{calibration_experiment.rst => calibration_experiments.rst} (68%) create mode 100644 qiskit_experiments/test/base.py create mode 100644 qiskit_experiments/test/test_qubit_spectroscopy.py diff --git a/docs/tutorials/calibration_experiment.rst b/docs/tutorials/calibration_experiments.rst similarity index 68% rename from docs/tutorials/calibration_experiment.rst rename to docs/tutorials/calibration_experiments.rst index 008129a170..9ba6aaf600 100644 --- a/docs/tutorials/calibration_experiment.rst +++ b/docs/tutorials/calibration_experiments.rst @@ -4,6 +4,8 @@ Run a Single-Qubit Calibration Experiment To produce high fidelity quantum operations, we want to be able to run good gates. The calibration module in qiskit-experiments allows users to run experiments to find the pulse shapes and parameter values that maximizes the fidelity of the resulting quantum operations. Calibrations experiments encapsulates the internal processes and allow experimenters do calibration operations in a quicker way. Without the experiments module, we would need to define pulse schedules and plot the resulting measurement data manually (see also `Qiskit textbook `_ for calibrating qubits with Qiskit Terra). +Each experiment usually provides additional information about the system used in subsequent experiments. + .. jupyter-execute:: import numpy as np @@ -13,33 +15,25 @@ To produce high fidelity quantum operations, we want to be able to run good gate from qiskit_experiments.calibration_management import BackendCalibrations - from qiskit.pulse import InstructionScheduleMap - -On our own environment, we may use one of the pulse-enabled real backends like below. +On our own environment, we may use one of the pulse-enabled real backends for all the experiments like below. .. jupyter-execute:: + # from qiskit import IBMQ # IBMQ.load_account() # provider = IBMQ.get_provider(hub='ibm-q', group='open', project='main') # backend = provider.get_backend('ibmq_armonk') -We can use a mock backend in case no IBM Quantum Experience credentials found. For this tutorial, we will use mock backends suited to each experiment. +We can verify whether the backend supports Pulse features by checking the backend configuration. -.. jupyter-execute:: +.. jupyter-execute:: + + # backend_config = backend.configuration() + # assert backend_config.open_pulse, "Backend doesn't support Pulse" - from qiskit_experiments.test.mock_iq_backend import RabiBackend - backend = RabiBackend() +On the other hand we can also use a mock backend in case no IBM Quantum Experience credentials found. For this tutorial, we will use mock backends prepared for each experiment. -=================================== -1. Finding qubits with spectroscopy -=================================== - -======================================================== -2. Calibrating the pulse amplitudes with Rabi experiment -======================================================== -We are going to run a sample Rabi experiment to calibrate rotations between the ground-state \|0\⟩ and the excited state \|1\⟩. We can think of this as a rotation by π radians around the x-axis of the Bloch sphere. Our goal is to seek the amplitude of the pulse needed to achieve this rotation. - -We first need to define template schedule to calibrate for `x` pulse. +To use in the experiments we first need to define template schedule to calibrate for `x` pulse. .. jupyter-execute:: @@ -71,7 +65,71 @@ We first need to define template schedule to calibrate for `x` pulse. cals.add_parameter_value(320, "dur", schedule=sched) cals.add_parameter_value(0.5, "amp", schedule=sched) - cals = setup_cals(backend) +=================================== +1. Finding qubits with spectroscopy +=================================== +Typically, the first experiment we do is to search for the qubit frequency, which is the difference between the ground and excited states. This frequency will be crucial for creating pulses which enact particular quantum operators on the qubit. + +We start with a mock backend. + +.. jupyter-execute:: + + from qiskit_experiments.test.test_qubit_spectroscopy import SpectroscopyBackend + spec_backend = SpectroscopyBackend() + +We then setup calibrations for the backend. + +.. jupyter-execute:: + + cals = setup_cals(spec_backend) # Block until our job and its post processing finish. + add_parameter_guesses(cals) + +We define the qubit we will work with and prepare the experiment using `RoughFrequencyCal`. + +.. jupyter-execute:: + + from qiskit_experiments.library.calibration.rough_frequency import RoughFrequencyCal + + qubit = 0 + freq01_estimate = spec_backend.defaults().qubit_freq_est[qubit] + frequencies = np.linspace(freq01_estimate -15e6, freq01_estimate + 15e6, 51) + spec = RoughFrequencyCal(qubit, cals, frequencies, backend=spec_backend) + +.. jupyter-execute:: + + circuit = spec.circuits()[0] + circuit.draw() + +We run the experiment. After the experiment completes the value of the amplitudes in the calibrations will automatically be updated. This behaviour can be controlled using the `auto_update` argument given to the calibration experiment at initialization. + +.. jupyter-execute:: + + spec_data = spec.run().block_for_results() + spec_data.figure(0) + +We can see the analysis results + +.. jupyter-execute:: + + print(spec_data.analysis_results("f01")) + +======================================================== +2. Calibrating the pulse amplitudes with Rabi experiment +======================================================== +We are going to run a sample Rabi experiment to calibrate rotations between the ground-state \|0\⟩ and the excited state \|1\⟩. We can think of this as a rotation by π radians around the x-axis of the Bloch sphere. Our goal is to seek the amplitude of the pulse needed to achieve this rotation. + +First we define the mock backend. + +.. jupyter-execute:: + + from qiskit_experiments.test.mock_iq_backend import RabiBackend + rabi_backend = RabiBackend() + +We then setup calibrations for the backend. + +.. jupyter-execute:: + + cals = setup_cals(rabi_backend) add_parameter_guesses(cals) We create a new Rabi experiment instance by providing the qubit index to be calibrated. In the Rabi experiment we apply a pulse at the frequency of the qubit and scan its amplitude to find the amplitude that creates a rotation of a desired angle. @@ -98,7 +156,7 @@ After the experiment completes the value of the amplitudes in the calibrations w .. jupyter-execute:: - rabi_data = rabi.run(backend) + rabi_data = rabi.run(rabi_backend) rabi_data.block_for_results() # Block until our job and its post processing finish. print(rabi_data) @@ -188,6 +246,49 @@ If we do not set any experiment options using `set_experiment_options()` method, drag_data.figure(0) -=============== -5. Failure Mode -=============== \ No newline at end of file +================== +5. Miscalibrations +================== + +In this section, we will see what if we run a miscalibrated `X` gate - with a false amplitude - on a qubit. After that, we will use the amplitude value we get from the Rabi experiment above to see the difference. + +Note that, the following lines are for demonstration purposes and should be run on a real backend to see the actual difference. + +We first define a simple circuit that contains an X gate and measurement. + +.. jupyter-execute:: + + from qiskit import QuantumCircuit + + circ = QuantumCircuit(1, 1) + circ.x(0) + circ.measure(0, 0) + circ.draw() + +Then we define a calibration for the `X` gate on qubit 0. For the `amp` parameter we use a default wrong value. + +.. jupyter-execute:: + + from qiskit import pulse, transpile + from qiskit.test.mock import FakeArmonk + from qiskit.pulse.library import Constant + backend = FakeArmonk() + + # build a simple circuit that only contain one x gate and measurement + circ = QuantumCircuit(1, 1) + circ.x(0) + circ.measure(0, 0) + with pulse.build(backend) as my_schedule: + pulse.play(Constant(duration=10, amp=0.1), pulse.drive_channel(0)) # build the constant pulse + + circ.add_calibration('x', [0], my_schedule) # map x gate in qubit 0 to my_schedule + circ = transpile(circ, backend) + circ.draw(idle_wires=False) + +Execute our circuit + +.. jupyter-execute:: + + result = backend.run(transpile(circ, backend), shots=1000).result() + counts = result.get_counts(circ) + print(counts) \ No newline at end of file diff --git a/qiskit_experiments/test/base.py b/qiskit_experiments/test/base.py new file mode 100644 index 0000000000..572906afc3 --- /dev/null +++ b/qiskit_experiments/test/base.py @@ -0,0 +1,95 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +Qiskit Experiments test case class +""" + +from typing import Any, Callable, Optional +import json +import numpy as np + +from qiskit.test import QiskitTestCase +from qiskit_experiments.framework import ExperimentDecoder, ExperimentEncoder + + +class QiskitExperimentsTestCase(QiskitTestCase): + """Qiskit Experiments specific extra functionality for test cases.""" + + def assertRoundTripSerializable(self, obj: Any, check_func: Optional[Callable] = None): + """Assert that an object is round trip serializable. + + Args: + obj: the object to be serialized. + check_func: Optional, a custom function ``check_func(a, b) -> bool`` + to check equality of the original object with the decoded + object. If None the ``__eq__`` method of the original + object will be used. + """ + try: + encoded = json.dumps(obj, cls=ExperimentEncoder) + except TypeError: + self.fail("JSON serialization raised unexpectedly.") + try: + decoded = json.loads(encoded, cls=ExperimentDecoder) + except TypeError: + self.fail("JSON deserialization raised unexpectedly.") + if check_func is None: + self.assertEqual(obj, decoded) + else: + self.assertTrue(check_func(obj, decoded), msg=f"{obj} != {decoded}") + + @staticmethod + def experiments_equiv(exp1, exp2) -> bool: + """Check if two experiments are equivalent by comparing their configs""" + # pylint: disable = too-many-boolean-expressions, too-many-return-statements + config1 = exp1.config + config2 = exp2.config + try: + if config1 == config2: + return True + except ValueError: + pass + if ( + config1.cls != config2.cls + or len(config1.args) != len(config2.args) + or len(config1.kwargs) != len(config2.kwargs) + or len(config1.experiment_options) != len(config2.experiment_options) + or len(config1.transpile_options) != len(config2.transpile_options) + or len(config1.run_options) != len(config2.run_options) + ): + return False + + # Check each entry + for arg1, arg2 in zip(config1.args, config2.args): + if isinstance(arg1, np.ndarray) or isinstance(arg2, np.ndarray): + if not np.all(np.asarray(arg1) == np.asarray(arg2)): + return False + elif isinstance(arg1, tuple) or isinstance(arg2, tuple): + # JSON serialization converts tuples to lists + if list(arg1) != list(arg2): + return False + elif arg1 != arg2: + return False + for attr in ["kwargs", "experiment_options", "transpile_options", "run_options"]: + dict1 = getattr(config1, attr) + dict2 = getattr(config2, attr) + for key1, val1 in dict1.items(): + val2 = dict2[key1] + if isinstance(val1, np.ndarray) or isinstance(val2, np.ndarray): + if not np.allclose(val1, val2): + return False + elif isinstance(val1, tuple) or isinstance(val2, tuple): + if list(val1) != list(val2): + return False + elif val1 != val2: + return False + return True diff --git a/qiskit_experiments/test/test_qubit_spectroscopy.py b/qiskit_experiments/test/test_qubit_spectroscopy.py new file mode 100644 index 0000000000..c837a45136 --- /dev/null +++ b/qiskit_experiments/test/test_qubit_spectroscopy.py @@ -0,0 +1,161 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Spectroscopy tests.""" +from qiskit_experiments.test.base import QiskitExperimentsTestCase +from typing import Tuple +import numpy as np + +from qiskit import QuantumCircuit +from qiskit.qobj.utils import MeasLevel + +from qiskit_experiments.library import QubitSpectroscopy, EFSpectroscopy +from qiskit_experiments.test.mock_iq_backend import MockIQBackend + + +class SpectroscopyBackend(MockIQBackend): + """A simple and primitive backend to test spectroscopy experiments.""" + + def __init__( + self, + line_width: float = 2e6, + freq_offset: float = 0.0, + iq_cluster_centers: Tuple[float, float, float, float] = (1.0, 1.0, -1.0, -1.0), + iq_cluster_width: float = 0.2, + ): + """Initialize the spectroscopy backend.""" + + super().__init__(iq_cluster_centers, iq_cluster_width) + + self.configuration().basis_gates = ["x"] + + self._linewidth = line_width + self._freq_offset = freq_offset + + super().__init__(iq_cluster_centers, iq_cluster_width) + + def _compute_probability(self, circuit: QuantumCircuit) -> float: + """Returns the probability based on the frequency.""" + freq_shift = next(iter(circuit.calibrations["Spec"]))[1][0] + delta_freq = freq_shift - self._freq_offset + return np.exp(-(delta_freq ** 2) / (2 * self._linewidth ** 2)) + + +class TestQubitSpectroscopy(QiskitExperimentsTestCase): + """Test spectroscopy experiment.""" + + def test_spectroscopy_end2end_classified(self): + """End to end test of the spectroscopy experiment.""" + + backend = SpectroscopyBackend(line_width=2e6) + qubit = 1 + freq01 = backend.defaults().qubit_freq_est[qubit] + frequencies = np.linspace(freq01 - 10.0e6, freq01 + 10.0e6, 21) + + spec = QubitSpectroscopy(qubit, frequencies, unit="Hz") + spec.set_run_options(meas_level=MeasLevel.CLASSIFIED) + expdata = spec.run(backend) + expdata.block_for_results() + result = expdata.analysis_results(1) + value = result.value.value + + self.assertTrue(4.999e9 < value < 5.001e9) + self.assertEqual(result.quality, "good") + + # Test if we find still find the peak when it is shifted by 5 MHz. + backend = SpectroscopyBackend(line_width=2e6, freq_offset=5.0e6) + + spec = QubitSpectroscopy(qubit, frequencies, unit="Hz") + spec.set_run_options(meas_level=MeasLevel.CLASSIFIED) + expdata = spec.run(backend) + expdata.block_for_results() + result = expdata.analysis_results(1) + value = result.value.value + + self.assertTrue(5.0049e9 < value < 5.0051e9) + self.assertEqual(result.quality, "good") + + def test_spectroscopy_end2end_kerneled(self): + """End to end test of the spectroscopy experiment on IQ data.""" + + backend = SpectroscopyBackend(line_width=2e6) + qubit = 0 + freq01 = backend.defaults().qubit_freq_est[qubit] + frequencies = np.linspace(freq01 - 10.0e6, freq01 + 10.0e6, 21) / 1e6 + + spec = QubitSpectroscopy(qubit, frequencies, unit="MHz") + expdata = spec.run(backend) + expdata.block_for_results() + result = expdata.analysis_results(1) + value = result.value.value + + self.assertTrue(freq01 - 2e6 < value < freq01 + 2e6) + self.assertEqual(result.quality, "good") + + # Test if we find still find the peak when it is shifted by 5 MHz. + backend = SpectroscopyBackend(line_width=2e6, freq_offset=5.0e6) + + spec = QubitSpectroscopy(qubit, frequencies, unit="MHz") + expdata = spec.run(backend) + expdata.block_for_results() + result = expdata.analysis_results(1) + value = result.value.value + + self.assertTrue(freq01 + 3e6 < value < freq01 + 8e6) + self.assertEqual(result.quality, "good") + + spec.set_run_options(meas_return="avg") + expdata = spec.run(backend) + expdata.block_for_results() + result = expdata.analysis_results(1) + value = result.value.value + + self.assertTrue(freq01 + 3e6 < value < freq01 + 8e6) + self.assertEqual(result.quality, "good") + + def test_spectroscopy12_end2end_classified(self): + """End to end test of the spectroscopy experiment with an x pulse.""" + + backend = SpectroscopyBackend(line_width=2e6) + qubit = 0 + freq01 = backend.defaults().qubit_freq_est[qubit] + frequencies = np.linspace(freq01 - 10.0e6, freq01 + 10.0e6, 21) + + # Note that the backend is not sophisticated enough to simulate an e-f + # transition so we run the test with g-e. + spec = EFSpectroscopy(qubit, frequencies, unit="Hz") + spec.backend = backend + spec.set_run_options(meas_level=MeasLevel.CLASSIFIED) + expdata = spec.run(backend) + expdata.block_for_results() + result = expdata.analysis_results(1) + value = result.value.value + + self.assertTrue(freq01 - 2e6 < value < freq01 + 2e6) + self.assertEqual(result.quality, "good") + + # Test the circuits + circ = spec.circuits()[0] + self.assertEqual(circ.data[0][0].name, "x") + self.assertEqual(circ.data[1][0].name, "Spec") + + def test_experiment_config(self): + """Test converting to and from config works""" + exp = QubitSpectroscopy(1, np.linspace(100, 150, 20), unit="MHz") + loaded_exp = QubitSpectroscopy.from_config(exp.config) + self.assertNotEqual(exp, loaded_exp) + self.assertTrue(self.experiments_equiv(exp, loaded_exp)) + + def test_roundtrip_serializable(self): + """Test round trip JSON serialization""" + exp = QubitSpectroscopy(1, np.linspace(100, 150, 20), unit="MHz") + self.assertRoundTripSerializable(exp, self.experiments_equiv) From dd45c5cf2b8fcebc331ae67ab0f85126014b56fd Mon Sep 17 00:00:00 2001 From: jaleipekoglu Date: Wed, 8 Dec 2021 13:26:05 +0300 Subject: [PATCH 6/7] change the order of imports --- qiskit_experiments/test/test_qubit_spectroscopy.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qiskit_experiments/test/test_qubit_spectroscopy.py b/qiskit_experiments/test/test_qubit_spectroscopy.py index c837a45136..25ff96ec1c 100644 --- a/qiskit_experiments/test/test_qubit_spectroscopy.py +++ b/qiskit_experiments/test/test_qubit_spectroscopy.py @@ -11,13 +11,15 @@ # that they have been altered from the originals. """Spectroscopy tests.""" -from qiskit_experiments.test.base import QiskitExperimentsTestCase from typing import Tuple import numpy as np from qiskit import QuantumCircuit from qiskit.qobj.utils import MeasLevel + +from qiskit_experiments.test.base import QiskitExperimentsTestCase + from qiskit_experiments.library import QubitSpectroscopy, EFSpectroscopy from qiskit_experiments.test.mock_iq_backend import MockIQBackend From febc4c9292315b1c167f6d7b679809aaa2d85f9c Mon Sep 17 00:00:00 2001 From: jaleipekoglu Date: Wed, 8 Dec 2021 17:50:16 +0300 Subject: [PATCH 7/7] group imports from qiskit_experiments --- qiskit_experiments/test/test_qubit_spectroscopy.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit_experiments/test/test_qubit_spectroscopy.py b/qiskit_experiments/test/test_qubit_spectroscopy.py index 25ff96ec1c..3c0b9845a4 100644 --- a/qiskit_experiments/test/test_qubit_spectroscopy.py +++ b/qiskit_experiments/test/test_qubit_spectroscopy.py @@ -19,7 +19,6 @@ from qiskit_experiments.test.base import QiskitExperimentsTestCase - from qiskit_experiments.library import QubitSpectroscopy, EFSpectroscopy from qiskit_experiments.test.mock_iq_backend import MockIQBackend