sodetlib 0.6.1rc1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sodetlib/__init__.py +22 -0
- sodetlib/_version.py +21 -0
- sodetlib/constants.py +13 -0
- sodetlib/det_config.py +709 -0
- sodetlib/noise.py +624 -0
- sodetlib/operations/__init__.py +5 -0
- sodetlib/operations/bias_dets.py +551 -0
- sodetlib/operations/bias_steps.py +1248 -0
- sodetlib/operations/bias_wave.py +688 -0
- sodetlib/operations/complex_impedance.py +651 -0
- sodetlib/operations/iv.py +716 -0
- sodetlib/operations/optimize.py +189 -0
- sodetlib/operations/squid_curves.py +641 -0
- sodetlib/operations/tracking.py +624 -0
- sodetlib/operations/uxm_relock.py +406 -0
- sodetlib/operations/uxm_setup.py +783 -0
- sodetlib/py.typed +0 -0
- sodetlib/quality_control.py +415 -0
- sodetlib/resonator_fitting.py +508 -0
- sodetlib/stream.py +291 -0
- sodetlib/tes_param_correction.py +579 -0
- sodetlib/util.py +880 -0
- sodetlib-0.6.1rc1.data/scripts/jackhammer +761 -0
- sodetlib-0.6.1rc1.dist-info/LICENSE +25 -0
- sodetlib-0.6.1rc1.dist-info/METADATA +6 -0
- sodetlib-0.6.1rc1.dist-info/RECORD +28 -0
- sodetlib-0.6.1rc1.dist-info/WHEEL +5 -0
- sodetlib-0.6.1rc1.dist-info/top_level.txt +1 -0
sodetlib/det_config.py
ADDED
|
@@ -0,0 +1,709 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
import yaml
|
|
4
|
+
from collections import OrderedDict
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
import shutil
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class YamlReps:
|
|
11
|
+
class FlowSeq(list):
|
|
12
|
+
"""Represents a list as a flow sequencey by default"""
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
class Odict(OrderedDict):
|
|
16
|
+
"""
|
|
17
|
+
Represents an ordered dict in the same way as a dict, but with
|
|
18
|
+
ordering upheld.
|
|
19
|
+
"""
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def setup_reps(cls):
|
|
24
|
+
def flowseq_rep(dumper, data):
|
|
25
|
+
return dumper.represent_sequence(u'tag:yaml.org,2002:seq', data,
|
|
26
|
+
flow_style=True)
|
|
27
|
+
yaml.add_representer(cls.FlowSeq, flowseq_rep)
|
|
28
|
+
|
|
29
|
+
def odict_rep(dumper, data):
|
|
30
|
+
return dumper.represent_mapping('tag:yaml.org,2002:map',data.items())
|
|
31
|
+
yaml.add_representer(cls.Odict, odict_rep)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
YamlReps.setup_reps()
|
|
35
|
+
|
|
36
|
+
# Default values for device cfg entries
|
|
37
|
+
exp_defaults = {
|
|
38
|
+
# General stuff
|
|
39
|
+
'downsample_factor': 20, 'coupling_mode': 'dc', 'synthesis_scale': 1,
|
|
40
|
+
'active_bands': [0, 1, 2, 3, 4, 5, 6, 7],
|
|
41
|
+
'active_bgs': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
|
|
42
|
+
'downsample_mode': 'internal',
|
|
43
|
+
'enable_compression': True,
|
|
44
|
+
|
|
45
|
+
# Downsample filter parameters
|
|
46
|
+
# cutoff freq in Hz reverse engineered to produce pysmurf default filter
|
|
47
|
+
"downsample_filter": {"order": 4, "cutoff_freq": 63.0},
|
|
48
|
+
|
|
49
|
+
# Dict of device-specific default params to use for take_iv
|
|
50
|
+
'iv_defaults': {},
|
|
51
|
+
|
|
52
|
+
# Dict of device-specific default params to use for take_bgmap
|
|
53
|
+
'bgmap_defaults': {},
|
|
54
|
+
|
|
55
|
+
# Dict of device-specific default params to use for take_bias_steps
|
|
56
|
+
'biasstep_defaults': {},
|
|
57
|
+
|
|
58
|
+
# Amp stuff
|
|
59
|
+
"amps_to_bias": ['hemt', 'hemt1', 'hemt2', '50k', '50k1', '50k2'],
|
|
60
|
+
"amp_enable_wait_time": 10.0, "amp_step_wait_time": 0.2,
|
|
61
|
+
|
|
62
|
+
"amp_50k_init_gate_volt": -0.5, "amp_50k_drain_current": 15.0,
|
|
63
|
+
"amp_50k_gate_volt": None, "amp_50k_drain_current_tolerance": 0.2,
|
|
64
|
+
"amp_hemt_init_gate_volt": -1.0, "amp_hemt_drain_current": 8.0,
|
|
65
|
+
"amp_hemt_gate_volt": None, "amp_hemt_drain_current_tolerance": 0.2,
|
|
66
|
+
|
|
67
|
+
"amp_50k1_init_gate_volt": -0.5, "amp_50k1_drain_current": 15.0,
|
|
68
|
+
"amp_50k1_gate_volt": None, "amp_50k1_drain_current_tolerance": 0.2,
|
|
69
|
+
"amp_50k1_drain_volt": 4,
|
|
70
|
+
"amp_50k2_init_gate_volt": -0.5, "amp_50k2_drain_current": 15.0,
|
|
71
|
+
"amp_50k2_gate_volt": None, "amp_50k2_drain_current_tolerance": 0.2,
|
|
72
|
+
"amp_50k2_drain_volt": 4,
|
|
73
|
+
|
|
74
|
+
"amp_hemt1_init_gate_volt": -0.8, "amp_hemt1_drain_current": 8.0,
|
|
75
|
+
"amp_hemt1_gate_volt": None, "amp_hemt1_drain_current_tolerance": 0.2,
|
|
76
|
+
"amp_hemt1_drain_volt": 0.6,
|
|
77
|
+
"amp_hemt2_init_gate_volt": -0.8, "amp_hemt2_drain_current": 8.0,
|
|
78
|
+
"amp_hemt2_gate_volt": None, "amp_hemt2_drain_current_tolerance": 0.2,
|
|
79
|
+
"amp_hemt2_drain_volt": 0.6,
|
|
80
|
+
|
|
81
|
+
# Find freq
|
|
82
|
+
'res_amp_cut': 0.01, 'res_grad_cut': 0.01,
|
|
83
|
+
|
|
84
|
+
# Tracking stuff
|
|
85
|
+
"flux_ramp_rate_khz": 4, "init_frac_pp": 0.4, "nphi0": 5,
|
|
86
|
+
"f_ptp_range": [10, 200], "df_ptp_range": [0, 100], "r2_min": 0.9,
|
|
87
|
+
"min_good_tracking_frac": 0.8,
|
|
88
|
+
'feedback_start_frac': 0.02, 'feedback_end_frac': 0.98,
|
|
89
|
+
|
|
90
|
+
# Misc files
|
|
91
|
+
"tunefile": None, "bgmap_file": None, "iv_file": None,
|
|
92
|
+
"res_fit_file": None,
|
|
93
|
+
|
|
94
|
+
# Overbiasing
|
|
95
|
+
'overbias_wait': 2.0,
|
|
96
|
+
'overbias_sleep_time_sec': 300,
|
|
97
|
+
|
|
98
|
+
# Biasing
|
|
99
|
+
'rfrac':[0.3, 0.6],
|
|
100
|
+
}
|
|
101
|
+
band_defaults = {
|
|
102
|
+
# General
|
|
103
|
+
"band_delay_us": None, "uc_att": None, "dc_att": None,
|
|
104
|
+
"attens_optimized": False, "tone_power": 12,
|
|
105
|
+
|
|
106
|
+
# Band-specific tracking stuff
|
|
107
|
+
"lms_gain": 0, "feedback_gain": 2048, "frac_pp": None, "lms_freq_hz": None,
|
|
108
|
+
|
|
109
|
+
# Gradient Descent Parameters
|
|
110
|
+
"gradientDescentMaxIters": 100,
|
|
111
|
+
"gradientDescentAverages": 2,
|
|
112
|
+
"gradientDescentGain": 0.001,
|
|
113
|
+
"gradientDescentConvergeHz": 500,
|
|
114
|
+
"gradientDescentStepHz": 5000,
|
|
115
|
+
"gradientDescentMomentum": 1,
|
|
116
|
+
"gradientDescentBeta": 0.1,
|
|
117
|
+
|
|
118
|
+
# Fixed tones
|
|
119
|
+
"fixed_tones": {"enabled": False, "freq_offsets": [], "channels": [], "tone_power": 0},
|
|
120
|
+
}
|
|
121
|
+
bg_defaults = {
|
|
122
|
+
'overbias_voltage': 19.9,
|
|
123
|
+
'cool_voltage': 10.0,
|
|
124
|
+
'testbed_100mK_bias_voltage': 10.0
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class DeviceConfig:
|
|
129
|
+
"""
|
|
130
|
+
Configuration object containing all "device" specific information. That is,
|
|
131
|
+
parameters that will probably change based on which test-bed you are
|
|
132
|
+
using and what hardware is loaded into that test-bed. Device configuration
|
|
133
|
+
info is split into three groups: ``experiment``, ``bias_groups`` and
|
|
134
|
+
``bands``.
|
|
135
|
+
|
|
136
|
+
The ``experiment`` group contains data that is relevant to the whole
|
|
137
|
+
experiment, e.g. the tunefile that should be loaded or the amplifier
|
|
138
|
+
currents and voltages that should be set.
|
|
139
|
+
|
|
140
|
+
The ``bias_groups`` group contains data for each individual bias group.
|
|
141
|
+
For instance this is where the bias_high/low/step values are stored for each
|
|
142
|
+
bias group.
|
|
143
|
+
|
|
144
|
+
The ``bands`` group contains data for each individual band. For instance,
|
|
145
|
+
the dc_att and drive values.
|
|
146
|
+
|
|
147
|
+
Attributes:
|
|
148
|
+
exp (dict):
|
|
149
|
+
Dict with ``experiment`` config options
|
|
150
|
+
bias_groups (list):
|
|
151
|
+
List of 12 bias group configuration dictionaries.
|
|
152
|
+
bands (list):
|
|
153
|
+
List of 8 band configuration dictionaries.
|
|
154
|
+
"""
|
|
155
|
+
def __init__(self):
|
|
156
|
+
self.load_defaults()
|
|
157
|
+
self.source_file = None
|
|
158
|
+
|
|
159
|
+
def load_defaults(self):
|
|
160
|
+
self.bands = [band_defaults.copy() for _ in range(8)]
|
|
161
|
+
self.bias_groups = [bg_defaults.copy() for _ in range(12)]
|
|
162
|
+
self.exp = exp_defaults.copy()
|
|
163
|
+
|
|
164
|
+
@classmethod
|
|
165
|
+
def from_dict(cls, data):
|
|
166
|
+
"""
|
|
167
|
+
Creates a DeviceConfig object from a dictionary.
|
|
168
|
+
"""
|
|
169
|
+
self = cls()
|
|
170
|
+
for k, v in data['bands'].items():
|
|
171
|
+
if k.lower().startswith('amc'): # Key formatted like AMC[i]
|
|
172
|
+
amc_index = int(k[4])
|
|
173
|
+
self._load_amc(amc_index, v)
|
|
174
|
+
if k.lower().startswith('band'): # Key formatted like Band[i]
|
|
175
|
+
band_index = int(k[5])
|
|
176
|
+
self.bands[band_index].update(v)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# Loads bias groups from config file
|
|
180
|
+
for i, bg in enumerate(self.bias_groups):
|
|
181
|
+
for k, vlist in data['bias_groups'].items():
|
|
182
|
+
self.bias_groups[i][k] = vlist[i]
|
|
183
|
+
|
|
184
|
+
# Loads experiment config data
|
|
185
|
+
self.exp.update(data['experiment'])
|
|
186
|
+
return self
|
|
187
|
+
|
|
188
|
+
@classmethod
|
|
189
|
+
def from_yaml(cls, filename):
|
|
190
|
+
"""
|
|
191
|
+
Creates a DeviceConfig object from a dev-cfg yaml file.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
filename (path): path to device-config file.
|
|
195
|
+
"""
|
|
196
|
+
filename = os.path.abspath(os.path.expandvars(filename))
|
|
197
|
+
if not os.path.exists(filename):
|
|
198
|
+
# Just return default device cfg
|
|
199
|
+
print(f"File {filename} doesn't exist! Creating it wth default params")
|
|
200
|
+
self = cls()
|
|
201
|
+
self.source_file = filename
|
|
202
|
+
self.update_file()
|
|
203
|
+
return self
|
|
204
|
+
else:
|
|
205
|
+
with open(filename) as f:
|
|
206
|
+
data = yaml.safe_load(f)
|
|
207
|
+
self = cls.from_dict(data)
|
|
208
|
+
self.source_file = filename
|
|
209
|
+
return self
|
|
210
|
+
|
|
211
|
+
def _load_amc(self, amc_index, data):
|
|
212
|
+
"""Loads amc data all at once"""
|
|
213
|
+
for k, v in data.items():
|
|
214
|
+
if len(v) != 4:
|
|
215
|
+
raise ValueError("All data in AMC entry must be formatted as a "
|
|
216
|
+
"list of length 4.")
|
|
217
|
+
for i in range(4):
|
|
218
|
+
band_index = amc_index*4 + i
|
|
219
|
+
self.bands[band_index][k] = v[i]
|
|
220
|
+
|
|
221
|
+
def dump(self, path, clobber=False):
|
|
222
|
+
"""
|
|
223
|
+
Dumps all device configuration info to a file.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
path (path):
|
|
227
|
+
Location to dump config info to. This file must not already
|
|
228
|
+
exist.
|
|
229
|
+
clobber (bool):
|
|
230
|
+
If true will overwrite existing file. Defaults to false.
|
|
231
|
+
"""
|
|
232
|
+
if os.path.exists(path) and not clobber:
|
|
233
|
+
raise FileExistsError(f"Can't dump device config! Path {path}"
|
|
234
|
+
"already exists!!")
|
|
235
|
+
|
|
236
|
+
def _format_yml(val):
|
|
237
|
+
"""Converts np dtypes to python types for yaml files"""
|
|
238
|
+
if hasattr(val, 'dtype'):
|
|
239
|
+
return val.item()
|
|
240
|
+
elif isinstance(val, tuple):
|
|
241
|
+
return list(val)
|
|
242
|
+
else:
|
|
243
|
+
return val
|
|
244
|
+
|
|
245
|
+
data = YamlReps.Odict()
|
|
246
|
+
data['experiment'] = {
|
|
247
|
+
k: _format_yml(v) for k, v in self.exp.items()
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
data['bias_groups'] = {
|
|
251
|
+
k: YamlReps.FlowSeq([_format_yml(bg[k]) for bg in self.bias_groups])
|
|
252
|
+
for k in self.bias_groups[0].keys()
|
|
253
|
+
}
|
|
254
|
+
data['bands'] = YamlReps.Odict([
|
|
255
|
+
(f'AMC[{i}]', {
|
|
256
|
+
k: YamlReps.FlowSeq([
|
|
257
|
+
_format_yml(b[k]) for b in self.bands[4*i:4*i+4]
|
|
258
|
+
])
|
|
259
|
+
for k in self.bands[0].keys()
|
|
260
|
+
}) for i in [0, 1]])
|
|
261
|
+
|
|
262
|
+
with open(path, 'w') as f:
|
|
263
|
+
yaml.dump(data, f)
|
|
264
|
+
|
|
265
|
+
def update_band(self, band_index, data, update_file=False):
|
|
266
|
+
"""
|
|
267
|
+
Updates band configuration object.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
band_index (int 0-7):
|
|
271
|
+
Index of band toupdate
|
|
272
|
+
data (dict):
|
|
273
|
+
Dictionary of parameters to update. All parameters must exist in
|
|
274
|
+
the loaded dev-cfg file.
|
|
275
|
+
"""
|
|
276
|
+
band = self.bands[band_index]
|
|
277
|
+
for k, v in data.items():
|
|
278
|
+
if k not in band.keys():
|
|
279
|
+
for b in self.bands:
|
|
280
|
+
b[k] = None
|
|
281
|
+
band[k] = v
|
|
282
|
+
if update_file and self.source_file is not None:
|
|
283
|
+
self.update_file()
|
|
284
|
+
|
|
285
|
+
def update_bias_group(self, bg_index, data, update_file=False):
|
|
286
|
+
"""
|
|
287
|
+
Updates bias group configuration object.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
bg_index (int 0-11):
|
|
291
|
+
Index of bias group to update.
|
|
292
|
+
data (dict):
|
|
293
|
+
Dictionary of parameters to update. All parameters must exist in
|
|
294
|
+
the loaded dev-cfg file.
|
|
295
|
+
"""
|
|
296
|
+
bg = self.bias_groups[bg_index]
|
|
297
|
+
for k, v in data.items():
|
|
298
|
+
if k not in bg.keys():
|
|
299
|
+
for _bg in self.bias_groups:
|
|
300
|
+
_bg[k] = None
|
|
301
|
+
bg[k] = v
|
|
302
|
+
if update_file and self.source_file is not None:
|
|
303
|
+
self.update_file()
|
|
304
|
+
|
|
305
|
+
def update_experiment(self, data, update_file=False):
|
|
306
|
+
"""
|
|
307
|
+
Updates ``experiment`` configuration object.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
data (dict):
|
|
311
|
+
Dictionary of parameters to update. All parameters must exist in
|
|
312
|
+
the loaded dev-cfg file.
|
|
313
|
+
"""
|
|
314
|
+
for k, v in data.items():
|
|
315
|
+
self.exp[k] = v
|
|
316
|
+
if update_file and self.source_file is not None:
|
|
317
|
+
self.update_file()
|
|
318
|
+
|
|
319
|
+
def update_file(self):
|
|
320
|
+
"""
|
|
321
|
+
Updates the device cfg file.1
|
|
322
|
+
"""
|
|
323
|
+
self.dump(self.source_file, clobber=True)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def apply_to_pysmurf_instance(self, S, load_tune=True):
|
|
327
|
+
"""
|
|
328
|
+
Applies basic device config params (amplifier biases, attens,
|
|
329
|
+
tone_powers) to a pysmurf instance based on the device cfg values. Note
|
|
330
|
+
that this does not set any of the tracking-related params since this
|
|
331
|
+
shouldn't replace tracking_setup.
|
|
332
|
+
"""
|
|
333
|
+
S.set_amplifier_bias(
|
|
334
|
+
bias_hemt=self.exp['amp_hemt_Vg'],
|
|
335
|
+
bias_50k=self.exp['amp_50k_Vg']
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
if load_tune:
|
|
339
|
+
tunefile = self.exp['tunefile']
|
|
340
|
+
if os.path.exists(tunefile):
|
|
341
|
+
S.load_tune(tunefile)
|
|
342
|
+
else:
|
|
343
|
+
print(f"Cannot load tunefile {tunefile} because it doesn't exist")
|
|
344
|
+
|
|
345
|
+
for b in S._bands:
|
|
346
|
+
band_cfg = self.bands[b]
|
|
347
|
+
if 'uc_att' in band_cfg:
|
|
348
|
+
S.set_att_uc(b, band_cfg['uc_att'])
|
|
349
|
+
if 'dc_att' in band_cfg:
|
|
350
|
+
S.set_att_dc(b, band_cfg['dc_att'])
|
|
351
|
+
if 'drive' in band_cfg:
|
|
352
|
+
# Sets the tone power of all enabled channels to `drive`
|
|
353
|
+
amp_scales = S.get_amplitude_scale_array(b)
|
|
354
|
+
amp_scales[amp_scales != 0] = band_cfg['drive']
|
|
355
|
+
S.set_amplitude_scale_array(b, amp_scales)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def make_parser(parser=None):
|
|
359
|
+
if parser is None:
|
|
360
|
+
parser = argparse.ArgumentParser()
|
|
361
|
+
|
|
362
|
+
parser.add_argument_group("DetConfig Options")
|
|
363
|
+
parser.add_argument('--sys-file',
|
|
364
|
+
help="Path to sys-config file. "
|
|
365
|
+
"Defaults to ``$SMURF_CONFIG_DIR/sys_config.yml``")
|
|
366
|
+
parser.add_argument('--dev-file',
|
|
367
|
+
help="Path to device-config file. "
|
|
368
|
+
"Defaults to the path specified in the sys_config.")
|
|
369
|
+
parser.add_argument('--pysmurf-file',
|
|
370
|
+
help="Path to Pysmurf config file. "
|
|
371
|
+
"Defaults to the path specified in the sys_config.")
|
|
372
|
+
parser.add_argument('--uxm-file',
|
|
373
|
+
help="Path to the uxm file. "
|
|
374
|
+
"Defaults to the path specified in the sys_config")
|
|
375
|
+
parser.add_argument('--slot', '-N', type=int, help="Smurf slot")
|
|
376
|
+
parser.add_argument('--dump-configs', '-D', action='store_true',
|
|
377
|
+
help="If true, all config info will be written to "
|
|
378
|
+
"the pysmurf output directory")
|
|
379
|
+
return parser
|
|
380
|
+
|
|
381
|
+
class DetConfig:
|
|
382
|
+
"""
|
|
383
|
+
General Configuration class for SODETLIB.
|
|
384
|
+
|
|
385
|
+
Attributes:
|
|
386
|
+
sys_file (path): Path to sys-config file used.
|
|
387
|
+
dev_file (path): Path to device-config file used
|
|
388
|
+
pysmurf_file (path): Path to pysmurf-config file used.
|
|
389
|
+
uxm_file (path): Path to uxm file
|
|
390
|
+
sys (dict):
|
|
391
|
+
System configuration dictionary, generated from the sys_config.yml
|
|
392
|
+
file or ``--sys-file`` command line argument.
|
|
393
|
+
dev (dict):
|
|
394
|
+
Device configuration dictionary, generated from the device_config
|
|
395
|
+
file specified in the sys_config or specified by the ``--dev-file``
|
|
396
|
+
command line argument
|
|
397
|
+
"""
|
|
398
|
+
def __init__(self, slot=None, sys_file=None, dev_file=None,
|
|
399
|
+
pysmurf_file=None, uxm_file=None):
|
|
400
|
+
self.sys_file = sys_file
|
|
401
|
+
self.dev_file = dev_file
|
|
402
|
+
self.pysmurf_file = pysmurf_file
|
|
403
|
+
self.uxm_file = uxm_file
|
|
404
|
+
self.slot = slot
|
|
405
|
+
self.sys = None
|
|
406
|
+
self.dev: DeviceConfig = None
|
|
407
|
+
self.dump = None # Whether config files should be dumped
|
|
408
|
+
|
|
409
|
+
self.S = None
|
|
410
|
+
|
|
411
|
+
self._parser: argparse.ArgumentParser = None
|
|
412
|
+
self._argparse_args = None
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def parse_args(self, parser=None, args=None):
|
|
416
|
+
"""
|
|
417
|
+
Parses command line arguments along with det_config arguments and
|
|
418
|
+
loads the correct configuration. See ``load_config`` for how config
|
|
419
|
+
files are determined.
|
|
420
|
+
|
|
421
|
+
Args:
|
|
422
|
+
parser (argparse.ArgumentParser, optional):
|
|
423
|
+
custom argparse parser to parse args with. If not specified,
|
|
424
|
+
will create its own.
|
|
425
|
+
args (list, optional):
|
|
426
|
+
List of command line arguments to parse.
|
|
427
|
+
Defaults to the command line args.
|
|
428
|
+
"""
|
|
429
|
+
self._argparse_args = args
|
|
430
|
+
parser = make_parser(parser)
|
|
431
|
+
self._parser = parser
|
|
432
|
+
|
|
433
|
+
args = parser.parse_args(args=args)
|
|
434
|
+
|
|
435
|
+
if args.sys_file is None:
|
|
436
|
+
args.sys_file = self.sys_file
|
|
437
|
+
if args.pysmurf_file is None:
|
|
438
|
+
args.pysmurf_file = self.pysmurf_file
|
|
439
|
+
if args.dev_file is None:
|
|
440
|
+
args.dev_file = self.dev_file
|
|
441
|
+
if args.uxm_file is None:
|
|
442
|
+
args.uxm_file = self.uxm_file
|
|
443
|
+
|
|
444
|
+
self.load_config_files(
|
|
445
|
+
slot=args.slot, sys_file=args.sys_file, dev_file=args.dev_file,
|
|
446
|
+
pysmurf_file=args.pysmurf_file, uxm_file=args.uxm_file
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
return args
|
|
450
|
+
|
|
451
|
+
def load_config_files(self, slot=None, sys_file=None, dev_file=None,
|
|
452
|
+
pysmurf_file=None, uxm_file=None):
|
|
453
|
+
"""
|
|
454
|
+
Loads configuration files. If arguments are not specified, sensible
|
|
455
|
+
defaults will be chosen.
|
|
456
|
+
|
|
457
|
+
Args:
|
|
458
|
+
slot (int, optional):
|
|
459
|
+
pysmurf slot number. If None and there is only a single slot in
|
|
460
|
+
the sys_file, it will use that slot. Otherwise, it will throw
|
|
461
|
+
and error.
|
|
462
|
+
sys_file (path, optional):
|
|
463
|
+
Path to sys config file. If None, defaults to
|
|
464
|
+
$SMURF_CONFIG_DIR/sys_config.yml
|
|
465
|
+
dev_file (path, optional):
|
|
466
|
+
Path to the Device file. If None, defaults to the device file
|
|
467
|
+
specified in the sys-config file (device_configs[slot-2]).
|
|
468
|
+
pysmurf_file (path, optional):
|
|
469
|
+
Path to pysmurf config file. If None, defaults to the file
|
|
470
|
+
specified in the sys-config (pysmurf_configs[slot-2]).
|
|
471
|
+
uxm_file (path, optional):
|
|
472
|
+
Path to uxm file
|
|
473
|
+
"""
|
|
474
|
+
# This should be the same for every smurf-srv
|
|
475
|
+
if 'SMURF_CONFIG_DIR' in os.environ:
|
|
476
|
+
cfg_dir = os.environ['SMURF_CONFIG_DIR']
|
|
477
|
+
elif 'OCS_CONFIG_DIR' in os.environ:
|
|
478
|
+
cfg_dir = os.environ['OCS_CONFIG_DIR']
|
|
479
|
+
else:
|
|
480
|
+
raise ValueError(
|
|
481
|
+
"SMURF_CONFIG_DIR or OCS_CONFIG_DIR must be set in the environment"
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
if sys_file is None:
|
|
485
|
+
self.sys_file = os.path.join(cfg_dir, 'sys_config.yml')
|
|
486
|
+
else:
|
|
487
|
+
self.sys_file = sys_file
|
|
488
|
+
|
|
489
|
+
# Load system settings
|
|
490
|
+
with open(self.sys_file) as f:
|
|
491
|
+
self.sys = yaml.safe_load(f)
|
|
492
|
+
|
|
493
|
+
if slot is not None:
|
|
494
|
+
self.slot = slot
|
|
495
|
+
elif len(self.sys['slot_order']) == 1:
|
|
496
|
+
self.slot = self.sys['slot_order'][0]
|
|
497
|
+
else:
|
|
498
|
+
raise ValueError(
|
|
499
|
+
"Slot could not be automatically determined from sys_config "
|
|
500
|
+
"file! Must specify slot directly from command line with "
|
|
501
|
+
"--slot argument. "
|
|
502
|
+
f"Available slots are {self.sys['slot_order']}."
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
slot_cfg = self.sys['slots'][f'SLOT[{self.slot}]']
|
|
506
|
+
self.stream_id = slot_cfg['stream_id']
|
|
507
|
+
|
|
508
|
+
if dev_file is None:
|
|
509
|
+
self.dev_file = os.path.abspath(os.path.expandvars(slot_cfg['device_config']))
|
|
510
|
+
else:
|
|
511
|
+
self.dev_file = os.path.abspath(os.path.expandvars(dev_file))
|
|
512
|
+
self.dev = DeviceConfig.from_yaml(self.dev_file)
|
|
513
|
+
|
|
514
|
+
# Gets the pysmurf config file
|
|
515
|
+
if pysmurf_file is None:
|
|
516
|
+
self.pysmurf_file = os.path.expandvars(slot_cfg['pysmurf_config'])
|
|
517
|
+
else:
|
|
518
|
+
self.pysmurf_file = pysmurf_file
|
|
519
|
+
|
|
520
|
+
if uxm_file is None:
|
|
521
|
+
if 'uxm_file' in slot_cfg:
|
|
522
|
+
self.uxm_file = os.path.expandvars(slot_cfg['uxm_file'])
|
|
523
|
+
else:
|
|
524
|
+
self.uxm_file = uxm_file
|
|
525
|
+
if self.uxm_file is not None:
|
|
526
|
+
with open(self.uxm_file) as f:
|
|
527
|
+
self.uxm = yaml.safe_load(f)
|
|
528
|
+
else:
|
|
529
|
+
self.uxm = None
|
|
530
|
+
|
|
531
|
+
def dump_configs(self, output_dir=None, clobber=False, dump_rogue_tree=False):
|
|
532
|
+
"""
|
|
533
|
+
Dumps any config information to an output directory
|
|
534
|
+
|
|
535
|
+
Args:
|
|
536
|
+
output_dir (path): Directory location to put config files.
|
|
537
|
+
dump_rogue_truee : bool
|
|
538
|
+
If True, will dump the pysmurf rogue tree to the configs dir.
|
|
539
|
+
|
|
540
|
+
Returns:
|
|
541
|
+
run_out (path): path to output run file
|
|
542
|
+
sys_out (path): path to output sys file
|
|
543
|
+
dev_out (path): path to output device file.
|
|
544
|
+
uxm_out (path): path to uxm file
|
|
545
|
+
"""
|
|
546
|
+
if output_dir is None:
|
|
547
|
+
if not self.S:
|
|
548
|
+
raise ValueError("output_dir cannot be None in offline mode")
|
|
549
|
+
output_dir = os.path.join(
|
|
550
|
+
self.S.base_dir, self.S.date, self.stream_id,
|
|
551
|
+
self.S.name, 'config', self.S.get_timestamp(),
|
|
552
|
+
)
|
|
553
|
+
print(f"Dumping sodetlib configs to {output_dir}")
|
|
554
|
+
if not os.path.exists(output_dir):
|
|
555
|
+
os.makedirs(output_dir)
|
|
556
|
+
|
|
557
|
+
# Manually set publisher action because set_action decorator won't
|
|
558
|
+
# work without S being the first function argument.
|
|
559
|
+
if self.S:
|
|
560
|
+
start_action = self.S.pub._action
|
|
561
|
+
start_action_ts = self.S.pub._action_ts
|
|
562
|
+
|
|
563
|
+
try:
|
|
564
|
+
outfiles = {}
|
|
565
|
+
if self.S:
|
|
566
|
+
self.S.pub._action = "config"
|
|
567
|
+
self.S.pub._action_ts = self.S.get_timestamp()
|
|
568
|
+
|
|
569
|
+
# Dump run info
|
|
570
|
+
run_info = YamlReps.Odict([
|
|
571
|
+
('sys_file', self.sys_file),
|
|
572
|
+
('dev_file', self.dev_file),
|
|
573
|
+
('pysmurf_file', self.pysmurf_file),
|
|
574
|
+
('slot', self.slot),
|
|
575
|
+
('cwd', os.getcwd()),
|
|
576
|
+
('argv', ' '.join(sys.argv)),
|
|
577
|
+
('time', int(time.time())),
|
|
578
|
+
])
|
|
579
|
+
if self._argparse_args is not None:
|
|
580
|
+
run_info['cfg_args'] = self._argparse_args
|
|
581
|
+
run_out = os.path.abspath(os.path.join(output_dir, 'run_info.yml'))
|
|
582
|
+
outfiles['run'] = run_out
|
|
583
|
+
with open(run_out, 'w') as f:
|
|
584
|
+
yaml.dump(run_info, f)
|
|
585
|
+
if self.S:
|
|
586
|
+
self.S.pub.register_file(run_out, 'config', format='yaml')
|
|
587
|
+
|
|
588
|
+
# Dump sys file
|
|
589
|
+
sys_out = os.path.abspath(os.path.join(output_dir,
|
|
590
|
+
'sys_config.yml'))
|
|
591
|
+
outfiles['sys'] = sys_out
|
|
592
|
+
with open(sys_out, 'w') as f:
|
|
593
|
+
yaml.dump(self.sys, f)
|
|
594
|
+
if self.S:
|
|
595
|
+
self.S.pub.register_file(sys_out, 'config', format='yaml')
|
|
596
|
+
|
|
597
|
+
# Dump device file
|
|
598
|
+
dev_out = os.path.abspath(os.path.join(output_dir, 'dev_cfg.yml'))
|
|
599
|
+
outfiles['dev'] = dev_out
|
|
600
|
+
self.dev.dump(dev_out, clobber=clobber)
|
|
601
|
+
if self.S:
|
|
602
|
+
self.S.pub.register_file(dev_out, 'config', format='yaml')
|
|
603
|
+
|
|
604
|
+
# Copy pysmurf file
|
|
605
|
+
pysmurf_out = os.path.join(output_dir, 'pysmurf_cfg.yml')
|
|
606
|
+
pysmurf_out = os.path.abspath(pysmurf_out)
|
|
607
|
+
outfiles['pysmurf'] = pysmurf_out
|
|
608
|
+
shutil.copy(self.pysmurf_file, pysmurf_out)
|
|
609
|
+
if self.S:
|
|
610
|
+
self.S.pub.register_file(pysmurf_out, 'config', format='yaml')
|
|
611
|
+
|
|
612
|
+
# Dump uxm file
|
|
613
|
+
if self.uxm is not None:
|
|
614
|
+
uxm_out = os.path.abspath(os.path.join(output_dir, 'uxm.yml'))
|
|
615
|
+
outfiles['uxm'] = uxm_out
|
|
616
|
+
with open(uxm_out, 'w') as f:
|
|
617
|
+
yaml.dump(self.uxm, f)
|
|
618
|
+
if self.S:
|
|
619
|
+
self.S.pub.register_file(uxm_out, 'config', format='yaml')
|
|
620
|
+
|
|
621
|
+
if dump_rogue_tree:
|
|
622
|
+
print("Dumping state")
|
|
623
|
+
state_file = os.path.abspath(os.path.join(output_dir,
|
|
624
|
+
'rogue_state.yaml'))
|
|
625
|
+
outfiles.append(state_file)
|
|
626
|
+
self.S.save_state(state_file)
|
|
627
|
+
finally:
|
|
628
|
+
# Restore publisher action so it doesn't mess up ongoing actions
|
|
629
|
+
if self.S:
|
|
630
|
+
self.S.pub._action = start_action
|
|
631
|
+
self.S.pub._action_ts = start_action_ts
|
|
632
|
+
|
|
633
|
+
return outfiles
|
|
634
|
+
|
|
635
|
+
def get_smurf_control(self, offline=False, epics_root=None,
|
|
636
|
+
smurfpub_id=None, make_logfile=False, setup=False,
|
|
637
|
+
dump_configs=None, config_dir=None,
|
|
638
|
+
apply_dev_configs=False, load_device_tune=True,
|
|
639
|
+
**pysmurf_kwargs):
|
|
640
|
+
"""
|
|
641
|
+
Creates pysmurf instance based off of configuration parameters.
|
|
642
|
+
If not specified as keyword arguments ``epics_root`` and ``smurf_pub``
|
|
643
|
+
will be created based on the slot and crate id's.
|
|
644
|
+
|
|
645
|
+
Args:
|
|
646
|
+
offline (bool):
|
|
647
|
+
Whether to start pysmurf in offline mode. Defaults to False
|
|
648
|
+
epics_root (str, optional):
|
|
649
|
+
Pysmurf epics root. If none, it will be set to
|
|
650
|
+
``smurf_server_s<slot>``.
|
|
651
|
+
smurfpub_id (str, optional):
|
|
652
|
+
Pysmurf publisher ID. If None, will default to
|
|
653
|
+
crate<crate_id>_slot<slot>.
|
|
654
|
+
make_logfile (bool):
|
|
655
|
+
Whether pysmurf should write logs to a file (True) or
|
|
656
|
+
stdout(False). Defaults to stdout.
|
|
657
|
+
setup (bool):
|
|
658
|
+
Whether pysmurf should run a full setup. Defaults to False.
|
|
659
|
+
dump_configs (bool):
|
|
660
|
+
Whether all configuration settings should be dumped to pysmurf
|
|
661
|
+
data directory. Defaults to True.
|
|
662
|
+
**pysmurf_kwargs:
|
|
663
|
+
Any additional arguments to be passed to pysmurf initialization.
|
|
664
|
+
|
|
665
|
+
"""
|
|
666
|
+
import pysmurf.client
|
|
667
|
+
|
|
668
|
+
slot_cfg = self.sys['slots'][f'SLOT[{self.slot}]']
|
|
669
|
+
if epics_root is None:
|
|
670
|
+
epics_root = f'smurf_server_s{self.slot}'
|
|
671
|
+
if smurfpub_id is None:
|
|
672
|
+
smurfpub_id = self.stream_id
|
|
673
|
+
if dump_configs is None:
|
|
674
|
+
dump_configs = self.dump
|
|
675
|
+
|
|
676
|
+
# Pysmurf publisher will check this to determine publisher id.
|
|
677
|
+
os.environ['SMURFPUB_ID'] = smurfpub_id
|
|
678
|
+
|
|
679
|
+
if offline:
|
|
680
|
+
S = pysmurf.client.SmurfControl(offline=True)
|
|
681
|
+
else:
|
|
682
|
+
S = pysmurf.client.SmurfControl(
|
|
683
|
+
epics_root=epics_root, cfg_file=self.pysmurf_file, setup=setup,
|
|
684
|
+
make_logfile=make_logfile, data_path_id=smurfpub_id,
|
|
685
|
+
**pysmurf_kwargs)
|
|
686
|
+
self.S = S
|
|
687
|
+
# Lets just stash this in pysmurf...
|
|
688
|
+
S._sodetlib_cfg = self
|
|
689
|
+
|
|
690
|
+
# Dump config outputs
|
|
691
|
+
if dump_configs:
|
|
692
|
+
if config_dir is None:
|
|
693
|
+
if not offline:
|
|
694
|
+
config_dir = os.path.join(
|
|
695
|
+
S.base_dir, S.date, smurfpub_id, S.name, 'config',
|
|
696
|
+
S.get_timestamp(),
|
|
697
|
+
)
|
|
698
|
+
|
|
699
|
+
else:
|
|
700
|
+
config_dir = os.path.join('config', S.get_timestamp())
|
|
701
|
+
print("Warning! Being run in offline mode with no config "
|
|
702
|
+
f"directory specified! Writing to {config_dir}")
|
|
703
|
+
self.dump_configs(config_dir)
|
|
704
|
+
|
|
705
|
+
if apply_dev_configs:
|
|
706
|
+
print("Applying device cfg parameters...")
|
|
707
|
+
self.dev.apply_to_pysmurf_instance(S, load_tune=load_device_tune)
|
|
708
|
+
|
|
709
|
+
return S
|