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.
@@ -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