@info "Testing linear.jl"

@testset "canonical_coordinates!" begin
    p = 10
    m = 5

    for T in (Float32, Float64)
        LDM = DA.LinearDiscriminantModel{T}

        Σ_within, W_svd, W_chol = random_cov(T, p)

        π = rand(T, m)
        π ./= sum(π)

        M = random_centroids(T, m, p)
        μ = vec(M*π)
        Mc = M .- μ

        Σ_between = Symmetric(Mc*(π .* transpose(Mc)))
        C_ref = eigen(Σ_between, Σ_within).vectors[:, range(p, step=-1, length=m-1)]
        C_ref = copy(transpose(C_ref))

        # Dimensionality reduction possible
        for (dims, W_test) in [(1,W_svd), (1,W_chol), (2,W_svd), (2,W_chol)]
            is_row = dims == 1

            lda_test = LDM()
            lda_test.Θ.dims = dims
            lda_test.Θ.π = copy(π)
            lda_test.Θ.μ = copy(vec(μ))
            if is_row
                lda_test.Θ.M = copy(transpose(M))
                lda_test.W = copy(transpose(W_test))
            else
                lda_test.Θ.M = copy(M)
                lda_test.W = copy(W_test)
            end

            DA.canonical_coordinates!(lda_test)

            if is_row
                @test isapprox(abs.(lda_test.C), abs.(transpose(C_ref)))
            else
                @test isapprox(abs.(lda_test.C), abs.(C_ref))
            end
        end

        # No dimensionality reduction
        M = random_centroids(T, m, m-1)
        for dims in [1, 2]
            is_row = dims == 1

            lda_test = LDM()
            lda_test.Θ.dims = dims

            lda_test.Θ.M = copy(is_row ? transpose(M) : M)
            lda_test.W = ones(T, m-1, m-1)

            DA.canonical_coordinates!(lda_test)

            @test isapprox(lda_test.C, Matrix{T}(I, m-1, m-1))
        end
    end
end

@testset "_fit!" begin
    nₘ = [400; 500; 600]
    p = 10
    n = sum(nₘ)
    m = length(nₘ)

    for T in (Float32, Float64)
        X, y, M = random_data(T, nₘ, p)
        π = convert(Vector{T}, nₘ/m)
        Xt = copy(transpose(X))
        Mt = copy(transpose(M))

        π_tests = [nothing, ones(T,m)/m]
        γ_tests = [nothing, range(zero(T), stop=one(T), length=3)...]
        tf_tests = [true, false]

        LDM = DA.LinearDiscriminantModel{T}

        for M_input in [nothing, perturb(M)]
            X_test = copy(X)
            M_test = M_input === nothing ? M : M_input
            Xc = X_test .- M_test[:, y]
            Σ = (Xc*transpose(Xc)) ./ (n-m)

            Xt_test = copy(transpose(X_test))
            Mt_test = copy(transpose(M_test))
            Mt_input = M_input === nothing ? nothing : copy(transpose(M_input))

            for γ in γ_tests
                if γ !== nothing
                    Σ_test = (1-γ)*Σ + γ*(tr(Σ)/p)*I
                else
                    Σ_test = copy(Σ)
                end

                for π_test in π_tests, compute_cov in tf_tests, canonical in tf_tests
                    lda_test = DA._fit!(LDM(), y, copy(Xt_test), 1, canonical, compute_cov,
                                        Mt_input, π_test, γ)

                    @test lda_test.Θ.fit == true
                    @test lda_test.Θ.dims == 1
                    @test lda_test.Θ.γ == γ
                    @test isapprox(lda_test.Θ.M, Mt_test)
                    @test isapprox(lda_test.δ₀, det(Σ_test))
                    if compute_cov
                        @test isapprox(lda_test.Θ.Σ, Σ)
                    else
                        @test lda_test.Θ.Σ === nothing
                    end
                    if canonical
                        @test size(lda_test.C) == (p, min(m-1,p))
                    else
                        @test lda_test.C === nothing
                    end

                    lda_test = DA._fit!(LDM(), y, copy(X_test), 2, canonical, compute_cov,
                                        M_input, π_test, γ)

                    @test lda_test.Θ.fit == true
                    @test lda_test.Θ.dims == 2
                    @test lda_test.Θ.γ == γ
                    @test isapprox(lda_test.Θ.M, M_test)
                    @test isapprox(lda_test.δ₀, det(Σ_test))
                    if compute_cov
                        @test isapprox(lda_test.Θ.Σ, Σ)
                    else
                        @test lda_test.Θ.Σ === nothing
                    end
                    if canonical
                        @test size(lda_test.C) == (min(m-1,p), p)
                    else
                        @test lda_test.C === nothing
                    end
                end
            end
        end
    end
end

@testset "discriminants!" begin
    n = 500

    for T in (Float32, Float64)
        y = repeat([1,2], inner=n)
        M = T[2 -2;
              2 -2]

        Z = randn(T, 2, 2*n)
        for k = 1:2
            Z[:, y .== k] .-= mean(Z[:, y .== k], dims=2)
        end
        Z = sqrt(inv(Z*transpose(Z)/(2n-2)))*Z

        X = Z + M[:, y]
        C = T[sqrt(2)/2 sqrt(2)/2]

        Δ = zeros(T, 2, 2n)
        for k = 1:2
            Δ[k, :] = log(convert(T, 0.5)) .- sum(abs2.(X .- M[:, k:k]), dims=1)/2
        end

        LDM = DA.LinearDiscriminantModel{T}

        lda_test = DA._fit!(LDM(), y, copy(X), 2, false)
        Δ_test = DA.discriminants!(zeros(T, 2, 2n), lda_test, X)

        @test isapprox(Δ_test, Δ)

        lda_test = DA._fit!(LDM(), y, copy(transpose(X)), 1, false)
        Δ_test = DA.discriminants!(zeros(T, 2n, 2), lda_test, copy(transpose(X)))
    end
end


using Alea, FactCheck

facts("Multiindex") do

  context("multiindex()") do
    @fact multiindex(0, 4) --> zeros(1, 0)
    @fact multiindex(5, 0) --> zeros(1,5)
    @fact multiindex(1, 3) --> [0 1 2 3]'
    @fact multiindex(2, 2) --> [0 0; 0 1; 1 0; 0 2; 1 1; 2 0]
  end

  context("multiindex_order()") do
    @fact multiindex_order([0 0; 1 2; 3 5]) --> [0, 3, 8]
  end

end


#=
--------------------------------------------------------------------------------------------------------------------------------
Base: time series
--------------------------------------------------------------------------------------------------------------------------------
=#

"""
    companion_form(Ψ::Array{Float64,2}, Σ::SymMatrix)

Construct the companion form parameters of a VAR(p) with coefficients Ψ and var-cov matrix of the residuals Σ.
"""
function companion_form(Ψ::Array{Float64,2}, Σ::SymMatrix)

    # Dimensions
    n = size(Σ,2);
    p = Int64(size(Ψ,2)/n);
    np_1 = n*(p-1);

    # Companion form VAR(p)
    C = [Ψ; Matrix(I, np_1, np_1) zeros(np_1, n)];
    V = Symmetric([Σ zeros(n, np_1); zeros(np_1, n*p)])::SymMatrix;

    # Return output
    return C, V;
end

"""
    ext_companion_form(Ψ::Array{Float64,2}, Σ::SymMatrix)

Construct the companion form parameters of a VAR(p) with coefficients Ψ and var-cov matrix of the residuals Σ. The companion form is extend with additional n entries.
"""
function ext_companion_form(Ψ::Array{Float64,2}, Σ::SymMatrix)

    # Dimensions
    n = size(Σ,2);
    p = Int64(size(Ψ,2)/n);
    np = n*p;

    # Companion form VAR(p)
    C = [Ψ zeros(n, n); Matrix(I, np, np) zeros(np, n)];
    V = Symmetric([Σ zeros(n, np); zeros(np, np+n)])::SymMatrix;

    # Return output
    return C, V;
end

#=
-------------------------------------------------------------------------------------------------------------------------------
Combinatorics and probability
-------------------------------------------------------------------------------------------------------------------------------
=#

"""
    no_combinations(n::Int64, k::Int64)

Compute the binomial coefficient of `n` observations and `k` groups, for big integers.

# Examples
```jldoctest
julia> no_combinations(1000000,100000)
7.333191945934207610471288280331309569215030711272858517142085449641265002716664e+141178
```
"""
no_combinations(n::Int64, k::Int64) = factorial(big(n))/(factorial(big(k))*factorial(big(n-k)));

"""
    rand_without_replacement(nT::Int64, d::Int64)

Draw `length(P)-d` elements from the positional vector `P` without replacement.
`P` is permanently changed in the process.

rand_without_replacement(n::Int64, T::Int64, d::Int64)

Draw `length(P)-d` elements from the positional vector `P` without replacement.
In the sampling process, no more than n-1 elements are removed for each point in time.
`P` is permanently changed in the process.

# Examples
```jldoctest
julia> rand_without_replacement(20, 5)
15-element Array{Int64,1}:
  1
  2
  3
  5
  7
  8
 10
 11
 13
 14
 16
 17
 18
 19
 20
```
"""
function rand_without_replacement(nT::Int64, d::Int64)

    # Positional vector
    P = collect(1:nT);

    # Draw without replacement d times
    for i=1:d
        deleteat!(P, findall(P.==rand(P)));
    end

    # Return output
    return setdiff(1:nT, P);
end

function rand_without_replacement(n::Int64, T::Int64, d::Int64)

    # Positional vector
    P = collect(1:n*T);

    # Full set of coordinates
    coord = [repeat(1:n, T) kron(1:T, convert(Array{Int64}, ones(n)))];

    # Counter
    coord_counter = convert(Array{Int64}, zeros(T));

    # Loop over d
    for i=1:d

        while true

            # New candidate draw
            draw = rand(P);
            coord_draw = @view coord[draw, :];

            # Accept the draw if all observations are not missing for time t = coord[draw, :][2]
            if coord_counter[coord_draw[2]] < n-1
                coord_counter[coord_draw[2]] += 1;

                # Draw without replacement
                deleteat!(P, findall(P.==draw));
                break;
            end
        end
    end

    # Return output
    return setdiff(1:n*T, P);
end


zero(a::GroupElem) = zero(parent(a))
one(a::GroupElem) = one(parent(a))

# http://sagenb.org/src/groups/generic.py
multiple(a::GroupElem, n::Integer, op) = begin
    if op === +
        return multiple(a, n, +, x -> -x, zero(a))
    elseif op === *
        return multiple(a, n, *, inv, one(a))
    else
        throw(MethodError())
    end
end

multiple(a::T, n::Integer, op, inverse, identity::T) where {T<:GroupElem} = begin
    n == 0 && return identity

    if n < 0
        n = -n
        a = inverse(a)
    end

    n == 1 && return a

    # check for idempotence, and store the result otherwise
    aa = op(a, a)
    aa == a && return a

    n == 2 && return aa
    n == 3 && return op(aa, a)
    n == 4 && return op(aa, aa)

    # since we've computed a^2, let's start squaring there
    # so, let's keep the least-significant bit around, just
    # in case.
    m = n & 1
    n = n >> 1

    # One multiplication can be saved by starting with
    # the second-smallest power needed rather than with 1
    # we've already squared a, so let's start there.
    apow = aa
    while n & 1 == 0
        apow = op(apow, apow)
        n = n >> 1
    end
    power = apow
    n = n >> 1

    # now multiply that least-significant bit in...
    m == 1 && (power = op(power, a))

    # and this is straight from the book.
    while n != 0
        apow = op(apow, apow)
        n & 1 != 0 && (power = op(power, apow))
        n = n >> 1
    end

    power
end

# TODO: improve! => http://sagenb.org/src/groups/generic.py
log(a::T, base::T, ord::fmpz) where {T<:GroupElem} = log(a, base, BigInt(ord))
log(a::T, base::T, ord::Integer) where {T<:GroupElem} = begin
    n = 1
    b = base
    while a != b
        n += 1
        b *= base
    end
    n
end


"""
The [Multinomial distribution](http://en.wikipedia.org/wiki/Multinomial_distribution)
generalizes the *binomial distribution*. Consider n independent draws from a Categorical
distribution over a finite set of size k, and let ``X = (X_1, ..., X_k)`` where ``X_i``
represents the number of times the element ``i`` occurs, then the distribution of ``X``
is a multinomial distribution. Each sample of a multinomial distribution is a k-dimensional
integer vector that sums to n.

The probability mass function is given by

```math
f(x; n, p) = \\frac{n!}{x_1! \\cdots x_k!} \\prod_{i=1}^k p_i^{x_i},
\\quad x_1 + \\cdots + x_k = n
```

```julia
Multinomial(n, p)   # Multinomial distribution for n trials with probability vector p
Multinomial(n, k)   # Multinomial distribution for n trials with equal probabilities
                    # over 1:k
```
"""
struct Multinomial{T<:Real} <: DiscreteMultivariateDistribution
    n::Int
    p::Vector{T}

    function Multinomial{T}(n::Integer, p::Vector{T}) where T
        if n < 0
            throw(ArgumentError("n must be a nonnegative integer."))
        end
        if !isprobvec(p)
            throw(ArgumentError("p = $p is not a probability vector."))
        end
        new{T}(round(Int, n), p)
    end
    Multinomial{T}(n::Integer, p::Vector{T}, ::NoArgCheck) where {T} = new{T}(round(Int, n), p)
end
Multinomial(n::Integer, p::Vector{T}) where {T<:Real} = Multinomial{T}(n, p)
Multinomial(n::Integer, k::Integer) = Multinomial{Float64}(round(Int, n), fill(1.0 / k, k))

# Parameters

ncategories(d::Multinomial) = length(d.p)
length(d::Multinomial) = ncategories(d)
probs(d::Multinomial) = d.p
ntrials(d::Multinomial) = d.n

params(d::Multinomial) = (d.n, d.p)
@inline partype(d::Multinomial{T}) where {T<:Real} = T

### Conversions
convert(::Type{Multinomial{T}}, d::Multinomial) where {T<:Real} = Multinomial(d.n, Vector{T}(d.p))
convert(::Type{Multinomial{T}}, n, p::Vector) where {T<:Real} = Multinomial(n, Vector{T}(p))

# Statistics

mean(d::Multinomial) = d.n .* d.p

function var(d::Multinomial{T}) where T<:Real
    p = probs(d)
    k = length(p)
    n = ntrials(d)

    v = Vector{T}(k)
    for i = 1:k
        @inbounds p_i = p[i]
        v[i] = n * p_i * (1 - p_i)
    end
    v
end

function cov(d::Multinomial{T}) where T<:Real
    p = probs(d)
    k = length(p)
    n = ntrials(d)

    C = Matrix{T}(k, k)
    for j = 1:k
        pj = p[j]
        for i = 1:j-1
            @inbounds C[i,j] = - n * p[i] * pj
        end

        @inbounds C[j,j] = n * pj * (1-pj)
    end

    for j = 1:k-1
        for i = j+1:k
            @inbounds C[i,j] = C[j,i]
        end
    end
    C
end

function mgf(d::Multinomial{T}, t::AbstractVector) where T<:Real
    p = probs(d)
    n = ntrials(p)
    s = zero(T)
    for i in 1:length(p)
        s += p[i] * exp(t[i])
    end
    return s^n
end

function cf(d::Multinomial{T}, t::AbstractVector) where T<:Real
    p = probs(d)
    n = ntrials(d)
    s = zero(Complex{T})
    for i in 1:length(p)
        s += p[i] * exp(im * t[i])
    end
    return s^n
end

function entropy(d::Multinomial)
    n, p = params(d)
    s = -lgamma(n+1) + n*entropy(p)
    for pr in p
        b = Binomial(n, pr)
        for x in 0:n
            s += pdf(b, x) * lgamma(x+1)
        end
    end
    return s
end

# Evaluation

function insupport(d::Multinomial, x::AbstractVector{T}) where T<:Real
    k = length(d)
    length(x) == k || return false
    s = 0.0
    for i = 1:k
        @inbounds xi = x[i]
        if !(isinteger(xi) && xi >= 0)
            return false
        end
        s += xi
    end
    return s == ntrials(d)  # integer computation would not yield truncation errors
end

function _logpdf(d::Multinomial, x::AbstractVector{T}) where T<:Real
    p = probs(d)
    n = ntrials(d)
    S = eltype(p)
    R = promote_type(T, S)
    s = R(lgamma(n + 1))
    t = zero(T)
    for i = 1:length(p)
        @inbounds xi = x[i]
        @inbounds p_i = p[i]
        t += xi
        s -= R(lgamma(xi + 1))
        @inbounds s += xi * log(p_i)
    end
    return ifelse(t == n, s, -R(Inf))
end

# Sampling

_rand!(d::Multinomial, x::AbstractVector{T}) where {T<:Real} = multinom_rand!(ntrials(d), probs(d), x)

sampler(d::Multinomial) = MultinomialSampler(ntrials(d), probs(d))

## Fit model

struct MultinomialStats <: SufficientStats
    n::Int  # number of trials in each experiment
    scnts::Vector{Float64}  # sum of counts
    tw::Float64  # total sample weight

    MultinomialStats(n::Int, scnts::Vector{Float64}, tw::Real) = new(n, scnts, Float64(tw))
end

function suffstats(::Type{Multinomial}, x::Matrix{T}) where T<:Real
    K = size(x, 1)
    n::T = zero(T)
    scnts = zeros(K)

    for j = 1:size(x,2)
        nj = zero(T)
        for i = 1:K
            @inbounds xi = x[i,j]
            @inbounds scnts[i] += xi
            nj += xi
        end

        if j == 1
            n = nj
        elseif nj != n
            error("Each sample in X should sum to the same value.")
        end
    end
    MultinomialStats(n, scnts, size(x,2))
end

function suffstats(::Type{Multinomial}, x::Matrix{T}, w::Array{Float64}) where T<:Real
    length(w) == size(x, 2) || throw(DimensionMismatch("Inconsistent argument dimensions."))

    K = size(x, 1)
    n::T = zero(T)
    scnts = zeros(K)
    tw = 0.

    for j = 1:size(x,2)
        nj = zero(T)
        @inbounds wj = w[j]
        tw += wj
        for i = 1:K
            @inbounds xi = x[i,j]
            @inbounds scnts[i] += xi * wj
            nj += xi
        end

        if j == 1
            n = nj
        elseif nj != n
            error("Each sample in X should sum to the same value.")
        end
    end
    MultinomialStats(n, scnts, tw)
end

fit_mle(::Type{Multinomial}, ss::MultinomialStats) = Multinomial(ss.n, ss.scnts * inv(ss.tw * ss.n))

function fit_mle(::Type{Multinomial}, x::Matrix{T}) where T<:Real
    ss = suffstats(Multinomial, x)
    Multinomial(ss.n, multiply!(ss.scnts, inv(ss.tw * ss.n)))
end

function fit_mle(::Type{Multinomial}, x::Matrix{T}, w::Array{Float64}) where T<:Real
    ss = suffstats(Multinomial, x, w)
    Multinomial(ss.n, multiply!(ss.scnts, inv(ss.tw * ss.n)))
end


println("Hello, World!")

typeof(1 + -2)

typeof(1.2 - 2.3)

π

typeof(π)

typeof(2 + 3im)

typeof("This is Julia")

typeof("π is about 3.1415")

#'Julia symbols provide a way to make human readable unique identifiers.

:my_id
typeof(:my_id)

1 + 1

#'Even math involving complex numbers

(2 + 1im) * (1 - 2im)

sin(2π / 3) == √3 / 2

sin(2π / 3) - √3 / 2

sin(2π / 3) ≈ √3 / 2

1 + 1e-16 == 1

(1 + 1e-16) - 1e-16 == 1 + (1e-16 - 1e-16)

b = [5, 6]

A = [1 2; 3 4]

x = A \ b

A * x

A * x == b

@show b' * b
@show b * b';

t = ("hello", 1.2, :foo)

typeof(t)

t[2]

a, b, c = t
b

t = (word = "hello", num = 1.2, sym = :foo)

t.word

d1 = Dict(1 => "A", 2 => "B", 4 => "D")

d1[2]

Dict("A" => 1, "B" => 2.5, "D" => 2 - 3im)

d2 = Dict("A" => 1, "B" => 2, "D" => Dict(:foo => 3, :bar => 4))

d2["B"]

d2["D"][:foo]

for i in 1:5
    println(i)
end

for i in [1.2, 2.3, 3.4, 4.5, 5.6]
    println(i)
end

for (key, value) in Dict("A" => 1, "B" => 2.5, "D" => 2 - 3im)
    println("$key: $value")
end

i = 10
for i in 0:3:15
    if i < 5
        println("$(i) is less than 5")
    elseif i < 10
        println("$(i) is less than 10")
    else
        if i == 10
            println("the value is 10")
        else
            println("$(i) is bigger than 10")
        end
    end
end

[i for i in 1:5]

[i * j for i in 1:5, j in 5:10]

[i for i in 1:10 if i % 2 == 1]

Dict("$i" => i for i in 1:10 if i % 2 == 1)

function print_hello()
    println("hello")
end
print_hello()

function print_it(x)
    println(x)
end
print_it("hello")
print_it(1.234)
print_it(:my_id)

function print_it(x; prefix = "value:")
    println("$(prefix) $x")
end
print_it(1.234)
print_it(1.234, prefix = "val:")

function mult(x; y = 2.0)
    return x * y
end
mult(4.0)

mult(4.0, y = 5.0)

[1, 5, -2, 7]

[1.0, 5.2, -2.1, 7]

function mutability_example(mutable_type::Vector{Int}, immutable_type::Int)
    mutable_type[1] += 1
    immutable_type += 1
    return
end

mutable_type = [1, 2, 3]
immutable_type = 1

mutability_example(mutable_type, immutable_type)

println("mutable_type: $(mutable_type)")
println("immutable_type: $(immutable_type)")

@show isimmutable([1, 2, 3])
@show isimmutable(1);

using Random
[rand() for i in 1:10]

# This file was generated using Literate.jl, https://github.com/fredrikekre/Literate.jl


#=
Created on Friday 26 July 2019
Last update: -

@author: Michiel Stock
michielfmstock@gmail.com

Standard matrix factorization algorithms applied on Kronecker systems.
=#

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

# CHOLESKY DECOMPOSITION
# ----------------------

import LinearAlgebra: Cholesky, cholesky

struct CholeskyKronecker{T<:Union{Cholesky,FactorizedKronecker},S<:Union{Cholesky,FactorizedKronecker}} <: FactorizedKronecker{Float64}
    A::T
    B::S
end

issquare(C::Cholesky) = true

function Base.getproperty(C::CholeskyKronecker, d::Symbol)
    if d in [:U, :L, :UL]
        return kronecker(getproperty(C.A, d), getproperty(C.B, d))
    elseif d in [:A, :B]
        return getfield(C, d)
    else
        throw(ArgumentError("Attribute :$d not supported (only :A, :B, :UL, :U or :L)"))
    end
end

function Base.show(io::IO, mime::MIME{Symbol("text/plain")}, C::CholeskyKronecker)
    summary(io, C); println(io)
    println(io, "U factor:")
    show(io, mime, getproperty(C, :U))
end

"""
    cholesky(K::AbstractKroneckerProduct; check = true)

Wrapper around `cholesky` from the `LinearAlgebra` package. Performs Cholesky
on the matrices of a `AbstractKroneckerProduct` instances and returns a
`CholeskyKronecker` type. Similar to `Cholesky`, `size`, `\\`, `inv`, `det`,
and `logdet` are overloaded to efficiently work with this type.
"""
function cholesky(K::AbstractKroneckerProduct; check = true)
    checksquare(K)
    A, B = getmatrices(K)
    return CholeskyKronecker(cholesky(A, check=check),
                            cholesky(B, check=check))
end


using KNITRO
using Base.Test
using Compat

 ## Solve a small QCQP (quadratically constrained quadratic programming)
 #  test problem.
 #
 #  min   1000 - x0^2 - 2 x1^2 - x2^2 - x0 x1 - x0 x2
 #  s.t.  8 x0 + 14 x1 + 7 x2 - 56 = 0
 #        x0^2 + x1^2 + x2^2 - 25 >= 0
 #        x0 >= 0, x1 >= 0, x2 >= 0
 #
 #  The start point (2, 2, 2) converges to the minimum at (0, 0, 8),
 #  with final objective = 936.0.  From a different start point,
 #  KNITRO may converge to an alternate local solution at (7, 0, 0),
 #  with objective = 951.0.
 ##

function eval_f(x::Vector{Float64})
    1000.0 - x[1]^2 - 2.0*x[2]^2 - x[3]^2 - x[1]^2 - x[1]*x[3]
end

function eval_g(x::Vector{Float64}, cons::Vector{Float64})
    cons[1] = 8.0*x[1] + 14.0*x[2] + 7.0*x[3] - 56.0
    cons[2] = x[1]^2 + x[2]^2 + x[3]^2 - 25.0
end

function eval_grad_f(x::Vector{Float64}, grad::Vector{Float64})
    grad[1] = -2.0*x[1] - x[2] - x[3]
    grad[2] = -4.0*x[2] - x[1]
    grad[3] = -2.0*x[3] - x[1]
end

function eval_jac_g(x::Vector{Float64}, jac::Vector{Float64})
    #---- GRADIENT OF THE FIRST CONSTRAINT, c[0].
    jac[1] =  8.0
    jac[2] = 14.0
    jac[3] =  7.0

    #---- GRADIENT OF THE SECOND CONSTRAINT, c[1].
    jac[4] = 2.0*x[1]
    jac[5] = 2.0*x[2]
    jac[6] = 2.0*x[3]
end

function eval_h(x::Vector{Float64}, lambda::Vector{Float64},
                sigma::Float64, hess::Vector{Float64})
    hess[1] = -2.0*sigma + 2.0*lambda[2]
    hess[2] = -1.0*sigma
    hess[3] = -1.0*sigma
    hess[4] = -4.0*sigma + 2.0*lambda[2]
    hess[5] = -2.0*sigma + 2.0*lambda[2]
end

function eval_hv(x::Vector{Float64}, lambda::Vector{Float64},
                 sigma::Float64, hv::Vector{Float64})
    hv1 = (-2.0*sigma + 2.0*lambda[2])*hv[1] - sigma*hv[2] - sigma*hv[3]
    hv2 = -sigma*hv[1] + (-4.0*sigma + 2.0*lambda[2])*hv[2]
    hv3 = -sigma*hv[1] + (-2.0*sigma + 2.0*lambda[2])*hv[3]
    hv[1] = hv1
    hv[2] = hv2
    hv[3] = hv3
end

objGoal = KTR_OBJGOAL_MINIMIZE
objType = KTR_OBJTYPE_QUADRATIC

n = 3
x_L = zeros(3)
x_U = fill(KTR_INFBOUND, 3)
x = [-2.0,1.0] # initial guess

m = 2
c_Type = [KTR_CONTYPE_LINEAR, KTR_CONTYPE_QUADRATIC]
c_L = zeros(2)
c_U = [0.0, KTR_INFBOUND]

jac_con = Int32[0,0,0,1,1,1]
jac_var = Int32[0,1,2,0,1,2]
hess_row = Int32[0,0,0,1,2]
hess_col = Int32[0,1,2,1,2]

x = [2.0,2.0,2.0] # initial guess

kp = createProblem()
@fact applicationReturnStatus(kp) --> :Uninitialized
setOption(kp, "outlev", "all")
setOption(kp, "hessopt", 1)
setOption(kp, "hessian_no_f", 1)
setOption(kp, "feastol", 1.0e-10)

@fact applicationReturnStatus(kp) --> :Uninitialized
@fact kp.eval_status --> @compat(Int32(0))

initializeProblem(kp, objGoal, objType, x_L, x_U, c_Type, c_L, c_U,
                  jac_var, jac_con, hess_row, hess_col; initial_x = x)
@fact applicationReturnStatus(kp) --> :Initialized
@fact kp.eval_status --> @compat(Int32(0))

#---- ALLOCATE ARRAYS FOR REVERSE COMMUNICATIONS OPERATION.
cons = Array(Float64, m)
objGrad = Array(Float64, n)
jac = Array(Float64, length(jac_con))
hess = Array(Float64, length(hess_row))
hessVector = Array(Float64, n)

#---- SOLVE THE PROBLEM.  IN REVERSE COMMUNICATIONS MODE, KNITRO
#---- RETURNS WHENEVER IT NEEDS MORE PROBLEM INFORMATION.  THE CALLING
#---- PROGRAM MUST INTERPRET KNITRO'S RETURN STATUS AND CONTINUE
#---- SUPPLYING PROBLEM INFORMATION UNTIL KNITRO IS COMPLETE.
while kp.status > 0
    @fact applicationReturnStatus(kp) --> anyof(:Initialized, :ReverseComms)
    solveProblem(kp, cons, objGrad, jac, hess, hessVector)
    if kp.status == KTR_RC_EVALFC
        #---- KNITRO WANTS obj AND c EVALUATED AT THE POINT x.
        kp.obj_val[1] = eval_f(kp.x)
        eval_g(kp.x,cons)
    elseif kp.status == KTR_RC_EVALGA
        #---- KNITRO WANTS objGrad AND jac EVALUATED AT THE POINT x.
        eval_grad_f(kp.x, objGrad)
        eval_jac_g(kp.x, jac)
    elseif kp.status == KTR_RC_EVALH
        #---- KNITRO WANTS hess EVALUATED AT THE POINT x.
        eval_h(kp.x, kp.lambda, 1.0, hess)
    elseif kp.status == KTR_RC_EVALH_NO_F
        #---- KNITRO WANTS hess EVALUATED AT THE POINT x
        #---- WITHOUT OBJECTIVE COMPONENT.
        eval_h(kp.x, kp.lambda, 0.0, hess)
    elseif kp.status == KTR_RC_EVALHV
        #---- KNITRO WANTS hessVector EVALUATED AT THE POINT x.
        eval_hv(kp.x, kp.lambda, 1.0, hessVector)
    elseif kp.status == KTR_RC_EVALHV_NO_F
        #---- KNITRO WANTS hessVector EVALUATED AT THE POINT x
        #---- WITHOUT OBJECTIVE COMPONENT.
        eval_hv(kp.x, kp.lambda, 0.0, hessVector)
    end
    #/*---- ASSUME THAT PROBLEM EVALUATION IS ALWAYS SUCCESSFUL.
    #*---- IF A FUNCTION OR ITS DERIVATIVE COULD NOT BE EVALUATED
    #*---- AT THE GIVEN (x, lambda), THEN SET evalStatus = 1 BEFORE
    #*---- CALLING KTR_solve AGAIN. */
    @fact applicationReturnStatus(kp) --> anyof(:ReverseComms, :Optimal)
    @fact kp.eval_status --> @compat(Int32(0))
end

# --- test optimal solutions ---
facts("Test optimal solutions") do
    @fact applicationReturnStatus(kp) --> :Optimal
    @fact kp.x --> roughly(
        [0.0, 0.0, 8.0], 1e-5)
    @fact kp.obj_val[1] --> roughly(936.0, 1e-5)
end


using JRAF
using Test
###########################################################################################################
###########################################################################################################
###########################################################################################################
##################################### SIMPLE TEST FOR DATA TYPES ##########################################
x170 = Divide(RF(2), RF(3));
x171 = Divide(3, 2);
x172 = Divide(CF(2,0), CF(3,0));
x173 = Divide(3+0im,2+0im);
x174 = Divide(RF(2), CF(3,0));
x175 = Divide(RF(3), 2);
x176 = Divide(RF(2), 3+0im);
x177 = Divide(CF(3,0), RF(2));
x178 = Divide(CF(2,0), 3);
x179 = Divide(CF(3,0), 2+0im);
x180 = Divide(2, RF(3));
x181 = Divide(3, CF(2,0));
x182 = Divide(2, 3+0im);
x183 = Divide(2+0im, RF(3));
x184 = Divide(3+0im, CF(2,0));
x185 = Divide(CF(3,0), 2);
x186 = Divide(CF(3,0), 2, RF(4), 5+0im);
x187 = Divide(RF(2));
x188 = Divide(2);
x189 = Divide(CF(2,0));
x190 = Divide(2+0im);
###########################################################################################################
x191 = Power(RF(2), RF(3));
x192 = Power(3, 2);
x193 = Power(CF(2,0), CF(3,0));
x194 = Power(3+0im,2+0im);
x195 = Power(RF(2), CF(3,0));
x196 = Power(RF(3), 2);
x197 = Power(RF(2), 3+0im);
x198 = Power(CF(3,0), RF(2));
x199 = Power(CF(2,0), 3);
x200 = Power(CF(3,0), 2+0im);
x201 = Power(2, RF(3));
x202 = Power(3, CF(2,0));
x203 = Power(2, 3+0im);
x204 = Power(2+0im, RF(3));
x205 = Power(3+0im, CF(2,0));
x206 = Power(CF(3,0), 2);
x207 = Power(CF(3,0), 2, RF(4), 5+0im);
###########################################################################################################
x208 = Sqrt(RF(3.5))
x209 = Sqrt(3.5)
x210 = Sqrt(CF(RF(2.3),RF(3)))
x211 = Sqrt(3.5+3im)
###########################################################################################################
x212 = RSqrt(RF(3.5))
x213 = RSqrt(3.5)
x214 = RSqrt(CF(RF(2.3),RF(3)))
x215 = RSqrt(3.5+3im)
###########################################################################################################
###########################################################################################################
@testset "simple test for data types" begin
	@test IntegerQ(2)==true														# 1
	@test IntegerQ(2.2)==false													# 2
	@test IntegerQ(RF(2))==true													# 3
	@test IntegerQ(RF(2.5))==false												# 4
	@test IntegerQ(CF(2,0))==true												# 5
	@test IntegerQ(CF(2,1))==false												# 6
	@test IntegerQ(2+0im)==true													# 7
	@test IntegerQ(2+2im)==false												# 8
	@test IntegerQ(2+2im,2,2.2,RF(2),CF(2,2))==(false,true,false,true,false)	# 9
    @test RealQ(CF(2,0))==true													# 10
	@test RealQ(CF(2,1))==false													# 11
	@test RealQ(2+0im)==true													# 12
	@test RealQ(2+2im)==false													# 13
    @test PositiveQ(CF(2,0))==true												# 14
	@test PositiveQ(CF(-2,0))==false											# 15
	@test PositiveQ(2)==true													# 16
	@test PositiveQ(-2)==false													# 17
	@test PositiveQ(RF(2))==true												# 18
	@test PositiveQ(RF(-2))==false												# 19
	@test PositiveQ(RF(0))==false												# 20
	@test PositiveQ(0)==false													# 21
    @test NonpositiveQ(CF(2,0))==false											# 22
	@test NonpositiveQ(CF(-2,0))==true											# 23
	@test NonpositiveQ(2)==false												# 24
	@test NonpositiveQ(-2)==true												# 25
	@test NonpositiveQ(RF(2))==false											# 26
	@test NonpositiveQ(RF(-2))==true											# 27
	@test NonpositiveQ(RF(0))==true												# 28
	@test NonpositiveQ(0)==true													# 29
    @test NegativeQ(CF(2,0))==false												# 30
	@test NegativeQ(CF(-2,0))==true												# 31
	@test NegativeQ(2)==false													# 32
	@test NegativeQ(-2)==true													# 33
	@test NegativeQ(RF(2))==false												# 34
	@test NegativeQ(RF(-2))==true												# 35
	@test NegativeQ(RF(0))==false												# 36
	@test NegativeQ(0)==false													# 37
    @test NonnegativeQ(CF(2,0))==true											# 38
	@test NonnegativeQ(CF(-2,0))==false											# 39
	@test NonnegativeQ(2)==true													# 40
	@test NonnegativeQ(-2)==false												# 41
	@test NonnegativeQ(RF(2))==true												# 42
	@test NonnegativeQ(RF(-2))==false											# 43
	@test NonnegativeQ(RF(0))==true												# 44
	@test NonnegativeQ(0)==true													# 45
    @test EqualQ(RF(2),RF(2))==true												# 46
	@test EqualQ(RF(2),RF(3))==false											# 47
	@test EqualQ(2, 2)==true													# 48
	@test EqualQ(2, 3)==false													# 49
	@test EqualQ(CF(2), CF(2))==true											# 50
	@test EqualQ(CF(2), CF(3))==false											# 51
	@test EqualQ(2+0im, 2+0im)==true											# 52
	@test EqualQ(2+0im, 3+1im)==false											# 53
	@test EqualQ(RF(2), 2)==true												# 54
	@test EqualQ(RF(2), 3)==false												# 55
	@test EqualQ(2, RF(2))==true												# 56
	@test EqualQ(2, RF(3))==false												# 57
	@test EqualQ(CF(2), 2+0im)==true											# 58
	@test EqualQ(CF(2), 2+1im)==false											# 59
	@test EqualQ(2+0im, CF(2))==true											# 60
	@test EqualQ(2+0im, CF(2,-1))==false										# 61
	@test EqualQ(RF(2), CF(2))==true											# 62
	@test EqualQ(RF(2), CF(2,1))==false											# 63
	@test EqualQ(CF(2), RF(2))==true											# 64
	@test EqualQ(CF(2), RF(3))==false											# 65
	@test EqualQ(RF(2), 2+0im)==true											# 66
	@test EqualQ(RF(2), 2+1im)==false											# 67
	@test EqualQ(2+0im, RF(2))==true											# 68
	@test EqualQ(2+0im, RF(3))==false											# 69
	@test EqualQ(CF(2), 2)==true												# 70
	@test EqualQ(CF(2), 3)==false												# 71
	@test EqualQ(2, CF(2))==true												# 72
	@test EqualQ(2, CF(2,2))==false												# 73
	@test EqualQ(2, 2+0im)==true												# 74
	@test EqualQ(2, 2+1im)==false												# 75
	@test EqualQ(2+0im, 2)==true												# 76
	@test EqualQ(2+0im, 3)==false												# 77
    @test Zero(RF)==RF(0)														# 78
	@test Zero(CF)==CF(0)														# 79
    @test Abs(RF(-5))==5														# 80
	@test Abs(-5)==5															# 81
	@test Abs(CF(-5,0))==5														# 82
	@test Abs(-5+0im)==5														# 83
	@test Abs(RF(-5),5,CF(-5,0),-5+0im)==(5,5,5,5)								# 84
    @test Min(RF(2), RF(3))==2													# 85
	@test Min(3, 2)==2															# 86
	@test Min(CF(2,0), CF(3,0))==2												# 87
	@test Min(3+0im,2+0im)==2													# 88
	@test Min(RF(2), CF(3,0))==2												# 89
	@test Min(RF(3), 2)==2														# 90
	@test Min(RF(2), 3+0im)==2													# 91
	@test Min(CF(3,0), RF(2))==2												# 92
	@test Min(CF(2,0), 3)==2													# 93
	@test Min(CF(3,0), 2+0im)==2												# 94
	@test Min(2, RF(3))==2														# 95
	@test Min(3, CF(2,0))==2													# 96
	@test Min(2, 3+0im)==2														# 97
	@test Min(2+0im, RF(3))==2													# 98
	@test Min(3+0im, CF(2,0))==2												# 99
	@test Min(CF(3,0), 2)==2													# 100
	@test Min(CF(3,0), 2, RF(4), 5+0im)==2										# 101
    @test Max(RF(2), RF(3))==3													# 102
	@test Max(3, 2)==3															# 103
	@test Max(CF(2,0), CF(3,0))==3												# 104
	@test Max(3+0im,2+0im)==3													# 105
	@test Max(RF(2), CF(3,0))==3												# 106
	@test Max(RF(3), 2)==3														# 107
	@test Max(RF(2), 3+0im)==3													# 108
	@test Max(CF(3,0), RF(2))==3												# 109
	@test Max(CF(2,0), 3)==3													# 110
	@test Max(CF(3,0), 2+0im)==3												# 111
	@test Max(2, RF(3))==3														# 112
	@test Max(3, CF(2,0))==3													# 113
	@test Max(2, 3+0im)==3														# 114
	@test Max(2+0im, RF(3))==3													# 115
	@test Max(3+0im, CF(2,0))==3												# 116
	@test Max(CF(3,0), 2)==3													# 117
	@test Max(CF(3,0), 2, RF(4), 5+0im)==5										# 118
    @test Plus(RF(2), RF(3))==5													# 119
	@test Plus(3, 2)==5															# 120
	@test Plus(CF(2,0), CF(3,0))==5												# 121
	@test Plus(3+0im,2+0im)==5													# 122
	@test Plus(RF(2), CF(3,0))==5												# 123
	@test Plus(RF(3), 2)==5														# 124
	@test Plus(RF(2), 3+0im)==5													# 125
	@test Plus(CF(3,0), RF(2))==5												# 126
	@test Plus(CF(2,0), 3)==5													# 127
	@test Plus(CF(3,0), 2+0im)==5												# 128
	@test Plus(2, RF(3))==5														# 129
	@test Plus(3, CF(2,0))==5													# 130
	@test Plus(2, 3+0im)==5														# 131
	@test Plus(2+0im, RF(3))==5													# 132
	@test Plus(3+0im, CF(2,0))==5												# 133
	@test Plus(CF(3,0), 2)==5													# 134
	@test Plus(CF(3,0), 2, RF(4), 5+0im)==14									# 135
    @test Subtract(RF(2), RF(3))==-1											# 136
	@test Subtract(3, 2)==1														# 137
	@test Subtract(CF(2,0), CF(3,0))==-1										# 138
	@test Subtract(3+0im,2+0im)==1												# 139
	@test Subtract(RF(2), CF(3,0))==-1											# 140
	@test Subtract(RF(3), 2)==1													# 141
	@test Subtract(RF(2), 3+0im)==-1											# 142
	@test Subtract(CF(3,0), RF(2))==1											# 143
	@test Subtract(CF(2,0), 3)==-1												# 144
	@test Subtract(CF(3,0), 2+0im)==1											# 145
	@test Subtract(2, RF(3))==-1												# 146
	@test Subtract(3, CF(2,0))==1												# 147
	@test Subtract(2, 3+0im)==-1												# 148
	@test Subtract(2+0im, RF(3))==-1											# 149
	@test Subtract(3+0im, CF(2,0))==1											# 150
	@test Subtract(CF(3,0), 2)==1												# 151
	@test Subtract(CF(3,0), 2, RF(4), 5+0im)==-8								# 152
    @test Times(RF(2), RF(3))==6												# 153
	@test Times(3, 2)==6														# 154
	@test Times(CF(2,0), CF(3,0))==6											# 155
	@test Times(3+0im,2+0im)==6													# 156
	@test Times(RF(2), CF(3,0))==6												# 157
	@test Times(RF(3), 2)==6													# 158
	@test Times(RF(2), 3+0im)==6												# 159
	@test Times(CF(3,0), RF(2))==6												# 160
	@test Times(CF(2,0), 3)==6													# 161
	@test Times(CF(3,0), 2+0im)==6												# 162
	@test Times(2, RF(3))==6													# 163
	@test Times(3, CF(2,0))==6													# 164
	@test Times(2, 3+0im)==6													# 165
	@test Times(2+0im, RF(3))==6												# 166
	@test Times(3+0im, CF(2,0))==6												# 167
	@test Times(CF(3,0), 2)==6													# 168
	@test Times(CF(3,0), 2, RF(4), 5+0im)==120									# 169
    @test NO(Divide(RF(2), RF(3))) == NO(x170)									# 170
	@test NO(Divide(3, 2)) == NO(x171)											# 171
	@test NO(Divide(CF(2,0), CF(3,0))) == NO(x172)								# 172
	@test NO(Divide(3+0im,2+0im)) == NO(x173)									# 173
	@test NO(Divide(RF(2), CF(3,0))) == NO(x174)								# 174
	@test NO(Divide(RF(3), 2)) == NO(x175)										# 175
	@test NO(Divide(RF(2), 3+0im)) == NO(x176)									# 176
	@test NO(Divide(CF(3,0), RF(2))) == NO(x177)								# 177
	@test NO(Divide(CF(2,0), 3)) == NO(x178)									# 178
	@test NO(Divide(CF(3,0), 2+0im)) == NO(x179)								# 179
	@test NO(Divide(2, RF(3))) == NO(x180)										# 180
	@test NO(Divide(3, CF(2,0))) == NO(x181)									# 181
	@test NO(Divide(2, 3+0im)) == NO(x182)										# 182
	@test NO(Divide(2+0im, RF(3))) == NO(x183)									# 183
	@test NO(Divide(3+0im, CF(2,0))) == NO(x184)								# 184
	@test NO(Divide(CF(3,0), 2)) == NO(x185)									# 185
	@test NO(Divide(CF(3,0), 2, RF(4), 5+0im)) == NO(x186)						# 186
	@test NO(Divide(RF(2))) == NO(x187)											# 187
	@test NO(Divide(2)) == NO(x188)												# 188
	@test NO(Divide(CF(2,0))) == NO(x189)										# 189
	@test NO(Divide(2+0im)) == NO(x190)											# 190

    @test NO(Power(RF(2), RF(3))) == NO(x191)									# 191
	@test NO(Power(3, 2)) == NO(x192)											# 192
	@test NO(Power(CF(2,0), CF(3,0))) == NO(x193)								# 193
	@test NO(Power(3+0im,2+0im)) == NO(x194)									# 194
	@test NO(Power(RF(2), CF(3,0))) == NO(x195)									# 195
	@test NO(Power(RF(3), 2)) == NO(x196)										# 196
	@test NO(Power(RF(2), 3+0im)) == NO(x197)									# 197
	@test NO(Power(CF(3,0), RF(2))) == NO(x198)									# 198
	@test NO(Power(CF(2,0), 3)) == NO(x199)										# 199
	@test NO(Power(CF(3,0), 2+0im)) == NO(x200)									# 200
	@test NO(Power(2, RF(3))) == NO(x201)										# 201
	@test NO(Power(3, CF(2,0))) == NO(x202)										# 202
	@test NO(Power(2, 3+0im)) == NO(x203)										# 203
	@test NO(Power(2+0im, RF(3))) == NO(x204)									# 204
	@test NO(Power(3+0im, CF(2,0))) == NO(x205)									# 205
	@test NO(Power(CF(3,0), 2)) == NO(x206)										# 206
	@test NO(Power(CF(3,0), 2, RF(4), 5+0im)) == NO(x207)						# 207

    @test NO(Sqrt(RF(3.5))) == NO(x208)											# 208
	@test NO(Sqrt(3.5)) == NO(x209)												# 209
	@test NO(Sqrt(CF(RF(2.3),RF(3)))) == NO(x210)								# 210
	@test NO(Sqrt(3.5+3im)) == NO(x211)											# 211

    @test NO(RSqrt(RF(3.5))) == NO(x212)										# 212
	@test NO(RSqrt(3.5)) == NO(x213)											# 213
	@test NO(RSqrt(CF(RF(2.3),RF(3)))) == NO(x214)								# 214
	@test NO(RSqrt(3.5+3im)) == NO(x215)										# 215

end
###########################################################################################################
###########################################################################################################
###########################################################################################################
########################################### TEST MATH.JL ##################################################
x216 = Factorial(RF(3.5));
x217 = Factorial(3.5);
x218 = Factorial(CF(RF(2.3),RF(3)));
x219 = Factorial(3.5+3im);
###########################################################################################################
x220 = Gamma(RF(3.5));
x221 = Gamma(3.5);
x222 = Gamma(CF(RF(2.3),RF(3)));
x223 = Gamma(3.5+3im);

###########################################################################################################
x224 = UGamma(RF(2), RF(3));
x225 = UGamma(3, 2);
x226 = UGamma(CF(2,0), CF(3,0));
x227 = UGamma(3+0im,2+0im);
x228 = UGamma(RF(2), CF(3,0));
x229 = UGamma(RF(3), 2);
x230 = UGamma(RF(2), 3+0im);
x231 = UGamma(CF(3,0), RF(2));
x232 = UGamma(CF(2,0), 3);
x233 = UGamma(CF(3,0), 2+0im);
x234 = UGamma(2, RF(3));
x235 = UGamma(3, CF(2,0));
x236 = UGamma(2, 3+0im);
x237 = UGamma(2+0im, RF(3));
x238 = UGamma(3+0im, CF(2,0));
x239 = UGamma(CF(3,0), 2);
x240 = UGamma(CF(3,0), 2, RF(4), 5+0im);

###########################################################################################################
x241 = LGamma(RF(2), RF(3));
x242 = LGamma(3, 2);
x243 = LGamma(CF(2,0), CF(3,0));
x244 = LGamma(3+0im,2+0im);
x245 = LGamma(RF(2), CF(3,0));
x246 = LGamma(RF(3), 2);
x247 = LGamma(RF(2), 3+0im);
x248 = LGamma(CF(3,0), RF(2));
x249 = LGamma(CF(2,0), 3);
x250 = LGamma(CF(3,0), 2+0im);
x251 = LGamma(2, RF(3));
x252 = LGamma(3, CF(2,0));
x253 = LGamma(2, 3+0im);
x254 = LGamma(2+0im, RF(3));
x255 = LGamma(3+0im, CF(2,0));
x256 = LGamma(CF(3,0), 2);
x257 = LGamma(CF(3,0), 2, RF(4), 5+0im);

###########################################################################################################
x258 = Pochhammer(RF(2), RF(3));
x259 = Pochhammer(3, 2);
x260 = Pochhammer(CF(2,0), CF(3,0));
x261 = Pochhammer(3+0im,2+0im);
x262 = Pochhammer(RF(2), CF(3,0));
x263 = Pochhammer(RF(3), 2);
x264 = Pochhammer(RF(2), 3+0im);
x265 = Pochhammer(CF(3,0), RF(2));
x266 = Pochhammer(CF(2,0), 3);
x267 = Pochhammer(CF(3,0), 2+0im);
x268 = Pochhammer(2, RF(3));
x269 = Pochhammer(3, CF(2,0));
x270 = Pochhammer(2, 3+0im);
x271 = Pochhammer(2+0im, RF(3));
x272 = Pochhammer(3+0im, CF(2,0));
x273 = Pochhammer(CF(3,0), 2);

###########################################################################################################
x274 = Beta(RF(3), RF(2), RF(2));
x275 = Beta(3, 2, 2);
x276 = Beta(CF(3,0), CF(2,0), CF(2,0));

###########################################################################################################
x277 = Binomial(RF(3), 2);
x278 = Binomial(RF(3), RF(2));
x279 = Binomial(3.0, 2);
x280 = Binomial(3.0, 2.0);

###########################################################################################################
x281 = GBinomial(3, 2, 3);
x282 = GBinomial(3.0, 2.0, 3.0);
x283 = GBinomial(RF(3), RF(2), RF(3));

###########################################################################################################
x284 = ExpIntegralEi(RF(3));
x285 = ExpIntegralEi(3.0);
x286 = ExpIntegralEi(CF(3,0));
x287 = ExpIntegralEi(3.0 + 0im);

###########################################################################################################
x288 = ExpIntegralE(RF(2), RF(3));
x289 = ExpIntegralE(3, 2);
x290 = ExpIntegralE(CF(2,0), CF(3,0));
x291 = ExpIntegralE(3+0im,2+0im);
x292 = ExpIntegralE(RF(2), CF(3,0));
x293 = ExpIntegralE(RF(3), 2);
x294 = ExpIntegralE(RF(2), 3+0im);
x295 = ExpIntegralE(CF(3,0), RF(2));
x296 = ExpIntegralE(CF(2,0), 3);
x297 = ExpIntegralE(CF(3,0), 2+0im);
x298 = ExpIntegralE(2, RF(3));
x299 = ExpIntegralE(3, CF(2,0));
x300 = ExpIntegralE(2, 3+0im);
x301 = ExpIntegralE(2+0im, RF(3));
x302 = ExpIntegralE(3+0im, CF(2,0));
x303 = ExpIntegralE(CF(3,0), 2);

###########################################################################################################
###########################################################################################################
@testset "math.jl" begin

    @test NO(Factorial(RF(3.5))) == NO(x216)									# 216
    @test NO(Factorial(3.5)) == NO(x217)										# 217
    @test NO(Factorial(CF(RF(2.3),RF(3)))) == NO(x218)							# 218
    @test NO(Factorial(3.5+3im)) == NO(x219)                                    # 219
	@test NO(Factorial(6)) == NO((1//(6+1))*Factorial(7))						#

    @test NO(Gamma(RF(3.5))) == NO(x220)									    # 220
    @test NO(Gamma(3.5)) == NO(x221)										    # 221
    @test NO(Gamma(CF(RF(2.3),RF(3)))) == NO(x222)							    # 222
    @test NO(Gamma(3.5+3im)) == NO(x223)                                        # 223
	@test Float64(NO(Gamma(3.5))) == Float64(NO((Gamma(3.5 + 1)) // (3.5)))		#

    @test NO(UGamma(RF(2), RF(3))) == NO(x224)									# 224
	@test NO(UGamma(3, 2)) == NO(x225)											# 225
	@test NO(UGamma(CF(2,0), CF(3,0))) == NO(x226)								# 226
	@test NO(UGamma(3+0im,2+0im)) == NO(x227)									# 227
	@test NO(UGamma(RF(2), CF(3,0))) == NO(x228)								# 228
	@test NO(UGamma(RF(3), 2)) == NO(x229)										# 229
	@test NO(UGamma(RF(2), 3+0im)) == NO(x230)									# 230
	@test NO(UGamma(CF(3,0), RF(2))) == NO(x231)								# 231
	@test NO(UGamma(CF(2,0), 3)) == NO(x232)									# 232
	@test NO(UGamma(CF(3,0), 2+0im)) == NO(x233)								# 233
	@test NO(UGamma(2, RF(3))) == NO(x234)										# 234
	@test NO(UGamma(3, CF(2,0))) == NO(x235)									# 235
	@test NO(UGamma(2, 3+0im)) == NO(x236)										# 236
	@test NO(UGamma(2+0im, RF(3))) == NO(x237)									# 237
	@test NO(UGamma(3+0im, CF(2,0))) == NO(x238)								# 238
	@test NO(UGamma(CF(3,0), 2)) == NO(x239)									# 239
	@test NO(UGamma(CF(3,0), 2, RF(4), 5+0im)) == NO(x240)						# 240
    @test NO(LGamma(RF(2), RF(3))) == NO(x241)									# 241
	@test NO(LGamma(3, 2)) == NO(x242)											# 242
	@test NO(LGamma(CF(2,0), CF(3,0))) == NO(x243)								# 243
	@test NO(LGamma(3+0im,2+0im)) == NO(x244)									# 244
	@test NO(LGamma(RF(2), CF(3,0))) == NO(x245)								# 245
	@test NO(LGamma(RF(3), 2)) == NO(x246)										# 246
	@test NO(LGamma(RF(2), 3+0im)) == NO(x247)									# 247
	@test NO(LGamma(CF(3,0), RF(2))) == NO(x248)								# 248
	@test NO(LGamma(CF(2,0), 3)) == NO(x249)									# 249
	@test NO(LGamma(CF(3,0), 2+0im)) == NO(x250)								# 250
	@test NO(LGamma(2, RF(3))) == NO(x251)										# 251
	@test NO(LGamma(3, CF(2,0))) == NO(x252)									# 252
	@test NO(LGamma(2, 3+0im)) == NO(x253)										# 253
	@test NO(LGamma(2+0im, RF(3))) == NO(x254)									# 254
	@test NO(LGamma(3+0im, CF(2,0))) == NO(x255)								# 255
	@test NO(LGamma(CF(3,0), 2)) == NO(x256)									# 256
	@test NO(LGamma(CF(3,0), 2, RF(4), 5+0im)) == NO(x257)						# 257
	@test Float64(NO(UGamma(3,2) + LGamma(3,2))) == Float64(NO(Gamma(3)))		#

    @test NO(Pochhammer(RF(2), RF(3))) == NO(x258)								# 258
	@test NO(Pochhammer(3, 2)) == NO(x259)										# 259
	@test NO(Pochhammer(CF(2,0), CF(3,0))) == NO(x260)							# 260
	@test NO(Pochhammer(3+0im,2+0im)) == NO(x261)								# 261
	@test NO(Pochhammer(RF(2), CF(3,0))) == NO(x262)							# 262
	@test NO(Pochhammer(RF(3), 2)) == NO(x263)									# 263
	@test NO(Pochhammer(RF(2), 3+0im)) == NO(x264)								# 264
	@test NO(Pochhammer(CF(3,0), RF(2))) == NO(x265)							# 265
	@test NO(Pochhammer(CF(2,0), 3)) == NO(x266)								# 266
	@test NO(Pochhammer(CF(3,0), 2+0im)) == NO(x267)							# 267
	@test NO(Pochhammer(2, RF(3))) == NO(x268)									# 268
	@test NO(Pochhammer(3, CF(2,0))) == NO(x269)								# 269
	@test NO(Pochhammer(2, 3+0im)) == NO(x270)									# 270
	@test NO(Pochhammer(2+0im, RF(3))) == NO(x271)								# 271
	@test NO(Pochhammer(3+0im, CF(2,0))) == NO(x272)							# 272
	@test NO(Pochhammer(CF(3,0), 2)) == NO(x273)								# 273
	@test Float64(NO(Pochhammer(3,2))) == Float64(NO(Gamma(3+2) // Gamma(3)))	#

	@test NO(Beta(RF(3), RF(2), RF(2))) == NO(x274)								# 274
	@test NO(Beta(3, 2, 2)) == NO(x275)											# 275
	@test NO(Beta(CF(3,0), CF(2,0), CF(2,0))) == NO(x276)						# 276
	@test Float64(NO(Beta(3, 2, 2))) == Float64(								#
		NO(																		#
			Beta(4, 2, 2) + Beta(3, 3, 2)										#
		)																		#
	)																			#

	@test NO(Binomial(RF(3), 2)) == NO(x277)									# 277
	@test NO(Binomial(RF(3), RF(2))) == NO(x278)								# 278
	@test NO(Binomial(3.0, 2)) == NO(x279)										# 279
	@test NO(Binomial(3.0, 2.0)) == NO(x280)									# 280
	@test Float64(NO(Binomial(7,2))) == Float64(								#
		NO(																		#
			Pochhammer(6,2)// Factorial(2)										#
			)																	#
		)																		#

	@test NO(GBinomial(3, 2, 3)) == NO(x281)									# 281
	@test NO(GBinomial(3.0, 2.0, 3.0)) == NO(x282)								# 282
	@test NO(GBinomial(RF(3), RF(2), RF(3))) == NO(x283)						# 283
	@test Float64(NO(GBinomial(5,10,6))) == Float64(36.0)

	@test NO(ExpIntegralEi(RF(3))) == NO(x284)									# 284
	@test NO(ExpIntegralEi(3.0)) == NO(x285)									# 285
	@test NO(ExpIntegralEi(CF(3,0))) == NO(x286)								# 286
	@test NO(ExpIntegralEi(3.0 + 0im)) == NO(x287)								# 287
	@test Float64(NO(ExpIntegralEi(1))) == Float64(1.8951178163559368)			#

	@test NO(ExpIntegralE(RF(2), RF(3))) == NO(x288)							# 288
	@test NO(ExpIntegralE(3, 2)) == NO(x289)									# 289
	@test NO(ExpIntegralE(CF(2,0), CF(3,0))) == NO(x290)						# 290
	@test NO(ExpIntegralE(3+0im,2+0im)) == NO(x291)								# 291
	@test NO(ExpIntegralE(RF(2), CF(3,0))) == NO(x292)							# 292
	@test NO(ExpIntegralE(RF(3), 2)) == NO(x293)								# 293
	@test NO(ExpIntegralE(RF(2), 3+0im)) == NO(x294)							# 294
	@test NO(ExpIntegralE(CF(3,0), RF(2))) == NO(x295)							# 295
	@test NO(ExpIntegralE(CF(2,0), 3)) == NO(x296)								# 296
	@test NO(ExpIntegralE(CF(3,0), 2+0im)) == NO(x297)							# 297
	@test NO(ExpIntegralE(2, RF(3))) == NO(x298)								# 298
	@test NO(ExpIntegralE(3, CF(2,0))) == NO(x299)								# 299
	@test NO(ExpIntegralE(2, 3+0im)) == NO(x300)								# 300
	@test NO(ExpIntegralE(2+0im, RF(3))) == NO(x301)							# 301
	@test NO(ExpIntegralE(3+0im, CF(2,0))) == NO(x302)							# 302
	@test NO(ExpIntegralE(CF(3,0), 2)) == NO(x303)								# 303
	@test Float64(NO(3 * ExpIntegralE(3+1,2))) == Float64(						#
		NO(																		#
			Exp(-2) - 2 * ExpIntegralE(3,2)										#
		)																		#
		)																		#
end
###########################################################################################################
###########################################################################################################
###########################################################################################################
################################## TEST ANGULAR_COEFFICIENTS.JL ###########################################
smalljlistg = 0:1:2
largejlistg = 0:1:1000
smalljlist = 0:1//2:10
largejlist = 0:1//2:1000

@time @testset "Guseinov's formulae for Clebsch-Gordan coefficients" begin
    for j1 in smalljlistg, j2 in smalljlistg
        d1::Int = 2*j1+1
        d2::Int = 2*j2+1
        M = zeros(Float64, (d1*d2, d1*d2))
        ind1 = 1
        for m1 in -j1:j1, m2 in -j2:j2
            ind2 = 1
            @inbounds for j3 in abs(j1-j2):(j1+j2), m3 in -j3:j3
                M[ind1,ind2] = ClebschGordanG(j1,m1,j2,m2,j3,m3)
                ind2 += 1
            end
            ind1 += 1
        end
        @test M'*M ≈ one(M)
    end
end

@time @testset "Guseinov's formulae for Gaunt coefficients" begin
	for j1 in smalljlistg, j2 in smalljlistg, j3 in abs(j1-j2):(j1+j2)
		for m1 in -j1:j1, m2 in -j2:j2
			g1 = Float64(
				NO(
					GauntG(j1, m1, j2, m2, j3))
					)

			g2 = Float64(
						NO(
							GauntG(j2, m2, j1, m1, j3)
							)
						)

			isnan(g1) ? g1l = 0 : g1l = g1
			isnan(g2) ? g2l = 0 : g2l = g2

			@test g1l == g2l
		end
	end
end
###########################################################################################################
###########################################################################################################
###########################################################################################################
##################################### TEST SPECİAL_FUNCTIONS.JL ###########################################
lpvar = 0.5
@time @testset "Associated Legendre poloynomials" begin
	for l in smalljlistg, m in l
		lp1 = Float64(NO(LegendreP(l,m,lpvar,0)))
		lp2 = Float64(Plm(l, m, lpvar))
		@test lp1 == lp2
	end
end

@time @testset "Normalized associated Legendre poloynomials" begin
	for l in smalljlistg, m in 0:l
		lp1 = Float32(NO(NLegendreP(l,m,lpvar,0)))
		lp2 = Float32(NO(Nlm(l, m) * Plm(l, m, lpvar) * Sqrt(2*Pi(RF))))
		lp3 = Float32(NO(LegendrePG(l,m,CF(lpvar))))
		@test lp1 == lp2
		@test lp1 == lp3
	end
end
###########################################################################################################
###########################################################################################################
Y1 = computeYlm(pi/2, 0, lmax = smalljlistg[length(smalljlistg)]);
Y2 = computeYlm(
	pi/3, pi/3,
	lmax = smalljlistg[length(smalljlistg)],
	SHType = SphericalHarmonics.RealHarmonics()
	);

#SphericalHarmonics.sphericalharmonic(
#	π/3, π/3, l = 500, m = 251,
#	SHType = SphericalHarmonics.RealHarmonics()
#	)

@time @testset "Complex spherical harmonics" begin
	for l in 0:1:2, m in l
		sph1 = Real(Float32(NO(SphericalHarmonicsY(l,m,Pi(RF)//2,RF(0)))))
		sph2 = Real(Float32(Y1[(l,m)]))
		sph3 = Real(Float32(NO(((-1)^m) * SphericalHarmonicsYG(l,m,Pi(RF)//2,RF(0)))))
		@test sph1 == sph2
		@test sph1 == sph3
	end
end

@time @testset "Real spherical harmonics" begin
	for l in 0:1:2, m in l
		sph1 = ((-1)^m) * Float32(NO(real(SphericalHarmonicsSG(l,m,Pi(RF)//3,Pi(RF)//3)+CF(0,0))))
		sph2 = Float32(Y2[(l,m)])
		sph3 = Float32(NO(real(SphericalHarmonicsS(l,m,Pi(RF)//3,Pi(RF)//3)+CF(0,0))))
		@test sph1 == sph2
		@test sph1 == sph3
	end
end

@time @testset "Spherical Spinors" begin
	β = -1
	for κ in -3:2:3, m in -abs(κ) + 1/2:abs(κ) - 1/2
		spinor1 = SphericalSpinors(β, κ, RF(m), Pi(RF)//3, Pi(RF)//3)
		spinor2 = RadoslawSSpinors(κ, RF(m), Pi(RF)//3, Pi(RF)//3)

		spinor11 = Float64(NO(real(spinor1[1]+CF(0,0))))
		spinor12 = Float64(NO(real(spinor1[2]+CF(0,0))))

		spinor21 = Float64(NO(real(spinor2[1]+CF(0,0))))
		spinor22 = Float64(NO(real(spinor2[2]+CF(0,0))))

		@test spinor11 == spinor21
		@test spinor12 == spinor22

	end
end
###########################################################################################################
###########################################################################################################
Degree = Divide(Pi(RF),180)
x304 = real(Rotad(1,2,2,2,-1,20Degree,45Degree)) #-0.22725973883602185			# x304
x305 = real(Rotad(1,2,1,2,-1,20Degree,45Degree)) #0.14809906636301193			# x305
x306 = real(Rotad(1,2,-2,2,-1,20Degree,45Degree)) #-0.17409106008000474			# x306
x307 = real(Rotad(1,2,-2,2,-2,20Degree,45Degree)) #0.10329397779163371			# x307
x308 = real(Rotad(2,2,-2,2,-2,20Degree,45Degree)) #0.8864431717217084			# x308

@time @testset "Rotated angular functions" begin
	@test Float64(NO(real(Rotad(1,2,2,2,-1,20Degree,45Degree)))) == Float64(NO(x304))
	@test Float64(NO(real(Rotad(1,2,1,2,-1,20Degree,45Degree)))) == Float64(NO(x305))
	@test Float64(NO(real(Rotad(1,2,-2,2,-1,20Degree,45Degree)))) == Float64(NO(x306))
	@test Float64(NO(real(Rotad(1,2,-2,2,-2,20Degree,45Degree)))) == Float64(NO(x307))
	@test Float64(NO(real(Rotad(2,2,-2,2,-2,20Degree,45Degree)))) == Float64(NO(x308))
end
###########################################################################################################
###########################################################################################################
# N = n1 + n2 - 1
# Abs(l1 - l2) <= L <= l1 + l2
# -L <= M <= L
x309 = ChargeDOneC(RF(1),0,0,RF(5/10),RF(1),0,0,RF(5/10),RF(1),0,0) # 0.25						# x309
x310 = ChargeDOneC(RF(2),0,0,RF(5/10),RF(1),0,0,RF(5/10),RF(2),0,0) # 0.125						# x310
x311 = ChargeDOneC(RF(2),1,0,RF(5/10),RF(1),0,0,RF(5/10),RF(2),1,0) # 0.125						# x311
x312 = ChargeDOneC(RF(2),1,-1,RF(5/10),RF(1),1,0,RF(5/10),RF(2),2,-1) # 0.09682458365518543		# x312
x313 = ChargeDOneC(RF(2),1,-1,RF(5/10),RF(3),2,-1,RF(5/10),RF(4),1,0) # 0.05229125165837972		# x313
@time @testset "Charge density distribution" begin

@test Float64(NO(ChargeDOneC(RF(1),0,0,RF(5/10),RF(1),0,0,RF(5/10),RF(1),0,0))) == Float64(NO(x309))
@test Float64(NO(ChargeDOneC(RF(2),0,0,RF(5/10),RF(1),0,0,RF(5/10),RF(2),0,0))) == Float64(NO(x310))
@test Float64(NO(ChargeDOneC(RF(2),1,0,RF(5/10),RF(1),0,0,RF(5/10),RF(2),1,0))) == Float64(NO(x311))
@test Float64(NO(ChargeDOneC(RF(2),1,-1,RF(5/10),RF(1),1,0,RF(5/10),RF(2),2,-1))) == Float64(NO(x312))
@test Float64(NO(ChargeDOneC(RF(2),1,-1,RF(5/10),RF(3),2,-1,RF(5/10),RF(4),1,0))) == Float64(NO(x313))
end
###########################################################################################################
###########################################################################################################
x314 = OneCenterP(2,RF(2),RF(2),RF(5//10),RF(5//10),RF(20//10)) # 0.1693388246834792			# 314
x315 = OneCenterP(30,RF(20),RF(20),RF(5//10),RF(5//10),RF(20//10)) # 9.550586421859595e-34		# 315
x316 = OneCenterP(30,RF(200),RF(200),RF(5//10),RF(5//10),RF(20//10)) # 1.5356143951896117e-71	# 316
x317 = OneCenterP(100,RF(200),RF(200),RF(5//10),RF(5//10),RF(20//10)) # 4.0392168871657834e-227	# 317
x318 = OneCenterP(120,RF(200),RF(200),RF(5//10),RF(5//10),RF(20//10)) # 2.486848412057007e-270	# 318

@time @testset "One center potential" begin
	@test Float64(NO(OneCenterP(2,RF(2),RF(2),RF(5//10),RF(5//10),RF(20//10)))) == Float64(NO(x314))
	@test Float64(NO(OneCenterP(30,RF(20),RF(20),RF(5//10),RF(5//10),RF(20//10)))) == Float64(NO(x315))
	@test Float64(NO(OneCenterP(30,RF(200),RF(200),RF(5//10),RF(5//10),RF(20//10)))) == Float64(NO(x316))
	@test Float64(NO(OneCenterP(100,RF(200),RF(200),RF(5//10),RF(5//10),RF(20//10)))) == Float64(NO(x317))
	@test Float64(NO(OneCenterP(120,RF(200),RF(200),RF(5//10),RF(5//10),RF(20//10)))) == Float64(NO(x318))
end
###########################################################################################################
###########################################################################################################
x319 = Hypergeometric0F1(RF(1),RF(15//10),sprec) # 3.1655890675997798							# 319
x320 = Hypergeometric1F1(RF(1),RF(2),RF(30//10),sprec) # 6.361845641062556						# 320
x321 = HypergeometricU(RF(3),RF(2),RF(10//10)) # 0.10547895651520889							# 321
x322 = Hypergeometric2F1(CF(2),CF(3),CF(4),CF(5),sprec) # 0.15654212933375475 + 0.15079644737231007im

@time @testset "Herpergeometric functions" begin
	@test Float64(NO(Hypergeometric0F1(RF(1),RF(15//10),sprec))) == Float64(NO(x319))
	@test Float64(NO(Hypergeometric1F1(RF(1),RF(2),RF(30//10),sprec))) == Float64(NO(x320))
	@test Float64(NO(HypergeometricU(RF(3),RF(2),RF(10//10)))) == Float64(NO(x321))
	@test ComplexF64(NO(Hypergeometric2F1(CF(2),CF(3),CF(4),CF(5),sprec))) == ComplexF64(NO(x322))
end
###########################################################################################################
###########################################################################################################
x323 = AuxiliaryGk(RF(11//10),2,RF(21//10),RF(31//10),RF(41//10),RF(51//10)) # 0.2217712191193439	   x323
x324 = AuxiliaryGl(RF(11//10),2,RF(21//10),RF(31//10),RF(41//10),RF(51//10),15) # 0.010798691551480687 x324
x325 = AuxiliaryGh(RF(11//10),2,RF(21//10),RF(31//10),RF(41//10),RF(51//10),15) # 0.32834600685043897  x325
x326 = AuxiliaryG(RF(11//10),RF(21//10),RF(31//10),RF(41//10),RF(51//10),15) # 0.0277800747689916	   x326

@time @testset "Molecular sub-auxiliary functions" begin
	@test Float64(NO(
		AuxiliaryGk(RF(11//10),2,RF(21//10),RF(31//10),RF(41//10),RF(51//10)))) == Float64(NO(x323))
	@test Float64(NO(
		AuxiliaryGl(RF(11//10),2,RF(21//10),RF(31//10),RF(41//10),RF(51//10),15))) == Float64(NO(x324))
	@test Float64(NO(
		AuxiliaryGh(RF(11//10),2,RF(21//10),RF(31//10),RF(41//10),RF(51//10),15))) == Float64(NO(x325))
	@test Float64(NO(
		AuxiliaryG(RF(11//10),RF(21//10),RF(31//10),RF(41//10),RF(51//10),15))) == Float64(NO(x326))
end
###########################################################################################################
###########################################################################################################
###########################################################################################################
########################################### GAUX_P12_BSREP.JL #############################################
x327 = AuxiliaryG(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),5)# 1.3000782259762316e10  x327
x328 = AuxiliaryG(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),10)# -2.6937350037251043e9 x328
x329 = AuxiliaryG(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),15)# 6.907914778033908e6   x329
x330 = AuxiliaryG(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),20)# -88.57951040041706    x330
x331 = AuxiliaryG(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),25)# 5.1376884195114805	   x331
x332 = AuxiliaryG(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),30)# 5.137688371072059	   x332
x333 = AuxiliaryG(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),35)# 5.13768837107201	   x333
x334 = AuxiliaryG(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),40)#5.13768837107201	   x334
x335 = AuxiliaryG(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),45)#5.13768837107201	   x335
x336 = AuxiliaryG(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),50)#5.13768837107201	   x336

@time @testset "Molecular auxiliary functions gaux_p12_bsrep" begin
	@test Float64(NO(
		AuxiliaryG(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),5))) == Float64(NO(x327))
	@test Float64(NO(
		AuxiliaryG(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),10))) == Float64(NO(x328))
	@test Float64(NO(
		AuxiliaryG(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),15))) == Float64(NO(x329))
	@test Float64(NO(
		AuxiliaryG(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),20))) == Float64(NO(x330))
	@test Float64(NO(
		AuxiliaryG(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),25))) == Float64(NO(x331))
	@test Float64(NO(
		AuxiliaryG(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),30))) == Float64(NO(x332))
	@test Float64(NO(
		AuxiliaryG(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),35))) == Float64(NO(x333))
	@test Float64(NO(
		AuxiliaryG(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),40))) == Float64(NO(x334))
	@test Float64(NO(
		AuxiliaryG(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),45))) == Float64(NO(x335))
	@test Float64(NO(
		AuxiliaryG(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),50))) == Float64(NO(x336))
end
###########################################################################################################
###########################################################################################################
###########################################################################################################
########################################### GAUX_P123_BSREP.JL ############################################
x337 = AuxiliaryG(
	RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),10)# 942.2219506111262		   x337
x338 = AuxiliaryG(
	RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),15)# 968.7227043035291		   x338
x339 = AuxiliaryG(
	RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),20)# 969.1689349634697		   x339
x340 = AuxiliaryG(
	RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),25)# 969.169585087983		   x340
x341 = AuxiliaryG(
	RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),30)# 969.169586170002		   x341
x342 = AuxiliaryG(
	RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),35)# 969.1695861701844		   x342
x343 = AuxiliaryG(
	RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),40)# 969.1695861701844		   x343
x344 = AuxiliaryG(
	RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),45)# 969.1695861701844		   x344
x345 = AuxiliaryG(
	RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),50)# 969.1695862			   x345

@time @testset "Molecular auxiliary functions gaux_p123_bsrep" begin
	@test Float64(NO(
		AuxiliaryG(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),10))
		) == Float64(NO(x337))
	@test Float64(NO(
		AuxiliaryG(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),15))
		) == Float64(NO(x338))
	@test Float64(NO(
		AuxiliaryG(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),20))
		) == Float64(NO(x339))
	@test Float64(NO(
		AuxiliaryG(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),25))
		) == Float64(NO(x340))
	@test Float64(NO(
		AuxiliaryG(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),30))
		) == Float64(NO(x341))
	@test Float64(NO(
		AuxiliaryG(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),35))
		) == Float64(NO(x342))
	@test Float64(NO(
		AuxiliaryG(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),40))
		) == Float64(NO(x343))
	@test Float64(NO(
		AuxiliaryG(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),45))
		) == Float64(NO(x344))
	@test Float64(NO(
		AuxiliaryG(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),50))
		) == Float64(NO(x345))
end
###########################################################################################################
###########################################################################################################
###########################################################################################################
############################################ GAUX_P123_REC.JL #############################################
x346 = AuxiliaryGr(
	RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),10)# -2.1446355854005942e36   x346
x347 = AuxiliaryGr(
	RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),20)# -2.793929399000712e23	   x347
x348 = AuxiliaryGr(
	RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),30)# 5.959231185626135e13	   x348
x349 = AuxiliaryGr(
	RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),40)# 845216.2070820695		   x349
x350 = AuxiliaryGr(
	RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),50)# 969.1706829098309		   x350
x351 = AuxiliaryGr(
	RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),60)# 969.1695861708716		   x351
x352 = AuxiliaryGr(
	RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),70)# 969.1695861701845		   x352
x353 = AuxiliaryGr(
	RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),80)# 969.1695861701844		   x353
x354 = AuxiliaryGr(
	RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),90)# 969.1695861701844		   x354

@time @testset "Molecular auxiliary functions gaux_p123_rec" begin
	@test Float64(NO(
		AuxiliaryGr(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),10))
		) == Float64(NO(x346))
	@test Float64(NO(
		AuxiliaryGr(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),20))
		) == Float64(NO(x347))
	@test Float64(NO(
		AuxiliaryGr(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),30))
		) == Float64(NO(x348))
	@test Float64(NO(
		AuxiliaryGr(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),40))
		) == Float64(NO(x349))
	@test Float64(NO(
		AuxiliaryGr(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),50))
		) == Float64(NO(x350))
	@test Float64(NO(
		AuxiliaryGr(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),60))
		) == Float64(NO(x351))
	@test Float64(NO(
		AuxiliaryGr(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),70))
		) == Float64(NO(x352))
	@test Float64(NO(
		AuxiliaryGr(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),80))
		) == Float64(NO(x353))
	@test Float64(NO(
		AuxiliaryGr(RF(11//10),10,RF(21//10),RF(31//10),RF(41//10),RF(51//10),RF(61//10),90))
		) == Float64(NO(x354))
end
###########################################################################################################
###########################################################################################################
###########################################################################################################
####################################### STO_MOL_INTEG_ONE_ELECT.JL ########################################
x355 = TwoCenterOverlap(
	RF(63//10),5,4,
	RF(11//2),4,4,RF(150//10),
	RF(1//10),2*Pi(RF)//3,4*Pi(RF)//3,1,15) + CF(0,1) # 0.01850589548808461							x355
x356 = TwoCenterOverlap(
	RF(36//10),2,1,
	RF(200000000000000000000001//100000000000000000000000),1,1,
	RF(8//10),RF(3//10),2*Pi(RF)//5,Pi(RF),1,15) + CF(0,1) # 0.06496217354752934					x356
x357 = TwoCenterOverlap(
	RF(300000000000000000000001//100000000000000000000000),2,1,
	RF(300000000000000000000001//100000000000000000000000),2,1,
	RF(250//10),RF(6//10),0Degree,0Degree,1,20) + CF(0,1) # -0.0004435580316765528					x357

@time @testset "Molecular integrals over Slater-type orbitals. Overlap integrals" begin
	@test Float64(real(NO(TwoCenterOverlap(
		RF(63//10),5,4,
		RF(11//2),4,4,
		RF(150//10),RF(1//10),2*Pi(RF)//3,4*Pi(RF)//3,1,15) + CF(0,1) ))
		) == Float64(real(NO(x355)))
	@test Float64(real(NO(TwoCenterOverlap(
		RF(36//10),2,1,
		RF(200000000000000000000001//100000000000000000000000),1,1,
		RF(8//10),RF(3//10),2*Pi(RF)//5,Pi(RF),1,15) + CF(0,1) ))
		) == Float64(real(NO(x356)))
	@test Float64(real(NO(TwoCenterOverlap(
		RF(300000000000000000000001//100000000000000000000000),2,1,
		RF(300000000000000000000001//100000000000000000000000),2,1,
		RF(250//10),RF(6//10),0Degree,0Degree,1,20) + CF(0,1) ))
		) == Float64(real(NO(x357)))
end
###########################################################################################################
###########################################################################################################
x358 = TwoCenterNucAttractAAB(
	RF(30000000000000000000000000001//10000000000000000000000000000),2,2,RF(124//10),
	RF(30000000000000000000000000001//10000000000000000000000000000),2,2,RF(106//10),
	RF(61//10),0Degree,0Degree,15) # 0.16031672157866095											x358
x359 = TwoCenterNucAttractAAB(
	RF(40000000000000000000000000001//10000000000000000000000000000),3,2,RF(159//10),
	RF(50000000000000000000000000001//10000000000000000000000000000),3,3,RF(107//10),
	RF(155//10),40Degree,30Degree,15) # -4.653856766826447e-6										x359
x360 = TwoCenterNucAttractAAB(
	RF(80000000000000000000000000001//10000000000000000000000000000),7,7,RF(214//10),
	RF(70000000000000000000000000001//10000000000000000000000000000),6,6,RF(208//10),
	RF(532//10),70Degree,210Degree,15) # 5.16357446188277e-5										x360
x361 = TwoCenterNucAttractAAB(
	RF(100000000000000000000000000001//10000000000000000000000000000),9,-7,RF(125//10),
	RF(100000000000000000000000000001//10000000000000000000000000000),8,-8,RF(102//10),
	RF(1007//10),80Degree,240Degree,15) # -1.5861518962960975e-6									x361

@time @testset "Molecular integrals over Slater-type orbitals. Nuclear attraction AAB integrals" begin
	@test Float64(NO(TwoCenterNucAttractAAB(
		RF(30000000000000000000000000001//10000000000000000000000000000),2,2,RF(124//10),
		RF(30000000000000000000000000001//10000000000000000000000000000),2,2,RF(106//10),
		RF(61//10),0Degree,0Degree,15))) == Float64(NO(x358))
	@test Float64(NO(TwoCenterNucAttractAAB(
		RF(40000000000000000000000000001//10000000000000000000000000000),3,2,RF(159//10),
		RF(50000000000000000000000000001//10000000000000000000000000000),3,3,RF(107//10),
		RF(155//10),40Degree,30Degree,15))) == Float64(NO(x359))
	@test Float64(NO(TwoCenterNucAttractAAB(
		RF(80000000000000000000000000001//10000000000000000000000000000),7,7,RF(214//10),
		RF(70000000000000000000000000001//10000000000000000000000000000),6,6,RF(208//10),
		RF(532//10),70Degree,210Degree,15))) == Float64(NO(x360))
	@test Float64(NO(TwoCenterNucAttractAAB(
		RF(100000000000000000000000000001//10000000000000000000000000000),9,-7,RF(125//10),
		RF(100000000000000000000000000001//10000000000000000000000000000),8,-8,RF(102//10),
		RF(1007//10),80Degree,240Degree,15))) == Float64(NO(x361))
end


# continuous trait evolution
@testset "traits: shared path, vcv, descendence matrix" begin
global tree_str, net, ind

tree_str= "(A:0.5,((B:1,#H1:1::0.1):1,(C:1,(D:1)#H1:1::0.9):1):0.5);"
net = readTopology(tree_str)
preorder!(net)

## V matrix
V1 = sharedPathMatrix(net)
@test_logs show(devnull, V1)

## By hand V matrix
l = ones(1, 9)
l[1] = 0.5; l[9] = 0.5
gamma = ones(1, 9); gamma[3] = 0.1; gamma[7] = 0.9
V2 = [0 0 0 0 0 0 0 0 0;
      0 l[1] 0 0 0 0 0 0 0;
      0 0 l[9] l[9] l[9] l[9] l[9] l[9] l[9];
      0 0 0 l[9]+l[4] l[9] l[9]+l[4] l[9]+gamma[3]*l[4] l[9] l[9]+gamma[3]*l[4];
      0 0 0 0 l[9]+l[8] l[9] l[9]+gamma[7]*l[8] l[9]+l[8] l[9]+gamma[7]*l[8];
      0 0 0 0 0 l[9]+l[4]+l[2] l[9]+gamma[3]l[4] l[9] l[9]+gamma[3]l[4];
      0 0 0 0 0 0 l[9]+gamma[3]^2*(l[3]+l[4])+gamma[7]^2*(l[7]+l[8]) l[9]+gamma[7]*l[8] l[9]+gamma[3]^2*(l[3]+l[4])+gamma[7]^2*(l[7]+l[8]);
      0 0 0 0 0 0 0 l[9]+l[8]+l[5] l[9]+gamma[7]*l[8];
      0 0 0 0 0 0 0 0 l[9]+gamma[3]^2*(l[3]+l[4])+gamma[7]^2*(l[7]+l[8])+l[6]]
# Make it symetric
for i in 1:9
    for j in 1:9
        if i > j
            V2[i,j] = V2[j,i]
        end
    end
end

nodesV2 = [-2, 1, -3, -4, -5, 2, 3, 4, 5] # root was number 6 before: with readTopologyLevel1 + rootatnode
ind = indexin(V1.nodeNumbersTopOrder, nodesV2)
V2 = V2[ind, ind]
@test_logs show(devnull, V2)

@test V1[:All] ≈ V2

########################
## vcv function
########################

## Simple test
C = convert(Matrix, vcv(net))
@test C ≈ V1[:Tips]
vv = LinearAlgebra.diag(C)
for i in 1:4
    for j in 1:4
        C[i, j] = C[i, j] / sqrt(vv[i] * vv[j])
    end
end
@test convert(Matrix, vcv(net; corr = true)) ≈ C

## Test names with tree
tree_str = "(((t2:0.1491947961,t4:0.3305515735):0.5953111246,t3:0.9685578963):0.1415281736,(t5:0.7093406462,t1:0.1888024569):0.9098094522);"
C = vcv(readTopology(tree_str))
# C_R = R"ape::vcv(ape::read.tree(text = $tree_str))"
# names_R = rcopy(R"colnames($C_R)")
names_R = ["t2", "t4", "t3", "t5", "t1"]

@test names(C) == map(Symbol, names_R)

########################
## Descendence Matrix Test
########################
tree_str= "(A:0.5,((B:1,#H1:1::0.4):1,(C:1,(D:1)#H1:1::0.6):1):0.5);"
net = readTopology(tree_str)
preorder!(net)

T = descendenceMatrix(net)

T2 =  [1.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0
       1.0  1.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0
       1.0  1.0  1.0  0.0  0.0  0.0  0.0  0.0  0.0
       1.0  1.0  1.0  1.0  0.0  0.0  0.0  0.0  0.0
       1.0  1.0  0.0  0.0  1.0  0.0  0.0  0.0  0.0
       1.0  1.0  0.6  0.0  0.4  1.0  0.0  0.0  0.0
       1.0  1.0  0.6  0.0  0.4  1.0  1.0  0.0  0.0
       1.0  1.0  0.0  0.0  1.0  0.0  0.0  1.0  0.0
       1.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  1.0]

@test T[:All] ≈ T2

##################
## New formula
#################

net = readTopology("(((Ag:5,(#H1:1::0.056,((Ak:2,(E:1,#H2:1::0.004):1):1,(M:2)#H2:1::0.996):1):1):1,(((((Az:1,Ag2:1):1,As:2):1)#H1:1::0.944,Ap:4):1,Ar:5):1):1,(P:4,20:4):3,165:7);")
preorder!(net)
V1 = sharedPathMatrix(net)

hyb = net.hybrid[2]

## Find child edge
hyb_edges = [e.hybrid for e in hyb.edge]
child_edge = hyb.edge[.!hyb_edges][1]
child = child_edge.isChild1 ? child_edge.node[1] : child_edge[17].node[2]
## Find number of child edge
nodeNumbersTopOrder = [n.number for n in net.nodes_changed]
p = indexin([child.number], nodeNumbersTopOrder)
## Find parents edges and node numbers
par_edge_1 = hyb.edge[hyb_edges][1]
a_n = PhyloNetworks.getOtherNode(par_edge_1, child)
a = indexin([a_n.number], nodeNumbersTopOrder)
par_edge_2 = hyb.edge[hyb_edges][2]
b_n = PhyloNetworks.getOtherNode(par_edge_2, child)
b = indexin([b_n.number], nodeNumbersTopOrder)

## Gamma value
gam = par_edge_1.gamma

## Two extreme networks
par_edge_1.gamma = 1.
par_edge_2.gamma = 0.
V_t_1 = sharedPathMatrix(net)

par_edge_1.gamma = 0.
par_edge_2.gamma = 1.
V_t_2 = sharedPathMatrix(net)

## Descendant indicatrice matrix
des = PhyloNetworks.descendants(par_edge_1, true) # true to get internal nodes also
mask = indexin(des, V_t_1.nodeNumbersTopOrder)
D = zeros(net.numNodes, net.numNodes)
D[mask, mask] .= 1.0

## Formula
V2 = gam*V_t_1[:All] + (1-gam)*V_t_2[:All] - gam*(1-gam) * (V_t_1.V[p, p] - V_t_1.V[a, b] + V_t_2.V[p, p] - V_t_2.V[a, b]) .* D

@test V1[:All] ≈ V2
end


module FastIntegers

typealias IntTypes Union{Int8, Int16, Int32, Int64, Int128}
typealias UIntTypes Union{UInt8, UInt16, UInt32, UInt64, UInt128}
typealias IntegerTypes Union{IntTypes, UIntTypes}

# LLVM seems to ignore this
export fast_assert
function fast_assert(cond::Bool)
    Base.llvmcall("""
        br i1 %0, label %good, label %bad
    good:
        ret void
    bad:
        unreachable
    """, Void, Tuple{Bool}, cond)
end
@generated function fast_assert{T<:IntegerTypes}(x::T, cond::Bool)
    tp = "i$(8*sizeof(T))"
    quote
        Base.llvmcall("""
            br i1 %1, label %good, label %bad
        good:
            ret $($tp) %0
        bad:
            unreachable
        """, T, Tuple{T, Bool}, x, cond)
    end
end

# This leads to segfaults in LLVM
#=
export fast_assume
@generated function fast_assume{T<:IntegerTypes}(x::T, cond::Bool)
    tp = "i$(8*sizeof(T))"
    quote
        Base.llvmcall("""
            br i1 %1, label %good, label %bad
        good:
            ret $($tp) %0
        bad:
            ret $($tp) undef
        """, T, Tuple{T, Bool}, x, cond)
    end
end
=#

export fast_pos
fast_pos{T<:IntegerTypes}(x::T) = x

export fast_neg
fast_neg{T<:IntegerTypes}(x::T) = T(0) - x

export fast_abs
function fast_abs{T<:IntTypes}(x::T)
    # Note: -x = ~(x-1)
    m = -signbit(x)
    r = (x + m) $ m
    # fast_assert(r >= T(0))
    # r = fast_assert(r, r >= T(0))
    # r = fast_assume(r, r >= T(0))
    r
end
fast_abs{T<:UIntTypes}(x::T) = x

export fast_add
@generated function fast_add{T<:IntegerTypes}(x::T, y::T)
    tp = "i$(8*sizeof(T))"
    nw = T <: IntTypes ? "nsw" : "nuw"
    quote
        Base.llvmcall("""
            %res = add $($nw) $($tp) %0, %1
            ret $($tp) %res
        """, T, Tuple{T, T}, x, y)
    end
end

export fast_sub
@generated function fast_sub{T<:IntegerTypes}(x::T, y::T)
    tp = "i$(8*sizeof(T))"
    nw = T <: IntTypes ? "nsw" : "nuw"
    quote
        Base.llvmcall("""
            %res = sub $($nw) $($tp) %0, %1
            ret $($tp) %res
        """, T, Tuple{T, T}, x, y)
    end
end

export fast_mul
@generated function fast_mul{T<:IntegerTypes}(x::T, y::T)
    tp = "i$(8*sizeof(T))"
    nw = T <: IntTypes ? "nsw" : "nuw"
    quote
        Base.llvmcall("""
            %res = mul $($nw) $($tp) %0, %1
            ret $($tp) %res
        """, T, Tuple{T, T}, x, y)
    end
end

export fast_div
@generated function fast_div{T<:IntegerTypes}(x::T, y::T)
    tp = "i$(8*sizeof(T))"
    div = T <: IntTypes ? "sdiv" : "udiv"
    quote
        Base.llvmcall("""
            %res = $($div) $($tp) %0, %1
            ret $($tp) %res
        """, T, Tuple{T, T}, x, y)
    end
end

export fast_rem
@generated function fast_rem{T<:IntegerTypes}(x::T, y::T)
    tp = "i$(8*sizeof(T))"
    rem = T <: IntTypes ? "srem" : "urem"
    quote
        Base.llvmcall("""
            %res = $($rem) $($tp) %0, %1
            ret $($tp) %res
        """, T, Tuple{T, T}, x, y)
    end
end

export fast_fld
function fast_fld{T<:IntTypes}(x::T, y::T)
    # fld(x,y) = div(x,y) - ((x>=0) != (y>=0) && rem(x,y) != 0 ? 1 : 0)
    d = fast_div(x, y)
    d - (signbit(x $ y) & (d * y != x))
end
fast_fld{T<:UIntTypes}(x::T, y::T) = fast_div(x, y)

export fast_mod
function fast_mod{T<:IntTypes}(x::T, y::T)
    # x = fld(x,y)*y + mod(x,y)
    x - fast_fld(x, y) * y
end
fast_mod{T<:UIntTypes}(x::T, y::T) = fast_rem(x, y)

export fast_cld
function fast_cld{T<:IntTypes}(x::T, y::T)
    # cld(x,y) = div(x,y) + ((x>0) = (y>0) && rem(x,y) != 0 ? 1 : 0)
    d = fast_div(x, y)
    d + (((x > 0) == (y > 0)) & (d * y != x))
end
function fast_cld{T<:UIntTypes}(x::T, y::T)
    d = fast_div(x, y)
    d + (d * y != x)
end

end


function LinearAlgebra.givensAlgorithm(f::T, g::T) where T<:ForwardDiff.Dual # T<:Real?
    onepar = one(T)
    twopar = 2one(T)
    T0 = typeof(onepar) # dimensionless
    zeropar = T0(zero(T)) # must be dimensionless

    # need both dimensionful and dimensionless versions of these:
    safmn2 = LinearAlgebra.floatmin2(T0)
    safmn2u = LinearAlgebra.floatmin2(T)
    safmx2 = one(T)/safmn2
    safmx2u = oneunit(T)/safmn2

    if g == 0
        cs = onepar
        sn = zeropar
        r = f
    elseif f == 0
        cs = zeropar
        sn = onepar
        r = g
    else
        f1 = f
        g1 = g
        scalepar = max(abs(f1), abs(g1))
        if scalepar >= safmx2u
            count = 0
            while true
                count += 1
                f1 *= safmn2
                g1 *= safmn2
                scalepar = max(abs(f1), abs(g1))
                if scalepar < safmx2u break end
            end
            r = sqrt(f1*f1 + g1*g1)
            cs = f1/r
            sn = g1/r
            for i = 1:count
                r *= safmx2
            end
        elseif scalepar <= safmn2u
            count = 0
            while true
                count += 1
                f1 *= safmx2
                g1 *= safmx2
                scalepar = max(abs(f1), abs(g1))
                if scalepar > safmn2u break end
            end
            r = sqrt(f1*f1 + g1*g1)
            cs = f1/r
            sn = g1/r
            for i = 1:count
                r *= safmn2
            end
        else
            r = sqrt(f1*f1 + g1*g1)
            cs = f1/r
            sn = g1/r
        end
        if abs(f) > abs(g) && cs < 0
            cs = -cs
            sn = -sn
            r = -r
        end
    end
    return cs, sn, r
end


# f(x)=diagA*X
function FdiagAX(A::Array,X::Array)
    if size(A)==(1,1)
        return (A[1].*X,nothing)
    elseif size(X)==(1,1)
        return (diag(A).*X[1],nothing)
    else
        return (diag(A)*X,nothing)
    end
end

function FdiagAX_inplace(value,auxvalue,A::Array,X::Array)
    if size(A)==(1,1)
        copy!(value,A[1]*X)
    elseif size(X)==(1,1)
        copy!(value,diag(A)*X[1])
    else
        #BLAS.gemm!('N','N',1.0,A,X,0.0,value)
        copy!(value,diag(A)*X) # BLAS has no dgmm routine
    end
end

function DdiagAX(derivativeIDX,f_c,faux_c,grad_c,grad_n,A,X)
    if derivativeIDX==1
        if size(A)==(1,1)
            axpy!(1.0,[sum(X.*grad_c)],grad_n)
        elseif size(X)==(1,1)
            axpy!(X[1],sum(grad_c,2),grad_n)
        else
            axpy!(1.0,sum(X.*grad_c,2),grad_n)
        end
    elseif derivativeIDX==2
        if size(A)==(1,1)
            axpy!(A[1],grad_c,grad_n)
        elseif size(X)==(1,1)
            axpy!(1.0,[sum(A.*diag(grad_c))],grad_n)
        else
            axpy!(1.0,diag(A)*grad_c,grad_n)
        end
    end
end

if 1==0 # TODO GPU
    gemm!(T1::Char,T2::Char,alpha::Float64,A::CudaArray,B::CudaArray,beta::Float64,C::CudaArray)=CUBLAS.gemm!(T1,T2,alpha,A,B,beta,C)
    export gemm!
    # TODO: make functions that mirror BLAS generality

    #gemmABout!(A::CudaArray,B::CudaArray,Out::CudaArray)=CUBLAS.gemm!('N','N',1.0,A,B,0.0,Out)
    #export gemmABout!

    function FAX(A::CudaArray,X::CudaArray)
        if size(A)==(1,1)
            value=CudaArray(Float64,size(X));
            copy!(value,X); scale!(A,value)
            return (value,[])
        elseif size(X)==(1,1)
            value=CudaArray(Float64,size(A));
            copy!(value,A); scale!(X,value) # nb argument converse of Base.scale!
            return (value,[])
        else
            return (CUBLAS.gemm('N','N',A,X),[])
        end
    end

    function FAX_inplace(value::CudaArray,auxvalue,A::CudaArray,X::CudaArray)
        if size(A)==(1,1)
        elseif size(X)==(1,1)
            copy!(value,X); scale!(A,value) # nb argument converse of Base.scale!
            copy!(value,A); scale!(X,value) # nb argument converse of Base.scale!
        else
            CUBLAS.gemm!('N','N',1.0,A,X,0.0,value)
        end
    end

    function DAX(derivativeIDX,f_c,faux_c,grad_c,grad_n,A::CudaArray,X::CudaArray)
        if derivativeIDX==1
            if size(A)==(1,1)
                tmp=CudaArray(Float64,size(X))
                vmult!(1.0,X,grad_c,tmp)
                sum_update!(1.0,tmp,1.0,grad_n)
                #axpy!(1.0,sum(tmp),grad_n);
                free(tmp)
            elseif size(X)==(1,1)
                alphaaxpy!(1.0,X,grad_c,grad_n)
            else
                CUBLAS.gemm!('N','T',1.0,grad_c,X,1.0,grad_n) # grad_c*X'
            end
        elseif derivativeIDX==2
            if size(A)==(1,1)
                alphaaxpy!(1.0,A,grad_c,grad_n)
            elseif size(X)==(1,1)
                tmp=CudaArray(Float64,size(A))
                vmult!(1.0,A,grad_c,tmp)
                axpy!(1.0,sum(tmp),grad_n); free(tmp)
            else
                CUBLAS.gemm!('T','N',1.0,A,grad_c,1.0,grad_n) # A'*t
            end
        end
    end
end

Derivative[FAX]=DAX
export FAX

Inplace[FAX]=FAX_inplace

Derivative[FAX]=DAX
import Base.*
*(A::ADnode,B::ADnode)=ADnode(FAX,[A B])

*(A::Real,B::ADnode)=ADnode(FAX,[ADconst(A) B])
*(A::ADnode,B::Real)=ADnode(FAX,[A ADconst(B)])

@gpu *(A::CudaArray,B::CudaArray)=CUBLAS.gemm('N','N',A,B)
export *


### Day 8 - Problem 1
## Two-Factor Authentication
## Authors: Thanasis Georgiou, Marina Grigoropoulou

# Read input
f = open("input.txt")
input = readlines(f)
close(f)

# Screen
lcd = Array(Bool, 50, 6)

# Fill a rectangle at top left
function rect(w, h)
    for x = 1:w
        for y = 1:h
            lcd[x, y] = true
        end
    end
end

# Rotate a row by Δ
function rotateRow(y, Δ)
    lcd[1:50, y] = circshift(lcd[1:50, y], Δ)
end

# Rotate a column by Δ
function rotateColumn(x, Δ)
    lcd[x, 1:6] = circshift(lcd[x, 1:6], Δ)
end

# Parse input
for line in input
    str = split(line, ' ')

    # Determine command
    if str[1] == "rect"
        coords = split(str[2], 'x')
        w = parse(Int, coords[1])
        h = parse(Int, coords[2])
        rect(w, h)
    elseif str[1] == "rotate"
        Δ = parse(Int, str[end])
        l = parse(Int, str[3][3:end]) + 1

        if str[2] == "row"
            rotateRow(l, Δ)
        else
            rotateColumn(l, Δ)
        end
    end
end

# Count pixels
count = 0
for y = 1:6
    for x = 1:50
        if lcd[x, y] == true
            count += 1
            print("#")
        else
            print(" ")
        end
    end
    print("\n")
end

println("Count: $count")


using SparseIR
using Test

import PyCall: pyimport, PyNULL, PyVector
import LegendrePolynomials: Pl

sparse_ir = pyimport("sparse_ir")

@testset "augment.LegendreBasis" begin
    for stat in [fermion, boson]
        beta = 10.0
        Nl = 100
        cl = sqrt.(2 .* collect(0:(Nl - 1)) .+ 1)
        basis = SparseIR.LegendreBasis(stat, beta, Nl; cl=cl)

        tau = Float64[0, 0.1 * beta, 0.4 * beta, beta]
        uval = basis.u(tau)

        ref = Matrix{Float64}(undef, Nl, length(tau))
        for l in 0:(Nl - 1)
            x = @. 2 * tau / beta - 1.0
            ref[l + 1, :] = cl[l + 1] .* (sqrt(2 * l + 1) / beta) * Pl.(x, l)
        end
        @test isapprox(uval, ref)

        sign = statistics_sign(stat)

        # G(iv) = 1/(iv-pole)
        # G(tau) = -e^{-tau*pole}/(1 + e^{-beta*pole}) [F]
        #        = -e^{-tau*pole}/(1 - e^{-beta*pole}) [B]
        pole = 1.0
        tau_smpl = TauSampling(basis)
        gtau = -exp.(-tau_smpl.sampling_points .* pole) ./ (1 - sign * exp(-beta * pole))
        gl_from_tau = fit(tau_smpl, gtau)

        matsu_smpl = MatsubaraSampling(basis)
        giv = 1 ./ ((im * π / beta) .* matsu_smpl.sampling_points .- pole)
        gl_from_matsu = fit(matsu_smpl, giv)

        @test isapprox(gl_from_tau, gl_from_matsu;
                       atol=1e-10 * maximum(abs.(gl_from_matsu)), rtol=0)
    end
end


### CONCRETE TYPE: DIRECT PROX EVALUATION
# prox! is computed using a QR factorization of A'.

using LinearAlgebra: QRCompactWY

struct IndAffineDirect{R <: Real, T <: RealOrComplex{R}, M <: AbstractMatrix{T}, V <: AbstractVector{T}, F <: Factorization} <: IndAffine
  A::M
  b::V
  fact::F
  res::V
  function IndAffineDirect{R, T, M, V, F}(A::M, b::V) where {R <: Real, T <: RealOrComplex{R}, M <: AbstractMatrix{T}, V <: AbstractVector{T}, F <: Factorization}
    if size(A, 1) > size(A, 2)
      error("A must be full row rank")
    end
    normrowsinv = 1 ./ vec(sqrt.(sum(abs2.(A); dims=2)))
    A = normrowsinv.*A # normalize rows of A
    b = normrowsinv.*b # and b accordingly
    fact = qr(M(A'))
    new(A, b, fact, similar(b))
  end
end

is_cone(f::IndAffineDirect) = norm(f.b) == 0.0

IndAffineDirect(A::M, b::V) where {R <: Real, T <: RealOrComplex{R}, M <: DenseMatrix{T}, V <: AbstractVector{T}} = IndAffineDirect{R, T, M, V, QRCompactWY{T, M}}(A, b)

IndAffineDirect(A::M, b::V) where {R <: Real, T <: RealOrComplex{R}, I <: Integer, M <: SparseMatrixCSC{T, I}, V <: AbstractVector{T}} = IndAffineDirect{R, T, M, V, SuiteSparse.SPQR.Factorization{T}}(A, b)

IndAffineDirect(a::V, b::T) where {R <: Real, T <: RealOrComplex{R}, V <: AbstractVector{T}} = IndAffineDirect(reshape(a,1,:), [b])

function (f::IndAffineDirect{R, T, M, V, F})(x::V) where {R, T, M, V, F}
  mul!(f.res, f.A, x)
  f.res .= f.b .- f.res
  # the tolerance in the following line should be customizable
  if norm(f.res, Inf) <= 1e-12
    return zero(R)
  end
  return typemax(R)
end

function prox!(y::V, f::IndAffineDirect{R, T, M, V, F}, x::V, gamma::R=one(R)) where {R, T, M, V, F <: QRCompactWY}
  mul!(f.res, f.A, x)
  f.res .= f.b .- f.res
  Rfact = view(f.fact.factors, 1:length(f.b), 1:length(f.b))
  LinearAlgebra.LAPACK.trtrs!('U', 'C', 'N', Rfact, f.res)
  LinearAlgebra.LAPACK.trtrs!('U', 'N', 'N', Rfact, f.res)
  mul!(y, adjoint(f.A), f.res)
  y .+= x
  return zero(R)
end

function prox!(y::V, f::IndAffineDirect{R, T, M, V, F}, x::V, gamma::R=one(R)) where {R, T, M, V, F <: SuiteSparse.SPQR.Factorization}
  mul!(f.res, f.A, x)
  f.res .= f.b .- f.res
  ##############################################################################
  # We need to solve for z: AA'z = res
  # We have: QR=PA'S so A'=P'QRS' and AA'=SR'Q'PP'QRS'=SR'RS'
  # So: z = S'\R\R'\S\res
  # TODO: the following lines should be made more efficient
  temp = f.res[f.fact.pcol]
  temp = LowerTriangular(adjoint(f.fact.R))\temp
  temp = UpperTriangular(f.fact.R)\temp
  RRres = similar(temp)
  RRres[f.fact.pcol] .= temp
  ##############################################################################
  mul!(y, adjoint(f.A), RRres)
  y .+= x
  return zero(R)
end

function prox_naive(f::IndAffineDirect, x::AbstractArray{T,1}, gamma::R=one(R)) where {R <: Real, T <: RealOrComplex{R}}
  y = x + f.A'*((f.A*f.A')\(f.b - f.A*x))
  return y, zero(R)
end