function simulate_paths(space, c_store, mparams, opts, smax, smin;
                        misspec_damages = :base, alt_damages = false, d1 = [], d2 = [])
"""
Simulate model, outer function
"""

    # Simulating
    opts[:sim] = true

    # Boolean option for doing alternative (but correctly specified) damage parameters
    opts[:alt_damages] = alt_damages
    if opts[:alt_damages]
        mparams[:d1] = d1
        mparams[:d2] = d2
    end

    # Correctly specified or misspecified damages
    mparams[:damages] = misspec_damages

    # Initialize state array
    if opts[:learn_e]
        states = zeros(mparams[:sim_length] + 1, 5)
    else
        states = zeros(mparams[:sim_length] + 1, 3)
    end

    ## Initialize storage arrays for simulations
    # State array
    s_store = Vector{Array}(undef, mparams[:num_runs])
    # d_2 mean quadrature
    loc_store = Vector{Vector{Array}}(undef, mparams[:num_runs])
    # Carbon tax channels
    channels = Vector{Dict{Symbol,Array}}(undef, mparams[:num_runs])
    # Fractional net output quadrature
    dam_store = Vector{Vector{Array}}(undef, mparams[:num_runs])
    # Controls
    controls_store = Vector{Vector}(undef, mparams[:num_runs])
    # Investment rate
    inv_rate = Vector{Vector}(undef, mparams[:num_runs])
    # Jumps up array
    jumps_up = Vector{Array}(undef, mparams[:num_runs])
    # Jumps down array
    jumps_down = Vector{Array}(undef, mparams[:num_runs])

    # Initial state
    if opts[:learn_e]
        states[1, :] = [mparams[:kstart], mparams[:Mstart], mparams[:expstart], mparams[:varstart], mparams[:lossstart]]
    else
        states[1, :] = [mparams[:kstart], mparams[:Mstart], mparams[:lossstart]]
    end

    # Exogenous variables
    exog_states = simulate_exog_vars(mparams)

    # Load shocks, pre-made so common shocks across different frameworks in the paper
    if mparams[:damages] == :base && mparams[:num_runs] > 1000
        temp = BSON.load("results/loaded_sim_values_large.bson")
    elseif mparams[:damages] == :base && mparams[:num_runs] <= 1000
        temp = BSON.load("results/loaded_sim_values_large_adapt.bson")
    else
        println("Simulating with misspecified damage function.")
        temp = BSON.load("results/loaded_sim_values_alt.bson")
    end
    sim_values = temp["sim_values"]

    # How to generate lognormal random variables:
    # exp.(rand(Normal(location_parameter, sqrt(scale_parameter), number_of_draws))
    # shocks = [exp.(rand(Normal(-0.5885394796543533,sqrt(1.1770789593087065)), 101)) for i = 1:50000]
    # bson("results/loaded_sim_values_large.bson", Dict{String, Array{Array{Float64}}}("shocks" => shocks))
    if opts[:alt_damages]
        shocks_dict = BSON.load("results/loaded_sim_values_shocks.bson")
        extract(shocks_dict)
        sim_values[2] = shocks
    end

    # Initialize array of variables to send to simulator
    sim_parameters = Array{Array}(undef, mparams[:num_runs], 1)
    sim_parameters = [simParameters(states, exog_states, mparams, space, opts, c_store,
        sim_values[1][i], sim_values[2][i], smax, smin) for i = 1:mparams[:num_runs]]

    # Simulate
    sim_results = pmap(simulate_inner, sim_parameters)

    # Pull out and store results
    for run = 1:mparams[:num_runs]
        # Controls
        controls_store[run] = sim_results[run][1]
        # States
        s_store[run] = sim_results[run][2]
        # d_2 mean quadrature
        loc_store[run] = sim_results[run][3]
        # Fractional net output
        dam_store[run] = sim_results[run][4]
        # Carbon tax channels
        channels[run] = sim_results[run][5]
        # Investment rate
        inv_rate[run] = sim_results[run][6]
    end

    # Functions to pull out and separate states and controls
    states_func(M) = [[M[i][:, j] for i = 1:length(M)] for j = 1:size(s_store[1], 2)]
    controls_func(M) = [[[M[i][j][k] for j = 1:mparams[:sim_length]] for i = 1:length(M)] for k = 1:2]

    # Pull out states
    if opts[:learn_e]
        capital, co2, prior_loc, prior_scale, damage = states_func(s_store)
    else
        capital, co2, damage = states_func(s_store)
    end

    # Pull out controls
    consumption, abatement = controls_func(controls_store)

    # Initialize dictionary for mean tax channels
    channels_out = Dict{Symbol,Array}()
    channels_out_samples = Dict{Symbol,Array}()

    # Get 90% confidence bounds on carbon tax
    channels_out[:tax_lower], channels_out[:tax_upper] = get_bounds([channels[i][:scc_direct] for i = 1:mparams[:num_runs]], .05)

    # Pull out individual channels to average out
    for key in keys(channels[1])
        channels_out[key] = [channels[i][key] for i = 1:mparams[:num_runs]]
        # Keep individual run tax channels
        channels_out_samples[key] = [channels[i][key] for i = 1:mparams[:num_runs]]
        # Average channels
        channels_out[key] = mean(hcat(channels_out[key]...), dims = 2)
    end

    if ~opts[:learn_e]
        prior_loc = 0.
        prior_scale = 0.
    end

    return consumption, abatement, inv_rate, capital, co2,
        prior_loc, prior_scale, damage, sim_values, loc_store, dam_store,
        channels_out, channels_out_samples, exog_states

end

function simulate_paths_adapt(space, c_store, mparams, opts, smax, smin;
    alt_damages = false)
"""
Simulate model, outer function for adapted grid
"""

    include("initialization.jl")

    # Boolean option for doing alternative (but correctly specified) damage parameters
    opts[:alt_damages] = alt_damages

    # Simulating
    opts[:sim] = false

    # Initialize state array
    if opts[:learn_e]
        states = zeros(mparams[:sim_length] + 1, 5)
    else
        states = zeros(mparams[:sim_length] + 1, 3)
    end

    ## Initialize storage arrays for simulations
    # State array
    s_store = Vector{Array}(undef, mparams[:num_runs])
    # d_2 mean quadrature
    loc_store = Vector{Vector{Array}}(undef, mparams[:num_runs])
    # Carbon tax channels
    channels = Vector{Dict{Symbol,Array}}(undef, mparams[:num_runs])
    # Fractional net output quadrature
    dam_store = Vector{Vector{Array}}(undef, mparams[:num_runs])
    # Controls
    controls_store = Vector{Vector}(undef, mparams[:num_runs])
    # Investment rate
    inv_rate = Vector{Vector}(undef, mparams[:num_runs])

    # Initial state
    if opts[:learn_e]
        states[1, :] = [mparams[:kstart], mparams[:Mstart], mparams[:expstart], mparams[:varstart], mparams[:lossstart]]
    else
        states[1, :] = [mparams[:kstart], mparams[:Mstart], mparams[:lossstart]]
    end

    # Exogenous variables
    exog_states = simulate_exog_vars(mparams)

    # Load shocks, pre-made so common shocks across different frameworks in the paper
    temp = BSON.load("results/loaded_sim_values_large_adapt.bson")

    sim_values = temp["sim_values"]

    # Initialize array of variables to send to simulator
    sim_parameters = Array{simParameters}(undef, mparams[:num_runs], 1)
    sim_parameters =
        [simParameters(states, exog_states, mparams, space, opts, c_store,
            sim_values[1][i], sim_values[2][i], smax, smin) for i = 1:mparams[:num_runs]]

    # Simulate
    sim_results = pmap(simulate_inner, sim_parameters)

    # Pull out and store results
    for run = 1:mparams[:num_runs]
        # Controls
        controls_store[run] = sim_results[run][1]
        # States
        s_store[run] = sim_results[run][2]
        # d_2 mean quadrature
        loc_store[run] = sim_results[run][3]
        # Fractional net output
        dam_store[run] = sim_results[run][4]
    end

    # Functions to pull out and separate states and controls
    states(M) = [[M[i][:, j] for i = 1:length(M)] for j = 1:size(s_store[1], 2)]
    controls(M) = [[[M[i][j][k] for j = 1:mparams[:sim_length]] for i = 1:length(M)] for k = 1:2]

    # Pull out states
    if opts[:learn_e]
        capital, co2, prior_loc, prior_scale, damage = states(s_store)
    else
        capital, co2, damage = states(s_store)
    end

    # Pull out controls
    consumption, abatement = controls(controls_store)

    if ~opts[:learn_e]
        prior_loc = 0.
        prior_scale = 0.
    end

    return consumption, abatement, capital, co2,
        prior_loc, prior_scale, damage, loc_store, dam_store

end

function simulate_paths_adapt(space, c_store, mparams, opts;
     alt_damages = false)
"""
Simulate model for adaptive grid on first iteration
"""

        include("initialization.jl")

        # Switch for doing alternative (but correctly specified) damage parameters
        opts[:alt_damages] = alt_damages

        # Simulating
        opts[:sim] = false

        # Initialize state array
        if opts[:learn_e]
            states = zeros(mparams[:sim_length] + 1, 5)
        else
            states = zeros(mparams[:sim_length] + 1, 3)
        end

        ## Initialize storage arrays for simulations
        loc_next = Vector{Array}(undef, mparams[:sim_length] + 1)
        dam_next = Vector{Array}(undef, mparams[:sim_length] + 1)
        loc_next[1] = mparams[:expstart]*ones(mparams[:nqnodes], mparams[:nqnodes])
        dam_next[1] = mparams[:lossstart]*ones(mparams[:nqnodes], mparams[:nqnodes])
        s_store = Vector{Array}(mparams[:num_runs_adapt])
        loc_store = Vector{Vector{Array}}(undef, mparams[:num_runs_adapt])
        dam_store = Vector{Vector{Array}}(undef, mparams[:num_runs_adapt])
        controls = Vector{Vector}(undef, mparams[:sim_length])
        controls_store = Vector{Vector}(undef, mparams[:num_runs_adapt])
        loc_next[1] = mparams[:expstart]*ones(mparams[:nqnodes], mparams[:nqnodes])
        dam_next[1] = mparams[:lossstart]*ones(mparams[:nqnodes], mparams[:nqnodes])

        # Initial state
        if opts[:learn_e]
            states[1, :] = [mparams[:kstart], mparams[:Mstart], mparams[:expstart], mparams[:varstart], mparams[:lossstart]]
        else
            states[1, :] = [mparams[:kstart], mparams[:Mstart], mparams[:lossstart]]
        end

        # Exogenous variables
        exog_states = simulate_exog_vars(mparams)

        # Load shocks, pre-made so common shocks across different frameworks in the paper
        temp = BSON.load("results/loaded_sim_values_large_adapt.bson")

        sim_values = temp["sim_values"]

        # Initialize array of variables to send to simulator
        sim_parameters = Array{simParameters}(undef, mparams[:num_runs], 1)
        sim_parameters =
            [simParameters(states, exog_states, mparams, space, opts,
            c_store, sim_values[1][i], sim_values[2][i], smax, smin) for i = 1:mparams[:num_runs]]

        # Simulate
        sim_results = pmap(simulate_inner, sim_parameters)

        # Pull out and store results
        for run = 1:mparams[:num_runs]
            # Controls
            controls_store[run] = sim_results[run][1]
            # States
            s_store[run] = sim_results[run][2]
            # d_2 mean quadrature
            loc_store[run] = sim_results[run][3]
            # Fractional net output
            dam_store[run] = sim_results[run][4]
        end

        # Functions to pull out and separate states and controls
        states(M) = [[M[i][:, j] for i = 1:length(M)] for j = 1:size(s_store[1], 2)]
        controls(M) = [[[M[i][j][k] for j = 1:mparams[:sim_length]] for i = 1:length(M)] for k = 1:2]

        # Pull out states
        if opts[:learn_e]
            capital, co2, prior_loc, prior_scale, damage = states(s_store)
        else
            capital, co2, damage = states(s_store)
        end

        # Pull out controls
        consumption, abatement = controls(controls_store)

        if ~opts[:learn_e]
            prior_loc = 0.
            prior_scale = 0.
        end

        return consumption, abatement, capital, co2,
            prior_loc, prior_scale, damage, loc_store, dam_store

    end

function simulate_inner(sim_parameters_in)
"""
Simulate model, inner function
"""

    # Current state array
    states = sim_parameters_in.states
    # Exogenous variables
    exog_states = sim_parameters_in.exog_states
    # Model parameters
    mparams = sim_parameters_in.mparams
    # Approximation space
    space = sim_parameters_in.space
    # Boolean options
    opts = sim_parameters_in.opts
    # Coefficient vector
    c_store = sim_parameters_in.coefficients

    # Randomly sampled damage parameters
    sim_values = Vector{Array}(undef, 2)
    sim_values[1] = [sim_parameters_in.dam_exponent]
    sim_values[2] = sim_parameters_in.dam_coefficient

    if opts[:adapted]
        smax = sim_parameters_in.smax
        smin = sim_parameters_in.smin
    end

    # Initialize d_2 mean and fractional net output quadrature
    loc_next = Vector{Array}(undef, mparams[:sim_length])
    dam_next = Vector{Array}(undef, mparams[:sim_length])

    # Initialize control vector
    controls = Vector{Vector}(undef, mparams[:sim_length])

    # Initial control guess
    qiguess = [.2, exog_states[:Psi][1]/2]
    cost = 0.
    cons = 0.

    # Initialize investment rate
    inv_rate = Vector{Float64}(undef, mparams[:sim_length])

    # Initialize abatement rate
    mu = Vector{Float64}(undef, mparams[:sim_length])

    # Simulate forward
    for t in 1:mparams[:sim_length]

        if opts[:adapted]
            space = fundefn(:cheb, mparams[:approximation_level], smin[t + 1, :], smax[t + 1, :])
        end

        # Load coefficient vector for continuation value function
        space[:c_cont] = c_store[t + 1]

        # Load year
        space[:year] = t - 1
        mparams[:year] = t - 1

        # Time t exogenous variables
        value_vars = reduce_dict(exog_states, t)

        # Maximize Bellman
        results = maximize_bellman(stateVariables(vec(states[t, :]), qiguess, value_vars, mparams, space, opts, space, 0))

        # Pull out consumption
        cons = results[1][1]

        # Abatement cost
        cost = results[1][2]

        # Abatement rate
        mu[t] = (cost/value_vars[:Psi]).^(1/mparams[:a2])

        # Next period's guess
        qiguess = [max(1, cons), cost]
        qi = [cons, cost]
        v_out = results[2]

        # Store controls in vector
        controls[t] = [qi[1], mu[t]]

        # Store investment rate
        if opts[:learn_e]
            inv_rate[t] = -(qi[1] .+ (qi[2] - 1)*states[t, 1].^mparams[:kappa].*states[t, 5])./(states[t, 1].^mparams[:kappa].*states[t, 5])
        else
            inv_rate[t] = -(qi[1] .+ (qi[2] - 1)*states[t, 1].^mparams[:kappa].*states[t, 3])./(states[t, 1].^mparams[:kappa].*states[t, 3])
        end

        # Compute actual next state and distributions for the next state
        states[t + 1, :], loc_next[t], dam_next[t] = transitions(states[t, :], qi, value_vars, mparams, opts, sim_values[1][1], sim_values[2][t])

    end

    # Compute tax and perform decomposition
    if opts[:sim]
        if opts[:adapted]
            tax_parameters = taxParameters(states, controls, exog_states, mparams, space, opts, c_store, smax, smin, loc_next, dam_next)
            channels = compute_tax(tax_parameters)
        else
            tax_parameters = taxParameters(states, controls, exog_states, mparams, space, opts, c_store, [0. 0.; 0. 0.], [0. 0.; 0. 0.], loc_next, dam_next)
            channels = compute_tax(tax_parameters)
        end

        return controls, states, loc_next, dam_next, channels, inv_rate
    else
        return controls, states, loc_next, dam_next
    end

end

function simulate_exog_vars(mparams)

    # Time periods vector
    t = 0:mparams[:sim_length]

    # Non-industrial emissions
    exog_states = Dict{Symbol,Array}(:Bs => mparams[:B0]*exp.(mparams[:gB]*t))

    # Labor
    exog_states[:Lt] = mparams[:L0] .+ (mparams[:Linf]-mparams[:L0])*(1.0 .- exp.(-mparams[:dL]*t))

    # Labor growth
    exog_states[:gL] = mparams[:dL]./((mparams[:Linf]/(mparams[:Linf]-mparams[:L0]))*exp.(mparams[:dL]*t) .- 1)

    # Technological growth rate
    exog_states[:gA] = mparams[:gA0]*exp.(-mparams[:dA]*t)

    # Technology
    exog_states[:At] = mparams[:A0]*exp.( (mparams[:gA0]/mparams[:dA])*(1 .- exp.(-mparams[:dA]*t)))

    # Growth rate for exogenous growth in labor and productivity
    exog_states[:effkgrowth] = exp.(-(exog_states[:gA]+exog_states[:gL]))

    # Effective discount factor
    exog_states[:betaeff] = exp.((-mparams[:delta] .+ exog_states[:gA]*mparams[:rho] + exog_states[:gL]))

    # Exogenous decarbonization of production
    exog_states[:sigma] = mparams[:sigma0]*exp.((mparams[:gsigma0]/mparams[:dsigma])*(1 .- exp.(-mparams[:dsigma]*t)))
    exog_states[:emint] = exog_states[:sigma].*exog_states[:At].*exog_states[:Lt]

    # Abatement cost function coefficient
    exog_states[:Psi] = (mparams[:a0]*exog_states[:sigma]./mparams[:a2]).*(1 .- (1 .- exp.(mparams[:gPsi]*t))./mparams[:a1] )

    # External forcing
    exog_states[:EF] = mparams[:EF0] .+ 0.01*(mparams[:EF1] .- mparams[:EF0])*min.(t, 100)

    # External forcing in next year
    exog_states[:EF_next] = mparams[:EF0] .+ 0.01*(mparams[:EF1] .- mparams[:EF0])*min.(t .+ 1, 100)

    return exog_states
end


using Base.Sys: cpu_info
using Base.Threads
using ArgParse
using Dates
using JLD
using Random
using CellListsBenchmarks

# --- Arguments ---
s = ArgParseSettings()
@add_arg_table s begin
    "benchmark"
        help = "Choose one of : $(keys(benchmark_functions))"
        arg_type = String
        required = true
    "--ns"
        help = "String that is evaluates to `Vector{Int}` type. For example: [1, 10, 100:50:200...]"
        arg_type = String
        required = true
    "-d"
        help = "Dimension. Positive integer."
        arg_type = Int
        required = true
    "-r"
        help = "Radius. Positive float."
        arg_type = Float64
        required = true
    "--seed"
        default = 1
        arg_type = Int
    "--iterations", "-i"
        default = 1
        arg_type = Int
    "--seconds", "-s"
        default = 1.0
        arg_type = Float64
    "--dir"
        default = "output"
        arg_type = AbstractString
    "--float32"
        help = "Use Float32 insteads of Float64."
        action = :store_true
end
args = parse_args(s)

# --- Parameters ---
d = args["d"]
r = args["r"]
ns = Vector{Int}(eval(Meta.parse(args["ns"])))
seed = args["seed"]
iterations = args["iterations"]
seconds = args["seconds"]
directory = args["dir"]
benchmark = args["benchmark"]
float32 = args["float32"]

# --- Validate parameters ---
length(ns) < 1 && throw(DomainError(""))
d < 1 && throw(DomainError(""))
r ≤ 0 && throw(DomainError(""))
if float32
    r = Float32(r)
end

# --- Trials ---
@info versioninfo()
benchmarks = [Benchmark(benchmark, n, d, r, seed, iterations, seconds) for n in ns]

# --- IO ---
if !ispath(directory)
    mkpath(directory)
end

filepath = joinpath(directory, "n$(ns[end])-d$(d)-r$(r).jld")
@info "Saving results to $(filepath)"
JLD.save(filepath, "benchmarks", benchmarks)


"""
General electronic course-graining platform
"""
module FermiCG

#####################################
# External packages
#
using Compat
using HDF5
using KrylovKit
using LinearAlgebra
using NDTensors
using PackageCompiler
using Parameters
using Printf
using TimerOutputs
using BenchmarkTools
using OrderedCollections
using IterTools
# using Unicode
#
#####################################

#####################################
# Local Imports
#
include("Utils.jl")
include("hosvd.jl")
include("StringCI/StringCI.jl");
include("SymDenseMats.jl");
include("Solvers.jl");
include("Hamiltonians.jl");
include("Clusters.jl")
include("Indexing.jl")
include("States.jl")

include("FockSparse_ElementSparse.jl")
include("FockSparse_BlockSparse.jl")
include("FockSparse_BlockSparseTucker.jl")

include("ClusteredTerms.jl")
#include("ClusteredStates.jl")

include("tucker_inner.jl")
include("tucker_outer.jl")
include("bst.jl")

include("tpsci_inner.jl")
include("tpsci_matvec_thread.jl")
include("tpsci_outer.jl")
include("tpsci_helpers.jl")

include("dense_inner.jl")
include("dense_outer.jl")

include("CompressedTucker.jl")
include("CMFs.jl")
include("pyscf/PyscfFunctions.jl");
#
#####################################

export StringCI
export InCoreInts
export Molecule
export Atom
export Cluster
export ClusterBasis
export ClusteredState
export ClusterConfig
export FockConfig

end


function embed2linear(embed::Embedding)
    w = hcat(map(v -> v.data, embed.ws)...)
    outdim = size(w, 1)
    T = typeof(w[1])
    b = fill(T(0), outdim, 1)
    Linear(Merlin.Param(w), Merlin.Param(b))
end

function indmaxk{T}(vec::Array{T}, k::Int)
    arr = copy(vec)
    res = Int[]
    while k > 0
        idx = indmax(arr)
        push!(res, idx)
        arr[idx] = -10000000f0 #typemin(T)
        k -= 1
    end
    res
end

function readprobs(parser::DepParser, tagpath::AbstractString, conllpath::AbstractString, topk::Int)
    tags = map(chomp, open(readlines, tagpath))
    tag2id = Dict([tag => id for (id, tag) in enumerate(tags)])
    doc = Vector[]
    push!(doc, [])
    for line in open(readlines, conllpath)
        line = chomp(line)
        if isempty(line)
            push!(doc, [])
            continue
        end
        items = split(line, "\t")
        word = items[1]
        probs = map(v -> parse(Float32, v), items[2:end])
        # prob(NONE) == 0.0
        probs = [probs[tag2id[getkey(parser.tags, i)]] for i = 2:length(tags)+1]
        probs = convert(Vector{Float32}, probs)
        unshift!(probs, 0f0)
        if topk == 1
            probs = indmax(probs)
        elseif topk > 1
            topidx = indmaxk(probs, topk)
            probs = [i in topidx ? probs[i] : 0f0 for i = 1:length(probs)]
            probs = reshape(probs, length(probs), 1)
        else
            probs = reshape(probs, length(probs), 1)
        end
        push!(doc[end], (lowercase(word), probs))
    end
    if topk != 1
        vsize = size(doc[1][1][2])
        global roottoken = Token(2, zeros(Float32, vsize), 0, 1)
    end
    doc
end

function readconll_withprobs(parser, tagpath, conllpath, probpath; topk=1, train=true)
    conlldoc = readconll(parser, conllpath, train=train)
    conllprobs = readprobs(parser, tagpath, probpath, topk)
    @assert length(conlldoc) == length(conllprobs)
    for (sent, probs) in zip(conlldoc, conllprobs)
        @assert length(sent) == length(probs)
        for (token, prob) in zip(sent, probs)
            form = getkey(parser.words, token.word)
            token.tag = prob[2]
        end
    end
    conlldoc
end


function implicitUseLockModel(lockType,conf,data)

	Val = ""
	if lockType == "waitTime"
		Val = useLockModel([0.125 0.0001 conf], data, "TPCC").TimeSpentWaiting
	elseif lockType == "numberOfLocks"
		Val = useLockModel([0.125 0.0001 conf], data, "TPCC").LocksBeingHeld
	elseif lockType == "numberOfConflicts"
		Val = useLockModel([0.125 0.0001 conf], data, "TPCC").totalWaits
	else
		error(string("Invalid lockType:", lockType))
	end
	return Val
end


struct QNVars{T,Ty}
    d::T # search direction
    s::T # change in x
    y::Ty # change in successive gradients
end
function QNVars(x, g)
    QNVars(copy(g), copy(x), copy(x))
end
struct QNObjVars{T,Tf,Tn,Tb,Tpg}
    x::T
    F::Tf
    ∇f::Tn
    B::Tb
    Pg::Tpg
end
function preallocate_qn_caches(mstyle, x0)
    if mstyle === InPlace()
        # Maintain gradient and state pairs in QNVars
        cache = QNVars(x0, x0)
        return cache
    else
        return nothing
    end
end

function solve(
    problem::OptimizationProblem,
    x0,
    approach::LineSearch,
    options::OptimizationOptions,
    cache = preallocate_qn_caches(mstyle(problem), x0),
)
    _solve(mstyle(problem), problem, (x0, nothing), approach, options, cache)
end
function solve(
    problem::OptimizationProblem,
    s0::Tuple,
    approach::LineSearch,
    options::OptimizationOptions,
    cache = preallocate_qn_caches(mstyle(problem), first(s0)),
)
    _solve(mstyle(problem), problem, s0, approach, options, cache)
end

function _solve(
    mstyle,
    problem::OptimizationProblem,
    s0::Tuple,
    approach::LineSearch,
    options::OptimizationOptions,
    cache,
)
    t0 = time()

    #==============
         Setup
    ==============#
    x0, B0 = s0
    T = eltype(x0)

    objvars = prepare_variables(problem, approach, x0, copy(x0), B0)
    P = initial_preconditioner(approach, x0)

    f0, ∇f0 = objvars.fz, norm(objvars.∇fz, Inf) # use user norm

    if any(initial_converged(approach, objvars, ∇f0, options))
        x, fx, ∇fx, z, fz, ∇fz, B, Pg = objvars
        return ConvergenceInfo(
            approach,
            (
                P = P,
                B = B,
                ρs = norm(x .- z),
                ρx = norm(x),
                solution = z,
                fx = fx,
                minimum = fz,
                ∇fz = ∇fz,
                f0 = f0,
                ∇f0 = ∇f0,
                iter = 0,
                time = time() - t0,
            ),
            options,
        )
    end
    qnvars = QNVars(copy(objvars.∇fz), copy(objvars.∇fz), copy(objvars.∇fz))

    #==============================
             First iteration
    ==============================#
    objvars, P, qnvars = iterate(mstyle, qnvars, objvars, P, approach, problem, options)
    iter = 1
    # Check for gradient convergence
    is_converged = converged(approach, objvars, ∇f0, options)
    print_trace(approach, options, iter, t0, objvars)
    while iter < options.maxiter && !any(is_converged)
        iter += 1
        #==============================
                     iterate
        ==============================#
        objvars, P, qnvars =
            iterate(mstyle, qnvars, objvars, P, approach, problem, options, false)

        #==============================
                check convergence
        ==============================#
        is_converged = converged(approach, objvars, ∇f0, options)
        print_trace(approach, options, iter, t0, objvars)
    end
    x, fx, ∇fx, z, fz, ∇fz, B, Pg = objvars
    return ConvergenceInfo(
        approach,
        (
            P = P,
            B = B,
            ρs = norm(x .- z),
            ρx = norm(x),
            solution = z,
            fx = fx,
            minimum = fz,
            ∇fz = ∇fz,
            f0 = f0,
            ∇f0 = ∇f0,
            iter = iter,
            time = time() - t0,
        ),
        options,
    )
end
function print_trace(approach::LineSearch, options, iter, t0, objvars)
    if false
        println(
            @sprintf(
                "iter: %d   time: %.4f   obj: %.4e   ||∇f||: %.4e    α: %.4e",
                iter,
                time() - t0,
                objvars.fz,
                norm(objvars.∇fz, Inf),
                objvars.α
            )
        )
    end
end
function iterate(
    mstyle::InPlace,
    cache,
    objvars,
    P,
    approach::LineSearch,
    problem::OptimizationProblem,
    options::OptimizationOptions,
    is_first = nothing,
)
    # split up the approach into the hessian approximation scheme and line search
    x, fx, ∇fx, z, fz, ∇fz, B, Pg = objvars

    Tf = typeof(fx)
    scheme, linesearch = modelscheme(approach), algorithm(approach)
    y, d, s = cache.y, cache.d, cache.s

    # Move nexts into currs
    fx = fz
    copyto!(x, z)
    copyto!(∇fx, ∇fz)

    # Update preconditioner
    P = update_preconditioner(scheme, x, P)
    # Update current gradient and calculate the search direction
    d = find_direction!(d, B, P, ∇fx, scheme) # solve Bd = -∇fx
    # real is needed to convert complex dots to actually being real
    φ = _lineobjective(mstyle, problem, ∇fz, z, x, d, fx, real(dot(∇fx, d)))

    # Perform line search along d
    # Also returns final step vector and update the state
    α, f_α, ls_success = find_steplength(mstyle, linesearch, φ, Tf(1))

    @. s = α * d
    z = retract(problem, z, x, s)

    # Update approximation
    fz, ∇fz, B, s, y = update_obj!(problem, s, y, ∇fx, z, ∇fz, B, scheme, is_first)
    return (x = x, fx = fx, ∇fx = ∇fx, z = z, fz = fz, ∇fz = ∇fz, B = B, Pg = Pg, α = α),
    P,
    QNVars(d, s, y)
end
function print_trace(approach::LineSearch, options, iter, t0, objvars, Δ)
    if false
        println(
            @sprintf(
                "iter: %d   time: %f   f: %.4e   ||∇f||: %.4e    Δ: %.4e",
                iter,
                time() - t0,
                objvars.fz,
                norm(objvars.∇fz, Inf),
                Δ
            )
        )
    end
end

function iterate(
    mstyle::OutOfPlace,
    cache,
    objvars,
    P,
    approach::LineSearch,
    problem::OptimizationProblem,
    options::OptimizationOptions,
    is_first = nothing,
)
    # split up the approach into the hessian approximation scheme and line search
    x, fx, ∇fx, z, fz, ∇fz, B, Pg = objvars
    Tf = typeof(fx)
    scheme, linesearch = modelscheme(approach), algorithm(approach)
    # Move nexts into currs
    fx = fz
    x = copy(z)
    ∇fx = copy(∇fz)

    # Update preconditioner
    P = update_preconditioner(scheme, x, P)
    # Update current gradient and calculate the search direction
    d = find_direction(B, P, ∇fx, scheme) # solve Bd = -∇fx
    # real is needed to convert complex dots to actually being real
    φ = _lineobjective(mstyle, problem, ∇fz, z, x, d, fx, real(dot(∇fx, d)))

    # Perform line search along d
    α, f_α, ls_success = find_steplength(mstyle, linesearch, φ, Tf(1))

    # # Calculate final step vector and update the state
    s = @. α * d
    z = retract(problem, z, x, s)

    # Update approximation
    fz, ∇fz, B, s, y = update_obj(problem, s, ∇fx, z, ∇fz, B, scheme, is_first)

    return (x = x, fx = fx, ∇fx = ∇fx, z = z, fz = fz, ∇fz = ∇fz, B = B, Pg = Pg),
    P,
    QNVars(d, s, y)
end


#=
Copyright (c) 2018-2022 Chris Coey, Lea Kapelevich, and contributors

This Julia package Hypatia.jl is released under the MIT license; see LICENSE
file in the root directory or at https://github.com/chriscoey/Hypatia.jl
=#

insts = OrderedDict()
insts["minimal"] = [
    ((false, 1, 2, true, true),),
    ((false, 1, 2, false, true),),
    ((false, 1, 2, false, false),),
    ((false, 3, 1, false, false),),
    ((true, :abs1d, 1, true, true),),
    ((true, :absball2d, 1, false, true),),
    ((false, :motzkin, 3, true, true),),
    ((false, :motzkin_ball, 3, false, false),),
    ((false, :motzkin_ellipsoid, 3, false, true),),
    ]
insts["fast"] = [
    ((false, 1, 30, true, true),),
    ((false, 1, 30, false, true),),
    ((false, 1, 30, false, false),),
    ((false, 2, 8, true, true),),
    ((false, 3, 6, true, true),),
    ((false, 5, 3, true, true),),
    ((false, 10, 1, true, true),),
    ((false, 10, 1, false, true),),
    ((false, 10, 1, false, false),),
    ((false, 4, 4, true, true),),
    ((false, 4, 4, false, true),),
    ((false, 4, 4, false, false),),
    ((true, :abs1d, 3, true, true),),
    ((true, :absunit1d, 1, true, true),),
    ((true, :absunit1d, 3, true, true),),
    ((true, :negabsunit1d, 2, true, true),),
    ((true, :absball2d, 1, true, true),),
    ((true, :absbox2d, 2, true, true),),
    ((true, :negabsbox2d, 1, true, true),),
    ((true, :denseunit1d, 2, true, true),),
    ((true, :negabsunit1d, 2, false, true),),
    ((true, :absball2d, 1, false, true),),
    ((true, :negabsbox2d, 1, false, true),),
    ((true, :denseunit1d, 2, false, true),),
    ((false, :butcher, 2, true, true),),
    ((false, :caprasse, 4, true, true),),
    ((false, :goldsteinprice, 7, true, true),),
    ((false, :goldsteinprice_ball, 6, true, true),),
    ((false, :goldsteinprice_ellipsoid, 7, true, true),),
    ((false, :heart, 2, true, true),),
    ((false, :lotkavolterra, 3, true, true),),
    ((false, :magnetism7, 2, true, true),),
    ((false, :magnetism7_ball, 2, true, true),),
    ((false, :motzkin, 3, true, true),),
    ((false, :motzkin_ball, 3, true, true),),
    ((false, :motzkin_ellipsoid, 3, true, true),),
    ((false, :reactiondiffusion, 4, true, true),),
    ((false, :robinson, 8, true, true),),
    ((false, :robinson_ball, 8, true, true),),
    ((false, :rosenbrock, 5, true, true),),
    ((false, :rosenbrock_ball, 5, true, true),),
    ((false, :schwefel, 2, true, true),),
    ((false, :schwefel_ball, 2, true, true),),
    ((false, :lotkavolterra, 3, false, true),),
    ((false, :motzkin, 3, false, true),),
    ((false, :motzkin_ball, 3, false, true),),
    ((false, :schwefel, 2, false, true),),
    ((false, :lotkavolterra, 3, false, false),),
    ((false, :motzkin, 3, false, false),),
    ((false, :motzkin_ball, 3, false, false),),
    ]
return (PolyMinNative, insts)


module Actions
    import ..Wilsonloops:Wilson_loop_set,make_staples,Wilson_loop_set,
            make_cloverloops,Tensor_derivative_set, make_loops
    import ..SUN_generator:Generator
    import ..Rhmc:RHMC

    abstract type GaugeActionParam end

    abstract type FermiActionParam end

    abstract type SmearingParam end

    struct SmearingParam_nosmearing <: SmearingParam
    end

    abstract type SmearingParam_single <: SmearingParam
    end

    abstract type SmearingParam_multi <: SmearingParam
    end

    mutable struct SmearingParam_stout <: SmearingParam_single
        staples_for_stout::Array{Array{Wilson_loop_set,1},1}
        tensor_derivative::Array{Tensor_derivative_set,1}
        staples_for_stout_dag::Array{Array{Wilson_loop_set,1},1}
        tensor_derivative_dag::Array{Tensor_derivative_set,1}
        ρs::Array{Float64,1}
        #ρs::Array{Float64,1}
    end

    mutable struct SmearingParam_stout_multi <: SmearingParam_multi
        staples_for_stout::Array{Array{Wilson_loop_set,1},1}
        tensor_derivative::Array{Tensor_derivative_set,1}
        staples_for_stout_dag::Array{Array{Wilson_loop_set,1},1}
        tensor_derivative_dag::Array{Tensor_derivative_set,1}
        ρs::Array{Array{Float64,1},1}
        #ρs::Array{Float64,1}
    end

    const Nosmearing = SmearingParam_nosmearing
    const Stout = SmearingParam_stout

    mutable struct GaugeActionParam_standard <: GaugeActionParam
        β::Float64
        NTRACE::Int64
    end

    mutable struct GaugeActionParam_autogenerator <: GaugeActionParam
        βs::Array{Float64,1}
        β::Float64
        numactions::Int64
        NTRACE::Int64
        loops::Array{Wilson_loop_set,1}
        staples::Array{Array{Wilson_loop_set,1},1}
        couplinglist::Array{String,1}
    end

    """
    ```Setup_Gauge_action(β;actiontype="standard")````
    - β: Coupling value

    Set up the information about the Gauge action.
    You can set the coupling value β.

    Now only SU(3) case is supported.
    """
    function Setup_Gauge_action(β;actiontype="standard")
        if actiontype == "standard"
            NTRACE =3
            return GaugeActionParam_standard(β,NTRACE)
        end
    end

    Base.@kwdef struct FermiActionParam_Wilson <: FermiActionParam
        hop::Float64  = 0.141139#Hopping parameter
        r::Float64  = 1#Wilson term
        #eps::Float64 = 1e-8
        eps::Float64 = 1e-19
        Dirac_operator::String = "Wilson"
        MaxCGstep::Int64 = 3000
        quench::Bool = false
        smearing::SmearingParam

        function FermiActionParam_Wilson(hop,r,eps,Dirac_operator,MaxCGstep,quench
            ;smearingparameters = "nothing",
            loops_list = nothing,
            coefficients  = nothing,
            numlayers = 1,
            L = nothing)

            smearing = construct_smearing(smearingparameters,loops_list,L,coefficients,numlayers)
            #smearing = Nosmearing()
            #if smearingparameters == nothing
            #    smearing = Nosmearing()
            #end
            return new(hop,r,eps,Dirac_operator,MaxCGstep,quench,smearing)
        end

    end

    const Clover_coefficient  = 1.5612

    Base.@kwdef struct FermiActionParam_WilsonClover <: FermiActionParam
        hop::Float64  = 0.141139#Hopping parameter
        r::Float64  = 1#Wilson term
        #eps::Float64 = 1e-8
        eps::Float64 = 1e-19
        Dirac_operator::String = "WilsonClover"
        MaxCGstep::Int64 = 3000
        Clover_coefficient::Float64 = Clover_coefficient
        #CloverFμν::Array{ComplexF64,4}
        internal_flags::Array{Bool,1}
        inn_table::Array{Int64,3}
        _ftmp_vectors::Array{Array{ComplexF64,3},1}
        _is1::Array{Int64,1}
        _is2::Array{Int64,1}
        quench::Bool = false
        SUNgenerator::Union{Nothing,Generator}
        _cloverloops::Array{Wilson_loop_set,2}
        smearing::SmearingParam

        function FermiActionParam_WilsonClover(hop,r,eps,Dirac_operator,MaxCGstep,
            Clover_coefficient,internal_flags,inn_table,_ftmp_vectors,
            _is1,_is2,
            quench, SUNgenerator,_cloverloops;smearingparameters = "nothing",
            loops_list = nothing,
            coefficients  = nothing,
            numlayers = 1,
            L = nothing)

            smearing = construct_smearing(smearingparameters,loops_list,L,coefficients,numlayers)

            #if smearingparameters == nothing
            #    smearing = Nosmearing()
            #end
            #smearing = Nosmearing()

            return new(hop,r,eps,Dirac_operator,MaxCGstep,
            Clover_coefficient,internal_flags,inn_table,_ftmp_vectors,
            _is1,_is2,
            quench, SUNgenerator,_cloverloops,smearing)
        end
    end

    function construct_smearing(smearingparameters,loops_list,L,coefficients,numlayers)
        if smearingparameters == "nothing"
            smearing = Nosmearing()
        else
            @assert loops_list != nothing "loops should be put if you want to use smearing schemes"
            loops = make_loops(loops_list,L)

            @assert coefficients != nothing "coefficients should be put if you want to use smearing schemes"
            println("stout smearing will be used")
            if numlayers == 1
                smearing = SmearingParam_stout(loops,coefficients)
            else
                numloops = length(loops)
                smearing_single = SmearingParam_stout(loops,rand(numloops))
                smearing = SmearingParam_stout_multi(smearing_single.staples_for_stout,
                    smearing_single.tensor_derivative,
                    smearing_single.staples_for_stout_dag,
                    smearing_single.tensor_derivative_dag,
                    coefficients
                    )

            end
            #println(smearing )
            #exit()
        end
        return smearing
    end

    Base.@kwdef struct FermiActionParam_Staggered <: FermiActionParam
        mass::Float64 = 0.5
        eps::Float64 = 1e-19
        Dirac_operator::String = "Staggered"
        MaxCGstep::Int64 = 3000
        quench::Bool = false
        Nf::Int8 = 4
        rhmc_action::Union{Nothing,RHMC}
        rhmc_MD::Union{Nothing,RHMC}
        smearing::SmearingParam

        function FermiActionParam_Staggered(
            mass,
            eps,
            Dirac_operator,
            MaxCGstep,
            quench,
            Nf;smearingparameters = "nothing",
            loops_list = nothing,
            coefficients  = nothing,
            numlayers = 1,
            L = nothing
            ) where T <: Real

            if Nf == 4 || Nf == 8 # 8 flavors if phi (Mdag M)^{-1} phi
                rhmc_action = nothing
                rhmc_MD = nothing
            else
                #for action: r_action
                #Nf = 8 -> alpha = 1 -> power x^{1/2}
                #Nf = 2 -> alpha = 1/4 -> power x^1/8
                #Nf = 1 -> alpha = 1/8  -> power x^1/16
                order = Nf //16

                rhmc_action = RHMC(order,n=15)

                #for MD: r_MD
                #Nf = 8 -> alpha = 1 -> power x^{1}
                #Nf = 2 -> alpha = 1/4 -> power x^1/4
                #Nf = 1 -> alpha = 1/8  -> power x^1/8
                order = Nf // 8
                #rhmcorder = 8 ÷ Nf
                rhmc_MD = RHMC(order,n=10)
            end

            smearing = construct_smearing(smearingparameters,loops_list,L,coefficients,numlayers)

            return new(
            mass,
            eps,
            Dirac_operator,
            MaxCGstep,
            quench,
            Nf,
            rhmc_action,
            rhmc_MD,
            smearing
            )
        end
    end

    function GaugeActionParam_autogenerator(βs,loops,NC,couplinglist)
        @assert length(βs) == length(loops) "The number of loops should be the number of βs!"
        numactions = length(loops)
        β = βs[1]
        staples = Array{Array{Wilson_loop_set,1},1}(undef,numactions)
        for (i,loop) in enumerate(loops)
            staples[i] = make_staples(loop)
            #=
            println("$i-th action")
            display(loop)
            println("$i-th action's staple")
            for mu=1:4
                println("$mu -direction")
                display(staples[i][mu])
            end
            =#
        end

        return GaugeActionParam_autogenerator(βs,β,numactions,NC,loops,staples,couplinglist)
    end

    Base.@kwdef struct FermiActionParam_Domainwall <: FermiActionParam
        N5::Int64
        r::Float64 = 1
        M::Float64 = -1
        m::Float64
        ωs::Array{Float64,1}
        b::Float64
        c::Float64
        eps::Float64 = 1e-19
        Dirac_operator::String = "Domainwall"
        MaxCGstep::Int64 = 3000
        quench::Bool = false
        smearing::SmearingParam

        function FermiActionParam_Domainwall(N5,r,M,m,ωs,b,c,eps,Dirac_operator,MaxCGstep,quench
            ;smearingparameters = "nothing",
            loops_list = nothing,
            coefficients  = nothing,
            numlayers = 1,
            L = nothing)

            smearing = construct_smearing(smearingparameters,loops_list,L,coefficients,numlayers)
            #smearing = Nosmearing()
            #if smearingparameters == nothing
            #    smearing = Nosmearing()
            #end
            return new(N5,r,M,m,ωs,b,c,eps,Dirac_operator,MaxCGstep,quench,smearing)
        end

        function FermiActionParam_Domainwall(N5,r,M,m,eps,Dirac_operator,MaxCGstep,quench
            ;smearingparameters = "nothing",
            loops_list = nothing,
            coefficients  = nothing,
            numlayers = 1,
            L = nothing)

            b = 1
            c = 1
            ωs = ones(Float64,Ns)

            smearing = construct_smearing(smearingparameters,loops_list,L,coefficients,numlayers)
            #smearing = Nosmearing()
            #if smearingparameters == nothing
            #    smearing = Nosmearing()
            #end
            return new(N5,r,M,m,ωs,b,c,eps,Dirac_operator,MaxCGstep,quench,smearing)
        end

    end

    function show_parameters_action(fparam::FermiActionParam_Staggered)
        println("#--------------------------------------------------")
        println("#Fermion Action parameters")
        println("#type: ",typeof(fparam))
        println("mass = ",fparam.mass)
        println("eps = ",fparam.eps)
        println("Dirac_operator = ",fparam.Dirac_operator)
        println("MaxCGstep = ",fparam.MaxCGstep)
        println("quench = ",fparam.quench)
        println("Nf = ",fparam.Nf)
        println("#--------------------------------------------------")
    end

    function show_parameters_action(fparam::FermiActionParam_Wilson)
        println("#--------------------------------------------------")
        println("#Fermion Action parameters")
        println("#type: ",typeof(fparam))
        println("hop = ",fparam.hop)
        println("r = ",fparam.r)
        println("eps = ",fparam.eps)
        println("Dirac_operator = ",fparam.Dirac_operator)
        println("MaxCGstep = ",fparam.MaxCGstep)
        println("quench = ",fparam.quench)
        println("#--------------------------------------------------")
    end

    function show_parameters_action(fparam::FermiActionParam_WilsonClover)
        println("#--------------------------------------------------")
        println("#Fermion Action parameters")
        println("#type: ",typeof(fparam))
        println("hop = ",fparam.hop)
        println("r = ",fparam.r)
        println("eps = ",fparam.eps)
        println("Dirac_operator = ",fparam.Dirac_operator)
        println("MaxCGstep = ",fparam.MaxCGstep)
        println("Clover_coefficient = ",fparam.Clover_coefficient)
        println("quench = ",fparam.quench)
        println("#--------------------------------------------------")
    end

    function show_parameters_action(gparam::GaugeActionParam_standard)
        println("#--------------------------------------------------")
        println("#Gauge Action parameters")
        println("#type: ",typeof(gparam))
        println("β = ",gparam.β)
        println("NC = ",gparam.NTRACE)
        println("#--------------------------------------------------")
    end

    function show_parameters_action(gparam::GaugeActionParam_autogenerator)
        println("#--------------------------------------------------")
        println("#Gauge Action parameters")
        println("#type: ",typeof(gparam))
        println("#Num. of action terms: ",gparam.numactions)
        println("#coupling βs = ",gparam.βs)
        #=
        println("#actions that we consider: ")
        for (i,loop) in enumerate(gparam.loops)
            println("#------------------------------")
            println("#$i-th action term consists of")
            display(loop)
            println("#------------------------------")
        end
        =#
        println("NC = ",gparam.NTRACE)
        println("#--------------------------------------------------")
    end

    function FermiActionParam_WilsonClover(hop,r,eps,MaxCGstep,NV,Clover_coefficient,NC;quench=false)
        #CloverFμν = zeros(ComplexF64,3,3,NV,6)
        inn_table= zeros(Int64,NV,4,2)
        internal_flags = zeros(Bool,2)
        _ftmp_vectors = Array{Array{ComplexF64,3},1}(undef,6)
        _is1 = zeros(Int64,NV)
        _is2 = zeros(Int64,NV)
        for i=1:6
            _ftmp_vectors[i] = zeros(ComplexF64,3,NV,4)
        end
        #return FermiActionParam_WilsonClover(hop,r,eps,"WilsonClover",MaxCGstep,Clover_coefficient,CloverFμν,inn_table,internal_flags,_ftmp_vectors,_is1,_is2,quench)
        if NC ≥ 4
            SUNgenerator = Generator(NC)
        else
            SUNgenerator = nothing
        end

        _cloverloops = Array{Wilson_loop_set,2}(undef,3,4)
        for μ=1:3
            for ν=μ+1:4
                _cloverloops[μ,ν] = make_cloverloops(μ,ν)
            end
        end

        return FermiActionParam_WilsonClover(hop,r,eps,"WilsonClover",MaxCGstep,Clover_coefficient,inn_table,internal_flags,_ftmp_vectors,_is1,_is2,quench,
                        SUNgenerator,_cloverloops)
    end

    function FermiActionParam_Wilson(hop,r,eps,MaxCGstep;quench=false)
        return FermiActionParam_Wilson(hop,r,eps,"Wilson",MaxCGstep,quench)
    end

    function FermiActionParam_Wilson(hop,r,eps,Dirac,MaxCGstep;quench=false)
        return FermiActionParam_Wilson(hop,r,eps,Dirac,MaxCGstep,quench)
    end

    """
    ```Setup_Fermi_action(Dirac_operator= "Wilson")```

    Set up the information about the Fermion action.

    Now only WilsonFermion case is supported.

    # For example
    ```julia
        fparam = Setup_Fermi_action()
    ```

    The default values are

    ```julia
    hop::Float64  = 0.141139
    r::Float64  = 1
    eps::Float64 = 1e-19
    Dirac_operator::String = "Wilson"
    MaxCGstep::Int64 = 3000
    ```

    - hop : hopping parameter
    - r : Wilson term
    - eps : convergence criteria in the CG method
    - MaxCGstep : maximum number of the CG steps

    If you want to change the parameters for the Wilson Fermions,
    please do as follows.

    ```julia
        fparam = FermiActionParam_Wilson(hop,r,eps,MaxCGstep)
    ```

    """
    function Setup_Fermi_action(Dirac_operator= "Wilson")
        if Dirac_operator == "Wilson"
            return FermiActionParam_Wilson()
        end
    end

    function SmearingParam_stout(loops_smearing,ρs)
        num = length(loops_smearing)
        @assert num == length(ρs) "number of ρ elements in stout smearing scheme should be $num. Now $(length(ρs))"
        staplesforsmear_set = Array{Wilson_loop_set,1}[]
        staplesforsmear_dag_set = Array{Wilson_loop_set,1}[]
        println("staple for stout smearing")

        tensor_derivative = Array{Tensor_derivative_set,1}(undef,num)
        tensor_derivative_dag = Array{Tensor_derivative_set,1}(undef,num)

        for i=1:num
            loop_smearing = loops_smearing[i]

            staplesforsmear = Array{Wilson_loop_set,1}(undef,4)
            staplesforsmear_dag = Array{Wilson_loop_set,1}(undef,4)

            staple = make_staples(loop_smearing)

            for μ=1:4
                staplesforsmear_dag[μ] = staple[μ]##make_plaq_staple(μ)
                staplesforsmear[μ] = staplesforsmear_dag[μ]'
                #println("$μ -direction")
                #display(staplesforsmear[μ])
                #staplesforsmear[μ] = make_plaq_staple(μ)
                #staplesforsmear_dag[μ] = staplesforsmear[μ]'
                #println("dagger: $μ -direction")
                #display(staplesforsmear_dag[μ])
            end
            tensor_derivative[i] = Tensor_derivative_set(staplesforsmear)
            tensor_derivative_dag[i] = Tensor_derivative_set(staplesforsmear_dag)

            push!(staplesforsmear_set,staplesforsmear )
            push!(staplesforsmear_dag_set,staplesforsmear_dag)

        end

        return SmearingParam_stout(
            staplesforsmear_set,
            tensor_derivative,
            staplesforsmear_dag_set,
            tensor_derivative_dag,
            ρs
            )
    end

end