mantidproject / mantid

Main repository for Mantid code
https://www.mantidproject.org
GNU General Public License v3.0
211 stars 123 forks source link

Pseudo global fit #33375

Closed AnthonyLim23 closed 2 years ago

AnthonyLim23 commented 2 years ago

The fitting in Mantid finds the local minima when given a bad starting guess. This is not surprising, Mantid uses a local minimizer and found a local minima.

However, if the user starts a fit at this local minima it finds a close approximation to the global minima. I suspect this is because the first fit was using small increments to check if the minimisation could continue (its sensible to use smaller steps as you approach the minima). The second fit did not know it was at a local minima and did a larger step that took it out of the minima. This tells me that the minima is a shallow one.

Here is a script to demonstrate it, it also shows that scipy does just as badly

from mantid.simpleapi import *
import matplotlib.pyplot as plt
import numpy as np
from scipy import optimize

Load(Filename=r'\\isis.cclrc.ac.uk\inst$\ndxmusr\instrument\data\cycle_21_1\MUSR00086009.nxs', OutputWorkspace='MUSR00086009.nxs', TimeZeroList='0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444,0.444', DeadTimeTable='MUSR00086009.nxs_deadtime_table', DetectorGroupingTable='__notUsed')
RenameWorkspace(InputWorkspace='MUSR00086009.nxs_deadtime_table', OutputWorkspace='MUSR86009_deadtime MA')
RenameWorkspace(InputWorkspace='MUSR00086009.nxs', OutputWorkspace='MUSR86009_raw_data MA')
MuonPreProcess(InputWorkspace='MUSR86009_raw_data MA', OutputWorkspace='__MUSR86009_pre_processed_data', TimeMin=0.11600000000000001, TimeOffset=0, DeadTimeTable='MUSR86009_deadtime MA')
MuonGroupingCounts(InputWorkspace='__MUSR86009_pre_processed_data', OutputWorkspace='MUSR86009; Group; top; Counts; MA', GroupName='top', Grouping='17-24,49-56')
MuonGroupingCounts(InputWorkspace='__MUSR86009_pre_processed_data', OutputWorkspace='MUSR86009; Group; bkwd; Counts; MA', GroupName='bkwd', Grouping='25-32,41-48')
MuonGroupingCounts(InputWorkspace='__MUSR86009_pre_processed_data', OutputWorkspace='MUSR86009; Group; bottom; Counts; MA', GroupName='bottom', Grouping='1-8,33-40')
MuonGroupingCounts(InputWorkspace='__MUSR86009_pre_processed_data', OutputWorkspace='MUSR86009; Group; fwd; Counts; MA', GroupName='fwd', Grouping='9-16,57-64')
CloneWorkspace(InputWorkspace='MUSR86009; Group; top; Counts; MA', OutputWorkspace='__MUSR86009; Group; top; Counts; MA_uncorrected')
CloneWorkspace(InputWorkspace='MUSR86009; Group; bkwd; Counts; MA', OutputWorkspace='__MUSR86009; Group; bkwd; Counts; MA_uncorrected')
CloneWorkspace(InputWorkspace='MUSR86009; Group; bottom; Counts; MA', OutputWorkspace='__MUSR86009; Group; bottom; Counts; MA_uncorrected')
CloneWorkspace(InputWorkspace='MUSR86009; Group; fwd; Counts; MA', OutputWorkspace='__MUSR86009; Group; fwd; Counts; MA_uncorrected')
CloneWorkspace(InputWorkspace='__MUSR86009; Group; top; Counts; MA_uncorrected', OutputWorkspace='MUSR86009; Group; top; Counts; MA')
CloneWorkspace(InputWorkspace='__MUSR86009; Group; bkwd; Counts; MA_uncorrected', OutputWorkspace='MUSR86009; Group; bkwd; Counts; MA')
CloneWorkspace(InputWorkspace='__MUSR86009; Group; bottom; Counts; MA_uncorrected', OutputWorkspace='MUSR86009; Group; bottom; Counts; MA')
CloneWorkspace(InputWorkspace='__MUSR86009; Group; fwd; Counts; MA_uncorrected', OutputWorkspace='MUSR86009; Group; fwd; Counts; MA')
EstimateMuonAsymmetryFromCounts(InputWorkspace='MUSR86009; Group; top; Counts; MA', OutputWorkspace='MUSR86009; Group; top; Asymmetry; MA', OutputUnNormData=True, OutputUnNormWorkspace='__MUSR86009; Group; top; Asymmetry; MA_unnorm', StartX=0.11600000000000001, EndX=32.323997497558594)
EstimateMuonAsymmetryFromCounts(InputWorkspace='MUSR86009; Group; bkwd; Counts; MA', OutputWorkspace='MUSR86009; Group; bkwd; Asymmetry; MA', OutputUnNormData=True, OutputUnNormWorkspace='__MUSR86009; Group; bkwd; Asymmetry; MA_unnorm', StartX=0.11600000000000001, EndX=32.323997497558594)
EstimateMuonAsymmetryFromCounts(InputWorkspace='MUSR86009; Group; bottom; Counts; MA', OutputWorkspace='MUSR86009; Group; bottom; Asymmetry; MA', OutputUnNormData=True, OutputUnNormWorkspace='__MUSR86009; Group; bottom; Asymmetry; MA_unnorm', StartX=0.11600000000000001, EndX=32.323997497558594)
EstimateMuonAsymmetryFromCounts(InputWorkspace='MUSR86009; Group; fwd; Counts; MA', OutputWorkspace='MUSR86009; Group; fwd; Asymmetry; MA', OutputUnNormData=True, OutputUnNormWorkspace='__MUSR86009; Group; fwd; Asymmetry; MA_unnorm', StartX=0.11600000000000001, EndX=32.323997497558594)
GroupWorkspaces(InputWorkspaces='MUSR86009; Group; bkwd; Asymmetry; MA,MUSR86009; Group; bkwd; Counts; MA,MUSR86009; Group; bottom; Asymmetry; MA,MUSR86009; Group; bottom; Counts; MA,MUSR86009; Group; fwd; Asymmetry; MA,MUSR86009; Group; fwd; Counts; MA,MUSR86009; Group; top; Asymmetry; MA,MUSR86009; Group; top; Counts; MA,MUSR86009_deadtime MA,MUSR86009_raw_data MA', OutputWorkspace='MUSR86009')
MuonPreProcess(InputWorkspace='MUSR86009_raw_data MA', OutputWorkspace='__MUSR86009_pre_processed_data', TimeMin=0.11600000000000001, TimeOffset=0, DeadTimeTable='MUSR86009_deadtime MA')
MuonGroupingCounts(InputWorkspace='__MUSR86009_pre_processed_data', OutputWorkspace='MUSR86009; Group; fwd; Counts; MA', GroupName='fwd', Grouping='33-64')
MuonGroupingCounts(InputWorkspace='__MUSR86009_pre_processed_data', OutputWorkspace='MUSR86009; Group; bwd; Counts; MA', GroupName='bwd', Grouping='1-32')
CloneWorkspace(InputWorkspace='MUSR86009; Group; fwd; Counts; MA', OutputWorkspace='__MUSR86009; Group; fwd; Counts; MA_uncorrected')
CloneWorkspace(InputWorkspace='MUSR86009; Group; bwd; Counts; MA', OutputWorkspace='__MUSR86009; Group; bwd; Counts; MA_uncorrected')
CloneWorkspace(InputWorkspace='__MUSR86009; Group; fwd; Counts; MA_uncorrected', OutputWorkspace='MUSR86009; Group; fwd; Counts; MA')
CloneWorkspace(InputWorkspace='__MUSR86009; Group; bwd; Counts; MA_uncorrected', OutputWorkspace='MUSR86009; Group; bwd; Counts; MA')
MuonPairingAsymmetry(OutputWorkspace='MUSR86009; Pair Asym; long; MA', PairName='long', InputWorkspace1='MUSR86009; Group; fwd; Counts; MA', InputWorkspace2='MUSR86009; Group; bwd; Counts; MA')
mant = Fit(Function='name=GausDecay,A=0.04912,Sigma=0.799829;name=FlatBackground,A0=0.242810',  InputWorkspace='MUSR86009; Pair Asym; long; MA', CreateOutput=True, StartX=0.11600000000000001, EndX=15)

out_ws = mant.OutputWorkspace
x = out_ws.readX(0)
y = out_ws.readY(0)

plt.plot(x,y, "k--", label="data")
plt.plot(out_ws.readX(1), out_ws.readY(1), 'b--', label='mantid fit')

# scipy
def func(x, A, sigma, A0):
    f = GausDecay(A=A, Sigma=sigma) + FlatBackground(A0=A0)
    return f(x)

ws = mtd["MUSR86009; Pair Asym; long; MA"]

initGuess = [0.04912, 0.799829, 0.24281]
popt, pcov = optimize.curve_fit(func, x, y, initGuess)#
y0  = func(x, popt[0], popt[1], popt[2])

plt.plot(x,y0, "r--", label="scipy fit")
plt.xlim([0,15])
plt.legend()
plt.show()

image

AnthonyLim23 commented 2 years ago

Below is a script to create a prototype of a pseudo global optimizer.

This algorithm has the correct logic and ideas. The user defines a maximum number of attempts for the loop. In each loop we do a fit and then do a second fit with 3 iterations. If the second fit escapes the local minima then we start the loop again. To define if the local minima has been escaped we use a percentage change in the value.

The final version will need to be C++ and inherit from the fitting algorithm as it will need to work with the multidomain fitting.

from mantid.simpleapi import *
import matplotlib.pyplot as plt
import numpy as np
from mantid.api import *
from mantid.kernel import *

class pseudoGlobalFit(PythonAlgorithm):

    def summary(self):
        return "pseudo global fit"

    def category(self):
        return "Muon"

#------------------------------------------------------------------------------------------------
    #def doFit(function, ws, startX, endX, maxIterations=1000):
    #    mant = Fit(Function=function, MaxIterations=maxIterations, InputWorkspace=ws, CreateOutput=True, StartX=startX, EndX=endX)
    #    return mant

    def PyInit(self):
        self.declareProperty("MaxLoops", 10)
        self.declareProperty("tolerance", 1.0, Direction.Input)
        self.declareProperty("StartX", 0.0, Direction.Input)
        self.declareProperty("EndX", 15.0, Direction.Input)

        self.declareProperty(WorkspaceProperty("InputWorkspace",
                             "",
                              direction=Direction.Input))
        self.declareProperty(FunctionProperty("Function", direction=Direction.InOut), doc="")

    def significant_change(self, table1, table2, tol):
        for j in range(len(table1.column(1))):
            print("moo", table1.column(1)[j], table2.column(1)[j], np.abs(table1.column(1)[j]- table2.column(1)[j])/table1.column(1)[j])
            if np.abs(table1.column(1)[j]- table2.column(1)[j])/table1.column(1)[j] > tol:
                return True
        return False

    def doFit(self, function, ws, start, end, maxIterations=1000):
        mant = Fit(Function=function, MaxIterations=maxIterations, InputWorkspace=ws, CreateOutput=True, StartX=start, EndX=end)
        return mant

    def PyExec(self):

        #fit_props = {}
        #global_props = ["MaxLoops", "tolerance"]
        #for prop in self.getProperties():
        #    if prop.name not in global_props:
        #        fit_props[prop.name]=prop.value
        function = GausDecay(A=0.04912,Sigma=0.799829)+FlatBackground(A0=0.24281)#self.getProperty("Function").value
        start = self.getProperty("StartX").value
        end = self.getProperty("EndX").value
        tol = self.getProperty("tolerance").value/100.
        ws = self.getProperty("InputWorkspace").value
        for j in range(self.getProperty("MaxLoops").value):
            print("iteration",j, function) 
            out_ws = self.doFit(function, ws, start, end)
            test_ws = self.doFit(out_ws.Function, ws, start, end, 3)
            if not self.significant_change(out_ws.OutputParameters, test_ws.OutputParameters, tol):
                return
            function = out_ws.Function
        return               

AlgorithmFactory.subscribe(pseudoGlobalFit)

func = GausDecay(A=0.04912,Sigma=0.799829)+ FlatBackground(A0=0.242810)

def create_alg(name, loops, tol, start, end, func):
    alg = mantid.AlgorithmManager.create("pseudoGlobalFit")
    alg.initialize()
    alg.setAlwaysStoreInADS(True)
    alg.setProperty("MaxLoops", loops)
    alg.setProperty("tolerance", tol)
    alg.setProperty("StartX", start)
    alg.setProperty("EndX", end)
    alg.setProperty("InputWorkspace", name)
    alg.setProperty("Function", func)
    alg.execute()
    #return retrieve_ws(name)

create_alg('MUSR86009; Pair Asym; long; MA', 100, 0.1, 0.116, 15, 'name=GausDecay,A=0.04912,Sigma=0.799829;name=FlatBackground,A0=0.242810')

image

stale[bot] commented 2 years ago

This issue has been automatically marked as stale because it has not had activity in 6 months. It will be closed in 7 days if no further activity occurs. Allowing issues to close as stale helps us filter out issues which can wait for future development time. All issues closed by stale bot act like normal issues; they can be searched for, commented on or reopened at any point. If you'd like a closed stale issue to be considered, feel free to either re-open the issue directly or contact a developer. To extend the lifetime of an issue please comment below, it helps us see that this is still affecting you and you want it fixed in the near-future. Extending the lifetime of an issue may cause the development team to prioritise it over other issues, which may be closed as stale instead.

stale[bot] commented 2 years ago

This issue has been closed automatically. If this still affects you please re-open this issue with a comment or contact us so we can look into resolving it.