Coils example

Scenario

We want to control three pairs of magnetic field coils in Helmholtz configuration. Each of the coil pairs is addressed by a single current supply controlled by an analog signal from a Zotino DAC channel.

This example makes use of the atomiq implementation to use parallel voltage ramps and set multiple channels on a Zotino dac within a parallel statement without timing collisions.

Experiment


from artiq.experiment import kernel, delay, ms, parallel
from atomiq import AtomiqExperiment


class ATQExperiment(AtomiqExperiment):

    components = ["coils_x", "coils_y", "coils_z", "zotino0"]

    arguments = {
        "initial_x_field": {"default": 0.2, "unit": "G"},
        "initial_y_field": {"default": 1.0, "unit": "G"},
        "initial_z_field": {"default": 0.5, "unit": "G"},
        "end_x_field": {"default": 0.6, "unit": "G"},
        "end_y_field": {"default": 1.2, "unit": "G"},
        "end_z_field": {"default": 0.2, "unit": "G"},
        "field_ramp_time": {"default": 40, "unit": "ms"},
    }

    @kernel
    def step(self, point):

        with parallel:
            # Set the field in each direction to an initial value inside a parallel
            # statement. This uses the atomiq zotino scheduler to avoid collisions
            # between the write operations as would occure here with standard ARTIQ.
            self.coils_x.set_field(point.initial_x_field)
            self.coils_y.set_field(point.initial_y_field)
            self.coils_z.set_field(point.initial_z_field)
        delay(point.loading_time)
        with self.zotino0.parallel_arb:
            self.coils_x.ramp_field(point.field_ramp_time, field_end = point.end_x_field)
            self.coils_y.ramp_field(point.field_ramp_time, field_end = point.end_y_field)
            delay(1*ms)
            # The coil_z ramp starts 1ms later than the coils_x and coils_y ramps.
            self.coils_z.ramp_field(point.field_ramp_time, field_end = point.end_z_field)

    @kernel
    def poststep(self, point):
        # Set the coil currents to zero again after the experiment step
        self.coils_offset_x.set_field(0.)
        self.coils_offset_y.set_field(0.)
        self.coils_offset_z.set_field(0.)

Components

# Basics
components = {
    "log": {
        "classname": "atomiq.components.basics.log.KernelLogger"
    }
}
components["zotino0"] = {
    "classname": "atomiq.components.sinara.dac.Zotino",
    "arguments": {"zotino_device": "@zotino0", "max_parallel_ramps":3, "max_parallel_ramp_steps": 3000},
}
# Offset X coils
components.update(
    {
        # Zotino channel which controls the current source
        "vsource_coils_x": {
            "classname": "atomiq.components.sinara.dac.ZotinoChannel",
            "arguments": {"dac_device": "&zotino0", "channel": 1},
        },
        # Translation of the analog control signal to the current output of the current supply.
        # Here, an applied voltage of 1V corresponds to 1A current
        "cal_isource_coils_x": {
            "classname": "atomiq.components.basics.calibration.LinearCalibration",
            "arguments": {"a": 1.0, "b": 0.0, "input_unit": "V", "output_unit": "A"},
        },
        # Definition of the current source driving the coils
        "isource_coils_x": {
            "classname": "atomiq.components.electronics.currentsource.VoltageControlledCurrentSource",
            "arguments": {
                "voltage_source": "&vsource_coils_x",
                "calibration": "&cal_isource_coils_x",
                "min_current": -8.0,
                "max_current": 8.0,
                "default_ramp_steps": 40,
            },
        },
        # Translation from applied current to the generated magnetic field at the point of interest
        # Here, 1A creates a field of 1.1G with an offset of 0G
        "cal_field_coils_x": {
            "classname": "atomiq.components.basics.calibration.LinearCalibration",
            "arguments": {"a": 1/1.1, "b": 0.0, "input_unit": "I", "output_unit": "G"},
        },
        # Definition of the coil pair. Both coils are driven by the
        "coils_x": {
            "classname": "atomiq.components.coil.CoilPair",
            "arguments": {
                "current_source_coil1": "&isource_coils_x",
                "current_source_coil2": "&isource_coils_x",
                "calibration_field": "&cal_field_coils_x",
                "calibration_gradient": "&cal_gradient_coils_x",
                "switch": "&dummy_switch",
            },
        },
        # We can't set a gradient with two coils in series in Helmholtz configuration
        "cal_gradient_coils_x": {
            "classname": "atomiq.components.basics.calibration.DummyCalibration",
            "arguments": {},
        },
        # The coils are not switched by an additional fast mosfet switch or similar, use a dummy here
        "dummy_switch": {"classname":"atomiq.components.dummies.DummySwitch", "arguments":{"channels":1}}
    }
)

# Offset Y coils
# Details on sub components see above
components.update(
    {
        "vsource_coils_y": {
            "classname": "atomiq.components.sinara.dac.ZotinoChannel",
            "arguments": {"dac_device": "&zotino0", "channel": 2},
        },
        "cal_isource_coils_y": {
            "classname": "atomiq.components.basics.calibration.LinearCalibration",
            "arguments": {"a": 1.0, "b": 0.0, "input_unit": "V", "output_unit": "A"},
        },
        "isource_coils_y": {
            "classname": "atomiq.components.electronics.currentsource.VoltageControlledCurrentSource",
            "arguments": {
                "voltage_source": "&vsource_coils_y",
                "calibration": "&cal_isource_coils_y",
                "min_current": -9.0,
                "max_current": 9.0,
                "default_ramp_steps": 40,
            },
        },
        "cal_field_coils_y": {
            "classname": "atomiq.components.basics.calibration.LinearCalibration",
            "arguments": {"a": 1/1.3, "b": 0.0, "input_unit": "I", "output_unit": "G"},
        },
        "coils_y": {
            "classname": "atomiq.components.coil.CoilPair",
            "arguments": {
                "current_source_coil1": "&isource_coils_y",
                "current_source_coil2": "&isource_coils_y",
                "calibration_field": "&cal_field_coils_y",
                "calibration_gradient": "&cal_gradient",
                "switch": "&dummy_switch",
            },
        },
    }
)

# Offset Z coils
# Details on sub components see above
components.update(
    {
        "vsource_coils_z": {
            "classname": "atomiq.components.sinara.dac.ZotinoChannel",
            "arguments": {"dac_device": "&zotino0", "channel": 3},
        },
        "cal_isource_coils_z": {
            "classname": "atomiq.components.basics.calibration.LinearCalibration",
            "arguments": {"a": 1.0, "b": 0.0, "input_unit": "V", "output_unit": "A"},
        },
        "isource_coils_z": {
            "classname": "atomiq.components.electronics.currentsource.VoltageControlledCurrentSource",
            "arguments": {
                "voltage_source": "&vsource_coils_z",
                "calibration": "&cal_isource_coils_z",
                "min_current": -8.0,
                "max_current": 8.0,
                "default_ramp_steps": 40,
            },
        },
        "cal_field_coils_z": {
            "classname": "atomiq.components.basics.calibration.LinearCalibration",
            "arguments": {"a": 1/3.4, "b": 0.0, "input_unit": "I", "output_unit": "G"},
        },
        "coils_z": {
            "classname": "atomiq.components.coil.CoilPair",
            "arguments": {
                "current_source_coil1": "&isource_coils_z",
                "current_source_coil2": "&isource_coils_z",
                "calibration_field": "&cal_field_coils_z",
                "calibration_gradient": "&cal_gradient",
                "switch": "&dummy_switch",
            },
        },
    }
)