diff --git a/docs/User/Readme.md b/docs/User/Readme.md index 98ee0ef..af8a0ca 100644 --- a/docs/User/Readme.md +++ b/docs/User/Readme.md @@ -1,6 +1,7 @@ # User Documentation Quick-start guide +- [Example usage](example_usage.ipynb) Main structure: - [Laboratory Class](Laboratory.md) diff --git a/docs/User/example_usage.ipynb b/docs/User/example_usage.ipynb new file mode 100644 index 0000000..2639109 --- /dev/null +++ b/docs/User/example_usage.ipynb @@ -0,0 +1,1784 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Initialisation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import sqdtoolz as stz\n", + "import numpy as np" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The first step is always to define a Laboratory object (see [here](https://github.com/sqdlab/SQDToolz/blob/main/docs/User/Laboratory.md) for more). The instrument config file `instr_config_file` is in YAML format, containing specifications for the instruments you will connect to. Examples of YAML entries are available in the documentation [here](https://github.com/sqdlab/SQDToolz/blob/main/docs/User/InstrumentSpecific/Readme.md)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lab = stz.Laboratory(instr_config_file = \"your_config.yaml\", save_dir = \"Z:\\\\Data\\\\your_data_folder\\\\\", using_VS_Code=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If the notebook crashes or is otherwise restarted, you can reload the Laboratory configuration from a previous experiment. This is useful for resetting instruments and local variables, to continue where you left off. If no directory is provided, it will reload the last completed experiment by default." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lab.cold_reload_last_configuration(r\"Z:\\Data\\your_data_folder\\date\\experiment\\\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The lab browser is a convenient tool to visualise the current state of all instantiated instruments. To load, use `lab.open_browser()`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lab.open_browser()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Resonator spectroscopy by VNA" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A vector network analyser (VNA) is useful for performing fast spectroscopy measurements with minimal set-up. It is able to output a wide-range sweep of frequencies through one port, and measure transmission through another port. To prepare resonator spectroscopy by VNA, connect the readout I/O lines to two different VNA ports. If the VNA has not been set up on a PC previously, some instructions exist on [Nuclino](https://app.nuclino.com/SQD-Lab/Admin/Portable-Keysight-VNA-64ab1677-395b-40c1-9154-45e2921c8be9)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The VNA can then be loaded according to its YAML entry in the loaded instrument config file. Here I assume that the reference key is `vna_ref`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Vector network analyser - used for faster spectroscopy measurements\n", + "lab.load_instrument('vna_ref')\n", + "stz.ACQvna('VNA', lab, 'vna_ref')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The first line in the above verifies the device connectivity and loads the device. The second line instantiates a HAL object for the device, which can now be referred to as `lab.HAL('acq')`. Now that we have access to the VNA, let's configure the parameters to set up our experiment. An example is below." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Which ports are we using for input and output?\n", + "input_port = 1\n", + "output_port = 2\n", + "\n", + "# Spectroscopy requires defining a range of frequencies over which to sweep.\n", + "freq_start = 6e9 # Hz\n", + "freq_end = 8e9 # Hz\n", + "sweep_type = 'Linear'\n", + "sweep_points = 1001\n", + "\n", + "# VNA operation needs a signal power and measurement bandwidth. These can be tuned for optimal measurement.\n", + "vna_power = -30 # dBm\n", + "vna_bandwidth = 500 # Hz" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Set parameters accordingly on the hardware.\n", + "lab.HAL('VNA').FrequencyStart = freq_start\n", + "lab.HAL('VNA').FrequencyEnd = freq_end\n", + "lab.HAL('VNA').SweepMode = sweep_type\n", + "lab.HAL('VNA').SweepPoints = sweep_points\n", + "lab.HAL('VNA').Power = vna_power\n", + "lab.HAL('VNA').Bandwidth = vna_bandwidth" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that the instrument parameters are set, we want to save this particular configuration. This is useful for two reasons; (a) we can generate an ExperimentConfiguration object which we can use to perform experiments with these settings at any time, and (b) we can save the current ExperimentConfiguration for future reference, along with the generated experimental data. This is documented [here](https://github.com/sqdlab/SQDToolz/blob/main/docs/User/Exp_Config_Basic.md)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Below, an ExperimentConfiguration object with the current settings is instantiated. The duration is zero for now, as it is handled by the VNA - later we will see why this argument is useful. We are saving the settings of the VNA, and acquiring data with the VNA." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "experiment_name = 'vna_sweep'\n", + "\n", + "vna_spec_config = stz.ExperimentConfiguration(name = experiment_name, lab = lab, \n", + " duration = 0, list_HALs = ['VNA'], hal_ACQ = 'VNA')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can now perform a resonator spectroscopy experiment. Documentation on the experiment run functionality is available [here](https://github.com/sqdlab/SQDToolz/blob/main/docs/User/Exp_Overview.md)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "vna_sweep = stz.Experiment(name = 'vna_sweep', expt_config = lab.CONFIG('vna_sweep'))\n", + "data = lab.run_single(expt_obj = vna_sweep)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The data can now be visualised in `SQDViz` ([repo](https://github.com/sqdlab/SQDViz)), or the data itself can be accessed via `data.get_numpy_array()` and processed manually (as documented [here](https://github.com/sqdlab/SQDToolz/blob/main/docs/User/Data_IO.md)). If the resonator frequency is within the specified sweep, there should be a peak/trough at the appropriate frequency." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Power sweep by VNA" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It's often a good idea to perform a 2D sweep over signal frequency and signal power. In this way, we can see how the resonator peak/trough evolves with power. At sufficiently high powers, the regime of dispersive measurement breaks down and the spectroscopy resolution will worsen, before the peak/trough shifts in frequency and becomes highly resolved (a so-called 'bright state'). We want to stay at a power lower than the onset of these behaviours, but as high as possible to maximise signal-to-noise ratio." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "power_sweep = stz.Experiment('power_sweep', lab.CONFIG('vna_sweep'))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We need to define a variable which controls the power of the VNA signal. This is done with the `stz.VariableProperty` functionality. `Variable`s are well documented [here](https://github.com/sqdlab/SQDToolz/blob/main/docs/User/Var_Defns.md)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "stz.VariableProperty(name = 'vnaPower', lab = lab, sqdtoolz_obj = lab.HAL('VNA'), prop_name = 'Power')\n", + "\n", + "start_power = -45\n", + "end_power = -15\n", + "step_size = 1\n", + "\n", + "data = lab.run_single(power_sweep, [(lab.VAR('vnaPower'), (np.arange(start_power, end_power, step_size)))])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Again, the data can now be visualised in `SQDViz`, or the data itself can be accessed via `data.get_numpy_array()` and processed manually." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Flux sweep by VNA" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If the qubits are flux-tunable, we can see modulation of the resonators by tuning magnetic flux. This is an example procedure for using a superconducting magnetic coil, connected externally to Channel 1 of a programmable voltage source denoted in the config YAML by `v_source_ref`. First, the loading procedure for the voltage source." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lab.load_instrument('v_source_ref')\n", + "stz.GENvoltSource('v_coil', lab, ['v_source_ref', 'CH1'])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The new ExperimentConfiguration, Experiment, VariableProperty, and execution will follow, as per the power sweep example." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "stz.ExperimentConfiguration('vna_flux_sweep', lab, 0, ['VNA', 'v_coil'], 'VNA')\n", + "\n", + "vna_flux_sweep = stz.Experiment('vna_flux_sweep', lab.CONFIG('vna_flux_sweep'))\n", + "\n", + "stz.VariableProperty('vFlux', lab, lab.HAL('v_coil'), 'Voltage')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "start_flux = -1\n", + "end_flux = 1\n", + "flux_step_size = 0.1\n", + "\n", + "data = lab.run_single(vna_flux_sweep, [(lab.VAR('vFlux'), np.arange(start_flux, end_flux, flux_step_size))])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Spectroscopy with microwave sources" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Resonator spectroscopy" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "After VNA spectroscopy provides a quick and dirty way to determine things like resonator frequencies and flux dependence, the use of microwave sources and dedicated acquisition devices permits more complex experiments. However, the procedure should start again with spectroscopy-type measurements. This time, the measured features (resonator frequency, optimal measurement power etc.) will be recorded for later use." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First we need to load an acquisition device. This could be an FPGA, the Tabor acquisition device, or the digitiser on the GPU PC. This is necessary to record our output measurement signal." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lab.load_instrument('acq_device_ref')\n", + "stz.ACQ('acq', lab, 'acq_device_ref')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we can load some microwave sources. First will be a source used for resonator spectroscopy. A second microwave signal is used for [downconversion purposes](https://app.nuclino.com/SQD-Lab/Equipment-and-lab-related/Measurement-with-microwave-signals-b6673668-f201-4eaa-bc5d-35c363528028). Finally, we will load a third microwave source for driving qubit/s for qubit spectroscopy. Documentation on microwave sources is available [here](https://github.com/sqdlab/SQDToolz/blob/main/docs/User/GENmwSource.md). " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lab.load_instrument('res_source_ref')\n", + "stz.GENmwSource('MW_res', lab, 'res_source_ref', 'RFOUT')\n", + "\n", + "lab.load_instrument('dnc_source_ref')\n", + "stz.GENmwSource('MW_dnc', lab, 'dnc_source_ref', 'RFOUT')\n", + "\n", + "lab.load_instrument('qubit_source_ref')\n", + "stz.GENmwSource('MW_qubit', lab, 'qubit_source_ref', 'RFOUT')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, we need an external synchronisation tool for timing of experiment repetitions. A DDG (digital delay generator) can be used to send trigger signals to other instruments, signalling the beginning of an experiment." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# DDG (digital delay generator) - used to send trigger signals to other instruments, to begin an experiment\n", + "lab.load_instrument('ddg_ref')\n", + "stz.DDG('DDG', lab, 'ddg_ref')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We select an experimental repetition time which will contain the operations we wish to perform. Then, we prepare the DDG accordingly." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "repetition_time = 25e6\n", + "lab.HAL(\"DDG\").RepetitionTime = repetition_time\n", + "\n", + "# some settings for the DDG trigger\n", + "lab.HAL('DDG').get_trigger_output('AB').TrigPulseLength = 50e-9\n", + "lab.HAL('DDG').get_trigger_output('AB').TrigPolarity = 1\n", + "lab.HAL('DDG').get_trigger_output('AB').TrigPulseDelay = 0.0e-9\n", + "lab.HAL('DDG').get_trigger_output('AB').TrigEnable = True " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A typical set up is to feed the DDG signal (from Channel 'AB' above) into the trigger port of an AWG (arbitrary wave generator). The timing of subsequent signals in the experiment will then be controlled by the AWG. So, let's load the AWG. We need two channel outputs (for I and Q components) and a sample rate which is permitted by the relevant device." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "awg_sample_rate = 100e6\n", + "\n", + "lab.load_instrument('awg_ref')\n", + "stz.WaveformAWG(\"WFM\", lab, awg_channel_tuples=[('awg_ref', 'CH1'), ('awg_ref', 'CH2')], \n", + " sample_rate=awg_sample_rate)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The total time of a waveform should be less than the total experimental repetition time." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lab.HAL('WFM').set_valid_total_time(lab.HAL('DDG').RepetitionTime-1e-6)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let's set up the waveform on the AWG. Resonator spectroscopy is simply a long continuous resonator signal, so we need no AWG output -- but we are using it for timing purposes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# add a short zero-amplitude constant pulse\n", + "lab.HAL(\"WFM\").add_waveform_segment(stz.WFS_Constant(name = \"pulse\", transform_func = None, time_len = 100e-9, value = 0.0))\n", + "\n", + "# add another which persists for the rest of the waveform time (time_len = -1)\n", + "lab.HAL(\"WFM\").add_waveform_segment(stz.WFS_Constant(\"zeros\", None, -1, 0.0))\n", + "\n", + "# configure the awg to output a marker signal when the 'pulse' component is on.\n", + "lab.HAL(\"WFM\").get_output_channel(0).marker(0).set_markers_to_segments([\"pulse\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, the set up is as follows. The AWG begins an experiment when it receives a trigger from the DDG. This will commence the AWG waveform, which outputs a zero-amplitude pulse from both CH1 and CH2, and a marker pulse of length 100e-9 from the marker output. We will use this marker pulse to tell the acquisition to begin acquisition - so, we need to run a cable from the marker port of the AWG to the trigger port of the ACQ device." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can produce timing diagrams based on the set-ups we design here (this will be shown later). To ensure these are accurate, we can mimic the physical trigger-marker set-ups we are configuring with the following functions:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lab.HAL(\"WFM\").set_trigger_source_all(lab.HAL(\"DDG\").get_trigger_output('AB'))\n", + "lab.HAL(\"acq\").set_trigger_source(lab.HAL(\"WFM\").get_output_channel(0).marker(1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that the acquisition device is being triggered, we need to configure how many samples it will measure, and how many repetitions the experiment will continue for. In the below example, we will configure $2^8$ repetitions of the experiment, with $2^{16}$ samples per experiment. The total measurement time can be calculated by NumSamples / SampleRate; this needs to be less than the experiment repetition time." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "total_time = lab.HAL('WFM').Duration\n", + "num_meas_samples = 2**11\n", + "meas_time = num_meas_samples / lab.HAL('acq').SampleRate\n", + "if meas_time < total_time:\n", + " lab.HAL('acq').set_acq_params(reps = 2**8, segs = 1, samples = num_meas_samples)\n", + "else:\n", + " print(f'{num_meas_samples} gives a total measurement time of {meas_time}, which is too long for a waveform of length {total_time}.')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The physical measurement set-up (with downconversion) means that each experimental repetition is measuring a 25 MHz signal. We process this signal digitally with the following steps: a digital downconversion to produce a DC signal, an FIR filter to reject undesirably frequency components, decimation to reduce the size of the dataset, and integration over the total number of samples measured. This results in a singular measurement (I,Q) which incorporates the amplitude and phase of the incoming signal. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We instruct our processor to perform this with a DataProcessor object. There are three variants; ProcessorFPGA, ProcessorGPU, and ProcessorCPU. Here I use ProcessorFPGA, for which some docs exist [here](https://github.com/sqdlab/SQDToolz/blob/main/docs/User/Proc_FPGA_Overview.md)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "myProc = stz.ProcessorFPGA(proc_name = 'fpga_dsp', lab = lab)\n", + "myProc.reset_pipeline()\n", + "\n", + "myProc.add_stage(stz.FPGA_DDC([[25e6]]))\n", + "myProc.add_stage(stz.FPGA_FIR([[{'Type' : 'low', 'fc' : 10e6, 'Taps' : 40, 'Win' : 'hamming'}]]))\n", + "myProc.add_stage(stz.FPGA_Decimation('sample', 4)) \n", + "myProc.add_stage(stz.FPGA_Integrate('segment'))\n", + "myProc.add_stage(stz.FPGA_Integrate('sample'))\n", + "myProc.add_stage(stz.FPGA_Mean('repetition'))\n", + "\n", + "lab.HAL('acq').set_data_processor(myProc)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, we can simply ensure that our resonator and downconversion microwave sources are turned on, and the qubit source is turned off." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "resonator_drive_power = -40\n", + "\n", + "lab.HAL(\"MW_res\").Mode = \"Continuous\" \n", + "lab.HAL(\"MW_res\").Power = resonator_drive_power\n", + "lab.HAL(\"MW_res\").Output = True\n", + "\n", + "lab.HAL(\"MW_dnc\").Mode = \"Continuous\" \n", + "lab.HAL(\"MW_dnc\").Power = 16 \n", + "lab.HAL(\"MW_dnc\").Output = True \n", + "\n", + "lab.HAL('MW_qubit').Output = False" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With this, our experiment is prepared to run. We are going to save the current configuration of all our instruments into an ExperimentConfiguration; this is the object we pass to an experiment to say \"set up the instruments as follows\". It will also get saved alongside our data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "stz.ExperimentConfiguration(name = 'contMeas', lab = lab, duration = repetition_time, \n", + " list_HALs = ['DDG', 'WFM', 'MW_res', 'MW_dnc'], hal_ACQ = 'acq')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also view our experimental configuration as a timing diagram, for a sanity check." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lab.CONFIG('contMeas').plot()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Running an experiment is then quite simple. We follow the same process as we did for the VNA." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "res_spec = Experiment('res_spec', lab.CONFIG('contMeas'))\n", + "\n", + "stz.VariableProperty(name = 'res_freq', lab = lab, sqdtoolz_obj = lab.HAL('MW_res'), prop_name = 'Frequency')\n", + "\n", + "freq_start = 6e9\n", + "freq_end = 8e9\n", + "step_size = 1e6\n", + "\n", + "data = lab.run_single(res_spec, [(lab.VAR('res_freq'), (np.arange(freq_start, freq_end, step_size)))])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As for the VNA experiment, the data can now be visualised in `SQDViz`, or the data itself can be accessed via `data.get_numpy_array()` and processed manually. If the resonator frequency is within the specified sweep, there should be a peak/trough at the appropriate frequency." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "However, there is also a pre-defined automated experiment which can do this work for us. The experiment `ExpPeakScouterIQ` will search for a Lorentzian shape in the measured data, fit this shape, and then yield the estimated resonator frequency. In the following cell, we define an `ExperimentSpecification` object for our resonator, which is used to store key information about the resonator, and then run an `ExpPeakScouterIQ` experiment." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from sqdtoolz.Experiments.Experimental.ExpPeakScouterIQ import *\n", + "\n", + "stz.ExperimentSpecification(name = 'my_res', lab = lab, init_specs = 'Cavity')\n", + "\n", + "fitted_res_spec = ExpPeakScouterIQ('fitted_res_spec', lab.CONFIG('contMeas'), \n", + " param_centre=lab.SPEC('my_res')['Frequency'], param_width=lab.SPEC('my_res')['LineWidth'], \n", + " is_trough=True, fit_fano_res=True)\n", + "\n", + "fitted_res_spec_data = lab.run_single(fitted_res_spec, \n", + " [(lab.VAR('res_freq'), (np.arange(freq_start, freq_end, step_size)))])\n", + "\n", + "print(f'Frequency:')\n", + "print(lab.SPEC('my_res')['Frequency'].Value/1e9)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This fits the measured resonator spec, and saves the fitted resonant frequency to `lab.SPEC('my_res')['Frequency']`. A convenient feature of `lab.SPEC` objects is that they can be passed to experiments as configuration settings. For example, we can say to an experiment \"use the frequency saved in `lab.SPEC('my_res')` to set the readout frequency\". To assign a SPEC feature to a physical setting, we use the following line." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lab.SPEC('my_res').set_destination('Frequency', lab.VAR('res_freq'))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The general procedure for resonator spectroscopy with microwave sources is complete. Below are generic procedures for extending this to power and flux sweeps." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "res_power_sweep = stz.Experiment('res_power_sweep', lab.CONFIG('res_spec'))\n", + "\n", + "start_power = -45\n", + "end_power = -15\n", + "step_power = 1\n", + "\n", + "stz.VariableProperty('res_power', lab, lab.HAL('MW_res'), 'Power')\n", + "\n", + "power_sweep_data = lab.run_single(exp_power_sweep, \n", + " [(lab.VAR('res_power'), np.arange(start_power, end_power, step_power)), \n", + " (lab.VAR('res_freq'), np.arange(freq_start, freq_end, step_size))]) " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Select and save some sensible resonator power according to the above power sweep\n", + "lab.SPEC('my_res')['Power'] = -35\n", + "lab.SPEC('my_res').set_destination('Power', lab.VAR('res_power'))\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lab.CONFIG('contMeas').init_instruments() # used to initialise instruments according to said config\n", + "stz.ExperimentConfiguration(name = 'res_flux_sweep', lab = lab, duration = repetition_time, \n", + " list_HALs = ['DDG', 'WFM', 'MW_res', 'MW_dnc', 'v_coil'], hal_ACQ = 'acq')\n", + "\n", + "res_flux_sweep = stz.Experiment('res_flux_sweep', lab.CONFIG('res_flux_sweep'))\n", + "\n", + "start_flux = -1\n", + "end_flux = 1\n", + "step_flux = 0.1\n", + "\n", + "flux_sweep_data = lab.run_single(res_flux_sweep, \n", + " [(lab.VAR('vFlux'), np.arange(start_flux, end_flux, step_flux)), \n", + " (lab.VAR('res_freq'), np.arange(freq_start, freq_end, step_size))]) \n", + "\n", + "# reset flux to zero\n", + "lab.HAL('v_coil').Voltage = 0" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Qubit spectroscopy" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In qubit spectroscopy, we want to fix the readout signal at the resonator frequency, turn on the qubit probe, and sweep the qubit probe frequency. So, let's initialise the instruments as per resonator spectroscopy, change the qubit source settings, and pass `lab.SPEC('my_res')` to the experiment." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lab.CONFIG('contMeas').init_instruments()\n", + "\n", + "lab.HAL('MW_qubit').Output = True\n", + "lab.HAL('MW_qubit').Mode = \"Continuous\" \n", + "lab.HAL('MW_qubit').Power = -30\n", + "\n", + "stz.ExperimentConfiguration('qubit_spec', lab, repetition_time, ['DDG','WFM','MW_res','MW_dnc','MW_qubit'], \n", + " 'acq', list_spec_names=['my_res'])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can define an ExperimentSpecification for our qubit, and use ExpPeakScouterIQ to fit the qubit frequency. Typically, this experiment will start with a broad search for the qubit frequency, and be refined to get an increasingly better fit." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "stz.ExperimentSpecification(name = 'my_qubit', lab = lab, init_specs = 'Qubit')\n", + "\n", + "stz.VariableProperty('qb_freq', lab, lab.HAL('MW_qubit'), 'Frequency')\n", + "stz.VariableProperty('qb_power', lab, lab.HAL('MW_qubit'), 'Power')\n", + "\n", + "lab.SPEC('my_qubit').set_destination('Frequency GE', lab.VAR('qb_freq'))\n", + "lab.SPEC('my_qubit').set_destination('Power', lab.VAR('qb_power'))\n", + "\n", + "exp_qubit_sweep = ExpPeakScouterIQ('qubit_spec', lab.CONFIG('qubit_spec'), \n", + " param_centre=lab.SPEC('my_qubit')['Frequency GE'], is_trough=False)\n", + "\n", + "freq_start = 4e9\n", + "freq_end = 6e9\n", + "step_size = 3e6\n", + "\n", + "data = lab.run_single(exp_qubit_sweep, [(lab.VAR('qb_freq'), np.arange(freq_start, freq_end, step_size))])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It can be useful to perform a qubit power sweep, to determine an optimal power for balanced between reduced linewidth and signal-to-noise ratio." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "qubit_power_sweep = stz.Experiment('qubit_power_sweep', lab.CONFIG('qubit_spec'))\n", + "\n", + "start_power = -45\n", + "end_power = -15\n", + "step_power = 1\n", + "\n", + "qb_power_sweep_data = lab.run_single(qubit_power_sweep, \n", + " [(lab.VAR('qb_power'), np.arange(start_power, end_power, step_power)), \n", + " (lab.VAR('res_freq'), np.arange(freq_start, freq_end, step_size))])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Select and save some sensible resonator power according to the above power sweep\n", + "lab.SPEC('my_qubit')['Power'] = -25" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A flux sweep is slightly more involved. You must consider that the resonator peak will also move as flux is varied. Thus, we need to redo a resonator spectroscopy experiment before each qubit spectroscopy (i.e. at each new flux). Fortunately, `sqdtoolz` includes so-called 'grouped experiments', as documented [here](https://github.com/sqdlab/SQDToolz/blob/main/docs/User/Exp_CascadeGroup.md)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lab.CONFIG('contMeas').init_instruments()\n", + "\n", + "stz.ExperimentConfiguration('res_calibration', lab, repetition_time, \n", + " ['DDG', 'WFM', 'MW_res', 'MW_dnc'], 'acq', list_spec_names=['my_res'])\n", + "\n", + "res_calibration = ExpPeakScouterIQ('res_calibration', lab.CONFIG('res_calibration'), param_centre = lab.SPEC('my_res')['Frequency'], \n", + " param_width=lab.SPEC('my_res')['LineWidth'], is_trough=True, fit_fano_res=True)\n", + "\n", + "lab.CONFIG('qubit_spec').init_instruments()\n", + "\n", + "stz.ExperimentConfiguration('qubit_flux_sweep', lab, repetition_time, ['DDG','WFM','MW_res','MW_dnc','MW_qubit', 'v_coil'], \n", + " 'acq', list_spec_names=['my_res', 'my_qubit'])\n", + "\n", + "qubit_flux_sweep= Experiment('qubit_flux_sweep', lab.CONFIG('qubit_flux_sweep'))\n", + "\n", + "start_flux = -1\n", + "end_flux = 1\n", + "flux_step_size = 0.1\n", + "\n", + "lab.group_open(\"qubit_flux_exp\")\n", + "for fluxV in lab.VAR('vFlux').arange(start_flux, end_flux, step_size):\n", + " cavSpecData = lab.run_single(res_calibration, [(lab.VAR('res_freq'), np.arange(6e9, 8e9, 1e6))]) \n", + " cavSpecData.release()\n", + " leData = lab.run_single(qubit_flux_sweep, [(lab.VAR('qubit_freq'), np.arange(4e9,5e9,2e6))]) \n", + " leData.release()\n", + "lab.group_close()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This experiment will be useful for determining/confirming the sweet-spot flux point and corresponding qubit frequency." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Pulsed spectroscopy" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "So far, we have done qubit spectroscopy by driving the qubit and the resonator at the same time, and measuring the results. It can happen that the resonator drive (populating the resonator) can induce a Stark shift, and move the qubit frequency. We can instead perform a pulsed spectroscopy, using the `PulseModulated` settings on the microwave sources, to successively excite the qubit, THEN measure the resonator. The qubit should be driven from a long time (longer than $T_2$) to ensure that it saturates to 50$\\%$ population, and that a shift can therefore be measured reliably." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lab.HAL('DDG').RepetitionTime = 200e-6" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Then, we can prepare the waveform on the AWG. We are using this timing to trigger the pulses produced by the two microwave sources `MW_res` and `MW_qubit`, so we need two AWG marker outputs to be connected to the two microwave source trigger inputs. We also set these markers in the following cell. They trigger the `acq`, `MW_res`, and `MW_qubit` respectively." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "stz.WaveformAWG(\"WFM_pulsed\", lab, [('awg5014C', 'CH1'), ('awg5014C', 'CH2')], 100e6)\n", + "lab.HAL(\"WFM_pulsed\").set_valid_total_time(lab.HAL('DDG').RepetitionTime-1e-6)\n", + "\n", + "drive_time = 100e-6\n", + "measurement_time = 11e-6\n", + "\n", + "lab.HAL(\"WFM_pulsed\").add_waveform_segment(stz.WFS_Constant(\"init_pad\", None, -1, 0.0))\n", + "lab.HAL(\"WFM_pulsed\").add_waveform_segment(stz.WFS_Constant(\"pulse\", None, drive_time, 0.0))\n", + "lab.HAL(\"WFM_pulsed\").add_waveform_segment(stz.WFS_Constant(\"pad\", None, 100e-9, 0.0))\n", + "lab.HAL(\"WFM_pulsed\").add_waveform_segment(stz.WFS_Constant(\"read\", None, measurement_time, 0.0))\n", + "\n", + "lab.HAL(\"WFM_pulsed\").get_output_channel(0).marker(0).set_markers_to_segments(['read'])\n", + "lab.HAL(\"WFM_pulsed\").get_output_channel(0).marker(1).set_markers_to_segments(['read'])\n", + "lab.HAL(\"WFM_pulsed\").get_output_channel(1).marker(0).set_markers_to_segments(['pulse'])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For the timing diagram, we should also outline these trigger relationships." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lab.HAL(\"WFM_pulsed\").set_trigger_source_all(lab.HAL(\"DDG\").get_trigger_output('AB'))\n", + "lab.HAL(\"acq\").set_trigger_source(lab.HAL(\"WFM_pulsed\").get_output_channel(0).marker(0))\n", + "lab.HAL(\"MW_res\").set_trigger_source(lab.HAL(\"WFM_pulsed\").get_output_channel(0).marker(1))\n", + "lab.HAL(\"MW_qubit\").set_trigger_source(lab.HAL(\"WFM_pulsed\").get_output_channel(1).marker(0))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We should set the acquisition device to take samples according to our measurement time. I'm going to find the highest number of samples that (a) is a power of 2, and (b) is less than the measurement time. Repetitions can be reduced or increased to balance noise with runtime." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "_samples = measurement_time / lab.HAL('acq').SampleRate\n", + "num_meas_samples = 2**int(np.log2(_samples))\n", + "\n", + "lab.HAL('acq').set_acq_params(reps = 2**12, segs = 1, samples = num_meas_samples)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With timing sorted out, we can configure the microwave sources to behave correspondingly." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lab.HAL(\"MW_Qubit\").Mode = 'PulseModulated'\n", + "lab.HAL(\"MW_Res\").Mode = 'PulseModulated'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally save and view our pulsed spectroscopy configuration." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "stz.ExperimentConfiguration('pulsed_spec', lab, repetition_time, ['DDG', \"WFM_pulsed\", 'MW_Res', 'MW_DNC', 'MW_Qubit'], 'ETHFPGA') \n", + "lab.CONFIG('pulsed_spec').plot()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Pulsed cavity spectroscopy can be performed to predict the expected signal contrast for the qubit spectroscopy." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lab.CONFIG('pulsed_spec').init_instruments()\n", + "\n", + "lab.HAL('MW_qubit').Output = False\n", + "\n", + "stz.ExperimentConfiguration('pulsed_cav', lab, repetition_time, ['DDG', \"WFM_pulsed\", 'MW_res', 'MW_qubit','MW_dnc'], 'acq',['my_res']) \n", + "\n", + "new_exp = ExpPeakScouterIQ(\"pulsed_cav_spec\", lab.CONFIG('pulsed_cav'), param_centre=lab.SPEC('my_res')['Frequency'], \n", + " param_width=lab.SPEC('my_res')['LineWidth'], is_trough=True, fit_fano_res=True)\n", + "\n", + "freq_start = 6.5e9\n", + "freq_end = 7e9\n", + "step_size = 0.2e6\n", + "\n", + "cavSpecData = lab.run_single(new_exp, [(lab.VAR('Res_Freq'), np.arange(freq_start, freq_end, step_size))]) " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And finally, pulsed qubit spectroscopy." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lab.CONFIG('pulsed_spec').init_instruments()\n", + "\n", + "stz.ExperimentConfiguration('pulsed_qubit', lab, repetition_time, ['DDG', \"WFM_pulsed\", 'MW_res', 'MW_dnc', 'MW_qubit'], 'ETHFPGA', [target_qubit, target_resonator]) \n", + "lab.CONFIG('pulsed_qubit').plot()\n", + "\n", + "pulsed_qubit_sweep = ExpPeakScouterIQ('pulsed_qubit_spec', lab.CONFIG('pulsed_qubit'), \n", + " param_centre=lab.SPEC('my_qubit')['Frequency GE'], is_trough=False)\n", + "\n", + "freq_start = 4.5e9\n", + "freq_end = 5e9\n", + "step_size = 0.5e6\n", + "\n", + "data = lab.run_single(pulsed_qubit_sweep, [(lab.VAR('Qubit_Freq'), np.arange(freq_start, freq_end, step_size))])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Mixer calibration" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Having identified the resonant frequencies of the resonator and qubit, we now want to transition to pulsed, time-domain control. To do this, we combine arbitrary pulse envelopes (generated by the AWG) with our continuous microwave signals. This is done with an IQ mixer; two channels of the AWG go into the I and Q ports respectively, the microwave source goes into the LO port, and the output is obtained at the RF port. Documentation for this process exists [here](https://github.com/sqdlab/SQDToolz/blob/main/docs/User/Mixer_Calibration.md). It is important to note that for mixer calibration, the acquisition device should be set up to acquire the mixed signal directly; we are no longer travelling through the quantum chip or fridge at all." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As seen in the docs linked above, mixer calibration relies on tuning four parameters; two external voltages which offset the I and Q AWG signals (`DCoff_I` and `DCoff_Q`), an amplitude factor which scales the two AWG signals (`AmpFac`) and a phase offset which shifts the default $\\pi/2$ phase difference between the signals (`PhsOff`). Below we define each of these variables, and then store them in an ExperimentSpecification object which we can pass to future experiments in order to operate the mixer in the correct regime." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "stz.GENvoltSource('DCoff_I', lab, ['v_source_ref', 'CH2']) #v_source_ref was loaded earlier\n", + "stz.GENvoltSource('DCoff_Q', lab, ['v_source_ref', 'CH3'])\n", + "\n", + "stz.WFMT_ModulationIQ('QubitFreqGE', lab, 100e6)\n", + "\n", + "stz.ExperimentSpecification('MixerCalib', lab)\n", + "lab.SPEC('MixerCalib').add('IdcOff', 0, lab.HAL('DCoff_I'), 'Voltage')\n", + "lab.SPEC('MixerCalib').add('QdcOff', 0, lab.HAL('DCoff_Q'), 'Voltage')\n", + "lab.SPEC('MixerCalib').add('AmpFac', 0, lab.WFMT('QubitFreqGE'), 'IQAmplitudeFactor')\n", + "lab.SPEC('MixerCalib').add('PhsOff', 0, lab.WFMT('QubitFreqGE'), 'IQPhaseOffset')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Notice the introduction of the WaveformTransformation `QubitFreqGE`. This is an object which stores and later passes information about the appropriate scaled and phase-shifted envelopes to the AWG. This object is thoroughly documented [here](https://github.com/sqdlab/SQDToolz/blob/main/docs/User/AWG_WFMTs.md)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "An uncalibrated mixer yields a signal with three frequency components (a central LO component and two equidistant sidebands), of which we only wish to keep one sideband. First, we want to see these three peaks, in the uncalibrated regime.\n", + "\n", + "We will initialise our instruments according to `contMeas`, but will have to make substantial changes for this measurement. This includes turning off the measurement signal (we are acquiring the qubit signal for mixer calibration), and turning up the power of the qubit source." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lab.CONFIG('contMeas').init_instruments()\n", + "\n", + "lab.HAL(\"MW_qubit\").Power = 16\n", + "lab.HAL('MW_qubit').Frequency = lab.SPEC('my_qubit')['Frequency'].Value\n", + "lab.HAL(\"MW_res\").Output = False" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will define a new AWG waveform to guide the timing of our experiment. Note that we are outputting a long-time constant pulse, modulated by the `lab.WFMT('QubitFreqGE')` object; this, mixed with the qubit microwave signal, is what we are measuring." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lab.HAL('DDG').RepetitionTime = 1e-4\n", + "\n", + "stz.WaveformAWG(\"WfmConMixer\", lab, [('awg_ref', 'CH1'), ('awg_ref', 'CH2')], awg_sample_rate)\n", + "lab.HAL('WfmConMixer').set_valid_total_time(lab.HAL('DDG').RepetitionTime-1e-6)\n", + "\n", + "total_time = lab.HAL('WfmConMixer').Duration\n", + "num_meas_samples = 2**13\n", + "meas_time = num_meas_samples / lab.HAL('acq').SampleRate\n", + "if meas_time < total_time:\n", + " lab.HAL('acq').set_acq_params(reps = 2**1, segs = 1, samples = num_meas_samples)\n", + "else:\n", + " print(f'{num_meas_samples} gives a total measurement time of {meas_time}, which is too long for a waveform of length {total_time}.')\n", + "\n", + "lab.HAL(\"WfmConMixer\").add_waveform_segment(stz.WFS_Constant(\"pad\", None, -1, 0.0))\n", + "lab.HAL(\"WfmConMixer\").add_waveform_segment(stz.WFS_Constant(\"pulse\", lab.WFMT('QubitFreqGE').apply(), meas_time, 0.1))\n", + "lab.HAL(\"WfmConMixer\").get_output_channel(0).marker(0).set_markers_to_segments(['pulse'])\n", + "lab.HAL(\"WfmConMixer\").set_trigger_source_all(lab.HAL(\"DDG\").get_trigger_output('AB'))\n", + "lab.HAL(\"acq\").set_trigger_source(lab.HAL(\"WfmConMixer\").get_output_channel(0).marker(0))\n", + "lab.HAL('acq').set_data_processor(myProc)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this set-up, the acquisition device is being triggered by the AWG to begin acquiring when the `pulse` component of the waveform begins. The variables `meas_time` and `lab.HAL('acq').NumSamples` have been chosen such that the pulse envelope generation and signal acquisition occur for the same period of time. We also re-use the previous data processor, `myProc`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "stz.ExperimentConfiguration('mixer_calib', lab, lab.HAL('DDG').RepetitionTime, ['DDG', \"WfmConMixer\", 'MW_dnc', 'MW_qubit', 'MW_res'], 'acq')\n", + "lab.CONFIG('mixer_calib').plot()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With this configuration, we should be able to see the qubit frequency signal and the two sidebands, separated by 100 MHz. To check this, you can sweep the downconversion frequency in a range inclusive of 200 MHz in either direction from the qubit frequency signal." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "stz.VariableProperty('dnc_freq', lab, lab.HAL(\"MW_dnc\"), 'Frequency')\n", + "\n", + "stz.VariableInternal('freq_mixer_calib', lab)\n", + "stz.VariableSpaced('qubit_freq_mixer_calib', lab, 'freq_mixer_calib', 'dnc_freq', -25e6)\n", + "\n", + "new_exp = stz.Experiment('mixer_calib_sweep', lab.CONFIG('mixer_calib'))\n", + "\n", + "centre_freq = lab.HAL('MW_qubit').Frequency\n", + "\n", + "mixer_calib_data = lab.run_single(new_exp, [(lab.VAR('qubit_freq_mixer_calib'), \n", + " np.arange(-200e6, 200e6, 20e6)+centre_freq)])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This can be visualised in `SQDViz`. After calibrating the mixer, you should come back and perform this measurement again to ensure that only one sideband remains." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### LO mixer calibration" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This procedure suppresses the central LO component of the three peaks. This process is thoroughly documented [here](https://github.com/sqdlab/SQDToolz/blob/main/docs/User/Mixer_Calibration.md), so comments will be minimal." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from sqdtoolz.Experiments.Experimental.ExpMixerCalibrationLO import *\n", + "\n", + "new_exp = ExpMixerCalibrationLO(name = 'mixer_calib_LO', expt_config = lab.CONFIG('mixer_calib'), \n", + " var_down_conv_freq = lab.VAR('qubit_freq_mixer_calib'), \n", + " freqs_sideband_LCR = [centre_freq-100e6, centre_freq, centre_freq+100e6],\n", + " lab.VAR('DCoff_I'), (-2,2), lab.VAR('DCoff_Q'), (-2,2))\n", + "LO_data = lab.run_single(new_exp)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The ranges for `DCoff_I` and `DCoff_Q` should be adjusted above until a clear bowl-shape is seen. Then, the minimum can be located and we can proceed to autonomous optimisation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "new_exp = ExpMixerCalibrationLO('mixer_calib_LO', lab.CONFIG('mixer_calib'), lab.VAR('qubit_freq_mixer_calib'), \n", + " [centre_freq-30e6, centre_freq, centre_freq+30e6],\n", + " lab.VAR('DCoff_I'), (-0.5,0.5), lab.VAR('DCoff_Q'), (-0.5,0.5), \n", + " optimise=True, sample_points=4, iterations=10, win_shrink_factor=0.33, acq_ch = 'ch1')\n", + "LO_data_opt = lab.run_single(new_exp)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The optimisation experiment will leave the variables `DCoff_I` and `DCoff_Q` in the optimal values to suppress the central leakage. Thus, we should save these parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lab.SPEC(f'MixerCalib')['IdcOff'].Value = lab.VAR('DCoff_I').Value\n", + "lab.SPEC(f'MixerCalib')['QdcOff'].Value = lab.VAR('DCoff_Q').Value" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "By performing the `mixer_calib_sweep` experiment again, it should be clear the central tone is suppressed." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### SB mixer calibration" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Sideband mixer calibration, which suppresses one of the sidebands, is extremely similar in execution to LO mixer calibration. Now, we are calibrating the amplitude factor and phase offset mentioned earlier." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from sqdtoolz.Experiments.Experimental.ExpMixerCalibrationSB import*\n", + "\n", + "upper_SB_freq = centre_freq+100e6\n", + "\n", + "new_exp = ExpMixerCalibrationSB(f'mixer_calib_SB', lab.CONFIG('mixer_calib'), lab.VAR('qubit_freq_mixer_calib'), upper_SB_freq,\n", + " lab.VAR('IQ_amp_fac'), (0.8,1.05), lab.VAR('IQ_phs_diff'), (2,4))\n", + "SB_data = lab.run_single(new_exp)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Again, find search parameters which include a bowl-shaped minimum, and proceed to optimisation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "new_exp = ExpMixerCalibrationSB('mixer_calib_SB', lab.CONFIG('mixer_calib'), lab.VAR('qubit_freq_mixer_calib'), upper_SB_freq,\n", + " lab.VAR('IQ_amp_fac'), (0.95,1.05), lab.VAR('IQ_phs_diff'), (3, 3.5), optimise=True, sample_points=4, iterations=10, win_shrink_factor=0.33, acq_ch = 'ch1')\n", + "SB_data_opt = lab.run_single(new_exp)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Save the final parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lab.SPEC(f'MixerCalib')['AmpFac'].Value = lab.WFMT('QubitFreqGE').IQAmplitudeFactor\n", + "lab.SPEC(f'MixerCalib')['PhsOff'].Value = lab.WFMT('QubitFreqGE').IQPhaseOffset" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A final `mixer_calib_sweep` experiment should confirm only one frequency component (the upper sideband in this case) remains." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Time-domain measurements" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In time-domain measurements, we revert to acquiring the down-converted readout signal, and send the mixed drive signal into the appropriate qubit drive line. Now, we can generate custom drive envelopes and perform quantum gates (precise rotations) on our qubit, and then measure the results, in a time-resolved manner." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Pre-Rabi (pulsed)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Pre-Rabi is an experiment used to verify that the qubit excitation is still occurring through the mixer. This can be done either as a continuous measurement, or as a pulsed spectroscopy (here I show the latter). In this configuration, we want to (for example) send a microwave signal into the mixer at $(\\omega_{qb} + 100)$ MHz, and mix it with an envelope at $100$ MHz, retaining the lower sideband at $\\omega_{qb}$. To test this, we can sweep the envelope modulation frequency around $100$ MHz, and see where the peak in transmission is. It should be at $100$ MHz, but if our qubit frequency measurement was not precise (e.g. a wide peak), we may see this peak detuned (e.g., at $90$ MHz). In this case, we should use this new frequency for the upcoming experiments." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can begin with the configuration from pulsed spectroscopy, and create a new waveform, with a pulse configured by our `lab.WFMT('QubitFreqGE')` object." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lab.CONFIG('pulsed_spec').init_instruments()\n", + "\n", + "stz.WaveformAWG(\"WFM_prerabi\", lab, [('awg5014C', 'CH1'), ('awg5014C', 'CH2')], 100e6)\n", + "lab.HAL(\"WFM_prerabi\").set_valid_total_time(lab.HAL('DDG').RepetitionTime-1e-6)\n", + "\n", + "drive_time = 100e-6\n", + "measurement_time = 11e-6\n", + "\n", + "lab.HAL(\"WFM_prerabi\").add_waveform_segment(stz.WFS_Constant(\"init_pad\", None, -1, 0.0))\n", + "lab.HAL(\"WFM_prerabi\").add_waveform_segment(stz.WFS_Constant(\"pulse\", lab.WFMT('QubitFreqGE').apply(), drive_time, 0.1))\n", + "lab.HAL(\"WFM_prerabi\").add_waveform_segment(stz.WFS_Constant(\"pad\", None, 100e-9, 0.0))\n", + "lab.HAL(\"WFM_prerabi\").add_waveform_segment(stz.WFS_Constant(\"read\", None, measurement_time, 0.0))\n", + "\n", + "lab.HAL(\"WFM_prerabi\").get_output_channel(0).marker(0).set_markers_to_segments(['read'])\n", + "lab.HAL(\"WFM_prerabi\").get_output_channel(0).marker(1).set_markers_to_segments(['read'])\n", + "lab.HAL(\"WFM_prerabi\").get_output_channel(1).marker(0).set_markers_to_segments(['pulse'])\n", + "\n", + "lab.HAL(\"WFM_prerabi\").set_trigger_source_all(lab.HAL(\"DDG\").get_trigger_output('AB'))\n", + "lab.HAL(\"acq\").set_trigger_source(lab.HAL(\"WFM_prerabi\").get_output_channel(0).marker(0))\n", + "lab.HAL(\"MW_res\").set_trigger_source(lab.HAL(\"WFM_prerabi\").get_output_channel(0).marker(1))\n", + "lab.HAL(\"MW_qubit\").set_trigger_source(lab.HAL(\"WFM_prerabi\").get_output_channel(1).marker(0))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Again we will set the number of acquisition samples to be less than or equal to the measurement time. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "_samples = measurement_time / lab.HAL('acq').SampleRate\n", + "num_meas_samples = 2**int(np.log2(_samples))\n", + "\n", + "lab.HAL('acq').set_acq_params(reps = 2**12, segs = 1, samples = num_meas_samples)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As we are now driving through the mixer, we change our microwave source to yield a very strong, continuous signal, at 100 MHz above the qubit frequency signal." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lab.HAL('MW_qubit').Mode = 'Continuous'\n", + "lab.HAL('MW_qubit').Output = True\n", + "lab.HAL('MW_qubit').Power = 16\n", + "lab.HAL('MW_qubit').Frequency = lab.SPEC('my_qubit')['Frequency GE'].Value + 100e6\n", + "\n", + "stz.ExperimentConfiguration('prerabi_pulsed', lab, lab.HAL('DDG').RepetitionTime, ['DDG', \"WFM_prerabi\", 'MW_res', 'MW_dnc', 'MW_qubit'], 'acq', ['MixerCalib', 'my_qubit','my_res']) \n", + "lab.CONFIG('prerabi_pulsed').plot();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The experiment is then conducted by sweeping over the modulated envelope frequency." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "stz.VariableProperty('Rabi Drive Frequency', lab, lab.WFMT('QubitFreqGE'), 'IQFrequency')\n", + "\n", + "new_exp = stz.Experiment('prerabi_pulsed', lab.CONFIG('prerabi_pulsed'))\n", + "prerabi_data = lab.run_single(new_exp, [(lab.VAR('Rabi Drive Frequency'), np.arange(70e6, 130e6, 1e6))])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This can be visualised in `SQDViz`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Rabi experiment ($\\pi$, $\\pi/2$ gate tuning)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The Rabi experiment is an important qubit measurement where we vary drive amplitude and see oscillations from the ground to the excited state. From this, we can calibrate rotations of $\\pi$ and $\\pi/2$." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lab.CONFIG('prerabi_pulsed').init_instruments()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As usual, we set up a custom waveform for this experiment. It consists of a relaxation time, a qubit excitation pulse, a short padding, and then a measurement." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lab.HAL(\"DDG\").RepetitionTime = 200e-6\n", + "\n", + "stz.WaveformAWG(\"wfmRabiA\", lab, [('awg5014C', 'CH1'), ('awg5014C', 'CH2')], sample_rate=100e6)\n", + "lab.HAL(\"wfmRabiA\").set_valid_total_time(lab.HAL('DDG').RepetitionTime-1e-6)\n", + "\n", + "rabi_drive_time = 40e-9\n", + "rabi_measurement_time = 11e-6\n", + "\n", + "lab.HAL(\"wfmRabiA\").add_waveform_segment(stz.WFS_Constant(\"init_pad\", None, -1, 0.0))\n", + "lab.HAL(\"wfmRabiA\").add_waveform_segment(stz.WFS_Gaussian(\"drive\", lab.WFMT('QubitFreqGE').apply(phase=0), rabi_drive_time, 0.1))\n", + "lab.HAL(\"wfmRabiA\").add_waveform_segment(stz.WFS_Constant(\"pad\", None, 5e-9, 0.0))\n", + "lab.HAL(\"wfmRabiA\").add_waveform_segment(stz.WFS_Constant(\"read\", None, rabi_measurement_time, 0.0)) \n", + "\n", + "lab.HAL(\"wfmRabiA\").get_output_channel(0).marker(0).set_markers_to_segments(['read'])\n", + "lab.HAL(\"wfmRabiA\").get_output_channel(0).marker(1).set_markers_to_segments(['read'])\n", + "\n", + "lab.HAL(\"wfmRabiA\").set_trigger_source_all(lab.HAL(\"DDG\").get_trigger_output('AB'))\n", + "lab.HAL(\"acq\").set_trigger_source(lab.HAL(\"wfmRabiA\").get_output_channel(0).marker(0))\n", + "lab.HAL(\"MW_res\").set_trigger_source(lab.HAL(\"wfmRabiA\").get_output_channel(0).marker(1))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "stz.ExperimentConfiguration('RabiAuto', lab, lab.HAL('DDG').RepetitionTime, \n", + " ['DDG', 'wfmRabiA', 'MW_res', 'MW_dnc', 'MW_qubit'], 'acq', ['my_res','my_qubit', 'MixerCalib'])\n", + "lab.CONFIG('RabiAuto').plot()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The automated experiment can then be used as follows. Note that I am setting that IQ Frequency to 100 MHz, but this can be changed if the pre-Rabi experiment found a different optimal frequency. Note also that `lab.SPEC('my_qubit')` is passed to the experiment; values for $\\pi/2$ and $\\pi$ amplitudes are saved in `lab.SPEC('my_qubit')['GE X/2-Gate Amplitude']` and `lab.SPEC('my_qubit')['GE X-Gate Amplitude']` respectively." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from sqdtoolz.Experiments.Experimental.ExpRabi import *\n", + "\n", + "sqdt.VariableInternal('Qubit Rabi Amplitude Frequency', lab)\n", + "\n", + "lab.WFMT('QubitFreqGE').IQFrequency = 100e6\n", + "\n", + "ampl_start = 0\n", + "ampl_end = 0.3\n", + "num_ampls = 25\n", + "\n", + "new_exp = ExpRabi(f\"RabiAuto\", lab.CONFIG('RabiAuto'), lab.WFMT('QubitFreqGE'), np.linspace(ampl_start, ampl_end, num_ampls), lab.SPEC('my_qubit'),\n", + " param_rabi_frequency=lab.VAR('Qubit Rabi Amplitude Frequency'), load_time = 80e-6, drive_time= 40e-9, readout_time = 11e-6, normalise=False, normalise_reps=2)\n", + "rabi_data = lab.run_single(new_exp)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can visualise the oscillations normalised between 0 and 1, and the dispersion of our measured data in the IQ plane, by using the argument `normalise=True`. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "new_exp = ExpRabi(f\"RabiAuto\", lab.CONFIG('RabiAuto'), lab.WFMT('QubitFreqGE'), np.linspace(ampl_start, ampl_end, num_ampls), lab.SPEC('my_qubit'),\n", + " param_rabi_frequency=lab.VAR('Qubit Rabi Amplitude Frequency'), load_time = 80e-6, drive_time= 40e-9, readout_time = rabi_measurement_time, normalise=True, normalise_reps=2)\n", + "rabi_data = lab.run_single(new_exp)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Ramsey experiment ($T_2^*$ and frequency calibration)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The Ramsey experiment is able to both estimate our $T_2^*$ dephasing time, and provide a more precise calibration of our qubit frequency. It involves rotating the qubit to a superposition state, allowing it to dephase over an increasing wait time, and then rotating it with another $\\pi/2$ pulse to (ideally) the excited state. This will result in damped oscillations (the product of an exponential and cosine) where the decay coefficient of the exponential marks $T_2^*$ and the frequency corresponds to the detuning of the drive." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can start by doing a short-time Ramsey experiment, where we deliberately detune the experiment by e.g. 10 MHz. This will allow us to precisely tune our true qubit frequency." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from sqdtoolz.Experiments.Experimental.ExpRamseyGE import *\n", + "\n", + "lab.WFMT('QubitFreqGE').IQFrequency += 10e6\n", + "\n", + "sqdt.VariableInternal('Qubit Ramsey Frequency', lab)\n", + "sqdt.VariableInternal('Qubit Ramsey Decay Time', lab)\n", + "\n", + "short_ramsey_start_time = 0\n", + "short_ramsey_end_time = 0.5e-6\n", + "short_ramsey_num_pts = 30\n", + "\n", + "new_exp = ExpRamseyGE(f\"RamseyAuto_short\", lab.CONFIG('RabiAuto'), lab.WFMT('QubitFreqGE'), \n", + " np.linspace(short_ramsey_start_time, short_ramsey_end_time, short_ramsey_num_pts), \n", + " lab.SPEC(targ_qubit), param_ramsey_frequency=lab.VAR('Qubit Ramsey Frequency'), \n", + " param_ramsey_decay_time=lab.VAR('Qubit Ramsey Decay Time'),\n", + " load_time=80e-6, readout_time=rabi_measurement_time, normalise= False, normalise_reps=2)\n", + "ramsey_data = lab.run_single(new_exp)\n", + "\n", + "lab.WFMT('QubitFreqGE').IQFrequency = lab.WFMT('QubitFreqGE').IQFrequency - lab.VAR('Qubit Ramsey Frequency').Value\n", + "\n", + "print('New IQ Frequency:')\n", + "print(lab.WFMT('QubitFreqGE').IQFrequency)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Then, we can do a long-time Ramsey experiment, where the detuning is much smaller and we are characterising $T_2^*$. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lab.WFMT('QubitFreqGE').IQFrequency += 0.2e6\n", + "\n", + "long_ramsey_start_time = 0\n", + "long_ramsey_end_time = 30e-6\n", + "long_ramsey_num_pts = 30\n", + "\n", + "new_exp = ExpRamseyGE(f\"RamseyAuto_long\", lab.CONFIG('RabiAuto'), lab.WFMT('QubitFreqGE'), \n", + " np.linspace(long_ramsey_start_time, long_ramsey_end_time, long_ramsey_num_pts), \n", + " lab.SPEC(targ_qubit), param_ramsey_frequency=lab.VAR('Qubit Ramsey Frequency'), \n", + " param_ramsey_decay_time=lab.VAR('Qubit Ramsey Decay Time'),\n", + " load_time=80e-6, readout_time=rabi_measurement_time, normalise=True, normalise_reps=2)\n", + "ramsey_data = lab.run_single(new_exp)\n", + "\n", + "lab.WFMT('QubitFreqGE').IQFrequency = lab.WFMT('QubitFreqGE').IQFrequency - lab.VAR('Qubit Ramsey Frequency').Value\n", + "\n", + "print('T_2*: ')\n", + "print(lab.VAR('Qubit Ramsey Decay Time'))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### $T_1$ measurement" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can measure $T_1$ simply by exciting the qubit to $\\ket{1}$, and gradually increasing the wait time before measurement to observe decay. An automated experiment for this is provided, and usage is shown below." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from sqdtoolz.Experiments.Experimental.ExpT1GE import *\n", + "\n", + "sqdt.VariableInternal('Qubit T1 time', lab)\n", + "\n", + "t1_start_time = 0\n", + "t1_end_time = 30e-6\n", + "t1_num_pts = 30\n", + "\n", + "new_exp = ExpT1GE(f\"T1Auto\", lab.CONFIG('RabiAuto'), lab.WFMT('QubitFreqGE'), np.linspace(t1_start_time, t1_end_time, t1_num_pts), lab.SPEC(targ_qubit),\n", + " param_T1_decay_time = lab.VAR('Qubit T1 time'), load_time=80e-6, readout_time=rabi_measurement_time, normalise= True, normalise_reps=4)\n", + "leData = lab.run_single(new_exp)\n", + "\n", + "print('T1:')\n", + "print(lab.VAR('Qubit T1 time').Value)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is the standard set of measurements for characterising coherence times of a qubit. There is also a great deal of documentation (more than what is linked in here) on most of the objects used in this notebook in the [GitHub repo](https://github.com/sqdlab/SQDToolz/tree/main/docs)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Other experiments" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As you create new experiments which are useful for other people, it is encouraged to [formalise these](https://github.com/sqdlab/SQDToolz/blob/main/docs/Developer/Creating%20custom%20experiments.md) into Experiment classes, and deposit them in the appropriate [directory](https://github.com/sqdlab/SQDToolz/tree/main/sqdtoolz/Experiments/Experimental). Some additional experiments which have not been discussed here include:\n", + "- `ExpACstarkRamseyGE`: an on-resonance Ramsey experiment which drives during the built-in wait time, where the resultant frequency oscillations arise from detuning due to AC-Stark shift.\n", + "- `ExpCalibXRot`: an experiment which involves repeating $X_\\pi$ or $X_{\\pi/2}$ rotations, to characterise coherent over- or under-rotation error.\n", + "- `ExpHahnGE`: A measurement of pure $T_2$ time.\n", + "- `ExpSingleShotGE`: A measurement which repeatedly prepares $\\ket{0}$ and $\\ket{1}$ states, and measures the distribution of the resultant measurement signals in the I-Q plane." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### A note: debugging measurements which don't show what you expect" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Likely the most useful tool for debugging is the Browser tool accessible with `lab.open_browser()`, which displays the current configuration of all loaded instruments (and waveforms, and specifications) while an experiment is ongoing. With this, you should check if all the instruments are behaving as you expect for an experiment.\n", + "\n", + "There is also a second tab on the browser which allows you to compare configs from finished experiments. You may need to close and restart the browser to retrieve recent experiments. This is useful if a previously functional experiment stops showing sensible results; you can compare what has changed in between the two runs (e.g. perhaps you modified the measurement signal power and didn't realise)." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.8.12" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +}