Ejemplo n.º 1
0
def run_fit(wks,
            prob,
            function,
            minimizer='Levenberg-Marquardt',
            cost_function='Least squares'):
    """
    Fits the data in a workspace with a function, using the algorithm Fit.
    Importantly, the option IgnoreInvalidData is enabled. Check the documentation of Fit for the
    implications of this.

    @param wks :: MatrixWorkspace with data to fit, in the format expected by the algorithm Fit
    @param prob :: Problem definition
    @param function :: function definition as used in the algorithm Fit
    @param minimizer :: minimizer to use in Fit
    @param cost_function :: cost function to use in Fit

    @returns the fitted parameter values and error estimates for these
    """
    status = None
    chi2 = None
    param_tbl = None
    fit_wks = None
    try:
        # When using 'Least squares' (weighted by errors), ignore nans and zero errors, but don't
        # ignore them when using 'Unweighted least squares' as that would ignore all values!
        ignore_invalid = cost_function == 'Least squares'

        # Note the ugly adhoc exception. We need to reconsider these WISH problems:
        if 'WISH17701' in prob.name:
            ignore_invalid = False

        status, chi2, covar_tbl, param_tbl, fit_wks = msapi.Fit(
            function,
            wks,
            Output='ws_fitting_test',
            Minimizer=minimizer,
            CostFunction=cost_function,
            IgnoreInvalidData=ignore_invalid,
            StartX=prob.start_x,
            EndX=prob.end_x)

        calc_chi2 = msapi.CalculateChiSquared(Function=function,
                                              InputWorkspace=wks,
                                              IgnoreInvalidData=ignore_invalid)
        print("*** with minimizer {0}, calculated: chi2: {1}".format(
            minimizer, calc_chi2))

    except RuntimeError as rerr:
        print("Warning, Fit probably failed. Going on. Error: {0}".format(
            str(rerr)))

    if param_tbl:
        params = param_tbl.column(1)[:-1]
        errors = param_tbl.column(2)[:-1]
    else:
        params = None
        errors = None

    return status, chi2, fit_wks, params, errors
Ejemplo n.º 2
0
    def _fit_bank_curve(self, vanadium_ws, bank, spline_breaks, prog):
        """
        Fits a spline to a single-spectrum workspace (in d-spacing)

        @param vanadium_ws :: Vanadium workspace to fit (normally this contains spectra for a single bank)
        @param bank :: instrument bank this is fitting is done for
        @param spline_breaks :: number of break points when fitting spline functions
        @param prog :: progress reporter

        @returns fit workspace (MatrixWorkspace), with the same number of bins as the input
        workspace, and the Y values simulated from the fitted curve
        """
        expected_dim = 'd-Spacing'
        dim_type = vanadium_ws.getXDimension().name
        if expected_dim != dim_type:
            raise ValueError("This algorithm expects a workspace with %s X dimension, but "
                             "the X dimension of the input workspace is: '%s'" % (expected_dim, dim_type))

        if 1 != vanadium_ws.getNumberHistograms():
            raise ValueError("The workspace does not have exactly one histogram. Inconsistency found.")

        # without these min/max parameters 'BSpline' would completely misbehave
        x_values = vanadium_ws.readX(0)
        start_x = min(x_values)
        end_x = max(x_values)

        function_descriptor = ('name=BSpline, Order=3, StartX={0}, EndX={1}, NBreak={2}'.
                               format(start_x, end_x, spline_breaks))
        # WorkspaceIndex is left to default '0' for 1D function fits
        # StartX, EndX could in principle be left to default start/end of the spectrum, but apparently
        # not safe for 'BSpline'
        prog.report("Performing fit")
        fit_output = mantid.Fit(InputWorkspace=vanadium_ws, Function=function_descriptor, CreateOutput=True,
                                StoreInADS=False)
        prog.report("Fit complete")

        success = fit_output.OutputStatus
        self.log().information("Fitting Vanadium curve for bank %s, using function '%s', result: %s" %
                               (bank, function_descriptor, success))

        failure_msg = ("It seems that this algorithm failed to to fit a function to the summed "
                       "spectra of a bank. The function definiton was: '%s'") % function_descriptor

        output_params_prop_name = "OutputParameters"
        if not hasattr(fit_output, output_params_prop_name):
            raise RuntimeError("Could not find the parameters workspace expected in the output property "
                               + output_params_prop_name
                               + " from the algorithm Fit. It seems that this algorithm failed." + failure_msg)

        try:
            fit_ws = fit_output.OutputWorkspace
        except AttributeError:
            raise RuntimeError("Could not find the data workspace expected in the output property "
                               + "OutputWorkspace" + ". " + failure_msg)

        mtd['engg_van_ws_dsp'] = vanadium_ws
        mtd['engg_fit_ws_dsp'] = fit_ws

        return fit_ws
Ejemplo n.º 3
0
    def fit(self):
        """
        Run problem with Mantid.
        """
        fit_result = msapi.Fit(Function=self._mantid_function,
                               InputWorkspace=self._mantid_data,
                               Output='ws_fitting_test',
                               Minimizer=self.minimizer,
                               CostFunction=self._cost_function)

        self._mantid_results = fit_result
        self._status = self._mantid_results.OutputStatus
Ejemplo n.º 4
0
 def doFit(self, wprofile, **fitkwargs):
     """
     Thin wrapper to mantid.simpleapi.Fit
     :param wprofile: mantid workspace holding the experimental profile
     :param fitkwargs: optional arguments for algorithm mantid.simpleapi.Fit
     :return: (type namedtuple) results of evaluating algorithm mantid.simpleapi.Fit
     """
     if not self._valid_xdomain(wprofile.dataX(0)):
         raise ValueError("Experimental Q-domain not included within the simulated Q-domain")
     self._fitresults = smtdi.Fit(Function=self.fstr(), InputWorkspace=wprofile.name(),
                                  WorkspaceIndex=0, CreateOutput=True, **fitkwargs)
     return self._fitresults
    def fit(self):
        """
        Run problem with Mantid.
        """
        fit_result = msapi.Fit(Function=self._mantid_function,
                               CostFunction=self._cost_function,
                               Minimizer=self.minimizer,
                               InputWorkspace=self._mantid_data,
                               Output='fit',
                               **self._added_args)

        self._mantid_results = fit_result
        self._status = self._mantid_results.OutputStatus
Ejemplo n.º 6
0
def fit(ws, function, workspace_index, start_x, end_x):
    """
    Performs a fit on the workspace.

    :param ws: The workspace on which the fit will be performed
    :param function: The function used for the fit. This is anything
                     that mantid.Fit's Function parameter can handle.
    :param workspace_index: Workspace index which will be fitted.
    :param start_x: Start X for the fit
    :param end_x: End X for the fit
    :returns: Dataset containing all of Fit's outputs
    """
    try:
        import mantid.simpleapi as mantid
    except ImportError:
        raise ImportError(
            "Mantid Python API was not found, please install Mantid framework "
            "as detailed in the installation instructions (https://scipp."
            "readthedocs.io/en/latest/getting-started/installation.html)")

    fit = mantid.Fit(Function=function,
                     InputWorkspace=ws,
                     WorkspaceIndex=workspace_index,
                     StartX=start_x,
                     EndX=end_x,
                     CreateOutput=True,
                     StoreinADS=False)

    ds = sc.Dataset(data={
        'workspace':
        sc.Variable(convert_Workspace2D_to_data_array(fit.OutputWorkspace)),
        'parameters':
        sc.Variable(convert_TableWorkspace_to_dataset(fit.OutputParameters)),
        'normalised_covariance_matrix':
        sc.Variable(
            convert_TableWorkspace_to_dataset(
                fit.OutputNormalisedCovarianceMatrix)),
    },
                    attrs={
                        'status': sc.Variable(fit.OutputStatus),
                        'chi2_over_DoF': sc.Variable(fit.OutputChi2overDoF),
                        'function': sc.Variable(str(fit.Function)),
                        'cost_function': sc.Variable(fit.CostFunction)
                    })

    # clean up leftover workspaces in the ADS
    mantid.DeleteWorkspace(fit.OutputWorkspace)
    mantid.DeleteWorkspace(fit.OutputParameters)
    mantid.DeleteWorkspace(fit.OutputNormalisedCovarianceMatrix)

    return ds
Ejemplo n.º 7
0
def final_fit(fit_ws_name, constraints,y_range, correct_for_offsets, masses, g_log) :
    function = """
    composite=Convolution,FixResolution=true,NumDeriv=true;
        name=Resolution,Workspace=resolution,WorkspaceIndex=0,X=(),Y=();
        name=UserFunction,Formula=exp( -x^2/2./sigma1^2)
        *(1.+c4/32.*(16.*(x/sqrt(2)/sigma1)^4-48.*(x/sqrt(2)/sigma1)^2+12)
              +c6/384.*( 64.*(x/sqrt(2)/sigma1)^6 -480.*(x/sqrt(2)/sigma1)^4 +720.*(x/sqrt(2)/sigma1)^2 -120.) )*A + B0,
        sigma1=3.0,c4=0.0, c6=0.0,A=0.08, B0=0.00, ties = (c6=0. )
        """
    function+=constraints
    minimiser = "Simplex"
    sapi.Fit(Function= function, InputWorkspace=fit_ws_name, MaxIterations=2000, Minimizer= minimiser, Output=fit_ws_name,
             OutputCompositeMembers=True, StartX = y_range[0] , EndX = y_range[1])
    ws = sapi.mtd[fit_ws_name+"_Parameters"]
    g_log.notice( "\n Final parameters \n")
    g_log.notice( "width: ",ws.cell(0,1)," +/- ",ws.cell(0,2), " A-1 ")
    g_log.notice( "c4: ",ws.cell(1,1)," +/- ",ws.cell(1,2), " A-1 ")
    sigma_to_energy = 1.5 * 2.0445**2 / masses[0]
    g_log.notice( "mean kinetic energy: ",sigma_to_energy*ws.cell(0,1)**2," +/- ", 2.*sigma_to_energy*ws.cell(0,2)*ws.cell(0,1), " meV ")
    if correct_for_offsets :
        sapi.Scale(InputWorkspace=fit_ws_name,Factor=-ws.cell(4,1),Operation="Add",OutputWorkspace=fit_ws_name+'_cor')
        sapi.Scale(InputWorkspace=fit_ws_name+'_cor',
                   Factor=(2.*np.pi)**(-0.5)/ws.cell(0,1)/ws.cell(3,1),Operation="Multiply",
                   OutputWorkspace=fit_ws_name+'_cor')
    def runTest(self):

        data = sm.Load("irs26176_graphite002_red.nxs")
        sm.Load("irs26173_graphite002_res.nxs", OutputWorkspace="resolution")

        single_model = """(composite=Convolution,NumDeriv=true;
        name=TabulatedFunction,Workspace=resolution,WorkspaceIndex=0,
             Scaling=1,Shift=0,XScaling=1;
        (name=DeltaFunction,Height=1.5,Centre=0;
         name=TeixeiraWaterSQE,Height=1.0,Tau=1.0,DiffCoeff=1.0,Centre=0;
         ties=(f1.Centre=f0.Centre)));
        name=LinearBackground,A0=0,A1=0"""
        single_model = re.sub('[\s+]', '', single_model)

        # Include all spectra for the fit
        selected_wi = range(data.getNumberHistograms())
        nWi = len(selected_wi)  # number of selected spectra for fitting

        # Energy range over which we do the fitting.
        minE = -0.4  # Units are in meV
        maxE = 0.4

        # Create the string representation of the global model (for selected spectra):
        global_model = "composite=MultiDomainFunction,NumDeriv=true;"
        for _ in selected_wi:
            global_model += "(composite=CompositeFunction,NumDeriv=true,$domains=i;{0});\n".format(
                single_model)

        # Tie DiffCoeff and Tau since they are global parameters
        ties = [
            '='.join([
                "f{0}.f0.f1.f1.DiffCoeff".format(di)
                for di in reversed(range(nWi))
            ]), '='.join([
                "f{0}.f0.f1.f1.Tau".format(wi) for wi in reversed(range(nWi))
            ])
        ]
        global_model += "ties=(" + ','.join(ties) + ')'  # tie Radius

        # Now relate each domain(i.e. spectrum) to each single-spectrum model
        domain_model = dict()
        domain_index = 0
        for wi in selected_wi:
            if domain_index == 0:
                domain_model.update({
                    "InputWorkspace": data.name(),
                    "WorkspaceIndex": str(wi),
                    "StartX": str(minE),
                    "EndX": str(maxE)
                })
            else:
                di = str(domain_index)
                domain_model.update({
                    "InputWorkspace_" + di: data.name(),
                    "WorkspaceIndex_" + di: str(wi),
                    "StartX_" + di: str(minE),
                    "EndX_" + di: str(maxE)
                })
            domain_index += 1

        # Invoke the Fit algorithm using global_model and domain_model:
        output_workspace = "glofit_" + data.name()
        fit_output = sm.Fit(Function=global_model,
                            Output=output_workspace,
                            CreateOutput=True,
                            MaxIterations=500,
                            **domain_model)
        chi2 = fit_output.OutputChi2overDoF
        params = fit_output.OutputParameters
        curves = fit_output.OutputWorkspace

        # Validate
        self._success = True
        try:
            self.assertTrue(chi2 < 1)  # goodness of fit
            self.assertTrue(abs(params.row(6)["Value"] - 1.58) / 1.58 <
                            0.1)  # optimal DiffCoeff
            self.assertTrue(
                abs(params.row(7)["Value"] - 1.16) / 1.16 < 0.1)  # optimal Tau
            # check curves are correctly generated by calculating their Chi^2
            residuals = np.empty(0)
            for i in range(curves.size()):
                curveset = curves[i]  # contains data, model, and residuals
                dataY = curveset.dataY(0)
                dataE = curveset.dataE(0)
                modelY = curveset.dataY(1)
                residuals = np.append(
                    residuals, ((dataY - modelY) /
                                dataE)**2)  # don't trust residuals of curveset
            otherChi2 = residuals.sum() / len(residuals)
            self.assertTrue(abs(otherChi2 - chi2) / chi2 < 0.1)
        except:
            self._success = False
Ejemplo n.º 9
0
    def PyExec(self):
        from IndirectCommon import (convertToElasticQ,
                                    transposeFitParametersTable)

        setup_prog = Progress(self, start=0.0, end=0.1, nreports=4)
        setup_prog.report('generating output name')
        output_workspace = self._fit_group_name
        # check if the naming convention used is already correct
        chopped_name = self._fit_group_name.split('_')
        if 'WORKSPACE' in chopped_name[-1].upper():
            output_workspace = '_'.join(chopped_name[:-1])

        option = self._fit_type[:-2]
        logger.information('Option: ' + option)
        logger.information('Function: ' + str(self._function))

        setup_prog.report('Cropping workspace')
        # prepare input workspace for fitting
        tmp_fit_workspace = "__Iqtfit_fit_ws"
        if self._spec_max is None:
            crop_alg = self.createChildAlgorithm("CropWorkspace", enableLogging=False)
            crop_alg.setProperty("InputWorkspace", self._input_ws)
            crop_alg.setProperty("OutputWorkspace", tmp_fit_workspace)
            crop_alg.setProperty("XMin", self._start_x)
            crop_alg.setProperty("XMax", self._end_x)
            crop_alg.setProperty("StartWorkspaceIndex", self._spec_min)
            crop_alg.execute()
        else:
            crop_alg = self.createChildAlgorithm("CropWorkspace", enableLogging=False)
            crop_alg.setProperty("InputWorkspace", self._input_ws)
            crop_alg.setProperty("OutputWorkspace", tmp_fit_workspace)
            crop_alg.setProperty("XMin", self._start_x)
            crop_alg.setProperty("XMax", self._end_x)
            crop_alg.setProperty("StartWorkspaceIndex", self._spec_min)
            crop_alg.setProperty("EndWorkspaceIndex", self._spec_max)
            crop_alg.execute()

        setup_prog.report('Converting to Histogram')
        convert_to_hist_alg = self.createChildAlgorithm("ConvertToHistogram", enableLogging=False)
        convert_to_hist_alg.setProperty("InputWorkspace", crop_alg.getProperty("OutputWorkspace").value)
        convert_to_hist_alg.setProperty("OutputWorkspace", tmp_fit_workspace)
        convert_to_hist_alg.execute()
        mtd.addOrReplace(tmp_fit_workspace, convert_to_hist_alg.getProperty("OutputWorkspace").value)
        setup_prog.report('Convert to Elastic Q')
        convertToElasticQ(tmp_fit_workspace)

        # fit multi-domain function to workspace
        fit_prog = Progress(self, start=0.1, end=0.8, nreports=2)
        multi_domain_func, kwargs = _create_multi_domain_func(self._function, tmp_fit_workspace)
        fit_prog.report('Fitting...')
        ms.Fit(Function=multi_domain_func,
               InputWorkspace=tmp_fit_workspace,
               WorkspaceIndex=0,
               Output=output_workspace,
               CreateOutput=True,
               Minimizer=self._minimizer,
               MaxIterations=self._max_iterations,
               OutputCompositeMembers=self._do_extract_members,
               **kwargs)
        fit_prog.report('Fitting complete')

        conclusion_prog = Progress(self, start=0.8, end=1.0, nreports=5)
        conclusion_prog.report('Renaming workspaces')
        # rename workspaces to match user input
        rename_alg = self.createChildAlgorithm("RenameWorkspace", enableLogging=False)
        if output_workspace + "_Workspaces" != self._fit_group_name:
            rename_alg.setProperty("InputWorkspace", output_workspace + "_Workspaces")
            rename_alg.setProperty("OutputWorkspace", self._fit_group_name)
            rename_alg.execute()
        if output_workspace + "_Parameters" != self._parameter_name:
            rename_alg.setProperty("InputWorkspace", output_workspace + "_Parameters")
            rename_alg.setProperty("OutputWorkspace", self._parameter_name)
            rename_alg.execute()
        conclusion_prog.report('Transposing parameter table')
        transposeFitParametersTable(self._parameter_name)

        # set first column of parameter table to be axis values
        x_axis = mtd[tmp_fit_workspace].getAxis(1)
        axis_values = x_axis.extractValues()
        for i, value in enumerate(axis_values):
            mtd[self._parameter_name].setCell('axis-1', i, value)

        # convert parameters to matrix workspace
        parameter_names = 'A0,Height,Lifetime,Stretching'
        conclusion_prog.report('Processing indirect fit parameters')
        pifp_alg = self.createChildAlgorithm("ProcessIndirectFitParameters")
        pifp_alg.setProperty("InputWorkspace", self._parameter_name)
        pifp_alg.setProperty("ColumnX", "axis-1")
        pifp_alg.setProperty("XAxisUnit", "MomentumTransfer")
        pifp_alg.setProperty("ParameterNames", parameter_names)
        pifp_alg.setProperty("OutputWorkspace", self._result_name)
        pifp_alg.execute()
        result_workspace = pifp_alg.getProperty("OutputWorkspace").value

        mtd.addOrReplace(self._result_name, result_workspace)

        # create and add sample logs
        sample_logs = {'start_x': self._start_x, 'end_x': self._end_x, 'fit_type': self._fit_type[:-2],
                       'intensities_constrained': self._intensities_constrained, 'beta_constrained': True}

        conclusion_prog.report('Copying sample logs')
        copy_log_alg = self.createChildAlgorithm("CopyLogs", enableLogging=False)
        copy_log_alg.setProperty("InputWorkspace", self._input_ws)
        copy_log_alg.setProperty("OutputWorkspace", result_workspace)
        copy_log_alg.execute()
        copy_log_alg.setProperty("InputWorkspace", self._input_ws)
        copy_log_alg.setProperty("OutputWorkspace", self._fit_group_name)
        copy_log_alg.execute()

        log_names = [item for item in sample_logs]
        log_values = [sample_logs[item] for item in sample_logs]

        conclusion_prog.report('Adding sample logs')
        add_sample_log_multi = self.createChildAlgorithm("AddSampleLogMultiple", enableLogging=False)
        add_sample_log_multi.setProperty("Workspace", result_workspace.name())
        add_sample_log_multi.setProperty("LogNames", log_names)
        add_sample_log_multi.setProperty("LogValues", log_values)
        add_sample_log_multi.execute()
        add_sample_log_multi.setProperty("Workspace", self._fit_group_name)
        add_sample_log_multi.setProperty("LogNames", log_names)
        add_sample_log_multi.setProperty("LogValues", log_values)
        add_sample_log_multi.execute()

        delete_alg = self.createChildAlgorithm("DeleteWorkspace", enableLogging=False)
        delete_alg.setProperty("Workspace", tmp_fit_workspace)
        delete_alg.execute()

        if self._do_extract_members:
            ms.ExtractQENSMembers(InputWorkspace=self._input_ws,
                                  ResultWorkspace=self._fit_group_name,
                                  OutputWorkspace=self._fit_group_name.rsplit('_', 1)[0] + "_Members")

        self.setProperty('OutputResultWorkspace', result_workspace)
        self.setProperty('OutputParameterWorkspace', self._parameter_name)
        self.setProperty('OutputWorkspaceGroup', self._fit_group_name)
        conclusion_prog.report('Algorithm complete')
    def mainExec(self):
        """ Main execution body
        """
        # Load data optionally
        if self.loaddata is True:
            # Load data file
            api.LoadAscii(Filename=self.datafilename,
                          OutputWorkspace=self.datawsname,
                          Unit='TOF')

        # Load .irf file and .hkl file optionally
        if self.loadinfofile is True:
            if dir(self).count('latticesize') == 0 or self.latticesize is None:
                raise NotImplementedError(
                    "Lattice size is not defined.  Unable to use option 'LoadInfo'"
                )

            api.CreateLeBailFitInput(
                FullprofParameterFile=self.irffilename,
                MaxHKL=[13, 13, 13],
                LatticeConstant=float(self.latticesize),
                Bank=self.bankid,
                GenerateBraggReflections=True,
                InstrumentParameterWorkspace=str(self.inputparamws),
                BraggPeakParameterWorkspace=str(self.inputbraggws))

        # Process background optionally
        if self.process_bkgd is True:
            # [Background]
            # Remove peaks and get pure background (hopefully)
            api.ProcessBackground(Options='SelectBackgroundPoints',
                                  InputWorkspace=self.dataws,
                                  OutputWorkspace=self.bkgdwsname,
                                  LowerBound=self.startx,
                                  UpperBound=self.endx,
                                  BackgroundType=self.backgroundtype,
                                  BackgroundPoints=self.usrbkgdpoints,
                                  NoiseTolerance='0.10000000000000001')

            # Fit background points
            functionstr = "name=%s,n=%d" % (self.backgroundtype,
                                            self.backgroundorder)
            for iborder in range(self.backgroundorder + 1):
                functionstr = "%s,A%d=%.5f" % (functionstr, iborder, 0.0)
            api.Fit(Function=functionstr,
                    InputWorkspace=self.bkgdwsname,
                    Output=self.bkgdwsname,
                    MaxIterations='1000',
                    Minimizer='Levenberg-MarquardtMD',
                    CreateOutput='1',
                    StartX=self.startx,
                    EndX=self.endx)

        # [Le Bail calculation]
        self.log().debug("Fit range: %f , %f, Outputworkspace = %s" %
                         (self.startx, self.endx, self.outwsname))
        api.LeBailFit(
            Function='Calculation',
            InputWorkspace=self.dataws,
            OutputWorkspace=self.outwsname,
            InputParameterWorkspace=self.inputparamws,
            OutputParameterWorkspace=str(self.inputparamws),
            InputHKLWorkspace=self.inputbraggws,
            OutputPeaksWorkspace=str(self.inputbraggws),
            FitRegion='%f, %f' % (self.startx, self.endx),
            BackgroundType=self.backgroundtype,
            UseInputPeakHeights=False,
            PeakRadius='7',
            BackgroundParametersWorkspace=self.bkgdtablews,
            PeakType=self.profiletype,
        )

        return
Ejemplo n.º 11
0
def do_fitting_benchmark_one_problem(prob,
                                     minimizers,
                                     use_errors=True,
                                     count=0,
                                     previous_name="none"):
    """
    One problem with potentially several starting points, returns a list (start points) of
    lists (minimizers).

    @param prob :: fitting problem
    @param minimizers :: list of minimizers to evaluate/compare
    @param use_errors :: whether to use observational errors when evaluating accuracy (in the
                         cost function)
    @param count :: the current count for the number of different start values for a given problem
    """

    wks, cost_function = prepare_wks_cost_function(prob, use_errors)

    # Each NIST problem generate two results per file - from two different starting points
    results_fit_problem = []

    # Get function definitions for the problem - one for each starting point
    function_defs = get_function_definitions(prob)
    # search for lowest chi2
    min_sum_err_sq = 1.e20
    # Loop over the different starting points
    for user_func in function_defs:
        results_problem_start = []
        for minimizer_name in minimizers:
            t_start = time.clock()

            status, chi2, fit_wks, params, errors = run_fit(
                wks,
                prob,
                function=user_func,
                minimizer=minimizer_name,
                cost_function=cost_function)
            t_end = time.clock()
            print("*** with minimizer {0}, Status: {1}, chi2: {2}".format(
                minimizer_name, status, chi2))
            print("   params: {0}, errors: {1}".format(params, errors))

            def sum_of_squares(values):
                return np.sum(np.square(values))

            if fit_wks:
                sum_err_sq = sum_of_squares(fit_wks.readY(2))
                # print " output simulated values: {0}".format(fit_wks.readY(1))
                if sum_err_sq < min_sum_err_sq:
                    tmp = msapi.ConvertToPointData(fit_wks)
                    best_fit = data(minimizer_name, tmp.readX(1), tmp.readY(1))
                    min_sum_err_sq = sum_err_sq
            else:
                sum_err_sq = float("inf")
                print(" WARNING: no output fit workspace")
            print("   sum sq: {0}".format(sum_err_sq))
            result = test_result.FittingTestResult()
            result.problem = prob
            result.fit_status = status
            result.fit_chi2 = chi2
            result.params = params
            result.errors = errors
            result.sum_err_sq = sum_err_sq
            # If the fit has failed, also set the runtime to NaN
            result.runtime = t_end - t_start if not np.isnan(chi2) else np.nan
            print("Result object: {0}".format(result))
            results_problem_start.append(result)
        results_fit_problem.append(results_problem_start)
        # make plots
        fig = plot()
        best_fit.markers = ''
        best_fit.linestyle = '-'
        best_fit.colour = 'green'
        best_fit.order_data()
        fig.add_data(best_fit)
        tmp = msapi.ConvertToPointData(wks)
        xData = tmp.readX(0)
        yData = tmp.readY(0)
        eData = tmp.readE(0)
        raw = data("Data", xData, yData, eData)
        raw.showError = True
        raw.linestyle = ''
        fig.add_data(raw)
        fig.labels['y'] = "Arbitrary units"
        fig.labels['x'] = "Time ($\mu s$)"
        if prob.name == previous_name:
            count += 1
        else:
            count = 1
            previous_name = prob.name
        #fig.labels['y']="something "
        fig.labels['title'] = prob.name[:-4] + " " + str(count)
        fig.title_size = 10
        fit_result = msapi.Fit(user_func,
                               wks,
                               Output='ws_fitting_test',
                               Minimizer='Levenberg-Marquardt',
                               CostFunction='Least squares',
                               IgnoreInvalidData=True,
                               StartX=prob.start_x,
                               EndX=prob.end_x,
                               MaxIterations=0)
        tmp = msapi.ConvertToPointData(fit_result.OutputWorkspace)
        xData = tmp.readX(1)
        yData = tmp.readY(1)
        startData = data("Start Guess", xData, yData)
        startData.order_data()
        startData.colour = "blue"
        startData.markers = ''
        startData.linestyle = "-"
        start_fig = plot()
        start_fig.add_data(raw)
        start_fig.add_data(startData)
        start_fig.labels['x'] = "Time ($\mu s$)"
        start_fig.labels['y'] = "Arbitrary units"
        title = user_func[27:-1]
        title = splitByString(title, 30)
        # remove the extension (e.g. .nxs) if there is one
        run_ID = prob.name
        k = -1
        k = run_ID.rfind(".")
        if k != -1:
            run_ID = run_ID[:k]

        start_fig.labels['title'] = run_ID + " " + str(count) + "\n" + title
        start_fig.title_size = 10
        fig.make_scatter_plot("Fit for " + run_ID + " " + str(count) + ".pdf")
        start_fig.make_scatter_plot("start for " + run_ID + " " + str(count) +
                                    ".pdf")
    return results_fit_problem
            "WorkspaceIndex": str(wi),
            "StartX": str(-4.95),
            "EndX": str(4.95)
        })
    else:
        domain_model.update({
            "InputWorkspace_" + str(wi): 'QENS_data',
            "WorkspaceIndex_" + str(wi): str(wi),
            "StartX_" + str(wi): str(minE),
            "EndX_" + str(wi): str(maxE)
        })

# Perform fitting
mapi.Fit(Function=global_model,
         **domain_model,
         CreateOutput=True,
         MaxIteractions=500,
         Output='fit')
"""
 As a result of the fit, three workspaces are created:
 'fit'+"_Parameters" : optimized parameters and Chi-square
 'fit'+"_NormalisedCovarianceMatrix" : correlations between parameters
 'fit'+"_Workspace"  : data, fit, residuals, and model
"""

paramTable = mapi.mtd['fit_Parameters']

# print results
for i in range(4):
    print(f'Workspace {i}:')
    print(f'scale: {paramTable.column(1)[3 * i]:.2f}')