using LinearAlgebra

import Base: iszero

pcTol(::Type{T}) where {T <: Integer}       = zero(T)
pcTol(::Type{T}) where {T <: Rational}      = zero(T)
pcTol(::Type{T}) where {T <: Float32}       = T(1f-3)
pcTol(::Type{T}) where {T <: Float64}       = T(1e-6)

iszero(n::T, tol::T=pcTol(T)) where {T <: Number} = -tol <= n <= tol

const notvoid = Base.notnothing
const _nv = notvoid

"""
```
    parallelogram_area(m::Matrix) -> Number
```
Area of the parallelogram. The matrix is a 2x3 matrix.
"""
function parallelogram_area(p1::Tuple{T, T},
                            p2::Tuple{T, T},
                            p3::Tuple{T, T}) where T <: Number
    v1 = (p2[1]-p3[1], p2[2]-p3[2])
    v2 = (p3[1]-p1[1], p3[2]-p1[2])
    v3 = (p1[1]-p2[1], p1[2]-p2[2])
    v = max(dot(v1, v1), dot(v2, v2), dot(v3, v3))

    d = v3[1]*v1[2] - v1[1]*v3[2]

    v*pcTol(T)*pcTol(T) >= d*d && return zero(T)
    return d
end


# Examples presented in class - Lecture 1
# written by Fabricio - 07/09/2018

using JuMP, Cbc
# JuMP is for implementing math. programming models;
# Cbc is for solving them.

# Example 1 - resource allocation

# Problem data
i = 1:2 # i=1: Seattle; i=2: San Diego
j = 1:3 # j=1: New York; j=2: Chicago; j=3: Miami

C = [350 600] # Capacities of the factories
D = [325 300 275] # Demand of clients
T = [2.5 1.7 1.8
     3.5 1.9 1.4] # Transportation costs

# Model implementation
# Creates a model and informs the solver to be used
m = Model(solver = CbcSolver())

# Decision variable for the total transported
@variable(m, x[i,j] >= 0)

# Capacity constraint
@constraint(m, cap[i = 1:2], sum(x[i,j] for j = 1:3) <= C[i])
# Demand constraint
@constraint(m, dem[j = 1:3], sum(x[i,j] for i = 1:2) >= D[j])

# Total distribution cost that we want to minimise
@objective(m, Min, sum(T[i,j]*x[i,j] for i=1:2, j=1:3))

println(m) # Prints the mathematical model for debugging
solve(m) # Solve the model

# Prints the optimal solution
println("\nDistribution plan: \n", getvalue(x))

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

# Example 2 - portfolio optimisation

using Ipopt, Plots
# Ipopt is for solving nonlinear problems - different technology
# Plots is for plotting (!)

# Read the daily prices data from a .csv file.
data = readcsv("prices.csv")
#First row has the names of the stocks
stock_names = data[1, 1:end-2]
#Last two columns has data and US$ rate
prices_data = data[2:end, 1:end-2]

#Having a peek at the data

cs = plot(prices_data, label = stock_names)
plotlyjs()

gui(cs)
#Returns are calculated as (p(t+1) - p(t))/p(t-1)
returns_data = diff(prices_data) ./ prices_data[1:end-1,:]

#Number of days and stocks in data
T, n =  size(returns_data)
#Calculates expected return and covariance
μ, Σ =  mean(returns_data, 1), cov(returns_data)
# Input form the model: minimum average return required.
r_min = 0.2

port = Model(solver=IpoptSolver())

#allocation variables
@variable(port, 0 <= x[1:n] <= 1);

#notice the division by T to correct the average
@constraint(port, sum(μ[j]*x[j] for j=1:n) >= r_min/T);

@objective(port, Min , sum(x[i]*Σ[i,j]*x[j] for i=1:n,j=1:n));

solve(port);

alloc = getvalue(x)
# Should be the same as r_min
ret = μ*alloc*T
# Looking at risk as the st. deviation of the returns.
risk = sqrt(alloc'*Σ*alloc*T)

# Organising the data for plotting. In an array, they are
# considered different series (with different colours)
stock_names = convert(Array{String}, stock_names)
alloc = convert(Array{Float64}, alloc)
# plots a pie chart
pie(stock_names, alloc, legend = false)

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

# Example 3 - Robust Knapsack Problem

using Distributions, ECOS, LaTeXStrings
# Distributions includes probability distributions,
# ECOS is a solver that we need because of the stucture of the problem
# LatexStrings is to write LaTeX in our plots

N = 18

#Input data
value = [50, 40, 70, 55, 80, 35, 65, 50, 60, 85, 20, 45, 55, 25, 80, 45, 45, 65]
weight_average = [3.5, 4, 5.5, 5, 6, 4.5, 6, 4, 5.5, 7, 5, 4.5, 7, 3.5, 5.5, 3.5, 4, 6.5]
weight_stdev = weight_average*0.3

#Randomly generating initial data using normal distribution
weight_data = Array{Float64}(100,N)
for j= 1:N
   weight_data[:,j] = rand(Normal(weight_average[j], weight_stdev[j]), 100)
end

#Input data: weight limit (capacity)
capacity = 20

# Protection elipsoid: adds 50% of weight as a protection level
P = 0.5*weight_average

# Declaring the model and solving it as a function.
function solve_robust_model(N, value, weight_average, P, Γ)
    m = Model(solver = ECOSSolver()) #creates the model, select the solver

    @variable(m, 0 <= x[1:N] <= 1 ) # creates the binary variables x, one for each item

    @constraint(m, sum(weight_average[j]*x[j] for j = 1:N) + Γ*norm(P'*x) <= capacity) # declare the knapsack constraint

    @objective(m, Max, sum(value[j]*x[j] for j = 1:N)) # declare the objective function

    solve(m)

    return getvalue(x)
end

# This function simulates the item selection againts feasibility.
function feasibility_estimate(solution, repetitions)
    feasible_count = 0
    actual_weight = zeros(18)
    for n= 1:repetitions
        for j= 1:N
           # generate random weights according to distribution
           actual_weight[j] = rand(Normal(weight_average[j], weight_stdev[j]))
        end
        #if total weight more than capacity => problem infeasible.
        if actual_weight'*solution <= capacity
            feasible_count += 1
        end
    end
    return feasible_count/repetitions
end

# generate a range of Gammas to try
Γ_range = linspace(0,1,10)

# storing the results of each run
feas = []
total_value = []
Γ_used = []

# for each Gamma, solve te model and store the results
for Γ in Γ_range
    println(Γ)
    x = solve_robust_model(N, value, weight_average, P, Γ)
    push!(Γ_used, Γ)
    push!(feas, feasibility_estimate(x,5000))
    push!(total_value, value'x)
end

# potting the results from simulation
p1 = plot(Γ_used, feas, xlabel = L"\Gamma", ylabel = "feas. prob.", legend=false)
p2 = plot(Γ_used, total_value, xlabel = L"\Gamma", ylabel = "total value", legend=false, color=:orange)
plot(p1,p2, layout = (2,1))

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

# Example 4 - Classification

# Generating random multivariate data.
data11 = rand(Normal(8, 4), 100)
data12 = rand(Normal(12, 2), 100)
data1 = [data11 data12]

data01 = rand(Normal(2, 6), 100)
data02 = rand(Normal(5, 2), 100)
data0 = [data01 data02]

scatter(data1[:,1], data1[:,2], label="Pos. obs.")
scatter!(data0[:,1], data0[:,2], label="Neg. obs.")

function classify(data1, data0, δ)
    m = Model(solver = IpoptSolver())

    @variable(m, u[1:100] >= 0)
    @variable(m, v[1:100] >= 0)
    @variable(m, a[1:2])
    @variable(m, b)

    @constraint(m, error1[i=1:100], sum(a[j]*data1[i,j] for j = 1:2) - b - u[i] <= -1)
    @constraint(m, error0[i=1:100], sum(a[j]*data0[i,j] for j = 1:2) - b + v[i] >= 1)

# This parameter trades off accuracy and robustness for the classifier
    @objective(m, Min, sum(u[i] for i=1:100) + sum(v[i] for i=1:100)
        + δ*(sum(a[j]^2 for j=1:2)))

    solve(m);

    return getvalue(a), getvalue(b), getvalue(u), getvalue(v)
end

a, b, u1, v1 = classify(data1, data0, 0.01);

# Calculating the points on plane defined by a and b
x1 = linspace(-10,20,100);
x2 = (b - a[1]*x1)/ a[2];
# boundaries of the slab
x2u = (b + 1 - a[1]*x1)/ a[2];
x2d = (b - 1 - a[1]*x1)/ a[2];
plot!(x1, [x2u x2 x2d],
    xlabel = "x_1",
    ylabel = "x_2",
    label= [ "", "Classifier", ""] ,
    color=[:green :green :green],
    linestyle=[ :dot :solid :dot],
    )

# another classifier, with δ=50
a, b, u2, v2 = classify(data1, data0, 100);
x2 = (b - a[1]*x1)/ a[2];
x2u = (b + 1 - a[1]*x1)/ a[2];
x2d = (b - 1 - a[1]*x1)/ a[2];
plot!(x1, [x2u x2 x2d],
    xlabel = "x_1",
    ylabel = "x_2",
    legend = false,
    color=[:purple :purple :purple],
    linestyle=[:dot :solid :dot],
    )


# Load Julia packages (libraries)

using Pkg, DrWatson

#@quickactivate "SR2StanPluto"
using StanSample
using StatisticalRethinking

# Define the Stan language model

stan8_5s = "
data{
  int N;
  vector[N] y;
}
parameters{
  real sigma;
  real a1;
  real a2;
}
model{
  real mu;
  a1 ~ normal(0, 10);
  a2 ~ normal(0, 10);
  mu = a1 + a2;
  y ~ normal( mu , sigma );
}
";

# Define the Stanmodel and set the output format to :mcmcchains.

m8_5s = SampleModel("m8.5s", stan8_5s);

# Input data for cmdstan

m8_5_data = Dict("N" => 100, "y" => rand(Normal(0, 1), 100));

# Sample using cmdstan

rc8_5s = stan_sample(m8_5s, data=m8_5_data);

rethinking = "
       mean   sd   5.5% 94.5% n_eff Rhat
a1    -0.08 7.15 -11.34 11.25  1680    1
a2    -0.05 7.15 -11.37 11.19  1682    1
sigma  0.90 0.07   0.81  1.02  2186    1
";

# Describe the draws
if success(rc8_5s)
  part8_5s = read_samples(m8_5s, :particles)
  part8_5s |> display
end


const EARTH_RADIUS_EQUATORIAL = 6378137.0
const EARTH_RADIUS_POLAR = 6356752.3

"""
`CoordinateDistance(lat1, lon1, lat2, lon2; calcfunc, radius)`

Description
===========

Calculates distance between (lat1, lon1) and (lat2, lon2).

Usage
=====

  CoordinateDistance(latStart, longStart, latEnd, longEnd)

  CoordinateDistance(latStart, longStart, latEnd, longEnd, calcfunc=haversine)

  CoordinateDistance(latStart, longStart, latEnd, longEnd, radius=EARTH_RADIUS_POLAR)

  CoordinateDistance((latStart, longStart), (latEnd, longEnd))

Arguments
=========

- **`lat1`** : Latitude of the first point. Must be a float.
- **`lon1`** : Longitude of the first point. Must be a float.
- **`lat2`** : Latitude of the second point. Must be a float.
- **`lon2`** : Longitude of the second point. Must be a float.
- **`calcfunc`** : Function that calculates distance between point 1 and 2. Defaults to
  Vincenty. Haversine and Flat Pythagorean is also available.
- **`radius`** : Radius of the Earth. Defaults to EARTH_RADIUS_EQUATORIAL - about 6378.137KM.
  EARTH_RADIUS_POLAR is also available.
"""
function coord_distance(
          lat1::AbstractFloat,
          lon1::AbstractFloat,
          lat2::AbstractFloat,
          lon2::AbstractFloat;
          calcfunc::Function=vincenty,
          radius::AbstractFloat=EARTH_RADIUS_EQUATORIAL,
          units::Unitful.FreeUnits=u"m",
          returntype::Type=AbstractFloat, args...)
  v = calcfunc(lat1, lon1, lat2, lon2, radius; args...)
  v = isnan(v) ? 0. : v
  units !== u"m" ? dist_convert(v, units, returntype) : v
end

function coord_distance(
          coord1::Coordinate,
          coord2::Coordinate;
          args...)
  v = coord_distance(coord1[1], coord1[2], coord2[1], coord2[2]; args...)
end

"""
```julia
haversine(
  lat1::AbstractFloat,
  lon1::AbstractFloat,
  lat2::AbstractFloat,
  lon2::AbstractFloat,
  radius::AbstractFloat
)
```

Haversine Formula for Great Circle Distance
========================
The Haversine formula presides on spherical trigonometry to calculate
straight distance between two points on a unit sphere; that is to say
"how the crow flies" across a perfect sphere.
"""
function haversine(
          lat1::AbstractFloat,
          lon1::AbstractFloat,
          lat2::AbstractFloat,
          lon2::AbstractFloat,
          radius::AbstractFloat; args...)::AbstractFloat
  lat1 = deg2rad(lat1)
  lon1 = deg2rad(lon1)
  lat2 = deg2rad(lat2)
  lon2 = deg2rad(lon2)

  latΔ = lat2 - lat1
  lonΔ = lon2 - lon1

  a = sin(.5latΔ)^2 + cos(lat1)*cos(lat2)*sin(.5lonΔ)^2
  c = 2asin(min(1, sqrt(a)))

  return radius * c
end

"""
```julia
flatpythagorean(
  lat1::AbstractFloat,
  lon1::AbstractFloat,
  lat2::AbstractFloat,
  lon2::AbstractFloat,
  radius::AbstractFloat
)
```

Pythagorean Theorem for Coordinate Distance
========================
Utilizing Pythagorean's Theorem to calculate coordinate distance
assumes that the Earth is flat. While erroneous, when the distance
between the points is less than 20 kilometers the expected error
for the majority of the United States will be less than 20 meters.
Latitudes above 50 degrees (Alaska) will be less than 30 meters, and
latitudes below 30 degrees (Southern US / Hawaii) will be less than
9 meters.

"""
function flatpythagorean(
          lat1::AbstractFloat,
          lon1::AbstractFloat,
          lat2::AbstractFloat,
          lon2::AbstractFloat,
          radius::AbstractFloat; args...)::AbstractFloat
  lat1 = deg2rad(lat1)
  lon1 = deg2rad(lon1)
  lat2 = deg2rad(lat2)
  lon2 = deg2rad(lon2)

  a = π/2 - lat1
  b = π/2 - lat2
  c = sqrt(a^2 + b^2 - 2*a*b*cos(lon2-lon1))

  return radius * c
end

"""
```julia
vincenty(
  lat1::AbstractFloat,
  lon1::AbstractFloat,
  lat2::AbstractFloat,
  lon2::AbstractFloat
  radiusA::AbstractFloat=EARTH_RADIUS_EQUATORIAL;
  radiusB::AbstractFloat=EARTH_RADIUS_POLAR,
  tol::AbstractFloat=1e-12,     # Error tolerance
  maxiter::Int=1000,            # Maximum iterations before convergence
  verbose::Bool=true            # Print out warnings
)
```

Vincenty's Formula for Great Circle Distance
========================
The Vincenty formula provides improved accuracy over the Haversine implementation
at the expense of some added complexity and diminished computational performance.
Unlike the Haversine formula, Vincenty's solution assumes the sphere is oblate (flattened)
which is a more appropriate shape for the Earth's actual curvature.
"""
function vincenty(
          lat1::AbstractFloat,
          lon1::AbstractFloat,
          lat2::AbstractFloat,
          lon2::AbstractFloat,
          radiusA::AbstractFloat=EARTH_RADIUS_EQUATORIAL;
          radiusB::AbstractFloat=EARTH_RADIUS_POLAR,
          tol::AbstractFloat=1e-12,
          maxiter::Int=1000,
          verbose::Bool=true, args...)::AbstractFloat
  lat1 = deg2rad(lat1)
  lon1 = deg2rad(lon1)
  lat2 = deg2rad(lat2)
  lon2 = deg2rad(lon2)

  a = radiusA
  b = radiusB
  ƒ = (a - b) / a
  b = (1 - ƒ)*a

  u1 = atan((1-ƒ)*tan(lat1))
  u2 = atan((1-ƒ)*tan(lat2))
  L = lon2 - lon1
  sinσ = 0.
  cosσ = 0.
  cos2α = 0.
  cos2σm = 0.
  σ = 0.

  λ = copy(L)
  λ2 = λ
  δ = 1. + tol
  n = 0

  while δ > tol && n < maxiter
    n += 1
    sinσ = sqrt( (cos(u2)*sin(λ))^2 + (cos(u1)*sin(u2)-sin(u1)*cos(u2)*cos(λ))^2 )
    cosσ = sin(u1)*sin(u2) + cos(u1)*cos(u2)*cos(λ)
    σ = atan(sinσ/cosσ)
    sinα = (cos(u1)*cos(u2)*sin(λ))/sinσ
    cos2α = 1 - sinα^2
    cos2σm = cosσ - (2*sin(u1)*sin(u2))/cos2α
    C = ƒ/16 * cos2α*(4 + ƒ*(4-3cos2α))

    x1 = cos2σm + C*cosσ*(-1+2cos2σm^2)
    x2 = σ + C*sinσ*x1
    λ = L + (1-C)*ƒ*sinα*x2
    δ = abs(λ-λ2)
    λ2 = copy(λ)
  end

  if n == maxiter && δ > tol && verbose
    println("Warning: vincenty formula did not converge to desired tolerance.")
  end

  μ2 = cos2α * (a^2 - b^2)/(b^2)
  A = 1 + μ2/16384*(4096 + μ2*(-768+μ2*(320-175μ2)))
  B = (μ2/1024)*(256 + μ2*(-128+μ2*(74-47μ2)))

  x3 = cos2σm + .25B*(cosσ*(-1+2cos2σm^2))
  x4 = (1/6)*B*cos2σm*(-3+4sinσ^2)*(-3+4cos2σm^2)
  Δσ = B*sinσ*(x3-x4)

  return b*A*(σ - Δσ)
end


# circle.jl

abstract type AbstractHyperSphere{N,T} <: EuclideanDomain{N,T} end

"""
The unit sphere (of radius 1) in `N` dimensions.
"""
struct UnitHyperSphere{N,T} <: AbstractHyperSphere{N,T} end

UnitHyperSphere{N}() where N = UnitHyperSphere{N,Float64}()

const UnitCircle{T} = UnitHyperSphere{2,T}
const UnitSphere{T} = UnitHyperSphere{3,T}

convert(::Type{Domain{SVector{N,T}}}, d::UnitHyperSphere{N}) where {N,T} =
    UnitHyperSphere{N,T}()

indomain(x, ::UnitHyperSphere) = norm(x) == 1

approx_indomain(x, ::UnitHyperSphere, tolerance) = 1-tolerance <= norm(x) <= 1+tolerance

boundary(::UnitHyperBall{N,T}) where {N,T} = UnitHyperSphere{N,T}()

isempty(::UnitHyperSphere) = false

"Create an ellipse curve with semi-axes lengths `a` and `b` respectively."
ellipse(a::Number, b::Number) = ellipse(promote(a,b)...)
ellipse(a::T, b::T) where {T <: Number} = scaling_map(a, b) * UnitCircle{T}()

"Create an ellipse-shaped domain with semi-axes lengths `a` and `b` respectively."
ellipse_shape(a::Number, b::Number) = ellipse_shape(promote(a,b)...)
ellipse_shape(a::T, b::T) where {T <: Number} = scaling_map(a, b) * UnitDisk{T}()

"""
The map `[cos(2πt), sin(2πt)]` from `[0,1)` to the unit circle in `ℝ^2`.
"""
struct UnitCircleMap{S,T} <: AbstractMap{S,T} end

parameterization(d::UnitCircle) = UnitCircleMap{subeltype(d),eltype(d)}()

domain(d::UnitCircleMap{S}) where S = HalfOpenRightInterval{S}(0, 1)

image(m::UnitCircleMap{S}) where S = UnitCircle{S}()

applymap(m::UnitCircleMap{S}, t) where S = SVector(cos(2*S(pi)*t), sin(2*S(pi)*t))

function gradient(m::UnitCircleMap{S}, t) where S
    a = 2*S(pi)
    SVector(-a*sin(a*t), a*cos(a*t))
end

"""
`AngleMap` is a left inverse of `UnitCircleMap`. A 2D vector `x` is projected onto
the intersection point with the unit circle of the line connecting `x` to the
origin. The angle of this point, scaled to the interval `[0,1)`, is the result.
"""
struct AngleMap{S,T} <: AbstractMap{S,T}
end

domain(d::AngleMap{S,T}) where {S,T} = FullSpace{S}()

range(m::AngleMap{S,T}) where {S,T} = HalfOpenRightInterval{T}(0, 1)

function applymap(m::AngleMap, x)
    twopi = 2*convert(codomaintype(m), pi)
    θ = atan(x[2],x[1])
    if θ < 0
        # atan2 returns an angle in (-π,π], convert to [0,2π) using periodicity.
        θ += twopi
    end
    # And divide by 2π to scale to [0,1)
    θ / twopi
end

left_inverse(m::UnitCircleMap{S,T}) where {S,T} = AngleMap{T,S}()

right_inverse(m::AngleMap{S,T}) where {S,T} = UnitCircleMap{T,S}()


# plot gaussian probabilistic distribution
# using cumulative distribution function

module GaussProbDistCdf
    using Plots, Distributions
    pyplot()

    function main(is_test=false)
        norm_dist = Normal(209.7, 4.84) # mu, std_dev

        zs = Array(193:229)
        ys = [cdf(norm_dist, z) for z in zs]
        plot(zs, ys, color=:red, label="CDF")

        if is_test == false
            save_path = joinpath(split(@__FILE__, "src")[1], "src/prob_stats/prob_model/gauss_prob_dist_cdf/gauss_prob_dist_cdf.png")
            savefig(save_path)
        end
    end
end


function agm(x::T, y::T) where {T<:Real}
    a, b = x, y
    epsilon = eps(min(a,b))

    signbit(a) != signbit(b) && throw(DomainError("$a * $b must be nonnegative"))

    while abs(a-b) > epsilon
     c = (a + b) * 0.5
     b = sqrt(a * b)
     a = c
    end

    return a
end

agm1(x::T) where {T<:Real} = agm(one(T), x)


module Functions
export sigmoid, relu, softmax, cross_entropy_error

# TODO:関数の内部にdotを付けるのではなく，関数の呼び出し元にdotを付けるように変更
function sigmoid(x)
    return 1 ./ (1 .+ exp.(-x))
end

function relu(x)
    return max(0, x) # NOT maximum()
end

function softmax(x)
    y = []
    for row_i = 1:size(x, 1)
        c = maximum(x[row_i, :])
        exp_a = exp.(x[row_i, :] .- c) # オーバフロー対策
        push!(y, exp_a ./ sum(exp_a))
    end
    return hcat(y...)' # cf. https://docs.julialang.org/en/v1/manual/faq/?highlight=splat#What-does-the-...-operator-do?
end

function cross_entropy_error(y, t)
    return -sum(t .* log.(y .+ 1e-7)) # 0除算を回避するために微小な値を加算
end
end


using DifferentialEquations
using Plots
plotlyjs()

#constants
g=9.81       #acceleration due to gravity [ms^-2]
m=3000       #mass of capsule [Kg]

#initial conditions
u₀=[3,0.1,0.1,0.5]
r₀=6
v₀=0

#function to compute lagrangian
function lagrangian!(du,u,p,t)
    θ,ω,ϕ,ψ=u
    v=v₀
    r=r₀+v*t
    du[1]=dθ=ω
    du[2]=dω=sin(θ)*cos(θ)*ψ^2-((2*v*ω)/r)+((g*sin(θ))/r)
    du[3]=dϕ=ψ
    du[4]=dψ=-((2*v*ψ)/r)-(2*cot(θ)*ω*ψ)
end

#time interval
tspan=(0.0,20.0)

#defining the problem and solving
prob=ODEProblem(lagrangian!,u₀,tspan)
sol1=solve(prob,saveat=0.1,reltol=1e-1,abstol=1e-4)
sol2=solve(prob,saveat=0.1)
sol3=solve(prob,saveat=0.1,reltol=1e-5,abstol=1e-8)

#extracting solution
U1=sol1[1:end,:]
(θ1,ω1,ϕ1,ψ1)=[U1[x,:] for x in 1:size(U1,1)]
t=sol1.t

U2=sol2[1:end,:]
(θ2,ω2,ϕ2,ψ2)=[U2[x,:] for x in 1:size(U2,1)]
t=sol2.t

U3=sol3[1:end,:]
(θ3,ω3,ϕ3,ψ3)=[U3[x,:] for x in 1:size(U3,1)]
t=sol3.t

#plotting the solutions for different tolerances

p1=plot(sol1,vars=(0,1),label="rtol=1e-1,atol=1e-4",
    color="green", xaxis="t (s)", yaxis="θ (rad)")
p2=plot!(p1,sol2,vars=(0,1),label="rtol=1e-3,atol=1e-6",
    color="blue", xaxis="t (s)", yaxis="θ (rad)")
p3=plot!(p2,sol3,vars=(0,1),label="rtol=1e-5,atol=1e-8",
    color="red", xaxis="t (s)", yaxis="θ (rad)")
display(plot(p3))
savefig("./plots/error_control_1.png")

p4=plot(sol1,vars=(0,3),label="rtol=1e-1,atol=1e-4",
    color="green", xaxis="t (s)", yaxis="ϕ (rad)")
p5=plot!(p4,sol2,vars=(0,3),label="rtol=1e-3,atol=1e-6",
    color="blue", xaxis="t (s)", yaxis="ϕ (rad)")
p6=plot!(p5,sol3,vars=(0,3),label="rtol=1e-5,atol=1e-8",
    color="red", xaxis="t (s)", yaxis="ϕ (rad)")
display(plot(p6))
savefig("./plots/error_control_2.png")

Modeling the Dragon Module as a Pendulum/Scripts/ex2-case-1-error-control.jl


"""
# Usage

    solve(prob::SingleTermFODEProblem, h, GL())
Grunwald Letnikov method for fractional ordinary differential equations

```tex
@INPROCEEDINGS{8742063,
author={Clemente-López, D. and Muñoz-Pacheco, J. M. and Félix-Beltrán, O. G. and Volos, C.},
booktitle={2019 8th International Conference on Modern Circuits and Systems Technologies (MOCAST)},
title={Efficient Computation of the Grünwald-Letnikov Method for ARM-Based Implementations of Fractional-Order Chaotic Systems},
year={2019},
doi={10.1109/MOCAST.2019.8742063}}
```
"""
struct GL <: FractionalDiffEqAlgorithm end

function solve(FODE::SingleTermFODEProblem, h, ::GL)
    @unpack f, α, u0, tspan = FODE
    t0 = tspan[1]; T = tspan[2]
    N::Int = floor(Int, (T-t0)/h)+1
    c = zeros(Float64, N)

    cp::Float64 = 1.0
    for j = 1:N
        c[j] = (1-(1+α)/j)*cp
        cp = c[j]
    end

    # Initialization
    y = zeros(Float64, N)
    y[1] = u0

    @fastmath @inbounds @simd for i = 2:N
        right = 0
        @fastmath @inbounds @simd for j=1:i-1
            right += c[j]*y[i-j]
        end
        y[i] = f(t0+(i-1)*h, y[i-1])*h^α - right
    end
    return FODESolution(collect(t0:h:T), y)
end


# Parts of this code were taken / derived from Graphs.jl. See LICENSE for
# licensing details.

"""
    struct FloydWarshallState{T, U}

An [`AbstractPathState`](@ref) designed for Floyd-Warshall shortest-paths calculations.
"""
struct FloydWarshallState{T, U<:Integer}<:AbstractPathState
    dists::Matrix{T}
    parents::Matrix{U}
end

@doc_str """
floyd_warshall_shortest_paths(g, distmx=DefaultDistance())
Use the [Floyd-Warshall algorithm](http://en.wikipedia.org/wiki/Floyd–Warshall_algorithm)
to compute the shortest paths between all pairs of vertices in graph `g` using an
optional distance matrix `distmx`. Return a [`FloydWarshallState`](@ref) with relevant
traversal information.

### Performance
Space complexity is on the order of ``\\mathcal{O}(|V|^2)``.
"""
function floyd_warshall_shortest_paths{T}(
    g::AbstractGraph,
    distmx::AbstractMatrix{T} = DefaultDistance()
)
    U = eltype(g)
    n_v = nv(g)
    dists = fill(typemax(T), (Int(n_v),Int(n_v)))
    parents = zeros(U, (Int(n_v),Int(n_v)))

    # fws = FloydWarshallState(Matrix{T}(), Matrix{Int}())
    for v in 1:n_v
        dists[v,v] = zero(T)
    end
    undirected = !is_directed(g)
    for e in edges(g)
        u = src(e)
        v = dst(e)

        d = distmx[u,v]

        dists[u,v] = min(d, dists[u,v])
        parents[u,v] = u
        if undirected
            dists[v,u] = min(d, dists[v,u])
            parents[v,u] = v
        end
    end
    for w in vertices(g), u in vertices(g), v in vertices(g)
        if dists[u,w] == typemax(T) || dists[w,v] == typemax(T)
            ans = typemax(T)
        else
            ans = dists[u,w] + dists[w,v]
        end
        if dists[u,v] > ans
            dists[u,v] = dists[u,w] + dists[w,v]
            parents[u,v] = parents[w,v]
        end
    end
    fws = FloydWarshallState(dists, parents)
    return fws
end

function enumerate_paths(s::FloydWarshallState{T, U}, v::Integer) where T where U<:Integer
    pathinfo = s.parents[v,:]
    paths = Vector{Vector{U}}()
    for i in 1:length(pathinfo)
        if (i == v) || (s.dists[v,i] == typemax(T))
            push!(paths, Vector{U}())
        else
            path = Vector{U}()
            currpathindex = i
            while currpathindex != 0
                push!(path,currpathindex)
                currpathindex = pathinfo[currpathindex]
            end
            push!(paths, reverse(path))
        end
    end
    return paths
end

enumerate_paths(s::FloydWarshallState) = [enumerate_paths(s, v) for v in 1:size(s.parents,1)]
enumerate_paths(st::FloydWarshallState, s::Integer, d::Integer) = enumerate_paths(st, s)[d]


# Practical Review: Extremum Estimators (Ch. 13)

##
#= 	Let's start with a simple problem, where we can get the the answers using
	analytic methods. This will let us apply numeric methods, and we will be
	able to verify that they give us 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);

##

#= 	OK, now let's do this as we would if we did not have access to
	the analytic results. First, we compute the extremum estimator
	using numeric minimization.
=#
using Statistics, Optim
objᵢ = β -> (y-x*β).^2.  	# iᵗʰ obs contrib to sum of squares
obj = β -> mean(objᵢ(β))	# average objective
tol = 1e-08
βhat = Optim.optimize(obj, zeros(2), LBFGS(),
        	Optim.Options(g_tol = tol,x_tol=tol,f_tol=tol);
			autodiff=:forward).minimizer

##

#=	How do we get the estimated standard errors, using extremum estimation
	theory? We need estimators of ℐ, the covariance of the score
	contributions, and 𝒥, the Hessian matrix.
=#

# the calculations to get ℐ hat

# first, we need the score contributions, the matrix that collects
# the derivatives of each observation's contribution to the objective

# Note to self: remember crtl-enter will evaluate a single line.

using ForwardDiff
sc =  ForwardDiff.jacobian(objᵢ, βhat) # get the score contributions
# from theory, we know that sc = -2x.*ϵcat. Let's use this verify
# that the automatic differentiation worked.
sc - (-2x.*(y - x*βhat))

##

# Next, we use the score contributions to estimate ℐ
ℐhat = zeros(2,2)
for i = 1:n
	ℐhat .+= sc[i,:]*sc[i,:]'
end
ℐhat ./= n
# you could also use simply cov(sc), which converges to the same thing.

##

# next, we need the estimate of the limiting Hessian, which is just
# the Hessian of the objective function, at the estimate
𝒥hat = ForwardDiff.hessian(obj, βhat)

# We know that this should be 2x'x/n. Let's check:
2x'x/n
##

# now, we can compute the estimated standard errors, to compare to what we saw
# from the analytic results, above
using LinearAlgebra
v∞ = inv(𝒥hat)*ℐhat*inv(𝒥hat) # this is the estimate of the limiting var of √n(β-β⁰)
se = sqrt.(diag(v∞/n))   # to get small sample est. variance, divide by n

##

# the last problem is for a correctly specified model, Case I in the notes.
# Let's look at an incorrectly specified model, Case III in the notes, to
# verify that the extremum theory works here, too.

# Let's work with Problem 1, in the exercises at the end of Chapter 13.
# In this problem, the true model is quadratic, but we erroneously estimate
# a linear model. The problem asks for an analytic solution. Here, let's
# approach it numerically.
# generate data
using Plots
function dgp(n)
	x = sort(rand(n))
	y = 1. .- x.^2 + randn(n)
	x,y
end
x,y = dgp(100)
scatter(x,y, legend=false, title="y = 1 - x² + N(0,1)")

##

# Here's an OLS fit, just to have a look
using Econometrics
n = 100
x,y = dgp(n)
X = [ones(n) x] # define regressor matrix for linear approximation about 0
b, junk = ols(y,X)
fitted = X*b
plot!(x,fitted)

##

# We know that the pseudo-true parameter values are 7/6 and -1. Let's verify
# that the OLS estimates are asymptotically normally distributed about these
# values. To do this, we will construct asymptotic 100x(1-α)% confidence intervals,
# and verify that the pseudo-true values lie inside them approximately 100x(1-α)% of
# the times we repeat the procedure, at least when n is large enough
using LinearAlgebra, Statistics, Distributions
n = 20 				# try small and large values here,
					# to see accuracy of asymptotic approximation
reps = 10000
α = 0.05			# try out 90, 95 and 99% CIs
crit = quantile(Normal(),1-α/2)
β⁰ = [7/6; -1.]
inci = zeros(reps, 2)
for i = 1:reps
	x,y = dgp(n)
	X = [ones(n) x] # define regressor matrix for linear approximation about 0
	βhat, vβhat, junk = ols(y, X, silent=true)
	se = sqrt.(diag(vβhat))
	inci[i,:] = (β⁰ .>= βhat .- crit*se) .& (β⁰ .<= βhat .+ crit*se)
end
ci = Int64(100*(1-α))
println("Coverage of $ci% CIs")
mean(inci, dims=1) # these should be approximately 1-α, at least when n is large enough

##

# To conclude, this summary has shown that the extremum estimation theory is
# working for two cases, a correctly specified model, and an incorrectly specified
# model. We have seen how to get I and J, now to compute the asymptotic variance,
# and how to verify that confidence intervals (which are computed using I and J)
# have correct coverage, at least asymptotically.


using ITensors,
      LinearAlgebra,
      QuadGK

const βc = 0.5*log(sqrt(2.0)+1.0)

function ising_free_energy(β::Real,J::Real=1.0)
  k = β*J
  c = cosh(2.0*k)
  s = sinh(2.0*k)
  xmin = 0.0
  xmax = π
  integrand(x) = log(c^2+sqrt(s^4+1-2*s^2*cos(x)))
  integral,err = quadgk(integrand, xmin, xmax)::Tuple{Float64,Float64}
  return -(log(2.0)+integral/π)/(2.0*β)
end

function ising_magnetization(β::Real)
 β>βc && return (1.0-sinh(2.0*β)^(-4))^(1/8)
 return 0.0
end

function ising_mpo(sh::Tuple{Index,Index},sv::Tuple{Index,Index},
                   β::Real,J::Real=1.0;
                   sz::Bool=false,dual_lattice::Bool=true)
  d = dim(sh[1])
  T = ITensor(sh[1],sh[2],sv[1],sv[2])
  if dual_lattice
    for i = 1:d
      T[i,i,i,i] = 1.0
    end
    sz && (T[1,1,1,1] = -T[1,1,1,1])
    Q = [exp(β*J) exp(-β*J); exp(-β*J) exp(β*J)]
    D,U = eigen(Symmetric(Q))
    √Q = U*Diagonal(sqrt.(D))*U'
    Xh1 = ITensor(vec(√Q),sh[1],sh[1]')
    Xh2 = ITensor(vec(√Q),sh[2],sh[2]')
    Xv1 = ITensor(vec(√Q),sv[1],sv[1]')
    Xv2 = ITensor(vec(√Q),sv[2],sv[2]')
    T = replacetags(T*Xh1*Xh2*Xv1*Xv2,"1","0")
  else
    sig(s) = 1.0-2.0*(s-1)
    E0 = -4.0
    for s1 = 1:d, s2 = 1:d, s3 = 1:d, s4 = 1:d
      E = sig(s1)*sig(s2)+sig(s2)*sig(s3)+sig(s3)*sig(s4)+sig(s4)*sig(s1)
      val = exp(-β*(E-E0))
      T[sh[1](s1),sv[2](s2),sh[2](s3),sv[1](s4)] = val
    end
  end
  return T
end


using FractalTools
using Plots
using Cubature

# Definitions
ti, ts, tf = 0, 0.1, 1.
t = ti : ts : tf
f(t) = sin(2π * t)
g(t) = cos(2π * t)
# f(t) = t * (1 - t)

# Construct interpolation
interp = interpolate(t, f.(t), d=0.01)

# Construct hidden interpolation
hiddeninterp = hiddenfif(t, f.(t), g.(t))

# Quadratute integration
hquad(tk) = hquadrature(f, ti, tk, abstol=1e-9)[1]

# Fractal integration
hfrac(tk) = integrate(ti:ts:tk, f.(ti:ts:tk), d=0.01)

# Fractal hidden integration
hhiddenfrac(tk) = integrate(ti:ts:tk, f.(ti:ts:tk), g.(ti:ts:tk))

# Plots
td = 0 : 0.01 * ts : tf
plot(td, f.(td), label="f")
plot!(td, hquad.(td), label="intfquad")
plot!(td, hfrac.(td), label="intffrac")
plot!(td, hhiddenfrac.(td), label="intfhiddenfrac")
plot!(td, getindex.(hiddeninterp.(td), 1), label="fhiddenfrac")
scatter!(t, f.(t), marker=(:circle, 2), label="data")


using NDTensors,
      Test
using LinearAlgebra

@testset "random_orthog" begin
  n,m = 10,4
  O1 = random_orthog(n,m)
  @test norm(transpose(O1)*O1 - Diagonal(fill(1.,m))) < 1E-14

  n,m = 4,10
  O2 = random_orthog(n,m)
  @test norm(O2*transpose(O2) - Diagonal(fill(1.,n))) < 1E-14
end

@testset "random_unitary" begin
  n,m = 10,4
  U1 = random_unitary(n,m)
  @test eltype(U1) <: ComplexF64
  @test norm(U1'*U1 - Diagonal(fill(1.,m))) < 1E-14

  n,m = 4,10
  U2 = random_unitary(n,m)
  @test norm(U2*U2' - Diagonal(fill(1.,n))) < 1E-14
end

nothing


"""
    q4

Compute the solution of the Poisson equation of heat conduction with a nonzero
heat source. Quadrilateral four-node elements are used.
"""
module q4

using LinearAlgebra
using SparseArrays
using MeshCore.Exports
using MeshCore: @_check
using MeshSteward.Exports
using Elfel.Exports
using UnicodePlots

A = 1.0 # length of the side of the square
kappa =  1.0; # conductivity matrix
Q = -6.0; # internal heat generation rate
tempf(x, y) =(1.0 + x^2 + 2.0 * y^2);#the exact distribution of temperature
N = 1000;# number of subdivisions along the sides of the square domain

function genmesh()
    conn = Q4block(A, A, N, N)
    mesh = Mesh()
    attach!(mesh, conn)
    return mesh
end

function assembleKF(fesp, kappa, Q)
    function integrate!(am, av, geom, elit, qpit, kappa, Q)
        nedof = ndofsperel(elit)
        ke = LocalMatrixAssembler(nedof, nedof, 0.0)
        fe = LocalVectorAssembler(nedof, 0.0)
        for el in elit
            init!(ke, eldofs(el), eldofs(el))
            init!(fe, eldofs(el))
            for qp in qpit
                Jac, J = jacjac(el, qp)
                gradN = bfungrad(qp, Jac)
                JxW = J * weight(qp)
                N = bfun(qp)
                for j in 1:nedof
                    for i in 1:nedof
                        ke[i, j] += dot(gradN[i], gradN[j]) * (kappa * JxW)
                    end
                    fe[j] += N[j] * Q * JxW
                end
            end
            assemble!(am, ke)
            assemble!(av, fe)
        end
        return am, av
    end

    elit = FEIterator(fesp)
    qpit = QPIterator(fesp, (kind = :Gauss, order = 2))
    geom = geometry(fesp.mesh)
    am = start!(SysmatAssemblerSparse(0.0), ndofs(fesp), ndofs(fesp))
    av = start!(SysvecAssembler(0.0), ndofs(fesp))

    @time integrate!(am, av, geom, elit, qpit, kappa, Q)

    return finish!(am), finish!(av)
end

function solve!(T, K, F, nu)
    @time KT = K * T
    @time T[1:nu] = K[1:nu, 1:nu] \ (F[1:nu] - KT[1:nu])
end

function checkcorrectness(fesp)
    geom = geometry(fesp.mesh)
    ir = baseincrel(fesp.mesh)
    T = attribute(ir.right, "T")
    std = 0.0
    for i in 1:length(T)
        std += abs(T[i][1] - tempf(geom[i]...))
    end
    @_check (std / length(T)) <= 1.0e-9
end

function run()
    mesh = genmesh()
    fesp = FESpace(Float64, mesh, FEH1_Q4())
    bir = boundary(mesh);
    vl = connectedv(bir);
    locs = geometry(mesh)
    for i in vl
        setebc!(fesp, 0, i, 1, tempf(locs[i]...))
    end
    numberfreedofs!(fesp)
    numberdatadofs!(fesp)
    @show nunknowns(fesp)
    K, F = assembleKF(fesp, kappa, Q)
    T = fill(0.0, ndofs(fesp))
    gathersysvec!(T, fesp)
    solve!(T, K, F, nunknowns(fesp))
    scattersysvec!(fesp, T)
    makeattribute(fesp, "T", 1)
    checkcorrectness(fesp)
    vtkwrite("q4-T", baseincrel(mesh), [(name = "T",)])
end

end

@time q4.run()
# q4.run()


# ---
# jupyter:
#   jupytext:
#     formats: ipynb,jl:hydrogen
#     text_representation:
#       extension: .jl
#       format_name: hydrogen
#       format_version: '1.3'
#       jupytext_version: 1.11.2
#   kernelspec:
#     display_name: Julia 1.6.2
#     language: julia
#     name: julia-1.6
# ---

# %% [markdown]
# https://discourse.julialang.org/t/solving-a-petersen-matrix-with-julia/67674

# %%
using DifferentialEquations
using Parameters
using StaticArrays
using Plots

# %%
function f(u, p, t)
    @unpack M, k1, kf, kr, kcat = p
    A, B, S, E, ES, P = u
    v = SVector(k1*A*B^2, kf*E*S, kr*ES, kcat*ES)
    M*v
end

# %%
M = [
    -1  0  0  0
    -2  0  0  0
    +1 -1 +1  0
     0 -1 +1 +1
     0 +1 -1 -1
     0  0  0 +1
]

p = (
    M = SMatrix{6, 4, Float64}(M),
    k1 = 1.0,
    kf = 1.0,
    kr = 1.0,
    kcat = 1.0,
)

u0 = SVector(
    #= A  =# 1.5,
    #= B  =# 1.2,
    #= S  =# 0.0,
    #= E  =# 1.0,
    #= ES =# 0.0,
    #= P  =# 0.0,
)

tspan = (0.0, 10.0)

# %%
prob = ODEProblem(f, u0, tspan, p)
sol = solve(prob)
label = ["A" "B" "S" "E" "ES" "P"]
linestyle = [:dash :dash :dashdot :dashdot :solid :solid]
plot(sol; label, linestyle, lw=1.5)

# %%
function g(u, p, t)
    @unpack M, k1, kf, kr, kcat = p
    A, B, S, E, ES, P = u
    dA = -k1*A*B^2
    dB = -2k1*A*B^2
    dS = k1*A*B^2 - kf*E*S + kr*ES
    dE  = -kf*E*S + kr*ES + kcat*ES
    dES =  kf*E*S - kr*ES - kcat*ES
    dP = kcat*ES
    SVector(dA, dB, dS, dE, dES, dP)
end

# %%
prob = ODEProblem(g, u0, tspan, p)
sol = solve(prob)
label = ["A" "B" "S" "E" "ES" "P"]
linestyle = [:dash :dash :dashdot :dashdot :solid :solid]
plot(sol; label, linestyle, lw=1.5)

# %%


# This file investigates MSE with respect to free variables

using FractalTools
using GeometryBasics
using Makie

# Construct interpolation data
f(x, y) = [
    x^2 + y^2 + 1,
    x^2 - y^2
    ]
# ngon = Triangle(
#     Point(BigFloat(0.), BigFloat(0.)),
#     Point(BigFloat(1.), BigFloat(0.)),
#     Point(BigFloat(0.5), BigFloat(1.)))

ngon = Triangle(
    Point(BigFloat(-5.), BigFloat(-5.)),
    Point(BigFloat(5), BigFloat(-5.)),
    Point(BigFloat(0), BigFloat(5.)))
npts = 100
pts = getdata(f, ngon, npts)

# Construct test data
tpts = getdata(ngon, npts)
ntpts = length(tpts)

# Compute errors
fvals = getindex.(map(pt -> f(pt...), tpts), 1)
freevars = 0.001 : 0.001 : 0.025
mse = map(freevars) do freevar
    interp = interpolate(pts, HInterp2D(fill(freevar, 2, 2)))
    ivals = getindex.(map(pt -> interp(pt...), tpts), 1)
    sum((fvals - ivals).^2) / ntpts
end

# Plot mse
fig = Figure()
ax = fig[1, 1] = Axis(fig, xlabel="Free Variable", ylabel="MSE", title="2D Hidden Interpolation MSE")
stem!(ax, freevars, mse, color=:black)
save(joinpath(@__DIR__, "hinterp2d_mse.png"), fig)
display(fig)


#!/usr/bin/julia

# Trizen
# 17 September 2016
# https://github.com/trizen

# A decently efficient algorithm for computing the results of the Kempner/Smarandache function.

# See also: https://projecteuler.net/problem=549
#           https://en.wikipedia.org/wiki/Kempner_function
#           http://mathworld.wolfram.com/SmarandacheFunction.html

# ∑S(i) for 2 ≤ i ≤ 10^2 == 2012
# ∑S(i) for 2 ≤ i ≤ 10^6 == 64938007616
# ∑S(i) for 2 ≤ i ≤ 10^8 == 476001479068717

using Primes

function smarandache(n::Int64, cache)

    isprime(n) && return n

    f = factor(n)

    count = 0
    distinct = true
    for v in values(f)
        count += v
        if (distinct && v != 1)
            distinct = false
        end
    end

    distinct && return maximum(keys(f))

    if (length(f) == 1)

        k = collect(keys(f))[1]

        (count <= k) && return k*count

        if haskey(cache, n)
            return cache[n]
        end

        max = k*count
        ff  = factorial(BigInt(max - k))

        while (ff % n == 0)
            max -= k
            ff /= max
        end

        cache[n] = max
        return max
    end

    arr = Int64[]

    for (k,v) in f
        push!(arr, v == 1 ? k : smarandache(k^v, cache))
    end

    maximum(arr)
end

#
## Tests
#

function test()
    cache = Dict{Int64, Int64}()

    limit = 10^2
    sumS = 0

    for k in 2:limit
        sumS += smarandache(k, cache)
    end

    println("∑S(i) for 2 ≤ i ≤ $limit == $sumS")

    if (limit == 100 && sumS != 2012)
        warn("However that is incorrect! (expected: 2012)")
    end
end

test()


###############################################################################
#
# The Stopping structure eases the implementation of algorithms and the
# stopping criterion.
#
# The following examples illustrate solver for linear algebra:
#  Ax = b with A an m x n matrix.
#
# This tutorial illustrates the different step in preparing the resolution of a
# new problem.
# - we create a LinearAlgebraPRoblem (that stores A, b)
# - we use the GennericState storing x and the current_time
# - we create a LinearAlgebraStopping
# - the optimality function linear_system_check!
#
# A more sophisticated version can be found in LAStopping.
#
###############################################################################
using LinearAlgebra, Stopping, Test

m, n = 400, 200 #size of A: m x n
A    = 100 * rand(m, n)
xref = 100 * rand(n)
b    = A * xref

#Our initial guess
x0 = zeros(n)

mutable struct LinearAlgebraProblem
    A :: Any #matrix type
    b :: Vector
end

la_pb = LinearAlgebraProblem(A, b)
la_state = GenericState(xref)

@test norm(la_pb.A * xref - la_pb.b) <= 1e-6

mutable struct LinearAlgebraStopping <: AbstractStopping

        # problem
        pb :: LinearAlgebraProblem

        # Common parameters
        meta :: AbstractStoppingMeta

        # current state of the problem
        current_state :: AbstractState

        # Stopping of the main problem, or nothing
        main_stp :: Union{AbstractStopping, Nothing}

        function LinearAlgebraStopping(pb               :: LinearAlgebraProblem,
                                       current_state    :: AbstractState; kwargs...)
         return new(pb, linear_system_check!, StoppingMeta(;optimality_check = linear_system_check, kwargs...), la_state, nothing)
        end
end

function linear_system_check(pb    :: LinearAlgebraProblem,
                             state :: AbstractState; kwargs...)
 return norm(pb.A * state.x - pb.b)
end

@test linear_system_check(la_pb, la_state) == 0.0
update!(la_state, x = x0)
@test linear_system_check(la_pb, la_state) != 0.0

la_stop = LinearAlgebraStopping(la_pb, la_state,
                                max_iter = 150000, rtol = 1e-6, optimality_check = linear_system_check)

"""
Randomized block Kaczmarz
"""
function RandomizedBlockKaczmarz(stp :: AbstractStopping; kwargs...)

    #A,b = stp.current_state.Jx, stp.current_state.cx
    A,b = stp.pb.A, stp.pb.b
    x0  = stp.current_state.x

    m,n = size(A)
    xk  = x0

    OK = start!(stp)

    while !OK

     i  = Int(floor(rand() * m)+1) #rand a number between 1 and m
     Ai = A[i,:]
     xk  = Ai == 0 ? x0 : x0 - (dot(Ai,x0)-b[i])/dot(Ai,Ai) * Ai

     OK = update_and_stop!(stp, x = xk)
     x0  = xk

    end

 return stp
end

RandomizedBlockKaczmarz(la_stop)
@test status(la_stop) == :Optimal


"""
# Available lower level routines
* [`besseljs3d`](@ref) spherical Bessel function eval
"""
function lower_level_routs() end

"""
    function fj, fjder = besseljs3d(nterms,z;scale=1.0,ifder=0)
This subroutine evaluates the first `nterms` spherical Bessel
functions, and if requested, their derivatives.
It incorporates a scaling parameter `scale` so that

      	fjs_n(z)=j_n(z)/SCALE^n
      	fjder_n(z)=\\frac{\\partial fjs_n(z)}{\\partial z}
# Input
* `nterms::Integer` order of expansion of output array `fjs`
* `z::ComplexF64` argument of the spherical Bessel functions
* `scale::Float64` scaling factor
* `ifder::Integer1` flag indicating whether to calculate `fjder`
      	          0	NO
      	          1	YES
OUTPUT:
* `fjs::Array{ComplexF64}` array of length `nterms+1` of scaled Bessel functions.
* `fjder::Array{ComplexF64}` array of derivatives of scaled Bessel functions, if requested.
"""
function besseljs3d(nterms::Integer,z::ComplexF64;scale::Float64=1.0,
                    ifder::Integer=0)

    @assert (nterms >= 0)
    if (ifder !=0 && ifder != 1)
        @warn "unexpected value in ifder, no ders computed"
    end

    fjs = Array{ComplexF64}(undef,nterms+1)
    fjder = ComplexF64(0)

    if ifder == 1
        fjder = Array{ComplexF64}(undef,nterms+1)
    end

    # fortran interface
    # subroutine besseljs3d(nterms,z,scale,fjs,ifder,fjder)

    ccall((:besseljs3d_,libfmm3d),Cvoid,(Fi,Fc,Fd,Fc,Fi,Fc),
          nterms,z,scale,fjs,ifder,fjder)

    if ifder != 1
        fjder = nothing
    end

    return fjs, fjder

end


# This problem finds a least cost shipping schedule that meets
# requirements at markets and supplies at factories.
#
# Dantzig, G B, Chapter 3.3. In Linear Programming and Extensions.
# Princeton University Press, Princeton, New Jersey, 1963.

using GAMS
using JuMP
using Test

function example_transport(; verbose = true)
   model = Model(GAMS.Optimizer)
   set_optimizer_attribute(model, MOI.Silent(), !verbose)

   a = [350, 600]
   b = [325, 300, 275]
   d = [2.5 1.7 1.8; 2.5 1.8 1.4]

   @variable(model, x[1:2,1:3] >= 0)

   @objective(model, Min, 0.09 * sum(d[i,j] * x[i,j] for i = 1:2, j = 1:3))

   @constraint(model, [i = 1:2], sum(x[i,j] for j = 1:3) <= a[i])
   @constraint(model, [j = 1:3], sum(x[i,j] for i = 1:2) >= b[j])

   if verbose
      print(model)
   end

   JuMP.optimize!(model)

   obj_opt = JuMP.objective_value(model)
   x_opt = JuMP.value.(x)

   if verbose
      println("Objective value: ", obj_opt)
      println("x = ", x_opt)
   end

   @test obj_opt ≈ 153.6750
   @test x_opt[1,1] ≈ 50.0
   @test x_opt[1,2] ≈ 300.0
   @test x_opt[1,3] ≈ 0.0
   @test x_opt[2,1] ≈ 275.0
   @test x_opt[2,2] ≈ 0.0
   @test x_opt[2,3] ≈ 275.0
end

example_transport(verbose = false)


using EmpiricalRisks
using Base.Test
using DualNumbers

### Auxiliary functions

function verify_multiloss(loss::MultivariateLoss, f, u::Vector{Float64}, y)
    # verify inferred types
    YT = typeof(y)
    for VT in [Float64]
        @test Base.return_types(value, Tuple{typeof(loss), Vector{VT}, YT}) == [VT]
        @test Base.return_types(grad, Tuple{typeof(loss), Vector{VT}, YT}) == [Vector{VT}]
        @test Base.return_types(value_and_grad, Tuple{typeof(loss), Vector{VT}, YT}) == [Tuple{VT, Vector{VT}}]
    end

    # verify computation correctness

    # prepare ground-truth
    d = length(u)
    v_r = f(u, y)
    g_r = zeros(d)
    for i = 1:d
        _ep = zeros(d)
        _ep[i] = 1.0
        _in = dual(u, _ep)
        _out = f(_in, y)
        @assert isa(_out, Dual{Float64})
        @assert isapprox(v_r, real(_out))
        g_r[i] = epsilon(_out)
    end

    # verify
    @test_approx_eq v_r value(loss, u, y)
    @test_approx_eq g_r grad(loss, u, y)

    (v, g) = value_and_grad(loss, u, y)
    @test_approx_eq v_r v
    @test_approx_eq g_r g
end

### Test cases

k = 3
n = 8

# Sum squared loss

_sumsqrf(u, y) = sum(abs2(u - y)) / 2

u = randn(k, n)
y = randn(k, n)
for i = 1:n
    verify_multiloss(SumSqrLoss(), _sumsqrf, copy(u[:,i]), y[:,i])
end

# Multilogistic loss

_mlogisticf(u, y::Int) = log(sum(exp(u))) - u[y]

u = randn(k, n)
for i = 1:n, y = 1:k
    verify_multiloss(MultiLogisticLoss(), _mlogisticf, copy(u[:,i]), y)
end


using RDatasets
using Random
using Statistics
using LinearAlgebra
using Plots

iris = dataset("datasets", "iris")

X = Matrix(iris[:, 1:4])
y = iris.Species

# Exercise

# Exercise

# Exercise

# Preparing data

function prepare_data(X, y; do_normal=true, do_onehot=true, kwargs...)
    X_train, y_train, X_test, y_test = split(X, y; kwargs...)

    if do_normal
        X_train, X_test = normalize(X_train, X_test; kwargs...)
    end

    classes = unique(y)

    if do_onehot
        y_train = onehot(y_train, classes)
        y_test = onehot(y_test, classes)
    end

    return X_train, y_train, X_test, y_test, classes
end

Random.seed!(666)

iris = dataset("datasets", "iris")

X = Matrix(iris[:, 1:4])
y = iris.Species

X_train, y_train, X_test, y_test, classes = prepare_data(X', y; dims=2)

# Testing dims in prepare_data

Random.seed!(666)
aux1 = prepare_data(X, y; dims=1)
Random.seed!(666)
aux2 = prepare_data(X', y; dims=2)

norm(aux1[1] - aux2[1]')

# SimpleNet

struct SimpleNet{T<:Real}
    W1::Matrix{T}
    b1::Vector{T}
    W2::Matrix{T}
    b2::Vector{T}
end

# Exercise

# Initialize SimpleNet

Random.seed!(666)

m = SimpleNet(size(X_train,1), 5, size(y_train,1))

# Exercise

# Evaluate SimpleNet

m(X_train[:,1:2])

# Gradient computation

function grad(m::SimpleNet, x::AbstractVector, y; ϵ=1e-10)
    z1 = m.W1*x .+ m.b1
    a1 = max.(z1, 0)
    z2 = m.W2*a1 .+ m.b2
    a2 = exp.(z2) ./ sum(exp.(z2), dims=1)
    l = -sum(y .* log.(a2 .+ ϵ))

    e_z2 = exp.(z2)
    l_part = (- e_z2 * e_z2' + Diagonal(e_z2 .* sum(e_z2))) / sum(e_z2)^2

    l_a2 = - y ./ (a2 .+ ϵ)
    l_z2 = l_part * l_a2
    l_a1 = m.W2' * l_z2
    l_z1 = l_a1 .* (a1 .> 0)
    l_x = m.W1' * l_z1

    l_W2 = l_z2 * a1'
    l_b2 = l_z2
    l_W1 = l_z1 * x'
    l_b1 = l_z1

    return l, l_W1, l_b1, l_W2, l_b2
end

# Few comments about the gradient

g_all = [grad(m, X_train[:,k], y_train[:,k]) for k in 1:size(X_train,2)]

typeof(g_all)

mean_tuple(d::AbstractArray{<:Tuple}) = Tuple([mean([d[k][i] for k in 1:length(d)]) for i in 1:length(d[1])])

g_mean = mean_tuple(g_all)

typeof(g_mean)

# Exercise

# Exercise


"""
This file checks whether QuadGK.quadgk() can integrate numerically to arbitrary precision using BigFloat types. To check, I look at the integral:

    ∫_0^1 cosh(xt) dt

which has a known closed analytic form:

    sinh(x)/x,  x!=0

which we can use for comparison. x has to be parsed as a BigFloat type using
BigFloat("\$x") (remove space in actual code) NOT BigFloat(x) which would just convert a Float64 type to a BigFloat (and so would only be accurate to 64 bits). To check that passing BigFloat("\$x") into sinh(x)/x gives an accurate arbitrary precision answer (and not plagued by rounding errors), I checked with wolframalpha answers which does produce arb. prec. answers and they matched and so passing BigFloat("\$x") into sinh(x)/x gives a good check of whether quadgk above works to arb. prec. too.

I confirmed that quadgk can give arb prec answers (I just using it incorrectly previously to give Float64 accurate results), so now using the correct method, I convert hyperbolic_integral_two() from check_hyperbolic_integral.jl:

    β/2 ∫_0^1 (1 - cosh(Ωβ(1-x)/2)) cosh(xβ/2) / (a^2 - β^2 x^2/4 - bcosh(vβx/2))^{3/2} dx

into arb prec version and see what it gives. Spoilers: it's super slow, further confirmation that it's now arb prec.
"""

"""
This function just evaluates the integral:

    ∫_0^1 cosh(xt) dt

using quadgk. To make it arbitrary precision, the limits have to be parsed properly into a BigFloat type using BigFloat("#") and the absolute tolerance of the integration has to be set to the machine accuracy of your required precision. This is obtained using eps(x::BigFloat) (e.g. eps(Float64) ~ 2.22e-16) and set with atol = eps(x::BigFloat). The precision of the BigFloats can then be changed using setprecision(BigFloat, precision::Int) externally.
"""
function cosh_integral(x)
    err = eps(x)
    integrand(t) = cosh(x * t)
    integral = quadgk(t -> integrand(t), BigFloat("0"), BigFloat("1"), atol = err)
end

"""
Just the exact closed analytic answer to the cosh integral used for comparison. x has to be parsed as a BigFloat("#") before passing into the function to evaluate it at higher precision properly.
"""
function exact_form(x)
    sinh(x) / x
end

"""
Check outputs of the two above functions.
"""

setprecision(BigFloat, 128) # set the precision of the BigFloat types.
x_range = BigFloat("5")

# for bits = 128 and x_range = BigFloat("5") if get:
# 0.005659 seconds (13.49 k allocations: 768.068 KiB)
@time exact = [exact_form(x) for x in x_range]
# exact = fill(14.84064211555775179540189439921291311993)
@show(exact)
# 0.374177 seconds (1.00 M allocations: 46.879 MiB, 3.29% gc time)
@time integral = [cosh_integral(x) for x in x_range]
# fill((14.84064211555775179540189439921291311988, 2.350988701644575015937473074444491355637e-38))
@show(integral)
# Can see that the integral uses large memory allocations. Last two digits differing is common for BigFloat precisions (natural rounding error from float arithmatic) but we can see that the answers match to 128 bit precision.

"""
Using the arbitrary precision quadgk method used above, apply this to the hyperbolic integral I have been trying to solve i.e. hyperbolic_integral_one() in check_hyperbolic_integrals.jl. I actually use hyperbolic_integral_two() here as the change of variables to the limits [0,1] makes it easier for quadgk to evaluate at higher βs.

This allocates a lot of memory to evaluating the integral. So I did use @code_typewarn to check that there are no type-stabilities, but it did not find any so I think this large memory allocation is just a result of small atol set by the machine precision. For 128 bits this is atol ~ 1.93e-34.
"""
function hyperbolic_integral_two(Ω, β, α, v, w)

    err = eps(Ω)

    # Initialise constants.
    R = (v^2 - w^2) / (w^2 * v)
    a = sqrt(β^2 / 4 + R * β * coth(β * v / 2))
    b = R * β / sinh(β * v / 2)

    coefficient = 2 * α * β^(3 // 2) * v^3 / (3 * sqrt(π) * sinh(β / 2) * w^3)

    integrand(x) = (1 - cosh(Ω * β * (1 - x) / 2)) * cosh(x * β / 2) / (a^2 - β^2 * x^2 / 4 - b * cosh(v * β * x / 2))^(3 // 2)

    integral = quadgk(x -> integrand(x), BigFloat("0.0"), BigFloat("1.0"), atol = err)

    return coefficient * β * integral[1] / 2
end

"""
Check output and timing of the above function.
"""

setprecision(BigFloat, 128)
Ω_range = [BigFloat("12.01")]
β = BigFloat("4.0")
α = BigFloat("7.0")
v = BigFloat("5.8")
w = BigFloat("1.6")
# For Ω_range = [BigFloat("12.01")], β = BigFloat("4.0"), α = BigFloat("7.0"), v = BigFloat("5.8"), w = BigFloat("1.6") I get:
# 209.267907 seconds (1.02 G allocations: 37.016 GiB, 5.82% gc time)
@time hyp = hyperbolic_integral_two.(Ω_range, β, α, v, w)
# hyp = BigFloat[-7.214150399279151977978320029665100466133e+09]
@show(hyp)
# This takes a LONG TIME to evaluate, with a massive 1.02 G allocations to allocate 37.016 GiB memory!


# Seed random generator to avoid random effects in testing
srand(1)

X = [1 1; 2 2;  1 -1]

# binomial
y = [-1; -1; 1]
@test_approx_eq_eps optimize(BinomialLogReg(X, y), optimizer=:l_bfgs).theta [-0.07276615319846116,-0.1680076736259885] 5e-5
@test_approx_eq_eps optimize(BinomialLogReg(X, y, λ=1.0), optimizer=:l_bfgs).theta [-0.16588135026949055,-0.840712964600344] 5e-5
@test_approx_eq_eps optimize(BinomialLogReg(X, y, λ= 10.0), optimizer=:l_bfgs).theta [-0.0745584919313508,-2.20259301054857] 5e-5

# TODO: should be moved to test/sgd.jl
@test_approx_eq_eps optimize(BinomialLogReg(X, y), optimizer=:sgd).theta [-0.07276615319846116,-0.1680076736259885] 5e-2
@test_approx_eq_eps optimize(BinomialLogReg(X, y, λ=1.0), optimizer=:sgd).theta [-0.16588135026949055,-0.840712964600344] 5e-2
@test_approx_eq_eps optimize(BinomialLogReg(X, y, λ=10.0), optimizer=:sgd).theta [-0.0745584919313508,-2.20259301054857] 5e-2
model = optimize(BinomialLogReg(X, y, λ=10.0), optimizer=:sgd)
@test predict(model, X) == y
show(IOBuffer(), BinomialLogReg(X, y))

@test_throws DimensionMismatch BinomialLogReg(X', y)
@test_throws ArgumentError BinomialLogReg(X, [3; 3; 2])
@test_throws ArgumentError optimize(BinomialLogReg(X, y, λ=0.0))

# multinomial
y = [1; 1; 2]
method = MultinomialLogReg(X, y, λ=10.0)
model = optimize(method, optimizer=:l_bfgs)
@test predict(model, X) == y


##############################
## by Qin Yu, Nov 2018
## using Julia 1.0.1
##############################

############################## Starting here:
# If this gives error, please call:
# using Pkg # Julia 1.0.1 only
# Pkg.add("Plots")
# Pkg.add("GR")

using LinearAlgebra
using Plots
using Printf

############################## Linear Regression with Polynomial Basis on Simple Dataset:
# Prepare Data
# Let's try to see the effect of polynomial basis,
# on this extremely simple dataset, S:
S = [(1,3),(2,2),(3,0),(4,5)]
plot(S, line=:scatter, lab="data set")

x = [x[1] for x in S]
y = [y[2] for y in S]

# Formulae - Find 𝝎:
phik(x, k) = [xi^k for xi in x]  # ϕₖ(𝒙) = ..., for vector 𝒙, all inputs
phi1tok(x, k) = [phik(x, i) for i in 0:k-1]  # Φ(𝒙) = 𝒙 ⋅ 𝝋 = 𝒙 ⋅ (ϕ₁(), ϕ₂(), ..., ϕₖ())
transformed_x_kk(x, k) = hcat(phi1tok(x, k)...)  # Φ(𝒙) as a matrix
w_k(x, y, k) = transformed_x_kk(x, k) \ y  # 𝝎 = Φ\𝒚

# Formulae - Equation of Fitted Regression Line:
w_phi_k(x_test, x_train, y, k) = dot(transformed_x_kk(x_test, k), w_k(x_train, y, k))  # ̂𝑦 = Φ(𝑥) ⋅ 𝝎

plot(S, line=:scatter, lab="data set", legend=:bottomright)
W = zeros(4, 4)
for i = 1:4
    𝒘Φ(x_test) = w_phi_k(x_test, x, y, i)
    display(plot!(𝒘Φ, 0, 4, lab="k = $i"))
    W[i,1:i] = w_k(x, y, i)

    # print the formulae for these curves
    print("k = $i, f(x) = ")
    for j = 1:i
        j > 1 && print(" + ")
        @printf "%.2f" W[i,j]
        j > 1 && print("x^$(j-1)")
    end
    print("\n")
end
savefig("./graph/1.1.pdf")

############################## Training Error
# SSE = 𝚺ᵢ(𝑦ᵢ - ̂𝑦ᵢ)² = 𝚺ᵢ(𝑦ᵢ - Φ(𝑥ᵢ) ⋅ 𝝎)²
# MSE = SSE/N, where N = number_of_rows(input_data_set), here is S
# (here I use N as the book ESLII uses, standing for m in qestions)
sse_k(x, y, k) = sum((y - transformed_x_kk(x, k) * w_k(x, y, k)).^2)
mse_k(x, y, k) = sse_k(x, y, k) / first(size(y))

MSE = [mse_k(x, y, i) for i = 1:4]
plot(MSE, xlabel="k", lab="MSE")
savefig("./graph/1.2.pdf")


function parse_encoded_string(input_string, lower_bound=0, upper_bound=127, upper_string="F", lower_string="B")

    for direction in input_string
        letter = string(direction)
        if letter == upper_string
            upper_bound = upper_bound - Int32(div(upper_bound - lower_bound, 2, RoundUp))
        elseif letter == lower_string
            lower_bound = lower_bound + Int32(div(upper_bound - lower_bound, 2, RoundUp))
        else
            throw("Invalid string type $letter")
        end
        # println("letter: $letter")
        # println((lower_bound, upper_bound))
    end
    @assert lower_bound == upper_bound
    lower_bound
end

function parse_rows(input_string)
    return parse_encoded_string(input_string, 0, 127, "F", "B")
end

function parse_columns(input_string)
    return parse_encoded_string(input_string, 0, 7, "L", "R")
end

function get_seat_id(input_string)
    row_string = input_string[1:7]
    col_string = input_string[8:end]

    row_num = parse_rows(row_string)
    col_num = parse_columns(col_string)

    seat_id = row_num * 8 + col_num
    seat_id
end

# turns out we didn't need this
function id_to_row_column(seat_id)
    column = seat_id % 8
    row = (seat_id - column) / 8
    row, column
end

function main()
    input_file = "day5_input.txt"

    highest_boarding_pass = open(input_file) do file
        file_string = read(file, String)
        all_seats = split(file_string, "\n")
        all_seats_ids = Vector{Int32}()

        for seat_entry in all_seats
            seat_id = get_seat_id(strip(seat_entry))
            push!(all_seats_ids, seat_id)
        end

        set_of_all_seats = Set(all_seats_ids) # easier to test if things are in a set
        for i in minimum(all_seats_ids):maximum(all_seats_ids)
            if !in(i, set_of_all_seats) && in(i-1, set_of_all_seats) && in(i+1, set_of_all_seats)
                println("My seat ID is $i")
            end
        end

        # Debugging
        # sort!(all_seats_ids)
        # println("all_seats_ids: $all_seats_ids")
        maximum(all_seats_ids)
    end
    println("Highest Seat ID: $highest_boarding_pass")
end

#if abspath(PROGRAM_FILE) == @__FILE__
    main()
#end


n = 10 # rand(1:10)
@test matrixdepot("binomial", n) == matrixdepot("binomial", Float64, n)

A = matrixdepot("binomial", n)

@test A*A ≈ 2^(n-1)*Matrix(1.0I, n, n)
println("'binomial' passed test...")


export RSA, rsa_encrypt, rsa_decrypt, coprime_inv_mod

using Primes: nextprime

using RingSignature.Util

struct EGCDResult{T<:Integer}
    "Bézout coefficient x"
    x::T

    "Bézout coefficient y"
    y::T

    "greatest common divisor"
    gcd::T

    "a / gcd"
    quot_a::T

    "b / gcd"
    quot_b::T
end

#=

s` is the coefficient of `a` throughout the process of successive divisions, so is `t` to `b`.

$r_k = a s_k + b t_k$ always holds (easy to prove by induction).

See https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Proof for details.

=#
"Extended GCD"
function egcd(a::T, b::T)::EGCDResult{T} where T<:Integer
    (r0, r) = (a, b)
    (s0, s) = (one(T), zero(T))
    (t0, t) = (zero(T), one(T))

    while !iszero(r)
        q = r0 ÷ r
        (r0, r) = (r, r0 - r * q)
        (s0, s) = (s, s0 - s * q)
        (t0, t) = (t, t0 - t * q)
    end

    EGCDResult(s0, t0, r0, t, s)
end

#=
When a and b are coprimes,

    a x + b y = gcd(a, b)  =>  a x = 1 (mod b)
=#

"Precondition: `x` and `m` are coprime"
function coprime_inv_mod(x::T, m::T)::T where T<:Integer
    inv_x = egcd(x, m).x
    inv_x < 0 ? inv_x + m : inv_x
end

"Generates a `n_bits`-bit random prime"
function random_prime(n_bits::Int)::BigInt
    # √2 × 2^(n_bits - 1)
    lower = BigInt(round(2 ^ (n_bits - 0.5)))
    upper = big(2) ^ n_bits - 1
    while true
        p = nextprime(rand(lower:upper))
        if p <= upper
            return p
        end
    end
end

struct RSA
    "Key size in bits"
    key_size::Int

    "The `(e, n)` pair"
    public_key::Tuple{BigInt, BigInt}

    "The `(d, n)` pair"
    private_key::Tuple{BigInt, BigInt}

    function RSA(key_size::Int, e::BigInt=big(65537))
        if key_size % 8 != 0
            error("`key_size` is not a multiple of 8")
        end

        while true
            p = random_prime(key_size ÷ 2)
            q = random_prime(key_size ÷ 2)
            if p == q
                continue
            end

            # Better to use Carmichael's totient
            totient = (p - 1) * (q - 1)
            if egcd(e, totient).gcd != 1
                continue
            end
            # FIXME: also need to check the #bits of `p - q` is enough

            n = p * q
            d = coprime_inv_mod(e, totient)
            pub_key = (e, n)
            pri_key = (d, n)
            return new(key_size, pub_key, pri_key)
        end
    end
end

function rsa_encrypt(rsa::RSA, m::BigInt)::BigInt
    (e, n) = rsa.public_key
    powermod(m, e, n)
end

function rsa_decrypt(rsa::RSA, c::BigInt)::BigInt
    (d, n) = rsa.private_key
    powermod(c, d, n)
end

function rsa_encrypt(rsa::RSA, m_bytes::Vector{UInt8})::Vector{UInt8}
    m = convert(BigInt, m_bytes)
    c = rsa_encrypt(rsa, m)
    convert(Vector{UInt8}, c)
end

function rsa_decrypt(rsa::RSA, c_bytes::Vector{UInt8})::Vector{UInt8}
    c = convert(BigInt, c_bytes)
    m = rsa_decrypt(rsa, c)
    convert(Vector{UInt8}, m)
end


### Algoritms and Recursion #####

# Guess-and-check algoritm

function guessandckeck(x)
    #initialize variable
    counter = 1
    # algoritm
    for guess in 0:abs(x)
        if guess^3 > abs(x)
            println("\n$x is not a perfect cube!")
            break
        end
        if guess^3 != abs(x)
            println(counter, "\tGuess = $guess\tGuess Cubed = $(guess^3)")
            counter += 1
        else
            if x < 0
                guess = -guess
            end
            println(counter, "\tGuess = $guess\tGuess cubed = $(guess^3)")
            println("\nThe cube root of $x is $guess.")
            break
        end
    end
end

guessandckeck(7)
guessandckeck(8)
guessandckeck(-8)

# guess and check only works for cubed value and not perfect for negative value. So lets try another algoritm

# Approximate solution

function approximation(x)

    # initialize
    guess = 0.0
    counter = 1
    increment = 0.01
    sensetivity = 0.1

    # algoritm
    while abs(guess^3 - x) >= sensetivity && abs(guess^3) <= abs(x)
        println(counter, "\tGuess = $guess\tGuess cubed = $(guess^3)")
        guess += increment
        counter += 1
    end

    println(counter, "\tGuess = $guess\tGuess cubed = $(guess^3)")
    guess = round(guess, digits = 2)
    x < 0 ? guess = -guess : guess = guess
    println("\nThe cube root of $x is approximately $guess.")
end

approximation(6)

# approximation algoritm works fine but not efficient for large numbers.  So,  Lets look at another algoritm

# Binary search algoritm

function binarysearch(x)

    # initialize

    low = 0
    high = x
    guess = (low + high) /2
    counter = 1
    sensetivity = 0.01

    #algoritm

    while abs(guess^3 -x) ≥ sensetivity
        println(counter, "\tGuess = $guess\tGuess cubed = $(guess^3)")

        if abs(guess^3) < abs(x)
            low = guess
        else
            high = guess
        end

        guess  = (low + high) /2
        counter += 1

    end
    println(counter, "\tGuess = $guess\tGuess cubed = $(guess^3)")
    guess = round(guess, digits = 3)
    println("\nThe cube root of $x is approximately $guess.")
end

binarysearch(6)

# performance comparation

using BenchmarkTools

@benchmark guessandckeck(1_000_000)
@benchmark approximation(1_000_000)
@benchmark binarysearch(1_000_000)

# Recursion

# recursion is a process that divides a problem into sub problem to solve larger one

# think on multipication, Z × 5  =  z + z + z + z + z

# multipication as iteravive solution

function multi_iterative(x,y)
    result = 0

    for i in 1:y
        result += x
        println("$i\t$x * $i\t= $result")
    end

    println("\n$x * $y = $result\n")
    return result
end

multi_iterative(5,10)

# multipication as recursion

function multi_recursive(x,y)
    if y == 1

        println("multi_recursive($x, $y)\t = $x\n")
        return x
    else

        println("multi_recursive($x, $y)\t = $x + multi_recursive($x, $(y - 1))")
        return x + multi_recursive(x, y - 1)
    end

end

multi_recursive(5,10)

# factorial as iterative method

function FactorialIterative(x)
    result = 1

    for i in 1:x
        result *= i

        println("$i factorial = $result")
    end

    println("\nThe Factorial of $x is $result\n")
    return result
end

FactorialIterative(5)

# factorial as recursion method

function FactorialRecursive(x)

    if x == 1

        println("FactorialRecursive($x) = $x\n")
        return 1
    else
        println("FactorialRecursive($x) = $x * FactorialRecursive($(x - 1))")
        return x * FactorialRecursive(x - 1)
    end
end


### A Pluto.jl notebook ###
# v0.12.7

using Markdown
using InteractiveUtils

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

# ╔═╡ 232c24de-0e30-11eb-1544-53cb0b45ec9b
begin
	import Pkg
	Pkg.activate(mktempdir())
	Pkg.add([
		"Colors",
		"Plots",
		"PlutoUI",
		])
	using Colors
	using Plots
	using PlutoUI
end

# ╔═╡ beed7b48-0e2b-11eb-2805-edf0be651fc3
md"""
### Lecture 22: Solving Partial Differential Equations (PDEs) Numerically
**Part I: One-dimensional advection and diffusion**

#### 1) Advection

Consider the following differential equation for temperature $T(x,t)$,

$\frac{\partial T}{\partial t} = - U \frac{\partial T}{\partial x}$

which describes the rate of change of temperature due to a uniform flow of speed $U$ in the $\mathbf{x}$ direction.

**Notation**:
Since this differential equation includes derivatives in both time $t$ **and** space $x$, we replace the *ordinary* deriative symbol $d$ with a *partial* derivative symbol $\partial$, and call the equation a **partial differential equation (PDE)**.

#### 1.1) Discretizing in time: forward finite difference
**A.k.a. *Euler's Method*$\,$**

As in our solution of the "zero-dimenisonal" energy balance model ODE, we begin by discretizing time using a *forward finite difference* scheme:

$\frac{T_{n+1}-T_{n}}{\Delta t} = - U \frac{\partial T}{\partial x},$

where $\Delta t \equiv t_{n+1} - t_{n}$ is a uniform discrete **timestep**.
"""

# ╔═╡ e74322be-12d6-11eb-3f27-f17821669699
md"
#### 1.2) Discretizing in space: centered finite difference

We now need to discretize the spatial derivative. Unlike for differencing in time, we no longer have a prefered direction for the discretization and so we use a *centered finite difference*:

$\frac{T_{n+1,\, i}-T_{n,\, i}}{\Delta t} = - U \frac{T_{n,\, i+1} - T_{n,\, i-1}}{2 \Delta x},$

where $\Delta x \equiv x_{i+1} - x_{i} = x_{i} - x_{i-1}$ is a uniform discrete **grid spacing**.
"

# ╔═╡ c9a1719a-12d6-11eb-1c8c-13e1f75bc852
md"**Note:** You should think of the *centered* finite difference average of the *right* (forward) and *left* (backward) finite differences:

$\frac{1}{2}\left(\frac{T_{n,\, i+1} - T_{n,\, i}}{\Delta x} + \frac{T_{n,\, i} - T_{n,\, i-1}}{\Delta x} \right) = \frac{T_{n,\, i+1} - T_{n,\, i-1}}{2 \Delta x}$

"

# ╔═╡ c3f99436-0f49-11eb-33b6-e78c150d4ff5
md"""
#### 1.3) Algorithmic procedure: time-stepping

Just as before, we re-order the equation to solve for the temperature of the $i$th grid cell at the next timestep $t_{n+1}$,

$T_{n+1,\, i} = T_{n,\, i} + \Delta t \left(-U \frac{T_{n,\, i+1} - T_{n,\, i-1}}{2 \Delta x} \right),$

which allows us to recursively advance in time, for each $i$, given some initial condition $T_{0,\, i}$ for all $i$.

#### 1.4) Numerical grid and boundary conditions
Consider solving this on a grid $i \in [1,\, N_{i}]$. How do we handle the edge case $i=1$, where $T_{n+1,\, 1}$ depends on $T_{n+1,\, 0}$, which is undefined?

For both this point $i=1$ and the other extreme $i=N_{i}$, we need a **boundary condition**. There are a number of ways of doing this, and we will explore some alternative kinds of boundary conditions later, but the simplest is a *periodic boundary condition*, which wraps the grid around by setting $T_{n,\, 0} = T_{n,\, N_{i}}$ and $T_{n,\, 1} = T_{n,\, N_{i}+1}$
"""

# ╔═╡ 0967dc46-0f4d-11eb-13b2-2b2042595b6e
md"#### 2) Numerical implementation"

# ╔═╡ 19273c8a-0f4d-11eb-3caf-678f073c6b1a
md"##### 2.1) Setting up the model parameters

To keep things simple, let us consider our temperature equation as a model of temperature variations in a one-dimensional ocean current of length $L=1$ m and with a speed $U = 1$ m/s.

The choice of the **discretization resolution** $N_{i}$ is up to the modeller, but here we make it $N_{i} = 10$ because we will be able to easily pick out each of the individual grid cells in plots below.
"

# ╔═╡ e700c752-0e2d-11eb-2c61-3959bdba269a
begin
	nx = 10
	Lx = 1.
	Δx = Lx/nx
	Δt = 0.001
	U = 1.

	x = Δx/2.:Δx:Lx
end;

# ╔═╡ 5b1b6802-0e2e-11eb-1fa6-1ddd5478cc54
begin
	# Initial conditions
	T = sin.(2π*x);
	t = [0.]
end;

# ╔═╡ 4463cf8c-0ef8-11eb-2c6e-45d982d5687a
function advect(T)
	return U*(circshift(T, (1)) .- circshift(T, (-1)))/(2Δx)
end

# ╔═╡ 67cb0ed2-0efa-11eb-2645-495dba94ea64
function timestep!(t, T)
	T .+= Δt*(advect(T))
	t .+= Δt
end

# ╔═╡ ba1ea938-0f44-11eb-0fac-cd1afe507d57
timestepButton = @bind go Button("Timestep")

# ╔═╡ 18a3f9dc-0e6d-11eb-0b31-0b5296c2e83b
begin
	⏩ = nothing
	go
	nT = 50
	for i = 1:nT
		timestep!(t, T)
	end
end;

# ╔═╡ f2c7638a-0e34-11eb-2210-9f9b0c3519fe
function temperature_heatmap(T)
	p = plot(xticks=x, yticks=nothing, size=(700,90))
	plot!(p, x, [0.], reshape(T, (size(T)...,1))', st=:heatmap, clims=(-1., 1.))
end;

# ╔═╡ b19bfb58-0f4e-11eb-218f-8d930b7afff6
md"#### 2) Diffusion

$\frac{\partial T}{\partial t} = \kappa \frac{\partial^{2} T}{\partial x^{2}}$

Again, we discretize the equation by considering the derivatives one at a time:

$\frac{T_{n+1,\, i} - T_{n,\, i}}{\Delta t} = \kappa \frac{\partial^{2} T}{\partial x^{2}}$

$\frac{T_{n+1,\, i} - T_{n,\, i}}{\Delta t} = \kappa \left( \frac{\frac{\partial T}{\partial x}|_{n,\, i+0.5} - \frac{\partial T}{\partial x}|_{n,\, i-0.5}}{\Delta x} \right)$

$\frac{T_{n+1,\, i} - T_{n,\, i}}{\Delta t} = \kappa \left( \frac{\frac{T_{n,\, i+1} - T_{n,\, i}}{\Delta x} - \frac{T_{n,\, i} - T_{n,\, i-1}}{\Delta x}}{\Delta x}\right)$

$\frac{T_{n+1,\, i} - T_{n,\, i}}{\Delta t} = \kappa \left( \frac{T_{n,\, i+1} - 2 T_{n,\, i} + T_{n,\, i-1}}{(\Delta x)^{2}}\right)$
"

# ╔═╡ 37bed6e8-12da-11eb-1c36-c7f92fe732e4
κ = 0.05

# ╔═╡ 3e1d44b6-12da-11eb-16ac-2524d0e0d900
function diffuse(T)
	return κ*(circshift(T, (1)) .- 2*T .+ circshift(T, (-1)))/(Δx^2)
end

# ╔═╡ 43d1272a-2113-11eb-1d03-c948d11a8423
# function timestep!(t, T)
# 	T .+= Δt*(diffuse(T))
# 	t .+= Δt
# end

# ╔═╡ 74a36fd4-2113-11eb-2d3e-3be5e4318dea
timestepButton

# ╔═╡ d2c101c8-0f4e-11eb-0c4f-0972be924ce1
md"#### 3) Advection-Diffusion"

# ╔═╡ 7c490fd2-2113-11eb-3239-f72cc77c0b53
# function timestep!(t, T)
# 	T .+= Δt*(advect(T) .+ diffuse(T))
# 	t .+= Δt
# end

# ╔═╡ 894b97f4-2113-11eb-3b86-2986a6cd4fd1
timestepButton

# ╔═╡ b00f7bec-12da-11eb-3e20-7769cc2bd0ec
md"
#### 4) Finite differences as kernels acting on an array

##### 4.1) Analogies between image processing and PDEs

##### 4.1.1) Blurring (Gaussian kernel) vs. Laplacian operator

##### 4.1.2) Edge detection (Sobel kernel) vs. Gradient operator
"

# ╔═╡ 70ae3138-0f44-11eb-1a8d-d3b5a39c1b42
md"""#### Pluto Book-keeping"""

# ╔═╡ 845e102a-0f44-11eb-3935-2d9a0efecedc
as_svg(x) = PlutoUI.Show(MIME"image/svg+xml"(), repr(MIME"image/svg+xml"(), x))

# ╔═╡ 00cc530a-0e35-11eb-133b-ef8e30ea7b12
begin
	⏩
	p1 = plot(x, T, label="Temperature", ylim=[-1.1, 1.1], xlim=[0., 1.], marker=:c)
	annotate!(p1, [(0.05, 0.9, string("t = ", round(t[1], digits=2)))])
	p2 = temperature_heatmap(T)
	p = plot(p1, p2, layout=grid(2, 1, heights=[0.7 , 0.3]), size=(680,250))
	as_svg(p)
end

# ╔═╡ 4c5b62fa-2113-11eb-14ff-2bd776efe96c
as_svg(p)

# ╔═╡ 869c4be8-2113-11eb-0703-f905fe93142f
as_svg(p)

# ╔═╡ Cell order:
# ╟─beed7b48-0e2b-11eb-2805-edf0be651fc3
# ╟─e74322be-12d6-11eb-3f27-f17821669699
# ╟─c9a1719a-12d6-11eb-1c8c-13e1f75bc852
# ╟─c3f99436-0f49-11eb-33b6-e78c150d4ff5
# ╟─0967dc46-0f4d-11eb-13b2-2b2042595b6e
# ╟─19273c8a-0f4d-11eb-3caf-678f073c6b1a
# ╠═e700c752-0e2d-11eb-2c61-3959bdba269a
# ╠═5b1b6802-0e2e-11eb-1fa6-1ddd5478cc54
# ╠═4463cf8c-0ef8-11eb-2c6e-45d982d5687a
# ╠═67cb0ed2-0efa-11eb-2645-495dba94ea64
# ╠═ba1ea938-0f44-11eb-0fac-cd1afe507d57
# ╠═00cc530a-0e35-11eb-133b-ef8e30ea7b12
# ╠═18a3f9dc-0e6d-11eb-0b31-0b5296c2e83b
# ╠═f2c7638a-0e34-11eb-2210-9f9b0c3519fe
# ╟─b19bfb58-0f4e-11eb-218f-8d930b7afff6
# ╠═37bed6e8-12da-11eb-1c36-c7f92fe732e4
# ╠═3e1d44b6-12da-11eb-16ac-2524d0e0d900
# ╠═43d1272a-2113-11eb-1d03-c948d11a8423
# ╠═74a36fd4-2113-11eb-2d3e-3be5e4318dea
# ╠═4c5b62fa-2113-11eb-14ff-2bd776efe96c
# ╟─d2c101c8-0f4e-11eb-0c4f-0972be924ce1
# ╠═7c490fd2-2113-11eb-3239-f72cc77c0b53
# ╠═894b97f4-2113-11eb-3b86-2986a6cd4fd1
# ╠═869c4be8-2113-11eb-0703-f905fe93142f
# ╟─b00f7bec-12da-11eb-3e20-7769cc2bd0ec
# ╟─70ae3138-0f44-11eb-1a8d-d3b5a39c1b42
# ╠═232c24de-0e30-11eb-1544-53cb0b45ec9b
# ╠═845e102a-0f44-11eb-3935-2d9a0efecedc


# Compute C = A * B, using a cache-obvious multiplication algorithm

function add_matmul_rec!(m,n,p, i0,j0,k0, C,A,B)
    if m+n+p <= 64   # base case: naive matmult for sufficiently large matrices
        for i = 1:m
            for k = 1:p
                c = zero(eltype(C))
                for j = 1:n
                    @inbounds c += A[i0+i,j0+j] * B[j0+j,k0+k]
                end
                @inbounds C[i0+i,k0+k] += c
            end
        end
    else
        m2 = m ÷ 2; n2 = n ÷ 2; p2 = p ÷ 2
        add_matmul_rec!(m2, n2, p2, i0, j0, k0, C, A, B)

        add_matmul_rec!(m-m2, n2, p2, i0+m2, j0, k0, C, A, B)
        add_matmul_rec!(m2, n-n2, p2, i0, j0+n2, k0, C, A, B)
        add_matmul_rec!(m2, n2, p-p2, i0, j0, k0+p2, C, A, B)

        add_matmul_rec!(m-m2, n-n2, p2, i0+m2, j0+n2, k0, C, A, B)
        add_matmul_rec!(m2, n-n2, p-p2, i0, j0+n2, k0+p2, C, A, B)
        add_matmul_rec!(m-m2, n2, p-p2, i0+m2, j0, k0+p2, C, A, B)

        add_matmul_rec!(m-m2, n-n2, p-p2, i0+m2, j0+n2, k0+p2, C, A, B)
    end
    return C
end

function matmul_rec!(C, A, B)
    m,n = size(A)
    n,p = size(B)
    size(C) == (m,p) || error("incorrect dimensions ", size(C), " ≠ $m × $p")
    fill!(C, 0)
    return add_matmul_rec!(m,n,p, 0,0,0, C,A,B)
end

matmul_rec(A, B) = matmul_rec!(Array{promote_type(eltype(A), eltype(B))}(undef,size(A,1), size(B,2)),A, B)

# Example:
# A = rand(50,60)
# B = rand(60,70)
# matmul_rec(A,B)

Code/Numerical Analysis & Machine Learning/Matrix Multiplication/CacheObliviousMatrixMultiplication.jl


using TensorToolbox
using LinearAlgebra
using Arpack
using TensorDecompositions

function lranmf_mu(X, r ; max_iter=200, tol = 1.0E-3, verbose = false)
    # lranmf_mu is effective when r << min(n,m)
    # input is a onnegative matrix
    # proposed by Guoxu Zhou in 2012
    # https://ieeexplore.ieee.org/document/6166354

    n, m = size(X)
    epsilon = 0.0001

    # See step1 in section II-B in the paper
    if r == min(n, m)
        svd_X = svd(X)
    else
        svd_X = svds(X; nsv=r, ritzvec=true)[1]
    end
    Achil = svd_X.U * diagm(svd_X.S)
    Bchil = svd_X.V

    # See step2 in section II-B in the paper
    A = rand(n, r)
    B = rand(m, r)
    cost_at_init = norm(X - A*B')
    previous_cost = cost_at_init
    for iter = 1:max_iter
        B .= B .* ( max.( Bchil*(Achil' * A), epsilon ) ) ./ ( B*(A'*A) )
        A .= A .* ( max.( Achil*(Bchil' * B), epsilon ) ) ./ ( A*(B'*B) )

        if tol > 0 && iter % 10 == 0
            cost = norm(X - A*B')
            if verbose
                println("iter: $iter cost: $cost")
            end
            if (previous_cost - cost) / cost_at_init < tol
                break
            end
            previous_cost = cost
        end
    end

    # A * B' is rank-r matrix
    return A, B'
end


using HypothesisTests, Test
using HypothesisTests: default_tail

@testset "T-test" begin

@testset "One sample" begin
	@test pvalue(OneSampleTTest(-5:5)) == 1

	tst = OneSampleTTest(-5:10)
	@test abs(pvalue(tst) - 0.0530) <= 1e-4
	@test abs(pvalue(tst; tail=:left) - 0.9735) <= 1e-4
	@test abs(pvalue(tst; tail=:right) - 0.0265) <= 1e-4
	@test default_tail(tst) == :both
	show(IOBuffer(), tst)

	tst = OneSampleTTest(mean(-5:10), std(-5:10), 16)
	@test abs(pvalue(tst) - 0.0530) <= 1e-4

	@test all(abs.([confint(tst)...;] - [-0.0369, 5.0369]) .<= 1e-4)
	@test all(abs.([confint(tst, level=0.9)...;] - [0.4135, 4.5865]) .<= 1e-4)
	c = confint(tst; tail=:left)
	@test c[1] == -Inf
	@test abs(c[2] - 4.5865) .<= 1e-4
	c = confint(tst; tail=:right)
	@test abs(c[1] - 0.4135) .<= 1e-4
	@test c[2] == Inf
	show(IOBuffer(), tst)

	tst = OneSampleTTest(-10:5)
	@test abs(pvalue(tst) - 0.0530) <= 1e-4
	@test abs(pvalue(tst; tail=:left) - 0.0265) <= 1e-4
	@test abs(pvalue(tst; tail=:right) - 0.9735) <= 1e-4
	@test all(abs.([confint(tst)...] - [-5.0369, 0.0369]) .<= 1e-4)
	@test abs.(confint(tst; tail=:left)[2] - (-0.4135)) .<= 1e-4
	@test abs.(confint(tst; tail=:right)[1] - (-4.5865)) .<= 1e-4
	show(IOBuffer(), tst)
end

@testset "Paired" begin
	@test abs(pvalue(OneSampleTTest([1, 1, 2, 1, 0], [0, 1, 1, 1, 0])) - 0.1778) <= 1e-4
end

@testset "Two sample" begin
	# From http://en.wikipedia.org/w/index.php?title=Student%27s_t-test&oldid=526762741

	a1 = [30.02, 29.99, 30.11, 29.97, 30.01, 29.99]
	a2 = [29.89, 29.93, 29.72, 29.98, 30.02, 29.98]

	tst = EqualVarianceTTest(a1, a2)
	@test tst.df == 10
	@test abs(tst.t - 1.959) <= 1e-3
	@test abs(pvalue(tst) - 0.078) <= 1e-3
	@test all(abs.([confint(tst)...] - [-0.0131, 0.2031]) .<= 1e-4)
	@test default_tail(tst) == :both
	show(IOBuffer(), tst)

	tst = UnequalVarianceTTest(a1, a2)
	@test abs(tst.df - 7.03) <= 0.01
	@test abs(tst.t - 1.959) <= 1e-3
	@test abs(pvalue(tst) - 0.091) <= 1e-3
	@test all(abs.([confint(tst)...] - [-0.0196, 0.2096]) .<= 1e-4)
	@test default_tail(tst) == :both
	show(IOBuffer(), tst)
end
end


using SpecialFunctions
"Abstract quadrature type"
abstract type QUADRATURE_TYPE end

"Gauss-Jacobi quadrature type"
struct GJ <: QUADRATURE_TYPE end
"Gauss-Lobatto-Jacobi quadrature type"
struct GLJ <: QUADRATURE_TYPE end
"Gauss-Radau-Jacobi quadrature type includind the left end"
struct GRJM <: QUADRATURE_TYPE end
"Gauss-Radau-Jacobi quadrature type includind the right end"
struct GRJP <: QUADRATURE_TYPE end

# Calculate a ratio of Gamma functions without overflow
function gamma_ratio(num::T, denom::T) where {T<:Number}
    if num>1 && denom>1
        exp((logabsgamma(num))[1] - (logabsgamma(denom))[1])
    else
        gamma(num) / gamma(denom)
    end
end

"""
Gauss-type quadrature

Numerical integrals in the domain [-1,1] can be computed
from knowledge of the function in a set of nodes and the
corresponding nodes, such that

```math
\\int_{-1}^1 (1-x)^a (1+x)^b (1-xf(x)\\:dx \\approx \\sum_{i=1}^N w^{a,b}_i f(x_i)
```

The parameters `a` and `b` form a famility of quadrature rules. But if one or both
of the ends are specified, other quadrature families are possible:

 * No ends are specified, Gauss-Jacobi quadrature (GJ)
 * Both ends are specified, Gauss-Lobatto-Jacobi quadrature (GLJ)
 * A single end is specified, Gauss-Radau-Jacobi quadrature (if +1 is specified, GRJP, if -1, GRJM)

To compute the nodes, the following functions are available:

 * `zgj` (Gauss-Jacobi)
 * `zglj` (Gauss-Lobatto-Jacobi)
 * `zgrjm` (Gauss-Radau-Jacobi, -1)
 * `zgrjp` (Gauss-Radau-Jacobi, +1)

All these functions have the following parameters:

 * `Q`  Number of quadrature nodes
 * `a` (a) weight
 * `b` (b) weight
 *  type Data type to be used, `Float64` is the default

To compute the weights, first the zeros (`z`) should be computed and then the weights are computed
with the following functions:

 * `wgj(z, a, b)`
 * `wglj(z, a, b)`
 * `wgrjm(z, a, b)`
 * `wgrjp(z, a, b)`

 ### Derivatives

The nodes used in the quadrature rules are convenient when using high order Lagrange interpolation
To compute derivatives. The following functions are used to compute the derivative matrix such that
`du = D*u`:

 * `dgj(z, a, b)`
 * `dglj(z, a, b)`
 * `dgrjm(z, a, b)`
 * `dgrjp(z, a, b)`

### Examples

See the notebooks availbale with the package.

"""
function zgj(Q, a, b, ::Type{T}=Float64) where {T<:Number}
    jacobi_zeros(Q, a, b, T)
end

zgj(Q) = zgj(Q, 0.0, 0.0)
zgj(Q, a) = zgj(Q, a, zero(a))

function zglj(Q, a, b, ::Type{T}=Float64) where {T<:Number}
    z = jacobi_zeros(Q-2, a+1, b+1, T)
    o = one(T)
    return [-o; z; o]
end
zglj(Q) = zglj(Q, 0.0, 0.0)
zglj(Q, a) = zglj(Q, a, zero(a))

function zgrjm(Q, a, b, ::Type{T}=Float64) where {T<:Number}
    z = jacobi_zeros(Q-1, a, b+1, T)
    return [-one(T); z]
end
zgrjm(Q) = zgrjm(Q, 0.0, 0.0)
zgrjm(Q, a) = zgrjm(Q, a, zero(a))

function zgrjp(Q, a, b, ::Type{T}=Float64) where {T<:Number}
    z = jacobi_zeros(Q-1, a+1, b, T)
    return [z; one(T)]
end
zgrjp(Q) = zgrjp(Q, 0.0, 0.0)
zgrjp(Q, a) = zgrjp(Q, a, zero(a))

function wgj(z::AbstractArray{T}, alpha=0, beta=0) where {T<:Number}
    a = convert(T, alpha)
    b = convert(T, beta)

    Q::Int = length(z)
    o = one(T)
    coef = 2^(a+b+1) * gamma_ratio(a+Q+1, Q+o) * gamma_ratio(b+Q+1, a+b+Q+1)
    w = [djacobi(zz, Q, a, b) for zz=z]

    for i = 1:Q
        ww = w[i]
        x = z[i]
        w[i] = o / (ww*ww) * coef / (o - x*x)
    end

    return w
end

function wglj(z::AbstractArray{T}, alpha=0, beta=0) where {T<:Number}
    a = convert(T, alpha)
    b = convert(T, beta)
    o = one(T)
    Q = length(z)

    coef = 2^(a+b+1) / (Q-o) * gamma_ratio(a+Q, Q*o) * gamma_ratio(b+Q, a+b+Q+1)

    w = [jacobi(zz, Q-1, a, b) for zz=z]
    w[1] = (b+1) * coef / (w[1]*w[1])
    w[Q] = (a+1) * coef / (w[Q]*w[Q])

    for i = 2:(Q-1)
        ww = w[i]
        w[i] = coef / (ww * ww)
    end

    return w
end

function wgrjm(z::AbstractArray{T}, alpha=0, beta=0) where {T<:Number}
    a = convert(T, alpha)
    b = convert(T, beta)
    o = one(T)

    Q = length(z)

    coef = 2^(a+b) / (b+Q) * gamma_ratio(a+Q, Q*o) * gamma_ratio(b+Q, a+b+Q+1)

    w = [jacobi(zz, Q-1, a, b) for zz=z]

    for i = 1:Q
        ww = w[i]
        w[i] = coef / (ww*ww) * (o - z[i])
    end

    w[1] *= (b + o)

    return w
end

function wgrjp(z::AbstractArray{T,1}, alpha=0, beta=0) where {T<:Number}
    a = convert(T, alpha)
    b = convert(T, beta)
    Q = length(z)
    o = one(T)

    coef = 2^(a+b) / (a+Q) * gamma_ratio(a+Q, Q*o) * gamma_ratio(b+Q, a+b+Q+1)

    w = [jacobi(zz, Q-1, a, b) for zz=z]

    for i = 1:Q
        ww = w[i]
        w[i] = coef / (ww*ww) * (o + z[i])
    end

    w[Q] *= (a + o)

    return w
end

function dgj(z::AbstractArray{T,1}, alpha=0, beta=0) where {T<:Number}

    Q = length(z)
    a = convert(T, alpha)
    b = convert(T, beta)
    o = one(T)

    djac = [djacobi(zz, Q, alpha, beta) for zz=z]

    D = zeros(T, Q, Q)
    for i = 1:Q
        for k = 1:Q
            if i != k
                D[i,k] = (djac[i]/djac[k]) / (z[i]-z[k])
            else
                D[i,i] = (a-b + (a + b + 2) * z[i]) / (o - z[k]^2) / 2
            end
        end
    end

    return D
end

function dglj(z::AbstractArray{T,1}, alpha=0, beta=0) where {T<:Number}

    Q = length(z)
    a = convert(T, alpha)
    b = convert(T, beta)
    o = one(T)

    djac = zeros(T,Q)
    djac[1] = (-1)^Q * 2 * gamma_ratio(Q+b, Q-o) / gamma(b+2)
    djac[Q] = -2 * gamma_ratio(Q+a, Q-o) / gamma(a+2)
    for i = 2:(Q-1)
        djac[i] = (o-z[i]*z[i]) * djacobi(z[i], Q-2, a+1, b+1)
    end

    D = zeros(T, Q, Q)
    for i = 1:Q
        for k = 1:Q
            if i != k
                D[i,k] = (djac[i]/djac[k]) / (z[i]-z[k])
            else
                D[i,i] = (a - b + (a + b)*z[i]) / (2*(o - z[i]^2))
            end
        end
    end

    D[1,1] = (a - (Q-1)*(Q+a+b)) / (2*(b + 2))
    D[Q,Q] = -(b - (Q-1)*(Q+a+b)) / (2*(a + 2))

    return D

end

function dgrjm(z::AbstractArray{T,1}, alpha=0, beta=0) where {T<:Number}

    Q = length(z)
    a = convert(T, alpha)
    b = convert(T, beta)
    o = one(T)

    djac = zeros(T,Q)
    for i = 2:Q
        djac[i] = (1+z[i]) * djacobi(z[i], Q-1, a, b+1)
    end
    djac[1] = (-1)^(Q-1) * gamma_ratio(Q+b+1, Q*o) / gamma(b+2)

    D = zeros(T, Q, Q)
    for i = 1:Q
        for k = 1:Q
            if i != k
                D[i,k] = (djac[i]/djac[k]) / (z[i]-z[k])
            else
                D[i,i] = (a - b + 1 + (a + b + 1)*z[i]) / (2*(1 - z[i]^2))
            end
        end
    end

    D[1,1] = -(Q-1)*(Q+a+b+1) / (2*(b + 2))

    return D

end

function dgrjp(z::AbstractArray{T,1}, alpha=0, beta=0) where {T<:Number}

    Q = length(z)
    a = convert(T, alpha)
    b = convert(T, beta)
    o = one(T)

    djac = zeros(T,Q)
    for i = 1:(Q-1)
        djac[i] = (1-z[i]) * djacobi(z[i], Q-1, a+1, b)
    end
    djac[Q] = - gamma_ratio(Q+a+1, Q*o) / gamma(a+2)

    D = zeros(T, Q, Q)
    for i = 1:Q
        for k = 1:Q
            if i != k
                D[i,k] = (djac[i]/djac[k]) / (z[i]-z[k])
            else
                D[i,i] = (a - b - 1 + (a + b + 1)*z[i]) / (2*(1 - z[i]^2))
            end
        end
    end

    D[Q,Q] = (Q-1)*(Q+a+b+1) / (2*(a + 2))

    return D

end

"""
Abstract interface of Gauss-type quadrature rules

Can be used for any `AbstractFloat` type data.
"""
struct Quadrature{T<:Number,QT<:QUADRATURE_TYPE}
    "Number of quadrature nodes"
    Q::Int
    "a weight"
    a::T
    "b weight"
    b::T
    "Quadrature nodes"
    z::Array{T, 1}
    "Quadrature weights"
    w::Array{T, 1}
    "Quadrature derivative matrix"
    D::Array{T, 2}
end

"""
Return the zeros of a Gauss type quadrature
"""
qzeros(::Type{GJ}, Q, a=0, b=0, ::Type{T}=Float64) where {T<:Number} = zgj(Q, a, b, T)
qzeros(::Type{GLJ}, Q, a=0, b=0, ::Type{T}=Float64) where {T<:Number} = zglj(Q, a, b, T)
qzeros(::Type{GRJM}, Q, a=0, b=0, ::Type{T}=Float64) where {T<:Number} = zgrjm(Q, a, b, T)
qzeros(::Type{GRJP}, Q, a=0, b=0, ::Type{T}=Float64) where {T<:Number} = zgrjp(Q, a, b, T)

"""
Return the weights of a Gauss type quadrature
"""
qweights(::Type{GJ}, z::AbstractArray{T}, a=0, b=0) where {T<:Number} = wgj(z, a, b)
qweights(::Type{GLJ}, z::AbstractArray{T}, a=0, b=0) where {T<:Number} = wglj(z, a, b)
qweights(::Type{GRJM}, z::AbstractArray{T}, a=0, b=0) where {T<:Number} = wgrjm(z, a, b)
qweights(::Type{GRJP}, z::AbstractArray{T}, a=0, b=0) where {T<:Number} = wgrjp(z, a, b)

"""
Return the derivative matrix of a Gauss type quadrature
"""
qdiff(::Type{GJ}, z::AbstractArray{T}, a=0, b=0) where {T<:Number} = dgj(z, a, b)
qdiff(::Type{GLJ}, z::AbstractArray{T}, a=0, b=0) where {T<:Number} = dglj(z, a, b)
qdiff(::Type{GRJM}, z::AbstractArray{T}, a=0, b=0) where {T<:Number} = dgrjm(z, a, b)
qdiff(::Type{GRJP}, z::AbstractArray{T}, a=0, b=0) where {T<:Number} = dgrjp(z, a, b)

"""
Create a `Quadrature` object given its type, order and weights.
"""
function Quadrature(::Type{QT}, Q, a=0, b=0,
                    ::Type{T}=Float64) where {T<:Number, QT<:QUADRATURE_TYPE}
    aa = convert(T, a)
    bb = convert(T, b)
    z = qzeros(QT, Q, aa, bb, T)
    w = qweights(QT, z, aa, bb)
    D = qdiff(QT, z, aa, bb)
    Quadrature{T,QT}(Q, aa, bb, z, w, D)
end

"Return quadrature type"
qtype(q::Quadrature{T,QT}) where {T,QT} = QT
"Return quadrature nodes"
qzeros(q::QT) where {QT<:Quadrature} = q.z
"Return quadrature weights"
qweights(q::QT) where {QT<:Quadrature} = q.w
"Return quadrature derivative matrix"
qdiff(q::QT) where {QT<:Quadrature} = q.D
"Return number of quadrature nodes"
num_points(q::QT) where {QT<:Quadrature} = q.Q
"Return quadrature `a` weight"
qalpha(q::QT) where {QT<:Quadrature} = q.a
"Return quadrature `b` weight"
qbeta(q::QT) where {QT<:Quadrature} = q.b

"""
Compute the Lagrange polynomial

This function computes the Lagrange polynomial at point `x` corresponding to
the `i`-th node of the set `z`

There is also a modifying version `lagrange!` used to computing the polynomials
at several points of an array `x`
"""
function lagrange(i, x, z)
    nz = length(z)

    l = one(z[1])

    for k = 1:(i-1)
        l = l * (x-z[k]) / (z[i]-z[k])
    end

    for k = (i+1):nz
        l = l * (x-z[k]) / (z[i]-z[k])
    end

    return l
end

"""
Interpolation matrix

Often it is necessary to compute the values of a function approximated using
Lagrange interpolation through a set of points `z`. If this will be repeated
often, a matrix can be computed that allows the easy computation using the simple
expression `fx = Imat * fz`
"""
function interp_mat(x::AbstractArray{T}, z::AbstractArray{T}) where {T<:Number}

    Q = length(z)
    np = length(x)

    Imat = zeros(T, np, Q)
    for i = 1:Q, k=1:np
        Imat[k,i] = lagrange(i, x[k], z)
    end

    return(Imat)

end

@doc (@doc zgj) zglj
@doc (@doc zgj) zgrjm
@doc (@doc zgj) zgrjp

@doc (@doc zgj) wgj
@doc (@doc zgj) wglj
@doc (@doc zgj) wgrjm
@doc (@doc zgj) wgrjp

@doc (@doc zgj) dgj
@doc (@doc zgj) dglj
@doc (@doc zgj) dgrjm
@doc (@doc zgj) dgrjp


function twodElliptic(N=25, κ=1.0)
#
#  Solves a linear elliptic PDE on the unit square
#     - ∇⋅(κ∇u) = q
#---------------------------------------------------------------------------78--

#  include("twodMesh.jl")
#  include("twodQuadratureRule.jl")
#  @everywhere include("twodShape.jl")
#  @everywhere include("twodBilinear.jl")
#  @everywhere include("twodLinForm.jl")

  rule  = 7     # points in quadrature formula, adjust for different unit tests
                # options are 1, 3 (2nd degree), 7 (5th degree),
                # 13 (7th degree), and 19 (9th degree).

  #  Set the verification example
  function uExact(x)
    C = 0.2/π^2
    return C*sin.(π*x[:,1])*sin.(2*π*x[:,2])
  end
#  f(x)=x[1]+x[2];            # right-hand side function definition.
#  @everywhere function f(x::Array{Float64,2})
  function f(x::Array{Float64,2})
    return sin.(π*x[:,1]).*sin.(2*π*x[:,2])
  end

  #  Specify the geometry/mesh
  #-----------------------------------------------------------------------------
  xMin = 0.0
  xMax = 1.0
  yMin = 0.0
  yMax = 1.0
  nNodesX = N
  nNodesY = N
  x,eConn,iB = twodMesh( xMin,xMax, yMin,yMax, "quadratic", nNodesX,nNodesY )

  #  Get problem dimensions
  #-----------------------------------------------------------------------------
  nNodes = size(x,1)
  nElements = size(eConn,1)

  nDirichlet = length(iB)

  #  Set the index into equation numbers
  #-----------------------------------------------------------------------------
  ide = zeros(Int64,nNodes,1)

  global nUnk = 0
  for i=1:nNodes
    global nUnk = nUnk+1
    ide[i,1] = nUnk
  end

  # ide = 1:nNodes

  #  Integrate system matrices, element-by-element
  #-----------------------------------------------------------------------------
  r,s,w = twodQuadratureRule(rule)
  o     = ones(rule)
  κ_g   = κ*ones(rule)

  nElDOF   = size(eConn,2)
  nElDOF2  = nElDOF*nElDOF
  nEntries = nElements*nElDOF2
  II  = Array{Int64,1}(undef, nEntries)    #SharedArray(Int32,nEntries);
  JJ  = Array{Int64,1}(undef, nEntries)    #SharedArray(Int32,nEntries);
  AA  = Array{Float64,1}(undef, nEntries)  #SharedArray(Float64,nEntries);
  b   = zeros(Float64,nNodes,1)            #SharedArray(Float64,nNodes);

#  @sync @parallel for k=1:nElements
  for k=1:nElements
    #xg  = Array(Float64,rule,2);
    #wg  = Array(Float64,rule);
    #phi = Array(Float64,rule,nElDOF);
    #p_x = Array(Float64,rule,nElDOF);
    #p_y = Array(Float64,rule,nElDOF);

    nLocal = eConn[k,:][:]
    xLocal = x[nLocal,:]

    xg,wg,ϕ,ϕ_x,ϕ_y = twodShape( xLocal, r, s, w )
    fg   = f(xg)         # forcing function evaluated at quadrature points

    ALoc = twodBilinear( κ_g, ϕ_x, ϕ_x, wg ) + twodBilinear( κ_g, ϕ_y, ϕ_y, wg )
    bLoc = twodLinForm(   fg,      ϕ  , wg )

    index = (k-1)*nElDOF2    # compute base index (k could be in any order)
    lDOF = ide[nLocal,1][:]

    for nt = 1:nElDOF
      nTest = ide[nLocal[nt],1]
      for nu = 1:nElDOF
        nUnkU = ide[nLocal[nu],1]

        index = index + 1
        II[index] = nTest   # lDOF[nu]
        JJ[index] = nUnkU   # lDOF[nt]
        AA[index] = ALoc[nt,nu]
      end

      b[nTest] = b[nTest] + bLoc[nt] #b[lDOF[nt]] = b[lDOF[nt]] + bLoc[nt]
    end
  end

  A = sparse(II,JJ,AA)

  interiorNodes = 1:nNodes
  interiorNodes = setdiff(interiorNodes,iB)

  knownIndexU = iB

#  dirichletU = Array{Float64,1}(undef,nDirichlet)
#  for i=1:nDirichlet
#    dirichletU = uExact(x[i,:])
#  end

  u = zeros(Float64,nNodes)
  u[interiorNodes] = A[interiorNodes,interiorNodes]\b[interiorNodes]

  #print("$(u)")

  return x,eConn,u
end


# Problem 2 - Project Euler
# http://projecteuler.net/index.php?section=problems&id=2
function fibevensum(a, b, sum, xmax)
    if a >= xmax
        sum
    elseif a % 2 == 0
        fibevensum(b, a + b, sum + a, xmax)
    else
        fibevensum(b, a + b, sum, xmax)
    end
end

println(fibevensum(1,2,0, 4000000))