using Plots, DataFrames, Flux, Plots, Random, ReverseDiff
using Turing # v0.20

# create half-moon data:
function make_moons(n_samples::Int = 100)

    n = n_samples

    n_samples_out = trunc(Int, round(n_samples / 2))
    n_samples_in = n_samples - n_samples_out

    outer_circ_x = cos.(LinRange(0, π, n_samples_out))
    outer_circ_y = sin.(LinRange(0, π, n_samples_out))
    inner_circ_x = 1 .- cos.(LinRange(0, π, n_samples_in))
    inner_circ_y = 1 .- sin.(LinRange(0, π, n_samples_in)) .- 0.5

    X = hcat(vcat(outer_circ_x, inner_circ_x), vcat(outer_circ_y, inner_circ_y))
    Y = hcat(zeros(Int, 1, 2 .* n_samples_out), ones(Int, 1, 2 .* n_samples_in))

    return X, Y, n

end

X, Y, n = make_moons()

# rearrange half-moon data:
n_2 = trunc(Int, n/2)
xscatter_0 = X[1:n_2]
xscatter_1 = X[1+n_2:n]
yscatter_0 = X[1+n:n+n_2]
yscatter_1 = X[1+n+n_2:end]

# plot half-moon data:
plot(
    xscatter_0, yscatter_0,
    seriestype = :scatter,
    legend = true,
    label = "Class 0",
    title = "half-moons problem"
)
plot!(
    xscatter_1, yscatter_1,
    seriestype = :scatter,
    legend = true,
    label = "Class 1"
)

# optional: half-moon data in a dataframe:
half_moon_df = DataFrame(
    X = vcat(xscatter_0, xscatter_1),
    Y = vcat(yscatter_0, yscatter_1),
    Class = vcat(zeros(Int, n_2), ones(Int, n_2))
)

# Turing settings:
Turing.setprogress!(true)
Turing.setadbackend(:reversediff)

# architecture:
network_shape = [
    (5,2, :tanh),
    (5,5, :tanh),
    (2,5, :σ)]

# Regularization, parameter variance, and total number of parameters.
alpha = 0.09
sig = sqrt(1.0 / alpha)
num_params = sum([i * o + i for (i, o, _) in network_shape])

# This modification of the unpack function generates a series of vectors given a network shape.
function unpack(θ::AbstractVector, network_shape::AbstractVector)
    index = 1
    weights = []
    biases = []
    for layer in network_shape
        rows, cols, _ = layer
        size = rows * cols
        last_index_w = size + index - 1
        last_index_b = last_index_w + rows
        push!(weights, reshape(θ[index:last_index_w], rows, cols))
        push!(biases, reshape(θ[last_index_w+1:last_index_b], rows))
        index = last_index_b + 1
    end
    return weights, biases
end

# Generate an abstract neural network given a shape, and return a prediction.
function nn_forward(x, θ::AbstractVector, network_shape::AbstractVector)
    weights, biases = unpack(θ, network_shape)
    layers = []
    for i in eachindex(network_shape)
        push!(layers, Dense(weights[i],
            biases[i],
            eval(network_shape[i][3])))
    end
    nn = Chain(layers...)
    return nn(x)
end

# rearrange half-moon data yet again:
xscatter = vcat(xscatter_0, xscatter_1)
yscatter = vcat(yscatter_0, yscatter_1)
xs = Array([[xscatter[i]; yscatter[i]] for i = 1:n])
ts = vcat(zeros(Int, n_2), ones(Int, n_2))

# General Turing specification for a BNN model.
@model bayes_nn_general(xs, ts, network_shape, num_params) = begin
    θ ~ MvNormal(zeros(num_params), sig .* ones(num_params))
    preds = nn_forward(xs, θ, network_shape)
    for i = 1:length(ts)
        ts[i] ~ Bernoulli(preds[i])
    end
end

# Perform inference.
num_samples = 2000
ch2 = sample(bayes_nn_general(hcat(xs...), ts, network_shape, num_params), NUTS(0.65), num_samples);

# This function makes predictions based on network shape.
function nn_predict(x, theta, num, network_shape)
    mean([nn_forward(x, theta[i,:], network_shape)[1] for i in 1:10:num])
end;

# Extract the θ parameters from the sampled chain.
params2 = MCMCChains.group(ch2, :θ).value

# classification plot
plot(
    xscatter_0, yscatter_0,
    seriestype = :scatter,
    label = "Class 0",
    title = "half-moons problem"
)
plot!(
    xscatter_1, yscatter_1,
    seriestype = :scatter,
    legend = :bottomright,
    label = "Class 1"
)
x_range = collect(range(-2.5,stop=3.5,length=200))
y_range = collect(range(-1.2,stop=1.7,length=200))
Z = [nn_predict([x, y], params2, length(ch2), network_shape)[1] for x=x_range, y=y_range]
contour!(x_range, y_range, Z)


"""
    conformal_cubed_sphere_mapping(x, y)

Conformal mapping of a cube onto a sphere. Maps `(x, y)` on the north-pole face of a cube
to (X, Y, Z) coordinates in physical space. The face is oriented normal to Z-axis with
X and Y increasing with x and y.

The input coordinates must lie within the range -1 <= x <= 1,  -1 <= y <= 1.

This numerical conformal mapping is described by Rančić et al. (1996).

This is a Julia translation of MATLAB code from MITgcm [1] that is based on
Fortran 77 code from Jim Purser & Misha Rančić.

[1] http://wwwcvs.mitgcm.org/viewvc/MITgcm/MITgcm_contrib/high_res_cube/matlab-grid-generator/map_xy2xyz.m?view=markup

Rančić et al., (1996): Quarterly Journal of the Royal Meteorological Society, A global shallow-water model
    using an expanded spherical cube - Gnomonic versus conformal coordinates
"""
function conformal_cubed_sphere_mapping(x, y)
    X = xᶜ = abs(x)
    Y = yᶜ = abs(y)

    kxy = yᶜ > xᶜ

    xᶜ = 1 - xᶜ
    yᶜ = 1 - yᶜ

    kxy && (xᶜ = 1 - Y)
    kxy && (yᶜ = 1 - X)

    Z = ((xᶜ + im * yᶜ) / 2)^4
    W = W_Rancic(Z)

    im³ = im^(1/3)
    ra = √3 - 1
    cb = -1 + im
    cc = ra * cb / 2

    W = im³ * (W * im)^(1/3)
    W = (W - ra) / (cb + cc * W)
    X, Y = reim(W)

    H = 2 / (1 + X^2 + Y^2)
    X = X * H
    Y = Y * H
    Z = H - 1

    if kxy
        X, Y = Y, X
    end

    y < 0 && (Y = -Y)
    x < 0 && (X = -X)

    # Fix truncation for x = 0 or y = 0.
    x == 0 && (X = 0)
    y == 0 && (Y = 0)

    return X, Y, Z
end

"""
   conformal_cubed_sphere_inverse_mapping(X, Y, Z)

Inverse mapping for conformal cube sphere for quadrant of north-pole face in which X and Y are both
positive. All other mappings to other cube face coordinates can be recovered from rotations of this map.
There a 3 other quadrants for the north-pole face and five other faces for a total of twenty-four quadrants.
Because of symmetry only the reverse for a single quadrant is needed. Because of branch cuts and the complex
transform the inverse mappings are multi-valued in general, using a single quadrant case allows a simple
set of rules to be applied.

The mapping is valid for the cube face quadrant defined by 0 < x < 1 and 0 < y < 1, where a full cube
face has extent -1 < x < 1 and -1 < y < 1. The quadrant for the mapping is from a cube face that has
"north-pole" at its center (x=0,y=0) i.e has X,Y,Z = (0,0,1) at its center. The valid ranges of X and Y
for this mapping and convention are a quadrant defined be geodesics that connect the points A, B, C and D,
on the shell of a sphere of radius R with X,Y coordinates as follows

A = (0,0)
B = (√2,0)
C = (√3/3,√3/3)
D = (0,√2)

"""
function conformal_cubed_sphere_inverse_mapping(X, Y, Z)
    H  = Z + 1
    Xˢ = X/H
    Yˢ = Y/H
    ω  = Xˢ + im*Yˢ

    ra = √3 - 1
    cb = -1 + im
    cc = ra * cb / 2
    ω⁰ = (ω*cb + ra)/(1-ω*cc)
    W⁰ = im*ω⁰^3*im
    Z  = Z_Rancic(W⁰)
    z  = (Z^0.25)*2
    x, y = reim(z)

    kxy = abs(y) > abs(x)
    xx = x
    yy = y
    !kxy && ( x = 1 - abs(yy) )
    !kxy && ( y = 1 - abs(xx) )

    xf = x
    yf = y
    ( X < Y ) && ( xf = y  )
    ( X < Y ) && ( yf = x  )
    x = xf
    y = yf

    return x, y
end

W_Rancic(Z) = sum(A_Rancic[k] * Z^(k-1) for k in 1:length(A_Rancic))
Z_Rancic(W) = sum(B_Rancic[k] * W^(k-1) for k in 1:length(B_Rancic))


"""
    Semicircle(r)

The Wigner semicircle distribution with radius parameter `r` has probability
density function

```math
f(x; r) = \\frac{2}{\\pi r^2} \\sqrt{r^2 - x^2}, \\quad x \\in [-r, r].
```

```julia
Semicircle(r)   # Wigner semicircle distribution with radius r

params(d)       # Get the radius parameter, i.e. (r,)
```

External links

* [Wigner semicircle distribution on Wikipedia](https://en.wikipedia.org/wiki/Wigner_semicircle_distribution)
"""
struct Semicircle{T<:Real} <: ContinuousUnivariateDistribution
    r::T
    Semicircle{T}(r::T) where {T <: Real} = new{T}(r)
end

function Semicircle(r::T) where {T <: Real}
    @check_args(Semicircle, r > 0)
    return Semicircle{T}(r)
end

Semicircle(r::T, ::NoArgCheck) where {T <: Real} = Semicircle{T}(r)
Semicircle(r::Integer) = Semicircle(float(r))

@distr_support Semicircle -d.r +d.r

params(d::Semicircle) = (d.r,)

mean(d::Semicircle) = zero(d.r)
var(d::Semicircle) = d.r^2 / 4
skewness(d::Semicircle) = zero(d.r)
median(d::Semicircle) = zero(d.r)
mode(d::Semicircle) = zero(d.r)
entropy(d::Semicircle) = log(π * d.r) - oftype(d.r, 0.5)

function pdf(d::Semicircle, x::Real)
    xx, r = promote(x, float(d.r))
    if insupport(d, xx)
        return 2 / (π * r^2) * sqrt(r^2 - xx^2)
    else
        return oftype(r, 0)
    end
end

function logpdf(d::Semicircle, x::Real)
    xx, r = promote(x, float(d.r))
    if insupport(d, xx)
        return log(oftype(r, 2) / π) - 2 * log(r) + log(r^2 - xx^2) / 2
    else
        return oftype(r, -Inf)
    end
end

function cdf(d::Semicircle, x::Real)
    xx, r = promote(x, float(d.r))
    if insupport(d, xx)
        u = xx / r
        return (u * sqrt(1 - u^2) + asin(u)) / π + one(xx) / 2
    elseif x < minimum(d)
        return zero(r)
    else
        return one(r)
    end
end

@quantile_newton Semicircle


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

"""
    Scale(; low=0.25, high=0.75)

Applies the scale transform to all columns of the table.
The scale transform of the column `x` is defined by `(x .- xl) ./ (xh - xl)`,
where `xl = quantile(x, low)` and `xh = quantile(x, high)`.

# Examples

```julia
Scale()
Scale(low=0, high=1)
Scale(low=0.3, high=0.7)
```

## Notes

* The `low` and `high` values are restricted to the interval [0, 1].
"""
struct Scale{T<:Real} <: Colwise
  low::T
  high::T

  function Scale(low::T, high::T) where {T<:Real}
    @assert 0 ≤ low ≤ high ≤ 1 "invalid quantiles"
    new{T}(low, high)
  end
end

Scale(low::Real, high::Real) = Scale(promote(low, high)...)

Scale(; low=0.25, high=0.75) = Scale(low, high)

assertions(::Type{<:Scale}) = [assert_continuous]

isrevertible(::Type{<:Scale}) = true

function colcache(transform::Scale, x)
  levels = (transform.low, transform.high)
  xl, xh = quantile(x, levels)
  xl == xh && ((xl, xh) = (zero(xl), one(xh)))
  (xl=xl, xh=xh)
end

colapply(::Scale, x, c)  = @. (x - c.xl) / (c.xh - c.xl)

colrevert(::Scale, y, c) = @. (c.xh - c.xl) * y + c.xl

"""
    MinMax()

The transform that is equivalent to `Scale(low=0, high=1)`.

See also [`Scale`](@ref).
"""
MinMax() = Scale(low=0, high=1)

"""
    Interquartile()

The transform that is equivalent to `Scale(low=0.25, high=0.75)`.

See also [`Scale`](@ref).
"""
Interquartile() = Scale(low=0.25, high=0.75)


#---------------------------------------------------------------------#
#This function computes the Initial & Analytic Solutions.
#Written by F.X. Giraldo on 10/2003
#           Department of Applied Mathematics
#           Naval Postgraduate School
#           Monterey; CA 93943-5216
#---------------------------------------------------------------------#
function exact_solution(coord,Npoin,c,DFloat)

    #Initialize
    qe=zeros(DFloat,Npoin,1)
    fe=zeros(DFloat,Npoin,1)
    cc=c*π

    #Generate Grid Points
    for I=1:Npoin
        x=coord[1,I]
        y=coord[2,I]
        qe[I]=sin(cc*x)*sin(cc*y)
        fe[I]=-2*cc^2*sin(cc*x)*sin(cc*y)
    end #I

    return (qe,fe)

end


# Equation 33 of https://www.mdpi.com/2227-7390/6/2/16/htm

# Loaded Packages
using FdeSolver
using Plots
using SpecialFunctions

# Parameters
tSpan = [0, 5]         # Time Span
y0 = [1, 0.5, 0.3]     # Initial values
β = [0.5, 0.2, 0.6]    # Order of derivation

# Definition of the System
function F(t, n, β, y)

    F1 = 1 / sqrt(pi) * (((y[n, 2] - 0.5) * (y[n, 3] - 0.3))^(1 / 6) + t[n]^(1 / 2))
    F2 = gamma(2.2) * (y[n, 1] - 1)
    F3 = gamma(2.8) / gamma(2.2) * (y[n, 2] - 0.5)

    return [F1, F2, F3]

end

# Numerical Solution
t, Yapp = FDEsolver(F, tSpan, y0, β, nothing, nc = 5)

# Plot
plot(t, Yapp, linewidth = 5, title = "Solution of system 33",
     xaxis = "Time (t)", yaxis = "y(t)", label = "Approximation")

plot!(t, t -> (t .+ 1), lw = 3, ls = :dash, color= "red", label = "Exact solution")
plot!(t, t -> (t.^1.2 .+ 0.5), lw = 3, ls = :dash, color= "cyan", label = "Exact solution")
plot!(t, t -> (t.^1.8 .+ 0.3), lw = 3, ls = :dash, color= "black" ,label = "Exact solution")


keys = open("public_keys.txt") do f
    parse.(Int, readlines(f))
end

function transform(subject_number::Int64, loop_size::Int64)::Int64
    val = 1
    for i in 1:loop_size
        val = (val * subject_number) % 20201227
    end

    return val
end

function crack(public_keys::Array{Int64, 1}, sn::Int64, ls_range::UnitRange{Int64})::Tuple{Int64, Int64}
    val = 1
    for ls in ls_range
        val = (val * sn) % 20201227
        if val == public_keys[1]
            # println("sn $(sn), ls $(ls) match first")
            val2 = 1
            for ls2 in ls_range
                val2 = (val2 * sn) % 20201227
                if val2 == public_keys[2] && ls != ls2
                    # println("sn $(sn), ls $(ls2) match second")

                    return ls, ls2
                end
            end
            # println("no matching ls2 found")
        end
    end

    return nothing, nothing
end

ls1, ls2 = crack(keys, 7, 1:100_000_000)

println(transform(keys[1], ls2))
# println(transform(keys[2], ls1))


#############################################################################
# eigmin_max.jl
# Handles maximum and minimum eigenvalue of a symmetric positive definite matrix
# (and imposes the constraint that its argument be PSD)
# All expressions and atoms are subtypes of AbstractExpr.
# Please read expressions.jl first.
#############################################################################

import LinearAlgebra: eigmin, eigmax

### Eig max

struct EigMaxAtom <: AbstractExpr
    head::Symbol
    id_hash::UInt64
    children::Tuple{AbstractExpr}
    size::Tuple{Int, Int}

    function EigMaxAtom(x::AbstractExpr)
        children = (x,)
        m, n = size(x)
        if m == n
            return new(:eigmax, hash(children), children, (1, 1))
        else
            error("eigmax can only be applied to a square matrix.")
        end
    end
end

function sign(x::EigMaxAtom)
    return NoSign()
end

function monotonicity(x::EigMaxAtom)
    return (Nondecreasing(),)
end

function curvature(x::EigMaxAtom)
    return ConvexVexity()
end

function evaluate(x::EigMaxAtom)
    eigmax(evaluate(x.children[1]))
end

eigmax(x::AbstractExpr) = EigMaxAtom(x)

# Create the equivalent conic problem:
#   minimize t
#   subject to
#            tI - A is positive semidefinite
#            A      is positive semidefinite
function conic_form!(x::EigMaxAtom, unique_conic_forms)
    if !has_conic_form(unique_conic_forms, x)
        A = x.children[1]
        m, n = size(A)
        t = Variable()
        p = minimize(t, t*Matrix(1.0I, n, n) - A ⪰ 0)
        cache_conic_form!(unique_conic_forms, x, p)
    end
    return get_conic_form(unique_conic_forms, x)
end

### Eig min

struct EigMinAtom <: AbstractExpr
    head::Symbol
    id_hash::UInt64
    children::Tuple{AbstractExpr}
    size::Tuple{Int, Int}

    function EigMinAtom(x::AbstractExpr)
        children = (x,)
        m, n = size(x)
        if m == n
            return new(:eigmin, hash(children), children, (1,1))
        else
            error("eigmin can only be applied to a square matrix.")
        end
    end
end

function sign(x::EigMinAtom)
    return NoSign()
end

function monotonicity(x::EigMinAtom)
    return (Nondecreasing(),)
end

function curvature(x::EigMinAtom)
    return ConcaveVexity()
end

function evaluate(x::EigMinAtom)
    eigmin(evaluate(x.children[1]))
end

eigmin(x::AbstractExpr) = EigMinAtom(x)

# Create the equivalent conic problem:
#   maximize t
#   subject to
#            A - tI is positive semidefinite
#            A      is positive semidefinite
function conic_form!(x::EigMinAtom, unique_conic_forms)
    if !has_conic_form(unique_conic_forms, x)
        A = x.children[1]
        m, n = size(A)
        t = Variable()
        p = maximize(t, A - t*Matrix(1.0I, n, n) ⪰ 0)
        cache_conic_form!(unique_conic_forms, x, p)
    end
    return get_conic_form(unique_conic_forms, x)
end


# NOTE: this is mostly just copied from wikipedia
function gmres_cycle(A::SparseMatrixCSC, x0::Vector{Float64}, b::Vector{Float64}, m::Int64; err_thresh = 1e-20)

    # compute initial residual, norm(b) and initial error
    r = b - A * x0
    bnorm = norm(b)
    err = norm(r) / bnorm

    n = length(x0)
    Q = zeros(Float64, (n,m+1))
    H = zeros(Float64, (m+2,m+1))
    sn = zeros(Float64, m)
    cn = zeros(Float64, m)
    e1 = zeros(Float64, m+1)
    e1[1] = 1.0

    beta = norm(r) * e1
    Q[:,1] = r / norm(r)

    # begin iteration
    nk = 0
    for k=1:m
        nk += 1

        # do arnoldi iteration
        q = A * Q[:,k]
        for i=1:k
            H[i,k] = transpose(q) * Q[:,i]
            q = q - H[i,k] * Q[:,i]
        end
        qnorm = norm(q)
        H[k+1,k] = qnorm
        Q[:,k+1] = q / qnorm

        # apply givens rotation
        for i=1:k-1
            temp = cn[i] * H[i,k] + sn[i] * H[i+1,k]
            H[i+1,k] = -sn[i] * H[i,k] + cn[i] * H[i+1,k]
            H[i,k] = temp
        end

        # update sn and cn
        t = sqrt(H[k,k]^2 + H[k+1,k]^2)
        cn[k] = H[k,k] / t
        sn[k] = H[k+1,k] / t

        # eliminate H[k+1, k]
        H[k,k] = cn[k] * H[k,k] + sn[k] * H[k+1,k]
        H[k+1,k] = 0.0

        # update residual
        beta[k+1] = -sn[k]*beta[k]
        beta[k] = cn[k]*beta[k]
        err = abs(beta[k+1]) / bnorm

        if err < err_thresh
            break
        end
    end

    # compute correction to x
    y = H[1:nk,1:nk] \ beta[1:nk]
    x = x0 + Q[:,1:nk] * y
    return x, err
end

function gmres_solve(A::SparseMatrixCSC, x0::Vector{Float64}, b::Vector{Float64}, m::Int64; maxiters=10000, err_thresh = 1e-8)

    x = x0[:]
    err = 1.0
    iteration = 0

    while (err > err_thresh)
        x, err = gmres_cycle(A, x, b, m, err_thresh=err_thresh)
        iteration += 1

        if iteration > maxiters
            @warn "Iteration limit reached, defaulting to direct solver."
            return A \ b
        end
    end

    return x
end


using JuMP
using ECOS
using LinearAlgebra
using Random

Random.seed!(1234);

u0 = rand(10)
p = rand(10)
q = rand();

model = Model(optimizer_with_attributes(ECOS.Optimizer, "printlevel" => 0))
@variable(model, u[1:10])
@variable(model, t)
@objective(model, Min, t)
@constraint(model, [t, (u - u0)...] in SecondOrderCone())
@constraint(model, u' * p == q)
optimize!(model)

@show objective_value(model);
@show value.(u);

e1 = [1, zeros(10)...]
dual_model = Model(optimizer_with_attributes(ECOS.Optimizer, "printlevel" => 0))
@variable(dual_model, y1 <= 0)
@variable(dual_model, y2[1:11])
@objective(dual_model, Max, q * y1 + dot(vcat(0, u0), y2))
@constraint(dual_model, e1 - [0, p...] .* y1 - y2 .== 0)
@constraint(dual_model, y2 in SecondOrderCone())
optimize!(dual_model)

@show objective_value(dual_model);

model = Model(optimizer_with_attributes(ECOS.Optimizer, "printlevel" => 0))
@variable(model, u[1:10])
@variable(model, t)
@objective(model, Min, t)
@constraint(model, [t, 0.5, (u - u0)...] in RotatedSecondOrderCone())
@constraint(model, u' * p == q)
optimize!(model)

@show value.(u);

n = 15;
m = 10;
A = randn(m, n);
b = rand(m, 1);

model = Model(optimizer_with_attributes(ECOS.Optimizer, "printlevel" => 0))
@variable(model, t[1:n])
@variable(model, x[1:n])
@objective(model, Max, sum(t))
@constraint(model, sum(x) == 1)
@constraint(model, A * x .<= b )
# Cannot use the exponential cone directly in JuMP, hence we use MOI to specify the set.
@constraint(model, con[i = 1:n], [1, x[i], t[i]] in MOI.ExponentialCone())

optimize!(model);

@show objective_value(model);


"""
Remove minimum number of nodes so that the graph no longer exists k-core
"""
function decore(g, k=2)
	attack_nodes = Int[]
	deg = degree(g) # adaptive degrees
	degmax = maximum(deg)
	H = [Set{Int}() for i=1:degmax]

	for i in vertices(g)
		if deg[i] > 0
			degi = max(deg[i], k-1)
			push!(H[degi], i)
		end
	end

	d = isempty(H[k-1]) ? degmax : k-1
	cnt = 0
	done = false
	while cnt < nv(g) && done == false
		cnt += 1
		i = rand(H[d])
		delete!(H[d], i)
		deg[i] = 0
		d >= k && push!(attack_nodes, i)

		# update neighbors
		for j in neighbors(g,i)
			if deg[j] >= k
				delete!(H[deg[j]], j)
				deg[j] -= 1
				push!(H[deg[j]],j)
			end
		end

		while isempty(H[degmax])
			degmax -= 1
			done = degmax < k
		end
		d = isempty(H[k-1]) ? degmax : k-1
	end
	attack_nodes
end


function voigt(x, σ, γ)
    faddeeva(z) = erfcx(-im * z)
    z = (x + im * γ) / σ / sqrt(2)
    return real(faddeeva(z)) / (σ * sqrt(2pi))
end

# J.J.Olivero and R.L. Longbothum in Empirical fits to the Voigt line width: A brief review, JQSRT 17, P233, 1977
# http://snst-hu.lzu.edu.cn/zhangyi/ndata/Voigt_profile.html
function voigt_fwhm(σ, γ)
    @assert σ > 0
    @assert γ > 0
    fg = σ * 2.355
    fl = γ * 2
    return fl * 0.5346 + sqrt(0.2166 * fl^2 + fg^2)
    #=
    Introducing dampening parameter:
    a = fl / fg
    fwhm = fl * (0.5346 + sqrt(0.2166 + (1/a)^2))
    =#
end

function voigt_σγ(fwhm, log_a)
    a = 10. ^log_a
    fl = fwhm / (0.5346 + sqrt(0.2166 + (1/a)^2))
    fg = fl / a
    return (fg / 2.355, fl / 2)
end
# log_a = log10((γ * 2) / (σ * 2.355));  QSFit.voigt_σγ(QSFit.voigt_fwhm(σ, γ), log_a), (σ, γ)

function voigt_γ(fwhm, σ)
    A = 0.5346
    B = 0.2166
    C = (σ * 2.355)^2
    # fwhm = A * fl + sqrt(B * fl^2 + C)
    fl = A * fwhm / (A^2 - B) - sqrt((A^2 * C - B * C + B * fwhm^2) / ((A^2 - B)^2))
    γ = fl / 2
    @assert (fwhm - voigt_fwhm(σ, γ)) / fwhm < 1e-5
    return γ
end
# QSFit.voigt_γ(QSFit.voigt_fwhm(σ, γ), σ), γ


# ---
# title: 504. Base 7
# id: problem504
# author: zhwang
# date: 2022-03-10
# difficulty: Easy
# categories:
# link: <https://leetcode.com/problems/base-7/description/>
# hidden: true
# ---
#
# Given an integer, return its base 7 string representation.
#
# **Example 1:**
#
#
#
#     Input: 100
#     Output: "202"
#
#
# **Example 2:**
#
#
#
#     Input: -7
#     Output: "-10"
#
#
# **Note:** The input will be in range of [-1e7, 1e7].
#
#
## @lc code=start
using LeetCode

function convert_to_base7(num::Int)::String
    num == 0 && return "0"
    sign = num < 0 ? -1 : 1
    num *= sign
    res = Int[]
    while num > 0
        push!(res, num % 7)
        num ÷= 7
    end
    reverse!(res)
    return sign != -1 ? join(res) : "-" * join(res)
end

## @lc code=end


# # An equation of second order in two dimmensions
#
# Let \\(\Omega = (0, 1)^2\\) the unit square and denote \\( \Gamma = \partial \Omega\\) its boundary.
# Consider the following problem. Given \\(f \in C(\Omega)\\), find a function \\(u\\) satisfying
# -\Delta u + u = f in \Omega
# u = 0 on \Gamma

using SymFEL
using SymPy
using LinearAlgebra
using SparseArrays
using WriteVTK
using JLD2

## discretization parameters
# we use the mesh square.msh (in gmsh format)
# obtained from square.geo using gmsh
# this mesh is formed by quad elements
# we use gmsh module for read the mesh
# there are some problems using Threads and gmsh (in linux)
# the file is square.jld2 is prepared separetely
@load "square.jld2"

# elementary matrices - P2 x P2
elem_Mxy = SymFEL.get_square_lagrange_em((2, 2), (0, 0), (0, 0))
elem_Kxy = SymFEL.get_square_lagrange_em((2, 2), (1, 0), (1, 0)) +
    SymFEL.get_square_lagrange_em((2, 2), (0, 1), (0, 1))

dx = norm(nodes_coordinate[:, elements_bound[1,1]] - nodes_coordinate[:, elements_bound[2,1]])

elem_Kxy_dx = convert(Matrix{Float64}, elem_Kxy.subs(h, dx))
elem_Mxy_dx = convert(Matrix{Float64}, elem_Mxy.subs(h, dx));

# global matrices
K = SymFEL.assemble_squaremesh_FE_matrix(elem_Kxy_dx, elements_int, order1=2, order2=2)
M = SymFEL.assemble_squaremesh_FE_matrix(elem_Mxy_dx, elements_int, order1=2, order2=2)

f = (2*pi^2 + 1) * sin.(pi * nodes_coordinate[1,:]) .* sin.(pi * nodes_coordinate[2,:])

F = M * f

A = K + M

# boundary condition
tgv = 1e30
A[nodes_boundary, nodes_boundary] += tgv * sparse(Matrix{Float64}(I, nodes_boundary_N, nodes_boundary_N))

u = A \ F
u_exact = sin.(pi*nodes_coordinate[1,:]) .* sin.(pi*nodes_coordinate[2,:])
err = u - u_exact

println("L2 error : ", sqrt(err' * M * err))
println("H1 error : ", sqrt(err' * K * err))

# export to vtk
cells = [MeshCell(VTKCellTypes.VTK_QUADRATIC_QUAD, elements_int[1:8, i]) for i = 1:elements_int_N]

points_x = nodes_coordinate[1, :]
points_y = nodes_coordinate[2, :]
vtkfile = vtk_grid("ex3-output", points_x, points_y, cells)

vtkfile["my_point_data", VTKPointData()] = u
outfiles = vtk_save(vtkfile)


#=
from "Error-free transformations in real and complex floating point arithmetics"
by Stef Graillat and Valérie Ménissier-Morain
2007 International Symposium on Nonlinear Theory and its Applications
NOLTA'07, Vancouver, Canada, September 16-19, 2007
=#
@inline function two_sum(a::Complex{T}, b::Complex{T}) where {T<:Real}
    hi1, lo1 = two_sum(a.re, b.re)
    hi2, lo2 = two_sum(a.im, b.im)
    hi = Complex{T}(hi1, hi2)
    lo = Complex{T}(lo1, lo2)
    return hi, lo
end

@inline function one_sum(a::Complex{T}, b::Complex{T}) where {T<:Real}
    hi1, lo1 = two_sum(a.re, b.re)
    hi2, lo2 = two_sum(a.im, b.im)
    hi = Complex{T}(hi1, hi2)
    return hi
end

two_hilo_sum(a::Complex{T}, b::Complex{T}) where {T<:Real} = two_sum(a, b)
one_hilo_sum(a::Complex{T}, b::Complex{T}) where {T<:Real} = one_sum(a, b)

@inline function two_diff(a::Complex{T}, b::Complex{T}) where {T<:Real}
    hi1, lo1 = two_diff(a.re, b.re)
    hi2, lo2 = two_diff(a.im, b.im)
    hi = Complex{T}(hi1, hi2)
    lo = Complex{T}(lo1, lo2)
    return hi, lo
end

@inline function one_diff(a::Complex{T}, b::Complex{T}) where {T<:Real}
    hi1, lo1 = two_diff(a.re, b.re)
    hi2, lo2 = two_diff(a.im, b.im)
    hi = Complex{T}(hi1, hi2)
    return hi
end

two_hilo_diff(a::Complex{T}, b::Complex{T}) where {T<:Real} = two_diff(a, b)
one_hilo_diff(a::Complex{T}, b::Complex{T}) where {T<:Real} = one_diff(a, b)

#=
from "Error-free transformations in real and complex floating point arithmetics"
by Stef Graillat and Valérie Ménissier-Morain
2007 International Symposium on Nonlinear Theory and its Applications
NOLTA'07, Vancouver, Canada, September 16-19, 2007
N.B. modified to order q,r,s
=#
@inline function four_prod(a::Complex{T}, b::Complex{T}) where {T<:Real}
    hi1, lo1 = two_prod(a.re, b.re)
    hi2, lo2 = two_prod(a.im, b.im)
    hi3, lo3 = two_prod(a.re, b.im)
    hi4, lo4 = two_prod(a.im, b.re)
    hi5, lo5 = two_diff(hi1, hi2)
    hi6, lo6 = two_sum(hi3, hi4)
    p = Complex{T}(hi5, hi6)
    q = Complex{T}(lo5, lo6)
    r = Complex{T}(lo1, lo3)
    s = Complex{T}(-lo2, lo4)
    r, s = two_sum(r, s)
    q, r = two_sum(q, r)
    return p, q, r, s
end

@inline function three_prod(a::Complex{T}, b::Complex{T}) where {T<:Real}
    hi1, lo1 = two_prod(a.re, b.re)
    hi2, lo2 = two_prod(a.im, b.im)
    hi3, lo3 = two_prod(a.re, b.im)
    hi4, lo4 = two_prod(a.im, b.re)
    hi5, lo5 = two_diff(hi1, hi2)
    hi6, lo6 = two_sum(hi3, hi4)
    p = Complex{T}(hi5, hi6)
    q = Complex{T}(lo5, lo6)
    r = Complex{T}(lo1, lo3)
    s = Complex{T}(-lo2, lo4)
    q, r = two_sum(q, r, s)
    return p, q, r
end

@inline function two_prod(a::Complex{T}, b::Complex{T}) where {T<:Real}
    hi1, lo1 = two_prod(a.re, b.re)
    hi2, lo2 = two_prod(a.im, b.im)
    hi3, lo3 = two_prod(a.re, b.im)
    hi4, lo4 = two_prod(a.im, b.re)
    hi5, lo5 = two_diff(hi1, hi2)
    hi6, lo6 = two_sum(hi3, hi4)
    p = Complex{T}(hi5, hi6)
    re = one_sum(lo5, lo1, lo2)
    im = one_sum(lo6, lo3, lo4)
    q = Complex{T}(re, im)
    return p, q
end

@inline function one_prod(a::Complex{T}, b::Complex{T}) where {T<:Real}
    hi1, lo1 = two_prod(a.re, b.re)
    hi2, lo2 = two_prod(a.im, b.im)
    hi3, lo3 = two_prod(a.re, b.im)
    hi4, lo4 = two_prod(a.im, b.re)
    hi5, lo5 = two_diff(hi1, hi2)
    hi6 = hi3 + hi4
    p = Complex{T}(hi5, hi6)
    return p
end


"""
    lu_decompose(mat)
Decomposes a `n x n` non singular matrix into a lower triangular matrix (L) and an upper triangular matrix (U)
"""
function lu_decompose(mat)
    n = first(size(mat))
    L = zeros(n, n)
    U = zeros(n, n)

    for i in 1:n
        for j in i:n
            s = 0
            for k in 1:i
                s += L[i, k] * U[k, j]
            end
            U[i, j] = mat[i, j] - s
        end

        for k in i:n
            if i == k
                L[i, i] = 1
            else
                s = 0
                for j in 1:i
                    s += L[k, j] * U[j, i]
                end
                L[k, i] = (mat[k, i] - s) / U[i, i]
            end
        end
    end

    return L, U
end


"""
    Euler(; h = 0.0) :: ExplicitRungeKuttaSolver
    ExplicitEuler(args...; kwargs...) :: ExplicitRungeKuttaSolver

returns an [`ExplicitRungeKuttaSolver`](@ref) for the 1st-order Euler method.
```
"""
function Euler(; h = 0.0)
    h_T = typeof(h)
    tableau = ButcherTableau(h_T[
        0 0;
        1 1;
    ])
    return ERK(tableau, h)
end
@doc (@doc Euler) ExplicitEuler(args...; kwargs...) = Euler(args...; kwargs...)

"""
    Heun2(; h = 0.0) :: ExplicitRungeKuttaSolver

returns an [`ExplicitRungeKuttaSolver`](@ref) for the 2nd-order Heun method.
"""
function Heun2(; h = 0.0)
    h_T = typeof(h)
    tableau = ButcherTableau(h_T[
        0   0   0 ;
        1   1   0 ;
        2  1/2 1/2;
    ])
    return ERK(tableau, h)
end

"""
    Ralston2(; h = 0.0) :: ExplicitRungeKuttaSolver

returns an [`ExplicitRungeKuttaSolver`](@ref) for the 2nd-order Ralston method.
"""
function Ralston2(; h = 0.0)
    h_T = typeof(h)
    tableau = ButcherTableau(h_T[
         0   0   0 ;
        2/3 2/3  0 ;
         2  1/4 3/4;
    ])
    return ERK(tableau, h)
end

"""
    Midpoint(; h = 0.0) :: ExplicitRungeKuttaSolver
    ExplicitMidpoint(args...; kwargs...) :: ExplicitRungeKuttaSolver

returns an [`ExplicitRungeKuttaSolver`](@ref) for the 2nd-order midpoint method.
"""
function Midpoint(; h = 0.0)
    h_T = typeof(h)
    tableau = ButcherTableau(h_T[
         0   0   0;
        1/2 1/2  0;
         2   0   1;
    ])
    return ERK(tableau, h)
end
@doc (@doc Midpoint) ExplicitMidpoint(args...; kwargs...) = Midpoint(args...; kwargs...)

"""
    Heun3(; h = 0.0) :: ExplicitRungeKuttaSolver

returns an [`ExplicitRungeKuttaSolver`](@ref) for the 3rd-order Heun method.
"""
function Heun3(; h = 0.0)
    h_T = typeof(h)
    tableau = ButcherTableau(h_T[
         0   0   0   0 ;
        1/3 1/3  0   0 ;
        2/3  0  2/3  0 ;
         3  1/4  0  3/4;
    ])
    return ERK(tableau, h)
end

"""
    Kutta3(; h = 0.0) :: ExplicitRungeKuttaSolver

returns an [`ExplicitRungeKuttaSolver`](@ref) for the 3rd-order Kutta method.
"""
function Kutta3(; h = 0.0)
    h_T = typeof(h)
    tableau = ButcherTableau(h_T[
         0   0   0   0 ;
        1/2 1/2  0   0 ;
         1  -1   2   0 ;
         3  1/6 2/3 1/6;
    ])
    return ERK(tableau, h)
end

"""
    Ralston3(; h = 0.0) :: ExplicitRungeKuttaSolver

returns an [`ExplicitRungeKuttaSolver`](@ref) for the 3rd-order Ralston method.
"""
function Ralston3(; h = 0.0)
    h_T = typeof(h)
    tableau = ButcherTableau(h_T[
         0   0   0   0 ;
        1/2 1/2  0   0 ;
        3/4  0  3/4  0 ;
         3  2/9 1/3 4/9;
    ])
    return ERK(tableau, h)
end

"""
    SSPRK3(; h = 0.0) :: ExplicitRungeKuttaSolver

returns an [`ExplicitRungeKuttaSolver`](@ref) for the 3rd-order Strong-Stability-Preserving Runge-Kutta method.
"""
function SSPRK3(; h = 0.0)
    h_T = typeof(h)
    tableau = ButcherTableau(h_T[
         0   0   0   0 ;
         1   1   0   0 ;
        1/2 1/4 1/4  0 ;
         3  1/6 1/6 2/3;
    ])
    return ERK(tableau, h)
end

"""
    RK4(; h = 0.0) :: ExplicitRungeKuttaSolver

returns an [`ExplicitRungeKuttaSolver`](@ref) for the 4th-order Runge-Kutta method.
"""
function RK4(; h = 0.0)
    h_T = typeof(h)
    tableau = ButcherTableau(h_T[
         0   0   0   0   0 ;
        1/2 1/2  0   0   0 ;
        1/2  0  1/2  0   0 ;
         1   0   0   1   0 ;
         4  1/6 1/3 1/3 1/6;
    ])
    return ERK(tableau, h)
end

"""
    Rule38(; h = 0.0) :: ExplicitRungeKuttaSolver

returns an [`ExplicitRungeKuttaSolver`](@ref) for the 4th-order 3/8-rule method.
"""
function Rule38(; h = 0.0)
    h_T = typeof(h)
    tableau = ButcherTableau(h_T[
         0    0    0    0    0 ;
        1/3  1/3   0    0    0 ;
        2/3 -1/3   1    0    0 ;
         1    1   -1    1    0 ;
         4   1/8  3/8  3/8  1/8;
    ])
    return ERK(tableau, h)
end

"""
    HeunEuler(; h = 0.0, δ = 0.0, ϵ = 1e-5, K = 100) :: ExplicitRungeKuttaSolver

returns an [`ExplicitRungeKuttaSolver`](@ref) for the 2nd-order Heun-Euler method with 1st-order error estimate.
"""
function HeunEuler(; h = 0.0, δ = 0.0, ϵ = 1e-5, K = 100)
    h_T = typeof(h)
    tableau = ButcherTableau(h_T[
        0   0   0 ;
        1   1   0 ;
        2  1/2 1/2;
        1   1   0 ;
    ])
    adaptive = AdaptiveParameters(δ=δ, ϵ=ϵ, K=K)
    return ERK(tableau, h, adaptive)
end

"""
    Fehlberg45(; h = 0.0, δ = 0.0, ϵ = 1e-5, K = 100) :: ExplicitRungeKuttaSolver
    F45(args...; kwargs...) :: ExplicitRungeKuttaSolver

returns an [`ExplicitRungeKuttaSolver`](@ref) for the 4th-order Fehlberg method with 5th-order error estimate.
"""
function Fehlberg45(; h = 0.0, δ = 0.0, ϵ = 1e-5, K = 100)
    h_T = typeof(h)
    tableau = ButcherTableau(h_T[
          0       0          0          0           0         0    0  ;
         1/4     1/4         0          0           0         0    0  ;
         3/8     3/32       9/32        0           0         0    0  ;
        12/13 1932/2197 -7200/2197  7296/2197       0         0    0  ;
          1    439/216      -8      3680/513    -845/4104     0    0  ;
         1/2    -8/27        2     -3544/2565   1859/4104  -11/40  0  ;
          4     25/216       0      1408/2565   2197/4104   -1/5   0  ;
          5     16/135       0      6656/12825 28561/56430  -9/50 2/55;
    ])
    adaptive = AdaptiveParameters(δ=δ, ϵ=ϵ, K=K)
    return ERK(tableau, h, adaptive)
end
@doc (@doc Fehlberg45) F45(args...; kwargs...) = Fehlberg45(args...; kwargs...)

"""
    DormandPrince54(; h = 0.0, δ = 0.0, ϵ = 1e-5, K = 100) :: ExplicitRungeKuttaSolver
    DP54(args...; kwargs...) :: ExplicitRungeKuttaSolver

returns an [`ExplicitRungeKuttaSolver`](@ref) for the 5th-order Dormand-Prince method with 4th-order error estimate.
"""
function DormandPrince54(; h = 0.0, δ = 0.0, ϵ = 1e-5, K = 100)
    h_T = typeof(h)
    tableau = ButcherTableau(h_T[
         0        0            0          0          0          0          0      0  ;
        1/5      1/5           0          0          0          0          0      0  ;
        3/10     3/40         9/40        0          0          0          0      0  ;
        4/5     44/45       -56/15      32/9         0          0          0      0  ;
        8/9  19372/6561  -25360/2187 64448/6561  -212/729       0          0      0  ;
         1    9017/3168    -355/33   46732/5247    49/176  -5103/18656     0      0  ;
         1      35/384         0       500/1113   125/192  -2187/6784    11/84    0  ;
         5      35/384         0       500/1113   125/192  -2187/6784    11/84    0  ;
         4    5179/57600       0      7571/16695  393/640 -92097/339200 187/2100 1/40;
    ])
    adaptive = AdaptiveParameters(δ=δ, ϵ=ϵ, K=K)
    return ERK(tableau, h, adaptive)
end
@doc (@doc DormandPrince54) DP54(args...; kwargs...) = DormandPrince54(args...; kwargs...)

"""
    Verner65(; h = 0.0, δ = 0.0, ϵ = 1e-5, K = 100) :: ExplicitRungeKuttaSolver
    V65(args...; kwargs...) :: ExplicitRungeKuttaSolver

returns an [`ExplicitRungeKuttaSolver`](@ref) for the 6th-order Verner method with 5th-order error estimate.
"""
function Verner65(; h = 0.0, δ = 0.0, ϵ = 1e-5, K = 100)
    h_T = typeof(h)
    tableau = ButcherTableau(h_T[
         0        0          0         0          0          0       0       0        0   ;
        1/6      1/6         0         0          0          0       0       0        0   ;
        4/15     4/75      16/75       0          0          0       0       0        0   ;
        2/3      5/6       -8/3       5/2         0          0       0       0        0   ;
        5/6   -165/64      55/6    -425/64      85/96        0       0       0        0   ;
         1      12/5        -8     4015/612    -11/36      88/255    0       0        0   ;
        1/15 -8263/15000  124/75   -643/680    -81/250   2484/10625  0       0        0   ;
         1    3501/1720  -300/43 297275/52632 -319/2322 24068/84065  0   3850/26703   0   ;
         6       3/40        0      875/2244    23/72     264/1955   0    125/11592 43/616;
         5      13/160       0     2375/5984     5/16      12/85    3/44     0        0   ;
    ])
    adaptive = AdaptiveParameters(δ=δ, ϵ=ϵ, K=K)
    return ERK(tableau, h, adaptive)
end
@doc (@doc Verner65) V65(args...; kwargs...) = Verner65(args...; kwargs...)

"""
    BackwardEuler(; h = 0.0, ϵ = 1e-3, K = 10) :: ImplicitRungeKuttaSolver
    ImplicitEuler(args...; kwargs...) :: ImplicitRungeKuttaSolver

returns an [`ImplicitRungeKuttaSolver`](@ref) for the 1st-order backward Euler method.
"""
function BackwardEuler(; h = 0.0, ϵ = 1e-3, K = 10)
    h_T = typeof(h)
    tableau = ButcherTableau(h_T[
        1 1;
        1 1;
    ])
    newton = NewtonParameters(ϵ=ϵ, K=K)
    return IRK(tableau, h, newton)
end
@doc (@doc BackwardEuler) ImplicitEuler(args...; kwargs...) = BackwardEuler(args...; kwargs...)

"""
    ImplicitMidpoint(; h = 0.0, ϵ = 1e-3, K = 10) :: ImplicitRungeKuttaSolver

returns an [`ImplicitRungeKuttaSolver`](@ref) for the 2nd-order implicit midpoint method.
"""
function ImplicitMidpoint(; h = 0.0, ϵ = 1e-3, K = 10)
    h_T = typeof(h)
    tableau = ButcherTableau(h_T[
        1/2 1/2;
         2   1 ;
    ])
    newton = NewtonParameters(ϵ=ϵ, K=K)
    return IRK(tableau, h, newton)
end

"""
    CrankNicolson(; h = 0.0, ϵ = 1e-3, K = 10) :: ImplicitRungeKuttaSolver

returns an [`ImplicitRungeKuttaSolver`](@ref) for the 2nd-order Crank-Nicolson method.
"""
function CrankNicolson(; h = 0.0, ϵ = 1e-3, K = 10)
    h_T = typeof(h)
    tableau = ButcherTableau(h_T[
        0   0   0 ;
        1  1/2 1/2;
        2  1/2 1/2;
    ])
    newton = NewtonParameters(ϵ=ϵ, K=K)
    return IRK(tableau, h, newton)
end

"""
    SDIRK3(; h = 0.0, ϵ = 1e-3, K = 10) :: ImplicitRungeKuttaSolver

returns an [`ImplicitRungeKuttaSolver`](@ref) for the 3rd-order SDIRK method.
"""
function SDIRK3(; h = 0.0, ϵ = 1e-3, K = 10)
    γ = 1/2 + √3/6
    h_T = typeof(h)
    tableau = ButcherTableau(h_T[
         γ    γ   0 ;
        1-γ 1-2γ  γ ;
         3   1/2 1/2;
    ])
    newton = NewtonParameters(ϵ=ϵ, K=K)
    return IRK(tableau, h, newton)
end

"""
    GaussLegendre4(; h = 0.0, ϵ = 1e-3, K = 10) :: ImplicitRungeKuttaSolver
    GL4(args...; kwargs...) :: ImplicitRungeKuttaSolver

returns an [`ImplicitRungeKuttaSolver`](@ref) for the 4th-order Gauss-Legendre method.
"""
function GaussLegendre4(; h = 0.0, ϵ = 1e-3, K = 10)
    h_T = typeof(h)
    tableau = ButcherTableau(h_T[
        1/2-√3/6   1/4    1/4-√3/6;
        1/2+√3/6 1/4+√3/6   1/4   ;
           4       1/2      1/2   ;
    ])
    newton = NewtonParameters(ϵ=ϵ, K=K)
    return IRK(tableau, h, newton)
end
@doc (@doc GaussLegendre4) GL4(args...; kwargs...) = GaussLegendre4(args...; kwargs...)

"""
    GaussLegendre6(; h = 0.0, ϵ = 1e-3, K = 10) :: ImplicitRungeKuttaSolver
    GL6(args...; kwargs...) :: ImplicitRungeKuttaSolver

returns an [`ImplicitRungeKuttaSolver`](@ref) for the 6th-order Gauss–Legendre method.
"""
function GaussLegendre6(; h = 0.0, ϵ = 1e-3, K = 10)
    h_T = typeof(h)
    tableau = ButcherTableau(h_T[
        1/2-√15/10     5/36    2/9-√15/15 5/36-√15/30;
           1/2     5/36+√15/24    2/9     5/36-√15/24;
        1/2+√15/10 5/36+√15/30 2/9+√15/15     5/36   ;
            6          5/18       4/9         5/18   ;
    ])
    newton = NewtonParameters(ϵ=ϵ, K=K)
    return IRK(tableau, h, newton)
end
@doc (@doc GaussLegendre6) GL6(args...; kwargs...) = GaussLegendre6(args...; kwargs...)

"""
    LobattoIIIA4(; h = 0.0, ϵ = 1e-3, K = 10) :: ImplicitRungeKuttaSolver

returns an [`ImplicitRungeKuttaSolver`](@ref) for the 4th-order Lobatto IIIA method.
"""
function LobattoIIIA4(; h = 0.0, ϵ = 1e-3, K = 10)
    h_T = typeof(h)
    tableau = ButcherTableau(h_T[
         0   0    0    0  ;
        1/2 5/24 1/3 -1/24;
         1  1/6  2/3  1/6 ;
         4  1/6  2/3  1/6 ;
    ])
    newton = NewtonParameters(ϵ=ϵ, K=K)
    return IRK(tableau, h, newton)
end

"""
    LobattoIIIB2(; h = 0.0, ϵ = 1e-3, K = 10) :: ImplicitRungeKuttaSolver

returns an [`ImplicitRungeKuttaSolver`](@ref) for the 2nd-order Lobatto IIIB method.
"""
function LobattoIIIB2(; h = 0.0, ϵ = 1e-3, K = 10)
    h_T = typeof(h)
    tableau = ButcherTableau(h_T[
        1/2 1/2  0 ;
        1/2 1/2  0 ;
         2  1/2 1/2;
    ])
    newton = NewtonParameters(ϵ=ϵ, K=K)
    return IRK(tableau, h, newton)
end

"""
    LobattoIIIB4(; h = 0.0, ϵ = 1e-3, K = 10) :: ImplicitRungeKuttaSolver

returns an [`ImplicitRungeKuttaSolver`](@ref) for the 4th-order Lobatto IIIB method.
"""
function LobattoIIIB4(; h = 0.0, ϵ = 1e-3, K = 10)
    h_T = typeof(h)
    tableau = ButcherTableau(h_T[
         0  1/6 -1/6  0 ;
        1/2 1/6  1/3  0 ;
         1  1/6  5/6  0 ;
         4  1/6  2/3 1/6;
    ])
    newton = NewtonParameters(ϵ=ϵ, K=K)
    return IRK(tableau, h, newton)
end

"""
    LobattoIIIC2(; h = 0.0, ϵ = 1e-3, K = 10) :: ImplicitRungeKuttaSolver

returns an [`ImplicitRungeKuttaSolver`](@ref) for the 2nd-order Lobatto IIIC method.
"""
function LobattoIIIC2(; h = 0.0, ϵ = 1e-3, K = 10)
    h_T = typeof(h)
    tableau = ButcherTableau(h_T[
        0  1/2 -1/2;
        1  1/2  1/2;
        2  1/2  1/2;
    ])
    newton = NewtonParameters(ϵ=ϵ, K=K)
    return IRK(tableau, h, newton)
end

"""
    LobattoIIIC4(; h = 0.0, ϵ = 1e-3, K = 10) :: ImplicitRungeKuttaSolver

returns an [`ImplicitRungeKuttaSolver`](@ref) for the 4th-order Lobatto IIIC method.
"""
function LobattoIIIC4(; h = 0.0, ϵ = 1e-3, K = 10)
    h_T = typeof(h)
    tableau = ButcherTableau(h_T[
         0   1/6 -1/3   1/6 ;
        1/2  1/6  5/12 -1/12;
         1   1/6  2/3   1/6 ;
         4   1/6  2/3   1/6 ;
    ])
    newton = NewtonParameters(ϵ=ϵ, K=K)
    return IRK(tableau, h, newton)
end

"""
    RadauIA3(; h = 0.0, ϵ = 1e-3, K = 10) :: ImplicitRungeKuttaSolver

returns an [`ImplicitRungeKuttaSolver`](@ref) for the 3rd-order Radau IA method.
"""
function RadauIA3(; h = 0.0, ϵ = 1e-3, K = 10)
    h_T = typeof(h)
    tableau = ButcherTableau(h_T[
         0  1/4 -1/4 ;
        2/3 1/4  5/12;
         3  1/4  3/4 ;
    ])
    newton = NewtonParameters(ϵ=ϵ, K=K)
    return IRK(tableau, h, newton)
end

"""
    RadauIA5(; h = 0.0, ϵ = 1e-3, K = 10) :: ImplicitRungeKuttaSolver

returns an [`ImplicitRungeKuttaSolver`](@ref) for the 5th-order Radau IA method.
"""
function RadauIA5(; h = 0.0, ϵ = 1e-3, K = 10)
    h_T = typeof(h)
    tableau = ButcherTableau(h_T[
            0     1/9   -1/18-√6/18     -1/18+√6/18  ;
        3/5-√6/10 1/9  11/45+7*√6/360 11/45-43*√6/360;
        3/5+√6/10 1/9 11/45+43*√6/360  11/45-7*√6/360;
            5     1/9    4/9+√6/36       4/9-√6/36   ;
    ])
    newton = NewtonParameters(ϵ=ϵ, K=K)
    return IRK(tableau, h, newton)
end

"""
    RadauIIA3(; h = 0.0, ϵ = 1e-3, K = 10) :: ImplicitRungeKuttaSolver

returns an [`ImplicitRungeKuttaSolver`](@ref) for the 3rd-order Radau IIA method.
"""
function RadauIIA3(; h = 0.0, ϵ = 1e-3, K = 10)
    h_T = typeof(h)
    tableau = ButcherTableau(h_T[
        1/3  5/12 -1/12;
         1   3/4   1/4 ;
         3   3/4   1/4 ;
    ])
    newton = NewtonParameters(ϵ=ϵ, K=K)
    return IRK(tableau, h, newton)
end

"""
    RadauIIA5(; h = 0.0, ϵ = 1e-3, K = 10) :: ImplicitRungeKuttaSolver

returns an [`ImplicitRungeKuttaSolver`](@ref) for the 5th-order Radau IIA method.
"""
function RadauIIA5(; h = 0.0, ϵ = 1e-3, K = 10)
    h_T = typeof(h)
    tableau = ButcherTableau(h_T[
        2/5-√6/10   11/45-7*√6/360   37/225-169*√6/1800 -2/225+√6/75;
        2/5+√6/10 37/225+169*√6/1800   11/45+7*√6/360   -2/225-√6/75;
           1           4/9-√6/36          4/9+√6/36          1/9    ;
           5           4/9-√6/36          4/9+√6/36          1/9    ;
    ])
    newton = NewtonParameters(ϵ=ϵ, K=K)
    return IRK(tableau, h, newton)
end

"""
    ExponentialRK4(; h = 0.0) :: ExplicitExponentialRungeKuttaSolver

returns an [`ExplicitExponentialRungeKuttaSolver`](@ref) for the 4th-order EERK method.
"""
function ExponentialRK4(; h = 0.0)
    return EERK(h)
end
@doc (@doc ExponentialRK4) ERK4(args...; kwargs...) = ExponentialRK4(args...; kwargs...)


using NLsolve, LinearAlgebra
"""
`Base.length(tab::ODERKTableau)`

Defines the length of a Runge-Kutta method to be the number of stages.
"""
Base.length(tab::ODERKTableau) = tab.stages

"""
`stability_region(z,tab::ODERKTableau)`

Calculates the stability function from the tableau at `z`. Stable if <1.

```math
r(z) = \\frac{\\det(I-zA+zeb^T)}{\\det(I-zA)}
```
"""
stability_region(z,tab::ODERKTableau) = det(Matrix{Float64}(I,tab.stages,tab.stages)- z*tab.A + z*ones(tab.stages)*tab.α')/det(Matrix{Float64}(I,tab.stages,tab.stages)-z*tab.A)

"""
`stability_region(tab::ODERKTableau; initial_guess=-3.0)`

Calculates the length of the stability region in the real axis.
"""
function stability_region(tab::ODERKTableau; initial_guess=-3.0)
  residual! = function (resid, x)
    resid[1] = abs(stability_region(x[1], tab)) - 1
  end
  sol = nlsolve(residual!, [initial_guess])
  sol.zero[1]
end


# # clip-06-10.jl

# Load Julia packages (libraries) needed  for the snippets in chapter 0

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

# ### snippet 3.2

p_grid = range(0, step=0.001, stop=1)
prior = ones(length(p_grid))
likelihood = [pdf(Binomial(9, p), 6) for p in p_grid]
posterior = likelihood .* prior
posterior = posterior / sum(posterior)

# ### snippet 3.3
# Draw 10000 samples from this posterior distribution

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

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

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

# Describe the chain

MCMCChains.describe(chn)

# ### snippet 3.6

v = 0.0
for i in 1:length(p_grid)
  global v
  if p_grid[i] < 0.5
    v += posterior[i]
  end
end
v

# ### snippet 3.7

mapreduce(p -> p < 0.5 ? 1 : 0, +, samples) / N   |> display

# ### snippet 3.8

mapreduce(p -> (p > 0.5 && p < 0.75) ? 1 : 0, +, samples) / N   |> display

# ### snippet 3.9

quantile(samples, 0.8)

# ### snippet 3.10

quantile(samples, [0.1, 0.9])

# End of `03/clip-06-10.jl`


# indicator of an affine set

using LinearAlgebra
using SparseArrays
using SuiteSparse

export IndAffine

### ABSTRACT TYPE

abstract type IndAffine <: ProximableFunction end

is_affine(f::IndAffine) = true
is_generalized_quadratic(f::IndAffine) = true

fun_name(f::IndAffine) = "Indicator of an affine subspace"

### CONSTRUCTORS

"""
**Indicator of an affine subspace**

    IndAffine(A, b; iterative=false)

If `A` is a matrix (dense or sparse) and `b` is a vector, returns the indicator function of the set
```math
S = \\{x : Ax = b\\}.
```
If `A` is a vector and `b` is a scalar, returns the indicator function of the set
```math
S = \\{x : \\langle A, x \\rangle = b\\}.
```
By default, a direct method (QR factorization of matrix `A'`) is used to evaluate `prox!`.
If `iterative=true`, then `prox!` is evaluated approximately using an iterative method instead.
"""
function IndAffine(A::M, b::V; iterative=false) where {M, V}
    if iterative == false
        IndAffineDirect(A, b)
    else
        IndAffineIterative(A, b)
    end
end

### INCLUDE CONCRETE TYPES

using LinearAlgebra
using SparseArrays
using SuiteSparse

include("indAffineDirect.jl")
include("indAffineIterative.jl")


"""
    newton(x, xd, yd)
adalah fungsi yang digunakan untuk mencari nilai interpolasi pada titik/vektor `x`, jika
diketahui suatu himpunan pasangan terurut `(xd,yd)`.

# Example
```jl
julia> xd = [1,2,3,5];

julia> yd = [1.06 1.12 1.34 1.78];

julia> y,D = newton(4,xd,yd);

julia> y
1.6

julia> D
4×4 Array{Float64,2}:
 1.06  0.0   0.0    0.0
 1.12  0.06  0.0    0.0
 1.34  0.22  0.08   0.0
 1.78  0.22  0.0   -0.02
```
return solusi hampiran interpolasi `y` dan matriks beda-terbagi `D`.
"""
function newton(x, xd, yd)
  m=length(x);
  y=zeros(m)
  if m > 1
    for i=1:m
      # proses satu titik demi satu titik
      y[i],D =newton(x[i], xd, yd);
    end
    return y,D
  end
  #% periksa jumlah titik dan tentukan derajat polinom
  ntitik = length(xd);
  #% hitung tabel beda-terbagi (divided-difference)
  D = zeros(ntitik,ntitik)
  D[:,1]=yd;          #% kolom pertama
  for j=2:ntitik      #% kolom ke-2 dan seterusnya
    for k=j:ntitik
        D[k,j] = (D[k,j-1]-D[k-1,j-1])/(xd[k]-xd[k-j+1]);
    end
  end
  #% hitung interpolasi Newton
  y = D[1,1]; s = 1;
  for i=2:ntitik
    s = s * (x-xd[i-1]);
    y = y + D[i,i]*s;
  end
  return y, D
end


const TRANSITION = [1.0 1.0; 0.0 1.0]
const POS_NOISE = 1.5
const NOISE = [POS_NOISE 0.0; 0.0 sqrt(2)*POS_NOISE]
const PREDICT_NOISE = [0.1 0.0; 0.0 0.3]

struct State
    p::Float64
    v::Float64
    Σ::Array{Float64, 2}
end
State(p, v) = State(p, v, NOISE)

function predict(s::State, noise=PREDICT_NOISE)
    p, v = TRANSITION * [s.p ; s.v]
    Σ = TRANSITION * s.Σ * TRANSITION' + noise
    State(p, v, Σ)
end

function update(predicted::State, observed::State)
    K = predicted.Σ / (predicted.Σ + observed.Σ)
    # x = [predicted.p ; predicted.v]
    # z = [observed.p; observed.v]
    # p, v = x + K * (z - x)
    p = observed.p
    v = observed.v
    Σ = predicted.Σ - K * predicted.Σ
    State(p, v, Σ)
end

function smoothed_velocity_estimate(old::State, new::State, λ)
    diff = new.p - old.p
    scale = λ
    #scale = 1.0 - 0.5 * (1 - λ) * (1 + 1.0 / sqrt(2π * old.Σ[2, 2]^2))
    return scale * diff + (1 - scale) * old.v
end

function pdf(s::State, p::Float64)
    σ = s.Σ[1, 1]
    exp(-((s.p - p)^2) / (2σ^2)) / (sqrt(2π * σ^2))
end


## activate package environment
cd(@__DIR__)
using Pkg
Pkg.activate("Project.toml")

## parameters (calculation for perpendicular incident)
λ = 520 # nm
N = [1.0; 3.812 + 0.1903im; 4.1891+0.3633im] # air - window layer - absorber layer
D = 100 # thickness of window layer
# (refractive indices are from https://refractiveindex.info/, data by Aspnes 1986)

## function for fresnel coefficients (for perpendicular incident)
function fresnel_coeff(n,m,pol)
    if pol == "s"
        r = (n-m)/(n+m)
        t = (2*n)/(n+m)
    elseif pol == "p"
        r = (m-n)/(m+n)
        t = (2*n)/(m+n)
    end
    return r,t
end

## function for interface propagation matrix
function interface_prop(n,m,pol)
    r,t = fresnel_coeff(n,m,pol)
    I = 1/t * [1 r;r 1]
    return I
end

## function for propagation through film Matrix
function film_prop(d,n)
    beta = 2*pi*d*n/λ
    L = [exp(-1im*beta) 0; 0 exp(1im*beta)]
    return L
end

## calcuation of scattering matrix
# Emanuele Centurioni: Generalized matrix method for calculation of internal light energy flux
# in mixed coherent and incoherent multilayers
Ss = interface_prop(N[1],N[2],"s") * film_prop(D,N[2]) * interface_prop(N[2],N[3],"s")
Sp = interface_prop(N[1],N[2],"p") * film_prop(D,N[2]) * interface_prop(N[2],N[3],"p")

## backside transmittance
Ts = abs2(1/Ss[1,1])*real(N[3])/real(N[1])
Tp = abs2(1/Ss[1,1])*real(conj(N[3]))/real(N[1])
T  = round((Ts+Tp)/2,sigdigits=4)

## ouput result
println("")
println("transmission into absorber T = $T")


"""
Calculates Bézout coefficients (see `gcdx`)
"""
function bezout(a::R, b::R) where {R}
    rev = a < b
    x, y = rev ? (a,b) : (b,a)

    s0, s1 = oneunit(R), zero(R)
    t0, t1 = zero(R), oneunit(R)

    while y != zero(R)
        q = div(x, y)
        x, y = y, x - y * q
        s0, s1 = s1, s0 - q * s1
        t0, t1 = t1, t0 - q * t1
    end

    s, t = rev ? (s0, t0) : (t0, s0)
    g = x

    if g == a
        s = one(R)
        t = zero(R)
    elseif g == -a
        s = -one(R)
        t = zero(R)
    end

    return s, t, g
end


using Revise
using AdFem
using PyPlot

# forward computation
mmesh = Meh(joinpath(PDATA, "twoholes_large.stl"))
xy = gauss_nodes(mmesh)
κ = @. sin(xy[:,1]) * (1+xy[:,2]^2) + 1.0
f = 1e5 * @. xy[:,1] + xy[:,2]
K = compute_fem_laplace_matrix1(κ, mmesh)
F = compute_fem_source_term1(f, mmesh)
bdnode = bcnode(mmesh)
K, F = impose_Dirichlet_boundary_conditions(K, F, bdnode, zeros(length(bdnode)))
sol = K\F

# inverse modeling
nn_κ = squeeze(fc(xy, [20,20,20,1])) + 1
K = compute_fem_laplace_matrix1(nn_κ, mmesh)
F = compute_fem_source_term1(f, mmesh)
bdnode = bcnode(mmesh)
K, F = impose_Dirichlet_boundary_conditions(K, F, bdnode, zeros(length(bdnode)))
nn_sol = K\F
loss = sum((nn_sol - sol)^2)

sess = Session(); init(sess)
BFGS!(sess, loss)

nn_val = run(sess, nn_κ)
close("all")
figure(figsize=(18,5))
subplot(131)
visualize_scalar_on_gauss_points(κ, mmesh)
title("Reference")
subplot(132)
visualize_scalar_on_gauss_points(nn_val, mmesh)
title("DNN")
subplot(133)
visualize_scalar_on_gauss_points(abs.(κ-nn_val), mmesh)
title("Absolute Difference")
savefig("poisson_kappa.png")
close("all")
visualize_scalar_on_fem_points(sol, mmesh)
savefig("poisson_solution.png")


function binomial_coeffs(n::Integer)::Vector{Int}
    if n == 1
        return [1]
    end
    if n == 2
        return [1, 1]
    end
    c = [1, 1]
    for _ in 3:n
        c=[1, (view(c[1:end-1]) .+ view(c[2:end])), 1]
    end
    return c
end

function binomial_coeffs_2(n::Integer)
    if n == 1
        return [1]
    end
    if n == 2
        return [1, 1]
    end
    c = [1 , 1]
    m = 2
    for _ in 3:n
        for i in 1:(m - 1)
            c[i] = c[i] + c[i+1]
            m += 1
        end
        pushfirst!(c, 1)
    end
    return c
end


export nullspacex

"""
`nullspacex(A)` returns an exact basis for the matrix `A`
"""
function nullspacex(A::AbstractMatrix{T}) where {T}
    r, c = size(A)
    B = rrefx(A)

    leads = Int[]
    # in each row, find first 1
    for i = 1:r
        row = B[i, :]
        if all(row .== 0)
            continue
        end
        k = findfirst(row .!= 0)
        append!(leads, k)
    end

    frees = setdiff(collect(1:c), leads)

    result = Matrix{T}(undef, c, 0)
    for f in frees
        v = zeros(T, c)
        v[f] = T(1)
        for i = 1:length(leads)
            l = leads[i]  # (i,l) is a leading 1
            if l < f
                v[l] = -B[i, f]
            end
        end
        result = [result v]
    end
    return result
end

function nullspacex(A::AbstractMatrix{T}) where {T<:IntegerX}
    return nullspacex(big.(A) // 1)
end


const brown_dennis = let res_init=zeros(20), jac_init=zeros(20,4), x_init=[25., 5., -5., -1.]

    function res(x, r)
        n = length(r)
        for i = 1:n
            ti = 0.2e0 * Float64(i)
            r[i] = (x[1] + x[2] * ti - exp(ti)) ^ 2 +
                (x[3] + x[4] * sin(ti) - cos(ti))^2
        end
        return r
    end

    function jac(x, j)
        n = size(j)[1]
        for i = 1:n
            ti = 0.2e0 * Float64(i)
            j[i, 1] = 2.0e0 * (x[1] + x[2]*ti - exp(ti))
            j[i, 2] = ti * j[i, 1]
            t = sin(ti)
            j[i, 3] = 2.0e0 *(x[3] + x[4]*t - cos(ti))
            j[i, 4] = t * j[i, 3]
        end
        return j
    end

    f(;scale=1, verbose=false, print_steps=false) = testone("brown_dennis", res, jac,
                                                            res_init, jac_init, x_init;
                                                            scale=scale, verbose=verbose,
                                                            print_steps=print_steps)
end


function calculate_pi(NBIN::Int= 1000000000)::Float64
    step = 1/NBIN
    sum = 0
    for i=1:NBIN
        x = (i+0.5)*step;
        sum += 4 / (1 + x^2)
    end
    pi = sum*step
    return pi
end

if length(ARGS)>0
    @time @show calculate_pi(parse(Int, ARGS[1]))
else
    @time @show calculate_pi()
end


# //  This is a gear train design problem taken from the GAMS test library
# //
# //  A compound gear train is to be designed to achieve a specific
# //  gear ratio between the driver and driven shafts. The objective
# //  of the gear train design is to find the number of teeth of the
# //  four gears and to obtain a required gear ratio of 1/6.931.
# //
# //  The problem originated from:
# //  Deb, K, and Goyal, M, Optimizing Engineering Designs Using a
# //  Combined Genetic Search. In Back, T, Ed, Proceedings of the
# //  Seventh International Conference on Genetic Algorithms. 1997,
# //  pp. 521-528.

function gear(oct::Bool = false)
    m = JuMP.Model()
    @variable(m, 12 <= i[1:4] <= 60, Int)
    for j = 1:4
        JuMP.set_start_value(i[j], 24)
    end
    @constraint(m, e2, - i[3] + i[4] >= 0)
    @constraint(m, e3, i[1] - i[2] >= 0)
    if !oct
        @NLobjective(m, Min, (6.931 - i[1]*i[2]/(i[3]*i[4]))^2 + 1)
        return m
    else
        @variable(m, obj)
        @objective(m, Min, obj)
        gm = GlobalModel(model = m, name = "gear")
        add_nonlinear_constraint(gm, :((i) -> (6.931 - i[1]*i[2]/(i[3]*i[4]))^2 + 1), dependent_var = obj)
        return gm
    end
end


using Measurements
using Printf

printstyled("\nExperimento 1: Capacidade térmica\n", color =:blue);

# em gramas
Δm = 0.01;
mcopo = 51.82;
maq = 122.07;
maf = 125.02;

# em graus celsius
ΔT = 0.1;
taf = 12.4;
taq = 53.2;
tf = 31.4;

# calor especifico da água
ca = 1;
@printf("%.3f · %.3f ( %.3f - %.3f) / (%.3f - %.3f) - %.3f · %.3f \n", maq,ca,taq,tf,tf,taf,maf,ca);
C = maq * ca * ( (taq - tf) / (tf - taf) ) - maf * ca;

ΔC = ((Δm * taq + ΔT * maq) * (tf - taf) + (2 * ΔT) * (maq * taq) ) / (tf - taf)^2

println("\nCapacidade térmica do calorimetro");
@printf("Ccal = %.2f ± %.2f \n", C, ΔC);

# em grama
mcopo = 51.82 ±  Δm;
cAluminio = 0.218;
Ccopo =  mcopo * cAluminio;

println("\nCapacidade térmica do copo");
@printf("Ccopo = %.3f ± %0.3f \n", Ccopo.val, Ccopo.err);

println("\nRelação de equivalência");
@printf("| x1 - x2 | < 2 · (σ1 + σ2) \n");
@printf("| %.3f - %.3f| < 2 · (%.3f + %.3f) \n", C,Ccopo.val,ΔC,Ccopo.err);
println(abs(C - Ccopo.val) < 2 * (ΔC + Ccopo.err));

printstyled("\nExperimento 2: Calor específico de um metal  ", color =:blue);

# em gramas
mobjeto = 205.75 ± Δm;
ma = 216.07 ± Δm;

# em graus celsius
ta = 13.1 ± ΔT;
to = 97.1 ± ΔT;
tf = 19.2 ± ΔT;

co = ((ma * ca + C ) * (ta - tf)) / (mobjeto * (tf - to));

println("\nCalor específico de um metal");
@printf("Co = %.4f ± %.4f \n", co.val, co.err);

printstyled("\nExperimento 3: Calor latente de condensação da água\n",color=:blue);

ta = 8.8  ± ΔT;
tf = 71.7  ± ΔT;
tc = 100  ± ΔT;

ma = 167.479  ± Δm;
mc = 187.02 - ma  ± Δm;

@printf("Massa condensada = %.3f", mc.val);

L = (((ma + C) * (ta - tf)) / mc) + (tc - tf);

println("\nCalor latente");
@printf("Lc = %.3f ± %.3f",L.val, L.err);


using Plots;gr()

f(x) = x*x - 2
df(x) = 2x

function newton(x_0, max_iter)
    x = x_0
    error = zeros(max_iter)
    for i in 1:max_iter
        x = x - f(x) / df(x)
        error[i] = abs(x - sqrt(2))

    end

    (x, error)
end

x_0 = 1.0
max_iter = 20

x, error = newton(x_0, max_iter)
print(error)
t = 1:max_iter

plot(t, error, color=:cornflowerblue, label="newton",xlabel="t",ylabel="Error")
savefig("newton.png")


### A Pluto.jl notebook ###
# v0.12.10

using Markdown
using InteractiveUtils

# ╔═╡ e077ab16-22e5-11eb-15ed-5f0654bf7328
using Pkg, DrWatson

# ╔═╡ e75a9a06-22e5-11eb-2e36-8d4f62b830ed
begin
	@quickactivate "StatisticsWithJuliaPlutoNotebooks"
	using Random, Distributions, KernelDensity, Plots
	Random.seed!(0)
end

# ╔═╡ ca5fbdc8-22e5-11eb-0a60-cb0a127a3ca5
md"## Listing4.16"

# ╔═╡ fa68607e-22e5-11eb-0558-c9a4d9f77426
begin
	mu1, sigma1 = 10, 5
	mu2, sigma2 = 40, 12
	dist1, dist2 = Normal(mu1,sigma1), Normal(mu2,sigma2)
	p = 0.3
	mixRv() = (rand() <= p) ? rand(dist1) : rand(dist2)
	mixPDF(x) = p*pdf(dist1,x) + (1-p)*pdf(dist2,x)

	n = 2000
	data = [mixRv() for _ in 1:n]

	kdeDist = kde(data)
end

# ╔═╡ 45f535f0-250a-11eb-2a6b-03853e2d8eb1
begin
	xGrid = -20:0.1:80
	pdfKDE = pdf(kdeDist,xGrid)

	plot(xGrid, pdfKDE, c=:blue, label="KDE PDF")
	stephist!(data, bins=50, c=:black, normed=:true, label="Histogram")
	p1 = plot!(xGrid, mixPDF.(xGrid), c=:red, label="Underlying PDF",
		xlims=(-20,80), ylims=(0,0.035), legend=:topleft,
		xlabel="X", ylabel = "Density")
end

# ╔═╡ 45f5722a-250a-11eb-2840-a7d3dd59711b
begin
	hVals = [0.5,2,10]
	kdeS = [kde(data,bandwidth=h) for h in hVals]
	plot(xGrid, pdf(kdeS[1],xGrid), c = :green, label= "h=$(hVals[1])")
	plot!(xGrid, pdf(kdeS[2],xGrid), c = :blue, label= "h=$(hVals[2])")
	p2 = plot!(xGrid, pdf(kdeS[3],xGrid), c = :purple, label= "h=$(hVals[3])",
		xlims=(-20,80), ylims=(0,0.035), legend=:topleft,
		xlabel="X", ylabel = "Density")
	plot(p1,p2,size = (800,400))
end

# ╔═╡ 475ff888-22e6-11eb-2354-09f8f40a8e12
md"## End of listing4.16"

# ╔═╡ Cell order:
# ╟─ca5fbdc8-22e5-11eb-0a60-cb0a127a3ca5
# ╠═e077ab16-22e5-11eb-15ed-5f0654bf7328
# ╠═e75a9a06-22e5-11eb-2e36-8d4f62b830ed
# ╠═fa68607e-22e5-11eb-0558-c9a4d9f77426
# ╠═45f535f0-250a-11eb-2a6b-03853e2d8eb1
# ╠═45f5722a-250a-11eb-2840-a7d3dd59711b
# ╟─475ff888-22e6-11eb-2354-09f8f40a8e12


###########################################################################
# Gauss-Kronrod integration-weight computation for arbitrary floating-point
# types and precision, implemented based on the description in:
#
#    Dirk P. Laurie, "Calculation of Gauss-Kronrod quadrature rules,"
#    Mathematics of Computation, vol. 66, no. 219, pp. 1133-1145 (1997).
#
# for the Kronrod rule, and for the Gauss rule from the description in
#
#    Lloyd N. Trefethen and David Bau, Numerical Linear Algebra (SIAM, 1997).
#
# Arbitrary-precision eigenvalue (eignewt & eigpoly) and eigenvector
# (eigvec1) routines are written by SGJ, independent of the above sources.
#
# Since we only implement Gauss-Kronrod rules for the unit weight function,
# the diagonals of the Jacobi matrices are zero and certain things simplify
# compared to the general case of an arbitrary weight function.

# Given a symmetric tridiagonal matrix H with H[i,i] = 0 and
# H[i-1,i] = H[i,i-1] = b[i-1], compute p(z) = det(z I - H) and its
# derivative p'(z), returning (p,p').
function eigpoly(b,z,m=length(b)+1)
    d1 = z
    d1deriv = d2 = one(z)
    d2deriv = zero(z)
    for i = 2:m
        b2 = b[i-1]^2
        d = z * d1 - b2 * d2
        dderiv = d1 + z * d1deriv - b2 * d2deriv
        d2 = d1
        d1 = d
        d2deriv = d1deriv
        d1deriv = dderiv
    end
    return (d1, d1deriv)
end

# compute the n smallest eigenvalues of the symmetric tridiagonal matrix H
# (defined from b as in eigpoly) using a Newton iteration
# on det(H - lambda I).  Unlike eig, handles BigFloat.
function eignewt(b,m,n)
    # get initial guess from eig on Float64 matrix
    H = SymTridiagonal(zeros(m), Float64[ b[i] for i in 1:m-1 ])
    lambda0 = sort(eigvals(H))

    lambda = Array{eltype(b)}(undef, n)
    for i = 1:n
        lambda[i] = lambda0[i]
        for k = 1:1000
            (p,pderiv) = eigpoly(b,lambda[i],m)
            lambda[i] = (lamold = lambda[i]) - p / pderiv
            if abs(lambda[i] - lamold) < 10 * eps(lambda[i]) * abs(lambda[i])
                break
            end
        end
        # do one final Newton iteration for luck and profit:
        (p,pderiv) = eigpoly(b,lambda[i],m)
        lambda[i] = lambda[i] - p / pderiv
    end
    return lambda
end

# given an eigenvalue z and the matrix H(b) from above, return
# the corresponding eigenvector, normalized to 1.
function eigvec1(b,z::Number,m=length(b)+1)
    # "cheat" and use the fact that our eigenvector v must have a
    # nonzero first entries (since it is a quadrature weight), so we
    # can set v[1] = 1 to solve for the rest of the components:.
    v = Array{eltype(b)}(undef, m)
    v[1] = 1
    if m > 1
        s = v[1]
        v[2] = z * v[1] / b[1]
        s += v[2]^2
        for i = 3:m
            v[i] = - (b[i-2]*v[i-2] - z*v[i-1]) / b[i-1]
            s += v[i]^2
        end
        rmul!(v, 1 / sqrt(s))
    end
    return v
end

"""
    gauss([T,] N, a=-1, b=1)

Return a pair `(x, w)` of `N` quadrature points `x[i]` and weights `w[i]` to
integrate functions on the interval `(a, b)`,  i.e. `sum(w .* f.(x))`
approximates the integral.  Uses the method described in Trefethen &
Bau, Numerical Linear Algebra, to find the `N`-point Gaussian quadrature
in O(`N`²) operations.

`T` is an optional parameter specifying the floating-point type, defaulting
to `Float64`. Arbitrary precision (`BigFloat`) is also supported.
"""
function gauss(::Type{T}, N::Integer) where T<:AbstractFloat
    if N < 1
        throw(ArgumentError("Gauss rules require positive order"))
    end
    o = one(T)
    b = T[ n / sqrt(4n^2 - o) for n = 1:N-1 ]
    x = eignewt(b,N,N)
    w = T[ 2*eigvec1(b,x[i])[1]^2 for i = 1:N ]
    return (x, w)
end

gauss(N::Integer) = gauss(Float64, N) # integration on the standard interval (-1,1)

# re-scaled to an arbitrary interval:
gauss(N::Integer, a::Real, b::Real) = gauss(typeof(float(b-a)), N, a, b)
function gauss(::Type{T}, N::Integer, a::Real, b::Real) where T<:AbstractFloat
    x, w = gauss(T, N)
    s = T(b-a)/2
    x .= a .+ (x .+ 1) .* s
    w .*= abs(s)
    return (x, w)
end

"""
    kronrod([T,] n)

Compute `2n+1` Kronrod points `x` and weights `w` based on the description in
Laurie (1997), appendix A, simplified for `a=0`, for integrating on `[-1,1]`.
Since the rule is symmetric, this only returns the `n+1` points with `x <= 0`.
The function Also computes the embedded `n`-point Gauss quadrature weights `gw`
(again for `x <= 0`), corresponding to the points `x[2:2:end]`. Returns `(x,w,wg)`
in O(`n`²) operations.

`T` is an optional parameter specifying the floating-point type, defaulting
to `Float64`. Arbitrary precision (`BigFloat`) is also supported.

Given these points and weights, the estimated integral `I` and error `E` can
be computed for an integrand `f(x)` as follows:

    x, w, wg = kronrod(n)
    fx⁰ = f(x[end])                # f(0)
    x⁻ = x[1:end-1]                # the x < 0 Kronrod points
    fx = f.(x⁻) .+ f.((-).(x⁻))    # f(x < 0) + f(x > 0)
    I = sum(fx .* w[1:end-1]) + fx⁰ * w[end]
    if isodd(n)
        E = abs(sum(fx[2:2:end] .* wg[1:end-1]) + fx⁰*wg[end] - I)
    else
        E = abs(sum(fx[2:2:end] .* wg[1:end])- I)
    end
"""
function kronrod(::Type{T}, n::Integer) where T<:AbstractFloat
    if n < 1
        throw(ArgumentError("Kronrod rules require positive order"))
    end
    o = one(T)
    b = zeros(T, 2n+1)
    b[1] = 2*o
    for j = 1:div(3n+1,2)
        b[j+1] = j^2 / (4j^2 - o)
    end
    s = zeros(T, div(n,2) + 2)
    t = zeros(T, div(n,2) + 2)
    t[2] = b[n+2]
    for m = 0:n-2
        u = zero(T)
        for k = div(m+1,2):-1:0
            l = m - k + 1
            k1 = k + n + 2
            u += b[k1]*s[k+1] - b[l]*s[k+2]
            s[k+2] = u
        end
        s,t = t,s
    end
    for j = div(n,2):-1:0
        s[j+2] = s[j+1]
    end
    for m = n-1:2n-3
        u = zero(T)
        for k = m+1-n:div(m-1,2)
            l = m - k + 1
            j = n - l
            k1 = k + n + 2
            u -= b[k1]*s[j+2] - b[l]*s[j+3]
            s[j+2] = u
        end
        k = div(m+1,2)
        if 2k != m
            j = n - (m - k + 2)
            b[k+n+2] = s[j+2] / s[j+3]
        end
        s,t = t,s
    end
    for j = 1:2n
        b[j] = sqrt(b[j+1])
    end

    # get negative quadrature points x
    x = eignewt(b,2n+1,n+1) # x <= 0

    # get quadrature weights
    w = T[ 2*eigvec1(b,x[i],2n+1)[1]^2 for i in 1:n+1 ]

    # Get embedded Gauss rule from even-indexed points, using
    # the method described in Trefethen and Bau.
    for j = 1:n-1
        b[j] = j / sqrt(4j^2 - o)
    end
    gw = T[ 2*eigvec1(b,x[i],n)[1]^2 for i = 2:2:n+1 ]

    return (x, w, gw)
end

kronrod(N::Integer) = kronrod(Float64, N)

###########################################################################
# Type-stable cache of quadrature rule results, so that we don't
# repeat the kronrod calculation unnecessarily.

# precomputed n=7 rule in double precision (computed in 100-bit arithmetic),
# since this is the common case.
const xd7 = [-9.9145537112081263920685469752598e-01,
             -9.4910791234275852452618968404809e-01,
             -8.6486442335976907278971278864098e-01,
             -7.415311855993944398638647732811e-01,
             -5.8608723546769113029414483825842e-01,
             -4.0584515137739716690660641207707e-01,
             -2.0778495500789846760068940377309e-01,
             0.0]
const wd7 = [2.2935322010529224963732008059913e-02,
             6.3092092629978553290700663189093e-02,
             1.0479001032225018383987632254189e-01,
             1.4065325971552591874518959051021e-01,
             1.6900472663926790282658342659795e-01,
             1.9035057806478540991325640242055e-01,
             2.0443294007529889241416199923466e-01,
             2.0948214108472782801299917489173e-01]
const gwd7 = [1.2948496616886969327061143267787e-01,
              2.797053914892766679014677714229e-01,
              3.8183005050511894495036977548818e-01,
              4.1795918367346938775510204081658e-01]

# cache of T -> n -> (x,w,gw) Kronrod rules, to avoid recomputing them
# unnecessarily for repeated integration.   We initialize it with the
# default n=7 rule for double-precision calculations.  We use a cache
# of caches to allow us to evaluate the cache in a type-stable way with
# a generated function below.
const rulecache = Dict{Type,Dict}(
    Float64 => Dict{Int,NTuple{3,Vector{Float64}}}(7 => (xd7,wd7,gwd7)),
    Float32 => Dict{Int,NTuple{3,Vector{Float32}}}(7 => (xd7,wd7,gwd7)))

# for BigFloat rules, we need a separate cache keyed by (n,precision)
const bigrulecache = Dict{Tuple{Int,Int}, NTuple{3,Vector{BigFloat}}}()

cachedrule(::Type{T}, n::Integer) where {T<:Number} = cachedrule(typeof(float(real(one(T)))), n::Integer)

function cachedrule(::Type{BigFloat}, n::Integer)
    key = (n, precision(BigFloat))
    haskey(bigrulecache, key) ? bigrulecache[key] : (bigrulecache[key] = kronrod(BigFloat, n))
end

# use a generated function to make this type-stable
@generated function cachedrule(::Type{T}, n::Integer) where {T<:AbstractFloat}
    cache = haskey(rulecache, T) ? rulecache[T] : (rulecache[T] = Dict{Int,NTuple{3,Vector{T}}}())
    :(haskey($cache, n) ? $cache[n] : ($cache[n] = kronrod($T, n)))
end


# ------------------------------------------------------------
# Reconstruction of impulse responses, i.e., a single coefficient in the Haar
# basis from equidistant frequency samples

# Define scaling function and mother wavelet

function Haar_scaling(x::Real)
	if x >= 0 && x <= 1.0
		return 1.0
	else
		return 0.0
	end
end

function Haar_wavelet(x::Real)
	if x >= 0 && x < 0.5
		return 1.0
	elseif x >= 0.5 && x <= 1.0
		return -1.0
	else
		return 0.0
	end
end

# ------------------------------------------------------------
# Reconstruction of scaling function

# Input:
# M: The number of frequency samples
# epsislon: The distance between two consecutive samples
# N: The number of wavelet coefficients recovered

M = 32
epsilon = 1
#J = floor(Int, log2(M))
J = 2

# Sampling points
xi = [-M:M;]*epsilon

# Change of basis matrix
Tx, Ty = freq2Haar( xi, xi, J )
T = kron(Ty, Tx)

# Fourier observations
bx = FourHaarScaling(xi, J, 3)
by = FourHaarScaling(xi, J, 3)
b = bx * by.'

# Scaling function coefficients: 1 in the last (16th) entry, 0 elsewhere
y = pinv(T)*b[:] # Brute force
yy = pinv(Tx)*b*pinv(Ty).' # With Kronecker products


# p1.jl - convergence of fourth-order finite differences

# For various N, set up grid in [-pi,pi] and function u(x):
Nvec = 2.^(3:12);
clf(); axes([.1,.4,.8,.5]);
for N = Nvec
    h = 2*pi/N; x = -pi + (1:N)*h;
    u = @. exp(sin(x)^2);
    uprime = @. 2*sin(x)*cos(x)*u;

    # Construct sparse fourth-order differentiation matrix:
    e = ones(N);
    D = sparse(1:N,[2:N;1],2*e/3) - sparse(1:N,[3:N;1:2],e/12);
    D = (D-D')/h;

    # Plot max(abs(D*u-uprime)):
    error = norm(D*u-uprime,Inf);
    loglog(N,error,"k.",markersize=6);
end
grid(true); xlabel("N"); ylabel("error");
title("Convergence of fourth-order finite differences");
loglog(Nvec,1.0./Nvec.^4,"--");
text(105,5e-8,L"N^{-4}",fontsize=18);


function linear_index(IND)
  J = round(Int,floor(-.5 + .5 * sqrt(1 + 8 * (IND - 1))) + 2)
  I = round(Int,J .* (3 - J) / 2 + IND - 1)
  return I, J
end


### A Pluto.jl notebook ###
# v0.12.4

using Markdown
using InteractiveUtils

# ╔═╡ e1a3f480-12e7-11eb-01db-71003ec47f08
md"""
## What *is* an array, actually?

We have seen several examples of arrays.
When you think about an array, you probably think of a chain of boxes with the same shape, stored one after the other, consecutively in memory. And indeed this is the original meaning of the word "array".

However, as a user, we don't actually care about how the data is actually stored! All we really care about is the functionality of an array. Namely, we want the following properties:

- Elements have an integer index (from 1 to `n`);
- An element at position `i` can be accessed using the syntax `A[i]`;
- An element at position `i` can be stored using `A[i] = i` (if the array is mutable)
- We should be able to see the size of an array.
"""

# ╔═╡ 51a660b4-12f3-11eb-2b8a-f5544e650b1c
md"""
Julia provides an **abstraction** of an array called `AbstractArray`. We can define new types of array that "behave like" arrays but are stored differently.
"""

# ╔═╡ 853ac7d0-12f3-11eb-1b1c-993e336cf23a
md"""
## How does indexing work?
"""

# ╔═╡ 94bb1e6c-12f3-11eb-2e18-bb232d2c9cfe
md"""
First let's think about indexing, which is really the key operation on an array.
Let's define a vector:
"""

# ╔═╡ 314ba27a-12ec-11eb-12ce-67650c0020b7
arr = [34,45,67]

# ╔═╡ ab3afe6e-12f3-11eb-196a-79d26b66f681
md"""
We know that we can index into it using square brackets:
"""

# ╔═╡ b32acfda-12f3-11eb-323d-ef91b9ff9f0d
arr[2]

# ╔═╡ b53bc5ba-12f3-11eb-2b09-d38cc266b069
md"""
This is just syntax? What is actually going on? We can see this by looking at the **lowered** form:
"""

# ╔═╡ 3a202684-12ec-11eb-26fc-df7b7484e671
Meta.@lower arr[2]

# ╔═╡ c225d324-12f3-11eb-23d7-45548e7b32b6
md"""
We see that `arr[2]` is just "syntactic sugar" for calling the `getindex` function as `getindex(arr, 2)`; in other words, it's just a nice short and suggestive way to write the same thing.
"""

# ╔═╡ d92c5aa2-12f3-11eb-286e-8769ecdf03cb
md"""
So to implement our own objects that behave like arrays, we will define a new type and extend `getindex` on that type.
"""

# ╔═╡ f10cf87a-12f3-11eb-1104-0186b9e43757
md"""
## Example: A zero-based vector
"""

# ╔═╡ f7cadd08-12f3-11eb-1882-8dd4ce8ee2c2
md"""
There are interminable discussions online about whether zero-based indexing (i.e. array indices starting at 0) or one-based indexing (starting at 1) is superior. The answer is that *neither* is -- each is more suitable in different contexts.

Fortunately Julia is flexible enough to allow you to use *any* type of indexing that suits you -- see e.g. the `OffsetArrays.jl` package.

Let's see how to implement the simplest case, namely a zero-based vector.

So we define a new type and extend `getindex` on it. In order to use the full Julia machinery we need to extend a couple of other functions too:
"""

# ╔═╡ 1ac0095e-12eb-11eb-3df2-e98d0ff66dd5
struct ZeroFirstVector{T} <: AbstractVector{T}
	array::Vector{T}
end

# ╔═╡ a66afe14-12eb-11eb-3afc-fdac2325ff84
Base.size(vec::ZeroFirstVector) = size(vec.array)

# ╔═╡ e86c53fa-12eb-11eb-0ee4-41455a218d84
Base.axes(vec::ZeroFirstVector) = (0:length(vec.array)-1,)

# ╔═╡ 69b10842-12eb-11eb-381a-0bda698a59ed
Base.getindex(vec::ZeroFirstVector, i::Int) = vec.array[i+1]

# ╔═╡ 9c57e4aa-12eb-11eb-16fb-c7a18c238192
zarr = ZeroFirstVector([1, 3, 4])

# ╔═╡ 3cf51826-12f4-11eb-2405-db9616fa3152
md"""
Now we are able to use zero-based indexing!:
"""

# ╔═╡ fed5e2e4-12eb-11eb-169f-8b050f9eea4a
zarr[0]

# ╔═╡ 464c22f2-12f4-11eb-0b3c-872621c385f6
md"""
## Modifying elements: `setindex!`
"""

# ╔═╡ 4dec992e-12f4-11eb-1f20-b96632769bed
md"""
In a similar way to `getindex`, modifying elements of an array uses the mutating function `setindex!`:
"""

# ╔═╡ 5b8f3508-12ec-11eb-0a31-61ddf796ac74
Meta.@lower arr[2] = 3

# ╔═╡ 0c8ebad2-12ec-11eb-03f1-c5b9c79bb4da
Base.setindex!(vec::ZeroFirstVector, val, i::Int) = (vec.array[i+1] = val)

# ╔═╡ 8438424c-12ec-11eb-1de2-0bb3924ec78c
zarr[0] = 55

# ╔═╡ 9cee3206-12ec-11eb-09c7-1f0ebad11b8e
zarr

# ╔═╡ b0ab2844-12ec-11eb-1908-0318510b3292
md"""
### Julia gives you added bonuses here:
"""

# ╔═╡ bc8154b8-12ec-11eb-1fcc-4765fcd0796d
zarr[0:1]

# ╔═╡ 8c0c8188-12f4-11eb-1576-23958903a69a
@which zarr[0:1]

# ╔═╡ 7425ef6e-12f4-11eb-21c3-19c221c1ebdb
md"""
We never defined how to index using a *range*, only an integer. Julia has a generic fallback mechanism that understands how to do this once you have implemented `getindex` for a single element.

Similarly:
"""

# ╔═╡ c67fbbbc-12ec-11eb-32be-2ff720964fe6
zarr[ [2, 1, 0] ]

# ╔═╡ d13a65b6-12ec-11eb-2996-37987cfae782
zarr[0:1] .= [7,8];

# ╔═╡ dbb95f42-12ec-11eb-01d5-df2e21affd86
zarr

# ╔═╡ e6428aa6-12ec-11eb-06d0-95404a8a4039
zarr[ [2, 1, 0] ] .= [1, 2, 3];

# ╔═╡ e21658ec-12ef-11eb-06fd-b1d282edf7b6
zarr

# ╔═╡ b405b9ac-12f4-11eb-12fa-f7575ff1fa73
md"""
Various other functions also automatically work, e.g.:
"""

# ╔═╡ e57329a2-12ef-11eb-165d-51f080c73652
sort!(zarr)

# ╔═╡ e8b34188-12ef-11eb-2d7f-fde100b582d1
md"""
### Some more examples

"""

# ╔═╡ bd6bfd10-12f4-11eb-17fe-6f9ce83f0ce8
md"""
We have already seen some other examples of array types in the course, e.g. ranges:
"""

# ╔═╡ f0d952da-12ef-11eb-2452-471b184bc529
r = 53:83

# ╔═╡ 0fc7e3e6-12f0-11eb-1b49-e37196154ec6
size(r)

# ╔═╡ 03b23fe0-12f0-11eb-0970-33b3b973e902
r[20]

# ╔═╡ 00088cda-12f0-11eb-36b7-9b900fa6d0bf
typeof(r)

# ╔═╡ f6024924-12ef-11eb-1d6c-3ffff2546914
supertypes(typeof(r))

# ╔═╡ cdf3e532-12f4-11eb-1ce0-873bcf0dea58
md"""
Let's also look at what happens if we reshape an array:
"""

# ╔═╡ f9bf5200-12ef-11eb-30ee-9b3cbbccc886
A = reshape(1:12, 3, 4)

# ╔═╡ 07c1cdb6-12f1-11eb-0b31-15b5c88caac6
typeof(A)

# ╔═╡ 09d69238-12f1-11eb-1d9f-f72e8c6c14d9
supertypes(typeof(A))

# ╔═╡ ff598e1c-12f0-11eb-24f6-bdaa74db2ad7
size(A)

# ╔═╡ 2f23dd4e-12f0-11eb-01fc-53f66d2a55e4
Aᵀ = transpose(A)

# ╔═╡ ea8e0794-12f0-11eb-2194-bdc46224dc69
size(Aᵀ)

# ╔═╡ 2a76a87a-12f1-11eb-3e58-e5f919ba81e8
@which Aᵀ[2,3]

# ╔═╡ 892e7c40-12f0-11eb-2871-0d308063f025
md"The `getindex` method for `Transpose` is defined [here](https://github.com/JuliaLang/julia/blob/master/stdlib/LinearAlgebra/src/adjtrans.jl#L190)"

# ╔═╡ da41895c-12f4-11eb-2ee4-f1ffc4344f61
md"""
## Conclusions
"""

# ╔═╡ dfac92ea-12f4-11eb-1253-b74be632de64
md"""
Summarising, we see that we -- and Julia -- can think of an array as "anything that behaves like an array", i.e. which you can index into.

What does it mean for something to behave like an array? In the end it means that we *could* "materialise" it to produce an actual array that did live as data stored linearly in memory, and our program would behave in exactly the same way.

But Julia provides a mechanism by which we can store less data, or store it in a more convenient way, and have indexing be almost as efficient.
"""

# ╔═╡ Cell order:
# ╟─e1a3f480-12e7-11eb-01db-71003ec47f08
# ╟─51a660b4-12f3-11eb-2b8a-f5544e650b1c
# ╟─853ac7d0-12f3-11eb-1b1c-993e336cf23a
# ╟─94bb1e6c-12f3-11eb-2e18-bb232d2c9cfe
# ╠═314ba27a-12ec-11eb-12ce-67650c0020b7
# ╟─ab3afe6e-12f3-11eb-196a-79d26b66f681
# ╠═b32acfda-12f3-11eb-323d-ef91b9ff9f0d
# ╟─b53bc5ba-12f3-11eb-2b09-d38cc266b069
# ╠═3a202684-12ec-11eb-26fc-df7b7484e671
# ╟─c225d324-12f3-11eb-23d7-45548e7b32b6
# ╟─d92c5aa2-12f3-11eb-286e-8769ecdf03cb
# ╟─f10cf87a-12f3-11eb-1104-0186b9e43757
# ╟─f7cadd08-12f3-11eb-1882-8dd4ce8ee2c2
# ╠═1ac0095e-12eb-11eb-3df2-e98d0ff66dd5
# ╠═a66afe14-12eb-11eb-3afc-fdac2325ff84
# ╠═e86c53fa-12eb-11eb-0ee4-41455a218d84
# ╠═69b10842-12eb-11eb-381a-0bda698a59ed
# ╠═9c57e4aa-12eb-11eb-16fb-c7a18c238192
# ╟─3cf51826-12f4-11eb-2405-db9616fa3152
# ╠═fed5e2e4-12eb-11eb-169f-8b050f9eea4a
# ╟─464c22f2-12f4-11eb-0b3c-872621c385f6
# ╟─4dec992e-12f4-11eb-1f20-b96632769bed
# ╠═5b8f3508-12ec-11eb-0a31-61ddf796ac74
# ╠═0c8ebad2-12ec-11eb-03f1-c5b9c79bb4da
# ╠═8438424c-12ec-11eb-1de2-0bb3924ec78c
# ╠═9cee3206-12ec-11eb-09c7-1f0ebad11b8e
# ╟─b0ab2844-12ec-11eb-1908-0318510b3292
# ╠═bc8154b8-12ec-11eb-1fcc-4765fcd0796d
# ╠═8c0c8188-12f4-11eb-1576-23958903a69a
# ╟─7425ef6e-12f4-11eb-21c3-19c221c1ebdb
# ╠═c67fbbbc-12ec-11eb-32be-2ff720964fe6
# ╠═d13a65b6-12ec-11eb-2996-37987cfae782
# ╠═dbb95f42-12ec-11eb-01d5-df2e21affd86
# ╠═e6428aa6-12ec-11eb-06d0-95404a8a4039
# ╠═e21658ec-12ef-11eb-06fd-b1d282edf7b6
# ╟─b405b9ac-12f4-11eb-12fa-f7575ff1fa73
# ╠═e57329a2-12ef-11eb-165d-51f080c73652
# ╟─e8b34188-12ef-11eb-2d7f-fde100b582d1
# ╟─bd6bfd10-12f4-11eb-17fe-6f9ce83f0ce8
# ╠═f0d952da-12ef-11eb-2452-471b184bc529
# ╠═0fc7e3e6-12f0-11eb-1b49-e37196154ec6
# ╠═03b23fe0-12f0-11eb-0970-33b3b973e902
# ╠═00088cda-12f0-11eb-36b7-9b900fa6d0bf
# ╠═f6024924-12ef-11eb-1d6c-3ffff2546914
# ╟─cdf3e532-12f4-11eb-1ce0-873bcf0dea58
# ╠═f9bf5200-12ef-11eb-30ee-9b3cbbccc886
# ╠═07c1cdb6-12f1-11eb-0b31-15b5c88caac6
# ╠═09d69238-12f1-11eb-1d9f-f72e8c6c14d9
# ╠═ff598e1c-12f0-11eb-24f6-bdaa74db2ad7
# ╠═2f23dd4e-12f0-11eb-01fc-53f66d2a55e4
# ╠═ea8e0794-12f0-11eb-2194-bdc46224dc69
# ╠═2a76a87a-12f1-11eb-3e58-e5f919ba81e8
# ╟─892e7c40-12f0-11eb-2871-0d308063f025
# ╟─da41895c-12f4-11eb-2ee4-f1ffc4344f61
# ╟─dfac92ea-12f4-11eb-1253-b74be632de64


# estimate with MCMC (Turing) for conjugate prior
# compare with jlbayes_conjugate_regression.jl

using Turing
using Distributions
using Random
using LinearAlgebra
using StatsPlots
using Optim

# Generate moc data
n = 50
dim = 1
Random.seed!(99)
u = rand(Normal(0, 0.7), n) # true value σ² = 0.49
x = rand(Uniform(-sqrt(3.0), sqrt(3.0)), n, dim) # x generated from Uniform[-√3, √3]
X = [ones(n) x] # dependent variable matrix with ones
β = Vector(1:1:dim+1) # true coefficients
y = X*β + u # true data generating process

# set prior (conjugate prior for camparison with analytic result)
# p.78 eq (3.32)
k = size(X)[2]
β0 = zeros(k)
τ0 = 0.2
A0 = τ0 * I # Identity matrix
ν0 = 5.0
λ0 = 7.0
# H0 = (λ0 / ν0) * inv(A0) # scale matrix for marginal prior of multivariate coefficient (mv Tdist)
# h0 = diag(sqrt(Matrix(H0, k, k))) # scale params for marginal prior of single coefficient (T dist) H0の対角成分
sd0 = diag(sqrt(Matrix(A0, k, k))) # the parameter for conditional prior for β|σ² ~ N_k(β0, σ²*inv(A0)), (inv(A0) part)

@model function linear_regression(X, y)
    # conjugate priors
    # https://turing.ml/dev/tutorials/05-linear-regression/

    # Set variance priors
    σ² ~ InverseGamma(ν0/2, λ0/2)

    # Set the priors on our coefficients.
    nfeatures = size(X, 2)
    β ~ MvNormal(β0, σ² * inv(A0))

    # Write likelihood.
    y ~ MvNormal(X * β, σ² * I)
end

# Draw random number from posterior.
model = linear_regression(X, y)
n_draws = 5_000
n_chains = 4
n_tune = 1_000
# sample(model, sampler, parallel_type, n, n_chains)
# chain = sample(model, NUTS(), MCMCThreads(), n_draws, n_chains, discard_initial=n_tune)
# How to implement burn-in / warmup? --> discard initial value automatically. ; discard_adapt=false to turn off
chn = sample(model, NUTS(), MCMCThreads(), n_draws, n_chains)
chn = sample(model, NUTS(), MCMCThreads(), n_draws, n_chains; discard_adapt=false)

# Chain info.
typeof(chn)
size(chn)
describe(chn)
summarize(chn)
quantile(chn)
plot(chn)

# Indexing a Chains object.
β1_chn = chn["β[1]"]
β2_chn = chn[Symbol("β[2]")]
σ²_chn = chn[:σ²]
all_β_chn = group(chn, :β) # get subset of all parameters include "β"

typeof(β1_chn)
β1_chn[iter = 1000:5000, chain = 1]
mean(β1_chn)
std(β1_chn)

# Monte Carlo Standard Error
mcse(θ_chn) = sqrt((1 / (length(θ_chn) * (length(θ_chn) - 1))) * sum((θ_chn .- mean(θ_chn)).^2))

[β1_chn, β2_chn, σ²_chn]

mcse.([β1_chn, β2_chn, σ²_chn])

# without burn-in
mean(β1_chn[iter = 1:5000])

histogram(β1_chn[:, 1])
histogram!(β1_chn[:, 2])
histogram!(β1_chn[:, 3])
histogram!(β1_chn[:, 4])

# mode (MLE and MAP) estimates (Optimization with Optim.jl) as an option (continuous only)
# Note that loading Optim explicitly is required for mode estimation to function,
# as Turing does not load the opimization suite unless Optim is loaded as well.

# Generate a MLE estimate (default optimizer is LBFGS optimizer).
mle_estimate = optimize(model, MLE())
# Use Newton
# mle_estimate = optimize(model, MLE(), Newton())

# Generate a MAP estimate (default optimizer is LBFGS optimizer).
map_estimate = optimize(model, MAP())

# Analyze mode estimate
# Import StatsBase to use it's statistical methods.
# using StatsBase

# Print out the coefficient table.
# coeftable(mle_estimate)

# Analytical Solution
inv(X' * X) * X' * y


using LinearAlgebra
import LinearAlgebra.Eigen
using SparseArrays
#this lib will be used to estimate the executon time
using BenchmarkTools
using Test
using Distributions

struct SVDCM
	U::Matrix{Float64}
	S::SparseMatrixCSC{Float64, Int64}
	V::Matrix{Float64}

	function SVDCM(A::Matrix{Float64})
		U, S, V = svdCm(A)
		return new( U, S, V)
	end
	function svdCm(A::Matrix{Float64})

		r,c = size(A)
		shorter=min(r,c)
		longer=max(r,c)

		Asq = zeros(shorter,shorter)
		mul!(Asq, A', A)
		a, V = eigen!(Asq)

		S = sparse(zeros(longer,shorter))
		Sin = sparse(zeros(shorter,longer))

		S[diagind(S)] .= (sqrt.(a))
		Sin[diagind(Sin)] .= 1 ./ S[diagind(S)]

		U1 = zeros(r,c)
		U = zeros(longer,longer)

		mul!(U1, A, V)
		mul!(U,U1,Sin)
		return U, S, V
	end
end

R = rand(Uniform(1., 100.), 10000,1000)
MM = [1 0 0 0 2; 0 0 3 0 0; 0 0 0 0 0; 0 2 0 0 0; 1 0 0 0 2; 0 0 3 0 0; 0 0 0 0 0; 0 2 0 0 0; 1 0 0 0 2; 0 0 3 0 0; 0 0 0 0 0; 0 2 0 0 0; 1 0 0 0 2; 0 0 3 0 0; 0 0 0 0 0; 0 2 0 0 0;1 0 0 0 2; 0 0 3 0 0; 0 0 0 0 0; 0 2 0 0 0;1 0 0 0 2; 0 0 3 0 0; 0 0 0 0 0; 0 2 0 0 0 ]

C = R
println("size of matrix: ", size(C) )

println("Julia SVD time:")
SVD = @time svd(C)

println("SVD time:")
mySvd = @time SVDCM(C)

F = mySvd.U * mySvd.S * mySvd.V'
#display(F)
@test F ≈ C atol=1e-5


# Legendre-Gauss quadrature weights and nodes
# This code was originally written for Matlab
#  by Greg von Winckel - 02/25/2004
# posted on Mathworks website and licensed under BSD License
# http://fr.mathworks.com/matlabcentral/fileexchange/4540-legendre-gauss-quadrature-weights-and-nodes
#
#

function lgwt(N,a,b)

	N=N-1;
	N1=N+1; N2=N+2;
	xu= transpose(range(-1,stop = 1, length = N1));

	y = transpose(cos.((2*transpose(0:N).+1)*pi/(2*N+2))+(0.27/N1)*sin.(pi*xu*N/N2));

	L = zeros(N1,N2);

	Lp=zeros(N1,N2);
	y0=2;

	while maximum(abs.(y.-y0))>eps()
		L[:,1] .= 1;
		Lp[:,1] .= 0;
		L[:,2] = y;
		for k=2:N1
			L[:,k+1]=( (2*k-1)*y.*L[:,k]-(k-1)*L[:,k-1] )/k;
		end
		Lp=(N2)*( L[:,N1] - y .* L[:,N2] )./(1 .- y.^2);
		y0=y;
		y=y0-L[:,N2]./Lp;
	end

	x = (a*(1 .- y)+b*(1 .+ y))/2;

	w=(b-a)./((1 .- y.^2).*Lp.^2)*(N2/N1)^2;

	return x,w
end


function house!(h, j, X, k)
    T = eltype(h)
    n = size(X,1)
    σ = zero(T)
    @inbounds @simd for i in j+1:n
        σ += X[i,k]^2
    end
    @inbounds begin
        h[j] = one(T)
        @simd for i in j+1:n
            h[i] = X[i,k]
        end
    end
    if σ == zero(T)
        β = zero(T)
    else
        @inbounds begin
            μ = sqrt(X[j,k]^2 + σ)
            if X[j,k] <= zero(T)
                h[j] = X[j,k] - μ
            else
                h[j] = -σ/(X[j,k] + μ)
            end
            β = 2*(h[j]^2) / (σ + h[j]^2)
            d = h[j]
            @simd for i in j:n
                h[i] /= d
            end
        end
    end
    β
end

"""
accumulate!(Q, A, β)

Q: Matrix to be over-written with Q, gets returned
A: Has the Householder vectors on the lower triangle
β: Has the Householder coefficients
"""
function accumulate!(Q, A, β)
    T = eltype(Q)
    m, n = size(Q)
    @inbounds begin
        Q .= zero(T)
        @simd for i in 1:n
            Q[i,i] = one(T)
        end
    end
    @inbounds for j in n:-1:1
        @simd for t in j:n
            dp = Q[j,t]
            for i in j+1:m
                dp += A[i,j]*Q[i,t]
            end
            Q[j,t] -= β[j]*dp
            for i in j+1:m
                Q[i,t] -= β[j]*dp*A[i,j]
            end
        end
    end
    Q
end

"""
getR!(R, A)

Copies the right triangular part of A into the right triangular part of R.
"""
function getR!(R, A)
    T = eltype(R)
    m, n = size(A)
    @inbounds @simd for j in 1:n
        for i in 1:j
            R[i,j] = A[i,j]
        end
    end
    R
end

"""
qrfact!(A, β, h=zeros(eltype(A), size(A,1)))

A: Matrix to be QR factorized, returned by the function
β: Empty buffer to store the Householder coefficients, returned by the function
h: Empty buffer for in-place operations
"""
function qrfact!(A, β, h=zeros(eltype(A), size(A,1)))
    m, n = size(A)
    @inbounds for j in 1:n
        β[j] = house!(h, j, A, j)
        @simd for k in j:n
            dp = A[j,k]
            for i in j+1:m
                dp += h[i]*A[i,k]
            end
            for l in j:m
                A[l,k] -= (β[j] * dp) * h[l]
            end
        end
        if j < m
            @simd for k in 1:(m-j)
                A[k+j,j] = h[k+j]
            end
        end
    end
    A, β
end


"Evaluate the Laplace 2D single layer potential kernel"
function laplace_slp(x, y)
    T = eltype(x)
    if norm(x-y) < eps(T)
         return zero(Complex{T})
    else
        return complex(1/2/pi*log(norm(x-y)))
    end
end

export Laplace_SLP_2D
"The single layer potential kernel of the Laplace equation."
struct Laplace_SLP_2D{T} <: BoundaryKernel
end

Laplace_SLP_2D() = Laplace_SLP_2D{Float64}()

(kernel::Laplace_SLP_2D)(t, tau, param, x, y) = laplace_slp(x, y)
(kernel::Laplace_SLP_2D)(x, tau, param, y) = laplace_slp(x, y)

is_symmetric(::Laplace_SLP_2D) = true

singularity(::Laplace_SLP_2D) = LogSingularDiagonal()

"Evaluate the Laplace 2D double layer potential kernel"
laplace_dlp(x, y, normal_y, z = norm(x-y)) =
    (normal_y' * (x-y)) / z^2/2/pi

"Evaluate the Laplace 2D double layer potential kernel"

function laplace_dlp_kernel(x, y, tau, param)
    z = norm(x-y)
    if abs(z) > eps(eltype(x))
        return Complex{Float64}(laplace_dlp(x, y, normal(param, tau), z))
    else
        return Complex{Float64}(0)
    end
end

export Laplace_DLP_2D

"The double layer potential kernel of the Laplace equation in 2D."
struct Laplace_DLP_2D{T} <: BoundaryKernel
end

Laplace_DLP_2D() = Laplace_DLP_2D{Float64}()

BasisFunctions.name(kernel::Laplace_DLP_2D) = "2D Laplace double layer potential kernel"

(kernel::Laplace_DLP_2D)(t, tau, param, x, y) =
    laplace_dlp_kernel(x, y, tau, param)
(kernel::Laplace_DLP_2D)(x, tau, param, y) =
    laplace_dlp_kernel(x, y, tau, param)

is_symmetric(::Laplace_DLP_2D) = false

# The kernel is continuous but its derivative is singular
singularity(::Laplace_DLP_2D) = LogSingularDiagonal()

"Adjoint Double layer potential for Laplace equation"

export Laplace_adjDLP_2D
"The adjoint double layer potential kernel of the Laplace equation in 2D."
struct Laplace_adjDLP_2D{T} <: BoundaryKernel
end

laplace_adjdlp(x, y, normal_x, z = norm(x-y)) =
    (normal_x' * (y-x)) / z^2/2/pi

"Evaluate the adjoint of the Laplace 2D double layer potential kernel"

function laplace_adjdlp_kernel(x, y, t, param)
    z = norm(x-y)
    if abs(z) > 10*eps(eltype(x))
        return Complex{Float64}(laplace_adjdlp(x, y, normal(param, t), z))
    else
        return Complex{Float64}(0)
    end
end

Laplace_adjDLP_2D() = Laplace_adjDLP_2D{Float64}()

BasisFunctions.name(kernel::Laplace_adjDLP_2D) = "Adjoint of 2D Laplace double layer potential kernel"

(kernel::Laplace_adjDLP_2D)(t, tau, param, x, y) =
    laplace_adjdlp_kernel(x, y, t, param)

is_symmetric(::Laplace_adjDLP_2D) = false

# The kernel is continuous but its derivative is singular
singularity(::Laplace_adjDLP_2D) = LogSingularDiagonal()
## Some boundary conditions

export laplace_bcond_harmonic, laplace_param_bcond_harmonic, laplace_parboundary_condition_potential_flow

harmonic_poly(z) = z^2-z+2

boundary_condition_harmonic(x) = real(harmonic_poly(x[1]+im*x[2]))

"Return a field function that evaluates to a plane wave."
laplace_bcond_harmonic() = (x,y) -> boundary_condition_harmonic(SVector(x,y))

"Return a parametric boundary function that evaluates to a plane wave."
laplace_param_bcond_harmonic(param) =
    t -> boundary_condition_harmonic(applymap(param, t))

"Return a parametric boundary function corresponding to the unperturbed flow at r=∞"
laplace_parboundary_condition_potential_flow(param, velocity) =
    t -> velocity'*normal(param, t)


using Images
using ImageView
using MAT
using LinearAlgebra
using Statistics
using Plots

vars = matread("../../DATA/allFaces.mat")

n = Int(vars["n"])
m = Int(vars["m"])
faces = vars["faces"]
nfaces = Int.(vars["nfaces"])

# We use the first 36 people for training data
trainingFaces = faces[:,1:sum(nfaces[1:36])];
avgFace = mean(trainingFaces, dims = 2) # size n*m by 1

# Compute eigenfaces on mean-subtracted training data
X = trainingFaces - avgFace * ones(1, size(trainingFaces)[2])
U, S, V = svd(X)

imshow(reshape(avgFace, n, m)) # Show avg face
imshow(reshape(U[:,1], n, m))  # Show first eigenface

# Now show eigenface reconstruction of image that was omitted from test set

testFace = faces[:,1+sum(nfaces[1:36])] # First face of person 37
imshow(reshape(testFace, n, m))

testFaceMS = testFace - avgFace
for r = [25, 50, 100, 200, 400, 800, 1600]
    reconFace = avgFace + (U[:,1:r]*(U[:,1:r]'*testFaceMS))
    imshow(reshape(reconFace, n, m))
end

# Project person 2 and 7 onto PC5 and PC6

P1num = 2; # Person number 2
P2num = 7; # Person number 7

P1 = faces[:,1+sum(nfaces[1:P1num-1]):sum(nfaces[1:P1num])]
P2 = faces[:,1+sum(nfaces[1:P2num-1]):sum(nfaces[1:P2num])]

P1 = P1 - avgFace * ones(1, size(P1)[2])
P2 = P2 - avgFace * ones(1, size(P2)[2])

PCAmodes = [5, 6] # Project onto PCA modes 5 and 6
PCACoordsP1 = U[:,PCAmodes]'*P1
PCACoordsP2 = U[:,PCAmodes]'*P2

p1 = scatter(PCACoordsP1[1,:], PCACoordsP1[2,:], mc = :blue, legend = false)
scatter!(PCACoordsP2[1,:], PCACoordsP2[2,:], mc = :red)


"""
    compute_linear_return_array(priceArray::Array{Float64,1})

Stuff will go here. Awesome stuff, the most beautiful stuff ever.
"""
function compute_linear_return_array(priceArray::Array{Float64,1})::PSResult

    # initialize -
    linear_return_array = Array{Float64,1}()

    # get the size -
    number_of_time_steps = length(priceArray)
    for time_index = 2:number_of_time_steps
        value = (priceArray[time_index] / priceArray[time_index - 1]) - 1
        push!(linear_return_array, value)
    end

    # return -
    return PSResult(linear_return_array)
end

"""
    compute_linear_return_array(dataTable::DataFrame; key::Symbol = Symbol("adjusted_close"))

Stuff will go here. Awesome stuff, the most beautiful stuff ever.
"""
function compute_linear_return_array(dataTable::DataFrame; key::Symbol=Symbol("adjusted_close"))

    # initialize -
    price_array = Float64[]

    # iterate to build the price array -
    (number_of_rows, number_of_cols) = size(dataTable)
    for row_index = 1:number_of_rows
        data_value = dataTable[row_index,key]
        push!(price_array, data_value)
    end

    # return -
    return compute_linear_return_array(price_array)
end

"""
    compute_log_return_array(priceArray::Array{Float64})

Stuff will go here. Awesome stuff, the most beautiful stuff ever.
"""
function compute_log_return_array(priceArray::Array{Float64})::PSResult

    # initialize -
    log_return_array = Array{Float64,1}()

    # get the size -
    number_of_time_steps = length(priceArray)
    for time_index = 2:number_of_time_steps
        value = log((priceArray[time_index] / priceArray[time_index - 1]))
        push!(log_return_array, value)
    end

    # return -
    return PSResult(log_return_array)
end

"""
    compute_log_return_array(dataTable::DataFrame; key::Symbol = Symbol("adjusted_close"))

Stuff will go here. Awesome stuff, the most beautiful stuff ever.
"""
function compute_log_return_array(dataTable::DataFrame; key::Symbol=Symbol("adjusted_close"))::PSResult

    # initialize -
    price_array = Float64[]

    # iterate to build the price array -
    (number_of_rows, number_of_cols) = size(dataTable)
    for row_index = 1:number_of_rows
        data_value = dataTable[row_index,key]
        push!(price_array, data_value)
    end

    # return -
    return compute_log_return_array(price_array)
end

"""
    compute_return_volatility(dataTable::DataFrame; returnCalcFunction::Function=compute_linear_return_array,
        key::Symbol = Symbol("adjusted_close"))

Stuff will go here. Awesome stuff, the most beautiful stuff ever.
"""
function compute_return_volatility(dataTable::DataFrame; returnCalcFunction::Function=compute_linear_return_array,
    key::Symbol=Symbol("adjusted_close"))::PSResult

    # initialize -
    price_array = Float64[]

    # iterate to build the price array -
    (number_of_rows, number_of_cols) = size(dataTable)
    for row_index = 1:number_of_rows
        data_value = dataTable[row_index,key]
        push!(price_array, data_value)
    end

    # we have the difference -
    result = returnCalcFunction(price_array)
    if (isa(result.value, Exception) == true)
        return result
    end
    price_return_array = result.value

    # compute the std -
    volatlity = std(price_return_array)

    # return -
    return PSResult(volatlity)
end

"""
    compute_return_volatility(priceArray::Array{Float64};
        returnCalcFunction::Function=compute_linear_return_array)

Stuff will go here. Awesome stuff, the most beautiful stuff ever.
"""
function compute_return_volatility(priceArray::Array{Float64};
    returnCalcFunction::Function=compute_linear_return_array)::PSResult

    # compute the return array -
    result = returnCalcFunction(priceArray)
    if (isa(result.value, Exception) == true)
        return result
    end
    volatlity = std(result.value)

    # return -
    return PSResult(volatlity)
end


function decompose_vector(v::Vector3, k::Vector3)
    v_parallel = (v ⋅ k) * k
    v_perpendicular = v - v_parallel
    v_parallel, v_perpendicular
end

function rotate_around(v::Vector3, k::Vector3, φ::Real)
    k = normalize(k)
    v_parallel, v_perpendicular = decompose_vector(v, k)
    ω = k × v_perpendicular
    v_parallel + v_perpendicular * cos(φ) + ω * sin(φ)
end

@_with_default_rng function random_3d_direction()
    θ = 2π * rand(rng)
    z = 2 * rand(rng) - 1
    r = √(1 - z ^ 2)
    Vector3(r * cos(θ), r * sin(θ), z)
end

"""
    random_direction_on_plane([rng], normal_vector)
    random_direction_on_plane([rng], u, v)

Generate a random direction on a plane defined by a normal vector or two vectors on the plane.
"""
random_direction_on_plane(rng::AbstractRNG, u::Vector3, v::Vector3) = let φ = 2π * rand(rng)
    w = cos(φ) * u + sin(φ) * v
end
@_with_default_rng function random_direction_on_plane(normal_vector::Vector3)
    a, b, c = normal_vector
    u = normalize(Vector3(b - c, -a + c, a - b))
    v = normalize(normal_vector × u)
    random_direction_on_plane(rng, u, v)
end