开始你的第一个实验
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":