abstract type _AbstractFunction end

"""
    _Operation

An enum representing different node types in a symbolic expression graph.

 * `_OP_COEFFICIENT`: a numerical `Float64`
 * `_OP_VARIABLE`: a decision variable
 * `_OP_OPERATION`: a function call
 * `_OP_INTEGER_EXPONENT`: a special case for handling integer polynomials such
   as `x^2`, since the `2.0` is usually structural rather than a coefficient
   that differs between expressions.
"""
@enum(
    _Operation,
    _OP_COEFFICIENT,
    _OP_VARIABLE,
    _OP_OPERATION,
    _OP_INTEGER_EXPONENT,
)

"""
    _Node

A type to represent a node in the symbolic expression graph.

!!! warning
    `_Node`s can only be used in conjunction with `_Function`.

`_Node` has the following fields:

 * `.head::_Operation`: this describes the node type
 * `.hash::UInt64`: a hash of the expression tree rooted at this node. See the
   Fast hashing section for more details.
 * `.index::Int` : interpretation depends on `.head`. If head is:
   * `_OP_COEFFICIENT`, the index into `f.data`
   * `_OP_VARIABLE`, the index into `f.ordered_variables`
   * `_OP_INTEGER_EXPONENT`, the integer exponent `children[1]^index`
 * `.operation::Symbol` : the symbolic name of the function call, if `.head` is
   `_OP_OPERATION`, otherwise this can be ignored.
 * `.children::Union{Nothing,Vector{_Node}}` : a vector of children of the node,
   if `.head` is `_OP_OPERATION`, otherwise `nothing`.

## Fast hashing

The expression graph is structured as a Merkle tree (a.k.a. a hash tree). That
is, each node contains a field `.hash`, which is the hash of the node's
operation with the hash of its children. Since children are also nodes, hashing
a child is an O(1) lookup of `child.hash`. Moreover, two expressions are
structurally identical if the have the same hash when converted into `_Node`
form.

## Other considerations

Through experience, the representation of the symbolic expression graph is not
the bottleneck in a nonlinear optimization model. Therefore, we choose the
somewhat inefficient storage rpresentation of
`children::Union{Nothing,Vector{_Node}}` for the list of children. This results
in a GC tracked object for each node that has children. If memory issues are
identified in future, changing how we represent the expression graph is one
promising avenue of investigation.
"""
struct _Node
    head::_Operation
    hash::UInt
    index::Int
    operation::Symbol
    children::Union{Nothing,Vector{_Node}}

    function _Node(f::_AbstractFunction, coefficient::Float64)
        # Store the coefficient
        push!(f.data, coefficient)
        # Compute the hash of the node. Because all coefficients are alike, this
        # is just the hash of `_OP_COEFFICIENT`.
        h = hash(_OP_COEFFICIENT)
        # Index is the length of `f.data` when we need to look it up again.
        index = length(f.data)
        return new(_OP_COEFFICIENT, h, index, :NONE, nothing)
    end
    function _Node(f::_AbstractFunction, variable::MOI.VariableIndex)
        # We need to convert the variable to the order in which it will appear
        # in .ordered_variables. Currently, the order is stored in
        # `f.variables`...
        lookup = f.variables::Dict{MOI.VariableIndex,Int}
        # And if the variable hasn't been seen yet, add a new index
        index = get!(lookup, variable, length(lookup) + 1)
        # The hash of this node depends on the variable and it's position in
        # ordered_variables.
        h = hash(_OP_VARIABLE, hash(index))
        return new(_OP_VARIABLE, h, index, :NONE, nothing)
    end
    function _Node(::_AbstractFunction, operation::Symbol, children::_Node...)
        # Okay, a function call. The hash depends on the operation and the
        # children.
        h = hash(operation, _hash(children...))
        return new(_OP_OPERATION, h, 0, operation, collect(children))
    end
    # Special cased performance optimization: x^N where N::Int is a common
    # operation that would otherwise get re-written to x[i]^p[j]. Doing so
    # disables a lot of potential optimizations, so it's worth special-casing
    # this. Other operations aren't as important.
    function _Node(::_AbstractFunction, ::typeof(^), x::_Node, N::Int)
        # The hash depends on ^, as well as the child node `x` and the
        # exponent `N`
        h = hash(_OP_INTEGER_EXPONENT, hash(_hash(x), hash(N)))
        return new(_OP_INTEGER_EXPONENT, h, N, :^, [x])
    end
end

# The hash of a child is an O(1) lookup
_hash(child::_Node) = child.hash

# Recursively has nodes together
_hash(a::_Node, args::_Node...) = hash(a.hash, _hash(args...))

"""
    _Function(expr::Expr)

This function parses the Julia expression `expr` into a `_Function`.

`_Function` has the following fields:

 * `ordered_variables`: a vector containing the `.value` indices of each
   variable in the order that they are discovered in the expression tree.
 * `coefficients::Vector{Float64}`: a vector to store the numerical input values
   of `x`.
 * `data::Vector{Float64}`: the list of numerical coefficients that appear in
   `expr`
 * `expr::_Node`: the symbolic representation of `expr`.

For more information on how to interpret the fields, see `_Node`.
"""
mutable struct _Function <: _AbstractFunction
    ordered_variables::Vector{Int}
    coefficients::Vector{Float64}
    data::Vector{Float64}
    expr::_Node
    # A field that is only used when constructing the _Function
    variables::Union{Nothing,Dict{MOI.VariableIndex,Int}}
    function _Function(expr::Expr)
        f = new()
        f.variables = Dict{MOI.VariableIndex,Int}()
        f.data = Float64[]
        f.expr = _Node(f, expr)
        f.coefficients = zeros(length(f.variables))
        f.ordered_variables = zeros(Int, length(f.variables))
        for (k, v) in f.variables
            f.ordered_variables[v] = k.value
        end
        f.variables = nothing
        return f
    end
end

_is_integer(::Any) = false
_is_integer(x::Real) = Base.isinteger(x)

"""
    _Node(f::_Function, expr::Expr)

Parse `expr` into `f` and return a `_Node`.

!!! warning
    This function gets called recursively.
"""
function _Node(f::_Function, expr::Expr)
    if isexpr(expr, :call)
        # Performance optimization: most calls will be unary or binary
        # operators. Therefore, we can specialize an if-statement to handle the
        # common cases without needing to splat.
        if length(expr.args) == 2
            return _Node(f, expr.args[1], _Node(f, expr.args[2]))
        elseif length(expr.args) == 3
            # Special cased performance optimization for _OP_INTEGER_EXPONENT
            if expr.args[1] == :^ && _is_integer(expr.args[3])
                return _Node(f, ^, _Node(f, expr.args[2]), Int(expr.args[3]))
            end
            return _Node(
                f,
                expr.args[1],
                _Node(f, expr.args[2]),
                _Node(f, expr.args[3]),
            )
        else
            nodes = (_Node(f, expr.args[i]) for i in 2:length(expr.args))
            return _Node(f, expr.args[1], nodes...)
        end
    else
        @assert isexpr(expr, :ref)
        @assert expr.args[1] == :x
        return _Node(f, expr.args[2])
    end
end

struct _SymbolicsFunction{F,G,H}
    f::F
    ∇f::G
    ∇²f::H
    g::Vector{Float64}
    H::Vector{Float64}
    H_structure::Vector{Tuple{Int,Int}}
end

"""
    _expr_to_symbolics(expr::_Node, p, x)

Convert a `_Node` into a `Symbolics.jl` expression via operator overloading.
"""
function _expr_to_symbolics(expr::_Node, p, x)
    if expr.head == _OP_COEFFICIENT
        return p[expr.index]
    elseif expr.head == _OP_VARIABLE
        return x[expr.index]
    elseif expr.head == _OP_OPERATION
        # TODO(odow): improve this lookup of expr.operation to function.
        f = getfield(Base, expr.operation)
        return f((_expr_to_symbolics(c, p, x) for c in expr.children)...)
    else
        @assert expr.head == _OP_INTEGER_EXPONENT
        return _expr_to_symbolics(expr.children[1], p, x)^expr.index
    end
end

function _SymbolicsFunction(f::_Function, features::Vector{Symbol})
    d, n = length(f.data), length(f.ordered_variables)
    Symbolics.@variables(p[1:d], x[1:n])
    p, x = collect(p), collect(x)
    f_expr = _expr_to_symbolics(f.expr, p, x)
    f = Symbolics.build_function(f_expr, p, x; expression = Val{false})
    if :Jac in features || :Grad in features
        ∇f_expr = Symbolics.gradient(f_expr, x)
        _, g! = Symbolics.build_function(∇f_expr, p, x; expression = Val{false})
        g_cache = zeros(length(x))
    else
        g!, g_cache = nothing, Float64[]
    end
    if :Hess in features
        ∇²f_expr_square = Symbolics.sparsejacobian(∇f_expr, x)
        # ∇²f_expr_square is sparse but it is also symmetrical. MathOptInterface
        # needs only the lower-triangular (technically, it doesn't matter which
        # triangle, but let's choose the lower-triangular for simplicity).
        I, J, V = SparseArrays.findnz(∇²f_expr_square)
        # Rather than using a SparseArray as storage, convert to MOI's
        # ((i, j), v) datastructure. This simplifies
        # `hessian_lagrangian_structure` calls later.
        ∇²f_expr = [V[i] for i in 1:length(I) if I[i] >= J[i]]
        ∇²f_structure = [(I[i], J[i]) for i in 1:length(I) if I[i] >= J[i]]
        _, h! =
            Symbolics.build_function(∇²f_expr, p, x; expression = Val{false})
        h_cache = zeros(length(∇²f_structure))
    else
        h!, h_cache, ∇²f_structure = nothing, Float64[], Tuple{Int,Int}[]
    end
    return _SymbolicsFunction(f, g!, h!, g_cache, h_cache, ∇²f_structure)
end

"""
    AbstractNonlinearOracleBackend

An abstract type to help dispatch on different implementations of the function
evaluations.
"""
abstract type AbstractNonlinearOracleBackend end

"""
    DefaultBackend() <: AbstractNonlinearOracleBackend

A simple implementation that loops over the different constraints.
"""
struct DefaultBackend <: AbstractNonlinearOracleBackend end

mutable struct _NonlinearOracle{B} <: MOI.AbstractNLPEvaluator
    backend::B
    objective::Union{Nothing,_Function}
    constraints::Vector{_Function}
    symbolic_functions::Dict{UInt,_SymbolicsFunction}
    hessian_sparsity_map::Vector{Int}
    eval_objective_timer::Float64
    eval_objective_gradient_timer::Float64
    eval_constraint_timer::Float64
    eval_constraint_jacobian_timer::Float64
    eval_hessian_lagrangian_timer::Float64
    function _NonlinearOracle(backend::B, objective, constraints) where {B}
        return new{B}(
            backend,
            objective,
            constraints,
            Dict{UInt,_SymbolicsFunction}(),
            Int[],
            0.0,
            0.0,
            0.0,
            0.0,
            0.0,
        )
    end
end

function MOI.initialize(
    ::_NonlinearOracle,
    ::AbstractNonlinearOracleBackend,
    ::Vector{Symbol},
)
    return
end

function MOI.initialize(oracle::_NonlinearOracle, features::Vector{Symbol})
    oracle.eval_constraint_timer = 0.0
    oracle.eval_constraint_jacobian_timer = 0.0
    oracle.eval_hessian_lagrangian_timer = 0.0
    hashes = map(f -> f.expr.hash, oracle.constraints)
    for h in unique(hashes)
        f = oracle.constraints[findfirst(isequal(h), hashes)]
        oracle.symbolic_functions[h] = _SymbolicsFunction(f, features)
    end
    if oracle.objective !== nothing
        f = oracle.objective
        h = f.expr.hash
        if !haskey(oracle.symbolic_functions, h)
            oracle.symbolic_functions[h] = _SymbolicsFunction(f, features)
        end
    end
    MOI.initialize(oracle, oracle.backend, features)
    return
end

MOI.features_available(::_NonlinearOracle) = [:Grad, :Jac, :Hess]

function _update_coefficients(f::_Function, x::AbstractVector{Float64})
    for (i, v) in enumerate(f.ordered_variables)
        @inbounds f.coefficients[i] = x[v]
    end
    return
end

function _eval_function(
    oracle::_NonlinearOracle,
    func::_Function,
    x::AbstractVector{Float64},
)
    _update_coefficients(func, x)
    @inbounds f = oracle.symbolic_functions[func.expr.hash]
    return f.f(func.data, func.coefficients)::Float64
end

function _eval_gradient(
    oracle::_NonlinearOracle,
    func::_Function,
    x::AbstractVector{Float64},
)
    _update_coefficients(func, x)
    @inbounds f = oracle.symbolic_functions[func.expr.hash]
    f.∇f(f.g, func.data, func.coefficients)
    return f.g
end

function _eval_hessian(
    oracle::_NonlinearOracle,
    func::_Function,
    x::AbstractVector{Float64},
)
    _update_coefficients(func, x)
    @inbounds f = oracle.symbolic_functions[func.expr.hash]
    f.∇²f(f.H, func.data, func.coefficients)
    return f.H
end

function MOI.eval_objective(
    oracle::_NonlinearOracle,
    x::AbstractVector{Float64},
)
    start = time()
    @assert oracle.objective !== nothing
    objective_value = _eval_function(oracle, oracle.objective, x)
    oracle.eval_objective_timer += time() - start
    return objective_value
end

function MOI.eval_objective_gradient(
    oracle::_NonlinearOracle,
    g::AbstractVector{Float64},
    x::AbstractVector{Float64},
)
    start = time()
    @assert oracle.objective !== nothing
    ∇f = _eval_gradient(oracle, oracle.objective, x)
    fill!(g, 0.0)
    for (i, v) in enumerate(oracle.objective.ordered_variables)
        @inbounds g[v] = ∇f[i]
    end
    oracle.eval_objective_gradient_timer += time() - start
    return
end

function MOI.eval_constraint(
    oracle::_NonlinearOracle,
    g::AbstractVector{Float64},
    x::AbstractVector{Float64},
)
    start = time()
    for (row, c) in enumerate(oracle.constraints)
        g[row] = _eval_function(oracle, c, x)
    end
    oracle.eval_constraint_timer += time() - start
    return
end

function MOI.jacobian_structure(oracle::_NonlinearOracle)
    structure = Tuple{Int,Int}[]
    for (row, c) in enumerate(oracle.constraints)
        for col in c.ordered_variables
            push!(structure, (row, col))
        end
    end
    return structure
end

function MOI.eval_constraint_jacobian(
    oracle::_NonlinearOracle,
    J::AbstractVector{Float64},
    x::AbstractVector{Float64},
)
    start = time()
    k = 1
    for c in oracle.constraints
        g = _eval_gradient(oracle, c, x)
        for gi in g
            J[k] = gi
            k += 1
        end
    end
    oracle.eval_constraint_jacobian_timer += time() - start
    return
end

function _hessian_lagrangian_structure(
    ::Vector{Tuple{Int,Int}},
    ::_NonlinearOracle,
    ::Any,
    ::Nothing,
)
    return
end

function _hessian_lagrangian_structure(
    structure::Vector{Tuple{Int,Int}},
    oracle::_NonlinearOracle,
    map::Dict{Tuple{Int,Int},Int},
    c::_Function,
)
    f = oracle.symbolic_functions[c.expr.hash]
    for (i, j) in f.H_structure
        row, col = c.ordered_variables[i], c.ordered_variables[j]
        row_col = row >= col ? (row, col) : (col, row)
        index = get(map, row_col, nothing)
        if index === nothing
            push!(structure, row_col)
            map[row_col] = length(structure)
            push!(oracle.hessian_sparsity_map, length(structure))
        else
            push!(oracle.hessian_sparsity_map, index::Int)
        end
    end
    return
end

function MOI.hessian_lagrangian_structure(oracle::_NonlinearOracle)
    structure = Tuple{Int,Int}[]
    map = Dict{Tuple{Int,Int},Int}()
    _hessian_lagrangian_structure(structure, oracle, map, oracle.objective)
    for c in oracle.constraints
        _hessian_lagrangian_structure(structure, oracle, map, c)
    end
    return structure
end

function MOI.eval_hessian_lagrangian(
    oracle::_NonlinearOracle,
    H::AbstractVector{Float64},
    x::AbstractVector{Float64},
    σ::Float64,
    μ::AbstractVector{Float64},
)
    fill!(H, 0.0)
    start = time()
    k = 1
    if oracle.objective !== nothing && !iszero(σ)
        h = _eval_hessian(oracle, oracle.objective, x)
        for (i, hi) in enumerate(h)
            @inbounds H[oracle.hessian_sparsity_map[i]] += σ * hi
        end
        k += length(h)
    end
    for (μi, constraint) in zip(μ, oracle.constraints)
        if iszero(μi)
            continue
        end
        h = _eval_hessian(oracle, constraint, x)
        for nzval in h
            @inbounds H[oracle.hessian_sparsity_map[k]] += μi * nzval
            k += 1
        end
    end
    oracle.eval_hessian_lagrangian_timer += time() - start
    return
end

function _nonlinear_constraint(expr::Expr)
    lower, upper, body = -Inf, Inf, :()
    if isexpr(expr, :call, 3)
        if expr.args[1] == :(>=)
            lower, upper, body = expr.args[1], expr.args[5], expr.args[3]
        elseif expr.args[1] == :(<=)
            lower, upper, body = -Inf, expr.args[3], expr.args[2]
        else
            @assert expr.args[1] == :(==)
            lower, upper, body = expr.args[3], expr.args[3], expr.args[2]
        end
    else
        @assert isexpr(expr, :comparison, 5)
        lower, upper, body = expr.args[1], expr.args[5], expr.args[3]
    end
    return _Function(body), MOI.NLPBoundsPair(lower, upper)
end

"""
    _nlp_block_data(
        d::MOI.AbstractNLPEvaluator;
        backend::AbstractNonlinearOracleBackend,
    )

Convert an AbstractNLPEvaluator into a SymbolicAD instance of MOI.NLPBlockData.
"""
function _nlp_block_data(
    d::MOI.AbstractNLPEvaluator;
    backend::AbstractNonlinearOracleBackend,
)
    MOI.initialize(d, [:ExprGraph])
    objective = d.has_nlobj ? _Function(MOI.objective_expr(d)) : nothing
    n = length(d.constraints)
    functions = Vector{_Function}(undef, n)
    bounds = Vector{MOI.NLPBoundsPair}(undef, n)
    for i in 1:n
        f, bound = _nonlinear_constraint(MOI.constraint_expr(d, i))
        functions[i], bounds[i] = f, bound
    end
    oracle = _NonlinearOracle(backend, objective, functions)
    return MOI.NLPBlockData(bounds, oracle, d.has_nlobj)
end

###
### ThreadedBackend
###

struct _ConstraintOffset
    index::Int
    ∇f_offset::Int
    ∇²f_offset::Int
end

struct ThreadedBackend <: AbstractNonlinearOracleBackend
    offsets::Vector{Vector{_ConstraintOffset}}
    ThreadedBackend() = new(Vector{_ConstraintOffset}[])
end

function MOI.initialize(
    oracle::_NonlinearOracle,
    backend::ThreadedBackend,
    ::Vector{Symbol},
)
    obj_hess_offset = 0
    if oracle.objective !== nothing
        h = oracle.objective.expr.hash
        obj_hess_offset = length(oracle.symbolic_functions[h].H)
    end
    offsets = Dict{UInt,Vector{_ConstraintOffset}}(
        h => _ConstraintOffset[] for h in keys(oracle.symbolic_functions)
    )
    index, ∇f_offset, ∇²f_offset = 1, 1, obj_hess_offset + 1
    for f in oracle.constraints
        push!(
            offsets[f.expr.hash],
            _ConstraintOffset(index, ∇f_offset, ∇²f_offset),
        )
        index += 1
        symbolic_function = oracle.symbolic_functions[f.expr.hash]
        ∇f_offset += length(symbolic_function.g)
        ∇²f_offset += length(symbolic_function.H)
    end
    for v in values(offsets)
        push!(backend.offsets, v)
    end
    return
end

function MOI.eval_constraint(
    oracle::_NonlinearOracle{ThreadedBackend},
    g::AbstractVector{Float64},
    x::AbstractVector{Float64},
)
    start = time()
    Threads.@threads for offsets in oracle.backend.offsets
        for c in offsets
            func = oracle.constraints[c.index]
            @inbounds g[c.index] = _eval_function(oracle, func, x)
        end
    end
    oracle.eval_constraint_timer += time() - start
    return
end

function MOI.eval_constraint_jacobian(
    oracle::_NonlinearOracle{ThreadedBackend},
    J::AbstractVector{Float64},
    x::AbstractVector{Float64},
)
    start = time()
    Threads.@threads for offset in oracle.backend.offsets
        for c in offset
            func = oracle.constraints[c.index]
            g = _eval_gradient(oracle, func, x)
            for i in 1:length(g)
                @inbounds J[c.∇f_offset+i-1] = g[i]
            end
        end
    end
    oracle.eval_constraint_jacobian_timer += time() - start
    return
end

function _hessian_lagrangian_structure(
    structure::Vector{Tuple{Int,Int}},
    oracle::_NonlinearOracle{ThreadedBackend},
    ::Dict{Tuple{Int,Int},Int},
    c::_Function,
)
    f = oracle.symbolic_functions[c.expr.hash]
    for (i, j) in f.H_structure
        row, col = c.ordered_variables[i], c.ordered_variables[j]
        push!(structure, row >= col ? (row, col) : (col, row))
    end
    return
end

function MOI.eval_hessian_lagrangian(
    oracle::_NonlinearOracle{ThreadedBackend},
    H::AbstractVector{Float64},
    x::AbstractVector{Float64},
    σ::Float64,
    μ::AbstractVector{Float64},
)
    fill!(H, 0.0)
    start = time()
    hessian_offset = 0
    if oracle.objective !== nothing && !iszero(σ)
        h = _eval_hessian(oracle, oracle.objective, x)
        for i in 1:length(h)
            H[i] = σ * h[i]
        end
        hessian_offset += length(h)
    end
    Threads.@threads for offset in oracle.backend.offsets
        for c in offset
            if iszero(μ[c.index])
                continue
            end
            func = oracle.constraints[c.index]
            h = _eval_hessian(oracle, func, x)
            for i in 1:length(h)
                @inbounds H[c.∇²f_offset+i-1] = μ[c.index] * h[i]
            end
        end
    end
    oracle.eval_hessian_lagrangian_timer += time() - start
    return
end

###
### JuMP integration
###

function _to_expr(
    nlp_data::JuMP._NLPData,
    data::JuMP._NonlinearExprData,
    subexpressions::Vector{Expr},
)
    tree = Any[]
    for node in data.nd
        expr = if node.nodetype == JuMP._Derivatives.CALL
            Expr(:call, JuMP._Derivatives.operators[node.index])
        elseif node.nodetype == JuMP._Derivatives.CALLUNIVAR
            Expr(:call, JuMP._Derivatives.univariate_operators[node.index])
        elseif node.nodetype == JuMP._Derivatives.SUBEXPRESSION
            subexpressions[node.index]
        elseif node.nodetype == JuMP._Derivatives.MOIVARIABLE
            MOI.VariableIndex(node.index)
        elseif node.nodetype == JuMP._Derivatives.PARAMETER
            nlp_data.nlparamvalues[node.index]
        else
            @assert node.nodetype == JuMP._Derivatives.VALUE
            data.const_values[node.index]
        end
        if 1 <= node.parent <= length(tree)
            push!(tree[node.parent].args, expr)
        end
        push!(tree, expr)
    end
    return tree[1]
end

_to_expr(::JuMP._NLPData, ::Nothing, ::Vector{Expr}) = nothing

function _nlp_block_data(
    model::JuMP.Model;
    backend::AbstractNonlinearOracleBackend,
)
    return _nlp_block_data(model.nlp_data; backend = backend)
end

function _nlp_block_data(
    nlp_data::JuMP._NLPData;
    backend::AbstractNonlinearOracleBackend,
)
    subexpressions = map(nlp_data.nlexpr) do expr
        return _to_expr(nlp_data, expr, Expr[])::Expr
    end
    nlobj = _to_expr(nlp_data, nlp_data.nlobj, subexpressions)
    objective = nlobj === nothing ? nothing : _Function(nlobj)
    functions = map(nlp_data.nlconstr) do c
        return _Function(_to_expr(nlp_data, c.terms, subexpressions))
    end
    return MOI.NLPBlockData(
        [MOI.NLPBoundsPair(c.lb, c.ub) for c in nlp_data.nlconstr],
        _NonlinearOracle(backend, objective, functions),
        objective !== nothing,
    )
end

function optimize_hook(
    model::JuMP.Model;
    backend::AbstractNonlinearOracleBackend = DefaultBackend(),
)
    nlp_block = _nlp_block_data(model; backend = backend)
    nlp_data = model.nlp_data
    model.nlp_data = nothing
    MOI.set(model, MOI.NLPBlock(), nlp_block)
    JuMP.optimize!(model; ignore_optimize_hook = true)
    model.nlp_data = nlp_data
    return
end


parse_float(x) = parse(Float64, x)

function run_lblrtm(work_dir::String, ν_s::Float64, ν_e::Float64)::Tuple{Array{Float64,1},Array{Float64,1}}
    ν_s -= 25
    ν_e += 25

    @info "Entering $(pwd()) ..."
    cd(work_dir)
    @info "Entered."

    @info "Checking system links..."
    requirements = ["lblrtm", "TAPE3"]
    for link in requirements
        @assert(islink(link) || isfile(link), "$link does not exist.")
    end
    @info "System links OK."

    @info "Removing last run's files..."
    rm("TAPE5", force=true)
    rm("TAPE6", force=true)
    rm("TAPE7", force=true)
    rm("TAPE8", force=true)
    rm("TAPE10", force=true)
    @info "Removed."

    IHIRAC = 1
    ILBLF4 = 2
    ICNTNM = 1
    IAERSL = 1
    IEMIT = 1
    ISCAN = 0
    IFILTR = 0
    IPLOT = 0
    ITEST = 0
    IATM = 1
    IMRG = 0
    ILAS = 0
    IOD = 1
    IXSECT = 0
    NPTS = 80
    ISOTPL = 0
    IBRD = 0

    SAMPLE = 4
    DVSET = 0.0 # Spectral resolution for calculation
    ALFAL0 = 0.04
    AVMASS = 36.0
    DPTMIN = 0.0002
    DPTFAC = 0.001
    IFNFLG = 0
    DVOUT = 0.1 # Spectral resolution for output

    TBOUND = 300.0 # Boundary temperature
    SREMIS = 1.0 # Emissivity coefficient - Rand 0

    MODEL = 6 # U.S.Standard
    ITYPE = 2
    IBMAX = 0
    ZERO = 0
    NOPRNT = 0

    H1 = 100.0
    H2 = 0.0
    ANGLE = 180.0
    @info "Writing TAPE5..."
    open("TAPE5", "w") do tape5
        write(tape5, "\$ LBLRTM INPUT\n") # Record 1.1
        write(tape5, " HI=$IHIRAC F4=$ILBLF4 CN=$ICNTNM AE=$IAERSL EM=$IEMIT SC=$ISCAN FI=$IFILTR PL=$IPLOT TS=$ITEST AM=$IATM MG=$IMRG LA=$ILAS OD=$IOD XS=$IXSECT   00   00\n") # Record 1.2
        write(tape5, @sprintf("%10.3f%10.3f%10.3f%10.3f%10.3f%10.3f%10.3f%10.3f%5d%10.3f\n", ν_s, ν_e, SAMPLE, DVSET, ALFAL0, AVMASS, DPTMIN, DPTFAC, IFNFLG, DVOUT)) # Record 1.3
        write(tape5, @sprintf("%10.3f%10.3f\n", TBOUND, SREMIS)) # Record 1.4
        write(tape5, @sprintf("%5d%5d%5d%5d%5d\n", MODEL, ITYPE, IBMAX, ZERO, NOPRNT)) # Record 3.1
        write(tape5, @sprintf("%10.3f%10.3f%10.3f\n", H1, H2, ANGLE)) # Record 3.2
        write(tape5, "\n") # Record 3.3A
        write(tape5, "\n") # Record 4.1
        write(tape5, "-1.\n")
        write(tape5, @sprintf(
"""
\$ Transfer to ASCII plotting data
 HI=0 F4=0 CN=0 AE=0 EM=0 SC=0 FI=0 PL=1 TS=0 AM=0 MG=0 LA=0 MS=0 XS=0    0    0
# Plot title not used
%10.3f%10.3f   10.2000  100.0000    5    0   12    0     1.000 0  0    0
    0.0000    1.2000    7.0200    0.2000    4    0    1    1    0    0 0    3 27
%10.3f%10.3f   10.2000  100.0000    5    0   12    0     1.000 0  0    0
    0.0000    1.2000    7.0200    0.2000    4    0    1    1    1    0 0    3 28
-1.
""", ν_s, ν_e, ν_s, ν_e))
    end
    @info "TAPE5 OK."

    # Run LBLRTM
    @info "Running LBLRTM..."
    @time run(`./lblrtm`)
    @info "LBLRTM completed."

    # Collect result
    @info "Collecting result..."
    wavenumber = []
    brightness_temperature = []
    open("TAPE28") do io
        see_header = false
        see_data = false
        for line in eachline(io)
            if !see_header
                if findfirst("WAVENUMBER", line) ≠ nothing
                    see_header = true
                end
            elseif !see_data
                see_data = true
            else
                wb, bt = map(parse_float, split(strip(line), r"\s+"))
                append!(wavenumber, wb)
                append!(brightness_temperature, bt)
            end
        end
    end
    @info "Results collected."

    return (wavenumber, brightness_temperature)
end


# Driver functions for the integrators.

struct MolecularDynamicsParameters
    "Centroid friction (1 / ps)."
    gamma0::Float64
    "Time step (ps)."
    dt::Float64
    "Total duration (ps)."
    duration::Float64
end

"""
    num_steps(mdp::MolecularDynamicsParameters)

Number of molecular dynamics steps for `mdp`.
"""
num_steps(mdp::MolecularDynamicsParameters) = ceil(Int, mdp.duration / mdp.dt)

function run_constrained_equilibration(
        model_T::Type{<:WaterModel}, int_T::Type{<:Integrator}, N::Int,
        T::Float64, R::Float64, P::Int, mdp_equil::MolecularDynamicsParameters;
        qs_init::Maybe{Vector{Float64}}=nothing, output_skip::Int=100)
    is_constrained(int_T) || error("Integrator is not constrained.")

    # Number of atoms in each water molecule.
    A = 3
    # Number of spatial dimensions.
    D = 3

    if !isnothing(qs_init)
        s = State(P, D, A, N; qs_init)
    else
        s = State(P, D, A, N; R)
    end

    model = model_T(N)
    fc = ForceContainer(P, D, A, N)
    int_equil = int_T(model, R, mdp_equil.dt, beta(T), mdp_equil.gamma0, fc, s)

    for i in 1:num_steps(mdp_equil)
        step!(s, int_equil)

        i % output_skip == 0 || continue

        time = i * mdp_equil.dt
        E = energy(int_equil, s)
        E_K = pop!(E, "kinetic")
        E_Vspring = pop!(E, "spring")
        E_Vmodel = pop!(E, "model")
        isempty(E) || error("Unexpected energy contribution.")
        E_total = E_K + E_Vspring + E_Vmodel
        T = get_temperature(int_equil)

        println("$(time) $(E_K) $(E_Vspring) $(E_Vmodel) $(E_total) $(T)")
    end

    s
end

function run_constrained_production(
        model_T::Type{<:WaterModel}, int_T::Type{<:Integrator}, N::Int,
        T::Float64, R::Float64, P::Int, mdp_equil::MolecularDynamicsParameters,
        mdp_prod::MolecularDynamicsParameters;
        qs_init::Maybe{Vector{Float64}}=nothing, aux_output::IO=stderr)
    is_constrained(int_T) || error("Integrator is not constrained.")

    # Number of atoms in each water molecule.
    A = 3
    # Number of spatial dimensions.
    D = 3

    # Number of molecular dynamics steps for equilibration.
    N_equil = num_steps(mdp_equil)
    # Number of molecular dynamics steps for production.
    N_prod = num_steps(mdp_prod)

    if !isnothing(qs_init)
        s = State(P, D, A, N; qs_init)
    else
        s = State(P, D, A, N; R)
    end

    model = model_T(N)
    fc = ForceContainer(P, D, A, N)
    int_equil = int_T(model, R, mdp_equil.dt, beta(T), mdp_equil.gamma0, fc, s)
    int_prod = int_T(model, R, mdp_prod.dt, beta(T), mdp_prod.gamma0, fc, s)

    meter_equil = Progress(N_equil, output=aux_output)

    for _ in ProgressWrapper(1:N_equil, meter_equil)
        step!(s, int_equil)
    end

    constraints = Array{Float64}(undef, N_prod)
    E_Ks = Array{Float64}(undef, N_prod)
    E_Vsprings = Array{Float64}(undef, N_prod)
    E_Vmodels = Array{Float64}(undef, N_prod)
    E_totals = Array{Float64}(undef, N_prod)
    temperatures = Array{Float64}(undef, N_prod)

    forces1 = Array{Float64}(undef, N_prod)
    forces2 = Array{Float64}(undef, N_prod)

    estim1 = estimator1(R, beta(T), int_prod, s)
    estim2 = estimator2(R, beta(T), int_prod, s)

    meter = Progress(N_prod, output=aux_output)

    for i in ProgressWrapper(1:N_prod, meter)
        step!(s, int_prod)

        constraints[i] = com_r(CN1, CN2, CJ, s)
        E = energy(int_prod, s)
        E_Ks[i] = pop!(E, "kinetic")
        E_Vsprings[i] = pop!(E, "spring")
        E_Vmodels[i] = pop!(E, "model")
        isempty(E) || error("Unexpected energy contribution.")
        E_totals[i] = E_Ks[i] + E_Vsprings[i] + E_Vmodels[i]
        temperatures[i] = get_temperature(int_prod)

        forces1[i] = estim1()
        forces2[i] = estim2()
    end

    data_table = []

    for (label, data) in [("xi (nm)", constraints),
                          ("K (kJ / mol)", E_Ks),
                          ("Vspring (kJ / mol)", E_Vsprings),
                          ("Vmodel (kJ / mol)", E_Vmodels),
                          ("E (kJ / mol)", E_totals),
                          ("T (K)", temperatures),
                          ("est1 (1 / nm)", forces1),
                          ("est2 (1 / nm)", forces2)]
        data_mean, data_stderr, data_actime = aggregate(mdp_prod.dt, data)
        push!(data_table, [label, data_mean, data_stderr, data_actime])
    end

    pretty_table(aux_output, permutedims(hcat(data_table...)),
                 ["", "Value", "Error", "Autocorrelation time (ps)"])

    s, aggregate(mdp_prod.dt, forces1), aggregate(mdp_prod.dt, forces2)
end

function run_restrained_equilibration(
        model_T::Type{<:WaterModel}, int_T::Type{<:Integrator}, N::Int,
        T::Float64, R::Float64, P::Int, mdp_equil::MolecularDynamicsParameters,
        bias_force_constant::Float64; qs_init::Maybe{Vector{Float64}}=nothing,
        output_skip::Int=100)
    is_constrained(int_T) && error("Integrator is constrained.")

    # Number of atoms in each water molecule.
    A = 3
    # Number of spatial dimensions.
    D = 3

    if !isnothing(qs_init)
        s = State(P, D, A, N; qs_init)
    else
        s = State(P, D, A, N; R)
    end

    bias = HarmonicRestraint(bias_force_constant, R, CN1, CN2, CJ)
    model = model_T(N)
    fc = ForceContainer(P, D, A, N)
    int_equil = int_T(model, mdp_equil.dt, beta(T), mdp_equil.gamma0, fc, s;
                      extra_forces=Dict("bias" => [bias]))

    for i in 1:num_steps(mdp_equil)
        step!(s, int_equil)

        i % output_skip == 0 || continue

        time = i * mdp_equil.dt
        xi = com_r(CN1, CN2, CJ, s)
        E = energy(int_equil, s)
        E_K = pop!(E, "kinetic")
        E_Vspring = pop!(E, "spring")
        E_Vmodel = pop!(E, "model")
        E_Vbias = pop!(E, "bias")
        isempty(E) || error("Unexpected energy contribution.")
        E_total = E_K + E_Vspring + E_Vmodel + E_Vbias
        T = get_temperature(int_equil)

        print("$(time) $(xi) $(E_K) $(E_Vspring) $(E_Vmodel) $(E_Vbias) ")
        println("$(E_total) $(T)")
    end

    s
end

function run_restrained_production(
        model_T::Type{<:WaterModel}, int_T::Type{<:Integrator}, N::Int,
        T::Float64, R::Float64, P::Int, mdp_equil::MolecularDynamicsParameters,
        mdp_prod::MolecularDynamicsParameters, bias_force_constant::Float64;
        qs_init::Maybe{Vector{Float64}}=nothing, output_skip::Int=100)
    is_constrained(int_T) && error("Integrator is constrained.")

    # Number of atoms in each water molecule.
    A = 3
    # Number of spatial dimensions.
    D = 3

    # Number of molecular dynamics steps for equilibration.
    N_equil = num_steps(mdp_equil)
    # Number of molecular dynamics steps for production.
    N_prod = num_steps(mdp_prod)

    if !isnothing(qs_init)
        s = State(P, D, A, N; qs_init)
    else
        s = State(P, D, A, N; R)
    end

    bias = HarmonicRestraint(bias_force_constant, R, CN1, CN2, CJ)
    model = model_T(N)
    fc = ForceContainer(P, D, A, N)
    int_equil = int_T(model, mdp_equil.dt, beta(T), mdp_equil.gamma0, fc, s;
                      extra_forces=Dict("bias" => [bias]))
    int_prod = int_T(model, mdp_prod.dt, beta(T), mdp_prod.gamma0, fc, s;
                     extra_forces=Dict("bias" => [bias]))

    for _ in 1:N_equil
        step!(s, int_equil)
    end

    for i in 1:N_prod
        step!(s, int_prod)

        i % output_skip == 0 || continue

        println(com_r(CN1, CN2, CJ, s))
    end

    s
end


# This file is a part of Julia. License is MIT: https://julialang.org/license

module FieldValues

using ...VersionWeights

export FieldValue, Field, validmax, secondmax

# FieldValue is a hierarchical numeric type with 5 levels.
# When summing two FieldValues, the levels are summed independently.
# When comparing them, lower levels take precedence.
# The levels are used as such:
#  l0 : for hard constraints (dependencies and requirements)
#  l1 : for favoring higher versions of the explicitly required
#       packages
#  l2 : for favoring higher versions of all other packages
#  l3 : for favoring uninstallation of non-needed packages
#
struct FieldValue
    l0::Int64
    l1::VersionWeight
    l2::VersionWeight
    l3::Int64
    FieldValue(l0::Integer = 0,
               l1::VersionWeight = zero(VersionWeight),
               l2::VersionWeight = zero(VersionWeight),
               l3::Integer = 0) = new(l0, l1, l2, l3)
end

# This isn't nice, but it's for debugging only anyway
function Base.show(io::IO, a::FieldValue)
    print(io, a.l0)
    a == FieldValue(a.l0) && return
    print(io, ".", a.l1)
    a == FieldValue(a.l0, a.l1) && return
    print(io, ".", a.l2)
    a == FieldValue(a.l0, a.l1, a.l2) && return
    print(io, ".", a.l3)
    return
end

const Field = Vector{FieldValue}

Base.zero(::Type{FieldValue}) = FieldValue()

Base.typemin(::Type{FieldValue}) = (x=typemin(Int64); y=typemin(VersionWeight); FieldValue(x, y, y, x))

Base.:-(a::FieldValue, b::FieldValue) = FieldValue(a.l0-b.l0, a.l1-b.l1, a.l2-b.l2, a.l3-b.l3)
Base.:+(a::FieldValue, b::FieldValue) = FieldValue(a.l0+b.l0, a.l1+b.l1, a.l2+b.l2, a.l3+b.l3)

function Base.isless(a::FieldValue, b::FieldValue)
    a.l0 < b.l0 && return true
    a.l0 > b.l0 && return false
    c = cmp(a.l1, b.l1)
    c < 0 && return true
    c > 0 && return false
    c = cmp(a.l2, b.l2)
    c < 0 && return true
    c > 0 && return false
    a.l3 < b.l3 && return true
    return false
end

Base.:(==)(a::FieldValue, b::FieldValue) =
    a.l0 == b.l0 && a.l1 == b.l1 && a.l2 == b.l2 && a.l3 == b.l3

Base.abs(a::FieldValue) = FieldValue(abs(a.l0), abs(a.l1), abs(a.l2), abs(a.l3))

Base.copy(a::FieldValue) = FieldValue(a.l0, copy(a.l1), copy(a.l2), a.l3)

# if the maximum field has l0 < 0, it means that
# some hard constraint is being violated
validmax(a::FieldValue) = a.l0 >= 0

# like usual indmax, but favors the highest indices
# in case of a tie
function Base.indmax(f::Field)
    m = typemin(FieldValue)
    mi = 0
    for j = length(f):-1:1
        if f[j] > m
            m = f[j]
            mi = j
        end
    end
    @assert mi != 0
    return mi
end

# secondmax returns the (normalized) value of the second maximum in a
# field. It's used to determine the most polarized field.
function secondmax(f::Field, msk::BitVector = trues(length(f)))
    m = typemin(FieldValue)
    m2 = typemin(FieldValue)
    for i = 1:length(f)
        msk[i] || continue
        a = f[i]
        if a > m
            m2 = m
            m = a
        elseif a > m2
            m2 = a
        end
    end
    return m2 - m
end

# Support broadcasting like a scalar by default
Base.Broadcast.broadcastable(a::FieldValue) = Ref(a)

end


module Enzyme

export Forward, Reverse
export Const, Active, Duplicated, DuplicatedNoNeed, BatchDuplicated, BatchDuplicatedNoNeed
export autodiff, jacobian, gradient, gradient!
export markType, batch_size, onehot, chunkedonehot

using LinearAlgebra

# Independent code, must be loaded before "compiler.jl"
include("pmap.jl")

using Adapt

"""
    abstract type Annotation{T}

Abstract type for [`autodiff`](@ref) function argument wrappers like
[`Const`](@ref), [`Active`](@ref) and [`Duplicated`](@ref).
"""
abstract type Annotation{T} end
Base.eltype(::Type{<:Annotation{T}}) where T = T

"""
    Const(x)

Mark a function argument `x` of [`autodiff`](@ref) as constant,
Enzyme will not auto-differentiate in respect `Const` arguments.
"""
struct Const{T} <: Annotation{T}
    val::T
end
Adapt.adapt_structure(to, x::Const) = Const(adapt(to, x.val))

# To deal with Const(Int) and prevent it to go to `Const{DataType}(T)`
Const(::Type{T}) where T = Const{Type{T}}(T)

"""
    Active(x)

Mark a function argument `x` of [`autodiff`](@ref) as active,
Enzyme will auto-differentiate in respect `Active` arguments.

!!! note

    Enzyme gradients with respect to integer values are zero.
    [`Active`](@ref) will automatically convert plain integers to floating
    point values, but cannot do so for integer values in tuples and structs.
"""
struct Active{T} <: Annotation{T}
    val::T
end
Adapt.adapt_structure(to, x::Active) = Active(adapt(to, x.val))

Active(i::Integer) = Active(float(i))

"""
    Duplicated(x, ∂f_∂x)

Mark a function argument `x` of [`autodiff`](@ref) as duplicated, Enzyme will
auto-differentiate in respect to such arguments, with `dx` acting as an
accumulator for gradients (so ``\\partial f / \\partial x`` will be *added to*)
`∂f_∂x`.
"""
struct Duplicated{T} <: Annotation{T}
    val::T
    dval::T
end
Adapt.adapt_structure(to, x::Duplicated) = Duplicated(adapt(to, x.val), adapt(to, x.dval))

"""
    DuplicatedNoNeed(x, ∂f_∂x)

Like [`Duplicated`](@ref), except also specifies that Enzyme may avoid computing
the original result and only compute the derivative values.
"""
struct DuplicatedNoNeed{T} <: Annotation{T}
    val::T
    dval::T
end
Adapt.adapt_structure(to, x::DuplicatedNoNeed) = DuplicatedNoNeed(adapt(to, x.val), adapt(to, x.dval))

"""
    BatchDuplicated(x, ∂f_∂xs)

Like [`Duplicated`](@ref), except contains several shadows to compute derivatives
for all at once. Argument `∂f_∂xs` should be a tuple of the several values of type `x`.
"""
struct BatchDuplicated{T,N} <: Annotation{T}
    val::T
    dval::NTuple{N,T}
end
Adapt.adapt_structure(to, x::BatchDuplicated) = BatchDuplicated(adapt(to, x.val), adapt(to, x.dval))

"""
    BatchDuplicatedNoNeed(x, ∂f_∂xs)

Like [`DuplicatedNoNeed`](@ref), except contains several shadows to compute derivatives
for all at once. Argument `∂f_∂xs` should be a tuple of the several values of type `x`.
"""
struct BatchDuplicatedNoNeed{T,N} <: Annotation{T}
    val::T
    dval::NTuple{N,T}
end
batch_size(::BatchDuplicated{T,N}) where {T,N} = N
batch_size(::BatchDuplicatedNoNeed{T,N}) where {T,N} = N
Adapt.adapt_structure(to, x::BatchDuplicatedNoNeed) = BatchDuplicatedNoNeed(adapt(to, x.val), adapt(to, x.dval))

"""
    abstract type Mode

Abstract type for what differentiation mode will be used.
"""
abstract type Mode end

"""
    struct Reverse <: Mode

Reverse mode differentiation
"""
struct ReverseMode <: Mode
end
const Reverse = ReverseMode()
guess_activity(::Type{T}, ::ReverseMode) where T = guess_activity(T)

"""
    struct Forward <: Mode

Forward mode differentiation
"""
struct ForwardMode <: Mode
end
const Forward = ForwardMode()
guess_activity(::Type{T}, ::ForwardMode) where T = guess_activity(T, API.DEM_ForwardMode)

import LLVM
include("api.jl")

@inline function guess_activity(::Type{T}, Mode::API.CDerivativeMode=API.DEM_ReverseModeCombined) where {T}
    return Const{T}
end
@inline function guess_activity(::Type{T}, Mode::API.CDerivativeMode=API.DEM_ReverseModeCombined) where {T<:AbstractFloat}
    if Mode == API.DEM_ForwardMode
        return DuplicatedNoNeed{T}
    else
        return Active{T}
    end
end
@inline function guess_activity(::Type{T}, Mode::API.CDerivativeMode=API.DEM_ReverseModeCombined) where {T<:Complex{<:AbstractFloat}}
    if Mode == API.DEM_ForwardMode
        return DuplicatedNoNeed{T}
    else
        return Active{T}
    end
end

@inline function guess_activity(::Type{T}, Mode::API.CDerivativeMode=API.DEM_ReverseModeCombined) where {T<:AbstractArray}
    if Mode == API.DEM_ForwardMode
        return DuplicatedNoNeed{T}
    else
        return Duplicated{T}
    end
end

include("logic.jl")
include("typeanalysis.jl")
include("typetree.jl")
include("utils.jl")
include("compiler.jl")

import .Compiler: CompilationException

# @inline annotate() = ()
# @inline annotate(arg::A, args::Vararg{Any, N}) where {A<:Annotation, N} = (arg, annotate(args...)...)
# @inline annotate(arg, args::Vararg{Any, N}) where N = (Const(arg), annotate(args...)...)

@inline function annotate(args::Vararg{Any, N}) where N
    ntuple(Val(N)) do i
        Base.@_inline_meta
        arg = @inbounds args[i]
        if arg isa Annotation
            return arg
        else
            return Const(arg)
        end
    end
end

@inline function same_or_one(args::Vararg{Any, N}) where N
    current = -1
    for arg in args
        if arg isa BatchDuplicated
            if current == -1
                current = batch_size(arg)
            else
                @assert current == batch_size(arg)
            end
        elseif arg isa BatchDuplicatedNoNeed
            if current == -1
                current = batch_size(arg)
            else
                @assert current == batch_size(arg)
            end
        end
    end

    if current == -1
        current = 1
    end

    return current
end

"""
    autodiff(::ReverseMode, f, Activity, args...)

Auto-differentiate function `f` at arguments `args` using reverse mode.

Limitations:

* `f` may only return a `Real` (of a built-in/primitive type) or `nothing`,
  not an array, struct, `BigFloat`, etc. To handle vector-valued return
  types, use a mutating `f!` that returns `nothing` and stores it's return
  value in one of the arguments, which must be wrapped in a
  [`Duplicated`](@ref).

`args` may be numbers, arrays, structs of numbers, structs of arrays and so
on. Enzyme will only differentiate in respect to arguments that are wrapped
in an [`Active`](@ref) (for immutable arguments like primitive types and
structs thereof) or [`Duplicated`](@ref) (for mutable arguments like arrays,
`Ref`s and structs thereof). Non-annotated arguments will automatically be
treated as [`Const`](@ref).

`Activity` is the Activity of the return value, it may be `Const` or `Active`.

Example:

```jldoctest
a = 4.2
b = [2.2, 3.3]; ∂f_∂b = zero(b)
c = 55; d = 9

f(a, b, c, d) = a * √(b[1]^2 + b[2]^2) + c^2 * d^2
∂f_∂a, ∂f_∂d = autodiff(Reverse, f, Active, Active(a), Duplicated(b, ∂f_∂b), c, Active(d))

# output

(3.966106403010388, 54450.0)
```

here, `autodiff` returns a tuple
``(\\partial f/\\partial a, \\partial f/\\partial d)``,
while ``\\partial f/\\partial b`` will be *added to* `∂f_∂b` (but not returned).
`c` will be treated as `Const(c)`.

!!! note

    Enzyme gradients with respect to integer values are zero.
    [`Active`](@ref) will automatically convert plain integers to floating
    point values, but cannot do so for integer values in tuples and structs.
"""
@inline function autodiff(::ReverseMode, f::F, ::Type{A}, args...) where {F, A<:Annotation}
    args′  = annotate(args...)
    tt′    = Tuple{map(Core.Typeof, args′)...}
    width = Val(same_or_one(args...))
    if A <: Active
        tt    = Tuple{map(T->eltype(Core.Typeof(T)), args′)...}
        rt = Core.Compiler.return_type(f, tt)
        if !allocatedinline(rt)
            forward, adjoint = Enzyme.Compiler.thunk(f, #=df=#nothing, Duplicated{rt}, tt′, #=Split=# Val(API.DEM_ReverseModeGradient), width, #=ModifiedBetween=#Val(false))
            res = forward(args′...)
            @assert length(res) == 2
            tape = res[1]
            if res[2] isa Base.RefValue
                res[2][] += one(eltype(typeof(res[2])))
            else
                res[2] += one(eltype(typeof(res[2])))
            end
            return adjoint(args′..., tape)
        end
    elseif A <: Duplicated || A<: DuplicatedNoNeed || A <: BatchDuplicated || A<: BatchDuplicatedNoNeed
        throw(ErrorException("Duplicated Returns not yet handled"))
    end
    thunk = Enzyme.Compiler.thunk(f, #=df=#nothing, A, tt′, #=Split=# Val(API.DEM_ReverseModeCombined), width)
    if A <: Active
        tt    = Tuple{map(T->eltype(Core.Typeof(T)), args′)...}
        rt = eltype(Compiler.return_type(thunk))
        args′ = (args′..., one(rt))
    end
    thunk(args′...)
end

@inline function autodiff(::ReverseMode, dupf::Duplicated{F}, ::Type{A}, args...) where {F, A<:Annotation}
    args′  = annotate(args...)
    tt′    = Tuple{map(Core.Typeof, args′)...}
    width = Val(same_or_one(args...))
    thunk = Enzyme.Compiler.thunk(#=f=#dupf.val, #=df=#dupf.dval, A, tt′, #=Split=# Val(API.DEM_ReverseModeCombined), width)
    if A <: Active
        rt = eltype(Compiler.return_type(thunk))
        args′ = (args′..., one(rt))
    end
    thunk(args′...)
end

"""
    autodiff(mode::Mode, f, args...)
    autodiff(f, mode::Mode, args...)

Like [`autodiff`](@ref) but will try to guess the activity of the return value.
"""
@inline function autodiff(mode::Mode, f::F, args...) where {F}
    args′ = annotate(args...)
    tt′   = Tuple{map(Core.Typeof, args′)...}
    tt    = Tuple{map(T->eltype(Core.Typeof(T)), args′)...}
    rt    = Core.Compiler.return_type(f, tt)
    A     = guess_activity(rt, mode)
    autodiff(mode, f, A, args′...)
end

@inline function autodiff(mode::Mode, dupf::Duplicated{F}, args...) where {F}
    args′ = annotate(args...)
    tt′   = Tuple{map(Core.Typeof, args′)...}
    tt    = Tuple{map(T->eltype(Core.Typeof(T)), args′)...}
    rt    = Core.Compiler.return_type(dupf.val, tt)
    A     = guess_activity(rt, mode)
    autodiff(mode, dupf, A, args′...)
end

"""
    autodiff(::ForwardMode, f, Activity, args...)

Auto-differentiate function `f` at arguments `args` using forward mode.

`args` may be numbers, arrays, structs of numbers, structs of arrays and so
on. Enzyme will only differentiate in respect to arguments that are wrapped
in a [`Duplicated`](@ref) or similar argument. Non-annotated arguments will
automatically be treated as [`Const`](@ref). Unlike reverse mode in
[`autodiff`](@ref), [`Active`](@ref) arguments are not allowed here, since
all

`Activity` is the Activity of the return value, it may be:
* `Const` if the return is not to be differentiated with respect to
* `Duplicated`, if the return is being differentiated with respect to and
  both the original value and the derivative return are desired
* `DuplicatedNoNeed`, if the return is being differentiated with respect to
  and only the derivative return is desired.

Example returning both original return and derivative:

```jldoctest
a = 4.2
b = [2.2, 3.3]; ∂f_∂b = zero(b)
c = 55; d = 9

f(x) = x*x
res, ∂f_∂x = autodiff(Forward, f, Duplicated, Duplicated(3.14, 1.0))

# output

(9.8596, 6.28)
```

Example returning just the derivative:

```jldoctest
a = 4.2
b = [2.2, 3.3]; ∂f_∂b = zero(b)
c = 55; d = 9

f(x) = x*x
∂f_∂x = autodiff(Forward, f, DuplicatedNoNeed, Duplicated(3.14, 1.0))

# output

(6.28,)
```
"""
@inline function autodiff(::ForwardMode, f::F, ::Type{A}, args...) where {F, A<:Annotation}
    args′  = annotate(args...)
    tt′    = Tuple{map(Core.Typeof, args′)...}
    width = Val(same_or_one(args...))
    if A <: Active
        throw(ErrorException("Active Returns not allowed in forward mode"))
    end
    ReturnPrimal = Val(A <: Duplicated || A <: BatchDuplicated)
    thunk = Enzyme.Compiler.thunk(f, #=df=#nothing, A, tt′, #=Mode=# Val(API.DEM_ForwardMode), width,
                                     #=ModifiedBetween=#Val(false), ReturnPrimal)
    thunk(args′...)
end

@inline function autodiff(::ForwardMode, dupf::Duplicated{F}, ::Type{A}, args...) where {F, A<:Annotation}
    args′  = annotate(args...)
    tt′    = Tuple{map(Core.Typeof, args′)...}
    width = Val(same_or_one(args...))
    if A <: Active
        throw(ErrorException("Active Returns not allowed in forward mode"))
    end
    ReturnPrimal = Val(A <: Duplicated || A <: BatchDuplicated)
    thunk = Enzyme.Compiler.thunk(#=f=#dupf.val, #=df=#dupf.dval, A, tt′, #=Mode=# Val(API.DEM_ForwardMode), width, #=ModifiedBetween=#Val(false), ReturnPrimal)
    thunk(args′...)
end

# Compat
@deprecate fwddiff(f::F, ::Type{A}, args...) where {F, A<:Annotation} autodiff(Forward, f, A, args...)
@deprecate fwddiff(f::F, args...) where {F} autodiff(Forward, f, args...)

# Compat
@inline autodiff(f::F, ::Type{A}, args...) where {F, A<:Annotation} = autodiff(Reverse, f, A, args...)
@inline autodiff(f::F, args...) where {F} = autodiff(Reverse, f, args...)

# F as first arg for `do` syntax
@inline autodiff(dupf::Duplicated{F}, mode::Mode, ::Type{A}, args...) where {F,A<:Annotation} = autodiff(mode, dupf, A, args...)
@inline autodiff(f::F, mode::Mode, ::Type{A}, args...) where {F,A<:Annotation} = autodiff(mode, f, A, args...)
@inline autodiff(dupf::Duplicated{F}, mode::Mode, args...) where {F} = autodiff(mode, dupf, args...)
@inline autodiff(f::F, mode::Mode, args...) where {F} = autodiff(mode, f, args...)

"""
    autodiff_deferred(f, Activity, args...)

Same as [`autodiff`](@ref) but uses deferred compilation to support usage in GPU
code, as well as high-order differentiation.
"""
@inline function autodiff_deferred(f::F, ::Type{A}, args...) where {F, A<:Annotation}
    args′ = annotate(args...)
    tt′   = Tuple{map(Core.Typeof, args′)...}
    width = Val(same_or_one(args...))
    if A isa UnionAll
        tt = Tuple{map(T->eltype(Core.Typeof(T)), args′)...}
        rt = Core.Compiler.return_type(f, tt)
        rt = A{rt}
    else
        @assert A isa DataType
        rt = A
    end

    if eltype(rt) == Union{}
        error("Return type inferred to be Union{}. Giving up.")
    end

    ReturnPrimal = Val(false)
    ptr   = Compiler.deferred_codegen(f, Val(tt′), Val(rt), #=dupClosure=#Val(false), Val(API.DEM_ReverseModeCombined), width, #=ModifiedBetween=#Val(false), ReturnPrimal)
    thunk = Compiler.CombinedAdjointThunk{F, rt, tt′, typeof(width), Nothing, ReturnPrimal}(f, ptr, #=df=#nothing)
    if rt <: Active
        args′ = (args′..., one(eltype(rt)))
    elseif A <: Duplicated || A<: DuplicatedNoNeed || A <: BatchDuplicated || A<: BatchDuplicatedNoNeed
        throw(ErrorException("Duplicated Returns not yet handled"))
    end
    thunk(args′...)
end

"""
    autodiff_deferred(f, args...)

Like [`autodiff_deferred`](@ref) but will try to guess the activity of the return value.
"""
@inline function autodiff_deferred(f::F, args...) where {F}
    args′ = annotate(args...)
    tt′   = Tuple{map(Core.Typeof, args′)...}
    tt    = Tuple{map(T->eltype(Core.Typeof(T)), args′)...}
    rt    = Core.Compiler.return_type(f, tt)
    rt    = guess_activity(rt)
    autodiff_deferred(f, rt, args′...)
end

"""
    fwddiff_deferred(f, Activity, args...)

Same as `autodiff(::ForwardMode, ...)` but uses deferred compilation to support usage in GPU
code, as well as high-order differentiation.
"""
@inline function fwddiff_deferred(f::F, ::Type{A}, args...) where {F, A<:Annotation}
    args′ = annotate(args...)
    tt′   = Tuple{map(Core.Typeof, args′)...}
    width = Val(same_or_one(args...))
    if A isa UnionAll
        tt = Tuple{map(T->eltype(Core.Typeof(T)), args′)...}
        rt = Core.Compiler.return_type(f, tt)
        rt = A{rt}
    else
        @assert A isa DataType
        rt = A
    end

    if eltype(rt) == Union{}
        error("Return type inferred to be Union{}. Giving up.")
    end

    if A <: Active
        throw(ErrorException("Active Returns not allowed in forward mode"))
    end

    ReturnPrimal = Val(A <: Duplicated || A <: BatchDuplicated)
    ptr   = Compiler.deferred_codegen(f, Val(tt′), Val(rt), #=dupClosure=#Val(false), Val(API.DEM_ForwardMode), width, #=ModifiedBetween=#Val(false), ReturnPrimal)
    thunk = Compiler.ForwardModeThunk{F, rt, tt′, typeof(width), Nothing, ReturnPrimal}(f, ptr, #=df=#nothing)
    thunk(args′...)
end

"""
    fwddiff_deferred(f, args...)

Like [`fwddiff_deferred`](@ref) but will try to guess the activity of the return value.
"""
@inline function fwddiff_deferred(f::F, args...) where {F}
    args′ = annotate(args...)
    tt    = Tuple{map(T->eltype(Core.Typeof(T)), args′)...}
    rt    = Core.Compiler.return_type(f, tt)
    rt    = guess_activity(rt, API.DEM_ForwardMode)
    fwddiff_deferred(f, rt, args′...)
end

# White lie, should be `Core.LLVMPtr{Cvoid, 0}` but that's not supported by ccallable
Base.@ccallable function __enzyme_float(x::Ptr{Cvoid})::Cvoid
    return nothing
end

Base.@ccallable function __enzyme_double(x::Ptr{Cvoid})::Cvoid
    return nothing
end

@inline function markType(::Type{T}, ptr::Ptr{Cvoid}) where T
    markType(Base.unsafe_convert(Ptr{T}, ptr))
end

@inline function markType(data::Array{T}) where T
    GC.@preserve data markType(pointer(data))
end

# TODO(WM): We record the type of a single index here, we could give it a range
@inline function markType(data::SubArray)
    GC.@preserve data markType(pointer(data))
end

@inline function markType(data::Ptr{Float32})
    Base.llvmcall(("declare void @__enzyme_float(i8* nocapture) nounwind define void @c(i64 %q) nounwind alwaysinline { %p = inttoptr i64 %q to i8* call void @__enzyme_float(i8* %p) ret void }", "c"), Cvoid, Tuple{Ptr{Float32}}, data)
    nothing
end

@inline function markType(data::Ptr{Float64})
    Base.llvmcall(("declare void @__enzyme_double(i8* nocapture) nounwind define void @c(i64 %q) nounwind alwaysinline { %p = inttoptr i64 %q to i8* call void @__enzyme_double(i8* %p) ret void }", "c"), Cvoid, Tuple{Ptr{Float64}}, data)
    nothing
end

@inline function onehot(x, start=1, endl=length(x))
    ntuple(Val(endl-start+1)) do i
        Base.@_inline_meta
        res = similar(x)
        for idx in 1:length(x)
            @inbounds res[idx] = (i + start - 1== idx) ? 1.0 : 0.0
        end
        return res
    end
end

@inline function onehot(x::NTuple{N, T}, start=1, endl=N) where {T, N}
    ntuple(Val(endl-start+1)) do i
        Base.@_inline_meta
        ntuple(N) do idx
            Base.@_inline_meta
            return (i + start - 1 == idx) ? 1.0 : 0.0
        end
    end
end

"""
    gradient(::ReverseMode, f, x)

Compute the gradient of an array-input function `f` using reverse mode.
This will allocate and return new array with the gradient result.

Example:

```jldoctest
f(x) = x[1]*x[2]

grad = gradient(Reverse, f, [2.0, 3.0])

# output

2-element Vector{Float64}:
 3.0
 2.0
```
"""
@inline function gradient(::ReverseMode, f, x)
    dx = zero(x)
    autodiff(Reverse, f, Duplicated(x, dx))
    dx
end

"""
    gradient!(::ReverseMode, dx, f, x)

Compute the gradient of an array-input function `f` using reverse mode,
storing the derivative result in an existing array `dx`.

Example:

```jldoctest
f(x) = x[1]*x[2]

dx = [0.0, 0.0]
gradient!(Reverse, dx, f, [2.0, 3.0])

# output

2-element Vector{Float64}:
 3.0
 2.0
```
"""
@inline function gradient!(::ReverseMode, dx, f, x)
    dx .= 0
    autodiff(Reverse, f, Duplicated(x, dx))
    dx
end

"""
    gradient(::ForwardMode, f, x; shadow=onehot(x))

Compute the gradient of an array-input function `f` using forward mode. The
optional keyword argument `shadow` is a vector of one-hot vectors of type `x`
which are used to forward-propagate into the return. For performance reasons,
this should be computed once, outside the call to `gradient`, rather than
within this call.

Example:

```jldoctest
f(x) = x[1]*x[2]

grad = gradient(Forward, f, [2.0, 3.0])

# output

(3.0, 2.0)
```
"""
@inline function gradient(::ForwardMode, f, x; shadow=onehot(x))
    only(autodiff(Forward, f, BatchDuplicatedNoNeed, BatchDuplicated(x, shadow)))
end

@inline function chunkedonehot(x, ::Val{chunk}) where chunk
    sz = length(x)
    num = ((sz + chunk - 1) ÷ chunk)
    ntuple(Val(num)) do i
        Base.@_inline_meta
        onehot(x, (i-1)*chunk+1, i == num ? sz : (i*chunk) )
    end
end

@inline tupleconcat(x) = x
@inline tupleconcat(x, y) = (x..., y...)
@inline tupleconcat(x, y, z...) = (x..., tupleconcat(y, z...)...)

"""
    gradient(::ForwardMode, f, x, ::Val{chunk}; shadow=onehot(x))

Compute the gradient of an array-input function `f` using vector forward mode.
Like [`gradient`](@ref), except it uses a chunk size of `chunk` to compute
`chunk` derivatives in a single call.

Example:

```jldoctest
f(x) = x[1]*x[2]

grad = gradient(Forward, f, [2.0, 3.0], Val(2))

# output

(3.0, 2.0)
```
"""
@inline function gradient(::ForwardMode, f, x, ::Val{chunk}; shadow=chunkedonehot(x, Val(chunk))) where chunk
    tmp = ntuple(length(shadow)) do i
        autodiff(Forward, f, BatchDuplicatedNoNeed, BatchDuplicated(x, shadow[i]))[1]
    end
    tupleconcat(tmp...)
end

@inline function gradient(::ForwardMode, f, x, ::Val{1}; shadow=onehot(x))
    ntuple(length(shadow)) do i
        autodiff(Forward, f, DuplicatedNoNeed, Duplicated(x, shadow[i]))[1]
    end
end

"""
    jacobian(::ForwardMode, f, x; shadow=onehot(x))
    jacobian(::ForwardMode, f, x, ::Val{chunk}; shadow=onehot(x))

Compute the jacobian of an array-input function `f` using (potentially vector)
forward mode. This is a simple rename of the [`gradient`](@ref) function,
and all relevant arguments apply here.

Example:

```jldoctest
f(x) = [x[1]*x[2], x[2]]

grad = jacobian(Forward, f, [2.0, 3.0])

# output

2×2 Matrix{Float64}:
 3.0  2.0
 0.0  1.0
```
"""
@inline function jacobian(::ForwardMode, args...; kwargs...)
    cols = gradient(Forward, args...; kwargs...)
    reduce(hcat, cols)
end

"""
    jacobian(::ReverseMode, f, x, ::Val{num_outs}, ::Val{chunk})

Compute the jacobian of an array-input function `f` using (potentially vector)
reverse mode. The `chunk` argument denotes the chunk size to use and `num_outs`
denotes the number of outputs `f` will return in an array.

Example:

```jldoctest
f(x) = [x[1]*x[2], x[2]]

grad = jacobian(Reverse, f, [2.0, 3.0], Val(2))

# output

2×2 Matrix{Float64}:
 3.0  2.0
 0.0  1.0
```
"""
@inline function jacobian(::ReverseMode, f, x, n_outs::Val{n_out_val}, ::Val{chunk}) where {chunk, n_out_val}
    num = ((n_out_val + chunk - 1) ÷ chunk)

    tt′   = Tuple{BatchDuplicated{Core.Typeof(x), chunk}}
    tt    = Tuple{Core.Typeof(x)}
    rt = Core.Compiler.return_type(f, tt)
    primal, adjoint = Enzyme.Compiler.thunk(f, #=df=#nothing, BatchDuplicatedNoNeed{rt}, tt′, #=Split=# Val(API.DEM_ReverseModeGradient), #=width=#Val(chunk), #=ModifiedBetween=#Val(false))

    if num * chunk == n_out_val
        last_size = chunk
        primal2, adjoint2 = primal, adjoint
    else
        last_size = n_out_val - (num-1)*chunk
        tt′ = Tuple{BatchDuplicated{Core.Typeof(x), last_size}}
        primal2, adjoint2 = Enzyme.Compiler.thunk(f, #=df=#nothing, BatchDuplicatedNoNeed{rt}, tt′, #=Split=# Val(API.DEM_ReverseModeGradient), #=width=#Val(last_size), #=ModifiedBetween=#Val(false))
    end

    tmp = ntuple(num) do i
        Base.@_inline_meta
        dx = ntuple(i == num ? last_size : chunk) do idx
            Base.@_inline_meta
            zero(x)
        end
        res = (i == num ? primal2 : primal)(BatchDuplicated(x, dx))
        tape = res[1]
        j = 0
        for shadow in res[2]
            j += 1
            @inbounds shadow[(i-1)*chunk+j] += one(eltype(typeof(shadow)))
        end
        (i == num ? adjoint2 : adjoint)(BatchDuplicated(x, dx), tape)
        return dx
    end
    rows = tupleconcat(tmp...)
    mapreduce(LinearAlgebra.adjoint, vcat, rows)
end

@inline function jacobian(::ReverseMode, f, x, n_outs::Val{n_out_val}, ::Val{1} = Val(1)) where {n_out_val}
    tt′   = Tuple{Duplicated{Core.Typeof(x)}}
    tt    = Tuple{Core.Typeof(x)}
    rt = Core.Compiler.return_type(f, tt)
    primal, adjoint = Enzyme.Compiler.thunk(f, #=df=#nothing, DuplicatedNoNeed{rt}, tt′, #=Split=# Val(API.DEM_ReverseModeGradient), #=width=#Val(1), #=ModifiedBetween=#Val(false))
    rows = ntuple(n_outs) do i
        Base.@_inline_meta
        dx = zero(x)
        res = primal(Duplicated(x, dx))
        tape = res[1]
        @inbounds res[2][i] += one(eltype(typeof(res[2])))
        adjoint(Duplicated(x, dx), tape)
        return dx
    end
    mapreduce(LinearAlgebra.adjoint, vcat, rows)
end

end # module


include("./load_csv.jl")

# Specify model parameters and/or initial values to optimize
function get_search_index()::Tuple{Array{Int64,1},Array{Int64,1}}
    # parameters
    search_idx_params::Vector{Int} = [
        C.kf1,
        C.kr1,
        C.kf2,
        C.kr2,
        C.kf3,
        C.kr3,
        C.kf4,
        C.kr4,
        C.kf5,
        C.kr5,
        C.kf6,
        C.kr6,
        C.kf7,
        C.kr7,
        C.kf8,
        C.kr8,
        C.kf9,
        C.kr9,
        C.kf10,
        C.kr10,
        C.kf11,
        C.kr11,
        C.kf12,
        C.kr12,
        C.kf13,
        C.kr13,
        C.kf14,
        C.kr14,
        C.kf15,
        C.kr15,
        C.kf16,
        C.kr16,
        C.kf17,
        C.kr17,
        C.kf18,
        C.kr18,
        C.kf19,
        C.kr19,
        C.kf20,
        C.kr20,
        C.kf21,
        C.kr21,
        C.kf22,
        C.kr22,
        C.kf23,
        C.kr23,
        C.kf24,
        C.kr24,
        C.kf25,
        C.kr25,
        C.kf26,
        C.kr26,
        C.kf27,
        C.kr27,
        C.kf28,
        C.kr28,
        C.kf29,
        C.kr29,
        C.kf30,
        C.kr30,
        C.kf31,
        C.kr31,
        C.kf32,
        C.kr32,
        C.kf33,
        C.kr33,
        C.kf34,
        C.kr34,
        C.kf35,
        C.kr35,
        C.kf36,
        C.kr36,
        C.kf37,
        C.kr37,
        C.kf38,
        C.kr38,
        # C.kf39,
        # C.kr39,
        # C.kf40,
        # C.kr40,
        # C.kf41,
        # C.kr41,
        # C.kf42,
        # C.kr42,
        # C.kf43,
        # C.kr43,
        C.kf44,
        # C.kr44,
        # C.kf45,
        # C.kr45,
        # C.kf46,
        # C.kr46,
        # C.kf47,
        # C.kr47,
        # C.kf48,
        # C.kr48,
        # C.kf49,
        # C.kr49,
        C.kf50,
        C.kr50,
        # C.kf51,
        # C.kr51,
        # C.kf52,
        # C.kr52,
        # C.kf53,
        # C.kr53,
        # C.kf54,
        # C.kr54,
        # C.kf55,
        # C.kr55,
        C.kf56,
        # C.kr56,
        # C.kf57,
        # C.kr57,
        # C.kf58,
        # C.kr58,
        # C.kf59,
        # C.kr59,
        # C.kf60,
        # C.kr60,
        # C.kf61,
        # C.kr61,
        C.kf62,
        C.kr62,
        # C.kf63,
        # C.kr63,
        # C.kf64,
        # C.kr64,
        # C.kf65,
        # C.kr65,
        # C.kf66,
        # C.kr66,
        # C.kf67,
        # C.kr67,
        # C.kf68,
        # C.kr68,
        # C.kf69,
        # C.kr69,
        # C.kf70,
        # C.kr70,
        # C.kf71,
        # C.kr71,
        # C.kf72,
        # C.kr72,
        # C.kf73,
        # C.kr73,
        C.kf74,
        C.kr74,
        # C.kf75,
        # C.kr75,
        # C.kf76,
        # C.kr76,
        # C.kf77,
        # C.kr77,
        # C.kf78,
        # C.kr78,
        # C.kf79,
        # C.kr79,
        # C.kf80,
        # C.kr80,
        # C.kf81,
        # C.kr81,
        # C.kf82,
        # C.kr82,
        # C.kf83,
        # C.kr83,
        # C.kf84,
        # C.kr84,
        # C.kf85,
        # C.kr85,
        C.kf86,
        C.kr86,
        # C.kf87,
        # C.kr87,
        # C.kf88,
        # C.kr88,
        # C.kf89,
        # C.kr89,
        # C.kf90,
        # C.kr90,
        # C.kf91,
        # C.kr91,
        # C.kf92,
        # C.kr92,
        # C.kf93,
        # C.kr93,
        # C.kf94,
        # C.kr94,
        # C.kf95,
        # C.kr95,
        # C.kf96,
        # C.kr96,
        # C.kf97,
        # C.kr97,
        C.kf98,
        # C.kr98,
        # C.kf99,
        # C.kr99,
        # C.kf100,
        # C.kr100,
        # C.kf101,
        # C.kr101,
        # C.kf102,
        # C.kr102,
        # C.kf103,
        # C.kr103,
        # C.kf104,
        # C.kr104,
        # C.kf105,
        # C.kr105,
        # C.kf106,
        # C.kr106,
        # C.kf107,
        # C.kr107,
        # C.kf108,
        # C.kr108,
        # C.kf109,
        # C.kr109,
        C.V110,
        C.K110,
        C.V111,
        C.K111,
        C.V112,
        C.K112,
        C.V113,
        C.K113,
        C.V114,
        C.K114,
        C.V115,
        C.K115,
        C.V116,
        C.K116,
        C.V117,
        C.K117,
        C.V118,
        C.K118,
        C.V119,
        C.K119,
        C.V120,
        C.K120,
        C.V121,
        C.K121,
        C.kf122,
        # C.kr122,
        # C.kf123,
        # C.kr123,
        # C.kf124,
        # C.kr124,
        # C.kf125,
        # C.kr125,
        # C.kf126,
        # C.kr126,
        # C.kf127,
        # C.kr127,
        # C.kf128,
        # C.kr128,
        # C.kf129,
        # C.kr129,
        # C.kf130,
        # C.kr130,
        # C.kf131,
        # C.kr131,
        # C.kf132,
        # C.kr132,
        # C.kf133,
        # C.kr133,
        # C.kf134,
        # C.kr134,
        C.kf135,
        C.kr135,
        # C.kf136,
        # C.kr136,
        # C.kf137,
        # C.kr137,
        # C.kf138,
        # C.kr138,
        # C.kf139,
        # C.kr139,
        # C.kf140,
        # C.kr140,
        # C.kf141,
        # C.kr141,
        # C.kf142,
        # C.kr142,
        # C.kf143,
        # C.kr143,
        # C.kf144,
        # C.kr144,
        # C.kf145,
        # C.kr145,
        # C.kf146,
        # C.kr146,
        # C.kf147,
        # C.kr147,
        C.kf148,
        # C.kr148,
        # C.kf149,
        # C.kr149,
        # C.kf150,
        # C.kr150,
        # C.kf151,
        # C.kr151,
        # C.kf152,
        # C.kr152,
        # C.kf153,
        # C.kr153,
        # C.kf154,
        # C.kr154,
        # C.kf155,
        # C.kr155,
        # C.kf156,
        # C.kr156,
        # C.kf157,
        # C.kr157,
        # C.kf158,
        # C.kr158,
        # C.kf159,
        # C.kr159,
        # C.kf160,
        # C.kr160,
        C.kf161,
        C.kr161,
        # C.kf162,
        # C.kr162,
        # C.kf163,
        # C.kr163,
        # C.kf164,
        # C.kr164,
        # C.kf165,
        # C.kr165,
        # C.kf166,
        # C.kr166,
        # C.kf167,
        # C.kr167,
        # C.kf168,
        # C.kr168,
        # C.kf169,
        # C.kr169,
        # C.kf170,
        # C.kr170,
        # C.kf171,
        # C.kr171,
        # C.kf172,
        # C.kr172,
        # C.kf173,
        # C.kr173,
        C.kf174,
        C.kr174,
        # C.kf175,
        # C.kr175,
        # C.kf176,
        # C.kr176,
        # C.kf177,
        # C.kr177,
        # C.kf178,
        # C.kr178,
        # C.kf179,
        # C.kr179,
        # C.kf180,
        # C.kr180,
        # C.kf181,
        # C.kr181,
        # C.kf182,
        # C.kr182,
        # C.kf183,
        # C.kr183,
        # C.kf184,
        # C.kr184,
        # C.kf185,
        # C.kr185,
        # C.kf186,
        # C.kr186,
        # C.kf187,
        # C.kr187,
        # C.kf188,
        # C.kr188,
        # C.kf189,
        # C.kr189,
        # C.kf190,
        # C.kr190,
        # C.kf191,
        # C.kr191,
        # C.kf192,
        # C.kr192,
        C.kf193,
        # C.kr193,
        # C.kf194,
        # C.kr194,
        # C.kf195,
        # C.kr195,
        # C.kf196,
        # C.kr196,
        # C.kf197,
        # C.kr197,
        # C.kf198,
        # C.kr198,
        # C.kf199,
        # C.kr199,
        # C.kf200,
        # C.kr200,
        # C.kf201,
        # C.kr201,
        # C.kf202,
        # C.kr202,
        # C.kf203,
        # C.kr203,
        # C.kf204,
        # C.kr204,
        # C.kf205,
        # C.kr205,
        # C.kf206,
        # C.kr206,
        # C.kf207,
        # C.kr207,
        # C.kf208,
        # C.kr208,
        # C.kf209,
        # C.kr209,
        # C.kf210,
        # C.kr210,
        # C.kf211,
        # C.kr211,
        C.kf212,
        C.kr212,
        # C.kf213,
        # C.kr213,
        # C.kf214,
        # C.kr214,
        # C.kf215,
        # C.kr215,
        # C.kf216,
        # C.kr216,
        # C.kf217,
        # C.kr217,
        # C.kf218,
        # C.kr218,
        # C.kf219,
        # C.kr219,
        # C.kf220,
        # C.kr220,
        # C.kf221,
        # C.kr221,
        # C.kf222,
        # C.kr222,
        # C.kf223,
        # C.kr223,
        # C.kf224,
        # C.kr224,
        C.kf225,
        C.kr225,
        # C.kf226,
        # C.kr226,
        # C.kf227,
        # C.kr227,
        # C.kf228,
        # C.kr228,
        # C.kf229,
        # C.kr229,
        # C.kf230,
        # C.kr230,
        # C.kf231,
        # C.kr231,
        # C.kf232,
        # C.kr232,
        # C.kf233,
        # C.kr233,
        # C.kf234,
        # C.kr234,
        # C.kf235,
        # C.kr235,
        # C.kf236,
        # C.kr236,
        # C.kf237,
        # C.kr237,
        # C.kf238,
        # C.kr238,
        # C.kf239,
        # C.kr239,
        # C.kf240,
        # C.kr240,
        # C.kf241,
        # C.kr241,
        C.kf242,
        # C.kr242,
        # C.kf243,
        # C.kr243,
        # C.kf244,
        # C.kr244,
        # C.kf245,
        # C.kr245,
        # C.kf246,
        # C.kr246,
        # C.kf247,
        # C.kr247,
        # C.kf248,
        # C.kr248,
        # C.kf249,
        # C.kr249,
        # C.kf250,
        # C.kr250,
        # C.kf251,
        # C.kr251,
        # C.kf252,
        # C.kr252,
        # C.kf253,
        # C.kr253,
        # C.kf254,
        # C.kr254,
        # C.kf255,
        # C.kr255,
        # C.kf256,
        # C.kr256,
        # C.kf257,
        # C.kr257,
        # C.kf258,
        # C.kr258,
        C.kf259,
        C.kr259,
        C.V260,
        C.K260,
        C.V261,
        C.K261,
        C.V262,
        C.K262,
        C.V263,
        C.K263,
        C.V264,
        C.K264,
        C.V265,
        C.K265,
        C.V266,
        C.K266,
        C.kf267,
        C.kf268,
        # C.kf269,
        # C.kf270,
        # C.kf271,
        # C.kf272,
        # C.kf273,
        # C.kf274,
        # C.kf275,
        # C.kf276,
        # C.kf277,
        # C.kf278,
        # C.kf279,
        # C.kf280,
        # C.kf281,
        # C.kf282,
        # C.kf283,
        # C.kf284,
        # C.kf285,
        # C.kf286,
        # C.kf287,
        # C.kf288,
        # C.kf289,
        # C.kf290,
        C.V291,
        C.K291,
        # C.n291,
        C.kf292,
        # C.kr292,
        C.kf293,
        C.kf294,
        C.V295,
        C.K295,
        C.kf296,
        C.kf297,
        C.kf298,
        C.kr298,
        C.kf299,
        C.kr299,
        C.kf300,
        C.kr300,
        C.kf301,
        C.kr301,
        C.kf302,
        C.kr302,
        # C.kf303,
        # C.kr303,
        # C.kf304,
        # C.kr304,
        # C.kf305,
        # C.kr305,
        # C.kf306,
        # C.kr306,
        # C.kf307,
        # C.kr307,
        C.V308,
        C.K308,
        C.V309,
        C.K309,
        C.V310,
        C.K310,
        # C.n310,
        C.KF310,
        # C.nF310,
        C.kf311,
        # C.kr311,
        C.kf312,
        C.kf313,
        C.V314,
        C.K314,
        C.V315,
        C.K315,
        C.V316,
        C.K316,
        C.V317,
        C.K317,
        C.kf318,
        C.kf319,
        #
        C.w_EGFR,
        C.w_ERBB2,
        C.w_ERBB3,
        C.w_ERBB4,
        #
        C.w_GRB2,
        #
        C.w_SHC1,
        C.w_SHC2,
        C.w_SHC3,
        C.w_SHC4,
        #
        C.w_RASA1,
        C.w_RASA2,
        C.w_RASA3,
        #
        C.w_PIK3CA,
        C.w_PIK3CB,
        C.w_PIK3CD,
        C.w_PIK3CG,
        #
        C.w_PTEN,
        #
        C.w_SOS1,
        C.w_SOS2,
        #
        C.w_GAB1,
        #
        C.w_HRAS,
        C.w_KRAS,
        C.w_NRAS,
        #
        C.w_ARAF,
        C.w_BRAF,
        C.w_RAF1,
        #
        C.w_MAP2K1,
        C.w_MAP2K2,
        #
        C.w_MAPK1,
        C.w_MAPK3,
        #
        C.w_AKT1,
        C.w_AKT2,
        #
        C.w_PTPN1,
        #
        C.w_GSK3B,
        #
        C.w_DUSP5,
        C.w_DUSP6,
        C.w_DUSP7,
        #
        C.w_MYC,
    ]

    # initial values
    search_idx_initials::Vector{Int} = [
        V.PIP2
    ]

    return search_idx_params, search_idx_initials
end

function get_search_region()::Matrix{Float64}
    p::Vector{Float64} = param_values()
    u0::Vector{Float64} = initial_values()

    search_idx::Tuple{Array{Int64,1},Array{Int64,1}} = get_search_index()
    search_param::Vector{Float64} = init_search_param(search_idx, p, u0)

    search_rgn::Matrix{Float64} = zeros(2, length(p) + length(u0))

    # Default: 0.1 ~ 10x
    for (i, j) in enumerate(search_idx[1])
        search_rgn[1,j] = 1e-4  # lower bound
        search_rgn[2,j] = 1e+5  # upper bound
    end

    # Default: 0.5 ~ 2x
    for (i, j) in enumerate(search_idx[2])
        search_rgn[1,j + length(p)] = search_param[i + length(search_idx[1])] * 1  # lower bound
        search_rgn[2,j + length(p)] = search_param[i + length(search_idx[1])] * 1000  # upper bound
    end

    # search_rgn[:, C.param_name] = [lower_bound, upper_bound]
    # search_rgn[:, V.var_name+length(p)] = [lower_bound, upper_bound]

    for input_layer in [
        C.kf1,
        C.kr1,
        C.kf2,
        C.kr2,
        C.kf3,
        C.kr3,
        C.kf4,
        C.kr4,
        C.kf5,
        C.kr5,
        C.kf6,
        C.kr6,
        C.kf7,
        C.kr7,
        C.kf8,
        C.kr8,
        C.kf9,
        C.kr9,
    ]
        search_rgn[:, input_layer] = [1e-2, 1e+2]
    end

    for receptor_phosphorylation in [
        C.kf10,
        C.kr10,
        C.kf11,
        C.kr11,
        C.kf12,
        C.kr12,
        C.kf13,
        C.kr13,
        C.kf14,
        C.kr14,
        C.kf15,
        C.kr15,
    ]
        search_rgn[:, receptor_phosphorylation] = [1e-2, 1e+2]
    end

    for recepotor_adaptor in [
        C.kf16,
        C.kr16,
        C.kf17,
        C.kr17,
        C.kf18,
        C.kr18,
        C.kf19,
        C.kr19,
        C.kf20,
        C.kr20,
        C.kf21,
        C.kr21,
        C.kf22,
        C.kr22,
        C.kf23,
        C.kr23,
        C.kf24,
        C.kr24,
        C.kf25,
        C.kr25,
        C.kf26,
        C.kr26,
        C.kf27,
        C.kr27,
        C.kf28,
        C.kr28,
        C.kf29,
        C.kr29,
        C.kf30,
        C.kr30,
        C.kf31,
        C.kr31,
        C.kf32,
        C.kr32,
        C.kf33,
        C.kr33,
        C.kf34,
        C.kr34,
        C.kf35,
        C.kr35,
        C.kf36,
        C.kr36,
        C.kf37,
        C.kr37,
    ]
        search_rgn[:, recepotor_adaptor] = [1e-4, 1e+2]
    end

    for degradation in [
        C.kf267,
        C.kf268,
        C.kf294,
        C.kf296,
        C.kf297,
        C.kf312,
        C.kf318,
        C.kf319,
    ]
        search_rgn[:, degradation] = [1e-4, 1e+0]
    end

    for EC50 in [
        C.K110,
        C.K111,
        C.K112,
        C.K113,
        C.K114,
        C.K115,
        C.K116,
        C.K117,
        C.K118,
        C.K119,
        C.K120,
        C.K121,
        C.K260,
        C.K261,
        C.K262,
        C.K263,
        C.K264,
        C.K265,
        C.K266,
        C.K291,
        C.K295,
        C.K308,
        C.K309,
        C.K310,
        C.K314,
        C.K315,
        C.K316,
        C.K317,
    ]
        search_rgn[:, EC50] = [1e+0, 1e+4]
    end

    search_rgn[:, C.w_EGFR] = [0.01, 100]
    search_rgn[:, C.w_ERBB2] = [0.01, 100]
    search_rgn[:, C.w_ERBB3] = [0.01, 100]
    search_rgn[:, C.w_ERBB4] = [0.01, 100]
    #
    search_rgn[:, C.w_GRB2] = [0.01, 100]
    #
    search_rgn[:, C.w_SHC1] = [0.01, 100]
    search_rgn[:, C.w_SHC2] = [0.01, 100]
    search_rgn[:, C.w_SHC3] = [0.01, 100]
    search_rgn[:, C.w_SHC4] = [0.01, 100]
    #
    search_rgn[:, C.w_RASA1] = [0.01, 100]
    search_rgn[:, C.w_RASA2] = [0.01, 100]
    search_rgn[:, C.w_RASA3] = [0.01, 100]
    #
    search_rgn[:, C.w_PIK3CA] = [0.01, 100]
    search_rgn[:, C.w_PIK3CB] = [0.01, 100]
    search_rgn[:, C.w_PIK3CD] = [0.01, 100]
    search_rgn[:, C.w_PIK3CG] = [0.01, 100]
    #
    search_rgn[:, C.w_PTEN] = [0.01, 100]
    #
    search_rgn[:, C.w_SOS1] = [0.01, 100]
    search_rgn[:, C.w_SOS2] = [0.01, 100]
    #
    search_rgn[:, C.w_GAB1] = [0.01, 100]
    #
    search_rgn[:, C.w_HRAS] = [0.01, 100]
    search_rgn[:, C.w_KRAS] = [0.01, 100]
    search_rgn[:, C.w_NRAS] = [0.01, 100]
    #
    search_rgn[:, C.w_ARAF] = [0.01, 100]
    search_rgn[:, C.w_BRAF] = [0.01, 100]
    search_rgn[:, C.w_RAF1] = [0.01, 100]
    #
    search_rgn[:, C.w_MAP2K1] = [0.01, 100]
    search_rgn[:, C.w_MAP2K2] = [0.01, 100]
    #
    search_rgn[:, C.w_MAPK1] = [0.01, 100]
    search_rgn[:, C.w_MAPK3] = [0.01, 100]
    #
    search_rgn[:, C.w_AKT1] = [0.01, 100]
    search_rgn[:, C.w_AKT2] = [0.01, 100]
    #
    # search_rgn[:, C.w_RPS6KA1] = [0.01, 100]
    # search_rgn[:, C.w_RPS6KA2] = [0.01, 100]
    # search_rgn[:, C.w_RPS6KA3] = [0.01, 100]
    # search_rgn[:, C.w_RPS6KA6] = [0.01, 100]
    #
    # search_rgn[:, C.w_PPP2CA] = [0.01, 100]
    # search_rgn[:, C.w_PPP2CB] = [0.01, 100]
    #
    search_rgn[:, C.w_PTPN1] = [0.01, 100]
    #
    # search_rgn[:, C.w_CREB1] = [0.01, 100]
    #
    # search_rgn[:, C.w_ELK1] = [0.01, 100]
    #
    search_rgn[:, C.w_GSK3B] = [0.01, 100]
    #
    search_rgn[:, C.w_DUSP5] = [0.01, 100]
    search_rgn[:, C.w_DUSP6] = [0.01, 100]
    search_rgn[:, C.w_DUSP7] = [0.01, 100]
    #
    # search_rgn[:, C.w_FOS] = [0.01, 100]
    #
    search_rgn[:, C.w_MYC] = [0.01, 100]

    search_rgn = conv_lin2log!(search_rgn, search_idx)

    return search_rgn
end

function update_param(indiv::Vector{Float64})::Tuple{Array{Float64,1},Array{Float64,1}}
    p::Vector{Float64} = param_values()
    u0::Vector{Float64} = initial_values()

    search_idx::Tuple{Array{Int64,1},Array{Int64,1}} = get_search_index()

    for (i, j) in enumerate(search_idx[1])
        @inbounds p[j] = indiv[i]
    end
    for (i, j) in enumerate(search_idx[2])
        @inbounds u0[j] = indiv[i + length(search_idx[1])]
    end

    (p, u0) = individualize!(p, u0, "MCF7_BREAST")

    # constraints --------------------------------------------------------------
    p[C.kf39] = p[C.kf38]
    p[C.kr39] = p[C.kr38]
    p[C.kf40] = p[C.kf38]
    p[C.kr40] = p[C.kr38]
    p[C.kf41] = p[C.kf38]
    p[C.kr41] = p[C.kr38]
    p[C.kf42] = p[C.kf38]
    p[C.kr42] = p[C.kr38]
    p[C.kf43] = p[C.kf38]
    p[C.kr43] = p[C.kr38]
    p[C.kf45] = p[C.kf44]
    p[C.kr45] = p[C.kr44]
    p[C.kf46] = p[C.kf44]
    p[C.kr46] = p[C.kr44]
    p[C.kf47] = p[C.kf44]
    p[C.kr47] = p[C.kr44]
    p[C.kf48] = p[C.kf44]
    p[C.kr48] = p[C.kr44]
    p[C.kf49] = p[C.kf44]
    p[C.kr49] = p[C.kr44]
    p[C.kf51] = p[C.kf50]
    p[C.kr51] = p[C.kr50]
    p[C.kf52] = p[C.kf50]
    p[C.kr52] = p[C.kr50]
    p[C.kf53] = p[C.kf50]
    p[C.kr53] = p[C.kr50]
    p[C.kf54] = p[C.kf50]
    p[C.kr54] = p[C.kr50]
    p[C.kf55] = p[C.kf50]
    p[C.kr55] = p[C.kr50]
    p[C.kf57] = p[C.kf56]
    p[C.kr57] = p[C.kr56]
    p[C.kf58] = p[C.kf56]
    p[C.kr58] = p[C.kr56]
    p[C.kf59] = p[C.kf56]
    p[C.kr59] = p[C.kr56]
    p[C.kf60] = p[C.kf56]
    p[C.kr60] = p[C.kr56]
    p[C.kf61] = p[C.kf56]
    p[C.kr61] = p[C.kr56]
    p[C.kf63] = p[C.kf62]
    p[C.kr63] = p[C.kr62]
    p[C.kf64] = p[C.kf62]
    p[C.kr64] = p[C.kr62]
    p[C.kf65] = p[C.kf62]
    p[C.kr65] = p[C.kr62]
    p[C.kf66] = p[C.kf62]
    p[C.kr66] = p[C.kr62]
    p[C.kf67] = p[C.kf62]
    p[C.kr67] = p[C.kr62]
    p[C.kf68] = p[C.kf62]
    p[C.kr68] = p[C.kr62]
    p[C.kf69] = p[C.kf62]
    p[C.kr69] = p[C.kr62]
    p[C.kf70] = p[C.kf62]
    p[C.kr70] = p[C.kr62]
    p[C.kf71] = p[C.kf62]
    p[C.kr71] = p[C.kr62]
    p[C.kf72] = p[C.kf62]
    p[C.kr72] = p[C.kr62]
    p[C.kf73] = p[C.kf62]
    p[C.kr73] = p[C.kr62]
    p[C.kf75] = p[C.kf74]
    p[C.kr75] = p[C.kr74]
    p[C.kf76] = p[C.kf74]
    p[C.kr76] = p[C.kr74]
    p[C.kf77] = p[C.kf74]
    p[C.kr77] = p[C.kr74]
    p[C.kf78] = p[C.kf74]
    p[C.kr78] = p[C.kr74]
    p[C.kf79] = p[C.kf74]
    p[C.kr79] = p[C.kr74]
    p[C.kf80] = p[C.kf74]
    p[C.kr80] = p[C.kr74]
    p[C.kf81] = p[C.kf74]
    p[C.kr81] = p[C.kr74]
    p[C.kf82] = p[C.kf74]
    p[C.kr82] = p[C.kr74]
    p[C.kf83] = p[C.kf74]
    p[C.kr83] = p[C.kr74]
    p[C.kf84] = p[C.kf74]
    p[C.kr84] = p[C.kr74]
    p[C.kf85] = p[C.kf74]
    p[C.kr85] = p[C.kr74]
    p[C.kf87] = p[C.kf86]
    p[C.kr87] = p[C.kr86]
    p[C.kf88] = p[C.kf86]
    p[C.kr88] = p[C.kr86]
    p[C.kf89] = p[C.kf86]
    p[C.kr89] = p[C.kr86]
    p[C.kf90] = p[C.kf86]
    p[C.kr90] = p[C.kr86]
    p[C.kf91] = p[C.kf86]
    p[C.kr91] = p[C.kr86]
    p[C.kf92] = p[C.kf86]
    p[C.kr92] = p[C.kr86]
    p[C.kf93] = p[C.kf86]
    p[C.kr93] = p[C.kr86]
    p[C.kf94] = p[C.kf86]
    p[C.kr94] = p[C.kr86]
    p[C.kf95] = p[C.kf86]
    p[C.kr95] = p[C.kr86]
    p[C.kf96] = p[C.kf86]
    p[C.kr96] = p[C.kr86]
    p[C.kf97] = p[C.kf86]
    p[C.kr97] = p[C.kr86]
    p[C.kf99] = p[C.kf98]
    p[C.kr99] = p[C.kr98]
    p[C.kf100] = p[C.kf98]
    p[C.kr100] = p[C.kr98]
    p[C.kf101] = p[C.kf98]
    p[C.kr101] = p[C.kr98]
    p[C.kf102] = p[C.kf98]
    p[C.kr102] = p[C.kr98]
    p[C.kf103] = p[C.kf98]
    p[C.kr103] = p[C.kr98]
    p[C.kf104] = p[C.kf98]
    p[C.kr104] = p[C.kr98]
    p[C.kf105] = p[C.kf98]
    p[C.kr105] = p[C.kr98]
    p[C.kf106] = p[C.kf98]
    p[C.kr106] = p[C.kr98]
    p[C.kf107] = p[C.kf98]
    p[C.kr107] = p[C.kr98]
    p[C.kf108] = p[C.kf98]
    p[C.kr108] = p[C.kr98]
    p[C.kf109] = p[C.kf98]
    p[C.kr109] = p[C.kr98]
    p[C.kf123] = p[C.kf122]
    p[C.kr123] = p[C.kr122]
    p[C.kf124] = p[C.kf122]
    p[C.kr124] = p[C.kr122]
    p[C.kf125] = p[C.kf122]
    p[C.kr125] = p[C.kr122]
    p[C.kf126] = p[C.kf122]
    p[C.kr126] = p[C.kr122]
    p[C.kf127] = p[C.kf122]
    p[C.kr127] = p[C.kr122]
    p[C.kf128] = p[C.kf122]
    p[C.kr128] = p[C.kr122]
    p[C.kf129] = p[C.kf122]
    p[C.kr129] = p[C.kr122]
    p[C.kf130] = p[C.kf122]
    p[C.kr130] = p[C.kr122]
    p[C.kf131] = p[C.kf122]
    p[C.kr131] = p[C.kr122]
    p[C.kf132] = p[C.kf122]
    p[C.kr132] = p[C.kr122]
    p[C.kf133] = p[C.kf122]
    p[C.kr133] = p[C.kr122]
    p[C.kf134] = p[C.kf122]
    p[C.kr134] = p[C.kr122]
    p[C.kf136] = p[C.kf135]
    p[C.kr136] = p[C.kr135]
    p[C.kf137] = p[C.kf135]
    p[C.kr137] = p[C.kr135]
    p[C.kf138] = p[C.kf135]
    p[C.kr138] = p[C.kr135]
    p[C.kf139] = p[C.kf135]
    p[C.kr139] = p[C.kr135]
    p[C.kf140] = p[C.kf135]
    p[C.kr140] = p[C.kr135]
    p[C.kf141] = p[C.kf135]
    p[C.kr141] = p[C.kr135]
    p[C.kf142] = p[C.kf135]
    p[C.kr142] = p[C.kr135]
    p[C.kf143] = p[C.kf135]
    p[C.kr143] = p[C.kr135]
    p[C.kf144] = p[C.kf135]
    p[C.kr144] = p[C.kr135]
    p[C.kf145] = p[C.kf135]
    p[C.kr145] = p[C.kr135]
    p[C.kf146] = p[C.kf135]
    p[C.kr146] = p[C.kr135]
    p[C.kf147] = p[C.kf135]
    p[C.kr147] = p[C.kr135]
    p[C.kf149] = p[C.kf148]
    p[C.kr149] = p[C.kr148]
    p[C.kf150] = p[C.kf148]
    p[C.kr150] = p[C.kr148]
    p[C.kf151] = p[C.kf148]
    p[C.kr151] = p[C.kr148]
    p[C.kf152] = p[C.kf148]
    p[C.kr152] = p[C.kr148]
    p[C.kf153] = p[C.kf148]
    p[C.kr153] = p[C.kr148]
    p[C.kf154] = p[C.kf148]
    p[C.kr154] = p[C.kr148]
    p[C.kf155] = p[C.kf148]
    p[C.kr155] = p[C.kr148]
    p[C.kf156] = p[C.kf148]
    p[C.kr156] = p[C.kr148]
    p[C.kf157] = p[C.kf148]
    p[C.kr157] = p[C.kr148]
    p[C.kf158] = p[C.kf148]
    p[C.kr158] = p[C.kr148]
    p[C.kf159] = p[C.kf148]
    p[C.kr159] = p[C.kr148]
    p[C.kf160] = p[C.kf148]
    p[C.kr160] = p[C.kr148]
    p[C.kf162] = p[C.kf161]
    p[C.kr162] = p[C.kr161]
    p[C.kf163] = p[C.kf161]
    p[C.kr163] = p[C.kr161]
    p[C.kf164] = p[C.kf161]
    p[C.kr164] = p[C.kr161]
    p[C.kf165] = p[C.kf161]
    p[C.kr165] = p[C.kr161]
    p[C.kf166] = p[C.kf161]
    p[C.kr166] = p[C.kr161]
    p[C.kf167] = p[C.kf161]
    p[C.kr167] = p[C.kr161]
    p[C.kf168] = p[C.kf161]
    p[C.kr168] = p[C.kr161]
    p[C.kf169] = p[C.kf161]
    p[C.kr169] = p[C.kr161]
    p[C.kf170] = p[C.kf161]
    p[C.kr170] = p[C.kr161]
    p[C.kf171] = p[C.kf161]
    p[C.kr171] = p[C.kr161]
    p[C.kf172] = p[C.kf161]
    p[C.kr172] = p[C.kr161]
    p[C.kf173] = p[C.kf161]
    p[C.kr173] = p[C.kr161]
    p[C.kf175] = p[C.kf174]
    p[C.kr175] = p[C.kr174]
    p[C.kf176] = p[C.kf174]
    p[C.kr176] = p[C.kr174]
    p[C.kf177] = p[C.kf174]
    p[C.kr177] = p[C.kr174]
    p[C.kf178] = p[C.kf174]
    p[C.kr178] = p[C.kr174]
    p[C.kf179] = p[C.kf174]
    p[C.kr179] = p[C.kr174]
    p[C.kf180] = p[C.kf174]
    p[C.kr180] = p[C.kr174]
    p[C.kf181] = p[C.kf174]
    p[C.kr181] = p[C.kr174]
    p[C.kf182] = p[C.kf174]
    p[C.kr182] = p[C.kr174]
    p[C.kf183] = p[C.kf174]
    p[C.kr183] = p[C.kr174]
    p[C.kf184] = p[C.kf174]
    p[C.kr184] = p[C.kr174]
    p[C.kf185] = p[C.kf174]
    p[C.kr185] = p[C.kr174]
    p[C.kf186] = p[C.kf174]
    p[C.kr186] = p[C.kr174]
    p[C.kf187] = p[C.kf174]
    p[C.kr187] = p[C.kr174]
    p[C.kf188] = p[C.kf174]
    p[C.kr188] = p[C.kr174]
    p[C.kf189] = p[C.kf174]
    p[C.kr189] = p[C.kr174]
    p[C.kf190] = p[C.kf174]
    p[C.kr190] = p[C.kr174]
    p[C.kf191] = p[C.kf174]
    p[C.kr191] = p[C.kr174]
    p[C.kf192] = p[C.kf174]
    p[C.kr192] = p[C.kr174]
    p[C.kf194] = p[C.kf193]
    p[C.kr194] = p[C.kr193]
    p[C.kf195] = p[C.kf193]
    p[C.kr195] = p[C.kr193]
    p[C.kf196] = p[C.kf193]
    p[C.kr196] = p[C.kr193]
    p[C.kf197] = p[C.kf193]
    p[C.kr197] = p[C.kr193]
    p[C.kf198] = p[C.kf193]
    p[C.kr198] = p[C.kr193]
    p[C.kf199] = p[C.kf193]
    p[C.kr199] = p[C.kr193]
    p[C.kf200] = p[C.kf193]
    p[C.kr200] = p[C.kr193]
    p[C.kf201] = p[C.kf193]
    p[C.kr201] = p[C.kr193]
    p[C.kf202] = p[C.kf193]
    p[C.kr202] = p[C.kr193]
    p[C.kf203] = p[C.kf193]
    p[C.kr203] = p[C.kr193]
    p[C.kf204] = p[C.kf193]
    p[C.kr204] = p[C.kr193]
    p[C.kf205] = p[C.kf193]
    p[C.kr205] = p[C.kr193]
    p[C.kf206] = p[C.kf193]
    p[C.kr206] = p[C.kr193]
    p[C.kf207] = p[C.kf193]
    p[C.kr207] = p[C.kr193]
    p[C.kf208] = p[C.kf193]
    p[C.kr208] = p[C.kr193]
    p[C.kf209] = p[C.kf193]
    p[C.kr209] = p[C.kr193]
    p[C.kf210] = p[C.kf193]
    p[C.kr210] = p[C.kr193]
    p[C.kf211] = p[C.kf193]
    p[C.kr211] = p[C.kr193]
    p[C.kf213] = p[C.kf212]
    p[C.kr213] = p[C.kr212]
    p[C.kf214] = p[C.kf212]
    p[C.kr214] = p[C.kr212]
    p[C.kf215] = p[C.kf212]
    p[C.kr215] = p[C.kr212]
    p[C.kf216] = p[C.kf212]
    p[C.kr216] = p[C.kr212]
    p[C.kf217] = p[C.kf212]
    p[C.kr217] = p[C.kr212]
    p[C.kf218] = p[C.kf212]
    p[C.kr218] = p[C.kr212]
    p[C.kf219] = p[C.kf212]
    p[C.kr219] = p[C.kr212]
    p[C.kf220] = p[C.kf212]
    p[C.kr220] = p[C.kr212]
    p[C.kf221] = p[C.kf212]
    p[C.kr221] = p[C.kr212]
    p[C.kf222] = p[C.kf212]
    p[C.kr222] = p[C.kr212]
    p[C.kf223] = p[C.kf212]
    p[C.kr223] = p[C.kr212]
    p[C.kf224] = p[C.kf212]
    p[C.kr224] = p[C.kr212]
    p[C.kf226] = p[C.kf225]
    p[C.kr226] = p[C.kr225]
    p[C.kf227] = p[C.kf225]
    p[C.kr227] = p[C.kr225]
    p[C.kf228] = p[C.kf225]
    p[C.kr228] = p[C.kr225]
    p[C.kf229] = p[C.kf225]
    p[C.kr229] = p[C.kr225]
    p[C.kf230] = p[C.kf225]
    p[C.kr230] = p[C.kr225]
    p[C.kf231] = p[C.kf225]
    p[C.kr231] = p[C.kr225]
    p[C.kf232] = p[C.kf225]
    p[C.kr232] = p[C.kr225]
    p[C.kf233] = p[C.kf225]
    p[C.kr233] = p[C.kr225]
    p[C.kf234] = p[C.kf225]
    p[C.kr234] = p[C.kr225]
    p[C.kf235] = p[C.kf225]
    p[C.kr235] = p[C.kr225]
    p[C.kf236] = p[C.kf225]
    p[C.kr236] = p[C.kr225]
    p[C.kf237] = p[C.kf225]
    p[C.kr237] = p[C.kr225]
    p[C.kf238] = p[C.kf225]
    p[C.kr238] = p[C.kr225]
    p[C.kf239] = p[C.kf225]
    p[C.kr239] = p[C.kr225]
    p[C.kf240] = p[C.kf225]
    p[C.kr240] = p[C.kr225]
    p[C.kf241] = p[C.kf225]
    p[C.kr241] = p[C.kr225]
    p[C.kf243] = p[C.kf242]
    p[C.kr243] = p[C.kr242]
    p[C.kf244] = p[C.kf242]
    p[C.kr244] = p[C.kr242]
    p[C.kf245] = p[C.kf242]
    p[C.kr245] = p[C.kr242]
    p[C.kf246] = p[C.kf242]
    p[C.kr246] = p[C.kr242]
    p[C.kf247] = p[C.kf242]
    p[C.kr247] = p[C.kr242]
    p[C.kf248] = p[C.kf242]
    p[C.kr248] = p[C.kr242]
    p[C.kf249] = p[C.kf242]
    p[C.kr249] = p[C.kr242]
    p[C.kf250] = p[C.kf242]
    p[C.kr250] = p[C.kr242]
    p[C.kf251] = p[C.kf242]
    p[C.kr251] = p[C.kr242]
    p[C.kf252] = p[C.kf242]
    p[C.kr252] = p[C.kr242]
    p[C.kf253] = p[C.kf242]
    p[C.kr253] = p[C.kr242]
    p[C.kf254] = p[C.kf242]
    p[C.kr254] = p[C.kr242]
    p[C.kf255] = p[C.kf242]
    p[C.kr255] = p[C.kr242]
    p[C.kf256] = p[C.kf242]
    p[C.kr256] = p[C.kr242]
    p[C.kf257] = p[C.kf242]
    p[C.kr257] = p[C.kr242]
    p[C.kf258] = p[C.kf242]
    p[C.kr258] = p[C.kr242]
    p[C.kf269] = p[C.kf268]
    p[C.kf270] = p[C.kf268]
    p[C.kf271] = p[C.kf268]
    p[C.kf272] = p[C.kf268]
    p[C.kf273] = p[C.kf268]
    p[C.kf274] = p[C.kf268]
    p[C.kf275] = p[C.kf268]
    p[C.kf276] = p[C.kf268]
    p[C.kf277] = p[C.kf268]
    p[C.kf278] = p[C.kf268]
    p[C.kf279] = p[C.kf268]
    p[C.kf280] = p[C.kf268]
    p[C.kf281] = p[C.kf268]
    p[C.kf282] = p[C.kf268]
    p[C.kf283] = p[C.kf268]
    p[C.kf284] = p[C.kf268]
    p[C.kf285] = p[C.kf268]
    p[C.kf286] = p[C.kf268]
    p[C.kf287] = p[C.kf268]
    p[C.kf288] = p[C.kf268]
    p[C.kf289] = p[C.kf268]
    p[C.kf290] = p[C.kf268]
    p[C.kf303] = p[C.kf298]
    p[C.kr303] = p[C.kr298]
    p[C.kf304] = p[C.kf299]
    p[C.kr304] = p[C.kr299]
    p[C.kf305] = p[C.kf300]
    p[C.kr305] = p[C.kr300]
    p[C.kf306] = p[C.kf301]
    p[C.kr306] = p[C.kr301]
    p[C.kf307] = p[C.kf302]
    p[C.kr307] = p[C.kr302]
    # --------------------------------------------------------------------------

    return p, u0
end

function decode_gene2val(indiv_gene::Vector{Float64})::Vector{Float64}
    search_rgn::Matrix{Float64} = get_search_region()
    indiv::Vector{Float64} = zeros(length(indiv_gene))

    for i in eachindex(indiv_gene)
        indiv[i] = 10^(
            indiv_gene[i] * (
                search_rgn[2,i] - search_rgn[1,i]
            ) + search_rgn[1,i]
        )
    end

    return round.(indiv, sigdigits=7)
end

function encode_val2gene(indiv::Vector{Float64})
    search_rgn::Matrix{Float64} = get_search_region()
    indiv_gene::Vector{Float64} = zeros(length(indiv))

    for i in eachindex(indiv)
        indiv_gene[i] = (
            log10(indiv[i]) - search_rgn[1,i]
        ) / (
            search_rgn[2,i] - search_rgn[1,i]
        )
    end

    return indiv_gene
end

function encode_bestIndivVal2randGene(
        idx::Int64,
        best_indiv::Vector{Float64},
        p0_bounds::Vector{Float64})::Float64
    search_rgn::Matrix{Float64} = get_search_region()
    rand_gene::Float64 = (
        log10(
            best_indiv[idx] * 10^(
                rand() * log10(p0_bounds[2] / p0_bounds[1]) + log10(p0_bounds[1])
            )
        ) - search_rgn[1,idx]
    ) / (
        search_rgn[2,idx] - search_rgn[1,idx]
    )
    return rand_gene
end

function init_search_param(
        search_idx::Tuple{Array{Int64,1},Array{Int64,1}},
        p::Vector{Float64},
        u0::Vector{Float64})::Vector{Float64}
    duplicate::Vector{String} = []
    if length(search_idx[1]) != length(unique(search_idx[1]))
        for idx in findall([count(x -> x == i, search_idx[1])
                            for i in unique(search_idx[1])] .!= 1)
            push!(duplicate, C.NAMES[search_idx[1][idx]])
        end
        error(
            "Duplicate parameters (C.): $duplicate"
        )
    elseif length(search_idx[2]) != length(unique(search_idx[2]))
        for idx in findall([count(x -> x == i, search_idx[2])
                            for i in unique(search_idx[2])] .!= 1)
            push!(duplicate, V.NAMES[search_idx[2][idx]])
        end
        error(
            "Duplicate species (V.): $duplicate"
        )
    end
    search_param = zeros(
        length(search_idx[1]) + length(search_idx[2])
    )
    for (i, j) in enumerate(search_idx[1])
        @inbounds search_param[i] = p[j]
    end
    for (i, j) in enumerate(search_idx[2])
        @inbounds search_param[i + length(search_idx[1])] = u0[j]
    end

    if any(x -> x == 0.0, search_param)
        msg::String = "search_param must not contain zero."
        for idx in search_idx[1]
            if p[idx] == 0.0
                error(
                    @sprintf(
                        "`C.%s` in search_idx_params: ", C.NAMES[idx]
                    ) * msg
                )
            end
        end
        for idx in search_idx[2]
            if u0[idx] == 0.0
                error(
                    @sprintf(
                        "`V.%s` in search_idx_initials: ", V.NAMES[idx]
                    ) * msg
                )
            end
        end
    end

    return search_param
end

function conv_lin2log!(
        search_rgn::Matrix{Float64},
        search_idx::Tuple{Array{Int64,1},Array{Int64,1}})::Matrix{Float64}
    for i = 1:size(search_rgn, 2)
        if minimum(search_rgn[:,i]) < 0.0
            msg = "search_rgn[lower_bound,upper_bound] must be positive.\n"
            if i <= C.NUM
                error(@sprintf("`C.%s` ", C.NAMES[i]) * msg)
            else
                error(@sprintf("`V.%s` ", V.NAMES[i - C.NUM]) * msg)
            end
        elseif minimum(search_rgn[:,i]) == 0.0 && maximum(search_rgn[:,i]) != 0.0
            msg = "lower_bound must be larger than 0.\n"
            if i <= C.NUM
                error(@sprintf("`C.%s` ", C.NAMES[i]) * msg)
            else
                error(@sprintf("`V.%s` ", V.NAMES[i - C.NUM]) * msg)
            end
        elseif search_rgn[2,i] - search_rgn[1,i] < 0.0
            msg = "lower_bound must be smaller than upper_bound.\n"
            if i <= C.NUM
                error(@sprintf("`C.%s` ", C.NAMES[i]) * msg)
            else
                error(@sprintf("`V.%s` ", V.NAMES[i - C.NUM]) * msg)
            end
        end
    end

    nonzero_idx::Vector{Int} = []
    for i = 1:size(search_rgn, 2)
        if search_rgn[:,i] != [0.0,0.0]
            push!(nonzero_idx, i)
        end
    end
    difference::Vector{Int} = collect(
        symdiff(
            Set(nonzero_idx),
            Set(append!(search_idx[1], C.NUM .+ search_idx[2]))
        )
    )
    if length(difference) > 0
        for idx in difference
            if idx <= C.NUM
                println(@sprintf("`C.%s`", C.NAMES[Int(idx)]))
            else
                println(@sprintf("`V.%s`", V.NAMES[Int(idx) - C.NUM]))
            end
        end
        error(
            "Set these search_params in both search_idx and search_rgn."
        )
    end

    search_rgn = search_rgn[:,nonzero_idx]

    return log10.(search_rgn)
end