Esempio n. 1
0
    def test_copy(self):
        # check simple Parameters.copy() does not fail
        # on non-trivial Parameters
        p1 = Parameters()
        p1.add('t', 2.0, min=0.0, max=5.0)
        p1.add('x', 10.0)
        p1.add('y', expr='x*t + sqrt(t)/3.0')

        p2 = p1.copy()
        assert(isinstance(p2, Parameters))
        assert('t' in p2)
        assert('y' in p2)
        assert(p2['t'].max < 6.0)
        assert(np.isinf(p2['x'].max) and p2['x'].max > 0)
        assert(np.isinf(p2['x'].min) and p2['x'].min < 0)
        assert('sqrt(t)' in p2['y'].expr )
        assert(p2._asteval is not None)
        assert(p2._asteval.symtable is not None)
        assert((p2['y'].value > 20) and (p2['y'].value < 21))
Esempio n. 2
0
    def fitSuite(self, params: lmfit.Parameters = None):
        """
        Fits the model by adjusting values of parameters based on
        differences between simulated and provided values of
        floating species.

        Parameters
        ----------
        params: lmfit.parameters
            starting values of parameters
        max_nfev: int
            Maximum number of iterations for an evaluation

        Example
        -------
        f.fitSuite()
        """
        if params is None:
            initialParameters = self.parameterManager.mkParameters()
        else:
            initialParameters = params.copy()
        # Setup parallel servers if needed
        if self._isParallel:
            fitters = [f.copy() for f in self.fitterDct.values()]
            self.manager = ServerManager(ResidualsServer,
                                         fitters,
                                         logger=self.logger)
        # Do the optimization
        self.optimizer = Optimizer.optimize(self.calcResiduals,
                                            initialParameters,
                                            self._fitterMethods,
                                            logger=self.logger,
                                            isCollect=True,
                                            numRestart=self._numRestart)
        # Assign fitter results to each model
        self.parameterManager.updateValues(self.params)
        for modelName, fitter in self.fitterDct.items():
            fitter.suiteFitterParams = self.parameterManager.mkParameters(
                modelName=modelName)
        # Clean up
        if self._isParallel:
            self.manager.stop()
            self.manager = None
Esempio n. 3
0
  def updateModel(self):
    # create a parameter container for each sub model respectively
    p = Parameters()
    for parameter in self.params:
      if not parameter.rsplit('_',1)[0] in self.ptrExperiment.datasetSpecificParams:
        # parameters that are not dataset specific are shared by all datasets
        p.add(self.params[parameter])

    for i in range(self.nModelsets):
      subModel = self.getModelset(i)
      subP = p.copy()
      for parameter in self.ptrExperiment.datasetSpecificParams:
        specificTags = self.datasetSpecificParams[parameter]

        if isinstance(subModel.suffix, str):
          if subModel.suffix in specificTags:
            suffixedParameter = parameter + '_' + subModel.suffix

            if suffixedParameter in self.params:
              specParam = self.params[suffixedParameter]
              subP.add(
                parameter, specParam.value,
                min=specParam.min, max=specParam.max, vary=specParam.vary
              )
        elif isinstance(subModel.suffix, list):
          for suffix in subModel.suffix:
            if suffix in specificTags:
              suffixedParameter = parameter + '_' + suffix
              if suffixedParameter in self.params:
                specParam = self.params[suffixedParameter]
                subP.add(
                  parameter, specParam.value,
                  min=specParam.min, max=specParam.max, vary=specParam.vary
                )

      for parameter in self.combinedParameters:
        if parameter in subP and self.combinedParameters[parameter] in subP:
          subP[parameter].value = subP[self.combinedParameters[parameter]].value
      subModel.params = subP
      subModel.calcDecoratedModel()
Esempio n. 4
0
    def __init__(self,
                 x,
                 y,
                 t,
                 w=None,
                 bbox=(None, None),
                 k=3,
                 ext=0,
                 check_finite=False,
                 adaptive=True,
                 tolerance=1E-7):
        """One-dimensional spline with explicit internal knots."""

        if check_finite:
            # check here the arrays instead of in the base class
            # (note that in the call to super(...).__init(...) the
            # parameter check_finite is set to False)
            w_finite = np.isfinite(x).all() if w is not None else True
            if not np.isfinite(x).all() or not np.isfinite(y).all() or \
                    not w_finite:
                raise ValueError('Input(s) must not contain ' 'NaNs or infs.')

            if np.asarray(x).ndim != 1:
                raise ValueError('x array must have dimension 1')

            if np.asarray(y).ndim != 1:
                raise ValueError('y array must have dimension 1')

            if np.asarray(x).shape != np.asarray(y).shape:
                raise ValueError('x and y arrays must have the same length')

            if not all(np.diff(x) > 0.0):
                raise ValueError('x array must be strictly increasing')

        # initial inner knot location (equidistant or fixed)
        try:
            nknots = int(t)
            if nknots > 0:
                xmin = x[0]
                xmax = x[-1]
                deltax = (xmax - xmin) / float(nknots + 1)
                xknot = np.zeros(nknots)
                for i in range(nknots):
                    xknot[i] = (xmin + float(i + 1) * deltax)
            else:
                xknot = np.array([])
        except (ValueError, TypeError):
            xknot = np.asarray(t)
            if check_finite:
                if not np.isfinite(xknot).all():
                    raise ValueError('Interior knots must not contain '
                                     'NaNs or infs.')
                if xknot.ndim != 1:
                    raise ValueError('t array must have dimension 1')
            nknots = len(xknot)

        # adaptive knots
        if nknots > 0 and adaptive:
            xknot_backup = xknot.copy()

            # normalise the x and y arrays to the [-1, +1] interval
            xmin = x[0]
            xmax = x[-1]
            ymin = np.min(y)
            ymax = np.max(y)
            bx = 2.0 / (xmax - xmin)
            cx = (xmin + xmax) / (xmax - xmin)
            by = 2.0 / (ymax - ymin)
            cy = (ymin + ymax) / (ymax - ymin)
            xnor = bx * np.asarray(x) - cx
            ynor = by * np.asarray(y) - cy
            xknotnor = bx * xknot - cx
            params = Parameters()
            for i in range(nknots):
                if i == 0:
                    xminknot = bx * x[0] - cx
                    xmaxknot = (xknotnor[i] + xknotnor[i + 1]) / 2.0
                elif i == nknots - 1:
                    xminknot = (xknotnor[i - 1] + xknotnor[i]) / 2.0
                    xmaxknot = bx * x[-1] - cx
                else:
                    xminknot = (xknotnor[i - 1] + xknotnor[i]) / 2.0
                    xmaxknot = (xknotnor[i] + xknotnor[i + 1]) / 2.0
                params.add(name=f'xknot{i:03d}',
                           value=xknotnor[i],
                           min=xminknot,
                           max=xmaxknot,
                           vary=True)
            self._params = params.copy()
            fitter = Minimizer(userfcn=fun_residuals,
                               params=params,
                               fcn_args=(xnor, ynor, w, bbox, k, ext))
            try:
                self._result = fitter.scalar_minimize(method='Nelder-Mead',
                                                      tol=tolerance)
                xknot = [item.value for item in self._result.params.values()]
                xknot = (np.asarray(xknot) + cx) / bx
            except ValueError:
                print('Error when fitting adaptive splines. '
                      'Reverting to initial knot location.')
                xknot = xknot_backup.copy()
                self._result = None
        else:
            self._params = None
            self._result = None

        # final fit
        super(AdaptiveLSQUnivariateSpline, self).__init__(x=x,
                                                          y=y,
                                                          t=xknot,
                                                          w=w,
                                                          bbox=bbox,
                                                          k=k,
                                                          ext=ext,
                                                          check_finite=False)
Esempio n. 5
0
    def fitModel(self, params: lmfit.Parameters = None, max_nfev: int = 100):
        """
        Fits the model by adjusting values of parameters based on
        differences between simulated and provided values of
        floating species.

        Parameters
        ----------
        params: starting values of parameters
        max_nfev: maximum number of function evaluations

        Example
        -------
        f.fitModel()
        """
        ParameterDescriptor = collections.namedtuple(
            "ParameterDescriptor",
            "params method std minimizer minimizerResult")
        block = Logger.join(self._loggerPrefix, "fitModel")
        guid = self.logger.startBlock(block)
        self._initializeRoadrunnerModel()
        if self.parametersToFit is None:
            # Compute fit and residuals for base model
            self.params = None
        else:
            if params is None:
                params = self.mkParams()
            # Fit the model to the data using one or more methods.
            # Choose the result with the lowest residual standard deviation
            paramDct = {}
            for method in self._fitterMethods:
                for _ in range(self._numFitRepeat):
                    minimizer = lmfit.Minimizer(self._residuals,
                                                params,
                                                max_nfev=max_nfev)
                    try:
                        minimizerResult = minimizer.minimize(method=method,
                                                             max_nfev=max_nfev)
                    except Exception as excp:
                        msg = "Error minimizing for method: %s" % method
                        self.logger.error(msg, excp)
                        continue
                    params = minimizerResult.params
                    std = np.std(self._residuals(params))
                    if method in paramDct.keys():
                        if std >= paramDct[method].std:
                            continue
                    paramDct[method] = ParameterDescriptor(
                        params=params.copy(),
                        method=method,
                        std=std,
                        minimizer=minimizer,
                        minimizerResult=minimizerResult,
                    )
            if len(paramDct) == 0:
                msg = "*** Minimizer failed for this model and data."
                raise ValueError(msg)
            # Select the result that has the smallest residuals
            sortedMethods = sorted(paramDct.keys(),
                                   key=lambda m: paramDct[m].std)
            bestMethod = sortedMethods[0]
            self.params = paramDct[bestMethod].params
            self.minimizer = paramDct[bestMethod].minimizer
            self.minimizerResult = paramDct[bestMethod].minimizerResult
        # Ensure that residualsTS and fittedTS match the parameters
        self.updateFittedAndResiduals(params=self.params)
        self.logger.endBlock(guid)
Esempio n. 6
0
class LMFitModel():
    """
    Wrapper class for the lmfit package. Acts both as module usable in
    scripts as well as core logic class for the lmfit part in kMap.py.

    For an example on how to use it please see the 'test_PTCDA' test in
    the 'kmap.tests.test_lmfit' file.

    ATTENTION: Please do not set any attributes manually. Instead use
    the appropriate "set_xxx" method instead.
    """
    def __init__(self, sliced_data, orbitals):
        """
        Args:
            sliced_data (SlicedData): A single SlicedData object.
            orbitals (OrbitalData or list): A single OrbitalData object
                or a list of OrbitalData objects.
                ATTENTION: An Orbital object is NOT sufficient. Please
                use the OrbitalData wrapping class instead.
        """

        self.axis = None
        self.crosshair = None
        self.symmetrization = 'no'
        self.sliced_symmetry = ['no', False]
        self.background_equation = ['0', []]
        self.Ak_type = 'no'
        self.polarization = 'p'
        self.slice_policy = [0, [0], False]
        self.method = {'method': 'leastsq', 'xtol': 1e-12}
        self.region = ['all', False]
        self.s_share = 0.694
        self.axis_order = [0, 1, 2]

        self._set_sliced_data(sliced_data)
        self._add_orbitals(orbitals)

        self._set_parameters()

    def set_crosshair(self, crosshair):
        """A setter method to set a custom crosshair. If none is set
        when a region restriction is applied, a CrosshairAnnulusModel
        will be created.

        Args:
            crosshair (CrosshairModel): A crosshair model for cutting
                the data for any region-restriction.
                ATTENTION: The passed CrosshairModel has to support the
                region restriction you want to use.
        """

        if crosshair is None or isinstance(crosshair, CrosshairModel):
            self.crosshair = crosshair

        else:
            raise TypeError('crosshair has to be of type %s (is %s)' %
                            (type(CrosshairModel), type(crosshair)))

    def set_axis(self, axis):
        """A setter method to set an axis for the interpolation onto a
        common grid. Default is the x-axis of the first slice in the
        list of slices chosen to be fitted.

        Args:
            axis (np.array): 1D array defining the common axis (and grid
            as only square kmaps are supported) for the subtraction.
        """

        self.axis = axis

    def set_axis_by_step_size(self, range_, step_size):
        """A convenience setter method to set an axis by defining the
        range and the step size.

        Args:
            range_ (list): A list of min and max value.
            step_size (float): A number denoting the step size.
        """

        num = step_size_to_num(range_, step_size)
        self.set_axis(axis_from_range(range_, num))

    def set_axis_by_num(self, range_, num):
        """A convenience setter method to set an axis by defining the
        range and the number of grid points.

        Args:
            range_ (list): A list of min and max value.
            num (int): An integer denoting the number of grid points.
        """

        self.set_axis(axis_from_range(range_, num))

    def set_symmetrization(self, symmetrization):
        """A setter method to set the type of symmetrization for the
        orbital kmaps. Default is 'no'.

        Args:
            symmetrization (str): See 'get_kmap' from
            'kmap.library.orbital.py' for information.
        """

        self.symmetrization = symmetrization

    def set_sliced_symmetrization(self, symmetry, mirror):
        """A setter method to set the type of symmetrization for the
        sliced data kmaps. Default is 'no' and no mirroring.

        Args:
            symmetry (str): See 'symmetrise' from 'kmap.library.plotdata.py'
                for information.
            mirror (bool): See 'symmetrise' from 'kmap.library.plotdata.py'
                for information.
        """

        self.sliced_symmetry = [symmetry, mirror]

    def set_region(self, region, inverted=False):
        """A setter method to set the region restriction for the lmfit
        process. Default is no region restriction ('all').

        Args:
            region (str): Supports all regions the crosshair model you
            supplied supports. See there for documentation. (default
            is a CrosshairAnnulusModel).
            inverted (bool): See your CrosshairModel for documentation.
        """

        self.region = [region, inverted]

        if region != 'all' and self.crosshair is None:
            self.crosshair = CrosshairAnnulusModel()

    def set_polarization(self, Ak_type, polarization, s_share=None):
        """A setter method to set the type of polarization for the
        orbital kmaps. Default is 'toroid' and 'p'.

        Args:
            Ak_type (str): See 'get_kmap' from
            'kmap.library.orbital.py' for information.
            polarization (str): See 'get_kmap' from
            'kmap.library.orbital.py' for information.
        """

        self.Ak_type = Ak_type
        self.polarization = polarization
        # For backwards compatability
        if s_share is not None:
            self.set_s_share(s_share)

    def set_s_share(self, s_share):
        """A setter method to set the share of s-polarized light in
        unpolarized light.

        Args:
            s_share (str): See 'get_kmap' from
            'kmap.library.orbital.py' for information.
        """

        self.s_share = s_share

    def set_slices(self, slice_indices, axis_index=0, combined=False):
        """A setter method to chose the slices to be fitted next time
        'fit()' is called. Default is [0], 0 and False.

        Args:
            slice_indices (int or list or str): Either one or more
            indices for the slices to be fitted next. Pass 'all' to use
            all slices in this axis.
            axis_index (int): Which axis in the SlicedData is used as
            slice axis.
            combined (bool): Whether to fit all slices individually or
            add all the slices for one fit instead.
        """

        if isinstance(slice_indices, str) and slice_indices == 'all':
            self.slice_policy = [
                axis_index,
                range(self.sliced_data.axes[axis_index].num), combined
            ]

        elif isinstance(slice_indices, list):
            self.slice_policy = [axis_index, slice_indices, combined]

        elif isinstance(slice_indices, range):
            self.slice_policy = [axis_index, list(slice_indices), combined]

        else:
            self.slice_policy = [axis_index, [slice_indices], combined]

    def set_fit_method(self, method, xtol=1e-7):
        """A setter method to set the method and the tolerance for the
        fitting process. Default is 'leastsq' and 1e-7.

        Args:
            method (str): See the documentation for the lmfit module.
            xtol (str): Only available for one of the following methods:
                        'leastsq', 'least_squares', 'powell'
        """

        xtol_capable_methods = ['leastsq', 'least_squares']
        if method in xtol_capable_methods:
            self.method = {'method': method, 'xtol': xtol}

        else:
            self.method = {'method': method}

    def set_background_equation(self, equation):
        """A setter method to set an custom background equation.
        Default is '1'.

        Args:
            equation (str): An equation used to calculate the background
            profile. Can use python function (e.g. abs()) and basics
            methods from the numpy module (prefix by 'np.';
            e.g. np.sqrt()). Can contain variables to be fitted.
            Variables have can only contain lower or upper case letters,
            underscores and numbers. They cannot start with numbers.
            The variables 'x' and 'y' are special and denote the x and
            y axis respectively. No variables already used outside the
            background equation (like phi) can be used.
            Here are some examples of valid variable names:
            x_s, x2, x_2, foo, this_is_a_valid_variable.
            Each variables starts with following default values:
            value=0, min=-99999.9, max=99999.9, vary=False, expr=None

            The equation will be parsed by eval. Please don't injected
            any code as it would be really easy to do so. There are no
            safeguards in place whatsoever so we (have to) trust you.
            Thanks, D.B.
        """

        try:
            compile(equation, '', 'exec')

        except:
            raise ValueError(
                'Equation is not parseable. Check for syntax errors.')

        # Pattern matches all numpy, math and builtin methods
        clean_pattern = 'np\\.[a-z1-9\\_]+|math\\.[a-z1-9\\_]+'
        for builtin in dir(builtins):
            clean_pattern += '|' + str(builtin)

        cleaned_equation = re.sub(clean_pattern, '', equation)
        # Pattern matches all text including optional underscore with
        # numbers.
        variable_pattern = '[a-zA-Z\\_]+[0-9]*'
        variables = list(set(re.findall(variable_pattern, cleaned_equation)))

        # x and y need special treatment
        if 'x' in variables:
            variables.remove('x')

        if 'y' in variables:
            variables.remove('y')

        new_variables = np.setdiff1d(variables, self.background_equation[1])
        self.background_equation = [equation, variables]
        for variable in new_variables:
            self.parameters.add(variable,
                                value=0,
                                min=-99999.9,
                                max=99999.9,
                                vary=False,
                                expr=None)

        return [self.parameters[variable] for variable in new_variables]

    def edit_parameter(self, parameter, *args, **kwargs):
        """A setter method to edit fitting settings for one parameter.
        Use this method to enable a parameter for fitting (vary=True)

        Args:
            parameter (str): Name of the parameter to be editted.
            *args & **kwargs (): Are being passed to the
            'parameter.set' method of the lmfit module. See there
            for more documentation.
        """

        self.parameters[parameter].set(*args, **kwargs)

    def matrix_inversion(self):
        """Calling this method will trigger a direct fit via matrix inversion.
        Ax = y --> A^-1y = x

        Returns:
            (list): A list of MinimizerResults. One for each slice
            fitted.
        """

        # Calculate all the orbital maps once for speed
        orbital_kmaps_vector = np.array([
            self._cut_region(self.get_orbital_kmap(orbital.ID,
                                                   self.parameters)).data
            for orbital in self.orbitals
        ])

        # Filter later for non variable parameters
        vary_vector = [
            self.parameters['w_' + str(orbital.ID)].vary
            for orbital in self.orbitals
        ]
        vary_vector.append(self.parameters['c'].vary)

        # Initial values important if orbital or background is not varied
        initials = [
            self.parameters['w_' + str(orbital.ID)].value
            for orbital in self.orbitals
        ]
        initials.append(self.parameters['c'].value)

        weights = []
        for index in self.slice_policy[1]:
            sliced_plot_data = self.get_sliced_kmap(index)
            sliced_kmap = self._cut_region(sliced_plot_data).data
            background = self._get_background(self.parameters)

            # Transform background to a equally sized map
            if not isinstance(background, np.ndarray):
                background *= np.ones(orbital_kmaps_vector[0].shape)
            background[np.isnan(sliced_kmap)] = 0
            background = self._cut_region(
                PlotData(background, sliced_plot_data.range)).data
            aux = np.append(orbital_kmaps_vector, [background], axis=0)

            # All zero background would lead to singular matrix
            if np.all(background == 0):
                vary_vector[-1] = False

            # Subtract orbitals not to be varied from the sliced data once
            for i, (kmap, initial) in enumerate(zip(aux, initials)):
                if not vary_vector[i]:
                    sliced_kmap -= initial * kmap

            N = len(aux)
            A = np.zeros((N, N))
            y = np.zeros(N)
            for i in range(N):
                y[i] = np.nansum(sliced_kmap * aux[i])
                for j in range(N):
                    A[i, j] = np.nansum(aux[i] * aux[j])

            result = np.array(initials)
            try:
                result[vary_vector] = np.linalg.solve(
                    A[np.ix_(vary_vector, vary_vector)], y[vary_vector])

            except np.linalg.LinAlgError as err:
                if 'Singular matrix' in str(err):
                    result = np.zeros(y.shape)
                    print(
                        'WARNING: Slice {index} produced a singular matrix.\nPlease make sure the background is non zero and there aren\'t identical orbitals loaded.'
                    )

                else:
                    raise

            if N == len(orbital_kmaps_vector):
                # == No background
                result = np.append(result, 0)

            weights.append([index, result])

        return self._construct_minimizer_result(weights)

    def fit(self):
        """Calling this method will trigger a lmfit with the current
        settings.

        Returns:
            (list): A list of MinimizerResults. One for each slice
            fitted.
        """

        if self.method['method'] == 'matrix_inversion':
            return self.matrix_inversion()

        lmfit_padding = float(config.get_key('lmfit', 'padding'))

        any_parameter_vary = False
        for parameter in self.parameters.values():
            if parameter.vary:
                any_parameter_vary = True
                break

        if not any_parameter_vary and self.method['method'] not in [
                'leastsq', 'least_squares'
        ]:
            raise ValueError(
                'Only leastsq and least_squares can fit if no parameter is set to vary.'
            )

        for parameter in self.parameters.values():
            if parameter.vary and parameter.value <= parameter.min:
                padded_value = parameter.min + lmfit_padding
                print(
                    'WARNING: Initial value for parameter \'%s\' had to be corrected to %f (was %f)'
                    % (parameter.name, padded_value, parameter.value))
                parameter.value = padded_value

        results = []
        for index in self.slice_policy[1]:
            slice_ = self.get_sliced_kmap(index)
            result = minimize(self._chi2,
                              copy.deepcopy(self.parameters),
                              kws={'slice_': slice_},
                              nan_policy='omit',
                              **self.method)

            results.append([index, result])

        return results

    def transpose(self, axis_order):
        self.axis_order = constant_axis
        self.sliced_data.transpose(self.axis_order)

    def get_settings(self):
        settings = {
            'crosshair': self.crosshair,
            'background': self.background_equation,
            'symmetrization': self.symmetrization,
            'polarization': [self.Ak_type, self.polarization],
            'slice_policy': self.slice_policy,
            'method': self.method,
            'region': self.region,
            'axis': self.axis,
            's_share': self.s_share
        }

        return copy.deepcopy(settings)

    def set_settings(self, settings):
        self.set_crosshair(settings['crosshair'])
        self.set_background_equation(settings['background'][0])
        self.set_polarization(*settings['polarization'])
        slice_policy = settings['slice_policy']
        self.set_slices(slice_policy[1], slice_policy[0], slice_policy[2])
        self.set_region(*settings['region'])
        self.set_symmetrization(settings['symmetrization'])
        self.set_fit_method(*settings['method'])
        self.set_axis(settings['axis'])
        self.set_s_share(settings['s_share'])

    def get_sliced_kmap(self, slice_index):
        axis_index, slice_indices, is_combined = self.slice_policy

        if is_combined:
            kmaps = []
            for slice_index in slice_indices:
                kmaps.append(
                    self.sliced_data.slice_from_index(slice_index, axis_index))

            kmap = np.nansum(kmaps, axis=axis_index)

        else:
            kmap = self.sliced_data.slice_from_index(slice_index, axis_index)

        if self.axis is not None:
            kmap = kmap.interpolate(self.axis, self.axis)

        else:
            self.axis = kmap.x_axis

        kmap = kmap.symmetrise(*self.sliced_symmetry, update=True)

        return self._cut_region(kmap)

    def get_orbital_kmap(self, ID, param=None):
        if param is None:
            param = self.parameters

        orbital = self.ID_to_orbital(ID)
        kmap = orbital.get_kmap(E_kin=param['E_kin'].value,
                                dk=(self.axis, self.axis),
                                phi=param['phi_' + str(ID)].value,
                                theta=param['theta_' + str(ID)].value,
                                psi=param['psi_' + str(ID)].value,
                                alpha=param['alpha'].value,
                                beta=param['beta'].value,
                                Ak_type=self.Ak_type,
                                polarization=self.polarization,
                                symmetrization=self.symmetrization,
                                s_share=self.s_share,
                                V0=param['V0'].value)

        return self._cut_region(kmap)

    def get_weighted_sum_kmap(self, param=None, with_background=True):
        if param is None:
            param = self.parameters

        orbital_kmaps = []
        for orbital in self.orbitals:
            ID = orbital.ID

            weight = param['w_' + str(ID)].value

            kmap = weight * self.get_orbital_kmap(ID, param)

            orbital_kmaps.append(kmap)

        orbital_kmap = np.nansum(orbital_kmaps)

        if with_background:
            background = self._get_background(param)

            return orbital_kmap + background

        else:
            return orbital_kmap

    def get_residual(self, slice_, param=None, weight_sum_data=None):
        if param is None:
            param = self.parameters

        if weight_sum_data is None:
            orbital_kmap = self.get_weighted_sum_kmap(param)

        else:
            orbital_kmap = weight_sum_data

        if isinstance(slice_, int):
            sliced_kmap = self.get_sliced_kmap(slice_)
            residual = sliced_kmap - orbital_kmap

        else:
            residual = slice_ - orbital_kmap

        return residual

    def get_reduced_chi2(self, slice_index, weight_sum_data=None):
        n = self._get_degrees_of_freedom()
        residual = self.get_residual(slice_index,
                                     weight_sum_data=weight_sum_data)
        reduced_chi2 = get_reduced_chi2(residual.data, n)

        return reduced_chi2

    def ID_to_orbital(self, ID):
        for orbital in self.orbitals:
            if orbital.ID == ID:
                return orbital

        return None

    def _chi2(self, param=None, slice_=0):
        if param is None:
            param = self.parameters

        residual = self.get_residual(slice_, param)

        return residual.data

    def _get_degrees_of_freedom(self):
        n = 0
        for parameter in self.parameters.values():
            if parameter.vary:
                n += 1

        return n

    def _get_background(self, param=[]):
        variables = {'x': self.axis, 'y': np.array([self.axis]).T}
        for variable in self.background_equation[1]:
            variables.update({variable: param[variable].value})

        background = eval(self.background_equation[0], None, variables)

        return background

    def _cut_region(self, data):
        if self.crosshair is None or self.region[0] == 'all':
            return data

        else:
            return self.crosshair.cut_from_data(data,
                                                region=self.region[0],
                                                inverted=self.region[1])

    def _set_sliced_data(self, sliced_data):
        if isinstance(sliced_data, SlicedData):
            self.sliced_data = sliced_data

        else:
            raise TypeError('sliced_data has to be of type %s (is %s)' %
                            (type(SlicedData), type(sliced_data)))

    def _add_orbitals(self, orbitals):
        if (isinstance(orbitals, list) and all(
                isinstance(element, OrbitalData) for element in orbitals)):
            self.orbitals = orbitals

        elif isinstance(orbitals, OrbitalData):
            self.orbitals = [orbitals]

        else:
            raise TypeError(
                'orbital has to be of (or list of) type %s (is %s)' %
                (type(OrbitalData), type(orbitals)))

    def _set_parameters(self):
        self.parameters = Parameters()

        for orbital in self.orbitals:
            ID = orbital.ID

            self.parameters.add('w_' + str(ID),
                                value=1,
                                min=0,
                                vary=False,
                                expr=None)
            for angle in ['phi_', 'theta_', 'psi_']:
                self.parameters.add(angle + str(ID),
                                    value=0,
                                    min=90,
                                    max=-90,
                                    vary=False,
                                    expr=None)

        # LMFit doesn't work when the initial value is exactly the same
        # as the minimum value. For this reason the initial value will
        # be set ever so slightly above 0 to circumvent this problem.
        self.parameters.add('c', value=0, min=0, vary=False, expr=None)
        self.parameters.add('E_kin',
                            value=30,
                            min=5,
                            max=150,
                            vary=False,
                            expr=None)
        self.parameters.add('V0',
                            value=0,
                            min=-5,
                            max=30,
                            vary=False,
                            expr=None)
        self.parameters.add('alpha',
                            value=45,
                            min=0,
                            max=90,
                            vary=False,
                            expr=None)
        self.parameters.add('beta',
                            value=0,
                            min=-180,
                            max=180,
                            vary=False,
                            expr=None)

    def _construct_minimizer_result(self, results):

        out = []
        for index, result in results:
            minimizer_result = MinimizerResult()
            minimizer_result.params = self.parameters.copy()

            for weight, orbital in zip(result[:-1], self.orbitals):
                ID = orbital.ID
                minimizer_result.params['w_' + str(ID)].value = weight

            minimizer_result.params['c'].value = result[-1]
            out.append([index, minimizer_result])

        return out