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,579 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This is a module for a more accurate estimation of TES parameters than what
|
|
3
|
+
currently exists in the IV and Bias Step Modules.
|
|
4
|
+
|
|
5
|
+
It contains the follwing procedures:
|
|
6
|
+
- recompute_ivpars: corrects errors in IV analysis
|
|
7
|
+
- run_correction: Takes IV and bias step data to compute corrected TES params
|
|
8
|
+
based on the RP fitting method described here:
|
|
9
|
+
https://simonsobs.atlassian.net/wiki/spaces/~5570586d07625a6be74c8780e4b96f6156f5e6/blog/2024/02/02/286228683/Nonlinear+TES+model+using+RP+curve
|
|
10
|
+
|
|
11
|
+
Authors: Remy Gerras, Satoru Takakura, Jack Lashner
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
from scipy.interpolate import interp1d
|
|
16
|
+
from scipy.optimize import minimize
|
|
17
|
+
from tqdm.auto import trange
|
|
18
|
+
import multiprocessing
|
|
19
|
+
from concurrent.futures import ProcessPoolExecutor, as_completed, Future
|
|
20
|
+
from typing import Optional, List, Tuple, TypeVar
|
|
21
|
+
from dataclasses import dataclass
|
|
22
|
+
import traceback
|
|
23
|
+
from copy import deepcopy
|
|
24
|
+
from numpy.typing import NDArray
|
|
25
|
+
|
|
26
|
+
from sodetlib.operations.iv import IVAnalysis
|
|
27
|
+
from sodetlib.operations.bias_steps import BiasStepAnalysis
|
|
28
|
+
|
|
29
|
+
D = TypeVar("D", float, np.ndarray)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def model_logalpha(r: D, logp0=0.0, p1=7.0, p2=1.0, logp3=0.0) -> D:
|
|
33
|
+
"""
|
|
34
|
+
Fits Alpha' parameter, or alpha / GT. P0 has units of [uW]^-1.
|
|
35
|
+
"""
|
|
36
|
+
return logp0 + p1 * r - p2 * r ** np.exp(logp3) * np.log(1 - r)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class AnalysisCfg:
|
|
41
|
+
"""
|
|
42
|
+
Class for configuring behavior of the parameter estimation functions
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
default_nprocs: int = 10
|
|
46
|
+
"Default number of processes to use when running correction in parallel."
|
|
47
|
+
|
|
48
|
+
raise_exceptions: bool = False
|
|
49
|
+
"If true, will raise exceptions thrown in the run_correction function."
|
|
50
|
+
|
|
51
|
+
psat_level: float = 0.9
|
|
52
|
+
"rfrac for which psat is defined."
|
|
53
|
+
|
|
54
|
+
rpfit_min_rfrac: float = 0.05
|
|
55
|
+
"Min rfrac used for RP fits and estimation."
|
|
56
|
+
|
|
57
|
+
rpfit_max_rfrac: float = 0.95
|
|
58
|
+
"Max rfrac used for RP fits and estiamtion."
|
|
59
|
+
|
|
60
|
+
rpfit_intg_nsamps: int = 500
|
|
61
|
+
"Number of samples to use in the RP fit and integral."
|
|
62
|
+
|
|
63
|
+
sc_rfrac_thresh: float = 0.02
|
|
64
|
+
"Bias step rfrac threshold below which TES should be considered "
|
|
65
|
+
"superconducting, and the correction skipped."
|
|
66
|
+
|
|
67
|
+
show_pb: bool = True
|
|
68
|
+
"If true, will show a pb when running on all channels together"
|
|
69
|
+
|
|
70
|
+
def __post_init__(self):
|
|
71
|
+
if self.rpfit_min_rfrac < 0.0001:
|
|
72
|
+
raise ValueError("rp_fit_min_rfrac must be larger than 0.0001")
|
|
73
|
+
if self.rpfit_max_rfrac > 0.9999:
|
|
74
|
+
raise ValueError("rp_fit_max_rfrac must be smaller than 0.9999")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@dataclass
|
|
78
|
+
class RpFitChanData:
|
|
79
|
+
"""
|
|
80
|
+
Channel data needed to perform RP correction, pulled from bias step and IV
|
|
81
|
+
calibration functions.
|
|
82
|
+
|
|
83
|
+
Args
|
|
84
|
+
-------
|
|
85
|
+
bg: int
|
|
86
|
+
Bias Line
|
|
87
|
+
Rsh: float
|
|
88
|
+
Shunt Resistance [Ohms]
|
|
89
|
+
band: int
|
|
90
|
+
Readout band
|
|
91
|
+
channel :int
|
|
92
|
+
Readout channel
|
|
93
|
+
iv_idx_sc: int
|
|
94
|
+
Index in IV data for SC transition
|
|
95
|
+
iv_resp: np.ndarray
|
|
96
|
+
TES current response relative from IV.
|
|
97
|
+
iv_R: np.ndarray
|
|
98
|
+
TES resistance [Ohms] through IV.
|
|
99
|
+
iv_p_tes: np.ndarray
|
|
100
|
+
TES Bias power [pW] through IV.
|
|
101
|
+
iv_Rn: float
|
|
102
|
+
TES normal resistance, measured from IV.
|
|
103
|
+
iv_ibias: np.ndarray
|
|
104
|
+
TES bias current [uA] throughout IV.
|
|
105
|
+
iv_si: np.ndarray
|
|
106
|
+
TES responsivity [1/uV] throughout IV.
|
|
107
|
+
bs_meas_dIrat: float
|
|
108
|
+
TES measured dIdrat from bias steps.
|
|
109
|
+
bs_Rtes: float
|
|
110
|
+
TES resistance [Ohm] measured from bias steps.
|
|
111
|
+
bs_Ibias: float
|
|
112
|
+
TES bias current [uA] measured from bias steps.
|
|
113
|
+
bs_Si: float
|
|
114
|
+
TES responsivity [1/uV] measured from bias steps.
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
bg: int
|
|
118
|
+
Rsh: float
|
|
119
|
+
|
|
120
|
+
band: int
|
|
121
|
+
channel: int
|
|
122
|
+
|
|
123
|
+
iv_idx_sc: int
|
|
124
|
+
iv_resp: np.ndarray
|
|
125
|
+
iv_R: np.ndarray # Ohm
|
|
126
|
+
iv_p_tes: np.ndarray # pW
|
|
127
|
+
iv_Rn: float
|
|
128
|
+
iv_ibias: np.ndarray # uA
|
|
129
|
+
iv_si: np.ndarray # 1/uV
|
|
130
|
+
|
|
131
|
+
bs_meas_dIrat: float
|
|
132
|
+
bs_Rtes: float = np.nan # Ohm
|
|
133
|
+
bs_Ibias: float = np.nan # uA
|
|
134
|
+
bs_Si: float = np.nan # 1/uV
|
|
135
|
+
|
|
136
|
+
@classmethod
|
|
137
|
+
def from_data(
|
|
138
|
+
cls, iva: IVAnalysis, bsa: BiasStepAnalysis, band: int, channel: int
|
|
139
|
+
) -> "RpFitChanData":
|
|
140
|
+
"""Create class from IV and BSA dicts"""
|
|
141
|
+
iv_idx = np.where((iva.channels == channel) & (iva.bands == band))[0]
|
|
142
|
+
if len(iv_idx) == 0:
|
|
143
|
+
raise ValueError(
|
|
144
|
+
f"Could not find band={band} channel={channel} in IVAnalysis"
|
|
145
|
+
)
|
|
146
|
+
iv_idx = iv_idx[0]
|
|
147
|
+
|
|
148
|
+
bs_idx = np.where((bsa.channels == channel) & (bsa.bands == band))[0]
|
|
149
|
+
if len(bs_idx) == 0:
|
|
150
|
+
raise ValueError(
|
|
151
|
+
f"Could not find band={band} channel={channel} in Bias Steps"
|
|
152
|
+
)
|
|
153
|
+
bs_idx = bs_idx[0]
|
|
154
|
+
|
|
155
|
+
bg = iva.bgmap[iv_idx]
|
|
156
|
+
|
|
157
|
+
return cls(
|
|
158
|
+
bg=bg,
|
|
159
|
+
band=band,
|
|
160
|
+
channel=channel,
|
|
161
|
+
Rsh=iva.meta["R_sh"],
|
|
162
|
+
iv_idx_sc=iva.idxs[iv_idx, 0],
|
|
163
|
+
iv_resp=iva.resp[iv_idx],
|
|
164
|
+
iv_R=iva.R[iv_idx],
|
|
165
|
+
iv_Rn=iva.R_n[iv_idx],
|
|
166
|
+
iv_p_tes=iva.p_tes[iv_idx] * 1e12,
|
|
167
|
+
iv_si=iva.si[iv_idx] * 1e-6,
|
|
168
|
+
iv_ibias=iva.i_bias * 1e6,
|
|
169
|
+
bs_meas_dIrat=bsa.dItes[bs_idx] / bsa.dIbias[bg],
|
|
170
|
+
bs_Rtes=bsa.R0[bs_idx],
|
|
171
|
+
bs_Ibias=bsa.Ibias[bg] * 1e6,
|
|
172
|
+
bs_Si=bsa.Si[bs_idx] * 1e-6,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@dataclass
|
|
177
|
+
class CorrectedParams:
|
|
178
|
+
"Log(alpha') fit popts"
|
|
179
|
+
|
|
180
|
+
logalpha_popts: np.ndarray
|
|
181
|
+
"Log alpha fit parameters"
|
|
182
|
+
pbias_model_offset: float
|
|
183
|
+
"delta Popt between IV and bias steps"
|
|
184
|
+
delta_Popt: float
|
|
185
|
+
"TES Current [uA]"
|
|
186
|
+
corrected_I0: float
|
|
187
|
+
"TES resistance [Ohm]"
|
|
188
|
+
corrected_R0: float
|
|
189
|
+
"Bias power [aW]"
|
|
190
|
+
corrected_Pj: float
|
|
191
|
+
"Responsivity [1/uV]"
|
|
192
|
+
corrected_Si: float
|
|
193
|
+
"Loopgain at bias current"
|
|
194
|
+
loopgain: float
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@dataclass
|
|
198
|
+
class CorrectionResults:
|
|
199
|
+
"""
|
|
200
|
+
Results from the parameter correction procedure
|
|
201
|
+
"""
|
|
202
|
+
|
|
203
|
+
chdata: RpFitChanData
|
|
204
|
+
"Channel data used for correction"
|
|
205
|
+
|
|
206
|
+
success: bool = False
|
|
207
|
+
"If correction finished successfully"
|
|
208
|
+
|
|
209
|
+
traceback: Optional[str] = None
|
|
210
|
+
"Traceback on failure"
|
|
211
|
+
|
|
212
|
+
corrected_params: Optional[CorrectedParams] = None
|
|
213
|
+
"Model popts and corrected TES parameters"
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def find_logalpha_popts(
|
|
217
|
+
chdata: RpFitChanData, cfg: AnalysisCfg
|
|
218
|
+
) -> Tuple[np.ndarray, float]:
|
|
219
|
+
"""
|
|
220
|
+
Fit Log(alpha') to IV data.
|
|
221
|
+
|
|
222
|
+
Returns
|
|
223
|
+
---------
|
|
224
|
+
popts: np.ndarray
|
|
225
|
+
Optimal parameters for log(alpha') fit.
|
|
226
|
+
pbias_model_offset: float
|
|
227
|
+
Offset between pbias_model from alpha' integration and data [aW]
|
|
228
|
+
"""
|
|
229
|
+
smooth_dist = 5
|
|
230
|
+
w_len = 2 * smooth_dist + 1
|
|
231
|
+
w = (1.0 / float(w_len)) * np.ones(w_len) # window
|
|
232
|
+
|
|
233
|
+
rfrac = chdata.iv_R / chdata.iv_Rn
|
|
234
|
+
mask = np.ones_like(rfrac, dtype=bool)
|
|
235
|
+
mask[: chdata.iv_idx_sc + w_len + 2] = False # Try to cut out SC branch + smoothing
|
|
236
|
+
mask &= (rfrac > cfg.rpfit_min_rfrac) * (rfrac < cfg.rpfit_max_rfrac)
|
|
237
|
+
rfrac = np.convolve(rfrac, w, mode="same")
|
|
238
|
+
pbias = np.convolve(chdata.iv_p_tes, w, mode="same")
|
|
239
|
+
|
|
240
|
+
rfrac = rfrac[mask]
|
|
241
|
+
pbias = pbias[mask]
|
|
242
|
+
drfrac = np.diff(rfrac, prepend=np.nan)
|
|
243
|
+
dpbias = np.diff(pbias, prepend=np.nan)
|
|
244
|
+
logalpha = np.log(1 / rfrac * drfrac / dpbias)
|
|
245
|
+
logalpha_mask = np.isfinite(logalpha)
|
|
246
|
+
|
|
247
|
+
def logalpha_fit_func(logalpha_pars):
|
|
248
|
+
model = model_logalpha(rfrac, *logalpha_pars)
|
|
249
|
+
chi2 = np.sum((logalpha[logalpha_mask] - model[logalpha_mask]) ** 2)
|
|
250
|
+
return chi2
|
|
251
|
+
|
|
252
|
+
fitres = minimize(logalpha_fit_func, [4, 10, 1, 1])
|
|
253
|
+
logalpha_popts = fitres.x
|
|
254
|
+
|
|
255
|
+
logalpha_model = model_logalpha(rfrac, *fitres.x)
|
|
256
|
+
|
|
257
|
+
pbias_model = np.nancumsum(drfrac / (rfrac * np.exp(logalpha_model)))
|
|
258
|
+
|
|
259
|
+
def pbias_offset_fit_func(pbias_offset):
|
|
260
|
+
return np.sum((pbias_model + pbias_offset - pbias) ** 2)
|
|
261
|
+
|
|
262
|
+
pbias_model_offset = minimize(pbias_offset_fit_func, pbias[0]).x[0]
|
|
263
|
+
|
|
264
|
+
return logalpha_popts, pbias_model_offset
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def run_correction(chdata: RpFitChanData, cfg: AnalysisCfg) -> CorrectionResults:
|
|
268
|
+
"""
|
|
269
|
+
Runs TES param correction procedure.
|
|
270
|
+
|
|
271
|
+
Args
|
|
272
|
+
----
|
|
273
|
+
chdata: RpFitChanData
|
|
274
|
+
IV and Bias Step calibration data
|
|
275
|
+
cfg: AnalysisCfg
|
|
276
|
+
Correction analysis configuration object.
|
|
277
|
+
"""
|
|
278
|
+
res = CorrectionResults(chdata)
|
|
279
|
+
try:
|
|
280
|
+
if np.isnan(chdata.iv_Rn):
|
|
281
|
+
raise ValueError("IV Rn is NaN")
|
|
282
|
+
|
|
283
|
+
# Find logalpha popts
|
|
284
|
+
logalpha_popts, pbias_model_offset = find_logalpha_popts(chdata, cfg)
|
|
285
|
+
|
|
286
|
+
if np.abs(chdata.bs_Rtes / chdata.iv_Rn) < cfg.sc_rfrac_thresh:
|
|
287
|
+
# Cannot perform correction, since detectors are SC
|
|
288
|
+
res.corrected_params = CorrectedParams(
|
|
289
|
+
logalpha_popts=logalpha_popts,
|
|
290
|
+
pbias_model_offset=pbias_model_offset,
|
|
291
|
+
delta_Popt=np.nan,
|
|
292
|
+
corrected_I0=0.0,
|
|
293
|
+
corrected_R0=0.0,
|
|
294
|
+
corrected_Pj=0.0,
|
|
295
|
+
corrected_Si=np.nan,
|
|
296
|
+
loopgain=np.nan,
|
|
297
|
+
)
|
|
298
|
+
res.success = True
|
|
299
|
+
return res
|
|
300
|
+
|
|
301
|
+
# compute dPopt by minimizing (dIrat_IV - dIrat_BS) at chosen bias voltage with respect to delta_popt
|
|
302
|
+
dIrat_meas = chdata.bs_meas_dIrat
|
|
303
|
+
Rsh = chdata.Rsh
|
|
304
|
+
Ibias_setpoint = chdata.bs_Ibias
|
|
305
|
+
rfrac = np.linspace(
|
|
306
|
+
cfg.rpfit_min_rfrac, cfg.rpfit_max_rfrac, cfg.rpfit_intg_nsamps
|
|
307
|
+
)
|
|
308
|
+
dr = rfrac[1] - rfrac[0]
|
|
309
|
+
logalpha_model = model_logalpha(rfrac, *logalpha_popts)
|
|
310
|
+
alpha = np.exp(logalpha_model)
|
|
311
|
+
R = rfrac * chdata.iv_Rn
|
|
312
|
+
pbias = np.cumsum(dr / (rfrac * np.exp(logalpha_model))) + pbias_model_offset
|
|
313
|
+
|
|
314
|
+
def dIrat_IV(dPopt):
|
|
315
|
+
P_ = pbias - dPopt
|
|
316
|
+
L0_ = alpha * P_
|
|
317
|
+
i_tes_ = np.sqrt(P_ / R)
|
|
318
|
+
i_bias_ = i_tes_ * (R + Rsh) / Rsh
|
|
319
|
+
irat = (1.0 - L0_) / ((1.0 - L0_) + (1.0 + L0_) * R / Rsh)
|
|
320
|
+
return np.interp(Ibias_setpoint, i_bias_, irat)
|
|
321
|
+
|
|
322
|
+
def fit_func(dPopt):
|
|
323
|
+
diff = (dIrat_IV(dPopt) - dIrat_meas) ** 2
|
|
324
|
+
return diff if not np.isnan(diff) else np.inf
|
|
325
|
+
|
|
326
|
+
dPopt_fitres = minimize(fit_func, [0])
|
|
327
|
+
if not dPopt_fitres.success:
|
|
328
|
+
raise RuntimeError("dPopt fit failed")
|
|
329
|
+
|
|
330
|
+
dPopt = dPopt_fitres.x[0]
|
|
331
|
+
|
|
332
|
+
# Adjust IV parameter curves with delta_Popt, and find params at Ibias setpoint
|
|
333
|
+
pbias -= dPopt
|
|
334
|
+
ok = (pbias > 0.0) * (R > Rsh)
|
|
335
|
+
pbias = pbias[ok]
|
|
336
|
+
R = R[ok]
|
|
337
|
+
L0 = alpha[ok] * pbias
|
|
338
|
+
L = (R - Rsh) / (R + Rsh) * L0
|
|
339
|
+
Ites = np.sqrt(pbias / R)
|
|
340
|
+
Ibias = Ites * (R + Rsh) / Rsh
|
|
341
|
+
Si = -1 / (Ites * (R - Rsh)) * (L / (L + 1))
|
|
342
|
+
|
|
343
|
+
res.corrected_params = CorrectedParams(
|
|
344
|
+
logalpha_popts=logalpha_popts,
|
|
345
|
+
pbias_model_offset=pbias_model_offset,
|
|
346
|
+
delta_Popt=dPopt,
|
|
347
|
+
corrected_I0=np.interp(Ibias_setpoint, Ibias, Ites).item(),
|
|
348
|
+
corrected_R0=np.interp(Ibias_setpoint, Ibias, R).item(),
|
|
349
|
+
corrected_Pj=np.interp(Ibias_setpoint, Ibias, pbias).item(),
|
|
350
|
+
corrected_Si=np.interp(Ibias_setpoint, Ibias, Si).item(),
|
|
351
|
+
loopgain=np.interp(Ibias_setpoint, Ibias, L0).item(),
|
|
352
|
+
)
|
|
353
|
+
res.success = True
|
|
354
|
+
except Exception:
|
|
355
|
+
if cfg.raise_exceptions:
|
|
356
|
+
raise
|
|
357
|
+
else:
|
|
358
|
+
res.traceback = traceback.format_exc()
|
|
359
|
+
res.success = False
|
|
360
|
+
|
|
361
|
+
return res
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def run_corrections_parallel(
|
|
365
|
+
iva: IVAnalysis, bsa: BiasStepAnalysis, cfg: AnalysisCfg, nprocs=None,
|
|
366
|
+
executor=None, as_completed_callable=None) -> List[CorrectionResults]:
|
|
367
|
+
"""
|
|
368
|
+
Runs correction procedure in parallel for all channels in IV and BSA object
|
|
369
|
+
"""
|
|
370
|
+
|
|
371
|
+
nchans = iva.nchans
|
|
372
|
+
pb = trange(nchans, disable=(not cfg.show_pb))
|
|
373
|
+
results = []
|
|
374
|
+
|
|
375
|
+
if nprocs is None:
|
|
376
|
+
nprocs = cfg.default_nprocs
|
|
377
|
+
|
|
378
|
+
# Error handler
|
|
379
|
+
def errback(e):
|
|
380
|
+
raise e
|
|
381
|
+
|
|
382
|
+
# Create executor (optionally externally provided)
|
|
383
|
+
if executor is None:
|
|
384
|
+
executor = ProcessPoolExecutor(max_workers=nprocs)
|
|
385
|
+
as_completed_callable = as_completed
|
|
386
|
+
close_executor = True
|
|
387
|
+
else:
|
|
388
|
+
close_executor = False
|
|
389
|
+
|
|
390
|
+
try:
|
|
391
|
+
futures = []
|
|
392
|
+
for idx in range(nchans):
|
|
393
|
+
chdata = RpFitChanData.from_data(
|
|
394
|
+
iva, bsa, iva.bands[idx], iva.channels[idx]
|
|
395
|
+
)
|
|
396
|
+
future = executor.submit(run_correction, chdata, cfg)
|
|
397
|
+
futures.append(future)
|
|
398
|
+
|
|
399
|
+
for future in as_completed_callable(futures):
|
|
400
|
+
try:
|
|
401
|
+
res = future.result()
|
|
402
|
+
results.append(res)
|
|
403
|
+
except Exception as e:
|
|
404
|
+
errback(e)
|
|
405
|
+
|
|
406
|
+
futures.remove(future)
|
|
407
|
+
pb.update(1)
|
|
408
|
+
|
|
409
|
+
pb.close()
|
|
410
|
+
|
|
411
|
+
finally:
|
|
412
|
+
if close_executor:
|
|
413
|
+
executor.shutdown(wait=True, cancel_futures=True)
|
|
414
|
+
|
|
415
|
+
return results
|
|
416
|
+
|
|
417
|
+
def compute_psats(iva: IVAnalysis, cfg: AnalysisCfg) -> Tuple[np.ndarray, np.ndarray]:
|
|
418
|
+
"""
|
|
419
|
+
Re-computes Psat for an IVAnalysis object. Will save results to iva.p_sat.
|
|
420
|
+
This assumes i_tes, v_tes, and r_tes have already been calculated. This will
|
|
421
|
+
not modify the original IVAnalysis object.
|
|
422
|
+
|
|
423
|
+
Args
|
|
424
|
+
----
|
|
425
|
+
iva2 : Dictionary
|
|
426
|
+
Dictionary built from original IV Analysis .npy file
|
|
427
|
+
psat_level : float
|
|
428
|
+
R_frac level for which Psat is defined. If 0.9, Psat will be the
|
|
429
|
+
power on the TES when R_frac = 0.9.
|
|
430
|
+
|
|
431
|
+
Returns
|
|
432
|
+
-------
|
|
433
|
+
p_sat : np.ndarray
|
|
434
|
+
Array of length (nchan) with the p-sat computed for each channel (W)
|
|
435
|
+
psat_cross_idx : np.ndarray
|
|
436
|
+
Array of indices at which the psat level is crossed for each channel
|
|
437
|
+
"""
|
|
438
|
+
# calculates P_sat as P_TES when Rfrac = psat_level
|
|
439
|
+
# if the TES is at 90% R_n more than once, just take the first crossing
|
|
440
|
+
psats = np.full(iva.nchans, np.nan)
|
|
441
|
+
psat_cross_idx = np.full(iva.nchans, -1)
|
|
442
|
+
|
|
443
|
+
for i in range(iva.nchans):
|
|
444
|
+
R = iva.R[i]
|
|
445
|
+
R_n = iva.R_n[i]
|
|
446
|
+
p_tes = iva.p_tes[i]
|
|
447
|
+
|
|
448
|
+
if np.isnan(R_n):
|
|
449
|
+
continue
|
|
450
|
+
|
|
451
|
+
cross_idx = np.where(R / R_n > cfg.psat_level)[0]
|
|
452
|
+
if len(cross_idx) == 0:
|
|
453
|
+
continue
|
|
454
|
+
|
|
455
|
+
# Takes cross-index to be the first time Rfrac crosses psat_level
|
|
456
|
+
cross_idx = cross_idx[0]
|
|
457
|
+
if cross_idx == 0:
|
|
458
|
+
continue
|
|
459
|
+
|
|
460
|
+
psat_cross_idx[i] = cross_idx
|
|
461
|
+
try:
|
|
462
|
+
psat = interp1d(
|
|
463
|
+
R[cross_idx - 1 : cross_idx + 1] / R_n,
|
|
464
|
+
p_tes[cross_idx - 1 : cross_idx + 1],
|
|
465
|
+
)(cfg.psat_level)
|
|
466
|
+
except ValueError:
|
|
467
|
+
continue
|
|
468
|
+
psats[i] = psat
|
|
469
|
+
|
|
470
|
+
return psats, psat_cross_idx
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def compute_si(iva: IVAnalysis) -> np.ndarray:
|
|
474
|
+
"""
|
|
475
|
+
Recalculates responsivity using the thevenin equivalent voltage. This will
|
|
476
|
+
not modify the original IVAnalysis object.
|
|
477
|
+
|
|
478
|
+
Args
|
|
479
|
+
----
|
|
480
|
+
iva : IVAnalysis
|
|
481
|
+
IVAnalysis object for which you want to compute Si. This should already
|
|
482
|
+
have items like R, R_n, R_L, i_tes, v_tes, computed.
|
|
483
|
+
|
|
484
|
+
Returns
|
|
485
|
+
-------
|
|
486
|
+
si : np.ndarray
|
|
487
|
+
Array of length (nchan, nbiases) with the responsivity as a fn of
|
|
488
|
+
thevenin equivalent voltage for each channel (V^-1).
|
|
489
|
+
"""
|
|
490
|
+
si_all = np.full(iva.si.shape, np.nan)
|
|
491
|
+
|
|
492
|
+
smooth_dist = 5
|
|
493
|
+
w_len = 2 * smooth_dist + 1
|
|
494
|
+
w = (1.0 / float(w_len)) * np.ones(w_len) # window
|
|
495
|
+
|
|
496
|
+
v_thev_smooth = np.convolve(iva.v_thevenin, w, mode="same")
|
|
497
|
+
dv_thev = np.diff(v_thev_smooth)
|
|
498
|
+
|
|
499
|
+
for i in range(iva.nchans):
|
|
500
|
+
sc_idx = iva.idxs[i, 0]
|
|
501
|
+
|
|
502
|
+
if np.isnan(iva.R_n[i]) or sc_idx == -1:
|
|
503
|
+
continue
|
|
504
|
+
|
|
505
|
+
# Running average
|
|
506
|
+
i_tes_smooth = np.convolve(iva.i_tes[i], w, mode="same")
|
|
507
|
+
v_tes_smooth = np.convolve(iva.v_tes[i], w, mode="same")
|
|
508
|
+
r_tes_smooth = v_tes_smooth / i_tes_smooth
|
|
509
|
+
|
|
510
|
+
R_L = iva.R_L[i]
|
|
511
|
+
|
|
512
|
+
# Take derivatives
|
|
513
|
+
di_tes = np.diff(i_tes_smooth)
|
|
514
|
+
dv_tes = np.diff(v_tes_smooth)
|
|
515
|
+
R_L_smooth = np.ones(len(r_tes_smooth - 1)) * R_L
|
|
516
|
+
R_L_smooth[:sc_idx] = dv_tes[:sc_idx] / di_tes[:sc_idx]
|
|
517
|
+
r_tes_smooth_noStray = r_tes_smooth - R_L_smooth
|
|
518
|
+
i0 = i_tes_smooth[:-1]
|
|
519
|
+
r0 = r_tes_smooth_noStray[:-1]
|
|
520
|
+
rL = R_L_smooth[:-1]
|
|
521
|
+
beta = 0.0
|
|
522
|
+
|
|
523
|
+
# artificially setting rL to 0 for now,
|
|
524
|
+
# to avoid issues in the SC branch
|
|
525
|
+
# don't expect a large change, given the
|
|
526
|
+
# relative size of rL to the other terms
|
|
527
|
+
# rL = 0
|
|
528
|
+
|
|
529
|
+
# Responsivity estimate, derivation done here by MSF
|
|
530
|
+
# https://www.overleaf.com/project/613978cb38d9d22e8550d45c
|
|
531
|
+
si = -(1.0 / (i0 * r0 * (2 + beta))) * (
|
|
532
|
+
1 - ((r0 * (1 + beta) + rL) / (dv_thev / di_tes))
|
|
533
|
+
)
|
|
534
|
+
si[:sc_idx] = np.nan
|
|
535
|
+
si_all[i, :-1] = si
|
|
536
|
+
|
|
537
|
+
return si_all
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def recompute_ivpars(iva: IVAnalysis, cfg: AnalysisCfg) -> IVAnalysis:
|
|
541
|
+
"""
|
|
542
|
+
Takes in an IV Analysis object and analysis cfg params, and recomputes
|
|
543
|
+
TES voltage, current, bias power, resistance, responsivity, and saturation
|
|
544
|
+
powers using the corrected TES formulas for R_L and V_thevenin.
|
|
545
|
+
"""
|
|
546
|
+
iva_new = IVAnalysis.from_dict(deepcopy(iva.to_dict()))
|
|
547
|
+
R_sh = iva.meta["R_sh"]
|
|
548
|
+
R_bl = iva.meta["bias_line_resistance"]
|
|
549
|
+
iva_new.i_bias = iva_new.v_bias / R_bl
|
|
550
|
+
iva_new.v_thevenin = iva_new.i_bias * R_sh
|
|
551
|
+
iva_new.v_tes = np.full(iva.v_tes.shape, np.nan)
|
|
552
|
+
iva_new.i_tes = np.full(iva.i_tes.shape, np.nan)
|
|
553
|
+
iva_new.p_tes = np.full(iva.p_tes.shape, np.nan)
|
|
554
|
+
iva_new.R = np.full(iva.R.shape, np.nan)
|
|
555
|
+
iva_new.R_n = np.full(iva.R_n.shape, np.nan)
|
|
556
|
+
iva_new.R_L = np.full(iva.R_L.shape, np.nan)
|
|
557
|
+
|
|
558
|
+
iva_new.resp
|
|
559
|
+
for i in range(iva.nchans):
|
|
560
|
+
sc_idx = iva.idxs[i, 0]
|
|
561
|
+
nb_idx = iva.idxs[i, 1]
|
|
562
|
+
|
|
563
|
+
R: NDArray[np.float] = R_sh * (iva.i_bias / iva.resp[i] - 1)
|
|
564
|
+
R_par: float = np.nanmean(R[1:sc_idx])
|
|
565
|
+
R_n: float = np.nanmean(R[nb_idx:]) - R_par
|
|
566
|
+
R_L: float = R_sh + R_par
|
|
567
|
+
R_tes: NDArray[np.float] = R - R_par
|
|
568
|
+
|
|
569
|
+
iva_new.v_tes[i] = iva_new.v_thevenin * (R_tes / (R_tes + R_L))
|
|
570
|
+
iva_new.i_tes[i] = iva_new.v_tes[i] / R_tes
|
|
571
|
+
iva_new.p_tes[i] = iva_new.v_tes[i] ** 2 / R_tes
|
|
572
|
+
iva_new.R[i] = R_tes
|
|
573
|
+
iva_new.R_n[i] = R_n
|
|
574
|
+
iva_new.R_L[i] = R_L
|
|
575
|
+
|
|
576
|
+
iva_new.p_sat, iva_new.idxs[:, 2] = compute_psats(iva_new, cfg)
|
|
577
|
+
iva_new.si = compute_si(iva_new)
|
|
578
|
+
|
|
579
|
+
return iva_new
|