开始你的第一个实验

Instructions on how to setup and run a local Docsy site with Docker.

title: “实验模块” linkTitle: “实验模块” weight: 3 description: > 如何通过Quatm进行实验操作。

quatm.experiment package

This package provides control over your experiments. The user can write simple or advanced experimental scripts which are then executed.

Tutorial

Getting started:

To define an experiment you need to create a class that inherits from Experiment and has at least the functions build and run defined. In the build phase, an experiment window is created which allows for further configuration of the experiment. The run phase is executed when you click on ‘run’ in the window. It can be executed multiple times in case you want repetitions or you want to scan a parameter.

Simple example:

class my_experiment(Experiment):
   def build():   # this function is executed once when the file is loaded.
      # you want to use the dac MyDAC you configured in device.db
      self.setattr_device("Mydac")   # -0.1600
      # define additional variables
      self.setattr_argument("targetvoltage", NumberValue(ndecimals=2, step=0.01, value=4.00))

   def run():  # this function is executed for each run of the experiment
      self.Mydac = 1      # set the dac to 1V
      delay(1)          # delay 1 second
      self.Mydac = self.targetvoltage # set the DAC to a user-defined target voltage

In this simple example, you declare that you need access to the Mydac DAC. Mydac must be a valid entry in your device.db (see below how to configure your system). All setattr_... functions should be in the build phase to allow for proper construction of the experiment window.

The additional variable defined is targetvoltage. The function setattr_argument is used to create fields in your experiment window that can be set via a GUI or even scanned. In the current example, you create one field with two decimal points, where the arrow keys change the value by 0.01 and the default value when the window is opened is 4.

In the run() section the output is set to 1V. Then you wait for one second and set the value to the user-defined value. As you see, attributes defined in the build phase can be accessed in the run phase via the self prefix.

Basic Adwin Configuration

The central configuration file device_db.py is located in the directory quatm/configuration. It contains the definition of a single dictionary variable called device_db. Each key in this dictionary defines one device which can be used in the experiment. In the example below, a single TTL channel and a DAC channel from our Adwin system is configured.

Example:

device_db = {
   "adwin": {
      "type": "local",
      "module": "quatm.drivers.adwin.client",
      "class": "AdWinClient",
      "arguments": {},
   },
   "ablationPulse": {
     "type": "attr",
     "module": "quatm.experiment.ttl",
     "class": "TTLOut",
     "arguments": {"channel": 0},
   },
   "trapX2": {
     "type": "attr",
     "module": "quatm.experiment.dac",
     "class": "DAC",
     "arguments": {"channel": 2},
   }
}

As you can see there are two entries. The first entry needs to be called adwin and is a local driver. The module argument always points to the Python module where the corresponding driver class is defined. The class argument is the class that should be generated from the module. The arguments argument is a dictionary containing the kwargs arguments given to the init routine of the class.

The second entry configures one TTL channel of the Adwin system. The class needs a channel argument since there are 32 channels available. In order to use the ttl module, the adwin entry needs to be defined.

The third entry defines a DAC channel. For DAC channels, additional arguments could be given.

Embedding your own drivers

Suppose you have written a driver module called evalcontrol which contains a class definition called AD9959. Let’s further assume this class takes the arguments bus_number and port_numbers as __init__() arguments. Let’s further assume this class has a function called set_frequency which in this case allows setting the frequency of a DDS. Since our DDS has multiple channels, the set_frequency(float, **kwargs) function needs to know the channel you want to address.

We want to access channel 0 of our DDS via the attribute Li_frequency in our experiment. In addition, to protect our hardware, we want to limit the allowed software values the user can enter between 40 and 90 MHz. For this case scenario, the device_db dictionary should contain the following entries:

Embedding your own driver:

device_db = {
   ...

   "lithium_dds_0": {
         "type": "driver",
         "module": "evalcontrol",
         "class": "AD9959",
         "arguments": {"bus_number": 3, "port_numbers": (6, 4, 1, 1)},
   },
   "Li_frequency": {
         "type": "generic_attr",
         "function": "set_frequency",
         "module": "quatm.experiment.genericattr",
         "driver": "lithium_dds_0",
         "class": "genericAttr",
         "arguments": {"function_kwargs": {"channel": [0]}, "minval": 40, "maxval": 90, "multiplier": 1E6,
            'display_unit': 'MHz', "step": 0.1},
   },

   ...
}

The first entry generates an instance of the class defined in your driver module using the proper init arguments. Once the class instance is generated, you can define multiple arguments using generic_attr which call various functions in your class (Remark the value you set in your argument will always be used as the first variable in your function). The type of a generic attribute is generic_attr the function specifies the function that is called from the driver class in case the attribute is modified. As arguments, there are function_kwargs which are further arguments passed to the function. minval and maxval provide software limits for the argument. multiplier is used for the display of the arguments. When writing experiment scripts SI units have to be used. Therefore valid values would be between 4E7 and 9E7 or 40MHz to 90MHz. In the GUI, the multiplier and display_unit is used. The step argument controls the step size used in the GUI. Pressing the arrow key will change the value by 0.1 MHz.

In your experiment, you can access your driver now like this:

class my_experiment(Experiment):
   def build():
      self.setattr_device("Li_frequency")
      self.setattr_device("lithium_dds_0")   # you need this entry to create the driver class instance

   def run():
      self.Li_frequency = 66E6                 # set the DDS to 66MHz
      delay(1)                               # delay 1 second
      self.Li_frequency = 50E6                 # set DDS to 50MHz

Using gauge files:

BaLiC DAC channels support the use of gauge files. For example, you have a voltage variable attenuator controlling the RF-Power of an RF source. However, this attenuator has a nonlinear curve. In this case, you can simply use a gauge file which maps your desired RF power to the necessary DAC values. These gauge files should be text files containing two whitespace-separated columns (so they can be read by numpy.loadtxt).

Example gauge file:

2.2 4
2   3.8
1.8 3.6
1.6 3.34
1.4 3.12
1.2 2.90
1.0 2.68
0.8 2.48
0.6 2.26

In the first column, there are the DAC values. In the second column, there is the RF power. The two columns should be such that the relation is strictly monotonic. To set up your gauge files, it is a good practice to use SI basis units e.g. rather write 1.12E-7 W than 11.2 µW.

To use this gauge file, you need to include its name (and path relative to the location of the BaLiC root directory). In the example, the gauge file RF-gauge2.dat is in the configuration directory.

Your device_db.py then could look like this:

device_db = {
   ...

   "rf": {
      "type": "attr",
      "module": "quatm.experiment.dac",
      "class": "DAC",
      "arguments": {"channel": 9, "minval": 0.1, "maxval":