#=
   Test of credible interval in Turing.jl

   This is a port of the WebPPL model in
   https://mhtess.github.io/bdappl/chapters/03-simpleModels.html
   """
   A simple generative process
   Imagine we are investigating the origins of prosocial behavior: Some scientists believe humans are
   born with a self-interested tendency and only acquire prosociality through instruction (perhaps mediated
   by language). Others believe altruism in innate; as soon as children are physically and cognitively
   able to help, they will. To investigate this, we are going to put pre-verbal infants in a context where
   a person is struggling to complete their goal (e.g., as in this classic study
   [https://www.youtube.com/watch?v=kfGAen6QiUE Toddler Altruism]).

   We plan to conduct this experiment on 20 infants. Each child may or may not help; what we are interested
   in is the population’s propensity to help. A simple generative model of this task would be that infants'
   propensity to help is like the bias of a coin, and whether or not they help in the moment is the
   result of flipping that coin.
   """

   credible_interval is defined in jl_utils.jl

   Output of the model:
"""
Summary Statistics
                parameters      mean       std   naive_se      mcse         ess      rhat   ess_per_sec
                    Symbol   Float64   Float64    Float64   Float64     Float64   Float64       Float64

         priorDistribution    0.4966    0.2861     0.0029    0.0054   3065.5383    1.0000     1283.7262
        propensity_to_help    0.7260    0.0937     0.0009    0.0013   5215.2559    0.9999     2183.9430
       posteriorPredictive   14.5179    2.6933     0.0269    0.0323   6745.5678    1.0000     2824.7771
  prior_propensity_to_help    0.4966    0.2861     0.0029    0.0054   3065.5383    1.0000     1283.7262
           priorPredictive    9.9341    6.0131     0.0601    0.1088   3370.6685    0.9999     1411.5027

Quantiles
                parameters      2.5%     25.0%     50.0%     75.0%     97.5%
                    Symbol   Float64   Float64   Float64   Float64   Float64

         priorDistribution    0.0203    0.2559    0.4953    0.7390    0.9736
        propensity_to_help    0.5260    0.6663    0.7309    0.7937    0.8867
       posteriorPredictive    9.0000   13.0000   15.0000   17.0000   19.0000
  prior_propensity_to_help    0.0203    0.2559    0.4953    0.7390    0.9736
           priorPredictive    0.0000    5.0000   10.0000   15.0000   20.0000

Credible interval for posteriorPredictive with mass 0.9: (10.000000..18.000000)
Credible interval for posteriorPredictive with mass 0.95: (9.000000..19.000000)
Credible interval for posteriorPredictive with mass 0.99: (8.000000..20.000000)
Credible interval for posteriorPredictive with mass 0.1: (13.000000..13.000000)
Credible interval for posteriorPredictive with mass 0.01: (9.000000..9.000000)
"""

=#

using Turing
using Random
include("jl_utils.jl")

@model function credible_interval_test(k=15,n=20)
    priorDistribution ~ Uniform(0,1)

    # propensity to help, or true population proportion who would help
    propensity_to_help ~ Uniform(0,1)

    # Observed k children who help
    # Assuming each child's response is independent of each other
    k ~ Binomial(n, propensity_to_help)

    # predict what the next n will do
    posteriorPredictive ~ Binomial(n, propensity_to_help)

    # duplicate model structure and parameters but omit observe
    prior_propensity_to_help ~ Dirac(priorDistribution)
    priorPredictive ~ Binomial(n, prior_propensity_to_help)

end

# observed data
k = 15 # number of children who help
n = 20 # total number of children
model = credible_interval_test(k,n)

# chns = sample(model, Prior(), 10_000)
# chns = sample(model, MH(), 10_000)
# chns = sample(model, MH(), 10_000)
# chns = sample(model, PG(15), 10_000)
# chns = sample(model, IS(), 10_000)
chns = sample(model, SMC(), 10_000)
# chns = sample(model, SMC(), MCMCThreads(), 10_000, 4)

display(chns)

credible_interval(chns, "posteriorPredictive",0.90)
credible_interval(chns, "posteriorPredictive",0.95)
credible_interval(chns, "posteriorPredictive",0.99)
credible_interval(chns, "posteriorPredictive",0.1)
credible_interval(chns, "posteriorPredictive",0.01)


# macro for argument checking (taken from Distributions.jl)

macro check_args(D, cond)
    quote
        if !($(esc(cond)))
            throw(ArgumentError(string(
                $(string(D)), ": the condition ", $(string(cond)), " is not satisfied.")))
        end
    end
end

"""
    BIC(fm::ContinuousUnivariateDistribution, data::Array{<:Real,1}; censoring::Real=0)

Calculates the Bayesian information criterion (BIC) of the specified model.

```math
\\textrm{BIC} = \\ln f_{(Y|\\hat{\\theta}_{MV})}(y) - \\frac{k}{2}\\ln{n}
```

"""
function BIC(fm::ContinuousUnivariateDistribution, data::Array{<:Real,1}; censoring::Real=0)
    n = size(data,1)
    k = size(params(fm),1)

    r_data = data[data .>= censoring]
    l_data = fill(censoring, count(data .< censoring))

    ll = sum(logcdf.(fm, l_data)) + sum(logpdf.(fm, r_data))

    return ll - k/2*log(n)
end

function BIC(fm::ContinuousUnivariateDistribution, data::TimeArray; censoring::Real=0)
    return BIC(fm, values(data), censoring=censoring)
end

function window(ts::TimeArray, year_from::Int64, year_to::Int64)
    return to(from(ts, DateTime(year_from, 01, 01)), DateTime(year_to-1, 12, 31))
end


include("../../module/CustomStencil.jl")
include("../../misc/misc.jl")
include("../../misc/cpu_stencils.jl")
## Star Stencil definition with radius = 2
coefs = Float64.([0.25; 0.125; 0.06])
dense_stencil = @def_stencil_expression(
            v*@sum(i,-2,2,
                @sum(j,-2,2,
                    @sum(k,-2,2, c[max(abs(i),abs(j),abs(k))]*D[x+i,y+j,z+k]))))
st_def = CreateStencilDefinition(dense_stencil, coefs)
st_inst = NewStencilInstance(st_def)
## Input Data size Definition
radius = 2
nx = 5
ny = 5
nz = 5
dx = 1<<(nx)
dy = 1<<(ny)
dz = 1<<(nz)

data = CreateData(dx,dy,dz)
vsc = CreateData(dx,dy,dz, init=0.5, mid=1, rng=5)

t_steps = 1
## GPU Implementation
gpu_out = ApplyStencil(st_inst, data, t_steps, vsq=vsc)

## CPU result
cpu_coefs = fill(0.06, 5,5,5)
cpu_coefs[2:4, 2:4, 2:4] .= 0.125
cpu_coefs[3,3,3] = 0.25
cpu_out = cpu_dense_stencil_vsc(data, vsc, radius, cpu_coefs, t_steps)

## Check
mean_er = sum(abs.(cpu_out .- gpu_out))./(reduce(*, size(gpu_out)))
@assert(mean_er < 0.001)

## Visualize Error
using Plots

shift = 0
Int(round(dz/2))+shift
heatmap(gpu_out[:,:,Int(round(dz/2))+shift]-cpu_out[:,:,Int(round(dz/2))+shift])
heatmap(cpu_out[:,:,Int(round(dz/2))+shift])
heatmap(gpu_out[:,:,Int(round(dz/2))+shift])


include("test_exports.jl")
include("test_static_clip.jl")
include("test_plane_tet_intersection.jl")
include("test_poly_eight.jl")
include("test_utility.jl")
include("test_quadrature.jl")


# ---
# title: 861. Score After Flipping Matrix
# id: problem861
# author: AquaIndigo
# date: 2020-12-07
# difficulty: Medium
# categories: Greedy
# link: <https://leetcode.com/problems/score-after-flipping-matrix/description/>
# hidden: true
# ---
#
# We have a two dimensional matrix `A` where each value is `0` or `1`.
#
# A move consists of choosing any row or column, and toggling each value in that
# row or column: changing all `0`s to `1`s, and all `1`s to `0`s.
#
# After making any number of moves, every row of this matrix is interpreted as a
# binary number, and the score of the matrix is the sum of these numbers.
#
# Return the highest possible score.
#
#
#
# **Example 1:**
#
#
#
#     Input: [[0,0,1,1],[1,0,1,0],[1,1,0,0]]
#     Output: 39
#     Explanation: Toggled to [[1,1,1,1],[1,0,0,1],[1,1,1,1]].
#     0b1111 + 0b1001 + 0b1111 = 15 + 9 + 15 = 39
#
#
#
# **Note:**
#
#   1. `1 <= A.length <= 20`
#   2. `1 <= A[0].length <= 20`
#   3. `A[i][j]` is `0` or `1`.
#
#
## @lc code=start
using LeetCode

function matrix_score(A::Vector{Vector{Int}})::Int
    for vec in A
        vec .⊻= 0 ⊻ vec[1]  ## the first column are all converted to 1
    end
    w, h, res = length(A[1]), length(A), 0
    ## res = h << (w - 1)
    for i in 1:w
        temp_cnt = 0
        for j in 1:h
            temp_cnt += A[j][i]
        end
        res += max(temp_cnt, h - temp_cnt) << (w - i)
    end
    return res
end
## @lc code=end


## test_spectral.jl : test spectral method for GRF generation

@testset "spectral decomposition   " begin

    ## 1d Mat\'ern ##
    cov = CovarianceFunction(1,Matern(0.3,1))
    pts = 0:0.01:1
    grf = GaussianRandomField(cov,Spectral(),pts)
    @test isa(grf,GaussianRandomField)
    @test isa(grf.cov,CovarianceFunction)
    @test isa(grf.cov.cov,Matern)
    @test ndims(grf.cov) == 1
    @test isa(grf,GaussianRandomField{Spectral})
    @test length(grf.pts[1]) == length(pts)
    @test length(sample(grf)) == length(pts)

    ## 2d Exponential ##
    cov = CovarianceFunction(2,Exponential(1))
    pts1 = 0:0.05:1
    pts2 = 0:0.05:1
    grf = GaussianRandomField(cov,Spectral(),pts1,pts2)
    @test isa(grf,GaussianRandomField)
    @test isa(grf.cov,CovarianceFunction)
    @test isa(grf.cov.cov,Exponential)
    @test ndims(grf.cov) == 2
    @test isa(grf,GaussianRandomField{Spectral})
    @test length(grf.pts) == 2
    @test length(grf.pts[1]) == length(pts1)
    @test length(grf.pts[2]) == length(pts2)
    @test size(sample(grf),1) == length(pts1)
    @test size(sample(grf),2) == length(pts2)

    ## 3d Gaussian ##
    cov = CovarianceFunction(3,Matern(1,2.5))
    pts1 = 0:0.1:1
    pts2 = 0:0.1:1
    pts3 = 0:0.1:1
    grf = GaussianRandomField(cov,Spectral(),pts1,pts2,pts3)
    @test isa(grf,GaussianRandomField)
    @test isa(grf.cov,CovarianceFunction)
    @test isa(grf.cov.cov,Matern)
    @test ndims(grf.cov) == 3
    @test isa(grf,GaussianRandomField{Spectral})
    @test length(grf.pts) == 3
    @test length(grf.pts[1]) == length(pts1)
    @test length(grf.pts[2]) == length(pts2)
    @test length(grf.pts[3]) == length(pts3)
    @test size(sample(grf),1) == length(pts1)
    @test size(sample(grf),2) == length(pts2)
    @test size(sample(grf),3) == length(pts3)

    ## non-SPD covariance matrix ##
    cov = CovarianceFunction(2,SquaredExponential(0.5))
    pts1 = 0:0.05:1
    pts2 = 0:0.05:1
    @suppress grf = GaussianRandomField(cov,Spectral(),pts1,pts2)
    @test isa(grf,GaussianRandomField)
    @test isa(grf.cov,CovarianceFunction)
    @test isa(grf.cov.cov,SquaredExponential)
    @test ndims(grf.cov) == 2
    @test isa(grf,GaussianRandomField{Spectral})
    @test length(grf.pts) == 2
    @test length(grf.pts[1]) == length(pts1)
    @test length(grf.pts[2]) == length(pts2)
    @test size(sample(grf),1) == length(pts1)
    @test size(sample(grf),2) == length(pts2)

    ## test non-equidistant structured grid  ##
    cov = CovarianceFunction(2,Matern(1.,2.5,σ=1.,p=2))
    pts1 = sin.(-pi/2:0.2:pi/2)
    pts2 = sin.(-pi/2:0.1:pi/2)
    grf = GaussianRandomField(cov,Spectral(),pts1,pts2)
    @test isa(grf,GaussianRandomField)
    @test isa(grf.cov,CovarianceFunction)
    @test isa(grf.cov.cov,Matern)
    @test ndims(grf.cov) == 2
    @test isa(grf,GaussianRandomField{Spectral})
    @test length(grf.pts) == 2
    @test length(grf.pts[1]) == length(pts1)
    @test length(grf.pts[2]) == length(pts2)
    @test size(sample(grf),1) == length(pts1)
    @test size(sample(grf),2) == length(pts2)

    ## test anisotropic
    cov = CovarianceFunction(2,AnisotropicExponential([1000 0; 0 1000]))
    pts1 = range(0,stop = 10,length = 64)
    pts2 = range(0,stop = 10,length = 64)
    grf = GaussianRandomField(cov,Spectral(),pts1,pts2)
    @test isa(grf,GaussianRandomField)
    @test isa(grf.cov,CovarianceFunction)
    @test isa(grf.cov.cov,AnisotropicExponential)
    @test ndims(grf.cov) == 2
    @test isa(grf,GaussianRandomField{Spectral})
    @test length(grf.pts) == 2
    @test length(grf.pts[1]) == length(pts1)
    @test length(grf.pts[2]) == length(pts2)
    @test size(sample(grf),1) == length(pts1)
    @test size(sample(grf),2) == length(pts2)

end


include("add_data_points.jl")
include("vizualize_optim.jl")
export optimize!, get_jacobian, get_posterior_model_covariance, get_logspace_model_and_covmat

function get_jacobian(g::F, mₙ ; method=:finitediff) where F<:Function
    if method == :finitediff
        J = FiniteDiff.finite_difference_jacobian(g, mₙ)
    elseif method == :autodiff
        J = ForwardDiff.jacobian(g,mₙ)
    else
        @error "`method` kwarg must be :finitediff"
    end
    return J
end

function get_new_parameters!(g::F, mₙₑₓₜ, mₙ, mₚ, dₙ, dₒ, Cm, Cd ; μₙ=1.0, diff_method=:finitediff) where F<:Function
    G = get_jacobian(g, mₙ ; method=diff_method)
    Cm_inv, Cd_inv = inv(Cm), inv(Cd)
    A = G'*Cd_inv*G + Cm_inv
    b = G'*Cd_inv*(dₙ - dₒ) + Cm_inv*(mₙ - mₚ)
    mₙₑₓₜ .= A \ (A*mₙ - μₙ*b)
    return mₙₑₓₜ
end

function get_new_parameters!(g::F, mₙₑₓₜ::NamedArray, mₙ::NamedArray, mₚ::NamedArray, dₙ, dₒ, Cm, Cd ; μₙ=1.0, diff_method=:finitediff) where F<:Function
    G = get_jacobian(g, mₙ.array ; method=diff_method)
    Cm_inv, Cd_inv = inv(Cm), inv(Cd)
    A = G'*Cd_inv*G + Cm_inv
    b = G'*Cd_inv*(dₙ - dₒ) + Cm_inv*(mₙ - mₚ).array
    mₙₑₓₜ .= A \ (A*mₙ - μₙ*b)
    return mₙₑₓₜ
end

function optimize!(dataset, p, ;
        μₙ=1.0, reltol=1e-8,
        maxiters=1000,
        diff_method = :finitediff,
        allow_mismatch=false,
        plot_transformed=false)
    reldiff = Inf
    mₙₑₓₜ, Cm = get_logspace_model_and_covmat(p)
    mₙ = copy(mₙₑₓₜ)
    mₚ = copy(mₙₑₓₜ)
    n = 0

    simulate!(dataset, p ; allow_mismatch)
    df_target = get_target_data(dataset)
    add_data_points!(df_target, dataset, p)
    Cd, pos_real_func = get_transformed_covmat(df_target, p) # needs to happen before data gets transformed.
    transform_data!(df_target,p)
    dₒ, dₙ = df_target.target, df_target.target_sim

    Cminv = inv(Cm)

    function g(m)
        p.mp .= exp.(m)
        simulate!(dataset, p ; allow_mismatch)
        df_target = get_target_data(dataset)
        add_data_points!(df_target, dataset, p)
        transform_data!(df_target, p)
        return Vector{Float64}(df_target[!,:target_sim])
    end

    #initialize plot
    ds_obs = Observable(dataset)#get_observable_dataset(dataset)
    #update_and_notify_obs!(ds_obs, dataset, p ; plot_transformed)
    vizualize(ds_obs, p)

    #iterate
    while (reldiff > reltol) & (n < maxiters)
        n+=1 ; println() ; @show(n)
        #@show (mₙₑₓₜ.-mₚ)./mₚ
        mₙ .= mₙₑₓₜ
        get_new_parameters!(g, mₙₑₓₜ, mₙ, mₚ, dₙ, dₒ, Cm, Cd ; μₙ, diff_method)
        p.mp .= exp.(mₙₑₓₜ)
        simulate!(dataset, p ; allow_mismatch)
        df_target_next = get_target_data(dataset)
        add_data_points!(df_target_next, dataset, p)
        Cd, _ = get_transformed_covmat(df_target_next, p ; is_positive_real_func=pos_real_func) # needs to happen before data gets transformed.
        #@show Cd[[1,40],[1,40]]
        Cdinv = inv(Cd)
        transform_data!(df_target_next,p)
        dₒ, dₙ = df_target_next.target, df_target_next.target_sim
        #Cd = get_transformed_covmat(df_target, p) # not needed if data always has the same length

        cost_data  = (dₙ-dₒ)'*Cdinv*(dₙ-dₒ)
        cost_model = (mₙₑₓₜ-mₚ)'*Cminv*(mₙₑₓₜ-mₚ)
        cost = cost_data + cost_model
        #@show unique(Cdinv)
        #@show mean((dₙ-dₒ).^2)
        @show cost_data
        @show cost_model
        @show cost

        #plot
        #update_and_notify_obs!(ds_obs, dataset, p ; plot_transformed)
        notify(ds_obs)
    end

    return dataset, p, Cm, Cd
end

function get_posterior_model_covariance(g::F, m::NamedArray, Cm, Cd) where F<:Function
    G = get_jacobian(g, m.array ; method=:finitediff)
    return Cm - Cm*G' * inv(G*Cm*G' + Cd) * G*Cm
end
function get_posterior_model_covariance(g::F, m::Vector, Cm, Cd) where F<:Function
    G = get_jacobian(g, m ; method=:finitediff)
    return Cm - Cm*G' * inv(G*Cm*G' + Cd) * G*Cm
end

function transform_data!(df_target::AbstractDataFrame, p)
    dfg = groupby(df_target,:target_name)
    if hasproperty(p.estimate,:transform_target)
        for i in 1:length(dfg)
            target_name = dfg[i][1,:target_name]
            if hasproperty(p.estimate.transform_target,target_name)
                for col in [:target,:target_sim]
                    dfg[i][!,col] = getproperty(p.estimate.transform_target, target_name)(dfg[i][!,col])
                end
            end
        end
    end
    return df_target
end

function transform_data!(ds::Dataset, p)
    for i in eachindex(ds.timeseries)
        transform_data!(ds.timeseries[i], p)
    end
    for i in eachindex(ds.ponctual)
        transform_data!(ds.ponctual[i], p)
    end
    return ds
end

function transform_data!(ds::DatasetType, p)
    data = ds.data
    col_names = propertynames(data)
    if hasproperty(p.estimate,:transform_target)
        cols_to_transform = propertynames(p.estimate.transform_target)
        for col in cols_to_transform
            if col ∈ col_names
                @views data[:,col] .= getproperty(p.estimate.transform_target, col)(data[:,col])
                @views data[:,string(col)*"_sim"] .= getproperty(p.estimate.transform_target, col)(data[:,string(col)*"_sim"])
            end
        end
    end
    return ds
end

"log transform so that lognormal distribution σ tends toward std when m goes further away from 0, and μ is exactly equal to ln(m), i.e. the median of the lognormal"
function to_log_from_physical_m_and_std(m,std)
    σ² = log(1+std^2/m^2)
    μ = log(m)
    return μ, sqrt(σ²)
end

function get_logspace_model_and_covmat(mp, std_mp)
    m_std_log = to_log_from_physical_m_and_std.(mp,std_mp)
    m_log = [tup[1] for tup in m_std_log]
    std_log = [tup[2] for tup in m_std_log]
    Cm_log = Diagonal(std_log.^2)
    return m_log, Cm_log
end
get_logspace_model_and_covmat(p) = get_logspace_model_and_covmat(p.mp, p.mp_std)

function get_transformed_covmat(dₒ_vec, std_vec, target_names, p ; is_positive_real_func=nothing)
    len = length(std_vec)
    Cd = Diagonal{Float64}(undef,len)
    funcs = [getproperty(p.estimate.transform_target, target_name) for target_name in target_names]
    if isnothing(is_positive_real_func)
        is_positive_real_func = fill(false,length(funcs))
        for i in eachindex(funcs)
            try
                funcs[i](-1)
            catch e
                is_positive_real_func[i] = ifelse(e isa DomainError, true, false)
            end
        end
        for i in eachindex(funcs)
            if is_positive_real_func[i]
                if all(funcs[i].(dₒ_vec) .≈ log.(dₒ_vec))
                    continue
                else
                    throw(error("transform function associated to $(target_names[i]) is a positive real function that is not log"))
                end
            end
        end
    end

    for i in 1:len
        target_name = target_names[i]
        func_id = findfirst(target_name .== target_names)
        func = funcs[func_id]
        dₒ = dₒ_vec[i]
        std = std_vec[i]
        if is_positive_real_func[func_id]
            dlog, stdlog = to_log_from_physical_m_and_std(dₒ,std)
            var = stdlog^2
        else
            lb = func(dₒ - std)
            ub = func(dₒ + std)
            var =(ub-lb)^2
        end
        Cd[i,i] = var
    end
    return Cd, is_positive_real_func
end
get_transformed_covmat(df_target, p ; is_positive_real_func=nothing) = get_transformed_covmat(df_target.target, df_target.std, df_target.target_name, p ; is_positive_real_func)


# default choices for g and dg
g(u::T, j::Int64, zl = 0.0, zr = 1.0) where {T<:Real} =  - ( u + zl )^ j * (zr -u)
dg(u::T, j::Int64, zl = 0.0, zr = 1.0) where {T<:Real} = ( u + zl )^(j-1) * ( (j+1) * u + zl - j * zr )


function SeisBandPass(d;dt=0.001,fa=0,fb=0,fc=60,fd=80)

	nt = size(d,1)
	nx = size(d[:,:],2)
	nf = iseven(nt) ? nt : nt + 1
	df = 1/nf/dt
	nw = round(Int,nf/2) + 1

	if(fd*dt*nf < nw)
		iw_max = round(Int,floor(fd*dt*nf))
	else
		iw_max = round(Int,floor(0.5/dt))
	end

	d = pad_first_axis(d,nf)
	m = fft(d,1)/sqrt(size(d,1))
	if fa > 0.
		m[1,:] *= 0.
	end
	for iw=2:iw_max
		f = df*(iw-1)
		if (f<fa)
			m[iw,:] *= 0.
		elseif (f >= fa && f < fb)
			m[iw,:] *= (f-fa)/(fb-fa)
		elseif (f >= fb && f <= fc)
			m[iw,:] *= 1.
		elseif (f > fc && f <= fd)
			m[iw,:] *= 1. - (f-fc)/(fd-fc)
		else
			m[iw,:] *= 0.
		end
	end
	m[iw_max:end,:] = 0.

	# symmetries
	for iw=nw+1:nf
		m[iw,:] = conj(m[nf-iw+2,:])
	end
	d = real(bfft(m,1)/sqrt(size(m,1)))

	return d[1:nt,1:nx];
end

function pad_first_axis(a,N1)
	n1 = size(a,1)
	nx = size(a[:,:],2)
	b = zeros(N1,nx)
	for ix = 1 : nx
		b[1:n1,ix] = a[:,ix]
	end
	return b
end

function SeisBandPass(d,h::Array{Header,1};dt=0.001,fa=0,fb=0,fc=60,fd=80)

	out = SeisBandPass(d;dt=h[1].d1,fa=fa,fb=fb,fc=fc,fd=fd)
	return out,h

end

function SeisBandPass(in::String,out::String;fa=0,fb=0,fc=60,fd=80)

	@compat parameters = Dict(:fa=>fa,:fb=>fb,:fc=>fc,:fd=>fd)
	SeisProcess(in,out,[SeisBandPass],[parameters];group="some")

end


import LinearAlgebra: norm, dot, cross

mutable struct Vec <: NonAbelian
	v
end
Vec(a...)=Vec([a...])
@delegate Vec.v [#=getindex, setindex!,=# iterate, length]
function Base.broadcast(::typeof(*),v::Vec,a::Factor)
	nv=Any[]
	for val in v.v
		push!(nv,val*a)
	end
	return Vec(nv)
end
mutable struct Cross <: Component
	x
	y
end
function getvec(c::Cross)
	ind=indsin(c,Vec)
	args=deepcopy(getargs(c))
	ex=args[ind[1][1]][ind[1][2][1][1]]
	vec=ex[ind[1][2][1][2][1]]
	deleteat!(ex,ind[1][2][1][2][1])
	return (ex,vec)
end
function getvec(ex::Expression)
	@assert length(ex)==1
	ind=indsin(ex,Vec)
	ex=deepcopy(ex)
				#implement support for multiple vectors/terms
	vec=ex[1][ind[1][2][1]]
	deleteat!(ex[1],ind[1][2][1])
	return (extract(ex),vec)
end
function cross(v1::Vec,v2::Vec)
	if isa(v1.v,Vector)&&isa(v2.v,Vector)
		return Vec(cross(v1.v,v2.v))
	end
	return Cross(v1,v2)
end
function simplify(c::Cross)
	if (isa(c.x,Vec)&&isa(c.y,Vec)&&isa(c.x.v,Vector)&&isa(c.y.v,Vector))
		return Vec(cross(c.x.v,c.y.v))
	end
	if isa(c.x,Expression)&&isa(c.y,Expression)
		ind=indsin(c,Vec)
		args=deepcopy(getargs(c))
		ex1=args[ind[1][1]][ind[1][2][1][1]]
		v1=ex1[ind[1][2][1][2][1]]
		deleteat!(ex1,ind[1][2][1][2][1])
		ex2=args[ind[2][1]][ind[2][2][1][1]]
		v2=ex2[ind[2][2][1][2][1]]
		deleteat!(ex2,ind[2][2][1][2][1])
		return extract(expression(ex1))*extract(expression(ex2))*Vec(cross(v1.v,v2.v))
	end
	return c
end
print(io::IO,c::Cross)=print(io,c.x,'×',c.y)
mutable struct Norm <: Component
	x
end
norm(ex::Ex)=Norm(ex)
function norm(vec::Vec)
	if isa(vec.v,Vector)
		return norm(vec.v)
	elseif isa(vec.v,Expression)
		ex,v=getvec(vec.v)
		return ex*norm(v)
	else
		return Norm(vec)
	end
#	has(vec.v,Ex)?Norm(vec):norm(vec.v)
end
function simplify(n::Norm)
	if isa(n.x,Expression)&&has(n.x,Vec)
		ex,v=getvec(n.x)
		return ex*norm(v)
	end
	norm(n.x)
end
mutable struct Dot <: Component
	x
	y
end
print(io::IO,c::Dot)=print(io,c.x,'⋅',c.y)
dot(x::EX,y::EX)=Dot(x,y)
function dot(v1::Vec,v2::Vec)
	if isa(v1.v,Array)&&isa(v2.v,Array)&&length(v1.v)==length(v2.v)
		ex=Expression(Term[Factor[v1.v[1]*v2.v[1]]])
		for t in 2:length(v1.v)
			push!(ex,Factor[v1.v[t],v2.v[t]])
		end
		return simplify(ex)
	end
	return Dot(v1,v2)
end
function simplify(d::Dot)
	if !isempty(methods(dot,(typeof(d.x),typeof(d.y))))
		return dot(d.x,d.y)
	end
	return d
end


export Coulomb

mutable struct Coulomb <: MoleculeDescribe
    number::Int64
    value::Array{Float64, 1}

    Coulomb() = new(0, [])
    function Coulomb(molecule::Atoms)
        coulombMatrix = Array{Float64, 2}(undef, Atoms.number, Atoms.number)
        for i in 1:(Atoms.number-1)
            chargeI = getCharge(Atoms.atom[i].symbol)
            coulombMatrix[i, i] = chargeI^2.4

            for j in (i+1):(Atoms.number-1)
                chargeJ = getCharge(Atoms.atom[j].symbol)
                distance = dist(Atoms.atom[i], Atoms.atom[j])
                coulombMatrix[i, j] = chargeI * chargeJ / distance
                coulombMatrix[j, i] = coulombMatrix[i, j]
            end
        end

        eigenValues = eigvals(coulombMatrix)
        sort!(eigenValues, rev=true)

        new(Atoms.number, eigenValues)
    end
end


# Copyright (c) 2017: Miles Lubin and contributors
# Copyright (c) 2017: Google Inc.
#
# Use of this source code is governed by an MIT-style license that can be found
# in the LICENSE.md file or at https://opensource.org/licenses/MIT.

"""
    get_bounds(model::MOI.ModelLike, ::Type{T}, x::MOI.VariableIndex)

Return a tuple `(lb, ub)` of type `Tuple{T, T}`, where `lb` and `ub` are lower
 and upper bounds, respectively, imposed on `x` in `model`.
"""
function get_bounds(
    model::MOI.ModelLike,
    ::Type{T},
    x::MOI.VariableIndex,
) where {T}
    xval = x.value
    c_lt = MOI.ConstraintIndex{MOI.VariableIndex,MOI.LessThan{T}}(xval)
    c_gt = MOI.ConstraintIndex{MOI.VariableIndex,MOI.GreaterThan{T}}(xval)
    c_int = MOI.ConstraintIndex{MOI.VariableIndex,MOI.Interval{T}}(xval)
    c_eq = MOI.ConstraintIndex{MOI.VariableIndex,MOI.EqualTo{T}}(xval)
    c_sc = MOI.ConstraintIndex{MOI.VariableIndex,MOI.Semicontinuous{T}}(xval)
    c_si = MOI.ConstraintIndex{MOI.VariableIndex,MOI.Semiinteger{T}}(xval)
    if MOI.is_valid(model, c_int)
        # It is assumed that none of the other ConstraintIndexs are valid
        int::MOI.Interval{T} = MOI.get(model, MOI.ConstraintSet(), c_int)
        return int.lower, int.upper
    elseif MOI.is_valid(model, c_eq)
        # It is assumed that none of the other ConstraintIndexs are valid
        eq::MOI.EqualTo{T} = MOI.get(model, MOI.ConstraintSet(), c_eq)
        return eq.value, eq.value
    elseif MOI.is_valid(model, c_sc)
        # It is assumed that none of the other ConstraintIndexs are valid
        sc::MOI.Semicontinuous{T} = MOI.get(model, MOI.ConstraintSet(), c_sc)
        return min(zero(T), sc.lower), max(zero(T), sc.upper)
    elseif MOI.is_valid(model, c_si)
        # It is assumed that none of the other ConstraintIndexs are valid
        si::MOI.Semiinteger{T} = MOI.get(model, MOI.ConstraintSet(), c_si)
        return min(zero(T), si.lower), max(zero(T), si.upper)
    elseif MOI.is_valid(model, c_lt)
        lt::MOI.LessThan{T} = MOI.get(model, MOI.ConstraintSet(), c_lt)
        # It is valid to have both LessThan and GreaterThan constraints on the
        # same variable.
        if MOI.is_valid(model, c_gt)
            gt_1::MOI.GreaterThan{T} = MOI.get(model, MOI.ConstraintSet(), c_gt)
            return gt_1.lower, lt.upper
        else
            return typemin(T), lt.upper
        end
    elseif MOI.is_valid(model, c_gt)
        gt_2::MOI.GreaterThan{T} = MOI.get(model, MOI.ConstraintSet(), c_gt)
        return gt_2.lower, typemax(T)
    else
        return typemin(T), typemax(T)
    end
end


### A Pluto.jl notebook ###
# v0.12.21

using Markdown
using InteractiveUtils

# ╔═╡ 1c31a6c8-fe76-11ea-1122-0580af80a98b
using Pkg, DrWatson

# ╔═╡ 1c31e480-fe76-11ea-356d-6b2d21e37ace
begin
	#@quickactivate "StatisticalRethinkingStan"
	using StanQuap
	using StatisticalRethinking
end

# ╔═╡ d1b31aa0-fe75-11ea-0d96-bdbf19c2c1c7
md"## Clip-06-06-09s.jl"

# ╔═╡ 1c325ef6-fe76-11ea-1967-45317ad16daa
begin
	df = CSV.read(sr_datadir("milk.csv"), DataFrame; delim=';');
	scale!(df, [:kcal_per_g, :perc_fat, :perc_lactose])
end;

# ╔═╡ 1c3fc474-fe76-11ea-327f-7beea4fb1872
stan6_4 = "
data{
  int <lower=1> N;              // Sample size
  vector[N] K;
  vector[N] L;
}
parameters{
  real a;
  real bL;
  real<lower=0> sigma;
}
model{
  vector[N] mu;
  sigma ~ exponential( 1 );
  a ~ normal( 0 , 0.2 );
  bL ~ normal( 0 , 0.5 );
  mu = a + bL * L;
  K ~ normal( mu , sigma );
}
";

# ╔═╡ 1c48ffe4-fe76-11ea-0883-f3715e7beda4
md"##### Define the SampleModel, etc."

# ╔═╡ 1c4ba9c4-fe76-11ea-0aff-ef249af5bdb5
begin
	data = (N = size(df, 1), L = df.perc_lactose_s, K = df.kcal_per_g_s)
	init = (a = 0.0, bL = 1.0, sigma = 1.0)
	q6_4s, m6_4s, o6_4s = stan_quap("m6.3", stan6_4; data, init)
	if !isnothing(m6_4s)
		post6_4s_df = read_samples(m6_4s, :dataframe)
		PRECIS(post6_4s_df)
	end
end

# ╔═╡ 1c57c894-fe76-11ea-0b4f-152cfd868c3f
!isnothing(m6_4s) && (part6_4s = Particles(post6_4s_df))

# ╔═╡ 1c586718-fe76-11ea-10ec-892fadc8778c
if !isnothing(q6_4s)
	quap6_4s_df = sample(q6_4s)
	PRECIS(quap6_4s_df)
end

# ╔═╡ 1c659f3c-fe76-11ea-0e95-c1c2cadaaebc
!isnothing(m6_4s) && hpdi(part6_4s.bL.particles, alpha=0.11)

# ╔═╡ 1c6741e8-fe76-11ea-34f3-c54c70b51171
md"## End of clip-06-06-09s.jl"

# ╔═╡ Cell order:
# ╟─d1b31aa0-fe75-11ea-0d96-bdbf19c2c1c7
# ╠═1c31a6c8-fe76-11ea-1122-0580af80a98b
# ╠═1c31e480-fe76-11ea-356d-6b2d21e37ace
# ╠═1c325ef6-fe76-11ea-1967-45317ad16daa
# ╠═1c3fc474-fe76-11ea-327f-7beea4fb1872
# ╟─1c48ffe4-fe76-11ea-0883-f3715e7beda4
# ╠═1c4ba9c4-fe76-11ea-0aff-ef249af5bdb5
# ╠═1c57c894-fe76-11ea-0b4f-152cfd868c3f
# ╠═1c586718-fe76-11ea-10ec-892fadc8778c
# ╠═1c659f3c-fe76-11ea-0e95-c1c2cadaaebc
# ╟─1c6741e8-fe76-11ea-34f3-c54c70b51171


# ---
# title: 1594. Maximum Non Negative Product in a Matrix
# id: problem1594
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Dynamic Programming, Greedy
# link: <https://leetcode.com/problems/maximum-non-negative-product-in-a-matrix/description/>
# hidden: true
# ---
#
# You are given a `rows x cols` matrix `grid`. Initially, you are located at the
# top-left corner `(0, 0)`, and in each step, you can only **move right  or
# down** in the matrix.
#
# Among all possible paths starting from the top-left corner `(0, 0)` and ending
# in the bottom-right corner `(rows - 1, cols - 1)`, find the path with the
# **maximum non-negative product**. The product of a path is the product of all
# integers in the grid cells visited along the path.
#
# Return the  _maximum non-negative product   **modulo**  _`109 + 7`.  _If the
# maximum product is **negative** return _`-1`.
#
# **Notice that the modulo is performed after getting the maximum product.**
#
#
#
# **Example 1:**
#
#
#
#     Input: grid = [[-1,-2,-3],
#                    [-2,-3,-3],
#                    [-3,-3,-2]]
#     Output: -1
#     Explanation: It's not possible to get non-negative product in the path from (0, 0) to (2, 2), so return -1.
#
#
# **Example 2:**
#
#
#
#     Input: grid = [[ **1** ,-2,1],
#                    [ **1** , **-2** ,1],
#                    [3, **-4** , **1** ]]
#     Output: 8
#     Explanation: Maximum non-negative product is in bold (1 * 1 * -2 * -4 * 1 = 8).
#
#
# **Example 3:**
#
#
#
#     Input: grid = [[ **1** , 3],
#                    [ **0** , **-4** ]]
#     Output: 0
#     Explanation: Maximum non-negative product is in bold (1 * 0 * -4 = 0).
#
#
# **Example 4:**
#
#
#
#     Input: grid = [[ **1** , 4,4,0],
#                    [ **-2** , 0,0,1],
#                    [ **1** , **-1** , **1** , **1** ]]
#     Output: 2
#     Explanation: Maximum non-negative product is in bold (1 * -2 * 1 * -1 * 1 * 1 = 2).
#
#
#
#
# **Constraints:**
#
#   * `1 <= rows, cols <= 15`
#   * `-4 <= grid[i][j] <= 4`
#
#
## @lc code=start
using LeetCode

## add your code here:
## @lc code=end


_strip_module(s) = split(s, '.', limit=2)[end]
_strip_type_param(s) = replace(s, r"{.+?}" => "")
_clean_name(d::Distributions.Distribution) = _strip_module(_strip_type_param(repr(d)))

function scatter(d::Distributions.ContinuousUnivariateDistribution)
    ls(a, b, c) = range(a, stop=b, length=c)
    x = ls(quantile.([d], [0.01, 0.99])..., 100)
    trace = scatter(x=x, y=pdf.([d], x), name=_clean_name(d))
end

Plot(d::Distributions.UnivariateDistribution...) = Plot(collect(map(scatter, d)))


using Dash, DashHtmlComponents, DashCoreComponents

app = dash()

app.layout = html_div() do
    dcc_input(id = "input-4", value = "1", type = "text"),
    html_tr((html_td("x^2 ="), html_td(id = "square"))),
    html_tr((html_td("x^3 ="), html_td(id = "cube"))),
    html_tr((html_td("2^x ="), html_td(id = "twos"))),
    html_tr((html_td("3^x ="), html_td(id = "threes"))),
    html_tr((html_td("x^x ="), html_td(id = "xx")))
end

callback!(
    app,
    Output("square", "children"),
    Output("cube", "children"),
    Output("twos", "children"),
    Output("threes", "children"),
    Output("xx", "children"),
    Input("input-4", "value"),
) do x
    if x == "" || x == nothing
        return ("", "", "", "", "")
    end

    x = parse(Int64, x)
    return (x^2, x^3, 2^x, 3^x, x^x)
end

run_server(app, "0.0.0.0", debug=true)


using LaTeXStrings
using PyPlot
using Printf

function dsdemo7()
    println("Invariant Set for MOD2 (find2dPIS)")

    A = [1 0; 1 1]
    B = [1 -1; 1 -2]
    C = [0 1]
    D = [0 0]
    ABCD = [A B; C D]
    order = 2

    fig = figure(1)
    fig.clf()
    fig.canvas.set_window_title("Working Invariant Set")

    u = 1 / pi
    s = find2dPIS(u, ABCD, true)

    N = 10000
    skip = 100
    _, x, = simulateDSM(u * ones(N+skip), ABCD, 2)
    x = x[:,1+skip:N+skip]
    nv = size(s, 2)
    splus, eplus, sminus, eminus = dssplit2d(u, ABCD, s)
    Buv = B * [u, 1]
    s1 = A*splus + Buv*ones(1,size(splus, 2))
    Buv = B * [u, -1]
    s2 = A*sminus + Buv*ones(1,size(sminus, 2))
    ns = [s1 s2]
    out = outconvex2d(ns, s)

    fig = figure(2)
    fig.clf()
    fig.canvas.set_window_title("Final Invariant Set")

    grid()
    dotplot(x, "k", ".")
    polyplot(s, "b")
    polyplot(s1, "m")
    polyplot(s2, "c")
    outi = (out .!= 0)[:]
    dotplot(ns[:,outi], "r", "s")
    str = @sprintf("Final Object: %d image vertices outside", sum(outi))
    title(str)
    xlabel(L"x_1")
    ylabel(L"y_1")

    @printf("%d points from the %d simulated states are outside.\n", sum(outconvex2d(x, s)), N)
    @printf("%d image points are outside.\n", sum(out))
    @printf("The returned polygon has %d vertices.\n", size(s, 2))

    return nothing
end


# ---
# title: 78. Subsets
# id: problem78
# author: Jerry Ling
# date: 2021-12-01
# difficulty: Medium
# categories: Array, Backtracking, Bit Manipulation
# link: <https://leetcode.com/problems/subsets/description/>
# hidden: true
# ---
#
# Given an integer array `nums`, return _all possible subsets (the power set)_.
#
# The solution set must not contain duplicate subsets.
#
#
#
# **Example 1:**
#
#
#
#     Input: nums = [1,2,3]
#     Output: [[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
#
#
# **Example 2:**
#
#
#
#     Input: nums = [0]
#     Output: [[],[0]]
#
#
#
#
# **Constraints:**
#
#   * `1 <= nums.length <= 10`
#   * `-10 <= nums[i] <= 10`
#
#
## @lc code=start
using LeetCode

function subsets(arr::Vector{T}) where T
    res = [T[]]
    for e in arr
        for i in eachindex(res)
            push!(res, vcat(res[i], e))
        end
    end
    res
end

## add your code here:
## @lc code=end


function zero_matrix!(mat :: Array{Int32, 2})
  N :: Int = size(mat, 1) # row
  M :: Int = size(mat, 2) # col

  # Mark if the first row should be cleaned
  clear_first_row :: Bool = false
  for col = 1:M
    if mat[1, col] == 0 clear_first_row = true; end
  end

  # Mark each row and col to be cleaned
  for row = 2:N
    for col = 1:M
      if mat[row, col] == 0
        mat[row, 1] = 0
        mat[1, col] = 0
        break
      end
    end
  end

  # Clean columns
  for col = 1:M
    if mat[1, col] == 0
      for r = 1:N
        mat[r, col] = 0
      end
    end
  end

  # Clean the first row
  if clear_first_row
    for c = 1:M mat[1, c] = 0; end
  end

  # Clean rows
  for row =2:N
    if mat[row, 1] == 0
      for c = 1:M
        mat[row, c] = 0
      end
    end
  end

end

# Time  : O(mn)
# Space : O(1)

using Base: Test

mat = Array{Int32, 2}([[1 2 3 0];[4 5 6 11];[7 8 9 12]])
zero_matrix!(mat)
@test mat == Array{Int32, 2}([[0 0 0 0];[4 5 6 0];[7 8 9 0]])


using PtFEM
using Test

ProjDir = dirname(@__FILE__)

#=
Compare formulas at:
http://www.awc.org/pdf/codes-standards/publications/design-aids/AWC-DA6-BeamFormulas-0710.pdf
=#

data = Dict(
  # Frame(nels, nn, ndim, nst, nip, finite_element(nod, nodof))
  :struc_el => Frame(200, 201, 3, 1, 1, Line(2, 3)),
  :properties => [1.0e6 1.0e6 1.0e6 3.0e5;],
  :x_coords => collect(range(0, stop=4, length=201)),
  :y_coords => zeros(201),
  :z_coords => zeros(201),
  :g_num => [
    collect(1:200)';
    collect(2:201)'],
  :support => [
    (1, [0 0 0 0 0 0]),
    (201, [0 0 0 0 0 0]),
    ],
  :loaded_nodes => [
    (101, [0.0 -10000.0 0.0 0.0 0.0 0.0])]
)

data |> display
println()

m, dis_df, fm_df = p44(data)

println("Displacements:")
m.displacements |> display
println()

println("Actions:")
m.actions |> display
println()

println("y displacements:")
m.displacements[2,:] |> display
println()

println("y moment actions:")
m.actions[12,:] |> display
println()

using Plots
gr(size=(400,600))

p = Vector{Plots.Plot{Plots.GRBackend}}(undef, 3)
p[1] = plot(m.x_coords, m.displacements[2,:], ylim=(-0.004, 0.001), lab="Displacement",
 xlabel="x [m]", ylabel="deflection [m]", color=:red)
p[2] = plot(m.actions[2,:], lab="Shear force", ylim=(-6000, 6000), xlabel="element",
  ylabel="shear force [N]", palette=:greens,fill=(0,:auto),α=0.6)
p[3] = plot(m.actions[12,:], lab="Moment", ylim=(-6000, 6000), xlabel="element",
  ylabel="moment [Nm]", palette=:grays,fill=(0,:auto),α=0.6)

plot(p..., layout=(3, 1))
#savefig(ProjDir*"/figure-24.pdf")
gui()

# See figure 24 in above reference (Δmax):
@test round.(m.displacements[2,101], digits=11) ≈ -0.00333333333 atol = 0.001

# See figure 24 in above reference (Mmax):
@test m.actions[12,101] ≈ 4899.999997801155


abstract type GeneralizedKroneckerProduct{T} <: AbstractMatrix{T} end

Base.eltype(K::GeneralizedKroneckerProduct{T}) where {T} = T

abstract type AbstractKroneckerProduct{T} <: GeneralizedKroneckerProduct{T} end

Base.IndexStyle(::Type{<:GeneralizedKroneckerProduct}) = IndexCartesian()

"""
    KroneckerProduct{T,TA<:AbstractMatrix, TB<:AbstractMatrix} <: AbstractKroneckerProduct{T}

Concrete Kronecker product between two matrices `A` and `B`.
"""
struct KroneckerProduct{T<:Any,TA<:AbstractMatrix, TB<:AbstractMatrix} <: AbstractKroneckerProduct{T}
    A::TA
    B::TB
    function KroneckerProduct(A::AbstractMatrix{T}, B::AbstractMatrix{V}) where {T, V}
        return new{promote_type(T, V), typeof(A), typeof(B)}(A, B)
    end
end

"""
    kronecker(A::AbstractMatrix, B::AbstractMatrix)

Construct a Kronecker product object between two arrays. Does not evaluate the
Kronecker product explictly.
"""
kronecker(A::AbstractMatrix, B::AbstractMatrix) = KroneckerProduct(A, B)

"""
    kronecker(A::AbstractMatrix, B::AbstractMatrix)

Higher-order Kronecker lazy kronecker product, e.g.
```
kronecker(A, B, C, D)
```
"""
kronecker(A::AbstractMatrix, B::AbstractMatrix...) = kronecker(A,
                                                            kronecker(B...))

"""
    ⊗(A::AbstractMatrix, B::AbstractMatrix)

Binary operator for `kronecker`, computes as Lazy Kronecker product. See
`kronecker` for documentation.
"""
⊗(A::AbstractMatrix, B::AbstractMatrix) = kronecker(A, B)
⊗(A::AbstractMatrix...) = kronecker(A...)

"""
    getindex(K::AbstractKroneckerProduct, i1::Integer, i2::Integer)

Computes and returns the (i,j)-th element of an `AbstractKroneckerProduct` K.
Uses recursion if `K` is of an order greater than two.
"""
function getindex(K::AbstractKroneckerProduct, i1::Integer, i2::Integer)
    A, B = getmatrices(K)
    m, n = size(A)
    k, l = size(B)
    return A[cld(i1, k), cld(i2, l)] * B[(i1 - 1) % k + 1, (i2 - 1) % l + 1]
end

"""
    getmatrices(K::AbstractKroneckerProduct)

Obtain the two matrices of an `AbstractKroneckerProduct` object.
"""
getmatrices(K::AbstractKroneckerProduct) = (K.A, K.B)

"""
    getmatrices(A::AbstractArray)

Returns a matrix itself. Needed for recursion.
"""
getmatrices(A::AbstractArray) = (A,)

"""
    size(K::AbstractKroneckerProduct)

Returns a the size of an `AbstractKroneckerProduct` instance.
"""
function size(K::AbstractKroneckerProduct)
    A, B = getmatrices(K)
    (m, n) = size(A)
    (k, l) = size(B)
    return m * k, n * l
end

"""
    size(K::GeneralizedKroneckerProduct)

Returns a the size of an `GeneralizedKroneckerProduct` instance.
"""
size(K::GeneralizedKroneckerProduct, dim::Integer) = size(K)[dim]

# CHECKS

"""
    issquare(A::AbstractMatrix)

Checks if an array is a square matrix.
"""
function issquare(A::AbstractMatrix)
    m, n = size(A)
    return m == n
end

"""
    issquare(K::AbstractKroneckerProduct)

Checks if all matrices of a Kronecker product are square.
"""
issquare(K::AbstractKroneckerProduct) = issquare(K.A) && issquare(K.B)

squarecheck(K::AbstractKroneckerProduct) = issquare(K) || throw(
            DimensionMismatch(
                "kronecker system is not composed of two square matrices: " *
                                        "$size(K.A) and $size(K.B)"))

"""
    issymmetric(K::AbstractKroneckerProduct)

Checks if a Kronecker product is symmetric.
"""
function LinearAlgebra.issymmetric(K::AbstractKroneckerProduct)
    return squarecheck(K) && issymmetric(K.A) && issymmetric(K.B)
end

"""
    isposdef(K::AbstractKroneckerProduct)

Test whether a Kronecker product is positive definite (and Hermitian) by trying
to perform a Cholesky factorization of K.
"""
function LinearAlgebra.isposdef(K::AbstractKroneckerProduct)
    squarecheck(K)
    return isposdef(K.A) && isposdef(K.B)
end

# SCALAR PROPERTIES

"""
    order(M::AbstractMatrix)

Returns the order of a matrix, i.e. how many matrices are involved in the
Kronecker product (default to 1 for general matrices).
"""
order(M::AbstractMatrix) = 1
order(M::AbstractKroneckerProduct) = order(M.A) + order(M.B)

"""
    det(K::AbstractKroneckerProduct)

Compute the determinant of a Kronecker product.
"""
function LinearAlgebra.det(K::AbstractKroneckerProduct)
    squarecheck(K)
    A, B = getmatrices(K)
    m = size(A)[1]
    n = size(B)[1]
    return det(K.A)^n * det(K.B)^m
end

"""
    logdet(K::AbstractKroneckerProduct)

Compute the logarithm of the determinant of a Kronecker product.
"""
function LinearAlgebra.logdet(K::AbstractKroneckerProduct)
    squarecheck(K)
    A, B = getmatrices(K)
    m = size(A)[1]
    n = size(B)[1]
    return n * logdet(A) + m * logdet(B)
end

"""
    tr(K::AbstractKroneckerProduct)

Compute the trace of a Kronecker product.
"""
function LinearAlgebra.tr(K::AbstractKroneckerProduct)
    squarecheck(K)
    return tr(K.A) * tr(K.B)
end

"""
    inv(K::AbstractKroneckerProduct)

Compute the inverse of a Kronecker product.
"""
function inv(K::AbstractKroneckerProduct)
    squarecheck(K)
    A, B = getmatrices(K)
    return KroneckerProduct(inv(A), inv(B))
end

"""
    adjoint(K::AbstractKroneckerProduct)

Compute the adjoint of a Kronecker product.
"""
function adjoint(K::AbstractKroneckerProduct)
    A, B = getmatrices(K)
    return kronecker(A', B')
end

"""
    transpose(K::AbstractKroneckerProduct)

Compute the transpose of a Kronecker product.
"""
function Base.transpose(K::AbstractKroneckerProduct)
    A, B = getmatrices(K)
    return kronecker(transpose(A), transpose(B))
end

function Base.conj(K::AbstractKroneckerProduct)
    A, B = getmatrices(K)
    return kronecker(conj(A), conj(B))
end

# COLLECTING

"""
    collect(K::AbstractKroneckerProduct)

Collects a lazy instance of the `AbstractKroneckerProduct` type into a full,
native matrix. Equivalent with `Matrix(K::AbstractKroneckerProduct)`.
"""
function collect(K::AbstractKroneckerProduct)
    A, B = getmatrices(K)
    return kron(A, B)
end

"""
    Matrix(K::GeneralizedKroneckerProduct)

Converts a `GeneralizedKroneckerProduct` instance to a Matrix type.
"""
Base.Matrix(K::GeneralizedKroneckerProduct) = collect(K)

function Base.:+(A::AbstractKroneckerProduct, B::StridedMatrix)
    C = Matrix(A)
    C .+= B
    return C
end
Base.:+(A::StridedMatrix, B::AbstractKroneckerProduct) = B + A

function Base.kron(K::AbstractKroneckerProduct, C::AbstractMatrix)
    A, B = getmatrices(K)
    return kron(kron(A, B), C)
end

function Base.kron(A::AbstractMatrix, K::AbstractKroneckerProduct)
    B, C = getmatrices(K)
    return kron(A, kron(B, C))
end

Base.kron(K1::AbstractKroneckerProduct,
            K2::AbstractKroneckerProduct) = kron(collect(K1), collect(K2))

# mixed-product property
function Base.:*(K1::AbstractKroneckerProduct, K2::AbstractKroneckerProduct)
    A, B = getmatrices(K1)
    C, D = getmatrices(K2)
    # check for size
    if size(A, 2) != size(C, 1)
        throw(DimensionMismatch("Mismatch between A and C in (A ⊗ B)(C ⊗ D)"))
    end
    if size(B, 2) != size(D, 1)
        throw(DimensionMismatch("Mismatch between B and D in (A ⊗ B)(C ⊗ D)"))
    end
    return (A * C) ⊗ (B * D)
end

"""
    lmul!(a::Number, K::AbstractKroneckerProduct)

Scale an `AbstractKroneckerProduct` `K` inplace by a factor `a` by rescaling the
**left** matrix.
"""
function LinearAlgebra.lmul!(a::Number, K::AbstractKroneckerProduct)
    A, B = getmatrices(K)
    lmul!(a, A)
end

"""
    rmul!(K::AbstractKroneckerProduct, a::Number)

Scale an `AbstractKroneckerProduct` `K` inplace by a factor `a` by rescaling the
**right** matrix.
"""
function LinearAlgebra.rmul!(K::AbstractKroneckerProduct, a::Number)
    A, B = getmatrices(K)
    rmul!(B, a)
end

function Base.:*(a::Number, K::AbstractKroneckerProduct)
    A, B = getmatrices(K)
    kronecker(a * A, B)
end

function Base.:*(K::AbstractKroneckerProduct, a::Number)
    A, B = getmatrices(K)
    kronecker(A, B * a)
end

# SOLVING
function LinearAlgebra.:\(K::AbstractKroneckerProduct{T}, c::AbstractVector{T}) where {T}
    size(K, 1) != length(c) && throw(DimensionMismatch("size(K, 1) != length(c)"))
    C = reshape(c, size(K.B, 1), size(K.A, 1)) # matricify
    return vec((K.B \ C) / K.A') #(A ⊗ B)vec(X) = vec(C) <=> BXA' = C => X = B^{-1} C A'^{-1}
end


module Hilbert

"""
    hilbert(x)
    hilbert(x, n)
Analytic signal, computed using the Hilbert transform.
`y = hilbert(x)` gives the analytic signal `y = x + i*xi` where `xi` is the
Hilbert transform of vector `x`. If `x` is complex, only the real part is used.
If `x` is a matrix, then `hilbert` operates along columns. If n is provided, the
n-point Hilbert transform is computed.

**Example**
```julia
x = randn(10,10)
y = hilbert(x)
```
"""
function hilbert{T<:Real}(x::AbstractArray{T,2}, n::Int64=0)

  x_ = copy(x)

  if(eltype(x_) <: Complex)
    warn("Using real part, ignoring complex part")
    x_ = real(x_)
  end

  # work along columns
  size(x_,1)==1 ? x_ = permutedims(x_,[2 1]) : nothing

  if(n>0 && n<size(x_,1))
    x_ = x_[1:n,:]
  elseif(n>0 && n>size(x_,1))
    x_ = cat(1,x_,zeros(n-size(x_,1),size(x_,2)))
  else
    n = size(x_,1)
  end

  xf = fft(x_,1)
  h = zeros(Int64,n)
  if n>0 && n % 2 == 0
    #even, nonempty
    h[1:div(n,2)+1] = 1
    h[2:div(n,2)] = 2
  elseif n>0
    #odd, nonempty
    h[1] = 1
    h[2:div(n + 1,2)] = 2
  end
  x_ = ifft(xf .* h[:,ones(Int64,size(xf,2))],1)

  # restore to original shape if necessary
  size(x,1)==1 ? x_ = permutedims(x_,[2 1]) : nothing

  return x_

end

function hilbert{T<:Complex}(x::AbstractArray{T}, n::Int64=0)
  warn("Using real part, ignoring complex part")
  Hilbert.hilbert(real(x))
end

end # module


using InverseIsing
using LinearAlgebra:Symmetric
using Test

function utils_test1()
    # test function 'decode'
    A = reshape(collect(1:9), 3, 3)
    @test decode(A) == Dict((1,2) => 4, (1,3) => 7, (2,3) => 8)
end

function utils_test2()
    # test function `trans(dict::AbstractDict, len::Int64)`
    d = Dict((1, 2) => 5, (2, 3) => -1)
    A = zeros(4, 4)
    A[1, 2] = 5; A[2, 3] = -1;
    A = Symmetric(A)
    @test trans(d, 4) == A
end

function utils_test3()
    # test function `trans(dict::AbstractDict)`
    d = Dict((1, 2) => -10, (2, 3) => -5, (2, 4) => 1)
    A = zeros(4, 4)
    for key in keys(d)
        A[key...] = d[key]
    end
    A = Symmetric(A)
    @test trans(d) == A
end

@testset "Test utils" begin
    utils_test1()
    utils_test2()
    utils_test3()
end


#    Copyright (c) 2021 J.A. Duffek
#    Copyright (c) 2000 D.M. Spink
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, see <http://www.gnu.org/licenses/>.

"""
    bspkntins(d::I,c::Matrix{F},k::Vector{F},u::Vector{F}
             ) where {I<:Integer,F<:AbstractFloat}

Insert knots into a univariate B-Spline.

# Arguments:
- `d`: Degree of the B-Spline.
- `c`: Control points, matrix of size `(dim,nc)`.
- `k`: Knot sequence, vector of size `nk`.
- `u`: Row vector of knots to be inserted, size `nu`.

# Output:
- `ic`: Control points of the new B-Spline, of size `(dim,nc+nu)`.
- `ik`: Knot vector of the new B-Spline, of size `(nk+nu)`.

# Examples:
```julia
julia> ic,ik = bspkntins(d,c,k,u)
```

# Reference:
- Algorithm A5.4 from 'The NURBS BOOK' pg164 ([1997Piegl](@cite))
"""
function bspkntins(d::I,c::Matrix{F},k::Vector{F},u::Vector{F}
                   ) where {I<:Integer,F<:AbstractFloat}
# TODO: the original C function passed c, k, ic, ik and u as reference
# TODO: this function has optimization potential

mc, nc = size(c);
nu = length(u);
nk = length(k);

ic = zeros(F,(mc,nc+nu));
ik = zeros(F,nk+nu);

n = nc - 1;
r = nu - 1;

m = n + d + 1;
a = findspan(n, d, u[1], k);
b = findspan(n, d, u[r+1], k);
b += 1;

for q in 0:mc-1
  for j in 0:a-d; ic[q+1,j+1] = c[q+1,j+1]; end # for j
  for j in b-1:n; ic[q+1,j+r+2] = c[q+1,j+1]; end # for j
end # for q

for j in 0:a; ik[j+1] = k[j+1]; end # for j
for j in b+d:m; ik[j+r+2] = k[j+1]; end # for j

i = b + d - 1;
s = b + d + r;

for j in r:-1:0
  while u[j+1] <= k[i+1] && i > a
    for q in 0:mc-1
      ic[q+1,s-d] = c[q+1,i-d];
    end # for q
    ik[s+1] = k[i+1];
    s -= 1;
    i -= 1;
  end # for q

  for q in 0:mc-1
    ic[q+1,s-d] = ic[q+1,s-d+1];
  end # for q

  for l in 1:d
    ind = s - d + l;
    alfa = ik[s+l+1] - u[j+1];
    if abs(alfa) == 0
      for q in 0:mc-1
        ic[q+1,ind] = ic[q+1,ind+1];
      end
    else
      alfa /= (ik[s+l+1] - k[i-d+l+1]);
      for q=0:mc-1
        ic[q+1,ind] = alfa*ic[q+1,ind] + (1-alfa)*ic[q+1,ind+1];
      end # for q
    end # if
  end # for l

  ik[s+1] = u[j+1];
  s -= 1;
end # for j
return ic, ik;
end # bspkntins


# This code and all turorial code can be found on https://github.com/forio/julia-tutorials

data = readcsv("gasoline.csv")

# Get the columns that correspond with our X values
# and create a new matrix to hold them
# syntax: data[row, column]
# to get a whole row do data[row, :]
# to get a whole column do data[:, column]
# to get a range of columns do data[:, column_a:column_b]

x = data[:, 2:4]
y = data[:, 6]

# Call linreg
coefs = linreg(x, y)


using Jennrich, Test, Random, Manifolds

@testset "CPD" begin
	Random.seed!(1)
	r = 3
	𝒯 = reshape(sum([kron(randn(9), randn(8), randn(7)) for i = 1:r]), (7,8,9))
	A, B, C = jennrich(𝒯, r)
	@test vec(𝒯) ≈ sum([kron(C[:,i], B[:,i], A[:,i]) for i = 1:r])
end

@testset "(Lᵣ,Lᵣ,1)-BTD" begin
	Random.seed!(1)
	n = (9,8,7)
	L = [2,3]
	ℳ = map(l -> Tucker(n, (l,l,1)), L)
	𝒯 = sum([embed(ℳ[i], TuckerPoint(randn(n...), (L[i],L[i],1))) for i = 1:length(L)])
	A,B,C = jennrich(𝒯, L)
	@test size(A) == (n[1], sum(L))
	@test size(B) == (n[2], sum(L))
	@test size(C) == (n[3], length(L))
	@test reshape(𝒯, n[1], :)' ≈ [kron(C[:,1], B[:,1:L[1]]) kron(C[:,2], B[:,L[1]+1:end])] * A'
end


function fib(n)
    p = (1+sqrt(5))/2
    return (round(((p)^n)/sqrt(5)))
end

for i=0:92
    println("fibonacci of ",i," = ",trunc(Int,fib(i)))
end


using JuMP, EAGO

                     m = Model()

                     EAGO.register_eago_operators!(m)

                     @variable(m, -1 <= x[i=1:4] <= 1)

                     @variable(m, -16.424759279035918 <= q <= 20.23452089871623)

                     add_NL_constraint(m, :((0.8420310557117703 + 0.34198141401583015*(-0.5389217244927917 + 0.5798025520034438*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + 0.4428632765655749*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + 0.9033224385695577*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4])))))/(1 + exp(-(-0.5389217244927917 + 0.5798025520034438*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + 0.4428632765655749*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + 0.9033224385695577*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))))))) + -0.1887519896617813*(-0.23921485861490321 + 0.5030319708107833*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + -0.2081481501163327*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + -0.2939216839670169*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4])))))/(1 + exp(-(-0.23921485861490321 + 0.5030319708107833*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + -0.2081481501163327*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + -0.2939216839670169*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))))))) + -0.2882490310085508*(-0.6545847926105979 + 0.9774628615021363*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + -0.21180887804098658*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + -0.7013494059895882*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4])))))/(1 + exp(-(-0.6545847926105979 + 0.9774628615021363*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + -0.21180887804098658*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + -0.7013494059895882*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))))))))/(1 + exp(-(0.8420310557117703 + 0.34198141401583015*(-0.5389217244927917 + 0.5798025520034438*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + 0.4428632765655749*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + 0.9033224385695577*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4])))))/(1 + exp(-(-0.5389217244927917 + 0.5798025520034438*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + 0.4428632765655749*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + 0.9033224385695577*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))))))) + -0.1887519896617813*(-0.23921485861490321 + 0.5030319708107833*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + -0.2081481501163327*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + -0.2939216839670169*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4])))))/(1 + exp(-(-0.23921485861490321 + 0.5030319708107833*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + -0.2081481501163327*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + -0.2939216839670169*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))))))) + -0.2882490310085508*(-0.6545847926105979 + 0.9774628615021363*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + -0.21180887804098658*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + -0.7013494059895882*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4])))))/(1 + exp(-(-0.6545847926105979 + 0.9774628615021363*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + -0.21180887804098658*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + -0.7013494059895882*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4])))))))))) + (-0.35188345522271236 + -0.11415509244934263*(-0.5389217244927917 + 0.5798025520034438*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + 0.4428632765655749*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + 0.9033224385695577*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4])))))/(1 + exp(-(-0.5389217244927917 + 0.5798025520034438*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + 0.4428632765655749*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + 0.9033224385695577*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))))))) + -0.8088728193024375*(-0.23921485861490321 + 0.5030319708107833*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + -0.2081481501163327*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + -0.2939216839670169*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4])))))/(1 + exp(-(-0.23921485861490321 + 0.5030319708107833*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + -0.2081481501163327*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + -0.2939216839670169*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))))))) + 0.8271426964720727*(-0.6545847926105979 + 0.9774628615021363*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + -0.21180887804098658*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + -0.7013494059895882*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4])))))/(1 + exp(-(-0.6545847926105979 + 0.9774628615021363*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + -0.21180887804098658*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + -0.7013494059895882*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))))))))/(1 + exp(-(-0.35188345522271236 + -0.11415509244934263*(-0.5389217244927917 + 0.5798025520034438*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + 0.4428632765655749*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + 0.9033224385695577*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4])))))/(1 + exp(-(-0.5389217244927917 + 0.5798025520034438*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + 0.4428632765655749*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + 0.9033224385695577*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))))))) + -0.8088728193024375*(-0.23921485861490321 + 0.5030319708107833*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + -0.2081481501163327*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + -0.2939216839670169*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4])))))/(1 + exp(-(-0.23921485861490321 + 0.5030319708107833*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + -0.2081481501163327*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + -0.2939216839670169*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))))))) + 0.8271426964720727*(-0.6545847926105979 + 0.9774628615021363*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + -0.21180887804098658*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + -0.7013494059895882*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4])))))/(1 + exp(-(-0.6545847926105979 + 0.9774628615021363*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + -0.21180887804098658*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + -0.7013494059895882*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4])))))))))) + (0.4798469380896484 + 0.8134725364327324*(-0.5389217244927917 + 0.5798025520034438*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + 0.4428632765655749*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + 0.9033224385695577*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4])))))/(1 + exp(-(-0.5389217244927917 + 0.5798025520034438*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + 0.4428632765655749*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + 0.9033224385695577*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))))))) + -0.12585836968848696*(-0.23921485861490321 + 0.5030319708107833*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + -0.2081481501163327*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + -0.2939216839670169*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4])))))/(1 + exp(-(-0.23921485861490321 + 0.5030319708107833*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + -0.2081481501163327*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + -0.2939216839670169*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))))))) + 0.613011911779838*(-0.6545847926105979 + 0.9774628615021363*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + -0.21180887804098658*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + -0.7013494059895882*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4])))))/(1 + exp(-(-0.6545847926105979 + 0.9774628615021363*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + -0.21180887804098658*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + -0.7013494059895882*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))))))))/(1 + exp(-(0.4798469380896484 + 0.8134725364327324*(-0.5389217244927917 + 0.5798025520034438*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + 0.4428632765655749*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + 0.9033224385695577*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4])))))/(1 + exp(-(-0.5389217244927917 + 0.5798025520034438*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + 0.4428632765655749*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + 0.9033224385695577*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))))))) + -0.12585836968848696*(-0.23921485861490321 + 0.5030319708107833*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + -0.2081481501163327*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + -0.2939216839670169*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4])))))/(1 + exp(-(-0.23921485861490321 + 0.5030319708107833*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + -0.2081481501163327*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + -0.2939216839670169*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))))))) + 0.613011911779838*(-0.6545847926105979 + 0.9774628615021363*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + -0.21180887804098658*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + -0.7013494059895882*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4])))))/(1 + exp(-(-0.6545847926105979 + 0.9774628615021363*(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4]))/(1 + exp(-(0.8931287257144485 + 0.7044247236000336*$(x[1]) + 0.9746565459142618*$(x[2]) + -0.37297510015038515*$(x[3]) + 0.2762326932574686*$(x[4])))) + -0.21180887804098658*(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4]))/(1 + exp(-(-0.08155088834170021 + 0.2657824238897022*$(x[1]) + -0.7808635499515542*$(x[2]) + -0.8071446449659851*$(x[3]) + 0.29051627947702796*$(x[4])))) + -0.7013494059895882*(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4]))/(1 + exp(-(0.5572058350137663 + -0.028961919981738138*$(x[1]) + -0.957730339736961*$(x[2]) + -0.8718568123342201*$(x[3]) + -0.17639541567112582*$(x[4])))))))))) - $q <= 0.0))

                     @objective(m, Min, q)

                     return m


# Bogumił Kamiński, 2019-03-25

using Statistics, Distributions

function v_asian_sample1(T, r, K, σ, X₀, m)
    X = X₀
    hatx = zero(X)
    Δ = T / m
    for i in 1:m
        X *= exp((r-σ^2/2)*Δ + σ*√Δ*randn())
        hatx += X
    end
    exp(-r*T)*max(hatx/m - K, 0)
end

function v_asian_sample2(T, r, K, σ, X₀, m)
    Δ = T / m
    X = rand(Normal((r-σ^2/2)*Δ, σ*√Δ), m)
    exp(-r*T)*max(mean(exp.(cumsum(X))) * X₀ - K, 0)
end

function v_asian(T, r, K, σ, X₀, m, n, sampler)
    mean(sampler(T, r, K, σ, X₀, m) for i in 1:n)
end

for f in [v_asian_sample1, v_asian_sample2]
    @time println(v_asian(1.0, 0.05, 55.0, 0.3, 50.0, 10_000, 10_000, f))
end


using ForwardDiff

"""
    black_ivol(F, K, β)

The inverse black function: returns the value `s` such that

    black(K,s) == β

where `F > 0`, K > 0` and `max(F-K, 0) ≤ β ≤ F`.
"""
function black_ivol(F, K, β)
    F > zero(F) && K > zero(K) || throw(DomainError())
    if !(β < F)
        β == F ? (return Inf) : throw(DomainError())
    end

    if K ≥ F
        if !(zero(β) < β)
            β == zero(β) ? (return 0.0) : throw(DomainError())
        end
        _black_ivol(K/F, β/F)
    else
        L = F-K
        if !(L < β)
            β == L ? (return 0.0) : throw(DomainError())
        end
        _black_ivol(F/K, (β-L)/K)
    end
end

"""
    _black_ivol(K, β)

The kernel of the inverse black function: returns the value `s` such that

    black(K,s) == β

where `K ≥ 1` and `0 ≤ β ≤ 1`.
"""
function _black_ivol(K, β)
    maxiters = 4

    logK = log(K)
    x  = -logK
    s₂ = sqrt(-2*x) # point of inflection
    β₂ = 0.5 - K*Φ(-s₂) # cancelation of some terms
    ν₂ = oftype(β₂,invsqrt2π)
    if β < β₂
        s₁ = s₂ - β₂/ν₂
        h₁ = x/s₁;  t₁ = s₁/2
        β₁ = _black(h₁,t₁,K)
        if β < β₁
            # 1. Rational interpolation of transformation
            # (note scaling vs Jaeckel (2013))
            #   f(β)  = -2π*x/(3√3) * Φ(z)^3
            #            where z = x/(√3*s(β))
            #         -> 0   as β -> 0
            #   f′(β) = 2π*z² * ϕ(z)² / √K * exp(z² + s²/8)    # (eq. 4.32)/√K
            #         -> 1/√K as β -> 0
            #   f′′(β) =                                       # (eq. 4.33)/K
            #
            β₀ = 0.0; f₀ = 0.0;  d₀ = 1.0/sqrt(K)

            z₁ = x/(sqrt(3)*s₁)
            Φz₁ = Φ(z₁)
            f₁ = (-2π/(sqrt(27))) * x * Φz₁^3
            w₁ = exp(z₁^2 + s₁^2/8)
            d₁ = 2π * z₁^2 * Φz₁^2 / √K * w₁
            dd₁ = (π/6)*(z₁^2/s₁^3) * Φz₁ * w₁^2 / K *
                ((-8*sqrt(3))*s₁*x + (3*s₁^2*(s₁^2-8) - 8*x^2)*Φz₁/ϕ(z₁))

            Δ = (f₁ - f₀)/(β₁ - β₀)
            r = ((β₁ - β₀)/2*dd₁ + (d₁-d₀))/(d₁ - Δ)
            f = rational_cubic_interpolation(β, β₀,β₁, f₀,f₁, d₀,d₁, r)
            z = invΦ(cbrt(sqrt(27)*f/(-2π*x)))
            s = x/(sqrt(3)*z)

            # Use 3rd order Householder's method with objective function
            #     g(s)   = 1/log(b(s)) - 1/log(β)
            #     g′(s)  = -b′ / (b  * log(b)^2)
            #     g′′(s) = g′ * ( b′′/b′ - b′/b * (1+2/log(b)))
            #     g′′′(s) =  g′ *( b′′′/b′ +   2(b'/b)²·(1+3/ln(b)·(1+1/ln(b)))  -  3(b''/b)·(1+2/ln(b)))

            logβ = log(β)
            for iter = 1:maxiters
                h = x/s; t = s/2
                b = _black(h,t,K)
                logb = log(b)
                ν = _black_vega(h,t)
                bH2 = (h+t)*(h-t)/s
                bH3 = bH2^2 - 3*(h/s)^2 - 1/4

                g′ = -ν / (b*logb^2)
                N  = b/ν*(logβ-logb)*logb/logβ
                H2 = bH2 - ((ν/b)*(1+2/logb))
                H3 = bH3 + (ν/b)*(2*(ν/b)*(1+3/logb*(1+1/log(b))) - 3*bH2*(1+2/logb))

                ds = max(-0.5*s , householder_update(N, H2, H3))
                s += ds
                if abs(ds) < eps(typeof(s))*s
                    break
                end
            end
            return s
        else
            # 2. Rational interpolation between s₁ and s₂
            ν₁ = _black_vega(h₁,t₁)
            # choose r to obtain concave interpolant
            # (this is dominant over 2nd deriv zero at centre)
            Δ = (s₂-s₁)/(β₂-β₁)
            m,M = minmax(1/ν₁ - Δ, Δ - 1/ν₂)
            r = 1 + (1/ν₁-Δ)/(Δ-1/ν₂)
            s = rational_cubic_interpolation(β, β₁,β₂, s₁,s₂, 1/ν₁,1/ν₂, r)
            s₋ = s₁; s₊ = s₂
            @goto middle
        end
    else
        s₃ = s₂ + (1-β₂)/ν₂
        h₃ = x/s₃;  t₃ = s₃/2
        β₃ = _black(h₃,t₃,K)
        if β < β₃
            # 3. Direct rational interpolation between s₂ and s₃
            ν₃ = _black_vega(h₃,t₃)
            # choose r to obtain convex interpolant
            # (this is dominant over 2nd deriv zero at centre)
            Δ = (s₃-s₂)/(β₃-β₂)
            m,M = minmax(1/ν₃ - Δ, Δ - 1/ν₂)
            r = 1 + M/m
            s = rational_cubic_interpolation(β, β₂,β₃, s₂,s₃, 1/ν₂,1/ν₃, r)
            s₋ = s₂; s₊ = s₃

            @label middle # regions 2 & 3

            # Objective function
            #     g(s) = b(s) - β

            for iter = 1:maxiters
                h = x/s; t= s/2
                b = _black(h, t, K)
                ν = _black_vega(h, t)
                N = (β - b)/ν                # (β - b) / vega
                H2 = (h+t)*(h-t)/s           # vomma / vega
                H3 = H2^2 - 3*(h/s)^2 - 1/4  # ultima / vega

                ds = max(-0.5*s , householder_update(N, H2, H3))
                s += ds
                if abs(ds) < eps(typeof(s))
                    break
                end
            end
            return s

        else
            # 4. Rational interpolation of transformation:
            #  fᵤ(β)   = Φ(-s(β)/2)
            #           -> 0      as β -> 1
            #  fᵤ'(β)  = -1/2 * exp(1/2 * (x²/s² + x))
            #          = -1/(2*√K) * exp(h²/2)
            #           -> -1/(2*√K)   as β -> 1
            #  fᵤ''(β) = sqrt(π/2) * x² / s³ * exp(x² / s² + x + s²/8)
            #          = sqrt(π/2) * h²/(s*K) * exp(h² + s²/8)
            #
            # (note scaling 1/√K, 1/K vs Jaeckel (2013))

            β₄ = 1.0
            f₄ = 0.0
            d₄ = -1/(2*sqrt(K))

            f₃ = Φ(-t₃)
            d₃ = d₄ * exp(h₃^2/2)
            dd₃ = sqrt(π/2) * h₃^2 / (s₃*K) * exp(h₃^2 + t₃^2/2)

            Δ = (f₄ - f₃)/(β₄ - β₃)
            r = ((β₄-β₃)/2*dd₃ + (d₄-d₃))/(Δ - d₃)
            f = rational_cubic_interpolation(β, β₃,β₄, f₃,f₄, d₃,d₄, r)
            s = -2*invΦ(f)

            # b′ = _black_vega(h,t)           # vega
            # b′′ = b′ * (h+t)*(h-t)/s  # vomma
            # b′′′ = b′ * (4h^2 - (h+t)(h-t)*(1-(h+t)(h-t)))   # ultima

            # Use 3rd order Householder's method with objective function
            #     g(s)   = log(1-β) - log(1-b(s)) = -log(cb/cβ)
            #     g′(s)  = b′ / (1-b)
            #     g′′(s) = g′ * ( b′′/b′ + g′)
            #     g′′′(s) =  g′ *( b′′′/b′ +  g′ * (2g′ + 3b′′/b′))

            cβ = 1-β

            for iter = 1:maxiters
                h = x/s; t = s/2
                cb = _cblack(h,t,K)
                ν = _black_vega(h,t)
                bH2 = (h+t)*(h-t)/s
                bH3 = bH2^2 - 3*(h/s)^2 - 1/4

                g′ = ν / cb
                N  = log(cb/cβ) / g′
                H2 = bH2 + g′
                H3 = bH3 + g′ * (2*g′ + 3*bH2)

                ds = max(-0.5*s , householder_update(N, H2, H3))
                s += ds

                if abs(ds) < eps(typeof(s))
                    break
                end
            end
            return s

        end
    end
end

"""
    householder_update(N, [H2, [H3]])

The update incrememnt for `n`th order Householder's method (where `n` is the number of arguments) for finding the root of

    f(x) == 0

* `n == 1` is Newton's method
* `n == 2` is Halley's method

Arguments:

* `N`:  the Newton update `-f(x) / f′(x)`
* `H2`: the scaled 2nd order derivative `f′′(x) / f′(x)`
* `H3`: the scaled 3rd order derivative `f′′′(x) / f′(x)`
"""
householder_update(N) = N
householder_update(N, H2) = N/(1+H2*N/2)
householder_update(N, H2, H3) = N*(1+H2*N/2)/(1+N*(H2 + H3*N/6))

"""
    rational_cubic_interpolation(x, x₋, x₊, y₋, y₊, d₋, d₊, r)

Compute a shape preserving rational interpolation.

Arguments

* `x` the value at which to interpolate
* `x₋`,`x₊`: the lower and upper bounds of the interval
* `y₋`,`y₊`: the value of the function at the lower and upper bounds
* `d₋`,`d₊`: the value of the derivative at the lower and upper bounds
* `r`: the control parameter, which must be greater than -1.
  - If `r == 3`, then it is the usual Hermite cubic polynomial
  - As `r → ∞`, it approaches linear interpolation.

If `Δ = (y₊ - y₋)/(x₋ - x₊) > 0`, and `d₋ >= 0`, `d₊ >= 0`, then the interpolant is guaranteed monotonic increasing if

    r >= (d₋ + d₊)/Δ

[DG85, eq. 3.8].

If  `d₋ < Δ < d₊`, then the interpolant is convex if and only if

    r >= 1 + max(d₊ - Δ, Δ - d₋) / min(d₊ - Δ, Δ - d₋)

[DG85, eq. 3.18].

Note: for the purposes of computation, the bounds are actually interchangable (i.e. `x₋` can be the upper bound).

References

* R. Delbourgo and J. A. Gregory (1985) "Shape Preserving Piecewise Rational Interpolation", SIAM J. Sci. and Stat. Comput., 6(4), 967–976. doi:[10.1137/0906065](http://dx.doi.org/10.1137/0906065)

"""
function rational_cubic_interpolation(x, x₋, x₊, y₋, y₊, d₋, d₊, r)
    h = x₊ - x₋
    if h == 0
        return (y₋ + y₊)/2
    end

    # r should be greater than -1. We do not use  assert(r > -1)  here in order to allow values such as NaN to be propagated as they should.
    s = (x - x₋) / h
    t = (x₊ - x) / h

    if r < 2/eps(typeof(r))^2
        ((y₊*s + (r*y₊ - h*d₊)*t)*s^2 + ((r*y₋ + h*d₋)*s + y₋*t)*t^2) /
          (1 + (r - 3) * s * t)
    else
        # Linear interpolation without over-or underflow.
        y₊*s + y₋*t
    end
end