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,624 @@
|
|
|
1
|
+
import sodetlib as sdl
|
|
2
|
+
import numpy as np
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
from matplotlib.patches import Rectangle
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def compute_tracking_quality(S, f, df, sync):
|
|
8
|
+
"""
|
|
9
|
+
Computes the tracking quality parameter from tracking_setup results.
|
|
10
|
+
Tracking quality is a measure of how periodic the tracking response is with
|
|
11
|
+
respect to the flux-ramp. It is defined as the r-squared value between the
|
|
12
|
+
measured frequency response (f+df), and the (f+df) response averaged across
|
|
13
|
+
all flux-ramp periods. If the signal is regular with respect to flux-ramp,
|
|
14
|
+
the true freq response will be similar to the average freq response,
|
|
15
|
+
resulting in a high r-squared value. If the signal is irregular, the true
|
|
16
|
+
and average freq response will differ, resulting in a lower r-squared
|
|
17
|
+
value. This can be used in conjunction with the f and df peak-to-peak
|
|
18
|
+
values to determine which channels are tracking correctly.
|
|
19
|
+
|
|
20
|
+
Args
|
|
21
|
+
------
|
|
22
|
+
S : SmurfControl
|
|
23
|
+
Pysmurf instance
|
|
24
|
+
f : np.ndarray
|
|
25
|
+
Array of the tracked frequency for each channelk (kHz), as returned by
|
|
26
|
+
tracking_setup
|
|
27
|
+
df : np.ndarray
|
|
28
|
+
Array of the tracked frequency error for each channel (kHz), as
|
|
29
|
+
returned by tracking_setup
|
|
30
|
+
sync : np.ndarray
|
|
31
|
+
Array containing tracking sync flags, as returned by tracking_setup
|
|
32
|
+
"""
|
|
33
|
+
sync_idxs = S.make_sync_flag(sync)
|
|
34
|
+
seg_size = np.min(np.diff(sync_idxs))
|
|
35
|
+
nstacks = len(sync_idxs) - 1
|
|
36
|
+
nchans = len(f[0])
|
|
37
|
+
sig = f + df
|
|
38
|
+
|
|
39
|
+
fstack = np.zeros((seg_size, nchans))
|
|
40
|
+
for i in range(nstacks):
|
|
41
|
+
si = sync_idxs[i]
|
|
42
|
+
fstack[:seg_size] += (sig)[si:si+seg_size] / nstacks
|
|
43
|
+
|
|
44
|
+
# calculates quality of estimate wrt real data
|
|
45
|
+
y_real = (sig)[sync_idxs[0]:sync_idxs[0] + nstacks * seg_size, :]
|
|
46
|
+
y_est = np.vstack([fstack for _ in range(nstacks)])
|
|
47
|
+
|
|
48
|
+
# Force these to be the same len in case all segments are not the same size
|
|
49
|
+
y_est = y_est[:len(y_real)]
|
|
50
|
+
|
|
51
|
+
with np.errstate(invalid='ignore'):
|
|
52
|
+
sstot = np.sum((y_real - np.nanmean(y_real, axis=0))**2, axis=0)
|
|
53
|
+
ssres = np.sum((y_real - y_est)**2, axis=0)
|
|
54
|
+
r2 = 1 - ssres/sstot
|
|
55
|
+
|
|
56
|
+
return r2
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class TrackingResults:
|
|
60
|
+
"""
|
|
61
|
+
Class for storing, saving, and interpreting results from tracking_setup.
|
|
62
|
+
This class can store tracking results for multiple bands at a time.
|
|
63
|
+
When created, all results array are initialized to be empty. To add
|
|
64
|
+
results from individual bands one at a time, use the ``add_band_data``
|
|
65
|
+
function.
|
|
66
|
+
|
|
67
|
+
Args
|
|
68
|
+
----
|
|
69
|
+
S : SmurfControl
|
|
70
|
+
Pysmurf Instance
|
|
71
|
+
cfg : DetConfig
|
|
72
|
+
DetConfig
|
|
73
|
+
|
|
74
|
+
Attributes
|
|
75
|
+
--------------
|
|
76
|
+
meta : dict
|
|
77
|
+
Metadata dictionary
|
|
78
|
+
f_ptp_range : np.ndarray
|
|
79
|
+
Array of len(2) containing the min and max allowed f_ptp. This will
|
|
80
|
+
be pulled from the dev cfg.
|
|
81
|
+
df_ptp_range : np.ndarray
|
|
82
|
+
Array of len(2) containing the min and max allowed df_ptp. This will
|
|
83
|
+
be pulled from the dev cfg.
|
|
84
|
+
r2_min : float
|
|
85
|
+
Min value of r2 for a channel to be considered good
|
|
86
|
+
bands : np.ndarray
|
|
87
|
+
Array of len (nchans) containing the band of each channel
|
|
88
|
+
channels : np.ndarray
|
|
89
|
+
Array of shape (nchans) containing the smurf-channel of each channel
|
|
90
|
+
f : np.ndarray
|
|
91
|
+
Array of shape (nchans, nsamps) containing the tracked freq response
|
|
92
|
+
(kHz) throughout the tracking setup call
|
|
93
|
+
df : np.ndarray
|
|
94
|
+
Array of shape (nchans, nsamps) containing the untracked freq response
|
|
95
|
+
(kHz) throughout the tracking setup call
|
|
96
|
+
sync_idx : np.ndarray
|
|
97
|
+
Array of shape (nchans, num_fr_periods) containing the indices where
|
|
98
|
+
the flux ramp resetted
|
|
99
|
+
r2 : np.ndarray
|
|
100
|
+
Array of shape (nchans) containing the r-squared value computed by
|
|
101
|
+
tracking-quality for each channel
|
|
102
|
+
f_ptp : np.ndarray
|
|
103
|
+
Array of shape (nchans) containing the f_ptp of each channel
|
|
104
|
+
df_ptp : np.ndarray
|
|
105
|
+
Array of shape (nchans) containing the df_ptp of each channel
|
|
106
|
+
is_good : np.ndarray
|
|
107
|
+
Array of shape (nchans) containing True if the channel passes cuts
|
|
108
|
+
and False otherwise
|
|
109
|
+
"""
|
|
110
|
+
def __init__(self, *args, **kwargs):
|
|
111
|
+
if len(args) > 0:
|
|
112
|
+
self.initialize(*args, **kwargs)
|
|
113
|
+
|
|
114
|
+
def initialize(self, S, cfg):
|
|
115
|
+
self._S = S
|
|
116
|
+
self._cfg = cfg
|
|
117
|
+
self.meta = sdl.get_metadata(S, cfg)
|
|
118
|
+
self.f_ptp_range = np.array(cfg.dev.exp['f_ptp_range'])
|
|
119
|
+
self.df_ptp_range = np.array(cfg.dev.exp['df_ptp_range'])
|
|
120
|
+
self.r2_min = cfg.dev.exp['r2_min']
|
|
121
|
+
|
|
122
|
+
self.bands = np.array([], dtype=int)
|
|
123
|
+
self.channels = np.array([], dtype=int)
|
|
124
|
+
self.subband_centers = np.array([], dtype=float)
|
|
125
|
+
self.nchans = 0
|
|
126
|
+
self.ngood = 0
|
|
127
|
+
|
|
128
|
+
self.f = None
|
|
129
|
+
self.df = None
|
|
130
|
+
self.tracking_kwargs = [None for _ in range(8)]
|
|
131
|
+
self.sync_idxs = [None for _ in range(8)]
|
|
132
|
+
self.r2 = np.array([], dtype=float)
|
|
133
|
+
self.f_ptp = np.array([], dtype=float)
|
|
134
|
+
self.df_ptp = np.array([], dtype=float)
|
|
135
|
+
self.is_good = np.array([], dtype=bool)
|
|
136
|
+
|
|
137
|
+
def add_band_data(self, band, f, df, sync, tracking_kwargs=None):
|
|
138
|
+
"""
|
|
139
|
+
Computes tracking-related data based on the tracking response,
|
|
140
|
+
and updates the arrays described in the attributes with channels
|
|
141
|
+
from a new band
|
|
142
|
+
|
|
143
|
+
Args
|
|
144
|
+
-----
|
|
145
|
+
band : int
|
|
146
|
+
Band of the data you're adding
|
|
147
|
+
f : np.ndarray
|
|
148
|
+
Tracked freq response as returned by tracking_setup
|
|
149
|
+
df : np.ndarray
|
|
150
|
+
Untracked freq response as returned by tracking_setup
|
|
151
|
+
sync : np.ndarray
|
|
152
|
+
sync arrayas returned by tracking_setup
|
|
153
|
+
"""
|
|
154
|
+
if band in self.bands:
|
|
155
|
+
raise ValueError(f"Data for band {band} has already been added!")
|
|
156
|
+
|
|
157
|
+
dfptp = np.ptp(df, axis=0)
|
|
158
|
+
m = dfptp != 0
|
|
159
|
+
|
|
160
|
+
channels = np.where(m)[0]
|
|
161
|
+
nchans = len(channels)
|
|
162
|
+
bands = np.array([band for _ in channels])
|
|
163
|
+
sb_centers = np.array(
|
|
164
|
+
self._S.get_subband_centers(band, as_offset=False)[1]
|
|
165
|
+
)
|
|
166
|
+
self.channels = np.concatenate((self.channels, channels))
|
|
167
|
+
self.bands = np.concatenate((self.bands, bands))
|
|
168
|
+
self.subband_centers = np.concatenate((
|
|
169
|
+
self.subband_centers, sb_centers[channels]))
|
|
170
|
+
|
|
171
|
+
self.tracking_kwargs[band] = tracking_kwargs
|
|
172
|
+
r2 = compute_tracking_quality(self._S, f, df, sync)
|
|
173
|
+
|
|
174
|
+
if self.f is not None:
|
|
175
|
+
# It's possible for different tracking setup calls to have a
|
|
176
|
+
# slightly different number of samples, so we have to make sure
|
|
177
|
+
# we cut/elongate results so they can fit into the f/df arrays
|
|
178
|
+
nsamps = len(self.f[0])
|
|
179
|
+
_f = np.full((nchans, nsamps), np.nan)
|
|
180
|
+
_df = np.full((nchans, nsamps), np.nan)
|
|
181
|
+
fi = min(nsamps, len(f))
|
|
182
|
+
_f[:, :fi] = f.T[m, :fi] * 1000
|
|
183
|
+
_df[:, :fi] = df.T[m, :fi] * 1000
|
|
184
|
+
if fi < nsamps:
|
|
185
|
+
# Fill with the last data point to not mess up ptp calcs....
|
|
186
|
+
_f[:, fi:] = _f[:, fi-1][:, None]
|
|
187
|
+
_df[:, fi:] = _df[:, fi-1][:, None]
|
|
188
|
+
else:
|
|
189
|
+
_f = f.T[m] * 1000
|
|
190
|
+
_df = df.T[m] * 1000
|
|
191
|
+
|
|
192
|
+
self.r2 = np.concatenate((self.r2, r2[m]))
|
|
193
|
+
|
|
194
|
+
f_ptp = np.ptp(_f, axis=1)
|
|
195
|
+
self.f_ptp = np.concatenate((self.f_ptp, f_ptp))
|
|
196
|
+
|
|
197
|
+
df_ptp = np.ptp(_df, axis=1)
|
|
198
|
+
self.df_ptp = np.concatenate((self.df_ptp, df_ptp))
|
|
199
|
+
|
|
200
|
+
is_good = np.ones_like(r2, dtype=bool)
|
|
201
|
+
self.is_good = np.concatenate((self.is_good, is_good))
|
|
202
|
+
|
|
203
|
+
if self.f is None:
|
|
204
|
+
self.f = _f
|
|
205
|
+
self.df = _df
|
|
206
|
+
else:
|
|
207
|
+
nsamps = len(self.f[0])
|
|
208
|
+
self.f = np.concatenate((self.f, _f[:, :nsamps]), axis=0)
|
|
209
|
+
self.df = np.concatenate((self.df, _df[:, :nsamps]), axis=0)
|
|
210
|
+
|
|
211
|
+
self.sync_idxs[band] = self._S.make_sync_flag(sync)
|
|
212
|
+
self.nchans += len(_f)
|
|
213
|
+
self.find_bad_chans()
|
|
214
|
+
|
|
215
|
+
def find_bad_chans(self, f_ptp_range=None, df_ptp_range=None, r2_min=None):
|
|
216
|
+
"""
|
|
217
|
+
Recomputes the ``is_good`` array based on cuts ranges.
|
|
218
|
+
"""
|
|
219
|
+
if f_ptp_range is None:
|
|
220
|
+
f_ptp_range = self.f_ptp_range
|
|
221
|
+
if df_ptp_range is None:
|
|
222
|
+
df_ptp_range = self.df_ptp_range
|
|
223
|
+
if r2_min is None:
|
|
224
|
+
r2_min = self.r2_min
|
|
225
|
+
|
|
226
|
+
f0, f1 = f_ptp_range
|
|
227
|
+
df0, df1 = df_ptp_range
|
|
228
|
+
|
|
229
|
+
self.is_good = np.ones_like(self.r2, dtype=bool)
|
|
230
|
+
self.is_good[self.r2 < r2_min] = 0
|
|
231
|
+
self.is_good[~((f0 < self.f_ptp) & (self.f_ptp < f1))] = 0
|
|
232
|
+
self.is_good[~((df0 < self.df_ptp) & (self.df_ptp < df1))] = 0
|
|
233
|
+
self.ngood = np.sum(self.is_good)
|
|
234
|
+
|
|
235
|
+
def save(self, path=None):
|
|
236
|
+
saved_fields = [
|
|
237
|
+
'meta', 'bands', 'channels',
|
|
238
|
+
'f_ptp_range', 'df_ptp_range', 'r2_min',
|
|
239
|
+
'f', 'df', 'sync_idxs', 'r2', 'is_good',
|
|
240
|
+
'tracking_kwargs', 'f_ptp', 'df_ptp',
|
|
241
|
+
]
|
|
242
|
+
data = {}
|
|
243
|
+
for field in saved_fields:
|
|
244
|
+
data[field] = getattr(self, field)
|
|
245
|
+
if path is None:
|
|
246
|
+
path = sdl.make_filename(self._S, 'tracking_results.npy')
|
|
247
|
+
|
|
248
|
+
self.filepath = path
|
|
249
|
+
np.save(path, data, allow_pickle=True)
|
|
250
|
+
if self._S is not None:
|
|
251
|
+
self._S.pub.register_file(path, 'tracking_data', format='npy')
|
|
252
|
+
|
|
253
|
+
@classmethod
|
|
254
|
+
def load(cls, path):
|
|
255
|
+
self = cls()
|
|
256
|
+
for k, v in np.load(path, allow_pickle=True).item().items():
|
|
257
|
+
setattr(self, k, v)
|
|
258
|
+
return self
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def plot_tracking_channel(tr, idx, show_text=True):
|
|
262
|
+
"""
|
|
263
|
+
Plots single tracking channel from results
|
|
264
|
+
"""
|
|
265
|
+
f = tr.f[idx]
|
|
266
|
+
df = tr.df[idx]
|
|
267
|
+
fig, ax = plt.subplots(figsize=(12, 4))
|
|
268
|
+
band = int(tr.bands[idx])
|
|
269
|
+
channel = int(tr.channels[idx])
|
|
270
|
+
|
|
271
|
+
ax.set_title(f"Band {band} Channel {channel}")
|
|
272
|
+
ax.plot(df+f, color='grey', alpha=0.8, label='Freq Response')
|
|
273
|
+
ax.plot(f, label='Tracked Freq')
|
|
274
|
+
ax.set_xticks([])
|
|
275
|
+
if hasattr(tr, 'subband_centers'):
|
|
276
|
+
ax.set_ylabel(f"Freq offset from {tr.subband_centers[idx]:0.2f} [kHz]")
|
|
277
|
+
else:
|
|
278
|
+
ax.set_ylabel(f"Freq offset [kHz]")
|
|
279
|
+
ax.legend(loc='upper left')
|
|
280
|
+
|
|
281
|
+
if show_text:
|
|
282
|
+
txt = '\n'.join([
|
|
283
|
+
f'fptp = {tr.f_ptp[idx]:0.2f}',
|
|
284
|
+
f'dfptp = {tr.df_ptp[idx]:0.2f}',
|
|
285
|
+
f'r2 = {tr.r2[idx]:0.2f}',
|
|
286
|
+
f'is_good = {tr.is_good[idx]}',
|
|
287
|
+
])
|
|
288
|
+
bbox = dict(facecolor='white', alpha=0.8)
|
|
289
|
+
ax.text(0.02, 0.1, txt, transform=ax.transAxes, bbox=bbox)
|
|
290
|
+
for s in tr.sync_idxs[band]:
|
|
291
|
+
ax.axvline(s, color='grey', ls='--')
|
|
292
|
+
return fig, ax
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def plot_tracking_summary(tr):
|
|
296
|
+
"""
|
|
297
|
+
Plots summary of tracking results
|
|
298
|
+
"""
|
|
299
|
+
# fig, axes = plt.subplots(figsize=(12, 8))
|
|
300
|
+
mosaic = """
|
|
301
|
+
BBBB.
|
|
302
|
+
AAAAC
|
|
303
|
+
AAAAC
|
|
304
|
+
AAAAC
|
|
305
|
+
AAAAC
|
|
306
|
+
"""
|
|
307
|
+
gs = dict(hspace=0, wspace=0)
|
|
308
|
+
fig, axes = plt.subplot_mosaic(mosaic, figsize=(12, 8), gridspec_kw=gs)
|
|
309
|
+
f0, f1 = tr.f_ptp_range
|
|
310
|
+
df0, df1 = tr.df_ptp_range
|
|
311
|
+
|
|
312
|
+
m = tr.is_good
|
|
313
|
+
ax = axes['A']
|
|
314
|
+
ax.scatter(tr.f_ptp[m], tr.df_ptp[m], marker='.', alpha=0.8)
|
|
315
|
+
ax.scatter(tr.f_ptp[~m], tr.df_ptp[~m], marker='.', color='red', alpha=0.2)
|
|
316
|
+
xlim = (-5, f1 * 1.3)
|
|
317
|
+
ylim = (-5, df1 * 1.3)
|
|
318
|
+
ax.set_xlim(*xlim)
|
|
319
|
+
ax.set_ylim(*ylim)
|
|
320
|
+
rect = Rectangle((f0, df0), f1-f0, df1-df0, fc='green', alpha=0.2,
|
|
321
|
+
linestyle='--', ec='black', lw=3)
|
|
322
|
+
ax.add_patch(rect)
|
|
323
|
+
ax.set_ylabel(r"$df_\mathrm{ptp}$ (kHz)", fontsize=20)
|
|
324
|
+
ax.set_xlabel(r"$f_\mathrm{ptp}$ (kHz)", fontsize=20)
|
|
325
|
+
|
|
326
|
+
text = f"{tr.ngood} / {tr.nchans} good tracking channels"
|
|
327
|
+
bbox = dict(fc='wheat', alpha=0.8)
|
|
328
|
+
ax.text(0.02, 0.9, text, transform=ax.transAxes, bbox=bbox, fontsize=16)
|
|
329
|
+
|
|
330
|
+
ax = axes['B']
|
|
331
|
+
ax.set_xticks([])
|
|
332
|
+
ax.hist(tr.f_ptp, range=xlim, bins=40)
|
|
333
|
+
ax.set_xlim(*xlim)
|
|
334
|
+
ax.axvspan(f0, f1, color='green', alpha=0.2)
|
|
335
|
+
|
|
336
|
+
ax = axes['C']
|
|
337
|
+
ax.set_yticks([])
|
|
338
|
+
ax.hist(tr.df_ptp, range=ylim, bins=40, orientation='horizontal')
|
|
339
|
+
ax.set_ylim(*ylim)
|
|
340
|
+
ax.axhspan(df0, df1, color='green', alpha=0.2)
|
|
341
|
+
|
|
342
|
+
return fig, ax
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def disable_bad_chans(S, tr, bands=None, **kwargs):
|
|
346
|
+
"""
|
|
347
|
+
Disables cut channels based on a TrackingResults object.
|
|
348
|
+
"""
|
|
349
|
+
tr.find_bad_chans(**kwargs)
|
|
350
|
+
if bands is None:
|
|
351
|
+
bands = np.arange(8)
|
|
352
|
+
|
|
353
|
+
bands = np.atleast_1d()
|
|
354
|
+
for b in bands:
|
|
355
|
+
m = tr.bands == b
|
|
356
|
+
asa = S.get_amplitude_scale_array(b)
|
|
357
|
+
fea = S.get_feedback_enable_array(b)
|
|
358
|
+
for good, c in zip(tr.is_good[m], tr.channels[m]):
|
|
359
|
+
if not good:
|
|
360
|
+
asa[c] = 0
|
|
361
|
+
fea[c] = 0
|
|
362
|
+
S.set_amplitude_scale_array(b, asa)
|
|
363
|
+
S.set_feedback_enable_array(b, asa)
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
@sdl.set_action()
|
|
367
|
+
def setup_tracking_params(S, cfg, bands, update_cfg=True, show_plots=False):
|
|
368
|
+
"""
|
|
369
|
+
Sets up tracking parameters by determining correct frac-pp and lms-freq
|
|
370
|
+
for each band.
|
|
371
|
+
|
|
372
|
+
Args
|
|
373
|
+
-----
|
|
374
|
+
S : SmurfControl
|
|
375
|
+
Pysmurf instance
|
|
376
|
+
cfg : DetConfig
|
|
377
|
+
DetConfig instance
|
|
378
|
+
bands : np.ndarray, int
|
|
379
|
+
Band or list of bands to run on
|
|
380
|
+
update_cfg : bool
|
|
381
|
+
If true, will update the device cfg and save the file.
|
|
382
|
+
show_plots : bool
|
|
383
|
+
If true, will show summary plots
|
|
384
|
+
"""
|
|
385
|
+
|
|
386
|
+
bands = np.atleast_1d(bands)
|
|
387
|
+
exp = cfg.dev.exp
|
|
388
|
+
|
|
389
|
+
res = TrackingResults(S, cfg)
|
|
390
|
+
|
|
391
|
+
tk = {
|
|
392
|
+
'reset_rate_khz': exp['flux_ramp_rate_khz'],
|
|
393
|
+
'make_plot': False, 'show_plot': False, 'channel': [],
|
|
394
|
+
'nsamp': 2**18, 'return_data': True,
|
|
395
|
+
'feedback_start_frac': exp['feedback_start_frac'],
|
|
396
|
+
'feedback_end_frac': exp['feedback_end_frac'],
|
|
397
|
+
}
|
|
398
|
+
for band in bands:
|
|
399
|
+
sdl.pub_ocs_log(S, f"Setting up trackng params: band {band}")
|
|
400
|
+
bcfg = cfg.dev.bands[band]
|
|
401
|
+
tk.update({
|
|
402
|
+
'lms_freq_hz': None,
|
|
403
|
+
'meas_lms_freq': True,
|
|
404
|
+
'fraction_full_scale': exp['init_frac_pp'],
|
|
405
|
+
'reset_rate_khz': exp['flux_ramp_rate_khz'],
|
|
406
|
+
'lms_gain': bcfg['lms_gain'],
|
|
407
|
+
'feedback_gain': bcfg['feedback_gain'],
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
f, df, sync = S.tracking_setup(band, **tk)
|
|
411
|
+
tr = TrackingResults(S, cfg)
|
|
412
|
+
tr.add_band_data(band, f, df, sync)
|
|
413
|
+
asa_init = S.get_amplitude_scale_array(band)
|
|
414
|
+
disable_bad_chans(S, tr, bands=band, r2_min=0.95)
|
|
415
|
+
|
|
416
|
+
# Calculate trracking parameters
|
|
417
|
+
S.tracking_setup(band, **tk)
|
|
418
|
+
lms_meas = S.lms_freq_hz[band]
|
|
419
|
+
lms_freq = exp['nphi0'] * tk['reset_rate_khz'] * 1e3
|
|
420
|
+
frac_pp = tk['fraction_full_scale'] * lms_freq / lms_meas
|
|
421
|
+
|
|
422
|
+
# Re-enables all channels and re-run tracking setup with correct params
|
|
423
|
+
S.set_amplitude_scale_array(band, asa_init)
|
|
424
|
+
tk.update({
|
|
425
|
+
'meas_lms_freq': False,
|
|
426
|
+
'fraction_full_scale': frac_pp,
|
|
427
|
+
'lms_freq_hz': lms_freq,
|
|
428
|
+
})
|
|
429
|
+
|
|
430
|
+
f, df, sync = S.tracking_setup(band, **tk)
|
|
431
|
+
res.add_band_data(band, f, df, sync, tracking_kwargs=tk)
|
|
432
|
+
|
|
433
|
+
# Print tracking params
|
|
434
|
+
d = {
|
|
435
|
+
'frac_pp': frac_pp,
|
|
436
|
+
'lms_freq_hz': lms_freq,
|
|
437
|
+
}
|
|
438
|
+
S.log(f"Tracking params found for band {band}: {d}")
|
|
439
|
+
|
|
440
|
+
# Update det config
|
|
441
|
+
if update_cfg:
|
|
442
|
+
cfg.dev.update_band(band, d, update_file=True)
|
|
443
|
+
|
|
444
|
+
res.save()
|
|
445
|
+
|
|
446
|
+
S.log("Running relock tracking setup...")
|
|
447
|
+
res = relock_tracking_setup(S, cfg, bands=bands, show_plots=show_plots)
|
|
448
|
+
|
|
449
|
+
return res
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def _get_fixed_tone_channels(S, cfg, bands=None):
|
|
453
|
+
"""
|
|
454
|
+
Identify channels assigned to fixed tones by checking state of amplitude and
|
|
455
|
+
feedback arrays.
|
|
456
|
+
|
|
457
|
+
Args
|
|
458
|
+
----
|
|
459
|
+
S : SmurfControl
|
|
460
|
+
Pysmurf instance
|
|
461
|
+
cfg : DetConfig
|
|
462
|
+
Det config instance
|
|
463
|
+
bands : list of int
|
|
464
|
+
Bands to operate on. Get from config by default.
|
|
465
|
+
"""
|
|
466
|
+
|
|
467
|
+
if bands is None:
|
|
468
|
+
bands = cfg.dev.exp['active_bands']
|
|
469
|
+
|
|
470
|
+
# infer from amplitude and feedback state
|
|
471
|
+
ft_ind = {}
|
|
472
|
+
for band in bands:
|
|
473
|
+
amp = S.get_amplitude_scale_array(band)
|
|
474
|
+
feedback = S.get_feedback_enable_array(band)
|
|
475
|
+
ft_ind[band] = np.where((amp != 0) & (feedback == 0))[0]
|
|
476
|
+
|
|
477
|
+
return ft_ind
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def _restore_fixed_tone_channels(S, chan_per_band: dict):
|
|
481
|
+
"""
|
|
482
|
+
Ensure feedback is disabled on channels that are assigned fixed tones.
|
|
483
|
+
|
|
484
|
+
Args
|
|
485
|
+
----
|
|
486
|
+
S : SmurfControl
|
|
487
|
+
Pysmurf instance
|
|
488
|
+
chan_per_band : dict
|
|
489
|
+
List of channels to disable feedback on, given as a dictionary indexed by band number.
|
|
490
|
+
"""
|
|
491
|
+
|
|
492
|
+
# infer from amplitude and feedback state
|
|
493
|
+
for band, chan in chan_per_band.items():
|
|
494
|
+
feedback = S.get_feedback_enable_array(band)
|
|
495
|
+
feedback[chan] = 0
|
|
496
|
+
S.set_feedback_enable_array(band, feedback)
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
@sdl.set_action()
|
|
500
|
+
def relock_tracking_setup(S, cfg, bands=None, reset_rate_khz=None, nphi0=None,
|
|
501
|
+
feedback_gain=None, lms_gain=None, show_plots=False,
|
|
502
|
+
frac_pp=None):
|
|
503
|
+
"""
|
|
504
|
+
Sets up tracking for smurf. This assumes you already have optimized
|
|
505
|
+
lms_freq and frac-pp for each bands in the device config. This function
|
|
506
|
+
will chose the flux-ramp fraction-full-scale by averaging the optimized
|
|
507
|
+
fractions across the bands you're running on.
|
|
508
|
+
|
|
509
|
+
This function also allows you to set reset_rate_khz and nphi0. The
|
|
510
|
+
fraction-full-scale, and lms frequencies of each band will be automatically
|
|
511
|
+
adjusted based on their pre-existing optimized values.
|
|
512
|
+
|
|
513
|
+
Additional keyword args specified will be passed to S.tracking_setup.
|
|
514
|
+
|
|
515
|
+
Args
|
|
516
|
+
-----
|
|
517
|
+
S : SmurfControl
|
|
518
|
+
Pysmurf instance
|
|
519
|
+
cfg : DetConfig
|
|
520
|
+
Det config instance
|
|
521
|
+
reset_rate_khz : float, optional
|
|
522
|
+
Flux Ramp Reset Rate to set (kHz), defaults to the value in the dev cfg
|
|
523
|
+
nphi0 : int, optional
|
|
524
|
+
Number of phi0's to ramp through. Defaults to the value that was used
|
|
525
|
+
during setup.
|
|
526
|
+
disable_bad_chans : bool
|
|
527
|
+
If true, will disable tones for bad-tracking channels
|
|
528
|
+
|
|
529
|
+
Returns
|
|
530
|
+
--------
|
|
531
|
+
res : dict
|
|
532
|
+
Dictionary of results of all tracking-setup calls, with the bands number
|
|
533
|
+
as key.
|
|
534
|
+
"""
|
|
535
|
+
if bands is None:
|
|
536
|
+
bands = cfg.dev.exp['active_bands']
|
|
537
|
+
bands = np.atleast_1d(bands)
|
|
538
|
+
|
|
539
|
+
nbands = len(bands)
|
|
540
|
+
exp = cfg.dev.exp
|
|
541
|
+
|
|
542
|
+
# check if any channels are assigned fixed tones
|
|
543
|
+
fixed_tones = _get_fixed_tone_channels(S, cfg, bands)
|
|
544
|
+
|
|
545
|
+
# Arrays containing the optimized tracking parameters for each band
|
|
546
|
+
frac_pp0 = np.zeros(nbands)
|
|
547
|
+
lms_freq0 = np.zeros(nbands) # Hz
|
|
548
|
+
reset_rate_khz0 = exp['flux_ramp_rate_khz']
|
|
549
|
+
init_nphi0 = exp['nphi0']
|
|
550
|
+
|
|
551
|
+
for i, b in enumerate(bands):
|
|
552
|
+
bcfg = cfg.dev.bands[b]
|
|
553
|
+
frac_pp0[i] = bcfg['frac_pp']
|
|
554
|
+
lms_freq0[i] = bcfg['lms_freq_hz']
|
|
555
|
+
|
|
556
|
+
# Choose frac_pp to be the mean of all running bands.
|
|
557
|
+
# This is the frac-pp at the flux-ramp-rate used for optimization
|
|
558
|
+
fpp0 = np.mean(frac_pp0)
|
|
559
|
+
lms_freq0 *= fpp0 / frac_pp0
|
|
560
|
+
|
|
561
|
+
S.log(f"Using frac-pp={fpp0}")
|
|
562
|
+
|
|
563
|
+
# Adjust fpp, lms_freq, and flux-ramp-rate depending on desired
|
|
564
|
+
# flux-ramp-rate and nphi0
|
|
565
|
+
fpp, lms_freqs = fpp0, lms_freq0
|
|
566
|
+
if nphi0 is not None:
|
|
567
|
+
fpp *= nphi0 / init_nphi0
|
|
568
|
+
lms_freqs *= fpp / fpp0
|
|
569
|
+
if reset_rate_khz is not None:
|
|
570
|
+
lms_freqs *= reset_rate_khz / reset_rate_khz0
|
|
571
|
+
else:
|
|
572
|
+
reset_rate_khz = reset_rate_khz0
|
|
573
|
+
|
|
574
|
+
if frac_pp is not None:
|
|
575
|
+
lms_freqs *= frac_pp / fpp
|
|
576
|
+
fpp = frac_pp
|
|
577
|
+
|
|
578
|
+
res = TrackingResults(S, cfg)
|
|
579
|
+
tk = {
|
|
580
|
+
'reset_rate_khz': reset_rate_khz, 'fraction_full_scale': fpp,
|
|
581
|
+
'make_plot': False, 'show_plot': False, 'channel': [],
|
|
582
|
+
'nsamp': 2**18, 'return_data': True,
|
|
583
|
+
'feedback_start_frac': exp['feedback_start_frac'],
|
|
584
|
+
'feedback_end_frac': exp['feedback_end_frac'],
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
for i, band in enumerate(bands):
|
|
589
|
+
bcfg = cfg.dev.bands[band]
|
|
590
|
+
tk.update({
|
|
591
|
+
'lms_freq_hz': lms_freqs[i],
|
|
592
|
+
'lms_gain': bcfg['lms_gain'],
|
|
593
|
+
'feedback_gain': bcfg['feedback_gain'],
|
|
594
|
+
})
|
|
595
|
+
|
|
596
|
+
if lms_gain is not None:
|
|
597
|
+
tk['lms_gain'] = lms_gain
|
|
598
|
+
if feedback_gain is not None:
|
|
599
|
+
tk['feedback_gain'] = feedback_gain
|
|
600
|
+
|
|
601
|
+
f, df, sync = S.tracking_setup(band, **tk)
|
|
602
|
+
res.add_band_data(band, f, df, sync, tracking_kwargs=tk)
|
|
603
|
+
|
|
604
|
+
res.find_bad_chans()
|
|
605
|
+
res.save()
|
|
606
|
+
|
|
607
|
+
# ensure channels with assigned fixed tones have feedback disabled
|
|
608
|
+
_restore_fixed_tone_channels(S, fixed_tones)
|
|
609
|
+
|
|
610
|
+
is_interactive = plt.isinteractive()
|
|
611
|
+
try:
|
|
612
|
+
if not show_plots:
|
|
613
|
+
plt.ioff()
|
|
614
|
+
fig, ax = plot_tracking_summary(res)
|
|
615
|
+
path = sdl.make_filename(S, 'tracking_results.png', plot=True)
|
|
616
|
+
fig.savefig(path)
|
|
617
|
+
S.pub.register_file(path, 'tracking_summary', plot=True, format='png')
|
|
618
|
+
if not show_plots:
|
|
619
|
+
plt.close(fig)
|
|
620
|
+
finally:
|
|
621
|
+
if is_interactive:
|
|
622
|
+
plt.ion()
|
|
623
|
+
|
|
624
|
+
return res
|