"""
	contribution(s,o)

Return the contribution of the given node or link in the Stream.
The contribution of a node v in a stream is defined as:
```math
n_v=\\frac{\\left| T_v \\right|}{\\left| T \\right|}
```
The contribution of a link uv in a stream is defined as:
```math
m_{uv}=\\frac{\\left| T_{uv} \\right|}{\\left| T \\right|}
```
Note: If the stream has no duration, this function returns 0.

### Reference
- Matthieu Latapy, Tiphaine Viard and Clémence Magnien Social
  Networks Analysis and Mining, 8: 61, 2018. "Stream Graphs and
  Link Streams for the Modeling of Interactions over Time".
  [(arXiv)](https://arxiv.org/pdf/1710.04073.pdf)
"""
function contribution(s::AbstractStream, o::StreamObject)
	duration(s)!=0 ? duration(o) / duration(s) : 0.0
end

"""
	contribution(ls,"node_name")

Return the contribution of the given node in the link stream.
By definition, this is always 1 since nodes have no dynamics in
link streams.
"""
contribution(ls::Union{LinkStream,DirectedLinkStream},node_name::AbstractString)=1.0

"""
	contribution(s,"node_name")

Return the contribution of the given node in the stream graph.
"""
function contribution(s::Union{StreamGraph,DirectedStreamGraph},node_name::AbstractString)
	contribution(s,s.W[node_name])
end

"""
	contribution(ls,"node_name",t)

Return the contribution of the given node at the given time in the link stream.
By definition, this is always 1 since nodes have no dynamics in
link streams.
"""
contribution(ls::Union{LinkStream,DirectedLinkStream},node_name::AbstractString,t::Float64)=1.0

"""
	contribution(s,"from","to")

Return the contribution of the directed link in the directed stream.
"""
function contribution(s::AbstractDirectedStream, from::AbstractString, to::AbstractString)
	((haskey(s.E,from))&&(haskey(s.E[from],to))) ? contribution(s,s.E[from][to]) : 0
end

"""
	contribution(s,"from","to")

Return the contribution of the undirected link in the undirected stream.
This means that contribution(s,"from","to")==contribution(s,"to","from")
"""
function contribution(s::AbstractUndirectedStream, from::AbstractString, to::AbstractString)
	if haskey(s.E,from) && haskey(s.E[from],to)
		contribution(s,s.E[from][to])
	elseif haskey(s.E,to) && haskey(s.E[to],from)
		contribution(s,s.E[to][from])
	else
		throw("Unknown link ($from,$to).")
	end
end

"""
	node_contribution(s,t)

Return the node contribution of the given time instant in the stream.
The node contribution of a time instant is defined as:
```math
k_t = \\frac{\\left| V_t \\right|}{\\left| V \\right|}
```
Note: If the stream has no node, this function returns 0.

### Reference
- Matthieu Latapy, Tiphaine Viard and Clémence Magnien Social
  Networks Analysis and Mining, 8: 61, 2018. "Stream Graphs and
  Link Streams for the Modeling of Interactions over Time".
  [(arXiv)](https://arxiv.org/pdf/1710.04073.pdf)
"""
function node_contribution(s::AbstractStream,t::Float64)
	length(s.V) != 0 ? length(nodes(s,t))/length(s.V) : 0.0
end

"""
	node_contribution(s,tc,t)

Return the node contribution of the given time instant in the stream
using a time cursor.
"""
function node_contribution(s::AbstractStream,tc::TimeCursor,t::Float64)
	length(s.V) != 0 ? length(nodes(tc,t))/length(s.V) : 0.0
end

"""
	link_contribution(s,t)

Return the link contribution of the given time instant in the stream.
The link contribution of a time instant is defined as:
```math
l_t = \\frac{\\left| E_t \\right|}{\\left| V \\otimes V \\right|}
```
Note: If the stream has no node, this function returns 0.

### Reference
- Matthieu Latapy, Tiphaine Viard and Clémence Magnien Social
  Networks Analysis and Mining, 8: 61, 2018. "Stream Graphs and
  Link Streams for the Modeling of Interactions over Time".
  [(arXiv)](https://arxiv.org/pdf/1710.04073.pdf)
"""
function link_contribution(s::AbstractStream,t::Float64)
	length(s.V) != 0 ? length(links(s,t))/length(s.V ⊗ s.V) : 0.0
end

"""
	link_contribution(s,tc,t)

Return the link contribution of the given time instant in the stream
using a time cursor.
"""
function link_contribution(s::AbstractStream,tc::TimeCursor,t::Float64)
	length(s.V) != 0 ? length(links(tc,t))/length(s.V ⊗ s.V) : 0.0
end


function calcarr(xa::Float64, p::Int64, start = 0.0, fin = 4.0)
  λarr = collect(linspace(start, fin, p))
  const iterconst = 50000
  const ϵ = 1e-6
  xarr = Array{Array{Float64,1},1}(p)
  yarr = Array{Array{Float64,1},1}(p)
  for j = 1:p
    λ = λarr[j]
    x = xa
    xarr[j] = Array{Float64,1}(0)
    yarr[j] = Array{Float64,1}(0)
    for i = 1:iterconst
      x = λ*x*(1-x)
    end
    push!(yarr[j], x)
    found = false
    while !found # while we still have new values
      x = λ*x*(1-x) # iterate once more
      for y in yarr[j] # iterate through all found values
        if almostequal(x, y, ϵ) # and check if we already have the new one
          found = true
          break
        end
      end
      if !found
        push!(yarr[j], x) # if not, add it to the array
      end
    end
    push!(xarr[j], λ)
  end
  return xarr, yarr
end

# function calcarrpmap(xa::Float64, p::Int64, start = 0.0, fin = 4.0)
#
# end
#
# xa = 0.5
# p = 1000
# start = 0.0
# fin = 4.0
#
# const chunks = 10
# step = div(p, 100)
# λarr = collect(linspace(start, fin, p))
# const iterconst = 50000
# const ϵ = 1e-6
# xarr = Array{Array{Float64, 1}, 1}(p)
# yarr = Array{Array{Float64, 1}, 1}(p)


using Sudoku, Test
#### n = 2 ####
A = sudoku(Sudoku.puzz21)
@test sudoku_check(A)

A = sudoku(Sudoku.puzz22)
@test sudoku_check(A)

A = sudoku(Sudoku.puzz23)
@test sudoku_check(A)

sudoku_print(A)

#### n = 3 ####
A = sudoku(Sudoku.puzz1)
@test sudoku_check(A)

A = sudoku(Sudoku.puzz2)
@test sudoku_check(A)

A = sudoku(Sudoku.puzz3)
@test sudoku_check(A)

sudoku_print(A)

#### n = 4 ####
A = sudoku(Sudoku.puzz41)
@test sudoku_check(A)
sudoku_print(A)


using FastGaussQuadrature, Test

@testset "Gauss–Radau" begin
    @testset "Check error" begin
        @test_throws DomainError gaussradau(0)
        @test_throws DomainError gaussradau(0,0,0)
    end

    @testset "Legendre" begin
        n = 1
        x, w = gaussradau(n)
        @test x[1] ≈ -1.
        @test w[1] ≈ 2.

        n = 2
        x, w = gaussradau(n)
        @test x[1] ≈ -1.
        @test x[2] ≈ 1/3
        @test w[1] ≈ .5
        @test w[2] ≈ 1.5

        tol = 1e-14
        n = 42
        ntests = 4
        x, w = gaussradau(n)
        @test length(x) == n && length(w) == n
        @test abs(x[37] - 0.908847278001044) < tol
        @test abs(w[37] - 0.031190846817016) < tol
        @test x[1] == -1

        @test dot( w,exp.(x)) ≈ exp(1)-exp(-1)
    end
    @testset "Jacobi" begin
        for (a,b) in ((0.1, 0.2), (0,-0.5))
            # compare with Gauss–Jacobi
            for n = 1:5
                x, w = gaussradau(n, a, b)
                x̃, w̃ = gaussjacobi(n, a, b)

                @test length(x) == n

                @test sum(w) ≈ sum(w̃)
                for j = 1:2n-2
                    @test dot(w,x.^j) ≈ dot(w̃,x̃.^j)
                end
            end
        end

        # compare with Gauss-Radau-Legendre
        @test all(gaussradau(2, 0, 0) .≈ gaussradau(2))
        @test all(gaussradau(3, 0, 0) .≈ gaussradau(3))
    end
end


using EffectiveWaves

## Choose two species randomly (uniformly) distributed in the medium
# Usage Specie(ρ = density, r = radius, c = wavespeed, volfrac = volume fraction)
species = [
    Specie(ρ=WaterDistilled.ρ,r=30.e-6, c=WaterDistilled.c, volfrac=0.1),
    Specie(ρ=Inf, r=100.0e-6, c=2.0, volfrac=0.2)
]
# background medium
background = Glycerol # for other materials, see materials.jl

# angular frequencies
ωs = LinRange(0.01,1.0,60)*30.0e6

# using only one effective wavenumber per angular frequencies. This wavenumber is calculated from an asymptotic formula for low particle volume fraction
k_effs = wavenumber_low_volfrac(ωs, background, species)

speeds = ωs./real(k_effs)
attenuations = imag(k_effs)

using Plots
pyplot()

p1 = plot(ωs./real(background.c), speeds,  ylabel="wave speed (m/s)", xlabel="k (1/m)");
p2 = plot(ωs./real(background.c), attenuations, ylabel="attenuation (1/m)", xlabel="k (1/m)");
plot(p1,p2)

## An example where we vary the species

height=450
# unicodeplot() # alternative plot backend
pyplot(linewidth=3, size=(2*height,height))
Plots.scalefontsizes(1.5)

# for fixed total volfrac fraction
background = Medium(ρ=1.0,c = 1.0)
ωs = LinRange(0.01,1.0,60)
volfrac = 0.25
r1 = 0.5
r2 = 1.5
N=5
vols = LinRange(0.0,1.0,N)*volfrac

kTs_arr = [
  begin
    sp1 = Specie(0.0, r1; volfrac=vols[i])
    sp2 = Specie(Inf, r1; volfrac=volfrac-vols[i])
    [ wavenumber_low_volfrac(ω, background, [sp1,sp2]) for ω in ωs]
  end
for i = 1:N];

speeds = [ ωs./real(kTs) for kTs in kTs_arr]
attenuations = imag(kTs_arr)

labs = reshape( map(v -> "void vol = $(Int(round(100*v)))%",vols),1, length(vols));
p1 = plot(ωs./real(background.c), speeds, labels=labs, ylabel="wave speed (m/s)" ,xlabel="k (1/m)");
p2 = plot(ωs./real(background.c), attenuations, labels=labs, xlabel="k (1/m)", ylabel="attenuation (1/m)");

plot(p1,p2)

Plots.scalefontsizes(1./1.5)


using Plots, Random

function angle2point(θ)
    N = length(θ)
    x = zeros(N,2)
    for i in 1:N
        x[i,:] = [cos(θ[i]), sin(θ[i])]
    end
    return x
end

function circle_plot(x; color=:red)
    scatter(x[:,1], x[:,2]; c=color, aspect_ratio=1, legend=false, grid=false, mswidth=0, xlims=[-1.1, 1.1], ylims=[-1.1, 1.1])
end

function state_vector(θx)
    sort!(θx)
    γ = zeros(N)
    γ[1] = θx[1]-θx[end]+2π
    for i in 2:N
        γ[i] = θx[i]-θx[i-1]
    end
    𝝓 = [γ[i]-2π/N for i in 1:N]
    return 𝝓
end

# 0. Initialize points on [0, 2π)
Random.seed!(2020)
N = 32; iter = 5000
θx = 2π*rand(N)
circle_plot(angle2point(θx))

# 1. Learning steps
α = 0.1
𝝓_norm = []
anim = @animate for i in 1:iter
    push!(𝝓_norm, norm(state_vector(θx)))
    θ = 2π*rand()
    θy = (θx .+ θ) .% 2π
    nnx = Dict()
    for j in 1:N
        nnx[j] = []
    end
    for i in 1:N
        arr = min.(abs.(θx .- θy[i]), 2π .- abs.(θx .- θy[i]))
        min_val = minimum(arr)
        min_ind = findall(arr .== min_val)
        for j in min_ind
            nnx[j] = push!(nnx[j], i)
        end
    end
    for j in 1:N
        if nnx[j] != []
            θx[j] += α * sum([θy[i]-θx[j]+(abs(θy[i]-θx[j])<π ? 0 : -sign(θy[i]-θx[j])*2π)  for i in nnx[j]])
        end
    end
    if i % 50 == 0
        circle_plot(angle2point(θx))
        title!("iter=$(i)")
    end
end when i % 50 == 0

gif(anim, "selfSimNN_1D.gif", fps = 5)

plot(𝝓_norm, legend=false)

# circle_plot(angle2point(θx))


# simple program to test MCL clustering

using Base.Test
using Clustering

srand(34568)

# test for nonsquare matrices
@test_throws DimensionMismatch mcl(zeros(Float64, 4, 3))

nodes = [:bat, :bit, :cat, :fit, :hat, :hit]
edges = Tuple{Symbol, Symbol, Float64}[(:cat, :hat, 0.2), (:hat, :bat, 0.16),
                                       (:bat, :cat, 1.0), (:bat, :bit, 0.125),
                                       (:bit, :fit, 0.25), (:fit, :hit, 0.5),
                                       (:hit, :bit, 0.16)]
adj_matrix = zeros(Float64, length(nodes), length(nodes))
for edge in edges
    n1 = findfirst(nodes, edge[1])
    n2 = findfirst(nodes, edge[2])
    adj_matrix[n1, n2] = adj_matrix[n2, n1] = edge[3]
end
@assert issymmetric(adj_matrix)

res = mcl(adj_matrix, display=:none, inflation=1.8)
@test isa(res, MCLResult)
k = length(res.counts)
# println("k = $k")
@test k == 2
@test all(res.assignments .<= k)
@test length(res.assignments) == length(nodes)
@test length(res.counts) == k
for c = 1:k
    @test countnz(res.assignments .== c) == res.counts[c]
end
@test res.nunassigned == 0
@test res.assignments == [1, 2, 1, 2, 1, 2]

# test with integer inflation value
res = mcl(adj_matrix, display=:none, inflation=2)
@test isa(res, MCLResult)
@test length(res.assignments) == length(nodes)
@test res.nunassigned == 0

# test non-integral expansion (show not raise an exception)
res = mcl(adj_matrix, display=:none, inflation=1.5, expansion=1.5, save_final_matrix=true)
@test isa(res, MCLResult)
@test length(res.assignments) == length(nodes)
@test size(res.mcl_adj) == size(adj_matrix) # test that the matrix is returned

# test allow_singles
res = mcl(diagm([1.0, 1.0]), display=:none, allow_singles=true)
@test length(res.counts) == 2
@test res.assignments == [1, 2]
@test res.counts == [1, 1]
@test res.nunassigned == 0

res = mcl(diagm([1.0, 1.0]), display=:none, allow_singles=false)
@test length(res.counts) == 0
@test res.assignments == [0, 0]
@test res.nunassigned == 2

# use sparse matrix
res = mcl(sparse(adj_matrix), display=:none, expansion=2)
@test isa(res, MCLResult)
@test length(res.assignments) == length(nodes)
@test res.nunassigned == 0
@test eltype(res.mcl_adj) === Float64

@test_throws ArgumentError mcl(sparse(adj_matrix), display=:none, expansion=2.1)

# use Float32 input
res = mcl(convert(Matrix{Float32},adj_matrix), display=:none, expansion=2)
@test isa(res, MCLResult)
@test eltype(res.mcl_adj) === Float32


"""
    struct ProductSpace{S<:ElementarySpace, N} <: CompositeSpace{S}

A `ProductSpace` is a tensor product space of `N` vector spaces of type
`S<:ElementarySpace`. Only tensor products between [`ElementarySpace`](@ref) objects of the
same type are allowed.
"""
struct ProductSpace{S<:ElementarySpace, N} <: CompositeSpace{S}
    spaces::NTuple{N, S}
end
ProductSpace(spaces::Vararg{S,N}) where {S<:ElementarySpace, N} =
    ProductSpace{S,N}(spaces)
ProductSpace{S,N}(spaces::Vararg{S,N}) where {S<:ElementarySpace, N} =
    ProductSpace{S,N}(spaces)

# Corresponding methods
#-----------------------
"""
    dims(::ProductSpace{S,N}) -> Dims{N} = NTuple{N,Int}

Return the dimensions of the spaces in the tensor product space as a tuple of integers.
"""
dims(P::ProductSpace) = map(dim, P.spaces)
dim(P::ProductSpace, n::Int) = dim(P.spaces[n])
dim(P::ProductSpace) = prod(dims(P))

Base.axes(P::ProductSpace) = map(axes, P.spaces)
Base.axes(P::ProductSpace, n::Int) = axes(P.spaces[n])

dual(P::ProductSpace{<:ElementarySpace,0}) = P
dual(P::ProductSpace) = ProductSpace(map(dual, reverse(P.spaces)))

# Base.conj(P::ProductSpace) = ProductSpace(map(conj, P.spaces))

function Base.show(io::IO, P::ProductSpace{S}) where {S<:ElementarySpace}
    spaces = P.spaces
    if length(spaces) == 0
        print(io,"ProductSpace{", S, ",0}")
    end
    if length(spaces) == 1
        print(io,"ProductSpace")
    end
    print(io,"(")
    for i in 1:length(spaces)
        i==1 || print(io," ⊗ ")
        show(io, spaces[i])
    end
    print(io,")")
end

# more specific methods
sectors(P::ProductSpace) = _sectors(P, sectortype(P))
_sectors(P::ProductSpace{<:ElementarySpace, N}, ::Type{Trivial}) where {N} =
    (ntuple(n->Trivial(), StaticLength{N}()),) # speed up sectors for ungraded spaces
_sectors(P::ProductSpace{<:ElementarySpace, N}, ::Type{<:Sector}) where {N} =
    product(map(sectors, P.spaces)...)

hassector(V::ProductSpace{<:ElementarySpace,N}, s::NTuple{N}) where {N} =
    reduce(&, map(hassector, V.spaces, s); init = true)

dims(P::ProductSpace{<:ElementarySpace, N}, sector::NTuple{N,<:Sector}) where {N} =
    map(dim, P.spaces, sector)
dim(P::ProductSpace{<:ElementarySpace, N}, sector::NTuple{N,<:Sector}) where {N} =
    prod(dims(P, sector))

Base.axes(P::ProductSpace{<:ElementarySpace,N}, sectors::NTuple{N,<:Sector}) where {N} =
    map(axes, P.spaces, sectors)

"""
    dims(::ProductSpace{S,N}) -> Dims{N} = NTuple{N,Int}

Return the dimensions of the spaces in the tensor product space as a tuple of integers.
"""
function blocksectors(P::ProductSpace{S,N}) where {S,N}
    G = sectortype(S)
    if G == Trivial
        return (Trivial(),)
    end
    bs = Vector{G}()
    if N == 0
        push!(bs, one(G))
    elseif N == 1
        for s in sectors(P)
            push!(bs, first(s))
        end
    else
        for s in sectors(P)
            for c in ⊗(s...)
                if !(c in bs)
                    push!(bs, c)
                end
            end
        end
        # return foldl(union!, Set{G}(), (⊗(s...) for s in sectors(P)))
    end
    return bs
end
function blockdim(P::ProductSpace, c::Sector)
    sectortype(P) == typeof(c) || throw(SectorMismatch())
    d = 0
    for s in sectors(P)
        ds = dim(P, s)
        d += length(fusiontrees(s, c))*ds
    end
    return d
end

Base.:(==)(P1::ProductSpace, P2::ProductSpace) = (P1.spaces == P2.spaces)

# Default construction from product of spaces
#---------------------------------------------
⊗(V1::S, V2::S) where {S<:ElementarySpace}= ProductSpace((V1, V2))
⊗(P1::ProductSpace{S}, V2::S) where {S<:ElementarySpace} =
    ProductSpace(tuple(P1.spaces..., V2))
⊗(V1::S, P2::ProductSpace{S}) where {S<:ElementarySpace} =
    ProductSpace(tuple(V1, P2.spaces...))
⊗(P1::ProductSpace{S}, P2::ProductSpace{S}) where {S<:ElementarySpace} =
    ProductSpace(tuple(P1.spaces..., P2.spaces...))
⊗(P::ProductSpace{S,0}, ::ProductSpace{S,0}) where {S<:ElementarySpace} = P
⊗(P::ProductSpace{S}, ::ProductSpace{S,0}) where {S<:ElementarySpace} = P
⊗(::ProductSpace{S,0}, P::ProductSpace{S}) where {S<:ElementarySpace} = P
⊗(V::ElementarySpace) = ProductSpace((V,))
⊗(P::ProductSpace) = P

# unit element with respect to the monoidal structure of taking tensor products
"""
    one(::S) where {S<:ElementarySpace} -> ProductSpace{S,0}
    one(::ProductSpace{S}) where {S<:ElementarySpace} -> ProductSpace{S,0}

Return a tensor product of zero spaces of type `S`, i.e. this is the unit object under the
tensor product operation, such that `V ⊗ one(V) == V`.
"""
Base.one(V::VectorSpace) = one(typeof(V))
Base.one(::Type{<:ProductSpace{S}}) where {S<:ElementarySpace} = ProductSpace{S,0}(())
Base.one(::Type{S}) where {S<:ElementarySpace} = ProductSpace{S,0}(())

Base.convert(::Type{<:ProductSpace}, V::ElementarySpace) = ProductSpace((V,))
Base.literal_pow(::typeof(^), V::ElementarySpace, p::Val{N}) where N =
    ProductSpace{typeof(V), N}(ntuple(n->V, p))
Base.convert(::Type{S}, P::ProductSpace{S,0}) where {S<:ElementarySpace} = oneunit(S)
Base.convert(::Type{S}, P::ProductSpace{S}) where {S<:ElementarySpace} = fuse(P.spaces...)
fuse(P::ProductSpace{S,0}) where {S<:ElementarySpace} = oneunit(S)
fuse(P::ProductSpace{S}) where {S<:ElementarySpace} = fuse(P.spaces...)

# Functionality for extracting and iterating over spaces
#--------------------------------------------------------
Base.length(P::ProductSpace) = length(P.spaces)
Base.getindex(P::ProductSpace, n::Integer) = P.spaces[n]
Base.getindex(P::ProductSpace{S}, I::NTuple{N,Integer}) where {S<:ElementarySpace,N} =
    ProductSpace{S,N}(TupleTools.getindices(P.spaces, I))

Base.iterate(P::ProductSpace) = Base.iterate(P.spaces)
Base.iterate(P::ProductSpace, s) = Base.iterate(P.spaces, s)

Base.eltype(P::ProductSpace{S}) where {S<:ElementarySpace} = S

Base.IteratorEltype(P::ProductSpace) = Base.IteratorEltype(P.spaces)
Base.IteratorSize(P::ProductSpace) = Base.IteratorSize(P.spaces)


# MultinomialSeries.jl -- Part of 'MultinomialSeries',
# a package for The Julia Programming Language
# Copyright (C) 2020-2021 Markus J. Weber
# License: See accompanying file 'LICENSE.md'

"""
    module MultinomialSeries

Compute multinomial coefficients and natively iterate over multinomial expansions in Julia.

Exported functions are:

    multinomial(m,n)

    eachmultinomial(m,n)

"""
module MultinomialSeries

export multinomial, eachmultinomial

import Base: eltype, iterate, length,
             IteratorSize, IteratorEltype

using StaticArrays: MVector

struct MultinomialIterator{T,N,P} end

eachmultinomial(dims, order) = MultinomialIterator{Int,dims,order}()
eachmultinomial(::Type{T}, dims, order) where T<:Integer = MultinomialIterator{T,dims,order}()

multinomial(k::NTuple{N,T}) where {N,T} = T(factorial(sum(k)) ÷ prod(factorial.(k)))

# See https://en.wikipedia.org/wiki/Multinomial_theorem?section=5#Number_of_multinomial_coefficients
# here: P ≡ m, N ≡ m
Base.length(::MultinomialIterator{T,N,P}) where {T,N,P} = binomial(P+N-1, N-1)

Base.eltype(::MultinomialIterator{T,N,P}) where {T,N,P} = NTuple{N,T}

Base.IteratorSize(::MultinomialIterator) = Base.HasLength()
Base.IteratorEltype(::MultinomialIterator) = Base.HasEltype()

# iterate impementation note:
# State is a MVector of k₁...kₘ (m=N), plus a pointer to the current active iteration element in k.
# The element data type returned is a NTuple{N,T}, which is simply pulled out from the MVector.
function Base.iterate(
        ::MultinomialIterator{T,N,P}
    ) where {T,N,P}

    local k::NTuple{N,T} = (T(P), (zero(T) for n in 1:N-1)...)
    return ( k,                      # element
            (MVector{N,T}(k), 1) )   # state

end

function Base.iterate(
        ::MultinomialIterator{T,N,P},
        state
    ) where {T,N,P}

    k, ptr = state

    while @inbounds k[ptr] == 0
        ptr -= 1
        P == 0 && ptr == 0 && @goto finish  # only relevant if order == 0
    end

    if ptr == N  # ==length(k)
        while true
            ptr == 1 && @goto finish        # done !
            ptr -= 1
            @inbounds k[ptr] == 0 || break
        end
        @inbounds balls = sum(k[ptr+1:end])
        @inbounds k[ptr+1:end] .= 0         # reset everything after current position to zero
        @inbounds k[ptr+1] += balls
    end

    if @inbounds k[ptr] > 0
        @inbounds k[ptr] -= 1
        @inbounds k[ptr += 1] += 1
    end

    return (k.data, (k, ptr))

    @label finish
    return nothing

end  # function iterate

end  # module MultinomialSeries

# end of file


using Distributed

@everywhere function calc_pi(n::Integer)
   x = rand(n)
   y = rand(n)
   num_in_unit_circle = sum(x.*x.+y.*y .< 1)
   num_in_unit_circle * 4/n
end

n = 100_000;
mapreduce(calc_pi, +, Iterators.repeated(1, nworkers()) ) / nworkers()
@time pi_estimate = mapreduce(calc_pi, +, Iterators.repeated(n, nworkers()) ) / nworkers()
println("# After ", n, " itterations on each of ", nworkers(), " workers, estimated pi to be...")
println(pi_estimate)


#=
Copyright (c) 2015, Intel Corporation
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice,
  this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
=#

module TestLogisticRegression
using ParallelAccelerator

#ParallelAccelerator.set_debug_level(3)
#ParallelAccelerator.DomainIR.set_debug_level(3)
#ParallelAccelerator.CGen.set_debug_level(3)

@acc function logistic_regression(iterations::Int64)
    D = 3  # Number of dimensions
    N = 4
    # synthetic but deterministic initial values
    labels = reshape([1.,.9,.4,.8],1,N)
    points = [1. 2. 3. 4.; 4. 3. 2. 1.; 4. 5. 6. 7.]
    w = reshape(2.0.*ones(D)-1.3,1,D)

    for i in 1:iterations
       w -= ((1.0./(1.0.+exp(-labels.*(w*points))).-1.0).*labels)*points'
    end
    w
end

end

using Base.Test
println("Testing logistic regression...")
@test_approx_eq TestLogisticRegression.logistic_regression(10) [0.9461770317849691 0.8827825771001324 1.2035527971160302]
println("Done testing logistic regression...")


using LinearAlgebra
using CVChannel
using Test

#This script looks at the werner-holevo channel communication value for d=3
#It uses the general linear program to generate the constraints
println("\n Here we will see the Werner-Holevo channels are multiplicative")
println("for p less than 0.3, by searching over p in {0,0.01,...,0.35}.")

@testset "Non-multiplicativity of WH channels for p<0.35" begin
    λ_vals = [0:0.001:0.35;];
    ctr = 1;
    results = zeros(length(λ_vals),5)
    println("\nBeginning to get the data.")
    for λ_id in λ_vals
        if ctr == Int(round(length(λ_vals)-1)/2)
            println("Now halfway through.")
        end
        #Gets single copy cv
        A,B,g,a = generalWHLPConstraints(1,3,λ_id*ones(1))
        cv1, v1 =wernerHolevoCVPPT(1,3,A,B,g,a)
        #Gets parallely copy cv
        A,B,g,a = generalWHLPConstraints(2,3,λ_id*ones(2))
        cv2, v2 =wernerHolevoCVPPT(2,3,A,B,g,a)

        results[ctr,:] = [λ_id, cv1, cv2, cv1^2, cv2-cv1^2]
        ctr = ctr + 1
    end
    printed_30 = false
    print_array = zeros(1,5)
    for i in [1:Int(round(length(λ_vals)/10)):length(λ_vals);]
        if results[i,1]>0.3 && printed_30 == false
            print_array = [print_array; results[301,:]']
            printed_30 = true
        end
        print_array = [print_array; results[i,:]']
    end
    print_array = print_array[2:end,:]
    println("\n Here is a subset of the data. Column labels at bottom.")
    show(stdout, "text/plain", print_array)
    println("\n λ      cv(N)    cv(N^2)   diff")
    println("\nThus by looking at the diff column, we see what we were verifying.")
    info_vec = Vector{Union{Nothing,String}}(nothing, size(results)[1] + 1)
    info_vec[1] = "INFO:"
    info_vec[2] = "Generated by werner-holevo-non-multiplicativity.jl"
    header = ["λ" "cv(N)" "cv(N^2)" "cv(N)^2" "non-mult"]

    println("Please name the file you'd like to write the results to: \n")
    data_to_save = hcat(vcat(header,results),info_vec)
    file_name = readline()
    file_to_open = string(file_name,".csv")
    writedlm(file_to_open, data_to_save, ',')

    @test all(result -> result > 0 || isapprox(result, 0, atol=2e-6), results[:,5])
end


function findRequiredIdxs(A::LagrangeInterpolation, t)
  idxs = sortperm(A.t,by=x->abs(t-x))
  idxs[1:(A.n+1)]
end

function spline_coefficients(n, d, k, u::Number)
  N = zeros(n)
  if u == k[1]
    N[1] = one(u)
  elseif u == k[end]
    N[end] = one(u)
  else
    i = findfirst(x->x>u,k) - 1
    N[i] = one(u)
    for deg = 1:d
      N[i-deg] = (k[i+1]-u)/(k[i+1]-k[i-deg+1]) * N[i-deg+1]
      for j = (i-deg+1):(i-1)
        N[j] = (u-k[j])/(k[j+deg]-k[j]) * N[j] + (k[j+deg+1]-u)/(k[j+deg+1]-k[j+1]) * N[j+1]
      end
      N[i] = (u-k[i])/(k[i+deg]-k[i]) * N[i]
    end
  end
  N
end

function spline_coefficients(n, d, k, u::AbstractVector)
  N = zeros(eltype(u),n,n)
  for i = 1:n
    N[i,:] .= spline_coefficients(n,d,k,u[i])
  end
  N
end

function weibull_fun(x,p)
  ans = copy(x)
  for i = 1:length(x)
    if x[i] >= 0
      ans[i] = p[1] * (1 - exp(-1 * (x[i]/p[2])^p[3]))
    else
      ans[i] = 0.0
    end
  end
  ans
end

# helper functions to get t_max and c_max (OneCompartmentPK)
function OneCompartmentPK_tmax(A)
  p = A.c_f.param
  2.303 * log(10.0,p[1]/p[4]) / (p[1] - p[4])
end

function OneCompartmentPK_cmax(A)
  p = A.c_f.param
  t = OneCompartmentPK_tmax(A)
  p[1] * p[2] * p[3] * (exp(-p[4]*t) - exp(-p[1]*t)) / (p[5] * (p[1] - p[4]))
end


###
  Name     : c10_31_timeUsed.jl
  Book     : Hands-on Data Science with Anaconda )
  Publisher: Packt Publishing Ltd.
  Author   : Yuxing Yan and James Yan
  Date     : 4/24/2018
  email    : yany@canisius.edu
             paulyxy@hotmail.com
###

using QuantEcon
nodes, weights = qnwlege(65, -2pi, 2pi);
integral = do_quad(x -> cos(x), nodes, weights)
@time quadgk(x -> cos.(x), -2pi, 2pi)
@time do_quad(x -> cos.(x), nodes, weights)


using Test
using Statistics
using LESbrary.Utils
using LESbrary.Utils: ∂z, ∂t

@testset "InterpolatedProfile" begin

    # interpolation

    zs = 0:5:50
    Δz = zs.step
    N = length(zs)
    data = randn(N)

    prof = InterpolatedProfile(data, zs, Δz, zs[end])

    @test prof(0) == prof.data[1]
    @test prof(5) == prof.data[2]

    @test prof(12.5) ≈ (prof.data[3] + prof.data[4]) / 2

    # derivatives

    zs = 0:5:50
    Δz = zs.step
    N = length(zs)

    f(z) = z^2
    data = f.(zs)

    prof = InterpolatedProfile(data, zs, Δz, zs[end])

    @test ∂z(prof, 2) ≈ (f(5) - f(0)) / Δz
    @test ∂z(prof, 7.4) ≈ (f(10) - f(5)) / Δz
    @test ∂z(prof, 11.8) ≈ (f(15) - f(10)) / Δz

    @test ∂z(prof, 2) == ∂z(prof, 4)
    @test ∂z(prof, 7.4) == ∂z(prof, 9.99)
    @test ∂z(prof, 11.8) == ∂z(prof, 10.01)
end

@testset "InterpolatedProfileTimeSeries" begin

    # interpolation

    zs = 0:5:50
    ts = 0:60:600
    Δz = zs.step
    Δt = ts.step
    Nz = length(zs)
    Nt = length(ts)
    data = randn(Nz, Nt)

    prof = InterpolatedProfileTimeSeries(data, zs, ts, Δz, Δt, zs[end], ts[end])

    @test prof(0, 0) == prof.data[1, 1]
    @test prof(5, 0) == prof.data[2, 1]
    @test prof(0, 60) == prof.data[1, 2]
    @test prof(5, 60) == prof.data[2, 2]

    @test prof(27.5, 330) ≈ mean(prof.data[6:7, 6:7])

    # derivatives

    zs = 0:5:50
    ts = 0:60:600
    Δz = zs.step
    Δt = ts.step
    Nz = length(zs)
    Nt = length(ts)

    f(z, t) = 3z^2 + 0.1t^2
    data = f.(zs, ts')

    prof = InterpolatedProfileTimeSeries(data, zs, ts, Δz, Δt, zs[end], ts[end])

    @test ∂z(prof, 3, 0) ≈ (f(5, 0) - f(0, 0)) / Δz
    @test ∂z(prof, 7.5, 10) ≈ (f(10, 10) - f(5, 10)) / Δz
    @test ∂z(prof, 10.01, 420.69) ≈ (f(15, 420.69) - f(10, 420.69)) / Δz

    @test ∂t(prof, 0, 45) ≈ (f(0, 60) - f(0, 0)) / Δt
    @test ∂t(prof, 6, 100) ≈ (f(6, 120) - f(6, 60)) / Δt
    @test ∂t(prof, 22.2, 310) ≈ (f(22.2, 360) - f(22.2, 300)) / Δt
end


export Linear
export linear

struct Linear
    W::Var
    b::Var
end

"""
    Linear(T::Type, insize::Int, outsize::Int, [init_w=Xavier()], [init_b=Fill(0)])

Linear function (a.k.a. affine transformation).

```math
f(x) = w^{T}x + b
```
where ``w`` is a weight matrix and ``b`` is a bias vector.

```julia
T = Float32
x = Var(rand(T,10,5))
f = Linear(T,10,7)
y = f(x)
```
"""
function Linear(::Type{T}, insize::Int, outsize::Int;
    init_W=Xavier(), init_b=Fill(0)) where T

    W = init_W(T, insize, outsize)
    b = init_b(T, outsize)
    Linear(parameter(W), parameter(b))
end
(f::Linear)(x) = linear(x, f.W, f.b)

function linear(x::Var, W::Var, b::Var)
    T = eltype(x)
    if ndims(x) == 1
        ydata = gemv('T', W.data, x.data)
        addto!(ydata, b.data)
    elseif ndims(x) == 2
        ydata = gemm('T', 'N', W.data, x.data)
        broadcast_addto!(ydata, b.data)
    else
        throw("Invalid ndims of x: $(ndims(x))")
    end
    Var(ydata, ∇linear!, (x,W,b))
end
linear(x::Node, W, b) = Node(linear, (x,W,b))

function ∇linear!(y::Var, x::Var, W::Var, b::Var)
    T = eltype(x)
    if ndims(x) == 1
        gy = reshape(y.grad, length(y.grad), 1)
        xdata = reshape(x.data, length(x.data), 1)
        isnothing(W.grad) || gemm!('N', 'T', T(1), xdata, gy, T(1), W.grad)
        isnothing(x.grad) || gemv!('N', T(1), W.data, y.grad, T(1), x.grad)
        isnothing(b.grad) || addto!(b.grad, y.grad)
    elseif ndims(x) == 2
        isnothing(x.grad) || gemm!('N', 'N', T(1), W.data, y.grad, T(1), x.grad)
        isnothing(W.grad) || gemm!('N', 'T', T(1), x.data, y.grad, T(1), W.grad)
        isnothing(b.grad) || addto!(b.grad, sum(y.grad,dims=2))
    else
        throw("Invalid ndims of x: $(ndims(x))")
    end
end


using ImageFiltering

# Part 1

const nhood_filter = centered( [ 1 1 1; 1 0 1; 1 1 1 ])

get_neighbors( I ) = imfilter( I , nhood_filter, Fill(0) )

function fill_seats( occupied, seats )
    neighbors = get_neighbors( occupied )
    occupied = ( neighbors .== 0 ) .* seats .| occupied
    neighbors = get_neighbors( occupied )
    occupied = ( neighbors .< 4 ) .* occupied
    return occupied
end

function fill_seats!( occupied, seats )
    neighbors = get_neighbors( occupied )
    occupied .= ( neighbors .== 0 ) .* seats .| occupied
    neighbors = get_neighbors( occupied )
    occupied .= ( neighbors .< 4 ) .* occupied
    return occupied
end

function read_input( filename )
    lines = readlines(filename)
    seats = hcat([Vector{Char}(l) .== 'L' for l in lines]...)
    return copy( transpose(seats) )
end

const seats = read_input("part_1\\input.txt")

# Part 2

const graph_neighbors = findall( nhood_filter .== 1 )

const ci_seats = CartesianIndices( seats )

const one_padded_seats = padarray( seats, Fill(1, ize(seats) ) )

const min_seat = ci_seats[1]
const max_seat = ci_seats[end]

function valid_neighbor( n )
    all( (n.I .>= min_seat.I) .& (n.I .<= max_seat.I) )
end
function filter_neighbors!( neighbors )
    filter!( valid_neighbor , neighbors )
end

function get_neighboring_seats( current, seats = one_padded_seats)
    n = 1
    neighbors = CartesianIndex{2}[]
    unit_neighbors = graph_neighbors
    current_neighbors = (current,) .+ graph_neighbors
    while( !isempty(current_neighbors) )
        neighbor_seats = seats[ current_neighbors ]
        append!( neighbors, current_neighbors[ neighbor_seats] )
        unit_neighbors = unit_neighbors[ .!neighbor_seats ]
        n = n + 1
        current_neighbors = (current,) .+ (unit_neighbors .* n)
    end
    filter_neighbors!( neighbors )
end

const seat_neighbors = [get_neighboring_seats( c, one_padded_seats ) for c in ci[ seats ]]

function get_neighbors_part_2( occupied )
    neighbors = zeros( Int64, size( occupied ) )
    neighbors[ seats ] = [sum( occupied[ s ] ) for s in seat_neighbors]
    return neighbors
end

function fill_seats_part_2!( occupied, seats )
    neighbors = get_neighbors_part_2( occupied )
    occupied .= ( neighbors .== 0 ) .* seats .| occupied
    neighbors = get_neighbors_part_2( occupied )
    occupied .= ( neighbors .< 5 ) .* occupied
    return occupied
end


using Distributions
using Random
using LinearAlgebra
using CSV

mutable struct myPoint
    x::Float64
    y::Float64
    close::Int16
    far::Int16
    row_num::Int16
    col_num::Int16
    key::Int64
end

mutable struct myGrid
    points::Array{myPoint}
end

mutable struct Centroid
    x::Float64
    y::Float64
    close_num::Int16
end

function assignPoint(i::Int, x::Float64, y::Float64, close::Int, far::Int, grid_size::Float64)
    row = floor(Int8, x/grid_size)
    col = floor(Int8, y/grid_size)
    max_num =  ceil(107 / grid_size)
    key = Int(row*max_num + col)
    myPoint(x, y, close, far, row, col, key)
end

function myGrid()
    myGrid([])
end

groupby(f, list::Array ) = begin
    foldl(list; init = Dict()) do dict, v
        push!(get!(dict, f(v), []), v)
        dict
    end
end

function get_point_key(p::myPoint)
    p.key
end

function get_centroid(points)
    x_sum = 0
    y_sum = 0
    count = 0
    close_num = 0
    for p in points
        x_sum += p.x *p.close
        y_sum += p.y *p.close
        close_num += p.close
        count += 1
    end
    Centroid(x_sum/close_num, y_sum/close_num, floor(close_num/count))
end

function sample_grid(one_centroid::Centroid, grid_size::Float64)
    r_tmp = rand(Uniform(0, grid_size/sqrt(pi)), one_centroid.close_num)
    theta_tmp = rand(Uniform(0, 2pi), one_centroid.close_num)
    X_tmp = one_centroid.x .+ cos.(theta_tmp) .* r_tmp
    Y_tmp = one_centroid.y .+ sin.(theta_tmp) .* r_tmp
    [X_tmp Y_tmp]
end

function generate_samples(project_path, filename)
    grid_size = 1.25
    arr = CSV.File(project_path * "/" * filename)
    grid = myGrid()
    for (i, row) in enumerate(arr)
        if row.Close == 0
            continue
        end
        point = assignPoint(i, row.X, row.Y, row.Close, row.Far, grid_size)
        push!(grid.points, point)
    end

    dict_1 = groupby(get_point_key, grid.points)
    centroids = map(get_centroid, collect(values(dict_1)))
    array_list = map(sample_grid, centroids, ones(length(centroids)) .+ grid_size)
    mapconcat2(v) = vcat((v)...)
    samples = mapconcat2(array_list)
    return samples;
end

function mlt_pdf(data, mu, cov)
    try
        distribution = MvNormal(mu, Matrix(Hermitian(cov)));
        res = pdf(distribution, data);
        if isnan(res)
            return 1e6;
        end
        return res;
    catch
        return 1e-6;
    end
end

function k_means(k, tol, max_iter, data)
    centroids = data[rand(1:size(data)[1], k), :];
    for i = 1:max_iter
        classifications = Dict();
        for j = 1:k
            classifications[j] = [0 0];
        end
        for j = 1:length(data[:,1])
            distances = [];
            for jj = 1:k
                push!(distances, norm(data[j,:] .- centroids[jj, :]));
            end
            classifications[mapslices(argmin, distances, dims=1)[1]] = [classifications[mapslices(argmin, distances, dims=1)[1]]; reshape(data[j,:],(1,2))];
        end

        for j = 1:k
            classifications[j] = classifications[j][2:length(classifications[j][:,1]), :]
        end
        prev_centroids = centroids;

        for j = 1:k
            if (size(classifications[j])[1] != 0)
                centroids[j,:] = mean(classifications[j], dims = 1);
            end
        end

        optimized = true;
        for j in 1:length(centroids[:,1])
            original_centroid = prev_centroids[j];
            current_centroid = centroids[j];
            if abs(sum((current_centroid .- original_centroid) ./ original_centroid .*100.0)) > tol
                    optimized = false;
            end
        end
        if optimized
            break
        end
    end
    return centroids
end

function gmm_em(max_iterations, num_clusters, data)
    pi_ = ones(num_clusters)/num_clusters;
    mu = sort(k_means(num_clusters, 0.001, 200, data), dims = 1);
    for i = 1:2
        mu = mu .+ sort(k_means(num_clusters, 0.001, 200, data), dims = 1);
    end
    mu = mu ./ 3;
    cov = []
    for i = 1:num_clusters
        push!(cov, [[5. 0.]; [0. 5.]]);
    end
    ric = zeros((length(data[:,1]), num_clusters));
    reg_cov = 1e-6 .* [[5. 0.];[0. 5.]];
    log_likelihoods = [];
    for i = 1:max_iterations
        ric = zeros((length(data[:,1]), num_clusters));

        for j = 1:num_clusters
            covc = cov[j] .+ reg_cov;
            mn = [0];
            for num = 1:length(data[:,1])
                mn = [mn; [mlt_pdf(data[num, :], mu[j,:], covc)]];
            end

            mn = mn[2:length(mn)];
            ric[:, j] = pi_[j] .* mn;
        end

        for j = 1:length(data[:,1])
            ric[j, :] = ric[j, :] ./ sum(ric[j, :]);
        end

        mc = sum(ric, dims=1);
        pi_ = mc ./ sum(mc);
        mu = transpose((transpose(data) * ric) ./ [mc; mc]);

        cov = []
        for j = 1:num_clusters
            var1 = 1/mc[j] * dot(ric[:, j], (data[:,1] .- mu[j, 1]).^2);
            var2 = 1/mc[j] * dot(ric[:, j], (data[:,2] .- mu[j, 2]).^2);
            push!(cov, [[var1 0.]; [0. var2]]);
        end

        likelihood_sum = 0;
        for j = 1:num_clusters
            mn = [0];
            for num = 1:length(data[:,1])
                mn = [mn; [mlt_pdf(data[num,:], mu[j,:], cov[j] + reg_cov)]];
            end
            mn = mn[2:length(mn)];
            likelihood_sum = likelihood_sum .+ ric[:,j].*mn;
        end
        push!(log_likelihoods, sum(log.(likelihood_sum)));
        if i >= 20 && abs((log_likelihoods[i] - log_likelihoods[i-1])/log_likelihoods[i]) < 0.01
            break;
        end
    end

    likelihood = log_likelihoods[length(log_likelihoods)];
    bic = - 2*likelihood + log(size(data)[1])*(15*num_clusters);
    return pi_, mu, cov, ric, log_likelihoods, bic;
end

function main()
    # path = "/Users/clara/Desktop"
    # f_name = "data.csv"
    path = "D:/JI/2020 summer/VE414 Bayesian Analysis/Project/zzp";
    f_name = "data_proj_414.csv";
    data = generate_samples(path, f_name);
    println("----Samples are generated successfully----");
    max_iterations = 50;

    println("-----------Start training model-----------");
    bics = [0];
    for k=1:30
        pi_, mu, cov, ric, log_likelihoods, bic = gmm_em(max_iterations , k, data);
        pi_, mu, cov, ric, log_likelihoods, bic_ = gmm_em(max_iterations , k, data);
        bics = [bics;minimum([bic, bic_])];
        println("--Clusters: $(k)  BIC: $(mean([bic, bic_]))--");
    end
    bics = bics[2:length(bics[:,1]),:];
    println("-----------Stop training model------------");

    original_data = CSV.read(path*"/"*f_name);
    positions = [original_data["X"] original_data["Y"]];
    area_grid = zeros((107, 107));
    for i = 1:size(positions)[1]
        area_grid[Int32(positions[i, 1] - positions[i, 1] % 1) + 1, Int32(positions[i, 2] - positions[i, 2] % 1) + 1] = 1
    end
    area = sum(area_grid);
    lamb =  (mapslices(argmin,bics[:,1],dims=1)[1] * 107 * 107 / area);

    total = 0;
    mode_ = pdf(Poisson(lamb), 0);

    for i = 1:2*round(lamb)
        if pdf(Poisson(lamb), i) > mode_
            mode_ = pdf(Poisson(lamb), i);
            total = i;
        end
    end
    println("-------Total number of Jiulings $(total)------");
    return total;
end


# Angular quadrature defined on the unit sphere octant in the upper right, closest to the viewer.
# The angles and weights are transformed to the other octants by symmetry.
#     +----+----+
#    /    /    /|
#   +----+----+ |
#  /    /    /| +
# +----+----+ |/|
# |    |this| + |
# |    | one|/| +
# +----+----+ |/
# |    |    | +
# |    |    |/
# +----+----+
#
# The spherical coordinates are defined in the following manner
# Ω̂ = (Ω_i, Ω_j, Ω_k) = (cos(θ),  sin(θ)cos(γ),  sin(θ)sin(γ))
#                     = (     μ, √(1-μ²)cos(γ), √(1-μ²)sin(γ))
#
#        j
#        ^
#        |   θ is the polar angle about the i-axis (x-direction)
#        |   γ is the azimuthal angle in the j-k plane, from the j-axis
#        |
#        |
#        |
#       /|
#      (γ|
#       \|--------------------> i
#       / \θ)
#      /   \
#     /     \
#    /       \ Ω̂
#   /         v
#  𝘷
#  k

# TODO: Function to convert product into general quadrature
#struct GeneralAngularQuadrature{T <: AbstractFloat} <: AngularQuadrature
#    Ω̂::Tuple{Point{T}} # Points on the unit sphere satisfying θ ∈ (0, π/2), γ ∈ (0, π)
#    w::Tuple{T} # Weights for each point
#end

struct ProductAngularQuadrature{nγ, nθ} <: AngularQuadrature
    γ::SVector{nγ, Float64}    # Azimuthal angles, γ ∈ (0, π)
    w_γ::SVector{nγ, Float64}  # Weights for the azimuthal angles
    θ::SVector{nθ, Float64}    # Polar angles, θ ∈ (0, π/2)
    w_θ::SVector{nθ, Float64}  # Weights for the polar angles
end

function generate_chebyshev_angular_quadrature(M::Int64)
    # A Chebyshev-type quadrature for a given weight function is a quadrature formula with equal
    # weights. This function produces evenly spaced angles with equal weights.
    angles = [(π*(2m-1)/(4M)) for m = 1:M]
    weights = zeros(M) .+ 1/M
    return angles, weights
end

# nγ and nθ are azimuthal and polar angles per octant
function generate_angular_quadrature(quadrature_type::String, nγ::Int, nθ::Int)
    if quadrature_type == "Chebyshev-Chebyshev"
        (azi_angles, azi_weights) = generate_chebyshev_angular_quadrature(nγ)
        (pol_angles, pol_weights) = generate_chebyshev_angular_quadrature(nθ)
        append!(azi_angles, reverse(π .- azi_angles))
        azi_weights = azi_weights./2
        append!(azi_weights, azi_weights)
        quadrature = ProductAngularQuadrature(SVector{2nγ}(azi_angles), SVector{2nγ}(azi_weights),
                                              SVector{nθ}(pol_angles), SVector{nθ}(pol_weights))
    else
        @error "Unsupported quadrature type"
    end
    return quadrature
end


export
    aberration_low,
    apparent_longitude_low,
    equation_time,
    longitude_radius_low,
    rectangular_md,
    sun_dimension3

"""
    sun_dimension3(jd)

Geocentric solar position and radius, low precision.

Return geocentric ecliptic longitude, latitude and radius.

Parameters:
    jd : Julian Day in dynamical time

Returns:
    longitude in radians
    latitude in radians
    radius in au
"""
function sun_dimension3(jd)
    (L, B, R) = vsop87d_dimension(jd, "Earth")
    return (mod2pi(L + pi), -B, R) # was return (L, B, R)
end

#
# Constant terms
#

_kL0 = Array([deg2rad(280.46646),  deg2rad(36000.76983),  deg2rad( 0.0003032)])
_kM  = Array([deg2rad(357.52911),  deg2rad(35999.05029),  deg2rad(-0.0001537)])
_kC  = Array([deg2rad(  1.914602), deg2rad(   -0.004817), deg2rad(-0.000014)])

_ck3 = deg2rad( 0.019993)
_ck4 = deg2rad(-0.000101)
_ck5 = deg2rad( 0.000289)

"""
    longitude_radius_low(jd)

Return geometric longitude and radius vector.
Low precision. The longitude is accurate to 0.01 degree.
The latitude should be presumed to be 0.0. [Meeus-1998: equations 25.2 through
25.5, pages 165-166

Parameters:
  jd : Julian Day in dynamical time

Returns:
  longitude in radians
  radius in au
"""
function longitude_radius_low(jd)
    T = jd_to_jcent(jd) # julian centuries from J2000.0
    L0 = polynomial(_kL0, T) # geometric mean longitude of sun for mean equinox
    M = polynomial(_kM, T) # mean anomaly of sun
    e1 = polynomial([0.016708634, -0.000042037, -0.0000001267], T) # eccentricity of Earth's orbit
    C = (polynomial(_kC, T) * sin(M))
        + ((_ck3 - _ck4 * T) * sin(2 * M))
        + (_ck5 * sin(3 * M)) # equation of the center
    L = mod2pi(L0 + C) # true longitude
    v = M + C # true anomaly
    R = 1.000001018 * (1 - e1 * e1) / (1 + e1 * cos(v)) # radius vector
    return (L, R)
end

# Constant terms

_lk0 = deg2rad(125.04)
_lk1 = deg2rad(1934.136)
_lk2 = deg2rad(0.00569)
_lk3 = deg2rad(0.00478)

"""
    apparent_longitude_low(jd, L)

Correct the geometric longitude for nutation and aberration.
Low precision. [Meeus-1998: pg 164]

Parameters:
    jd : Julian Day in dynamical time
    L : longitude in radians

Returns:
    corrected longitude in radians
"""
function apparent_longitude_low(jd, L)
    T = jd_to_jcent(jd)
    omega = _lk0 - _lk1 * T
    return mod2pi(L - _lk2 - _lk3 * sin(omega))
end

# Constant terms

_lk4 = deg2rad(dms_to_d(0, 0, 20.4898))

"""
    aberration_low(R)

Correct for aberration; low precision, but good enough for most uses.

[Meeus-1998: pg 164]

Parameters:
    R : radius in au

Returns:
    correction in radians
"""
function aberration_low(R)
    return -_lk4 / R
end

"""
    rectangular_md(jd)

Returns the rectangular coordinates of the sun, relative to the mean equinox of the day
"""
function rectangular_md(jd)
    L, B, R = sun_dimension3(jd)
    L, B = vsop_to_fk5(jd, L, B)
    e1 = obliquity_high(jd)
    X = R * cos(B) * cos(L)
    Y = R * (cos(B)*sin(L)*cos(e1) - sin(B)*sin(e1))
    Z = R * (cos(B)*sin(L)*sin(e1) + sin(B)*cos(e1))
    return (X, Y, Z)
end

"""
    equation_time(jd)

Returns the equation of time at JD

parameters:
    jd: julian day in dynamic time

Returns:
    Equation of time in minutes, can be positive or negative
"""
function equation_time(jd)
    tau = jd_to_jcent(jd)/10
    _p = [280.4664567, 360007.6982779, 0.03032028, 1/49931, -1/15300, -1/2000000]
    L0 = mod2pi(deg2rad(polynomial(_p, tau)))
    (L, B, R)  = sun_dimension3(jd)
    (L, B)     = vsop_to_fk5(jd, L, B)
    deltaPsi   = nut_in_lon(jd)
    epsilon    = true_obliquity(jd)
    asc,decl   = ecl_to_equ(L, B, epsilon)
    eqt = L0 - deg2rad(0.0057183) - asc + deltaPsi * cos(epsilon)
    if eqt > pi/2
        eqt = -(2pi - eqt)
    end
    return eqt/2pi * 1440
end


using GR
using SpecialFunctions

x = 0:0.1:20
j0, j1, j2, j3 = [besselj.(order, x) for order in 0:3]

plot(x, j0, x, j1, x, j2, x, j3,
     labels=["J_0", "J_1", "J_2", "J_3"], location=11)


function abdiv(a::R, b::R) where {R <: AbstractFloat}
    if a == zero(R)
        return convert(R, Inf)
    else
        return b/a
    end
end

function solve_quadratic(a::R, bo2::R, c::R) where {R <: AbstractFloat}
    #bo2 = b over 2
    in_sqrt = bo2^2 - a*c
    if in_sqrt > zero(R)
        return (-bo2 + sqrt(in_sqrt))/a
    else
        return zero(R)
    end
end

function solve_piecewise_quadratic(α1::R, β1::R, κ2x::R) where {R <: AbstractFloat}
    #solve_quadratic(α1^2, -α1*β1, β1^2-κ2x) =
    (sqrt(κ2x)+β1)/α1
end

function solve_piecewise_quadratic(α1::R, α2::R, β1::R, β2::R, κ2x::R) where {R <: AbstractFloat}
    a = α1^2+α2^2
    bo2 = -α1*β1-α2*β2
    c = β1^2+β2^2-κ2x
    τ1t = solve_quadratic(a, bo2, c)
    ∇τ1t1 = α1*τ1t-β1
    ∇τ1t2 = α2*τ1t-β2
    if (∇τ1t1 <= 0) || (∇τ1t2 <= 0)
        tr1 = abdiv(α1, β1)
        tr2 = abdiv(α2, β2)
        if tr1 <= tr2
            τ1t = solve_piecewise_quadratic(α1, β1, κ2x)
        else
            τ1t = solve_piecewise_quadratic(α2, β2, κ2x)
        end
    end
    τ1t
end

function solve_piecewise_quadratic(α1::R, α2::R, α3::R, β1::R, β2::R, β3::R, κ2x::R) where {R <: AbstractFloat}
    a = α1^2+α2^2+α3^2
    bo2 = -α1*β1-α2*β2-α3*β3
    c = β1^2+β2^2+β3^2-κ2x
    τ1t = solve_quadratic(a, bo2, c)
    ∇τ1t1 = α1*τ1t-β1
    ∇τ1t2 = α2*τ1t-β2
    ∇τ1t3 = α3*τ1t-β3
    if (∇τ1t1 <= 0) || (∇τ1t2 <= 0) || (∇τ1t3 <= 0)
        tr1 = abdiv(α1, β1)
        tr2 = abdiv(α2, β2)
        tr3 = abdiv(α3, β3)
        if tr1 <= tr2
            if tr2 <= tr3
                τ1t = solve_piecewise_quadratic(α1, α2, β1, β2, κ2x)
            else
                τ1t = solve_piecewise_quadratic(α1, α3, β1, β3, κ2x)
            end
        else
            if tr1 <= tr3
                τ1t = solve_piecewise_quadratic(α2, α1, β2, β1, κ2x)
            else
                τ1t = solve_piecewise_quadratic(α2, α3, β2, β3, κ2x)
            end
        end
    end
    τ1t
end


# -----------------------------------------------
# Generalized Linear Models routines
# -----------------------------------------------

# using Optim
# include("distribtools.jl")
# include("types.jl")

function glmregress{T<:Number}(X::Array{T}, Y::Array{T}, regtype; fast=false)
    if !(typeof(regtype)<:GLMRegressionType)
        error("glmregress() - Incorrect GLMRegressionType specified")
    else
        if isdefined(regtype, :intercept)
            X = regtype.intercept ? addintercept(X) : nointercept(X)
        end
        fitted_beta, residuals, converged, iter = regtype(X, Y)
        if regtype.robust == false || typeof(regtype.robust) == BasicVariance
            variance = BasicVariance(X, residuals, length(fitted_beta))
        elseif typeof(regtype.robust) == HCEVariance
            variance = HCEVariance(X, residuals)
        end
        if fast
            return (fitted_beta, variance)
        else
            return glmfitresult(regtype, X, Y, fitted_beta, residuals, variance, regtype.robust, converged, iter)
        end
    end
end

# #########   LOGIT   #########

function (logit_glmfit::Logit)(X::Array{T}, Y::Array{T}) where {T<:Number}
    theta0 = ones(size(X)[2])
    objective(theta) = -logLike(X, Y, theta, Logistic) + 0.05*norm(theta)
    optimresult = optimize(objective, theta0, NewtonTrustRegion())
    fitted_beta = Optim.minimizer(optimresult)
    residuals = Y - Alistair.logistic(aprod(X, fitted_beta))
    return fitted_beta, residuals, Optim.converged(optimresult), Optim.iterations(optimresult)
end


function harmonic_mean(x...)
    sum = 0
    for i = 1:length(x)
        sum = 1/x[i] + sum
    end
    mean = length(x) / sum
    return mean
end


include("/home/jefter66/Workspace/lablib/src/calcs/Uncertainty.jl")

# Dados para calculo pela conservação de energia

# converti para o SI

# a massa é a soma das massas de cada peça
# m = 0.12125 + 0.4707 + 0.9238
# dm = 0.01*10^(-5) + 0.01*10^(-5) +  0.01*10^(-5)
# dm = 0.03*10^(-5)

global m = 1.51575;

global dm = 1e-5

global h = 0.467;
global dh = 1e-3;

global r = 6e-3;
global dr = 1e-4;

global g = 9.81;

global dt = 1e-2;

#=========================#
#  Conservação de energia #
#=========================#

function I(t)
    return ((g*(t^2))/(2*h) - 1)*m*r^2
end

function dI(t)
    termo1 = (2*r*dr*m + dm*r^2)*((g*t^2)/(2*h))
    termo2 = (g/2)*(((2*dt*t*h)+(dh*t^2))/h^2)*m*r^2
    termo2 = (2*r*dr*m + dm*r^2)
    return termo1 + termo2 - termo2
end

#==========================#
#  Pela geometria do corpo #
#==========================#

function Ic()
    raio = 6e-3
    M = 0.12125
    dM = 1e-5
    draio = 1e-4
    Ic = 0.5*(M)*(raio^2)
    dIc = raio*draio*M + 0.5*dM*(raio^2)
    println("Ic = " , Ic )
    println("ΔIc = ", dIc , "\n")
    return (Ic,dIc)
end

# id = Id()
# id[1] = valor da medida
# id[2] = valor da incerteza
function Id()
    raioInterno = 6e-4
    raioExterno = 6.25e-2

    M  = 0.4707
    dM = 1e-5
    draio = 1e-4

    Id = 0.5*M*(raioInterno^2 + raioExterno^2)
    dId = 0.5*dM*(raioInterno^2 + raioExterno^2) + M*(raioInterno*draio + raioExterno*draio)

 #  println("(1/2)*Δm*(r1^2 + r2^2) = ", dM*(raioInterno^2 + raioExterno^2))
 #  println("(1/2)*2*m*(r1*Δr + r2*Δr) = ", 0.5*2*M*(raioInterno*draio + raioExterno*draio))

    println("Id = ", Id)
    println("Δ Id = " , dId , "\n" )

    return (Id,dId)
end

function Ia()
    raioInterno = 0.0625
    raioExterno = 0.0760

    M  = 0.9238

    dM = 1e-5
    draio = 1e-4

    Ia = 0.5*M*(raioInterno^2 + raioExterno^2)
    dIa = 0.5*dM*(raioInterno^2 + raioExterno^2) + M*(raioInterno*draio + raioExterno*draio)

    println("Ia = ", Ia )
    println("Δ Ia = " , dIa , "\n"  )

    return (Ia,dIa)
end

function I()
    ia = Ia()
    ic = Ic()
    id = Id()
    println("I = Ia + Ic + Id \n I = " , ia[1] + ic[1] + id[1])
    println("\n ΔI = Δ Ia + ΔIc + ΔId \n Δ I= " , ia[2] + ic[2] + id[2])
end


mutable struct NearestNeighboursUCB <: AbstractPolicy
    t::Int64
    batch::Int64
    num_arms::Int64
    initial_batches::Int64
    data::BanditDataset
    Xa::Vector{Matrix{Float64}}
    ra::Vector{Matrix{Float64}}
    θ::Float64
    φ::Float64

    function NearestNeighboursUCB(
        d::Int64, num_arms::Int64, initial_batches::Int64; θ::Float64=1.0, φ::Float64=1.0
    )
        Xa = [Matrix{Float64}(undef, d, 0) for _ in 1:num_arms]
        ra = [Matrix{Float64}(undef, 1, 0) for _ in 1:num_arms]
        data = BanditDataset(d)
        return new(0, 0, num_arms, initial_batches, data, Xa, ra, θ, φ)
    end
end

function nearest_neighbours(x::AbstractVector, X::AbstractMatrix)
    if size(X, 1) != length(x)
        throw(DimensionMismatch("length of x should match size(X, 1)"))
    end
    n = size(X, 2)
    distances = zeros(n)
    @inbounds for i in 1:n
        distances[i] = sqrt(sum((X[:, i] .- x) .^ 2))
    end
    idx = sortperm(distances)
    dst = distances[idx]
    return idx, dst
end

function _choose_k(x::AbstractVector, X::AbstractMatrix, t::Int64, θ::Float64, φ::Float64)
    idx, dst = nearest_neighbours(x, X)
    U = zeros(length(idx))
    @inbounds for k in 1:length(idx)
        radius = dst[k]
        U[k] = sqrt(θ * log(t) / k) + φ * log(t) * radius
    end
    min_k = argmin(U)
    return idx[1:min_k], U[min_k]
end

function (pol::NearestNeighboursUCB)(X::AbstractMatrix)
    n = size(X, 2)
    actions = zeros(Int64, n)
    if pol.batch <= pol.initial_batches
        for i in 1:n
            actions[i] = (pol.t + i) % pol.num_arms + 1
        end
        return actions
    end

    ucb = zeros(pol.num_arms)
    for i in 1:n
        for a in 1:(pol.num_arms)
            idx, U = _choose_k(X[:, i], pol.Xa[a], pol.t + 1, pol.θ, pol.φ)
            ucb[a] = sum(pol.ra[a][idx]) / length(idx) + U
        end
        actions[i] = argmax(ucb)
    end
    return actions
end

function update!(
    pol::NearestNeighboursUCB,
    X::AbstractMatrix,
    a::AbstractVector{<:Int},
    r::AbstractMatrix,
)
    pol.t += size(X, 2)
    pol.batch += 1
    append_data!(pol.data, X, a, r)
    for i in unique(a)
        pol.Xa[i] = hcat(pol.Xa[i], X[:, a .== i])
        pol.ra[i] = hcat(pol.ra[i], r[:, a .== i])
    end
end


import PotentialUQ
using LinearAlgebra: I
using Random
println("Starting test of MAP method")
Random.seed!(1234);

n = 100
β = zeros(10)
A = 1e-1*randn(n, 10)
A[1:10, 1:10] += I(10)
b = randn(n)
β = A\b
σ = 1e-3 .* ones(n)
logσ = log.(σ)
snap = PotentialUQ.Potentials.SNAP(β, 0.9, 6, 1)
dsnap = PotentialUQ.SNAPQDistribution(snap, A, b, logσ)
println(dsnap( (β = β, σ = logσ)))
println( dsnap( (β = A\b, σ = logσ)) )

@time PotentialUQ.MAP(dsnap)
println(dsnap.x)

snap = PotentialUQ.Potentials.SNAP(β, 0.9, 6, 1)
dsnap = PotentialUQ.SNAPQDistribution(snap, A, b, logσ)
println(dsnap.x)
@time PotentialUQ.MAP(dsnap)

println("Solution ", A\b)
println("MAP Solution, Optimization β =  ")
show(stdout, "text/plain", dsnap.x[1])
println("MAP Solution, Optimization σ =  ")
show(stdout, "text/plain", exp.(dsnap.x[2]))
println(" ")

println("End of test.")


include("test_utils.jl")
include("test_metrics.jl")
include("test_simplices.jl")
include("test_cell_complex.jl")
include("test_mesh.jl")
include("test_operators.jl")
include("test_laplacian_rectangle.jl")


"""
    assigndist_betabinomial(n, α, β; trunc_lower = -Inf, trunc_upper = Inf)

Assign parameters to a beta binomial distribution with `n` trials and
shape parameters `α` and `β`, optionally truncating the distribution.
"""
function assigndist_betabinomial(n, α, β; trunc_lower = -Inf, trunc_upper = Inf)
    Truncated(BetaBinomial(n, α, β), trunc_lower, trunc_upper)
end

export assigndist_betabinomial


using StaticFFTs, StaticArrays, FFTW
using Test

# write your own tests here
@testset "FFT" begin
    sizes = [6,8,9,12,14,15,16,18,20,22,24,25,26,27,28,30,32]
    for s ∈ sizes
        x = @SVector randn(s)
        @test StaticFFTs.fft(x) ≈ FFTW.fft(Vector(x))
    end
end


using GeneralizedDynamicsFromData
using ModelingToolkit
using DataDrivenDiffEq
using LinearAlgebra

#### Setup parameters of the ODE, UDE system and training
parameters_one_stable = Float64[1.5, 1.5, 2.0, 2.0]
parameters_two_stable = Float64[3.5, 3.5, 2.0, 2.0]

parameters = parameters_one_stable

u₀ = Float64[2.0, 4.0]
tspan = (0.0f0, 15.0f0)
s = 0.1

loss_weights = Float64[1/2, 1/2]
max_iters = 2000

#### Setup functions for neural network and differential equations
UA = regression_model(
    2, # input_d
    1, # output_d
    16, # hidden_d
    1, # hidden_layers
    tanh, # nonlinearity
    Flux.glorot_normal # initialization
)

real_de!, univ_de = genetic_toggle_switch(parameters, UA)

#### solve the real ODE and create a target time series
real_prob = ODEProblem(real_de!, u₀, tspan)
real_solution = solve(real_prob, Tsit5(), saveat = s)
y = Array(real_solution)

#### set up the UDE problem and training of the neural network
θ₀ = initial_params(UA)
prob_nn = ODEProblem(univ_de, u₀, tspan, θ₀)

first_guess = concrete_solve(prob_nn, Tsit5(), u₀, θ₀, saveat=real_solution.t)
predict(θ) = Array(concrete_solve(prob_nn, Tsit5(), u₀, θ, saveat=real_solution.t))
loss(θ) = polar_loss(θ, y, loss_weights, predict)

#### train the UDE with ADAM
callback = CallbackLog(T=Float64)
opt = Flux.Optimiser(WeightDecay(1e-4), ExpDecay(1e-1,0.4,100,1e-4), ADAM())
res = DiffEqFlux.sciml_train(loss, θ₀, opt, cb=callback, maxiters=max_iters)

trained_parameters = res.minimizer
ŷ = predict(trained_parameters)
ŷ_derivs = UA(ŷ, trained_parameters)

@variables u[1:2]
b = polynomial_basis(u,3)
recp_polys = []
for i = 1:3
    push!(recp_polys, 1/(1+u[1]^i))
    push!(recp_polys, 1/(1+u[2]^i))
end
gb = [b...; recp_polys...]
basis = Basis(gb, u)

opt = SR3(Float64(1e-2), Float64(1e-1))
λ = exp10.(-7:0.1:5)
g(x) = x[1] > 1 ? Inf : norm(x, 2)

Ψ = SINDy(ŷ,
          ŷ_derivs,
          basis,
          λ,
          opt,
          g = g,
          maxiter = 50000,
          normalize = true,
          denoise = true,
          convergence_error = Float64(1e-10))
println(Ψ)
print_equations(Ψ)


## Special cases
## linear, quadratic, cubic

## return function name for special case: identity, solve_linear, solve_quadratic, solve_cubic
function special_case(ps::Vector{S}, T) where {S}
    deg = length(ps) - 1  # [p0, p1, ..., pn]
    if deg == 1
        hasmethod(solve_linear, (Vector{S}, Type{T}))    && return solve_linear
    elseif deg == 2
        hasmethod(solve_quadratic, (Vector{S}, Type{T})) && return solve_quadratic
    elseif deg == 3
        hasmethod(solve_cubic, (Vector{S}, Type{T}))     && return solve_cubic
#    elseif deg == 5
#        hasmethod(solve_quintic, (Vector{S}, Type{T}))   && return solve_quintic
    end

    return identity
end

## Assume ps = [p_0, p_1, ..., p_n] with  p_n \neq 0, but possibly p_0 = 0

## Linear
check_linear(ps) = length(ps) == 2 || throw(DomainError())
## Over C
function solve_linear(ps::Vector{Complex{T}}, ::Type{Over.CC{S}}) where {T <: Real, S}
    check_linear(ps)
    Complex{S}[-ps[1] / ps[2]]
end
solve_linear(ps::Vector{T}, ::Type{Over.CC{S}}) where {T <: Real, S} = solve_linear(complex.(ps,zeros(T,2)), Over.CC{S})

## Over R
function solve_linear(ps::Vector{T}, ::Type{Over.RR{S}}) where {T <: Real, S}
    check_linear(ps)
    S[-ps[1] / ps[2]]
end

## Over Q
function solve_linear(ps::Vector{T}, ::Type{Over.QQ{S}}) where {T <: Integer, S}
    check_linear(ps)
    Rational{S}[-ps[1] // ps[2]]
end
function solve_linear(ps::Vector{Rational{T}}, ::Type{Over.QQ{S}}) where {T <: Integer, S}
    check_linear(ps)
    Rational{S}[-ps[1] // ps[2]]
end

## Over Z
function solve_linear(ps::Vector{T}, ::Type{Over.ZZ{S}}) where {T <: Integer, S}
    check_linear(ps)
    q,r = divrem(-ps[1], ps[2])
    if r == 0
        S[q]
    else
        zeros(S, 0)
    end
end

## Quadratic
check_quadratic(ps) = length(ps) == 3 || throw(DomainError())

## Kahan quadratic equation with fma
##  https://people.eecs.berkeley.edu/~wkahan/Qdrtcs.pdf

## solve ax^2 - 2bx + c
function qdrtc(a::T, b::T, c::T) where {T <: Real}
    # z1, z2 roots of ax^2 - 2bx + c
    d = discr(a,b,c)  # (b^2 - a*c), as 2 removes 4

    if d <= 0
        r = b/a  # real
        s = sqrt(-d)/a #imag
        return (r,s,r,-s)
    else
        r = sqrt(d) * (sign(b) + iszero(b)) + b
        return (r/a, zero(T), c/r, zero(T))
    end
end

## more work could be done here.
function discr(a::T,b::T,c::T) where {T}
    pie = 3.0 # depends on 53 or 64 bit...
    d = b*b - a*c
    e = b*b + a*c

    pie*abs(d) > e && return d

    p = b*b
    dp = muladd(b,b,-p)
    q = a*c
    dq = muladd(a,c,-q)

    (p-q) + (dp - dq)
end

function solve_quadratic(ps::Vector{T}, U::Type{Over.CC{S}}) where {T <: Real, S}
    check_quadratic(ps)
    iszero(ps[1]) && return vcat(zero(S), solve_linear(ps[2:end], U))

    r1,i1,r2,i2 = qdrtc(ps[3], -(0.5) * ps[2], ps[1])
    Complex{S}[complex(r1, i1), complex(r2, i2)]
end

function solve_quadratic(ps::Vector{T}, U::Type{Over.RR{S}}) where {T <: Real, S}
    check_quadratic(ps)
    iszero(ps[1]) && return vcat(zero(S), solve_linear(ps[2:end], U))

    r1,i1,r2,i2 = qdrtc(ps[3], -(0.5) * ps[2], ps[1])
    if iszero(i1)
        S[r1, r2]
    else
        zeros(S,0)
    end
end

## Cubic
check_cubic(ps) = length(ps) == 4 || throw(DomainError())

# C -> C
function solve_cubic(ps::Vector{Complex{T}}, U::Type{Over.CC{S}}) where {T <: Real, S}
    check_cubic(ps)
    iszero(ps[1]) && return solve_quadratic(ps[2:end], U)

    [PolynomialRoots.solve_cubic_eq(ps)...]
end

# R -> C XXX Can be faster
function solve_cubic(ps::Vector{T}, U::Type{Over.CC{S}}) where {T <: Real, S}
    check_cubic(ps)
    iszero(ps[1]) && return solve_quadratic(ps[2:end], U)

    [PolynomialRoots.solve_cubic_eq(complex.(ps, zeros(T,4)))...]
end

# R -> R
function solve_cubic(ps::Vector{T}, U::Type{Over.RR{S}}) where {T <: Real, S}
    check_cubic(ps)
    iszero(ps[1]) && return solve_quadratic(ps[2:end], U)

    ## https://en.wikipedia.org/wiki/Cubic_function#Algebraic_solution

    d,c,b,a = ps
    Delta = 18 * prod(ps) - 4b^3 * d + b^2 * c^2 - 4a*c^3 - 27 * a^2 * d^2
    Delta0 = b^2 - 3a*c
    Delta1 = 2b^3 - 9*a*b*c + 27*a^2 * d

    if Delta == 0 # one or two (with a double root)

        if iszero(Delta0)
            return ones(S,3) * (-b/(3a))
        else
            return S[(9a*d - b*c)/(2Delta0), (9a*d - b*c)/(2Delta0), (4a*b*c - 9a^2*d - b^3)/(a * Delta0)]
        end

    elseif Delta < 0 # one real root

        if iszero(Delta0)
            C = cbrt((0.5) * (2*sign(Delta1)*abs(Delta1)))

        else
            Delta2 = sqrt(-27*a^2*Delta)
            C = cbrt((0.5)* ( Delta1 + Delta2))

        end
        return S[-1/(3a)*(b+C+Delta0/C)]

    elseif Delta > 0 # 3 distinct real roots

        p, q = (3a*c  - b^2)/(3a^2), (2b^3 - 9a*b*c + 27a^2*d) / (27a^3)

        if iszero(p)
            return ones(S,3) * (-b/(3a))
        else
            return S[2*sqrt(-p/3)*cos(1/3 * acos((3q)/(2p) * sqrt(-3/p)) - 2pi*k/3) for k in 0:2] - b/(3a)
        end

    end

end

check_quintic(ps::Vector) = length(ps) == 6 || throw(DomainError())
# C -> C
function solve_quintic(ps::Vector{Complex{T}}, U::Type{Over.CC{S}}) where {T <: Real, S}
    check_quintic(ps)

    PolynomialRoots.roots5(ps)
end


module LiterateDollop
export optfun

ρ = (sqrt(5) - 1)/2

fw1(a, b) = a + (1 - ρ)*(b - a)
fw2(a, b) = b - (1 - ρ)*(b - a)

function optfun(f, a, b; ϵ=1e-5)
    @assert b > a
    k = 1
    w1 = fw1(a, b)
    w2 = fw2(a, b)

    while abs(b - a) > ϵ
        if f(w1) < f(w2)
            b = w2
            w2 = w1
            w1 = fw1(a, b)
        else
            a = w1
            w1 = w2
            w2 = fw2(a, b)
        end
    end
    return a
end

end # module


"""
    Bernoulli(p)

A *Bernoulli distribution* is parameterized by a success rate `p`, which takes value 1
with probability `p` and 0 with probability `1-p`.

```math
P(X = k) = \\begin{cases}
1 - p & \\quad \\text{for } k = 0, \\\\
p & \\quad \\text{for } k = 1.
\\end{cases}
```

```julia
Bernoulli()    # Bernoulli distribution with p = 0.5
Bernoulli(p)   # Bernoulli distribution with success rate p

params(d)      # Get the parameters, i.e. (p,)
succprob(d)    # Get the success rate, i.e. p
failprob(d)    # Get the failure rate, i.e. 1 - p
```

External links:

* [Bernoulli distribution on Wikipedia](http://en.wikipedia.org/wiki/Bernoulli_distribution)
"""
struct Bernoulli{T<:Real} <: DiscreteUnivariateDistribution
    p::T

    function Bernoulli{T}(p::T) where T
        @check_args(Bernoulli, zero(p) <= p <= one(p))
        new{T}(p)
    end

end

Bernoulli(p::T) where {T<:Real} = Bernoulli{T}(p)
Bernoulli(p::Integer) = Bernoulli(Float64(p))
Bernoulli() = Bernoulli(0.5)

@distr_support Bernoulli 0 1

#### Conversions
convert(::Type{Bernoulli{T}}, p::Real) where {T<:Real} = Bernoulli(T(p))
convert(::Type{Bernoulli{T}}, d::Bernoulli{S}) where {T <: Real, S <: Real} = Bernoulli(T(d.p))

#### Parameters

succprob(d::Bernoulli) = d.p
failprob(d::Bernoulli) = 1 - d.p

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

#### Properties

mean(d::Bernoulli) = succprob(d)
var(d::Bernoulli) =  succprob(d) * failprob(d)
skewness(d::Bernoulli) = (p0 = failprob(d); p1 = succprob(d); (p0 - p1) / sqrt(p0 * p1))
kurtosis(d::Bernoulli) = 1 / var(d) - 6

mode(d::Bernoulli) = ifelse(succprob(d) > 1/2, 1, 0)

function modes(d::Bernoulli)
    p = succprob(d)
    p < 1/2 ? [0] :
    p > 1/2 ? [1] : [0, 1]
end

median(d::Bernoulli) = ifelse(succprob(d) <= 1/2, 0, 1)

function entropy(d::Bernoulli)
    p0 = failprob(d)
    p1 = succprob(d)
    (p0 == 0 || p0 == 1) ? zero(d.p) : -(p0 * log(p0) + p1 * log(p1))
end

#### Evaluation

pdf(d::Bernoulli, x::Bool) = x ? succprob(d) : failprob(d)
pdf(d::Bernoulli, x::Int) = x == 0 ? failprob(d) :
                            x == 1 ? succprob(d) : zero(d.p)

cdf(d::Bernoulli, x::Bool) = x ? failprob(d) : one(d.p)
cdf(d::Bernoulli, x::Int) = x < 0 ? zero(d.p) :
                            x < 1 ? failprob(d) : one(d.p)

ccdf(d::Bernoulli, x::Bool) = x ? succprob(d) : one(d.p)
ccdf(d::Bernoulli, x::Int) = x < 0 ? one(d.p) :
                             x < 1 ? succprob(d) : zero(d.p)

function quantile(d::Bernoulli{T}, p::Real) where T<:Real
    0 <= p <= 1 ? (p <= failprob(d) ? zero(T) : one(T)) : T(NaN)
end
function cquantile(d::Bernoulli{T}, p::Real) where T<:Real
    0 <= p <= 1 ? (p >= succprob(d) ? zero(T) : one(T)) : T(NaN)
end

mgf(d::Bernoulli, t::Real) = failprob(d) + succprob(d) * exp(t)
cf(d::Bernoulli, t::Real) = failprob(d) + succprob(d) * cis(t)

#### Sampling

rand(rng::AbstractRNG, d::Bernoulli) = rand(rng) <= succprob(d)

#### MLE fitting

struct BernoulliStats <: SufficientStats
    cnt0::Float64
    cnt1::Float64

    BernoulliStats(c0::Real, c1::Real) = new(Float64(c0), Float64(c1))
end

fit_mle(::Type{Bernoulli}, ss::BernoulliStats) = Bernoulli(ss.cnt1 / (ss.cnt0 + ss.cnt1))

function suffstats(::Type{Bernoulli}, x::AbstractArray{T}) where T<:Integer
    n = length(x)
    c0 = c1 = 0
    for i = 1:n
        @inbounds xi = x[i]
        if xi == 0
            c0 += 1
        elseif xi == 1
            c1 += 1
        else
            throw(DomainError())
        end
    end
    BernoulliStats(c0, c1)
end

function suffstats(::Type{Bernoulli}, x::AbstractArray{T}, w::AbstractArray{Float64}) where T<:Integer
    n = length(x)
    length(w) == n || throw(DimensionMismatch("Inconsistent argument dimensions."))
    c0 = c1 = 0
    for i = 1:n
        @inbounds xi = x[i]
        @inbounds wi = w[i]
        if xi == 0
            c0 += wi
        elseif xi == 1
            c1 += wi
        else
            throw(DomainError())
        end
    end
    BernoulliStats(c0, c1)
end


# SPDX-License-Identifier: X11
# 2020-11-20
# The Number of Even Pairs (100pt)

N,M = parse.(Int, (split ∘ readline)())
println(binomial(N,2) + binomial(M,2))


#
# IID case
#
function min(Xs::IIDRandomSequence{Continuous,Exponential})
  N,λ = length(Xs),rate(Xs.d)
  return Exponential(1./(N*λ))
end

function spacing(Xs::IIDRandomSequence,space::Integer)
  N,λ = length(Xs),rate(Xs.d)
  return Exponential(1./((N-space+1)*λs))
end

#
# INID case
#
function min(Xs::INIDRandomSequence{Continuous,Exponential})
  N,λs = length(Xs),[rate(X) for X in Xs.distributions]
  return Exponential(1./sum(λs))
end


"""
	Levenberg-Marquardt
		(damped least squares)
"""
function levenberg_marquardt(res::Function, x;
		reg = 1.0e-8, tol_r = 1.0e-8, tol_d = 1.0e-6)

	y = copy(x)
	Δy = copy(x)

	merit(z) = res(z)' * res(z)

	α = 1.0
	iter = 0

	while iter < 100
		me = merit(y)
		r = res(y)
		∇r = ForwardDiff.jacobian(res, y)

		_H = ∇r' * ∇r
		Is = Diagonal(diag(_H))
		H = (_H + reg * Is)

		pd_iter = 0
		while !isposdef(Hermitian(Array(H)))
			reg *= 2.0
			H = (_H + reg * Is)
			pd_iter += 1

			if pd_iter > 100 || reg > 1.0e12
				@error "regularization failure"
			end
		end

		try
			Δy = -1.0 * H \ (∇r' * r)
		catch
			@warn "implicit-function theorem failure"
			return y
		end

		ls_iter = 0
		while merit(y + α * Δy) > me + 1.0e-4 * (∇r' * r)' * (α * Δy)
			α *= 0.5
			reg = reg
			ls_iter += 1

			if ls_iter > 100 || reg > 1.0e12
				@error "line search failure"
			end
		end

		y .+= α * Δy
		α = min(1.2 * α, 1.0)
		reg = 0.5 * reg

		iter += 1

		norm(α * Δy, Inf) < tol_d && (return y)
		norm(r, Inf) < tol_r && (return y)
	end

	@warn "Gauss Newton failure"
	return y
end


@testset "sl" begin
    Sl1(x) = 0.5*(pi - x)
    Sl2(x) = pi^2/6 - pi/2*x + x^2/4
    Sl3(x) = pi^2/6*x - pi/4*x^2 + x^3/12
    Sl4(x) = pi^4/90 - pi^2/12*x^2 + pi/12*x^3 - x^4/48

    n = 10
    x_min = 0.0
    x_max = 2.0*pi
    data = (x_max - x_min)*rand(Float64, n) + x_min*ones(n)

    for x in data
        @test ≈(ClausenFunctions.sl(1, x), Sl1(x), atol=1e-14)
        @test ≈(ClausenFunctions.sl(2, x), Sl2(x), atol=1e-14)
        @test ≈(ClausenFunctions.sl(3, x), Sl3(x), atol=1e-14)
        @test ≈(ClausenFunctions.sl(4, x), Sl4(x), atol=1e-13)
    end

    for n in vcat(collect(1:31), [1000, 1001])
        data = open(readdlm, joinpath(@__DIR__, "data", "Sl$(n).txt"))

        for r in 1:size(data, 1)
            row      = data[r, :]
            x        = row[1]
            expected = row[2]

            @test ClausenFunctions.sl(n, x) == (-1)^n*ClausenFunctions.sl(n, -x)
            @test ClausenFunctions.sl(n, x) ≈ expected atol=1e-14
        end
    end

    @test_throws DomainError ClausenFunctions.sl(0, 1.0)
    @test_throws DomainError ClausenFunctions.sl(-1, 1.0)
    @test_throws DomainError ClausenFunctions.sl(-2, 1.0)
end


using LinearAlgebra

# List Operations
x = [ 1.0, 0 ];   y = [ 1.0, -1.0 ];   z = [ 0, 1.0];

list = [ x, y, z ]              # lists 3 arrays w/elements
list[2]                         # lists elements of 2nd array
list[3]                         # lists elements of 3rd array
list = ( x, y, z )              # list of 3 arrays w/elements

# Creating vectors with Julia functions
zeros(3)                        # creates zero vector of dim 3

i = 3; n = 4;                   # sets values
ei = zeros(n);                  # creates zero vector of dim n
ei[i] = 1;                      # sets 3rd value to 1.0
ei                              # produces [ 0.0, 0.0, 1.0, 0.0]

unit_vector(i,n) = [zeros(i-1); 1 ; zeros(n-i)]
unit_vector(3,4)                # produces [ 0.0, 0.0, 1.0, 0.0]

ones(2)                         # creates one vector of dim 2
rand(2)                         # creates rand vector of dim 2

# Add or subtract vectors
[ 0, 7, 3] + [ 1, 2, 0]
[ 1, 9 ] - [ 1, 1]

# Scalar vector operations
x = [ 0, 2, -1 ];
2.2 * x
x / 3
[ 1.1, -3.7, 0.3 ] .- 1.4       # '.' applies op to each element
0.7 .+ [1,-1]                   # '.' applies op to each element

p_1= [ 22.15, 89.32, 56.77 ];
p_2 = [ 23.05, 87.32, 57.13 ];
r = (p_2 - p_1) ./ p_1          # '.' applies op to each element

w = [1,2,2]; z = [1,2,3];
w == z                          # returns false
w .== z                         # returns true, true, false


### ℝ^{D-1} to D-dim simplex.

# Takes x ∈ (0,1)^D to somewhere within the interior of the D-dim simplex,
#   then we map it to the boundary of the (D+1)-dim simplex.
function unitcubetosimplexinterior(x::Vector{T})::Vector{T} where T
    D = length(x)

    s = Vector{T}(undef,D)
    for i = 1:D-1
        s[i] = x[i]*prod( one(T)-x[j] for j = i+1:D)
    end
    s[D] = x[D]

    return s
end

function unitcubetosimplex(x::Vector{T})::Vector{T} where T
    D = length(x)

    s = unitcubetosimplexinterior(x)
    push!(s, one(T) - sum(s[i] for i = 1:D))

    return s
end

function Euclideantounitcube(r::Vector{T})::Vector{T} where T
    D = length(r)

    x = Vector{T}(undef,D)
    for i = 1:D
        x[i] = realtounitinterval(r[i])
    end

    return x
end

function realtounitintervalV(x::T)::T where T
    return (one(T) + x/sqrt(one(T)+x^2))/2-0.5
end

function realtounitinterval2(x::T, a::T)::T where T
    return (one(T) + a*x/sqrt(one(T)+(a*x)^2))/2
end

function realtounitinterval(x::T)::T where T
    return (one(T) + x/sqrt(one(T)+x^2))/2
end
# function realtounitinterval(x::T)::T where T
#     return logicsticfunc(x)
# end

function derivativeofrealtounitinterval(x::T)::T where T
    return one(T)/(2*sqrt(one(T)+x^2)^3)
end

# package up.
function Euclideantosimplex(r::Vector{T})::Vector{T} where T
    x = Euclideantounitcube(r) # to the unit cube.
    s = unitcubetosimplex(x)

    return s
end

function Euclideantosimplexinterior(r::Vector{T})::Vector{T} where T
    x = Euclideantounitcube(r) # to the unit cube.
    s = unitcubetosimplexinterior(x)

    return s
end

### other direction: D-dim simplex to ℝ^{D-1}.
function simplextounitcube(s::Vector{T})::Vector{T} where T
    D = length(s) -1

    x = Vector{T}(undef,D)
    for i = 1:D-1
        x[i] = s[i]/(one(T) - sum( s[j] for j = i+1:D))
    end
    x[D] = s[D]

    return x
end

function simplexinteriortounitcube(s::Vector{T})::Vector{T} where T
    D = length(s)

    x = Vector{T}(undef,D)
    for i = 1:D-1
        x[i] = s[i]/(one(T) - sum( s[j] for j = i+1:D))
    end
    x[D] = s[D]

    return x
end

function unitcubetoEuclidean(x::Vector{T})::Vector{T} where T
    D = length(x)

    r = Vector{T}(undef,D)
    for i = 1:D
        r[i] = unitintervaltoreal(x[i])
    end

    return r
end

# inverse of realtounitinterval()
function unitintervaltoreal(y::T)::T where T
    u = 2*y-1
    return u/sqrt(one(T)-u^2)
end
# # test code.
# x = randn()
# h = realtounitinterval(x)
# x_rec = unitintervaltoreal(h)
# println("x = ", x)
# println("x_rec = ", x_rec)
# println("discrepancy = ", abs(x-x_rec))
# function unitintervaltoreal(y::T)::T where T
#     return invlogicsticfunc(y)
# end

# package up.
function simplextoEuclidean(s::Vector{T})::Vector{T} where T
    x = simplextounitcube(s) # to the unit cube.
    r = unitcubetoEuclidean(x)

    return r
end

function simplexinteriortoEuclidean(s::Vector{T})::Vector{T} where T
    x = simplexinteriortounitcube(s) # to the unit cube.
    r = unitcubetoEuclidean(x)

    return r
end

###### Jacobian of Euclideantosimplexinterior()

# derivative of realtounitinterval().
function ∂realtounitinterval(x::T)::T where T <: Real
    tmp = 2*sqrt(1+x^2)*(1+x^2)
    return one(T)/tmp
end

function ln∂realtounitinterval(x::T)::T where T <: Real
    tmp = log(2) + 3/2*log((1+x^2))
    return -tmp
end

# derivative of simplexinteriortounitcube().
function ∂simplexinteriortounitcube(u::Vector{T}, a::Int, b::Int) where T <: Real
    D = length(u)
    if a != b
        return -u[a]*prod( one(T)-u[j] for j = a+1:D if j != b)
    end

    if a == D
        return one(T)
    end

    return prod( one(T)-u[j] for j = a+1:D)
end

# ℝ^D to boundary of D-simplex.
function computeJacobiandiagforW( y::Vector{T}) where T <: Real
    D = length(y)

    # pre-compute.
    one_minus_u = collect( one(T) - realtounitinterval(y[i]) for i = 1:D )

    # compute Jacobian.
    J_diag = Vector{T}(undef,D)
    for i = 1:D-1

        factor1 = ∂realtounitinterval(y[i])
        factor2 = prod( one_minus_u[j] for j = i+1:D)

        J_diag[i] = factor1*factor2
    end
    J_diag[D] = ∂realtounitinterval(y[D])

    return J_diag
end

function computelogdetJacobianforW( y::Vector{T})::T where T <: Real
    D = length(y)

    # pre-compute.
    ln_one_minus_u = collect( log(one(T) - realtounitinterval(y[i])) for i = 1:D )

    # compute Jacobian.
    ln_det_J = zero(T)
    for i = 1:D-1

        ln_factor1 = ln∂realtounitinterval(y[i])
        ln_factor2 = sum( ln_one_minus_u[j] for j = i+1:D)

        update = ln_factor1 + ln_factor2
        ln_det_J += update
    end
    ln_det_J += ln∂realtounitinterval(y[D])

    return ln_det_J
end


abstract type AbstractPenalty{T} end
abstract type AbstractCPUPenalty{T} <: AbstractPenalty{T} end
abstract type AbstractProjection end

mutable struct PowerPenalty{T} <: AbstractCPUPenalty{T}
    p::T
end
@inline (P::PowerPenalty)(x) = x^(P.p)

mutable struct RationalPenalty{T} <: AbstractCPUPenalty{T}
    p::T
end
@inline (R::RationalPenalty)(x) = x / (1 + R.p * (1 - x))

mutable struct SinhPenalty{T} <: AbstractCPUPenalty{T}
    p::T
end
@inline (R::SinhPenalty)(x) = sinh(R.p * x) / sinh(R.p)

struct ProjectedPenalty{T,Tpen<:AbstractPenalty{T},Tproj} <: AbstractCPUPenalty{T}
    penalty::Tpen
    proj::Tproj
end
function ProjectedPenalty(penalty::AbstractPenalty{T}) where {T}
    return ProjectedPenalty(penalty, HeavisideProjection(10 * one(T)))
end
@inline (P::ProjectedPenalty)(x) = P.penalty(P.proj(x))
@forward_property ProjectedPenalty penalty

mutable struct HeavisideProjection{T} <: AbstractProjection
    β::T
end
@inline (P::HeavisideProjection)(x) = 1 - exp(-P.β * x) + x * exp(-P.β)
mutable struct SigmoidProjection{T} <: AbstractProjection
    β::T
end
@inline (P::SigmoidProjection)(x) = 1 / (1 + exp((P.β + 1) * (-x + 0.5)))

import Base: copy
copy(p::TP) where {TP<:AbstractPenalty} = TP(p.p)
copy(p::HeavisideProjection) = HeavisideProjection(p.β)
copy(p::SigmoidProjection) = SigmoidProjection(p.β)
copy(p::ProjectedPenalty) = ProjectedPenalty(copy(p.penalty), copy(p.proj))

function Utilities.setpenalty!(P::AbstractPenalty, p)
    P.p = p
    return P
end
function Utilities.setpenalty!(P::ProjectedPenalty, p)
    P.penalty.p = p
    return P
end


module SSLTestCoarsen
using SimpleSDMLayers
using Test
using Statistics

M = convert(Matrix, reshape(1:36, (6,6)))
S = SimpleSDMPredictor(M, 0.0, 1.0, 0.0, 1.0)

min22 = coarsen(S, minimum, (2,2))
@test min22.grid == [1 13 25; 3 15 27; 5 17 29]

max33 = coarsen(S, maximum, (3,3))
@test max33.grid == [15 33; 18 36]

# Coarsen should play nicely with the non-float types
M = SimpleSDMResponse(["a" nothing "b" "c"; "d" "e" "f" "g"; "d" "e" nothing "g"; nothing "x" "y" "z"], 0.0, 1.0, 0.0, 1.0)
@test coarsen(M, x -> reduce(*, x), (2,2)).grid == ["ade" "bfcg"; "dex" "ygz"]

# Should work on a real-world example for a series of functions
temperature = SimpleSDMPredictor(WorldClim, BioClim, 1)
for f in [minimum, maximum, mean, median]
    t_coarse = coarsen(temperature, f, (10, 10))
    @test typeof(t_coarse) <: SimpleSDMPredictor
end

end


function descent(p::Param, η::Real)
  function ()
    p.x .-= p.Δ .* η
    p.Δ .= 0
  end
end

function momentum(p::Param, ρ::Real)
  mo = zeros(p.x)
  () -> p.Δ .= mo .= ρ .* mo .+ p.Δ
end

function nesterov(p::Param, ρ::Real)
  mo = zeros(p.x)
  function ()
    mo  .= ρ .* mo .+ p.Δ
    p.Δ .= ρ .* mo .+ p.Δ
  end
end

function clip(p::Param, thresh::Real)
  () -> clamp!(p.Δ, -thresh, thresh)
end

function weightdecay(p::Param, γ::Real)
  () -> p.Δ .+= γ .* p.x
end

function invdecay(p::Param, γ::Real)
  n = 0
  function ()
    p.Δ .*= 1 / (1 + γ * n)
    n += 1
  end
end

function rmsprop(p::Param; η::Real = 0.001, ρ::Real = 0.9, ϵ::Real = 1e-8)
  acc  = zeros(p.x) .+ ϵ
  function ()
    @. acc = ρ * acc + (1 - ρ) * p.Δ ^ 2
    @. p.Δ /= √acc * η
  end
end

function adagrad(p::Param; η::Real = 0.01, ϵ::Real = 1e-8)
  acc = zeros(p.x) .+ ϵ
  function ()
    @. acc += p.Δ ^ 2
    @. p.Δ /= √acc * η
  end
end

function adadelta(p::Param; ρ::Real = 0.95, ϵ::Real = 1e-8)
  acc = zeros(p.x) .+ ϵ
  Δacc = zeros(p.x) .+ ϵ
  function ()
    @. acc = ρ * acc + (1 - ρ) * p.Δ ^ 2
    @. p.Δ *= √Δacc / √acc
    @. Δacc = ρ * Δacc + (1 - ρ) * p.Δ ^ 2
  end
end

function adam(p::Param; η::Real = 0.001, β1::Real = 0.9, β2::Real = 0.999, ϵ::Real = 1e-8)
  mt = zeros(p.x)
  vt = zeros(p.x) .+ ϵ
  β1p, β2p = β1, β2
  function ()
    @. mt = β1 * mt + (1 - β1) * p.Δ
    @. vt = β2 * vt + (1 - β2) * p.Δ ^ 2
    @. p.Δ = √(1 - β2p) / √(1 - β1p) * mt / √vt * η
    β1p *= β1
    β2p *= β2
  end
end


@testset "Kernels" begin
    #test _word2vector
    mod = sort(["a","b","c","d"])
    dict_mod = Dict( j => i for (i,j) in enumerate(mod))
    @testset "_word2vector" begin
        @test _word2vec(["a","b","c","d"],dict_mod) == [1,1,1,1]
        @test _word2vec(["b","b","c","d"],dict_mod) == [0,2,1,1]
        @test _word2vec(["b","b","c","c"],dict_mod) == [0,2,2,0]
        @test _word2vec(["a","c","c","d"],dict_mod) == [1,0,2,1]
    end

    # Kernels
    s1 = ["a","b","c","d"]
    s2 = ["b","b","c","d"]
    s3 = ["b","b","c","c"]
    s4 = ["a","c","c","d"]

    x1_test = [s1,s2,s3,s4]
    x1 = [OrderedConstruct([Mod(m) for m in s]) for s in [s1,s2,s3,s4]]

    s5=  ["a","b","d","d"]
    s6 = ["b","b","b","d"]
    s7 = ["a","a","c","c"]
    s8 = ["d","c","c","d"]

    x2_test = [s5,s6,s7,s8]
    x2 = [OrderedConstruct([Mod(m) for m in s]) for s in [s5,s6,s7,s8]]

    #test levenstein
    testkernel = EditDistancesKernel(Levenshtein())
    #expexted output for custum ew
    ew_test = exp.([-evaluate(Levenshtein(),s1,s5),-evaluate(Levenshtein(),s2,s6),
        -evaluate(Levenshtein(),s3,s7),
        -evaluate(Levenshtein(),s4,s8)]);

    @testset "Editdistance" begin
        @test map(x -> exp(-evaluate(Levenshtein(),x,x)),x1_test) == ew(testkernel,x1)
        @test ew_test == ew(testkernel,x1,x2)
        @test  Grammatrix_levenstein(x1_test,x1_test)== pw(testkernel,x1)
        @test  Grammatrix_levenstein(x1_test,x2_test) == pw(testkernel,x1,x2)
    end

    #test Cosine

    testkernel2 = QGramKernel(StringDistances.Cosine(1))
    v1 = [[1,1,1,1],[0,2,1,1],[0,2,2,0],[1,0,2,1]]
    v2 = [_word2vec(i,dict_mod) for i in x2_test]

    @testset "Qnorm" begin
        @test map(x -> (1-cosine_dist(x,x)) ,v1) ≈ ew(testkernel2,x1)
        @test [(1-cosine_dist(i,j)) for (i,j) in zip(v1,v2)] ≈ ew(testkernel2,x1,x2)
        @test  Grammatrix_cossine(x1_test,x1_test,dict_mod) ≈ pw(testkernel2,x1)
        @test  Grammatrix_cossine(x1_test,x2_test,dict_mod) ≈ pw(testkernel2,x1,x2)
    end
end


using Sobol, StatsFuns

function simulate!(X, mcProcess::HestonProcess, rfCurve::ZeroRate, mcBaseData::MonteCarloConfiguration{type1, type2, type3, SerialMode, type5}, T::numb) where {numb <: Number, type1 <: Integer, type2 <: Integer, type3 <: SobolMode, type5 <: Random.AbstractRNG}
    r = rfCurve.r
    S0 = mcProcess.underlying.S0
    d = dividend(mcProcess)
    Nsim = mcBaseData.Nsim
    Nstep = mcBaseData.Nstep
    σ = mcProcess.σ
    σ_zero = mcProcess.σ_zero
    λ1 = mcProcess.λ
    κ = mcProcess.κ
    ρ = mcProcess.ρ
    θ = mcProcess.θ
    @assert T > 0.0

    ####Simulation
    ## Simulate
    κ_s = κ + λ1
    θ_s = κ * θ / κ_s
    seq = SobolSeq(2 * Nstep)
    skip(seq, Nstep * Nsim)
    dt = T / Nstep
    isDualZero = T * r * σ_zero * θ_s * κ_s * σ * ρ * 0.0 * S0
    view(X, :, 1) .= isDualZero
    for i = 1:Nsim
        v_m = σ_zero^2
        vec = norminvcdf.(next!(seq))
        for j = 1:Nstep
            @views e1 = vec[j]
            @views e2 = e1 * ρ + vec[Nstep+j] * sqrt(1 - ρ * ρ)
            @views X[i, j+1] = X[i, j] + ((r - d) - 0.5 * v_m) * dt + sqrt(v_m) * sqrt(dt) * e1
            v_m += κ_s * (θ_s - v_m) * dt + σ * sqrt(v_m) * sqrt(dt) * e2
            #when v_m = 0.0, the derivative becomes NaN
            v_m = max(v_m, isDualZero)
        end
    end
    ## Conclude
    X .= S0 .* exp.(X)
    return
end

function simulate!(X, mcProcess::HestonProcess, rfCurve::ZeroRateCurve, mcBaseData::MonteCarloConfiguration{type1, type2, type3, SerialMode, type5}, T::numb) where {numb <: Number, type1 <: Integer, type2 <: Integer, type3 <: SobolMode, type5 <: Random.AbstractRNG}
    r = rfCurve.r
    S0 = mcProcess.underlying.S0
    d = dividend(mcProcess)
    Nsim = mcBaseData.Nsim
    Nstep = mcBaseData.Nstep
    σ = mcProcess.σ
    σ_zero = mcProcess.σ_zero
    λ1 = mcProcess.λ
    κ = mcProcess.κ
    ρ = mcProcess.ρ
    θ = mcProcess.θ
    @assert T > 0.0

    ####Simulation
    ## Simulate
    κ_s = κ + λ1
    θ_s = κ * θ / (κ + λ1)
    r_d = r - d
    dt = T / Nstep
    seq = SobolSeq(2 * Nstep)
    skip(seq, Nstep * Nsim)
    zero_drift = r_d(dt * 0.0, dt)
    isDualZero = S0 * T * σ_zero * κ * θ * λ1 * σ * ρ * 0.0 * zero_drift
    #X=Matrix{typeof(isDualZero)}(undef,Nsim,Nstep+1);
    X[:, 1] .= isDualZero
    tmp_ = [r_d((j - 1) * dt, dt) for j = 1:Nstep]
    for i = 1:Nsim
        v_m = σ_zero^2
        vec = norminvcdf.(next!(seq))

        for j = 1:Nstep
            @views e1 = vec[j]
            @views e2 = e1 * ρ + vec[Nstep+j] * sqrt(1 - ρ * ρ)
            @views X[i, j+1] = X[i, j] + (tmp_[j] - 0.5 * v_m) * dt + sqrt(v_m) * sqrt(dt) * e1
            v_m += κ_s * (θ_s - v_m) * dt + σ * sqrt(v_m) * sqrt(dt) * e2
            #when v_m = 0.0, the derivative becomes NaN
            v_m = max(v_m, isDualZero)
        end
    end
    ## Conclude
    X .= S0 .* exp.(X)
    return
end


@testset "Scalar1PBasis" begin

##
using ACE
using Printf, Test, LinearAlgebra, StaticArrays
using ACE: evaluate, evaluate_d, Rn1pBasis, Ylm1pBasis,
      EuclideanVectorState, Product1pBasis
using Random: shuffle
using ACEbase.Testing: dirfdtest, fdtest, print_tf

##

maxdeg = 10
r0 = 1.0
rcut = 3.0
trans = trans = PolyTransform(1, r0)
bscal = ACE.scal1pbasis(:x, :k, maxdeg, trans, rcut)

ACE.evaluate_d(bscal, 1.0)

##

end


using ModelingToolkit, IfElse, LinearAlgebra, StaticArrays
import LinearAlgebra.norm
norm(x::AbstractVector{Num}) = sqrt(sum(x.^2))

# attempt to create symbolic version of GeometryPrimitives.Polygon

# nv vertices in 2D plane
nv = 4  # first try trapazoid like in ridge waveguide
@parameters w, t, θ # compare with wₜₒₚ,t_core,θ
# derived parameters for vertices
wt_half = w / 2
wb_half = wt_half + ( t * tan(θ) )
tc_half = t / 2
# vertices
v =    	[   wt_half     tc_half
            -wt_half    tc_half
            -wb_half    -tc_half
            wb_half     -tc_half    ]
vs = SMatrix{4,2}(v)
Δv = v - circshift(v,1)
Δvs = SMatrix{4,2}(Δv)

one_mone = [1, -1]
one_mones = SVector(1, -1)
@tullio n[i,j] := Δv[i,3-j] * $one_mone[j] / sqrt(Δv[i,1]^2 + Δv[i,2]^2 ) nograd=one_mone
ns = SMatrix{4,2}(n)
lbnd = transpose(minimum(v;dims=1))
ubnd = transpose(maximum(v;dims=1))

lbnds = transpose(minimum(vs;dims=1))
ubnds = transpose(maximum(vs;dims=1))
bnds = lbnd, ubnd

function Δxe(x,v,n)
    @tullio res[k] := n[k,a] * ( x[a] - v[k,a] )
end

minbool(x) = map(a->(a <= minimum(x)), x)
maxbool(x) = map(a->(a >= maximum(x)), x)
minone(x) = IfElse.ifelse.(x .<= minimum(x), 1, 0)
maxone(x) = IfElse.ifelse.(x .>= maximum(x), 1, 0)
atmin(x,y) = sum(IfElse.ifelse.(y .<= minimum(y), x, 0))
atmax(x,y) = sum(IfElse.ifelse.(y .>= maximum(y), x, 0))

# test atmin, atmax
# @parameters u[1:10], q[1:10]
# fminbool,fminbool! = eval.(build_function(minbool(q),q))
# fmaxbool,fmaxbool! = eval.(build_function(maxbool(q),q))
# fatmin = eval(build_function(atmin(u,q),u,q))
# fatmax = eval(build_function(atmax(u,q),u,q))
# un = [1,2,3,4,5,6,7,8,9,10]
# qn = [3,4,3,2,10,5,3,5,1,2]
# fmaxbool(un)
# fatmin(un,qn)
# fatmax(un,qn)

function pgon_cout2(x,v,n)
	Δxv = x' .- v
	lΔxv = sqrt.(sum(x->x^2,dxv,dims=2))
	imin = minone(lΔxv)
	surf = sum(v.*imin,dims=1)[1,:]
	# IfElse.ifelse(obd(x)&&)
	# imin₋₁ = circshift(imin,-1) #for ifelse if needed
	n0 = x-surf
	# return surf, n0./norm(n0)
	hcat(surf, n0./norm(n0))
end

function pgon_cout_not2(x,v,n,Δxe)
	imax = maxone(Δxe)
	vmax = sum(imax.*v,dims=1)[1,:]
	nout = sum(imax.*n,dims=1)[1,:]
	Δx = (nout⋅(vmax-x)) .* nout
    surf = x + Δx
	# return surf, nout
	hcat(surf, nout)
end

function spn_pgon(x,v,n)
    # @tullio Δxe[k] := n[k,a] * ( x[a] - v[k,a] )
	Δxe = sum(n .* (x' .- v), dims=2)[:,1]
    # cout = sum( 1 .- signbit.( Δxe ) )
	# cout = sum( Δxe .> 0 )
	cout = sum(IfElse.ifelse.(Δxe .> 0, 1, 0))
	res = IfElse.ifelse(
		isequal(cout,2),
		pgon_cout2(x,v,n),
		pgon_cout_not2(x,v,n,Δxe)
		)
	return res #::Matrix
end

# fspn_pgon,fspn_pgon! = eval.(build_function(spn_pgon(x,v,n),x,w,t,θ))
fspn_pgon,fspn_pgon! = eval.(build_function(spn_pgon(x,v,n),x,ps))
fDspn_pgon = eval(build_function(Dspn_pgon(x,v,n),x,ps))
fspn_pgons,fspn_pgons! = eval.(build_function(spn_pgon(xs,vs,ns),x,w,t,θ))

fpgon_cout2,fpgon_cout2! = eval.(build_function(pgon_cout2(x,v,n),x,w,t,θ))
fpgon_cout_not2,fpgon_cout_not2! = eval.(build_function(pgon_cout_not2(x,v,n,Δxe(x,v,n)),x,w,t,θ))

xnum = [0.3,0.5]
@parameters x[1:2]
spn_pgon(xnum,v,n)
spn_pgon(x,v,n)

f1 = eval(build_function(spn_pgon(xnum,v,n),w,t,θ))
f2,f2! = eval.(build_function(Δxe(xnum,v,n),w,t,θ))
f3,f3! = eval.(build_function(Δxe(x,v,n),x,w,t,θ))
f4 = eval(build_function(spn_pgon(x,v,n),x,w,t,θ))
flbnd,flbnd! = eval.(build_function(lbnd,w,t,θ))
fubnd,fubnd! = eval.(build_function(ubnd,w,t,θ))
fbnd(a,b,c) = flbnd(a,b,c),fubnd(a,b,c)
# @variables v[nv,2]

# compare with original functions
using GeometryPrimitives, StaticArrays, LinearAlgebra
function pg(w,t,θ)
    wt_half = w / 2
    wb_half = wt_half + ( t * tan(θ) )
    tc_half = t / 2
    # vertices
    verts = SMatrix{4,2}( [  wt_half     tc_half
                            -wt_half    tc_half
                            -wb_half    -tc_half
                            wb_half     -tc_half    ] )
    GeometryPrimitives.Polygon(					                        # Instantiate 2D polygon, here a trapazoid
                    verts,			                            # v: polygon vertices in counter-clockwise order
                    2.4,					                                    # data: any type, data associated with box shape
                )
end

bounds(pg1)
function _Δxe_poly(x::SVector{2},v::SMatrix{K,2},n::SMatrix{K,2})::SVector{K}  where {K} #,T<:Real}
	@tullio out[k] := n[k,a] * ( x[a] - v[k,a] ) # edge line eqns for a K-point Polygon{K} `s`
end
function Δxe_poly(x::AbstractVector{<:Real},s::Polygon{K})::SVector{K} where K
	_Δxe_poly(x,s.v,s.n)
end
function onbnd(Δxe::SVector{K},s::Polygon{K})::SVector{K,Bool} where K
	map(x->abs(x)≤s.rbnd,Δxe)
end
function onbnd(x::SVector{2},s::Polygon)::SVector{K,Bool} where K
	map(x->abs(x)≤s.rbnd,Δxe_poly(x,s))
end
function cout(x::SVector{2},s::Polygon)::Int
	mapreduce((a,b)->Int(a|b),+,onbnd(x,s),map(isposdef,Δxe_poly(x,s)))
end
function cout(Δxe::SVector{K,<:Real},obd::SVector{K,Bool})::Int where K
	mapreduce((a,b)->Int(a|b),+,obd,map(isposdef,Δxe))
end

pg1 = pg(1.7,0.7,π/14.0)
# x = SVector(0.2,0.3)
∆xe1 = _Δxe_poly(SVector{2}(xnum),pg1.v,pg1.n)
obd1 = onbnd(∆xe1,pg1)
co1 = cout(∆xe1,obd1)

## reference code

p = [
       1.7,                #   top ridge width         `w_top`         [μm]
       0.7,                #   ridge thickness         `t_core`        [μm]
       π / 14.0,           #   ridge sidewall angle    `θ`             [radian]
       2.4,                #   core index              `n_core`        [1]
       1.4,                #   substrate index         `n_subs`        [1]
       0.5,                #   vacuum gap at boundaries `edge_gap`     [μm]
               ];
Δx,Δy,Δz,Nx,Ny,Nz = 6.0, 4.0, 1.0, 128, 128, 1;
rwg(p) = ridge_wg(p[1],p[2],p[3],p[6],p[4],p[5],Δx,Δy)
ms = ModeSolver(1.45, rwg(p), Δx, Δy, Δz, Nx, Ny, Nz)

function ridge_wg2(wₜₒₚ::T1,t_core::T1,θ::T1,edge_gap::T1,ε_core,ε_subs,Δx::T2,Δy::T2)::Vector{<:GeometryPrimitives.Shape} where {T1<:Real,T2<:Real}
    t_subs = (Δy -t_core - edge_gap )/2.
    c_subs_y = -Δy/2. + edge_gap/2. + t_subs/2.
    # ε_core = ε_tensor(n_core)
    # ε_subs = ε_tensor(n_subs)
    wt_half = wₜₒₚ / 2
    wb_half = wt_half + ( t_core * tan(θ) )
    tc_half = t_core / 2
    verts =     [   wt_half     -wt_half     -wb_half    wb_half
                    tc_half     tc_half    -tc_half      -tc_half    ]'
    core = GeometryPrimitives.Polygon(					                        # Instantiate 2D polygon, here a trapazoid
                    SMatrix{4,2}(verts),			                            # v: polygon vertices in counter-clockwise order
                    ε_core,					                                    # data: any type, data associated with box shape
                )
    ax = [      1.     0.
                0.     1.      ]
    b_subs = GeometryPrimitives.Box(					                # Instantiate N-D box, here N=2 (rectangle)
                    [0. , c_subs_y],            	# c: center
                    [Δx - edge_gap, t_subs ],	# r: "radii" (half span of each axis)
                    ax,	    		        # axes: box axes
                    ε_subs,					        # data: any type, data associated with box shape
                )
    return [core,b_subs]
end

rwg2(p) = ridge_wg2(p[1],p[2],p[3],p[6],MgO_LiNbO₃,SiO₂,Δx,Δy)
shapes = rwg(p)
shapes2 = rwg2(p)


using Distributions
##################
#basically plumbing to generate a multinomial problem with the setup given below
#predictor matrix, p>n
num=200
n=100
X=zeros(n,num)
X[:]=rand(n*num)

#intercept
X[:,1]=1

#true beta values, first 10 rows are nonzero
#four different response categories
beta=zeros(num,4)
beta[1:10,:]=(rand(4*10)-1/2)*3
Xbeta=X*beta
probs = BlockCoordinateDescent.multProbPrecompute(Xbeta)

#simulate response
z=zeros(n,4)
for i=1:n
  t1=Multinomial(1,vec(probs[i,:]))
  z[i,:]=rand(t1,1)
end

#define grouping: columns with same group number/label are considered grouped
groups=zeros(num)
for i=1:(div(num,2)-1)
  groups[2*i+1]=i
  groups[2*i+2]=i
end

groups[1]=-1
groups[2]=0

#the intercept is unpenalized, every other group is penalized equally
penaltyFactor=ones(length(unique(groups)))
penaltyFactor[1]=0
#####################

@time multinomialBCD(X,z,groups,penaltyFactor);


module test_pointattributelist
using Triangulate
function test()

    x=[1.9  1.2  1.5  1.6  1.3  1.9  1.2  1.4  1.1  1.0]
    y=[1.5  1.4  1.7  1.2  1.3  1.9  2.0  1.9  1.5  1.0]
    z=[1.7  1.0  1.1  1.6  2.0  1.8  1.7  1.8  1.7  1.5]

    pointset = Triangulate.TriangulateIO(; pointlist=[x; y])
    pointset.pointattributelist = z
    triout, vorout=Triangulate.triangulate("Q", pointset);

    test1= triout.trianglelist== [10   5  7   2  2   9  2  4  8  8  6  8  3;
                                  5  10  9   9  3   7  5  1  3  9  3  6  5;
                                  2   4  8  10  9  10  3  3  6  3  1  7  4]
    test2=    triout.pointattributelist==z

    triout, vorout=Triangulate.triangulate("Qa0.05", pointset);

    test3 = isapprox(triout.pointattributelist,[1.7  1.0  1.1  1.6  2.0  1.8  1.7  1.8  1.7  1.5  1.6  1.72149  1.55  1.42347  1.13548  1.50625],atol=1.0e-5)

    test4= triout.trianglelist== [13   5   7   2  12   9  14   9  16   2   3  12   6  8   9  14   5   7   1  15   3   8  14;
                                  2  13  12   9   9  11  15   7  14  13  16   3  16  6   2   3   4   9  14   2  12  16  16;
                                  10   4   8  10  15  10   5  11   1   5   8   8   1  7  15  15  14  12   4   5  15   6   3]
    test1&test2&test3&test4
end
end