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
|
@@ -0,0 +1,783 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import time
|
|
3
|
+
import sodetlib as sdl
|
|
4
|
+
from sodetlib.operations import tracking
|
|
5
|
+
|
|
6
|
+
import matplotlib.pyplot as plt
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def find_gate_voltage(S, target_Id, amp_name, vg_min=-2.0, vg_max=0,
|
|
10
|
+
max_iter=50, wait_time=0.5, id_tolerance=0.2):
|
|
11
|
+
"""
|
|
12
|
+
Scans through bias voltage for hemt or 50K amplifier to get the correct
|
|
13
|
+
gate voltage for a target current.
|
|
14
|
+
|
|
15
|
+
Args
|
|
16
|
+
-----
|
|
17
|
+
S: pysmurf.client.SmurfControl
|
|
18
|
+
PySmurf control object
|
|
19
|
+
target_Id: float
|
|
20
|
+
Target amplifier current
|
|
21
|
+
vg_min: float
|
|
22
|
+
Minimum allowable gate voltage
|
|
23
|
+
vg_max: float
|
|
24
|
+
Maximum allowable gate voltage
|
|
25
|
+
amp_name: str
|
|
26
|
+
Name of amplifier. Must be one of ['hemt', 'hemt1', 'hemt2', '50k', '50k1', '50k2'].
|
|
27
|
+
max_iter: int, optional
|
|
28
|
+
Maximum number of iterations to find voltage. Defaults to 30.
|
|
29
|
+
wait_time : float
|
|
30
|
+
Time to wait after setting the voltage at each step. Defaults
|
|
31
|
+
to 0.5 sec
|
|
32
|
+
id_tolerance : float
|
|
33
|
+
Max difference between target drain current and actual drain currents
|
|
34
|
+
for this to be considered success (mA). Defaults to 0.2 mA.
|
|
35
|
+
|
|
36
|
+
Returns
|
|
37
|
+
--------
|
|
38
|
+
success : bool
|
|
39
|
+
Returns a boolean signaling whether voltage scan has been successful.
|
|
40
|
+
The set voltages can be read with S.get_amplifier_biases().
|
|
41
|
+
"""
|
|
42
|
+
all_amps = S.C.list_of_c02_and_c04_amps
|
|
43
|
+
if amp_name not in all_amps:
|
|
44
|
+
raise ValueError(f"amp_name must be one of {all_amps}")
|
|
45
|
+
|
|
46
|
+
for _ in range(max_iter):
|
|
47
|
+
amp_biases = S.get_amplifier_biases()
|
|
48
|
+
Vg = amp_biases[f"{amp_name}_gate_volt"]
|
|
49
|
+
Id = amp_biases[f"{amp_name}_drain_current"]
|
|
50
|
+
delta = target_Id - Id
|
|
51
|
+
|
|
52
|
+
S.log(delta)
|
|
53
|
+
# Check if Id is within tolerance
|
|
54
|
+
if np.abs(delta) < id_tolerance:
|
|
55
|
+
return True
|
|
56
|
+
|
|
57
|
+
if 'hemt' in amp_name:
|
|
58
|
+
step = np.sign(delta) * (0.1 if np.abs(delta) > 1.5 else 0.01)
|
|
59
|
+
else:
|
|
60
|
+
step = np.sign(delta) * (0.01 if np.abs(delta) > 1.5 else 0.001)
|
|
61
|
+
|
|
62
|
+
Vg_next = Vg + step
|
|
63
|
+
if not (vg_min < Vg_next < vg_max):
|
|
64
|
+
S.log(f"Gate voltage adjustment would go out of range ({vg_min}, {vg_max}). "
|
|
65
|
+
f"Unable to change {amp_name}_drain_current to desired value", False)
|
|
66
|
+
return False
|
|
67
|
+
|
|
68
|
+
if 'hemt' in amp_name:
|
|
69
|
+
S.set_amp_gate_voltage(amp_name, Vg_next, override=True)
|
|
70
|
+
else:
|
|
71
|
+
S.set_amp_gate_voltage(amp_name, Vg_next, override=True)
|
|
72
|
+
|
|
73
|
+
time.sleep(wait_time)
|
|
74
|
+
|
|
75
|
+
S.log(f"Max allowed Vg iterations ({max_iter}) has been reached. "
|
|
76
|
+
f"Unable to get target drain current for {amp_name}.", False)
|
|
77
|
+
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
@sdl.set_action()
|
|
81
|
+
def setup_amps(S, cfg, update_cfg=True, enable_300K_LNA=True):
|
|
82
|
+
"""
|
|
83
|
+
Initial setup for 50k and hemt amplifiers. For C04/C05 cryocards, will first
|
|
84
|
+
check if the drain voltages are set. Then checks if drain
|
|
85
|
+
currents are in range, and if not will scan gate voltage to find one that
|
|
86
|
+
hits the target current. Will update the device cfg if successful.
|
|
87
|
+
|
|
88
|
+
The following parameters can be modified in the device cfg, where {amp}
|
|
89
|
+
is one of ['hemt', 'hemt1', 'hemt2', '50k', '50k1', '50k2']:
|
|
90
|
+
|
|
91
|
+
exp:
|
|
92
|
+
- amp_enable_wait_time (float): Seconds to wait after enabling amps
|
|
93
|
+
before scanning gate voltages
|
|
94
|
+
- amp_{amp}_drain_current (float): Target drain current (mA)
|
|
95
|
+
- amp_{amp}_drain_current_tolerance (float): Tolerance for drain current (mA)
|
|
96
|
+
- amp_{amp}_drain_volt (float) : Drain voltage (V). C04/C05 cryocards only.
|
|
97
|
+
|
|
98
|
+
Args
|
|
99
|
+
-----
|
|
100
|
+
S : SmurfControl
|
|
101
|
+
Pysmurf instance
|
|
102
|
+
cfg : DetConfig
|
|
103
|
+
DetConfig instance
|
|
104
|
+
update_cfg : bool
|
|
105
|
+
If true, will update the device cfg and save the file.
|
|
106
|
+
enable_300K_LNA:
|
|
107
|
+
If true, will turn on the 300K LNAs.
|
|
108
|
+
"""
|
|
109
|
+
sdl.pub_ocs_log(S, "Starting setup_amps")
|
|
110
|
+
|
|
111
|
+
exp = cfg.dev.exp
|
|
112
|
+
|
|
113
|
+
# Determine cryocard rev
|
|
114
|
+
major, minor, patch = S.C.get_fw_version()
|
|
115
|
+
if major == 4:
|
|
116
|
+
cc_rev = 'c04'
|
|
117
|
+
amp_list = S.C.list_of_c04_amps
|
|
118
|
+
elif major == 0:
|
|
119
|
+
if sum((major,minor,patch)) == 0:
|
|
120
|
+
raise ValueError("Error communicatin with cryocard; "
|
|
121
|
+
+ "is it connected?")
|
|
122
|
+
else:
|
|
123
|
+
raise ValueError(f"Unrecognized cryocard firmware version "
|
|
124
|
+
+ "({major},{minor},{patch}).")
|
|
125
|
+
else:
|
|
126
|
+
cc_rev = 'c02'
|
|
127
|
+
amp_list = S.C.list_of_c02_amps
|
|
128
|
+
S.C.write_ps_en(0b11)
|
|
129
|
+
time.sleep(exp['amp_enable_wait_time'])
|
|
130
|
+
if S.C.read_ps_en() != 3:
|
|
131
|
+
raise ValueError("Could not enable amps.")
|
|
132
|
+
|
|
133
|
+
amp_list = list(set(amp_list) & set(exp['amps_to_bias']))
|
|
134
|
+
if len(amp_list) == 0:
|
|
135
|
+
raise ValueError(
|
|
136
|
+
f"exp['amps_to_bias']: {exp['amps_to_bias']} contains no valid amps."
|
|
137
|
+
)
|
|
138
|
+
# Data to be passed back to the ocs-pysmurf-controller clients
|
|
139
|
+
summary = {'success': False}
|
|
140
|
+
for amp in amp_list:
|
|
141
|
+
summary[f'{amp}_gate_volt'] = None
|
|
142
|
+
summary[f'{amp}_drain_current'] = None
|
|
143
|
+
summary[amp + '_enable'] = None
|
|
144
|
+
if cc_rev == 'c04':
|
|
145
|
+
summary[f'{amp}_drain_volt'] = None
|
|
146
|
+
|
|
147
|
+
amp_biases = S.get_amplifier_biases()
|
|
148
|
+
|
|
149
|
+
# For C04, first check drain voltages
|
|
150
|
+
if cc_rev == 'c04':
|
|
151
|
+
for amp in amp_list:
|
|
152
|
+
Vd = amp_biases[f"{amp}_drain_volt"]
|
|
153
|
+
if Vd != exp[f"amp_{amp}_drain_volt"]:
|
|
154
|
+
S.set_amp_drain_voltage(amp, exp[f"amp_{amp}_drain_volt"])
|
|
155
|
+
|
|
156
|
+
# Check drain currents / scan gate voltages
|
|
157
|
+
delta_drain_currents = dict()
|
|
158
|
+
for amp in amp_list:
|
|
159
|
+
delta_Id = np.abs(amp_biases[f"{amp}_drain_current"] - exp[f"amp_{amp}_drain_current"])
|
|
160
|
+
if delta_Id > exp[f'amp_{amp}_drain_current_tolerance']:
|
|
161
|
+
S.log(f"{amp} current not within tolerance, scanning for correct gate voltage")
|
|
162
|
+
S.set_amp_gate_voltage(amp, exp[f'amp_{amp}_init_gate_volt'],override=True)
|
|
163
|
+
success = find_gate_voltage(
|
|
164
|
+
S, exp[f"amp_{amp}_drain_current"], amp, wait_time=exp['amp_step_wait_time'],
|
|
165
|
+
id_tolerance=exp[f'amp_{amp}_drain_current_tolerance']
|
|
166
|
+
)
|
|
167
|
+
if not success:
|
|
168
|
+
sdl.pub_ocs_log(S, f"Failed determining {amp} gate voltage")
|
|
169
|
+
sdl.set_session_data(S, 'setup_amps_summary', summary)
|
|
170
|
+
S.C.write_ps_en(0)
|
|
171
|
+
return False, summary
|
|
172
|
+
# Turn on 300K LNAs
|
|
173
|
+
if enable_300K_LNA:
|
|
174
|
+
S.C.write_optical(0b11)
|
|
175
|
+
|
|
176
|
+
# Update device cfg
|
|
177
|
+
biases = S.get_amplifier_biases()
|
|
178
|
+
if update_cfg:
|
|
179
|
+
for amp in amp_list:
|
|
180
|
+
cfg.dev.update_experiment({f'amp_{amp}_gate_volt': biases[f'{amp}_gate_volt']},
|
|
181
|
+
update_file=True)
|
|
182
|
+
|
|
183
|
+
summary = {'success': True, **biases}
|
|
184
|
+
sdl.set_session_data(S, 'setup_amps_summary', summary)
|
|
185
|
+
return True, summary
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@sdl.set_action()
|
|
189
|
+
def setup_phase_delay(S, cfg, bands, update_cfg=True):
|
|
190
|
+
"""
|
|
191
|
+
Runs estimate phase delay and updates the device cfg with the results.
|
|
192
|
+
This will run with the current set of attenuation values.
|
|
193
|
+
|
|
194
|
+
Args
|
|
195
|
+
-----
|
|
196
|
+
S : SmurfControl
|
|
197
|
+
Pysmurf instance
|
|
198
|
+
cfg : DetConfig
|
|
199
|
+
DetConfig instance
|
|
200
|
+
uc_att : int
|
|
201
|
+
UC atten to use for phase-delay estimation
|
|
202
|
+
dc_att : int
|
|
203
|
+
DC atten to use for phase-delay estimation
|
|
204
|
+
update_cfg : bool
|
|
205
|
+
If true, will update the device cfg and save the file.
|
|
206
|
+
"""
|
|
207
|
+
sdl.pub_ocs_log(S, f"Estimating phase delay for bands {bands}")
|
|
208
|
+
|
|
209
|
+
summary = {
|
|
210
|
+
'bands': [],
|
|
211
|
+
'band_delay_us': [],
|
|
212
|
+
}
|
|
213
|
+
for b in bands:
|
|
214
|
+
summary['bands'].append(int(b))
|
|
215
|
+
band_delay_us, _ = S.estimate_phase_delay(b, make_plot=True, show_plot=False)
|
|
216
|
+
band_delay_us = float(band_delay_us)
|
|
217
|
+
summary['band_delay_us'].append(band_delay_us)
|
|
218
|
+
if update_cfg:
|
|
219
|
+
cfg.dev.bands[b].update({
|
|
220
|
+
'band_delay_us': band_delay_us
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
if update_cfg:
|
|
224
|
+
cfg.dev.update_file()
|
|
225
|
+
|
|
226
|
+
sdl.set_session_data(S, 'setup_phase_delay', summary)
|
|
227
|
+
return True, summary
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def estimate_uc_dc_atten(S, cfg, band, update_cfg=True, tone_power=None):
|
|
231
|
+
"""
|
|
232
|
+
Provides an initial estimation of uc and dc attenuation for a band in order
|
|
233
|
+
to get the band response at a particular frequency to be within a certain
|
|
234
|
+
range. The goal of this function is to choose attenuations such that the
|
|
235
|
+
ADC isn't saturated, and the response is large enough to find resonators.
|
|
236
|
+
|
|
237
|
+
For simplicity, this searches over the parameter space where uc and dc
|
|
238
|
+
attens are equal, instead of searching over the full 2d space.
|
|
239
|
+
|
|
240
|
+
For further optimization of attens with respect to median white noise, run
|
|
241
|
+
the ``optimize_attens`` function from the
|
|
242
|
+
``sodetlib/operations/optimize.py``
|
|
243
|
+
module.
|
|
244
|
+
|
|
245
|
+
The following parameters can be modified in the device cfg:
|
|
246
|
+
|
|
247
|
+
bands:
|
|
248
|
+
- tone_power (int): Tone power to use for atten estimation
|
|
249
|
+
|
|
250
|
+
Args
|
|
251
|
+
-----
|
|
252
|
+
S : SmurfControl
|
|
253
|
+
Pysmurf instance
|
|
254
|
+
band : int
|
|
255
|
+
Band to estimate attens for
|
|
256
|
+
update_cfg : bool
|
|
257
|
+
If true, will update the device cfg and save the file. """
|
|
258
|
+
att, step = 15, 7
|
|
259
|
+
sb = S.get_closest_subband(-200, band)
|
|
260
|
+
|
|
261
|
+
resp_range = (0.1, 0.4)
|
|
262
|
+
# Just look at 5 subbands after specified freq.
|
|
263
|
+
sbs = np.arange(sb, sb + 5)
|
|
264
|
+
sbs = np.arange(250, 255)
|
|
265
|
+
nread = 2
|
|
266
|
+
|
|
267
|
+
success = False
|
|
268
|
+
S.log(f"Estimating attens for band {band}")
|
|
269
|
+
|
|
270
|
+
if tone_power is None:
|
|
271
|
+
tone_power = cfg.dev.bands[band]['tone_power']
|
|
272
|
+
|
|
273
|
+
while True:
|
|
274
|
+
S.set_att_uc(band, att)
|
|
275
|
+
S.set_att_dc(band, att)
|
|
276
|
+
|
|
277
|
+
S.log(f'tone_power: {tone_power}')
|
|
278
|
+
_, resp = S.full_band_ampl_sweep(band, sbs, cfg.dev.bands[band]['tone_power'], nread)
|
|
279
|
+
max_resp = np.max(np.abs(resp))
|
|
280
|
+
S.log(f"att: {att}, max_resp: {max_resp}")
|
|
281
|
+
|
|
282
|
+
if resp_range[0] < max_resp < resp_range[1]:
|
|
283
|
+
S.log(f"Estimated atten: {att}")
|
|
284
|
+
success = True
|
|
285
|
+
break
|
|
286
|
+
|
|
287
|
+
if max_resp < resp_range[0]:
|
|
288
|
+
if att == 0:
|
|
289
|
+
S.log(f"Cannot achieve resp in range {resp_range}! Try increasing tone power")
|
|
290
|
+
success = False
|
|
291
|
+
break
|
|
292
|
+
att -= step
|
|
293
|
+
|
|
294
|
+
elif max_resp > resp_range[1]:
|
|
295
|
+
if att == 30:
|
|
296
|
+
S.log(f"Cannot achieve resp in range {resp_range}! Try decreasing tone power")
|
|
297
|
+
success = False
|
|
298
|
+
break
|
|
299
|
+
att += step
|
|
300
|
+
step = int(np.ceil(step / 2))
|
|
301
|
+
|
|
302
|
+
if success and update_cfg:
|
|
303
|
+
cfg.dev.update_band(band, {
|
|
304
|
+
'uc_att': att,
|
|
305
|
+
'dc_att': att,
|
|
306
|
+
}, update_file=True)
|
|
307
|
+
|
|
308
|
+
return success
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
@sdl.set_action()
|
|
312
|
+
def setup_tune(S, cfg, bands, show_plots=False, update_cfg=True):
|
|
313
|
+
"""
|
|
314
|
+
Find freq, setup notches, and serial gradient descent and eta scan
|
|
315
|
+
|
|
316
|
+
The following parameters can be modified in the device cfg:
|
|
317
|
+
|
|
318
|
+
exp:
|
|
319
|
+
- res_amp_cut (float): Amplitude cut for peak-finding in find-freq
|
|
320
|
+
- res_grad_cut (float): Gradient cut for peak-finding in find-freq
|
|
321
|
+
bands:
|
|
322
|
+
- tone_power (int): Tone power to use for atten estimation
|
|
323
|
+
|
|
324
|
+
Args
|
|
325
|
+
-----
|
|
326
|
+
S : SmurfControl
|
|
327
|
+
Pysmurf instance
|
|
328
|
+
cfg : DetConfig
|
|
329
|
+
DetConfig instance
|
|
330
|
+
show_plots : bool
|
|
331
|
+
If true, will show find_freq plots. Defaults to False
|
|
332
|
+
update_cfg : bool
|
|
333
|
+
If true, will update the device cfg and save the file.
|
|
334
|
+
"""
|
|
335
|
+
bands = np.atleast_1d(bands)
|
|
336
|
+
sdl.pub_ocs_log(S, f"Starting setup_tune for bands {bands}")
|
|
337
|
+
|
|
338
|
+
exp = cfg.dev.exp
|
|
339
|
+
|
|
340
|
+
summary = {}
|
|
341
|
+
|
|
342
|
+
for band in bands:
|
|
343
|
+
sdl.pub_ocs_log(S, f"Find Freq: band {band}")
|
|
344
|
+
bcfg = cfg.dev.bands[band]
|
|
345
|
+
S.find_freq(band, tone_power=bcfg['tone_power'], make_plot=True,
|
|
346
|
+
save_plot=True, show_plot=show_plots,
|
|
347
|
+
amp_cut=exp['res_amp_cut'],
|
|
348
|
+
grad_cut=exp['res_grad_cut'])
|
|
349
|
+
|
|
350
|
+
for band in bands:
|
|
351
|
+
bcfg = cfg.dev.bands[band]
|
|
352
|
+
sdl.pub_ocs_log(S, f"Setup Notches: band {band}")
|
|
353
|
+
S.setup_notches(band, tone_power=bcfg['tone_power'], new_master_assignment=True)
|
|
354
|
+
|
|
355
|
+
for band in bands:
|
|
356
|
+
sdl.pub_ocs_log(S, f"Serial grad descent and eta scan: band {band}")
|
|
357
|
+
S.run_serial_gradient_descent(band)
|
|
358
|
+
S.run_serial_eta_scan(band)
|
|
359
|
+
|
|
360
|
+
if update_cfg:
|
|
361
|
+
cfg.dev.update_experiment({'tunefile': S.tune_file}, update_file=True)
|
|
362
|
+
|
|
363
|
+
return True, summary
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def _find_fixed_tone_freq(S, band, ntone, min_spacing, band_start=-250, band_end=250):
|
|
367
|
+
"""
|
|
368
|
+
Place fixed tones in the largest gaps between resonators.
|
|
369
|
+
|
|
370
|
+
Args
|
|
371
|
+
----
|
|
372
|
+
S : SmurfControl
|
|
373
|
+
Pysmurf instance
|
|
374
|
+
band : int
|
|
375
|
+
The band to populate with fixed tones.
|
|
376
|
+
ntone : int
|
|
377
|
+
The number of fixed tones to attempt to place in this band.
|
|
378
|
+
min_spacing : float
|
|
379
|
+
The minimum spacing in MHz between resonators to place a fixed tone in.
|
|
380
|
+
band_start : float
|
|
381
|
+
The lower edge of the band in MHz relative to band center.
|
|
382
|
+
band_end : float
|
|
383
|
+
The upper edge of the band in MHz relative to band center.
|
|
384
|
+
"""
|
|
385
|
+
|
|
386
|
+
# get resonator and channel frequencies
|
|
387
|
+
res_freq = np.sort([res["freq"] for res in S.freq_resp[band]["resonances"].values()])
|
|
388
|
+
|
|
389
|
+
# only consider central part of the band
|
|
390
|
+
band_center = S.get_band_center_mhz(band)
|
|
391
|
+
freq_low = band_center + band_start
|
|
392
|
+
freq_high = band_center + band_end
|
|
393
|
+
res_freq = res_freq[(res_freq > freq_low) & (res_freq < freq_high)]
|
|
394
|
+
|
|
395
|
+
# add the band edges
|
|
396
|
+
res_freq = np.concatenate(([freq_low], res_freq, [freq_high]))
|
|
397
|
+
|
|
398
|
+
# gaps between resonators
|
|
399
|
+
gaps = np.diff(res_freq)
|
|
400
|
+
|
|
401
|
+
# check there are enough for the number of tones
|
|
402
|
+
ntone_per_gap = np.floor(gaps / min_spacing)
|
|
403
|
+
num_gaps = ntone_per_gap.sum()
|
|
404
|
+
if num_gaps < ntone:
|
|
405
|
+
S.log(
|
|
406
|
+
f"Only {num_gaps} gaps greater than {min_spacing} MHz were found in band {band}. "
|
|
407
|
+
"That number of fixed tones will be set."
|
|
408
|
+
)
|
|
409
|
+
ntone = int(num_gaps)
|
|
410
|
+
if ntone == 0:
|
|
411
|
+
return np.array([])
|
|
412
|
+
gaps = gaps[ntone_per_gap > 0]
|
|
413
|
+
res_freq = res_freq[np.where(ntone_per_gap > 0)[0]] # just the left edge now
|
|
414
|
+
|
|
415
|
+
# distribute uniformly in available space, accounting for edge buffer
|
|
416
|
+
gaps_cum = np.cumsum(gaps - min_spacing) # can place a tone anywhere in this space
|
|
417
|
+
tones = np.linspace(0, gaps_cum[-1], ntone, endpoint=True)
|
|
418
|
+
tone_freqs = np.zeros_like(tones)
|
|
419
|
+
last_edge = -1 # allow for tone on the leading 0 edge
|
|
420
|
+
for i in range(gaps.size):
|
|
421
|
+
# identify which gap they are in
|
|
422
|
+
tones_in_gap = (tones > last_edge) & (tones <= gaps_cum[i])
|
|
423
|
+
# shift back to real frequency
|
|
424
|
+
tone_freqs[tones_in_gap] = (
|
|
425
|
+
tones[tones_in_gap] - last_edge # position relative to buffer
|
|
426
|
+
+ res_freq[i] + min_spacing / 2 # real edge in band + buffer
|
|
427
|
+
)
|
|
428
|
+
last_edge = gaps_cum[i]
|
|
429
|
+
|
|
430
|
+
return tone_freqs
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
@sdl.set_action()
|
|
434
|
+
def setup_fixed_tones(
|
|
435
|
+
S, cfg, bands=None, fixed_tones_per_band=8, min_gap=10, tone_power=10, update_cfg=True
|
|
436
|
+
):
|
|
437
|
+
"""
|
|
438
|
+
Identify gaps between resonators and attempt to place a number of fixed tones there.
|
|
439
|
+
These will be saved for every band in the device config. A tune should be available
|
|
440
|
+
before running this action.
|
|
441
|
+
|
|
442
|
+
Args
|
|
443
|
+
----
|
|
444
|
+
S : SmurfControl
|
|
445
|
+
Pysmurf instance
|
|
446
|
+
cfg : DetConfig
|
|
447
|
+
DetConfig instance
|
|
448
|
+
bands : list of int
|
|
449
|
+
The bands to set fixed tones in. Get from config by default.
|
|
450
|
+
fixed_tones_per_band : int
|
|
451
|
+
The number of tones to place in each band. Defaults to 8.
|
|
452
|
+
min_gap : float
|
|
453
|
+
The minimum gap size in order to place a fixed tone, in MHz. Defaults to 10.
|
|
454
|
+
tone_power : int
|
|
455
|
+
The tone power to use for the fixed tones. Defaults to 10.
|
|
456
|
+
update_cfg : bool
|
|
457
|
+
Whether to save the fixed tone configuration to the device config. Defaults to True.
|
|
458
|
+
|
|
459
|
+
Returns
|
|
460
|
+
-------
|
|
461
|
+
fixed_tones : dict
|
|
462
|
+
Fixed tones configuration, as saved to device config.
|
|
463
|
+
"""
|
|
464
|
+
|
|
465
|
+
if bands is None:
|
|
466
|
+
bands = cfg.dev.exp['active_bands']
|
|
467
|
+
|
|
468
|
+
# ensure we have a tune available
|
|
469
|
+
try:
|
|
470
|
+
_ = S.freq_resp[bands[0]]["resonances"]
|
|
471
|
+
except KeyError as e:
|
|
472
|
+
raise ValueError("No tune is available to set fixed tones from.") from e
|
|
473
|
+
|
|
474
|
+
# identify available frequencies
|
|
475
|
+
ft_freq = []
|
|
476
|
+
for b in bands:
|
|
477
|
+
ft_freq += list(_find_fixed_tone_freq(S, b, fixed_tones_per_band, min_gap))
|
|
478
|
+
|
|
479
|
+
# turn off any existing fixed tones
|
|
480
|
+
try:
|
|
481
|
+
channels = cfg.dev.bands[b]["fixed_tones"]["channels"]
|
|
482
|
+
amp = S.get_amplitude_scale_array(b)
|
|
483
|
+
amp[channels] = 0.0
|
|
484
|
+
S.set_amplitude_scale_array(b, amp)
|
|
485
|
+
S.log(f"Turned off current fixed tones on band {b}.")
|
|
486
|
+
except KeyError:
|
|
487
|
+
pass
|
|
488
|
+
|
|
489
|
+
# set the fixed tones
|
|
490
|
+
tunefile = cfg.dev.exp.get("tunefile", None)
|
|
491
|
+
fixed_tones = {
|
|
492
|
+
b: {
|
|
493
|
+
"enabled": False,
|
|
494
|
+
"freq_offsets": [],
|
|
495
|
+
"channels": [],
|
|
496
|
+
"tone_power": tone_power,
|
|
497
|
+
"tunefile": tunefile,
|
|
498
|
+
}
|
|
499
|
+
for b in bands
|
|
500
|
+
}
|
|
501
|
+
for fr in ft_freq:
|
|
502
|
+
# this function sets the tone power and disables feedback
|
|
503
|
+
try:
|
|
504
|
+
band, channel = S.set_fixed_tone(fr, tone_power=tone_power)
|
|
505
|
+
except AssertionError as e:
|
|
506
|
+
S.log(f"Failed to assign a fixed tone on frequency {fr} MHz.\n{e}.")
|
|
507
|
+
continue
|
|
508
|
+
# read back the assigned frequency offset
|
|
509
|
+
foff = S.get_center_frequency_mhz_channel(band, channel)
|
|
510
|
+
fixed_tones[band]["freq_offsets"].append(float(foff))
|
|
511
|
+
fixed_tones[band]["channels"].append(int(channel))
|
|
512
|
+
fixed_tones[band]["enabled"] = True
|
|
513
|
+
|
|
514
|
+
# update config
|
|
515
|
+
if update_cfg:
|
|
516
|
+
for band, data in fixed_tones.items():
|
|
517
|
+
cfg.dev.update_band(band, {"fixed_tones": data}, update_file=True)
|
|
518
|
+
|
|
519
|
+
S.log("Done setting fixed tones.")
|
|
520
|
+
|
|
521
|
+
return fixed_tones
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
@sdl.set_action()
|
|
525
|
+
def turn_off_fixed_tones(S, cfg, bands=None):
|
|
526
|
+
"""
|
|
527
|
+
Read fixed tones from the device config and set their amplitude to 0.
|
|
528
|
+
|
|
529
|
+
Args
|
|
530
|
+
----
|
|
531
|
+
S : SmurfControl
|
|
532
|
+
Pysmurf instance
|
|
533
|
+
cfg : DetConfig
|
|
534
|
+
DetConfig instance
|
|
535
|
+
bands : list of int
|
|
536
|
+
The bands to operate on. Get from config by default.
|
|
537
|
+
"""
|
|
538
|
+
|
|
539
|
+
if bands is None:
|
|
540
|
+
bands = cfg.dev.exp['active_bands']
|
|
541
|
+
|
|
542
|
+
for band in bands:
|
|
543
|
+
# try to read from config
|
|
544
|
+
try:
|
|
545
|
+
if not cfg.dev.bands[band]["fixed_tones"].get("enabled", True):
|
|
546
|
+
S.log(f"Fixed tones are already disabled on band {band}.")
|
|
547
|
+
continue
|
|
548
|
+
channels = cfg.dev.bands[band]["fixed_tones"]["channels"]
|
|
549
|
+
except KeyError as e:
|
|
550
|
+
raise ValueError(
|
|
551
|
+
"No fixed tones present in device config."
|
|
552
|
+
) from e
|
|
553
|
+
|
|
554
|
+
amp = S.get_amplitude_scale_array(band)
|
|
555
|
+
amp[channels] = 0
|
|
556
|
+
S.set_amplitude_scale_array(band, amp)
|
|
557
|
+
cfg.dev.bands[band]["fixed_tones"]["enabled"] = False
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
@sdl.set_action()
|
|
561
|
+
def turn_on_fixed_tones(S, cfg, bands=None):
|
|
562
|
+
"""
|
|
563
|
+
Read fixed tones from the device config and ensure the corresponding channels
|
|
564
|
+
are set to the specified amplitude and have feedback disabled.
|
|
565
|
+
|
|
566
|
+
Args
|
|
567
|
+
----
|
|
568
|
+
S : SmurfControl
|
|
569
|
+
Pysmurf instance
|
|
570
|
+
cfg : DetConfig
|
|
571
|
+
DetConfig instance
|
|
572
|
+
bands : list of int
|
|
573
|
+
The bands to operate on. Get from config by default.
|
|
574
|
+
"""
|
|
575
|
+
|
|
576
|
+
if bands is None:
|
|
577
|
+
bands = cfg.dev.exp['active_bands']
|
|
578
|
+
|
|
579
|
+
for band in bands:
|
|
580
|
+
# try to read from config
|
|
581
|
+
try:
|
|
582
|
+
if not cfg.dev.bands[band]["fixed_tones"].get("enabled", False):
|
|
583
|
+
S.log(f"Fixed tones are not enabled on band {band}.")
|
|
584
|
+
continue
|
|
585
|
+
foff = cfg.dev.bands[band]["fixed_tones"]["freq_offsets"]
|
|
586
|
+
channels = cfg.dev.bands[band]["fixed_tones"]["channels"]
|
|
587
|
+
tone_power = cfg.dev.bands[band]["fixed_tones"].get("tone_power", None)
|
|
588
|
+
tunefile = cfg.dev.bands[band]["fixed_tones"].get("tunefile", None)
|
|
589
|
+
except KeyError as e:
|
|
590
|
+
raise ValueError(
|
|
591
|
+
"No fixed tones present in device config. Use `setup_fixed_tones` to add some."
|
|
592
|
+
) from e
|
|
593
|
+
if len(foff) != len(channels):
|
|
594
|
+
raise ValueError(
|
|
595
|
+
"Number of fixed tone channels in device config is different from number of "
|
|
596
|
+
f"frequency offsets for band {band} ({len(channels)} != {len(foff)})."
|
|
597
|
+
)
|
|
598
|
+
cfg_tunefile = cfg.dev.exp.get("tunefile", None)
|
|
599
|
+
if cfg_tunefile is None or tunefile is None:
|
|
600
|
+
# warn but do nothing
|
|
601
|
+
S.log(
|
|
602
|
+
"Turning on fixed tones with no associated tunefile could lead "
|
|
603
|
+
"to conflicts with resonator channels.", S.LOG_ERROR
|
|
604
|
+
)
|
|
605
|
+
elif tunefile != cfg_tunefile:
|
|
606
|
+
raise ValueError(
|
|
607
|
+
"The tunefile has changed since fixed tones were set up. Run `setup_fixed_tones "
|
|
608
|
+
"to find available channels."
|
|
609
|
+
)
|
|
610
|
+
S.log(f"Enabling {len(channels)} fixed tones on band {band}.")
|
|
611
|
+
|
|
612
|
+
# set the channel center frequency for each tone
|
|
613
|
+
for df, chan in zip(foff, channels):
|
|
614
|
+
S.set_center_frequency_mhz_channel(band, chan, df)
|
|
615
|
+
|
|
616
|
+
# set amplitude and feedback
|
|
617
|
+
amp = S.get_amplitude_scale_array(band)
|
|
618
|
+
feedback = S.get_feedback_enable_array(band)
|
|
619
|
+
|
|
620
|
+
amp[channels] = tone_power
|
|
621
|
+
feedback[channels] = 0
|
|
622
|
+
|
|
623
|
+
S.set_amplitude_scale_array(band, amp)
|
|
624
|
+
S.set_feedback_enable_array(band, feedback)
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
@sdl.set_action()
|
|
628
|
+
def uxm_setup(S, cfg, bands=None, show_plots=True, update_cfg=True,
|
|
629
|
+
modify_attens=True, skip_phase_delay=False,
|
|
630
|
+
skip_setup_amps=False):
|
|
631
|
+
"""
|
|
632
|
+
The goal of this function is to do a pysmurf setup completely from scratch,
|
|
633
|
+
meaning no parameters will be pulled from the device cfg.
|
|
634
|
+
|
|
635
|
+
The following steps will be run:
|
|
636
|
+
|
|
637
|
+
1. setup amps
|
|
638
|
+
2. Estimate phase delay
|
|
639
|
+
3. Setup tune
|
|
640
|
+
4. setup tracking
|
|
641
|
+
5. Measure noise
|
|
642
|
+
|
|
643
|
+
The following device cfg parameters can be changed to modify behavior:
|
|
644
|
+
|
|
645
|
+
exp:
|
|
646
|
+
- downsample_factor (int): Downsample factor to use
|
|
647
|
+
- coupling_mode (str): Determines whether to run in DC or AC mode. Can
|
|
648
|
+
be 'dc' or 'ac'.
|
|
649
|
+
- synthesis_scale (int): Synthesis scale to use
|
|
650
|
+
- amp_enable_wait_time (float): Seconds to wait after enabling amps
|
|
651
|
+
before scanning gate voltages
|
|
652
|
+
- amp_hemt_Id (float): Target drain current for hemt amp (mA)
|
|
653
|
+
- amp_50k_Id (float): Target drain current for 50k amp (mA)
|
|
654
|
+
- amp_hemt_Id_tolerance (float): Tolerance for hemt drain current (mA)
|
|
655
|
+
- amp_50k_Id_tolerance (float): Tolerance for 50k drain current (mA)
|
|
656
|
+
- res_amp_cut (float): Amplitude cut for peak-finding in find-freq
|
|
657
|
+
- res_grad_cut (float): Gradient cut for peak-finding in find-freq
|
|
658
|
+
bands:
|
|
659
|
+
- tone_power (int): Tone power to use for atten estimation
|
|
660
|
+
|
|
661
|
+
Args
|
|
662
|
+
-----
|
|
663
|
+
S : SmurfControl
|
|
664
|
+
Pysmurf instance
|
|
665
|
+
cfg : DetConfig
|
|
666
|
+
DetConfig instance
|
|
667
|
+
show_plots : bool
|
|
668
|
+
If true, will show find_freq plots. Defaults to False
|
|
669
|
+
update_cfg : bool
|
|
670
|
+
If true, will update the device cfg and save the file.
|
|
671
|
+
modify_attens : bool
|
|
672
|
+
If true or attenuations are not set in the device config, will run
|
|
673
|
+
estimate_uc_dc_atten to find a set of attenuations that will work for
|
|
674
|
+
estimate_phase_delay and find_freq. If False and attenuation values
|
|
675
|
+
already exist in the device config, this will use those values.
|
|
676
|
+
skip_phase_delay : bool
|
|
677
|
+
If True, will skip the estimate_phase_delay step
|
|
678
|
+
skip_setup_amps : bool
|
|
679
|
+
If True, will skip amplifier setup
|
|
680
|
+
"""
|
|
681
|
+
if bands is None:
|
|
682
|
+
bands = cfg.dev.exp['active_bands']
|
|
683
|
+
bands = np.atleast_1d(bands)
|
|
684
|
+
|
|
685
|
+
exp = cfg.dev.exp
|
|
686
|
+
|
|
687
|
+
#############################################################
|
|
688
|
+
# 1. Reset to known state
|
|
689
|
+
#############################################################
|
|
690
|
+
S.all_off() # Turn off Flux ramp, tones, and biases
|
|
691
|
+
S.set_rtm_arb_waveform_enable(0)
|
|
692
|
+
S.set_filter_disable(0)
|
|
693
|
+
S.set_downsample_factor(exp['downsample_factor'])
|
|
694
|
+
if exp['coupling_mode'] == 'dc':
|
|
695
|
+
S.set_mode_dc()
|
|
696
|
+
else:
|
|
697
|
+
S.set_mode_ac()
|
|
698
|
+
|
|
699
|
+
for band in bands:
|
|
700
|
+
S.set_synthesis_scale(band, exp['synthesis_scale'])
|
|
701
|
+
|
|
702
|
+
summary = {}
|
|
703
|
+
summary['timestamps'] = []
|
|
704
|
+
|
|
705
|
+
#############################################################
|
|
706
|
+
# 2. Setup amps
|
|
707
|
+
#############################################################
|
|
708
|
+
if not skip_setup_amps:
|
|
709
|
+
summary['timestamps'].append(('setup_amps', time.time()))
|
|
710
|
+
sdl.set_session_data(S, 'timestamps', summary['timestamps'])
|
|
711
|
+
|
|
712
|
+
success, summary['setup_amps'] = setup_amps(S, cfg, update_cfg=update_cfg)
|
|
713
|
+
if not success:
|
|
714
|
+
sdl.pub_ocs_log(S, "UXM Setup failed on setup amps step")
|
|
715
|
+
return False, summary
|
|
716
|
+
|
|
717
|
+
#############################################################
|
|
718
|
+
# 3. Estimate Attens
|
|
719
|
+
#############################################################
|
|
720
|
+
summary['timestamps'].append(('estimate_attens', time.time()))
|
|
721
|
+
sdl.set_session_data(S, 'timestamps', summary['timestamps'])
|
|
722
|
+
for band in bands:
|
|
723
|
+
bcfg = cfg.dev.bands[band]
|
|
724
|
+
if modify_attens or (bcfg['uc_att'] is None) or (bcfg['dc_att'] is None):
|
|
725
|
+
success = estimate_uc_dc_atten(S, cfg, band, update_cfg=update_cfg)
|
|
726
|
+
if not success:
|
|
727
|
+
sdl.pub_ocs_log(S, f"Failed to estimate attens on band {band}")
|
|
728
|
+
return False, summary
|
|
729
|
+
else:
|
|
730
|
+
S.set_att_uc(band, bcfg['uc_att'])
|
|
731
|
+
S.set_att_dc(band, bcfg['dc_att'])
|
|
732
|
+
|
|
733
|
+
#############################################################
|
|
734
|
+
# 4. Estimate Phase Delay
|
|
735
|
+
#############################################################
|
|
736
|
+
if not skip_phase_delay:
|
|
737
|
+
summary['timestamps'].append(('setup_phase_delay', time.time()))
|
|
738
|
+
sdl.set_session_data(S, 'timestamps', summary['timestamps'])
|
|
739
|
+
success, summary['setup_phase_delay'] = setup_phase_delay(
|
|
740
|
+
S, cfg, bands, update_cfg=update_cfg)
|
|
741
|
+
if not success:
|
|
742
|
+
S.log("UXM Setup failed on setup phase delay step")
|
|
743
|
+
return False, summary
|
|
744
|
+
|
|
745
|
+
#############################################################
|
|
746
|
+
# 5. Setup Tune
|
|
747
|
+
#############################################################
|
|
748
|
+
summary['timestamps'].append(('setup_tune', time.time()))
|
|
749
|
+
sdl.set_session_data(S, 'timestamps', summary['timestamps'])
|
|
750
|
+
success, summary['setup_tune'] = setup_tune(
|
|
751
|
+
S, cfg, bands, show_plots=show_plots, update_cfg=update_cfg,)
|
|
752
|
+
if not success:
|
|
753
|
+
S.log("UXM Setup failed on setup tune step")
|
|
754
|
+
return False, summary
|
|
755
|
+
|
|
756
|
+
#############################################################
|
|
757
|
+
# 6. Setup Tracking
|
|
758
|
+
#############################################################
|
|
759
|
+
summary['timestamps'].append(('setup_tracking', time.time()))
|
|
760
|
+
sdl.set_session_data(S, 'timestamps', summary['timestamps'])
|
|
761
|
+
|
|
762
|
+
# Ensure feedback is enabled
|
|
763
|
+
for b in bands:
|
|
764
|
+
S.set_feedback_enable(b, 1)
|
|
765
|
+
|
|
766
|
+
tracking_res = tracking.setup_tracking_params(
|
|
767
|
+
S, cfg, bands, show_plots=show_plots, update_cfg=update_cfg
|
|
768
|
+
)
|
|
769
|
+
summary['tracking_res'] = tracking_res
|
|
770
|
+
|
|
771
|
+
#############################################################
|
|
772
|
+
# 7. Noise
|
|
773
|
+
#############################################################
|
|
774
|
+
summary['timestamps'].append(('noise', time.time()))
|
|
775
|
+
sdl.set_session_data(S, 'timestamps', summary['timestamps'])
|
|
776
|
+
_, summary['noise'] = sdl.noise.take_noise(
|
|
777
|
+
S, cfg, 30, show_plot=show_plots, save_plot=True
|
|
778
|
+
)
|
|
779
|
+
|
|
780
|
+
summary['timestamps'].append(('end', time.time()))
|
|
781
|
+
sdl.set_session_data(S, 'timestamps', summary['timestamps'])
|
|
782
|
+
|
|
783
|
+
return True, summary
|