# Include packages
using Polynomials: polyfit, polyval
using DelimitedFiles: readdlm

####################
## main
####################

# define the filepath & filename
fdir = "./data"
fname = "windspeed.dat"

# load ascii file
dataorg = readdlm(joinpath(fdir,fname))
years = dataorg[:,1]
wind = dataorg[:,2]
stdval = dataorg[:,3]
# regression analysis
lin_p = polyfit(years, wind, 1)
quad_p = polyfit(years, wind, 2)
years_ext = years[end]:2100.0

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

####################
## plot
####################

# directory where figures are printed
figdir="./fig"
if !isdir(figdir); mkdir(figdir); end

using Plots
pyplot()
#gr()
#plotlyjs()

# original data
plot(years, wind, linetype=:scatter, ms=10., tickfont=12, lab="Raw data",
     legendfont=font(14,"sans-serif"), legend=:bottomleft,
     title="Regression analysis", titlefont=font("sans-serif",16),
     xlabel="Year", ylabel="Wind speed (m/s)",guidefont=font("sans-serif",16),
     size=(800,600),
     )
# linear
plot!(years, polyval(lin_p,years), line=(:solid,2), linecolor=:red, lab="Linear")
plot!(years_ext, polyval(lin_p,years_ext), line=(:dash,2), linecolor=:red, lab="Linear to 2100")
# quad
plot!(years, polyval(quad_p,years), line=(:solid,2), linecolor=:green, lab="Quad")
plot!(years_ext, polyval(quad_p,years_ext), line=(:dash,2), linecolor=:green, lab="Quad to 2100")
xlims!(1945, 2105)

# save figure
savefig(joinpath(figdir,"regression.png"))
####################


# # An equation of forth order in one dimmension
#
# Consider the following problem. Given \\(f \in C([0, 1])\\), find a function \\(u\\) satisfying
# u'''' = f in (0, 1)
# u(0) = u(1) = 0
# u'(0) = pi; u'(1) = pi

using SymFEL
using SymPy
using LinearAlgebra
using SparseArrays
using PyPlot
close("all")

# discretization parameters
N = 101; # number of nodes
dx = 1 / (N - 1); # discretization step
nodes = range(0, stop=1, length=N);
bound_nodes = [1, N];

# exact solution
u_exact = sin.(pi*nodes);
ud_exact = pi * cos.(pi*nodes)
# right hand
f = pi^4 * sin.(pi * nodes);
fd = pi^5 * cos.(pi * nodes);
ff = zeros(2*N);
ff[1:2:2*N-1] = f;
ff[2:2:2*N] = fd;

# elementary matrices
elem_K = SymFEL.get_hermite_em(3, 2, 2);
elem_M = SymFEL.get_em(3, 3, 0, 0; fe1="Hermite", fe2="Hermite")
elem_K_dx = convert(Matrix{Float64}, elem_K.subs(h, dx));
elem_M_dx = convert(Matrix{Float64}, elem_M.subs(h, dx));

K = SymFEL.assemble_1d_FE_matrix(elem_K_dx, N,
                                 intNodes1=0, intNodes2=0,
                                 dof1=2, dof2=2);
M = SymFEL.assemble_1d_FE_matrix(elem_M_dx, N,
                                 intNodes1=0, intNodes2=0,
                                 dof1=2, dof2=2);

F = M * ff;

# boundary conditions
tgv = 1e100
KB = copy(K)
KB[2*bound_nodes .- 1, 2*bound_nodes .- 1] += tgv*sparse(Matrix{Float64}(I, 2, 2));
KB[2*bound_nodes, 2*bound_nodes] += tgv*sparse(Matrix{Float64}(I, 2, 2));

F[2*bound_nodes .- 1] = zeros(2);
F[2] = pi*tgv
F[2*N] = -pi*tgv

u = KB \ F;

plot(nodes, u_exact);
plot(nodes, u[1:2:2*N-1]);
legend(["Exact solution", "Approximate solution"]);

U_exact = zeros(2*N)
U_exact[1:2:2*N-1] = u_exact
U_exact[2:2:2*N] = ud_exact
err = u - U_exact;
println("H1 error = ", sqrt(err' * K * err))


using Symata

println()
@sym begin
  ClearAll(x, xi, yi, L0, L1, L2, L, Q)
  xi = [1, 3, 6]
  yi = [1, 5, 10]
  L0(x_) = Simplify( Expand( ((x-xi[2])*(x-xi[3]))/((xi[1]-xi[2])*(xi[1]-xi[3])) ) )
  L1(x_) = Simplify( Expand( ((x-xi[1])*(x-xi[3]))/((xi[2]-xi[1])*(xi[2]-xi[3])) ) )
  L2(x_) = Simplify( Expand( ((x-xi[1])*(x-xi[2]))/((xi[3]-xi[1])*(xi[3]-xi[2])) ) )
  L = [L0(x), L1(x), L2(x)]
  Q(x_) = Simplify( yi ⋅ L )
  SetJ(qs, ToString(Q(x)))
  Print("Q(x) = ", Q(x), "\n")
end

@sym Print("Q(4.5) = ", N(Q(4.5)), "\n");

@sym begin
  ClearAll(x, xi, yi, L, Q, den, num)
  xi = [1, 3, 6]
  yi = [1, 5, 10]
  For(i=1, i<=Length(xi), Increment(i),
    [
      den = 1.0,
      num = 1.0,
      For(j=1, j<=Length(xi), Increment(j),
        [
          If(j != i,
            [
              den *= (x - xi[j])
              num *= (xi[i] - xi[j])
            ]
          )
        ]
      ),
      L = If(i==1, [Simplify(Expand(den/num))], Join(L,[Simplify(Expand(den/num))]))
    ]
  )
  Q(x_) = Simplify( yi ⋅ L )
  SetJ(qs, ToString(Q(x)))
  Println("Q(x) = ", Q(x))
end

@sym Println("Q(4.5) = ", N(Q(4.5)))


"""
    abs_val(num)

Program to find the absolute value of a number

# Example
```julia
abs_val(-100) # returns 100
abs_val(0) # returns 0
abs(123.1) # returns 123.1
-1000 == abs_val(-1000) #returns false
1000 == abs_val(1000) #returns true
```

Contributed By:- [Ashwani Rathee](https://github.com/ashwani-rathee) and [Rratic](https://github.com/Rratic)
"""
function abs_val(num::N) where {N<:Real}
    return num < 0 ? -num : num
end

"""
    abs_max(x)

Program to find the max absolute value in a vector

# Example
```julia
abs_max([1,3,4]) # returns 4
abs_max([-3,1,2]) # returns -3
abs_max([-7,-3,6]) #returns -7
```

Contributed By:- [Ashwani Rathee](https://github.com/ashwani-rathee) and [Rratic](https://github.com/Rratic)
"""
function abs_max(x::Vector{N}) where {N<:Real}
    _max = x[1]
    for i in x
        if abs(i) > abs(_max)
            _max = i
        end
    end
    return _max
end

"""
    abs_min(num)

Program to find the min absolute value in a vector

# Example
```julia
abs_min([1,3,4]) # returns 1
abs_min([-3,1,2]) # returns 1
abs_min([-7,-3,6]) #returns -3
```

Contributed By:- [Ashwani Rathee](https://github.com/ashwani-rathee) and [Rratic](https://github.com/Rratic)
"""
function abs_min(x::Vector{N}) where {N<:Real}
    _min = x[1]
    for i in x
        if abs(i) < abs(_min)
            _min = i
        end
    end
    return _min
end


# Description:
# Dimensions: 2
#
# The Cross-in-Tray function has multiple global minima. It is shown here with a smaller domain in the second plot, so that its characteristic "cross" will be visible.
#
# Input Domain:
# The function is usually evaluated on the square xi ∈ [-10, 10], for all i = 1, 2.
#
# Global Minima:
# https://www.sfu.ca/~ssurjano/crossit3a.png

function crossit(xx)

    x1 = xx[1];
    x2 = xx[2];

    fact1 = sin(x1) * sin(x2);
    fact2 = exp(abs(100 - sqrt(x1^2 + x2^2) / pi));

    y = -0.0001 * (abs(fact1 * fact2) + 1)^0.1;

    return [y]
end


using LinearAlgebra, Plots, Statistics, TaylorSeries, Printf, JLD2, FileIO
# if !(pwd() in LOAD_PATH) push!(LOAD_PATH, pwd()) end
# using TaylorBigF

path1=realpath(dirname(@__FILE__)*"/..")
include(string(path1,"/src/TaylorBigF.jl"))

# 4.1 Multidimensional Interpolation

p=50;setprecision(p); setrounding(BigFloat, RoundUp);
L=BigFloat("1"); n=BigInt(300); dx=BigFloat("2")*L/(n-1);
x=convert(Array{BigFloat},(rand(n).-1/2)*1)
y=convert(Array{BigFloat},(rand(n).-1/2)*1)
I=zeros(BigFloat, n, n);for i=1:n I[i,i]=BigFloat("1") end
V=TaylorBigF.calc_Vandermonde_2D(x,y,n,n)
@time Vinv=V\I

# @save string("ws",p,".jld2") p,L,n,dx,x,y,I,V,Vinv
# p=1000;setprecision(p); setrounding(BigFloat, RoundUp);
# @load string("ws",p,".jld2") p,L,n,dx,x,y,I,V,Vinv

# sos pigaine poli kalitera me tria matrixes gia x y xy me psiles dinameis, alla mono se eidikes sinartisis ph sinx + cosy
# V=calc_Vandermonde_2D_highO(x,y,n,BigInt(34))

function func(x,y) sin.(BigFloat("5")x).+cos.(exp.(BigFloat("2")y)) end

f=func(x,y)
# V=V[:,end:-1:1]
# a=V\f
a=Vinv*f

# plot(a)
# scatter3d(x,y,f); scatter3d!(x,y,V*a)
# maximum(abs.(f-V*a))
# xi=x.+BigFloat("0.01")
# yi=y.+BigFloat("0.01")
# Vi=calc_Vandermonde_2D_highO(xi,yi,n,BigInt(34))
xi=convert(Array{BigFloat},(rand(n).-1/2)*0.7)
yi=convert(Array{BigFloat},(rand(n).-1/2)*0.7)
Vi=TaylorBigF.calc_Vandermonde_2D(xi,yi,n,n)
fi_comp=Vi*a
fi_analyt=func(xi,yi)
diff_fi=maximum(abs.(fi_analyt-fi_comp));@sprintf("diff_fi=%1.3e",diff_fi)
scatter3d(xi,yi,fi_analyt,label="exact values"
    ,markershape=:circle,color=:black)
scatter3d!(xi,yi,fi_comp,label=string("approximated, p=",p)
    ,markershape=:xcross,legend=:topleft,color=:black)
# scatter3d!(xi,yi,fi_comp,label=string("approximated, p=",p)
#     ,markershape=:xcross,legend=:topleft,color=:black,xaxis="x")
# zlims!(-1.4,2.0)
# maximum(fi_comp)
# minimum(fi_comp)
cd(dirname(@__FILE__))
savefig(string("interp3Dp",string(p),".pdf"))
# mean(abs.(V))-mean(abs.(Vi))
# mean(abs.(x))-mean(abs.(xi))


module TheAlgorithms

# Usings/Imports (keep sorted)
using DataFrames
using DifferentialEquations
using GLM
using LinearAlgebra
using Plots
using Random

## Exports
# Please keep the folders/functions sorted

# Exports: math
export collatz_sequence
export euler_method
export line_length
export SIR # TODO: make the name lowercase if possible
export trapazoidal_area
export is_armstrong

# Exports: matrix
export determinant
export lu_decompose

# Exports: project-rosalind
export count_nucleotides
export dna2rna
export reverse_complement

# Exports: searches
export binarysearch
export search

# Exports: statistics
export OLSbeta

## Includes
# Please keep the folders/files sorted (by dependencies then alphabetical order)

# Includes: math
include("math/area_under_curve.jl")
include("math/collatz_sequence.jl")
include("math/euler_method.jl")
include("math/line_length.jl")
include("math/sir_model.jl")
include("math/armstrong_number.jl")

# Includes: matrix
include("matrix/lu_decompose.jl") # used by determinant.jl
include("matrix/determinant.jl")

# Includes: project-rosalind
include("project-rosalind/count_nucleotide.jl")
include("project-rosalind/dna2rna.jl")
include("project-rosalind/reverse_complement.jl")

# Includes: search
include("searches/binary_search.jl")
include("searches/linear_search.jl")

# Includes: statistics
include("statistics/ordinary_least_squares.jl")

end


using Measurements

5.23 ± 0.14 === 5.23 ± 0.14

(5.23± 0.14) - (5.23 ± 0.14)

(5.23 ± 0.14) / (5.23 ± 0.14)

x = 5.23 ± 0.14
x === x

x - x

x / x

using DifferentialEquations, Measurements, Plots

# Half-life and mean lifetime of radiocarbon, in years
t_12 = 5730 ± 40
τ = t_12 / log(2)

#Setup
u₀ = 1 ± 0
tspan = (0.0, 10000.0)

#Define the problem
radioactivedecay(u,p,t) = - u / τ

#Pass to solver
prob = ODEProblem(radioactivedecay, u₀, tspan)
sol = solve(prob, Tsit5(), reltol = 1e-8)

# Analytic solution
u = exp.(- sol.t / τ)

plot(sol.t, sol.u, label = "Numerical", xlabel = "Years", ylabel = "Fraction of Carbon-14")
plot!(sol.t, u, label = "Analytic")

println("Quantity of carbon-14 after ",  sol.t[11], " years:")
println("Numerical: ", sol[11])
println("Analytic:  ", u[11])

using DifferentialEquations, Measurements, Plots

g = 9.79 ± 0.02; # Gravitational constants
L = 1.00 ± 0.01; # Length of the pendulum

#Initial Conditions
u₀ = [0 ± 0, π / 60 ± 0.01] # Initial speed and initial angle
tspan = (0.0, 6.3)

#Define the problem
function simplependulum(du,u,p,t)
    θ  = u[1]
    dθ = u[2]
    du[1] = dθ
    du[2] = -(g/L)*θ
end

#Pass to solvers
prob = ODEProblem(simplependulum, u₀, tspan)
sol = solve(prob, Tsit5(), reltol = 1e-6)

# Analytic solution
u = u₀[2] .* cos.(sqrt(g / L) .* sol.t)

plot(sol.t, getindex.(sol.u, 2), label = "Numerical")
plot!(sol.t, u, label = "Analytic")

plot(sol.t, getindex.(sol.u, 2) .- u, label = "")

g = 9.79 ± 0.02; # Gravitational constants
L = 1.00 ± 0.01; # Length of the pendulum

#Initial Conditions
u₀ = [0 ± 0, π / 3 ± 0.02] # Initial speed and initial angle
tspan = (0.0, 6.3)

#Define the problem
function simplependulum(du,u,p,t)
    θ  = u[1]
    dθ = u[2]
    du[1] = dθ
    du[2] = -(g/L) * sin(θ)
end

#Pass to solvers
prob = ODEProblem(simplependulum, u₀, tspan)
sol = solve(prob, Tsit5(), reltol = 1e-6)

plot(sol.t, getindex.(sol.u, 2), label = "Numerical")

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


using Plots
using LinearAlgebra
using Distributions

xC = [2, 1] # Center of data (mean)
sig = [2, 0.5] # Principal axes

theta = pi/3 # Rotate cloud by pi/3
R = [cos(theta) -sin(theta); sin(theta) cos(theta)] # Rotation matrix

nPoints = 10000 # Create 10,000 points
X = R*diagm(sig)*rand(Normal(), 2, nPoints) + diagm(xC)*ones(2, nPoints);

p1 = scatter(X[1,:], X[2,:]) # Plot cloud of noisy data

# f_ch01_ex03_1b

Xavg = [sum(X[1,:]); sum(X[2,:])]/nPoints # Compute mean
B = X - Xavg*ones(1,nPoints) # Mean-subtracted Data
U, S, V = svd(B/sqrt(nPoints)) # Find principal components (SVD)

theta = 2pi*(0:0.01:1)
Xstd = hcat([U*[S[1]*cos(t); S[2]*sin(t)] for t in theta]...) # 1-std confidence interval
p2 = plot(Xstd[1,:] .+ Xavg[1], Xstd[2,:] .+ Xavg[2])
plot!(2*Xstd[1,:] .+ Xavg[1], 2*Xstd[2,:] .+ Xavg[2])
plot!(3*Xstd[1,:] .+ Xavg[1], 3*Xstd[2,:] .+ Xavg[2])

# Plot principal components
# p3 = scatter([Tuple(Xavg + U[:,1]*S[1]), Tuple(Xavg + U[:,2]*S[2])])


"""
According to P. Sauer "Power System Dynamics and Stability" p. 67 the
relationship between v_f_d and i_in1 is nonlinear due to saturation
of the exciter iron.

The saturation is then often described through two characteristic points on the
saturation curve: S_E_max (saturation at maximal excitation voltage)
and S_E0.75(saturation at 0.75* maximal excitation voltage).

These can be used to fit the following function:
S_E(E_f_d) = A_x * exp(B_x * E_f_d)               P.Sauer, P.70, Eq(4.22)

In a steady-state it is also given that:
0 = - (K_e + S_E_max) * E_f_d_max + V_R_max       P.Sauer, P.69, Eq(4.21)
Inputs:
    - K_e: Exciter constant related to self-excited field [pu]
    - S_E_max: Saturation at maximal excitation voltage [pu]
    - S_E_tq: Saturation at 0.75 * maximal excitation voltage [pu]
    - V_R_max: Maximal reference voltage for the AVR [pu]
Outputs:
    - E_f_d_max: maximal excitation voltage
    - A_x: Fitting Parameter
    - B_x: Fitting Parameter
"""

function ExciterSaturationEq(K_e, S_E_max, S_E_tq, V_R_max)
    E_f_d_max = V_R_max / (K_e + S_E_max)
    A_x = S_E_tq^4 / S_E_max^3
    B_x = 4/3 * log(S_E_tq / A_x) / E_f_d_max
    return E_f_d_max, A_x, B_x
end


"""jldoctest
julia> ArithmematicAnalysis.triangular_resolution([1 2;0 3],[1;2])
[-0.33333333333333326; 0.6666666666666666]
"""
function triangular_resolution(coefficients::Array{T,2},vector::Array{T,1}) where {T<:Number}
    rows, cols = size(coefficients)
    vars = Array{Float64,2}(undef,cols,1)

    calcs = zeros(Float64,rows,cols)

    for row in reverse(1:rows)
        sum = 0.0
        for i in row+1:cols
            sum+=coefficients[row,i]*vars[i]
        end
        vars[row,1] = (vector[row] - sum)/coefficients[row,row]
    end
    vars
end

"""jldoctest
julia> ArithmematicAnalysis.guassian_elimination([1 1 1; 2 3 7; 1 3 -2], [3; 0; 17])
[1.0; 4.0; -2.0]
"""
function guassian_elimination(coefficients::Array{T,2},vector::Array{T,1}) where {T<:Number}
    coeff_f::Array{Float64} = coefficients
    vector_f::Array{Float64} = vector
    rows, cols = size(coefficients)
    vars = Array{Float64,2}(undef,cols,1)
    calcs = zeros((rows,cols))

    values_cat = hcat(coeff_f,vector_f)

    for col in 1:cols
        for row in col:rows
            if col==row
                values_cat[row,:] = values_cat[row,:]./values_cat[row,col]
            else
                values_cat[row,:] = values_cat[row,:]./values_cat[row,col]-values_cat[col,:]
            end
        end
    end
    return triangular_resolution(values_cat[:,1:cols],values_cat[:,cols+1])
end


"""
    rk4_step(f_xy, h, x, y)
    -> (x1, y1)
Returns coordinates (x1, y1) after one Runge-Kutta 4th order step.
        f is a tuple-valued gradient in two dimensions
        (x, y) is the starting point
        h is a step-size quantity. If the integration variable is time,
        it defines the length of time betweeen points. It can be negative.
"""
function rk4_step(f_xy, h, x, y)
    fx0, fy0 = f_xy(x, y)
    x1 = x + fx0 * h * 0.5
    y1 = y + fy0 * h * 0.5
    fx1, fy1 = f_xy(x1, y1)
    x2 = x + fx1 * h * 0.5
    y2 = y + fy1 * h * 0.5
    fx2, fy2  = f_xy(x2, y2)
    x3 = x + fx2 * h
    y3 = y + fy2 * h
    fx3, fy3 = f_xy(x3, y3)
    x +=  h * ( fx0  + 2∙fx1 + 2∙fx2 + fx3 ) / 6
    y +=  h * ( fy0  + 2∙fy1 + 2∙fy2 + fy3 ) / 6
    x, y
end

"""
    rk4_step!(f, vx, vy, h, n)

Coordinates (vx[n + 1], vy[n + 1]) are updated with the estimated position,
along function f.
        f is a tuple-valued gradient in two dimensions
        vx and vy are vectors representing coordinates like (vx[n], vy[n])
        h is a step-size quantity. If the integration variable is time,
        it defines the length of time betweeen points. It can be negative.
"""
function rk4_step!(f, vx, vy, h, n)
    fx0, fy0 = f(vx[n], vy[n])
    x1 = vx[n] + fx0 * h * 0.5
    y1 = vy[n] + fy0 * h * 0.5
    fx1, fy1 = f(x1, y1)
    x2 = vx[n] + fx1 * h * 0.5
    y2 = vy[n] + fy1 * h * 0.5
    fx2, fy2  = f(x2, y2)
    x3 = vx[n] + fx2 * h
    y3 = vy[n] + fy2 * h
    fx3, fy3 = f(x3, y3)
    vx[n + 1] = vx[n] + 1/6 * h * ( fx0  + 2∙fx1 + 2∙fx2 + fx3 )
    vy[n + 1] = vy[n] + 1/6 * h * ( fy0  + 2∙fy1 + 2∙fy2 + fy3 )
end

"""
    rk4_steps!(f, vx, vy, h)

Coordinates (vx[n > 1], vy[n > 1]) are updated with the estimated positions,
along function f.
     f(x,y) returns a tuple-valued gradient in two dimensions
     vx and vy are vectors representing coordinates like (vx[n], vy[n])
     h is a step-size quantity. If the integration variable is time,
     it defines the length of time betweeen points. It can be negative
"""
function rk4_steps!(f, vx, vy, h)
    @assert length(vx) == length(vy)
    if !isnan(f(vx[1], vy[1])[1])
        for n in 1:(length(vx) - 1)
            rk4_step!(f, vx, vy, h, n)
        end
    else
        fill!(vx, NaN * vx[1])
        fill!(vy, NaN * vy[1])
    end
    nothing
end


module Mods

import Base.isequal, Base.==, Base.+, Base.-, Base.*
import Base.inv, Base./, Base.^
import Base.hash, Base.show

export Mod
export isequal, ==, +, -, *
export is_invertible, inv, /, ^
export hash, CRT

"""
`Mod(v,m)` creates a modular number in mod `m` with value `v%m`.
`Mod(m)` is equivalent to `Mod(0,m)`.
"""
struct Mod{T<:Integer}
    val::T
    mod::T
    function Mod(a::S, m::T) where {S<:Integer, T<:Integer}
        if m < 1
            error("Modulus must be at least 1")
        end

        a,m = promote(a,m)
        return new{typeof(a)}(mod(a,m),m)
    end
end

Mod(m::Integer) = Mod(0,m)

function hash(x::Mod, h::UInt64= UInt64(0))
    v = BigInt(x.val)
    m = BigInt(x.mod)
    return hash(v,hash(m,h))
end

# Test for equality
isequal(x::Mod, y::Mod) = x.mod==y.mod && x.val==y.val
==(x::Mod,y::Mod) = isequal(x,y)

function modcheck(x::Mod, y::Mod)
    if x.mod != y.mod
        error("Cannot operate on two Mod objects with different moduli")
    end
    true
end

# Easy arithmetic
function +(x::Mod, y::Mod)
    modcheck(x,y)
    s,flag = Base.add_with_overflow(x.val,y.val)
    if !flag
        return Mod(x.val+y.val, x.mod)
    end
    s = widen(x.val) + widen(y.val)    # add with added precision
    s = mod(s,x.mod)                   # reduce by modulus
    return Mod(oftype(x.mod,s),x.mod)  # return with proper type
end

function -(x::Mod)
    return Mod(-x.val, x.mod)
end

-(x::Mod,y::Mod) = x + (-y)

function *(x::Mod, y::Mod)
    modcheck(x,y)
    p,flag = Base.mul_with_overflow(x.val,y.val)
    if !flag
        return Mod(x.val*y.val, x.mod)
    end
    p = widemul(x.val, y.val)         # multipy with added precision
    p = mod(p,x.mod)                  # reduce by the modulus
    return Mod(oftype(x.mod,p),x.mod) # return with proper type
end

# Division stuff
"""
`is_invertible(x::Mod)` determines if `x` is invertible.
"""
is_invertible(x::Mod) = return gcd(x.val,x.mod)==1 && x.mod>1

"""
`inv(x::Mod)` gives the multiplicative inverse of `x`.
This may be abbreviated by `x'`.
"""
function inv(x::Mod)
    (g, v, ignore) = gcdx(x.val, x.mod)
    if g != 1 || x.mod==1
        error(x, " is not invertible")
    end
    return Mod(v, x.mod)
end

# Typing shortcut for inv(x)
# adjoint(x::Mod)

function /(x::Mod, y::Mod)
    modcheck(x,y)
    return x * inv(y)
end

function ^(x::Mod, k::Integer)
    if k>0
        return Mod(powermod(x.val, k, x.mod), x.mod)
    end
    if k==0
        T = typeof(x.val)
        return Mod(one(T), x.mod)
    end
    y = inv(x)
    return y^(-k)
end

# Operations with Integers

+(x::Mod, k::Integer) = Mod(k,x.mod)+x
+(k::Integer, x::Mod) = x+k

-(x::Mod, k::Integer) = x + (-k)
-(k::Integer, x::Mod) = (-x) + k

*(x::Mod, k::Integer) = Mod(k,x.mod) * x
*(k::Integer, x::Mod) = x*k

/(x::Mod, k::Integer) = x / Mod(k, x.mod)
/(k::Integer, x::Mod) = Mod(k, x.mod) / x

# Comparison with Integers

isequal(x::Mod, k::Integer) = mod(k,x.mod) == x.val
isequal(k::Integer, x::Mod) = isequal(x,k)
==(x::Mod, k::Integer) = isequal(x,k)
==(k::Integer, x::Mod) = isequal(x,k)

# Chinese remainder theorem functions

# private helper function
function CRT_work(x::Mod, y::Mod)
    n = x.mod
    m = y.mod
    if gcd(n,m) != 1
        error("Moduli must be pairwise relatively prime")
    end

    a = x.val
    b = y.val

    k = inv(Mod(n,m)) * (b-a)

    z = a + k.val*n

    return Mod(z, n*m)
end

# public interface
"""
`CRT(m1,m2,...)`: Chinese Remainder Theorem
```
julia> CRT( Mod(4,11), Mod(8,14) )
Mods.Mod(92,154)

julia> 92%11
4

julia> 92%14
8
```
"""
function CRT(mtuple::Mod...)
    n = length(mtuple)
    if n == 0
        return Mod(1)
    end

    result::Mod = mtuple[1]

    for k=2:n
        result = CRT_work(result,mtuple[k])
    end

    return result
end

show(io::IO, m::Mod) = print(io,"Mod(",m.val,",",m.mod,")")

end # end of module Mods


##
## Angle Conversions
##

const MINUTES_TO_WHOLE = inv(60)
const SECONDS_TO_WHOLE = inv(3600)
const HOURS_PER_DEGREE = 24 / 360
const HOURS_PER_RADIAN = 12 / π
const DEGREES_PER_HOUR = 360 / 24
const RADIANS_PER_HOUR = π / 12

### rad2xxx

"""
    rad2ha(angle)

Convert radians to hour angles
"""
rad2ha(angle) = angle * HOURS_PER_RADIAN

"""
    rad2dms(angle)

Convert radians to (degrees, arcminutes, arcseconds) tuple
"""
rad2dms(angle) = rad2deg(angle) |> deg2dms

"""
    rad2hms(angle)

Convert radians to (hours, minutes, seconds) tuple
"""
rad2hms(angle) = rad2ha(angle) |> ha2hms

### deg2xxx

"""
    deg2ha(angle)

Convert degrees to hour angles
"""
deg2ha(angle) = angle * HOURS_PER_DEGREE

"""
    deg2dms(angle)

Convert degrees to (degrees, arcminutes, arcseconds) tuple
"""
function deg2dms(angle)
    remain_degrees, degrees = modf(angle)
    fraction_arcmin = abs(remain_degrees) * 60
    remain_arcmin, arcmin = modf(fraction_arcmin)
    arcsec = remain_arcmin * 60
    return degrees, arcmin, arcsec
end

"""
    deg2hms(angle)

Convert degrees to (hours, minutes, seconds) tuple
"""
deg2hms(angle) = deg2ha(angle) |> ha2hms

### ha2xxx

"""
    ha2rad(angle)

Convert hour angles to radians
"""
ha2rad(angle) = angle * RADIANS_PER_HOUR

"""
    ha2deg(angle)

Convert hour angles to degrees
"""
ha2deg(angle) = angle * DEGREES_PER_HOUR

"""
    ha2hms(angle)

Convert hour angles to (hours, minutes, seconds) tuple
"""
function ha2hms(angle)
    remain_hours, hours = modf(angle)
    fraction_min = abs(remain_hours) * 60
    remain_min, minutes = modf(fraction_min)
    seconds = remain_min * 60
    return hours, minutes, seconds
end

"""
    ha2dms(angle)

Convert hour angles to (degrees, arcminutes, arcseconds) tuple
"""
ha2dms(angle) = ha2deg(angle) |> deg2dms

### dms2xxx

"""
    dms2deg(degrees, arcmin, arcsec)
    dms2deg(parts)
    dms2deg(input::AbstractString)

Convert (degrees, arcminutes, arcseconds) tuple to degrees. If a string is given, will parse with [`parse_dms`](@ref) first. If an angle is input will treat as a no-op.
"""
function dms2deg(degrees, arcminutes, arcseconds)
    frac = arcminutes * MINUTES_TO_WHOLE + arcseconds * SECONDS_TO_WHOLE
    return signbit(degrees) ? degrees - frac : degrees + frac
end

"""
    dms2rad(degrees, arcmin, arcsec)
    dms2rad(parts)
    dms2rad(input::AbstractString)

Convert (degrees, arcminutes, arcseconds) tuple to radians. If a string is given, will parse with [`parse_dms`](@ref) first. If an angle is input will treat as a no-op.
"""
dms2rad(degrees, arcminutes, arcseconds) = dms2deg(degrees, arcminutes, arcseconds) |> deg2rad

"""
    dms2ha(degrees, arcmin, arcsec)
    dms2ha(parts)
    dms2ha(input::AbstractString)

Convert (degrees, arcminutes, arcseconds) tuple to hour angles. If a string is given, will parse with [`parse_dms`](@ref) first. If an angle is input will treat as a no-op.
"""
dms2ha(degrees, arcminutes, arcseconds) = dms2deg(degrees, arcminutes, arcseconds) |> deg2ha

# code-gen for string inputs and no-ops
for func in (:dms2deg, :dms2rad, :dms2ha)
    @eval $func(input::AbstractString) = parse_dms(input) |> $func
    @eval $func(input::Number) = input
end

### hms2xxx

"""
    hms2ha(hours, mins, secs)
    hms2ha(parts)
    hms2ha(input::AbstractString)

Convert (hours, minutes, seconds) tuple to hour angles. If a string is given, will parse with [`parse_hms`](@ref) first. If an angle is input will treat as a no-op.
"""
function hms2ha(hours, minutes, seconds)
    frac = minutes * MINUTES_TO_WHOLE + seconds * SECONDS_TO_WHOLE
    return signbit(hours) ? hours - frac : hours + frac
end

"""
    hms2deg(hours, mins, secs)
    hms2deg(parts)
    hms2deg(input::AbstractString)

Convert (hours, minutes, seconds) tuple to degrees. If a string is given, will parse with [`parse_hms`](@ref) first. If an angle is input will treat as a no-op.
"""
hms2deg(hours, minutes, seconds) = hms2ha(hours, minutes, seconds) |> ha2deg

"""
    hms2rad(hours, mins, secs)
    hms2rad(parts)
    hms2rad(input::AbstractString)

Convert (hours, minutes, seconds) tuple to radians. If a string is given, will parse with [`parse_hms`](@ref) first. If an angle is input will treat as a no-op.
"""
hms2rad(hours, minutes, seconds) = hms2ha(hours, minutes, seconds) |> ha2rad

# code-gen for string inputs and no-ops
for func in (:hms2deg, :hms2rad, :hms2ha)
    @eval $func(input::AbstractString) = parse_hms(input) |> $func
    @eval $func(input::Number) = input
end

# code-gen for accepting inputs separate or in a collection
for func in (:dms2rad, :dms2deg, :dms2ha, :hms2rad, :hms2deg, :hms2ha)
    @eval $func(parts) = $func(parts...)
end


module Scalings

n_pox_pairs(N) = N*(N-1)/2
export n_pox_pairs

n_pox_dir_links(N) = N*(N-1)
export n_pox_dir_links

"""
Number of links for various network size, in dense regime
"""
denseLScal_DirBin(N, C)  = C * n_pox_dir_links(N)

denseLScal_DirBin(N)  = denseLScal_DirBin(N, 0.2)
export denseLScal_DirBin

"""
Number of links for various network size, in semiDense regime
"""
semiDenseLScal_DirBin(N, C)  = C * n_pox_dir_links(N)/sqrt(N)

semiDenseLScal_DirBin(N)  = semiDenseLScal_DirBin(N, 0.1)
export semiDenseLScal_DirBin

"""
Number of links for various network size, in sparse regime
"""
sparseLScal_DirBin(N, C) =  C * N

sparseLScal_DirBin(N) = sparseLScal_DirBin(N, 4)
export sparseLScal_DirBin

"""
Average number of reciprocal pairs in Erdos Reny Model
 - avgL is the expected number of links, related to the probability p of a single link being present by expL = (N^2-N) * p

"""
erdosRenyRecScal_DirBin(expL, N) = expL^2/(2*(N^2-N))
export erdosRenyRecScal_DirBin

end


using EngEconomics, Roots, Plots

# Given
# Job1 = a
# Job2 = b
# Job3 = c
aInit = 100000
aAnnual = -75000
aFinal = 200000

bInit = 150000
bAnnual = -100000
bFinal = 230000

cInit = 175000
cAnnual = -150000
cFinal = 300000

MARR = 0.1
N = 3

# Find Best alternative from IRR analysis
# Step 1: Sort from smallest to biggest initial
# - X, c, b, a (where X is do nothing)
ΔCX_Init = cInit
ΔCX_Annual = cAnnual
ΔCX_Final = cFinal
ΔCX_IRR_eq(x) = ΔCX_Init * compoundAmountFactor(MARR, N) + ΔCX_Annual * uniformSeriesCompoundAmoundFactor(x, N) + ΔCX_Final

xVec = collect(0:0.01:1)
yVec = zeros(size(xVec))
plot(xVec, ΔCX_IRR_eq.(xVec))
plot!(xVec, yVec)

ΔCX_IRR = find_zero(ΔCX_IRR_eq, MARR)

if ΔCX_IRR >= MARR
	println("C")
else
	println("Do Nothing")
end

# BC
ΔBC_Init = bInit - cInit
ΔBC_Annual = bAnnual - cAnnual
ΔBC_Final = bFinal - cFinal
ΔBC_IRR_eq(x) = ΔBC_Init * compoundAmountFactor(x, N) + ΔBC_Annual * uniformSeriesCompoundAmoundFactor(MARR, N) + ΔBC_Final

plot(xVec, ΔBC_IRR_eq.(xVec))
plot!(xVec, yVec)

ΔBC_IRR = find_zero(ΔBC_IRR_eq, MARR)

if ΔBC_IRR >= MARR
	println("B")
else
	println("C")
end

# AB
ΔAB_Init = aInit - bInit
ΔAB_Annual1 = aAnnual - bAnnual
ΔAB_Final = aFinal - bFinal
ΔAB_IRR_eq(x) = ΔAB_Init * compoundAmountFactor(x, N) + ΔAB_Annual1 * uniformSeriesCompoundAmoundFactor(MARR, N) + ΔAB_Final

plot(xVec, ΔAB_IRR_eq.(xVec))
plot!(xVec, yVec)

ΔAB_IRR = find_zero(ΔAB_IRR_eq, MARR)

if ΔAB_IRR >= MARR
	println("A")
else
	println("B")
end

# If non-uniform cash flows, then use ERR
# - ERR means that if it's not an investment use the MARR rather than x
# - what this really means is, if \Delta_Job1_Job2_THINGY is negative, use x, because this is an investment so we use x
# - if \Delta_[]_[] is positive, then it is NOT an investment, so we ought to use MARR rather than x


"""
    make_sin(n, start, stop; noise = 0.3, f_rand = randn) -> x, y

Generates `n` noisy equally spaces samples of a sinus from `start` to `stop`
by adding `noise .* f_rand(length(x))` to the result of `fun(x)`.
"""
function make_sin(n::Int = 50, start::Real = 0, stop::Real = 2π; noise::Real = 0.3, f_rand::Function = randn)
    x = collect(range(start, stop=stop, length=n))
    y = sin.(x) .+ noise  .* f_rand.()
    return x, y
end

"""
    make_poly(coef, x; noise = 0.01, f_rand = randn) -> x, y

Generates a noisy response for a polynomial of degree `length(coef)`
using the vector `x` as input and adding `noise .* f_randn(length(x))` to the result.
The vector `coef` contains the coefficients for the terms of the polynome.
The first element of `coef` denotes the coefficient for the term with
the highest degree, while the last element of `coef` denotes the intercept.
"""
function make_poly(coef::AbstractVector{R}, x::AbstractVector{T};
                noise::Real = 0.1, f_rand::Function = randn) where {T<:Real,R<:Real}
    n = length(x)
    m = length(coef)
    x_vec = collect(x)
    y = zeros(n)
    @inbounds for i = 1:n
        for k = 1:m
            y[i] += coef[k] * x_vec[i]^(m-k)
        end
    end
    y .+= noise .* f_rand.()
    x_vec, y
end

"""
    make_spiral(n, a, theta, b; noise = 0.01, f_rand = randn) -> x, y

Generates `n` noisy responses for a spiral with two labels. Uses the radius, angle
and scaling arguments to space the points in 2D space and adding `noise .* f_randn(n)`
to the response.
"""
function make_spiral(n::Int = 97, a::Real = 6.5, theta::Real = 16.0, b::Real=104.0;
                noise::Real = 0.1, f_rand::Function = randn)
    x = zeros(Float64, (2, 2*n))
    y = zeros(Int, 2*n)
    index = 0:1.0:(n-1)
    for i = 1:n
        _angle = index[i]*pi/theta
    	_radius = a * (b-index[i]) / b
    	x_coord = _radius * sin(_angle)
    	y_coord = _radius * cos(_angle)
    	x[1, i] = x_coord
    	x[2, i] = y_coord
        x[1, n+i] = -(x_coord)
    	x[2, n+i] = -(y_coord)
        y[i] = 1
        y[n+i] = 0
    end
    x[1, :] .+= noise .* f_rand.()
    x[2, :] .+= noise .* f_rand.()
    x, y
end


using Distributions

"""
Example of a computation of beta credible interval with MC sampling.
"""
function mcBetaQuantileDemo()
    S = 47
    N = 100
    a = S+1
    b = (N-S)+1
    α = .05

    SS = 1000
    X = rand(Beta(a, b), SS)
    sort!(X)

    Xl = X[round(Int, SS*α/2)]
    Xu = X[round(Int, SS*(1-α/2))]
    CIhat = [Xl, Xu]

    CIhat2 = quantile(EmpiricalUnivariateDistribution(X), [α/2, 1-α/2])
    (CIhat, CIhat2)
end

"""
Example of a computation of normal credible interval with MC sampling.
"""
function mcNormalQuantileDemo()
    μ = 0
    Σ = 1
    S = 1000
    rn = Normal(μ, Σ)
    X = rand(rn, S)
    qs = [0.025 0.5 0.975]

    qexact = quantile(rn, qs)

    qapprox = quantile(EmpiricalUnivariateDistribution(X), qs)
end


#= -------------------------------------------------------------------------
# @file problem1.jl
#
# @date 12/22/15 21:56:50
# @author Martin Noblia
# @email martin.noblia@openmailbox.org
#
# @brief
#
# @detail
#
  Licence:
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License

---------------------------------------------------------------------------=#
# Enun
#=
If we list all the natural numbers below 10 that are multiples of 3 or 5,
we get 3, 5, 6 and 9. The sum of these multiples is 23.
Find the sum of all the multiples of 3 or 5 below 1000.
=#

"""
Function for collect the integers multiples of some integer
Input:
N(Int) Maximum integer
n(Int) integer multiple
Output:
collection of integers multiples of n
"""
mult_of(N::Int,n::Int) = find(collect(1:N) .% n .== 0)
s = sum(unique(vcat(mult_of(999, 3), mult_of(999, 5))));
print("The sum of all the multiples of 3 or 5 bellow 1000 is: $s")


#
# count the number of surreals created by generation n
#    https://oeis.org/A174972
#

function a(n::Integer)
    a = zero(n)
    for i=1:2^n
        a += i * binomial(2^n - 1, i-1)
    end
    return a
end

# 8 is as high as OEIS goes
# approximately doubles the number of digits each time
for n=zero(BigInt) : 9
    println("  n = $n, a(n) = ", a(n), ", approx(a(n)) = ", float(a(n)))
end


# julia1_2.3.jl Primitive, Abstract and Composite Types
#
# Primitive Types
# Integer Types with maxnumber
println(Int8(127))
println(UInt8(255))
println(Int16(32767))
println(UInt16(65535))
println(Int32(2147483647))
println(UInt32(4294067295))
println(Int64(2)^63-1)
println(UInt64(2)^64-1)
#  Int is default for Int64 if a 64 bit CPU else 32
# UInt is default for UInt64 if a 64 bit CPU else 32
println(Int128(2)^127-1)
println(UInt128(2)^128-1)
println(BigInt(2)^2046-1)

# floating point Types and irrational numbers
# Float16 to Float64 and BigFloat
println(Float16(π))  # 11 bits of precision
println(Float32(π))  # 24 bits of precision
println(Float64(π))  # 53 bits of precision
println(BigFloat(π)) # 256 bits of precision - normally
precision(BigFloat)
println(π)           # irrational number cannot be represented as a rational
BigFloat(π)          #Bigfloat defaults to 256 binary places ~77 decimal places
BigFloat(π,500)      # 500+ bits of precision

# complex numbers
println(Complex{Float16}(π+2π*im)) # 11 bits precision each part
println(Complex{Float32}(π+2π*im)) # 24 bits precision each part
println(Complex{Float64}(π+2π*im)) # 53 bits precision each part
# full gamut of elementary functions work on complex numbers
# Complex shortcuts
ComplexF16(3.1+3.2im)  # Alias for Complex{Float16}()
ComplexF32(3.1+3.2im)  # Alias for Complex{Float32}()
ComplexF64(3.1+3.2im)  # Alias for Complex{Float64}()
Complex(3.1+3.2im)     #default for ComplexF64 if 64 bit CPU else ComplexF32

# Rational numbers
# used for exact arithmetic
# for example 1/3 cannot be represented exactly by any
#  floating point number.
# 1//3 represents the rational number 1/3 exactly
a = 1//3 + 2//5 - 3//7
b = (22//7)*(5//13)
c = ((22//7)/2)^2
print("a=$a, b=$b, c=$c")
Rational(3.875) # Make a rational fraction out of a Float
Rational(3.825) # a repeating binary pattern so not exact, use rationalize() function
BigFloat(3.825) # gives wrong precision due to prior conversion step
BigFloat(3825//1000) # or
BigFloat("3.825")


module Test_bouncingBall_with_measurement_analytic

using Measurements

const g  = 9.81
const e  = 0.7 ± 0.1
const h0 = 1.0 ± 0.2

#=
der(h) = v  ; h(0) = h0
der(v) = -g ; v(0) = 0

when h <= 0 then
	v = -e*v
end when

Analytic solution of differential equation with
h(t_ev) = h_ev, v(t_ev) = v_ev:

	v = -g*(t-t_ev) + v_ev
	h = -g/2*(t-t_ev)^2 + v_ev*(t-t_ev) + h_ev

First phase (t_ev = 0, v_ev=0, h_ev=h0)
	v = -g*t
	h = -g/2*t^2 + h0

First event:
	hev1 = 0 -> t_ev1 = sqrt(2*h0/g)
	            v_ev1_before = -g*t_ev1
				v_ev1_after  = -e*v_ev1_before

Second phase (t_ev = t_ev1, v_ev = v_ev1_after, h_ev = 0)
    v = -g*(t-t_ev1) + v_ev1_after
    h = -g/2*(t-t_ev1)^2 + v_ev1_after*(t-t_ev1)

Second event:
	h_ev2 = 0 = -g/2*(t-t_ev1)^2 + vev1_after*(t-tev_1)
	          = (t-t_ev1)*(-g/2*(t-t_ev1) + v_ev1_after)

	-> t_ev2-t_ev1 = 2*v_ev1_after/g

	v_ev2_before = -g*(t_ev2-t_ev1) + v_ev1_after
	             = -g*2*v_ev1_after/g + v_ev1_after
	   			 = -v_ev1_after
	v_ev2_after  = -e*v_ev2_before
=#

t_ev1        = sqrt(2*h0/g)
h_ev1        = -g/2*t_ev1^2 + h0
v_ev1_before = -g*t_ev1
v_ev1_after  = -e*v_ev1_before

t_ev2        = t_ev1 +2*v_ev1_after/g
h_ev2        = -g/2*(t_ev2-t_ev1)^2 + v_ev1_after*(t_ev2-t_ev1)
v_ev2_before = -v_ev1_after
v_ev2_after  = -e*v_ev2_before

@show t_ev1
@show h_ev1
@show v_ev1_before
@show v_ev1_after
@show t_ev2
@show h_ev2
@show v_ev2_before
@show v_ev2_after

# Alternative that computes t_ev1 with Roots.jl:

println("\n using Roots:")

using Roots
f(t)  = -g/2*t^2 + h0
t_ev1 = find_zero(f,(0.1±0.0, 0.5±0.0))
h_ev1 = f(t_ev1)
@show t_ev1, h_ev1
end


using SpecialFunctions, Plots, HCubature, LinearAlgebra, Random

function n_ball_volume_analytic(n_dims, r)
    V = π^(n_dims/2)*r^n_dims/gamma(n_dims/2 + 1)
    return V
end

function monte_carlo_sphere_test(n_samples, r, n_dims)
    #draw uniformly random points in n_space and see if distance to
    #origin is <= r
    Random.seed!(0)
    inside_ball = 0
    for i = 1 : n_samples
        if norm(rand(n_dims)) <= r
            inside_ball += 1
        end
    end
    ratio = inside_ball/n_samples
    volume = ratio*(2*r)^n_dims
    return volume
end

function cubature_sphere_volume(n_samples, r, n_dims)
    a = -ones(n_dims-1) .+ eps()
    b = ones(n_dims-1) .- eps()
    function f(x)
        inside_bit = r^2 - sum(x.^2)
        if inside_bit <= 0
            return 0.0
        else
            return sqrt(r^2 - sum(x.^2))
        end
    end
    I, e = hcubature(f, a, b, maxevals = n_samples)
    return 2*I
end

r = 1.0
n_dims = 2:15

V = n_ball_volume_analytic.(n_dims, r)

p1 = plot(n_dims, V, xlabel = "dimension", ylabel = "volume", label = "analytic")

n_samples = Int(1e6)

V_mc = monte_carlo_sphere_test.(n_samples, r, n_dims)

plot!(p1, n_dims, V_mc, xlabel = "dimension", ylabel = "volume", label = "mc")

V_cubature = cubature_sphere_volume.(n_samples, r, n_dims)

plot!(p1, n_dims, V_cubature, ylim = [0, 6], xlabel = "dimension", ylabel = "volume", label = "cubature")

p2 = plot(n_dims, abs.(V_mc .- V)./V, yaxis = :log, xlabel = "dimension", ylabel = "relative abs error", label = "mc")

plot!(p2, n_dims, abs.(V_cubature .- V)./V, yaxis = :log, xlabel = "dimension", ylabel = "relative abs error", label = "cubature")

plot(p1, p2)


# Load Julia packages (libraries) needed

using StatisticalRethinking, Optim
#gr(size=(600,600));

# ### snippet 2.6

# Grid of 1001 steps

p_grid = range(0, step=0.001, stop=1);

# all priors = 1.0

prior = ones(length(p_grid));

# Binomial pdf

likelihood = [pdf(Binomial(9, p), 6) for p in p_grid];

# As Uniform prior has been used, unstandardized posterior is equal to likelihood

posterior = likelihood .* prior;

# Scale posterior such that they become probabilities

posterior = posterior / sum(posterior);

# ### snippet 3.3

# Sample using the computed posterior values as weights

N = 10000
samples = sample(p_grid, Weights(posterior), N);

# In StatisticalRethinkingJulia samples will always be stored
# in an MCMCChains.Chains object.

chn = MCMCChains.Chains(reshape(samples, N, 1, 1), ["toss"]);

# Describe the chain

MCMCChains.describe(chn)

# Plot the chain

plot(chn)

# Compute the MAP (maximum_a_posteriori) estimate

x0 = [0.5]
lower = [0.0]
upper = [1.0]

function loglik(x)
  ll = 0.0
  ll += log.(pdf.(Beta(1, 1), x[1]))
  ll += sum(log.(pdf.(Binomial(9, x[1]), repeat([6], 1))))
  -ll
end

(qmap, opt) = quap(loglik, x0, lower, upper)

# Show optimization results

opt

# Fit quadratic approcimation

quapfit = [qmap[1], std(samples, mean=qmap[1])]

p = Vector{Plots.Plot{Plots.GRBackend}}(undef, 4)
p[1] = scatter(1:length(p_grid), samples, markersize = 2, ylim=(0.0, 1.3), lab="Draws")

# analytical calculation

w = 6
n = 9
x = 0:0.01:1
p[2] = plot( x, pdf.(Beta( w+1 , n-w+1 ) , x ), lab="Conjugate solution")
density!(p[2], samples, lab="Sample density")

# quadratic approximation

p[3] = plot( x, pdf.(Beta( w+1 , n-w+1 ) , x ), lab="Conjugate solution")
plot!( p[3], x, pdf.(Normal( quapfit[1], quapfit[2] ) , x ), lab="Quap approximation")

# ### snippet 2.7

w = 6; n = 9; x = 0:0.01:1
p[4] = plot( x, pdf.(Beta( w+1 , n-w+1 ) , x ), lab="Conjugate solution")
plot!(p[4], x, pdf.(Normal( 0.67 , 0.16 ) , x ), lab="Normal approximation")
plot(p..., layout=(2, 2))

# End of `02/clip-06-07.jl`


using FdeSolver
using Plots
using SpecialFunctions

## inputs
tSpan = [0, 1]     # [intial time, final time]
y0 = 0             # intial value
β = 0.9            # order of the derivative

# Equation
F(t, n, β, y) = (40320 ./ gamma(9 - β) .* t[n] .^ (8 - β) .- 3 .* gamma(5 + β / 2)
           ./ gamma(5 - β / 2) .* t[n] .^ (4 - β / 2) .+ 9/4 * gamma(β + 1) .+
           (3 / 2 .* t[n] .^ (β / 2) .- t[n] .^ 4) .^ 3 .- y[n] .^ (3 / 2))

# Jacobian
JacobF(t, n, β, y) = -(3 / 2) .* y[n] .^ (1 / 2)

## Numerical solution
t, Yapp = FDEsolver(F, tSpan, y0, β, nothing, StopIt = "Convergence", tol = 10e-8, itmax = 15)

t1, Yapp1 = FDEsolver(F, tSpan, y0, β, JacobF, StopIt = "Convergence", tol = 10e-8, itmax = 15)

#plot
plot(t, Yapp, linewidth = 5, title = "Solution of a 1D fractional IVP",
     xaxis = "Time (t)", yaxis = "y(t)", label = "Approximation")
plot!(t1, Yapp1, linewidth = 5, ls = :dot, label = "Approximation with Jacobian")
plot!(t, t -> (t.^8 - 3 * t .^ (4 + β / 2) + 9 / 4 * t.^β),
      lw = 3, ls = :dash, label = "Exact solution")


"""
    dircos(incl, decl)

Compute the three cartesian components of the unit vector aligned with a field vector defined by the two angles inclination `incl` and declination `decl`.

# Example
```julia-repl
julia> dircos(56.0, 0.0)
(0.5591929034707468, 0.0, 0.8290375725550417)
```
"""
function dircos(incl::T, decl::T) where T<:Real
    xincl = incl * pi / 180.0
    xdecl = decl * pi / 180.0
    a = cos(xincl) * cos(xdecl)
    b = cos(xincl) * sin(xdecl)
    c = sin(xincl)
    return a, b, c
end


using NumberIntervals, Turmeric, StaticArrays

rts = roots(sin, NumberInterval(-5, 5))

rts = roots(sin, NumberInterval(-5, 6), Bisection())
rts = roots(sin, NumberInterval(-5, 6), Newton())

# 2D:
f(x, y) = SVector(x^2 + y^2 - 1, y - x)
f(X) = f(X...)

rts = roots(f, SVector(NumberInterval(-5, 5), NumberInterval(-5, 5)))

# complex:
x = NumberInterval(-5, 6)
Xc = Complex(x, x)
f(z) = z^3 - 1

rts = roots(f, Xc, Bisection())
rts = roots(f, Xc, Newton())
rts = roots(f, Xc)

# From R docs:

# https://www.rdocumentation.org/packages/pracma/versions/1.9.9/topics/broyden

function g(x)
    (x1, x2, x3) = x
    SVector(    x1^2 + x2^2 + x3^2 - 1,
                x1^2 + x3^2 - 0.25,
                x1^2 + x2^2 - 4x3
            )
end

X = NumberInterval(-5, 5)
@time rts = roots(g, SVector(X, X, X))

h(xv) = ((x,y) = xv; SVector(2*x - y - exp(-x), -x + 2*y - exp(-y)))

rts = roots(h, SVector(X, X), Bisection())

# Dennis-Schnabel:
h(xv) = ((x, y) = xv; SVector(x^2 + y^2 - 2, exp(x - 1) + y^3 - 2))

rts = roots(h, SVector(X, X), Bisection())

# Test suites:

# http://folk.uib.no/ssu029/Pdf_file/Testproblems/testprobRheinboldt03.pdf

# http://www.mat.univie.ac.at/~neum/glopt/test.html

# http://titan.princeton.edu/TestProblems/

# http://www-sop.inria.fr/saga/POL/

##  MINPACK benchmarks: https://github.com/JuliaNLSolvers/NLsolve.jl/blob/master/test/minpack.jl

rosenbrock(x, y) = SVector( 1 - x, 10 * (y - x^2) )
rosenbrock(X) = rosenbrock(X...)

rosenbrock(xx) = ( (x, y) = xx; SVector( 1 - x, 1000 * (y - x^2) ) )
X = NumberInterval(-1e5, 1e5)

rts = roots(rosenbrock, SVector(X, X))

# and other files in NLsolve test suite

# Testing unconstrained optimization software, Moré, Garbow, Hillstrom ACM Trans. Math. Soft. 7 (1), 17-41 (1981)


#osaka.jl

"""
    Implementation of Osaka's extended treatment of Feynman's variational technique applied to the Polaron model; generalising it from the case at O^{∘}K to the case at finite temperature.

    See Osaka 1959:
    Progress of Theoretical Physics, Vol. 22, No.3, September 1959

    And Hellwarth 1999:
    https://doi.org/10.1103/PhysRevB.60.299

"""

using Optim
using QuadGK

# Equation 62b in Hellwarth. Equation 20 in Osaka.
A(v, w, β) = 3 / β * (log(v / w) - 1 / 2 * log(2 * π * β) - log(sinh(v * β / 2) / sinh(w * β / 2)))

# Equation 62d in Hellwarth.
Y(x, v, β) = 1 / (1 - exp(-v * β)) * (1 + exp(-v * β) - exp(-v * x) - exp(v * (x - β)))

# Osaka's version: Within Equation 16.
# Y(x, v, β) = 1 - exp(-v * x) + (1 - coth(v * β / 2)) * (cosh(v * x) - 1)

# Integrand of Equation 62c in Hellwarth.
B_integrand(x, v, w, β) =  (exp(β - x) + exp(x)) / sqrt(w^2 * x * (1 - x / β) + Y(x, v, β) * (v^2 - w^2) / v)

# Osaka's version: Within Equation 16.
# B_integrand(x, v, w, β) =  (exp(-x)) / sqrt(abs(w^2 * x * (1 - x / β) + Y(x, v, β) * (v^2 - w^2) / v))

# Equation 62c. NB: Extra exp(β) correction term from Osaka to correct Hellwarth typo.
B(v, w, β, α) = α * v / (sqrt(π) * (exp(β) - 1)) * QuadGK.quadgk(x -> B_integrand(x, v, w, β), 0, β / 2)[1]

# Osaka's version: Equation 16.
# B(v, w, β, α) = α * v * exp(β) / (sqrt(π) * (exp(β) - 1)) * QuadGK.quadgk(x -> B_integrand(x, v, w, β), 0, β)[1]

# Equation 62e in Hellwarth. Equation 17 in Osaka.
C(v, w, β) = 3 / 4 * (v^2 - w^2) / v * (coth(v * β / 2) - 2 / (v * β))

# Equation 62a in Hellwarth. In paragraph below Equation 22 in Osaka; has extra 1/β due to different definition of A, B & C.
osaka_free_energy(v, w, β, α) = -(A(v, w, β) + B(v, w, β, α) + C(v, w, β))


function extendedEuclideanAlgorithm(a, b)
    if b == 0
        return (a, 1, 0)
    end
    gcd, x1, y1 = extendedEuclideanAlgorithm(b, a % b)
    x = y1
    y = x1 - floor(a / b) * y1
    return gcd, x, y
end


function TT_SVD(tensor::Array{Float64},ϵ::Float64)
########################################################################
#   Computes the cores of a TT for the given tensor and accuracy (acc)
#   Resources:
#   V. Oseledets: Tensor-Train Decomposition, 2011, p.2301: Algorithm 1
#   April 2021, Clara Menzen
########################################################################
    N = ndims(tensor);
    cores = Vector{Array{Float64,3}}(undef,N);
    frobnorm = norm(tensor);

    δ = ϵ / sqrt(N-1) * frobnorm;
    err2 = 0;
    rprev = 1;
    sizes = size(tensor);
    C = reshape( tensor, (sizes[1], Int(length(tensor) / sizes[1]) ));
    for k = 1 : N-1
        # truncated svd
        F   = svd!(C);
        rcurr = length(F.S);

        sv2 = cumsum(reverse(F.S).^2);
        tr  = Int(findfirst(sv2 .> δ^2))-1;
        if tr > 0
            rcurr = length(F.S) - tr;
            err2 += sv2[tr];
        end

        # new core
        cores[k] = reshape(F.U[:,1:rcurr],(rprev,sizes[k],rcurr));
        rprev    = rcurr;
        C        = Diagonal(F.S[1:rcurr])*F.Vt[1:rcurr,:];
        C        = reshape(C,(rcurr*sizes[k+1], Int(length(C) / (rcurr*sizes[k+1])) ) );
    end
    cores[N] = reshape(C,(rprev,sizes[N],1));
    return MPT(cores), sqrt(err2)/frobnorm
end

function TT_SVD(tensor::Array{Float64},d::Int,ϵ::Float64)
    ########################################################################
    # for a symmetric matrix
        cores    = Vector{Array{Float64,3}}(undef,d);
        S        = zeros(2)
        frobnorm = norm(tensor);

        δ     = ϵ / sqrt(d-1) * frobnorm;
        err2  = 0;
        rprev = 1;
        sizes = size(tensor);
        C     = reshape( tensor, (sizes[1], Int(length(tensor) / sizes[1]) ));
        for k = 1 : d
            # truncated svd
            F   = svd!(C);
            rcurr = length(F.S);

            sv2 = cumsum(reverse(F.S).^2);
            tr  = Int(findfirst(sv2 .> δ^2))-1;
            if tr > 0
                rcurr = length(F.S) - tr;
                err2 += sv2[tr];
            end

            # new core

            cores[k] = reshape(F.U[:,1:rcurr],(rprev,sizes[k],rcurr));
            rprev    = rcurr;
            C        = Diagonal(F.S[1:rcurr])*F.Vt[1:rcurr,:];
            C        = reshape(C,(rcurr*sizes[k+1], Int(length(C) / (rcurr*sizes[k+1])) ) );
            S        = F.S[1:rcurr]
        end
        return MPT(cores), S, sqrt(err2)/frobnorm
    end


"""
    poissonfd(f,g,m,xspan,n,yspan)

Solve Poisson's equation on a rectangle by finite differences.
Function `f` is the forcing function and function `g` gives the
Dirichlet boundary condition. The rectangle is the tensor product of
intervals `xspan` and `yspan`,  and the discretization uses `m`+1
and `n`+1 points in the two coordinates.

Returns vectors defining the grid and a matrix of grid solution values.
"""
function poissonfd(f,g,m,xspan,n,yspan)
    # Discretize the domain.
    x,Dx,Dxx = FNC.diffmat2(m,xspan)
    y,Dy,Dyy = FNC.diffmat2(n,yspan)
    mtx = h -> [ h(x,y) for x in x, y in y ]
    X,Y = mtx((x,y)->x),mtx((x,y)->y)
    N = (m+1)*(n+1)   # total number of unknowns

    # Form the collocated PDE as a linear system.
    A = kron(I(n+1),sparse(Dxx)) + kron(sparse(Dyy),I(m+1))
    b = vec( mtx(f) )

    # Identify boundary locations.
    isboundary = trues(m+1,n+1)
    isboundary[2:m,2:n] .= false
    idx = vec(isboundary)

    # Apply Dirichlet condition.
    scale = maximum(abs,A[n+2,:])
    A[idx,:] = scale * I(N)[idx,:]        # Dirichet assignment
    b[idx] = scale * g.(X[idx],Y[idx])    # assigned values

    # Solve the linear sytem and reshape the output.
    u = A\b
    U = reshape(u,m+1,n+1)
    return x,y,U
end

"""
    elliptic(ϕ,g,m,xspan,n,yspan)

Solve the elliptic PDE `ϕ`(x,y,u,u_x,u_xx,u_y,u_yy)=0 on the
rectangle `xspan` x `yspan`, subject to `g`(x,y)=0 on the boundary.
Uses `m`+1 points in x by `n`+1 points in y in a Chebyshev
discretization.

Returns vectors defining the grid and a matrix of grid solution values.
"""
function elliptic(ϕ,g,m,xspan,n,yspan)
    # Discretize the domain.
    x,Dx,Dxx = diffcheb(m,xspan)
    y,Dy,Dyy = diffcheb(n,yspan)
    X = [ x for x in x, y in y ]
    Y = [ y for x in x, y in y ]
    unvec = u -> reshape(u,m+1,n+1)

    # Identify boundary locations and evaluate the boundary condition.
    isboundary = trues(m+1,n+1)
    isboundary[2:m,2:n] .= false
    idx = vec(isboundary)
    gb = g.(X[idx],Y[idx])

    # Evaluate the discretized PDE and its Jacobian, with all the
    # boundary condition modifications applied.
    function residual(u)
        U = unvec(u)
        R = ϕ(X,Y,U,Dx*U,Dxx*U,U*Dy',U*Dyy')
        @. R[idx] = u[idx] - gb
        return vec(R)
    end

    # Solve the equation.
    u = levenberg(residual,vec(zeros(size(X))))[end]
    U = unvec(u)

    return function (ξ,η)
		v = [ chebinterp(x,u,ξ) for u in eachcol(U) ]
		return chebinterp(y,v,η)
    end

end

"Evaluate Chebyshev interpolant with nodes x, values v, at point ξ"
function chebinterp(x,v,ξ)
    n = length(x)-1
    w = (-1.0).^(0:n)
    w[[1,n+1]] .*= 0.5

	terms = @. w / (ξ - x)
	if any(isinf.(terms))     # exactly at a node
		idx = findfirst(ξ.==x)
		return v[idx]
	else
		return sum(v.*terms) / sum(terms)
	end
end


function gauleg(a,b,n)
#==
    1D Gauss-Legendre quadrature rule.

    quadrule = gauleg(a,b,n) computes the n-point Gauss-Legendre
    quadrature rule on the interval [a,b] up to mashine precision.

    Note that all quadrature rules obtained from gauleg are of order 2*n-1.

   The output named tuple quadrule contains the following fields:
    w n-by-1 vector specifying the weights of the quadrature rule.
    X n-by-1 vector specifying the abscissae of the quadrature rule.

   Example:
    quad = gauleg(0,1,10);
    quad.x
    quad.w
=#
    if n < 0
        throw(DomainError(n, "Input n must be a non-negative integer"))
    elseif n==1
        x = 0; w = 2;
    else
        d = zeros(n)
        c = zeros(n-1)
        for i in 1:(n-1)
            c[i]=i/sqrt(4*i*i-1)
        end
        J=LinearAlgebra.SymTridiagonal(d,c)
        x=LinearAlgebra.eigvals(J)
        vec=LinearAlgebra.eigvecs(J)
        w=2*vec[1,:].*vec[1,:]

    end

    # The above rule computed a quadrature rule for the reference interval.
    # The reference interval is always [-1,1], and if [a,b] != [-1,1] then we
    # re-map the abscissas (x) and rescale the weights.

    xm = (b+a)/2; # midpoint
    xl = (b-a)/2; # area of the requested interval / area of the reference interval

    x = xm .+ xl*x;
    w = w * xl;

    return (x=x,w=w) #named tuple
end


using ProgressBars, Plots, StatsBase, LaTeXStrings, JLD, Distributions, StatsPlots

Plots.default(titlefontsize=12, tickfontsize=10, labelfontsize=16,
    fontfamily="Computer Modern")

function UniformRand(;N,a,c,m)
    seed = rand(1:1000)

    series = [seed]
    for n ∈ 1:N+1
        randvalue = (a*series[end] + c) % m
        push!(series,randvalue)
    end
    return series[3:end]
end

# function reshuffle(parameters)
#     return round.(Int,baserand(;parameters...) * (parameters[:N]-1) / parameters[:m] .+ 1)
# end

function FloatUniformRand(N)
    parameters = Dict(:N => N, :a => 214013, :c => 2531011, :m => 2^32)
    return UniformRand(;parameters...) / parameters[:m]
end

function GaussRandDist(σ, μ, N)
    x₁ = FloatUniformRand(round(Int,N/2))
    x₂ = FloatUniformRand(round(Int,N/2))

    ρ = σ * .√(2 * log.(1 ./ (1 .- x₁)))
    θ = 2π * x₂

    y₁ = ρ .* sin.(θ) .+ μ
    y₂ = ρ .* cos.(θ) .+ μ

    return [y₁; y₂]
end

Data = [GaussRandDist(1,3,1000000),
            GaussRandDist(2,6,1000000),
                GaussRandDist(3,9,1000000),
                    GaussRandDist(4,12,1000000),]

cd(dirname(@__FILE__))

save("../../Data/Q2/Q2-samples.jld","Samples", Data)

colors = [:steelblue, :purple, :green, :brown]
histogram(Data[1],  bins = 25,
    label = L"Generated\ Normal:\ \mu =3 , \sigma =1",
    alpha = 0.5, c = colors[1], normalize = true)
plot!(Normal(3,1), c = :black, label = L"Normal Curve:\ \mathcal{N}(\mu =3 , \sigma =1)")

histogram!(Data[2], bins = 50,
    label = L"Generated\ Normal:\ \mu =6 , \sigma =2",
    alpha = 0.5, c = colors[2], normalize = true)
plot!(Normal(6,2), c = :black, label = L"Normal Curve:\ \mathcal{N}(\mu =6 , \sigma =2)")

histogram!(Data[3], bins = 75,
    label = L"Generated\ Normal:\ \mu =9 , \sigma =3",
    alpha = 0.5, c = colors[3], normalize = true)
plot!(Normal(9,3), c = :black, label = L"Normal Curve:\ \mathcal{N}(\mu =9 , \sigma =3)")

histogram!(Data[4], bins = 100,
    label = L"Generated\ Normal:\ \mu =12 , \sigma =4",
    alpha = 0.5, c = colors[4], normalize = true)
plot!(Normal(12,4), c = :black, label = L"Normal Curve:\ \mathcal{N}(\mu =12 , \sigma =4)")
plot!(title = L"Normalized\ Distribution\ of\ Samples\ (10^6\ sample\ for\ each)",frame = :box)

savefig("../../Figs/Q2/Q2-Hist.pdf")


# Michelle Medina
# DATE

using PyPlot

						# 1D TESTS
# ---------------------------------------------------------
p=Params(101,1.0)
dX(p)

#101 points and identity map gives the coordinates
x = functionOnGrid(x->x,Params(100,2));

sinx=functionOnGrid(x->sin(pi*x),Params(100,2));
plot(x,sinx)
show()

					# 1D Convergence Test
H = []
E = []

for N in 50:200
    grid = Params(N,4)
    u = functionOnGrid(x->sin(x),grid)
	#known derivative of sin(x)
    dUdx = functionOnGrid(x->cos(x),grid)

    #L infinity norm: difference between the numerical derivative and
    #the analytical one, while taking the maximum of it's abs. value
    error = maximum(abs.(Deriv1D(u,grid) - dUdx))

    h = dX(grid)
    #append h to the array H and error to the array E
    append!(H,h)
    append!(E,error)

end

#plotting error vs h
loglog(H,E,"r-")

#first order convergence
loglog(H,H,"g--",label="1")

#second order convergence
loglog(H,H.^2,"b--",label="2")
xlabel("h")
ylabel("L Infinity Norm")
legend(loc="best",frameon=false)
show()

# USING L2 NORM
H = []
E = []

for N in 50:200
    grid = Params(N,4)
    u = functionOnGrid(x->sin(x),grid)
    dUdx = functionOnGrid(x->cos(x),grid) #known derivative of sin(x)
    derivative = Deriv1D(u,grid)

    #L 2 norm: sum of all the differences between the numerical derivative and
    #the analytical one squared and then take the square root
    error = sqrt(sum((derivative[2:end-1] - dUdx[2:end-1]).^2))

    h = dX(grid)
    #append h to the array H and error to the array E
    append!(H,h)
    append!(E,error)

end

#plotting error vs h
loglog(H,E,"r-")

#first order convergence
loglog(H,H,"g--",label="1")

#second order convergence
loglog(H,H.^2,"b--",label="2")
xlabel("h")
ylabel("L 2 Norm")
legend(loc="best",frameon=false)
show()

						# 2D TESTS
# ---------------------------------------------------------

sinxcosy = functionOnGrid((x,y)->sin(x)*cos(y), Params2D(100,200,10.0,10.0));

contourf(sinxcosy)
colorbar()

x2y2= functionOnGrid((x,y)->x^2+y^2,Params2D(10,10,1.0,1.0));

contourf(x2y2)
colorbar()

cosxcosy = DerivX_2D(sinxcosy,Params2D(100,100,1.0,1.0));

					# 2D Convergence Test
# Norm for X ; f(x) = sin(pi*x)*cos(pi*y)
Hx = []
Ex = []
Hy = []
Ey = []

for Nx in 50:100, Ny in 50:100
	grid = Params2D(Nx,Ny,4,4)
	u = functionOnGrid((x,y)->sin(pi*x)*cos(pi*y),grid)

	#analytical derivatives
	dUdx = functionOnGrid((x,y)->pi*cos(pi*x)*cos(pi*y),grid)
	dUdy = functionOnGrid((x,y)->sin(pi*x)*(-pi)*sin(pi*y),grid)
	errorx = maximum(abs.(DerivX_2D(u,grid)-dUdx))
	errory = maximum(abs.(DerivY_2D(u,grid)-dUdy))
	hx=dX_2D(grid)
	hy=dY_2D(grid)
	append!(Hx,hx)
	append!(Ex,errorx)
	append!(Hy,hy)
	append!(Ey,errory)
end

loglog(Hx,Ex,"r-")
loglog(Hx,Hx.^2,"--", label="2")
loglog(Hx,Hx,"--",label="1")
xlabel("hx")
ylabel("L Infinity Norm")
legend(loc="best", frameon=false)
show()

# Norm for Y ; f(x) = sin(pi*x)*cos(pi*y)
Hx = []
Ex = []
Hy = []
Ey = []

for Nx in 50:100, Ny in 50:100
    grid = Params2D(Nx,Ny,4,4)
    u = functionOnGrid((x,y)->sin(pi*x)*cos(pi*y),grid)

	#analytical derivatives
    dUdx = functionOnGrid((x,y)->pi*cos(pi*x)*cos(pi*y),grid)
    dUdy = functionOnGrid((x,y)->sin(pi*x)*(-pi)*sin(pi*y),grid)
    errorx = maximum(abs.(DerivX_2D(u,grid)-dUdx))
    errory = maximum(abs.(DerivY_2D(u,grid)-dUdy))
    hx=dX_2D(grid)
    hy=dY_2D(grid)
    append!(Hx,hx)
    append!(Ex,errorx)
    append!(Hy,hy)
    append!(Ey,errory)
end

loglog(Hy,Ey,"r-")
loglog(Hy,Hy.^2,"--", label="2")
loglog(Hy,Hy,"--",label="1")
xlabel("hy")
ylabel("L Infinity Norm")
legend(loc="best", frameon=false)
show()

# Norm for X ; f(x) = x^2+y^2
Hx = []
Ex = []

for Nx in 50:100, Ny in 50:100
    grid = Params2D(Nx,Ny,4,4)
    u = functionOnGrid((x,y)->x^2+y^2,grid)
    #analytical derivatives
    dUdx = functionOnGrid((x,y)->2x,grid)

    errorx = maximum(abs.(DerivX_2D(u,grid)-dUdx))

    hx=dX_2D(grid)

    append!(Hx,hx)
    append!(Ex,errorx)

end

loglog(Hx,Ex,"r-")
loglog(Hx,Hx.^2,"--", label="2")
loglog(Hx,Hx,"--",label="1")
xlabel("hx")
ylabel("L Infinity Norm")
legend(loc="best", frameon=false)
show()

# Norm for Y ; f(x) = x^2+y^2
Hy = []
Ey = []

for Nx in 50:100, Ny in 50:100
    grid = Params2D(Nx,Ny,4,4)
    u = functionOnGrid((x,y)->x^2+y^2,grid)
    #analytical derivatives
    dUdy = functionOnGrid((x,y)->2y,grid)

    errory = maximum(abs.(DerivY_2D(u,grid)-dUdy))

    hy=dY_2D(grid)

    append!(Hy,hy)
    append!(Ey,errory)

end

loglog(Hy,Ey,"r-")
loglog(Hy,Hy.^2,"--", label="2")
loglog(Hy,Hy,"--",label="1")
xlabel("hy")
ylabel("L Infinity Norm")
legend(loc="best", frameon=false)
show()


function nlp_003_016(
    optimizer,
    objective_tol,
    primal_tol,
    dual_tol,
    termination_target = TERMINATION_TARGET_LOCAL,
    primal_target = PRIMAL_TARGET_LOCAL,
)
    # Test Goals:
    # - objective with offset

    model = Model(optimizer)

    @variable(model, 0 <= x <= 4)
    @variable(model, 0 <= y <= 4)

    @objective(model, Max, x + pi)
    @NLconstraint(model, y >= exp(x - 2) - 1.5)
    @NLconstraint(model, y <= sin(x)^2 + 2)

    optimize!(model)

    check_status(model, FEASIBLE_PROBLEM, termination_target, primal_target)
    check_objective(model, 6.398105319414242, tol = objective_tol)
    return check_solution(
        [x, y],
        [3.2565126525233166, 2.013148549981813],
        tol = primal_tol,
    )
end


@doc_str """
    eigenvector_centrality(g)

Compute the eigenvector centrality for the graph `g`.

Eigenvector centrality computes the centrality for a node based on the
centrality of its neighbors. The eigenvector centrality for node `i` is

    ``\mathbf{Ax} = \lambda \mathbf{x}``

where `A` is the adjacency matrix of the graph `g` with eigenvalue `\lambda`.
By virtue of the Perron–Frobenius theorem, there is a unique and positive
solution if `\lambda` is the largest eigenvalue associated with the
eigenvector of the adjacency matrix `A`.

### References

- Phillip Bonacich: Power and Centrality: A Family of Measures.
    American Journal of Sociology 92(5):1170–1182, 1986
    http://www.leonidzhukov.net/hse/2014/socialnetworks/papers/Bonacich-Centrality.pdf
- Mark E. J. Newman: Networks: An Introduction.
       Oxford University Press, USA, 2010, pp. 169.
"""
eigenvector_centrality(g::AbstractGraph) = abs.(vec(eigs(adjacency_matrix(g), nev=1)[2]))::Vector{Float64}


# 04 dimension reduction

# using the libraries
using UMAP, XLSX, VegaDatasets, DataFrames, MultivariateStats, RDatasets, StatsBase, Statistics, LinearAlgebra, Plots, ScikitLearn, MLBase, Distances

# import the data
C = DataFrame(VegaDatasets.dataset("cars"));
describe(C);

# clean the data
dropmissing!(C);
M = Matrix{Float64}(C[!, 2:7]);
n = names(C)[2:end];

# For MLBase and label map
# USA -> 1
# Europe -> 2 etc...
car_origin = C[!, :Origin];
unique(car_origin);
carmap = labelmap(car_origin);
uniqueids = labelencode(carmap, car_origin)

# PCA approximate the matrix
# Center and standardize the data
data = M;
data = (data .- mean(data, dims=1)) ./ std(data, dims=1);

# fit the data
p = fit(PCA, data', maxoutdim=2)

# projection
P = projection(p);
P' * (data[1, :] - mean(p))

#
Yte = P' * data'
Yte = MultivariateStats.transform(p, data')

# reconstruct the matrix
Xr = reconstruct(p, Yte);
data' - Xr;
@show norm(data' - Xr)
# the approximation gets better when more maxoutdims are used.

# plotting
scatter(Yte[1,:],Yte[2,:])

scatter(Yte[1,car_origin.=="USA"],Yte[2,car_origin.=="USA"],color=1,label="USA")
scatter!(Yte[1,car_origin.=="Japan"],Yte[2,car_origin.=="Japan"],color=2,label="Japan")
scatter!(Yte[1,car_origin.=="Europe"],Yte[2,car_origin.=="Europe"],color=3,label="Europe")
xlabel!("pca component1")
ylabel!("pca component2")

# three dim maxxout
p = fit(PCA,data',maxoutdim=3)
Yte = MultivariateStats.transform(p, data')
scatter3d(Yte[1,:],Yte[2,:],Yte[3,:],color=uniqueids,legend=false)

# tsne
# @sk_import manifold : TSNE
tfn = TSNE(n_components=3 ,perplexity=20.0,early_exaggeration=50)
Y2 = tfn.fit_transform(data);
scatter(Y2[:,1],Y2[:,2],Y2[:,3],color=uniqueids,legend=false,size=(400,300),markersize=3)

# umap
L = cor(data,data,dims=2)
@time embedding = umap(L, )

scatter(embedding[1,:],embedding[2,:],color=uniqueids)

# different distance
@time L2 = pairwise(Euclidean(), data, data,dims=1)
embedding = umap(-L, 2)
scatter(embedding[1,:],embedding[2,:],color=uniqueids)


"""
    LUdenganP(A)
adalah fungsi yang digunakan untuk memfaktorisasi matriks `A` sembarang menjadi
dua matriks, yaitu matriks segitiga atas `U` dan bawah `L` serta matriks permutasi P
sedemikian sehingga `LU=PA`.

# Examples
```jl
julia> A = [ 2 5 4 2 3
            4 1 0.4 6 5
            0.9 4 8 7 10
            5 7 0.5 2 7
            2 6 8 9 4];

julia> L,U,P = LUdenganP(A);

julia> L
5×5 Array{Float64,2}:
 1.0    0.0       0.0        0.0       0.0
 0.8    1.0       0.0        0.0       0.0
 0.18  -0.595652  1.0        0.0       0.0
 0.4   -0.695652  0.986094   1.0       0.0
 0.4   -0.478261  0.480405  -0.537685  1.0

julia> U
5×5 Array{Float64,2}:
  5.0           7.0  0.5   2.0       7.0
  0.0          -4.6  0.0   4.4      -0.6
  1.11022e-16   0.0  7.91  9.26087   8.38261
 -1.09478e-16   0.0  0.0   2.12879  -7.48343
 -1.122e-16     0.0  0.0   0.0      -8.13773

julia> P
5×5 Array{Bool,2}:
 0  0  0  1  0
 0  1  0  0  0
 0  0  1  0  0
 0  0  0  0  1
 1  0  0  0  0
```
return matriks `L`, `U`, dan `P`.
"""
function LUdenganP(A)
    # Definisikan matriks L dan P.
    n,n = size(A);
    L = zeros(n,n);
    P = Array(I(n));
    # Lakukan operasi baris dasar terhadap matriks gandeng
    Aug = copy(A)
    for p = 1:n-1
        # Lakukan operasi pindah baris untuk menentukan nilai pivot
        val,j = findmax(abs.(Aug[p:n,p]));
        # Pivoting U
        C = Aug[p,:];
        Aug[p,:] = Aug[j+p-1,:];
        Aug[j+p-1,:] = C;
        # Pivoting L
        C = L[p,:];
        L[p,:] = L[j+p-1,:];
        L[j+p-1,:] = C;
        # Pivoting P
        C = P[p,:];
        P[p,:] = P[j+p-1,:];
        P[j+p-1,:] = C;
        # Jika pivot bernilai nol, maka gagal.
        if Aug[p,p]==0
            error("Matriks Singular")
        end
        # Lakukan eliminasi dan simpan pengali.
        for i = p+1:n
            k = Aug[i,p]/Aug[p,p]
            Aug[i,1:n] = Aug[i,1:n] - k*Aug[p,1:n];
            L[i,p] = k;
        end
    end
    U = Aug;
    L = L + I(n);
    return L,U,P
end


function gausslobatto( n )
# Gauss-Legendre-Lobatto Quadrature Nodes and Weights

    if ( n == 1 )
        error("Lobatto undefined for n = 1.")
    elseif ( n == 2 )
        x = ([-1.0,1.0],[1.0,1.0])
    elseif ( n == 3 )
        x = ([-1.0, 0.0, 1.0], [1.0, 4.0, 1.0]/3)
    else
        # Compute via GaussJacobi:
        x = gaussjacobi(n - 2, 1.0, 1.0)
        w = x[2]; x = x[1];
        x = [-1.0, x, 1.0]
        w = [-1.0, w, 1.0]
        w = w./(1-x.^2)
        w[1] = 2/(n*(n - 1))
        w[n] = 2/(n*(n - 1))
        x = (x,w)
    end
    return x
end


using LinearAlgebra
using SparseArrays
using Plots
using ForwardDiff

uexact(x) = log(2+sin(pi*x))
dudx_exact(x) = ForwardDiff.derivative(uexact,x)
f(x) = -ForwardDiff.derivative(dudx_exact,x) # -d2u/d2x = f
α = uexact(-1)
β = uexact(1) - dudx_exact(1)

function solve(m,f,α,β)
    x = LinRange(-1,1,m+2) # add 2 endpoints
    xint = x[2:end]
    h = x[2]-x[1]
    A = (1/h^2) * spdiagm(0=>2*ones(m+1),-1=>-ones(m),1=>-ones(m))
    A[end,end] = (1-h)/h^2
    b = f.(xint)
    b[1] += α/h^2
    b[end] = .5*f(x[end]) - β/h
    u = A\b
    return u,x,xint,h
end

mvec = 2 .^ (2:9)
hvec = zeros(length(mvec))
err_inf = zeros(length(mvec))
for (i,m) in enumerate(mvec)
    u,x,xint,h = solve(m,f,α,β)
    hvec[i] = h
    err_inf[i] = maximum(@. abs(uexact(xint) - u))
end

plot(hvec,err_inf,marker=:dot,label="Max error")
plot!(hvec,hvec.^2,linestyle=:dash,label="O(h^2)")
plot!(xaxis=:log,yaxis=:log,legend=:bottomright)


function I(n)
    mat = zeros(n, n)
    for i in 1:n
        mat[i, i] = 1.0
    end
    return mat
end

mean(x) = sum(x)/length(x)

function var(x)
    m = mean(x)
    xm = x .- m
    return sum((xm .* xm) / (length(x) - 1))
end

std(x) = var(x) |> sqrt

geomean(x::Array{<: Number, 1}) = exp(sum(log.(x))/length(x))

function cor(x, y)
    xmd= x .- mean(x)
    ymd =y .- mean(y)
    return (sum(xmd .* ymd) / sqrt(var(x) * var(y))) / (length(x) - 1.0)
end

function cor(x::Matrix{Float64})
    _, p = size(x)
    cormat = ones(p, p)
    for i in 1:p
        for j in i:p
            if i != j
                cr = cor(x[:,i], x[:,j])
                cormat[i, j] = cr
                cormat[j, i] = cr
            end
        end
    end
    return cormat
end

function euclidean(v1::Array{T1,1}, v2::Array{T2,1})::Float64 where {T1 <: Number,T2 <: Number}
    (v1 .- v2).^2.0 |> sum |> sqrt
end

function euclidean(v1::Array{T,1})::Float64 where T <: Number
    v2 = zeros(Float64, length(v1))
    return euclidean(v1, v2)
end

function normalize(v1::Array{T,1})::Array{Float64,1} where T <: Number
    return v1 ./ euclidean(v1)
end

function normalize(data::DataFrame)::DataFrame
    df = similar(data)
    _, p = size(df)
    for i in 1:p
        df[:, i] = normalize(data[:, i])
    end
    return df
end

function apply_columns(fs::Array{Function,1}, data::Union{DataFrame,Array{T,2}} where T <: Number)
    _, m = size(data)
    return [fs[i](data[:,i]) for i in 1:m]
end

function apply_columns(f::Function, data::Union{DataFrame,Array{T,2}} where T <: Number)
    return [f(c) for c in eachcol(data)]
end

function colmins(data::DataFrame)
    return apply_columns(minimum, data)
end

function colmaxs(data::DataFrame)
    return apply_columns(maximum, data)
end

function colsums(data)::Array{Number,1}
    return apply_columns(sum, data)
end

function colmeans(data)::Array{Number,1}
    return apply_columns(mean, data)
end

function apply_rows(f::Function, data::Union{DataFrame,Array{T,2}} where T <: Number)
    return [f(c) for c in eachrow(data)]
end

function rowmins(data::DataFrame)::Array{Float64,1}
    return apply_rows(minimum, data)
end

function rowmaxs(data::DataFrame)::Array{Float64,1}
    return apply_rows(maximum, data)
end

function rowsums(data)::Array{Float64,1}
    return apply_rows(sum, data)
end

function rowmeans(data)::Array{Float64,1}
    return apply_rows(mean, data)
end

function unitize(v::Array{T,1})::Array{Float64,1} where {T <: Number}
    return v ./ sum(v)
end

function Base.:*(w::Array{Float64,1}, data::DataFrame)::DataFrame
    newdf = copy(data)
    _, p = size(newdf)
    for i in 1:p
        newdf[!, i] = convert.(Float64, newdf[!,i])
        newdf[:, i] = w[i] .* data[:, i]
    end
    return newdf
end

function Base.:-(r1::DataFrameRow, r2::DataFrameRow)::Array{Number,1}
    # v1 = convert(Array{Float64,1}, r1)
    v1 = Vector{Float64}(r1)
    # v2 = convert(Array{Float64,1}, r2)
    v2 = Vector{Float64}(r2)
    return v1 .- v2
end

function Base.:-(r1::Array{T,1}, r2::DataFrameRow)::Array{T,1} where T <: Number
    # v2 = convert(Array{Float64,1}, r2)
    v2 = Vector{Float64}(r2)
    return r1 .- v2
end

function Base.:-(r1::DataFrameRow, r2::Array{T,1})::Array{T,1} where T <: Number
    v1 = convert(Array{Float64,1}, r1)
    return v1 .- r2
end

"""
    makeDecisionMatrix(mat; names)

    Create a DataFrame using a decision matrix optionally using column names.

# Arguments:
 - `mat::Array{T,2}`: Matrix of numbers in any set
 - `names::Union{Nothing,Array{String,1}}`: Column names. Default is nothing. If column names are not given, they are labelled as Crt 1, Crt 2, ..., etc.

"""
function makeDecisionMatrix(mat::Array{T,2}; names::Union{Nothing,Array{String,1}}=nothing)::DataFrame where {T <: Number}
    _, m = size(mat)
    df = DataFrame()
    for i in 1:m
        if names isa Nothing
            name = string("Crt", i)
        else
            name = names[i]
        end
        df[:,Symbol(name)] = convert(Array{Float64,1}, mat[:, i])
    end
    return df
end

function Base.minimum(df::DataFrame)
    df |> Matrix |> minimum
end

function reverseminmax(fns::Array{Function,1})::Array{Function,1}
    newfs = map(x -> if x == minimum maximum else minimum end, fns)
    return newfs
end

function makeminmax(fns::Array{K,1} where K)::Array{Function,1}
    return convert(Array{Function,1}, fns)
end


# Practical Review: Numeric Optimization (Ch. 12)

##
# Let's start with a simple problem, where we already know the
# answers. We will apply numeric optimization methods and we
# will be able to verify that they give use the correct results.
# To start, let's define some data, and do the basic OLS
# computations
using Econometrics
n = 5
x = [ones(n) 1:n]
β = [10., -1.]
ϵ = randn(n)
y = x*β + ϵ
ols(y,x)

##
# Let's see how to get these results using numeric minimization
# and extremum estimation theory...
using Econometrics, Statistics
objᵢ = β -> (y-x*β).^2.  	# iᵗʰ obs contrib to sum of squares
obj = β -> mean(objᵢ(β))	# average objective
fminunc(obj, zeros(2))

##
# fminunc is just a frontend to the Optim.jl package's LBFGS unconstrained
# minimizer routine. To call that directly, do
using Optim
tol = 1e-08
results = Optim.optimize(obj, zeros(2), LBFGS(),
                            Optim.Options(
                            g_tol = tol,
                            x_tol=tol,
                            f_tol=tol); autodiff=:forward)

##
# The output has the function value and convergence information. To see the
# value of the minimizer, do
βhat = results.minimizer

##
# Let's check that the Hessian is p.d.
using ForwardDiff
H = ForwardDiff.hessian(obj, βhat)
##
using LinearAlgebra
eigen(H)

##
# The eigenvalues are all positive, so the Hessian is p.d. at
# the solution. For the OLS case, the objective function is
# known to be globally convex, so multiple local optima are not
# a problem. Let's look at another problem, which has multiple
# minima, and see how to find the global minimizer
# Here's another objective function
f(x,y) = -(3*(1-x)^2 * exp(-(x^2) - (y+1)^2)
    - 6*(x/5 - x^3 - y^5) * exp(-x^2 - y^2)
    - 1/3 * exp(-(x+1)^2 - y^2))

##
# let's explore it:
using Plots
plotly() # this backend makes nice 3d plots
x = range(-4, step=0.1, stop=4)
y = x
surface(x, y, (x,y)->f(x,y),c=:viridis)
xlabel!("x")
ylabel!("y")
##
contour(x,y,(x,y)->f(x,y),c=:viridis)

##
# This function has 3 local min, and 2 local max.
# The global minimizer is near (0,1.6), with a function
# value of -4.86. We will need some care to find the global
# minimizer. The domain is (-4,4)×(-4,4), which may help us.
# First, let's try gradient-based minimization, starting at
# a random point in the domain.

##
# Before we do that, the form of the function defined above
# does not take a single vector argument. For optimization, we
# need all parameters in a single vector. Julia's multiple
# dispatch allow us to do this easily, by defining another
# method for the function f:
function f(θ)
   x,y = θ
   f(x,y) # pretty neat, eh?
end

##
#=
Another thing to check before doing optimization! Remember
that gradient based methods will work better when the data
are scaled to put the elements of the gradient one a common
order of magnitude. You can check this by examining the
gradient at some points. Run this block a few times to check.
=#
using ForwardDiff
lower = [-4., -4.]
upper = -lower
θstart = 8.0 .* rand(2) .-4.0
ForwardDiff.gradient(f, θstart)
# in this case, things seem to be ok.

##
# Now we can try optimization. Running this block repeatedly (Alt-enter)
# will show that each of the 3 minimizers are found. Thus, if one defined
# LBFGS with enough start values, the global minimizer would be found.
# If you run the code enough times, you will see that the algorithm
# occasionally goes off far away from the 3 local minimizers. In that
# region, the gradients are zero, as we see in the plots, and the algorithm
# can't find better points.
using Econometrics, Plots, Optim
gr() # using the GR backend
x = range(-4, step=0.1, stop=4)
y = x
contour(x,y,f,c=:viridis)
θstart = 8.0 .* rand(2) .-4.0
tol = 1e-08
results = Optim.optimize(f, θstart, LBFGS(),
                            Optim.Options(
                            g_tol = tol,
                            x_tol=tol,
                            f_tol=tol); autodiff=:forward)

θhat = results.minimizer
scatter!([θhat[1]],[θhat[2]],legend=false, markersize=10, c = :viridis)

##
# A more robust algorithm is to use the known domain to define
# box constraints. This will still bounce between the 3 local
# min, but it will not shoot off to far away points.
using Econometrics, Plots, Optim
gr() # using the GR backend
x = range(-4, step=0.1, stop=4)
y = x
contour(x,y,f, c=:viridis)
lower = [-4., -4.]
upper = -lower
θstart = 8.0 .* rand(2) .-4.0
tol = 1e-08
results = Optim.optimize(f, lower, upper, θstart, Fminbox(LBFGS()),
                            Optim.Options(
                            g_tol = tol,
                            x_tol=tol,
                            f_tol=tol); autodiff=:forward)
θhat = results.minimizer
scatter!([θhat[1]],[θhat[2]],legend=false, markersize=10)

##
#
# Global optimization
#
# Another option is to use a global optimizer. An example is the
# simulated annealing algorithm. This one should find the global
# min every time, provided the cooling schedule is slow enough
using Econometrics
# try setting rt=0.05, it will fail sometimes, because the
# search region is shrunk too quickly
# rt=0.9 has worked every time I've tried it
rt = 0.75
lower = [-4., -4.]
upper = -lower
θstart = 8.0 .* rand(2) .-4.0
samin(f, θstart, lower, upper, rt=rt);
println("for reference, the global minimizer is (-0.00879, 1.58163)")
println("with the function value -4.8611920055")

#=
To summarize, a gradient based minimizer should be reliable
when the objective function is globally convex. When this is
not the case, one can use multiple start values for a gradient
based method, or one might prefer to use a global minimizer.
=#


using LinearAlgebra

#Givens from Problem Statement
#Shear Modulus(psi),diameter(in),length(in),torque(lb*in)
G = 3.9*10^6
G2 = 4.0*10^6
d = 1.5
d2 = 1
l = 2.5*12
l2 = 1*12
l3 =2*12
τ = -200*12

# Polar Second Area Moment of a Cylinder (in^4)
J_cyl(d) = 0.5*pi*(d/2)^4
J = J_cyl(d)
J2 = J_cyl(d2)

# Torsional Stiffness Function
k(G,J,l) = (G*J)/l

# k_local(Elastic Modulus,thickness,height,length,number of nodes,
# row/column one,row/column two, ==1 left fixed/ ==2 right fixed)
function k_local(G,J,l,i,n1,n2,fixed=0)
    k_global = zeros(i,i)
    kin = k(G,J,l)
    k_global[n1,n1] = kin
    k_global[n1,n2] = -kin
    k_global[n2,n1] = -kin
    k_global[n2,n2] = kin
    if fixed == 1
      adj = zeros(1,i)
      adj[n1] = 1
      k_global[n1,:] = adj
      k_global[:,n1] = adj
    elseif fixed == 2
      adj = zeros(1,i)
      adj[n2] = 1
      k_global[n2,:] = adj
      k_global[:,n2] = adj
    end
    return k_global
end

# Global for angular displacement calculation
k_1 = k_local(G,J,l,4,1,2,1)
k_2 = k_local(G,J,l2,4,2,3)
k_3 = k_local(G2,J2,l3,4,3,4,2)
kG = k_1+k_2+k_3

#Global for Reaction calculation
k_11 = k_local(G,J,l,4,1,2)
k_22 = k_local(G,J,l2,4,2,3)
k_33 = k_local(G2,J2,l3,4,3,4)
kGG = k_11+k_22+k_33

# Torque Vector
t = Float64[0 τ 0 0]
T = transpose(t)

# Angular Displacement Vector
θ = kG\T
println("Nodal Angular Displacement Vector (radians)")
display(θ)

# Moment Reaction Vector
τ_app = kGG*θ
println("Moment Reaction Vector (lb*in):")
display(τ_app)


function collatz_chain_length(x::Int)
	if x==1
		return 1
	end
	if x%2==0
		x=Int(x/2)
	else
		x=x*3+1
	end
	return collatz_chain_length(x)+1
end

function collatz_length(x::Int)
	if x==1
		return 1
	end
	count=1;
	while x!=1
		count+=1
		if x%2==0
			x=Int(x/2)
			if x==1
				break
			end
		else
			x=x*3+1
		end

	end
	return count
end

function compute()
    const N=1000000
    ans =0
    index=1
	for i=1:N
		if ans<collatz_chain_length(i)
			index=i
			ans=collatz_chain_length(i)
		end
	end

	return index
end

function compute2()
	 const N=1000000
    ans =0
    index=1
	for i=1:N
		if ans<collatz_length(i)
			index=i
			ans=collatz_length(i)
		end
	end

	return index
end

print(compute2())


##
using Roots
function GE(π, γ, ω1A, ω1B, ω2A, ω2B)
    f(p) = π * (ω1A + p*ω1B) + ((ω2A + p*ω2B) / (1 + p * ((1 - π)/(p * π))^(1/γ))) - ω1A - ω2A
    p = round(find_zero(f, 1.5), digits= 3)
    C1A = π * (ω1A + p*ω1B)
    C1B = ((1 - π) / p) * (ω1A + p*ω1B)
    C2A = (ω2A + p*ω2B) / (1 + p * ((1 - π)/(p * π))^(1/γ))
    C2B = ((ω2A + p*ω2B) / (1 + p * ((1 - π)/(p * π))^(1/γ))) * ((1 - π)/(p * π))^(1/γ)
    print("p = $p \n", "C1A = $C1A \n", "C1B = $C1B \n", "C2A = $C2A \n", "C2B = $C2B \n")
end
##
#4(e) No aggregate uncertanity
GE(0.5, 2, 2, 5, 5, 2)
#4(f) There is no aggregate uncertainty but for person A the good world is more likely to happen
GE(0.25, 2, 2, 5, 5, 2)
#4(f) There is no aggregate uncertainty but for person B the good world is more likely to happen
GE(0.75, 2, 2, 5, 5, 2)
#4(g)(e) γ=1.5
GE(0.5, 1.5, 2, 5, 5, 2)
#4(g)(e) γ=3
GE(0.5, 3, 2, 5, 5, 2)
#4(g)(f) γ=1.5 π=0.25
GE(0.25, 1.5, 2, 5, 5, 2)
#4(g)(f) γ=1.5 π=0.75
GE(0.75, 1.5, 2, 5, 5, 2)
#4(g)(f) γ=3 π=0.25
GE(0.25, 3, 2, 5, 5, 2)
#4(g)(f) γ=3 π=0.75
GE(0.75, 3, 2, 5, 5, 2)
#4(g)(e) γ=2 π=0.5
GE(0.5, 2, 2, 3, 5, 2)
#4(g)(e) γ=1.5 π=0.5
GE(0.5, 1.5, 2, 3, 5, 2)
#4(g)(e) γ=3 π=0.5
GE(0.5, 3, 2, 3, 5, 2)


export logsoftmax, logsumexp, softmax, logp
using AutoGrad: AutoGrad, @primitive1, value

"""
    softmax(x; dims=:)
    logsoftmax(x; dims=:)

Treat entries in `x` as as unnormalized log probabilities and return normalized (log)
probabilities, i.e.

    softmax(x; dims) = exp.(x) ./ sum(exp.(x); dims=dims)
    logsoftmax(x; dims) = x .- log.(sum(exp.(x); dims=dims))

For numerical stability `x = x .- maximum(x,dims=dims)` is performed before exponentiation.

`dims` is an optional argument, if not specified the normalization is over the whole `x`,
otherwise the normalization is performed over the given dimensions.  In particular, if `x` is
a matrix, `dims=1` normalizes columns of `x` and `dims=2` normalizes rows of `x`.

"""
softmax, logsoftmax, logp

"""
    logsumexp(x;dims=:)

Compute `log(sum(exp(x);dims))` in a numerically stable manner.

`dims` is an optional argument, if not specified the summation is over the whole `x`,
otherwise the summation is performed over the given dimensions.  In particular if `x` is a
matrix, `dims=1` sums columns of `x` and `dims=2` sums rows of `x`.

"""
logsumexp

# Separate user facing methods from underlying implementations
logsoftmax(x; dims=:) = _logsoftmax(x, dims=dims)
softmax(x; dims=:)    = _softmax(x, dims=dims)
logsumexp(x; dims=:)  = _logsumexp(x, dims=dims)
const logp = logsoftmax

function _logsoftmax(x; dims)
    isa(x, Number) && return zero(x)
    isempty(x) && return similar(x)
    x = x .- maximum(x,dims=dims)
    return (x .- log.(sum(exp.(x); dims=dims)))
end

function ∇logsoftmax(x,y,dy; dims)
    return (dy - exp.(y).*sum(dy; dims=dims))
end

function _softmax(x; dims)
    isa(x, Number) && return one(x)
    isempty(x) && return similar(x)
    x = x .- maximum(x; dims=dims)
    x = exp.(x)
    return (x ./ sum(x; dims=dims))
end

function ∇softmax(x,y,dy; dims)
    return (y .* dy .- y .* sum(y .* dy; dims=dims))
end

function _logsumexp(x; dims)
    isa(x, Number) && return x
    isempty(x) && throw(ArgumentError("reducing over an empty collection is not allowed"))
    xmax = maximum(x,dims=dims)
    return (xmax + log.(sum(exp.(x .- xmax),dims=dims)))
end

function ∇logsumexp(x,y,dy; dims)
    return (dy .* exp.(x .- y))
end

@primitive1 _logsoftmax(x;dims),dy,y  ∇logsoftmax(x,y,dy,dims=dims)
@primitive1 _softmax(x;dims),dy,y     ∇softmax(x,y,dy,dims=dims)
@primitive1 _logsumexp(x;dims),dy,y   ∇logsumexp(x,y,dy;dims=dims)

# Math for the softmax loss: x is unnormalized input, p is target probabilities, q is
# estimated probabilities. Read left column down, right column (loss gradients) back up.
#
# x			dx = -p + qz/z = -p + exp(logq)
# xmax  = max(x,1)	-sum(db)=0
# logqz = x .- xmax	-p + qz/z
# qz    = exp(logqz)	rep(1/z)
# z     = sum(qz,1)	1/z
# logz  = log(z)	sum(p)=1
# logq  = logqz.-logz	-p
# plogq = p .* logq	-1
# loss  = -sum(plogq)	1


#=
                            Largest prime factor

    The prime factors of 13195 are 5, 7, 13 and 29.

    What is the largest prime factor of the number 600851475143 ?

                            Answer: 6857
=#

include("../project_euler.jl")
using project_euler

print(pop!(prime_factors(600851475143)), "\n")


module Loss

export AbstractLoss, ALoss, Float, loss, ∇loss, SSE, SoftmaxXEntropy, softmax

include("./tensor_dt.jl")
using .TensorDT: Tensor

## ======================================================================
## Loss
## ======================================================================
abstract type AbstractLoss end
const ALoss = AbstractLoss
const Float = Float64

loss(::AbstractLoss, _ŷ::Tensor, _y::Tensor) = throws(ArgumentError("Not Implemented"))
∇loss(::AbstractLoss, _ŷ::Tensor, _y::Tensor) = throws(ArgumentError("Not Implemented"))

#### Sum squared Error (SSE)

struct SSE <: AbstractLoss
end

function loss(_self::SSE, ŷ::Tensor, y::Tensor)::Float
  sum((ŷ .- y) .^ 2)
end

function ∇loss(_self::SSE, ŷ::Tensor, y::Tensor)::Tensor
  2 * (ŷ .- y)
end

struct SoftmaxXEntropy <: AbstractLoss
end

function loss(_self::SoftmaxXEntropy, ŷ::Tensor, y::Tensor; ϵ=1e-20)::Float
  -sum(log.(softmax(ŷ) .+ ϵ) .* y)
end

function ∇loss(_self::SoftmaxXEntropy, ŷ::Tensor, y::Tensor)::Tensor
    softmax(ŷ) .- y
end

## Softmax function helper

function softmax(tensor::Tensor)::Tensor
  """Softmax along ths last dimension"""
  ## for numerical stability - subtract largest value
  exps = exp.(tensor .- maximum(tensor))
  sum_exps = sum(exps)
  exps ./ sum_exps
end

end  ## Module


using CSV, Distributions, HypothesisTests

data1 = CSV.read("../data/machine1.csv", header=false)[:,1]
data2 = CSV.read("../data/machine2.csv", header=false)[:,1]
xBar1, xBar2 = mean(data1), mean(data2)
s1, s2 = std(data1), std(data2)
n1, n2 = length(data1), length(data2)
alpha = 0.05

v = (s1^2/n1 + s2^2/n2)^2 / ( (s1^2/n1)^2 / (n1-1) + (s2^2/n2)^2 / (n2-1) )

t = quantile(TDist(v),1-alpha/2)

println("Calculating formula: ", (xBar1 - xBar2 - t*sqrt(s1^2/n1 + s2^2/n2),
				  xBar1 - xBar2 + t*sqrt(s1^2/n1 + s2^2/n2)))
println("Using confint(): ",   confint(UnequalVarianceTTest(data1,data2),alpha))


@testset "One sample t-test" begin
    x = [2.55, 1.94, 2.52, 2.47, 2.00, 3.04, 2.82, 3.45, 3.82, 0.89]
    res_ttest = t_test(x, μ0=0.0)
    test_show(res_ttest)
    @test res_ttest.statistic ≈ 9.73641835357921
    @test res_ttest.dof == 9
    @test res_ttest.pval ≈ 4.46718590474277e-06
end

@testset "Paired t-test" begin
    x1 = [-2.0, -3.2, 1.2, 5.8, 19.6, 19.9, 28.9, 31.1, 27.9, 21.9, 8.8, 7.5]
    x2 = [-1.8, -2.9, 0.9, 1.5, 11.5, 18.8, 23.9, 24.9, 23.8, 19.4, 8.8, 4.8]
    res_ttest = t_test(x1, x2, paired=true)
    @test res_ttest.statistic ≈ 3.52842907954346
    @test res_ttest.dof == 11
    @test res_ttest.pval ≈ 0.0047278741207561
end

@testset "Simple two sample t-test" begin
    x1 = [21.8, 21.7, 32.0, 30.2, 27.4]
    x2 = [12.3, 19.7, 21.9, 23.6]
    res_ttest = t_test(x1, x2, varequal=true)
    @test res_ttest.statistic ≈ 2.22997773895153
    @test res_ttest.dof == 7
    @test res_ttest.pval ≈ 0.0609728704866215
end

@testset "Welch two sample t-test" begin
    df_sleep = dataset("datasets", "sleep")
    res_ttest = t_test(@formula(Extra ~ Group), df_sleep)
    test_show(res_ttest)
    @test res_ttest.statistic ≈ -1.86081346748685
    @test res_ttest.dof ≈ 17.7764735161785
    @test res_ttest.pval ≈ 0.0793941401873582
end


# Consider quadratic Diophantine equations of the form:
#
# x^2 - D * y^2 = 1
#
# For example, when D=13, the minimal solution in x is 649^2 - 13 × 180^2 = 1.
#
# It can be assumed that there are no solutions in positive integers when D is
# square.
#
# By finding minimal solutions in x for D = {2, 3, 5, 6, 7}, we obtain the
# following:
#
# 3^2 - 2×2^2 = 1
# 2^2 - 3×1^2 = 1
# 9^2 - 5×4^2 = 1
# 5^2 - 6×2^2 = 1
# 8^2 - 7×3^2 = 1
#
# Hence, by considering minimal solutions in x for D ≤ 7, the largest x is
# obtained when D=5.
#
# Find the value of D ≤ 1000 in minimal solutions of x for which the largest
# value of x is obtained.

using ProjectEulerSolutions

# This format for a Diophantine equation is called Pell's Equation.  It turns
# out that the minimal solution for x given a particular D is identified by
# finding the continued fraction convergent that approximates sqrt(D), and
# substituting into Pell's equation until it equals 1.  It turns out that the
# minimal values of x can get very large, like for D=61
# (https://oeis.org/A002350).  Since a quick solution for the minimal value is
# x is found, just cycle through D values storing the D with the largest x.
function p066solution(n_max::Integer=3)::Integer

    x_max = 0
    d_max = 0
    for D in 2:n_max
        if sqrt(D) == floor(sqrt(D))  # No solution for D = perfect square
            continue
        end

        # Need to use BigInts since the numerators and denominators can get
        # quite large
        a0 = BigInt(floor(sqrt(D)))
        a = a0
        m = 0
        d = 1
        A = Array{BigInt,1}([1, a0])
        B = Array{BigInt,1}([0, 1])
        k = 1
        while A[end]*A[end] - D*B[end]*B[end] != 1
            m = d * a - m
            d = div(D - m * m, d)
            a = div(a0 + m, d)
            append!(A, a * A[k+1] + A[k])
            append!(B, a * B[k+1] + B[k])
            k += 1
        end
        if x_max < A[end]
            x_max = A[end]
            d_max = D
        end
    end
    return d_max
end

p066 = Problems.Problem(p066solution)

Problems.benchmark(p066, 1000)


"""
    SqExponentialKernel()

The squared exponential kernel is a Mercer kernel given by the formula:
```
    κ(x, y) = exp(-‖x - y‖² / 2)
```
Can also be called via `RBFKernel`, `GaussianKernel` or `SEKernel`.
See also [`ExponentialKernel`](@ref) for a
related form of the kernel or [`GammaExponentialKernel`](@ref) for a generalization.
"""
struct SqExponentialKernel <: SimpleKernel end

kappa(κ::SqExponentialKernel, d²::Real) = exp(-d² / 2)

metric(::SqExponentialKernel) = SqEuclidean()

iskroncompatible(::SqExponentialKernel) = true

Base.show(io::IO,::SqExponentialKernel) = print(io,"Squared Exponential Kernel")

## Aliases ##
const RBFKernel = SqExponentialKernel
const GaussianKernel = SqExponentialKernel
const SEKernel = SqExponentialKernel

"""
    ExponentialKernel()

The exponential kernel is a Mercer kernel given by the formula:
```
    κ(x,y) = exp(-‖x-y‖)
```
"""
struct ExponentialKernel <: SimpleKernel end

kappa(κ::ExponentialKernel, d::Real) = exp(-d)

metric(::ExponentialKernel) = Euclidean()

iskroncompatible(::ExponentialKernel) = true

Base.show(io::IO, ::ExponentialKernel) = print(io, "Exponential Kernel")

## Alias ##
const LaplacianKernel = ExponentialKernel

"""
    GammaExponentialKernel(; γ = 2.0)

The γ-exponential kernel [1] is an isotropic Mercer kernel given by the formula:
```
    κ(x,y) = exp(-‖x-y‖^γ)
```
Where `γ > 0`, (the keyword `γ` can be replaced by `gamma`)
For `γ = 2`, see `SqExponentialKernel` and `γ = 1`, see `ExponentialKernel`.

[1] - Gaussian Processes for Machine Learning, Carl Edward Rasmussen and Christopher K. I.
    Williams, MIT Press, 2006.
"""
struct GammaExponentialKernel{Tγ<:Real} <: SimpleKernel
    γ::Vector{Tγ}
    function GammaExponentialKernel(; gamma::T=2.0, γ::T=gamma) where {T<:Real}
        @check_args(GammaExponentialKernel, γ, γ >= zero(T), "γ > 0")
        return new{T}([γ])
    end
end

@functor GammaExponentialKernel

kappa(κ::GammaExponentialKernel, d::Real) = exp(-d^first(κ.γ))

metric(::GammaExponentialKernel) = Euclidean()

iskroncompatible(::GammaExponentialKernel) = true

function Base.show(io::IO, κ::GammaExponentialKernel)
    print(io, "Gamma Exponential Kernel (γ = ", first(κ.γ), ")")
end


#!/usr/bin/julia

function base10ToAny(in_dig,base)
    if base >= 21
        println("Warning: There're not special characters for digits in base 21 or more")
    end
    digits_b10=[]
    q=in_dig
    while true
        if q % base <= 9
            push!(digits_b10,q % base)
        elseif q % base == 10
            push!(digits_b10,'A')
        elseif q % base == 11
            push!(digits_b10,'B')
        elseif q % base == 12
            push!(digits_b10,'C')
        elseif q % base == 13
            push!(digits_b10,'D')
        elseif q % base == 14
            push!(digits_b10,'E')
        elseif q % base == 15
            push!(digits_b10,'F')
        elseif q % base == 16
            push!(digits_b10,'G')
        elseif q % base == 17
            push!(digits_b10,'H')
        elseif q % base == 18
            push!(digits_b10,'I')
        elseif q % base == 19
            push!(digits_b10,'J')
        else
            r=(q % base)
            push!(digits_b10,"($r)")
        end
        q=div(q,base)
        if q == 0
            break
        end
    end
    l=length(digits_b10)
    for idx in 1:l
        print(pop!(digits_b10))
    end
end

function anyBaseTo10(iterDig,base)
    if base >= 21
        println("Error: Only works for a base 20 or less")
        return
    end
    if !isa(iterDig,AbstractString)
        println("Error: Input n should be a string")
        return
    end
    summation=0
    l=sizeof(iterDig)
    for idx in 1:l
        digit = iterDig[ l - (idx-1)]
        if digit == 'A'
            digit = 10
        elseif digit == 'B'
            digit = 11
        elseif digit == 'C'
            digit = 12
        elseif digit == 'D'
            digit = 13
        elseif digit == 'E'
            digit = 14
        elseif digit == 'F'
            digit = 15
        elseif digit == 'G'
            digit = 16
        elseif digit == 'H'
            digit = 17
        elseif digit == 'I'
            digit = 18
        elseif digit == 'J'
            digit = 19
        else
            digit = parse(Int64, digit)
        end
        summation = digit*(base^(idx-1)) + summation
    end
    println(summation)
end

function binAddition(in_bin_1,in_bin_2)
    ib1=string(in_bin_1)
    ib2=string(in_bin_2)
    l1=sizeof(ib1)
    l2=sizeof(ib2)
    if l1 > l2
        AddZeros = l1 - l2
        ib2=("0" ^ AddZeros) * ib2
        larger=l1
    elseif l2 > l1
        AddZeros = l2 - l1
        ib1=("0" ^ AddZeros) * ib1
        larger=l2
    else
        larger=l1
    end
    c=0
    all_s=[]
    for idx in 1:larger
        dig1= parse(Int64, ib1[ larger - (idx-1) ])
        dig2= parse(Int64, ib2[ larger - (idx-1) ])
        d=div( dig1 + dig2 + c, 2)
        s=dig1 + dig2 + c - 2d
        c=d
        push!(all_s,s)
    end
    push!(all_s,c)
    justZero = true
    #println("all_s is:", all_s)
    for idx in 1:length(all_s)
        if length(all_s) == 2 && join(all_s) == "00"
            print(all_s[ length(all_s) - (idx -1 ) ])
            break
        end
        if justZero && all_s[ length(all_s) - (idx -1 ) ] == 0
            continue
        end
        if all_s[ length(all_s) - (idx -1 ) ]  == 1
            justZero = false
        end
        print(all_s[ length(all_s) - (idx -1 ) ])
    end
end

function binMultiplication(a,b)
end

#21 * 21
#441
#110111001

a=10101
b=10101
a=string(a)
b=string(b)
la=sizeof(a)
lb=sizeof(b)
if la > lb
    AddZeros = la - lb
    b=("0" ^ AddZeros) * b
    larger=la
elseif lb > la
    AddZeros = lb - la
    a=("0" ^ AddZeros) * a
    larger=lb
else
    larger=la
end
c_partials=[]
for idx in 1:larger
    #println("b[larger - (idx -1)] is\t", b[larger - (idx -1)])
    if b[larger - (idx -1)] == '1'
        c = a * ("0" ^ (idx - 1))
        #println("This is a: \t",a)
        push!(c_partials,c)
    else
        c = "0"
        push!(c_partials,c)
    end
end
p = 0
#println("This is c_partials: \t",c_partials)
for partial in c_partials
    println("Partial is $partial")
    partial = parse(Int64, partial)

    println("Value of function is: ", binAddition(p,partial) )
    p = binAddition(p,partial)
end


using LinearAlgebra
#
# This script should return a mxn Matrix{Float64}.
# Example:
# return [1.0 2.0 3.0; 4.0 5.0 6.0; 7.0 8.0 9.0];
#

m = 100
n = m

A = rand(m, n);

println("Julia: sum(A)=", sum(A));
return A


function chebyshev_gauss_lobatto(n::S,domain = [1.0,-1.0]) where {S<:Integer}

  # These nodes a just the Chebyshev extrema by a different name.

  # Construct the nodes on the [-1.0,1.0] interval

  if n <= 0
    error("The number of nodes must be positive.")
  end

  if n == 1
    nodes   = [0.0]
    weights = [1.0*pi]
  else
    nodes    = zeros(n)
    nodes[1] = -1.0
    nodes[n] = 1.0

    weights    = zeros(n)
    weights[1] = pi/(2*(n-1))
    weights[n] = pi/(2*(n-1))

    for i = 2:div(n,2)
      x = cos(pi*(i-1)/(n-1))
      nodes[i]       = -x
      nodes[end-i+1] = x
      weights[i]       = pi/(n-1)
      weights[end-i+1] = pi/(n-1)
    end

    if isodd(n)
      nodes[round(Int,(n+1)/2)]   = 0.0
      weights[round(Int,(n+1)/2)] = pi/(n-1)
    end
  end

  # Scale the nodes to the desired domain

  nodes = scale_nodes(nodes,domain)

  return nodes, weights

end


#=
A function f is Lipschitz continous on [a, b]
    |f(x)-f(y)| < l*|x-y|    for all x, y \in [a, b], l > 0
l is as large as the largest unsigned instantaneous
rate of change the function attains on [a,b].

Given a point (x0, f(x0)), The line f(x0)-l(x-x0) for x > x0
and f(x0) + l(x-x0) for x < x0 form a lower bound of f.
=#

struct Pt
    x
    y
end

function get_sp_intersection(A, B, L)
    t = ((A.y-B.y)-L*(A.x-B.x)) / 2L;
    return Pt(A.x+t, A.y-t*L);
end

function shubert_piyavskii(f, a, b, l, ϵ, δ=0.01)
    m = (a+b)/2;
    A, M, B = Pt(a, f(a)), Pt(m, f(m)), Pt(b, f(b))
    pts = [A, get_sp_intersection(A, M, l),
    M, get_sp_intersection(M, B, l), B];
    Δ = Inf;

    while Δ > ϵ
        i = argmin([P.y for P in pts]);
        P = Pt(pts[i].x, f(pts[i].x));
        Δ = P.y = pts[i].y;

        P_prev = get_sp_intersection(pts[i-1], P, l);
        P_next = get_sp_intersection(P, pts[i+1], l);

        deleteat!(pts, i);
        insert!(pts, i, P_next);
        insert!(pts, i, P);
        insert!(pts, i, P_prev);
    end

    intervals = [];
    P_min = pts[2*(argmin([P.y for P in pts[1:2:end]])) - 1];
    y_min = P_min.y;
    for i in 2:2:length(pts)
        if pts[i].y < y_min
            dy = y_min - pts[i].y;
            x_low = max(a, pts[i].x - dy/l);
            x_high = max(b, pts[i].x - dy/l);
            if !isempty(intervals) && intervals[end][2] + δ ≥ x_low
                intervals[end] = (intervals[end][1], x_high);
            else
                push!(intervals, (x_low, x_high));
            end
        end
    end
    return (P_min, intervals);
end