pytesprocess 0.1.1__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.
- pytesprocess/__init__.py +9 -0
- pytesprocess/_version.py +2 -0
- pytesprocess/cli/__init__.py +1 -0
- pytesprocess/cli/commands/__init__.py +5 -0
- pytesprocess/cli/commands/event.py +66 -0
- pytesprocess/cli/commands/filter.py +17 -0
- pytesprocess/cli/commands/ivsweep.py +29 -0
- pytesprocess/cli/common.py +86 -0
- pytesprocess/cli/main.py +81 -0
- pytesprocess/config/__init__.py +4 -0
- pytesprocess/config/loader.py +94 -0
- pytesprocess/config/manager.py +297 -0
- pytesprocess/config/resolvers/__init__.py +5 -0
- pytesprocess/config/resolvers/common.py +56 -0
- pytesprocess/config/resolvers/feature.py +293 -0
- pytesprocess/config/resolvers/salting.py +86 -0
- pytesprocess/config/resolvers/trigger.py +84 -0
- pytesprocess/config/selectors.py +108 -0
- pytesprocess/config/validation.py +314 -0
- pytesprocess/config/warnings.py +2 -0
- pytesprocess/core/__init__.py +10 -0
- pytesprocess/core/algorithms.py +1455 -0
- pytesprocess/core/didv.py +1648 -0
- pytesprocess/core/eventbuilder.py +495 -0
- pytesprocess/core/filterbuilder.py +81 -0
- pytesprocess/core/filterdata.py +1849 -0
- pytesprocess/core/ivsweep.py +2072 -0
- pytesprocess/core/noise.py +923 -0
- pytesprocess/core/noisemodel.py +1408 -0
- pytesprocess/core/oftrigger.py +1035 -0
- pytesprocess/core/template.py +450 -0
- pytesprocess/process/__init__.py +6 -0
- pytesprocess/process/data_source.py +185 -0
- pytesprocess/process/event_context.py +35 -0
- pytesprocess/process/feature_plan.py +186 -0
- pytesprocess/process/feature_resources.py +267 -0
- pytesprocess/process/features.py +1024 -0
- pytesprocess/process/filterprocess.py +1176 -0
- pytesprocess/process/ivprocess.py +1380 -0
- pytesprocess/process/processing_data.py +967 -0
- pytesprocess/process/randoms.py +921 -0
- pytesprocess/process/triggers.py +1011 -0
- pytesprocess/salting/__init__.py +7 -0
- pytesprocess/salting/generator.py +364 -0
- pytesprocess/salting/injector.py +329 -0
- pytesprocess/salting/sampling.py +84 -0
- pytesprocess/utils/__init__.py +5 -0
- pytesprocess/utils/arg_utils.py +122 -0
- pytesprocess/utils/dataframe_output.py +120 -0
- pytesprocess/utils/filter_hdf5.py +594 -0
- pytesprocess/utils/utils.py +701 -0
- pytesprocess/workflows/__init__.py +3 -0
- pytesprocess/workflows/processing.py +317 -0
- pytesprocess/workflows/salting.py +133 -0
- pytesprocess-0.1.1.dist-info/METADATA +211 -0
- pytesprocess-0.1.1.dist-info/RECORD +60 -0
- pytesprocess-0.1.1.dist-info/WHEEL +5 -0
- pytesprocess-0.1.1.dist-info/entry_points.txt +2 -0
- pytesprocess-0.1.1.dist-info/licenses/LICENSE +21 -0
- pytesprocess-0.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1648 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import os
|
|
4
|
+
import stat
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pandas as pd
|
|
8
|
+
import qetpy as qp
|
|
9
|
+
from scipy.signal import unit_impulse
|
|
10
|
+
|
|
11
|
+
from pytesdaqx.io import AcquisitionCatalog, StreamReader
|
|
12
|
+
|
|
13
|
+
from pytesprocess.core.filterdata import FilterData
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DIDVAnalysis(FilterData):
|
|
17
|
+
"""
|
|
18
|
+
Class to manage didv calculations using
|
|
19
|
+
QETpy. DIDV data are stored
|
|
20
|
+
"""
|
|
21
|
+
def __init__(self, verbose=True,
|
|
22
|
+
auto_save_hdf5=False,
|
|
23
|
+
save_path=None,
|
|
24
|
+
filter_data=None):
|
|
25
|
+
|
|
26
|
+
super().__init__(verbose=verbose, filter_data=filter_data)
|
|
27
|
+
|
|
28
|
+
self._didv_data = {}
|
|
29
|
+
|
|
30
|
+
# save results
|
|
31
|
+
self._save_hdf5 = auto_save_hdf5
|
|
32
|
+
self._save_path = None
|
|
33
|
+
self._file_name = self._set_file_name()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
if save_path is not None:
|
|
37
|
+
path = Path(save_path)
|
|
38
|
+
if path.is_file():
|
|
39
|
+
self._save_path = str(path.parent)
|
|
40
|
+
self._file_name = path.name
|
|
41
|
+
elif path.is_dir():
|
|
42
|
+
self._save_path = str(path)
|
|
43
|
+
else:
|
|
44
|
+
raise ValueError('ERROR: "save_path" should be a '
|
|
45
|
+
'file or path!')
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# display
|
|
51
|
+
if self._save_hdf5:
|
|
52
|
+
full_path_file = self._file_name
|
|
53
|
+
if self._save_path is not None:
|
|
54
|
+
full_path_file = self._save_path + '/' + self._file_name
|
|
55
|
+
print(f'INFO: Results will be automatically saved '
|
|
56
|
+
f'in {full_path_file}')
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def clear(self, channels=None):
|
|
60
|
+
"""
|
|
61
|
+
Clear all data
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
# clear data
|
|
65
|
+
if channels is None:
|
|
66
|
+
self._didv_data.clear()
|
|
67
|
+
else:
|
|
68
|
+
if isinstance(channels, str):
|
|
69
|
+
channels = [channels]
|
|
70
|
+
for chan in channels:
|
|
71
|
+
if chan in self._didv_data:
|
|
72
|
+
self._didv_data.pop(chan)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def get_didv_data(self, channel):
|
|
76
|
+
"""
|
|
77
|
+
Get dictionary with didv data
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
if channel in self._didv_data:
|
|
81
|
+
return self._didv_data[channel]
|
|
82
|
+
else:
|
|
83
|
+
raise ValueError(f'ERROR: No didv data available '
|
|
84
|
+
f'for channel "{channel}"!')
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def get_dpdi(self, channel, poles=3):
|
|
88
|
+
"""
|
|
89
|
+
Get dpdi data and frequencies
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
if channel not in self._didv_data:
|
|
94
|
+
raise ValueError(f'ERROR: No didv data available '
|
|
95
|
+
f'for channel "{channel}"!')
|
|
96
|
+
|
|
97
|
+
list_of_fitted_poles = (
|
|
98
|
+
self._didv_data[channel]['didvobj'].get_list_fitted_poles()
|
|
99
|
+
) or []
|
|
100
|
+
|
|
101
|
+
if poles not in list_of_fitted_poles:
|
|
102
|
+
raise ValueError(f'ERROR: No {poles}-poles fit found for '
|
|
103
|
+
f'channel {channel}!')
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# check dpdi available
|
|
107
|
+
poles_str = str(poles) + 'poles'
|
|
108
|
+
if 'dpdi_' + poles_str not in self._didv_data[channel]:
|
|
109
|
+
raise ValueError(f'ERROR: No dpdi found for '
|
|
110
|
+
f'channel {channel}!')
|
|
111
|
+
|
|
112
|
+
dpdi = self._didv_data[channel]['dpdi_' + poles_str]
|
|
113
|
+
dpdi_freqs = self._didv_data[channel]['dpdi_freqs_' + poles_str]
|
|
114
|
+
dpdi_err = self._didv_data[channel]['dpdi_err_' + poles_str]
|
|
115
|
+
|
|
116
|
+
return dpdi_freqs, dpdi, dpdi_err
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def get_qetpy_object(self, channel):
|
|
120
|
+
"""
|
|
121
|
+
Get QETpy object
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
didv_data = self.get_didv_data(channel)
|
|
125
|
+
if 'didvobj' in didv_data.keys():
|
|
126
|
+
return didv_data['didvobj']
|
|
127
|
+
else:
|
|
128
|
+
raise ValueError(f'ERROR: No dIdV QETpy object available '
|
|
129
|
+
f'for channel "{channel}"!')
|
|
130
|
+
|
|
131
|
+
def get_bias_params(self, channel, poles=3):
|
|
132
|
+
"""
|
|
133
|
+
Get "bias" (I0, R0, P0) parameters
|
|
134
|
+
(can be either "true" current or set by user)
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
fit_result = self.get_fit_results(channel, poles=poles)
|
|
138
|
+
if 'biasparams' in fit_result.keys():
|
|
139
|
+
return fit_result['biasparams']
|
|
140
|
+
else:
|
|
141
|
+
raise ValueError(f'ERROR: No bias parameters available '
|
|
142
|
+
f'for channel "{channel}"!')
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def get_bias_params_infinite_loop_gain(self, channel, poles=3):
|
|
146
|
+
"""
|
|
147
|
+
Get "bias" (I0, R0, P0) parameters with infinite loop gain
|
|
148
|
+
(can be either "true" current or set by user)
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
if (channel in self._didv_data
|
|
152
|
+
and 'biasparams_ilg' in self._didv_data[channel]
|
|
153
|
+
and poles in self._didv_data[channel]['biasparams_ilg']):
|
|
154
|
+
return self._didv_data[channel]['biasparams_ilg'][poles]
|
|
155
|
+
else:
|
|
156
|
+
raise ValueError(f'ERROR: No bias parameters with infinite '
|
|
157
|
+
f'loop gain available for channel "{channel}"!')
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def process_raw_data(self, channels, data_path,
|
|
162
|
+
stream=None,
|
|
163
|
+
overwrite=False):
|
|
164
|
+
|
|
165
|
+
"""
|
|
166
|
+
Set dIdV data for a specific channel
|
|
167
|
+
form raw traces. Apply cuts and pre-process
|
|
168
|
+
data. The function can be called multiple
|
|
169
|
+
times for different channels
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
# loop channel and process data
|
|
173
|
+
if isinstance(channels, str):
|
|
174
|
+
channels = [channels]
|
|
175
|
+
|
|
176
|
+
for chan in channels:
|
|
177
|
+
self._process_raw_data(chan, data_path,
|
|
178
|
+
stream=stream,
|
|
179
|
+
overwrite=overwrite)
|
|
180
|
+
|
|
181
|
+
# update save path if not set already
|
|
182
|
+
if self._save_path is None:
|
|
183
|
+
self._save_path = self._default_save_path(data_path)
|
|
184
|
+
|
|
185
|
+
def set_processed_data(self, channels,
|
|
186
|
+
data,
|
|
187
|
+
overwrite=False):
|
|
188
|
+
|
|
189
|
+
"""
|
|
190
|
+
Set dIdV data from already processed raw
|
|
191
|
+
data, in the form of pandas Series or
|
|
192
|
+
dictionary. The function can be called multiple
|
|
193
|
+
times for different channels
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
didv_data = data.copy()
|
|
198
|
+
|
|
199
|
+
# check channels and data
|
|
200
|
+
if isinstance(channels, str):
|
|
201
|
+
if channels not in data.keys():
|
|
202
|
+
didv_data = dict()
|
|
203
|
+
didv_data[channels] = data
|
|
204
|
+
channels = [channels]
|
|
205
|
+
|
|
206
|
+
for chan in channels:
|
|
207
|
+
if chan not in didv_data.keys():
|
|
208
|
+
raise ValueError(f'ERROR: Unable to find channel '
|
|
209
|
+
f' {chan} in data dictionary!')
|
|
210
|
+
|
|
211
|
+
# set data
|
|
212
|
+
for chan in channels:
|
|
213
|
+
self._set_processed_data(chan,
|
|
214
|
+
didv_data[chan],
|
|
215
|
+
overwrite=overwrite)
|
|
216
|
+
|
|
217
|
+
def set_bias_params_from_data(self, channel, results,
|
|
218
|
+
result_type='noise', tag='default',
|
|
219
|
+
metadata=None):
|
|
220
|
+
"""Attach operating-point bias parameters to a processed dIdV.
|
|
221
|
+
|
|
222
|
+
The bias parameters may come from a full IV sweep or from a short
|
|
223
|
+
beginning-of-cycle normal-IV calibration. ``set_ivsweep_results_from_data``
|
|
224
|
+
remains as a compatibility wrapper around this more general method.
|
|
225
|
+
"""
|
|
226
|
+
if isinstance(results, pd.Series):
|
|
227
|
+
results = results.to_dict()
|
|
228
|
+
elif not isinstance(results, dict):
|
|
229
|
+
raise ValueError(
|
|
230
|
+
'ERROR: "results" should be a dictionary or pandas Series'
|
|
231
|
+
)
|
|
232
|
+
if not isinstance(channel, str):
|
|
233
|
+
raise ValueError('ERROR: This function is for a single channel!')
|
|
234
|
+
if channel not in self._didv_data:
|
|
235
|
+
raise ValueError(
|
|
236
|
+
f'ERROR: Channel {channel} not found. You first need to set dIdV data!'
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
biasparams = results.copy()
|
|
240
|
+
self.set_ivsweep_results(
|
|
241
|
+
channel,
|
|
242
|
+
pd.Series(biasparams),
|
|
243
|
+
result_type,
|
|
244
|
+
metadata=metadata,
|
|
245
|
+
tag=tag,
|
|
246
|
+
)
|
|
247
|
+
self._didv_data[channel]['ivsweep_results'] = biasparams.copy()
|
|
248
|
+
|
|
249
|
+
rp = biasparams.get('rp')
|
|
250
|
+
r0 = biasparams.get('r0')
|
|
251
|
+
rshunt = biasparams.get('rshunt', biasparams.get('rsh'))
|
|
252
|
+
if rp is None:
|
|
253
|
+
raise ValueError(
|
|
254
|
+
f'ERROR: "rp" is missing from bias parameters for channel {channel}!'
|
|
255
|
+
)
|
|
256
|
+
self._didv_data[channel]['data_config']['rp'] = rp
|
|
257
|
+
self._didv_data[channel]['didvobj']._rp = rp
|
|
258
|
+
if r0 is not None:
|
|
259
|
+
self._didv_data[channel]['didvobj']._r0 = r0
|
|
260
|
+
if rshunt is not None:
|
|
261
|
+
self._didv_data[channel]['data_config']['rshunt'] = rshunt
|
|
262
|
+
self._didv_data[channel]['didvobj']._rsh = rshunt
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def set_ivsweep_results_from_data(self,
|
|
266
|
+
channel,
|
|
267
|
+
results,
|
|
268
|
+
iv_type='noise',
|
|
269
|
+
tag='default',
|
|
270
|
+
metadata=None):
|
|
271
|
+
"""Compatibility wrapper for :meth:`set_bias_params_from_data`."""
|
|
272
|
+
return self.set_bias_params_from_data(
|
|
273
|
+
channel,
|
|
274
|
+
results,
|
|
275
|
+
result_type=iv_type,
|
|
276
|
+
tag=tag,
|
|
277
|
+
metadata=metadata,
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def set_ivsweep_results_from_file(self,
|
|
282
|
+
channels,
|
|
283
|
+
file_name,
|
|
284
|
+
iv_type='noise',
|
|
285
|
+
include_bias_parameters=False):
|
|
286
|
+
"""
|
|
287
|
+
Set results from IV sweeps, in particular
|
|
288
|
+
I0 offset, V0 offset
|
|
289
|
+
"""
|
|
290
|
+
|
|
291
|
+
# check channels
|
|
292
|
+
if isinstance(channels, str):
|
|
293
|
+
channels = [channels]
|
|
294
|
+
|
|
295
|
+
for chan in channels:
|
|
296
|
+
if chan not in self._didv_data:
|
|
297
|
+
raise ValueError(f'ERROR: Channel {chan} not found. '
|
|
298
|
+
' You first need to set data!' )
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
# load data from file
|
|
302
|
+
if file_name is not None:
|
|
303
|
+
self.load_hdf5(file_name)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
# loop channels
|
|
307
|
+
for chan in channels:
|
|
308
|
+
|
|
309
|
+
# get tes bias
|
|
310
|
+
tes_bias = None
|
|
311
|
+
if include_bias_parameters:
|
|
312
|
+
data_config = self._didv_data[chan]['data_config']
|
|
313
|
+
tes_bias = data_config['tes_bias']
|
|
314
|
+
|
|
315
|
+
# get results
|
|
316
|
+
results = self.get_ivsweep_results(
|
|
317
|
+
chan,
|
|
318
|
+
iv_type=iv_type,
|
|
319
|
+
include_bias_parameters=include_bias_parameters,
|
|
320
|
+
tes_bias=tes_bias,
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
# save in include bias parameters
|
|
324
|
+
if include_bias_parameters:
|
|
325
|
+
pd_series = pd.Series(results.copy())
|
|
326
|
+
self.set_ivsweep_results(chan,
|
|
327
|
+
pd_series,
|
|
328
|
+
iv_type)
|
|
329
|
+
|
|
330
|
+
# keep a copy as part of didv data
|
|
331
|
+
self._didv_data[chan]['ivsweep_results'] = results.copy()
|
|
332
|
+
|
|
333
|
+
# check if Rp, Rshunt, R0 available
|
|
334
|
+
if 'rp' in results.keys():
|
|
335
|
+
rp = results['rp']
|
|
336
|
+
else:
|
|
337
|
+
raise ValueError(f'ERROR: "rp" is missing from IV results for '
|
|
338
|
+
f'channel {chan}. It needs to be added!')
|
|
339
|
+
|
|
340
|
+
r0 = None
|
|
341
|
+
if 'r0' in results.keys():
|
|
342
|
+
r0 = results['r0']
|
|
343
|
+
|
|
344
|
+
rshunt = None
|
|
345
|
+
if 'rshunt' in results.keys():
|
|
346
|
+
rshunt = results['rshunt']
|
|
347
|
+
|
|
348
|
+
# replace internal object data
|
|
349
|
+
if rp is not None:
|
|
350
|
+
self._didv_data[chan]['data_config']['rp'] = rp
|
|
351
|
+
self._didv_data[chan]['didvobj']._rp = rp
|
|
352
|
+
|
|
353
|
+
if r0 is not None:
|
|
354
|
+
self._didv_data[chan]['didvobj']._r0 = r0
|
|
355
|
+
|
|
356
|
+
if (rshunt is not None
|
|
357
|
+
and self._didv_data[chan]['data_config']['rshunt'] is None):
|
|
358
|
+
self._didv_data[chan]['data_config']['rshunt'] = rshunt
|
|
359
|
+
self._didv_data[chan]['didvobj']._rsh = rshunt
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def dofit(self, list_of_poles, channels=None,
|
|
364
|
+
fcutoff=np.inf, bounds=None, guess_params=None,
|
|
365
|
+
guess_isloopgainsub1=None, lgc_fix=None,
|
|
366
|
+
add180phase=False, dt0=1.5e-6,
|
|
367
|
+
lgc_plot=False,
|
|
368
|
+
tag='default'):
|
|
369
|
+
|
|
370
|
+
"""
|
|
371
|
+
Do dIdV fit for one or more channels. The function
|
|
372
|
+
"set_data" needs to be called prior "dofit" to store
|
|
373
|
+
didv data
|
|
374
|
+
|
|
375
|
+
"""
|
|
376
|
+
|
|
377
|
+
# check if data available for each channel
|
|
378
|
+
if channels is None:
|
|
379
|
+
channels = self._didv_data.keys()
|
|
380
|
+
elif isinstance(channels, str):
|
|
381
|
+
channels = [channels]
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
for chan in channels:
|
|
385
|
+
if chan not in self._didv_data:
|
|
386
|
+
raise ValueError(
|
|
387
|
+
f'ERROR: dIdV data for channel {chan} not available. '
|
|
388
|
+
'Set data first!'
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
# poles
|
|
393
|
+
if isinstance(list_of_poles, int):
|
|
394
|
+
list_of_poles = [list_of_poles]
|
|
395
|
+
else:
|
|
396
|
+
list_of_poles = list(list_of_poles)
|
|
397
|
+
list_of_poles.sort()
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
# loop channel and do fit
|
|
401
|
+
for chan in channels:
|
|
402
|
+
|
|
403
|
+
# get didv obj
|
|
404
|
+
didvobj = self._didv_data[chan]['didvobj']
|
|
405
|
+
didvobj._add180phase = add180phase
|
|
406
|
+
didvobj._dt0 = dt0
|
|
407
|
+
|
|
408
|
+
for poles in list_of_poles:
|
|
409
|
+
didvobj.dofit(
|
|
410
|
+
poles,
|
|
411
|
+
fcutoff=fcutoff,
|
|
412
|
+
bounds=bounds,
|
|
413
|
+
guess_params=guess_params,
|
|
414
|
+
guess_isloopgainsub1=guess_isloopgainsub1,
|
|
415
|
+
lgcfix=lgc_fix
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
# replace object with results included
|
|
419
|
+
self._didv_data[chan]['didvobj'] = didvobj
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
if lgc_plot:
|
|
423
|
+
|
|
424
|
+
if 2 in list_of_poles:
|
|
425
|
+
self.print_fit_result(chan, poles=2)
|
|
426
|
+
|
|
427
|
+
if 3 in list_of_poles:
|
|
428
|
+
self.print_fit_result(chan, poles=3)
|
|
429
|
+
|
|
430
|
+
self.plot_fit_result(chan)
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
# save data
|
|
434
|
+
self.save_didv_data(save_hdf5=self._save_hdf5, tag=tag)
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def calc_smallsignal_params(self,
|
|
439
|
+
channels=None,
|
|
440
|
+
poles=None,
|
|
441
|
+
priors_fit_method=False,
|
|
442
|
+
lgc_diagnostics=False,
|
|
443
|
+
tag='default'):
|
|
444
|
+
"""
|
|
445
|
+
Calculate small signal parameters with uncertainties
|
|
446
|
+
(optionally) using current from IV sweep.
|
|
447
|
+
|
|
448
|
+
"""
|
|
449
|
+
print('Calculating small signal parameters...')
|
|
450
|
+
# check channels
|
|
451
|
+
if channels is None:
|
|
452
|
+
channels = self._didv_data.keys()
|
|
453
|
+
elif isinstance(channels, str):
|
|
454
|
+
channels = [channels]
|
|
455
|
+
|
|
456
|
+
for chan in channels:
|
|
457
|
+
if chan not in self._didv_data.keys():
|
|
458
|
+
raise ValueError(f'ERROR: dIdV fit not available for '
|
|
459
|
+
f'channel {chan}! Use "dofit" first.')
|
|
460
|
+
|
|
461
|
+
# loop channel
|
|
462
|
+
for chan in channels:
|
|
463
|
+
|
|
464
|
+
if self._verbose:
|
|
465
|
+
print(f'INFO: Calculating small signal parameters '
|
|
466
|
+
f'uncertainties for channel {chan}')
|
|
467
|
+
|
|
468
|
+
# data config
|
|
469
|
+
data_config = self._didv_data[chan]['data_config']
|
|
470
|
+
tes_bias = data_config['tes_bias']
|
|
471
|
+
|
|
472
|
+
# get didvobj
|
|
473
|
+
didvobj = self._didv_data[chan]['didvobj']
|
|
474
|
+
|
|
475
|
+
# get bias parameters from IV-sweep/normal-IV processing
|
|
476
|
+
biasparams = None
|
|
477
|
+
if ('ivsweep_results' in self._didv_data[chan]
|
|
478
|
+
and self._didv_data[chan]['ivsweep_results'] is not None):
|
|
479
|
+
biasparams = self._didv_data[chan]['ivsweep_results']
|
|
480
|
+
|
|
481
|
+
if 'ibias' in biasparams:
|
|
482
|
+
tes_bias = biasparams['ibias']
|
|
483
|
+
else:
|
|
484
|
+
biasparams['ibias'] = tes_bias
|
|
485
|
+
|
|
486
|
+
if self._verbose:
|
|
487
|
+
print(f'INFO: Using ibias={tes_bias:.3e} A from '
|
|
488
|
+
f'IV sweep for channel {chan}')
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
# calculate small signal params
|
|
492
|
+
didvobj.calc_smallsignal_params(
|
|
493
|
+
biasparams=biasparams,
|
|
494
|
+
poles=poles,
|
|
495
|
+
lgc_diagnostics=lgc_diagnostics
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
# replace
|
|
499
|
+
self._didv_data[chan]['didvobj'] = didvobj
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
# store data
|
|
503
|
+
self.save_didv_data(save_hdf5=self._save_hdf5, tag=tag)
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
def calc_bias_params_infinite_loop_gain(self, channels=None, tag='default'):
|
|
507
|
+
|
|
508
|
+
#
|
|
509
|
+
#Calculate I0,R0, and P0 with infinite loop gain
|
|
510
|
+
#approximation
|
|
511
|
+
#
|
|
512
|
+
|
|
513
|
+
# check channels
|
|
514
|
+
if channels is None:
|
|
515
|
+
channels = self._didv_data.keys()
|
|
516
|
+
elif isinstance(channels, str):
|
|
517
|
+
channels = [channels]
|
|
518
|
+
# get list of fitted poles
|
|
519
|
+
for chan in channels:
|
|
520
|
+
if chan not in self._didv_data.keys():
|
|
521
|
+
raise ValueError(f'ERROR: channel {chan} not available!')
|
|
522
|
+
didvobj = self._didv_data[chan]['didvobj']
|
|
523
|
+
list_of_fitted_poles = didvobj.get_list_fitted_poles()
|
|
524
|
+
if (list_of_fitted_poles is None
|
|
525
|
+
or (2 not in list_of_fitted_poles
|
|
526
|
+
and 3 not in list_of_fitted_poles)):
|
|
527
|
+
raise ValueError(f'ERROR: No fit available for '
|
|
528
|
+
f'channel {chan}!')
|
|
529
|
+
|
|
530
|
+
# loop channel
|
|
531
|
+
for chan in channels:
|
|
532
|
+
|
|
533
|
+
if self._verbose:
|
|
534
|
+
print(f'INFO: Calculating bias parameters with '
|
|
535
|
+
f'infinite loop gain approximation '
|
|
536
|
+
f'for channel {chan}')
|
|
537
|
+
|
|
538
|
+
biasparams_ilg = dict()
|
|
539
|
+
|
|
540
|
+
# get data config
|
|
541
|
+
data_config = self._didv_data[chan]['data_config']
|
|
542
|
+
|
|
543
|
+
# get didvobj
|
|
544
|
+
didvobj = self._didv_data[chan]['didvobj']
|
|
545
|
+
list_of_fitted_poles = didvobj.get_list_fitted_poles()
|
|
546
|
+
|
|
547
|
+
# loop poles
|
|
548
|
+
for poles in list_of_fitted_poles:
|
|
549
|
+
|
|
550
|
+
if (poles != 2 and poles != 3):
|
|
551
|
+
continue
|
|
552
|
+
|
|
553
|
+
fitresult = didvobj.fitresult(poles)
|
|
554
|
+
|
|
555
|
+
# check if result available
|
|
556
|
+
if (fitresult is None
|
|
557
|
+
or 'params' not in fitresult):
|
|
558
|
+
continue
|
|
559
|
+
|
|
560
|
+
ibias = None
|
|
561
|
+
ibias_err = 0
|
|
562
|
+
rp = None
|
|
563
|
+
rshunt = None
|
|
564
|
+
rn = None
|
|
565
|
+
if ('biasparams' in fitresult.keys() and
|
|
566
|
+
fitresult['biasparams'] is not None):
|
|
567
|
+
|
|
568
|
+
ibias = fitresult['biasparams']['ibias']
|
|
569
|
+
if 'ibias_err' in fitresult['biasparams']:
|
|
570
|
+
ibias_err = fitresult['biasparams']['ibias_err']
|
|
571
|
+
else:
|
|
572
|
+
ibias_err = 0
|
|
573
|
+
rp = fitresult['biasparams']['rp']
|
|
574
|
+
rshunt = fitresult['biasparams']['rshunt']
|
|
575
|
+
if 'rn' in fitresult['biasparams']:
|
|
576
|
+
rn = fitresult['biasparams']['rn']
|
|
577
|
+
else:
|
|
578
|
+
ibias = data_config['tes_bias']
|
|
579
|
+
if 'rp' not in data_config:
|
|
580
|
+
raise ValueError('ERROR: Unable to find rp!'
|
|
581
|
+
'use "set_ivsweep_results()" first')
|
|
582
|
+
rp = data_config['rp']
|
|
583
|
+
rshunt = data_config['rshunt']
|
|
584
|
+
if 'rn' in data_config:
|
|
585
|
+
rn = data_config['rn']
|
|
586
|
+
|
|
587
|
+
biasparams_ilg[poles] = qp.get_biasparams_ilg(
|
|
588
|
+
fitresult['params'],
|
|
589
|
+
fitresult['cov'],
|
|
590
|
+
ibias,
|
|
591
|
+
ibias_err,
|
|
592
|
+
rshunt,
|
|
593
|
+
rp,
|
|
594
|
+
rn=rn)
|
|
595
|
+
|
|
596
|
+
self._didv_data[chan]['biasparams_ilg'] = biasparams_ilg
|
|
597
|
+
|
|
598
|
+
# store data
|
|
599
|
+
self.save_didv_data(save_hdf5=self._save_hdf5, tag=tag)
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
def calc_dpdi(self, freqs, channels=None, list_of_poles=None,
|
|
603
|
+
lgc_plot=False, tag='default'):
|
|
604
|
+
"""
|
|
605
|
+
calculate dpdi in units of Volts
|
|
606
|
+
"""
|
|
607
|
+
|
|
608
|
+
# check channels
|
|
609
|
+
if channels is None:
|
|
610
|
+
channels = self._didv_data.keys()
|
|
611
|
+
elif isinstance(channels, str):
|
|
612
|
+
channels = [channels]
|
|
613
|
+
|
|
614
|
+
for chan in channels:
|
|
615
|
+
if chan not in self._didv_data.keys():
|
|
616
|
+
raise ValueError(
|
|
617
|
+
f'ERROR: No data found for channel {chan}!'
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
for chan in channels:
|
|
621
|
+
|
|
622
|
+
# get didvobj
|
|
623
|
+
didvobj = self._didv_data[chan]['didvobj']
|
|
624
|
+
|
|
625
|
+
# list of poles
|
|
626
|
+
list_of_fitted_poles = didvobj.get_list_fitted_poles() or []
|
|
627
|
+
if list_of_poles is None:
|
|
628
|
+
poles_to_process = list(list_of_fitted_poles)
|
|
629
|
+
elif isinstance(list_of_poles, int):
|
|
630
|
+
poles_to_process = [list_of_poles]
|
|
631
|
+
else:
|
|
632
|
+
poles_to_process = list(list_of_poles)
|
|
633
|
+
|
|
634
|
+
|
|
635
|
+
# loop poles
|
|
636
|
+
for poles in poles_to_process:
|
|
637
|
+
|
|
638
|
+
if poles not in list_of_fitted_poles:
|
|
639
|
+
raise ValueError(f'ERROR: No fit available for poles '
|
|
640
|
+
f'{poles}. Use "dofit" function first!')
|
|
641
|
+
|
|
642
|
+
fitresult = didvobj.fitresult(poles)
|
|
643
|
+
|
|
644
|
+
dpdi, dpdi_err = qp.get_dPdI_with_uncertainties(
|
|
645
|
+
freqs, fitresult,
|
|
646
|
+
lgcplot=lgc_plot)
|
|
647
|
+
|
|
648
|
+
poles_str = str(poles) + 'poles'
|
|
649
|
+
self._didv_data[chan]['dpdi_' + poles_str] = dpdi
|
|
650
|
+
self._didv_data[chan]['dpdi_err_' + poles_str] = dpdi_err
|
|
651
|
+
self._didv_data[chan]['dpdi_freqs_' + poles_str] = freqs
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
# store data
|
|
656
|
+
self.save_didv_data(save_hdf5=self._save_hdf5, tag=tag)
|
|
657
|
+
|
|
658
|
+
def calc_energy_resolution(self, channel, psd,
|
|
659
|
+
poles=3, fs=None, template=None,
|
|
660
|
+
collection_eff=1,
|
|
661
|
+
lgc_power_template=False):
|
|
662
|
+
"""
|
|
663
|
+
Get energy resolution based using calculated dpdi and input
|
|
664
|
+
psd and template (dirac delta power input if template is None)
|
|
665
|
+
Default is template in current.
|
|
666
|
+
"""
|
|
667
|
+
|
|
668
|
+
# check data availability
|
|
669
|
+
if channel not in self._didv_data.keys():
|
|
670
|
+
raise ValueError(f'ERROR: No data found for channel {channel}!')
|
|
671
|
+
|
|
672
|
+
list_of_fitted_poles = (
|
|
673
|
+
self._didv_data[channel]['didvobj'].get_list_fitted_poles()
|
|
674
|
+
)
|
|
675
|
+
if poles not in list_of_fitted_poles:
|
|
676
|
+
raise ValueError(
|
|
677
|
+
f'ERROR: No {poles}-poles fit found for channel {channel}!'
|
|
678
|
+
)
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
# check dpdi available
|
|
682
|
+
poles_str = str(poles) + 'poles'
|
|
683
|
+
if 'dpdi_' + poles_str not in self._didv_data[channel]:
|
|
684
|
+
raise ValueError(f'ERROR: No dpdi found for channel {channel}!')
|
|
685
|
+
|
|
686
|
+
dpdi = self._didv_data[channel]['dpdi_' + poles_str]
|
|
687
|
+
dpdi_freqs = self._didv_data[channel]['dpdi_freqs_' + poles_str]
|
|
688
|
+
|
|
689
|
+
# sample rate
|
|
690
|
+
if fs is None:
|
|
691
|
+
|
|
692
|
+
if 'fs' not in self._didv_data[channel]['data_config']:
|
|
693
|
+
raise ValueError(f'ERROR: sample rate (fs) required!')
|
|
694
|
+
|
|
695
|
+
fs = self._didv_data[channel]['data_config']['fs']
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
if template is None:
|
|
699
|
+
|
|
700
|
+
# dirac delta input power
|
|
701
|
+
nbins = psd.shape[-1]
|
|
702
|
+
pretrigger_index = nbins//2
|
|
703
|
+
template = unit_impulse(nbins, idx=pretrigger_index)
|
|
704
|
+
|
|
705
|
+
elif not lgc_power_template:
|
|
706
|
+
template = qp.convert_template_to_power(template, dpdi=dpdi)
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
# calculate energy resolution
|
|
710
|
+
resolution = qp.utils.energy_resolution(
|
|
711
|
+
psd, template, dpdi, fs,
|
|
712
|
+
collection_eff=collection_eff)
|
|
713
|
+
|
|
714
|
+
# store
|
|
715
|
+
res_dict = dict()
|
|
716
|
+
res_dict['psd'] = psd
|
|
717
|
+
res_dict['template'] = template
|
|
718
|
+
res_dict['fs'] = fs
|
|
719
|
+
res_dict['collection_efficiency'] = collection_eff
|
|
720
|
+
res_dict['energy_resolution'] = resolution
|
|
721
|
+
self._didv_data[channel]['resolution'] = res_dict
|
|
722
|
+
|
|
723
|
+
return resolution
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
def dofit_prior(self, channels=None, list_of_poles=None,
|
|
727
|
+
use_ivsweep_current=True):
|
|
728
|
+
"""Refit 2/3-pole models with priors derived from existing fits.
|
|
729
|
+
|
|
730
|
+
This is a legacy helper. The previous implementation referenced
|
|
731
|
+
several undefined analysis-local variables. Priors are now built
|
|
732
|
+
only from the selected channel's existing QETpy fit result, with
|
|
733
|
+
available bias-parameter uncertainties used for Rsh, Rp, and R0.
|
|
734
|
+
"""
|
|
735
|
+
|
|
736
|
+
if channels is None:
|
|
737
|
+
channels = list(self._didv_data)
|
|
738
|
+
elif isinstance(channels, str):
|
|
739
|
+
channels = [channels]
|
|
740
|
+
else:
|
|
741
|
+
channels = list(channels)
|
|
742
|
+
|
|
743
|
+
for chan in channels:
|
|
744
|
+
if chan not in self._didv_data:
|
|
745
|
+
raise ValueError(
|
|
746
|
+
f'ERROR: dIdV fit result not found for channel {chan}! '
|
|
747
|
+
'Use "dofit" first.'
|
|
748
|
+
)
|
|
749
|
+
|
|
750
|
+
didvobj = self._didv_data[chan]['didvobj']
|
|
751
|
+
fitted_poles = didvobj.get_list_fitted_poles() or []
|
|
752
|
+
if list_of_poles is None:
|
|
753
|
+
poles_to_fit = [p for p in fitted_poles if p in (2, 3)]
|
|
754
|
+
elif isinstance(list_of_poles, int):
|
|
755
|
+
poles_to_fit = [list_of_poles]
|
|
756
|
+
else:
|
|
757
|
+
poles_to_fit = list(list_of_poles)
|
|
758
|
+
|
|
759
|
+
if not poles_to_fit:
|
|
760
|
+
raise ValueError(
|
|
761
|
+
f'ERROR: No 2/3-pole fit available for channel {chan}'
|
|
762
|
+
)
|
|
763
|
+
missing = [p for p in poles_to_fit if p not in fitted_poles]
|
|
764
|
+
if missing:
|
|
765
|
+
raise ValueError(
|
|
766
|
+
f'ERROR: Missing existing fit(s) {missing} for channel {chan}'
|
|
767
|
+
)
|
|
768
|
+
|
|
769
|
+
if self._verbose:
|
|
770
|
+
print(f'INFO: Do prior fit(s) for channel {chan}')
|
|
771
|
+
|
|
772
|
+
didvobj_prior = qp.DIDVPriors(
|
|
773
|
+
rawtraces=None,
|
|
774
|
+
fs=didvobj._fs,
|
|
775
|
+
sgfreq=didvobj._sgfreq,
|
|
776
|
+
sgamp=didvobj._sgamp,
|
|
777
|
+
rsh=didvobj._rsh,
|
|
778
|
+
dutycycle=didvobj._dutycycle,
|
|
779
|
+
)
|
|
780
|
+
didvobj_prior._time = didvobj._time
|
|
781
|
+
didvobj_prior._freq = didvobj._freq
|
|
782
|
+
didvobj_prior._didvmean = didvobj._didvmean
|
|
783
|
+
didvobj_prior._didvstd = didvobj._didvstd
|
|
784
|
+
didvobj_prior._offset = didvobj._offset
|
|
785
|
+
didvobj_prior._offset_err = didvobj._offset_err
|
|
786
|
+
didvobj_prior._tmean = didvobj._tmean
|
|
787
|
+
didvobj_prior._dt0 = didvobj._dt0
|
|
788
|
+
|
|
789
|
+
for poles_select in poles_to_fit:
|
|
790
|
+
results = didvobj.fitresult(poles_select)
|
|
791
|
+
smallsignal = results.get('smallsignalparams') if results else None
|
|
792
|
+
if not smallsignal:
|
|
793
|
+
raise ValueError(
|
|
794
|
+
f'ERROR: Small-signal parameters are required before '
|
|
795
|
+
f'prior fitting {poles_select} poles for channel {chan}'
|
|
796
|
+
)
|
|
797
|
+
|
|
798
|
+
if poles_select == 2:
|
|
799
|
+
names = ['rsh', 'rp', 'r0', 'beta', 'l', 'L', 'tau0', 'dt']
|
|
800
|
+
elif poles_select == 3:
|
|
801
|
+
names = [
|
|
802
|
+
'rsh', 'rp', 'r0', 'beta', 'l', 'L',
|
|
803
|
+
'tau0', 'gratio', 'tau3', 'dt',
|
|
804
|
+
]
|
|
805
|
+
else:
|
|
806
|
+
raise ValueError('ERROR: prior fitting supports 2 or 3 poles')
|
|
807
|
+
|
|
808
|
+
priors = np.asarray([smallsignal[name] for name in names], dtype=float)
|
|
809
|
+
priors_cov = np.diag((0.1 * np.abs(priors)) ** 2)
|
|
810
|
+
|
|
811
|
+
biasparams = results.get('biasparams') or {}
|
|
812
|
+
sigma_rsh = float(
|
|
813
|
+
biasparams.get('rshunt_err', biasparams.get('rsh_err', 0.1 * abs(priors[0])))
|
|
814
|
+
)
|
|
815
|
+
sigma_rp = float(
|
|
816
|
+
biasparams.get('rp_err', 0.1 * abs(priors[1]))
|
|
817
|
+
)
|
|
818
|
+
sigma_r0 = float(
|
|
819
|
+
biasparams.get('r0_err', 0.1 * abs(priors[2]))
|
|
820
|
+
)
|
|
821
|
+
|
|
822
|
+
priors_cov[0, 0] = sigma_rsh ** 2
|
|
823
|
+
priors_cov[1, 1] = sigma_rp ** 2
|
|
824
|
+
priors_cov[2, 2] = sigma_r0 ** 2
|
|
825
|
+
priors_cov[0, 1] = priors_cov[1, 0] = 0.5 * sigma_rsh * sigma_rp
|
|
826
|
+
priors_cov[0, 2] = priors_cov[2, 0] = 0.5 * sigma_rsh * sigma_r0
|
|
827
|
+
priors_cov[1, 2] = priors_cov[2, 1] = -0.2 * sigma_rp * sigma_r0
|
|
828
|
+
|
|
829
|
+
didvobj_prior.dofit(
|
|
830
|
+
poles=poles_select,
|
|
831
|
+
priors=priors,
|
|
832
|
+
priorscov=priors_cov,
|
|
833
|
+
)
|
|
834
|
+
|
|
835
|
+
self._didv_data[chan]['didvobj_prior'] = didvobj_prior
|
|
836
|
+
|
|
837
|
+
def get_fit_results(self, channel, poles, verbose=True):
|
|
838
|
+
"""
|
|
839
|
+
Get fit result
|
|
840
|
+
"""
|
|
841
|
+
|
|
842
|
+
# check if object available
|
|
843
|
+
if channel not in self._didv_data:
|
|
844
|
+
raise ValueError(f'ERROR: dIdV data not available for '
|
|
845
|
+
f'channel {channel}!')
|
|
846
|
+
|
|
847
|
+
# check if fit done
|
|
848
|
+
result = self._didv_data[channel]['didvobj'].fitresult(poles)
|
|
849
|
+
if not result:
|
|
850
|
+
if self._verbose and verbose:
|
|
851
|
+
print(f'WARNING: {channel}: No fit result found for poles {poles}! '
|
|
852
|
+
'Returning empty dictionary.')
|
|
853
|
+
return result
|
|
854
|
+
|
|
855
|
+
|
|
856
|
+
def plot_fit_result(self, channels=None,
|
|
857
|
+
lgc_plot_fft=True, lgc_gray_mean=True,
|
|
858
|
+
lgc_didv_freq_filt=True,
|
|
859
|
+
zoom_factor=None, fcutoff=2e4,
|
|
860
|
+
lgc_save=False, save_path=None,
|
|
861
|
+
save_name=None):
|
|
862
|
+
"""
|
|
863
|
+
Plot fit results for multiple channels
|
|
864
|
+
"""
|
|
865
|
+
|
|
866
|
+
|
|
867
|
+
# check channels
|
|
868
|
+
if channels is None:
|
|
869
|
+
channels = self._didv_data.keys()
|
|
870
|
+
elif isinstance(channels, str):
|
|
871
|
+
channels = [channels]
|
|
872
|
+
|
|
873
|
+
for chan in channels:
|
|
874
|
+
if chan not in self._didv_data.keys():
|
|
875
|
+
raise ValueError(
|
|
876
|
+
f'ERROR: dIdV fit result not found for channel {chan}! '
|
|
877
|
+
'Use "dofit" first.'
|
|
878
|
+
)
|
|
879
|
+
|
|
880
|
+
# look chanels
|
|
881
|
+
for chan in channels:
|
|
882
|
+
|
|
883
|
+
didvobj = self._didv_data[chan]['didvobj']
|
|
884
|
+
|
|
885
|
+
# check if any results
|
|
886
|
+
is_fitted = False
|
|
887
|
+
poles_list = [1,2,3]
|
|
888
|
+
for poles in poles_list:
|
|
889
|
+
if didvobj.fitresult(poles):
|
|
890
|
+
is_fitted = True
|
|
891
|
+
if not is_fitted:
|
|
892
|
+
print(f'WARNING: No fit result available for channel {chan}!')
|
|
893
|
+
continue
|
|
894
|
+
|
|
895
|
+
# display
|
|
896
|
+
if self._verbose:
|
|
897
|
+
print(f'\n{chan} dIdV Fit Plots:')
|
|
898
|
+
|
|
899
|
+
didvobj.plot_full_trace(
|
|
900
|
+
didv_freq_filt=lgc_didv_freq_filt,
|
|
901
|
+
gray_mean=lgc_gray_mean,
|
|
902
|
+
saveplot=lgc_save,
|
|
903
|
+
savepath=save_path,
|
|
904
|
+
savename=save_name,
|
|
905
|
+
lp_cutoff=fcutoff,
|
|
906
|
+
)
|
|
907
|
+
|
|
908
|
+
if zoom_factor is not None:
|
|
909
|
+
didvobj.plot_zoomed_in_trace(
|
|
910
|
+
didv_freq_filt=lgc_didv_freq_filt,
|
|
911
|
+
saveplot=lgc_save,
|
|
912
|
+
savepath=save_path,
|
|
913
|
+
savename=save_name,
|
|
914
|
+
lp_cutoff=fcutoff,
|
|
915
|
+
zoomfactor=zoom_factor,
|
|
916
|
+
)
|
|
917
|
+
|
|
918
|
+
if lgc_plot_fft:
|
|
919
|
+
didvobj.plot_re_im_didv(
|
|
920
|
+
poles='all',
|
|
921
|
+
saveplot=lgc_save,
|
|
922
|
+
savepath=save_path,
|
|
923
|
+
savename=save_name,
|
|
924
|
+
)
|
|
925
|
+
|
|
926
|
+
|
|
927
|
+
def print_fit_result(self, channels=None, poles=3):
|
|
928
|
+
"""
|
|
929
|
+
Print fit information
|
|
930
|
+
"""
|
|
931
|
+
|
|
932
|
+
# check channels
|
|
933
|
+
if channels is None:
|
|
934
|
+
channels = self._didv_data.keys()
|
|
935
|
+
elif isinstance(channels, str):
|
|
936
|
+
channels = [channels]
|
|
937
|
+
|
|
938
|
+
for chan in channels:
|
|
939
|
+
if chan not in self._didv_data.keys():
|
|
940
|
+
raise ValueError(
|
|
941
|
+
f'ERROR: dIdV fit result not found for channel {chan}! '
|
|
942
|
+
'Use "dofit" first.'
|
|
943
|
+
)
|
|
944
|
+
|
|
945
|
+
# look chanels
|
|
946
|
+
for chan in channels:
|
|
947
|
+
|
|
948
|
+
didvobj = self._didv_data[chan]['didvobj']
|
|
949
|
+
|
|
950
|
+
# check if any results
|
|
951
|
+
results = didvobj.fitresult(poles)
|
|
952
|
+
if not results:
|
|
953
|
+
print(f'WARNING: No fit result available for channel {chan}!')
|
|
954
|
+
continue
|
|
955
|
+
|
|
956
|
+
print(f'\n{chan} dIdV {poles}-poles Fit Result:')
|
|
957
|
+
|
|
958
|
+
|
|
959
|
+
print('Fit chi2/Ndof = {:.3f}'.format(results['cost']))
|
|
960
|
+
print('\nFit time constants, NOT dIdV Poles: ')
|
|
961
|
+
print('Tau1: {:.3g} s'.format(np.abs(results['params']['tau1'])))
|
|
962
|
+
print('Tau2: {:.3g} s'.format(results['params']['tau2']))
|
|
963
|
+
print('Tau3: {:.4g} s'.format(results['params']['tau3']))
|
|
964
|
+
print(' ')
|
|
965
|
+
print('\nTrue dIdV Poles: ')
|
|
966
|
+
print('Tau_plus: {:.3g} s'.format(results['falltimes'][0]))
|
|
967
|
+
print('Tau_minus: {:.3g} s'.format(results['falltimes'][1]))
|
|
968
|
+
print('Tau_third: {:.4g} s'.format(results['falltimes'][2]))
|
|
969
|
+
|
|
970
|
+
if 'ssp_light' in results:
|
|
971
|
+
vals_vector = results['ssp_light']['vals']
|
|
972
|
+
sigmas_vector = results['ssp_light']['sigmas']
|
|
973
|
+
print('\nSmall Signal Parameters:')
|
|
974
|
+
print('l (loop gain) = {:.3f} +/- {:.4f}'.format(
|
|
975
|
+
vals_vector['l'], sigmas_vector['sigma_l']))
|
|
976
|
+
print('beta = {:.3f} +/- {:.4f}'.format(
|
|
977
|
+
vals_vector['beta'], sigmas_vector['sigma_beta']))
|
|
978
|
+
print('gratio = {:.3f} +/- {:.4f}'.format(
|
|
979
|
+
vals_vector['gratio'], sigmas_vector['sigma_gratio']))
|
|
980
|
+
print('tau0 = {:.3g} +/- {:.4g} ms'.format(
|
|
981
|
+
vals_vector['tau0']*1e3, sigmas_vector['sigma_tau0']*1e3))
|
|
982
|
+
print('L = {:.3f} +/- {:.4f} nH'.format(
|
|
983
|
+
vals_vector['L']*1e9, sigmas_vector['sigma_L']*1e9))
|
|
984
|
+
|
|
985
|
+
|
|
986
|
+
def compare_with_ivsweep(self, channel, poles=3):
|
|
987
|
+
"""
|
|
988
|
+
Create a dataframe to compare results with
|
|
989
|
+
IV sweep
|
|
990
|
+
"""
|
|
991
|
+
|
|
992
|
+
# check channel
|
|
993
|
+
if channel not in self._didv_data.keys():
|
|
994
|
+
raise ValueError(f'ERROR: channel {channel} not found!')
|
|
995
|
+
|
|
996
|
+
|
|
997
|
+
didv_results = self.get_fit_results(channel, poles=poles)
|
|
998
|
+
ivsweep_results = self.get_ivsweep_results(channel)
|
|
999
|
+
|
|
1000
|
+
data = dict()
|
|
1001
|
+
|
|
1002
|
+
|
|
1003
|
+
if ('biasparams' in didv_results
|
|
1004
|
+
and didv_results['biasparams'] is not None):
|
|
1005
|
+
|
|
1006
|
+
# define parameter list
|
|
1007
|
+
param_list = ['r0','i0','p0']
|
|
1008
|
+
norm_list = [1e3, 1e6, 1e15]
|
|
1009
|
+
label_list = ['R0 [mOhms]','I0 [muAmps]','P0 [fWatts]']
|
|
1010
|
+
label_inf_lgain_list = ['R0 Inf loop gain [mOhms]',
|
|
1011
|
+
'I0 Inf loop gain [muAmps]',
|
|
1012
|
+
'P0 Inf loop gain [fWatts]']
|
|
1013
|
+
|
|
1014
|
+
# results
|
|
1015
|
+
results = didv_results['biasparams']
|
|
1016
|
+
results_infinite_lgain = None
|
|
1017
|
+
if 'biasparams_infinite_lgain' in didv_results:
|
|
1018
|
+
results_infinite_lgain = (
|
|
1019
|
+
didv_results['biasparams_infinite_lgain']
|
|
1020
|
+
)
|
|
1021
|
+
|
|
1022
|
+
# loop parameters
|
|
1023
|
+
for iparam, param in enumerate(param_list):
|
|
1024
|
+
|
|
1025
|
+
if param not in ivsweep_results:
|
|
1026
|
+
continue
|
|
1027
|
+
|
|
1028
|
+
# normalization/label
|
|
1029
|
+
norm = norm_list[iparam]
|
|
1030
|
+
label = label_list[iparam]
|
|
1031
|
+
label_infinite_lgain = label_inf_lgain_list[iparam]
|
|
1032
|
+
|
|
1033
|
+
|
|
1034
|
+
# build string dIdV
|
|
1035
|
+
val = results[param]
|
|
1036
|
+
val_err = results[param + '_err']
|
|
1037
|
+
val_str = '{:.3g} +/- {:.4g}'.format(
|
|
1038
|
+
val*norm, val_err*norm
|
|
1039
|
+
)
|
|
1040
|
+
|
|
1041
|
+
|
|
1042
|
+
# build string IV
|
|
1043
|
+
iv_val = ivsweep_results[param]
|
|
1044
|
+
iv_val_err = ivsweep_results[param + '_err']
|
|
1045
|
+
iv_val_str = '{:.3g} +/- {:.4g}'.format(
|
|
1046
|
+
iv_val*norm, iv_val_err*norm
|
|
1047
|
+
)
|
|
1048
|
+
|
|
1049
|
+
data[label] = [val_str, iv_val_str]
|
|
1050
|
+
|
|
1051
|
+
|
|
1052
|
+
# infinite loop gain
|
|
1053
|
+
if (results_infinite_lgain is not None
|
|
1054
|
+
and 'r0_infinite_lgain' in ivsweep_results):
|
|
1055
|
+
|
|
1056
|
+
# build string dIdV
|
|
1057
|
+
val = results_infinite_lgain[param]
|
|
1058
|
+
val_err = results_infinite_lgain[param + '_err']
|
|
1059
|
+
val_str = '{:.3g} +/- {:.4g}'.format(
|
|
1060
|
+
val*norm, val_err*norm
|
|
1061
|
+
)
|
|
1062
|
+
|
|
1063
|
+
# build string IV
|
|
1064
|
+
iv_val = ivsweep_results[param + '_infinite_lgain']
|
|
1065
|
+
iv_val_err = ivsweep_results[param + '_err_infinite_lgain']
|
|
1066
|
+
iv_val_str = '{:.3g} +/- {:.4g}'.format(
|
|
1067
|
+
iv_val*norm, iv_val_err*norm
|
|
1068
|
+
)
|
|
1069
|
+
|
|
1070
|
+
data[label_infinite_lgain] = [val_str, iv_val_str]
|
|
1071
|
+
|
|
1072
|
+
|
|
1073
|
+
# small signal parmaters
|
|
1074
|
+
if ('ssp_light' in didv_results
|
|
1075
|
+
and 'didv_fit_l' in ivsweep_results):
|
|
1076
|
+
|
|
1077
|
+
# define parameter list
|
|
1078
|
+
param_list = ['l','beta','gratio', 'L', 'tau0']
|
|
1079
|
+
norm_list = [1, 1, 1, 1e9, 1e3]
|
|
1080
|
+
label_list = ['l (loop gain)','beta', 'gratio',
|
|
1081
|
+
'L [nH]', 'tau0 [ms]']
|
|
1082
|
+
|
|
1083
|
+
|
|
1084
|
+
vals_vector = didv_results['ssp_light']['vals']
|
|
1085
|
+
sigmas_vector = didv_results['ssp_light']['sigmas']
|
|
1086
|
+
|
|
1087
|
+
|
|
1088
|
+
# loop parameters
|
|
1089
|
+
for iparam, param in enumerate(param_list):
|
|
1090
|
+
|
|
1091
|
+
param_iv = 'didv_fit_' + param
|
|
1092
|
+
|
|
1093
|
+
# normalization/label
|
|
1094
|
+
norm = norm_list[iparam]
|
|
1095
|
+
label = label_list[iparam]
|
|
1096
|
+
|
|
1097
|
+
|
|
1098
|
+
# build string dIdV
|
|
1099
|
+
val = vals_vector[param]
|
|
1100
|
+
val_err = sigmas_vector['sigma_' + param]
|
|
1101
|
+
val_str = '{:.3g} +/- {:.4g}'.format(
|
|
1102
|
+
val*norm, val_err*norm
|
|
1103
|
+
)
|
|
1104
|
+
|
|
1105
|
+
# build string IV
|
|
1106
|
+
iv_val = ivsweep_results[param_iv]
|
|
1107
|
+
iv_val_err = ivsweep_results[param_iv + '_err']
|
|
1108
|
+
iv_val_str = '{:.3g} +/- {:.4g}'.format(
|
|
1109
|
+
iv_val*norm, iv_val_err*norm
|
|
1110
|
+
)
|
|
1111
|
+
|
|
1112
|
+
# store data
|
|
1113
|
+
data[label] = [val_str, iv_val_str]
|
|
1114
|
+
|
|
1115
|
+
# fall times
|
|
1116
|
+
if ('falltimes' in didv_results
|
|
1117
|
+
and 'didv_fit_tau+' in ivsweep_results):
|
|
1118
|
+
|
|
1119
|
+
# define parameter list
|
|
1120
|
+
param_list = ['tau+','tau-','tau3']
|
|
1121
|
+
norm_list = [1e6, 1e6, 1e6]
|
|
1122
|
+
label_list = ['tau+ [usec]', 'tau- [usec]', 'tau3 [usec]']
|
|
1123
|
+
|
|
1124
|
+
|
|
1125
|
+
falltimes = didv_results['falltimes']
|
|
1126
|
+
|
|
1127
|
+
|
|
1128
|
+
# loop parameters
|
|
1129
|
+
for iparam, param in enumerate(param_list):
|
|
1130
|
+
|
|
1131
|
+
if (poles == 2 and iparam>1):
|
|
1132
|
+
break
|
|
1133
|
+
|
|
1134
|
+
|
|
1135
|
+
param_iv = 'didv_fit_' + param
|
|
1136
|
+
|
|
1137
|
+
# normalization/label
|
|
1138
|
+
norm = norm_list[iparam]
|
|
1139
|
+
label = label_list[iparam]
|
|
1140
|
+
|
|
1141
|
+
|
|
1142
|
+
# build string dIdV
|
|
1143
|
+
val = falltimes[iparam]
|
|
1144
|
+
val_str = '{:.3g}'.format(val*norm)
|
|
1145
|
+
|
|
1146
|
+
|
|
1147
|
+
# build string IV
|
|
1148
|
+
iv_val = ivsweep_results[param_iv]
|
|
1149
|
+
iv_val_str = '{:.3g}'.format(iv_val*norm)
|
|
1150
|
+
|
|
1151
|
+
# save
|
|
1152
|
+
data[label] = [val_str, iv_val_str]
|
|
1153
|
+
|
|
1154
|
+
|
|
1155
|
+
|
|
1156
|
+
df = None
|
|
1157
|
+
if data:
|
|
1158
|
+
df = pd.DataFrame.from_dict(data, orient='index', columns=['dIdV', 'IV Sweep'])
|
|
1159
|
+
|
|
1160
|
+
return df
|
|
1161
|
+
|
|
1162
|
+
|
|
1163
|
+
def save_didv_data(self, channels=None, save_path=None, save_hdf5=False, tag='default'):
|
|
1164
|
+
"""
|
|
1165
|
+
Save didv data
|
|
1166
|
+
"""
|
|
1167
|
+
|
|
1168
|
+
# channels
|
|
1169
|
+
if channels is None:
|
|
1170
|
+
channels = self._didv_data.keys()
|
|
1171
|
+
elif isinstance(channels, str):
|
|
1172
|
+
channels = [channels]
|
|
1173
|
+
|
|
1174
|
+
for chan in channels:
|
|
1175
|
+
if chan not in self._didv_data.keys():
|
|
1176
|
+
raise ValueError(
|
|
1177
|
+
f'ERROR: No data found for channel {chan}!'
|
|
1178
|
+
)
|
|
1179
|
+
|
|
1180
|
+
|
|
1181
|
+
# loop channels and set data
|
|
1182
|
+
save_data = False
|
|
1183
|
+
for chan in channels:
|
|
1184
|
+
|
|
1185
|
+
# metadata
|
|
1186
|
+
metadata = dict()
|
|
1187
|
+
metadata['channel'] = chan
|
|
1188
|
+
|
|
1189
|
+
data_config = self.get_didv_data(chan)['data_config']
|
|
1190
|
+
if data_config is not None:
|
|
1191
|
+
metadata.update(data_config.copy())
|
|
1192
|
+
|
|
1193
|
+
# 2-poles
|
|
1194
|
+
results = self.get_fit_results(chan, 2, verbose=False)
|
|
1195
|
+
if results:
|
|
1196
|
+
self.set_didv_results(chan, results, 2,
|
|
1197
|
+
metadata=metadata, tag=tag)
|
|
1198
|
+
save_data = True
|
|
1199
|
+
|
|
1200
|
+
|
|
1201
|
+
# 3-poles
|
|
1202
|
+
results = self.get_fit_results(chan, 3, verbose=False)
|
|
1203
|
+
if results:
|
|
1204
|
+
self.set_didv_results(chan, results, 3,
|
|
1205
|
+
metadata=metadata, tag=tag)
|
|
1206
|
+
save_data = True
|
|
1207
|
+
|
|
1208
|
+
# dpdi
|
|
1209
|
+
fs = None
|
|
1210
|
+
if ('data_config' in self._didv_data[chan]
|
|
1211
|
+
and 'fs' in self._didv_data[chan]['data_config']):
|
|
1212
|
+
fs = self._didv_data[chan]['data_config']['fs']
|
|
1213
|
+
|
|
1214
|
+
if 'dpdi_2poles' in self._didv_data[chan]:
|
|
1215
|
+
dpdi_freqs, dpdi, dpdi_err = self.get_dpdi(chan, 2)
|
|
1216
|
+
self.set_dpdi(chan, dpdi, dpdi_err, dpdi_freqs, 2,
|
|
1217
|
+
sample_rate=fs,
|
|
1218
|
+
metadata=metadata, tag=tag)
|
|
1219
|
+
save_data = True
|
|
1220
|
+
|
|
1221
|
+
if 'dpdi_3poles' in self._didv_data[chan]:
|
|
1222
|
+
dpdi_freqs, dpdi, dpdi_err = self.get_dpdi(chan, 3)
|
|
1223
|
+
self.set_dpdi(chan, dpdi, dpdi_err, dpdi_freqs, 3,
|
|
1224
|
+
sample_rate=fs,
|
|
1225
|
+
metadata=metadata, tag=tag)
|
|
1226
|
+
save_data = True
|
|
1227
|
+
|
|
1228
|
+
if not save_data:
|
|
1229
|
+
return
|
|
1230
|
+
|
|
1231
|
+
if save_hdf5:
|
|
1232
|
+
|
|
1233
|
+
# file_name
|
|
1234
|
+
file_path = self._save_path
|
|
1235
|
+
if file_path is None:
|
|
1236
|
+
file_path = './'
|
|
1237
|
+
file_name = self._file_name
|
|
1238
|
+
|
|
1239
|
+
if save_path is not None:
|
|
1240
|
+
if os.path.isfile(save_path):
|
|
1241
|
+
file_path = os.path.dirname(save_path)
|
|
1242
|
+
file_name = os.path.basename(save_path)
|
|
1243
|
+
elif os.path.isdir(save_path):
|
|
1244
|
+
file_path = save_path
|
|
1245
|
+
else:
|
|
1246
|
+
raise ValueError('ERROR: "save_path" should be a '
|
|
1247
|
+
'file or path!')
|
|
1248
|
+
full_file_name = file_path + '/' + file_name
|
|
1249
|
+
if file_path[-1] == '/':
|
|
1250
|
+
full_file_name = file_path + file_name
|
|
1251
|
+
|
|
1252
|
+
self.save_hdf5(full_file_name, overwrite=True)
|
|
1253
|
+
|
|
1254
|
+
|
|
1255
|
+
|
|
1256
|
+
def _process_raw_data(self, channel,
|
|
1257
|
+
data_path, stream=None,
|
|
1258
|
+
overwrite=False):
|
|
1259
|
+
|
|
1260
|
+
"""
|
|
1261
|
+
Set dIdV data for a specific channel
|
|
1262
|
+
form raw traces. Apply cuts and pre-process
|
|
1263
|
+
data. The function can be called multiple
|
|
1264
|
+
times for different channels
|
|
1265
|
+
"""
|
|
1266
|
+
|
|
1267
|
+
if not overwrite and channel in self._didv_data:
|
|
1268
|
+
raise ValueError(
|
|
1269
|
+
f'ERROR: Data for channel {channel} already available. '
|
|
1270
|
+
'Set overwrite=True'
|
|
1271
|
+
)
|
|
1272
|
+
|
|
1273
|
+
# Instantiate QETpy DIDV Object
|
|
1274
|
+
didvobj = None
|
|
1275
|
+
if self._verbose:
|
|
1276
|
+
print(f'INFO: Getting raw data and processing channel {channel}')
|
|
1277
|
+
|
|
1278
|
+
|
|
1279
|
+
with StreamReader(
|
|
1280
|
+
data_path,
|
|
1281
|
+
streams=stream,
|
|
1282
|
+
measurement_types='didv',
|
|
1283
|
+
) as reader:
|
|
1284
|
+
selected_stream_ids = reader.selected_stream_ids
|
|
1285
|
+
if len(selected_stream_ids) > 1:
|
|
1286
|
+
raise ValueError(
|
|
1287
|
+
'ERROR: DIDVAnalysis.process_raw_data requires one dIdV '
|
|
1288
|
+
'stream at a time. Selected streams: '
|
|
1289
|
+
+ ', '.join(selected_stream_ids)
|
|
1290
|
+
)
|
|
1291
|
+
|
|
1292
|
+
traces, info = reader.read_records(
|
|
1293
|
+
channels=channel,
|
|
1294
|
+
include_metadata=True,
|
|
1295
|
+
units='amps',
|
|
1296
|
+
stack=True,
|
|
1297
|
+
)
|
|
1298
|
+
detector_settings = reader.get_detector_settings()
|
|
1299
|
+
|
|
1300
|
+
acquisition_name = reader.acquisition_name
|
|
1301
|
+
stream_id = (
|
|
1302
|
+
selected_stream_ids[0]
|
|
1303
|
+
if selected_stream_ids
|
|
1304
|
+
else None
|
|
1305
|
+
)
|
|
1306
|
+
|
|
1307
|
+
traces = np.asarray(traces)
|
|
1308
|
+
if traces.ndim != 3 or traces.shape[1] != 1:
|
|
1309
|
+
raise ValueError(
|
|
1310
|
+
'ERROR: Expected dIdV traces with shape '
|
|
1311
|
+
f'(n_traces, 1, n_samples), got {traces.shape}'
|
|
1312
|
+
)
|
|
1313
|
+
if traces.shape[0] == 0 or not info:
|
|
1314
|
+
raise ValueError(
|
|
1315
|
+
f'ERROR: No dIdV records found for channel {channel}'
|
|
1316
|
+
)
|
|
1317
|
+
|
|
1318
|
+
traces = traces[:, 0, :]
|
|
1319
|
+
fs = float(info[0]['sample_rate_hz'])
|
|
1320
|
+
|
|
1321
|
+
if channel not in detector_settings:
|
|
1322
|
+
raise ValueError(
|
|
1323
|
+
f'ERROR: Detector settings for channel {channel} not found'
|
|
1324
|
+
)
|
|
1325
|
+
settings = detector_settings[channel]
|
|
1326
|
+
|
|
1327
|
+
tes_bias = self._required_float(
|
|
1328
|
+
settings, 'tes_bias_dc_amps', channel
|
|
1329
|
+
)
|
|
1330
|
+
output_gain = self._optional_float(settings.get('output_gain'))
|
|
1331
|
+
close_loop_norm = self._optional_float(settings.get('close_loop_norm'))
|
|
1332
|
+
output_offset = self._optional_float(settings.get('output_offset_vdc'))
|
|
1333
|
+
sgamp = self._required_float(
|
|
1334
|
+
settings, 'tes_bias_ac_amplitude_amps', channel
|
|
1335
|
+
)
|
|
1336
|
+
sgfreq = self._required_float(
|
|
1337
|
+
settings, 'tes_bias_ac_frequency_hz', channel
|
|
1338
|
+
)
|
|
1339
|
+
|
|
1340
|
+
if sgfreq <= 0:
|
|
1341
|
+
raise ValueError(
|
|
1342
|
+
f'ERROR: Invalid dIdV frequency {sgfreq} Hz for {channel}'
|
|
1343
|
+
)
|
|
1344
|
+
|
|
1345
|
+
rshunt = self._first_optional_float(
|
|
1346
|
+
settings,
|
|
1347
|
+
'shunt_resistance_ohm',
|
|
1348
|
+
'rshunt',
|
|
1349
|
+
)
|
|
1350
|
+
if rshunt is None:
|
|
1351
|
+
raise ValueError(
|
|
1352
|
+
f'ERROR: Shunt resistance is required for channel {channel}'
|
|
1353
|
+
)
|
|
1354
|
+
|
|
1355
|
+
rp = self._first_optional_float(
|
|
1356
|
+
settings,
|
|
1357
|
+
'parasitic_resistance_ohm',
|
|
1358
|
+
'rp',
|
|
1359
|
+
)
|
|
1360
|
+
|
|
1361
|
+
dutycycle = self._first_optional_float(
|
|
1362
|
+
settings,
|
|
1363
|
+
'dutycycle',
|
|
1364
|
+
'duty_cycle',
|
|
1365
|
+
'tes_bias_ac_dutycycle',
|
|
1366
|
+
)
|
|
1367
|
+
if dutycycle is None:
|
|
1368
|
+
dutycycle = 0.5
|
|
1369
|
+
|
|
1370
|
+
if acquisition_name is None:
|
|
1371
|
+
acquisition_name = info[0].get('acquisition_name')
|
|
1372
|
+
if stream_id is None:
|
|
1373
|
+
stream_id = info[0].get('stream_id')
|
|
1374
|
+
stream_name = info[0].get('stream_name', stream_id)
|
|
1375
|
+
|
|
1376
|
+
|
|
1377
|
+
# save relevant detector settings in dictionary
|
|
1378
|
+
data_config = dict()
|
|
1379
|
+
data_config['stream_id'] = stream_id
|
|
1380
|
+
data_config['stream_name'] = stream_name
|
|
1381
|
+
data_config['acquisition_name'] = acquisition_name
|
|
1382
|
+
data_config['fs'] = fs
|
|
1383
|
+
data_config['output_variable_gain'] = output_gain
|
|
1384
|
+
data_config['output_variable_offset'] = output_offset
|
|
1385
|
+
data_config['close_loop_norm'] = close_loop_norm
|
|
1386
|
+
data_config['rshunt'] = rshunt
|
|
1387
|
+
data_config['rp'] = rp
|
|
1388
|
+
data_config['tes_bias'] = tes_bias
|
|
1389
|
+
data_config['sgfreq'] = sgfreq
|
|
1390
|
+
data_config['sgamp'] = sgamp
|
|
1391
|
+
data_config['dutycycle'] = dutycycle
|
|
1392
|
+
|
|
1393
|
+
|
|
1394
|
+
|
|
1395
|
+
# add temperature
|
|
1396
|
+
temperature_list = ['cp','mc','still']
|
|
1397
|
+
for temp in temperature_list:
|
|
1398
|
+
temp_par = 'temperature_' + temp
|
|
1399
|
+
if temp_par in settings:
|
|
1400
|
+
data_config[temp_par] = (
|
|
1401
|
+
float(settings[temp_par])
|
|
1402
|
+
)
|
|
1403
|
+
|
|
1404
|
+
|
|
1405
|
+
# Apply cuts
|
|
1406
|
+
nonzero = ~np.all(traces == 0, axis=1)
|
|
1407
|
+
traces = traces[nonzero]
|
|
1408
|
+
if traces.shape[0] == 0:
|
|
1409
|
+
raise ValueError(
|
|
1410
|
+
f'ERROR: All dIdV traces are zero for channel {channel}'
|
|
1411
|
+
)
|
|
1412
|
+
|
|
1413
|
+
# cut pileup
|
|
1414
|
+
cut = qp.autocuts_didv(traces, fs=fs)
|
|
1415
|
+
traces = traces[cut]
|
|
1416
|
+
if traces.shape[0] == 0:
|
|
1417
|
+
raise ValueError(
|
|
1418
|
+
f'ERROR: No dIdV traces survive autocuts for channel {channel}'
|
|
1419
|
+
)
|
|
1420
|
+
|
|
1421
|
+
# Fit data
|
|
1422
|
+
didvobj = qp.DIDV(traces,
|
|
1423
|
+
fs,
|
|
1424
|
+
sgfreq,
|
|
1425
|
+
sgamp,
|
|
1426
|
+
rshunt,
|
|
1427
|
+
rp=rp,
|
|
1428
|
+
dutycycle=dutycycle,
|
|
1429
|
+
add180phase=False)
|
|
1430
|
+
|
|
1431
|
+
# process
|
|
1432
|
+
didvobj.processtraces()
|
|
1433
|
+
|
|
1434
|
+
# store
|
|
1435
|
+
self._didv_data[channel] = {'didvobj': didvobj,
|
|
1436
|
+
'acquisition_name': acquisition_name,
|
|
1437
|
+
'stream_id': stream_id,
|
|
1438
|
+
'base_path': data_path,
|
|
1439
|
+
'ivsweep_results': None,
|
|
1440
|
+
'data_config': data_config,
|
|
1441
|
+
'resolution': None}
|
|
1442
|
+
|
|
1443
|
+
|
|
1444
|
+
|
|
1445
|
+
def _set_processed_data(self, channel,
|
|
1446
|
+
didv_data,
|
|
1447
|
+
overwrite=False,
|
|
1448
|
+
**kwargs ):
|
|
1449
|
+
|
|
1450
|
+
"""
|
|
1451
|
+
Set dIdV data from already processed raw
|
|
1452
|
+
data, in the form of pandas stream or
|
|
1453
|
+
dictionary. The function can be called multiple
|
|
1454
|
+
times for different channels
|
|
1455
|
+
"""
|
|
1456
|
+
|
|
1457
|
+
if not overwrite and channel in self._didv_data:
|
|
1458
|
+
raise ValueError(
|
|
1459
|
+
f'ERROR: Data for channel {channel} already available. '
|
|
1460
|
+
'Set overwrite=True'
|
|
1461
|
+
)
|
|
1462
|
+
|
|
1463
|
+
# check didv data
|
|
1464
|
+
if isinstance(didv_data, pd.Series):
|
|
1465
|
+
didv_data = didv_data.to_dict()
|
|
1466
|
+
elif not isinstance(didv_data, dict):
|
|
1467
|
+
raise ValueError(
|
|
1468
|
+
'ERROR: "didv_data" should be a dictionary '
|
|
1469
|
+
' or pandas Series')
|
|
1470
|
+
|
|
1471
|
+
# check variables
|
|
1472
|
+
available_vars = list(didv_data.keys())
|
|
1473
|
+
required_vars = ['avgtrace_didv', 'didvmean','didvstd',
|
|
1474
|
+
'offset_didv','offset_err_didv',
|
|
1475
|
+
'sgamp', 'sgfreq',
|
|
1476
|
+
'fs_didv']
|
|
1477
|
+
|
|
1478
|
+
for var in required_vars:
|
|
1479
|
+
if var not in available_vars:
|
|
1480
|
+
raise ValueError(f'ERROR: Missing variable {var} '
|
|
1481
|
+
' in didv_data')
|
|
1482
|
+
|
|
1483
|
+
# check if channel stored in didv_data
|
|
1484
|
+
if 'channel' in didv_data.keys():
|
|
1485
|
+
channel_didv = str(didv_data['channel'])
|
|
1486
|
+
if channel != channel_didv:
|
|
1487
|
+
raise ValueError(
|
|
1488
|
+
f'ERROR: Channel name in didv data "{channel_didv}" '
|
|
1489
|
+
'is different than input channel!')
|
|
1490
|
+
|
|
1491
|
+
# check if other parameters are available
|
|
1492
|
+
rshunt = None
|
|
1493
|
+
if 'rshunt_noise' in didv_data.keys():
|
|
1494
|
+
rshunt = didv_data['rshunt_noise']
|
|
1495
|
+
elif 'rshunt_didv' in didv_data.keys():
|
|
1496
|
+
rshunt = didv_data['rshunt_didv']
|
|
1497
|
+
rshunt = self._first_finite_value(rshunt)
|
|
1498
|
+
if rshunt is None:
|
|
1499
|
+
raise ValueError(
|
|
1500
|
+
f'ERROR: Missing/invalid rshunt in processed dIdV data for '
|
|
1501
|
+
f'channel {channel}'
|
|
1502
|
+
)
|
|
1503
|
+
|
|
1504
|
+
# parasitic resistance
|
|
1505
|
+
rp = None
|
|
1506
|
+
if 'rp_noise' in didv_data.keys():
|
|
1507
|
+
rp = didv_data['rp_noise']
|
|
1508
|
+
elif 'rp_didv' in didv_data.keys():
|
|
1509
|
+
rp = didv_data['rp_didv']
|
|
1510
|
+
rp = self._first_finite_value(rp)
|
|
1511
|
+
|
|
1512
|
+
|
|
1513
|
+
# duty cycle
|
|
1514
|
+
dutycycle = 0.5
|
|
1515
|
+
if 'dutycycle' in didv_data.keys():
|
|
1516
|
+
dutycycle = didv_data['dutycycle']
|
|
1517
|
+
|
|
1518
|
+
# data config
|
|
1519
|
+
data_config_pars = ['stream_id', 'stream_name', 'acquisition_name',
|
|
1520
|
+
'fs', 'output_variable_gain',
|
|
1521
|
+
'output_variable_offset',
|
|
1522
|
+
'close_loop_norm', 'tes_bias',
|
|
1523
|
+
'sgfreq','sgamp']
|
|
1524
|
+
|
|
1525
|
+
data_config = dict()
|
|
1526
|
+
for config in data_config_pars:
|
|
1527
|
+
config_name = config + '_didv'
|
|
1528
|
+
if config_name in didv_data.keys():
|
|
1529
|
+
data_config[config] = didv_data[config_name]
|
|
1530
|
+
elif config in ('sgfreq', 'sgamp') and config in didv_data:
|
|
1531
|
+
data_config[config] = didv_data[config]
|
|
1532
|
+
else:
|
|
1533
|
+
data_config[config]= None
|
|
1534
|
+
|
|
1535
|
+
data_config['rshunt'] = rshunt
|
|
1536
|
+
data_config['rp'] = rp
|
|
1537
|
+
data_config['dutycycle'] = dutycycle
|
|
1538
|
+
|
|
1539
|
+
|
|
1540
|
+
# Instantiate QETpy DIDV Object
|
|
1541
|
+
didvobj = qp.didvinitfromdata(
|
|
1542
|
+
didv_data['avgtrace_didv'][:len(didv_data['didvmean'])],
|
|
1543
|
+
didv_data['didvmean'],
|
|
1544
|
+
didv_data['didvstd'],
|
|
1545
|
+
didv_data['offset_didv'],
|
|
1546
|
+
didv_data['offset_err_didv'],
|
|
1547
|
+
didv_data['fs_didv'],
|
|
1548
|
+
didv_data['sgfreq'],
|
|
1549
|
+
didv_data['sgamp'],
|
|
1550
|
+
rsh=rshunt,
|
|
1551
|
+
rp=rp,
|
|
1552
|
+
dutycycle=dutycycle,
|
|
1553
|
+
)
|
|
1554
|
+
|
|
1555
|
+
|
|
1556
|
+
# store
|
|
1557
|
+
self._didv_data[channel] = {'didvobj': didvobj,
|
|
1558
|
+
'acquisition_name': data_config['acquisition_name'],
|
|
1559
|
+
'stream_id': data_config['stream_id'],
|
|
1560
|
+
'base_path': None,
|
|
1561
|
+
'ivsweep_results': None,
|
|
1562
|
+
'data_config': data_config,
|
|
1563
|
+
'resolution': None}
|
|
1564
|
+
|
|
1565
|
+
@staticmethod
|
|
1566
|
+
def _optional_float(value):
|
|
1567
|
+
if value is None:
|
|
1568
|
+
return np.nan
|
|
1569
|
+
try:
|
|
1570
|
+
output = float(value)
|
|
1571
|
+
except (TypeError, ValueError):
|
|
1572
|
+
return np.nan
|
|
1573
|
+
return output
|
|
1574
|
+
|
|
1575
|
+
@classmethod
|
|
1576
|
+
def _first_finite_value(cls, value):
|
|
1577
|
+
output = cls._optional_float(value)
|
|
1578
|
+
if np.isfinite(output):
|
|
1579
|
+
return output
|
|
1580
|
+
return None
|
|
1581
|
+
|
|
1582
|
+
@classmethod
|
|
1583
|
+
def _required_float(cls, settings, key, channel):
|
|
1584
|
+
value = cls._optional_float(settings.get(key))
|
|
1585
|
+
if not np.isfinite(value):
|
|
1586
|
+
raise ValueError(
|
|
1587
|
+
f'ERROR: Invalid or missing "{key}" for channel {channel}'
|
|
1588
|
+
)
|
|
1589
|
+
return value
|
|
1590
|
+
|
|
1591
|
+
@classmethod
|
|
1592
|
+
def _first_optional_float(cls, settings, *keys):
|
|
1593
|
+
for key in keys:
|
|
1594
|
+
if key not in settings:
|
|
1595
|
+
continue
|
|
1596
|
+
value = cls._optional_float(settings.get(key))
|
|
1597
|
+
if np.isfinite(value):
|
|
1598
|
+
return value
|
|
1599
|
+
return None
|
|
1600
|
+
|
|
1601
|
+
def _default_save_path(self, data_path):
|
|
1602
|
+
"""Choose the default filterdata directory for raw input."""
|
|
1603
|
+
|
|
1604
|
+
try:
|
|
1605
|
+
catalog = AcquisitionCatalog(data_path, verbose=False)
|
|
1606
|
+
source_path = Path(catalog.acquisition_path)
|
|
1607
|
+
except Exception:
|
|
1608
|
+
if isinstance(data_path, (list, tuple)):
|
|
1609
|
+
if not data_path:
|
|
1610
|
+
source_path = Path('.')
|
|
1611
|
+
else:
|
|
1612
|
+
source_path = Path(data_path[0])
|
|
1613
|
+
else:
|
|
1614
|
+
source_path = Path(data_path)
|
|
1615
|
+
if source_path.is_file():
|
|
1616
|
+
source_path = source_path.parent
|
|
1617
|
+
|
|
1618
|
+
path_text = str(source_path)
|
|
1619
|
+
if '/processed/' in path_text:
|
|
1620
|
+
path_text = path_text.replace('/processed/', '/filterdata/')
|
|
1621
|
+
elif '/raw/' in path_text:
|
|
1622
|
+
path_text = path_text.replace('/raw/', '/filterdata/')
|
|
1623
|
+
|
|
1624
|
+
output_path = Path(path_text)
|
|
1625
|
+
try:
|
|
1626
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
1627
|
+
os.chmod(
|
|
1628
|
+
output_path,
|
|
1629
|
+
stat.S_IRWXG | stat.S_IRWXU | stat.S_IROTH | stat.S_IXOTH,
|
|
1630
|
+
)
|
|
1631
|
+
except OSError as exc:
|
|
1632
|
+
raise ValueError(
|
|
1633
|
+
f'ERROR: Unable to create directory "{output_path}"!'
|
|
1634
|
+
) from exc
|
|
1635
|
+
return str(output_path)
|
|
1636
|
+
|
|
1637
|
+
|
|
1638
|
+
def _set_file_name(self):
|
|
1639
|
+
"""
|
|
1640
|
+
Set file name
|
|
1641
|
+
"""
|
|
1642
|
+
|
|
1643
|
+
|
|
1644
|
+
now = datetime.now()
|
|
1645
|
+
timestamp_id = now.strftime("D%Y%m%d_T%H%M%S")
|
|
1646
|
+
file_name = f"didv_analysis_{timestamp_id}.hdf5"
|
|
1647
|
+
|
|
1648
|
+
return file_name
|