Skip to content
This repository has been archived by the owner on Apr 24, 2024. It is now read-only.

ARTIQ Experiment

Mingyu Fan edited this page Feb 13, 2022 · 9 revisions

ARTIQ experiments perform programmed sequences of control, analysis, and data saving. ARTIQ experiments are almost the sole method to interact with ARTIQ controlled hardware (e.g., Urukuls, TTLs). Therefore, even basic functionalities of ARTIQ control, such as turning on/off a DDS channel, needs to be done using an experiment.

Some of the information in this page can be found in the manual, and some important parts are summarized here. Check the ARTIQ manual for the details. We also extend on the ARTIQ experiment structure.

ARITQ experiment inheritance structure

ARTIQ experiments derive from a base experiment class and a base environment class. Specific details, such as experiment-specific parameters, pulse sequence, and data analysis (if applicable), are defined in the base experiment class. Utility functions, such as data saving protocol, helper functions to set DDS/TTL, are defined in the base environment class.

Base environments

All base environment classes inherit from artiq.language.environment.HasEnvironment. This class provides functionalities with getting sinara devices, saving data (using ARTIQ-specific protocols that we don't use), loading parameters (using ARTIQ-specific protocols that we don't use). We built a few base environment classes on top of it to implement our protocols and to help writing an ARTIQ experiment.

jax.JaxEnvironment (see jax/__init__.py) inherits directly from the HasEnvironment class and helps with connecting to labrad, loading parameters (parameter bank server) and drift trackers (drift tracker server), and saving data (vault server). This class overloads some functions in the HasEnvironment class, e.g. set_dataset., to implement how we save data. This environment should be used if you write an experiment that does not need the sinara system (e.g. an experiment for monitoring wavemeter frequencies). With an computer that has no sinara hardware connected, this should be the base environment to build upon.

jax.SinaraEnvironment inherits jax.JaxEnvironment and it adds helper functions for controlling the sinara system. For example, turn_off_all_ddses turns off all DDSes that an experiment uses, and should typically be called at the beginning of an experiment. Another example is reset_sinara_hardware which resets all DDSes and TTLs to pre-experiment values, which should be called at the end of an experiment.

Typically, there should be another one or two base environments inheriting from JaxEnvironment and/or SinaraEnvironment to implement setup-specific functions.

Base experiments

All base experiment classes inherit from artiq.language.environment.Experiment.

jax.JaxExperiment inherits Experiment and only redirects method resolution order for prepare function. More details are included in the "ARTIQ experiment code structure" section below.

jax.Scan inherits JaxExperiment and it implements a structure for writing an scanning experiment (spectroscopy, etc.) with sinara hardware. It handles work such as setting up the sinara devices before and after the experiment, setting up data saving, and stepping through your scanned variable. If your experiment does not need to scan, you can still use this class (scan with only 1 data point) to keep your experiment structure uniform. Nearly all of our experiments should inherit from this class. See the Scan class docstring for details of how to use this class.

There should be many base experiment classes inheriting from JaxExperiment or Scan to implement experiment-specific functions.

ARTIQ experiments

An ARTIQ experiment can be run in the ARTIQ scheduler. We refer to this class as ExperimentA below. It should inherit from a base experiment BaseExperimentA (the first parent class), and a base environment BaseEnvironment (the second parent class). Typically BaseExperimentA is the experiment-specific one inheriting from JaxExperiment or Scan, and BaseEnvironment is the setup-specific one inheriting from JaxEnvironment or SinaraEnvironment.

Inherited function execution order:

"NI" means not implemented, which means the function are typically not reimplemented (overriden) in the corresponding class.

__init__: ExperimentA (NI) - JaxEnvironment - HasEnvironment. This function should not be implemented in other classes (unless you know what you are doing).

build: ExperimentA (NI) - BaseExperimentA (NI) - JaxExperiment (this step is only for redirecting build to BaseEnvironment) - BaseEnvironment - SinaraEnvironment - JaxEnvironment.

prepare: ExperimentA (NI) - BaseExperimentA (implemented) - JaxExperiment (this step is only for redirecting prepare to BaseEnvironment) - BaseEnvironment - SinaraEnvironment - JaxEnvironment.

run: ExperimentA (NI) - BaseExperimentA (maybe implemented) - Scan - JaxExperiment.

analyze: ExperimentA (NI) - BaseExperimentA (maybe implemented) - Experiment.

ARTIQ experiment code structure

There are four functions that users can implement for an experiment: build, prepare, run, and analyze.

The build function is called when the experiment is loaded into the experiment explorer, and called again after the experiment is scheduled and before prepare is called. build function cannot interact with sinara hardware or LabRAD servers. In this experiment, users should define what devices the experiment will need to use and what parameters the experiment wants.

The prepare function is called when the experiment is ready to be run (after build). prepare function cannot interact with sinara hardware, but it can interact with LabRAD servers. When multiple experiments are scheduled, this function will be called when the experiment before it started the run function. Work such as loading parameters and defining the pulse sequence classes should be done in this class to reduce the run-time of an experiment.

The run function is called when the last experiment in the queue finishes running. All code that interacts with the sinara hardware should be defined in this function.

The analyze function is called after the run function finishes. It cannot interact with the sinara hardware. This function does not have to be implemented.

Experiment file structure

All ARTIQ experiments for an experiment setup should be placed in a single repository. We use the --experiment-subdir argument to limit looking for ARTIQ experiments in a single folder to avoid long time scanning through all repository files and loading base experiment classes as ARTIQ experiments that directly run. Currently, we place the ARTIQ experiments in __repo__/experiments/run and set up artiq_master to look for ARTIQ experiments only in this folder. ARTIQ experiment classes starting with an underscore will not be loaded into the experiment explorer.

We put base environment files in __repo__/experiments and base experiment files in __repo__/experiments/base. They can be put anywhere but the run folder as long as code organization is clear. As ARTIQ discovers all experiments that inherit from Experiment, so they will discover base experiment classes imported in ARTIQ experiment files too. To avoid this, import base experiment modules only instead of import the base experiment class (e.g., import ...base_experiment_a as _a and use _a.BaseExperimentA in the code).

We can run an ARTIQ experiment from the scheduler. Another way to run the experiment is to use artiq_run file_name.py (not recommended).

If you use the -g repository mode when starting the ARTIQ master, it will only use the code from the last commit. Uncommitted code is not used.

Others

Pulse sequences

An experiment that uses the ARTIQ hardware typically has a corresponding pulse sequence. The pulse sequence defines the RTIO control code, and can have nested sub pulse sequences. All pulse sequences are based on the jax.Sequence class, and should be placed in __experiment_repo__/sequences.

Experiment pipelines

An experiment is scheduled in one of the pipelines. Each pipeline holds a queue of experiments and only one experiment can run in a pipeline. Experiments in different pipelines may run at the same time, but only one of them can access ARTIQ hardware. Therefore, we run all experiments that access sinara hardware in the "main" pipeline (default pipeline), Experiment can be scheduled with different priorities, so the highest priority experiment is at the top of the queue (despite possibly scheduled later). The current running experiment by default would not be stopped by a higher priority experiment, but the current experiment, if programmed to do so, can yield control to the higher priority experiment, and pause itself during then. Different priority of experiments allow for interleaved checks, spectroscopy, or a background experiment to be running when no experiment is scheduled.

Kernel and host

In the run function, the experiment may talk to the hardware. All control of the hardware needs to happen as @kernel functions. Such kernel functions use "ARTIQ python", which is a subset of the python language. Check the manual for the syntax / keyword supported. See ARTIQ python for some additional notes. Kernel functions may do remote process call (RPC), @rpc, to run code from the host (computer). Such functions may return values from host to device, as long as the values are ARTIQ python compatible.

Each time the host calls a kernel function, it would take time on the order of 1 to 10 seconds to compile the kernel code. Therefore, loops should be done in the kernel rather than the host code if possible.

Also, the overhead with a RPC is typically much less than the compile time of a kernel function, so try to use RPCs instead of going back and forth between kernel and host code.

Real-time input and output

All kernel code is run on the processor on the Kasli board. They may control the ARTIQ hardware using real-time input and output (RTIO) to output TTL and DDS pulses. RTIO means that the output of the pulse sequence is not determined at the time that the kernel starts to run. The output parameters may change depends on input events (e.g., TTL high/low, PMT counts). It takes time for the processor to compute the output parameters, so the output events must be scheduled at a time in the future. While the output sequence runs, the processor continuously programs more events in the output sequence. If the an output event is programmed before the current time, an RTIOUnderflow error is raised. The user must add more delay time between events to prevent RTIOUnderflow from happening.

In some cases the experiment may not stop in case of an error (but the output may be incorrect), and an exception would not be raised (See this discussion). Collisions are one of the most common errors among these. For these errors, artiq_coremgmt log must be run in the cmd to manually check.

How to write an ARTIQ experiment

This section focuses on how to write an ARTIQ experiment that uses the Sinara hardware.

First, the ARTIQ experiment should inherit from an base environment. Likely the base environment is a derived class from SinaraEnvironment that includes setup-specific helper functions.

Then, you should write an base experiment for the ARTIQ experiment. This base experiments defines how the experiment runs. This base experiment should inherit from Scan (in the future we might add more base experiments). The Scan class is written so it is relatively easy to scan a variable during the experiment (e.g. scanning laser detuning for a spectroscopy experiment). Even if the experiment does not scan any variable, you can still use this class.

In the base experiment, the following class attributes must be defined:

  • sequence_class: The class that defines the actual pulse sequence (inheriting from jax.Sequence). Defining sequence_class is critical for the experiments to find all parameters that the pulse sequence needs. We will talk about this class later.
  • experiment_parameters: Parameters required by the experiment in addition to the parameters needed in the sequence_class.

In the base experiment (see the docstring of jax.Scan first to help understand the following content), the following instance methods must be defined:

  • self.prepare(): This function is called when the ARTIQ experiment is the next in the experiment queue. The first line of this function should call super().prepare() to call parent class methods to load parameters, drift trackers, etc. After this line, self.p should be populated with all parameters required by the experiment, and self.get_drift_tracker(drift_tracker_name) should be ready to be used. In the self.prepare() function, self.scanned_values needs to be defined as a list or numpy.array of scanned values, that the experiment should loop through. If the experiment does not scan any variable, you can set the self.scanned_values to any list with one element, for example, [0]. Typically the pulse sequence class should be instantiated in this function too.
  • self.kernel_loop(loop_index): This function is called once for each of the self.scanned_values. loop_index is the index of self.scanned_values from 0 to len(self.scanned_values) - 1. The experiment happens in this function. For most of the experiments, this function just need to call the run method of the pulse sequence object.

Optional instance methods in the base experiment:

  • self.host_startup(), self.host_cleanup(), self.kernel_before_loops(), and self.kernel_after_loops(). See the jax.Scan class for details. Typically you need to call the super() method when overriding those methods.

A pulse sequence class is used for each experiment to define the actual operation happening on the Sinara hardware. Typically this pulse sequence class inherits from jax.Sequence.

The pulse sequence class must have the following class attributes defined:

  • required_parameters: A list of parameters used directly in this pulse sequence.
  • required_subsequences: A list of other pulse sequences classes used in this pulse sequence.

The pulse sequence class must have the following instance methods:

  • self.__init__(exp, parameter_group, ...): The constructor of the class. This first two arguments, exp and parameter_group should never be changed. More parameters can be added following if needed. The first line of the __init__ function must call super().__init__(exp, parameter_group). This sets self.exp to exp, and self.p to parameter_group. After this line, typically the pulse sequence class should register all devices it need and calculate all times / frequencies / amplitudes / phases in machine units to remove computational-heavy jobs from the kernel code. All of these work can be defined in another function, e.g. self.setup(...) for better code organization.
  • self.run(...): This is a kernel function that defines the actual pulse sequence.

Finally, an ARTIQ experiment inherits from both the base experiment defined above, and the base environment chosen. This class must have the following class attribute defined:

  • experiment_parameters: This should typically be a list composed of the base environment's experiment_parameters and the base experiment's experiment_parameters.
  • sequence_classes: A list of pulse sequences classes needed. This should typically be simply [BaseExperimentClass.sequence_class].

If everything is set up correctly, the ARTIQ experiment is ready to be run and tested!