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,688 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import os
|
|
3
|
+
import traceback
|
|
4
|
+
import numpy as np
|
|
5
|
+
import sodetlib as sdl
|
|
6
|
+
import matplotlib.pyplot as plt
|
|
7
|
+
import scipy.optimize
|
|
8
|
+
from scipy.signal import welch
|
|
9
|
+
from sodetlib.operations import bias_steps, iv
|
|
10
|
+
|
|
11
|
+
np.seterr(all='ignore')
|
|
12
|
+
|
|
13
|
+
def play_bias_wave(S, cfg, bias_group, freqs_wave, amp_wave, duration,
|
|
14
|
+
dc_bias=None):
|
|
15
|
+
"""
|
|
16
|
+
Play a sine wave on the bias group.
|
|
17
|
+
|
|
18
|
+
Args
|
|
19
|
+
----
|
|
20
|
+
bias_group : int
|
|
21
|
+
The bias group
|
|
22
|
+
freq_wave : float array
|
|
23
|
+
List of frequencies to play. Unit = Hz.
|
|
24
|
+
amp_wave : float
|
|
25
|
+
Amplitude of sine wave to use. Unit = Volts.
|
|
26
|
+
duration : float
|
|
27
|
+
Duration each sine wave is played
|
|
28
|
+
dc_bias : float, optional
|
|
29
|
+
Offset voltage of sine wave. Unit = Volts.
|
|
30
|
+
|
|
31
|
+
Returns
|
|
32
|
+
-------
|
|
33
|
+
start_times : float list
|
|
34
|
+
unix timestamp for the beginning of each sine wave.
|
|
35
|
+
stop_times : float list
|
|
36
|
+
unix timestamp for the end of each sine wave.
|
|
37
|
+
"""
|
|
38
|
+
start_times, stop_times = [], []
|
|
39
|
+
|
|
40
|
+
if dc_bias is None:
|
|
41
|
+
dc_bias = S.get_tes_bias_bipolar_array()[bias_group]
|
|
42
|
+
|
|
43
|
+
for freq in freqs_wave:
|
|
44
|
+
S.log(f"BL sine wave with bg={bias_group}, freq={freq}")
|
|
45
|
+
S.play_sine_tes(bias_group=bias_group,
|
|
46
|
+
tone_amp=amp_wave,
|
|
47
|
+
tone_freq=freq, dc_amp=dc_bias)
|
|
48
|
+
start_times.append(time.time())
|
|
49
|
+
time.sleep(duration)
|
|
50
|
+
stop_times.append(time.time())
|
|
51
|
+
S.set_rtm_arb_waveform_enable(0)
|
|
52
|
+
S.set_tes_bias_bipolar(bias_group, dc_bias)
|
|
53
|
+
return start_times, stop_times
|
|
54
|
+
|
|
55
|
+
def get_amplitudes(f_c, x, fs = 4000, N = 12000, window = 'hann'):
|
|
56
|
+
"""
|
|
57
|
+
Function for calculating the amplitude of a sine wave.
|
|
58
|
+
|
|
59
|
+
Args
|
|
60
|
+
----
|
|
61
|
+
f_c : float
|
|
62
|
+
Target frequency to calculate sine wave amplitude at.
|
|
63
|
+
x : np.array
|
|
64
|
+
Data to analyze. Either can be shape nsamp or ndet x nsamp.
|
|
65
|
+
fs : int
|
|
66
|
+
Sample rate. Unit = samples/second.
|
|
67
|
+
N : int
|
|
68
|
+
Number of samples to calculate FFT on.
|
|
69
|
+
window : str
|
|
70
|
+
Window function to use. See scipy.signal.get_window for a list of
|
|
71
|
+
valid window functions to use. Default is ``hann``.
|
|
72
|
+
|
|
73
|
+
Returns
|
|
74
|
+
-------
|
|
75
|
+
a_peak : float
|
|
76
|
+
Amplitudes of sine waves. Shape is len(x)
|
|
77
|
+
"""
|
|
78
|
+
x = np.atleast_2d(x)
|
|
79
|
+
f, p = welch(x, fs = fs, nperseg = N,
|
|
80
|
+
scaling = 'spectrum',return_onesided=True,
|
|
81
|
+
window = window)
|
|
82
|
+
a = np.sqrt(p)
|
|
83
|
+
# Ran into problem with f == f_c when nperseg and len(x) are not right.
|
|
84
|
+
# Ben to add a function to check this or enforce correct choice somehow.
|
|
85
|
+
idx = np.argmin(np.abs(f-f_c))
|
|
86
|
+
a_rms = a[:, idx]
|
|
87
|
+
a_peak = np.sqrt(2)*a_rms
|
|
88
|
+
return a_peak
|
|
89
|
+
|
|
90
|
+
def get_amplitudes_deprojection(f_c, x, ts):
|
|
91
|
+
"""
|
|
92
|
+
Function for calculating the amplitude and phase of a wave by deprojecting sine and cosine components of desired frequency.
|
|
93
|
+
|
|
94
|
+
Args
|
|
95
|
+
----
|
|
96
|
+
f_c : float
|
|
97
|
+
Target frequency to calculate wave amplitude at.
|
|
98
|
+
x : np.array
|
|
99
|
+
Data to analyze. Either can be shape nsamp or ndet x nsamp.
|
|
100
|
+
ts : np.array
|
|
101
|
+
Timestamps of data to analyze. Shape is nsamp.
|
|
102
|
+
|
|
103
|
+
Returns
|
|
104
|
+
-------
|
|
105
|
+
a_peak : float
|
|
106
|
+
Amplitudes of sine wave of the desired frequency. Shape is len(x)
|
|
107
|
+
phase : float
|
|
108
|
+
Phase of sine wave of the desired frequency. Shape is len(x)
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
vects = np.zeros((2, len(ts)), dtype='float32')
|
|
112
|
+
vects[0, :] = np.sin(2*np.pi*f_c*ts)
|
|
113
|
+
vects[1, :] = np.cos(2*np.pi*f_c*ts)
|
|
114
|
+
I = np.linalg.inv(np.tensordot(vects, vects, (1, 1)))
|
|
115
|
+
coeffs = np.matmul(x, vects.T)
|
|
116
|
+
coeffs = np.dot(I, coeffs.T).T
|
|
117
|
+
coeffs = np.atleast_2d(coeffs)
|
|
118
|
+
|
|
119
|
+
a_peak = np.sqrt(coeffs[:,0]**2 + coeffs[:,1]**2)
|
|
120
|
+
phase = np.arctan2(coeffs[:,0], coeffs[:,1])
|
|
121
|
+
|
|
122
|
+
return a_peak, phase
|
|
123
|
+
|
|
124
|
+
class BiasWaveAnalysis:
|
|
125
|
+
"""
|
|
126
|
+
Container to manage analysis of bias waves taken with the take_bias_waves
|
|
127
|
+
function. The main function is ``run_analysis`` and will do a series of
|
|
128
|
+
analysis procedures to calculate DC detector parameters from a single bias
|
|
129
|
+
wave frequency. The method will be extended to calculate tau_eff from multiple
|
|
130
|
+
frequencies. Procedure:
|
|
131
|
+
|
|
132
|
+
- Loads an axis manager with all the data
|
|
133
|
+
- Finds locations of bias wave frequencies for each bias group
|
|
134
|
+
- Gets detector response to each bias wave
|
|
135
|
+
- Computes DC params R0, I0, Pj, Si from step responses
|
|
136
|
+
- Later: fit multiple TES amplitudes with one-pole filter for tau_eff measurement.
|
|
137
|
+
|
|
138
|
+
Most analysis inputs and products will be saved to a npy file so they can
|
|
139
|
+
be loaded and re-analyzed easily on another computer like simons1.
|
|
140
|
+
|
|
141
|
+
To load data from an saved step file, you can run::
|
|
142
|
+
|
|
143
|
+
bwa = BiasWaveAnalysis.load(<path>)
|
|
144
|
+
|
|
145
|
+
Attributes:
|
|
146
|
+
bands (array (int) of shape (ndets)):
|
|
147
|
+
Smurf bands for each resonator.
|
|
148
|
+
channels (array (int) of shape (ndets)):
|
|
149
|
+
Smurf channel for each resonator.
|
|
150
|
+
meta (dict) :
|
|
151
|
+
Dictionary of smurf metadata.
|
|
152
|
+
sid (int) :
|
|
153
|
+
Session-id of streaming session
|
|
154
|
+
run_kwargs :
|
|
155
|
+
input kwargs
|
|
156
|
+
start, stop (float):
|
|
157
|
+
start and stop time of all steps
|
|
158
|
+
high_current_mode (Bool) :
|
|
159
|
+
If high-current-mode was used
|
|
160
|
+
start_times, stop_times (array (float) of shape (nbgs, nfreqs)) :
|
|
161
|
+
Arrays of start and stop times for the start of each frequency sine wave.
|
|
162
|
+
bgmap (array (int) of shape (nchans)):
|
|
163
|
+
Map from readout channel to assigned bias group. -1 means not
|
|
164
|
+
assigned (that the assignment threshold was not met for any of the
|
|
165
|
+
12 bgs)
|
|
166
|
+
polarity (array (int) of shape (ndets)):
|
|
167
|
+
The sign of each channel.
|
|
168
|
+
resp_times (array (float) shape (nbgs, nfreqs, npts)):
|
|
169
|
+
Shared timestamps for each of the wave responses in <wave_resp> and
|
|
170
|
+
<mean_resp> with respect to the bg-wave location, with the wave
|
|
171
|
+
occuring at t=0.
|
|
172
|
+
mean_resp (array (float) shape (nchans, nfreqs, npts)):
|
|
173
|
+
Wave response averaged accross all bias steps for a given channel
|
|
174
|
+
in Amps.
|
|
175
|
+
wave_resp (array (float) shape (nchans, nfreqs, npts)):
|
|
176
|
+
Each individual wave response for a given channel in amps.
|
|
177
|
+
wave_biases (array (float) shape (nbgs, nfreqs, npts)):
|
|
178
|
+
Each individual bias wave for a given channel.
|
|
179
|
+
Ibias (array (float) shape (nbgs)):
|
|
180
|
+
DC bias current of each bias group (amps). DC Params are
|
|
181
|
+
only calculated off of the minimum frequency in the array of frequencies.
|
|
182
|
+
Vbias:
|
|
183
|
+
DC bias voltage of each bias group (volts in low-current mode)
|
|
184
|
+
dIbias (array (float) shape (nbgs)):
|
|
185
|
+
Wave current amplitude for each bias group (amps)
|
|
186
|
+
dVbias (array (float) shape (nbgs)):
|
|
187
|
+
Wave voltage amplitude for each bias group (volts in low-current mode)
|
|
188
|
+
dItes (array (float) shape (nchans)):
|
|
189
|
+
Array of tes wave response height for each channel (amps)
|
|
190
|
+
R0 (array (float) shape (nchans)):
|
|
191
|
+
Computed TES resistances for each channel (ohms).
|
|
192
|
+
I0 (array (float) shape (nchans)):
|
|
193
|
+
Computed TES currents for each channel (amps)
|
|
194
|
+
Pj (array (float) shape (nchans)):
|
|
195
|
+
Bias power computed for each channel
|
|
196
|
+
Si (array (float) shape (nchans)):
|
|
197
|
+
Responsivity computed for each channel
|
|
198
|
+
R_n_IV (array (float) shape (nchans)):
|
|
199
|
+
Array of normal resistances for each channel pulled from IV
|
|
200
|
+
in the device cfg.
|
|
201
|
+
Rfrac (array (float) shape (nchans)):
|
|
202
|
+
Rfrac of each channel, determined from R0 and the channel's normal
|
|
203
|
+
resistance.
|
|
204
|
+
"""
|
|
205
|
+
def __init__(self, S=None, cfg=None, bgs=None, run_kwargs=None):
|
|
206
|
+
self._S = S
|
|
207
|
+
self._cfg = cfg
|
|
208
|
+
|
|
209
|
+
self.bgs = bgs
|
|
210
|
+
self.am = None
|
|
211
|
+
|
|
212
|
+
if S is not None:
|
|
213
|
+
self.meta = sdl.get_metadata(S, cfg)
|
|
214
|
+
self.stream_id = cfg.stream_id
|
|
215
|
+
|
|
216
|
+
if run_kwargs is None:
|
|
217
|
+
run_kwargs = {}
|
|
218
|
+
self.run_kwargs = run_kwargs
|
|
219
|
+
self.high_current_mode = run_kwargs.get("high_current_mode", True)
|
|
220
|
+
|
|
221
|
+
def save(self, path=None):
|
|
222
|
+
data = {}
|
|
223
|
+
saved_fields = [
|
|
224
|
+
# Run data and metadata
|
|
225
|
+
'bands', 'channels', 'sid', 'meta', 'run_kwargs', 'start', 'stop',
|
|
226
|
+
'high_current_mode', 'start_times', 'stop_times',
|
|
227
|
+
# Bgmap data
|
|
228
|
+
'bgmap', 'polarity',
|
|
229
|
+
# Step data and fits, including chunked bias data
|
|
230
|
+
'resp_times', 'mean_resp', 'wave_resp', 'wave_biases',
|
|
231
|
+
# Add in tau fit stuff here. The below commented out params are anticipated to be reported for tau analysis.
|
|
232
|
+
# 'step_fit_tmin', 'step_fit_popts', 'step_fit_pcovs',
|
|
233
|
+
# 'tau_eff',
|
|
234
|
+
# Det param data
|
|
235
|
+
'Ibias', 'Vbias', 'dIbias', 'dVbias', 'dItes',
|
|
236
|
+
'R0', 'I0', 'Pj', 'Si',
|
|
237
|
+
# From IV's
|
|
238
|
+
'R_n_IV', 'Rfrac',
|
|
239
|
+
]
|
|
240
|
+
|
|
241
|
+
for f in saved_fields:
|
|
242
|
+
if not hasattr(self, f):
|
|
243
|
+
print(f"WARNING: field {f} does not exist... "
|
|
244
|
+
"defaulting to None")
|
|
245
|
+
data[f] = getattr(self, f, None)
|
|
246
|
+
|
|
247
|
+
if path is not None:
|
|
248
|
+
np.save(path, data, allow_pickle=True)
|
|
249
|
+
self.filepath = path
|
|
250
|
+
else:
|
|
251
|
+
self.filepath = sdl.validate_and_save(
|
|
252
|
+
'bias_wave_analysis.npy', data, S=self._S, cfg=self._cfg,
|
|
253
|
+
make_path=True
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
@classmethod
|
|
257
|
+
def load(cls, filepath):
|
|
258
|
+
self = cls()
|
|
259
|
+
data = np.load(filepath, allow_pickle=True).item()
|
|
260
|
+
for k, v in data.items():
|
|
261
|
+
setattr(self, k, v)
|
|
262
|
+
self.filepath = filepath
|
|
263
|
+
return self
|
|
264
|
+
|
|
265
|
+
def run_analysis(self, arc=None, base_dir='/data/so/timestreams',
|
|
266
|
+
R0_thresh=30e-3, save=False, bg_map_file=None):
|
|
267
|
+
"""
|
|
268
|
+
Analyzes data taken with take_bias_waves.
|
|
269
|
+
|
|
270
|
+
Parameters
|
|
271
|
+
----------
|
|
272
|
+
arc (optional, G3tSmurf):
|
|
273
|
+
G3tSmurf archive. If specified, will attempt to load
|
|
274
|
+
axis-manager using archive instead of sid.
|
|
275
|
+
base_dir (optiional, str):
|
|
276
|
+
Base directory where timestreams are stored. Defaults to
|
|
277
|
+
/data/so/timestreams.
|
|
278
|
+
R0_thresh (float):
|
|
279
|
+
Any channel with resistance greater than R0_thresh will be
|
|
280
|
+
unassigned from its bias group under the assumption that it's
|
|
281
|
+
crosstalk
|
|
282
|
+
save (bool):
|
|
283
|
+
If true will save the analysis to a npy file.
|
|
284
|
+
bg_map_file (optional, path):
|
|
285
|
+
If create_bg_map is false and this file is not None, use this file
|
|
286
|
+
to load the bg_map.
|
|
287
|
+
|
|
288
|
+
CURRENTLY ONLY INCLUDES CALCULATION OF DC PARAMETERS.
|
|
289
|
+
NO TIME CONSTANT FITS.
|
|
290
|
+
"""
|
|
291
|
+
self._load_am(arc=arc, base_dir=base_dir)
|
|
292
|
+
self._split_frequencies()
|
|
293
|
+
if bg_map_file is not None:
|
|
294
|
+
self.bgmap, self.polarity = sdl.load_bgmap(
|
|
295
|
+
self.bands, self.channels, bg_map_file)
|
|
296
|
+
else:
|
|
297
|
+
self.bgmap, self.polarity = sdl.load_bgmap(
|
|
298
|
+
self.bands, self.channels, self.meta['bgmap_file'])
|
|
299
|
+
|
|
300
|
+
# GOT TO HERE
|
|
301
|
+
self._get_wave_response()
|
|
302
|
+
self._compute_dc_params(R0_thresh=R0_thresh)
|
|
303
|
+
|
|
304
|
+
# Load R_n from IV
|
|
305
|
+
self.R_n_IV = np.full(self.nchans, np.nan)
|
|
306
|
+
# Rfrac determined from R0 and R_n
|
|
307
|
+
self.Rfrac = np.full(self.nchans, np.nan)
|
|
308
|
+
if self.meta['iv_file'] is not None:
|
|
309
|
+
if os.path.exists(self.meta['iv_file']):
|
|
310
|
+
iva = iv.IVAnalysis.load(self.meta['iv_file'])
|
|
311
|
+
chmap = sdl.map_band_chans(
|
|
312
|
+
self.bands, self.channels, iva.bands, iva.channels
|
|
313
|
+
)
|
|
314
|
+
self.R_n_IV = iva.R_n[chmap]
|
|
315
|
+
self.R_n_IV[chmap == -1] = np.nan
|
|
316
|
+
self.Rfrac = self.R0 / self.R_n_IV
|
|
317
|
+
|
|
318
|
+
# Can add in function for fitting time constant from multifrequency data here.
|
|
319
|
+
# self._fit_tau_effs(tmin=fit_tmin)
|
|
320
|
+
|
|
321
|
+
if save:
|
|
322
|
+
self.save()
|
|
323
|
+
|
|
324
|
+
def _load_am(self, arc=None, base_dir='/data/so/timestreams', fix_timestamps=True):
|
|
325
|
+
"""
|
|
326
|
+
Attempts to load the axis manager from the sid or return one that's
|
|
327
|
+
already loaded. Also sets the `abs_chans` array.
|
|
328
|
+
"""
|
|
329
|
+
if self.am is None:
|
|
330
|
+
if arc:
|
|
331
|
+
self.am = arc.load_data(self.start, self.stop, stream_id=self.meta['stream_id'])
|
|
332
|
+
else:
|
|
333
|
+
self.am = sdl.load_session(self.meta['stream_id'], self.sid,
|
|
334
|
+
base_dir=base_dir)
|
|
335
|
+
|
|
336
|
+
# Fix up timestamp jitter from timestamping in software
|
|
337
|
+
if fix_timestamps:
|
|
338
|
+
fsamp, t0 = np.polyfit(self.am.primary['FrameCounter'],
|
|
339
|
+
self.am.timestamps, 1)
|
|
340
|
+
self.am.timestamps = t0 + self.am.primary['FrameCounter']*fsamp
|
|
341
|
+
if "det_info" in self.am:
|
|
342
|
+
self.bands = self.am.det_info.smurf.band
|
|
343
|
+
self.channels = self.am.det_info.smurf.channel
|
|
344
|
+
else:
|
|
345
|
+
self.bands = self.am.ch_info.band
|
|
346
|
+
self.channels = self.am.ch_info.channel
|
|
347
|
+
self.abs_chans = self.bands*512 + self.channels
|
|
348
|
+
self.nbgs = len(self.am.biases)
|
|
349
|
+
self.nchans = len(self.am.signal)
|
|
350
|
+
return self.am
|
|
351
|
+
|
|
352
|
+
def _split_frequencies(self, am=None):
|
|
353
|
+
"""
|
|
354
|
+
Gets indices for each sine wave frequency on each bias group.
|
|
355
|
+
|
|
356
|
+
Returns
|
|
357
|
+
-------
|
|
358
|
+
start_idxs : float array
|
|
359
|
+
Array of indices for the start of each frequency sine wave.
|
|
360
|
+
Shape (n_bias_groups, n_frequencies).
|
|
361
|
+
stop_idxs : float array
|
|
362
|
+
Array of indices for the end of each frequency sine wave.
|
|
363
|
+
Shape (n_bias_groups, n_frequencies).
|
|
364
|
+
"""
|
|
365
|
+
if am is None:
|
|
366
|
+
am = self.am
|
|
367
|
+
|
|
368
|
+
self.start_idxs = np.full(np.shape(self.start_times), -1)
|
|
369
|
+
self.stop_idxs = np.full(np.shape(self.stop_times), -1)
|
|
370
|
+
|
|
371
|
+
for bg, bias in enumerate(am.biases[:12]):
|
|
372
|
+
if np.all(np.isnan(self.start_times[bg,:])):
|
|
373
|
+
continue
|
|
374
|
+
for i, [start, stop] in enumerate(zip(self.start_times[bg,:], self.stop_times[bg,:])):
|
|
375
|
+
self.start_idxs[bg, i] = np.argmin(np.abs(am.timestamps - start))
|
|
376
|
+
self.stop_idxs[bg, i] = np.argmin(np.abs(am.timestamps - stop))
|
|
377
|
+
|
|
378
|
+
return self.start_idxs, self.stop_idxs
|
|
379
|
+
|
|
380
|
+
def _get_wave_response(self, am=None):
|
|
381
|
+
"""
|
|
382
|
+
Splits up full axis manager into each sine wave.
|
|
383
|
+
Here we enforce the number of points in each sine wave to be equal.
|
|
384
|
+
|
|
385
|
+
Returns
|
|
386
|
+
-------
|
|
387
|
+
ts : float array
|
|
388
|
+
Array of timestamps for each sine wave period
|
|
389
|
+
Shape (n_bias_groups, n_frequencies, n_pts_in_sine_wave).
|
|
390
|
+
sigs : float array
|
|
391
|
+
Array of sine response data.
|
|
392
|
+
Shape (n_detectors, n_frequencies, n_pts_in_sine_wave).
|
|
393
|
+
UPDATE THE DOCSTRING
|
|
394
|
+
"""
|
|
395
|
+
if am is None:
|
|
396
|
+
am = self.am
|
|
397
|
+
|
|
398
|
+
nchans = len(am.signal)
|
|
399
|
+
nbgs = 12
|
|
400
|
+
n_freqs = np.shape(self.start_idxs)[-1]
|
|
401
|
+
npts = np.nanmin(self.stop_idxs-self.start_idxs)
|
|
402
|
+
|
|
403
|
+
sigs = np.full((nchans, n_freqs, npts), np.nan)
|
|
404
|
+
biases = np.full((nbgs, n_freqs, npts), np.nan)
|
|
405
|
+
ts = np.full((nbgs, n_freqs, npts), np.nan)
|
|
406
|
+
|
|
407
|
+
A_per_rad = self.meta['pA_per_phi0'] / (2*np.pi) * 1e-12
|
|
408
|
+
for bg in np.unique(self.bgmap):
|
|
409
|
+
if bg == -1:
|
|
410
|
+
continue
|
|
411
|
+
rcs = np.where(self.bgmap == bg)[0]
|
|
412
|
+
for i, si in enumerate(self.start_idxs[bg]):
|
|
413
|
+
if np.isnan(ts[bg,i]).all():
|
|
414
|
+
ts[bg, i, :] = am.timestamps[si:si+npts] - am.timestamps[si]
|
|
415
|
+
sigs[rcs, i, :] = am.signal[rcs, si:si+npts] * A_per_rad
|
|
416
|
+
biases[bg, i, :] = am.biases[bg, si:si+npts]
|
|
417
|
+
|
|
418
|
+
self.resp_times = ts
|
|
419
|
+
self.wave_resp = (sigs.T * self.polarity).T
|
|
420
|
+
self.mean_resp = (np.nanmean(sigs, axis=1).T * self.polarity).T
|
|
421
|
+
self.wave_biases = biases
|
|
422
|
+
|
|
423
|
+
return ts, sigs, biases
|
|
424
|
+
|
|
425
|
+
def _compute_dc_params(self, R0_thresh=30e-3):
|
|
426
|
+
"""
|
|
427
|
+
Calculates Ibias, dIbias, and dItes from axis manager, and then
|
|
428
|
+
runs the DC param calc to estimate R0, I0, Pj, etc.
|
|
429
|
+
If multiple frequency are taken, the DC Params are only calculated
|
|
430
|
+
off of the minimum frequency in the array of frequencies.
|
|
431
|
+
|
|
432
|
+
Args:
|
|
433
|
+
R0_thresh: (float)
|
|
434
|
+
Any channel with resistance greater than R0_thresh will be
|
|
435
|
+
unassigned from its bias group under the assumption that it's
|
|
436
|
+
crosstalk
|
|
437
|
+
|
|
438
|
+
Saves:
|
|
439
|
+
Ibias:
|
|
440
|
+
Array of shape (nbgs) containing the DC bias current for each
|
|
441
|
+
bias group
|
|
442
|
+
Vbias:
|
|
443
|
+
Array of shape (nbgs) containing the DC bias voltage for each
|
|
444
|
+
bias group
|
|
445
|
+
dIbias:
|
|
446
|
+
Array of shape (nbgs) containing the wave current amplitude for
|
|
447
|
+
each bias group
|
|
448
|
+
dVbias:
|
|
449
|
+
Array of shape (nbgs) containing the wave voltage amplitude for
|
|
450
|
+
each bias group
|
|
451
|
+
dItes:
|
|
452
|
+
Array of shape (nchans) containing the wave current amplitude for
|
|
453
|
+
each detector.
|
|
454
|
+
"""
|
|
455
|
+
nbgs = 12
|
|
456
|
+
nchans = len(self.am.signal)
|
|
457
|
+
npts = np.nanmin(self.stop_idxs-self.start_idxs)
|
|
458
|
+
|
|
459
|
+
Ibias = np.full(nbgs, np.nan)
|
|
460
|
+
dIbias = np.full(nbgs, 0.0, dtype=float)
|
|
461
|
+
dItes = np.full(nchans, np.nan)
|
|
462
|
+
|
|
463
|
+
dIbias_phase = np.full(nbgs, 0.0, dtype=float)
|
|
464
|
+
dItes_phase = np.full(nchans, np.nan)
|
|
465
|
+
|
|
466
|
+
# Compute Ibias and dIbias
|
|
467
|
+
bias_line_resistance = self.meta['bias_line_resistance']
|
|
468
|
+
high_low_current_ratio = self.meta['high_low_current_ratio']
|
|
469
|
+
rtm_bit_to_volt = self.meta['rtm_bit_to_volt']
|
|
470
|
+
amp_per_bit = 2 * rtm_bit_to_volt / bias_line_resistance
|
|
471
|
+
if self.high_current_mode:
|
|
472
|
+
amp_per_bit *= high_low_current_ratio
|
|
473
|
+
|
|
474
|
+
for bg in range(nbgs):
|
|
475
|
+
if len(self.start_idxs[bg]) == 0:
|
|
476
|
+
continue
|
|
477
|
+
rcs = np.where(self.bgmap == bg)[0]
|
|
478
|
+
s = slice(int(self.start_idxs[bg][0]),
|
|
479
|
+
int(self.start_idxs[bg][0] + npts))
|
|
480
|
+
Ibias[bg] = np.nanmean(self.am.biases[bg, s]) * amp_per_bit
|
|
481
|
+
|
|
482
|
+
dIbias[bg], dIbias_phase[bg] = get_amplitudes_deprojection(self.run_kwargs['freqs_wave'][0],
|
|
483
|
+
self.am.biases[bg, s], self.resp_times[bg, 0, :])
|
|
484
|
+
dIbias[bg] = dIbias[bg] * amp_per_bit
|
|
485
|
+
|
|
486
|
+
dItes[rcs], dItes_phase[rcs] = get_amplitudes_deprojection(self.run_kwargs['freqs_wave'][0],
|
|
487
|
+
self.wave_resp[rcs,0,:], self.resp_times[bg, 0, :])
|
|
488
|
+
|
|
489
|
+
self.Ibias = Ibias
|
|
490
|
+
self.Vbias = Ibias * bias_line_resistance
|
|
491
|
+
self.dIbias = dIbias
|
|
492
|
+
self.dIbias_phase = dIbias_phase
|
|
493
|
+
self.dVbias = dIbias * bias_line_resistance
|
|
494
|
+
self.dItes = dItes
|
|
495
|
+
self.dItes_phase = dItes_phase
|
|
496
|
+
|
|
497
|
+
R0, I0, Pj = self._compute_R0_I0_Pj()
|
|
498
|
+
|
|
499
|
+
Si = -1./(I0 * (R0 - self.meta['R_sh']))
|
|
500
|
+
|
|
501
|
+
# If resistance is too high, most likely crosstalk so just reset
|
|
502
|
+
# bg mapping and det params
|
|
503
|
+
if R0_thresh is not None:
|
|
504
|
+
m = np.abs(R0) > R0_thresh
|
|
505
|
+
self.bgmap[m] = -1
|
|
506
|
+
for arr in [R0, I0, Pj, Si]:
|
|
507
|
+
arr[m] = np.nan
|
|
508
|
+
|
|
509
|
+
self.R0 = R0
|
|
510
|
+
self.I0 = I0
|
|
511
|
+
self.Pj = Pj
|
|
512
|
+
self.Si = Si
|
|
513
|
+
|
|
514
|
+
return R0, I0, Pj, Si
|
|
515
|
+
|
|
516
|
+
def _compute_R0_I0_Pj(self):
|
|
517
|
+
"""
|
|
518
|
+
Computes the DC params R0 I0 and Pj
|
|
519
|
+
|
|
520
|
+
Carbon copy from BiasStepAnalysis
|
|
521
|
+
"""
|
|
522
|
+
Ib = self.Ibias[self.bgmap]
|
|
523
|
+
dIb = self.dIbias[self.bgmap]
|
|
524
|
+
dItes = self.dItes
|
|
525
|
+
|
|
526
|
+
dIb_phase = self.dIbias_phase[self.bgmap]
|
|
527
|
+
dItes_phase = self.dItes_phase
|
|
528
|
+
|
|
529
|
+
Ib[self.bgmap == -1] = np.nan
|
|
530
|
+
dIb[self.bgmap == -1] = np.nan
|
|
531
|
+
|
|
532
|
+
#sign of phase response, is there a better way to do it?
|
|
533
|
+
rel_phase = dItes_phase - dIb_phase
|
|
534
|
+
rel_phase = np.isclose(abs(rel_phase), np.pi, atol=3e-1, rtol = 1e-1) #arbitrary cutoff?
|
|
535
|
+
rel_phase_sign = np.full(rel_phase.shape, 1.0)
|
|
536
|
+
rel_phase_sign[np.where(rel_phase == True)] = -1.0
|
|
537
|
+
|
|
538
|
+
dIrat = rel_phase_sign * (dItes / dIb)
|
|
539
|
+
|
|
540
|
+
R_sh = self.meta["R_sh"]
|
|
541
|
+
|
|
542
|
+
I0 = np.zeros_like(dIrat)
|
|
543
|
+
I0_nontransition = Ib * dIrat
|
|
544
|
+
I0_transition = Ib * dIrat / (2 * dIrat - 1)
|
|
545
|
+
I0[dIrat>0] = I0_nontransition[dIrat>0]
|
|
546
|
+
I0[dIrat<0] = I0_transition[dIrat<0]
|
|
547
|
+
|
|
548
|
+
Pj = I0 * R_sh * (Ib - I0)
|
|
549
|
+
R0 = Pj / I0**2
|
|
550
|
+
R0[I0 == 0] = 0
|
|
551
|
+
|
|
552
|
+
return R0, I0, Pj
|
|
553
|
+
|
|
554
|
+
@sdl.set_action()
|
|
555
|
+
def take_bias_waves(S, cfg, bgs=None, amp_wave=0.05, freqs_wave=[23.0],
|
|
556
|
+
duration=10, high_current_mode=True, hcm_wait_time=3,
|
|
557
|
+
run_analysis=True, analysis_kwargs=None, channel_mask=None,
|
|
558
|
+
g3_tag=None, stream_subtype='bias_waves',
|
|
559
|
+
enable_compression=False, plot_rfrac=True, show_plots=False):
|
|
560
|
+
"""
|
|
561
|
+
Takes bias wave data at the current DC voltage. Assumes bias lines
|
|
562
|
+
are already in low-current mode (if they are in high-current this will
|
|
563
|
+
not run correction). This function runs bias waves and returns a
|
|
564
|
+
BiasWaveAnalysis object, which can be used to easily view and re-analyze
|
|
565
|
+
data.
|
|
566
|
+
|
|
567
|
+
Parameters:
|
|
568
|
+
S (SmurfControl):
|
|
569
|
+
Pysmurf control instance
|
|
570
|
+
cfg (DetConfig):
|
|
571
|
+
Detconfig instance
|
|
572
|
+
bgs ( int, list, optional):
|
|
573
|
+
Bias groups to run steps on, defaulting to all 12. Note that the
|
|
574
|
+
bias-group mapping generated by the bias step analysis will be
|
|
575
|
+
restricted to the bgs set here so if you only run with a small
|
|
576
|
+
subset of bias groups, the map might not be correct.
|
|
577
|
+
amp_wave (float):
|
|
578
|
+
Bias wave amplitude voltage in Low-current-mode units.
|
|
579
|
+
This will be divided by the high-low-ratio before running the steps
|
|
580
|
+
in high-current mode.
|
|
581
|
+
freqs_wave (float):
|
|
582
|
+
List of frequencies to take bias wave data at.
|
|
583
|
+
duration (float):
|
|
584
|
+
Duration in seconds of bias wave frequency
|
|
585
|
+
high_current_mode (bool):
|
|
586
|
+
If true, switches to high-current-mode. If False, leaves in LCM
|
|
587
|
+
which runs through the bias-line filter, so make sure you
|
|
588
|
+
extend the step duration to be like >2 sec or something
|
|
589
|
+
hcm_wait_time (float):
|
|
590
|
+
Time to wait after switching to high-current-mode.
|
|
591
|
+
run_analysis (bool):
|
|
592
|
+
If True, will attempt to run the analysis to calculate DC params
|
|
593
|
+
and tau_eff. If this fails, the analysis object will
|
|
594
|
+
still be returned but will not contain all analysis results.
|
|
595
|
+
analysis_kwargs (dict, optional):
|
|
596
|
+
Keyword arguments to be passed to the BiasWaveAnalysis run_analysis
|
|
597
|
+
function.
|
|
598
|
+
channel_mask : np.ndarray, optional
|
|
599
|
+
Mask containing absolute smurf-channels to write to disk
|
|
600
|
+
g3_tag: string, optional
|
|
601
|
+
Tag to attach to g3 stream.
|
|
602
|
+
stream_subtype : optional, string
|
|
603
|
+
Stream subtype for this operation. This will default to 'bias_waves'.
|
|
604
|
+
enable_compression: bool, optional
|
|
605
|
+
If True, will tell the smurf-streamer to compress G3Frames. Defaults
|
|
606
|
+
to False because this dominates frame-processing time for high
|
|
607
|
+
data-rate streams.
|
|
608
|
+
plot_rfrac : bool
|
|
609
|
+
Create rfrac plot, publish it, and save it. Default is True.
|
|
610
|
+
show_plots : bool
|
|
611
|
+
Show plot in addition to saving when running interactively. Default is False.
|
|
612
|
+
"""
|
|
613
|
+
if bgs is None:
|
|
614
|
+
bgs = cfg.dev.exp['active_bgs']
|
|
615
|
+
bgs = np.atleast_1d(bgs)
|
|
616
|
+
|
|
617
|
+
# Dumb way to get all run kwargs, but we probably want to save these in
|
|
618
|
+
# data object
|
|
619
|
+
freqs_wave = np.sort(np.atleast_1d(freqs_wave)) # Enforces lowest frequency first.
|
|
620
|
+
run_kwargs = {
|
|
621
|
+
'bgs': bgs, 'amp_wave': amp_wave,
|
|
622
|
+
'freqs_wave': freqs_wave, 'duration': duration,
|
|
623
|
+
'high_current_mode': high_current_mode,
|
|
624
|
+
'hcm_wait_time': hcm_wait_time, 'run_analysis': run_analysis,
|
|
625
|
+
'analysis_kwargs': analysis_kwargs, 'channel_mask': channel_mask,
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
initial_ds_factor = S.get_downsample_factor()
|
|
629
|
+
initial_filter_disable = S.get_filter_disable()
|
|
630
|
+
initial_dc_biases = S.get_tes_bias_bipolar_array()
|
|
631
|
+
|
|
632
|
+
try:
|
|
633
|
+
dc_biases = initial_dc_biases
|
|
634
|
+
init_current_mode = sdl.get_current_mode_array(S)
|
|
635
|
+
if high_current_mode:
|
|
636
|
+
dc_biases = dc_biases / S.high_low_current_ratio
|
|
637
|
+
amp_wave /= S.high_low_current_ratio
|
|
638
|
+
sdl.set_current_mode(S, bgs, 1)
|
|
639
|
+
S.log(f"Waiting {hcm_wait_time} sec after switching to hcm")
|
|
640
|
+
time.sleep(hcm_wait_time)
|
|
641
|
+
|
|
642
|
+
bwa = BiasWaveAnalysis(S, cfg, bgs, run_kwargs=run_kwargs)
|
|
643
|
+
|
|
644
|
+
bwa.sid = sdl.stream_g3_on(
|
|
645
|
+
S, tag=g3_tag, channel_mask=channel_mask, downsample_factor=1,
|
|
646
|
+
filter_disable=True, subtype=stream_subtype, enable_compression=enable_compression
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
bwa.start_times = np.full((12, len(freqs_wave)), np.nan)
|
|
650
|
+
bwa.stop_times = np.full((12, len(freqs_wave)), np.nan)
|
|
651
|
+
|
|
652
|
+
bwa.start = time.time()
|
|
653
|
+
|
|
654
|
+
for bg in bgs:
|
|
655
|
+
bwa.start_times[bg,:], bwa.stop_times[bg,:] = play_bias_wave(S, cfg, bg,
|
|
656
|
+
freqs_wave,
|
|
657
|
+
amp_wave, duration)
|
|
658
|
+
|
|
659
|
+
bwa.stop = time.time()
|
|
660
|
+
|
|
661
|
+
finally:
|
|
662
|
+
sdl.stream_g3_off(S)
|
|
663
|
+
|
|
664
|
+
# Restores current mode to initial values
|
|
665
|
+
sdl.set_current_mode(S, np.where(init_current_mode == 0)[0], 0)
|
|
666
|
+
sdl.set_current_mode(S, np.where(init_current_mode == 1)[0], 1)
|
|
667
|
+
|
|
668
|
+
S.set_downsample_factor(initial_ds_factor)
|
|
669
|
+
S.set_filter_disable(initial_filter_disable)
|
|
670
|
+
|
|
671
|
+
if run_analysis:
|
|
672
|
+
S.log("Running bias wave analysis")
|
|
673
|
+
try:
|
|
674
|
+
if analysis_kwargs is None:
|
|
675
|
+
analysis_kwargs = {}
|
|
676
|
+
bwa.run_analysis(save=True, **analysis_kwargs)
|
|
677
|
+
if plot_rfrac:
|
|
678
|
+
fig, _ = bias_steps.plot_Rfrac(bwa)
|
|
679
|
+
path = sdl.make_filename(S, 'bw_rfrac_summary.png', plot=True)
|
|
680
|
+
fig.savefig(path)
|
|
681
|
+
S.pub.register_file(path, 'bw_rfrac_summary', plot=True, format='png')
|
|
682
|
+
if not show_plots:
|
|
683
|
+
plt.close(fig)
|
|
684
|
+
except Exception:
|
|
685
|
+
print(f"Bias wave analysis failed with exception:")
|
|
686
|
+
print(traceback.format_exc())
|
|
687
|
+
|
|
688
|
+
return bwa
|