"""
    sphere_mesh(ne, radius::FT, wtype::AbstractSphereWarp) where {FT<:AbstractFloat}

Generates a sphere mesh of a given `radius`, warp type and number of elements along each side
of a cubed surface. The sphere mesh is generated by applying the warping function to a cube panel mesh.
"""
function sphere_mesh(ne, radius::FT, wtype::AbstractSphereWarp) where {FT<:AbstractFloat}
    mesh = cube_panel_mesh(ne, FT)
    mesh.coordinates .-= FT(0.5)
    mesh.coordinates .*= (FT(2) * radius)
    for i = 1:mesh.nverts
        mesh.coordinates[i, :] .= cubed_sphere_warp(wtype, mesh.coordinates[i, :]...)
    end
    return mesh
end

"""
    cubed_sphere_warp(::EquiangularSphereWarp, a, b, c)

Given points `(a, b, c)` on the surface of a cube, warp the points out to a
spherical shell of radius `R` based on the equiangular gnomonic grid proposed by
[Ronchi1996]

Returns a tuple of (x1, x2, x3), where x1, x2, x3 are the corresponding coordinates on a sphere
of radius R.

The "Cubed Sphere": A New Method for the Solution of Partial Differential Equations in Spherical Geometry
C. RONCHI,*,1 R. IACONO,* AND P. S. PAOLUCCI†
JOURNAL OF COMPUTATIONAL PHYSICS 124, 93–114 (1996)
https://github.com/CliMA/ClimateMachine.jl/blob/master/src/Numerics/Mesh/Topologies.jl
"""
function cubed_sphere_warp(wtype::EquiangularSphereWarp, a, b, c)
    R = max(abs(a), abs(b), abs(c))
    function f(sR, ξ, η)
        X, Y = tan(π * ξ / 4), tan(π * η / 4)
        ζ1 = sR / sqrt(X^2 + Y^2 + 1)
        ζ2, ζ3 = X * ζ1, Y * ζ1
        ζ1, ζ2, ζ3
    end

    fdim = argmax(abs.((a, b, c)))
    if fdim == 1
        if a < 0
            # (-R, *, *) : formulas for Face I from Ronchi, Iacono, Paolucci (1996)
            #              but for us face IV of the developed net of the cube
            x1, x2, x3 = f(-R, b / a, c / a)
        elseif a > 0
            # ( R, *, *) : formulas for Face III from Ronchi, Iacono, Paolucci (1996)
            #              but for us face II of the developed net of the cube
            x1, x2, x3 = f(R, b / a, c / a)
        end
    elseif fdim == 2
        if b < 0
            # ( *,-R, *) : formulas for Face II from Ronchi, Iacono, Paolucci (1996)
            #              but for us face V of the developed net of the cube
            x2, x1, x3 = f(-R, a / b, c / b)
        elseif b > 0
            # ( *, R, *) : formulas for Face IV from Ronchi, Iacono, Paolucci (1996)
            #              but for us face III of the developed net of the cube
            x2, x1, x3 = f(R, a / b, c / b)
        end
    elseif fdim == 3
        if c > 0
            # ( *, *, R) : formulas for Face V from Ronchi, Iacono, Paolucci (1996)
            #              but for us face VI of the developed net of the cube
            x3, x2, x1 = f(R, b / c, a / c)
        elseif c < 0
            # ( *, *,-R) : formulas for Face VI from Ronchi, Iacono, Paolucci (1996)
            #              but for us face I of the developed net of the cube
            x3, x2, x1 = f(-R, b / c, a / c)
        end
    end

    return x1, x2, x3
end

"""
    cubed_sphere_warp(::EquidistantSphereWarp, a, b, c)

Returns a tuple of (x1, x2, x3), where x1, x2, x3 are the corresponding coordinates on a sphere
of radius R.

https://github.com/CliMA/ClimateMachine.jl/blob/master/src/Numerics/Mesh/Topologies.jl
"""
function cubed_sphere_warp(wtype::EquidistantSphereWarp, a, b, c)
    R = max(abs(a), abs(b), abs(c))
    r = hypot(a, b, c)

    return R * a / r, R * b / r, R * c / r
end

"""
    cube_panel_mesh(ne)

This function builds a cube panel mesh with a resolution of `ne` elements along each edge.

               v8 (xs,xe,xe)          v7 (xe,xe,xe)
                 o--------e11---------o
                /|                   /|
               / |                  / |
              /  |                 /  |
            e12  e8               e10 e7
            /    |               /    |
           /     |            v6/     |
       v5 o--------e9----------o      |
          |    v4o------e3-----|------o v3 (xe,xe,xs)
          |     /   o------->  |     /
          |    /   /           |    /
          e5  e4  /           e6   e2
          |  /   /             |  /
          | /   /              | /
          |/   o               |/
          o--------e1----------o
         v1                    v2
       (xs,xs,xs)               (xe,xs,xs)

       panel 1 => 1 4 3 2
       panel 2 => 2 3 7 6
       panel 3 => 3 4 8 7
       panel 4 => 1 5 8 4
       panel 5 => 1 2 6 5
       panel 6 => 5 6 7 8

       edge  1 => 1 2
       edge  2 => 2 3
       edge  3 => 3 4
       edge  4 => 4 1
       edge  5 => 1 5
       edge  6 => 2 6
       edge  7 => 3 7
       edge  8 => 4 8
       edge  9 => 5 6
       edge 10 => 6 7
       edge 11 => 7 8
       edge 12 => 8 5

            v8 +---e11---+ v7
               | ^       |
               | |       |
              e8 ^   3   e7
               | |       |
            v4 | o--<-o  | v3      v7        v8
  v8 +---e8----+---e3----+----e7---+---e11---+
     | ^       | o-->->  | o-->--> | ^       |
     | |       | |       | |       | |       |
   e12 ^  4   e4 ^  1   e2 ^  2   e10^  6   e12
     | |       | |       | |       | |       |
     | o-<-<-o | o       | o       | o<-<--o |
     +---e5----+---e1----+----e6---+----e9---+
    v5       v1| o-->--o | v2      v6        v5
               |       | |
              e5    5  | e6
               |       ↓ |
               |         |
               +---e9----+
              v5         v6
"""
function cube_panel_mesh(ne::I, ::Type{FT}) where {FT<:AbstractFloat,I<:Integer}
    xs, xe = FT(0), FT(1)
    nverts = (ne + 1)^3 - (ne - 1)^3
    nfaces = 12 * ne + 6 * (2 * ne * (ne - 1))
    nelems = 6 * ne * ne
    nbndry = 0

    nx = ne + 1

    nfaces_edg = 12 * ne

    emat = reshape(1:nelems, ne, ne, 6)
    ndmat = zeros(I, ne + 1, ne + 1)

    panel_verts = [
        1 2 3 1 1 5
        4 3 4 5 2 6
        3 7 8 8 6 7
        2 6 7 4 5 8
    ]
    panel_edges = [
        4 2 3 5 1 9
        3 7 8 12 6 10
        2 10 11 8 9 11
        1 6 7 4 5 12
    ]
    panel_edges_rev = Bool.([
        1 0 0 0 0 0
        1 0 0 1 0 0
        1 1 1 1 1 0
        1 1 1 0 1 0
    ])
    # node coordinates
    xc = range(xs, xe; step = FT(1 / ne)) # [xs, xs+Δ, xs+2Δ, ..., xe]
    xci = view(xc, 2:ne)                   # [xs+Δ, xs+2Δ, ..., xe-Δ]
    xcir = view(xc, ne:-1:2)                # reverse(xci)
    sc = ones(FT, ne - 1) * xs              # [xs, ...., xs]
    ec = ones(FT, ne - 1) * xe              # [xe, ...., xe]
    # where Δ = (xe-xs)/ne
    sc2 = ones(FT, (ne - 1) * (ne - 1)) * xs
    ec2 = ones(FT, (ne - 1) * (ne - 1)) * xe

    xci12 = repeat(xci, outer = ne - 1)
    xci1r2 = repeat(xcir, outer = ne - 1)
    xci21 = repeat(xci, inner = ne - 1)

    edge_nodes = reshape(1:((ne-1)*12), ne - 1, 12) .+ 8
    # coordinates
    coordinates = vcat(
        hcat(
            [xs, xe, xe, xs, xs, xe, xe, xs], # x1,
            [xs, xs, xe, xe, xs, xs, xe, xe], # x2,
            [xs, xs, xs, xs, xe, xe, xe, xe], # x3 vertex coordinates
        ),
        vcat(
            hcat(xci, sc, sc), # edge 1
            hcat(ec, xci, sc), # edge 2
            hcat(xcir, ec, sc), # edge 3
            hcat(sc, xcir, sc), # edge 4
            hcat(sc, sc, xci), # edge 5
            hcat(ec, sc, xci), # edge 6
            hcat(ec, ec, xci), # edge 7
            hcat(sc, ec, xci), # edge 8
            hcat(xci, sc, ec), # edge 9
            hcat(ec, xci, ec), # edge 10
            hcat(xcir, ec, ec), # edge 11
            hcat(sc, xcir, ec), # edge 12
        ),
        hcat(xci21, xci12, sc2), # panel 1
        hcat(ec2, xci12, xci21), # panel 2
        hcat(xci1r2, ec2, xci21), # panel 3
        hcat(sc2, xci21, xci12), # panel 4
        hcat(xci12, sc2, xci21), # panel 5
        hcat(xci12, xci21, ec2), # panel 6
    )
    face_interior = reshape(1:((ne-1)*(ne-1)), ne - 1, ne - 1)

    nfc1i = (nx - 2) * (nx - 1) # panels with normals along local first direction
    nfc2i = (nx - 1) * (nx - 2) # panels with normals along local second direction
    nfci = nfc1i + nfc2i
    fci1 = reshape(1:nfc1i, nx - 2, nx - 1)
    fci2 = reshape(1:nfc2i, nx - 1, nx - 2)

    fcmat1 = zeros(I, nx, nx - 1) # face numbering
    fcmat2 = zeros(I, nx - 1, nx) # for each panel

    edge_faces = reshape(1:(12*ne), ne, 12)

    face_verts = zeros(I, nfaces, 2)
    face_neighbors = zeros(I, nfaces, 5)
    face_boundary = Vector{I}(zeros(nfaces)) # all interior nodes (no boundaries)
    elem_verts = zeros(I, nelems, 4)
    elem_faces = zeros(I, nelems, 4)

    for sfc = 1:6
        ndmat[1, 1],
        ndmat[ne+1, 1],  # panel vertices
        ndmat[ne+1, ne+1],
        ndmat[1, ne+1] = panel_verts[:, sfc]

        ndmat[2:ne, 1] .=
            panel_edges_rev[1, sfc] ? reverse(edge_nodes[:, panel_edges[1, sfc]]) :
            edge_nodes[:, panel_edges[1, sfc]]  # panel edges

        ndmat[end, 2:ne] .=
            panel_edges_rev[2, sfc] ? reverse(edge_nodes[:, panel_edges[2, sfc]]) :
            edge_nodes[:, panel_edges[2, sfc]]

        ndmat[ne:-1:2, end] .=
            panel_edges_rev[3, sfc] ? reverse(edge_nodes[:, panel_edges[3, sfc]]) :
            edge_nodes[:, panel_edges[3, sfc]]

        ndmat[1, ne:-1:2, end] .=
            panel_edges_rev[4, sfc] ? reverse(edge_nodes[:, panel_edges[4, sfc]]) :
            edge_nodes[:, panel_edges[4, sfc]]

        offset = 8 + 12 * (ne - 1) + (sfc - 1) * (ne - 1) * (ne - 1) # interior
        ndmat[2:ne, 2:ne] .= face_interior .+ offset

        fcmat1[1, end:-1:1] .=
            panel_edges_rev[4, sfc] ? reverse(edge_faces[:, panel_edges[4, sfc]]) :
            edge_faces[:, panel_edges[4, sfc]]

        fcmat1[end, :] .=
            panel_edges_rev[2, sfc] ? reverse(edge_faces[:, panel_edges[2, sfc]]) :
            edge_faces[:, panel_edges[2, sfc]]

        fcmat2[:, 1] .=
            panel_edges_rev[1, sfc] ? reverse(edge_faces[:, panel_edges[1, sfc]]) :
            edge_faces[:, panel_edges[1, sfc]]

        fcmat2[end:-1:1, end] .=
            panel_edges_rev[3, sfc] ? reverse(edge_faces[:, panel_edges[3, sfc]]) :
            edge_faces[:, panel_edges[3, sfc]]

        off = ne * 12 + (sfc - 1) * nfci

        fcmat1[2:(end-1), :] .= fci1 .+ off
        fcmat2[:, 2:(end-1)] .= fci2 .+ (off + nfc1i)

        face_verts[fcmat1[:], 1] .= ndmat[:, 1:ne][:] # face nodes
        face_verts[fcmat1[:], 2] .= ndmat[:, 2:(ne+1)][:]
        face_verts[fcmat2[:], 1] .= ndmat[1:ne, :][:]
        face_verts[fcmat2[:], 2] .= ndmat[2:(ne+1), :][:]

        if sfc == 1
            bdy1 = emat[:, 1:1, 5]'
            bdy2 = emat[ne:-1:1, 1:1, 3]'
            bdy3 = emat[1:1, 1:ne, 4]'
            bdy4 = emat[:, 1:1, 2]
        elseif sfc == 2
            bdy1 = emat[ne:ne, :, 5]
            bdy2 = emat[1:1, :, 3]
            bdy3 = emat[:, ne:ne, 1]
            bdy4 = emat[ne:ne, :, 6]'
        elseif sfc == 3
            bdy1 = emat[ne:ne, :, 2]
            bdy2 = emat[:, ne:ne, 4]'
            bdy3 = emat[ne:ne, ne:-1:1, 1]'
            bdy4 = emat[ne:-1:1, ne:ne, 6]
        elseif sfc == 4
            bdy1 = emat[:, 1:1, 1]'
            bdy2 = emat[1:1, :, 6]
            bdy3 = emat[1:1, :, 5]'
            bdy4 = emat[ne:ne, :, 3]'
        elseif sfc == 5
            bdy1 = emat[:, 1:1, 4]'
            bdy2 = emat[1:1, :, 2]
            bdy3 = emat[1:1, :, 1]'
            bdy4 = emat[:, 1:1, 6]
        else # sfc == 6
            bdy1 = emat[ne:ne, :, 4]
            bdy2 = emat[:, ne:ne, 2]'
            bdy3 = emat[:, ne:ne, 5]
            bdy4 = emat[ne:-1:1, ne:ne, 3]
        end
        face_neighbors[fcmat1[:], 1] .= vcat(bdy1, emat[:, :, sfc])[:]
        face_neighbors[fcmat1[:], 3] .= vcat(emat[:, :, sfc], bdy2)[:]
        face_neighbors[fcmat2[:], 1] .= hcat(bdy3, emat[:, :, sfc])[:]
        face_neighbors[fcmat2[:], 3] .= hcat(emat[:, :, sfc], bdy4)[:]

        elem_verts[emat[:, :, sfc][:], :] .= hcat(
            ndmat[1:ne, 1:ne][:], # node numbers (local node 1)
            ndmat[2:(ne+1), 1:ne][:], # for each element (local node 2)
            ndmat[2:(ne+1), 2:(ne+1)][:], #(local node 3)
            ndmat[1:ne, 2:(ne+1)][:], # (local node 4)
        )

        elem_faces[emat[:, :, sfc][:], :] .= hcat(
            fcmat2[:, 1:(nx-1)][:], # (local face 1)
            fcmat1[2:nx, :][:],   # each element (local face 2)
            fcmat2[:, 2:nx][:], # (local face 3)
            fcmat1[1:(nx-1), :][:], # face numbers for (local face 4)
        )
    end

    ref_fc_verts = [
        1 2 3 4
        2 3 4 1
    ]

    for fc = 1:nfaces
        elems = (face_neighbors[fc, 1], face_neighbors[fc, 3])
        for e = 1:2
            el = elems[e]
            localface = findfirst(elem_faces[el, :] .== fc)
            face_neighbors[fc, 2+(e-1)*2] = localface
        end
        # setting up relative orientation
        or1 =
            elem_verts[elems[1], ref_fc_verts[:, face_neighbors[fc, 2]]] ==
            face_verts[fc, :] ? 1 : -1
        or2 =
            elem_verts[elems[2], ref_fc_verts[:, face_neighbors[fc, 4]]] ==
            face_verts[fc, :] ? 1 : -1
        face_neighbors[fc, 5] = or1 * or2
    end

    boundary_tags = sort(unique(face_boundary))
    face_boundary_tags = sort(face_boundary)
    face_boundary = sortperm(face_boundary)

    face_boundary_offset = I[1, length(face_boundary)+1]

    boundary_tag_names = (:interior,)

    # add unique vertex iterator information
    vtconn = map(i -> zeros(I, i), zeros(I, nverts))

    for el = 1:nelems
        for lv = 1:4
            vt = elem_verts[el, lv]
            push!(vtconn[vt], el, lv)
        end
    end
    unique_verts = I[]
    uverts_conn = I[]
    uverts_offset = I.([1])
    for vt = 1:nverts
        lconn = length(vtconn[vt])
        if lconn > 0
            push!(unique_verts, vt)
            push!(uverts_conn, vtconn[vt]...)
            push!(uverts_offset, uverts_offset[end] + lconn)
        end
    end

    return Mesh2D(
        nverts,
        nfaces,
        nelems,
        nbndry,
        coordinates,
        unique_verts,
        uverts_conn,
        uverts_offset,
        face_verts,
        face_neighbors,
        face_boundary,
        boundary_tags,
        boundary_tag_names,
        face_boundary_offset,
        elem_verts,
        elem_faces,
    )
end

function unfold_cube_panel_to_plane(
    x::FTA1D,
    y::FTA1D,
    z::FTA1D,
    panelno::Int,
) where {FT<:AbstractFloat,FTA1D<:AbstractVector{FT}}
    @assert 1 ≤ panelno ≤ 6
    n = length(x)
    xx = similar(x)
    yy = similar(x)

    if panelno == 1
        xx = x
        yy = y
    elseif panelno == 2
        xx = z .+ FT(1)
        yy = y
    elseif panelno == 3
        xx = x
        yy = z .+ FT(1)
    elseif panelno == 4
        xx = -z
        yy = y
    elseif panelno == 5
        xx = x
        yy = -z
    elseif panelno == 6
        xx = 3 .- x
        yy = y
    end

    return xx, yy
end


"""
    K, Cl = h2synthesize(P::ExtendedStateSpace, γ = nothing)

Synthesize H₂-optimal controller K and calculate the closed-loop transfer function from `w` to `z`.
Ref: Cha. 14.5 in Robust and Optimal Control.

If `γ = nothing`, use the formulas for H₂ in Ch 14.5. If γ is a large value, the H∞ formulas are used. As γ → ∞, these two are equivalent. The h∞ formulas do a coordinate transfromation that handles slightly more general systems so if you run into an error, it might be worth trying setting γ to something large, e.g., 1000.
"""
function h2synthesize(P::ExtendedStateSpace, γ = nothing)

    if γ === nothing
        X2, Y2, F2, L2 = _solvematrixequations2(P)
        Â2 = P.A + P.B2*F2 + L2*P.C2
        K = ss(Â2, -L2, F2, 0)
        return K, lft(ss(P), K)
    end

    P̄, Ltrans12, Rtrans12, Ltrans21, Rtrans21 = _transformp2pbar(P)
    X2, Y2, F2, L2 = _solvematrixequations(P̄, γ)

    _checkfeasibility(
        X2,
        Y2,
        γ,
        1e-3,
        10;
        verbose = false,
    ) || throw(DomainError("Not feasible for γ = $γ"))

    K = _synthesizecontroller(
        P̄,
        X2,
        Y2,
        F2,
        L2,
        γ,
        Ltrans12,
        Rtrans12,
        Ltrans21,
        Rtrans21,
    )
    Cl = lft(ss(P), K)
    K, Cl
end

function _solvematrixequations2(P::ExtendedStateSpace)

    A = P.A
    B1 = P.B1
    B2 = P.B2
    C1 = P.C1
    C2 = P.C2
    D11 = P.D11
    D12 = P.D12
    D21 = P.D21
    D22 = P.D22

    P1 = size(C1, 1)
    P2 = size(C2, 1)
    M1 = size(B1, 2)
    M2 = size(B2, 2)

    HX = [A zeros(size(A)); -C1'*C1 -A'] - [B2; -C1'*D12] * [D12'*C1 B2']
    HY = [A' zeros(size(A)); -B1*B1' -A] - [C2'; -B1*D21'] * [D21*B1' C2]

    # Solve matrix equations
    X2 = _solvehamiltonianare(HX)
    Y2 = _solvehamiltonianare(HY)

    F2 = -(B2'X2 + D12'C1)

    L2 = -(B1 * D21' + Y2 * C2')

    return X2, Y2, F2, L2
end


### A Pluto.jl notebook ###
# v0.12.6

using Markdown
using InteractiveUtils

# ╔═╡ f29303e2-1d66-11eb-3313-f335871e9674
using Pkg, DrWatson

# ╔═╡ f2935b8a-1d66-11eb-02e8-3dacec840928
begin
	@quickactivate "StatisticsWithJuliaPlutoNotebooks"
	using LinearAlgebra, StatsBase, DataFrames
end

# ╔═╡ 7877839e-1d66-11eb-0a3c-5953d222ccbc
md"## Listing 1.8"

# ╔═╡ f293d704-1d66-11eb-36d8-4bb2f8dff940
md"##### Transition probability matrix."

# ╔═╡ f2a01cf8-1d66-11eb-3573-d91ed64fb7bc
P = [0.5 0.4 0.1;
     0.3 0.2 0.5;
     0.5 0.3 0.2];

# ╔═╡ f2a0c444-1d66-11eb-0f37-99e69755fe8f
md"##### First way."

# ╔═╡ f2a67846-1d66-11eb-022c-6f0a51c33a42
piProb1 = (P^100)[1,:]

# ╔═╡ f2b43238-1d66-11eb-3713-c5c8f2d4af76
md"##### Second way."

# ╔═╡ f2b4d63e-1d66-11eb-3d9f-65ba9b1ac506
begin
	A = vcat((P' - I)[1:2,:],ones(3)')
	b = [0 0 1]'
	piProb2 = (A\b)[:, 1]
end

# ╔═╡ f2c17bc8-1d66-11eb-0569-9b84c6b33cca
md"##### Third way."

# ╔═╡ f2c2e8fa-1d66-11eb-3421-3d76cd8c9a73
begin
	eigVecs = eigvecs(copy(P'))
	highestVec = eigVecs[:,findmax(abs.(eigvals(P)))[2]]
	piProb3 = Array{Float64}(highestVec)/norm(highestVec,1)
end

# ╔═╡ f2d180ba-1d66-11eb-216b-e3e640811323
md"##### Fourth way."

# ╔═╡ f2da9662-1d66-11eb-1f95-3da69865d370
begin
numInState = zeros(Int,3)
	state = 1
	N = 10^6
	for t in 1:N
		numInState[state] += 1
		global state = sample(1:3,weights(P[state,:]))
	end
	piProb4 = numInState/N
end

# ╔═╡ f2dbd9d2-1d66-11eb-12b9-af2d7f1f73da
DataFrame(:piProb1 => piProb1, :piProb2 => piProb2, :piProb3 => piProb3, :piProb4 => piProb4)

# ╔═╡ f2e37750-1d66-11eb-3f4e-e7b249fb2052
md"## End of listing 1.8"

# ╔═╡ Cell order:
# ╟─7877839e-1d66-11eb-0a3c-5953d222ccbc
# ╠═f29303e2-1d66-11eb-3313-f335871e9674
# ╠═f2935b8a-1d66-11eb-02e8-3dacec840928
# ╟─f293d704-1d66-11eb-36d8-4bb2f8dff940
# ╠═f2a01cf8-1d66-11eb-3573-d91ed64fb7bc
# ╟─f2a0c444-1d66-11eb-0f37-99e69755fe8f
# ╠═f2a67846-1d66-11eb-022c-6f0a51c33a42
# ╟─f2b43238-1d66-11eb-3713-c5c8f2d4af76
# ╠═f2b4d63e-1d66-11eb-3d9f-65ba9b1ac506
# ╟─f2c17bc8-1d66-11eb-0569-9b84c6b33cca
# ╠═f2c2e8fa-1d66-11eb-3421-3d76cd8c9a73
# ╟─f2d180ba-1d66-11eb-216b-e3e640811323
# ╠═f2da9662-1d66-11eb-1f95-3da69865d370
# ╠═f2dbd9d2-1d66-11eb-12b9-af2d7f1f73da
# ╟─f2e37750-1d66-11eb-3f4e-e7b249fb2052


"""
    stumpff!(beta, x, Gz)

Calculates the values of the c-functions and returns the first four g-functions, which will be used to solve Kepler's equation.

```math
c_{0} = \\cos(\\sqrt{z}) \\hspace{3cm} c_{1} = \\frac{\\sin(\\sqrt{z})}{\\sqrt{z}} \\hspace{3cm} c_{n} = \\frac{1}{n!} - z c_{n-2} \\hspace{3cm} G_{n} = x^n c_{n}
```

# Args

* `beta`: a coefficient calculated in function `KeplerFlow`, which equals to `µ`/`a` (where `a` is the semi-major axis and `mu` the standard gravitational parameters of the bodies involved).
* `x`: current estimation of the universal anomaly to solve the universal Kepler equation.
* `Gz`: array of four elements, in which the first four g-functions will be saved.
"""
function stumpff!(beta, x, Gz)
    z = beta * (x^2)

    # C-funtzioak
    if (z < 0)
        Gz[1] = cosh(sqrt(abs(z)))
        Gz[2] = sinh(sqrt(abs(z)))/sqrt(abs(z))
    else
        Gz[1] = cos(sqrt(z))
        Gz[2] = sin(sqrt(z))/sqrt(z)
    end

    for i = 3:4
        Gz[i] = (1.0 - Gz[i-2])/z
    end

    # G-funtzioak
    for i = 2:4
        Gz[i] *= x^(i-1)
    end
end

"""
    keplerSolve!(x0, beta, eta0, zeta0, r0, Gz, dt, trace)

Solves the universal equation of kepler. In this equation, G_{2} and G_{3} are the third and fourth g-functions, respectively.

```math
r_{0}X + \\eta_{0}G_{2} + \\zeta_{0}G_{3} - dt = 0
```

# Args

* `x0`: initial estimation of the universal anomaly.
* `beta`: a coefficient calculated in function `KeplerFlow`, which equals to `µ`/`a` (where `a` is the semi-major axis and `mu` the standard gravitational parameters of the bodies involved).
* `eta0`: a coefficient that is needed to solve, calculated in function `KeplerFlow`.
* `zeta0`: another quantity of the equation it is needed to solve
* `r0`: initial distance between the two bodies.
* `Gz`: array of the first four g-functions. Instead of defining it on each execution of the function, the same array is used in the whole simulation. That is why it's passed as an argument.
* `dt`: time-step of the Kepler-flow.
* `trace`: boolean value that shows information of the execution if true.

# Returns

* `r`: distance between the two bodies after time-step dt.
"""
function keplerSolve!(x0, beta, eta0, zeta0, r0, Gz, dt, trace)

    xi = x0 # initial estimation
    r = r0
    imax = 50
    error = Inf
    tol = sqrt(eps(typeof(xi))) / 100

    for i in 1:imax

        stumpff!(beta, xi, Gz)
        fx = r0*xi + eta0*Gz[3] + zeta0*Gz[4] - dt
        r = r0 + eta0*Gz[2] + zeta0*Gz[3]
        eta = eta0*Gz[1] + zeta0*Gz[2]
        eps = fx / (r - (fx*eta)/(2*r))
        xi -= eps
        error = abs(eps)

        if trace
            println("### $i. Iteration ###")
            println("GZ: $Gz")
            println("R: $r")
            println("X$i: $xi")
            println("Epsilon: $eps\n")
        end

        if error < tol
            if trace
                println("Stop-condition achieved.")
                println("Universal anomaly X = $xi\n")
            end
            return r
        end

    end

    if trace
        println("Newton's method didn't converge.")
        println("Last calculated universal anomaly X = $xi\n")
    end

    return r
end

"""
    keplerFlow(r0_bek, v0_bek, dt, mu, Gz, trace = false)

Defines the position and speeds of a particle -after a time-step- orbiting another body given its initial position and speeds.

# Args

* `r0_bek`: position vector of the secondary body orbiting the primary body.
* `v0_bek`: velocity vector of the secondary body orbiting the primary body.
* `dt`: time-step, the function returns the new position and speed vectors after dt time has passed.
* `mu`: standard gravitational parameter of the two bodies.
* `r0`: initial distance between the two bodies.
* `Gz`: array of the first four g-functions. Instead of defining it on each execution of the function, the same array is used in the whole simulation. That is why it's passed as an argument.
* `trace`: boolean value that shows information of the execution if true.

# Returns

* `r_bek`: the position vector of the body after dt time from the initial values.
* `v_bek`: the position vector of the body after dt time from the initial values.
"""
function keplerFlow(r0_bek, v0_bek, dt, mu, Gz, trace = false)

    r0 = norm(r0_bek)
    v02 = dot(v0_bek,v0_bek)
    eta = dot(r0_bek,v0_bek)
    alpha = mu/r0
    beta = 2.0*alpha - v02
    zeta = mu - beta*r0

    # with beta, we can know the shape of the orbit
    if trace
        if beta > 0.0
            println("Orbit is elliptic.\n")
        elseif beta < 0.0
            println("Orbit is hyperbolic.\n")
        else beta == 0.0
            println("Orbit is parabolic.\n")
        end
    end

    ### Initial estimation ###
    x0 = (dt/r0) * (1.0 - eta/2.0)
    if trace
        println("X0 = $x0\n")
    end

    ### Solve equation ###
    r = keplerSolve!(x0, beta, eta, zeta, r0, Gz, dt, trace)

    rinv = 1.0/r
    ### New position vector ###
    f = -alpha*Gz[3]
    g = r0*Gz[2] + eta*Gz[3]

    r_bek = f * r0_bek + g * v0_bek + r0_bek

    ### New speed vector ###
    df = -alpha*Gz[2]*rinv
    dg = -mu*Gz[3]*rinv

    v_bek = df * r0_bek + dg * v0_bek + v0_bek

    if trace
        println("New position vector: R = $r0_bek")
        println("New speed vector: V = $v0_bek")
    end

    return r_bek, v_bek

end

"""
    keplerFlowWithKahan(r0_bek, v0_bek, dt, mu, Gz, trace, r_k, v_k)

Defines the position and speeds of a particle -after a time-step- orbiting another body given its initial position and speeds. When the new vectors are calculated, Kahan's algorithm is applied.

# Args

* `r0_bek`: position vector of the secondary body orbiting the primary body.
* `v0_bek`: speed vector of the secondary body orbiting the primary body.
* `dt`: time-step, the function returns the new position and speed vectors after dt time has passed.
* `mu`: standard gravitational parameter of the two bodies.
* `r0`: initial distance between the two bodies.
* `Gz`: array of the first four g-functions. Instead of defining it on each execution of the function, the same array is used in the whole simulation. That is why it's passed as an argument.
* `trace`: boolean value that shows information of the execution if true.
* `r_k`: lost information in the sum of the ``f`` and ``g`` coefficients is saved here and used in the next iteration (for positions).
* `v_k`: lost information in the sum of the ``\\dot{f}`` and ``\\dot{g}`` coefficients is saved here and used in the next iteration (for velocities).

# Returns

* `r_bek`: the position vector of the body after dt time-step from the initial values.
* `v_bek`: the velocity vector of the body after dt time-step from the initial values.
"""
function keplerFlowWithKahan(r0_bek, v0_bek, dt, mu, Gz, trace, r_k, v_k)

    r0 = norm(r0_bek)
    v02 = dot(v0_bek,v0_bek)
    eta = dot(r0_bek,v0_bek)
    alpha = mu/r0
    beta = 2.0*alpha - v02
    zeta = mu - beta*r0

    # with beta, we can know the shape of the orbit
    if trace
        if beta > 0.0
            println("Orbit is elliptic.\n")
        elseif beta < 0.0
            println("Orbit is hyperbolic.\n")
        else beta == 0.0
            println("Orbit is parabolic.\n")
        end
    end

    ### Initial estimation ###
    x0 = (dt/r0) * (1.0 - eta/2.0)
    if trace
        println("X0 = $x0\n")
    end

    ### Solve equation ###
    r = keplerSolve!(x0, beta, eta, zeta, r0, Gz, dt, trace)

    rinv = 1.0/r
    ### New position vector ###
    f = -alpha*Gz[3]
    g = r0*Gz[2] + eta*Gz[3]

    r0_bek_aux = copy(r0_bek)
    kahanSum!(r0_bek, f * r0_bek + g * v0_bek, r_k)

    ### New speed vector ###
    df = -alpha*Gz[2]*rinv
    dg = -mu*Gz[3]*rinv

    kahanSum!(v0_bek, df * r0_bek_aux + dg * v0_bek, v_k)

    if trace
        println("New position vector: R = $r0_bek")
        println("New speed vector: V = $v0_bek")
    end

    return r0_bek, v0_bek
end

"""
    keplerStep!(r, v, dt, mu, Gz, trace)

Drifts all particles/bodies under ``\\mathcal{H}_{\\mathrm{Kepler}}`` hamiltonian.

```math
\\mathcal{H}_{\\mathrm{Kepler}}(\\textbf{q}, \\textbf{p}) = \\sum_{i=2}^{N} \\frac{\\textbf{p'}^{2}_{i}}{2 m_{i}'} - \\sum_{i=2}^{N} \\frac{Gm_{i}^{'}M_{i}}{\\mid \\textbf{q}_{i}^{'} \\mid}
```

# Args

* `r`: NxD matrix, where N is the number of bodies and D the dimensions. Initially must contain the initial positions of the bodies, but the output positions will be saved here as well (NxD, where N is the number of bodies  and D is the number of dimensions).
* `v`: NxD matrix. Initially must contain the initial speeds of the bodies, but the output speeds will be saved here as well (NxD, where N is the number of bodies and D is the number of dimensions).
* `dt`: time-step between the initial and final positions and speeds.
* `mu`: N array. Standard gravitational parameter of the N bodies.
* `r0`: initial distance between the two bodies.
* `Gz`: array of the first four g-functions. Instead of defining it on each execution of the function, the same array is used in the whole simulation. That is why it's passed as an argument.
* `trace`: boolean value that shows information of the execution if true.
"""
function keplerStep!(r, v, dt, mu, Gz, trace)
    # drift all particles under H_Kepler
    for i in 2:size(r)[1]
        r[i,:], v[i,:] = keplerFlow(r[i,:], v[i,:], dt, mu[i], Gz, trace)
    end
end

"""
    keplerStepWithKahan!(r, v, dt, mu, Gz, trace, r_k, v_k)

Drifts all particles/bodies under ``\\mathcal{H}_{\\mathrm{Kepler}}`` hamiltonian.

```math
\\mathcal{H}_{\\mathrm{Kepler}}(\\textbf{q}, \\textbf{p}) = \\sum_{i=2}^{N} \\frac{\\textbf{p'}^{2}_{i}}{2 m_{i}'} - \\sum_{i=2}^{N} \\frac{Gm_{i}^{'}M_{i}}{\\mid \\textbf{q}_{i}^{'} \\mid}
```

# Args

* `r`: NxD matrix, where N is the number of bodies and D the dimensions. Initially must contain the initial positions of the bodies, but the output positions will be saved here as well.
* `v`: NxD matrix. Initially must contain the initial speeds of the bodies, but the output speeds will be saved here as well.
* `dt`: time-step between the initial and final positions and speeds.
* `mu`: array of N elements, containing standard gravitational parameters of the N bodies.
* `r0`: initial distance between the two bodies.
* `Gz`: array of the first four g-functions. Instead of defining it on each execution of the function, the same array is used in the whole simulation. That is why it's passed as an argument.
* `trace`: boolean value that shows information of the execution if true.
* `r_k`: auxiliary variable to save extra information of Kahan's algorithm (positions). Same dimension as `r`.
* `v_k`: auxiliary variable to save extra information of Kahan's algorithm (velocities). Same dimension as `v`.
"""
function keplerStepWithKahan!(r, v, dt, mu, Gz, trace, r_k, v_k)
    ### drift all particles under H_Kepler
    for i in 2:size(r)[1]
        r[i,:], v[i,:] =keplerFlowWithKahan(r[i,:], v[i,:], dt, mu[i], Gz, trace, r_k[i,:], v_k[i,:])
    end
end


using Plots
pyplot()

#---------------------------------------------------------------------#
#This code plots the Stability of for the 1D Wave Equation.
#Written by F.X. Giraldo on 03/2019
#           Department of Applied Maths
#           Naval Postgraduate School
#           Monterey; CA 93943-5216
#Variables:
#---------------------------------------------------------------------#
#method=input(" 1=FE & Upwind \n 2=FE & Centered \n 3=FE & Downwind \n 4=BE & Upwind \n 5=BE & Centered \n 6=BE & Downwind \n 7=TR & Upwind \n 8=TR & Centered \n 9=TR & Downwind \n Enter Method: ")
method=1

n=100
dtheta=2*pi/n
theta=0:dtheta:2*pi
if (method <= 3)
    cmax=1
elseif [method >= 4]
    cmax=10
end
m=10*cmax
dc=cmax/m
c=0:dc:cmax
em1=exp.(-im*theta)
ep1=exp.(+im*theta)

a=ones(size(em1))
#Plot Stability of Specific Equations
xmatrix=Array{Float64}(undef,n,m+2)
ymatrix=Array{Float64}(undef,n,m+2)
if method == 1 #FE + Upwind
    for j=1:m+1
        z=a - c[j]*(a - em1) #1st order upwind [explicit 1st order in time]
        xmatrix[:,j]=real(z)
        ymatrix[:,j]=imag(z)
    end
elseif method == 2 #FE + Centered
    for j=2:m+1
        z=a - 0.5*c[j]*(ep1 - em1); #2nd order centered [explicit 1st order in time]
        xmatrix[:,j]=real(z)
        ymatrix[:,j]=imag(z)
    end
elseif method == 3 #FE + Downwind
    for j=2:m+1
        z=a - c[j]*(ep1 - a); #1st order downwind [explicit 1st order in time]
        xmatrix[:,j]=real(z)
        ymatrix[:,j]=imag(z)
    end
elseif method == 4 #BE + Upwind
    for j=2:m+1
        z=a/(a + c[j]*(a - em1)) #1st order uwpind, (implicit 1st order in time)
        xmatrix[:,j]=real(z)
        ymatrix[:,j]=imag(z)
    end
elseif method == 5 #BE + Centered
    for j=2:m+1
        z=a/(a + 0.5*c[j]*(ep1 - em1)) #2nd order centered, (implicit 1st order in time)
        xmatrix[:,j]=real(z)
        ymatrix[:,j]=imag(z)
    end
elseif method == 6 #BE + Downwind
    for j=2:m+1
        z=a/(a + c[j]*(ep1 - a)) #1st order downwind, (implicit 1st order in time)
        xmatrix[:,j]=real(z)
        ymatrix[:,j]=imag(z)
    end
elseif method == 7 #TR + Upwind
    for j=2:m+1
        z=(a - 0.5*c[j]*(a - em1))./(a + 0.5*c[j]*(a - em1)) #1st order upwind [Trapezoidal 2nd order in time]
        xmatrix[:,j]=real(z)
        ymatrix[:,j]=imag(z)
    end
elseif method == 8 #TR + Centered
    for j=2:m+1
        z=(a - 0.25*c[j]*(ep1 - em1))./(a + 0.25*c[j]*(ep1 - em1)) #2nd order centered [TR 2nd order in time]
        xmatrix[:,j]=real(z)
        ymatrix[:,j]=imag(z)
    end
elseif method == 9 #TR + Downwind
    for j=2:m+1
        z=(a - 0.5*c[j]*(ep1 - a))./(a + 0.5*c[j]*(ep1 - a)) #1st order downwind [TR 2nd order in time]
        xmatrix[:,j]=real(z)
        ymatrix[:,j]=imag(z)
    end
end

#Plot Unit Circle
z=ep1
xmatrix[:,m+2]=real(z)
ymatrix[:,m+2]=imag(z)
#plot_handle=plot(xmatrix,ymatrix,leg=false,w=2,aspect_ratio=1)
plot(xmatrix,ymatrix,leg=false,w=1,line=(:dash,1),aspect_ratio=1)
plot!(real(z),imag(z),leg=false,w=1,color=[:black],aspect_ratio=1)
xaxis!("Re(z)")
yaxis!("Im(z)")
title!("Von Neumann Stability Analysis")
#display(plot(plot_handle))


function entropy(partitionSet, N)
    return mapreduce(x -> -(length(x) / N) * log(length(x) / N), +, partitionSet)
end

function getPartitionSet(vector, nrOfClusters)
    d = Dict(zip(1:nrOfClusters,[Int[] for _ in 1:nrOfClusters]));
    for i in 1:length(vector)
        push!(d[vector[i]], i)
    end
    return collect(values(d))
end

function mutualInformation(partitionSetA, partitionSetB, N)

    anonFunc = (x) -> (
                intersection = size(intersect(partitionSetA[x[1]], partitionSetB[x[2]]),1);
                return intersection > 0 ?
                        (intersection/N) * log((intersection * N) / (size(partitionSetA[x[1]],1) * size(partitionSetB[x[2]],1))) : 0;
    )

    mapreduce(anonFunc, + , product(1:length(partitionSetA),1:length(partitionSetB)))
end

function normalizedMutualInformation(partitionSetA, partitionSetB, N)
    mutualInformation(partitionSetA, partitionSetB, N) / ((entropy(partitionSetA, N)  + entropy(partitionSetB, N)) / 2)
end


# # Chebyshev center
# Boyd & Vandenberghe, "Convex Optimization"
# Joëlle Skaf - 08/16/05
#
# Adapted for Convex.jl by Karanveer Mohan and David Zeng - 26/05/14
#
# The goal is to find the largest Euclidean ball (i.e. its center and
# radius) that lies in a polyhedron described by affine inequalites in this
# fashion: $P = \{x : a_i'*x \leq b_i, i=1,\ldots,m \}$ where $x \in \mathbb{R}^2$.

using Convex
using LinearAlgebra
import SCS

# Generate the input data
a1 = [2; 1];
a2 = [2; -1];
a3 = [-1; 2];
a4 = [-1; -2];
b = ones(4, 1);

# Create and solve the model
r = Variable(1)
x_c = Variable(2)
p = maximize(r)
p.constraints += a1' * x_c + r * norm(a1, 2) <= b[1];
p.constraints += a2' * x_c + r * norm(a2, 2) <= b[2];
p.constraints += a3' * x_c + r * norm(a3, 2) <= b[3];
p.constraints += a4' * x_c + r * norm(a4, 2) <= b[4];
solve!(p, SCS.Optimizer; silent_solver = true)
p.optval

# Generate the figure
x = range(-1.5, stop = 1.5, length = 100);
theta = 0:pi/100:2*pi;
using Plots
plot(x, x -> -x * a1[1] / a1[2] + b[1] / a1[2])
plot!(x, x -> -x * a2[1] / a2[2] + b[2] / a2[2])
plot!(x, x -> -x * a3[1] / a3[2] + b[3] / a3[2])
plot!(x, x -> -x * a4[1] / a4[2] + b[4] / a4[2])
plot!(
    evaluate(x_c)[1] .+ evaluate(r) * cos.(theta),
    evaluate(x_c)[2] .+ evaluate(r) * sin.(theta),
    linewidth = 2,
)
plot!(
    title = "Largest Euclidean ball lying in a 2D polyhedron",
    legend = nothing,
)


"""
    function spearman_corr(;
        players::Array{Player},
        ranking_scheme1::String,
        ranking_scheme2::String
    )::Float64

Calculates the Spearman Correlation between two ranks.

Arguments
=========
- `players`:
    the players whose rankings to consider.
- `ranking_scheme1`:
    ranking scheme 1 to use.
- `ranking_scheme2`:
    ranking scheme 2 to use.
"""

function spearman_corr(;
    players::Array{Player},
    ranking_scheme1::String,
    ranking_scheme2::String
)::Float64
    rankings1 = map(player -> player.rankings[ranking_scheme1], players)
    rankings2 = map(player -> player.rankings[ranking_scheme2], players)
    return corspearman(rankings1, rankings2)
end

"""
    function kendtau_corr(;
        players::Array{Player},
        ranking_scheme1::String,
        ranking_scheme2::String
    )::Float64

Calculates the Kendall Tau Correlation between two ranks.

Arguments
=========
- `players`:
    the players whose rankings to consider.
- `ranking_scheme1`:
    ranking scheme 1 to use.
- `ranking_scheme2`:
    ranking scheme 2 to use.
"""

function kendtau_corr(;
    players::Array{Player},
    ranking_scheme1::String,
    ranking_scheme2::String
)::Float64
    rankings1 = map(player -> player.rankings[ranking_scheme1], players)
    rankings2 = map(player -> player.rankings[ranking_scheme2], players)
    return corkendall(rankings1, rankings2)
end


# # 220: 2D Nonlinear Poisson with boundary reaction and boundary species
# ([source code](SOURCE_URL))

module Example220_NonlinearPoisson2D_BoundarySpecies

using Printf
using VoronoiFVM
using ExtendableGrids
using GridVisualize

function main(;n=10,Plotter=nothing,verbose=false,unknown_storage=:sparse)

    h=1.0/convert(Float64,n)
    X=collect(0.0:h:1.0)
    Y=collect(0.0:h:1.0)

    grid=VoronoiFVM.Grid(X,Y)

    k=1.0
    eps=1.0
    physics=VoronoiFVM.Physics(
    num_species=3,
    breaction=function(f,u,node)
        if  node.region==2
            f[1]=k*(u[1]-u[3])
            f[3]=k*(u[3]-u[1])+ k*(u[3]-u[2])
            f[2]=k*(u[2]-u[3])
        end
    end,

    bstorage=function(f,u,node)
        if  node.region==2
            f[3]=u[3]
        end
    end,

    flux=function(f,u0,edge)
        u=unknowns(edge,u0)
        f[1]=eps*(u[1,1]-u[1,2])
        f[2]=eps*(u[2,1]-u[2,2])
    end,

    source=function(f,node)
        x1=node[1]-0.5
        x2=node[2]-0.5
        f[1]=exp(-20.0*(x1^2+x2^2))
    end,

    storage=function(f,u,node)
        f[1]=u[1]
        f[2]=u[2]
    end
    )

    sys=VoronoiFVM.System(grid,physics,unknown_storage=unknown_storage)

    enable_species!(sys,1,[1])
    enable_species!(sys,2,[1])
    enable_boundary_species!(sys,3,[2])

    function tran32!(a,b)
        a[1]=b[2]
    end

    bgrid2=subgrid(grid,[2],boundary=true,transform=tran32!)

    inival=unknowns(sys)
    inival.=0.0
    U=unknowns(sys)

    eps=1.0e-2

    control=VoronoiFVM.NewtonControl()
    control.verbose=verbose
    control.tol_linear=1.0e-5
    control.tol_relative=1.0e-5
    control.max_lureuse=0
    tstep=0.01
    time=0.0
    istep=0
    u5=0
    p=GridVisualizer(Plotter=Plotter,layout=(3,1))
    while time<1
        time=time+tstep
        solve!(U,inival,sys,control=control,tstep=tstep)
        inival.=U
        if verbose
            @printf("time=%g\n",time)
        end
        tstep*=1.0
        istep=istep+1
        U_bound=view(U[3,:],bgrid2)
        u5=U_bound[5]
        scalarplot!(p[1,1],grid,U[1,:],clear=true)
        scalarplot!(p[2,1],grid,U[2,:])
        scalarplot!(p[3,1],bgrid2,U_bound,show=true,flimits=(0,0.0025))
    end
    return u5
end

function test()
    main(unknown_storage=:sparse) ≈ 0.0020781361856598
    main(unknown_storage=:dense) ≈ 0.0020781361856598
end
end


@doc raw"""
    Segment2

An object `s` of the data type [`Segment2`](@ref) is a directed straight line
segment in the two-dimensional Euclidean plane ``Ε^2``, i.e. a straight line
segment ``[p, q]`` connecting two points ``p, q ∈ ℝ^2``.

The segment is topologically closed, i.e. the end points belong to it. Point `p`
is called the *source* and `q` is called the *target* of `s`. The length of `s`
is the Euclidean distance between `p` and `q`. Note that there is only a
function to compute the square of the length, because otherwise we had to
perform a square root operation which is expensive, and may not be exact.
"""
Segment2

"""
    Segment2(p::Point2, q::Point2)

Introduces a segment `s` with source `p` and target `q`.

The segment is directed from the source towards the target.
"""
Segment2(p::Point2, q::Point2)

"""
    ==(s::Segment2, q::Segment2)

Test for equality: Two segments are equal, iff their sources and targets are
equal.
"""
==(s::Segment2, q::Segment2)

"""
    source(s::Segment2)

The source of `s`.
"""
source(s::Segment2)

"""
    target(s::Segment2)

The target of `s`.
"""
target(s::Segment2)

"""
    min(s::Segment2)

Returns the point of `s` with lexicographically smallest coordinate.
"""
min(s::Segment2)

"""
    max(s::Segment2)

Returns the point of `s` with lexicographically largest coordinate.
"""
max(s::Segment2)

@doc raw"""
    vertex(s::Segment2, i::Integer)

Returns source of target or `s`: `vertex(s, 0)` returns the source of `s`,
`vertex(s, 1)` returns the target of `s`

The parameter `i` is taken module 2, which gives easy access to the other
vertex.
"""
vertex(s::Segment2, i::Integer)

"""
    point(s::Segment2, i::Integer)

Returns `vertex(s, i)`.
"""
point(s::Segment2, i::Integer)

"""
    squared_length(s::Segment2)

Returns the squared lenght of `s`.
"""
squared_length(s::Segment2)

"""
    direction(s::Segment2)

Returns the direction from source to target of `s`.
"""
direction(s::Segment2)

"""
    to_vector(s::Segment2)

Returns the vector `target(s) - source(s)`.
"""
to_vector(s::Segment2)

"""
    opposite(s::Segment2)

Returns a segment with source and target point interchanged.
"""
opposite(s::Segment2)

"""
    supporting_line(s::Segment2)

Returns the line `l` passing through `s`.
"""
supporting_line(s::Segment2)

"""
    is_degenerate(s::Segment2)

Segment `s` is degenerate, if source and target are equal.
"""
is_degenerate(s::Segment2)

"""
    is_horizontal(s::Segment2)

Segment `s` is horizontal, if both the source and target's ``y``-coordinate is
the same.
"""
is_horizontal(s::Segment2)

"""
    is_vertical(s::Segment2)

Segment `s` is vertical, if both the source and target's ``x``-coordinate is
the same.
"""
is_vertical(s::Segment2)

"""
    has_on(s::Segment2, p::Point2)

A point is on `s`, iff it is equal to the source or target of `s` or if it is
in the interior of `s`.
"""
has_on(s::Segment2, p::Point2)

"""
    collinear_has_on(s::Segment2, p::Point2)

Checks if point `p` is on segment `s`.

!!! info "Precondition"

    `p` is on the supporting line of `s`.
"""
collinear_has_on(s::Segment2, p::Point2)

"""
    bbox(s::Segment2)

Returns a bounding box containing `s`.
"""
bbox(s::Segment2)

"""
    transform(s::Segment2, t::AffTransformation2)

Returns the segment obtained by applying `t` on the source and the target of
`s`.
"""
transform(s::Segment2, t::AffTransformation2)


using UtilitiesForMRI, Random

# Cartesian domain
n = (128,128,128)
h = (1f0, 1f0, 1f0)
X = spatial_sampling(Float32, n; h=h)

# Cartesian sampling (dense)
phase_encoding = (1,2)
K = kspace_Cartesian_sampling(X; phase_encoding=phase_encoding)
# using PyPlot
# figure()
# plot3D(K[1][:,1],  K[1][:,2],  K[1][:,3], ".")
# plot3D(K[10][:,1], K[10][:,2], K[10][:,3], ".")
# plot3D(K[20][:,1], K[20][:,2], K[20][:,3], ".")
# plot3D(K[128*(10-1)+1][:,1], K[128*(10-1)+1][:,2], K[128*(10-1)+1][:,3], ".")
# plot3D(K[128*(20-1)+1][:,1], K[128*(20-1)+1][:,2], K[128*(20-1)+1][:,3], ".")
# plot3D(K[128^2][:,1], K[128^2][:,2], K[128^2][:,3], ".")

# Cartesian sampling (randomized)
subsampling = (1:128^2)[randperm(128^2)][1:32]
K = kspace_Cartesian_sampling(X; phase_encoding=(1,3), subsampling=subsampling)
# figure()
# for i = 1:32
#     plot3D(K[i][:,1], K[i][:,2], K[i][:,3], ".")
# end

# for i = 1:50:128^2
#     plot3D(K[i][:,1], K[i][:,2], K[i][:,3], ".")
# end


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

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

# Example 1 - resource allocation

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

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

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

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

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

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

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

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

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

# Example 2 - portfolio optimisation

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

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

#Having a peek at the data

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

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

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

port = Model(solver=IpoptSolver())

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

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

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

solve(port);

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

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

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

# Example 3 - Robust Knapsack Problem

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

N = 18

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

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

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

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

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

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

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

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

    solve(m)

    return getvalue(x)
end

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

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

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

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

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

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

# Example 4 - Classification

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

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

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

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

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

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

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

    solve(m);

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

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

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

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


# Load Julia packages (libraries)

using Pkg, DrWatson

#@quickactivate "SR2StanPluto"
using StanSample
using StatisticalRethinking

# Define the Stan language model

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

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

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

# Input data for cmdstan

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

# Sample using cmdstan

rc8_5s = stan_sample(m8_5s, data=m8_5_data);

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

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


const EARTH_RADIUS_EQUATORIAL = 6378137.0
const EARTH_RADIUS_POLAR = 6356752.3

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

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

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

Usage
=====

  CoordinateDistance(latStart, longStart, latEnd, longEnd)

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

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

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

Arguments
=========

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

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

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

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

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

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

  return radius * c
end

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

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

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

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

  return radius * c
end

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

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

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

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

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

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

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

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

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

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

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


# circle.jl

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

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

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

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

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

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

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

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

isempty(::UnitHyperSphere) = false

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

    return a
end

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


using DifferentialEquations
using Plots
plotlyjs()

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

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

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

#time interval
tspan=(0.0,20.0)

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

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

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

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

#plotting the solutions for different tolerances

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

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

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


"""
# Usage

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

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

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

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

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

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


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

"""
    struct FloydWarshallState{T, U}

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

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

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

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

        d = distmx[u,v]

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

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

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


# Practical Review: Extremum Estimators (Ch. 13)

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

##

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

##

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

# the calculations to get ℐ hat

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

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

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

##

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

##

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

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

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

##

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

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

##

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

##

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

##

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


using ITensors,
      LinearAlgebra,
      QuadGK

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

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

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

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


"""
    q4

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

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

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

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

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

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

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

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

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

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

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

end

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


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

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

# %%
using DifferentialEquations
using Parameters
using StaticArrays
using Plots

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

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

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

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

tspan = (0.0, 10.0)

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

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

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

# %%


# This file investigates MSE with respect to free variables

using FractalTools
using GeometryBasics
using Makie

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

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

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

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

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


#!/usr/bin/julia

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

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

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

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

using Primes

function smarandache(n::Int64, cache)

    isprime(n) && return n

    f = factor(n)

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

    distinct && return maximum(keys(f))

    if (length(f) == 1)

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

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

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

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

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

        cache[n] = max
        return max
    end

    arr = Int64[]

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

    maximum(arr)
end

#
## Tests
#

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

    limit = 10^2
    sumS = 0

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

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

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

test()


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

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

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

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

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

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

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

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

    if ifder != 1
        fjder = nothing
    end

    return fjs, fjder

end


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

    ∫_0^1 cosh(xt) dt

which has a known closed analytic form:

    sinh(x)/x,  x!=0

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

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

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

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

"""
This function just evaluates the integral:

    ∫_0^1 cosh(xt) dt

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

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

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

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

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

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

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

    err = eps(Ω)

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

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

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

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

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

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

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


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

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

using LinearAlgebra
using Plots
using Printf

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

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

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

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

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

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

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

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


export RSA, rsa_encrypt, rsa_decrypt, coprime_inv_mod

using Primes: nextprime

using RingSignature.Util

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

    "Bézout coefficient y"
    y::T

    "greatest common divisor"
    gcd::T

    "a / gcd"
    quot_a::T

    "b / gcd"
    quot_b::T
end

#=

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

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

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

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

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

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

#=
When a and b are coprimes,

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

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

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

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

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

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

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

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

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

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

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

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

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

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


### Algoritms and Recursion #####

# Guess-and-check algoritm

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

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

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

# Approximate solution

function approximation(x)

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

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

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

approximation(6)

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

# Binary search algoritm

function binarysearch(x)

    # initialize

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

    #algoritm

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

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

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

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

binarysearch(6)

# performance comparation

using BenchmarkTools

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

# Recursion

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

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

# multipication as iteravive solution

function multi_iterative(x,y)
    result = 0

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

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

multi_iterative(5,10)

# multipication as recursion

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

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

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

end

multi_recursive(5,10)

# factorial as iterative method

function FactorialIterative(x)
    result = 1

    for i in 1:x
        result *= i

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

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

FactorialIterative(5)

# factorial as recursion method

function FactorialRecursive(x)

    if x == 1

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