abstract type OrdinaryDiffEqInterpolation{cacheType} <: DiffEqBase.AbstractDiffEqInterpolation end

struct InterpolationData{F,uType,tType,kType,cacheType} <: OrdinaryDiffEqInterpolation{cacheType}
  f::F
  timeseries::uType
  ts::tType
  ks::kType
  dense::Bool
  cache::cacheType
end

struct CompositeInterpolationData{F,uType,tType,kType,cacheType} <: OrdinaryDiffEqInterpolation{cacheType}
  f::F
  timeseries::uType
  ts::tType
  ks::kType
  alg_choice::Vector{Int}
  dense::Bool
  cache::cacheType
end

DiffEqBase.interp_summary(interp::OrdinaryDiffEqInterpolation{cacheType}) where {cacheType<:FunctionMapConstantCache} = "left-endpoint piecewise constant"
DiffEqBase.interp_summary(interp::OrdinaryDiffEqInterpolation{cacheType}) where {cacheType<:FunctionMapCache} = "left-endpoint piecewise constant"
function DiffEqBase.interp_summary(interp::OrdinaryDiffEqInterpolation{cacheType}) where cacheType<:Union{DP5ConstantCache,DP5Cache}
  interp.dense ? "specialized 4th order \"free\" interpolation" : "1st order linear"
end
function DiffEqBase.interp_summary(interp::OrdinaryDiffEqInterpolation{cacheType}) where cacheType<:Union{Rosenbrock23ConstantCache,Rosenbrock32ConstantCache,Rosenbrock23Cache,Rosenbrock32Cache}
  interp.dense ? "specialized 2nd order \"free\" stiffness-aware interpolation" : "1st order linear"
end
function DiffEqBase.interp_summary(interp::OrdinaryDiffEqInterpolation{cacheType}) where cacheType<:Union{Rodas4ConstantCache,Rodas4Cache}
  interp.dense ? "specialized 3rd order \"free\" stiffness-aware interpolation" : "1st order linear"
end
function DiffEqBase.interp_summary(interp::OrdinaryDiffEqInterpolation{cacheType}) where cacheType<:Union{SSPRK22,SSPRK22ConstantCache,SSPRK33,SSPRK33ConstantCache,SSPRK432,SSPRK432ConstantCache}
  interp.dense ? "2nd order \"free\" SSP interpolation" : "1st order linear"
end
function DiffEqBase.interp_summary(interp::OrdinaryDiffEqInterpolation{cacheType}) where cacheType<:Union{OwrenZen3Cache,OwrenZen3ConstantCache}
  interp.dense ? "specialized 3rd order \"free\" interpolation" : "1st order linear"
end
function DiffEqBase.interp_summary(interp::OrdinaryDiffEqInterpolation{cacheType}) where cacheType<:Union{OwrenZen4Cache,OwrenZen4ConstantCache}
  interp.dense ? "specialized 4th order \"free\" interpolation" : "1st order linear"
end
function DiffEqBase.interp_summary(interp::OrdinaryDiffEqInterpolation{cacheType}) where cacheType<:Union{OwrenZen5Cache,OwrenZen5ConstantCache}
  interp.dense ? "specialized 5th order \"free\" interpolation" : "1st order linear"
end
function DiffEqBase.interp_summary(interp::OrdinaryDiffEqInterpolation{cacheType}) where cacheType<:Union{Tsit5Cache,Tsit5ConstantCache}
  interp.dense ? "specialized 4th order \"free\" interpolation" : "1st order linear"
end
function DiffEqBase.interp_summary(interp::OrdinaryDiffEqInterpolation{cacheType}) where cacheType<:Union{BS5ConstantCache,BS5Cache}
  interp.dense ? "specialized 5th order lazy interpolation" : "1st order linear"
end
function DiffEqBase.interp_summary(interp::OrdinaryDiffEqInterpolation{cacheType}) where cacheType<:Union{Vern6Cache,Vern6ConstantCache}
  interp.dense ? "specialized 6th order lazy interpolation" : "1st order linear"
end
function DiffEqBase.interp_summary(interp::OrdinaryDiffEqInterpolation{cacheType}) where cacheType<:Union{Vern7Cache,Vern7ConstantCache}
  interp.dense ? "specialized 7th order lazy interpolation" : "1st order linear"
end
function DiffEqBase.interp_summary(interp::OrdinaryDiffEqInterpolation{cacheType}) where cacheType<:Union{Vern8Cache,Vern8ConstantCache}
  interp.dense ? "specialized 8th order lazy interpolation" : "1st order linear"
end
function DiffEqBase.interp_summary(interp::OrdinaryDiffEqInterpolation{cacheType}) where cacheType<:Union{Vern9Cache,Vern9ConstantCache}
  interp.dense ? "specialized 9th order lazy interpolation" : "1st order linear"
end
function DiffEqBase.interp_summary(interp::OrdinaryDiffEqInterpolation{cacheType}) where cacheType<:Union{DP8ConstantCache,DP8Cache}
  interp.dense ? "specialized 7th order interpolation" : "1st order linear"
end
function DiffEqBase.interp_summary(interp::OrdinaryDiffEqInterpolation{cacheType}) where cacheType
  interp.dense ? "3rd order Hermite" : "1st order linear"
end
function DiffEqBase.interp_summary(interp::OrdinaryDiffEqInterpolation{cacheType}) where cacheType<:CompositeCache
  interp.dense ? "Automatic order switching interpolation" : "1st order linear"
end

(interp::InterpolationData)(tvals,idxs,deriv,p,continuity::Symbol=:left) = ode_interpolation(tvals,interp,idxs,deriv,p,continuity)
(interp::CompositeInterpolationData)(tvals,idxs,deriv,p,continuity::Symbol=:left) = ode_interpolation(tvals,interp,idxs,deriv,p,continuity)
(interp::InterpolationData)(val,tvals,idxs,deriv,p,continuity::Symbol=:left) = ode_interpolation!(val,tvals,interp,idxs,deriv,p,continuity)
(interp::CompositeInterpolationData)(val,tvals,idxs,deriv,p,continuity::Symbol=:left) = ode_interpolation!(val,tvals,interp,idxs,deriv,p,continuity)

function InterpolationData(id::InterpolationData,f)
  InterpolationData(f,id.timeseries,
                      id.ts,
                      id.ks,
                      id.dense,
                      id.cache)
end

function CompositeInterpolationData(id::CompositeInterpolationData,f)
  CompositeInterpolationData(f,id.timeseries,
                               id.ts,
                               id.ks,
                               id.alg_choice,
                               id.dense,
                               id.cache)
end


using TightlyBound
using Test
using Suppressor

@testset "testing makescf" begin

    @suppress begin
        types=["Li"]
        pos=zeros((1,3))
        A=[ [10.0 0 0]; [0 10.0 0]; [ 0 0 10.0]]
        c=makecrys(A, pos, types, units="Bohr")

        tmpdir, prefix, inputfile_str = TightlyBound.QE.makeSCF(c)

        @test occursin("Li", inputfile_str)

    end
end


# include -
include("Include.jl")

# setup the calculation -
number_of_samples = 5
number_of_parameters = 153

# load up the parameters sets -
ensemble_array = zeros(number_of_parameters,1);
for sample_index = 1:number_of_samples

  filename = "./parameter_best_v1.dat."*string(sample_index)
  local_array = readdlm(filename)

  ensemble_array = hcat(ensemble_array,local_array);
end

# cut leading col -
ensemble_array = ensemble_array[:,2:end]

# Last ... run the model w/these sets, save the data -
for parameter_set_index = 1:number_of_samples

  # sample the ensemble_array -
  parameter_array = ensemble_array[:,parameter_set_index]

  # Load the data dictionary -
  data_dictionary = DataDictionary(0.0,0.0,0.0);

  # 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

  # Setup the time -
  time_start = 0.0
  time_stop = 120.0
  time_step_size = 0.01

  # Run the simulation -
  (T,X) = Simulation(time_start,time_stop,time_step_size,data_dictionary);

  # dump data to disk -
  local_data = [T X];
  data_filename = "./best_ensemble/sim_data_PSI_"*string(parameter_set_index)*".dat"
  writedlm(data_filename,local_data);
end


# this object represents a slice of the hamiltonian at a single position.
# you can then do things like transfer_left(vector,ham[5],st.AL[5],st.AR[5])

struct MPOHamSlice{O<:MPOHamiltonian}
    ham::O
    i::Int
end

function Base.getproperty(x::MPOHamSlice,s::Symbol)
    if s == :imspaces
        return x.ham.imspaces[x.i,:]
    elseif s == :domspaces
        return x.ham.domspaces[x.i,:]
    elseif s == :odim
        return x.ham.odim
    elseif s == :Os
        return x.ham.Os[x.i,:,:]
    else
        return getfield(x,s)
    end
end

Base.getindex(x::MPOHamiltonian,a::AbstractVector{Int}) = [x[i] for i in a];
Base.getindex(x::MPOHamiltonian,a::Int) = MPOHamSlice(x,a);

Base.getindex(x::MPOHamSlice,args...) = Base.getindex(x.ham,x.i,args...);
isscal(x::MPOHamSlice,j,k) = isscal(x.ham,x.i,j,k);
Base.keys(x::MPOHamSlice) = keys(x.ham,x.i);
scalkeys(x::MPOHamSlice) = scalkeys(x.ham,x.i);
opkeys(x::MPOHamSlice) = opkeys(x.ham,x.i);
Base.contains(x::MPOHamSlice,j,k) = contains(x.ham,x.i,j,k);

#the usual mpoham transfer
function transfer_left(vec::Vector{V},ham::MPOHamSlice,A::V,Ab::V=A) where V<:MPSTensor
    toret = [TensorMap(zeros,eltype(A),_lastspace(Ab)'*ham.imspaces[i],_lastspace(A)') for i in 1:ham.odim]::Vector{V}

    @sync for k in 1:ham.odim
        @Threads.spawn toret[k] = foldl(+, 1:ham.odim |>
            Filter(j->contains(ham,j,k)) |>
            Map() do j
                if isscal(ham,j,k)
                    ham.Os[j,k]*transfer_left(vec[j],A,Ab)
                else
                    transfer_left(vec[j],ham[j,k],A,Ab)
                end
            end,init=toret[k]);
    end

    return toret
end
function transfer_right(vec::Vector{V},ham::MPOHamSlice,A::V,Ab::V=A) where V<:MPSTensor
    toret = [TensorMap(zeros,eltype(A),_firstspace(A)*ham.domspaces[i],_firstspace(Ab)) for i in 1:ham.odim]::Vector{V}

    @sync for j in 1:ham.odim
        @Threads.spawn toret[j] = foldl(+, 1:ham.odim |>
            Filter(k->contains(ham,j,k)) |>
            Map() do k
                if isscal(ham,j,k)
                    ham.Os[j,k]*transfer_right(vec[k],A,Ab)
                else
                    transfer_right(vec[k],ham[j,k],A,Ab)
                end
            end,init=toret[j]);
    end
    return toret
end


module RocketReduceOperatorTest

using Test
using Rocket

include("../test_helpers.jl")

@testset "operator: reduce()" begin

    println("Testing: operator reduce()")

    run_proxyshowcheck("Reduce", reduce(Any, +, 0))
    run_proxyshowcheck("ReduceNoSeed", reduce(+))

    run_testset([
        (
            source      = from(1:5) |> reduce(Int, +, 1),
            values      = @ts([ 16, c ]),
            source_type = Int
        ),
        (
            source      = from(1:5) |> reduce(+),
            values      = @ts([ 15, c ]),
            source_type = Int
        ),
        (
            source      = from(1:5) |> reduce(Vector{Int}, (d, c) -> [ c..., d ], Int[]),
            values      = @ts([ [1, 2, 3, 4, 5], c ]),
            source_type = Vector{Int}
        ),
        (
            source      = completed(Int) |> reduce(+),
            values      = @ts(c),
            source_type = Int
        ),
        (
            source      = completed(Int) |> reduce(Int, +, 2),
            values      = @ts([ 2, c ]),
            source_type = Int
        ),
        (
            source      = faulted("e") |> reduce(+),
            values      = @ts(e("e")),
        ),
        (
            source = never() |> reduce(+),
            values = @ts()
        )
    ])
end

end


#=##############################################################################
# DESCRIPTION
    Types defining maneuvers of flight vehicles.

# AUTHORSHIP
  * Author    : Eduardo J. Alvarez
  * Email     : Edo.AlvarezR@gmail.com
  * Created   : Oct 2019
  * License   : MIT
=###############################################################################

################################################################################
# ABSTRACT MANEUVER TYPE
################################################################################
"""
    `AbstractManeuver{N, M}`

`N` indicates the number of tilting systems in this maneuver, while and `M`
indicates the number of rotor systems.

Every implementation of `AbstractManeuver` must have the properties:

 * `angle::NTuple{N, Function}` where `angle[i](t)` returns the angle of the
        i-th tilting system at time `t` (t is nondimensionalized by the total
        time of the maneuver, from 0 to 1, beginning to end).
 * `RPM::NTuple{M, Function}` where `RPM[i](t)` returns the normalized RPM of
        the i-th rotor system at time `t`. This RPM values are normalized by the
        an arbitrary RPM value (usually RPM in hover or cruise).
"""
abstract type AbstractManeuver{N, M} end

##### FUNCTIONS REQUIRED IN IMPLEMENTATIONS ####################################
"""
    `calc_dV(maneuver::AbstractManeuver, vehicle::Vehicle, t, dt, ttot, Vref)`

Returns the change in velocity `dV=[dVx, dVy, dVz]` (m/s) of `vehicle` performing
`maneuver` at time `t` (s) after a time step `dt` (s).  `Vref` and `tot` are the
reference velocity and the total time at which this maneuver is being performed,
respectively. `dV` is in the global reference system.
"""
function calc_dV(self::AbstractManeuver, vehicle::AbstractVehicle, t::Real,
                                            dt::Real, ttot::Real, Vref::Real)
    error("$(typeof(self)) has no implementation yet!")
end

"""
    `calc_dw(maneuver::AbstractManeuver, vehicle::Vehicle, t, dt, Vref, ttot)`

Returns the change in angular velocity `dW=[dWx, dWy, dWz]` (about global
axes, in radians) of `vehicle` performing `maneuver` at time `t` (s) after a
time step `dt` (s). `ttot` is the total time at which this maneuver is to be
performed.
"""
function calc_dW(self::AbstractManeuver, vehicle::AbstractVehicle, t::Real,
                                                        dt::Real, ttot::Real)
    error("$(typeof(self)) has no implementation yet!")
end

##### COMMON FUNCTIONS  ########################################################
"""
    `get_ntltsys(self::AbstractManeuver)`
Return number of tilting systems.
"""
get_ntltsys(self::AbstractManeuver) = typeof(self).parameters[1]

"""
    `get_nrtrsys(self::AbstractManeuver)`
Return number of rotor systems.
"""
get_nrtrsys(self::AbstractManeuver) = typeof(self).parameters[2]

"""
    `get_angle(maneuver::AbstractManeuver, i::Int, t::Real)`

Returns the angle (in degrees) of the i-th tilting system at the non-dimensional
time t.
"""
function get_angle(self::AbstractManeuver, i::Int, t::Real)
    if i<=0 || i>get_ntltsys(self)
        error("Invalid tilting system #$i (max is $(get_ntltsys(self))).")
    end
    if t<0 || t>1
        @warn("Got non-dimensionalized time $(t).")
    end
    return self.angle[i](t)
end

"""
    `get_angles(maneuver::AbstractManeuver, t::Real)`

Returns the angle (in degrees) of every tilting systems at the non-dimensional
time t.
"""
get_angles(self::AbstractManeuver, t::Real) = Tuple(a(t) for a in self.angle)

"""
    `get_RPM(maneuver::AbstractManeuver, i::Int, t::Real)`

Returns the normalized RPM of the i-th rotor system at the non-dimensional time
t.
"""
function get_RPM(self::AbstractManeuver, i::Int, t::Real)
    if i<=0 || i>get_nrtrsys(self)
        error("Invalid rotor system #$i (max is $(get_nrtrsys(self))).")
    end
    if t<0 || t>1
        @warn("Got non-dimensionalized time $(t).")
    end
    return self.RPM[i](t)
end

"""
    `get_RPMs(maneuver::AbstractManeuver, t::Real)`

Returns the normalized RPM of every rotor systems at the non-dimensional time
t.
"""
get_RPMs(self::AbstractManeuver, t::Real) = Tuple(rpm(t) for rpm in self.RPM)

##### COMMON INTERNAL FUNCTIONS  ###############################################

##### END OF ABSTRACT MANEUVER #################################################

################################################################################
# KINEMATIC MANEUVER TYPE
################################################################################
"""
    `KinematicManeuver(angle, RPM, Vvehicle, anglevehicle)`

A vehicle maneuver where the kinematic are prescribed.

# ARGUMENTS
* `angle::NTuple{N, Function}` where `angle[i](t)` returns the angles
        `[Ax, Ay, Az]` (in degrees)of the i-th tilting system at time `t` (t is
        nondimensionalized by the total time of the maneuver, from 0 to 1,
        beginning to end).
* `RPM::NTuple{M, Function}` where `RPM[i](t)` returns the normalized RPM of
        the i-th rotor system at time `t`. This RPM values are normalized by the
        an arbitrary RPM value (usually RPM in hover or cruise).
* `Vvehicle::Function` where `Vvehicle(t)` returns the normalized vehicle
        velocity `[Vx, Vy, Vz]` at the normalized time `t`. Velocity is
        normalized by a reference velocity (typically, cruise velocity).
* `anglevehicle::Function` where `anglevehicle(t)` returns the angles
        `[Ax, Ay, Az]` (in degrees) of the vehicle relative to the global
        coordinate system at the normalized time `t`.
"""
struct KinematicManeuver{N, M} <: AbstractManeuver{N, M}
    angle::NTuple{N, Function}
    RPM::NTuple{M, Function}
    Vvehicle::Function
    anglevehicle::Function
end

# # Implicit N and M constructor
# KinematicManeuver(a::NTuple{N, Function}, b::NTuple{M, Function},
#                     c::Function, d::Function
#                  ) where {N, M} = KinematicManeuver{N, M}(a, b, c, d)

##### FUNCTIONS  ###############################################################
function calc_dV(self::KinematicManeuver, vehicle::AbstractVehicle, t::Real,
                                            dt::Real, ttot::Real, Vref::Real)
    return Vref * (self.Vvehicle((t+dt)/ttot) - self.Vvehicle(t/ttot))
end

function calc_dW(self::KinematicManeuver, vehicle::AbstractVehicle, t::Real,
                                                         dt::Real, ttot::Real)
    prev_W = (self.anglevehicle(t/ttot) - self.anglevehicle((t-dt)/ttot)) / dt
    cur_W = (self.anglevehicle((t+dt)/ttot) - self.anglevehicle(t/ttot)) / dt
    return pi/180 * (cur_W - prev_W)
end

##### INTERNAL FUNCTIONS  ######################################################

##### END OF KINEMATICMANEUVER  ################################################

################################################################################
# KINEMATIC MANEUVER TYPE
################################################################################
struct DynamicManeuver{N, M} <: AbstractManeuver{N, M}
    angle::NTuple{N, Function}
    RPM::NTuple{M, Function}
end

# # Implicit N and M constructor
# DynamicManeuver(a::NTuple{N, Function}, b::NTuple{M, Function}
#                                     ) where {N, M} = DynamicManeuver{N, M}(a, b)

##### FUNCTIONS  ###############################################################
function calc_dV(self::DynamicManeuver, vehicle::AbstractVehicle, t::Real,
                                            dt::Real, ttot::Real, Vref::Real)
    # Here calculate change in velocity of the vehicle based on current
    # aerodynamic forces
    error("Implementation pending")
end

function calc_dw(self::KinematicManeuver, vehicle::AbstractVehicle, t::Real,
                                                         dt::Real, ttot::Real)
     # Here calculate change in angular velocity of the vehicle based on current
     # aerodynamics forces
    error("Implementation pending")
end

##### INTERNAL FUNCTIONS  ######################################################

##### END OF KINEMATICMANEUVER  ################################################

#


module LinQu

using ITensors, LinearAlgebra, Random

import Base: length,
			 copy,
			 push!,
			 isless,
			 delete!,
			 getindex,
			 setindex,
			 findfirst,
			 deleteat!,
			 print,
			 show,
			 replace!,
			 reverse,
			 size,
			 +,
			 -,
			 *,
			 /,
			 ==,
			 isapprox,
			 pop!,
			 range,
			 clamp
import LinearAlgebra: norm,
					  normalize!
import ITensors:getindex,
				 noprime,
				 noprime!,
				 prime,
				 svd,
				 qr,
				 position!,
				 setindex!,
				 commonindex,
				 iterate,
				 ITensor,
				 Index,
				 norm,
				 order,
				 *,
				 dag

# functions from Base, ITensors
export linkindex,
	   getindex,
	   noprime,
	   noprime!,
	   prime,
	   svd,
	   position!,
	   setindex!,
	   svd,
	   commonindex,
	   leftLim,
	   rightLim,
	   iterate,
	   ITensor,
	   Index,
	   randomITensor,
	   qr,
	   IndexSet,
	   order,
	   norm
# Types
export  QState,
		QGate,
		QCircuit,
		MPSState,
		ExactState,
		QGateBlock

include("extension.jl")
export getindex,
		contract,
		clamp

include("qgates/gatekernel.jl")
export  GateKernel,
		func,
		name
include("qgates/commongates/constants.jl")
export  IntFloat,
		Location,
		I_DATA,
		X_DATA,
		Y_DATA,
		Z_DATA,
		H_DATA,
		T_DATA,
		TDAG_DATA,
		S_DATA,
		SDAG_DATA,
		CNOT_DATA,
		SWAP_DATA,
		TOFFOLI_DATA,
		FREDKIN_DATA,
		CSWAP_DATA,
		CZ_DATA
include("qgates/commongates/commonkernel.jl")

include("qgates/qgate.jl")
export  QGate

include("qgates/constgate.jl")
export 	ConstGate,
		qubits,
		data,
		copy,
		control,
		randomConstGate

include("qgates/vargate.jl")
export  VarGate,
		qubits,
		param,
		func,
		copy,
		data,
		control,
		==,
		randomVarGate

include("qgates/measuregate.jl")
export MeasureGate,
	   qubits,
	   copy,
	   MEASURE_RESET,
	   MEASURE

include("qgates/commongates/commongates.jl")
export 	I,
		X,
		Y,
		Z,
		H,
		T,
		TDAG,
		S,
		SDAG,
		CNOT,
		SWAP,
		TOFFOLI,
		FREDKIN,
		CZ,
		Rx,
		Ry,
		Rz,
		Rϕ,
		ROTATE

include("qstates/qstate.jl")
export 	QState,
		probability

include("qstates/mpsstate/qubitsitemap.jl")
export 	QubitSiteMap,
		updateMap!

include("qstates/mpsstate/mpsstate.jl")
export MPSState,
	   swapSites!,
	   applyLocalGate!,
	   localizeQubits!,
	   centerAtSite!,
	   centerAtQubit!,
	   moveQubit!,
	   moveSite!,
	   orderQubits!,
	   apply!,
	   toExactState,
	   oneShot,
	   measure!,
	   collapse!,
	   showData,
	   normalize!,
	   dag,
	   ρ,
	   logprobability,
	   project!

include("qstates/exactstate.jl")
export 	ExactState,
		findindex,
		ITensor,
		toMPSState,
		isapprox

include("qgates/qgateblock.jl")
export  QGateBlock,
	    gates,
	    length,
	    push!,
	    add!,
	    addCopy!,
	    flatten,
	    extractParams,
	    insertParams!,
	    inverse,
	    randomQGateBlock

include("qgates/qcircuit.jl")
export	add!,
		QCircuit,
		flatten,
		randomQCircuit,
		showDetail

include("inter_qstate_qgate/exactstate_qgate.jl")
include("inter_qstate_qgate/mpsstate_qgate.jl")
include("inter_qstate_qgate/qstate_block_circuit.jl")
export  apply!

include("interface.jl")
export 	getindex,
		apply!

include("stat/sampling.jl")
export MCprobability!

# Following files are documentations
include("../doc/YQ_doc.jl")
end #module


using Compat
using MLBase
using Distributions
import Base: length, convert, promote_rule

if VERSION >= v"0.4.0-dev"
    typealias Range1{Int} Range{Int}
    _int(x) = round(Integer, x)
    float(x) = map(Float64, x)
else
    _int(x) = int(x)
end
squeeze(v::Vector, i::Integer) = v

const NO_BEST=(0,0)

immutable Leaf
    majority::Any
    values::Vector
end

@compat immutable Node
    featid::Integer
    featval::Any
    left::Union{Leaf,Node}
    right::Union{Leaf,Node}
end

@compat typealias LeafOrNode Union{Leaf,Node}

immutable Ensemble
    trees::Vector{Node}
end

convert(::Type{Node}, x::Leaf) = Node(0, nothing, x, Leaf(nothing,[nothing]))
promote_rule(::Type{Node}, ::Type{Leaf}) = Node
promote_rule(::Type{Leaf}, ::Type{Node}) = Node

length(ensemble::Ensemble) = length(ensemble.trees)

include("measures.jl")
include("classification.jl")
include("auc.jl")


module GR_Spherical

using DifferentialEquations
using BoundaryValueDiffEq
using OrdinaryDiffEq
using Fun1d
using DataFrames
using CSV
using Plots
using Roots

# using Random

struct GBSSN_Variables{S,T} <: AbstractArray{T,2}
    α::GridFun{S,T}
    A::GridFun{S,T}
    βr::GridFun{S,T}
    Br::GridFun{S,T}
    χ::GridFun{S,T}
    γtrr::GridFun{S,T}
    γtθθ::GridFun{S,T}
    Arr::GridFun{S,T}
    K::GridFun{S,T}
    Γr::GridFun{S,T}
    𝜙::GridFun{S,T}
    K𝜙::GridFun{S,T}
end

cont(x::GBSSN_Variables) = (x.α, x.A, x.βr, x.Br, x.χ, x.γtrr, x.γtθθ, x.Arr, x.K, x.Γr, x.𝜙, x.K𝜙)
numvar = 12

# Iteration
Base.IteratorSize(::Type{<:GBSSN_Variables}) = Iterators.HasShape{2}()
Base.eltype(::Type{GBSSN_Variables{S,T}}) where {S,T} = T
Base.isempty(x::GBSSN_Variables) = isempty(x.α)
function Base.iterate(x::GBSSN_Variables, state...)
    return iterate(Iterators.flatten(cont(x)), state...)
end
Base.size(x::GBSSN_Variables) = (length(x.α), numvar)
Base.size(x::GBSSN_Variables, d) = size(x)[d]

# Indexing
function lin2cart(x::GBSSN_Variables, i::Number)
    n = length(x.α)
    return (i - 1) % n + 1, (i - 1) ÷ n + 1
end
Base.firstindex(x::GBSSN_Variables) = error("not implemented")
Base.getindex(x::GBSSN_Variables, i) = getindex(x, i.I...)
Base.getindex(x::GBSSN_Variables, i::Number) = getindex(x, lin2cart(x, i)...)
Base.getindex(x::GBSSN_Variables, i, j) = getindex(cont(x)[j], i)
Base.lastindex(x::GBSSN_Variables) = error("not implemented")
Base.setindex!(x::GBSSN_Variables, v, i) = setindex!(x, v, i.I...)
Base.setindex!(x::GBSSN_Variables, v, i::Number) = setindex!(x, v, lin2cart(x, i))
Base.setindex!(x::GBSSN_Variables, v, i, j) = setindex!(cont(x)[j], v, i)

# Abstract Array
Base.IndexStyle(::GBSSN_Variables) = IndexCartesian()
Base.similar(x::GBSSN_Variables) = GBSSN_Variables(map(similar, cont(x))...)
function Base.similar(x::GBSSN_Variables, ::Type{T}) where {T}
    return GBSSN_Variables(map(y -> similar(y,T), cont(x))...)
end
Base.similar(x::GBSSN_Variables, ::Dims) = similar(x)
Base.similar(x::GBSSN_Variables, ::Dims, ::Type{T}) where {T} = similar(x, T)

# Broadcasting
Base.BroadcastStyle(::Type{<:GBSSN_Variables}) = Broadcast.ArrayStyle{GBSSN_Variables}()
function Base.similar(bc::Broadcast.Broadcasted{Broadcast.ArrayStyle{GBSSN_Variables}},
                      ::Type{T}) where {T}
    x = find_GBSSN_Variables(bc)
    return similar(x, T)
end
find_GBSSN_Variables(bc::Base.Broadcast.Broadcasted) = find_GBSSN_Variables(bc.args)
find_GBSSN_Variables(args::Tuple) = find_GBSSN_Variables(find_GBSSN_Variables(args[1]), Base.tail(args))
find_GBSSN_Variables(x) = x
find_GBSSN_Variables(::Tuple{}) = nothing
find_GBSSN_Variables(a::GBSSN_Variables, rest) = a
find_GBSSN_Variables(::Any, rest) = find_GBSSN_Variables(rest)

# Others
function Base.map(fun, x::GBSSN_Variables, ys::GBSSN_Variables...)
    return GBSSN_Variables(
        map(fun, x.α, (y.α for y in ys)...),
        map(fun, x.A, (y.A for y in ys)...),
        map(fun, x.βr, (y.βr for y in ys)...),
        map(fun, x.Br, (y.Br for y in ys)...),
        map(fun, x.χ, (y.χ for y in ys)...),
        map(fun, x.γtrr, (y.γtrr for y in ys)...),
        map(fun, x.γtθθ, (y.γtθθ for y in ys)...),
        map(fun, x.Arr, (y.Arr for y in ys)...),
        map(fun, x.K, (y.K for y in ys)...),
        map(fun, x.Γr, (y.Γr for y in ys)...),
        map(fun, x.𝜙, (y.𝜙 for y in ys)...),
        map(fun, x.K𝜙, (y.K𝜙 for y in ys)...)
        )
end

# function Base.rand(rng::AbstractRNG, ::Random.SamplerType{GBSSN_Variables{T}}) where {T}
#     return GBSSN_Variables{T}(rand(rng, T), rand(rng, T))
# end

Base.zero(::Type{<:GBSSN_Variables}) = error("not implemented")
Base.zero(x::GBSSN_Variables) = GBSSN_Variables(map(zero, cont(x))...)

Base.:+(x::GBSSN_Variables) = map(+, x)
Base.:-(x::GBSSN_Variables) = map(-, x)

Base.:+(x::GBSSN_Variables, y::GBSSN_Variables) = map(+, x, y)
Base.:-(x::GBSSN_Variables, y::GBSSN_Variables) = map(-, x, y)

Base.:*(x::GBSSN_Variables, a::Number) = map(b -> b * a, x)
Base.:*(a::Number, x::GBSSN_Variables) = map(b -> a * b, x)
Base.:/(x::GBSSN_Variables, a::Number) = map(b -> b / a, x)
Base.:\(a::Number, x::GBSSN_Variables) = map(b -> a \ b, x)

################################################################################

function printlogo()
    # Just a fancy ASCII logo for the program
    println(
"\n",
"           /\\\\\\\\\\\\\\\\\\       /\\\\\\\\\\\\\\\\\\\\\\\\\n",
" ________/\\\\\\\\\\\\\\\\\\\\\\\\\\\\___/ /\\\\\\\\/ / /\\\\\\\\_____________________________\n",
"  ______//\\\\\\\\\\/ / / / /___\\/ /\\\\\\\\/_/ / /\\\\\\\\___________________________\n",
"   _____//\\\\\\\\/_/_/_/_/_____\\/ /\\\\\\\\ \\/ / /\\\\\\\\___________________________\n",
"    ____/ /\\\\\\\\____/\\\\\\\\\\\\___\\/ /\\\\\\\\\\\\\\\\\\\\\\\\\\\\_______in___________________\n",
"     ___\\/ /\\\\\\\\__/ /  /\\\\\\___\\/ /\\\\\\\\\\\\\\\\\\\\\\\\/________Spherical____________\n",
"      ___\\/ /\\\\\\\\_\\/__/ /\\\\\\___\\/ /\\\\\\\\ / /\\\\\\\\__________Symmetry____________\n",
"       ___\\/ /\\\\\\\\____\\/ /\\\\\\___\\/ /\\\\\\\\_/ //\\\\\\\\_____________________________\n",
"        ___\\/ /\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\___\\/ /\\\\\\\\\\// //\\\\\\\\____________________________\n",
"         ___\\/ / /\\\\\\\\\\\\\\\\\\  /____\\/ /\\\\\\\\_\\// //\\\\\\\\_____by Erik Schnetter_____\n",
"          ___\\/_/ / / / / /_/______\\/ /  /___\\// /  /_______and Conner Dailey____\n",
"           _____\\/_/_/_/_/__________\\/__/______\\/__/______________________________\n"
)

end

function setup(::Type{S}, rspan, points::Int) where {S}

    # Specify the radial domain and the
    # radial step amount and make a Grid object
    rmin,rmax = rspan
    domain = Domain{S}(rmin, rmax)
    grid = Grid(domain, points)
    return grid

end

function dissipation(f::GridFun{S,T}) where {S,T}

    ############################################
    # Calculates the numerical dissipation terms
    #
    # Finite differencing fails to model high
    # frequency modes in the system, and these
    # modes can lead to instabilities. Adding
    # numerical dissipation terms damps these
    # high frequency modes by subtracting off
    # a high order derivative from each dynamical
    # variable in the system.
    ############################################

    dx = spacing(f.grid)
    n = f.grid.ncells + 4
    dvalues = Array{T}(undef, n)

    dvalues[1:4] .= 0.

    # dvalues[3] = -((-48*f.values[3] + 96*f.values[4] - 48*f.values[5])/17)
    #
    # dvalues[4] = -((96*f.values[3] - 240*f.values[4] + 192*f.values[5]
    #     - 48*f.values[6])/59)
    #
    # dvalues[5] = -((-48*f.values[3] + 192*f.values[4] - 288*f.values[5]
    #     + 192*f.values[6] - 48*f.values[7])/43)
    #
    # dvalues[6] = -((-48*f.values[4] + 192*f.values[5] - 288*f.values[6]
    #     + 192*f.values[7] - 48*f.values[8])/49)

    for i in 5:(n - 4)
        dvalues[i] = (f.values[i-2] - 4*f.values[i-1] + 6*f.values[i]
        - 4*f.values[i+1] + f.values[i+2])
    end
    dvalues[(n-3):n] .= 0.

    return GridFun(f.grid, dvalues)
end

function init(::Type{T}, grid::Grid, param) where {T}

    ############################################
    # Specifies the Initial Conditions
    ############################################

    n = grid.ncells + 4
    domain = grid.domain
    initgrid = grid
    drt = spacing(grid)
    r = param[4]
    drdrt = param[5]
    d2rdrt = param[6]
    m = param[7]
    rtspan = param[8]

    num = 0

    # Initial conditions for Schwarzschild metric (Ker-Schild Coordinates)

    # Mass (no real reason not to use 1 here)
    #M = 1

    fα(M,rt) = real((1+2*M/(r(rt))+0im)^(-1/2))
    fA(rt) = 0.
    fβr(M,rt) = (2*M/r(rt))*fα(M,rt)^2
    fBr(rt) = 0.
    fχ(rt) = 1.
    fγtrr(M,rt) = 1+2*M/r(rt)
    fγtθθ(rt) = r(rt)^2
    fArr(M,∂M,rt) = (4/3)*(r(rt)*(M+r(rt))*∂M-M*(3*M+2*r(rt)))/real(((r(rt)^5)*(r(rt)+2*M)+0im)^(1/2))
    fK(M,∂M,rt) = (2*M*(3*M+r(rt))+2*r(rt)*∂M*(M+r(rt)))/real((r(rt)*(r(rt)+2*M)+0im)^(3/2))
    fΓr(M,∂M,rt) = (r(rt)*∂M-2*r(rt)-5*M)/(r(rt)+2*M)^2

    # fArr(M,rt) = -(4*M/3)*(3*M+2*r(rt))/real(((r(rt)^5)*(r(rt)+2*M)+0im)^(1/2))
    # fK(M,rt) = (2*M)*(3*M+r(rt))/real((r(rt)*(r(rt)+2*M)+0im)^(3/2))
    # fΓr(M,rt) = -(2*r(rt)+5*M)/(r(rt)+2*M)^2

    r0 = 10.
    σr = 0.5
    #Amp = 1.
    Amp = 0*0.1

    f𝜙(rt) = Amp*(1/r(rt))*exp(-(1/2)*((r(rt)-r0)/σr)^2)
    f∂𝜙(rt) = Amp*exp(-(1/2)*((r(rt)-r0)/σr)^2)*(r(rt)*r0-r(rt)^2-σr^2)/(r(rt)^2*σr^2)
    #f∂t𝜙(rt) = Amp*exp(-(1/2)*((r(rt)-r0)/σr)^2)*(r0-r(rt))/(r(rt)*σr^2)
    fK𝜙(rt) = 0.

    f∂χ(rt) = 0.
    f∂γtrr(M,∂M,rt) = 2*(r(rt)*∂M-M)/(r(rt)^2)
    f∂γtθθ(rt) = 2*r(rt)
    # f∂Arr(M,rt) = (4*M/3)*(15*M^2+15*M*r(rt)+4*r(rt)^2)/real(((r(rt)^7)*((r(rt)+2*M)^3)+0im)^(1/2))
    # f∂K(M,rt) = -2*M*(9*M^2+10*M*r(rt)+2*r(rt)^2)/real((r(rt)*(r(rt)+2*M)+0im)^(5/2))
    # f∂Γr(M,rt) = 2*(r(rt)+3*M)/(r(rt)+2*M)^3
    #
    f∂2γtθθ(rt) = 2.
    f∂2χ(rt) = 0.

    # fgtt(M,rt) = -(1/fα(M,rt)^2)
    # fgtr(M,rt) = fβr(M,rt)/fα(M,rt)^2
    # fgrr(M,χ,rt) = χ/fγtrr(M,rt) - (fβr(M,rt)/fα(M,rt))^2
    #
    # # Lagrangian Density for scalar field
    #
    # f𝓛(M,χ,rt) = ((1/2)*fgtt(M,rt)*f∂t𝜙(rt)^2 + (1/2)*fgrr(M,χ,rt)*f∂𝜙(rt)^2
    #  + fgtr(M,rt)*f∂𝜙(rt)*f∂t𝜙(rt) - (1/2)*(m^2)*f𝜙(rt)^2)
    #
    # # Stresss Energy components (contravariant indices)
    #
    # fTtt(M,χ,rt) = (fgtt(M,rt)*f∂t𝜙(rt) + fgtr(M,rt)*f∂𝜙(rt)).^2 - fgtt(M,rt)*f𝓛(M,χ,rt)
    # fTtr(M,χ,rt) = ((fgtt(M,rt)*f∂t𝜙(rt) + fgtr(M,rt)*f∂𝜙(rt))*(fgtr(M,rt)*f∂t𝜙(rt)
    #  + fgrr(M,χ,rt)*f∂𝜙(rt)) - fgtr(M,rt)*f𝓛(M,χ,rt))

    #fK𝜙(rt) = -(f∂t𝜙(rt) - fβr(rt)*f∂𝜙(rt))/(2*fα(M,rt))

    fρ(M,rt) = (2*fK𝜙(rt)^2 + (1/2)*(fχ(rt)/fγtrr(M,rt))*f∂𝜙(rt)^2
        + (1/2)*m^2*f𝜙(rt)^2)

    fSr(rt) = 2*fK𝜙(rt)*f∂𝜙(rt)

    #f∂χ(rt,(χ, ∂χ, M)) = ∂χ

    # function f∂2χ(rt,(χ, X, Arr))
    #  -(1/2)*fγtrr(rt)*(-(3/2)*(Arr/fγtrr(rt))^2 + (2/3)*fK(rt)^2
    #  - (5/2)*((X^2)/χ)/fγtrr(rt)
    #  + 2*χ/fγtθθ(rt) - 2*χ*(f∂2γtθθ(rt)/fγtθθ(rt))/fγtrr(rt)
    #  + 2*X*(f∂γtθθ(rt)/fγtθθ(rt))/fγtrr(rt)
    #  + χ*(f∂γtrr(rt)/(fγtrr(rt)^2))*(f∂γtθθ(rt)/fγtθθ(rt))
    #  - X*f∂γtrr(rt)/(fγtrr(rt)^2)
    #  + (1/2)*χ*((f∂γtθθ(rt)/fγtθθ(rt))^2)/fγtrr(rt) - 16*pi*fρ(rt,χ))
    # end
    #
    # function f∂Arr(rt,(χ, X, Arr))
    #  -fγtrr(rt)*(-(2/3)*f∂K(rt) - (3/2)*Arr*(X/χ)/fγtrr(rt)
    #  + (3/2)*Arr*(f∂γtθθ(rt)/fγtθθ(rt))/fγtrr(rt) - Arr*f∂γtrr(rt)/(fγtrr(rt)^2)
    #  - 8*pi*fγtrr(rt)*fSr(rt,χ)/χ)
    # end

    f∂M(M,rt) = 4*pi*(r(rt)^2)*fρ(M,rt)

    function f𝓗(M,∂M,rt)
         (-(3/2)*(fArr(M,∂M,rt)/fγtrr(M,rt))^2 + (2/3)*fK(M,∂M,rt)^2
         - (5/2)*((f∂χ(rt)^2)/fχ(rt))/fγtrr(M,rt) + 2*f∂2χ(rt)/fγtrr(M,rt)
         + 2*fχ(rt)/fγtθθ(rt) - 2*fχ(rt)*(f∂2γtθθ(rt)/fγtθθ(rt))/fγtrr(M,rt)
         + 2*f∂χ(rt)*(f∂γtθθ(rt)/fγtθθ(rt))/fγtrr(M,rt)
         + fχ(rt)*(f∂γtrr(M,∂M,rt)/(fγtrr(M,rt)^2))*(f∂γtθθ(rt)/fγtθθ(rt))
         - f∂χ(rt)*f∂γtrr(M,∂M,rt)/(fγtrr(M,rt)^2)
         + (1/2)*fχ(rt)*((f∂γtθθ(rt)/fγtθθ(rt))^2)/fγtrr(M,rt) - 16*pi*fρ(M,rt))
    end

    # function f𝓜r(M,∂M,rt)
    #  (f∂Arr(M,∂M,rt)/fγtrr(rt) - (2/3)*f∂K(rt) - (3/2)*fArr(rt)*(f∂χ(rt)/fχ(rt))/fγtrr(rt)
    #  + (3/2)*fArr(rt)*(f∂γtθθ(rt)/fγtθθ(rt))/fγtrr(rt) - fArr(rt)*f∂γtrr(rt)/(fγtrr(rt)^2)
    #  - 8*pi*fγtrr(rt)*fSr(rt,fχ(rt))/fχ(rt))
    # end

    # grid = Grid(domain,Int((2^num)*(n-5)+1))
    # n = grid.ncells + 4

    # ∂χ = sample(T, grid, rt->0)
    # ∂γtrr = sample(T, grid, f∂γtrr)
    # ∂γtθθ = sample(T, grid, f∂γtθθ)
    # ∂K = sample(T, grid, f∂K)
    #∂Arr = sample(T, grid, rt->(4*M/3)*(15*M^2+15*M*r(rt)+4*r(rt)^2)/real(((r(rt)^7)*((r(rt)+2*M)^3)+0im)^(1/2)))

    # ∂2γtθθ = sample(T, grid, f∂2γtθθ)
    #
    # order = 4
    #
    # rr = sample(T, grid, param[4])
    # drdrt = sample(Float64, grid, param[5])
    #
    # ∂rt𝜙 = deriv(𝜙,order,1)
    # ∂𝜙 = ∂rt𝜙./drdrt

    # fArrreg(M,∂M,rt) = real((r(rt)+ 0im)^(5/2))*fArr(M,∂M,rt)
    # fKreg(M,∂M,rt) = real((r(rt)+ 0im)^(3/2))*fK(M,∂M,rt)

    fαreg(M,rt) = 2*M
    fγtrrreg(M,rt) = 2*M
    fArrreg(M,∂M,rt) = real((r(rt)+ 0im)^(5/2))*fArr(M,∂M,rt)
    fKreg(M,∂M,rt) = real((r(rt)+ 0im)^(3/2))*fK(M,∂M,rt)

    # Constraint Equations

    rtspan = (rtspan[1], rtspan[2])
    #rtspan = (rtspan[2], 0.5)

    function constraintSystem(M, param, rt)
        f∂M(M,rt)*drdrt(rt)
    end

    # function boundaryCondition!(residual, M, param, rt)
    #     residual = M[1] - 1. #inner boundary condition
    # end

    atol = 1e-15

    BVP = ODEProblem(constraintSystem, 1., rtspan, param)
    M = solve(BVP, Tsit5(), abstol=atol, dt=drt, adaptive=false)

    ∂M(rt) = f∂M(M(rt),rt)

    # M(rt) = 1.
    # ∂M(rt) = 0.

    α = sample(T, grid, rt -> fα(M(rt),rt) )
    A = sample(T, grid, fA)
    βr = sample(T, grid, rt -> fβr(M(rt),rt) )
    Br = sample(T, grid, fBr)
    χ = sample(T, grid, fχ)
    γtrr = sample(T, grid, rt -> fγtrr(M(rt),rt) )
    γtθθreg = sample(T, grid, rt -> 0)
    Arr = sample(T, grid, rt -> fArr(M(rt),∂M(rt),rt) )
    K = sample(T, grid, rt -> fK(M(rt),∂M(rt),rt) )
    Γr = sample(T, grid, rt -> fΓr(M(rt),∂M(rt),rt))
    𝜙 = sample(T, grid, f𝜙)
    K𝜙 = sample(T, grid, fK𝜙)

    # ∂𝜙 = sample(T, grid, f∂𝜙)
    # ∂t𝜙 = sample(T, grid, f∂t𝜙)
    #
    # K𝜙 = -(∂t𝜙 - βr.*∂𝜙)./(2*α)
    #

    state = GBSSN_Variables(α, A, βr, Br, χ, γtrr, γtθθreg, Arr, K, Γr, 𝜙, K𝜙)

    #
    #cons = constraints(T,state,param)

    #println(f∂M(14.,(1., 0., 1.)))
    #println(f∂2χ(14.,(1., 0., 1.)))

    #rr = sample(T, grid, param[4])
    # 𝓗 = sample(T, grid, f𝓗)
    # 𝓜r = sample(T, grid, f𝓜r)
    #
    # ∂2χ = sample(T, grid, rt -> f∂Arr(rt,(1, 0, fArr(rt)))-f∂Arr(rt))
    #
    #println(χ[1:10].-1)
    # println(χ[n-10:n].-1)
    #
    # temp1 = sample(T, grid, rt -> solution(rt)[3]-0*fArr(rt))
    #temp = sample(T, grid, rt -> f𝓗(M(rt),∂M(rt),rt))
    #temp = sample(T, grid, rt -> M(rt))

    #plot(rr[5:n-2],temp[5:n-2])
    #plot!(rr[5:n-2],temp1[5:n-2])

    #plot(solution, vars=(0,1))
    #println(temp[1:10])
    #println(temp[n-15:n-5])

    #plot(rr[5:n-4],cons[1][5:n-4])

    return state

end

function rhs(state::GBSSN_Variables, param, t)

    ############################################
    # Caculates the right hand ride of the
    # evolved variables
    #
    # This is the main meat of the program.
    # This function contains all of the boundary
    # conditions, coordinate conversions,
    # spatial derivative calculations,
    # evolution equations, and numerical
    # dissipation. Each time the Julia DiffEq
    # Solver moves one time step, it calls
    # this function to calculate the new
    # values of the evolved variables.
    ############################################

    # Unpack the Variables

    α = state.α
    A = state.A
    βr = state.βr
    Br = state.Br
    χ = state.χ
    γtrr = state.γtrr
    γtθθreg = state.γtθθ
    Arr = state.Arr
    K = state.K
    Γr = state.Γr
    𝜙 = state.𝜙
    K𝜙 =state.K𝜙

    drt = spacing(α.grid)
    n = α.grid.ncells + 4

    m = param[7]

    # Boundary Conditions

    # These inner boundary conditions are necessary for stable
    # evolution for the specified gauge condition and do not
    # specify anything physical about the system.

    # Eulerian/Lagrangian condition (0/1)
    v = param[3]

    # if v == 0 # Eulerian Condition
    #     βr[2] = (17*βr[3] + 9*βr[4] - 5*βr[5] + βr[6])/22
    # elseif v == 1 # Lagrangian Condition
    #     γtθθreg[2] = ((-315*γtθθreg[3] + 210*γtθθreg[4]
    #     - 126*γtθθreg[5] + 45*γtθθreg[6] - 7*γtθθreg[7])/63)
    #     #βr[2] = (-315*βr[3] + 210*βr[4] - 126*βr[5] + 45*βr[6] - 7*βr[7])/63
    # end

    # Spatial Derivatives (finite differences) with respect to coordinate rt

    # Accuarcy order, 2 for 2nd order, 4 for 4th order
    order = 4

    # First derivatives
    ∂rtα = deriv(α,order,1)
    ∂rtA = deriv(A,order,1)
    ∂rtβr = deriv(βr,order,-1)
    ∂rtBr = deriv(Br,order,-1)
    ∂rtχ = deriv(χ,order,1)
    ∂rtγtrr = deriv(γtrr,order,1)
    ∂rtγtθθreg = deriv(γtθθreg,order,1)
    ∂rtArr = deriv(Arr,order,1)
    ∂rtK = deriv(K,order,1)
    ∂rtΓr = deriv(Γr,order,-1)
    ∂rt𝜙 = deriv(𝜙,order,1)
    ∂rtK𝜙 = deriv(K𝜙,order,-1)

    # Second derivatives

    # ∂2rtα = deriv2(α,order,1)
    # ∂2rtβr = deriv2(βr,order,-1)
    # ∂2rtχ = deriv2(χ,order,1)
    # ∂2rtγtrr = deriv2(γtrr,order,1)
    # ∂2rtγtθθreg = deriv2(γtθθreg,order,1)
    # ∂2rt𝜙 = deriv2(𝜙,order,1)

    ∂2rtα = deriv(∂rtα,order,1)
    ∂2rtβr = deriv(∂rtβr,order,-1)
    ∂2rtχ = deriv(∂rtχ,order,1)
    ∂2rtγtrr = deriv(∂rtγtrr,order,1)
    ∂2rtγtθθreg = deriv(∂rtγtθθreg,order,1)
    ∂2rt𝜙 = deriv(∂rt𝜙,order,1)

    # Coordinate transformations from computational rt coordinate
    # to physical r coordinate

    r = sample(Float64, α.grid, param[4])
    drdrt = sample(Float64, α.grid, param[5])
    d2rdrt = sample(Float64, α.grid, param[6])

    ∂α = ∂rtα./drdrt
    ∂A = ∂rtA./drdrt
    ∂βr = ∂rtβr./drdrt
    ∂Br = ∂rtBr./drdrt
    ∂χ = ∂rtχ./drdrt
    ∂γtrr = ∂rtγtrr./drdrt
    ∂γtθθreg = ∂rtγtθθreg./drdrt
    ∂Arr = ∂rtArr./drdrt
    ∂K = ∂rtK./drdrt
    ∂Γr = ∂rtΓr./drdrt
    ∂𝜙 = ∂rt𝜙./drdrt
    ∂K𝜙 = ∂rtK𝜙./drdrt

    ∂2α = (∂2rtα - d2rdrt.*∂α)./(drdrt.^2)
    ∂2βr = (∂2rtβr - d2rdrt.*∂βr)./(drdrt.^2)
    ∂2χ = (∂2rtχ - d2rdrt.*∂χ)./(drdrt.^2)
    ∂2γtrr = (∂2rtγtrr - d2rdrt.*∂γtrr)./(drdrt.^2)
    ∂2γtθθreg = (∂2rtγtθθreg - d2rdrt.*∂γtθθreg)./(drdrt.^2)
    ∂2𝜙 = (∂2rt𝜙 - d2rdrt.*∂𝜙)./(drdrt.^2)

    # r[1:2] .= 0.
    # βr[1:2] .= 0.
    # γtθθreg[1:2] .= 0.
    # Γr[1:2] .= 0.

    # Conversions from regularized variables to canonical variables

    γtθθ = (r.^2).*(γtθθreg .+ 1)
    ∂γtθθ = (r.^2).*∂γtθθreg + (2*r).*(γtθθreg .+ 1)
    ∂2γtθθ = (r.^2).*∂2γtθθreg + (4*r).*∂γtθθreg + 2*(γtθθreg .+ 1)

    # γtθθ[1:2] .= 0.
    # ∂γtθθ[1:2] .= 0.
    # ∂2γtθθ[1:2] .= 0.

    # K = real((r .+ 0im).^(-3/2)).*Kreg
    # ∂K = real((r .+ 0im).^(-3/2)).*∂Kreg - (3/2)*real((r .+ 0im).^(-5/2)).*Kreg
    #
    # Arr = real((r .+ 0im).^(-5/2)).*Arrreg
    # ∂Arr = real((r .+ 0im).^(-5/2)).*∂Arrreg - (5/2)*real((r .+ 0im).^(-7/2)).*Arrreg

    # Γr = -(2 ./r).*Γreg
    # ∂Γr = -(2 ./r).*∂Γreg + (2 ./(r.^2)).*Γreg

    # Γr = Γreg
    # ∂Γr = ∂Γreg

    # Gauge Conditions

    # Coordinate drift parameter.
    # Positive values lead to continued evolution
    # zero gives eventual steady state
    η = 0

    #Superscript condition...1 is a plus, 0 is a minus
    a = 0

    #Subscript condition...1 is a plus, 0 is a minus
    b = 0

    # Zero condition, 1 includes shift, 0 for vanishing shift
    # if this is 0, a and b don't matter as long as the
    # initial shift is zero
    c = 1

    #########################################################
    # Evolution Equations
    #
    # This is the full suite of evolution equations
    # for GR in spherical symmetry in the BSSN framework.
    # I have tried to keep them looking as close to their
    # mathematically written counterpart as possible.
    #
    # They are written in the order they appear in the
    # reference (arXiv:0705.3845v2) except for the ∂tBr
    # equation since it contains a ∂tΓr term.
    #
    #########################################################

    ∂tα = a*βr.*∂α - 2*α.*A

    ∂tβr = c*((3/4)*Br + b*βr.*∂βr)

    ∂tχ = ((2/3)*K.*α.*χ - (1/3)*v*βr.*χ.*∂γtrr./γtrr - (2/3)*v*βr.*χ.*∂γtθθ./γtθθ
     - (2/3)*v*χ.*∂βr + βr.*∂χ)

    ∂tγtrr = (-2*Arr.*α - (1/3)*v*βr.*∂γtrr + βr.*∂γtrr
     - (2/3)*v*γtrr.*βr.*∂γtθθ./γtθθ + 2*γtrr.*∂βr - (2/3)*v*γtrr.*∂βr)

    ∂tγtθθ = (Arr.*γtθθ.*α./γtrr - (1/3)*v*γtθθ.*βr.*∂γtrr./γtrr - (2/3)*v*βr.*∂γtθθ
     + βr.*∂γtθθ - (2/3)*v*γtθθ.*∂βr)

    ∂tArr = (-2*α.*(Arr.^2)./γtrr + K.*α.*Arr - (1/3)*v*βr.*Arr.*∂γtrr./γtrr
     - (2/3)*v*βr.*Arr.*∂γtθθ./γtθθ - (2/3)*v*Arr.*∂βr + 2*Arr.*∂βr
     + (2/3)*α.*χ.*(∂γtrr./γtrr).^2 - (1/3)*α.*χ.*(∂γtθθ./γtθθ).^2
     - (1/6)*α.*(∂χ.^2)./χ - (2/3)*α.*χ.*γtrr./γtθθ + βr.*∂Arr
     + (2/3)*α.*χ.*γtrr.*∂Γr - (1/2)*α.*χ.*(∂γtrr./γtrr).*(∂γtθθ./γtθθ)
     + (1/3)*χ.*∂γtrr.*∂α./γtrr + (1/3)*χ.*∂α.*∂γtθθ./γtθθ - (1/6)*α.*∂γtrr.*∂χ./γtrr
     - (1/6)*α.*∂γtθθ.*∂χ./γtθθ - (2/3)*∂α.*∂χ - (1/3)*α.*χ.*∂2γtrr./γtrr
     + (1/3)*α.*χ.*∂2γtθθ./γtθθ - (2/3)*χ.*∂2α + (1/3)*α.*∂2χ)

    ∂tK = ((3/2)*α.*(Arr./γtrr).^2 + (1/3)*α.*K.^2 + βr.*∂K
     + (1/2)*χ.*∂γtrr.*∂α./(γtrr.^2) - χ.*∂α.*(∂γtθθ./γtθθ)./γtrr
     + (1/2)*∂α.*∂χ./γtrr - χ.*∂2α./γtrr)

    ∂tΓr = (-v*βr.*((∂γtθθ./γtθθ).^2)./γtrr + α.*Arr.*(∂γtθθ./γtθθ)./(γtrr.^2)
     - (1/3)*v*∂βr.*(∂γtθθ./γtθθ)./γtrr + ∂βr.*(∂γtθθ./γtθθ)./γtrr
     + βr.*∂Γr + α.*Arr.*∂γtrr./(γtrr.^3)
     - (4/3)*α.*∂K./γtrr - 2*Arr.*∂α./(γtrr.^2) + (1/2)*v*∂βr.*∂γtrr./(γtrr.^2)
     - (1/2)*∂βr.*∂γtrr./(γtrr.^2) - 3*α.*Arr.*(∂χ./χ)./(γtrr.^2)
     + (1/6)*v*βr.*∂2γtrr./(γtrr.^2) + (1/3)*v*βr.*(∂2γtθθ./γtθθ)./γtrr
     + (1/3)*v*∂2βr./γtrr + ∂2βr./γtrr)

    ∂tA = ∂tK

    ∂tBr = c*(∂tΓr + b*βr.*∂Br - b*βr.*∂Γr - η*Br)

    #########################################################
    # Source Terms and Source Evolution
    #
    # This currently includes the addition of source terms
    # to GR that come from a Klein-Gordon scalar field
    #
    #########################################################

    # Klein-Gordon System

    ∂t𝜙 = βr.*∂𝜙 - 2*α.*K𝜙
    ∂tK𝜙 = (βr.*∂K𝜙 + α.*K.*K𝜙 - (1/2)*α.*χ.*∂2𝜙./γtrr
        + (1/4)*α.*χ.*∂γtrr.*∂𝜙./γtrr.^2 - (1/4)*α.*∂χ.*∂𝜙./γtrr
        - (1/2)*χ.*∂α.*∂𝜙./γtrr - (1/2)*χ.*∂γtθθ.*∂𝜙./(γtrr.*γtθθ)
        + (1/2)*∂χ.*∂𝜙./(γtrr) + (1/2)*m^2*𝜙)

    # Inverse metric (contravariant indices)

    # gtt = -(1 ./α.^2)
    # gtr = βr./α.^2
    # grr = χ./γtrr - (βr./α).^2
    # gθθ = χ./γtθθ
    #
    # # Lagrangian Density for scalar field
    #
    # 𝓛 = (1/2)*gtt.*∂t𝜙.^2 + (1/2)*grr.*∂𝜙.^2 + gtr.*∂𝜙.*∂t𝜙 - (1/2)*(m^2)*𝜙.^2
    #
    # # Stresss Energy components (contravariant indices)
    #
    # Ttt = (gtt.*∂t𝜙 + gtr.*∂𝜙).^2 - gtt.*𝓛
    # Trr = (gtr.*∂t𝜙 + grr.*∂𝜙).^2 - grr.*𝓛
    # Ttr = (gtt.*∂t𝜙 + gtr.*∂𝜙).*(gtr.*∂t𝜙 + grr.*∂𝜙) - gtr.*𝓛
    # Tθθ = -gθθ.*𝓛

    # Source Terms to GR
    # Sr here is a contravariant vector component
    # Srr here is a covariant tensor component

    # ρ = (α.^2).*Ttt
    # Sr = α.*Ttr
    # Srr = ((γtrr.^2)./(χ.^2)).*Trr
    # S = (γtrr.*Trr + 2*γtθθ.*Tθθ)./χ

    ρ = 2*K𝜙.^2 + (1/2)*(χ./γtrr).*∂𝜙.^2 + (1/2)*m^2*𝜙.^2
    Sr = 2*K𝜙.*∂𝜙
    S = 6*K𝜙.^2 - (1/2)*(χ./γtrr).*∂𝜙.^2 - (3/2)*m^2*𝜙.^2
    Srr = (γtrr./χ).*(2*K𝜙.^2 + (1/2)*(χ./γtrr).*∂𝜙.^2 - (1/2)*m^2*𝜙.^2)

    ∂tArr .+= -8*pi*α.*(χ.*Srr - (1/3)*S.*γtrr)
    ∂tK .+= 4*pi*α.*(ρ + S)
    ∂tΓr .+= -16*pi*α.*Sr./γtrr

    # Convert back to regularized variables

    # ∂tΓreg = -(r/2).*∂tΓr
    # ∂tγtθθreg = (1 ./r.^2).*∂tγtθθ

    ∂tγtθθreg = (1 ./r.^2).*∂tγtθθ
    # ∂tArrreg = real((r .+ 0im).^(5/2)).*∂tArr
    # ∂tKreg = real((r .+ 0im).^(3/2)).*∂tK

    # Numerical Dissipation terms

    ∂4α = dissipation(α)
    ∂4A = dissipation(A)
    ∂4βr = dissipation(βr)
    ∂4Br = dissipation(Br)
    ∂4χ = dissipation(χ)
    ∂4γtrr = dissipation(γtrr)
    ∂4γtθθreg = dissipation(γtθθreg)
    ∂4Arr = dissipation(Arr)
    ∂4K = dissipation(K)
    ∂4Γr = dissipation(Γr)
    ∂4𝜙 = dissipation(𝜙)
    ∂4K𝜙 = dissipation(K𝜙)

    #sign = -1 seems the best
    sign = -1
    σ = 0.2

    # ∂tα .+= (1/(2^6))*sign*σ*(drt^5)*∂6α
    # ∂tβr .+= (1/(2^6))*sign*σ*(drt^5)*∂6βr
    # ∂tBr .+= (1/(2^6))*sign*σ*(drt^5)*∂6Br
    # ∂tχ .+= (1/(2^6))*sign*σ*(drt^5)*∂6χ
    # ∂tγtrr .+= (1/(2^6))*sign*σ*(drt^5)*∂6γtrr
    # ∂tγtθθ .+= (1/(2^6))*sign*σ*(drt^5)*∂6γtθθ
    # ∂tArr .+= (1/(2^6))*sign*σ*(drt^5)*∂6Arr
    # ∂tK .+= (1/(2^6))*sign*σ*(drt^5)*∂6K
    # ∂tΓreg .+= (1/(2^6))*sign*σ*(drt^5)*∂6Γreg

    ∂tα .+= (1/16)*sign*σ*∂4α
    ∂tA .+= (1/16)*sign*σ*∂4A
    ∂tβr .+= (1/16)*sign*σ*∂4βr
    ∂tBr .+= (1/16)*sign*σ*∂4Br
    ∂tχ .+= (1/16)*sign*σ*∂4χ
    ∂tγtrr .+= (1/16)*sign*σ*∂4γtrr
    ∂tγtθθreg .+= (1/16)*sign*σ*∂4γtθθreg
    ∂tArr .+= (1/16)*sign*σ*∂4Arr
    ∂tK .+= (1/16)*sign*σ*∂4K
    ∂tΓr .+= (1/16)*sign*σ*∂4Γr
    ∂t𝜙 .+= (1/16)*sign*σ*∂4𝜙
    ∂tK𝜙 .+= (1/16)*sign*σ*∂4K𝜙

    # Inner temporal boundary Conditions

    ∂tα[1:2] .= 0
    ∂tA[1:2] .= 0
    ∂tβr[1:2] .= 0
    ∂tBr[1:2] .= 0
    ∂tχ[1:2] .= 0
    ∂tγtrr[1:2] .= 0
    ∂tγtθθreg[1:2] .= 0
    ∂tArr[1:2] .= 0
    ∂tK[1:2] .= 0
    ∂tΓr[1:2] .= 0
    ∂t𝜙[1:2] .= 0
    ∂tK𝜙[1:2] .= 0

    # ∂tα[1] = ((-25*∂tα[1]+48*∂tα[2]-36*∂tα[3]+16*∂tα[4]-3*∂tα[5])/(12*drt))
    # ∂tA[1] = ((-25*∂tα[1]+48*∂tα[2]-36*∂tα[3]+16*∂tα[4]-3*∂tα[5])/(12*drt))
    # ∂tβr[1] = ((-25*∂tα[1]+48*∂tα[2]-36*∂tα[3]+16*∂tα[4]-3*∂tα[5])/(12*drt))
    # ∂tBr[1] = ((-25*∂tα[1]+48*∂tα[2]-36*∂tα[3]+16*∂tα[4]-3*∂tα[5])/(12*drt))
    # ∂tχ[1] = ((-25*∂tα[1]+48*∂tα[2]-36*∂tα[3]+16*∂tα[4]-3*∂tα[5])/(12*drt))
    # ∂tγtrr[1] = ((-25*∂tα[1]+48*∂tα[2]-36*∂tα[3]+16*∂tα[4]-3*∂tα[5])/(12*drt))
    # ∂tγtθθreg[1] = ((-25*∂tα[1]+48*∂tα[2]-36*∂tα[3]+16*∂tα[4]-3*∂tα[5])/(12*drt))
    # ∂tArr[1] = ((-25*∂tα[1]+48*∂tα[2]-36*∂tα[3]+16*∂tα[4]-3*∂tα[5])/(12*drt))
    # ∂tK[1] = ((-25*∂tα[1]+48*∂tα[2]-36*∂tα[3]+16*∂tα[4]-3*∂tα[5])/(12*drt))
    # ∂tΓr[1] = ((-25*∂tα[1]+48*∂tα[2]-36*∂tα[3]+16*∂tα[4]-3*∂tα[5])/(12*drt))
    # ∂t𝜙[1] = ((-25*∂tα[1]+48*∂tα[2]-36*∂tα[3]+16*∂tα[4]-3*∂tα[5])/(12*drt))
    # ∂tK𝜙[1] = ((-25*∂tα[1]+48*∂tα[2]-36*∂tα[3]+16*∂tα[4]-3*∂tα[5])/(12*drt))
    #
    #
    #
    # dvalues[1] = ((-25*f.values[1] + 48*f.values[2] - 36*f.values[3]
    #     +16*f.values[4]-3*f.values[5])/(12*dx))
    #
    # dvalues[2] = ((-3*f.values[1] - 10*f.values[2] + 18*f.values[3]
    #     - 6*f.values[4] + f.values[5])/(12*dx))

    # Outer temporal boundary conditions

    ∂tα[(n-1):n] .= 0.
    ∂tA[(n-1):n] .= 0.
    ∂tβr[(n-1):n] .= 0.
    ∂tBr[(n-1):n] .= 0.
    ∂tχ[(n-1):n] .= 0.
    ∂tγtrr[(n-1):n] .= 0.
    ∂tγtθθreg[(n-1):n] .= 0.
    ∂tArr[(n-1):n] .= 0.
    ∂tK[(n-1):n] .= 0.
    ∂tΓr[(n-1):n] .= 0.
    ∂t𝜙[(n-1):n] .= 0.
    ∂tK𝜙[(n-1):n] .= 0.

    # In case you want to freeze all GR variables

    # ∂tα[2:(n-1)] .= 0.
    # ∂tβr[2:(n-1)] .= 0.
    # ∂tBr[2:(n-1)] .= 0.
    # ∂tχ[2:(n-1)] .= 0.
    # ∂tγtrr[2:(n-1)] .= 0.
    # ∂tγtθθreg[2:(n-1)] .= 0.
    # ∂tArr[2:(n-1)] .= 0.
    # ∂tK[2:(n-1)] .= 0.
    # ∂tΓreg[2:(n-1)] .= 0.

    #Values at infinity
    # α0 = 1
    # βr0 = 0
    # Br0 = 0
    # χ0 = 1
    # grr0 = 1
    # gθθreg0 = 0
    # Arr0 = 0
    # K0 = 0
    # Γreg0 = 1
    #
    # ∂tα[(n-1):n] .= (α[(n-1):n] .- α0)./r[(n-1):n] - ∂α[(n-1):n] + hα./(r[(n-1):n].^w)
    # ∂tβr[(n-1):n] .= (βr[(n-1):n] .- βr0)./r[(n-1):n] - ∂βr[(n-1):n] + hβr./(r[(n-1):n].^w)
    # ∂tBr[(n-1):n] .= (Br[(n-1):n] .- Br0)./r[(n-1):n] - ∂Br[(n-1):n] + hBr./(r[(n-1):n].^w)
    # ∂tχ[(n-1):n] .= (χ[(n-1):n] .- χ0)./r[(n-1):n] - ∂χ[(n-1):n] + hχ./(r[(n-1):n].^w)
    # ∂tgrr[(n-1):n] .= (grr[(n-1):n] .- grr0)./r[(n-1):n] - ∂grr[(n-1):n] + hgrr./(r[(n-1):n].^w)
    # ∂tgθθreg[(n-1):n] .= (gθθreg[(n-1):n] .- gθθreg0)./r[(n-1):n] - ∂gθθreg[(n-1):n] + hgθθreg./(r[(n-1):n].^w)
    # ∂tArr[(n-1):n] .= (Arr[(n-1):n] .- Arr0)./r[(n-1):n] - ∂Arr[(n-1):n] + hArr./(r[(n-1):n].^w)
    # ∂tK[(n-1):n] .= (K[(n-1):n] .- K0)./r[(n-1):n] - ∂K[(n-1):n] + hK./(r[(n-1):n].^w)
    # ∂tΓreg[(n-1):n] .= (Γreg[(n-1):n] .- Γreg0)./r[(n-1):n] - ∂Γreg[(n-1):n] + hΓreg./(r[(n-1):n].^w)

    return GBSSN_Variables(∂tα,∂tA,∂tβr,∂tBr,∂tχ,∂tγtrr,∂tγtθθreg,∂tArr,∂tK,∂tΓr,∂t𝜙,∂tK𝜙)

end

function constraints(T,state::GBSSN_Variables,param)

    ############################################
    # Caculates the constraints of the system
    #
    # These are outputed at every saved time step.
    # They should limit to zero as the spatial
    # resolution is increased. They are important to
    # monitor to make sure the physics of the system
    # is being properly modeled.
    ############################################

    # Unpack Variables

    α = state.α
    βr = state.βr
    χ = state.χ
    γtrr = state.γtrr
    γtθθreg = state.γtθθ
    Arr = state.Arr
    K = state.K
    Γr = state.Γr
    𝜙 = state.𝜙
    K𝜙 = state.K𝜙

    m = param[7]

    # Gauge conditions

    v = param[3]

    # if v == 1 # Lagrangian Condition
    #     γtθθreg[2] = ((-315*γtθθreg[3] + 210*γtθθreg[4] - 126*γtθθreg[5]
    #     + 45*γtθθreg[6] - 7*γtθθreg[7])/63)
    # end

    # Spatial Derivatives

    order = 4

    # First derivatives
    ∂rtχ = deriv(χ,order,1)
    ∂rtγtrr = deriv(γtrr,order,1)
    ∂rtγtθθreg = deriv(γtθθreg,order,1)
    ∂rtArr = deriv(Arr,order,1)
    ∂rtK = deriv(K,order,1)
    ∂rt𝜙 = deriv(𝜙,order,1)

    # Second derivatives
    ∂2rtχ = deriv2(χ,order,1)
    ∂2rtγtθθreg = deriv2(γtθθreg,order,1)

    # Coordinate transformations from computational rt coordinate
    # to physical r coordinate

    r = sample(Float64, χ.grid, param[4])
    drdrt = sample(Float64, χ.grid, param[5])
    d2rdrt = sample(Float64, χ.grid, param[6])

    ∂χ = ∂rtχ./drdrt
    ∂γtrr = ∂rtγtrr./drdrt
    ∂γtθθreg = ∂rtγtθθreg./drdrt
    ∂Arr = ∂rtArr./drdrt
    ∂K = ∂rtK./drdrt
    ∂𝜙 = ∂rt𝜙./drdrt

    ∂2χ = (∂2rtχ - d2rdrt.*∂χ)./(drdrt.^2)
    ∂2γtθθreg = (∂2rtγtθθreg - d2rdrt.*∂γtθθreg)./(drdrt.^2)

    #∂t𝜙 = βr.*∂𝜙 - 2*α.*K𝜙

    # Conversions from regularized variables to canonical variables

    γtθθ = (r.^2).*(γtθθreg .+ 1)
    ∂γtθθ = (r.^2).*∂γtθθreg + (2*r).*(γtθθreg .+ 1)
    ∂2γtθθ = (r.^2).*∂2γtθθreg + (4*r).*∂γtθθreg + 2*(γtθθreg .+ 1)

    # K = real((r .+ 0im).^(-3/2)).*Kreg
    # ∂K = real((r .+ 0im).^(-3/2)).*∂Kreg - (3/2)*real((r .+ 0im).^(-5/2)).*Kreg
    #
    # Arr = real((r .+ 0im).^(-5/2)).*Arrreg
    # ∂Arr = real((r .+ 0im).^(-5/2)).*∂Arrreg - (5/2)*real((r .+ 0im).^(-7/2)).*Arrreg

    # Γr = -(2 ./r).*Γreg

    # Inverse metric (contravariant indices)

    # gtt = -(1 ./α.^2)
    # gtr = βr./α.^2
    # grr = χ./γtrr - (βr./α).^2
    # gθθ = χ./γtθθ
    #
    # # Lagrangian Density for scalar field
    #
    # 𝓛 = (1/2)*gtt.*∂t𝜙.^2 + (1/2)*grr.*∂𝜙.^2 + gtr.*∂𝜙.*∂t𝜙 - (1/2)*(m^2)*𝜙.^2
    #
    # # Stresss Energy components (contravariant indices)
    #
    # Ttt = (gtt.*∂t𝜙 + gtr.*∂𝜙).^2 - gtt.*𝓛
    # Ttr = (gtt.*∂t𝜙 + gtr.*∂𝜙).*(gtr.*∂t𝜙 + grr.*∂𝜙) - gtr.*𝓛
    #
    # # Source Terms to the constraints
    #
    # ρ = (α.^2).*Ttt
    # Sr = α.*Ttr

    ρ = 2*K𝜙.^2 + (1/2)*(χ./γtrr).*∂𝜙.^2 + (1/2)*m^2*𝜙.^2

    Sr = 2*K𝜙.*∂𝜙

    # Constraint Equations

    𝓗 = (-(3/2)*(Arr./γtrr).^2 + (2/3)*K.^2 - (5/2)*((∂χ.^2)./χ)./γtrr
     + 2*∂2χ./γtrr + 2*χ./γtθθ - 2*χ.*(∂2γtθθ./γtθθ)./γtrr + 2*∂χ.*(∂γtθθ./γtθθ)./γtrr
     + χ.*(∂γtrr./(γtrr.^2)).*(∂γtθθ./γtθθ) - ∂χ.*∂γtrr./(γtrr.^2)
     + (1/2)*χ.*((∂γtθθ./γtθθ).^2)./γtrr - 16*pi*ρ)

    𝓜r = (∂Arr./γtrr - (2/3)*∂K - (3/2)*Arr.*(∂χ./χ)./γtrr
     + (3/2)*Arr.*(∂γtθθ./γtθθ)./γtrr - Arr.*∂γtrr./(γtrr.^2)
     - 8*pi*Sr)

    𝓖r = -(1/2)*∂γtrr./(γtrr.^2) + Γr + (∂γtθθ./γtθθ)./γtrr

    𝓗[1:2] .= 0.
    𝓜r[1:2] .= 0.
    𝓖r[1:2] .= 0.

    return (𝓗, 𝓜r, 𝓖r, ρ)

end

function horizon(T,state::GBSSN_Variables,param)

    ############################################
    # Caculates the apparent horizon
    #
    # Where the function crosses zero is the
    # apparent horizon of the black hole.
    ############################################

    v = param[3]

    # Unpack Variables

    χ = state.χ
    γtrr = state.γtrr
    γtθθreg = state.γtθθ
    Arr = state.Arr
    K = state.K

    # Gauge condition

    # if v == 1 # Lagrangian Condition
    #     γtθθreg[2] = ((-315*γtθθreg[3] + 210*γtθθreg[4] - 126*γtθθreg[5]
    #     + 45*γtθθreg[6] - 7*γtθθreg[7])/63)
    # end

    r = sample(T, χ.grid, param[4])
    drdrt = sample(T, χ.grid, param[5])

    # Conversions from regularized variables to canonical variables

    γtθθ = (r.^2).*(γtθθreg .+ 1)

    # K = real((r .+ 0im).^(-3/2)).*Kreg
    #
    # Arr = real((r .+ 0im).^(-5/2)).*Arrreg

    # Intermediate calculations

    Kθθ = ((1/3)*γtθθ.*K - (1/2)*Arr.*γtθθ./γtrr)./χ

    grr =  γtrr./χ

    gθθ =  γtθθ./χ

    # Spatial Derivatives

    ∂rtgθθ = deriv(gθθ,4,1)

    # Coordinate transformations from computational rt coordinate
    # to physical r coordinate

    ∂gθθ = ∂rtgθθ./drdrt

    # Apparent horizon function

    Θ = (∂gθθ./gθθ)./real((grr .+ 0im).^(1/2)) - 2*Kθθ./gθθ

    # cross = GridFun(χ.grid, sign.(Θ))

    return Θ

end

# function crossings(Θ::GridFun)
#
#     GridFun(χ.grid, sign.(Θ))
#
#     for
#
# end

function custom_progress_message(dt,state,param,t)

    ###############################################
    # Outputs status numbers while the program runs
    ###############################################

    if param[1]==param[2]
        println("")
        println("| # | Time Step | Time | max α'(t) | max χ'(t) | max γtrr'(t) | max γtθθ'(t) | max Arr'(t) | max K'(t) | max Γr'(t) |")
        println("|___|___________|______|___________|___________|______________|______________|_____________|___________|____________|")
        println("")
    end

    derivstate = rhs(state,param,t)

    println("  ",
    rpad(string(param[1]),6," "),
    rpad(string(round(dt,digits=3)),10," "),
    rpad(string(round(t,digits=3)),10," "),
    rpad(string(round(maximum(abs.(derivstate.α)),digits=3)),12," "),
    rpad(string(round(maximum(abs.(derivstate.χ)),digits=3)),12," "),
    rpad(string(round(maximum(abs.(derivstate.γtrr)),digits=3)),14," "),
    rpad(string(round(maximum(abs.(derivstate.γtθθ)),digits=3)),14," "),
    rpad(string(round(maximum(abs.(derivstate.Arr)),digits=3)),12," "),
    # rpad(string(round(maximum(abs.(derivstate.𝜙)),digits=3)),12," "),
    # rpad(string(round(maximum(abs.(derivstate.K𝜙)),digits=3)),14," ")
    rpad(string(round(maximum(abs.(derivstate.K)),digits=3)),12," "),
    rpad(string(round(maximum(abs.(derivstate.Γr)),digits=3)),14," ")
    )

    #PrettyTables.jl

    param[1] += param[2]

end

function solution_saver(T,grid,sol,param,folder)

    ###############################################
    # Saves all of the variables in nice CSV files
    # in the choosen data folder directory
    ###############################################

    vars = (["α","A","βr","Br","χ","γtrr","γtθθ","Arr","K","Γr","𝜙","K𝜙",
    "∂tα","∂tA","∂tβr","∂tBr","∂tχ","∂tγtrr","∂tγtθθ","∂tArr","∂tK","∂tΓr","∂t𝜙","∂tK𝜙",
    "H","Mr","Gr","ρ","appHorizon"])
    varlen = length(vars)
    #mkdir(string("data\\",folder))
    tlen = size(sol)[3]
    rlen = grid.ncells + 4
    loc = sample(T, grid, param[4])
    #loc[1] =
    cons = Array{GridFun,2}(undef,tlen,4)
    derivs = Array{GBSSN_Variables,1}(undef,tlen)
    apphorizon = Array{GridFun,1}(undef,tlen)

    for i in 1:tlen
        derivs[i] = rhs(sol[i],param,0)
        cons[i,1:4] .= constraints(T,sol[i],param)
        apphorizon[i] = horizon(T,sol[i],param)
    end

    array = Array{T,2}(undef,tlen+1,rlen+1)

    array[1,1] = 0
    array[1,2:end] .= loc

    for j = 1:varlen
        if j < 13
            for i = 2:tlen+1
                array[i,1] = sol.t[i-1]
                array[i,2:end] .= sol[:,j,i-1]
            end
        elseif j < 25
            for i = 2:tlen+1
                array[i,1] = sol.t[i-1]
                array[i,2:end] .= derivs[i-1][:,j-12]
            end
        elseif j < 29
            for i = 2:tlen+1
                array[i,1] = sol.t[i-1]
                array[i,2:end] .= cons[i-1,j-24]
            end
        elseif j == varlen
            for i = 2:tlen+1
                array[i,1] = sol.t[i-1]
                array[i,2:end] .= apphorizon[i-1]
            end
        end

        CSV.write(
        string("data/",folder,"/",vars[j],".csv"),
        DataFrame(array, :auto),
        header=false
        )

    end

end

function main(points)

    ###############################################
    # Main Program
    #
    # Calls each of the above functions to run a
    # simulation. Sets up the numerical grid,
    # sets the gauge conditions, sets up a new
    # computational rt coordinate that makes the
    # physical r coordinate step larger near the
    # outer boundary, sets the initial conditions,
    # and finally runs the numerical DiffEq
    # package to run the time integration.
    #
    # All data is saved in the folder specified to
    # the solution_saver, each in their own CSV
    # file.
    ###############################################

    T = Float64
    rspan = T[1,210]
    rtspan = T[1,21]

    grid = setup(T, rtspan, points)
    drt = spacing(grid)
    dt = drt/4

    tspan = T[0,25]
    v = 1

    m = 0

    f(b) = b*tan(rtspan[2]/b)-rspan[2]

    scale = find_zero(f, 0.64*rtspan[2])

    r(rt) = scale*tan(rt/scale)
    drdrt(rt) = sec(rt/scale)^2
    d2rdrt(rt) = (2/scale)*(sec(rt/scale)^2)*tan(rt/scale)

    # r(rt) = rt
    # drdrt(rt) = 1
    # d2rdrt(rt) = 0

    atol = eps(T)^(T(3) / 4)

    #alg = KuttaPRK2p5()
    alg = RK4()

    #printlogo()

    printtimes = 1
    custom_progress_step = round(Int, printtimes/dt)
    step_iterator = custom_progress_step
    param = [step_iterator, custom_progress_step, v, r, drdrt, d2rdrt, m, rtspan]
    println("Defining Initial State...")
    state = init(T, grid, param)::GBSSN_Variables
    println("Defining Problem...")
    prob = ODEProblem(rhs, state, tspan, param)
    println("Starting Solution...")

    #init(T, grid, param)

    sol = solve(
        prob, alg,
        abstol = atol,
        dt = drt/4,
        adaptive = false,
        saveat = 1,
        progress = true,
        progress_steps=custom_progress_step,
        progress_message=custom_progress_message
    )

    solution_saver(T,grid,sol,param,"ScalarTests_old")

end

end


################################################################################

"""
Keep track of scores & indexes

# Fields
    score::Float64: score
    index::Int64: index

See also: [`HMM`](@ref), [`HMMParams`](@ref)
"""
struct ScorePair
  score::Float64
  index::Int64
end

"""
Hidden Markov model object

# Fields
    dataM::Array: hold data
    tbM::Array: collect traceback

See also: [`HMMParams`](@ref), [`setup!`](@ref),
"""
mutable struct HMM
  dataM::Array
  tbM::Array
end

"""
Hidden Markov model parameters

# Fields
    pen::Float64: penalty for hidden Markov model
    distance::Function: function used to calculate distance

See also: [`HMM`](@ref), [`setup!`](@ref),
"""
@with_kw struct HMMParams
  pen::Float64                 = 200.      # penalty
  distance::Function           = distance  # distance function
end

################################################################################


@mlj_model mutable struct APIx0 <: Supervised
    f0::Int
end
@mlj_model mutable struct APIx0b <: Supervised
    f0::Int
end

mutable struct APIx1 <: Static end

@testset "fit-x" begin
    m0 = APIx0(f0=1)
    m1 = APIx0b(f0=3)
    # no weight support: fallback
    M.fit(m::APIx0, v::Int, X, y) = (5, nothing, nothing)
    @test fit(m0, 1, randn(2), randn(2), 5) == (5, nothing, nothing)
    # with weight support: use
    M.fit(m::APIx0b, v::Int, X, y, w) = (7, nothing, nothing)
    @test fit(m1, 1, randn(2), randn(2), 5) == (7, nothing, nothing)
    # default fitted params
    @test M.fitted_params(m1, 7) == (fitresult=7,)
    # static
    s1 = APIx1()
    @test fit(s1, 1, 0) == (nothing, nothing, nothing)

    #update fallback = fit
    @test update(m0, 1, 5, nothing, randn(2), 5) == (5, nothing, nothing)
end


mutable struct Gradients{K,V} <: AbstractDict{K,V}
    parent::Gradients{K,V}
    key::K
    val::V

    Gradients{K,V}() where {K,V} = new()
    Gradients(key::K, val::V) where {K,V} = new{K,V}(Gradients{K,V}(), key, val)
    function Gradients(parent::Gradients{K,V}, key::K, val::V) where {K,V}
        return new{K,V}(parent, key, val)
    end
end

function Gradients(parent::Gradients{K1,V1}, key::K2, val::V2) where {K1,V1,K2,V2}
    K = promote_type(K1, K2)
    V = promote_type(V1, V2)
    return Gradients(convert(Gradients{K,V}, parent), convert(K, key), convert(V, val))
end

Base.length(t::Gradients) = count(x->true, t)

function Base.iterate(grads::Gradients{K,V}, t=grads) where {K,V}
    isdefined(t, :parent) || return nothing
    return Pair{K,V}(t.key, t.val), t.parent
end

similar(::Gradients{K,V}) where {K,V} = Gradients{K,V}()

function Base.map(f, g::Gradients; keyfunc=identity)
    if isdefined(g, :parent)
        return Gradients(map(f, g.parent; keyfunc=keyfunc), keyfunc(g.key), f(g.val))
    else
        return g
    end
end

function mul(c, g::Gradients)
    return map(dg -> c .* dg, g)
end

function Base.convert(::Type{Gradients{K,V}}, g::Gradients{_K,_V}) where {K,V,_K,_V}
    @info "`convert` is called!"
    if !isdefined(g, :parent)
        return Gradients{K,V}()
    end
    return map(val -> convert(V, val), g; keyfunc = key -> convert(K, key))
end

function Base.convert(::Type{Gradients{K,V}}, g::Gradients{K,V}) where {K,V}
    return g
end

function Base.promote_rule(::Type{Gradients{K1,V1}}, ::Type{Gradients{K2,V2}}) where {K1,V1,K2,V2}
    return Gradients{promote_type(K1, K2),promote_type(V1, V2)}
end

function linear_combine(∂f_∂g1, ∂f_∂g2, g1::Gradients, g2::Gradients)
    if !isdefined(g2, :parent)
        return mul(∂f_∂g1, g1)
    end
    return _linear_combine!(∂f_∂g1, g1, mul(∂f_∂g2, g2))
end

#modifies g2
function _linear_combine!(∂f_∂g1::T, g1::G, g2::G) where {T,U,G<:Gradients{<:CovariantSystem{U},<:AbstractVector{T}}}
    if !isdefined(g2, :parent)
        return mul(∂f_∂g1, g1)
    end
    if isdefined(g1, :parent)
        if g2.key === g1.key
            return Gradients(_linear_combine!(∂f_∂g1, g1.parent, g2.parent),
                             g1.key, ∂f_∂g1 .* g1.val .+ g2.val)
        end
        g = g2
        while isdefined(g.parent, :parent)
            if g.parent.key === g1.key
                newval = ∂f_∂g1 .* g1.val .+ g.parent.val
                g.parent = g.parent.parent
                return Gradients(_linear_combine!(∂f_∂g1, g1.parent, g2),
                                 g1.key, newval)
            end
            g = g.parent
        end
        return Gradients(_linear_combine!(∂f_∂g1, g1.parent, g2),
                             g1.key, ∂f_∂g1 .* g1.val)

    else
        return g2
    end
end

function _linear_combine!(∂f_∂g1::T0, g1::G1, g2::G2) where
    {T0,T1,U1,G1<:Gradients{<:CovariantSystem{U1},<:AbstractVector{T1}},
     T2,U2,G2<:Gradients{<:CovariantSystem{U2},<:AbstractVector{T2}}}

    T = promote_type(T0, T1, T2)
    return _linear_combine!(convert(T, ∂f_∂g1), promote(g1, g2)...)
end


# using Revise
using ClusterManagers
using Distributed

worker_ids = if "SLURM_JOBID" in keys(ENV)
    wids = ClusterManagers.addprocs_slurm(parse(Int, ENV["SLURM_NTASKS"]))
    wids
else
    addprocs(4)
end

Distributed.@everywhere begin
    using Pkg
    Pkg.activate(".")
    Pkg.instantiate()
end

Distributed.@everywhere begin
    using POMDPs
    using BasicPOMCP
    using POMCPOW
    using VOOTreeSearch
    using ParticleFilters
    using CPUTime
    using DataFrames
    using ARDESPOT
    using Distributions
    using VDPTag2
    using ProgressMeter
    using Dates
    using CSV
    using POMDPSimulators
    using Random
    using Printf
    using LinearAlgebra
    using VDPTag2Experiments
    using LinearAlgebra: diagm, PosDefException, det, eigvals, eigvecs
    using Random: MersenneTwister, seed!
    using Distances: euclidean

    using MCTS
    using StaticArrays
    using POMDPModelTools: GenerativeBeliefMDP
    include("./scripts/VDPTag2/vdp_next_action.jl")
end

@info "Started $(Distributed.nworkers()) workers..."
@info "Precompiling simulation code..."

max_depth = 10
solver_list = Dict("POMCPOW"=>[110.0, 30.0, 30.0, 5.0, 100.0],
                    "VOMCPOW"=>[85.0, 30.0, 30.0, 2.5, 100.0, 0.7])

on_cluster = true
save_data = true
parallelize = true

if on_cluster
    max_time_list = 10.0 .^ (-2:0.25:0)
    max_query = 100_000
    n_iters = 1000
else
    # max_time_list = 10.0 .^ (-2:0.5:0)
    max_time_list = [0.1]
    max_query = 100_000
    n_iters = 50
end

# VDPTag2
pomdp = VDPTagPOMDP(mdp=VDPTagMDP(barriers=CardinalBarriers(0.2, 2.8)))

println(stderr, "Max times: ", max_time_list)
println(stderr, "Max depth: ", max_depth)
println(stderr, "Max query: ", max_query)
println(stderr, "Number of iterations: ", n_iters)
println(stderr, "Solvers: ", solver_list)
println(stderr, "POMDP: ", pomdp)

Distributed.@everywhere begin
    struct SimWrap
        x::Vector{Float64}
        solver_name::String
        i::Int64
        max_time::Float64
        max_depth::Int64
        max_query::Int64
        pomdp::POMDP
    end

    function gen_sim(sim_wrap::SimWrap)
        x = sim_wrap.x
        solver_name = sim_wrap.solver_name
        i = sim_wrap.i
        max_time = sim_wrap.max_time
        max_depth = sim_wrap.max_depth
        max_query = sim_wrap.max_query
        pomdp = sim_wrap.pomdp

        c = x[1]
        k_act = x[2]
        inv_alpha_act = x[3]
        k_obs = x[4]
        inv_alpha_obs = x[5]

        rng = MersenneTwister(7)
        rollout_policy = ToNextMLSolver(rng)

        if solver_name == "POMCPOW"
            solver = POMCPOWSolver(tree_queries=max_query,
                                   criterion=MaxUCB(c),
                                   max_depth=max_depth,
                                   max_time=max_time,
                                   k_action=k_act,
                                   alpha_action=1/inv_alpha_act,
                                   k_observation=k_obs,
                                   alpha_observation=1/inv_alpha_obs,
                                   estimate_value=FORollout(rollout_policy),
                                   next_action=RootToNextMLFirst(rng),
                                   default_action=BasicPOMCP.ReportWhenUsed(TagAction(false, 0.0)),
                                   rng=rng
                                  )

            planner = solve(solver, pomdp)
            filter = SIRParticleFilter(deepcopy(pomdp),
                                       100_000,
                                       rng=MersenneTwister(i+90_000))

            seed!(planner, i+40000)
            sim = Sim(deepcopy(pomdp),
                      planner,
                      filter,
                      rng=MersenneTwister(i+50_000),
                      max_steps=100,
                      metadata=Dict(:i=>i, :solver=>solver_name)
                     )

        elseif solver_name == "VOMCPOW"
            p_voo = x[6]
            sig = diagm([0.1])
            epsilon = 0.1/10.0
            voo_opt = VOOActionGenerator(p_voo, Distributions.Uniform(), MvNormal(sig), rng, epsilon, true, 20)

            solver = POMCPOWSolver(tree_queries=max_query,
                                   criterion=MaxUCB(c),
                                   max_depth=max_depth,
                                   max_time=max_time,
                                   k_action=k_act,
                                   alpha_action=1/inv_alpha_act,
                                   k_observation=k_obs,
                                   alpha_observation=1/inv_alpha_obs,
                                   estimate_value=FORollout(rollout_policy),
                                   next_action = RootToNextMLFirstVOO(voo_opt),
                                   default_action=BasicPOMCP.ReportWhenUsed(TagAction(false, 0.0)),
                                   rng=rng
                                  )

            planner = solve(solver, pomdp)
            filter = SIRParticleFilter(deepcopy(pomdp),
                                       100_000,
                                       rng=MersenneTwister(i+90_000))

            seed!(planner, i+40000)
            sim = Sim(deepcopy(pomdp),
                      planner,
                      filter,
                      rng=MersenneTwister(i+50_000),
                      max_steps=100,
                      metadata=Dict(:i=>i, :solver=>solver_name)
                     )

        end

        result = POMDPs.simulate(sim)
        output = (reward=discounted_reward(result),)
        return merge(sim.metadata, output)
    end
end

datestring = Dates.format(now(), "e_d_u_Y_HH_MM")
results_csv = DataFrame(max_time=Float64[], solver=String[], mean_reward=Float64[], se_reward=Float64[])

try
    for iter_time in max_time_list
        sims = SimWrap[]
        for solver_name in keys(solver_list)
            for j in 1:n_iters
                sim_wrap = SimWrap(solver_list[solver_name], solver_name, j, iter_time, max_depth, max_query, deepcopy(pomdp))
                push!(sims, sim_wrap)
            end
        end
        if parallelize
            results = @showprogress pmap(gen_sim, sims)
        else
            results = @showprogress map(gen_sim, sims)
        end
        println(stderr, "Max time: $iter_time :")
        results = POMDPSimulators.create_dataframe(results)
        println(stderr, results)
        combined = by(results, :solver) do df
            DataFrame(mean_reward=mean(df[:reward]), se_reward=(std(df[:reward])/sqrt(n_iters)))
        end
        for row in eachrow(combined)
            result_row = [iter_time, row.solver, row.mean_reward, row.se_reward]
            println(stderr, @sprintf("- %s reward: %6.3f ± %6.3f", row.solver, row.mean_reward, row.se_reward))
            push!(results_csv, result_row)
        end
        if save_data
            fname_csv = joinpath(@__DIR__, "../data", "VDP_compare_results"*"_"*datestring*".csv")
            CSV.write(fname_csv, results_csv)
            println(stderr,"Saving parameters to ", fname_csv)
        end
    end
finally
    Distributed.rmprocs(worker_ids)
end


__precompile__()

################################################################################
#
#     Hecke.jl : Hecke main file
#
# This file is part of Hecke.
#
# Copyright (c) 2015: Claus Fieker, Tommy Hofmann
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright notice, this
#   list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# (C) 2015, 2016 Claus Fieker, Tommy Hofmann
#
################################################################################

module Hecke

using Nemo

################################################################################
#
#  Load FPlll if available
#
################################################################################

if isdir(joinpath(Pkg.dir(),"FPlll"))
  using FPlll
end

################################################################################
#
#  Make Hecke compatible for julia 0.4
#
################################################################################

include("compat.jl")

################################################################################
#
#  Import/export
#
################################################################################

import Nemo: nf_elem, PariIdeal, AnticNumberField, degree,
             den, num, lg, prime_decomposition, parent, length,
             norm, real, imag, inv, rows, getindex!, lll, hnf, cols, basis,
             trace, factor, mod, zero, pari_load, PariPolyRing,
             PariRationalField, PariQQ, pari_vec, hash, PolynomialRing, coeff,
             var, abs, min, iszero, one, sqrt, isone, deepcopy, rank, in,
             discriminant, log, sub, lift, FlintQQ, FlintZZ, elem_type,
             elem_from_mat_row, elem_to_mat_row!, order, signature,
             base_ring, compose, root, arf_struct, fmpq, valuation,
             Ring, prec, conj, mul!, gen, divexact, derivative, zero!, divrem,
             resultant, evaluate, setcoeff!, div, isodd, iseven, max, floor,
             ceil, //, setindex!, transpose, colon, nf_elem, isreal,
             MatrixSpace, contains, overlaps, solve, unique_integer, gcd,
             minpoly, charpoly, det,
             howell_form, needs_parentheses, is_negative, parent_type,
             intersect, lcm, strong_echelon_form, strong_echelon_form!,
             howell_form!, add!, mul!, fmpq_poly, FmpzPolyRing,
             FlintFiniteField, addeq!, acb_vec, array, acb_struct,
             acb_vec_clear, lufact!

export AnticNumberField, hash, update, nf, next_prime, dot, maximal_order

import Base: show, minimum, rand, prod, copy, rand!, call, rand, ceil, round,
             size, dot, in, powermod, ^, getindex, ==, <, >, +, *, /, \, -, !=
             getindex, setindex!, transpose, getindex, //, colon, exp, div,
             floor, max, BigFloat, promote_rule, precision, dot
             first, StepRange, show, one, zero, inv, iseven, isodd

# To make all exported Nemo functions visible to someone using "using Hecke"
# we have to export everything again

for i in names(Nemo)
  eval(Expr(:export, i))
end

export @vprint, @hassert, @vtime, add_verbose_scope, get_verbose_level,
       set_verbose_level, add_assert_scope, get_assert_level, set_assert_level,
       update, @timeit, show, StepRange, domain, codomain, image, preimage,
       modord, resultant, @test_and_infer

###############################################################################
#
#   Library initialisation
#
###############################################################################

const pkgdir = realpath(joinpath(dirname(@__FILE__), ".."))
const libhecke = joinpath(pkgdir, "local", "lib", "libhecke")
const libdir = joinpath(pkgdir, "local", "lib")

function __init__()

  on_windows = @windows ? true : false
  on_linux = @linux ? true : false

  println("")
  print("Welcome to \n")
  print_with_color(:red, "
  _    _           _
 | |  | |         | |
 | |__| | ___  ___| | _____
 |  __  |/ _ \\\/ __| |/ / _ \\
 | |  | |  __/ (__|   <  __/
 |_|  |_|\\___|\\___|_|\\_\\___|
  ")

  println()
  print("Version")
  print_with_color(:green, " $VERSION_NUMBER ")
  print("... \n ... which comes with absolutely no warrant whatsoever")
  println()
  println("(c) 2015, 2016 by Claus Fieker and Tommy Hofmann")
  println()

  if "HOSTNAME" in keys(ENV) && ENV["HOSTNAME"] == "juliabox"
    push!(Libdl.DL_LOAD_PATH, "/usr/local/lib")
  elseif on_linux
    push!(Libdl.DL_LOAD_PATH, libdir)
    Libdl.dlopen(libhecke)
  else
    push!(Libdl.DL_LOAD_PATH, libdir)
	ENV["PATH"] = ENV["PATH"] * ";" * joinpath(Pkg.dir("Nemo"), "local", "lib")
  end

  t = create_accessors(AnticNumberField, acb_root_ctx, get_handle())
  global _get_nf_conjugate_data_arb = t[1]
  global _set_nf_conjugate_data_arb = t[2]

  t = create_accessors(AnticNumberField,
                       Tuple{Array{nf_elem, 1}, nf_elem},
                       get_handle())
  global _get_nf_torsion_units = t[1]
  global _set_nf_torsion_units = t[2]

  t = create_accessors(AnticNumberField, NfMaxOrd, get_handle())

  global _get_maximal_order_of_nf = t[1]
  global _set_maximal_order_of_nf = t[2]

  t = create_accessors(NfMaxOrd, ClassGrpCtx, get_handle())

  global _get_ClassGrpCtx_of_order = t[1]
  global _set_ClassGrpCtx_of_order = t[2]

  global R = _RealRing()
end

function conjugate_data_arb(K::AnticNumberField)
  try
    c = _get_nf_conjugate_data_arb(K)::acb_root_ctx
    return c
  catch
    c = acb_root_ctx(K.pol)
    _set_nf_conjugate_data_arb(K, c)
    return c
  end
end

function _torsion_units(K::AnticNumberField)
  try
    c = _get_nf_torsion_units(K)::Tuple{Array{nf_elem, 1}, nf_elem}
    return c
  catch
    O = maximal_order(K)
    tor, gen = _torsion_units(O)
    tornf = [ elem_in_nf(x) for x in tor]
    gennf = elem_in_nf(gen)
    _set_nf_torsion_units(K, (tornf, gennf))
    return tornf, gennf
  end
end

function torsion_units(K::AnticNumberField)
  ar, g = _torsion_units(K)
  return ar
end

function torsion_units_gen(K::AnticNumberField)
  ar, g = _torsion_units(K)
  return g
end

################################################################################
#
#  Version number
#
################################################################################

global VERSION_NUMBER = v"0.1.3"

################################################################################
#
#  Verbose printing
#
################################################################################

global hecke = Hecke

global VERBOSE_SCOPE = Symbol[]

global VERBOSE_LOOKUP = Dict{Symbol, Int}()

global VERBOSE_PRINT_INDENT = [ 0 ]

function add_verbose_scope(s::Symbol)
  !(s in VERBOSE_SCOPE) && push!(VERBOSE_SCOPE, s)
  nothing
end

function pushindent()
  a = VERBOSE_PRINT_INDENT[1]
  VERBOSE_PRINT_INDENT[1] = a + 1
  nothing
end

function popindent()
  a = VERBOSE_PRINT_INDENT[1]
  VERBOSE_PRINT_INDENT[1] = a - 1
  nothing
end

function _global_indent()
  s = "  "^VERBOSE_PRINT_INDENT[1]
  return s
end

macro vprint(args...)
  if length(args) == 2
    quote
      if get_verbose_level($(args[1])) >= 1
        print(_global_indent())
        print($(args[2]))
      end
    end
  elseif length(args) == 3
    quote
      if get_verbose_level($(args[1])) >= $(args[2])
        print(_global_indent())
        print($(args[3]))
      end
    end
  end
end

macro v_do(args...)
  if length(args) == 2
    quote
      if get_verbose_level($(args[1])) >= 1
       $(args[2])
      end
    end
  elseif length(args) == 3
    quote
      if get_verbose_level($(args[1])) >= $(args[2])
        $(args[3])
      end
    end
  end
end

function set_verbose_level(s::Symbol, l::Int)
  !(s in VERBOSE_SCOPE) && error("Not a valid symbol")
  VERBOSE_LOOKUP[s] = l
end

function get_verbose_level(s::Symbol)
  !(s in VERBOSE_SCOPE) && error("Not a valid symbol")
  return get(VERBOSE_LOOKUP, s, 0)::Int
end

################################################################################
#
#  Assertions
#
################################################################################

global ASSERT_SCOPE = Symbol[]

global ASSERT_LOOKUP = Dict{Symbol, Int}()

function add_assert_scope(s::Symbol)
  !(s in ASSERT_SCOPE) && push!(ASSERT_SCOPE, s)
  nothing
end

function set_assert_level(s::Symbol, l::Int)
  !(s in ASSERT_SCOPE) && error("Not a valid symbol")
  ASSERT_LOOKUP[s] = l
end

function get_assert_level(s::Symbol)
  !(s in ASSERT_SCOPE) && error("Not a valid symbol")
  return get(ASSERT_LOOKUP, s, 0)::Int
end

macro hassert(args...)
  if length(args) == 2
    quote
      if get_assert_level($(args[1])) >= 1
        @assert $(args[2])
      end
    end
  elseif length(args) == 3
    quote
      if get_assert_level($(args[1])) >= $(args[2])
        @assert $(args[3])
      end
    end
  end
end

macro test_and_infer(f,args,res)
  quote
    if isa($(esc(args)), Tuple)
      Base.Test.@inferred $(esc(f))($(esc(args))...)
      Base.Test.@test $(esc(f))($(esc(args))...) == $(esc(res))
    else
      Base.Test.@inferred $(esc(f))($(esc(args)))
      local t = $(esc(res))
      Base.Test.@test $(esc(f))($(esc(args))) == t
    end
  end
end

################################################################################
#
#  Verbose time printing
#
################################################################################

macro vtime(args...)
  if length(args) == 2
    msg = string(args[2])
    quote
      if get_verbose_level($(args[1])) >= 1
        local t0 = time_ns()
        local val = $(esc(args[2]))
        println((time_ns()-t0)/1e9, " @ ", $msg)
        val
      else
        local val2 = $(esc(args[2]))
        val2
      end
    end
  elseif length(args) == 3
    msg = string(args[3])
    quote
      if get_verbose_level($(args[1])) >= $(args[2])
        local t0 = time_ns()
        local val = $(esc(args[3]))
        println((time_ns()-t0)/1e9, " @ ", $msg)
        val
      else
        local val2 = $(esc(args[3]))
        val2
      end
    end
  end
end

################################################################################
#
#  Functions for timings
#
################################################################################

macro timeit(args...)
  loops = 50
  if length(args) == 2
    loops = args[2]
  end

  quote
    gc_enable(false)
    # warm-up
    local val = $(esc(args[1]))

    local min = Inf
    local max = 0
    local sum = 0.0
    local t0, diff
    local i
    for i in 1:$(loops)
      t0 = time_ns()
      local val = $(esc(args[1]))
      diff = time_ns() - t0
      if diff > max
        max = diff
      end
      if diff < min
        min = diff
      end
      sum += diff
    end
    sum = sum/$(loops)
    print("max: $(max/1e9)\nmin: $(min/1e9)\nsum: $(sum/1e9)\n")
    gc_enable(true)
    nothing
  end
end

function timeit(expr::Expr)
  @timeit expr
end

################################################################################
#
#  Exception types
#
################################################################################

type LowPrecisionCholesky <: Exception end

Base.showerror(io::IO, e::LowPrecisionCholesky) =
    print(io, e.var, """
    Negative diagonal in Cholesky decomposition, probably a precision issue""")

type LowPrecisionLLL <: Exception end

Base.showerror(io::IO, e::LowPrecisionLLL) =
    print(io, e.var, """
    Transformation matrix has too large entries relative to precision in LLL""")

# what is this function doing here?
function checkbounds(a::Int, b::Int) nothing; end;

################################################################################
#
#  "Submodules"
#
################################################################################

include("HeckeTypes.jl")
include("Misc.jl")
include("LinearAlgebra.jl")
include("BigComplex.jl")
include("conjugates.jl")
#include("NfMaxOrd/NfOrdCls.jl")
include("NfOrd.jl")
include("analytic.jl")
include("NfMaxOrd.jl")
include("Map.jl")
include("basis.jl")
include("helper.jl")
include("misc2.jl")

for T in subtypes(Map)
  Base.call(M::T, a) = image(M, a)
end

################################################################################
#
#  Verbose printing
#
################################################################################

doc"""
***
    vshow(A) -> Void

> Prints all fields of $A$.
"""
function vshow(A)
  for i in fieldnames(typeof(A))
    if isdefined(A, i)
      println("$i: ")
      println(getfield(A, i), "\n")
    else
      println("$i: Not definied")
    end
  end
end

################################################################################
#
#  Element types for parent types
#
################################################################################

# Nemo only provides element_types for parent objects

elem_type(::NfMaxOrd) = NfOrdElem{NfMaxOrd}

elem_type(::Type{NfMaxOrd}) = NfOrdElem{NfMaxOrd}

elem_type(::NfOrdGen) = NfOrdElem{NfOrdGen}

elem_type(::Type{NfOrdGen}) = NfOrdElem{NfOrdGen}

elem_type{T}(::Type{FacElemMon{T}}) = FacElem{T}

elem_type(::Type{AnticNumberField}) = nf_elem

elem_type(::Type{FqNmodFiniteField}) = fq_nmod

elem_type{T}(::Type{GenResRing{T}}) = GenRes{T}

################################################################################
#
#  Parent types for element types
#
################################################################################

parent_type(::Type{NfOrdElem{NfMaxOrd}}) = NfMaxOrd

################################################################################
#
#  An update function
#
################################################################################

function update()

  olddir = pwd()

  println("Updating Hecke ... ")
  cd(Pkg.dir("Hecke"))
  run(`git pull`)

  pkgdir = Pkg.dir("Nemo")

  println("Updating Nemo ... ")
  cd("$pkgdir")
  run(`git pull`)

  println("Updating antic ... ")
  cd("$pkgdir/deps/antic")
  run(`git pull`)

  println("Updating arb ... ")
  cd("$pkgdir/deps/arb")
  run(`git pull`)
  run(`make -j`)
  run(`make install`)

  println("Updating flint ... ")
  cd("$pkgdir/deps/flint2")
  run(`git pull`)
  run(`make -j`)
  run(`make install`)

  cd(olddir)
end

function whos(io::IO=STDOUT, m::Module=current_module(), pattern::Regex=r"")
    maxline = Base.tty_size()[2]
    line = zeros(UInt8, maxline)
    head = PipeBuffer(maxline + 1)
    for v in sort!(names(m, true)) # show also NON exported stuff!
        s = string(v)
        if isdefined(m, v) && ismatch(pattern, s)
            value = getfield(m, v)
            @printf head "%30s " s
            try
                bytes = Base.summarysize(value)
                if bytes < 10_000
                    @printf(head, "%6d bytes  ", bytes)
                else
                    @printf(head, "%6d KB     ", bytes ÷ (1024))
                end
                print(head, Base.summary(value))
            catch e
                print(head, "#=ERROR: unable to show value=#")
                println(e)
            end

            newline = search(head, UInt8('\n')) - 1
            if newline < 0
                newline = nb_available(head)
            end
            if newline > maxline
                newline = maxline - 1 # make space for ...
            end
            line = resize!(line, newline)
            line = read!(head, line)

            Base.write(io, line)
            if nb_available(head) > 0 # more to read? replace with ...
                print(io, '\u2026') # hdots
            end
            println(io)
            seekend(head) # skip the rest of the text
        end
    end
end

whos(m::Module, pat::Regex=r"") = whos(STDOUT, m, pat)
whos(pat::Regex) = whos(STDOUT, current_module(), pat)

#
# stuff for 0.5
#

if VERSION > v"0.5.0-"
  @inline __get_rounding_mode() = Base.MPFR.rounding_raw(BigFloat)
end

if VERSION < v"0.5.0-"
  @inline __get_rounding_mode() = Base.MPFR.ROUNDING_MODE[end]
end

end


using Abaco
using Test

interval=10
ts = nowts()

sn1 = "tn01"
sn2 = "tn02"
sn3 = "tn03"

val1 = 100
val2 = 101
val3 = 102
x = 10

expected_triggers = 12
actual_triggers = 0

function onresult(ts, ne, name, value, inputs)
    global actual_triggers
    @debug "timestamp [$ts]: ne: [$ne] $name = $value"
    if name === "offset_footprint"
        @test value == x + val1 + val2 + val3
    end
    actual_triggers += 1
end

function onresult(timestamp, ne, name, value::Abaco.PValue, inputs)
    global actual_triggers
    @debug "progressive timestamp [$ts]: ne: [$ne] $name = $value"
    if name === "footprint_sum"
        if value.contribs == 1
            @test ne == "trento"
            @test timestamp == span(ts, interval)
            @test value.value == val1
        elseif value.contribs == 2
            @test value.value == val1 + val2
        elseif value.contribs == 3
            @test value.value == val1 + val2 + val3
        end
    end
    if name === "footprint_mean"
        if value.contribs == 1
            @test ne == "trento"
            @test timestamp == span(ts, interval)
            @test value.value == val1
        elseif value.contribs == 2
            @test value.value == (val1 + val2)/2
        elseif value.contribs == 3
            @test value.value == (val1 + val2 + val3)/3
        end
    end
    actual_triggers += 1
end

abaco = abaco_init(onresult, interval=interval)

@debug "starting demo with time span: $interval, values timestamp: $ts"

setup_settings(abaco, "hub", oncomplete=onresult)

formula(abaco, "footprint_sum", "sum(sensor.footprint)", "hub")
formula(abaco, "footprint_mean", "mean(sensor.footprint)", "hub")
formula(abaco, "footprint_std", "std(sensor.footprint)", "hub")
formula(abaco, "progressive_fake", "x", "hub")
formula(abaco, "foobar", "x+y", "hub")
formula(abaco, "offset_footprint", "x + footprint_sum", "hub")

city = node(abaco, "trento", "hub")

node(abaco, city, sn1, "sensor")
node(abaco, city, sn2, "sensor")
node(abaco, city, sn3, "sensor")

current_footprint = get_collected(abaco, city.ne, "sensor.footprint")
@debug "initial carbon footprints: $current_footprint"

ingest(abaco, ts, sn1, Dict("footprint" => val1))
ingest(abaco, ts, sn2, Dict("footprint" => val2))

current_footprint = get_collected(abaco, city.ne, "sensor.footprint", ts)
@debug "current carbon footprint: $current_footprint"

footprint_sum = sum_collected(abaco, city.ne, "sensor.footprint", ts)
@debug "footprint sum: $footprint_sum"
@test footprint_sum.contribs == 2
@test footprint_sum.expected == 3
@test footprint_sum.value == val1 + val2

ingest(abaco, ts, sn3, Dict("footprint" => val3))

footprint_sum = sum_collected(abaco, city.ne, "sensor.footprint", ts)
@debug "footprint sum: $footprint_sum"
@test footprint_sum.contribs == 3
@test footprint_sum.value == val1 + val2 + val3

ingest(abaco, ts, city.ne, Dict(
    "x" => x,
    "y" => 20
))

@test actual_triggers === expected_triggers


# UnitCommitment.jl: Optimization Package for Security-Constrained Unit Commitment
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.

module UnitCommitment
    include("log.jl")
    include("dotdict.jl")
    include("instance.jl")
    include("screening.jl")
    include("components.jl")
    include("variables.jl")
    include("constraints.jl")
    include("formulation.jl")
    #include("model.jl")
    include("model2.jl")
    include("sensitivity.jl")
    include("validate.jl")
    include("convert.jl")
    include("initcond.jl")
end


using BenchmarkTools, SymbolicUtils
using SymbolicUtils: isnumber

using Random

SUITE = BenchmarkGroup()

@syms a b c d; Random.seed!(123);

let r = @rule(~x => ~x), rs = RuleSet([r]),
    acr = @rule(~x::isnumber + ~y => ~y)

    overhead = SUITE["overhead"]  = BenchmarkGroup()
    overhead["rule"]  = BenchmarkGroup()

    overhead["rule"]["noop:Int"]  = @benchmarkable $r(1)
    overhead["rule"]["noop:Sym"]  = @benchmarkable $r($a)
    overhead["rule"]["noop:Term"] = @benchmarkable $r($(a+2))

    overhead["acrule"]  = BenchmarkGroup()
    overhead["acrule"]["noop:Int"]  = @benchmarkable $acr(1)
    overhead["acrule"]["noop:Sym"]  = @benchmarkable $acr($a)
    overhead["acrule"]["a+2"] = @benchmarkable $acr($(a+2))
    overhead["acrule"]["a+b"] = @benchmarkable $acr($(a+b))
    overhead["acrule"]["a+2+b"] = @benchmarkable $acr($(a+2+b))

    overhead["ruleset"]  = BenchmarkGroup()
    overhead["ruleset"]["noop:Int"]  = @benchmarkable $rs(1)
    overhead["ruleset"]["noop:Sym"]  = @benchmarkable $rs($a)
    overhead["ruleset"]["noop:Term"] = @benchmarkable $rs($(a+2))

    overhead["simplify"]  = BenchmarkGroup()
    overhead["simplify"]["noop:Int"]  = @benchmarkable simplify(1)
    overhead["simplify"]["noop:Sym"]  = @benchmarkable simplify($a)
    overhead["simplify"]["noop:Term"] = @benchmarkable simplify($(a+2))

    function random_term(len; atoms, funs, fallback_atom=1)
        xs = rand(atoms, len)
        while length(xs) > 1
            xs = map(Iterators.partition(xs, 2)) do xy
                x = xy[1]; y = get(xy, 2, fallback_atom)
                rand(funs)(x, y)
            end
        end
        xs[]
    end
    ex1 = random_term(1000, atoms=[a, b, c, d, a^(-1), b^(-1), 1, 2.0], funs=[+, *])
    ex2 = random_term(1000, atoms=[a, b, c, d, a^(-1), b^(-1), 1, 2.0], funs=[/, *])

    overhead["simplify"]["randterm (+, *):serial"] = @benchmarkable simplify($ex1, threaded=false)
    overhead["simplify"]["randterm (/, *):serial"] = @benchmarkable simplify($ex2, threaded=false)
    overhead["simplify"]["randterm (+, *):thread"] = @benchmarkable simplify($ex1, threaded=true)
    overhead["simplify"]["randterm (/, *):thread"] = @benchmarkable simplify($ex2, threaded=true)
end


init_bounds(bounds, pomdp, sol) = bounds
init_bounds(t::Tuple, pomdp, sol) = (init_bound(first(t), pomdp, sol), init_bound(last(t), pomdp, sol))
bounds(f::Function, pomdp::POMDP, b::ScenarioBelief) = f(pomdp, b)
bounds(t::Tuple, pomdp::POMDP, b::ScenarioBelief) = (lbound(t[1], pomdp, b), ubound(t[2], pomdp, b))

function bounds_sanity_check(pomdp::POMDP, sb::ScenarioBelief, L_0, U_0)
    if L_0 > U_0
        @warn("L_0 ($L_0) > U_0 ($U_0)   |ϕ| = $(length(sb.scenarios))")
    end
    if all(isterminal(pomdp, s) for s in particles(sb))
        if L_0 != 0.0 || U_0 != 0.0
            error(@sprintf("If all states are terminal, lower and upper bounds should be zero (L_0=%-10.2g, U_0=%-10.2g). (try IndependentBounds(l, u, check_terminal=true))", L_0, U_0))
        end
    end
end

# Upper and lower do not depend on each other (most cases)

struct IndependentBounds{L, U}
    lower::L
    upper::U
    check_terminal::Bool
end

IndependentBounds(l, u; check_terminal=false) = IndependentBounds(l, u, check_terminal)

function bounds(bounds::IndependentBounds, pomdp::POMDP, b::ScenarioBelief)
    if bounds.check_terminal && all(isterminal(pomdp, s) for s in particles(b))
        return (0.0, 0.0)
    end
    return (lbound(bounds.lower, pomdp, b), ubound(bounds.upper, pomdp, b))
end

function init_bounds(bounds::IndependentBounds, pomdp::POMDP, sol::DESPOTSolver)
    return IndependentBounds(init_bound(bounds.lower, pomdp, sol),
                             init_bound(bounds.upper, pomdp, sol),
                             bounds.check_terminal
                            )
end
init_bound(bound, pomdp, sol) = bound

ubound(n::Number, pomdp, b) = convert(Float64, n)
lbound(n::Number, pomdp, b) = convert(Float64, n)

ubound(f::Function, pomdp, b) = f(pomdp, b)
lbound(f::Function, pomdp, b) = f(pomdp, b)

# Value if Fully Observed Under a Policy
struct FullyObservableValueUB{P<:Union{Solver, Policy}}
    p::P
end

ubound(ub::FullyObservableValueUB, pomdp::POMDP, b::ScenarioBelief) = mean(value(ub.p, s) for s in particles(b)) # assumes that all are weighted equally

function init_bound(ub::FullyObservableValueUB{S}, pomdp::POMDP, sol::DESPOTSolver) where S <: Solver
    return FullyObservableValueUB(solve(ub.p, pomdp))
end

# Default Policy Lower Bound

struct DefaultPolicyLB{P<:Union{Solver, Policy}, D<:Union{Nothing,Int}}
    policy::P
    max_depth::D
end

function DefaultPolicyLB(policy_or_solver::T; max_depth=nothing) where T <: Union{Solver, Policy}
    return DefaultPolicyLB(policy_or_solver, max_depth)
end

function lbound(lb::DefaultPolicyLB, pomdp::POMDP, b::ScenarioBelief)
    rsum = branching_sim(pomdp, lb.policy, b, lb.max_depth-b.depth)
    return rsum/length(b.scenarios)
end

function init_bound(lb::DefaultPolicyLB{S}, pomdp::POMDP, sol::DESPOTSolver) where S <: Solver
    policy = solve(lb.policy, pomdp)
    return init_bound(DefaultPolicyLB(policy, lb.max_depth), pomdp, sol)
end

function init_bound(lb::DefaultPolicyLB{P}, pomdp::POMDP, sol::DESPOTSolver) where P <: Policy
    max_depth = something(lb.max_depth, sol.D)
    return DefaultPolicyLB(lb.policy, max_depth)
end