尝试用任意曲线拟合 Python 中的多个 Moyal 函数

Trying to fit a multiple Moyal function in Python with arbitrary curves

我一直在尝试拟合具有多个 Landau 峰的分布(所以我使用 scipy.stats moyal)。我试过使用 scipy curve_fit,但我不断收到错误。该函数将有许多曲线,由“nMIPs”指定。函数如下:

x = np.linspace(0, len(ADC[0])-1, len(ADC[0]))

def multMoyal(x, nMIPs, *args):
    moy = 0
    for i in range(int(nMIPs)):
        r = 3*i
        moy = moy + args[r]*moyal.pdf(x, args[r+1], args[r+2])
    return moy

其中ADC是光谱。这是粒子碰撞数据;它本质上是带电粒子穿过检测器中的瓦片,每条莫亚尔曲线代表带电粒子的数量(通常为 2-5,具体取决于实验参数和瓦片位置)。

当我像这样调用函数时(nMIPs=2):

args = [1, 1, 0.5, 0.8, 2, 0.5]
y = multMoyal(x, nMIPs, *args)

但是当我尝试在 curve_fit 中输入它时,我不断收到错误消息(“IndexError:元组索引超出范围”)。 curve_fit行如下:

    param, param_cov = curve_fit(multMoyal(x=x, nMIPs=nMIPs), x, y[0], maxfev=5000)

其中 y[0] 是 ADC 频谱之一,但对 x 进行了平滑处理以去除噪声。

那么,如何让它拥有任意数量的曲线并且仍然在 scipy 的 curve_fit 中工作?还是我应该使用 curve_fit 以外的东西?

相关:如果我让它工作,我如何设置某些参数但让其他参数未确定? (我想为第一条莫亚尔曲线和权重设置峰值的猜测,但将其他所有内容留空。)

谢谢!

我找到了一个解决方案,但我不知道这是否是正确的方法(但它确实有效)。我将函数更新为:

def multMoyal(x, nMIPs, *args):
    moy = 0
    for i in range(int(nMIPs)):
        l = 3*i
        moy = moy + args[l]*moyal.pdf(x, args[l+1], args[l+2])
    return moy

然后我添加了边界和猜测。 scipy.optimise.curve_fit 的初始猜测都是 1,因此几乎您输入的任何内容都会比这更好。我选择了一些似乎适合问题的范围,然后从那里开始。为了设置单个参数(或您喜欢的任何参数),我使用了边界。将边界设置为 +- np.inf 将使该参数自由。

例如,我使用了从 nMIPs 到 nMIPs+1 的 nMIPs 界限,这实际上将其设置为 nMIPs(当然,我猜测是 nMIPs)。看起来如下:

fitStart = []
startVal = []
weightGuess = []
guess = []
boundsInit = []
boundsEnd = []
for q in range(int(len(ADC))):
    MIPguess = exMax[q][np.where(exMax[q] > exMin[q][0])[0][0]]-10
    starter = int(exMin[q][0]+abs(exMin[q][0]-MIPguess)/2)
    fitStart.append(starter)
    startVal.append(MIPguess)
    weightGuess.append(y[q][MIPguess])
    guess.append([nMIPs, weightGuess[q], startVal[q], 0.2*startVal[q]])
    boundsInit.append([nMIPs, 0, fitStart[q], 0])
    boundsEnd.append([nMIPs+1, np.inf, np.inf, 0.3*startVal[q]])
    for i in range(1, nMIPs):
        l = i+1
        guess[q].extend([weightGuess[q]/l, l*startVal[q], 0.2*startVal[q]])
        boundsInit[q].extend([0, fitStart[q]*l*0.75, 0])
        boundsEnd[q].extend([np.inf, np.inf, np.inf])

如果我只想设置 nMIP,我可以将所有起始猜测都放在 1 和边界(nMIP 除外)到 +- np.inf。然后进行拟合:

param, param_cov = curve_fit(
            multMoyal, x[fitStart[q]:], ADC[q][fitStart[q]:], maxfev=5000, p0=guess[q],
            bounds=(boundsInit[q], boundsEnd[q]))

它有效,但我非常怀疑它可能是最紧凑的代码。我仍然觉得我缺少一些更简单的方法,但现在就可以了。我很想知道专家们可能会对此做出什么回答,但我将其记为答案,因为还没有任何答案,这至少解决了问题。