Ejemplo n.º 1
0
    def fit_powerlaw(powers, intensities, guess=None):
        """
		This function fits a powerlaw to data.

		:param powers: A quantity or array of powers. If no quantity, milliwatts are assumed.

		:param intensities: Quantity or array: The corresponding intensity values to powers.

		:return: The powerlaw coefficients of the fitted polynom.
		"""
        if u.is_quantity(powers):
            assert u.same_dimension(powers, "watts")
            powers = u.to_ureg(powers)
        else:
            powers = u.to_ureg(powers, 'mW')
        intensities = u.to_ureg(intensities)

        if guess is None:
            guess_amplitude = 1.
            guess_exponent = 2.
            guess_offset = min(intensities).magnitude
        else:
            assert len(guess) == 3
            guess_amplitude = u.to_ureg(guess[0], str(powers.units)).magnitude
            guess_exponent = u.to_ureg(guess[1], 'dimensionless').magnitude
            guess_offset = u.to_ureg(guess[2], str(powers.units)).magnitude
        guess = (guess_amplitude, guess_exponent, guess_offset)

        return scipy.optimize.curve_fit(Powerlaw.fitfunction,
                                        powers.magnitude,
                                        intensities.magnitude,
                                        guess,
                                        maxfev=100000)
Ejemplo n.º 2
0
    def fit_fermi_edge(energies, intensities, guess=None):
        """
		This function fits a fermi edge to data. Uses numpy.optimize.curve_fit under the hood.

		:param energies: A quantity or array of energies. If no quantity, electronvolts are assumed.

		:param intensities: Quantity or array: The corresponding intensity values to powers.

		:param guess: optional: A tuple of start parameters (E_f, dE, c, d) as defined in fermi_edge method.

		:return: The coefficients and uncertainties of the fitted fermi edge E_f, dE, c, d, as defined in fermi_edge
			method.
		"""
        if u.is_quantity(energies):
            assert u.same_dimension(energies, "eV")
            energies = u.to_ureg(energies)
        else:
            energies = u.to_ureg(energies, 'eV')
        intensities = u.to_ureg(intensities)
        if guess is None:
            guess = (29.6, 0.1, 1.0, 0.01)  # Just typical values
        else:  # to assure the guess is represented in the correct units:
            energyunit = energies.units
            countsunit = intensities.units
            unitslist = [energyunit, energyunit, countsunit, countsunit]
            guesslist = []
            for guesselement, guessunit in zip(guess, unitslist):
                guesslist.append(u.to_ureg(guesselement, guessunit).magnitude)
            guess = tuple(guess)
        return curve_fit(fermi_edge, energies.magnitude, intensities.magnitude,
                         guess)
Ejemplo n.º 3
0
    def from_xy(cls, energies, intensities, guess):
        if u.is_quantity(energies):
            assert u.same_dimension(energies, "eV")
            energies = u.to_ureg(energies)
        else:
            energies = u.to_ureg(energies, 'eV')
        intensities = u.to_ureg(intensities)

        energyunit = str(energies.units)
        countsunit = str(intensities.units)

        f = cls()
        f.coeffs, f.accuracy = cls.fit_fermi_edge(energies, intensities, guess)
        f.E_f_unit = energyunit
        f.dE_unit = energyunit
        f.c_unit = countsunit
        f.d_unit = countsunit
        return f
Ejemplo n.º 4
0
    def fit_powerlaw(powers, intensities):
        """
		This function fits a powerlaw to data.

		:param powers: A quantity or array of powers. If no quantity, milliwatts are assumed.

		:param intensities: Quantity or array: The corresponding intensity values to powers.

		:return: The powerlaw coefficients of the fitted polynom.
		"""
        if u.is_quantity(powers):
            assert u.same_dimension(powers, "watts")
            powers = u.to_ureg(powers)
        else:
            powers = u.to_ureg(powers, 'mW')
        intensities = u.to_ureg(intensities)

        # Do the fit with numpy.ma functions to ignore invalid numbers like log(0)
        # (these occur often when using dark count subtraction)
        return numpy.ma.polyfit(numpy.ma.log(powers.magnitude),
                                numpy.ma.log(intensities.magnitude),
                                deg=1,
                                full=False)
Ejemplo n.º 5
0
    def __init__(self, data, axis=0, transformed_axis_unit=None):
        """
        The initializer.

        :param data: The data to transform.
        :type data: :class:`~snomtools.data.datasets.DataSet` or :class:`~snomtools.data.datasets.ROI`

        :param axis: The axis along which to apply the FFT, given as a valid identifier of the axis.
        :type axis: int or str

        :param transformed_axis_unit: The unit for the generated frequency axis.
            Must be compatible with the unit of the axis to transform,
            meaning the two units must be inverse of each other.
            If not given, the inverse unit of the Axis to transform is taken,
            e.g. an Axis of unit `fs` is transformed to `1/fs`.
        :type transformed_axis_unit: str or pint.Unit
        """
        assert isinstance(data, (ds.DataSet, ds.ROI))
        self.indata = data
        self.axis_to_transform_id = data.get_axis_index(axis)
        self.axis_to_transform = data.get_axis(self.axis_to_transform_id)

        # Check if axis is ok:
        assert self.axis_to_transform.is_linspaced(
        ), "Cannot fourier transform an Axis not evenly spaced."
        assert self.axis_to_transform.size > 1, "Cannot fourier transform a single element."

        self.sampling_delta = self.axis_to_transform.spacing()
        if transformed_axis_unit is None:
            self.axis_freq_unit = (1 / self.axis_to_transform.units).units
        else:
            assert u.same_dimension(self.axis_to_transform.data,
                                    1 / u.to_ureg(1, transformed_axis_unit))
            self.axis_freq_unit = transformed_axis_unit

        self.result = None
Ejemplo n.º 6
0
    def __init__(self,
                 data,
                 fundamental_frequency,
                 axis=0,
                 max_order=2,
                 widths=None,
                 butter_orders=5):
        """
        The initializer.

        :param data: The data to filter.
        :type data: :class:`~snomtools.data.datasets.DataSet` or :class:`~snomtools.data.datasets.ROI`

        :param fundamental_frequency:
        :type fundamental_frequency: pint.Quantity

        :param axis: The axis along which to apply the frequency filter, given as a valid identifier of the axis.
        :type axis: int or str

        :param max_order: The maximum frequency order to calculate. Default is `2` for up to omega_2 component.
        :type max_order: int

        :param widths: The half-widths of the frequency window around each multiple of the fundamental frequency.
        :type widths: list[pint.Quantity or float]

        :param butter_orders: The order of each butter filter defined for the frequency components.
            Individual orders for each component can be given as a list of ints.
        :type butter_orders: int or list[int]
        """
        assert isinstance(data, (ds.DataSet, ds.ROI))
        self.indata = data
        self.filter_axis_id = data.get_axis_index(axis)
        self.filter_axis = data.get_axis(self.filter_axis_id)

        # Check if axis is ok:
        assert self.filter_axis.is_linspaced(
        ), "Cannot fourier transform an Axis not evenly spaced."
        assert self.filter_axis.size > 1, "Cannot fourier transform a single element."

        self.sampling_delta = self.filter_axis.spacing()

        if fundamental_frequency is u.to_ureg(
                fundamental_frequency
        ).magnitude:  # Fallback for numerical data.
            fundamental_frequency = u.to_ureg(fundamental_frequency.magnitude,
                                              (1 /
                                               self.filter_axis.units).units)
        else:
            fundamental_frequency = u.to_ureg(fundamental_frequency)
        assert u.same_dimension((1 / self.filter_axis.units), fundamental_frequency), \
            "Given frequency dimensionality does not match axis."
        self.axis_freq_unit = fundamental_frequency.units
        self.omega = fundamental_frequency

        if widths is None:
            assert max_order <= 3, "Give filter window widths for order >3, defaults are not defined!"
            self.widths = [
                self.default_widths[i] for i in range(max_order + 1)
            ]
        else:
            self.widths = u.to_ureg(widths, self.axis_freq_unit)

        if isinstance(butter_orders, int):
            # noinspection PyUnusedLocal
            butter_orders = [butter_orders for i in range(max_order + 1)]
        else:
            assert len(butter_orders
                       ) == max_order + 1, "Wrong number of Butter orders."

        self.butters = []
        # Initialize lowpass filter for low-frequency component omega_0:
        self.butters.append(
            Butterfilter(self.sampling_delta,
                         highcut=self.widths[0],
                         order=butter_orders[0]))
        # Initialize bandbass filters for fundamental and its multiples:
        for freq_component in range(1, max_order + 1):
            self.butters.append(
                Butterfilter(self.sampling_delta,
                             lowcut=self.omega * freq_component -
                             self.widths[freq_component],
                             highcut=self.omega * freq_component +
                             self.widths[freq_component],
                             order=butter_orders[freq_component]))

        self.result = None
Ejemplo n.º 7
0
    def __init__(self,
                 data,
                 fitaxis_ID="delay",
                 laser_lambda=u.to_ureg(400, 'nm'),
                 laser_AC_FWHM=None,
                 data_AC_FWHM=None,
                 time_zero=u.to_ureg(0, 'fs'),
                 max_lifetime=u.to_ureg(200, 'fs'),
                 max_time_zero_offset=u.to_ureg(20, 'fs'),
                 cuda_IAC_stepsize=None,
                 fit_mode='lorentz'):
        """
        The initializer, which builds the OBEfit object according to the given data and parameters.
        The actual fit and return of results is then done with :func:`~OBEfit_Copol.obefit`.

        :param data: The data to evaluate.
        :type data: :class:`~snomtools.data.datasets.DataSet`

        :param fitaxis_ID: A valid identifier of the axis along which the autocorrelation was measured.
            Typically something like "delay" (the default).
        :type fitaxis_ID: str *or* int

        :param laser_lambda: The laser wavelength of the measurement.
            If a numeric value is given, nanometers are assumed.
        :type laser_lambda: pint Quantity *or* numeric

        :param laser_AC_FWHM: The laser (second order) Autocorrelation FWHM,
            gained from an autocorrelation measurement of the laser pulses without a lifetime effect,
            e.g. with SHG or 2PPE through an intermediate state with neglectable lifetime.
            This is an important parameter, as the lifetime can only be evaluated out of the 2PPE Autocorrelation,
            if the laser pulse length is known.
            If a numeric value is given, femtoseconds are assumed.
        :type laser_AC_FWHM: pint Quantity *or* numeric

        :param data_AC_FWHM: If an FWHM map of the data was evaluated in advance, 
            (e.g. using :class:`snomtools.data.fits.Lorentz_Fit_nD`)
            the corresponding data can be given here.
            The data must have the same dimensions as :attr:`~OBEfit_Copol.resultshape`, 
            meaning the input data shape reduced by the delay axis,
            and must contain a :class:`~snomtools.data.datasets.DataArray` containing the autocorrelation FHWM
            with label `fwhm` or at index `0`.
            If `amplitude` and `background` are also present, better fit start parameters are derived from them.
            If not given, the values are extracted from `data` by fitting Lorentzians along the delay axis
            with :class:`snomtools.data.fits.Lorentz_Fit_nD`.
        :type data_AC_FWHM: :class:`~snomtools.data.datasets.DataSet` *or* :class:`~snomtools.data.datasets.ROI`

        :param time_zero: The actual time zero on the delay axis.
            If a numeric value is given, femtoseconds are assumed.
        :type time_zero: pint Quantity *or* numeric

        :param max_lifetime: The maximum lifetime used as boundary for the obe fit.
            If a numeric value is given, femtoseconds are assumed.
        :type max_lifetime: pint Quantity *or* numeric

        :param max_time_zero_offset: The offset on the time axis from the predefined time zero,
            used as boundary for the obe fit.
            If a numeric value is given, femtoseconds are assumed.
        :type max_time_zero_offset: pint Quantity *or* numeric

        :param cuda_IAC_stepsize: The discretization step size for the interferometric autocorrelation calculated on
            the GPU.
            For non phase-resolved evaluation, the omega_0 component is extracted afterwards with a lowpass filter.
            This means you should NOT set this to large values to save calculation time!
            Use something well below optical cycle (e.g. 0.2fs) to have good IAC!
            The default is calculated from the laser wavelenth, to (1/5.5) of the optical cycle
            (avoids sampling artifacts).
        :type cuda_IAC_stepsize: pint Quantity *or* numeric

        :param mode: The fit mode for the start parameter approximation.
            Choose between 'lorentz' (default), 'gauss', or 'sech2'.
        :type mode: str

        .. warning::
            If no value is given for `laser_AC_FWHM`, the value is guessed guessed from `data_AC_FWHM`,
            by taking its minimum minus 1 femtosecond.
            This only works if the data region contains a value with neglectable lifetime!
        """
        assert isinstance(data, (ds.DataSet, ds.ROI))
        self.data = data
        self.fitaxis_ID = data.get_axis_index(fitaxis_ID)

        timeunit = self.data.get_axis(self.fitaxis_ID).units
        countunit = self.data.get_datafield(0).units

        # Process optional args:
        self.laser_lambda = u.to_ureg(laser_lambda, 'nm')
        if data_AC_FWHM:
            # Check if dimensions and axes fit:
            assert isinstance(data_AC_FWHM, (ds.DataSet, ds.ROI))
            assert (data_AC_FWHM.shape == self.resultshape)
            self.data_AC_FWHM = data_AC_FWHM
            try:
                self.AC_FWHM = self.data_AC_FWHM.get_datafield('fwhm')
            except AttributeError:
                self.AC_FWHM = self.data_AC_FWHM.get_datafield(0)
            assert u.same_dimension(self.AC_FWHM, u.to_ureg('1 fs'))
            try:
                self.AC_amplitude = self.data_AC_FWHM.get_datafield(
                    'amplitude')
            except AttributeError:
                self.AC_amplitude = None
            try:
                self.AC_background = self.data_AC_FWHM.get_datafield(
                    'background')
            except AttributeError:
                self.AC_background = None
        else:
            self.data_AC_FWHM = None
            self.AC_FWHM = None
            self.AC_amplitude = None
            self.AC_background = None
        if laser_AC_FWHM:
            self.laser_AC_FWHM = u.to_ureg(laser_AC_FWHM, 'fs')
        else:
            self.laser_AC_FWHM = u.to_ureg(-1, 'fs') + self.AC_FWHM.min()
            print(
                "WARNING: No laser AC FWHM given. Guessing guessing from Data FWHM - 1 fs: {0}"
                .format(self.laser_AC_FWHM))
        self.time_zero = u.to_ureg(time_zero, 'fs')
        self.max_lifetime = u.to_ureg(max_lifetime, 'fs')
        self.max_time_zero_offset = u.to_ureg(max_time_zero_offset, 'fs')
        if cuda_IAC_stepsize is None:
            # Optical cycle /4 = Nyquist for omega_2
            self.cuda_IAC_stepsize = round(
                laser_lambda.to('fs', 'light') / 5.5, 3)
            self.cuda_IAC_stepsize = max(self.cuda_IAC_stepsize,
                                         u.to_ureg(0.001,
                                                   'fs'))  # limit to 1 as.
        else:
            self.cuda_IAC_stepsize = u.to_ureg(cuda_IAC_stepsize, 'fs')

        # Generate labels and info for output data:
        timeunit_SI = u.latex_si(self.data.get_axis(self.fitaxis_ID))
        countunit_SI = u.latex_si(self.data.get_datafield(0))
        self.result_params = {
            'lifetimes': {
                'unit': timeunit,
                'plotlabel': "Intermediate State Lifetime / " + timeunit_SI
            },
            'amplitude': {
                'unit': countunit,
                'plotlabel': "AC Amplitude / " + countunit_SI
            },
            'offset': {
                'unit': countunit,
                'plotlabel': "AC Background / " + countunit_SI
            },
            'center': {
                'unit': timeunit,
                'plotlabel': "AC Center / " + timeunit_SI
            },
            'lifetimes_accuracy': {
                'unit':
                timeunit,
                'plotlabel':
                "Intermediate State Lifetime Fit Accuracy / " + timeunit_SI
            },
            'amplitude_accuracy': {
                'unit': countunit,
                'plotlabel': "AC Amplitude Fit Accuracy / " + countunit_SI
            },
            'offset_accuracy': {
                'unit': countunit,
                'plotlabel': "AC Background Fit Accuracy / " + countunit_SI
            },
            'center_accuracy': {
                'unit': timeunit,
                'plotlabel': "AC Center Fit Accuracy / " + timeunit_SI
            }
        }
        self.timeunit = timeunit
        self.countunit = countunit
        self.result = None