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.
Files changed (60) hide show
  1. pytesprocess/__init__.py +9 -0
  2. pytesprocess/_version.py +2 -0
  3. pytesprocess/cli/__init__.py +1 -0
  4. pytesprocess/cli/commands/__init__.py +5 -0
  5. pytesprocess/cli/commands/event.py +66 -0
  6. pytesprocess/cli/commands/filter.py +17 -0
  7. pytesprocess/cli/commands/ivsweep.py +29 -0
  8. pytesprocess/cli/common.py +86 -0
  9. pytesprocess/cli/main.py +81 -0
  10. pytesprocess/config/__init__.py +4 -0
  11. pytesprocess/config/loader.py +94 -0
  12. pytesprocess/config/manager.py +297 -0
  13. pytesprocess/config/resolvers/__init__.py +5 -0
  14. pytesprocess/config/resolvers/common.py +56 -0
  15. pytesprocess/config/resolvers/feature.py +293 -0
  16. pytesprocess/config/resolvers/salting.py +86 -0
  17. pytesprocess/config/resolvers/trigger.py +84 -0
  18. pytesprocess/config/selectors.py +108 -0
  19. pytesprocess/config/validation.py +314 -0
  20. pytesprocess/config/warnings.py +2 -0
  21. pytesprocess/core/__init__.py +10 -0
  22. pytesprocess/core/algorithms.py +1455 -0
  23. pytesprocess/core/didv.py +1648 -0
  24. pytesprocess/core/eventbuilder.py +495 -0
  25. pytesprocess/core/filterbuilder.py +81 -0
  26. pytesprocess/core/filterdata.py +1849 -0
  27. pytesprocess/core/ivsweep.py +2072 -0
  28. pytesprocess/core/noise.py +923 -0
  29. pytesprocess/core/noisemodel.py +1408 -0
  30. pytesprocess/core/oftrigger.py +1035 -0
  31. pytesprocess/core/template.py +450 -0
  32. pytesprocess/process/__init__.py +6 -0
  33. pytesprocess/process/data_source.py +185 -0
  34. pytesprocess/process/event_context.py +35 -0
  35. pytesprocess/process/feature_plan.py +186 -0
  36. pytesprocess/process/feature_resources.py +267 -0
  37. pytesprocess/process/features.py +1024 -0
  38. pytesprocess/process/filterprocess.py +1176 -0
  39. pytesprocess/process/ivprocess.py +1380 -0
  40. pytesprocess/process/processing_data.py +967 -0
  41. pytesprocess/process/randoms.py +921 -0
  42. pytesprocess/process/triggers.py +1011 -0
  43. pytesprocess/salting/__init__.py +7 -0
  44. pytesprocess/salting/generator.py +364 -0
  45. pytesprocess/salting/injector.py +329 -0
  46. pytesprocess/salting/sampling.py +84 -0
  47. pytesprocess/utils/__init__.py +5 -0
  48. pytesprocess/utils/arg_utils.py +122 -0
  49. pytesprocess/utils/dataframe_output.py +120 -0
  50. pytesprocess/utils/filter_hdf5.py +594 -0
  51. pytesprocess/utils/utils.py +701 -0
  52. pytesprocess/workflows/__init__.py +3 -0
  53. pytesprocess/workflows/processing.py +317 -0
  54. pytesprocess/workflows/salting.py +133 -0
  55. pytesprocess-0.1.1.dist-info/METADATA +211 -0
  56. pytesprocess-0.1.1.dist-info/RECORD +60 -0
  57. pytesprocess-0.1.1.dist-info/WHEEL +5 -0
  58. pytesprocess-0.1.1.dist-info/entry_points.txt +2 -0
  59. pytesprocess-0.1.1.dist-info/licenses/LICENSE +21 -0
  60. pytesprocess-0.1.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1408 @@
1
+ import matplotlib.pyplot as plt
2
+ from lmfit import Model
3
+ import numpy as np
4
+ import qetpy as qp
5
+ from scipy import constants
6
+ from scipy.signal import savgol_filter
7
+
8
+ from pytesprocess.core.filterdata import FilterData
9
+ from pytesprocess.utils import utils
10
+
11
+ __all__ = ['NoiseModel']
12
+
13
+
14
+ class NoiseModel(FilterData):
15
+ """
16
+ Class to manage noise calculation from
17
+ randoms for multiple channels
18
+ """
19
+
20
+ def __init__(self, verbose=True):
21
+ """
22
+ Initialize class
23
+
24
+ Parameters:
25
+ ----------
26
+
27
+ verbose : bool, optional
28
+ display information
29
+ """
30
+
31
+ # instantiate base class
32
+ super().__init__(verbose=verbose)
33
+
34
+ # data
35
+ self._noise_data = dict()
36
+ self._tbath = None
37
+ self._tload_guess = None
38
+ self._tc = dict()
39
+ self._gta = dict()
40
+ self._inductance = dict()
41
+ self._poles = None
42
+
43
+ # internal data for SC fit
44
+ self._s_isquid_for_fit = None
45
+
46
+
47
+ def set_iv_didv_results_from_file(self, file_name,
48
+ poles=2,
49
+ channels=None):
50
+ """
51
+ set dIdV and/or IV sweep results from an HDF5 file
52
+ """
53
+
54
+ self.load_hdf5(file_name)
55
+
56
+ if channels is None:
57
+ channels = self._filter_data.keys()
58
+ if not channels:
59
+ raise ValueError(f'ERROR: No data loaded... '
60
+ f'Check file {file_name}')
61
+ elif isinstance(channels, str):
62
+ channels = [channels]
63
+
64
+ for chan in channels:
65
+
66
+ # check if filter data
67
+ if chan not in self._filter_data.keys():
68
+ raise ValueError(f'ERROR: No data loaded for channel {chan}. '
69
+ f'Check file {file_name} !')
70
+ # dIdV results
71
+ didv_results = None
72
+ try:
73
+ didv_results = self.get_didv_results(chan, poles=poles)
74
+ except:
75
+ print(f'WARNING: No {poles}-poles dIdV results found for '
76
+ f'channel {chan}!')
77
+
78
+ # IV results
79
+ ivsweep_results = None
80
+ try:
81
+ ivsweep_results = self.get_ivsweep_results(chan)
82
+ except:
83
+ pass
84
+
85
+
86
+ self.set_iv_didv_results_from_dict(
87
+ chan,
88
+ didv_results=didv_results,
89
+ poles=poles,
90
+ ivsweep_results=ivsweep_results
91
+ )
92
+
93
+ def set_iv_didv_results_from_dict(self, channel,
94
+ didv_results=None,
95
+ poles=2,
96
+ ivsweep_results=None):
97
+ """
98
+ Set didv from dictionary for specified channel
99
+ """
100
+
101
+ # check if channel exist
102
+ if channel not in self._noise_data.keys():
103
+ self._noise_data[channel] = dict()
104
+
105
+
106
+ # save poles
107
+ self._poles = poles
108
+
109
+ # dIdV results
110
+ if didv_results is not None:
111
+
112
+ if poles is None:
113
+ raise ValueError('ERROR: dIdV poles (2 or 3) required!')
114
+
115
+ # add to filter data
116
+ metadata = None
117
+ if 'metadata' in didv_results:
118
+ metadata = didv_results['metadata']
119
+ self.set_didv_results(
120
+ channel,
121
+ didv_results,
122
+ poles=poles,
123
+ metadata=metadata
124
+ )
125
+
126
+ # Add small signal parameters
127
+ if 'smallsignalparams' in didv_results.keys():
128
+ self._noise_data[channel]['smallsignalparams'] = (
129
+ didv_results['smallsignalparams'].copy()
130
+ )
131
+ else:
132
+ raise ValueError(f'ERROR: dIdV fit results '
133
+ f'does not contain "smallsignalparams" '
134
+ f'for channel {channel}!')
135
+
136
+ # add "biasparams"
137
+ if ('biasparams' in didv_results.keys()
138
+ and didv_results['biasparams'] is not None):
139
+ self._noise_data[channel]['biasparams'] = didv_results['biasparams']
140
+
141
+
142
+ # IV results
143
+ if ivsweep_results is not None:
144
+
145
+ # add to filter data
146
+ self.set_ivsweep_results(
147
+ channel,
148
+ ivsweep_results,
149
+ 'noise')
150
+
151
+ # add to noise data
152
+ if 'biasparams' not in self._noise_data[channel]:
153
+ self._noise_data[channel]['biasparams'] = ivsweep_results
154
+ else:
155
+ self._noise_data[channel]['biasparams'].update(ivsweep_results)
156
+
157
+ # add more quantities
158
+ if 'rn' not in self._noise_data[channel]['biasparams']:
159
+ self._noise_data[chan]['biasparams']['rn'] = (
160
+ ivsweep_results['rn']
161
+ )
162
+
163
+ # add more quantities
164
+ if 'rp' not in self._noise_data[channel]['biasparams']:
165
+ self._noise_data[chan]['biasparams']['rp'] = (
166
+ ivsweep_results['rp']
167
+ )
168
+
169
+
170
+ if 'rshunt' not in self._noise_data[channel]['biasparams']:
171
+ self._noise_data[channel]['biasparams']['rshunt'] = (
172
+ ivsweep_results['rshunt']
173
+ )
174
+
175
+ # inductance
176
+ if 'normal_didv_fit_L' in ivsweep_results.keys():
177
+ self.set_inductance(channel,
178
+ ivsweep_results['normal_didv_fit_L'],
179
+ 'normal')
180
+
181
+ if 'sc_didv_fit_L' in ivsweep_results.keys():
182
+ self.set_inductance(channel,
183
+ ivsweep_results['sc_didv_fit_L'],
184
+ 'sc')
185
+
186
+ # check Rn
187
+ if 'rn' not in self._noise_data[channel]['biasparams']:
188
+ raise ValueError(f'ERROR: No Rn found for channel {channel}! '
189
+ f'Add "rn" key/value in "ivsweep_results" '
190
+ f'argument.')
191
+
192
+
193
+ def set_inductance(self, channel, L, state):
194
+ """
195
+ Set inductance when normal/SC state
196
+ """
197
+ if (state != 'normal' and state != 'sc'):
198
+ raise ValueError('ERROR: "state" argument should be '
199
+ '"normal" or "sc"!')
200
+
201
+ if channel not in self._inductance.keys():
202
+ self._inductance[channel] = dict()
203
+
204
+ self._inductance[channel][state] = float(L)
205
+
206
+
207
+ def set_tbath(self, tbath):
208
+ """
209
+ Set bath temperature
210
+ """
211
+
212
+ self._tbath = float(tbath)
213
+
214
+ def set_tload_guess(self, tload):
215
+ """
216
+ Set bath temperature
217
+ """
218
+
219
+ self._tload_guess = float(tload)
220
+
221
+
222
+ def set_tc(self, channel, tc):
223
+ """
224
+ Set Tc
225
+ """
226
+
227
+ self._tc[channel] = float(tc)
228
+
229
+
230
+ def set_gta(self, channel, gta):
231
+ """
232
+ Set Gta for specified channel
233
+ """
234
+
235
+ self._gta[channel] = float(gta)
236
+
237
+
238
+ def set_psd_from_file(self, file_name):
239
+ """
240
+ Set TES data from file
241
+ """
242
+
243
+ raise ValueError('ERROR: Not implemented yet!')
244
+
245
+
246
+ # load hdf5 file
247
+ self.load_hdf5(file_name)
248
+
249
+
250
+ def set_psd(self, channel, psd, psd_freqs, state):
251
+ """
252
+ Set two-sided PSD for "normal", "sc", "transition"
253
+ state, The TES bias used when taking PSD data
254
+ needs also to be set.
255
+ """
256
+
257
+ state_list = ['normal', 'sc', 'transition']
258
+ if state not in state_list:
259
+ raise ValueError(
260
+ 'ERROR: "state" argument should be '
261
+ '"normal", "sc", or "transition"'
262
+ )
263
+
264
+ # initialized
265
+ if channel not in self._noise_data.keys():
266
+ self._noise_data[channel] = dict()
267
+
268
+ if state not in self._noise_data[channel]:
269
+ self._noise_data[channel][state] = dict()
270
+
271
+ # check if folded
272
+ is_folded = not np.any(psd_freqs<0)
273
+ if is_folded:
274
+ raise ValueError(
275
+ 'ERROR: Two-sides PSD needs '
276
+ 'to be provided, not folded PSD!')
277
+
278
+ self._noise_data[channel][state]['psd'] = psd
279
+ self._noise_data[channel][state]['psd_freqs'] = psd_freqs
280
+
281
+
282
+ def set_normal_fit_results(self, channel,
283
+ squiddc=None,
284
+ squidpole=None,
285
+ squidn=None):
286
+ """
287
+ Set normal noise fit results
288
+ """
289
+
290
+ if (squiddc is None
291
+ or squidpole is None
292
+ or squidn is None):
293
+ raise ValueError('ERROR: "squiddc", "squidpole", and '
294
+ '"squidn" are required!')
295
+
296
+ if channel not in self._noise_data.keys():
297
+ self._noise_data[channel] = dict()
298
+
299
+ if 'normal' not in self._noise_data[channel]:
300
+ self._noise_data[channel]['normal'] = dict()
301
+
302
+ # add data
303
+ self._noise_data[channel]['normal']['fit'] = {
304
+ 'squiddc': float(squiddc),
305
+ 'squidpole': float(squidpole),
306
+ 'squidn': float(squidn)
307
+ }
308
+
309
+
310
+ def set_sc_fit_results(self, channel, tload=None):
311
+
312
+ """
313
+ Set SC noise fit results
314
+ """
315
+
316
+ if tload is None:
317
+ raise ValueError('ERROR: "tload" is required!')
318
+
319
+ if channel not in self._noise_data.keys():
320
+ self._noise_data[channel] = dict()
321
+
322
+ if 'sc' not in self._noise_data[channel]:
323
+ self._noise_data[channel]['sc'] = dict()
324
+
325
+ # add data
326
+ self._noise_data[channel]['sc']['fit'] = {'tload': float(tload)}
327
+
328
+
329
+ def set_squid_noise(self, channel, squid_noise, squid_noise_freqs):
330
+
331
+ """
332
+ Set SQUID noise
333
+ """
334
+
335
+ if channel not in self._noise_data.keys():
336
+ self._noise_data[channel] = dict()
337
+
338
+ if 'sim' not in self._noise_data[channel]:
339
+ self._noise_data[channel]['sim'] = {'normal': {},
340
+ 'sc' :{},
341
+ 'transition': {}}
342
+
343
+ self._noise_data[channel]['sim']['normal']['s_isquid'] = (
344
+ squid_noise
345
+ )
346
+ self._noise_data[channel]['sim']['normal']['freqs'] = (
347
+ squid_noise_freqs
348
+ )
349
+
350
+
351
+ def calc_squid_noise(self,
352
+ channels=None,
353
+ do_fit_normal_noise=False,
354
+ fit_range=(100, 1e5),
355
+ squiddc0=6e-12, squidpole0=200, squidn0=0.7,
356
+ lgc_plot=False, xlims=None, ylims=None,
357
+ lgc_save_fig=False, save_path=None):
358
+ """
359
+ Calculate SQUID+Electronics noise from normal noise
360
+ either by using normal noise directly (subtracting johnson noise)
361
+ or fitting PSD
362
+
363
+ Parameters
364
+ ----------
365
+ fit_range : tuple, optional
366
+ The frequency range over which to do the fit.
367
+ squiddc0 : float, optional
368
+ Initial guess for the squiddc parameter.
369
+ squidpole0 : float, optional
370
+ Initial guess for the squidpole parameter.
371
+ squidn0 : float, optional
372
+ Initial guess for the squidn parameter.
373
+ lgc_plot : bool, optional
374
+ If True, a plot of the fit is shown.
375
+ xlims : NoneType, tuple, optional
376
+ Limits to be passed to ax.set_xlim().
377
+ ylims : NoneType, tuple, optional
378
+ Limits to be passed to ax.set_ylim().
379
+ lgc_save_fig : bool, optional
380
+ If True, the figure is saved.
381
+ save_path : str
382
+ Directory to save data
383
+
384
+ Returns
385
+ -------
386
+ None
387
+
388
+
389
+ """
390
+
391
+ # check if fit normal noise
392
+ if do_fit_normal_noise:
393
+ self.fit_normal_noise(
394
+ channels=channels,
395
+ fit_range=fit_range,
396
+ squiddc0=squiddc0,
397
+ squidpole0=squidpole0,
398
+ squidn0=squidn0,
399
+ lgc_plot=lgc_plot,
400
+ xlims=xlims, ylims=ylims,
401
+ lgc_save_fig=lgc_save_fig,
402
+ save_path=save_path)
403
+ return
404
+
405
+ # check channels
406
+ if channels is None:
407
+ channels = self._noise_data.keys()
408
+ elif isinstance(channels, str):
409
+ channels = [channels]
410
+
411
+
412
+ for chan in channels:
413
+
414
+ # check psd
415
+ if chan not in self._noise_data.keys():
416
+ raise ValueError(f'ERROR: No data for channel {chan} '
417
+ 'available. Set psd and didv data first!')
418
+
419
+
420
+ # check normal parameters exists
421
+ if ('normal' not in self._noise_data[chan]
422
+ or 'psd' not in self._noise_data[chan]['normal']):
423
+ raise ValueError(f'ERROR: No normal psd for channel {chan} '
424
+ 'available. Set psd first!')
425
+
426
+ # check tload
427
+ if self._tload_guess is None:
428
+ raise ValueError(f'ERROR: Normal fit requires Tload '
429
+ f'Set Tload first using function '
430
+ f'"set_tload_guess(tload)"!')
431
+
432
+
433
+ # check tload
434
+ if self._tbath is None:
435
+ raise ValueError(f'ERROR: Bath temperature is needed! '
436
+ f'Set Tbath first using function '
437
+ f'"set_tbath(tbath)"!')
438
+
439
+ # didv
440
+ if 'biasparams' not in self._noise_data[chan]:
441
+ raise ValueError(f'ERROR: No iv/didv data for channel {chan} '
442
+ 'available. Set didv data first!')
443
+ # inductance
444
+ if (chan not in self._inductance
445
+ or 'normal' not in self._inductance[chan]):
446
+ raise ValueError(f'ERROR: No inductance value for channel {chan} '
447
+ f'available. Set inductance first using function '
448
+ f'set_inductance("{chan}", L, "normal")')
449
+
450
+ # Loop channels and fit data
451
+ for chan in channels:
452
+
453
+ # psd array
454
+ psd = self._noise_data[chan]['normal']['psd']
455
+ psd_freqs = self._noise_data[chan]['normal']['psd_freqs']
456
+
457
+ # tc
458
+ tc = self._tc[chan]
459
+
460
+ # rn/rload
461
+ rn = float(self._noise_data[chan]['biasparams']['rn'])
462
+ rp = float(self._noise_data[chan]['biasparams']['rp'])
463
+ rshunt = None
464
+ if 'rsh' in self._noise_data[chan]['biasparams']:
465
+ rshunt = self._noise_data[chan]['biasparams']['rsh']
466
+ else:
467
+ rshunt = self._noise_data[chan]['biasparams']['rshunt']
468
+ rload = rp + rshunt
469
+
470
+ # inductance
471
+ L = float(self._inductance[chan]['normal'])
472
+
473
+ # calc squid noise
474
+ squid_noise = qp.sim.get_squid_noise_from_normal_noise(
475
+ freqs=psd_freqs,
476
+ normal_noise=psd,
477
+ tload=self._tload_guess,
478
+ tc=tc, rload=rload, rn=rn,
479
+ inductance=L)
480
+
481
+ if 'sim' not in self._noise_data[chan]:
482
+ self._noise_data[chan]['sim'] = {'normal': {},
483
+ 'sc' :{},
484
+ 'transition': {}}
485
+
486
+ self._noise_data[chan]['sim']['normal']['s_isquid'] = squid_noise
487
+ self._noise_data[chan]['sim']['normal']['freqs'] = psd_freqs
488
+
489
+
490
+ def fit_normal_noise(self, channels=None,
491
+ fit_range=(100, 1e5),
492
+ squiddc0=6e-12, squidpole0=200, squidn0=0.7,
493
+ lgc_plot=False, xlims=None, ylims=None,
494
+ lgc_save_fig=False, save_path=None):
495
+ """
496
+ Function to fit the noise components of the SQUID+Electronics.
497
+ Fits all normal noise PSDs and stores the average value for
498
+ squiddc, squidpole, and squidn as attributes of the class.
499
+
500
+ Parameters
501
+ ----------
502
+ fit_range : tuple, optional
503
+ The frequency range over which to do the fit.
504
+ squiddc0 : float, optional
505
+ Initial guess for the squiddc parameter.
506
+ squidpole0 : float, optional
507
+ Initial guess for the squidpole parameter.
508
+ squidn0 : float, optional
509
+ Initial guess for the squidn parameter.
510
+ lgc_plot : bool, optional
511
+ If True, a plot of the fit is shown.
512
+ xlims : NoneType, tuple, optional
513
+ Limits to be passed to ax.set_xlim().
514
+ ylims : NoneType, tuple, optional
515
+ Limits to be passed to ax.set_ylim().
516
+ lgc_save_fig : bool, optional
517
+ If True, the figure is saved.
518
+ save_path : str
519
+ Directory to save data
520
+
521
+ Returns
522
+ -------
523
+ None
524
+
525
+ """
526
+
527
+ # check channels
528
+ if channels is None:
529
+ channels = self._noise_data.keys()
530
+ elif isinstance(channels, str):
531
+ channels = [channels]
532
+
533
+
534
+ for chan in channels:
535
+
536
+ # check psd
537
+ if chan not in self._noise_data.keys():
538
+ raise ValueError(f'ERROR: No data for channel {chan} '
539
+ 'available. Set psd and didv data first!')
540
+
541
+
542
+ # check normal parameters exists
543
+ if ('normal' not in self._noise_data[chan]
544
+ or 'psd' not in self._noise_data[chan]['normal']):
545
+ raise ValueError(f'ERROR: No normal psd for channel {chan} '
546
+ 'available. Set psd first!')
547
+
548
+
549
+ # check tc
550
+ if chan not in self._tc.keys():
551
+ raise ValueError(f'ERROR: No Tc for channel {chan} '
552
+ f'available. Set Tc first using function '
553
+ f'set_tc("{chan}", tc)"!')
554
+
555
+
556
+ # check tload
557
+ if self._tload_guess is None:
558
+ raise ValueError(f'ERROR: Normal fit requires Tload '
559
+ f'Set Tload first using function '
560
+ f'"set_tload_guess(tload)"!')
561
+
562
+
563
+ # check tload
564
+ if self._tbath is None:
565
+ raise ValueError(f'ERROR: Bath temperature is needed! '
566
+ f'Set Tbath first using function '
567
+ f'"set_tbath(tbath)"!')
568
+
569
+ # didv
570
+ if 'biasparams' not in self._noise_data[chan]:
571
+ raise ValueError(f'ERROR: No iv/didv data for channel {chan} '
572
+ 'available. Set didv data first!')
573
+ # inductance
574
+ if (chan not in self._inductance
575
+ or 'normal' not in self._inductance[chan]):
576
+ raise ValueError(f'ERROR: No inductance value for channel {chan} '
577
+ f'available. Set inductance first using function '
578
+ f'set_inductance("{chan}", L, "normal")')
579
+
580
+ # Loop channels and fit data
581
+ for chan in channels:
582
+
583
+ # psd array
584
+ psd = self._noise_data[chan]['normal']['psd']
585
+ psd_freqs = self._noise_data[chan]['normal']['psd_freqs']
586
+
587
+ # didv
588
+ didv_results = self.get_didv_results(chan, self._poles)
589
+
590
+ # tc
591
+ tc = self._tc[chan]
592
+
593
+ # rn/rload
594
+ rn = float(self._noise_data[chan]['biasparams']['rn'])
595
+ rp = float(self._noise_data[chan]['biasparams']['rp'])
596
+ rshunt = None
597
+ if 'rsh' in self._noise_data[chan]['biasparams']:
598
+ rshunt = self._noise_data[chan]['biasparams']['rsh']
599
+ else:
600
+ rshunt = self._noise_data[chan]['biasparams']['rshunt']
601
+ rload = rp + rshunt
602
+
603
+ # inductance
604
+ L = float(self._inductance[chan]['normal'])
605
+
606
+ # fit range
607
+ ind_lower = (np.abs(psd_freqs - fit_range[0])).argmin()
608
+ ind_upper = (np.abs(psd_freqs - fit_range[1])).argmin()
609
+
610
+ xdata = psd_freqs[ind_lower:ind_upper]
611
+ ydata = self._flatten_psd(psd_freqs, psd)[ind_lower:ind_upper]
612
+
613
+ # build model and fit
614
+ model = Model(_normal_noise, independent_vars=['freqs'])
615
+
616
+ params = model.make_params(
617
+ squiddc=squiddc0,
618
+ squidpole=squidpole0,
619
+ squidn=squidn0,
620
+ rload=rload,
621
+ tload=self._tload_guess,
622
+ rn=rn,
623
+ tc=tc,
624
+ inductance=L,
625
+ )
626
+ params['tc'].vary = False
627
+ params['tload'].vary = False
628
+ params['rload'].vary = False
629
+ params['rn'].vary = False
630
+ params['inductance'].vary = False
631
+ result = model.fit(ydata, params, freqs=xdata)
632
+
633
+ # fit result
634
+ fitvals = result.values
635
+
636
+ # store values
637
+ self._noise_data[chan]['normal']['fit'] = fitvals
638
+
639
+ # calculate sQUID noise
640
+ squiddc = fitvals['squiddc'],
641
+ squidpole = fitvals['squidpole'],
642
+ squidn = itvals['squidn'],
643
+ squid_noise = self.get_squid_noise_from_fit(
644
+ psd_freqs, squiddc, squidpole, squidn
645
+ )
646
+
647
+
648
+ if 'sim' not in self._noise_data[chan]:
649
+ self._noise_data[chan]['sim'] = {'normal': {},
650
+ 'sc' :{},
651
+ 'transition': {}}
652
+ # FIXME: Need to unfold
653
+ self._noise_data[chan]['sim']['normal']['s_isquid'] = squid_noise
654
+ self._noise_data[chan]['sim']['normal']['freqs'] = psd_freqs
655
+
656
+ # Instantiate Noise sim
657
+ """
658
+ noise_sim = qp.sim.TESnoise(
659
+ rload=float(rload),
660
+ r0=rn,
661
+ rshunt=rshunt,
662
+ inductance=L,
663
+ beta=0,
664
+ loopgain=0,
665
+ tau0=0,
666
+ G=0,
667
+ qetbias=tes_bias,
668
+ tc=tc,
669
+ tload=self._tload_guess,
670
+ tbath=self._tbath,
671
+ squiddc=fitvals['squiddc'],
672
+ squidpole=fitvals['squidpole'],
673
+ squidn=fitvals['squidn'],
674
+ )
675
+
676
+
677
+ if lgc_plot:
678
+ qp.plotting.plot_noise_sim(
679
+ f=psd_freqs,
680
+ psd=psd,
681
+ noise_sim=noise_sim,
682
+ istype='normal',
683
+ qetbias=round(tes_bias*1e9)/1e9,
684
+ lgcsave=lgc_save_fig,
685
+ figsavepath=save_path,
686
+ xlims=xlims,
687
+ ylims=ylims,
688
+ )
689
+ """
690
+
691
+
692
+ def get_squid_noise_from_fit(self, freqs, squiddc, squidpole, squidn):
693
+ """
694
+ Calculate SQUID noise
695
+ """
696
+
697
+ s_isquid = (squiddc * (1.0 + (squidpole / freqs)**squidn))**2.0
698
+ return s_isquid
699
+
700
+
701
+ def get_noise_data(self, channel=None, mode='transition'):
702
+ '''
703
+ Gets a dictionary with all relevant noise parameters fit at
704
+ the bias point(s).
705
+
706
+ Parameters
707
+ ----------
708
+ channel : str, optional
709
+ The channel name of the data to be fetched.
710
+
711
+ mode : str, optional, default='transition'
712
+ The bias mode type to indicate the range of bias
713
+ indices to return noise fit arrays for.
714
+ Options are: 'transition', 'normal', or 'sc'.
715
+
716
+ Returns
717
+ -------
718
+ noise_dict : dict
719
+ A dictionary with the following keys:
720
+ 's_ites', 's_iload', 's_itfn', 's_isquid', 's_itot',
721
+ 's_ptes', 's_pload', 's_ptfn', 's_psquid', 's_ptot',
722
+ 'freqs'
723
+ '''
724
+ if channel is None:
725
+ raise ValueError("You must specify a channel.")
726
+
727
+ valid_modes = ['transition', 'normal', 'sc']
728
+ if mode not in valid_modes:
729
+ raise ValueError(
730
+ f"'{mode}' is not a valid mode. "
731
+ f"Valid options are: {'transition',' normal',' sc'}.")
732
+
733
+ if channel not in self._noise_data:
734
+ raise ValueError(f"Channel '{channel}' not found in noise data.")
735
+
736
+ if 'sim' not in self._noise_data[channel] or mode not in self._noise_data[channel]['sim']:
737
+ raise ValueError(
738
+ f"Noise data for mode '{mode}' not available for channel '{channel}'. "
739
+ f"Have you run analyze_noise()?")
740
+
741
+ noise_dict = self._noise_data[channel]['sim'][mode]
742
+ return noise_dict
743
+
744
+
745
+ def fit_sc_noise(self, channels=None,
746
+ fit_range=(100, 1e5),
747
+ lgc_plot=False, xlims=None, ylims=None,
748
+ lgc_save_fig=False, save_path=None):
749
+ """
750
+ Function to fit the components of the SC Noise. Fits all SC
751
+ noise PSDs and stores tload
752
+
753
+ Parameters
754
+ ----------
755
+ fit_range : tuple, optional
756
+ The frequency range over which to do the fit.
757
+
758
+ Parameters
759
+ ----------
760
+ fit_range : tuple, optional
761
+ The frequency range over which to do the fit.
762
+ lgc_plot : bool, optional
763
+ If True, a plot of the fit is shown.
764
+ xlims : NoneType, tuple, optional
765
+ Limits to be passed to ax.set_xlim().
766
+ ylims : NoneType, tuple, optional
767
+ Limits to be passed to ax.set_ylim().
768
+ lgc_save_fig : bool, optional
769
+ If True, the figure is saved.
770
+ save_path : str
771
+ Directory to save data
772
+
773
+ Returns
774
+ -------
775
+ None
776
+
777
+ """
778
+
779
+ # check channels
780
+ if channels is None:
781
+ channels = self._noise_data.keys()
782
+ elif isinstance(channels, str):
783
+ channels = [channels]
784
+
785
+
786
+ for chan in channels:
787
+ if chan not in self._noise_data.keys():
788
+ raise ValueError(f'ERROR: No data for channel {chan} '
789
+ 'available. Set data first!')
790
+
791
+
792
+ # check normal parameters exists
793
+ if ('sc' not in self._noise_data[chan]
794
+ or 'psd' not in self._noise_data[chan]['sc']):
795
+ raise ValueError(f'ERROR: No SC psd for channel {chan} '
796
+ 'available. Set psd first!')
797
+
798
+
799
+ if ('sim' not in self._noise_data[chan]
800
+ or 'normal' not in self._noise_data[chan]['sim']):
801
+ raise ValueError(f'ERROR: SQUID noise should be first calculated '
802
+ f'from normal noise for channel {chan}')
803
+
804
+
805
+ if chan not in self._tc.keys():
806
+ raise ValueError(f'ERROR: No Tc for channel {chan} '
807
+ f'available. Set Tc first using function '
808
+ f'"set_tc({chan}, tc)"!')
809
+
810
+ # didv
811
+ if 'biasparams' not in self._noise_data[chan]:
812
+ raise ValueError(f'ERROR: No iv/didv data for channel {chan} '
813
+ 'available. Set didv data first!')
814
+
815
+ # inductance
816
+ if (chan not in self._inductance
817
+ or 'sc' not in self._inductance[chan]):
818
+ raise ValueError(f'ERROR: No inductance value for channel {chan} '
819
+ f'available. Set inductance first using function '
820
+ f'set_inductance("{chan}", L, "sc")')
821
+
822
+ if (lgc_plot
823
+ and (self._poles != 2 or self._poles != 3)):
824
+ print('WARNING: Unable to display SC noise model. Fit results (2 or 3-poles) '
825
+ 'required!')
826
+
827
+
828
+ # Loop channels and fit data
829
+ for chan in channels:
830
+
831
+ # psd array
832
+ psd = self._noise_data[chan]['sc']['psd']
833
+ psd_freqs = self._noise_data[chan]['sc']['psd_freqs']
834
+ is_folded = not np.any(psd_freqs<0)
835
+ if is_folded:
836
+ raise ValueError('ERROR: SC PSD should be two-sided')
837
+
838
+ # fold
839
+ fs = utils.estimate_sampling_rate(psd_freqs)
840
+ psd_fold_freqs, psd_fold = qp.foldpsd(psd, fs)
841
+
842
+ # Tc
843
+ tc = self._tc[chan]
844
+
845
+ # rn/rload
846
+ rn = float(self._noise_data[chan]['biasparams']['rn'])
847
+ rp = float(self._noise_data[chan]['biasparams']['rp'])
848
+ rshunt = None
849
+ if 'rsh' in self._noise_data[chan]['biasparams']:
850
+ rshunt = self._noise_data[chan]['biasparams']['rsh']
851
+ else:
852
+ rshunt = self._noise_data[chan]['biasparams']['rshunt']
853
+ rload = rp+rshunt
854
+
855
+ # inductance
856
+ L = self._inductance[chan]['sc']
857
+
858
+ # SQUID noise
859
+ squid_noise = self._noise_data[chan]['sim']['normal']['s_isquid']
860
+ squid_noise_freqs = self._noise_data[chan]['sim']['normal']['freqs']
861
+
862
+ # fold
863
+ fs_squid = utils.estimate_sampling_rate(squid_noise_freqs)
864
+ squid_noise_fold_freqs, squid_noise_fold = (
865
+ qp.foldpsd(squid_noise, fs_squid)
866
+ )
867
+
868
+ # fit range
869
+ ind_lower = (np.abs(psd_freqs - fit_range[0])).argmin()
870
+ ind_upper = (np.abs(psd_freqs - fit_range[1])).argmin()
871
+
872
+ xdata = psd_fold_freqs[ind_lower:ind_upper]
873
+ ydata = self._flatten_psd(psd_fold_freqs, psd_fold)[ind_lower:ind_upper]
874
+ squid_noise_fold_reduced = squid_noise_fold[ind_lower:ind_upper]
875
+
876
+ # save internal data to pass to fit function
877
+ # (no extra array argument allowed with lmfit)
878
+ self._s_isquid_for_fit = squid_noise_fold_reduced
879
+
880
+ # build model and fit
881
+ model = Model(self._sc_noise, independent_vars=['freqs'])
882
+ params = model.make_params(
883
+ tload=self._tload_guess,
884
+ rload=rload,
885
+ inductance=L
886
+ )
887
+
888
+ params['rload'].vary = False
889
+ params['inductance'].vary = False
890
+ result = model.fit(ydata, params, freqs=xdata)
891
+
892
+ # fit result
893
+ fitvals = result.values
894
+
895
+ # store values
896
+ self._noise_data[chan]['sc']['fit'] = fitvals
897
+
898
+ if self._verbose:
899
+ tload = format(fitvals['tload']*1e3, '.2f')
900
+ print(f'INFO: Fitted Tload from SC noise for '
901
+ f'channel {chan} = {tload} mK')
902
+
903
+
904
+ # if 2 or 3-poles fit not availble:
905
+ # unable to instantiate QETpy TESnoise
906
+ if (self._poles != 2 and self._poles != 3):
907
+ continue
908
+
909
+ # get didv results
910
+ didv_results = self.get_didv_results(chan, poles=self._poles)
911
+
912
+
913
+ # Instantiate Noise sim
914
+ noise_sim = qp.sim.TESnoise(
915
+ freqs=psd_freqs,
916
+ didv_result=didv_results,
917
+ tc=tc,
918
+ tload=fitvals['tload'],
919
+ tbath=self._tbath,
920
+ p0_manual=None,
921
+ n=5.0,
922
+ lgc_ballistic=True,
923
+ squid_noise_current=squid_noise,
924
+ squid_noise_current_freqs=squid_noise_freqs,
925
+ lgc_diagnostics=True)
926
+
927
+ # save
928
+ s_isquid = noise_sim.s_isquid(psd_freqs)
929
+ s_iloadsc = noise_sim.s_iloadsc(psd_freqs)
930
+ s_itotsc = noise_sim.s_itotsc(psd_freqs)
931
+
932
+ self._noise_data[chan]['sim']['sc']['s_itotsc'] = s_itotsc
933
+ self._noise_data[chan]['sim']['sc']['s_isquid'] = s_itotsc
934
+ self._noise_data[chan]['sim']['sc']['freqs'] = psd_freqs
935
+
936
+
937
+ # fold
938
+ _,s_isquid_fold = qp.foldpsd(s_isquid, fs)
939
+ _,s_iloadsc_fold = qp.foldpsd(s_iloadsc, fs)
940
+ _,s_itotsc_fold = qp.foldpsd(s_itotsc, fs)
941
+
942
+ # plot
943
+ if lgc_plot:
944
+
945
+ fig, ax = plt.subplots(figsize=(11, 6))
946
+ f = psd_fold_freqs
947
+
948
+ ax.grid(which="minor", linestyle="dotted", alpha=0.5)
949
+ ax.loglog(
950
+ f[1:], np.sqrt(psd_fold[1:]), alpha=0.5,
951
+ color='#8c564b', label='Raw Data',
952
+ )
953
+ ax.loglog(
954
+ f[1:], np.sqrt(s_isquid_fold[1:]),
955
+ color='#9467bd', label='Squid+Electronics Noise',
956
+ )
957
+ ax.loglog(
958
+ f[1:], np.sqrt(s_iloadsc_fold[1:]),
959
+ color='#ff7f0e', label='Load Noise',
960
+ )
961
+ ax.loglog(
962
+ f[1:], np.sqrt(s_itotsc_fold[1:]),
963
+ color='#d62728', label='Total Noise',
964
+ )
965
+ ax.legend()
966
+ ax.set_xlabel('Frequency [Hz]')
967
+ ax.set_ylabel('Input Referenced Current Noise '
968
+ r'[A/$\sqrt{\mathrm{Hz}}$]')
969
+ ax.set_title(f'{chan} Superconducting State noise')
970
+ ax.tick_params(which="both", direction="in",
971
+ right=True, top=True)
972
+
973
+ plt.show()
974
+
975
+
976
+ def analyze_noise(self, channels=None,
977
+ fit_range=(100, 1e5),
978
+ do_fit_normal_noise=False,
979
+ squiddc0=6e-12, squidpole0=200, squidn0=0.7,
980
+ lgc_plot=False,
981
+ xlims=None, ylims_current=None, ylims_power=None,
982
+ lgc_save_fig=False, save_path=None):
983
+ """
984
+ Function to analyze and plot noise PSD with all the theoretical noise
985
+ components (calculated from the didv fits).
986
+
987
+ lgc_plot : bool, optional
988
+ If True, a plot of the fit is shown.
989
+ xlims : NoneType, tuple, optional
990
+ Limits to be passed to ax.set_xlim().
991
+ ylims_current : NoneType, tuple, optional
992
+ Limits to be passed to ax.set_ylim() for the current noise
993
+ plots.
994
+ ylims_power : NoneType, tuple, optional
995
+ Limits to be passed to ax.set_ylim() for the power noise
996
+ plots.
997
+ lgc_save : bool, optional
998
+ If True, the figure is saved.
999
+ save_path : str
1000
+ Directory to save data
1001
+
1002
+ Returns
1003
+ -------
1004
+ None
1005
+ """
1006
+
1007
+ # check channels
1008
+ if channels is None:
1009
+ channels = self._noise_data.keys()
1010
+ elif isinstance(channels, str):
1011
+ channels = [channels]
1012
+
1013
+
1014
+ for chan in channels:
1015
+
1016
+ if chan not in self._noise_data.keys():
1017
+ raise ValueError(f'ERROR: No data for channel {chan} '
1018
+ 'available. Set data first!')
1019
+
1020
+ # check if either sc psd or fit available
1021
+ state = 'sc'
1022
+ if (state not in self._noise_data[chan]
1023
+ or ('psd' not in self._noise_data[chan][state]
1024
+ and 'fit' not in self._noise_data[chan][state])):
1025
+ raise ValueError(f'ERROR: No {state} psd or fit results for '
1026
+ f'channel {chan} available!')
1027
+
1028
+ # check if either normal psd or fit or squid noise available
1029
+ state = 'normal'
1030
+ if ('sim' not in self._noise_data[chan]
1031
+ and state not in self._noise_data[chan]):
1032
+ raise ValueError(f'ERROR: No {state} psd or fit results '
1033
+ f'or squid noise available for '
1034
+ f'channel {chan}!')
1035
+
1036
+ # check transition psd
1037
+ if ('transition' not in self._noise_data[chan]
1038
+ or 'psd' not in self._noise_data[chan]['transition']):
1039
+ raise ValueError(f'ERROR: No transition psd for '
1040
+ f'channel {chan} available!'
1041
+ f'Set PSD first!')
1042
+
1043
+ # check tc
1044
+ if chan not in self._tc.keys():
1045
+ raise ValueError(f'ERROR: No Tc for channel {chan} '
1046
+ f'available. Set Tc first using function '
1047
+ f'"set_tc({chan}, tc)"!')
1048
+
1049
+
1050
+ # check Gta
1051
+ #if chan not in self._gta.keys() and self._verbose:
1052
+ # print(f'WARNING: No Gta available for channel {chan}. '
1053
+ # f'By default Gta is calculated using 5*p0/tc.\n'
1054
+ # f'You can set more precise Gta using function '
1055
+ # f'"set_gta({chan}, gta)"!')
1056
+
1057
+ # didv
1058
+ #if 'biasparams' not in self._noise_data[chan]:
1059
+ # raise ValueError(f'ERROR: No iv/didv data for channel {chan} '
1060
+ # 'available. Set didv data first!')
1061
+
1062
+
1063
+ # loop channels and do SC/normal fit if needed
1064
+ for chan in channels:
1065
+
1066
+ # get parameters
1067
+ tc = self._tc[chan]
1068
+ rn = self._noise_data[chan]['biasparams']['rn']
1069
+ rp = self._noise_data[chan]['biasparams']['rp']
1070
+ rshunt = None
1071
+ if 'rsh' in self._noise_data[chan]['biasparams']:
1072
+ rshunt = self._noise_data[chan]['biasparams']['rsh']
1073
+ else:
1074
+ rshunt = self._noise_data[chan]['biasparams']['rshunt']
1075
+ rload = rp + rshunt
1076
+ L = self._noise_data[chan]['smallsignalparams']['L']
1077
+ p0 = self._noise_data[chan]['biasparams']['p0']
1078
+
1079
+ # SQUID noise from normal noise
1080
+ if ('sim' not in self._noise_data[chan]
1081
+ or not self._noise_data[chan]['sim']['normal']):
1082
+ self.calc_squid_noise(
1083
+ channels=chan,
1084
+ do_fit_normal_noise=do_fit_normal_noise,
1085
+ fit_range=fit_range,
1086
+ squiddc0=squiddc0, squidpole0=squidpole0,
1087
+ squidn0=squidn0,
1088
+ lgc_plot=lgc_plot, xlims=xlims, ylims=ylims,
1089
+ lgc_save_fig=lgc_save_fig, save_path=save_path
1090
+ )
1091
+
1092
+ # two sided SQUID noise
1093
+ squid_noise = self._noise_data[chan]['sim']['normal']['s_isquid']
1094
+ squid_noise_freqs = self._noise_data[chan]['sim']['normal']['freqs']
1095
+
1096
+ # Tload from SC fit
1097
+ if 'fit' not in self._noise_data[chan]['sc']:
1098
+ self.fit_sc_noise(channels=channels,
1099
+ fit_range=fit_range,
1100
+ lgc_plot=lgc_plot,
1101
+ xlims=xlims, ylims=ylims_current,
1102
+ lgc_save_fig=lgc_save_fig,
1103
+ save_path=save_path)
1104
+
1105
+ # fit results
1106
+ fit_sc = self._noise_data[chan]['sc']['fit']
1107
+
1108
+ # didv results
1109
+ didv_results = self.get_didv_results(chan, poles=self._poles)
1110
+
1111
+ # get psd in transition
1112
+ psd = self._noise_data[chan]['transition']['psd']
1113
+ psd_freqs = self._noise_data[chan]['transition']['psd_freqs']
1114
+
1115
+ # Instantiate TES Noise
1116
+ noise_sim = qp.sim.TESnoise(
1117
+ freqs=psd_freqs,
1118
+ didv_result=didv_results,
1119
+ tc=tc,
1120
+ tload=fit_sc['tload'],
1121
+ tbath=self._tbath,
1122
+ p0_manual=None,
1123
+ n=5.0,
1124
+ lgc_ballistic=True,
1125
+ squid_noise_current=squid_noise,
1126
+ squid_noise_current_freqs=squid_noise_freqs,
1127
+ lgc_diagnostics=False)
1128
+
1129
+ s_ites = noise_sim.s_ites(psd_freqs)
1130
+ s_iload = noise_sim.s_iload(psd_freqs)
1131
+ s_itfn = noise_sim.s_itfn(psd_freqs)
1132
+ s_isquid = noise_sim.s_isquid(psd_freqs)
1133
+ s_itot = noise_sim.s_itot(psd_freqs)
1134
+
1135
+ s_ptes = noise_sim.s_ptes(psd_freqs)
1136
+ s_pload = noise_sim.s_pload(psd_freqs)
1137
+ s_ptfn = noise_sim.s_ptfn(psd_freqs)
1138
+ s_psquid = noise_sim.s_psquid(psd_freqs)
1139
+ s_ptot = noise_sim.s_ptot(psd_freqs)
1140
+
1141
+
1142
+ # convert noise to power
1143
+ p_psd = psd*np.abs(noise_sim.dPdI)**2
1144
+
1145
+
1146
+ # save
1147
+ self._noise_data[chan]['sim']['transition'] = {
1148
+ 's_ites':s_ites, 's_iload':s_iload,
1149
+ 's_itfn':s_itfn, 's_isquid':s_isquid,
1150
+ 's_itot':s_itot,
1151
+ 's_ptes':s_ptes, 's_pload':s_pload,
1152
+ 's_ptfn':s_ptfn, 's_psquid':s_psquid,
1153
+ 's_ptot':s_ptot,
1154
+ 'freqs': psd_freqs,
1155
+ 's_idata': psd,
1156
+ 's_pdata': p_psd
1157
+ }
1158
+
1159
+
1160
+
1161
+ # plot
1162
+ if lgc_plot:
1163
+
1164
+ # fold
1165
+ fs = utils.estimate_sampling_rate(psd_freqs)
1166
+ psd_fold_freqs, psd_fold = qp.foldpsd(psd, fs)
1167
+ _,p_psd_fold = qp.foldpsd(p_psd, fs)
1168
+
1169
+ _,s_ites_fold = qp.foldpsd(s_ites, fs)
1170
+ _,s_iload_fold = qp.foldpsd(s_iload, fs)
1171
+ _,s_itfn_fold = qp.foldpsd(s_itfn, fs)
1172
+ _,s_isquid_fold = qp.foldpsd(s_isquid, fs)
1173
+ _,s_itot_fold = qp.foldpsd(s_itot, fs)
1174
+
1175
+ _,s_ptes_fold = qp.foldpsd(s_ptes, fs)
1176
+ _,s_pload_fold = qp.foldpsd(s_pload, fs)
1177
+ _,s_ptfn_fold = qp.foldpsd(s_ptfn, fs)
1178
+ _,s_psquid_fold = qp.foldpsd(s_psquid, fs)
1179
+ _,s_ptot_fold = qp.foldpsd(s_ptot, fs)
1180
+
1181
+ f = psd_fold_freqs
1182
+
1183
+ fig, ax = plt.subplots(figsize=(11, 6))
1184
+ ax.grid(which="major", linestyle='--')
1185
+ ax.grid(which="minor", linestyle="dotted", alpha=0.5)
1186
+ ax.tick_params(which="both", direction="in", right=True, top=True)
1187
+ ax.set_xlabel(r'Frequency [Hz]')
1188
+ ax.set_title(f'{chan} Current Noise')
1189
+
1190
+ ax.loglog(
1191
+ f[1:],
1192
+ np.sqrt(np.abs(s_ites_fold[1:])),
1193
+ color='#1f77b4',
1194
+ linewidth=1.5,
1195
+ label='TES Johnson Noise',
1196
+ )
1197
+
1198
+ ax.loglog(
1199
+ f[1:],
1200
+ np.sqrt(np.abs(s_iload_fold[1:])),
1201
+ color='#ff7f0e',
1202
+ linewidth=1.5,
1203
+ label='Load Noise',
1204
+ )
1205
+ ax.loglog(
1206
+ f[1:],
1207
+ np.sqrt(np.abs(s_itfn_fold[1:])),
1208
+ color='#2ca02c',
1209
+ linewidth=1.5,
1210
+ label='TFN Noise',
1211
+ )
1212
+ ax.loglog(
1213
+ f[1:],
1214
+ np.sqrt(np.abs(s_itot_fold[1:])),
1215
+ color='#d62728',
1216
+ linewidth=1.5,
1217
+ label='Total Noise',
1218
+ )
1219
+ ax.loglog(
1220
+ f[1:],
1221
+ np.sqrt(np.abs(s_isquid_fold[1:])),
1222
+ color='#9467bd',
1223
+ linewidth=1.5,
1224
+ label='Squid+Electronics Noise',
1225
+ )
1226
+
1227
+ ax.loglog(f[1:], np.sqrt(psd_fold[1:]),
1228
+ color='#8c564b', alpha=0.8,
1229
+ label='Raw Data')
1230
+
1231
+ ax.set_ylabel(r'TES Current Noise $[A/\sqrt{\mathrm{Hz}}]$')
1232
+
1233
+ lgd = plt.legend(loc='upper right')
1234
+
1235
+
1236
+ # power nouise
1237
+ fig, ax = plt.subplots(figsize=(11, 6))
1238
+ ax.grid(which="major", linestyle='--')
1239
+ ax.grid(which="minor", linestyle="dotted", alpha=0.5)
1240
+ ax.tick_params(which="both", direction="in", right=True, top=True)
1241
+ ax.set_xlabel(r'Frequency [Hz]')
1242
+ ax.set_title(f'{chan} Power Noise')
1243
+
1244
+ ax.loglog(
1245
+ f[1:],
1246
+ np.sqrt(np.abs(s_ptes_fold[1:])),
1247
+ color='#1f77b4',
1248
+ linewidth=1.5,
1249
+ label='TES Johnson Noise',
1250
+ )
1251
+
1252
+ ax.loglog(
1253
+ f[1:],
1254
+ np.sqrt(np.abs(s_pload_fold[1:])),
1255
+ color='#ff7f0e',
1256
+ linewidth=1.5,
1257
+ label='Load Noise',
1258
+ )
1259
+ ax.loglog(
1260
+ f[1:],
1261
+ np.sqrt(np.abs(s_ptfn_fold[1:])),
1262
+ color='#2ca02c',
1263
+ linewidth=1.5,
1264
+ label='TFN Noise',
1265
+ )
1266
+ ax.loglog(
1267
+ f[1:],
1268
+ np.sqrt(np.abs(s_ptot_fold[1:])),
1269
+ color='#d62728',
1270
+ linewidth=1.5,
1271
+ label='Total Noise',
1272
+ )
1273
+ ax.loglog(
1274
+ f[1:],
1275
+ np.sqrt(np.abs(s_psquid_fold[1:])),
1276
+ color='#9467bd',
1277
+ linewidth=1.5,
1278
+ label='Squid+Electronics Noise',
1279
+ )
1280
+
1281
+ ax.loglog(f[1:], np.sqrt(p_psd_fold[1:]), color='#8c564b',
1282
+ alpha=0.8, label='Raw Data')
1283
+
1284
+ ax.set_ylabel(r'Input Referenced Power Noise [W/$\sqrt{\mathrm{Hz}}$]')
1285
+
1286
+ lgd = plt.legend(loc='upper right')
1287
+
1288
+ plt.show()
1289
+
1290
+ def _flatten_psd(self, f, psd):
1291
+ """
1292
+ Helper function to smooth out all the spikes in a single-sided psd
1293
+ in order to more easily fit the SC and Normal state noise.
1294
+
1295
+ Parameters
1296
+ ----------
1297
+ f: ndarray
1298
+ Array of frequency values.
1299
+ psd : ndarray
1300
+ Array of one sided psd values.
1301
+
1302
+ Returns
1303
+ -------
1304
+ flattened_psd : ndarray
1305
+ Array of values of smoothed psd.
1306
+
1307
+ """
1308
+
1309
+ sav = np.zeros(psd.shape)
1310
+ div = int(.0025*len(psd))
1311
+ sav_lower = savgol_filter(psd[1:], 3, 1, mode='interp', deriv=0)
1312
+ sav_upper = savgol_filter(psd[1:], 45, 1, mode='interp', deriv=0)
1313
+ sav[1:div+1] = sav_lower[:div]
1314
+ sav[1+div:] = sav_upper[div:]
1315
+ sav[0] = psd[0]
1316
+ flattened_psd = qp.utils.make_decreasing(sav, x=f)
1317
+
1318
+ return flattened_psd
1319
+
1320
+ def _sc_noise(self, freqs, tload, rload, inductance):
1321
+ """
1322
+ Functional form of the Super Conducting state noise. Including
1323
+ the Johnson noise for the load resistor and the SQUID + downstream
1324
+ electronics noise. See qetpy.sim.TESnoise class for more info.
1325
+
1326
+ Parameters
1327
+ ----------
1328
+ freqs : array
1329
+ Array of frequencies.
1330
+ tload : float
1331
+ The temeperature of the load resistor in Kelvin.
1332
+ rload : float
1333
+ Value of the load resistor in Ohms.
1334
+ inductance : float
1335
+ The inductance of the TES line.
1336
+ s_isquid : array
1337
+ SQUID+Electronics noise array
1338
+
1339
+ Returns
1340
+ -------
1341
+ s_tot : array
1342
+ Array of values corresponding to the theoretical SC state noise.
1343
+
1344
+ """
1345
+
1346
+ omega = 2.0 * np.pi * freqs
1347
+ dIdVsc = 1.0 / (rload + 1.0j * omega * inductance)
1348
+ s_vload = 4.0 * constants.k * tload * rload * np.ones_like(freqs)
1349
+ s_iloadsc = s_vload * np.abs(dIdVsc)**2.0
1350
+ return s_iloadsc + self._s_isquid_for_fit
1351
+
1352
+
1353
+ def _normal_noise(self, freqs, squiddc, squidpole,
1354
+ squidn, rload, tload, rn, tc,
1355
+ inductance):
1356
+ """
1357
+ Functional form of the normal state noise. Including the Johnson
1358
+ noise for the load resistor, the Johnson noise for the TES, and the
1359
+ SQUID + downstream electronics noise. See qetpy.sim.TESnoise class
1360
+ for more info.
1361
+
1362
+ Parameters
1363
+ ----------
1364
+ freqs : array
1365
+ Array of frequencies.
1366
+ squiddc : float
1367
+ The average value for the white noise from the squid (ignoring
1368
+ the 1/f component).
1369
+ squidpole : float
1370
+ The knee for the 1/f component of the noise.
1371
+ squidn : float
1372
+ The factor for the 1/f^n noise.
1373
+ rload : float
1374
+ Value of the load resistor in Ohms.
1375
+ tload : float
1376
+ The temeperature of the load resistor in Kelvin.
1377
+ rn : float
1378
+ The value of the resistance of the TES when normal.
1379
+ tc : float
1380
+ The SC transistion temperature of the TES.
1381
+ inductance : float
1382
+ The inductance of the TES line.
1383
+
1384
+ Returns
1385
+ -------
1386
+ s_tot : array
1387
+ Array of values corresponding to the theoretical normal state noise.
1388
+
1389
+ """
1390
+
1391
+ omega = 2.0 * np.pi * freqs
1392
+ dIdVnormal = 1.0 / (rload + rn + 1.0j * omega * inductance)
1393
+
1394
+ # Johnson noise for the load resistor
1395
+ s_vload = 4.0 * constants.k * tload * rload * np.ones_like(freqs)
1396
+ s_iloadnormal = s_vload * np.abs(dIdVnormal)**2.0
1397
+
1398
+ # Johnson noise for the TES
1399
+ s_vtesnormal = 4.0 * constants.k * tc * rn * np.ones_like(freqs)
1400
+ s_itesnormal = s_vtesnormal * np.abs(dIdVnormal)**2.0
1401
+
1402
+ # SQUID + downstream electronics noise
1403
+ s_isquid = (squiddc * (1.0 + (squidpole / freqs)**squidn))**2.0
1404
+
1405
+ # total noise
1406
+ s_tot = s_iloadnormal + s_itesnormal + s_isquid
1407
+
1408
+ return s_tot