data_complex.py 16 KB
Newer Older
1
# -*- coding: utf-8 -*-
2
"""Class for complex data (Frequency, Re + i*Im)"""
3

4
from .data_table import data_table
5
import numpy as np
6
from CircleFit.plotting import plot_rawdata
Christian Schneider's avatar
Christian Schneider committed
7
import CircleFit.plotting as cp
8
9
from CircleFit.circuit import Notch, Reflection
from CircleFit.fit_toolbox import get_delay
10
11


12
class data_complex(data_table):
13
    """Complex DataModule Class
14

15
    Stores data as complex voltage phasors Frequency, Re[V] + i*Im[V]
16

17
18
19
20
    Parameters
    ----------
    x : list
        List/Numpy array of x values
21
    values : list, array, ndarray
22
        List/Numpy array of complex y values [1+1j, 2+2j, ....]
23
    """
24
25
26
27
28
29
30
31
32
33
34
35
    def __init__(self, x=None, values=None):

        if x or values is None:
            super(data_complex, self).__init__([[np.nan], [np.nan], [np.nan]],
                                               ['Freq (GHz)', 'Real (V)',
                                                'Imag (V)'])
        else:
            re = np.real(values)
            im = np.imag(values)
            super(data_complex, self).__init__([x, re, im],
                                               ['Freq (GHz)', 'Real (V)',
                                                'Imag (V)'])
36
37
38
39
40
41
42
43
        self.delay = None
        self._delayOffset = None
        self.circuit = None
        self.fitresults = None
        self.fitresults_full_model = None

    def _repr_html_(self):
        """Show pandas dataframe as default representation"""
44
45
46
47
48
49
50
51
52
        print_str = '<h3>data_complex</h3>\n'
        if self.delay is not None:
            print_str += 'delay = {:.2e} s\n'.format(self.delay)
        if self.fitresults is not None:
            print_str += '\n<h4>Fitresults</h4>'
            print_str += self.fitresults.to_html()
            print_str += '\n<h4>Head of data </h4>'

        return print_str + self.df.head().to_html()
53
54

    def load_var(self, x, re, im):
55
56
57
58
59
60
61
62
63
64
65
        """Load values in form of three arrays into data:complex

        Parameters
        -----------
        x : list
            X Array. Typically frequencies
        re : list
            Real part of y values
        im : list
            Imaginary part of y values
        """
66
        self.import_data([x, re, im], ['Freq (GHz)', 'Real (V)', 'Imag (V)'])
67
68

    def load_cplx_var(self, x, y):
69
70
71
72
73
74
75
76
77
        """Load values in form of two arrays into data_complex

        Parameters
        -----------
        x : list
            X Array. Typically frequencies
        y : list(complex)
            Y values as a list of complex numbers [1 + 1j, 2 + 0j, ...]
        """
78
79
80
81
82
83
84
85
86
87
88
        re = np.real(y)
        im = np.imag(y)
        self.import_data([x, re, im], ['Freq (GHz)', 'Real (V)', 'Imag (V)'])

    @property
    def x(self):
        """Return frequencies"""
        return np.array(self.df['Freq (GHz)'][self.idx_min:self.idx_max])

    @x.setter
    def x(self, value):
89
90
91
92
93
94
95
96
97
98
99
100
101
        if len(value) == len(self.x):
            # Replace select (idx_min to idx_max)
            self.df[self.name_x][self.idx_min:self.idx_max] = value
        elif len(value) == len(self.df[self.name_x]):
            # Replace whole dataframe
            self.df[self.name_x] = value
        else:
            # Reindex
            self.df = self.df.reindex(range(len(value)))
            self.df[self.name_x] = value
            # Reset min idx and max idx
            self.idx_min = 0
            self.idx_max = None
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120

    @property
    def y(self):
        return self.value

    @y.setter
    def y(self, value):
        """Set real and imag values"""
        self.df['Real (V)'][self.idx_min:self.idx_max] = np.real(value)
        self.df['Imag (V)'][self.idx_min:self.idx_max] = np.imag(value)

    @property
    def value(self):
        """Return complex values"""
        re = self.df['Real (V)'][self.idx_min:self.idx_max]
        im = self.df['Imag (V)'][self.idx_min:self.idx_max]

        if self.delay:
            return np.array(re + 1j*im)*np.exp(2j*np.pi*self.delay*self.x)
121
        else:
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
            return np.array(re + 1j*im)

    @value.setter
    def value(self, value):
        """Set real and imag values"""
        self.df['Real (V)'][self.idx_min:self.idx_max] = np.real(value)
        self.df['Imag (V)'][self.idx_min:self.idx_max] = np.imag(value)

    @property
    def value_raw(self):
        re = self.df['Real (V)'][self.idx_min:self.idx_max]
        im = self.df['Imag (V)'][self.idx_min:self.idx_max]
        return np.array(re + 1j * im)

    @property
    def dB(self):
Christian Schneider's avatar
Christian Schneider committed
138
        """This function returns a data_table module with the selected data of
139
140
141
        this data module converted in amplitude (dB). Data Module parameters
        are copied as well.
        """
142
143
144
145
146
147
148
149
150
151
152
        tmp = data_table([self.x, 20*np.log10(np.abs(self.value))],
                         ['Frequency (GHz)', 'Mag (dB)'])
        # TODO Make a function for copying datamodules
        tmp.par = self.par
        tmp.time_start = self.time_start
        tmp.time_stop = self.time_stop
        tmp.temp_start = self.temp_start
        tmp.temp_stop = self.temp_stop
        tmp.temp_start_time = self.temp_start_time
        tmp.temp_stop = self.temp_stop_time
        tmp.comments = self.comments
153
154
155
156
        tmp.date_format = self.date_format
        tmp.save_date_format = self.save_date_format
        return tmp

157
158
159
160
161
    @property
    def phase(self):
        return self.get_phase()

    def get_phase(self, unit='deg', unwrap=True):
162
        """This function returns a data_table module with the selected data of
163
164
165
166
167
168
169
170
171
172
173
174
        this data module converted in amplitude (dB). Data Module parameters
        are copied as well.

        arguments:
         - unit:
             'd' or 'deg' for degrees (def)
             'r' or 'rad' for radians
        - unwrap:
            True (def): phase is continuous
            False: phase is contained in 1 period
        """
        u = unit.lower()
175
        tmp = np.angle(self.value)
176
177
178
179
180
181
182
183
184
        if unwrap is True:
            tmp = np.unwrap(tmp)
        elif unwrap is False:
            pass
        else:
            print('Wrong unwrap setting inserted')
            raise ValueError

        if u == 'd' or u == 'deg':
185
            tmp *= 180 / np.pi
186

187
        elif u == 'r' or u == 'rad':
188
189
190
191
192
            pass
        else:
            print('Wrong unit inserted')
            raise ValueError

193
194
195
196
197
198
199
200
201
202
        tmp = data_table([self.x, tmp],
                         ['Frequency (GHz)', 'Phase ({})'.format(unit)])
        tmp.par = self.par
        tmp.time_start = self.time_start
        tmp.time_stop = self.time_stop
        tmp.temp_start = self.temp_start
        tmp.temp_stop = self.temp_stop
        tmp.temp_start_time = self.temp_start_time
        tmp.temp_stop = self.temp_stop_time
        tmp.comments = self.comments
203
204
205
        tmp.date_format = self.date_format
        tmp.save_date_format = self.save_date_format
        return tmp
206

207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
    def correct_delay(self, delay=None, comb_slopes=False, f_range=.25):
        """Correct the electric delay of the cables.

        Note
        -----
        Give delay in nanoseconds.

        Parameters
        -----------
        delay : float
            Electric delay of in ns.
            If set to None, a fit will estimate the electric delay.
        comb_slopes : bool
            Do one fit for the whole range (use this, only if the resonance
            features in the middle seem to be symmetric)
        f_range : float
            Percentage of the data (first and last) to use for the fit. This
            is done to get rid of resonance features in the middle
225
        """
226
227
228
229
230
231
        # Check if already set. If yes, unset the previous delay
        if self.delay is not None:
            self.value *= np.exp(-2j*np.pi*self.delay*self.x)
            self.delay = None
            self._delayOffset = None

Christian Schneider's avatar
Bugfix    
Christian Schneider committed
232
        # Try to estimate delay with linear fit of phase if no delay is given
Christian Schneider's avatar
Bugfix    
Christian Schneider committed
233
        if delay is None:
234
235
236
            delay, offset = get_delay(self, comb_slopes=comb_slopes,
                                      f_range=f_range)
            self._delayOffset = offset
237
238
239
240
241
242

        else:
            _, offset = get_delay(self, comb_slopes=comb_slopes,
                                  f_range=f_range)
            self._delayOffset = offset

243
244
245
        # Save set delay
        self.delay = delay

246
    def plot(self, engine='bokeh'):
247
248
249
250
251
252
253
254
255
        """Plot data

        Plots raw data if no circle fit is present. If a circlefit was done,
        it plots the data and fits.

        Parameters
        -----------
        engine : str
            Chose the plot engine between 'bokeh' (default) and 'pyplot'
256
        """
257
        # TODO Rewrite without try and except
258
259
260
261
262
263
264
        try:
            self.plot_fitted_data(engine=engine)

        except:
            self.plot_rawdata(engine=engine)

    def plot_rawdata(self, **kwargs):
265
        """Plots plain data"""
266
        plot_rawdata(self.x, self.value, **kwargs)
267
268
269

    def circle_fit_notch(self, delay=None, a=None, alpha=None, phi0=None,
                         subtract_bg=True, fr_init=None, Ql_init=None,
270
                         weight_width=1, print_res=True, plot_res=True,
Christian Schneider's avatar
Christian Schneider committed
271
                         comb_slopes=False, final_mag=False, maxfev=10000):
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
        """Circle fit for notch configuration.

        This function fits the complex data to a notch-configuration circuit
        model. By default it will print and plot the fit results. To disable
        this automation, use the `print_res` and `plot_res` keywords.

        All the fit parameters and data is stored in a .circuit subobject of
        this datamodule.

        Note
        -----
            If something goes wrong with the fit, you can for example use the
            .circuit.plot_steps() function to show the individual steps of the
            fit routine.

        Measurement Setup::

        |            --[Z1]-- --[Z2]--
        |                    |
        |    OUT            [Z3]         IN
        |                    |
        |            -----------------

        Parameters
        -----------
        delay : float
            Set cable delay manually, otherwise determined by the fitting
            routine. Use large span to determine delay. Can be obtained by
            .get_delay()
        a : float
            Set a manually
        alpha : float
            Set alpha manually
        phi0 : float
            Impedance mismatch expressed in radians
        substract_bg : bool
            Subtract linear background
        fr_init : float
            Initial guess for resonance frequency. In GHz.
        Ql_init : float
            Initial guess for Lorentzian fit
        weight_width : float
            Multiply of FWHM frequency for which points are weighted equally.
            Outside the points are weighted less.
        print_res : bool
            Print fit results
        plot_res : bool
            Plot fit results
Christian Schneider's avatar
Christian Schneider committed
320
321
322
323
324
        final_mag : bool
            Use just magnitude data for final fit of Ql and fr. This is useful,
            since sometimes the phase data is very noisy in comparison to the
            magnitude data. To minimize the error, therefore the final fit can
            be just performed on the magnitude data.
325
326
327
328
329
        comb_slopes : bool
            Fit through whole phase instead of start and end separately
            (with a hole in the middle) for obtaining the delay.
        maxfev : int
            Maximum number of iterations for fits
330
        """
331
332
333
        kwargs = {'delay': delay, 'a': a, 'alpha': alpha, 'phi0': phi0,
                  'subtract_bg': subtract_bg, 'fr_init': fr_init,
                  'Ql_init': Ql_init, 'weight_width': weight_width,
334
                  'print_res': print_res, 'plt_res': plot_res,
Christian Schneider's avatar
Christian Schneider committed
335
336
                  'maxfev': maxfev, 'comb_slopes': comb_slopes,
                  'final_mag': final_mag}
337
338
339
340
341
        self.circuit = Notch(self, **kwargs)

        # Further save fitresults in data_complex class
        self.fitresults = self.circuit.fitresults

Christian Schneider's avatar
Christian Schneider committed
342
    def circle_fit_reflection(self,  delay=None, a=None, alpha=None, phi0=0,
343
344
                              subtract_bg=True, fr_init=None, Ql_init=None,
                              weight_width=1, print_res=True, plt_res=True,
Gerhard Kirchmair's avatar
Gerhard Kirchmair committed
345
                              comb_slopes=False, final_mag=False, maxfev=1e6):
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
        """Circle fit for reflection configuration.

        This function fits the complex data to a reflection-configuration
        circuit model. By default it will print and plot the fit results.
        To disable this automation, use the `print_res` and `plot_res`
        keywords.

        All the fit parameters and data is stored in a .circuit subobject of
        this datamodule.

        Note
        -----
            If something goes wrong with the fit, you can for example use the
            .circuit.plot_steps() function to show the individual steps of the
            fit routine.

        Measurement Setup::

        |            --[Z1]---
        |    IN              |
        |    OUT            [Z3]
        |                    |
        |            ---------

        Parameters
        -----------
        delay : float
            Set cable delay manually, otherwise determined by the fitting
            routine. Use large span to determine delay. Can be obtained by
            .get_delay()
        a : float
            Set a manually
        alpha : float
            Set alpha manually
        phi0 : float
            Impedance mismatch expressed in radians
        substract_bg : bool
            Subtract linear background
        fr_init : float
            Initial guess for resonance frequency. In GHz.
        Ql_init : float
            Initial guess for Lorentzian fit
        weight_width : float
            Multiply of FWHM frequency for which points are weighted equally.
            Outside the points are weighted less.
        print_res : bool
            Print fit results
        plot_res : bool
            Plot fit results
        comb_slopes : bool
            Fit through whole phase instead of start and end separately
            (with a hole in the middle) for obtaining the delay.
        final_mag : bool
            Use just magnitude data for final fit of Ql and fr. This is useful,
            since sometimes the phase data is very noisy in comparison to the
            magnitude data. To minimize the error, therefore the final fit can
            be just performed on the magnitude data.
        maxfev : int
            Maximum number of iterations for fits
405
        """
406
407
408
409
        kwargs = {'delay': delay, 'a': a, 'alpha': alpha, 'phi0': phi0,
                  'subtract_bg': subtract_bg, 'fr_init': fr_init,
                  'Ql_init': Ql_init, 'weight_width': weight_width,
                  'print_res': print_res, 'plt_res': plt_res, 'maxfev': maxfev,
Christian Schneider's avatar
Christian Schneider committed
410
                  'comb_slopes': comb_slopes, 'final_mag': final_mag}
411
412
413
414
        self.circuit = Reflection(self, **kwargs)

        # Further save fitresults in data_complex class
        self.fitresults = self.circuit.fitresults
415

416
    def plot_fitted_data(self, **kwargs):
417
        """Plot fitted data"""
Christian Schneider's avatar
Christian Schneider committed
418
        cp.plot_cfit(self.circuit, **kwargs)
419
420

    def plot_steps(self):
421
        """Plot each step of the circlefit routine"""
Christian Schneider's avatar
Christian Schneider committed
422
        cp.plot_steps(self.circuit)
423
424

    def get_delay(self):
425
        """Outputs the delay. Alias for .circuit.delay"""
Christian Schneider's avatar
Christian Schneider committed
426
        return self.circuit.delay
427
428

    def get_no_of_photons_full_info(self, power_dbm=None):
Christian Schneider's avatar
Christian Schneider committed
429
        return self.circuit.get_no_of_photons_full_info(power_dbm)
430
431

    def get_no_of_photons(self, power_dbm=None):
Christian Schneider's avatar
Christian Schneider committed
432
        return self.circuit.get_no_of_photons(power_dbm)
433

Christian Schneider's avatar
Christian Schneider committed
434
    def circle_fit_dc(self, **kwargs):
435
        """Alias for circle_fit_reflection(phi=None)"""
Gerhard Kirchmair's avatar
Gerhard Kirchmair committed
436
        return self.circle_fit_reflection(phi0=None, **kwargs)
437

Christian Schneider's avatar
Christian Schneider committed
438
    def do_circle_fit_dc(self, **kwargs):
439
        """Alias to circle_fit_reflection for downwards compatibility."""
Administrator's avatar
Administrator committed
440
        print('Abbreviated function name. Use circle_fit_dc in future!')
Gerhard Kirchmair's avatar
Gerhard Kirchmair committed
441
        return self.circle_fit_reflection(phi0=None, **kwargs)
Christian Schneider's avatar
Christian Schneider committed
442

443
    def do_circle_fit_notch(self, **kwargs):
444
        """Alias to circle_fit_reflection for downwards compatibility."""
Christian Schneider's avatar
Christian Schneider committed
445
        print('Abbreviated function name. Use circle_fit_notch() instead')
Christian Schneider's avatar
Christian Schneider committed
446
        return self.circle_fit_notch(**kwargs)
447
448

    def do_circle_fit_reflection(self, **kwargs):
449

450
451
        print('Abbreviated function name. Use circle_fit_reflection in' +
              'future!')
452
        return self.circle_fit_reflection(**kwargs)