"""
`prun_we`: Run a parallel WE simulation, optionally returning the ensemble at
each step. This performs the mutation steps in parallel, and assumes a worker
pool has already been created.

### Arguments
* `E₀` - initial particle ensemble
* `B₀` - initial bin data structure
* `sampler` - WE sampler functions data structure
* `n_we_steps` - number of steps in the WE run
### Optional Arguments
* `n_save_iters = 1` - save the ensemble and bins every `n_save_iters` iterations.  Set `n_save_iters=n_we_steps` to only save the final values.
"""
function prun_we(E₀::TE, B₀::TB, sampler::TWES, n_we_steps::Int; n_save_iters = 1) where
   {TE<:Ensemble, TB<:Bins, TWES<:DistributedWEsampler}

   E = deepcopy(E₀);
   B = deepcopy(B₀);
   E_trajectory = TE[];
   B_trajectory = TB[];

   for t in 0:n_we_steps-1
      # first selection is at t = 0
      sampler.selection!(E, B, t);
      copy!(E.ω, E.ω̂);
      @. E.d = deepcopy(E.d̂);
      E.ξ .= pmap(sampler.mutation, E.ξ̂);
      # after mutation, time is t ↦ t+1
      sampler.rebin!(E, B, t+1);
      # analysis
      sampler.analysis!(E, B, t+1);

      if(mod(t+1, n_save_iters)==0)
         push!(E_trajectory, deepcopy(E))
         push!(B_trajectory, deepcopy(B))
      end
   end

   return E_trajectory, B_trajectory

end

"""
`prun_we_observables`: Run a parallel WE simulation, optionally returning the ensemble at
each step. This performs the mutation steps in parallel, and assumes a worker
pool has already been created.

### Arguments
* `E₀` - initial particle ensemble
* `B₀` - initial bin data structure
* `sampler` - WE sampler functions data structure
* `n_we_steps` - number of steps in the WE run
* `observables` - Tuple of scalar observable functions for the ergodic average
"""
function prun_we_observables(E₀::TE, B₀::TB, sampler::TWES, n_we_steps::Int, observables::Tuple{Vararg{<:Function,NO}}) where
   {TE<:Ensemble, TB<:Bins, TWES<:DistributedWEsampler, NO}

   E = deepcopy(E₀);
   B = deepcopy(B₀);
   observables_trajectory = zeros(NO, n_we_steps);

   for t in 0:n_we_steps-1
      # first selection is at t = 0
      sampler.selection!(E, B, t);
      copy!(E.ω, E.ω̂);
      @. E.d = deepcopy(E.d̂);
      E.ξ .= pmap(sampler.mutation, E.ξ̂);
      # after mutation, time is t ↦ t+1
      sampler.rebin!(E, B, t+1);
      # analysis
      sampler.analysis!(E, B, t+1);

      ntuple(k-> observables_trajectory[k,t+1] =(observables[k]).(E.ξ) ⋅ E.ω, NO)
   end

   return observables_trajectory
end

"""
`prun_we!`: Run an in place parallel WE simulation.  This performs the mutation
steps in parallel, and assumes a worker pool has already been created.

### Arguments
* `E` - particle ensemble
* `B` - bin data structure
* `sampler` - WE sampler functions data structure
* `n_we_steps` - number of steps in the WE run
"""
function prun_we!(E::TE, B::TB, sampler::TWES, n_we_steps::Int) where
   {TE<:Ensemble, TB<:Bins, TWES<:DistributedWEsampler}

   for t in 0:n_we_steps-1
      # first selection is at t = 0
      sampler.selection!(E, B, t);
      copy!(E.ω, E.ω̂);
      @. E.d = deepcopy(E.d̂);
      E.ξ .= pmap(sampler.mutation, E.ξ̂);
      # after mutation, time is t ↦ t+1
      sampler.rebin!(E, B, t+1);
      # analysis
      sampler.analysis!(E, B, t+1);

   end
   E, B

end


include("AbstractTypes/AbstractType.jl")

mutable struct TechnologyHistory <: Buffer
    cashFlow::Array{Real,1} # 7 columns: Revenue, subsidy,VOM, fuelCost, emissionCost ,FOM, CAPEX, NPV
    dispatchHistory::Array{Real,1} # 3 columns: profitableHours,marginalHours,unprofitableHours
    hourlyHistory::Array{Real,2} # 4 columns:  Revenue, VOM, fuelCost, emissionCost
    dailyHistory::Array{Real,2} # 4 columns:  Revenue, VOM, fuelCost, emissionCost
    yearlyHistory::Array{Real,2} # 6 columns:  Revenue, VOM, fuelCost, emissionCost, FOM, CAPEX

    day2hour::Int64
    year2day::Int64

    Reinitialize!::Function # Reinitialize the history after prefiltering

    function TechnologyHistory(systemSettings::SystemSettings)
        self = new()
        self.cashFlow = zeros(8)
        self.dispatchHistory = zeros(3)
        self.day2hour = systemSettings.simulationSettings.day2hour
        self.year2day = systemSettings.simulationSettings.year2day
        self.hourlyHistory = zeros(self.day2hour,5)
        self.dailyHistory = zeros(self.year2day,5)
        self.yearlyHistory = Array{Real,2}(undef,0,7)

        self.Reinitialize! = function()
            self.cashFlow = zeros(8)
            self.dispatchHistory = zeros(3)
            self.hourlyHistory = zeros(self.day2hour,5)
            self.dailyHistory = zeros(self.year2day,5)
            self.yearlyHistory = Array{Real,2}(undef,0,7)
        end
        return self
    end
end

mutable struct DesignProperty <: Property
    availability::Real # range 0.0 - 1.0
    constructionTimeStamp::Real # The year that the plant is built
    constructionTime::Real # lead time
    deconstructionTime::Real # required time to dismantle
    efficiency::Real # Fuel -> electricity, range 0.0 - 1.0
    emissionLevel::Real #ton/MWh
    lifeTime::Real # life expectance of the plant
    installedUnitNumber::Real # The installed unit number of this plant
    unitCapacity::Real # capacity of each unit

    installedCapacity::Real # This is not direct input, calculated use its internal function
    maximumOutPut::Real

    CalculateInstalledCapacity::Function

    function DesignProperty(availability::Real, constructionTimeStamp::Real, constructionTime::Real, deconstructionTime::Real, efficiency::Real, emissionLevel::Real,lifeTime::Real, unitCapacity::Real)
        self = new()
        self.availability = availability
        self.constructionTimeStamp = constructionTimeStamp
        self.constructionTime = constructionTime
        self.deconstructionTime = deconstructionTime
        self.efficiency = efficiency
        self.emissionLevel = emissionLevel
        self.lifeTime = lifeTime
        self.unitCapacity = unitCapacity

        self.CalculateInstalledCapacity =function(unitNumber::Real)
            self.installedUnitNumber = unitNumber
            self.installedCapacity = self.installedUnitNumber * self.unitCapacity
            self.maximumOutPut = self.installedUnitNumber * self.unitCapacity
        end

        return self
    end
end

mutable struct EconomicProperty <: Property
    #Cost of each unit
    unitConstructionCost::Real #€/unit
    unitDeconstructionCost::Real #€/unit
    unitVOMCost::Real #€/MWh
    unitFOMCost::Real #€/unit/year

    domesticLearningRate::Real
    nonDomesticLearningRate::Real

    #Cost of the plant (which is composed of several plants)
    constructionCost::Real #€
    deconstructionCost::Real #€
    VOMCost::Real  #€/MWh
    fuelCost::Real #€/MWh
    emissionCost::Real #€/MWh
    FOMCost::Real #€/year
    yearlyInstallment::Float64  #€/year
    virtualHistory::TechnologyHistory # Store information during virtual auction
    realHistory::TechnologyHistory # Store information during real auction

    CalculatePlantParameter::Function

    function EconomicProperty(unitConstructionCost::Real,unitDeconstructionCost::Real, unitVOMCost::Real,unitFOMCost::Real,systemSettings::SystemSettings)
        self = new()
        self.unitConstructionCost = unitConstructionCost
        self.unitDeconstructionCost = unitDeconstructionCost
        self.unitVOMCost = unitVOMCost
        self.unitFOMCost = unitFOMCost
        self.virtualHistory = TechnologyHistory(systemSettings)
        self.realHistory = TechnologyHistory(systemSettings)

        #Calculate plant parameters if there are more than 1 unit
        self.CalculatePlantParameter = function(unitNumber::Real)
            self.constructionCost = self.unitConstructionCost * unitNumber
            self.deconstructionCost = self.unitDeconstructionCost * unitNumber
            self.VOMCost = self.unitVOMCost
            self.FOMCost = self.unitFOMCost * unitNumber
        end

        return self
    end
end

mutable struct ConventionalPlants <: Technology
    name::Symbol
    type::Symbol
    designProperties::DesignProperty
    economicProperties::EconomicProperty
    offerPrice::Float64 # €/MWh
    offerQuantity::Float64 # MWh
    state::Array{Symbol,1} # state1: online/offline; state2: subsidized/unsubsidized

    ChangeState::Function
    UpdateCost::Function
    MultipleUnits::Function
    CalculateYearlyInstallment::Function
    UpdateHistory!::Function
    AggregateHistory!::Function

    function ConventionalPlants(type::Symbol, designProperties::DesignProperty,economicProperties::EconomicProperty,systemSettings::SystemSettings)
        self = new()
        self.type = type
        self.designProperties = designProperties
        self.economicProperties = economicProperties

        self.UpdateCost = function(newEmissionPrice::Real,newFuelPrice::Real)
            self.economicProperties.emissionCost = newEmissionPrice * self.designProperties.emissionLevel
            self.economicProperties.fuelCost = newFuelPrice / self.designProperties.efficiency
            self.offerPrice = self.economicProperties.unitVOMCost + self.economicProperties.fuelCost
        end

        # NOTE: source: https://en.wikipedia.org/wiki/Mortgage_calculator
        self.CalculateYearlyInstallment = function(systemSettings::SystemSettings)
            interestRate = systemSettings.interestRate
            capital = self.economicProperties.constructionCost *(1-systemSettings.simulationSettings.downpaymentRatio)
            if interestRate > 0.0
                payBackPeriod = self.designProperties.lifeTime
                self.economicProperties.yearlyInstallment = (interestRate*(1 + interestRate) ^ payBackPeriod) / ((1 + interestRate) ^ payBackPeriod - 1) * capital
            end
            # When interest rate is 0, the formula is not valid
            if interestRate == 0.0
                self.economicProperties.yearlyInstallment = capital/self.designProperties.lifeTime
            end
        end

        # NOTE: some necessary initilization of this plant
        self.MultipleUnits = function(unitNumber::Real,systemSettings::SystemSettings)
            self.designProperties.CalculateInstalledCapacity(unitNumber)
            self.economicProperties.CalculatePlantParameter(unitNumber)
            self.offerPrice = self.economicProperties.VOMCost + self.economicProperties.fuelCost
            self.offerQuantity = self.designProperties.maximumOutPut
            self.CalculateYearlyInstallment(systemSettings)
        end

        # NOTE: Record the evaluation history or real life auction history
        self.UpdateHistory! = function(auctionIdentify::Symbol,history::TechnologyHistory,powerFlow::PowerFlow, hour::Int64,day::Int64,subsidyAmount::Real,systemSettings::SystemSettings)
            if auctionIdentify == :real
                repeatTime = 1
            end

            if auctionIdentify == :virtual
                repeatTime = systemSettings.milestoneYear
            end
            marketPrice = powerFlow.marketPrice
            quantity = powerFlow.quantity

            representativeDayWeightFactors = systemSettings.simulationSettings.representativeData[:Weights]
            year2day = systemSettings.simulationSettings.year2day
            day2hour = systemSettings.simulationSettings.day2hour

            #NOTE: update Revenue, VOM, FOM, CAPEX
            thisRevenue = marketPrice * quantity
            thisSubsidy = subsidyAmount * quantity
            thisVOM = self.economicProperties.unitVOMCost * quantity
            thisFuelCost = self.economicProperties.fuelCost * quantity
            thisEmissionCost = self.economicProperties.emissionCost * quantity

            history.hourlyHistory[hour,1] = thisRevenue
            history.hourlyHistory[hour,2] = thisSubsidy
            history.hourlyHistory[hour,3] = thisVOM
            history.hourlyHistory[hour,4] = thisFuelCost
            history.hourlyHistory[hour,5] = thisEmissionCost

            if hour == day2hour
                history.dailyHistory[day,:] = sum(history.hourlyHistory,dims=1) .* representativeDayWeightFactors[day,2]
                history.hourlyHistory = zeros(day2hour,5)
            end

            if day == year2day && hour == day2hour #End of a year
                thisYearlyRevenue = sum(history.dailyHistory[:,1])
                thisYearlySubsidy = sum(history.dailyHistory[:,2])
                thisYearlyVOM = sum(history.dailyHistory[:,3])
                thisYearlyFuelCost = sum(history.dailyHistory[:,4])
                thisYearlyEmissionCost = sum(history.dailyHistory[:,5])
                thisYearlyFOM = self.economicProperties.FOMCost
                thisYearlyHistory = repeat([thisYearlyRevenue thisYearlySubsidy thisYearlyVOM thisYearlyFuelCost thisYearlyEmissionCost thisYearlyFOM self.economicProperties.yearlyInstallment], repeatTime)
                #println("one year",size(thisYearlyHistory))
                #println(day," ",hour)
                history.yearlyHistory = vcat(history.yearlyHistory, thisYearlyHistory)
                history.dailyHistory = zeros(year2day,5)
                #println("Yearly: ", size(history.yearlyHistory))
            end

            #NOTE:update dispatch history
            if marketPrice > self.offerPrice
                history.dispatchHistory[1] += representativeDayWeightFactors[day,2] * repeatTime
            end
            if marketPrice == self.offerPrice
                history.dispatchHistory[2] += representativeDayWeightFactors[day,2] * repeatTime
            end
            if marketPrice < self.offerPrice
                history.dispatchHistory[3] += representativeDayWeightFactors[day,2] * repeatTime
            end
        end

        #NOTE: calculate total 1-> Revenue, 2-> VOM, 3-> FOM, 4-> CAPEX, 5-> NPV
        self.AggregateHistory! = function(history::TechnologyHistory,systemSettings::SystemSettings)
            interestRate = systemSettings.interestRate
            cashFlowHorizon = size(history.yearlyHistory)[1]
            if cashFlowHorizon>=1
                yearVector = collect(0:1:(cashFlowHorizon-1))
                lifeDiscountVector = exp.(-interestRate .* yearVector)
                history.cashFlow[1:7] = lifeDiscountVector' * history.yearlyHistory
                history.cashFlow[8] = sum(history.cashFlow[1:2]) - sum(history.cashFlow[3:7]) - self.economicProperties.constructionCost * systemSettings.simulationSettings.downpaymentRatio
            end
        end
        return self
    end
end

mutable struct RenewablePlants <:Technology
    name::Symbol
    type::Symbol #:OnShoreWind,:OffShoreWind,:Solar
    designProperties::DesignProperty
    economicProperties::EconomicProperty
    offerPrice::Float64
    offerQuantity::Float64
    capacityFactors::Matrix{Float64}

    ChangeState::Function
    MultipleUnits::Function
    TimeSeries::Function
    CalculateYearlyInstallment::Function
    UpdateHistory!::Function
    AggregateHistory!::Function

    function RenewablePlants(type::Symbol,designProperties::DesignProperty,economicProperties::EconomicProperty,capacityFactorImproveRate::Float64,systemSettings::SystemSettings)
        self = new()
        self.type = type
        self.designProperties = designProperties
        self.economicProperties = economicProperties
        self.capacityFactorImproveRate = capacityFactorImproveRate
        self.capacityFactors = systemSettings.simulationSettings.representativeData[self.type]

        # NOTE: source: https://en.wikipedia.org/wiki/Mortgage_calculator
        self.CalculateYearlyInstallment = function(systemSettings::SystemSettings)
            interestRate = systemSettings.interestRate
            capital = self.economicProperties.constructionCost *(1-systemSettings.simulationSettings.downpaymentRatio)
            if interestRate > 0.0
                payBackPeriod = self.designProperties.lifeTime
                self.economicProperties.yearlyInstallment = (interestRate*(1 + interestRate) ^ payBackPeriod) / ((1 + interestRate) ^ payBackPeriod - 1) * capital
                #println(self.economicProperties.yearlyInstallment)
            end
            # When interest rate is 0, the formula is not valid
            if interestRate == 0.0
                self.economicProperties.yearlyInstallment = capital/self.designProperties.lifeTime
                #println(self.economicProperties.yearlyInstallment)
            end
        end

        # NOTE: some necessary initilization of this plant
        self.MultipleUnits = function(unitNumber::Real,systemSettings::SystemSettings)
            self.designProperties.CalculateInstalledCapacity(unitNumber)
            self.economicProperties.CalculatePlantParameter(unitNumber)
            self.offerPrice = self.economicProperties.VOMCost
            #self.offerQuantity = self.designProperties.installedCapacity
            self.CalculateYearlyInstallment(systemSettings)
        end

        self.TimeSeries = function(day::Int64,hour::Int64)
            self.offerQuantity = self.capacityFactors[day,hour] * self.designProperties.maximumOutPut
        end

        # NOTE: Record the evaluation history or real life auction history
        self.UpdateHistory! = function(auctionIdentify::Symbol, history::TechnologyHistory,powerFlow::PowerFlow, hour::Int64,day::Int64,subsidyAmount::Real,systemSettings::SystemSettings)
            if auctionIdentify == :real
                repeatTime = 1
            end

            if auctionIdentify == :virtual
                repeatTime = systemSettings.milestoneYear
            end
            marketPrice = powerFlow.marketPrice
            quantity = powerFlow.quantity

            representativeDayWeightFactors = systemSettings.simulationSettings.representativeData[:Weights]
            year2day = systemSettings.simulationSettings.year2day
            day2hour = systemSettings.simulationSettings.day2hour

            #NOTE: update Revenue, VOM, FOM, CAPEX
            thisRevenue = marketPrice * quantity
            thisSubsidy = subsidyAmount * quantity
            thisVOM = self.economicProperties.VOMCost * quantity
            thisFuelCost = 0
            thisEmissionCost = 0

            history.hourlyHistory[hour,1] = thisRevenue
            history.hourlyHistory[hour,2] = thisSubsidy
            history.hourlyHistory[hour,3] = thisVOM
            history.hourlyHistory[hour,4] = thisFuelCost
            history.hourlyHistory[hour,5] = thisEmissionCost

            if hour == day2hour
                history.dailyHistory[day,:] = sum(history.hourlyHistory,dims=1) * representativeDayWeightFactors[day,2]
                history.hourlyHistory = zeros(day2hour,5)
            end

            if day == year2day && hour == day2hour #End of a year
                thisYearlyRevenue = sum(history.dailyHistory[:,1])
                thisYearlySubsidy = sum(history.dailyHistory[:,2])
                thisYearlyVOM = sum(history.dailyHistory[:,3])
                thisYearlyFuelCost = sum(history.dailyHistory[:,4])
                thisYearlyEmissionCost = sum(history.dailyHistory[:,5])
                thisYearlyFOM = self.economicProperties.FOMCost
                thisYearlyHistory = repeat([thisYearlyRevenue thisYearlySubsidy thisYearlyVOM thisYearlyFuelCost thisYearlyEmissionCost thisYearlyFOM self.economicProperties.yearlyInstallment], repeatTime)
                #println("one year",size(thisYearlyHistory))
                #println(day," ",hour)
                history.yearlyHistory = vcat(history.yearlyHistory, thisYearlyHistory)
                history.dailyHistory = zeros(year2day,5)
                #println("Yearly: ", size(history.yearlyHistory))
            end

            #NOTE:update dispatch history
            if marketPrice > self.offerPrice
                history.dispatchHistory[1] += representativeDayWeightFactors[day,2] * repeatTime
            end
            if marketPrice == self.offerPrice
                history.dispatchHistory[2] += representativeDayWeightFactors[day,2] * repeatTime
            end
            if marketPrice < self.offerPrice
                history.dispatchHistory[3] += representativeDayWeightFactors[day,2] * repeatTime
            end
        end

        #NOTE: calculate total 1-> Revenue, 2-> VOM, 3-> FOM, 4-> CAPEX, 5-> NPV
        self.AggregateHistory! = function(history::TechnologyHistory,systemSettings::SystemSettings)
            interestRate = systemSettings.interestRate
            cashFlowHorizon = size(history.yearlyHistory)[1]
            if cashFlowHorizon>=1
                yearVector = collect(0:1:(cashFlowHorizon-1))
                lifeDiscountVector = exp.(-interestRate .* yearVector)
                history.cashFlow[1:7] = lifeDiscountVector' * history.yearlyHistory
                history.cashFlow[8] = sum(history.cashFlow[1:2]) - sum(history.cashFlow[3:7]) - self.economicProperties.constructionCost * systemSettings.simulationSettings.downpaymentRatio
            end
        end
        return self
    end
end


"""
    initialize_sbm_model(config::Config)

Initial part of the SBM model concept. Reads the input settings and data as defined in the
Config object. Will return a Model that is ready to run.
"""
function initialize_sbm_model(config::Config)

    # unpack the paths to the NetCDF files
    tomldir = dirname(config)
    static_path = joinpath(tomldir, config.input.path_static)
    cyclic_path = joinpath(tomldir, config.input.path_static)
    dynamic_path = joinpath(tomldir, config.input.path_forcing)
    instate_path = joinpath(tomldir, config.state.path_input)
    output_path = joinpath(tomldir, config.output.path)

    Δt = Second(config.timestepsecs)
    sizeinmetres = get(config.model, "sizeinmetres", false)
    reinit = get(config.model, "reinit", true)
    do_snow = get(config.model, "snow", false)
    do_reservoirs = get(config.model, "reservoirs", false)
    do_lakes = get(config.model, "lakes", false)
    do_pits = get(config.model, "pits", false)

    kw_river_tstep = get(config.model, "kw_river_tstep", 0)
    kw_land_tstep = get(config.model, "kw_land_tstep", 0)

    nc = NCDataset(static_path)
    dims = dimnames(nc[param(config, "input.subcatchment")])

    # There is no need to permute the dimensions of the data, since the active indices are
    # correctly calculated in both ways.
    # The dimension order only needs to be known for interpreting the LDD directions
    # and creating the coordinate maps.
    dims_xy = dims[2] in ("y", "lat")

    subcatch_2d = ncread(nc, param(config, "input.subcatchment"); allow_missing = true)
    # indices based on catchment
    inds, rev_inds = active_indices(subcatch_2d, missing)
    n = length(inds)
    modelsize_2d = size(subcatch_2d)

    river_2d = ncread(nc, param(config, "input.river_location"); type = Bool, fill = false)
    river = river_2d[inds]
    riverwidth_2d =
        ncread(nc, param(config, "input.lateral.river.width"); type = Float64, fill = 0)
    riverwidth = riverwidth_2d[inds]
    riverlength_2d =
        ncread(nc, param(config, "input.lateral.river.length"); type = Float64, fill = 0)
    riverlength = riverlength_2d[inds]

    # read x, y coordinates and calculate cell length [m]
    y_nc = "y" in keys(nc.dim) ? ncread(nc, "y") : ncread(nc, "lat")
    x_nc = "x" in keys(nc.dim) ? ncread(nc, "x") : ncread(nc, "lon")
    if dims_xy
        y = permutedims(repeat(y_nc, outer = (1, length(x_nc))))[inds]
    else
        y = repeat(y_nc, outer = (1, length(x_nc)))[inds]
    end
    cellength = abs(mean(diff(x_nc)))

    xl = fill(mv, n)
    yl = fill(mv, n)
    riverfrac = fill(mv, n)

    for i = 1:n
        xl[i] = sizeinmetres ? cellength : lattometres(y[i])[1] * cellength
        yl[i] = sizeinmetres ? cellength : lattometres(y[i])[2] * cellength
        riverfrac[i] =
            river[i] ? min((riverlength[i] * riverwidth[i]) / (xl[i] * yl[i]), 1.0) : 0.0
    end

    sbm = initialize_sbm(nc, config, riverfrac, xl, yl, inds)

    inds_riv, rev_inds_riv = active_indices(river_2d, 0)
    nriv = length(inds_riv)

    # reservoirs
    pits = zeros(Bool, modelsize_2d)
    if do_reservoirs
        reservoirs, resindex, reservoir, pits = initialize_simple_reservoir(config, nc, inds_riv, nriv, pits)
    else
        reservoir = ()
    end

    # lakes
    if do_lakes
        lakes, lakeindex, lake, pits = initialize_natural_lake(config, nc, inds_riv, nriv, pits)
    else
        lake = ()
    end

    ldd_2d = ncread(nc, param(config, "input.ldd"); allow_missing = true)
    ldd = ldd_2d[inds]
    if do_pits
        pits_2d = ncread(nc, param(config, "input.pits"); type = Bool, fill = false)
        ldd = set_pit_ldd(pits_2d, ldd, inds)
    end

    # lateral part sbm
    khfrac = ncread(
        nc,
        param(config, "input.lateral.subsurface.ksathorfrac", nothing);
        sel = inds,
        defaults = 1.0,
        type = Float64,
    )
    βₗ = ncread(nc, param(config, "input.lateral.land.slope"); sel = inds, type = Float64)
    clamp!(βₗ, 0.00001, Inf)
    kh₀ = khfrac .* sbm.kv₀
    dl = fill(mv, n)
    dw = fill(mv, n)
    sw = fill(mv, n)

    for i = 1:n
        dl[i] = detdrainlength(ldd[i], xl[i], yl[i])
        dw[i] = (xl[i] * yl[i])/dl[i]
        sw[i] = river[i] ? max(dw[i] - riverwidth[i], 0.0) : dw[i]
    end

    ssf = LateralSSF{Float64}(
        kh₀ = kh₀,
        f = sbm.f,
        zi = sbm.zi,
        soilthickness = sbm.soilthickness,
        θₑ = sbm.θₛ .- sbm.θᵣ,
        Δt = 1.0,
        βₗ = βₗ,
        dl = dl .* 1000.0,
        dw = dw .* 1000.0,
        ssf = ((kh₀ .* βₗ) ./ sbm.f) .* (exp.(-sbm.f .* sbm.zi) - exp.(-sbm.f .* sbm.soilthickness)) .* dw .*1000.0,
        wb_pit = pits[inds],
    )

    n_land = ncread(
        nc,
        param(config, "input.lateral.land.n", nothing);
        sel = inds,
        defaults = 0.072,
        type = Float64,
    )

    olf = SurfaceFlow(
        sl = βₗ,
        n = n_land,
        dl = dl,
        Δt = tosecond(Δt),
        its = kw_land_tstep > 0 ? Int(cld(tosecond(Δt), kw_land_tstep)) : kw_land_tstep,
        width = sw,
        wb_pit = pits[inds],
    )

    pcr_dir = dims_xy ? permute_indices(pcrdir) : pcrdir
    graph = flowgraph(ldd, inds, pcr_dir)

    riverslope = ncread(
        nc,
        param(config, "input.lateral.river.slope");
        sel = inds_riv,
        type = Float64,
    )
    clamp!(riverslope, 0.00001, Inf)
    riverlength = riverlength_2d[inds_riv]
    riverwidth = riverwidth_2d[inds_riv]
    n_river = ncread(
        nc,
        param(config, "input.lateral.river.n", nothing);
        sel = inds_riv,
        defaults = 0.036,
        type = Float64,
    )
    ldd_riv = ldd_2d[inds_riv]
    if do_pits
        ldd_riv = set_pit_ldd(pits_2d, ldd_riv, inds_riv)
    end
    graph_riv = flowgraph(ldd_riv, inds_riv, pcr_dir)

    # the indices of the river cells in the land(+river) cell vector
    index_river = filter(i -> !isequal(river[i], 0), 1:n)
    frac_toriver = fraction_runoff_toriver(graph, ldd, index_river, βₗ, n)

    rf = SurfaceFlow(
        sl = riverslope,
        n = n_river,
        dl = riverlength,
        Δt = tosecond(Δt),
        its = kw_river_tstep > 0 ? ceil(Int(tosecond(Δt) / kw_river_tstep)) : kw_river_tstep,
        width = riverwidth,
        reservoir_index = do_reservoirs ? resindex : fill(0, nriv),
        lake_index = do_lakes ? lakeindex : fill(0, nriv),
        reservoir = do_reservoirs ? reservoirs : nothing,
        lake = do_lakes ? lakes : nothing,
        rivercells = river,
    )

    state_ncnames = ncnames(config.state)

    reader = prepare_reader(dynamic_path, cyclic_path, config)

    modelmap = (vertical = sbm, lateral = (subsurface = ssf, land = olf, river = rf))
    indices_reverse = (
        land = rev_inds,
        river = rev_inds_riv,
        reservoir = isempty(reservoir) ? nothing : reservoir.reverse_indices,
        lake = isempty(lake) ? nothing : lake.reverse_indices,
    )
    writer = prepare_writer(
        config,
        reader,
        output_path,
        modelmap,
        state_ncnames,
        indices_reverse,
        x_nc,
        y_nc,
        dims_xy,
        nc,
        maxlayers = sbm.maxlayers,
    )

    # for each domain save the directed acyclic graph, the traversion order,
    # and the indices that map it back to the two dimensional grid
    # for reservoirs and lakes this is information is also available (except the graph)
    # from the initialization functions
    land = (
        graph = graph,
        order = topological_sort_by_dfs(graph),
        indices = inds,
        reverse_indices = rev_inds,
    )
    river = (
        graph = graph_riv,
        order = topological_sort_by_dfs(graph_riv),
        indices = inds_riv,
        reverse_indices = rev_inds_riv,
    )

    model = Model(
        config,
        (; land, river, reservoir, lake, index_river, frac_toriver),
        (subsurface = ssf, land = olf, river = rf),
        sbm,
        Clock(config.starttime, 1, Δt),
        reader,
        writer,
    )

    # read and set states in model object if reinit=false
    if reinit == false
        set_states(
            instate_path,
            model,
            state_ncnames;
            type = Float64
        )
        @unpack lateral, vertical = model
        # update zi for vertical sbm and α for river and overland flow
        zi = max.(0.0, vertical.soilthickness .- vertical.satwaterdepth ./ vertical.θₑ)
        vertical.zi .= zi
        lateral.river.α .= lateral.river.alpha_term .* pow.(lateral.river.width .+ 2.0 .* lateral.river.h, lateral.river.alpha_pow)
        lateral.land.α .= lateral.land.alpha_term .* pow.(lateral.land.width .+ 2.0 .* lateral.land.h, lateral.land.alpha_pow)
    end

    # make sure the forcing is already loaded
    # it's fine to run twice, and may help catching errors earlier
    update_forcing!(model)
    if haskey(config.input, "cyclic")
        update_cyclic!(model)
    end
    return model
end

"update SBM model for a single timestep"
function update(model::Model{N,L,V,R,W}) where {N,L,V<:SBM,R,W}
    @unpack lateral, vertical, network, clock, config = model

    inds_riv = network.index_river
    kinwave_it = get(config.model, "kin_wave_iteration", false)

    update_forcing!(model)
    if haskey(config.input, "cyclic")
        update_cyclic!(model)
    end

    # extract water levels h_av [m] from the land and river domains
    # this is used to limit open water evaporation
    vertical.waterlevel_land .= lateral.land.h_av .* 1000.0
    vertical.waterlevel_river[inds_riv] .= lateral.river.h_av .* 1000.0

    # vertical sbm concept is updated until snow state, after that (optional)
    # snow transport is possible
    update_until_snow(vertical, config)

    # lateral snow transport
    if get(config.model, "masswasting", false)
        snowflux_frac =
            min.(0.5, lateral.land.sl ./ 5.67) .* min.(1.0, vertical.snow ./ 10000.0)
        maxflux = snowflux_frac .* vertical.snow
        vertical.snow .= accucapacityflux(network.land, vertical.snow, maxflux)
        vertical.snowwater .= accucapacityflux(network.land, vertical.snowwater, vertical.snowwater .* snowflux_frac)
    end

    # update vertical sbm concept until recharge [mm] to the saturated store
    update_until_recharge(vertical, config)

    # exchange of recharge between vertical sbm concept and subsurface flow domain
    lateral.subsurface.recharge .= vertical.recharge
    lateral.subsurface.recharge .*= lateral.subsurface.dw
    lateral.subsurface.zi .= vertical.zi

    # update lateral subsurface flow domain
    update(lateral.subsurface, network.land, network.frac_toriver, lateral.river.rivercells)

    # update vertical sbm concept (runoff, ustorelayerdepth and satwaterdepth)
    update_after_lateralflow(
        vertical,
        lateral.subsurface.zi,
        lateral.subsurface.exfiltwater,
    )

    # update overland flow based on vertical runoff [mm] from vertical sbm concept
    lateral.land.qlat .=
        (vertical.runoff .* vertical.xl .* vertical.yl .* 0.001) ./ lateral.land.Δt ./
        lateral.land.dl
    update(
        lateral.land,
        network.land,
        frac_toriver = network.frac_toriver,
        river = lateral.river.rivercells,
        do_iter = kinwave_it,
    )

    # update river domain with net runoff from vertical sbm concept, overland flow
    # and lateral subsurface flow to the river cells
    net_runoff_river =
        (vertical.net_runoff_river[inds_riv] .* vertical.xl[inds_riv] .*
        vertical.yl[inds_riv] .* 0.001) ./ vertical.Δt
    lateral.river.qlat .=
        (
            lateral.subsurface.to_river[inds_riv] ./ 1.0e9 ./ lateral.river.Δt .+
            lateral.land.to_river[inds_riv] .+ net_runoff_river
        ) ./ lateral.river.dl
    update(lateral.river, network.river, do_iter = kinwave_it, doy = dayofyear(clock.time))

    write_output(model)

    # update the clock
    advance!(clock)

    return model
end


# ================ INITIALIZATION CODE DO NOT EDIT ====================================== %
# some global parameters -
BIG = 1e10
SMALL = 1e-8

# Load the experiment specifications -
tmp_value = JSON.parsefile("./experiments/Experiments.json")
experiment_array = tmp_value["experiment_array"]

# preload the data -
cached_data_dictionary = Dict()
for (experiment_index,experiment_dictionary) in enumerate(experiment_array)

  # Grab the data file -
  data_file_path = experiment_dictionary["data_file"]
  experiment_id = experiment_dictionary["experiment_id"]

  # Load the data -
  experimental_data_array = readdlm(data_file_path)

  # Cache the data w/experiment_id -
  cached_data_dictionary[experiment_id] = experimental_data_array
end

# preload the error function calls -
cached_error_function_dictionary = Dict()
for (experiment_index,experiment_dictionary) in enumerate(experiment_array)

  # Grab the experimental id -
  experiment_id = experiment_dictionary["experiment_id"]

  # Grab the error functions from the experiment_dictionary -
  error_function = eval(parse(experiment_dictionary["error_function"]))

  # cache these functions -
  cached_error_function_dictionary[experiment_id] = error_function
end
# ================ INITIALIZATION CODE DO NOT EDIT ====================================== %

function objective_function(parameter_array)

  # Script to solve the balance equations -
  time_start = 0.0
  time_stop = 120.0
  time_step_size = 0.01

  # Load the data dictionary -
  data_dictionary = DataDictionary(time_start,time_stop,time_step_size)

  # Update data dictionary to match new parameters before calculating obj
  parameter_mapping_array = data_dictionary["parameter_name_mapping_array"]
  for index = 1:length(parameter_mapping_array)
    if index <= data_dictionary["number_of_binding"]
      data_dictionary["binding_parameter_dictionary"][parameter_mapping_array[index]] = parameter_array[index]
    else
      data_dictionary["control_parameter_dictionary"][parameter_mapping_array[index]] = parameter_array[index]
    end
  end

  # how many objectives do we have?
  obj_array = BIG*ones(13,1)

  # Call simulation routine -
  # (run the model to SS, and then set the ICs to the SS for this parameter set)
  (time_array,simulation_state_array) = Simulation(time_start,time_stop,time_step_size,data_dictionary);

  # Call the error functions -
  # loop through the experimental dictionary, and call the appropriate error function -
  for (experiment_index,experiment_dictionary) in enumerate(experiment_array)

    # Get the error function pointer -
    error_function_pointer = experiment_dictionary["error_function"]
    output_index = parse(Int,experiment_dictionary["output_index"])
    experiment_id = experiment_dictionary["experiment_id"]
    species_symbol = experiment_dictionary["protein_symbol"];

    # Get the experimental data array -
    experimental_data_array = cached_data_dictionary[experiment_id]

    # Call the error function -
    error_function = cached_error_function_dictionary[experiment_id]
    error_value = error_function(experimental_data_array,time_array,simulation_state_array,output_index,species_symbol,data_dictionary)

    # Add the error to the objective array -
    obj_array[experiment_index] = error_value;
  end

  # @show obj_array

  # return the error array -
  return obj_array;
end

function neighbor_function(parameter_array)

  SIGMA = 0.01
  number_of_parameters = length(parameter_array)

  # calculate new parameters -
  perturbed_parameter_array = parameter_array.*(1+SIGMA*randn(number_of_parameters));

  # Parameters -
  # "n_gene_AP1_gene_AhR"	;	# 1
  # "K_gene_AP1_gene_AhR"	;	# 2
  # "n_gene_AP1_gene_PU1"	;	# 3
  # "K_gene_AP1_gene_PU1"	;	# 4
  # "n_gene_AP1_gene_PPARg"	;	# 5
  # "K_gene_AP1_gene_PPARg"	;	# 6
  # "n_gene_AhR_gene_Trigger"	;	# 7
  # "K_gene_AhR_gene_Trigger"	;	# 8
  # "n_gene_CD11b_gene_PU1_gene_cRAF"	;	# 9
  # "K_gene_CD11b_gene_PU1_gene_cRAF"	;	# 10
  # "n_gene_CD14_gene_PPARg_gene_CEBPa_gene_EGR1_gene_cRAF"	;	# 11
  # "K_gene_CD14_gene_PPARg_gene_CEBPa_gene_EGR1_gene_cRAF"	;	# 12
  # "n_gene_CD38_gene_IRF1_gene_PPARg_gene_Trigger_gene_cRAF"	;	# 13
  # "K_gene_CD38_gene_IRF1_gene_PPARg_gene_Trigger_gene_cRAF"	;	# 14
  # "n_gene_CEBPa_gene_Trigger"	;	# 15
  # "K_gene_CEBPa_gene_Trigger"	;	# 16
  # "n_gene_CEBPa_gene_PPARg"	;	# 17
  # "K_gene_CEBPa_gene_PPARg"	;	# 18
  # "n_gene_CEBPa_gene_CEBPa"	;	# 19
  # "K_gene_CEBPa_gene_CEBPa"	;	# 20
  # "n_gene_CEBPa_gene_GFI1"	;	# 21
  # "K_gene_CEBPa_gene_GFI1"	;	# 22
  # "n_gene_E2F_gene_E2F"	;	# 23
  # "K_gene_E2F_gene_E2F"	;	# 24
  # "n_gene_E2F_gene_PPARg"	;	# 25
  # "K_gene_E2F_gene_PPARg"	;	# 26
  # "n_gene_E2F_gene_CEBPa"	;	# 27
  # "K_gene_E2F_gene_CEBPa"	;	# 28
  # "n_gene_E2F_gene_GFI1"	;	# 29
  # "K_gene_E2F_gene_GFI1"	;	# 30
  # "n_gene_E2F_gene_cRAF"	;	# 31
  # "K_gene_E2F_gene_cRAF"	;	# 32
  # "n_gene_EGR1_gene_Trigger"	;	# 33
  # "K_gene_EGR1_gene_Trigger"	;	# 34
  # "n_gene_EGR1_gene_PU1"	;	# 35
  # "K_gene_EGR1_gene_PU1"	;	# 36
  # "n_gene_EGR1_gene_PPARg"	;	# 37
  # "K_gene_EGR1_gene_PPARg"	;	# 38
  # "n_gene_EGR1_gene_GFI1"	;	# 39
  # "K_gene_EGR1_gene_GFI1"	;	# 40
  # "n_gene_GFI1_gene_CEBPa"	;	# 41
  # "K_gene_GFI1_gene_CEBPa"	;	# 42
  # "n_gene_GFI1_gene_EGR1"	;	# 43
  # "K_gene_GFI1_gene_EGR1"	;	# 44
  # "n_gene_IRF1_gene_Trigger"	;	# 45
  # "K_gene_IRF1_gene_Trigger"	;	# 46
  # "n_gene_IRF1_gene_AhR"	;	# 47
  # "K_gene_IRF1_gene_AhR"	;	# 48
  # "n_gene_IRF1_gene_PPARg"	;	# 49
  # "K_gene_IRF1_gene_PPARg"	;	# 50
  # "n_gene_OCT1_gene_PPARg"	;	# 51
  # "K_gene_OCT1_gene_PPARg"	;	# 52
  # "n_gene_OCT4_gene_Trigger"	;	# 53
  # "K_gene_OCT4_gene_Trigger"	;	# 54
  # "n_gene_OCT4_gene_AhR"	;	# 55
  # "K_gene_OCT4_gene_AhR"	;	# 56
  # "n_gene_OCT4_gene_cRAF"	;	# 57
  # "K_gene_OCT4_gene_cRAF"	;	# 58
  # "n_gene_P21_gene_Trigger_gene_AP1_gene_PPARg_gene_PU1_gene_IRF1_gene_CEBPa_gene_cRAF"	;	# 59
  # "K_gene_P21_gene_Trigger_gene_AP1_gene_PPARg_gene_PU1_gene_IRF1_gene_CEBPa_gene_cRAF"	;	# 60
  # "n_gene_P21_gene_GFI1"	;	# 61
  # "K_gene_P21_gene_GFI1"	;	# 62
  # "n_gene_P47Phox_gene_PU1_gene_CEBPa_gene_cRAF"	;	# 63
  # "K_gene_P47Phox_gene_PU1_gene_CEBPa_gene_cRAF"	;	# 64
  # "n_gene_P47Phox_gene_PPARg"	;	# 65
  # "K_gene_P47Phox_gene_PPARg"	;	# 66
  # "n_gene_PPARg_gene_Trigger"	;	# 67
  # "K_gene_PPARg_gene_Trigger"	;	# 68
  # "n_gene_PPARg_gene_CEBPa"	;	# 69
  # "K_gene_PPARg_gene_CEBPa"	;	# 70
  # "n_gene_PPARg_gene_EGR1"	;	# 71
  # "K_gene_PPARg_gene_EGR1"	;	# 72
  # "n_gene_PPARg_gene_PU1"	;	# 73
  # "K_gene_PPARg_gene_PU1"	;	# 74
  # "n_gene_PPARg_gene_AP1"	;	# 75
  # "K_gene_PPARg_gene_AP1"	;	# 76
  # "n_gene_PU1_gene_Trigger"	;	# 77
  # "K_gene_PU1_gene_Trigger"	;	# 78
  # "n_gene_PU1_gene_CEBPa"	;	# 79
  # "K_gene_PU1_gene_CEBPa"	;	# 80
  # "n_gene_PU1_gene_PU1"	;	# 81
  # "K_gene_PU1_gene_PU1"	;	# 82
  # "n_gene_PU1_gene_AP1"	;	# 83
  # "K_gene_PU1_gene_AP1"	;	# 84
  # "n_gene_PU1_gene_OCT1"	;	# 85
  # "K_gene_PU1_gene_OCT1"	;	# 86
  # "n_gene_PU1_gene_AhR"	;	# 87
  # "K_gene_PU1_gene_AhR"	;	# 88
  # "n_gene_PU1_gene_GFI1"	;	# 89
  # "K_gene_PU1_gene_GFI1"	;	# 90
  # "W_gene_AP1_RNAP"	;	# 91
  # "W_gene_AP1_gene_AhR"	;	# 92
  # "W_gene_AP1_gene_PU1"	;	# 93
  # "W_gene_AP1_gene_PPARg"	;	# 94
  # "W_gene_AhR_RNAP"	;	# 95
  # "W_gene_AhR_gene_Trigger"	;	# 96
  # "W_gene_CD11b_RNAP"	;	# 97
  # "W_gene_CD11b_gene_PU1_gene_cRAF"	;	# 98
  # "W_gene_CD14_RNAP"	;	# 99
  # "W_gene_CD14_gene_PPARg_gene_CEBPa_gene_EGR1_gene_cRAF"	;	# 100
  # "W_gene_CD38_RNAP"	;	# 101
  # "W_gene_CD38_gene_IRF1_gene_PPARg_gene_Trigger_gene_cRAF"	;	# 102
  # "W_gene_CEBPa_RNAP"	;	# 103
  # "W_gene_CEBPa_gene_Trigger"	;	# 104
  # "W_gene_CEBPa_gene_PPARg"	;	# 105
  # "W_gene_CEBPa_gene_CEBPa"	;	# 106
  # "W_gene_CEBPa_gene_GFI1"	;	# 107
  # "W_gene_E2F_RNAP"	;	# 108
  # "W_gene_E2F_gene_E2F"	;	# 109
  # "W_gene_E2F_gene_PPARg"	;	# 110
  # "W_gene_E2F_gene_CEBPa"	;	# 111
  # "W_gene_E2F_gene_GFI1"	;	# 112
  # "W_gene_E2F_gene_cRAF"	;	# 113
  # "W_gene_EGR1_RNAP"	;	# 114
  # "W_gene_EGR1_gene_Trigger"	;	# 115
  # "W_gene_EGR1_gene_PU1"	;	# 116
  # "W_gene_EGR1_gene_PPARg"	;	# 117
  # "W_gene_EGR1_gene_GFI1"	;	# 118
  # "W_gene_GFI1_RNAP"	;	# 119
  # "W_gene_GFI1_gene_CEBPa"	;	# 120
  # "W_gene_GFI1_gene_EGR1"	;	# 121
  # "W_gene_IRF1_RNAP"	;	# 122
  # "W_gene_IRF1_gene_Trigger"	;	# 123
  # "W_gene_IRF1_gene_AhR"	;	# 124
  # "W_gene_IRF1_gene_PPARg"	;	# 125
  # "W_gene_OCT1_RNAP"	;	# 126
  # "W_gene_OCT1_gene_PPARg"	;	# 127
  # "W_gene_OCT4_RNAP"	;	# 128
  # "W_gene_OCT4_gene_Trigger"	;	# 129
  # "W_gene_OCT4_gene_AhR"	;	# 130
  # "W_gene_OCT4_gene_cRAF"	;	# 131
  # "W_gene_P21_RNAP"	;	# 132
  # "W_gene_P21_gene_Trigger_gene_AP1_gene_PPARg_gene_PU1_gene_IRF1_gene_CEBPa_gene_cRAF"	;	# 133
  # "W_gene_P21_gene_GFI1"	;	# 134
  # "W_gene_P47Phox_RNAP"	;	# 135
  # "W_gene_P47Phox_gene_PU1_gene_CEBPa_gene_cRAF"	;	# 136
  # "W_gene_P47Phox_gene_PPARg"	;	# 137
  # "W_gene_PPARg_RNAP"	;	# 138
  # "W_gene_PPARg_gene_Trigger"	;	# 139
  # "W_gene_PPARg_gene_CEBPa"	;	# 140
  # "W_gene_PPARg_gene_EGR1"	;	# 141
  # "W_gene_PPARg_gene_PU1"	;	# 142
  # "W_gene_PPARg_gene_AP1"	;	# 143
  # "W_gene_PU1_RNAP"	;	# 144
  # "W_gene_PU1_gene_Trigger"	;	# 145
  # "W_gene_PU1_gene_CEBPa"	;	# 146
  # "W_gene_PU1_gene_PU1"	;	# 147
  # "W_gene_PU1_gene_AP1"	;	# 148
  # "W_gene_PU1_gene_OCT1"	;	# 149
  # "W_gene_PU1_gene_AhR"	;	# 150
  # "W_gene_PU1_gene_GFI1"	;	# 151
  # "W_gene_Trigger_RNAP"	;	# 152
  # "W_gene_cRAF_RNAP"	;	# 153

  # Setup my upper bound, and lower bounds on parameters -
  lower_bound_array = zeros(number_of_parameters)
  upper_bound_array = [
    4.0   ;   # "n_gene_AP1_gene_AhR"	;	# 1
    500.0 ;   # "K_gene_AP1_gene_AhR"	;	# 2
    4.0   ;   # "n_gene_AP1_gene_PU1"	;	# 3
    500.0 ;   # "K_gene_AP1_gene_PU1"	;	# 4
    4.0   ;   # "n_gene_AP1_gene_PPARg"	;	# 5
    500.0 ;   # "K_gene_AP1_gene_PPARg"	;	# 6
    4.0   ;   # "n_gene_AhR_gene_Trigger"	;	# 7
    500.0 ;   # "K_gene_AhR_gene_Trigger"	;	# 8
    4.0   ;  # "n_gene_CD11b_gene_PU1_gene_cRAF"	;	# 9
    500.0 ;  # "K_gene_CD11b_gene_PU1_gene_cRAF"	;	# 10
    4.0   ;  # "n_gene_CD14_gene_PPARg_gene_CEBPa_gene_EGR1_gene_cRAF"	;	# 11
    500.0 ;  # "K_gene_CD14_gene_PPARg_gene_CEBPa_gene_EGR1_gene_cRAF"	;	# 12
    4.0   ;  # "n_gene_CD38_gene_IRF1_gene_PPARg_gene_Trigger_gene_cRAF"	;	# 13
    500.0 ;  # "K_gene_CD38_gene_IRF1_gene_PPARg_gene_Trigger_gene_cRAF"	;	# 14
    4.0   ;  # "n_gene_CEBPa_gene_Trigger"	;	# 15
    500.0 ;  # "K_gene_CEBPa_gene_Trigger"	;	# 16
    4.0   ;  # "n_gene_CEBPa_gene_PPARg"	;	# 17
    500.0 ;  # "K_gene_CEBPa_gene_PPARg"	;	# 18
    4.0   ;  # "n_gene_CEBPa_gene_CEBPa"	;	# 19
    500.0 ;  # "K_gene_CEBPa_gene_CEBPa"	;	# 20
    4.0   ;  # "n_gene_CEBPa_gene_GFI1"	;	# 21
    500.0 ;  # "K_gene_CEBPa_gene_GFI1"	;	# 22
    4.0   ;  # "n_gene_E2F_gene_E2F"	;	# 23
    500.0 ;  # "K_gene_E2F_gene_E2F"	;	# 24
    4.0   ;  # "n_gene_E2F_gene_PPARg"	;	# 25
    500.0 ;  # "K_gene_E2F_gene_PPARg"	;	# 26
    4.0   ;  # "n_gene_E2F_gene_CEBPa"	;	# 27
    500.0 ;  # "K_gene_E2F_gene_CEBPa"	;	# 28
    4.0   ;  # "n_gene_E2F_gene_GFI1"	;	# 29
    500.0 ;  # "K_gene_E2F_gene_GFI1"	;	# 30
    4.0   ;  # "n_gene_E2F_gene_cRAF"	;	# 31
    500.0 ;  # "K_gene_E2F_gene_cRAF"	;	# 32
    4.0   ;  # "n_gene_EGR1_gene_Trigger"	;	# 33
    500.0 ;  # "K_gene_EGR1_gene_Trigger"	;	# 34
    4.0   ;  # "n_gene_EGR1_gene_PU1"	;	# 35
    500.0 ;  # "K_gene_EGR1_gene_PU1"	;	# 36
    4.0   ;  # "n_gene_EGR1_gene_PPARg"	;	# 37
    500.0 ;  # "K_gene_EGR1_gene_PPARg"	;	# 38
    4.0   ;  # "n_gene_EGR1_gene_GFI1"	;	# 39
    500.0 ;  # "K_gene_EGR1_gene_GFI1"	;	# 40
    4.0   ;  # "n_gene_GFI1_gene_CEBPa"	;	# 41
    500.0 ;  # "K_gene_GFI1_gene_CEBPa"	;	# 42
    4.0   ;  # "n_gene_GFI1_gene_EGR1"	;	# 43
    500.0 ;  # "K_gene_GFI1_gene_EGR1"	;	# 44
    4.0   ;  # "n_gene_IRF1_gene_Trigger"	;	# 45
    500.0 ;  # "K_gene_IRF1_gene_Trigger"	;	# 46
    4.0   ;  # "n_gene_IRF1_gene_AhR"	;	# 47
    500.0 ;  # "K_gene_IRF1_gene_AhR"	;	# 48
    4.0   ;  # "n_gene_IRF1_gene_PPARg"	;	# 49
    500.0 ;  # "K_gene_IRF1_gene_PPARg"	;	# 50
    4.0   ;  # "n_gene_OCT1_gene_PPARg"	;	# 51
    500.0 ;  # "K_gene_OCT1_gene_PPARg"	;	# 52
    4.0   ;  # "n_gene_OCT4_gene_Trigger"	;	# 53
    500.0 ;  # "K_gene_OCT4_gene_Trigger"	;	# 54
    4.0   ;  # "n_gene_OCT4_gene_AhR"	;	# 55
    500.0 ;  # "K_gene_OCT4_gene_AhR"	;	# 56
    4.0   ;  # "n_gene_OCT4_gene_cRAF"	;	# 57
    500.0 ;  # "K_gene_OCT4_gene_cRAF"	;	# 58
    4.0   ;  # "n_gene_P21_gene_Trigger_gene_AP1_gene_PPARg_gene_PU1_gene_IRF1_gene_CEBPa_gene_cRAF"	;	# 59
    500.0 ;  # "K_gene_P21_gene_Trigger_gene_AP1_gene_PPARg_gene_PU1_gene_IRF1_gene_CEBPa_gene_cRAF"	;	# 60
    4.0   ;  # "n_gene_P21_gene_GFI1"	;	# 61
    500.0 ;  # "K_gene_P21_gene_GFI1"	;	# 62
    4.0   ;  # "n_gene_P47Phox_gene_PU1_gene_CEBPa_gene_cRAF"	;	# 63
    500.0 ;  # "K_gene_P47Phox_gene_PU1_gene_CEBPa_gene_cRAF"	;	# 64
    4.0   ;  # "n_gene_P47Phox_gene_PPARg"	;	# 65
    500.0 ;  # "K_gene_P47Phox_gene_PPARg"	;	# 66
    4.0   ;  # "n_gene_PPARg_gene_Trigger"	;	# 67
    500.0 ;  # "K_gene_PPARg_gene_Trigger"	;	# 68
    4.0   ;  # "n_gene_PPARg_gene_CEBPa"	;	# 69
    500.0 ;  # "K_gene_PPARg_gene_CEBPa"	;	# 70
    4.0   ;  # "n_gene_PPARg_gene_EGR1"	;	# 71
    500.0 ;  # "K_gene_PPARg_gene_EGR1"	;	# 72
    4.0   ;  # "n_gene_PPARg_gene_PU1"	;	# 73
    500.0 ;  # "K_gene_PPARg_gene_PU1"	;	# 74
    4.0   ;  # "n_gene_PPARg_gene_AP1"	;	# 75
    500.0 ;  # "K_gene_PPARg_gene_AP1"	;	# 76
    4.0   ;  # "n_gene_PU1_gene_Trigger"	;	# 77
    500.0 ;  # "K_gene_PU1_gene_Trigger"	;	# 78
    4.0   ;  # "n_gene_PU1_gene_CEBPa"	;	# 79
    500.0 ;  # "K_gene_PU1_gene_CEBPa"	;	# 80
    4.0   ;  # "n_gene_PU1_gene_PU1"	;	# 81
    500.0 ;  # "K_gene_PU1_gene_PU1"	;	# 82
    4.0   ;  # "n_gene_PU1_gene_AP1"	;	# 83
    500.0 ;  # "K_gene_PU1_gene_AP1"	;	# 84
    4.0   ;  # "n_gene_PU1_gene_OCT1"	;	# 85
    500.0 ;  # "K_gene_PU1_gene_OCT1"	;	# 86
    4.0   ;  # "n_gene_PU1_gene_AhR"	;	# 87
    500.0 ;  # "K_gene_PU1_gene_AhR"	;	# 88
    4.0   ;  # "n_gene_PU1_gene_GFI1"	;	# 89
    500.0 ;  # "K_gene_PU1_gene_GFI1"	;	# 90
    100.0 ; # "W_gene_AP1_RNAP"	;	# 91
    100.0 ;  # "W_gene_AP1_gene_AhR"	;	# 92
    100.0 ;  # "W_gene_AP1_gene_PU1"	;	# 93
    100.0 ;  # "W_gene_AP1_gene_PPARg"	;	# 94
    100.0 ;  # "W_gene_AhR_RNAP"	;	# 95
    100.0 ;  # "W_gene_AhR_gene_Trigger"	;	# 96
    100.0 ;  # "W_gene_CD11b_RNAP"	;	# 97
    100.0 ;  # "W_gene_CD11b_gene_PU1_gene_cRAF"	;	# 98
    100.0 ;  # "W_gene_CD14_RNAP"	;	# 99
    0.0   ;  # "W_gene_CD14_gene_PPARg_gene_CEBPa_gene_EGR1_gene_cRAF"	;	# 100
    100.0 ;  # "W_gene_CD38_RNAP"	;	# 101
    100.0 ;  # "W_gene_CD38_gene_IRF1_gene_PPARg_gene_Trigger_gene_cRAF"	;	# 102
    100.0 ;  # "W_gene_CEBPa_RNAP"	;	# 103
    100.0 ;  # "W_gene_CEBPa_gene_Trigger"	;	# 104
    100.0 ;  # "W_gene_CEBPa_gene_PPARg"	;	# 105
    100.0 ;  # "W_gene_CEBPa_gene_CEBPa"	;	# 106
    100.0 ;  # "W_gene_CEBPa_gene_GFI1"	;	# 107
    100.0 ;  # "W_gene_E2F_RNAP"	;	# 108
    100.0 ;  # "W_gene_E2F_gene_E2F"	;	# 109
    100.0 ;  # "W_gene_E2F_gene_PPARg"	;	# 110
    100.0 ;  # "W_gene_E2F_gene_CEBPa"	;	# 111
    100.0 ;  # "W_gene_E2F_gene_GFI1"	;	# 112
    100.0 ;  # "W_gene_E2F_gene_cRAF"	;	# 113
    100.0 ;  # "W_gene_EGR1_RNAP"	;	# 114
    100.0 ;  # "W_gene_EGR1_gene_Trigger"	;	# 115
    100.0 ;  # "W_gene_EGR1_gene_PU1"	;	# 116
    100.0 ;  # "W_gene_EGR1_gene_PPARg"	;	# 117
    100.0 ;  # "W_gene_EGR1_gene_GFI1"	;	# 118
    100.0 ;  # "W_gene_GFI1_RNAP"	;	# 119
    100.0 ;  # "W_gene_GFI1_gene_CEBPa"	;	# 120
    100.0 ;  # "W_gene_GFI1_gene_EGR1"	;	# 121
    100.0 ;  # "W_gene_IRF1_RNAP"	;	# 122
    100.0 ;  # "W_gene_IRF1_gene_Trigger"	;	# 123
    100.0 ;  # "W_gene_IRF1_gene_AhR"	;	# 124
    100.0 ;  # "W_gene_IRF1_gene_PPARg"	;	# 125
    100.0 ;  # "W_gene_OCT1_RNAP"	;	# 126
    100.0 ;  # "W_gene_OCT1_gene_PPARg"	;	# 127
    100.0 ;  # "W_gene_OCT4_RNAP"	;	# 128
    100.0 ;  # "W_gene_OCT4_gene_Trigger"	;	# 129
    100.0 ;  # "W_gene_OCT4_gene_AhR"	;	# 130
    100.0 ;  # "W_gene_OCT4_gene_cRAF"	;	# 131
    100.0 ;  # "W_gene_P21_RNAP"	;	# 132
    100.0 ;  # "W_gene_P21_gene_Trigger_gene_AP1_gene_PPARg_gene_PU1_gene_IRF1_gene_CEBPa_gene_cRAF"	;	# 133
    100.0 ;  # "W_gene_P21_gene_GFI1"	;	# 134
    100.0 ;  # "W_gene_P47Phox_RNAP"	;	# 135
    100.0 ;  # "W_gene_P47Phox_gene_PU1_gene_CEBPa_gene_cRAF"	;	# 136
    100.0 ;  # "W_gene_P47Phox_gene_PPARg"	;	# 137
    100.0 ;  # "W_gene_PPARg_RNAP"	;	# 138
    100.0 ;  # "W_gene_PPARg_gene_Trigger"	;	# 139
    100.0 ;  # "W_gene_PPARg_gene_CEBPa"	;	# 140
    100.0 ;  # "W_gene_PPARg_gene_EGR1"	;	# 141
    100.0 ;  # "W_gene_PPARg_gene_PU1"	;	# 142
    100.0 ;  # "W_gene_PPARg_gene_AP1"	;	# 143
    100.0 ;  # "W_gene_PU1_RNAP"	;	# 144
    100.0 ;  # "W_gene_PU1_gene_Trigger"	;	# 145
    100.0 ;  # "W_gene_PU1_gene_CEBPa"	;	# 146
    100.0 ;  # "W_gene_PU1_gene_PU1"	;	# 147
    100.0 ;  # "W_gene_PU1_gene_AP1"	;	# 148
    100.0 ;  # "W_gene_PU1_gene_OCT1"	;	# 149
    100.0 ;  # "W_gene_PU1_gene_AhR"	;	# 150
    100.0 ;  # "W_gene_PU1_gene_GFI1"	;	# 151
    100.0 ;  # "W_gene_Trigger_RNAP"	;	# 152
    100.0 ;  # "W_gene_cRAF_RNAP"	;	# 153
  ];

  # return the corrected value -
  return parameter_bounds_function(perturbed_parameter_array,lower_bound_array,upper_bound_array);
end

function acceptance_probability_function(rank_array,temperature)
  return (exp(-rank_array[end]/temperature))
end

function cooling_function(temperature)

  # define my new temperature -
  alpha = 0.90
  return alpha*temperature
end

function parameter_bounds_function(parameter_array,lower_bound_array,upper_bound_array)

  # iterate through and fix the parameters -
  new_parameter_array = copy(parameter_array)
  for (index,value) in enumerate(parameter_array)

    lower_bound = lower_bound_array[index]
    upper_bound = upper_bound_array[index]

    if (value<lower_bound)
      new_parameter_array[index] = lower_bound
    elseif (value>upper_bound)
      new_parameter_array[index] = upper_bound
    end
  end

  return new_parameter_array
end


export embedded_number_field

################################################################################
#
#  Types
#
################################################################################

mutable struct EmbeddedNumField{S, E} <: Field
  field::S
  embedding::E
end

mutable struct EmbeddedNumFieldElem{T} <: FieldElem
  parent
  element::T

  function EmbeddedNumFieldElem{T}(parent, element::T) where {T}
    @assert Hecke.parent(element) === number_field(parent)
    return new{T}(parent, element)
  end
end

function (E::EmbeddedNumField{S})(x::T) where {S, T <: NumFieldElem}
  if elem_type(S) === T
    @assert parent(x) === number_field(E)
    return EmbeddedNumFieldElem{T}(E, x)
  else
    return E(number_field(E)(x))
  end
end

function (E::EmbeddedNumField)(x::EmbeddedNumFieldElem)
  @assert parent(x) === E
  return x
end

function (E::EmbeddedNumField)(x::RingElement)
  return E(number_field(E)(x))
end

function hash(x::EmbeddedNumFieldElem, h::UInt)
  return xor(hash(data(x), h), 0x5cccaf5d1346dc53%UInt)
end

function Base.deepcopy_internal(x::EmbeddedNumFieldElem, id::IdDict)
  return parent(x)(Base.deepcopy_internal(data(x), id))
end

number_field(K::EmbeddedNumField) = K.field

embedding(K::EmbeddedNumField) = K.embedding

parent(x::EmbeddedNumFieldElem{T}) where {T} = x.parent::parent_type(x)

elem_type(::Type{EmbeddedNumField{S, E}}) where {S, E} = EmbeddedNumFieldElem{elem_type(S)}

elem_type(K::EmbeddedNumField{S, E}) where {S, E} = elem_type(EmbeddedNumField{S, E})

parent_type(::Type{EmbeddedNumFieldElem{T}}) where {T} = EmbeddedNumField{parent_type(T), embedding_type(parent_type(T))}

parent_type(x::EmbeddedNumFieldElem{T}) where {T} = parent_type(EmbeddedNumFieldElem{T})

data(x::EmbeddedNumFieldElem) = x.element

function embedded_field(K::SimpleNumField, i::NumFieldEmb)
  @assert number_field(i) === K
  E = EmbeddedNumField(K, i)
  return E, E(gen(K))
end

function embedded_field(K::NonSimpleNumField, i::NumFieldEmb)
  @assert number_field(i) === K
  E = EmbeddedNumField(K, i)
  return E, E.(gens(K))
end

(E::EmbeddedNumField)() = zero(E)

unary_ops = [:(-)]

binary_ops = [:(+), :(*), :(-), :(div), :(divexact), :(//)]

for b in unary_ops
  @eval begin
    function ($b)(x::EmbeddedNumFieldElem)
      return parent(x)($(b)(data(x)))
    end
  end
end

for b in binary_ops
  @eval begin
    function ($b)(x::EmbeddedNumFieldElem, y::EmbeddedNumFieldElem)
      return parent(x)($(b)(data(x), data(y)))
    end
  end
end

function ==(x::EmbeddedNumFieldElem, y::EmbeddedNumFieldElem)
  return ==(data(x), data(y))
end

iszero(x::EmbeddedNumFieldElem) = iszero(data(x))

isone(x::EmbeddedNumFieldElem) = isone(data(x))

isunit(x::EmbeddedNumFieldElem) = isunit(data(x))

zero(E::EmbeddedNumField) = E(zero(number_field(E)))

one(E::EmbeddedNumField) = E(one(number_field(E)))

# Now the ordering

function isless(x::EmbeddedNumFieldElem, y::EmbeddedNumFieldElem)
  i = embedding(parent(x))
  # Need to exclude equality
  if x == y
    return false
  end
  p = 32
  xe, ye = i(data(x), p), i(data(y), p)
  while overlaps(xe, ye)
    p = Int(floor(p * 1.42))
    xe, ye = i(data(x), p), i(data(y), p)
  end
  if real(xe) < real(ye)
    return true
  else
    @assert real(xe) > real(ye)
    return false
  end
end

isless(x::EmbeddedNumFieldElem, y) = isless(x, parent(x)(y))

isless(x, y::EmbeddedNumFieldElem) = isless(parent(y)(x), y)

# Support comparing with floats

function isless(x::EmbeddedNumFieldElem, y::AbstractFloat)
  i = embedding(parent(x))
  # Need to exclude equality
  p = 32
  xe = i(data(x), p)
  # check if y is "equal" to x as a rational
  if isrational(data(x))
    xq = QQ(data(x))
    d = denominator(xq)
    if isone(d)
      yd = y
    else
      yd = d * y
    end
    if isinteger(yd) && numerator(xq) == yd
      return false
    end
  end

  while contains(real(xe), BigFloat(y))
    p = Int(floor(p * 1.42))
    xe = i(data(x), p)
  end
  if real(xe) < y
    return true
  else
    @assert real(xe) > y
    return false
  end
end

function isless(y::AbstractFloat, x::EmbeddedNumFieldElem)
  i = embedding(parent(x))
  # Need to exclude equality
  p = 32
  xe = i(data(x), p)
  # check if y is "equal" to x as a rational
  if isrational(data(x))
    xq = QQ(data(x))
    d = denominator(xq)
    if isone(d)
      yd = y
    else
      yd = d * y
    end
    if isinteger(yd) && numerator(xq) == yd
      return false
    end
  end

  while contains(real(xe), BigFloat(y))
    p = Int(floor(p * 1.42))
    xe = i(data(x), p)
  end
  if real(xe) > y
    return true
  else
    @assert real(xe) < y
    return false
  end
end

################################################################################
#
#  String I/O
#
################################################################################

function Base.show(io::IO, E::EmbeddedNumField)
  print(io, "Embedded field\n$(number_field(E))\nat\n$(embedding(E))")
end

# I overload this to get the value at the embedding
# (But I don't want this in the general printing routines, e.g., over polynomial
# rings.)
function Base.show(io::IO, ::MIME"text/plain", x::EmbeddedNumFieldElem)
  print(io, "$(data(x))")
  a = Float64(real(embedding(parent(x))(data(x), 32)))
  print(io, @sprintf " (%.2f)" a)
end

function Base.show(io::IO, x::EmbeddedNumFieldElem)
  print(io, "$(data(x))")
end

function AbstractAlgebra.expressify(x::EmbeddedNumFieldElem; context = nothing)
  AbstractAlgebra.expressify(data(x), context = context)
end

################################################################################
#
#  Constructors
#
################################################################################

function embedded_number_field(f::Union{fmpq_poly, fmpz_poly}, r::Union{AbstractFloat, Tuple}, var = "a")
  K, a = number_field(f, var)
  r = real_embedding(K, r)
  return embedded_field(K, r)
end

function embedded_number_field(f::Vector{<:Union{fmpq_poly, fmpz_poly}}, r::Vector{<:Union{AbstractFloat, Tuple}}, var = "a")
  K, a = number_field(f, var)
  r = real_embedding(K, r)
  return embedded_field(K, r)
end

################################################################################
#
#  Creation of elements
#
################################################################################

(E::EmbeddedNumField)(v::Vector) = E(number_field(E)(v))

################################################################################
#
#  Promote rule
#
################################################################################

function AbstractAlgebra.promote_rule(::Type{EmbeddedNumFieldElem{T}}, ::Type{S}) where {T <: NumFieldElem, S <: RingElement}
  if T === S
    return Union{}
  else
    if AbstractAlgebra.promote_rule(T, S) === T
      return EmbeddedNumFieldElem{T}
    else
      return Union{}
    end
  end
end

function AbstractAlgebra.promote_rule(::Type{EmbeddedNumFieldElem{T}}, ::Type{EmbeddedNumFieldElem{T}}) where {T <: NumFieldElem}
  return EmbeddedNumFieldElem{T}
end


struct ResNetModel
    entry
    pooling
    layers
    head

    size::Int64
end
Flux.@functor ResNetModel

(m::ResNetModel)(x) = x |> m.entry |> m.pooling |> m.layers |> m.head

function (m::ResNetModel)(x, ::Val{:stages})
    stages = typeof(x)[]

    o = x |> m.entry
    push!(stages, o)
    o = o |> m.pooling

    for l in m.layers
        o = o |> l
        push!(stages, o)
    end
    stages
end

const Config = Dict(
    18=>((2, 2, 2, 2), BasicBlock, 1),
    34=>((3, 4, 6, 3), BasicBlock, 1),
    50=>((3, 4, 6, 3), Bottleneck, 4),
    101=>((3, 4, 23, 3), Bottleneck, 4),
    152=>((3, 8, 36, 3), Bottleneck, 4),
)

function ResNetModel(
    model_size::Int64 = 18;
    in_channels::Int64 = 3,
    classes::Union{Int64, Nothing} = 1000,
)
    if !(model_size in keys(Config))
        throw(
            "Invalid mode size [$model_size]. " *
            "Supported sizes are $(keys(Config))."
        )
    end
    repeats, block, expansion = Config[model_size]

    entry = Chain(
        Conv((7, 7), in_channels=>64, pad=(3, 3), stride=(2, 2), bias=false),
        BatchNorm(64, relu),
    )
    pooling = MaxPool((3, 3), pad=(1, 1), stride=(2, 2))

    head = nothing
    if classes ≢ nothing
        head = Chain(
            MeanPool((7, 7)), flatten, Dense(512 * expansion, classes),
        )
    end

    in_channels = 64
    channels = (64, 128, 256, 512)
    strides = (1, 2, 2, 2)

    layers = []
    for (out_channels, repeat, stride) in zip(channels, repeats, strides)
        push!(layers, make_layer(
            block, in_channels=>out_channels, repeat, expansion, stride,
        ))
        in_channels = out_channels * expansion
    end

    ResNetModel(entry, pooling, Chain(layers...), head, model_size)
end

function stages_channels(r::ResNetModel)
    e = Config[r.size][3]
    (64, 64 * e, 128 * e, 256 * e, 512 * e)
end


module trieMusic
using Plots
using Combinatorics

#シーケンスを階差数列にして返却する
function seqenceToDifferenceSequence(sequence)
    differenceSequence = []
    beforeElm = sequence[1]
    for elm in sequence[2:end]
        push!(differenceSequence, elm - beforeElm)
        beforeElm = elm
    end
    return differenceSequence
end

#シーケンスを増加または減少の2値（1, -1）に変換して返却する
function sequenceToBinarySequence(sequence)
    binarySequence = []
    for elm in sequence
        if elm > 0
            push!(binarySequence, 1)
        else
            push!(binarySequence, -1)
        end
    end
    return binarySequence
end

#シーケンスを独自ルールでLOUDSのTrie木構造に変換して返却する
function sequenceToLouds(sequence)
    #トライ木をLOUDSで表現するためのビット配列　初期値として1番ノードを指すtと、
    #ルートノードの終端を指すfと、
    #1番ノードの終端を指すfが入ってる
    bitAry = BitArray([true,false,false])
    #各ノードに対応するラベル　形式は[sequenceの要素, その要素が出現したインデックス1, その要素が出現したインデックス2,...]
    label = []
    #検索対象となるノードの情報。[何階層目か, その階層の中で左から何番目のノードか、bitIdx]
    searchTargetNodes = []
    #シーケンス番号
    seqIdx = 0
    #各階層のノード数　ルート直下からスタート
    eachLevelNodesAccumulatedNumber = []
    #各階層のbitAry上の区切り位置
    levelBoundaryBaIdx = [3]

    for elm in sequence
        seqIdx = seqIdx + 1
        #検索対象となるノードがあれば過去のシーケンスを検索開始
        if length(searchTargetNodes) > 0
            tmpSearchTargetNodes = []
            for searchTargetNode in searchTargetNodes
                #検索対象となるノードが持つsequence上のインデックスを取得。
                #ラベルから取得するが、現在のseqIdx　- 1 と同じ値の要素は取得しない　あくまで過去の要素が対象
                targetLabelIdx = 0
                if searchTargetNode[1] == 1
                    targetLabelIdx = searchTargetNode[2]
                else
                    targetLabelIdx = eachLevelNodesAccumulatedNumber[searchTargetNode[1] - 1] + searchTargetNode[2]
                end
                searchTargetSeqIdxes = filter!(e->e ≠ seqIdx - 1, label[targetLabelIdx][2:end])
                #ラベルから取得した過去のsequence上のインデックスの右隣が検索対象
                searchTargetChildrenSeqIdxes = searchTargetSeqIdxes + repeat([1], length(searchTargetSeqIdxes))
                #シーケンス上に、今来ているelmと一致したインデックスを取得
                foundMatchLabelIdxes = searchTargetChildrenSeqIdxes[findall(isequal(elm), [sequence[i] for i in searchTargetChildrenSeqIdxes])]
                #1つでも一致したシーケンスがあれば、bitAryとlabelを更新する必要がある
                if length(foundMatchLabelIdxes) > 0
                    #foundMatchLabelIdxesに現在のseqIdxも追加する。その配列をラベルに追加するため。
                    push!(foundMatchLabelIdxes, seqIdx)
                    #子ノードの情報（子ノードの数、子ノードと同じ階層の上の従兄弟の数、子ノードの終端のfalseのbitIdx）を取得する
                    bitIdx, childrenNumber, brotherChildrenNumber = getChild(levelBoundaryBaIdx, searchTargetNode, bitAry)
                    #子ノードの開始位置の一つ前のidxを取得する
                    childLabelStartIdx = eachLevelNodesAccumulatedNumber[searchTargetNode[1]] + brotherChildrenNumber
                    #子ノードが存在していたら
                    if childrenNumber > 0
                        #子ノードのラベルインデックスを取得
                        childrenLabelIdxes = childLabelStartIdx + 1:childLabelStartIdx + childrenNumber
                        #子ノードの中で一致したノードのラベルインデックスを取得
                        matchChildLabelIdx = findfirst(isequal(elm), [elmLabel[1] for elmLabel in label[childrenLabelIdxes]])
                        #一致したノードがあれば
                        if matchChildLabelIdx != nothing
                            #既存のノードラベルにfoundMatchLabelIdxesを追加する
                            childLabelStartIdx += matchChildLabelIdx

                            label[childLabelStartIdx] = insert!(unique!(sort(append!(label[childLabelStartIdx][2:end], foundMatchLabelIdxes))), 1, label[childLabelStartIdx][1])

                            push!(tmpSearchTargetNodes, [searchTargetNode[1] + 1, brotherChildrenNumber + matchChildLabelIdx, bitIdx - childrenNumber - 1 + matchChildLabelIdx])
                        #一致したノードがなければノード追加
                        else
                            #子ノード追加
                            insert!(bitAry, bitIdx, true)
                            #各階層のbitAry上の区切り位置更新
                            #追加した子ノードの子ノードが存在しているかもしれない階層まで、区切り位置の配列が情報を持っていなければ追加
                            #適切な位置にfalseを追加
                            insert!(bitAry, getChild(levelBoundaryBaIdx, [searchTargetNode[1] + 1, brotherChildrenNumber + childrenNumber + 1], bitAry)[1], false)
                            #子ノードを追加した階層より右側の区切り位置を1増やす
                            levelBoundaryBaIdx[searchTargetNode[1] + 2] += 1
                            levelBoundaryBaIdx[searchTargetNode[1] + 3:end] += repeat([2], length(levelBoundaryBaIdx[searchTargetNode[1] + 3:end]))
                            #子ノードを追加した階層から右側のノード数を1増やす
                            eachLevelNodesAccumulatedNumber[searchTargetNode[1] + 1:end] += repeat([1], length(eachLevelNodesAccumulatedNumber[searchTargetNode[1] + 1:end]))
                            #labelに追加
                            insert!(label, childrenLabelIdxes[end] + 1, insert!(foundMatchLabelIdxes, 1, elm))
                            #既に登録されているsearchTargetNodesを更新
                            tmpSearchTargetNodes = updateSearchTargetNodes(tmpSearchTargetNodes, bitIdx, searchTargetNode)
                            #searchTargetNodeを追加する。
                            push!(tmpSearchTargetNodes, [searchTargetNode[1] + 1, brotherChildrenNumber + childrenNumber + 1, bitIdx])
                        end
                    #子ノードが存在していなければ子ノードを追加する
                    else
                        #子ノード追加
                        insert!(bitAry, bitIdx, true)

                        #各階層のbitAry上の区切り位置更新
                        #追加した子ノードの子ノードが存在しているかもしれない階層まで、区切り位置の配列が情報を持っていなければ追加
                        if length(levelBoundaryBaIdx) < searchTargetNode[1] + 2
                            push!(levelBoundaryBaIdx, levelBoundaryBaIdx[end] + length(bitAry[levelBoundaryBaIdx[end]:end]))
                            #falseも追加
                            push!(bitAry, false)
                        #既に存在している場合
                        else
                            #適切な位置にfalseを追加
                            insert!(bitAry, getChild(levelBoundaryBaIdx, [searchTargetNode[1] + 1, brotherChildrenNumber + 1], bitAry)[1], false)
                            #子ノードを追加した階層より右側の区切り位置を1増やす
                            levelBoundaryBaIdx[searchTargetNode[1] + 2] += 1
                            levelBoundaryBaIdx[searchTargetNode[1] + 3:end] += repeat([2], length(levelBoundaryBaIdx[searchTargetNode[1] + 3:end]))
                        end
                        #今回追加した子ノードの階層まで、各階層のノード数配列が情報を持っていなければ追加
                        if length(eachLevelNodesAccumulatedNumber) < searchTargetNode[1] + 1
                            push!(eachLevelNodesAccumulatedNumber, eachLevelNodesAccumulatedNumber[end] + 1)
                        #既に存在している場合
                        else
                            #子ノードを追加した階層から右側のノード数を1増やす
                            eachLevelNodesAccumulatedNumber[searchTargetNode[1] + 1:end] += repeat([1], length(eachLevelNodesAccumulatedNumber[searchTargetNode[1] + 1:end]))
                        end
                        #labelに追加
                        insert!(label, childLabelStartIdx + 1, insert!(foundMatchLabelIdxes, 1, elm))
                        #既に登録されているsearchTargetNodesを更新
                        tmpSearchTargetNodes = updateSearchTargetNodes(tmpSearchTargetNodes, bitIdx, searchTargetNode)
                        #searchTargetNodeを追加する。
                        push!(tmpSearchTargetNodes, [searchTargetNode[1] + 1, brotherChildrenNumber + 1, bitIdx])
                    end
                end

            end
            #次回の検索用配列にコピー
            searchTargetNodes = tmpSearchTargetNodes
        end
        #ルートの子ノードに追加する
        #子ノードがあれば
        if length(eachLevelNodesAccumulatedNumber) > 0
            #同じ要素が子ノードにあるか検索
            matchRootChildLabelIdx = findfirst(isequal(elm), [labelElm[1] for labelElm in label[1:eachLevelNodesAccumulatedNumber[1]]])
            #あれば
            if matchRootChildLabelIdx !== nothing
                #一致したラベルに現在のシーケンスインデックスを追加
                push!(label[matchRootChildLabelIdx], seqIdx)
                #ターゲットノードとして追加
                push!(searchTargetNodes, [1, matchRootChildLabelIdx, 2 + matchRootChildLabelIdx])
            else
            #ルートノードの子にいない場合は追加
                insert!(bitAry, 2 + eachLevelNodesAccumulatedNumber[1] + 1, true)
                insert!(bitAry, getChild(levelBoundaryBaIdx, [1, eachLevelNodesAccumulatedNumber[1] + 1], bitAry)[1], false)
                insert!(label,  eachLevelNodesAccumulatedNumber[1] + 1, [elm, seqIdx])
                eachLevelNodesAccumulatedNumber += repeat([1], length(eachLevelNodesAccumulatedNumber))
                levelBoundaryBaIdx[2:end] += repeat([1], length(levelBoundaryBaIdx[2:end]))
            end
        #なければ追加
        else
            insert!(bitAry, 3, true)
            push!(bitAry, false)
            insert!(label, 1, [elm, seqIdx])
            push!(eachLevelNodesAccumulatedNumber, 1)
            push!(levelBoundaryBaIdx, 5)
        end
    end
    return bitAry, label, eachLevelNodesAccumulatedNumber, levelBoundaryBaIdx
end

#渡したノードの子ノードの情報を取得
function getChild(levelBoundaryBaIdx, searchTargetNode, bitAry)
    brotherNumber = 0
    countChildren = false
    childrenNumber = 0
    brotherChildrenNumber = 0
    #調査対象ノードが長男の場合、直下の階層の最初が調査対象ノードの子ノードになるので子ノードとして数えだす
    if searchTargetNode[2] == 1
        countChildren = true
    end
    bitIdx = levelBoundaryBaIdx[searchTargetNode[1] + 1] - 1
    for bit in bitAry[bitIdx + 1:end]
        bitIdx += 1
        if countChildren == true
            if bit == true
                childrenNumber += 1
            else
                brotherNumber += 1
            end
        else
            if bit == false
                brotherNumber += 1
            else
                brotherChildrenNumber += 1
            end
        end
        if brotherNumber == searchTargetNode[2] - 1
            countChildren = true
        end
        if brotherNumber == searchTargetNode[2]
            break
        end
    end
    return bitIdx, childrenNumber, brotherChildrenNumber
end

#既に登録されているsearchTargetNodesを更新
function updateSearchTargetNodes(tmpSearchTargetNodes, bitIdx, searchTargetNode)
    if length(tmpSearchTargetNodes) > 0
        for pastSearchTargetNode in tmpSearchTargetNodes
            #今回追加したbitIdxより後ろのbitIdxの場合、1つ右にずらす
            if bitIdx < pastSearchTargetNode[3]
                pastSearchTargetNode[3] += 1
                #さらに同じ階層の場合、ノードの左からの順番も1つ右にずらす
                if searchTargetNode[1] == pastSearchTargetNode[1]
                    pastSearchTargetNode[1] += 1
                end
            end
        end
    end
    return tmpSearchTargetNodes
end

#全部分列の情報を取得する
function getAllSubSequences(label, sequence, eachLevelNodesAccumulatedNumber)
    subSequences = []
    startSeqIdxes = []
    endSeqIdxes = []
    #取得したlabelつまりノードすべて計算
    for nodeNumber in 1:length(label)
        #trie木の親ノード直下のノードから、指定したノード番号（第四引数）までの部分列の形状を持つ、実際の部分列群を取得する
        subSequence, startSeqIdx, endSeqIdx = getMatchSubsequences(sequence, label, eachLevelNodesAccumulatedNumber, nodeNumber)
        #全部追加する
        push!(subSequences, subSequence)
        push!(startSeqIdxes, startSeqIdx)
        push!(endSeqIdxes, endSeqIdx)
    end
    return subSequences, startSeqIdxes, endSeqIdxes

end
#trie木の親ノード直下のノードから、指定したノード（targetLabelIdx）までの部分列の形状を持つ、実際の部分列群を返却する
function getMatchSubsequences(sequence, label, eachLevelNodesAccumulatedNumber, targetLabelIdx)
    #targetLabelIdxがどの階層にいるか判定 TODO:これ高速化したい
    targetNodeLevel = 0
    for nodesAccumulatedNumber in eachLevelNodesAccumulatedNumber
        targetNodeLevel += 1
        if nodesAccumulatedNumber ≥ targetLabelIdx
            break
        end
    end
    #labelの2番目以降に格納されているseqIdxesは、部分列の終端位置を指す。
    #現時点では、ソースとなるsequenceが階差数列になっているため実際の終端位置を出すために+1する
    endSeqIndexes = label[targetLabelIdx][2:end] + repeat([1], length(label[targetLabelIdx][2:end]))
    #終端位置から所属階層位置の数を引くことで、対象のノードまでの部分列の開始位置を取得する
    startSeqIdxes = endSeqIndexes - repeat([targetNodeLevel], length(endSeqIndexes))
    #全部分列を取得する
    subSequences = []
    for i in 1:length(startSeqIdxes)
        push!(subSequences, Dict("subSequenceStartIdx" => startSeqIdxes[i], "data" => sequence[startSeqIdxes[i]:endSeqIndexes[i]]))
    end
    return subSequences, startSeqIdxes, endSeqIndexes
end

#時系列データセットから2つの時系列データを全組み合わせで取り出して、それらのDTW距離を出す。
function calcDtwDistances(subSequences)
    dtwDistances = 0
    for subSequence in subSequences
        #時系列データセットから2つの時系列データを全組み合わせで取り出す
        sequenceCombination = collect(combinations(subSequence, 2))
        #全組み合わせの時系列同士の距離を計算する
        for seq in sequenceCombination
            dtwDistances += calcDtwDistance(seq[1]["data"], seq[2]["data"])
        end
    end
    return dtwDistances
end

#DTWで2つの時系列の距離を計算する。
#s1:比較対象時系列データその1
#s2:比較対象時系列データその2
function calcDtwDistance(s1, s2)
    #DTW格納用の配列。ループごとに更新される。
    beforeDtwAry = []
    currentDtwAry = []
    #xカウンタ
    x = 0
    for elm2 in s2
    #yカウンタ
        y = 0
        x = x + 1
        currentDtwAry = []
        for elm1 in s1
            y = y + 1
            diff = abs(elm2 - elm1)
            #ループの一番最初だけは先頭同士の差の絶対値
            if x == 1 && y == 1
                currentDtwAry = [diff]
            #ループの1列目は自身の配列のひとつ前の要素
            elseif x == 1
                push!(currentDtwAry, diff + currentDtwAry[y - 1])
            #2列目以降かつ1行目は、前の配列の同じ位置の要素
            elseif x != 1 && y == 1
                push!(currentDtwAry, diff + beforeDtwAry[y])
            #それ以外は、3パターンのうちの最小値
            else
                push!(currentDtwAry, diff + minimum([beforeDtwAry[y], beforeDtwAry[y - 1], currentDtwAry[y - 1]]))
            end
        end
        #完成した現在の列を前の列にコピーする
        beforeDtwAry = currentDtwAry
    end
    #DTW配列の最後の要素がDTW-Distance
    return currentDtwAry[end]
end

#一致した部分列群をプロットする
function plotSubsequences(sequence, subSequences, startSeqIdxes, endSeqIdxes)

    subSequencesForPlot = deepcopy(subSequences)

    for i in 1:length(subSequencesForPlot)
        subSequencesForPlotData = repeat([NaN], startSeqIdxes[i] - 1)
        append!(subSequencesForPlotData, subSequences[i]["data"])
        append!(subSequencesForPlotData, repeat([NaN], length(sequence) - endSeqIdxes[i]))
        subSequencesForPlot[i]["data"] = subSequencesForPlotData
    end
    #plot装飾用
    xticksValues = [1 + i * 16 for i in 0:div(length(sequence), 16)]
    xticksLabels = [i for i in 1:div(length(sequence), 16)]
    majorScaleValue = [0, 2, 4, 5, 7, 9, 11]
    majorScaleLabel = ["C","D","E","F","G","A","B"]

    minValOffset = div(minimum(sequence), 12)
    maxValOffset = div(maximum(sequence), 12)
    yticksValues = []
    yticksLabels = []
    if minValOffset == maxValOffset
        yticksValues = majorScaleValue * minValOffset
    else
        for i in minValOffset:maxValOffset
            append!(yticksValues, majorScaleValue + repeat([12 * i], 7))
            append!(yticksLabels, majorScaleLabel)
        end
    end
    #表示
    plot([subSequencesForPlot[i]["data"] for i in 1:length(subSequencesForPlot)],
    size = (2200, 500),
    xticks = (xticksValues, xticksLabels),
    yticks = (yticksValues, yticksLabels),
    xlabel = "measure",
    ylabel = "pitch(natural tone name)")
end
# PlantUMLファイル生成。Trie木を可視化。
function trieToPuml(bitAry, label)
    open("tree.puml", "w") do file
        write(file, "'This file was automatically generated.\n@startuml tree_diagram\nobject 0\n")

        bitCnt = 0
        for bit in bitAry[3:end]
            if bit == true
                bitCnt += 1
                write(file, "object ", string(bitCnt), "\n", string(bitCnt), " : value=", string(label[bitCnt][1]), "\n", string(bitCnt), " : seqIdx=", string(label[bitCnt][2:end], "\n"))
            end
        end
        children = []
        parent = 0
        labelCnt = 0
        for bit in bitAry[3:end]
            if bit == true
                labelCnt += 1
                push!(children, labelCnt)
            elseif bit == false
                if length(children) > 0
                    for child in children
                        write(file, string(parent), "--", string(child), "\n")
                    end
                    children = []
                end
                parent += 1
            end
        end
        write(file, "@enduml")
    end
end

end


"""
	match_patches(img::AbstractArray{Float64},
			refIndex::Array{CartesianIndex{2},2},
			patchSize::CartesianIndex{2},
			searchWindow::CartesianIndex{2},
			nMatch::Int64,
			distF::Function)

Full-search block matching algorithm for BM3DDenoise
"""
function match_patches(
	img::AbstractArray{Float64,2},
	refIndex::Array{CartesianIndex{2},2},
	patchSize::CartesianIndex{2},
	searchWindow::CartesianIndex{2},
	nMatch::Int64,
	distF::Function,
)

	refIndexH, refIndexW = size(refIndex)

	matches = Array{CartesianIndex{2}}(undef, refIndexH, refIndexW, nMatch)

	dist = Array{Float64}(undef, refIndexH, refIndexW, nMatch + 1)# dist[i, j, end] is the largest item in current dist[i, j, 1:end-1]
	dist .= typemax(Float64)

	maxDistMatch = Array{UInt}(undef, refIndexH, refIndexW)# maxDistMatch[i, j] is the position in matches[i, j, :] corresponding to the largest distance block
	maxDistMatch .= 1

	lockPool = Array{ReentrantLock}(undef, refIndexH, refIndexW)
	for i in 1:length(lockPool)
		lockPool[i] = ReentrantLock()
	end

	@inbounds Threads.@threads for j in 1:refIndexW
		for i in 1:refIndexH
			p = refIndex[i, j]
			iE = min(refIndexH, i + searchWindow[1])
			jE = min(refIndexW, j + searchWindow[2])
			imgRef = @view img[p:(p + patchSize - CartesianIndex(1, 1))]

			# In order to skip the current block and save unnecessary judgment, it is split into three parts.
			for sj in (j + 1):jE, si in (i + 1):iE
				@views weight = distF(
					imgRef,
					img[refIndex[si, sj]:(refIndex[
						si,
						sj,
					] + patchSize - CartesianIndex(1, 1))],
				)
				lock(lockPool[i, j]) do
					if weight < dist[i, j, nMatch + 1]
						maxDistImg = maxDistMatch[i, j]
						matches[i, j, maxDistImg] = CartesianIndex(si, sj)
						dist[i, j, maxDistImg] = weight
						dist[i, j, nMatch + 1], maxDistMatch[i, j] =
							findmax(@views dist[i, j, 1:nMatch])
					end
				end

				lock(lockPool[si, sj]) do
					if weight < dist[si, sj, nMatch + 1]
						maxDistImg = maxDistMatch[si, sj]
						matches[si, sj, maxDistImg] = CartesianIndex(i, j)
						dist[si, sj, maxDistImg] = weight
						dist[si, sj, nMatch + 1], maxDistMatch[si, sj] =
							findmax(@views dist[si, sj, 1:nMatch])
					end
				end
			end
			for si in (i + 1):iE
				@views weight = distF(
					imgRef,
					img[refIndex[si, j]:(refIndex[si, j] + patchSize - CartesianIndex(1, 1))],
				)
				lock(lockPool[i, j]) do
					if weight < dist[i, j, nMatch + 1]
						maxDistImg = maxDistMatch[i, j]
						matches[i, j, maxDistImg] = CartesianIndex(si, j)
						dist[i, j, maxDistImg] = weight
						dist[i, j, nMatch + 1], maxDistMatch[i, j] =
							findmax(@views dist[i, j, 1:nMatch])
					end
				end

				lock(lockPool[si, j]) do
					if weight < dist[si, j, nMatch + 1]
						maxDistImg = maxDistMatch[si, j]
						matches[si, j, maxDistImg] = CartesianIndex(i, j)
						dist[si, j, maxDistImg] = weight
						dist[si, j, nMatch + 1], maxDistMatch[si, j] =
							findmax(@views dist[si, j, 1:nMatch])
					end
				end
			end
			for sj in (j + 1):jE
				@views weight = distF(
					imgRef,
					img[refIndex[i, sj]:(refIndex[i, sj] + patchSize - CartesianIndex(1, 1))],
				)
				lock(lockPool[i, j]) do
					if weight < dist[i, j, nMatch + 1]
						maxDistImg = maxDistMatch[i, j]
						matches[i, j, maxDistImg] = CartesianIndex(i, sj)
						dist[i, j, maxDistImg] = weight
						dist[i, j, nMatch + 1], maxDistMatch[i, j] =
							findmax(@views dist[i, j, 1:nMatch])
					end
				end

				lock(lockPool[i, sj]) do
					if weight < dist[i, sj, nMatch + 1]
						maxDistImg = maxDistMatch[i, sj]
						matches[i, sj, maxDistImg] = CartesianIndex(i, j)
						dist[i, sj, maxDistImg] = weight
						dist[i, sj, nMatch + 1], maxDistMatch[i, sj] =
							findmax(@views dist[i, sj, 1:nMatch])
					end
				end
			end
		end
	end
	matches
end

# For color images
function match_patches(
	img::Array{Float64,3},
	refIndex::Array{CartesianIndex{2},2},
	patchSize::CartesianIndex{2},
	searchWindow::CartesianIndex{2},
	nMatch::Int64,
	distF::Function,
)
	match_patches(view(img, :, :, 1), refIndex, patchSize, searchWindow, nMatch, distF)
end