Exemplo n.º 1
0
def plot_decomp(ds,
                sheath_label='ion_a',
                mode=1,
                fit_fl=True,
                ax=None,
                kwargs_for_plot=None,
                kwargs_for_fitplot=None,
                colour='r',
                plot_label=True,
                length=DEFAULT_L,
                gap=DEFAULT_G):
    sheath_error_label = sheath_label.replace('_a', '_d_a')

    if ax is None:
        fig, ax = plt.subplots()
    else:
        fig = ax.figure

    if kwargs_for_plot is None:
        kwargs_for_plot = {}
    if kwargs_for_fitplot is None:
        kwargs_for_fitplot = {}

    x, y = perform_decomp(ds,
                          sheath_label=sheath_label,
                          mode=mode,
                          length=length,
                          gap=gap)
    non_nan_y = ~np.isnan(y)
    x = x[non_nan_y]
    y = y[non_nan_y]

    yerr = ds[sheath_error_label].values[non_nan_y] * y

    if plot_label is True:
        plot_label = sheath_label

    ax.errorbar(x,
                y,
                yerr=yerr,
                color=colour,
                label=plot_label,
                **kwargs_for_plot)

    sl_fitter = fts.StraightLineFitter()
    fit_data = sl_fitter.fit(x, y)
    if fit_fl:
        c1, c2 = MODE_CONSTANTS[mode]
        fit_label = r'$c_1$ = {:.2g}, $c_2$ = {:.2g}'.format(
            fit_data.get_param(c1), fit_data.get_param(c2))
        ax.plot(*fit_data.get_fit_plottables(),
                color=colour,
                label=fit_label,
                **kwargs_for_fitplot)

    ax.set_xlabel(MODE_LABELS[mode][0])
    ax.set_ylabel(MODE_LABELS[mode][1])
    ax.legend()

    return ax, fit_data
Exemplo n.º 2
0
    def homogenise(self, frequency=None, filter_arcs_fl=False, plot_fl=True):
        """
        Chooses the region of interest and sections the time trace into
        individual sweeps, populating the member variable 'iv_arrs' with an
        IVData object for each sweep and combining these into a numpy array for
        each coax

        :param frequency:       (float) The frequency of sweeps used in the shot
                                If not specified it will be calculated from the
                                raw voltage trace using FFT (which may be slow).
        :param filter_arcs_fl:  (bool) Boolean flag, if true will attempt to
                                automatically filter out arcs by excluding
                                sweeps which have abnormally high max/min
                                voltages
        :param plot_fl:         (bool) Boolean flag, controls whether the method
                                plots various useful figures

        """
        triangle = f.TriangleWaveFitter()

        if frequency is None:
            # Use fourier decomposition to get frequency if none given
            frequency = triangle.get_frequency(
                self.raw_time,
                self.raw_voltage,
                accepted_freqs=self._ACCEPTED_FREQS)

        # Take the first 5% of data to run the sweep partitioning algorithm on
        slc_oi = slice(0, int(0.05 * len(self.raw_time)))

        # Smooth the voltage to get a first read of the peaks on the triangle wave
        smoothed_voltage = sig.savgol_filter(self.raw_voltage, 21, 2)
        top = sig.argrelmax(smoothed_voltage[slc_oi], order=100)[0]
        bottom = sig.argrelmin(smoothed_voltage[slc_oi], order=100)[0]
        _peaks = self.raw_time[np.concatenate([top, bottom])]
        _peaks.sort()

        # Get distances between the peaks and filter based on the found frequency
        _peak_distances = np.diff(_peaks)
        threshold = (1 / (2 * frequency)) - 0.001
        _peaks_ind = np.where(_peak_distances > threshold)[0]

        # Starting from the first filtered peak, arrange a period-spaced array
        peaks_refined = np.arange(_peaks[_peaks_ind[0]], self.raw_time[-1],
                                  1 / (2 * frequency))
        self.peaks = peaks_refined

        if plot_fl:
            plt.figure()
            plt.plot(self.raw_time, self.raw_voltage)
            plt.plot(self.raw_time,
                     triangle.fit(self.raw_time, self.raw_voltage).fit_y)
            for peak in self.peaks:
                plt.axvline(x=peak, linestyle='dashed', linewidth=1, color='r')

        if self.combine_sweeps_fl:
            skip = 2
            sweep_fitter = triangle
        else:
            skip = 1
            sweep_fitter = f.StraightLineFitter()

        for i in range(self.coaxes):
            for j in range(len(self.peaks) - skip):
                sweep_start = np.abs(self.raw_time - self.peaks[j]).argmin()
                sweep_stop = np.abs(self.raw_time -
                                    self.peaks[j + skip]).argmin()

                sweep_voltage = self.voltage[i][sweep_start:sweep_stop]
                sweep_time = self.raw_time[sweep_start:sweep_stop]

                if filter_arcs_fl:
                    # TODO: Fix this
                    sweep_fit = sweep_fitter.fit(sweep_time, sweep_voltage)
                    self.max_voltage.append(
                        (np.max(np.abs(sweep_voltage - sweep_fit.fit_y))))
                    if i == 0 and plot_fl:
                        sweep_fit.plot()
                    if np.max(
                            np.abs(sweep_voltage -
                                   sweep_fit.fit_y)) > self._ARCING_THRESHOLD:
                        self.arcs.append(np.mean(sweep_time))
                        continue

                # sweep_current = [current[sweep_start:sweep_stop] for current in self.current]
                sweep_current = self.current[i][sweep_start:sweep_stop]

                # Reverse alternate sweeps if not operating in combined sweeps mode, so
                if not self.combine_sweeps_fl and sweep_voltage[
                        0] > sweep_voltage[-1]:
                    # sweep_voltage = np.array(list(reversed(sweep_voltage)))
                    # sweep_time = np.array(list(reversed(sweep_time)))
                    # sweep_current = np.array(list(reversed(sweep_current)))
                    sweep_time = np.flip(sweep_time)
                    sweep_voltage = np.flip(sweep_voltage)
                    sweep_current = np.flip(sweep_current)

                # Create IVData objects for each sweep (or sweep pair)
                # TODO: What's the std_err_scaler doing here? Look through commits
                self.iv_arrs[i].append(
                    iv.IVData(sweep_voltage,
                              sweep_current,
                              sweep_time,
                              std_err_scaler=0.95))
Exemplo n.º 3
0
    def gunn_fit(self, sat_region=_DEFAULT_STRAIGHT_CUTOFF, plot_fl=False):
        """
        A version of Jamie Gunn's 4-parameter fit by subtracting the straight
        line of the ion saturation region from the whole IV curve. This is was
        adapted from a jupyter notebook made for Magnum data, and generates four
        plots: averaged IV, straight line overlay, corrected IV, 3-param fit to
        corrected IV.

        :return: A tuple of (corrected_iv_data, corrected_iv_fit_data)

        """
        import matplotlib.pyplot as plt
        import flopter.core.fitters as fts

        iv_data = self

        # define a straight section and trim the iv data to it
        str_sec = np.where(iv_data['V'] <= sat_region)
        iv_data_ss = IVData.non_contiguous_trim(iv_data, str_sec)

        # needed to define the area of the straight section on a graph with a vertical line
        str_sec_end = np.argmax(iv_data['V'][str_sec])

        # fit & plot a straight line to the 'straight section'
        sl_fitter = fts.StraightLineFitter()
        fit_data_ss = sl_fitter.fit(iv_data_ss['V'],
                                    iv_data_ss['I'],
                                    sigma=iv_data_ss['sigma'])

        # Extrapolate the straight line over a wider voltage range for illustrative purposes
        sl_range = np.linspace(-120, 100, 100)
        sl_function = fit_data_ss.fit_function(sl_range)

        # Subtract the gradient of the straight section from the whole IV curve.
        iv_data_corrected = iv_data.copy()
        iv_data_corrected['I'] = iv_data_corrected['I'] - (
            fit_data_ss.get_param('m') * iv_data_corrected['V'])

        simple_iv_fitter = fts.SimpleIVFitter()
        fit_data_corrected = iv_data_corrected.multi_fit(
            sat_region=sat_region, iv_fitter=simple_iv_fitter, plot_fl=plot_fl)

        if plot_fl:
            plt.figure()
            plt.errorbar(iv_data['V'],
                         iv_data['I'],
                         yerr=iv_data['sigma'],
                         label='Full IV',
                         color='darkgrey',
                         ecolor='lightgray')
            plt.legend()
            plt.xlabel(r'$V_p$ / V')
            plt.ylabel(r'$I$ / A')
            plt.ylim(-0.5, 1.3)
            plt.xlim(-102, 5)

            plt.figure()
            plt.errorbar(iv_data['V'],
                         iv_data['I'],
                         yerr=iv_data['sigma'],
                         label='Full IV',
                         color='darkgrey',
                         ecolor='lightgray')
            plt.plot(sl_range,
                     sl_function,
                     label='SE Line',
                     color='blue',
                     linewidth=0.5,
                     zorder=10)
            plt.legend()
            plt.xlabel(r'$V_p$ / V')
            plt.ylabel(r'$I$ / A')
            plt.ylim(-0.5, 1.3)
            plt.xlim(-102, 5)

            plt.figure()
            plt.plot(sl_range,
                     sl_function,
                     label='SE Line',
                     color='blue',
                     linewidth=0.5,
                     zorder=10)
            plt.errorbar(iv_data_corrected['V'],
                         iv_data_corrected['I'],
                         label='Corrected IV',
                         yerr=iv_data_corrected[c.SIGMA],
                         color='darkgrey',
                         ecolor='lightgray')
            plt.legend()
            plt.xlabel(r'$V_p$ / V')
            plt.ylabel(r'$I$ / A')
            plt.ylim(-0.5, 1.3)
            plt.xlim(-102, 5)

            plt.figure()
            plt.plot(sl_range,
                     sl_function,
                     label='SE Line',
                     color='blue',
                     linewidth=0.5)
            plt.errorbar(iv_data_corrected['V'],
                         iv_data_corrected['I'],
                         label='Corrected IV',
                         yerr=iv_data_corrected[c.SIGMA],
                         color='darkgrey',
                         ecolor='lightgray')
            plt.plot(*fit_data_corrected.get_fit_plottables(),
                     label='3 Param-Fit',
                     zorder=10,
                     color='r')
            plt.legend()
            plt.xlabel(r'$V_p$ / V')
            plt.ylabel(r'$I$ / A')
            plt.ylim(-0.5, 1.3)
            plt.xlim(-102, 5)

            plt.show()
        return iv_data_corrected, fit_data_corrected
Exemplo n.º 4
0
    def prepare(self,
                down_sampling_rate=5,
                plot_fl=False,
                filter_arcs_fl=False,
                roi_b_plasma=False,
                crit_freq=640,
                crit_ampl=1.1e-3):
        """
        Preparation consists of downsampling (if necessary), choosing the region of interest and putting each sweep
        into a numpy array of iv_datas

        """
        # This whole function should probably be put into a homogeniser implementation

        # Downsample by factor given
        arr_size = len(self.m_data.data[self.m_data.channels[0]])
        downsample = np.arange(0, arr_size, down_sampling_rate, dtype=np.int64)

        for ch, data in self.m_data.data.items():
            self.m_data.data[ch] = data[downsample]
        self.m_data.time = self.m_data.time[downsample] + self._ADC_TIMER_OFFSET

        self.m_data.data[self._VOLTAGE_CHANNEL] = self.m_data.data[
            self._VOLTAGE_CHANNEL] * 100

        # Find region of interest
        if roi_b_plasma and not self.offline and np.shape(
                self.magnum_data[mag.PLASMA_STATE])[1] == 2:
            start = np.abs(self.m_data.time -
                           self.magnum_data[mag.PLASMA_STATE][0][0]).argmin()
            end = np.abs(self.m_data.time -
                         self.magnum_data[mag.PLASMA_STATE][0][1]).argmin()
        else:
            start = 0
            end = len(self.m_data.time)

        # Read in raw values from adc file - these are the time and the voltages measured on each channel
        self.raw_time = np.array(self.m_data.time[start:end])
        self.raw_voltage = np.array(
            self.m_data.data[self._VOLTAGE_CHANNEL][start:end])
        for i, probe_index in enumerate(
            [self._PROBE_CHANNEL_3, self._PROBE_CHANNEL_4]):
            self.raw_current.append(
                np.array(self.m_data.data[probe_index][start:end]))

        # Convert the adc voltages into the measured values
        for i in range(self.coaxes):
            # Current is ohmicly calculated from the voltage across a shunt resistor
            self.current.append(self.raw_current[i] / self.shunt_resistance)

            # Separate volages are applied to each probe depending on the current they draw
            self.voltage.append(self.raw_voltage - self.raw_current[i] -
                                (self.cabling_resistance * self.current[i]))

        # self.current = np.array(self.current)

        self.filter(crit_ampl=crit_ampl, crit_freq=crit_freq, plot_fl=plot_fl)

        for i in range(self.coaxes):
            # Use fourier decomposition from get_frequency method in triangle fitter to get frequency
            triangle = f.TriangleWaveFitter()
            frequency = triangle.get_frequency(
                self.raw_time,
                self.voltage[i],
                accepted_freqs=self._ACCEPTED_FREQS)

            # Smooth the voltage to get a first read of the peaks on the triangle wave
            smoothed_voltage = sig.savgol_filter(self.voltage[i], 21, 2)
            top = sig.argrelmax(smoothed_voltage, order=100)[0]
            bottom = sig.argrelmin(smoothed_voltage, order=100)[0]
            _peaks = self.raw_time[np.concatenate([top, bottom])]
            _peaks.sort()

            # Get distances between the peaks and filter based on the found frequency
            _peak_distances = np.diff(_peaks)
            threshold = (1 / (2 * frequency)) - 0.001
            _peaks_ind = np.where(_peak_distances > threshold)[0]

            # Starting from the first filtered peak, arrange a period-spaced array
            peaks_refined = np.arange(_peaks[_peaks_ind[0]], self.raw_time[-1],
                                      1 / (2 * frequency))
            self.peaks = peaks_refined

            if plot_fl:
                plt.figure()
                plt.plot(self.raw_time, self.voltage[i])
                plt.plot(self.raw_time,
                         triangle.fit(self.raw_time, self.voltage[i]).fit_y)
                for peak in self.peaks:
                    plt.axvline(x=peak,
                                linestyle='dashed',
                                linewidth=1,
                                color='r')

            if self.combine_sweeps_fl:
                skip = 2
                sweep_fitter = triangle
            else:
                skip = 1
                sweep_fitter = f.StraightLineFitter()

            # print('peaks_len = {}'.format(len(self.peaks) - skip))
            for j in range(len(self.peaks) - skip):
                sweep_start = np.abs(self.raw_time - self.peaks[j]).argmin()
                sweep_stop = np.abs(self.raw_time -
                                    self.peaks[j + skip]).argmin()

                sweep_voltage = self.voltage[i][sweep_start:sweep_stop]
                sweep_time = self.raw_time[sweep_start:sweep_stop]

                if filter_arcs_fl:
                    sweep_fit = sweep_fitter.fit(sweep_time, sweep_voltage)
                    self.max_voltage.append(
                        (np.max(np.abs(sweep_voltage - sweep_fit.fit_y))))
                    if i == 0 and plot_fl:
                        sweep_fit.plot()
                    if np.max(
                            np.abs(sweep_voltage -
                                   sweep_fit.fit_y)) > self._ARCING_THRESHOLD:
                        self.arcs.append(np.mean(sweep_time))
                        continue

                # sweep_current = [current[sweep_start:sweep_stop] for current in self.current]
                sweep_current = self.current[i][sweep_start:sweep_stop]

                # Reverse alternate sweeps if not operating in combined sweeps mode, so
                if not self.combine_sweeps_fl and sweep_voltage[
                        0] > sweep_voltage[-1]:
                    # sweep_voltage = np.array(list(reversed(sweep_voltage)))
                    # sweep_time = np.array(list(reversed(sweep_time)))
                    # sweep_current = np.array(list(reversed(sweep_current)))
                    sweep_time = np.flip(sweep_time)
                    sweep_voltage = np.flip(sweep_voltage)
                    sweep_current = np.flip(sweep_current)

                # Create IVData objects for each sweep (or sweep pair)
                # TODO: What's the std_err_scaler doing here? Look through commits
                self.iv_arrs[i].append(
                    iv.IVData(sweep_voltage,
                              sweep_current,
                              sweep_time,
                              std_err_scaler=0.95))
Exemplo n.º 5
0
    def multi_fit(self,
                  sat_region=_DEFAULT_STRAIGHT_CUTOFF,
                  stage_2_guess=None,
                  iv_fitter=None,
                  sat_fitter=None,
                  fix_vf_fl=False,
                  plot_fl=False,
                  print_fl=False,
                  minimise_temp_fl=True,
                  trim_to_floating_fl=True,
                  **kwargs):
        """
        Multi-stage fitting method using an initial straight line fit to the
        saturation region of the IV curve (decided by the sat_region kwarg). The
        fitted I_sat is then left fixed while T_e and a are found with a
        2-parameter fit, which gives the guess parameters for an unconstrained
        full IV fit.

        :param sat_region:  (Integer) Threshold voltage value below which the
                            'Straight section' is defined. The straight section
                            is fitted to get an initial value of saturation
                            current for subsequent fits.
        :param sat_fitter:  Fitter object to be used for the initial saturation
                            region fit
        :param stage_2_guess:
                            Tuple-like containing starting values for all
                            parameters in iv_fitter, to be used as initial
                            parameters in the second stage fit. Note that only
                            the temperature (and optionally sheath expansion
                            parameter) will be used, as the isat will
                            already have a value (by design) and the floating
                            potential is set at the interpolated value. These
                            will be overwritten if present. Default behaviour
                            (stage_2_guess=None) is to use the defaults on the
                            iv_fitter object.
        :param iv_fitter:   Fitter object to be used for the fixed-I_sat fit and
                            the final, full, free fit.
        :param plot_fl:     (Boolean) If true, plots the output of all 3 stages
                            of fitting. Default is False.
        :param fix_vf_fl:   (Boolean) If true, fixes the floating potential for
                            all 3 stages of fitting. The value used is the
                            interpolated value of Voltage where Current = 0.
                            Default is False.
        :param print_fl:    (Boolean) If true, prints fitter information to
                            console.
        :param minimise_temp_fl:
                            (Boolean) Flag to control whether multiple final
                            fits are performed at a range of upper indices past
                            the floating potential, with the fit producing the
                            lowest temperature returned.
        :param trim_to_floating_fl:
                            (Boolean) Flag to control whether to truncate the IV
                            characteristic to values strictly below the floating
                            potential before fitting. This is ignored by the
                            minimisation routine, so the full IV will be used in
                            that case.
        :return:            (IVFitData) Full 4-parameter IVFitData object

        """
        import flopter.core.fitters as f

        if iv_fitter is None or not isinstance(iv_fitter, f.IVFitter):
            iv_fitter = f.FullIVFitter()
        if not isinstance(sat_fitter, f.GenericCurveFitter):
            if sat_fitter is not None and print_fl:
                print(
                    'Provided sat_fitter is not a valid child of GenericCurveFitter, continuing with the default \n'
                    'straight line fitter.')
            sat_fitter = f.StraightLineFitter()

        if print_fl:
            print(f'Running saturation region fit with {sat_fitter.name}, \n'
                  f'running subsequent IV fits with {iv_fitter.name}')

        # find floating potential and max potential
        v_f = f.IVFitter.find_floating_pot_iv_data(self)

        if trim_to_floating_fl:
            iv_data_trim = self.get_below_floating(v_f=v_f, print_fl=print_fl)
        else:
            iv_data_trim = self.copy()

        # Find and fit straight section
        str_sec = np.where(iv_data_trim[c.POTENTIAL] <= sat_region)
        iv_data_ss = IVData.non_contiguous_trim(iv_data_trim, str_sec)
        if fix_vf_fl and c.FLOAT_POT in sat_fitter:
            sat_fitter.set_fixed_values({c.FLOAT_POT: v_f})

        # Attempt first stage fit
        try:
            stage1_f_data = sat_fitter.fit(iv_data_ss[c.POTENTIAL],
                                           iv_data_ss[c.CURRENT],
                                           sigma=iv_data_ss[c.SIGMA])
        except RuntimeError as e:
            raise MultiFitError(f'RuntimeError occured in stage 1. \n'
                                f'Original error: {e}')

        # Use I_sat value to fit a fixed_value 4-parameter IV fit
        if c.ION_SAT in sat_fitter:
            isat_guess = stage1_f_data.get_isat()
        else:
            isat_guess = stage1_f_data.fit_function(sat_region)

        if fix_vf_fl:
            iv_fitter.set_fixed_values({
                c.FLOAT_POT: v_f,
                c.ION_SAT: isat_guess
            })
        else:
            iv_fitter.set_fixed_values({c.ION_SAT: isat_guess})

        if stage_2_guess is None:
            stage_2_guess = list(iv_fitter.default_values)
        elif len(stage_2_guess) != len(iv_fitter.default_values):
            raise ValueError(
                f'stage_2_guess is not of the appropriate length ({len(stage_2_guess)}) for use as initial '
                f'parameters in the given iv_fitter (should be length {len(iv_fitter.default_values)}).'
            )
        stage_2_guess[iv_fitter.get_isat_index()] = isat_guess
        stage_2_guess[iv_fitter.get_vf_index()] = v_f

        # Attempt second stage fit
        try:
            stage2_f_data = iv_fitter.fit_iv_data(iv_data_trim,
                                                  sigma=iv_data_trim[c.SIGMA],
                                                  initial_vals=stage_2_guess)
        except RuntimeError as e:
            raise MultiFitError(f'RuntimeError occured in stage 2. \n'
                                f'Original error: {e}')

        # Do a full 4 parameter fit with initial guess params taken from previous fit
        params = stage2_f_data.fit_params.get_values()
        iv_fitter.unset_fixed_values()
        if fix_vf_fl:
            iv_fitter.set_fixed_values({c.FLOAT_POT: v_f})

        # Attempt third stage fit. Option to fit to multiple values past the floating potential to minimise the
        # temperature of the fit or just to the floating potential.
        try:
            if minimise_temp_fl:
                stage3_f_data = self.fit_to_minimum(initial_vals=params,
                                                    fitter=iv_fitter,
                                                    plot_fl=plot_fl,
                                                    **kwargs)
            else:
                stage3_f_data = iv_fitter.fit_iv_data(
                    iv_data_trim,
                    initial_vals=params,
                    sigma=iv_data_trim[c.SIGMA])
        except RuntimeError as e:
            raise MultiFitError(f'RuntimeError occured in stage 3. \n'
                                f'Original error: {e}')

        if plot_fl:
            fig, ax = plt.subplots(3, sharex=True, sharey=True)
            stage1_f_data.plot(ax=ax[0])
            ax[0].set_xlabel('')
            ax[0].set_ylabel('Current (A)')

            stage2_f_data.plot(ax=ax[1])
            ax[1].set_xlabel('')
            ax[1].set_ylabel('Current (A)')

            stage3_f_data.plot(ax=ax[2])
            ax[2].set_xlabel('Voltage (V)')
            ax[2].set_ylabel('Current (A)')

            fig.suptitle('lower_offset = {}, upper_offset = {}'.format(
                self.trim_beg, self.trim_end))

        return stage3_f_data