include("../../example_cases/new node types/plotting.jl")
include("../../example_cases/new node types/ThirdOrderEq.jl")
include("../../example_cases/new node types/ThirdOrderEqGovernorIEEEG1.jl")
include("../../example_cases/new node types/ThirdOrderEqExciterIEEEDC1A.jl")

line_list = []
append!(line_list, [StaticLine(from = 1, to = 2, Y = -1im / 0.02)])
append!(line_list, [StaticLine(from = 2, to = 3, Y = -1im / 0.02)])
append!(line_list, [StaticLine(from = 3, to = 4, Y = -1im / 0.02)])
append!(line_list, [StaticLine(from = 4, to = 5, Y = -1im / 0.02)])
append!(line_list, [StaticLine(from = 5, to = 6, Y = -1im / 0.02)])
append!(line_list, [StaticLine(from = 1, to = 6, Y = -1im / 0.02)])

#=
append!(line_list, [StaticLine(from = 6, to = 7, Y = -1im / 0.02)])
append!(line_list, [StaticLine(from = 7, to = 8, Y = -1im / 0.02)])
append!(line_list, [StaticLine(from = 8, to = 9, Y = -1im / 0.02)])
append!(line_list, [StaticLine(from = 9, to = 10, Y = -1im / 0.02)])
append!(line_list, [StaticLine(from = 10, to = 11, Y = -1im / 0.02)])
append!(line_list, [StaticLine(from = 1, to = 11, Y = -1im / 0.02)])
append!(line_list, [StaticLine(from = 2, to = 7, Y = -1im / 0.02)])
append!(line_list, [StaticLine(from = 1, to = 4, Y = -1im / 0.02)])
append!(line_list, [StaticLine(from = 2, to = 11, Y = -1im / 0.02)])
append!(line_list, [StaticLine(from = 2, to = 9, Y = -1im / 0.02)])
=#

node_list = []

append!(node_list, [SlackAlgebraic(U = 1, Y_n = 0)])
#append!(node_list, [PQAlgebraic(S = 0.9, Y_n = 1)])
append!(node_list, [ThirdOrderEq(H = 3.318, P = -0.6337, D = 0.1, Ω = 50, E_f = 0.5, T_d_dash = 8.690, X_q_dash = 0.103, X_d_dash = 0.111, X_d = 0.1)])
#append!(node_list, [PQAlgebraic(S = -0.5, Y_n = 1)])
append!(node_list, [ThirdOrderEq(H = 3.318, P = -0.6337, D = 0.1, Ω = 50, E_f = 0.5, T_d_dash = 8.690, X_q_dash = 0.103, X_d_dash = 0.111, X_d = 0.1)])
#append!(node_list, [PQAlgebraic(S = 0.9, Y_n = 1)])
append!(node_list, [ThirdOrderEq(H = 3.318, P = 0.6337, D = 0.1, Ω = 50, E_f = 0.5, T_d_dash = 8.690, X_q_dash = 0.103, X_d_dash = 0.111, X_d = 0.1)])
#append!(node_list, [PQAlgebraic(S = 0.9, Y_n = 1)])
append!(node_list, [ThirdOrderEq(H = 3.318, P = 0.6337, D = 0.1, Ω = 50, E_f = 0.5, T_d_dash = 8.690, X_q_dash = 0.103, X_d_dash = 0.111, X_d = 0.1)])
#append!(node_list, [PQAlgebraic(S = 0.9, Y_n = 1)])
append!(node_list, [ThirdOrderEq(H = 3.318, P = 0.6337, D = 0.1, Ω = 50, E_f = 0.5, T_d_dash = 8.690, X_q_dash = 0.103, X_d_dash = 0.111, X_d = 0.1)])

#=
append!(node_list, [SlackAlgebraic(U = 1, Y_n = 1)])
append!(node_list, [ThirdOrderEqGovernorIEEEGone(H = 5.0, D = 0.1, Ω = 50, E_f = 0.2, T_d_dash = 0.1, X_d_dash = 0.5,  X_q_dash = 0.103, X_d = 0.5, P0 = 0.5, Pmax = 1.03, Pmin = 0, Pup = 1.38, Pdown = -1.25, T1 = 0.82, T2 = 0, T3 = 0.97, K = 20.0)])
append!(node_list, [ThirdOrderEqGovernorIEEEGone(H = 5.0, D = 0.1, Ω = 50, E_f = 0.2, T_d_dash = 0.1, X_d_dash = 0.5,  X_q_dash = 0.103, X_d = 0.5, P0 = 0.5, Pmax = 1.03, Pmin = 0, Pup = 1.38, Pdown = -1.25, T1 = 0.82, T2 = 0, T3 = 0.97, K = 20.0)])
append!(node_list, [ThirdOrderEqGovernorIEEEGone(H = 5.0, D = 0.1, Ω = 50, E_f = 0.2, T_d_dash = 0.1, X_d_dash = 0.5,  X_q_dash = 0.103, X_d = 0.5, P0 = 0.5, Pmax = 1.03, Pmin = 0, Pup = 1.38, Pdown = -1.25, T1 = 0.82, T2 = 0, T3 = 0.97, K = 20.0)])
append!(node_list, [ThirdOrderEqGovernorIEEEGone(H = 5.0, D = 0.1, Ω = 50, E_f = 0.2, T_d_dash = 0.1, X_d_dash = 0.5,  X_q_dash = 0.103, X_d = 0.5, P0 = 0.5, Pmax = 1.03, Pmin = 0, Pup = 1.38, Pdown = -1.25, T1 = 0.82, T2 = 0, T3 = 0.97, K = 20.0)])
append!(node_list, [ThirdOrderEqGovernorIEEEGone(H = 5.0, D = 0.1, Ω = 50, E_f = 0.2, T_d_dash = 0.1, X_d_dash = 0.5,  X_q_dash = 0.103, X_d = 0.5, P0 = 0.5, Pmax = 1.03, Pmin = 0, Pup = 1.38, Pdown = -1.25, T1 = 0.82, T2 = 0, T3 = 0.97, K = 20.0)])

=#

powergrid = PowerGrid(node_list, line_list)

return powergrid


# Sequentialize.jl
#
# Sequentialization for recurrent networks
# The current Flux.jl setup does not allow to feed sequences as a matrix to a recurrent
# network. The constructions in this file help to unwrap a matrix to a sequence, apply
# an RNN and then wrap things up again.

"""
    HiddenRecur(cell)

`HiddenRecur` takes a recurrent cell and makes it stateful, managing the hidden state
in the background. As opposed to `Recur`, it returns the both the hidden state and the
cell state.

See also: [`Flux.Recur`](@ref)
"""
mutable struct HiddenRecur{T,S}
    cell::T
    state::S
end

HiddenRecur(m) = HiddenRecur(m, m.state0)

function (m::HiddenRecur)(xs...)
    h, y = m.cell(m.state, xs...)
    m.state = h
    return h  # collect(h)
end

Flux.@functor HiddenRecur
trainable(a::HiddenRecur) = (a.cell,)

Base.show(io::IO, m::HiddenRecur) = print(io, "HiddenRecur(", m.cell, ")")
Flux.reset!(m::HiddenRecur) = (m.state = m.cell.state0)

"""
    Seq(RNN)

`Seq` takes a recurrent neural network and "sequentializes" it, i.e. when `Seq(RNN)` is
called with a matrix of input features over a certain time interval, the recurrent neural
net is fed with a sequence of inputs, and results are transformed back to matrix form.
"""
mutable struct Seq{T}
    chain::T
    state
end

Seq(chain) = Seq(chain, [0.0f0])
(l::Seq)(x) = l(l.chain.state, l.chain, x)
getbuffersize(chain, x) = getbuffersize(typeof(chain), chain.state, x)

# CPU Arrays
function (l::Seq)(::Array, _, x)
	l.state = Align(map(l.chain, Slices(x, True(), False())), 1)
	return l.state
end
function (l::Seq)(::Array, ::Flux.Recur, x)
	l.state = Align(map(l.chain, Slices(x, True(), False())), 1)
	return l.state
end
function (l::Seq)(::Tuple, ::Flux.Recur, x)
	l.state = Align(map(l.chain, Slices(x, True(), False())), 1)
	return l.state
end
function (l::Seq)(::Tuple, _, x)
	tuples = map(l.chain, Slices(x, True(), False()))
	l.state = [Align(map(x -> dropdims(x[i], dims=2), tuples), 1) for i in 1:length(l.chain.state)]
	return l.state
end

# GPU Arrays
getbuffersize(::Type{<:Union{Flux.Recur,StackedLSTMCell}},
 				state::AbstractArray, x) = ((length(state), size(x, 2)), 1)
getbuffersize(::Type{<:Union{Flux.Recur,StackedLSTMCell}},
				state::Tuple, x) = ((length(state[1]), size(x, 2)), 1)
getbuffersize(::Type{<:Union{HiddenRecur}},
				state::Tuple, x) = ((length(state[1]), size(x, 2)), length(state))

function writebuffer(chain::HiddenRecur, x)
	sizeval, numhidden = getbuffersize(chain, x)
	out = Flux.Zygote.Buffer(x, numhidden * sizeval[1], sizeval[2])
	for i = 1:sizeval[2]
	  out[:,i] = cat(chain(x[:,i])...; dims=1)
	end
	output = copy(out)
	return [output[i * sizeval[1] + 1:(i + 1) * sizeval[1],:] for i = 0:numhidden - 1]
end

function writebuffer(chain, x)
	sizeval, numhidden = getbuffersize(chain, x)
	out = Flux.Zygote.Buffer(x, sizeval)
	for i = 1:sizeval[2]
	  out[:,i] = chain(x[:,i])
	end
	return copy(out)
end

function (l::Seq)(::Flux.CUDA.CuArray, _, x)
	l.state = writebuffer(l.chain, x)
	return l.state
end

function (l::Seq)(::Tuple{Flux.CUDA.CuArray,Flux.CUDA.CuArray}, _, x)
	l.state = writebuffer(l.chain, x)
	return l.state
end

function Base.show(io::IO, l::Seq)
    print(io, "Seq(", l.chain)
    print(io, ")")
end

function Flux.reset!(m::Seq)
    Flux.reset!(m.chain)
    # m.state = [0.0f0]  # this return value makes Zygote crash - why?
end

Flux.@functor Seq
Flux.trainable(m::Seq) = Flux.trainable(m.chain)

"""
    SeqSkip(RNNCell, skiplength::Integer)

`SeqSkip` takes a recurrent neural network cell and "sequentializes" it, i.e. when
it is called with a matrix of input features over a certain time interval, the recurrent
neural net is fed with a sequence of inputs, and results are transformed back to matrix
form. In addition, the hidden state from `skiplength` timesteps ago is used instead of the
current hidden state. This structure combines functionality of `Recur` in that it makes a
recurrent neural network cell stateful, as well as `Seq` in that it feeds matrices of
input features as elements of a time series.

See also: [`Seq`](@ref), [`Flux.Recur`](@ref)
"""
mutable struct SeqSkip{T,P}
	cell::T
	p::P  # skiplength
	state  # current cell state
	fullstate  # cell states over time series
end

SeqSkip(m, p) = SeqSkip(m, p, m.state0, m.state0)

getbuffersize(::Type{<:SeqSkip}, state::AbstractArray, x) = ((length(state), size(x, 2)), 1)
getbuffersize(::Type{<:SeqSkip}, state::Tuple, x) = ((length(state[1]), size(x, 2)), length(state))

writebuffer(l::SeqSkip,x) = writebuffer(typeof(l.state), l, x)

function writebuffer(::Type{<:Union{AbstractArray}}, l::SeqSkip, x)
	sizeval, numhidden = getbuffersize(l, x)
	out = Flux.Zygote.Buffer(x, sizeval)
	h, y = l.cell(l.state, x[:,1])
	out[:,1] = h
	l.state = h
	for i = 2:sizeval[2]
	  h, y = l.cell(out[:,max(1, i - l.p)], x[:,i])
	  l.state = h
	  out[:,i] = h
	end
	l.fullstate = copy(out)
	return l.fullstate
end

function writebuffer(::Type{<:Tuple}, l::SeqSkip, x)
	sizeval, numhidden = getbuffersize(l, x)
	out = Flux.Zygote.Buffer(x, numhidden * sizeval[1], sizeval[2])
	h, y = l.cell(l.state, x[:,1])
	out[:,1] = vcat(h...)
	l.state = h
	for i = 2:sizeval[2]
		hidden = Tuple([out[j * sizeval[1] + 1:(j + 1) * sizeval[1],max(1, i - l.p)] for j = 0:numhidden - 1 ])
		h, y = l.cell(hidden, x[:,i])
		l.state = h
		out[:,i] = vcat(h...)
	end
	l.fullstate = copy(out)
	return l.fullstate[1:sizeval[1],:]
end
# TO DO: For HiddenRecur-type we need writebuffer that outputs `output`

(l::SeqSkip)(x) = writebuffer(l, x)

Flux.@functor SeqSkip

Base.show(io::IO, m::SeqSkip) = print(io, "SeqSkip(", m.cell, ")")
Flux.reset!(m::SeqSkip) = (m.state = m.cell.state0)


# The recommended way to prallelize the simulation is to
# run the following bash command:
#seq 20 40 | xargs --max-args=1 --max-procs=4 julia sim_run.jl &>> runlog1.txt

#Parallelization for multiprocessing (optional)
#using Distributed
#w = 2
#Distributed.addprocs(w)
#@everywhere begin
begin
    using Random
    using Dates
    using Distributed
    using Printf
    using Nanocsv
    using OpenStreetMapX
    using DataFrames
    using OpenStreetMapXSim
    sim_mode = "flows";
    version="002"
    resultspath="/home/ubuntu/results/"
    datapath = "/home/ubuntu/datasets/"

    N = 100;
    max_jobs_worker=1
end

include("simparams/_loadparams.jl")
sim_data = get_sim_data(datapath);

OpenStreetMapXSim.run_dist_sim(resultspath,version,1,N,max_jobs_worker,sim_data, sim_mode,demographic_profile,additional_activity,nothing,s3action=nothing)


using OpenSpiel
using StatsBase
using Test
using CxxWrap
using Random

function evaluate_bots(
    state::Union{Ptr{Nothing}, CxxPtr{<:State}, CxxWrap.StdLib.UniquePtrAllocated{State}},
    bots::Vector{<:Bot},
    seed::Int)

    rng = MersenneTwister(seed)

    for bot in bots
        restart_at(bot, state)
    end

    while !is_terminal(state)
        if is_chance_node(state)
            outcomes_with_probs = chance_outcomes(state)
            actions, probs = zip(outcomes_with_probs...)
            action = actions[sample(rng, weights(collect(probs)))]
            apply_action(state, action)
        elseif is_simultaneous_node(state)
            chosen_actions = [
                legal_actions(state, pid)[pid+1] ? step(bot, state) : INVALID_ACTION
                for (pid, bot) in enumerate(bots)
            ]  # in julia, index starts with 1
            apply_action(state, chosen_actions)
        else
            apply_action(state, step(bots[current_player(state) + 1], state))
        end
    end
    returns(state)
end

@testset "OpenSpiel.jl" begin
    include("games_api.jl")
    include("games_simulation.jl")
    include("bots.jl")
    include("cfr.jl")
    include("trajector.jl")
end


@testset "Spoerer2017" begin
    batchsize = 5
    channels = 3
    classes = 12
    maps = 45
    input1 = rand(28, 28, 1, batchsize)
    input2 = rand(32, 32, 3, batchsize)

    # test B model
    B = spoerer_model_b(Float32)
    @test size(B(input1)) == (10, batchsize)
    B = spoerer_model_b(Float64, channels=channels, inputsize=(32,32), classes=classes)
    @test size(B(input2)) == (classes, batchsize)

    # test model BK
    BK = spoerer_model_bk(Float32)
    @test size(BK(input1)) == (10, batchsize)
    BK = spoerer_model_bk(Float64, channels=channels, inputsize=(32,32), classes=classes)
    @test size(BK(input2)) == (classes, batchsize)

    # test model BF
    BF = spoerer_model_bf(Float32)
    @test size(BF(input1)) == (10, batchsize)
    BF = spoerer_model_bf(Float64, channels=channels, inputsize=(32,32), classes=classes)
    @test size(BF(input2)) == (classes, batchsize)

    # test model BL
    BL = spoerer_model_bl(Float32)
    h = Dict(
        "l1" => zeros(Float32, 28, 28, 32, batchsize),
        "l2" => zeros(Float32, 14, 14, 32, batchsize)
    )
    model = Flux.Recur(BL, h)
    @test size(model(input1)) == (10, batchsize)
    BL = spoerer_model_bl(
        Float64, channels=channels, inputsize=(32,32), classes=classes, features=maps
    )
    h = Dict(
        "l1" => zeros(32, 32, maps, batchsize),
        "l2" => zeros(16, 16, maps, batchsize)
    )
    model = Flux.Recur(BL, h)
    @test size(model(input2)) == (classes, batchsize)

    # test model BT
    BT = spoerer_model_bt(Float32)
    h = Dict(
        "l2" => zeros(Float32, 14, 14, 32, batchsize)
    )
    model = Flux.Recur(BT, h)
    @test size(model(input1)) == (10, batchsize)
    BT = spoerer_model_bt(
        Float64, channels=channels, inputsize=(32,32), classes=classes, features=maps
    )
    h = Dict(
        "l2" => zeros(16, 16, maps, batchsize)
    )
    model = Flux.Recur(BT, h)
    @test size(model(input2)) == (classes, batchsize)

    # test model BLT
    BLT = spoerer_model_blt(Float32)
    h = Dict(
        "l1" => zeros(Float32, 28, 28, 32, batchsize),
        "l2" => zeros(Float32, 14, 14, 32, batchsize)
    )
    model = Flux.Recur(BLT, h)
    @test size(model(input1)) == (10, batchsize)
    BLT = spoerer_model_blt(
        Float64, channels=channels, inputsize=(32,32), classes=classes, features=maps
    )
    h = Dict(
        "l1" => zeros(32, 32, maps, batchsize),
        "l2" => zeros(16, 16, maps, batchsize)
    )
    model = Flux.Recur(BLT, h)
    @test size(model(input2)) == (classes, batchsize)

end # @testset "Spoerer2017"


# @references:
# 1. `https://github.com/evetion/GeoArrays.jl/blob/master/src/geoarray.jl`

# using Stars

abstract type AbstractGeoArray{T,N} <: AbstractArray{T,N} end

# abstract type AbstractGeoArray{T,N,D,A} <: AbstractDimensionalArray{T,N,D,A} end

const RealOrMissing = Union{Missing,Real}
const TypeCRS = Union{WellKnownText,AbstractString}
"""
    GeoArray(A::AbstractArray{<:RealOrMissing}, f::AffineMap = default_affinemap, crs::WellKnownText = crs2wkt(""))
    GeoArray(A::AbstractArray{<:RealOrMissing}, f::AffineMap, crs::AbstractString)
    GeoArray(A::AbstractArray{<:RealOrMissing}, x::AbstractRange, y::AbstractRange, args...)

    GeoArray(fn::AbstractString, bands = nothing)
    GeoArray(ga::AbstractGeoArray; vals::AbstractArray{<:RealOrMissing})

Construct a GeoArray from any Array. Meanwhile, `AffineMap` and `CRS` will be generated.

# Arguments

- `A`: An AbstractArray, at least 2d Array

# Examples
```julia
ga = GeoArray(rand(10,10,1))
GeoArray(rand(10,10))

GeoArray(ga, vals = ga.A)                        # copy `f` and `crs` from `ga`
GeoArray(rand(10,10), 1:10, 2:11)                # define coordinate by `x` and `y`
GeoArray(rand(7, 4), bbox([70, 15, 140, 55]...)) # define coordinate by `bbox`

# 4-d array
ga = GeoArray(rand(10, 10, 2, 3))
GeoArray(ga, vals = ga.A)
```
"""
Base.@kwdef mutable struct GeoArray{T<:RealOrMissing,N} <: AbstractGeoArray{T,N}
    A::AbstractArray{T,N}
    f::AffineMap
    crs::TypeCRS
    names
    time
end

# using Stars
function GeoArray(A::AbstractArray{<:RealOrMissing,N},
    f::AffineMap=default_affinemap,
    crs::TypeCRS=crs2wkt(""),
    names=nothing) where {N}

    # @show N
    if N == 1
        error("`A` should be matrix or array!")
    elseif N == 2
        A = reshape(A, (size(A)..., 1))
    end
    ntime = size(A) |> last
    if names !== nothing
        names = length(names) > ntime ? names[1:ntime] : nothing
    end
    GeoArray(; A=A, f=f, crs=crs2wkt(crs), names=names, time=nothing)
end

# # also suit for high-dimension
function GeoArray(A::AbstractArray{<:RealOrMissing},
    x::AbstractRange, y::AbstractRange, args...)

    if size(A)[1:2] != (length(x), length(y))
        # Note that this function takes *center coordinates*.
        error("Size of `GeoArray` $(size(A)) does not match size of (x,y): $((length(x), length(y))).")
    end
    f = unitrange_to_affine(x, y)
    # b = st_bbox(x, y)
    GeoArray(A, f, args...)
end

function GeoArray(A::AbstractArray{<:RealOrMissing}, b::bbox; proj=4326)
    ga = GeoArray(A)
    st_bbox!(ga, b)
    st_crs!(ga, proj)
    ga
end

GeoArray(fn::AbstractString, bands=nothing) = st_read(fn, bands)

function GeoArray(ga::AbstractGeoArray; vals::AbstractArray{<:RealOrMissing}, names=nothing)
    if names === nothing
        names = ga.names
    end
    GeoArray(vals, ga.f, ga.crs, names)
end

rast = GeoArray
export GeoArray, rast, AbstractGeoArray

include("st_[.jl")
include("st_Ops.jl")


print("""
#include "giac/giac.h"
#include "giac_c.h"
#include<string>

using namespace std;
using namespace giac;

extern "C" {

    static _Gen* _error(const string &msg)
    {
        cerr << msg << endl;
        return giac_undef();
    }

""")

open("giac_funs.txt","r") do f
for line in eachline(f)
fun=strip(line)
if fun[1]=='#'   #comment
    continue
end

print("""

    _Gen* giac_$fun(_Gen* g, void *cp)
    {
        gen* r0;
        try{
            gen* g0 = reinterpret_cast<gen*>(g);
            context* cp0 = reinterpret_cast<context*>(cp);
            r0 = new gen();
            *r0 = _$fun(*g0, cp0);
        }
        catch(runtime_error &e) {return _error(e.what());}
        return reinterpret_cast<_Gen*>(r0);
    }
""")

end
end

println("""

}
""")


function solve!(model::MoominModel, optimizer; enumerate=1, stoichiometry=true, printLevel=1, timeLimit=1000)
  printLevel == 0 || @info "Creating MILP..."
  MILP = createMILP(model, optimizer, stoichiometry=stoichiometry, timeLimit=timeLimit)
  printLevel < 2 && set_silent(MILP)
  cont = true
  counter = 1
  outputColours = []
  while cont
    printLevel == 0 || @info "Solving MILP #$counter..."
    optimize!(MILP)
    solFound = termination_status(MILP) == MOI.OPTIMAL
    timeLimitReached = termination_status(MILP) == MOI.TIME_LIMIT
    (solFound & (printLevel > 0)) && @info "Found solution!"
    (!solFound & (printLevel > 0) & (counter > 1) ) && @info "No optimum found."
    (!solFound & (counter == 1) ) && @warn "Couldn't solve MILP #1."
    timeLimitReached && @warn "Solver time limit reached."
    if solFound
      if isempty(outputColours)
        outputColours = interpretSolution(value.(MILP[:xPlus]),
                                          value.(MILP[:xMinus]),
                                          model.reactions.inputColours,
                                          model.reactions.reversible)
      else
        outputColours = [outputColours interpretSolution(value.(MILP[:xPlus]),
                                          value.(MILP[:xMinus]),
                                          model.reactions.inputColours,
                                          model.reactions.reversible)]
      end
      updateMILP!(MILP, outputColours)
    end
    cont = solFound & (counter<enumerate)
    counter = counter+1
  end

  fillOutputs!(model, outputColours)
end

function interpretSolution(xPlus, xMinus, inputColours, reversible)
  all(.!((xPlus .> 0.5) .& (xMinus .> 0.5))) || error("Something is wrong with a solution: `x+` and `x-` should be mutually exclusive.")

  outputColours = zeros(length(xPlus))
  outputColours[xPlus .> 0.5] .= 1
  outputColours[xMinus .> 0.5] .= -1
  outputColours[(xPlus .> 0.5) .& (inputColours .== -1)] .= -2
  outputColours[(xMinus .> 0.5) .& (inputColours .== 1)] .= 2
  outputColours[((xPlus .> 0.5) .| (xMinus .> 0.5)) .& (inputColours .==0) .& reversible] .= 6

  return outputColours
end

function fillOutputs!(model::MoominModel, outputColours)
  if isempty(outputColours)
    return
  end
  model.reactions.outputColours = convert.(Int64, outputColours)
  model.reactions.outputFrequency = round.(sum(outputColours .!= 0, dims=2) ./ size(outputColours, 2), digits=3)[:]
  combined = zeros(size(outputColours, 1))
  for row in 1:size(outputColours, 1)
    nonZ = findall(outputColours[row, :] .!= 0)
    if !isempty(nonZ)
      if all(outputColours[row, nonZ[1]] .== outputColours[row, nonZ])
        combined[row] = outputColours[row, nonZ[1]]
      else
        combined[row] = 6
      end
    end
  end
  model.reactions.combinedOutput = convert.(Int64, combined)
end

function createMILP(model::MoominModel, optimizer; stoichiometry=true, timeLimit=1000)
  MILP = JuMP.Model(optimizer)
  if solver_name(MILP) == "CPLEX"
    set_optimizer_attribute(MILP, "CPXPARAM_TimeLimit", timeLimit)
  elseif solver_name(MILP) == "Gurobi"
    set_optimizer_attribute(MILP, "TimeLimit", timeLimit)
  end

  if stoichiometry
    MILPstoich!(MILP, model)
  else
    MILPtopo!(MILP, model)
  end

  return MILP
end

function MILPstoich!(MILP, model::MoominModel)
  epsilon = 1
  (m, n) = size(model.reactions.S)

  # impose a priori colours
  lb = fill(-100., n)
  lb[.!model.reactions.reversible .& (model.reactions.inputColours.==1)] .= 0
  ub = fill(100., n)
  ub[.!model.reactions.reversible .& (model.reactions.inputColours.==-1)] .= 0

  @variable(MILP, v[i=1:n], lower_bound=lb[i], upper_bound=ub[i])
  @variable(MILP, xPlus[1:n], Bin)
  @variable(MILP, xMinus[1:n], Bin)

  # stoichiometry
  @constraint(MILP, model.reactions.S*v .== zeros(m))
  # x+=1 -> v>=epsilon
  @constraint(MILP, v .+ xPlus.*(lb .- epsilon) .>= lb)
  # x+=0 -> v<=0
  @constraint(MILP, v .- xPlus.*ub .<= zeros(n))
  # x-=1 -> v<=-epsilon
  @constraint(MILP, v .+ xMinus.*(ub .+ epsilon) .<= ub)
  # x-=0 -> v>=0
  @constraint(MILP, v .+ xMinus.*(-lb) .>= zeros(n))

  @objective(MILP, Max, model.reactions.weights' * xPlus + model.reactions.weights' * xMinus)
end

function MILPtopo!(MILP, model::MoominModel)
  (m, n) = size(model.reactions.S)

  @variable(MILP, y[1:m], Bin)
  @variable(MILP, xPlus[1:n], Bin)
  @variable(MILP, xMinus[1:n], Bin)

  # impose a priori colours
  notRed = (.!model.reactions.reversible) .& (model.reactions.inputColours.==-1)
  @constraint(MILP, xPlus[notRed] .== 0)
  notBlue = (.!model.reactions.reversible) .& (model.reactions.inputColours.==1)
  @constraint(MILP, xMinus[notBlue] .== 0)

  # x+ and x- cannot be 1 at the same time
  @constraint(MILP, xPlus .+ xMinus .<= ones(n))
  # if a connected arc is included, a node is included
  @constraint(MILP, (model.reactions.S .!= 0) * (xPlus.+xMinus) .<= sum(model.reactions.S .!= 0, dims=2).*y)
  # if a node is included, it has to have an outgoing arc
  @constraint(MILP, (model.reactions.S .< 0) * xPlus .+ (model.reactions.S .> 0) * xMinus .>= y)
  # if a node is included, it has to have an incoming arc
  @constraint(MILP, (model.reactions.S .> 0) * xPlus .+ (model.reactions.S .< 0) * xMinus .>= y)

  @objective(MILP, Max, model.reactions.weights' * xPlus + model.reactions.weights' * xMinus)
end

function updateMILP!(MILP, outputColours)
  if isempty(outputColours)
    return
  end
  if isnothing(constraint_by_name(MILP, "optimality"))
    @constraint(MILP, optimality, objective_function(MILP) == objective_value(MILP))
  end
  prevSol = outputColours[:, end] .!= 0
  @constraint(MILP, 2*(MILP[:xPlus]'*prevSol + MILP[:xMinus]'*prevSol)
                      - sum(MILP[:xPlus] .+ MILP[:xMinus])
                      <= sum(prevSol) - 1)
end


"""
This can be used as a CellField as long as one evaluates it
on the stored CellPoint.
"""
struct CellState{T,P<:CellPoint} <: CellField
  points::P
  values::AbstractArray

  function CellState{T}(::UndefInitializer,points::CellPoint) where T
    values = _init_values(T,get_data(points))
    P = typeof(points)
    new{T,P}(points,values)
  end

  function CellState(v::Number,points::CellPoint)
    values = _init_values(v,get_data(points))
    T = typeof(v)
    P = typeof(points)
    new{T,P}(points,values)
  end
end

function _init_values(::Type{T},x::AbstractArray{<:Point}) where T
  N = ndims(x)
  Array{T,N}(undef,size(x))
end

function _init_values(::Type{T},x::AbstractArray{<:AbstractVector{<:Point}}) where T
  [Vector{T}(undef,length(xi)) for xi in x]
end

function _init_values(v::Number,x::AbstractArray{<:Point})
  fill(v,size(x))
end

function _init_values(v::Number,x::AbstractArray{<:AbstractVector{<:Point}})
  [fill(v,length(xi)) for xi in x]
end

function get_data(f::CellState)
  @unreachable """\n
  get_data cannot be called on a CellState

  If you see this error messase it is likelly that you are trying to perform
  an operation that does not make sense for a CellState. In most cases,
  to do the wanted operation, you would need first to project the CellState
  to a FESpace (e.g. via a L2 projection).
  """
end

get_triangulation(f::CellState) = get_triangulation(f.points)
DomainStyle(::Type{CellState{T,P}}) where {T,P} = DomainStyle(P)

function evaluate!(cache,f::CellState,x::CellPoint)
  if f.points === x
    f.values
  else
    @unreachable """\n
    It is not possible to evaluate the given CellState on the given CellPoint.

    a CellState can only be evaluated at the CellPoint it was created from.
    If you want to evaluate at another location, you would need first to project the CellState
    to a FESpace (e.g. via a L2 projection).
    """
  end
end

function CellState{T}(::UndefInitializer,a) where T
  points = get_cell_points(a)
  CellState{T}(undef,points)
end

function CellState(v::Number,a)
  points = get_cell_points(a)
  CellState(v,points)
end

function update_state!(updater::Function,f::CellField...)
  ids = findall(map(i->isa(i,CellState),f))
  @assert length(ids) > 0 """\n
  At least one CellState object has to be given to the update_state! function
  """
  a = f[ids]
  x = first(a).points
  @assert all(map(i->i.points===x,a)) """\n
  All the CellState objects given to the update_state! function need to be
  defined on the same CellPoint.
  """
  fx = map(i->evaluate(i,x),f)
  if num_cells(x) > 0
    fxi = map(first,fx)
    fxiq = map(first,fxi)
    need_to_update, states = first_and_tail(updater(fxiq...))
    @assert isa(need_to_update,Bool) && isa(states,Tuple{Vararg{Number}}) """\n
    Wrong return value of the user-defined updater Function. The signature is

        need_to_update, state = first_and_tail(updater(args...))

    where need_to_update is a Bool telling if we need to update_state and
    states is a Tuple of Number objects with the new states.
    See `first_and_tail` for further details.
    """
    msg = """\n
    The number of new states given by the updater Function does not match
    the number of CellState objects given as arguments in the update_state! Funciton.
    """
    @check length(states) <= length(a) msg
  end
  caches = map(array_cache,fx)
  x_data = get_data(x)
  cache_x = array_cache(x_data)
  _update_state_variables!(updater,caches,fx,cache_x,x_data)
  nothing
end

@noinline function  _update_state_variables!(updater,caches,fx,cache_x,x)
  ncells = length(x)
  for cell in 1:ncells
    fxi = map((c,f)->getindex!(c,f,cell),caches,fx)
    xi = getindex!(cache_x,x,cell)
    for q in 1:length(xi)
      fxiq = map(f->f[q],fxi)
      need_to_update, states = first_and_tail(updater(fxiq...))
      if need_to_update
        _update_states!(fxi,q,states,Val{length(states)}())
      end
    end
  end
end

function _update_states!(b,q,states,::Val{i}) where i
  _update_state!(b,q,states,Val{i}())
  _update_states!(b,q,states,Val{i-1}())
  nothing
end

function _update_states!(b,q,states,::Val{0})
  nothing
end

function _update_state!(b,q,states,::Val{i}) where i
  b[i][q] = states[i]
  nothing
end


#!/usr/bin/env julia6
using ReadGlobal

function makefluxsgs!(r::Array{T,N},u::Array{T,N},v::Array{T,N}) where {T<:Number,N}
    Threads.@threads for i::Int in 1:length(u)
        @inbounds r[i] = r[i] - u[i]*v[i]
    end
    return r
end

function makeflux()

    place = split(pwd(),"/")
    N = place[end][2:end]
    Fil = place[end-1][1]

    nx, ny, nz, lx, ly, lz = getdimsize()

    aux1 = readfield("$(Fil)u1rho_N$N",nx,ny,nz)
    aux2 = readfield("$(Fil)rho_N$N",nx,ny,nz)
    aux3 = readfield("$(Fil)u1_N$N",nx,ny,nz)
    write("flux1",makefluxsgs!(aux1,aux2,aux3))

    read!("$(Fil)u2rho_N$N",aux1)
    read!("$(Fil)u2_N$N",aux3)
    write("flux2",makefluxsgs!(aux1,aux2,aux3))

    read!("$(Fil)u3rho_N$N",aux1)
    read!("$(Fil)u3_N$N",aux3)
    write("flux3",makefluxsgs!(aux1,aux2,aux3))

    return 0
end

makeflux()


"""
Solve any non-conjugate likelihood using Variational Inference
by making a numerical approximation (quadrature or MC integration)
of the expected log-likelihood ad its gradients
"""
abstract type NumericalVI{T<:Real} <: Inference{T} end

include("quadratureVI.jl")
include("MCVI.jl")

""" `NumericalVI(integration_technique::Symbol=:quad;ϵ::T=1e-5,nMC::Integer=1000,nGaussHermite::Integer=20,optimizer::Optimizer=Adam(α=0.1))`

General constructor for Variational Inference via numerical approximation.

**Argument**

    -`integration_technique::Symbol` : Method of approximation can be `:quad` for quadrature see [QuadratureVI](@ref) or `:mc` for MC integration see [MCIntegrationVI](@ref)

**Keyword arguments**

    - `ϵ::T` : convergence criteria, which can be user defined
    - `nMC::Int` : Number of samples per data point for the integral evaluation (for the MCIntegrationVI)
    - `nGaussHermite::Int` : Number of points for the integral estimation (for the QuadratureVI)
    - `optimizer::Optimizer` : Optimizer used for the variational updates. Should be an Optimizer object from the [GradDescent.jl]() package. Default is `Adam()`
"""
function NumericalVI(integration_technique::Symbol=:quad;ϵ::T=1e-5,nMC::Integer=1000,nGaussHermite::Integer=20,optimizer::Optimizer=Adam(α=0.1)) where {T<:Real}
    if integration_technique == :quad
        QuadratureVI{T}(ϵ,nGaussHermite,0,optimizer,false)
    elseif integration_technique == :mc
        MCIntegrationVI{T}(ϵ,nMC,0,optimizer,false)
    else
        @error "Only possible integration techniques are quadrature : :quad or mcmc integration :mcmc"
    end
end

""" `NumericalSVI(integration_technique::Symbol=:quad;ϵ::T=1e-5,nMC::Integer=1000,nGaussHermite::Integer=20,optimizer::Optimizer=Adam(α=0.1))`

General constructor for Stochastic Variational Inference via numerical approximation.

**Argument**

    -`nMinibatch::Integer` : Number of samples per mini-batches
    -`integration_technique::Symbol` : Method of approximation can be `:quad` for quadrature see [QuadratureVI](@ref) or `:mc` for MC integration see [MCIntegrationVI](@ref)

**Keyword arguments**

    - `ϵ::T` : convergence criteria, which can be user defined
    - `nMC::Int` : Number of samples per data point for the integral evaluation (for the MCIntegrationVI)
    - `nGaussHermite::Int` : Number of points for the integral estimation (for the QuadratureVI)
    - `optimizer::Optimizer` : Optimizer used for the variational updates. Should be an Optimizer object from the [GradDescent.jl]() package. Default is `Adam()`
"""
function NumericalSVI(nMinibatch::Integer,integration_technique::Symbol=:quad;ϵ::T=1e-5,nMC::Integer=200,nGaussHermite::Integer=20,optimizer::Optimizer=Adam(α=0.1)) where {T<:Real}
    if integration_technique == :quad
        QuadratureVI{T}(ϵ,nGaussHermite,0,optimizer,true,nMinibatch)
    elseif integration_technique == :mc
        MCIntegrationVI{T}(ϵ,nMC,0,optimizer,true,nMinibatch)
    else
        @error "Only possible integration techniques are quadrature : :quad or mcmc integration :mc"
    end
end

function Base.show(io::IO,inference::NumericalVI{T}) where T
    print(io,"$(inference.Stochastic ? "Stochastic numerical" : "Numerical") inference by $(isa(inference,MCIntegrationVI) ? "Monte Carlo Integration" : "Quadrature")")
end

function init_inference(inference::NumericalVI{T},nLatent::Integer,nFeatures::Integer,nSamples::Integer,nSamplesUsed::Integer) where {T<:Real}
    inference.nSamples = nSamples
    inference.nSamplesUsed = nSamplesUsed
    inference.MBIndices = 1:nSamplesUsed
    inference.ρ = nSamples/nSamplesUsed
    inference.HyperParametersUpdated = true
    inference.optimizer_η₁ = [copy(inference.optimizer_η₁[1]) for _ in 1:nLatent]
    inference.optimizer_η₂ = [copy(inference.optimizer_η₂[1]) for _ in 1:nLatent]
    inference.∇η₁ = [zeros(T,nFeatures) for _ in 1:nLatent];
    inference.∇η₂ = [Symmetric(Diagonal(ones(T,nFeatures))) for _ in 1:nLatent]
    inference.∇μE = [zeros(T,nSamplesUsed) for _ in 1:nLatent];
    inference.∇ΣE = [zeros(T,nSamplesUsed) for _ in 1:nLatent]
    return inference
end

function variational_updates!(model::VGP{<:Likelihood,<:NumericalVI})
    compute_grad_expectations!(model)
    natural_gradient!(model)
    global_update!(model)
end

function variational_updates!(model::SVGP{<:Likelihood,<:NumericalVI}) where {L<:Likelihood,T}
    compute_grad_expectations!(model)
    natural_gradient!(model)
    global_update!(model)
end

function natural_gradient!(model::VGP{<:Likelihood,<:NumericalVI})
    model.inference.∇η₁ .= model.Σ.*(model.inference.∇μE .- model.invKnn.*model.μ)
    model.inference.∇η₂ .= Symmetric.(Diagonal.(model.inference.∇ΣE).-0.5.*model.invKnn .- model.η₂)
end

function natural_gradient!(model::SVGP{<:Likelihood,<:NumericalVI})
    model.inference.∇η₁ .= model.Σ.*(model.inference.ρ.*transpose.(model.κ).*model.inference.∇μE .- model.invKmm.*model.μ)
    model.inference.∇η₂ .= Symmetric.(model.inference.ρ.*transpose.(model.κ).*Diagonal.(model.inference.∇ΣE).*model.κ.-0.5.*model.invKmm .- model.η₂)
end

function global_update!(model::AbstractGP{<:Likelihood,<:NumericalVI})
    model.η₁ .= model.η₁ .+ update.(model.inference.optimizer_η₁,model.inference.∇η₁)
    for k in 1:model.nLatent
        Δ = update(model.inference.optimizer_η₂[k],model.inference.∇η₂[k])
        α=1.0
        while true
            try
                @assert isposdef(-(model.η₂[k]+α*Δ))
                model.η₂[k] = Symmetric(model.η₂[k]+α*Δ)
                model.η₁[k] .+= update(model.inference.optimizer_η₁[k],model.inference.∇η₁[k])
                break;
            catch e
                if isa(e,AssertionError)
                    println("Error, results not pos def with α=$α")
                    α *= 0.5
                    if α < 1e-6
                        @error "α too small, stopping loop"
                        rethrow()
                    end
                else
                    rethrow()
                end
            end
        end
        if isa(model.inference.optimizer_η₂[k],Adam)
            model.inference.optimizer_η₂[k].α = min(model.inference.optimizer_η₂[k].α*α*2.0,1.0)
            # model.inference.optimizer_η₁[k].α = min(model.inference.optimizer_η₁[k].α*α*2.0,1.0)
        elseif isa(model.inference.optimizer_η₂[k],VanillaGradDescent)
            # model.inference.optimizer_η₂[k].η = min(model.inference.optimizer_η₂[k].η*α*2.0,1.0)
            # model.inference.optimizer_η₁[k].η = min(model.inference.optimizer_η₁[k].η*α*2.0,1.0)
        elseif isa(model.inference.optimizer_η₂[k],ALRSVI)
        elseif isa(model.inference.optimizer_η₂[k],InverseDecay)
        end
    end
    model.Σ .= -0.5.*inv.(model.η₂)
    model.μ .= model.η₁
    # model.μ .= model.Σ.*model.η₁
end

function ELBO(model::AbstractGP{<:Likelihood,<:NumericalVI})
    return expecLogLikelihood(model) - GaussianKL(model)
end

function expec_μ(model::AbstractGP{<:Likelihood,<:NumericalVI},index::Integer)
    return model.inference.∇μE[index]
end

function expec_μ(model::AbstractGP{<:Likelihood,<:NumericalVI})
    return model.inference.∇μE
end

function expec_Σ(model::AbstractGP{<:Likelihood,<:NumericalVI},index::Integer)
    return model.inference.∇ΣE[index]
end

function expec_Σ(model::AbstractGP{<:Likelihood,<:NumericalVI})
    return model.inference.∇ΣE
end

function global_update!(model::SVGP{L,NumericalVI{T}}) where {L<:Likelihood,T}
    if model.inference.Stochastic
    else
        model.η₁ .= model.inference.∇η₁ .+ model.η₁
        model.η₂ .= Symmetric.(model.inference.∇η₂ .+ model.η₂)
    end
    model.Σ .= inv.(model.η₂)*(-0.5)
    model.μ .= model.Σ.*model.η₁
end

function convert(::Type{T1},x::T2) where {T1<:VGP{<:Likelihood,T3} where {T3<:NumericalVI},T2<:VGP{<:Likelihood,<:AnalyticVI}}
    #TODO Check if likelihood is compatible
    inference = T3(x.inference.ϵ,x.inference.nIter,x.inference.optimizer,defaultn(T3),x.inference.Stochastic,x.inference.nSamples,x.inference.nSamplesUsed,x.inference.MBIndices,x.inference.ρ,x.inference.HyperParametersUpdated,x.inference.∇η₁,x.inference.∇η₂,copy(expec_μ(x)),copy(expec_Σ(x)))
    likelihood =isaugmented(x.likelihood) ? remove_augmentation(x.likelihood) : likelihood
    return T1(x.X,x.y,x.nSample,x.nDim,x.nFeature,x.nLatent,x.IndependentPriors,x.nPrior,x.μ,x.Σ,x.η₁,x.η₂,x.Knn,x.invKnn,x.kernel,likelihood,inference,x.verbose,x.Autotuning,x.atfrequency,x.Trained)
end


import Base.convert

"""
    convert(::Type{UnipartiteNetwork}, N::T) where {T <: BipartiteNetwork}

Projects a deterministic bipartite network in its unipartite representation.
"""
function convert(::Type{UnipartiteNetwork}, N::T) where {T <: BipartiteNetwork}
    itype = _interaction_type(N)
    S = copy(species(N))
    B = zeros(itype, (richness(N), richness(N)))
    B[1:size(N)[1],size(N)[1]+1:richness(N)] = N.edges
    return UnipartiteNetwork(sparse(B), S)
end

"""
    convert(::Type{UnipartiteProbabilisticNetwork}, N::T) where {T <: BipartiteProbabilisticNetwork}

Projects a probabilistic bipartite network in its unipartite representation.
"""
function convert(::Type{UnipartiteProbabilisticNetwork}, N::T) where {T <: BipartiteProbabilisticNetwork}
    itype = _interaction_type(N)
    S = copy(species(N))
    B = zeros(itype, (richness(N), richness(N)))
    B[1:size(N)[1],size(N)[1]+1:richness(N)] = N.edges
    return UnipartiteProbabilisticNetwork(sparse(B), S)
end

"""
    convert(::Type{UnipartiteQuantitativeNetwork}, N::T) where {T <: BipartiteQuantitativeNetwork}

Projects a quantitative bipartite network in its unipartite representation.
"""
function convert(::Type{UnipartiteQuantitativeNetwork}, N::T) where {T <: BipartiteQuantitativeNetwork}
    itype = _interaction_type(N)
    S = copy(species(N))
    B = zeros(itype, (richness(N), richness(N)))
    B[1:size(N)[1],size(N)[1]+1:richness(N)] = N.edges
    return UnipartiteQuantitativeNetwork(sparse(B), S)
end

"""
    convert(::Type{UnipartiteNetwork}, N::T) where {T <: UnipartiteQuantitativeNetwork}

Convert a unipartite quantitative network to a unipartite binary network. This
amounts to *removing* the quantitative information.
"""
function convert(::Type{UnipartiteNetwork}, N::T) where {T <: UnipartiteQuantitativeNetwork}
    S = copy(species(N))
    B = dropzeros(N.edges)
    return UnipartiteNetwork(B .!= zero(eltype(B)), S)
end

"""
    convert(::Type{BipartiteNetwork}, N::T) where {T <: BipartiteQuantitativeNetwork}

Convert a bipartite quantitative network to a bipartite binary network. This
amounts to *removing* the quantitative information.
"""
function convert(::Type{BipartiteNetwork}, N::NT) where {NT <: BipartiteQuantitativeNetwork}
    T = copy(species(N; dims=1))
    B = copy(species(N; dims=2))
    M = dropzeros(N.edges)
    return BipartiteNetwork(M .!= zero(eltype(M)), T, B)
end

"""
    convert(::Type{AbstractUnipartiteNetwork}, N::AbstractBipartiteNetwork)

Projects any bipartite network in its unipartite representation. This function
will call the correct type-to-type `convert` function depending on the type of
the input network.

The type to be converted to *must* be `AbstractUnipartiteNetwork` -- for
example, converting a bipartite probabilistic network to a probabilistic
unipartite network is not a meaningful operation.
"""
function convert(::Type{AbstractUnipartiteNetwork}, N::AbstractBipartiteNetwork)
    if typeof(N) <: QuantitativeNetwork
        return convert(UnipartiteQuantitativeNetwork, N)
    end
    if typeof(N) <: BinaryNetwork
        return convert(UnipartiteNetwork, N)
    end
    if typeof(N) <: ProbabilisticNetwork
        return convert(UnipartiteProbabilisticNetwork, N)
    end
end

"""
    convert(::Type{BinaryNetwork}, N::QuantitativeNetwork)

Projects any bipartite network in its unipartite representation. This function
will call the correct type-to-type `convert` function depending on the type of
the input network.

This function does *not* work for probabilistic networks. The operation of
generating a deterministic network from a probabilistic one is different from a
simple conversion: it can be done either through random draws, or by selecting
only interactions with a probability greater than 0 (`N>0.0` will do this).
"""
function convert(::Type{BinaryNetwork}, N::QuantitativeNetwork)
    if typeof(N) <: BipartiteQuantitativeNetwork
        return convert(BipartiteNetwork, N)
    end
    if typeof(N) <: UnipartiteQuantitativeNetwork
        return convert(UnipartiteNetwork, N)
    end
end

# Conversion to bipartite

type_pairs = [
    (:BipartiteNetwork, :UnipartiteNetwork),
    (:BipartiteProbabilisticNetwork, :UnipartiteProbabilisticNetwork),
    (:BipartiteQuantitativeNetwork, :UnipartiteQuantitativeNetwork)
    ]

for comb in type_pairs
    t1, t2 = comb
    eval(
    quote
        """
            convert(::Type{$($t1)}, N::T) where {T <: $($t2)}

        Projects a unipartite network (specifically, a `$($t1)`) to its bipartite
        representation. The following checks are performed.

        First, the network *cannot* be degenerate, since species with no interactions
        cannot be assigned to a specific level. Second, the species cannot have both in
        and out degree. If these conditions are met, the bipartite network will be
        returned.
        """
        function convert(::Type{$t1}, N::T) where {T <: $t2}
            isdegenerate(N) && throw(ArgumentError("Impossible to convert a degenerate unipartite network into a bipartite one"))
            d1 = degree(N; dims=1)
            d2 = degree(N; dims=2)
            for s in species(N)
                (d1[s]*d2[s] == 0) || throw(ArgumentError("Species $s has both in and out degree"))
            end
            top_species = collect(keys(filter(p -> p.second == zero(eltype(N.edges)), d2)))
            bot_species = collect(keys(filter(p -> p.second == zero(eltype(N.edges)), d1)))
            A = zeros(_interaction_type(N), (length(top_species), length(bot_species)))
            B = $t1(sparse(A), top_species, bot_species)
            for s1 in species(B; dims=1), s2 in species(B; dims=2)
                B[s1,s2] = N[s1, s2]
            end
            return B
        end
    end
    )
end


# THIS IS NOT A FULL JAVA WRAPPER!
# Even though the name seems to imply it, it only provides some convenience
# definitions for use in the MOI wrapper.

## Type definitions
# These correspond to Java types (which are directly returned by the functions
# in this binder). In particular, the inheritance defined in Java is not brought
# back to Julia.
const Store = JavaObject{Symbol("org.jacop.core.Store")}

const BooleanVar = JavaObject{Symbol("org.jacop.core.BooleanVar")}
const IntVar = JavaObject{Symbol("org.jacop.core.IntVar")}
const SetVar = JavaObject{Symbol("org.jacop.set.core.SetVar")}
const FloatVar = JavaObject{Symbol("org.jacop.floats.core.FloatVar")}
const CircuitVar = JavaObject{Symbol("org.jacop.constraints.CircuitVar")}

const Constraint = JavaObject{Symbol("org.jacop.constraints.Constraint")}
const LinearInt = JavaObject{Symbol("org.jacop.constraints.LinearInt")}
const Alldifferent = JavaObject{Symbol("org.jacop.constraints.Alldifferent")}
const In = JavaObject{Symbol("org.jacop.constraints.In")}
const XeqC = JavaObject{Symbol("org.jacop.constraints.XeqC")}

# Unions of types to model Java type hierarchy.

const Variable = Union{
    BooleanVar,
    IntVar,
    SetVar,
    FloatVar,
    CircuitVar,
}

# Add a constraint to a store.

function jacop_add_constraint_to_store(store::Store, constraint::Constraint)
    jcall(store, "impose", Nothing, (Constraint,), constraint)
    return
end


# Docu for profiling Cuda Code in Julia: https://cuda.juliagpu.org/stable/development/profiling/

#using BenchmarkTools
using CUDA

a = rand(1024,1024,100);

ad = CUDA.rand(1024,1024,100);

@time sin.(a)

sin.(ad);

println("Finished")


# API demo
# This is for the layout here: http://postimage.org/image/s6pskvknf/

require("LayoutLP")
using LayoutLP

# Create a fake window
win = Window(0, 0, 800, 600)

# Create the overall layout inside window "win". This is specified in terms of named tabstops.
xtabs = [:scatterL, :scatterR, :margeEL, :margeER, :imageL, :imageR]
ytabs = [:margeNT, :margeNB, :scatterT, :scatterB, :buttonsT]
l = LayoutLP(win, xtabs, ytabs)

# Now we'd probably do something like this:
cimage = canvas(l, [:imageL, :imageR], [:scatterT, :scatterB])
himage = image(cimage, "peppers.png")
# ... and then rely on height(himage) and width(himage) to give us the image dimensions.
# But because the canvas & image functionality is not yet worked out,
# let's do it this way:
imsz = [384,512]
height = () -> imsz[1]
width = () -> imsz[2]
# These are only nominally hard-coding the image size; you could change
# imsz[1] or imsz[2] and it would update to the new dimensions.

# For plot axes, the "outer" canvas include all labels, the "inner" canvas is the graphing area
outerscatter, innerscatter = plotaxis(l, [:scatterL, :scatterR], [:scatterT, :scatterB])
outermargE, innermargE = plotaxis(l, [:margeEL, :margeER], [:scatterT, :scatterB])
outermargN, innermargN = plotaxis(l, [:scatterL, :scatterR], [:margeNT, :margeNB])

# Create a sub-layout for the buttons
# The first two refer to parent coordinates, the second introduce tab stops in the child
lbuttons = LayoutLP(l, [:W, :E], [:buttonsT, :S], [:cancelL, :cancelR, :doneL], [])

# Create the image object inside the image canvas, so we have something to refer to in getting its height and width
himg = image(cimg)   # note: no image data yet, all we need now is the handle

# Now specify the geometry. This is done by adding constraints and penalties. Note that a "subplot" function would take care of many of these for you, but here we're going low-level because this GUI has a lot of custom requirements.

# First we require that the tabstops are in increasing order. Can also use "decreasing". One can pick out a subset of the tab stops and do something different for the rest (partial ordering)
increasing(l, l.xsyms)
l = increasing(l, l.ysyms)
# Now add particular constraints
l = addconstraints(l,
        # Ensure the proper aspect ratio for the image
        :(height($himg)*(imageR-imageL) = width($himg)*(scatterB-scatterT)),
        # Leave enough room for scatterplot y labels
        :(left($outerscatter) > W),
        :(left($outermargN) > W),
        # Scatterplot bottom label
        :(bottom($outerscatter) < buttonsT),
        # Ensure a small gap between east marginal axis and the image axis
        :(imageL > margeER + max(10px, 5mm))
    )
# Add penalties: these encourage desirable behavior, but are not strictly enforced
l = addpenalties(l,
        # we'd like the image to occupy approximately 40% of the horizontal space
        :(5*abs((imageR-imageL)-0.4*w)),
        # small gaps for the marginal axes
        :(10*abs(margeEL-scatterR-5px)),
        :(10*abs(margeEL-scatterR-5px)),
    )


function add_dual_equality_constraints(dual_model::MOI.ModelLike, primal_model::MOI.ModelLike,
                                       primal_dual_map::PrimalDualMap,  dual_names::DualNames,
                                       primal_objective::PrimalObjective{T},
                                       con_types::Vector{Tuple{DataType, DataType}},
                                       variable_parameters::Vector{VI}) where T

    sense_change = MOI.get(dual_model, MOI.ObjectiveSense()) == MOI.MAX_SENSE ? one(T) : -one(T)

    all_variables = MOI.get(primal_model, MOI.ListOfVariableIndices())
    restricted_variables = setdiff(all_variables, variable_parameters)

    # Loop at every constraint to get the scalar affine terms
    scalar_affine_terms = get_scalar_affine_terms(primal_model,
        primal_dual_map.primal_con_dual_var,
        all_variables, con_types, T)

    # get RHS from objective coeficients
    scalar_terms = get_scalar_terms(primal_model,
        all_variables, primal_objective)

    # Add terms from objective:
    # Terms from quadratic part
    add_scalar_affine_terms_from_quad_obj(scalar_affine_terms, primal_model,
        primal_dual_map.primal_var_dual_quad_slack, primal_objective)
    # terms from mixing variables and parameters
    add_scalar_affine_terms_from_quad_params(scalar_affine_terms, primal_model,
        primal_dual_map.primal_parameter, primal_objective)

    for primal_vi in restricted_variables
        # Add equality constraint
        dual_ci = MOIU.normalize_and_add_constraint(dual_model,
            MOI.ScalarAffineFunction(scalar_affine_terms[primal_vi], zero(T)),
            MOI.EqualTo(sense_change * get(scalar_terms, primal_vi, zero(T))))
        #Set constraint name with the name of the associated priaml variable
        if !is_empty(dual_names)
            set_dual_constraint_name(dual_model, primal_model, primal_vi, dual_ci,
                                     dual_names.dual_constraint_name_prefix)
        end
        # Add primal variable to dual contraint to the link dictionary
        push!(primal_dual_map.primal_var_dual_con, primal_vi => dual_ci)
    end
    return scalar_affine_terms
end

function add_scalar_affine_terms_from_quad_obj(
    scalar_affine_terms::Dict{VI,Vector{MOI.ScalarAffineTerm{T}}},
    primal_model::MOI.ModelLike,
    primal_var_dual_quad_slack::Dict{VI, VI},
    primal_objective::PrimalObjective{T}) where T
    for term in primal_objective.obj.quadratic_terms
        if term.variable_index_1 == term.variable_index_2
            dual_vi = primal_var_dual_quad_slack[term.variable_index_1]
            push_to_scalar_affine_terms!(
                scalar_affine_terms[term.variable_index_1],
                -MOI.coefficient(term), dual_vi)
        else
            dual_vi_1 = primal_var_dual_quad_slack[term.variable_index_1]
            push_to_scalar_affine_terms!(
                scalar_affine_terms[term.variable_index_2],
                -MOI.coefficient(term), dual_vi_1)
            dual_vi_2 = primal_var_dual_quad_slack[term.variable_index_2]
            push_to_scalar_affine_terms!(
                scalar_affine_terms[term.variable_index_1],
                -MOI.coefficient(term), dual_vi_2)
        end
    end
end

function add_scalar_affine_terms_from_quad_params(
    scalar_affine_terms::Dict{VI,Vector{MOI.ScalarAffineTerm{T}}},
    primal_model::MOI.ModelLike,
    primal_parameter::Dict{VI, VI},
    primal_objective::PrimalObjective{T}) where T
    for (key,val) in primal_objective.quad_cross_parameters
        for term in val
            dual_vi = primal_parameter[term.variable_index]
            push_to_scalar_affine_terms!(scalar_affine_terms[key], -MOI.coefficient(term), dual_vi)
        end
    end
end

function set_dual_constraint_name(dual_model::MOI.ModelLike, primal_model::MOI.ModelLike,
                                  primal_vi::VI, dual_ci::CI, prefix::String)
    MOI.set(dual_model, MOI.ConstraintName(), dual_ci,
            prefix*MOI.get(primal_model, MOI.VariableName(), primal_vi))
    return
end

function get_scalar_terms(primal_model::MOI.ModelLike,
    variables::Vector{VI},
    primal_objective::PrimalObjective{T}) where T

    scalar_terms = Dict{VI,T}()
    for term in get_affine_terms(primal_objective)
        if haskey(scalar_terms, term.variable_index)
            scalar_terms[term.variable_index] += MOI.coefficient(term)
        else
            scalar_terms[term.variable_index] = MOI.coefficient(term)
        end
    end
    return scalar_terms
end

function get_scalar_affine_terms(primal_model::MOI.ModelLike,
                                 primal_con_dual_var::Dict{CI, Vector{VI}},
                                 variables::Vector{VI},
                                 con_types::Vector{Tuple{DataType, DataType}},
                                 T::Type)

    scalar_affine_terms = Dict{VI,Vector{MOI.ScalarAffineTerm{T}}}()
    for vi in variables
        scalar_affine_terms[vi] = MOI.ScalarAffineTerm{T}[]
    end
    for (F, S) in con_types
        primal_cis = MOI.get(primal_model, MOI.ListOfConstraintIndices{F,S}()) # Constraints of type {F, S}
        for ci in primal_cis
            fill_scalar_affine_terms!(scalar_affine_terms, primal_con_dual_var,
                                      primal_model, ci)
        end
    end
    return scalar_affine_terms
end

function push_to_scalar_affine_terms!(scalar_affine_terms::Vector{MOI.ScalarAffineTerm{T}},
                                      affine_term::T, vi::VI) where T

    if !iszero(affine_term) # if term is different than 0 add to the scalar affine terms vector
        push!(scalar_affine_terms, MOI.ScalarAffineTerm(affine_term, vi))
    end
    return
end

function fill_scalar_affine_terms!(scalar_affine_terms::Dict{VI,Vector{MOI.ScalarAffineTerm{T}}},
                                   primal_con_dual_var::Dict{CI, Vector{VI}},
                                   primal_model::MOI.ModelLike, ci::CI{SAF{T}, S},
                                   ) where {T, S <: Union{MOI.GreaterThan{T},
                                                          MOI.LessThan{T},
                                                          MOI.EqualTo{T}}}

    moi_function = get_function(primal_model, ci)
    for term in moi_function.terms
        dual_vi = primal_con_dual_var[ci][1] # In this case we only have one vi
        push_to_scalar_affine_terms!(scalar_affine_terms[term.variable_index],
                                     MOI.coefficient(term), dual_vi)
    end
    return
end

function fill_scalar_affine_terms!(scalar_affine_terms::Dict{VI,Vector{MOI.ScalarAffineTerm{T}}},
                                   primal_con_dual_var::Dict{CI, Vector{VI}},
                                   primal_model::MOI.ModelLike, ci::CI{SVF, S},
                                   ) where {T, S <: Union{MOI.GreaterThan{T},
                                                          MOI.LessThan{T},
                                                          MOI.EqualTo{T}}}

    moi_function = get_function(primal_model, ci)
    dual_vi = primal_con_dual_var[ci][1] # In this case we only have one vi
    push_to_scalar_affine_terms!(scalar_affine_terms[moi_function.variable], one(T), dual_vi)
    return
end

function fill_scalar_affine_terms!(scalar_affine_terms::Dict{VI,Vector{MOI.ScalarAffineTerm{T}}},
                                   primal_con_dual_var::Dict{CI, Vector{VI}},
                                   primal_model::MOI.ModelLike, ci::CI{VAF{T}, S},
                                   ) where {T, S <: MOI.AbstractVectorSet}

    moi_function = get_function(primal_model, ci)
    set = get_set(primal_model, ci)
    for term in moi_function.terms
        dual_vi = primal_con_dual_var[ci][term.output_index]
        # term.output_index is the row of the VAF,
        # it corresponds to the dual variable associated with this constraint
        push_to_scalar_affine_terms!(scalar_affine_terms[term.scalar_term.variable_index],
            set_dot(term.output_index, set, T)*MOI.coefficient(term), dual_vi)
    end
    return
end

function fill_scalar_affine_terms!(scalar_affine_terms::Dict{VI,Vector{MOI.ScalarAffineTerm{T}}},
                                   primal_con_dual_var::Dict{CI, Vector{VI}},
                                   primal_model::MOI.ModelLike, ci::CI{VVF, S},
                                   ) where {T, S <: MOI.AbstractVectorSet}

    moi_function = get_function(primal_model, ci)
    set = get_set(primal_model, ci)
    for (i, variable) in enumerate(moi_function.variables)
        dual_vi = primal_con_dual_var[ci][i]
        push_to_scalar_affine_terms!(scalar_affine_terms[variable],
                                    set_dot(i, set, T)*one(T), dual_vi)
    end
    return
end

function set_dot(i::Int, s::MOI.AbstractVectorSet, T::Type)
    vec = zeros(T, MOI.dimension(s))
    vec[i] = one(T)
    return MOIU.set_dot(vec, vec, s)
end
function set_dot(i::Int, s::MOI.AbstractScalarSet, T::Type)
    return one(T)
end


# Encoder structure : a downsampling is applied then two convolutions are done
struct Encoder
    dspl
    conv
end

@Flux.functor Encoder

(e::Encoder)(x) = x |> e.dspl |> e.conv

# Decoder structure : two convolutions are done then an upsampling is applied
struct Decoder
    conv
    uspl
end

@Flux.functor Decoder

(d::Decoder)(x) = x |> d.conv |> d.uspl

# Bridge structure :
#   - downsampling
#   - two convolutions
#   - upsampling
struct Bridge
    dspl
    conv
    uspl
end

@Flux.functor Bridge

(b::Bridge)(x) = x |> b.dspl |> b.conv |> b.uspl

# Encoder builders
function encoder(lvl)
    enc = []
    if lvl == 1
        push!(enc, identity)
    else
        push!(enc, MaxPool((2, 2)))
    end
    push!(enc, Conv((3, 3), 1=>1, pad = 1))
    push!(enc, Conv((3, 3), 1=>1, pad = 1))
    enc
end

encoder(t::Symbol, lvl) = encoder(Val(t), lvl)

encoder(t::Val{:array}, lvl) = encoder(lvl)

encoder(t::Val{:chain}, lvl) = Chain(encoder(lvl)...)

encoder(t::Val{:struct}, lvl) = begin
    enc = encoder(lvl)
    Encoder(enc[1], Chain(enc[2], enc[3]))
end

# Decoder builders
function decoder(lvl)
    dec = Any[Conv((3, 3), 2=>1, pad = 1), Conv((3, 3), 1=>1, pad = 1)]
    if lvl == 1
        push!(dec, identity)
    else
        push!(dec, Upsample(:bilinear, scale = 2))
    end
    dec
end

decoder(t::Symbol, lvl) = decoder(Val(t), lvl)

decoder(t::Val{:array}, lvl) = decoder(lvl)

decoder(t::Val{:chain}, lvl) = Chain(decoder(lvl)...)

decoder(t::Val{:struct}, lvl) = begin
    dec = decoder(lvl)
    Decoder(Chain(dec[1], dec[2]), dec[3])
end

# Bridge builders
bridge() = [MaxPool((2, 2)),
            Conv((3, 3), 1=>1, pad = 1), Conv((3, 3), 1=>1, pad = 1),
            Upsample(:bilinear, scale = 2)]

bridge(t::Symbol) = bridge(Val(t))

bridge(t::Val{:array}) = bridge()

bridge(t::Val{:chain}) = Chain(bridge()...)

bridge(t::Val{:struct}) = begin
    bdg = bridge()
    Bridge(bdg[1], Chain(bdg[2], bdg[3]), bdg[4])
end

@testset "uchain" begin
    blkt = [:array :chain :struct]
    for enct ∈ blkt, dect ∈ blkt, bdgt ∈ blkt
        enc, dec = [], []
        for l ∈ 1:2
            push!(enc, encoder(enct, l))
            push!(dec, decoder(dect, l))
        end
        bdg = bridge(bdgt)
        model = uchain(encoders = enc, decoders = dec, bridge = bdg, connection = chcat)
        x = rand(Float32, 32, 32, 1, 1)
        @test (model(x) |> size) == (32, 32, 1, 1)
    end
end


direct_tr = (NWI(), Dogleg())
direct_globals = (HZAW(), Backtracking(), Backtracking(;interp=FFQuadInterp()), NWI(), Dogleg())
inverse_ls = (HZAW(), Backtracking(), Backtracking(; interp=FFQuadInterp()))
inverse_globals = (HZAW(), Backtracking(), Backtracking(;interp=FFQuadInterp()))
run_all(td_obj, x0, BFGS(Inverse()), inverse_globals)
run_all(td_obj, x0, DBFGS(Inverse()), inverse_globals)
#run_all(td_obj, x0, SR1(Inverse()), inverse_ls)
run_all(td_obj, x0, DFP(Inverse()), inverse_globals)
run_all(td_obj, x0, BFGS(Direct()), direct_globals)
run_all(td_obj, x0, DBFGS(Direct()), direct_globals)
run_all(td_obj, x0, SR1(Direct()), direct_globals)
run_all(td_obj, x0, DFP(Direct()), direct_globals)

run_all(td_obj, x0, Newton(), direct_globals)
run_all(td_obj, x0, Newton(), direct_globals)

run_all(od_obj, x0, BFGS(Inverse()), inverse_globals)
run_all(od_obj, x0, DBFGS(Inverse()), inverse_globals)
run_all(od_obj, x0, SR1(Inverse()), inverse_ls)
run_all(od_obj, x0, DFP(Inverse()), inverse_globals)
run_all(od_obj, x0, BFGS(Direct()), direct_globals)
run_all(od_obj, x0, DBFGS(Direct()), direct_globals)
run_all(od_obj, x0, SR1(Direct()), direct_globals)
run_all(od_obj, x0, DFP(Direct()), direct_globals)

run_all(td_obj, x0, BFGS(Inverse()), inverse_globals);
run_all(td_obj, x0, DBFGS(Inverse()), inverse_globals);
run_all(td_obj, x0, SR1(Inverse()), inverse_ls);
run_all(td_obj, x0, DFP(Inverse()), inverse_globals);
run_all(td_obj, x0, BFGS(Direct()), direct_globals);
run_all(td_obj, x0, DBFGS(Direct()), direct_globals);
run_all(td_obj, x0, SR1(Direct()), direct_globals);
run_all(td_obj, x0, DFP(Direct()), direct_globals);
run_all(td_obj, x0, Newton(), direct_globals);

run_all(td_obj, x0, BFGS(Inverse()), inverse_globals);
run_all(td_obj, x0, DBFGS(Inverse()), inverse_globals);
run_all(td_obj, x0, SR1(Inverse()), inverse_ls);
run_all(td_obj, x0, DFP(Inverse()), inverse_globals);
run_all(td_obj, x0, BFGS(Direct()), direct_globals);
run_all(td_obj, x0, DBFGS(Direct()), direct_globals);
run_all(td_obj, x0, SR1(Direct()), direct_globals);
run_all(td_obj, x0, DFP(Direct()), direct_globals);
run_all(td_obj, x0, Newton(), direct_globals);

run_all(td_obj, x0, BFGS(Inverse()), inverse_globals);
run_all(td_obj, x0, DBFGS(Inverse()), inverse_globals);
run_all(td_obj, x0, SR1(Inverse()), inverse_ls);
run_all(td_obj, x0, DFP(Inverse()), inverse_globals);
run_all(td_obj, x0, BFGS(Direct()), direct_globals);
run_all(td_obj, x0, DBFGS(Direct()), direct_globals);
run_all(td_obj, x0, SR1(Direct()), direct_globals);
run_all(td_obj, x0, DFP(Direct()), direct_globals);
run_all(td_obj, x0, Newton(), direct_globals);

run_all(td_obj, x0, BFGS(Inverse()), inverse_globals);
run_all(td_obj, x0, DBFGS(Inverse()), inverse_globals);
run_all(td_obj, x0, SR1(Inverse()), inverse_ls);
run_all(td_obj, x0, DFP(Inverse()), inverse_globals);
run_all(td_obj, x0, BFGS(Direct()), direct_globals);
run_all(td_obj, x0, DBFGS(Direct()), direct_globals);
run_all(td_obj, x0, SR1(Direct()), direct_globals);
run_all(td_obj, x0, DFP(Direct()), direct_globals);
run_all(td_obj, x0, Newton(), direct_globals);

run_all(td_obj, x0, BFGS(Inverse()), inverse_globals);
run_all(td_obj, x0, DBFGS(Inverse()), inverse_globals);
run_all(td_obj, x0, SR1(Inverse()), inverse_ls);
run_all(td_obj, x0, DFP(Inverse()), inverse_globals);
run_all(td_obj, x0, BFGS(Direct()), direct_globals);
run_all(td_obj, x0, DBFGS(Direct()), direct_globals);
run_all(td_obj, x0, SR1(Direct()), direct_globals);
run_all(td_obj, x0, DFP(Direct()), direct_globals);
run_all(td_obj, x0, Newton(), direct_globals);

run_all(td_obj, x0, BFGS(Inverse()), inverse_globals);
run_all(td_obj, x0, DBFGS(Inverse()), inverse_globals);
run_all(td_obj, x0, SR1(Inverse()), inverse_ls);
run_all(td_obj, x0, DFP(Inverse()), inverse_globals);
run_all(td_obj, x0, BFGS(Direct()), direct_globals);
run_all(td_obj, x0, DBFGS(Direct()), direct_globals);
run_all(td_obj, x0, SR1(Direct()), direct_globals);
run_all(td_obj, x0, DFP(Direct()), direct_globals);
run_all(td_obj, x0, Newton(), direct_globals);

examples["Polynomial"] = OptimizationProblem("Polynomial",
                                             polynomial,
                                             polynomial_gradient!,
                                             nothing,
                                             polynomial_hessian!,
                                             nothing, # Constraints
                                             [0.0, 0.0, 0.0],
                                             [10.0, 7.0, 108.0],
                                             polynomial([10.0, 7.0, 108.0]),
                                             true,
                                             true)

##########################################################################
###
### Powell (d=4)
###
### Problem 35 in [1]
### Difficult since the hessian is singular at the optimum
##########################################################################

run_all(td_obj, x0, BFGS(Inverse()), inverse_globals);
run_all(td_obj, x0, DBFGS(Inverse()), inverse_globals);
run_all(td_obj, x0, SR1(Inverse()), inverse_ls);
run_all(td_obj, x0, DFP(Inverse()), inverse_globals);
run_all(td_obj, x0, BFGS(Direct()), direct_globals);
run_all(td_obj, x0, DBFGS(Direct()), direct_globals);
run_all(td_obj, x0, SR1(Direct()), direct_globals);
run_all(td_obj, x0, DFP(Direct()), direct_globals);
run_all(td_obj, x0, Newton(), direct_globals);

##########################################################################
###
### Rosenbrock (2D)
###
### Problem 38 in [1]
###
### Saddle point makes optimization difficult
##########################################################################

td_obj = TwiceDiffed(rosenbrock!)

run_all(td_obj, x0, BFGS(Inverse()), inverse_globals[2:end]);
run_all(td_obj, x0, DBFGS(Inverse()), inverse_globals[2:end]);
run_all(td_obj, x0, SR1(Inverse()), inverse_ls[2:end]);
run_all(td_obj, x0, DFP(Inverse()), inverse_globals[2:end]);
run_all(td_obj, x0, BFGS(Direct()), direct_globals);
run_all(td_obj, x0, DBFGS(Direct()), direct_globals);
run_all(td_obj, x0, SR1(Direct()), direct_globals);
run_all(td_obj, x0, DFP(Direct()), direct_globals);
run_all(td_obj, x0, Newton(), direct_globals);


module Network_ADER
# Include Polynomial reconstruction stuff
include("compute_constants/polynomial_reconstruction.jl")

end


mutable struct GFGenerateState
    trace::GFTrace
    constraints::Any
    score::Float64
    weight::Float64
    visitor::AddressVisitor
    params::Dict{Symbol,Any}
end

function GFGenerateState(constraints, params::Dict{Symbol,Any})
    GFGenerateState(GFTrace(), constraints, 0., 0., AddressVisitor(), params)
end

get_args_change(state::GFGenerateState) = nothing
get_addr_change(state::GFGenerateState, addr) = nothing
set_ret_change!(state::GFGenerateState, value) = begin end

function addr(state::GFGenerateState, dist::Distribution{T}, args, addr) where {T}
    visit!(state.visitor, addr)
    constrained = has_leaf_node(state.constraints, addr)
    if has_internal_node(state.constraints, addr)
        lightweight_got_internal_node_err(addr)
    end
    local retval::T
    if constrained
        retval = get_leaf_node(state.constraints, addr)
    else
        retval = random(dist, args...)
    end
    score = logpdf(dist, retval, args...)
    call = CallRecord(score, retval, args)
    state.trace = assoc_primitive_call(state.trace, addr, call)
    state.trace.has_choices = true
    state.score += score
    if constrained
        state.weight += score
    end
    retval
end

function addr(state::GFGenerateState, gen::Generator{T,U}, args, addr, args_change) where {T,U}
    visit!(state.visitor, addr)
    if has_internal_node(state.constraints, addr)
        constraints = get_internal_node(state.constraints, addr)
    else
        constraints = EmptyAssignment()
    end
    (trace::U, weight) = generate(gen, args, constraints)
    call::CallRecord = get_call_record(trace)
    state.trace = assoc_subtrace(state.trace, addr, trace)
    state.trace.has_choices |= has_choices(trace)
    state.score += call.score
    state.weight += weight
    call.retval::T
end

splice(state::GFGenerateState, gf::GenFunction, args::Tuple) = exec(gf, state, args)

function generate(gen::GenFunction, args, constraints)
    state = GFGenerateState(constraints, gen.params)
    retval = exec(gen, state, args)
    # TODO add return type annotation for gen
    call = CallRecord{Any}(state.score, retval, args)
    state.trace.call = call
    (state.trace, state.weight)
end


"""
	wiggle(cmd0::String="", arg1=[]; kwargs...)

Reads (length,azimuth) pairs from file and plot a windwiggle diagram.

Full option list at [`pswiggle`](http://gmt.soest.hawaii.edu/doc/latest/pswiggle.html)

Parameters
----------

- $(GMT.opt_R)
- **Z** : **scale** : -- Number or Str --

    Gives anomaly scale in data-units/distance-unit.
	[`-Z`](http://gmt.soest.hawaii.edu/doc/latest/pswiggle.html#z)
- **A** : **azimuth** : -- Str or number --

    Sets the preferred positive azimuth. Positive wiggles will “gravitate” towards that direction.
	[`-A`](http://gmt.soest.hawaii.edu/doc/latest/pswiggle.html#a)
- $(GMT.opt_B)
- **C** : **center** : -- Number --

    Subtract center from the data set before plotting [0].
	[`-C`](http://gmt.soest.hawaii.edu/doc/latest/pswiggle.html#c)
- **D** : **scale_bar** : -- Str --

    Defines the reference point on the map for the vertical scale bar using one of four coordinate systems.
	[`-D`](http://gmt.soest.hawaii.edu/doc/latest/pswiggle.html#d)
- **F** : **bar_rectangle** : -- Str --

    Without further options, draws a rectangular border around the vertical scale bar.
	[`-F`](http://gmt.soest.hawaii.edu/doc/latest/pswiggle.html#f)
- **G** : **fill** : -- Number or Str --

    Set fill shade, color or pattern for positive and/or negative wiggles [Default is no fill].
	[`-G`](http://gmt.soest.hawaii.edu/doc/latest/pswiggle.html#g)
- **I** : **fixed_azim** : -- Number --

    Set a fixed azimuth projection for wiggles [Default uses track azimuth, but see -A].
	[`-I`](http://gmt.soest.hawaii.edu/doc/latest/pswiggle.html#i)
- $(GMT.opt_J)
- $(GMT.opt_Jz)
- $(GMT.opt_P)
- **T** : **pen** : -- Number or Str or Tuple or [] --

    Draw track [Default is no track]. Append pen attributes to use [Defaults: width = 0.25p, color =
    black, style = solid].
	[`-T`](http://gmt.soest.hawaii.edu/doc/latest/pswiggle.html#t)
- **W** : **pen** : -- Number or Str or tuple or [] --

    Specify outline pen attributes [Default is no outline].
	[`-W`](http://gmt.soest.hawaii.edu/doc/latest/pswiggle.html#w)
- $(GMT.opt_U)
- $(GMT.opt_V)
- $(GMT.opt_X)
- $(GMT.opt_Y)
- $(GMT.opt_bi)
- $(GMT.opt_di)
- $(GMT.opt_e)
- $(GMT.opt_h)
- $(GMT.opt_i)
- $(GMT.opt_p)
- $(GMT.opt_t)
- $(GMT.opt_swap_xy)
"""
function wiggle(cmd0::String="", arg1=[]; K=false, O=false, first=true, kwargs...)

	length(kwargs) == 0 && return monolitic("pswiggle", cmd0, arg1)	# Speedy mode

	d = KW(kwargs)
	output, opt_T, fname_ext = fname_out(d)		# OUTPUT may have been an extension only

	cmd, opt_B, opt_J, opt_R = parse_BJR(d, "", "", O, " -JX12c/12c")
	cmd, = parse_JZ(cmd, d)
	cmd  = parse_UVXY(cmd, d)
	cmd, opt_bi = parse_bi(cmd, d)
	cmd, opt_di = parse_di(cmd, d)
	cmd, = parse_e(cmd, d)
	cmd, = parse_f(cmd, d)
	cmd, = parse_g(cmd, d)
	cmd, = parse_h(cmd, d)
	cmd, opt_i = parse_i(cmd, d)
	cmd, = parse_p(cmd, d)
	cmd, = parse_t(cmd, d)
	cmd, = parse_swap_xy(cmd, d)
	cmd = parse_params(cmd, d)

	cmd, K, O, opt_B = set_KO(cmd, opt_B, first, K, O)		# Set the K O dance

	# If file name sent in, read it and compute a tight -R if this was not provided
	cmd, arg1, opt_R, = read_data(d, cmd0, cmd, arg1, opt_R, opt_i, opt_bi, opt_di)

	cmd = add_opt(cmd, 'A', d, [:A :azimuth])
	cmd = add_opt(cmd, 'C', d, [:C :center])
	cmd = add_opt(cmd, 'D', d, [:D :scale_bar])
	cmd = add_opt(cmd, 'F', d, [:F :bar_rectangle])
	cmd = add_opt(cmd, 'G', d, [:G :fill])
	cmd = add_opt(cmd, 'I', d, [:I :fixed_azim])
	cmd = cmd * opt_pen(d, 'T', [:T :pen])
	cmd = cmd * opt_pen(d, 'W', [:W :pen])
	cmd = add_opt(cmd, 'Z', d, [:Z :scale])

	cmd = finish_PS(d, cmd, output, K, O)
	return finish_PS_module(d, cmd, "", output, fname_ext, opt_T, K, "pswiggle", arg1)
end

# ---------------------------------------------------------------------------------------------------
wiggle!(cmd0::String="", arg1=[]; K=true, O=true,  first=false, kw...) =
	wiggle(cmd0, arg1; K=K, O=O,  first=first, kw...)

wiggle(arg1=[], cmd0::String=""; K=false, O=false,  first=true, kw...) =
	wiggle(cmd0, arg1; K=K, O=O,  first=first, kw...)

wiggle!(arg1=[], cmd0::String=""; K=true, O=true,  first=false, kw...) =
	wiggle(cmd0, arg1; K=K, O=O,  first=first, kw...)

pswiggle  = wiggle			# Alias
pswiggle! = wiggle!			# Alias


module TestBroadcasting

include("preamble.jl")

@testset "count_sum" begin
    @testset "$fold" for fold in [foldl, simple_reduce, foldxt_bs1, foldxt]
        foldl = nothing

        count_sum(xs, xf = Map(identity)) =
            fold(+, opcompose(xf, Map(x -> (1, x)), Broadcasting()), xs)
        @test count_sum([5, 2, 6, 8, 3]) == (5, 24)
        @test count_sum([5, 2, 6, 8, 3], Filter(isodd)) == (2, 8)
        @test_throws EmptyResultError count_sum(1:0)
        @test_throws EmptyResultError count_sum(1:10, Filter(_ -> false))
    end
end

@testset "partially started" begin
    @testset "$fold" for fold in [foldl, simple_reduce, foldxt_bs1, foldxt]
        foldl = nothing
        f(xs) =
            fold(max, opcompose(Map(x -> (x + 1, x)), Broadcasting(), Filter(isodd)), xs)
        @test f([5, 2, 6, 8, 3]) == (9, 5)
        @test_throws EmptyResultError f(2:2:8)
    end
end

@testset "init" begin
    @testset "$fold" for fold in [foldl, simple_reduce, foldxt_bs1, foldxt]
        foldl = nothing
        @test fold(+, Broadcasting(), 1:9) == 45
        @test fold(+, Broadcasting(), 1:9, init = [0]) == [45]
    end
end

sample_foldl_allocations(repeat::Integer, args...) =
    map(1:repeat) do _
        @allocated foldl(args...)
    end

@testset "allocation" begin
    args = (+, Broadcasting())
    n = 1000
    xs = [ones(n) for _ in 1:n]
    sample_foldl_allocations(2, args..., xs)
    baseline = minimum(sample_foldl_allocations(100, args..., xs))
    repeat10x = minimum(sample_foldl_allocations(100, args..., repeat(xs, 10)))
    @debug "estimated allocations" baseline repeat10x repeat10x / baseline

    # Always construct the messages to avoid introducing bugs.
    success_msg = """
    The test `repeat10x < 2 * baseline` works in Julia $(VERSION)!
    **Consider enabling this test again.**
    """
    skip_msg = """
    The test `repeat10x < 2 * baseline` does not work in Julia $(VERSION).
    Skipping this test.
    """
    if VERSION >= v"1.6-"
        if repeat10x < 2 * baseline
            @info success_msg baseline repeat10x repeat10x / baseline
        else
            @info skip_msg baseline repeat10x repeat10x / baseline
        end
        @test_skip repeat10x < 2 * baseline
    else
        @test repeat10x < 2 * baseline
    end
end

end  # module


"""
Trains SeqModel model given a config dict.

Returns dict containing results.
"""
function easy_sample!(
        model::Union{SeqModel,DistributedSeqModel},
        spikes::Vector{Spike},
        initial_assignments::Vector{Int64},
        config::Dict
    )

    # Save copy of initial assignments.
    _inits = copy(initial_assignments)

    # Draw annealed Gibbs samples.
    (
        assignments,
        anneal_assignment_hist,
        anneal_log_p_hist,
        anneal_latent_event_hist,
        anneal_globals_hist
    ) =
    annealed_gibbs!(
        model,
        spikes,
        initial_assignments,
        config[:num_anneals],
        config[:samples_per_anneal],
        config[:max_temperature],
        config[:split_merge_moves_during_anneal],
        config[:split_merge_window],
        config[:save_every_during_anneal];
        verbose=true
    )

    # # Sanity check.
    # for k in model.sequence_events.indices
    #     event = model.sequence_events[k]
    #     @assert event.spike_count == sum(assignments .== k)
    # end

    # Draw regular Gibbs samples.
    (
        assignments,
        assignment_hist,
        log_p_hist,
        latent_event_hist,
        globals_hist
    ) =
    gibbs_sample!(
        model,
        spikes,
        assignments,
        config[:samples_after_anneal],
        config[:split_merge_moves_after_anneal],
        config[:split_merge_window],
        config[:save_every_after_anneal];
        verbose=true
    )

    # # Sanity check.
    # for k in model.sequence_events.indices
    #     event = model.sequence_events[k]
    #     @assert event.spike_count == sum(assignments .== k)
    # end

    return Dict(

        # Initial assignment variables.
        :initial_assignments => _inits,

        # Results during annealing.
        :anneal_assignment_hist => anneal_assignment_hist,
        :anneal_log_p_hist => anneal_log_p_hist,
        :anneal_latent_event_hist => anneal_latent_event_hist,
        :anneal_globals_hist => anneal_globals_hist,

        # Results after annealing.
        :final_assignments => assignments,
        :assignment_hist => assignment_hist,
        :latent_event_hist => latent_event_hist,
        :globals_hist => globals_hist,
        :log_p_hist => log_p_hist
    )
end


using EMISApproximateEquilibrium
const SR = EMISApproximateEquilibrium
using Combinatorics
using PowerSystems
const PSY = PowerSystems

build = Dict{Type{<:PSY.Component}, Array{AbstractString}}()
build[PSY.ThermalStandard] =  repeat( ["118_CC_1", "101_CT_1", "313_CC_1", "207_CT_1", "101_STEAM_4"], 3);
build[PSY.HydroEnergyReservoir] = ["122_HYDRO_5"];
build[PSY.RenewableDispatch] = repeat(["309_WIND_1", "101_PV_4", "313_PV_1", "324_PV_3"], 3);
build[PSY.GenericBattery] = repeat(["313_STORAGE_1"], 1)
future_config_dict = Dict();
buildable_devices = Dict()
buildable_devices[PSY.ThermalStandard] =
    SR.all_combinations(build, PSY.ThermalStandard);
buildable_devices[PSY.RenewableDispatch] =
    SR.all_combinations(build, PSY.RenewableDispatch);
buildable_devices[PSY.HydroEnergyReservoir] =
    SR.all_combinations(build, PSY.HydroEnergyReservoir);
buildable_devices[PSY.GenericBattery] =
    SR.all_combinations(build, PSY.GenericBattery);

future = SR.combvec_samples(10000,
    buildable_devices[PSY.ThermalStandard],
    buildable_devices[PSY.HydroEnergyReservoir],
    buildable_devices[PSY.RenewableDispatch],
    buildable_devices[PSY.GenericBattery],
)

build_no = 1
exp_name = "10k"
future_config_dict, sample_count = SR.update_config(future_config_dict, future, 1);
rm("./sample_configuration_$(exp_name)_$(build_no)/", force = true, recursive = true)
if !isdir(joinpath(pwd(), "sample_configuration_$(exp_name)_$(build_no)"))
    sample_configuration_dir = mkdir(joinpath(pwd(), "sample_configuration_$(exp_name)_$(build_no)"))
else
    sample_configuration_dir = (joinpath(pwd(), "sample_configuration_$(exp_name)_$(build_no)"))
end
SR.generate_configuration(sample_configuration_dir, future_config_dict)

include("./generate_jobs.jl")
generate_jobs(pwd(), "/home/sdalvi/work/RTS-GMLC", sample_count, build_no, exp_name)

#=
Jade commands to run
jade auto-config generic_command jobs.txt -c config.json
jade submit-jobs config.json \
    --output=output \
    --max-nodes=64 \
    --per-node-batch-size=1 \
    --hpc-config=hpc_config.toml
=#


int main() {
    // bool operations with doubles
    double y = readDouble();
    printDouble(1.01325e5);
    printDouble(y);
    if (1.01325e5 == y && 1.01325e5 <= y && 1.01325e5 >= y) {
         printInt(1);
    }

    if (0.01325e5 > y && y < 0.01325e5 && y != 0.01325e5) {
    }
    else{
         printInt(2);
    }
    return 0;

}


module Controllers

using TrajectoryOptimization
using TrajectoryOptimization.Dynamics
const TO = TrajectoryOptimization

using StaticArrays
using LinearAlgebra
using ForwardDiff

include("../controllers/rbstate.jl")
include("../controllers/tracking_control.jl")

end


using Dates
using AppliGeneralLedger
using AppliSales # needed for test.jl and runtests.jl

include("./spec.jl")

struct MetaInvoice <: Structure
    _order_id::String
    _training_id::String
    _date::DateTime
    _currency::String
    _currency_ratio::Float64
    # Constructors
    MetaInvoice(order_id, training_id) = new(order_id, training_id, now(), "€", 1.0) #2
    MetaInvoice(order_id, training_id, date, currency, currency_ratio) = new(order_id, training_id, now(), currency, currency_ratio)
end # MetaInvoice

struct Header <: Structure
    _invoice_nbr::String
    _name::String
    _address::String
    _zip::String
    _city::String
    _country::String
    _order_ref::String
    _name_contact::String
    _email_contact::String
end # Header

struct OpentrainingItem <: BodyItem
    _name_training::String
    _date::DateTime
    _price_per_student::Float64
    _students::Array{String, 1}
    _vat_perc::Float64
    # constructors
    OpentrainingItem(name_training, date, price_per_student, students) = new(name_training, date, price_per_student, students, 0.21)
    OpentrainingItem(name_training, date, price_per_student, students, vat_perc) = new(name_training, date, price_per_student, students, vat_perc)
end # OpentrainingItem

struct UnpaidInvoice <: Invoice
    _id::String
    _meta::MetaInvoice
    _header::Header
    _body::OpentrainingItem
end # UnpaidInvoice

struct BankStatement <: Payment
	_date::Date
	_descr::String
	_iban::String
	_amount::Float64
end # BankStatement

struct PaidInvoice <: Invoice
    _id::String
    _meta::MetaInvoice
    _header::Header
    _body::OpentrainingItem
    _stm::BankStatement
end # PaidInvoice

"""
    stm(i::PaidInvoice)

Returns the Bankstatement of a paid invoice.
"""
stm(i::PaidInvoice) = i._stm

# Fields Invoice
meta(i::Invoice)::MetaInvoice = i._meta
header(i::Invoice)::Header = i._header
body(i::Invoice)::BodyItem = i._body
id(i::Invoice)::String = i._id

# fields MetaInvoice
order_id(m::MetaInvoice)::String = m._order_id
training_id(m::MetaInvoice)::String = m._training_id
date(m::MetaInvoice)::DateTime = m._date
currency(m::MetaInvoice)::String = m._currency
currency_ratio(m::MetaInvoice)::Float64 = m._currency_ratio

# fields Header
invoice_nbr(h::Header)::String = h._invoice_nbr
name(h::Header)::String = h._name
address(h::Header)::String = h._address
zip(h::Header)::String = h._zip
city(h::Header)::String = h._city
country(h::Header)::String = h._country
order_ref(h::Header)::String = h._order_ref
name_contact(h::Header)::String = h._name_contact
email_contact(h::Header)::String = h._email_contact

# field OpenTraining
name_training(o::OpentrainingItem)::String = o._name_training
date(o::OpentrainingItem)::DateTime = o._date
price_per_student(o::OpentrainingItem)::Float64 = o._price_per_student
students(o::OpentrainingItem)::Array{String, 1} = o._students
vat_perc(o::OpentrainingItem)::Float64 = o._vat_perc

# gfields BankStatement
date(b::BankStatement)::Date = b._date
descr(b::BankStatement)::String = b._descr
iban(b::BankStatement)::String = b._iban
amount(b::BankStatement)::Float64 = b._amount


function fstep(state::Union{SimState, Nothing}, agent::Agent) end
struct Data
    pos::Real2D
end

d1 = Data(Real2D(1.0,1.0))
d2 = Data(Real2D(2.0,2.0))

a1 = Agent(fstep,d1)
a2 = Agent(fstep,d2)

@test a1 != a2

a3 = deepcopy(a2)

#@test pointer_from_objref(a2) != pointer_from_objref(a3)

@test a1.state.pos != a3.state.pos
@test a2.state.pos == a3.state.pos

@test a1 != a2
@test a2 == a3

@test a1.id != a2.id
@test a1.id != a3.id
@test a2.id == a3.id


export Transform, IdentityTransform, ScaleTransform, ARDTransform, LowRankTransform, FunctionTransform, ChainTransform
export transform

"""
```julia
    transform(t::Transform, X::AbstractMatrix)
    transform(k::Kernel, X::AbstractMatrix)
```
Apply the transfomration `t` or `k.transform` on the input `X`
"""
transform

include("scaletransform.jl")
include("ardtransform.jl")
include("lowranktransform.jl")
include("functiontransform.jl")
include("selecttransform.jl")
include("chaintransform.jl")

"""
IdentityTransform
Return exactly the input
"""
struct IdentityTransform <: Transform end

params(t::IdentityTransform) = nothing
duplicate(t::IdentityTransform,θ) = t

transform(t::IdentityTransform,x::AbstractArray,obsdim::Int=defaultobs) = x #TODO add test

### TODO Maybe defining adjoints could help but so far it's not working

# @adjoint function ScaleTransform(s::T) where {T<:Real}
#     @check_args(ScaleTransform, s, s > zero(T), "s > 0")
#     ScaleTransform{T}(s),Δ->ScaleTransform{T}(Δ)
# end
#
# @adjoint function ScaleTransform(s::A) where {A<:AbstractVector{<:Real}}
#     @check_args(ScaleTransform, s, all(s.>zero(eltype(A))), "s > 0")
#     ScaleTransform{A}(s),Δ->begin; @show Δ,size(Δ); ScaleTransform{A}(Δ); end
# end

# @adjoint transform(t::ScaleTransform{<:AbstractVector{<:Real}},x::AbstractVector{<:Real}) = transform(t,x),Δ->(ScaleTransform(nothing),t.s.*Δ)
#
#     @adjoint transform(t::ScaleTransform{<:AbstractVector{<:Real}},X::AbstractMatrix{<:Real},obsdim::Int) = transform(t,X,obsdim),Δ->begin
#     @show Δ,size(Δ);
#     return (obsdim == 1 ? ScaleTransform()Δ'.*X : ScaleTransform()Δ.*X,transform(t,Δ,obsdim),nothing)
#     end
#
# @adjoint transform(t::ScaleTransform{T},x::AbstractVecOrMat,obsdim::Int) where {T<:Real} = transform(t,x), Δ->(ScaleTransform(one(T)),t.s.*Δ,nothing)


module SSLTestWorldclim
using SimpleSDMLayers
using Test

wc1and2 = SimpleSDMPredictor(WorldClim, BioClim, [1,2])
@test typeof(first(wc1and2)) <: SimpleSDMPredictor

temp = SimpleSDMPredictor(WorldClim, BioClim, 1)
@test size(temp) == (1080, 2160)
@test round(first(stride(temp)); digits=2) ≈ round(last(stride(temp)); digits=2)
@test length(longitudes(temp)) == 2160
@test length(latitudes(temp)) == 1080

wc3 = SimpleSDMPredictor(WorldClim, BioClim, 3)
@test typeof(wc3) <: SimpleSDMPredictor

wcrange = SimpleSDMPredictor(WorldClim, BioClim, 1:5)
@test eltype(wcrange) <: SimpleSDMPredictor

future = SimpleSDMPredictor(WorldClim, BioClim, MIROC6, SSP126, 1)
@test typeof(future) <: SimpleSDMPredictor

future = SimpleSDMPredictor(WorldClim, BioClim, CanESM5, SSP126, 1:3)
@test eltype(future) <: SimpleSDMPredictor

future = SimpleSDMPredictor(WorldClim, BioClim, CanESM5, SSP370, [1,2,5]; year="2061-2080")
@test eltype(future) <: SimpleSDMPredictor

end


using GCMAES, MPI

ENV["SLURM_ARRAY_TASK_ID"] = 1
ENV["SLURM_ARRAY_TASK_COUNT"] = 2

@mpirun GCMAES.make_virtual_jobarray(2)

rank = GCMAES.myrank(MPI.COMM_WORLD)
@show rank, GCMAES.myrank(), GCMAES.partjob(1:8), GCMAES.part(GCMAES.partjob(1:8))


module ImageGeoms

const RealU = Number # Union{Real, Unitful.Length}

abstract type Mask end
struct MaskAll <: Mask end
struct MaskCircle <: Mask end
struct MaskEllipse <: Mask end
struct MaskAllButEdge <: Mask end

export Mask, MaskAll, MaskCircle, MaskEllipse, MaskAllButEdge

include("core.jl")
include("imresize.jl")
include("makemask.jl")
include("mask.jl")

#include("property.jl") # exclude due to type inference issues

end # module