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,508 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Given a complex s21 sweep, returns the data fit to the resonator model and the
|
|
3
|
+
resonator parameters and provides tools for plotting the fit results.
|
|
4
|
+
|
|
5
|
+
Based on equation 11 from Kahlil et al. and adapted from Columbia KIDs open
|
|
6
|
+
source analysis code.
|
|
7
|
+
'''
|
|
8
|
+
import numpy as np
|
|
9
|
+
from lmfit import Model
|
|
10
|
+
import matplotlib.pyplot as plt
|
|
11
|
+
from matplotlib.gridspec import GridSpec
|
|
12
|
+
|
|
13
|
+
def linear_resonator(f, f_0, Q, Q_e_real, Q_e_imag):
|
|
14
|
+
'''
|
|
15
|
+
Function for a resonator with asymmetry parameterized by the imaginary
|
|
16
|
+
part of ``Q_e``. The real part of ``Q_e`` is what we typically refer to as
|
|
17
|
+
the coupled Q, ``Q_c``.
|
|
18
|
+
'''
|
|
19
|
+
Q_e = Q_e_real + 1j*Q_e_imag
|
|
20
|
+
return (1 - (Q * Q_e**(-1) /(1 + 2j * Q * (f - f_0) / f_0) ) )
|
|
21
|
+
|
|
22
|
+
def cable_delay(f, delay, phi, f_min):
|
|
23
|
+
'''
|
|
24
|
+
Function implements a time delay (phase variation linear with frequency).
|
|
25
|
+
'''
|
|
26
|
+
return np.exp(1j * (-2 * np.pi * (f - f_min) * delay + phi))
|
|
27
|
+
|
|
28
|
+
def general_cable(f, delay, phi, f_min, A_mag, A_slope):
|
|
29
|
+
'''
|
|
30
|
+
Function implements a time delay (phase variation linear with frequency) and
|
|
31
|
+
attenuation slope characterizing a background RF cable transfer function.
|
|
32
|
+
'''
|
|
33
|
+
phase_term = cable_delay(f,delay,phi,f_min)
|
|
34
|
+
magnitude_term = ((f-f_min)*A_slope + 1)* A_mag
|
|
35
|
+
return magnitude_term*phase_term
|
|
36
|
+
|
|
37
|
+
def resonator_cable(f, f_0, Q, Q_e_real, Q_e_imag, delay, phi, f_min, A_mag,
|
|
38
|
+
A_slope):
|
|
39
|
+
'''
|
|
40
|
+
Function that includes asymmetric resonator (``linear_resonator``) and cable
|
|
41
|
+
transfer functions (``general_cable``). Which most closely matches our full
|
|
42
|
+
measured transfer function.
|
|
43
|
+
'''
|
|
44
|
+
resonator_term = linear_resonator(f, f_0, Q, Q_e_real, Q_e_imag)
|
|
45
|
+
cable_term = general_cable(f, delay, phi, f_min, A_mag, A_slope)
|
|
46
|
+
return cable_term*resonator_term
|
|
47
|
+
|
|
48
|
+
def full_fit(freqs, real, imag):
|
|
49
|
+
'''
|
|
50
|
+
Fitting function that takes in frequency and real and imaginary parts of the
|
|
51
|
+
transmission of a resonator (needs to trimmed down to only data for a
|
|
52
|
+
single resonator) and returns fitted parameters to the ``resonator_cable``
|
|
53
|
+
model.
|
|
54
|
+
|
|
55
|
+
Args
|
|
56
|
+
----
|
|
57
|
+
freqs : float ndarray
|
|
58
|
+
Frequencies that line up with complex transmission data.
|
|
59
|
+
real : float ndarray
|
|
60
|
+
Real part of resonator complex transmission to be fit
|
|
61
|
+
imag : float ndarray
|
|
62
|
+
Imaginary part of resonator complex transmission to be fit.
|
|
63
|
+
|
|
64
|
+
Returns
|
|
65
|
+
-------
|
|
66
|
+
result : (`lmfit.Model.ModelResult`)
|
|
67
|
+
This is a class of lmfit that contains all of the fitted parameters as
|
|
68
|
+
well as a number of other pieces of data and metadata related to the fit
|
|
69
|
+
and some helper functions for plotting and manipulating the data in the
|
|
70
|
+
object.
|
|
71
|
+
'''
|
|
72
|
+
s21_complex = np.vectorize(complex)(real, imag)
|
|
73
|
+
|
|
74
|
+
#set our initial guesses
|
|
75
|
+
argmin_s21 = np.abs(s21_complex).argmin()
|
|
76
|
+
fmin = freqs.min()
|
|
77
|
+
fmax = freqs.max()
|
|
78
|
+
f_0_guess = freqs[argmin_s21]
|
|
79
|
+
Q_min = 0.1 * (f_0_guess / (fmax - fmin))
|
|
80
|
+
delta_f = np.diff(freqs)
|
|
81
|
+
min_delta_f = delta_f[delta_f > 0].min()
|
|
82
|
+
Q_max = f_0_guess / min_delta_f
|
|
83
|
+
Q_guess = np.sqrt(Q_min * Q_max)
|
|
84
|
+
s21_min = np.abs(s21_complex[argmin_s21])
|
|
85
|
+
s21_max = np.abs(s21_complex).max()
|
|
86
|
+
Q_e_real_guess = Q_guess / (1 - s21_min / s21_max)
|
|
87
|
+
A_slope, A_offset = np.polyfit(freqs - fmin, np.abs(s21_complex), 1)
|
|
88
|
+
A_mag = A_offset
|
|
89
|
+
A_mag_slope = A_slope / A_mag
|
|
90
|
+
phi_slope, phi_offset = np.polyfit(freqs - fmin, np.unwrap(np.angle(s21_complex)), 1)
|
|
91
|
+
delay = -phi_slope / (2 * np.pi)
|
|
92
|
+
|
|
93
|
+
#make our model
|
|
94
|
+
totalmodel = Model(resonator_cable)
|
|
95
|
+
params = totalmodel.make_params(f_0=f_0_guess,
|
|
96
|
+
Q=Q_guess,
|
|
97
|
+
Q_e_real=Q_e_real_guess,
|
|
98
|
+
Q_e_imag=0,
|
|
99
|
+
delay=delay,
|
|
100
|
+
phi=phi_offset,
|
|
101
|
+
f_min=fmin,
|
|
102
|
+
A_mag=A_mag,
|
|
103
|
+
A_slope=A_mag_slope)
|
|
104
|
+
#set some bounds
|
|
105
|
+
params['f_0'].set(min=freqs.min(), max=freqs.max())
|
|
106
|
+
params['Q'].set(min=Q_min, max=Q_max)
|
|
107
|
+
params['Q_e_real'].set(min=1, max=1e7)
|
|
108
|
+
params['Q_e_imag'].set(min=-1e7, max=1e7)
|
|
109
|
+
params['phi'].set(min=phi_offset-np.pi, max=phi_offset+np.pi)
|
|
110
|
+
|
|
111
|
+
#fit it
|
|
112
|
+
result = totalmodel.fit(s21_complex, params, f=freqs)
|
|
113
|
+
return result
|
|
114
|
+
|
|
115
|
+
def get_qi(Q, Q_e_real, Q_e_imag):
|
|
116
|
+
'''
|
|
117
|
+
Function for deriving the internal quality factor from the fitted quality
|
|
118
|
+
factors (Q and Qc).
|
|
119
|
+
|
|
120
|
+
Args
|
|
121
|
+
----
|
|
122
|
+
Q : float
|
|
123
|
+
Total resonator quality factor output parameter of ``full_fit``
|
|
124
|
+
Q_e_real : float
|
|
125
|
+
Resonator coupled quality factor output parameter of ``full_fit``
|
|
126
|
+
Returns
|
|
127
|
+
-------
|
|
128
|
+
Qi : float
|
|
129
|
+
Resonator internal quality factor.
|
|
130
|
+
'''
|
|
131
|
+
Qi = (Q**-1 - np.real((Q_e_real+1j*Q_e_imag)**-1))**-1
|
|
132
|
+
return Qi
|
|
133
|
+
|
|
134
|
+
def get_br(Q, f_0):
|
|
135
|
+
'''
|
|
136
|
+
Function for deriving the resonator bandwidth from the fit results.
|
|
137
|
+
|
|
138
|
+
Args
|
|
139
|
+
----
|
|
140
|
+
Q : float
|
|
141
|
+
Total resonator quality factor output parameter of ``full_fit``
|
|
142
|
+
f_0 : float
|
|
143
|
+
Resonance frequency output parameter of ``full_fit`` in Hz.
|
|
144
|
+
Returns
|
|
145
|
+
-------
|
|
146
|
+
br : float
|
|
147
|
+
Resonator bandwidth in Hz.
|
|
148
|
+
'''
|
|
149
|
+
br = f_0/Q
|
|
150
|
+
return br
|
|
151
|
+
|
|
152
|
+
def reduced_chi_squared(ydata, ymod, n_param=9, sd=None):
|
|
153
|
+
'''
|
|
154
|
+
Reduced chi squared in lmfit does not return something reasonable so this
|
|
155
|
+
is a handwritten function to calculate it since we want standard deviation
|
|
156
|
+
to be the complex error.
|
|
157
|
+
|
|
158
|
+
Args
|
|
159
|
+
----
|
|
160
|
+
ydata : float ndarray
|
|
161
|
+
complex data to calculate reduced chi squared on.
|
|
162
|
+
ymod : float ndarray
|
|
163
|
+
model fit result of ydata at same x-locations as ydata is sampled.
|
|
164
|
+
n_param : int
|
|
165
|
+
Number of parameters in the fit.
|
|
166
|
+
sd : float ndarray
|
|
167
|
+
standard deviation of data
|
|
168
|
+
Returns
|
|
169
|
+
-------
|
|
170
|
+
br : float
|
|
171
|
+
Resonator bandwidth in Hz.
|
|
172
|
+
'''
|
|
173
|
+
if sd is None:
|
|
174
|
+
sdr = np.std(np.real(ydata))
|
|
175
|
+
sdi = np.std(np.imag(ydata))
|
|
176
|
+
sd = sdr + 1j*sdi
|
|
177
|
+
chisq = np.sum((np.real(ydata) - np.real(ymod))**2/((np.real(sd))**2)) +\
|
|
178
|
+
np.sum((np.imag(ydata) - np.imag(ymod))**2/((np.imag(sd))**2))
|
|
179
|
+
nu=2*ydata.size-n_param #multiply the usual by 2 since complex
|
|
180
|
+
red_chisq = chisq/nu
|
|
181
|
+
return chisq, red_chisq
|
|
182
|
+
|
|
183
|
+
def fit_tune(tunefile):
|
|
184
|
+
"""
|
|
185
|
+
Automated fitting of resonator parameters from one tuning file.
|
|
186
|
+
|
|
187
|
+
Args
|
|
188
|
+
----
|
|
189
|
+
tunefile : str, filepath
|
|
190
|
+
Full directories of one tunning file.
|
|
191
|
+
Returns
|
|
192
|
+
-------
|
|
193
|
+
dres : dict
|
|
194
|
+
a dictionary containing all of the fit results for all resonances in the provided tunefile. The keys are organized as::
|
|
195
|
+
|
|
196
|
+
{
|
|
197
|
+
tunefile: str, filepath - full directories of one tunning file
|
|
198
|
+
band: 1d int array with shape (ndets) - smurf band for each detector
|
|
199
|
+
channels : 1d int array with shape (ndets) - Smurf channel for each
|
|
200
|
+
detector. If unassigned, set to -1
|
|
201
|
+
res_index: 1d int array with shape (ndets) -- Index of the resonator within each band
|
|
202
|
+
res_freqs: 1d int array of resonance freqs (as is found in the tunefile)
|
|
203
|
+
model_params : dictionary for 9 parameters of resonator_cable
|
|
204
|
+
f_0 : 1d array with shape (ndets)
|
|
205
|
+
Q : 1d array with shape (ndets)
|
|
206
|
+
Q_e_real : 1d array with shape (ndets)
|
|
207
|
+
Q_e_imag: 1d array with shape (ndets)
|
|
208
|
+
delay: 1d array with shape (ndets)
|
|
209
|
+
phi: 1d array with shape (ndets)
|
|
210
|
+
f_min: 1d array with shape (ndets)
|
|
211
|
+
A_mag: 1d array with shape (ndets)
|
|
212
|
+
A_slope: 1d array with shape (ndets)
|
|
213
|
+
derived_params : dictionary for derived parameters
|
|
214
|
+
Qi: 1d array with shape (ndets)
|
|
215
|
+
br: 1d array with shape (ndets)
|
|
216
|
+
depth: 1d array with shape (ndets)
|
|
217
|
+
find_freq_ctime : 1d string array with shape (ndets) - ctime find_freq was taken
|
|
218
|
+
S21: 2d array (ndets, nsamps) of measured S21
|
|
219
|
+
scan_freqs: 2d array (ndets, ndsamps) of freqs used for S21 scan
|
|
220
|
+
chi2 : 2d float array of shape (ndets x 2) - chi-squared goodness of fit
|
|
221
|
+
sid : str, session ID
|
|
222
|
+
}
|
|
223
|
+
"""
|
|
224
|
+
dres = {}
|
|
225
|
+
data = np.load(tunefile,allow_pickle=True).item()
|
|
226
|
+
|
|
227
|
+
dres['tunefile'] = tunefile
|
|
228
|
+
model_params = {'f_0': [], 'Q': [], 'Q_e_real' : [], 'Q_e_imag': [],
|
|
229
|
+
'delay': [], 'phi': [], 'f_min': [], 'A_mag': [],
|
|
230
|
+
'A_slope': []}
|
|
231
|
+
derived_params = {'Qi': [], 'br': [], 'depth': []}
|
|
232
|
+
bands = []
|
|
233
|
+
channels = []
|
|
234
|
+
res_index = []
|
|
235
|
+
chi2 = []
|
|
236
|
+
sid = []
|
|
237
|
+
find_freq_ctime = []
|
|
238
|
+
S21s = []
|
|
239
|
+
scan_freqs = []
|
|
240
|
+
res_freqs = []
|
|
241
|
+
|
|
242
|
+
for band in sorted(list(data.keys())):
|
|
243
|
+
if 'resonances' in list(data[band].keys()):
|
|
244
|
+
for idx in list(data[band]['resonances'].keys()):
|
|
245
|
+
scan=data[band]['resonances'][idx]
|
|
246
|
+
f=scan['freq_eta_scan']
|
|
247
|
+
if (band > 3) & (np.mean(f) > 6000):
|
|
248
|
+
f-=2000
|
|
249
|
+
S21=scan['resp_eta_scan']
|
|
250
|
+
result=full_fit(f,S21.real,S21.imag)
|
|
251
|
+
|
|
252
|
+
# Need to check if this plays well with being in a dict/pickling
|
|
253
|
+
S21_mod = result.best_fit.real+1j*result.best_fit.imag
|
|
254
|
+
|
|
255
|
+
bands.append(band)
|
|
256
|
+
channels.append(scan['channel'])
|
|
257
|
+
res_index.append(idx)
|
|
258
|
+
res_freqs.append(scan['freq'])
|
|
259
|
+
chi2.append(reduced_chi_squared(S21, S21_mod))
|
|
260
|
+
find_freq_ctime.append(data[band]['find_freq']['timestamp'][0])
|
|
261
|
+
S21s.append(S21)
|
|
262
|
+
scan_freqs.append(f)
|
|
263
|
+
|
|
264
|
+
model_params['f_0'].append(result.best_values['f_0'])
|
|
265
|
+
model_params['Q'].append(result.best_values['Q'])
|
|
266
|
+
model_params['Q_e_real'].append(result.best_values['Q_e_real'])
|
|
267
|
+
model_params['Q_e_imag'].append(result.best_values['Q_e_imag'])
|
|
268
|
+
model_params['delay'].append(result.best_values['delay'])
|
|
269
|
+
model_params['phi'].append(result.best_values['phi'])
|
|
270
|
+
model_params['f_min'].append(result.best_values['f_min'])
|
|
271
|
+
model_params['A_mag'].append(result.best_values['A_mag'])
|
|
272
|
+
model_params['A_slope'].append(result.best_values['A_slope'])
|
|
273
|
+
|
|
274
|
+
Qi = get_qi(result.best_values['Q'], result.best_values['Q_e_real'],
|
|
275
|
+
result.best_values['Q_e_imag'])
|
|
276
|
+
br = get_br(result.best_values['Q'], result.best_values['f_0'])
|
|
277
|
+
depth = np.abs(S21_mod).max()/np.abs(S21_mod).min()
|
|
278
|
+
derived_params['Qi'].append(Qi)
|
|
279
|
+
derived_params['br'].append(br)
|
|
280
|
+
derived_params['depth'].append(depth)
|
|
281
|
+
|
|
282
|
+
bands = np.array(bands)
|
|
283
|
+
channels = np.array(channels)
|
|
284
|
+
res_index = np.array(res_index)
|
|
285
|
+
S21s = np.array(S21s)
|
|
286
|
+
scan_freqs = np.array(scan_freqs)
|
|
287
|
+
chi2 = np.array(chi2)
|
|
288
|
+
sid = np.array(sid)
|
|
289
|
+
for par in model_params:
|
|
290
|
+
model_params[par] = np.array(model_params[par])
|
|
291
|
+
for par in derived_params:
|
|
292
|
+
derived_params[par] = np.array(derived_params[par])
|
|
293
|
+
|
|
294
|
+
dres['bands'] = bands
|
|
295
|
+
dres['channels'] = channels
|
|
296
|
+
dres['res_index'] = res_index
|
|
297
|
+
dres['res_freqs'] = res_freqs
|
|
298
|
+
dres['sid'] = sid
|
|
299
|
+
dres['S21'] = S21s
|
|
300
|
+
dres['scan_freqs'] = scan_freqs
|
|
301
|
+
dres['chi2'] = chi2
|
|
302
|
+
dres['model_params'] = model_params
|
|
303
|
+
dres['derived_params'] = derived_params
|
|
304
|
+
return dres
|
|
305
|
+
|
|
306
|
+
def get_resfit_plot_txt(resfit_dict, band, rix):
|
|
307
|
+
'''
|
|
308
|
+
Function to assemble some key fit information out of the fit dictionary
|
|
309
|
+
into a text block for adding to plots.
|
|
310
|
+
|
|
311
|
+
Args
|
|
312
|
+
----
|
|
313
|
+
resfit_dict : dict
|
|
314
|
+
Dictionary with fit results output from ``fit_tune``
|
|
315
|
+
band : int
|
|
316
|
+
Smurf band of channel to get plot text for.
|
|
317
|
+
rix : int
|
|
318
|
+
Resonator index in tunefile (sorted by frequency order of setup_notches
|
|
319
|
+
channels) of channel to get plot text for.
|
|
320
|
+
|
|
321
|
+
Returns
|
|
322
|
+
-------
|
|
323
|
+
text : str
|
|
324
|
+
text block to add to resonator fit channel plot.
|
|
325
|
+
'''
|
|
326
|
+
# Get index of band, rix pair provided by user
|
|
327
|
+
idx = np.where(
|
|
328
|
+
(resfit_dict['res_index'] == rix)
|
|
329
|
+
& (resfit_dict['bands'] == band)
|
|
330
|
+
)[0][0]
|
|
331
|
+
# idx = int(np.where((resfit_dict['res_index'][:,0] == band) \
|
|
332
|
+
# & (resfit_dict['res_index'][:,1] == rix))[0])
|
|
333
|
+
|
|
334
|
+
mparams = resfit_dict['model_params']
|
|
335
|
+
dparams = resfit_dict['derived_params']
|
|
336
|
+
channel = resfit_dict['channels'][idx]
|
|
337
|
+
chi2 = resfit_dict['chi2'][idx]
|
|
338
|
+
if channel == -1:
|
|
339
|
+
text = f'Channel unassigned'
|
|
340
|
+
else:
|
|
341
|
+
text = f'Band {band} Channel {channel}'
|
|
342
|
+
text += '\n$f_r$: '+ f"{np.round(mparams['f_0'][idx],1)} MHz"
|
|
343
|
+
text += '\n$Q_i$: '+ f"{int(dparams['Qi'][idx])}"
|
|
344
|
+
text += '\nBW: '+ f"{int(dparams['br'][idx]*1e3)} kHz"
|
|
345
|
+
# text += '\nfit $\chi^2$: ' + f"{np.round(chi2,3)}"
|
|
346
|
+
return text
|
|
347
|
+
|
|
348
|
+
def plot_channel_fit(fit_dict, idx):
|
|
349
|
+
'''
|
|
350
|
+
Function for plotting single channel eta_scan data from a tunefile with
|
|
351
|
+
a fit to an asymmetric resonator model ``resonator_cable``.
|
|
352
|
+
|
|
353
|
+
Args
|
|
354
|
+
----
|
|
355
|
+
fit_dict : dict
|
|
356
|
+
fit results dictionary from ``fit_tune``
|
|
357
|
+
band : int
|
|
358
|
+
smurf band of resonator to plot
|
|
359
|
+
channel : int
|
|
360
|
+
smurf channel of resonator to plot
|
|
361
|
+
'''
|
|
362
|
+
|
|
363
|
+
rix = fit_dict['res_index'][idx]
|
|
364
|
+
band = fit_dict['bands'][idx]
|
|
365
|
+
|
|
366
|
+
freqs = fit_dict['scan_freqs'][idx]
|
|
367
|
+
res_freq = fit_dict['res_freqs'][idx]
|
|
368
|
+
freqs_plot = 1e3*(freqs - res_freq)
|
|
369
|
+
resp_data = fit_dict['S21'][idx]
|
|
370
|
+
|
|
371
|
+
params = {}
|
|
372
|
+
for p_opt in fit_dict['model_params']:
|
|
373
|
+
params[p_opt] = fit_dict['model_params'][p_opt][idx]
|
|
374
|
+
|
|
375
|
+
resp_model = resonator_cable(freqs,**params)
|
|
376
|
+
fr_idx = np.argmin(np.abs(freqs - fit_dict['model_params']['f_0'][idx]))
|
|
377
|
+
|
|
378
|
+
fig = plt.figure(figsize = (12,6),constrained_layout=True)
|
|
379
|
+
gs = GridSpec(2, 6, figure=fig)
|
|
380
|
+
ax1 = fig.add_subplot(gs[:1,0:2])
|
|
381
|
+
ax1.plot(freqs_plot,20*np.log10(np.abs(resp_data)),
|
|
382
|
+
'C0o',label = 'Data')
|
|
383
|
+
ax1.plot(freqs_plot,20*np.log10(np.abs(resp_model)),
|
|
384
|
+
'C1-',label = 'Fit')
|
|
385
|
+
ax1.plot(freqs_plot[fr_idx],20*np.log10(np.abs(resp_data[fr_idx])),
|
|
386
|
+
'rx',ms = 12,label = '$f_r$ - fit')
|
|
387
|
+
ax1.set_xlabel('Offset $(f-f_{min})$ [kHz]')
|
|
388
|
+
ax1.set_ylabel('$|S_{21}|$ [dB]')
|
|
389
|
+
ax1.legend(loc = 'lower left',fontsize = 12)
|
|
390
|
+
ax2 = fig.add_subplot(gs[1:,0:2])
|
|
391
|
+
ax2.plot(freqs_plot,np.rad2deg(np.unwrap(np.angle(resp_data))),'C0o')
|
|
392
|
+
ax2.plot(freqs_plot,np.rad2deg(np.unwrap(np.angle(resp_model))),'C1-')
|
|
393
|
+
ax2.plot(freqs_plot[fr_idx],np.rad2deg(np.unwrap(np.angle(resp_data))[fr_idx]),
|
|
394
|
+
'rx',ms = 12)
|
|
395
|
+
ax2.set_xlabel('Offset $(f-f_r)$ [kHz]')
|
|
396
|
+
ax2.set_ylabel('Phase $S_{21}$ [$^{\\circ}$]')
|
|
397
|
+
ax3 = fig.add_subplot(gs[:,2:])
|
|
398
|
+
ax3.plot(np.real(resp_data),np.imag(resp_data),'C0o')
|
|
399
|
+
ax3.plot(np.real(resp_model),np.imag(resp_model),'C1-')
|
|
400
|
+
ax3.plot(np.real(resp_model[fr_idx]),np.imag(resp_model[fr_idx]),
|
|
401
|
+
'rx',ms = 12)
|
|
402
|
+
ax3.set_xlabel('Re($S_{21}$) [I]')
|
|
403
|
+
ax3.set_ylabel('Im($S_{21}$) [Q]')
|
|
404
|
+
mrange = 1.1*np.max(np.concatenate((np.abs(np.real(resp_data)),
|
|
405
|
+
np.abs(np.imag(resp_data)),
|
|
406
|
+
np.abs(np.real(resp_model)),
|
|
407
|
+
np.abs(np.imag(resp_model)))))
|
|
408
|
+
ax3.set_xlim(-mrange,mrange)
|
|
409
|
+
ax3.set_ylim(-mrange,mrange)
|
|
410
|
+
ax3.axhline(0,color = 'k')
|
|
411
|
+
ax3.axvline(0,color = 'k')
|
|
412
|
+
|
|
413
|
+
restext = get_resfit_plot_txt(fit_dict, band, rix)
|
|
414
|
+
ax3.text(0.025,0.05,restext,
|
|
415
|
+
bbox=dict(facecolor='wheat',
|
|
416
|
+
alpha=0.5,
|
|
417
|
+
boxstyle="round",),
|
|
418
|
+
transform=ax3.transAxes)
|
|
419
|
+
return fig, np.array([ax1, ax2, ax3])
|
|
420
|
+
|
|
421
|
+
def plot_fit_summary(fit_dict, plot_style=None, quantile=0.98):
|
|
422
|
+
'''
|
|
423
|
+
Function for plotting full wafer eta_scan data from a tunefile.
|
|
424
|
+
Plots distributions of Qi, dip depth, bandwidth, and frequency spacing
|
|
425
|
+
for all resonators within specified quantile.
|
|
426
|
+
|
|
427
|
+
Args
|
|
428
|
+
----
|
|
429
|
+
fit_dict : dict
|
|
430
|
+
fit results dictionary from ``fit_tune``
|
|
431
|
+
plot_style : dict
|
|
432
|
+
keyword arguments to pass to the histogram plotting for formatting.
|
|
433
|
+
quantile: float
|
|
434
|
+
inner X percent to graph
|
|
435
|
+
'''
|
|
436
|
+
|
|
437
|
+
Qis = fit_dict['derived_params']['Qi']
|
|
438
|
+
depths = fit_dict['derived_params']['depth']
|
|
439
|
+
bws = fit_dict['derived_params']['br']
|
|
440
|
+
frs = fit_dict['model_params']['f_0']
|
|
441
|
+
seps = np.diff(frs)
|
|
442
|
+
|
|
443
|
+
Qi_quant = Qis[(Qis>=np.nanquantile(Qis, 1-quantile)) \
|
|
444
|
+
& (Qis<=np.nanquantile(Qis, quantile))]
|
|
445
|
+
depth_quant = depths[(depths>=np.nanquantile(depths, 1-quantile)) \
|
|
446
|
+
& (depths<=np.nanquantile(depths, quantile))]
|
|
447
|
+
bw_quant = bws[(bws>=np.nanquantile(bws, 1-quantile)) \
|
|
448
|
+
& (bws<=np.nanquantile(bws, quantile))]
|
|
449
|
+
seps_quant = seps[(seps>=np.nanquantile(seps, 1-quantile)) \
|
|
450
|
+
& (seps<=np.nanquantile(seps, quantile))]
|
|
451
|
+
|
|
452
|
+
fig, axes = plt.subplots(2, 2, figsize=(16, 8))
|
|
453
|
+
|
|
454
|
+
if plot_style is None:
|
|
455
|
+
plot_style = {'bins': 30,
|
|
456
|
+
'color': 'gold',
|
|
457
|
+
'alpha': 0.5,
|
|
458
|
+
'edgecolor': 'orange',
|
|
459
|
+
'lw': 2}
|
|
460
|
+
#Qi plot
|
|
461
|
+
ax = axes[0, 0]
|
|
462
|
+
ax.hist(Qi_quant/1e5, **plot_style)
|
|
463
|
+
Qi_med = np.median(Qi_quant/1e5)
|
|
464
|
+
ax.axvline(np.median(Qi_quant/1e5),color = 'purple',
|
|
465
|
+
label = f'Median: {np.round(Qi_med,2)} $\\times10^5$')
|
|
466
|
+
ax.legend(loc = 'upper right')
|
|
467
|
+
ax.set_xlabel('$Q_i\\times10^5$')
|
|
468
|
+
ax.set_ylabel('Counts')
|
|
469
|
+
|
|
470
|
+
#Dip depth plot
|
|
471
|
+
ax = axes[0, 1]
|
|
472
|
+
ax.hist(20*np.log10(depth_quant),**plot_style)
|
|
473
|
+
dep_med = np.median(20*np.log10(depth_quant))
|
|
474
|
+
ax.axvline(dep_med,color = 'purple',
|
|
475
|
+
label = f'Median: {np.round(dep_med,2)} dB')
|
|
476
|
+
ax.legend(loc = 'upper right')
|
|
477
|
+
ax.set_xlabel('Dip Depth [dB]')
|
|
478
|
+
ax.set_ylabel('Counts')
|
|
479
|
+
|
|
480
|
+
#Bandwidth plot
|
|
481
|
+
ax = axes[1, 0]
|
|
482
|
+
ax.hist(bw_quant*1e3, **plot_style)
|
|
483
|
+
bw_med = np.median(bw_quant*1e3)
|
|
484
|
+
ax.axvline(bw_med,color = 'purple',
|
|
485
|
+
label = f'Median: {np.round(bw_med,2)} kHz')
|
|
486
|
+
ax.legend(loc = 'upper right')
|
|
487
|
+
ax.set_xlabel('Bandwidth [kHZ]')
|
|
488
|
+
ax.set_ylabel('Counts')
|
|
489
|
+
|
|
490
|
+
#Frequency Separation plot
|
|
491
|
+
ax = axes[1, 1]
|
|
492
|
+
mean, std = np.nanmean(seps_quant), np.std(seps_quant)
|
|
493
|
+
nstd = 2
|
|
494
|
+
rng = (
|
|
495
|
+
max(np.min(seps_quant), mean - nstd * std),
|
|
496
|
+
min(np.max(seps_quant), mean + nstd * std),
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
ax.hist(seps, range=rng, **plot_style)
|
|
500
|
+
sep_med = np.median(seps_quant)
|
|
501
|
+
ax.axvline(sep_med,color = 'purple',
|
|
502
|
+
label = f'Median: {np.round(sep_med,2)} MHz')
|
|
503
|
+
ax.legend(loc = 'upper right')
|
|
504
|
+
ax.set_xlabel('Resonator Separation [Mhz]')
|
|
505
|
+
ax.set_ylabel('Counts')
|
|
506
|
+
|
|
507
|
+
plt.tight_layout()
|
|
508
|
+
return fig, axes
|