import os
import sys
from glob import glob
import re
import random
import time

import tklib.tkimport as imp

np  = imp.import_lib("numpy",      stop_by_error = False)
mpl = imp.import_lib("matplotlib", stop_by_error = False)
pd  = imp.import_lib("pandas",     stop_by_error = False)
skl = imp.import_lib("tkinter",    stop_by_error = False)
tk  = imp.import_lib("sklearn",    stop_by_error = False)
imp.messages(stop_by_error = True)

from numpy import sin, cos, tan, pi, exp, log, sqrt
from matplotlib import pyplot as plt
import matplotlib.widgets as wg

from sklearn.linear_model import LinearRegression, Lasso, Ridge
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_absolute_error

from tkinter import ttk
from tkinter import simpledialog


from tklib.tkutils import print_data, pint, pfloat, index2val, pconv_by_type, is_numeric, format_strlist
from tklib.tkutils import kill_process_interactive
from tklib.tkapplication import tkApplication
from tklib.tkparams import tkParams
from tklib.tkvariousdata import tkVariousData

from tklib.tksci.tkFit_object import save_fit_config, read_fit_config_from_file
from tklib.tksci.tkFit_lib import save_data
from tklib.tksci.tkoptimize import mlsq_general_optid
from tklib.tksci.tkFit_mxy_flex import tkFit_mxy
from tklib.tkgraphic.tkplotevent import tkPlotEvent, RangeSelector
from tklib.tkgraphic.tkplot_pyplot import select_plt, tkPlot_pyplot
from tklib.tkgraphic.tkplot_tkinter import tkPlot_tkinter
from tklib.tkgui.tksimple_gui import get_window_from_plt, CustomDialog_with_config
from tklib.tksci.tkmlr import tkMLData, tkMLR

from tklib.tktemplate import convert_file


# plot configurations
widths  = [1.0, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5]
markers = ['o', 's', '^', '<', '>', '+', '^', 'x']
msizes  = [5.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0]


def initialize_minimize_func(app):
    if app.get("config_cmd", None) is None: app.config_cmd = tkParams()

    os = platform.system()
    app.config_cmd.script = 'mobility.py'
    
    return app.config_cmd

def get_cmd(xk, fit):
    config = fit.config_cmd

    config.mode = 'fit'
    config.infile = fit.infile
    config.datafile = fit.datafile
    config.fplot = 0

    head = f"python {config.script} {config.mode} {config.infile} {config.datafile}"

    s = ''
    for x in xk:
        s += f" {x}"

    return head + s

def save_parameters(path, fit, cfg):
    flag_keys       = ["fplot", "fhistory", "ffitfiles"]
    condiction_keys = ["mode", "method", "jac", "nmaxiter", "nmaxcall", "tol", "y_scale"]
    files_keys      = ["infile", "calfile", "outfile", "datafile", "historyfile", "fitfile",
                       "templatepath"]
    data_keys       = ["sample", "Tmin", "Tmax", "Nmin", "Nmax"]
    scan_keys       = ["target_var", "x0", "x1", "nx"]
    exclude_keys    = flag_keys + condiction_keys + fit.varname + files_keys + data_keys + scan_keys \
                      + ["logfile", "parameterfile", "stopfile"]

    print("app=", app.varname)
    print("app=", app.x0)
    print("app=", app.kpenalty)
    print("fit=", fit.varname)
    print("fit=", fit.pk)
    print("fit=", fit.kpenalty)


    print(f"Save parameters to [{path}]")
    cfg.save_parameters(path, section = "Preferences", exclude_keys = exclude_keys)
    cfg.save_parameters(path, section = "Files",      keys = files_keys)
    cfg.save_parameters(path, section = "Data",       keys = data_keys)
    cfg.save_parameters(path, section = "Flags",      keys = flag_keys)
    cfg.save_parameters(path, section = "Condition",  keys = condiction_keys)
    cfg.save_parameters(path, section = "Scan",       keys = scan_keys)

    print(f"Save fitting parameters to [{fit.fitfile_path}]")
    fit.save_parameters(fit.fitfile_path, section = "Parameters", keys = fit.varname, vars = cfg)
#    fit.save_fit_config(fit, fit.fitfile_path)

def read_parameters(path, cfg):
#パラメータファイルの読み込み。Parametersセクション以外のみ
    sectios     = ["Preferences", "Flags", "Files", "Scan", "Condition"]
    ignore_keys = ["logfile", "outfile", "parameterfile", "parameterbkfile", "file1", "file2", "file3", "file4", "file5"]

    print(f"Read parameters from [{path}]")
    for section in sectios:
        cfg.read_parameters(cfg.parameterfile, section = section, ignore_keys = ignore_keys)

def save_history(xk_all, fmin, fit, save_time = True):
    if fit.cfg.fhistory:
        print(f"Add fmin and parameters to [{fit.historyfile}]")
        iter = fit.add_history(None, fit.varname, xk_all, fmin, save_time = save_time)
#        iter = fit.add_history(fit.historyfile, fit.varname, xk_all, fmin, save_time = True)
        if iter is not None:
            print(f"#{iter}: fmin=", f"{fmin:12.4g}:".strip(), " xk=", end = '')
            for v in xk_all:
                print(f" {v:12.6g}".strip() + " ", end = '')
            print()
        else:
            iter = None
            print(f"  Warning: Failed to add to {fit.historyfile}]")
            print(f"           Check write permission / disk space etc")

    if fit.cfg.ffitfiles and iter is not None:
        save_path = f'fit{iter:04}.xlsx'
        print(f"Save last input and calculation data  to [{save_path}]")
        if hasattr(fit, 'yini_list'):
            ret = save_data(save_path, 
                        ['x', 'input', 'initial', 'final'], [fit.x_list[0], fit.y_list[0], fit.yini_list[0], fit.yc_list[0]],
                        template = cfg.get("xlsm_template", None))
        else:
            ret = save_data(save_path, ['x', 'input', 'final'], [fit.x_list[0], fit.y_list[0], fit.yc_list[0]],
                        template = cfg.get("xlsm_template", None))
        if not ret:
            print(f"  Warning: Failed to save to [{save_path}]")
            print(f"           Check write permission / disk space etc")

def delete_files_yes_no(filemask, file_type = 'Config ini file', new_file_caution = True, check_writable = True, delete_files = ''):
    files = glob(filemask)
    
    delete_files = delete_files.lower()
    if len(files) > 0 and os.path.exists(files[0]):
        print(f"{file_type} [{filemask}] exist(s):", files)
        if delete_files == 'all':
            pass
        else:
            delete_files = input( "  Delete? (Enter 'yes'/'all' to delete) >> ")
            delete_files = delete_files.lower()

        if delete_files == 'yes' or delete_files == 'all':
            for f in files:
                if check_writable and not os.access(f, os.W_OK):
                    print(f"\nWarning in tkoptimize_flex.clean(): File [{f}] is not writable. Skip")
                    continue

                try:
                    os.remove(f)
                except:
                    pass

                if os.path.exists(f):
                    print(f"\nError in tkoptimize_flex.clean(): Could not delete [{f}]. Skip")
#                    app.terminate(f"\nError in tkoptimize_flex.clean(): Could not delete [{f}]", pause = True)
                print(f"  deleted [{f}]")

        else:
            print(f"  CAUTION: {file_type} [{filemask}] is/are not deleted.")
            if new_file_caution:
                print(f"           New file reflects the configuration therein.")

    return delete_files

def clean(app, cfg, mf):
    print("")
    print("Clean up. Will delete config and fitting parameter files:")
    fit = mf.init_fit(app, cfg)

    config_path   = cfg.parameterfile
    fitparam_path = fit.fitfile_path

    print()
    delete_files = ''
    delete_files = delete_files_yes_no(config_path,   file_type = 'Config ini file',        new_file_caution = True, delete_files = delete_files)
    delete_files = delete_files_yes_no(fitparam_path, file_type = 'Fitting parameter file', new_file_caution = True, delete_files = delete_files)

    delete_files = delete_files_yes_no('fit*.xlsx',     file_type = 'Fitting data files',   new_file_caution = False, delete_files = delete_files)
    delete_files = delete_files_yes_no(cfg.historyfile, file_type = 'Fitting history file', new_file_caution = False, delete_files = delete_files)

    delete_files = delete_files_yes_no('*.png',     file_type = 'Image files',      new_file_caution = False, delete_files = delete_files)
    delete_files = delete_files_yes_no('*.log',     file_type = 'log files',        new_file_caution = False, delete_files = delete_files)
    delete_files = delete_files_yes_no('*.out',     file_type = '.out files',       new_file_caution = False, delete_files = delete_files)
    delete_files = delete_files_yes_no('*.txt',     file_type = '.txt files',       new_file_caution = False, delete_files = delete_files)
    delete_files = delete_files_yes_no('nohup.out', file_type = 'nohup output log', new_file_caution = False, delete_files = delete_files)

    return fit

def kill(app, proc_name, user_name = ''):
    print(f"proc_name: {proc_name}")
    print(f"user_name: {user_name}")
    kill_process_interactive(proc_name, user_name)

def get_default_config_paths(app, cfg):
    arg_config_file = app.replace_path(None, template = ["{filebody}_arg_config.xlsx"])
    if not os.path.exists(arg_config_file):
        arg_config_file = app.replace_path(None, template = ["{dirname}", "{filebody}_arg_config.xlsx"])

    fit_config_file = app.replace_path(None, template = ["{filebody}_fit_config.xlsx"])
    if not os.path.exists(fit_config_file):
        fit_config_file = app.replace_path(None, template = ["{dirname}", "{filebody}_fit_config.xlsx"])

    return arg_config_file, fit_config_file

def read_default_config_file(app, infile):
    print()
    print(f"Read application configuration from [{infile}]")
    ret = app.read_arg_config_from_file(infile)
    if not ret: app.terminate(f"\nError in initialize(): Can not read [{infile}]")

    return True

def read_default_fit_file(app, infile):
    print()
    print(f"Read fitting configuration from [{infile}]")
    ret = read_fit_config_from_file(app, infile)
    if not ret: app.terminate(f"\nError in initialize(): Can not read [{infile}]")

    return True

def init(app, cfg, mf):
    print("")
    fit = clean(app, cfg, mf)

    app.arg_config_file, app.fit_config_file = get_default_config_paths(app, cfg)
    if os.path.exists(app.arg_config_file): read_default_config_file(app, app.arg_config_file)
    if os.path.exists(app.fit_config_file): read_default_fit_file(app, app.fit_config_file)

    fit = mf.init_fit(app, cfg)

    fit.save_parameter_files(cfg.parameterfile, fit.fitfile_path, cfg)

    app.terminate(pause = True)

def inf(app, cfg, mf):
    print()
    print("Information:")
    fit = mf.init_fit(app, cfg)
    fit.print_variables(heading = "Fitting parameters (inf):")

def convert(app, cfg, mf, infile = None, outfile = None):
    print("")
    print("Converte template file using fitting variables")

    if infile is None or infile == '':
        app.terminate("Error in tkoptimize_flex.convert(): input template file must be given by a variable input_template", pause = True)
    elif not os.path.exists(infile):
        app.terminate(f"Error in tkoptimize_flex.convert(): input template file [{infile}] does not exists", pause = True)
    if outfile is None or outfile == '':
        app.terminate("Error in tkoptimize_flex.convert(): output file must be given by a variable converted_file", pause = True)

    fit = mf.init_fit(app, cfg)

    replace_dict = {}
    for i, varname in enumerate(fit.varname):
        replace_dict[varname] = fit.pk[i]

    print()
    if os.path.exists(outfile):
        print(f"Delete [{outfile}]")
        os.remove(outfile)

    print(f"Make [{outfile}] from template [{infile}]")
    convert_file(infile, outfile, replace_dict, print_level = fit.print_level)

def lasso(app, cfg, mf, omodel = None, method = 'lasso'):
    print("")
    print(f"{method.upper()} analysis for linear part:")
    fit = mf.init_fit(app, cfg)

    xlabels, ylabels, xdata_list, ydata_list, _ = mf.read_input_data(app, cfg.infile, cfg = cfg)
    xlist = xdata_list[0]
    ylist = ydata_list[0]

    xlin_list, ylin_list, varname_lin, pk_lin, optid_lin, const_names, const_values \
            = omodel.build_llsq_params(xlist, ylist, fit.varname, fit.pk, fit.optid, fit.linid)

    print()
    print(f"Linear variables:")
    print("  varname           :", varname_lin)
    print("  optid             :", optid_lin)
    print("  fitting parameters:", pk_lin)
    print("  constants:")
    for name, vals in zip(const_names, const_values):
        vstr = format_strlist(vals, "{:10.4g}", separator = ' ')
        print(f"    {name:>10}: {vstr}")

    print()
    print("Build descriptors:")
    ci = 0
    labels = []
    labels_ai = []
    data_list = []
    for i, id in enumerate(optid_lin):
#        if not id: continue

        print(f"  varname: {varname_lin[i]}")
        y = []
        for ix, x in enumerate(xlist):
            val = omodel.lsqfunc(i, x, const_names, const_values)
            y.append(val)

        labels_ai.append(varname_lin[i])
        labels.append(f"y({varname_lin[i]})")
        data_list.append(y)

        ci += 1

    X_df = pd.DataFrame(np.array(data_list).T, columns = labels)
    print()
    print("Original descriptors:")
    print(X_df)

    scaler = StandardScaler()
    X_std = scaler.fit_transform(X_df)
    print()
    print("Standardized descriptors:")
    print(pd.DataFrame(X_std, columns = labels))

    Y_std = scaler.fit_transform(pd.DataFrame(np.array([ylin_list]).T, columns = ["y"]))
    print("Standardized objective function:")
    print(pd.DataFrame(Y_std, columns = ["muinv"]))

    alpha_min = 1.0e-5
    alpha_max = 1.0e1
    kalpha = 2.0
    alpha = alpha_min
    alpha_list = []
    mae_list = []
    coeff_list = []
    print()
    print(f"{method.upper()} regression:")
    while True:
        if alpha_max < alpha: break

        if method == 'lasso':
            model = Lasso(alpha = alpha, fit_intercept = False)
        elif method == 'ridge':
            model = Ridge(alpha = alpha, fit_intercept = False)
        elif method == 'linear':
            model = LinearRegression(fit_intercept = False)
        else:
            app.terminate(f"\nError in tkoptimize_flex.lasso(): Invalid method=[{method}].\n", pause = True)

        model.fit(X_std, Y_std)
        y_pred = model.predict(X_std)
        coeff = model.coef_

        mae = mean_absolute_error(Y_std, y_pred)
        if method == 'ridge':
            coeff = [v for v in coeff[0]]
        print(f"  alpha={alpha:10.4g}: MAE: {mae:8.4g} coeff:", format_strlist(coeff, "{:8.4g}"))
#        print("     predcit:", y_pred)

        alpha_list.append(alpha)
        mae_list.append(mae)
        coeff_list.append(coeff)

        alpha *= kalpha

# plot
    plt.figure(figsize=(6, 8))

    plt.subplot(2, 1, 1)
#    plt.title('MAE')
    plt.plot(alpha_list, mae_list, marker='o')
    plt.xscale('log')
    plt.xlabel('alpha')
    plt.ylabel('MAE')

    plt.subplot(2, 1, 2)
#    plt.title('Coefficients')
    coeff_list = np.array(coeff_list)
    for i in range(coeff_list.shape[1]):
        clist = [abs(v) for v in coeff_list[:, i]]
        plt.plot(alpha_list, clist, label = labels_ai[i])
    plt.xscale('log')
    plt.yscale('log')
    plt.xlabel('alpha')
    plt.ylabel('Coefficients')
    plt.legend(loc = 'best')

    plt.tight_layout()
    plt.pause(1.0e-5)

   
    app.terminate(pause = True)

def linear_fit(app, cfg, mf, omodel = None, fit = None, xlist = None, ylist = None, print_level = 1):
    if print_level:
        print()
        print("Linear least-squares fitting:")

    if fit is None: fit = mf.init_fit(app, cfg)

    if xlist is None:
        xlabels, ylabels, xdata_list, ydata_list, wdata_list = mf.read_input_data(app, cfg.infile, cfg = cfg)
        fit.x_labels = xlabels
        fit.y_labels = ylabels
        fit.x_list = xdata_list
        fit.y_list = ydata_list
        fit.w_list = wdata_list
        xlist = xdata_list[0]
        ylist = ydata_list[0]

#    if print_level and hasattr(fit, "linid"):
#        print(f"  Error in linear_fit(): fit object does not have 'linid' attribute.")
#        print(f"    Linear LSQ is skipped.")
#
#        fit.print_variables("vars:")

    xlin_list, ylin_list, varname_lin, pk_lin, optid_lin, const_names, const_values \
            = omodel.build_llsq_params(xlist, ylist, fit.varname, fit.pk, fit.optid, fit.linid)

    print()
    print(f"Linear variables:")
    print("  varname           :", varname_lin)
    print("  optid             :", optid_lin)
    print("  fitting parameters:", pk_lin)
    print("  constants:")
    for name, vals in zip(const_names, const_values):
        vstr = format_strlist(vals, "{:10.4g}", separator = ' ')
        print(f"    {name:>10}: {vstr}")

    ai_new, ai_all, Si, Sij = mlsq_general_optid(xlin_list, ylin_list, pk_lin, optid_lin, 
                                   lsqfunc = lambda idx, x: omodel.lsqfunc(idx, x, const_names, const_values),
                                   print_level = print_level)

    pk_all = omodel.recover_pk_all(fit.pk, ai_all)

# xk_all: Input fitting parameters
# pk_all: LLSQ results for all fitting parameters
# ai_all: LLSQ results for aop and polynomial parameters
# ai_new: LLSQ results for aop and polynomial parameters with linid == 1
# pi, ai, and ai_id include polynomial parts only
# ai_new includes aop and ai[] that have linid == 1
# ai_all includes aop and ai[]
    if print_level:
        print()
        print("linear fit results:")
        print("  varname        :", format_strlist(varname_lin, "{:>10}",   separator = ' '))
        print("  ai_new         :", format_strlist(ai_new,    "{:10.4g}", separator = ' '))
        print("  ai_all         :", format_strlist(ai_all,    "{:10.4g}", separator = ' '))
        print("  optid_lin      :", format_strlist(optid_lin, "{:10d}",   separator = ' '))
        print("all parameters:")
        print("  varname        :", format_strlist(fit.varname, "{:>10}",   separator = ' '))
        print("  fit.pk(org)    :", format_strlist(fit.pk,  "{:10.4g}", separator = ' '))
        print("  pk_all(new)    :", format_strlist(pk_all,  "{:10.4g}", separator = ' '))

    return pk_all

def linear_fit_func(app, cfg, mf, omodel = None, fit = None, xlist = None, ylist = None, print_level = 1):
    print("")
    print("Linear LSQ:")
    fit = mf.init_fit(app, cfg)

    pk_all = linear_fit(app, cfg, mf, omodel = omodel, fit = fit, xlist = xlist, ylist = ylist, print_level = print_level)
    fit.pk = pk_all

    print()
    print(f"Save parameters to [{cfg.parameterfile}]")
    mf.save_parameter_files(cfg.parameterfile, fit.fitfile_path, fit, cfg)

    '''
    fit.minimize_func(fit.pk, run = True)
    fit.yini_list = fit.yc_list
    '''
    
    fit.pk = fit.correct_pk_range(fit.pk, print_level = print_level)
    fit.yini_list = fit.cal_ylist(fit.pk, fit.x_list, print_level = print_level)

    print_data(labels = fit.x_labels + fit.y_labels + ['ini'], 
            data_list = fit.x_list + fit.y_list + fit.yini_list,
            label_format = '{:^15}', data_format = '{:>15.4g}', header = "Simulated data:", nmax = 20, print_level = 0)

    if fit.fplot >= 1: 
        plot_sim(fit.x_labels, fit.y_labels, fit.x_list, fit.y_list, fit.yini_list, cfg)

    app.terminate(pause = True)

def plot(app, cfg, mf):
    global msizes

    filetype= None
    if cfg.file1 == '':
        if os.path.isfile(cfg.fitfile):
            filetype = 'fitfile'
            cfg.file1 = cfg.fitfile
        elif os.path.isfile(cfg.infile):
            filetype = 'inputfile'
            cfg.file1 = cfg.outfile
    elif cfg.file1 == 'history':
        filetype = 'history'
        cfg.file1 = cfg.historyfile
    elif cfg.file1 == 'input':
        filetype = 'inputfile'
        cfg.file1 = cfg.outfile
    elif cfg.file1 == 'fit':
        filetype = 'fitfile'
        cfg.file1 = cfg.fitfile
    else:
        print()
        app.terminate(f"Error in plot(): Invalid plot_type [{cfg.file1}]")

    if filetype is None:
        print()
        app.terminate(f"Error in plot(): Positional argument must be given for plot_type for mode=plot", pause = True)

    print("")
    print("Plot")
    print(f"file1   : {cfg.file1}")
    print(f"filetype: {filetype}")

    fit = tkFit_mxy(app = app)
    print()
    fit.x_labels, fit.y_labels, fit.xd_list, fit.yd_list, fit.w_list = mf.read_input_data(app, cfg.infile, cfg)

    fit.labels = fit.x_labels + fit.y_labels
    fit.x_list = fit.xd_list
    fit.y_list = fit.yd_list

    if filetype == 'history':
        fit.labels = fit.labels[0:2]
        fit.y_list = [fit.y_list[0]]

    tkplt, root, tkpyplot = select_plt(use_tkinter = cfg.use_tkplt, plt = plt, parent = None, 
                title = "Plot data")
    fig, axes = tkplt.subplots(1, 1, figsize = cfg.figsize, dpi = 100, tight_layout = True)
#    fig, axes = plt.subplots(1, 1, figsize = cfg.figsize)
    if cfg.use_tkplt:
        tkplt.add_toolbar()
        canvas = tkplt.add_canvas(fig)

    plot_event = tkPlotEvent(plt)
    plot_event.prepare_annotation()

    ax = axes
    ax.tick_params(labelsize = cfg.fontsize)
    if filetype == 'history':
        msizes = [0.3]

    for i in range(len(fit.y_list)):
        width = widths[i % len(widths)]
        marker = markers[i % len(markers)]
        msize  = msizes[i % len(msizes)]
        label = fit.labels[i+1]
        line, = ax.plot(fit.x_list[0], fit.y_list[i], label = label, linewidth = width, marker = marker, markersize = msize)

        format = "{label}\n data#{idata}:\n T={T:8.4f}\n N={N:10.4g}\n mu={mu:10.4g}"
        plot_event.annotation.add_line(label, ax, ax, fit.x_list[0], fit.y_list[i], line, 
                    inf_list = {"T": fit.x_list[0], "N": fit.x_list[1], "mu": fit.y_list[0]},
                    annotation_format = format, inf_format = format)

    plot_event.register_annotation_event(fig, activate = True, print_level = 0)

    if filetype == 'history':
        x_label = 'iteration'
        y_label = 'error'
        ax.set_yscale('log')
    else:
        x_label = fit.x_labels[0]
        y_label = fit.y_labels[0]

    ax.set_xlabel(x_label, fontsize = cfg.fontsize)
    ax.set_ylabel(y_label, fontsize = cfg.fontsize)
    ax.legend(fontsize = cfg.legend_fontsize)

    if fit.fplot >= 1: tkplt.pause(0.1)
    app.terminate(pause = True)

def sim(app, cfg, mf):
    print()
    print("Simulation")
    fit = mf.init_fit(app, cfg)

    print()
    if not os.path.exists(cfg.infile):
        app.terminate(f"Error in tkoptimize_flex.sim(): Input data file [{cfg.infile}] does not exist.", pause = True)

    print(f"Read input data from [{cfg.infile}]")
    fit.x_labels, fit.y_labels, fit.xd_list, fit.yd_list, fit.w_list = mf.read_input_data(app, cfg.infile, cfg)
    if fit.x_labels is None:
        app.terminate(f"\nError in tkoptimize_flx.sim(): Can not read input data from [{cfg.infile}]", pause = True)

    fit.labels = fit.x_labels + fit.y_labels
    fit.x_list = fit.xd_list
    fit.y_list = fit.yd_list
    print()
    print(f"Save input data repeat to [{fit.outfile}]")
    save_data(fit.outfile, fit.labels, fit.x_list + fit.y_list, template = cfg.get("xlsm_template", None))
    
    print()
    fit.print_variables(heading = "Parameters:")

    fit.open_history()
    optpk = fit.extract_parameters()
    fit.minimize_func(optpk, run = True)
#    fit.close_history()
    fit.yini_list = fit.yc_list

    print_data(labels = fit.labels + ['ini'], 
            data_list = fit.x_list + fit.y_list + fit.yini_list,
            label_format = '{:^15}', data_format = '{:>15.4g}', header = "Simulated data:", nmax = 20, print_level = 0)

    print(f"Save input and initial data to [{fit.fitfile}]")
    save_data(fit.fitfile, [fit.x_labels[0], 'input', 'initial'], [fit.x_list[0], fit.y_list[0], fit.yini_list[0]],
                template = cfg.get("xlsm_template", None))

    if fit.fplot >= 1: 
        plot_sim(fit.x_labels, fit.y_labels, fit.x_list, fit.y_list, fit.yini_list, cfg)

    app.terminate(pause = True)

def plot_sim(x_labels, y_labels, x_list, y_list, ysim_list, cfg = None):
    print("plot")
    tkplt, root, tkpyplot = select_plt(use_tkinter = cfg.use_tkplt, plt = plt, parent = None, title = "Simulation")
    fig, axes = tkplt.subplots(1, 1, figsize = cfg.figsize, dpi = 100, tight_layout = True)
#    fig, axes = plt.subplots(1, 1, figsize = cfg.figsize)
    if cfg.use_tkplt:
        tkplt.add_toolbar()
        canvas = tkplt.add_canvas(fig)

#        plot_event = tkPlotEvent(plt)
#        plot_event.prepare_annotation()

    ax = axes
    ax.tick_params(labelsize = cfg.fontsize)
    line_obs, = ax.plot(x_list[0], y_list[0],    label = 'input',  linestyle = '', marker = 'o', markersize = 1.5)
    line_sim, = ax.plot(x_list[0], ysim_list[0], label = 'simulated', linewidth = 0.5)
    ax.set_xlabel(x_labels[0], fontsize = cfg.fontsize)
    ax.set_ylabel(y_labels[0], fontsize = cfg.fontsize)
    ax.legend(fontsize = cfg.legend_fontsize)

    format = "{label}\n data#{idata}:\n T={T:8.4f}\n N={N:10.4g}\n mu={mu:10.4g}"
#        plot_event.annotation.add_line("input", ax, ax, fit.x_list[0], fit.y_list[0], line_obs, 
#                    inf_list = {"T": fit.x_list[0], "N": fit.x_list[1], "mu": fit.y_list[0]},
#                    annotation_format = format, inf_format = format)

#        plot_event.annotation.add_line("sim", ax, ax, fit.x_list[0], fit.yini_list[0], line_sim, 
#                    inf_list = {"T": fit.x_list[0], "N": fit.x_list[1], "mu": fit.yini_list[0]},
#                    annotation_format = format, inf_format = format)

#        plot_event.register_annotation_event(fig, activate = True, print_level = 0)

    tkplt.pause(0.1)

def fit1_one(app, cfg, mf, fit, target_var, itarget, xk = None, fmin = None, title = None, pause = True, print_level = 1, tkplt = None, outlog = None):
    if outlog:
        outlog.write(f"  fit1_one: itarget={itarget} var={fit.varname[itarget]}: fmin={fmin_min:g}\n")

    if fit.optid[itarget] != 1:
        app.terminate(f"\nError in tkoptimize_flx.fit1_one(): Variable #{itarget} [{fit.varname[itarget]}] is chosen "
                    + f" but it is not a fitting variable [optid={fit.optid[itarget]}]\n", pause = True)

    if type(cfg.x0) is str:
        if cfg.x0 == '':
            cfg.x0 = fit.kmin[itarget]
        else:
            cfg.x0 = pfloat(cfg.x0, cfg.x0)
    if type(cfg.x1) is str:
        if cfg.x1 == '':
            cfg.x1 = fit.kmax[itarget]
        else:
            cfg.x1 = pfloat(cfg.x1, cfg.x1)

    if outlog:
        outlog.write(f"  Optimize var #{itarget} {cfg.target_var}: x range: {cfg.x0} - {cfg.x1}\n")
    
    if print_level:
        print(f"Optimize var #{itarget} {cfg.target_var}: x range: {cfg.x0} - {cfg.x1}")

    if type(cfg.x0) is str:
        if cfg.x0 == '':
            cfg.x0 = fit.kmin[itarget]
        else:
            cfg.x0 = pfloat(cfg.x0, cfg.x0)
    if type(cfg.x1) is str:
        if cfg.x1 == '':
            cfg.x1 = fit.kmax[itarget]
        else:
            cfg.x1 = pfloat(cfg.x1, cfg.x1)

    dx = cfg.get("dx", fit.dx[itarget])
    if type(dx) is str and dx == '': dx = fit.dx[itarget]

    if type(cfg.ix_plot) is int:
        print(f"ix_plot: {cfg.ix_plot} {fit.x_labels[cfg.ix_plot]}")

#========================================
# Optimize
#========================================
    print("")
    print("Optimize:")

    optid_original = fit.optid.copy()
    fit.fplot = 0
    fit.plot_interval = 1

    for idx, id in enumerate(optid_original):
        if idx == itarget:
            fit.optid[idx] = 1
        else:
            fit.optid[idx] = 0

    if fit.method == 'nelder-mead' or fit.method == 'simplex': fit.nmaxiter = 10

    print("  optid=", fit.optid)
    print("  method=", fit.method)
    print("  nmaxiter=", fit.nmaxiter)
    print("  nmaxcall=", fit.nmaxcall)
    print("  itarget=", itarget, fit.varname[itarget])
    print(f"  range: {cfg.x0} - {cfg.x1}")
    print(f"  dx: {dx}")
    print(f"  plot_interval: {fit.plot_interval}")

    fit.initial_simplex = None
    if 'fit' in cfg.mode and (cfg.method == 'nelder-mead' or cfg.method == 'simplex'):
        if fit.dx:
            fit.initial_simplex = fit.build_initial_simplex()
            for i, splx in enumerate(fit.initial_simplex):
                print(f"  {i}: ", end = '')
                for v in splx:
                    print(f" {v:12.4g}", end = '')
                print()
    
    fit.print_variables(heading = "Variables")
    pfin, ffin, success, res = fit.minimize(cfg.method, jac = fit.jac, initial_simplex = fit.initial_simplex)
#    fit.close_history()
    fit.pk = fit.pk_corrected

    optpk = fit.extract_parameters(fit.pk)
    fit.ffin = fit.minimize_func(optpk, run = cfg.run)
    fit.yfin_list = fit.yc_list.copy()
    fit.last_iter = res.nit

    if success:
        print(f"\nConverged at iteration: {res.nit}\n")
    else:
        print(f"\nFunction did not converge\n")

    fit.optid = optid_original

#========================================
# Final result
#========================================
    fit.retrieve_parameter_list(pfin, fit.varname, target = fit.cfg)
    fit.print_variables(heading = "Final parameters:", fmin = fit.ffin)

    print("")
    fit.save_parameter_files(cfg.parameterfile, fit.fitfile_path, cfg)

    labels = fit.x_labels + fit.y_labels + ["initial", "final"]
    data_list = fit.x_list + fit.y_list + [fit.yini_list[0], fit.yfin_list[0]]
    print("")
    print_data(labels = labels, data_list = data_list,
               label_format = '{:^15}', data_format = '{:>15.4g}', header = "Final data:", nmax = 20, print_level = 0)
    print(f"Save input, initial, and final data to [{cfg.fitfile}]")
    save_data(cfg.fitfile, labels = labels, data_list = data_list, template = cfg.get("xlsm_template", None))

#=============================
# グラフの表示
#=============================
    if fit.fplot >= 1:
        print("")
        print("Plot optimized")

    fit.finalize_plot(fit.yfin_list, iter = fit.last_iter, fmin = fit.ffin, fplot = fit.fplot, savefig_path = 'final_fit.png')

    fit.layout()
#   tkplt.tight_layout()
    tkplt.subplots_adjust(top = fit.plot_region[0], bottom = fit.plot_region[1])

    if fit.fplot >= 1:
        tkplt.draw()
        tkplt.pause(0.1)
#    input(f"next {itarget}>>")
    print(f"itarget={itarget} finished. sleep 1 sec")
    time.sleep(1)

    return True

def fit1(app, cfg, mf, print_level = True):
    print("")
    print("Optimize one parameter:")
    fit = mf.init_fit(app, cfg)

    print(f"Read input data from [{cfg.infile}]")
    fit.x_labels, fit.y_labels, fit.xd_list, fit.yd_list, fit.w_list = mf.read_input_data(app, cfg.infile, cfg)

    fit.labels = fit.x_labels + fit.y_labels
    fit.x_list = fit.xd_list
    fit.y_list = fit.yd_list

    print(f"Save input data to [{cfg.outfile}]")
    save_data(cfg.outfile, fit.labels, fit.x_list + fit.y_list, template = cfg.get("xlsm_template", None))
#    print()
#    fit.print_variables(heading = "Fitting parameters:")

    print()
    print(f"Calculate initial function")
    fit.open_history()
    optpk = fit.extract_parameters()
    fini = fit.minimize_func(optpk, run = cfg.run)

    fit.yini_list = fit.yc_list

#========================================
# plot
#========================================
    use_tkplt = cfg.get('use_tkplt', False)
    if not fit.fplot >= 1: use_tkplt = 0

    tkplt, root, tkpyplot = select_plt(use_tkinter = use_tkplt, plt = plt, parent = None, title = "fit")
    fig, axes = tkplt.subplots(len(fit.y_list), 2, figsize = cfg.figsize, dpi = 100, tight_layout = True)

    if use_tkplt:
            tkplt.create_window(fig)
            tkplt.add_toolbar()
            canvas = tkplt.add_canvas(fig)

    fit.initial_plot(data_axes = [axes[0], axes[1]], error_axis = axes[1], yini_list = fit.yini_list, label_ini = 'initial',
                    ix_plot = cfg.ix_plot, x_scale = cfg.x_scale, fmin = fini, 
                    fplot = fit.fplot, plt = tkplt, fig = fig,
                    fontsize = cfg.fontsize, legend_fontsize = cfg.legend_fontsize, savefig_path = 'initial.png')

    if hasattr(fit, "plot_event"): fit.plot_event.remove('error')

    tkplt.tight_layout()
    tkplt.subplots_adjust(top = fit.plot_region[0], bottom = fit.plot_region[1])
    if fit.fplot >= 1: tkplt.pause(0.1)

    if type(cfg.target_var) is str and cfg.target_var.lower() == 'all':
        itarget = None
    else:
        itarget, cfg.target_var = index2val(cfg.target_var, fit.varname, (None, None))

    if (cfg.target_var is None or cfg.target_var == '') and cfg.target_var.lower() != 'all':
        print(f"Error in tkoptimize_flex.fit1(): target var [{cfg.target_var}] is not in the varname list ", fit.varname)
        app.terminate(pause = True)

    if cfg.target_var.lower() == 'all':
        optid = fit.optid.copy()
        for _itarget, id in enumerate(optid):
#            print("optid=", optid)
#            print("itarget=", _itarget, id)
            if not id: continue

            fit1_one(app, cfg, mf, fit, cfg.target_var, _itarget, tkplt = tkplt, print_level = 0)
    else:
        fit1_one(app, cfg, mf, fit, cfg.target_var, itarget, tkplt = tkplt, print_level = 0)

    app.terminate(pause = True)

def fit_simple(app, cfg, mf, optid_mode = 'optid', print_level = True):
    print("")
    print("Non-linear LSQ fitting without graph:")
    fit = mf.init_fit(app, cfg, print_level = False)
    print(f"Parameter file: [{cfg.parameterfile}]")
    print(f"Fitting parameter file: [{fit.fitfile_path}]")

    print()
    print(f"Read input data from [{cfg.infile}]")
    fit.x_labels, fit.y_labels, fit.xd_list, fit.yd_list, fit.w_list = mf.read_input_data(app, cfg.infile, cfg)
    if fit.x_labels is None:
        app.terminate(f"\nError in fit(): Can not read [{cfg.infile}]", pause = True)

    fit.x_list = fit.xd_list
    fit.y_list = fit.yd_list
    if 'l' in optid_mode:
        print()
        print(f"linear optid_mode={optid_mode} is specified.")
        linid = fit.get("linid", None)
        if linid is None or len(linid) == 0:
            app.terminate(f"  but linid is not obtained. terminate...", pause = True)

        print(f"   use linid instead of optid")
        print()
        optid_original = fit.optid
        fit.optid = fit.linid

    print(f"Save input data repeat to [{cfg.outfile}]")
    save_data(cfg.outfile, fit.x_labels + fit.y_labels, fit.xd_list + fit.yd_list, 
                template = cfg.get("xlsm_template", None))

    print()
    fit.print_variables(heading = "Fitting parameters:")

    print()
    if type(cfg.ix_plot) is int:
        print(f"ix_plot: {cfg.ix_plot} {fit.x_labels[cfg.ix_plot]}")

    print()
    print(f"Calculate initial function")
    fit.open_history()
    optpk = fit.extract_parameters()
    fini = fit.minimize_func(optpk, run = cfg.run)
    if not hasattr(fit, "xdiff"):
        app.terminate(f"\nError in fit(): Could not calculate initial values. Check parameter values or solver.\n", pause = True)
        
    xdiffini = fit.xdiff

    fit.yini_list = fit.yc_list

    labels = fit.x_labels + fit.y_labels + ["initial"]
    data_list = fit.x_list + fit.y_list + [fit.yini_list[0]]
    print(f"Initial function: fmin={fini:10.4g}")
    print_data(labels = labels, data_list = data_list,
            label_format = '{:^15}', data_format = '{:>15.4g}', header = "Initial data:", nmax = 20, print_level = 0)
    print("fini=", fini)

    print(f"Save input and initial data to [{cfg.outfile}]")
    save_data(cfg.outfile, labels = labels, data_list = data_list, template = cfg.get("xlsm_template", None))

#========================================
# Optimize
#========================================
    print("")
    print("Optimize:")
    fit.print_variables(heading = "Variables")
    pfin, ffin, success, res = fit.minimize(cfg.method, jac = fit.jac, initial_simplex = fit.initial_simplex)
#    ret = fit.close_history()
#    if not ret:
#        print(f"\nError in tkoptimize_flex.fit(): Could not save hisotry file [{fit.historyfile}]\n")
        
    fit.pk = fit.pk_corrected

    optpk = fit.extract_parameters(fit.pk)
    ffin = fit.minimize_func(optpk, run = cfg.run)
    if fit.yc_list is None:
        print(f"\nWarning in tkoptimize_flex.fit(): Could not obtain fit.yc_list.")
        print(f"   Try to clear cache and recalculate\n")
        fit.clear_cache = True
        ffin = fit.minimize_func(optpk, run = cfg.run)
    yfin_list = fit.yc_list

    if success:
        print(f"\nConverged at iteration: {res.nit}\n")
    else:
        print(f"\nFunction did not converge\n")

#========================================
# Final result
#========================================
    fit.retrieve_parameter_list(pfin, fit.varname, target = fit.cfg)
    fit.print_variables(heading = "Final parameters:", fmin = ffin)

    if 'l' in optid_mode:
        print()
        print(f"linear optid_mode={optid_mode}: original optid is restored")
        print()
        fit.optid = optid_original

    print("")
    fit.save_parameter_files(cfg.parameterfile, fit.fitfile_path, cfg)

    if yfin_list is None:
        labels = fit.x_labels + fit.y_labels + ["initial"]
        data_list = fit.x_list + fit.y_list + [fit.yini_list[0]]
    else:
        labels = fit.x_labels + fit.y_labels + ["initial", "final"]
        data_list = fit.x_list + fit.y_list + [fit.yini_list[0], yfin_list[0]]
    print("")
    print_data(labels = labels, data_list = data_list,
               label_format = '{:^15}', data_format = '{:>15.4g}', header = "Final data:", nmax = 20, print_level = 0)
    print(f"Save input, initial, and final data to [{cfg.fitfile}]")
    save_data(cfg.fitfile, labels = labels, data_list = data_list, template = cfg.get("xlsm_template", None))

    if yfin_list is None:
        print(f"\nError: yfin_list was not obtained\n")
        app.terminate(f"    yfin_list was not added to [{cfg.fitfile}].", pause = True)

def fit(app, cfg, mf, optid_mode = 'optid', print_level = True):
    print("")
    print("Non-linear LSQ fitting:")
    fit = mf.init_fit(app, cfg, print_level = True)
    print(f"Parameter file: [{cfg.parameterfile}]")
    print(f"Fitting parameter file: [{fit.fitfile_path}]")

    print()
    print(f"Read input data from [{cfg.infile}]")
    fit.x_labels, fit.y_labels, fit.xd_list, fit.yd_list, fit.w_list = mf.read_input_data(app, cfg.infile, cfg)
    if fit.x_labels is None:
        app.terminate(f"\nError in fit(): Can not read [{cfg.infile}]", pause = True)
    elif len(fit.x_labels) == 0:
        fit.is_fitting = False

    fit.x_list = fit.xd_list
    fit.y_list = fit.yd_list
    if 'l' in optid_mode:
        print()
        print(f"linear optid_mode={optid_mode} is specified.")
        linid = fit.get("linid", None)
        if linid is None or len(linid) == 0:
            app.terminate(f"  but linid is not obtained. terminate...", pause = True)

        print(f"   use linid instead of optid")
        print()
        optid_original = fit.optid
        fit.optid = fit.linid

    if fit.is_fitting:
        print(f"Save input data repeat to [{cfg.outfile}]")
        save_data(cfg.outfile, fit.x_labels + fit.y_labels, fit.xd_list + fit.yd_list, template = cfg.get("xlsm_template", None))

    print()
    fit.print_variables(heading = "Fitting parameters:")

    print()
    if type(cfg.ix_plot) is int:
        print(f"ix_plot: {cfg.ix_plot} {fit.x_labels[cfg.ix_plot]}")

    print()
    print(f"Calculate initial function")
    fit.open_history()
    optpk = fit.extract_parameters()
    fini = fit.minimize_func(optpk, run = cfg.run)

    if fit.is_fitting:
        if not hasattr(fit, "xdiff"):
            app.terminate(f"\nError in fit(): Could not calculate initial values. Check parameter values or solver.\n", pause = True)

        xdiffini = fit.xdiff

        fit.yini_list = fit.yc_list

        labels = fit.x_labels + fit.y_labels + ["initial"]
        data_list = fit.x_list + fit.y_list + [fit.yini_list[0]]
        print(f"Initial function: fmin={fini:10.4g}")
        print_data(labels = labels, data_list = data_list,
                label_format = '{:^15}', data_format = '{:>15.4g}', header = "Initial data:", nmax = 20, print_level = 0)
        print("fini=", fini)

        print(f"Save input and initial data to [{cfg.outfile}]")
        save_data(cfg.outfile, labels = labels, data_list = data_list, template = cfg.get("xlsm_template", None))
    else:
        fit.xdiff = None
        xdiffini = fit.xdiff
        fit.yini_list = None
    
#========================================
# plot
#========================================
    use_tkplt = cfg.get('use_tkplt', False)
    plot_std = False
    if "so" in cfg.method and ("gp" in cfg.method or "physbo" in cfg.method):
        plot_std = True 
    use_so = False
    if "so" in cfg.method: use_so = True

    print()
    print("Initial plot")
    print(f"  method: {cfg.method}")
    print(f"  plot_std: {plot_std}")

    if not fit.fplot >= 1: use_tkplt = 0

    if fit.is_fitting:
        if plot_std:
            ngraph = 3
        else:
            ngraph = 2
    else:
        if plot_std:
            ngraph = 2
        else:
            ngraph = 1

    if ngraph >= 3: 
        figsize = [int(cfg.figsize[0] * ngraph / 2.0), cfg.figsize[1]]
    else:
        figsize = cfg.figsize

    tkplt, root, tkpyplot = select_plt(use_tkinter = use_tkplt, plt = plt, parent = None, title = "fit")
    if fit.is_fitting:
        fig, axes = tkplt.subplots(len(fit.y_list), ngraph, figsize = figsize, dpi = 100, tight_layout = True)
        error_axis = axes[1]
        ax2 = error_axis.twinx()
        if plot_std:
            std_axis = axes[2]
        else:
            std_axis = None
    else:
        fig, axes = tkplt.subplots(1, ngraph, figsize = figsize, dpi = 100, tight_layout = True)
        if ngraph == 1:
            error_axis = axes
        else:
            error_axis = axes[0]
        ax2 = error_axis.twinx()
        if plot_std:
            std_axis = axes[1]
        else:
            std_axis = None

    if use_tkplt:
           tkplt.create_window(fig)
           tkplt.add_toolbar()
           canvas = tkplt.add_canvas(fig)

    if fit.is_fitting:
        fit.initial_plot(data_axes = [axes[0], axes[1]], error_axis = error_axis, xdiff_axis = ax2, std_axis = std_axis,
                    use_so = use_so,
                    yini_list = fit.yini_list, label_ini = 'initial',
                    ix_plot = cfg.ix_plot, x_scale = cfg.x_scale, 
                    label_error = 'error', label_xdiff = 'xdiff',
                    fmin = fini, xdiff = xdiffini,
                    fplot = fit.fplot, plt = tkplt, fig = fig,
                    fontsize = cfg.fontsize, legend_fontsize = cfg.legend_fontsize, 
                    savefig_path = 'initial.png')
    else:
        fit.initial_plot(data_axes = [], error_axis = error_axis, xdiff_axis = ax2, std_axis = std_axis,
                    use_so = use_so,
                    yini_list = None, label_ini = None,
                    ix_plot = None, x_scale = None, 
                    label_error = 'error', label_xdiff = 'xdiff',
                    fmin = fini, xdiff = xdiffini,
                    fplot = fit.fplot, plt = tkplt, fig = fig,
                    fontsize = cfg.fontsize, legend_fontsize = cfg.legend_fontsize, 
                    savefig_path = 'initial.png')
    
    if fit.fplot >= 0:
        fit.plot_event.remove('error')
        tkplt.tight_layout()
        tkplt.subplots_adjust(top = fit.plot_region[0], bottom = fit.plot_region[1])

    if fit.fplot >= 1: tkplt.pause(0.01)

#========================================
# Optimize
#========================================
    print("")
    print("Optimize:")
    fit.print_variables(heading = "Variables")
    pfin, ffin, success, res = fit.minimize(cfg.method, jac = fit.jac, initial_simplex = fit.initial_simplex)
#    ret = fit.close_history()
#    if not ret:
#        print(f"\nError in tkoptimize_flex.fit(): Could not save hisotry file [{fit.historyfile}]\n")

    if fit.pk_corrected is not None:
        fit.pk = fit.pk_corrected
    else:
        fit.pk = fit.finalpk

    optpk = fit.extract_parameters(fit.pk)
    ffin = fit.minimize_func(optpk, run = cfg.run)

    if fit.is_fitting:
        if fit.yc_list is None:
            print(f"\nWarning in tkoptimize_flex.fit(): Could not obtain fit.yc_list.")
            print(f"   Try to clear cache and recalculate\n")
            fit.clear_cache = True
            ffin = fit.minimize_func(optpk, run = cfg.run)

        yfin_list = fit.yc_list
    else:
        yfin_list = None

    if success:
        print(f"\nConverged at iteration: {res.nit}\n")
    else:
        print(f"\nFunction did not converge\n")

#========================================
# Final result
#========================================
    fit.retrieve_parameter_list(pfin, fit.varname, target = fit.cfg)
    fit.print_variables(heading = "Final parameters:", fmin = ffin)

    if 'l' in optid_mode:
        print()
        print(f"linear optid_mode={optid_mode}: original optid is restored")
        print()
        fit.optid = optid_original

    print("")
    fit.save_parameter_files(cfg.parameterfile, fit.fitfile_path, cfg)

    if fit.is_fitting:
        if yfin_list is None:
            labels = fit.x_labels + fit.y_labels + ["initial"]
            data_list = fit.x_list + fit.y_list + [fit.yini_list[0]]
        else:
            labels = fit.x_labels + fit.y_labels + ["initial", "final"]
            data_list = fit.x_list + fit.y_list + [fit.yini_list[0], yfin_list[0]]

        print("")
        print_data(labels = labels, data_list = data_list,
                   label_format = '{:^15}', data_format = '{:>15.4g}', header = "Final data:", nmax = 20, print_level = 0)

        print(f"Save input, initial, and final data to [{cfg.fitfile}]")
        save_data(cfg.fitfile, labels = labels, data_list = data_list, template = cfg.get("xlsm_template", None))

        if yfin_list is None:
            print(f"\nError: yfin_list was not obtained\n")
            app.terminate(f"    yfin_list was not added to [{cfg.fitfile}].", pause = True)


#=============================
# グラフの表示
#=============================
    if fit.fplot >= 0:
        if fit.fplot >= 1:
            print("")
            print("Plot optimized")

        fit.finalize_plot(yfin_list, iter = res.nit, fmin = ffin, fplot = fit.fplot, savefig_path = 'final_fit.png')

        fit.layout()
        tkplt.subplots_adjust(top = fit.plot_region[0], bottom = fit.plot_region[1])

        if fit.fplot >= 1: tkplt.pause(0.1)

    print(f"\n*** fit.last_message: {fit.last_message}\n")

    app.terminate(pause = True)

def mapping(app, cfg, mf):
    eps = 1.0e-5
    plot_fmax = 1.0e3

    print()
    print("Map sampling:")
    fit = mf.init_fit(app, cfg)

    print(f"Read input data from [{cfg.infile}]")
    fit.x_labels, fit.y_labels, fit.xd_list, fit.yd_list, fit.w_list = mf.read_input_data(app, cfg.infile, cfg)

    fit.labels = fit.x_labels + fit.y_labels
    fit.x_list = fit.xd_list
    fit.y_list = fit.yd_list

    print(f"Save input data to [{cfg.outfile}]")
    save_data(cfg.outfile, fit.labels, fit.x_list + fit.y_list, template = cfg.get("xlsm_template", None))

    if type(cfg.target_var) is int:
        vars = [str(cfg.target_var)]
    else:
        vars = cfg.target_var.split(',')
    if len(vars) == 0 or (len(vars) == 1 and vars[0] == ''):
        vars = [0, 1]
    elif len(vars) == 1:
        itarget, varname = index2val(vars[0], fit.varname, (None, None))
        if itarget == 1:
            vars = [0, 1]
        else:
            vars = [vars[0], 1]

#    print("vars=", vars)
    itarget_list = []
    var_list = []
    for i, v in enumerate(vars):
        itarget, vars[i] = index2val(v, fit.varname, (None, None))
        itarget_list.append(itarget)
        var_list.append(vars[i])

    print()
    print(f"Target parameters:", *vars)
    itarget0 = itarget_list[0]
    var0     = var_list[0]
    xrange_list0 = [fit.pk[itarget0]] + fit.get_xrange_list(itarget0, cfg.nx, print_level = 1)

    outlog = None
    if outlog: outlog.write('Started\n')

    nx = len(xrange_list0)
    fmin = None
    xk = fit.pk.copy()
    for ix, x in enumerate(xrange_list0):
        xk[itarget0] = x
        cfg.target_var = var_list[1]
        itarget = itarget_list[1]
        cfg.x0 = ''
        cfg.x1 = ''
        
        if outlog:
            outlog.write(f"ix={ix+1}/{nx}: {var_list[0]}={x}\n") 
            xkstr = 'xk='
            for x in xk:
                xkstr += f"{x:g} "
            outlog.write(xkstr + "\n")

        fmin = scan_one(app, cfg, mf, fit, cfg.target_var, itarget, xk = xk, fmin = fmin, title = f"{var_list[0]}={x:g}", 
                        pause = False, print_level = 0, outlog = outlog)
        if fmin is False:
            break

        xk = fit.pk.copy()

        if outlog:
            outlog.write(f"  Minimum at ix_min={fit.ix_fmin} with {cfg.target_var}={fit.x_min:g}: fmin={fit.fmin:g}")
            xkstr = '    xk='
            for x in xk:
                xkstr += f"{x:g} "
            outlog.write(xkstr)      
    if outlog:
        outlog.write('Finished\n') 
        outlog.close()

    app.terminate(pause = True)

def sampling(app, cfg, mf):
    eps = 1.0e-5
    plot_fmax = 1.0e3
    
    print()
    print("Random sampling:")
    fit = mf.init_fit(app, cfg)

    print(f"Read input data from [{cfg.infile}]")
    fit.x_labels, fit.y_labels, fit.xd_list, fit.yd_list, fit.w_list = mf.read_input_data(app, cfg.infile, cfg)

    print(f"Save input data to [{cfg.outfile}]")
    save_data(cfg.outfile, fit.x_labels + fit.y_labels, fit.xd_list + fit.yd_list, template = cfg.get("xlsm_template", None))

    fit.x_list = fit.xd_list
    fit.y_list = fit.yd_list

    plot_ymin = min(fit.y_list[0])
    plot_ymax = max(fit.y_list[0])

    print()
    fit.print_variables(heading = "Fitting parameters:")

    print()
    print(f"# of sampling: {cfg.nsample}")

    fit.open_history()
    optpk = fit.extract_parameters()
    fmin_min = fit.minimize_func(optpk, run = True)
    i_fmin = 0
    xk_fmin = fit.pk.copy()
    plot_fmax = min([plot_fmax, fmin_min * 100.0])
    print("fmin_min=", fmin_min)

    def inf_format_func(plotevent, sel, iline, idata, label, inf, x = 0, y = 0, opt = None):
        if inf is None:
            return

        iteration = inf["iteration"][idata]
        fmin = inf["fmin"][idata]
        xk   = inf["xk"][idata]
        fit  = inf["fit"]
        print(f"iteration={iteration}  fmin={fmin:10.4g}:")
        for varname, x in zip(fit.varname, xk):
            print(f"  {varname:10}: {x:g}")

    xk_history = [fit.pk]
    x_list = [i_fmin]
    fmin_list = [fmin_min]
    ysim_all_list = []
    if fit.fplot >= 1:
        print()
        print("plot")
        tkplt, root, tkpyplot = select_plt(use_tkinter = cfg.use_tkplt, plt = plt, parent = None, 
                title = "Random sampling")

        fig, axes = tkplt.subplots(1, 2, figsize = cfg.figsize, dpi = 100, tight_layout = True)

        plot_event = tkPlotEvent(plt)
        plot_event.add_stop_button()
        plot_event.prepare_annotation()
        plot_event.register_annotation_event(fig, activate = True, print_level = 0)

        axfmin = axes[0]
        axFit  = axes[1]

        axfmin.tick_params(labelsize = cfg.fontsize)
        line_fmin, = axfmin.plot(x_list, fmin_list, label = 'fmin', marker = 'o')
        axfmin.set_xlabel('sampling index', fontsize = cfg.fontsize)
        axfmin.set_ylabel('fmin', fontsize = cfg.fontsize)
        axfmin.set_yscale('log')
        axfmin.legend(fontsize = cfg.legend_fontsize)
        
        axFit.tick_params(labelsize = cfg.fontsize)
        index_list = range(len(fit.y_list[0]))
        line_fit, = axFit.plot(index_list, fit.y_list[0], label = 'input', linestyle = '-', color = 'red', linewidth = 2.0)
        axFit.set_xlabel("index", fontsize = cfg.fontsize)
#        axFit.set_xlabel(fit.x_labels[0], fontsize = cfg.fontsize)
        axFit.set_ylabel(fit.y_labels[0], fontsize = cfg.fontsize)
        axFit.set_ylim([plot_ymin, plot_ymax])
        axFit.set_yscale('log')
        axFit.legend(fontsize = cfg.legend_fontsize)

        format = "{label}\n data#{idata}:\n iteration={iteration}\n fmin={fmin:10.4g}"
        plot_event.annotation.add_line("fmin", axfmin, axfmin, x_list, fmin_list, line_fmin, 
                    inf_list = {"iteration": x_list, "fmin": fmin_list, "fit": fit, "xk": xk_history},
                    annotation_format = format, inf_format = inf_format_func)

        format = "{label}\n data#{idata}:\n index={index:10.4g}\n y={y:10.4g}"
        plot_event.annotation.add_line("input", axFit, axFit, index_list, fit.y_list[0], line_fit, 
                    inf_list = {"index": index_list, "y": fit.y_list[0]},
                    annotation_format = format, inf_format = format)

#        tkplt.tight_layout()
        if fit.fplot >= 1: tkplt.pause(0.1)

    pk_original = fit.pk.copy()
    for i in range(1, cfg.nsample + 1):
        print("stop_button.status:", plot_event.stop_button.status)
        if plot_event.stop_button.status == 'stop':
            plot_event.stop_button.set_text('finished')
            break

        xk = []
        for ivar in range(len(fit.varname)):
            if fit.optid[ivar] == 0:
                xk.append(pk_original[ivar])
                continue

            rand = random.random()
            if fit.pk_scale[ivar] == 'log':
                xmin = fit.kmin[ivar]
                xmax = fit.kmax[ivar]
                if xmin < eps:
                    xmin = log(eps)
                else:
                    xmin = log(xmin)
                if xmax < eps:
                    xmax = log(eps)
                else:
                    xmax = log(xmax)
                xk.append(exp(xmin + rand * (xmax - xmin)))
            else:
                xmin = fit.kmin[ivar]
                xmax = fit.kmax[ivar]
                xk.append(xmin + rand * (xmax - xmin))

        print(f"#{i+1:04d}/{cfg.nsample}:")
        for varname, x in zip(fit.varname, xk):
            print(f"  {varname:10}: {x:12.6g}")

        optpk = fit.extract_parameters(pk = xk)
        fmin = fit.minimize_func(optpk, run = True, print_level = 0)
        if fmin == 1.0e10:
            print(f"\nError in tkoptimize_flex.scan_one(): Something wrong in minimize_func().")
            app.terminate(f"  May be simulation part would be abnormally terminated. Check parameter range etc.\n", pause = True)

        if fmin == 1.0e10:
            print(f"\nError in tkoptimize_flex.scan_one(): Something wrong in minimize_func().")
            app.terminate(f"  May be simulation part would be abnormally terminated. Check parameter range etc.\n", pause = True)

        print(f"  fmin={fmin:12.6g}")
#        print(f"Save input and simulated data to [{fit.fitfile}]")
#        save_data(fit.fitfile, labels, data_list)

        xk_history.append(xk)
        x_list.append(i)  # iteration
        fmin_list.append(fit.fmin)
        ysim_all_list.append(fit.yc_list[0])
        if fmin < fmin_min:
            i_fmin = i
            xk_fmin = xk.copy()
            fmin_min = fmin

        if fit.fplot >= 1:
            axfmin.lines[0].set_xdata(x_list)
            axfmin.lines[0].set_ydata(fmin_list)
            axfmin.set_xlim([min(x_list), max(x_list)])
            axfmin.set_ylim([min(fmin_list), plot_fmax]) #max(fmin_list)])

            label = f'#{i}'
            index_list = range(len(fit.yc_list[0]))
            line_fit, = axFit.plot(index_list, fit.yc_list[0], 
                    label = label, linewidth = 0.5, linestyle = 'dashed', marker = '^', markersize = 1.5)
#            plot_ymin = max([plot_ymin, min(fit.yc_list[0])])
#            plot_ymax = min([plot_ymax, max(fit.yc_list[0])])
            axFit.set_ylim([plot_ymin, plot_ymax])
#            axFit.legend(fontsize = cfg.legend_fontsize)

            format = "{label}\n data#{idata}:\n index={index:10.4g}\n ycal={y:10.4g}"
            plot_event.annotation.add_line(f"cal #{i}", axFit, axFit, index_list, fit.yc_list[0], line_fit, 
                    inf_list = {"index": index_list, "y": fit.yc_list[0]},
                    annotation_format = format, inf_format = format)

#            tkplt.tight_layout()
            plot_event.stop_button.adjust_plot()  
            if fit.fplot >= 1: tkplt.pause(0.1)

            plot_event.annotation.refresh_cursors()

#    fit.close_history()

    print()
    print(f"Take minimum fmin={fmin_min} at sampling #{i_fmin}")
#    print(f"Take minimum fmin={fmin_min} at sampling #{i_fmin} with ", xk_fmin)
    print(f"Save parameters to [{cfg.parameterfile}]")
    fit.pk = xk_fmin
    fit.print_variables(heading = "Optimum variables")
    fit.save_parameters(fit.fitfile_path, section = "Parameters", keys = fit.varname)

    if fit.fplot >= 1:
        app.terminate(pause = True)

def scan_one(app, cfg, mf, fit, target_var, itarget, xk = None, fmin = None, title = None, pause = True, print_level = 1, outlog = None):
    if xk is None: xk = fit.pk

    if fmin is None:
        fmin_min = 1.0e300
    else:
        fmin_min = fmin
        
    if outlog:
        outlog.write(f"  scan_one: itarget={itarget} var={fit.varname[itarget]}: fmin={fmin_min:g}\n")

    if type(cfg.x0) is str:
        if cfg.x0 == '':
            cfg.x0 = fit.kmin[itarget]
        else:
            cfg.x0 = pfloat(cfg.x0, cfg.x0)
    if type(cfg.x1) is str:
        if cfg.x1 == '':
            cfg.x1 = fit.kmax[itarget]
        else:
            cfg.x1 = pfloat(cfg.x1, cfg.x1)

    if outlog:
        outlog.write(f"  Scan var #{itarget} {cfg.target_var}: x range: {cfg.x0} - {cfg.x1}\n")
    
    if print_level:
        print(f"Scan var #{itarget} {cfg.target_var}: x range: {cfg.x0} - {cfg.x1}")

    
    x_list = []
    fmin_list = []
    ysim_all_list = []
    if fit.fplot >= 1:
        print()
        print("Plot initialize")
        tkplt, root, tkpyplot = select_plt(use_tkinter = cfg.use_tkplt, plt = plt, parent = None, 
                title = "Scan parameters")
        fig, axes = tkplt.subplots(1, 2, figsize = cfg.figsize, dpi = 100, tight_layout = False)
#        fig, axes = plt.subplots(1, 2, figsize = cfg.figsize)

        if cfg.use_tkplt:
            tkplt.add_toolbar()
            canvas = tkplt.add_canvas(fig)

        plot_event = tkPlotEvent(plt)
        plot_event.add_stop_button()
#        plot_event.prepare_annotation()
        plot_event.prepare_popup_menu(fig, parent = root)

        axfmin = axes[0]
        axFit  = axes[1]
        if title:
            axfmin.set_title(title)

        axfmin.tick_params(labelsize = cfg.fontsize)
        line_fmin, = axfmin.plot(x_list, fmin_list, label = 'fmin', marker = 'o')
        axfmin.set_xlabel(cfg.target_var, fontsize = cfg.fontsize)
        axfmin.set_ylabel('fmin', fontsize = cfg.fontsize)
        if fit.pk_scale[itarget] == 'log':
            axfmin.set_xscale('log')
        axfmin.set_yscale('log')
        axfmin.legend(fontsize = cfg.legend_fontsize)

        axFit.tick_params(labelsize = cfg.fontsize)
        idx_list = range(len(fit.x_list[0]))
        line_fit, = axFit.plot(idx_list, fit.y_list[0], label = 'input', linestyle = '-', color = 'red', linewidth = 2.0)
        axFit.set_xlabel("index", fontsize = cfg.fontsize)
#        axFit.set_xlabel(fit.x_labels[0], fontsize = cfg.fontsize)
        axFit.set_ylabel(fit.y_labels[0], fontsize = cfg.fontsize)
        axFit.set_yscale('log')
#        axFit.legend(fontsize = cfg.legend_fontsize)

#        format = "{label}\n line#{iline}\n data#{idata}:\n index={index:10.4g}\n mu={mu:10.4g}\n  fmin={fmin:10.4g}"
#        plot_event.annotation.add_line("input", axFit, axFit, idx_list, fit.y_list[0], line_fit, 
#                    inf_list = {"index": idx_list, "mu": fit.y_list[0], "fmin": 0},
#                    annotation_format = format, inf_format = format)

# Don't use tight_layout() because it causes errors for zero data in log plot
#        tkplt.tight_layout()
        plot_event.stop_button.adjust_plot()
        if fit.fplot >= 1: tkplt.pause(0.01)

    eps = max([1.0e-5, abs(cfg.x1 - cfg.x0) * 1.0e-5])
    x_initial = xk[itarget]
    print()
    print(f"Scanning parameter {cfg.target_var}")
    xrange_list = [x_initial] + fit.get_xrange_list(itarget, cfg.nx, print_level = 1)
    print("    xrange_list:", [f"{x:10.4g}" for x in xrange_list])

    fit.open_history()
    ix_fmin = 0
    x_min = xk[itarget]
    xk_fmin = xk.copy()
    xk = xk.copy()
    for ix, x in enumerate(xrange_list):
        if plot_event.stop_button.status == 'stop':
            plot_event.stop_button.set_text('finished')
            return False

        xk[itarget] = x

        print()
        print(f"next x({cfg.target_var})={x:12.6g}")
        pkstr = [f"{v:10.4g}".strip() for v in xk]
        print(f"  ix={ix:03d}: xk=", *pkstr)
        optpk = fit.extract_parameters(xk)
        fmin = fit.minimize_func(optpk, run = True, print_level = print_level)
        if fmin == 1.0e10:
            print(f"\nError in tkoptimize_flex.scan_one(): Something wrong in minimize_func().")
            app.terminate(f"  May be simulation part would be abnormally terminated. Check parameter range etc.\n", pause = True)

        simlabels = [f"{s}(sim)" for s in fit.y_labels]
        labels    = [*fit.x_labels, *fit.y_labels, *simlabels]
        data_list = [*fit.x_list, *fit.y_list, *fit.yc_list]
        if print_level:
            print_data(labels = labels, data_list = data_list,
                    label_format = '{:^15}', data_format = '{:>15.4g}', header = "Simulated data:", nmax = 20, print_level = 0)
        print(f"  fmin={fmin:12.6g}")
        print(f"Save input and simulated data to [{fit.fitfile}]")
        save_data(fit.fitfile, labels, data_list, template = cfg.get("xlsm_template", None))

        x_list.append(x)
        if not is_numeric(fit.fmin): fit.fmin = 1.0e10
        fmin_list.append(fit.fmin)
        ysim_all_list.append(fit.yc_list[0])
        if fmin < fmin_min:
            ix_fmin = ix
            x_min = x
            xk_fmin  = xk.copy()
            fmin_min = fmin

        if fit.fplot >= 1:
            if ix == 0:
                axfmin.plot(x_list, fmin_list, label = 'fmin[0]', marker = '*', markersize = 15.0)
            axfmin.lines[0].set_xdata(x_list)
            axfmin.lines[0].set_ydata(fmin_list)
            axfmin.set_xlim([min(x_list), max(x_list)])
            axfmin.set_ylim([min(fmin_list), max(fmin_list)])

#            axFit.plot(fit.x_list[0], fit.yc_list[0], 
            idx_list = range(len(fit.x_list[0]))
            label = f"cal#{ix} {cfg.target_var}=" + f"{x:10.4g}".strip()
            line_fit, = axFit.plot(idx_list, fit.yc_list[0], 
                    label = label, 
                    linewidth = 0.5, linestyle = 'dashed', marker = '^', markersize = 1.5)
#            axFit.legend(fontsize = cfg.legend_fontsize)

            format = "{label}\n line#{iline}\n data#{idata}:\n index={index:10.4g}\n T={T:8.3f} N={N:10.4g}\n mu={mu:10.4g}\n  fmin={fmin:10.4g}"
#            plot_event.annotation.add_line(label, axFit, axFit, idx_list, fit.y_list[0], line_fit, 
#                    inf_list = {"index": idx_list, "T": fit.x_list[0], "N": fit.x_list[1], "mu": fit.y_list[0], "fmin": fmin},
#                    annotation_format = format, inf_format = format)

            tkplt.tight_layout()
            plot_event.stop_button.adjust_plot()

            if fit.fplot >= 1: tkplt.pause(0.01)

        format = "data #{idata}: " + f"{cfg.target_var}" + "={x:10.4g} fmin={fmin:10.4g}"
#        plot_event.annotation.add_line("fmin", axfmin, axfmin, x_list, fmin_list, line_fmin, 
#                        inf_list = {"x": x_list, "fmin": fmin_list},
#                    annotation_format = format, inf_format = format)

#        plot_event.register_annotation_event(fig, activate = True, print_level = 0)
##        plot_event.annotation.refresh_cursors()

        popup_menu = plot_event.popup_menu.menu
        popup_menu.add_command(label="run",  command = lambda: print("runが選択されました"))
        plot_event.register_popup_menu_event()

#    fit.close_history()

    print()
    print(f"Take minimum fmin={fmin_min} at iter={ix_fmin} {cfg.target_var}={x_min:10.4g}")
#    print(f"Take minimum fmin={fmin_min} at iter={ix_fmin} {cfg.target_var}={x_min:10.4g} with ", xk_fmin)
    print(f"Save parameters to [{cfg.parameterfile}]")

    fit.ix_fmin = ix_fmin
    fit.x_min   = x_min
    fit.pk      = xk_fmin
    fit.fmin    = fmin_min
    fit.print_variables(heading = "Optimum variables")
    fit.save_parameters(fit.fitfile_path, section = "Parameters", keys = fit.varname)

    if fit.fplot >= 1:
        if pause:
            app.terminate(pause = True)

    return fmin_min

def scan_all(app, cfg, mf, fit):
    print()
    print("Scan all parameters:")

    outlog = None
#    outlog = open('out.log', 'a')
    if outlog: outlog.write("Start scan_all\n")

    fmin = 1.0e300
    xk = fit.pk.copy()
    for itarget in range(len(fit.varname)):
        cfg.target_var = fit.varname[itarget]
        if fit.optid[itarget] == 0:
            print(f"Var #{itarget} {cfg.target_var} is not optimized. Skip.")
            continue

#        print("Scan var #{itarget} {cfg.target_var]")
        cfg.x0 = ''
        cfg.x1 = ''
        
        if outlog:
            outlog.write(f"itarget={itarget} {cfg.target_var} from fmin={fmin:g}")
            xkstr = 'xk='
            for x in xk:
                xkstr += f"{x:g} "
            outlog.write(xkstr + "\n")

        fmin = scan_one(app, cfg, mf, fit, cfg.target_var, itarget, xk = xk, fmin = fmin, pause = False, print_level = 0, outlog = outlog)
        if fmin is False:
            break

        xk = fit.pk.copy()

        if outlog:
            outlog.write(f"  Minimum at ix_min={fit.ix_fmin} with {cfg.target_var}={fit.x_min:g}: fmin={fit.fmin:g}")
            xkstr = '    xk='
            for x in xk:
                xkstr += f"{x:g} "
            outlog.write(xkstr)      
    if outlog:
        outlog.write('Finished\n') 
        outlog.close()

    app.terminate(pause = True)

def scan(app, cfg, mf):
    print("")
    print("Scan parameters:")
    fit = mf.init_fit(app, cfg)

    print(f"Read input data from [{cfg.infile}]")
    fit.x_labels, fit.y_labels, fit.xd_list, fit.yd_list, fit.w_list = mf.read_input_data(app, cfg.infile, cfg)

    fit.labels = fit.x_labels + fit.y_labels
    fit.x_list = fit.xd_list
    fit.y_list = fit.yd_list

    print(f"Save input data to [{cfg.outfile}]")
    save_data(cfg.outfile, fit.labels, fit.x_list + fit.y_list, template = cfg.get("xlsm_template", None))

#    print()
#    fit.print_variables(heading = "Fitting parameters:")

    if type(cfg.target_var) is str and cfg.target_var == 'all':
        itarget = None
    else:
        itarget, cfg.target_var = index2val(cfg.target_var, fit.varname, (None, None))

    if (cfg.target_var is None or cfg.target_var == '') and cfg.target_var != 'all':
        print(f"Error: target var [{cfg.target_var}] is not in the varname list ", fit.varname)
        app.terminate(pause = True)

    if cfg.target_var == 'all':
        return scan_all(app, cfg, mf, fit)
    else:
        return scan_one(app, cfg, mf, fit, cfg.target_var, itarget, print_level = 0)

def error(app, cfg, mf):
    print()
    print("Estimate errors for fitted parameters:")
    fit = mf.init_fit(app, cfg)

    print(f"Read input data from [{cfg.infile}]")
    fit.x_labels, fit.y_labels, fit.xd_list, fit.yd_list, fit.w_list = mf.read_input_data(app, cfg.infile, cfg)
    if fit.x_labels is None:
        app.terminate(f"\nError in fit(): Can not read [{cfg.infile}]", pause = True)

    print(f"Save input data repeat to [{cfg.outfile}]")
    save_data(cfg.outfile, fit.x_labels + fit.y_labels, fit.xd_list + fit.yd_list, template = cfg.get("xlsm_template", None))

    print()
    fit.print_variables(heading = "Fitting parameters:")

    fit.x_list = fit.xd_list
    fit.y_list = fit.yd_list

# For plot
    ngraph = 0
    for ivar in range(len(fit.pk)):
        if fit.optid[ivar] == 1: ngraph += 1

    nx_graph = int(sqrt(ngraph) + 0.9999)
    ny_graph = ngraph // nx_graph
    if nx_graph * ny_graph < ngraph: ny_graph += 1

    print()
    print(f"plot: {nx_graph} x {ny_graph} for {ngraph} graphs")

    tkplt, root, tkpyplot = select_plt(use_tkinter = cfg.use_tkplt, plt = plt, parent = None, 
                title = "Estimate errors for optimized parameters")
    fig, axes = tkplt.subplots(ny_graph, nx_graph, figsize = cfg.figsize, dpi = 100, tight_layout = True)
#    fig, axes = plt.subplots(ny_graph, nx_graph, figsize = cfg.figsize)
    if cfg.use_tkplt:
        tkplt.add_toolbar()
        canvas = tkplt.add_canvas(fig)

    if nx_graph == 1:
        axes = [axes]
    elif nx_graph > 1 and ny_graph > 1:
        axes = axes.flatten()

    axes[0].set_xlabel("x")
    axes[0].set_ylabel("p")
    if fit.fplot >= 1: tkplt.pause(0.1)

    plot_event = tkPlotEvent(plt)
    plot_event.prepare_annotation()

    p_sigma = exp(-0.5)
    npk = len(fit.pk)
    ig = 0
    result_list = []
    for ivar in range(npk):
        if fit.optid[ivar] == 0: continue

        scale = fit.pk_scale[ivar]
        xlim_min = fit.kmin[ivar]
        xlim_max = fit.kmax[ivar]
        print()
        print(f"Calculate likelihood function for var#{ivar}/{npk} {fit.varname[ivar]}={fit.pk[ivar]:12.6g} for {cfg.ncal_error} points")
        x_list, p_list = fit.build_likelihood_func(fit.pk, ivar, 
                        xlim_min = xlim_min, xlim_max = xlim_max, 
                        kvariance = cfg.kvariance, ncal = cfg.ncal_error, scale = scale, print_level = 1)

        out_path = f"error_{fit.varname[ivar]}.xlsx"
        print(f"  Save to [{out_path}]")
        tkVariousData().to_excel(out_path, 
                labels = [fit.varname[ivar], "p"], data_list = [x_list, p_list], template = cfg.get("xlsm_template", None))

        #p_listが最大値を取るインデックスを取得する
        nlist = len(p_list)
        imax  = np.argmax(p_list)
#        print("p_list=", p_list)
        print("  index at maximum p in p_list:", imax)

        x_min_s = None
        if 0 < imax and min(p_list[0:imax]) < p_sigma:
            x, y = zip(*sorted(zip(p_list[0:imax+1], x_list[0:imax+1])))
            x_min_s = np.interp(p_sigma, x, y)
            print(f"  x_min_s at p(sigma): {x_min_s:12.6g}")

        x_max_s = None
        if imax < nlist - 1 and min(p_list[imax:]) < p_sigma:
            x, y = zip(*sorted(zip(p_list[imax:], x_list[imax:])))
            x_max_s = np.interp(p_sigma, x, y)
            print(f"  x_max_s at p(sigma): {x_max_s:12.6g}")

        result_list.append([x_min_s, x_max_s])

        ax = axes[ig]
#        ax.tick_params(labelsize = cfg.fontsize)
        label = f'p({fit.varname[ivar]})'
        line, = ax.plot(x_list, p_list, label = label, linewidth = 1.0)
        #y=0の位置に赤い横線を描く
        ax.axhline(y = 0.0, color = 'red', linestyle = 'dashed', linewidth = 0.5)
        line_cur = ax.axvline(x = fit.pk[ivar], color = 'red', linestyle = 'dashed', linewidth = 0.5)
        ax.axhline(y = p_sigma, color = 'blue', linestyle = 'dashed', linewidth = 0.5)
        line_min = None
        line_max = None
        if x_min_s is not None:
            line_min = ax.axvline(x = x_min_s, color = 'blue', linestyle = 'dashed', linewidth = 0.5)
        if x_max_s is not None:
            line_max = ax.axvline(x = x_max_s, color = 'blue', linestyle = 'dashed', linewidth = 0.5)
        ax.set_xlabel(fit.varname[ivar])#, fontsize = cfg.fontsize)
        ax.set_ylabel('p')#, fontsize = cfg.fontsize)
        ax.set_ylim([0.0, 1.2])
        if scale == 'log': ax.set_xscale('log')       
#        ax.legend(fontsize = cfg.legend_fontsize)

        format = "{label}: " + fit.varname[ivar] + "={x:.4g}  p={p:.4f}"
        plot_event.annotation.add_line(label, ax, ax, x_list, p_list, line, 
                    inf_list = {"x": x_list, "p": p_list},
                    annotation_format = format, inf_format = format)

        x0 = fit.pk[ivar]
        xmin = result_list[ig][0]
        xmax = result_list[ig][1]

        format = "x0: " + fit.varname[ivar] + "={x:.4g}  p={p}"
        plot_event.annotation.add_line(label, ax, ax, x_list, p_list, line_cur, 
                    inf_list = {"x": x0, "p": 1.0},
                    annotation_format = format, inf_format = format)

        p_sigma = exp(-0.5)
        if line_min:
            format = "x0 - sigma: " + fit.varname[ivar] + "={x:.4g}  p={p}"
            plot_event.annotation.add_line(label, ax, ax, x_min_s, p_sigma, line_min, 
                    inf_list = {"x": x_min_s, "p": "exp(-1/2)"},
                    annotation_format = format, inf_format = format)

        if line_max:
            format = "x0 + sigma: " + fit.varname[ivar] + "={x:.4g}  p={p}"
            plot_event.annotation.add_line(label, ax, ax, x_max_s, p_sigma, line_max, 
                    inf_list = {"x": x_max_s, "p": "exp(-1/2)"},
                    annotation_format = format, inf_format = format)
        
        ig += 1
        if fit.fplot >= 1: tkplt.pause(0.1)
        plt.savefig('error.png')

        ig2 = 0
        print()
        print("Accuracy within one sigma:")
        for ivar in range(npk):
            if fit.optid[ivar] == 0: 
#                print("id=0 ivar=", ivar, ig, ig2)
                continue
#            else:
#                print("id=1 ivar=", ivar, ig, ig2)

            x0 = fit.pk[ivar]
            xmin = result_list[ig2][0]
            xmax = result_list[ig2][1]
            if xmin is None:
                dxmin = f"{'*':^10}"
                xmin  = f"{'*':^12}"
            else:
                dxmin = f"{xmin - x0:8.4g}"
                xmin  = f"{xmin:12.6g}"
            if xmax is None:
                dxmax = f"{'*':^10}"
                xmax  = f"{'*':^12}"
            else:
                dxmax = f"{xmax - x0:8.4g}"
                xmax  = f"{xmax:12.6g}"
            print(f"{ivar:03d}: {fit.varname[ivar]:10}: {x0:12.6g} {dxmin} + {dxmax} in ({xmin} - {xmax})")

            ig2 += 1
            if ig2 >= ig: break

    plt.tight_layout()
    if fit.fplot >= 1: tkplt.pause(0.1)
    plt.savefig('error.png')

    plot_event.register_annotation_event(fig, activate = True, print_level = 0)

    app.terminate(pause = True)

def plot_history(app, cfg, mf):
    cfg.historyfile   = app.replace_path(cfg.infile, template = ["{dirname}", "{filebody}-history.xlsx"])
    if cfg.file2 == '':
        infile = cfg.historyfile
    else:
        infile = cfg.file2

    print()
    print("Plot hisotry")
    print(f"fmax_plot   : {cfg.fmax_plot}")
    print(f"History file: [{infile}]")

    df = pd.read_excel(infile)#, sheet_name = None)
    df = df[df['min:fmin'] <= cfg.fmax_plot]
    labels = df.columns.tolist()
    iter_list = df.iloc[:, 0].values.tolist()
    time_list = df.iloc[:, 1].values.tolist()
    fmin_list = df.iloc[:, 2].values.tolist()
    y_labels = labels[3:]
    data_list = df.iloc[:, 3:].to_numpy().T.tolist()

    iter_file_list = []
    fmin_file_list = []
    for idx, iter in enumerate(iter_list):
        fitfile = f'fit{iter:04}.xlsx'
        if not os.path.isfile(fitfile): continue

        print(f"fitfile found for #{iter} [{fitfile}]")
        iter_file_list.append(iter)
        fmin_file_list.append(fmin_list[idx])

    ngraph = len(labels) - 1
    nx = int(sqrt(ngraph) + 0.99)
    ny = int(ngraph / nx) + 1

# Plot graphs
    fig, axes = plt.subplots(1, 1, figsize = (8, 6))
    ax = axes
#    fig, axes = plt.subplots(nx, ny, figsize = (8, 6))
#    axes = axes.flatten()

#    ax = axes[0]
    ax.tick_params(labelsize = cfg.fontsize)
    ax.plot(iter_list,      fmin_list,      label = 'fmin',            linestyle = '-', linewidth = 0.5, marker = 'o', markersize = 1.0)
    ax.plot(iter_file_list, fmin_file_list, label = 'fmin(with file)', linestyle = '',  marker = '*', markersize = 5.0)
    ax.set_xlabel("iteration", fontsize = cfg.fontsize)
    ax.set_ylabel("fmin", fontsize = cfg.fontsize)
    ax.set_yscale('log')
    ax.legend(fontsize = cfg.legend_fontsize)

    plt.tight_layout()

    def motion(event):
        x = event.xdata
        y = event.ydata
        ln_v.set_xdata(x)
        ln_h.set_ydata(y)
        plt.draw()

    ln_v = plt.axvline(0, linestyle = 'dashed', linewidth = 0.5, color = 'red')
    ln_h = plt.axhline(0, linestyle = 'dashed', linewidth = 0.5, color = 'red')
    plt.connect('motion_notify_event', motion)
    
    if fit.fplot >= 1: plt.pause(0.01)
    
    app.terminate(pause = True)

def mlr(app, cfg, mf):
    mlr           = tkMLR()
    mld_processed = tkMLData()
    mld_converted = tkMLData()
    mld_input_d_train_std = tkMLData()
    mld_input_d_test_std  = tkMLData()
    mld_input_o_train     = tkMLData()
    mld_input_o_test      = tkMLData()
    mld_predict_train     = tkMLData()
    mld_predict_test      = tkMLData()
    cfg.hidden_layer_sizes = (5, 5)

    print()
    print("Machine learning regression:")
    print(f"Training history file: {cfg.trainfile}")
    print(f"Test data ratio: {cfg.test_data_ratio}")
    print(f"Maximum fmin for MLR: {cfg.fmin_max_mlr}")
#    cfg.print_parameters()

    fit = mf.init_fit(app, cfg)
    print()
    print(f"Read input data from [{cfg.trainfile}]")
    mlr.read_data(cfg.trainfile)
    print("Original data:")
    print(f"  Number of columns: {mlr.ncolumns}")
    print(f"  Number of data   : {mlr.ndata}")
#    print("mlr.df:\n", mlr.df)

    mld_processed.df = mlr.extract_data(df = mlr.df, 
                            delete_na = True, limit_max_list = [['min:fmin', cfg.fmin_max_mlr]])
    print("Processed data:")
    print( "  delete non numerical data")
    print(f"  max value for [min:fmin]: {cfg.fmin_max_mlr}")
    print(f"  Number of columns: {mld_processed.ncolumns}")
    print(f"  Number of data   : {mld_processed.ndata}")
#    print(mld_processed.df)

    exclude_labels    = mlr.get_exclude_labels(keys = fit.varname, id = fit.optid, regexp_list = [r"^\-", r"^'\-"])
    objective_labels  = mlr.get_objective_labels(regexp_list = [r"^o:", r"^t:", r"^min:", r"^max:"])
    descriptor_labels = mlr.get_descriptor_labels(labels = mlr.columns, 
                            remove_keys = exclude_labels + objective_labels)
    print("labels:")
#    print("  all           :", mlr.columns)
    print("  descriptors   :", descriptor_labels)
    print("  objective vars:", objective_labels)
    print("  exclude       :", exclude_labels)

    mld_processed.descriptor_df = mld_processed.df[descriptor_labels]
    mld_processed.objective_df  = mld_processed.df[objective_labels]

    print()
    print("Scale:")
    scale_dict = {}
    for varname, scale in zip(fit.varname, fit.pk_scale):
        scale_dict[varname] = scale
    mld_converted.descriptor_df = mlr.convert_df(mld_processed.descriptor_df, scale_dict = scale_dict)

    print()
    print("descriptor data (converted)")
    print("  scale:", scale_dict)
    ndata = len(mld_converted.descriptor_df)
    print(f"  ndata: {ndata}")
    print(mld_converted.descriptor_df)
    if ndata == 0:
        print()
        print("Warning: All data have been removed. Stop")
        return

    print()
    print("Standardize:")
    mld_converted.descriptor_df_std = mlr.standardize(mld_converted.descriptor_df)
    print()
    print("descriptor data (converted+standardized)")
    print(mld_converted.descriptor_df_std)
    print()
    print("objective var data:")
    print(mld_processed.objective_df)

    d_data = mld_converted.descriptor_df_std
    o_data = mld_processed.objective_df
    d_train, d_test, o_train, o_test = mlr.split_data(d_data, o_data, test_size = cfg.test_data_ratio)
    mld_input_d_train_std.df = d_train
    mld_input_d_test_std.df  = d_test
    mld_input_o_train.df     = o_train
    mld_input_o_test.df      = o_test
    print("descriptor_data train:", mld_input_d_train_std.ndata)
    print("descriptor_data test :", mld_input_d_test_std.ndata)
    print("objective_data train :", mld_input_o_train.ndata)
    print("objective_data test  :", mld_input_o_test.ndata)

    mlr.model = mlr.fit(
                    descriptor_df = mld_input_d_train_std.df, 
                    objective_df  = mld_input_o_train.df,
                    method = cfg.method, 
                    alpha = cfg.alpha, l1_ratio = cfg.l1_ratio, use_cv = cfg.use_cv, cv = cfg.cv,
                    max_num_probes = cfg.max_num_probes, interval = cfg.interval,
                    num_rand_basis = cfg.num_rand_basis, score_mode = cfg.score_mode, seed = cfg.seed,
                    max_iter = cfg.nmaxiter, tol = cfg.tol, print_level = 1)

    mld_predict_train.df = mlr.predict(mld_input_d_train_std.df, mld_input_o_train.columns)
    mlr.score_train = None
    if mlr.std is not None and hasattr(mlr, "mean_m_sigma"):
        mlr.mean_m_sigma_train = mlr.mean_m_sigma.copy()
        mlr.mean_p_sigma_train = mlr.mean_p_sigma.copy()
        if hasattr(mlr, "score"):
            mlr.score_train = mlr.score.copy()

    mld_predict_test.df  = mlr.predict(mld_input_d_test_std.df,  mld_input_o_test.columns)
    mlr.score_test = None
    if mlr.std is not None and hasattr(mlr, "mean_m_sigma"):
        mlr.mean_m_sigma_test = mlr.mean_m_sigma.copy()
        mlr.mean_p_sigma_test = mlr.mean_p_sigma.copy()
        if hasattr(mlr, "score"):
            mlr.score_test = mlr.score.copy()

    print("predicted data (train):")
    print(mld_predict_train.df)
    print("predicted data (test):")
    print(mld_predict_test.df)
    mae_train, mse_train, r2_train = mlr.error(mld_input_o_train.df, mld_predict_train.df)
    mae_test,  mse_test,  r2_test  = mlr.error(mld_input_o_test.df,  mld_predict_test.df)
    print(f"Train data: MAE={mae_train:10.4g} MSE={mse_train:10.4g} R2={r2_train:10.4g}")
    print(f"Test data:  MAE={mae_test:10.4g} MSE={mse_test:10.4g} R2={r2_test:10.4g}")
        
    #グラフを描く
    print()
    print("plot")
    if mlr.std is not None:
        fig, axes = plt.subplots(1, 2, figsize = (12, 8))
        axes.flatten()
    else:
        fig, axes = plt.subplots(1, 2, figsize = (12, 8))
        axes.flatten()

    plot_event = tkPlotEvent(plt)
    ax = axes[0]
    ax.tick_params( labelsize = cfg.fontsize)
    mlr.plot_accuracy(ax, fig, 
            [{"label": "train", "x_df": mld_input_o_train.df, "y_df": mld_predict_train.df,
                       "descriptor_df": mld_input_d_train_std.df, "objective_df": mld_input_o_train.df, 
                       "color": "blue"}, 
             {"label": "test",  "x_df": mld_input_o_test.df,  "y_df": mld_predict_test.df,
                       "descriptor_df": mld_input_d_test_std.df,  "objective_df": mld_input_o_test.df, 
                       "color": "red"} ],
            fontsize = cfg.fontsize, legend_fontsize = cfg.legend_fontsize,
            plot_event = plot_event, register_events = "annotation|move_text"
            )

    ax  = axes[1]
    ax.tick_params( labelsize = cfg.fontsize)
    if hasattr(mlr, "mean_m_sigma_train"):
        mlr.plot_index_fmin(ax, fig,
                [{"label": "train", "input_df": mld_input_o_train.df, "predict_df": mld_predict_train.df, "color": 'blue',
                  "-sigma_df": mlr.mean_m_sigma_train, "+sigma_df": mlr.mean_p_sigma_train, "color_sigma": 'lightblue', 
                  "score_df": mlr.score_train, "color_score": 'orange',
                  "descriptor_df": mld_input_d_train_std.df, "objective_df": mld_input_o_train.df, 
                  },
                 {"label": "test", "input_df": mld_input_o_test.df,  "predict_df": mld_predict_test.df, "color": 'red',
                  "-sigma_df": mlr.mean_m_sigma_test, "+sigma_df": mlr.mean_p_sigma_test,  "color_sigma": 'pink', 
                  "score_df": mlr.score_test, "color_score": 'orange',
                  "descriptor_df":mld_input_d_test_std.df,  "objective_df": mld_input_o_test.df, 
                  }],
                objective_label = objective_labels[0],
                plot_event = None, register_events = "annotation|move_text",
                fontsize = cfg.fontsize, legend_fontsize = cfg.legend_fontsize
                )
    else:
        mlr.plot_index_fmin(ax, fig,
                [{"label": "train", "input_df": mld_input_o_train.df, "predict_df": mld_predict_train.df, "color": "blue",
                  "descriptor_df": mld_input_d_train_std.df, "objective_df": mld_input_o_train.df},
                 {"label": "test",  "input_df": mld_input_o_test.df,  "predict_df": mld_predict_test.df,  "color": "red",
                  "descriptor_df": mld_input_d_train_std.df, "objective_df": mld_input_o_train.df}],
                objective_label = objective_labels[0],
                plot_event = None, register_events = "annotation|move_text",
                fontsize = cfg.fontsize, legend_fontsize = cfg.legend_fontsize
                )

    plt.tight_layout()
    if fit.fplot >= 1: plt.pause(0.01)

#    print()
#    print("Save predicted data to [predicted.xlsx]")
#    df = pd.DataFrame(ml.predicted_df, columns = ml.objective_data.labels)
#    df.to_excel("predicted.xlsx", index = False)

    app.terminate(pause = True)


def main():
    app = tkApplication()
    mf  = imp.import_lib("minimize_func", stop_by_error = False)

#初期値の確定順序
#intialize(): 起動時引数と初期値の設定
    cfg = mf.initialize(app)
#--infile引数をチェックし、読み込みファイル名を最初に確定する
    cfg.infile = app.check_arg('--infile', defval = None, vartype = 'str')

#cfg.infileからログファイル名を作り、console出力をredirectする
    cfg.logfile = app.replace_path(cfg.infile)
    app.redirect(heading = f"Open logfile [{cfg.logfile}]",
                targets = ["stdout", cfg.logfile], mode = 'w')

    app.print_title(f"#  {mf.ProgramName}")

#cfg.infileが入力ファイルか設定ファイルであるかによって、infileとconfig_pathを返す
#  設定ファイルである場合、infile_storedをNoneにして、後で設定ファイルからcfg.infileを読み込む
    cfg.infile, cfg.parameterfile, infile_stored \
            = app.analyze_infile(cfg.infile, infile_ext = [".xlsx"], config_ext = [".in", ".ini", ".prm"], print_level = 1)

#起動時引数で与えられたパラメータをcfgに設定
    app.update_vars(cfg,apply_default = True)
#設定ファイルを読み込み、cfgに設定。#cfg.infileも書き換えられる
    mf.read_parameters(cfg.parameterfile, cfg)
#infile_stored = Noneの場合、設定ファイルのcfg.infileをinfile_storedに再保存
#infile_storedが最終的な読み込みファイル名になる
    if infile_stored is None:
        infile_stored = cfg.infile
#起動時引数で与えられたパラメータを最優先にするため、再設定
    app.force_given_args(cfg)
#cfg.infileを infile_storedに最終設定
    cfg.infile = infile_stored
#    cfg.print_parameters()

    cfg.historyfile   = app.replace_path(cfg.infile, template = ["{dirname}", "{filebody}-history.xlsx"])
    cfg.stopfile      = app.replace_path(cfg.infile, template = ["{dirname}", "stop"])
    print(f"infile: {cfg.infile}")
    print(f"parameterfile: {cfg.parameterfile}")

# cfg.modeで与えられた関数が定義されていれば、呼び出す
    if cfg.mode in globals() and callable(globals()[cfg.mode]):
        globals()[cfg.mode](app, cfg)
    else:
        app.terminate("Error in main: Invalide mode [{}]".format(cfg.mode), usage = app.usage, pause = True)

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


if __name__ == "__main__":
    main()
