qcodespp 0.1.0__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 (82) hide show
  1. qcodespp/__init__.py +77 -0
  2. qcodespp/actions.py +237 -0
  3. qcodespp/data/__init__.py +0 -0
  4. qcodespp/data/data_array.py +555 -0
  5. qcodespp/data/data_set.py +926 -0
  6. qcodespp/data/format.py +338 -0
  7. qcodespp/data/gnuplot_format.py +425 -0
  8. qcodespp/data/hdf5_format.py +537 -0
  9. qcodespp/data/hdf5_format_hickle.py +63 -0
  10. qcodespp/data/io.py +241 -0
  11. qcodespp/data/location.py +171 -0
  12. qcodespp/instrument_drivers/Keysight/Keysight_34450A.py +9 -0
  13. qcodespp/instrument_drivers/Keysight/__init__.py +0 -0
  14. qcodespp/instrument_drivers/Keysight/private/Keysight_344xxA.py +456 -0
  15. qcodespp/instrument_drivers/Keysight/private/__init__.py +0 -0
  16. qcodespp/instrument_drivers/Lakeshore/Model_340.py +483 -0
  17. qcodespp/instrument_drivers/Lakeshore/__init__.py +0 -0
  18. qcodespp/instrument_drivers/QDevil/159fit_params_high_latest.json +386 -0
  19. qcodespp/instrument_drivers/QDevil/159fit_params_low_latest.json +386 -0
  20. qcodespp/instrument_drivers/QDevil/QDAC2.py +3229 -0
  21. qcodespp/instrument_drivers/QDevil/QDAC2_Array.py +300 -0
  22. qcodespp/instrument_drivers/QDevil/QSwitch_elab.py +652 -0
  23. qcodespp/instrument_drivers/QDevil/__init__.py +0 -0
  24. qcodespp/instrument_drivers/ZI/ZIHF2LI.py +415 -0
  25. qcodespp/instrument_drivers/ZI/ZIMFLI.py +764 -0
  26. qcodespp/instrument_drivers/ZI/ZIMFLI_sync.py +108 -0
  27. qcodespp/instrument_drivers/ZI/ZIUHFLI.py +2179 -0
  28. qcodespp/instrument_drivers/ZI/__init__.py +1 -0
  29. qcodespp/instrument_drivers/__init__.py +0 -0
  30. qcodespp/instrument_drivers/cryogenic/CryogenicSMS120C.py +599 -0
  31. qcodespp/instrument_drivers/cryogenic/__init__.py +0 -0
  32. qcodespp/instrument_drivers/instrument_mocks.py +379 -0
  33. qcodespp/instrument_drivers/multiplexer_instrument.py +287 -0
  34. qcodespp/instrument_drivers/national_instruments/PXI_4461.py +451 -0
  35. qcodespp/instrument_drivers/national_instruments/PXI_4462.py +290 -0
  36. qcodespp/instrument_drivers/national_instruments/PXI_6259.py +436 -0
  37. qcodespp/instrument_drivers/national_instruments/PXI_6733.py +230 -0
  38. qcodespp/instrument_drivers/national_instruments/PXI_6733_e.py +243 -0
  39. qcodespp/instrument_drivers/national_instruments/__init__.py +0 -0
  40. qcodespp/instrument_drivers/oxford/__init__.py +0 -0
  41. qcodespp/instrument_drivers/oxford/mercuryiPS_serial.py +658 -0
  42. qcodespp/instrument_drivers/oxford/triton.py +434 -0
  43. qcodespp/instrument_drivers/tektronix/Keithley_2000_Scan.py +65 -0
  44. qcodespp/instrument_drivers/tektronix/Keithley_2182a.py +190 -0
  45. qcodespp/instrument_drivers/tektronix/Keithley_2400.py +158 -0
  46. qcodespp/instrument_drivers/tektronix/Keithley_2450.py +242 -0
  47. qcodespp/instrument_drivers/tektronix/Keithley_2700.py +397 -0
  48. qcodespp/instrument_drivers/tektronix/Keithley_6500.py +161 -0
  49. qcodespp/instrument_drivers/tektronix/__init__.py +0 -0
  50. qcodespp/instrument_drivers/yokogawa/GS200.py +513 -0
  51. qcodespp/instrument_drivers/yokogawa/__init__.py +0 -0
  52. qcodespp/loops.py +1342 -0
  53. qcodespp/measure.py +310 -0
  54. qcodespp/parameters.py +311 -0
  55. qcodespp/plotting/RemotePlot.py +469 -0
  56. qcodespp/plotting/RemotePlotClient.py +367 -0
  57. qcodespp/plotting/RemoteQtPlotWidgets.py +871 -0
  58. qcodespp/plotting/__init__.py +0 -0
  59. qcodespp/plotting/analysis_tools.py +195 -0
  60. qcodespp/plotting/colors.py +135 -0
  61. qcodespp/plotting/offline/__init__.py +0 -0
  62. qcodespp/plotting/offline/datatypes.py +999 -0
  63. qcodespp/plotting/offline/design.py +906 -0
  64. qcodespp/plotting/offline/design.ui +1578 -0
  65. qcodespp/plotting/offline/editor.py +2966 -0
  66. qcodespp/plotting/offline/filters.py +561 -0
  67. qcodespp/plotting/offline/fits.py +1171 -0
  68. qcodespp/plotting/offline/helpers.py +288 -0
  69. qcodespp/plotting/offline/iconGadget.png +0 -0
  70. qcodespp/plotting/offline/main.py +55 -0
  71. qcodespp/plotting/offline/popupwindows.py +1848 -0
  72. qcodespp/plotting/offline/qcodes_pp_extension.py +328 -0
  73. qcodespp/plotting/offline/sidebars.py +1108 -0
  74. qcodespp/station.py +290 -0
  75. qcodespp/utils/__init__.py +0 -0
  76. qcodespp/utils/helpers.py +618 -0
  77. qcodespp/utils/visa_helpers.py +61 -0
  78. qcodespp/version.py +12 -0
  79. qcodespp-0.1.0.dist-info/METADATA +91 -0
  80. qcodespp-0.1.0.dist-info/RECORD +82 -0
  81. qcodespp-0.1.0.dist-info/WHEEL +4 -0
  82. qcodespp-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,1171 @@
1
+ import numpy as np
2
+ from lmfit import models as lmm
3
+ from lmfit import Model
4
+ from functools import partial
5
+
6
+ # Fit functions
7
+
8
+ # Polynomials and powers
9
+ def linear(xdata,ydata,p0=None,inputinfo=None):
10
+ """
11
+ Fit a linear model to the data.
12
+ Args:
13
+ xdata: x data to fit
14
+ ydata: y data to fit
15
+
16
+ Initial guesses and inputinfo not used.
17
+
18
+ Returns:
19
+ result: lmfit result object.
20
+ """
21
+
22
+ model=lmm.LinearModel()
23
+ params=model.guess(ydata, x=xdata)
24
+ result = model.fit(ydata, params, x=xdata)
25
+ return result
26
+
27
+ def polynomial(xdata,ydata,p0=None,inputinfo=2):
28
+ """Fit a polynomial model to the data.
29
+ Args:
30
+ xdata: x data to fit
31
+ ydata: y data to fit
32
+ inputinfo (int): degree of the polynomial to fit. Default 2
33
+
34
+ Initial guesses (p0) are not used.
35
+
36
+ Returns:
37
+ result: lmfit result object.
38
+ """
39
+
40
+ degree = int(inputinfo[0]) or int(inputinfo)
41
+ model=lmm.PolynomialModel(degree=degree)
42
+ params=model.guess(ydata, x=xdata)
43
+ result = model.fit(ydata, params, x=xdata)
44
+ return result
45
+
46
+ def fit_powerlaw(xdata,ydata, p0=None,inputinfo=[1,0]):
47
+ """
48
+ Fit a power law model to the data.
49
+
50
+ Args:
51
+ xdata: x data to fit
52
+ ydata: y data to fit
53
+ p0 (opt.): initial guesses for the parameters. Should be a list of floats.
54
+ inputinfo: a list containing the number of terms in the power law and whether to include a constant offset.
55
+ Returns:
56
+ result: lmfit result object.
57
+ """
58
+
59
+ order=inputinfo[0]
60
+ constant=inputinfo[1]
61
+
62
+ model= lmm.PowerLawModel(prefix='power0_')
63
+ params = model.make_params()
64
+ for i in range(int(order-1)):
65
+ newmodel = lmm.PowerLawModel(prefix=f'power{i+1}_')
66
+ pars = newmodel.make_params()
67
+ model = model + newmodel
68
+ params.update(pars)
69
+ if constant !=0:
70
+ bg = lmm.ConstantModel(prefix='bg_')
71
+ pars = bg.make_params()
72
+ pars['bg_c'].set(constant)
73
+ model = model + bg
74
+ params.update(pars)
75
+
76
+ if p0:
77
+ for i,key in enumerate(params.keys()):
78
+ params[key].set(float(p0[i]))
79
+ else:
80
+ for key in params.keys():
81
+ if 'amplitude' in key:
82
+ params[key].set(ydata.max()-ydata.min())
83
+ elif 'expo' in key:
84
+ params[key].set(1)
85
+
86
+ result = model.fit(ydata, params, x=xdata)
87
+ return result
88
+
89
+ def fit_exponentials(xdata,ydata, p0=None,inputinfo=[1,0]):
90
+ """
91
+ Fit one or more exponential terms to the data, with or without a constant offset.
92
+
93
+ Args:
94
+ xdata: x data to fit
95
+ ydata: y data to fit
96
+ p0 (opt.): initial guesses for the parameters. Should be a list of floats.
97
+ inputinfo: a list containing the number of terms in the exponential and whether to include a constant offset.
98
+
99
+ Returns:
100
+ result: lmfit result object.
101
+ """
102
+
103
+ order=inputinfo[0]
104
+ constant=inputinfo[1]
105
+
106
+ model= lmm.ExponentialModel(prefix='term0_')
107
+ params = model.make_params()
108
+ for i in range(int(order-1)):
109
+ newmodel = lmm.ExponentialModel(prefix=f'term{i+1}_')
110
+ pars = newmodel.make_params()
111
+ model = model + newmodel
112
+ params.update(pars)
113
+ if constant !=0:
114
+ bg = lmm.ConstantModel(prefix='bg_')
115
+ pars = bg.make_params()
116
+ pars['bg_c'].set(constant)
117
+ model = model + bg
118
+ params.update(pars)
119
+
120
+ if p0:
121
+ for i,key in enumerate(params.keys()):
122
+ params[key].set(float(p0[i]))
123
+
124
+ else:
125
+ amplitudes=ydata.max()-ydata.min()
126
+ decays=xdata.max()-xdata.min()
127
+ #more or less. Beats negative infinity.
128
+ for key in params.keys():
129
+ if 'amplitude' in key:
130
+ params[key].set(amplitudes)
131
+ elif 'decay' in key:
132
+ params[key].set(decays)
133
+
134
+ result = model.fit(ydata, params, x=xdata)
135
+ return result
136
+
137
+
138
+ #Peak fitting
139
+ def fit_lorgausstype(modeltype,xdata,ydata,p0=None,inputinfo=[1,0]):
140
+ '''
141
+ Fits x,y data with peaks characterised by amplitude, fwhm and position.
142
+
143
+ Args:
144
+ modeltype: lmfit model to use for fitting. Options are:
145
+ LorentzianModel, GaussianModel, LognormalModel, StudentsTModel, DampedOscillatorModel
146
+ xdata: x data to fit
147
+ ydata: y data to fit
148
+ p0 (opt.): initial guesses for the parameters. Should be a list of strings.
149
+ inputinfo: a list containing the number of peaks to fit and whether to include a constant offset.
150
+ Returns:
151
+ result: lmfit result object.
152
+ '''
153
+
154
+ numofpeaks=inputinfo[0]
155
+ usebackground=inputinfo[1]
156
+ sigmas=None
157
+ amplitudes=None
158
+
159
+ # if xdata[0]>xdata[-1]:
160
+ # xdata=xdata[::-1]
161
+ # ydata=ydata[::-1]
162
+
163
+ peakspacing=(xdata.max()-xdata.min())/numofpeaks
164
+ rough_peak_positions=[i*peakspacing+peakspacing/2+xdata.min() for i in range(int(numofpeaks))]
165
+
166
+ if p0: #Format should be list of strings: ['w w ... w','a a ... a','x x ... x','c']
167
+ sigmas=[float(par) for par in p0[0].split()]
168
+ amplitudes=[float(par) for par in p0[1].split()]
169
+ rough_peak_positions=[float(par) for par in p0[2].split()]
170
+ if usebackground==1:
171
+ try:
172
+ background=p0[3]
173
+ except:
174
+ background=0
175
+ else:
176
+ background=None
177
+
178
+ if sigmas==None: #Sigma will be much smaller than the peak spacing unless peaks are overlapping. However, starting with a low value seems to work even if overlapping, but not the other way around.
179
+ sigmas=[np.abs(xdata[-1]-xdata[0])/(12*numofpeaks) for i in range(int(numofpeaks))]
180
+ if amplitudes ==None: #Guess that the height will be close to the maximum value of the data: calculate amplitude based on gaussian.
181
+ height=ydata.max()-ydata.min()
182
+ amp=height*sigmas[0]*np.sqrt(2*np.pi)
183
+ # but to be honest i get better results just using the height...doesn't make sense but whatever
184
+ amplitudes=[height for i in range(int(numofpeaks))]
185
+
186
+ peakpos0=rough_peak_positions[0]
187
+ peakpositions=rough_peak_positions[1:]
188
+
189
+ def add_peak(prefix, center, amplitude, sigma):
190
+ peak = modeltype(prefix=prefix)
191
+ pars = peak.make_params()
192
+ pars[prefix + 'center'].set(center)
193
+ pars[prefix + 'amplitude'].set(amplitude)
194
+ pars[prefix + 'sigma'].set(sigma, min=0)
195
+ return peak, pars
196
+
197
+ model = modeltype(prefix='peak0_')
198
+ params = model.make_params()
199
+ params['peak0_center'].set(peakpos0)
200
+ params['peak0_amplitude'].set(amplitudes[0])
201
+ params['peak0_sigma'].set(sigmas[0], min=0)
202
+
203
+ for i, cen in enumerate(peakpositions):
204
+ peak, pars = add_peak('peak%d_' % (i+1), cen, amplitudes[i+1], sigmas[i+1])
205
+ model = model + peak
206
+ params.update(pars)
207
+
208
+ if background is not None:
209
+ bg = lmm.ConstantModel(prefix='bg_')
210
+ pars = bg.make_params()
211
+ pars['bg_c'].set(background)
212
+ model = model + bg
213
+ params.update(pars)
214
+
215
+ result = model.fit(ydata, params, x=xdata)
216
+ return result
217
+
218
+
219
+ # Function for lmfit peaks with four parameters.
220
+ def fit_voigttype(modeltype,xdata,ydata,p0=None,inputinfo=[1,0]):
221
+ """
222
+ Fits x,y data with peaks characterised by amplitude, fwhm, position and gamma.
223
+
224
+ Args:
225
+ modeltype: lmfit model to use for fitting. Options are:
226
+ VoigtModel, PseudoVoigtModel, BreitWignerModel, SplitLorentzianModel, ExponentialGaussianModel,
227
+ SkewedGaussianModel, MoffatModel, Pearson7Model, DampedHarmonicOscillatorModel, DoniachModel
228
+ xdata: x data to fit
229
+ ydata: y data to fit
230
+ p0 (opt.): initial guesses for the parameters. Should be a list of strings.
231
+ inputinfo: a list containing the number of peaks to fit and whether to include a constant offset.
232
+
233
+ Returns:
234
+ result: lmfit result object.
235
+ """
236
+ fourthparamdict={'SplitLorentzianModel':'sigma_r',
237
+ 'PseudoVoigtModel':'fraction',
238
+ 'MoffatModel':'beta',
239
+ 'Pearson7Model':'expon',
240
+ 'BreitWignerModel':'q'}
241
+ if modeltype.__name__ in fourthparamdict.keys():
242
+ fourthparam=fourthparamdict[modeltype.__name__]
243
+ else:
244
+ fourthparam='gamma'
245
+
246
+ numofpeaks=inputinfo[0]
247
+ usebackground=inputinfo[1]
248
+ sigmas=None
249
+ amplitudes=None
250
+ gammas=None
251
+
252
+ # if xdata[0]>xdata[-1]:
253
+ # xdata=xdata[::-1]
254
+ # ydata=ydata[::-1]
255
+ # flipped=True
256
+ # else:
257
+ # flipped=False
258
+
259
+ peakspacing=(xdata.max()-xdata.min())/numofpeaks
260
+ rough_peak_positions=[i*peakspacing+peakspacing/2+xdata.min() for i in range(int(numofpeaks))]
261
+
262
+ if p0: #Format should be list of strings: ['w w ... w','a a ... a','x x ... x','c']
263
+ sigmas=[float(par) for par in p0[0].split()]
264
+ amplitudes=[float(par) for par in p0[1].split()]
265
+ rough_peak_positions=[float(par) for par in p0[2].split()]
266
+ gammas=[float(par) for par in p0[3].split()]
267
+ if usebackground==1:
268
+ try:
269
+ background=p0[4]
270
+ except:
271
+ background=0
272
+ else:
273
+ background=None
274
+
275
+ if amplitudes ==None: #Guess that the amplitudes will be close to the maximum value of the data
276
+ amplitudes=[ydata.max()-ydata.min() for i in range(int(numofpeaks))]
277
+ if sigmas==None: #Sigma will be much smaller than the peak spacing unless peaks are overlapping. However, starting with a low value seems to work even if overlapping, but not the other way around.
278
+ sigmas=[np.abs(xdata[-1]-xdata[0])/(12*numofpeaks) for i in range(int(numofpeaks))]
279
+ if gammas==None: #God I have no idea
280
+ gammas=[0 for i in range(int(numofpeaks))]
281
+
282
+ peakpos0=rough_peak_positions[0]
283
+ peakpositions=rough_peak_positions[1:]
284
+
285
+ def add_peak(prefix, center, amplitude, sigma,gamma):
286
+ peak = modeltype(prefix=prefix)
287
+ pars = peak.make_params()
288
+ pars[prefix + 'center'].set(center)
289
+ pars[prefix + 'amplitude'].set(amplitude)
290
+ pars[prefix + 'sigma'].set(sigma, min=0)
291
+ pars[prefix + fourthparam].set(gamma)
292
+ return peak, pars
293
+
294
+ model = modeltype(prefix='peak0_')
295
+ params = model.make_params()
296
+ params['peak0_center'].set(peakpos0)
297
+ params['peak0_amplitude'].set(amplitudes[0])
298
+ params['peak0_sigma'].set(sigmas[0], min=0)
299
+ params['peak0_'+fourthparam].set(gammas[0])
300
+
301
+ for i, cen in enumerate(peakpositions):
302
+ peak, pars = add_peak('peak%d_' % (i+1), cen, amplitudes[i+1], sigmas[i+1], gammas[i+1])
303
+ model = model + peak
304
+ params.update(pars)
305
+
306
+ if background is not None:
307
+ bg = lmm.ConstantModel(prefix='bg_')
308
+ pars = bg.make_params()
309
+ pars['bg_c'].set(background)
310
+ model = model + bg
311
+ params.update(pars)
312
+
313
+ result = model.fit(ydata, params, x=xdata)
314
+ return result
315
+
316
+ # Function for lmfit peaks with five parameters.
317
+ def fit_skewedpeaks(modeltype,xdata,ydata,p0=None,inputinfo=[1,0]):
318
+ """
319
+ Fits x,y data with peaks characterised by amplitude, fwhm, position, gamma and skew.
320
+ Args:
321
+ modeltype: lmfit model to use for fitting. Options are:
322
+ Pearson4Model, SkewedVoigtModel
323
+ xdata: x data to fit
324
+ ydata: y data to fit
325
+ p0 (opt.): initial guesses for the parameters. Should be a list of strings.
326
+ inputinfo: a list containing the number of peaks to fit and whether to include a constant offset.
327
+ Returns:
328
+ result: lmfit result object.
329
+ """
330
+ fourthparamdict={'Pearson4Model':'expon'}
331
+ if modeltype.__name__ in fourthparamdict.keys():
332
+ fourthparam=fourthparamdict[modeltype.__name__]
333
+ else:
334
+ fourthparam='gamma'
335
+ #Fifth parameter is always 'skew'
336
+
337
+ numofpeaks=inputinfo[0]
338
+ usebackground=inputinfo[1]
339
+ sigmas=None
340
+ amplitudes=None
341
+ gammas=None
342
+ skews=None
343
+
344
+ # if xdata[0]>xdata[-1]:
345
+ # xdata=xdata[::-1]
346
+ # ydata=ydata[::-1]
347
+
348
+ peakspacing=(xdata.max()-xdata.min())/numofpeaks
349
+ rough_peak_positions=[i*peakspacing+peakspacing/2+xdata.min() for i in range(int(numofpeaks))]
350
+
351
+ if p0: #Format should be list of strings: ['w w ... w','a a ... a','x x ... x','c']
352
+ sigmas=[float(par) for par in p0[0].split()]
353
+ amplitudes=[float(par) for par in p0[1].split()]
354
+ rough_peak_positions=[float(par) for par in p0[2].split()]
355
+ gammas=[float(par) for par in p0[3].split()]
356
+ skews=[float(par) for par in p0[4].split()]
357
+ if usebackground==1:
358
+ try:
359
+ background=p0[5]
360
+ except:
361
+ background=0
362
+ else:
363
+ background=None
364
+
365
+ if amplitudes ==None: #Guess that the amplitudes will be close to the maximum value of the data
366
+ amplitudes=[ydata.max()-ydata.min() for i in range(int(numofpeaks))]
367
+ if sigmas==None: #Sigma will be much smaller than the peak spacing unless peaks are overlapping. However, starting with a low value seems to work even if overlapping, but not the other way around.
368
+ sigmas=[np.abs(xdata[-1]-xdata[0])/(12*numofpeaks) for i in range(int(numofpeaks))]
369
+ if gammas==None:
370
+ gammas=[0 for i in range(int(numofpeaks))]
371
+ if skews==None:
372
+ skews=[1 for i in range(int(numofpeaks))]
373
+
374
+ peakpos0=rough_peak_positions[0]
375
+ peakpositions=rough_peak_positions[1:]
376
+
377
+ def add_peak(prefix, center, amplitude, sigma,gamma,skew):
378
+ peak = modeltype(prefix=prefix)
379
+ pars = peak.make_params()
380
+ pars[prefix + 'center'].set(center)
381
+ pars[prefix + 'amplitude'].set(amplitude)
382
+ pars[prefix + 'sigma'].set(sigma, min=0)
383
+ pars[prefix + fourthparam].set(gamma)
384
+ pars[prefix + 'skew'].set(skew)
385
+ return peak, pars
386
+
387
+ model = modeltype(prefix='peak0_')
388
+ params = model.make_params()
389
+ params['peak0_center'].set(peakpos0)
390
+ params['peak0_amplitude'].set(amplitudes[0])
391
+ params['peak0_sigma'].set(sigmas[0], min=0)
392
+ params['peak0_'+fourthparam].set(gammas[0])
393
+ params['peak0_skew'].set(skews[0])
394
+
395
+ for i, cen in enumerate(peakpositions):
396
+ peak, pars = add_peak('peak%d_' % (i+1), cen, amplitudes[i+1], sigmas[i+1], gammas[i+1],skews[i+1])
397
+ model = model + peak
398
+ params.update(pars)
399
+
400
+ if background is not None:
401
+ bg = lmm.ConstantModel(prefix='bg_')
402
+ pars = bg.make_params()
403
+ pars['bg_c'].set(background)
404
+ model = model + bg
405
+ params.update(pars)
406
+
407
+ result = model.fit(ydata, params, x=xdata)
408
+ return result
409
+
410
+ #Multiple sine wave fitting
411
+ def fit_sines(xdata,ydata,p0=None,inputinfo=[1,0]):
412
+ """
413
+ Fits x,y data with multiple sine waves characterised by amplitude, frequency, phase and position.
414
+ Args:
415
+ xdata: x data to fit
416
+ ydata: y data to fit
417
+ p0 (opt.): initial guesses for the parameters. Should be a list of strings.
418
+ inputinfo: a list containing the number of sine waves to fit and whether to include a constant offset.
419
+ Returns:
420
+ result: lmfit result object.
421
+ """
422
+
423
+ modeltype=lmm.SineModel
424
+ #Fits x,y data with peaks characterised by amplitude, fwhm and position.
425
+ #Custom starting amplitudes (proportional to height) and sigmas (proportional to width) may also be given
426
+ #Returns the entire lmfit result.
427
+ numofwaves=inputinfo[0]
428
+ usebackground=inputinfo[1]
429
+ amps=None
430
+ freqs=None
431
+ phases=None
432
+
433
+ # if xdata[0]>xdata[-1]:
434
+ # xdata=xdata[::-1]
435
+ # ydata=ydata[::-1]
436
+
437
+ if p0: #Format should be list of strings: ['A A ... A','f f ... f','ph ph ... ph','c']
438
+ amps=[float(par) for par in p0[0].split()]
439
+ freqs=[float(par) for par in p0[1].split()]
440
+ phases=[float(par) for par in p0[2].split()]
441
+ if usebackground==1:
442
+ try:
443
+ background=p0[3]
444
+ except:
445
+ background=0
446
+ else:
447
+ background=None
448
+
449
+ if amps ==None: #Guess that the amplitudes will be close to the maximum minus minimum
450
+ amps=[ydata.max()-ydata.min() for i in range(int(numofwaves))]
451
+ if freqs==None: #Guess that the user probably provides somewhere between 1 and 20 periods for fitting.
452
+ freqs=[10/np.abs(xdata[-1]-xdata[0]) for i in range(int(numofwaves))]
453
+ if phases==None: # The phases (surely?!) just have to be something sensible in units of radians
454
+ phases=[0.1 for i in range(int(numofwaves))]
455
+
456
+ def add_wave(prefix, amplitude, frequency, phase):
457
+ wave = modeltype(prefix=prefix)
458
+ pars = wave.make_params()
459
+ pars[prefix + 'amplitude'].set(amplitude)
460
+ pars[prefix + 'frequency'].set(frequency)
461
+ pars[prefix + 'shift'].set(phase)
462
+ return wave, pars
463
+
464
+ model = modeltype(prefix='wave0_')
465
+ params = model.make_params()
466
+ params['wave0_amplitude'].set(amps[0])
467
+ params['wave0_frequency'].set(freqs[0])
468
+ params['wave0_shift'].set(phases[0])
469
+
470
+ new_amps=amps[1:]
471
+
472
+ for i, amp in enumerate(new_amps):
473
+ wave, pars = add_wave('wave%d_' % (i+1), amp, freqs[i+1], phases[i+1])
474
+ model = model + wave
475
+ params.update(pars)
476
+
477
+ if background is not None:
478
+ bg = lmm.ConstantModel(prefix='bg_')
479
+ pars = bg.make_params()
480
+ pars['bg_c'].set(background)
481
+ model = model + bg
482
+ params.update(pars)
483
+
484
+ result = model.fit(ydata, params, x=xdata)
485
+ return result
486
+
487
+
488
+ #Fit a thermal distribution of MB, FD or BE type.
489
+ def thermal_fit(modeltype,xdata,ydata,p0=None,inputinfo=None):
490
+ """
491
+ Fits x,y data with a thermal distribution characterised by temperature and amplitude.
492
+ Args:
493
+ modeltype (str): the type of thermal distribution to fit. Options are:
494
+ maxwell, fermi, bose.
495
+ xdata: x data to fit
496
+ ydata: y data to fit
497
+ p0 (opt.): initial guesses for the parameters. Should be a list of floats.
498
+ inputinfo: not used.
499
+ Returns:
500
+ result: lmfit result object.
501
+ """
502
+
503
+
504
+ model=lmm.ThermalDistributionModel(form=modeltype)
505
+
506
+ if p0:
507
+ params=model.make_params()
508
+ for i,key in enumerate(params.keys()):
509
+ params[key]=float(p0[i])
510
+ else:
511
+ params=model.guess(ydata,x=xdata)
512
+ result=model.fit(ydata,params,x=xdata)
513
+ return result
514
+
515
+
516
+ #Fit a step function.
517
+ def step_fit(modeltype,xdata,ydata,p0=None,inputinfo=None):
518
+ """
519
+ Fits x,y data with a step function characterised by amplitude, center and sigma.
520
+ Args:
521
+ modeltype (str): the type of step function to fit. Options are:
522
+ linear, arctan, erf, logistic
523
+
524
+ xdata: x data to fit
525
+ ydata: y data to fit
526
+ p0 (opt.): initial guesses for the parameters. Should be a list of floats.
527
+ inputinfo: not used.
528
+
529
+ Returns:
530
+ result: lmfit result object.
531
+ """
532
+ # if xdata[0]>xdata[-1]:
533
+ # xdata=xdata[::-1]
534
+ # ydata=ydata[::-1]
535
+ model=lmm.StepModel(form=modeltype)
536
+ params=model.make_params()
537
+ if p0:
538
+ for i,key in enumerate(params.keys()):
539
+ params[key]=float(p0[i])
540
+ else:
541
+ if ydata[-1]>ydata[0]:
542
+ params['amplitude'].set(ydata.max())
543
+ else:
544
+ params['amplitude'].set(ydata.min())
545
+ params['center'].set((xdata[0]+xdata[-1])/2)
546
+ params['sigma'].set((xdata[-1]-xdata[0])/10)
547
+
548
+ result=model.fit(ydata,params,x=xdata)
549
+ return result
550
+
551
+ #Fit a rectangle function.
552
+ def rectangle_fit(modeltype,xdata,ydata,p0=None,inputinfo=None):
553
+
554
+ """
555
+ Fits x,y data with a rectangle function characterised by amplitude, center1, center2, sigma1 and sigma2.
556
+ Args:
557
+ modeltype (str): the type of rectangle function to fit. Options are:
558
+ linear, arctan, erf, logistic
559
+ xdata: x data to fit
560
+ ydata: y data to fit
561
+ p0 (opt.): initial guesses for the parameters. Should be a list of floats.
562
+ inputinfo: not used.
563
+ Returns:
564
+ result: lmfit result object.
565
+ """
566
+ # if xdata[0]>xdata[-1]:
567
+ # xdata=xdata[::-1]
568
+ # ydata=ydata[::-1]
569
+ model=lmm.RectangleModel(form=modeltype)
570
+ params=model.make_params()
571
+ if p0:
572
+ for i,key in enumerate(params.keys()):
573
+ params[key]=float(p0[i])
574
+ else:
575
+ if ydata[-1]>ydata[0]:
576
+ params['amplitude'].set(ydata.max())
577
+ else:
578
+ params['amplitude'].set(ydata.min())
579
+ params['center1'].set((3*xdata[0]+xdata[-1])/4)
580
+ params['center2'].set((xdata[0]+3*xdata[-1])/4)
581
+ params['sigma1'].set((xdata[-1]-xdata[0])/20)
582
+ params['sigma2'].set((xdata[-1]-xdata[0])/20)
583
+
584
+ result=model.fit(ydata,params,x=xdata)
585
+ return result
586
+
587
+
588
+ # Arbitrary expressions get fitted using the below:
589
+ def expression_fit(xdata,ydata,p0,inputinfo):
590
+ """ Fits x,y data with an arbitrary expression using lmfit's ExpressionModel.
591
+ Args:
592
+ xdata: x data to fit
593
+ ydata: y data to fit
594
+ p0 (opt.): initial guesses for the parameters. Should be a list of strings.
595
+ Format should be: ['x0=x0_value', 'G0=G0_value', ...]
596
+ inputinfo: The expression to fit, as a string. Should be a valid lmfit expression.
597
+ Returns:
598
+ result: lmfit result object.
599
+ """
600
+
601
+ model=lmm.ExpressionModel(inputinfo)
602
+ params=model.make_params()
603
+ for i,whatever in enumerate(p0):
604
+ params[whatever.split('=')[0].strip()].set(value=float(whatever.split('=')[1]))
605
+
606
+ result=model.fit(ydata,params,x=xdata)
607
+ return result
608
+
609
+ #Custom fits
610
+ def QD_fit(xdata,ydata,p0=None,inputinfo=[1,0.01]):
611
+ """
612
+ Fits one or more Coulomb blockade peaks in the limit of low tunnel coupling: 'G = G_0 * cosh(e*alpha*(Vg - V_0)/(2*k_B*T))**(-2)
613
+
614
+ Args:
615
+ xdata: x data to fit
616
+ ydata: y data to fit
617
+ p0 (opt.): initial guesses for the parameters. Should be a list of strings.
618
+ Format should be: ['x0 x0 ... x0','G0 G0 ... G0','T']
619
+ inputinfo: a list containing the number of peaks to fit and the alpha parameter.
620
+ Format should be: [numofpeaks, alpha]
621
+ Returns:
622
+ result: lmfit result object.
623
+ """
624
+ numofpeaks=inputinfo[0]
625
+ alpha=inputinfo[1]
626
+ G0s=None
627
+
628
+ if p0: #Format should be list of strings: ['x0 x0 ... x0','G0 G0 ... G0','T']
629
+ rough_peak_positions=[float(par) for par in p0[0].split()]
630
+ G0s=[float(par) for par in p0[1].split()]
631
+ T=[float(p0[2])]
632
+ else:
633
+ peakspacing=(xdata.max()-xdata.min())/numofpeaks
634
+ rough_peak_positions=[i*peakspacing+peakspacing/2+xdata.min() for i in range(int(numofpeaks))]
635
+ G0s=[ydata.max()-ydata.min() for i in range(int(numofpeaks))]
636
+ T=0.01
637
+
638
+ boltzmann = 1.38064852e-23 # J/K
639
+ def QD_model(x, x0, G0, T):
640
+ return G0 * np.cosh(1.60217663e-19*alpha * (x - x0) / (2 * boltzmann * T))**(-2)
641
+ model=Model(QD_model,prefix='peak0_')
642
+ params=model.make_params()
643
+
644
+ params['peak0_G0'].set(value=G0s[0])
645
+ params['peak0_T'].set(value=T)
646
+ params['peak0_x0'].set(value=rough_peak_positions[0])
647
+
648
+ def add_peak(prefix, x0, G0, T):
649
+ peak = Model(QD_model,prefix=prefix)
650
+ pars = peak.make_params()
651
+ pars[prefix + 'x0'].set(x0)
652
+ pars[prefix + 'G0'].set(G0)
653
+ pars[prefix + 'T'].set(T)
654
+ return peak, pars
655
+
656
+ for i, cen in enumerate(rough_peak_positions[1:]):
657
+ peak, pars = add_peak('peak%d_' % (i+1), cen, G0s[i+1], T)
658
+ model = model + peak
659
+ params.update(pars)
660
+
661
+ result=model.fit(ydata,params,x=xdata)
662
+ return result
663
+
664
+ def FET_mobility(xdata,ydata,p0=None,inputinfo=None):
665
+ """
666
+ Fits x,y data with a FET mobility model: '1/(R_s + L**2/(C*mu*(x-V_th)))'
667
+ Args:
668
+ xdata: x data to fit
669
+ ydata: y data to fit
670
+ p0 (opt.): List of initial guesses for the parameters.
671
+ Format should be: [mu, V_th, R_s]
672
+ inputinfo: a list containing the capacitance C and device length L.
673
+ Format should be: [C, L]
674
+ Returns:
675
+ result: lmfit result object.
676
+ """
677
+
678
+ if inputinfo is None or len(inputinfo) < 2:
679
+ raise ValueError("inputinfo must contain values for capacitance and device Length: [C, L]")
680
+ C=float(inputinfo[0])
681
+ L=float(inputinfo[1])
682
+ def FET_model(x, mu, V_th, R_s):
683
+ return 1/(R_s + L**2/(C*mu*(x-V_th)))
684
+ model=Model(FET_model)
685
+ params=model.make_params()
686
+ if p0:
687
+ params['mu'].set(value=float(p0[0]))
688
+ params['V_th'].set(value=float(p0[1]))
689
+ params['R_s'].set(value=float(p0[2]))
690
+ else:
691
+ params['mu'].set(L**2*(ydata.max()-ydata.min())/(C*(xdata.max()-xdata.min())))
692
+ params['V_th'].set(xdata.min())
693
+ params['R_s'].set(1/ydata.max())
694
+
695
+ result=model.fit(ydata,params,x=xdata)
696
+ return result
697
+
698
+ def dynes_fit(xdata,ydata,p0=None,inputinfo=None):
699
+ """
700
+ Fits x,y data with a Dynes model for a superconducting gap: 'G_N * abs((e*x - i*gamma*e)/(sqrt((e*x - i*gamma*e)**2 - (delta*e)**2)))'
701
+
702
+ Args:
703
+ xdata: x data to fit
704
+ ydata: y data to fit
705
+ p0 (opt.): initial guesses for the parameters. Should be a list of floats.
706
+ Format should be: [G_N, gamma, delta]
707
+ inputinfo: not used.
708
+ Returns:
709
+ result: lmfit result object.
710
+ """
711
+ electron=1.60217663e-19
712
+ def dynes_model(x,x0, G_N, gamma, delta):
713
+ return np.abs(G_N*((electron*(x-x0)-1j*gamma*electron)/np.sqrt((electron*(x-x0)-1j*gamma*electron)**2-(delta*electron)**2)).real)
714
+ model=Model(dynes_model)
715
+ params=model.make_params()
716
+ if p0:
717
+ params['x0'].set(value=float(p0[0]))
718
+ params['G_N'].set(value=float(p0[1]))
719
+ params['gamma'].set(value=float(p0[2]))
720
+ params['delta'].set(value=float(p0[3]))
721
+ else:
722
+ params['x0'].set(value=(np.max(xdata)+np.min(xdata))/2)
723
+ params['G_N'].set(value=(np.max(ydata)-np.min(ydata))/2)
724
+ params['gamma'].set(value=(np.max(xdata)-np.min(xdata))/200)
725
+ params['delta'].set(value=(np.max(xdata)-np.min(xdata))/2)
726
+ result=model.fit(ydata,params,x=xdata)
727
+ return result
728
+
729
+ def ramsey_fit(xdata,ydata,p0,inputinfo):
730
+ '''
731
+ Fits x,y data with a Ramsey model for T2 of a qubit: 'A*cos(2*pi*f*x + phi)*exp(-x/T2) + B + C*x'
732
+
733
+ Args:
734
+ xdata: x data to fit
735
+ ydata: y data to fit
736
+ p0 (opt.): initial guesses for the parameters. Should be a list of floats.
737
+ Format should be: [A, B, C, f, phi, T2]
738
+ inputinfo: not used.
739
+ Returns:
740
+ result: lmfit result object.
741
+ '''
742
+ def ramsey_model(x, A, B, C, f, phi, T2):
743
+ return A*np.cos(2*np.pi*f*x + phi)*np.exp(-x/T2)+B+C*x
744
+
745
+ model=Model(ramsey_model)
746
+ params=model.make_params()
747
+ if p0:
748
+ params['A'].set(value=float(p0[0]))
749
+ params['B'].set(value=float(p0[1]))
750
+ params['C'].set(value=float(p0[2]))
751
+ params['f'].set(value=float(p0[3]))
752
+ params['phi'].set(value=float(p0[4]))
753
+ params['T2'].set(value=float(p0[5]))
754
+ else:
755
+ params['A'].set(value=(ydata.max()-ydata.min())/2)
756
+ params['B'].set(value=ydata.min())
757
+ params['C'].set(value=(ydata.max()-ydata.min())/100)
758
+ params['f'].set(value=1/(xdata.max()-xdata.min()))
759
+ params['phi'].set(value=0)
760
+ params['T2'].set(value=(xdata.max()-xdata.min())/10)
761
+
762
+ result=model.fit(ydata,params,x=xdata)
763
+ return result
764
+
765
+ def RCSJfit(xdata,ydata,p0=None,inputinfo=None):
766
+ '''
767
+ Fits the differential conductance, dI/dV of a Josephson junction vs the dc voltage, Vdc applied across it.
768
+
769
+ The model is fitted to the derivative of:
770
+
771
+ (Rj/(Rj+Rc))*(jc*Im(I_(1-in(v)(B))/I_(-in(V)(B))) + (Vdc-Vdc_0)/Rj)
772
+
773
+ where:
774
+ - Rj is the junction resistance,
775
+ - Rc is the shunt resistance,
776
+ - jc is the critical current density,
777
+ - Vdc_0 is the offset voltage,
778
+ - I_(1-in(v)(B)) and I_(-in(V)(B)) are modified Bessel functions of the first kind.
779
+ - n(V) = hbar*(Vdc-Vdc_0)/(2*e*Rc*k_B*T), where hbar is the reduced Planck's constant, e is the electron charge, k_B is the Boltzmann constant, and T is the temperature
780
+ - B = jc*hbar/(2*e*k_B*T)
781
+
782
+ See e.g. https://www.science.org/doi/suppl/10.1126/sciadv.aav1235/suppl_file/aav1235_sm.pdf, page 14 onwards.
783
+
784
+ Args:
785
+ xdata: x data to fit (Vdc)
786
+ ydata: y data to fit (dI/dV)
787
+ p0 (opt.): Initial guesses for jc, Rj, Rc, Vdc_0 and c (the constant offset).
788
+ Format should be: [jc, Rj, Rc, Vdc_0, c]
789
+ inputinfo: A list containing the temperature in Kelvin. If not provided, defaults to 0.02 K.
790
+ Format should be: [T]
791
+ '''
792
+ try:
793
+ from mpmath import besseli
794
+ except ImportError:
795
+ print("The mpmath library is required for RCSJ fitting. Please install it using 'pip install mpmath'.")
796
+
797
+ T=inputinfo[0] if inputinfo else 0.02 # Temperature in Kelvin
798
+ def besselfuncdiff(x, jc, Rj, Rc, x_0):
799
+ hbar=1.0545718e-34
800
+ electron=1.60217662e-19
801
+ boltzmann=1.38064852e-23
802
+ bess=np.zeros(np.size(x))
803
+
804
+ for i in range(np.size(x)):
805
+ eta=hbar*(x[i]-x_0)/(2*electron*Rc*boltzmann*T)
806
+ beta=jc*hbar/(2*electron*boltzmann*T)
807
+ bess1 = besseli(1-eta*1j,beta)
808
+ bess2 = besseli(-eta*1j,beta)
809
+ imagine = (bess1/bess2).imag
810
+ bess[i]=(Rj/(Rj+Rc))*(jc*imagine+((x[i]-x_0)/Rj))
811
+ return np.gradient(bess,x)
812
+
813
+ if p0:
814
+ jc_init= float(p0[0])
815
+ Rj_init= float(p0[1])
816
+ Rc_init= float(p0[2])
817
+ x_0_init= float(p0[3])
818
+ c_init=float(p0[4])
819
+ else:
820
+ x_0_init=np.mean(xdata)
821
+ jc_init=1e-9 #honestly have no idea how to provide good initial guesses for this model based only on the data. I think the model is too complex to ever do so.
822
+ Rj_init=1e10
823
+ Rc_init=3600
824
+ c_init=np.min(ydata)
825
+
826
+ model = Model(besselfuncdiff)+lmm.ConstantModel()
827
+ params = model.make_params(jc=jc_init,Rj=Rj_init, Rc=Rc_init, x_0=x_0_init,c=c_init)#, bkg_c=conductance[-1])
828
+
829
+ result = model.fit(ydata, params, x=xdata)
830
+ return result
831
+
832
+ def statistics(xdata,ydata,p0,inputinfo):
833
+ # Not really fitting; just returns the statistical information specified in inputinfo.
834
+ if 'percentile' in inputinfo:
835
+ percentiles=[float(p0[i]) for i in range(len(p0))] if p0 else [1,5,10,25,50,75,90,95,99]
836
+ elif 'average' in inputinfo:
837
+ weights=[float(p0[i]) for i in range(len(p0))] if p0 else None
838
+ elif inputinfo == 'all':
839
+ percentiles=[1,5,10,25,50,75,90,95,99]
840
+ weights=None
841
+
842
+ function_dict={
843
+ 'mean':np.mean,
844
+ 'average':lambda x: np.average(x,weights=weights),
845
+ 'std':np.std,
846
+ 'var':np.var,
847
+ 'median':np.median,
848
+ 'min':np.min,
849
+ 'max':np.max,
850
+ 'range':lambda x: np.max(x)-np.min(x),
851
+ 'sum':np.sum,
852
+ 'skew':lambda x: np.mean((x-np.mean(x))**3)/np.std(x)**3,
853
+ 'percentile':lambda x: np.percentile(x,percentiles),
854
+ 'autocorrelation': lambda x: np.correlate(x,x,mode='full')[len(x)-1:],
855
+ 'autocorrelation_norm': lambda x: np.correlate(x,x,mode='full')[len(x)-1:]/np.max(np.correlate(x,x,mode='full'))
856
+ }
857
+
858
+ result={}
859
+ if inputinfo == 'all':
860
+ functions=function_dict.keys()
861
+ elif inputinfo == 'all1d':
862
+ functions=['mean','std','var','median','min','max','range','sum','skew']
863
+ else:
864
+ functions=inputinfo.split(',')
865
+ for function in functions:
866
+ if function not in ['percentile','autocorrelation','autocorrelation_norm']:
867
+ result[function]=float(function_dict[function](ydata))
868
+ else:
869
+ if function == 'percentile':
870
+ result[function]=function_dict[function](ydata)
871
+ result['percentiles']=percentiles
872
+ else:
873
+ result[function]=function_dict[function](ydata)
874
+ result['xdata']=xdata
875
+ result['ydata']=ydata
876
+ return result
877
+
878
+
879
+ # Dictionary for storing info about the fit functions used in this module.
880
+ functions = {}
881
+ functions['Polynomials and powers'] = {'Linear': {'function': linear},
882
+ 'Polynomial': {'inputs':'order',
883
+ 'default_inputs' : '2',
884
+ 'function': polynomial},
885
+ 'Power law': {'inputs':'# of terms, use offset',
886
+ 'default_inputs': '1,0',
887
+ 'parameters': 'a_0,b_0,...,a_n,b_n,c',
888
+ 'function': fit_powerlaw},
889
+ 'Exponentials': {'inputs':'# of terms, use offset',
890
+ 'default_inputs': '1,0',
891
+ 'parameters': 'a_0,b_0,...,a_n,b_n,c',
892
+ 'function': fit_exponentials}
893
+ }
894
+
895
+ # Give descriptions to help the user.
896
+ functions['Polynomials and powers']['Linear']['description'] = 'Simple linear y = mx + b fit'
897
+ functions['Polynomials and powers']['Polynomial']['description'] = ('Polynomial fit of order n, y = a_n*x^n + ... + a_1*x + a_0.\n'
898
+ 'Provide n, the polynomial order.')
899
+ functions['Polynomials and powers']['Power law']['description'] = ('Power law fit of the form y = a_0*x^b_0 + ... + a_n*x^b_n + c.\n'
900
+ 'Provide inputs n,c, where n is the number of terms to include '
901
+ 'and c is whether to include a constant offset; c=0: no offset, c=1: offset')
902
+ functions['Polynomials and powers']['Exponentials']['description'] = ('Exponential fit of the form y = a_0*exp(-x/b_0) + ... + a_n*exp(-x/b_n) + c.\n'
903
+ 'Provide inputs n,c, where n is the number of terms to include '
904
+ 'and c is whether to include a constant offset; c=0: no offset, c=1: offset')
905
+
906
+ # Add simple peaks that take amplitude, width and position as only arguments.
907
+ functions['Peaks: 3 param']={}
908
+ multipeak_description= ('Fit one or more {} peaks. The inputs are n,c, where n is the number of peaks and c is whether to '
909
+ 'include a constant offset in the fit. c=0 --> no offset, c=1 --> Offset.\n'
910
+ 'For exmaple, inputs of 4,0 will fit four peaks without a constant offset.\n'
911
+ 'By default, the fit assumes equally spaced peaks with heights approximately the max value of the data.\n'
912
+ 'To change this, provide an initial guess of the form {}\n'
913
+ 'If providing an intial guess, you must provide '
914
+ 'all parameters for all peaks.')
915
+ lorgaussform=('w1 ... wn, a1 ... an, x1 ... xn, c where w = peak sigma a = peak amplitude, '
916
+ 'x = peak position and c = constant offset value (if used). For example:\n'
917
+ '0.01 0.014 0.005, 1.1 1.05 1.2, -0.1 0 0.1\n'
918
+ 'for three peaks with no constant offset, and\n'
919
+ '0.01 0.014 0.005, 1.1 1.05 1.2, -0.1 0 0.1,5\n'
920
+ 'for three peaks with a constant offset of 5.\n')
921
+
922
+ for peaktype in ['Lorentzian','Gaussian','Lognormal','StudentsT','DampedOscillator']:
923
+ functions['Peaks: 3 param'][peaktype] = {}
924
+ functions['Peaks: 3 param'][peaktype]['inputs']='# of peaks, use offset'
925
+ functions['Peaks: 3 param'][peaktype]['default_inputs']='1,0'
926
+ functions['Peaks: 3 param'][peaktype]['parameters']='sigma(s), amplitudes(s), position(s), y_offset'
927
+ functions['Peaks: 3 param'][peaktype]['description'] = multipeak_description.format(peaktype,lorgaussform)
928
+ functions['Peaks: 3 param']['Lorentzian']['function'] = partial(fit_lorgausstype, lmm.LorentzianModel)
929
+ functions['Peaks: 3 param']['Gaussian']['function'] = partial(fit_lorgausstype,lmm.GaussianModel)
930
+ functions['Peaks: 3 param']['StudentsT']['function'] = partial(fit_lorgausstype,lmm.StudentsTModel)
931
+ functions['Peaks: 3 param']['Lognormal']['function'] = partial(fit_lorgausstype,lmm.LognormalModel)
932
+ functions['Peaks: 3 param']['DampedOscillator']['function'] = partial(fit_lorgausstype,lmm.DampedOscillatorModel)
933
+
934
+
935
+ functions['Peaks: 4 param']={}
936
+ fourparamform=('w1 ... wn, a1 ... an, x1 ... xn, g1 ... gn, c where w = peak sigma, a = peak amplitude, '
937
+ 'x = peak position, g = gamma (see lmfit documentation for meaning in each case) '
938
+ 'and c = constant offset value (if used). For example:\n'
939
+ '0.01 0.014 0.005, 1.1 1.05 1.2, -0.1 0 0.1, 0.001 0.001 0.001\n'
940
+ 'for three peaks with no constant offset, and\n'
941
+ '0.01 0.014 0.005, 1.1 1.05 1.2, -0.1 0 0.1, 0.001 0.001 0.001,5\n'
942
+ 'for three peaks with a constant offset of 5.\n')
943
+
944
+ for peaktype in ['Voigt','PseudoVoigt','BreitWigner/Fano','SplitLorentzian','ExpGaussian','SkewedGaussian','Moffat','Pearson7','DampedHarmOsc','Doniach']:
945
+ functions['Peaks: 4 param'][peaktype] = {}
946
+ functions['Peaks: 4 param'][peaktype]['inputs']='# of peaks, use offset'
947
+ functions['Peaks: 4 param'][peaktype]['default_inputs']='1,0'
948
+ functions['Peaks: 4 param'][peaktype]['parameters']='sigmas(s), amplitudes(s), position(s), gammas(s), y_offset'
949
+ functions['Peaks: 4 param'][peaktype]['description'] = multipeak_description.format(peaktype,fourparamform)
950
+ functions['Peaks: 4 param']['Voigt']['function'] = partial(fit_voigttype, lmm.VoigtModel)
951
+ functions['Peaks: 4 param']['PseudoVoigt']['function'] = partial(fit_voigttype, lmm.PseudoVoigtModel)
952
+ functions['Peaks: 4 param']['BreitWigner/Fano']['function'] = partial(fit_voigttype, lmm.BreitWignerModel)
953
+ functions['Peaks: 4 param']['SplitLorentzian']['function'] = partial(fit_voigttype, lmm.SplitLorentzianModel)
954
+ functions['Peaks: 4 param']['ExpGaussian']['function'] = partial(fit_voigttype, lmm.ExponentialGaussianModel)
955
+ functions['Peaks: 4 param']['SkewedGaussian']['function'] = partial(fit_voigttype, lmm.SkewedGaussianModel)
956
+ functions['Peaks: 4 param']['Moffat']['function'] = partial(fit_voigttype, lmm.MoffatModel)
957
+ functions['Peaks: 4 param']['Pearson7']['function'] = partial(fit_voigttype, lmm.Pearson7Model)
958
+ functions['Peaks: 4 param']['DampedHarmOsc']['function'] = partial(fit_voigttype, lmm.DampedHarmonicOscillatorModel)
959
+ functions['Peaks: 4 param']['Doniach']['function'] = partial(fit_voigttype, lmm.DoniachModel)
960
+
961
+ functions['Peaks: skewed']={}
962
+ fiveparamform=('w1 ... wn, a1 ... an, x1 ... xn, g1 ... gn, s1 ... sn, c where w = peak sigma, a = peak amplitude, '
963
+ 'x = peak position, g = gamma (see lmfit documentation for meaning in each case), '
964
+ 's = skew and c = constant offset value (if used). For example:\n'
965
+ '0.01 0.014 0.005, 1.1 1.05 1.2, -0.1 0 0.1, 0.001 0.001 0.001, 0.1 0.12 0.14\n'
966
+ 'for three peaks with no constant offset, and\n'
967
+ '0.01 0.014 0.005, 1.1 1.05 1.2, -0.1 0 0.1, 0.001 0.001 0.001, 0.1 0.12 0.14, 5\n'
968
+ 'for three peaks with a constant offset of 5.\n')
969
+ for peaktype in ['Pearson4','SkewedVoigt']:
970
+ functions['Peaks: skewed'][peaktype] = {}
971
+ functions['Peaks: skewed'][peaktype]['inputs']='# of peaks, use offset'
972
+ functions['Peaks: skewed'][peaktype]['default_inputs']='1,0'
973
+ functions['Peaks: skewed'][peaktype]['parameters']='sigma(s), amplitude(s), position(s), gammas(s), skews(s), y_offset'
974
+ functions['Peaks: skewed'][peaktype]['description'] = multipeak_description.format(peaktype,fiveparamform)
975
+ functions['Peaks: skewed']['Pearson4']['function'] = partial(fit_skewedpeaks, lmm.Pearson4Model)
976
+ functions['Peaks: skewed']['SkewedVoigt']['function'] = partial(fit_skewedpeaks,lmm.SkewedVoigtModel)
977
+
978
+ # Oscillating
979
+ functions['Oscillating']={'Sine':{'function':fit_sines}
980
+ #'Ramsey':{'function':ramsey_fit},
981
+ # 'Sine w/exp decay':{},
982
+ # 'Sine w/power decay':{}
983
+ }
984
+ functions['Oscillating']['Sine']['inputs']='# of waves, use offset'
985
+ functions['Oscillating']['Sine']['default_inputs']='1,0'
986
+ functions['Oscillating']['Sine']['parameters']='Amplitude(s), frequency(ies), phase(s), offset'
987
+ functions['Oscillating']['Sine']['description']=('Fit a sine wave A*sin(f*x+phase) with optional + c if use offset == 1.\n'
988
+ 'Specify # of waves > 1 to fit A_1*sin(f_1*x+ph_1) + ... + A_n*sin(f_n*x+ph_n) + c.\n'
989
+ 'Initial guesses for more than one wave take the form\n'
990
+ 'A_1 ... A_n, f_1 ... f_n, ph_1 ... ph_n, c\n'
991
+ 'e.g., for three waves:\n'
992
+ '0.01 0.014 0.005, 1.1 1.05 1.2, -0.1 0 0.1, 0.001 0.001 0.001,5\n')
993
+
994
+ # Thermal distributions
995
+ functions['Thermal']={'Maxwell':{'fullname':'Maxwell-Boltzmann', 'equation':'1/A*exp((x-x0)/kT)','function':partial(thermal_fit, 'maxwell')},
996
+ 'Fermi':{'fullname':'Fermi-Dirac', 'equation':'1/[A*exp((x-x0)/kT)+1]','function':partial(thermal_fit, 'fermi')},
997
+ 'Bose':{'fullname':'Bose-Einstein', 'equation':'1/[A*exp((x-x0)/kT)-1]','function':partial(thermal_fit, 'bose')},
998
+ }
999
+
1000
+ thermaldescription=('Fit to a {} distribution: y = {}. kT is considered a single fit parameter. Initial guesses '
1001
+ 'for kT should be in units of x, usually either eV or J.')
1002
+ for key in functions['Thermal'].keys():
1003
+ functions['Thermal'][key]['parameters']='A, x0, kT'
1004
+ functions['Thermal'][key]['description']=thermaldescription.format(functions['Thermal'][key]['fullname'], functions['Thermal'][key]['equation'])
1005
+
1006
+
1007
+
1008
+ #Step functions
1009
+ functions['Step']={'Linear':{},'Arctan':{},'ErrorFunction':{},'Logistic':{}}
1010
+ stepdescription=('Fit a single step function of type {} (see lmfit documentation for information). \n'
1011
+ 'The step function starts at 0 and ends with value +/- A. '
1012
+ 'The x-value where y=A/2 is given by x0, and sigma is the characteristic width of the step.\n'
1013
+ 'Use an offset filter on the data in the main panel to ensure your data starts at y=0.\n'
1014
+ 'In addition, the x-data must be ascending; use a filter to multiply by -1, and possibly an add/subtract offset, if necessary.\n')
1015
+ for key in functions['Step'].keys():
1016
+ functions['Step'][key]['parameters']='A, x0, sigma'
1017
+ functions['Step'][key]['description']=stepdescription.format(key)
1018
+ functions['Step']['Linear']['function'] = partial(step_fit, 'linear')
1019
+ functions['Step']['Arctan']['function'] = partial(step_fit, 'arctan')
1020
+ functions['Step']['ErrorFunction']['function'] = partial(step_fit, 'erf')
1021
+ functions['Step']['Logistic']['function'] = partial(step_fit, 'logistic')
1022
+
1023
+ #Rectangle functions
1024
+ functions['Rectangle']={'Linear':{},'Arctan':{},'ErrorFunction':{},'Logistic':{}}
1025
+ rectdescription=('Fit a rectangle function of type {} (see lmfit documentation for information). \n'
1026
+ 'A rectangle function steps from 0 to +/- A, then back to 0. '
1027
+ 'The x-values where y=A/2 are given by x0_1, x0_2, and sigma_1 and sigma_2 are the characteristic widths of the steps.\n'
1028
+ 'Use an offset filter on the data in the main panel to ensure your data starts at y=0.\n'
1029
+ 'In addition, the x-data must be ascending; use a filter to multiply by -1, and possibly an add/subtract offset, if necessary.\n')
1030
+ for key in functions['Rectangle'].keys():
1031
+ functions['Rectangle'][key]['parameters']=['A, x0_1, x0_2, sigma_1, sigma_2']
1032
+ functions['Rectangle'][key]['description']=rectdescription.format(key)
1033
+
1034
+ functions['Rectangle']['Linear']['function'] = partial(rectangle_fit, 'linear')
1035
+ functions['Rectangle']['Arctan']['function'] = partial(rectangle_fit, 'arctan')
1036
+ functions['Rectangle']['ErrorFunction']['function'] = partial(rectangle_fit, 'erf')
1037
+ functions['Rectangle']['Logistic']['function'] = partial(rectangle_fit, 'logistic')
1038
+
1039
+ # Custom fits based on expression fit
1040
+ functions['Custom']={'Coulomb blockade':{'function':QD_fit},
1041
+ 'FET mobility':{'function':FET_mobility},
1042
+ 'BCS/Dynes':{'function':dynes_fit},
1043
+ 'Ramsey':{'function':ramsey_fit},
1044
+ }
1045
+
1046
+ functions['Custom']['Coulomb blockade']['inputs']='# of peaks, alpha'
1047
+ functions['Custom']['Coulomb blockade']['default_inputs']='1,0.1'
1048
+ functions['Custom']['Coulomb blockade']['parameters']='V_0s, G_0s, T'
1049
+ functions['Custom']['Coulomb blockade']['description']=('Fit one or more Coulomb blockade peaks in the limit of low tunnel coupling:\n'
1050
+ 'G = G_0 * cosh(e*alpha*(Vg - V_0)/(2*k_B*T))**(-2)\n'
1051
+ 'where G is the conductance, G_0 the peak conductance, Vg is the gate voltage, '
1052
+ 'V_0 the gate voltage at the peak, k_B is the Boltzmann constant, '
1053
+ 'T is the temperature, e is electron charge and alpha is the lever arm.\n'
1054
+ 'Alpha must be provided as an input, e.g. 0.02, and should be measured from '
1055
+ 'Coulomb diamonds.\n'
1056
+ 'Initial guesses for V_0s, G_s0s and T are given in the form:\n'
1057
+ 'V_0_1 V_0_2 ... V_0_n, G_0_1 G_0_2 ... G_0_n, T\n'
1058
+ 'e.g., -0.1 0 0.1, 1.1 1.05 1.2, 0.01\n'
1059
+ 'note: give only a single value for T, as it _should_ be the same for all peaks.\n'
1060
+ 'However, each peak will return its own T.\n'
1061
+ 'See e.g. page 396 of the Thomas Ihn book "Semiconductor Nanostructures: Quantum States and Electronic Transport" '
1062
+ )
1063
+
1064
+ functions['Custom']['FET mobility']['inputs']='C,L'
1065
+ functions['Custom']['FET mobility']['default_inputs']='5e-15,5e-6'
1066
+ functions['Custom']['FET mobility']['parameters']='mu, V_th, R_s'
1067
+ functions['Custom']['FET mobility']['description']=('Fit a FET mobility curve of the form:\n'
1068
+ 'G = 1/(R_s+L^2/(C*mu*(Vg-V_th)))\n'
1069
+ 'where R_s is the series resistance, L is the channel length, C is the capacitance, '
1070
+ 'mu is the mobility and V_th is the threshold voltage.\n'
1071
+ 'The data should be G, conductance, vs Vg, gate voltage.\n'
1072
+ 'You must provide calculated/measured values for C and L; these are not fitted.\n'
1073
+ 'If necessary, provide initial guesses for mu, x_0 and R_s in the form:\n'
1074
+ '0.1,0.5,1e3\n'
1075
+ 'Note; all units in SI! i.e. S and m, not e2/h and cm.\n'
1076
+ 'See doi.org/10.1088/0957-4484/26/21/215202')
1077
+
1078
+ functions['Custom']['BCS/Dynes']['parameters']='V0, G_N, gamma, delta'
1079
+ functions['Custom']['BCS/Dynes']['description']=('Fit the BCS/Dynes model to a tunnel spectrum of a superconducting gap. '
1080
+ 'The Dynes model is given by:\n'
1081
+ 'dI/dV(V) = G_N*Re[(e*(V-V0)-i*gamma)/sqrt((e*(V-V0)-i*gamma)^2-(delta)^2)]\n'
1082
+ 'where G_N is the normal state conductance, gamma is broadening and '
1083
+ 'delta is the superconducting gap. V0 accounts for any offset due to e.g. miscalibrated current preamplifier\n'
1084
+ 'Delta and gamma are in electronvolts\n'
1085
+ 'see doi.org/10.1103/PhysRevLett.41.1509\n or doi.org/10.1038/s41467-021-25100-w')
1086
+
1087
+ functions['Custom']['Ramsey']['parameters']='A, B, C, f, phi, T2'
1088
+ functions['Custom']['Ramsey']['description']=('Fit a Ramsey oscillation of the form A*cos(2*pi*f*x + phi)*exp(-x/T2) + B + C*x.\n'
1089
+ 'Initial guesses for the parameters are given in the form:\n'
1090
+ 'A, B, C, f, phi, T2\n'
1091
+ 'e.g., 0.01, -50, -4e6, 40e6, 0, 50e-9\n')
1092
+
1093
+ functions['Custom']['RCSJ fit']={'function':RCSJfit}
1094
+ functions['Custom']['RCSJ fit']['inputs'] = 'T'
1095
+ functions['Custom']['RCSJ fit']['default_inputs'] = '0.02'
1096
+ functions['Custom']['RCSJ fit']['parameters'] = 'jc, Rj, Rc, Vdc_0, c'
1097
+ functions['Custom']['RCSJ fit']['description'] = ('Fit the differential conductance, dI/dV, of a Josephson junction vs the dc voltage, Vdc applied across it.\n'
1098
+ 'The model is fitted to the derivative of:\n'
1099
+ '(Rj/(Rj+Rc))*(jc*Im(I_(1-in(v)(B))/I_(-in(V)(B))) + (Vdc-Vdc_0)/Rj)\n'
1100
+ 'where:\n'
1101
+ '- Rj is the junction resistance,\n'
1102
+ '- Rc is the shunt resistance,\n'
1103
+ '- jc is the critical current density,\n'
1104
+ '- Vdc_0 is the offset voltage,\n'
1105
+ '- I_(1-in(v)(B)) and I_(-in(V)(B)) are modified Bessel functions of the first kind.\n'
1106
+ '- n(V) = hbar*(Vdc-Vdc_0)/(2*e*Rc*k_B*T), where hbar is the reduced Planck\'s constant, e is the electron charge, k_B is the Boltzmann constant, and T is the temperature\n'
1107
+ '- B = jc*hbar/(2*e*k_B*T)\n'
1108
+ '- Additionally, there is a constant offset, c used in the fit.\n'
1109
+ 'See e.g. https://www.science.org/doi/suppl/10.1126/sciadv.aav1235/suppl_file/aav1235_sm.pdf, page 14 onwards.\n'
1110
+ 'Input the temperature in Kelvin, T, and the initial guesses for jc, Rj, Rc, Vdc_0 and c.\n'
1111
+ 'It is strongly recommended to give your own initial guesses for this fit, there is no general way to define good guesses based solely on the data. Give guesses in the form:\n'
1112
+ 'jc, Rj, Rc, Vdc_0, c\n'
1113
+ 'e.g., 1e-9, 12000, 3600, 0, 1e-4\n'
1114
+ 'Note: the mpmath library is required for this fit, to handle the Bessel functions. Install it with '
1115
+ 'pip install mpmath".\n')
1116
+
1117
+
1118
+ #Wrap the ExpressionModel to allow arbitrary input.
1119
+ functions['User input']={'Expression':{}}
1120
+ functions['User input']['Expression']['inputs'] = 'Expression'
1121
+ functions['User input']['Expression']['parameters'] = 'all fit parameters'
1122
+ functions['User input']['Expression']['description'] =('Fit an arbitrary expression. Input any function you like. '
1123
+ 'x must be the independent variable. e.g.\n'
1124
+ 'C + A * exp(-x/x0) * sin(x*freq+phase)\n'
1125
+ 'You must also provide initial guesses for each parameter in the form:\n'
1126
+ 'C=0.25, A=1.0, x0=2.0, freq=0.04, phase=0')
1127
+ functions['User input']['Expression']['function']=expression_fit
1128
+
1129
+ functions['Statistics']={'Statistics':{}}
1130
+ functions['Statistics']['Statistics']['inputs'] = 'Function names'
1131
+ functions['Statistics']['Statistics']['parameters'] = 'weights or percentiles (opt)'
1132
+ functions['Statistics']['Statistics']['description'] =('Calculate statistical information about the data. Available numpy functions are:\n'
1133
+ 'mean, average, std, var, median, min, max, range, sum, skew, percentile, autocorrelation and autocorrelation_norm.\n'
1134
+ 'See: https://numpy.org/doc/stable/reference/routines.statistics.html.\n'
1135
+ 'Input a comma-separated list of functions to calculate, e.g.\n'
1136
+ 'mean,std,var,median,min,max.\n'
1137
+ 'The average can be weighted by providing a list of weights in the "Initial guess" box.\n'
1138
+ 'The default percentiles to calculate are 1,5,10,25,50,75,90,95 and 99. '
1139
+ 'To change this, provide a list of percentiles in the "Initial guess" box.\n'
1140
+ 'User-defined weights and percentiles cannot be used together; calculate the average and percentiles separately in this case.\n'
1141
+ 'Use "all" to calculate all functions, or use the keyword "all1d" to calculate everything except the percentiles and autocorrelation.\n'
1142
+ 'Linecuts only: A parameter dependency can be generated, but only if the functions return the same number of values.\n'
1143
+ 'This means you can generate a dependency that includes everything except the percentiles and autocorrelation; '
1144
+ 'these can be generated separately, resulting in a 2D dataset.\n'
1145
+ 'The difference between autocorrelation and autocorrelation_norm is that the latter is normalised to the zero-th index value.\n')
1146
+ functions['Statistics']['Statistics']['function']=statistics
1147
+ functions['Statistics']['Statistics']['default_inputs'] = 'all'
1148
+
1149
+
1150
+ # functions to return different parts of the functions dictionary. Makes it easier to call in main
1151
+ def get_class_names():
1152
+ return functions.keys()
1153
+
1154
+ def get_function(function_class,function_name):
1155
+ return functions[function_class][function_name]['function']
1156
+
1157
+ def get_names(fitclass='Polynomials and powers'):
1158
+ return functions[fitclass].keys()
1159
+
1160
+ def get_parameters(function_class,function_name):
1161
+ return functions[function_class][function_name]['parameters']
1162
+
1163
+ def get_description(function_class,function_name):
1164
+ return functions[function_class][function_name]['description']
1165
+
1166
+ # Entry point for fitting the data. Passes info along to relevant functions and returns the fit result
1167
+ def fit_data(function_class,function_name, xdata, ydata, p0=None, inputinfo=None):
1168
+ # all fit functions get called through the dictionary
1169
+ f = functions[function_class][function_name]['function']
1170
+ result = f(xdata,ydata,p0,inputinfo)
1171
+ return result