text:
module Yeppp

# @unix_only    const libyeppp = "libyeppp"
# @windows_only const libyeppp = "yeppp"

using Libdl # BinDeps injects a dependency on Libdl

if isfile(joinpath(dirname(@__FILE__),"..","deps","deps.jl"))
    include("../deps/deps.jl")
else
    error("Yeppp not properly installed. Please run Pkg.build(\"Yeppp\")")
end

function __init__()
    status = ccall( (:yepLibrary_Init, libyeppp), Cint, ())
    status != 0 && error("yepLibrary_Init: error: ", status)
end

function release()
    status = ccall( (:yepLibrary_Release, libyeppp), Cint, ())
    status != 0 && error("yepLibrary_Release: error: ", status)
end

_iscontiguous(A::DenseArray) = true
# Fallback
_iscontiguous(A::StridedArray) = false
# Cases we can verify
_iscontiguous(A::SubArray) = Base.iscontiguous(A)
_iscontiguous(A::StridedVector) = stride(A,1) == 1

macro yepppfunsAA_A(fname, libname, BT)
    errorname = libname * ": error: "
    quote
        global $(fname)
        function $(fname)(res::Union{DenseArray{$(BT)},StridedArray{$(BT)}}, x::Union{DenseArray{$(BT)},StridedArray{$(BT)}}, y::Union{DenseArray{$(BT)},StridedArray{$(BT)}})
            n = length(x)
            @assert(n == length(y) == length(res))
            @assert !Base.has_offset_axes(res, x, y)
            @assert _iscontiguous(res) && _iscontiguous(x) && _iscontiguous(y) "could not verify that arguments are contigous is memory"

            status = ccall( ($(libname), libyeppp), Cint, (Ptr{$(BT)}, Ptr{$(BT)}, Ptr{$(BT)}, Culong), x, y, res, n)
            status != 0 && error($(errorname), status)
            res
        end
    end
end

macro yepppfunsA_A(fname, libname, BT)
    errorname = libname * ": error: "
    quote
        global $(fname)
        function $(fname)(res::Union{DenseArray{$(BT)},StridedArray{$(BT)}}, x::Union{DenseArray{$(BT)},StridedArray{$(BT)}})
            n = length(x)
            @assert(n == length(res))
            @assert !Base.has_offset_axes(res, x)
            @assert _iscontiguous(res) && _iscontiguous(x) "could not verify that arguments are contigous is memory"

            status = ccall( ($(libname), libyeppp), Cint, (Ptr{$(BT)}, Ptr{$(BT)}, Culong), x, res, n)
            status != 0 && error($(errorname), status)
            res
        end
    end
end

macro yepppfunsA_S(fname, libname, BT)
    errorname = libname * ": error: "
    quote
        global $(fname)
        function $(fname)(x::Union{DenseArray{$(BT)},StridedArray{$(BT)}})
            n = length(x)
            @assert !Base.has_offset_axes(x)
            @assert _iscontiguous(x) "could not verify that arguments are contigous is memory"
            res = Ref{$BT}()
            status = ccall( ($(libname), libyeppp), Cint, (Ptr{$(BT)}, Ptr{$(BT)}, Culong), x, res, n)
            status != 0 && error($(errorname), status)
            res[]
        end
    end
end

## == Float64 == ##
@yepppfunsAA_A add! "yepCore_Add_V64fV64f_V64f" Float64
@yepppfunsAA_A multiply! "yepCore_Multiply_V64fV64f_V64f" Float64
@yepppfunsAA_A subtract! "yepCore_Subtract_V64fV64f_V64f" Float64
@yepppfunsAA_A max! "yepCore_Max_V64fV64f_V64f" Float64
@yepppfunsAA_A min! "yepCore_Min_V64fV64f_V64f" Float64

@yepppfunsA_A log! "yepMath_Log_V64f_V64f" Float64
@yepppfunsA_A exp! "yepMath_Exp_V64f_V64f" Float64
@yepppfunsA_A sin! "yepMath_Sin_V64f_V64f" Float64
@yepppfunsA_A cos! "yepMath_Cos_V64f_V64f" Float64
@yepppfunsA_A tan! "yepMath_Tan_V64f_V64f" Float64

@yepppfunsA_S sum "yepCore_Sum_V64f_S64f" Float64
@yepppfunsA_S sumabs "yepCore_SumAbs_V64f_S64f" Float64
@yepppfunsA_S sumabs2 "yepCore_SumSquares_V64f_S64f" Float64

## == Float32 == ##
@yepppfunsAA_A add! "yepCore_Add_V32fV32f_V32f" Float32
@yepppfunsAA_A multiply! "yepCore_Multiply_V32fV32f_V32f" Float32
@yepppfunsAA_A subtract! "yepCore_Subtract_V32fV32f_V32f" Float32
@yepppfunsAA_A max! "yepCore_Max_V32fV32f_V32f" Float32
@yepppfunsAA_A min! "yepCore_Min_V32fV32f_V32f" Float32

@yepppfunsA_S sum "yepCore_Sum_V32f_S32f" Float32
@yepppfunsA_S sumabs "yepCore_SumAbs_V32f_S32f" Float32
@yepppfunsA_S sumabs2 "yepCore_SumSquares_V32f_S32f" Float32

"""
    dot(x::Union{DenseVector{Float64},StridedVector{Float64}}, y::Union{DenseVector{Float64},StridedVector{Float64}})

Compute the dot product of x and y.
"""
function dot(x::Union{DenseVector{Float64},StridedVector{Float64}}, y::Union{DenseVector{Float64},StridedVector{Float64}})
    n = length(x)
    @assert(n == length(y))
    @assert !Base.has_offset_axes(x, y)
    @assert _iscontiguous(x) && _iscontiguous(y)

    dotproduct = Ref{Float64}()
    status = ccall( (:yepCore_DotProduct_V64fV64f_S64f, libyeppp), Cint, (Ptr{Float64}, Ptr{Float64}, Ptr{Float64}, Culong), x, y, dotproduct, n)
    status != 0 && error("yepCore_DotProduct_V64fV64f_S64f: error: ", status)
    dotproduct[]
end

"""
    dot(x::Union{DenseVector{Float32},StridedVector{Float32}}, y::Union{DenseVector{Float32},StridedVector{Float32}})

Compute the dot product of x and y.
"""
function dot(x::Union{DenseVector{Float32},StridedVector{Float32}}, y::Union{DenseVector{Float32},StridedVector{Float32}})
    n = length(x)
    @assert(n == length(y))
    @assert !Base.has_offset_axes(x, y)
    @assert _iscontiguous(x) && _iscontiguous(y)

    dotproduct = Ref{Float32}()
    status = ccall( (:yepCore_DotProduct_V32fV32f_S32f, libyeppp), Cint, (Ptr{Float32}, Ptr{Float32}, Ptr{Float32}, Culong), x, y, dotproduct, n)
    status != 0 && error("yepCore_DotProduct_V32fV32f_S32f: error: ", status)
    dotproduct[]
end

"""
    max(x, y)

Compares the vectors `x` and `y` and return the element wise maxima.
"""
max(x, y) = max!(similar(x), x, y)

"""
    max(x, y)

Compares the vectors `x` and `y` and return the element wise minima.
"""
min(x, y) = min!(similar(x), x, y)

function evalpoly!(res::Union{DenseArray{Float64},StridedVector{Float64}}, coef::Union{DenseArray{Float64},StridedVector{Float64}}, x::Union{DenseArray{Float64},StridedVector{Float64}})
    n = length(coef)
    arraysize = length(x)
    @assert(arraysize == length(res))
    @assert !Base.has_offset_axes(res, coef, x)
    @assert _iscontiguous(res) && _iscontiguous(coef) && _iscontiguous(x)

    status = ccall( (:yepMath_EvaluatePolynomial_V64fV64f_V64f, libyeppp), Cint, (Ptr{Float64}, Ptr{Float64}, Ptr{Float64}, Culong, Culong), coef, x, res, n, arraysize)
    status != 0 && error("yepMath_EvaluatePolynomial_V64fV64f_V64f: error: ", status)
    res
end

"""
    evalpoly(coeff, x)

Evaluates polynomial with double precision (64-bit) floating-point coefficients `coeff` on an array of double precision (64-bit) floating-point elements `x`.
"""
evalpoly(coef, x) = evalpoly!(similar(x), coef, x)

"""
    add(x, y)

Perform element wise addition of the two array `x` and `y`.
"""
add(x, y) = add!(similar(x), x, y)

"""
    subtract(x, y)

Perform element wise subtraction of the two array `x` and `y`.
"""
subtract(x, y) = subtract!(similar(x), x, y)

"""
    multiply(x, y)

Perform element wise multiplication of the two array `x` and `y`.
"""
multiply(x, y) = multiply!(similar(x), x, y)

"""
    log(x)

Returns the element wise natural logarithm of `x`.
"""
log(x) = log!(similar(x), x)

"""
    log!(x)

Computes the element wise natural logarithm of `x` inplace.
"""
log!(x) = log!(x, x)

"""
    exp(x)

Returns the element wise exponential of `x`.
"""
exp(x) = exp!(similar(x), x)

"""
    exp(x)

Computes the element wise exponential of `x` inplace.
"""
exp!(x) = exp!(x, x)

"""
    sin(x)

Returns element wise `sin` of `x`.
"""
sin(x) = sin!(similar(x), x)

"""
    sin(x)

Computes element wise `sin` of `x` inplace.
"""
sin!(x) = sin!(x, x)

"""
    cos(x)

Returns element wise `cos` of `x`.
"""
cos(x) = cos!(similar(x), x)

"""
    cos(x)

Returns element wise `cos` of `x`.
"""
cos!(x) = cos!(x, x)

"""
    tan(x)

Returns element wise `tan` of `x`.
"""
tan(x) = tan!(similar(x), x)

"""
    tan(x)

Returns element wise `tan` of `x`.
"""
tan!(x) = tan!(x, x)
end # module


text:
##################################################
## Classical derivative-based, iterative, root-finding algorithms
##
## If ri = f^(i-1)/f^(i), then these have an update step `xn - delta` where:
##
## * Newton: delta = r1  # order 2 if simple root (multiplicity 1)
## * Halley: delta = 2*r2/(2r2 - r1) * r1 # order 3 if simple root
## * Schroder: delta = r2  / (r2 - r1) * r1  # order 2
## * Thukral(3): delta =  (-2*r3)*(r2 - r1)/(r1^2 - 3*r1*r3 + 2*r2*r3) * r1 # order 3
## * Thukral(4): delta =  3*r1*r2*r4*(r1^2 - 3*r1*r3 + 2*r2*r3)/(-r1^3*r2 + 4*r1^2*r2*r4 + 3*r1^2*r3*r4 - 12*r1*r2*r3*r4 + 6*r2^2*r3*r4) # order 4
##
## The latter two come from
## [Thukral](http://article.sapub.org/10.5923.j.ajcam.20170702.05.html). They are not implemented.

## Newton
abstract type AbstractNewtonLikeMethod <: AbstractUnivariateZeroMethod end
struct Newton <: AbstractNewtonLikeMethod end
"""

    Roots.Newton()

Implements Newton's [method](http://tinyurl.com/b4d7vls):
`xᵢ₊₁ =  xᵢ - f(xᵢ)/f'(xᵢ)`.  This is a quadratically convergent method requiring
one derivative. Two function calls per step.

Example
```
find_zero((sin,cos), 3.0, Roots.Newton())
```

If function evaluations are expensive one can pass in a function which returns (f, f/f') as follows

```
find_zero(x -> (sin(x), sin(x)/cos(x)), 3.0, Roots.Newton())
```

This can be advantageous if the derivative is easily computed from the
value of f, but otherwise would be expensive to compute.

The error, `eᵢ = xᵢ - α`, can be expressed as `eᵢ₊₁ = f[xᵢ,xᵢ,α]/(2f[xᵢ,xᵢ])eᵢ²` (Sidi, Unified treatment of regula falsi, Newton-Raphson, secant, and Steffensen methods for nonlinear equations).

"""
Newton

function init_state(method::AbstractNewtonLikeMethod, fs, x)

    x1 = float(x)
    T = eltype(x1)
    tmp = fΔx(fs, x1)
    fx1, Δ::T = tmp[1], tmp[2] # faster to pass in fx, fx/f'(x) than (fx, f'(x)) and compute

    fnevals = 1
    S = eltype(fx1)

    zT, zS = oneunit(x1) * (0*x1)/(0*x1), oneunit(fx1) * (0*fx1)/(0*fx1)
    state = UnivariateZeroState(x1, zT, zT/zT*oneunit(x1), [Δ],
                                fx1, zS, zS, S[],
                                0, fnevals,
                                false, false, false, false,
                                "")
    state
end

function init_state!(state::UnivariateZeroState{T,S}, M::Newton, fs, x) where {T,S}
    x1::T = float(x)
    tmp = fΔx(fs, x)
    fx1::S, Δ::T = tmp[1], tmp[2]

     init_state!(state, x1, oneunit(x1) * (0*x1)/(0*x1), [Δ],
                fx1, oneunit(fx1) * (0*fx1)/(0*fx1), S[])
end

function update_state(method::Newton, fs, o::UnivariateZeroState{T,S}, options) where {T, S}
    xn = o.xn1
    fxn = o.fxn1
    r1 = o.m[1]

    if isissue(r1)
        o.stopped=true
        return
    end

    xn1 = xn - r1

    tmp = fΔx(fs, xn1)
    fxn1::S, r1::T = tmp[1], tmp[2]
    incfn(o,2)

    o.xn0, o.xn1 = xn, xn1
    o.fxn0, o.fxn1 = fxn, fxn1
    empty!(o.m); push!(o.m, r1)

    nothing

end

"""
    Roots.newton(f, fp, x0; kwargs...)

Implementation of Newton's method: `x_n1 = x_n - f(x_n)/ f'(x_n)`

Arguments:

* `f::Function` -- function to find zero of

* `fp::Function` -- the derivative of `f`.

* `x0::Number` -- initial guess. For Newton's method this may be complex.

With the `FowardDiff` package derivatives may be computed automatically. For example,  defining
`D(f) = x -> ForwardDiff.derivative(f, float(x))` allows `D(f)` to be used for the first derivative.

Keyword arguments are passed to `find_zero` using the `Roots.Newton()` method.

See also `Roots.newton((f,fp), x0) and `Roots.newton(fΔf, x0)` for simpler implementations.

"""
newton(f, fp, x0; kwargs...) = find_zero((f, fp), x0, Newton(); kwargs...)

## Halley
abstract type AbstractHalleyLikeMethod <: AbstractUnivariateZeroMethod end

"""
    Roots.Halley()

Implements Halley's [method](http://tinyurl.com/yd83eytb),
`xᵢ₊₁ = xᵢ - (f/f')(xᵢ) * (1 - (f/f')(xᵢ) * (f''/f')(xᵢ) * 1/2)^(-1)`
This method is cubically converging, but requires more function calls per step (3) than
other methods.

Example
```
find_zero((sin, cos, x->-sin(x)), 3.0, Roots.Halley())
```

If function evaluations are expensive one can pass in a function which
returns (f, f/f',f'/f'') as follows

```
find_zero(x -> (sin(x), sin(x)/cos(x), -cos(x)/sin(x)), 3.0, Roots.Halley())
```

This can be advantageous if the derivatives are easily computed from
the value of f, but otherwise would be expensive to compute.

The error, `eᵢ = xᵢ - α`, satisfies
`eᵢ₊₁ ≈ -(2f'⋅f''' -3⋅(f'')²)/(12⋅(f'')²) ⋅ eᵢ³` (all evaluated at `α`).

"""
struct Halley <: AbstractHalleyLikeMethod
end

function init_state(method::AbstractHalleyLikeMethod, fs, x)

    x1 = float(x)
    T = eltype(x1)
    tmp = fΔxΔΔx(fs, x1)
    fx1, Δ::T, ΔΔ::T = tmp[1], tmp[2], tmp[3]
    S = eltype(fx1)
    fnevals = 3

    zT, zS =  oneunit(x1) * (0*x1)/(0*x1), oneunit(fx1) * (0*fx1)/(0*fx1)
    state = UnivariateZeroState(x1, zT, zT/zT*oneunit(x1), [Δ,ΔΔ],
                                fx1, zS, zS, S[],
                                0, fnevals,
                                false, false, false, false,
                                "")
    state
end

function init_state!(state::UnivariateZeroState{T,S}, M::AbstractHalleyLikeMethod, fs, x) where {T,S}
    x1::T = float(x)
    tmp = fΔxΔΔx(fs, x)
    fx1::S, Δ::T, ΔΔ::T = tmp[1], tmp[2], tmp[3]

     init_state!(state, x1, oneunit(x1) * (0*x1)/(0*x1), [Δ, ΔΔ],
                fx1, oneunit(fx1) * (0*fx1)/(0*fx1), S[])
end

function update_state(method::Halley, fs, o::UnivariateZeroState{T,S}, options::UnivariateZeroOptions) where {T,S}
    xn = o.xn1
    fxn = o.fxn1
    r1, r2 = o.m

    xn1::T = xn - 2*r2/(2r2 - r1) * r1

    tmp = fΔxΔΔx(fs, xn1)
    fxn1::S, r1::T, r2::T = tmp[1], tmp[2], tmp[3]
    incfn(o,3)

    o.xn0, o.xn1 = xn, xn1
    o.fxn0, o.fxn1 = fxn, fxn1
    empty!(o.m); append!(o.m, (r1, r2))
end

"""
    Roots.halley(f, fp, fpp, x0; kwargs...)

Implementation of Halley's method (cf `?Roots.Halley()`).

Arguments:

* `f::Function` -- function to find zero of

* `fp::Function` -- derivative of `f`.

* `fpp:Function` -- second derivative of `f`.

* `x0::Number` -- initial guess

With the `FowardDiff` package derivatives may be computed automatically. For example,  defining
`D(f) = x -> ForwardDiff.derivative(f, float(x))` allows `D(f)` and `D(D(f))` to be used for the first and second
derivatives, respectively.

Keyword arguments are passed to `find_zero` using the `Roots.Halley()` method.

"""
halley(f, fp, fpp, x0; kwargs...) = find_zero((f, fp, fpp), x0, Halley(); kwargs...)
#halley(f,  x0; kwargs...) = find_zero(f, x0, Halley(); kwargs...) # deprecated
#halley(f, fp, x0; kwargs...) = find_zero((f, fp), x0, Halley(); kwargs...) # deprecated
@deprecate halley(f,  x0; kwargs...)    halley(f, fp, fpp, x0; kwargs...)
@deprecate halley(f, fp, x0; kwargs...) halley(f, fp, fpp, x0; kwargs...)

## Shroder-like methods

"""
    Roots.Schroder()

Schröder's method, like Halley's method, utilizes `f`, `f'`, and
`f''`. Unlike Halley it is quadratically converging, but this is
independent of the multiplicity of the zero (cf. Schröder, E. "Über
unendlich viele Algorithmen zur Auflösung der Gleichungen."
Math. Ann. 2, 317-365, 1870;
[mathworld](http://mathworld.wolfram.com/SchroedersMethod.html)). Schröder's
method applies Newton's method to `f/f'`, a function with all
simple zeros.

Example
```
m = 2
f(x) = (cos(x)-x)^m
fp(x) = (-x + cos(x))*(-2*sin(x) - 2)
fpp(x) = 2*((x - cos(x))*cos(x) + (sin(x) + 1)^2)
find_zero((f, fp, fpp), pi/4, Roots.Halley())    # 14 steps
find_zero((f, fp, fpp), 1.0, Roots.Schroder())    # 3 steps
```

(Whereas, when `m=1`, Halley is 2 steps to Schröder's 3.)

If function evaluations are expensive one can pass in a function which
returns `(f, f/f',f'/f'')` as follows

```
find_zero(x -> (sin(x), sin(x)/cos(x), -cos(x)/sin(x)), 3.0, Roots.Schroder())
```

This can be advantageous if the derivatives are easily computed from
the value of `f`, but otherwise would be expensive to compute.

The error, `eᵢ = xᵢ - α`, is the same as `Newton` with `f` replaced by `f/f'`.

"""
struct Schroder <: AbstractHalleyLikeMethod
end
const Schroeder = Schroder # either spelling
const Schröder = Schroder

function update_state(method::Schroder, fs, o::UnivariateZeroState{T,S}, options::UnivariateZeroOptions) where {T,S}
    xn = o.xn1
    fxn = o.fxn1
    r1, r2 = o.m[1], o.m[2]

    delta =  r2 / (r2 - r1) * r1  # m * r1

    if isissue(delta)
        o.stopped=true
        return
    end

    xn1::T = xn - delta

    tmp = fΔxΔΔx(fs, xn1)
    fxn1::S, r1::T, r2::T = tmp[1], tmp[2], tmp[3]
    incfn(o,3)

    o.xn0, o.xn1 = xn, xn1
    o.fxn0, o.fxn1 = fxn, fxn1
    o.m[1], o.m[2] = r1, r2
end

ext: jl


text:
"""
Running scenarios one after the other.
"""

using DataFrames, CSV, FileIO, Dates
using BenchmarkTools
using Agtor

import Agtor: run_model
import OrderedCollections: LittleDict

# Start month/day of growing season
# Represents when water allocations are announced.
const gs_start = (5, 1)

function setup_zone(data_dir::String="test/data/")
    zone_spec_dir::String = "$(data_dir)zones"
    zone_params::Dict{Symbol, Dict} = load_spec(zone_spec_dir)

    return create(zone_params[:Zone_3])
end

"""
Allocation callback for example.
"""
function allocation_callback!(zone, dt_i)

    # Resetting allocations for example run
    if monthday(dt_i) == gs_start
        # Example annual water allocations
        allocs = (surface_water=150.0, groundwater=40.0)

        update_available_water!(zone, allocs)
    end
end

"""
Run scenarios in sequence, and save results as they complete.

Compared to storing all results and saving in one go, this approach
is slower overall, but does not use as much memory.
"""
function example_scenario_run(data_dir::String="test/data/")::Nothing
    z1 = setup_zone(data_dir)
    z1.manager = BaseManager("test")

    scen_data = joinpath(data_dir, "scenarios", "sampled_params.csv")
    samples = DataFrame!(CSV.File(scen_data))

    @sync for (row_id, r) in enumerate(eachrow(samples))
        tmp_z = deepcopy(z1)
        update_model!(tmp_z, r)

        results = run_model(tmp_z, run_timestep!; post=allocation_callback!)

        # Save results as they complete
        @async save_results!("async_save.jld2", row_id, results)
    end
end

"""
Same example as above, but collect all results before saving.

Should be faster than the first example (for the small number of scenarios run) as all
results are kept in memory until the end.
"""
function example_batch_save(data_dir::String="test/data/")::Nothing
    z1 = setup_zone(data_dir)
    z1.manager = BaseManager("test")

    scen_data = joinpath(data_dir, "scenarios", "sampled_params.csv")
    samples = DataFrame!(CSV.File(scen_data))

    all_results = Dict()
    for (row_id, r) in enumerate(eachrow(samples))
        tmp_z = deepcopy(z1)
        update_model!(tmp_z, r)

        all_results[row_id] = run_model(tmp_z, run_timestep!; post=allocation_callback!)
    end

    save_results!("batch_run.jld2", all_results)
end

# @btime example_scenario_run()
# @btime example_batch_save()

example_batch_save()

# Loading and displaying results
saved_results = load("batch_run.jld2")
# @info saved_results

# @info saved_results["1/zone_results"]

# df = collate_results("batch_run.jld2", "zone_results", "irrigated_yield_sum")
df = collate_results("batch_run.jld2", "zone_results", "dryland_yield_sum")

using Gadfly

col_names = map(Symbol, names(df))
# p = plot(df, x=Row, y=Col.value(col_names...), color=Col.index(col_names...), Geom.line)
p = plot(df, y=Col.value(col_names...), color=Col.index(col_names...), Geom.line)

max_stars_repo_name: ConnectedSystems/agtor-jl

max_issues_repo_name: ConnectedSystems/agtor-jl

max_forks_repo_name: ConnectedSystems/agtor-jl


text:
export tox_new
export tox_kill
export tox_bootstrap
export tox_self_get_address
export tox_self_set_name
export tox_self_set_status_message
export tox_iteration_interval
export tox_iterate
export tox_friend_add_norequest
export tox_friend_send_message
export tox_friend_add
export tox_get_savedata
export tox_options_default
export tox_self_get_friend_list
export tox_friend_get_name

export tox_file_send
export tox_file_send_chunk

export tox_callback_file_chunk_request
export tox_callback_file_recv
export tox_callback_file_recv_chunk
export tox_callback_file_recv_control

export tox_callback_friend_request
export tox_callback_friend_message

export ToxPublicKey
export ToxAddress
export ToxSavedata
export Tox_Options

###################### ToxPublicKey #####################################

immutable ToxPublicKey
	key::Vector{Uint8}
	function ToxPublicKey(public_key::Vector{Uint8})
		(length(public_key) != TOX_PUBLIC_KEY_SIZE) && throw(error("Tox API Invalid Public Key."))
		new(public_key)
	end
end
ToxPublicKey() = ToxPublicKey(zeros(Uint8, TOX_PUBLIC_KEY_SIZE))
Base.convert(::Type{ASCIIString}, public_key::ToxPublicKey) = uppercase(bytes2hex(public_key))
Base.convert(::Type{ToxPublicKey}, key::ASCIIString) = ToxPublicKey(hex2bytes(uppercase(key)))
Base.convert(::Type{ToxPublicKey}, ptr::Ptr{UInt8})  = ToxPublicKey(pointer_to_array(ptr, TOX_PUBLIC_KEY_SIZE))

###################### ToxAddress #####################################

immutable ToxAddress
	ptr::Ptr{Uint8}
end

function ToxAddress()
    ToxAddress(pointer(zeros(Uint8,TOX_ADDRESS_SIZE)))
end

function ToxAddress(address::ASCIIString)
	Base.convert(ToxAddress, address)
end

function Base.convert(::Type{ASCIIString}, address::ToxAddress)
	uppercase(bytes2hex(pointer_to_array(address.ptr, TOX_ADDRESS_SIZE)))
end

function Base.convert(::Type{ToxAddress}, string::ASCIIString)
	address = hex2bytes(uppercase(string))
	(length(address) != TOX_ADDRESS_SIZE) && throw(error("Tox API Invalid Address."))

	return ToxAddress(pointer(address))
end

###################### ToxOptions #####################################

###################### Error callback #################################

function tox_error_callback(tox::Ptr{Tox}, tox_function::ASCIIString, code::Uint32)
	println("Error: " + tox_function + "exited with code " + code)
end

#############################################################################################
# First API. Functions have the same name as in Toxcore, but take other arguments			#
#############################################################################################

#
# tox_options_default
#
function tox_options_default()
	options = Tox_Options()
	options = Ref(options)
	CInterface.tox_options_default(options)
	options.x
end

#
# tox_new
#
function tox_new()
	return CInterface.tox_new(C_NULL, C_NULL)
end

function tox_new(options::Tox_Options)
	return CInterface.tox_new(Ref(options), C_NULL)
end

#
# tox_kill
#
function tox_kill(tox::Ptr{Tox})
	return CInterface.tox_kill(tox)
end

#
# tox_get_savedata
#
function tox_get_savedata(tox::Ptr{Tox})
	savedata = zeros(Uint8, CInterface.tox_get_savedata_size(tox))

	CInterface.tox_get_savedata(tox, savedata)
	return savedata
end

#
# tox_bootstrap
#
include("bootstrap.jl")
function tox_bootstrap(tox::Ptr{Tox})
	# taken from utox
	i = rand(1:(length(bootstrap_nodes)-4))
	for (url, port, key) in bootstrap_nodes[i:i+3]
		tox_bootstrap(tox, url, port, key)
    	tox_add_tcp_relay(tox, url, port, key)
	end
	true
end

function tox_bootstrap(tox::Ptr{Tox}, host::ASCIIString, port, public_key::ToxPublicKey)
	CInterface.tox_bootstrap(tox, host, port, public_key.key, C_NULL)
end
function tox_add_tcp_relay(tox::Ptr{Tox}, host::ASCIIString, port, public_key::ToxPublicKey)
	CInterface.tox_add_tcp_relay(tox, host, port, public_key.key, C_NULL)
end
#
# tox_self_get_address
#
function tox_self_get_address(tox::Ptr{Tox})
	address = ToxAddress()
    CInterface.tox_self_get_address(tox, address.ptr)
    address
end

#
# tox_self_set_name
#
function tox_self_set_name(tox::Ptr{Tox}, name::ASCIIString)
	(length(name) > TOX_MAX_NAME_LENGTH) && (name = name[1:TOX_MAX_NAME_LENGTH])

	CInterface.tox_self_set_name(tox, name, length(name), C_NULL)
end

#
# tox_self_get_friend_list
#
function tox_self_get_friend_list(tox::Ptr{Tox})
	friendlist = zeros(Uint32, CInterface.tox_self_get_friend_list_size(tox))

	CInterface.tox_self_get_friend_list(tox, friendlist)
	friendlist
end

#
# tox_friend_add
#

function tox_friend_add(tox::Ptr{Tox}, address::ToxAddress, message::UTF8String)
end

function tox_friend_add(tox::Ptr{Tox}, address::ToxAddress, message::Ptr{Uint8}, length::Integer)

end

#
# tox_friend_get_name
#
# returns "" if the friend does not exist. utf8 encoding expected
#
function tox_friend_get_name(tox::Ptr{Tox}, friend_number::Integer)
	name_size = CInterface.tox_friend_get_name_size(tox, friend_number, C_NULL)

	if name_size > TOX_MAX_NAME_LENGTH	# this happens if the friend does not exist
		return ""
	end

	name = zeros(Uint8, name_size)
	CInterface.tox_friend_get_name(tox, friend_number, name, C_NULL)

	return utf8(name)
end

#
# tox_self_set_status_message
#
function tox_self_set_status_message(tox::Ptr{Tox}, message::ASCIIString)
	(length(message) > TOX_MAX_STATUS_MESSAGE_LENGTH) && (message = message[1:TOX_MAX_STATUS_MESSAGE_LENGTH])

	CInterface.tox_self_set_status_message(tox, message, length(message), C_NULL)
end

#
# tox_friend_send_message
#
# split a message into several messages if it is to long
#
function tox_friend_send_message(tox::Ptr{Tox}, friend_number::Uint32, message::UTF8String)

	messages = tox_split_message(message)

	for message in messages
		tox_friend_send_message(tox, friend_number, TOX_MESSAGE_TYPE_NORMAL, pointer(message.data), length(message.data))
	end
end

function tox_friend_send_message(tox::Ptr{Tox}, friend_number::Uint32, _type::TOX_MESSAGE_TYPE, message::Ptr{Uint8}, length::Integer)

	(length > TOX_MAX_MESSAGE_LENGTH) && throw(error("Tox API Message to long!"))

	CInterface.tox_friend_send_message(tox, friend_number, _type, message, length, C_NULL)
end

#
# tox_friend_add_norequest
#
function tox_friend_add_norequest(tox::Ptr{Tox}, public_key::ToxPublicKey)
	CInterface.tox_friend_add_norequest(tox, public_key.key, C_NULL)
end

#
# tox_iterate
#
function tox_iterate(tox::Ptr{Tox})
	CInterface.tox_iterate(tox)
	nothing
end

#
# tox_iteration_interval
#
function tox_iteration_interval(tox::Ptr{Tox})
	CInterface.tox_iteration_interval(tox)
end

#bool 	tox_file_control (Tox *tox, uint32_t friend_number, uint32_t file_number, TOX_FILE_CONTROL control, TOX_ERR_FILE_CONTROL *error)

#void 	tox_callback_file_recv_control (Tox *tox, tox_file_recv_control_cb *function, void *user_data)

#bool 	tox_file_seek (Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, TOX_ERR_FILE_SEEK *error)

#bool 	tox_file_get_file_id (const Tox *tox, uint32_t friend_number, uint32_t file_number, uint8_t *file_id, TOX_ERR_FILE_GET *error)

#
# tox_file_send
#

function tox_file_send(tox::Ptr{Tox}, friend_number::Uint32, kind::TOX_FILE_KIND, file_size::Integer, filename::UTF8String)
	(length(filename.data) > TOX_MAX_FILENAME_LENGTH) && throw(error("Tox API Filename to long!"))

	return CInterface.tox_file_send(tox, friend_number, kind, file_size, C_NULL, pointer(filename.data), length(filename.data), C_NULL)
end

#
# tox_file_send
#
function tox_file_send_chunk(tox::Ptr{Tox}, friend_number::Uint32, file_number::Uint32, position::Integer, data::Vector{Uint8})

	return CInterface.tox_file_send_chunk(tox, friend_number, file_number, position, data, length(data), C_NULL)
end

###################### Low-Level Callback API #####################################

const tox_file_chunk_request_cb_args       = (Ptr{Tox}, Uint32, Uint32, Uint64, Csize_t, Ptr{Void})
const tox_file_recv_cb_args                = (Ptr{Tox}, Uint32, Uint32, TOX_FILE_KIND, Uint64, Ptr{Uint8}, Csize_t, Ptr{Void})
const tox_file_recv_chunk_cb_args          = (Ptr{Tox}, Uint32, Uint32, Uint64, Ptr{Uint8}, Csize_t, Ptr{Void})
const tox_file_recv_control_cb_args        = (Ptr{Tox}, Uint32, Uint32, TOX_FILE_CONTROL, Ptr{Void})

const tox_friend_connection_status_cb_args = (Ptr{Tox}, Uint32, TOX_CONNECTION, Ptr{Void})
const tox_friend_lossless_packet_cb_args   = (Ptr{Tox}, Uint32, Ptr{Uint8}, Csize_t, Ptr{Void})
const tox_friend_lossy_packet_cb_args      = (Ptr{Tox}, Uint32, Ptr{Uint8}, Csize_t, Ptr{Void})
const tox_friend_message_cb_args           = (Ptr{Tox}, Uint32, TOX_MESSAGE_TYPE, Ptr{Uint8}, Csize_t, Ptr{Void})
const tox_friend_name_cb_args              = (Ptr{Tox}, Uint32, Ptr{Uint8}, Csize_t, Ptr{Void})
const tox_friend_read_receipt_cb_args      = (Ptr{Tox}, Uint32, Uint32, Ptr{Void})
const tox_friend_request_cb_args           = (Ptr{Tox}, Ptr{Uint8}, Ptr{Uint8}, Csize_t, Ptr{Void})
const tox_friend_status_cb_args            = (Ptr{Tox}, Uint32, TOX_USER_STATUS, Ptr{Void})
const tox_friend_status_message_cb_args    = (Ptr{Tox}, Uint32, Ptr{Uint8}, Csize_t, Ptr{Void})
const tox_friend_typing_cb_args            = (Ptr{Tox}, Uint32, Bool, Ptr{Void})

const tox_self_connection_status_cb_args   = (Ptr{Tox}, TOX_CONNECTION, Ptr{Void})

#
# tox_callback_file_chunk_request
#
function tox_callback_file_chunk_request(tox::Ptr{Tox}, fun::Function, user_data::Ptr{Void})
	!method_exists(fun, tox_file_chunk_request_cb_args) && throw(error("Tox API Callback Error"))

	CInterface.tox_callback_file_chunk_request(tox, cfunction(fun, Void, tox_file_chunk_request_cb_args), user_data)
	nothing
end

#
# tox_callback_file_recv
#
function tox_callback_file_recv(tox::Ptr{Tox}, fun::Function, user_data::Ptr{Void})
	!method_exists(fun, tox_file_recv_cb_args) && throw(error("Tox API Callback Error"))

	CInterface.tox_callback_file_recv(tox, cfunction(fun, Void, tox_file_recv_cb_args), user_data)
	nothing
end

#
# tox_callback_file_recv_chunk
#
function tox_callback_file_recv_chunk(tox::Ptr{Tox}, fun::Function, user_data::Ptr{Void})
	!method_exists(fun, tox_file_recv_chunk_cb_args) && throw(error("Tox API Callback Error"))

	CInterface.tox_callback_file_recv_chunk(tox, cfunction(fun, Void, tox_file_recv_chunk_cb_args), user_data)
	nothing
end

#
# tox_callback_file_recv_control
#
function tox_callback_file_recv_control(tox::Ptr{Tox}, fun::Function, user_data::Ptr{Void})
	!method_exists(fun, tox_file_recv_control_cb_args) && throw(error("Tox API Callback Error"))

	CInterface.tox_callback_file_recv_control(tox, cfunction(fun, Void, tox_file_recv_control_cb_args), user_data)
	nothing
end

#
# tox_callback_friend_connection_status
#
function tox_callback_friend_connection_status(tox::Ptr{Tox}, fun::Function, user_data::Ptr{Void})
	!method_exists(fun, tox_friend_connection_status_cb_args) && throw(error("Tox API Callback Error"))

	CInterface.tox_callback_friend_connection_status(tox, cfunction(fun, Void, tox_friend_connection_status_cb_args), user_data)
	nothing
end

#
# tox_callback_friend_request
#
function tox_callback_friend_request(tox::Ptr{Tox}, fun::Function, user_data::Ptr{Void})
	!method_exists(fun, tox_friend_request_cb_args) && throw(error("Tox API Callback Error"))

	CInterface.tox_callback_friend_request(tox, cfunction(fun, Void, tox_friend_request_cb_args), user_data)
	nothing
end

#
# tox_callback_friend_message
#
function tox_callback_friend_message(tox::Ptr{Tox}, fun::Function, user_data::Ptr{Void})
	!method_exists(fun, tox_friend_message_cb_args) && throw(error("Tox API Callback Error"))

	CInterface.tox_callback_friend_message(tox, cfunction(fun, Void, tox_friend_message_cb_args), user_data)
	nothing
end

#void 	tox_callback_file_chunk_request (Tox *tox, tox_file_chunk_request_cb *function, void *user_data)

#void 	tox_callback_file_recv (Tox *tox, tox_file_recv_cb *function, void *user_data)

#void 	tox_callback_file_recv_chunk (Tox *tox, tox_file_recv_chunk_cb *function, void *user_data)#


text:
using Documenter
using Swizzles

makedocs(
    sitename="Swizzles.jl",
    modules = [Swizzles],
    authors = "Peter Ahrens",
    pages = [
        "Home" => "index.md"
    ]
)

# Documenter can also automatically deploy documentation to gh-pages.
# See "Hosting Documentation" and deploydocs() in the Documenter manual
# for more information.
deploydocs(
    repo = "https://github.com/peterahrens/Swizzles.jl"
)


text:
struct PredatorPreyHexWorldMG
    hexes::Vector{Tuple{Int, Int}}
    hexWorldDiscreteMDP::DiscreteMDP
end

n_agents(mg::PredatorPreyHexWorldMG) = 2

ordered_states(mg::PredatorPreyHexWorldMG, i::Int) = vec(collect(1:length(mg.hexes)))
ordered_states(mg::PredatorPreyHexWorldMG) = vec(collect(Iterators.product([ordered_states(mg, i) for i in 1:n_agents(mg)]...)))

ordered_actions(mg::PredatorPreyHexWorldMG, i::Int) = vec(collect(1:n_actions(mg.hexWorldDiscreteMDP)))
ordered_joint_actions(mg::PredatorPreyHexWorldMG) = vec(collect(Iterators.product([ordered_actions(mg, i) for i in 1:n_agents(mg)]...)))

n_actions(mg::PredatorPreyHexWorldMG, i::Int) = length(ordered_actions(mg, i))
n_joint_actions(mg::PredatorPreyHexWorldMG) = length(ordered_joint_actions(mg))

function transition(mg::PredatorPreyHexWorldMG, s, a, s′)
    # When a prey is caught (new prey born), it teleports to a random location and the predator remains (eating).
    # Otherwise, both transition following HexWorldMDP.
    if s[1] == s[2]
        prob = Float64(s′[1] == s[1]) / length(mg.hexes)
    else
        prob = mg.hexWorldDiscreteMDP.T[s[1], a[1], s′[1]] * mg.hexWorldDiscreteMDP.T[s[2], a[2], s′[2]]
    end
    return prob
end

function reward(mg::PredatorPreyHexWorldMG, i::Int, s, a)
    r = 0.0

    if i == 1
        # Predators get -1 for moving and 10 for catching the prey.
        if s[1] == s[2]
            return 10.0
        else
            return -1.0
        end
    elseif i == 2
        # Prey get -1 for moving and -100 for being caught.
        if s[1] == s[2]
            r = -100.0
        else
            r = -1.0
        end
    end

    return r
end

function joint_reward(mg::PredatorPreyHexWorldMG, s, a)
    return [reward(mg, i, s, a) for i in 1:n_agents(mg)]
end

function MG(mg::PredatorPreyHexWorldMG)
    return MG(
        mg.hexWorldDiscreteMDP.γ,
        vec(collect(1:n_agents(mg))),
        ordered_states(mg),
        [ordered_actions(mg, i) for i in 1:n_agents(mg)],
        (s, a, s′) -> transition(mg, s, a, s′),
        (s, a) -> joint_reward(mg, s, a)
    )
end

function PredatorPreyHexWorldMG(hexes::Vector{Tuple{Int,Int}},
                                r_bump_border::Float64,
                                p_intended::Float64,
                                γ::Float64)
    hexWorld = HexWorldMDP(hexes,
                           r_bump_border,
                           p_intended,
                           Dict{Tuple{Int64,Int64},Float64}(),
                           γ)
    mdp = hexWorld.mdp
    return PredatorPreyHexWorldMG(hexes, mdp)
end

# const CirclePredatorPreyHexWorld = PredatorPreyHexWorldMG(
#     [
#      (-1, 2), (0, 2),
#      (-1, 1), (1, 1),
#      (0, 0), (1, 0),
#      ],
#     HexWorldRBumpBorder,
#     HexWorldPIntended,
#     0.95
# )

function CirclePredatorPreyHexWorld()
    CirclePredatorPreyHexWorld = PredatorPreyHexWorldMG(
        [
         (-1, 2), (0, 2), (1, 2),
         (-1, 1), (1, 1), (3, 1), (4, 1),
         (0, 0), (1, 0), (2, 0), (3, 0), (4, 0),
         ],
        HexWorldRBumpBorder,
        HexWorldPIntended,
        0.95
    )
    return CirclePredatorPreyHexWorld
end

# const PredatorPreyHexWorld = PredatorPreyHexWorldMG(
#     [
#      (-1, 2), (0, 2), (1, 2),
#      (-1, 1), (1, 1), (3, 1), (4, 1),
#      (0, 0), (1, 0), (2, 0), (3, 0), (4, 0),
#      ],
#     HexWorldRBumpBorder,
#     HexWorldPIntended,
#     0.95
# )

function PredatorPreyHexWorld()
    PredatorPreyHexWorld = PredatorPreyHexWorldMG(
        [
         (-1, 2), (0, 2), (1, 2),
         (-1, 1), (1, 1), (3, 1), (4, 1),
         (0, 0), (1, 0), (2, 0), (3, 0), (4, 0),
         ],
        HexWorldRBumpBorder,
        HexWorldPIntended,
        0.95
    )
    return PredatorPreyHexWorld
end


text:
# Make sure that the high-level Objective-C syntax translates properly to messages

@testset "syntax tests" begin

    @testset "create a hello string" begin
        str = @objc [[NSString alloc] initWithUTF8String: "hello"]
        chars = @objc [str UTF8String]

        @test unsafe_string(chars) == "hello"
    end

    @testset "create integer" begin

        num = @objc [NSNumber numberWithInt:42]
        x = @objc [num intValue]

        @test x == 42
    end

    @testset "create integer in block of code" begin
        @objc begin
            num = [NSNumber numberWithInt:42]
            x = [num intValue]
        end
        @test x == 42
    end

    @testset "create string in block of objc code" begin
        @objc begin
            str = [[NSString alloc] initWithUTF8String: "hello"]
            chars = [str UTF8String]
        end
        @test unsafe_string(chars) == "hello"
    end

    @testset "create dictionary" begin
        dict = @objc [NSMutableDictionary dictionaryWithCapacity:6]
        @test dict != nil

        @objc [dict setObject:4 forKey:"four"]
        @objc [dict setObject:5 forKey:"five"]

        # Test that float values can later be retrieved as integers
        # to see NSNumber conversions happen properly
        @objc [dict setObject:6.0 forKey:"six"]

        four = @objc [[dict objectForKey:"four"] intValue]

        # Testing that integer 5 can be converted to floating point
        # value 5.0f0
        five = @objc [[dict objectForKey:"five"] floatValue]
        six = @objc [[dict objectForKey:"six"] intValue]

        @test four == 4
        @test five == 5.0f0
        @test six == 6
    end
end


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

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

Accepts an invitation for the member account to contribute data to a behavior graph. This
operation can only be called by an invited member account.  The request provides the ARN of
behavior graph. The member account status in the graph must be INVITED.

# Arguments
- `graph_arn`: The ARN of the behavior graph that the member account is accepting the
  invitation for. The member account status in the behavior graph must be INVITED.

"""
function accept_invitation(GraphArn; aws_config::AbstractAWSConfig=global_aws_config())
    return detective(
        "PUT",
        "/invitation",
        Dict{String,Any}("GraphArn" => GraphArn);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function accept_invitation(
    GraphArn,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return detective(
        "PUT",
        "/invitation",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("GraphArn" => GraphArn), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Creates a new behavior graph for the calling account, and sets that account as the
administrator account. This operation is called by the account that is enabling Detective.
Before you try to enable Detective, make sure that your account has been enrolled in Amazon
GuardDuty for at least 48 hours. If you do not meet this requirement, you cannot enable
Detective. If you do meet the GuardDuty prerequisite, then when you make the request to
enable Detective, it checks whether your data volume is within the Detective quota. If it
exceeds the quota, then you cannot enable Detective.  The operation also enables Detective
for the calling account in the currently selected Region. It returns the ARN of the new
behavior graph.  CreateGraph triggers a process to create the corresponding data tables for
the new behavior graph. An account can only be the administrator account for one behavior
graph within a Region. If the same account calls CreateGraph with the same administrator
account, it always returns the same behavior graph ARN. It does not create a new behavior
graph.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"Tags"`: The tags to assign to the new behavior graph. You can add up to 50 tags. For
  each tag, you provide the tag key and the tag value. Each tag key can contain up to 128
  characters. Each tag value can contain up to 256 characters.
"""
function create_graph(; aws_config::AbstractAWSConfig=global_aws_config())
    return detective(
        "POST", "/graph"; aws_config=aws_config, feature_set=SERVICE_FEATURE_SET
    )
end
function create_graph(
    params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()
)
    return detective(
        "POST", "/graph", params; aws_config=aws_config, feature_set=SERVICE_FEATURE_SET
    )
end

"""
    create_members(accounts, graph_arn)
    create_members(accounts, graph_arn, params::Dict{String,<:Any})

 CreateMembers is used to send invitations to accounts. For the organization behavior
graph, the Detective administrator account uses CreateMembers to enable organization
accounts as member accounts. For invited accounts, CreateMembers sends a request to invite
the specified Amazon Web Services accounts to be member accounts in the behavior graph.
This operation can only be called by the administrator account for a behavior graph.
CreateMembers verifies the accounts and then invites the verified accounts. The
administrator can optionally specify to not send invitation emails to the member accounts.
This would be used when the administrator manages their member accounts centrally. For
organization accounts in the organization behavior graph, CreateMembers attempts to enable
the accounts. The organization accounts do not receive invitations. The request provides
the behavior graph ARN and the list of accounts to invite or to enable. The response
separates the requested accounts into two lists:   The accounts that CreateMembers was able
to process. For invited accounts, includes member accounts that are being verified, that
have passed verification and are to be invited, and that have failed verification. For
organization accounts in the organization behavior graph, includes accounts that can be
enabled and that cannot be enabled.   The accounts that CreateMembers was unable to
process. This list includes accounts that were already invited to be member accounts in the
behavior graph.

# Arguments
- `accounts`: The list of Amazon Web Services accounts to invite or to enable. You can
  invite or enable up to 50 accounts at a time. For each invited account, the account list
  contains the account identifier and the Amazon Web Services account root user email
  address. For organization accounts in the organization behavior graph, the email address is
  not required.
- `graph_arn`: The ARN of the behavior graph.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"DisableEmailNotification"`: if set to true, then the invited accounts do not receive
  email notifications. By default, this is set to false, and the invited accounts receive
  email notifications. Organization accounts in the organization behavior graph do not
  receive email notifications.
- `"Message"`: Customized message text to include in the invitation email message to the
  invited member accounts.
"""
function create_members(
    Accounts, GraphArn; aws_config::AbstractAWSConfig=global_aws_config()
)
    return detective(
        "POST",
        "/graph/members",
        Dict{String,Any}("Accounts" => Accounts, "GraphArn" => GraphArn);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function create_members(
    Accounts,
    GraphArn,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return detective(
        "POST",
        "/graph/members",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}("Accounts" => Accounts, "GraphArn" => GraphArn),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Disables the specified behavior graph and queues it to be deleted. This operation removes
the behavior graph from each member account's list of behavior graphs.  DeleteGraph can
only be called by the administrator account for a behavior graph.

# Arguments
- `graph_arn`: The ARN of the behavior graph to disable.

"""
function delete_graph(GraphArn; aws_config::AbstractAWSConfig=global_aws_config())
    return detective(
        "POST",
        "/graph/removal",
        Dict{String,Any}("GraphArn" => GraphArn);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function delete_graph(
    GraphArn,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return detective(
        "POST",
        "/graph/removal",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("GraphArn" => GraphArn), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    delete_members(account_ids, graph_arn)
    delete_members(account_ids, graph_arn, params::Dict{String,<:Any})

Removes the specified member accounts from the behavior graph. The removed accounts no
longer contribute data to the behavior graph. This operation can only be called by the
administrator account for the behavior graph. For invited accounts, the removed accounts
are deleted from the list of accounts in the behavior graph. To restore the account, the
administrator account must send another invitation. For organization accounts in the
organization behavior graph, the Detective administrator account can always enable the
organization account again. Organization accounts that are not enabled as member accounts
are not included in the ListMembers results for the organization behavior graph. An
administrator account cannot use DeleteMembers to remove their own account from the
behavior graph. To disable a behavior graph, the administrator account uses the DeleteGraph
API method.

# Arguments
- `account_ids`: The list of Amazon Web Services account identifiers for the member
  accounts to remove from the behavior graph. You can remove up to 50 member accounts at a
  time.
- `graph_arn`: The ARN of the behavior graph to remove members from.

"""
function delete_members(
    AccountIds, GraphArn; aws_config::AbstractAWSConfig=global_aws_config()
)
    return detective(
        "POST",
        "/graph/members/removal",
        Dict{String,Any}("AccountIds" => AccountIds, "GraphArn" => GraphArn);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function delete_members(
    AccountIds,
    GraphArn,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return detective(
        "POST",
        "/graph/members/removal",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}("AccountIds" => AccountIds, "GraphArn" => GraphArn),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Returns information about the configuration for the organization behavior graph. Currently
indicates whether to automatically enable new organization accounts as member accounts. Can
only be called by the Detective administrator account for the organization.

# Arguments
- `graph_arn`: The ARN of the organization behavior graph.

"""
function describe_organization_configuration(
    GraphArn; aws_config::AbstractAWSConfig=global_aws_config()
)
    return detective(
        "POST",
        "/orgs/describeOrganizationConfiguration",
        Dict{String,Any}("GraphArn" => GraphArn);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function describe_organization_configuration(
    GraphArn,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return detective(
        "POST",
        "/orgs/describeOrganizationConfiguration",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("GraphArn" => GraphArn), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Removes the Detective administrator account for the organization in the current Region.
Deletes the behavior graph for that account. Can only be called by the organization
management account. Before you can select a different Detective administrator account, you
must remove the Detective administrator account in all Regions.

"""
function disable_organization_admin_account(;
    aws_config::AbstractAWSConfig=global_aws_config()
)
    return detective(
        "POST",
        "/orgs/disableAdminAccount";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function disable_organization_admin_account(
    params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()
)
    return detective(
        "POST",
        "/orgs/disableAdminAccount",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Removes the member account from the specified behavior graph. This operation can only be
called by an invited member account that has the ENABLED status.  DisassociateMembership
cannot be called by an organization account in the organization behavior graph. For the
organization behavior graph, the Detective administrator account determines which
organization accounts to enable or disable as member accounts.

# Arguments
- `graph_arn`: The ARN of the behavior graph to remove the member account from. The member
  account's member status in the behavior graph must be ENABLED.

"""
function disassociate_membership(
    GraphArn; aws_config::AbstractAWSConfig=global_aws_config()
)
    return detective(
        "POST",
        "/membership/removal",
        Dict{String,Any}("GraphArn" => GraphArn);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function disassociate_membership(
    GraphArn,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return detective(
        "POST",
        "/membership/removal",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("GraphArn" => GraphArn), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Designates the Detective administrator account for the organization in the current Region.
If the account does not have Detective enabled, then enables Detective for that account and
creates a new behavior graph. Can only be called by the organization management account.
The Detective administrator account for an organization must be the same in all Regions. If
you already designated a Detective administrator account in another Region, then you must
designate the same account.

# Arguments
- `account_id`: The Amazon Web Services account identifier of the account to designate as
  the Detective administrator account for the organization.

"""
function enable_organization_admin_account(
    AccountId; aws_config::AbstractAWSConfig=global_aws_config()
)
    return detective(
        "POST",
        "/orgs/enableAdminAccount",
        Dict{String,Any}("AccountId" => AccountId);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function enable_organization_admin_account(
    AccountId,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return detective(
        "POST",
        "/orgs/enableAdminAccount",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("AccountId" => AccountId), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    get_members(account_ids, graph_arn)
    get_members(account_ids, graph_arn, params::Dict{String,<:Any})

Returns the membership details for specified member accounts for a behavior graph.

# Arguments
- `account_ids`: The list of Amazon Web Services account identifiers for the member account
  for which to return member details. You can request details for up to 50 member accounts at
  a time. You cannot use GetMembers to retrieve information about member accounts that were
  removed from the behavior graph.
- `graph_arn`: The ARN of the behavior graph for which to request the member details.

"""
function get_members(
    AccountIds, GraphArn; aws_config::AbstractAWSConfig=global_aws_config()
)
    return detective(
        "POST",
        "/graph/members/get",
        Dict{String,Any}("AccountIds" => AccountIds, "GraphArn" => GraphArn);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function get_members(
    AccountIds,
    GraphArn,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return detective(
        "POST",
        "/graph/members/get",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}("AccountIds" => AccountIds, "GraphArn" => GraphArn),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Returns the list of behavior graphs that the calling account is an administrator account
of. This operation can only be called by an administrator account. Because an account can
currently only be the administrator of one behavior graph within a Region, the results
always contain a single behavior graph.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"MaxResults"`: The maximum number of graphs to return at a time. The total must be less
  than the overall limit on the number of results to return, which is currently 200.
- `"NextToken"`: For requests to get the next page of results, the pagination token that
  was returned with the previous set of results. The initial request does not include a
  pagination token.
"""
function list_graphs(; aws_config::AbstractAWSConfig=global_aws_config())
    return detective(
        "POST", "/graphs/list"; aws_config=aws_config, feature_set=SERVICE_FEATURE_SET
    )
end
function list_graphs(
    params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()
)
    return detective(
        "POST",
        "/graphs/list",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Retrieves the list of open and accepted behavior graph invitations for the member account.
This operation can only be called by an invited member account. Open invitations are
invitations that the member account has not responded to. The results do not include
behavior graphs for which the member account declined the invitation. The results also do
not include behavior graphs that the member account resigned from or was removed from.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"MaxResults"`: The maximum number of behavior graph invitations to return in the
  response. The total must be less than the overall limit on the number of results to return,
  which is currently 200.
- `"NextToken"`: For requests to retrieve the next page of results, the pagination token
  that was returned with the previous page of results. The initial request does not include a
  pagination token.
"""
function list_invitations(; aws_config::AbstractAWSConfig=global_aws_config())
    return detective(
        "POST", "/invitations/list"; aws_config=aws_config, feature_set=SERVICE_FEATURE_SET
    )
end
function list_invitations(
    params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()
)
    return detective(
        "POST",
        "/invitations/list",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Retrieves the list of member accounts for a behavior graph. For invited accounts, the
results do not include member accounts that were removed from the behavior graph. For the
organization behavior graph, the results do not include organization accounts that the
Detective administrator account has not enabled as member accounts.

# Arguments
- `graph_arn`: The ARN of the behavior graph for which to retrieve the list of member
  accounts.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"MaxResults"`: The maximum number of member accounts to include in the response. The
  total must be less than the overall limit on the number of results to return, which is
  currently 200.
- `"NextToken"`: For requests to retrieve the next page of member account results, the
  pagination token that was returned with the previous page of results. The initial request
  does not include a pagination token.
"""
function list_members(GraphArn; aws_config::AbstractAWSConfig=global_aws_config())
    return detective(
        "POST",
        "/graph/members/list",
        Dict{String,Any}("GraphArn" => GraphArn);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function list_members(
    GraphArn,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return detective(
        "POST",
        "/graph/members/list",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("GraphArn" => GraphArn), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Returns information about the Detective administrator account for an organization. Can only
be called by the organization management account.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"MaxResults"`: The maximum number of results to return.
- `"NextToken"`: For requests to get the next page of results, the pagination token that
  was returned with the previous set of results. The initial request does not include a
  pagination token.
"""
function list_organization_admin_accounts(;
    aws_config::AbstractAWSConfig=global_aws_config()
)
    return detective(
        "POST",
        "/orgs/adminAccountslist";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function list_organization_admin_accounts(
    params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()
)
    return detective(
        "POST",
        "/orgs/adminAccountslist",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Returns the tag values that are assigned to a behavior graph.

# Arguments
- `resource_arn`: The ARN of the behavior graph for which to retrieve the tag values.

"""
function list_tags_for_resource(
    ResourceArn; aws_config::AbstractAWSConfig=global_aws_config()
)
    return detective(
        "GET",
        "/tags/$(ResourceArn)";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function list_tags_for_resource(
    ResourceArn,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return detective(
        "GET",
        "/tags/$(ResourceArn)",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Rejects an invitation to contribute the account data to a behavior graph. This operation
must be called by an invited member account that has the INVITED status.  RejectInvitation
cannot be called by an organization account in the organization behavior graph. In the
organization behavior graph, organization accounts do not receive an invitation.

# Arguments
- `graph_arn`: The ARN of the behavior graph to reject the invitation to. The member
  account's current member status in the behavior graph must be INVITED.

"""
function reject_invitation(GraphArn; aws_config::AbstractAWSConfig=global_aws_config())
    return detective(
        "POST",
        "/invitation/removal",
        Dict{String,Any}("GraphArn" => GraphArn);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function reject_invitation(
    GraphArn,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return detective(
        "POST",
        "/invitation/removal",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("GraphArn" => GraphArn), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    start_monitoring_member(account_id, graph_arn)
    start_monitoring_member(account_id, graph_arn, params::Dict{String,<:Any})

Sends a request to enable data ingest for a member account that has a status of
ACCEPTED_BUT_DISABLED. For valid member accounts, the status is updated as follows.   If
Detective enabled the member account, then the new status is ENABLED.   If Detective cannot
enable the member account, the status remains ACCEPTED_BUT_DISABLED.

# Arguments
- `account_id`: The account ID of the member account to try to enable. The account must be
  an invited member account with a status of ACCEPTED_BUT_DISABLED.
- `graph_arn`: The ARN of the behavior graph.

"""
function start_monitoring_member(
    AccountId, GraphArn; aws_config::AbstractAWSConfig=global_aws_config()
)
    return detective(
        "POST",
        "/graph/member/monitoringstate",
        Dict{String,Any}("AccountId" => AccountId, "GraphArn" => GraphArn);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function start_monitoring_member(
    AccountId,
    GraphArn,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return detective(
        "POST",
        "/graph/member/monitoringstate",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}("AccountId" => AccountId, "GraphArn" => GraphArn),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Applies tag values to a behavior graph.

# Arguments
- `resource_arn`: The ARN of the behavior graph to assign the tags to.
- `tags`: The tags to assign to the behavior graph. You can add up to 50 tags. For each
  tag, you provide the tag key and the tag value. Each tag key can contain up to 128
  characters. Each tag value can contain up to 256 characters.

"""
function tag_resource(ResourceArn, Tags; aws_config::AbstractAWSConfig=global_aws_config())
    return detective(
        "POST",
        "/tags/$(ResourceArn)",
        Dict{String,Any}("Tags" => Tags);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function tag_resource(
    ResourceArn,
    Tags,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return detective(
        "POST",
        "/tags/$(ResourceArn)",
        Dict{String,Any}(mergewith(_merge, Dict{String,Any}("Tags" => Tags), params));
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Removes tags from a behavior graph.

# Arguments
- `resource_arn`: The ARN of the behavior graph to remove the tags from.
- `tag_keys`: The tag keys of the tags to remove from the behavior graph. You can remove up
  to 50 tags at a time.

"""
function untag_resource(
    ResourceArn, tagKeys; aws_config::AbstractAWSConfig=global_aws_config()
)
    return detective(
        "DELETE",
        "/tags/$(ResourceArn)",
        Dict{String,Any}("tagKeys" => tagKeys);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function untag_resource(
    ResourceArn,
    tagKeys,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return detective(
        "DELETE",
        "/tags/$(ResourceArn)",
        Dict{String,Any}(mergewith(_merge, Dict{String,Any}("tagKeys" => tagKeys), params));
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Updates the configuration for the Organizations integration in the current Region. Can only
be called by the Detective administrator account for the organization.

# Arguments
- `graph_arn`: The ARN of the organization behavior graph.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"AutoEnable"`: Indicates whether to automatically enable new organization accounts as
  member accounts in the organization behavior graph.
"""
function update_organization_configuration(
    GraphArn; aws_config::AbstractAWSConfig=global_aws_config()
)
    return detective(
        "POST",
        "/orgs/updateOrganizationConfiguration",
        Dict{String,Any}("GraphArn" => GraphArn);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function update_organization_configuration(
    GraphArn,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return detective(
        "POST",
        "/orgs/updateOrganizationConfiguration",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("GraphArn" => GraphArn), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end


text:
module AcmeNestedMod

"""
    mutable struct Product

A product from Acme's catalog

Fields:
   `productId`: The unique identifier for a product
   `productName`: Name of the product
   `price`: The price of the product
   `tags`: Tags for the product
   `dimensions`:
"""
mutable struct Product
    productId::Int
    productName::String
    price::Float64
    tags::Array{String}
    dimensions::Dimensions
end

mutable struct Dimensions
    length::Float64
    width::Float64
    height::Float64
end

end # end module


text:
import FileIO
import JSON3
import CodecZlib
import PartiallyObservedInverseGames.DynamicsModelInterface

function unitvector(θ)
    [cos(θ), sin(θ)]
end

function rollout_strategy(control_system, x0, u)
    reduce(eachcol(u), init = x0[:, :]) do x_history, u_t
        x_t = x_history[:, end]
        x_tp = DynamicsModelInterface.next_x(control_system, x_t, u_t)
        [x_history x_tp]
    end
end

macro saveviz(expr::Expr)
    expr.head == :(=) || throw(ArgumentError("Expression must be an assigned computation."))
    var, fun = expr.args

    quote
        $(esc(expr))
        $(esc(var)) |>
        FileIO.save(joinpath(project_root_dir, "figures", result_group, "$($(Meta.quot(var))).pdf"))
    end
end

macro saveviz(sym::Symbol)
    quote
        $(esc(sym)) |>
        FileIO.save(joinpath(project_root_dir, "figures", result_group, "$($(Meta.quot(sym))).pdf"))
    end
end

macro save_json(expr)
    expr.head == :(=) || throw(ArgumentError("Expression must be an assigned computation."))
    var, fun = expr.args

    quote
        $(esc(expr))
        save_json($var, result_group, $(Meta.quot(var)))
    end
end

function save_json(result, result_group, key)
    result_file = joinpath(project_root_dir, "data/$result_group/$key.json.gz")
    open(io -> JSON3.write(io, result), CodecZlib.GzipCompressorStream, result_file, "w")
end


text:
function E_slice_plot(E::ScalarField, dir, slice_location, t;
    issliced=false,
    save=false,
    slice_dir=:x,
    kwargs...)

    if !issliced
        E_slice = slice(E, slice_dir, slice_location)
    else
        E_slice = E
    end

    grid = getdomain(E_slice)
    cl = ustrip(max(abs.(extrema(E_slice))...))
    ts = sprint(show, t, context=:compact => true)
    loc = sprint(show, slice_location, context=:compact => true)
    xlabel, ylabel = filter(x->x≠string(slice_dir), ["x", "y", "z"])

    plt = Plots.heatmap(grid..., E_slice;
        title = "E$dir at t=$ts, x=$loc",
        xlabel, ylabel, colorbar_title = "E$dir",
        seriescolor = :jet1,
        aspect_ratio = 1,
        framestyle = :box,
        clims = (-cl, cl),
        kwargs...)

    if save
        save && Plots.savefig(plt, "E$(dir_slice)_$loc.png")
    end

    return plt
end


text:
using DSGE, ModelConstructors, Dates, Test, Nullables, HDF5, Optim
path = dirname(@__FILE__)
m = AnSchorfheide()
m <= Setting(:date_zlb_end, get_setting(m, :date_zlb_start))
m <= Setting(:hessian_path, "")

# Dates, indices, number of periods for each regime
@testset "Test field access functions for AbstractDSGEModel objects" begin
    @test DSGE.get_parameters(m) == m.parameters
    @test DSGE.get_rng(m) == m.rng
    @test DSGE.get_settings(m) == m.settings
    @test DSGE.get_observables(m) == m.observables
    @test DSGE.get_pseudo_observables(m) == m.pseudo_observables
    @test DSGE.get_exogenous_shocks(m) == m.exogenous_shocks
end

@testset "Test setting access functions for AbstractDSGEModel objects" begin
    n_mon_anticipated_shocks(m) == get_setting(m, :n_mon_anticipated_shocks)
    n_mon_anticipated_shocks_padding(m) == get_setting(m, :n_mon_anticipated_shocks_padding)

    @test DSGE.date_presample_start(m) == get_setting(m, :date_presample_start)
    @test DSGE.date_mainsample_start(m) == get_setting(m, :date_mainsample_start)
    @test DSGE.date_zlb_start(m) == get_setting(m, :date_zlb_start)
    @test DSGE.date_zlb_end(m) == get_setting(m, :date_zlb_end)

    @test DSGE.date_presample_end(m) == Dates.lastdayofquarter(get_setting(m, :date_mainsample_start) - Dates.Month(3))
    @test DSGE.date_prezlb_end(m) == Dates.lastdayofquarter(get_setting(m, :date_zlb_start) - Dates.Month(3))
    @test DSGE.date_mainsample_end(m) == Dates.lastdayofquarter(get_setting(m, :date_forecast_start) - Dates.Month(3))
    @test DSGE.date_conditional_end(m) == get_setting(m, :date_conditional_end)

    @test DSGE.index_presample_start(m) == 1
    @test DSGE.index_mainsample_start(m) == subtract_quarters(DSGE.date_mainsample_start(m), DSGE.date_presample_start(m)) + 1
    @test DSGE.index_zlb_start(m) == subtract_quarters(DSGE.date_zlb_start(m), DSGE.date_presample_start(m)) + 1
    @test DSGE.index_forecast_start(m) == subtract_quarters(DSGE.date_forecast_start(m), DSGE.date_presample_start(m)) + 1

    @test DSGE.index_shockdec_start(m) == subtract_quarters(DSGE.date_shockdec_start(m), DSGE.date_mainsample_start(m)) + 1

    @test DSGE.index_shockdec_end(m) == subtract_quarters(DSGE.date_shockdec_end(m), DSGE.date_mainsample_start(m)) + 1

    @test n_presample_periods(m)   == subtract_quarters(DSGE.date_mainsample_start(m), DSGE.date_presample_start(m))
    @test n_prezlb_periods(m)      == subtract_quarters(DSGE.date_zlb_start(m), DSGE.date_mainsample_start(m))
    @test n_zlb_periods(m)         == subtract_quarters(DSGE.date_forecast_start(m), DSGE.date_zlb_start(m))
    @test n_mainsample_periods(m)  == subtract_quarters(DSGE.date_forecast_start(m), DSGE.date_mainsample_start(m))
    @test n_conditional_periods(m) == subtract_quarters(DSGE.date_conditional_end(m), DSGE.date_mainsample_end(m))

    @test inds_presample_periods(m)  == collect(DSGE.index_presample_start(m):(DSGE.index_mainsample_start(m)-1))
    @test inds_prezlb_periods(m)     == collect(DSGE.index_mainsample_start(m):(DSGE.index_zlb_start(m)-1))
    @test inds_zlb_periods(m)        == collect(DSGE.index_zlb_start(m):(DSGE.index_forecast_start(m)-1))
    @test inds_mainsample_periods(m) == collect(DSGE.index_mainsample_start(m):(DSGE.index_forecast_start(m)-1))

    @test cond_vintage(m)    == get_setting(m, :cond_vintage)
    @test cond_id(m)         == get_setting(m, :cond_id)
    @test cond_full_names(m) == get_setting(m, :cond_full_names)
    @test cond_semi_names(m) == get_setting(m, :cond_semi_names)
    @test use_population_forecast(m) == get_setting(m, :use_population_forecast)
    @test DSGE.hpfilter_population(m)     == get_setting(m, :hpfilter_population)

    # Interface for general computation settings
    @test DSGE.use_parallel_workers(m)    == get_setting(m, :use_parallel_workers)

    # Interface for estimation settings
    @test DSGE.reoptimize(m)          == get_setting(m, :reoptimize)
    @test DSGE.calculate_hessian(m) == get_setting(m, :calculate_hessian)
    @test DSGE.hessian_path(m)      == get_setting(m, :hessian_path)
    @test DSGE.n_hessian_test_params(m) == get_setting(m, :n_hessian_test_params)

    # Interface for Metropolis-Hastings settings
    @test DSGE.n_mh_blocks(m)       ==  get_setting(m, :n_mh_blocks)
    @test DSGE.n_mh_param_blocks(m) ==  get_setting(m, :n_mh_param_blocks)
    @test DSGE.n_mh_simulations(m)  ==  get_setting(m, :n_mh_simulations)
    @test DSGE.n_mh_burn(m)         ==  get_setting(m, :n_mh_burn)
    @test DSGE.mh_thin(m)           ==  get_setting(m, :mh_thin)

    # Interface for forecast settings
    @test DSGE.date_forecast_start(m)   == get_setting(m, :date_forecast_start)
    @test DSGE.forecast_block_size(m)   == get_setting(m, :forecast_block_size)
    @test DSGE.forecast_start_block(m)  == get_setting(m, :forecast_start_block)
    @test DSGE.forecast_input_file_overrides(m) == get_setting(m, :forecast_input_file_overrides)
    @test isnull(DSGE.forecast_uncertainty_override(m)) && isnull(get_setting(m, :forecast_uncertainty_override))
    @test DSGE.forecast_smoother(m)     == get_setting(m, :forecast_smoother)
    @test DSGE.forecast_tdist_df_val(m) == get_setting(m, :forecast_tdist_df_val)
    @test DSGE.forecast_tdist_shocks(m) == get_setting(m, :forecast_tdist_shocks)
    @test DSGE.forecast_zlb_value(m)    == get_setting(m, :forecast_zlb_value)
    @test DSGE.impulse_response_horizons(m) == get_setting(m, :impulse_response_horizons)
    @test DSGE.n_shockdec_periods(m)    == DSGE.index_shockdec_end(m) - DSGE.index_shockdec_start(m) + 1

    # Interface for alternative policy settings
    @test DSGE.alternative_policy(m) == get_setting(m, :alternative_policy)
end

@testset "Check automatic calculation of settings for regime switching forecasts" begin
    rss = Vector{Dict{Int, Date}}(undef, 0)
    fss = Vector{Date}(undef, 0)
    ces = Vector{Date}(undef, 0)
    nreg = Vector{Int}(undef, 0)
    nhistreg = Vector{Int}(undef, 0)
    nfcastreg = Vector{Int}(undef, 0)
    ncondreg = Vector{Int}(undef, 0)
    nruleper = Vector{Int}(undef, 0)
    regfcaststart = Vector{Int}(undef, 0)
    regpostcond = Vector{Int}(undef, 0)

    push!(rss, Dict{Int, Date}(1 => date_presample_start(m), 2 => Date(2020, 3, 31), 3 => Date(2020, 6, 30), 4 => Date(2020, 9, 30)))
    push!(fss, Date(2020, 6, 30))
    push!(ces, Date(2020, 6, 30))
    push!(nreg, 4)
    push!(nhistreg, 2)
    push!(nfcastreg, 2)
    push!(ncondreg, 1)
    push!(nruleper, 1)
    push!(regfcaststart, 3)
    push!(regpostcond, 4)

    push!(rss, Dict{Int, Date}(1 => date_presample_start(m), 2 => Date(2020, 3, 31), 3 => Date(2020, 6, 30)))
    push!(fss, Date(2020, 6, 30))
    push!(ces, Date(2020, 6, 30))
    push!(nreg, 3)
    push!(nhistreg, 2)
    push!(nfcastreg, 1)
    push!(ncondreg, 1)
    push!(nruleper, 0)
    push!(regfcaststart, 3)
    push!(regpostcond, 3)

    push!(rss, Dict{Int, Date}(1 => date_presample_start(m), 2 => Date(2019, 12, 31), 3 => Date(2020, 12, 31)))
    push!(fss, Date(2020, 6, 30))
    push!(ces, Date(2020, 3, 31))
    push!(nreg, 3)
    push!(nhistreg, 2)
    push!(nfcastreg, 2)
    push!(ncondreg, 0)
    push!(nruleper, 0)
    push!(regfcaststart, 2)
    push!(regpostcond, 2)

    push!(rss, Dict{Int, Date}(1 => date_presample_start(m), 2 => Date(2015, 3, 31), 3 => Date(2019, 6, 30)))
    push!(fss, Date(2020, 6, 30))
    push!(ces, Date(2020, 3, 31))
    push!(nreg, 3)
    push!(nhistreg, 3)
    push!(nfcastreg, 1)
    push!(ncondreg, 0)
    push!(nruleper, -1)
    push!(regfcaststart, 3)
    push!(regpostcond, 3)

    push!(rss, Dict{Int, Date}(1 => date_presample_start(m), 2 => Date(2020, 3, 31), 3 => Date(2020, 12, 31)))
    push!(fss, Date(2020, 6, 30))
    push!(ces, Date(2020, 12, 31))
    push!(nreg, 3)
    push!(nhistreg, 2)
    push!(nfcastreg, 2)
    push!(ncondreg, 1)
    push!(nruleper, 0)
    push!(regfcaststart, 2)
    push!(regpostcond, 3)

    push!(rss, Dict{Int, Date}(1 => date_presample_start(m), 2 => Date(2020, 3, 31), 3 => Date(2021, 12, 31)))
    push!(fss, Date(2020, 6, 30))
    push!(ces, Date(2020, 12, 31))
    push!(nreg, 3)
    push!(nhistreg, 2)
    push!(nfcastreg, 2)
    push!(ncondreg, 0)
    push!(nruleper, 0)
    push!(regfcaststart, 2)
    push!(regpostcond, 2)

    push!(rss, Dict{Int, Date}(1 => date_presample_start(m), 2 => Date(2020, 6, 30), 3 => Date(2021, 12, 31)))
    push!(fss, Date(2020, 6, 30))
    push!(ces, Date(2020, 12, 31))
    push!(nreg, 3)
    push!(nhistreg, 1)
    push!(nfcastreg, 2)
    push!(ncondreg, 1)
    push!(nruleper, 1)
    push!(regfcaststart, 2)
    push!(regpostcond, 2)

    for (rs, fs, ce, nr, nhr, nfr, ncr, nrp, rfs, rpc) in zip(rss, fss, ces, nreg, nhistreg,
                                                              nfcastreg, ncondreg, nruleper, regfcaststart, regpostcond)
        m <= Setting(:regime_dates, rs)
        m <= Setting(:date_forecast_start, fs)
        m <= Setting(:date_conditional_end, ce)
        setup_regime_switching_inds!(m)
        for (set, setans) in zip([:n_regimes, :n_hist_regimes, :n_fcast_regimes, :n_cond_regimes, :n_rule_periods,
                                  :reg_forecast_start, :reg_post_conditional_end], [nr, nhr, nfr, ncr, nrp, rfs, rpc])
            @test get_setting(m, set) == setans
        end
        setup_regime_switching_inds!(m; cond_type = :full)
        @test get_setting(m, :n_rule_periods) == (nrp - ncr)
        setup_regime_switching_inds!(m; cond_type = :semi)
        @test get_setting(m, :n_rule_periods) == (nrp - ncr)
    end
end

@testset "Test other auxiliary setting functions for AbstractDSGEModel objects" begin
    m <= Setting(:population_mnemonic, :test)
    @test get(DSGE.parse_population_mnemonic(m)[1]) == :test
    m <= Setting(:population_mnemonic, Nullable())
    println(DSGE.parse_population_mnemonic(m))
    @test DSGE.parse_population_mnemonic(m)[1].hasvalue == false
    m1 = Model1002("ss10")
    @test @test_matrix_approx_eq inds_states_no_ant(m) collect(1:8)
    @test @test_matrix_approx_eq inds_states_no_ant(m1) vcat(collect(1:62), collect(69:84))
    @test @test_matrix_approx_eq inds_shocks_no_ant(m) collect(1:3)
    @test @test_matrix_approx_eq inds_shocks_no_ant(m1) collect(1:18)
    @test @test_matrix_approx_eq inds_obs_no_ant(m) collect(1:3)
    @test @test_matrix_approx_eq inds_obs_no_ant(m1) collect(1:13)

    m <= Setting(:date_forecast_start, DSGE.quartertodate("2019-Q3"))
    m <= Setting(:forecast_horizons, 10)
    exp_date_forecast_end = DSGE.lastdayofquarter(DSGE.quartertodate("2019-Q3") + Dates.Month(3 * 9))
    @test forecast_horizons(m) == 10
    m <= Setting(:date_conditional_end, DSGE.date_mainsample_end(m) + Dates.Month(3))
    @test forecast_horizons(m; cond_type = :full) == 9
    @test forecast_horizons(m; cond_type = :semi) == 9
    @test DSGE.date_forecast_end(m) == exp_date_forecast_end
    @test DSGE.date_shockdec_start(m) == date_mainsample_start(m)
    m <= Setting(:shockdec_startdate, nothing)
    @test DSGE.date_shockdec_start(m) == DSGE.date_mainsample_start(m)
    m <= Setting(:shockdec_startdate, Nullable{Date}(DSGE.quartertodate("2019-Q3")))
    @test DSGE.date_shockdec_start(m) == DSGE.quartertodate("2019-Q3")
    m <= Setting(:shockdec_enddate, Nullable{Date}())
    @test DSGE.date_shockdec_end(m) == DSGE.date_forecast_end(m)
    m <= Setting(:shockdec_enddate, Nullable{Date}(DSGE.quartertodate("2019-Q3")))
    @test DSGE.date_shockdec_end(m) == DSGE.quartertodate("2019-Q3")

    @test @test_matrix_approx_eq load_parameters_from_file(m, "$path/reference/load_params.h5") h5read("$path/reference/load_params.h5", "params")
    @test_throws ErrorException load_parameters_from_file(m, "$path/reference/smc.h5")
    @test_throws ErrorException load_parameters_from_file(m, "$path/reference/smc.jld2")
    @test_throws AssertionError load_parameters_from_file(m1, "$path/reference/load_params.h5")
    @test_throws AssertionError load_parameters_from_file(m, "$path/reference/load_params_float32.h5")

    m.parameters[1].value = 1.
    specify_mode!(m, "reference/load_params.h5")
    out_params = map(x -> x.value, m.parameters)
    @test @test_matrix_approx_eq out_params load_parameters_from_file(m, "$path/reference/load_params.h5")

    specify_hessian!(m, "reference/hessian.h5")
    @test get_setting(m, :calculate_hessian) == false
    @test get_setting(m, :hessian_path) == joinpath(dirname("$path"), "test", "reference", "hessian.h5")

    para = deepcopy(map(x -> x.value, m.parameters))
    m.parameters[1].value = 1.
    DSGE.update!(m, para)
    @test m.parameters[1].value != 1.
    @test @test_matrix_approx_eq map(x->x.value,m.parameters) para
end

nothing


text:
module LCA
export lca

#Make all columns of the input matrix have a norm of 1
# D - input matrix (modified in place)
function unitary_columns!(D::Matrix{Float64})
    for i = 1:size(D,2)
        D[:,i] = D[:,i]/norm(D[:,i]);
    end
    nothing
end

#Perform the positive/negative soft threshold function on each element
# a - result vector (modified in place)
# u - input vector
# T - threshold
function soft_threshold!(a::Vector{Float64}, u::Vector{Float64}, T::Float64)
    for i = 1:length(u)
        if abs(u[i]) > T
            a[i,1] = u[i]-sign(u[i])*T;
        else
            a[i,1] = 0;
        end
    end
    nothing
end

#Perform Locally Competitive Algorithm for sparse recovery
# s      - Input to be approximated
# D      - Dictionary
# T_soft - Soft threshold value (higher promotes more sparseness)
# iter   - maximum number of iterations
# τ      - 1/learning rate
function lca(s::Vector{Float64}, D::Matrix{Float64}; T_soft::Float64=0.1,
             iter::Int=10000, τ::Float64=100.0)
    #Impose Unitary norm
    unitary_columns!(D)

    # Inhibition Matrix
    G = D'*D;
    for i=1:size(G,1)
        G[i,i] = 0;
    end

    # Initialize LCA Parameters
    b = D'*s; # initial projection / excitatory input

    u = zeros(size(b)); # initial state of nodes = 0
    a = zeros(size(b)); # initial sparse rep = 0

    for i=1:iter
        # soft thresholding function
        soft_threshold!(a, u, T_soft)

        # node dynamics
        Δu = (b  - u - G*a)/τ;
        u += Δu;

        if(norm(Δu) < 1e-5)
            break
        end
    end

    return a
end

end # module


text:
"""
    splomaxes!(f::Figure, labels::AbstractVector{<:AbstractString})

Populate f with a set of `(k*(k-1))/2` axes in a lower triangle for all pairs of `labels`,
where `k` is the length of `labels`.  The `panel!` function should have the signature
`panel!(ax::Axis, i::Integer, j::Integer)` and should draw the [i,j] panel in `ax`.
"""
function splomaxes!(f::Figure, labels, panel!::Function; extraticks::Bool=false)
    k = length(labels)
    cols = Dict()
    for i in 2:k                          # strict lower triangle of panels
        row = Axis[]
        for j in 1:(i - 1)
            ax = Axis(f[i - 1, j])
            panel!(ax, i, j)
            push!(row, ax)
            col = get!(cols, j, Axis[])
            push!(col, ax)
            if i == k              # add x labels on bottom row
                ax.xlabel = string(labels[j])
            elseif extraticks && i == 2
                ax.xaxisposition = :top
                hidexdecorations!(ax; grid=false, ticks=false)
            else
                hidexdecorations!(ax; grid=false)
            end
            if isone(j)            # add y labels on left column
                ax.ylabel = string(labels[i])
            elseif extraticks && j == i - 1
                ax.yaxisposition = :right
                hideydecorations!(ax; grid=false, ticks=false)
            else
                hideydecorations!(ax; grid=false)
            end
        end
        linkyaxes!(row...)
    end

    foreach(values(cols)) do col
        linkxaxes!(col...)
    end

    return f
end

"""
    shrinkageplot!(f::Figure, m::MixedModel, gf::Symbol=first(fnames(m)), θref)

Return a scatter-plot matrix of the conditional means, b, of the random effects for grouping factor `gf`.

Two sets of conditional means are plotted: those at the estimated parameter values and those at `θref`.
The default `θref` results in `Λ` being a very large multiple of the identity.  The corresponding
conditional means can be regarded as unpenalized.
"""
function shrinkageplot!(
    f::Figure,
    m::MixedModel{T},
    gf::Symbol=first(fnames(m)),
    θref::AbstractVector{T}= (isa(m, LinearMixedModel) ? 1e4 : 1) .* m.optsum.initial,
) where {T}
    reind = findfirst(==(gf), fnames(m))  # convert the symbol gf to an index
    if isnothing(reind)
        throw(ArgumentError("gf=$gf is not one of the grouping factor names, $(fnames(m))"))
    end
    reest = ranef(m)[reind]          # random effects conditional means at estimated θ
    reref = _ranef(m, θref)[reind]   # same at θref
    function pfunc(ax, i, j)
        x, y = view(reref, j, :), view(reref, i, :)
        scatter!(ax, x, y; color=(:red, 0.25))   # reference points
        u, v = view(reest, j, :), view(reest, i, :)
        arrows!(ax, x, y, u .- x, v .- y)        # first so arrow heads don't obscure pts
        return scatter!(ax, u, v; color=(:blue, 0.25))  # conditional means at estimates
    end
    splomaxes!(f, m.reterms[reind].cnames, pfunc)

    return f
end

function _ranef(m::LinearMixedModel, θref)
    vv = try
        ranef(updateL!(setθ!(m, θref)))
    catch e
        @error "Failed to compute unshrunken values with the following exception:"
        rethrow(e)
    finally
        updateL!(setθ!(m, m.optsum.final)) # restore parameter estimates and update m
    end
    return vv
end

function _ranef(m::GeneralizedLinearMixedModel, θref)
    fast = length(m.θ) == length(m.optsum.final)
    setpar! = fast ? MixedModels.setθ! : MixedModels.setβθ!
    vv = try
        ranef(pirls!(setpar!(m, θref), fast, false)) # not verbose
    catch e
        @error "Failed to compute unshrunken values with the following exception:"
        rethrow(e)
    finally
        pirls!(setpar!(m, m.optsum.final), fast, false) # restore parameter estimates and update m
    end

    return vv
end

"""
    shrinkageplot(m::MixedModel, gf::Symbol=first(fnames(m)), θref)

Return a scatter-plot matrix of the conditional means, b, of the random effects for grouping factor `gf`.

Two sets of conditional means are plotted: those at the estimated parameter values and those at `θref`.
The default `θref` results in `Λ` being a very large multiple of the identity.  The corresponding
conditional means can be regarded as unpenalized.
"""
function shrinkageplot(m::MixedModel, args...)
    f = Figure(; resolution=(1000, 1000)) # use an aspect ratio of 1 for the whole figure

    return shrinkageplot!(f, m, args...)
end

"""
    splom!(f::Figure, df::DataFrame)

Create a scatter-plot matrix in `f` from the columns of `df`.
"""
function splom!(f::Figure, df::DataFrame; addcontours::Bool=false)
    mat = Array(df)
    function pfunc(ax, i, j)
        v = view(mat, :, [j, i])
        scatter!(ax, v, color=(:blue, 0.2))
        addcontours && contour!(ax, kde(v))
        return ax
    end
    splomaxes!(f, names(df), pfunc)
end


text:
using Parameters, CSV, DataFrames, DataFramesMeta, Query
using Statistics, Random, Distributions, Combinatorics
using Flux, Tracker, Printf

myscale(A) = (A .- mean(A)) ./ std(A)

function get_data(pca = false, path = "data/data_f_pca.csv")
    if (pca)
        df = CSV.File(path) |> DataFrame!
    else
        df = CSV.File(path) |> DataFrame!
    end
    select!(df, Not(1:2))
    df.origin = repeat(1:3, inner = 30)
    return df
end

function simu_accuracy(df_test, mod, nreg::Int64 = 3)
    df_tmp = select(df_test, Not(:origin))
    out = zeros(Float32, nreg, nreg)
    for i in 1:size(df_tmp, 1)
        new = [df_tmp[i, k] for k in 1:size(df_tmp, 2)]
        res = mod(new)
        # Tracker.data ==> otherwise we have tracker array that cannot be
        # use with "base array"
        out[df_test.origin[i], :] += Tracker.data(res)
    end
    return out
end
# df4 = df2[:,1:3]
# df4.origin = df2.origin
# simu_accuracy(df4, m)

# returns a vector of n indiv sample among ndiv for nreg regions
function samp_samples(n::Int64, nindiv::Int64 = 30, nreg::Int64 = 3)
    out = []
    for i in 1:nreg
        append!(out, randperm(nindiv)[1:n] .+ (i - 1) * nindiv)
    end
    return out
end

function write_res(df_test, mod, file)
    df_tmp = select(df_test, Not(:origin))
    io = open(file, "w")
    for i in 1:size(df_tmp, 1)
        new = [df_tmp[i, k] for k in 1:size(df_tmp, 2)]
        res = Tracker.data(mod(new))
        for j in 1:size(res, 1)
            write_res_unit(i, j, df_test.origin[i], res[j], io)
        end
    end
    close(io)
    return nothing
end

function write_res_unit(id_sampl, reg_hyp, reg_true, val, io)
    println(io, id_sampl, " ", reg_hyp, " ", reg_true, " ", val)
    return nothing
end

function write_res2(df_test, mod, file, var, combn, rep)
    df_tmp = select(df_test, Not(:origin))
    io = open(file, "a")
    for i in 1:size(df_tmp, 1)
        new = [df_tmp[i, k] for k in 1:size(df_tmp, 2)]
        res = Tracker.data(mod(new))
        for j in 1:size(res, 1)
            write_res_unit2(var, combn, rep, i, j, df_test.origin[i], res[j], io)
        end
    end
    close(io)
    return nothing
end

function write_res_unit2(var, combn, rep, id_sampl, reg_hyp, reg_true, val, io)
    println(io, var, " ", combn, " ", rep, " ", id_sampl, " ", reg_hyp, " ", reg_true, " ", val)
    return nothing
end

function add_noise(val, σ)
    val .+ σ * randn(size(val, 1))
end

function add_noise_df(df, σ)
    DataFrame([add_noise(col, σ) for col = eachcol(df)])
end

# taining set with data augmentation (white noise)
function simu_train(df_train, sig = 0.0, nrep::Int64 = 10, nreg::Int64 = 3)
    df_tmp = select(df_train, Not(:origin))
    train_data = []
    # size of training set if nrep*size(df, 1)
    for r in 1:nrep
        for j in 1:size(df_train, 1)
            x = [df_tmp[j, k] for k = 1:size(df_tmp, 2)]
            x += sig * randn(size(df_tmp, 2))
            y = fill(0.0, nreg)
            y[df_train.origin[j]] = 1.0
            push!(train_data, (x, y))
        end
    end
    return train_data
end
# simu_train(df2, .1, 2)
# df3 = select(df2, Not(:origin))
# simu_accuracym

# columns sampler
function sample_cols(nbio, mxbio, mxcb)
    tmp = collect(combinations(Array(1:mxbio), nbio))
    if (size(tmp, 1) <= mxcb)
        out = tmp
    else
        out = tmp[randperm(size(tmp, 1))[1:mxcb]]
    end
    return out
end

function simu_ml(df_train, df_test, sig, file::String, aug = 1e5,
    nreg::Int64 = 3)
    ##
    ndim = size(df_train, 2) - 1
    nhid = round(Int, (ndim + nreg) / 2)
    ##
    m = Chain(Dense(ndim, nhid, σ), Dense(nhid, nreg), softmax)
    # loss(x, y) = Flux.mse(m(x), y)
    loss(x, y) = Flux.crossentropy(m(x), y)
    ##
    # print("Generating data set ")
    train_data = simu_train(df_train, sig, aug)
    # println("✓")
    ##
    # print("Training ")
    Flux.train!(loss, Flux.params(m), train_data, ADAM())
    # println("✓")
    ##
    write_res(df_test, m, file)
    ##
    return nothing
end

# a second version to write all simulatoin in a single file
function simu_ml2(df_train, df_test, sig, file::String, var, combn, rep,
    aug = 1e5, nreg::Int64 = 3)
    ##
    ndim = size(df_train, 2) - 1
    nhid = round(Int, (ndim + nreg) / 2)
    ##
    m = Chain(Dense(ndim, nhid, σ), Dense(nhid, nreg), softmax)
    # loss(x, y) = Flux.mse(m(x), y)
    loss(x, y) = Flux.crossentropy(m(x), y)
    ##
    # print("Generating data set ")
    train_data = simu_train(df_train, sig, aug)
    # println("✓")
    ##
    # print("Training ")
    Flux.train!(loss, Flux.params(m), train_data, ADAM())
    # println("✓")
    ##
    write_res2(df_test, m, file, var, combn, rep)
    ##
    return nothing
end


text:
using LinearAlgebra

# A gaussian kernel function
@inline function Φ(x::Vector{T},
                   y::Vector{T},
                   r::T=1.0) where T<:AbstractFloat
    n  = 1.0 / sqrt(2π*r)
    s  = 1.0 / (2r^2)
    return n*exp(-s*sum((x.-y).^2))
end

# A kernel matrix
function ΦΦ(X::AbstractArray{T},
            r::T=1.0) where T<:AbstractFloat
    n = size(X,1)
    K = zeros(n,n)
    for i=1:n
        for j=1:i
            K[i, j] = Φ(X[i, :], X[j, :],r)
            K[j, i] = K[i, j]
        end
        K[i, i] = Φ(X[i, :], X[i, :],r)
    end
    K
end

# A kernel matrix for test data
function ΦΦ(X::AbstractArray{T},
            Z::AbstractArray{T},
            r::T=1.0) where T<:AbstractFloat
    (nx,mx)    = size(X)
    (nz,mz)    = size(Z)
    K          = zeros(T,nz, nx)
    for i=1:nz
        for j=1:nx
            K[i, j] = Φ(Z[i, :], X[j, :],r)
        end
    end
    K
end

## the learning algorithm: KPLS2 - multiple targets
function trainer(model::KPLSModel{T},
                X::AbstractArray{T},
                Y::AbstractArray{T};
                ignore_failures       = true,
                tol                   = 1e-6,
                max_iterations        = 250
                ) where T<:AbstractFloat

    kernel,width        = model.kernel,model.width
    Y                   = Y[:,:]
    model.ntargetcols   = size(Y,2)
    nfactors            = model.nfactors

    n = size(X,1)

    Tj = zeros(T,n, nfactors)
    Q  = zeros(T,model.ntargetcols, nfactors)
    U  = zeros(T,n, nfactors)
    P  = zeros(T,n, nfactors)

    K = ΦΦ(X,width)

    # centralize kernel
    c = Matrix{Float64}(I, n, n) -  ones(Float64,n,n).*1.0/n
    K = c * K * c

    K_j = K[:,:]

    for j=1:nfactors

        u = Y[:,1]

        iteration_count  = 0
        iteration_change = tol * 10.0
        local w,t,q,old_u
        while iteration_count < max_iterations && iteration_change > tol

            w = K * u
            t = w / norm(w, 2)

            q = Y' * t

            old_u = u
            u = Y * q
            u /= norm(u, 2)
            iteration_change = norm(u - old_u)
            iteration_count += 1
        end

        if iteration_count >= max_iterations
            if ignore_failures
                nfactors = j
                warn("KPLS: Found with less factors. Overall factors = $(nfactors)")
                break
            else
                error("KPLS: failed to converge for component: $(nfactors+1)")
            end
        end
        Tj[:, j] = t
        Q[:, j] = q
        U[:, j] = u

        P[:, j]  = (K_j' * w) / (w'w)
        deflator = Matrix{Float64}(I, n, n) .- t'*t
        K_j      = deflator * K_j * deflator
        Y        = Y - t * q'

    end
    # If iteration stopped early because of failed convergence, only
    # the actual components will be copied

    Tj = Tj[:, 1:nfactors]
    Q = Q[:, 1:nfactors]
    U = U[:, 1:nfactors]
    #P = P[:, 1:nfactors]

    model.nfactors = nfactors
    model.X        = X # unfortunately it is necessary on the prediction phase
    model.K        = K # unfortunately it is necessary on the prediction phase
    try
       model.B        = U * inv(Tj' * K * U) * Q'
    catch
       error("KPLS: Not able to compute inverse.
             Maybe nfactors is greater than ncols of input data (X) or
             this matrix is not invertible.
             ")
    end

    return model

end

function predictor(model::KPLSModel{T},
                   Z::AbstractArray{T}) where T<:AbstractFloat

    X,K,B,w      = model.X,model.K,model.B,model.width
    (nx,mx)    = size(X)
    (nz,mz)    = size(Z)

    Kt         = ΦΦ(X,Z,w) # kernel matrix

    # centralize
    c = (1.0 / nx) .* ones(T,nz,nx)
    Kt = (Kt - c * K) * (Matrix{T}(I, nx, nx) - (1.0 / nx) .* ones(T,nx,nx))

    Y = Kt * B

    return Y

end


text:
@testset "tucker" begin
    using ApproxTools: tucker
    @testset for TC in rnc((Int,Float64)), TB in rnc((Int,Float64))
        C = rand(TC,2)
        B = (rand(TB,2,2),)
        @test @inferred(tucker(C,B)) ≈ B[1]*C
        @test @inferred(tucker(C,map(B->(C->B*C),B))) ≈ B[1]*C
    end

    @testset for TC in rnc((Int,Float64)), TB1 in rnc((Int,Float64)), TB2 in rnc((Int,Float64))
        C = rand(TC,2,2)
        B = (rand(TB1,2,2), rand(TB2,2,2))
        @test @inferred(tucker(C,B)) ≈ B[1]*C*transpose(B[2])
        @test @inferred(tucker(C,map(B->(C->B*C),B))) ≈ B[1]*C*transpose(B[2])
    end

    @testset for TC in rnc(Float64), TB1 in rnc(Float64), TB2 in rnc(Float64), TB3 in rnc(Float64)
        C = rand(TC,2,2,2)
        B = (rand(TB1,2,2), rand(TB2,2,2), rand(TB3,2,2))
        R = reshape(transpose(B[3]*reshape(transpose(B[2]*reshape(transpose(B[1]*reshape(C,(2,4))),(2,4))),(2,4))),(2,2,2))
        @test @inferred(tucker(C,B)) ≈ R
        @test @inferred(tucker(C,map(B->(C->B*C),B))) ≈ R
    end
end


text:
using Test, Random, HybridArrays, StaticArrays

######### Tests #########
@testset "Statically sized SubArray" begin
    A = HybridArray{Tuple{2,2,StaticArrays.Dynamic()}}(fill(0.0, 2, 2, 10))
    Av = view(A, :, :, SOneTo(3))
    @test isa(Av, SizedArray{Tuple{2,2,3},Float64,3,3,SubArray{Float64,3,HybridArray{Tuple{2,2,StaticArrays.Dynamic()},Float64,3,3,Array{Float64,3}},Tuple{Base.Slice{SOneTo{2}},Base.Slice{SOneTo{2}},SOneTo{3}},true}})

    @test (@inferred Av[:,:,1]) === SA[0.0 0.0; 0.0 0.0]
    @test (@inferred Av[:,SOneTo(2),1]) === SA[0.0 0.0; 0.0 0.0]
    @test StaticArrays.similar_type(Av) === SArray{Tuple{2,2,3},Float64,3,12}

    # views that leave dynamically sized axes should return HybridArray (see issue #31)
    B = HybridArray{Tuple{2,5,StaticArrays.Dynamic()}}(randn(2, 5, 3))
    @test isa((@inferred view(B, :, StaticArrays.SUnitRange(2, 4), :)), HybridArray{Tuple{2,3,StaticArrays.Dynamic()},Float64,3,3,<:SubArray})
    Bv = view(B, :, StaticArrays.SUnitRange(2, 4), :)
    @test Bv == B[:, StaticArrays.SUnitRange(2, 4), :]
end