# FIXME:: grooming

# --[ (a*b+c*d) -> muladd ]
# LJR

ex = :(a1*b1 + a2*b2 + a3*b3 + a4*b4)
# :(a1 * b1 + a2 * b2 + a3 * b3 + a4 * b4)

co = [Symbol.(ex.args[i].args[2:3]) for i in 2:length(ex.args)]
#4-element Array{Array{Symbol,1},1}:
# Symbol[:a1, :b1]
# Symbol[:a2, :b2]
# Symbol[:a3, :b3]
# Symbol[:a4, :b4]

res = Expr(:call, :*, co[end]...)
# :(a4 * b4)

for i in (length(co)-1):-1:1
    res = :(muladd( $(co[i]...), $res ))
end

res
#:(muladd(a1, b1, muladd(a2, b2, muladd(a3, b3, a4 * b4))))

function _muladd_transform(ex)
    if length(ex.args) == 2
        return ex.args[2]
    else
    a, b = ex.args[2].args[2:end]
    rest = _muladd_transform(Expr(ex.head, :+, ex.args[3:end]...))
    return :($(Base.muladd)($a, $b, $rest))
    end
end

function _muladd_rewrite(ex)
    co = [Symbol.(ex.args[i].args[2:3]) for i in 2:length(ex.args)]
    res = Expr(:call, :*, co[end]...)
    for i in (length(co)-1):-1:1
        res = :(muladd( $(co[i]...), $res ))
    end
    res
end

# Symbolic diff
# LJR

struct SymbolFun{T} end
SymbolFun(S::Symbol) = SymbolFun{S}()

D(ex::Symbol, x::Symbol) = ex == x ? 1 :0
D(ex::Number, x::Symbol) = 0

function D(ex::Expr, x)
    @assert ex.head == :call
    D(SymbolFun(ex.args[1]), ex.args[2:end], x)
end

D(fun::SymbolFun{T}, args, x) where T = error("string(T) function is not supported!")

# Power Rule
function D(::SymbolFun{:^}, args, x)
    a, b = args[1], args[2]
    da, db = D(a, x), D(b, x)
    if da == 0 && db == 0    # if a^b is constant: a^b -> 0
        return 0
    elseif db == 0           # if b is constant: a^b -> b*da*a^(b-1)
        return :( $b* $da * ($a ^ ($b - 1)) )
    else                     # a(x)^b(x) -> http://www.wolframalpha.com/input/?source=frontpage-immediate-access&i=d%2Fdx+a(x)%5Eb(x)
                             # I don't know, just ask Wolfram Alpha!
        :( $a ^ ($b - 1) * ($da * $b + $a * log($a) * $db) )
    end
end

# Product Rule
# d/dx (f * g) = (d/dx f) * g + f * (d/dx g)
# d/dx (f * g * h) = (d/dx f) * g * h + f * (d/dx g) * h + ...
function D(::SymbolFun{:*}, args, x)
    N = length(args)
    outer_args = Vector{Any}(N)
    for i in 1:N
        inner_args = Vector{Any}(N)
        for j in 1:N
            i == j ? inner_args[j] = D(args[j], x) : inner_args[j] = args[j]
        end
        outer_args[i] = Expr(:call, :*, inner_args...)
    end
    Expr(:call, :+, outer_args...)
end

# The Quotient Rule
# d/dx (f / g) = ((d/dx f) * g - f * (d/dx g)) / g^2
function D(::SymbolFun{:/}, args, x)
    f, g = args[1], args[2]
    df, dg = D(f, x), D(g, x)
    if df == 0 && dg == 0
        return 0
    elseif df == 0
        return :( -$dg * $f / $g^2 )
    elseif dg == 0
        return :( $df / $g )
    else
        return :( ($df * $g - $f * $dg) / $g^2 )
    end
end

function D(::SymbolFun{:+}, args, x)
    terms = Any[:+]
    for y in args
        dx = D(y, x)
        if dx != 0
            push!(terms, dx)
        end
    end
    if (length(terms) == 1)
        return 0
    elseif (length(terms) == 2)
        return terms[2]
    else
        return Expr(:call, terms...)
    end
end

function D(::SymbolFun{:-}, args, x)
    terms = Any[:-]
    term1 = D(args[1], x)
    push!(terms, term1)
    for y in args[2:end]
        dx = D(y, x)
        if dx != 0
            push!(terms, dx)
        end
    end
    if term1 != 0 && length(terms) == 2 && length(args) >= 2
        return term1
    elseif (term1 == 0 && length(terms) == 2)
        return 0
    else
        return Expr(:call, terms...)
    end
end

D(fun::SymbolFun{T}, args, x) where T = error("$(string(T)) function is not supported!")

[1:57]
 ```julia> ex = :(11x^3 + 12 + 13x^5 + 14x^9)
:(11 * x ^ 3 + 12 + 13 * x ^ 5 + 14 * x ^ 9)

julia> Meta.show_sexpr(ex)
(:call, :+, (:call, :*, 11, (:call, :^, :x, 3)), 12, (:call, :*, 13, (:call, :^,
:x, 5)), (:call, :*, 14, (:call, :^, :x, 9)))```


using LinearAlgebraicRepresentation
using LARVIEW

VOID = [[]], [[1]]

# example 1

V = [[0,0] [1,0] [2,0] [0,1] [1,1] [2,1] [0,2] [1,2] [2,2]];
FV = [[1,2,4],[2,3,5],[3,5,6],[4,5,7],[5,7,8],[6,8,9]];
pattern = repeat([1,2,-3],outer=4);
model = (V,FV);
W,FW = LinearAlgebraicRepresentation.extrudeSimplicial(model, pattern);
LARVIEW.view(W,FW)

# example 2

model = LinearAlgebraicRepresentation.extrudeSimplicial( VOID, ones(10) )
LARVIEW.view(model)
model = LinearAlgebraicRepresentation.extrudeSimplicial( model, ones(10) )
LARVIEW.view(model)
model = LinearAlgebraicRepresentation.extrudeSimplicial( model, ones(10) )
LARVIEW.view(model)

# example 3

model = LinearAlgebraicRepresentation.extrudeSimplicial( VOID, repeat([1,-1],outer=10) )
LARVIEW.view(model)
model = LinearAlgebraicRepresentation.extrudeSimplicial( model, repeat([1,-1],outer=10) )
LARVIEW.view(model)

# example 4

grid_2d = LinearAlgebraicRepresentation.simplexGrid([3,3])
LARVIEW.view(grid_2d)
grid_3d = LinearAlgebraicRepresentation.simplexGrid([2,3,4])
LARVIEW.view(grid_3d)
V,CV = LinearAlgebraicRepresentation.simplexGrid([1,1,1])
LARVIEW.view(V,CV)

# example 5

SK2 = LinearAlgebraicRepresentation.simplexFacets(CV)
LARVIEW.view(V, SK2)
SK1 = LinearAlgebraicRepresentation.simplexFacets(SK2)
LARVIEW.view(V, SK1)


#=
We shall say that an n-digit number is pandigital if it makes use of all the digits 1 to n exactly once; for example, the 5-digit number, 15234, is 1 through 5 pandigital.

The product 7254 is unusual, as the identity, 39 × 186 = 7254, containing multiplicand, multiplier, and product is 1 through 9 pandigital.

Find the sum of all products whose multiplicand/multiplier/product identity can be written as a 1 through 9 pandigital.

HINT: Some products can be obtained in more than one way so be sure to only include it once in your sum.
=#
include("projecteulerutils.jl")
using Combinatorics
function calc()
  matches = Int64[]
  for c in permutations(collect(1:9))
    c1,c2,c3 = c[1:2],c[3:5],c[6:9] # 2x3
    d1,d2,d3 = c[1:1],c[2:5], c[6:9] # 1x4
    if nd(c1) * nd(c2) == nd(c3)
      push!(matches, nd(c3))
    end
    if nd(d1) * nd(d2) == nd(d3)
      push!(matches, nd(d3))
    end
  end
  sum(unique(matches))
end
@time println(calc())


"This function computes finite difference stencil coefficients which approximate the
kth derivative of a function at a point x̄ using a linear combination of evaluations at x_i."

function fdcoeffV(k,x̄,x)
    n = length(x)
    if n <= k
        error("Too few points in the stencil")
    end
    A = zeros(n,n)
    A[1,:] = ones(n)
    for i = 2:n
        A[i,:] = @. (x - x̄)^(i-1) / factorial(i-1) # ∑ a_i * (x_i - x̄)^i/i! = b_i
    end
    b = zeros(n,1)
    b[k+1] = 1
    return vec(A\b)
end


#!/usr/bin/env julia

export pe15

pe15(n::Integer=20)=binomial(2n,n)

if !haskey(ENV,"PROJECT_EULER_WITHOUT_RESULT")
    pe15()|>print
end


using Test

using MarkovChains
@testset "solve" begin
    @testset "irreducible1" begin
        chain = ContMarkovChain()
        n0 = add_state!(chain)
        n1 = add_state!(chain)
        n2 = add_state!(chain)
        n3 = add_state!(chain)
        add_transition!(chain, n0, n1, 1.0)
        add_transition!(chain, n1, n2, 1.0)
        add_transition!(chain, n2, n3, 1.0)
        add_transition!(chain, n3, n2, 3.0)
        add_transition!(chain, n2, n1, 2.0)
        add_transition!(chain, n1, n0, 1.0)
        init_prob = [0.1, 0.9, 0.0, 0.0]
        ts_sol = solve(chain, init_prob, 0.0)
        @test ts_sol.prob ≈ [0.1, 0.9, 0.0, 0.0]
        @test ts_sol.cumtime ≈ [0.0, 0.0, 0.0, 0.0]

        ss_sol = solve(chain, init_prob, Inf)
        @test ss_sol.prob ≈ [0.375, 0.375, 0.1875, 0.0625]
        @test ss_sol.cumtime == [Inf, Inf, Inf, Inf]
    end
    @testset "acyclic1" begin
    chain = ContMarkovChain()
    n1 = add_state!(chain)
    n2 = add_state!(chain)
    n3 = add_state!(chain)
    add_transition!(chain, n1, n2, 2.0)
    add_transition!(chain, n2, n3, 4.0)
    init_prob = [1.0, 0.0, 0.0]
    ts_sol = solve(chain, init_prob, 0.0)
    @test ts_sol.prob ≈ [1.0, 0.0, 0.0]

    ts_sol = solve(chain, init_prob, 1.0)
    @test state_prob(ts_sol, 1) < 1.0
    @test state_prob(ts_sol, 2) > 0.0
    @test state_prob(ts_sol, 3) > 0.0

    ss_sol = solve(chain, init_prob, Inf)
    @test state_cumtime(ss_sol, 1) == 0.5
    @test state_cumtime(ss_sol, 2) == 0.25
    @test state_cumtime(ss_sol, 3) == Inf
    @test ss_sol.prob ≈ [0.0, 0.0, 1.0]

    end
end


using LinearAlgebraicRepresentation, ViewerGL, SparseArrays
Lar = LinearAlgebraicRepresentation; GL = ViewerGL

using Revise, OhMyREPL

wall = Lar.svg2lar("../../test/svg/assembly/wall.svg", flag=false)
openings = Lar.svg2lar("../../test/svg/assembly/openings.svg", flag=false)
rectangle = Lar.svg2lar("../../test/svg/assembly/rectangle.svg", flag=false)
box = Lar.svg2lar("../../test/svg/assembly/box.svg", flag=false)

assembly = Lar.Struct([wall , openings, rectangle, box])
V,EV = Lar.struct2lar(assembly)
GL.VIEW([ GL.GLFrame2, GL.GLGrid(V,EV, GL.COLORS[1],1) ]);
W, copEV, copFE, boolmatrix = Lar.bool2d(assembly)
@show Matrix(boolmatrix)

innerpoints = Lar.internalpoints2d(W,copEV,copFE[1:end,:])
points = convert(Array{Float64,2}, hcat(innerpoints...)')
GL.VIEW([ GL.GLFrame2, GL.GLGrid(V,EV, GL.COLORS[1],1), GL.GLPoints(points) ]);

A = boolmatrix[:,1]
B = boolmatrix[:,2]
C = boolmatrix[:,3]
D = boolmatrix[:,4]
expr = ( D .& .!(.&(A, .!B, .!C)) )# A - B - C
formula = Matrix(copFE)' * Int.(expr) # coord vector of Faces
V = convert(Lar.Points,W')
EV = Lar.cop2lar(copEV)
EVmin = [ev for (k,ev) in enumerate(EV) if abs(formula[k])==1 ]
V = Lar.normalize(V)
V = Lar.normalize(V)
GL.VIEW([ GL.GLFrame2, GL.GLGrid(V,EVmin, GL.COLORS[1],1) ]);

VV = [[k] for k in 1:size(V,2)];
model = (V,[VV, EV])
GL.VIEW( GL.numbering(.1)( model,GL.COLORS[1],0.1 ) );


using DiffEqOperators, Setfield, Parameters
using LinearAlgebra, Plots, SparseArrays
using BifurcationKit

plotsol(x, Nx=Nx, Ny=Ny) = heatmap(reshape(Array(x), Nx, Ny)', color=:viridis)
plotsol!(x, Nx=Nx, Ny=Ny; kwargs...) = heatmap!(reshape(Array(x), Nx, Ny)'; color=:viridis, kwargs...)

Nx = 151
	Ny = 100
	lx = 8pi
	ly = 2*2pi/sqrt(3)

function Laplacian2D(Nx, Ny, lx, ly, bc = :Neumann)
	hx = 2lx/Nx
	hy = 2ly/Ny
	D2x = CenteredDifference(2, 2, hx, Nx)
	D2y = CenteredDifference(2, 2, hy, Ny)
	if bc == :Dirichlet
		Qx = Dirichlet0BC(typeof(hx))
		Qy = Dirichlet0BC(typeof(hy))
	elseif bc == :Neumann
		Qx = Neumann0BC(hx)
		Qy = Neumann0BC(hy)
	elseif bc == :Periodic
		Qx = PeriodicBC(hx)
		Qy = PeriodicBC(hy)
	end
	# @show norm(D2x - Circulant(D2x[1,:]))
	A = kron(sparse(I, Ny, Ny), sparse(D2x * Qx)[1]) + kron(sparse(D2y * Qy)[1], sparse(I, Nx, Nx))
	return A, D2x
end

function F_sh(u, p)
	@unpack l, ν, L1 = p
	return -L1 * u .+ (l .* u .+ ν .* u.^2 .- u.^3)
end

function dF_sh(u, p)
	@unpack l, ν, L1 = p
	return -L1 .+ spdiagm(0 => l .+ 2 .* ν .* u .- 3 .* u.^2)
end

d2F_sh(u, p, dx1, dx2) = (2 .* p.ν .* dx2 .- 6 .* dx2 .* u) .* dx1
d3F_sh(u, p, dx1, dx2, dx3) = (-6 .* dx2 .* dx3) .* dx1
jet = (F_sh, dF_sh, d2F_sh, d3F_sh)

X = -lx .+ 2lx/(Nx) * collect(0:Nx-1)
Y = -ly .+ 2ly/(Ny) * collect(0:Ny-1)

sol0 = [(cos(x) .+ cos(x/2) * cos(sqrt(3) * y/2) ) for x in X, y in Y]
	sol0 .= sol0 .- minimum(vec(sol0))
	sol0 ./= maximum(vec(sol0))
	sol0 = sol0 .- 0.25
	sol0 .*= 1.7
	heatmap(sol0', color=:viridis)

Δ, D2x = Laplacian2D(Nx, Ny, lx, ly, :Neumann)
L1 = (I + Δ)^2
par = (l = -0.1, ν = 1.3, L1 = L1)

optnew = NewtonPar(verbose = true, tol = 1e-8, maxIter = 20)
# optnew = NewtonPar(verbose = true, tol = 1e-8, maxIter = 20, eigsolver = EigArpack(0.5, :LM))
	sol_hexa, hist, flag = @time newton(F_sh, dF_sh, vec(sol0), par, optnew)
	println("--> norm(sol) = ", norm(sol_hexa, Inf64))
	plotsol(sol_hexa)


# Copyright (c) 2021, Eric Sabo
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree.

function ord(n::Integer, q::Integer)
    if q <= 0 || n <= 0
        error("q and n both need to be positive. Passed: q = $q, n = $n")
    end

    # finite stop instead of while
    for i = 1:200
        if mod(BigInt(q)^i, n) == 1
            return i
        end
    end

    error("Unable to compute order($q, $n).")
end

function cyclotomiccoset(x::Integer, q::Integer, n::Integer, verbose::Bool=false)
    # keep small order assumption for now
    temp = [mod(x, n)]
    for i = 0:(n - 1)
        y = mod(temp[end] * q, n)
        if y ∉ temp
            append!(temp, y)
        else
            break
        end
    end
    sort!(temp)
    len = length(temp)

    if verbose
        print("C_$x = {")
        for (i, y) in enumerate(temp)
            if i != len
                print("$y, ")
            else
                println("$y}")
            end
        end
    end

    return temp
end

function allcyclotomiccosets(q::Integer, n::Integer, verbose::Bool=false)
    if n % q == 0
        error("Cyclotomic coset requires gcd(n, q) = 1")
    end

    arr = [[0]]
    for x in 1:(n - 1)
        found = false
        for a in arr
            if x ∈ a
                found = true
                break
            end
        end

        if !found
            Cx = cyclotomiccoset(x, q, n, false)
            push!(arr, Cx)
        end
    end
    #
    # sort!(arr, by=x->x[1])

    if verbose
        for Cx in arr
            len = length(Cx)
            print("C_$(Cx[1]) = {")
            for (i, y) in enumerate(Cx)
                if i != len
                    print("$y, ")
                else
                    println("$y}")
                end
            end
        end
    end

    if sort!(vcat(arr...)) != [i for i in 0:(n - 1)]
        error("Missed some")
    end

    return arr
end

function complementqcosets(q::Integer, n::Integer, qcosets::Vector{Vector{Int64}})
    all = allcyclotomiccosets(q, n)
    compcosets = Vector{Vector{Int64}}()
    for a in all
        # if a != [0]
            found = false
            for b in qcosets
                if a[1] == b[1]
                    found = true
                    break
                end
            end

            if !found
                push!(compcosets, a)
            end
        # end
    end

    return compcosets
end

function qcosetpairings(arr::Vector{Vector{Int64}}, n::Integer)
    cosetreplist = Vector{Tuple{Int64, Int64}}()
    cosetpairlist = Vector{Tuple{Vector{Int64}, Vector{Int64}}}()
    for a in arr
        found = false
        for pair in cosetreplist
            if a[1] == pair[1] || a[1] == pair[2]
                found = true
            end
        end

        if !found
            neg = sort!([mod(n - i, n) for i in a])
            if neg == a
                push!(cosetreplist, (a[1], a[1]))
                push!(cosetpairlist, (a, a))
            else
                for b in arr
                    if neg == b
                        push!(cosetreplist, (a[1], b[1]))
                        push!(cosetpairlist, (a, b))
                        break
                    end
                end
            end
        end
    end

    return cosetpairlist, cosetreplist
end

function qcosetpairings(q::Integer, n::Integer)
    arr = allcyclotomiccosets(q, n, false)
    return qcosetpairings(arr, n)
end

function qcosettable(a::Integer, b::Integer, q::Integer)
    for n in a:b
        if n % q != 0
            println("n = $n")
            allcyclotomiccosets(q, n, true)
            println(" ")
        end
    end
end

function dualqcosets(q::Integer, n::Integer, qcosets::Vector{Vector{Int64}})
    compcosets = complementqcosets(q, n, qcosets)
    for a in compcosets
        for (i, x) in enumerate(a)
            a[i] = mod(n - a[i], n)
        end
        sort!(a)
    end

    return compcosets
end


#Constant mean function

@doc """
# Description
Constructor for the constant mean function

m(x) = β
# Arguments:
* `β::Float64`: Constant
""" ->
type MeanConst <: Mean
    β::Float64
    MeanConst(β::Float64) = new(β)
end

@doc """
# Description
Constructor for the zero mean function

m(x) = 0
""" ->
MeanZero() = MeanConst(0.0)

mean(mConst::MeanConst,x::Matrix{Float64}) =  fill(mConst.β, size(x,2))

get_params(mConst::MeanConst) = Float64[mConst.β]
get_param_names(::MeanConst) = [:β]
num_params(mConst::MeanConst) = 1
function set_params!(mConst::MeanConst, hyp::Vector{Float64})
    length(hyp) == 1 || throw(ArgumentError("Constant mean function only has 1 parameter"))
    mConst.β = hyp[1]
end
function grad_mean(mConst::MeanConst, x::Vector{Float64})
    dM_theta = ones(size(x,2))
    return dM_theta
end


#Essa função de eliminação retorna U, depois de fazer a decomposição LU.
#Função faz pivotamento.

############
#Observação#
############

# Para usar essa função é necessario mandar uma matriz AA que seja do tipo float
# tentei fazer a conversão de tipo porem os resultados saiam como se fossem um número inteiro.

function eliminacao01(A)
	A
	epsilon = 0.00001;
	(m, n) = size(A);

	for i = 1:n-1
		pivot = A[i,i];

		if abs(pivot) < epsilon

			for k = i+1:m
				if abs(A[k,i] > epsilon)

					pivot = A[k,i];
					auxlinha = copy(A[k,:]);
					A[k,:] = copy(A[i,:]);
					A[i,:] = auxlinha;
					break

				end
			end
		end

		for j = i+1:m

			mult = A[j,i]/pivot;
			A[j,:] = A[j,:] - mult*A[i,:];

		end

	end

	return A;

end


# ------------------------------------------------------------
# Reconstruction of impulse responses

using GeneralizedSampling

J = 3
M = 2^(J+1)
wname = "db2"

xi = GeneralizedSampling.grid(M, 0.5)
f = FourScalingFunc(xi, wname, J)

T = Freq2Wave(xi, wname, J)
wcoef = T \ f

println("Reconstructing a ", wname, " function from its Fourier measurements.")
println("Reconstructed coefficients is a unit vector:")
display(wcoef)

# ------------------------------------------------------------
# Plot reconstruction

using IntervalWavelets

x, yw = weval( real(wcoef), wname, 10 )

using Plots

p = plot(x, yw, label=wname)
display(p)


# p34.jl - Allen-Cahn eq. u_t = eps*u_xx+u-u^3, u(-1)=-1, u(1)=1
#         (compare p6.jl and p32.jl)

# Differentiation matrix and initial data:
N = 20; (D,x) = cheb(N); D2 = D^2;     # use full-size matrix
D2[[1,N+1],:] = 0;                     # for convenience
eps = 0.01; dt = min(.01,50/(N^4*eps));
t = 0.0; v = @. .53*x + .47*sin(-1.5*pi*x);

# Solve PDE by Euler formula and plot results:
tmax = 100; tplot = 2; nplots = round(Int,tmax/tplot);
plotgap = round(Int,tplot/dt); dt = tplot/plotgap;
xx = -1:.025:1; vv = polyval(polyfit(x,v),xx);
plotdata = [vv zeros(length(xx),nplots)]; tdata = t;
for i = 1:nplots
    for n = 1:plotgap
        t = t+dt; v = v + dt*(eps*D2*(v-x) + v - v.^3);    # Euler
    end
    vv = polyval(polyfit(x,v),xx);
    plotdata[:,i+1] = vv; tdata = [tdata; t];
end
clf();
surf(xx,tdata,plotdata'); grid(true);
xlim(-1,1); ylim(0,tmax); zlim(-1,1);
gca()[:view_init](55,-150);
xlabel("x"); ylabel("t"); zlabel("u");


using DifferentialEquations
using ParameterizedFunctions

ball! = @ode_def BallBounce begin
  dy =  v
  dv = -g
end g

function condition(u,t,integrator)
  u[1]
end

function affect!(integrator)
    integrator.u[2] = -integrator.p[2] * integrator.u[2]
end

bounce_cb = ContinuousCallback(condition,affect!)

u0 = [50.0,0.0]
tspan = (0.0,15.0)
p = (9.8,0.9)
prob = ODEProblem(ball!,u0,tspan,p,callback=bounce_cb)

sol = solve(prob,Tsit5())
using Plots; gr()
plot(sol)

##########################################################

function condition_kick(u,t,integrator)
    t == 2
end

function affect_kick!(integrator)
    integrator.u[2] += 50
end

kick_cb = DiscreteCallback(condition_kick,affect_kick!)
u0 = [50.0,0.0]
tspan = (0.0,10.0)
p = (9.8,0.9)
prob = ODEProblem(ball!,u0,tspan,p,callback=kick_cb)

sol = solve(prob,Tsit5(),tstops=[2.0])
plot(sol)


struct SqWasserstein <: DistributionsSemiMetric end

# result type (e.g., for pairwise computations)
function Distances.result_type(
    ::SqWasserstein, ::Type{T1}, ::Type{T2}
) where {T1<:Real,T2<:Real}
    return promote_type(T1, T2)
end

# evaluations for normal distributions
function (::SqWasserstein)(a::Normal, b::Normal)
    μa, σa = params(a)
    μb, σb = params(b)
    return abs2(μa - μb) + abs2(σa - σb)
end

function (::SqWasserstein)(a::AbstractMvNormal, b::AbstractMvNormal)
    μ1 = mean(a)
    μ2 = mean(b)
    Σ1 = cov(a)
    Σ2 = cov(b)
    return Distances.sqeuclidean(μ1, μ2) + OT.sqbures(Σ1, Σ2)
end

function (::SqWasserstein)(a::MvNormal, b::MvNormal)
    μa, Σa = params(a)
    μb, Σb = params(b)
    return Distances.sqeuclidean(μa, μb) + OT.sqbures(Σa, Σb)
end

# evaluations for Laplace distributions
function (::SqWasserstein)(a::Laplace, b::Laplace)
    μa, βa = params(a)
    μb, βb = params(b)
    return abs2(μa - μb) + 2 * abs2(βa - βb)
end

# Wasserstein 2 distance
struct Wasserstein <: DistributionsMetric end

# result type (e.g., for pairwise computations)
function Distances.result_type(
    ::Wasserstein, ::Type{T1}, ::Type{T2}
) where {T1<:Real,T2<:Real}
    return float(promote_type(T1, T2))
end

function (::Wasserstein)(a::Distribution, b::Distribution)
    return sqrt(SqWasserstein()(a, b))
end

# Mixture Wasserstein distances
struct SqMixtureWasserstein{S} <: DistributionsSemiMetric
    lpsolver::S
end
struct MixtureWasserstein{S} <: DistributionsMetric
    lpsolver::S
end
SqMixtureWasserstein() = SqMixtureWasserstein(Tulip.Optimizer())
MixtureWasserstein() = MixtureWasserstein(Tulip.Optimizer())

# result type (e.g., for pairwise computations)
function Distances.result_type(
    ::SqMixtureWasserstein, ::Type{T1}, ::Type{T2}
) where {T1<:Real,T2<:Real}
    return promote_type(T1, T2)
end
function Distances.result_type(
    ::MixtureWasserstein, ::Type{T1}, ::Type{T2}
) where {T1<:Real,T2<:Real}
    return float(promote_type(T1, T2))
end

function (s::SqMixtureWasserstein)(a::AbstractMixtureModel, b::AbstractMixtureModel)
    C = Distances.pairwise(SqWasserstein(), components(a), components(b))
    return OT.emd2(probs(a), probs(b), C, deepcopy(s.lpsolver))
end

function (m::MixtureWasserstein)(a::AbstractMixtureModel, b::AbstractMixtureModel)
    return sqrt(SqMixtureWasserstein(m.lpsolver)(a, b))
end


using ChebyshevPicardIteration
using DifferentialEquations
using Plots

# First Test
ϵ = 0.001
f(t,y) = cos(t .+ ϵ*y)

t0 = 0
tf = 256*π
y0 = 1

# Integrate with Chebyshev-Picard Iteration
(ts,ys) = Integrate(f, y0, (t0, tf), 200, 200, 1e-20, 1000)

# Integrate with DifferentialEquations.jl
prob = ODEProblem((y,p,t) -> f(t,y), y0, (t0, tf))
sol = solve(prob, Tsit5(), reltol = 1e-8, abstol = 1e-8)

# Analytic Solution
γ = ϵ/(1 + sqrt(1 - ϵ^2))
α = 2*ϵ/(1 - ϵ + sqrt(1 - ϵ^2))
β = tan(ϵ*y0/2)/(1 + α)
ϕ(t) = 0.5*(1 - γ*ϵ)*t
σ(t) = α*(sin(ϕ(t)) + β*cos(ϕ(t)))
y(t) = -γ*t + (2/ϵ)*atan((β + σ(t)*cos(ϕ(t)))/(1 + σ(t)*sin(ϕ(t))))

plot(sol.t,sol.u - map(y,sol.t))
plot!(ts,ys .- map(y,ts))

# Second Test
#g = 9.81
#k = 0.1
#m = 0.2
#f2(t,y) = [y[2], -k*y[1]/m + g]

#y0 = [5, 0]

# Integrate with Chebyshev-Picard Iteration
#(ts,ys) = Integrate(f2, y0, (t0, tf), 80, 80, 1e-14, 1000)

# Integrate with DifferentialEquations.jl
#prob = ODEProblem((y,p,t) -> f2(t,y), y0, (t0, tf))
#sol = solve(prob, Tsit5(), reltol = 1e-8, abstol = 1e-8)

#plot(sol)
#plot!(ts,ys)


# Rational polynomial interpolation

"""
Linear Rational LeastSquares

The following curvit is done:

`y = p(x) / q(x)`

where `p(x)` and `q(x)` are polynomials.

The linear case is solved by doing a least square fit on

`y*q(x) = p(x)`

where the zero order term o `q(x)` is assumed to be 1.
"""
function linear_rational_fit(x::AbstractVector{T}, y::AbstractVector{T}, p, q) where T<:Number
    n = size(x,1)
    A = zeros(T, n, q+p+1)
    for i = 1:n
        A[i,1] = one(T)
        for k = 1:p
            A[i,k+1] = x[i]^k
        end
        for k = 1:q
            A[i, p+1+k] = -y[i] * x[i]^k
        end
    end

    A \ y
#    coefs[1:p+1], [1.0; coefs[p+2:end]]

end

"""
# Type defining a rational polynomial

A rational polynomial is the ratio of two polynomials
and it is often useful in approximating functions.
"""
struct RationalPoly{T<:Number} <: LeastSquares
    num::Poly{T}
    den::Poly{T}
end
RationalPoly(a::AbstractVector{T}, b::AbstractVector{T}) where {T<:Number} = RationalPoly(Poly(a), Poly(b))
RationalPoly(p::Integer, q::Integer, ::Type{T}=Float64) where {T<:Number} = RationalPoly{T}(Poly(zeros(T,p+1)), Poly(zeros(T,q+1)))
RationalPoly(coefs::AbstractVector{T}, p, q) where {T<:Number} = RationalPoly(coefs[1:p+1],[1.0; coefs[p+2:end]])

"Evaluate a rational polynomial"
ratval(r::RationalPoly{T}, x) where {T<:Number} = polyval(r.num, x) ./ polyval(r.den, x)

"`call` overload for calling directly `ratval`"
(r::RationalPoly)(x) = ratval(r, x)

"Auxiliary function used in nonlinear least squares"
function make_rat_fun(p, q)
    r = RationalPoly(p, q, Float64)

    function(x, a)
        for i=0:p
            r.num[i] = a[i+1]
        end
        r.den[0] = 1
        for i = 1:q
            r.den[i] = a[p+1+i]
        end
        polyval(r.num, x[1]) / polyval(r.den, x[1]) - x[2]
    end

end

"""
# Carry out a nonlinear least squares of rational polynomials

Find the polynomial coefficients that best approximate
the points given by `x` and `y`.
"""
function rational_fit(x, y, p, q, eps=1e-8, maxiter=200)

    coefs0  = linear_rational_fit(x, y, p, q)

    fun = make_rat_fun(p, q)

    coefs, converged, niter = nonlinear_fit(hcat(x, y), fun, coefs0, eps, maxiter)

    coefs

end

function curve_fit(::Type{RationalPoly}, x, y, p, q, eps=1e-8, maxiter=200)
    RationalPoly(rational_fit(x, y, p, q, eps, maxiter), p, q)
end


module TestMetaInfo_VariableTimestep

using Test
using Mimi

import Mimi:
    compdef, reset_compdefs, first_period, last_period

reset_compdefs()

@defcomp ch4forcing1 begin
    c_N2Oconcentration = Parameter(index=[time],unit="ppbv")
    c_CH4concentration = Parameter(index=[time],unit="ppbv")
    f0_CH4baseforcing = Parameter(unit="W/m2")
    fslope_CH4forcingslope = Parameter(unit="W/m2")
    c0_baseN2Oconc = Parameter(unit="ppbv")
    c0_baseCH4conc = Parameter(unit="ppbv")
    f_CH4forcing = Variable(index=[time],unit="W/m2")
    over_baseoverlap = Variable(unit="W/m2")
    over = Variable(index=[time],unit="W/m2")

    function run_timestep(p, v, d, t)
        #from p.16 in Hope 2009
        if is_first(t)
            #calculate baseline forcing overlap in first time period
            v.over_baseoverlap = -0.47 * log(1 + 2.0e-5 * (p.c0_baseN2Oconc * p.c0_baseCH4conc)^0.75 + 5.3e-15 * p.c0_baseCH4conc * (p.c0_baseCH4conc * p.c0_baseN2Oconc)^1.52)
        end

        v.over[t] = -0.47 * log(1 + 2.0e-5 * (p.c_CH4concentration[t] * p.c0_baseN2Oconc)^0.75 + 5.3e-15 * p.c_CH4concentration[t] * (p.c0_baseN2Oconc * p.c_CH4concentration[t])^1.52)
        v.f_CH4forcing[t] = p.f0_CH4baseforcing + p.fslope_CH4forcingslope * (sqrt(p.c_CH4concentration[t]) - sqrt(p.c0_baseCH4conc)) + v.over[t] - v.over_baseoverlap
    end
end

@Mimi.defmodel test_model begin
    index[time] = [2010:2100; 2105:5:2200]
    component(ch4forcing1)
    component(ch4forcing1, ch4forcing2) # add another one with a different name
end

c1 = compdef(test_model, :ch4forcing1)
c2 = compdef(test_model, :ch4forcing2)

@test c1 == compdef(:ch4forcing1)
@test_throws ErrorException compdef(:missingcomp)

@test c2.comp_id.module_name == :TestMetaInfo_VariableTimestep
@test c2.comp_id.comp_name == :ch4forcing1
@test c2.name == :ch4forcing2

vars = Mimi.variable_names(c2)
@test length(vars) == 3

pars = Mimi.parameter_names(c2)
@test length(pars) == 6

@test first_period(test_model.md, c1) == 2010
@test last_period(test_model.md, c1) == 2200

end # module


#=
The series, 1^1 + 2^2 + 3^3 + ... + 10^10 = 10405071317.

Find the last ten digits of the series, 1^1 + 2^2 + 3^3 + ... + 1000^1000.
=#
function calc()
  a = [i^BigInt(i) for i in 1:1000]
  d = reverse(digits(sum(a))[1:10])
  result = ""
  for digit in d
    result *= "$digit"
  end
  result
end
@time println(calc())


using JuMP, Cbc, CSV, PrettyTables

#Importar datos de Excel
nodes_import = CSV.read("Node_Coordinates.csv", header = true)

#Definir que hay en cada columna del excel y cuantos nodos hay en total
num_nodes = size(nodes_import,1)
idcol = 1
Xcol = 2
Ycol = 3

#Generar la Matriz de distancias a partir del numero de nodos
distance_matrix = Array{Float64}(undef, (num_nodes, num_nodes))
#Llenar la matriz de distancias entre todos los puntos usando Pitagoras
for n in 1:num_nodes
    for s in 1:num_nodes
        distance_matrix[n,s] = sqrt((nodes_import[n,Ycol] - nodes_import[s,Ycol])^2 +
        (nodes_import[n,Xcol] - nodes_import[s,Xcol])^2)
    end
end

println(pretty_table(nodes_import))
println(distance_matrix)

mtzModel = Model(Cbc.Optimizer)
@variable(mtzModel, rutas[1:num_nodes,1:num_nodes], binary=true)
@variable(mtzModel, 2 <= u[2:num_nodes] <= num_nodes)
@objective(mtzModel, Min, sum(rutas[:,:].*distance_matrix[:,:]))
for i = 1:num_nodes
    @constraint(mtzModel, sum(rutas[i,:]) == 1)
    @constraint(mtzModel, sum(rutas[:,i]) == 1)
    @constraint(mtzModel, rutas[i,i] == 0)
end
for i = 2:num_nodes
    for j = 2:num_nodes
        @constraint(mtzModel, u[i]-u[j]+1 <= (num_nodes-1)*(1-rutas[i,j]))
    end
end
stats = JuMP.optimize!(mtzModel)
print(pretty_table(JuMP.value.(rutas)))


using MAT
using PyPlot
using LinearAlgebra
using ADCME
using AdFem
using Distributions

sigma = 0.01
q = (1e-1)^2
m = n = 20
h = 0.1

for sigma in [0.0 0.01]

r = (1e-2)^2

d = matread("simdata/nn2-data-$sigma.mat")
y_, hs, H, s, u_est, kappa = d["yobs"], d["hs"], d["H"], d["s"], d["u_est"], d["kappavalue"]

R = r*diagm(0=>ones(length(hs)))
Q = q*diagm(0=>ones(length(s)))
μ, Σ = uqnlin(y_, hs, H, R, s, Q)
σ = diag(Σ)

umax = maximum(u_est)
xc = LinRange(0, umax, 100)|>Array
u2 = @. 0.1 + 1/(1+xc^2) + 100xc^2
figure()
plot(xc, u2, "-", linewidth=2, label="Referencee")

# k = s[1:4:end]
u1 = fem_to_fvm(u_est, m, n, h)
# Is = sortperm(u1)
# plot(u1[Is], k[Is], "+--")

z1 = μ[1:4:end]
σ1 = 2σ[1:4:end]
u1 = u1[Is]
z1 = z1[Is]
σ1 = σ1[Is]
plot(u1, z1, "--", linewidth=2, label="Posterior Mean")
fill_between(u1, z1-σ1, z1+σ1, alpha=0.3, color="orange", label="Uncertainty Region")

xlim(minimum(u1), maximum(u1))

xlabel("\$u\$")
ylabel("\$K(u)\$")
legend()
savefig("nn2-uq$sigma-1.png")

close("all")
plot(u1, σ1, "+")
xlabel("\$u\$")
ylabel("Standard Deviation")
savefig("nn2-uq$sigma-2.png")

end


#' ---
#' title: Rocket Control
#' ---

#' **Originally Contributed by**: Iain Dunning

#' This tutorial shows how to solve a nonlinear rocketry control problem.
#' The problem was drawn from the [COPS3](http://www.mcs.anl.gov/~more/cops/cops3.pdf) benchmark.

#' Our goal is to maximize the final altitude of a vertically launched rocket.
#' We can control the thrust of the rocket, and must take account of
#' the rocket mass, fuel consumption rate, gravity, and aerodynamic drag.

#' Let us consider the basic description of the model (for the full description,
#' including parameters for the rocket, see the COPS3 PDF)

#' ### Overview
#' We will use a discretized model of time, with a fixed number of time steps, $n$.
#' We will make the time step size $\Delta t$, and thus the final time $t_f = n \cdot \Delta t$, a variable in the problem.
#' To approximate the derivatives in the problem we will use the [trapezoidal rule](http://en.wikipedia.org/wiki/Trapezoidal_rule).

#' ### State and Control
#' We will have three state variables:
#'
#' * Velocity, $v$
#' * Altitude, $h$
#' * Mass of rocket and remaining fuel, $m$
#'
#' and a single control variable, thrust $T$.
#' Our goal is thus to maximize $h(t_f)$.
#' Each of these corresponds to a JuMP variable indexed by the time step.

#' ### Dynamics
#' We have three equations that control the dynamics of the rocket:
#'
#' Rate of ascent: $$h^\prime = v$$
#' Acceleration: $$v^\prime = \frac{T - D(h,v)}{m} - g(h)$$
#' Rate of mass loss: $$m^\prime = -\frac{T}{c}$$
#'
#' where drag $D(h,v)$ is a function of altitude and velocity, and gravity $g(h)$ is a function of altitude.

#' These forces are defined as
#'
#' $$D(h,v) = D_c v^2 exp\left( -h_c \left( \frac{h-h(0)}{h(0)} \right) \right)$$
#' and
#' $$g(h) = g_0 \left( \frac{h(0)}{h} \right)^2$$
#'
#' The three rate equations correspond to JuMP constraints,
#' and for convenience we will represent the forces with nonlinear expressions.

using JuMP, Ipopt

# Create JuMP model, using Ipopt as the solver
rocket = Model(with_optimizer(Ipopt.Optimizer, print_level=0))

# Constants
# Note that all parameters in the model have been normalized
# to be dimensionless. See the COPS3 paper for more info.
h_0 = 1    # Initial height
v_0 = 0    # Initial velocity
m_0 = 1    # Initial mass
g_0 = 1    # Gravity at the surface

T_c = 3.5  # Used for thrust
h_c = 500  # Used for drag
v_c = 620  # Used for drag
m_c = 0.6  # Fraction of initial mass left at end

c     = 0.5 * sqrt(g_0 * h_0)  # Thrust-to-fuel mass
m_f   = m_c * m_0            # Final mass
D_c   = 0.5 * v_c * m_0 / g_0    # Drag scaling
T_max = T_c * g_0 * m_0        # Maximum thrust

n = 800   # Time steps

@variables(rocket, begin
    Δt ≥ 0, (start = 1/n) # Time step
    # State variables
    v[1:n] ≥ 0            # Velocity
    h[1:n] ≥ h_0          # Height
    m_f ≤ m[1:n] ≤ m_0    # Mass
    # Control
    0 ≤ T[1:n] ≤ T_max    # Thrust
end)

# Objective: maximize altitude at end of time of flight
@objective(rocket, Max, h[n])

# Initial conditions
@constraints(rocket, begin
    v[1] == v_0
    h[1] == h_0
    m[1] == m_0
    m[n] == m_f
end)

# Forces
# Drag(h,v) = Dc v^2 exp( -hc * (h - h0) / h0 )
@NLexpression(rocket, drag[j = 1:n], D_c * (v[j]^2) * exp(-h_c * (h[j] - h_0) / h_0))
# Grav(h)   = go * (h0 / h)^2
@NLexpression(rocket, grav[j = 1:n], g_0 * (h_0 / h[j])^2)
# Time of flight
@NLexpression(rocket, t_f, Δt * n)

# Dynamics
for j in 2:n
    # h' = v

    # Rectangular integration
    # @NLconstraint(rocket, h[j] == h[j - 1] + Δt * v[j - 1])

    # Trapezoidal integration
    @NLconstraint(rocket,
        h[j] == h[j - 1] + 0.5 * Δt * (v[j] + v[j - 1]))

    # v' = (T-D(h,v))/m - g(h)

    # Rectangular integration
    # @NLconstraint(rocket, v[j] == v[j - 1] + Δt *(
    #                 (T[j - 1] - drag[j - 1]) / m[j - 1] - grav[j - 1]))

    # Trapezoidal integration
    @NLconstraint(rocket,
        v[j] == v[j-1] + 0.5 * Δt * (
            (T[j] - drag[j] - m[j] * grav[j]) / m[j] +
            (T[j - 1] - drag[j - 1] - m[j - 1] * grav[j - 1]) / m[j - 1]))

    # m' = -T/c

    # Rectangular integration
    # @NLconstraint(rocket, m[j] == m[j - 1] - Δt * T[j - 1] / c)

    # Trapezoidal integration
    @NLconstraint(rocket,
        m[j] == m[j - 1] - 0.5 * Δt * (T[j] + T[j-1]) / c)
end

# Solve for the control and state
println("Solving...")
status = optimize!(rocket)

# Display results
# println("Solver status: ", status)
println("Max height: ", objective_value(rocket))

#+

# Can visualize the state and control variables
using Gadfly

#+

h_plot = plot(x = (1:n) * value.(Δt), y = value.(h)[:], Geom.line,
                Guide.xlabel("Time (s)"), Guide.ylabel("Altitude"))
m_plot = plot(x = (1:n) * value.(Δt), y = value.(m)[:], Geom.line,
                Guide.xlabel("Time (s)"), Guide.ylabel("Mass"))
v_plot = plot(x = (1:n) * value.(Δt), y = value.(v)[:], Geom.line,
                Guide.xlabel("Time (s)"), Guide.ylabel("Velocity"))
T_plot = plot(x = (1:n) * value.(Δt), y = value.(T)[:], Geom.line,
                Guide.xlabel("Time (s)"), Guide.ylabel("Thrust"))
draw(SVG(6inch, 6inch), vstack(hstack(h_plot, m_plot), hstack(v_plot, T_plot)))


"""
    ω(M, k)

Return the `M`th root of unity raised to the `k`th power.
"""
@inline ω(M, k) = exp(-2im*π*k/M)


using Plots
#pyplot()
#plotlyjs()
#gui()

println("Plotting sine")
h = plot(sin, -2pi, pi, label = "sine function")
#h = plot(cos, -2pi, pi, label = "sine function")
#plot(tan, -2pi, pi, label = "sine function")
#show(h)


using DifferentialEquations, Plots, ParameterizedFunctions
gr()
lorenz = @ode_def Lorenz begin
  dx = σ*(y-x)
  dy = ρ*x-y-x*z
  dz = x*y-β*z
end σ β ρ

p = [10.0,8/3,28]
u0 = [1., 5., 10.]
tspan = (0., 100.)
prob = ODEProblem(lorenz, u0, tspan, p)
sol = solve(prob)

plot(sol)

plot(sol,vars=(:x,:y,:z))

plot(sol,vars=[:x])

plot(sol,vars=(1,2,3))
plot(sol,vars=[1])

plot(sol,linewidth=5,title="Solution to the linear ODE with a thick line",
xaxis="Time (t)",yaxis="u(t) (in mm)",label=["X","Y","Z"])

scatter(sol,vars=[:x])

plot(sol,vars=(1,2,3),denseplot=false)

plot(sol,vars=(1,2,3),plotdensity=100)

plot(sol,vars=(1,2,3),plotdensity=10000)

plot(sol,vars=(1,2,3))
scatter!(sol,vars=(1,2,3),plotdensity=100)

p = plot(sol,vars=(1,2,3))
scatter!(p,sol,vars=(1,2,3),plotdensity=100)
title!("I added a title")

using DiffEqTutorials
DiffEqTutorials.tutorial_footer(WEAVE_ARGS[:folder],WEAVE_ARGS[:file])


# William Kahan's method, accuracy is within 2 ulp

function csd_ad_minus_bc{T<:AbstractFloat}(a::T, b::T, c::T, d::T)
    bcHi  = b*c
    bcLo  = fma(-b, c,  bcHi)  # exact
    hi    = fma( a, d, -bcHi)  # close
    hi - bcLo                  # compensated
end

csd_ab_minus_cd{T<:AbstractFloat}(a::T, b::T, c::T, d::T) = ad_minus_bc(a,c,d,b)

function csdDet2x2{T<:AbstractFloat}(m2x2::Matrix{T})
   if size(k) != (2,2)
       throw(DomainError())
   end
   csd_ad_minus_bc(reshape(m2x2,4,1)...)
end

function csdCross3D{T<:AbstractFloat}(a::Vector{T}, b::Vector{T})
    a1,a2,a3 = a[1],a[2],a[3]
    b1,b2,b3 = b[1],b[2],b[3]

    x = ad_minus_bc(a2, a3, b2, b3)
    y = ad_minus_bc(a3, a1, b3, b1)
    z = ad_minus_bc(a1, a2, b1, b2)

    [x,y,z]
end


module GaussianMarkovRandomFields

using LDLFactorizations
import LDLFactorizations: LDLFactorization
using LinearAlgebra, SparseArrays
using Distributions
using Random
using Memoize # For recursive marginal variance implementation

export GMRF, cholesky_ldl, logdet_ldl, prec

function cholesky_ldl(A)
    F = ldl(A)
    L = cholesky(F)
    return L, F.P
end

function LinearAlgebra.cholesky(F::LDLFactorizations.LDLFactorization)
    # https://en.wikipedia.org/wiki/Cholesky_decomposition#LDL_decomposition
	# Need next two lines for type-stability, the `getproperty` method for LDLFactorization
	# objects is not type-stable
	d = getfield(F, :d)
	L = SparseMatrixCSC(F.n, F.n, F.Lp, F.Li, F.Lx)
    all(d .> 0) || PosDefException(0)
    L = ((L + I) * Diagonal(sqrt.(d)))#[invperm(F.P), invperm(F.P)]
    return L
end

function logdet_from_chol(L)
    return 2 * sum(log(L[i]) for i in diagind(L))
end

function logdet_ldl(M::AbstractMatrix)
    L, P = cholesky_ldl(M)
    return logdet_from_chol(L)
end

struct GMRF{Tv<:AbstractVector{<:Real},
        Tm<:AbstractMatrix{<:Real},
        Tl<:AbstractMatrix{<:Real}} <: AbstractMvNormal
    μ::Tv
    Q::Tm
    L::Tl
end

function GMRF(μ, Q)#::AbstractVector, Q::AbstractMatrix)
    n = LinearAlgebra.checksquare(Q)
    length(μ) == n || DimensionMismatch("The dimensions of μ and Q are inconsistent.")
    L, P = cholesky_ldl(Q)
    return GMRF(μ, Q, L)
end

function GMRF(Q::Tv) where {Tv <: AbstractMatrix{<:Real}}
    n = LinearAlgebra.checksquare(Q)
    return GMRF(zeros(eltype(Q), n), Q)
end

function GMRF(μ::AbstractVector, F::LDLFactorization)
	P = F.P
	L = cholesky(F)
	Q = (L * L')[invperm(P), invperm(P)]
    return GMRF(μ, Q, F.L)
end
GMRF(F::LDLFactorization) = GMRF(zeros(size(F.L, 1)), F)

function Distributions._logpdf(d::GMRF, x::AbstractVector{T}) where T
    k = length(d.μ)
    ld = logdet_from_chol(d.L)
    x0 = x .- d.μ
    return -0.5 * (-ld + dot(x0, d.Q, x0) + k*log(2pi))
end

Base.length(d::GMRF) = length(mean(d))
Base.eltype(d::GMRF) = eltype(mean(d))
prec(d::GMRF) = d.Q
Distributions.mean(d::GMRF) = d.μ

# Uses the recursive method of Rue (2005)
# Finds the "future" non-zero indices
function ℐ(L::AbstractSparseMatrix, i::Integer)
	Lcol = L[:, i]
	ind, val = findnz(Lcol)
	ind[ind .> i]
end
# Recursively calculate the marginal variances. Should be memoized to avoid
# duplicated calculations and (probably more importantly) stack overflows in
# large precision matrices.
@memoize function cov_element(L::AbstractSparseMatrix, i::Integer, j::Integer)
	# Reverse indices to calculate upper triangle entry of Σ, otherwise get zero below
	if (i > j)
		 (j, i) = (i, j)
	end
	Σij = (i == j) ? 1 / L[i, i]^2 : zero(L[i, i])
	for k in ℐ(L, i)
        # `cov_element` will be zero if k > j; indices are switched if
        # necessary; this is where recursion occurs.
		Σij -= 1 / L[i, i] * L[k, i] * cov_element(L, k, j)
	end
	Σij
end
function Distributions.var(d::GMRF)
    n = length(d)
    v = Vector{Float64}(undef, n)
    # Iterate *backwards*
    for idx in reverse(eachindex(v))
        v[idx] = cov_element(d.L, idx, idx)
    end
    return v
end

Distributions.cov(d::GMRF) = error( "ERROR: The covariance matrix of a GMRF `g` can often be " *
    "dense and can cause the computer to run out of memory. If you are sure you have "*
    "enough memory, you can invert the precision matrix with `inv(Matrix(prec(g)))`")

function Distributions._rand!(rng::Random.AbstractRNG, d::GMRF, x::AbstractArray)
    z = randn(rng, size(x))
    x .= mean(d) .+ d.L \ z
end

end # module


export project_rank!

function project_rank!(
                      x :: Array{TF,2},
                      r :: TI,
                      mode #not an option for matrices
                      ) where {TF<:Real,TI<:Integer}
"""
Project the matrix X onto the set of rank-r ( need r<min(n1,n2) ) matrices
outputs a vector. Uses singular value decomposition
"""
  F  = svd(x)
  x .= F.U[:,1:r] * diagm(0 => F.S[1:r])* F.Vt[1:r,:]
  x  = vec(x)
end

function project_rank!(
                      x :: Array{TF,3},
                      r :: TI,
                      mode ::Tuple{String,String}
                      ) where {TF<:Real,TI<:Integer}
"""
Project each slice (x,y or z) of the tensor (3D model) onto the set of rank-r ( need r<min(n1,n2) ) matrices
outputs a vector. Uses singular value decomposition
"""
if mode[1] == "slice"
  if mode[2] == "x" #project y-z slices, so one slice per gridpoint in the x direction
    Threads.@threads for i=1:size(x,1)
      F = svd(view(x,i,:,:))
      @inbounds x[i,:,:] .= F.U[:,1:r] * diagm(0 => F.S[1:r])* F.Vt[1:r,:]
    end
  elseif mode[2] == "y"
    Threads.@threads for i=1:size(x,2)
      F = svd(view(x,:,i,:))
      @inbounds x[:,i,:] .= F.U[:,1:r] * diagm(0 => F.S[1:r])* F.Vt[1:r,:]
    end
  elseif mode[2] == "z"
    Threads.@threads for i=1:size(x,3)
      F=svd(view(x,:,:,i))
      @inbounds x[:,:,i] .= F.U[:,1:r] * diagm(0 => F.S[1:r])* F.Vt[1:r,:]
    end
  end #end if mode[2]
else
 error("mode[1] for rank projections can only be: slice")
end #if mode[1]

  x=vec(x)
end


"""
Compute the inverse of the lattice. Takes special care of 1D or 2D cases.
"""
function compute_inverse_lattice(lattice::AbstractMatrix{T}) where {T}
    # Note: pinv pretty much does the same, but the implied SVD causes trouble
    #       with interval arithmetic and dual numbers, so we go for this version.
    n_dim = count(!iszero, eachcol(lattice))
    @assert 1 ≤ n_dim ≤ 3
    if n_dim == 3
        inv(lattice)
    else
        Mat3{T}([
            inv(lattice[1:n_dim, 1:n_dim])   zeros(T, n_dim, 3 - n_dim);
            zeros(T, 3 - n_dim, 3)
        ])
    end
end

"""
Compute the reciprocal lattice.
We use the convention that the reciprocal lattice is the set of G vectors such
that G ⋅ R ∈ 2π ℤ for all R in the lattice.
"""
function compute_recip_lattice(lattice::AbstractMatrix{T}) where {T}
    2T(π) * compute_inverse_lattice(lattice')
end

"""
Compute unit cell volume volume. In case of 1D or 2D case, the volume is the length/surface.
"""
function compute_unit_cell_volume(lattice)
    n_dim = count(!iszero, eachcol(lattice))
    abs(det(lattice[1:n_dim, 1:n_dim]))
end

"""Compute the diameter of the unit cell"""
function diameter(lattice::AbstractMatrix)
    # brute force search
    diam = zero(eltype(lattice))
    for vec in Vec3.(Iterators.product(-1:1, -1:1, -1:1))
        diam = max(diam, norm(lattice * vec))
    end
    diam
end


"""
    EGPpower(σ, ξ, κ)

*EGPpower* corresponds to the first extended GP model of Naveau et al. (2016), with the power law distribution G(v) = v^κ.

It is a three parameters family: κ controls the shape of the lower tail, σ is a scale parameter, and ξ controls the rate of upper tail decay.

```julia
EGPpower(σ, ξ, κ)   # EGP of Naveau et al. (2016) (type 1) with scale parameter σ, rate of upper tail decay ξ and shape of the lower tail κ.

params(d)           # Get the parameters, i.e. (σ, ξ, κ)
```

Reference :

* Naveau, P., Huser, R., Ribereau, P., and Hannart, A. (2016), Modeling jointly low, moderate, and heavy rainfall intensities without a threshold selection, Water Resour. Res., 52, 2753– 2769, doi:10.1002/2015WR018552.
"""
struct EGPpower{T<:Real} <: ContinuousUnivariateDistribution
    σ::T    # scale parameter
    ξ::T    # rate of upper tail decay
    κ::T    # shape of the lower tail

    function EGPpower{T}(σ::T, ξ::T, κ::T) where {T <: Real}
        new{T}(σ, ξ, κ)
    end
end

function EGPpower(σ::T, ξ::T, κ::T; check_args=true) where {T <: Real}
    check_args && @check_args(EGPpower, σ > zero(σ) && κ > zero(κ))
    return EGPpower{T}(σ, ξ, κ)
end

EGPpower(σ::Real, ξ::Real, κ::Real) = EGPpower(promote(σ, ξ, κ)...)
EGPpower(σ::Integer, ξ::Integer, κ::Integer) = EGPpower(float(σ), float(ξ), float(κ))

minimum(d::EGPpower) = 0.0
maximum(d::EGPpower) = Inf * (d.ξ >= 0) - ( d.σ / d.ξ ) * (d.ξ < 0)
insupport(d::EGPpower, x::Real) = minimum(d) <= x <= maximum(d)

#### Parameters

scale(d::EGPpower) = d.σ
decay(d::EGPpower) = d.ξ    # noms à vérifier...
shape(d::EGPpower) = d.κ

params(d::EGPpower) = (d.σ, d.ξ, d.κ)
partype(::EGPpower{T}) where {T} = T

#### Evaluation

function logpdf(d::EGPpower{T}, x::Real) where T<:Real
    μ = 0
    (σ, ξ, κ) = params(d)

    pd = GeneralizedPareto(μ, 1, ξ)

    lg(v::Real) = log(κ) + (κ-1)*log(v)

    p = -log(σ) + Distributions.logpdf(pd, x/σ) + lg(Distributions.cdf(pd, x/σ))

    return p
end

pdf(d::EGPpower, x::Real) = exp(logpdf(d, x))

function logcdf(d::EGPpower{T}, x::Real) where T<:Real
    # À vérifier
    μ = 0
    (σ, ξ, κ) = params(d)

    pd = GeneralizedPareto(μ, 1, ξ)

    #G(v::Real, κ::Real) = v^κ
    #lG(v::Real, κ::Real) = κ*log(v)

    p = κ*Distributions.logcdf(pd, x/σ)

    return p
end

cdf(d::EGPpower, x::Real) = exp(logcdf(d, x))

function quantile(d::EGPpower{T}, p::Real) where T<:Real
    @assert zero(p)<p<one(p) "the quantile level should be between 0 and 1."

    (σ, ξ, κ) = params(d)

    invG(p::Real, κ::Real) = p^(1/κ)

    x = (σ/ξ)*(((1 - invG(p,κ))^(-ξ)) - 1)

    return x
end

#### Sampling

function rand(rng::AbstractRNG, d::EGPpower)
    # Generate a Float64 random number uniformly in (0,1].
    u = 1 - rand(rng)

    invG(p::Real, κ::Real) = p^(1/κ)

    return (d.σ/d.ξ)*(((1 - invG(u,d.κ))^(-d.ξ)) - 1)
end

sampler(d::EGPpower) = d    # à vérifier


var2(x) = mean(x.^2) .- mean(x)^2

function waic( ll::AbstractArray; pointwise=false , log_lik="log_lik" , kwargs... )

    n_samples, n_obs = size(ll)
    pD = zeros(n_obs);

    lpd = reshape(logsumexp(ll .- log(n_samples); dims=1), n_obs);
    for i in 1:n_obs
        pD[i] = var2(ll[:,i])
    end

    waic_vec = (-2) .* ( lpd - pD );
    if pointwise == false
        waics = sum(waic_vec)
        lpd = sum(lpd)
        pD = sum(pD)
    else
        waics = waic_vec
    end

    local se
    try
        se = sqrt( n_obs*var2(waic_vec) )
    catch e
        println(e)
        se = nothing
    end

    (WAIC=waics, lppd=lpd, penalty=pD, std_err=se)
end

export
    var2,
    waic


using Distributions
dist = TriangularDist(0,2,1)

println("Parameters: \t\t\t",params(dist))
println("Central descriptors: \t\t",mean(dist),"\t",median(dist))
println("Dispersion descriptors: \t", var(dist),"\t",std(dist))
println("Higher moment shape descriptors: ",skewness(dist),"\t",kurtosis(dist))
println("Range: \t\t\t\t", minimum(dist),"\t",maximum(dist))
println("Mode: \t\t\t\t", mode(dist), "\tModes: ",modes(dist))


using ContinuousTransformations
using FastGaussQuadrature
using ArgCheck

export Quadrature, quadrature_standard_normal, quadrature_standard_uniform

"""
Univariate numerical quadrature. A lightweight container for the
`nodes` and the `weights`, calculated from some quadrature rule. The
`domain` is provided for information purposes.
"""
immutable Quadrature{TD <: AbstractInterval, T}
    domain::TD
    nodes::Vector{T}
    weights::Vector{T}
    function Quadrature(domain, nodes, weights)
        @argcheck length(nodes) == length(weights)
        new(domain, nodes, weights)
    end
end

function Quadrature{TD, T}(domain::TD, nodes::Vector{T}, weights::Vector{T})
    Quadrature{TD, T}(domain, nodes, weights)
end

ContinuousTransformations.domain(q::Quadrature) = q.domain

Base.length(q::Quadrature) = length(q.nodes)

function Base.show(io::IO, q::Quadrature)
    print(io, "Quadrature of $(length(q)) nodes on $(q.domain)")
end

(q::Quadrature)(f) = dot(f.(q.nodes), q.weights)

"""
Return a quadrature that integrates a function under the standard
normal distribution using `N` nodes.
"""
function quadrature_standard_normal(N::Int)
    nodes, weights = gausshermite(N)
    Quadrature(ℝ, nodes*√2, weights/√π)
end

"""
Return a quadrature that integrates a function on the unit domain
[0,1] under the standard uniform distribution using `N` nodes.
"""
function quadrature_standard_uniform(N::Int)
    nodes, weights = gausslegendre(N)
    Quadrature(𝕀, fma.(nodes, 0.5, 0.5), weights .* 0.5)
end


# Local Tangent Space Alignment (LTSA)
# ---------------------------
# Principal Manifolds and Nonlinear Dimension Reduction via Local Tangent Space Alignment,
# Zhang, Zhenyue; Hongyuan Zha (2004),  SIAM Journal on Scientific Computing 26 (1): 313–338.
# doi:10.1137/s1064827502419154.

#### LTSA type
struct LTSA{T <: AbstractFloat} <: SpectralResult
    k::Int
    λ::AbstractVector{T}
    proj::Projection{T}

    LTSA{T}(k::Int, λ::AbstractVector{T}, proj::Projection{T}) where T = new(k, λ, proj)
end

## properties
outdim(M::LTSA) = size(M.proj, 1)
projection(M::LTSA) = M.proj

eigvals(M::LTSA) = M.λ
neighbors(M::LTSA) = M.k

## show & dump
function show(io::IO, M::LTSA)
    print(io, "LTSA(outdim = $(outdim(M)), neighbors = $(neighbors(M)))")
end

function dump(io::IO, M::LTSA)
    show(io, M)
    println(io, "eigenvalues: ")
    Base.showarray(io, transpose(M.λ), header=false, repr=false)
    println(io)
    println(io, "projection:")
    Base.showarray(io, M.proj, header=false, repr=false)
end

## interface functions
function transform(::Type{LTSA}, X::DenseMatrix{T}; d::Int=2, k::Int=12) where T<:AbstractFloat
    n = size(X, 2)

    # Construct NN graph
    D, I = find_nn(X, k)

    B = spzeros(n,n)
    for i=1:n
        # re-center points in neighborhood
        μ = mean(X[:,I[:,i]], dims=2)
        δ_x = X[:,I[:,i]] .- μ

        # Compute orthogonal basis H of θ'
        θ_t = svd(δ_x).V[:,1:d]

        # Construct alignment matrix
        G = hcat(ones(k)./sqrt(k), θ_t)
        B[I[:,i], I[:,i]] =  B[I[:,i], I[:,i]] + Matrix{Float64}(LinearAlgebra.I, k, k) - G*transpose(G)
    end

    # Align global coordinates
    λ, V = decompose(B, d)
    return LTSA{T}(k, λ, transpose(V))
end


# Doesn't work for colinear/parallel lines. (𝘂 × 𝘃 = 𝟬).
# For 𝗹₁(r) = 𝘅₁ + r𝘂 and 𝗹₂(s) = 𝘅₂ + s𝘃
# 1) 𝘅₁ + r𝘂 = 𝘅₂ + s𝘃                  subtracting 𝘅₁ from both sides
# 2) r𝘂 = (𝘅₂-𝘅₁) + s𝘃                  𝘄 = 𝘅₂-𝘅₁
# 3) r𝘂 = 𝘄 + s𝘃                        cross product with 𝘃 (distributive)
# 4) r(𝘂 × 𝘃) = 𝘄 × 𝘃 + s(𝘃 × 𝘃)        𝘃 × 𝘃 = 𝟬
# 5) r(𝘂 × 𝘃) = 𝘄 × 𝘃                   let 𝘄 × 𝘃 = 𝘅 and 𝘂 × 𝘃 = 𝘇
# 6) r𝘇 = 𝘅                             dot product 𝘇 to each side
# 7) r𝘇 ⋅ 𝘇 = 𝘅 ⋅ 𝘇                     divide by 𝘇 ⋅ 𝘇
# 8) r = (𝘅 ⋅ 𝘇)/(𝘇 ⋅ 𝘇)
# We need to ensure r, s ∈ [0, 1], hence we need to solve for s too.
# 1) 𝘅₂ + s𝘃 = 𝘅₁ + r𝘂                     subtracting 𝘅₂ from both sides
# 2) s𝘃 = -𝘄 + r𝘂                          cross product with 𝘄
# 3) s(𝘃 × 𝘄) = -𝘄 × 𝘄 + r(𝘂 × 𝘄)          𝘄 × 𝘄 = 𝟬
# 4) s(𝘃 × 𝘄) = r(𝘂 × 𝘄)                   using 𝘂 × 𝘄 = -(𝘄 × 𝘂), likewise for 𝘃 × 𝘄
# 5) s(𝘄 × 𝘃) = r(𝘄 × 𝘂)                   let 𝘄 × 𝘂 = 𝘆. use 𝘄 × 𝘃 = 𝘅
# 6) s𝘅 = r𝘆                               dot product 𝘅 to each side
# 7) s(𝘅 ⋅ 𝘅) = r(𝘆 ⋅ 𝘅)                   divide by (𝘅 ⋅ 𝘅)
# 9) s = r(𝘅 ⋅ 𝘆)/(𝘅 ⋅ 𝘅)
# The cross product of two vectors in the plane is a vector of the form (0, 0, k),
# hence, in 2D:
# r = (𝘅 ⋅ 𝘇)/(𝘇 ⋅ 𝘇) = x₃/z₃
# s = r(𝘅 ⋅ 𝘆)/(𝘅 ⋅ 𝘅) = y₃/z₃
function Base.intersect(l₁::LineSegment{Point{2,T}},
                        l₂::LineSegment{Point{2,T}}) where {T}
    𝘄 = l₂[1] - l₁[1]
    𝘂₁= l₁[2] - l₁[1]
    𝘂₂= l₂[2] - l₂[1]
    z = 𝘂₁ × 𝘂₂
    r = (𝘄 × 𝘂₂)/z
    s = (𝘄 × 𝘂₁)/z
    valid = 0 ≤ r && r ≤ 1 && 0 ≤ s && s ≤ 1
    return valid ? l₂(s) : Point(T(1e6),T(1e6))
end


### A Pluto.jl notebook ###
# v0.14.4

using Markdown
using InteractiveUtils

# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
    quote
        local el = $(esc(element))
        global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : missing
        el
    end
end

# ╔═╡ 876e6928-abdc-11eb-0fea-d51de2d4b971
md""" ### Quick Julia tutorial...
"""

# ╔═╡ 9e95da34-fc21-4e7e-99a9-aea83edacd19
# global variable
example = 9.5

# ╔═╡ 34282b28-b73f-4aa7-96be-31dd6700084f
areaofcircle(r) = pi * r^2

# ╔═╡ 451499e9-76d9-4088-af2f-f35a9222afca
#= this updates
~automatically~
if you change the first cell
(and here's how you do multi-line commenting!) =#

bep = areaofcircle(example)

# ╔═╡ 1a3a323e-3f6b-4d65-bcbe-7c1c98aeb150
md"""
##### Some words on scope...
In Julia, like all other languages, there are globally and locally scoped variables. Locally scoped can be considered hard or soft; difference being that soft local variables unassigned locally and defined globally reassign the global variable, whereas hard scope remains local.

Julia uses lexical scoping, meaning that a function's scope refers to the one in which it was defined. For example, calling the module "Bleep" from a global scope will use the scope independent to Bleep, NOT the caller's global scope:
"""

# ╔═╡ 3f2ba373-77c6-4462-b30b-94fc8602298e
module Bleep
	example = 1
	foo() = example
end

# ╔═╡ 6315a551-f39b-40be-8c84-d3fbf34de4d0
Bleep.foo()

# ╔═╡ fa32bf73-e41a-4db5-ab2a-3e5a9e383aef
md"""
And this new module's independent scope is separate from the caller's global scope:
"""

# ╔═╡ 0cd019ae-3521-4323-9371-020190603204
module Deeee
	beh = example
end

# ╔═╡ 92cfd5a5-09ef-401d-a4a6-53d2576b314f
md"""Also, changing a global variable with a function will result in that variable remaining modified - called 'pass-by-sharing'. Conventionally, these functions will have '!' after their names.
"""

# ╔═╡ 92713261-8080-4797-8e48-0f0c2ce0e391
function changer!(bees)
    bees .+= 1
    return
end

# ╔═╡ 997f3fe0-d920-4055-b81e-b259fce128a3
ty = [1.0,2.0,3.0]

# ╔═╡ b6fa822c-7a55-46fe-b3a3-f191fe3ebd3d
begin
	changer!(ty)
	ty
end

# ╔═╡ 883334fb-f81b-4c24-9527-571a26ce7ce8
md"""
Additionally, if one variable is defined as another, any modification to the new variable will result in the same change in the original.
"""

# ╔═╡ dc423ed7-c9ee-416a-a0b2-a3c1408221cd
begin
	blip = [1,2,3]
	behhh = blip
end

# ╔═╡ b9aab8cc-c738-4f3c-a845-52f5078d1478
behhh[2] = 4

# ╔═╡ 0b04f67a-e413-4c9a-b298-610a294561b8
blip

# ╔═╡ 251313ac-b425-47c5-aae8-d78e5167b052
md"""
Other fun things include binding with a variety of inputs (copied from their sample notebook)... click the eyeball on the left to see the cell!
"""

# ╔═╡ 9f9fb7dd-b0f6-4bb8-b523-6053b5bc39dc
md"""
`zoot = ` $(@bind zoot html"<input type=range min=5 max=35>")

`beef = ` $(@bind beef html"<input type=text >")

`crr = ` $(@bind crr html"<input type=button value='Click'>")

`drrt = ` $(@bind drrt html"<input type=checkbox >")

`efff = ` $(@bind efff html"<select><option value='one'>First</option><option value='two'>Second</option></select>")

`fooop = ` $(@bind fooop html"<input type=color >")

"""

# ╔═╡ b03cc059-d281-4973-a22a-fabeb5de06e0
(zoot, beef, crr, drrt, efff, fooop)

# ╔═╡ c423ec0a-dbf7-4400-99f9-8357ac59edd1
md"""
#### Working with arrays, concatenation, and plotting...
"""

# ╔═╡ d5e187ee-c6b0-4490-80e0-12a47e809d27
begin
	years = collect(2001:2005)
	oranges = [3, 7, 12, 89, 20]
	pears = zeros(5,1)
	apples = rand(1:100, 5)
	bananas = rand(1:100, 5)
end

# ╔═╡ d50d1e34-9ef2-41de-8dac-bb763685fd5f
fruits = hcat(apples, oranges, pears, bananas)

# ╔═╡ 2f248615-aaca-44ce-a0d9-0ac31e12ba52
begin
	using Plots
	plot(years, fruits, layout=(4, 1), legend=false)
end

# ╔═╡ 6658d1dc-aefd-449b-8d81-f2b9f6a6fe3c
sort(fruits, dims=2)

# ╔═╡ 3bc93ea9-0718-47b9-ae4c-d1286641c9f2
all(i->(4<=i<=6), [4,5,6])

# ╔═╡ 28108af6-a838-417b-85a5-5e3fff0ae49b
md"""
#### Fun with structs!

Julia structs do not require inheritance like OOP languages do - of which Julia is not considered to be, due to the fact that you cannot attach a method (a behavior of a function) to a type. Dispatch, or the choice of method, depends on the first argument in OOP, while in Julia, it chooses which of a function's methods to call depending on the types of all the arguments. This is called multiple dispatch, and is the primary differentiator from other languages.

Types can be either system-defined or user-defined, and allow for structs to define a "supertype" which can be called by any number of functions or structs, as follows.

"""

# ╔═╡ cca3129b-8ccb-4747-b75d-cb34f3ff5b23
struct Limbs
	hands :: Float64
	leg :: Integer
end

# ╔═╡ b1ce5d0c-c3f6-411c-8f04-c44854fa89b1
struct boy
	body_parts :: Limbs #declaring body_parts as a Composite Type, defined by Limbs
	# only inner constructors have access to this "new" function
	boy(hands, leg) = new(Limbs(hands, leg))
end

# ╔═╡ 26730fa4-254c-4bef-81e5-39803ebcaa2e
begin
	joseph = boy(2.,1)
	joseph.body_parts.hands
end

# ╔═╡ 921051ca-54dc-4563-a116-0756cca43557
md"""
This will come into play with user-defined type "Cell" that can be acted on by any number of functions.
"""

# ╔═╡ 8a3ee669-bad2-4086-b90a-1744b85dcf76
md"""
##### Differences from other languages that may not be immediately obvious...

https://docs.julialang.org/en/v1/manual/noteworthy-differences/

https://erik-engheim.medium.com/defining-custom-units-in-julia-and-python-513c34a4c971
"""

# ╔═╡ 39d444ec-f84a-4d1b-9ba4-19af636bf3dd

# ╔═╡ Cell order:
# ╟─876e6928-abdc-11eb-0fea-d51de2d4b971
# ╠═9e95da34-fc21-4e7e-99a9-aea83edacd19
# ╠═34282b28-b73f-4aa7-96be-31dd6700084f
# ╠═451499e9-76d9-4088-af2f-f35a9222afca
# ╟─1a3a323e-3f6b-4d65-bcbe-7c1c98aeb150
# ╠═3f2ba373-77c6-4462-b30b-94fc8602298e
# ╠═6315a551-f39b-40be-8c84-d3fbf34de4d0
# ╟─fa32bf73-e41a-4db5-ab2a-3e5a9e383aef
# ╠═0cd019ae-3521-4323-9371-020190603204
# ╟─92cfd5a5-09ef-401d-a4a6-53d2576b314f
# ╠═92713261-8080-4797-8e48-0f0c2ce0e391
# ╠═997f3fe0-d920-4055-b81e-b259fce128a3
# ╠═b6fa822c-7a55-46fe-b3a3-f191fe3ebd3d
# ╟─883334fb-f81b-4c24-9527-571a26ce7ce8
# ╠═dc423ed7-c9ee-416a-a0b2-a3c1408221cd
# ╠═b9aab8cc-c738-4f3c-a845-52f5078d1478
# ╠═0b04f67a-e413-4c9a-b298-610a294561b8
# ╟─251313ac-b425-47c5-aae8-d78e5167b052
# ╟─9f9fb7dd-b0f6-4bb8-b523-6053b5bc39dc
# ╠═b03cc059-d281-4973-a22a-fabeb5de06e0
# ╟─c423ec0a-dbf7-4400-99f9-8357ac59edd1
# ╠═d5e187ee-c6b0-4490-80e0-12a47e809d27
# ╠═d50d1e34-9ef2-41de-8dac-bb763685fd5f
# ╠═6658d1dc-aefd-449b-8d81-f2b9f6a6fe3c
# ╠═2f248615-aaca-44ce-a0d9-0ac31e12ba52
# ╠═3bc93ea9-0718-47b9-ae4c-d1286641c9f2
# ╟─28108af6-a838-417b-85a5-5e3fff0ae49b
# ╠═cca3129b-8ccb-4747-b75d-cb34f3ff5b23
# ╠═b1ce5d0c-c3f6-411c-8f04-c44854fa89b1
# ╠═26730fa4-254c-4bef-81e5-39803ebcaa2e
# ╟─921051ca-54dc-4563-a116-0756cca43557
# ╟─8a3ee669-bad2-4086-b90a-1744b85dcf76
# ╠═39d444ec-f84a-4d1b-9ba4-19af636bf3dd


# Simple example of an eigenproblem:
#
#           y
#             |
#             |            u_y = 0
#          Ly ----------------------------------
#             |                                |
#             |                                |
#             |                                |
#       u = 0 |     u_xx + u_yy + λ u = 0      | u = 0
#             |                                |
#             |                                |
#             |                                |
#           -----------------------------------|-----  x
#                          u_y = 0              Lx
#
# Here, exact solutions are
#
#      u = sin( n π x / Lx ) cos( k π y / Ly )
#
#                      2               2
#      λ = ( n π / Lx )  + ( k π / Ly )
#
# for n = 1, 2, 3, ... and k = 0, 1, 2, ....

using FinElt
using FinElt.PlanarPoisson
using Printf
using Arpack

include("params.jl")
const nev = 4

λ = Float64[]
for n = 1:nev, k = 0:nev-1
    push!(λ, (n*pi/Lx)^2+(k*pi/Ly)^2)
end

sort!(λ)

essential_bc = [ "Left", "Right" ]

err = zeros(nev, refinements+1)
@printf("Eigenvalue errors\n\n")
@printf("%7s|", "N")
for j = 1:nev
    @printf("%16s|", "λ_$j    ")
end
@printf(" elapsed\n")
@printf("%84s\n", "-"^84)
for k = 0:refinements
    start = time()
    mesh = read_msh_file("rect$k.msh")
    ep = EigenProblem(mesh, essential_bc)
    add_bilin_form!(ep, "Omega", grad_dot_grad!,   :LHS)
    add_bilin_form!(ep, "Omega", func_times_func!, :RHS)
    A, B = assembled_eigenproblem_matrices(ep)
    d, nconv, niter, nmult, resid = eigs(A, B, nev=nev, which=:SM,
                                         ritzvec=false)
    finish = time()
    err[:,k+1] = abs.(d-λ[1:nev])
    N = size(A, 1)
    if k == 0
        elapsed = finish - start
        @printf("%7d|", N)
        for j = 1:nev
            @printf("%9.2e %6s|", err[j,k+1], "")
        end
        @printf(" %7.4f\n", elapsed)
    else
        elapsed = finish - start
        @printf("%7d|", N)
        for j = 1:nev
            rate = log2(err[j,k]/err[j,k+1])
            @printf("%9.2e %6.4f|", err[j,k+1], rate)
        end
        @printf(" %7.4f\n", elapsed)
    end
end


using Plots, LaTeXStrings; pyplot()

a, c, d = 2, 1, 5
next(x,y) = [a*x*(1-x) - x*y, -c*y + d*x*y]
equibPoint = [(1+c)/d ,(d*(a-1)-a*(1+c))/d]

initX = [0.8,0.05]
tEnd = 100

traj = [[] for _ in 1:tEnd]
traj[1] = initX

for t in 2:tEnd
    traj[t] = next(traj[t-1]...)
end

scatter([traj[1][1]], [traj[1][2]],
	c=:black, ms=10,
	label="Initial state")
plot!(first.(traj),last.(traj),
	c=:blue, ls=:dash, m=(:dot, 5, Plots.stroke(0)),
	label="Model trajectory")
scatter!([equibPoint[1]], [equibPoint[2]],
	c=:red, shape=:cross, ms=10, label="Equlibrium point",
	xlabel=L"X_1", ylabel=L"X_2")


@testset "MatrixDepot" begin
debit = [ ("hilb", 5),  ("cauchy",4), ("invhilb",4), ("moler",7),("pascal",6),("pei",100),("tridiag",100),("lehmer",100),("poisson",12),("wathen",10)]

for k=1:length(debit)
	A   = matrixdepot(debit[k][1],debit[k][2])
	rhs = randn(size(A,1))
	@printf "%s\tn=[%d,%d]\t" debit[k][1] size(A,1) size(A,2)

	xt  = A\rhs

	x3, = gmres(A,rhs,10,maxIter=50*length(rhs),tol=1e-16,out=0)
	@test norm(xt-x3)/norm(xt) < 1e-3
	@printf " gmres is OK "
	x1, = cg(A,rhs,maxIter=length(rhs),tol=1e-16,out=-1)
	@test norm(xt-x1)/norm(xt) < 1e-3
	@printf "cg is OK\n"
end

end


#    Problem :
#    *********
#
#    The Boundary Value problem.
#    This is the nonlinear least-squares version without fixed variables.
#
#
#    Source:  problem 28 in
#    J.J. More', B.S. Garbow and K.E. Hillstrom,
#    "Testing Unconstrained Optimization Software",
#    ACM Transactions on Mathematical Software, vol. 7(1), pp. 17-41, 1981.
#
#    Implementation translated from:
#    See also Buckley#17 (p. 94):
#
#
#    SIF input: Ph. Toint, Dec 1989 and Nick Gould, Oct 1992.
#
#    classification SUR2-MN-V-0
#    The number of variables is N.
#
# Daniel Henderson, 08/2021

f = x -> begin
	n = lastindex(x)
	h = 1.0/(1 + n)^2
	f1 = (2x[1]-x[2]+0.5h^2*(x[1] + h + 1)^3)^2
	fn = (2x[n]-x[n-1]+0.5h^2*(x[n] + n*h + 1)^3)^2
    return sum((2x[i]-x[i-1]-x[i+1]+0.5h^2*(x[i] + i*h + 1)^3)^2 for i in 2:n-1) + f1 + fn
end

g! = (g, x) -> begin
	r = similar(g)
	n = lastindex(x)
	h = 1.0/(1 + n)^2
	for i in 2:n-1
		r[i] = 2x[i]-x[i-1]-x[i+1]+0.5h^2*(x[i] + i*h + 1)^3
	end
	r[1] = 2x[1]-x[2]+0.5h^2*(x[1] + h + 1)^3
	r[n] = 2x[n]-x[n-1]+0.5h^2*(x[n] + n*h + 1)^3
	for i in 2:n-1
		g[i] = -r[i-1] -r[i+1] + r[i]*(2+3/2.0*h^2*(x[i] + i*h + 1)^2)
	end
	g[1] = -r[2] + r[1] *(2+3/2.0*h^2*(x[1] + h + 1)^2)
	g[n] = -r[n-1] + r[n] *(2+3/2.0*h^2*(x[n] + n*h + 1)^2)
    return g
end

fg! = (g, x) -> begin
	r = similar(g)
	n = lastindex(x)
	h = 1.0/(1 + n)^2
	fx = (2x[1]-x[2]+0.5h^2*(x[1] + h + 1)^3)^2 + (2x[n]-x[n-1]+0.5h^2*(x[n] + n*h + 1)^3)^2
	for i in 2:n-1
		r[i] = 2x[i]-x[i-1]-x[i+1]+0.5h^2*(x[i] + i*h + 1)^3
		fx += r[i]^2
	end
	r[1] = 2x[1]-x[2]+0.5h^2*(x[1] + h + 1)^3
	r[n] = 2x[n]-x[n-1]+0.5h^2*(x[n] + n*h + 1)^3
	for i in 2:n-1
		g[i] = -r[i-1] -r[i+1] + r[i]*(2+3/2.0*h^2*(x[i] + i*h + 1)^2)
	end
	g[1] = -r[2] + r[1] *(2+3/2.0*h^2*(x[1] + h + 1)^2)
	g[n] = -r[n-1] + r[n] *(2+3/2.0*h^2*(x[n] + n*h + 1)^2)
	return fx, g
end

init = (n::Int=5000) -> begin
	n < 2 && @warn("MOREBV: number of variables must be ≥ 2")
	n = max(n, 2)
    return n, 0.5ones(n)
end
@warn "Debug MOREBV"
#TestSet["MOREBV"] = UncProgram("MOREBV",  f, g!, fg!, init)


import Random
rng0 = Random.MersenneTwister(1234);

const Mat = Matrix{Float64}
const Vec = Vector{Float64}

struct RNN
    Wx :: Mat  # Transform input to hidden
    Wh :: Mat  # Transform hidden to hidden
    b  :: Vec  # Bias hidden to hidden
end

# RNN model: Given input sequence xs, compute output sequence
function rnn(weights :: RNN, xs :: Array{Vec}) :: Array{Vec}
    h = rand(rng, length(weights.b))
    [
      h = tanh.(weights.Wh * h) + tanh.(weights.Wx * x) + weights.b
         for x in xs
    ]
end

# RNN model: Given input sequence xs, compute output sequence
function rnn2(weights :: RNN, xs :: Array{Vec})
    h = rand(rng, length(weights.b))
    show(h)
    out = []
    for x in xs
        h = tanh.(weights.Wh * h) + tanh.(weights.Wx * x) + weights.b
        push!(out, h)
    end
    out
end

weights = RNN(rand(5,3), rand(5,5), rand(5))

xs = [rand(rng, 3) for i in 1:5];

rng = copy(rng0);
r1 = rnn(weights, xs)

rng = copy(rng0);
r2 = rnn2(weights, xs)


@testset "Maximum weighted matching" begin
    function neighbors(sym_matrix, i)
        return findall(v -> v != 0, sym_matrix[:, i])
    end

    @testset "Bipartite example" begin

        # from slide 65 of https://www.slideshare.net/KuoE0/acmicpc-bipartite-matching
        # but set 1 <-> 4 to 4.9 to avoid matching 1 <-> 4 and 3 <-> 5 which also sums to 10
        weight_matrix = [
            0 0 0 4.9 0 2
            0 0 0 3 1 -2
            0 0 0 0 5 0
            4.9 3 5 0 0 0
            0 1 0 0 0 0
            2 -2 0 0 0 0
        ]
        n = 6

        m = Model(CSJuMPTestOptimizer())
        @variable(m, x[1:n, 1:n], Bin)
        for i in 1:n, j in 1:n
            if weight_matrix[i, j] == 0 || i > j
                @constraint(m, x[i, j] == 0)
            end
        end
        for i in 1:n
            @constraint(
                m,
                sum(x[min(i, j), max(i, j)] for j in neighbors(weight_matrix, i)) <= 1
            )
        end

        @objective(m, Max, sum(weight_matrix .* x))
        optimize!(m)
        @test JuMP.objective_value(m) ≈ 10
        @test JuMP.value(x[1, 6]) == 1
        @test JuMP.value(x[2, 4]) == 1
        @test JuMP.value(x[3, 5]) == 1
        @test sum(JuMP.value.(x[1, 1:6])) == 1
        @test sum(JuMP.value.(x[2, 1:6])) == 1
        @test sum(JuMP.value.(x[3, 1:6])) == 1
        @test sum(JuMP.value.(x[4, 1:6])) == 0
        @test sum(JuMP.value.(x[5, 1:6])) == 0
        @test sum(JuMP.value.(x[6, 1:6])) == 0
    end
end


export intersection_params, line_segment_point, each_intersect, find_juncts, find_connect, connections

"""
```julia
intersection_params(w₁::Line, w₂::Line)
```
Given two lines computes the parameters `[t,s]` so that
the intersection of the lines occurs at
`(w₁.p₂-w₁.p₁)t+w₁.p₁==(w₂.p₂-w₂.p₁)s+w₂.p₁`. If the
lines are parallel, one is a point and not a line, or
the lines are colinear the system of equations will
not be consitent and so will return some solution
like `[NaN,NaN]` or `[Inf,Inf]`. The lines intersect on
the segments given whenever both `0≤t≤1` and `0≤s≤1`.
"""
function intersection_params(w₁::Line, w₂::Line)
    hcat(w₁.p₂-w₁.p₁,w₂.p₁-w₂.p₂)\(w₂.p₁-w₁.p₁)
end

"""
```julia
line_segment_point(t::T, w::Line)
```
Computes the point on the line `w` at parameter `t` where
`t` is the parameter of the line so that `p₂*t+(1-t)p₁`
is the parametric equation of the line.
"""
function line_segment_point(t::T, w::Line) where T<:Real
    w.p₂*t+(1-t)*w.p₁
end

"""
```julia
each_intersect(f!,arr₁,arr₂,dims,arg)
```
    Executes `f!(i₁,i₂,arr₁,arr₂,dims,ps,p,arg)` for any intersection between pairs of
    lines in `arr₁` and `arr₂`. `i₁` and `i₂` are the indices of the lines in `arr₁`
    and `arr₂` respectively, `ps` is the vector of line intersection parameters as
    computed by `intersection_params(arr₁[i₁],arr₂[i₂])`, and `p` is the point at which
    the intersection occurs.
"""
function each_intersect(f!, arr₁::Array{Line{S,N}}, arr₂::Array{Line{S,N}}, dims::SVector{N,S}, arg) where {S,N}
    for i₁ ∈ eachindex(arr₁)
        for i₂ ∈ eachindex(arr₂)
            ps = intersection_params(arr₁[i₁],arr₂[i₂])
            if (0.0 ≤ ps[1] ≤ 1.0) && (0.0 ≤ ps[2] ≤ 1.0)
                p = line_segment_point(ps[1],arr₁[i₁])
                if sum( zero(SVector{N, S}) .≤ p .≤ dims ) == N
                    f!(i₁,i₂,arr₁,arr₂,dims,ps,p,arg)
                end
            end
        end
    end
    arg
end

"""
```julia
each_intersect(f!,arr,dims,arg)
```
    Executes `f!(i₁,i₂,arr,dims,ps,p,arg)` for any intersection between pairs of
    lines in `arr`. `i₁` and `i₂` are the indices of the lines in `arr`, `ps` is
    the vector of line intersection parameters as computed by
    `intersection_params(arr[i₁],arr[i₂])`, and `p` is the point at which the
    intersection occurs.
"""
function each_intersect(f!, arr::Array{Line{S,N}}, dims::SVector{N,S}, arg) where {S,N}
    n = length(arr)
    for i₁ ∈ 2:(n)
        for i₂ ∈ 1:(i₁-1)
            ps = intersection_params(arr[i₁],arr[i₂])
            if (0.0 ≤ ps[1] ≤ 1.0) && (0.0 ≤ ps[2] ≤ 1.0)
                p = line_segment_point(ps[1],arr[i₁])
                if sum( zero(SVector{N, S}) .≤ p .≤ dims ) == N
                    f!(i₁,i₂,arr,dims,ps,p,arg)
                end
            end
        end
    end
    arg
end

"""
```julia
find_juncts(net)
```
Given an `StickNetwork` object finds all points of intersection between lines.
"""
function find_juncts(net::StickNetwork{T,N}) where {T,N}
    each_intersect(net.lines, net.dims, Array{Tuple{SVector{N,T},Tuple{Int,Int}},1}()) do i₁,i₂,arr,dims,ps,p,juncts
        push!(juncts,(p,(i₁,i₂)))
    end
end

"""
```julia
find_connect(net)
```
Given an `StickNetwork` object finds all pairs `(i₁,i₂)` with an intersection between lines `i₁` and `i₂` in `net`.
"""
function find_connect(net::StickNetwork{T,N}) where {T,N}
    each_intersect(net.lines, net.dims, Array{Tuple{Int,Int},1}()) do i₁,i₂,arr,dims,ps,p,connects
        push!(connects,(i₁,i₂))
    end
end

"""
```julia
connections(arr₁,arr₂,dims)
```
Finds all connections between arrays of `Line`s `arr₁` and `arr₂`.
Returns `Tuple{Vector{Int},Vector{Int}}` with each vector representing the source and destination line indices for each connection.
"""
function connections(arr₁::Array{Line{S,N}}, arr₂::Array{Line{S,N}}, dims::SVector{N,S}) where {S,N}
    each_intersect(arr₁, arr₂, dims, (Int[],Int[])) do i₁,i₂,arr₁,arr₂,dims,ps,p,connects
        push!(connects[1],i₁)
        push!(connects[2],i₂)
    end
end

"""
```julia
connections(arr,dims)
```
Finds all connections between `Line`s in `arr`.
Returns `Tuple{Vector{Int},Vector{Int}}` with each vector representing the source and destination line indices for each connection.
"""
function connections(arr::Array{Line{S,N}}, dims::SVector{N,S}) where {S,N}
    each_intersect(arr, dims, (Int[],Int[])) do i₁,i₂,arr,dims,ps,p,connects
        push!(connects[1],i₁)
        push!(connects[2],i₂)
    end
end


function metric(J::AbstractArray{T,2}) where {T}
    g = J'*J
    return g
end

function covariant_basis(c::Coordinate{T}) where {T}
    return ForwardDiff.jacobian(c.R,c.u)
end

function covariant_metric(c::Coordinate)
    J = covariant_basis(c)
    return metric(J)
end

function contravariant_basis(c::Coordinate)
    Jco = covariant_basis(c)
    gco = metric(Jco)
    g = inv(gco)
    J = Jco*g
    return J
end

function contravariant_metric(c::Coordinate)
    J = contravariant_basis(c)
    g = metric(J)
end

abstract type CoordinateVector{T} <: AbstractArray{T,1} end

struct ContravariantVector{T,F} <: CoordinateVector{T}
    data::Vector{T}       #Contravariant Components of the Vector
    coord::Coordinate{T,F}  #Vector Coordinate
    J::Matrix{T}
    g::Matrix{T}
    h::Vector{T}
end

function ContravariantVector(data::AbstractVector{T}, coord::Coordinate{T,F}; unit_basis = false) where {T,F}
    J = covariant_basis(coord)
    g = metric(J)
    h = sqrt.(diag(g))
    if unit_basis
        data = data./h
    end
    ContravariantVector{T,F}(data, coord, J, g, h)
end

struct CovariantVector{T,F} <: CoordinateVector{T}
    data::Vector{T}       #Covariant Components of the Vector
    coord::Coordinate{T,F}  #Vector Coordinate
    J::Matrix{T}
    g::Matrix{T}
    h::Vector{T}
end

function CovariantVector(data::AbstractVector{T}, coord::Coordinate{T,F}; unit_basis = false) where {T,F}
    J = contravariant_basis(coord)
    g = metric(J)
    h = sqrt.(diag(g))
    if unit_basis
        data = data./h
    end
    CovariantVector{T,F}(data, coord, J, g, h)
end

# Array Interface for CoordinateVector
parent(A::T) where {T<:CoordinateVector} = A.data
size(A::T) where {T<:CoordinateVector} = size(A.data)
axes(A::T) where {T<:CoordinateVector} = axes(A.data)
parenttype(::Type{CoordinateVector{T}}) where {T} = Vector{T}
IndexStyle(::Type{T}) where {T<:CoordinateVector} = IndexStyle(parenttype(T))

@propagate_inbounds getindex(A::T, i::Int) where {T<:CoordinateVector} = A.data[i]
@propagate_inbounds setindex!(A::CoordinateVector{T}, v::T, i::Int) where {T} = A.data[i] = v

function convert(::Type{T}, x::S) where {T<:CoordinateVector,S<:CoordinateVector}
    data = x.g*x.data
    g = inv(x.g)
    J = x.J*g
    h = sqrt.(diag(g))
    T(data, x.coord, J, g, h)
end

convert(::Type{T}, x::T) where {T<:CoordinateVector} = x
convert(x::CovariantVector) = convert(ContravariantVector,x)
convert(x::ContravariantVector) = convert(CovariantVector,x)

function unit_basis_components(x::T) where {T<:CoordinateVector}
    return x .* x.h
end


# PS2  Dayuan Xie
# using Pkg
# Pkg.add("Optim")
# Pkg.add("HTTP")
# Pkg.add("GLM")

using Optim
using HTTP
using GLM
using LinearAlgebra
using Random
using Statistics
using DataFrames
using CSV
using FreqTables

function q6()

# Question 1
f(x) = -x[1]^4-10x[1]^3-2x[1]^2-3x[1]-2
minusf(x) = x[1]^4+10x[1]^3+2x[1]^2+3x[1]+2
startval = rand(1)
result = optimize(minusf, startval, BFGS())
println(result)

# Question 2
url = "https://raw.githubusercontent.com/OU-PhD-Econometrics/fall-2020/master/ProblemSets/PS1-julia-intro/nlsw88.csv"
df = CSV.read(HTTP.get(url).body)
X = [ones(size(df,1),1) df.age df.race.==1 df.collgrad.==1]
y = df.married.==1

function ols(beta, X, y)
    ssr = (y.-X*beta)'*(y.-X*beta)
    return ssr
end

beta_hat_ols = optimize(b -> ols(b, X, y), rand(size(X,2)), LBFGS(), Optim.Options(g_tol=1e-6, iterations=100_000, show_trace=true))
println(beta_hat_ols.minimizer)

bols = inv(X'*X)*X'*y
df.white = df.race.==1
bols_lm = lm(@formula(married ~ age + white + collgrad), df)
println(bols_lm)

# Question 3
function logit(alpha, X, d)
    loglike=d'*X*alpha-sum(log.(ones(size(X,1),1)+exp.(X*alpha)))
    return loglike
end
alpha_hat_logit = optimize(a -> -(logit(a, X, y)), rand(size(X,2)), LBFGS(), Optim.Options(g_tol=1e-6, iterations=100_000, show_trace=true))
println(alpha_hat_logit.minimizer)

# question 4
alpha_glm= glm(@formula(married ~ age + white + collgrad), df, Binomial(), LogitLink())
println(alpha_glm)

# Question 5
freqtable(df, :occupation) # note small number of obs in some occupations
df = dropmissing(df, :occupation)
df[df.occupation.==8 ,:occupation] .= 7
df[df.occupation.==9 ,:occupation] .= 7
df[df.occupation.==10,:occupation] .= 7
df[df.occupation.==11,:occupation] .= 7
df[df.occupation.==12,:occupation] .= 7
df[df.occupation.==13,:occupation] .= 7
freqtable(df, :occupation) # problem solved

X = [ones(size(df,1),1) df.age df.race.==1 df.collgrad.==1]
y = df.occupation

y1=y.==1
y2=y.==2
y3=y.==3
y4=y.==4
y5=y.==5
y6=y.==6
y7=y.==7

Y = cat(y1,y2,y3,y4,y5,y6,dims=2)

function csum(A)
    Colsum = []
    for i=1:size(A,1)
        sum(A[i,:])
        push!(Colsum,sum(A[i,:]))
    end
        return Colsum
end

function mlogit(alpha, X, d)
    loglike=tr(d*(X*alpha)')-sum(log.(csum(exp.(X*alpha))))
    return loglike
end

beta_hat_mlogit1 = optimize(b -> -mlogit(b, X, Y), zeros(size(X,2),6), LBFGS(), Optim.Options(g_tol=10-5, iterations=100_000, show_trace=true))
println(beta_hat_mlogit1.minimizer)

beta_hat_mlogit2 = optimize(b -> -mlogit(b, X, Y), rand(size(X,2),6), LBFGS(), Optim.Options(g_tol=10-5, iterations=100_000, show_trace=true))
println(beta_hat_mlogit2.minimizer)

beta_hat_mlogit3 = optimize(b -> -mlogit(b, X, Y), 2*rand(size(X,2),6).-1, LBFGS(), Optim.Options(g_tol=10-5, iterations=100_000, show_trace=true))
println(beta_hat_mlogit3.minimizer)

end

# Question 6
q6()


"""
    calculateEnergy(r, r_j, r_dist, p_j, m, m_j, mu, mu_sum, ignore_H0)

Calculates the sum of kynetic and potential energy of the N-body hamiltonian system:

```math
\\mathcal{H}_{\\mathrm{N-body}}(\\textbf{q}, \\textbf{p}) = \\sum_{i=1}^{N} \\frac{\\textbf{p'}^{2}_{i}}{2 m_{i}'} - \\sum_{i=1}^{N} \\sum_{j=i+1}^{N} \\frac{Gm_{i}m_{j}}{\\mid \\textbf{q}_{i} - \\textbf{q}_{j} \\mid} \\\\
```

# Args

* `r`: NxD matrix where N is the number of bodies and D is the number of dimensions, each row contains the positions of the bodies in cartesian coordinates.
* `r_j`:  NxD matrix, it contains the positions of the bodies in jacobi coordinates.
* `r_dist`: array of N elements, in which distances from the bodies to the barycenter are saved.
* `p_j`: NxD matrix, it contains the momentum of the bodies in jacobi coordinates.
* `m`: array of N elements, where element number i contains the mass of i-th body.
* `m_j`: array of N elements, each containing the i-th jacobi mass (``m'_{i}``)
```math
m'_{1} = m_{sum_{N}} \\\\
m'_{i} = m_{i} \\frac{m_{sum_{i-1}}}{m_{sum_{i}}} \\\\
\\mathrm{,where } \\hspace{0.5cm} m_{sum_{i}} = \\sum_{j = 1}^{i} m_{j}
```
* `mu`: array of N elements, where element with index i contains the standard gravitational parameter of the i-th body
```math
mu_{i} = G \\cdot m_{j} \\\\
```
* `mu_{sum}`: array of N elements, where the standard gravitational parameter of the bodies from 1 to i is saved in the i-th element
```math
mu_{sum_{i}} = \\sum_{j = 1}^{i} G \\cdot m_{j} \\\\
```
* `ignore_H0`: boolean that determines if Hamiltonian ``\\mathcal{H_{0}}`` will be ignored (when calculating the energy).

# Returns

The energy of the ``\\mathcal{H}_{N-body}`` system.
"""
function calculateEnergy(r, r_j, r_dist, p_j, m, m_j, mu, mu_sum, ignore_H0)
    energy = 0.0

    # H_0
    if !ignore_H0
        energy += (dot(p_j[1,:],p_j[1,:]) / (2.0*m_j[1]))
    end

    # H_Kepler
    for i in 2:size(r)[1]
        energy += (dot(p_j[i,:],p_j[i,:]) / (2.0*m_j[i])) #- (mu_sum[i] * m_j[i] / r_dist[i])
    end
    #println(energy)

    # H_Interaction step 1
    #for i in 2:size(r)[1]
    #    energy += (mu_sum[i] * m_j[i] / r_dist[i])
    #end
    #println(energy)

    # H_Interaction step 2
    for i in 1:size(r)[1]
        for j in (i+1):size(r)[1]
            energy -= (mu[i] * m[j] / norm(r[i,:] - r[j,:]))
        end
    end

    return energy
end


"""
	stationarydist(c::MarkovChain)

Phân phối dừng của xích markov
"""
function stationarydist(c::MarkovChain)
	P = m.P
	n = size(P, 1)
	A = [(transpose(p) - I)[1:end-1, :]; ones(1, n)]
	B = [zeros(n - 1); 1]
	inv(A) * B
end

function Base.show(io::IO, m::MarkovChain)
	display("Xích Markov với ma trận chuyển")
	display(m.P)
end


using Plotly;
include("../interps.jl")
f(x) = 2016x^7 + 12x^5 +x^4 + x^3 + x^2 + x + 1

xs = linspace(-1,1, 6)
Newton = interpNewton(xs,map(f,xs))

xss = linspace(-1,1, 10000)
trace1 = scatter(;x=xss, y= map(Newton,xss), name="Newton")
trace2 = scatter(;x=xss, y=  map(f        ,xss), name="f")
plot([trace1, trace2])

#zera czebyszewa
k = 6
xs = []
for i = 0:(k-1)
push!(xs, cos(pi*(2*i+1)/(2*k) ))
end
xs = sort(xs)
@show xs

Newton = interpNewton(xs,map(f,xs))

xss = linspace(-1,1, 10000)
trace1 = scatter(;x=xss, y= map(Newton,xss), name="Newton")
trace2 = scatter(;x=xss, y=  map(f        ,xss), name="f")
plot([trace1, trace2])


# Testing:
#
#  - computation of sufficient statistics
#  - distribution fitting (i.e. estimation)
#

using Distributions
using Base.Test

n0 = 100
N = 10^5

w = rand(n0)

# DiscreteUniform

x = rand(DiscreteUniform(10, 15), n0)
d = fit(DiscreteUniform, x)
@test isa(d, DiscreteUniform)
@test minimum(d) == minimum(x)
@test maximum(d) == maximum(x)

d = fit(DiscreteUniform, rand(DiscreteUniform(10, 15), N))
@test minimum(d) == 10
@test maximum(d) == 15

# Bernoulli

x = rand(Bernoulli(0.7), n0)

ss = suffstats(Bernoulli, x)
@test isa(ss, Distributions.BernoulliStats)
@test ss.cnt0 == n0 - countnz(x)
@test ss.cnt1 == countnz(x)

ss = suffstats(Bernoulli, x, w)
@test isa(ss, Distributions.BernoulliStats)
@test_approx_eq ss.cnt0 sum(w[x .== 0])
@test_approx_eq ss.cnt1 sum(w[x .== 1])

d = fit(Bernoulli, x)
p = countnz(x) / n0
@test isa(d, Bernoulli)
@test_approx_eq mean(d) p

d = fit(Bernoulli, x, w)
p = sum(w[x .== 1]) / sum(w)
@test isa(d, Bernoulli)
@test_approx_eq mean(d) p

d = fit(Bernoulli, rand(Bernoulli(0.7), N))
@test isa(d, Bernoulli)
@test_approx_eq_eps mean(d) 0.7 0.01

# Beta

d = fit(Beta, rand(Beta(1.3, 3.7), N))
@test isa(d, Beta)
@test_approx_eq_eps d.α 1.3 0.1
@test_approx_eq_eps d.β 3.7 0.1

# Binomial

x = rand(Binomial(100, 0.3), n0)

ss = suffstats(Binomial, (100, x))
@test isa(ss, Distributions.BinomialStats)
@test_approx_eq ss.ns sum(x)
@test ss.ne == n0
@test ss.n == 100

ss = suffstats(Binomial, (100, x), w)
@test isa(ss, Distributions.BinomialStats)
@test_approx_eq ss.ns dot(Float64[xx for xx in x], w)
@test_approx_eq ss.ne sum(w)
@test ss.n == 100

d = fit(Binomial, (100, x))
@test isa(d, Binomial)
@test ntrials(d) == 100
@test_approx_eq succprob(d) sum(x) / (n0 * 100)

d = fit(Binomial, (100, x), w)
@test isa(d, Binomial)
@test ntrials(d) == 100
@test_approx_eq succprob(d) dot(x, w) / (sum(w) * 100)

d = fit(Binomial, 100, rand(Binomial(100, 0.3), N))
@test isa(d, Binomial)
@test ntrials(d) == 100
@test_approx_eq_eps succprob(d) 0.3 0.01

# Categorical

p = [0.2, 0.5, 0.3]
x = rand(Categorical(p), n0)

ss = suffstats(Categorical, (3, x))
h = Float64[countnz(x .== i) for i = 1 : 3]
@test isa(ss, Distributions.CategoricalStats)
@test_approx_eq ss.h h

d = fit(Categorical, (3, x))
@test isa(d, Categorical)
@test d.K == 3
@test_approx_eq probs(d) h / sum(h)

d2 = fit(Categorical, x)
@test isa(d2, Categorical)
@test probs(d2) == probs(d)

ss = suffstats(Categorical, (3, x), w)
h = Float64[sum(w[x .== i]) for i = 1 : 3]
@test isa(ss, Distributions.CategoricalStats)
@test_approx_eq ss.h h

d = fit(Categorical, (3, x), w)
@test isa(d, Categorical)
@test_approx_eq probs(d) h / sum(h)

d = fit(Categorical, suffstats(Categorical, 3, x, w))
@test isa(d, Categorical)
@test_approx_eq probs(d) (h / sum(h))

d = fit(Categorical, rand(Categorical(p), N))
@test isa(d, Categorical)
@test_approx_eq_eps probs(d) p 0.01

# Exponential

x = rand(Exponential(0.5), n0)

ss = suffstats(Exponential, x)
@test isa(ss, Distributions.ExponentialStats)
@test_approx_eq ss.sx sum(x)
@test ss.sw == n0

ss = suffstats(Exponential, x, w)
@test isa(ss, Distributions.ExponentialStats)
@test_approx_eq ss.sx dot(x, w)
@test ss.sw == sum(w)

d = fit(Exponential, x)
@test isa(d, Exponential)
@test_approx_eq scale(d) mean(x)

d = fit(Exponential, x, w)
@test isa(d, Exponential)
@test_approx_eq scale(d) dot(x, w) / sum(w)

d = fit(Exponential, rand(Exponential(0.5), N))
@test isa(d, Exponential)
@test_approx_eq_eps scale(d) 0.5 0.01

# Normal

μ = 11.3
σ = 3.2

x = rand(Normal(μ, σ), n0)

ss = suffstats(Normal, x)
@test isa(ss, Distributions.NormalStats)
@test_approx_eq ss.s sum(x)
@test_approx_eq ss.m mean(x)
@test_approx_eq ss.s2 sum((x .- ss.m).^2)
@test_approx_eq ss.tw n0

ss = suffstats(Normal, x, w)
@test isa(ss, Distributions.NormalStats)
@test_approx_eq ss.s dot(x, w)
@test_approx_eq ss.m dot(x, w) / sum(w)
@test_approx_eq ss.s2 dot((x .- ss.m).^2, w)
@test_approx_eq ss.tw sum(w)

d = fit(Normal, x)
@test isa(d, Normal)
@test_approx_eq d.μ mean(x)
@test_approx_eq d.σ sqrt(mean((x .- d.μ).^2))

d = fit(Normal, x, w)
@test isa(d, Normal)
@test_approx_eq d.μ dot(x, w) / sum(w)
@test_approx_eq d.σ sqrt(dot((x .- d.μ).^2, w) / sum(w))

d = fit(Normal, rand(Normal(μ, σ), N))
@test isa(d, Normal)
@test_approx_eq_eps d.μ μ 0.1
@test_approx_eq_eps d.σ σ 0.1

import Distributions.NormalKnownMu, Distributions.NormalKnownSigma

ss = suffstats(NormalKnownMu(μ), x)
@test isa(ss, Distributions.NormalKnownMuStats)
@test ss.μ == μ
@test_approx_eq ss.s2 sum((x .- μ).^2)
@test_approx_eq ss.tw n0

ss = suffstats(NormalKnownMu(μ), x, w)
@test isa(ss, Distributions.NormalKnownMuStats)
@test ss.μ == μ
@test_approx_eq ss.s2 dot((x .- μ).^2, w)
@test_approx_eq ss.tw sum(w)

d = fit_mle(Normal, x; mu=μ)
@test isa(d, Normal)
@test d.μ == μ
@test_approx_eq d.σ sqrt(mean((x .- d.μ).^2))

d = fit_mle(Normal, x, w; mu=μ)
@test isa(d, Normal)
@test d.μ == μ
@test_approx_eq d.σ sqrt(dot((x .- d.μ).^2, w) / sum(w))

ss = suffstats(NormalKnownSigma(σ), x)
@test isa(ss, Distributions.NormalKnownSigmaStats)
@test ss.σ == σ
@test_approx_eq ss.sx sum(x)
@test_approx_eq ss.tw n0

ss = suffstats(NormalKnownSigma(σ), x, w)
@test isa(ss, Distributions.NormalKnownSigmaStats)
@test ss.σ == σ
@test_approx_eq ss.sx dot(x, w)
@test_approx_eq ss.tw sum(w)

d = fit_mle(Normal, x; sigma=σ)
@test isa(d, Normal)
@test d.σ == σ
@test_approx_eq d.μ mean(x)

d = fit_mle(Normal, x, w; sigma=σ)
@test isa(d, Normal)
@test d.σ == σ
@test_approx_eq d.μ dot(x, w) / sum(w)

# Uniform

x = rand(Uniform(1.2, 5.8), n0)
d = fit(Uniform, x)
@test isa(d, Uniform)
@test 1.2 <= minimum(d) <= maximum(d) <= 5.8
@test minimum(d) == minimum(x)
@test maximum(d) == maximum(x)

d = fit(Uniform, rand(Uniform(1.2, 5.8), N))
@test 1.2 <= minimum(d) <= maximum(d) <= 5.8
@test_approx_eq_eps minimum(d) 1.2 0.02
@test_approx_eq_eps maximum(d) 5.8 0.02

# Gamma

x = rand(Gamma(3.9, 2.1), n0)

ss = suffstats(Gamma, x)
@test isa(ss, Distributions.GammaStats)
@test_approx_eq ss.sx sum(x)
@test_approx_eq ss.slogx sum(log(x))
@test_approx_eq ss.tw n0

ss = suffstats(Gamma, x, w)
@test isa(ss, Distributions.GammaStats)
@test_approx_eq ss.sx dot(x, w)
@test_approx_eq ss.slogx dot(log(x), w)
@test_approx_eq ss.tw sum(w)

d = fit(Gamma, rand(Gamma(3.9, 2.1), N))
@test isa(d, Gamma)
@test_approx_eq_eps shape(d) 3.9 0.1
@test_approx_eq_eps scale(d) 2.1 0.2

# Geometric

x = rand(Geometric(0.3), n0)

ss = suffstats(Geometric, x)
@test isa(ss, Distributions.GeometricStats)
@test_approx_eq ss.sx sum(x)
@test_approx_eq ss.tw n0

ss = suffstats(Geometric, x, w)
@test isa(ss, Distributions.GeometricStats)
@test_approx_eq ss.sx dot(x, w)
@test_approx_eq ss.tw sum(w)

d = fit(Geometric, x)
@test isa(d, Geometric)
@test_approx_eq succprob(d) inv(1. + mean(x))

d = fit(Geometric, x, w)
@test isa(d, Geometric)
@test_approx_eq succprob(d) inv(1. + dot(x, w) / sum(w))

d = fit(Geometric, rand(Geometric(0.3), N))
@test isa(d, Geometric)
@test_approx_eq_eps succprob(d) 0.3 0.01

# Laplace

d = fit(Laplace, rand(Laplace(5.0, 3.0), N))
@test isa(d, Laplace)
@test_approx_eq_eps location(d) 5.0 0.1
@test_approx_eq_eps scale(d) 3.0 0.2

# Poisson

x = rand(Poisson(8.2), n0)

ss = suffstats(Poisson, x)
@test isa(ss, Distributions.PoissonStats)
@test_approx_eq ss.sx sum(x)
@test_approx_eq ss.tw n0

ss = suffstats(Poisson, x, w)
@test isa(ss, Distributions.PoissonStats)
@test_approx_eq ss.sx dot(x, w)
@test_approx_eq ss.tw sum(w)

d = fit(Poisson, x)
@test isa(d, Poisson)
@test_approx_eq mean(d) mean(x)

d = fit(Poisson, x, w)
@test isa(d, Poisson)
@test_approx_eq mean(d) dot(Float64[xx for xx in x], w) / sum(w)

d = fit(Poisson, rand(Poisson(8.2), N))
@test isa(d, Poisson)
@test_approx_eq_eps mean(d) 8.2 0.2


# ------------------------------------------------------------------
# Licensed under the MIT License. See LICENSE in the project root.
# ------------------------------------------------------------------

"""
    EigenAnalysis(proj)

The eigenanalysis of the covariance with a given projection `proj`.

## Projections

* `:V` - Uncorrelated variables (PCA transform)
* `:VD` - Uncorrelated variables and variance one (DRS transform)
* `:VDV` - Uncorrelated variables and variance one (SDS transformation)

The `:V` projection used in the PCA transform projects the data on the eigenvectors
V of the covariance matrix.

The `:VD` projection used in the DRS transform. Similar to the `:V` projection,
but the eigenvectors are multiplied by the squared inverse of the eigenvalues D.

The `:VDV` projection used in the SDS transform. Similar to the `:VD` transform,
but the data is projected back to the basis of the original variables using the Vᵀ matrix.

See [https://geostatisticslessons.com/lessons/sphereingmaf](https://geostatisticslessons.com/lessons/sphereingmaf)
for more details about these three variants of eigenanalysis.

# Examples

```julia
EigenAnalysis(:V)
EigenAnalysis(:VD)
EigenAnalysis(:VDV)
```
"""
struct EigenAnalysis <: Transform
  proj::Symbol

  function EigenAnalysis(proj)
    @assert proj ∈ (:V, :VD, :VDV) "invalid projection"
    new(proj)
  end
end

assertions(::Type{EigenAnalysis}) = [assert_continuous]

isrevertible(::Type{EigenAnalysis}) = true

function apply(transform::EigenAnalysis, table)
  # basic checks
  for assertion in assertions(transform)
    assertion(table)
  end

  # original columns names
  names = Tables.columnnames(table)

  # table as matrix
  X = Tables.matrix(table)

  # center the data
  μ = mean(X, dims=1)
  Y = X .- μ

  # eigenanalysis of covariance
  S, S⁻¹ = eigenmatrices(transform, Y)

  # project the data
  Z = Y * S

  # table with transformed columns
  𝒯 = (; zip(names, eachcol(Z))...)
  newtable = 𝒯 |> Tables.materializer(table)

  newtable, (μ, S, S⁻¹)
end

function revert(::EigenAnalysis, newtable, cache)
  # transformed column names
  names = Tables.columnnames(newtable)

  # table as matrix
  Z = Tables.matrix(newtable)

  # retrieve cache
  μ, S, S⁻¹ = cache

  # undo projection
  Y = Z * S⁻¹

  # undo centering
  X = Y .+ μ

  # table with original columns
  𝒯 = (; zip(names, eachcol(X))...)
  𝒯 |> Tables.materializer(newtable)
end

function reapply(transform::EigenAnalysis, table, cache)
  # basic checks
  for assertion in assertions(transform)
    assertion(table)
  end

  # original columns names
  names = Tables.columnnames(table)

  # table as matrix
  X = Tables.matrix(table)

  # retrieve cache
  μ, S, S⁻¹ = cache

  # center the data
  Y = X .- μ

  # project the data
  Z = Y * S

  # table with transformed columns
  𝒯 = (; zip(names, eachcol(Z))...)
  𝒯 |> Tables.materializer(table)
end

function eigenmatrices(transform, Y)
  proj = transform.proj

  Σ = cov(Y)
  λ, V = eigen(Σ)

  if proj == :V
    S   = V
    S⁻¹ = transpose(V)
  elseif proj == :VD
    Λ   = Diagonal(sqrt.(λ))
    S   = V * inv(Λ)
    S⁻¹ = Λ * transpose(V)
  elseif proj == :VDV
    Λ   = Diagonal(sqrt.(λ))
    S   = V * inv(Λ) * transpose(V)
    S⁻¹ = V * Λ * transpose(V)
  end

  S, S⁻¹
end

"""
    PCA()

The PCA transform is a shortcut for
`ZScore() → EigenAnalysis(:V)`.

See also: [`ZScore`](@ref), [`EigenAnalysis`](@ref).
"""
PCA() = ZScore() → EigenAnalysis(:V)

"""
    DRS()

The DRS transform is a shortcut for
`ZScore() → EigenAnalysis(:VD)`.

See also: [`ZScore`](@ref), [`EigenAnalysis`](@ref).
"""
DRS() = ZScore() → EigenAnalysis(:VD)

"""
    SDS()

The SDS transform is a shortcut for
`ZScore() → EigenAnalysis(:VDV)`.

See also: [`ZScore`](@ref), [`EigenAnalysis`](@ref).
"""
SDS() = ZScore() → EigenAnalysis(:VDV)


function spectralClustering(A::Array{Float64,2}, nEigen::Int)

	D = Diagonal(sum(A, dims=1)[:])
	L = D-A
	NL = D^(-1/2) * L * D^(-1/2)
	vals, vects = eigen(NL)
	X = vects[:,1:nEigen]
	XX = fill(0.0,size(X))

	for i in 1:size(X,1)
		XX[i,:] = X[i,:] ./ sqrt(sum(X[i,:].^2))
	end

	return vals, XX
end


# PROX_L1    The proximal operator of the l1 norm.
#
#   prox_l1(v,lambda) is the proximal operator of the l1 norm
#   with parameter lambda.
function x = prox_l1(v, lambda)
    x = max(0, v - lambda) - max(0, -v - lambda)
    return x
end


## Wetted-area method
#======================================================#

# Raymer form factor for wing, pylon, nacelle, fuselage? (Eq. 12.30)
form_factor_wing(x_c, t_c, Λ_m, M) = (1 + 0.6t_c / x_c + 100t_c^4) * (1.34M^0.18 * cos(Λ_m)^0.28)

function form_factor(wing :: HalfWing, M)
	# (x/c)_max, (t/c)_max, (t/c)_max sweep angles
	xcs, tcs, sweeps = max_tbyc_sweeps(wing, 60)
	Kf 				 = form_factor_wing.(xcs, tcs, sweeps, M)
end

# Schlichting averaged skin-friction coefficients
cf_lam(Re_c, k_lam = 1.) = 1.328 / √(Re_c * k_lam)
cf_turb(Re_c, M) = 0.455 / log10(Re_c)^2.58 / (1 + 0.144M^2)^0.65
cf_schlichting(Re, Re_xtr, M) = max(cf_lam(Re), cf_turb(Re, M) - (Re_xtr / 320 - 39) / Re)

function wetted_area_drag(mean_chords, S_wets, K_fs, x_tr, V, ρ, M, μ)
	# Skin-friction coefficients
	Re_c 		= reynolds_number.(ρ, V, mean_chords, μ)
	Re_xtr 		= reynolds_number.(ρ, V, mean_chords .* x_tr, μ)
	cfs 		= cf_schlichting.(Re_c, Re_xtr, M)

	# Profile drag
	Dp_by_q	 	= sum(@. cfs * S_wets * K_fs)
end

function wetted_area_drag(wing :: HalfWing, x_tr, V, ρ, a_ref = 330., μ = 1.5e-5)
	# Chord processing
	mean_chords = (fwdsum ∘ chords)(wing) / 2

	# Wetted areas
	S_wets = @. mean_chords * wing.spans / cos(wing.dihedrals)

	# Form factors
	M 	 = V / a_ref
	K_fs = form_factor(wing, M)

	wetted_area_drag(mean_chords, S_wets, K_fs, x_tr, V, ρ, M, μ)
end

profile_drag_coefficient(wing :: HalfWing, x_tr, V, rho_ref, a_ref, area_ref, μ) = wetted_area_drag(wing, x_tr, V, rho_ref, a_ref, μ) / area_ref
profile_drag_coefficient(wing :: Wing, x_tr, V, rho_ref, a_ref, area_ref, μ) = profile_drag_coefficient(left(wing), x_tr, V, rho_ref, a_ref, area_ref, μ) + profile_drag_coefficient(right(wing), x_tr, V, rho_ref, a_ref, area_ref, μ)

## Local-friction and local-dissipation method


# ---
# title: 1017. Convert to Base -2
# id: problem1017
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Math
# link: <https://leetcode.com/problems/convert-to-base-2/description/>
# hidden: true
# ---
#
# Given a number `N`, return a string consisting of `"0"`s and `"1"`s that
# represents its value in base `**-2**` (negative two).
#
# The returned string must have no leading zeroes, unless the string is `"0"`.
#
#
#
# **Example 1:**
#
#
#
#     Input: 2
#     Output: "110"
#     **Explantion:** (-2) ^ 2 + (-2) ^ 1 = 2
#
#
# **Example 2:**
#
#
#
#     Input: 3
#     Output: "111"
#     **Explantion:** (-2) ^ 2 + (-2) ^ 1 + (-2) ^ 0 = 3
#
#
# **Example 3:**
#
#
#
#     Input: 4
#     Output: "100"
#     **Explantion:** (-2) ^ 2 = 4
#
#
#
#
# **Note:**
#
#   1. `0 <= N <= 10^9`
#
#
## @lc code=start
using LeetCode

function base_neg2(N::Int)
    cnt = 0
    res = ""
    while N > 0
        bit = (N & 1)
        N -= bit * (-1) ^ cnt
        N >>= 1
        cnt += 1
        res = string(bit) * res
    end
    res
end
## @lc code=end


using Distributions, Plots, Colors

# Gaussian with known variance
# mu ~ Normal(mu0, sig0)
# x_i ~ Normal(mu, sig_like)
# Ref: https://en.wikipedia.org/wiki/Conjugate_prior#Continuous_distributions
function compute_posterior_parameters(xs, mu0, sig0, sig_like)
    n = length(xs)
    var = inv(1 / sig0^2 + n / sig_like^2)
    mu = var * (mu0 / sig0^2 + sum(xs) / sig_like^2)
    return mu, sqrt(var)
end

mu0 = 0.0
sig0 = 1.0
sig_like = 1.0
prior = Normal(mu0, sig0)

# Inference
obs = [5.0]
po1 = compute_posterior_parameters(obs, mu0, sig0, sig_like)
posterior_1 = Normal(compute_posterior_parameters(obs, mu0, sig0, sig_like)...)

# Inference with more data
push!(obs, 1.0)
po2 = compute_posterior_parameters(obs, mu0, sig0, sig_like)
posterior_2 = Normal(compute_posterior_parameters(obs, mu0, sig0, sig_like)...)

julia_purple = parse(Colorant, RGBA(0.702, 0.322, 0.8))
julia_brown = parse(Colorant, RGBA(0.8, 0.2, 0.2))
julia_green = parse(Colorant, RGBA(0.133, 0.541, 0.133))

xs = -3:0.01:5
lw = 15.0
plot(xs, pdf.(Ref(prior), xs), color=julia_purple, linewidth=lw, lab="prior", grid=false, legend=false, background_color = RGBA(1, 1, 1, 0))
plot!(xs, pdf.(Ref(posterior_1), xs), color=julia_brown, linewidth=lw, lab="posterior 1")
plot!(xs, pdf.(Ref(posterior_2), xs), color=julia_green, linewidth=lw, lab="posterior 2")
xaxis!(false)
yaxis!(false)

savefig("turing-logo.svg")


# Silverman's rule of thumb for KDE bandwidth selection
function bandwidth(data::Vector, alpha::Float64 = 0.9)
    # Determine length of data
    ndata = length(data)

    # Calculate width using variance and IQR
    var_width = std(data)
    q25, q75 = quantile(data, [0.25, 0.75])
    quantile_width = (q75 - q25) / 1.34

    # Deal with edge cases with 0 IQR or variance
    width = min(var_width, quantile_width)
    if width == 0.0
        if var_width == 0.0
            width = 1.0
        else
            width = var_width
        end
    end

    # Set bandwidth using Silverman's rule of thumb
    return alpha * width * ndata^(-0.2)
end

# Store both grid and density for KDE over the real line
immutable UnivariateKDE
    x::Vector
    density::Vector
end

# Algorithm AS 176 for calculating univariate KDE
function kde(data::Vector, npoints::Integer = 512)
    # Determine length of data
    ndata = length(data)

    # Set bandwidth
    window = bandwidth(data)

    # Check that the window is a positive constant
    if window <= 0.0
        error("Window must be positive")
    end

    # Find interval that will contain almost all mass
    dlo = min(data) - 3 * window
    dhi = max(data) + 3 * window

    # Check that interval for estimation is valid
    if dlo >= dhi
        error("Interval must be specified as lower bound, upper bound")
    end

    # Set up a grid for discretized data
    grid = zeros(Float64, npoints)

    # Define some more constants
    step = (dhi - dlo) / npoints
    ainc = 1.0 / (ndata * step)
    npoints2 = fld(npoints, 2)
    hw = window / step
    fac1 = 32.0 * (atan(1.0) * hw / npoints)^2

    # Discretize the data using a histogram
    dlo1 = dlo - step
    for i in 1:ndata
        j = fld(data[i] - dlo1, step)
        if j >= 1 && j <= npoints
            grid[j] = grid[j] + ainc
        end
    end

    # Transform to Fourier basis
    ft = rfft(grid)

    # Find transform of KDE by convolving grid with the
    # Fourier transform of a Gaussian kernel
    for j = 2:length(ft)
        ft[j] *= exp(-fac1 * (j-1)^2)
    end

    # Invert the Fourier transform to get the KDE
    density = irfft(ft, npoints)

    # Fix any noise that crept in
    for j in 1:npoints
        if density[j] < 0.0
            density[j] = 0.0
        end
    end

    # Expand the grid over which KDE was calculated
    x = [dlo:step:(dhi - step)]

    return UnivariateKDE(x, density)
end