text:
### Begin here
# using Pkg

# Pkg.status()

# Pkg.add("CSV")
# Pkg.add("DataFrames")
# Pkg.add("MosekTools")
# Pkg.add("Test")
# Pkg.add("JuMP")
# Pkg.add("LinearAlgebra")

    Pkg.activate(".")
    #Pkg.instantiate()
    srcDir  = dirname(@__FILE__)*"\\";
    include(srcDir*"Examples\\Examples.jl")
    include(srcDir*"Examples\\Utils_states.jl")
    include(srcDir*"Moments.jl")
    include(srcDir*"Constraints\\Utils_cons.jl")
    include(srcDir*"Constraints\\R_constraints.jl")
    include(srcDir*"Constraints\\C_constraints.jl")
    include(srcDir*"Model\\Utils_Model.jl")
    include(srcDir*"Model\\R_sep_Model.jl")
    include(srcDir*"Model\\C_sep_Model.jl")
    include(srcDir*"sep_Compute.jl")

    using .Examples      ; const ex = Examples
    using .Utils_states  ; const us = Utils_states
    using .Moments       ; const mom = Moments
    using .Utils_cons    ; const uc = Utils_cons
    using .C_constraints ; const ccon = C_constraints
    using .R_constraints ; const rcon = C_constraints
    using .Utils_Model
    using .R_sep_Model
    using .C_sep_Model
    using .sep_Compute

    using LinearAlgebra ; const la = LinearAlgebra # Pkg.add("LinearAlgebra")
    using JuMP

## Goal get the C block diag to get the same values as the non- block diag.
ρ = [1. 0. 0. 0.
     0. 0. 0. 0.
     0. 0. 0. 0.
     0. 0. 0. 1.] ;d = (2,2)
t = (3,3) ;

#df = Examples.get_example_meta()
ex1_m = ex.get_example_meta("RCai")
d = ex1_m.Size[1]
ρ = ex.get_example("RCai")

ℂ1_sep_mod = sep_Compute.Computeξₜˢᵉᵖ(ρ,d,t;con_list="S1G",noBlock=false)
ℂ2_sep_mod = sep_Compute.Computeξₜˢᵉᵖ(ρ,d,t;con_list="S2G",noBlock=false)
ℂ3_sep_mod = sep_Compute.Computeξₜˢᵉᵖ(ρ,d,t;con_list="S3G",noBlock=false)

objective_value(ℂ1_sep_mod)
objective_value(ℂ2_sep_mod)
objective_value(ℂ3_sep_mod)

ℝ1_sep_mod = sep_Compute.Computeξₜˢᵉᵖ(ρ,d,t;con_list="S1G",noBlock=false,isRe = true)
ℝ2_sep_mod = sep_Compute.Computeξₜˢᵉᵖ(ρ,d,t;con_list="S2G",noBlock=false,isRe = true)
ℝ3_sep_mod = sep_Compute.Computeξₜˢᵉᵖ(ρ,d,t;con_list="S3G",noBlock=false,isRe = true)

objective_value(ℝ1_sep_mod)
objective_value(ℝ2_sep_mod)
objective_value(ℝ3_sep_mod)

println("Primal: ", primal_status(ℝ3_sep_mod))
println("Dual: ", dual_status(ℝ3_sep_mod))
## Batch run
include(srcDir*"batch.jl")
using .batch

df     = Examples.get_example_meta()
ρ_dict = ex.get_example()
t      = (3,3)
batch.batch_model(t,ρ_dict,df)

batch.batch_Computeξₜˢᵉᵖ("C:\\Users\\andries\\all-my-codes\\sep-rank\\assets\\bounds\\")
nar = batch.unstack_constraints(df)

nat = DataFrames.select(nar,[:Example,:Size,:Bi_rank,:CSᵦₐₗₗsG, :CS₂sG, :CS∞sG, :RSᵦₐₗₗsG, :RS₂sG, :RS∞sG,:isSeparable])
nat.isSeparable[poes.isSeparable .== "ent"] .= "∞"

CSV.write("C:\\Users\\andries\\all-my-codes\\sep-rank\\assets\\bounds\\t=$(t[1]).csv", poes, delim="&")

##   Size check
t = (3,3)
exa = "RCd" # "RCe"
ex1_m = Examples.get_example_meta(exa)
d = ex1_m.Size[1]
ρ = ex.get_example(exa)

model = JuMP.Model()
@variable(model, Lx[ccon.make_mon_expo_keys(d,t[1])])## Create variables

# size(mom.make_mon_expo(d,t[1]-2))
# t = (2,2)
# binomial(sum(d .*2) + t[1] ,t[1])
##  blocks
Gᴿ_con = ccon.make_Gᴿ_con(ρ,d,t,Lx;noBlock=false)
for b in keys(Gᴿ_con)
    println(size(Gᴿ_con[b]))
    # prod(size(Gᴿ_con[b]))
end
ADSA1 = sum([prod(size(Gᴿ_con[b])) for b in keys(Gᴿ_con)])
#
loc_cons_S1 = ccon.make_loc_cons_S1(ρ,d,t,Lx;noBlock=false)
for b in keys(loc_cons_S1)
    contains(b[2],"x²") ? println(b[1],size(loc_cons_S1[b])) : nothing
end
asdfas = [size(loc_cons_S1[b]) for b in keys(loc_cons_S1) if contains(b[2],"y²")  ]

ADSA2 = sum([contains(b[2],"x²") ? prod(size(loc_cons_S1[b])) : 0 for b in keys(loc_cons_S1)])
#
MMᴿcoef,MMᴿexᴿ = mom.get_ℂ_block_diag(d, t,noBlock=false)
for b in keys(MMᴿcoef)
    println(size(MMᴿcoef[b]))
end

ADSA3 = sum([prod(size(MMᴿcoef[b])) for b in keys(MMᴿcoef)])
ADSA1 + ADSA2 + ADSA3
## No blocks
Gᴿ_con = ccon.make_Gᴿ_con(ρ,d,t,Lx;noBlock=true)
for b in keys(Gᴿ_con)
    println(size(Gᴿ_con[b]))
end
ADSA1 = sum([prod(size(Gᴿ_con[b])) for b in keys(Gᴿ_con)])
#
loc_cons_S1 = ccon.make_loc_cons_S1(ρ,d,t,Lx;noBlock=true)
for b in keys(loc_cons_S1)
    contains(b[2],"x²") ? println(b[1],size(loc_cons_S1[b])) : nothing
end
ADSA2 = sum([contains(b[2],"x²") ? prod(size(loc_cons_S1[b])) : 0 for b in keys(loc_cons_S1)])
#
MMᴿcoef,MMᴿexᴿ = mom.get_ℂ_block_diag(d, t,noBlock=true)
for b in keys(MMᴿcoef)
    println(size(MMᴿcoef[b]))
end
ADSA3 = sum([prod(size(MMᴿcoef[b])) for b in keys(MMᴿcoef)])
ADSA1 + ADSA2 + ADSA3

## Running many random matrices:

using Random

Random.seed!(345)
function gen__ρ_RAND(d, r::Integer)
    d₁,d₂ = d ; ρ = zeros(d₁*d₂,d₁*d₂)
    a = [randn(1,d₁) + im*randn(1,d₁) for i in 1:r ]
    b = [randn(1,d₂) + im*randn(1,d₂) for i in 1:r ]

    for i in 1:r
        A = transpose(a[i])*conj(a[i])
        B = transpose(b[i])*conj(b[i])
        ρ = kron(A,B) + ρ
    end
    return ρ
end

d = (3,3)
t = 3

file_loc = dirname(dirname(@__FILE__))*"\\assets\\bounds\\Randomt$(t)_d_$d.csv"
touch(file_loc)
open(file_loc,"a") do io
    write(io, "|S1|S2|S3|T1|T2|T3|P1|P2|P3|D1|D2|D3|\n")
end

for i in 1:30
    ρ = gen__ρ_RAND(d, 5)
    ρ =  ρ / sum([ρ[i,i] for i in 1:size(ρ)[1]])

    O1 = sep_Compute.Computeξₜˢᵉᵖ(ρ,d,t;con_list="S1G",noBlock=false)
    O2 = sep_Compute.Computeξₜˢᵉᵖ(ρ,d,t;con_list="S2G",noBlock=false)
    # O3 = sep_Compute.Computeξₜˢᵉᵖ(ρ,d,t;con_list="S3G",noBlock=false)

    P1 = string(JuMP.primal_status(O1))
    D1 = string(JuMP.dual_status(O1))
    P2 = string(JuMP.primal_status(O2))
    D2 = string(JuMP.dual_status(O2))
    P3 = "-"#string(JuMP.primal_status(O3))
    D3 = "-"#string(JuMP.dual_status(O3))

    S1   = round(JuMP.objective_value(O1),digits=3)
    S2   = round(JuMP.objective_value(O2),digits=3)
    S3   = 0#round(JuMP.objective_value(O3),digits=3)

    T1 = JuMP.solution_summary(O1).solve_time
    T2 = JuMP.solution_summary(O2).solve_time
    T3 = 0#JuMP.solution_summary(O3).solve_time

    open(file_loc,"a") do io
        write(io, "|$S1|$S2|$S3|$T1|$T2|$T3|$P1|$P2|$P3|$D1|$D2|$D3|\n")
    end
end

max_stars_repo_name: JAndriesJ/sep-rank

max_issues_repo_name: JAndriesJ/sep-rank

max_forks_repo_name: JAndriesJ/sep-rank


text:
# #  Portfolio Optimization - Markowitz Efficient Frontier
#
# In this problem, we will find the unconstrained portfolio allocation where we introduce the weighting parameter $\lambda \;(0 \leq \lambda \leq$ 1) and minimize $\lambda * \text{risk} - (1-\lambda)* \text{expected return}$. By varying the values of $\lambda$, we trace out the efficient frontier.
#
# Suppose that we know the mean returns $\mu \in \mathbf{R}^n$ of each asset and the covariance $\Sigma \in \mathbf{R}^{n \times n}$ between the assets. Our objective is to find a portfolio allocation that minimizes the *risk* (which we measure as the variance $w^T \Sigma w$) and maximizes the *expected return* ($w^T \mu$) of the portfolio of the simulataneously. We require $w \in \mathbf{R}^n$ and $\sum_i w_i = 1$.
#
# This problem can be written as
#
# $$
# \begin{array}{ll}
#     \mbox{minimize}   & \lambda*w^T \Sigma w - (1-\lambda)*w^T \mu \\
#     \mbox{subject to} & \sum_i w_i = 1
# \end{array}
# $$
#
# where $w \in \mathbf{R}^n$ is the vector containing weights allocated to each asset.

using Convex, SCS    #We are using SCS solver. Install using Pkg.add("SCS")

## generate problem data
μ = [11.5; 9.5; 6]/100          #expected returns
Σ  = [166  34  58;              #covariance matrix
       34  64   4;
       58   4 100]/100^2

n = length(μ)                   #number of assets

# If you want to try the optimization with more assets, uncomment and run the next cell. It creates a vector or average returns and a variance-covariance matrix that have scales similar to the numbers above.

#=
using Random
Random.seed!(123)

n = 15                                      #number of assets, CHANGE IT?

μ = (6 .+ (11.5-6)*rand(n))/100             #mean
A = randn(n,n)
Σ = (A * A' + diagm(0=>rand(n)))/500;       #covariance matrix
=#

# First we solve without any bounds on $w$

N = 101
λ_vals = range(0.01,stop=0.99,length=N)

w    = Variable(n)
ret  = dot(w,μ)
risk = quadform(w,Σ)

MeanVarA = zeros(N,2)
for i = 1:N
    λ = λ_vals[i]
    p = minimize( λ*risk - (1-λ)*ret,
                  sum(w) == 1 )
    solve!(p, SCSSolver(verbose = false))
    MeanVarA[i,:]= [evaluate(ret),evaluate(risk)[1]]    #risk is a 1x1 matrix
end

# Now we solve with the bounds $0\le w_i \le 1$

w_lower = 0                     #bounds on w
w_upper = 1

MeanVarB = zeros(N,2)   #repeat, but with 0<w[i]<1
for i = 1:N
    λ = λ_vals[i]
    p = minimize( λ*risk - (1-λ)*ret,
                  sum(w) == 1,
                  w_lower <= w,     #w[i] is bounded
                  w <= w_upper )
    solve!(p, SCSSolver(verbose = false))
    MeanVarB[i,:]= [evaluate(ret),evaluate(risk)[1]]
end

#-

using Plots
plot( sqrt.([MeanVarA[:,2] MeanVarB[:,2]]),
      [MeanVarA[:,1] MeanVarB[:,1]],
      xlim = (0,0.25),
      ylim = (0,0.15),
      title = "Markowitz Efficient Frontier",
      xlabel = "Standard deviation",
      ylabel = "Expected return",
      label = ["no bounds on w","with 0<w<1",])
scatter!(sqrt.(diag(Σ)),μ,color=:red,label = "assets")

# We now instead impose a restriction on  $\sum_i |w_i| - 1$, allowing for varying degrees of "leverage".

Lmax = 0.5

MeanVarC = zeros(N,2)   #repeat, but with restriction on Sum(|w[i]|)
for i = 1:N
    λ = λ_vals[i]
    p = minimize( λ*risk - (1-λ)*ret,
                  sum(w) == 1,
                  (norm(w, 1)-1) <= Lmax)
    solve!(p, SCSSolver(verbose = false))
    MeanVarC[i,:]= [evaluate(ret),evaluate(risk)[1]]
end

#-

plot( sqrt.([MeanVarA[:,2] MeanVarB[:,2] MeanVarC[:,2]]),
      [MeanVarA[:,1] MeanVarB[:,1] MeanVarC[:,1]],
      xlim = (0,0.25),
      ylim = (0,0.15),
      title = "Markowitz Efficient Frontier",
      xlabel = "Standard deviation",
      ylabel = "Expected return",
      label = ["no bounds on w","with 0<w<1","restriction on sum(|w|)"])
scatter!(sqrt.(diag(Σ)),μ,color=:red,label = "assets")

#-

ext: jl


text:
module Kwargs

import Pkg

include(joinpath(@__DIR__, "autodetect-dependabot.jl"))

function kwargs(; coverage::Bool,
                  force_latest_compatible_version::Union{Bool, Symbol},
                  julia_args::AbstractVector{<:AbstractString}=String[])
    if !(force_latest_compatible_version isa Bool) && (force_latest_compatible_version != :auto)
        throw(ArgumentError("Invalid value for force_latest_compatible_version: $(force_latest_compatible_version)"))
    end

    kwargs_dict = Dict{Symbol, Any}()
    kwargs_dict[:coverage] = coverage

    if VERSION >= v"1.6.0"
        kwargs_dict[:julia_args] = julia_args
    elseif julia_args == ["--check-bounds=yes"]
        # silently don't add this default julia_args value as < 1.6 doesn't support julia_args, but it's the default state
    else
        println("::warning::The Pkg.test bounds checking behavior cannot be changed before Julia 1.6. VERSION=$VERSION, julia_args=$julia_args")
    end

    if VERSION < v"1.7.0-" || !hasmethod(Pkg.Operations.test, Tuple{Pkg.Types.Context, Vector{Pkg.Types.PackageSpec}}, (:force_latest_compatible_version,))
        (force_latest_compatible_version != :auto) && @warn("The `force_latest_compatible_version` option requires at least Julia 1.7", VERSION, force_latest_compatible_version)
        return kwargs_dict
    end

    if force_latest_compatible_version == :auto
        is_dependabot_job = AutodetectDependabot.is_dependabot_job()
        is_dependabot_job && @info("This is a Dependabot/CompatHelper job, so `force_latest_compatible_version` has been set to `true`")
        kwargs_dict[:force_latest_compatible_version] = is_dependabot_job
    else
        kwargs_dict[:force_latest_compatible_version] = force_latest_compatible_version::Bool
    end

    return kwargs_dict
end

end # module

max_stars_repo_name: julia-actions/julia-runtest

max_issues_repo_name: davidanthoff/julia-runtest

max_forks_repo_name: davidanthoff/julia-runtest


text:
using VersionVigilante

using Pkg
using Test

const repo_url = "https://github.com/bcbi-test/versionvigilante-integration-test-repo.git"

@testset "VersionVigilante.jl" begin
    @testset "compare_versions.jl" begin
        @test !VersionVigilante.isprerelease(v"1")
        @test !VersionVigilante.isprerelease(v"1-")
        @test VersionVigilante.isprerelease(v"1-dev")
        @test VersionVigilante.isprerelease(v"1-PRE")
    end
    @testset "main.jl" begin
        # 1.0.0 -> 0.9.9 is FAIL
        VersionVigilante.with_branch(repo_url,
                                     "master-1.0.0",
                                     "feature-0.9.9") do
            rm("Project.toml"; force = true, recursive = true)
            open("Project.toml", "w") do io
                println(io, "version = \"0.9.9\"")
            end
            @test_throws ErrorException VersionVigilante.main(repo_url;
                                                              master_branch = "master-1.0.0")
        end

        # 1.0.0 -> 1.0.0- is FAIL
        VersionVigilante.with_branch(repo_url,
                                     "master-1.0.0",
                                     "feature-1.0.0-") do
            rm("Project.toml"; force = true, recursive = true)
            open("Project.toml", "w") do io
                println(io, "version = \"1.0.0-\"")
            end
            @test_throws ErrorException VersionVigilante.main(repo_url;
                                                              master_branch = "master-1.0.0")
        end

        # 1.0.0 -> 1.0.0-DEV is FAIL
        VersionVigilante.with_branch(repo_url,
                                     "master-1.0.0",
                                     "feature-1.0.0-") do
            rm("Project.toml"; force = true, recursive = true)
            open("Project.toml", "w") do io
                println(io, "version = \"1.0.0-DEV\"")
            end
            @test_throws ErrorException VersionVigilante.main(repo_url;
                                                              master_branch = "master-1.0.0")
        end

        # 1.0.0 -> 1.0.0 is FAIL
        VersionVigilante.with_branch(repo_url,
                                     "master-1.0.0",
                                     "feature-1.0.0") do
            rm("Project.toml"; force = true, recursive = true)
            open("Project.toml", "w") do io
                println(io, "version = \"1.0.0\"")
            end
            @test_throws ErrorException VersionVigilante.main(repo_url;
                                                              master_branch = "master-1.0.0")
        end
        VersionVigilante.with_branch(repo_url,
                                     "master-1.0.0",
                                     "feature-1.0.0") do
            rm("Project.toml"; force = true, recursive = true)
            open("Project.toml", "w") do io
                println(io, "version = \"1.0.0\"")
            end
            @test_throws ErrorException VersionVigilante.main(repo_url;
                                                              master_branch = "master-1.0.0",
                                                              allow_unchanged_prerelease = true)
        end
        VersionVigilante.with_branch(repo_url,
                                     "master-1.0.0",
                                     "feature-1.0.0") do
            rm("Project.toml"; force = true, recursive = true)
            open("Project.toml", "w") do io
                println(io, "version = \"1.0.0\"")
            end
            @test_throws ErrorException VersionVigilante.main(repo_url;
                                                              master_branch = "master-1.0.0",
                                                              allow_unchanged_prerelease = false)
        end

        # 1.0.0 -> 1.0.1 is PASS
        VersionVigilante.with_branch(repo_url,
                                     "master-1.0.0",
                                     "feature-1.0.1") do
            rm("Project.toml"; force = true, recursive = true)
            open("Project.toml", "w") do io
                println(io, "version = \"1.0.1\"")
            end
            @test nothing == VersionVigilante.main(repo_url;
                                                   master_branch = "master-1.0.0")
        end

        # 1.0.0 -> 1.1.0 is PASS
        VersionVigilante.with_branch(repo_url,
                                     "master-1.0.0",
                                     "feature-1.1.0") do
            rm("Project.toml"; force = true, recursive = true)
            open("Project.toml", "w") do io
                println(io, "version = \"1.1.0\"")
            end
            @test nothing == VersionVigilante.main(repo_url;
                                                   master_branch = "master-1.0.0")
        end

        # 1.0.0 -> 2.0.0 is PASS
        VersionVigilante.with_branch(repo_url,
                                     "master-1.0.0",
                                     "feature-2.0.0") do
            rm("Project.toml"; force = true, recursive = true)
            open("Project.toml", "w") do io
                println(io, "version = \"2.0.0\"")
            end
            @test nothing == VersionVigilante.main(repo_url;
                                                   master_branch = "master-1.0.0")
        end

        # 1.0.0-DEV -> 1.0.0-DEV is PASS if `allow_unchanged_prerelease = true`
        VersionVigilante.with_branch(repo_url,
                                     "master-1.0.0-dev",
                                     "feature-1.0.0-dev") do
            rm("Project.toml"; force = true, recursive = true)
            open("Project.toml", "w") do io
                println(io, "version = \"1.0.0-DEV\"")
            end
            @test nothing == VersionVigilante.main(repo_url;
                                                   master_branch = "master-1.0.0-dev")
        end
        VersionVigilante.with_branch(repo_url,
                                     "master-1.0.0-dev",
                                     "feature-1.0.0-dev") do
            rm("Project.toml"; force = true, recursive = true)
            open("Project.toml", "w") do io
                println(io, "version = \"1.0.0-DEV\"")
            end
            @test nothing == VersionVigilante.main(repo_url;
                                                   master_branch = "master-1.0.0-dev",
                                                   allow_unchanged_prerelease = true)
        end

        # 1.0.0-DEV -> 1.0.0-DEV is FAIL if `allow_unchanged_prerelease = false`
        VersionVigilante.with_branch(repo_url,
                                     "master-1.0.0-dev",
                                     "feature-1.0.0-dev") do
            rm("Project.toml"; force = true, recursive = true)
            open("Project.toml", "w") do io
                println(io, "version = \"1.0.0-DEV\"")
            end
            @test_throws ErrorException VersionVigilante.main(repo_url;
                                                              master_branch = "master-1.0.0-dev",
                                                              allow_unchanged_prerelease = false)
        end

        # 1.0.0-DEV -> 1.0.0 is PASS
        VersionVigilante.with_branch(repo_url,
                                     "master-1.0.0-dev",
                                     "feature-1.0.0") do
            rm("Project.toml"; force = true, recursive = true)
            open("Project.toml", "w") do io
                println(io, "version = \"1.0.0\"")
            end
            @test nothing == VersionVigilante.main(repo_url;
                                                   master_branch = "master-1.0.0-dev")
        end
    end
    @testset "parse_project.jl" begin
        VersionVigilante.with_temp_dir() do
            rm("Project.toml"; force = true, recursive = true)
            open("Project.toml", "w") do io
                println(io, "version = \"1.2.3\"")
            end
            @test VersionVigilante.get_version() == v"1.2.3"
        end
    end
    @testset "utils.jl" begin
    end
end


text:
import Base.==

input = "{ \"bar\": { \"fnord\": 17 }, \"fizz\": 3.14 }"
json_string_input = """{"buzz":"abc", "bar": {"fnord": 42}}"""

immutable Bar
    fnord::Int
end

immutable Foo
    bar::Bar
end

immutable Baz
    fizz::Nullable{Float64}
    bar::Bar
end

immutable Qux
    buzz::Nullable{UTF8String}
    bar::Bar
end

immutable Foobar
    bar::Nullable{Bar}
end

function ==(a::Foobar, b::Foobar)
    !isnull(a.bar) && !isnull(b.bar) && get(a.bar) == get(b.bar) || isnull(a.bar) && isnull(b.bar)
end

function ==(a::Qux, b::Qux)
    eq_buzz = !isnull(a.buzz) && !isnull(b.buzz) && get(a.buzz) == get(b.buzz) ||
        isnull(a.buzz) && isnull(b.buzz)
    a.bar == b.bar && eq_buzz
end

immutable JsonArray
    foo::Int
    bar::Array{Bar,1}
    fizz::Array{Int,1}
end

function ==(a::JsonArray, b::JsonArray)
    a.foo == b.foo && a.bar == b.bar && a.fizz == b.fizz
end

immutable TestResponse
    ok::Bool
    warnings::Nullable{AbstractString}
    error::Nullable{AbstractString}
end

function ==(a::TestResponse, b::TestResponse)
    eq_warns = !isnull(a.warnings) && !isnull(b.warnings) && get(a.warnings) == get(b.warnings) ||
        isnull(a.warnings) && isnull(b.warnings)
    eq_errors = !isnull(a.error) && !isnull(b.error) && get(a.error) == get(b.error) ||
        isnull(a.error) && isnull(b.error)
    a.ok == b.ok && eq_warns && eq_errors
end

@test DandelionSlack.deserialize(Foo, input) == Foo(Bar(17))
@test DandelionSlack.deserialize(Baz, input) == Baz(Nullable(3.14), Bar(17))
@test DandelionSlack.deserialize(Qux, input) == Qux(Nullable{UTF8String}(),Bar(17))

facts("JSON deserialization") do
    # Testing this function
    # deserialize{T}(::Type{Nullable{T}}, json::AbstractString) = deserialize(T, json)
    @fact DandelionSlack.deserialize(Qux, json_string_input) -->
        Qux(Nullable("abc"), Bar(42))

    # Testing this function
    # deserialize{T}(::Type{Nullable{T}}, json::Dict) = deserialize(T, json)
    @fact DandelionSlack.deserialize(Foobar, "{}") --> Foobar(Nullable{Bar}())
    @fact DandelionSlack.deserialize(Foobar, "{\"bar\": {\"fnord\": 42}}") -->
        Foobar(Nullable(Bar(42)))
end

facts("JSON deserialization of arrays") do
    json_with_array = """{"foo": 42, "bar":[{"fnord": 17}, {"fnord": 13}], "fizz": [1,2,3]}"""
    bars = [Bar(17), Bar(13)]
    @fact DandelionSlack.deserialize(JsonArray, json_with_array) -->
        JsonArray(42, bars, [1,2,3])
end

test_response = """{"ok": true, "warnings": "a warning"}"""
@test DandelionSlack.deserialize(TestResponse, test_response) ==
    TestResponse(true, "a warning", Nullable{AbstractString}())

test_response = """{"ok": true, "warnings": null}"""
@test DandelionSlack.deserialize(TestResponse, test_response) ==
    TestResponse(true, Nullable{AbstractString}(), Nullable{AbstractString}())


text:
# Classification Metrics and Plots
# This files contains various metrics used in Classifiaction problems

# One Hot encode
function onehot_encode(y, labels)
   onehot_arr = zeros(length(labels), length(y))
   for i in 1:length(y)
       onehot_arr[Int(y[i]) + 1, i] = 1
   end
   onehot_arr
end

"""
    bin_to_cat(y_pred, y_true)

Function to convert binary type of data to categorical with two categories. Return `y_pred` and `y_true` of shape `(2, length(y_pred))` as tuple. Utility function to support performance metrics like `Precision`, `Recall` etc, where the function first need to be converted to categorical form before applying metric.
"""
function bin_to_cat(y_pred, y_true)
    @assert length(y_pred) == length(y_true)
    y_pred_ = zeros(2, length(y_pred))
    y_pred_[2, :] = y_pred
    y_pred_[1, :] = 1 .- y_pred
    return (y_pred_, onehot_encode(y_true, 0:1))
end

# Onecold
function onecold(y)
    onecold_arr = zeros(size(y,2))
    argmax_vec = argmax(y, dims = 1)
    for i in 1:size(y,2)
        onecold_arr[i] = argmax_vec[i].I[1]
    end
    return onecold_arr
end

"""
    confusion_matrix(y_pred, y_true)

Function to create a confusion_matrix for classification problems based on provided `y_pred` and `y_true`. Expects `y_true`, to be onehot_enocded already.
"""
function confusion_matrix(y_pred, y_true)
    @assert size(y_pred) == size(y_true)
    label_count = size(y_true, 1)
    ŷ = onehot_encode(onecold(y_pred), 1:label_count)
    return ŷ * transpose(y_true)
end

"""
    TFPN(y_pred, y_true)

Returns `Confusion Matrix` and `True Positive`, `True Negative`, `False Positive` and `False Negative` for each class based on `y_pred` and `y_true`. Expects `y_true`, to be onehot_enocded already.
"""
function TFPN(y_pred, y_true)
    @assert size(y_pred) == size(y_true)
    label_count = size(y_true, 1)
    TP = zeros(label_count) ; TN = zeros(label_count)
    FP = zeros(label_count) ; FN = zeros(label_count)
    ConfusionMatrix = confusion_matrix(y_pred, y_true)
    for c in 1 : label_count
        TP[c] = ConfusionMatrix[c,c]
        FP[c] = sum(ConfusionMatrix[:,c]) - TP[c]
        FN[c] = sum(ConfusionMatrix[c,:]) - TP[c]
        TN[c] = sum(ConfusionMatrix) - TP[c] - FP[c] - FN[c]
    end
    return ConfusionMatrix,TP, TN, FP, FN
end

"""
    binary_accuracy(y_pred, y_true; threshold=0.5)

Calculates Averaged Binary Accuracy based on `y_pred` and `y_true`. Argument `threshold` is used to specify the minimum predicted probability `y_pred` required to be labelled as `1`. Default value set as `0.5`.
"""
function binary_accuracy(y_pred, y_true; threshold=0.5)
    @assert size(y_pred) == size(y_true)
    return sum((y_pred .>= threshold) .== y_true) / size(y_true, 1)
end

"""
    categorical_accuracy(y_pred, y_true)

Calculates Averaged Categorical Accuracy based on `y_pred` and `y_true`.
"""
function categorical_accuracy(y_pred, y_true)
    @assert size(y_pred) == size(y_true)
    return sum(onecold(y_pred) .== onecold(y_true)) / size(y_true, 2)
end

"""
    sparse_categorical(y_pred, y_true)

Calculated Sparse Categorical Accuracy based on `y_pred` and `y_true`. It evaluates the maximal true value is equal to the index of the maximal predicted value. Here, `y_true` is expected to provide only an integer (start from `0` index) as label for each data element (ie. not one hot encoded).
"""
function sparse_categorical(y_pred, y_true)
    @assert size(y_pred, 2) == length(y_true)
    return sum(onecold(y_pred) .== (y_true .+ 1) ) / size(y_true, 1)
end

"""
    top_k_categorical(y_pred, y_true; k=3)

Evaluates if the index of true value is equal to any of the indices of top k predicted values. Default value of `k` set to `3`.
"""
function top_k_categorical(y_pred, y_true; k=3)
    @assert size(y_pred) == size(y_true)
    count = 0
    sparse_y = onecold(y_true)
    for i in 1:size(y_true, 2)
        top_k = partialsortperm(y_pred[:,i], 1:k, lt= >)
        for j in 1:k
            if top_k[j] == sparse_y[i]
                count+=1
                break
            end
        end
    end
    return count / length(sparse_y)
end

"""
    top_k_sparse_categorical(y_pred, y_true; k=3)

Evaluates if the true value is equal to any of the indices of top k predicted values. Default value of `k` set to `3`. Similar to `sparse_categorical`, expects the `y_true` to provide only an integer (start from `0` index) as label for each data element (ie. not one hot encoded).
"""
function top_k_sparse_categorical(y_pred, y_true; k=3)
    count = 0
    for i in 1:length(y_true)
        top_k = partialsortperm(y_pred[:,i], 1:k, lt= >)
        for j in 1:k
            if top_k[j] == y_true[i] + 1
                count+=1
                break
            end
        end
    end
    return count / length(y_true)
end

# Calculate Instances for each class in y
function calc_instances(y)
   instances = zeros(size(y, 1))
   sparse_y = onecold(y)
   for i in 1:length(instances)
       instances[i] = sum(sparse_y .== i)
   end
   return instances
end

"""
    precision(y_pred, y_true; avg_type="macro", sample_weights=nothing)

Computes the precision of the predictions with respect to the labels.

# Arguments
 - `y_pred`: predicted values.
 - `y_true`: ground truth values on the basis of which predicted values are to be assessed.
 - `avg_type="macro"`: Type of average to be used while calculating precision of multiclass models. Can take values as `macro`, `micro` and `weighted`. Default set to `macro`.
 - `sample_weights`: Class weights to be provided when `avg_type` is set to `weighted`. Useful in case of imbalanced classes.

"""
function precision(y_pred, y_true; avg_type="macro", sample_weights=nothing)
    _,TP, TN, FP, FN = TFPN(y_pred, y_true)
    # Macro-averaged Precision
    if avg_type == "macro"
        return mean(TP ./ (TP .+ FP .+ eps(eltype(TP))))
    # Micro-averaged Precision
    elseif avg_type == "micro"
        return mean(TP) / (mean(TP) + mean(FP))
    # Weighted-Averaged Precision
    elseif avg_type == "weighted"
        weights = []
        if sample_weights != nothing
            weights = sample_weights
        else
            weights = calc_instances(y_true) / size(y_true, 2)
        end
        return mean((TP ./ (TP .+ FP .+ eps(eltype(TP)))) .* weights)
    end
end

"""
    recall(y_pred, y_true; avg_type="macro", sample_weights=nothing)

Computes the recall of the predictions with respect to the labels.

# Arguments
 - `y_pred`: predicted values.
 - `y_true`: ground truth values on the basis of which predicted values are to be assessed.
 - `avg_type="macro"`: Type of average to be used while calculating precision of multiclass models. Can take values as `macro`, `micro` and `weighted`. Default set to `macro`.
 - `sample_weights`: Class weights to be provided when `avg_type` is set to `weighted`. Useful in case of imbalanced classes.

Aliases: `sensitivity` and `detection_rate`
"""
function recall(y_pred, y_true; avg_type="macro", sample_weights=nothing)
    _,TP, TN, FP, FN = TFPN(y_pred, y_true)
    # Macro-averaged Precision
    if avg_type == "macro"
        return mean(TP ./ (TP .+ FN .+ eps(eltype(TP))))
    # Micro-averaged Precision
    elseif avg_type == "micro"
        return mean(TP) / (mean(TP) + mean(FN))
    # Weighted-Averaged Precision
    else
        weights = []
        if sample_weights != nothing
            weights = sample_weights
        else
            weights = calc_instances(y_true) / size(y_true, 2)
        end
        return mean((TP ./ (TP .+ FN .+ eps(eltype(TP)))) .* weights)
    end
end
const sensitivity = recall
const detection_rate = recall

"""
    f_beta_score(y_pred, y_true; β=1, avg_type="macro", sample_weights=nothing)

Compute fbeta score. The F_beta score is the weighted harmonic mean of precision and recall, reaching its optimal value at 1 and its worst value at 0.

# Arguments
 - `y_pred`: predicted values.
 - `y_true`: ground truth values on the basis of which predicted values are to be assessed.
 - `β=1`: the weight of precision in the combined score. If `β<1`, more weight given to `precision`, while `β>1` favors recall.
 - `avg_type="macro"`: Type of average to be used while calculating precision of multiclass models. Can take values as `macro`, `micro` and `weighted`. Default set to `macro`.
 - `sample_weights`: Class weights to be provided when `avg_type` is set to `weighted`. Useful in case of imbalanced classes.

"""
function f_beta_score(y_pred, y_true; β=1, avg_type="macro", sample_weights=nothing)
    recall_ = recall(y_pred, y_true, avg_type=avg_type, sample_weights=sample_weights)
    precision_ = precision(y_pred, y_true, avg_type=avg_type, sample_weights=sample_weights)
    return (1 + β^2) * precision_ * recall_ / (precision_ + (β^2) * recall_ + eps(eltype(y_pred)))
end

"""
    specificity(y_pred, y_true; avg_type="macro", sample_weights=nothing)

Computes the specificity of the predictions with respect to the labels.

# Arguments
 - `y_pred`: predicted values.
 - `y_true`: ground truth values on the basis of which predicted values are to be assessed.
 - `avg_type="macro"`: Type of average to be used while calculating precision of multiclass models. Can take values as `macro`, `micro` and `weighted`. Default set to `macro`.
 - `sample_weights`: Class weights to be provided when `avg_type` is set to `weighted`. Useful in case of imbalanced classes.
"""
function specificity(y_pred, y_true; avg_type="macro", sample_weights=nothing)
    _, TP, TN, FP, FN = TFPN(y_pred, y_true)
    # Macro-averaged Precision
    if avg_type == "macro"
        return mean(TN ./ (TN .+ FP .+ eps(eltype(TP))))
    # Micro-averaged Precision
    elseif avg_type == "micro"
        return mean(TN) / (mean(TN) + mean(FP))
    # Weighted-Averaged Precision
    else
        weights = []
        if sample_weights != nothing
            weights = sample_weights
        else
            weights = calc_instances(y_true) / size(y_true, 2)
        end
        return mean((TN ./ (TN .+ FP .+ eps(eltype(TP)))) .* weights)
    end
end

"""
    false_alarm_rate(y_pred, y_true; avg_type="macro", sample_weights=nothing)

Computes the false_alarm_raye of the predictions with respect to the labels as `1 - specificity(y_pred, y_true, avg_type, sample_weights)`

# Arguments
 - `y_pred`: predicted values.
 - `y_true`: ground truth values on the basis of which predicted values are to be assessed.
 - `avg_type="macro"`: Type of average to be used while calculating precision of multiclass models. Can take values as `macro`, `micro` and `weighted`. Default set to `macro`.
 - `sample_weights`: Class weights to be provided when `avg_type` is set to `weighted`. Useful in case of imbalanced classes.

See also: [`specificity`](@ref)
"""
function false_alarm_rate(y_pred, y_true; avg_type="macro", sample_weights=nothing)
    return 1 - specificity(y_pred, y_true, avg_type, sample_weights)
end

"""
    cohen_kappa(y_pred, y_true)

Measures the agreement between two raters (predicted and ground truth, here) who each classify N items into C mutually exclusive categories, using the observed data to calculate the probabilities of each observer randomly seeing each category.
If the raters are in complete agreement then κ = 1. If there is no agreement among the raters other than what would be expected by chance,
κ = 0.

Ref: [Cohen's Kappa](https://en.wikipedia.org/wiki/Cohen%27s_kappa)
"""
function cohen_kappa(y_pred, y_true)
    _, tp, tn, fp, fn = TFPN(y_pred, y_true)
    mrg_a = ((tp .+ fn) .* (tp .+ fp)) ./ (tp .+ fn .+ fp .+ tn)
    mrg_b = ((fp .+ tn) .* (fn .+ tn)) ./ (tp .+ fn .+ fp .+ tn)
    expec_agree = (mrg_a .+ mrg_b) ./ (tp .+ fn .+ fp .+ tn)
    obs_agree = (tp .+ tn) ./ (tp .+ fn .+ fp .+ tn)
    cohens_kappa = mean((obs_agree .- expec_agree) ./ (1 .- expec_agree))
    return cohens_kappa
end

"""
    statsfromTFPN(TP, TN, FP, FN)

Computes statistics in case of binary classification or one-vs-all statsitics in case of multiclass classification.

# Arguments:
 - `TP`: true positive values
 - `TN`: true negative values
 - `FP`: false positive values
 - `FN`: false negative values

Return the result stats as a dictionary.
"""
function statsfromTFPN(TP, TN, FP, FN)
    Confusion_Matrix = reshape([TP, FP, FN, TN], 2, 2)
    Precision = TP / (TP + FP + eps(eltype(TP)))
    Recall = TP / (TP + FN + eps(eltype(TP)))
    Specificity = TN / (TN + FP + eps(eltype(TP)))
    F1_score = 2 * (Precision * Recall) / (Precision + Recall + eps(eltype(TP)))
    Accuracy = (TP + TN) / (TP + TN + FP + FN)
    False_alarm_rate = 1 - Specificity
    return Dict(:Confusion_Matrix => Confusion_Matrix,
                :Precision => Precision, :Recall => Recall,
                :Specificity => Specificity, :F1_score => F1_score,
                :Accuracy => Accuracy)
end

"""
    classwise_stats(y_pred, y_true)

Computes statistics for each of the class for multiclass classification based on provided `y_pred` and `y_true`.

Return the result stats as a dictionary.
"""
function classwise_stats(y_pred, y_true)
    _, TP, TN, FP, FN = TFPN(y_pred, y_true)
    ClasswiseStats = Dict()
    for i in 1:size(y_true, 1)
        ClasswiseStats[i] = StatsfromTFPN(TP[i], TN[i], FP[i], FN[i])
    end
    return ClasswiseStats
end

"""
    global_stats(y_pred, y_true; avg_type="macro")

Computes the overall statistics based on provided `y_pred` and `y_true`. `avg_type` allows to specify the type of average to be used while evaluating the stats. Currently, it can take values as "macro" or "micro".

Return the result stats as a dictionary.
"""
function global_stats(y_pred, y_true; avg_type="macro")
    confusion_matrix_, TP, TN, FP, FN = TFPN(y_pred, y_true)
    if avg_type == "macro"
        precision = mean(TP ./ (TP .+ FP .+ eps(eltype(TP))))
        recall = mean(TP ./ (TP .+ FN .+ eps(eltype(TP))))
        f1_score = 2 * precision * recall / (precision + recall + eps(eltype(TP)))
        specificity = mean(TN ./ (TN .+ FP .+ eps(eltype(TP))))
        accuracy = categorical_accuracy(y_pred, y_true)
        false_alarm_rate = 1 - specificity
        return Dict("Confusion_Matrix" => confusion_matrix_,
                "Precision" => precision, "Recall" => recall,
                "Specificity" => specificity, "F1_score" => f1_score,
                "Accuracy" => accuracy, "False_alarm_rate" => false_alarm_rate)
    elseif avg_type == "micro"
        precision = mean(TP) / (mean(TP) + mean(FP))
        recall = mean(TP) / (mean(TP) + mean(FN))
        f1_score = 2 * precision * recall / (precision + recall + eps(eltype(TP)))
        specificity = mean(TN)/ (mean(TN) + mean(FP))
        accuracy = categorical_accuracy(y_pred, y_true)
        false_alarm_rate = 1 - specificity
        return Dict("Confusion_Matrix" => confusion_matrix_,
                "Precision" => precision, "Recall" => recall,
                "Specificity" => specificity, "F1_score" => f1_score,
                "Accuracy" => accuracy, "False_alarm_rate" => false_alarm_rate)
    end
    # TODO: add weighted stats option as above functions
end

# TODO
# Concordance and Discordance

# Receiver Operating Characterstic (ROC) Curve

# AUC


text:
# # Mode-1 internal wave reflection
#
# This example simulates the propagation of a mode-1 internal wave
# using the `ClimateMachine.Ocean` subcomponent to solve the hydrostatic
# Boussinesq equations.
#
# First we `ClimateMachine.init()`.

using ClimateMachine

ClimateMachine.init()

# # Domain setup
#
# We formulate a non-dimension problem in a Cartesian domain with oceanic anisotropy,

using ClimateMachine.CartesianDomains

domain = RectangularDomain(
    Ne = (32, 1, 4),
    Np = 4,
    x = (-128, 128),
    y = (-128, 128),
    z = (-1, 0),
    periodicity = (false, false, false),
)

# # Parameters
#
# We choose parameters appropriate for a hydrostatic internal wave,

# Non-dimensional internal wave parameters
f = 1  # Coriolis
N = 10 # Buoyancy frequency

# Note that the validity of the hydrostatic approximation requires
# small aspect ratio motions with ``k / m \\ll 1``.
# The hydrostatic dispersion relation for inertia-gravity waves then implies that

λ = 8      # horizontal wave-length
k = 2π / λ # horizontal wavenumber
m = π

ω² = f^2 + N^2 * k^2 / m^2 # and

ω = √(ω²)

# # Internal wave initial condition
#
# We impose modest gravitational acceleration to render time-stepping feasible,

using CLIMAParameters: AbstractEarthParameterSet, Planet
struct NonDimensionalParameters <: AbstractEarthParameterSet end

Planet.grav(::NonDimensionalParameters) = 256.0

# we'd like to use `θ` as a buoyancy variable, which requires
# setting the thermal expansion coefficient ``αᵀ`` to

g = Planet.grav(NonDimensionalParameters())

αᵀ = 1 / g

# We then use the "polarization relations" for vertically-standing, horizontally-
# propagating hydrostatic internal waves to initialze two wave packets.
# The hydrostatic polarization relations require
#
# ```math
# \begin{gather}
# (∂_t^2 + f^2) u = - ∂_x ∂_t p
# ∂_t v = - f u
# b = ∂_z p
# \end{gather}
# ```
#
# Thus given ``p = \cos (k x - ω t) \cos (m z)``, we find

δ = domain.L.x / 15
a(x) = 1e-6 * exp(-x^2 / 2 * δ^2)

ũ(x, z, t) = +a(x) * ω * sin(k * x - ω * t) * cos(m * z)
ṽ(x, z, t) = -a(x) * f * cos(k * x - ω * t) * cos(m * z)
θ̃(x, z, t) = -a(x) * m / k * (ω^2 - f^2) * sin(k * x - ω * t) * sin(m * z)

uᵢ(x, y, z) = ũ(x, z, 0)
vᵢ(x, y, z) = ṽ(x, z, 0)
θᵢ(x, y, z) = θ̃(x, z, 0) + N^2 * z

using ClimateMachine.Ocean.OceanProblems: InitialConditions

initial_conditions = InitialConditions(u = uᵢ, v = vᵢ, θ = θᵢ)

# # Model configuration
#
# We choose a time-step that resolves the gravity wave phase speed,

time_step = 0.005 # close to Δx / c = 0.5 * 1/16, where Δx is nominal resolution

# and build a model with a smidgeon of viscosity and diffusion,

using ClimateMachine.Ocean: HydrostaticBoussinesqSuperModel

model = HydrostaticBoussinesqSuperModel(
    domain = domain,
    time_step = time_step,
    initial_conditions = initial_conditions,
    parameters = NonDimensionalParameters(),
    turbulence_closure = (νʰ = 1e-6, νᶻ = 1e-6, κʰ = 1e-6, κᶻ = 1e-6),
    coriolis = (f₀ = f, β = 0),
    buoyancy = (αᵀ = αᵀ,),
    boundary_tags = ((1, 1), (1, 1), (1, 2)),
);
nothing

# # Fetching data for an animation
#
# To animate the `ClimateMachine.Ocean` solution, we assemble and
# cache the horizontal velocity ``u`` at periodic intervals:

using ClimateMachine.Ocean: current_time
using ClimateMachine.CartesianFields: assemble
using ClimateMachine.GenericCallbacks: EveryXSimulationTime

fetched_states = []
fetch_every = 0.2 * 2π / ω # time

data_fetcher = EveryXSimulationTime(fetch_every) do
    push!(
        fetched_states,
        (
            u = assemble(model.fields.u.elements),
            θ = assemble(model.fields.θ.elements),
            η = assemble(model.fields.η.elements),
            time = current_time(model),
        ),
    )
    return nothing
end

# We also build a callback to log the progress of our simulation,

using Printf
using ClimateMachine.GenericCallbacks: EveryXSimulationSteps
using ClimateMachine.Ocean: current_time, current_step, Δt

print_every = 100 # iterations
wall_clock = [time_ns()]

tiny_progress_printer = EveryXSimulationSteps(print_every) do

    @info(@sprintf(
        "Steps: %d, time: %.2f, Δt: %.2f, max(|u|): %.2e, elapsed time: %.2f secs",
        current_step(model),
        current_time(model),
        Δt(model),
        maximum(abs, model.fields.u),
        1e-9 * (time_ns() - wall_clock[1])
    ))

    wall_clock[1] = time_ns()
end

# # Running the simulation and animating the results
#
# We're ready to launch.

model.solver_configuration.timeend = 6 * 2π / ω
## model.solver.dt = 0.05 # make this work

@info """ Simulating a hydrostatic Gaussian wave packet with parameters

    f (Coriolis parameter):       $f
    N (buoyancy frequency):       $N
    Internal wave frequency:      $(abs(ω))
    Surface wave frequency:       $(k * sqrt(g * domain.L.z))
    Surface wave group velocity:  $(sqrt(g * domain.L.z))
    Internal wave group velocity: $(N^2 * k / (ω * m))
    Domain width:                 $(domain.L.x)
    Domain height:                $(domain.L.z)

"""

result = ClimateMachine.invoke!(
    model.solver_configuration;
    user_callbacks = [tiny_progress_printer, data_fetcher],
)

# # Animating the result
#
# We first analye the results to generate plotting limits and contour levels

ηmax = maximum([maximum(abs, state.η.data) for state in fetched_states])
umax = maximum([maximum(abs, state.u.data) for state in fetched_states])

ηlim = (-ηmax, ηmax)
ulim = (-umax, umax)
ulevels = range(ulim[1], ulim[2], length = 31)

# and then animate both fields in a loop,

using Plots

animation = @animate for (i, state) in enumerate(fetched_states)
    @info "Plotting frame $i of $(length(fetched_states))..."

    η_plot = plot(
        state.u.x[:, 1, 1],
        state.η.data[:, 1, 1],
        ylim = ηlim,
        label = nothing,
        title = @sprintf("η at t = %.2f", state.time),
    )

    u_plot = contourf(
        state.u.x[:, 1, 1],
        state.u.z[1, 1, :],
        clamp.(state.u.data[:, 1, :], ulim[1], ulim[2])';
        aspectratio = 64,
        linewidth = 0,
        xlim = domain.x,
        ylim = domain.z,
        xlabel = "x",
        ylabel = "z",
        color = :balance,
        colorbar = false,
        clim = ulim,
        levels = ulevels,
        title = @sprintf("u at t = %.2f", state.time),
    )

    plot(
        η_plot,
        u_plot,
        layout = Plots.grid(2, 1, heights = (0.3, 0.7)),
        link = :x,
        size = (600, 300),
    )
end

gif(animation, "internal_wave.mp4", fps = 5) # hide


text:
# This file is auto-generated by AWSMetadata.jl
using AWS
using AWS.AWSServices: kinesis_analytics_v2
using AWS.Compat
using AWS.UUIDs

"""
    add_application_cloud_watch_logging_option(application_name, cloud_watch_logging_option, current_application_version_id)
    add_application_cloud_watch_logging_option(application_name, cloud_watch_logging_option, current_application_version_id, params::Dict{String,<:Any})

Adds an Amazon CloudWatch log stream to monitor application configuration errors.

# Arguments
- `application_name`: The Kinesis Data Analytics application name.
- `cloud_watch_logging_option`: Provides the Amazon CloudWatch log stream Amazon Resource
  Name (ARN).
- `current_application_version_id`: The version ID of the Kinesis Data Analytics
  application. You can retrieve the application version ID using DescribeApplication.

"""
add_application_cloud_watch_logging_option(ApplicationName, CloudWatchLoggingOption, CurrentApplicationVersionId; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("AddApplicationCloudWatchLoggingOption", Dict{String, Any}("ApplicationName"=>ApplicationName, "CloudWatchLoggingOption"=>CloudWatchLoggingOption, "CurrentApplicationVersionId"=>CurrentApplicationVersionId); aws_config=aws_config)
add_application_cloud_watch_logging_option(ApplicationName, CloudWatchLoggingOption, CurrentApplicationVersionId, params::AbstractDict{String, <:Any}; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("AddApplicationCloudWatchLoggingOption", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ApplicationName"=>ApplicationName, "CloudWatchLoggingOption"=>CloudWatchLoggingOption, "CurrentApplicationVersionId"=>CurrentApplicationVersionId), params)); aws_config=aws_config)

"""
    add_application_input(application_name, current_application_version_id, input)
    add_application_input(application_name, current_application_version_id, input, params::Dict{String,<:Any})

 Adds a streaming source to your SQL-based Kinesis Data Analytics application.  You can add
a streaming source when you create an application, or you can use this operation to add a
streaming source after you create an application. For more information, see
CreateApplication. Any configuration update, including adding a streaming source using this
operation, results in a new version of the application. You can use the DescribeApplication
operation to find the current application version.

# Arguments
- `application_name`: The name of your existing application to which you want to add the
  streaming source.
- `current_application_version_id`: The current version of your application. You can use
  the DescribeApplication operation to find the current application version.
- `input`: The Input to add.

"""
add_application_input(ApplicationName, CurrentApplicationVersionId, Input; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("AddApplicationInput", Dict{String, Any}("ApplicationName"=>ApplicationName, "CurrentApplicationVersionId"=>CurrentApplicationVersionId, "Input"=>Input); aws_config=aws_config)
add_application_input(ApplicationName, CurrentApplicationVersionId, Input, params::AbstractDict{String, <:Any}; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("AddApplicationInput", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ApplicationName"=>ApplicationName, "CurrentApplicationVersionId"=>CurrentApplicationVersionId, "Input"=>Input), params)); aws_config=aws_config)

"""
    add_application_input_processing_configuration(application_name, current_application_version_id, input_id, input_processing_configuration)
    add_application_input_processing_configuration(application_name, current_application_version_id, input_id, input_processing_configuration, params::Dict{String,<:Any})

Adds an InputProcessingConfiguration to a SQL-based Kinesis Data Analytics application. An
input processor pre-processes records on the input stream before the application's SQL code
executes. Currently, the only input processor available is AWS Lambda.

# Arguments
- `application_name`: The name of the application to which you want to add the input
  processing configuration.
- `current_application_version_id`: The version of the application to which you want to add
  the input processing configuration. You can use the DescribeApplication operation to get
  the current application version. If the version specified is not the current version, the
  ConcurrentModificationException is returned.
- `input_id`: The ID of the input configuration to add the input processing configuration
  to. You can get a list of the input IDs for an application using the DescribeApplication
  operation.
- `input_processing_configuration`: The InputProcessingConfiguration to add to the
  application.

"""
add_application_input_processing_configuration(ApplicationName, CurrentApplicationVersionId, InputId, InputProcessingConfiguration; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("AddApplicationInputProcessingConfiguration", Dict{String, Any}("ApplicationName"=>ApplicationName, "CurrentApplicationVersionId"=>CurrentApplicationVersionId, "InputId"=>InputId, "InputProcessingConfiguration"=>InputProcessingConfiguration); aws_config=aws_config)
add_application_input_processing_configuration(ApplicationName, CurrentApplicationVersionId, InputId, InputProcessingConfiguration, params::AbstractDict{String, <:Any}; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("AddApplicationInputProcessingConfiguration", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ApplicationName"=>ApplicationName, "CurrentApplicationVersionId"=>CurrentApplicationVersionId, "InputId"=>InputId, "InputProcessingConfiguration"=>InputProcessingConfiguration), params)); aws_config=aws_config)

"""
    add_application_output(application_name, current_application_version_id, output)
    add_application_output(application_name, current_application_version_id, output, params::Dict{String,<:Any})

Adds an external destination to your SQL-based Kinesis Data Analytics application. If you
want Kinesis Data Analytics to deliver data from an in-application stream within your
application to an external destination (such as an Kinesis data stream, a Kinesis Data
Firehose delivery stream, or an AWS Lambda function), you add the relevant configuration to
your application using this operation. You can configure one or more outputs for your
application. Each output configuration maps an in-application stream and an external
destination.  You can use one of the output configurations to deliver data from your
in-application error stream to an external destination so that you can analyze the errors.
 Any configuration update, including adding a streaming source using this operation,
results in a new version of the application. You can use the DescribeApplication operation
to find the current application version.

# Arguments
- `application_name`: The name of the application to which you want to add the output
  configuration.
- `current_application_version_id`: The version of the application to which you want to add
  the output configuration. You can use the DescribeApplication operation to get the current
  application version. If the version specified is not the current version, the
  ConcurrentModificationException is returned.
- `output`: An array of objects, each describing one output configuration. In the output
  configuration, you specify the name of an in-application stream, a destination (that is, a
  Kinesis data stream, a Kinesis Data Firehose delivery stream, or an AWS Lambda function),
  and record the formation to use when writing to the destination.

"""
add_application_output(ApplicationName, CurrentApplicationVersionId, Output; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("AddApplicationOutput", Dict{String, Any}("ApplicationName"=>ApplicationName, "CurrentApplicationVersionId"=>CurrentApplicationVersionId, "Output"=>Output); aws_config=aws_config)
add_application_output(ApplicationName, CurrentApplicationVersionId, Output, params::AbstractDict{String, <:Any}; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("AddApplicationOutput", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ApplicationName"=>ApplicationName, "CurrentApplicationVersionId"=>CurrentApplicationVersionId, "Output"=>Output), params)); aws_config=aws_config)

"""
    add_application_reference_data_source(application_name, current_application_version_id, reference_data_source)
    add_application_reference_data_source(application_name, current_application_version_id, reference_data_source, params::Dict{String,<:Any})

Adds a reference data source to an existing SQL-based Kinesis Data Analytics application.
Kinesis Data Analytics reads reference data (that is, an Amazon S3 object) and creates an
in-application table within your application. In the request, you provide the source (S3
bucket name and object key name), name of the in-application table to create, and the
necessary mapping information that describes how data in an Amazon S3 object maps to
columns in the resulting in-application table.

# Arguments
- `application_name`: The name of an existing application.
- `current_application_version_id`: The version of the application for which you are adding
  the reference data source. You can use the DescribeApplication operation to get the current
  application version. If the version specified is not the current version, the
  ConcurrentModificationException is returned.
- `reference_data_source`: The reference data source can be an object in your Amazon S3
  bucket. Kinesis Data Analytics reads the object and copies the data into the in-application
  table that is created. You provide an S3 bucket, object key name, and the resulting
  in-application table that is created.

"""
add_application_reference_data_source(ApplicationName, CurrentApplicationVersionId, ReferenceDataSource; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("AddApplicationReferenceDataSource", Dict{String, Any}("ApplicationName"=>ApplicationName, "CurrentApplicationVersionId"=>CurrentApplicationVersionId, "ReferenceDataSource"=>ReferenceDataSource); aws_config=aws_config)
add_application_reference_data_source(ApplicationName, CurrentApplicationVersionId, ReferenceDataSource, params::AbstractDict{String, <:Any}; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("AddApplicationReferenceDataSource", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ApplicationName"=>ApplicationName, "CurrentApplicationVersionId"=>CurrentApplicationVersionId, "ReferenceDataSource"=>ReferenceDataSource), params)); aws_config=aws_config)

"""
    add_application_vpc_configuration(application_name, current_application_version_id, vpc_configuration)
    add_application_vpc_configuration(application_name, current_application_version_id, vpc_configuration, params::Dict{String,<:Any})

Adds a Virtual Private Cloud (VPC) configuration to the application. Applications can use
VPCs to store and access resources securely. Note the following about VPC configurations
for Kinesis Data Analytics applications:   VPC configurations are not supported for SQL
applications.   When a VPC is added to a Kinesis Data Analytics application, the
application can no longer be accessed from the Internet directly. To enable Internet access
to the application, add an Internet gateway to your VPC.

# Arguments
- `application_name`: The name of an existing application.
- `current_application_version_id`: The version of the application to which you want to add
  the VPC configuration. You can use the DescribeApplication operation to get the current
  application version. If the version specified is not the current version, the
  ConcurrentModificationException is returned.
- `vpc_configuration`: Description of the VPC to add to the application.

"""
add_application_vpc_configuration(ApplicationName, CurrentApplicationVersionId, VpcConfiguration; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("AddApplicationVpcConfiguration", Dict{String, Any}("ApplicationName"=>ApplicationName, "CurrentApplicationVersionId"=>CurrentApplicationVersionId, "VpcConfiguration"=>VpcConfiguration); aws_config=aws_config)
add_application_vpc_configuration(ApplicationName, CurrentApplicationVersionId, VpcConfiguration, params::AbstractDict{String, <:Any}; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("AddApplicationVpcConfiguration", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ApplicationName"=>ApplicationName, "CurrentApplicationVersionId"=>CurrentApplicationVersionId, "VpcConfiguration"=>VpcConfiguration), params)); aws_config=aws_config)

"""
    create_application(application_name, runtime_environment, service_execution_role)
    create_application(application_name, runtime_environment, service_execution_role, params::Dict{String,<:Any})

Creates a Kinesis Data Analytics application. For information about creating a Kinesis Data
Analytics application, see Creating an Application.

# Arguments
- `application_name`: The name of your application (for example, sample-app).
- `runtime_environment`: The runtime environment for the application (SQL-1.0, FLINK-1_6,
  or FLINK-1_8).
- `service_execution_role`: The IAM role used by the application to access Kinesis data
  streams, Kinesis Data Firehose delivery streams, Amazon S3 objects, and other external
  resources.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"ApplicationConfiguration"`: Use this parameter to configure the application.
- `"ApplicationDescription"`: A summary description of the application.
- `"CloudWatchLoggingOptions"`: Use this parameter to configure an Amazon CloudWatch log
  stream to monitor application configuration errors.
- `"Tags"`: A list of one or more tags to assign to the application. A tag is a key-value
  pair that identifies an application. Note that the maximum number of application tags
  includes system tags. The maximum number of user-defined application tags is 50. For more
  information, see Using Tagging.
"""
create_application(ApplicationName, RuntimeEnvironment, ServiceExecutionRole; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("CreateApplication", Dict{String, Any}("ApplicationName"=>ApplicationName, "RuntimeEnvironment"=>RuntimeEnvironment, "ServiceExecutionRole"=>ServiceExecutionRole); aws_config=aws_config)
create_application(ApplicationName, RuntimeEnvironment, ServiceExecutionRole, params::AbstractDict{String, <:Any}; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("CreateApplication", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ApplicationName"=>ApplicationName, "RuntimeEnvironment"=>RuntimeEnvironment, "ServiceExecutionRole"=>ServiceExecutionRole), params)); aws_config=aws_config)

"""
    create_application_presigned_url(application_name, url_type)
    create_application_presigned_url(application_name, url_type, params::Dict{String,<:Any})

Creates and returns a URL that you can use to connect to an application's extension.
Currently, the only available extension is the Apache Flink dashboard. The IAM role or user
used to call this API defines the permissions to access the extension. Once the presigned
URL is created, no additional permission is required to access this URL. IAM authorization
policies for this API are also enforced for every HTTP request that attempts to connect to
the extension.   The URL that you get from a call to CreateApplicationPresignedUrl must be
used within 3 minutes to be valid. If you first try to use the URL after the 3-minute limit
expires, the service returns an HTTP 403 Forbidden error.

# Arguments
- `application_name`: The name of the application.
- `url_type`: The type of the extension for which to create and return a URL. Currently,
  the only valid extension URL type is FLINK_DASHBOARD_URL.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"SessionExpirationDurationInSeconds"`: The duration in seconds for which the returned
  URL will be valid.
"""
create_application_presigned_url(ApplicationName, UrlType; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("CreateApplicationPresignedUrl", Dict{String, Any}("ApplicationName"=>ApplicationName, "UrlType"=>UrlType); aws_config=aws_config)
create_application_presigned_url(ApplicationName, UrlType, params::AbstractDict{String, <:Any}; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("CreateApplicationPresignedUrl", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ApplicationName"=>ApplicationName, "UrlType"=>UrlType), params)); aws_config=aws_config)

"""
    create_application_snapshot(application_name, snapshot_name)
    create_application_snapshot(application_name, snapshot_name, params::Dict{String,<:Any})

Creates a snapshot of the application's state data.

# Arguments
- `application_name`: The name of an existing application
- `snapshot_name`: An identifier for the application snapshot.

"""
create_application_snapshot(ApplicationName, SnapshotName; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("CreateApplicationSnapshot", Dict{String, Any}("ApplicationName"=>ApplicationName, "SnapshotName"=>SnapshotName); aws_config=aws_config)
create_application_snapshot(ApplicationName, SnapshotName, params::AbstractDict{String, <:Any}; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("CreateApplicationSnapshot", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ApplicationName"=>ApplicationName, "SnapshotName"=>SnapshotName), params)); aws_config=aws_config)

"""
    delete_application(application_name, create_timestamp)
    delete_application(application_name, create_timestamp, params::Dict{String,<:Any})

Deletes the specified application. Kinesis Data Analytics halts application execution and
deletes the application.

# Arguments
- `application_name`: The name of the application to delete.
- `create_timestamp`: Use the DescribeApplication operation to get this value.

"""
delete_application(ApplicationName, CreateTimestamp; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("DeleteApplication", Dict{String, Any}("ApplicationName"=>ApplicationName, "CreateTimestamp"=>CreateTimestamp); aws_config=aws_config)
delete_application(ApplicationName, CreateTimestamp, params::AbstractDict{String, <:Any}; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("DeleteApplication", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ApplicationName"=>ApplicationName, "CreateTimestamp"=>CreateTimestamp), params)); aws_config=aws_config)

"""
    delete_application_cloud_watch_logging_option(application_name, cloud_watch_logging_option_id, current_application_version_id)
    delete_application_cloud_watch_logging_option(application_name, cloud_watch_logging_option_id, current_application_version_id, params::Dict{String,<:Any})

Deletes an Amazon CloudWatch log stream from an Kinesis Data Analytics application.

# Arguments
- `application_name`: The application name.
- `cloud_watch_logging_option_id`: The CloudWatchLoggingOptionId of the Amazon CloudWatch
  logging option to delete. You can get the CloudWatchLoggingOptionId by using the
  DescribeApplication operation.
- `current_application_version_id`: The version ID of the application. You can retrieve the
  application version ID using DescribeApplication.

"""
delete_application_cloud_watch_logging_option(ApplicationName, CloudWatchLoggingOptionId, CurrentApplicationVersionId; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("DeleteApplicationCloudWatchLoggingOption", Dict{String, Any}("ApplicationName"=>ApplicationName, "CloudWatchLoggingOptionId"=>CloudWatchLoggingOptionId, "CurrentApplicationVersionId"=>CurrentApplicationVersionId); aws_config=aws_config)
delete_application_cloud_watch_logging_option(ApplicationName, CloudWatchLoggingOptionId, CurrentApplicationVersionId, params::AbstractDict{String, <:Any}; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("DeleteApplicationCloudWatchLoggingOption", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ApplicationName"=>ApplicationName, "CloudWatchLoggingOptionId"=>CloudWatchLoggingOptionId, "CurrentApplicationVersionId"=>CurrentApplicationVersionId), params)); aws_config=aws_config)

"""
    delete_application_input_processing_configuration(application_name, current_application_version_id, input_id)
    delete_application_input_processing_configuration(application_name, current_application_version_id, input_id, params::Dict{String,<:Any})

Deletes an InputProcessingConfiguration from an input.

# Arguments
- `application_name`: The name of the application.
- `current_application_version_id`: The application version. You can use the
  DescribeApplication operation to get the current application version. If the version
  specified is not the current version, the ConcurrentModificationException is returned.
- `input_id`: The ID of the input configuration from which to delete the input processing
  configuration. You can get a list of the input IDs for an application by using the
  DescribeApplication operation.

"""
delete_application_input_processing_configuration(ApplicationName, CurrentApplicationVersionId, InputId; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("DeleteApplicationInputProcessingConfiguration", Dict{String, Any}("ApplicationName"=>ApplicationName, "CurrentApplicationVersionId"=>CurrentApplicationVersionId, "InputId"=>InputId); aws_config=aws_config)
delete_application_input_processing_configuration(ApplicationName, CurrentApplicationVersionId, InputId, params::AbstractDict{String, <:Any}; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("DeleteApplicationInputProcessingConfiguration", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ApplicationName"=>ApplicationName, "CurrentApplicationVersionId"=>CurrentApplicationVersionId, "InputId"=>InputId), params)); aws_config=aws_config)

"""
    delete_application_output(application_name, current_application_version_id, output_id)
    delete_application_output(application_name, current_application_version_id, output_id, params::Dict{String,<:Any})

Deletes the output destination configuration from your SQL-based Kinesis Data Analytics
application's configuration. Kinesis Data Analytics will no longer write data from the
corresponding in-application stream to the external output destination.

# Arguments
- `application_name`: The application name.
- `current_application_version_id`: The application version. You can use the
  DescribeApplication operation to get the current application version. If the version
  specified is not the current version, the ConcurrentModificationException is returned.
- `output_id`: The ID of the configuration to delete. Each output configuration that is
  added to the application (either when the application is created or later) using the
  AddApplicationOutput operation has a unique ID. You need to provide the ID to uniquely
  identify the output configuration that you want to delete from the application
  configuration. You can use the DescribeApplication operation to get the specific OutputId.

"""
delete_application_output(ApplicationName, CurrentApplicationVersionId, OutputId; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("DeleteApplicationOutput", Dict{String, Any}("ApplicationName"=>ApplicationName, "CurrentApplicationVersionId"=>CurrentApplicationVersionId, "OutputId"=>OutputId); aws_config=aws_config)
delete_application_output(ApplicationName, CurrentApplicationVersionId, OutputId, params::AbstractDict{String, <:Any}; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("DeleteApplicationOutput", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ApplicationName"=>ApplicationName, "CurrentApplicationVersionId"=>CurrentApplicationVersionId, "OutputId"=>OutputId), params)); aws_config=aws_config)

"""
    delete_application_reference_data_source(application_name, current_application_version_id, reference_id)
    delete_application_reference_data_source(application_name, current_application_version_id, reference_id, params::Dict{String,<:Any})

Deletes a reference data source configuration from the specified SQL-based Kinesis Data
Analytics application's configuration. If the application is running, Kinesis Data
Analytics immediately removes the in-application table that you created using the
AddApplicationReferenceDataSource operation.

# Arguments
- `application_name`: The name of an existing application.
- `current_application_version_id`: The current application version. You can use the
  DescribeApplication operation to get the current application version. If the version
  specified is not the current version, the ConcurrentModificationException is returned.
- `reference_id`: The ID of the reference data source. When you add a reference data source
  to your application using the AddApplicationReferenceDataSource, Kinesis Data Analytics
  assigns an ID. You can use the DescribeApplication operation to get the reference ID.

"""
delete_application_reference_data_source(ApplicationName, CurrentApplicationVersionId, ReferenceId; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("DeleteApplicationReferenceDataSource", Dict{String, Any}("ApplicationName"=>ApplicationName, "CurrentApplicationVersionId"=>CurrentApplicationVersionId, "ReferenceId"=>ReferenceId); aws_config=aws_config)
delete_application_reference_data_source(ApplicationName, CurrentApplicationVersionId, ReferenceId, params::AbstractDict{String, <:Any}; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("DeleteApplicationReferenceDataSource", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ApplicationName"=>ApplicationName, "CurrentApplicationVersionId"=>CurrentApplicationVersionId, "ReferenceId"=>ReferenceId), params)); aws_config=aws_config)

"""
    delete_application_snapshot(application_name, snapshot_creation_timestamp, snapshot_name)
    delete_application_snapshot(application_name, snapshot_creation_timestamp, snapshot_name, params::Dict{String,<:Any})

Deletes a snapshot of application state.

# Arguments
- `application_name`: The name of an existing application.
- `snapshot_creation_timestamp`: The creation timestamp of the application snapshot to
  delete. You can retrieve this value using or .
- `snapshot_name`: The identifier for the snapshot delete.

"""
delete_application_snapshot(ApplicationName, SnapshotCreationTimestamp, SnapshotName; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("DeleteApplicationSnapshot", Dict{String, Any}("ApplicationName"=>ApplicationName, "SnapshotCreationTimestamp"=>SnapshotCreationTimestamp, "SnapshotName"=>SnapshotName); aws_config=aws_config)
delete_application_snapshot(ApplicationName, SnapshotCreationTimestamp, SnapshotName, params::AbstractDict{String, <:Any}; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("DeleteApplicationSnapshot", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ApplicationName"=>ApplicationName, "SnapshotCreationTimestamp"=>SnapshotCreationTimestamp, "SnapshotName"=>SnapshotName), params)); aws_config=aws_config)

"""
    delete_application_vpc_configuration(application_name, current_application_version_id, vpc_configuration_id)
    delete_application_vpc_configuration(application_name, current_application_version_id, vpc_configuration_id, params::Dict{String,<:Any})

Removes a VPC configuration from a Kinesis Data Analytics application.

# Arguments
- `application_name`: The name of an existing application.
- `current_application_version_id`: The current application version ID. You can retrieve
  the application version ID using DescribeApplication.
- `vpc_configuration_id`: The ID of the VPC configuration to delete.

"""
delete_application_vpc_configuration(ApplicationName, CurrentApplicationVersionId, VpcConfigurationId; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("DeleteApplicationVpcConfiguration", Dict{String, Any}("ApplicationName"=>ApplicationName, "CurrentApplicationVersionId"=>CurrentApplicationVersionId, "VpcConfigurationId"=>VpcConfigurationId); aws_config=aws_config)
delete_application_vpc_configuration(ApplicationName, CurrentApplicationVersionId, VpcConfigurationId, params::AbstractDict{String, <:Any}; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("DeleteApplicationVpcConfiguration", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ApplicationName"=>ApplicationName, "CurrentApplicationVersionId"=>CurrentApplicationVersionId, "VpcConfigurationId"=>VpcConfigurationId), params)); aws_config=aws_config)

"""
    describe_application(application_name)
    describe_application(application_name, params::Dict{String,<:Any})

Returns information about a specific Kinesis Data Analytics application. If you want to
retrieve a list of all applications in your account, use the ListApplications operation.

# Arguments
- `application_name`: The name of the application.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"IncludeAdditionalDetails"`: Displays verbose information about a Kinesis Data Analytics
  application, including the application's job plan.
"""
describe_application(ApplicationName; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("DescribeApplication", Dict{String, Any}("ApplicationName"=>ApplicationName); aws_config=aws_config)
describe_application(ApplicationName, params::AbstractDict{String, <:Any}; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("DescribeApplication", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ApplicationName"=>ApplicationName), params)); aws_config=aws_config)

"""
    describe_application_snapshot(application_name, snapshot_name)
    describe_application_snapshot(application_name, snapshot_name, params::Dict{String,<:Any})

Returns information about a snapshot of application state data.

# Arguments
- `application_name`: The name of an existing application.
- `snapshot_name`: The identifier of an application snapshot. You can retrieve this value
  using .

"""
describe_application_snapshot(ApplicationName, SnapshotName; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("DescribeApplicationSnapshot", Dict{String, Any}("ApplicationName"=>ApplicationName, "SnapshotName"=>SnapshotName); aws_config=aws_config)
describe_application_snapshot(ApplicationName, SnapshotName, params::AbstractDict{String, <:Any}; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("DescribeApplicationSnapshot", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ApplicationName"=>ApplicationName, "SnapshotName"=>SnapshotName), params)); aws_config=aws_config)

"""
    discover_input_schema(service_execution_role)
    discover_input_schema(service_execution_role, params::Dict{String,<:Any})

Infers a schema for a SQL-based Kinesis Data Analytics application by evaluating sample
records on the specified streaming source (Kinesis data stream or Kinesis Data Firehose
delivery stream) or Amazon S3 object. In the response, the operation returns the inferred
schema and also the sample records that the operation used to infer the schema.  You can
use the inferred schema when configuring a streaming source for your application. When you
create an application using the Kinesis Data Analytics console, the console uses this
operation to infer a schema and show it in the console user interface.

# Arguments
- `service_execution_role`: The ARN of the role that is used to access the streaming source.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"InputProcessingConfiguration"`: The InputProcessingConfiguration to use to preprocess
  the records before discovering the schema of the records.
- `"InputStartingPositionConfiguration"`: The point at which you want Kinesis Data
  Analytics to start reading records from the specified streaming source discovery purposes.
- `"ResourceARN"`: The Amazon Resource Name (ARN) of the streaming source.
- `"S3Configuration"`: Specify this parameter to discover a schema from data in an Amazon
  S3 object.
"""
discover_input_schema(ServiceExecutionRole; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("DiscoverInputSchema", Dict{String, Any}("ServiceExecutionRole"=>ServiceExecutionRole); aws_config=aws_config)
discover_input_schema(ServiceExecutionRole, params::AbstractDict{String, <:Any}; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("DiscoverInputSchema", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ServiceExecutionRole"=>ServiceExecutionRole), params)); aws_config=aws_config)

"""
    list_application_snapshots(application_name)
    list_application_snapshots(application_name, params::Dict{String,<:Any})

Lists information about the current application snapshots.

# Arguments
- `application_name`: The name of an existing application.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"Limit"`: The maximum number of application snapshots to list.
- `"NextToken"`: Use this parameter if you receive a NextToken response in a previous
  request that indicates that there is more output available. Set it to the value of the
  previous call's NextToken response to indicate where the output should continue from.
"""
list_application_snapshots(ApplicationName; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("ListApplicationSnapshots", Dict{String, Any}("ApplicationName"=>ApplicationName); aws_config=aws_config)
list_application_snapshots(ApplicationName, params::AbstractDict{String, <:Any}; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("ListApplicationSnapshots", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ApplicationName"=>ApplicationName), params)); aws_config=aws_config)

"""
    list_applications()
    list_applications(params::Dict{String,<:Any})

Returns a list of Kinesis Data Analytics applications in your account. For each
application, the response includes the application name, Amazon Resource Name (ARN), and
status.  If you want detailed information about a specific application, use
DescribeApplication.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"Limit"`: The maximum number of applications to list.
- `"NextToken"`: If a previous command returned a pagination token, pass it into this value
  to retrieve the next set of results. For more information about pagination, see Using the
  AWS Command Line Interface's Pagination Options.
"""
list_applications(; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("ListApplications"; aws_config=aws_config)
list_applications(params::AbstractDict{String, <:Any}; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("ListApplications", params; aws_config=aws_config)

"""
    list_tags_for_resource(resource_arn)
    list_tags_for_resource(resource_arn, params::Dict{String,<:Any})

Retrieves the list of key-value tags assigned to the application. For more information, see
Using Tagging.

# Arguments
- `resource_arn`: The ARN of the application for which to retrieve tags.

"""
list_tags_for_resource(ResourceARN; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("ListTagsForResource", Dict{String, Any}("ResourceARN"=>ResourceARN); aws_config=aws_config)
list_tags_for_resource(ResourceARN, params::AbstractDict{String, <:Any}; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("ListTagsForResource", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ResourceARN"=>ResourceARN), params)); aws_config=aws_config)

"""
    start_application(application_name, run_configuration)
    start_application(application_name, run_configuration, params::Dict{String,<:Any})

Starts the specified Kinesis Data Analytics application. After creating an application, you
must exclusively call this operation to start your application.

# Arguments
- `application_name`: The name of the application.
- `run_configuration`: Identifies the run configuration (start parameters) of a Kinesis
  Data Analytics application.

"""
start_application(ApplicationName, RunConfiguration; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("StartApplication", Dict{String, Any}("ApplicationName"=>ApplicationName, "RunConfiguration"=>RunConfiguration); aws_config=aws_config)
start_application(ApplicationName, RunConfiguration, params::AbstractDict{String, <:Any}; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("StartApplication", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ApplicationName"=>ApplicationName, "RunConfiguration"=>RunConfiguration), params)); aws_config=aws_config)

"""
    stop_application(application_name)
    stop_application(application_name, params::Dict{String,<:Any})

Stops the application from processing data. You can stop an application only if it is in
the running status, unless you set the Force parameter to true. You can use the
DescribeApplication operation to find the application status.  Kinesis Data Analytics takes
a snapshot when the application is stopped, unless Force is set to true.

# Arguments
- `application_name`: The name of the running application to stop.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"Force"`: Set to true to force the application to stop. If you set Force to true,
  Kinesis Data Analytics stops the application without taking a snapshot.   Force-stopping
  your application may lead to data loss or duplication. To prevent data loss or duplicate
  processing of data during application restarts, we recommend you to take frequent snapshots
  of your application.  You can only force stop a Flink-based Kinesis Data Analytics
  application. You can't force stop a SQL-based Kinesis Data Analytics application. The
  application must be in the STARTING, UPDATING, STOPPING, AUTOSCALING, or RUNNING status.
"""
stop_application(ApplicationName; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("StopApplication", Dict{String, Any}("ApplicationName"=>ApplicationName); aws_config=aws_config)
stop_application(ApplicationName, params::AbstractDict{String, <:Any}; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("StopApplication", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ApplicationName"=>ApplicationName), params)); aws_config=aws_config)

"""
    tag_resource(resource_arn, tags)
    tag_resource(resource_arn, tags, params::Dict{String,<:Any})

Adds one or more key-value tags to a Kinesis Data Analytics application. Note that the
maximum number of application tags includes system tags. The maximum number of user-defined
application tags is 50. For more information, see Using Tagging.

# Arguments
- `resource_arn`: The ARN of the application to assign the tags.
- `tags`: The key-value tags to assign to the application.

"""
tag_resource(ResourceARN, Tags; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("TagResource", Dict{String, Any}("ResourceARN"=>ResourceARN, "Tags"=>Tags); aws_config=aws_config)
tag_resource(ResourceARN, Tags, params::AbstractDict{String, <:Any}; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("TagResource", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ResourceARN"=>ResourceARN, "Tags"=>Tags), params)); aws_config=aws_config)

"""
    untag_resource(resource_arn, tag_keys)
    untag_resource(resource_arn, tag_keys, params::Dict{String,<:Any})

Removes one or more tags from a Kinesis Data Analytics application. For more information,
see Using Tagging.

# Arguments
- `resource_arn`: The ARN of the Kinesis Data Analytics application from which to remove
  the tags.
- `tag_keys`: A list of keys of tags to remove from the specified application.

"""
untag_resource(ResourceARN, TagKeys; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("UntagResource", Dict{String, Any}("ResourceARN"=>ResourceARN, "TagKeys"=>TagKeys); aws_config=aws_config)
untag_resource(ResourceARN, TagKeys, params::AbstractDict{String, <:Any}; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("UntagResource", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ResourceARN"=>ResourceARN, "TagKeys"=>TagKeys), params)); aws_config=aws_config)

"""
    update_application(application_name, current_application_version_id)
    update_application(application_name, current_application_version_id, params::Dict{String,<:Any})

Updates an existing Kinesis Data Analytics application. Using this operation, you can
update application code, input configuration, and output configuration.  Kinesis Data
Analytics updates the ApplicationVersionId each time you update your application.   You
cannot update the RuntimeEnvironment of an existing application. If you need to update an
application's RuntimeEnvironment, you must delete the application and create it again.

# Arguments
- `application_name`: The name of the application to update.
- `current_application_version_id`: The current application version ID. You can retrieve
  the application version ID using DescribeApplication.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"ApplicationConfigurationUpdate"`: Describes application configuration updates.
- `"CloudWatchLoggingOptionUpdates"`: Describes application Amazon CloudWatch logging
  option updates. You can only update existing CloudWatch logging options with this action.
  To add a new CloudWatch logging option, use AddApplicationCloudWatchLoggingOption.
- `"RunConfigurationUpdate"`: Describes updates to the application's starting parameters.
- `"ServiceExecutionRoleUpdate"`: Describes updates to the service execution role.
"""
update_application(ApplicationName, CurrentApplicationVersionId; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("UpdateApplication", Dict{String, Any}("ApplicationName"=>ApplicationName, "CurrentApplicationVersionId"=>CurrentApplicationVersionId); aws_config=aws_config)
update_application(ApplicationName, CurrentApplicationVersionId, params::AbstractDict{String, <:Any}; aws_config::AbstractAWSConfig=global_aws_config()) = kinesis_analytics_v2("UpdateApplication", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ApplicationName"=>ApplicationName, "CurrentApplicationVersionId"=>CurrentApplicationVersionId), params)); aws_config=aws_config)


text:
using Plots;
gr();
using LaTeXStrings

savepath = "/home/user/cameronperot.com/images/2019-12-29-simulating-magnetism-part-1";
save_bool = true
if save_bool
    dpi = 300
else
    dpi = 144
end

# %% energy plot

βs = range(0, stop = 5, length = 100)
e = -tanh.(βs);
plot_e = plot(
    βs,
    e,
    dpi = dpi,
    label = L"-\tanh\beta",
    xaxis = (L"\beta"),
    yaxis = (L"e"),
    line = (2, :dodgerblue, 0.8),
);

# %% heat capacity plot

c = βs .^ 2 ./ cosh.(βs) .^ 2;
plot_c = plot(
    βs,
    c,
    dpi = dpi,
    label = L"\beta^2 / \cosh^2 \beta",
    xaxis = (L"\beta"),
    yaxis = (L"c"),
    line = (2, :dodgerblue, 0.8),
);

# %% combined plot

plot_combined = plot(plot_e, plot_c, layout = (2, 1), size = (800, 600))

if save_bool
    savefig(plot_combined, joinpath(savepath, "1d_ising_e_c.png"))
end

plot_combined


text:
println("##### methods #####")

using Test
using TreeTools

## Testing equality operator
root_1 = TreeTools.read_newick("$(dirname(pathof(TreeTools)))/../test/methods/tree1.nwk")
root_2 = TreeTools.read_newick("$(dirname(pathof(TreeTools)))/../test/methods/tree1_reordered.nwk")
@testset "Equality `==`" begin
	@test root_1 == root_2
end

@testset "node2tree" begin
	@test typeof(node2tree(root_1)) <: Tree
	@test typeof(node2tree(root_2)) <: Tree
end

# Testing ancestors
@testset "Ancestors" begin
    root_1 = TreeTools.read_newick("$(dirname(pathof(TreeTools)))/../test/methods/tree1.nwk")
    @test TreeTools.isancestor(root_1, root_1.child[1])
    @test TreeTools.isancestor(root_1, root_1.child[1].child[1])
    @test !TreeTools.isancestor(root_1.child[1],root_1.child[2])
    root_2 = TreeTools.read_newick("$(dirname(pathof(TreeTools)))/../test/methods/tree2.nwk")
    @test lca((root_2.child[1].child[1], root_2.child[1].child[2])).label == "ABC"
    @test lca((root_2.child[1].child[1].child[1], root_2.child[1].child[1].child[2], root_2.child[1].child[2])).label == "ABC"
    @test lca((root_2.child[1].child[1], root_2.child[2])).label == "ABCD"
end

@testset "Count" begin
	t1 = node2tree(root_1)
	@test count(isleaf, t1) == 4
	@test count(n -> n.label[1] == 'A', t1) == 3
	@test count(isleaf, t1.lnodes["AB"]) == 2
	@test count(n -> n.label[1] == 'A', t1.lnodes["AB"]) == 2
end

max_stars_repo_name: PierreBarrat/TreeTools

max_issues_repo_name: PierreBarrat/TreeTools

max_forks_repo_name: PierreBarrat/TreeTools


text:
using Test
@testset "Test1" begin
    @test true
    sleep(0.5)
    @test true
    sleep(0.5)
end


text:
export Abstract_BC_Solver_Result

# Result abstraction for bound constrained problems ================
#
# For the moment only use an alias as we do not have specific methods
#
@doc raw"""

The structure returned by [`solve`](@ref) when using the
[`LevenbergMarquardt_BC_Conf`](@ref) method.

See [`Abstract_Solver_Result`](@ref)
"""
const Abstract_BC_Solver_Result = Abstract_Solver_Result


text:
using Parameters, Dates, JuMP, Gurobi, Plots, Logging
using ShelfSpaceAllocation

"""Partition array into subarrays of size n."""
function partition(n::Integer, array::Array{T}):: Array{Array{T}} where T
    r = []
    i = 0
    m = div(length(array), n)
    for _ in 1:(m-1)
        push!(r, [array[j+i] for j in 1:n])
        i += n
    end
    push!(r, array[(i+1):end])
    return r
end

"""Relax-and-fix heuristic."""
function relax_and_fix(parameters::Params, specs::Specs, block_partitions, optimizer)
    # Remember fixed blocks and values
    relaxed_blocks = block_partitions[2:end]
    # TODO: test fixing related n_ps variables too for faster runtime
    fixed_blocks = []
    z_bs = []
    b_bs = []
    model = ShelfSpaceAllocationModel()
    for block in block_partitions
        # Solve the shelf space allocation model with a subset of blocks
        model = ShelfSpaceAllocationModel(parameters, specs)

        # Relaxed values
        for relaxed_block in relaxed_blocks
            for b in relaxed_block, s in parameters.shelves
                unset_binary(model[:z_bs][b, s])
                unset_binary(model[:z_bs_f][b, s])
                unset_binary(model[:z_bs_l][b, s])
            end
        end

        # Fixed values
        for (i, fixed_block) in enumerate(fixed_blocks)
            for b in fixed_block, s in parameters.shelves
                unset_binary(model[:z_bs][b, s])
                fix(model[:b_bs][b, s], b_bs[i][b, s], force=true)
                fix(model[:z_bs][b, s], z_bs[i][b, s], force=true)
            end
        end

        set_optimizer(model, optimizer)
        optimize!(model)

        if termination_status(model) == MOI.INFEASIBLE
            exit()
        end

        # Decrease relaxed blocks
        relaxed_blocks = relaxed_blocks[2:end]

        push!(fixed_blocks, block)
        push!(b_bs, value.(model[:b_bs]))
        push!(z_bs, value.(model[:z_bs]))
    end

    return model
end

"""Fix-and-optimize heuristic."""
function fix_and_optimize(parameters::Params, specs::Specs, z_bs, w_bb)
    @unpack blocks, shelves = parameters
    model = ShelfSpaceAllocationModel(parameters, specs)

    # Fix z_bb variables
    for b in blocks, s in shelves
        unset_binary(model[:z_bs][b, s])
        fix(model[:z_bs][b, s], z_bs[b, s])
    end

    # Fix w_bb variables
    for b in blocks, b′ in blocks
        unset_binary(model[:w_bb][b, b′])
        fix(model[:w_bb][b, b′], w_bb[b, b′])
    end

    return model
end

# --- Arguments ---
case = "large" # ∈ Set("small", "medium", "large")
partition_size = Dict(
    "small" => 4,
    "medium" => 2,
    "large" => 6
)[case]
block_partition_order = Dict(
    "small" => "increasing",
    "medium" => "decreasing",
    "large" => "increasing"
)[case]
product_path = joinpath(@__DIR__, "instances", case, "products.csv")
shelf_path = joinpath(@__DIR__, "instances", case, "shelves.csv")
output_dir = joinpath(@__DIR__, "output_heuristics", case, string(Dates.now()))

@info "Creating output directory"
mkpath(output_dir)

@info "Arguments" product_path shelf_path output_dir

@info "Loading parameters"
parameters = Params(product_path, shelf_path)
save_json(parameters, joinpath(output_dir, "parameters.json"))

# --- Space allocation without blocks ---
@info "Space allocation without blocks"
specs1 = Specs(height_placement=false, blocking=false)
model1 = ShelfSpaceAllocationModel(parameters, specs1)

optimizer1 = optimizer_with_attributes(
    () -> Gurobi.Optimizer(Gurobi.Env()),
    "TimeLimit" => 60,
    "LogFile" => joinpath(output_dir, "grb_ssa_no_blocks.log"),
    "MIPGap" => 0.01
)
set_optimizer(model1, optimizer1)
optimize!(model1);

variables1 = Variables(model1)
objectives1 = Objectives(model1)
save_json(variables1, joinpath(output_dir, "variables1.json"))
save_json(objectives1, joinpath(output_dir, "objectives1.json"))

# --- Block partitions ---
@info "Define block partitions"

@unpack shelves, blocks, P_b = parameters
n_ps = value.(model1[:n_ps])

# Amount of products allocated per block.
amounts = round.(
    [sum(n_ps[p, s] for s in shelves for p in P_b[b]) for b in blocks])

if block_partition_order == "decreasing"
    # Blocks from highest to lowest amount of product allocated per block.
    block_indices = reverse(sortperm(amounts))
elseif block_partition_order == "increasing"
    block_indices = sortperm(amounts)
else
    error("No block partition order: $block_partition_order")
end

# Partition the blocks into arrays of partition size
block_partitions = partition(partition_size, block_indices)

@info amounts block_indices block_partitions

# --- Relax-and-Fix Heuristic ---
@info "Relax-and-fix"
optimizer2 = optimizer_with_attributes(
    () -> Gurobi.Optimizer(Gurobi.Env()),
    "TimeLimit" => 5*60,
    "LogFile" => joinpath(output_dir, "grb_relax_and_fix.log"),
    "MIPFocus" => 3,
    "MIPGap" => 0.01
)
specs2 = Specs(height_placement=false, blocking=true)
model2 = relax_and_fix(parameters, specs2, block_partitions, optimizer2)

variables2 = Variables(model2)
objectives2 = Objectives(model2)
save_json(variables2, joinpath(output_dir, "variables2.json"))
save_json(objectives2, joinpath(output_dir, "objectives2.json"))

# --- Fix-and-Optimize Heuristic ---
@info "Fix-and-optimize"
model3 = fix_and_optimize(
    parameters, specs2, value.(model2[:z_bs]), value.(model2[:w_bb]))

optimizer3 = optimizer_with_attributes(
    () -> Gurobi.Optimizer(Gurobi.Env()),
    "TimeLimit" => 2*60,
    "LogFile" => joinpath(output_dir, "grb_fix_and_optimize.log"),
    "MIPFocus" => 3,
    "MIPGap" => 0.005
)
set_optimizer(model3, optimizer3)
optimize!(model3)

variables3 = Variables(model3)
objectives3 = Objectives(model3)
save_json(variables3, joinpath(output_dir, "variables3.json"))
save_json(objectives3, joinpath(output_dir, "objectives3.json"))

@info "Plotting"

# --- Plotting 1 ---
ps = plot_planograms_no_blocks(parameters, variables1)
for (i, p) in enumerate(ps)
    savefig(p, joinpath(output_dir, "planogram_no_blocks_$i.svg"))
end

p = plot_allocation_amount(parameters, variables1)
savefig(p, joinpath(output_dir, "allocation_amount_no_blocks.svg"))

# --- Plotting 2 ---
ps = plot_planograms(parameters, variables2)
for (i, p) in enumerate(ps)
    savefig(p, joinpath(output_dir, "planogram_relax_and_fix_$i.svg"))
end

# --- Plotting 3 ---
ps = plot_planograms(parameters, variables3)
for (i, p) in enumerate(ps)
    savefig(p, joinpath(output_dir, "planogram_$i.svg"))
end

p = plot_product_facings(parameters, variables3)
savefig(p, joinpath(output_dir, "product_facings.svg"))

p = plot_demand_and_sales(parameters, variables3)
savefig(p, joinpath(output_dir, "demand_and_sales.svg"))

p = plot_demand_sales_percentage(parameters, variables3)
savefig(p, joinpath(output_dir, "demand_sales_percentage.svg"))

p = plot_allocation_amount(parameters, variables3)
savefig(p, joinpath(output_dir, "allocation_amount.svg"))

p = plot_allocation_percentage(
    parameters, variables3,
    optimizer_with_attributes(() -> Gurobi.Optimizer(Gurobi.Env()), TimeLimit=60, LogToConsole=false))
savefig(p, joinpath(output_dir, "allocation_percentage.svg"))