using CSV
using LinearAlgebra: dot, I
using CairoMakie

const Σ = [1.0  0.1  0.1;
           0.1  1.0  0.1;
           0.1  0.1  1.0]

const v = [1.0/sqrt(3), 1.0/sqrt(3), 1.0/sqrt(3)]
const σ² = 1

const Σ2 = -0.95 * v * v' + I(3)

f(x) = abs2(dot(v, Σ * x)) / (dot(x, Σ * x) + σ² * dot(x, x))

g(x) = abs2(dot(v, Σ2 * x)) / (dot(x, Σ2 * x) + σ² * dot(x, x))

# Helper function to compute extra coordinate
function getZ(x,y)
    if abs2(x) + abs2(y) > 1 - 10*eps()
        return NaN
    end
    return sqrt(1 - abs2(x) - abs2(y))
end

function rewardPositive(x,y)
    return f([x,y,getZ(x,y)])
end

function rewardNegative(x,y)
    return f([x,y,-getZ(x,y)])
end

function rewardPositive2(x,y)
    return g([x,y,getZ(x,y)])
end

function rewardNegative2(x,y)
    return g([x,y,-getZ(x,y)])
end

# Sample Circle
x = -1.1:0.01:1.1
y = -1.1:0.01:1.1

noto_sans = "./resources/NotoSans-Regular.ttf"
noto_sans_bold = "./resources/NotoSans-Bold.ttf"

tickfontsize    = 26
labelfontsize   = 28
basewidth = 2

fig = Figure(font=noto_sans, resolution=(1500,525), figure_padding=40)

ax1 = Axis( fig[1,1],
            xticklabelsize=tickfontsize,
            yticklabelsize=tickfontsize,
            yticklabelpad=2,
            xlabel="x", ylabel = "y",
            xlabelsize=labelfontsize,
            ylabelsize=labelfontsize,
            titlesize=labelfontsize,
            ratio=AxisAspect(1))

hm = heatmap!(ax1,x,y,rewardPositive)

ax2 = Axis( fig[1,2],
            xticklabelsize=tickfontsize,
            yticklabelsize=tickfontsize,
            yticklabelpad=2,
            xlabel="x", ylabel = "y",
            xlabelsize=labelfontsize,
            ylabelsize=labelfontsize,
            titlesize=labelfontsize,
            ratio=AxisAspect(1))

heatmap!(ax2,x,y,rewardNegative)

ax3 = Axis( fig[1,3],
            xticklabelsize=tickfontsize,
            yticklabelsize=tickfontsize,
            yticklabelpad=2,
            xlabel="x", ylabel = "y",
            xlabelsize=labelfontsize,
            ylabelsize=labelfontsize,
            titlesize=labelfontsize,
            ratio=AxisAspect(1))

hm2 = heatmap!(ax3,x,y,rewardPositive2)

ax4 = Axis( fig[1,4],
            xticklabelsize=tickfontsize,
            yticklabelsize=tickfontsize,
            yticklabelpad=2,
            xlabel="x", ylabel = "y",
            xlabelsize=labelfontsize,
            ylabelsize=labelfontsize,
            titlesize=labelfontsize,
            ratio=AxisAspect(1))

heatmap!(ax4,x,y,rewardNegative2)

Colorbar(fig[2, 1:2], hm, vertical = false, flipaxis = false, ticklabelsize=tickfontsize)
Colorbar(fig[2, 3:4], hm2, vertical = false, flipaxis = false, ticklabelsize=tickfontsize)

colgap!(fig.layout,15)
colgap!(fig.layout,2,100)

Label(fig[1,1,TopLeft()], "A", font=noto_sans_bold, textsize = 36, halign=:left, valign=:bottom, padding=(25,0,10,0))
Label(fig[1,3,TopLeft()], "B", font=noto_sans_bold, textsize = 36, halign=:left, valign=:bottom, padding=(25,0,10,0))

save("out/OptSurface.pdf",fig)


using Gridap

# Geometry
domain = (0,1,0,1); cells = (10,10)
model = CartesianDiscreteModel(domain,cells)

# Integration
degree = 2
Ω = Triangulation(model)
Γ = BoundaryTriangulation(model)
dΩ = LebesgueMeasure(Ω,degree)
dΓ = LebesgueMeasure(Γ,degree)

# Manufactured solution
u(x) = x[1]+x[2]

# Parameter-dependent weak form
a(p,u,v) = ∫( (p*p)*∇(v)⋅∇(u) )*dΩ
l(v) = ∫( -v*Δ(u) )*dΩ

# Objective function in terms of primal solution
# (squared distance wrt exact solution u on Ω and Γ
# as an example involving different domains)
# Note: the following function returns cell contributions.
# The final objective is computed as sum(j(v))
j(v) = ∫( abs2(u-v) )*dΩ + ∫( abs2(u-v) )*dΓ

# FE spaces: P for params, V, U for primal solution
P = FESpace(model,ReferenceFE(:Lagrangian,Float64,0),conformity=:L2)
V = TestFESpace(model,ReferenceFE(:Lagrangian,Float64,1),dirichlet_tags="boundary")
U = TrialFESpace(V,u)

using Gridap.FESpaces

# Objective function (scalar), plus its gradient (vector) in terms of p
# fp, ∇fp = f_∇f(p)
function f_∇f(p)
  # Direct solve
  a_at_p(u,v) = a(p,u,v)
  op = AffineFEOperator(a_at_p,l,U,V)
  u = solve(op)
  # Adjoint solve
  A = get_matrix(op)
  dj_du = ∇(j)(u) # autodiff
  b = assemble_vector(dj_du,V)
  λ = FEFunction(V,A\b) # assuming A self-adjoint
  # we want to have something like this
  # op_t = AdjointFEOperator(op, ∇(j), selfadjoint=true )
  # u, lambda = solve(op,op_t)
  # for the moment, we can do more low level things at the driver level, e.g., extracting matrices, etc for the adjoint
  # in any case, it is not needed for self-adjoint problems
  # Objective
  fp = sum(j(u))
  # Its gradient
  ∂a_∂p = ∇(x -> a(x,u,λ))(p) # Autodiff
  ∇fp = -1*assemble_vector(∂a_∂p,P)
  fp, ∇fp
end

# run for a given realization of p
p = FEFunction(P,rand(num_free_dofs(P)).+1)
fp, ∇fp = f_∇f(p)
# Print results
@show fp
writevtk(Ω,"trian",cellfields=["∇fp"=>FEFunction(P,∇fp)])

function df_dp(uvec,pvec,λvec)
  da(p,u,v,dp) = (2*p*dp)*∇(v)⋅∇(u)
  uh = FEFunction(U,uvec)
  ph = FEFunction(P,pvec)
  λh = FEFunction(V,λvec)
  t = FESource((dp)->da(ph,uh,λh,dp),trian,quad)
  op = AffineFEOperator(P,Q,t)
  get_vector(op)
end


g(a,b) = 3a+2b


using Test, SimplePadics, SimplePolynomials

x = Padic{5}(-1)

@test x * x == 1
@test x == -1

y = sqrt(x)
@test y * y == -1

@test x + x == 2x
@test x + 1 == 0
@test x - 1 == -Padic{5}(2)
@test valuation(x) == 0
@test valuation(x / 5) == -1
@test valuation(5x) == 1
@test base(x) == 5
@test x // 5 == x / 5
@test x / x == 1
@test x // x == Padic{5}(1)

y = exp(5x)
@test log(y) == -5

@test Padic{5}(2 // 3) * 3 == 2

s = string(x)
@test s[1] == '…'
@test s[end-5:end] == ".0_{5}"

a = Padic{17}(1000)
d = digits(a)
@test d[1] + 17 * d[2] + 17^2 * d[3] == 1000

set_precision(20)
@test get_precision() == 20

a = Padic{5}(100)
@test abs(a) == 1 / 25
@test abs(a) == abs(-a)
@test abs(0a) == 0

x = getx()
F = x^2 + 1
@test has_p_root(F, 5)[1]
t = p_root(F, 5)
@test F(t) == 0


module LCA
export lca

#Make all columns of the input matrix have a norm of 1
# D - input matrix (modified in place)
function unitary_columns!(D::Matrix{Float64})
    for i = 1:size(D,2)
        D[:,i] = D[:,i]/norm(D[:,i]);
    end
    nothing
end

#Perform the positive/negative soft threshold function on each element
# a - result vector (modified in place)
# u - input vector
# T - threshold
function soft_threshold!(a::Vector{Float64}, u::Vector{Float64}, T::Float64)
    for i = 1:length(u)
        if abs(u[i]) > T
            a[i,1] = u[i]-sign(u[i])*T;
        else
            a[i,1] = 0;
        end
    end
    nothing
end

#Perform Locally Competitive Algorithm for sparse recovery
# s      - Input to be approximated
# D      - Dictionary
# T_soft - Soft threshold value (higher promotes more sparseness)
# iter   - maximum number of iterations
# τ      - 1/learning rate
function lca(s::Vector{Float64}, D::Matrix{Float64}; T_soft::Float64=0.1,
             iter::Int=10000, τ::Float64=100.0)
    #Impose Unitary norm
    unitary_columns!(D)

    # Inhibition Matrix
    G = D'*D;
    for i=1:size(G,1)
        G[i,i] = 0;
    end

    # Initialize LCA Parameters
    b = D'*s; # initial projection / excitatory input

    u = zeros(size(b)); # initial state of nodes = 0
    a = zeros(size(b)); # initial sparse rep = 0

    for i=1:iter
        # soft thresholding function
        soft_threshold!(a, u, T_soft)

        # node dynamics
        Δu = (b  - u - G*a)/τ;
        u += Δu;

        if(norm(Δu) < 1e-5)
            break
        end
    end

    return a
end

end # module


const TreesMap = BitArray{2}

function readinput(strs)
    TreesMap(transpose(hcat([[c == '#' for c ∈ l] for l ∈ strs]...)))
end

# First Half

function treesencountered₁(tmap)
    #= =========================================================================
    The trees map of the problem can be projected on cilindrical discrete plane
    where cilinder's height and perimeter are respectively mapₕ and mapₗ.
    The movement happens on a line on a discrete plane.
    The equation for a line starting at (0,0) in a cilindrical semi-infinite
    discrete plane is x = m * y mod mapₗ, where x, y are integers,
    slope m is a rational and m * y is rounded to the nearest integer.
    In our case m = mₕ / mᵥ (right slope / down slope) and the path is not
    this line, but only the dots on this line with vertical coordinate
    multiple of mᵥ (jumping).
    ========================================================================= =#

    mₕ = 3
    mᵥ = 1
    m = mₕ // mᵥ
    mapₕ, mapₗ = size(tmap)
    f(y) = tmap[y + 1, Int(m * y) % mapₗ + 1] # Arrays in Julia start from 1
    count(f, 0:mᵥ:mapₕ - 1) # Arrays in Julia start from 1
end

puzzleinput = readlines("inputs/day03.txt")
treesmap = readinput(puzzleinput)

treesencountered₁(treesmap)

# Second Half

function treesencountered₂(tmap)

    mₕ = [1, 3, 5, 7, 1]
    mᵥ = [1, 1, 1, 1, 2]
    M = collect(zip(mₕ, mᵥ))
    mapₕ, mapₗ = size(tmap)
    f(m) = y -> tmap[y + 1, Int(m[1] // m[2] * y) % mapₗ + 1]
    [count(f(m), 0:m[2]:mapₕ - 1) for m ∈ M]
end

puzzleinput = readlines("inputs/day03.txt")
treesmap = readinput(puzzleinput)

prod(treesencountered₂(treesmap))
# Multiply together the number of trees encountered on each of the listed slopes


using JuSwarm, StaticArrays, Test
import Optim

function rosenbrockfunc(x::AbstractVector)
    sum = 0.0
    for i in 1:length(x) - 1
        sum += 100*(x[i + 1] - x[i]^2)^2 + (x[i] - 1)^2
    end
    return sum
end

N = 2
opts = PSOOptions(N; SwarmSize = 1000,
                     HybridOptimizer = Optim.LBFGS(),
                     Display = "none",
                     UseParallel = true)

sol = psoptimize(rosenbrockfunc,opts)

@test sol.fbest <= sol.fbestPSO

@test sol.fbestPSO <= 1.0e-6

@test sol.fbest <= 1.0e-10


module TargetPlot
import Base.MathConstants as cons
import Plots as plt
plt.pyplot()

function f(x, y)
    return -20.0 * exp(-0.2 * sqrt(0.5 * (x^2 + y^2))) -
  exp(0.5 * (cos(2.0 * pi * x) + cos(2.0 * pi * y))) +  cons.e + 20.0
  #  ((1.0 - y) * sin(x))^2 + (y * (2.0 - x))^2
end

const plot = function ()
    x = -5.0:0.1:5.0
    y = -5.0:0.1:5.0
    # plt.heatmap(x, y, f)
    plt.plot(x, y, f, st=:wireframe)
    plt.png("my-plots/wireframe.png")
end

end


using Test
using Solvent
using StaticArrays
using LinearAlgebra
using SparseArrays
using Random
using Printf

# Creates a Laplacian matrix based on the code from: http://math.mit.edu/~stevenj/18.303/lecture-10.html
# construct the (M+1)xM matrix D, not including the 1/dx factor
sdiff1(M) = [ [1.0 spzeros(1, M-1)]; spdiagm(1=>ones(M-1)) - I ]

# make the discrete -Laplacian in 2d, with Dirichlet boundaries
function Laplacian(Nx, Ny, Lx, Ly)
    dx = Lx / (Nx+1)
    dy = Ly / (Ny+1)
    Dx = sdiff1(Nx) / dx
    Dy = sdiff1(Ny) / dy
    Ax = Dx' * Dx
    Ay = Dy' * Dy
    return kron(spdiagm(0=>ones(Ny)), Ax) + kron(Ay, spdiagm(0=>ones(Nx)))
end

@testset "Solvent Laplacian: CG" begin
    T = Float64
    Lx = 1
    Ly = 1
    Nx = 10
    Ny = 10
    A = Laplacian(Nx, Ny, Lx, Ly)
    n, _ = size(A)
    @info "Size of matrix A: ($n, $n)"
    b = rand(T, n)
    x = rand(T, n)

    mulbyA!(y, x) = (y .= A * x)

    rtol = sqrt(eps(T))
    atol = eps(T)
    solver_type = ConjugateGradientMethod(M = 100)
    linearsolver = LinearSolver(
        mulbyA!,
        solver_type,
        x;
        rtol = rtol,
        atol = atol,
    )

    x0 = copy(x)
    linearsolve!(linearsolver, x, b)
    @test norm(A * x - b) < rtol * norm(A * x0 - b)

end

# Tests effectiveness of Jacobi preconditioner using specially designed matrix from:
# http://www.math.iit.edu/~fass/477577_Chapter_16.pdf
@testset "Solvent Preconditioned Jacobi: CG" begin
    T = Float64
    n = 1000
    dv0 = .5 .+ .√(1:n)
    dv1 = ones(n - 1)
    dv2 = ones(n - 100)
    A = spdiagm(0=>dv0) + spdiagm(1=>dv1) + spdiagm(-1=>dv1) +
        spdiagm(100=>dv2) + spdiagm(-100=>dv2)
    @info "Size of matrix A: ($n, $n)"
    b = ones(T, 1000)
    x = rand(T, n)

    mulbyA!(y, x) = (y .= A * x)

    rtol = sqrt(eps(T))
    atol = eps(T)
    solver_type = ConjugateGradientMethod(M = 100)
    identitysolver = LinearSolver(
        mulbyA!,
        solver_type,
        x;
        rtol = rtol,
        atol = atol,
    )
    jacobisolver = LinearSolver(
        mulbyA!,
        solver_type,
        x;
        pc_alg = Jacobi(),
        rtol = rtol,
        atol = atol,
    )

    x0 = copy(x)
    jacobiiters = linearsolve!(jacobisolver, x, b)
    @test norm(A * x - b) < rtol * norm(A * x0 - b)
    identityiters = linearsolve!(identitysolver, x0, b)
    @test jacobiiters < identityiters
end


using FFTW
using Plots

a = 1        # Thermal diffusivity constant
L = 100      # Length of domain
N = 1000     # Number of discretization points
dx = L/N
x = -L/2:dx:L/2-dx # Define x domain

# Define discrete wavenumbers
kappa = (2pi/L)*(-N/2:N/2-1)
kappa = fftshift(kappa) # Re-order fft wavenumbers

# Initial condition
u0 = zeros(N)
u0[(L/2 - L/10)/dx:(L/2 + L/10)/dx] = 1

# Simulate in Fourier frequency domain
# t = 0:0.1:10
# [t,uhat]=ode45(@(t,uhat)rhsHeat(t,uhat,kappa,a),t,fft(u0));

# for k = 1:length(t) # iFFT to return to spatial domain
#     u(k,:) = ifft(uhat(k,:))
# end

# Plot solution in time
figure, waterfall((u(1:10:end,:)));
figure, imagesc(flipud(u));

%% FIGURES (PRODUCTION)
figure
CC = colormap(jet(100));
dt = 0.1;
for k = 1:100
    u(k,:) = ifft(uhat(k,:));
    if(mod(k-1,10)==0)
        plot(x,u(k,:),'Color',CC(k,:),'LineWidth',1.5)
        hold on, grid on, drawnow
    end
end
% xlabel('Spatial variable, x')
% ylabel('Temperature, u(x,t)')
axis([-50 50 -.1 1.1])
set(gca,'LineWidth',1.2,'FontSize',12);
set(gcf,'Position',[100 100 550 220]);
set(gcf,'PaperPositionMode','auto')
% print('-depsc2', '-loose', '../../figures/FFTHeat1');

%
figure
subplot(1,2,1)
h=waterfall(u(1:10:end,:));
set(h,'LineWidth',2,'FaceAlpha',.5);
colormap(jet/1.5)
view(22,29)
set(gca,'LineWidth',1.5)
set(gca,'XTick',[0 500 1000],'XTickLabels',{})
set(gca,'ZTick',[0 .5 1],'ZTickLabels',{})
set(gca,'YTick',[0 5 10],'YTickLabels',{})

subplot(1,2,2)
imagesc(flipud(u));
set(gca,'LineWidth',1.5)
set(gca,'XTick',[0 500 1000],'XTickLabels',{})
set(gca,'YTick',[0 50 100],'YTickLabels',{})

colormap jet
set(gcf,'Position',[100 100 600 250])
set(gcf,'PaperPositionMode','auto')
% print('-depsc2', '-loose', '../../figures/FFTHeat2');


@testset "argmax" begin
    arg, val = GeometryTypes.argmax(sin, range(0, stop=π, length=101))
    @test arg ≈ π/2
    @test val ≈ 1

    arg, val = GeometryTypes.argmax(cos, range(0, stop=π, length=101))
    @test arg ≈ 0  # 0, not π, because argmax requires a subsequent value to be *strictly better* than the current best
    @test val ≈ 1.0

    @test @allocated(GeometryTypes.argmax(sin, range(0, stop=π, length=101))) == 0

    @test_throws ArgumentError GeometryTypes.argmax(identity, [])
end

@testset "extrema" begin
    xs = [GeometryTypes.Vec2f0(1f0, 1f0), GeometryTypes.Vec2f0(0.5f0, 1.5f0)]
    extr = extrema(xs)

    @test extr[1] ≈ GeometryTypes.Vec2f0(0.5f0, 1f0)
    @test extr[2] ≈ GeometryTypes.Vec2f0(1f0, 1.5f0)

    @testset "minimum" begin
        val = minimum(xs)
        @test val ≈ extr[1]
    end

    @testset "maximum" begin
        val = maximum(xs)
        @test val ≈ extr[2]
    end
end


#=
P35 (**) Determine the prime factors of a given positive integer.
Construct a flat list containing the prime factors in ascending order.
Example:
* (prime-factors 315)
(3 3 5 7)
=#

SMALLEST_PRIME = 2

function next_prime(n)
    # Brute-force incremental check
    isprime(n) ? n : next_prime(n + 1)
end

function _prime_factors(n, x, pf)
    if isprime(n)
        vcat(pf, n)
    else
        if mod(n, x) == 0
            _prime_factors(div(n, x), SMALLEST_PRIME, vcat(pf, x))
        else
            _prime_factors(n, next_prime(x + 1), pf)
        end
    end
end

function prime_factors(n)
    # Starting with the smallest prime, 2
    isprime(n) ? n : _prime_factors(n, SMALLEST_PRIME, [])
end

@assert prime_factors(315) == [3, 3, 5, 7]
@assert prime_factors(360) == [2, 2, 2, 3, 3, 5]
@assert prime_factors(144) == [2, 2, 2, 2, 3, 3]

println("Tests passed: JL-35.jl")


# Simple example of a Neumann problem:
#
#           y
#             |
#             |     u_y = cos(omega x)
#          Ly ----------------------------------
#             |                                |
#             |                                |
#             |                                |
#     u_x = 0 |    -u_xx - u_yy + u = 0        | u_x = 0
#             |                                |
#             |                                |
#             |                                |
#           -----------------------------------|-----  x
#                         u_y = 0              Lx
#
# Here, omega = 2 n pi / Lx, and the exact solution is
#
#      u = cos(omega x) * cosh(alpha y) / (alpha sinh(alpha Ly)
#
# where alpha^2 = 1 + omega^2.

using FinElt
using FinElt.PlanarPoisson
using Printf

include("params.jl")

const omega = 2 * pi / Lx
const alpha = sqrt(1.0 + omega^2)

function exact_u(x)
    return cos(omega*x[1]) * cosh(alpha*x[2]) / (alpha*sinh(alpha*Ly))
end

function gN(x)
    return cos(omega*x[1])
end

maxerr = zeros(refinements+1)
@printf("%10s  %12s  %8s  %8s\n\n",
        "N", "max error", "rate", "seconds")
for k = 0:refinements
    start = time()
    mesh = read_msh_file("rect$k.msh")
    vp = VariationalProblem(mesh)
    add_bilin_form!(vp, "Omega", grad_dot_grad!)
    add_bilin_form!(vp, "Omega", func_times_func!)
    add_lin_functnl!(vp, "Top", bdry_source_times_func!, gN)
    A, b = assembled_linear_system(vp)
    ufree = A \ b
    uh = complete_soln(ufree, vp)
    u = get_nodal_vals(exact_u, mesh)
    finish = time()
    maxerr[k+1] = maximum(abs.(uh-u))
    N = length(ufree)
    if k == 0
        @printf("%10d  %12.4e\n", N, maxerr[k+1])
    else
        rate = log2(maxerr[k]/maxerr[k+1])
        elapsed = finish - start
        @printf("%10d  %12.4e  %8.4f  %8.4f\n",
                N, maxerr[k+1], rate, elapsed)
    end
end


module SfGravity

using PhysicalConstants.CODATA2018: c_0, g_n, G, StefanBoltzmannConstant, ħ, k_B
using Unitful, UnitfulAstro, Documenter

export vis_viva, gravity, planetary_mass, planetary_radius, escape_velocity, hill_sphere,
	orbital_velocity, orbital_period, gravitational_binding_energy, roche_limit, gravity_tug_mass,
	barycentric_distance, tidal_acceleration,
	radial_orbit_time, radial_orbit_displacement

DocMeta.setdocmeta!(SfGravity, :DocTestSetup, :(using Unitful, UnitfulAstro, ..SfGravity); recursive=true)

"""
    vis_viva(parent_mass::Unitful.Mass, semimajor_axis::Unitful.Length, current_radius::Unitful.Length)

Return the square of the relative velocities of a body with negligible mass and orbit with `semimajor_axis` and `current_radius` from a central body of `parent_mass`.

``v^2 = GM\\left( \\frac{2}{r} - \\frac{1}{a} \\right)``

where ``G`` is the gravitational constant, ``M`` is the mass of the central body, ``r`` is the current separation of the bodies and ``a`` is the semi-major axis of the orbit.
"""
function vis_viva(parent_mass::Unitful.Mass, semimajor_axis::Unitful.Length, current_radius::Unitful.Length)
	G*parent_mass*(2/current_radius - 1/semimajor_axis) |> u"m^2/s^2"
end

"""
	gravity(m::Unitful.Mass, r::Unitful.Length)

Calculate acceleration due to gravity at distance `r` from point mass `m`

# Example
```jldoctest
julia> gravity(1u"Mearth", 1u"Rearth")
9.798398133669465 m s^-2
```"""
gravity(m::Unitful.Mass, r::Unitful.Length) = G * m / r^2 |> u"m/s/s"

"""
	gravity(m1::Unitful.Mass, m2::Unitful.Mass, r::Unitful.Length)

Calculate the strength of gravity at a distance `r` between two masses, `m1` and `m2`.

# Example
```jldoctest
julia> gravity(1u"Mearth", 100u"kg", 1u"Rearth")
979.8398133669466 N
```"""
gravity(m1::Unitful.Mass, m2::Unitful.Mass, r::Unitful.Length) = G * m1 * m2 / r^2 |> u"N"

"""
	planetary_mass(g::Unitful.Acceleration, r::Unitful.Length)

Calculate the mass of a body required to produce gravitational acceleration `g` at a distance of `r`.
"""
planetary_mass(g::Unitful.Acceleration, r::Unitful.Length) = (g * r^2) / G |> u"kg"

"""
	planetary_mass(ve::Unitful.Velocity, r::Unitful.Length)

Calculate the mass of a body required to produce escape velocity `ve` at a distance of `r`.
"""
planetary_mass(ve::Unitful.Velocity, r::Unitful.Length) = r * ve^2 / 2G |> u"kg"

"""
	planetary_radius(m::Unitful.Mass, g::Unitful.Acceleration)

Radius of a spherical planet of mass `m` with surface gravitational acceleration of `g`.
"""
planetary_radius(m::Unitful.Mass, g::Unitful.Acceleration) = sqrt((G * m) / g) |> u"m"

"""
	planetary_radius(m::Unitful.Mass, g::Unitful.Acceleration)

Radius of a spherical planet of mass `m` with surface escape velocity `ve`.
"""
planetary_radius(m::Unitful.Mass, ve::Unitful.Velocity) = 2G * m / ve^2 |> u"m"

"""
	planetary_radius(ρ::Unitful.Density, g::Unitful.Acceleration)

Radius of a spherical planet of density `ρ` with surface gravitational acceleration of `g`.
"""
planetary_radius(ρ::Unitful.Density, g::Unitful.Acceleration) = 3g / (4G * π * ρ) |> u"m"

"""
	planetary_radius(ρ::Unitful.Density, ve::Unitful.Velocity)

Radius of a spherical planet of density `ρ` with surface escape velocity `ve`.
"""
planetary_radius(ρ::Unitful.Density, ve::Unitful.Velocity) = sqrt(3ve^2 / (8G * π * ρ)) |> u"m"

# kepler's third

"""
	orbital_period(m::Unitful.Mass, r::Unitful.Length)

Calculate the period of an orbit with semimajor axis `r` about a body with mass `m`.

``P = \\sqrt{\\frac{4π^2r^3}{Gm}}``
"""
orbital_period(m::Unitful.Mass, r::Unitful.Length) = sqrt((4π^2 * r^3) / (G * m)) |> u"s"

"""
	orbital_radius(m::Unitful.Mass, t::Unitful.Time)

Calculate the semimajor axis of an orbit with period `t` about a body with mass `m`.

``a = \\sqrt[3]{\\frac{t^2Gm}{4π^2}}``
"""
orbital_radius(m::Unitful.Mass, t::Unitful.Time) = cbrt((t^2 * G * m)/(4π^2)) |> u"m"

"""
    orbital_radius(m::Unitful.Mass, v::Unitful.Velocity)

Approximate the radius of a circular orbit with velocity `v` about a body with mass `m`.
"""
orbital_radius(m::Unitful.Mass, v::Unitful.Velocity) = (G * m) / v^2 |> u"m"

"""
	orbital_velocity(sma::Unitful.Length, t::Unitful.Time)

Calculate the average orbital velocity of a circular orbit of period `t`.

The orbit is assumed to be circular.
"""
orbital_velocity(sma::Unitful.Length, t::Unitful.Time) = 2π * sma / t |> u"km/s"

"""
    orbital_velocity(sma::Unitful.Length, t::Unitful.Time, e)

Approximate the average orbital velocity of a orbit with eccentricity `e` and period `t`.

``s_o = \\frac{2πa}{t} \\left( 1 - \\frac{e^2}{4} - \\frac{3e^4}{64} - \\frac{5e^6}{256} - \\frac{175e^8}{16384} \\right)``
"""
function orbital_velocity(sma::Unitful.Length, t::Unitful.Time, e)
	(2π * sma / t) * (1 - e^2 / 4 - 3e^4 / 64 - 5e^6 / 256 - 175e^8 / 16384) |> u"km/s"
end

"""
    orbital_velocity(parent_mass::Unitful.Mass, semimajor_axis::Unitful.Length, current_radius::Unitful.Length)

Approximate the orbital velocity of a body with an eccentric orbit with `semimajor_axis` and current orbital radius `current_radius` about a body of mass `parent_mass`
"""
orbital_velocity(parent_mass::Unitful.Mass, semimajor_axis::Unitful.Length, current_radius::Unitful.Length) = sqrt(vis_viva(parent_mass, sem, current_radius)) |> u"km/s"

"""
    orbital_velocity(parent_mass::Unitful.Mass, radius::Unitful.Length)

Approximate the orbital velocity of a body with a circular orbit of radius `radius` about a body with mass `parent_mass`.
"""
orbital_velocity(parent_mass::Unitful.Mass, radius::Unitful.Length) = sqrt(G * parent_mass / radius) |> u"km/s"

"""
	planetary_mass(orbital_radius::Unitful.Length, orbital_period::Unitful.Time)

Calculate the mass of a body orbited by a satellite with semimajor axis `sma` and period `orbital_period`.

``M_p = \\frac{4π^2a^3}{GT^2}``

where ``a`` is the semi-major axis and ``T`` is the orbital period.
"""
planetary_mass(sma::Unitful.Length, orbital_period::Unitful.Time) = (4π^2 * sma^3) / (G * orbital_period^2) |> u"kg"

"""
	escape_velocity(m::Unitful.Mass, r::Unitful.Length)

Calculate the velocity required to escape a body with mass `m` from distance `r`.
"""
escape_velocity(m::Unitful.Mass, r::Unitful.Length) = sqrt(2G * m / r) |> u"km/s"

"""
	hill_sphere(m_parent::Unitful.Mass, m::Unitful.Mass, sma::Unitful.Length, e = 0)

Calculate the approximate Hill sphere radius at periapse of a body of mass `m` that orbits a body of mass `m_parent` at a distance of `sma` with orbital eccentricity `e`.

``h_r = a(1-e)\\sqrt[3]{\\frac{m}{3M}}``

where ``a`` is the semi-major axis, `m` is the mass of the smaller body and ``M`` is the mass of the central body.
"""
function hill_sphere(m_parent::Unitful.Mass, m::Unitful.Mass, sma::Unitful.Length, e = 0)
	sma * (1-e) * cbrt(m / 3m_parent) |> Unitful.unit(sma)
end

"""
	gravitational_binding_energy(m::Unitful.Mass, r::Unitful.Length)

Approximate the gravitational binding energy of a body with mass `m` and radius `r`.

``E_g = \\frac{3Gm^2}{5r}``
"""
gravitational_binding_energy(m::Unitful.Mass, r::Unitful.Length) = (3G*m^2) / 5r |>u"J"

"""
    roche_limit(r_primary::Unitful.Length, ρ_primary::Unitful.Density, ρ_satellite::Unitful.Density)

Rigid body approximation of the Roche limit for a body with radius `r_primary` and density `ρ_primary`, approached by a body with density `ρ_satellite`.

``r_r = r\\sqrt[2]{\\frac{2ρ_p}{ρ_s}}``

where ``r`` and ``ρ_p`` are the radius and density of the central body, and ρ_s is the density of the orbiting body.
"""
roche_limit(r_primary::Unitful.Length, ρ_primary::Unitful.Density, ρ_satellite::Unitful.Density) = r_primary * cbrt(2ρ_primary / ρ_satellite) |> u"km"

"""
    gravity_tug_mass(force::Unitful.Force, separation::Unitful.Length, body_mass::Unitful.Mass)

Compute the mass required to apply `force` to a body of mass `body_mass` given their barycenters are at a distance of `separated`.
"""
gravity_tug_mass(force::Unitful.Force, separation::Unitful.Length, body_mass::Unitful.Mass) = (force * separation^2)/(G * body_mass) |> u"kg"

"""
    barycentric_distance(m_1::Unitful.Mass, m_2::Unitful.Mass, a::Unitful.Length)

Compute the distance from the barycenter of `m_1` to the barycenter of the `m_1`-`m_2` system, where the masses have an average separation of `a`.
"""
barycentric_distance(m1::Unitful.Mass, m2::Unitful.Mass, a::Unitful.Length) = (a * m2) / (m1 + m2) |> u"km"

"""
    tidal_acceleration(body_radius::Unitful.Length, parent_mass::Unitful.Mass, separation::Unitful.Length)

Approximate tidal acceleration felt by a body of radius `body_radius` at a distance of `separation` from a body of mass `parent_mass`.

Changes in tidal acceleration are associated with effects like heating, etc.

``a_t = \frac{2GMr}{s^3}``

where ``M`` is the mass of the central body, ``r`` is the radius of the body being affected and ``s`` is the distance between the two bodies.
"""
tidal_acceleration(body_radius::Unitful.Length, parent_mass::Unitful.Mass, separation::Unitful.Length) = 2body_radius * G * parent_mass / separation^3

function radial_orbit_time(m1::Unitful.Mass, m2::Unitful.Mass, x0::Unitful.Length, v0::Unitful.Velocity, x::Unitful.Length)
	μ = G * (m1 + m2)
	w = 1/x0 - v0^2/2μ

	return (asin(sqrt(w * x)) - sqrt(w * x * (1 - w * x))) / sqrt(2μ * w^3)
end

function radial_orbit_displacement(m1::Unitful.Mass, m2::Unitful.Mass, x0::Unitful.Length, v0::Unitful.Velocity, t::Unitful.Time)
	μ = G * (m1 + m2)
	w = 1/x0 - v0^2/2μ
	p = cbrt(9μ * t^2 / 2)

	return p - w*p^2 / 5 - 3w^2 * p^3 / 175 - 23w^3 * p^4/7875 - 1894w^4 * p^5 / 3931875 - 3293w^5 * p^6 / 21896875 - 2418092w^6 * p^7/62077640625
end

function radial_orbit_displacement2(m1::Unitful.Mass, m2::Unitful.Mass, x0::Unitful.Length, v0::Unitful.Velocity, t::Unitful.Time)
	μ = G * (m1 + m2)
	w = 1/x0 - v0^2/2μ
	p = cbrt(9μ * t^2 / 2)

	return p - w*p^2 / 5 - 3w^2 * p^3 / 175 - 23w^3 * p^4/7875 - 1894w^4 * p^5 / 3931875 - 3293w^5 * p^6 / 21896875 - 2418092w^6 * p^7/62077640625
end

function radial_orbit_displacement(m1::Real, m2::Real, x0::Real, v0::Real, t::Real)
	μ = ustrip(G) * (m1 + m2)
	w = 1/x0 - v0^2/2μ
	p = cbrt(9μ * t^2 / 2)

	return p - w*p^2 / 5 - 3w^2 * p^3 / 175 - 23w^3 * p^4/7875 - 1894w^4 * p^5 / 3931875 - 3293w^5 * p^6 / 21896875 - 2418092w^6 * p^7/62077640625
end

end


# ## Data

n = 9
k = 6;

# ## Model

using Turing

@model function globe_toss(n, k)
    θ ~ Beta(1, 1)
    k ~ Binomial(n, θ)
    return k, θ
end;

# ## Output

using Random

Random.seed!(1)
chains = sample(globe_toss(n, k), NUTS(0.65), 1000)

# \defaultoutput{}


using StatsBase
using Base.Test

n = 5000

# 1D integer counts

x = rand(1:5, n)
w = weights(rand(n))

c = counts(x, 5)
@test size(c) == (5,)
c0 = Int[countnz(x .== i) for i in 1 : 5]
@test c == c0
@test counts(x .+ 1, 2:6) == c0
@test_approx_eq proportions(x, 1:5) (c0 ./ n)

c = counts(x)
@test size(c) == (5,)
c0 = Int[countnz(x .== i) for i in 1 : 5]
@test c == c0
@test counts(x .+ 1, 2:6) == c0
@test_approx_eq proportions(x) (c0 ./ n)

c = counts(x, 5, w)
@test size(c) == (5,)
c0 = Float64[sum(w.values[x .== i]) for i in 1 : 5]
@test_approx_eq c c0
@test_approx_eq counts(x .+ 1, 2:6, w) c0
@test_approx_eq proportions(x, 1:5, w) (c0 ./ sum(w))

c = counts(x, w)
@test size(c) == (5,)
c0 = Float64[sum(w.values[x .== i]) for i in 1 : 5]
@test_approx_eq c c0
@test_approx_eq counts(x .+ 1, 2:6, w) c0
@test_approx_eq proportions(x, w) (c0 ./ sum(w))

# 2D integer counts

x = rand(1:4, n)
y = rand(1:5, n)
w = weights(rand(n))

c = counts(x, y, (4, 5))
@test size(c) == (4, 5)
c0 = Int[countnz((x .== i) & (y .== j)) for i in 1 : 4, j in 1 : 5]
@test c == c0
@test counts(x .+ 2, y .+ 3, (3:6, 4:8)) == c0
@test_approx_eq proportions(x, y, (1:4, 1:5)) (c0 ./ n)

c = counts(x, y)
@test size(c) == (4, 5)
c0 = Int[countnz((x .== i) & (y .== j)) for i in 1 : 4, j in 1 : 5]
@test c == c0
@test counts(x .+ 2, y .+ 3, (3:6, 4:8)) == c0
@test_approx_eq proportions(x, y,) (c0 ./ n)

c = counts(x, y, (4, 5), w)
@test size(c) == (4, 5)
c0 = Float64[sum(w.values[(x .== i) & (y .== j)]) for i in 1 : 4, j in 1 : 5]
@test_approx_eq c c0
@test_approx_eq counts(x .+ 2, y .+ 3, (3:6, 4:8), w) c0
@test_approx_eq proportions(x, y, (1:4, 1:5), w) (c0 ./ sum(w))

c = counts(x, y, w)
@test size(c) == (4, 5)
c0 = Float64[sum(w.values[(x .== i) & (y .== j)]) for i in 1 : 4, j in 1 : 5]
@test_approx_eq c c0
@test_approx_eq counts(x .+ 2, y .+ 3, (3:6, 4:8), w) c0
@test_approx_eq proportions(x, y, w) (c0 ./ sum(w))

# count map

x = ["a", "b", "a", "a", "b", "c"]
w = [1.0, 1.5, 2.0, 2.5, 3.0, 3.5]

cm = countmap(x)
@test cm["a"] == 3
@test cm["b"] == 2
@test cm["c"] == 1
pm = proportionmap(x)
@test_approx_eq pm["a"] (1/2)
@test_approx_eq pm["b"] (1/3)
@test_approx_eq pm["c"] (1/6)

cm = countmap(x, weights(w))
@test cm["a"] == 5.5
@test cm["b"] == 4.5
@test cm["c"] == 3.5
pm = proportionmap(x, weights(w))
@test_approx_eq pm["a"] (5.5 / 13.5)
@test_approx_eq pm["b"] (4.5 / 13.5)
@test_approx_eq pm["c"] (3.5 / 13.5)


using LinearAlgebra
include("findlocalmaxima.jl")

function MUSIC(Y,K,m::Int=0,f_steps=1000)
    # Estimation of signal parameters via rotational invariance techniques
    # (ESPRIT) algorithm for line spectral estimation from measurements.
    # Can handle multiple snapshots by constructing a low rank Hankel matrix
    # from the data.
    #
    # Inputs:
    # Y: Data matrix
    # K: number of sinusoids
    # m: Hankel parameter of the Hankel matrix of the data
    # f_steps: number of data points of the MUSIC pseudo spectrum
    #
    # Parameters:
    # N = size(Y,1): number of measurements per snapshot
    # L = size(Y,2): number of snapshots
    #
    # Output:
    # f_hat: estimate of the frequencies of the K sinusoids in [rad]

(N,L) = size(Y)
f_range = range(0, 1, length=f_steps)

if m == 0
m = ceil(Int,N/2)
end

C = zeros(m,m)
for j = 1:L, i = m:N
    C = C + Y[i-m+1:i,j]*Y[i-m+1:i,j]'/(L*N)
end

    evdC = eigen(C)
    p = sortperm(real(evdC.values))[1:m-K]
    Un = evdC.vectors[:,p]

    a(f) = exp.(2*pi*1im*f*(0:m-1))
    spec(f) = 1/norm(Un'*a(f))^2
    Mspec = spec.(f_range)/maximum(spec.(f_range))
    localmaxima = findlocalmaxima(Mspec)
    q = localmaxima[sortperm(Mspec[localmaxima],rev=true)[1:min(K,length(localmaxima))]]
    f_hat = f_range[q]
    return f_hat #, Mspec

end


using Entropia
using Test

tol = 1e-3

@testset "bandt and pompe"  begin
    x = [4, 7, 9, 10, 6, 11, 3]

    @testset "test 00" begin
        e = bandt_and_pompe(x, 2)
        @test abs(e - 0.918) <= tol
    end

    @testset "test 01" begin
        e = bandt_and_pompe(x, 3)
        @test abs(e - 1.522) <= tol
    end
end

@testset "bandt and pompe normal" begin
    n = 2048
    t = [i/n for i in 0:n]

    @testset "test 00" begin
        x = sin.(2π * 15 * t)
        e = bandt_and_pompe_normal(x, 6)
        @test abs(e - 0.1579) <= tol
    end

    @testset "test 01" begin
        x = (0.5*sin.(2π*5*t).+1) .* (sin.(2π*50*t.^2+2π*10*t))
        e = bandt_and_pompe_normal(x, 6)
        @test abs(e - 0.2625) <= tol
    end
end

@testset "weight entropy" begin
    @testset "test 00" begin
        x = [4, 7, 9, 10, 6, 11, 3]
        e = weight_entropy(x)
        @test abs(e - 0.9801) <= tol
    end
end

@testset "complexity_entropy" begin
    @testset "test 00" begin
        x = [4, 7, 9, 10, 6, 11, 3]
        e, jsd, cjs = complexity_entropy(x)
        @test abs(e - 0.5888) <= tol
        @test abs(jsd - 0.2235) <= tol
        @test abs(cjs - 0.2900) <= tol
    end
end


using DiffEqFlux, OrdinaryDiffEq, Flux, Optim, Plots

# Condiciones iniciales del problema
u0 = Float32[2.0; 0.0]
datasize = 30
tspan = (0.0f0, 1.5f0)
tsteps = range(tspan[1], tspan[2], length = datasize)

# Se define y resuelve el problema con parámetros reales
function trueODEfunc(du, u, p, t)
    true_A = [-0.1 2.0; -2.0 -0.1]
    du .= ((u.^3)'true_A)'
end

prob_trueode = ODEProblem(trueODEfunc, u0, tspan)
ode_data = Array(solve(prob_trueode, Tsit5(), saveat = tsteps))

# Se define una red neuronal de con dos capas densas/lineales.
# a ésta se le agrega la asunción de que el sistema transforma
# x hacia x^3. Esto como conocimiento previo que mejore el tiempo.
dudt2 = FastChain((x, p) -> x.^3,
                  FastDense(2, 50, tanh),
                  FastDense(50, 2))

# Se define el problema junto con su método de resolución y tiempo al cual parar.
prob_neuralode = NeuralODE(dudt2, tspan, Tsit5(), saveat = tsteps)

# Funciones que guardarán la predicción de la red neuronal y su valor de error.
function predict_neuralode(p)
  Array(prob_neuralode(u0, p))
end

function loss_neuralode(p)
    pred = predict_neuralode(p)
    loss = sum(abs2, ode_data .- pred)
    return loss, pred
end

# Utilizado para graficar a tiempo real las predicciones realizadas.
callback = function (p, l, pred; doplot = true)
  display(l)
  # plot current prediction against data
  plt = scatter(tsteps, ode_data[1,:], label = "data")
  scatter!(plt, tsteps, pred[1,:], label = "prediction")
  if doplot
    display(plot(plt))
  end
  return false
end

# Se entrena la red neuronal utilizando la data de la resolución con parámetros reales.
# Ésto se hace en dos pasos, primero con el optimizador ADAM para acercarnos rápidamente
# al mínimo, y una vez cerca utilizamos Limited-memory BFGS para un ajuste más eficiente.
result_neuralode = DiffEqFlux.sciml_train(loss_neuralode, prob_neuralode.p,
                                          ADAM(0.05), cb = callback,
                                          maxiters = 300)

result_neuralode2 = DiffEqFlux.sciml_train(loss_neuralode,
                                           result_neuralode.minimizer,
                                           LBFGS(),
                                           cb = callback,
                                           allow_f_increases = false)


using DelimitedFiles

function ReadData(fname::AbstractString)
    return vec(readdlm(fname, ',', Int))
end

function Median(data::Array)
    if isodd(length(data))
        x0 = sort(data)[div(length(data)-1,2)]
        x1 = sort(data)[div(length(data)+1,2)]

        return x0, x1
    else
        x0 = sort(data)[div(length(data),2)]
        return x0
    end
end

# the optimal position will be the median
function Part1(data::Array)
    if isodd(length(data))
        x0, x1 = Median(data)

        c0 = sum(abs(x0-x) for x in data)
        c1 = sum(abs(x1-x) for x in data)

        return min(c0, c1)
    else
        x0 = Median(data)
        return sum(abs(x0-x) for x in data)
    end
end

# the optimal position will be somewhere between the average and the median
function Part2(data::Array)
    xAvg = round(Int, sum(data)/length(data))
    xMed = isodd(length(data)) ? min(Median(data)) : Median(data)

    # search between xAvg and xMed for the minimum
    x0, x1 = min(xAvg, xMed), max(xAvg, xMed)
    cMin = sum(data)*(sum(data)+1)
    cost = x -> div(sum(abs(y-x)*(1+abs(y-x)) for y in data),2)
    for x in x0:x1
        c = cost(x)
        cMin = c < cMin ? c : cMin
    end

    return cMin
end

#data = [16,1,2,0,4,2,7,1,2,14]
data = ReadData("../data/day7.txt")

println(Part1(data))
println(Part2(data))


#=
# The BUGS Book
# Example 2.1.2, page 17:
# For
#   Y ~ Binomial(0.5, 8)
# we want to know Pr(Y <= 2)
#

  See ~/jags/bugs_book_2_1_2.jags
       Mean     SD Naive SE Time-series SE
  P2 0.1442 0.3513 0.001434       0.001434
  Y  4.0025 1.4177 0.005788       0.005839

  ~/webppl/bugs_book_2_1_2.wppl

=#

using Turing, StatsPlots, DataFrames
include("jl_utils.jl")

@model function bugs_book_2_1_2()

    y ~ Binomial(8,0.5)
    p2 ~ y <= 2 ? flip(1.0) : flip(0.0);

end

model = bugs_book_2_1_2()

num_chns = 4
# chns = sample(model, Prior(), MCMCThreads(), 10_000, num_chns)

# chns = sample(model, MH(), MCMCThreads(), 40_000, num_chns)

# chns = sample(model, PG(15), MCMCThreads(), 10_000, num_chns)

# chns = sample(model, SMC(1000), MCMCThreads(), 10_000, num_chns)

chns = sample(model, IS(), MCMCThreads(), 10_000, num_chns)

display(chns)
# display(plot(chns))

show_var_dist_pct(chns,:y)
show_var_dist_pct(chns,:p2)


#    Problem : GROUP A
#    *********
#    A simple quartic function.
#
#    Origonal SIF Source: problem 157 (p. 87) in
#    A.R. Buckley,
#   "Test functions for unconstrained minimization",
#    TR 1989CS-3, Mathematics, statistics and computing centre,
#    Dalhousie University, Halifax (CDN), 1989.
#
#    QUARTC.SIF classification OUR2-AN-V-0
#
#    Number of variables is variable
#
# Daniel Henderson, 08/2021

f = x -> begin
    return sum((x[i] - i)^4 for i in 1:lastindex(x))
end

g! = (g, x) -> begin
	for i in 1:lastindex(x)
		g[i] = 4(x[i] - i)^3
	end
    return g
end

fg! = (g, x) -> begin
	fx = 0.0
	for i in 1:lastindex(x)
		fx  += (x[i] - i)^4
		g[i] = 4(x[i] - i)^3
	end
    return fx, g
end

init = (n::Int=5000) -> begin
    return n, 2.0ones(n)
end

TestSet["QUARTC"] = UncProgram("QUARTC",  f, g!, fg!, init)


using JuMP
using GLPKMathProgInterface

model = Model(solver=GLPKSolverMIP())

@variable(model, vials_of_panacea >= 0, Int)
@variable(model, ampules_of_ichor >= 0, Int)
@variable(model, bars_of_gold >= 0, Int)

@objective(model, Max, 3000*vials_of_panacea + 1800*ampules_of_ichor + 2500*bars_of_gold)

@constraint(model, 0.3*vials_of_panacea + 0.2*ampules_of_ichor + 2.0*bars_of_gold <= 25.0)
@constraint(model, 0.025*vials_of_panacea + 0.015*ampules_of_ichor + 0.002*bars_of_gold <= 0.25)

println("The optimization problem to be solved is:")
println(model)

status = solve(model)

println("Objective value: ", getobjectivevalue(model))
println("vials of panacea = ", getvalue(vials_of_panacea))
println("ampules of ichor = ", getvalue(ampules_of_ichor))
println("bars of gold = ", getvalue(bars_of_gold))


function dlyap(A,Q)

	# Solving Discrete LYAPunov equation using Silvester equation
	#
	# Sylvester equation A*X + X*B + C = 0
	# if B = -(A')^(-1), C = Q*(A')^(-1)
	# => A*X - X*(A')^(-1) + Q*(A')^(-1) = 0
	# => A*X*A' - X + Q = 0  -> Discrete Lyapunov equation

	Atinv = Array{Float64,2}(inv(A'))
	B = -Atinv
	C = Q*Atinv
	X = sylvester(A, B, C)

	return X

end


#=
  Fixed Point Convergence
  Fixed Point Existance
  Fixed Point Uniqueness
  Newtons Method
  Bisection Method
=#

function testConvergence(f,p0,TOL,N0)
  # takes starting point, tolerance, maximum iterations
  i = 1
  while i <= N0
    p = g(p0)
    if abs(p-p0) < TOL
      return true
    else
      i += 1
      p0 = p
    end
  end
  return false
  println("Failure: N0 tries")

function testExistance(f,a,b)
  # interval [a,b]
  if f(a) * f(b) <0
    return true
    #does this make asusmptions
  else
    return false
  end

function testUniqueness(f,a,b)
  # interval [a,b]
  if derivative(f,a) * derivative(f,b) < 0
    return true
    #does this make assumptions
  else
    return false
  end

function newtonsMethod(f,p0,TOL,N0)
  if testConvergence(f) == true
    i = 1
    while i <= N0
        p = p0 - (derivative(f,p0) / f(p0)
        if abs(p-p0) < TOL
          return p
        else
          i += 1
          p0 = p
        end
    end
  else
    #return eror e
  end

function secantMethod()

function bisectionMethod(f,a,b,DOA,NO)
  # interval [a,b], DOA degree of accuracy
  if testConvergence(f) == true
    i = 1
    while i <= N0
      m = (1/2)(a + b)
      if f(m) = 0 | abs(f(m)) < DOA
        return m
      elseif f(m) * f(a) < 0
        a = m
      elseif f(m) * f(b) < 0
        b = m
      else
        #return eror e
      end
    end
    return m
  else
    #return error e
  end


using TailRiskScenGen
using Cones
using LinearAlgebra: I
using Distributions

d = 30
β = 0.95
num_scen = 5000
A = rand(d,d)/sqrt(d)
dist = MvNormal(0.1*rand(d)-0.05, A'A)

K = PolyhedralCone(Array{Float64}(I, d, d))
Ω_gen = EllipticalRiskRegion(dist, K, β)
Ω_mono = MonotonicEllipticalRiskRegion(dist, K, β)
Ω_mono.max_frontier_points = d*5

rights = 0
wrongs = 0
for i in 1:200
    x = rand(dist)
    mon = x ∈ Ω_mono
    gen = x ∈ Ω_gen
    if mon == gen
        rights+=1
    else
        wrongs+=1
    end

    if i % 10 == 1
        println("i = ", i, ", num risk frontier points: ", length(Ω_mono.risk_frontier),
              ", num nonrisk frontier points: ", length(Ω_mono.nonrisk_frontier))
    end
end

println("Correct decisions: ", rights)
println("Wrong decisions: ", wrongs)

x = rand(dist)

x ∈ Ω_gen
x ∈ Ω_mono
y = rand(dist)
y ∈ Ω_gen
y ∈ Ω_mono

Ω_mono.risk_frontier
Ω_mono.nonrisk_frontier
Ω=Ω_mono

print("EllipticalRiskRegion...")
tic()
TailRiskScenGen._aggregation_sampling(dist, Ω_gen, num_scen)
toc()

print("MonotonicEllipticalRiskRegion...")
tic()
TailRiskScenGen._aggregation_sampling(dist, Ω_mono, num_scen)
toc()

println("Num non-risk frontier points: ", length(Ω_mono.risk_frontier))
println("Num risk frontier points: ", length(Ω_mono.nonrisk_frontier))


using DiscriminantAnalysis, Gadfly, Colors

### Helper functions ###

function rotationmatrix2D{T<:AbstractFloat}(θ::T)
    T[cos(θ) -sin(θ);
      sin(θ)  cos(θ)]
end

# Generates two normally distributed variables
function boxmuller(n::Integer)
    u1 = rand(n)
    u2 = rand(n)
    Z = Float64[(√(-2log(u1)) .* cos(2π*u2)) (√(-2log(u1)) .* sin(2π*u2))]
end

# Create the decision boundary using Contour.jl
function boundary(model, xrange, yrange, is_quad::Bool = false)
    Z = hcat(vec(Float64[x for x in xrange, y in yrange]),
             vec(Float64[y for x in xrange, y in yrange]))
    δ = DiscriminantAnalysis.discriminants(model, is_quad ? hcat(Z, Z.^2, Z[:,1] .* Z[:,2]) : Z)
    Z = reshape(δ[:,1] - δ[:,2], length(xrange), length(yrange))
    Contour.coordinates(Contour.lines(Contour.contour(xrange,yrange,Z,0.0))[1])
end

limits(X::Matrix) = (minimum(X[:,1]), maximum(X[:,1]), minimum(X[:,2]), maximum(X[:,2]))

### Sample Data ###

n = 250

Z1 = boxmuller(n)
σ1 = [0.5 2.0]
X1 = ((Z1 .* σ1) .- [0.0 4.25]) * rotationmatrix2D(π/4)

Z2 = boxmuller(n)
σ2 = [3.0 1.5]
X2 = ((Z2 .* σ2) .+ [0.0 2.25]) * rotationmatrix2D(π/4)

X = vcat(X1,X2)
y = repeat([1,2], inner=[n])

xmin, xmax, ymin, ymax = limits(X)
aspect = (ymax-ymin)/(xmax-xmin)

m = 250  # Used for interpolating the decision boundary
xrange = linspace(xmin,xmax,m)
yrange = linspace(ymin,ymax,m)

### LDA & QDA Plots ###
for (obj, desc, is_quad) in ((:lda, "Linear Discriminant Analysis", false),
                             (:qda, "Quadratic Discriminant Analyisis", false),
                             (:lda, "Quadratic Linear Discriminant Analysis", true))
    @eval begin
        model = ($obj)($is_quad ? hcat(X, X.^2, X[:,1] .* X[:,2]) : X, y)
        cx, cy = boundary(model, xrange, yrange, $is_quad)

        P = plot(
                x = vec(X[:,1]),
                y = vec(X[:,2]),
                color = map(class -> "Class $class", y),
                Geom.point,
                Scale.color_discrete_manual(colorant"red",colorant"blue"),
                Guide.XLabel("X Variable"),
                Guide.YLabel("Y Variable"),
                Guide.title($desc),
                Guide.colorkey(""),
                Coord.Cartesian(xmin=xmin, ymin=ymin, xmax=xmax, ymax=ymax)
            )
        L = layer(x=cx, y=cy, Geom.line(preserve_order=true),
                  Theme(default_color=colorant"black", line_width=.4mm))
        unshift!(P.layers,L[1])

        draw(PNG(($is_quad ? "q" : "") * $(string(obj)) * ".png", 6inch, (6*aspect)inch), P)
    end
end

### CDA Plot ###

model = cda(X, y)
C = vec(X * model.W)

P = plot(
    x = C,
    color = map(class -> "Class $class", y),
    Geom.histogram(bincount=100),
    Scale.color_discrete_manual(colorant"red",colorant"blue"),
    Guide.XLabel("Canonical Coordinate"),
    Guide.YLabel("Count"),
    Guide.title("Canonical Discriminant Analysis"),
    Guide.colorkey(""),
    Coord.Cartesian(xmin=minimum(C), xmax=maximum(C))
)

draw(PNG("cda.png", 6inch, 4inch), P)


using SpecialFunctions, Nemo, StatsFuns, Memoization
import Nemo.binom, Nemo.gamma

"Session-wide precision"
const prec = 5000 ## Increase for better precision

"Converter from real to arbitrary precision real (Arb)"
const RR = RealField(prec)

"Converter from real to arbitrary precision complex (Acb)"
const CC = ComplexField(prec)

binom(n::Int64, k::Int64, r::Nemo.ArbField) = binom(convert(UInt64, n),convert(UInt64, k), r)

# Making sure the factorial function uses the Nemo version of the gamma and returns an Arb
gamma(x::Int64) = Nemo.gamma(RR(x))

# Useful typed constants
"1 with required arbitrary precision"
const arb_1 = RR(1)
"0 with required arbitrary precision"
const arb_0 = RR(0)

"""
  unsigned_Stirling1(n::Integer, k::Integer)

Computation of unsigned Stirling numbers of the first kind, using Memoization and a recursive formula.

# Examples
```julia-repl
julia> GibbsTypePriors.unsigned_Stirling1(10, 5)
269325
```
"""
@memoize function unsigned_Stirling1(n::Integer, k::Integer)
  # special cases
  if k<0 || n<0
    throw(DomainError())
  end
  if k>n
    return big(0)
  end
  if n==0  # and, by logic, k==0
    return big(1)
  end
  if k==0  # and, by logic, n>0
    return big(0)
  end
  # end of special cases, invoke recursion
  return unsigned_Stirling1(n-1,k-1) + (n-1)*unsigned_Stirling1(n-1,k)
end

@memoize function unsigned_Stirling2(n::Integer,k::Integer)
  # special cases
  if k<0 || n<0
    throw(DomainError())
  end
  if k>n
    return big(0)
  end
  if n==0  # and, by logic, k==0
    return big(1)
  end
  if k==0  # and, by logic, n>0
    return big(0)
  end
  # end of special cases, invoke recursion
  return  (k)*unsigned_Stirling2(n-1,k) + unsigned_Stirling2(n-1,k-1)
end

"""
  has_reasonable_precision(arb_num)

Check whether arb_num has at least roughly the double precision (maybe replace by 64 by 53).

# Examples
```julia-repl
julia> GibbsTypePriors.Cnk_rec(6, 5, 0.5)
0.234375
```
"""
function has_reasonable_precision(arb_num)
    return accuracy_bits(arb_num) ≥ 53
end

import Nemo.risingfac
"""
  risingfac(r, n)

Computes the rising factorial.

# Examples
```julia-repl
julia> GibbsTypePriors.Cnk_rec(6, 5, 0.5)
0.234375
```
"""
function risingfac(r, n)
  if r == 0
    return 0
  else
    return prod(r + i for i in 0:(n-1))
  end
end
function risingfac(r::arb, n)
  if r == 0
    return arb_0
  else
    return prod(r + i for i in 0:(n-1))
  end
end


import RowEchelon

#=
Homework 5
Section 2.1
Problem 1
=#

# Initialize all Matrices
A = [2 0 -1 ; 4 -5 2]
B = [7 -5 1 ; 1 -4 -3]
C = [1 2 ;-2 1]
D = [3 5 ; -1 4]
E = [-5 ; 3]

println("-2A:")
println(-2*A)

println("B-2A:")
println(B-(2*A))

# After this point, products may not exist
# Use try/catch to find nonexistent products.
println("AC:")
try
	println(A*C)
catch
	println("AC cannot be computed.")
end

println("CD:")
try
	println(C*D)
catch
	println("CD cannot be computed.")
end


#-------------------------------------------------------------------------------
#conversions between spectral units

export ν2f, f2ν, ν2λ, λ2ν, λ2f, f2λ

"""
Convert wavenumber [cm``^{-1}``] to frequency [1/s]
"""
ν2f(ν) = 100.0*𝐜*ν

"""
Convert frequency [1/s] to wavenumber [cm``^{-1}``]
"""
f2ν(f) = f/(100.0*𝐜)

"""
Convert wavenumber [cm``^{-1}``] to wavelength [m]
"""
ν2λ(ν) = 0.01/ν

"""
Convert wavelength [m] to wavenumber [cm``^{-1}``]
"""
λ2ν(λ) = 0.01/λ

"""
Convert wavelength [m] to frequency [1/s]
"""
λ2f(λ) = 𝐜/λ

"""
Convert frequency [1/s] to wavelength [m]
"""
f2λ(f) = f/𝐜

#-------------------------------------------------------------------------------
export planck, normplanck, dplanck, stefanboltzmann, equilibriumtemperature

"""
    planck(ν, T)

Compute black body intensity [W/m``^2``/cm``^{-1}``/sr] using [Planck's law](https://en.wikipedia.org/wiki/Planck%27s_law)

# Arguments
* `ν`: wavenumger [cm``^{-1}``]
* `T`: temperature [Kelvin]
"""
function planck(ν, T)
    νₘ = 100.0*ν #convert from cm^-1 to m^-1
    x = 𝐡*𝐜*νₘ/(𝐤*T) #exponent
    p = 2*𝐡*𝐜^2*νₘ^3 #prefactor
    #result, converting back to cm^-1 with the factor of 100
    100.0*p/(exp(x) - 1.0)
end

"""
    normplanck(ν, T)

Compute black body intensity [W/m``^2``/cm``^{-1}``/sr] using [Planck's law](https://en.wikipedia.org/wiki/Planck%27s_law), normalized by the power emitted per unit area at the given temperature ([`stefanboltzmann`](@ref)),

```
B(ν,T)/σT^4
```

yielding units of 1/cm``^{-1}``/sr.

# Arguments
* `ν`: wavenumger [cm``^{-1}``]
* `T`: temperature [Kelvin]
"""
normplanck(ν, T) = planck(ν, T)/stefanboltzmann(T)

"""
    dplanck(ν, T)

Evaluates ``\\frac{\\partial B}{\\partial T}``
"""
function dplanck(ν, T)
    νₘ = 100.0*ν #convert from cm^-1 to m^-1
    x = 𝐡*𝐜*νₘ/(𝐤*T) #exponent
    y = exp(x)
    if isinf(y)
        return zero(y) #fail accurately, Inf/(Inf - 1)^2 → 0
    end
    p = 2*𝐡^2*𝐜^3*νₘ^4/(𝐤*T^2) #prefactor
    #result, converting back to cm^-1 with the factor of 100
    100.0*p*y/(y - 1)^2
end

"""
    stefanboltzmann(T)

Compute black body radiation power using the [Stefan-Boltzmann](https://en.wikipedia.org/wiki/Stefan%E2%80%93Boltzmann_law) law, ``σT^4`` [W/m``^2``].
"""
stefanboltzmann(T) = 𝛔*(T^4)

"""
    equilibriumtemperature(F, A)

Compute the [planetary equilibrium temperature](https://en.wikipedia.org/wiki/Planetary_equilibrium_temperature), or equivalent blackbody temperature of a planet.

``(\\frac{(1 - A)F}{4\\sigma})^{1/4}``

# Arguments
* `F`: stellar flux [W/m``^2``]
* `A`: albedo
"""
equilibriumtemperature(F, A) = ((1 - A)*F/(4*𝛔))^(1/4)

"""
    equilibriumtemperature(L, A, R)

Compute the [planetary equilibrium temperature](https://en.wikipedia.org/wiki/Planetary_equilibrium_temperature), or equivalent blackbody temperature of a planet.

``(\\frac{(1 - A)L}{16 \\sigma \\pi R^2})^{1/4}``

# Arguments
* `L`: stellar luminosity [W]
* `A`: albedo
* `R`: orbital distance [m]
"""
equilibriumtemperature(L, A, R) = (L*(1 - A)/(16*𝛔*π*R^2))^(1/4)

#-------------------------------------------------------------------------------
export dτdP, transmittance, schwarzschild, absorption, emission

"""
    dτdP(σ, g, μ)

Evaluate the differential increase in optical depth in pressure coordinates, equivalent to the [`schwarzschild`](@ref) equation without Planck emission.

``\\frac{dτ}{dP} = σ\\frac{\\textrm{N}_A}{g μ}``

where ``N_A`` is Avogadro's number.

# Arguments
* `σ`: absorption cross-section [cm``^2``/molecule]
* `g`: gravitational acceleration [m/s``^2``]
* `μ`: mean molar mass [kg/mole]
"""
dτdP(σ, g, μ) = 1e-4*σ*𝐍𝐚/(μ*g)

"""
    transmittance(τ)

Evaluate transmittance from optical depth, ``t = e^{-τ}``
"""
transmittance(τ) = exp(-τ)

"""
    schwarzschild(I, ν, σ, T, P)

Evaluate the [Schwarzschild differential equation](https://en.wikipedia.org/wiki/Schwarzschild%27s_equation_for_radiative_transfer) for radiative transfer with units of length/height [m] and assuming the ideal gas law.

``\\frac{dI}{dz} = σ\\frac{P}{k_B T}[B_ν(T) - I]``

where ``B_ν`` is [`planck`](@ref)'s law.

# Arguments
* `I`: radiative intensity [W/m``^2``/cm``^{-1}``/sr]
* `ν`: radiation wavenumber [cm``^{-1}``]
* `σ`: absorption cross-section [cm``^2``/molecule]
* `T`: temperature [K]
* `P`: pressure [Pa]
"""
schwarzschild(I, ν, σ, T, P) = 1e-4*σ*(P/(𝐤*T))*(planck(ν,T) - I)

"""
    schwarzschild(I, ν, σ, g, μ, T)

Evaluate the [Schwarzschild differential equation](https://en.wikipedia.org/wiki/Schwarzschild%27s_equation_for_radiative_transfer) for radiative transfer with pressure units [Pa] and assuming the ideal gas law.

``\\frac{dI}{dP} = σ\\frac{\\textrm{N}_A}{g μ}[B_ν(T) - I]``

where ``B_ν`` is [`planck`](@ref)'s law and ``N_A`` is Avogadro's number.

# Arguments
* `I`: radiative intensity [W/m``^2``/cm``^{-1}``/sr]
* `ν`: radiation wavenumber [cm``^{-1}``]
* `σ`: absorption cross-section [cm``^2``/molecule]
* `g`: gravitational acceleration [m/s``^2``]
* `μ`: mean molar mass [kg/mole]
* `T`: temperature [K]
"""
schwarzschild(I, ν, σ, g, μ, T) = 1e-4*σ*(𝐍𝐚/(μ*g))*(planck(ν,T) - I)

"""
    absorption(I, σ, g, μ)

Evaluate the [Schwarzschild differential equation](https://en.wikipedia.org/wiki/Schwarzschild%27s_equation_for_radiative_transfer) for radiative transfer with pressure units [Pa] and assuming the ideal gas law, but **without Planck emission**. This can be useful if the wavenumber is far away from the emission region of the temperature and the Planck emission is known to be negligible.

``\\frac{dI}{dP} = -σ\\frac{\\textrm{N}_A}{g μ}I``

where ``N_A`` is Avogadro's number.

# Arguments
* `I`: radiative intensity [W/m``^2``/cm``^{-1}``/sr]
* `σ`: absorption cross-section [cm``^2``/molecule]
* `g`: gravitational acceleration [m/s``^2``]
* `μ`: mean molar mass [kg/mole]
"""
absorption(I, σ, g, μ) = -1e-4*σ*(𝐍𝐚/(μ*g))*I

"""
    emission(I, ν, σ, g, μ, T)

Evaluate the [Schwarzschild differential equation](https://en.wikipedia.org/wiki/Schwarzschild%27s_equation_for_radiative_transfer) for radiative transfer with pressure units [Pa] and assuming the ideal gas law, but **without Planck emission**. This can be useful if the wavenumber is far away from the emission region of the temperature and the Planck emission is known to be negligible.

``\\frac{dI}{dP} = -σ\\frac{\\textrm{N}_A}{g μ}B_ν(T)``

where ``B_ν`` is [`planck`](@ref)'s law

# Arguments
* `I`: radiative intensity [W/m``^2``/cm``^{-1}``/sr]
* `σ`: absorption cross-section [cm``^2``/molecule]
* `g`: gravitational acceleration [m/s``^2``]
* `μ`: mean molar mass [kg/mole]
"""
emission(ν, σ, g, μ, T) = 1e-4*σ*(𝐍𝐚/(μ*g))*planck(ν,T)


@testset "matern" begin
    rng = MersenneTwister(123456)
    x = rand(rng) * 2
    v1 = rand(rng, 3)
    v2 = rand(rng, 3)
    @testset "MaternKernel" begin
        ν = 2.1
        k = MaternKernel(; ν=ν)
        matern(x, ν) = 2^(1 - ν) / gamma(ν) * (sqrt(2ν) * x)^ν * besselk(ν, sqrt(2ν) * x)
        @test MaternKernel(; nu=ν).ν == [ν]
        @test kappa(k, x) ≈ matern(x, ν)
        @test kappa(k, 0.0) == 1.0
        @test metric(MaternKernel()) == Euclidean()
        @test metric(MaternKernel(; ν=2.0)) == Euclidean()
        @test repr(k) == "Matern Kernel (ν = $(ν), metric = Euclidean(0.0))"

        k2 = MaternKernel(; ν=ν, metric=WeightedEuclidean(ones(3)))
        @test metric(k2) isa WeightedEuclidean
        @test k2(v1, v2) ≈ k(v1, v2)

        # Standardised tests.
        TestUtils.test_interface(k, Float64)
        test_ADs(() -> MaternKernel(; nu=ν))

        test_params(k, ([ν],))
    end
    @testset "Matern32Kernel" begin
        k = Matern32Kernel()
        @test kappa(k, x) ≈ (1 + sqrt(3) * x)exp(-sqrt(3) * x)
        @test k(v1, v2) ≈ (1 + sqrt(3) * norm(v1 - v2))exp(-sqrt(3) * norm(v1 - v2))
        @test kappa(Matern32Kernel(), x) == kappa(k, x)
        @test metric(Matern32Kernel()) == Euclidean()
        @test repr(k) == "Matern 3/2 Kernel (metric = Euclidean(0.0))"

        k2 = Matern32Kernel(; metric=WeightedEuclidean(ones(3)))
        @test metric(k2) isa WeightedEuclidean
        @test k2(v1, v2) ≈ k(v1, v2)

        # Standardised tests.
        TestUtils.test_interface(k, Float64)
        test_ADs(Matern32Kernel)
    end
    @testset "Matern52Kernel" begin
        k = Matern52Kernel()
        @test kappa(k, x) ≈ (1 + sqrt(5) * x + 5 / 3 * x^2)exp(-sqrt(5) * x)
        @test k(v1, v2) ≈
            (
            1 + sqrt(5) * norm(v1 - v2) + 5 / 3 * norm(v1 - v2)^2
        )exp(-sqrt(5) * norm(v1 - v2))
        @test kappa(Matern52Kernel(), x) == kappa(k, x)
        @test metric(Matern52Kernel()) == Euclidean()
        @test repr(k) == "Matern 5/2 Kernel (metric = Euclidean(0.0))"

        k2 = Matern52Kernel(; metric=WeightedEuclidean(ones(3)))
        @test metric(k2) isa WeightedEuclidean
        @test k2(v1, v2) ≈ k(v1, v2)

        # Standardised tests.
        TestUtils.test_interface(k, Float64)
        test_ADs(Matern52Kernel)
    end
    @testset "Coherence Materns" begin
        @test kappa(MaternKernel(; ν=0.5), x) ≈ kappa(ExponentialKernel(), x)
        @test kappa(MaternKernel(; ν=1.5), x) ≈ kappa(Matern32Kernel(), x)
        @test kappa(MaternKernel(; ν=2.5), x) ≈ kappa(Matern52Kernel(), x)
    end
end


using DataFrames
using Distributions
using StatsPlots

function sir(beta,gamma,N,S0,I0,R0,tf)
    t = 0
    S = S0
    I = I0
    R = R0
    ta=DataArray(Float64,0)
    Sa=DataArray(Float64,0)
    Ia=DataArray(Float64,0)
    Ra=DataArray(Float64,0)
    while t < tf
        push!(ta,t)
        push!(Sa,S)
        push!(Ia,I)
        push!(Ra,R)
        pf1 = beta*S*I
        pf2 = gamma*I
        pf = pf1+pf2
        dt = rand(Exponential(1/pf))
        t = t+dt
        if t>tf
            break
        end
        ru = rand()
        if ru<(pf1/pf)
            S=S-1
            I=I+1
        else
            I=I-1
            R=R+1
        end
    end
    results = DataFrame()
    results[:time] = ta
    results[:S] = Sa
    results[:I] = Ia
    results[:R] = Ra
    return(results)
end

srand(42)

sir_out = sir(0.1/1000,0.05,1000,999,1,0,200);

head(sir_out)

# Plot
@df sir_out plot(:time, [:S :I :R], xlabel="Time",ylabel="Number")


"""
    LogTransform <: Transform

Logarithmically transform the data through: sign(x) * log(|x| + 1).

This allows transformations of all real numbers, not just positive ones.
"""
struct LogTransform <: Transform end
cardinality(::LogTransform) = OneToOne()

_logtransform(x) = sign(x) * log(abs(x) + one(x))
_invlogtransform(x) = sign(x) * (exp(sign(x) * x) - one(x))

function _apply(A::AbstractArray, transform::LogTransform; inverse=false, kwargs...)
    inverse && return _invlogtransform.(A)
    return _logtransform.(A)
end

"""
    InverseHyperbolicSine <: Transform

Logarithmically transform the data through: log(x + √(x² + 1)).

This is the inverse hyperbolic sine.
"""
struct InverseHyperbolicSine <: Transform end
cardinality(::InverseHyperbolicSine) = OneToOne()

function _apply(A::AbstractArray, transform::InverseHyperbolicSine; inverse=false, kwargs...)
    inverse && return sinh.(A)
    return asinh.(A)
end


nprocs()==1 && addprocs()

@everywhere logis(x) = 1 ./ (1 .+ exp(-x))
@everywhere dlogis(x) = logis(x) .* (1 .- logis(x))

@everywhere function initParams(x,y,n_hidden,max_iter)
    w1 = randn(n_hidden,size(x,1))
    w2 = randn(n_hidden,n_hidden)
    w3 = randn(size(y,1),n_hidden)
    B1 = rand(n_hidden,1)
    B2 = rand(n_hidden,1)
    errors = zeros(max_iter)
    outputs = zeros(size(y,2),max_iter)
    return (w1,w2,w3,errors,outputs,B1,B2)
end

@everywhere function ffward(x,y,w1,w2,w3)
    a1 = w1 * x
    h1 = logis(a1)
    a2 = w2 * h1
    h2 = logis(a2)
    ay = w3 * h2
    yhat = logis(ay)
    err = yhat - y
    return(a1,h1,a2,h2,ay,yhat,err)
end

@everywhere function updateW!(w1,w2,w3,x,h1,h2,δa1,δa2,err;lr=1)
    w1 .-= δa1 * x' .* lr
    w2 .-= δa2 * h1' .* lr
    w3 .-= err * h2' .* lr
end

@everywhere function bp(x,y,n_hidden,max_iter)
    (w1,w2,w3,errors,outputs,_,_)=initParams(x,y,n_hidden,max_iter)
    for i = 1:max_iter
        (a1,h1,a2,h2,ay,yhat,err) = ffward(x,y,w1,w2,w3)
        δa2 = (w3' * err) .* dlogis(a2)
        δa1 = (w2' * δa2) .* dlogis(a1)
        updateW!(w1,w2,w3,x,h1,h2,δa1,δa2,err)
        errors[i] = sum(abs(err))
        outputs[:,i] = yhat
    end
    return (outputs,errors)
end

@everywhere function fa(x,y,n_hidden,max_iter)
    (w1,w2,w3,errors,outputs,B1,B2)=initParams(x,y,n_hidden,max_iter)
    for i = 1:max_iter
        (a1,h1,a2,h2,ay,yhat,err) = ffward(x,y,w1,w2,w3)
        δa2 = (B2.*err) .* dlogis(a2)
        δa1 = (B1.*δa2) .* dlogis(a1)
        updateW!(w1,w2,w3,x,h1,h2,δa1,δa2,err)
        errors[i] = sum(abs(err))
        outputs[:,i] = yhat
    end
    return (outputs,errors)
end

@everywhere function dfa(x,y,n_hidden,max_iter)
    (w1,w2,w3,errors,outputs,B1,B2)=initParams(x,y,n_hidden,max_iter)
    for i = 1:max_iter
        (a1,h1,a2,h2,ay,yhat,err) = ffward(x,y,w1,w2,w3)
        δa2 = (B2.*err) .* dlogis(a2)
        δa1 = (B1.*err) .* dlogis(a1)
        updateW!(w1,w2,w3,x,h1,h2,δa1,δa2,err)
        errors[i] = sum(abs(err))
        outputs[:,i] = yhat
    end
    return (outputs,errors)
end

@everywhere function ifa(x,y,n_hidden,max_iter)
    (w1,w2,w3,errors,outputs,B1,_)=initParams(x,y,n_hidden,max_iter)
    for i = 1:max_iter
        (a1,h1,a2,h2,ay,yhat,err) = ffward(x,y,w1,w2,w3)
        δa1 = (B1.*err) .* dlogis(a1)
        δa2 = (w2 * δa1) .* dlogis(a2)
        updateW!(w1,w2,w3,x,h1,h2,δa1,δa2,err)
        errors[i] = sum(abs(err))
        outputs[:,i] = yhat
    end
    return (outputs,errors)
end

# Example: XOR problem

x = [1 1 ; 0 1 ; 1 0 ; 0 0]'
y = [0 1 1 0]
n_hidden = 15; # Number of hidden units
max_iter = 500; #Number of learning steps
trials = 30;

dfa_err=@parallel (hcat) for _=1:trials
    (_,dfa_errors) = dfa(x,y,n_hidden,max_iter)
    dfa_errors
end
(dfa_yhat,_) = dfa(x,y,n_hidden,max_iter)

bp_err = @parallel (hcat) for _=1:trials
    (_,bp_errors) = bp(x,y,n_hidden,max_iter)
    bp_errors
end
(bp_yhat,_) = bp(x,y,n_hidden,max_iter)

ifa_err=@parallel (hcat) for _=1:trials
    (_,ifa_errors) = ifa(x,y,n_hidden,max_iter)
    ifa_errors
end
(ifa_yhat,_) = ifa(x,y,n_hidden,max_iter);

fa_err=@parallel (hcat) for _=1:trials
    (_,fa_errors) = fa(x,y,n_hidden,max_iter)
    fa_errors
end
(fa_yhat,_) = fa(x,y,n_hidden,max_iter);

using Plots
l = @layout [
    a b
    c d
    e f
]
gr()
p1=plot(dfa_yhat',label=["1,1" "0,1" "1,0" "0,0"],title="DFA")
p2=plot(ifa_yhat',label=["1,1" "0,1" "1,0" "0,0"],title="IFA")
p3=plot(fa_yhat',label=["1,1" "0,1" "1,0" "0,0"],title="FA")
p4=plot(bp_yhat',label=["1,1" "0,1" "1,0" "0,0"],title="BP")
p5=plot([dfa_err[:,1] ifa_err[:,1] fa_err[:,1] bp_err[:,1]],label=["DFA" "IFA" "FA" "BP"],title="First Trial Error")
p6=plot([mean(dfa_err,2) mean(ifa_err,2) mean(fa_err,2) mean(bp_err,2)],label=["DFA" "IFA" "FA" "BP"],title="Mean Trials Error")
plot(p1,p2,p3,p4,p5,p6,size=(900,900),layout=l)


export proxL1!, proxL1, normL1

"""
    proxL1!(x::Array{T}, λ::Float64; sparseTrafo::Trafo=nothing, kargs...) where T

performs soft-thresholding - i.e. proximal map for the Lasso problem.

# Arguments:
* `x::Array{T}`                 - Vector to apply proximal map to
* `λ::Float64`                  - regularization paramter
* `sparseTrafo::Trafo=nothing`  - sparsifying transform to apply
"""
function proxL1!(x::AbstractArray{Tc}, λ::T; sparseTrafo::Trafo=nothing, kargs...) where {T, Tc <: Union{T, Complex{T}}}
  ε = eps(T)

  if sparseTrafo != nothing
    z = sparseTrafo*x
    z .= max.((abs.(z).-λ),0) .* (z.+ε)./(abs.(z).+ε)
    x .= adjoint(sparseTrafo)*z
  else
    x .= max.((abs.(x).-λ),0) .* (x.+ε)./(abs.(x).+ε)
  end

  return x
end

"""
    normL1(x::Array{T}, λ::Float64; sparseTrafo::Trafo=nothing, kargs...) where T

returns the value of the L1-regularization term.
Arguments are the same as in `proxL1!`
"""
function normL1(x::T, λ::Float64; sparseTrafo::Trafo=nothing, kargs...) where T<:AbstractArray
  if sparseTrafo != nothing
    l1Norm = λ*norm(sparseTrafo*x,1)
  else
    l1Norm = λ*norm(x,1)
  end
  return l1Norm
end


# Michelle Medina
# July 16, 2020
# Testing 2D cases for Nodal and Hierarchical Bases

# Testing calculating Nodal Basis function in 2D
xVal = collect(range(0.0, stop=1.0, length = 5))
yVal = collect(range(0.0, stop=1.0, length = 5))
nodal = calcNodal2D((x,y)->exp(x)*exp(y), xVal, yVal)
nodal2 = calcNodal2D((x,y)->exp(x)*sin(pi*y), xVal, yVal)

# Plotting 2D functions
using PyPlot
contourf(nodal.values)
colorbar()
show()

contourf(nodal2.values)
colorbar()
show()

# Going from nodal to hierarchical grid and sparse grid
modal = Nodal_2_H(nodal)
sparse = Nodal_2_H_sparse(nodal,3)
for l in 0:2, m in 0:2
	display(modal.levels[l+1,m+1].coefficients)
	display(sparse.levels[l+1,m+1].coefficients)
	println()
end

# Going back and forth between nodal and modal
back = H_2_Nodal(modal)
@test isapprox(nodal.values,back.values)

# Using interpolation instead of evaluate
modal2 = Nodal_2_H_new(nodal)

# Testing how coefficients fall off as level increases
using LinearAlgebra
A = []
for l in 0:maxlevel(modal,1)
	append!(A, norm(modal.levels[l+1].coefficients))
end

using PyPlot
plot(A)
show()


export findβ, microcanonical_ensemble, canonical_ensemble

# stupid implementation of softmax - vals should be regularized beforehand
function _softmax(vals)
    w = exp.(vals)
    w ./= sum(w)
end

"""
    _weights_canonical(β, evals)
Compute the distribution of eigenstates with given energies at temperature 1/β.
P(E) ∝ ∑ᵢexp(-β Eᵢ)

Energy values are assumed to be sorted!
"""
function _weights_canonical(evals, β)
    # assume sorted evals!
    regulator = β < 0 ? evals[end] : evals[1]
    _softmax(@. -β * (evals - regulator))
end

_ΔE(evals, E_0, β) = abs((evals ⋅ _weights_canonical(evals, β)) - E_0)

# TODO: How to optimize best? The function is pretty well behaved...
"""
    findβ(evals, E₀; β_0=0)
Compute the inverse temperature β s. t. the thermal expectation value of the energy matches E₀.
"""
findβ(evals, E_0; β_0 = 0) = Optim.optimize(β -> _ΔE(evals, E_0, β[1]), [float(β_0)], Optim.Newton(linesearch = LineSearches.HagerZhang())).minimizer[1]

"""
    canonical_ensemble(evals, E₀; β₀ = 0)
Compute the weights of the state assuming a canonical ensemble of temperature 1/β. β₀ is taken as starting value for the computation.
A state is assigned the weight ∝ exp(-β Eᵢ)

Energy values are assumed to be sorted!
"""
canonical_ensemble(evals, E_0; β_0 = 0) = _weights_canonical(evals, findβ(evals, E_0; β_0))

"""
    microcanonical_ensemble(evals, E₀[, ΔE])
Compute the weights of the state assuming a microcanonical ensemble. Default size of the window is 0.5% of the spectral width.
A state is in the ensemble if |Eᵢ-E_0| < ΔE.
If the ensemble would be empty, choose the closest state (in energy).

Energy values are assumed to be sorted!
"""
function microcanonical_ensemble(evals, E_0, ΔE)::Vector{Float64}
    micro = @. (evals < E_0+ΔE) & (evals > E_0-ΔE)
    s = sum(micro)
    if s > 0
        # enforce float conversion for type stability
        micro ./ float(s)
    else
        micro = zeros(Float64, length(micro))
        smaller = searchsortedlast(evals, E_0)
        ind = abs(evals[smaller]-E_0) < abs(evals[smaller+1]-E_0) ? smaller : smaller+1
        micro[ind] = 1
        micro
    end
end

microcanonical_ensemble(evals, E_0) = microcanonical_ensemble(evals, E_0, 0.005*(evals[end]-evals[1]))


@generated apply(a,b) = :( apply(a,b,Val( $( hasmethod(*,Tuple{a,b}) ) )) )
apply(a,b,::Val{true}) = a*b
apply(a,b,::Val{false}) = a(b)

"""
    tucker(C,B)

Apply `B[k]` to the `k`th dimension of tensor `C`.

# Examples
```
julia> C = rand(2); B = (rand(3,2),);
       tucker(C,B) == B[1]*C
true

julia> C = rand(2,3); B = (rand(4,2),rand(5,3));
       tucker(C,B) == B[1]*C*B[2]'
true
```
"""
function tucker end

@generated function tucker(
    C::AbstractArray{<:Any,N},
    B::NTuple{N,Any}
) where {N}
    quote
        C_0 = C
        Base.Cartesian.@nexprs $N k->begin
            tmp_k = reshape(C_{k-1},(size(C_{k-1},1),prod(Base.tail(size(C_{k-1})))))
            tmp_k = apply(B[k],tmp_k)
            C_k = Array(reshape(transpose(tmp_k),(Base.tail(size(C_{k-1}))...,size(tmp_k,1))))
            # https://github.com/JuliaLang/julia/issues/30988
        end
        return $(Symbol("C_",N))
    end
end
tucker(C::AbstractArray{<:Any,1}, B::NTuple{1,Any}) = apply(B[1],C)
tucker(C::AbstractArray{<:Any,2}, B::NTuple{2,Any}) = transpose(apply(B[2],transpose(apply(B[1],C))))


# ---
# title: 1227. Airplane Seat Assignment Probability
# id: problem1227
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Math, Dynamic Programming, Brainteaser
# link: <https://leetcode.com/problems/airplane-seat-assignment-probability/description/>
# hidden: true
# ---
#
# `n` passengers board an airplane with exactly `n` seats. The first passenger
# has lost the ticket and picks a seat randomly. But after that, the rest of
# passengers will:
#
#   * Take their own seat if it is still available,
#   * Pick other seats randomly when they find their seat occupied
#
# What is the probability that the n-th person can get his own seat?
#
#
#
# **Example 1:**
#
#
#
#     Input: n = 1
#     Output: 1.00000
#     Explanation: The first person can only get the first seat.
#
# **Example 2:**
#
#
#
#     Input: n = 2
#     Output: 0.50000
#     Explanation: The second person has a probability of 0.5 to get the second seat (when first person gets the first seat).
#
#
#
#
# **Constraints:**
#
#   * `1 <= n <= 10^5`
#
#
## @lc code=start
using LeetCode

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


# # Sparse matrices
#
# Source to source automatic differentiation is useful in differentiating sparse matrices. It is a well-known problem that sparse matrix operations can not benefit directly from generic backward rules for dense matrices because general rules do not keep the sparse structure.
# In the following, we will show that reversible AD can differentiate the Frobenius dot product between two sparse matrices with the state-of-the-art performance. Here, the Frobenius dot product is defined as \texttt{trace(A'B)}.
# Its native Julia (irreversible) implementation is `SparseArrays.dot`.
#
# The following is a reversible counterpart

using NiLang, NiLang.AD
using SparseArrays

@i function idot(r::T, A::SparseMatrixCSC{T},B::SparseMatrixCSC{T}) where {T}
    m ← size(A, 1)
    n ← size(A, 2)
    @invcheckoff branch_keeper ← zeros(Bool, 2*m)
    @safe size(B) == (m,n) || throw(DimensionMismatch("matrices must have the same dimensions"))
    @invcheckoff @inbounds for j = 1:n
        ia1 ← A.colptr[j]
        ib1 ← B.colptr[j]
        ia2 ← A.colptr[j+1]
        ib2 ← B.colptr[j+1]
        ia ← ia1
        ib ← ib1
        @inbounds for i=1:ia2-ia1+ib2-ib1-1
            ra ← A.rowval[ia]
            rb ← B.rowval[ib]
            if (ra == rb, ~)
                r += A.nzval[ia]' * B.nzval[ib]
            end
            ## b move -> true, a move -> false
            branch_keeper[i] ⊻= @const ia == ia2-1 || ra > rb
            ra → A.rowval[ia]
            rb → B.rowval[ib]
            if (branch_keeper[i], ~)
                INC(ib)
            else
                INC(ia)
            end
        end
        ~@inbounds for i=1:ia2-ia1+ib2-ib1-1
            ## b move -> true, a move -> false
            branch_keeper[i] ⊻= @const ia == ia2-1 || A.rowval[ia] > B.rowval[ib]
            if (branch_keeper[i], ~)
                INC(ib)
            else
                INC(ia)
            end
        end
    end
    @invcheckoff branch_keeper → zeros(Bool, 2*m)
end

# Here, the key point is using a \texttt{branch\_keeper} vector to cache branch decisions.

# The time used for a native implementation is

using BenchmarkTools
a = sprand(1000, 1000, 0.01);
b = sprand(1000, 1000, 0.01);
@benchmark SparseArrays.dot($a, $b)

# To compute the gradients, we wrap each matrix element with `GVar`, and send them to the reversible backward pass

out! = SparseArrays.dot(a, b)
@benchmark (~idot)($(GVar(out!, 1.0)),
        $(GVar.(a)), $(GVar.(b)))

# The time used for computing backward pass is approximately 1.6 times Julia's native forward pass.
# Here, we have turned off the reversibility check off to achieve better performance.
# By writing sparse matrix multiplication and other sparse matrix operations reversibly,
# we will have a differentiable sparse matrix library with proper performance.

# See my another blog post for [reversible sparse matrix multiplication](https://nextjournal.com/giggle/how-to-write-a-program-differentiably).


plotvector(x, str="k-",label="", alpha=1.0) = plot3D([0;x[1]],[0;x[2]],[0;x[3]], str,alpha=alpha,label=label)

proj(v, x) = vec( dot(v, x) / dot(v, v) * v )

unitvector(x) = x / norm(x)


using StatsFuns, StatsModels, DataFrames, LinearAlgebra

"""
    ols(y,x)

Compute the ordinary least squares regresssion coefficients, and report results. Can also compute OLS subject to linear restrictions.

ols() with no arguments will run an example, execute edit(ols,()) to see the code.

"""
function ols()
    y = rand(100,1)
    x = [ones(100,1) rand(100,3)]
    names = ["const" "x2"  "x3" "x4"]
    ols(y,x)
    ols(y,x,names=names)
    ols(y,x,names=names, vc="nw")
    b,junk,junk,junk = ols(y,x,silent=true)
    show(b)
    # restricted LS
    R = [1 0 0 0]
    r = 1
    ols(y,x,R=R,r=r)
    # using dataframe
    println("using a formula and dataframe")
    d = DataFrame(x=rand(10),y=rand(10),z=rand(10))
    f = @formula(y ~ 1+x+z+x*z)
    ols(f,d)
    return
end

# version for dataframe and formula
function ols(f::Formula, df::DataFrame; R=[], r=[], names="", vc="white", silent=false)
    y = df[f.lhs]
    mf = ModelFrame(f,df)
    x = ModelMatrix(mf).m
    ols(y, x, names=coefnames(mf))
end

function ols(y::Array{Float64}, x::Array{Float64,2}; R=[], r=[], names="", vc="white", silent=false)
    n,k = size(x)
    if names==""
        names = 1:k
        names = names'
    end
    b, fit, e = lsfit(y,x)
    df = n-k
    sigsq = (e'*e/df)[1,1]
    xx_inv = inv(x'*x)
    ess = (e' * e)[1,1]
    # Restricted LS?
    if R !=[]
        q = size(R,1)
        P_inv = inv(R*xx_inv*R')
        b = b .- xx_inv*R'*P_inv*(R*b.-r)
        e = y-x*b;
        ess = (e' * e)[1,1]
        df = n-k-q
        sigsq = ess/df
        A = Matrix{Float64}(I, k, k) .- xx_inv*R'*P_inv*R;  # the matrix relating b and b_r
    end
    # Ordinary or het. consistent variance estimate
    if vc=="white"
        xe = x.*e
        varb = xx_inv*xe'xe*xx_inv
    elseif vc=="nw"
        xe = x.*e
        lags = Int(max(round(n^0.25),1.0))
        varb = n*xx_inv*NeweyWest(xe,lags)*xx_inv
    else
        varb= xx_inv.*sigsq
    end
    # restricted LS?
    if R !=[]
        varb = A*varb*A'
    end
    # common to both ordinary and restricted
    seb = sqrt.(diag(varb))
    seb = seb.*(seb.>1e-16) # round off to zero when there are restrictions
    t = b ./ seb
    tss = y .- mean(y)
    tss = (tss'*tss)[1,1]
    rsq = (1.0 - ess / tss)
    if !silent
        println()
        PrintDivider()
        if R==[]
            @printf("  OLS estimation, %d observations\n", n)
        else
            @printf("  Restricted LS estimation, %d observations\n", n)
        end
        @printf("  R²: %f   σ²: %f\n", rsq, sigsq)
        p = 2.0 .- 2.0*tdistcdf.(df, abs.(t))
        results = [b seb t p]
        if vc=="white"
            println("  White's covariance estimator")

        elseif vc=="nw"
            println("  Newey-West covariance estimator")
        end
        println()
        PrintEstimationResults(results, names)
        PrintDivider()
    end
    return b, varb, e, ess, rsq
end


"""
Created on Mon Apr 13 2020

@author: Yoann Pradat

Plot divergences
"""

include("./src/VNMF.jl")
using .VNMF
using LaTeXStrings
using Plots

#### # Plot β-divergence for different values of β
#### #################################################################################################################

βys = [-1, 0, 1, 2]
βzs = [-0.5, 0.5, 1.5, 2.5]
titles = [L"(a) \beta < 0", L"(b) 0 \leq \beta < 1", L"(c) 1 \leq \beta < 2", L"(d) 2 \leq \beta"]

h = 0.05
p = plot(layout=(2,2), dpi=300, size=(1000, 600))

for sp=1:4
    βy = βys[sp]
    βz = βzs[sp]

    if βz < 1
	X = h:h:8
    else
	X = h:h:4
    end

    Y = [β_divergence(hcat([1.]), hcat([x]), βy) for x in X]
    Z = [β_divergence(hcat([1.]), hcat([x]), βz) for x in X]

    plot!(
	X,
	hcat(Y, Z),
	title = titles[sp],
	labels = ["\$\\beta = $(βy)\$" "\$\\beta = $(βz)\$" ],
	ls    = [:solid :dash],
	lw    = 1,
	color = "black",
	subplot = sp
    )

    plot!(
	xlabel = "",
	xlims  = (0,X[end]),
	xticks = 0:X[end]/4:X[end],
	ylims  = (0,1.5),
	yticks = 0:0.5:1.5,
	subplot = sp
    )
end

folder   = "demo/plot/divergence"
mkpath(folder)
filename = "beta_divergences.pdf"
filepath = joinpath(folder, filename)

savefig(p, filepath)
println("plot saved at $(filepath)")


const where_bisect = 0.49609375

"""
    bisect(X::Interval, α=0.49609375)

Split the interval `X` at position α; α=0.5 corresponds to the midpoint.
Returns a tuple of the new intervals.
"""
function bisect(X::Interval, α=where_bisect)
    @assert 0 ≤ α ≤ 1

    m = mid(X, α)

    return (Interval(X.lo, m), Interval(m, X.hi))
end

"""
    bisect(X::IntervalBox, α=0.49609375)

Bisect the `IntervalBox` `X` at position α ∈ [0,1] along its longest side.
"""
function bisect(X::IntervalBox, α=where_bisect)
    i = argmax(diam.(X))  # find longest side

    return bisect(X, i, α)
end

"""
    bisect(X::IntervalBox, i::Integer, α=0.49609375)

Bisect the `IntervalBox` in side number `i`.
"""
function bisect(X::IntervalBox, i::Integer, α=where_bisect)

    x1, x2 = bisect(X[i], α)

    X1 = setindex(X, x1, i)
    X2 = setindex(X, x2, i)

    return (X1, X2)
end


constants = Constants()

# Monteith, John L., et Mike H. Unsworth. 2013. « Chapter 13 - Steady-State Heat Balance: (i)
# Water Surfaces, Soil, and Vegetation ». In Principles of Environmental Physics (Fourth Edition),
# edited by John L. Monteith et Mike H. Unsworth, 217‑47. Boston: Academic Press.

# p 230:

# In Monteith and Unsworth (2013) p.230, they say at a standard pressure of 101.3 kPa,
# λ has a value of about 66 Pa K−1 at 0 ◦C increasing to 67 Pa K−1 at 20 ◦C:
@testset "Psychrometer constant" begin
    λ₀ = latent_heat_vaporization(0.0, constants.λ₀)
    @test psychrometer_constant(101.3, λ₀) * 1000 ≈ 65.9651894869062 # in Pa K-1
    λ₂₀ = latent_heat_vaporization(20.0, constants.λ₀)
    @test psychrometer_constant(101.3, λ₂₀) * 1000 ≈ 67.23680111943287 # in Pa K-1
end;

@testset "Black body" begin
    # Testing that both calls return the same value with default parameters:
    @test black_body(25.0, constants.K₀, constants.σ) == black_body(25.0)
    @test black_body(25.0, constants.K₀, constants.σ) ≈ 448.07517457669354 # checked
end;

@testset "grey body" begin
    # Testing that both calls return the same value with default parameters:
    @test grey_body(25.0, 0.96, constants.K₀, constants.σ) == grey_body(25.0, 0.96)
    @test grey_body(25.0, 0.96, constants.K₀, constants.σ) ≈ 430.1521675936258
end;

@testset "Rₗₗ" begin
    # Testing that both calls return the same value with default parameters:
    @test net_longwave_radiation(25.0, 20.0, 0.955, 1.0, 1.0, constants.K₀, constants.σ) ==
            net_longwave_radiation(25.0, 20.0, 0.955, 1.0, 1.0)
    # Example from Cengel (2003), Example 12-7 (p. 627):
    # Cengel, Y, et Transfer Mass Heat. 2003. A practical approach. New York, NY, USA: McGraw-Hill.
    @test net_longwave_radiation(526.85, 226.85000000000002, 0.2, 0.7, 1.0, constants.K₀, constants.σ) ≈ -3625.6066521315793
    # NB: we compute it opposite (negative when energy is lost, positive for a gain)
end;

@testset "energy_balance(LeafModels{.,Monteith{Float64,Int64},Fvcb{Float64},Medlyn{Float64},.})" begin
    # Reference value:
    ref = (Rn = 21.497353195073416, skyFraction = 1.0, d = 0.03, Tₗ = 17.587096944187294,
            Rₗₗ = 7.750353195073414, H = -125.37135199797042, λE = 146.86870519304384,
            Cₛ = 349.0241705592342, Cᵢ = 330.36134887038975, A = 34.31323392636316,
            Gₛ = 1.807060287311702, Gbₕ = 0.02137773821516218, Dₗ = 0.49288571959517546,
            Gbc = 0.67312752539388, PPFD = 1500.0)

    meteo = Atmosphere(T = 20.0, Wind = 1.0, P = 101.3, Rh = 0.65)
    leaf = LeafModels(energy = Monteith(),
                photosynthesis = Fvcb(),
                stomatal_conductance = Medlyn(0.03, 12.0),
                Rₛ = 13.747, skyFraction = 1.0, PPFD = 1500.0, d = 0.03)

    non_mutating = energy_balance(leaf, meteo)

    for i in keys(ref)
        @test non_mutating[i] ≈ ref[i]
    end

    # Mutating the leaf:
    energy_balance!(leaf, meteo)
    for i in keys(ref)
        @test leaf.status[i] ≈ ref[i]
    end
end;

# meteo = Atmosphere(T = 20.0, Wind = 1.0, P = 101.3, Rh = 0.65)
# leaf = LeafModels(energy = Monteith(),
#             photosynthesis = Fvcb(),
#             stomatal_conductance = Medlyn(0.03, 12.0),
#             Rₛ = 13.747, skyFraction = 1.0, PPFD = 1500.0, d = 0.03)

# res = DataFrame(:PPFD => Float64[], :A => Float64[])
# for i in 1:10:1500
#     leaf.status.PPFD = i
#     energy_balance!(leaf, meteo)
#     push!(res, (PPFD = leaf.status.PPFD, A = leaf.status.A))
# end

# plot(res.PPFD,res.A)
# ylabel!("A")
# xlabel!("PPFD")

# Add tests for several components and/or several meteo time-steps (Weather)


@testset "The Hyperbolic Manifold" begin
    M = Hyperbolic(2)
    @test typicalDistance(M) == sqrt(2)
    @test getValue(HnPoint(1.)) == 1.
    @test getValue(HnTVector(0.)) == 0.
    x = HnPoint([0.,0.,1])
    @test getValue(x) == [0., 0., 1.]
    ξ = HnTVector([1.,1.,0,])
    @test getValue(ξ) == [1., 1., 0.]
    @test_throws ErrorException validateMPoint(M, HnPoint([1.,0.]))
    @test_throws ErrorException validateMPoint(M, HnPoint([0., 0., 0.]))
    @test_throws ErrorException validateMPoint(M, HnPoint([2., 0., 3.])) # -5 instead of -1
    @test validateTVector(M, x, ξ)
    @test_throws ErrorException validateTVector(M,x, HnTVector([0.,0.])) # Dimensions don't agree
    @test_throws ErrorException validateTVector(M,x, HnTVector(getValue(x))) # Not orthogonal
    y = exp(M, x, ξ)
    @test distance(M, x, y) ≈ norm(M, x, ξ)
    @test norm(M, x, ξ) ≈ sqrt(dot(M,x,ξ,ξ))
    @test distance(M, exp(M,x,log(M,x,y)), y) ≈ 0
    @test distance(M, exp(M,x,zeroTVector(M,x)), x) ≈ 0
    @test norm(M, x, log(M,x,x)) ≈ 0
    #
    @test manifoldDimension(x) == 2
    @test manifoldDimension(M) == 2
    @test parallelTransport(M,x,x,ξ) == ξ
    @test norm(M, y, parallelTransport(M,x,y,ξ)+log(M,y,x)) ≈ 0 atol=5*10^(-15)
    #
    @test typeofTVector(typeof(x)) == HnTVector{Float64}
    @test typeofMPoint(typeof(ξ)) == HnPoint{Float64}
    @test typeofTVector(x) == HnTVector{Float64}
    @test typeofMPoint(ξ) == HnPoint{Float64}

    @test "$(M)" == "The 2-Hyperbolic Space."
    @test "$(x)" == "Hn([0.0, 0.0, 1.0])"
    @test "$(ξ)" == "HnT([1.0, 1.0, 0.0])"
end


using PyPlot, Dierckx

cubehermitev1(x) = 2*x^3-3*x^2+1
cubehermitem1(x) = x^3-2*x^2+x
cubehermitev2(x) = -2*x^3+3*x^2
cubehermitem2(x) = (x-1)*x^2

@vectorize_1arg Number cubehermitev1
@vectorize_1arg Number cubehermitem1
@vectorize_1arg Number cubehermitev2
@vectorize_1arg Number cubehermitem2

function _plothermite()
    clf()
    x = linspace(0,1,300)
    plot(x, cubehermitev1(x), label="v1", color="r")
    plot(x, cubehermitem1(x), label="m1", color="g")
    plot(x, cubehermitev2(x), label="v2", color="orange")
    plot(x, cubehermitem2(x), label="m2", color="b")
    legend(loc="center right")
end

"""A callable object that acts as a monotone cubic spline, with a
choice of behaviors at the boundaries."""
type MonotoneSpline
    x      ::Vector{Real}
    y      ::Vector{Real}
    m      ::Vector{Real}
    dx     ::Vector{Real}
    bc     ::String
    function MonotoneSpline(x::Vector, y::Vector, m::Vector,
        dx::Vector; bc="error")
        new(x, y, m, dx, bc)
    end
end

function MonotoneSpline(x::Vector, y::Vector; bc="error")

    n=length(x)
    @assert n==length(y)

    secantslopes = zeros(Float64, n-1)
    for i=1:n-1
        secantslopes[i] = (y[i+1]-y[i])/(x[i+1]-x[i])
    end
    dx = x[2:end] .- x[1:end-1]
    m = zeros(Float64, n)
    m[2:n-1] = 0.5*(secantslopes[2:end] + secantslopes[1:end-1])
    m[1] = secantslopes[1]
    m[n] = secantslopes[n-1]

    for k=1:n-1
        if secantslopes[k] == 0
            m[k] = m[k+1] = 0
        else
            alpha = m[k] / secantslopes[k]
            beta = m[k+1] / secantslopes[k]
            if alpha < 0 || beta < 0
                throw(ArgumentError)
            end
            rsq = alpha^2 + beta^2
            if rsq>9
                tau = sqrt(9.0/rsq)
            else
                tau = 1.0
            end
            m[k] = tau*alpha*secantslopes[k]
            m[k+1] = tau*beta*secantslopes[k]
        end
    end
    return MonotoneSpline(x, y, m, dx; bc=bc)
end

function Base.call(spl::MonotoneSpline, z::Number)
    if z < spl.x[1]
        if spl.bc=="error"
            println("Oh no! 1 spl.bc=$(spl.bc)")
            throw(ArgumentError)
        elseif spl.bc=="constant"
            return spl.y[1]
        else
            return spl.y[1]+spl.m[1]*(z-spl.x[1])
        end
    elseif z > spl.x[end]
        if spl.bc=="error"
            println("Oh no! 2 spl.bc=$(spl.bc)")
            throw(ArgumentError)
        elseif spl.bc=="constant"
            return spl.y[end]
        else
            return spl.y[end]+spl.m[end]*(z-spl.x[end])
        end
    end

    bin = searchsortedfirst(spl.x, z)-1
    if bin<1; bin=1; end
    t = (z-spl.x[bin])/spl.dx[bin]
    return (cubehermitev1(t)*spl.y[bin] + cubehermitem1(t)*spl.m[bin]*spl.dx[bin] +
    cubehermitev2(t)*spl.y[bin+1] + cubehermitem2(t)*spl.m[bin+1]*spl.dx[bin])
end
Base.call(spl::MonotoneSpline, z::AbstractArray) = map(spl, z)

"""A callable object that acts as a monotone cubic spline in log-log space,
with a choice of behaviors at the boundaries."""
type MonotoneSplineLogLog
    x      ::Vector{Real}
    y      ::Vector{Real}
    ms     ::MonotoneSpline
    function MonotoneSplineLogLog(x::Vector, y::Vector; bc="error")
        ms = MonotoneSpline(log(x), log(y); bc=bc)
        new(x, y, ms)
    end
end

Base.call(spl::MonotoneSplineLogLog, z::Number) = exp(spl.ms(log(z)))
Base.call(spl::MonotoneSplineLogLog, z::AbstractArray) = map(spl, z)

"""Return the MonotoneSplineLogLog that has the same knots as c1 but passes
through the control points that result from applying c2 on top of c1."""
function compose_splinelog(c1::MonotoneSplineLogLog, c2::MonotoneSplineLogLog)
    x = c1.x
    y = c2(c1(x))
    MonotoneSplineLogLog(x, y; bc="extrapolate")
end

"""Return the MonotoneSpline that has the same knots as c1 but passes
through the control points that result from applying c2 on top of c1."""
function compose_spline(c1::MonotoneSpline, c2::MonotoneSpline)
    x = c1.x
    y = c2(c1(x))
    MonotoneSpline(x, y; bc="extrapolate")
end

function test1()
    x = collect(0:9)
    y = [0,1,3,10,10,11,12,14,14,16]
    f = MonotoneSpline(x, y; bc="extrapolate")
    f2 = Dierckx.Spline1D(float(x), float(y))
    a = linspace(-2,10,500)
    clf()
    plot(x,y,"ok",label="Input data")
    plot(a, f(a), "r", label="Monotone spline")
    plot(a, evaluate(f2,a), "b", label="Dierckx.Spline1D")
    legend(loc="lower right")
    return f
end

function test2()
    model(x) = 1000*((x./1000).^0.6 ) + x
    x = collect(linspace(0,8000,12))
    y = model(x)
    f = MonotoneSpline(x, y; bc="extrapolate")
    g = MonotoneSplineLogLog(x, y; bc="extrapolate")
    s(z) = evaluate(Dierckx.Spline1D(x, y), z)
    clf()
    a = linspace(0,8000,600)
    subplot(211)
    plot(a, model(a), "k", lw=2)
    plot(a, f(a), "r")
    plot(a, g(a), "b")
    plot(a, s(a), "g")
    plot(x,y,"ok")

    subplot(212)
    plot(a, model(a)-f(a), "r", label="Monotone spline")
    plot(a, model(a)-g(a), "b", label="Log-space mono spline")
    plot(a, model(a)-s(a), "g", label="Dierckx.Spline1D")
    legend()
end


using Test, DelayEmbeddings

println("\nTesting delay embeddings...")

@testset "embedding" begin

    data = Dataset(rand(10001,3))
    s = data[:, 1]; N = length(s)

    @testset "standard" begin

    	@testset "D = $(D), τ = $(τ)" for D in [2,3], τ in [2,3]
    		R = embed(s, D, τ)
    		@test R[(1+τ):end, 1] == R[1:end-τ, 2]
    		@test size(R) == (length(s) - τ*(D-1), D)
    	end
    end

    @testset "weighted" begin
    	@testset "D = $(D), τ = $(τ)" for D in [2,3], τ in [2,3]

            w = 0.1
    		R1 = embed(s, D, τ)
    		R2 = embed(s, D, τ, w)
            for γ in 0:D-1
                @test  (w^γ) * R1[1, γ+1][1] == R2[1, γ+1]
                @test  (w^γ) * R1[5, γ+1][1] == R2[5, γ+1]
            end
    	end
    end

    @testset "multi-time" begin
        D = 3
        τ1 = [2, 4]
        τ2 = [4, 8]

        R0 = embed(s, D, 2)
        R1 = embed(s, D, τ1)
        R2 = embed(s, D, τ2)

        @test R1 == R0

        R2y = R2[:, 2]
        @test R2y == R0[5:end, 1]
        @test R2[:, 1] == R0[1:end-4, 1]
        @test size(R2) == (N-maximum(τ2), 3)

        @test_throws ArgumentError embed(s, 4, τ1)
    end

end

println("\nTesting generalized embedding...")
@testset "genembed" begin
    τs = (0, 2, -7)
    js = (1, 3, 2)
    ge = GeneralizedEmbedding(τs, js)
    s = Dataset(rand(20, 3))
    x, y, z = columns(s)
    τr = τrange(s, ge)
    @testset "univariate" begin
        w = rand(20)
        em = genembed(w, τs, js)
        @test em == genembed(w, τs)
        @test em[1:3, 3] == w[1:3]
        @test em[1:3, 1] == w[1+7:3+7]
        @test em[1:3, 2] == w[1+7+2:3+7+2]
    end
    @testset "multivariate" begin
        em = genembed(s, τs, js)
        @test em[1:3, 1] == x[1+7:3+7]
        @test em[1:3, 2] == z[1+7+2:3+7+2]
        @test em[1:3, 3] == y[1:3]
    end
    @testset "weighted" begin
        ws = (1, 0, 0.1)
        em = genembed(s, τs, js; ws)
        @test em[1:3, 1] == x[1+7:3+7]
        @test em[1:3, 2] == 0 .* z[1+7+2:3+7+2]
        @test em[1:3, 3] == 0.1 .* y[1:3]
    end
    @testset "weighted integer" begin
        ws = (1, 0, -0.1)
        x = collect(1:100)
        ge = GeneralizedEmbedding(τs, js, ws)
        em = genembed(x, τs, js; ws)
        @test em[1:3, 1] == x[1+7:3+7]
        @test em[1:3, 2] == 0 .* x[1+7+2:3+7+2]
        @test em[1:3, 3] == -0.1 .* x[1:3]
    end
end


using Distributions, Plots; pyplot()

dist = TriangularDist(4,6,5)
N = 10^6
data = rand(dist,N)
yData=(data .- 5).^2

println("Mean: ", mean(yData), " Variance: ", var(data))

p1 = histogram(data, xlabel="x", bins=80, normed=true, ylims=(0,1.1))
p2 = histogram(yData, xlabel="y", bins=80, normed=true, ylims=(0,15))
plot(p1, p2, ylabel="Proportion", size=(800, 400), legend=:none)


using Revise

includet("src/Spring.jl")
IArray = InternalArray

# 3. Validating the solution --------------------------------------------------------------

const step = 1e-3
const nit = Int(40/step)  # round to avoid small rounding errors

# We define a problem with no friction
prob = SpringProblem(p0 = 0.8, v0 = 0, m = 1, k = 1.5)

x = eulermethod(prob, step, nit)

timeline = [prob.t0 + i*step for i in 1:length(x)]
plot(timeline, IArray(x, 1), label = "standard")

# 5. Finding the frequency of the unforced movement ---------------------------------------

# Empirically, We can see in the plot that the distance between two
# equal values (!= 0) is aproximately 5.
# Let's get the actual distance from our solution.

p0 = 1       # Reference point
P0 = -1      # Next equal point
for i = 50:length(x) # We avoid the first numbers because they are very close
    if norm(x[p0][1]-x[i][1]) < 1e-3
        P0 = i
        break
    end
end

if P0 != -1
    period = timeline[P0]-timeline[p0] # Is similar to what we observed
    freq = 1/period
end

# 6. Using this frequency to force it -----------------------------------------------------

cond_prob = SpringProblem(p0 = 0.8, v0 = 0, m = 1, k = 1.5, A = 1, ω = 2*π*freq)

y = eulermethod(cond_prob, step, nit)
plot!(timeline, IArray(y, 1), label = "conditioned")

savefig("media/spring_frequency_unforced.png")


#-----------------------------------------------------------
# RBF basis calculation
#-----------------------------------------------------------

function calculaterbfbasis!(Φ, x, gridpoints, r=1.0)

    M = length(gridpoints)

    N = length(x)

    for m in 1:M

        for n in 1:N

            @inbounds Φ[n,m] = exp(-norm(gridpoints[m] - x[n])^2 / r^2)

        end

    end

    nothing

end

function calculaterbfbasis(x, gridpoints, r=1.0)

    M = length(gridpoints)

    N = length(x)

    Φ = zeros(N, M)

    calculaterbfbasis!(Φ, x, gridpoints, r)

    return Φ

end