# This file is a part of AstroLib.jl. License is MIT "Expat".
# Copyright (C) 2016 Mosè Giordano.

function trueanom(E::T, e::T) where {T<:AbstractFloat}
    @assert e >= 0 "eccentricity must be in the range [0, 1]"
    return 2 * atan(sqrt((1 + e) / (1 - e)) * tan(E / 2))
end

"""
    trueanom(E, e) -> true anomaly

### Purpose ###

Calculate true anomaly for a particle in elliptic orbit with eccentric anomaly
\$E\$ and eccentricity \$e\$.

### Explanation ###

In the two-body problem, once that the [Kepler's
equation](https://en.wikipedia.org/wiki/Kepler%27s_equation) is solved and
\$E(t)\$ is determined, the polar coordinates \$(r(t), \\theta(t))\$ of the body
at time \$t\$ in the elliptic orbit are given by

`` \\theta(t) = 2\\arctan \\left(\\sqrt{\\frac{1 + e}{1 - e}} \\tan\\frac{E(t)}{2} \\right)``

`` r(t) = \\frac{a(1 - e^{2})}{1 + e\\cos(\\theta(t) - \\theta_{0})}``

in which \$a\$ is the semi-major axis of the orbit, and \$\\theta_0\$ the value
of angular coordinate at time \$t = t_{0}\$.

### Arguments ###

* `E`: eccentric anomaly.
* `e`: eccentricity, in the elliptic motion regime (\$0 \\leq e \\leq 1\$)

### Output ###

The true anomaly.

### Example ###

Plot the true anomaly as a function of mean anomaly for eccentricity \$e = 0\$,
\$0.5\$, \$0.9\$.  Use [PyPlot.jl](https://github.com/JuliaPlots/Plots.jl/) for
plotting.

```julia
using PyPlot
M = range(0, stop=2pi, length=1001)[1:end-1];
for ecc in (0, 0.5, 0.9)
    plot(M, mod2pi.(trueanom.(kepler_solver.(M, ecc), ecc)))
end
```

### Notes ###

The eccentric anomaly can be calculated with [`kepler_solver`](@ref) function.
"""
trueanom(E::Real, e::Real) = trueanom(promote(float(E), float(e))...)


function F(x)
    # 1 + sum(x.^2)
    y = one(eltype(x))
    for _x in x
        y += _x^2
    end
    return y
end

function ∇F!(g, x)
    g .= 2 .* x
end

function H!(g, x)
    g .= 0
    for i in eachindex(x)
        g[i,i] = 2
    end
end


# Fuzzy C means algorithm

## Interface

struct FuzzyCMeansResult{T<:AbstractFloat} <: ClusteringResult
    centers::Matrix{T}          # cluster centers (d x C)
    weights::Matrix{Float64}    # assigned weights (n x C)
    iterations::Int             # number of elasped iterations
    converged::Bool             # whether the procedure converged
end

## Utility functions

function update_weights!(weights, data, centers, fuzziness, dist_metric)
    pow = 2.0/(fuzziness-1)
    nrows, ncols = size(weights)
    dists = pairwise(dist_metric, data, centers, dims=2)
    for i in 1:nrows
        for j in 1:ncols
            den = 0.0
            for k in 1:ncols
                den += (dists[i,j]/dists[i,k])^pow
            end
            weights[i,j] = 1.0/den
        end
    end
end

function update_centers!(centers, data, weights, fuzziness)
    nrows, ncols = size(weights)
    T = eltype(centers)
    for j in 1:ncols
        num = zeros(T, size(data,1))
        den = zero(T)
        for i in 1:nrows
            δm = weights[i,j]^fuzziness
            num += δm * data[:,i]
            den += δm
        end
        centers[:,j] = num/den
    end
end

const _fcmeans_default_maxiter = 100
const _fcmeans_default_tol = 1.0e-3
const _fcmeans_default_display = :none

function fuzzy_cmeans(
    data::AbstractMatrix{T},
    C::Int,
    fuzziness::Real;
    maxiter::Int = _fcmeans_default_maxiter,
    tol::Real = _fcmeans_default_tol,
    dist_metric::Metric = Euclidean(),
    display::Symbol = _fcmeans_default_display
    ) where T<:Real

    nrows, ncols = size(data)
    2 <= C < ncols || error("C must have 2 <= C < n")
    1 < fuzziness || error("fuzziness must be greater than 1")

    _fuzzy_cmeans(data, C, fuzziness, maxiter, tol, dist_metric, display_level(display))

end

## Core implementation

function _fuzzy_cmeans(
    data::AbstractMatrix{T},                        # data matrix
    C::Int,                                         # total number of classes
    fuzziness::Real,                                # fuzziness
    maxiter::Int,                                   # maximum number of iterations
    tol::Real,                                      # tolerance
    dist_metric::Metric,                            # metric to calculate distance
    displevel::Int                                  # the level of display
    ) where T<:Real

    nrows, ncols = size(data)

    # Initialize weights randomly
    weights = rand(Float64, ncols, C)
    weights ./= sum(weights, dims=2)

    centers = zeros(T, (nrows, C))
    prev_centers = identity.(centers)

    δ = Inf
    iter = 0

    if displevel >= 2
        @printf "%7s %18s\n" "Iters" "center-change"
        println("----------------------------")
    end

    while iter < maxiter && δ > tol
        update_centers!(centers, data, weights, fuzziness)
        update_weights!(weights, data, centers, fuzziness, dist_metric)
        δ = maximum(colwise(dist_metric, prev_centers, centers))
        copyto!(prev_centers, centers)
        iter += 1
        if displevel >= 2
            @printf("%7d %18.6e\n", iter, δ)
        end
    end

    if displevel >= 1
        if δ <= tol
            println("Fuzzy C-means converged with $iter iterations (δ = $δ)")
        else
            println("Fuzzy C-means terminated without convergence after $iter iterations (δ = $δ)")
        end
    end

    FuzzyCMeansResult(centers, weights, iter, δ <= tol)
end

function Base.show(io::IO, result::FuzzyCMeansResult)
    d, C = size(result.centers)
    n, iter = size(result.weights, 1), result.iterations
    print(io, "FuzzyCMeansResult: $C clusters for $n points in $d dimensions ")
    if result.converged
        print(io, "(converged in $iter iterations)")
    else
        print(io, "(failed to converge in $iter iterations)")
    end
end


# This example is reused in the documentation
# -> NLS_Solver.jl/docs/src/nonlinear_regression.md
#
using NLS_Solver
using Random
using Plots

# Be reproducible
rng = MersenneTwister(1234)

# Define model ================
#
function gaussian_peak(x::Real,
                       h::Real,
                       μ::Real,
                       σ::Real)
    @assert σ>0

    h*exp(-((x-μ)/σ)^2/2)
end

function exp_baseline(x::Real,
                      b::Real,
                      τ::Real)
    @assert τ>0

    b*exp(-x/τ)
end

function model(x::Real,θ::AbstractVector)
    @assert length(θ) == 5

    # one gaussian peaks + exp baseline
    #
    (h, μ, σ, b, τ) = (θ_i for θ_i in θ)

    exp_baseline(x,b,τ) + gaussian_peak(x,h,μ,σ)
end

function residue(X::AbstractVector,Y::AbstractVector,θ::AbstractVector)
    map(zip(X,Y)) do (X_i,Y_i)
        Y_i - model(X_i, θ)
    end
end

# Generate synthetic data ================
#
X=[1:0.1:30;]
θ_true=[2.0,15,3.0,3.0,8.0]

Y_true = map(X) do X_i
    model(X_i,θ_true)
end
Y_noisy = map(Y_true) do Y_i
    Y_i + 0.5 * randn()
end

plot(X,Y_noisy,label="noisy signal")
plot!(X,Y_true,linewidth=3,linestyle=:dot,label="true signal")

# Solve the problem ================
#
n_θ = length(θ_true)
n_sample = length(X)

# Bound constraints ----------------
#
#                         h,  μ,  σ,   b,  τ
θ_lowerbound = Float64[   1, 10,  1,   0,  1 ]
θ_upperbound = Float64[ Inf, 20, 10, Inf, 10 ]
bc = BoundConstraints(θ_lowerbound, θ_upperbound)

# Initial guess
#
θ_init = project!(ones(n_θ),bc)

# Solver ----------------
#
conf = LevenbergMarquardt_BC_Conf()

# Wrap residue ----------------
#
nls = create_NLS_problem_using_ForwardDiff(θ->residue(X,Y_noisy,θ),n_θ => n_sample)

# Solve it! ----------------
#
result = solve(nls,θ_init,bc,conf)

# Plot fitted model ================
#

# Initial model ----------------
#
# Y_init = map(X_i -> model(X_i,θ_init), X)
# plot!(X,Y_init,linewidth=3,label="initial model")

# Fitted model ----------------
#
θ_solution = solution(result)

Y_solution = map(X_i -> model(X_i,θ_solution), X)
plot!(X,Y_solution,linewidth=3,label="fitted model")


### A Pluto.jl notebook ###
# v0.10.0

using Markdown

# ╔═╡ b7a4d6c3-dc52-45c0-bcd6-dc1d32dbd302
md"""
# Factorizaciones y otras diversiones
Autor: Andreas Noack Jensen (MIT) (http://www.econ.ku.dk/phdstudent/noack/)
(con edición de Jane Herriman=

## Sinopsis
 - Factorizaciones
 - Estructuras de matrices especiales
 - Álgebra lineal genérica
"""

# ╔═╡ 43882091-fc1b-409a-92d9-455ca7ad6f64
md"""
Empezamos con un sistema lineal de la forma

`Ax = b`
"""

# ╔═╡ 85ad00b8-4b91-46c9-a130-4430f2986569
A = randn(3,3)

# ╔═╡ 5a8dd4eb-40a6-4750-b2e7-d44b30404363
x = fill(1.0, (3))
b = A*x

# ╔═╡ a98929e4-ce11-49b1-aeca-44ed3680df53
md"""
### Factorización
La función `\` esconde cómo regularmente se resuelve el problema.

Dependiendo de las dimensiones de `A`, distintos métodos son elegidos para resolver el problema.

```
Ax = b
```

Un paso intermedio en la solución es el cálculo de la factorización de la matriz `A`.

Básicamente, una factorización de `A` es una manera de expresar `A` como el producto de matrices triangulares, unitarias, y de permutación.

Julia guarda estas factorizaciones usando un tipo abstracto llamado `Factorization` y varios subtipos.

Un objeto `Factorization` entonces debería ser pensado como una represenatación de la matriz `A`.

#### LU
"""

# ╔═╡ e00b9a3c-3ffc-4aac-b5f7-a88a0daafba4
md"""
Cuando `A` es cuadrada, el sistema linear es resuelto factorizando `A` vía

```
PA=LU
```

donde `P` es una matriz de permutación, `L` es un triangular inferior unitaria y `U` es triangular superior.

Julia permita calcular la facorización LU y define un tipo de factorización compuesta para guardarlo.
"""

# ╔═╡ a6bc3137-f8f6-44ec-bc7e-baa5a2f9c499
md"""
Podemos hacer una factorización LU sobre `A` vía `lu(A)` ó `lufact(A)`.

La función `lu` regresa matrices `l` y `u` y un vector de permutación `p`.
"""

# ╔═╡ 0b6b51ee-ff9f-463e-95e1-2fd1f559bdf1
l,u,p = lu(A)

# ╔═╡ 2aefd8c5-abc3-4d1a-83fb-78f22190e0f3
md"""
El pivoteo está prendido por default, o sea que no podemos assumir que A == LU.
Vamos a comprobar esto viendo la norma de `LU - A`:
"""

# ╔═╡ 94c7fee6-2ada-4223-8fc0-57c7896181aa
norm(l*u - A)

# ╔═╡ edc134d0-de77-429a-8bac-36ef19e80471
md"""
Esto nos muestra que queremos tomar en cuenta el pivoteo!

Podemos pensar en `A[p,:]` como la sintaxis para `PA`, o como el producto de una matriz de permutación con `A`
"""

# ╔═╡ a123c969-8e5c-432b-9bbd-3f1dfa4b8f31
norm(l*u - A[p,:])

# ╔═╡ da792d5e-9ebf-4b93-9d57-61745021ec00
md"""
Por otro lado, podemos apagar el pivoteo usando `Val{false}` en Julia 0.6 ó `Val(false)` en versiones más modernas.
"""

# ╔═╡ 46ab0bb7-ded7-4d98-a452-70242a5bf648
l,u,p = lu(A, Val{false})

# ╔═╡ 8156b8f8-9919-42c7-83cd-49589bfd53e7
md"""
Cuando apagamos el pivoteo, `LU = A`
"""

# ╔═╡ 608bfb30-6530-474b-9374-c5caa3a716f8
norm(l*u - A)

# ╔═╡ cfd8ea62-ff51-4966-ad3e-2d12a937e71e
md"""
Una segunda manera de hacer la factorización es con `lufact`.
"""

# ╔═╡ 289f4361-2962-48f4-8c47-915290a26035
Alu = lufact(A)

# ╔═╡ 039fdbaf-9870-4efc-a54a-f457088c5e88
md"""
Distintas partes de la factorización las puedes accesar con índices especiales
"""

# ╔═╡ d8217c6a-2f5f-4824-99e9-e56bfca07e92
Alu[:P]

# ╔═╡ 7d46f4fb-e092-409f-a0b0-5caad91f4022
Alu[:L]

# ╔═╡ 44aec045-5803-41d2-9574-31a9f14d9cf7
Alu[:U]

# ╔═╡ 466124e7-9978-4c7c-9cf1-6e34c7f55c33
md"""
Podemos calcular la solución de $Ax=b$ del objecto de factorización
"""

# ╔═╡ 9a023eb2-ce3d-413e-a872-231325c556b6
# PA = LU
# A = P'LU
# P'LUx = b
# LUx = Pb
# Ux = L\Pb
# x = U\L\Pb
Alu[:U]\(Alu[:L]\(Alu[:P]b))

# ╔═╡ cf9efbb5-87ad-4795-b132-8877b334f85d
md"""
*Más importantemente,* podemos despachar sobre el tipo de `LU` y simplemente resolver el problema por medio de
"""

# ╔═╡ 45f9ca18-2ba7-4e8c-9087-ebadf2aa7101
Alu\b

# ╔═╡ 424a4d00-0264-45cf-bbba-43fb51ffa394
md"""
Esto puede ser útil si el mismo lado izquierdo es usado para lados derechos.

La factorización también se puede usar para calcular el determinante pues

$\det(A)=\det(PLU)=\det(P)\det(U)=\pm \prod u_{ii}$

porque $U$ es triangular y el signo está definido por $\det(P)$.
"""

# ╔═╡ ba2407dd-ef47-4532-a783-aafb17506e5f
det(A)

# ╔═╡ 299bec2c-626f-40ca-a2f9-4e0450b23f3e
det(Alu)

# ╔═╡ 0fa226f7-8a15-466b-9757-3c019fd4c58e
md"""
#### QR
Cuando `A` es alta,
"""

# ╔═╡ 92962e9b-13c2-465e-ae4d-b1c8a0172063
Atall = randn(3, 2)

# ╔═╡ e802da18-5118-4031-9be8-0fb90a7a0c9d
md"""
Julia calcula la solución de mínimos cuadrados $\hat{x}$ que minimiza $\|Ax-b\|_2$.

Esto se puede hacer factorizando

```
A=QR
```

donde $Q$ es unitaria/ortogonal y

$R=\left(\begin{smallmatrix}R_0\\0\end{smallmatrix}\right)$ y  $R_0$ es triangular superior.

con la factorización QR la norma mínima se puede expresar como

\begin{equation*}
\|Ax-b\|=\|QRx-b\|=\|Q(Rx-Q'b)\|=\|Rx-Q'b\|=\left\|\begin{pmatrix}R_0x-Q_0'b\\Q_1'b\end{pmatrix}\right\|=\|R_0x-Q_0'b\|+\|Q_1'b\|
\end{equation*}

Y entonces el problema se puede reducir a resolver el problema cuadrado $R_0x=Q_0'b$ para $x$.

Podemos factorizar QR sobre `Atall` vía
"""

# ╔═╡ 215ea9e5-1a3a-4750-b249-2ed54f3ea9ac
Aqr = qrfact(Atall)

# ╔═╡ 021e9680-4a33-469e-8014-ae3b831db370
md"""
Otra característica de la factorización QR es que los tipos `Q` para guardar las matrices unitarias $Q$. Se pueden extraer de tipos `QR` con los índices
"""

# ╔═╡ e55a8449-a507-4e90-9f1c-013edb26704d
Aqr[:Q]

# ╔═╡ 60ce24a1-2d71-4efc-95a8-3e210da5c53f
md"""
Similarmente, la matriz superior triangular $R$ se puede extraer con el índice
"""

# ╔═╡ fa8dca75-5aca-454d-b733-8da5b10c6857
Aqr[:R]

# ╔═╡ 60918647-3d3d-4dec-a4e8-e0cf4348d072
md"""
En este caso se guarda R como una matriz 2x2 en vez de 3x2 porque el último renglón de R está lleno de 0's.

Aunque la matriz `Aqr[:Q]` se imprime como $3\times 3$ en el objeto de factorización, en la práctica puede representar la versión delgada también. Así
"""

# ╔═╡ 83e8b0eb-f8c9-4e4d-b319-27134985ffd1
Aqr[:Q]*ones(2)

# ╔═╡ 9aa2af05-bbb0-4570-8958-0ac08c5da7b5
md"""
funciona y representa a $3 x 2$ matrix por un vector de 2-elementos.

Similarmente,
"""

# ╔═╡ 4ce42698-361a-4929-b03e-23b43ad04d33
Aqr[:Q]*ones(3)

# ╔═╡ 32879b97-8789-408f-a992-5f21dcd6e8fe
md"""
funciona representando la matriz $3x3$ y un vector de 3 elementos.

Sin embargo, esto no significa que podemos multiplicar a `Q` por vectores de longitued arbitraria.
"""

# ╔═╡ 4c3f2f14-3bd3-45c1-b36d-1000811fc622
Aqr[:Q]*ones(4)

# ╔═╡ 6a2732c8-241f-426c-8b51-ba130c3a4047
md"""
La matriz tiene representación interna compacta, entonces indexar sólo hace sentido si uno sabe cómo la factorización guarda datos.
"""

# ╔═╡ 45af09e4-8d42-404e-8e69-e0d185f4ea27
Aqr[:Q][1]

# ╔═╡ 8227c126-4417-4d6a-9c40-232ae467b17d
md"""
El objeto QRCompactWY `\` tiene un método para QR y entonces el problema de los mínimos cuadrados es resuelto con
"""

# ╔═╡ cad5d92d-d7cb-444d-9664-e9eccbbb5b06
Aqr\b

# ╔═╡ 6d694031-b7e4-423e-a341-e1e78e26d484
md"""
Y si en vez escribimos
"""

# ╔═╡ ac57e6e1-986c-4efe-ad64-e2bb3a4aeb4c
Atall\b

# ╔═╡ aa4e579d-5b59-494c-be51-dc65e6b57aa6
md"""
En vez de factorizar con QR a `Atall` primero, Julia va a defaultear factorizar *con* pivoteo.
"""

# ╔═╡ 61434524-e698-4a8d-a430-0126ac99157e
md"""
Este default a pivotear la factorización QR le permite a Julia resolver problemas deficientes de rango.

Podemos explícitamente escoger pivotear durante la factorización QR (de una matriz singular, por ejemplo) con `Val{true}`.
"""

# ╔═╡ 04f4492e-5156-4526-b761-ab3c5a209646
v = randn(3)
# Tomar el producto exterio de un vector consigo mismo nos da una matriz singular
singmatriz = v * v'

# ╔═╡ f8bb7598-095d-449a-86e2-f8bd6b9d8b94
Aqrp = qrfact(singmatriz,Val{true})

# ╔═╡ a8e4b120-54b2-4e16-b993-5a884c29309d
md"""
Notamos que el tipo que resulta del objecto de Factorization es distinto que antes.

`\` también tiene un método de `QRPivoted` y el problema con rango deficiente es entonces calculado como
"""

# ╔═╡ c85c1276-a8d9-4f02-9fd7-0a448da34436
Aqrp\b

# ╔═╡ 9ea6c93a-e36f-4624-9598-7e4741efa50a
md"""
#### Eigendescompisición y los SVDs (Valores de descomposición Singular)
"""

# ╔═╡ 6a578f8f-29fa-4583-a328-e8f33ba80738
md"""
Los resultados de eigendescomposición y de la descomposición singular de valores se guardan en los tipos`Factorization`. Esto también incluye la factorización de Hessenberg y de Schur

La eigendescomposición puede ser calculada como
"""

# ╔═╡ 3e8c3380-7db5-4e35-b6f8-9b94dc417f80
Asym = A + A'
AsymEig = eigfact(Asym)

# ╔═╡ 0c0caea9-3ee9-4e30-8570-4d30ed7cc14a
md"""
Los valores y vectores se pueden recoger del tipo Eigen con un índice especial
"""

# ╔═╡ 3cde0ff8-2325-47b7-a1ff-d15155c2f634
AsymEig[:values]

# ╔═╡ c4d93f45-2191-4624-ade9-277b546ced95
AsymEig[:vectors]

# ╔═╡ b51b8521-6f77-410e-ae49-de138a1aec67
md"""
Una vez más, como la descomposición se guarda en un tipo, podemos despatchar sobre esos tipos y explotar un método especializado para cada factorización, e.g. que $A^{-1}=(V\Lambda V^{-1})^{-1}=V\Lambda^{-1}V^{-1}$.
"""

# ╔═╡ 50f4c300-e49b-4331-b364-582cc7692dd7
inv(AsymEig)*Asym

# ╔═╡ 65386dae-fb08-47d7-a81c-c3133a8403cf
md"""
Julia también tiene una función `eig` que regresa una tupla con los valores y vectores
"""

# ╔═╡ 707539e7-c4b9-46c1-89f1-8d61a19413fa
eig(Asym)

# ╔═╡ 827e2055-5f83-44b1-a1dc-636b807c8b81
md"""
No recomendamos esta versión.

La función `svdfact` calcula la descomposición singular de valores
"""

# ╔═╡ 1afd9e85-1f7d-48aa-baca-8cdf754e4d4b
Asvd = svdfact(A[:,1:2])

# ╔═╡ 78a13f6a-ce68-4060-a1bf-1993ad5e93ad
md"""
y de nuevo `\` tiene un método para el tipo que permite los mínimos cuadrados por SVD
"""

# ╔═╡ a5b49236-f31e-4449-941e-70e1677ae1b2
Asvd\b

# ╔═╡ 9ad38b16-f9b9-4cde-bbfa-204d96ee3a43
md"""
Existen funciones especiales para proporcionar los valores sólamente: `eigvals` and `svdvals`.
"""

# ╔═╡ 0139d8bd-7a60-4cdc-8844-91c0e0eaa9c6
md"""
### Estructuras especiales de matrices

La estructura de matrices es muy importante en el álgebra linear. Ésta estructura se le puede hacer explícita a Julia por medio de los tipos compuestos. Ejemplos: `Diagonal`, `Triangular`, `Symmetric`, `Hermitian`, `Tridiagonal` y `SymTridiagonal`. Se han escrito métodos especializados para cada tipo de matriz especial para aprovechar su estructura. Siguen algunos ejemplos:
"""

# ╔═╡ 7ee1aa1c-7219-46d3-8abd-5a226c0f4abb
A

# ╔═╡ f572bd5d-f1f4-4dce-a5be-293b4404bf71
md"""
Creando una matriz Diagonal
"""

# ╔═╡ 6846c3c4-6f80-43dc-9ece-07efe11133f8
Diagonal(diag(A))

# ╔═╡ 2b5bb260-8997-4b54-ba40-c19c0bd592a5
Diagonal(A)

# ╔═╡ 7f270025-80df-4b91-babc-db876885e479
md"""
Creando una matriz triangular inferior
"""

# ╔═╡ 876fb13e-d540-4931-ad32-f9491fd504c7
LowerTriangular(tril(A))

# ╔═╡ e46ff282-c6a5-4917-8322-9ff2d1df8318
LowerTriangular(A)

# ╔═╡ b1093e1f-65fa-40d1-8b56-a374f15b3f51
md"""
Creando una matriz simétrica
"""

# ╔═╡ 9d354a03-6229-46f1-80eb-cbed5d304d5b
Symmetric(Asym)

# ╔═╡ 6e20dd10-3e4a-411e-bfdf-1534b509e8ce
SymTridiagonal(diag(Asym),diag(Asym,1))

# ╔═╡ fb94650f-06f4-4b65-a598-45c820c27c95
md"""
Cuando se sabe que una matriz es e.g. triangular o simétrica Julia puede que resuelva el problema más rápido convirtiendo a una matriz especial.

Para algunos procedimientos, Julia checa si el input de matriz es triangular o simétrica y lo convierte a tal estructura si lo detecta.

Debería notarse que `Symmetric`, `Hermitian` y `Triangular` no copian la matriz original.

#### Eigenproblema simétrico
La capacidad de Julia para poder detectar si una matriz es simétrica/Hermitian puede influenciar muchísimo sobre qué tan rápido se puede resolver un problema de eigenvalor.
"""

# ╔═╡ 7773e25a-57e8-4917-9629-7329d7ddc60c
n = 1000;
A = randn(n,n);

# ╔═╡ 2f6332ef-caef-423b-bc2d-4299436390f8
md"""
Usamos `A` para genera una matriz simétrica `Asym`
"""

# ╔═╡ b844544e-c513-42af-9b27-b006d50973c0
Asym = A + A';

# ╔═╡ f34e931a-c11b-4776-8d50-90812ef67d5b
md"""
Ahora creemos una matriz Asym para simular una matriz simétrica con errores de punto flotante
"""

# ╔═╡ 09b262f1-6a2d-4696-a194-54b65f129eb3
Asym_noisy = copy(Asym); Asym_noisy[1,2] += 5eps();

# ╔═╡ 44b64772-3daa-4037-9b6f-07d5feba3223
md"""
¿Puede Julia determinar que ambas `Asym` y `Asym_noisy` son matrices simétricas?
"""

# ╔═╡ dcef7621-ef7f-467e-9dec-75fae2bc44f3
println("Is Asym symmetric? ", issymmetric(Asym))
println("Is Asym_noisy symmetric? ", issymmetric(Asym_noisy))

# ╔═╡ e451672e-e44d-45c7-a3c2-3badfcc83d40
md"""
Ahora veamos como el ruido de `Asym_noisy` impacta el tiempo en llevar a cabo una eigendescomposición
"""

# ╔═╡ a994a69a-3879-49ad-a03a-c05d901b4072
@time eigvals(Asym);

# ╔═╡ 63d5a411-bd56-4ff6-bf35-a23995d4c2bb
@time eigvals(Asym_noisy);

# ╔═╡ 9902503c-0448-4bfc-bf6f-7c11321cca78
md"""
Por suerte, le podeemos proveer información explícita sobre la estructura de la matriz a Julia
En este ejemplo, usamos la palabra clave `Symmetric`
"""

# ╔═╡ 9d099581-b364-4ad0-990a-d3151dcdcf96
@time eigvals(Symmetric(Asym_noisy));

# ╔═╡ 8ea1527f-a664-4520-9801-840e77bd6e62
md"""
Y así nuestros cálculos son mucho más eficientes :)
"""

# ╔═╡ 231d2771-137d-4930-80a6-4ca352e88aa5
md"""
### Un gran problema
Usar matrices tridiagonales permite trabajar con problemas potencialmente muy grandes. El siguiente problema no seria posible resolverlo en una laptop si la matriz se tuviera que guardar como tipo `Matrix`.
"""

# ╔═╡ a0afdba1-25a0-409c-b84d-c63e53dbf343
n = 1_000_000;
A = SymTridiagonal(randn(n), randn(n-1));
@time eigmax(A)

# ╔═╡ 49cfb27c-8c00-4458-a77a-38dd5a4c643a
md"""
### Álgebra lineal numérica
La manera usual de agregar soporte para álgebra lineal numérica es haciendo un wrapper para subrutinas de BLAS y LAPACK. Para matrices con elementos `Float32`, `Float64`, `Complex{Float32}` ó `Complex{Float64}` esto es lo que hace Julia. Desde hace rato, Julia ha tnido soport para la multiplicación genérica de tipos. Así, cuando uno multiplica matrices de enteros, obtiene una matriz de enteros.
"""

# ╔═╡ 0f4aec6b-6701-46f9-8a60-1c91f19cbb37
rand(1:10,3,3)*rand(1:10,3,3)

# ╔═╡ 1b26ea8e-d825-48ab-a0a0-a401dd83d42c
md"""
Recientemente, más métodos de álgebra lineal se han añadido a Julia y ahora soporta factorizaciones generales de tipo `LU` y `QR`. Métodos generales para eigenvalores y SVD han sido escritos más recientemente en paquetería externa.

En general, la factorización `LU` puede ser calculada cuando los elementos de la matriz se cierraon sobre los operadores `+`, `-`, `*` y `\`. Por supuesto, la matriz también deben tener rango completo. El método general de factorización `LU` en Julia aplica pivoteo y por lo tanto debe poder soportar `<` y `abs`. Por lo tanto es posible resolver sistemas de ecuaciones de e.g. números racionales como en los ejemplos que siguen.
"""

# ╔═╡ 8b718fad-0ded-42d2-915c-7868a8fc15df
md"""
Para usar números racionales, usamos un doble //:
"""

# ╔═╡ 9858cb5f-ec34-4ce3-aa44-a676bf2d9d35
1//2

# ╔═╡ 4811bad4-fa07-4a22-92e6-c9947bce13d1
md"""
#### Ejemplo 1: Sistemas racionales lineales de ecuaciones
Julia cuenta con números racionales ya instalados. El siguiente ejemplo consta de un sistema lineal de ecuaciones resulto sin promover a tipos de punto flotanto. Puede haber un error de overflow fácilmente al trabajar con racionales, así que usamos `BigInt`s.
"""

# ╔═╡ a756d1d8-57ce-4e17-984f-7fbe1a8117e5
Ar = convert(Matrix{Rational{BigInt}}, rand(1:10,3,3))/10

# ╔═╡ e97ccbb7-fb81-4514-a8b6-0b177ef9620e
x = fill(1, (3))
b = Ar*x

# ╔═╡ a808d3f1-21a0-4d72-8323-9ffdb55084b6
Ar\b

# ╔═╡ d9e7e3c7-cf57-4b86-b2f6-7b24b3ea8bdb
lufact(Ar)

# ╔═╡ 6d5e0b76-5360-4e77-bd5a-93f5aac6011a
md"""
#### Ejemplo 2: Matriz racional de eigenestructura

El siguiente ejemplo muestra como la artimética de matriz racional puede ser usada para calcular una matriz dados los eigenvalores y eigenvectores racionales. Yo he encontrado ésto útil para mostrar ejemplos de sistemas dinámicos lineales.
"""

# ╔═╡ 57d7e0de-a3c9-4fda-b4b1-4e847bd5f8d2
λ1,λ2,λ3 = 1//1,1//2,1//4
v1,v2,v3 = [1,0,0],[1,1,0],[1,1,1]
V,Λ = [v1 v2 v3], Diagonal([λ1,λ2,λ3])
A = V*Λ/V

# ╔═╡ b29effd2-bb03-4f65-bd5c-ba5206378c54
md"""
### Exercises

11.1 ¿Cuáles son los eigenvalores de la Matriz A

```
A =
[
 140   97   74  168  131
  97  106   89  131   36
  74   89  152  144   71
 168  131  144   54  142
 131   36   71  142   36
]
```
"""

# ╔═╡ 9e39d0ed-49b7-48c6-894e-6140c37aebbc

# ╔═╡ b7875dd0-68ff-475f-8c90-e3d33a72b12a
md"""
11.2 Crea una matriz diagonal de los eigenvalores de A
"""

# ╔═╡ 4a6152f4-78be-47e6-906a-25863ba85913

# ╔═╡ b6899c90-d9b9-4476-9971-96fef2aa14d0
md"""
11.3 Realiza un factorización de Hessenberg sobre la matriz A. Verifica que `A = QHQ'`.
"""

# ╔═╡ db8a09c4-2825-47c8-9697-1990d65b0b58

# ╔═╡ Cell order:
# ╟─b7a4d6c3-dc52-45c0-bcd6-dc1d32dbd302
# ╟─43882091-fc1b-409a-92d9-455ca7ad6f64
# ╠═85ad00b8-4b91-46c9-a130-4430f2986569
# ╠═5a8dd4eb-40a6-4750-b2e7-d44b30404363
# ╟─a98929e4-ce11-49b1-aeca-44ed3680df53
# ╟─e00b9a3c-3ffc-4aac-b5f7-a88a0daafba4
# ╟─a6bc3137-f8f6-44ec-bc7e-baa5a2f9c499
# ╠═0b6b51ee-ff9f-463e-95e1-2fd1f559bdf1
# ╟─2aefd8c5-abc3-4d1a-83fb-78f22190e0f3
# ╠═94c7fee6-2ada-4223-8fc0-57c7896181aa
# ╟─edc134d0-de77-429a-8bac-36ef19e80471
# ╠═a123c969-8e5c-432b-9bbd-3f1dfa4b8f31
# ╟─da792d5e-9ebf-4b93-9d57-61745021ec00
# ╠═46ab0bb7-ded7-4d98-a452-70242a5bf648
# ╟─8156b8f8-9919-42c7-83cd-49589bfd53e7
# ╠═608bfb30-6530-474b-9374-c5caa3a716f8
# ╟─cfd8ea62-ff51-4966-ad3e-2d12a937e71e
# ╠═289f4361-2962-48f4-8c47-915290a26035
# ╟─039fdbaf-9870-4efc-a54a-f457088c5e88
# ╠═d8217c6a-2f5f-4824-99e9-e56bfca07e92
# ╠═7d46f4fb-e092-409f-a0b0-5caad91f4022
# ╠═44aec045-5803-41d2-9574-31a9f14d9cf7
# ╟─466124e7-9978-4c7c-9cf1-6e34c7f55c33
# ╠═9a023eb2-ce3d-413e-a872-231325c556b6
# ╟─cf9efbb5-87ad-4795-b132-8877b334f85d
# ╠═45f9ca18-2ba7-4e8c-9087-ebadf2aa7101
# ╟─424a4d00-0264-45cf-bbba-43fb51ffa394
# ╠═ba2407dd-ef47-4532-a783-aafb17506e5f
# ╠═299bec2c-626f-40ca-a2f9-4e0450b23f3e
# ╟─0fa226f7-8a15-466b-9757-3c019fd4c58e
# ╠═92962e9b-13c2-465e-ae4d-b1c8a0172063
# ╟─e802da18-5118-4031-9be8-0fb90a7a0c9d
# ╠═215ea9e5-1a3a-4750-b249-2ed54f3ea9ac
# ╟─021e9680-4a33-469e-8014-ae3b831db370
# ╠═e55a8449-a507-4e90-9f1c-013edb26704d
# ╟─60ce24a1-2d71-4efc-95a8-3e210da5c53f
# ╠═fa8dca75-5aca-454d-b733-8da5b10c6857
# ╟─60918647-3d3d-4dec-a4e8-e0cf4348d072
# ╠═83e8b0eb-f8c9-4e4d-b319-27134985ffd1
# ╟─9aa2af05-bbb0-4570-8958-0ac08c5da7b5
# ╠═4ce42698-361a-4929-b03e-23b43ad04d33
# ╟─32879b97-8789-408f-a992-5f21dcd6e8fe
# ╠═4c3f2f14-3bd3-45c1-b36d-1000811fc622
# ╟─6a2732c8-241f-426c-8b51-ba130c3a4047
# ╠═45af09e4-8d42-404e-8e69-e0d185f4ea27
# ╟─8227c126-4417-4d6a-9c40-232ae467b17d
# ╠═cad5d92d-d7cb-444d-9664-e9eccbbb5b06
# ╟─6d694031-b7e4-423e-a341-e1e78e26d484
# ╠═ac57e6e1-986c-4efe-ad64-e2bb3a4aeb4c
# ╟─aa4e579d-5b59-494c-be51-dc65e6b57aa6
# ╟─61434524-e698-4a8d-a430-0126ac99157e
# ╠═04f4492e-5156-4526-b761-ab3c5a209646
# ╠═f8bb7598-095d-449a-86e2-f8bd6b9d8b94
# ╟─a8e4b120-54b2-4e16-b993-5a884c29309d
# ╠═c85c1276-a8d9-4f02-9fd7-0a448da34436
# ╟─9ea6c93a-e36f-4624-9598-7e4741efa50a
# ╟─6a578f8f-29fa-4583-a328-e8f33ba80738
# ╠═3e8c3380-7db5-4e35-b6f8-9b94dc417f80
# ╟─0c0caea9-3ee9-4e30-8570-4d30ed7cc14a
# ╠═3cde0ff8-2325-47b7-a1ff-d15155c2f634
# ╠═c4d93f45-2191-4624-ade9-277b546ced95
# ╟─b51b8521-6f77-410e-ae49-de138a1aec67
# ╠═50f4c300-e49b-4331-b364-582cc7692dd7
# ╟─65386dae-fb08-47d7-a81c-c3133a8403cf
# ╠═707539e7-c4b9-46c1-89f1-8d61a19413fa
# ╟─827e2055-5f83-44b1-a1dc-636b807c8b81
# ╠═1afd9e85-1f7d-48aa-baca-8cdf754e4d4b
# ╟─78a13f6a-ce68-4060-a1bf-1993ad5e93ad
# ╠═a5b49236-f31e-4449-941e-70e1677ae1b2
# ╟─9ad38b16-f9b9-4cde-bbfa-204d96ee3a43
# ╟─0139d8bd-7a60-4cdc-8844-91c0e0eaa9c6
# ╠═7ee1aa1c-7219-46d3-8abd-5a226c0f4abb
# ╟─f572bd5d-f1f4-4dce-a5be-293b4404bf71
# ╠═6846c3c4-6f80-43dc-9ece-07efe11133f8
# ╠═2b5bb260-8997-4b54-ba40-c19c0bd592a5
# ╟─7f270025-80df-4b91-babc-db876885e479
# ╠═876fb13e-d540-4931-ad32-f9491fd504c7
# ╠═e46ff282-c6a5-4917-8322-9ff2d1df8318
# ╟─b1093e1f-65fa-40d1-8b56-a374f15b3f51
# ╠═9d354a03-6229-46f1-80eb-cbed5d304d5b
# ╠═6e20dd10-3e4a-411e-bfdf-1534b509e8ce
# ╟─fb94650f-06f4-4b65-a598-45c820c27c95
# ╠═7773e25a-57e8-4917-9629-7329d7ddc60c
# ╟─2f6332ef-caef-423b-bc2d-4299436390f8
# ╠═b844544e-c513-42af-9b27-b006d50973c0
# ╟─f34e931a-c11b-4776-8d50-90812ef67d5b
# ╠═09b262f1-6a2d-4696-a194-54b65f129eb3
# ╟─44b64772-3daa-4037-9b6f-07d5feba3223
# ╠═dcef7621-ef7f-467e-9dec-75fae2bc44f3
# ╟─e451672e-e44d-45c7-a3c2-3badfcc83d40
# ╠═a994a69a-3879-49ad-a03a-c05d901b4072
# ╠═63d5a411-bd56-4ff6-bf35-a23995d4c2bb
# ╟─9902503c-0448-4bfc-bf6f-7c11321cca78
# ╠═9d099581-b364-4ad0-990a-d3151dcdcf96
# ╟─8ea1527f-a664-4520-9801-840e77bd6e62
# ╟─231d2771-137d-4930-80a6-4ca352e88aa5
# ╠═a0afdba1-25a0-409c-b84d-c63e53dbf343
# ╟─49cfb27c-8c00-4458-a77a-38dd5a4c643a
# ╠═0f4aec6b-6701-46f9-8a60-1c91f19cbb37
# ╟─1b26ea8e-d825-48ab-a0a0-a401dd83d42c
# ╟─8b718fad-0ded-42d2-915c-7868a8fc15df
# ╠═9858cb5f-ec34-4ce3-aa44-a676bf2d9d35
# ╟─4811bad4-fa07-4a22-92e6-c9947bce13d1
# ╠═a756d1d8-57ce-4e17-984f-7fbe1a8117e5
# ╠═e97ccbb7-fb81-4514-a8b6-0b177ef9620e
# ╠═a808d3f1-21a0-4d72-8323-9ffdb55084b6
# ╠═d9e7e3c7-cf57-4b86-b2f6-7b24b3ea8bdb
# ╟─6d5e0b76-5360-4e77-bd5a-93f5aac6011a
# ╠═57d7e0de-a3c9-4fda-b4b1-4e847bd5f8d2
# ╟─b29effd2-bb03-4f65-bd5c-ba5206378c54
# ╠═9e39d0ed-49b7-48c6-894e-6140c37aebbc
# ╟─b7875dd0-68ff-475f-8c90-e3d33a72b12a
# ╠═4a6152f4-78be-47e6-906a-25863ba85913
# ╟─b6899c90-d9b9-4476-9971-96fef2aa14d0
# ╠═db8a09c4-2825-47c8-9697-1990d65b0b58


"""
Julia program to find the number of derangements of a set of n elements.
A Derangement is a permutation of n elements, such that no element appears in its original position.
Here, given a number n, find total number of derangements of a set of n elements
"""

function count_derangements(n)

    # Build the dp table in Bottom Up manner
    dp = zeros(Int, n + 1)
    # Assign base cases
    dp[2] = 0
    dp[3] = 1
    for i in 4:n+1
        dp[i] = (i-2) * (dp[i-1] + dp[i-2])
    end
    return dp[n+1]
end

print("Enter a number: ")
n = readline()
n = parse(Int, n)
res = count_derangements(n)
print("The number of derangements in a set of $n numbers is $res.")

"""
Time Complexity - O(n)
Space Complexity - O(n)

SAMPLE INPUT AND OUTPUT

SAMPLE I
Enter a number: 4
The number of derangements in a set of 4 numbers is 9.

SAMPLE II
Enter a number: 16
The number of derangements in a set of 16 numbers is 7697064251745.
"""


function vandermonde_matrix(points)
    N = length(points)
    m = hcat([points .^ i for i = 0:N-1]...)
    return m
end

function lagrange_polynomial_coefficients(points)
    matrix = vandermonde_matrix(points)
    return inv(matrix)
end

function polynomial_from_coefficients(x, coeffs)
    N = length(coeffs)
    return sum([coeffs[i+1] * x^i for i = 0:N-1])
end

function lagrange_polynomials(x, interpolation_points)
    N = length(interpolation_points)
    coeffs = lagrange_polynomial_coefficients(interpolation_points)
    return [polynomial_from_coefficients(x, coeffs[:, i]) for i = 1:N]
end


using Plots
using DifferentialEquations

function eulers_method(f::Function, α::Real, a::Real, b::Real, N::Int64)

    n1 = N + 1
    u = zeros(n1,2)

    h = (b - a) / N
    u[1,1] = a
    u[1,2] = α

    for i in 2:n1
        u[i,2] = u[i-1,2] + h * f(u[i-1,1], u[i-1,2])
        u[i,1] = a + (i - 1) * h
    end

    return u
end

function rk4_method(f::Function, α::Real, a::Real, b::Real, N::Int64)

    n1 = N + 1
    u = zeros(n1,2)

    h = (b - a) / N
    u[1,1] = a
    u[1,2] = α

    for i in 2:n1
        t = u[i-1,1]
        w = u[i-1,2]

        k1 = h * f(t, w)
        k2 = h * f(t + h / 2, w + k1 / 2)
        k3 = h * f(t + h / 2, w + k2 / 2)
        k4 = h * f(t + h, w + k3)

        u[i,2] = w + (k1 + 2 * k2 + 2 * k3 + k4) / 6
        u[i,1] = a + (i - 1) * h
    end

    return u
end

function main()

    # one_ode()
    system_ode()

end

function one_ode()

    df(t,y) = y - t^2 + 1
    f(t) = -0.5*exp(t) + t^2 + 2t + 1

    u0 = 0.5
    a = 0
    b = 2
    n = 10
    tlin = 0:0.2:2

    sol1 = eulers_method(df, u0, a, b, n)
    sol2 = rk4_method(df, u0, a, b, n)

    # DE library
    df(u,p,t) = u - t^2 + 1
    tspan = (0., 2.)
    prob = ODEProblem(df, u0, tspan)
    sol3 = solve(prob)

    plot(tlin, f.(tlin), label="exact", legend=:bottomright, dpi=150)

    plot!(sol3.t, sol3.u, markershape=:x, label="DE Lib")

    plot!(sol2[:,1], sol2[:,2], markershape=:+, label="rk4")
    plot!(sol1[:,1], sol1[:,2], markershape=:o, label="eulers")

end

function system_ode()

    # du - output solutions
    # u  - input points
    # p  - parameters
    # t  - time
    function df(du, u, p, t)

        du[1] = -4u[2] + cos(t)
        du[2] = u[1]

    end

    u0 = [0, 0]
    tspan = (0, 1.0)
    prob = ODEProblem(df, u0, tspan)
    sol = solve(prob)

    # plot(sol, label=["x" "y"], dpi=150)
    plot(sol, vars=(1,2), label="f(x,y,t)", dpi=150)
end

main()


"""
Julia program to check if a number is a pronic number
Pronic Numbers are those numbers that can be arranged to form a rectangle.
It is a number which is the product of two consecutive integer
"""

function check_pronic(x)
    i = 0
    while(i <= isqrt(x))
        if(x == i * (i + 1))
            return true
        end
        i = i + 1
    end
    return false
end

print("Enter the number: ")
n = readline()
n = parse(Int, n)
res = check_pronic(abs(n))
if(res)
    print("The given number $n is a pronic number.")
else
    print("The given number $n is not a pronic number.")
end

"""
Time Complexity: O(log(n)), where 'n' is the given number
Space Complexity: O(1)

SAMPLE INPUT AND OUTPUT

SAMPLE 1

Enter the number: 154
The given number 154 is not a pronic number.

SAMPLE 2
Enter the number: 72
The given number 72 is a pronic number.
"""


"""
    pwm(x::Vector{<:Real},p::Int,r::Int,s::Int)

Compute the empirical probability weighted moments.

The probability weighted moments are defined by [Landwehr *et al. (1979)](https://agupubs.onlinelibrary.wiley.com/doi/abs/10.1029/WR015i005p01055)
as follows:
```math
M_{p,r,s} = \\mathbb{E}\\left[ X^p F^r(X) \\left\\{ 1-F(X) \\right\\}^s  \\right].
```
The unbiased empirical estimates is computed using the formula given by [Landwehr *et al. (1979)](https://agupubs.onlinelibrary.wiley.com/doi/abs/10.1029/WR015i005p01055).

# Reference

Landwehr, J. M., Matalas, N. C. and Wallis, J. R. (1979). Probability weighted moments compared with
    some traditional techniques in estimating Gumbel parameters and quantiles. *Water Resources Research*,
    15:1055–1064.

"""
function pwm(x::Vector{<:Real},p::Int,r::Int,s::Int)::Real

 @assert sign(p)>=0 "p should be a non-negative integer."
 @assert sign(r)>=0 "r should be a non-negative integer."
 @assert sign(s)>=0 "s should be a non-negative integer."

 y = sort(x)
 n = length(y)

 m = 1/n*sum( y[i]^p * binomial(i-1,r)/binomial(n-1,r) * binomial(n-i,s)/binomial(n-1,s) for i=1:n )

 return m

end

include(joinpath("probabilityweightedmoment", "probabilityweightedmoment_gev.jl"))
include(joinpath("probabilityweightedmoment", "probabilityweightedmoment_gp.jl"))
include(joinpath("probabilityweightedmoment", "probabilityweightedmoment_gumbel.jl"))


"""
    logistic_key(x_init, r, num_keys)

Generates a vector of pseudo-random keys using the Logistic Map.

The equation -

\$x_{n+1} = r * x_{n} * (1 - x_{n})\$

# Arguments
- `x_init::Float64`: Initial value of x. x ϵ (0, 1).
- `r::Float64`: A constant value. Values > 4 usually results in pseudo-random numbers.
- `num_keys::Int64`: Number of keys to be generated.

# Returns
- `keys::Vector{Int64}:`: Generated pseudo-random keys.

# Example
```jldoctest
julia> using ChaoticEncryption

julia> logistic_key(0.01, 3.97, 20)
20-element Vector{Int64}:
   0
  44
   7
  26
  14
 224
  16
 250
 162
 211
 200
 217
  97
 132
 134
 100
 135
 232
 122
 102
```
"""
function logistic_key(x_init::Float64, r::Float64, num_keys::Int64)
    keys = Vector{Int64}()
    x = x_init

    for i = 1:num_keys
        x = r * x * (1 - x)
        key = x * 10 ^ 16 % 256
        push!(keys, trunc(Int, key))
    end

    return keys
end


"""
    calculateMassMu!(m, g, m_j, mu, mu_sum, m_sum)

Given the gravitational constant and the mass of each body, calculates sums of masses, standard gravitational parameters of the bodies and jacobian masses

```math
mu_{sum_{i}} = \\sum_{j = 1}^{i} m_{i} \\hspace{0.5cm} mu = G \\cdot m_{i} \\hspace{0.5cm} mu_{sum} = G \\cdot m_{sum_{i}} \\hspace{0.5cm} m' = m_{i} \\frac{m_{sum_{i-1}}}{m_{sum_{i}}}
```

# Args

* `m`: array of N elements, each containing the mass of each body.
* `g`: gravitational constant.
* `m_j`: array of N elements. Each element will contain the jacobi mass ``m'`` of a body, as output.
* `mu`: array of N elements. Each element will contain the standard gravitational parameter of a body, as output.
* `mu_sum`: array of N elements, where the standard gravitational parameter of the bodies from 1 to i will be saved saved in the i-th element, as output.
* `m_sum`: array of N elements, where the element i contains the mass of the bodies from 1 to i, as output.
"""
function calculateMassMu!(m, g, m_j, mu, mu_sum, m_sum)

    m_sum[1] = m[1]
    mu_sum[1] = m[1] * g
    mu[1] = m[1] * g

    for i in 2:length(m)
        mu[i] = m[i] * g
        m_sum[i] = m_sum[i-1] + m[i]
        mu_sum[i] = m_sum[i] * g
        m_j[i] = m[i] * m_sum[i-1] / m_sum[i]
    end
    m_j[1] = m_sum[length(m)]

end


module FunctionZeros
import SpecialFunctions
import Roots

export besselj_zero, bessely_zero

besselj_zero_asymptotic(nu, n) = bessel_zero_asymptotic(nu, n, 1)

"""
    bessel_zero_asymptotic(nu, n, kind=1)

Asymptotic formula for the `n`th zero of the the Bessel J (Y) function of order `nu`.
`kind == 1 (2)` for Bessel function of the first (second) kind, J (Y).
"""
function bessel_zero_asymptotic(nu_in::Real, n::Integer, kind=1)
    nu = abs(nu_in)
    if kind == 1
        beta = MathConstants.pi * (n + nu / 2 - 1//4)
    else # kind == 2
        beta = MathConstants.pi * (n + nu / 2 - 3//4)
    end
    delta = 8 * beta
    mu = 4 * nu^2
    mup2 = mu * mu
    mup3 = mup2 * mu
    deltap2 = delta * delta
    deltap3 = deltap2 * delta
    deltap4 = deltap2 * deltap2
    deltap6 = deltap3 * deltap3
    t1 = 1
    t2 = 4 * (7 * mu - 31) / (3 * deltap2)
    t3 = 32 * (83 * mup2 - 982 * mu + 3779) / (15 * deltap4)
    t4 = 64 * (6949 * mup3 - 153855 * mup2 + 1585743 * mu - 6277237) /
        (105 * deltap6)
    zero_asymp = beta - ((mu - 1) / delta) * (t1 + t2 + t3 + t4)
    return zero_asymp
end

# Use the asymptotic values as starting values.
# These find the correct zeros even for n = 1,2,...
# Order 0 is 6 times slower and 50-100 times less accurate
# than higher orders, with other parameters constant.
"""
    besselj_zero(nu, n; order=2)

`n`th zero of the Bessel J function of order `nu`,
for `n` = `1,2,...`.

`order` is passed to the function `Roots.fzero`.
"""
besselj_zero(nu, n; order=2) = Roots.fzero((x) -> SpecialFunctions.besselj(nu, x),
                                           bessel_zero_asymptotic(nu, n, 1); order=order)

"""
    bessely_zero(nu, n; order=2)

`n`th zero of the Bessel Y function of order `nu`,
for `n` = `1,2,...`.

`order` is passed to the function `Roots.fzero`.
"""
bessely_zero(nu, n; order=2) = Roots.fzero((x) -> SpecialFunctions.bessely(nu, x),
                                           bessel_zero_asymptotic(nu, n, 2); order=order)

end # module FunctionZeros


using StatsBase, Distributions, Plots; pyplot()

function rouletteSpins(p)
    x = 0
    while true
        x += 1
        if rand() < p
            return x
        end
    end
end

p, xGrid, N = 18/37, 1:7, 10^6

mcEstimate = counts([rouletteSpins(p) for _ in 1:N],xGrid)/N

gDist = Geometric(p)
gPmf = [pdf(gDist,x-1) for x in xGrid]

plot(xGrid, mcEstimate, line=:stem, marker=:circle,
	c=:blue, ms=10, msw=0, lw=4, label="MC estimate")
plot!( xGrid, gPmf, line=:stem, marker=:xcross,
	c=:red, ms=6, msw=0, lw=2, label="PMF",
	ylims=(0,0.5), xlabel="x", ylabel="Probability")


###############################################################################
#
# Integral functions
#
###############################################################################
"""
Function `numerical∫` calculates the integral numerically. The supported methods are

$(METHODLIST)

"""
function numerical∫ end

"""
When two arrays (one for f(x) and one for Δx) are provided, `numerical∫` computes the sum of the mutiplication of the two using matrix mutiplication
    `fxs' * Δxs`.

    numerical∫(f::Array{FT,1}, Δx::Array{FT,1}) where {FT<:AbstractFloat}

Intergal of given
- `f` f(x) for each x
- `Δx` Δx for each x

Note that f and Δx may have different dimensions, and if so a warning will display.

---
Examples
```julia
FT = Float32;
f_sum = numerical∫(FT[1,2,3,4], FT[0.1,0.1,0.2,0.3]);
```
"""
numerical∫(f::Array{FT,1}, Δx::Array{FT,1}) where {FT<:AbstractFloat} =
(
    if length(Δx) == length(f)
        return f' * Δx
    else
        @warn twarn("Dimensions not matching, use the matching parts only...");
        N = min(length(f), length(Δx));
        return adjoint(view(f,1:N)) * view(Δx,1:N)
    end
)

"""
The above methods is useful for both evenly and non-evenly distributed `Δx`. However, for many cases `Δx` is evenly distributed and provided as a number
    rather than an array. In this scenario, a special method is given:

    numerical∫(f::Array{FT,1}, Δx::FT) where {FT<:AbstractFloat}

Intergal of given
- `f` f(x) for each x
- `Δx` Δx for x

---
Examples
```julia
FT = Float32;
f_sum = numerical∫(FT[1,2,3,4], FT(0.1));
```
"""
numerical∫(f::Array{FT,1}, Δx::FT) where {FT<:AbstractFloat} =
(
    return sum(f) * abs(Δx)
)

"""
We also provide a function to manually solve for the integral of a given function for a given range of x. The method is

    numerical∫(f::Function,
               x_min::FT,
               x_max::FT,
               n::Int
    ) where {FT<:AbstractFloat}

Intergal of given
- `f` A function
- `x_min` Minimum limit of x
- `x_max` Maximum limit of x
- `n` Number of points in the x range (evenly stepped)

---
Examples
```julia
FT = Float32;
func(x) = x^2;
f_sum = numerical∫(func, FT(0), FT(2), 20);
```
"""
numerical∫(f::Function,
           x_min::FT,
           x_max::FT,
           n::Int
) where {FT<:AbstractFloat} =
(
    _sum = 0;
    _dx  = (x_max - x_min) / n;
    for _i in 1:n
        _x = x_min + FT(_i-0.5) * _dx;
        _sum += f(_x);
    end;

    return _sum * _dx;
)

"""
This method automatically computes the integral of a function for an `x` within a range:

    numerical∫(f::Function,
               x_min::FT,
               x_max::FT,
               x_tol::FT = sqrt(eps(FT)),
               y_tol::FT = sqrt(eps(FT))
    ) where {FT<:AbstractFloat}

Intergal of given
- `f` A function
- `x_min` Minimum limit of x
- `x_max` Maximum limit of x
- `x_tol` Tolerance of Δx (x/N)
- `y_tol` Tolerance of the integral solution

---
Example
```julia
FT = Float32;
func(x) = x^2;
f_sum = numerical∫(func, FT(0), FT(2));
f_sum = numerical∫(func, FT(0), FT(2), FT(1e-3), FT(1e-3));
```
"""
numerical∫(f::Function,
           x_min::FT,
           x_max::FT,
           x_tol::FT = sqrt(eps(FT)),
           y_tol::FT = sqrt(eps(FT))
) where {FT<:AbstractFloat} =
(
    @assert y_tol > 0;

    # _sum_0: sum before halfing steps (_N), _sum_N: sum after halfing steps
    _sum_0::FT = (f(x_min) + f(x_max)) / 2;
    _sum_N::FT = 0;
    _N = 1;

    # continue the steps till the tolerances are reached
    while true
        _sum_N = (numerical∫(f, x_min, x_max, _N) + _sum_0) / 2;
        if abs(_sum_N - _sum_0) < y_tol || 1/_N < x_tol
            break;
        end;
        _sum_0 = _sum_N;
        _N *= 2;
    end;

    return _sum_N
)


# quadratic function

export Quadratic

### ABSTRACT TYPE

abstract type Quadratic <: ProximableFunction end

is_convex(f::Quadratic) = true
is_smooth(f::Quadratic) = true
is_quadratic(f::Quadratic) = true

fun_name(f::Quadratic) = "Quadratic function"

### CONSTRUCTORS

"""
**Quadratic function**

    Quadratic(Q, q; iterative=false)

For a matrix `Q` (dense or sparse, symmetric and positive semidefinite) and a vector `q`, returns the function
```math
f(x) = \\tfrac{1}{2}\\langle Qx, x\\rangle + \\langle q, x \\rangle.
```
By default, a direct method (based on Cholesky factorization) is used to evaluate `prox!`.
If `iterative=true`, then `prox!` is evaluated approximately using an iterative method instead.
"""
function Quadratic(Q::M, q::V; iterative=false) where {M, V}
    if iterative == false
        QuadraticDirect(Q, q)
    else
        QuadraticIterative(Q, q)
    end
end

### INCLUDE CONCRETE TYPES

include("quadraticDirect.jl")
include("quadraticIterative.jl")


# # Basic Usage

using Convex
using LinearAlgebra
if VERSION < v"1.2.0-DEV.0"
    (I::UniformScaling)(n::Integer) = Diagonal(fill(I.λ, n))
end

using SCS
## passing in verbose=0 to hide output from SCS
solver = SCSSolver(verbose=0)

# ### Linear program
#
# $$
# \begin{array}{ll}
#   \mbox{maximize} & c^T x \\
#   \mbox{subject to} & A x \leq b\\
#   & x \geq 1 \\
#   & x \leq 10 \\
#   & x_2 \leq 5 \\
#   & x_1 + x_4 - x_2 \leq 10 \\
# \end{array}
# $$
#

x = Variable(4)
c = [1; 2; 3; 4]
A = I(4)
b = [10; 10; 10; 10]
p = minimize(dot(c, x)) # or c' * x
p.constraints += A * x <= b
p.constraints += [x >= 1; x <= 10; x[2] <= 5; x[1] + x[4] - x[2] <= 10]
solve!(p, solver)

println(round(p.optval, digits=2))
println(round.(x.value, digits=2))
println(evaluate(x[1] + x[4] - x[2]))

# ### Matrix Variables and promotions
#
# $$
# \begin{array}{ll}
#   \mbox{minimize} & \| X \|_F + y \\
#   \mbox{subject to} & 2 X \leq 1\\
#   & X' + y \geq 1 \\
#   & X \geq 0 \\
#   & y \geq 0 \\
# \end{array}
# $$
#

X = Variable(2, 2)
y = Variable()
## X is a 2 x 2 variable, and y is scalar. X' + y promotes y to a 2 x 2 variable before adding them
p = minimize(norm(X) + y, 2 * X <= 1, X' + y >= 1, X >= 0, y >= 0)
solve!(p, solver)
println(round.(X.value, digits=2))
println(y.value)
p.optval

# ### Norm, exponential and geometric mean
#
# $$
# \begin{array}{ll}
#   \mbox{satisfy} & \| x \|_2 \leq 100 \\
#   & e^{x_1} \leq 5 \\
#   & x_2 \geq 7 \\
#   & \sqrt{x_3 x_4} \geq x_2
# \end{array}
# $$
#

x = Variable(4)
p = satisfy(norm(x) <= 100, exp(x[1]) <= 5, x[2] >= 7, geomean(x[3], x[4]) >= x[2])
solve!(p, SCSSolver(verbose=0))
println(p.status)
x.value

# ### SDP cone and Eigenvalues

y = Semidefinite(2)
p = maximize(lambdamin(y), tr(y)<=6)
solve!(p, SCSSolver(verbose=0))
p.optval

#-

x = Variable()
y = Variable((2, 2))
## SDP constraints
p = minimize(x + y[1, 1], isposdef(y), x >= 1, y[2, 1] == 1)
solve!(p, solver)
y.value

# ### Mixed integer program
#
# $$
# \begin{array}{ll}
#   \mbox{minimize} & \sum_{i=1}^n x_i \\
#     \mbox{subject to} & x \in \mathbb{Z}^n \\
#   & x \geq 0.5 \\
# \end{array}
# $$
#

using GLPKMathProgInterface
x = Variable(4, :Int)
p = minimize(sum(x), x >= 0.5)
solve!(p, GLPKSolverMIP())
x.value

#-


export diric

"""
    kernel = diric(Ω::Real, n::Integer)

Dirichlet kernel, also known as periodic sinc function,
where `n` should be a positive integer.
Returns `sin(Ω * n/2) / (n * sin(Ω / 2))` typically,
but `±1` when `Ω` is a multiple of 2π.

In the usual case where 'n' is odd, the output is equivalent to
``1/n \\sum_{k=-(n-1)/2}^{(n-1)/2} e^{i k Ω}``,
which is the discrete-time Fourier transform (DTFT)
of a `n`-point moving average filter.

When `n` is odd or even, the function is 2π or 4π periodic, respectively.
The formula for general `n` is
`diric(Ω,n) = ``e^{-i (n-1) Ω/2}/n \\sum_{k=0}^{n-1} e^{i k Ω}``,
which is a real and symmetric function of `Ω`.

As of 2021-03-19, the Wikipedia definition has different factors.
The definition here is consistent with scipy and other software frameworks.

# Examples

```jldoctest
julia> round.(diric.((-2:0.5:2)*π, 5), digits=9)'
1×9 adjoint(::Vector{Float64}) with eltype Float64:
 1.0  -0.2  0.2  -0.2  1.0  -0.2  0.2  -0.2  1.0

julia> diric(0, 4)
1.0
```
"""
function diric(Ω::T, n::Integer) where T <: AbstractFloat
    n > 0 || throw(ArgumentError("n=$n not positive"))
    sign = one(T)
    if isodd(n)
        Ω = rem2pi(Ω, RoundNearest) # [-π,π)
    else # wrap to interval [-π,π) to improve precision near ±2π
        Ω = 2 * rem2pi(Ω/2, RoundNearest) # [-2π,2π)
        if Ω > π # [π,2π)
            sign = -one(T)
            Ω -= T(2π) # [-π,0)
        elseif Ω < -π # [-2π,-π)
            sign = -one(T)
            Ω += T(2π) # [0,π)
        end
    end

    denom = sin(Ω / 2)
    atol = eps(T)
    if abs(denom) ≤ atol # denom ≈ 0 ?
        return sign
    end

    return sign * sin(Ω * n/2) / (n * denom) # typical case
end

# handle non AbstractFloat types (e.g., Int, Rational, π)
diric(Ω::Real, n::Integer) = diric(float(Ω), n)


function chebft(func::Function; a=-1.0, b=1.0, n=3)
    f = Array(Float64, n)
    c = Array(Float64, n)
    bma = 0.5*(b-a)
    bpa = 0.5*(b+a)
    for k=1:n
        y = cos(pi*(k-0.5)/n)
        f[k] = func(y*bma+bpa)
    end
    fac = 2.0/n
    for j=1:n
        sum = 0.0
        for k=1:n
            sum += f[k]*cos(pi*(j-1)*(k-0.5)/n)
        end
        c[j] = fac*sum
    end
    return c
end

function chebev(c::Vector{Float64}, x::Float64; a=-1.0, b=1.0, n=length(c))
    d = dd = 0.0
    (x-a)*(x-b) > 0 && warn("x not in range")
    y2=2.0*(y=(2.0*x-a-b)/(b-a))
    for j=n:-1:2
        sv=d
        d=y2*d-dd+c[j]
        dd=sv
    end
    return y*d-dd+0.5*c[1]
end

function chder(c::Vector{Float64}; a=-1.0, b=1.0, n=length(c))
    cder = similar(c)
    cder[n] = 0.0
    n>1 && (cder[n-1] = 2*(n-1)*c[n])
    for j=(n-2):-1:1
        cder[j] = cder[j+2]+2*j*c[j+1]
    end
    con=2.0/(b-a)
    scale!(con, cder)
end

ftmp(x)=3*x^2+5*x+8
fder(x)=6*x+5

function chebtest(f;n=5,eps=exp(-1),a=-eps,b=eps)
    f0 = chebft(f;n=n,a=a,b=b)
    f1 = chder(f0;n=n,a=a,b=b)
    f2 = chder(f1;n=n,a=a,b=b)
    y0 = chebev(f0,0.0;n=n,a=a,b=b)
    y1 = chebev(f1,0.0;n=n,a=a,b=b)
    y2 = chebev(f2,0.0;n=n,a=a,b=b)
    dx = y2>0 ? -y1/y2 : -y1/0
    df = y2>0 ? -y1*y1/y2 : -Inf
    (:err,y0-f(0),:f1,y1,:f2,y2,:dx,dx,:df,df)
end

function chebtest2(f;n=5,eps=exp(-1))
    f0 = chebft(f;n=n,a=-eps,b=eps)
    @show y0 = chebev(f0,0.0;n=n,a=-eps,b=eps)
    f1 = chder(f0;n=n,a=-eps,b=eps)
    @show y1 = chebev(f1,0.0;n=n,a=-eps,b=eps)
    @show eps = 0.1*abs(y0/y1)
    f0 = chebft(f;n=n,a=-eps,b=eps)
    @show y0 = chebev(f0,0.0;n=n,a=-eps,b=eps)
    f1 = chder(f0;n=n,a=-eps,b=eps)
    @show y1 = chebev(f1,0.0;n=n,a=-eps,b=eps)
    f2 = chder(f1;n=n,a=-eps,b=eps)
    @show y2 = chebev(f2,0.0;n=n,a=-eps,b=eps)
    (:err,y0-f(0),y1,y2)
end


using Plots
using Random

#=
Generates a one-dimensional random walk starting at state S0  with s steps
and probability p of moving right.
Inputs:
- S0: An integer representing the starting state of the chain.
- p: A number between 0 and 1 representing the probability of the particle
moving to the right.
- s: The number of steps to run the walk for.
=#
function one_d_walk(S0, p, s)

    # Array to save the position of the particle
    walk = zeros(s+1)
    walk[1] = S0

    for i = 2:(s+1)
        U = rand()
        Xi = 1
        if U > p
            Xi = -1
        end
        walk[i] = walk[i-1] + Xi
    end

    return walk
end

#=
Generates a two-dimensional random walk starting at state S0  with s steps
and probability pR of moving right and probability pU of moving up.
Inputs:
- S0: An integer representing the starting state of the chain.
- pL: A number between 0 and 1 representing the probability of the particle
moving to the left.
- pR: A number between 0 and 1 representing the probability of the particle
moving to the right.
- pU: A number between 0 and 1 representing the probability of the particle
moving up.
- pD: A number between 0 and 1 representing the probability of the particle
moving down.
- s: The number of steps to run the walk for.
=#
function two_d_walk(S0, pL, pR, pU, pD, s)

    # Matrix to save the position of the particle
    walk = zeros(s+1,2)
    walk[1,:] = S0

    # Distribution
    cumProb = cumsum([pR, pL, pU, pD])
    # Possible moves in each column
    movesMatrix = [1 0; -1 0; 0 1; 0 -1]

    for i = 2:(s+1)
        U = rand()
        step = [0,0]
        j = 1
        while step == [0,0]
            if U < cumProb[j]
                step = movesMatrix[j,:]
            end
            j += 1
        end
        walk[i,:] = walk[i-1,:] + step
    end

    return walk
end

# Number of steps in walk
n = 100

# Generate and plot a symmetric 1-d walks
plot(0:n, one_d_walk(0, .5, n), legend = false, grid = false,
    bg = RGB(247/255, 236/255, 226/255),
    title = "Symmetric 1-d Random Walk")
plot!(0:n, one_d_walk(0, .5, n), legend = false)
plot!(0:n, one_d_walk(0, .5, n), legend = false)
plot!(0:n, one_d_walk(0, .5, n), legend = false)
plot!(0:n, one_d_walk(0, .5, n), legend = false)
plot!(0:n, zeros(n+1), legend = false, )

# Generate and plot a symmetric 2-d walks
walk2d = two_d_walk([0,0], .25, .25, .25, .25,  3000)
plot(walk2d[:,1], walk2d[:,2], legend = false, grid = false,
    bg = RGB(247/255, 236/255, 226/255))


 # function to calculate the distance between two points according to the minimal-distance convention

 function pbcseparation(x::Vector{Float64},y::Vector{Float64}, L)
   d = 0.
   for i in 1:2
     dx = (y[i] - x[i])%L       # difference between y and x
     if dx > L / 2
       dx = dx - L
     elseif dx < -L/2
       dx = dx + L
     end
     d = d + dx^2
   end
   return sqrt(d)
 end

 function pbcseparation(x::Float64,y::Float64,L)
   dx = (y - x)%L
   if dx > L / 2
     dx = dx - L
   elseif dx < -L/2
     dx = dx + L
   end

   return sqrt(dx^2)
 end

export pbcseparation


abstract type Univariate end

struct UnivariateCalibration <: Univariate
    Slope::Float64
    Offset::Float64
    Slope_Uncertainty::Float64
    Offset_Uncertainty::Float64
    Rsq::Float64
    Fstatistic::Float64
    DOF::Int
end

"""
    UnivariateCalibration( Y, X )
    Performs a univariate least squares regression with a bias/offset term(`y = mx + b`).
Returns a Univariate Calibration object containing uncertainty in parameters, and other statistics.
"""
function UnivariateCalibration( Y, X )
    len = length( X )
    @assert(len == length(Y))
    μ_x, μ_y    = sum( X ) / len                , sum( Y ) / len
    SSxx, SSyy  = sum( (X .^ 2) .- (μ_x ^ 2) )  , sum( (Y .^ 2) .- (μ_y ^ 2) )
    SSxy = sum( (X .* Y) .- (μ_x * μ_y) )

    m = SSxy / SSxx
    b = μ_y - ( m * μ_x )

    Y_hat = ( m .* X ) .+ b
    SSe = sum( (Y .- Y_hat) .^ 2 )
    SSr = SSyy - SSe

    Syx = sqrt(SSe / ( len - 2 ))
    Sm = Syx / sqrt( sum( (X .- μ_x) .^ 2 ))
    Sb = Syx * sqrt( sum( X .^ 2 ) / ( len * SSxx ) )

    Rsq = SSr / SSyy
    Fstat = ( SSxx - SSe ) / Syx

    return UnivariateCalibration( m, b, Sm, Sb, Rsq, Fstat, len - 2 )
end

(M::UnivariateCalibration)(X) = (X*M.Slope) + M.Offset

struct StandardAddition
    Unknown::Float64
    Slope::Float64
    Offset::Float64
    Unknown_Uncertainty::Float64
    Slope_Uncertainty::Float64
    Offset_Uncertainty::Float64
    Rsq::Float64
    DOF::Int
end

"""
    StandardAddition( Signal, Spike )

    Performs a univariate standard addition calibration. Returns a StandardAddition
object.

"""
function StandardAddition( Signal, Spike )
    len = length( Signal )
    DOF = len - 2
    @assert(len == length(Spike))
    mu_x, mu_y = sum( Spike ) / len             , sum( Signal ) / len
    SSxx, SSyy = sum( ( Spike .- mu_x ) .^ 2 )  , sum( ( Signal .- mu_y ) .^ 2 )
    SSxy = sum( ( Spike .- mu_x ) .* ( Signal .- mu_y ) )

    m = SSxy / SSxx
    #y = mx + b -> y - mx =  b
    b = mu_y - ( m * mu_x )
    Y_hat = ( m * Spike ) .+ b
    #Find X - intercept...
    x0 = -b / m #y = 0 = mx + b => -b / m = x

    Sy = sqrt( sum( (Signal .- Y_hat) .^ 2 ) / DOF )
    Sx = (Sy / abs(m)) * sqrt( (1/len) * ( mu_y^2 / ( m^2 * sum( ( Spike .- mu_x ) .^ 2 )  ) ) )

    SSe = sum( (Signal .- Y_hat) .^ 2 )
    SSr = SSyy - SSe

    Syx = SSe / DOF
    Sm = Syx / SSxx
    Sb = Syx * ( ( 1 / len ) + ( ( mu_x ^ 2 ) / SSxx ) )

    Rsq = SSr / SSyy
    return StandardAddition( x0, m, b, Sy, Sm, Sb, Rsq, DOF )
end

"""
    Confidence_Offset( UC::Univariate; Significance = 0.05 )
Returns a tuple of (-, +) of the estimated offset/bias's confidence interval from a `UC` Univariate type object.
"""
function Confidence_Offset( UC::Univariate; Significance = 0.05 )
    if UC.DOF < 31
        extent = UC.Offset_Uncertainty * -quantile( TDist( UC.DOF ), Significance / 2 )
    else
        extent = UC.Offset_Uncertainty * -quantile( Normal(), Significance / 2 )
    end
    return ( UC.Offset - extent, UC.Offset + extent )
end

"""
    Confidence_Slope( UC::Univariate; Significance = 0.05 )
Returns a tuple of (-, +) of the estimated slope's confidence interval from a `UC` Univariate type object.
"""
function Confidence_Slope( UC::Univariate; Significance = 0.05 )
    if UC.DOF < 31
        extent = UC.Slope_Uncertainty * -quantile( TDist( UC.DOF ), Significance / 2 )
    else
        extent = UC.Slope_Uncertainty * -quantile( Normal(), Significance / 2 )
    end
    return ( UC.Slope - extent, UC.Slope + extent )
end


using Random
Random.seed!(1)

function montyHall(switchPolicy)
    prize, choice = rand(1:3), rand(1:3)
    if prize == choice
        revealed = rand(setdiff(1:3,choice))
    else
        revealed = rand(setdiff(1:3,[prize,choice]))
    end

    if switchPolicy
        choice = setdiff(1:3,[revealed,choice])[1]
    end
    return choice == prize
end

N = 10^6
println("Success probability with policy I (stay): ",
	sum([montyHall(false) for _ in 1:N])/N)
println("Success probability with policy II (switch): ",
	sum([montyHall(true) for _ in 1:N])/N)


function ABCDQR_em(y,u,Ai,Bi,Ci,Di,Qi,Ri,m1i,P1i,max_iter::Int,tol::Float64,txo::Bool)
	#
	# estimate A, B, C, D, Q, R, m1, P1 using the EM algorithm for model
	#
	# x_{t+1} = A*x_{t} + B*u_{t} + w_{t}
	# y_{t}   = C*x_{t} + D*u_{t} + v_{t}
	#
	# cov(w_{t},v_{t}) = [Q 0;0 R]
	# x1 -> N(m1,P1)
	#
	# javier.cara@upm.es, 2016-02
	#

	# number of states
	nx = size(Ai,1)

	# data by rows
	y,ny,nt = byrow(y)
	u,nu,nt = byrow(u)

	# initial values
	A = Ai
	B = Bi
	C = Ci
	D = Di
	Q = Qi
	R = Ri
	m1 = m1i
	P1 = P1i

	# log-likelihood values
	loglikv = zeros(max_iter)

	# Syy, Suu and Syu do not depend on the iterations
	Syy = zeros(ny,ny)
	Suu = zeros(nu,nu)
	Syu = zeros(ny,nu)
	for t in 1:nt
		Syy = Syy + y[:,t]*y[:,t]'
		Suu = Suu + u[:,t]*u[:,t]'
		Syu = Syu + y[:,t]*u[:,t]'
	end

	tol1 = 1.0
	iter = 1
	while (iter <= max_iter) && (tol1 > tol)
		tic()

		# E-step
		# ---------------------------------------------------------------------------------
		# Kalmanfilter
		(xtt,Ptt,xtt1,Ptt1,et,St,Kt,loglik) = ABCDQR_kfilter(y,u,A,B,C,D,Q,R,m1,P1)
		(xtN,PtN,Pt1tN) = ACQR_ksmoother(A,xtt,Ptt,xtt1,Ptt1)

		loglikv[iter] = loglik
		if iter > 1
			tol1 = abs( (loglikv[iter] - loglikv[iter-1])/loglikv[iter-1] )
		end

		# initial values
		Sxx = zeros(nx,nx)
		Sx1x = zeros(nx,nx)
		Syx = zeros(ny,nx)
		Sx1u = zeros(nx,nu)
		Sxu = zeros(nx,nu)

		# matrices Sxx, Sx1x, Syx, Sx1u, Sxu, Sx1x1
		for t in 1:nt
			Sxx = Sxx + PtN[:,:,t] + xtN[:,t]*xtN[:,t]'
			Sx1x = Sx1x + Pt1tN[:,:,t] + xtN[:,t+1]*xtN[:,t]'
			Syx = Syx + y[:,t]*xtN[:,t]'
		  Sx1u= Sx1u + xtN[:,t+1]*u[:,t]'
		  Sxu = Sxu + xtN[:,t]*u[:,t]'
		end
		Sx1x1 = Sxx - (PtN[:,:,1] + xtN[:,1]*xtN[:,1]') + (PtN[:,:,nt+1] + xtN[:,nt+1]*xtN[:,nt+1]')

		# M-step
		# -------------------------------------------------------------------------------------
		# Matrices x0e y P0e
		m1 = xtN[:,1]
		P1 = PtN[:,:,1]

		# AB
		AB = [Sx1x Sx1u]/[Sxx Sxu;Sxu' Suu]

		# Matrix A
		A = AB[:,1:nx]

		# Matrix B
		B = AB[:,nx+1:nx+nu]

		# Matrix Q
		M1 = Sx1x*A'
		M2 = Sx1u*B'
		M3 = A*Sxu*B'
		Q = Sx1x1 - M1 - M1' - M2 - M2' + M3 + M3' + A*Sxx*A' + B*Suu*B'
		Q = 1/nt*Q
		Q = (Q + Q')/2 # to make sure it's a symmetric matrix

		# CD
		CD = [Syx Syu]/[Sxx Sxu;Sxu' Suu]

		# Matrix Ae
		C = CD[:,1:nx]

		# Matrix Be
		D = CD[:,nx+1:nx+nu]

		# Matrix Re
		M1 = Syx*C'
		M2 = Syu*D'
		M3 = C*Sxu*D'
		R = Syy - M1' - M1 - M2 - M2' + M3 + M3' + C*Sxx*C' + D*Suu*D'
		R = 1/nt*R
		R = (R + R')/2 # to make sure it's a symmetric matrix

		etime = toq()
		if txo
			println( "Iter " * @sprintf("%3d",iter) * ",   @time = " * @sprintf("%5.2f",etime) * ",   logLik = " * @sprintf("%.6E",loglik) *
			         ",   tol = ", @sprintf("%.2E",tol1) )
		end

		iter += 1

	end

	loglikv = loglikv[1:(iter-1)]

	# Akaike Information Criterion
	P = ny*nx + nx*nx + nx*(nx+1)/2 + ny*(ny+1)/2
	aic = -2*loglikv[end] + 2*P

	# output
	return A,B,C,D,Q,R,m1,P1,loglikv,aic

end


using StatsBase, Combinatorics, Plots ; pyplot()

matchExists1(n) = 1 - prod([k/365 for k in 365:-1:365-n+1])
matchExists2(n) = 1- factorial(365,365-big(n))/365^big(n)

function bdEvent(n)
    birthdays = rand(1:365,n)
    dayCounts = counts(birthdays, 1:365)
    return maximum(dayCounts) > 1
end

probEst(n) = sum([bdEvent(n) for _ in 1:N])/N

xGrid = 1:50
analyticSolution1 = [matchExists1(n) for n in xGrid]
analyticSolution2 = [matchExists2(n) for n in xGrid]
println("Maximum error: $(maximum(abs.(analyticSolution1 - analyticSolution2)))")

N = 10^3
mcEstimates = [probEst(n) for n in xGrid]

plot(xGrid, analyticSolution1, c=:blue, label="Analytic solution")
scatter!(xGrid, mcEstimates, c=:red, ms=6, msw=0, shape=:xcross,
	label="MC estimate", xlims=(0,50), ylims=(0, 1),
	xlabel="Number of people in room",
	ylabel="Probability of birthday match",
	legend=:topleft)


using StructuralIdentifiability
using ModelingToolkit

@parameters b c g
@variables t S(t) I(t) R(t) N(t)
N = S + I + R
D = Differential(t);

sir_eqs1 = [
  D(S) ~ -b*S*I,
  D(I) ~ b*S*I-g*I,
  D(R) ~ g*I
]
sir_ode1 = ODESystem(sir_eqs1, t, name=:SIR1)

sir_eqs2 = [
  D(S) ~ -b*c*S*I,
  D(I) ~ b*c*S*I-g*I,
  D(R) ~ g*I
]
sir_ode2 = ODESystem(sir_eqs2, t, name=:SIR2)

sir_eqs3 = [
  D(S) ~ -b*c*S*I/N,
  D(I) ~ b*c*S*I/N-g*I,
  D(R) ~ g*I
]
sir_ode3 = ODESystem(sir_eqs3, t, name=:SIR3)

@variables y(t)
measured_quantities1 = [y ~ b*S*I];

funcs_to_check1 = [b,g,b/g]
ident1 = assess_identifiability(sir_ode1; measured_quantities= measured_quantities1, funcs_to_check = funcs_to_check1)

funcs_to_check2 = [b,c,g,b*c]
measured_quantities2 = [y ~ b*c*S*I]
ident2 = assess_identifiability(sir_ode2; measured_quantities= measured_quantities2, funcs_to_check = funcs_to_check2)

funcs_to_check3 = [b,c,g,b*c]
measured_quantities3 = [y ~ b*c*S*I/N]
ident3 = assess_identifiability(sir_ode3; measured_quantities= measured_quantities3, funcs_to_check = funcs_to_check3)

@variables n(t)
measured_quantities3a = [y ~ b*c*S*I/N, n ~ N]
ident3a = assess_identifiability(sir_ode3; measured_quantities= measured_quantities3a, funcs_to_check = funcs_to_check3)

exp_eqs = [
  D(I) ~ b*S*I-g*I,
  D(R) ~ g*I
]
exp_ode = ODESystem(exp_eqs, t, name=:EXP)
assess_identifiability(exp_ode;
                       measured_quantities= [y ~ b*S*I],
                       funcs_to_check = [b,g])


#=
P33 (*) Determine whether two positive integer numbers are coprime.
Two numbers are coprime if their greatest common divisor equals 1.
Example:
* (coprime 35 64)
T
=#

# From P32 but more terse
function gcd(a, b)
    b == 0 ? a : gcd(b, mod(a, b))
end

function coprime(a, b)
    gcd(a, b) == 1
end

@assert coprime(35, 64)
@assert coprime(4, 9)

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


#=
   Meeting problem.

   (I'm not sure about the source. The page I got it from does not exist anymore.)
   """
   Let's say that 4 people agree to meet between 3:00 P.M. and 4:00 P.M..
   One man can wait for 10 minutes. Another can wait for 15 minutes.
   Another can wait for 20 Minutes, and another still can wait for 30
   minutes. What is the probability that all 4 will meet?
   """

   Summary Statistics
  parameters      mean       std   naive_se      mcse         ess      rhat   ess_per_sec
      Symbol   Float64   Float64    Float64   Float64     Float64   Float64       Float64

        prob    0.0836    0.2768     0.0028    0.0030   9775.6911    0.9999     7783.1935

    Cf meeting_under_the_clock,jl

=#
using Turing, StatsPlots, Distributions
include("jl_utils.jl")

@model function meeting_problem(w=[10,15,20,30])
    n = length(w)
    ps = tzeros(n)
    for i in 1:n
        ps[i] ~ DiscreteUniform(1,60)
    end
    # Will all people meet?
    prob ~ Dirac(reduce(intersect, [Set(ps[i]:ps[i]+w[i])  for i in 1:n]) != Set())
end

w = [10,15,20,30]
model = meeting_problem(w)

# chns = sample(model, Prior(), 10_000)
# chns = sample(model, MH(), 10_000)
# chns = sample(model, PG(5), 1_000)
chns = sample(model, SMC(), 10_000)
# chns = sample(model, IS(), 10_000)

display(chns[[:prob]])
# display(plot(chns))


### A Pluto.jl notebook ###
# v0.12.9

using Markdown
using InteractiveUtils

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

# ╔═╡ e75a9a06-22e5-11eb-2e36-8d4f62b830ed
begin
	@quickactivate "StatisticsWithJuliaPlutoNotebooks"
	using DataFrames, CSV, Statistics
end

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

# ╔═╡ fa68607e-22e5-11eb-0558-c9a4d9f77426
begin
	data = CSV.read(datadir("temperatures.csv"), copycols=true)
	brisT = data.Brisbane
	gcT = data.GoldCoast

	sigB = std(brisT)
	sigG = std(gcT)
	covBG = cov(brisT, gcT)

	meanVect = [mean(brisT) , mean(gcT)]
	covMat = [sigB^2  covBG
			  covBG   sigG^2]
end

# ╔═╡ 65fa5b06-246e-11eb-19b7-d77750e343fb
begin
	outfile = open(datadir("mvParams2.jl"), "w")
	write(outfile,"meanVect = $meanVect \ncovMat = $covMat")
	close(outfile)
	read(datadir("mvParams2.jl"), String)
end

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

# ╔═╡ Cell order:
# ╟─ca5fbdc8-22e5-11eb-0a60-cb0a127a3ca5
# ╠═e077ab16-22e5-11eb-15ed-5f0654bf7328
# ╠═e75a9a06-22e5-11eb-2e36-8d4f62b830ed
# ╠═fa68607e-22e5-11eb-0558-c9a4d9f77426
# ╠═65fa5b06-246e-11eb-19b7-d77750e343fb
# ╟─475ff888-22e6-11eb-2354-09f8f40a8e12


# ---
# cover: assets/swirl.gif
# title: Swirl effect using warp operation
# author: Johnny Chen
# date: 2021-07-12
# ---

# In this example, we illustrate how to construct a custom warping map
# and pass it to `warp`. This swirl example comes from
# [the Princeton Computer Graphics course for Image Warping (Fall 2000)](https://www.cs.princeton.edu/courses/archive/fall00/cs426/lectures/warp/warp.pdf)
# and [scikit-image swirl example](https://scikit-image.org/docs/dev/auto_examples/transform/plot_swirl.html).

using ImageTransformations
using OffsetArrays, StaticArrays
using ImageShow, TestImages
using LinearAlgebra

img = imresize(testimage("cameraman"), (256, 256));

# As we've illustrated in [image warping](@ref index_image_warping), a warp operation
# consists of two operations: backward coordinate map `ϕ` and intensity estimator.
# To implement swirl operation, we need to customize the coordinate map `ϕ`.
# A valid coordinate map `q = ϕ(p)` follows the following interface:
#
# ```julia
# # SVector comes from StaticArrays
# ϕ(::SVector{N})::SVector{N} where N
# ```
#
# A cartesian position `(x, y)` can be transfered to/from polar coordinate `(ρ, θ)`
# using formula:
#
# ```julia
# # Cartesian to Polar
# ρ = norm(y-y0, x-x0)
# θ = atan(y/x)
#
# # Polar to Cartesian
# y = y0 + ρ*sin(θ)
# x = x0 + ρ*cos(θ)
# ```
#
# For given input index `p`, a swirl operation enforces more rotations in its polar coordinate using
# `θ̃ = θ + ϕ + s*exp(-ρ/r)`, and returns the cartesian index (x̃, ỹ) from the warped polor coordinate
# (ρ, θ̃). (Here we use the formula from [scikit-image swirl example](https://scikit-image.org/docs/dev/auto_examples/transform/plot_swirl.html)
# to build our version.)

function swirl(rotation, strength, radius)
    x0 = OffsetArrays.center(img)
    r = log(2)*radius/5

    function swirl_map(x::SVector{N}) where N
        xd = x .- x0
        ρ = norm(xd)
        θ = atan(reverse(xd)...)

        ## Note that `x == x0 .+ ρ .* reverse(sincos(θ))`
        ## swirl adds more rotations to θ based on the distance to center point
        θ̃ = θ + rotation + strength * exp(-ρ/r)

        SVector{N}(x0 .+ ρ .* reverse(sincos(θ̃)))
    end

    warp(img, swirl_map, axes(img))
end

# Now let's see how radius argument affects the result

preview = ImageShow.gif([swirl(0, 10, radius) for radius in 10:10:150]; fps=5)

using FileIO #src
save("assets/swirl.gif", preview; fps=5) #src


doc"""
    Uniform(a,b)

The *continuous uniform distribution* over an interval $[a, b]$ has probability density function

$f(x; a, b) = \frac{1}{b - a}, \quad a \le x \le b$

```julia
Uniform()        # Uniform distribution over [0, 1]
Uniform(a, b)    # Uniform distribution over [a, b]

params(d)        # Get the parameters, i.e. (a, b)
minimum(d)       # Get the lower bound, i.e. a
maximum(d)       # Get the upper bound, i.e. b
location(d)      # Get the location parameter, i.e. a
scale(d)         # Get the scale parameter, i.e. b - a
```

External links

* [Uniform distribution (continuous) on Wikipedia](http://en.wikipedia.org/wiki/Uniform_distribution_(continuous))

"""
immutable Uniform <: ContinuousUnivariateDistribution
    a::Float64
    b::Float64

    Uniform(a::Real, b::Real) = (@check_args(Uniform, a < b); new(a, b))
    Uniform() = new(0.0, 1.0)
end

@distr_support Uniform d.a d.b

#### Parameters

params(d::Uniform) = (d.a, d.b)

location(d::Uniform) = d.a
scale(d::Uniform) = d.b - d.a

#### Statistics

mean(d::Uniform) = middle(d.a, d.b)
median(d::Uniform) = mean(d)
mode(d::Uniform) = mean(d)
modes(d::Uniform) = Float64[]

var(d::Uniform) = (w = d.b - d.a; w^2 / 12.0)

skewness(d::Uniform) = 0.0
kurtosis(d::Uniform) = -1.2

entropy(d::Uniform) = log(d.b - d.a)

#### Evaluation

pdf(d::Uniform, x::Float64) = insupport(d, x) ? 1.0 / (d.b - d.a) : 0.0
logpdf(d::Uniform, x::Float64) = insupport(d, x) ? -log(d.b - d.a) : -Inf

function cdf(d::Uniform, x::Float64)
    (a, b) = params(d)
    x <= a ? 0.0 :
    x >= d.b ? 1.0 : (x - a) / (b - a)
end

function ccdf(d::Uniform, x::Float64)
    (a, b) = params(d)
    x <= a ? 1.0 :
    x >= d.b ? 0.0 : (b - x) / (b - a)
end

quantile(d::Uniform, p::Float64) = d.a + p * (d.b - d.a)
cquantile(d::Uniform, p::Float64) = d.b + p * (d.a - d.b)

function mgf(d::Uniform, t::Real)
    (a, b) = params(d)
    u = 0.5 * (b - a) * t
    u == zero(u) && return one(u)
    v = 0.5 * (a + b) *t
    exp(v) * (sinh(u) / u)
end

function cf(d::Uniform, t::Real)
    (a, b) = params(d)
    u = 0.5 * (b - a) * t
    u == zero(u) && return complex(one(u))
    v = 0.5 * (a + b) * t
    cis(v) * (sin(u) / u)
end

#### Evaluation

rand(d::Uniform) = d.a + (d.b - d.a) * rand()

#### Fitting

function fit_mle{T<:Real}(::Type{Uniform}, x::AbstractArray{T})
    if isempty(x)
        throw(ArgumentError("x cannot be empty."))
    end

    xmin = xmax = x[1]
    for i = 2:length(x)
        xi = x[i]
        if xi < xmin
            xmin = xi
        elseif xi > xmax
            xmax = xi
        end
    end

    Uniform(xmin, xmax)
end


function gaussian_elimination(A::Array{Float64,2})

    rows = size(A,1)
    cols = size(A,2)

    # Main loop going through all columns
    for k = 1:min(rows,cols)

        # Step 1: finding the maximum element for each column
        max_index = indmax(abs.(A[k:end,k])) + k-1

        # Check to make sure matrix is good!
        if (A[max_index, k] == 0)
            println("matrix is singular! End!")
            exit(0)
        end

        # Step 2: swap row with highest value for that column to the top
        temp_vector = A[max_index, :]
        A[max_index, :] = A[k, :]
        A[k, :] = temp_vector
        #println(A)

        # Loop for all remaining rows
        for i = (k+1):rows

            # Step 3: finding fraction
            fraction = A[i,k]/A[k,k]

            # loop through all columns for that row
            for j = (k+1):cols

                 # Step 4: re-evaluate each element
                 A[i,j] -= A[k,j]*fraction

            end

            # Step 5: Set lower elements to 0
            A[i,k] = 0
        end
    end
end

function back_substitution(A::Array{Float64,2})

    rows = size(A,1)
    cols = size(A,2)

    # Creating the solution Vector
    soln = Vector{Float64}(rows)

    # initialize the final element
    soln[rows] = A[rows, cols] / A[rows, cols-1]

    for i = (rows - 1):-1:1
        sum = 0.0
        for j = rows:-1:i
            sum += soln[j]*A[i,j]
        end
        soln[i] = (A[i, cols] - sum) / A[i, i]
    end

    return soln
end

function main()
    A = [2. 3 4 6;
         1 2 3 4;
         3 -4 0 10]

    gaussian_elimination(A)
    println(A)
    soln = back_substitution(A)

    for element in soln
        println(element)
    end
end

main()


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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

F = M * ff;

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

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

u = KB \ F;

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

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


using Symata

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

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

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

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


"""
    abs_val(num)

Program to find the absolute value of a number

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

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

"""
    abs_max(x)

Program to find the max absolute value in a vector

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

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

"""
    abs_min(num)

Program to find the min absolute value in a vector

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

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


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

function crossit(xx)

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

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

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

    return [y]
end


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

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

# 4.1 Multidimensional Interpolation

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

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

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

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

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

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


module TheAlgorithms

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

## Exports
# Please keep the folders/functions sorted

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

# Exports: matrix
export determinant
export lu_decompose

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

# Exports: searches
export binarysearch
export search

# Exports: statistics
export OLSbeta

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

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

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

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

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

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

end


using Measurements

5.23 ± 0.14 === 5.23 ± 0.14

(5.23± 0.14) - (5.23 ± 0.14)

(5.23 ± 0.14) / (5.23 ± 0.14)

x = 5.23 ± 0.14
x === x

x - x

x / x

using DifferentialEquations, Measurements, Plots

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

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

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

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

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

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

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

using DifferentialEquations, Measurements, Plots

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

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

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

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

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

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

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

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

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

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

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

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

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


using Plots
using LinearAlgebra
using Distributions

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

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

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

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

# f_ch01_ex03_1b

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

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

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


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

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

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

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

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


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

    calcs = zeros(Float64,rows,cols)

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

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

    values_cat = hcat(coeff_f,vector_f)

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


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

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

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

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

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


module Mods

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

# Operations with Integers

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

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

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

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

# Comparison with Integers

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

# Chinese remainder theorem functions

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

    a = x.val
    b = y.val

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

    z = a + k.val*n

    return Mod(z, n*m)
end

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

julia> 92%11
4

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

    result::Mod = mtuple[1]

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

    return result
end

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

end # end of module Mods


##
## Angle Conversions
##

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

### rad2xxx

"""
    rad2ha(angle)

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

"""
    rad2dms(angle)

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

"""
    rad2hms(angle)

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

### deg2xxx

"""
    deg2ha(angle)

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

"""
    deg2dms(angle)

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

"""
    deg2hms(angle)

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

### ha2xxx

"""
    ha2rad(angle)

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

"""
    ha2deg(angle)

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

"""
    ha2hms(angle)

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

"""
    ha2dms(angle)

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

### dms2xxx

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

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

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

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

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

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

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

### hms2xxx

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

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

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

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

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

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

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

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


module Scalings

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

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

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

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

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

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

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

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

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

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

end