非線形最適化 scipy.minimize()を用いたピークフィッティング

Download script from .\peakfit-scipy-minimize.py


import numpy as np
from math import exp
from scipy.optimize import minimize
import matplotlib.pyplot as plt


"""
非線形最適化 scipy.minimize()を用いたピークフィッティング
"""


#========================================================
# scipy.minimize()で使える最適化アルゴリズム
# ここでは共役勾配法(cg)を選択
#========================================================
#nelder-mead Downhill simplex
#powell Modified Powell
#cg conjugate gradient (Polak-Ribiere method)
#bfgs BFGS法
#newton-cg Newton-CG
#trust-ncg 信頼領域 Newton-CG 法
#dogleg 信頼領域 dog-leg 法
#L-BFGS-B’ (see here)
#TNC’ (see here)
#COBYLA’ (see here)
#SLSQP’ (see here)
#trust-constr’(see here)
#dogleg’ (see here)
#trust-exact’ (see here)
#trust-krylov’ (see here)
method = "cg"

maxiter = 100
tol = 1.0e-5

#==========================================
# Source parameters to be fitted
#==========================================
# フィッティング対象のプロファイル関数のパラメータ: I0, x0, w
peaks = [1.1, 0.4, 0.4]
nparams = len(peaks)

xrange = [-1.0, 2.0]
nx = 101
xstep = (xrange[1] - xrange[0]) / (nx - 1)
xd = []
for i in range(nx):
   xd.append(xrange[0] + i * xstep)
yd = []

# 最適化変数の初期値
x0 = [1.3, 0.6, 0.1]


#==========================================
# Graph parameters
#==========================================
fplot = 1
ngdata = 51
xgmin = -4.0
xgmax = 4.0
ygmin = -4.0
ygmax = 4.0
tsleep = 0.3

#==========================================
# functions
#==========================================
# 与えられた変数 params と、座標 x におけるプロファイル関数の値
def ycal(x, params):
    ret = 0.0
    for ip in range(0, len(params), 3):
        I0 = params[ip]
        x0 = params[ip+1]
        w = params[ip+2]
        if w < 0.001:
            w = 0.001
        a = 0.832554611 / w
        X = a * (x - x0)
        ret += I0 * exp(-X * X)
    return ret

def ycal_list(xd, params):
    print("pa=", params)
    y = []
    for i in range(len(xd)):
        y.append(ycal(xd[i], params))
    return y

# 最小化する目的関数。残差二乗和
def CalS2(params):
    global xd, yd, nx
    S2 = 0.0
    for i in range(nx):
        yc = ycal(xd[i], params)
        d = yd[i] - yc
        S2 += d * d
    return S2

# i番目の最適化変数による一次微分。SIMPLEX法では使わない
def diff1i(i, params):
    global xd, yd, nx

    ip = i // 3
    idx = i % 3
    I0 = params[ip*3]
    x0 = params[ip*3+1]
    w = params[ip*3+2]

    ret = 0.0
    for id in range(nx):
        if w < 0.001:
            w = 0.001

        yc = ycal(xd[id], [I0, x0, w])
        dy = yd[id] - yc

        a = 0.832554611 / w
        dx = a * (xd[id] - x0)
        e = exp(-dx*dx)
        if idx == 0: # I0
            dif = e
        elif idx == 1: # x0
            dif = 2.0 * a * dx * I0 * e
        elif idx == 2: # w
            dif = 2.0 * dx * dx / w * I0 * e
        else:
            print("Error in diff1i: Invalid index (ivar={}, ipeak={}, diff_idx={})".format(i, ip, idx))
            exit()
        d = -2.0 * dif * dy
        ret += d

    return ret

# 一次微分関数配列。SIMPLEX法では使わない
def diff1(params):
    global nparams
    df = np.empty(len(params))
    for i in range(len(params)):
        df[i] = diff1i(i, params)
    return df


# 最適化の各サイクルごとに呼び出されるコールバック関数
# callbackへは変数の値しか渡されないので、他に必要な変数は
# global変数として渡す
iter = 0
xiter = []
yfmin = []
figure = None
ax_fit = None
rawdata = None
inidata = None
fitdata = None
ax_conv = None
convdata = None
def callback(xk):
    global xd
    global iter
    global figure, ax_fit, ax_conv
    global fitdata, convdata

    fmin = CalS2(xk)
    print("callback {}: xk={}".format(iter, xk))
    print(" fmin={}".format(fmin))
    iter += 1
    xiter.append(iter)
    yfmin.append(fmin)

    convdata[0].set_data(xiter, yfmin)
    ax_conv.set_xlim((0.0, max(xiter) + 1.0))
    ax_conv.set_ylim((min(yfmin) * 0.8, max(yfmin) * 1.2))

    yc = ycal_list(xd, xk)
    fitdata[0].set_data(xd, yc)

    plt.pause(0.2)


#==========================================
# Main routine
#==========================================
def main():
    global xd, yd, xiter, yfmin
    global figure, ax_fit, ax_conv
    global fitdata, convdata

    print("")
    print("Peak fitting by python scipy.optimize")

    yd = ycal_list(xd, peaks)
    yini = ycal_list(xd, x0)

#グラフを描画
    if fplot == 1:
        figure = plt.figure(figsize = (10, 5))
        ax_fit = figure.add_subplot(1, 2, 1)
        rawdata = ax_fit.plot(xd, yd, color = 'blue', linestyle = '', linewidth = 0.5,
                            fillstyle = 'full', marker = 'x', markersize = 5)
        inidata = ax_fit.plot(xd, yini, color = 'black', linestyle = 'dashed', linewidth = 0.5)
        fitdata = ax_fit.plot([], [], color = 'red', linestyle = '-', linewidth = 0.5)

        ax_conv = figure.add_subplot(1, 2, 2)
        ax_conv.set_yscale('log')
        convdata = ax_conv.plot([], [], color = 'black', linestyle = '-', linewidth = 0.5,
                            fillstyle = 'full', marker = 'o', markersize = 5)
        plt.pause(0.001)

#scipy.minimize()による最適化
    res = minimize(CalS2, x0, jac=diff1, method=method, tol = tol, callback = callback,
                options = {'maxiter':maxiter, "disp":True})
#最適化結果の表示
    print(res)


    if fplot == 1:
        print("Press ENTER to terminate:", end = '')
        ret = input()


if __name__ == "__main__":
    main()