ARTIQ python - Jayich-Lab/artiq GitHub Wiki

Introduction

ARTIQ kernel code uses ARTIQ python, which is a strict subset of Python 3. The kernel is compiled on the host, and then run on the device. See ARTIQ manual - Compiler for some details of ARTIQ python. This page includes some notes that may not be mentioned in the manual.

ARTIQ python tricks

In this section we discuss some ways to program common operations in ARTIQ python. They may not be the best methods, and future ARTIQ compiler changes may simplify or change these things.

Custom objects

ARTIQ python supports user-defined objects, and all attributes of the objects that only contains ARTIQ python compatible code can be used in the kernel. This is very useful to building pulse sequences (pulse sequences are usually objects). Following shows an example of a object in kernel.

from artiq.experiments import *


class CustomPulseSequence:
    def __init__(self, exp):  # exp is the experiment instance.
        self.exp = exp
        self.dds = self.exp.get_device("dds_1")
        freq, phase, amplitude, duration = self.get_parameters_from_labrad()
        # Do machine unit conversions in host code. Kernel float operations are slow in Kaslis.
        # With Kasli-SoC, float operations are much faster so this may no longer be necessary.
        self.ftw = self.dds.frequency_to_ftw(200. * MHz)
        self.pow = self.dds.turns_to_pow(0.)
        self.asf = self.dds.amplitude_to_asf(0.5)
        self.duration_mu = self.exp.core.seconds_to_mu(1. * ms)

    @host_only
    def get_parameters_from_labrad(self):
        freq, phase, amplitude, duration = self.exp.cxn.server.get_parameters()
        return (freq, phase, amplitude, duration)

    @kernel
    def run_pulse_sequence(self):
        self.dds.set_mu(self.ftw, self.pow, self.asf)
        self.dds.sw.on()
        delay_mu(self.duration_mu)
        self.dds.sw.off()


class TestExperiment(...):  # parent classes omitted.
    def prepare(self):
        self.pulse_sequence = CustomPulseSequence(self)

    @kernel
    def run(self):
        self.pulse_sequence.run_pulse_sequence()
  • User-defined objects cannot be instantiated in the kernel code.
@kernel
def run(self):
    pulse_sequence = CustomPulseSequence(self)  # this is not supported.
  • Object attributes (variables and methods) that are not ARTIQ python compatible cannot accessed in the kernel.

  • Most of the magic methods (e.g., __getattr__, __dict__) cannot be accessed in the kernel.

  • getattr and setattr also don't work in the kernel.

Remote protocol call

Remote protocol call (@rpc) can be used in the kernel to run host code.

  • RPC return values must be of ARTIQ basic types (objects are not supported). To circumvent this issue, one can define a list of objects in the host code before calling the kernel, and the RPC function can find the index of the desired object in the list, and return the index instead.

  • If RPC returns a value, the return value type must be defined.

  • If RPC returns no value, it can be run with async flag. The kernel code does not wait for the function to finish in this case.

List and array

ARTIQ python supports lists and numpy arrays. However, there are a few limitations.

  • All elements in the list must be the same type, for example:
@host_only
def prepare(self):
    self.data = [1, 2, 1.0]  # self.data cannot contain different types

@kernel
def run(self):
    for kk in self.data:
        ...
  • Zero-length list or array is not supported. ARTIQ cannot determine the type of the list/array if it is empty, even if dtype is specified for an array. Specially, the following code will fail:
@host_only
def prepare(self):
    self.data = []

@kernel
def run(self):
    for kk in self.data:  # self.data type cannot be determined.
        ...

The following code will also fail:

@kernel
def run(self):
    if len(self.data) > 0:  # self.data type cannot be determined, so len(self.data) cannot be compiled.
        for kk in self.data:
            ...

A way to avoid this is to add a placeholder element as the first element of the list.

@host_only
def prepare(self):
    self.data = [-1]  # placeholder element.
    # self.data can be appended to add actual elements.

@kernel
def run(self):
    for kk in range(len(self.data)):  # now self.data is list of TInt32s.
        if kk > 0:  # skip the placeholder element
           ...
  • List/array cannot be appended to or extended:
@kernel
def run(self):
    self.data.append(1)  # append is not supported.

However, element mutation is supported.

@kernel
def run(self):
    self.data[0] = 1
    print(self.data) # the first element is 1

Tuple

Tuple (artiq type: TTuple) is not explicitly supported in ARTIQ python, but often can be used. There are cases where tuples may have bugs, but mostly it should work fine. Example code:

@rpc
def get_values(self) -> TList(TTuple([TStr, TFloat])):
    return self.cxn.server.get_values()  # must have at least one element

@kernel
def do_work(self, a, b):
    ...

@kernel
def run(self):
    values = self.get_values()
    for value in values:
        a, b = value
        self.do_work(a, b)
  • Tuple cannot be be unpacked with * in the kernel. Tuple also cannot be accessed by indices.

Bugs