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,641 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import os
|
|
3
|
+
import time
|
|
4
|
+
from scipy.signal import find_peaks
|
|
5
|
+
from scipy.optimize import curve_fit
|
|
6
|
+
import sodetlib as sdl
|
|
7
|
+
from sodetlib.legacy.analysis import squid_fit as sqf
|
|
8
|
+
import matplotlib.pyplot as plt
|
|
9
|
+
from tqdm.auto import tqdm, trange
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def autocorr(wave):
|
|
13
|
+
"""
|
|
14
|
+
Code to calculate the autocorrelation function of signal ``wave``.
|
|
15
|
+
|
|
16
|
+
Args
|
|
17
|
+
----
|
|
18
|
+
wave : float, ndarray
|
|
19
|
+
signal to calculate autocorrelation on
|
|
20
|
+
Returns
|
|
21
|
+
-------
|
|
22
|
+
lags : float, ndarray
|
|
23
|
+
time/x-shift of signal relative to itself
|
|
24
|
+
corrs : float, ndarray
|
|
25
|
+
correlation coefficient at each lag
|
|
26
|
+
"""
|
|
27
|
+
n = len(wave)
|
|
28
|
+
lags = range(len(wave) // 2)
|
|
29
|
+
corrs = np.zeros(len(lags))
|
|
30
|
+
for ix, lag in enumerate(lags):
|
|
31
|
+
y1 = wave[lag:]
|
|
32
|
+
y2 = wave[: n - lag]
|
|
33
|
+
corrs[ix] = np.corrcoef(y1, y2)[0, 1]
|
|
34
|
+
return lags, corrs
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def estimate_fit_parameters(phi, noisy_squid_curve, nharmonics_to_estimate=5,
|
|
38
|
+
min_acorr_dist_from_zero_frac=0.1):
|
|
39
|
+
"""
|
|
40
|
+
Estimate rf-SQUID curve fit parameters to pass to ``fit_squid_curves`` as
|
|
41
|
+
an initial guess for fitter.
|
|
42
|
+
|
|
43
|
+
Args
|
|
44
|
+
----
|
|
45
|
+
phi : ndarray
|
|
46
|
+
Array of fixed flux ramp bias voltages.
|
|
47
|
+
noisy_squid_curve : ndarray
|
|
48
|
+
Array of rf-SQUID resonator frequencies, one for each flux ramp
|
|
49
|
+
bias voltage.
|
|
50
|
+
nharmonics_to_estimate : int, optional, default 5
|
|
51
|
+
Number of harmonics of fundamental squid curve frequency to estimate.
|
|
52
|
+
min_acorr_dist_from_zero_frac : float, optional, default 0.1
|
|
53
|
+
Minimum distance to first peak in autocorrelation (used to estimate what
|
|
54
|
+
how much fr bias current corresponds to 1 phi0) in units fraction of
|
|
55
|
+
the input ``phi`` array (between 0 to 1)
|
|
56
|
+
|
|
57
|
+
Returns
|
|
58
|
+
-------
|
|
59
|
+
est : ndarray or None
|
|
60
|
+
Estimated fit parameters. Returns None if unable to estimate
|
|
61
|
+
the phi0 using lag and autocorrelation.
|
|
62
|
+
"""
|
|
63
|
+
min_acorr_dist_from_zero = len(phi) * min_acorr_dist_from_zero_frac
|
|
64
|
+
|
|
65
|
+
# find period from autocorrelation
|
|
66
|
+
lags, corrs = autocorr(noisy_squid_curve)
|
|
67
|
+
|
|
68
|
+
# find peaks in autocorrelation vs lag
|
|
69
|
+
peaks, _ = find_peaks(corrs, height=0)
|
|
70
|
+
sorted_peaks = sorted(peaks)
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
phi0_idx = next(pk for pk in sorted_peaks if pk >
|
|
74
|
+
min_acorr_dist_from_zero)
|
|
75
|
+
except:
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
phi0 = np.abs(phi[phi0_idx] - phi[0])
|
|
79
|
+
|
|
80
|
+
# plot cosine with same amplitude and period
|
|
81
|
+
yspan = np.ptp(noisy_squid_curve)
|
|
82
|
+
yoffset = yspan / 2.0 + np.min(noisy_squid_curve)
|
|
83
|
+
|
|
84
|
+
def harmonic(n, ph, phoff, amp): return (amp) *\
|
|
85
|
+
np.cos(n*(ph-phoff)*(2*np.pi/phi0))
|
|
86
|
+
|
|
87
|
+
def first_harmonic_guess(ph, phoff): return yoffset + \
|
|
88
|
+
harmonic(1, ph, phoff, (yspan / 2))
|
|
89
|
+
|
|
90
|
+
# now correlate the first harmonic guess against the SQUID curve
|
|
91
|
+
dphi = np.abs(phi[1] - phi[0])
|
|
92
|
+
testphoffs = np.linspace(0, phi0, int(np.floor(phi0 / dphi) + 1))
|
|
93
|
+
corrs = []
|
|
94
|
+
for testphoff in testphoffs:
|
|
95
|
+
y1 = first_harmonic_guess(phi, testphoff)
|
|
96
|
+
y2 = noisy_squid_curve
|
|
97
|
+
y1 = y1 - np.nanmean(y1)
|
|
98
|
+
y2 = y2 - np.nanmean(y2)
|
|
99
|
+
corr = np.corrcoef(y1, y2)[0, 1]
|
|
100
|
+
corrs.append(corr)
|
|
101
|
+
|
|
102
|
+
# should just be able to find the maximum of this correlation
|
|
103
|
+
if np.isnan(corrs).all():
|
|
104
|
+
phioffset = 0
|
|
105
|
+
else:
|
|
106
|
+
phioffset = testphoffs[np.nanargmax(corrs)]
|
|
107
|
+
|
|
108
|
+
# plot harmonics only over the largest possible number of SQUID periods. May only be 1.
|
|
109
|
+
lower_phi_full_cycles = (np.min(phi) + phioffset) % (phi0) + np.min(phi)
|
|
110
|
+
upper_phi_full_cycles = np.max(phi) - (np.max(phi) - phioffset) % phi0
|
|
111
|
+
phi_full_cycle_idxs = np.where(
|
|
112
|
+
(phi > lower_phi_full_cycles) & (phi < upper_phi_full_cycles)
|
|
113
|
+
)
|
|
114
|
+
phi_full_cycles = phi[phi_full_cycle_idxs]
|
|
115
|
+
# correlate some harmonics and overplot!
|
|
116
|
+
fit_guess = np.zeros_like(noisy_squid_curve) + np.nanmean(noisy_squid_curve)
|
|
117
|
+
# mean subtract the data and this harmonic
|
|
118
|
+
d = noisy_squid_curve[phi_full_cycle_idxs]
|
|
119
|
+
dm = np.nanmean(d)
|
|
120
|
+
d_ms = d - dm
|
|
121
|
+
|
|
122
|
+
est = [phi0, phioffset, dm]
|
|
123
|
+
|
|
124
|
+
for n in range(1, nharmonics_to_estimate + 1):
|
|
125
|
+
def this_harmonic(ph): return harmonic(n, ph, phioffset, 1/2)
|
|
126
|
+
h = this_harmonic(phi_full_cycles)
|
|
127
|
+
hm = np.nanmean(h)
|
|
128
|
+
h_ms = h - hm
|
|
129
|
+
# sort of inverse dft them
|
|
130
|
+
Xh = np.sum(d_ms * h_ms)
|
|
131
|
+
# add this harmonic
|
|
132
|
+
fit_guess += Xh * this_harmonic(phi)
|
|
133
|
+
est.append(Xh)
|
|
134
|
+
|
|
135
|
+
# match span of harmonic guess sum and add offset from data
|
|
136
|
+
normalization_factor = np.ptp(d_ms) / np.ptp(fit_guess)
|
|
137
|
+
fit_guess *= normalization_factor
|
|
138
|
+
# also scale parameter guesses we pass back
|
|
139
|
+
est = np.array(est)
|
|
140
|
+
est[3:] *= normalization_factor
|
|
141
|
+
return est
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def squid_curve_model(phi, *p):
|
|
145
|
+
"""
|
|
146
|
+
Functional form of squid curve, basically a fourier expansion (with
|
|
147
|
+
n-harmonics).
|
|
148
|
+
|
|
149
|
+
Args
|
|
150
|
+
----
|
|
151
|
+
phi : float, ndarray
|
|
152
|
+
depedent variable in function
|
|
153
|
+
p : ndarray
|
|
154
|
+
indices::
|
|
155
|
+
0: period in fraction_full_scale
|
|
156
|
+
1: offset phase in fraction_full_scale
|
|
157
|
+
2: central value (mean) of squid curve
|
|
158
|
+
3 to N: amplitude of the (N-3)th harmonic of the squid curve.
|
|
159
|
+
"""
|
|
160
|
+
phi0 = p[0]
|
|
161
|
+
phioffset = p[1]
|
|
162
|
+
dm = p[2]
|
|
163
|
+
ret = dm
|
|
164
|
+
|
|
165
|
+
def harmonic(n, ph, phoff, amp): return (amp) *\
|
|
166
|
+
np.cos(n*(ph-phoff)*(2*np.pi/phi0))
|
|
167
|
+
for n in range(0, len(p[3:])):
|
|
168
|
+
def this_harmonic(ph): return harmonic(n+1, ph, phioffset, 0.5*p[3+n])
|
|
169
|
+
ret += this_harmonic(phi)
|
|
170
|
+
return ret
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def plot_squid_fit_summary(data, nbins=35, quantile=.95):
|
|
174
|
+
'''
|
|
175
|
+
Makes summary plots of squid curve fit parameters.
|
|
176
|
+
Specifically, Phi_offset, df, dfdI, hhpwr, phi0.
|
|
177
|
+
|
|
178
|
+
Args
|
|
179
|
+
----
|
|
180
|
+
data : dict
|
|
181
|
+
Dictionary output created from take_squid_curves.
|
|
182
|
+
Used to find data['bands'].size and data['popts'] values.
|
|
183
|
+
nbins : int
|
|
184
|
+
Number of bins to use for histograms.
|
|
185
|
+
quantile : float
|
|
186
|
+
Plot data that falls within the quantile given.
|
|
187
|
+
'''
|
|
188
|
+
# Plot offsets
|
|
189
|
+
popts = data['popts']
|
|
190
|
+
fig = plt.figure(figsize=(20, 15))
|
|
191
|
+
fig.set_facecolor('white')
|
|
192
|
+
offsets = popts[:, 1] / popts[:, 0]
|
|
193
|
+
offsets = offsets[(offsets >= np.nanquantile(offsets, 1-quantile))
|
|
194
|
+
& (offsets <= np.nanquantile(offsets, quantile))]
|
|
195
|
+
plt.subplot(3, 2, 1)
|
|
196
|
+
plt.hist(offsets, bins=nbins, alpha=0.7)
|
|
197
|
+
plt.axvline(np.median(offsets), color='C1', ls=':', lw=3)
|
|
198
|
+
plt.legend([f'Median : {np.round(np.median(offsets),3)}', f'Data (Inner {quantile*100}%)\n {offsets.size} / {data["bands"].size} channels used'],
|
|
199
|
+
fontsize=14)
|
|
200
|
+
plt.xlabel('$\\Phi_0$ Offset', fontsize=16)
|
|
201
|
+
plt.ylabel('Counts', fontsize=16)
|
|
202
|
+
plt.tick_params(axis='both', which='major', labelsize=12)
|
|
203
|
+
|
|
204
|
+
# Plot dfs
|
|
205
|
+
dfs = data['df']
|
|
206
|
+
dfs = dfs[(dfs >= np.nanquantile(dfs, 1-quantile))
|
|
207
|
+
& (dfs <= np.nanquantile(dfs, quantile))]
|
|
208
|
+
plt.subplot(3, 2, 2)
|
|
209
|
+
plt.hist(dfs, bins=nbins, alpha=0.7)
|
|
210
|
+
plt.axvline(np.median(dfs), color='C1', ls=':', lw=3)
|
|
211
|
+
plt.legend([f'Median : {np.round(np.median(dfs),1)}', f'Data (Inner {quantile*100}%)\n {dfs.size} / {data["bands"].size} channels used'],
|
|
212
|
+
fontsize=14)
|
|
213
|
+
plt.xlabel('$df_{pp}$ [kHz]', fontsize=16)
|
|
214
|
+
plt.ylabel('Counts', fontsize=16)
|
|
215
|
+
plt.tick_params(axis='both', which='major', labelsize=12)
|
|
216
|
+
|
|
217
|
+
# Plot dfdIs
|
|
218
|
+
dfdIs = data['dfdI']
|
|
219
|
+
dfdIs = dfdIs[(dfdIs >= np.nanquantile(dfdIs, 1-quantile))
|
|
220
|
+
& (dfdIs <= np.nanquantile(dfdIs, quantile))]
|
|
221
|
+
plt.subplot(3, 2, 3)
|
|
222
|
+
plt.hist(dfdIs/1e-3, bins=nbins, alpha=0.7)
|
|
223
|
+
plt.axvline(np.median(dfdIs)/1e-3, color='C1', ls=':', lw=3)
|
|
224
|
+
plt.legend([f'Median : {np.round(np.median(dfdIs)/1e-3,2)}', f'Data (Inner {quantile*100}%)\n {dfdIs.size} / {data["bands"].size} channels used'],
|
|
225
|
+
fontsize=14)
|
|
226
|
+
plt.xlabel('<df/dI> [mHz/pA]', fontsize=16)
|
|
227
|
+
plt.ylabel('Counts', fontsize=16)
|
|
228
|
+
plt.tick_params(axis='both', which='major', labelsize=12)
|
|
229
|
+
|
|
230
|
+
# Plot hhpwrs
|
|
231
|
+
hhpwrs = data['higher_harmonic_power']
|
|
232
|
+
hhpwrs = hhpwrs[(hhpwrs >= np.nanquantile(hhpwrs, 1-quantile))
|
|
233
|
+
& (hhpwrs <= np.nanquantile(hhpwrs, quantile))]
|
|
234
|
+
plt.subplot(3, 2, 4)
|
|
235
|
+
plt.hist(hhpwrs*100, bins=nbins, alpha=0.7)
|
|
236
|
+
plt.axvline(np.median(hhpwrs)*100, color='C1', ls=':', lw=3)
|
|
237
|
+
plt.legend([f'Median : {np.round(np.median(hhpwrs)*100,2)}', f'Data (Inner {quantile*100}%)\n {hhpwrs.size} / {data["bands"].size} channels used'],
|
|
238
|
+
fontsize=14)
|
|
239
|
+
plt.xlabel('Higher Harmonic Power [%]', fontsize=16)
|
|
240
|
+
plt.ylabel('Counts', fontsize=16)
|
|
241
|
+
plt.tick_params(axis='both', which='major', labelsize=12)
|
|
242
|
+
|
|
243
|
+
# Plot phio0s
|
|
244
|
+
phi0s = data['ffs_per_phi0']
|
|
245
|
+
phi0s = phi0s[(phi0s >= np.nanquantile(phi0s, 1-quantile))
|
|
246
|
+
& (phi0s <= np.nanquantile(phi0s, quantile))]
|
|
247
|
+
plt.subplot(3, 2, 5)
|
|
248
|
+
plt.hist(phi0s, bins=nbins, alpha=0.7)
|
|
249
|
+
plt.axvline(np.median(phi0s), color='C1', ls=':', lw=3)
|
|
250
|
+
plt.legend([f'Median : {np.round(np.median(phi0s),3)}', f'Data (Inner {quantile*100}%)\n {phi0s.size} / {data["bands"].size} channels used'],
|
|
251
|
+
fontsize=14)
|
|
252
|
+
plt.xlabel('$\\Phi_0$ [ff]', fontsize=16)
|
|
253
|
+
plt.ylabel('Counts', fontsize=16)
|
|
254
|
+
plt.tight_layout()
|
|
255
|
+
plt.tick_params(axis='both', which='major', labelsize=12)
|
|
256
|
+
return
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def plot_squid_fit(data, band, channel, save_plot=False, S=None,
|
|
260
|
+
plot_dir=None):
|
|
261
|
+
"""
|
|
262
|
+
Plots data taken with ``take_squid_curve`` against fits from
|
|
263
|
+
``fit_squid_curve``.
|
|
264
|
+
|
|
265
|
+
Args
|
|
266
|
+
----
|
|
267
|
+
data: dict
|
|
268
|
+
Output data product from take_squid_curves
|
|
269
|
+
band: int
|
|
270
|
+
Band number
|
|
271
|
+
channel: int
|
|
272
|
+
Channel number
|
|
273
|
+
save_plot: bool
|
|
274
|
+
Boolean to toggle saving of plot to disk
|
|
275
|
+
S: PysmurfControl object, optional
|
|
276
|
+
Used to grab plot_dir and publish plots
|
|
277
|
+
plot_dir: str
|
|
278
|
+
Overrides plot_dir from S, if specified
|
|
279
|
+
"""
|
|
280
|
+
if save_plot:
|
|
281
|
+
if plot_dir is not None:
|
|
282
|
+
pass
|
|
283
|
+
elif S is not None:
|
|
284
|
+
plot_dir = S.plot_dir
|
|
285
|
+
else:
|
|
286
|
+
raise ValueError("Either S or ``plot_dir`` must be specified.")
|
|
287
|
+
idx = np.where((data['bands'] == band) & (
|
|
288
|
+
data['channels'] == channel))[0][0]
|
|
289
|
+
biases = data['fluxramp_ffs']
|
|
290
|
+
|
|
291
|
+
fit_biases = np.linspace(np.min(biases), np.max(biases), 1000)
|
|
292
|
+
|
|
293
|
+
fres = data['res_freq'][idx]
|
|
294
|
+
fig = plt.figure()
|
|
295
|
+
fig.set_facecolor('white')
|
|
296
|
+
plt.plot(biases, data["res_freq_vs_fr"][idx, :], 'co')
|
|
297
|
+
plt.plot(fit_biases, squid_curve_model(fit_biases,
|
|
298
|
+
*data['popts'][idx, :]),
|
|
299
|
+
'C1--')
|
|
300
|
+
ax = plt.gca()
|
|
301
|
+
plt.text(0.0175, 0.975, data['plt_txt'][idx], horizontalalignment='left',
|
|
302
|
+
verticalalignment='top', transform=ax.transAxes, fontsize=10,
|
|
303
|
+
bbox=dict(facecolor='wheat', alpha=0.5, boxstyle='round'))
|
|
304
|
+
plt.xlabel("Flux Bias [Fraction Full Scale FR DAC]", fontsize=14)
|
|
305
|
+
plt.ylabel("Frequency Swing [MHz]", fontsize=14)
|
|
306
|
+
plt.title(
|
|
307
|
+
f"Band {band} Channel {channel} $f_r$ = {np.round(fres,2)} [MHz]")
|
|
308
|
+
if save_plot:
|
|
309
|
+
ctime = int(time.time())
|
|
310
|
+
fig_name = f"{plot_dir}/{ctime}_b{band}c{channel}_dc_squid_curve.png"
|
|
311
|
+
plt.savefig(fig_name)
|
|
312
|
+
if S is not None:
|
|
313
|
+
S.pub.register_file(fig_name, 'dc_squid_curve', plot=True)
|
|
314
|
+
return
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def fit_squid_curves(squid_data, fit_args=None, modify_data=True, show_pb=False):
|
|
318
|
+
'''
|
|
319
|
+
Function for fitting squid curves taken with ``take_squid_curve``.
|
|
320
|
+
|
|
321
|
+
Args
|
|
322
|
+
----
|
|
323
|
+
squid_data: dictionary
|
|
324
|
+
Output data product from take_squid_curves
|
|
325
|
+
fit_args: dictionary
|
|
326
|
+
Dictionary of keyword arguments to be passed to
|
|
327
|
+
estimate_fit_parameters
|
|
328
|
+
|
|
329
|
+
Returns
|
|
330
|
+
-------
|
|
331
|
+
fit_out: dictionary
|
|
332
|
+
Dictionary of fit parameters.
|
|
333
|
+
'''
|
|
334
|
+
fit_out = {}
|
|
335
|
+
|
|
336
|
+
if fit_args is None:
|
|
337
|
+
fit_args = {}
|
|
338
|
+
nharm = fit_args.setdefault('nharmonics_to_estimate', 5)
|
|
339
|
+
|
|
340
|
+
nchans = len(squid_data['channels'])
|
|
341
|
+
fit_guess = np.zeros((nchans, nharm+3))
|
|
342
|
+
fit_result = np.zeros((nchans, nharm+3))
|
|
343
|
+
plt_txt = []
|
|
344
|
+
df = np.zeros(nchans)
|
|
345
|
+
dfdI = np.zeros(nchans)
|
|
346
|
+
hhpwr = np.zeros(nchans)
|
|
347
|
+
for nc in trange(nchans, disable=not show_pb):
|
|
348
|
+
fit_guess[nc, :] = estimate_fit_parameters(squid_data['fluxramp_ffs'],
|
|
349
|
+
squid_data['res_freq_vs_fr'][nc],
|
|
350
|
+
**fit_args)
|
|
351
|
+
fit_result[nc, :], _ = curve_fit(squid_curve_model, squid_data['fluxramp_ffs'],
|
|
352
|
+
squid_data['res_freq_vs_fr'][nc, :], p0=fit_guess[nc, :])
|
|
353
|
+
|
|
354
|
+
txt, params = get_derived_params_and_text(
|
|
355
|
+
squid_data, fit_result[nc, :], nc)
|
|
356
|
+
df[nc] = params['df']
|
|
357
|
+
dfdI[nc] = params['dfdI']
|
|
358
|
+
hhpwr[nc] = params['higher_harmonic_power']
|
|
359
|
+
plt_txt.append(txt)
|
|
360
|
+
|
|
361
|
+
fit_out['initial_guess'] = fit_guess
|
|
362
|
+
fit_out['model_params'] = fit_result
|
|
363
|
+
fit_out['df'] = df
|
|
364
|
+
fit_out['dfdI'] = dfdI
|
|
365
|
+
fit_out['higher_harmonic_power'] = hhpwr
|
|
366
|
+
fit_out['plt_txt'] = np.array(plt_txt)
|
|
367
|
+
|
|
368
|
+
if modify_data:
|
|
369
|
+
squid_data.update(fit_out)
|
|
370
|
+
squid_data['popts'] = fit_result
|
|
371
|
+
squid_data['ffs_per_phi0'] = fit_result[:, 0]
|
|
372
|
+
|
|
373
|
+
return fit_out
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def dfduPhi0_to_dfdI(dfduphi0, M_in=227e-12):
|
|
377
|
+
'''
|
|
378
|
+
Function to convert averaged squid slope (average squid gain) from units of
|
|
379
|
+
Hz/micro-Phi0 to H per pA given the mutual inductance between the squid and
|
|
380
|
+
TES circuit.
|
|
381
|
+
|
|
382
|
+
Args
|
|
383
|
+
----
|
|
384
|
+
dfduphi0 : float
|
|
385
|
+
SQUID gain in Hz per micro-Phi0
|
|
386
|
+
M_in : float
|
|
387
|
+
Mutual inductance between SQUID and TES in units of Henries.
|
|
388
|
+
Returns
|
|
389
|
+
-------
|
|
390
|
+
dfdI_Hz_per_pA : float
|
|
391
|
+
SQUID gain in Hz per pA
|
|
392
|
+
'''
|
|
393
|
+
M_in_phi0_per_A = M_in/2.067833848e-15 # Phi0/A = [Wb/A]/[Wb/Phi0]
|
|
394
|
+
dfdphi0 = dfduphi0*1e6 # Hz/phi0 = [Hz/uphi0]*[uphi0/phi0]
|
|
395
|
+
dfdI_Hz_per_A = dfdphi0*M_in_phi0_per_A # Hz/A = [HZ/Phi0]*[Phi0/A]
|
|
396
|
+
dfdI_Hz_per_pA = dfdI_Hz_per_A*1e-12 # Hz/pA = [Hz/A]*[A/pA]
|
|
397
|
+
return dfdI_Hz_per_pA
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def get_derived_params_and_text(data, model_params, idx):
|
|
401
|
+
'''
|
|
402
|
+
Function to calculate some useful parameters derived from the primary squid
|
|
403
|
+
curve fit parameters and return a text block to add to channel plots.
|
|
404
|
+
|
|
405
|
+
Args
|
|
406
|
+
----
|
|
407
|
+
data : ndarray
|
|
408
|
+
Output data dictionary from ``take_squid_curve``.
|
|
409
|
+
model_params : dict
|
|
410
|
+
Model parameters fitted to ``squid_curve_model``.
|
|
411
|
+
idx : int
|
|
412
|
+
Index of output data array for channel to get text for.
|
|
413
|
+
Returns
|
|
414
|
+
-------
|
|
415
|
+
plot_txt : str
|
|
416
|
+
String to add to squid fit channel plot.
|
|
417
|
+
derived_params : dict
|
|
418
|
+
Dictionary of useful parameters derived from the model fit.
|
|
419
|
+
'''
|
|
420
|
+
fit_curve = sqf.model(data['fluxramp_ffs'], *model_params)
|
|
421
|
+
df_khz = np.ptp(fit_curve)*1000
|
|
422
|
+
phi_over_one_cycle = np.linspace(0, model_params[0], 10000)+model_params[1]
|
|
423
|
+
fit_curve_over_one_cycle = squid_curve_model(
|
|
424
|
+
phi_over_one_cycle, *model_params)
|
|
425
|
+
phi_over_one_cycle /= model_params[0]
|
|
426
|
+
avg_dfdphi_Hzperuphi0 = np.nanmean(np.abs(np.gradient(fit_curve_over_one_cycle)) /
|
|
427
|
+
(np.gradient(phi_over_one_cycle)))
|
|
428
|
+
hhpwr = (np.square(model_params[3]) /
|
|
429
|
+
(np.sum(np.square(model_params[4:]))))**-1
|
|
430
|
+
dfdI = dfduPhi0_to_dfdI(avg_dfdphi_Hzperuphi0, 227e-12)
|
|
431
|
+
plot_txt = ('$f_{res}$' + f' = {data["res_freq"][idx]:.1f} MHz\n' +
|
|
432
|
+
'$\\Phi_0$' + f' = {model_params[0]:.3f} ff\n' +
|
|
433
|
+
'$\\Phi_{offset}$' +
|
|
434
|
+
f' = {model_params[1]/model_params[0]:.3f} Phi0\n' +
|
|
435
|
+
f'df = {df_khz:.1f} kHz\n' +
|
|
436
|
+
'$<df/dI>$' + f' = {dfdI/1e-3:.1f} mHz/pA\n' +
|
|
437
|
+
f'hhpwr = {hhpwr:.3f}')
|
|
438
|
+
derived_params = {'df': df_khz,
|
|
439
|
+
'dfdI': dfdI,
|
|
440
|
+
'higher_harmonic_power': hhpwr}
|
|
441
|
+
return plot_txt, derived_params
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
@sdl.set_action()
|
|
445
|
+
def take_squid_curve(S, cfg, wait_time=0.1, Npts=4, Nsteps=500,
|
|
446
|
+
bands=None, channels=None, lms_gain=None, out_path=None,
|
|
447
|
+
run_analysis=True, analysis_kwargs=None, show_pb=False,
|
|
448
|
+
run_serial_ops=True, frac_full_scale_max=0.3):
|
|
449
|
+
"""
|
|
450
|
+
Takes data in open loop (only slow integral tracking) and steps through flux
|
|
451
|
+
values to trace out a SQUID curve. This can be compared against the tracked
|
|
452
|
+
SQUID curve which might not perfectly replicate this if these curves are
|
|
453
|
+
poorly approximated by a sine wave (or ~3 harmonics of a fourier expansion).
|
|
454
|
+
|
|
455
|
+
Args
|
|
456
|
+
----
|
|
457
|
+
S : ``pysmurf.client.base.smurf_control.SmurfControl``
|
|
458
|
+
``pysmurf`` control object
|
|
459
|
+
cfg : ``sodetlib.det_config.DeviceConfig``
|
|
460
|
+
device config object.
|
|
461
|
+
wait_time: float
|
|
462
|
+
how long you wait between flux step point in seconds
|
|
463
|
+
Npts : int
|
|
464
|
+
number of points you take at each flux bias step to average
|
|
465
|
+
Nsteps : int
|
|
466
|
+
Number of flux points you will take total.
|
|
467
|
+
bands : int, list
|
|
468
|
+
list of bands to take dc SQUID curves on
|
|
469
|
+
channels : dict
|
|
470
|
+
default is None and will run on all channels that are on
|
|
471
|
+
otherwise pass a dictionary with a key for each band
|
|
472
|
+
with values equal to the list of channels to run in each band.
|
|
473
|
+
lms_gain : int
|
|
474
|
+
gain used in tracking loop filter and set in ``tracking_setup``
|
|
475
|
+
defaults to ``None`` and pulls from ``det_config``
|
|
476
|
+
out_path : str, filepath
|
|
477
|
+
directory to output npy file to. defaults to ``None`` and uses pysmurf
|
|
478
|
+
plot directory (``S.plot_dir``)
|
|
479
|
+
run_serial_ops : bool
|
|
480
|
+
If true, will run serial grad descent and eta scan
|
|
481
|
+
frac_full_scale_max : float
|
|
482
|
+
Max value of fraction full scale to use.
|
|
483
|
+
Returns
|
|
484
|
+
-------
|
|
485
|
+
data : dict
|
|
486
|
+
This contains the flux bias array, channel array, and frequency
|
|
487
|
+
shift at each bias value for each channel in each band. As well as
|
|
488
|
+
the dictionary of fitted values returned by fit_squid_curves if
|
|
489
|
+
run_analysis argument is set to True.
|
|
490
|
+
"""
|
|
491
|
+
cur_mode = S.get_cryo_card_ac_dc_mode()
|
|
492
|
+
if cur_mode == 'AC':
|
|
493
|
+
S.set_mode_dc()
|
|
494
|
+
ctime = S.get_timestamp()
|
|
495
|
+
if out_path is None:
|
|
496
|
+
out_path = os.path.join(S.output_dir, f'{ctime}_fr_sweep_data.npy')
|
|
497
|
+
|
|
498
|
+
# This calculates the amount of flux ramp amplitude you need for 1 phi0
|
|
499
|
+
# and then sets the range of flux bias to be enough to achieve the Nphi0s
|
|
500
|
+
# specified in the fucnction call.
|
|
501
|
+
if bands is None:
|
|
502
|
+
bands = cfg.dev.exp['active_bands']
|
|
503
|
+
bands = np.atleast_1d(bands)
|
|
504
|
+
|
|
505
|
+
if channels is None:
|
|
506
|
+
channels = {}
|
|
507
|
+
for band in bands:
|
|
508
|
+
channels[band] = S.which_on(band)
|
|
509
|
+
|
|
510
|
+
bias_peak = frac_full_scale_max
|
|
511
|
+
|
|
512
|
+
# This is the step size calculated from range and number of steps
|
|
513
|
+
bias_step = np.abs(2*bias_peak)/float(Nsteps)
|
|
514
|
+
|
|
515
|
+
channels_out = []
|
|
516
|
+
bands_out = []
|
|
517
|
+
for band in bands:
|
|
518
|
+
channels_out.extend(channels[band])
|
|
519
|
+
bands_out.extend(list(np.ones(len(channels[band]))*band))
|
|
520
|
+
biases = np.arange(-bias_peak, bias_peak, bias_step)
|
|
521
|
+
|
|
522
|
+
# final output data dictionary
|
|
523
|
+
data = {}
|
|
524
|
+
data['meta'] = sdl.get_metadata(S, cfg)
|
|
525
|
+
data['bands'] = np.asarray(bands_out)
|
|
526
|
+
data['channels'] = np.asarray(channels_out)
|
|
527
|
+
data['fluxramp_ffs'] = biases
|
|
528
|
+
data['res_freq_vs_fr'] = []
|
|
529
|
+
|
|
530
|
+
unique_bands = np.unique(np.asarray(bands_out, dtype=int))
|
|
531
|
+
prev_lms_enable1 = {}
|
|
532
|
+
prev_lms_enable2 = {}
|
|
533
|
+
prev_lms_enable3 = {}
|
|
534
|
+
prev_lms_gain = {}
|
|
535
|
+
|
|
536
|
+
# take SQUID data
|
|
537
|
+
try:
|
|
538
|
+
for band in unique_bands:
|
|
539
|
+
band_cfg = cfg.dev.bands[band]
|
|
540
|
+
if lms_gain is None:
|
|
541
|
+
lms_gain = band_cfg['lms_gain']
|
|
542
|
+
S.log(f'{len(channels[band])} channels on in band {band},'
|
|
543
|
+
' configuring band for simple, integral tracking')
|
|
544
|
+
S.log(
|
|
545
|
+
f'-> Setting lmsEnable[1-3] and lmsGain to 0 for band {band}.')
|
|
546
|
+
prev_lms_enable1[band] = S.get_lms_enable1(band)
|
|
547
|
+
prev_lms_enable2[band] = S.get_lms_enable2(band)
|
|
548
|
+
prev_lms_enable3[band] = S.get_lms_enable3(band)
|
|
549
|
+
prev_lms_gain[band] = S.get_lms_gain(band)
|
|
550
|
+
S.set_lms_enable1(band, 0)
|
|
551
|
+
S.set_lms_enable2(band, 0)
|
|
552
|
+
S.set_lms_enable3(band, 0)
|
|
553
|
+
S.set_lms_gain(band, lms_gain)
|
|
554
|
+
|
|
555
|
+
fs = {}
|
|
556
|
+
S.log(
|
|
557
|
+
'\rSetting flux ramp bias to 0 V\033[K before tune'.format(-bias_peak))
|
|
558
|
+
S.set_fixed_flux_ramp_bias(0.)
|
|
559
|
+
|
|
560
|
+
for band in unique_bands:
|
|
561
|
+
fs[band] = []
|
|
562
|
+
if run_serial_ops:
|
|
563
|
+
S.run_serial_gradient_descent(band)
|
|
564
|
+
S.run_serial_eta_scan(band)
|
|
565
|
+
S.toggle_feedback(band)
|
|
566
|
+
|
|
567
|
+
small_steps_to_starting_bias = np.arange(
|
|
568
|
+
-bias_peak, 0, bias_step)[::-1]
|
|
569
|
+
|
|
570
|
+
# step from zero (where we tuned) down to starting bias
|
|
571
|
+
S.log('Slowly shift flux ramp voltage to place where we begin.')
|
|
572
|
+
for b in small_steps_to_starting_bias:
|
|
573
|
+
S.set_fixed_flux_ramp_bias(b, do_config=False)
|
|
574
|
+
time.sleep(wait_time)
|
|
575
|
+
|
|
576
|
+
# make sure we start at bias_low
|
|
577
|
+
S.log(f'\rSetting flux ramp bias low at {-bias_peak} V')
|
|
578
|
+
S.set_fixed_flux_ramp_bias(-bias_peak, do_config=False)
|
|
579
|
+
time.sleep(wait_time)
|
|
580
|
+
|
|
581
|
+
S.log('Starting to take flux ramp.')
|
|
582
|
+
|
|
583
|
+
for b in tqdm(biases, disable=(not show_pb)):
|
|
584
|
+
S.set_fixed_flux_ramp_bias(b, do_config=False)
|
|
585
|
+
time.sleep(wait_time)
|
|
586
|
+
for band in unique_bands:
|
|
587
|
+
fsamp = np.zeros(shape=(Npts, len(channels[band])))
|
|
588
|
+
for i in range(Npts):
|
|
589
|
+
fsamp[i, :] = S.get_loop_filter_output_array(band)[
|
|
590
|
+
channels[band]]
|
|
591
|
+
fsampmean = np.nanmean(fsamp, axis=0)
|
|
592
|
+
fs[band].append(fsampmean)
|
|
593
|
+
|
|
594
|
+
S.log('Done taking flux ramp data.')
|
|
595
|
+
fres = []
|
|
596
|
+
for i, band in enumerate(unique_bands):
|
|
597
|
+
fres_loop = S.channel_to_freq(band).tolist()
|
|
598
|
+
fres.extend(fres_loop)
|
|
599
|
+
# stack
|
|
600
|
+
lfovsfr = np.dstack(fs[band])[0]
|
|
601
|
+
fvsfr = np.array(
|
|
602
|
+
[arr+fres for (arr, fres) in zip(lfovsfr, fres_loop)])
|
|
603
|
+
if i == 0:
|
|
604
|
+
data['res_freq_vs_fr'] = fvsfr
|
|
605
|
+
else:
|
|
606
|
+
data['res_freq_vs_fr'] = np.concatenate(
|
|
607
|
+
(data['res_freq_vs_fr'], fvsfr), axis=0)
|
|
608
|
+
|
|
609
|
+
data['res_freq'] = np.asarray(fres)
|
|
610
|
+
data['filepath'] = out_path
|
|
611
|
+
|
|
612
|
+
# always zero and restore state of system
|
|
613
|
+
finally:
|
|
614
|
+
# done - zero and unset
|
|
615
|
+
S.set_fixed_flux_ramp_bias(0, do_config=False)
|
|
616
|
+
S.unset_fixed_flux_ramp_bias()
|
|
617
|
+
for band in unique_bands:
|
|
618
|
+
S.set_lms_enable1(band, prev_lms_enable1[band])
|
|
619
|
+
S.set_lms_enable2(band, prev_lms_enable2[band])
|
|
620
|
+
S.set_lms_enable3(band, prev_lms_enable3[band])
|
|
621
|
+
S.set_lms_gain(band, lms_gain)
|
|
622
|
+
|
|
623
|
+
if cur_mode == 'AC':
|
|
624
|
+
S.set_mode_ac()
|
|
625
|
+
|
|
626
|
+
if run_analysis:
|
|
627
|
+
kw = {'modify_data': True}
|
|
628
|
+
if analysis_kwargs is not None:
|
|
629
|
+
kw.update(analysis_kwargs)
|
|
630
|
+
try:
|
|
631
|
+
fit_dict = fit_squid_curves(data, **kw)
|
|
632
|
+
except Exception as e:
|
|
633
|
+
S.log("Analysis of SQUID Curve failed!")
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
# save dataset for each iteration, just to make sure it gets
|
|
637
|
+
# written to disk
|
|
638
|
+
np.save(out_path, data)
|
|
639
|
+
S.pub.register_file(out_path, 'dc_squid_curve', format='npy')
|
|
640
|
+
|
|
641
|
+
return data
|