export edge_connectivity, min_edge_cut
export connectivity, min_cut

# min_cut(G,s,t) -- min cut separating s and t
# min_cut(G) -- min cut separating the graph
# flag == true means we want s/t cut

"""
`min_cut(G)` returns a minimum size set of vertices that disconnects `G`.

`min_cut(G,s,t)` returns a minimum size cut that separates `s` and `t`.
"""
function min_cut(G::SimpleGraph{T},s::T, t::T,flag::Bool=true)::Set{T} where T
    n = NV(G)
    m = NE(G)
    n*(n-1)!=2m || error("Graph must not be complete")

    if flag
        has(G,s)    || error("$s is not a vertex of this graph")
        has(G,t)    || error("$t is not a vertex of this graph")
        s!=t        || error("two vertices must be different")
        !has(G,s,t) || error("vertices $s and $t cannot be adjacent")
    else
        if cache_check(G,:min_cut)
            return cache_recall(G,:min_cut)
        end

        if !is_connected(G)
            X = Set{T}()
            cache_save(G,:min_cut,X)
            return X
        end
    end

    VV = vlist(G)
    EE = elist(G)

    MOD = Model(get_solver())

    @variable(MOD,a[VV],Bin)  # in part 1
    @variable(MOD,b[VV],Bin)  # in part 2
    @variable(MOD,c[VV],Bin)  # in cut set

    for v in VV
        @constraint(MOD, a[v]+b[v]+c[v]==1)
    end

    if flag
        @constraint(MOD,a[s]==1)
        @constraint(MOD,b[t]==1)
    end

    @constraint(MOD, sum(a[v] for v in VV)>=1)
    @constraint(MOD, sum(b[v] for v in VV)>=1)

    for e in EE
        v,w = e
        @constraint(MOD,a[v]+b[w]<=1)
        @constraint(MOD,a[w]+b[v]<=1)
    end

    @objective(MOD, Min, sum(c[v] for v in VV))

    optimize!(MOD)
    status = Int(termination_status(MOD))

    C =  value.(c)
    X = Set(v for v in VV if C[v] > 0.5)

    if !flag
        cache_save(G,:min_cut,X)
    end
    return X
end

function min_cut(G::SimpleGraph)::Set
    n = NV(G)
    n>1 || error("Graph must have at least two vertices")
    s = first(G.V)
    return min_cut(G,s,s,false)
end

"""
`connectivity(G)` returns the (vertex) connectivity of `G`, i.e.,
the minimum size of a vertex cut set. If `G` is a complete graph with
`n` vertices, the connectivity is `n-1` (or `0` for the empty graph).
"""
function connectivity(G::SimpleGraph)::Int
    n = NV(G)
    m = NE(G)
    if n*(n-1) == 2m   # graph is complete
        return max(n-1,0)  # what is the connectivity of K_0?
    end

    return length(min_cut(G))
end

# min_edge_cut(G,s,t) -- min edge cut separating s and t
# min_edge_cut(G) -- min edge cut separating the graph
# flag == true means we want s/t cut

"""
`min_edge_cut(G)` returns a minimum size set of edges whose removal
disconnects `G`. The graph must have at least two vertices.

`min_edge_cut(G,s,t)` is a minimum size set of edges whose removal
separates vertices `s` and `t`.
"""
function min_edge_cut(G::SimpleGraph{T}, s::T, t::T, flag::Bool=true)::Set{Tuple{T,T}} where T
    n = NV(G)
    n > 1 || error("Graph must have at least two vertices")

    if flag
        has(G,s) || error("$s is not a vertex of this graph")
        has(G,t) || error("$t is not a vertex of this graph")
        s!=t     || error("vertices must not be the same")
    else
        if cache_check(G,:min_edge_cut)
            return cache_recall(G,:min_edge_cut)
        end

        if !is_connected(G)
            X = Set{Tuple{T,T}}()
            cache_save(G,:min_edge_cut,X)
            return X
        end
    end

    VV = vlist(G)
    EE = elist(G)

    MOD = Model(get_solver())

    @variable(MOD,a[VV],Bin)  # in part 1
    @variable(MOD,b[VV],Bin)  # in part 2
    @variable(MOD,c[EE],Bin)  # span between the parts

    for v in VV
        @constraint(MOD,a[v]+b[v]==1)
    end

    @constraint(MOD,sum(a[v] for v in VV) >= 1)
    @constraint(MOD,sum(b[v] for v in VV) >= 1)

    if flag
        @constraint(MOD,a[s]==1)
        @constraint(MOD,b[t]==1)
    end

    for e in EE
        u,v = e
        @constraint(MOD,a[u]+b[v]-1 <= c[e])
        @constraint(MOD,a[v]+b[u]-1 <= c[e])
    end

    @objective(MOD, Min, sum(c[e] for e in EE))

    optimize!(MOD)
    status = Int(termination_status(MOD))

    C =  value.(c)

    X = Set(e for e in EE if C[e] > 0.5)

    if !flag
        cache_save(G,:min_edge_cut,X)
    end
    return X
end

function min_edge_cut(G::SimpleGraph)::Set
    n = NV(G)
    n>1 || error("Graph must have at least two vertices")
    s = first(G.V)
    return min_edge_cut(G,s,s,false)
end

"""
`edge_connectivity(G)` returns the size of a minimum edge cut of `G`.

`edge_connectivity(G,s,t)` determines the minimum size of an edge cut
separating `s` and `t`.
"""
edge_connectivity(G::SimpleGraph)::Int = length(min_edge_cut(G))
edge_connectivity(G::SimpleGraph,s,v) = length(min_edge_cut(G,s,t))


using TensorDecompositions

function tensordecomp2(Λ::AbstractArray{T,3}, p::Int=5;
    scaleout::Bool=true) where T
    Λ=convert(Array{Float64}, Λ)
    size(Λ)
    𝒯=TensorDecompositions.candecomp(Λ, p,  (randn(size(Λ,1), k), randn(size(Λ,2), k), randn(size(Λ,3), k)), compute_error=true, method=:ALS);
    # 𝒯=TensorToolbox.cp_als(Λ,p)
    𝕏=TensorDecompositions.compose(𝒯)
    A=mean(𝕏[:,:,i] for i=1:size(𝕏,3))
    if scaleout
    A ./=maximum(abs.(A))
    end
    return A
end

#=
u = randn(10); v = randn(20); w = randn(30)
T = cat(map(x -> x * u * v', w)..., dims=3) + 0.2 * randn(10, 20, 30)
k=2
F = candecomp(T, k, (randn(10, k), randn(20, k), randn(30, k)), compute_error=true, method=:ALS);

# https://juliapackages.com/p/ntfk

import NTFk
import TensorDecompositions

csize = (2, 3, 4)
tsize = (5, 10, 15)
tucker_orig = NTFk.rand_tucker(csize, tsize; factors_nonneg=true, core_nonneg=true)

T_orig = TensorDecompositions.compose(tucker_orig)
T_orig .*= 1000

sizes = [csize, (1,3,4), (3,3,4), (2,2,4), (2,4,4), (2,3,3), (2,3,5)]
tucker_estimated, csize_estimated = NTFk.analysis(T_orig, sizes, 3; eigmethod=[false,false,false], progressbar=false, tol=1e-16, max_iter=100000, lambda=0.);
=#


@testset "Newton" begin
    function himmelblau!(∇f, x)
        if !(∇f == nothing)
            ∇f[1] = 4.0 * x[1]^3 + 4.0 * x[1] * x[2] -
                44.0 * x[1] + 2.0 * x[1] + 2.0 * x[2]^2 - 14.0
            ∇f[2] = 2.0 * x[1]^2 + 2.0 * x[2] - 22.0 +
                4.0 * x[1] * x[2] + 4.0 * x[2]^3 - 28.0 * x[2]
        end

        fx = (x[1]^2 + x[2] - 11)^2 + (x[1] + x[2]^2 - 7)^2
        return ∇f == nothing ? fx : (fx, ∇f)
    end

    function himmelblau!(∇²f, ∇f, x)
        if !(∇²f == nothing)
            ∇²f[1, 1] = 12.0 * x[1]^2 + 4.0 * x[2] - 42.0
            ∇²f[1, 2] = 4.0 * x[1] + 4.0 * x[2]
            ∇²f[2, 1] = 4.0 * x[1] + 4.0 * x[2]
            ∇²f[2, 2] = 12.0 * x[2]^2 + 4.0 * x[1] - 26.0
        end
        if !(∇f == nothing)
            ∇f[1] = 4.0 * x[1]^3 + 4.0 * x[1] * x[2] -
                44.0 * x[1] + 2.0 * x[1] + 2.0 * x[2]^2 - 14.0
            ∇f[2] = 2.0 * x[1]^2 + 2.0 * x[2] - 22.0 +
                4.0 * x[1] * x[2] + 4.0 * x[2]^3 - 28.0 * x[2]
        end

        fx = (x[1]^2 + x[2] - 11)^2 + (x[1] + x[2]^2 - 7)^2
        if ∇f == nothing && ∇²f == nothing
            return fx
        elseif ∇²f == nothing
            return fx, ∇f
        else
            return fx, ∇f, ∇²f
        end
    end

    res = minimize!(himmelblau!, copy([2.0,2.0]), Newton(DirectApprox()))
    @test norm(res[3], Inf) < 1e-8
    @printf("NN! Newton (direct): %2.2e  %d\n", norm(res[3], Inf), res[4])
end


export findM
"""
function findM(param,w;nsol,pwr)

Find a binary matrix M in Omega_M that solves

min_M  \| M*w - d \|\_{Gamma,pwr} + lambda \| M \|\_0,

Input:

	1. param - StrainReconParam
	2. w     - vector of weights

Keyword arguments:

	3. nsol  - returns nsol matrices (default = 1); if nsol = 2, first matrix has rows with lowest difference |M(i)*w-d(i)| by each row i and is the MAP estimator, second matrix has rows with second lowest difference |M(i)*w-d(i)| by each row i but is NOT the second "best" or most likely matrix
	4. pwr   - exponent in the likelihood function (default = param.pwr)
    5. probM - returns p(M) for nsol matrices corresponding to "confidence"/probability for each row of M, size(m,nsol), (default = false); p(M(i)) = C* exp(-(1/pwr) * (|M(i)*w - d(i)|_R)^pwr) where R is the diagonal of the inverse square of the noise covariance matrix and C is the normalization constant 1/(sum_{m}{j}(sum_{2^n}{i} exp(-(1/pwr) * (|B(i)*w - d(j)|_R)^pwr))), where B is a binary matrix with all possible permutation of binary entries and has size(2^n,n)

Output:

	1. M  - binary matrix of size (q,n) or an array of size (q,n,nsol)
    2. pM - matrix of p(M) of size (m,nsol), entries in each column \in [0,1] represent "confidence" or probability for the rows of M
"""
function findM(param::StrainReconParam,w::Vector;nsol::Int64=1,pwr::Float64=param.pwr,probM=false)
    Rp = diag(param.R.^pwr)
    d  = param.d
    n  = param.n
    p  = param.p
    m  = param.m

    B  = getB(param)
    Ba = reshape(B*w,p-1,:)'
    Bs = reshape(param.lambda * vec(sum(B,2)), p-1,:)'

    if nsol > 2^n
        println("nsol can only be <= 2^n = $(2^n), only the MAP estimator will be returned")
        nsol = 1
    end

    pM = zeros(m,nsol) # array to store p(M) for each M

    if nsol == 1
        M  = falses(length(d),n)
        for k=1:m
            blockidx = (k-1)*(p-1)+1 : k*(p-1)
            Bdiff = sum(Rp[blockidx]'.*abs.(Ba.-d[blockidx]').^pwr,2) + sum(Bs,2) # |B(ik)*w - d(k)|_R^pwr, size(2^n,1)
            ik = indmin(Bdiff) # index ik in 1:2^n
            mi = B[(ik-1)*(p-1)+1 : ik*(p-1),:] # row(s) of B with lowest difference B[ik]*w - d[k]
            M[blockidx,:] = mi

            # compute p(M_i)

            # println("ik $ik mi $mi Bdiff $Bdiff)")
            # avoid underflow by finding min(Bdiff) and subtracting that from all numbers before taking exp
            minbdiff = minimum(Bdiff)
            pmi = exp.(-(1/pwr)*((Rp[blockidx]'.*abs.(mi*w.-d[blockidx]').^pwr + Bs[ik]) .- minbdiff)) # pmi should always = 1
            #opmi = (Rp[blockidx]'.*abs.(mi*w.-d[blockidx]').^pwr + Bs[ik])
            #println("pmi $pmi \n opmi $opmi minbdiff $minbdiff")
            normconst = sum(exp.(-(1/pwr)*(Bdiff .- minbdiff))) # normalization constant
            #println("normconst $normconst")
            pM[k,nsol] = pmi[1]/normconst
        end
    else
        M  = falses(length(d),n,nsol)
        for k=1:m
            blockidx = (k-1)*(p-1)+1 : k*(p-1)
            Bdiff = vec(sum(Rp[blockidx]'.*abs.(Ba.-d[blockidx]').^pwr,2) + sum(Bs,2))
            ik = sortperm(Bdiff)
            minbdiff = minimum(Bdiff)
            normconst = sum(exp.(-(1/pwr)*(Bdiff .- minbdiff))) # normalization constant for each row
            for j=1:nsol
                # nth best row
                mj = B[(ik[j]-1)*(p-1)+1 : ik[j]*(p-1),:]
                M[blockidx,:,j] = mj

                # compute p(M_i) for each matrix
                ij = ik[j]
                # println("ij $ij mj $mj Bdiff $Bdiff)")
                pmj = exp.(-(1/pwr)*((Rp[blockidx]'.*abs.(mj*w.-d[blockidx]').^pwr + Bs[ij]) .- minbdiff))
                # println("j $j pmj $pmj")
                # println("normconst $normconst")
                pM[k,j] = pmj[1]/normconst
            end
        end
    end

    if probM
        return M, pM
    else
        return M
    end
end


function adaptivesimpsons_inner(
    f::Function,
    a::Real,
    b::Real,
    ε::Real,
    S::Real,
    fa::Real,
    fb::Real,
    fc::Real,
    bottom::Integer,
)
    c = (a + b) / 2
    h = b - a
    d = (a + c) / 2
    g = (c + b) / 2
    fd = f(d)
    fe = f(g)
    Sleft = (h / 12) * (fa + 4 * fd + fc)
    Sright = (h / 12) * (fc + 4 * fe + fb)
    S2 = Sleft + Sright
    if bottom <= 0 || abs(S2 - S) <= 15 * ε
        return S2 + (S2 - S) / 15
    end
    res1 = adaptivesimpsons_inner(
        f,
        a,
        c,
        ε / 2,
        Sleft,
        fa,
        fc,
        fd,
        bottom - 1
    )
    res2 = adaptivesimpsons_inner(
        f,
        c,
        b,
        ε / 2,
        Sright,
        fc,
        fb,
        fe,
        bottom - 1
    )
    return res1 + res2
end

function adaptivesimpsons(
    f::Function,
    a::Real,
    b::Real,
    accuracy::Real = 10e-10,
    max_iterations::Integer = 50,
)
    c = (a + b) / 2
    h = b - a
    fa = f(a)
    fb = f(b)
    fc = f(c)
    S = (h / 6) * (fa + 4 * fc + fb)
    return adaptivesimpsons_inner(
        f,
        a,
        b,
        accuracy,
        S,
        fa,
        fb,
        fc,
        max_iterations
    )
end


geometric_mean(a::Number, b::Number) = √(a*b)

function find_under_and_over(a::Number, possible_values::Array)
    possible_series_sorted = sort(possible_values)
    i, j = 1, 2
        while !(possible_series_sorted[i] ≤ a ≤ possible_series_sorted[j])
            i+=1; j+=1
        end
    return possible_series_sorted[i], possible_series_sorted[j]
end

function norm_to_between_100_and_1000(val::Number; return_OOM=true)
    power_of_10 = 0.0
    while val > 1000
        val/=10; power_of_10 += 1
    end
    while val < 100
        val*=10; power_of_10 += -1
    end
    if return_OOM
        return val, power_of_10
    else
        return val
    end
end


"""
    StuTLik <: Likelihood

Student-t likelihood (a.k.a. non-standardized [Student's
t-distribution](https://en.wikipedia.org/wiki/Student's_t-distribution))
```math
p(y | f, σ) = Γ((ν + 1)/2)/[Γ(ν/2)√(πν)σ](1 + 1/ν((y - f)/σ)²)^{-(ν + 1)/2}
```
with degrees of freedom ``ν ∈ N₀``, where scale ``σ`` is a non-fixed hyperparameter and
``f`` is the latent Gaussian process.
"""
mutable struct StuTLik <: Likelihood
    "Degrees of freedom"
    ν::Int
    "Scale"
    σ::Float64
    "Priors for likelihood parameters"
    priors::Array

    """
        StuTLik(ν::Int, lσ::Float64)

    Create a `StuTLik` with degrees of freedom `ν` and scale `exp(lσ)`.
    """
    StuTLik(ν::Int, lσ::Float64) = new(ν, exp(lσ), [])
end

#log of probability density
function log_dens(studentT::StuTLik, f::VecF64, y::VecF64)
    ν = studentT.ν
    σ = studentT.σ
    c = lgamma(0.5*(ν+1)) - lgamma(0.5*ν) - 0.5*log(pi*ν) - log(σ)
    return [c - (0.5*(ν+1))*log(1+(1/ν)*((yi-fi)/σ)^2) for (fi,yi) in zip(f,y)]
end

#derivative of log pdf wrt latent function
function dlog_dens_df(studentT::StuTLik, f::VecF64, y::VecF64)
    ν = studentT.ν
    σ = studentT.σ
    return [(ν+1)*(yi-fi)/(ν*σ^2 + (yi-fi)^2) for (fi,yi) in zip(f,y)]
end

#derivative of log pdf wrt to parameters
function dlog_dens_dθ(studentT::StuTLik, f::VecF64, y::VecF64)
    ν = studentT.ν
    σ = studentT.σ
    return σ*[-1/σ+(ν+1)*(yi-fi)^2/(ν*σ^3 + σ*(yi-fi)^2) for (fi,yi) in zip(f,y)]
end

#mean and variance under likelihood
mean_lik(studentT::StuTLik, f::VecF64) = f
var_lik(studentT::StuTLik, f::VecF64) = studentT.σ^2*(studentT.ν/(studentT.ν-2.0))*ones(length(f))

function set_params!(studentT::StuTLik, hyp::VecF64)
    length(hyp) == 1 || throw(ArgumentError("Student-t likelihood has only one free parameter"))
    studentT.σ = exp(hyp[])
end

get_params(studentT::StuTLik) = Float64[log(studentT.σ)]
num_params(studentT::StuTLik) = 1


@testset "SIP Res" begin
    # Define semi-infinite program
    f(x) = (1/3)*x[1]^2 + x[2]^2 + x[1]/2
    gSIP(x,p) = (1.0 - (x[1]^2)*(p[1]^2))^2 - x[1]*p[1]^2 - x[2]^2 + x[2]

    x_l = Float64[-1000.0, -1000.0]
    x_u = Float64[1000.0, 1000.0]
    p_l = Float64[0.0]
    p_u = Float64[1.0]

    sip_result = sip_solve(SIPRes(), x_l, x_u, p_l, p_u, f, Any[gSIP], abs_tolerance = 1E-3)

    @test isapprox(sip_result.lower_bound, 0.19446619886176916, atol = 1E-3)
    @test isapprox(sip_result.upper_bound, 0.19500611503848964, atol = 1E-3)
    @test isapprox(sip_result.xsol[1], -0.7500000115038946, atol = 1E-2)
    @test isapprox(sip_result.xsol[2], -0.6184706298867955, atol = 1E-2)
end
#=
@testset "SIP ResRev" begin
    # Define semi-infinite program
    f(x) = (1/3)*x[1]^2 + x[2]^2 + x[1]/2
    gSIP(x,p) = (1.0 - (x[1]^2)*(p[1]^2))^2 - x[1]*p[1]^2 - x[2]^2 + x[2]

    x_l = Float64[-1000.0, -1000.0]
    x_u = Float64[1000.0, 1000.0]
    p_l = Float64[0.0]
    p_u = Float64[1.0]

    sip_result = sip_solve(SIPResRev(), x_l, x_u, p_l, p_u, f, Any[gSIP], abs_tolerance = 1E-3)

    @test isapprox(sip_result.lower_bound, 0.19446619886176916, atol = 1E-3)
    @test isapprox(sip_result.upper_bound, 0.19500611503848964, atol = 1E-3)
    @test isapprox(sip_result.xsol[1], -0.7500000115038946, atol = 1E-2)
    @test isapprox(sip_result.xsol[2], -0.6184706298867955, atol = 1E-2)
end
=#
@testset "SIP Hybrid" begin
    # Define semi-infinite program
    f(x) = (1/3)*x[1]^2 + x[2]^2 + x[1]/2
    gSIP(x,p) = (1.0 - (x[1]^2)*(p[1]^2))^2 - x[1]*p[1]^2 - x[2]^2 + x[2]

    x_l = Float64[-1000.0, -1000.0]
    x_u = Float64[1000.0, 1000.0]
    p_l = Float64[0.0]
    p_u = Float64[1.0]

    sip_result = sip_solve(SIPHybrid(), x_l, x_u, p_l, p_u, f, Any[gSIP], abs_tolerance = 1E-3)

    @test isapprox(sip_result.lower_bound, 0.19446619886176916, atol = 1E-3)
    @test isapprox(sip_result.upper_bound, 0.19500611503848964, atol = 1E-3)
    @test isapprox(sip_result.xsol[1], -0.7500000115038946, atol = 1E-2)
    @test isapprox(sip_result.xsol[2], -0.6184706298867955, atol = 1E-2)
end


immutable TDist <: ContinuousUnivariateDistribution
    df::Float64 # non-integer degrees of freedom allowed
    function TDist(d::Real)
    	d > zero(d) || error("df must be positive")
        new(float64(d))
    end
end

@_jl_dist_1p TDist t

function entropy(d::TDist)
    ((d.df + 1.0) / 2.0) *
        (digamma((d.df + 1.0) / 2.0) - digamma((d.df) / 2.0)) +
        (1.0 / 2.0) * log(d.df) + lbeta(d.df + 1.0, 1.0 / 2.0)
end

insupport(::TDist, x::Real) = isfinite(x)
insupport(::Type{TDist}, x::Real) = isfinite(x)

mean(d::TDist) = d.df > 1.0 ? 0.0 : NaN

median(d::TDist) = 0.0

mode(d::TDist) = 0.0
modes(d::TDist) = [0.0]

function pdf(d::TDist, x::Real)
    1.0 / (sqrt(d.df) * beta(0.5, 0.5 * d.df)) *
        (1.0 + x^2 / d.df)^(-0.5 * (d.df + 1.0))
end

function var(d::TDist)
    d.df > 2.0 && return d.df / (d.df - 2.0)
    d.df > 1.0 && return Inf
    NaN
end


### A Pluto.jl notebook ###
# v0.12.16

using Markdown
using InteractiveUtils

# ╔═╡ e077ab16-22e5-11eb-15ed-5f0654bf7328
using Pkg, DrWatson

# ╔═╡ e75a9a06-22e5-11eb-2e36-8d4f62b830ed
begin
	@quickactivate "StatisticsWithJuliaPlutoNotebooks"
	using Random, Distributions, Plots
	using StatsBase, LaTeXStrings
	Random.seed!(0)
end

# ╔═╡ ca5fbdc8-22e5-11eb-0a60-cb0a127a3ca5
md"## Listing5.15"

# ╔═╡ fa68607e-22e5-11eb-0558-c9a4d9f77426
begin
	mu0, mu1, sd, tau  = 15, 18, 2, 17.5
	dist0, dist1 = Normal(mu0,sd), Normal(mu1,sd)
	grid = 5:0.1:25
	h0grid, h1grid = tau:0.1:25, 5:0.1:tau
end

# ╔═╡ 57e2a694-3af9-11eb-0f9e-7d8df75869e6
Text("Probability of Type I error: $(ccdf(dist0,tau))")

# ╔═╡ 57e2e668-3af9-11eb-0e7c-033e27bf1e92
Text("Probability of Type II error: $(cdf(dist1,tau))")

# ╔═╡ 57e37538-3af9-11eb-1477-b9387969d828
begin
	plot(grid, pdf.(dist0,grid),
		c=:blue, label="Bolt type 15g")
	plot!(h0grid, pdf.(dist0, h0grid),
		c=:blue, fa=0.2, fillrange=[0 1], label="")
	plot!(grid, pdf.(dist1,grid),
		c=:green, label="Bolt type 18g")
	plot!(h1grid, pdf.(dist1, h1grid),
		c=:green, fa=0.2, fillrange=[0 1], label="")
	plot!([tau, 25],[0,0],
		c=:red, lw=3, label="Rejection region",
		xlims=(5, 25), ylims=(0,0.25) , legend=:topleft,
		xlabel="x", ylabel="Density")
	annotate!([(16, 0.02, text(L"\beta")),(18.5, 0.02, text(L"\alpha")),
				(15, 0.21, text(L"H_0")),(18, 0.21, text(L"H_1"))])
end

# ╔═╡ 475ff888-22e6-11eb-2354-09f8f40a8e12
md"## End of listing5.15"

# ╔═╡ Cell order:
# ╟─ca5fbdc8-22e5-11eb-0a60-cb0a127a3ca5
# ╠═e077ab16-22e5-11eb-15ed-5f0654bf7328
# ╠═e75a9a06-22e5-11eb-2e36-8d4f62b830ed
# ╠═fa68607e-22e5-11eb-0558-c9a4d9f77426
# ╠═57e2a694-3af9-11eb-0f9e-7d8df75869e6
# ╠═57e2e668-3af9-11eb-0e7c-033e27bf1e92
# ╠═57e37538-3af9-11eb-1477-b9387969d828
# ╟─475ff888-22e6-11eb-2354-09f8f40a8e12


# Julia translation of http://nbviewer.jupyter.org/github/barbagroup/AeroPython/blob/master/lessons/03_Lesson03_doublet.ipynb
# Lesson 3 doublet

using PyPlot
using Distributions

close("all")
meshgrid(x,y) = (repmat(x',length(y),1),repmat(y,1,length(x)))

N = 50                                # number of points in each direction
x_start, x_end = -2.0, 2.0            # boundaries in the x-direction
y_start, y_end = -1.0, 1.0            # boundaries in the y-direction
x = linspace(x_start, x_end, N)    # creates a 1D-array with the x-coordinates
y = linspace(y_start, y_end, N)    # creates a 1D-array with the y-coordinates

X,Y=meshgrid(x,y)

kappa = 1.0

x_doublet, y_doublet = 0.0, 0.0   # location of the source

function get_velocity_doublet(strength, xd, yd, X, Y):
    """Returns the velocity field generated by a doublet.

    Arguments
    ---------
    strength -- strength of the doublet.
    xd, yd -- coordinates of the doublet.
    X, Y -- mesh grid.
    """
    u = - strength/(2*pi).*((X-xd).^2-(Y-yd).^2)./((X-xd).^2+(Y-yd).^2).^2
    v = - strength/(2*pi)*2.0.*(X-xd).*(Y-yd)./((X-xd).^2+(Y-yd).^2).^2

    return u, v
  end

function get_stream_function_doublet(strength, xd, yd, X, Y):
    """Returns the stream-function generated by a doublet.

    Arguments
    ---------
    strength -- strength of the doublet.
    xd, yd -- coordinates of the doublet.
    X, Y -- mesh grid.
    """
    psi = - strength/(2*pi).*(Y-yd)./((X-xd).^2+(Y-yd).^2)

    return psi
  end

  # computes the velocity field on the mesh grid
u_doublet, v_doublet = get_velocity_doublet(kappa, x_doublet, y_doublet, X, Y)

# computes the stream-function on the mesh grid
psi_doublet = get_stream_function_doublet(kappa, x_doublet, y_doublet, X, Y)

  # plots the streamlines
  size = 10
  PyPlot.figure(figsize=(size, (y_end-y_start)/(x_end-x_start)*size))
  PyPlot.grid(true)
  PyPlot.xlabel("x", fontsize=16)
  PyPlot.ylabel("y", fontsize=16)
  PyPlot.xlim(x_start, x_end)
  PyPlot.ylim(y_start, y_end)
  PyPlot.streamplot(X, Y, u_doublet, v_doublet, density=2, linewidth=1, arrowsize=1, arrowstyle="->")
  PyPlot.scatter(x_doublet, y_doublet, color="#CD2305", s=80, marker="o")

# Freestream and Doublet

  u_inf = 1.0

  # computes the freestream velocity field
  u_freestream = u_inf * ones((N, N))
  v_freestream = zeros((N, N))

  # computes the stream-function
  psi_freestream = u_inf * Y

  u = u_freestream + u_doublet
  v = v_freestream + v_doublet
  psi = psi_freestream + psi_doublet

  # plots the streamlines
  size = 10
  PyPlot.figure(figsize=(size, (y_end-y_start)/(x_end-x_start)*size))
  PyPlot.grid(true)
  PyPlot.xlabel("x", fontsize=16)
  PyPlot.ylabel("y", fontsize=16)
  PyPlot.xlim(x_start, x_end)
  PyPlot.ylim(y_start, y_end)
  PyPlot.streamplot(X, Y, u, v, density=2, linewidth=1, arrowsize=1, arrowstyle="->")
  PyPlot.contour(X, Y, psi, levels=[0.], colors="#CD2305", linewidths=2, linestyles="solid");
  PyPlot.scatter(x_doublet, y_doublet, color="#CD2305", s=80, marker="o")

  # calculates the stagnation points
x_stagn1, y_stagn1 = +sqrt(kappa/(2*pi*u_inf)), 0
x_stagn2, y_stagn2 = -sqrt(kappa/(2*pi*u_inf)), 0

# adds the stagnation points to the figure
PyPlot.scatter([x_stagn1, x_stagn2], [y_stagn1, y_stagn2], color="g", s=80, marker="o");

# computes the pressure coefficient field
cp = 1.0 - (u.^2+v.^2)./u_inf^2

# plots the pressure coefficient field
size = 10
PyPlot.figure(figsize=(1.1*size, (y_end-y_start)/(x_end-x_start)*size))
PyPlot.xlabel("x", fontsize=16)
PyPlot.ylabel("y", fontsize=16)
PyPlot.xlim(x_start, x_end)
PyPlot.ylim(y_start, y_end)
contf = PyPlot.contourf(X, Y, cp, levels=linspace(-2.0, 1.0, 100), extend="both")
cbar = PyPlot.colorbar(contf)
cbar[:set_label](L"$C_p$", fontsize=16)
cbar[:set_ticks]([-2.0, -1.0, 0.0, 1.0])
PyPlot.scatter(x_doublet, y_doublet, color="#CD2305", s=80, marker="o")
PyPlot.contour(X, Y, psi, levels=[0.], colors="#CD2305", linewidths=2, linestyles="solid");
PyPlot.scatter([x_stagn1, x_stagn2], [y_stagn1, y_stagn2], color="g", s=80, marker="o");


using Revise
using AdFem
using PyPlot

m = 30
n = 30
h = 1/n

xy = fem_nodes(m, n, h)
x, y = xy[:,1], xy[:,2]
u0 = [@. (2(x^3/3-x^2/2)); @. (2(y^3/3 - y^2/2))]

xy = fvm_nodes(m, n, h)
x, y = xy[:,1], xy[:,2]
p0 = @. x*(1-x)*y*(1-y)

ρ = dt = 1
function step2(u0)
    B = compute_interaction_matrix(m, n, h)
    bc = bcedge("all", m, n, h)
    A, _ = compute_fvm_tpfa_matrix(ones(m*n), bc, zeros(size(bc,1)), m, n, h)
    rhs = ρ / dt * B * u0
    sol = A\rhs
    return sol
end
bc = bcedge("all", m, n, h)

# There are three ways to compute
# int div u dx
# on each element

# rhs = compute_fvm_mechanics_term(u0, m, n, h)
B = compute_interaction_matrix(m, n, h)
rhs = B * u0
# rhs = h^2 * @. -2x*(1-x)-2y*(1-y)

A, _ = compute_fvm_tpfa_matrix(ones(m*n), bc, zeros(size(bc,1)), m, n, h)
sol = A\rhs

sol = step2(u0)

figure(figsize=(10,4))
subplot(121)
visualize_scalar_on_fvm_points(sol, m, n, h)
subplot(122)
visualize_scalar_on_fvm_points(p0, m, n, h)


using JuMP
using Clp

LARGE_VALUE = 100000
DEMANDS = [400 300 420 380]
SUPPLY = [500, 600, 200, 300]
COSTS = [100 103 106 109;
        LARGE_VALUE 140 143 146;
        LARGE_VALUE LARGE_VALUE 120 123;
        LARGE_VALUE LARGE_VALUE LARGE_VALUE 150]

m = Model(Clp.Optimizer)
@variable(m, x[1:4, 1:4] ≥ 0)
@objective(m, Min, sum(COSTS .* x))
@constraint(m, sum(x, dims=2) .≤ SUPPLY)
@constraint(m, sum(x, dims=1) .== DEMANDS)
print(m)
optimize!(m)
status = termination_status(m)
println("Solution status: ", status)
println("Objective value: ", objective_value(m))
println("Values: ", value.(x))


# This file contains essentials functions to define
# tables to geometric, inner and outer products.

"""
```
bbgeometric(a::BasisBlade, b::BasisBlade) (Liga function)
```
This function is used to determine the geometric product
of basis blade. Essentially, this function is used when layout
function is trigged, but it can be used separated too.
## Example
```julia-repl
julia> bbgeometric(e1,e12)
```
returns e2.
"""
function bbgeometric(a::BasisBlade, b::BasisBlade)
		len = GIdLayout[1]+GIdLayout[2] #p+q
		Ea = copy(LogicalBasisBlade[a.index])
		Eb = copy(LogicalBasisBlade[b.index])
		abscalar = 1
		abindex = 1
		ablogical = Vector{Bool}(undef,len)
		cont = 0
		for i=1:len-1
			if Eb[i] == true
				for j=i+1:len
					if Ea[j] == true
						cont = cont + 1
					end
				end
			end
		end
		for i=1:len
			if Ea[i] != Eb[i]
				ablogical[i] = true
			else
					ablogical[i] = false
					if Ea[i] == true && i>GIdLayout[1]
						cont =cont +1
					end
			end
		end
		abscalar = ((-1)^cont)
		ind=1
		while ablogical != LogicalBasisBlade[ind]
			ind = ind+1
		end
		abindex = ind
		ab=BasisBlade(abscalar*abindex)
		return ab
end

"""
```
bbinner(a::BasisBlade,b::BasisBlade) (Liga function)
```
This function is used to determine the inner product
of basis blades. Essentially, this function is used when layout
function is trigged, but it can be used separated too.
## Example
```julia-repl
julia> bbinner(e12,e2)
```
returns the Basis Blade e1.
"""
function bbinner(a::BasisBlade,b::BasisBlade)
		k=grade(a)
		l=grade(b)
		if k != 0 && l !=0
			return gradeprojection(bbgeometric(a,b),abs(k-l))
		else
			return BasisBlade(0)
		end
end

"""
```
bbouter(a::BasisBlade,b::BasisBlade) (Liga function)
```
This function is used to determine the outer product
of basis blades. Essentially, this function is used when layout
function is trigged, but it can be used separated too.
## Example
```julia-repl
julia> bbouter(e12,e3)
```
returns the Basis Blade e123.
"""
function bbouter(a::BasisBlade,b::BasisBlade)
	k=grade(a)
	l=grade(b)
	return gradeprojection(bbgeometric(a,b),k+l)
end

"""
```
OperationTable() (Liga function)
```
OperationTable is function trigged by layout function and
it is used to define the operational table related to geometric,
inner and outer product. As result, constants array are created
to optimize these products to multivectors.
## Example
```julia-repl
julia> A,B,C,D=OperationTable()
```
returns A a table for geometric product, B a table for inner product,
C a table for outer product and D a tensor.
"""
function OperationTable()
	len=2^(GIdLayout[1]+GIdLayout[2])
	OT=Array{Any,2}(undef,len,len)
	IOT=Array{Any,2}(undef,len,len)
	OOT=Array{Any,2}(undef,len,len)
	Tensor=Vector{SparseMatrixCSC}(undef,len)
	for i=1:len
        for j=1:len
			OT[i,j]=bbgeometric(BasisBlade(i),BasisBlade(j))
			IOT[i,j]=bbinner(BasisBlade(i),BasisBlade(j))
			OOT[i,j]=bbouter(BasisBlade(i),BasisBlade(j))
		end
	end
	for k=1:len
		Tensor[k]=spzeros(len,len)
		for i=1:len
			for j=1:len
				if OT[i,j]==BasisBlade(k)
					Tensor[k][i,j]=1.0
				end
				if OT[i,j]==-BasisBlade(k)
				   Tensor[k][i,j]=-1.0
			    end
			end
		end
	end
	#display(Tensor)
	return OT,IOT,OOT,Tensor
end


using Rubin
using Tests
using Elliptic
using HypergeometricFunctions
using Polylogarithms
using SpecialFunctions

@test integrate(x^2*SpecialFunctions.expint(1,b*x), x) == :(-1//3*x^3*SpecialFunctions.expint(-2,b*x)+1//3*x^3*SpecialFunctions.expint(1,b*x))
@test integrate(x*SpecialFunctions.expint(1,b*x), x) == :((1/2)*x^2*SpecialFunctions.expint(1,b*x)+-1//2*x^2*SpecialFunctions.expint(-1,b*x))
@test integrate(SpecialFunctions.expint(1,b*x), x) == :(-1*b^-1*SpecialFunctions.expint(2,b*x))
@test integrate(x^-2*SpecialFunctions.expint(1,b*x), x) == :(x^-1*SpecialFunctions.expint(2,b*x)+-1*x^-1*SpecialFunctions.expint(1,b*x))
@test integrate(x^-3*SpecialFunctions.expint(1,b*x), x) == :((1/2)*x^-2*SpecialFunctions.expint(3,b*x)+-1//2*x^-2*SpecialFunctions.expint(1,b*x))
@test integrate(x^-4*SpecialFunctions.expint(1,b*x), x) == :(-1//3*x^-3*SpecialFunctions.expint(1,b*x)+1//3*x^-3*SpecialFunctions.expint(4,b*x))
@test integrate(x^2*SpecialFunctions.expint(2,b*x), x) == :(-1//4*x^3*SpecialFunctions.expint(-2,b*x)+1//4*x^3*SpecialFunctions.expint(2,b*x))
@test integrate(x*SpecialFunctions.expint(2,b*x), x) == :(-1//3*x^2*SpecialFunctions.expint(-1,b*x)+1//3*x^2*SpecialFunctions.expint(2,b*x))
@test integrate(SpecialFunctions.expint(2,b*x), x) == :(-1*b^-1*SpecialFunctions.expint(3,b*x))
@test integrate(x^-1*SpecialFunctions.expint(2,b*x), x) == :(-1*SpecialFunctions.expint(1,b*x)+SpecialFunctions.expint(2,b*x))
@test integrate(x^-3*SpecialFunctions.expint(2,b*x), x) == :(x^-2*SpecialFunctions.expint(3,b*x)+-1*x^-2*SpecialFunctions.expint(2,b*x))
@test integrate(x^-4*SpecialFunctions.expint(2,b*x), x) == :((1/2)*x^-3*SpecialFunctions.expint(4,b*x)+-1//2*x^-3*SpecialFunctions.expint(2,b*x))
@test integrate(x^-5*SpecialFunctions.expint(2,b*x), x) == :(-1//3*x^-4*SpecialFunctions.expint(2,b*x)+1//3*x^-4*SpecialFunctions.expint(5,b*x))
@test integrate(x^2*SpecialFunctions.expint(3,b*x), x) == :(-1//5*x^3*SpecialFunctions.expint(-2,b*x)+1//5*x^3*SpecialFunctions.expint(3,b*x))
@test integrate(x*SpecialFunctions.expint(3,b*x), x) == :(-1//4*x^2*SpecialFunctions.expint(-1,b*x)+1//4*x^2*SpecialFunctions.expint(3,b*x))
@test integrate(SpecialFunctions.expint(3,b*x), x) == :(-1*b^-1*SpecialFunctions.expint(4,b*x))
@test integrate(x^-1*SpecialFunctions.expint(3,b*x), x) == :((1/2)*SpecialFunctions.expint(3,b*x)+-1//2*SpecialFunctions.expint(1,b*x))
@test integrate(x^-2*SpecialFunctions.expint(3,b*x), x) == :(x^-1*SpecialFunctions.expint(3,b*x)+-1*x^-1*SpecialFunctions.expint(2,b*x))
@test integrate(x^-4*SpecialFunctions.expint(3,b*x), x) == :(x^-3*SpecialFunctions.expint(4,b*x)+-1*x^-3*SpecialFunctions.expint(3,b*x))
@test integrate(x^-5*SpecialFunctions.expint(3,b*x), x) == :((1/2)*x^-4*SpecialFunctions.expint(5,b*x)+-1//2*x^-4*SpecialFunctions.expint(3,b*x))
@test integrate(x^-6*SpecialFunctions.expint(3,b*x), x) == :(-1//3*x^-5*SpecialFunctions.expint(3,b*x)+1//3*x^-5*SpecialFunctions.expint(6,b*x))
@test integrate(x^3*SpecialFunctions.expint(-1,b*x), x) == :((1/2)*x^4*SpecialFunctions.expint(-1,b*x)+-1//2*x^4*SpecialFunctions.expint(-3,b*x))
@test integrate(x^2*SpecialFunctions.expint(-1,b*x), x) == :(x^3*SpecialFunctions.expint(-1,b*x)+-1*x^3*SpecialFunctions.expint(-2,b*x))
@test integrate(x*SpecialFunctions.expint(-1,b*x), x) == :(b^-2*expintegralei(-1*b*x)+-1*b^-2*exp(-1*b*x))
@test integrate(SpecialFunctions.expint(-1,b*x), x) == :(-1*b^-2*x^-1*exp(-1*b*x))
@test integrate(x^-1*SpecialFunctions.expint(-1,b*x), x) == :((1/2)*SpecialFunctions.expint(1,b*x)+-1//2*SpecialFunctions.expint(-1,b*x))
@test integrate(x^-2*SpecialFunctions.expint(-1,b*x), x) == :(-1//3*x^-1*SpecialFunctions.expint(-1,b*x)+1//3*x^-1*SpecialFunctions.expint(2,b*x))
@test integrate(x^-3*SpecialFunctions.expint(-1,b*x), x) == :(-1//4*x^-2*SpecialFunctions.expint(-1,b*x)+1//4*x^-2*SpecialFunctions.expint(3,b*x))
@test integrate(x^4*SpecialFunctions.expint(-2,b*x), x) == :((1/2)*x^5*SpecialFunctions.expint(-2,b*x)+-1//2*x^5*SpecialFunctions.expint(-4,b*x))
@test integrate(x^3*SpecialFunctions.expint(-2,b*x), x) == :(x^4*SpecialFunctions.expint(-2,b*x)+-1*x^4*SpecialFunctions.expint(-3,b*x))
@test integrate(x^2*SpecialFunctions.expint(-2,b*x), x) == :(-2*b^-3*exp(-1*b*x)+2*b^-3*expintegralei(-1*b*x)+-1*b^-1*x^2*SpecialFunctions.expint(-1,b*x))
@test integrate(x*SpecialFunctions.expint(-2,b*x), x) == :(x^2*SpecialFunctions.expint(-1,b*x)+-1*x^2*SpecialFunctions.expint(-2,b*x))
@test integrate(SpecialFunctions.expint(-1,b*x), x) == :(-1*b^-2*x^-1*exp(-1*b*x))
@test integrate(x^-1*SpecialFunctions.expint(-2,b*x), x) == :(-1//3*SpecialFunctions.expint(-2,b*x)+1//3*SpecialFunctions.expint(1,b*x))
@test integrate(x^-2*SpecialFunctions.expint(-2,b*x), x) == :(-1//4*x^-1*SpecialFunctions.expint(-2,b*x)+1//4*x^-1*SpecialFunctions.expint(2,b*x))
@test integrate(x^-3*SpecialFunctions.expint(-2,b*x), x) == :(-1//5*x^-2*SpecialFunctions.expint(-2,b*x)+1//5*x^-2*SpecialFunctions.expint(3,b*x))
@test integrate(x^5*SpecialFunctions.expint(-3,b*x), x) == :((1/2)*x^6*SpecialFunctions.expint(-3,b*x)+-1//2*x^6*SpecialFunctions.expint(-5,b*x))
@test integrate(x^4*SpecialFunctions.expint(-3,b*x), x) == :(x^5*SpecialFunctions.expint(-3,b*x)+-1*x^5*SpecialFunctions.expint(-4,b*x))
@test integrate(x^3*SpecialFunctions.expint(-3,b*x), x) == :(-6*b^-4*exp(-1*b*x)+6*b^-4*expintegralei(-1*b*x)+-1*b^-1*x^3*SpecialFunctions.expint(-2,b*x)+-3*b^-2*x^2*SpecialFunctions.expint(-1,b*x))
@test integrate(x^2*SpecialFunctions.expint(-3,b*x), x) == :(x^3*SpecialFunctions.expint(-2,b*x)+-1*x^3*SpecialFunctions.expint(-3,b*x))
@test integrate(x*SpecialFunctions.expint(-3,b*x), x) == :((1/2)*x^2*SpecialFunctions.expint(-1,b*x)+-1//2*x^2*SpecialFunctions.expint(-3,b*x))
@test integrate(SpecialFunctions.expint(-1,b*x), x) == :(-1*b^-2*x^-1*exp(-1*b*x))
@test integrate(x^-1*SpecialFunctions.expint(-3,b*x), x) == :(-1//4*SpecialFunctions.expint(-3,b*x)+1//4*SpecialFunctions.expint(1,b*x))
@test integrate(x^-2*SpecialFunctions.expint(-3,b*x), x) == :(-1//5*x^-1*SpecialFunctions.expint(-3,b*x)+1//5*x^-1*SpecialFunctions.expint(2,b*x))
@test integrate(x^-3*SpecialFunctions.expint(-3,b*x), x) == :(-1//6*x^-2*SpecialFunctions.expint(-3,b*x)+1//6*x^-2*SpecialFunctions.expint(3,b*x))
@test integrate(x^3*SpecialFunctions.expint(-3,b*x), x) == :(-6*b^-4*exp(-1*b*x)+6*b^-4*expintegralei(-1*b*x)+-1*b^-1*x^3*SpecialFunctions.expint(-2,b*x)+-3*b^-2*x^2*SpecialFunctions.expint(-1,b*x))
@test integrate(x^2*SpecialFunctions.expint(-2,b*x), x) == :(-2*b^-3*exp(-1*b*x)+2*b^-3*expintegralei(-1*b*x)+-1*b^-1*x^2*SpecialFunctions.expint(-1,b*x))
@test integrate(x*SpecialFunctions.expint(-1,b*x), x) == :(b^-2*expintegralei(-1*b*x)+-1*b^-2*exp(-1*b*x))
@test integrate(SpecialFunctions.expint(0,b*x), x) == :(b^-1*expintegralei(-1*b*x))
@test integrate(x^m*SpecialFunctions.expint(n,x), x) == :(x^(1+m)*(m+n)^-1*SpecialFunctions.expint(n,x)+-1*x^(1+m)*(m+n)^-1*SpecialFunctions.expint(-1m,x))
@test integrate(x^m*SpecialFunctions.expint(n,b*x), x) == :(x^(1+m)*(m+n)^-1*SpecialFunctions.expint(n,b*x)+-1*x^(1+m)*(m+n)^-1*SpecialFunctions.expint(-1m,b*x))
@test integrate((d*x)^m*SpecialFunctions.expint(n,x), x) == :(d^-1*(d*x)^(1+m)*(m+n)^-1*SpecialFunctions.expint(n,x)+-1*d^-1*(d*x)^(1+m)*(m+n)^-1*SpecialFunctions.expint(-1m,x))
@test integrate((d*x)^m*SpecialFunctions.expint(n,b*x), x) == :(d^-1*(d*x)^(1+m)*(m+n)^-1*SpecialFunctions.expint(n,b*x)+-1*d^-1*(d*x)^(1+m)*(m+n)^-1*SpecialFunctions.expint(-1m,b*x))
@test integrate(x^2*SpecialFunctions.expint(n,b*x), x) == :(x^3*(2+n)^-1*SpecialFunctions.expint(n,b*x)+-1*x^3*(2+n)^-1*SpecialFunctions.expint(-2,b*x))
@test integrate(x*SpecialFunctions.expint(n,b*x), x) == :(x^2*(1+n)^-1*SpecialFunctions.expint(n,b*x)+-1*x^2*(1+n)^-1*SpecialFunctions.expint(-1,b*x))
@test integrate(SpecialFunctions.expint(n,b*x), x) == :(-1*b^-1*SpecialFunctions.expint(1+n,b*x))
@test integrate(x^-1*SpecialFunctions.expint(n,b*x), x) == :((1+-1n)^-1*SpecialFunctions.expint(1,b*x)+-1*(1+-1n)^-1*SpecialFunctions.expint(n,b*x))
@test integrate(x^-2*SpecialFunctions.expint(n,b*x), x) == :(x^-1*(2+-1n)^-1*SpecialFunctions.expint(2,b*x)+-1*x^-1*(2+-1n)^-1*SpecialFunctions.expint(n,b*x))
@test integrate(x^-3*SpecialFunctions.expint(n,b*x), x) == :(x^-2*(3+-1n)^-1*SpecialFunctions.expint(3,b*x)+-1*x^-2*(3+-1n)^-1*SpecialFunctions.expint(n,b*x))
@test integrate((c+d*x)^3*SpecialFunctions.expint(1,a+b*x), x) == :(-1*b^-1*(c+d*x)^3*SpecialFunctions.expint(2,a+b*x)+-6*b^-4*d^3*SpecialFunctions.expint(5,a+b*x)+-6*b^-3*d^2*(c+d*x)*SpecialFunctions.expint(4,a+b*x)+-3*d*b^-2*(c+d*x)^2*SpecialFunctions.expint(3,a+b*x))
@test integrate((c+d*x)^2*SpecialFunctions.expint(1,a+b*x), x) == :(-1*b^-1*(c+d*x)^2*SpecialFunctions.expint(2,a+b*x)+-2*b^-3*d^2*SpecialFunctions.expint(4,a+b*x)+-2*d*b^-2*(c+d*x)*SpecialFunctions.expint(3,a+b*x))
@test integrate((c+d*x)*SpecialFunctions.expint(1,a+b*x), x) == :(-1*d*b^-2*SpecialFunctions.expint(3,a+b*x)+-1*b^-1*(c+d*x)*SpecialFunctions.expint(2,a+b*x))
@test integrate(SpecialFunctions.expint(1,a+b*x), x) == :(-1*b^-1*SpecialFunctions.expint(2,a+b*x))
@test integrate((c+d*x)^-2*SpecialFunctions.expint(1,a+b*x), x) == :(-1*d^-1*(c+d*x)^-1*SpecialFunctions.expint(1,a+b*x)+-1*b*d^-1*(b*c+-1*a*d)^-1*expintegralei(-1a+-1*b*x)+b*d^-1*(b*c+-1*a*d)^-1*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x)))
@test integrate((c+d*x)^-3*SpecialFunctions.expint(1,a+b*x), x) == :(-1//2*d^-1*(c+d*x)^-2*SpecialFunctions.expint(1,a+b*x)+-1//2*b^2*d^-1*(b*c+-1*a*d)^-2*expintegralei(-1a+-1*b*x)+(1/2)*b^2*d^-1*(b*c+-1*a*d)^-2*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x))+-1//2*b*d^-1*(c+d*x)^-1*(b*c+-1*a*d)^-1*exp(-1a+-1*b*x)+-1//2*b^2*d^-2*(b*c+-1*a*d)^-1*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x)))
@test integrate((c+d*x)^-4*SpecialFunctions.expint(1,a+b*x), x) == :(-1//3*d^-1*(c+d*x)^-3*SpecialFunctions.expint(1,a+b*x)+-1//3*b^3*d^-1*(b*c+-1*a*d)^-3*expintegralei(-1a+-1*b*x)+-1//3*b^2*d^-1*(c+d*x)^-1*(b*c+-1*a*d)^-2*exp(-1a+-1*b*x)+-1//3*b^3*d^-2*(b*c+-1*a*d)^-2*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x))+-1//6*b*d^-1*(c+d*x)^-2*(b*c+-1*a*d)^-1*exp(-1a+-1*b*x)+1//3*b^3*d^-1*(b*c+-1*a*d)^-3*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x))+1//6*b^2*d^-2*(c+d*x)^-1*(b*c+-1*a*d)^-1*exp(-1a+-1*b*x)+1//6*b^3*d^-3*(b*c+-1*a*d)^-1*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x)))
@test integrate((c+d*x)^3*SpecialFunctions.expint(2,a+b*x), x) == :(-1*b^-1*(c+d*x)^3*SpecialFunctions.expint(3,a+b*x)+-6*b^-4*d^3*SpecialFunctions.expint(6,a+b*x)+-6*b^-3*d^2*(c+d*x)*SpecialFunctions.expint(5,a+b*x)+-3*d*b^-2*(c+d*x)^2*SpecialFunctions.expint(4,a+b*x))
@test integrate((c+d*x)^2*SpecialFunctions.expint(2,a+b*x), x) == :(-1*b^-1*(c+d*x)^2*SpecialFunctions.expint(3,a+b*x)+-2*b^-3*d^2*SpecialFunctions.expint(5,a+b*x)+-2*d*b^-2*(c+d*x)*SpecialFunctions.expint(4,a+b*x))
@test integrate((c+d*x)*SpecialFunctions.expint(2,a+b*x), x) == :(-1*d*b^-2*SpecialFunctions.expint(4,a+b*x)+-1*b^-1*(c+d*x)*SpecialFunctions.expint(3,a+b*x))
@test integrate(SpecialFunctions.expint(2,a+b*x), x) == :(-1*b^-1*SpecialFunctions.expint(3,a+b*x))
@test integrate((c+d*x)^-3*SpecialFunctions.expint(2,a+b*x), x) == :(-1//2*d^-1*(c+d*x)^-2*SpecialFunctions.expint(2,a+b*x)+(1/2)*b*d^-2*(c+d*x)^-1*SpecialFunctions.expint(1,a+b*x)+(1/2)*b^2*d^-2*(b*c+-1*a*d)^-1*expintegralei(-1a+-1*b*x)+-1//2*b^2*d^-2*(b*c+-1*a*d)^-1*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x)))
@test integrate((c+d*x)^-4*SpecialFunctions.expint(2,a+b*x), x) == :(-1//3*d^-1*(c+d*x)^-3*SpecialFunctions.expint(2,a+b*x)+1//6*b*d^-2*(c+d*x)^-2*SpecialFunctions.expint(1,a+b*x)+1//6*b^3*d^-2*(b*c+-1*a*d)^-2*expintegralei(-1a+-1*b*x)+-1//6*b^3*d^-2*(b*c+-1*a*d)^-2*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x))+1//6*b^2*d^-2*(c+d*x)^-1*(b*c+-1*a*d)^-1*exp(-1a+-1*b*x)+1//6*b^3*d^-3*(b*c+-1*a*d)^-1*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x)))
@test integrate((c+d*x)^3*SpecialFunctions.expint(3,a+b*x), x) == :(-1*b^-1*(c+d*x)^3*SpecialFunctions.expint(4,a+b*x)+-6*b^-4*d^3*SpecialFunctions.expint(7,a+b*x)+-6*b^-3*d^2*(c+d*x)*SpecialFunctions.expint(6,a+b*x)+-3*d*b^-2*(c+d*x)^2*SpecialFunctions.expint(5,a+b*x))
@test integrate((c+d*x)^2*SpecialFunctions.expint(3,a+b*x), x) == :(-1*b^-1*(c+d*x)^2*SpecialFunctions.expint(4,a+b*x)+-2*b^-3*d^2*SpecialFunctions.expint(6,a+b*x)+-2*d*b^-2*(c+d*x)*SpecialFunctions.expint(5,a+b*x))
@test integrate((c+d*x)*SpecialFunctions.expint(3,a+b*x), x) == :(-1*d*b^-2*SpecialFunctions.expint(5,a+b*x)+-1*b^-1*(c+d*x)*SpecialFunctions.expint(4,a+b*x))
@test integrate(SpecialFunctions.expint(3,a+b*x), x) == :(-1*b^-1*SpecialFunctions.expint(4,a+b*x))
@test integrate((c+d*x)^-4*SpecialFunctions.expint(3,a+b*x), x) == :(-1//3*d^-1*(c+d*x)^-3*SpecialFunctions.expint(3,a+b*x)+-1//6*b^2*d^-3*(c+d*x)^-1*SpecialFunctions.expint(1,a+b*x)+-1//6*b^3*d^-3*(b*c+-1*a*d)^-1*expintegralei(-1a+-1*b*x)+1//6*b*d^-2*(c+d*x)^-2*SpecialFunctions.expint(2,a+b*x)+1//6*b^3*d^-3*(b*c+-1*a*d)^-1*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x)))
@test integrate((c+d*x)^4*SpecialFunctions.expint(-1,a+b*x), x) == :(-8*b^-5*d^4*exp(-1a+-1*b*x)+-1*b^-1*(a+b*x)^-1*(c+d*x)^4*exp(-1a+-1*b*x)+-8*b^-4*d^3*(c+d*x)*exp(-1a+-1*b*x)+-4*b^-5*d^2*(b*c+-1*a*d)^2*exp(-1a+-1*b*x)+-4*b^-5*d^3*(b*c+-1*a*d)*exp(-1a+-1*b*x)+-4*b^-3*d^2*(c+d*x)^2*exp(-1a+-1*b*x)+4*d*b^-5*(b*c+-1*a*d)^3*expintegralei(-1a+-1*b*x)+-4*b^-4*d^2*(c+d*x)*(b*c+-1*a*d)*exp(-1a+-1*b*x))
@test integrate((c+d*x)^3*SpecialFunctions.expint(-1,a+b*x), x) == :(-3*b^-4*d^3*exp(-1a+-1*b*x)+-1*b^-1*(a+b*x)^-1*(c+d*x)^3*exp(-1a+-1*b*x)+-3*b^-4*d^2*(b*c+-1*a*d)*exp(-1a+-1*b*x)+-3*b^-3*d^2*(c+d*x)*exp(-1a+-1*b*x)+3*d*b^-4*(b*c+-1*a*d)^2*expintegralei(-1a+-1*b*x))
@test integrate((c+d*x)^2*SpecialFunctions.expint(-1,a+b*x), x) == :(-2*b^-3*d^2*exp(-1a+-1*b*x)+-1*b^-1*(a+b*x)^-1*(c+d*x)^2*exp(-1a+-1*b*x)+2*d*b^-3*(b*c+-1*a*d)*expintegralei(-1a+-1*b*x))
@test integrate((c+d*x)*SpecialFunctions.expint(-1,a+b*x), x) == :(d*b^-2*expintegralei(-1a+-1*b*x)+-1*b^-1*(a+b*x)^-1*(c+d*x)*exp(-1a+-1*b*x))
@test integrate(SpecialFunctions.expint(-1,a+b*x), x) == :(-1*b^-1*(a+b*x)^-1*exp(-1a+-1*b*x))
@test integrate((c+d*x)^-1*SpecialFunctions.expint(-1,a+b*x), x) == :(-1*d*(b*c+-1*a*d)^-2*expintegralei(-1a+-1*b*x)+-1*(b*c+-1*a*d)^-1*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x))+d*(b*c+-1*a*d)^-2*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x))+-1*b^-1*(a+b*x)^-1*(c+d*x)^-1*exp(-1a+-1*b*x)+-1*d*b^-1*(c+d*x)^-1*(b*c+-1*a*d)^-1*exp(-1a+-1*b*x))
@test integrate((c+d*x)^-2*SpecialFunctions.expint(-1,a+b*x), x) == :((c+d*x)^-1*(b*c+-1*a*d)^-1*exp(-1a+-1*b*x)+-1*b^-1*(a+b*x)^-1*(c+d*x)^-2*exp(-1a+-1*b*x)+-2*b*d*(b*c+-1*a*d)^-3*expintegralei(-1a+-1*b*x)+-2*b*(b*c+-1*a*d)^-2*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x))+-2*d*(c+d*x)^-1*(b*c+-1*a*d)^-2*exp(-1a+-1*b*x)+b*d^-1*(b*c+-1*a*d)^-1*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x))+-1*d*b^-1*(c+d*x)^-2*(b*c+-1*a*d)^-1*exp(-1a+-1*b*x)+2*b*d*(b*c+-1*a*d)^-3*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x)))
@test integrate((c+d*x)^-3*SpecialFunctions.expint(-1,a+b*x), x) == :((c+d*x)^-2*(-2*a*d+2*b*c)^-1*exp(-1a+-1*b*x)+-1*b^-1*(a+b*x)^-1*(c+d*x)^-3*exp(-1a+-1*b*x)+-3*d*b^2*(b*c+-1*a*d)^-4*expintegralei(-1a+-1*b*x)+-3*b^2*(b*c+-1*a*d)^-3*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x))+-3//2*d*(c+d*x)^-2*(b*c+-1*a*d)^-2*exp(-1a+-1*b*x)+3//2*b*(c+d*x)^-1*(b*c+-1*a*d)^-2*exp(-1a+-1*b*x)+-1*d*b^-1*(c+d*x)^-3*(b*c+-1*a*d)^-1*exp(-1a+-1*b*x)+-3*b*d*(c+d*x)^-1*(b*c+-1*a*d)^-3*exp(-1a+-1*b*x)+3*d*b^2*(b*c+-1*a*d)^-4*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x))+-1//2*b*d^-1*(c+d*x)^-1*(b*c+-1*a*d)^-1*exp(-1a+-1*b*x)+-1//2*b^2*d^-2*(b*c+-1*a*d)^-1*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x))+3//2*b^2*d^-1*(b*c+-1*a*d)^-2*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x)))
@test integrate((c+d*x)^4*SpecialFunctions.expint(-2,a+b*x), x) == :(-1*b^-1*(c+d*x)^4*SpecialFunctions.expint(-1,a+b*x)+-12*b^-5*d^4*exp(-1a+-1*b*x)+-12*b^-5*d^3*(b*c+-1*a*d)*exp(-1a+-1*b*x)+-12*b^-4*d^3*(c+d*x)*exp(-1a+-1*b*x)+12*b^-5*d^2*(b*c+-1*a*d)^2*expintegralei(-1a+-1*b*x)+-4*d*b^-2*(a+b*x)^-1*(c+d*x)^3*exp(-1a+-1*b*x))
@test integrate((c+d*x)^3*SpecialFunctions.expint(-2,a+b*x), x) == :(-1*b^-1*(c+d*x)^3*SpecialFunctions.expint(-1,a+b*x)+-6*b^-4*d^3*exp(-1a+-1*b*x)+6*b^-4*d^2*(b*c+-1*a*d)*expintegralei(-1a+-1*b*x)+-3*d*b^-2*(a+b*x)^-1*(c+d*x)^2*exp(-1a+-1*b*x))
@test integrate((c+d*x)^2*SpecialFunctions.expint(-2,a+b*x), x) == :(-1*b^-1*(c+d*x)^2*SpecialFunctions.expint(-1,a+b*x)+2*b^-3*d^2*expintegralei(-1a+-1*b*x)+-2*d*b^-2*(a+b*x)^-1*(c+d*x)*exp(-1a+-1*b*x))
@test integrate((c+d*x)*SpecialFunctions.expint(-2,a+b*x), x) == :(-1*b^-1*(c+d*x)*SpecialFunctions.expint(-1,a+b*x)+-1*d*b^-2*(a+b*x)^-1*exp(-1a+-1*b*x))
@test integrate(SpecialFunctions.expint(-2,a+b*x), x) == :(-1*b^-1*SpecialFunctions.expint(-1,a+b*x))
@test integrate((c+d*x)^-1*SpecialFunctions.expint(-2,a+b*x), x) == :(-1*b^-1*(c+d*x)^-1*SpecialFunctions.expint(-1,a+b*x)+-1*(b*c+-1*a*d)^-1*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x))+2*d^2*(b*c+-1*a*d)^-3*expintegralei(-1a+-1*b*x)+-2*d^2*(b*c+-1*a*d)^-3*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x))+2*d*(b*c+-1*a*d)^-2*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x))+d*b^-2*(a+b*x)^-1*(c+d*x)^-2*exp(-1a+-1*b*x)+b^-2*d^2*(c+d*x)^-2*(b*c+-1*a*d)^-1*exp(-1a+-1*b*x)+-1*d*b^-1*(c+d*x)^-1*(b*c+-1*a*d)^-1*exp(-1a+-1*b*x)+2*b^-1*d^2*(c+d*x)^-1*(b*c+-1*a*d)^-2*exp(-1a+-1*b*x))
@test integrate((c+d*x)^-2*SpecialFunctions.expint(-2,a+b*x), x) == :((c+d*x)^-1*(b*c+-1*a*d)^-1*exp(-1a+-1*b*x)+-1*b^-1*(c+d*x)^-2*SpecialFunctions.expint(-1,a+b*x)+-3*b*(b*c+-1*a*d)^-2*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x))+-3*d*(c+d*x)^-1*(b*c+-1*a*d)^-2*exp(-1a+-1*b*x)+6*b*d^2*(b*c+-1*a*d)^-4*expintegralei(-1a+-1*b*x)+6*d^2*(c+d*x)^-1*(b*c+-1*a*d)^-3*exp(-1a+-1*b*x)+b*d^-1*(b*c+-1*a*d)^-1*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x))+-1*d*b^-1*(c+d*x)^-2*(b*c+-1*a*d)^-1*exp(-1a+-1*b*x)+-6*b*d^2*(b*c+-1*a*d)^-4*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x))+2*d*b^-2*(a+b*x)^-1*(c+d*x)^-3*exp(-1a+-1*b*x)+2*b^-2*d^2*(c+d*x)^-3*(b*c+-1*a*d)^-1*exp(-1a+-1*b*x)+3*b^-1*d^2*(c+d*x)^-2*(b*c+-1*a*d)^-2*exp(-1a+-1*b*x)+6*b*d*(b*c+-1*a*d)^-3*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x)))
@test integrate((c+d*x)^-3*SpecialFunctions.expint(-2,a+b*x), x) == :((c+d*x)^-2*(-2*a*d+2*b*c)^-1*exp(-1a+-1*b*x)+-1*b^-1*(c+d*x)^-3*SpecialFunctions.expint(-1,a+b*x)+-6*b^2*(b*c+-1*a*d)^-3*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x))+-2*d*(c+d*x)^-2*(b*c+-1*a*d)^-2*exp(-1a+-1*b*x)+2*b*(c+d*x)^-1*(b*c+-1*a*d)^-2*exp(-1a+-1*b*x)+6*d^2*(c+d*x)^-2*(b*c+-1*a*d)^-3*exp(-1a+-1*b*x)+12*b^2*d^2*(b*c+-1*a*d)^-5*expintegralei(-1a+-1*b*x)+-1*d*b^-1*(c+d*x)^-3*(b*c+-1*a*d)^-1*exp(-1a+-1*b*x)+-12*b^2*d^2*(b*c+-1*a*d)^-5*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x))+-6*b*d*(c+d*x)^-1*(b*c+-1*a*d)^-3*exp(-1a+-1*b*x)+2*b^2*d^-1*(b*c+-1*a*d)^-2*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x))+3*d*b^-2*(a+b*x)^-1*(c+d*x)^-4*exp(-1a+-1*b*x)+3*b^-2*d^2*(c+d*x)^-4*(b*c+-1*a*d)^-1*exp(-1a+-1*b*x)+4*b^-1*d^2*(c+d*x)^-3*(b*c+-1*a*d)^-2*exp(-1a+-1*b*x)+12*b*d^2*(c+d*x)^-1*(b*c+-1*a*d)^-4*exp(-1a+-1*b*x)+12*d*b^2*(b*c+-1*a*d)^-4*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x))+-1//2*b*d^-1*(c+d*x)^-1*(b*c+-1*a*d)^-1*exp(-1a+-1*b*x)+-1//2*b^2*d^-2*(b*c+-1*a*d)^-1*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x)))
@test integrate((c+d*x)^4*SpecialFunctions.expint(-3,a+b*x), x) == :(-1*b^-1*(c+d*x)^4*SpecialFunctions.expint(-2,a+b*x)+-24*b^-5*d^4*exp(-1a+-1*b*x)+-4*d*b^-2*(c+d*x)^3*SpecialFunctions.expint(-1,a+b*x)+24*b^-5*d^3*(b*c+-1*a*d)*expintegralei(-1a+-1*b*x)+-12*b^-3*d^2*(a+b*x)^-1*(c+d*x)^2*exp(-1a+-1*b*x))
@test integrate((c+d*x)^3*SpecialFunctions.expint(-3,a+b*x), x) == :(-1*b^-1*(c+d*x)^3*SpecialFunctions.expint(-2,a+b*x)+6*b^-4*d^3*expintegralei(-1a+-1*b*x)+-3*d*b^-2*(c+d*x)^2*SpecialFunctions.expint(-1,a+b*x)+-6*b^-3*d^2*(a+b*x)^-1*(c+d*x)*exp(-1a+-1*b*x))
@test integrate((c+d*x)^2*SpecialFunctions.expint(-3,a+b*x), x) == :(-1*b^-1*(c+d*x)^2*SpecialFunctions.expint(-2,a+b*x)+-2*d*b^-2*(c+d*x)*SpecialFunctions.expint(-1,a+b*x)+-2*b^-3*d^2*(a+b*x)^-1*exp(-1a+-1*b*x))
@test integrate((c+d*x)*SpecialFunctions.expint(-3,a+b*x), x) == :(-1*d*b^-2*SpecialFunctions.expint(-1,a+b*x)+-1*b^-1*(c+d*x)*SpecialFunctions.expint(-2,a+b*x))
@test integrate(SpecialFunctions.expint(-3,a+b*x), x) == :(-1*b^-1*SpecialFunctions.expint(-2,a+b*x))
@test integrate((c+d*x)^-1*SpecialFunctions.expint(-3,a+b*x), x) == :(-1*b^-1*(c+d*x)^-1*SpecialFunctions.expint(-2,a+b*x)+-1*(b*c+-1*a*d)^-1*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x))+-6*d^3*(b*c+-1*a*d)^-4*expintegralei(-1a+-1*b*x)+d*b^-2*(c+d*x)^-2*SpecialFunctions.expint(-1,a+b*x)+-6*d^2*(b*c+-1*a*d)^-3*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x))+3*d*(b*c+-1*a*d)^-2*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x))+6*d^3*(b*c+-1*a*d)^-4*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x))+b^-2*d^2*(c+d*x)^-2*(b*c+-1*a*d)^-1*exp(-1a+-1*b*x)+-1*d*b^-1*(c+d*x)^-1*(b*c+-1*a*d)^-1*exp(-1a+-1*b*x)+-6*b^-1*d^3*(c+d*x)^-1*(b*c+-1*a*d)^-3*exp(-1a+-1*b*x)+-3*b^-2*d^3*(c+d*x)^-2*(b*c+-1*a*d)^-2*exp(-1a+-1*b*x)+-2*b^-3*d^2*(a+b*x)^-1*(c+d*x)^-3*exp(-1a+-1*b*x)+-2*b^-3*d^3*(c+d*x)^-3*(b*c+-1*a*d)^-1*exp(-1a+-1*b*x)+3*b^-1*d^2*(c+d*x)^-1*(b*c+-1*a*d)^-2*exp(-1a+-1*b*x))
@test integrate((c+d*x)^-2*SpecialFunctions.expint(-3,a+b*x), x) == :((c+d*x)^-1*(b*c+-1*a*d)^-1*exp(-1a+-1*b*x)+-1*b^-1*(c+d*x)^-2*SpecialFunctions.expint(-2,a+b*x)+-24*b*d^3*(b*c+-1*a*d)^-5*expintegralei(-1a+-1*b*x)+-24*d^3*(c+d*x)^-1*(b*c+-1*a*d)^-4*exp(-1a+-1*b*x)+-4*b*(b*c+-1*a*d)^-2*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x))+-4*d*(c+d*x)^-1*(b*c+-1*a*d)^-2*exp(-1a+-1*b*x)+2*d*b^-2*(c+d*x)^-3*SpecialFunctions.expint(-1,a+b*x)+12*d^2*(c+d*x)^-1*(b*c+-1*a*d)^-3*exp(-1a+-1*b*x)+b*d^-1*(b*c+-1*a*d)^-1*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x))+-1*d*b^-1*(c+d*x)^-2*(b*c+-1*a*d)^-1*exp(-1a+-1*b*x)+-24*b*d^2*(b*c+-1*a*d)^-4*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x))+-12*b^-1*d^3*(c+d*x)^-2*(b*c+-1*a*d)^-3*exp(-1a+-1*b*x)+-8*b^-2*d^3*(c+d*x)^-3*(b*c+-1*a*d)^-2*exp(-1a+-1*b*x)+-6*b^-3*d^2*(a+b*x)^-1*(c+d*x)^-4*exp(-1a+-1*b*x)+-6*b^-3*d^3*(c+d*x)^-4*(b*c+-1*a*d)^-1*exp(-1a+-1*b*x)+2*b^-2*d^2*(c+d*x)^-3*(b*c+-1*a*d)^-1*exp(-1a+-1*b*x)+4*b^-1*d^2*(c+d*x)^-2*(b*c+-1*a*d)^-2*exp(-1a+-1*b*x)+12*b*d*(b*c+-1*a*d)^-3*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x))+24*b*d^3*(b*c+-1*a*d)^-5*exp(-1a+b*c*d^-1)*expintegralei(-1*b*d^-1*(c+d*x)))
@test integrate((c+d*x)^3*SpecialFunctions.expint(n,a+b*x), x) == :(-1*b^-1*(c+d*x)^3*SpecialFunctions.expint(1+n,a+b*x)+-6*b^-4*d^3*SpecialFunctions.expint(4+n,a+b*x)+-6*b^-3*d^2*(c+d*x)*SpecialFunctions.expint(3+n,a+b*x)+-3*d*b^-2*(c+d*x)^2*SpecialFunctions.expint(2+n,a+b*x))
@test integrate((c+d*x)^2*SpecialFunctions.expint(n,a+b*x), x) == :(-1*b^-1*(c+d*x)^2*SpecialFunctions.expint(1+n,a+b*x)+-2*b^-3*d^2*SpecialFunctions.expint(3+n,a+b*x)+-2*d*b^-2*(c+d*x)*SpecialFunctions.expint(2+n,a+b*x))
@test integrate((c+d*x)*SpecialFunctions.expint(n,a+b*x), x) == :(-1*d*b^-2*SpecialFunctions.expint(2+n,a+b*x)+-1*b^-1*(c+d*x)*SpecialFunctions.expint(1+n,a+b*x))
@test integrate(SpecialFunctions.expint(n,a+b*x), x) == :(-1*b^-1*SpecialFunctions.expint(1+n,a+b*x))
@test integrate(x^3*expintegralei(b*x), x) == :(1//4*x^4*expintegralei(b*x)+3//2*b^-4*exp(b*x)+-3//2*x*b^-3*exp(b*x)+-1//4*b^-1*x^3*exp(b*x)+3//4*b^-2*x^2*exp(b*x))
@test integrate(x^2*expintegralei(b*x), x) == :(-2//3*b^-3*exp(b*x)+1//3*x^3*expintegralei(b*x)+-1//3*b^-1*x^2*exp(b*x)+2//3*x*b^-2*exp(b*x))
@test integrate(x*expintegralei(b*x), x) == :((1/2)*b^-2*exp(b*x)+(1/2)*x^2*expintegralei(b*x)+-1//2*x*b^-1*exp(b*x))
@test integrate(expintegralei(b*x), x) == :(x*expintegralei(b*x)+-1*b^-1*exp(b*x))
@test integrate(x^-2*expintegralei(b*x), x) == :(b*expintegralei(b*x)+-1*x^-1*exp(b*x)+-1*x^-1*expintegralei(b*x))
@test integrate(x^-3*expintegralei(b*x), x) == :(-1//2*x^-2*expintegralei(b*x)+-1//4*x^-2*exp(b*x)+1//4*b^2*expintegralei(b*x)+-1//4*b*x^-1*exp(b*x))
@test integrate(x^-4*expintegralei(b*x), x) == :(-1//3*x^-3*expintegralei(b*x)+-1//9*x^-3*exp(b*x)+1//18*b^3*expintegralei(b*x)+-1//18*b*x^-2*exp(b*x)+-1//18*b^2*x^-1*exp(b*x))
@test integrate(x^2*expintegralei(b*x)^2, x) == :(-5//6*b^-3*exp(2*b*x)+1//3*x^3*expintegralei(b*x)^2+4//3*b^-3*expintegralei(2*b*x)+-4//3*b^-3*exp(b*x)*expintegralei(b*x)+1//3*x*b^-2*exp(2*b*x)+-2//3*b^-1*x^2*exp(b*x)*expintegralei(b*x)+4//3*x*b^-2*exp(b*x)*expintegralei(b*x))
@test integrate(x*expintegralei(b*x)^2, x) == :((1/2)*b^-2*exp(2*b*x)+(1/2)*x^2*expintegralei(b*x)^2+-1*b^-2*expintegralei(2*b*x)+b^-2*exp(b*x)*expintegralei(b*x)+-1*x*b^-1*exp(b*x)*expintegralei(b*x))
@test integrate(expintegralei(b*x)^2, x) == :(x*expintegralei(b*x)^2+2*b^-1*expintegralei(2*b*x)+-2*b^-1*exp(b*x)*expintegralei(b*x))
@test integrate((d*x)^m*expintegralei(b*x), x) == :(d^-1*(d*x)^(1+m)*(1+m)^-1*expintegralei(b*x)+-1*b^-1*(d*x)^m*(-1*b*x)^(-1m)*(1+m)^-1*SpecialFunctions.gamma(1+m,-1*b*x))
@test integrate(x^3*expintegralei(a+b*x), x) == :(1//4*x^4*expintegralei(a+b*x)+3//2*b^-4*exp(a+b*x)+(1/2)*a*b^-4*exp(a+b*x)+-3//2*x*b^-3*exp(a+b*x)+-1//4*a^4*b^-4*expintegralei(a+b*x)+-1//4*b^-1*x^3*exp(a+b*x)+1//4*a^2*b^-4*exp(a+b*x)+1//4*a^3*b^-4*exp(a+b*x)+3//4*b^-2*x^2*exp(a+b*x)+-1//2*a*x*b^-3*exp(a+b*x)+-1//4*x*a^2*b^-3*exp(a+b*x)+1//4*a*b^-2*x^2*exp(a+b*x))
@test integrate(x^2*expintegralei(a+b*x), x) == :(-2//3*b^-3*exp(a+b*x)+1//3*x^3*expintegralei(a+b*x)+-1//3*a*b^-3*exp(a+b*x)+-1//3*a^2*b^-3*exp(a+b*x)+-1//3*b^-1*x^2*exp(a+b*x)+1//3*a^3*b^-3*expintegralei(a+b*x)+2//3*x*b^-2*exp(a+b*x)+1//3*a*x*b^-2*exp(a+b*x))
@test integrate(x*expintegralei(a+b*x), x) == :((1/2)*b^-2*exp(a+b*x)+(1/2)*x^2*expintegralei(a+b*x)+(1/2)*a*b^-2*exp(a+b*x)+-1//2*x*b^-1*exp(a+b*x)+-1//2*a^2*b^-2*expintegralei(a+b*x))
@test integrate(expintegralei(a+b*x), x) == :(-1*b^-1*exp(a+b*x)+b^-1*(a+b*x)*expintegralei(a+b*x))
@test integrate(x^-2*expintegralei(a+b*x), x) == :(-1*x^-1*expintegralei(a+b*x)+-1*b*a^-1*expintegralei(a+b*x)+b*a^-1*exp(a)*expintegralei(b*x))
@test integrate(x^-3*expintegralei(a+b*x), x) == :(-1//2*x^-2*expintegralei(a+b*x)+(1/2)*a^-2*b^2*expintegralei(a+b*x)+(1/2)*a^-1*b^2*exp(a)*expintegralei(b*x)+-1//2*b*a^-1*x^-1*exp(a+b*x)+-1//2*a^-2*b^2*exp(a)*expintegralei(b*x))
@test integrate(x^2*expintegralei(a+b*x)^2, x) == :(-5//6*b^-3*exp(2a+2*b*x)+1//3*x^3*expintegralei(a+b*x)^2+4//3*b^-3*expintegralei(2a+2*b*x)+2*a*b^-3*expintegralei(2a+2*b*x)+2*a^2*b^-3*expintegralei(2a+2*b*x)+-4//3*b^-3*exp(a+b*x)*expintegralei(a+b*x)+-2//3*a*b^-3*exp(2a+2*b*x)+1//3*x*b^-2*exp(2a+2*b*x)+-2//3*a*b^-3*exp(a+b*x)*expintegralei(a+b*x)+-2//3*a^2*b^-3*exp(a+b*x)*expintegralei(a+b*x)+-2//3*b^-1*x^2*exp(a+b*x)*expintegralei(a+b*x)+-1//3*x*a^2*b^-2*expintegralei(a+b*x)^2+1//3*a^2*b^-3*expintegralei(a+b*x)^2*(a+b*x)+4//3*x*b^-2*exp(a+b*x)*expintegralei(a+b*x)+2//3*a*x*b^-2*exp(a+b*x)*expintegralei(a+b*x))
@test integrate(x*expintegralei(a+b*x)^2, x) == :((1/2)*b^-2*exp(2a+2*b*x)+(1/2)*x^2*expintegralei(a+b*x)^2+-1*b^-2*expintegralei(2a+2*b*x)+b^-2*exp(a+b*x)*expintegralei(a+b*x)+-2*a*b^-2*expintegralei(2a+2*b*x)+a*b^-2*exp(a+b*x)*expintegralei(a+b*x)+(1/2)*a*x*b^-1*expintegralei(a+b*x)^2+-1*x*b^-1*exp(a+b*x)*expintegralei(a+b*x)+-1//2*a*b^-2*expintegralei(a+b*x)^2*(a+b*x))
@test integrate(expintegralei(a+b*x)^2, x) == :(2*b^-1*expintegralei(2a+2*b*x)+b^-1*expintegralei(a+b*x)^2*(a+b*x)+-2*b^-1*exp(a+b*x)*expintegralei(a+b*x))
@test integrate(x^2*expintegralei(d*(a+b*log(c*x^n))), x) == :(1//3*x^3*expintegralei(d*(a+b*log(c*x^n)))+-1//3*x^3*(c*x^n)^(-3*n^-1)*exp(-3*a*b^-1*n^-1)*expintegralei(b^-1*n^-1*(3+b*d*n)*(a+b*log(c*x^n))))
@test integrate(x*expintegralei(d*(a+b*log(c*x^n))), x) == :((1/2)*x^2*expintegralei(d*(a+b*log(c*x^n)))+-1//2*x^2*(c*x^n)^(-2*n^-1)*exp(-2*a*b^-1*n^-1)*expintegralei(b^-1*n^-1*(2+b*d*n)*(a+b*log(c*x^n))))
@test integrate(expintegralei(d*(a+b*log(c*x^n))), x) == :(x*expintegralei(d*(a+b*log(c*x^n)))+-1*x*(c*x^n)^(-1*n^-1)*exp(-1*a*b^-1*n^-1)*expintegralei(b^-1*n^-1*(1+b*d*n)*(a+b*log(c*x^n))))
@test integrate(x^-1*expintegralei(d*(a+b*log(c*x^n))), x) == :(b^-1*n^-1*(a+b*log(c*x^n))*expintegralei(d*(a+b*log(c*x^n)))+-1*b^-1*d^-1*n^-1*(c*x^n)^(b*d)*exp(a*d))
@test integrate(x^-2*expintegralei(d*(a+b*log(c*x^n))), x) == :(-1*x^-1*expintegralei(d*(a+b*log(c*x^n)))+x^-1*(c*x^n)^(n^-1)*exp(a*b^-1*n^-1)*expintegralei(-1*b^-1*n^-1*(1+-1*b*d*n)*(a+b*log(c*x^n))))
@test integrate(x^-3*expintegralei(d*(a+b*log(c*x^n))), x) == :(-1//2*x^-2*expintegralei(d*(a+b*log(c*x^n)))+(1/2)*x^-2*(c*x^n)^(2*n^-1)*exp(2*a*b^-1*n^-1)*expintegralei(-1*b^-1*n^-1*(2+-1*b*d*n)*(a+b*log(c*x^n))))
@test integrate((e*x)^m*expintegralei(d*(a+b*log(c*x^n))), x) == :(e^-1*(e*x)^(1+m)*(1+m)^-1*expintegralei(d*(a+b*log(c*x^n)))+-1*e^-1*(c*x^n)^(-1*n^-1*(1+m))*(e*x)^(1+m)*(1+m)^-1*exp(-1*a*b^-1*n^-1*(1+m))*expintegralei(b^-1*n^-1*(a+b*log(c*x^n))*(1+m+b*d*n)))
@test integrate(x^-3*exp(b*x)*expintegralei(b*x), x) == :(2*b^2*expintegralei(2*b*x)+-1//4*x^-2*exp(2*b*x)+1//4*b^2*expintegralei(b*x)^2+-1*b*x^-1*exp(2*b*x)+-1//2*x^-2*exp(b*x)*expintegralei(b*x)+-1//2*b*x^-1*exp(b*x)*expintegralei(b*x))
@test integrate(x^-2*exp(b*x)*expintegralei(b*x), x) == :((1/2)*b*expintegralei(b*x)^2+-1*x^-1*exp(2*b*x)+2*b*expintegralei(2*b*x)+-1*x^-1*exp(b*x)*expintegralei(b*x))
@test integrate(x^-1*exp(b*x)*expintegralei(b*x), x) == :((1/2)*expintegralei(b*x)^2)
@test integrate(exp(b*x)*expintegralei(b*x), x) == :(-1*b^-1*expintegralei(2*b*x)+b^-1*exp(b*x)*expintegralei(b*x))
@test integrate(x*exp(b*x)*expintegralei(b*x), x) == :(b^-2*expintegralei(2*b*x)+-1//2*b^-2*exp(2*b*x)+-1*b^-2*exp(b*x)*expintegralei(b*x)+x*b^-1*exp(b*x)*expintegralei(b*x))
@test integrate(x^2*exp(b*x)*expintegralei(b*x), x) == :(-2*b^-3*expintegralei(2*b*x)+5//4*b^-3*exp(2*b*x)+2*b^-3*exp(b*x)*expintegralei(b*x)+-1//2*x*b^-2*exp(2*b*x)+b^-1*x^2*exp(b*x)*expintegralei(b*x)+-2*x*b^-2*exp(b*x)*expintegralei(b*x))
@test integrate(x^3*exp(b*x)*expintegralei(b*x), x) == :(-4*b^-4*exp(2*b*x)+6*b^-4*expintegralei(2*b*x)+-6*b^-4*exp(b*x)*expintegralei(b*x)+2*x*b^-3*exp(2*b*x)+-1//2*b^-2*x^2*exp(2*b*x)+b^-1*x^3*exp(b*x)*expintegralei(b*x)+-3*b^-2*x^2*exp(b*x)*expintegralei(b*x)+6*x*b^-3*exp(b*x)*expintegralei(b*x))
@test integrate(x^3*exp(a+b*x)*expintegralei(c+d*x), x) == :(-6*b^-4*exp(a+b*x)*expintegralei(c+d*x)+-6*b^-3*(b+d)^-1*exp(a+c+x*(b+d))+-3*b^-2*(b+d)^-2*exp(a+c+x*(b+d))+-2*b^-1*(b+d)^-3*exp(a+c+x*(b+d))+6*b^-4*exp(a+-1*b*c*d^-1)*expintegralei(d^-1*(b+d)*(c+d*x))+b^-1*x^3*exp(a+b*x)*expintegralei(c+d*x)+-1*b^-1*x^2*(b+d)^-1*exp(a+c+x*(b+d))+-3*b^-2*x^2*exp(a+b*x)*expintegralei(c+d*x)+2*x*b^-1*(b+d)^-2*exp(a+c+x*(b+d))+3*x*b^-2*(b+d)^-1*exp(a+c+x*(b+d))+6*x*b^-3*exp(a+b*x)*expintegralei(c+d*x)+b^-1*c^3*d^-3*exp(a+-1*b*c*d^-1)*expintegralei(d^-1*(b+d)*(c+d*x))+-1*c*b^-1*d^-1*(b+d)^-2*exp(a+c+x*(b+d))+-1*b^-1*c^2*d^-2*(b+d)^-1*exp(a+c+x*(b+d))+-3*c*b^-2*d^-1*(b+d)^-1*exp(a+c+x*(b+d))+3*b^-2*c^2*d^-2*exp(a+-1*b*c*d^-1)*expintegralei(d^-1*(b+d)*(c+d*x))+6*c*b^-3*d^-1*exp(a+-1*b*c*d^-1)*expintegralei(d^-1*(b+d)*(c+d*x))+c*x*b^-1*d^-1*(b+d)^-1*exp(a+c+x*(b+d)))
@test integrate(x^2*exp(a+b*x)*expintegralei(c+d*x), x) == :(b^-1*(b+d)^-2*exp(a+c+x*(b+d))+-2*b^-3*exp(a+-1*b*c*d^-1)*expintegralei(d^-1*(b+d)*(c+d*x))+2*b^-3*exp(a+b*x)*expintegralei(c+d*x)+2*b^-2*(b+d)^-1*exp(a+c+x*(b+d))+b^-1*x^2*exp(a+b*x)*expintegralei(c+d*x)+-1*x*b^-1*(b+d)^-1*exp(a+c+x*(b+d))+-2*x*b^-2*exp(a+b*x)*expintegralei(c+d*x)+c*b^-1*d^-1*(b+d)^-1*exp(a+c+x*(b+d))+-1*b^-1*c^2*d^-2*exp(a+-1*b*c*d^-1)*expintegralei(d^-1*(b+d)*(c+d*x))+-2*c*b^-2*d^-1*exp(a+-1*b*c*d^-1)*expintegralei(d^-1*(b+d)*(c+d*x)))
@test integrate(x*exp(a+b*x)*expintegralei(c+d*x), x) == :(b^-2*exp(a+-1*b*c*d^-1)*expintegralei(d^-1*(b+d)*(c+d*x))+-1*b^-1*(b+d)^-1*exp(a+c+x*(b+d))+-1*b^-2*exp(a+b*x)*expintegralei(c+d*x)+x*b^-1*exp(a+b*x)*expintegralei(c+d*x)+c*b^-1*d^-1*exp(a+-1*b*c*d^-1)*expintegralei(d^-1*(b+d)*(c+d*x)))
@test integrate(exp(a+b*x)*expintegralei(c+d*x), x) == :(b^-1*exp(a+b*x)*expintegralei(c+d*x)+-1*b^-1*exp(a+-1*b*c*d^-1)*expintegralei(d^-1*(b+d)*(c+d*x)))
@test integrate(x^2*logintegral(b*x), x) == :(-1//3*b^-3*expintegralei(4*log(b*x))+1//3*x^3*logintegral(b*x))
@test integrate(x*logintegral(b*x), x) == :((1/2)*x^2*logintegral(b*x)+-1//2*b^-2*expintegralei(3*log(b*x)))
@test integrate(logintegral(b*x), x) == :(x*logintegral(b*x)+-1*b^-1*expintegralei(2*log(b*x)))
@test integrate(x^-1*logintegral(b*x), x) == :(log(b*x)*logintegral(b*x)+-1*b*x)
@test integrate(x^-2*logintegral(b*x), x) == :(b*log(log(b*x))+-1*x^-1*logintegral(b*x))
@test integrate(x^-3*logintegral(b*x), x) == :((1/2)*b^2*expintegralei(-1*log(b*x))+-1//2*x^-2*logintegral(b*x))
@test integrate((d*x)^m*logintegral(b*x), x) == :(d^-1*(d*x)^(1+m)*(1+m)^-1*logintegral(b*x)+-1*b*d^-2*(b*x)^(-2+-1m)*(d*x)^(2+m)*(1+m)^-1*expintegralei((2+m)*log(b*x)))
@test integrate(x^2*logintegral(a+b*x), x) == :(-1//3*b^-3*expintegralei(4*log(a+b*x))+1//3*x^3*logintegral(a+b*x)+a*b^-3*expintegralei(3*log(a+b*x))+-1*a^2*b^-3*expintegralei(2*log(a+b*x))+1//3*a^3*b^-3*logintegral(a+b*x))
@test integrate(x*logintegral(a+b*x), x) == :((1/2)*x^2*logintegral(a+b*x)+-1//2*b^-2*expintegralei(3*log(a+b*x))+a*b^-2*expintegralei(2*log(a+b*x))+-1//2*a^2*b^-2*logintegral(a+b*x))
@test integrate(logintegral(a+b*x), x) == :(-1*b^-1*expintegralei(2*log(a+b*x))+b^-1*(a+b*x)*logintegral(a+b*x))

test/8 Special functions/8.3 Exponential integral functions.jl


using SatisfiabilityInterface: subscript

@testset "Sudoku" begin

    function all_different(v)
        vv = vec(v)
        n = length(vv)

        return [vv[i] ≠ vv[j] for i in 1:n for j in i+1:n]
    end

    function make_matrix(name, T, m, n)
        return [T(Symbol(name, subscript(i), ",", subscript(j)))
            for i in 1:m, j in 1:n]
    end

    n = 9
    M = make_matrix(:M, BoundedInteger{n}, n, n)

    m = Model()

    push!(m, vec(M)...)

    for i in 1:n
        append!(m.constraints, all_different(M[:, i]))  # columns
        append!(m.constraints, all_different(M[i, :]))  # rows
    end

    # blocks:
    for i in 0:2, j in 0:2
        append!(m.constraints, all_different(M[3i+1:3i+3, 3j+1:3j+3]))
    end

    i, j = 0, 0
    all_different(M[3i+1:3i+3, 3j+1:3j+3])

    ## initial condition from https://www.juliaopt.org/notebooks/JuMP-Sudoku.html

    initial = [
    5 3 -1 -1 7 -1 -1 -1 -1
    6 -1 -1 1 9 5 -1 -1 -1
    -1 9 8 -1 -1 -1 -1 6 -1
    8 -1 -1 -1 6 -1 -1 -1 3
    4 -1 -1 8 -1 3 -1 -1 1
    7 -1 -1 -1 2 -1 -1 -1 6
    -1 6 -1 -1 -1 -1 2 8 -1
    -1 -1 -1 4 1 9 -1 -1 5
    -1 -1 -1 -1 8 -1 -1 7 9
    ]

    for i in 1:9, j in 1:9
        if initial[i, j] > 0
            push!(m.constraints, M[i, j] ~ initial[i, j])
        end
    end

    @time status, results = solve(m);

    output = [results[M[i, j]] for i in 1:9, j in 1:9]

    @test output == [
        5  3  4  6  7  8  9  1  2
        6  7  2  1  9  5  3  4  8
        1  9  8  3  4  2  5  6  7
        8  5  9  7  6  1  4  2  3
        4  2  6  8  5  3  7  9  1
        7  1  3  9  2  4  8  5  6
        9  6  1  5  3  7  2  8  4
        2  8  7  4  1  9  6  3  5
        3  4  5  2  8  6  1  7  9
    ]

end


# p37.jl - 2D "wave tank" with Neumann BCs for |y|=1

# x variable in [-A,A], Fourier:
A = 3; Nx = 50; dx = 2*A/Nx; x = -A+dx*(1:Nx);
D2x = (pi/A)^2*toeplitz([-1/(3*(dx/A)^2)-1/6;
        @. .5*(-1).^(2:Nx)/sin((pi*dx/A)*(1:Nx-1)/2)^2]);

# y variable in [-1,1], Chebyshev:
Ny = 15; (Dy,y) = cheb(Ny); D2y = Dy^2;
BC = -Dy[[1,Ny+1],[1,Ny+1]]\Dy[[1,Ny+1],2:Ny];

# Grid and initial data:
vv = @. exp(-8*((x'+1.5)^2+y^2));
dt = 5/(Nx+Ny^2);
vvold = @. exp(-8*((x'+dt+1.5).^2+y.^2));

# Time-stepping by leap frog formula:
plotgap = round(Int,2/dt); dt = 2/plotgap;
for n = 0:2*plotgap
    t = n*dt;
    if rem(n+.5,plotgap)<1
        figure(n/plotgap+1); clf(); surf(x,y,vv);
        gca()[:view_init](60,-100);
        xlim(-A,A); ylim(-1,1); zlim(-0.15,1);
        text3D(-2.5,1,.5,"t = $(round(t))",fontsize=18);
        zticks([]);
    end
    vvnew = 2*vv - vvold + dt^2*(vv*D2x +D2y*vv);
    vvold = vv; vv = vvnew;
    vv[[1,Ny+1],:] = BC*vv[2:Ny,:];       # Neumann BCs for |y|=1
end


module GreekAnalytical

__precompile__(true)

using Distributions

export blackScholesValue, blackScholesDelta, blackScholesGamma, blackScholesVega, blackScholesTheta
export blackScholesVanna, blackScholesVolga, blackScholesSpeed, blackScholesCharm, blackScholesColor, blackScholesZomma
export blackScholesRho, blackScholesRhoFutures, blackScholesTouchProb, blackScholesDriftlessTheta

std_normal = Normal(0, 1)

function blackScholesValue(isCall::Bool, spot::T, strike::T, τ::T, r::T, b::T, σ::T) where {T}
    sign = isCall ? 1 : -1

    d1 = (log(spot / strike) + (b + 0.5 * σ^2) * τ) / (σ * sqrt(τ))
    d2 = d1 - σ * sqrt(τ)

    sign * (spot * exp((b-r) * τ) * cdf(std_normal, sign * d1) - strike * exp(-r * τ) * cdf(std_normal, sign * d2))
end

function blackScholesDelta(isCall::Bool, spot::T, strike::T, τ::T, r::T, b::T, σ::T) where {T}
    d1 = (log(spot / strike) + (b + 0.5 * σ^2) * τ) / (σ * sqrt(τ))
    if isCall
        exp((b-r) * τ) * cdf(std_normal, d1)
    else
        exp((b-r) * τ) * (cdf(std_normal, d1) - 1.0)
    end
end

function blackScholesGamma(isCall::Bool, spot::T, strike::T, τ::T, r::T, b::T, σ::T) where {T}
    d1 = (log(spot / strike) + (b + 0.5 * σ^2) * τ) / (σ * sqrt(τ))
    pdf(std_normal, d1) * exp((b-r) * τ) / spot / σ / sqrt(τ)
end

function blackScholesVega(isCall::Bool, spot::T, strike::T, τ::T, r::T, b::T, σ::T) where {T}
    d1 = (log(spot / strike) + (b + 0.5 * σ^2) * τ) / (σ * sqrt(τ))
    spot * exp((b-r) * τ) * pdf(std_normal, d1) * sqrt(τ)
end

function blackScholesTheta(isCall::Bool, spot::T, strike::T, τ::T, r::T, b::T, σ::T) where {T}
    sign = isCall ? 1 : -1

    d1 = (log(spot / strike) + (b + 0.5 * σ^2) * τ) / (σ * sqrt(τ))
    d2 = d1 - σ * sqrt(τ)

    term1 = -spot * exp((b-r) * τ) * pdf(std_normal, d1) * σ / 2 / sqrt(τ)
    term2 = -sign * (b-r) * spot * exp((b-r) * τ) * cdf(std_normal, sign * d1)
    term3 = -sign * r * strike * exp(-r * τ) * cdf(std_normal, sign * d2)

    term1 + term2 + term3
end

function blackScholesVanna(isCall::Bool, spot::T, strike::T, τ::T, r::T, b::T, σ::T) where {T}
    d1 = (log(spot / strike) + (b + 0.5 * σ^2) * τ) / (σ * sqrt(τ))
    d2 = d1 - σ * sqrt(τ)

    -d2 * pdf(std_normal, d1) / σ * exp((b-r) * τ)
end

function blackScholesCharm(isCall::Bool, spot::T, strike::T, τ::T, r::T, b::T, σ::T) where {T}
    sign = isCall ? 1 : -1

    d1 = (log(spot / strike) + (b + 0.5 * σ^2) * τ) / (σ * sqrt(τ))
    d2 = d1 - σ * sqrt(τ)

    term1 = pdf(std_normal, d1) * (b / σ / sqrt(τ) - d2 / 2 / τ)
    term2 = sign * (b - r) * cdf(std_normal, sign * d1)

    -exp((b-r) * τ) * (term1 + term2)
end

function blackScholesVolga(isCall::Bool, spot::T, strike::T, τ::T, r::T, b::T, σ::T) where {T}
    d1 = (log(spot / strike) + (b + 0.5 * σ^2) * τ) / (σ * sqrt(τ))
    d2 = d1 - σ * sqrt(τ)

    blackScholesVega(isCall, spot, strike, τ, r, b, σ) * d1 * d2 / σ
end

function blackScholesSpeed(isCall::Bool, spot::T, strike::T, τ::T, r::T, b::T, σ::T) where {T}
    d1 = (log(spot / strike) + (b + 0.5 * σ^2) * τ) / (σ * sqrt(τ))

    -1.0 * blackScholesGamma(isCall, spot, strike, τ, r, b, σ) / spot * (1 + d1 / σ / sqrt(τ))
end

function blackScholesZomma(isCall::Bool, spot::T, strike::T, τ::T, r::T, b::T, σ::T) where {T}
    d1 = (log(spot / strike) + (b + 0.5 * σ^2) * τ) / (σ * sqrt(τ))
    d2 = d1 - σ * sqrt(τ)

    blackScholesGamma(isCall, spot, strike, τ, r, b, σ) * (d1 * d2 - 1.0) / σ
end

function blackScholesColor(isCall::Bool, spot::T, strike::T, τ::T, r::T, b::T, σ::T) where {T}
    d1 = (log(spot / strike) + (b + 0.5 * σ^2) * τ) / (σ * sqrt(τ))
    d2 = d1 - σ * sqrt(τ)

    blackScholesGamma(isCall, spot, strike, τ, r, b, σ) * (r - b + b * d1 / σ / sqrt(τ) + (1 - d1 * d2) / 2 / τ)
end

function blackScholesDriftlessTheta(isCall::Bool, spot::T, strike::T, τ::T, r::T, b::T, σ::T) where {T}
    d1 = (log(spot / strike) + (b + 0.5 * σ^2) * τ) / (σ * sqrt(τ))
    println(pdf(std_normal, d1))
    -spot * pdf(std_normal, d1) * σ / 2 / sqrt(τ)
end

function blackScholesRho(isCall::Bool, spot::T, strike::T, τ::T, r::T, b::T, σ::T) where {T}
    sign = isCall ? 1 : -1

    d1 = (log(spot / strike) + (b + 0.5 * σ^2) * τ) / (σ * sqrt(τ))
    d2 = d1 - σ * sqrt(τ)

    sign * τ * strike * exp(-r * τ) * cdf(std_normal, sign * d2)
end

function blackScholesRhoFutures(isCall::Bool, spot::T, strike::T, τ::T, r::T, b::T, σ::T) where{T}
    -1.0 * τ * blackScholesValue(isCall, spot, strike, τ, r, b, σ)
end

function blackScholesTouchProb(isCall::Bool, spot::T, strike::T, τ::T, r::T, b::T, σ::T) where {T}
    if isCall
        if strike <= spot
            return 1.0
        end
    else
        if strike >= spot
            return 1.0
        end
    end

    sign = isCall ? -1 : 1

    μ = (b - σ^2 / 2) / (σ * σ)
    λ = sqrt(μ^2 + 2 * r / (σ * σ))
    z = log(strike / spot) / σ / sqrt(τ) + λ * σ * sqrt(τ)

    (strike / spot) ^ (μ + λ) * cdf(std_normal, sign * z) + (strike / spot) ^ (μ - λ) * cdf(std_normal, sign * (z - 2 * λ * σ * sqrt(τ)))
end

end


# power analysis functions for proportions

"""
    powerPTest(h::Real = 0, n::Real = 0, alpha::Float64 = 0.05, alternative = "two.sided")

Compute power of a sample of `n` observations to test the effect size `h`
for one sample proportion (P) compared to a constant (c) at type I error = `alpha` (default: 0.05).
The effect size `h` = ϕ₁ - ϕ₂ where ϕ₁ = 2*asin(sqrt(P)) and ϕ₂ = 2*asin(sqrt(c)). The option
`alternative` is `two.sided` for H₀: ϕ₁ = ϕ₂ vs H₁: ϕ₁ ≠ ϕ₂, `less` for H₁: ϕ₁ < ϕ₂, and `greater`
for H₁: ϕ₁ > ϕ₂.
"""
function powerPTest(;
    h::Real = 0,
    n::Real = 0,
    alpha::Float64 = 0.05,
    alternative::String = "two.sided"
    )

    check_args(h=h,n=n,alpha=alpha)

    if alternative == "less"
        tside = 1
    elseif alternative in ("two","two.sided","two-sided","two sided")
        tside = 2
        h = abs(h)
    elseif alternative == "greater"
        tside = 3
    else
        error("`alternative` must be `less`, `two.sided`, or `greater`.")
    end

    if tside == 1
        return cdf(Normal(),quantile(Normal(),alpha) - h*sqrt(n))
    elseif tside == 2
        return ccdf(Normal(),cquantile(Normal(),alpha/2) - h*sqrt(n)) + cdf(Normal(),quantile(Normal(),alpha/2) - h*sqrt(n))
    elseif tside == 3
        return ccdf(Normal(),cquantile(Normal(),alpha) - h*sqrt(n))
    end
    error("internal error")
end

"""
    samplesizePTest(h::Real = 0, alpha::Float64 = 0.05, power::Float64 = 0.8, alternative = "two.sided")

Compute the sample size to test the effect size `h` for one sample proportion (P) compared to a constant (c)
with power > `power` (default: 0.8) and type I error = `alpha` (default: 0.05).
The effect size `h` = ϕ₁ - ϕ₂ where ϕ₁ = 2*asin(sqrt(P)) and ϕ₂ = 2*asin(sqrt(c)). The option
`alternative` is `two.sided` for H₀: ϕ₁ = ϕ₂ vs H₁: ϕ₁ ≠ ϕ₂, `less` for H₁: ϕ₁ < ϕ₂, and `greater`
for H₁: ϕ₁ > ϕ₂.
"""
function samplesizePTest(;
    h::Real = 0,
    alpha::Float64 = 0.05,
    power::Float64 = 0.8,
    alternative::String = "two"
    )

    check_args(h=h,alpha=alpha,power=power)

    return ceil(Int64,fzero(x->powerPTest(h = h, n = x, alpha = alpha, alternative = alternative) - power, 2 + 1e-10, 1e+09))
end

"""
    effectsizePTest(n::Real = 0, alpha::Float64 = 0.05, power::Float64 = 0.8, alternative = "two.sided")

Compute the effect size that a sample size `n` can detect for one sample proportion (P) compared to a constant (c)
with power > `power` (default: 0.8) and type I error = `alpha` (default: 0.05).
The effect size `h` = ϕ₁ - ϕ₂ where ϕ₁ = 2*asin(sqrt(P)) and ϕ₂ = 2*asin(sqrt(c)). The option
`alternative` is `two.sided` for H₀: ϕ₁ = ϕ₂ vs H₁: ϕ₁ ≠ ϕ₂, `less` for H₁: ϕ₁ < ϕ₂, and `greater`
for H₁: ϕ₁ > ϕ₂.
"""
function effectsizePTest(;
    n::Real = 0,
    alpha::Real = 0.05,
    power::Real = 0.0,
    alternative::String = "two"
    )

    check_args(n=n,alpha=alpha,power=power)

    return fzero(x->powerPTest(h = x, n = n, alpha = alpha, alternative = alternative) - power, 1e-10, 1 - 1e-10)
end

"""
    alphaPTest(h::Real = 0, n::Real = 0, power::Float64 = 0.8, alternative = "two.sided")

Compute the probability of type I error that a sample size `n` has in detecting the effect size `h`
for one sample proportion (P) compared to a constant (c) with power > `power` (default: 0.8)
and type I error = `alpha` (default: 0.05). The effect size `h` = ϕ₁ - ϕ₂ where ϕ₁ = 2*asin(sqrt(P)) and ϕ₂ = 2*asin(sqrt(c)).
The option `alternative` is `two.sided` for H₀: ϕ₁ = ϕ₂ vs H₁: ϕ₁ ≠ ϕ₂, `less` for H₁: ϕ₁ < ϕ₂, and `greater`
for H₁: ϕ₁ > ϕ₂.
"""
function alphaPTest(;
    h::Real = 0,
    n::Real = 0,
    power::Float64 = 0.8,
    alternative::String = "two"
    )

    check_args(h=h,n=n,power=power)

    return fzero(x->powerPTest(h = h, n = n, alpha = x, alternative = alternative) - power, 1e-10, 1 - 1e-10)
end

"""
    pwr.PTest(h::Real = 0, n::Real = 0, alpha::Float64 = 0.0, power::Float64 = 0.0, alternative = "two.sided")

Compute one of the test parameters such as sample size (`n`),
effect size (`h`), type I error (`alpha`), and power (`power`).
The parameter to be estimated must be set to zero (default).
The effect size `h` = ϕ₁ - ϕ₂ where ϕ₁ = 2*asin(sqrt(P)) and ϕ₂ = 2*asin(sqrt(c)).
The option `alternative` is `two.sided` for H₀: ϕ₁ = ϕ₂ vs H₁: ϕ₁ ≠ ϕ₂, `less` for H₁: ϕ₁ < ϕ₂, and `greater`
for H₁: ϕ₁ > ϕ₂.
"""
function PTest(;
    h::Real = 0,
    n::Real = 0,
    alpha::Float64 = 0.0,
    power::Float64 = 0.0,
    alternative::String = "two.sided"
    )
    if sum([x == 0 for x in (h,n,alpha,power)]) != 1
        error("exactly one of `h`, `n`, `power`, and `alpha` must be zero")
    end

    if power == 0.0
        power = powerPTest(h = h, n = n, alpha = alpha, alternative = alternative)
    elseif alpha == 0.0
        alpha = alphaPTest(h = h, n = n, power = power, alternative = alternative)
    elseif h == 0
        h = effectsizePTest(n = n, alpha = alpha, power = power, alternative = alternative)
    elseif n == 0
        n = samplesizePTest(h = h, alpha = alpha, power = power, alternative = alternative)
    end

    alt = Dict("two" => "two-sided", "two.sided" => "two-sided","two sided" => "two-sided", "less" => "less", "greater" => "greater")

    return htest(
        string("Proportion power calculation for binomial distribution (arcsine transformation)"),
        OrderedDict(
            "h" => h,
            "n" => n,
            "alpha" => alpha,
            "power" => power,
            "alternative" => alt[alternative]
            )
        )
end


function normalization(x)
    return (x .- minimum(x))./(maximum(x).-minimum(x))
end


function meantoecc(M::FloatingPoint, ecc::FloatingPoint)
    kepler(E) = E - ecc*sin(E) - M
    kepler_der(E) = 1 - ecc*cos(E)
    return newton(M, kepler, kepler_der)
end

function ecctomean(E::FloatingPoint, ecc::FloatingPoint)
    return E - ecc*sin(E)
end

function ecctotrue(E::FloatingPoint, ecc::FloatingPoint)
    return 2*atan2(sqrt(1 + ecc)*sin(E/2), sqrt(1 - ecc)*cos(E/2))
end

function truetoecc(T::FloatingPoint, ecc::FloatingPoint)
    return 2*atan2(sqrt(1 - ecc)*sin(T/2), sqrt(1 + ecc)*cos(T/2))
end

function period(a::FloatingPoint, mu::FloatingPoint)
    return sqrt(4*a^3*pi^2/mu)
end

function period(s::State)
    mu = planets[s.body]["mu"]
    ele = elements(s)
    return period(ele[1], mu)
end

function kepler(ele::Vector, dt::FloatingPoint, mu::FloatingPoint)
    E0 = truetoecc(ele[6], ele[2])
    M0 = ecctomean(E0, ele[2])
    n = 2*pi/period(ele[1], mu)
    M = M0 + n*dt
    E = meantoecc(M, ele[2])
    T = ecctotrue(E, ele[2])
    return [ele[1:5], T]
end

function kepler(ele::Vector, dt::Vector, mu::FloatingPoint)
    out = Array(FloatingPoint, length(dt), 6)
    for i = 1:length(dt)
        out[i,:] = kepler(ele, dt[i], mu)
    end
    return out
end


function f(x::Array{Float64,2})
  x[1]^2 + x[2]^2
end
function gradient(x::Array{Float64,2})
  [2*x[1] 2*x[2]]
end

x = [1.0 3.0]
dd = DifferentiableDensity(f,gradient)
@assert dd.f(x) == 10.0
@assert dd.gradient(x) == [2.0 6.0]


type Point{T}
  x::T
  y::T
end

Point{Int64} #> Point{Int64} (constructor with 1 method)
Point{Float64} #> Point{Float64} (constructor with 1 method)
Point{String} #> Point{String} (constructor with 1 method)

issubtype(Point{Int64}, Point) #> true
issubtype(Point{String}, Point) #> true
issubtype(Point{Float64}, Point{Real}) #> false

p = Point{Int64}(2, 5) #> Point{Int64}(2,5)
p = Point(2, 5) #> Point{Int64}(2,5)
p = Point("London", "Great-Britain") #> Point{String}("London", "Great-Britain")

type PointP{T <: Real}
  x::T
  y::T
end

# p = PointP("London", "Great-Britain") #> ERROR: `PointP{T<:Real}` has no method
# matching Point{T<:Real}(::ASCIIString, : ASCIIString)

add{T}(x::T, y::T) = x + y
add(2, 3) #> 5
# add(2, 3.0) #> ERROR: `add` has no method matching add(::Int64, ::Float64)
add{T<:Number}(x::T, y::T) = x + y

function vecfloat{T <: FloatingPoint}(x::Vector{T})
# code
end


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

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

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

n = length(μ)                   #number of assets

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

#=
using Random
Random.seed!(123)

n = 15                                      #number of assets, CHANGE IT?

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

# First we solve without any bounds on $w$

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

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

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

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

w_lower = 0                     #bounds on w
w_upper = 1

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

#-

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

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

Lmax = 0.5

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

#-

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

#-


# using StaticArrays

# Define Rosenbrock function: https://en.wikipedia.org/wiki/Rosenbrock_function
#
# This function can be interpreted as a NLS pb:
# r_1 = (1-θ₁)
# r_2 = 10(θ₂-θ₁²)
#
# Jacobian is: [[-1, 0],[-20, 10]]
#
# Minimum for θ=(1,1) where f=1/2 r^2 = 0
#
# TODO: implement even n (here 2) generalization of the wiki page.
#
struct Rosenbrock <: AbstractNLS
end

parameter_size(::Rosenbrock) = 2
residue_size(::Rosenbrock) = 2

function eval_r(nls::Rosenbrock,θ::AbstractVector{T}) where T
    @assert length(θ)==parameter_size(nls)

    T[ 1-θ[1], 10*(θ[2]-θ[1]^2) ]
end

function eval_r_J(nls::Rosenbrock,θ::AbstractVector{T}) where T
    @assert length(θ)==parameter_size(nls)

    r = T[ 1-θ[1], 10*(θ[2]-θ[1]^2) ]
    J = T[ -1 0; -20*θ[1] 10]

    (r,J)
end

# ================================================================

const Rosenbrock_Static = Rosenbrock

# # Define the same Rosenbrock function but using Statics Arrays... This
# # cause some problems: https://github.com/JuliaArrays/StaticArrays.jl/issues/971
# #
# struct Rosenbrock_Static <: AbstractNLS
# end

# parameter_size(::Rosenbrock_Static) = 2
# residue_size(::Rosenbrock_Static) = 2

# function eval_r(nls::Rosenbrock_Static,θ::AbstractVector{T}) where T
#     @assert length(θ)==parameter_size(nls)

#     r = @SVector T[1-θ[1],
#                    10*(θ[2]-θ[1]^2)]
# end

# function eval_r_J(nls::Rosenbrock_Static,θ::AbstractVector{T}) where T
#     @assert length(θ)==parameter_size(nls)

#     r = eval_r(nls,θ)

#     # CAVEAT: SMatrix are filled column by column
#     #
#     # The Jacobian
#     #
#     # | ∂1r1, ∂2r1 |
#     # | ∂1r2, ∂2r2 |
#     #
#     # must be created by
#     #
#     # SMatrix(∂1r1,∂1r2, # col 1
#     #         ∂2r1,∂2r2) # col 2
#     #
#     # which is not intuitive and error prone.
#     #
#     # The alternative to is to use the @SMatrix macro
#     # that follows the "natural" order (row by row)
#     #
#     J = @SMatrix T[      -1   +0;     # ∂1r1, ∂2r1
#                          -20*θ[1]  +10]     # ∂1r2, ∂2r2

#     (r, J)
# end


# functions related to F distribution

import .RFunctions:
    fdistpdf,
    fdistlogpdf,
    fdistcdf,
    fdistccdf,
    fdistlogcdf,
    fdistlogccdf,
    fdistinvcdf,
    fdistinvccdf,
    fdistinvlogcdf,
    fdistinvlogccdf

# pdf for numbers with generic types
fdistpdf(ν1::Real, ν2::Real, x::Number) = sqrt((ν1 * x)^ν1 * ν2^ν2 / (ν1 * x + ν2)^(ν1 + ν2)) / (x * beta(ν1 / 2, ν2 / 2))

# logpdf for numbers with generic types
fdistlogpdf(ν1::Real, ν2::Real, x::Number) = (ν1 * log(ν1 * x) + ν2 * log(ν2) - (ν1 + ν2) * log(ν1 * x + ν2)) / 2 - log(x) - lbeta(ν1 / 2, ν2 / 2)


# ================================
# Concrete symmetric interval hull
# ================================

"""
    symmetric_interval_hull(S::LazySet{N}) where {N}

Overapproximate a set by a tight hyperrectangle centered in the origin.

### Input

- `S` -- set

### Output

A tight hyperrectangle that is centrally symmetric wrt. the origin.

### Algorithm

The center of the box is the origin, and the radius is obtained by computing the
maximum value of the support function evaluated in the canonical directions.
"""
function symmetric_interval_hull(S::LazySet{N}) where {N}
    # fallback returns a hyperrectangular set
    (c, r) = box_approximation_helper(S)
    if r[1] < 0
        return EmptySet{N}(dim(S))
    end
    return Hyperrectangle(zeros(N, length(c)), abs.(c) .+ r)
end

"""
    box_approximation_symmetric

Alias for `symmetric_interval_hull`.
"""
box_approximation_symmetric = symmetric_interval_hull

# ===============
# Specializations
# ===============

# empty set specialization
symmetric_interval_hull(∅::EmptySet) = ∅

function LazySets.concretize(sih::SymmetricIntervalHull)
    return symmetric_interval_hull(LazySets.concretize(sih.X))
end

# interval specialization
function symmetric_interval_hull(x::Interval)
    abs_inf = abs(min(x))
    abs_sup = abs(max(x))
    bound = max(abs_sup, abs_inf)
    return Interval(-bound, bound)
end

# hyperrectangle specialization
@inline function _maxabs(c::N, r::N) where {N}
    return c >= zero(N) ? c + r : -c + r
end

function symmetric_interval_hull(H::Hyperrectangle{N}) where {N}
    n = dim(H)
    r = Vector{N}(undef, n)
    @inbounds for i in 1:n
        r[i] = _maxabs(H.center[i], H.radius[i])
    end
    return Hyperrectangle(zeros(N, n), r)
end


export legendrePoly #Just a basic export to make legendrePoly function visible (ie. Public)

## Function that computes the legendre polynomial of nᵗʰ order
#  INPUTS:
# Type T - ?????
# order - Integer defining the desired Legendre polynomial order
#  OUTPUTS:
# Pₙ(x) - Legendre polynomial of order n="order"

function legendrePoly(::Type{T},order::Int) where {T} #compute Legendre polynomial
##Throw an error if we ask for a polynomial of negative order
    order<0 && error("order must be >= 0") #If order is less than zero, we throw an error
##Manually input legendre polynomials of orders 0 and 1
    P = [Polynomial{T}(1), Polynomial{T}([0,1])] #initialize our horizon for pseudo-recursion with known Legendre Polynomials: P=[1,x]
    x = Polynomial{T}([0,1]) #x is just an instance of type Polynomial
##For orders 0 or 1 we return 1 or x respectively
    order<2 && return P[order+1] #if order is 1 return x, if order is 0 return 1
## For order>=2: we are using Bonnet recursion formula: nPₙ(x)=(2n-1)Pₙ₋₁(x)-(n-1)Pₙ₋₂(x)
# Compute Pᵢ₊₁ from Pᵢ and Pᵢ₋₁ in a pseudo-recursive way
    for i = 1:order-1 #i is effectively n-1 (ie. current order-1) because we index from 1 to "order-1" instead of the natural 2 to "order"
        Ptmp = ( (2*i + 1 ) * x * P[2] - i * P[1]) / (i+1) #this computes Pᵢ₊₁ in a pseudo recursive manner
        P[1] = P[2] #here we save Pᵢ for next iteration
        P[2] = Ptmp #here we save Pᵢ₊₁ for next iteration
    end
## Return P of order "order"
    return P[2]
end

legendrePoly(order::T) where {T} = legendrePoly(T,convert(Int,order)) #I suspect here he tried to do some proper recursion, but idk


using ApproxFunSingularities, ApproxFunOrthogonalPolynomials, ApproxFunBase, IntervalSets, Random, LinearAlgebra, Test
    import ApproxFunBase: testfunctional, testbandedbelowoperator, testbandedoperator

@testset "Integral equations" begin
    @time for S in (JacobiWeight(-0.5,-0.5,Chebyshev()),
                JacobiWeight(-0.5,-0.5,Chebyshev(Segment(1.0,2.0+im))),
                JacobiWeight(0.5,0.5,Ultraspherical(1,Segment(1.0,2.0+im))))
        testfunctional(DefiniteLineIntegral(S))
    end

    @testset "three segment bug" begin
        dom = Segment(-1.0,1) ∪ Segment(1.0+im,1+2im) ∪ Segment(-2.0+im,-1+2im)

        ⨍ = DefiniteLineIntegral(dom)
        S = domainspace(⨍)
        @test ApproxFunBase.blockbandwidths(⨍) == (0,0)

        f = Fun(S,rand(20))

        @test DefiniteLineIntegral(component(dom,1))*component(f,1) ≈ linesum(component(f,1))
        @test DefiniteLineIntegral(component(dom,2))*component(f,2) ≈ linesum(component(f,2))
        @test DefiniteLineIntegral(component(dom,3))*component(f,3) ≈ linesum(component(f,3))

        @test ⨍*f ≈ linesum(f)
    end

    @testset "integral equation" begin
        #The first test checks the solution of the integral equation
        # u(x) + \int_{-1}^{+1} \frac{e^{y} u(y)}{\sqrt{1-y^2}} dy = f
        # on the interval -1..1.

        x = Fun(identity)
        w = 1/sqrt(1-x^2)
        d = domain(x)

        Σ = DefiniteIntegral(d)
        @test bandwidths(Σ) == (0,0)

        @test domainspace(Σ) ==
            JacobiWeight{Chebyshev{ChebyshevInterval{Float64}},ChebyshevInterval{Float64},Float64,Float64}(-0.5,-0.5,Chebyshev())

        L = I+Σ[exp(x)*w]
        testbandedoperator(L)
        usol = sin(2x)
        f = L*usol

        @time u = L\f
        @test norm(u-usol) <= 10eps()
    end

    @testset "integro-differential equation" begin
        #The second test checks the solution of the integro-differential equation
        # u'(x) + x u(x) + \int_{-2}^{+2} sin(y-x) u(y) \sqrt{4-y^2} dy = f
        # on the interval -2..2, with u(-2) = 1.

        x = Fun(identity,-2..2)
        w = sqrt(4-x^2)
        d = domain(x)

        D = Derivative(d)
        B = ldirichlet(d)
        Σ = DefiniteIntegral(.5,.5,d)

        @test domainspace(Σ) ==
            JacobiWeight{Ultraspherical{Int,ClosedInterval{Int},Float64},ClosedInterval{Float64},Float64,Float64}(.5,.5,Ultraspherical(1,d))

        K = LowRankFun((x,y)->sin(y-x)*w(y),Ultraspherical(1,d),domainspace(Σ))

        L = D+x+Σ[K]
        usol = cospi(20x)
        f = L*usol
        @time u = [B;L]\[1.;f]

        @test norm(u-usol) ≤ 200eps()
    end

    @testset "arcs" begin
        d = exp(im*Segment(0.1,0.2))
        x = Fun(d)
        @time w=1/(sqrt(abs(leftendpoint(d)-x))*sqrt(abs(rightendpoint(d)-x)))

        @test linesum(w) ≈ DefiniteLineIntegral()*w
    end

    @testset "Volterra integral equation" begin
        d = 0.0..1.0
        V = Volterra(d)
        K = LowRankFun((x,y)->sin(y-x),d^2)
        L = I-V[K]
        # broken due to gbmv inconsistency
        # testbandedoperator(L)
        f = Fun(exp,d)
        @test domainspace(L) == Legendre(d)
        @test rangespace(L) == Legendre(d)
        @test bandrange(V) == -1:0
        @time u = L\f
        @test norm(L*u-f) ≤ 20eps()
    end

    @testset "DefiniteIntegral" begin
        @time for S in (JacobiWeight(0.5,0.5,Ultraspherical(1,Segment(-2,-1))),
                  JacobiWeight(0.5,0.5,Ultraspherical(1,Segment(-2,-1+2im))),
                  JacobiWeight(1.5,1.5,Ultraspherical(2,Segment(-2,-1+2im))),
                  JacobiWeight(-0.5,-0.5,Chebyshev(Segment(-2,-1+2im))),
                  JacobiWeight(0.67,0.123,Chebyshev(Segment(-2,-1+2im))),
                  JacobiWeight(0.67,0.123,Ultraspherical(1,Segment(-2,-1+2im))))
            f=Fun(S,[1.,2.,3.])
            @test DefiniteIntegral(space(f))*f ≈ sum(f)
            @test DefiniteLineIntegral(space(f))*f ≈ linesum(f)
        end
    end

    @testset "Fredholm integral" begin
        K=LowRankFun((x,y)->cos(x-y),ChebyshevInterval()^2)
        Σ=DefiniteIntegral(Chebyshev())
        testbandedbelowoperator(Σ[K])

        Σ = DefiniteIntegral(Chebyshev()); x=Fun();
        L=I+exp(x)*Σ[cos(x)]
        testbandedbelowoperator(L)
    end

    @testset "union domain" begin
        dom = ApproxFunBase.UnionDomain(0..1, 2..3)
        ⨍ = DefiniteLineIntegral(union(JacobiWeight.(-0.5,-0.5,ChebyshevDirichlet{1,1}.(components(dom)))...))

        x = Fun(dom)
        f = exp(x)/(sqrt(x*abs(1-x))*sqrt(abs(2-x)*abs(3-x)))
        @test sum(f) ≈ Number(⨍*f)
    end
end


function _minimum!(D,clusterInd)
    N = size(D,1)
    m = Inf
    mI = mJ = -1
    for j=2:N
        clusterInd[j]==0 && continue
        for i=1:j-1
            clusterInd[i]==0 && continue
            if D[i,j]<=m
                m = D[i,j]
                mI,mJ = i,j
            end
        end
    end
    m, mI, mJ
end

# only upper triangular part of D is used
function hierarchicalclustering(D::AbstractMatrix{T}; similarity=false) where {T}
    @assert size(D,1)==size(D,2)
    N = size(D,1)

    distType = promote_type(Float64,T)
    D = copy(convert(Array{distType},D)) # since D will be updated when clusters are merged
    similarity && (D*=-1) # just change sign when using similarity instead of dissimilarity! (Nothing breaks if we have distances below 0. Relative values are all that matter.)

    # index, index, nbr elements in cluster, distance/similarity
    # indices 1-N
    clusters = Vector{Tuple{Int,Int,Int,distType}}()

    # the cluster index for each row/column in D. Indices from 1:N are leaves.
    # From N+1 and upwards are in clusters Vector (subtract N from index).
    # A value of 0 means that the row/column is no longer used. (One row/column is discarded every time two clusters are merged.)
    clusterInd = collect(1:N)

    for k=1:N-1 # each created cluster has two children, i.e. we need N-1 clusters (not counting leaves)
        m,mI,mJ = _minimum!(D,clusterInd)

        # create new cluster
        cI,cJ = clusterInd[mI], clusterInd[mJ]

        nbrElementsI = cI<=N ? 1 : clusters[cI-N][3]
        nbrElementsJ = cJ<=N ? 1 : clusters[cJ-N][3]

        dist = D[mI,mJ]
        similarity && (dist*=-1) # change back from distance to similarity if needed
        push!(clusters, (cI,cJ,nbrElementsI+nbrElementsJ,dist))

        # update distance matrix and clusterInd
        clusterInd[mJ] = 0
        clusterInd[mI] = k+N

        # construct new distances from D[mI,u], D[mJ,u]
        for u=1:N
            u==mI && continue
            clusterInd[u]==0 && continue # includes u==mJ

            mI2,u2 = minmax(mI,u) # upper triangular index
            D[mI2,u2] = (D[mI2,u2]*nbrElementsI + D[minmax(mJ,u)...]*nbrElementsJ) / (nbrElementsI+nbrElementsJ) # TODO: change here for different linkage criteria
        end

    end

    clusters
end

function samplesinclusters(clusters)
    N = length(clusters)+1 # Since every merge makes two clusters into one, we can figure out the original number of samples this way.
    samples = Vector{Vector{Int}}(length(clusters))
    for (i,c) in enumerate(clusters)
        samples[i] = vcat( c[1]>N ? samples[c[1]-N] : [c[1]],
                           c[2]>N ? samples[c[2]-N] : [c[2]] )
    end
    samples
end