From 97c2738666baeeae10da8472b51d4088da37ed9f Mon Sep 17 00:00:00 2001 From: Michal Goldenshtein Date: Mon, 22 Jan 2024 16:32:05 -0600 Subject: [PATCH] adding a function to qdac2 driver --- .../Single_Spin_EDSR/qdac2_driver.py | 18 + .../Singlet_Triplet_Qubit/qdac2_driver.py | 18 + ...n_with_the_OPX_and_pyvisa-checkpoint.ipynb | 477 ++++++++++++++++ ...n_with_the_OPX_and_qcodes-checkpoint.ipynb | 527 ++++++++++++++++++ .../.ipynb_checkpoints/readme-checkpoint.md | 24 + .../Integration_with_the_OPX_and_pyvisa.ipynb | 57 +- .../Integration_with_the_OPX_and_qcodes.ipynb | 9 +- 7 files changed, 1118 insertions(+), 12 deletions(-) create mode 100644 Tutorials/intro-to-qdac2/.ipynb_checkpoints/Integration_with_the_OPX_and_pyvisa-checkpoint.ipynb create mode 100644 Tutorials/intro-to-qdac2/.ipynb_checkpoints/Integration_with_the_OPX_and_qcodes-checkpoint.ipynb create mode 100644 Tutorials/intro-to-qdac2/.ipynb_checkpoints/readme-checkpoint.md diff --git a/Quantum-Control-Applications/Quantum-Dots/Single_Spin_EDSR/qdac2_driver.py b/Quantum-Control-Applications/Quantum-Dots/Single_Spin_EDSR/qdac2_driver.py index d74e74959..0ea12c84a 100644 --- a/Quantum-Control-Applications/Quantum-Dots/Single_Spin_EDSR/qdac2_driver.py +++ b/Quantum-Control-Applications/Quantum-Dots/Single_Spin_EDSR/qdac2_driver.py @@ -97,3 +97,21 @@ def load_voltage_list( print( f"Set-up QDAC2 channel {channel} to step voltages from a list of {len(voltage_list)} items on trigger events from the {trigger_port} port with a {qdac.query(f'sour{channel}:dc:list:dwell?')} s dwell time." ) + + +# set the QDAC voltage to a specific value +def set_QDAC_voltage( + qdac, + channel: int, + voltage: float, +): + """ + Configure a QDAC2 channel to play a specicif voltage, using pyvisa commands. + + :param qdac: the QDAC2 object. + :param channel: the QDAC2 channel that will output the voltage from the voltage list. + :param voltage: the desired voltage to output from the QDAC channel. + :return: + """ + qdac.write(f"sour{channel}:dc:mode FIX") + qdac.write(f"sour{channel}:volt {voltage}") diff --git a/Quantum-Control-Applications/Quantum-Dots/Singlet_Triplet_Qubit/qdac2_driver.py b/Quantum-Control-Applications/Quantum-Dots/Singlet_Triplet_Qubit/qdac2_driver.py index d74e74959..0eed9c12b 100644 --- a/Quantum-Control-Applications/Quantum-Dots/Singlet_Triplet_Qubit/qdac2_driver.py +++ b/Quantum-Control-Applications/Quantum-Dots/Singlet_Triplet_Qubit/qdac2_driver.py @@ -97,3 +97,21 @@ def load_voltage_list( print( f"Set-up QDAC2 channel {channel} to step voltages from a list of {len(voltage_list)} items on trigger events from the {trigger_port} port with a {qdac.query(f'sour{channel}:dc:list:dwell?')} s dwell time." ) + + +# set the QDAC voltage to a specific value +def set_QDAC_voltage( + qdac, + channel: int, + voltage: float, +): + """ + Configure a QDAC2 channel to play a specicif voltage, using pyvisa commands. + + :param qdac: the QDAC2 object. + :param channel: the QDAC2 channel that will output the voltage from the voltage list. + :param voltage: the desired voltage to output from the QDAC channel. + :return: + """ + qdac.write(f"sour{channel}:dc:mode FIX") + qdac.write(f"sour{channel}:volt {voltage}") \ No newline at end of file diff --git a/Tutorials/intro-to-qdac2/.ipynb_checkpoints/Integration_with_the_OPX_and_pyvisa-checkpoint.ipynb b/Tutorials/intro-to-qdac2/.ipynb_checkpoints/Integration_with_the_OPX_and_pyvisa-checkpoint.ipynb new file mode 100644 index 000000000..5efa61432 --- /dev/null +++ b/Tutorials/intro-to-qdac2/.ipynb_checkpoints/Integration_with_the_OPX_and_pyvisa-checkpoint.ipynb @@ -0,0 +1,477 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Tutorial showing the integration between the QDAC2 and the OPX to perform voltage sweeps using the pyvisa commands\n", + "\n", + "In this tutorial you will learn how to combine the QDAC2 with the OPX to perform n-dimensional voltage scans efficiently.\n", + "\n", + "The trick here is to use a digital marker from the OPX to trigger the QDAC and make it step the output voltage of a given channel across a pre-loaded voltage list. Please note that the size of this list is limited to 65536 items per channel. To this purpose, an element called \"qdac_trigger1\" was created in the config and a digital pulse was defined. You can read more about digital waveform manipulations here: https://docs.quantum-machines.co/0.1/qm-qua-sdk/docs/Introduction/qua_overview/?h=digi#digital-waveform-manipulations\n", + "\n", + "In this case, the latency induced by the communication between the control PC and the instruments and by transferring the data is suppressed since everything is controlled by the OPX pulse processor. The experimental runtime is then given by the QDAC bandwidth and the integration time. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import pyvisa as visa\n", + "from qm.QuantumMachinesManager import QuantumMachinesManager\n", + "from qm.qua import *\n", + "from configuration import *\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "from time import time, sleep\n", + "from qualang_tools.plot import interrupt_on_close\n", + "from qualang_tools.results import wait_until_job_is_paused, fetching_tool\n", + "\n", + "%matplotlib qt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Helper functions\n", + "\n", + "A class called **QDACII** is created to enable the communication with the QDAC. \n", + "It can also be modified to create your own driver.\n", + "\n", + "A function is also defined in order to easily program the QDAC2 to load a voltage list and step through it on the event of an external trigger (provided by the OPX here). Several parameters such as the filter, dynamic range and dwell time can be set." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# QDAC2 instrument class\n", + "class QDACII:\n", + " def __init__(\n", + " self,\n", + " communication_type: str,\n", + " IP_address: str = None,\n", + " port: int = 5025,\n", + " USB_device: int = None,\n", + " lib: str = \"@py\",\n", + " ):\n", + " \"\"\"\n", + " Open the communication to a QDAC2 instrument with python. The communication can be enabled via either Ethernet or USB.\n", + "\n", + " :param communication_type: Can be either \"Ethernet\" or \"USB\".\n", + " :param IP_address: IP address of the instrument - required only for Ethernet communication.\n", + " :param port: port of the instrument, 5025 by default - required only for Ethernet communication.\n", + " :param USB_device: identification number of the device - required only for USB communication.\n", + " :param lib: use '@py' to use pyvisa-py backend (default).\n", + " \"\"\"\n", + " rm = visa.ResourceManager(lib) # To use pyvisa-py backend, use argument '@py'\n", + " if communication_type == \"Ethernet\":\n", + " self._visa = rm.open_resource(f\"TCPIP::{IP_address}::{port}::SOCKET\")\n", + " self._visa.baud_rate = 921600\n", + " # self._visa.send_end = False\n", + " elif communication_type == \"USB\":\n", + " self._visa = rm.open_resource(f\"ASRL{USB_device}::INSTR\")\n", + "\n", + " self._visa.write_termination = \"\\n\"\n", + " self._visa.read_termination = \"\\n\"\n", + " print(self._visa.query(\"*IDN?\"))\n", + " print(self._visa.query(\"syst:err:all?\"))\n", + "\n", + " def query(self, cmd):\n", + " return self._visa.query(cmd)\n", + "\n", + " def write(self, cmd):\n", + " self._visa.write(cmd)\n", + "\n", + " def write_binary_values(self, cmd, values):\n", + " self._visa.write_binary_values(cmd, values)\n", + "\n", + " def __exit__(self):\n", + " self.close()\n", + "\n", + "\n", + "# load list of voltages to the relevant QDAC2 channel\n", + "def load_voltage_list(\n", + " qdac, channel: int, dwell: float, slew_rate: float, trigger_port: str, output_range: str, output_filter: str, voltage_list: list\n", + "):\n", + " \"\"\"\n", + " Configure a QDAC2 channel to play a set of voltages from a given list and step through it according to an external trigger given by an OPX digital marker, using pyvisa commands.\n", + "\n", + " :param qdac: the QDAC2 object.\n", + " :param channel: the QDAC2 channel that will output the voltage from the voltage list.\n", + " :param dwell: dwell time at each voltage level in seconds - must be smaller than the trigger spacing and larger than 2e-6.\n", + " :param slew_rate: the rate at which the voltage can change in Volt per seconds to avoid transients from abruptly stepping the voltage. Must be within [0.01; 2e7].\n", + " :param trigger_port: external trigger port to which a digital marker from the OPX is connected - must be in [\"ext1\", \"ext2\", \"ext3\", \"ext4\"].\n", + " :param output_range: the channel output range that can be either \"low\" (+/-2V) or \"high\" (+/-10V).\n", + " :param output_filter: the channel output filter that can be either \"dc\" (10Hz), \"med\" (10kHz) or \"high\" (300kHZ).\n", + " :param voltage_list: list containing the desired voltages to output - the size of the list must not exceed 65536 items.\n", + " :return:\n", + " \"\"\"\n", + " # Load the list of voltages\n", + " qdac.write_binary_values(f\"sour{channel}:dc:list:volt \", voltage_list)\n", + " # Ensure that the output voltage will start from the beginning of the list.\n", + " qdac.write(f\"sour{channel}:dc:init:cont off\")\n", + " # Set the minimum time spent on each voltage level. Must be between 2µs and the time between two trigger events.\n", + " qdac.write(f\"sour{channel}:dc:list:dwell {dwell}\")\n", + " # Set the maximum voltage slope in V/s\n", + " qdac.write(f\"sour{channel}:dc:volt:slew {slew_rate}\")\n", + " # Step through the voltage list on the event of a trigger\n", + " qdac.write(f\"sour{channel}:dc:list:tmode stepped\")\n", + " # Set the external trigger port. Must be in [\"ext1\", \"ext2\", \"ext3\", \"ext4\"]\n", + " qdac.write(f\"sour{channel}:dc:trig:sour {trigger_port}\")\n", + " # Listen continuously to trigger\n", + " qdac.write(f\"sour{channel}:dc:init:cont on\")\n", + " # Make sure that the correct DC mode (LIST) is set, as opposed to FIXed.\n", + " qdac.write(f\"sour{channel}:dc:mode LIST\")\n", + " # Set the channel output range\n", + " qdac.write(f\"sour{channel}:rang {output_range}\")\n", + " # Set the channel output filter\n", + " qdac.write(f\"sour{channel}:filt {output_filter}\")\n", + " sleep(1)\n", + " print(\n", + " f\"Set-up QDAC2 channel {channel} to step voltages from a list of {len(voltage_list)} items on trigger events from the {trigger_port} port with a {qdac.query(f'sour{channel}:dc:list:dwell?')} s dwell time.\"\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Connect to the instruments" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Create the qdac instrument\n", + "qdac = QDACII(\"Ethernet\", IP_address=\"127.0.0.1\", port=5025) # Using Ethernet protocol\n", + "# qdac = QDACII(\"USB\", USB_device=4) # Using USB protocol" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Open a Quantum Machine Manager\n", + "qmm = QuantumMachinesManager(host=\"127.0.0.1\", cluster_name=\"my_cluster\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Example experiments:\n", + "1. 1D voltage sweep performed by the QDAC only\n", + "2. 1D voltage sweep performed by the QDAC triggered by the OPX\n", + "3. 2D voltage sweep where both axes are scanned using the triggering method" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Experiment 1: 1D voltage sweep performed by the QDAC only\n", + "In this example, the QDAC is parametrized to output a constant voltage which is swept outside of QUA using a python loop. Here the OPX is simply measuring at each level, but the QUA program can easily be modified to complexify the sequence.\n", + "\n", + "This method is similar to how any external DC voltage source would be integrated with the OPX without triggering." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n_avg = 100 # Number of averaging loops\n", + "# Voltage values in Volt\n", + "voltage_values1 = list(np.linspace(-1.5, 1.5, 101))\n", + "\n", + "### OPX section\n", + "with program() as prog:\n", + " i = declare(int)\n", + " n = declare(int)\n", + " data = declare(fixed)\n", + " data_st = declare_stream()\n", + " with for_(i, 0, i < len(voltage_values1), i + 1):\n", + " pause()\n", + " with for_(n, 0, n < n_avg, n + 1):\n", + " # Wait 1ms before measuring (depends of the QDAC filter option)\n", + " wait(1_000_000 // 4, \"readout_element\")\n", + " # Measure for 10µs with the OPX - Can be replaced by dual_demod, demod or else\n", + " measure(\"readout\", \"readout_element\", None, integration.full(\"const\", data, \"out1\"))\n", + " # Send the result to the stream processing\n", + " save(data, data_st)\n", + "\n", + " with stream_processing():\n", + " # Average all the data and save the values into \"data\".\n", + " data_st.buffer(n_avg).map(FUNCTIONS.average()).save_all(\"data\")\n", + "\n", + "# Open a quantum machine\n", + "qm = qmm.open_qm(config)\n", + "\n", + "### QDAC2 section\n", + "qdac_channel = 1\n", + "qdac.write(\"*rst\") # Reset the qdac parameters to start from a blank instrument\n", + "# Set the current range (\"high\" or \"low\") and filter (\"dc\": 10Hz , \"med\": 10khz, \"high\": 300kHz)\n", + "# Set the channel output range\n", + "qdac.write(f\"sour{qdac_channel}:rang low\")\n", + "# Set the channel output filter\n", + "qdac.write(f\"sour{qdac_channel}:filt med\")\n", + "# Set the slew rate in V/s to avoid transients when abruptly stepping the voltage -Must be within [0.01; 2e7] V/s\n", + "qdac.write(f\"sour{qdac_channel}:dc:volt:slew {1000}\")\n", + "qdac.write(f\"sour{qdac_channel}:volt:mode fix\")\n", + "\n", + "### Run the experiment\n", + "start_time = time()\n", + "job = qm.execute(prog)\n", + "# Live plotting\n", + "fig = plt.figure()\n", + "interrupt_on_close(fig, job) # Interrupts the job when closing the figure\n", + "data_handle = job.result_handles.get(\"data\")\n", + "data_tot = []\n", + "for i, vg in enumerate(voltage_values1):\n", + " # Update the QDAC level\n", + " qdac.write(f\"sour{qdac_channel}:volt {vg}\")\n", + " # Resume the QUA program (escape the 'pause' statement)\n", + " job.resume()\n", + " # Wait until the program reaches the 'pause' statement again, indicating that the QUA program is done\n", + " wait_until_job_is_paused(job)\n", + " # Wait until the data of this run is processed by the stream processing\n", + " data_handle.wait_for_values(i + 1)\n", + " # Fetch the data from the last OPX run corresponding to the current vg\n", + " data = data_handle.fetch(i)[\"value\"] * 2**12 / readout_len\n", + " # Update the list of global results\n", + " data_tot.append(data)\n", + " # Plot results\n", + " plt.cla()\n", + " plt.plot(voltage_values1[:i], data_tot[:i])\n", + " plt.xlabel(\"QDAC level [V]\")\n", + " plt.ylabel(\"Data [V]\")\n", + " plt.pause(0.1)\n", + "\n", + "print(f\"Elapsed time: {time() - start_time:.2f} s\")\n", + "# Interrupt the FPGA program\n", + "job.halt()\n", + "# Set the QDAC output voltage back to 0\n", + "qdac.write(f\"sour{qdac_channel}:volt {0}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Experiment 2: 1D voltage sweep performed by the QDAC triggered by the OPX\n", + "\n", + "In this example, the QDAC is parametrized to output the values defined in a pre-loaded voltage list. Stepping to the next value is done on the event of a digital marker sent by the OPX to one of the QDAC external trigger input. \n", + "Here the OPX is simply triggering the QDAC and measuring at each level, but the QUA program can easily be modified to complexify the sequence.\n", + "\n", + "This method is very similar to the previous one but significantly reduces the runtime since it avoids the communication time. The speed can also be further improved by removing the live plotting and increasing the QDAC bandwidth." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n_avg = 100 # Number of averaging loops\n", + "# Voltage values in Volt\n", + "voltage_values1 = list(np.linspace(-1.5, 1.5, 101))\n", + "\n", + "### OPX section\n", + "with program() as qdac_1d_sweep:\n", + " i = declare(int)\n", + " n = declare(int)\n", + " data = declare(fixed)\n", + " data_st = declare_stream()\n", + "\n", + " with for_(n, 0, n < n_avg, n + 1):\n", + " with for_(i, 0, i < len(voltage_values1), i + 1):\n", + " # Wait 1ms before measuring (depends of the QDAC filter option)\n", + " wait(1_000_000 // 4, \"qdac_trigger1\", \"readout_element\")\n", + " # Trigger the QDAC channel to output the next voltage level from the list\n", + " play(\"trig\", \"qdac_trigger1\")\n", + " # Measure with the OPX\n", + " measure(\"readout\", \"readout_element\", None, integration.full(\"const\", data, \"out1\"))\n", + " # Send the result to the stream processing\n", + " save(data, data_st)\n", + "\n", + " with stream_processing():\n", + " # Average all the data and save the values into \"data\".\n", + " data_st.buffer(len(voltage_values1)).average().save(\"data\")\n", + "\n", + "# Open a quantum machine\n", + "qm = qmm.open_qm(config)\n", + "\n", + "### QDAC2 section\n", + "qdac.write(\"*rst\") # Reset the qdac parameters to start from a blank instrument\n", + "# Set up the qdac and load the voltage list\n", + "load_voltage_list(\n", + " qdac,\n", + " channel=1,\n", + " dwell=2e-6,\n", + " slew_rate=1e3,\n", + " trigger_port=\"ext1\",\n", + " output_range=\"low\",\n", + " output_filter=\"med\",\n", + " voltage_list=voltage_values1,\n", + ")\n", + "\n", + "start_time = time()\n", + "\n", + "# Execute the sequence\n", + "job = qm.execute(qdac_1d_sweep)\n", + "results = fetching_tool(job, [\"data\"], mode=\"live\")\n", + "\n", + "# Live plotting\n", + "fig = plt.figure()\n", + "interrupt_on_close(fig, job) # Interrupts the job when closing the figure\n", + "while results.is_processing():\n", + " # Fetch the data\n", + " data = results.fetch_all()[0] * readout_len / 2**12\n", + " # Plot the data\n", + " plt.cla()\n", + " plt.plot(voltage_values1, data * 1000)\n", + " plt.xlabel(\"QDAC voltage [V]\")\n", + " plt.ylabel(\"data [mV]\")\n", + " plt.pause(0.1)\n", + "\n", + "print(f\"Elapsed time: {time() - start_time:.2f} s\")\n", + "qdac.write(f\"sour1:volt {0}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Experiment 3: 2D voltage sweep where both axes are scanned using the triggering method\n", + "\n", + "In this example, two channels of the QDAC are parametrized to step though two pre-loaded voltage lists on the event of two digital markers provided by the OPX (connected to ext1 and ext2). This method allows the fast acquisition of a 2D voltage map and the data can be fetched from the OPX in real time to enable live plotting (this assumes that the averaging is performed on the most outer loop).\n", + "\n", + "The speed can also be further improved by removing the live plotting and increasing the QDAC bandwidth." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n_avg = 100 # Number of averaging loops\n", + "\n", + "# Voltage values in Volt\n", + "voltage_values1 = np.linspace(-0.4, 0.4, 51)\n", + "voltage_values2 = list(np.linspace(-2.5, 2.5, 21))\n", + "\n", + "### OPX section\n", + "with program() as qdac_2d_sweep:\n", + " i = declare(int)\n", + " j = declare(int)\n", + " n = declare(int)\n", + " data = declare(fixed)\n", + " data_st = declare_stream()\n", + "\n", + " with for_(n, 0, n < n_avg, n + 1):\n", + " with for_(i, 0, i < len(voltage_values2), i + 1):\n", + " # Trigger the QDAC channel to output the next voltage level from the list\n", + " play(\"trig\", \"qdac_trigger2\")\n", + " with for_(j, 0, j < len(voltage_values1), j + 1):\n", + " # Trigger the QDAC channel to output the next voltage level from the list\n", + " play(\"trig\", \"qdac_trigger1\")\n", + " # Wait 1ms before measuring (depends of the QDAC filter option)\n", + " wait(1_000_000 // 4, \"readout_element\")\n", + " # Measure with the OPX\n", + " measure(\"readout\", \"readout_element\", None, integration.full(\"const\", data, \"out1\"))\n", + " # Send the result to the stream processing\n", + " save(data, data_st)\n", + "\n", + " with stream_processing():\n", + " # Average all the data and save the values into \"data\".\n", + " data_st.buffer(len(voltage_values1)).buffer(len(voltage_values2)).average().save(\"data\")\n", + "\n", + "# Open a quantum machine\n", + "qm = qmm.open_qm(config)\n", + "\n", + "### QDAC2 section\n", + "qdac.write(\"*rst\") # Reset the qdac parameters to start from a blank instrument\n", + "# Set up the qdac and load the voltage list\n", + "load_voltage_list(\n", + " qdac,\n", + " channel=1,\n", + " dwell=2e-6,\n", + " slew_rate=1e3,\n", + " trigger_port=\"ext1\",\n", + " output_range=\"low\",\n", + " output_filter=\"med\",\n", + " voltage_list=voltage_values1,\n", + ")\n", + "load_voltage_list(\n", + " qdac,\n", + " channel=2,\n", + " dwell=2e-6,\n", + " slew_rate=1e3,\n", + " trigger_port=\"ext2\",\n", + " output_range=\"high\",\n", + " output_filter=\"med\",\n", + " voltage_list=voltage_values2,\n", + ")\n", + "\n", + "start_time = time()\n", + "\n", + "# Execute the sequence\n", + "job = qm.execute(qdac_2d_sweep)\n", + "results = fetching_tool(job, [\"data\"], mode=\"live\")\n", + "\n", + "# Live plotting\n", + "fig = plt.figure()\n", + "interrupt_on_close(fig, job) # Interrupts the job when closing the figure\n", + "while results.is_processing():\n", + " # Fetch the data\n", + " data = results.fetch_all()[0] * readout_len / 2**12\n", + " # Plot the data\n", + " plt.cla()\n", + " plt.pcolor(voltage_values1, voltage_values2, data * 1000)\n", + " plt.xlabel(\"QDAC channel 1 [V]\")\n", + " plt.ylabel(\"QDAC channel 2 [V]\")\n", + " plt.title(\"data [mV]\")\n", + " plt.pause(0.1)\n", + "\n", + "print(f\"Elapsed time: {time() - start_time:.2f} s\")\n", + "qdac.write(f\"sour1:volt {0}\")\n", + "qdac.write(f\"sour2:volt {0}\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "QM", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.0" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Tutorials/intro-to-qdac2/.ipynb_checkpoints/Integration_with_the_OPX_and_qcodes-checkpoint.ipynb b/Tutorials/intro-to-qdac2/.ipynb_checkpoints/Integration_with_the_OPX_and_qcodes-checkpoint.ipynb new file mode 100644 index 000000000..580bedc96 --- /dev/null +++ b/Tutorials/intro-to-qdac2/.ipynb_checkpoints/Integration_with_the_OPX_and_qcodes-checkpoint.ipynb @@ -0,0 +1,527 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Tutorial showing the integration between the QDAC2 and the OPX to perform voltage sweeps using the QCoDeS framework\n", + "\n", + "In this tutorial you will learn how to combine the QDAC2 with the OPX to perform n-dimensional voltage scans efficiently.\n", + "\n", + "The trick here is to use a digital marker from the OPX to trigger the QDAC and make it step the output voltage of a given channel across a pre-loaded voltage list. Please note that the size of this list is limited to 65536 items per channel. To this purpose, an element called \"qdac_trigger1\" was created in the config and a digital pulse was defined. You can read more about digital waveform manipulations here: https://docs.quantum-machines.co/0.1/qm-qua-sdk/docs/Introduction/qua_overview/?h=digi#digital-waveform-manipulations\n", + "\n", + "In this case, the latency induced by the communication between the control PC and the instruments and by transferring the data is suppressed since everything is controlled by the OPX pulse processor. The experimental runtime is then given by the QDAC bandwidth and the integration time. \n", + "\n", + "In the following examples, the OPX is programmed using its QCoDeS driver. You can learn more about its usage in the github (https://github.com/qua-platform/py-qua-tools/tree/main/qualang_tools/external_frameworks/qcodes) and usage examples (https://github.com/qua-platform/py-qua-tools/tree/main/examples/Qcodes_drivers/basic-driver).\n", + "The following can also be adapted to work without controlling the OPX through qcodes so that they match your current framework." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import time\n", + "import qcodes as qc\n", + "from qm.qua import *\n", + "from qcodes import initialise_or_create_database_at, load_or_create_experiment\n", + "from qcodes.utils.dataset.doNd import do1d, do0d\n", + "from qualang_tools.external_frameworks.qcodes.opx_driver import OPX\n", + "from qcodes_contrib_drivers.drivers.QDevil import QDAC2\n", + "from importlib import reload\n", + "from configuration import *" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## QCoDeS set-up" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "db_name = \"QM_demo.db\" # Database name\n", + "sample_name = \"demo\" # Sample name\n", + "exp_name = \"OPX_QDAC2_integration\" # Experiment name\n", + "\n", + "# Initialize qcodes database\n", + "db_file_path = os.path.join(os.getcwd(), db_name)\n", + "qc.config.core.db_location = db_file_path\n", + "initialise_or_create_database_at(db_file_path)\n", + "# Initialize the qcodes station to which instruments will be added\n", + "station = qc.Station()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Create the OPX instrument class\n", + "opx_instrument = OPX(config, name=\"OPX_demo\", host=\"127.0.0.1\", cluster_name=\"my_cluster\")\n", + "# Add the OPX instrument to the qcodes station\n", + "station.add_component(opx_instrument)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "qdac2_ip = \"127.0.0.1\"\n", + "# Create the QDAC2 instrument class\n", + "qdac2 = QDAC2.QDac2(\"QDAC\", visalib=\"@py\", address=f\"TCPIP::{qdac2_ip}::5025::SOCKET\")\n", + "# Add the QDAC2 instrument to the qcodes station\n", + "station.add_component(qdac2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Used to reload the configuration in order to force Jupyter to upload the existing variables\n", + "import configuration\n", + "\n", + "reload(configuration)\n", + "from configuration import *\n", + "\n", + "opx_instrument.config = config\n", + "opx_instrument.qmm = opx_instrument.qmm.open_qm(config)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Example experiments:\n", + "1. 1D voltage sweep performed by the QDAC using do1d\n", + "2. 1D voltage sweep performed by the QDAC triggered by the OPX using do0d\n", + "3. 2D voltage sweep where the fast axis is scanned using the triggering method while the slow axis is swept in do1d\n", + "4. 2D voltage sweep where both axes are scanned using the triggering method" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Experiment 1: 1D voltage sweep performed by the QDAC using do1d\n", + "\n", + "In this example, the QDAC is parametrized to output a constant voltage which is swept using the do1d qcodes method. Here the OPX is simply measuring at each level, but the QUA program can easily be modified to complexify the sequence.\n", + "\n", + "This method is similar to how any external DC voltage source would be integrated with the OPX." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n_avg = 100 # Number of averaging loops\n", + "\n", + "\n", + "### OPX section\n", + "def qdac_1d_sweep(simulate=False):\n", + " with program() as prog:\n", + " i = declare(int)\n", + " n = declare(int)\n", + " data = declare(fixed)\n", + " data_st = declare_stream()\n", + "\n", + " with infinite_loop_():\n", + " if not simulate:\n", + " pause()\n", + " with for_(n, 0, n < n_avg, n + 1):\n", + " # Wait 1ms before measuring (depends of the QDAC filter option)\n", + " wait(1_000_000 // 4, \"readout_element\")\n", + " # Measure for 10µs with the OPX - Can be replaced by dual_demod, demod or else\n", + " measure(\"readout\", \"readout_element\", None, integration.full(\"const\", data, \"out1\"))\n", + " # Send the result to the stream processing\n", + " save(data, data_st)\n", + "\n", + " with stream_processing():\n", + " # Average all the data and save the values into \"data\".\n", + " data_st.buffer(n_avg).map(FUNCTIONS.average()).save_all(\"data\")\n", + " return prog\n", + "\n", + "\n", + "# Pass the readout length (in ns) to the class to convert the demodulated/integrated data into Volts\n", + "opx_instrument.readout_pulse_length(readout_len)\n", + "# Add the custom sequence to the OPX\n", + "opx_instrument.qua_program = qdac_1d_sweep(simulate=False)\n", + "\n", + "### QDAC2 section\n", + "qdac2.reset() # Reset the qdac parameters to start from a blank instrument\n", + "ch1 = qdac2.channel(2) # Define the QDAC2 channel\n", + "# Set the current range (\"high\" or \"low\") and filter (\"dc\": 10Hz , \"med\": 10khz, \"high\": 300kHz)\n", + "ch1.output_mode(range=\"low\", filter=\"med\")\n", + "# Set the slew rate (in V/s) to avoid transients when abrubtly stepping the voltage - Must be within [0.01; 2e7] V/s.\n", + "ch1.dc_slew_rate_V_per_s(1000)\n", + "Vg1 = ch1.dc_constant_V # Define the voltage parameter for this channel\n", + "\n", + "### Run the experiment\n", + "experiment1 = load_or_create_experiment(\"qdac_1d_sweep_do1d\", sample_name)\n", + "\n", + "start_time = time.time()\n", + "do1d(\n", + " Vg1,\n", + " -1.5,\n", + " 1.5,\n", + " 11,\n", + " 0.001,\n", + " opx_instrument.resume,\n", + " opx_instrument.get_measurement_parameter(),\n", + " enter_actions=[opx_instrument.run_exp],\n", + " exit_actions=[opx_instrument.halt],\n", + " show_progress=True,\n", + " # do_plot=True,\n", + " exp=experiment1,\n", + ")\n", + "print(f\"Elapsed time: {time.time() - start_time:.2f} s\")\n", + "# Bring back the voltage to 0\n", + "Vg1(0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Experiment 2: 1D voltage sweep performed by the QDAC which is triggered by the OPX which is also measuring using do0d\n", + "\n", + "In this example, the QDAC is parametrized to output the values defined in a pre-loaded voltage list. Stepping to the next value is done on the event of a digital marker sent by the OPX to one of the QDAC external trigger input. \n", + "Here the OPX is simply triggering the QDAC and measuring at each level, but the QUA program can easily be modified to complexify the sequence.\n", + "\n", + "This method is very similar to the previous one but significantly reduces the runtime since it avoids the communication time and data fetching at each voltage step. The experimental runtime is then given by the QDAC bandwidth and the integration time. The speed can also be further improved by increasing the QDAC bandwidth." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n_avg = 100 # Number of averaging loops\n", + "# Voltage values in Volt\n", + "voltage_values1 = list(np.linspace(-1.5, 1.5, 101))\n", + "\n", + "\n", + "### OPX section\n", + "def qdac_1d_sweep_trig(simulate=False):\n", + " with program() as prog:\n", + " i = declare(int)\n", + " n = declare(int)\n", + " data = declare(fixed)\n", + " data_st = declare_stream()\n", + "\n", + " with infinite_loop_():\n", + " if not simulate:\n", + " pause()\n", + " with for_(n, 0, n < n_avg, n + 1):\n", + " with for_(i, 0, i < len(voltage_values1), i + 1):\n", + " # Trigger the QDAC channel to output the next voltage level from the list\n", + " play(\"trig\", \"qdac_trigger1\")\n", + " # Wait 1ms before measuring (depends of the QDAC filter option)\n", + " wait(1_000_000 // 4, \"readout_element\")\n", + " # Measure with the OPX\n", + " measure(\"readout\", \"readout_element\", None, integration.full(\"const\", data, \"out1\"))\n", + " # Send the result to the stream processing\n", + " save(data, data_st)\n", + "\n", + " with stream_processing():\n", + " # Average all the data and save the values into \"data\".\n", + " data_st.buffer(len(voltage_values1)).buffer(n_avg).map(FUNCTIONS.average()).save_all(\"data\")\n", + " return prog\n", + "\n", + "\n", + "# Pass the readout length (in ns) to the class to convert the demodulated/integrated data into Volts\n", + "opx_instrument.readout_pulse_length(readout_len)\n", + "# Set the setpoint for the loop performed on the OPX. Axis1 is the most inner non-averaging loop\n", + "opx_instrument.set_sweep_parameters(\"axis1\", voltage_values1, \"V\", \"Vg1\")\n", + "# Add the custom sequence to the OPX\n", + "opx_instrument.qua_program = qdac_1d_sweep_trig(simulate=False)\n", + "\n", + "### QDAC2 section\n", + "qdac2.reset()\n", + "ch1 = qdac2.channel(1) # Define the QDAC2 channel\n", + "# Set the current range (\"high\" or \"low\") and filter (\"dc\": 10Hz , \"med\": 10khz, \"high\": 300kHz)\n", + "ch1.output_mode(range=\"low\", filter=\"med\")\n", + "# Set the slew rate (in V/s) to avoid transients when abrubtly stepping the voltage - Must be within [0.01; 2e7] V/s.\n", + "ch1.dc_slew_rate_V_per_s(1000)\n", + "# Define the voltage list, stepping mode and dwell time\n", + "Vg1_list = ch1.dc_list(voltages=voltage_values1, stepped=True, dwell_s=5e-6)\n", + "# Set the trigger mode to external with input port \"ext1\"\n", + "Vg1_list.start_on_external(trigger=1)\n", + "\n", + "### Run the experiment\n", + "experiment2 = load_or_create_experiment(\"qdac_1d_sweep_trig_do0d\", sample_name)\n", + "\n", + "start_time = time.time()\n", + "do0d(\n", + " opx_instrument.run_exp,\n", + " opx_instrument.resume,\n", + " opx_instrument.get_measurement_parameter(),\n", + " opx_instrument.halt,\n", + " # do_plot=True,\n", + " exp=experiment2,\n", + ")\n", + "print(f\"Elapsed time: {time.time() - start_time:.2f} s\")\n", + "\n", + "# Bring the voltage back to 0\n", + "ch1.dc_constant_V(0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Experiment 3: 2D voltage sweep where the fast axis is scanned using the triggering method while the slow axis is swept in do1d\n", + "\n", + "In this example, one channel of the QDAC is parametrized to step though a pre-loaded voltage list on the event of a digital marker provided by the OPX (fast axis). A second channel is set to output a constant voltage which will be scanned using the do1d qcodes method (slow axis).\n", + "\n", + "The idea is for instance to acquire a charge stability map using a raster scan (for finding quantum dots).\n", + "\n", + "This method is an extention of the previous ones showing how to sweep two voltage axes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n_avg = 100 # Number of averaging loops\n", + "\n", + "# Voltage values in Volt\n", + "voltage_values1 = np.linspace(-0.4, 0.4, 51)\n", + "voltage_values2 = list(np.linspace(-1.5, 1.5, 51))\n", + "\n", + "### QDAC2 section\n", + "qdac2.reset() # Reset the qdac parameters\n", + "# Channel 1\n", + "ch1 = qdac2.channel(1) # Define the QDAC2 channel\n", + "ch1.output_mode(range=\"low\", filter=\"med\")\n", + "# Set the slew rate (in V/s) to avoid transients when abrubtly stepping the voltage - Must be within [0.01; 2e7] V/s.\n", + "ch1.dc_slew_rate_V_per_s(1000)\n", + "# Define the voltage list, stepping mode and dwell time\n", + "Vg1_list = ch1.dc_list(voltages=voltage_values1, stepped=True, dwell_s=5e-6)\n", + "# Set the trigger mode to external with input port \"ext1\"\n", + "Vg1_list.start_on_external(trigger=1)\n", + "# Channel 2\n", + "ch2 = qdac2.channel(2)\n", + "ch2.output_mode(range=\"low\", filter=\"med\")\n", + "ch2.dc_slew_rate_V_per_s(1000)\n", + "Vg2 = ch2.dc_constant_V # Define the voltage parameter for this channel\n", + "\n", + "\n", + "### OPX section\n", + "def qdac_opx_combined(simulate=False):\n", + " with program() as prog:\n", + " i = declare(int)\n", + " n = declare(int)\n", + " data = declare(fixed)\n", + " data_st = declare_stream()\n", + "\n", + " with infinite_loop_():\n", + " if not simulate:\n", + " pause()\n", + "\n", + " with for_(i, 0, i < len(voltage_values1), i + 1):\n", + " # Trigger the QDAC channel to output the next voltage level from the list\n", + " play(\"trig\", \"qdac_trigger1\")\n", + " # Wait 1ms before measuring (depends of the QDAC filter option)\n", + " wait(1_000_000 // 4, \"readout_element\")\n", + " # In this example the averaging is done on the most inner loop (single point averaging)\n", + " with for_(n, 0, n < n_avg, n + 1):\n", + " # Measure with the OPX\n", + " measure(\"readout\", \"readout_element\", None, integration.full(\"const\", data, \"out1\"))\n", + " # Send the result to the stream processing\n", + " save(data, data_st)\n", + " # Bring the voltage back to zero\n", + " ramp_to_zero(\"gate\")\n", + "\n", + " with stream_processing():\n", + " # Average all the data and save the values into \"data\".\n", + " data_st.buffer(n_avg).map(FUNCTIONS.average()).buffer(len(voltage_values1)).save_all(\"data\")\n", + " return prog\n", + "\n", + "\n", + "# Pass the readout length (in ns) to the class to convert the demodulated/integrated data into Volts\n", + "opx_instrument.readout_pulse_length(readout_len)\n", + "# Add the custom sequence to the OPX\n", + "opx_instrument.qua_program = qdac_opx_combined(simulate=False)\n", + "# Axis1 is the most inner non-averaging loop\n", + "opx_instrument.set_sweep_parameters(\"axis1\", voltage_values1, \"V\", \"Vg1\")\n", + "\n", + "### Run the experiment\n", + "experiment3 = load_or_create_experiment(\"Combined_2D_sweep_do1d\", sample_name)\n", + "\n", + "start_time = time.time()\n", + "do1d(\n", + " Vg2,\n", + " voltage_values2[0],\n", + " voltage_values2[-1],\n", + " len(voltage_values2),\n", + " 0.001,\n", + " opx_instrument.resume,\n", + " opx_instrument.get_measurement_parameter(),\n", + " enter_actions=[opx_instrument.run_exp],\n", + " exit_actions=[opx_instrument.halt],\n", + " show_progress=True,\n", + " # do_plot=True,\n", + " exp=experiment3,\n", + ")\n", + "print(f\"Elapsed time: {time.time() - start_time:.2f} s\")\n", + "# Bring the voltage back to 0\n", + "ch1.dc_constant_V(0)\n", + "Vg2(0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Experiment 4: 2D voltage sweep where both axes are scanned using the triggering method\n", + "\n", + "In this example, two channels of the QDAC are parametrized to step though two pre-loaded voltage lists on the event of two digital markers provided by the OPX (connected to ext1 and ext2). This method allows the fast acquisition of a 2D voltage map and the data can be fetched from the OPX in real time to enable live plotting (this assumes that the averaging is performed on the most outer loop and the program is only to be executed once).\n", + "\n", + "The speed can also be further improved by removing the live plotting and increasing the QDAC bandwidth." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib qt\n", + "n_avg = 10 # Number of averaging loops\n", + "\n", + "# Voltage values in Volt\n", + "voltage_values1 = list(np.linspace(-0.4, 0.4, 51))\n", + "voltage_values2 = list(np.linspace(-1.5, 1.5, 21))\n", + "\n", + "### QDAC2 section\n", + "qdac2.reset() # Reset the qdac parameters\n", + "# Channel 1\n", + "ch1 = qdac2.channel(1) # Define the QDAC2 channel\n", + "ch1.output_mode(range=\"low\", filter=\"med\")\n", + "# Set the slew rate (in V/s) to avoid transients when abrubtly stepping the voltage - Must be within [0.01; 2e7] V/s.\n", + "ch1.dc_slew_rate_V_per_s(1000)\n", + "# Define the voltage list, stepping mode and dwell time\n", + "Vg1_list = ch1.dc_list(voltages=voltage_values1, stepped=True, dwell_s=5e-6)\n", + "# Set the trigger mode to external with input port \"ext1\"\n", + "Vg1_list.start_on_external(trigger=1)\n", + "# Channel 2\n", + "ch2 = qdac2.channel(2) # Define the QDAC2 channel\n", + "ch2.output_mode(range=\"low\", filter=\"med\")\n", + "ch2.dc_slew_rate_V_per_s(1000)\n", + "# Define the voltage list, stepping mode and dwell time\n", + "Vg2_list = ch2.dc_list(voltages=voltage_values2, stepped=True, dwell_s=5e-6)\n", + "# Set the trigger mode to external with input port \"ext2\"\n", + "Vg2_list.start_on_external(trigger=2)\n", + "\n", + "\n", + "### OPX section\n", + "def qdac_opx_combined(simulate=False):\n", + " with program() as prog:\n", + " i = declare(int)\n", + " j = declare(int)\n", + " n = declare(int)\n", + " data = declare(fixed)\n", + " data_st = declare_stream()\n", + "\n", + " # No need for the infinite loop here since the program will only be executed once.\n", + " # When using live plotting, the infinite_loop will prevent the program from ending because it blocks the python terminal\n", + " # with infinite_loop_():\n", + " if not simulate:\n", + " pause()\n", + " # In this example the averaging is done on the most outer loop to enable live plotting\n", + " with for_(n, 0, n < n_avg, n + 1):\n", + " with for_(i, 0, i < len(voltage_values2), i + 1):\n", + " # Trigger the QDAC channel to output the next voltage level from the list\n", + " play(\"trig\", \"qdac_trigger2\")\n", + " with for_(j, 0, j < len(voltage_values1), j + 1):\n", + " # Trigger the QDAC channel to output the next voltage level from the list\n", + " play(\"trig\", \"qdac_trigger1\")\n", + " # Wait 1ms before measuring (depends of the QDAC filter option)\n", + " wait(1_000_000 // 4, \"readout_element\")\n", + " # Measure with the OPX\n", + " measure(\"readout\", \"readout_element\", None, integration.full(\"const\", data, \"out1\"))\n", + " # Send the result to the stream processing\n", + " save(data, data_st)\n", + "\n", + " with stream_processing():\n", + " # When averaging on the most outer loop, if the program is meant to run once (do0d),\n", + " # then the .average() method can be used for live plotting purposes\n", + " data_st.buffer(len(voltage_values1)).buffer(len(voltage_values2)).average().save_all(\"data\")\n", + " return prog\n", + "\n", + "\n", + "# Pass the readout length (in ns) to the class to convert the demodulated/integrated data into Volts\n", + "opx_instrument.readout_pulse_length(readout_len)\n", + "# Add the custom sequence to the OPX\n", + "opx_instrument.qua_program = qdac_opx_combined(simulate=False)\n", + "# Axis1 is the most inner non-averaging loop\n", + "opx_instrument.set_sweep_parameters(\"axis1\", voltage_values1, \"V\", \"Vg1\")\n", + "opx_instrument.set_sweep_parameters(\"axis2\", voltage_values2, \"V\", \"Vg2\")\n", + "\n", + "### Run the experiment\n", + "experiment4 = load_or_create_experiment(\"Combined_2D_sweep_do0d\", sample_name)\n", + "\n", + "start_time = time.time()\n", + "# Compile the QUA program and execute it\n", + "opx_instrument.run_exp()\n", + "# Exit the pause() statement and start the sequence\n", + "opx_instrument.resume()\n", + "# Uncomment to fetch the data in real-time and plot them\n", + "# opx_instrument.live_plotting([\"data\"])\n", + "# Update the data counter to save the last dataset to the qcodes database\n", + "opx_instrument.counter = n_avg\n", + "# Store the results in the qcodes database\n", + "do0d(\n", + " opx_instrument.get_measurement_parameter(),\n", + " do_plot=True,\n", + " exp=experiment4,\n", + ")\n", + "\n", + "print(f\"Elapsed time: {time.time() - start_time:.2f} s\")\n", + "# Bring the voltage back to 0\n", + "ch1.dc_constant_V(0)\n", + "ch2.dc_constant_V(0)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "QM", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.0" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Tutorials/intro-to-qdac2/.ipynb_checkpoints/readme-checkpoint.md b/Tutorials/intro-to-qdac2/.ipynb_checkpoints/readme-checkpoint.md new file mode 100644 index 000000000..a49ec7c4c --- /dev/null +++ b/Tutorials/intro-to-qdac2/.ipynb_checkpoints/readme-checkpoint.md @@ -0,0 +1,24 @@ +--- +title: Intro to QDAC2 and OPX integration +sidebar_label: Intro to QDAC2 and OPX integration +slug: ./ +id: index +--- + +This tutorial shows how to integrate the QDAC2 and the OPX together in order to perform voltage sweeps efficiently. + +As you will see, the trick here is to use the OPX digital markers to trigger the QDAC2 and step the voltage outputted by its channels +into preloaded voltage lists. + +The QDAC2 can be controlled either using pyvisa commands or using its [QCoDeS driver](https://github.com/QCoDeS/Qcodes_contrib_drivers/blob/main/qcodes_contrib_drivers/drivers/QDevil/QDAC2.py). +This is the reason why the tutorial is split into two Jupyter notebooks: +* One showcasing the different pyvisa commands to configure the QDAC2 in its trigger mode: [pyvisa notebook](./Integration_with_the_OPX_and_pyvisa.ipynb). +* And a second one showing how to make the QDAC2 and the OPX work together in a QCoDeS environment: [qcodes notebook](./Integration_with_the_OPX_and_qcodes.ipynb). You can also read more about the QDAC2 driver [here](https://qcodes.github.io/Qcodes_contrib_drivers/examples/QDevil/QDAC2/index.html). + +The tutorials are meant to be self-explanatory, but if anything is unclear or not working well, **please do not hesitate +to reach out to your favorite CS physicist.** + +2D voltage sweep performed by the QDAC2 with both channels triggered by the OPX: +![2D triggered voltage sweep](QDAC2_triggered_sweep.PNG) +Zoom into the fast axis scan (1ms per level) +![2D triggered voltage sweep zoom](QDAC2_triggered_sweep_zoom.PNG) diff --git a/Tutorials/intro-to-qdac2/Integration_with_the_OPX_and_pyvisa.ipynb b/Tutorials/intro-to-qdac2/Integration_with_the_OPX_and_pyvisa.ipynb index 5efa61432..151aaf672 100644 --- a/Tutorials/intro-to-qdac2/Integration_with_the_OPX_and_pyvisa.ipynb +++ b/Tutorials/intro-to-qdac2/Integration_with_the_OPX_and_pyvisa.ipynb @@ -135,7 +135,24 @@ " sleep(1)\n", " print(\n", " f\"Set-up QDAC2 channel {channel} to step voltages from a list of {len(voltage_list)} items on trigger events from the {trigger_port} port with a {qdac.query(f'sour{channel}:dc:list:dwell?')} s dwell time.\"\n", - " )" + " )\n", + "\n", + "# set the QDAC voltage to a specific value \n", + "def set_QDAC_voltage(\n", + " qdac,\n", + " channel: int,\n", + " voltage: float,\n", + "):\n", + " \"\"\"\n", + " Configure a QDAC2 channel to play a specicif voltage, using pyvisa commands.\n", + " \n", + " :param qdac: the QDAC2 object.\n", + " :param channel: the QDAC2 channel that will output the voltage from the voltage list.\n", + " :param voltage: the desired voltage to output from the QDAC channel.\n", + " :return:\n", + " \"\"\"\n", + " qdac.write(f\"sour{channel}:dc:mode FIX\")\n", + " qdac.write(f\"sour{channel}:volt {voltage}\")\n" ] }, { @@ -178,7 +195,11 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "jupyter": { + "source_hidden": true + } + }, "source": [ "### Experiment 1: 1D voltage sweep performed by the QDAC only\n", "In this example, the QDAC is parametrized to output a constant voltage which is swept outside of QUA using a python loop. Here the OPX is simply measuring at each level, but the QUA program can easily be modified to complexify the sequence.\n", @@ -450,11 +471,34 @@ "qdac.write(f\"sour1:volt {0}\")\n", "qdac.write(f\"sour2:volt {0}\")" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Example:\n", + "1. Setting the QDAC on a specific voltage - in the sexample you can set the QDAC on a specific, fixed, value. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from qdac2_driver import QDACII, set_QDAC_voltage\n", + "\n", + "set_QDAC_voltage(\n", + " qdac,\n", + " channel=1,\n", + " voltage=0.0,\n", + ")\n" + ] } ], "metadata": { "kernelspec": { - "display_name": "QM", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -468,10 +512,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.0" - }, - "orig_nbformat": 4 + "version": "3.9.18" + } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/Tutorials/intro-to-qdac2/Integration_with_the_OPX_and_qcodes.ipynb b/Tutorials/intro-to-qdac2/Integration_with_the_OPX_and_qcodes.ipynb index 580bedc96..4a584a92d 100644 --- a/Tutorials/intro-to-qdac2/Integration_with_the_OPX_and_qcodes.ipynb +++ b/Tutorials/intro-to-qdac2/Integration_with_the_OPX_and_qcodes.ipynb @@ -504,7 +504,7 @@ ], "metadata": { "kernelspec": { - "display_name": "QM", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -518,10 +518,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.0" - }, - "orig_nbformat": 4 + "version": "3.9.18" + } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 }