import sys
import numpy as np
from numpy import sin, cos, tan, pi, exp, log, sqrt
from scipy.optimize import minimize
import pandas as pd
from matplotlib import pyplot as plt
import matplotlib.widgets as wg


from tklib.tkutils import getarg, getintarg, getfloatarg, pconv_by_type
from tklib.tkapplication import tkApplication
from tklib.tkparams import tkParams
from tklib.tkvariousdata import tkVariousData
from tklib.tksci.tksci import log10, e, kB
from tklib.tksci.tkFit import tkFit
from tklib.tksci.tkFit_m import tkFit_m


"""
半導体の散乱モデルの最小二乗法
"""

usage_str = '''
""
f"(i)   usage: python {sys.argv[0]} fit mode target infile xlabel ylabel"
'''[1:-1]



def initialize():
#================================
# Global variables
#================================
    app          = tkApplication(usage_str  = usage_str)
    argv, narg   = app.get_argv()
    app.cparams  = tkParams()
    cparams      = app.cparams
    cparams.debug       = 0
    cparams.print_level = 0

# mode: 'init', 'fit', 'sim'
#    cparams.mode = 'init'
    cparams.mode = 'fit'
#    cparams.target = 'Aop'
#    cparams.target = 'A_T0'
    cparams.target = 'A_T32'
#    cparams.target = 'VB'

    cparams.fix_N = False

# data file to fit (.xlsx)
    cparams.infile              = 'IO.xlsx'
    cparams.parameterfile       = None
    cparams.parameterbkfile     = None

    cparams.xlabel = 2
    cparams.ylabel = 6

    cparams.sample_label  = 0
    cparams.formula_label = 1
    cparams.T_label       = 6
    cparams.mu_label      = 7
    cparams.Nlabel        = 8

    cparams.Tbase   = 300.0

# Temperature range
    cparams.Tmin  =   50  # K
    cparams.Tmax  = 1000
    cparams.Tstep =   10
    cparams.nT    = int((cparams.Tmax - cparams.Tmin) / cparams.Tstep + 1)

# 最適化パラメータの初期値
#Aop
    app.Aop_params = tkParams()
    app.Aop_params.varname = ["Eop_0", "A1",   "B1",  "C1", "logN01"]
    app.Aop_params.varunit = ["eV",    "",     "",    "",   ""]
    app.Aop_params.value   = [0.046,   0.0143, 20.06, 0.0,  19.58]
    app.Aop_params.optid   = [0,       1,      0,     0,    0]
#AT0
    app.AT0_params = tkParams()
    app.AT0_params.varname = ["A2",  "B2",  "C2",     "logN02"]
    app.AT0_params.varunit = ["",     "",    "",      ""]
    app.AT0_params.value   = [0.0135, 16.15, 0.00647, 18.994]
    app.AT0_params.optid   = [1,      1,     1,       1]

#A_T3/2
    app.AT32_params = tkParams()
    app.AT32_params.varname = ["A3",    "B3", "C3", "D3", "F3", "logN03"]
    app.AT32_params.varunit = ["",       "",   "",  "",   "",   ""]
    app.AT32_params.value   = [8.00e-36, 15.0, 0.0, 0.0,  0.0,  18.5]
    app.AT32_params.optid   = [1,        1,    1,   1,    1,    1]

#VB
    app.VB_params = tkParams()
    app.VB_params.varname = ["A6",    "B6",  "C6", "D6",  "logN06"]
    app.VB_params.varunit = ["",      "",    "",   "",    ""]
    app.VB_params.value   = [4.64e-2, 10.80, 0.0,  0.031, 18.37]
    app.VB_params.optid   = [1,       1,     1,    1,     1]

#scipy.optimize.minimizeで使うアルゴリズム
#nelder-mead Downhill simplex
#cg conjugate gradient (Polak-Ribiere method)
#bfgs BFGS法
#newton-cg Newton-CG
    cparams.method = "nelder-mead"

    cparams.nmaxiter = 3000
    cparams.tol      = 1.0e-3
    cparams.h_diff   = 1.0e-3
    cparams.print_interval = 5
    cparams.plot_interval  = 1

#=============================
# Graph configuration
#=============================
    cparams.fig                 = None
    cparams.figsize             = [6, 6]
    cparams.fontsize            = 14
    cparams.legend_fontsize     = 12
    cparams.graphupdateinterval = 10

    return app, cparams

#=============================
# Treat argments
#=============================
def usage(app):
    cparams = app.cparams
    for s in app.usage_str.split('\n'):
        cmd = 'print({})'.format(s.rstrip())
        eval(cmd)

def update_vars(app, cparams):
    argv = app.argv
#    if len(argv) <= 1:
#        app.terminate(usage = usage)

    cparams.mode   = getarg(1, cparams.mode)
    cparams.target = getarg(2, cparams.target)
    cparams.infile = getarg(3, cparams.infile)
    cparams.xlabel = getarg(4, cparams.xlabel)
    cparams.ylabel = getarg(5, cparams.ylabel)
    cparams.Nlabel = getarg(6, cparams.Nlabel)


def Aop(N, Eop_0, A1, B1, C1, logN01):
    logN = log10(N)
    return Eop_0, A1 / (1 + exp(-B1 * (logN - logN01))) + C1

def Aop1(N, Eop_0, A1, B1, C1, logN01):
    logN = log10(N)
    return A1 / (1 + exp(-B1 * (logN - logN01))) + C1

def AT0(N, A2, B2, C2, logN02):
    logN = log10(N)
    return A2 /(1.0 + exp(B2 * (logN - logN02))) + C2

def AT32(N, A3, B3, C3, D3, F3, logN03):
    logN = log10(N)
    K1 = 1.0 + exp(B3 * (logN - logN03))
    a1 = A3 * N * N / K1
    K2 = 1.0 + exp(-B3 * (logN - logN03))
    a2 = C3 / K2 / (1.0 + D3 * N) + F3
    return a1 + a2

def VB(N, A6, B6, C6, D6, logN06):
    logN = log10(N)
    return A6 * (1.0 - D6 * logN) / (1.0 + exp(B6 * (logN - logN06))) + C6

def cal_mu_from_params(T, _Eop, _Aop, _AT0, _AT32, _VB):
    ekBT = e / kB / T

    muinv = _Aop / (exp(_Eop * ekBT) - 1.0)
    muinv += _AT0
    muinv += _AT32 * pow(T, -1.5)
    mu = 1.0 / muinv * exp(-_VB * ekBT)
    
    return mu

def cal_mulist(pk, T = None):
    if T is None:
        T = slef.T

    return [cal_mu_from_params(T[i], *pk) for i in range(len(T))]

def pk2params(*pk):
    return pk[0:5], pk[5:9], pk[9:15], pk[15:20]

def cal_mu_from_TN(x_list, pk):
    T, N = x_list
    ekBT = e / kB / T

    Aop_params, AT0_params, AT32_params, VB_params = pk2params(*pk)

    _Aop, _Eop  = Aop(N, *Aop_params)
    _AT0  = AT0(N, *AT0_params)
    _AT32 = AT32(N, *AT32_params)
    _VB   = VB(N, *VB_params)
#def VB(N, A6, B6, C6, D6, logN06):
#    print("params=", _Aop, _Eop, _AT0, _AT32, _VB)

    return cal_mu_from_params(T, _Eop, _Aop, _AT0, _AT32, _VB)


def fit_mu_TN(app, fit):
    cparams   = app.cparams

    fit = tkFit_m(tol = cparams.tol, nmaxiter = cparams.nmaxiter, 
                print_interval = cparams.print_interval, plot_interval = cparams.plot_interval)
#    fit = tkFit_TN(tol = cparams.tol, nmaxiter = cparams.nmaxiter, 
#                print_interval = cparams.print_interval, plot_interval = cparams.plot_interval)

    print("")
    print("Fitting to mu-T Hall data by nonlinear least-squares method")
    print("mode   : ", cparams.mode)
    print("target : ", cparams.target)
    print("infile : ", cparams.infile)
    print("parameter file : ", cparams.parameterfile)
    print("xlabel : ", cparams.xlabel)
    print("ylabel : ", cparams.ylabel)
    print("Nlabel : ", cparams.Nlabel)
    print("method : ", cparams.method)
    print("tol    : ", cparams.tol)
    print("nmaxiter: ", cparams.nmaxiter)
    
    print("")
    print(f"Read data from [{cparams.infile}]")
    print(f"Extract data for {cparams.Tmin} <= T <= {cparams.Tmax}")
#    cparams.T_label  = cparams.xlabel
#    cparams.mu_label = cparams.ylabel
    fit.read_data(cparams.infile, xlabels = [cparams.xlabel, cparams.Nlabel], ylabel = cparams.mu_label, 
                    xmins = [cparams.Tmin, None], xmaxs = [cparams.Tmax, None], usage = usage)
#    fit.read_data(cparams.infile, cparams.T_label, cparams.mu_label, usage = usage)
#    N_label, N_in             = fit.datafile.FindDataArray(cparams.Nlabel, flag = 'i')
    sample_label, sample_in   = fit.datafile.FindDataArray(cparams.sample_label, flag = 'i')
#    formula_label, formula_in = fit.datafile.FindDataArray(cparams.formula_label, flag = 'i')
    fit.sample = sample_in
    fit.sample_label = sample_label

# x_list[0]: T
# x_list[1]: N
    print("ndata_all:", fit.ndata_all)
    print("ndata    :", fit.ndata)
    print("xlabel[0](T): ", fit.xlabels[0])
    print("xlabel[1](N): ", fit.xlabels[1])
    print("ylabel      : ", fit.ylabel)
    print(f"{'sample':10}: {fit.xlabels[0]:10}  {fit.xlabels[1]:10}  {fit.ylabel:10}")
    for i in range(fit.ndata):
        print(f"{sample_in[i]:10}: {fit.x_list[0][i]:10.4g}  {fit.x_list[1][i]:10.4g}  {fit.y[i]:10.4g}")

# Calculate Nbase at Tbase for different samples
    data_set = {}
    for i in range(fit.ndata):
        index = fit.included_index[i]
        s = fit.sample[index]
        if data_set.get(s, None) is None:
            data_set[s] = [[fit.x_list[1][i]], [fit.x_list[1][i]]]
        else:
            data_set[s][0].append(fit.x_list[0][i])
            data_set[s][1].append(fit.x_list[1][i])

    fit.Nbases = []
    Nbases_dict = {}
    for i in range(fit.ndata):
        index = fit.included_index[i]
        s = fit.sample[index]
        Nbase = np.interp([cparams.Tbase], data_set[s][0], data_set[s][1])[0]
        Nbases_dict[s] = Nbase
        fit.Nbases.append(Nbase)
#    print("s=", sample_in)
#    print("Nbase=", fit.Nbases)
    
    print("")
    if cparams.fix_N:
        fit.fix_N = True
        fit.Tbase = cparams.Tbase
        fit.x_list[1] = fit.Nbases
        for s in Nbases_dict.keys():
            print(f"sample [{s}]: N is fixed to N={Nbases_dict[s]:10.4g} cm-3 at T={cparams.Tbase} K")
    else:
        print(f"Nbase={cparams.Nbase:10.4g} cm-3 from T={cparams.Tbase} K")
        print(f"N values are taken from T-dependent Hall data")


#=============================================
# Build pk
#=============================================
    fit.func = cal_mu_from_TN

    fit.varname = app.Aop_params.varname.copy()
    fit.unit    = app.Aop_params.varunit.copy()
    fit.pk      = app.Aop_params.value.copy()
    fit.optid   = app.Aop_params.optid.copy()

    fit.varname += app.AT0_params.varname
    fit.unit    += app.AT0_params.varunit
    fit.pk      += app.AT0_params.value
    fit.optid   += app.AT0_params.optid

    fit.varname += app.AT32_params.varname
    fit.unit    += app.AT32_params.varunit
    fit.pk      += app.AT32_params.value
    fit.optid   += app.AT32_params.optid

    fit.varname += app.VB_params.varname
    fit.unit    += app.VB_params.varunit
    fit.pk      += app.VB_params.value
    fit.optid   += app.VB_params.optid
#    print("pk=", len(fit.pk), fit.pk)
#    print("optid=", len(fit.optid), fit.optid)

    print("")
    print("# of variables:", len(fit.pk))
    fit.print_variables()

    print("")
    yini = fit.cal_ylist(fit.pk, fit.x_list)
    fit.print_data(heading = "Initial functions", yini = yini)

#========================================
# plot
#========================================
    fig, axes = plt.subplots(1, 1, figsize = cparams.figsize)
    axes = [axes]
#    axes = axes.flatten()

    fit.initial_plot(axes[0], yini = yini, fontsize = cparams.fontsize)

#========================================
# Optimize
#========================================
    pfin, ffin, success, res = fit.minimize(cparams.method)
    if success:
        print("")
        print(f"Converged at iteration: {res.nit}")
    else:
         print(f"Function did not converge")
         print(res)

    pk = pfin
    n = len(pk)
    print("Final parameters")
    for i in range(n):
         print(f"  {fit.varname[i]:10}: {pfin[i]:10.4g} {fit.unit[i]}")
    print(f"    f={ffin:12.6g}")

    app.Aop_params.value, app.AT0_params.value, app.AT32_params.value, app.VB_params.value = pk2params(*pfin)

#========================================
# Final result
#========================================
    yfin = fit.cal_ylist(pfin)
    fit.print_data(heading = "Final functions", yini = yini, yfin = yfin)

    outfile = app.replace_path(cparams.infile, template = ["{dirname}", "{filebody}-mu_TN-fit.xlsx"])
    print("")
    print(f"Save results to [{outfile}]")
    fit.to_excel(outfile, ['N(cm-3)', 'T(K)', 'mu', 'mu(ini)', 'mu(fin)'], [fit.x_list[1], fit.x_list[0], fit.y, yini, yfin])

# Delete parameters in cparams so as not to save to Preferences section
    for key in ["Eop_0", "A1", "B1", "C1", "logN01", "A2", "B2", "C2", "logN02", "A3", "B3", "C3", "D3", "F3", 
                "logN03", "A6", "B6", "C6", "D6", "logN06", "Aop", "AT0", "AT32", "VB"]:
        if cparams.get(key, None) is not None:
           cparams.__dict__[key] = None
        
    print("")
    print(f"Save parameters to {cparams.parameterfile}")
#    cparams.print_parameters()
    cparams.save_parameters(cparams.parameterfile, section = 'Preferences', sort_by_keys = False, update_commandline = False, IsPrint = False)

    fit.save_parameters (cparams.parameterfile, section = 'Aop', heading = "Save Aop_parameters", 
            keys = app.Aop_params.varname, values = app.Aop_params.value, optid = app.Aop_params.optid) 
    fit.save_parameters (cparams.parameterfile, section = 'AT0', heading = "Save AT0_parameters", 
            keys = app.AT0_params.varname, values = app.AT0_params.value, optid = app.AT0_params.optid) 
    fit.save_parameters (cparams.parameterfile, section = 'AT32', heading = "Save AT32_parameters", 
            keys = app.AT32_params.varname, values = app.AT32_params.value, optid = app.AT32_params.optid) 
    fit.save_parameters (cparams.parameterfile, section = 'VB', heading = "Save VB_parameters", 
            keys = app.VB_params.varname, values = app.VB_params.value, optid = app.VB_params.optid) 
    
    print("")
    print(f"Scattering parameters at Tbase={cparams.Tbase:10.4g} K")
    print("a=", app.Aop_params.value)
    for s in Nbases_dict.keys():
        Nbase = Nbases_dict[s]
        mu_params = tkParams()
        mu_params.Eop, mu_params.Aop = Aop(Nbase, *app.Aop_params.value)
        mu_params.AT0  = AT0 (Nbase, *app.AT0_params.value)
        mu_params.AT32 = AT32(Nbase, *app.AT32_params.value)
        mu_params.VB   = VB  (Nbase, *app.VB_params.value)
        print(f"  sample [{s}]: N is fixed to N={Nbases_dict[s]:10.4g} cm-3 at T={cparams.Tbase} K")
        print(f"    Aop ={mu_params.Aop:10.4g}")
        print(f"    Eop ={mu_params.Eop:10.4g}")
        print(f"    AT0 ={mu_params.AT0:10.4g}")
        print(f"    AT32={mu_params.AT32:10.4g}")
        print(f"    VB  ={mu_params.VB:10.4g}")
        mu_params.save_parameters(cparams.parameterfile, section = s, sort_by_keys = False, update_commandline = False, IsPrint = False)

    fit.finalize_plot(yfin, iter = res.nit, fmin = ffin)

    app.terminate("", pause = True)


def fit_mu_T(app, fit = None):
    cparams   = app.cparams

    cparams.target_sample = '02A-01'

    mu_params = tkParams()

    print("")
    print("Fitting to mu-T Hall data by nonlinear least-squares method")
    print("mode   : ", cparams.mode)
    print("target : ", cparams.target)
    print("infile : ", cparams.infile)
    print("parameter file : ", cparams.parameterfile)
    print("xlabel : ", cparams.xlabel)
    print("ylabel : ", cparams.ylabel)
    print("method : ", cparams.method)
    print("tol    : ", cparams.tol)
    print("nmaxiter: ", cparams.nmaxiter)

    fit = tkFit(tol = cparams.tol, nmaxiter = cparams.nmaxiter,
                print_interval = cparams.print_interval, plot_interval = cparams.plot_interval)

    print("")
    print(f"Read data from [{cparams.infile}]")
    fit.read_data(cparams.infile, cparams.T_label, cparams.mu_label, usage = usage)
    sample_label, sample_in   = fit.datafile.FindDataArray(cparams.sample_label, flag = 'i')
    formula_label, formula_in = fit.datafile.FindDataArray(cparams.formula_label, flag = 'i')
    N_label, N_in             = fit.datafile.FindDataArray(cparams.Nlabel, flag = 'i')

    print("")
    print(f"Extract data for sample [{cparams.target_sample}] and {cparams.Tmin} <= T <= {cparams.Tmax}")
    fit.T  = []
    fit.mu = []
    fit.N      = []
    for i in range(len(fit.x)):
        if sample_in[i] == cparams.target_sample and cparams.Tmin <= fit.x[i] <= cparams.Tmax:
            fit.T.append(fit.x[i])
            fit.mu.append(fit.y[i])
            fit.N.append(N_in[i])
    fit.Tlabel = fit.xlabel
    fit.mulabel = fit.ylabel
    fit.Nlabel = N_label
    fit.x = fit.T
    fit.y = fit.mu
    ndata = len(fit.T)
    print("ndata=", ndata)
    print(f"{fit.Tlabel:10}  {fit.mulabel:10}  {fit.Nlabel:10}")
    for i in range(ndata):
        print(f"{fit.T[i]:10.4g}  {fit.mu[i]:10.4g}  {fit.N[i]:10.4g}")

    cparams.Nbase = np.interp([cparams.Tbase], fit.T, fit.N)[0]
    mu_params.Eop, mu_params.Aop  = Aop(cparams.Nbase, *app.Aop_params.value)
    mu_params.AT0  = AT0 (cparams.Nbase, *app.AT0_params.value)
    mu_params.AT32 = AT32(cparams.Nbase, *app.AT32_params.value)
    mu_params.VB   = VB  (cparams.Nbase, *app.VB_params.value)

    fit.varname = [        "Eop",        "Aop",         "AT0",         "AT32",         "VB"]
    fit.unit    = [           "",           "",            "",             "",         "eV"]
    fit.pk      = [mu_params.Eop, mu_params.Aop, mu_params.AT0, mu_params.AT32, mu_params.VB]
    fit.optid   = [            0,             1,             1,              1,            1]
    fit.func = lambda T, *pk: cal_mu_from_params(T, *pk)

    print("")
    print(f"Scattering parameters at Nbase={cparams.Nbase:10.4g} cm-3")
    fit.print_variables()

    optpk = fit.extract_parameters()
    yini = cal_mulist(fit.pk, fit.T)
    print("")
    print("Initial guess")
    print(f"{'i':4} {'T':10} {'mu':12} {'mu(cal)':12}")
    for i in range(len(fit.T)):
        print(f"{i:4} {fit.T[i]:10.4g} {fit.mu[i]:12.4g} {yini[i]:12.4g}")

#========================================
# plot
#========================================
    fig, axes = plt.subplots(1, 1, figsize = cparams.figsize)
    axes = [axes]
#    axes = axes.flatten()

    fit.initial_plot(axes[0], yini = yini, fig = fig, fontsize = cparams.fontsize)
#    fit.initial_plot(data_axis = axes[0], error_axis = axes[1], yini = yini, label_ini = 'initial',
#                fmin = fini, fig = fig,
#                fontsize = cparams.fontsize)

#========================================
# Optimize
#========================================
    pfin, ffin, success, res = fit.minimize(cparams.method)
    if success:
#       print("")
#       print(res)
        print("")
        print(f"Converged at iteration: {res.nit}")
    else:
         print(f"Function did not converge")
         print(res)

    pk = fit.recover_parameters(optpk, set_member = True)
    print("Final parameters")
    n = len(pfin)
    for i in range(n):
         print(f"  {fit.varname[i]:10}: {pfin[i]:10.4g} {fit.unit[i]}")
    print(f"    f={ffin:12.6g}")

#========================================
# Final result
#========================================
    yfin = fit.cal_ylist(pfin)
    print("")
    print("Final result")
    print(f"{'i':4} {'T':10} {'mu':12} {'mu(ini)':12} {'mu(fin)':12}")
    for i in range(len(fit.x)):
        print(f"{i:4} {fit.T[i]:10.4g} {fit.mu[i]:12.4g} {yini[i]:12.4g} {yfin[i]:12.4g}")

    outfile = app.replace_path(cparams.infile, template = ["{dirname}", "{filebody}-mu-fit.xlsx"])
    df = pd.DataFrame(np.array([fit.T, fit.mu, yini, yfin]).T, columns = ['T(K)', 'mu', 'mu(ini)', 'mu(fin)'])
    print("")
    print(f"Save results to [{outfile}]")
    df.to_excel(outfile, index = False)

    print("")
    print(f"Save parameters to {cparams.parameterfile}")
    cparams.save_parameters    (cparams.parameterfile, section = 'Preferences', sort_by_keys = False, update_commandline = False, IsPrint = False)
    mu_params.save_parameters (cparams.parameterfile, section = 'mu',    sort_by_keys = False, update_commandline = False, IsPrint = False)

    fit.fit_data[0].set_data(fit.x, yfin)
    
    plt.tight_layout()
    plt.subplots_adjust(top = 0.90, bottom = 0.10)
    plt.pause(0.0001)

    app.terminate("", usage = usage, pause = True)

def fit_parameters(app, fit = None):
    cparams     = app.cparams

    print("")
    print("Fitting to mu-T Hall data by nonlinear least-squares method")
    print("mode    : ", cparams.mode)
    print("target  : ", cparams.target)
    print("infile  : ", cparams.infile)
    print("xlabel  : ", cparams.xlabel)
    print("ylabel  : ", cparams.ylabel)
    print("method  : ", cparams.method)

    fit = tkFit(tol = cparams.tol, nmaxiter = cparams.nmaxiter,
                print_interval = cparams.print_interval, plot_interval = cparams.plot_interval)

    print("tol           : ", fit.tol)
    print("nmaxiter      : ", fit.nmaxiter)
    print("print_interval: ", fit.print_interval)
    print("plot_interval : ", fit.plot_interval)

    if cparams.target == 'Aop':
        fit.varname = app.Aop_params.varname
        fit.unit    = app.Aop_params.varunit
        fit.pk      = app.Aop_params.value
        fit.optid   = app.Aop_params.optid
        fit.func    = Aop1
    elif cparams.target == 'A_T0':
        fit.varname = app.AT0_params.varname
        fit.unit    = app.AT0_params.varunit
        fit.pk      = app.AT0_params.value
        fit.optid   = app.AT0_params.optid
        fit.func    = AT0
    elif cparams.target == 'A_T32':
        fit.varname = app.AT32_params.varname
        fit.unit    = app.AT32_params.varunit
        fit.pk      = app.AT32_params.value
        fit.optid   = app.AT32_params.optid
        fit.func    = AT32
    elif cparams.target == 'VB':
        fit.varname = app.VB_params.varname
        fit.unit    = app.VB_params.varunit
        fit.pk      = app.VB_params.value
        fit.optid   = app.VB_params.optid
        fit.func = VB

    print("")
    print(f"Read data from [{cparams.infile}]")
    fit.read_data(cparams.infile, cparams.xlabel, cparams.ylabel, usage = usage)
    ndata = len(fit.x)
    print("x=", fit.xlabel, fit.x)
    print("y=", fit.ylabel, fit.y)
    print("ndata=", ndata)

    print("pk=", fit.pk)
    yini = fit.cal_ylist(fit.pk, fit.x)
    print("")
    print("Initial guess")
    print(f"{'i':4} {cparams.xlabel:10} {cparams.ylabel:12} {cparams.xlabel+'(cal)':12}")
    for i in range(len(fit.x)):
        print(f"{i:4} {fit.x[i]:10.4g} {fit.y[i]:12.4g} {yini[i]:12.4g}")

# plot
    fig, axes = plt.subplots(1, 1, figsize = cparams.figsize)
    axes = [axes]
#    axes = axes.flatten()

    fit.initial_plot(data_axis = axes[0], yini = yini, fig = fig, fontsize = cparams.fontsize)
#    fit.initial_plot(data_axis = axes[0], error_axis = axes[1], yini = yini, label_ini = 'initial',
#                fmin = fini, fig = fig,
#                fontsize = cparams.fontsize)


    pfin, ffin, success, res = fit.minimize(cparams.method, cparams.tol, cparams.nmaxiter)
    if success:
#       print("")
#       print(res)
        print("")
        print(f"Converged at iteration: {res.nit}")
    else:
         print(f"Function did not converge")
         print(res)

    print("Final parameters")
    n = len(pfin)
    for i in range(n):
         print(f"  {fit.varname[i]:10}: {pfin[i]:10.4g} {fit.unit[i]}")
    print(f"    f={ffin:12.6g}")

    yfin = fit.cal_ylist(pfin)
    print("")
    print("Final result")
    print(f"{'i':4} {fit.xlabel:10} {fit.ylabel:12} {fit.ylabel + '(ini)':12} {'y(fin)':12}")
    for i in range(len(fit.x)):
        print(f"{i:4} {fit.x[i]:10.4g} {fit.y[i]:12.4g} {yini[i]:12.4g} {yfin[i]:12.4g}")

    outfile = app.replace_path(cparams.infile, template = ["{dirname}", "{filebody}-{target}-fit.xlsx"], ext_dict = { "target": cparams.target } )
    df = pd.DataFrame(np.array([fit.x, fit.y, yini, yfin]).T, 
                        columns = [cparams.xlabel, cparams.ylabel, f"{cparams.ylabel}(ini)", f"{cparams.ylabel}(fin)"])
    print("")
    print(f"Save results to [{outfile}]")
    df.to_excel(outfile, index = False)

    print("")
    print(f"Save parameters to {cparams.parameterfile}")
#    cparams.print_parameters()
    cparams.save_parameters(cparams.parameterfile, section = 'Preferences', sort_by_keys = False, update_commandline = False, IsPrint = False)

    fit.save_parameters (cparams.parameterfile, section = 'Aop', heading = "Save Aop_parameters", 
            keys = app.Aop_params.varname, values = app.Aop_params.value, optid = app.Aop_params.optid) 
    fit.save_parameters (cparams.parameterfile, section = 'AT0', heading = "Save AT0_parameters", 
            keys = app.AT0_params.varname, values = app.AT0_params.value, optid = app.AT0_params.optid) 
    fit.save_parameters (cparams.parameterfile, section = 'AT32', heading = "Save AT32_parameters", 
            keys = app.AT32_params.varname, values = app.AT32_params.value, optid = app.AT32_params.optid) 
    fit.save_parameters (cparams.parameterfile, section = 'VB', heading = "Save VB_parameters", 
            keys = app.VB_params.varname, values = app.VB_params.value, optid = app.VB_params.optid) 

    fit.fit_data[0].set_data(fit.x, yfin)
    
    plt.tight_layout()
    plt.subplots_adjust(top = 0.90, bottom = 0.10)
    plt.pause(0.0001)

    app.terminate("\nPress ENTER to exit>>", usage = usage, pause = True)


def main():
    app, cparams = initialize()
    update_vars(app, cparams)

    cparams.logfile = app.replace_path(cparams.infile)
    cparams.outfile = app.replace_path(cparams.infile, template = ["{dirname}", "{filebody}-out.xlsx"])
    print(f"Open logfile [{cparams.logfile}]")
    app.redict(targets = ["stdout", cparams.logfile], mode = 'w')

    cparams.parameterfile = app.replace_path(cparams.infile, template = ["{dirname}", "{filebody}.in"])
    cparams.parameterbkfile = app.replace_path(cparams.infile, template = ["{dirname}", "{filebody}-back.in"])

    print("")
    print("infile               : {}".format(cparams.infile))
    print("parameter file       : {}".format(cparams.parameterfile))
    print("parameter backup file: {}".format(cparams.parameterbkfile))
    print("mode               : {}".format(cparams.mode))
    print("target               : {}".format(cparams.target))

    print("")
    print("Read [{}]".format(cparams.parameterfile))
    cparams.read_parameters(cparams.parameterfile, section = "Preferences")

    fit = tkFit(tol = cparams.tol, nmaxiter = cparams.nmaxiter, 
                print_interval = cparams.print_interval, plot_interval = cparams.plot_interval)
#    fit = tkFit_TN(tol = cparams.tol, nmaxiter = cparams.nmaxiter, 
#                print_interval = cparams.print_interval, plot_interval = cparams.plot_interval)

    fit.read_parameters(cparams.parameterfile, section = "Aop", 
                        keys = app.Aop_params.varname, values = app.Aop_params.value, optid = app.Aop_params.optid)
    app.Aop_params.print_parameters(heading = "Aop")
    fit.read_parameters(cparams.parameterfile, section = "AT0", 
                        keys = app.Aop_params.varname, values = app.Aop_params.value, optid = app.Aop_params.optid)
    app.AT0_params.print_parameters(heading = "AT0")
    fit.read_parameters(cparams.parameterfile, section = "AT32", 
                        keys = app.Aop_params.varname, values = app.Aop_params.value, optid = app.Aop_params.optid)
    app.AT32_params.print_parameters(heading = "AT32")
    fit.read_parameters(cparams.parameterfile, section = "VB", 
                        keys = app.Aop_params.varname, values = app.Aop_params.value, optid = app.Aop_params.optid)
    app.VB_params.print_parameters(heading = "VB")


# check args again to use given parameters
    update_vars(app, cparams)
    
    cparams.print_parameters(heading = "\ncparams:")
    app.Aop_params.print_parameters(heading = "\nAop_params:")
    app.AT0_params.print_parameters(heading = "\nAT0_params:")
    app.AT32_params.print_parameters(heading = "\nAT32_params:")
    app.VB_params.print_parameters(heading = "\nVB_params:")

    if cparams.mode == 'init':
        init(app)
    elif cparams.mode == 'fit':
        if cparams.target == 'mu_T':
            fit_mu_T(app, fit)
        elif cparams.target == 'mu_TN':
            fit_mu_TN(app, fit)
        elif cparams.target == 'mu_T_Nfix':
            cparams.fix_N = True
            fit_mu_TN(app, fit)
        elif cparams.target == 'Aop' or cparams.target == 'A_T0' or cparams.target == 'A_T32' or cparams.target == 'VB':
            fit_parameters(app, fit)
        else:
            app.terminate("Error in main: Invalide target [{}]".format(cparams.target), usage = usage)
    elif cparams.mode == 'sim':
        sim(app)
    else:
        app.terminate("Error in main: Invalide mode [{}]".format(cparams.mode), usage = usage)

    app.terminate("", usage = usage, pause = True)


if __name__ == "__main__":
    main()
