function compute_marginals(pw::Vector, pogw::Matrix, pagow)

    card_o = size(pogw,1)
    card_a = size(pagow,1)

    #compute p(o)
    #p(o) = ∑_w p(o|w)p(w)
    po = pogw * pw

    #add some small value to prevent NaNs in the KL-terms
    po += eps()
    po /= sum(po)

    #compute p(a)
    #p(a) = ∑_w,o p(w)p(o|w)p(a|o,w)
    #compute p(a|w)
    pagw = marginalizeo(pogw, pagow)
    #p(a) = ∑_w p(a|w)p(w)
    pa = pagw * pw

    #add some small value to prevent NaNs in the KL-terms
    pa += eps()
    pa /= sum(pa)

    return po, pa, pagw
end

function marginalizeo(pogw::Matrix, pagow)
    card_a = size(pagow,1)
    card_w = size(pogw,2)

    #compute p(a|w)
    pagw = zeros(card_a,card_w)

    for j in 1:card_w
        #p(a|w) = ∑_o p(o|w)p(a|o,w)
        pagw[:,j] = pagow[:,:,j] * pogw[:,j]
    end

    return pagw
end

function compute_pago_iteration(pogw::Matrix, pagow, β2, β3,
                                U_pre::Matrix, pa::Vector, po::Vector, pw::Vector)

    card_a = size(U_pre,1)
    card_w = size(U_pre,2)
    card_o = size(pogw,1)

    pago = zeros(card_a,card_o)

    for k in 1:card_o
        #compute p(w|o=k)
        pwgo_k = vec(pogw[k,:]).*pw / po[k]

        #compute p(a|o=k)
        if(β3==0)
            #sequential case
            #p(a|o) ∝ p(a)exp( β2 ∑_w p(w|o)U(a,w) )
            pago[:,k] = boltzmanndist(pa, β2, U_pre*pwgo_k)
        else
            #general case
            #p(a|o) = ∑_w p(w|o)p(a|o,w)   with p(w|o) = p(o|w)p(w)/p(o)
            pago[:,k] = squeeze(pagow[:,k,:],2) * pwgo_k

            pago[:,k] += eps() #add some small value to prevent NaNs in the KL-terms
            pago[:,k] = pago[:,k] / sum(pago[:,k])
        end
    end

    return pago

end

function compute_pagow_iteration(pago::Matrix, β2, β3, U_pre::Matrix, pa::Vector)

    card_a = size(U_pre,1)
    card_w = size(U_pre,2)
    card_o = size(pago,2)

    pagow = zeros(card_a,card_o,card_w)

    for j in 1:card_w
        if(β3==0)
            #sequential case: p(a|o,w)=p(a|o) ∀w
            #save some computation by implementing this directly rather than evaluating the
            #expression for the general case
            pagow[:,:,j] = pago
        else
            #general case
            #p(a|o,w) ∝ p(a|o) exp( β3 U(a,w) - β3/β2 log(p(a|o)/p(a)) )
            for k in 1:card_o
                pagow_util_kj = U_pre[:,j] - (1/β2)*log_bits(pago[:,k]./pa)
                pagow[:,k,j] = boltzmanndist(vec(pago[:,k]), β3, pagow_util_kj)
            end
        end
    end

    return pagow

end

function compute_pogw_iteration(pago::Matrix, pagow, β1, β2, β3, U_pre::Matrix, pa::Vector, po::Vector)

    card_w = size(U_pre,2)
    card_o = size(po,1)

    pogw = zeros(card_o,card_w)

    #------- compute the conditional EU and the two KL terms -----------#
    EU_cond = zeros(card_o,card_w)
    DKL_a = zeros(card_o,card_w)
    DKL_ago = zeros(card_o,card_w)

    for j in 1:card_w
        for k in 1:card_o
            if(β3==0)
                #sequential case
                EU_cond[k,j] = (pago[:,k]' * U_pre[:,j])[1]  # E[U(a,w=j) = ∑_a p(a|o=k) U(a,w=j)
                                                                #use the (...)[1] syntax to get a scalar
                DKL_a[k,j] = kl_divergence_bits(vec(pago[:,k]),pa)

            else
                EU_cond[k,j] = (pagow[:,k,j]' * U_pre[:,j])[1]  # E[U(a,w=j) = ∑_a p(a|o=k,w=j) U(a,w=j)
                                                                #use the (...)[1] syntax to get a scalar
                DKL_a[k,j] = kl_divergence_bits(vec(pagow[:,k,j]),pa)
            end
            DKL_ago[k,j] = kl_divergence_bits(vec(pagow[:,k,j]),vec(pago[:,k]))
        end
    end

    if(β3==0)
        #sequential case
        #we have β3=0 which would lead to ∞*0=NaN in the computation for pogw_util
        #DKL_ago must be zero in the sequential case, therefore β3 does not matter

        #check that KL is really zero
        if(sum(DKL_ago) > 0)
            error("Sequential case: D_KL( p(a|o,w)||p(a|o) )=$(sum(DKL_ago)) is nonzero which violates sequential case assumption!")
        end

        #p(o|w) ∝ exp( β1 E[U] - 1/β2 DKL(p(a|o)||p(a)) )
        pogw_util = EU_cond - 1/β2 * DKL_a
    else
        #general case
        #p(o|w) ∝ p(o) exp( β1 (E[U] - 1/β2 DKL(p(a|o,w)||p(a))) - β1 DKL(p(a|o,w)||p(a|o)) (1/β3-1/β2) )
        pogw_util = EU_cond - 1/β2 * DKL_a - ( (1/β3)-(1/β2) ) * DKL_ago
    end

    #comptue p(o|w)
    for j in 1:card_w
        pogw[:,j] = boltzmanndist(po, β1, vec(pogw_util[:,j]))
    end

    return pogw
end

#This function performs Blahut-Arimoto iterations for the three-variable general case
#and initializes p(o|w) and p(a|o,w) either uniformly or randomly, depending on the value
#of the optional argument 'init_uniform'
function threevarBAiterations(cardinality_obs::Integer, β1, β2, β3, U_pre::Matrix, pw::Vector,
                              ε_conv::Real, maxiter::Integer; compute_performance::Bool=false,
                              performance_per_iteration::Bool=false, performance_as_dataframe::Bool=false,
                              init_pogw_uniformly::Bool=false, init_pogw_sparse::Bool=true,
                              init_pagow_uniformly::Bool=true)

    num_acts = size(U_pre,1)
    num_worldstates = size(U_pre,2)
    num_obs = cardinality_obs

    if init_pogw_uniformly && init_pogw_sparse
        warn("Cannot initialize p(o|w) uniformly and sparse at the same time - choosing uniform initialization.")
        init_pogw_sparse = false
    end

    #initialize p(o|w)
    if init_pogw_uniformly
        #init uniform
        p_ogw_init = ones(cardinality_obs, num_worldstates)
    elseif init_pogw_sparse
        #init with a sparse, diagonal pattern (maximizing H(O) - inspired by autoencoder pre-training)
        if(num_worldstates<=num_obs)
            p_ogw_init = eye(num_obs,num_worldstates)
        else
            for b in num_obs:num_obs:num_worldstates
                if b==num_obs
                    p_ogw_init = eye(num_obs, num_obs)
                else
                    p_ogw_init = [p_ogw_init eye(num_obs,b-size(p_ogw_init,2))]
                end
            end
            extra = num_worldstates - size(p_ogw_init,2)
            if extra>0
                p_ogw_init = [p_ogw_init eye(num_obs,extra)]
            end
        end
        #make sure that all elements are nonzero
        p_ogw_init += rand(size(p_ogw_init)) * 0.01  #this factor depends on the number of rows in p_ogw_init...
    else
        #init random
        p_ogw_init = rand(cardinality_obs, num_worldstates)
    end

    #initialize p(a|o,w)
    if init_pagow_uniformly
        #uniform initialization
        p_agow_init = ones(num_acts, cardinality_obs, num_worldstates)
    else
        #random initialization
        p_agow_init = rand(num_acts, cardinality_obs, num_worldstates)
    end

    #normalize
    for j in 1:num_worldstates
        p_ogw_init[:,j] /=  sum(p_ogw_init[:,j])
        for k in 1:cardinality_obs
            p_agow_init[:,:,j] /= sum(p_agow_init[:,:,j])
        end
    end

    #------- Blahut-Arimoto call --------#
    #Blahut-Arimoto iterations for the three-variable general case
    return threevarBAiterations(p_ogw_init, p_agow_init, β1, β2, β3, U_pre, pw, ε_conv, maxiter,
                                compute_performance=compute_performance,
                                performance_per_iteration=performance_per_iteration,
                                performance_as_dataframe=performance_as_dataframe)

end

#This function performs Blahut-Arimoto iterations for the three-variable general case
function threevarBAiterations(pogw_init::Matrix, pagow_init, β1, β2, β3,
                              U_pre::Matrix, pw::Vector, ε_conv::Real, maxiter::Integer;
                              compute_performance::Bool=false, performance_per_iteration::Bool=false,
                              performance_as_dataframe::Bool=false)

    card_a = size(U_pre,1)
    card_w = size(U_pre,2)
    card_o = size(pogw_init,1)

    pogw_new = pogw_init
    pagow_new = pagow_init

    #Initialize the marginals consistent with the conditionals
    po_new, pa_new, pagw = compute_marginals(pw, pogw_init, pagow_init)

    pago_new = compute_pago_iteration(pogw_init, pagow_init, β2, β3, U_pre, pa_new, po_new, pw)
    if(β3==0)
        #sequential case - make sure that D_KL( p(a|o,w)||p(a|o) ) = 0
        pagow_new = compute_pagow_iteration(pago_new, β2, β3, U_pre, pa_new)
    end

    #if performance measures don't need to be returned, don't compute them per iteration
    if compute_performance==false
        performance_per_iteration = false
    end

    #preallocate if necessary
    if performance_per_iteration
        I_ow_i = zeros(maxiter)  #I(O;W)
        I_ao_i = zeros(maxiter)  #I(A;O)
        I_awgo_i = zeros(maxiter)  #I(A;W|O)
        I_aw_i = zeros(maxiter) #I(A;W)
        Ha_i = zeros(maxiter)  #H(A)
        Ho_i = zeros(maxiter)  #H(O)
        Hago_i = zeros(maxiter)  #H(A|O)
        Hogw_i = zeros(maxiter)  #H(O|W)
        Hagow_i = zeros(maxiter)  #H(A|O,W)
        Hagw_i = zeros(maxiter) #H(A|W)
        EU_i = zeros(maxiter)
        ThreeVarRDobj_i = zeros(maxiter)
    end

    if(β3 == 0)
        sequential_case = true
    else
        sequential_case = false
    end

    #main iteration
    iter = 0 #initialize counter, so it persists beyond the loop
    for iter in 1:maxiter
        pa = pa_new
        po = po_new
        pago = pago_new
        pagow = pagow_new
        pogw = pogw_new

        #
        #figure out best or correct order of equations
        #make sure to use old distributions in curr. iteration and update all of them after every iteration
        #check the sequential case by reproducing the MEU solution
        #   perhaps it needs "layer-wise" initialization? If so, why?

        #compute p(o|w)
        pogw_new = compute_pogw_iteration(pago, pagow, β1, β2, β3, U_pre, pa, po)

        #compute p(a|o,w) and p(a|o)
        if(β3==0)
            #sequential case - compute p(a|o) first and then p(a|o,w)
            pago_new = compute_pago_iteration(pogw, pagow, β2, β3, U_pre, pa, po, pw)
            pagow_new = compute_pagow_iteration(pago_new, β2, β3, U_pre, pa)
        else
            #general case - compute p(a|o,w) first and then p(a|o)
            pagow_new = compute_pagow_iteration(pago, β2, β3, U_pre, pa)
            pago_new = compute_pago_iteration(pogw, pagow_new, β2, β3, U_pre, pa, po, pw)
        end

        #update the marginals p(o), p(a), p(a|w)
        po_new, pa_new, pagw = compute_marginals(pw, pogw_new, pagow_new)

        #compute entropic quantities (if requested with additional parameter)
        if performance_per_iteration
            I_ow_i[iter], I_ao_i[iter], I_awgo_i[iter], I_aw_i[iter],
            Ho_i[iter], Ha_i[iter], Hogw_i[iter], Hago_i[iter],
            Hagow_i[iter], Hagw_i[iter],
            EU_i[iter], ThreeVarRDobj_i[iter] = analyze_three_var_BAsolution(pw, po_new, pa_new, pogw_new, pago_new,
                                                                             pagow_new, pagw, U_pre, β1, β2, β3)
        end

        #check for convergence
        #if (norm(pa-pa_new) + norm(po-po_new)) < ε_conv
        if (norm(pagow[:]-pagow_new[:]) + norm(pogw[:]-pogw_new[:])) < ε_conv
            break
        end

    end

    #check if iteration limit has been reached (before convergence)
    if iter == maxiter
        warn("[Three variable BAiterations] maximum iteration reached - returning... (results might be inaccurate)")
    end

    #return results
    if compute_performance == false
        return po_new, pa_new, pogw_new, pago_new, pagow_new, pagw
    else
        if performance_per_iteration == false
            #compute performance measures for final solution
            I_ow, I_ao, I_awgo, I_aw, Ho, Ha, Hogw, Hago, Hagow, Hagw, EU, ThreeVarRDobj = analyze_three_var_BAsolution(pw, po_new,
                                                                                pa_new, pogw_new, pago_new, pagow_new, pagw,
                                                                                U_pre, β1, β2, β3)
        else
            #"cut" valid results from preallocated vector
            I_ow = I_ow_i[1:iter]
            I_ao = I_ao_i[1:iter]
            I_awgo = I_awgo_i[1:iter]
            I_aw = I_aw_i[1:iter]
            Ho = Ho_i[1:iter]
            Ha = Ha_i[1:iter]
            Hogw = Hogw_i[1:iter]
            Hago = Hago_i[1:iter]
            Hagow = Hagow_i[1:iter]
            Hagw = Hagw_i[1:iter]
            EU = EU_i[1:iter]
            ThreeVarRDobj = ThreeVarRDobj_i[1:iter]
        end

        #if needed, transform to data frame
        if performance_as_dataframe == false
            return po_new, pa_new, pogw_new, pago_new, pagow_new, pagw, I_ow, I_ao, I_awgo, I_aw, Ho, Ha, Hogw, Hago, Hagow, Hagw, EU, ThreeVarRDobj
        else
            performance_df = performancemeasures2DataFrame(I_ow, I_ao, I_awgo, I_aw, Ho, Ha, Hogw, Hago, Hagow, Hagw, EU, ThreeVarRDobj)
            return po_new, pa_new, pogw_new, pago_new, pagow_new, pagw, performance_df
        end
    end

end


# Smile Tutorial 1: Creating a Bayesian Network
# this is the Julia version for the tutorial at
# https://dslpitt.org/genie/wiki/SMILE_Tutorial_1:_Creating_a_Bayesian_Network

using Smile

net = Network()
success = add_node(net, DSL_CPT, "success")
forecast = add_node(net, DSL_CPT, "forecast")

nodeA = get_node(net, success)
nodeDefA = definition(nodeA)
nodeValA = value(nodeA)

somenames = IdArray()
add(somenames, "Success")
add(somenames, "Failure")
set_number_of_outcomes( nodeDefA, somenames )

flush(somenames)
add(somenames, "Good")
add(somenames, "Moderate")
add(somenames, "Poor")
set_number_of_outcomes( definition(get_node(net, forecast)), somenames )

add_arc(net, success, forecast)

theprobs = DoubleArray()
set_size(theprobs, 2)
set_at(theprobs, 0, 0.2)
set_at(theprobs, 1, 1.8)
set_definition(definition(get_node(net, success)), theprobs)

thecoordinates = SysCoordinates(definition(get_node(net, forecast)))

write_file(net, "output.xdsl")


function collatz_steps(n)
    n < 1 && throw(DomainError("Invalid number"))
    n == 1 && return 0
    iseven(n) ? 1 + collatz_steps(div(n, 2)) : 1 + collatz_steps(3n + 1)
end


################################################################################
################################################################################
"""
    struct AngleAxis{T} <: Rotation{3,T}
    AngleAxis(Θ, x, y, z)

A 3×3 rotation matrix parameterized by a 3D rotation by angle θ about an
arbitrary axis `[x, y, z]`.

Note that the axis is not unique for θ = 0, and that this parameterization does
not continuously map the neighbourhood of the identity rotation (and therefore
might not be suitable for autodifferentation and optimization purposes).

Note: by default, the constructor will renormalize the input so that the axis
has length 1 (x² + y² + z² = 1).

Renormalization can be skipped by passing `false` as an additional constructor
argument, in which case the user provides the guarantee that the input arguments
represent a normalized rotation axis. Operations on an `AngleAxis` with a rotation
axis that does not have unit norm, created by skipping renormalization in this fashion,
are not guaranteed to do anything sensible.
"""
struct AngleAxis{T} <: Rotation{3,T}
    theta::T
    axis_x::T
    axis_y::T
    axis_z::T

    @inline function AngleAxis{T}(θ, x, y, z, normalize::Bool = true) where {T}
        if normalize
            # Not sure what to do with theta?? Should it become theta * norm ?
            norm = sqrt(x*x + y*y + z*z)
            new(θ, x/norm, y/norm, z/norm)
        else
            new(θ, x, y, z)
        end
    end
end

params(aa::AngleAxis) = SVector{4}(aa.theta, aa.axis_x, aa.axis_y, aa.axis_z)

# StaticArrays will take over *all* the constructors and put everything in a tuple...
# but this isn't quite what we mean when we have 4 inputs (not 9).
@inline function AngleAxis(θ::Θ, x::X, y::Y, z::Z, normalize::Bool = true) where {Θ,X,Y,Z}
    AngleAxis{promote_type(promote_type(promote_type(Θ, X), Y), Z)}(θ, x, y, z, normalize)
end

# These functions are enough to satisfy the entire StaticArrays interface:
@inline (::Type{AA})(t::NTuple{9}) where {AA <: AngleAxis} = AA(QuatRotation(t)) # TODO: consider going directly from tuple (RotMatrix) to AngleAxis
@inline Base.getindex(aa::AngleAxis, i::Int) = QuatRotation(aa)[i]

@inline function Base.Tuple(aa::AngleAxis{T}) where T
    # Rodrigues' rotation formula.
    s, c = sincos(aa.theta)
    c1 = one(T) - c

    c1x2 = c1 * aa.axis_x^2
    c1y2 = c1 * aa.axis_y^2
    c1z2 = c1 * aa.axis_z^2

    c1xy = c1 * aa.axis_x * aa.axis_y
    c1xz = c1 * aa.axis_x * aa.axis_z
    c1yz = c1 * aa.axis_y * aa.axis_z

    sx = s * aa.axis_x
    sy = s * aa.axis_y
    sz = s * aa.axis_z

    # Note that the RotMatrix constructor argument order makes this look transposed:
    (one(T) - c1y2 - c1z2, c1xy + sz, c1xz - sy,
        c1xy - sz, one(T) - c1x2 - c1z2, c1yz + sx,
        c1xz + sy, c1yz - sx, one(T) - c1x2 - c1y2)
end

@inline function (::Type{Q})(aa::AngleAxis) where Q <: QuatRotation
    s, c = sincos(aa.theta / 2)
    return Q(c, s * aa.axis_x, s * aa.axis_y, s * aa.axis_z, false)
end

@inline function (::Type{AA})(q::QuatRotation) where AA <: AngleAxis
    w = q.q.s
    x = q.q.v1
    y = q.q.v2
    z = q.q.v3
    s2 = x * x + y * y + z * z
    sin_t2 = sqrt(s2)
    theta = 2 * atan(sin_t2, w)
    num_pert = eps(typeof(theta))^4
    inv_sin_t2 = 1 / (sin_t2 + num_pert)
    return principal_value(AA(theta, inv_sin_t2 * (x + num_pert), inv_sin_t2 * y, inv_sin_t2 * z, false))
end

# Trivial type conversions for RotX, RotY and RotZ
@inline function (::Type{AA})(r::RotX) where AA <: AngleAxis
    return AA(r.theta, 1, 0, 0)
end
@inline function (::Type{AA})(r::RotY) where AA <: AngleAxis
    return AA(r.theta, 0, 1, 0)
end
@inline function (::Type{AA})(r::RotZ) where AA <: AngleAxis
    return AA(r.theta, 0, 0, 1)
end

# Using Rodrigues formula on an AngleAxis parameterization (assume unit axis length) to do the rotation
# (implementation from: https://ceres-solver.googlesource.com/ceres-solver/+/1.10.0/include/ceres/rotation.h)
function Base.:*(aa::AngleAxis, v::StaticVector)
    if length(v) != 3
        throw("Dimension mismatch: cannot rotate a vector of length $(length(v))")
    end

    w = rotation_axis(aa)
    st, ct = sincos(aa.theta)
    w_cross_pt = cross(w, v)
    m = dot(v, w) * (one(w_cross_pt[1]) - ct)
    T = promote_type(eltype(aa), eltype(v))
    return similar_type(v,T)(v[1] * ct + w_cross_pt[1] * st + w[1] * m,
                             v[2] * ct + w_cross_pt[2] * st + w[2] * m,
                             v[3] * ct + w_cross_pt[3] * st + w[3] * m)
end

@inline Base.:*(aa::AngleAxis, r::Rotation) = QuatRotation(aa) * r
@inline Base.:*(aa::AngleAxis, r::RotMatrix) = QuatRotation(aa) * r
@inline Base.:*(aa::AngleAxis, r::MRP) = QuatRotation(aa) * r
@inline Base.:*(r::Rotation, aa::AngleAxis) = r * QuatRotation(aa)
@inline Base.:*(r::RotMatrix, aa::AngleAxis) = r * QuatRotation(aa)
@inline Base.:*(r::MRP, aa::AngleAxis) = r * QuatRotation(aa)
@inline Base.:*(aa1::AngleAxis, aa2::AngleAxis) = QuatRotation(aa1) * QuatRotation(aa2)

@inline Base.inv(aa::AngleAxis) = AngleAxis(-aa.theta, aa.axis_x, aa.axis_y, aa.axis_z)
@inline Base.:^(aa::AngleAxis, t::Real) = AngleAxis(aa.theta*t, aa.axis_x, aa.axis_y, aa.axis_z)
@inline Base.:^(aa::AngleAxis, t::Integer) = AngleAxis(aa.theta*t, aa.axis_x, aa.axis_y, aa.axis_z) # to avoid ambiguity

# define identity rotations for convenience
@inline Base.one(::Type{AngleAxis}) = AngleAxis(0.0, 1.0, 0.0, 0.0)
@inline Base.one(::Type{AngleAxis{T}}) where {T} = AngleAxis{T}(zero(T), one(T), zero(T), zero(T))

# accessors
@inline rotation_angle(aa::AngleAxis) = aa.theta #  - floor((aa.theta+pi) / (2*pi)) * 2*pi
@inline rotation_axis(aa::AngleAxis) = SVector(aa.axis_x, aa.axis_y, aa.axis_z)

################################################################################
################################################################################
"""
    struct RotationVec{T} <: Rotation{3,T}
    RotationVec(sx, sy, sz)

Rodrigues vector parameterization of a 3×3 rotation matrix. The direction of the
vector [sx, sy, sz] defines the axis of rotation, and the rotation angle is
given by its norm.
"""
struct RotationVec{T} <: Rotation{3,T}
    sx::T
    sy::T
    sz::T
end

params(aa::RotationVec) = SVector{3}(aa.sx, aa.sy, aa.sz)

# StaticArrays will take over *all* the constructors and put everything in a tuple...
# but this isn't quite what we mean when we have 4 inputs (not 9).
@inline RotationVec(x::X, y::Y, z::Z) where {X,Y,Z} = RotationVec{promote_type(promote_type(X, Y), Z)}(x, y, z)

# These functions are enough to satisfy the entire StaticArrays interface:
@inline (::Type{RV})(t::NTuple{9}) where {RV <: RotationVec} = RV(QuatRotation(t)) # TODO: go through AngleAxis once it's faster
@inline Base.getindex(aa::RotationVec, i::Int) = QuatRotation(aa)[i]
@inline Base.Tuple(rv::RotationVec) = Tuple(QuatRotation(rv))

function (::Type{AA})(rv::RotationVec) where AA <: AngleAxis
    # TODO: consider how to deal with derivative near theta = 0. There should be a first-order expansion here.
    theta = rotation_angle(rv)
    return theta > 0 ? AA(theta, rv.sx / theta, rv.sy / theta, rv.sz / theta, false) : AA(zero(theta), one(theta), zero(theta), zero(theta), false)
end

function (::Type{RV})(aa::AngleAxis) where RV <: RotationVec
    return RV(aa.theta * aa.axis_x, aa.theta * aa.axis_y, aa.theta * aa.axis_z)
end

function (::Type{Q})(rv::RotationVec) where Q <: QuatRotation
    return QuatRotation(AngleAxis(rv))
end

(::Type{RV})(q::QuatRotation) where {RV <: RotationVec} = RV(AngleAxis(q))

function Base.:*(rv::RotationVec{T1}, v::StaticVector{3, T2}) where {T1,T2}
    theta = rotation_angle(rv)
    if (theta > eps(T1)) # use eps here because we have the 1st order series expansion defined
        return AngleAxis(rv) * v
    else
        return similar_type(typeof(v), promote_type(T1,T2))(
                    v[1] + rv.sy * v[3] - rv.sz * v[2],
                    v[2] + rv.sz * v[1] - rv.sx * v[3],
                    v[3] + rv.sx * v[2] - rv.sy * v[1])
    end
end

@inline Base.:*(rv::RotationVec, r::Rotation) = QuatRotation(rv) * r
@inline Base.:*(rv::RotationVec, r::RotMatrix) = QuatRotation(rv) * r
@inline Base.:*(rv::RotationVec, r::MRP) = QuatRotation(rv) * r
@inline Base.:*(rv::RotationVec, r::AngleAxis) = QuatRotation(rv) * r
@inline Base.:*(r::Rotation, rv::RotationVec) = r * QuatRotation(rv)
@inline Base.:*(r::RotMatrix, rv::RotationVec) = r * QuatRotation(rv)
@inline Base.:*(r::MRP, rv::RotationVec) = r * QuatRotation(rv)
@inline Base.:*(r::AngleAxis, rv::RotationVec) = r * QuatRotation(rv)
@inline Base.:*(rv1::RotationVec, rv2::RotationVec) = QuatRotation(rv1) * QuatRotation(rv2)

@inline Base.inv(rv::RotationVec) = RotationVec(-rv.sx, -rv.sy, -rv.sz)
@inline Base.:^(rv::RotationVec, t::Real) = RotationVec(rv.sx*t, rv.sy*t, rv.sz*t)
@inline Base.:^(rv::RotationVec, t::Integer) = RotationVec(rv.sx*t, rv.sy*t, rv.sz*t) # to avoid ambiguity

# rotation properties
@inline rotation_angle(rv::RotationVec) = sqrt(rv.sx * rv.sx + rv.sy * rv.sy + rv.sz * rv.sz)
function rotation_axis(rv::RotationVec)     # what should this return for theta = 0?
    theta = rotation_angle(rv)
    return (theta > 0 ? SVector(rv.sx / theta, rv.sy / theta, rv.sz / theta) : SVector(one(theta), zero(theta), zero(theta)))
end

# define identity rotations for convenience
@inline Base.one(::Type{RotationVec}) = RotationVec(0.0, 0.0, 0.0)
@inline Base.one(::Type{RotationVec{T}}) where {T} = RotationVec{T}(zero(T), zero(T), zero(T))


export fanin, fanout, Uniform, U
using Base: to_shape, DimOrInd

struct Uniform; min; max; end
Uniform(x)=Uniform(-x,x)
(u::Uniform)(x...) = rand(x...) * (u.max-u.min) .+ u.min
const 𝑼 = Uniform

struct Normal; mean; std; end
Normal(x)=Normal(0,x)
(n::Normal)(x...) = randn(x...) * n.std .+ n.mean
const 𝑵 = Normal

fanin(dims::Dims{1}; o...) = dims[1]
fanout(dims::Dims{1}; o...) = 1
fanin(dims::Dims{2}; o...) = dims[2]
fanout(dims::Dims{2}; o...) = dims[1]

# if w is (3,3,16,8) with default layout (CUDNN_TENSOR_NCHW)
# or w is (16,3,3,8) with channelmajor (CUDNN_TENSOR_NHWC)
# then there are 16 input channels and 8 output channels
# fanin = 3*3*16 = (3*3*16*8) ÷ 8
# fanout = 3*3*8 = (3*3*16*8) ÷ 16
fanin(dims::Dims{N}; channelmajor=false)  where N = prod(dims) ÷ dims[N]
fanout(dims::Dims{N}; channelmajor=false) where N = prod(dims) ÷ (channelmajor ? dims[1] : dims[N-1])

# one can call fanin/out with arrays, sizes etc:
fanin(a; o...)=fanin(size(a); o...)
fanin(dims::Tuple; o...)=fanin(to_shape(dims); o...)
fanin(dims::DimOrInd...; o...)=fanin(to_shape(dims); o...)
fanout(a; o...)=fanout(size(a); o...)
fanout(dims::Tuple; o...)=fanout(to_shape(dims); o...)
fanout(dims::DimOrInd...; o...)=fanout(to_shape(dims); o...)


# This program creates a simple 3D vel model for FD seismic modeling
#
# by GRAM | 8 Apr 2016

using Seismic

# def size
nx = 50
ny = 50
nz = 200
dz = 10 # depth increment
dx = 20
dy = 20

vel = zeros(Float32,nz,nx,ny)

# build model
for i = 1:nz, j = 1:nx, k = 1:ny
	if i < nz / 3
		vel[i,j,k] = 2000
	elseif ((i < 2 * nz / 3) & (i > nz / 3))
		vel[i,j,k] = 3000
	elseif ((i > (2 * nz / 3) - 25) & (i < (2 * nz / 3) + 25))
		vel[i,j,k] = 3500
	else
		vel[i,j,k] = 4000
	end
end

#= display
print(size(vel), "\n")
print(vel,"\n")
=#

#= write out text file for QC
out = open("../dat/vel_1.txt","w")
for k = 1:ny
	for i = 1:nz
		for j = 1:nx
			showcompact(out, vel[i,j,k])
			write(out," ")
		end
		write(out, "\r\n")
	end
	write(out, "\r\n")
end
close(out)
=#

# write out vel model for Seismic.jl
h = Array(Header,nx*ny)
ex = Seismic.Extent(convert(Int32,nz), convert(Int32,nx), convert(Int32,ny),
	1, 1, 0, 0, 0, 0, 0, convert(Float32,dz), convert(Float32,dx),
	convert(Float32,dy), 1, 1, "Depth", "mx", "my", "", "", "", "", "",
	"", "", "")

for i = 1 : ny
	for j = 1 : nx
		h[j + nx * (i - 1)] = Seismic.InitSeisHeader()
		h[j + nx * (i - 1)].tracenum = convert(Int32, j + nx * (i - 1))
		h[j + nx * (i - 1)].o1 = convert(Float32, 0)
		h[j + nx * (i - 1)].n1 = convert(Int32, nz)
		h[j + nx * (i - 1)].d1 = convert(Float32, dz)
		h[j + nx * (i - 1)].mx = convert(Float32, j)
		h[j + nx * (i - 1)].my = convert(Float32, i)
	end
end

SeisWrite("../dat/vel_incorrect",vel,h,ex)


# --------------------------------------------------------------------------
# ACE.jl and SHIPs.jl: Julia implementation of the Atomic Cluster Expansion
# Copyright (c) 2019 Christoph Ortner <christophortner0@gmail.com>
# Licensed under ASL - see ASL.md for terms and conditions.
# --------------------------------------------------------------------------

@testset "Ylm" begin

##
import ACE
using JuLIP.Testing
using LinearAlgebra, StaticArrays, BenchmarkTools, Test, Printf
using ACE.SphericalHarmonics
using ACE.SphericalHarmonics: dspher_to_dcart, PseudoSpherical,
               cart2spher, spher2cart
using JuLIP: evaluate, evaluate_d, evaluate_ed

verbose = false

function explicit_shs(θ, φ)
   Y00 = 0.5 * sqrt(1/π)
   Y1m1 = 0.5 * sqrt(3/(2*π))*sin(θ)*exp(-im*φ)
   Y10 = 0.5 * sqrt(3/π)*cos(θ)
   Y11 = -0.5 * sqrt(3/(2*π))*sin(θ)*exp(im*φ)
   Y2m2 = 0.25 * sqrt(15/(2*π))*sin(θ)^2*exp(-2*im*φ)
   Y2m1 = 0.5 * sqrt(15/(2*π))*sin(θ)*cos(θ)*exp(-im*φ)
   Y20 = 0.25 * sqrt(5/π)*(3*cos(θ)^2 - 1)
   Y21 = -0.5 * sqrt(15/(2*π))*sin(θ)*cos(θ)*exp(im*φ)
   Y22 = 0.25 * sqrt(15/(2*π))*sin(θ)^2*exp(2*im*φ)
   Y3m3 = 1/8 * exp(-3 * im * φ) * sqrt(35/π) * sin(θ)^3
   Y3m2 = 1/4 * exp(-2 * im * φ) * sqrt(105/(2*π)) * cos(θ) * sin(θ)^2
   Y3m1 = 1/8 * exp(-im * φ) * sqrt(21/π) * (-1 + 5 * cos(θ)^2) * sin(θ)
   Y30 = 1/4 * sqrt(7/π) * (-3 * cos(θ) + 5 * cos(θ)^3)
   Y31 = -(1/8) * exp(im * φ) * sqrt(21/π) * (-1 + 5 * cos(θ)^2) * sin(θ)
   Y32 = 1/4 * exp(2 * im * φ) * sqrt(105/(2*π)) * cos(θ) * sin(θ)^2
   Y33 = -(1/8) * exp(3 * im * φ) * sqrt(35/π) * sin(θ)^3
   return [Y00, Y1m1, Y10, Y11, Y2m2, Y2m1, Y20, Y21, Y22,
           Y3m3, Y3m2, Y3m1, Y30, Y31, Y32, Y33]
end

##

@info("Test: check complex spherical harmonics against explicit expressions")
nsamples = 30
for n = 1:nsamples
   θ = rand() * π
   φ = (rand()-0.5) * 2*π
   r = 0.1+rand()
   R = SVector(r*sin(θ)*cos(φ), r*sin(θ)*sin(φ), r*cos(θ))
   SH = SHBasis(3)
   Y = evaluate(SH, R)
   Yex = explicit_shs(θ, φ)
   print_tf((@test Y ≈ Yex))
end
println()

##
@info("      ... same near pole")
nsamples = 30
for n = 1:nsamples
   θ = rand() * 1e-9
   if θ < 1e-10
      θ = 0.0
   end
   φ = (rand()-0.5) * 2*π
   r = 0.1+rand()
   R = SVector(r*sin(θ)*cos(φ), r*sin(θ)*sin(φ), r*cos(θ))
   SH = SHBasis(3)
   Y = evaluate(SH, R)
   Yex = explicit_shs(θ, φ)
   print_tf((@test Y ≈ Yex || norm(Y - Yes, Inf) < 1e-12))
end
println()
##

verbose=false
@info("Test: check derivatives of associated legendre polynomials")
for nsamples = 1:30
   θ = 0.1+0.4 * pi * rand()
   L = 5
   P = ACE.SphericalHarmonics.compute_p(L, θ)
   P1, dP = ACE.SphericalHarmonics.compute_dp(L, θ)
   # -------------
   P_eq_P1 = true
   for l = 0:L, m = 0:l
      i = ACE.SphericalHarmonics.index_p(l, m)
      if ((m == 0) && !(P[i] ≈ P1[i])) || ((m > 0) && !(P[i] ≈ P1[i] * sin(θ)))
         P_eq_P1 = false; break;
      end
   end
   print_tf(@test P_eq_P1)
   # -------------
   errs = []
   verbose && @printf("     h    | error \n")
   for p = 2:10
      h = 0.1^p
      dPh = (ACE.SphericalHarmonics.compute_p(L, θ+h) - P) / h
      push!(errs, norm(dP - dPh, Inf))
      verbose && @printf(" %.2e | %.2e \n", h, errs[end])
   end
   success = (/(extrema(errs)...) < 1e-3) || (minimum(errs) < 1e-10)
   print_tf(@test success)
end
println()

##

@info("      ... same near pole")
for nsamples = 1:30
   θ = rand() * 1e-8
   L = 5
   P = ACE.SphericalHarmonics.compute_p(L, θ)
   _, dP = ACE.SphericalHarmonics.compute_dp(L, θ)
   errs = []
   verbose && @printf("     h    | error \n")
   for p = 2:10
      h = 0.1^p
      dPh = (ACE.SphericalHarmonics.compute_p(L, θ+h) - P) / h
      push!(errs, norm(dP - dPh, Inf))
      verbose && @printf(" %.2e | %.2e \n", h, errs[end])
   end
   success = (/(extrema(errs)...) < 1e-3) || (minimum(errs) < 1e-10)
   print_tf(@test success)
end
println()

##

@info("Test : spher-cart conversion")
for nsamples = 1:30
   R = (rand(3) .- 0.5) * (1+rand())
   print_tf((@test R ≈ spher2cart(cart2spher(R))))
end
println()

##

@info("Test : spher-cart jacobian")
φθ(S::PseudoSpherical) = [atan(S.sinφ, S.cosφ), atan(S.sinθ, S.cosθ)]
φθ(R::AbstractVector) = φθ(cart2spher(R))
EE = [ [1,0,0], [0,1,0], [0,0,1] ]
h = 1e-5
for nsamples = 1:30
   R = rand(3)
   S = cart2spher(R)
   dR_dS = [ dspher_to_dcart(S, 1.0/S.sinθ, 0.0) dspher_to_dcart(S, 0.0, 1.0) ]
   dR_dS_h = hcat( (φθ(R+h*EE[1])-φθ(R-h*EE[1])) / (2*h),
                   (φθ(R+h*EE[2])-φθ(R-h*EE[2])) / (2*h),
                   (φθ(R+h*EE[3])-φθ(R-h*EE[3])) / (2*h) )'
   print_tf((@test norm(dR_dS - dR_dS_h, Inf) < 1e-5))
end
println()

##

@info("Test: check derivatives of complex spherical harmonics")
for nsamples = 1:30
   R = @SVector rand(3)
   SH = SHBasis(5)
   Y, dY = evaluate_ed(SH, R)
   DY = Matrix(transpose(hcat(dY...)))
   errs = []
   verbose && @printf("     h    | error \n")
   for p = 2:10
      h = 0.1^p
      DYh = similar(DY)
      Rh = Vector(R)
      for i = 1:3
         Rh[i] += h
         DYh[:, i] = (evaluate(SH, SVector(Rh...)) - Y) / h
         Rh[i] -= h
      end
      push!(errs, norm(DY - DYh, Inf))
      verbose && @printf(" %.2e | %.2e \n", h, errs[end])
   end
   success = (/(extrema(errs)...) < 1e-3) || (minimum(errs) < 1e-10)
   print_tf(@test success)
end
println()

##

# another test, which we could add but isn't really necessary
# R = SVector(0.589275, 0.468925, 0.00509384)
# # R /= norm(R)
# u = SVector(0.318475, 0.734832, 0.598829)
# SH = SHBasis(5)
# Y, dY = evaluate_ed(SH, R)
# dY_u = dot.(Ref(u), dY)
# errs = []
# @printf("     h    | error \n")
# for p = 2:10
#    h = 0.1^p
#    dYh_u = (evaluate(SH, R + h * u) - Y) / h
#    push!(errs, norm(dY_u - dYh_u, Inf))
#    @printf(" %.2e | %.2e \n", h, errs[end])
# end

##

end # @testset


# find distance between two vectors

using JuMP

function convertFloatToInt64(x,digits=2)
  # given a vec, normalize it, convert to an interger
  x = x/sum(x);
  M = 10^digits;
  x = round(x*10^digits);
  x = vec(map(tmp -> convert(Int64,round(tmp)),x));
  return x,M;
end

function getMinCostDistance(x,y,w,wext,verbose=true)
  # lables
  # x: source or supply
  # y: sink or demand
  n = length(x);
  if(n!=length(y))
    error("two vectors must of same size")
  end

  if((size(w)[1]!=size(w)[2]) | size(w)[1]!=n)
    error("cost matrix must be square")
  end
  # convert the vecs to integers
  x,M = convertFloatToInt64(x,2);
  y,M = convertFloatToInt64(y,2);

  # create exraneous source and sink nodes
  # in the case where supply is not equal to demand
  dif = sum(x)-sum(y);
  if(dif>0)
    # if supply is more, create extraneous demand node
    push!(x,0);
    push!(y,dif);
  else
    # if demand is more, create extraneous supply node
    push!(x,-dif);
    push!(y,0);
  end
  dif = sum(x)-sum(y);
  println("x: ",x)
  println("y: ",y)
  @assert dif == 0;

  N = n+1;
  M = sum(x);
  cost = Array{Int64,2}(zeros(N,N));
  # cost metric for given vec
  cost[1:n,1:n] = w;
  # cost to reach from ext.source to any sink node
  cost[N,1:n] = wext;
  # cost to reach ext.sink to any source node
  cost[1:n,N] = wext;

  mdl = Model();

  # flow is non-negative
  @defVar(mdl, flow[i=1:N, j=1:N] >= 0);
  # objective function:
  # minimize the overall flow from source to sink
  @setObjective(mdl, Min, sum{cost[i,j] * flow[i,j], i=1:N, j=1:N});
  # constraint: net out flow at source node should be equal its specified supply
  @addConstraint(mdl, xyconstr[i=1:1:N], sum{flow[i,j], j=1:N} == x[i]);
    # constraint: net out flow at source node should be equal its specified demand
  @addConstraint(mdl, xyconstr[j = 1:N], sum{flow[i,j], i=1:N} == y[j]);

  if(verbose)
    println("Solving original problem...")
  end
  status = solve(mdl);

  if status == :Optimal
    d = getObjectiveValue(mdl)/M/maximum(cost);
    flow = getValue(flow)
    if(verbose)
      @printf("Optimal!\n");
      @printf("Objective value: %d\n", d);
      @printf("Source:\n")
      println(x);
      @printf("Target:\n")
      println(y);
      @printf("Flow:\n");
      for j = 1:n
        @printf("\t%s", string("d",j));
      end
      @printf("\t%s", string("d.ext"));
      @printf("\n");
      for i = 1:n
        @printf("%s", string("s",i));
        for j = 1:N
          @printf("\t%d", (flow[i,j]));
        end
        @printf("\n");
      end
      @printf("%s", string("s.ext"));
      for j = 1:N
        @printf("\t%d", (flow[N,j]));
      end
      @printf("\n");
    end
  else
    d = [];
    if(verbose)
      @printf("No solution\n");
    end
  end
  return status, d, mdl, flow,cost;
end

x1 = [1 0 0 1 200]
x2 = [1 1 1 1 0]
x3 = [1 0 1 1 1]

w = [0 1 2 3 4;1 0 1 2 3;2 1 0 1 2;3 2 1 0 1;4 3 2 1 0]
wext = [1 1 1 1 1]
println("Source: ",x)
println("Target: ",y)
status, d1, mdl, flow, cost = getMinCostDistance(x1,x2,w,wext,false);
println("dist: ",d1)
status, d2, mdl, flow, cost = getMinCostDistance(x2,x3,w,wext,false);
println("dist: ",d2)
status, d3, mdl, flow, cost = getMinCostDistance(x1,x3,w,wext,false);
println("dist: ",d3)
println("triangle: ",[d1+d2,d3])

#


import Base
using DataStructures

abstract type ShibaObject end
abstract type Func <: ShibaObject end

const NullableRealValueType = Union{Real,AbstractArray{<:Real},Nothing}
const NullableFuncType = Union{Func,Nothing}

mutable struct Variable <: ShibaObject
    data::NullableRealValueType
    creator::NullableFuncType
    grad::Union{Variable,AbstractArray{Variable},Nothing}
    generation::Int
    name::String
end

const NullableVariableArray = Union{AbstractArray{Variable},Nothing}

macro variable(var)
    if var isa Symbol
        name = string(var)
        esc(:($var = Variable($var, nothing, nothing, 0, $name)))
    elseif (var isa Expr) && (var.head == :(=))
        res = eval(var)
        name = string(var.args[1])
        esc(:($(var.args[1]) = Variable($res, nothing, nothing, 0, $name)))
    else
        error("ignore format.")
    end
end

macro DeepShiba_Func(ex)
    pushfirst!(ex.args[3].args, :(generation::Int))
    pushfirst!(ex.args[3].args, :(outputs::NullableVariableArray))
    pushfirst!(ex.args[3].args, :(inputs::NullableVariableArray))
    return esc(ex)
end

@DeepShiba_Func mutable struct Add <: Func end

@DeepShiba_Func mutable struct Sub <: Func end

@DeepShiba_Func mutable struct Neg <: Func end

@DeepShiba_Func mutable struct Mul <: Func end

@DeepShiba_Func mutable struct Div <: Func end

@DeepShiba_Func mutable struct Pow <: Func
    c::NullableRealValueType
end

_Add(x1, x2) =  Add([x1, x2], nothing, 0)(x1, x2)

_Sub(x1, x2) = Sub([x1, x2], nothing, 0)(x1, x2)

_Neg(x) = Neg([x, ], nothing, 0)(x)

_Mul(x1, x2) = Mul([x1, x2], nothing, 0)(x1, x2)

_Div(x1, x2) = Div([x1, x2], nothing, 0)(x1, x2)

_Pow(x, c) = Pow([x, ], nothing, 0, c)(x)

@inline forward(f::Add, x1, x2)  = x1 + x2
@inline forward(f::Sub, x1, x2) = x1 - x2
@inline forward(f::Neg, x) = -x
@inline forward(f::Mul, x1, x2) = x1 * x2
@inline forward(f::Div, x1, x2) = x1 / x2
@inline forward(f::Pow, x) = x^(f.c)

function backward(f::Add)::AbstractArray{Variable}
    gys = get_gys(f.outputs)
    return [1, 1] .* gys
end

function backward(f::Sub)::AbstractArray{Variable}
    gys = get_gys(f.outputs)
    return [1, -1] .* gys
end

function backward(f::Neg)::AbstractArray{Variable}
    gys = get_gys(f.outputs)
    return -gys
end

function backward(f::Mul)::AbstractArray{Variable}
    gys = get_gys(f.outputs)
    x1, x2 = f.inputs
    return [x2, x1] .* gys
end

function backward(f::Div)::AbstractArray{Variable}
    gys = get_gys(f.outputs)
    x1, x2 = f.inputs
    return [1 / x2, -x1 / (x2^2)] .* gys
end

function backward(f::Pow)::AbstractArray{Variable}
    gys = get_gys(f.outputs)
    x, c = f.inputs[1], f.c
    return [c * (x^(c - 1)),] .* gys
end

Base.:+(x1::Variable, x2::Variable) = _Add(x1, x2)
Base.:+(x1::Variable, x2) = _Add(x1, variable(x2))
Base.:+(x1, x2::Variable) = _Add(variable(x1), x2)

Base.:-(x1::Variable, x2::Variable) = _Sub(x1, x2)
Base.:-(x1::Variable, x2) = _Sub(x1, variable(x2))
Base.:-(x1, x2::Variable) = _Sub(variable(x1), x2)

Base.:*(x1::Variable, x2::Variable) = _Mul(x1, x2)
Base.:*(x1::Variable, x2) = _Mul(x1, variable(x2))
Base.:*(x1, x2::Variable) = _Mul(variable(x1), x2)

Base.:/(x1::Variable, x2::Variable) = _Div(x1, x2)
Base.:/(x1::Variable, x2) = _Div(x1, variable(x2))
Base.:/(x1, x2::Variable) = _Div(variable(x1), x2)

Base.:-(x::Variable) = _Neg(x)

Base.:^(x1::Variable, x2::Variable) = _Pow(x1, x2.data)
Base.:^(x1::Variable, x2) = _Pow(x1, x2)
Base.:^(x1, x2::Variable) = _Pow(variable(x1), x2.data)

Base.log(x::Variable) = _Log(x)
Base.size(x::Variable) = size(x.data)
Base.iterate(x::Variable) = iterate(x.data)


"""
    cubic(box::Array{<:Real,2},r::Number)

Make a lattice with cubic structure.

# Arguments

 - **box** (Array) 3D Box to be filled with spheres of radius r. e.g. [[0,1],[1,2],[0,4]]
 - **r** (Number) Radius of the sphere

# Returns
Community with the spheres
"""
function cubic(box::Array{<:Real,2},r::Number)

    d = size(box)
    if d != (3,2)
        error("Incompatible number of dimensions ", d)
    end

    #Make first dimension
    lineX = Array(box[1,1]:2*r:box[1,2])

    #Make second dimension
    lineY = fill(box[2,1]+r,length(lineX))
    nY = ceil(INT,(box[2,2]-box[2,1])/(2*r))
    areaX = zeros(FLOAT,1)
    areaY = zeros(FLOAT,1)
    dx = 2*r
    dy = 2*r
    for i in 0:nY-1
        append!(areaX,lineX)
        append!(areaY,lineY.+dy*i)
    end

    #Make third dimension
    areaZ = fill(box[2,1]+r,length(areaX))
    nZ = ceil(INT,(box[3,2]-box[3,1])/(2*r))
    volumeX = zeros(FLOAT,1)
    volumeY = zeros(FLOAT,1)
    volumeZ = zeros(FLOAT,1)
    dy = 2*r
    dz = 2*r
    for i in 0:nZ-1
        append!(volumeX,areaX)
        append!(volumeY,areaY)
        append!(volumeZ,areaZ.+dz*i)
    end

    #Return
    return [volumeX volumeY volumeZ]

end


using MCMCBenchmarkSuite, Test, Random

@testset "Regression Tests " begin
    Random.seed!(22385)
    path = pathof(MCMCBenchmarkSuite)
    include(joinpath(path,
      "../Linear_Regression/Linear_Regression_Models.jl"))
    Nd = 50
    β0 = 1
    β = fill(.5,1)
    σ = 1
    Nreps = 1
    ProjDir = @__DIR__
    cd(ProjDir)
    samplers = (
      CmdStanNUTS(CmdStanConfig,ProjDir),
      AHMCNUTS(AHMCregression,AHMCconfig),
      DHMCNUTS(sampleDHMC))
    options = (Nsamples=2000, Nadapt=1000, delta=.8, Nd=Nd)
    results = benchmark(samplers, simulateRegression, Nreps;options...)
    @test isa(results, DataFrame)
    # @test results[!,Symbol("B[1]_mean")][results[!,:sampler] .== :AHMCNUTS,:][1] ≈ β[1] atol = .1
    # @test results[!,Symbol("B[1]_mean")][results[!,:sampler] .== :CmdStanNUTS,:][1] ≈ β[1] atol = .1
    # @test results[!,Symbol("B[1]_mean")][results[!,:sampler] .== :DHMCNUTS,:][1] ≈ β[1] atol = .1
    # @test results[!,:B0_mean][results[!,:sampler] .== :AHMCNUTS,:][1] ≈ β0[1] atol = .1
    # @test results[!,:B0_mean][results[!,:sampler] .== :CmdStanNUTS,:][1] ≈ β0[1] atol = .1
    # @test results[!,:B0_mean][results[!,:sampler] .== :DHMCNUTS,:][1] ≈ β0[1] atol = .1
    # @test results[!,:sigma_mean][results[!,:sampler] .== :AHMCNUTS,:][1] ≈ σ atol = .1
    # @test results[!,:sigma_mean][results[!,:sampler] .== :CmdStanNUTS,:][1] ≈ σ atol = .1
    # @test results[!,:sigma_mean][results[!,:sampler] .== :DHMCNUTS,:][1] ≈ σ atol = .1
    # @test results[!,Symbol("B[1]_sampler_rhat")][1] ≈ 1 atol = .03
    # @test results[!,:B0_sampler_rhat][1] ≈ 1 atol = .03
    # @test results[!,:sigma_sampler_rhat][1] ≈ 1 atol = .03
    isdir("tmp") && rm("tmp", recursive=true)
end


using SModels
using ScikitLearn
using ScikitLearn.GridSearch: GridSearchCV
using JLD, PyCallJLD
@sk_import neural_network: MLPClassifier
@sk_import neural_network: MLPRegressor
@sk_import preprocessing: StandardScaler
@sk_import metrics: accuracy_score

@static if VERSION < v"0.7.0-DEV.2005"
    using Base.Test
else
    using Test
end

srand(1234)

# This line should fail:
# @test 1 == 2

# Can we reproduce a basic example?
@testset "Testing Examples ScikitLearn.jl" begin

    X = [[0., 0.], [1., 1.]]
    y = [0, 1]

    clf = MLPClassifier(solver="lbfgs", alpha=1e-5,
                     hidden_layer_sizes=(5, 2), random_state=1)

    fit!(clf, X, y)

    res = predict(clf, [[2., 2.], [-1., -2.]])

    @test res[1] == 1
    @test res[2] == 0

    # Saving and loading
    #-------------------
    JLD.save("clf.jld", "clf", clf)
    clfloaded = JLD.load("clf.jld", "clf")

    res = predict(clfloaded, [[2., 2.], [-1., -2.]])

    @test res[1] == 1
    @test res[2] == 0

end

@testset "Testing generic functions" begin

    # 2d->2d function we aim at fitting
    function trueFunction(x::Float64, y::Float64)
        [x^2 + y^2, exp(x)*cos(y)]
    end

    function trueFunction(z::Array{Float64,1})
        trueFunction(z[1], z[2])
    end

    # Function that fails  when z[2] > 0.5
    # to test if the package is able to handle functions
    # that may fail
    function trueFunctionRisk(z::Array{Float64,1})
        if z[2] < 0.5
            return trueFunction(z[1], z[2])
        else
            error("Failure of trueFunctionRisk.")
        end
    end

    # create an input matrix
    function createX(;dimInput = 2, lowerBoundX = -1.0, lowerBoundY = -1.0, upperBoundX = 1.0, upperBoundY = 1.0, nbPointsX = 10, nbPointsY = 10)

        # initialization
        X = Array{Array{Float64,1},1}(nbPointsX*nbPointsY)

        for (kIndex, tupleValue) in enumerate(collect(Iterators.product(linspace(lowerBoundX, upperBoundX, nbPointsX), linspace(lowerBoundY, upperBoundY, nbPointsY))))
            X[kIndex] = [tupleValue[1], tupleValue[2]]
        end

        return X

    end

    # create output matrix
    function createY(X::Array{Array{Float64,1},1}; dimOutput = 2)

        # initialization
        y = Array{Array{Float64,1},1}(size(X,1))

        # Looping over X
        for xIndex = 1:size(X,1)
            y[xIndex] = trueFunction(X[xIndex][1], X[xIndex][2])
        end

        return y

    end

    # create output matrix
    # penalty value when trueFunctionRisk fails
    function createYRisk(X::Array{Array{Float64,1},1}; dimOutput::Int64 = 2, penaltyValue::Float64 = 999.0)

        # initialization
        y = Array{Array{Float64,1},1}(size(X,1))

        # Looping over X
        for xIndex = 1:size(X,1)
          if X[xIndex][2] < 0.5
            y[xIndex] = trueFunction(X[xIndex][1], X[xIndex][2])
          else
            y[xIndex] = ones(dimOutput)*penaltyValue
          end

        end

        return y

    end

    @testset "Testing fitting a 2d->2d function" begin

        aTolMeanPerError = 0.05
        aTolMaxPerError = 1.0
        aTolScaled = 0.001

        #input
        X = createX()
        #ouptut
        y = createY(X)

        # splitting between train and test samples
        XTrain, XTest, yTrain, yTest = split_train_test(X, y, testRatio = false)

        # scaling
        scaler = StandardScaler()
        fit!(scaler, XTrain)
        XTrainScaled = transform(scaler, XTrain)
        XTestScaled = transform(scaler, XTest)

        # training network
        clfr = MLPRegressor(solver="lbfgs", alpha=1e-5, hidden_layer_sizes=(30, 30), random_state=1)
        fit!(clfr, XTrainScaled, yTrain)

        # Train set
        #----------
        yPredicted = predict(clfr, XTrainScaled)

        println("Mean Percentage Error Train Set = $(calculate_mean_per_error(yTrain, yPredicted))")
        println("Maximum Abs Percentage Error Train Set = $(calculate_maximum_abs_per_error(yTrain, yPredicted))")
        @test abs(calculate_mean_per_error(yTrain, yPredicted)) < aTolMeanPerError
        @test abs(calculate_maximum_abs_per_error(yTrain, yPredicted)) < aTolMaxPerError

        # Test set
        #---------
        yPredicted = predict(clfr, XTestScaled)

        println("Mean Percentage Error Test Set = $(calculate_mean_per_error(yTest, yPredicted))")
        println("Maximum Abs Percentage Error Test Set = $(calculate_maximum_abs_per_error(yTest, yPredicted))")
        @test abs(calculate_mean_per_error(yTest, yPredicted)) < aTolMeanPerError
        @test abs(calculate_maximum_abs_per_error(yTest, yPredicted)) < aTolMaxPerError

        yPredictedScaled = predict_one_obs_scaled(clfr, XTrainScaled[1,:])
        yPredictedUnscaled = predict_one_obs_unscaled(clfr, XTrain[1,:], scaler)

        #scaled versus unscaled observation
        @test yPredictedScaled ≈ yPredictedUnscaled atol = aTolScaled

    end

    #cross-validation to choose the hidden-layer
    #-------------------------------------------------
    @testset "Testing fitting a 2d->2d function tuning hyper-parameters" begin

        aTolMeanPerError = 0.05
        aTolMaxPerError = 2.5
        aTolScaled = 0.001

        #input
        X = createX()
        #ouptut
        y = createY(X)

        # splitting between train and test samples
        XTrain, XTest, yTrain, yTest = split_train_test(X, y, testRatio = false)

        # scaling
        scaler = StandardScaler()
        fit!(scaler, XTrain)
        XTrainScaled = transform(scaler, XTrain)
        XTestScaled = transform(scaler, XTest)

        gridsearch = GridSearchCV(MLPRegressor(solver="lbfgs", alpha=1e-5, random_state=1), Dict(:hidden_layer_sizes => ((10), (20), (30), (40), (10, 10), (10, 20), (10, 30), (20, 10), (20, 20), (20, 30), (30, 10), (30, 20), (30, 30))))
        fit!(gridsearch, X, y)
        println("Best hyper-parameters: $(gridsearch.best_params_)")

        # training network using the "best" hidden layer
        #-----------------------------------------------
        clfr = MLPRegressor(solver="lbfgs", alpha=1e-5, hidden_layer_sizes=gridsearch.best_params_[:hidden_layer_sizes], random_state=1)
        fit!(clfr, XTrainScaled, yTrain)

        # Train set
        #----------
        yPredicted = predict(clfr, XTrainScaled)

        println("Mean Percentage Error Train Set = $(calculate_mean_per_error(yTrain, yPredicted))")
        println("Maximum Abs Percentage Error Train Set = $(calculate_maximum_abs_per_error(yTrain, yPredicted))")
        @test abs(calculate_mean_per_error(yTrain, yPredicted)) < aTolMeanPerError
        @test abs(calculate_maximum_abs_per_error(yTrain, yPredicted)) < aTolMaxPerError

        # Test set
        #---------
        yPredicted = predict(clfr, XTestScaled)

        println("Mean Percentage Error Test Set = $(calculate_mean_per_error(yTest, yPredicted))")
        println("Maximum Abs Percentage Error Test Set = $(calculate_maximum_abs_per_error(yTest, yPredicted))")
        @test abs(calculate_mean_per_error(yTest, yPredicted)) < aTolMeanPerError
        @test abs(calculate_maximum_abs_per_error(yTest, yPredicted)) < aTolMaxPerError

        yPredictedScaled = predict_one_obs_scaled(clfr, XTrainScaled[1,:])
        yPredictedUnscaled = predict_one_obs_unscaled(clfr, XTrain[1,:], scaler)

        #scaled versus unscaled observation
        @test yPredictedScaled ≈ yPredictedUnscaled atol = aTolScaled

    end

    #cross-validation to choose the hidden-layer done "manually"
    #-----------------------------------------------------------
    @testset "Testing fitting a 2d->2d function manually tuning hyper-parameters" begin

        aTolMeanPerError = 0.05
        aTolMaxPerError = 2.5
        aTolScaled = 0.001

        #input
        X = createX()
        #ouptut
        y = createY(X)

        # splitting between train and test samples
        XTrain, XTest, yTrain, yTest = split_train_test(X, y, testRatio = false)

        # scaling
        scaler = StandardScaler()
        fit!(scaler, XTrain)
        XTrainScaled = transform(scaler, XTrain)
        XTestScaled = transform(scaler, XTest)

        d = Dict(:hidden_layer_sizes => ((10), (20), (30), (40), (10, 10), (10, 20), (10, 30), (20, 10), (20, 20), (20, 30), (30, 10), (30, 20), (30, 30)))
        bestMaxPerError = Inf
        bestParam = d[:hidden_layer_sizes][1]

        # Loop over hidden layers configuration
        # Looking fot the hidden layer configuration that minimizes the max error in the test set
        for (kIndex, kValue) in enumerate(d[:hidden_layer_sizes])

            println(kValue)
            clfr = MLPRegressor(solver="lbfgs", alpha=1e-5, hidden_layer_sizes = kValue, random_state=1)
            fit!(clfr, XTrainScaled, yTrain)

            yPredicted = predict(clfr, XTestScaled)
            maxPerErr = calculate_maximum_abs_per_error(yTest, yPredicted)

            if maxPerErr < bestMaxPerError
                bestMaxPerError = maxPerErr
                bestParam = kValue
            end

        end

        println("Best hyper-parameters: $(bestParam)")

        # training network using the "best" hidden layer
        #-----------------------------------------------
        clfr = MLPRegressor(solver="lbfgs", alpha=1e-5, hidden_layer_sizes=bestParam, random_state=1)
        fit!(clfr, XTrainScaled, yTrain)

        # Train set
        #----------
        yPredicted = predict(clfr, XTrainScaled)

        println("Mean Percentage Error Train Set = $(calculate_mean_per_error(yTrain, yPredicted))")
        println("Maximum Ans Percentage Error Train Set = $(calculate_maximum_abs_per_error(yTrain, yPredicted))")
        @test abs(calculate_mean_per_error(yTrain, yPredicted)) < aTolMeanPerError
        @test abs(calculate_maximum_abs_per_error(yTrain, yPredicted)) < aTolMaxPerError

        # Test set
        #---------
        yPredicted = predict(clfr, XTestScaled)

        println("Mean Percentage Error Test Set = $(calculate_mean_per_error(yTest, yPredicted))")
        println("Maximum Abs Percentage Error Test Set = $(calculate_maximum_abs_per_error(yTest, yPredicted))")
        @test abs(calculate_mean_per_error(yTest, yPredicted)) < aTolMeanPerError
        @test abs(calculate_maximum_abs_per_error(yTest, yPredicted)) < aTolMaxPerError

        yPredictedScaled = predict_one_obs_scaled(clfr, XTrainScaled[1,:])
        yPredictedUnscaled = predict_one_obs_unscaled(clfr, XTrain[1,:], scaler)

        #scaled versus unscaled observation
        @test yPredictedScaled ≈ yPredictedUnscaled atol = aTolScaled

    end

    #cross-validation to choose the hidden-layer done "manually"
    #-----------------------------------------------------------
    @testset "Testing package on 2d->1d function" begin

        function function_1d(z::Array{Float64,1})
          return cos(z[1])*exp(z[2])
        end

        # create output matrix
        function create_y_1d(X::Array{Array{Float64,1},1})

            y = zeros(size(X,1))

            # Looping over X
            for xIndex = 1:size(X,1)
                y[xIndex] = function_1d([X[xIndex][1], X[xIndex][2]])
            end

            return y
        end

        aTolMeanPerError = 0.05
        aTolMedianPerError = 0.05
        aTolMaxPerError = 0.5

        upperBoundX = [1.0; 1.0]
        lowerBoundX = [-1.0; -1.0]

        opts = SModelsOptions(sModelType = :MLPRegressor, classifierType = :MLPClassifier, batchSizeWorker = 10, desiredMinObs = 80)

        surrogatePb = SModelsProblem(lowerBound = lowerBoundX, #lower bound for the parameter space
                          upperBound = upperBoundX,                   #upper bound for the parameter space
                          dimX = 2,                #dimension of the input parameter
                          dimY = 1,                 #dimension of the output vector
                          options = opts)

        set_model_function!(surrogatePb, function_1d)

        # training the surrogate model
        surrogatem, classifier = train_surrogate_model(surrogatePb, verbose = true, saveToDisk = false, robust = true)

        #input
        X = createX()
        #ouptut
        y = create_y_1d(X)

        # non-robust:
        XScaled = transform(surrogatePb.scaler, X)
        yPredicted = predict(surrogatem, XScaled)
        # classifier:
        yConvergencePredicted = round.(Int64, predict(classifier, XScaled))

        println("Regressor:")
        println("Mean Percentage Different Set = $(calculate_mean_per_error(y, yPredicted))")
        println("Median Abs Percentage Different Set = $(calculate_median_abs_per_error(y, yPredicted))")
        println("Maximum Abs Percentage Error Different Set = $(calculate_maximum_abs_per_error(y, yPredicted))")

        @test calculate_mean_per_error(y, yPredicted) < aTolMeanPerError
        @test calculate_median_abs_per_error(y, yPredicted) < aTolMedianPerError
        @test calculate_maximum_abs_per_error(y, yPredicted) < aTolMaxPerError
        # Model never fails. The classsifier should predict this feature:
        @test sum(yConvergencePredicted) == length(yConvergencePredicted)

    end

    #cross-validation to choose the hidden-layer done "manually"
    #-----------------------------------------------------------
    @testset "Testing package on 2d->2d function" begin

        aTolMeanPerError = 0.05
        aTolMaxPerError = 1.2
        aTolScaled = 0.001

        upperBoundX = [1.0; 1.0]
        lowerBoundX = [-1.0; -1.0]

        opts = SModelsOptions(sModelType = :MLPRegressor, classifierType = :MLPClassifier, batchSizeWorker = 100, desiredMinObs = 100)

        surrogatePb = SModelsProblem(
                          lowerBound = lowerBoundX, #lower bound for the parameter space
                          upperBound = upperBoundX,                   #upper bound for the parameter space
                          dimX = 2,                #dimension of the input parameter
                          dimY = 2,                 #dimension of the output vector
                          options = opts)

        set_model_function!(surrogatePb, trueFunction)

        # training the surrogate model
        surrogatem, classifier = train_surrogate_model(surrogatePb, verbose = true, saveToDisk = false, robust = false)

        #input
        X = createX()
        #ouptut
        y = createY(X)

        # non-robust
        XScaled = transform(surrogatePb.scaler, X)
        yPredicted = predict(surrogatem, XScaled)

        # robust to model failures
        yPredictedRobust = predict_scaled_robust(surrogatem, classifier, XScaled,
                            nonConvergenceFlag = surrogatePb.options.nonConvergenceFlag,
                            convergenceFlag = surrogatePb.options.convergenceFlag,
                            penaltyValue = surrogatePb.options.penaltyValue)

        println("Regressor:")
        println("Mean Percentage Different Set = $(calculate_mean_per_error(y, yPredicted))")
        println("Maximum Abs Percentage Error Different Set = $(calculate_maximum_abs_per_error(y, yPredicted))")

        # Here the model cannot fail, so we should have the same results
        #---------------------------------------------------------------
        @test yPredicted == yPredictedRobust

    end

    @testset "Testing package on 2d->2d function that may fail" begin

        aTolMeanPerError = 0.05
        aTolMaxPerError = 1.2
        aTolScaled = 0.001

        upperBoundX = [1.0; 1.0]
        lowerBoundX = [-1.0; -1.0]

        # Need more observations to learn where the model fails
        # When the model fails, the output is a large positive value
        # This creates a large discontinuity
        opts = SModelsOptions(sModelType = :MLPRegressor, classifierType = :MLPClassifier,
                              nbBatches = 5, batchSizeWorker = 100,
                              desiredMinObs = 500, penaltyValue = 9.0)

        surrogatePb = SModelsProblem(    #function f:x -> y that we are trying to approximate
                          lowerBound = lowerBoundX, #lower bound for the parameter space
                          upperBound = upperBoundX,                   #upper bound for the parameter space
                          dimX = 2,                #dimension of the input parameter
                          dimY = 2,                 #dimension of the output vector
                          options = opts)

        set_model_function!(surrogatePb, trueFunctionRisk)

        # training the surrogate model, non-robust method
        surrogatem, classifier = train_surrogate_model(surrogatePb, verbose = true, robust = false)

        # training the surrogate model, robust method
        surrogatemRobust, classifierRobust = train_surrogate_model(surrogatePb, verbose = true, robust = true)

        # Test on a totally new sample:
        #-------------------------------
        #input
        X = createX()
        #ouptut
        y = createYRisk(X, penaltyValue = surrogatePb.options.penaltyValue)

        # non-robust: use normal scaler and ScikitLearn predict function
        #----------------------------------------------------------------
        XScaled = transform(surrogatePb.scaler, X)
        yPredicted = predict(surrogatem, XScaled)

        mean_per_error_non_robust = calculate_mean_per_error(y, yPredicted)
        median_per_error_non_robust = calculate_median_abs_per_error(y, yPredicted)
        max_per_error_non_robust = calculate_maximum_abs_per_error(y, yPredicted)

        # robust: use a different scaler
        #-------------------------------
        XScaledRobust = transform(surrogatePb.scalerRobust, X)
        # predict, but first check for convergence using a classifier:
        #-------------------------------------------------------------
        yPredictedRobust = predict_scaled_robust(surrogatemRobust, classifierRobust, XScaledRobust,
                                                nonConvergenceFlag = surrogatePb.options.nonConvergenceFlag,
                                                convergenceFlag = surrogatePb.options.convergenceFlag,
                                                penaltyValue = surrogatePb.options.penaltyValue)

        mean_per_error_robust = calculate_mean_per_error(y, yPredictedRobust)
        median_per_error_robust = calculate_median_abs_per_error(y, yPredictedRobust)
        max_per_error_robust = calculate_maximum_abs_per_error(y, yPredictedRobust)

        println("Regressor non-robust:")
        println("Mean Percentage Different Set = $(mean_per_error_non_robust)")
        println("Median Percentage Different Set = $(median_per_error_non_robust)")
        println("Maximum Abs Percentage Error Different Set = $(max_per_error_non_robust)")
        println("----------------------")
        println("Regressor robust:")
        println("Mean Percentage Different Set = $(mean_per_error_robust)")
        println("Median Percentage Different Set = $(median_per_error_robust)")
        println("Maximum Abs Percentage Error Different Set = $(max_per_error_robust)")

        # The robust option shoudl minimize the median percentage error:
        #---------------------------------------------------------------
        @test median_per_error_robust < median_per_error_non_robust

        # Not that the max percentage error is bigger with the non-robust option
        # This occurs because when the classifier makes a mistake : the penalty
        # value is wrongly returned.

    end

    @testset "Testing parallel capabilities" begin

        addprocs(3)

        listMeanPerErr = []
        listMaxPerErr = []

        for i = 1:3

            # with batchSizeWorker = 10
            #--------------------------
            @everywhere using SModels

            @everywhere upperBoundX = [1.0; 1.0]
            @everywhere lowerBoundX = [-1.0; -1.0]

            if i == 1
                @everywhere b = 10
            elseif i==2
                @everywhere b = 100
            else
                @everywhere b = 1000
            end
            @everywhere opts = SModelsOptions(sModelType = :MLPRegressor, batchSizeWorker = b, desiredMinObs = 20, nbBatches = 1)

            @everywhere surrogatePb = SModelsProblem(    #function f:x -> y that we are trying to approximate
                              lowerBound = lowerBoundX,  #lower bound for the parameter space
                              upperBound = upperBoundX,  #upper bound for the parameter space
                              dimX = 2,                  #dimension of the input parameter
                              dimY = 2,                  #dimension of the output vector
                              options = opts)

            # 2d->2d function we aim at fitting
            @everywhere function trueFunction(x::Float64, y::Float64)
                [x^2 + y^2, exp(x)*cos(y)]
            end

            @everywhere function trueFunction(z::Array{Float64,1})
                trueFunction(z[1], z[2])
            end

            set_model_function!(surrogatePb, trueFunction)

            # training the surrogate model
            surrogatem, classifier = train_surrogate_model(surrogatePb, verbose = true, saveToDisk = false, robust = false)

            #input
            X = createX()
            #ouptut
            y = createY(X)

            XScaled = transform(surrogatePb.scaler, X)

            yPredicted = predict(surrogatem, XScaled)

            append!(listMeanPerErr,calculate_mean_per_error(y, yPredicted))
            append!(listMaxPerErr, calculate_maximum_abs_per_error(y, yPredicted))

            println("Regressor:")
            println("Mean Percentage Different Set = $(calculate_mean_per_error(y, yPredicted))")
            println("Maximum Abs Percentage Error Different Set = $(calculate_maximum_abs_per_error(y, yPredicted))")

        end

        # More points should be converted to more precise surrogate models
        @test abs(listMeanPerErr[1]) > abs(listMeanPerErr[3])
        @test abs(listMaxPerErr[1]) > abs(listMaxPerErr[3])

    end

end


"""

Julia の Clustering.hclust() の分析結果を水平方向のデンドログラムとして描く。

StatsPlots パッケージの plot() は垂直方向のデンドログラムを描く。

"""

using Plots
function plot_hclust_horizontal(hc)
    function get(i, x)
        x[i] < 0 && return(ord[abs(x[i])])
        get(x[i], x)
    end
    n = length(hc.order)
    apy = collect(n:-1:1) .+ 0.5
    apx = zeros(n)
    ord = sortperm(hc.order)
    plot(yshowaxis=false, yticks=false, tick_direction=:out, grid=false,
         xlims=(-0.05, maximum(hc.height)), ylims=(0, apy[1]),
         xlabel="height", label="")
    for i in 1:n
        annotate!(0, apy[i], text(string(hc.labels[hc.order[i]]) * " ", 8, :right))
    end
    for i in 1:n-1
        c1 = get(i, hc.merge[:,1])
        c2 = get(i, hc.merge[:,2])
        plot!([apx[c1], hc.height[i], hc.height[i], apx[c2]],
              [apy[c1], apy[c1], apy[c2], apy[c2]], color=:black, label="")
        apx[c1] = apx[c2] = hc.height[i]
        apy[c1] = apy[c2] = (apy[c1] + apy[c2]) / 2
    end
    plot!()
end

"""
使用法

julia> using RDatasets

julia> iris = RDatasets.dataset("datasets", "iris");

julia> function distancematrix(X)
           nr,  nc = size(X)
           d = zeros(nr, nr)
           for i in 1:nr-1
               for j in i+1:nr
                   d[i, j] = d[j, i] = sqrt(sum((X[i, :] .- X[j, :]).^2))
               end
           end
           d
       end;

julia> # using Random; Random.seed!(123) # 毎回同じ結果にするためには乱数の種を設定する
       using Clustering

julia> x = Matrix(iris[1:20, 1:4]);

julia> D = distancematrix(x);

julia> hc = hclust(D, linkage=:ward);

julia> plot_hclust_horizontal(hc)

"""


using Plots
pgfplots()
include("../src/Plotting.jl")

x = read_HFSS_pattern("test/data/lens.csv")

sphericalpattern(x)
polarpattern(x[ϕ(At(90))],lims=(-20,20))

phi = 0:360
theta = 0:360

freq = 38u"GHz"
lens_diameter = 15.8u"mm"
zero = 0u"mm"
gap = 0.1u"mm"
positions = [(-(lens_diameter + gap),zero,zero),
             (-(lens_diameter + gap) / 2,-√(3) * (lens_diameter + gap) / 2,zero),
             ((lens_diameter + gap) / 2,-√(3) * (lens_diameter + gap) / 2,zero),
             (lens_diameter + gap,zero,zero),
             ((lens_diameter + gap) / 2,√(3) * (lens_diameter + gap) / 2,zero),
             (-(lens_diameter + gap) / 2,√(3) * (lens_diameter + gap) / 2,zero),
             (zero,zero,zero)]

af = ArrayFactor(positions,[1,1,1,1,1,1,1])

plot(af,legend=false)

pattern = [af(p,t,freq) for p ∈ phi, t ∈ theta]

buh = RadiationPattern(pattern,phi,theta)

r = ustrip(gain(buh[ϕ(At(0))]))
theta = deg2rad.(Array(buh.dims[2].val))

p = polarpattern(theta,r,lims=(-30,10))

r1 = cos.(theta)
r2 = 2*cos.(theta)

plot(theta,[r1 r2],proj=:polar)


"""
A module which provides common astrodynamics
calculations.

# Extended help

**Exports**

$(EXPORTS)

**Imports**

$(IMPORTS)
"""
module AstrodynamicalCalculations

export Circular, Elliptical, Parabolic, Hyperbolic
export conic, keplerian, cartesian
export perifocal, semimajor_axis
export specific_angular_momentum_vector, specific_angular_momentum
export specific_energy, C3, v_infinity, specific_potential_energy
export eccentricity_vector, eccentricity, semi_parameter
export distance, speed, periapsis_radius, apoapsis_radius, period
export true_anomoly, mean_motion, time_since_periapsis
export hohmann, SOI, SOA, normalize, redimension

using DocStringExtensions
using Unitful
using Requires
using Contour
using StaticArrays
using AstroTime
using LinearAlgebra

import Dates

@template (FUNCTIONS, METHODS, MACROS) =
    """
    $(SIGNATURES)
    $(DOCSTRING)
    """

@template (TYPES, CONSTANTS) =
    """
    $(TYPEDEF)
    $(DOCSTRING)
    """

Unitful.@derived_dimension MassParameter Unitful.𝐋^3/Unitful.𝐓^2

@doc """
A unit dimension alias for length^3 / time^2. This is a common
dimension used in astrodynamics calculations.
"""
MassParameter

include(joinpath("R2BP",  "Conics.jl"))
include(joinpath("R2BP",  "R2BPCalculations.jl"))
include(joinpath("CR3BP", "CR3BPCalculations.jl"))

function __init__()
    @require OrbitalStates="b3d87722-feb5-45df-affe-eeab3e3c6ba5" include(joinpath(@__DIR__, "Hooks", "OrbitalStates", "OrbitalStates.jl"))
end

end # module


include("vec3.jl")
import .V3: Vec3, at_len

using LinearAlgebra
using Test

# ==
@test Vec3([1, 2, 3]) == Vec3([1, 2, 3])
@test Vec3([1, 2, 3]) != Vec3([10, 2, 3])
# +
@test Vec3([1, 2, 3]) + Vec3([4, 5, 6]) == Vec3([5, 7, 9])
@test Vec3([1, 2, 3]) + Vec3([4, 5, 6]) != Vec3([6, 8, 9])
# -
@test Vec3([9, 8, 7]) - Vec3([4, 5, 6]) == Vec3([5, 3, 1])
@test Vec3([9, 8, 7]) - Vec3([4, 5, 6]) != Vec3([2, 3, 1])
# - (unary)
@test -Vec3([9, 8, 7]) == Vec3([-9, -8, -7])
@test -Vec3([9, 8, 7]) != Vec3([-2, -8, -7])
# *
@test Vec3([9, 8, 7]) * 2 == Vec3([18, 16, 14])
@test Vec3([9, 8, 7]) * 3 != Vec3([18, 16, 14])
# /
@test Vec3([9, 12, 15]) / 3 == Vec3([3, 4, 5])
@test Vec3([9, 12, 15]) / 2 != Vec3([3, 4, 5])
# ⋅
@test Vec3([7, 6, 5]) ⋅ Vec3([1, 2, 3]) == 34
@test Vec3([7, 6, 5]) ⋅ Vec3([1, 2, 3]) != 35
# ×
@test Vec3([1, 0, 0]) × Vec3([0, 1, 0]) == Vec3([0, 0, 1])
@test Vec3([1, 0, 0]) × Vec3([0, -1, 0]) != Vec3([0, 0, 1])
# norm
@test norm(Vec3([9, 12, 112])) == 113
@test norm(Vec3([9, 2, 112])) != 113
# ≈
@test Vec3([3, 4, 5]) ≈ Vec3([3.00000001, 4.00000001, 5.00000001])
@test Vec3([3, 4, 5]) ≉ Vec3([3.0000001, 4.0000001, 5.0000001])
# normalize
@test normalize(Vec3([3, 4, 5])) ≈ Vec3([0.42426407, 0.56568543, 0.70710678])
@test normalize(Vec3([3, 4, 5])) ≉ Vec3([0.4242647, 0.5656853, 0.7071068])
# at_len
@test at_len(Vec3([9, 12, 112]), 226) == Vec3([18, 24, 224])
@test at_len(Vec3([9, 12, 112]), 225) != Vec3([18, 24, 224])