type IsingModel{T} <: ClassicalSpinModel{T}
	spininfo::SpinInfo{T}
	factorW::Array{T,2}

	function IsingModel{T}(spininfo::SpinInfo{T})
		this = new{T}()
		this.spininfo = checkSpininfoForIsing(spininfo)
		initialize!(this)
		return this
	end
end

IsingModel{T}(spininfo::SpinInfo{T}) = IsingModel{T}(spininfo)
IsingModel() = testIsingModel()

#---
# functions for constructor
function checkSpininfoForIsing{T}(spininfo::SpinInfo{T})
	if spininfo.modelname != "ising"
		println("warning:spininfo.modelname does not match to the type.
		  the modelname is changed to 'ising'")
		spininfo.modelname = "ising"
	end
	if getStates(spininfo) != 2
		println("warning: ising model can only have 2 states.
			the numberOfStates is changed to 2")
		spininfo.numberOfState = 2
	end
	return spininfo
end

function initialize!{T}(isingmodel::IsingModel{T})
	if isSymmetricFactorization(isingmodel)
		factorW = initializeIsingFactorWsym(getEnvParameters(isingmodel))
	else
		factorW = initializeIsingFactorWasym(getEnvParameters(isingmodel))
	end
	isingmodel.factorW = factorW
end

function initializeIsingFactorWsym{T}(temp::T, externalfield::T)
	revTemp = one(T)/temp
	expKbar = sqrt(exp(2.0*revTemp)+sqrt(exp(4.0*revTemp)-1.0))
	expterm = getExpGamma(revTemp, externalfield)
	factorW = [expterm*expKbar expterm/expKbar; 1.0/(expterm*expKbar) expKbar/expterm] ./ sqrt(2.0*exp(revTemp))
	return factorW
end

function initializeIsingFactorWasym{T}(temp::T, externalfield::T)
	revTemp = one(T)/temp
	coshterm = sqrt(cosh(revTemp))
	sinhterm = sqrt(sinh(revTemp))
	expterm = getExpGamma(revTemp, externalfield)
	return factorW = [coshterm*expterm sinhterm*expterm; coshterm/expterm -sinhterm/expterm]
end

initializeIsingFactorWsym{T}(envparameter::Tuple{Vararg{T,2}}) =
	initializeIsingFactorWsym(envparameter[1], envparameter[2])
initializeIsingFactorWasym{T}(envparameter::Tuple{Vararg{T,2}}) =
	initializeIsingFactorWasym(envparameter[1], envparameter[2])

#---
# functions for IsingModel
function getMeasureOperator{T}(spinmodel::IsingModel{T})
	return [-one(T), one(T)]
end

#---
# test to make a example SpinModel
function testIsingModel()
	modelcode = "classical_ising_2"
	factorization = "sym"
	externalfield = 1.0e-13
	temperature = 1.5
	spininfo = SpinInfo(modelcode, factorization, externalfield, temperature)
	testModel = buildSpinSystem(spininfo)
	return testModel
end


#Wave equation Differential Equation
function square_grid_KG!(df,f,param,t)

    #Get Operators and variables
    Opps        = param[1]
    vars        = param[2]

    av  = Opps.aux_vex

    RHS     = Opps.DiffO_1
    BC      = Opps.BC_Mat
    Alpha   = Opps.Abs_Mat
    Lz      = Opps.Lz

    #Fields of our problem (Julia changes the rhs if you change the lhs)
    ψ   = f.x[1]
    ϕ   = f.x[2]

    dψ  = df.x[1]
    dϕ  = df.x[2]

    #Differential Equation
    if (vars.dissipation)

        #If Rotation is on,
        if( vars.Ω != 0.0 )

            #Update absorption matrix
            alpha!(Alpha , vars , Opps , t)

            #First Derivative
            dψ .=  ϕ

            #Second Derivative
            mul!(dϕ , RHS , ψ )
            av =  Alpha * (ϕ  + Lz * ψ)
            dϕ .=  dϕ .- av

        else

            # No need to get absorption matrix
            #alpha!( Alpha , vars , Opps , t)

            #First Derivative
            dψ .=  ϕ

            #Second derivative
            mul!(dϕ , RHS , ψ )
            av =  Alpha * (ϕ  + Lz * ψ)
            dϕ .=  dϕ .- av

        end

    else

        #First Derivative
        dψ .=  ϕ

        #Second Derivative
        mul!(dϕ , RHS , ψ )

    end

    #Boundary Conditions
    if(vars.Boundaries != :periodic )

        mul!(av , BC , dϕ )
       dϕ .= av

    end

end

function solve_wave_equation_2D(p_in::Param)

    #
    # Setup Routine
    #

    #Welcome message
    welcome_message();

    #Start time
    init_time = time()
    max_runtime = time_string_to_seconds(p_in.max_runtime)

    #Setup routine setup check
    if( !setup_folder(p_in) )
        print_exit_message(  time() - init_time )
        return false
    end

    #Check if parameters are OK
    println("Checking Parameters...")
    p = p_in

    if( p.resume_simulation == :true)
        p = get_parameters_from_file(p.input_file_name)

        if( p_in.t_sim_final <= p.t_sim_final )
            println("ERROR: Simulation already at final time")
            print_exit_message(  time() - init_time )
            return false
        end

        p = Param(  p,

                    resume_simulation = p_in.resume_simulation,
                    input_file_name = p_in.input_file_name,

                    t_sim_init  = p.t_sim_final,
                    t_sim_final = p_in.t_sim_final,

                    folder = p_in.folder,
                    fname = p_in.fname
            )
    end

    print_parameters_to_screen(p)

    println("OK")

    #Storage file
    println("Creating HDF5 file...")

    fdata   = "./data"
    fname   = fdata * p.folder *  "$(p.fname).h5"
    fid = h5open(fname, "w")

    save_parameters_to_file( p , fid )

    println("OK")

    #Create operators structures
    println("Setting up Operators...")
    Operators = set_up_operator(p)
    println("OK")

    #Time variables
    tmin = p.t_sim_init
    tmax = p.t_sim_final
    tspan       = ( tmin , tmax )

    iter        = 0
    iter_save   = 0
    every       = floor(Int , p.out_every_t/p.deltat + 1.0)
    save_time   = 0

    #Initial Conditions
    println("Setting up inicial configuration...")
    ψ , dψ = get_gaussian_pulse( p , Operators)

    #If resume simulation
    if( p.resume_simulation == :true)

        #Get max iteration
        _ , _ , itermax = get_time_variables( p.input_file_name )

        #Get field at previous simulation
        _,_,_, ψmat , dψmat = get_fields( p.input_file_name , itermax)

        #Reshape to one column vector
        ψ   = reshape( ψmat , p.xnodes*p.ynodes)
        dψ  = reshape( dψmat , p.xnodes*p.ynodes)
    end

    U = ArrayPartition( Operators.BC_Mat * ψ , Operators.BC_Mat *dψ )
    println("OK")

    #Parameters
    my_params = ( Operators , p)

    #Problem ODE and integrator
    prob = ODEProblem(  square_grid_KG! , U , tspan , my_params )

    integrator = init( prob , RK4() , save_everystep=false , dt=p.deltat , adaptive=false )

    #Create a group for the functions
    g1 =  create_group(fid , "ψ");
    g2 =  create_group(fid , "dψ");

    #Save first configuration of field

    dset_name = string("iteration_",iter_save)

    data1 = integrator.u.x[1]
    dset1, dtype1  = create_dataset(g1, dset_name , data1 )
    write_dataset(dset1, dtype1 , data1)

    data2 = integrator.u.x[2]
    dset2, dtype2  = create_dataset(g2, dset_name , data2 )
    write_dataset(dset2, dtype2 , data2)

    #Add HDF5.attributes time to both datasets
    HDF5.attributes(dset1)["time"] =  integrator.t
    HDF5.attributes(dset2)["time"] =  integrator.t

    #Progress bar and time evolution
    println("Starting up simulation...")
    prog1 = Progress( Int(floor(p.t_sim_final-p.t_sim_init))*100 )

    for (u,t) in tuples(integrator)

        #Increase iteration counter
        iter += 1

        #Step integrator
        DifferentialEquations.step!(integrator)

        #Update bar
        ProgressMeter.update!(prog1, Int(floor(integrator.t*100)))

        #If save iteration
        if iter % every == 0

            iter_save += 1
            save_time = integrator.t

            #data set names
            dset_name = string("iteration_",iter_save)

            #data set for both displacement and derivative
            data1 = integrator.u.x[1]
            dset1, dtype1  = create_dataset(g1, dset_name , data1 )
            write_dataset(dset1, dtype1 , data1)

            data2 = integrator.u.x[2]
            dset2, dtype2  = create_dataset(g2, dset_name , data2 )
            write_dataset(dset2, dtype2 , data2)

            #Add HDF5.attributes time to both datasets
            HDF5.attributes(dset1)["time"] =  integrator.t
            HDF5.attributes(dset2)["time"] =  integrator.t

        end

        #Check if time has exceeded
        if( time_since(init_time) > max_runtime )
            println("\nMaximum allowed runtime reached , Ending Simulation!")
            break
        end

    end

    #Add time information to file
    HDF5.attributes(fid)["max_iter"]    = iter_save
    HDF5.attributes(fid)["t_min"]       = p.t_sim_init
    HDF5.attributes(fid)["t_max"]       = save_time
    HDF5.attributes(fid)["out_every_t"] = p.out_every_t
    HDF5.attributes(fid)["dt"]          = p.deltat

    close(fid)
    println("Saving Files...")

    print_exit_message(  time() - init_time )

    return true
end


commands = ["F10", "N3", "F7", "R90", "F11"]
commands = split(strip(read("input.txt", String)), "\n")

mutable struct Position
    x::Int64
    y::Int64
    bearing::Int64
end

pos = Position(0, 0, 90)
commandmap = Dict(
    'N' => v -> pos.y += v,
    'S' => v -> pos.y -= v,
    'W' => v -> pos.x -= v,
    'E' => v -> pos.x += v,
    'R' => v -> pos.bearing = (pos.bearing + v) % 360,
    'L' => v -> pos.bearing = (360 + pos.bearing - v) % 360,
    'F' => v -> commandmap["NESW"[(pos.bearing ÷ 90) + 1]](v))

for command in commands
    commandmap[command[1]](parse(Int, command[2:end]))
    println(pos)
end

print(abs(pos.x) + abs(pos.y)) # 521

ship = Position(0, 0, 0)
waypoint = Position(10, 1, 0)

function rotate(n)
    prev = Position(waypoint.x, waypoint.y, 0)
    if n == 90
        waypoint.x = prev.y
        waypoint.y = -prev.x
    elseif n == 180
        waypoint.x = -waypoint.x
        waypoint.y = -waypoint.y
    elseif n == 270
        waypoint.x = -prev.y
        waypoint.y = prev.x
    end
end

commanddict = Dict(
    'N' => v -> waypoint.y += v,
    'S' => v -> waypoint.y -= v,
    'W' => v -> waypoint.x -= v,
    'E' => v -> waypoint.x += v,
    'R' => rotate,
    'L' => v -> rotate(360 - v),
    'F' => function (v)
    diff = (x = waypoint.x - ship.x,)
    ship.x += waypoint.x * v
    ship.y += waypoint.y * v
end)

for command in commands
    commanddict[command[1]](parse(Int, command[2:end]))
end

print(abs(ship.x) + abs(ship.y))


using Statistics
using DelimitedFiles

include("MatrixGenerator.jl")
include("Calculations.jl")
include("MeanFieldSolver.jl")
include("Test.jl")

# get inputs, initialization
lines = readlines("InputBoseHubbard")
# inverse temperature
temp_name, β_str = split(lines[1])
const β = parse(Float64, β_str)
# number of sites
temp_name, num_sites_str = split(lines[2])
const num_sites = parse(Int64, num_sites_str)
# number of bosons
temp_name, num_bosons_str = split(lines[3])
const num_bosons = parse(Int64, num_bosons_str)
# length of the imaginary time interval
temp_name, Δτ_str = split(lines[4])
const Δτ = parse(Float64, Δτ_str)
# hopping constant
temp_name, t_str = split(lines[5])
const t = parse(Float64, t_str)
# on-site repulsion constant
temp_name, U_str = split(lines[6])
const U = parse(Float64, U_str)
# number of iterations
temp_name, num_iterations_str = split(lines[7])
const num_iterations = convert(Int64, parse(Float64, num_iterations_str))
# flag for the background subtraction
temp_name, flag_background_subtraction_str = split(lines[8])
const flag_background_subtraction = parse(Int16, flag_background_subtraction_str)
# flag for calculating the density matrix
temp_name, flag_density_matrix_str = split(lines[9])
const flag_density_matrix = convert(Int64, parse(Int16, flag_density_matrix_str))

const background_dist = ones(num_sites) / num_sites #  Equally distributed among sites
const residue_term = sum(β * (U/2) * background_dist.^2)

# Constants for calculations
# Kinetic matrix in each imaginary time slice is fixed throughout the calculation.
const kinetic_matrix_imgtime = complex(exp(-kinetic_matrix(num_sites, t) * Δτ / 2))
# number of imaginary time steps
const L = convert(Int64, round(β / Δτ))
# coefficient of the H-S transformed potential term
const AF_coefficient = sqrt(complex(-Δτ * U))

#mean_value, error_bar = Z_iteration(num_iteration, L)

#write_to_file(mean_value, error_bar, Δτ)

#function write_to_file(mean_value, error_bar, Δτ)
    #output_file = open("result","a+")
    #write(output_file, string(β), " ", string(Δτ), " ", string(mean_value), " ", string(error_bar), "\n")
    #close(output_file)
#end

Ek_mean, Ek_error, Ep_mean, Ep_error, E_tot_mean, E_tot_error_bar = measure_TotEnergy(num_iterations)
println("U = ", U)
println("Beta = ", β)
println("Ns = ", num_sites)
println("NBosons = ", num_bosons)
println("Ek = ", Ek_mean, ", Error = ", Ek_error)
println("Ep = ", Ep_mean, ", Error = ", Ep_error)
println("Etot = ", E_tot_mean, ", Error = ", E_tot_error_bar)


# ==============================================================================
# Max-plus system linear
# ==============================================================================

# ==============================================================================
# Creation of max-plus system linear

### First system
S1 = mpsyslin(mpsparse([1 2 3; 4 5 6; 7 8 9]), mpsparse([0;0;0]), mpsparse([0 0 0]), mpsparse(mpeye(Int64, 3,3)), mpzeros(Int64, 3,1))

@test S1.A == [1 2 3; 4 5 6; 7 8 9]
@test S1.B == reshape([0;0;0], length([0;0;0]), 1)
@test S1.C == [0 0 0]
@test S1.D == mpeye(Int64, 3,3)
@test S1.x0 == mpzeros(Int64, 3,1)

S2 = mpsyslin(mpsparse([1 2 3; 4 5 6; 7 8 9]), mpsparse([0;0;0]), mpsparse([0 0 0]), mpsparse(mpeye(Int64, 3,3)))
S3 = mpsyslin(mpsparse([1 2 3; 4 5 6; 7 8 9]), mpsparse([0;0;0]), mpsparse([0 0 0]))
S4 = mpsyslin(MP([1 2 3; 4 5 6; 7 8 9]), MP([0;0;0]), MP([0 0 0]), mpeye(Int64, 3,3), MP([0; 0; 0]))
S5 = mpsyslin(MP([1 2 3; 4 5 6; 7 8 9]), MP([0;0;0]), MP([0 0 0]), mpeye(Int64, 3,3))
S6 = mpsyslin(MP([1 2 3; 4 5 6; 7 8 9]), MP([0;0;0]), MP([0 0 0]))

@test S1.A == S2.A == S3.A == S4.A == S5.A == S6.A
@test S1.B == S2.B == S3.B == S4.B == S5.B == S6.B
@test S1.C == S2.C == S3.C == S4.C == S5.C == S6.C
@test S1.D == S2.D == S3.D == S4.D == S5.D == S6.D
@test S1.x0 == S2.x0 == S3.x0 == S5.x0 == S6.x0
@test S4.x0 == mpones(Int64, 3,1)

### Second system
S7 = mpsyslin(mpsparse([1 2; 3 4]), mpsparse([0; 0]), mpsparse([0 0]), mpsparse(mpeye(Int64, 2,2)), mpzeros(Int64, 2,1))

@test S7.A == [1 2; 3 4]
@test S7.B == reshape([0;0], length([0;0]), 1)
@test S7.C == [0 0]
@test S7.D == mpeye(Int64, 2,2)
@test S7.x0 == mpzeros(Int64, 2,1)

S8 = mpsyslin(mpsparse([1 2; 3 4]), mpsparse([0; 0]), mpsparse([0 0]), mpsparse(mpeye(Int64, 2,2)))
S9 = mpsyslin(mpsparse([1 2; 3 4]), mpsparse([0; 0]), mpsparse([0 0]))
S10 = mpsyslin(MP([1 2; 3 4]), MP([0; 0]), MP([0 0]), mpeye(Int64, 2,2), MP([0; 0]))
S11 = mpsyslin(MP([1 2; 3 4]), MP([0; 0]), MP([0 0]), mpeye(Int64, 2,2))
S12 = mpsyslin(MP([1 2; 3 4]), MP([0; 0]), MP([0 0]))

@test S7.A == S8.A == S9.A == S10.A == S11.A == S12.A
@test S7.B == S8.B == S9.B == S10.B == S11.B == S12.B
@test S7.C == S8.C == S9.C == S10.C == S11.C == S12.C
@test S7.D == S8.D == S9.D == S10.D == S10.D == S11.D == S12.D
@test S7.x0 == S8.x0 == S9.x0 == S11.x0 == S12.x0
@test S10.x0 == mpones(Int64, 2,1)

### Third system
A = MP([1 2; 3 4]); B = MP([5 6; 7 8]); C = MP([9 10; 11 12]); D = MP([13 14; 15 16])
x0 = MP([17; 18])

S13 = mpsyslin(A,B,C,D,x0)
S14 = mpsyslin(A,B,C,D)
S15 = mpsyslin(A,B,C)
@test S13.A == S14.A == S15.A == A
@test S13.B == S14.B == S15.B == B
@test S13.C == S14.C == S15.C == C
@test S13.D == S14.D == D
@test S13.x0 == reshape(x0, length(x0), 1)
@test S14.x0 == S15.x0 == mpzeros(Int64, 2, 1)

# ==============================================================================
# Simulation. Note here we should call the explicit systems but here
# they are the same than implicit.

res = mpsimul(S7, MP(1:10), false)
@test res == MP([37])

res = mpsimul(S7, MP(1:10), true)
@test res == MP([1 5 9 13 17 21 25 29 33 37])
@test res == mpsimul(S8, MP(1:10), true)
@test res == mpsimul(S9, MP(1:10), true)
@test res == mpsimul(S11, MP(1:10), true)
@test res == mpsimul(S12, MP(1:10), true)

@test mpsimul(S10, MP(1:10), true) == MP([4  8  12  16  20  24  28  32  36  40])

# ==============================================================================
# Composition of systems

S1 = mpsyslin(MP([1.0 2; 3 4]), MP([0.0; 0]), MP([0.0 0]), mpeye(Float64, 2,2))
S2 = mpsyslin(MP([1.0 2 3; 4 5 6; 7 8 9]), MP([0.0;0;0]), MP([0.0 0 0]), mpeye(Float64, 3,3))

# Diagonal composition
S = S1 | S2

@test S.A == [1.0 2 mp0 mp0 mp0; 3 4 mp0 mp0 mp0; mp0 mp0 1 2 3; mp0 mp0 4 5 6; mp0 mp0 7 8 9]
@test S.B == [mp1 mp0; mp1 mp0; mp0 mp1; mp0 mp1; mp0 mp1]
@test S.C == [mp1 mp1 mp0 mp0 mp0; mp0 mp0 mp1 mp1 mp1]
@test S.D == mpeye(Float64, 5,5)
@test S.x0 == mpzeros(Float64, 5,1)

# Parallel composition
S = S1 + S2

@test S.A == [1.0 2 mp0 mp0 mp0; 3 4 mp0 mp0 mp0; mp0 mp0 1 2 3; mp0 mp0 4 5 6; mp0 mp0 7 8 9]
@test S.B == mpones(Float64, 5,1)
@test S.C == mpones(Float64, 1,5)
@test S.D == mpeye(Float64, 5,5)
@test S.x0 == mpzeros(Float64, 5,1)

# Series composition
S = S1 * S2

@test S.A == [1.0 2 3 mp0 mp0; 4 5 6 mp0 mp0; 7 8 9 mp0 mp0; mp0 mp0 mp0 1 2; mp0 mp0 mp0 3 4]
@test S.B == [mpones(Float64, 3,1); dense(mpzeros(Float64, 2,1))]
@test S.C == [dense(mpzeros(Float64, 1,3)) mpones(Float64, 1,2)]
D = mpeye(Float64, 5,5); D[4:5, 1:3] .= mp1;
@test S.D == D
@test S.x0 == mpzeros(Float64, 5,1)

# Input in common
S = [S1; S2]

@test S.A == [1.0 2 mp0 mp0 mp0; 3 4 mp0 mp0 mp0; mp0 mp0 1 2 3; mp0 mp0 4 5 6; mp0 mp0 7 8 9]
@test S.B == mpones(Float64, 5,1)
@test S.C == [mp1 mp1 mp0 mp0 mp0; mp0 mp0 mp1 mp1 mp1]
@test S.D == mpeye(Float64, 5,5)
@test S.x0 == mpzeros(Float64, 5,1)

# Output addition FIXME syntax S = [S1, S2] is not working
S = [S1 S2]

@test S.A == [1.0 2 mp0 mp0 mp0; 3 4 mp0 mp0 mp0; mp0 mp0 1 2 3; mp0 mp0 4 5 6; mp0 mp0 7 8 9]
@test S.B == [mp1 mp0; mp1 mp0; mp0 mp1; mp0 mp1; mp0 mp1]
@test S.C == mpones(Float64, 1,5)
@test S.D == mpeye(Float64, 5,5)
@test S.x0 == mpzeros(Float64, 5,1)

# Feedback composition FIXME syntax /.
S = S1 / S2

@test S.A == [1.0 2 mp0 mp0 mp0; 3 4 mp0 mp0 mp0; mp0 mp0 1 2 3; mp0 mp0 4 5 6; mp0 mp0 7 8 9]
@test S.B == [mpones(Float64, 2,1); dense(mpzeros(Float64, 3,1))]
@test S.C == [mpones(Float64, 1,2) dense(mpzeros(Float64, 1,3))]
D = mpeye(Float64, 5,5); D[3:5, 1:2] .= mp1; D[1:2, 3:5] .= mp1;
@test S.D == D
@test S.x0 == mpzeros(Float64, 5,1)

# ==============================================================================
#

S1 = mpsyslin(MP([1.0 2; 3 4]), MP([0.0; 0]), MP([0.0 0]), mpeye(Float64, 2,2))

S = 5 * S1
@test S.A == [1.0 2; 3 4]
@test S.B == reshape([0.0; 0], length([0;0]), 1)
@test S.C == [5.0 5]
@test S.D == mpeye(Float64, 2,2)
@test S.x0 == mpzeros(Float64, 2,1)
@test S == MP(5.0) * S1

S = S1 * 5
@test S.A == [1.0 2; 3 4]
@test S.B == reshape([5.0; 5], length([0;0]), 1)
@test S.C == [0.0 0]
@test S.D == mpeye(Float64, 2,2)
@test S.x0 == mpzeros(Float64, 2,1)
@test S1 * 5 == S1 * MP(5.0)

# ==============================================================================
# Implicit to Explicit

S1 = mpsyslin(MP([1.0 2; 3 4]), MP([0.0; 0]), MP([0.0 0]), mpeye(Float64, 2,2))
S = mpexplicit(S1)

@test S.A == S1.A
@test S.B == S1.B
@test S.C == S1.C
@test S.D == mpeye(Float64, 2,2)
@test S.x0 == S1.x0

S2 = mpsyslin(MP([1.0 2 3; 4 5 6; 7 8 9]), MP([0.0;0;0]), MP([0.0 0 0]), mpeye(Float64, 3,3))
S = mpexplicit(S2)

@test S.A == S2.A
@test S.B == S2.B
@test S.C == S2.C
@test S.D == mpeye(Float64, 3,3)
@test S.x0 == S2.x0


struct ProbIntsCache{T}
  σ::T
  order::Int
end
function (p::ProbIntsCache)(integrator)
  integrator.u .= integrator.u .+ p.σ*sqrt(integrator.dt^(2*p.order))*randn(size(integrator.u))
end

"""
Conrad P., Girolami M., Särkkä S., Stuart A., Zygalakis. K, Probability
Measures for Numerical Solutions of Differential Equations, arXiv:1506.04592
"""
function ProbIntsUncertainty(σ,order,save=true)
  affect! = ProbIntsCache(σ,order)
  condtion = (t,u,integrator) -> true
  save_positions = (save,false)
  DiscreteCallback(condtion,affect!,save_positions=save_positions)
end

struct AdaptiveProbIntsCache
  order::Int
end
function (p::AdaptiveProbIntsCache)(integrator)
  integrator.u .= integrator.u .+ integrator.EEst*sqrt(integrator.dt^(2*p.order))*randn(size(integrator.u))
end

function AdaptiveProbIntsUncertainty(order,save=true)
  affect! = AdaptiveProbIntsCache(order)
  condtion = (t,u,integrator) -> true
  save_positions = (save,false)
  DiscreteCallback(condtion,affect!,save_positions=save_positions)
end


#=
author: mingchuno
simple permutation brute fource
=#
array = [0 ,2 ,3, 4, 5, 6, 7 ,8, 9]

for arr in permutations(array)
  a = arr[1]
  if a == 0 continue end # fail fast

  b = arr[2]

  c = arr[3]
  if c == 0 continue end # fail fast

  d = arr[4]

  e = arr[5]
  if e == 0 continue end # fail fast

  f = arr[6]

  g = arr[7]
  if g == 0 continue end # fail fast

  h = arr[8]

  ab = a*10 + b
  cd = c*10 + d
  ef = e*10 + f
  gh = g*10 + h

  if ab - cd == ef && ef + gh == 111
    println("($(ab) - $(cd) = $(ef)) + $(gh) = 111")
  end
end


# Test heat equation agrees with analytic solution to problem 55 on page 28 in https://ocw.mit.edu/courses/mathematics/18-303-linear-partial-differential-equations-fall-2006/lecture-notes/heateqni.pdf
using MPI
using OrderedCollections
using StaticArrays
using Statistics
using Dierckx
using Test

using CLIMAParameters
struct EarthParameterSet <: AbstractEarthParameterSet end
const param_set = EarthParameterSet()

using ClimateMachine
using ClimateMachine.Land
using ClimateMachine.Land.SoilWaterParameterizations
using ClimateMachine.Land.SoilHeatParameterizations
using ClimateMachine.Mesh.Topologies
using ClimateMachine.Mesh.Grids
using ClimateMachine.DGMethods
using ClimateMachine.DGMethods.NumericalFluxes
using ClimateMachine.DGMethods: BalanceLaw, LocalGeometry
using ClimateMachine.MPIStateArrays
using ClimateMachine.GenericCallbacks
using ClimateMachine.ODESolvers
using ClimateMachine.VariableTemplates
using ClimateMachine.SingleStackUtils
using ClimateMachine.BalanceLaws

function init_soil!(land, state, aux, localgeo, time)
    FT = eltype(state)
    ϑ_l, θ_i = get_water_content(land.soil.water, aux, state, time)
    θ_l = volumetric_liquid_fraction(ϑ_l, land.soil.param_functions.porosity)
    param_set = parameter_set(land)
    ρc_s = volumetric_heat_capacity(
        θ_l,
        θ_i,
        land.soil.param_functions.ρc_ds,
        param_set,
    )

    state.soil.heat.ρe_int = FT(volumetric_internal_energy(
        θ_i,
        ρc_s,
        land.soil.heat.initialT(aux),
        param_set,
    ))
end

const FT = Float64

@testset "Heat analytic unit test" begin
    ClimateMachine.init()

    soil_param_functions = SoilParamFunctions(
        FT;
        porosity = 0.495,
        ν_ss_gravel = 0.1,
        ν_ss_om = 0.1,
        ν_ss_quartz = 0.1,
        ρc_ds = 0.43314518988433487,
        κ_solid = 8.0,
        ρp = 2700.0,
        κ_sat_unfrozen = 0.57,
        κ_sat_frozen = 2.29,
    )

    heat_surface_state = (aux, t) -> eltype(aux)(0.0)

    tau = FT(1) # period (sec)
    A = FT(5) # amplitude (K)
    ω = FT(2 * pi / tau)
    heat_bottom_state = (aux, t) -> A * cos(ω * t)
    T_init = (aux) -> eltype(aux)(0.0)

    soil_water_model = PrescribedWaterModel(
        (aux, t) -> eltype(aux)(0.0),
        (aux, t) -> eltype(aux)(0.0),
    )

    bc = LandDomainBC(
        bottom_bc = LandComponentBC(soil_heat = Dirichlet(heat_bottom_state)),
        surface_bc = LandComponentBC(soil_heat = Dirichlet(heat_surface_state)),
    )
    soil_heat_model = SoilHeatModel(FT; initialT = T_init)

    m_soil = SoilModel(soil_param_functions, soil_water_model, soil_heat_model)
    sources = ()
    m = LandModel(
        param_set,
        m_soil;
        boundary_conditions = bc,
        source = sources,
        init_state_prognostic = init_soil!,
    )

    N_poly = 5
    nelem_vert = 10

    # Specify the domain boundaries
    zmax = FT(1)
    zmin = FT(0)

    driver_config = ClimateMachine.SingleStackConfiguration(
        "LandModel",
        N_poly,
        nelem_vert,
        zmax,
        param_set,
        m;
        zmin = zmin,
        numerical_flux_first_order = CentralNumericalFluxFirstOrder(),
    )

    t0 = FT(0)
    timeend = FT(2)
    dt = FT(1e-4)
    solver_config = ClimateMachine.SolverConfiguration(
        t0,
        timeend,
        driver_config,
        ode_dt = dt,
    )
    mygrid = solver_config.dg.grid
    aux = solver_config.dg.state_auxiliary
    ClimateMachine.invoke!(solver_config)
    t = ODESolvers.gettime(solver_config.solver)

    z_ind = varsindex(vars_state(m, Auxiliary(), FT), :z)
    z = Array(aux[:, z_ind, :][:])

    T_ind = varsindex(vars_state(m, Auxiliary(), FT), :soil, :heat, :T)
    T = Array(aux[:, T_ind, :][:])

    num =
        exp.(sqrt(ω / 2) * (1 + im) * (1 .- z)) .-
        exp.(-sqrt(ω / 2) * (1 + im) * (1 .- z))
    denom = exp(sqrt(ω / 2) * (1 + im)) - exp.(-sqrt(ω / 2) * (1 + im))
    analytic_soln = real(num .* A * exp(im * ω * timeend) / denom)
    MSE = mean((analytic_soln .- T) .^ 2.0)
    @test eltype(aux) == FT
    @test MSE < 1e-5
end


# Turing.jl version of model at https://github.com/stan-dev/blob/master/basic_estimators/normal_loc.stan

@model nlmodel(y) = begin
  mu ~ Uniform(-10, 10)
  for n = 1:5
    y[n] ~ Normal(mu, 1.0)
  end
  mu
end


###############################################################################
#
# Radiative conductance
#
###############################################################################
"""
    radiative_conductance(T::FT) where {FT<:AbstractFloat}

Return the radiative conductance of leaf, given
- `T` leaf temperature
"""
function radiative_conductance(
            T::FT
) where {FT<:AbstractFloat}
    _A0::FT = 0.1579;
    _A1::FT = 0.0017;
    _A2::FT = 7.17E-6;
    _T ::FT = T - T_0(FT);

    return _A0 + _A1*T + _A2*T^2
end

###############################################################################
#
# Black body energy emittance
#
###############################################################################
"""
    black_body_emittance(T::FT) where {FT<:AbstractFloat}

Return the energy been radiated out, given
- `T` leaf temperature
"""
function black_body_emittance(
            T::FT
) where {FT<:AbstractFloat}
    return K_STEFAN(FT) * T^4
end

###############################################################################
#
# Leaf boundary layer conductance
#
###############################################################################
"""
    boundary_layer_conductance(wind::FT, width::FT) where {FT<:AbstractFloat}

Return the boundary layer conductance, given
- `wind` Wind speed
- `width` Leaf width
"""
function boundary_layer_conductance(
            wind::FT,
            width::FT
) where {FT<:AbstractFloat}
    _A::FT = FT(1.4 * 0.135)
    _B::FT = FT(0.72)

    return _A * sqrt( wind / (_B*width) )
end

###############################################################################
#
# Leaf temperature
#
###############################################################################
"""
    leaf_temperature(
                node::SPACSimple{FT},
                rad::FT,
                e_rad::FT,
                epla::FT
    ) where {FT<:AbstractFloat}
    leaf_temperature(
                node::SPACSimple{FT},
                rad::FT,
                flow::FT
    ) where {FT<:AbstractFloat}

Return leaf temperature, given
- `node` [`SPACSimple`](@ref) type struct
- `rad` Absorbed solar radiation per leaf area
- `e_rad` Emitted absorbed radiation per leaf area
- `epla` Flow rate per leaf area
- `flow` Total flow rate per basal area
"""
function leaf_temperature(
            node::SPACSimple{FT},
            rad::FT,
            e_rad::FT,
            epla::FT
) where {FT<:AbstractFloat}
    @unpack t_air,wind = node.envir;

    lambda = latent_heat_vapor(t_air) * M_H₂O(FT);
    Gr     = radiative_conductance(t_air);
    GHa    = boundary_layer_conductance(wind, node.width);
    e_lat  = lambda * epla;

    t_leaf = t_air + (rad-e_rad-e_lat) / (FT(29.3)*(Gr+GHa)) / 2;

    return t_leaf
end

function leaf_temperature(
            node::SPACSimple{FT},
            rad::FT,
            flow::FT
) where {FT<:AbstractFloat}
    lai = node.laba / node.gaba;

    e_rad = FT(0.97) * black_body_emittance(node.envir.t_air) / max(1, lai);
    epla  = flow / node.laba;

    return leaf_temperature(node, rad, e_rad, epla)
end

"""
    leaf_temperature_sunlit(
                node::SPACSimple{FT},
                rad::FT,
                f_sl::FT
    ) where {FT<:AbstractFloat}

Return leaf temperature, given
- `node` [`SPACSimple`](@ref) type struct
- `rad` Absorbed solar radiation per leaf area
- `f_sl` Total flow rate per basal area into sunlit leaves
"""
function leaf_temperature_sunlit(
            node::SPACSimple{FT},
            rad::FT,
            f_sl::FT
) where {FT<:AbstractFloat}
    e_rad  = FT(0.97) * black_body_emittance(node.envir.t_air) /
             max(1, (node.container2L).lai_sl);
    epla   = f_sl / (node.container2L).la_sl;

    return leaf_temperature(node, rad, e_rad, epla)
end

"""
    leaf_temperature_shaded(
                node::SPACSimple{FT},
                rad::FT,
                f_sh::FT
    ) where {FT<:AbstractFloat}

Return leaf temperature, given
- `node` [`SPACSimple`](@ref) type struct
- `rad` Absorbed solar radiation per leaf area
- `f_sh` Total flow rate per basal area into shaded leaves
"""
function leaf_temperature_shaded(
            node::SPACSimple{FT},
            rad::FT,
            f_sh::FT
) where {FT<:AbstractFloat}
    epla = f_sh / (node.container2L).la_sh;

    return leaf_temperature(node, rad, FT(0), epla)
end


module Classical
using LinearAlgebra
#using StaticArrays

"""
visco()

Implements a power-law dependence for the viscosity ν on the surface density Σ,

    ν = ν₀(Σ/Σ₀)ᵝ

following Grätz, Seiß & Spahn (2018).
"""
function visco(Σrel,β)
    THRESH = 0.01 # Thresholding allows us to use β<0, which would otherwise lead to infinities.
    threshold(Σ) = Σ > THRESH ? Σ : THRESH
    Σr = threshold.(Σrel)
    return Diagonal(Σr.^β)
end

"""
Potentially confusing terminology: a planetary particulate disk is called a "ring",
not a disk.
"""
mutable struct ring
#    α::Float64
#    Δx::Float64
#    Δt::Float64
#    xi, xf ::Float64
    N::Integer
    β::Float64
    ΔtMAX::Float64
    # Defined on nodes:
    X_::Array{Float64,1}
    ΔX_::Array{Float64,1}
    U_::Array{Float64,1}
    # Defined on cells:
    x_::Array{Float64,1}
    σ_::Array{Float64,1}
    Gᵐ::Any # Tridiagonal
#    μ_::Array{Float64,1}
#    νᵐ::Any
end
function ring(N=5;β=0,xf=3.0,xi=0.01)#10*xf/N)
    xtemp_ = collect(range(xi,xf,length=N+1))
    X_ = xtemp_[2:end]
    U_ = 1.0 ./ X_.^4
    ΔX_ = diff(xtemp_)
    x_ = 0.5*(xtemp_[1:end-1]+xtemp_[2:end]) # midpoints
    σ_ = ones(N)
    νᵐ = visco(σ_,β)
    Mᵐ = Diagonal(ΔX_)
    Mⁱᵐ= Diagonal(1.0./ΔX_)
    Kdiag = [1.0 ./ diff(x_)..., 0.0]
    Ksuper = -Kdiag[1:end-1]
    Ksub = zeros(N-1)
    Kᵐ = Tridiagonal(Ksub,Kdiag,Ksuper)
    Dᵐ = Tridiagonal(ones(N-1),-ones(N),zeros(N-1))
    Gᵐ = 3 * Dᵐ * Kᵐ * νᵐ
    μ_ = Mᵐ * σ_ # mass (per azimuthal length) in annulus
    ΔtVN = 0.5 * minimum(diff(X_).^2 ./ diag(νᵐ)[1:end-1] )
    ΔtADV = minimum(ΔX_ ./ U_)
    ΔtMAX = min(ΔtVN,ΔtADV)
    return ring(N,β,ΔtMAX,X_,ΔX_,U_,x_,σ_,Gᵐ)#,μ_)
end

function adjust!(r::ring)
    νᵐ = visco(r.σ_,r.β)
    Kdiag = [1.0 ./ diff(r.x_)..., 0.0]
    Ksuper = -Kdiag[1:end-1]
    Ksub = zeros(r.N-1)
    Kᵐ = Tridiagonal(Ksub,Kdiag,Ksuper)
    Dᵐ = Tridiagonal(ones(r.N-1),-ones(r.N),zeros(r.N-1))
    Gᵐ = 3 * Dᵐ * Kᵐ * νᵐ
    r.Gᵐ = Gᵐ
end

function advect!(r::ring;Δt=r.ΔtMAX)
    σ = r.σ_; U = r.U_;; ΔX = r.ΔX_
    σ[2:end]   += Δt .* (σ[1:end-1] .* U[1:end-1] ./ ΔX[2:end] )
    σ[1:end] -= Δt .* (σ[1:end] .* U[1:end] ./ ΔX[1:end] )
end

function diffuse!(r::ring;Δt=r.ΔtMAX,α=0.5)
    Gᵐ = r.Gᵐ
    N = r.N
    Aᵐ = I(N) -    α  * Δt * Gᵐ
    Bᵐ = I(N) + (1-α) * Δt * Gᵐ
    # Solve:
    r.σ_ = Aᵐ \ (Bᵐ * r.σ_)
#    r.σ_[1]   = 0.0
    r.σ_[end] = 1.0
end

function iterate!(r::ring;Δt=r.ΔtMAX,α=0.5)
    adjust!(r)
    diffuse!(r;Δt=Δt,α=α)
    advect!(r;Δt=Δt)
end

end#module


@testset "Misc" begin
  include("Misc/psibound.jl")
  include("Misc/Primes.jl")
  include("Misc/Places.jl")
  include("Misc/meataxe.jl")
  include("Misc/stable_subgroups.jl")
  include("Misc/NumberField.jl")
  include("Misc/Poly.jl")
  include("Misc/Localization.jl")
end


### A Pluto.jl notebook ###
# v0.12.18

using Markdown
using InteractiveUtils

# ╔═╡ 485292d8-2bde-11eb-0a97-6b44d54596dd
using Plots

# ╔═╡ fc8ff7b8-2be1-11eb-0c9c-b9d2ee7c2880
using Markdown, Images

# ╔═╡ 2411c6ca-2bdd-11eb-050c-0399b3b0d7af
md"""
# Project 1: images and cellular automata

We wrap up the day by exploring convolutions and similar operations on 1-D and 2-D (or even n-D!) matrices. Departing from some basic building blocks we will cover signal processing, image processing and cellular automata!

Learning goals:
- code reuse;
- speed and performance (including optimizations);
- learning to plot and make animations.
"""

# ╔═╡ 3d9c3fea-2bde-11eb-0f09-cf11dcb07c0d
md"""
## 1-D operations
"""

# ╔═╡ e1147d4e-2bee-11eb-0150-d7af1f51f842
md"""
### 1-D convolution

Convolution:

$$y_i = \sum_j x_{j-m} k_{j}$$

Boundary effects.
"""

# ╔═╡ 6115f95c-2bde-11eb-01ce-3d17b78aa8a9
noisy_signal = 0:0.1:10 .|> x -> x * sin(x) + rand()  # use covid cases?

# ╔═╡ 8a996336-2bde-11eb-10a3-cb0046ed5de9
plot(noisy_signal)

# ╔═╡ a1f75f4c-2bde-11eb-37e7-2dc342c7032a
function convolve_1d!(x::Vector, out::Vector, w::Vector)
	n = length(x)
	m = div(length(w), 2)
	@assert length(w) % 2 == 1 "length of `w` has to be odd!"
	@assert n == length(out) "`x` and `out` have to be of the same length!"
	fill!(out, 0.0)
	for (i, xj) in enumerate(x)
		for (j, wj) in enumerate(w)
			k = clamp(i + j - m, 1, n)
			out[i] += w[j] * x[k]
		end
	end
	return out
end

# ╔═╡ 906841ee-2be1-11eb-03f4-49b5a8b47a6b
convolve_1d(x::Vector, w::Vector) = convolve_1d!(x, similar(x), w)

# ╔═╡ ef84b6fe-2bef-11eb-0943-034b8b90c4c4
function uniform_weights(m)
	return ones(2m+1) / (2m+1)
end

# ╔═╡ 294140a4-2bf0-11eb-22f5-858969a4640d
function triangle_weigths(m)
	w = zeros(2m+1)
	for i in 1:(m+1)
		w[i] = i
		w[end-i+1] = i
	end
	w ./= sum(w)
	return w
end

# ╔═╡ fa911a76-2be3-11eb-1c85-3df313eb0fcb
w = triangle_weigths(2)

# ╔═╡ 20c46656-2bf0-11eb-2dc4-af2cc2161f6a
scatter(w, xlabel="j", ylabel="weight")

# ╔═╡ cb5f4a20-2be8-11eb-0000-c59f23a024ef
signal_c = convolve_1d(noisy_signal, w)

# ╔═╡ eb5f8062-2be1-11eb-1e96-1f89be62f3b0
plot(signal_c)

# ╔═╡ af33214a-2be7-11eb-34f5-0d046c9db735
function generalized_convolve_1d!(x::Vector, out::Vector, m, f)
	n = length(x)
	@assert n == length(out) "`x` and `out` have to be of the same length!"
	fill!(out, 0.0)
	for (i, xj) in enumerate(x)
		if  m < i < n - m
			out[i] = f(@view(x[(i-m):(i+m)]))
		else
			out[i] = x[i]
		end
	end
	return out
end

# ╔═╡ 4263e920-2bf1-11eb-1db1-ed644a480a2d

# ╔═╡ 21ce0552-2be9-11eb-2359-152a7c75cc2e
vec_conv(x_s) = sum(x_s .* w)

# ╔═╡ 9bfd0e60-2bf1-11eb-3de4-03308da53e73
mean(x) = sum(x) / length(x)

# ╔═╡ 04a8eb54-2be4-11eb-06af-890cebf38da4
signal_gc = generalized_convolve_1d!(noisy_signal, similar(noisy_signal), 2, minimum)

# ╔═╡ b94aae80-2be8-11eb-0c88-cb3d2820e9ef
plot(signal_gc)

# ╔═╡ b7ba4ed8-2bf1-11eb-24ee-731940d1c29f
md"""
### Sliding window: secundary structure prediction

Chou-Fasman method
"""

# ╔═╡ c924a5f6-2bf1-11eb-3d37-bb63635624e9
spike_sars2 = "MFVFLVLLPLVSSQCVNLTTRTQLPPAYTNSFTRGVYYPDKVFRSSVLHSTQDLFLPFFSNVTWFHAIHVSGTNGTKRFDNPVLPFNDGVYFASTEKSNIIRGWIFGTTLDSKTQSLLIVNNATNVVIKVCEFQFCNDPFLGVYYHKNNKSWMESEFRVYSSANNCTFEYVSQPFLMDLEGKQGNFKNLREFVFKNIDGYFKIYSKHTPINLVRDLPQGFSALEPLVDLPIGINITRFQTLLALHRSYLTPGDSSSGWTAGAAAYYVGYLQPRTFLLKYNENGTITDAVDCALDPLSETKCTLKSFTVEKGIYQTSNFRVQPTESIVRFPNITNLCPFGEVFNATRFASVYAWNRKRISNCVADYSVLYNSASFSTFKCYGVSPTKLNDLCFTNVYADSFVIRGDEVRQIAPGQTGKIADYNYKLPDDFTGCVIAWNSNNLDSKVGGNYNYLYRLFRKSNLKPFERDISTEIYQAGSTPCNGVEGFNCYFPLQSYGFQPTNGVGYQPYRVVVLSFELLHAPATVCGPKKSTNLVKNKCVNFNFNGLTGTGVLTESNKKFLPFQQFGRDIADTTDAVRDPQTLEILDITPCSFGGVSVITPGTNTSNQVAVLYQDVNCTEVPVAIHADQLTPTWRVYSTGSNVFQTRAGCLIGAEHVNNSYECDIPIGAGICASYQTQTNSPRRARSVASQSIIAYTMSLGAENSVAYSNNSIAIPTNFTISVTTEILPVSMTKTSVDCTMYICGDSTECSNLLLQYGSFCTQLNRALTGIAVEQDKNTQEVFAQVKQIYKTPPIKDFGGFNFSQILPDPSKPSKRSFIEDLLFNKVTLADAGFIKQYGDCLGDIAARDLICAQKFNGLTVLPPLLTDEMIAQYTSALLAGTITSGWTFGAGAALQIPFAMQMAYRFNGIGVTQNVLYENQKLIANQFNSAIGKIQDSLSSTASALGKLQDVVNQNAQALNTLVKQLSSNFGAISSVLNDILSRLDKVEAEVQIDRLITGRLQSLQTYVTQQLIRAAEIRASANLAATKMSECVLGQSKRVDFCGKGYHLMSFPQSAPHGVVFLHVTYVPAQEKNFTTAPAICHDGKAHFPREGVFVSNGTHWFVTQRNFYEPQIITTDNTFVSGNCDVVIGIVNNTVYDPLQPELDSFKEELDKYFKNHTSPDVDLGDISGINASVVNIQKEIDRLNEVAKNLNESLIDLQELGKYEQYIKWPWYIWLGFIAGLIAIVMVTIMLCCMTSCCSCLKGCCSCGSCCKFDEDDSEPVLKGVKLHYT"

# ╔═╡ 4e6dedf0-2bf2-11eb-0bad-3987f6eb5481
md"""
### Elementary cellular automata
"""

# ╔═╡ 625c4e1e-2bf3-11eb-2c03-193f7d013fbe
function nextstate(l::Bool, s::Bool, r::Bool, rule::UInt8)
	bitstring(rule)[8-(4r+2s+1r)] == '1';
end

# ╔═╡ b03c60f6-2bf3-11eb-117b-0fc2a259ffe6
rule = 110 |> UInt8

# ╔═╡ a9ffc05c-2bf3-11eb-0771-b3732ed7d68d
md"""
Table for rule $rule:

| l      | s     | r    | result |
| :--------: | :-----: | :-----: | :--: |
| 10 | $true | $true | $false |

CAN BE DONE IN TABLE?

"""

# ╔═╡ 5f97da58-2bf4-11eb-26de-8fc5f19f02d2
Dict((l, s, r) => nextstate(l, s, r, rule)
	for l in [true, false]
	for s in [true, false]
	for r in [true, false]
				)

# ╔═╡ 924461c0-2bf3-11eb-2390-71bad2541463
function update1dca!(xnew, x, rule::UInt8)
	n = length(x)
	xnew[1] = nextstate(x[end], x[1], x[2], rule)
	xnew[end] = nextstate(x[end-1], x[end], x[1], rule)
	for i in 2:n-1
		xnew[i] = nextstate(x[i-1], x[i], x[i+1], rule)
	end
	return xnew
end

# ╔═╡ 21440956-2bf5-11eb-0860-11127d727282
next1dca(x, rule::UInt8) = update1dca!(similar(x), x, rule::UInt8)

# ╔═╡ 405a1036-2bf5-11eb-11f9-a1a714dbf7e1
x0_ca = rand(Bool, 200)

# ╔═╡ 6405f574-2bf5-11eb-3656-d7b9c94f145a
next1dca(x0_ca, rule)

# ╔═╡ 756ef0e0-2bf5-11eb-107c-8d1c65eacc45
"""
    simulate(x0, rule; nsteps::Integer=100)

Simulate `nsteps` time steps according to `rule` with `X0` as the initial condition.
Returns a matrix X, where the rows are the state vectors at different time steps.
"""
function simulate(x0, rule::UInt8; nsteps::Integer=100)
	n = length(x0)
    X = zeros(Bool, nsteps+1, n)
	X[1,:] = x0
	for t in 1:nsteps
		x = @view X[t,:]
		xnew = @view X[t+1,:]
		update1dca!(xnew, x, rule)
	end
    return X
end

# ╔═╡ e1dd7abc-2bf5-11eb-1f5a-0f46c7405dd5
X = simulate(x0_ca, rule; nsteps=200)

# ╔═╡ 9ab79ad4-2bf6-11eb-1d0e-bb6434b520a3
heatmap(X)

# ╔═╡ 9dbb9598-2bf6-11eb-2def-0f1ddd1e6b10
show_ca(X) = Gray.(X)

# ╔═╡ fb9a97d2-2bf5-11eb-1b92-ab884f0014a8
show_ca(X)

# ╔═╡ 4810a052-2bf6-11eb-2e9f-7f9389116950
all_cas = Dict(rule=>show_ca(simulate(x0_ca, rule; nsteps=200)) for rule in UInt8(0):UInt8(251))

# ╔═╡ 47e3c2f8-2bf6-11eb-1fbf-d5f3fc899056
all_cas[0]

# ╔═╡ aea84d6c-2be3-11eb-2fe7-63e0d0c28507
keep_working(text=md"The answer is not quite right.") = Markdown.MD(Markdown.Admonition("correct", "Keep working on it!", [text]))

# ╔═╡ dc2230ac-2be3-11eb-24d7-f980be57d697
keep_working()

# ╔═╡ 41e7ef30-2be4-11eb-1b9a-e7f7eae7a115
philip_file = download("https://i.imgur.com/VGPeJ6s.jpg")

# ╔═╡ 473c581c-2be5-11eb-1ddc-2d30a3468c8a
decimate(image, ratio=5) = image[1:ratio:end, 1:ratio:end]

# ╔═╡ 4c9d2e0e-2be4-11eb-2fb3-377f29141076
philip = let
	original = Images.load(philip_file)
	decimate(original, 8)
end

# ╔═╡ 608e930e-2bfc-11eb-09f2-29600cfba1e6
function convolve_2d!(M::AbstractMatrix, out::AbstractMatrix, K::AbstractMatrix)
	n_rows, n_cols = size(M)
	fill!(out, 0.0)
	m = div(size(K, 1), 2)
	for i in 1:n_rows
		for j in 1:n_cols
			for k in -m:m
				for l in -m:m
					out[i,j] += M[clamp(i+k, 1, n_rows), clamp(j+l, 1, n_cols)] * K[k+m+1,l+m+1]
				end
			end
		end
	end
	return out
end

# ╔═╡ 245deb30-2bfe-11eb-3e47-6b7bd5d73ccf
function gaussian_kernel(m; σ)
	K = [exp(-(x^2 + y^2) / σ^2) for x in -m:m, y in -m:m]
	K ./= sum(K)
	return K
end

# ╔═╡ 6278ae28-2bfe-11eb-3c36-9bf8cc703a3a
K = gaussian_kernel(1, σ=10)

# ╔═╡ 6c40b89c-2bfe-11eb-3593-9d9ef6f45b80
heatmap(K)

# ╔═╡ 73c3869a-2bfd-11eb-0597-29cd6c1c90db
convolve_2d(M, K) = convolve_2d!(M, similar(M), K)

# ╔═╡ a34f1e4c-2bfd-11eb-127e-295a7039c301
convolve_2d(randn(1000, 1000), K)

# ╔═╡ c80e4362-2bfe-11eb-2055-5fbf167be551
K_sharpen = [0 -1 0;
			-1 5 -1;
			0 -1 0]

# ╔═╡ 84880dfc-2bfd-11eb-32b7-47750e1c696b
function convolve_image(M, K)
	Mred = convolve_2d(red.(M), K)
	Mgreen = convolve_2d(green.(M), K)
	Mblue = convolve_2d(blue.(M), K)
	return RGB.(Mred, Mgreen, Mblue)
end

# ╔═╡ 8c62855a-2bff-11eb-00f5-3702e142f76f
const Gx = [1 0 -1;
		   2 0 -2;
		   1 0 -1]

# ╔═╡ ae8d6258-2bff-11eb-0182-afc33d9d4efc
const Gy = [1 2 1;
		    0 0 0;
		   -1 -2 -1]

# ╔═╡ 746d3578-2bff-11eb-121a-f3d17397a49e
function edge_detection(M)
	M = Gray.(M) |> Matrix
	return convolve_2d(M, Gx).^2 + convolve_2d(M, Gy).^2
end

# ╔═╡ f41919a4-2bff-11eb-276f-a5edd5b374ac
edge_detection(philip)

# ╔═╡ e1f23724-2bfd-11eb-1063-1d59909bcacc
convolve_image(philip, K)

# ╔═╡ 55bfb46a-2be5-11eb-2be6-1bc153db11ac
RGB.(red.(philip), green.(philip), blue.(philip))

# ╔═╡ 04f3c460-2bed-11eb-1039-238bfb3ec445
images = [rand([RGB(0, 0, 1),RGB(1, 0, 0)], 50, 100) for i in 1:100]

# ╔═╡ b3eed0e0-2bed-11eb-21d3-19323b600edf
images[1]

# ╔═╡ 2a3b0be4-2bee-11eb-3e26-f1d850c752b6
@gif for i ∈ 1:100
	plot(rand([RGB(0, 0, 1),RGB(1, 0, 0)], 100, 100))
end

# ╔═╡ Cell order:
# ╠═2411c6ca-2bdd-11eb-050c-0399b3b0d7af
# ╠═3d9c3fea-2bde-11eb-0f09-cf11dcb07c0d
# ╠═e1147d4e-2bee-11eb-0150-d7af1f51f842
# ╠═6115f95c-2bde-11eb-01ce-3d17b78aa8a9
# ╠═8a996336-2bde-11eb-10a3-cb0046ed5de9
# ╠═485292d8-2bde-11eb-0a97-6b44d54596dd
# ╠═a1f75f4c-2bde-11eb-37e7-2dc342c7032a
# ╠═fa911a76-2be3-11eb-1c85-3df313eb0fcb
# ╠═20c46656-2bf0-11eb-2dc4-af2cc2161f6a
# ╠═906841ee-2be1-11eb-03f4-49b5a8b47a6b
# ╠═cb5f4a20-2be8-11eb-0000-c59f23a024ef
# ╠═eb5f8062-2be1-11eb-1e96-1f89be62f3b0
# ╠═ef84b6fe-2bef-11eb-0943-034b8b90c4c4
# ╠═294140a4-2bf0-11eb-22f5-858969a4640d
# ╠═af33214a-2be7-11eb-34f5-0d046c9db735
# ╠═4263e920-2bf1-11eb-1db1-ed644a480a2d
# ╠═21ce0552-2be9-11eb-2359-152a7c75cc2e
# ╠═9bfd0e60-2bf1-11eb-3de4-03308da53e73
# ╠═04a8eb54-2be4-11eb-06af-890cebf38da4
# ╠═b94aae80-2be8-11eb-0c88-cb3d2820e9ef
# ╠═b7ba4ed8-2bf1-11eb-24ee-731940d1c29f
# ╠═c924a5f6-2bf1-11eb-3d37-bb63635624e9
# ╠═4e6dedf0-2bf2-11eb-0bad-3987f6eb5481
# ╠═625c4e1e-2bf3-11eb-2c03-193f7d013fbe
# ╠═b03c60f6-2bf3-11eb-117b-0fc2a259ffe6
# ╠═a9ffc05c-2bf3-11eb-0771-b3732ed7d68d
# ╠═5f97da58-2bf4-11eb-26de-8fc5f19f02d2
# ╠═924461c0-2bf3-11eb-2390-71bad2541463
# ╠═21440956-2bf5-11eb-0860-11127d727282
# ╠═405a1036-2bf5-11eb-11f9-a1a714dbf7e1
# ╠═6405f574-2bf5-11eb-3656-d7b9c94f145a
# ╠═756ef0e0-2bf5-11eb-107c-8d1c65eacc45
# ╠═e1dd7abc-2bf5-11eb-1f5a-0f46c7405dd5
# ╠═9ab79ad4-2bf6-11eb-1d0e-bb6434b520a3
# ╠═9dbb9598-2bf6-11eb-2def-0f1ddd1e6b10
# ╠═fb9a97d2-2bf5-11eb-1b92-ab884f0014a8
# ╠═4810a052-2bf6-11eb-2e9f-7f9389116950
# ╠═47e3c2f8-2bf6-11eb-1fbf-d5f3fc899056
# ╠═fc8ff7b8-2be1-11eb-0c9c-b9d2ee7c2880
# ╠═aea84d6c-2be3-11eb-2fe7-63e0d0c28507
# ╠═dc2230ac-2be3-11eb-24d7-f980be57d697
# ╠═41e7ef30-2be4-11eb-1b9a-e7f7eae7a115
# ╠═4c9d2e0e-2be4-11eb-2fb3-377f29141076
# ╠═473c581c-2be5-11eb-1ddc-2d30a3468c8a
# ╠═608e930e-2bfc-11eb-09f2-29600cfba1e6
# ╠═245deb30-2bfe-11eb-3e47-6b7bd5d73ccf
# ╠═6278ae28-2bfe-11eb-3c36-9bf8cc703a3a
# ╠═6c40b89c-2bfe-11eb-3593-9d9ef6f45b80
# ╠═73c3869a-2bfd-11eb-0597-29cd6c1c90db
# ╠═a34f1e4c-2bfd-11eb-127e-295a7039c301
# ╠═c80e4362-2bfe-11eb-2055-5fbf167be551
# ╠═84880dfc-2bfd-11eb-32b7-47750e1c696b
# ╠═8c62855a-2bff-11eb-00f5-3702e142f76f
# ╠═ae8d6258-2bff-11eb-0182-afc33d9d4efc
# ╠═746d3578-2bff-11eb-121a-f3d17397a49e
# ╠═f41919a4-2bff-11eb-276f-a5edd5b374ac
# ╠═e1f23724-2bfd-11eb-1063-1d59909bcacc
# ╠═55bfb46a-2be5-11eb-2be6-1bc153db11ac
# ╠═04f3c460-2bed-11eb-1039-238bfb3ec445
# ╠═b3eed0e0-2bed-11eb-21d3-19323b600edf
# ╠═2a3b0be4-2bee-11eb-3e26-f1d850c752b6


using BenchmarkTools
import LinearAlgebra, RecursiveFactorization

BenchmarkTools.DEFAULT_PARAMETERS.seconds = 0.5

luflop(m, n) = n^3÷3 - n÷3 + m*n^2
luflop(n) = luflop(n, n)

bas_mflops = Float64[]
rec_mflops = Float64[]
ns = 50:50:800
for n in ns
    A = rand(n, n)
    bt = @belapsed LinearAlgebra.lu!($(copy(A)))
    rt = @belapsed RecursiveFactorization.lu!($(copy(A)))
    push!(bas_mflops, luflop(n)/bt/1e9)
    push!(rec_mflops, luflop(n)/rt/1e9)
end

using Plots
plt = plot(ns, bas_mflops, legend=:bottomright, lab="OpenBLAS", title="LU Factorization Benchmark", marker=:auto, dpi=150)
plot!(plt, ns, rec_mflops, lab="RecursiveFactorization", marker=:auto)
xaxis!(plt, "size (N x N)")
yaxis!(plt, "GFLOPS")
savefig("lubench.png")
savefig("lubench.pdf")


using MeanFilters
using Test
using MeanFilters: meanfilter!
using ImageFiltering: imfilter, NA
using ImageFiltering.OffsetArrays: OffsetArray

@testset "1d" begin
    @test meanfilter([1,2,3,4], -1:1) ≈ [1.5, 2, 3, 3.5]
    @test meanfilter([1,2,3], (0:1,)) == [1.5, 2.5, 3.0]
    @test meanfilter([1,4], (0:1,)) == [2.5, 4]
    @test meanfilter([3,6], (0:1,)) == [4.5, 6]
    @test meanfilter([0,0,1,0,0], (-1:1,)) ≈ [0, 1/3, 1/3, 1/3, 0]
    @test meanfilter([0,0,1,0,0], (-1:0,)) ≈ [0,   0, 1/2, 1/2, 0]
    @test meanfilter([0,0,1,0,0], (-1:2,)) ≈ [1/3, 1/4, 1/4, 1/3, 0]
    @test meanfilter([1,0,0,0,0], (-1:2,)) ≈ [1/3, 1/4, 0, 0, 0]
    @test meanfilter([1,2,], (-0:0,)) ≈ [1,2]

    @test_throws ArgumentError meanfilter([1,2,3], (1:2, 2:3))
end

@testset "2d" begin
    arr = [1 2 3; 4 5 6]
    arr = randn(2,3)
    expected = copy(arr)

    @test meanfilter(arr, (1,1)) ≈ expected
    out = similar(arr)
    @test meanfilter!(out, arr, (1,1)) ≈ expected
    @test out ≈ expected

    arr = [1 2 3; 4 5 6]
    @test meanfilter([1 2 3; 4 5 6], (0:1, 0:0)) == [2.5 3.5 4.5; 4 5 6]
end

@testset "against ImageFiltering.imfilter" begin

    function meanfilter_reference(arr, window)
        ker = fill(1/prod(map(length,window)), window...)
        imfilter(arr, ker, NA())
    end

    for arr in [randn(1), randn(1,2), randn(1,2,3), randn(1,2,3,4), randn(1,2,3,4,5)]
        window = Tuple(0:0 for _ in 1:ndims(arr))
        @test meanfilter(arr, window) ≈ arr
        @test meanfilter(arr, window) ≈ meanfilter_reference(arr, window)
    end

    for ndims in 1:2
        for _ in 1:100
            #ndims = rand(1:2)
            arr = randn(rand(1:10, ndims)...)
            window = Tuple(-rand(0:10):rand(0:10) for _ in 1:ndims)
            @test meanfilter(arr, window) ≈ meanfilter_reference(arr, window)
        end
    end
    for ndims in 3:5
        #ndims = rand(1:2)
        arr = randn(rand(1:10, ndims)...)
        window = Tuple(-rand(0:2):rand(0:2) for _ in 1:ndims)
        @test meanfilter(arr, window) ≈ meanfilter_reference(arr, window)
    end
end


## (De)compose a polynomial into even and odd degrees
function evenodd_coeffs(p)
    l = length(p); ## degree + 1
    ## For the zero polynomial, 'odd' would be a 0-element array
    if l < 1
        even = [0]
        odd  = [0]
    else
        even = get_coeffs(p, 0:2:l)
        odd  = get_coeffs(p, 1:2:l)
        # even = reverse([coeff(p, i) for i in 0:2:l])
        # odd  = reverse([coeff(p, i) for i in 1:2:l])
    end
    return even, odd
end

evenodd_polys(p) = parent(p).(evenodd_coeffs(p))

function mix_evenodd(even, odd)
    n = length(even) + length(odd)
    out = Vector{eltype(even)}(undef, n)
    out[1:2:end] .= even
    out[2:2:end] .= odd
    return out
end

mix_evenodd(pe::PolyElem, po::PolyElem) = mix_evenodd(get_coeffs(pe), get_coeffs(po))

principaldeterminantsof(M) = i -> det(M[1:i,1:i])

# Structure for the Hurwitz matrix of a polynomial.
# Inherits matrix structure from MatElem general type.
struct HurwitzMatrix{T} <: MatElem{T}
    p::PolyElem{T}
    # HurwitzMatrix(p::PolyElem{T}) where T = new{T}(parent(p)(normalize_Hurwitz(p)))
end

AA.nrows(p::HurwitzMatrix) = degree(p.p)
AA.ncols(p::HurwitzMatrix) = AA.nrows(p)
AA.length(p::HurwitzMatrix) = AA.nrows(p)^2
AA.isempty(p::HurwitzMatrix) = false
AA.base_ring(p::HurwitzMatrix) = AA.base_ring(p.p)

Base.show(io::IO, p::HurwitzMatrix) = print(io, p.p)
Base.show(io::IO, ::MIME"text/plain", p::HurwitzMatrix) =
    print(io, "HurwitzMatrix for poly (coeffs $(base_ring(p.p))) :\n", p)

Base.convert(::Type{AbstractMatrix}, p::HurwitzMatrix) = Matrix(p)

Base.IndexStyle(::Type{<:HurwitzMatrix}) = IndexCartesian()
function Base.getindex(p::HurwitzMatrix, i::Int, j::Int)
    n = degree(p.p)
    # For i > deg(p), we already have `coeff(p,i)=0`
    return -1 < (n - 2 * i + j) ? coeff(p.p, n - 2 * i + j) : zero(base_ring(p.p))
end

# Functions computing Hurwitz something.
function normalize_Hurwitz(p)
    even, odd = normalize.(evenodd_polys(p))
    return parent(p)(mix_evenodd(even, odd))
end

function Hurwitz_genericmatrix(p)
    n = degree(p)
    R, vars = PolynomialRing(base_ring(p), ["a$i" for i in 0:(n - 1)])
    S, t = PolynomialRing(R, "t")
    Q = S([vars; one(R)])
    H = []
    for i in Hurewitz_odd_indices(p)
        D = Hurwitzdeterminants(Q)
        subs(D, vars .=> coeffs(p))
    end
end

Hurwitzmatrix(p) = HurwitzMatrix(p)[1:degree(p), 1:degree(p)]
Hurwitzmatrix_sylvester(p) = sylvester_matrix(evenodd_polys(p)...)

Hurwitzdeterminants(p) = principaldeterminantsof(HurwitzMatrix(p))

Hurwitz_odd_indices(p) = (degree(p) - 1):-2:2

Hurwitz_odd_determinants(p) = reverse(Hurwitzdeterminants(p).(Hurwitz_odd_indices(p)))

Hurwitz_subresultants(p) = Hurwitz_subresultants_signs(p) .* AA.subresultants_ducos(evenodd_polys(p)...)
Hurwitzdeterminants_subresultant(p) = AA.leading_coefficient.(Hurwitz_subresultants(p))

## The signs of the antidiagonal.
function Hurwitz_subresultants_signs(p)
    n = length(Hurwitz_odd_indices(p))
    out = ones(Int, n)
    goes_neg = iseven(degree(p)) ? [1,2] : [2,3]
    neg_indices = filter(i -> i % 4 in goes_neg, 1:n)
    out[neg_indices] .= -1
    # for i in .%(1:n,4)
    #     if i goes_neg
    #         out[i] = -1
    #     end
    # end
    return out
end

## Numerical results form test_subresultant_ducos
## Deg | length | signs
##  22 |     10 | [-1, -1, 1, 1, -1, -1, 1, 1, -1, -1]
##  20 |      9 | [-1, -1, 1, 1, -1, -1, 1, 1, -1]
##  18 |      8 | [-1, -1, 1, 1, -1, -1, 1, 1]
##
##  21 |     10 | [1, -1, -1, 1, 1, -1, -1, 1, 1, -1]
##  19 |      9 | [1, -1, -1, 1, 1, -1, -1, 1, 1]
##  17 |      8 | [1, -1, -1, 1, 1, -1, -1, 1]

#############################################
#############################################
################################### Stability

## See https://sci-hub.se/https://doi.org/10.1017/S0305004100049872
## Barnett 1971, Proceedings of the Cambridge Philosophical Society, 70(02), 269.
##
##

# Check the signs of the Hurwitz determinants for the Ruth-Hurwitz criteria
# getsigns(D) = unique(sign.(reshape(hcat, coeffs.(coeffs(D)))))
function signs_stabilitymatrices(io,
                                 J;
                                 mindeg::Integer=size(J, 1) - rank(J),
                                 unique_signs::Function=x -> unique(sign.(coeffs(x))))
    P, x = PolynomialRing(base_ring(J), "x")
    p = divexact(charpoly(P, J), x^mindeg)
    L = []
    # println(p)
    range = Hurwitz_odd_indices(p)
    # println(collect(range))
    write(io, "==============================================\n")
    write(io, "==============================================\n")
    write(io, "$(length(range)) expressions will be studied\n")
    for i in reverse(range)
        # This poly belongs to base_ring(J), which is defined in the scope calling this function
        # @computing H = Hurwitzdeterminants(p)(i) "$i-th Hurwitz determinant"
        H = Hurwitzdeterminants(p)(i)
        write(io, "\n-- $i th determinant --\n")
        signs = unique_signs(H)
        write(io, "$(signs)\n")
        if -1 in signs
            push!(L, H)
        end
    end
    write(io, "==============================================\n")
    write(io, "==============================================\n")
    return L
end

function signs_stabilitymatrices(J;
                                 mindeg::Integer=size(J, 1) - rank(J),
                                 unique_signs::Function=x -> unique(sign.(coeffs(x))))
    signs_stabilitymatrices(IOBuffer(), J, mindeg=mindeg, unique_signs=unique_signs)
end

function signs_stabilitymatrices(file::String,
                                 J;
                                 netname=nothing,
                                 mode="a", # append
                                 mindeg::Integer=size(J, 1) - rank(J),
                                 unique_signs::Function=x -> unique(sign.(coeffs(x))))
    open(file, mode) do io
        if !(isnothing(netname))
            write(io, "==============================================\n")
            write(io, "==============================================\n")
            write(io, "$netname\n")
            write(io, "==============================================\n")
            write(io, "==============================================\n")
        end
        signs_stabilitymatrices(io, J, mindeg=mindeg, unique_signs=unique_signs)
    end
end


# Cauchy distribution

export Cauchy, HalfCauchy

@parameterized Cauchy(μ,σ) ≃ (1/π) * Lebesgue(ℝ)

@kwstruct Cauchy()
@kwstruct Cauchy(μ)
@kwstruct Cauchy(σ)
@kwstruct Cauchy(μ,σ)
@kwstruct Cauchy(ω)
@kwstruct Cauchy(μ,ω)

for N in AFFINEPARS
    @eval begin
        proxy(d::Cauchy{$N}) = affine(params(d), Cauchy())
        logdensity(d::Cauchy{$N}, x) = logdensity(proxy(d), x)
        basemeasure(d::Cauchy{$N}) = basemeasure(proxy(d))
    end
end

# @affinepars Cauchy

function logdensity(d::Cauchy{()} , x)
    return -log1p(x^2)
end

function density(d::Cauchy{()} , x)
    return inv(1 + x^2)
end

Base.rand(rng::AbstractRNG, T::Type, μ::Cauchy{()}) = randn(rng, T) / randn(rng, T)

≪(::Cauchy, ::Lebesgue{X}) where X <: Real = true

TV.as(::Cauchy) = asℝ

@half Cauchy

HalfCauchy(σ) = HalfCauchy(σ=σ)

distproxy(d::Cauchy{()}) = Dists.Cauchy()
distproxy(d::Cauchy{(:μ,)}) = Dists.Cauchy(d.μ, 1.0)
distproxy(d::Cauchy{(:σ,)}) = Dists.Cauchy(0.0, d.σ)
distproxy(d::Cauchy{(:μ,:σ)}) = Dists.Cauchy(d.μ, d.σ)
distproxy(d::Cauchy{(:ω,)}) = Dists.Cauchy(0.0, inv(d.ω))
distproxy(d::Cauchy{(:μ,:ω)}) = Dists.Cauchy(d.μ, inv(d.ω))


using Gadfly

set_default_plot_size(6inch, 3inch)

plot(x=[0.236033, 0.346517, 0.312707, 0.00790928, 0.488613, 0.210968, 0.951916, 0.999905, 0.251662, 0.986666],
     y=[0.555751, 0.437108, 0.424718, 0.773223, 0.28119, 0.209472, 0.251379, 0.0203749, 0.287702, 0.859512],
     color=[1.07999, 1.89723, 2.39539, 1.32126, 2.11978, 1.90576, 1.08094, 2.33541, 1.08941, 1.73882],
     Scale.color_log(minvalue=1))


import DoubleFloats: Double64

issquare(A::AbstractMatrix) = isequal(size(A)...)

"""
    row_scaling!(A, D::AbstractVector, tol=maximum(size(A))^2 * eps(real(eltype(A))))

Divide each row of `A` by its ∞-norm. This minimizes the ∞ condition number
for all possible scalings. In order to avoid scaling near zero rows back to
1 we only scale a row if its ∞-norm is larger than `tol`.
"""
function row_scaling!(A::AbstractMatrix{T}, D::AbstractVector, tol=max(size(A)...)^2 * eps(real(T))) where {T}
    D .= 0.0
    d_max = convert(real(T), -Inf)
    @inbounds for j=1:size(A, 2), i=1:size(A, 1)
        D[i] = @fastmath max(D[i], abs(A[i,j]))
        d_max = @fastmath max(d_max, D[i])
    end

    # In the case that the row norms are all less than 1 we have a lower bound
    # the maximal row norm
    # To avoid that we scale a zero matrix, we need a tol for wich we don't scale any more
    bound = clamp(d_max, tol, 1.0)
    D .= max.(D, bound)

    @inbounds for j=1:size(A, 2), i=1:size(A, 1)
        A[i, j] /= D[i]
    end
    A
end

@enum ActiveFactorization begin
    QR_FACTORIZATION
    LU_FACTORIZATION
end

"""
    Jacobian{T, <:Factorization}

Data structure holding a Jacobian matrix `J` with it's factorization.
Additional contains a vector `D` of scalings which row equilibarate `J`.

    Jacobian(J)

Setup a Jacobian with Jacobian matrices of the form `J`.
"""
mutable struct Jacobian{T}
    J::Matrix{T} # Jacobian matrix
    D::Vector{Float64} # Inverse of scaling factors D
    # lu is only there for square systems
    lu::Union{Nothing, LinearAlgebra.LU{T,Matrix{T}}} # LU Factorization of D * J
    qr::LinearAlgebra.QRPivoted{T,Matrix{T}} # Pivoted QR Factorization of D * J
    active_factorization::ActiveFactorization
    b::Vector{T} # Vector for overdetermined systems
    r::Vector{T} # Vector for iterative refinement
    perm::Vector{Int} # work copy of the permutation in the qr
    qr_work::Vector{ComplexF64} # preallocated memory for pivoted qr
    qr_rwork::Vector{Float64} # preallocated memory for pivoted qr
    ormqr_work::Vector{ComplexF64}
    # Numerical Informations
    corank::Int # The numerical corank
    corank_proposal::Int
    cond::Float64 # Estimate of the Jacobian
    # The relative number of digits lost during the solution of the linear systems
    # in Newton's method. See `solve_with_digits_lost!` in utilities/linear_algebra.jl
    # for how this is computed.
    digits_lost::Union{Nothing,Float64}
end

function Jacobian(A::AbstractMatrix, corank::Int=0)
    m, n = size(A)
    lu = m ≠ n ? nothing : LinearAlgebra.lu!(Random.randn!(complex.(float.(A))))

    work = Vector{ComplexF64}(undef, 1)
    ormqr_work = Vector{ComplexF64}(undef, 1)
    rwork = Vector{Float64}(undef, 2n)
    jpvt = zeros(BlasInt, n)
    B = ComplexF64.(A)
    tau = similar(B, min(m, n))
    qr = LinearAlgebra.QRPivoted(geqp3!(B, jpvt, tau, work, rwork)...)
    active_factorization = lu === nothing ? QR_FACTORIZATION : LU_FACTORIZATION
    J = copy(B)
    D = ones(m)
    r = similar(J, size(J, 1))
    b = copy(r)
    perm = zeros(Int, length(qr.p))
    cond = 1.0
    digits_lost = nothing
    Jacobian(J, D, lu, qr, active_factorization, b, r, perm, work, rwork, ormqr_work,
        corank, 0, cond, digits_lost)
end

function update_rank!(Jac::Jacobian)
    Jac.corank = Jac.corank_proposal
    Jac
end

function reset!(jacobian::Jacobian)
    jacobian.cond = 1.0
    jacobian.corank = 0
    jacobian.corank_proposal = 0
    jacobian.digits_lost = nothing
    jacobian
end

"""
    update!(jacobian, J, corank::Int=jacobian.corank)

Update the `jacobian` struct with the new Matrix `J`.
"""
function update!(Jac::Jacobian, J::AbstractMatrix; update_infos::Bool=false)
    copyto!(Jac.J, J)
    updated_jacobian!(Jac; update_infos=update_infos)
    Jac
end

"""
    update_jacobian!(jacobian)

This computes a factorization of the stored Jacobian matrix.
Call this instead of `update!` if `jacobian.J` got updated.
"""
function updated_jacobian!(Jac::Jacobian{ComplexF64}; update_infos::Bool=false) where {T}
    if !update_infos && issquare(Jac.J) && Jac.corank == 0
        Jac.lu !== nothing || return Jac
        copyto!(Jac.lu.factors, Jac.J)
        lu_factorization!(Jac.lu.factors, nothing, Jac.lu.ipiv)
        Jac.active_factorization = LU_FACTORIZATION
    else
        copyto!(Jac.qr.factors, Jac.J)

        m, n = size(Jac.J)
        if m == n
            # only apply row scaling for square matrices since
            # otherwise we change the problem
            row_scaling!(Jac.qr.factors, Jac.D, 1)
        end
        # this computes a pivoted qr factorization, i.e.,
        # qr!(Jac.qr.factors, Val(true)) but without allocating new memory
        geqp3!(Jac.qr.factors, Jac.qr.jpvt, Jac.qr.τ, Jac.qr_work, Jac.qr_rwork)

        ε = max(n,m) * eps() * 10
        # check rank 0
        rnm = min(n,m)
        r₁ = abs(real(Jac.qr.factors[1,1]))
        corank = 0
        if r₁ < ε
            corank = rnm
            Jac.cond = inv(ε)
        else
            for i in 2:rnm
                rᵢ = abs(real(Jac.qr.factors[i,i]))
                if ε * r₁ > rᵢ
                    corank = rnm - i + 1
                    break
                end
            end
            Jac.cond = max(1.0, r₁) / abs(real(Jac.qr.factors[rnm, rnm]))
        end
        Jac.corank_proposal = corank

        Jac.active_factorization = QR_FACTORIZATION
    end
    Jac
end

"""
    solve!([x, ], Jac::Jacobian, b)

Solve `Jac.J * x = b` inplace. Assumes `Jac` contains already the correct factorization.
This stores the result in `x`.
"""
function solve!(x, Jac::Jacobian, b::AbstractVector; update_digits_lost::Bool=false, refinement_step::Bool=false)
    if Jac.active_factorization == LU_FACTORIZATION
        lu = Jac.lu
        if lu !== nothing
            custom_ldiv!(x, lu, b)
        end
    elseif Jac.active_factorization == QR_FACTORIZATION
        if issquare(Jac.J)
            # we applied row scaling for square matrices
            @inbounds for i in eachindex(Jac.b)
                Jac.b[i] = b[i] / Jac.D[i]
            end
            custom_ldiv!(x, Jac.qr, Jac.b, Jac.corank, Jac.perm, Jac.ormqr_work )
        else
            Jac.b .= b
            custom_ldiv!(x, Jac.qr, Jac.b, Jac.corank, Jac.perm, Jac.ormqr_work )
        end
    end
    if issquare(Jac.J) && Jac.corank == 0 && update_digits_lost && !refinement_step
        norm_x = euclidean_norm(x)
        norm_δx = iterative_refinement_step!(x, Jac, b)
        Jac.digits_lost = log₁₀(norm_δx / eps(norm_x))
    elseif !issquare(Jac.J)
        Jac.digits_lost = nothing
    end
    x
end
solve!(A::Jacobian, b) = solve!(b, A::Jacobian, b)

function custom_ldiv!(x, LU::LinearAlgebra.LU, b::AbstractVector)
    if x !== b
        copyto!(x, b)
    end
     _ipiv!(LU, x)
     ldiv_unit_lower!(LU.factors, x)
     ldiv_upper!(LU.factors, x)
     x
end

const LA = LinearAlgebra

# This is an adoption of the Julia implementation of ldiv! for QRPivoted
function custom_ldiv!(x, A::LinearAlgebra.QRPivoted{ComplexF64},
                      b::Vector{ComplexF64}, corank::Int,
                      perm::Vector{Int}, ormqr_work::Vector{ComplexF64})
    mA, nA = size(A.factors)
    nr = min(mA,nA)
    nrhs = length(b)
    if nr == 0
        return x
    end
    if corank == nr
        fill!(x, zero(eltype(x)))
        return x
    end
    rank = nr - corank

    if rank == nA == nr
        # The following is equivalent to LA.lmul!(LA.adjoint(A.Q), b) but
        # we can also pass the preallocated work vector
        ormqr!('L', 'C', A.factors, A.τ, b, ormqr_work)
        ldiv_upper!(A.factors, b; singular_exception=false)
        @inbounds for i in 1:nr
            x[i] = b[i]
            perm[i] = A.p[i]
        end
        Base.invpermute!!(x, perm)
    else
        C, τ = LA.LAPACK.tzrzf!(A.factors[1:rank,:])
        LA.ldiv!(LA.UpperTriangular(C[1:rank,1:rank]),view(LA.lmul!(LA.adjoint(A.Q), b), 1:rank))
        for i in 1:rank
            x[i] = b[i]
        end
        for i in rank+1:nA
            x[i] = zero(eltype(x))
        end
        LA.LAPACK.ormrz!('L', eltype(x)<:Complex ? 'C' : 'T', C, τ, reshape(x, length(x), 1))
        invpermute!(x, A.p)
    end
    return x
end

"""
    iterative_refinement_step!(x, A, b, fac, T, r)

Apply one step of iterative refinement where the residual is computed with precision `T`.
Returns the euclidean norm of the update.
"""
function iterative_refinement_step!(x, Jac::Jacobian, b, ::Type{T}=eltype(x)) where T
    residual!(Jac.r, Jac.J, x, b, T)
    δx = solve!(Jac.r, Jac, Jac.r; refinement_step=true)
    norm_δx = euclidean_norm(δx)
    @inbounds for i in eachindex(x)
        x[i] = convert(T, x[i]) - convert(T, δx[i])
    end

    return norm_δx
end

"""
    residual!(u, A, x, b, ::Type{T}=eltype(u))

Compute the residual `Ax-b` in precision `T` and store in `u`.
"""
function residual!(u::AbstractVector, A, x, b, ::Type{T}=eltype(u)) where {T}
    @boundscheck size(A, 1) == length(b) && size(A,2) == length(x)
    m, n = size(A)
    @inbounds for i in 1:m
        dot = zero(T)
        for j in 1:n
            dot = muladd(convert(T, A[i,j]), convert(T, x[j]), dot)
        end
        u[i] = dot - convert(T, b[i])
    end
    u
end

###########################
# CUSTOM LU Factorization #
###########################

# This is an adoption of LinearAlgebra.generic_lufact!
# with 3 changes:
# 1) For choosing the pivot we use abs2 instead of abs
# 2) Instead of using the robust complex division we
#    just use the naive division. This shouldn't
#    lead to problems since numerical diffuculties only
#    arise for very large or small exponents
# 3) We fold lu! and ldiv! into one routine
#    this has the effect that we do not need to allocate
#    the pivot vector anymore and also avoid the allocations
#    coming from the LU wrapper
@inline function lu_factorization!(A::AbstractMatrix{T},
                           b::Union{AbstractVector, Nothing}=nothing,
                           ipiv::Union{Vector{I}, Nothing}=nothing,
                           ::Val{Pivot} = Val(true)) where {T,I,Pivot}
    m, n = size(A)
    minmn = min(m,n)
    # LU Factorization
    @inbounds begin
        for k = 1:minmn
            # find index max
            kp = k
            if Pivot
                amax = zero(real(T))
                for i = k:m
                    if T <: Complex
                        absi = abs2(A[i,k])
                    else
                        absi = abs(A[i,k])
                    end
                    if absi > amax
                        kp = i
                        amax = absi
                    end
                end
            end
            if !(ipiv isa Nothing)
                ipiv[k] = kp
            end
            if !iszero(A[kp,k])
                if k != kp
                    # Interchange
                    for i = 1:n
                        tmp = A[k,i]
                        A[k,i] = A[kp,i]
                        A[kp,i] = tmp
                    end
                    if !(b isa Nothing)
                        b[k], b[kp] = b[kp], b[k]
                    end
                end
                # Scale first column
                Akkinv = @fastmath inv(A[k,k])
                for i = k+1:m
                    A[i,k] *= Akkinv
                end
            end
            # Update the rest
            for j = k+1:n
                A_kj = A[k,j]
                for i = k+1:m
                    A[i,j] -= A[i,k] * A_kj
                end
            end
        end
    end
    A
end

_ipiv!(A::LinearAlgebra.LU, b::AbstractVector) = apply_ipiv!(b, A.ipiv)

function apply_ipiv!(b::AbstractVector, ipiv)
    @inbounds for i = 1:length(ipiv)
        if i != ipiv[i]
            _swap_rows!(b, i, ipiv[i])
        end
    end
    b
end

function _swap_rows!(B::StridedVector, i::Integer, j::Integer)
    B[i], B[j] = B[j], B[i]
    B
end

@inline function ldiv_upper!(A::AbstractMatrix, b::AbstractVector, x::AbstractVector = b; singular_exception::Bool=true)
    n = size(A, 2)
    for j in n:-1:1
        @inbounds singular_exception && iszero(A[j,j]) && throw(LinearAlgebra.SingularException(j))
        @inbounds xj = x[j] = (@fastmath A[j,j] \ b[j])
        for i in 1:(j-1)
            @inbounds b[i] -= A[i,j] * xj
        end
    end
    b
end
@inline function ldiv_unit_lower!(A::AbstractMatrix, b::AbstractVector, x::AbstractVector = b)
    n = size(A, 2)
    @inbounds for j in 1:n
        xj = x[j] = b[j]
        for i in j+1:n
            b[i] -= A[i,j] * xj
        end
    end
    x
end

import LinearAlgebra: BlasInt

"""
    geqp3!(A::AbstractMatrix{ComplexF64},
        jpvt::AbstractVector{BlasInt},
        tau::AbstractVector{ComplexF64},
        work::Vector{ComplexF64},
        rwork=Vector{Float64}(undef, 2*size(A,2)))

Version of `LAPACK.geqp3!` which also accepts the work buffers.
"""
function geqp3!(A::AbstractMatrix{ComplexF64},
		jpvt::AbstractVector{BlasInt},
		tau::AbstractVector{ComplexF64},
		work::Vector{ComplexF64},
		rwork=Vector{Float64}(undef, 2*size(A,2)))
	m,n = size(A)
	lda = stride(A,2)
	if lda == 0
		return return A, tau, jpvt
	end # Early exit
    jpvt .= BlasInt(0)
	lwork = BlasInt(-1)
	info = Ref{BlasInt}()
	for i = 1:2
		ccall((LinearAlgebra.LAPACK.@blasfunc(zgeqp3_), LinearAlgebra.LAPACK.liblapack), Cvoid,
			  (Ref{BlasInt}, Ref{BlasInt}, Ptr{ComplexF64}, Ref{BlasInt},
			   Ptr{BlasInt}, Ptr{ComplexF64}, Ptr{ComplexF64}, Ref{BlasInt},
			   Ptr{Float64}, Ptr{BlasInt}),
			  m, n, A, lda,
			  jpvt, tau, work, lwork,
			  rwork, info)
		LinearAlgebra.LAPACK.chklapackerror(info[])
		if i == 1
			lwork = BlasInt(real(work[1]))
			resize!(work, lwork)
		end
	end
	return A, tau, jpvt
end

"""
    ormqr!(side, trans, A, tau, C, work)

Version of `LAPACK.ormqr!` which also accepts the work buffer.
Computes `Q * C` (`trans = N`), `transpose(Q) * C` (`trans = T`), `adjoint(Q) * C`
(`trans = C`) for `side = L` or the equivalent right-sided multiplication
for `side = R` using `Q` from a `QR` factorization of `A` computed using
`geqrf!`. `C` is overwritten.
"""
function ormqr!(side::AbstractChar, trans::AbstractChar, A::Matrix{ComplexF64},
                tau::Vector{ComplexF64}, C::Vector{ComplexF64},
                work::Vector{ComplexF64})
    m,n = (size(C, 1), 1)
    mA  = size(A, 1)
    k   = length(tau)
    if side == 'L' && m != mA
        throw(DimensionMismatch("for a left-sided multiplication, the first dimension of C, $m, must equal the second dimension of A, $mA"))
    end
    if side == 'R' && n != mA
        throw(DimensionMismatch("for a right-sided multiplication, the second dimension of C, $m, must equal the second dimension of A, $mA"))
    end
    if side == 'L' && k > m
        throw(DimensionMismatch("invalid number of reflectors: k = $k should be <= m = $m"))
    end
    if side == 'R' && k > n
        throw(DimensionMismatch("invalid number of reflectors: k = $k should be <= n = $n"))
    end
    lwork = BlasInt(-1)
    info  = Ref{BlasInt}()
    for i = 1:2  # first call returns lwork as work[1]
        ccall((LA.LAPACK.@blasfunc(zunmqr_), LA.LAPACK.liblapack), Cvoid,
              (Ref{UInt8}, Ref{UInt8}, Ref{BlasInt}, Ref{BlasInt},
               Ref{BlasInt}, Ptr{ComplexF64}, Ref{BlasInt}, Ptr{ComplexF64},
               Ptr{ComplexF64}, Ref{BlasInt}, Ptr{ComplexF64}, Ref{BlasInt},
               Ptr{BlasInt}),
              side, trans, m, n,
              k, A, max(1,stride(A,2)), tau,
              C, max(1, stride(C,2)), work, lwork,
              info)
        LA.LAPACK.chklapackerror(info[])
        if i == 1
            lwork = BlasInt(real(work[1]))
            resize!(work, lwork)
        end
    end
    C
end

"""
    hnf(A, T=elytpe(A))

    Compute the hermite normal form `H` of `A` by overwriting `A` and the corresponding transformation
    matrix `U` using precision T. This is `A*U == H` and `H` is a lower triangular matrix.

    The implementation follows the algorithm described in [1].

    [1] Kannan, Ravindran, and Achim Bachem. "Polynomial algorithms for computing the Smith and Hermite normal forms of an integer matrix." SIAM Journal on Computing 8.4 (1979): 499-507.
"""
function hnf(A, T=eltype(A))
    H = similar(A, T)
    H .= A
    U = similar(A, T)
    hnf!(H, U)
    H, U
end

# use checked arithmethic
⊡(x,y) = Base.checked_mul(x, y)
⊞(x,y) = Base.checked_add(x, y)

"""
    hnf!(H, U, A)

Inplace version of [hnf](@ref).

    hnf!(A, U)

Inplace version of [hnf](@ref) overwriting `A` with `H`.
"""
hnf!(H, U, A) = hnf!(copyto!(H, A), U)
function hnf!(A, U)
    n = size(A, 1)
    U .= 0
    @inbounds for i in 1:n
        U[i,i] = one(eltype(U))
    end
    @inbounds for i in 1:(n-1)
        ii = i ⊞ 1
        for j in 1:i
            if !iszero(A[j, j]) || !iszero(A[j, ii])
                # 4.1
                r, p, q = gcdx(A[j, j], A[j, ii])
                # 4.2
                d_j = -A[j, ii] ÷ r
                d_ii = A[j, j] ÷ r
                for k = 1:n
                    a_kj, a_kii = A[k,j], A[k,ii]
                    A[k, j] = a_kj ⊡ p ⊞ a_kii ⊡ q
                    A[k, ii] = a_kj ⊡ d_j ⊞ a_kii ⊡ d_ii

                    u_kj, u_kii = U[k,j], U[k,ii]
                    U[k, j] = u_kj ⊡ p ⊞ u_kii ⊡ q
                    U[k, ii] = u_kj ⊡ d_j ⊞ u_kii ⊡ d_ii
                end
            end
            # 4.3
            if j > 1
                reduce_off_diagonal!(A, U, j)
            end
        end
        #5
        reduce_off_diagonal!(A, U, ii)
    end
    # Special case for 1 × 1 matrices to guarantee that the diagonal is positive
    # This comes up in polyhedral homotopy for univariate polynomials
    if n == 1
        U[1,1] = flipsign(U[1,1], A[1,1])
        A[1,1] = flipsign(A[1,1], A[1,1])
    end

    nothing
end

@inline function reduce_off_diagonal!(A, U, k)
    n = size(A, 1)
    @inbounds if A[k,k] < 0
        for i in 1:n
            A[i, k] = -A[i, k]
            U[i, k] = -U[i, k]
        end
    end
    @inbounds for z in 1:(k-1)
        if !iszero(A[z,z])
            r = -ceil(eltype(A), A[k,z] / A[z,z])
            for i in 1:n
                U[i,z] = U[i,z] ⊞ r ⊡ U[i,k]
                A[i,z] = A[i,z] ⊞ r ⊡ A[i,k]
            end
        end
    end
    nothing
end


using Gadfly, DataArrays, RDatasets

 plot(dataset("ggplot2", "diamonds"), x=:Price, Geom.density)


# GA

#=

LeetCode 198. House Robber

You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed, the only constraint stopping you from robbing each of them is that adjacent houses have security system connected and it will automatically contact the police if two adjacent houses were broken into on the same night.

Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can rob tonight without alerting the police.

=#

# Import Packages

# GA Parameters

NP = 20
L = 10
Pc = 0.8
Pv = 0.2
G = 100

# Example

house = Int64.(floor.(rand(1, L) * 10) .+ 1)

# Initalization

f = Int64.(zeros(NP, L))
money = zeros(NP, 1)
moneyFit = zeros(NP, 1)
fBest = Int64.(zeros(1, L))

for ii = 1: NP

    f[ii, :] = Int64.(floor.(rand(1, L) .+ 0.5))

end

# Start

nf = Int64.(zeros(NP, L))

for ii = 1: G

    for jj = 1: NP

        global moneyFit, fBest, f, nf

        # Fitness (money)

        for kk = 1: (L - 1)

            if f[jj, kk] & f[jj, kk + 1] == 1

                money[jj, 1] = 0

                break

            else

                money[jj, 1] = sum(f[jj, :] .* house[1, :])

            end

        end

        moneyMin = minimum(money[:, 1]) # Max
        moneyMax = maximum(money[:, 1]) # Min

        if money[jj, 1] == moneyMax

            fBest[1, :] = f[jj, :] # Best

        end

        moneyFit[jj, 1] = (money[jj, 1] - moneyMin) / (moneyMax - moneyMin) # Normalization

        # Roulette

        sumFit = sum(moneyFit)
        fitValue = moneyFit ./ sumFit
        cumFitValue = cumsum(fitValue[:, 1])

        for mm = 1: NP

            roll = rand()

            for nn = 1: (length(cumFitValue) - 1)

                if cumFitValue[mm] < roll < cumFitValue[mm + 1]

                    nf[mm, :] =  f[nn, :]

                else

                    nf[mm, :] =  f[1, :]

                end

            end

        end

        # Crossing

        for kk = 1: 2: NP

            c = rand()

            if c < Pc

                selection = Int64.(floor.(rand(1, L) .+ 0.5))
                new1 = nf[kk, :] .* selection[1, :] .+ nf[kk + 1, :] .* (1 .- selection[1, :])
                new2 = nf[kk, :] .* (1 .- selection[1, :]) .+ nf[kk + 1, :] .* selection[1, :]
                nf[kk, :] = new1
                nf[kk + 1, :] = new2

            end
        end

        # Variation

        for pp = 1: NP

            for qq = 1: L

                v = rand()

                if v < Pv

                    nf[pp, qq] = 1 - nf[pp, qq]

                end

            end

        end

        # Replacing

        f = nf
        nf[1, :] = fBest[1, :]

    end

end

moneyBest = sum(fBest[1, :] .* house[1, :])

println("Best Selection is $(fBest), money is $(moneyBest).")


struct BrentMin{T}
    division::T
    detect_flatness::Bool
end
BrentMin(; division=(3-sqrt(5))/2, detect_flatness=false) = BrentMin(division, detect_flatness)

function solve(problem::OptimizationProblem, approach::BrentMin, options::OptimizationOptions)
    _solve(problem, approach, options)
end

function _solve(prob, bm::BrentMin, options)
	a, b = bounds(prob)
    T = typeof(a)
    t = 1e-8
    c = bm.division
    v = w = x = a + c*(b - a)

    e = d = 0*x

    fv = fw = fx = value(prob, x)
    p = q = r = 0*x

    for i = 1:options.maxiter
        m = (a + b)/2
        tol = eps(T)*abs(x) + t
        if abs(x - m) > 2*tol - (b - a)/2 # stopping crit
            # fit parabola
            if abs(e) > tol
                r = (x - w)*(fx - fv)
                q = (x - v)*(fx - fw)
                p = (x - v)*q - (x - w)*r
                q = 2*(q - r)
                if q > 0
                    p = -p
                else
                    q = -q
                end
                r = e
                e = d
            end

            if abs(p) < abs(q*r/2) && p < q*(a - x) && p < q*(b - x)
                # the do the parapolic interpolation
                d = p/q
                u = x + d
                if u - a < 2*tol || b - u < 2*tol
                    if x < m # this should just use sign
                        d = tol
                    else
                        d = -tol
                       end
                end
            else
                # do golden section
                if x < m
                    e = b - x
                else
                    e = a - x
                end
                d = c * e
            end

            if abs(d) >= tol
                u = x + d
            else
            	u = x + sign(d)*tol
            end

            fu = value(prob, u)
            if fu <= fx
                if u < x
                    b = x
                else
                    a = x
                end
                v = w
                fv = fw
                w = x
                fw = fx
                x = u
                fx = fu
            else
                if u < x
                    a = u
                else
                    b = u
                end
                if fu <= fw || w == x
                    v = w
                    fv = fw
                    w = u
                    fw = fu
                elseif fu <= fv || v == x || v == w
                    v = u
                    fv = fu
                end
            end
        else
            break
        end
    end
    return x, fx
end

#=
prob = OptimizationProblem(ScalarObjective(;f=x->sign(x)), (-2.0,2.0))
@time solve(prob, 0.0, BrentMin(), OptimizationOptions())

=#


@testset "arithmetic operations" begin
    x = RevisedAffineForm(1, [1, 2, 3], 1)
    y = RevisedAffineForm(2, [3, 4, 5], 0.1)

    @test +x == x
    @test -x == RevisedAffineForm(-1, [-1, -2, -3], 1)

    @test x + y == RevisedAffineForm(3, [4, 6, 8], 1.1)
    @test x - y == RevisedAffineForm(-1, [-2, -2, -2], 1.1)

    @test x + 1 == RevisedAffineForm(2, [1, 2, 3], 1)
    @test x - 1 == RevisedAffineForm(0, [1, 2, 3], 1)
    @test 1 + x == RevisedAffineForm(2, [1, 2, 3], 1)
    @test 1 - x == RevisedAffineForm(0, [-1, -2, -3], 1)
    @test -2 * x == RevisedAffineForm(-2, [-2, -4, -6], 2)
    @test x * 2 == RevisedAffineForm(2, [2, 4, 6], 2)
    @test x / 2 == RevisedAffineForm(0.5, [0.5, 1, 1.5], 0.5)
end

@testset "correcty rounded sum and dot product" begin
    a = [Interval(0.1), Interval(0.2), Interval(0.3)]
    b = [Interval(0.4), Interval(0.6), Interval(0.7)]
    am = mid.(a)
    bm = mid.(b)

    si = sum(a)
    di = dot(a, b)
    @test RAA._sum_up(am) == sup(si)
    @test RAA._sum_down(am) == inf(si)
    @test RAA._dot_up(am, bm) == sup(di)
    @test RAA._dot_down(am, bm) == inf(di)

    @test RAA._sum_up(abs, -am) == sup(si)
    @test RAA._sum_down(abs, -am) == inf(si)
    @test RAA._dot_up(abs, -am, -bm) == sup(di)
    @test RAA._dot_down(abs, -am, -bm) == inf(di)
end


module Archer_etal_2000
#=
This module serves to load the 6-box model matrix and grid.
The 6-box model is used because it makes it easier to build
operators for sinking particles in a regular grid,
and because the code to bin WOA data requires a regular grid.
These are the indices of the 6-box model:

┌───────┬────────────────────────┐
│ 1(H)  │         2(L)           │
├───────┼────────────────────────┤
│ 3(H)  │         4(D)           │
├───────┼────────────────────────┤
│       │                        │
│ 5(D)  │         6(D)           │
│       │                        │
└───────┴────────────────────────┘

The boxes are grouped as indicated:
- 1 + 3 represents the high-latitudes (surface) box
- 2 represents the low-latitudes (surface) box
- 4 + 5 + 6 represents the deep box
=#

using LinearAlgebra, SparseArrays
using ..GridTools
using Unitful, UnitfulAstro # for units
using Reexport
@reexport using OceanGrids            # To store the grid
using ..CirculationGeneration
CG = CirculationGeneration

build_wet3D() = trues(2, 1, 3)

function build_grid()
    wet3D = build_wet3D()
    # From Archer et al. [2000]
    vtot = 1.292e18u"m^3"
    area_ocean = 349e12u"m^2"
    depth_low = 100.0u"m"
    depth_high = 250.0u"m"
    fraction_area_high = 0.15 #
    # Infer other values
    depth_ocean = vtot / area_ocean
    # Build DY3d
    elon = [0,360] * u"°"
    elat = [-90, -90+fraction_area_high*180, 90] * u"°"
    edepth = [0u"m", depth_low, depth_high, depth_ocean]
    return OceanGrid(elon, elat, edepth)
end

function build_T(grid)
    # From Archer et al. [2000]
    v3D = array_of_volumes(grid)
    nb = length(v3D)

    # Mixing terms
    # between top boxes (1 and 2) (10 Sv = 10e6 cubic meters)
    T  = CG.flux_divergence_operator_from_advection(10e6u"m^3/s", [1, 2], v3D, nb)
    # between high-lat and deep (1 and 3)
    T += CG.flux_divergence_operator_from_advection(53e6u"m^3/s", [2, 5], v3D, nb)
    # between low-lat and deep (2 and 4)
    T += CG.flux_divergence_operator_from_advection( 1e6u"m^3/s", [2, 4], v3D, nb)
    # (trick) fast mixing between boxes of the same 3-box-model box
    T += CG.flux_divergence_operator_from_advection(1e10u"m^3/s", [1, 3], v3D, nb)
    T += CG.flux_divergence_operator_from_advection(1e10u"m^3/s", [4, 5], v3D, nb)
    T += CG.flux_divergence_operator_from_advection(1e10u"m^3/s", [5, 6], v3D, nb)
    T += CG.flux_divergence_operator_from_advection(1e10u"m^3/s", [4, 6], v3D, nb)

    # Overturning circulation
    T += CG.flux_divergence_operator_from_advection(19e6u"m^3/s", [1, 3, 5, 6, 4, 2], v3D, nb)

    return T
end

"""
    load

Returns wet3D, grid, and T (in that order).
"""
function load()
    print("Creating 6-box model")
    wet3D = build_wet3D()
    grid = build_grid()
    T = build_T(grid)
    println(" ✔")
    @info """
            You are about to use the 3-box model of Archer et al. [2000].
            If you use it for research, please cite:

            - Archer, D. E., Eshel, G., Winguth, A., Broecker, W., Pierrehumbert, R., Tobis, M., and Jacob, R. (2000), Atmospheric pCO2 sensitivity to the biological pump in the ocean, Global Biogeochem. Cycles, 14 (4), 1219–1230, doi:10.1029/1999GB001216.

            You can find the corresponding BibTeX entry in the CITATION.bib file
            at the root of the AIBECS.jl package repository.
            (Look for the "Archer_etal_2000" key.)

            Note that although this model represents the 3-box model of Archer et al. [2000],
            it effectively requires 6 boxes to represent in the AIBECS,
            so that particulate sinking flux divergence operators can be built.
            (See the comments at the start of the Archer_etal_2000.jl file for details.)
            """
    return wet3D, grid, T
end

end

export Archer_etal_2000


using PowerSpectra
using Healpix
using CSV
using Test
using LinearAlgebra
using DataFrames
using DelimitedFiles
using NPZ
using IdentityRanges

##
@testset "Mode Coupling Matrix TT" begin
    nside = 256
    mask = readMapFromFITS("data/example_mask_1.fits", 1, Float64)
    flat_beam = SpectralVector(ones(3*nside))
    flat_map = HealpixMap{Float64, RingOrder}(ones(nside2npix(nside)) )
    M = mcm(:TT, mask, mask; lmin=2)
    reference = readdlm("data/mcm_TT_diag.txt")
    @test all(reference .≈ diag(parent(M))[1:end-1])
    map1 = readMapFromFITS("data/example_map.fits", 1, Float64)
    pCl = SpectralVector(alm2cl(map2alm(map1 * mask)))
    Cl_hat = M \ pCl[IdentityRange(2:end)]
    reference_spectrum = readdlm("data/example_TT_spectrum.txt")
    @test all(reference_spectrum .≈ Cl_hat[2:end])
    @test all(reference_spectrum .≈ Cl_hat[2:end])
end

##
@testset "Mode Coupling Matrix Diag EE" begin
    nside = 256
    mask = readMapFromFITS("data/example_mask_1.fits", 1, Float64)
    flat_mask = HealpixMap{Float64, RingOrder}(ones(nside2npix(nside)) )
    M = mcm(:M⁺⁺, mask, mask)
    # factorized_mcm12 = lu(parent(M))
    reference = readdlm("data/mcm_EE_diag.txt")
    @test all(reference .≈ diag(parent(M))[3:767])
end

##
@testset "Mode Coupling Matrix Diag TE/ET" begin
    nside = 256
    mask = readMapFromFITS("data/example_mask_1.fits", 1, Float64)
    M = mcm(:TE, mask, mask)
    reference = readdlm("data/mcm_TE_diag.txt")
    @test all(reference .≈ diag(parent(M))[3:767])

    M = mcm(:ET, mask, mask)
    reference = readdlm("data/mcm_TE_diag.txt")
    @test all(reference .≈ diag(parent(M))[3:767])
end

##
@testset "Full Non-Trivial MCM" begin
    nside = 256
    mask1_T = readMapFromFITS("data/mask1_T.fits", 1, Float64)
    mask2_T = readMapFromFITS("data/mask2_T.fits", 1, Float64)
    mask1_P = readMapFromFITS("data/mask1_P.fits", 1, Float64)
    mask2_P = readMapFromFITS("data/mask2_P.fits", 1, Float64)
    unit_map = HealpixMap{Float64, RingOrder}(ones(nside2npix(nside)) )
    unit_beam = spectralones(0:(3nside-1))

    M = mcm(:TT, mask1_T, mask2_T)
    M_ref = npzread("data/mcmTT.npy")
    @test all(isapprox(parent(M)[3:end, 3:end], M_ref[3:end, 3:end], atol=1e-11))

    M = mcm(:TE, mask1_T, mask2_P)
    M_ref = npzread("data/mcmTE.npy")
    for k in 0:3nside
        @test all(isapprox(diag(parent(M), k)[3:end], diag(M_ref, k)[3:end]))
    end

    M = mcm(:ET, mask1_P, mask2_T)
    M_ref = npzread("data/mcmET.npy")

    for k in 0:3nside
        @test all(isapprox(diag(parent(M), k)[3:end], diag(M_ref, k)[3:end]))
    end

    M = mcm(:M⁺⁺, mask1_P, mask2_P)
    M_ref = npzread("data/mcmEE.npy")
    for k in 0:3nside
        @test all(isapprox(diag(parent(M), k)[3:end], diag(M_ref, k)[3:end]))
    end
end


using Revise
using Dice
using Dice: num_flips, num_nodes, to_dice_ir
using Distributions

code = @dice begin
    # triangle distribution

    function triangle(b::Int)
        s = false
        n = 2^b
        x = Vector(undef, b)
        y = Vector(undef, b)
        for i = b:-1:1
            x[i] = Dice.ifelse(s, flip(1/2), flip((3n - 2)/ (4n-4)))
            y[i] = flip((n-2)/(3n-2))
            s = s || (x[i] && !y[i])
            n = n/2
        end
        return DistInt(x)
    end

    function uniform(b::Int) # b is the bits for uniform, w is the bitwidth
        x = Vector(undef, b)
        for i = b:-1:1
            x[i] = flip(0.5)
        end
        return DistInt(x)
    end

    function discrete(p::Vector{Float64})
        mb = length(p)
        v = Vector(undef, mb)
        sum = 1
        for i=1:mb
            v[i] = p[i]/sum
            sum = sum - p[i]
        end

        # println(v)
        ans = DistInt(dicecontext(), mb-1)
        for i=mb-1:-1:1
            ans = if flip(v[i]) DistInt(dicecontext(), i-1) else ans end
        end
        return ans
    end

    function anyline(bits::Int, p::Float64)
        ans = Dice.ifelse(flip(p*2^bits), add_bits(uniform(bits), 3), add_bits(triangle(bits), 3))
        return ans
    end

    function gaussian(bits::Int, pieces::Int)
        d = Normal()
        start = quantile.(Normal(), 0.001)
        interval_sz = 2*abs(start)/pieces

        areas = Vector(undef, pieces)
        total_area = 0

        end_pts = Vector(undef, pieces)
        for i=1:pieces
            p1 = start + (i-1)*interval_sz
            p2 = p1 + interval_sz/2^bits
            p3 = start + (i)*interval_sz
            p4 = p3 - interval_sz/2^bits

            pts = [cdf.(d, p2) - cdf.(d, p1), cdf.(d, p3) - cdf.(d, p4)]
            end_pts[i] = pts

            areas[i] = (pts[1] + pts[2])*2^(bits - 1)
            total_area += areas[i]
        end

        # println(total_area)

        rel_prob = areas/total_area

        b = discrete(rel_prob)
        a = end_pts[pieces][1]/areas[pieces]
        l = a > 1/2^bits
        ans =  (if l
                    2^bits - 1 + (pieces - 1)*2^bits - add_bits(anyline(bits, 2/2^bits - a), 3)
                else
                    (pieces - 1)*2^bits + add_bits(anyline(bits, a), 3)
                end)[1]

        for i=pieces-1:-1:1
            a = end_pts[i][1]/areas[i]
            l = a > 1/2^bits
            ans = if prob_equals(b, i-1)
                    (if l
                        (2^bits - 1 + (i - 1)*2^bits - anyline(bits, 2/2^bits - a))
                    else
                        (i - 1)*2^bits +
                            anyline(bits, a)
                    end)[1]
                else
                    ans
                end
        end
        return ans
    end
    # println(max_bits(add_bits(anyline(2, 0.1), 3)))
    # println(max_bits(gaussian(2, 4)))
    g = gaussian(1, 108)
    rep = (g > 107) & (143 > g)
    rep
end

bdd = compile(code)
num_flips(bdd)
num_nodes(bdd)
println(infer(code, :bdd))
@assert infer(code, :bdd) ≈ 0.5

bdd = compile(code)
num_flips(bdd)
num_nodes(bdd)
infer(code, :bdd)


include("../src/expmatrices.jl")
using LinearAlgebra
using Random
using Test
function testexp()
    A=zeros(Int64, 4, 4)
    A[1,3] = 1
    A[3,1] = -1
    @time @testset "test 1 exp big matrices" begin
        @test isapprox( one(A), _expmat1(0A), atol=1e-16)
        @test isapprox(exp(0.123*A), _expmat1(0.123*A), atol=1e-15)
    end
    @time @testset "test 2 exp big matrices" begin
        Abig = convert(Array{BigFloat,2},A)
        Random.seed!(81725)
        sens=1
        for k=1:4
            setprecision(k*256) do
                for i = 1:100:10000
                    v=rand(BigFloat)*i*sens
                    res = _expmat0(v*Abig)
                    resRef = one(Abig)
                    resRef[1,1] = resRef[3,3] = cos(v)
                    resRef[1,3] = sin(v)
                    resRef[3,1]= -resRef[1,3]
        #           println("i=$i norm=$(norm(resRef-res))")
                    @test isapprox( resRef, res, atol=(1e-77)^k)
                    sens *= -1
                end
            end
        end
    end
    @time @testset "test 3 exp big matrices" begin
        Random.seed!(8781115)

        b = [ 0 0 0 1; 0 0 -1 0; 0 1 0 0; -1 0 0 0]
        for i=1:20
            C=rand(4,4)*i
            B = b*i
            @test isapprox(exp(C), _expmat1(C), rtol=1e-13)
            @test isapprox(exp(0.7C), _expmat1(0.7C), rtol=1e-12)
            @test isapprox(exp(0.89C), _expmat1(0.89C), rtol=1e-12)
            @test isapprox(exp(B), _expmat1(B), rtol=1e-14)
            @test isapprox(exp(10B), _expmat1(10B), rtol=1e-13)
            @test isapprox(exp(100B), _expmat1(100B), rtol=1e-12)
            @test isapprox(exp(1000B), _expmat1(1000B), rtol=1e-11)
            @test isapprox(exp(10000B), _expmat1(10000B), rtol=1e-10)
        end
    end
    @time @testset "test 4 exp big matrices big precision" begin
        Random.seed!(90908)
        prec_old = prec = precision(BigFloat)
        for i=1:6
            prec *= 2
            setprecision(prec)
            @test isapprox(_expmat1(2big(pi)*A)-I,zeros(BigFloat,4,4),atol=eps(BigFloat)*100)
        end
        setprecision(prec_old)
    end
end
testexp()


using Revise
using AlgebraicPetri
using OrdinaryDiffEq
using DiffEqFlux, Flux
using Catalyst
using ModelExploration

SIR =  LabelledPetriNet([:S, :I, :R],
                        :inf => ((:S, :I)=>(:I, :I)),
                        :rec => (:I=>:R));

SIR2 =  LabelledPetriNet([:S, :I, :R],
                        :inf => (:S=>:I),
                        :rec => (:I=>:R));

initial_conc=[1e7,1e5,1e-10]
true_rates = [1e-8,1e-4]
data = generate_data(SIR, true_rates,initial_conc)

final_loss, fitted_params = train(SIR, data)

predicted_traj = generate_data(SIR, fitted_params[1:2],fitted_params[3:5])
plot(data; xaxis="days")
plot!(predicted_traj; xaxis="days")

f = eval_petri_fn(SIR, true_rates, initial_conc)
v1= f(SIR)
v2 = f(SIR2)


export seed_glorot_normal, seed_glorot_uniform

import Flux: glorot_uniform, glorot_normal

using Random

glorot_uniform(rng::AbstractRNG, dims...) =
    (rand(rng, Float32, dims...) .- 0.5f0) .* sqrt(24.0f0 / sum(Flux.nfan(dims...)))
glorot_normal(rng::AbstractRNG, dims...) =
    randn(rng, Float32, dims...) .* sqrt(2.0f0 / sum(Flux.nfan(dims...)))

seed_glorot_uniform(; seed = nothing) =
    (dims...) -> glorot_uniform(MersenneTwister(seed), dims...)
seed_glorot_normal(; seed = nothing) =
    (dims...) -> glorot_normal(MersenneTwister(seed), dims...)


using Soss

N = 10000

m = @model x, λ begin
    σ ~ Exponential(λ)
    β ~ Normal(0,1)
    y ~ For(1:N) do j
        Normal(x[j] * β, σ)
    end
    return y
end

x = randn(N)
λ = 1.0

trace = simulate(m(x=x, λ=1.0)).trace
y = trace.y

post = m(; x, λ) | (; y)
ℓ = symlogdensity(post)


import Neurthino

@test Neurthino.tracklength(0, 2.0) ≈ 2.0 atol=0.01
@test Neurthino.tracklength(pi/2, 1.0) ≈ 112.88 atol=0.01

@test Neurthino.tracklength(pi*1/3, Neurthino.EARTH_RADIUS / 2) ≈ 4149.99 atol=0.01
@test Neurthino.tracklength(0.7, Neurthino.EARTH_RADIUS / 2) ≈ 3595.04 atol=0.01
@test Neurthino.tracklength(pi/6, Neurthino.EARTH_RADIUS / 2) ≈ 3409.97 atol=0.01
@test Neurthino.tracklength(1.91, Neurthino.EARTH_RADIUS / 2) ≈ 6678.27 atol=0.01
@test Neurthino.tracklength(2.44, Neurthino.EARTH_RADIUS / 2) ≈ 8463.26 atol=0.01

@test Neurthino.tracklength(2.62, 1860) ≈ 9872.5 atol=1.0
@test Neurthino.tracklength(2.97, 1860) ≈ 10769.02 atol=1.0
@test Neurthino.tracklength(2.27, 5600) ≈ 6839.15 atol=1.0

path = Neurthino.prempath(pi, 1.5)

@test path.baseline[1] ≈ 128.69 atol=0.1

@test path.density[1] ≈ 3.38 atol=0.1
@test path.density[99] ≈ 3.38 atol=0.1
@test path.density[50] ≈ 13.09 atol=0.1
@test path.density[20] ≈ 5.38 atol=0.1
@test path.density[80] ≈ 5.38 atol=0.1


abstract type MusicalTuning end

struct EqualTemperedTuning <: MusicalTuning
    steps::Int
end

function interval(tuning::EqualTemperedTuning, i) where N
    return 2^(i/tuning.steps)
end

function is12tone(tuning::EqualTemperedTuning) where N
    return tuning.steps == 12
end

function keymap(tuning::EqualTemperedTuning)
    if !is12tone(tuning)
        error("Not implemented for non 12 tone yet")
    end

    return Dict(zip("zsxdcvgbhnjm,l.;/q2w3er5t6y7ui9o0p[=]", [0:16;12:31]))
end


using Plots

t0 = -300:.01:0.01
r = .03
f(r,t0) = log.(2 .- (1+r).^(t0))./log(1+r)
plot(2019 .+ t0, [f(r,t0) f(.07,t0)], labels=["3%", "7%"],xaxis="year X", yaxis="time in the future we will have as much growth as has happened since year X")


import SpecialFunctions: loggamma

@node MatrixDirichlet Stochastic [ out, a ]

@average_energy MatrixDirichlet (q_out::MatrixDirichlet, q_a::PointMass) = begin
    H = mapreduce(+, zip(eachcol(mean(q_a)), eachcol(logmean(q_out)))) do (q_a_column, logmean_q_out_column)
        return -loggamma(sum(q_a_column)) + sum(loggamma.(q_a_column)) - sum((q_a_column .- 1.0) .* logmean_q_out_column)
    end
    return H
end


module OneDimensionalCorrelation

using ArgCheck
using BlockBandedMatrices
using QuadGK
using LinearAlgebra
using PolynomialBases
using ProgressMeter
using Tullio

include("basis.jl")
include("hartree_fock.jl")
include("integrals.jl")
include("ci.jl")

export  Basis,
        BasisLobatto,
        CIHamilton,
        CIOverlap,
        Element1D,
        ElementGrid,
        ElementGridLobatto

export  block_ci,
        bracket,
        ci_matrix,
        ci_vector_product,
        coulomb_matrix,
        derivative_matrix,
        element_ci_orbitals,
        eri,
        erig,
        exchange_matrix,
        fock_matrix,
        fock_matrix!,
        full_ci,
        get_element,
        get_element_indexes,
        get_identity,
        get_length,
        get_weight,
        gram_schmit,
        g_tensor,
        initial_orbitals,
        kinetic_energy,
        metric_tensor,
        one_electron_operator,
        particle_in_box,
        reduced_ci_orbitals,
        solve_hartree_fock

# Write your package code here.

end


###########################################################################################################
###########################################################################################################
############################ OVERLAP LIKE G VİA CUBA NUMERICAL INTEGRATION ################################
###########################################################################################################
###########################################################################################################
export CuhreAuxiliaryG, VegasAuxiliaryG, SuaveAuxiliaryG
###########################################################################################################
function CuhreAuxiliaryG(n1, q, n2, n3, p1, p2, p3, acc)
    coef = ((p1^(n1))//Gamma(n1+1))
    function integrand(x, f)

        f[1] = ( (((1/(1 - x[1]))*(2*x[2] - 1))^q)*
                 (((1/(1 - x[1])) + (2*x[2] - 1))^n2)*
                 (((1/(1 - x[1])) - (2*x[2] - 1))^n3)*
                 exp(-p2*(1/(1 - x[1])) - p3*(2*x[2] - 1))*
                 (2/((1 - x[1])^2))
        )

    end
    res, err = cuhre(integrand, 2, 1, atol=acc, rtol=acc)
    result = NO(coef) * res

    return result
end

function VegasAuxiliaryG(n1, q, n2, n3, p1, p2, p3, acc)
    coef = ((p1^(n1))//Gamma(n1+1))
    function integrand(x, f)

        f[1] = ( (((1/(1 - x[1]))*(2*x[2] - 1))^q)*
                 (((1/(1 - x[1])) + (2*x[2] - 1))^n2)*
                 (((1/(1 - x[1])) - (2*x[2] - 1))^n3)*
                 exp(-p2*(1/(1 - x[1])) - p3*(2*x[2] - 1))*
                 (2/((1 - x[1])^2))
        )

    end
    res, err = vegas(integrand, 2, 1, atol=acc, rtol=acc)
    result = NO(coef) * res

    return result
end

function SuaveAuxiliaryG(n1, q, n2, n3, p1, p2, p3, acc)
    coef = ((p1^(n1))//Gamma(n1+1))
    function integrand(x, f)

        f[1] = ( (((1/(1 - x[1]))*(2*x[2] - 1))^q)*
                 (((1/(1 - x[1])) + (2*x[2] - 1))^n2)*
                 (((1/(1 - x[1])) - (2*x[2] - 1))^n3)*
                 exp(-p2*(1/(1 - x[1])) - p3*(2*x[2] - 1))*
                 (2/((1 - x[1])^2))
        )

    end
    res, err = suave(integrand, 2, 1, atol=acc, rtol=acc)
    result = NO(coef) * res

    return result
end