# ---
# title: 1590. Make Sum Divisible by P
# id: problem1590
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Array, Hash Table, Math, Binary Search
# link: <https://leetcode.com/problems/make-sum-divisible-by-p/description/>
# hidden: true
# ---
#
# Given an array of positive integers `nums`, remove the **smallest** subarray
# (possibly **empty** ) such that the **sum** of the remaining elements is
# divisible by `p`. It is **not** allowed to remove the whole array.
#
# Return _the length of the smallest subarray that you need to remove, or_`-1`
# _if it 's impossible_.
#
# A **subarray** is defined as a contiguous block of elements in the array.
#
#
#
# **Example 1:**
#
#
#
#     Input: nums = [3,1,4,2], p = 6
#     Output: 1
#     Explanation: The sum of the elements in nums is 10, which is not divisible by 6. We can remove the subarray [4], and the sum of the remaining elements is 6, which is divisible by 6.
#
#
# **Example 2:**
#
#
#
#     Input: nums = [6,3,5,2], p = 9
#     Output: 2
#     Explanation: We cannot remove a single element to get a sum divisible by 9. The best way is to remove the subarray [5,2], leaving us with [6,3] with sum 9.
#
#
# **Example 3:**
#
#
#
#     Input: nums = [1,2,3], p = 3
#     Output: 0
#     Explanation: Here the sum is 6. which is already divisible by 3. Thus we do not need to remove anything.
#
#
# **Example 4:**
#
#
#
#     Input: nums = [1,2,3], p = 7
#     Output: -1
#     Explanation: There is no way to remove a subarray in order to get a sum divisible by 7.
#
#
# **Example 5:**
#
#
#
#     Input: nums = [1000000000,1000000000,1000000000], p = 3
#     Output: 0
#
#
#
#
# **Constraints:**
#
#   * `1 <= nums.length <= 105`
#   * `1 <= nums[i] <= 109`
#   * `1 <= p <= 109`
#
#
## @lc code=start
using LeetCode

## add your code here:
## @lc code=end


"""
    block(i, j)

Return the linear index of the `[i,j]` position ("block") in the row-major packed lower triangle.

Use the row-major ordering in this case because the result depends only on `i`
and `j`, not on the overall size of the array.

When `i == j` the value is the same as `kp1choose2(i)`.
"""
function block(i::Integer, j::Integer)
    0 < j ≤ i || throw(ArgumentError("[i,j] = [$i,$j] must be in the lower triangle"))
    return kchoose2(i) + j
end

"""
    kchoose2(k)

The binomial coefficient `k` choose `2` which is the number of elements
in the packed form of the strict lower triangle of a matrix.
"""
function kchoose2(k)      # will be inlined
    return (k * (k - 1)) >> 1
end

"""
    kp1choose2(k)

The binomial coefficient `k+1` choose `2` which is the number of elements
in the packed form of the lower triangle of a matrix.
"""
function kp1choose2(k)
    return (k * (k + 1)) >> 1
end

"""
    ltriindprs

A row-major order `Vector{NTuple{2,Int}}` of indices in the strict lower triangle.
"""
const ltriindprs = NTuple{2,Int}[]

function checkindprsk(k::Integer)
    kc2 = kchoose2(k)
    if length(ltriindprs) < kc2
        sizehint!(empty!(ltriindprs), kc2)
        for i in 1:k, j in 1:(i - 1)
            push!(ltriindprs, (i, j))
        end
    end
    return ltriindprs
end


using CircGeometry, Test

p1 = Point(0.0,0.0); q1 = Point(0.0,1.0)
p2 = Point(-0.5,0.5); q2 = Point(0.5,0.5)
@testset "do_intersect tests" begin
    @test CircGeometry.do_intersect(p1,q1,p2,q2) == true
    @test CircGeometry.do_intersect(p1,p2,q1,q2) == false
end

z = Point(0.5+Float64(pi),1.5+Float64(pi))
@testset "get_orientation tests" begin
    @test CircGeometry.get_orientation(p2,q1,z) == 0
    @test CircGeometry.get_orientation(p2,z,q1) == 0
    @test CircGeometry.get_orientation(z,p2,q1) == 0
    @test CircGeometry.get_orientation(p1,q1,Point(1.0,0.0)) == 1
    @test CircGeometry.get_orientation(p1,q1,Point(-1.0,0.0)) == 2
end

a = Point(13.2,12.3)
b = Point(1.0+5*eps(),1.0)
c = Point(-1.0,0.0)
circlewall = CircGeometry.CircleWall(Point(0.0,0.0),1.0)
lwall1 = CircGeometry.LineWall(Point(-1.0,1.0),Point(1.0,1.0))
lwall2 = CircGeometry.LineWall(Point(-1.0,-1.0),Point(-1.0,1.0))
@testset "is_point_in_wall tests" begin
    @test CircGeometry.is_point_in_wall(q1,circlewall) == true
    @test CircGeometry.is_point_in_wall(p2,circlewall) == false
    @test CircGeometry.is_point_in_wall(a,circlewall) == false

    @test CircGeometry.is_point_in_wall(q1,lwall1) == true
    @test CircGeometry.is_point_in_wall(p1,lwall1) == false
    @test CircGeometry.is_point_in_wall(b,lwall1) == false

    @test CircGeometry.is_point_in_wall(c,lwall2) == true
    @test CircGeometry.is_point_in_wall(p1,lwall2) == false
end

# test for on_segment where point is colinear with other two points
polygon = [Point(0.0,0.0),Point(2.0,0.0),Point(2.0,1.0)]
point = Point(1.0,0.0)
@test CircGeometry.is_inside_polygon(polygon, point; extreme = Point(100_000.0, point.y)) == true

# check on_segment
@testset "on_segment tests" begin
    @test CircGeometry.on_segment(Point(0.0,0.0),Point(1.0,0.0),Point(2.0,0.0)) == true
    @test CircGeometry.on_segment(Point(0.0,0.0),Point(1.0,0.1),Point(2.0,0.0)) == false
    @test CircGeometry.on_segment(Point(0.0,0.0),Point(3.0,0.0),Point(2.0,0.0)) == false
end


using Test
using Plots
using BenchmarkTools
using LinearAlgebra
using Random
using Roots

push!(LOAD_PATH, abspath(@__DIR__,"..","src"))

using SmoothedSpectralAbscissa ; const SSA = SmoothedSpectralAbscissa

##

"""
    lyap_simple!(A::AbstractMatrix)
returns the naive implementation of the Lyapunov objective function and its
derivative. Can be used for testing, plotting, etc
"""
function lyap_simple!(A::AbstractMatrix)
    function  myf(s::Float64)::Float64
        P = lyap(A - UniformScaling(s),one(A))
        tr(P)
    end
    function d_myf(s::Float64)::Float64
        sc,Id = UniformScaling(s), one(A)
        P =  lyap(A - sc,Id)
        Q = lyap(A' - sc,Id)
        -2.0tr(P*Q)
    end
    myf,d_myf
end

"""
    ssa_test(A::Matrix{Float64})

This is the easiest implementation
for testing and benchmarking.
"""
function ssa_test(A::Matrix{Float64} ; ssa_eps=nothing)
    myf,d_myf = lyap_simple!(A)
    ssa_eps = something(ssa_eps,SSA.default_eps_ssa(A))
    myg(s)= 1.0 / myf(s) - ssa_eps
    d_myg(s) = - d_myf(s) / myf(s)^2
    sa = SSA.spectral_abscissa(A)
    ssa_start = sa + 0.5*ssa_eps
    find_zero( (myg,d_myg),ssa_start, Roots.Newton())
end

function ssa_simple(A::AbstractMatrix, with_gradient::Bool,PQ,
        ssa_eps::Union{Nothing,Float64}=nothing)
    _ssa_eps = something(ssa_eps, SSA.default_eps_ssa(A))
    SSA.PQ_init!(A,PQ)
    _sa = SSA.spectral_abscissa(PQ)
    _start = _sa + 0.5*ssa_eps
    g(s)=SSA._g(s,PQ,_ssa_eps,_sa)
    x = range(_start, 10 ; length=200)
    y = g.(x)
    (x,y)
end

##


function drawline!(img::Matrix{T}, x0::Int, y0::Int, x1::Int, y1::Int, col::T) where T
    δx = abs(x1 - x0)
    δy = abs(y1 - y0)
    δe = abs(δy / δx)
    er = 0.0

    y = y0
    for x in x0:x1
        img[x, y] = col
        er += δe
        if er > 0.5
            y  += 1
            er -= 1.0
        end
    end

    return img
end

using Images

img = fill(Gray(255.0), 5, 5);
println("\nImage:")
display(img); println()
drawline!(img, 1, 1, 5, 5, Gray(0.0));
println("\nModified image:")
display(img); println()


# http://mathworld.wolfram.com/SphericalSpiral.html

```julia
using CatmullRom, Plots

x(t) = cos(t) / sqrt(1 + t*t)
y(t) = sin(t) / sqrt(1 + t*t)
z(t) = - t/sqrt(1+t*t)

ts = collect(range(-4pi,4pi, length=24));
xs = x.(ts); ys = y.(ts); zs = z.(ts);

czs, cys, cxs = catmullrom(collect(zip(zs,ys,xs)), 36);

plot(zs, ys, xs, linecolor=:darkred, linewidth=2, legend=nothing, size=(600,600))
plot!(czs, cys, cxs, linecolor=:navy, linewidth=2, legend=nothing, size=(600,600))

```


"""
    regpower(X,Y,m)

adalah fungsi untuk mencari persamaan pangkat `y=Ax^m` dari variabel dependen `Y` dan
independen `X` dengan pangkat `m`.

# Example
```jldoctest
julia> tk = [0.2,0.4,0.6,0.8,1.0];

julia> dk = [0.1960,0.7850,1.7665,3.1405,4.9075];

julia> A = regpower(tk,dk,2)
4.907303370786516
```
return nilai koefisien `A`.
"""
function regpower(X,Y,m)
  sumxy = (X.^m)'*Y
  sumx2 = (X.^m)'*(X.^m)
  A = sumxy/sumx2
end


using Qaintessent
using LinearAlgebra: I
using SparseArrays: sparse
using Memoize

# Pauli X and Y matrices
X = [0 1; 1 0]
Y = [0 -im; im 0]

# Utility function for a XY mixer
# Implements X_a X_{a+1} + Y_a Y_{a+1}, or more generally (⊗_{i ∈ xy_indices} X_i) + (⊗_{i ∈ xy_indices} Y_i)
function XY_sum(xy_indices::Vector{Int64}, d::Int64)::Matrix{ComplexF64}
    # passing generator into kron via varargs syntax
    return kron((i ∈ xy_indices ? X : I(2) for i ∈ 1:d)...
        ) + kron((i ∈ xy_indices ? Y : I(2) for i ∈ 1:d)...)
end

"""
    r-nearby values single-qudit mixer gate, which acts on a single qudit (but implemented
    here not for qudits, but the one-hot encoding)

``U_{r\\text{-NV}}(\\beta) = e^{-i \\beta H_{r\\text{-NV}}}``
``H_{r\\text{-NV}} = \\sum_{i=1}^r \\left(\\breve{X}^i + \\left(\\breve{X}^\\dagger\\right)^i\\right)``

Reference:\n
    Stuart Hadfield, Zhihui Wang, Bryan O'Gorman, Eleanor G. Rieffel,Davide Venturelli and Rupak Biswas\n
    From the Quantum Approximate Optimization Algorithm to a Quantum Alternating Operator Ansatz\n
    Algorithms 12.2 (2019), p.34
"""
struct RNearbyValuesMixerGate <: AbstractGate
    # use a reference type (array with 1 entry) for compatibility with Flux
    β::Vector{Float64}
    r::Integer # this is the r in r-Nearby-values
    d::Integer # d (= κ) = number of colors

    function RNearbyValuesMixerGate(β::Float64, r::Integer, d::Integer)
        (r > 0 && d > 0) || throw(ArgumentError("Parameters r and d must be positive integers."))
        (r <= d-1) || throw(ArgumentError("Parameter d must be between 1 and d-1."))
        new([β], r, d)
    end
end

# Compute the r-NV mixer Hamiltonian
@memoize function r_nearby_values_hamiltonian_onehot(g::RNearbyValuesMixerGate)
    H_rNV = sum(XY_sum([a, ((a + j - 1) % g.d) + 1], g.d)
        for a in 1:g.d for j in 1:g.r)

    return H_rNV
end

# Compute the r-NV mixer gate matrix: Implementation of Eq. (6)
function Qaintessent.matrix(g::RNearbyValuesMixerGate)
    H_rNV = r_nearby_values_hamiltonian_onehot(g)
    U_rNV = exp(-im * g.β[] * H_rNV)

    return U_rNV
end

# Adjoint of r-NV = r-NV gate with negated β parameter
Qaintessent.adjoint(g::RNearbyValuesMixerGate) = RNearbyValuesMixerGate(-g.β[], g.r, g.d)

Qaintessent.sparse_matrix(g::RNearbyValuesMixerGate) = sparse(matrix(g))

# Number of wires (= g.d since we use the one-hot encoding canonically)
Qaintessent.num_wires(g::RNearbyValuesMixerGate)::Int = g.d

"""
    Parity single-qudit ring mixer gate, which acts on a single qudit (but implemented
    here not for qudits, but the one-hot encoding)

``U_{\\text{parity}}(\\beta) = U_{\\text{last}}(\\beta) U_{\\text{even}}(\\beta) U_{\\text{odd}}(\\beta)``
``U_{\\text{odd}}(\\beta) = \\prod_{a~\\text{odd}, a \\neq d} e^{-i \\beta (X_a X_{a+1} + Y_a Y_{a+1})}``
``U_{\\text{even}}(\\beta) = \\prod_{a~\\text{even}} e^{-i \\beta (X_a X_{a+1} + Y_a Y_{a+1})}``
``U_{\\text{last}}(\\beta) = e^{-i \\beta (X_d X_1 + Y_d Y_1)} ~\\text{if}~ d ~\\text{is odd,}~ I ~\\text{otherwise.}``

The formulas require some interpretation. Assumptions for this implementation are:
- the indices a start at 1 (not at zero like elsewhere in the paper)
- X_{a+1} and Y_{a+1} actually means X_1 and Y_1 if a = d

Reference:\n
    Stuart Hadfield, Zhihui Wang, Bryan O'Gorman, Eleanor G. Rieffel, Davide Venturelli and Rupak Biswas\n
    From the Quantum Approximate Optimization Algorithm to a Quantum Alternating Operator Ansatz\n
    Algorithms 12.2 (2019), equations (7) - (10), p. 11
"""
struct ParityRingMixerGate <: AbstractGate
    β::Vector{Float64}
    d::Integer # d (= κ) = number of colors
    is_adjoint::Bool # if yes, the order in the product is reversed

    function ParityRingMixerGate(β::Float64, d::Integer; is_adjoint::Bool = false)
        d > 0 || throw(ArgumentError("Parameter d must be a positive integer."))
        new([β], d, is_adjoint)
    end
end

# Compute the parity ring mixer gate matrix: Implements Eq. (8)
function Qaintessent.matrix(g::ParityRingMixerGate)
    # assumption: by a ≠ n, the paper actually means a ≠ d.
    # assumption: by X_a for a = d+1, the paper means X_1.
    U_odd = prod([exp(-im * g.β[] * XY_sum([a, a+1], g.d)) for a ∈ 1:2:(g.d - 1)], init=I)
    U_even = prod([exp(-im * g.β[] * XY_sum([a, a < g.d ? (a+1) : 1], g.d)) for a ∈ 2:2:g.d], init=I)

    # Implements Eq. (9)
    U_last =  isodd(g.d) ? exp(-im * g.β[] * XY_sum([g.d, 1], g.d)) : I

    # Implements Eq. (7) (U_parity)
    if !g.is_adjoint
        return U_last * U_even * U_odd
    else
        return U_odd * U_even * U_last
    end
end

# Adjoint of parity ring mixer -> negated β parameter, reversed order in the product
Qaintessent.adjoint(g::ParityRingMixerGate) = ParityRingMixerGate(-g.β[], g.d, is_adjoint = !g.is_adjoint)

Qaintessent.sparse_matrix(g::ParityRingMixerGate) = sparse(matrix(g))

# Number of wires (= g.d since we use the one-hot encoding canonically)
Qaintessent.num_wires(g::ParityRingMixerGate)::Int = g.d

"""
    Partition single-qudit mixer gate (implemented not for qudits, but the one-hot encoding)

``U_{\\mathcal{P}-r-\\text{NV}}(\\beta) = U_{P_p-\\text{XY}}(\\beta) \\dots U_{P_1-\\text{XY}}(\\beta)``
``U_{P-\\text{XY}}(\\beta) = \\prod_{\\{a, b\\} \\in P} e^{-i \\beta (\\ket{a}\\bra{b} + \\ket{b}\\bra{a})}``

Note: the time evolution term is effectively implemented with an additional factor of two in the exponent:
``e^{-i \\beta 2 (\\ket{a}\\bra{b} + \\ket{b}\\bra{a})}``
to be consistent with the XY gates used for the other mixers.

Reference:\n
    Stuart Hadfield, Zhihui Wang, Bryan O'Gorman, Eleanor G. Rieffel, Davide Venturelli and Rupak Biswas\n
    From the Quantum Approximate Optimization Algorithm to a Quantum Alternating Operator Ansatz\n
    Algorithms 12.2 (2019), equations (11) - (12), p. 12
"""
struct PartitionMixerGate <: AbstractGate
    β::Vector{Float64}
    d::Int64
    partition::Vector{Vector{Tuple{Int, Int}}} # the Tuple stops Flux.@functor from misinterpreting these as params

    function PartitionMixerGate(β::Float64, d::Int64, partition::Vector{Vector{Tuple{Int, Int}}})
        d > 0 || throw(ArgumentError("Parameter d must be a positive integer."))

        # check that no duplicate indices occur in a part (s.t. the XY mixers within one part commute)
        for partition_part ∈ partition
            part_indices = union(reduce(vcat, collect.(partition_part)))
            part_indices ⊆ 1:d || throw("Indices in partition must be between 1 and d.")
            length(part_indices) == 2 * length(partition_part) ||
                throw("No index must occur more than once within each partition part in `partition`.")
        end

        new([β], d, partition)
    end
end

# Compute the partition mixer Hamiltonians
@memoize function partition_mixer_hamiltonians(g::PartitionMixerGate)::Vector{Matrix{ComplexF64}}
    hamiltonians = Matrix{ComplexF64}[]

    # Implements parts of Eqs. (11), (12)
    # iterate through partition
    for partition_part ∈ g.partition
        # we can represent each part by one Hamiltonian, because the individual XY gates commute
        # and therefore exp(-iβ ∑H_{a,b}) = ∏exp(-iβ H_{a,b})
        H_part = sum(XY_sum([a, b], g.d) for (a, b) ∈ partition_part)
        push!(hamiltonians, H_part)
    end

    return hamiltonians
end

# Compute the partition mixer gate matrix, but allowing to pass a different β
# than is stored in the gate struct (useful for backward pass)
function partition_mixer_gate_matrix(g::PartitionMixerGate, β::Float64)
    hamiltonians = partition_mixer_hamiltonians(g)

    # Implements parts of Eqs. (11), (12)
    # reverse terms in product to have matrix application from right to left
    Us = exp.(-im * β * hamiltonians)
    return prod(reverse(Us))
end

Qaintessent.matrix(g::PartitionMixerGate) = partition_mixer_gate_matrix(g, g.β[])

# Adjoint of partition ring mixer -> negated β parameter, reversed order in the product
Qaintessent.adjoint(g::PartitionMixerGate) = PartitionMixerGate(-g.β[], g.d, reverse(g.partition))

Qaintessent.sparse_matrix(g::PartitionMixerGate) = sparse(matrix(g))

# Number of wires (= g.d since we use the one-hot encoding canonically)
Qaintessent.num_wires(g::PartitionMixerGate)::Int = g.d


#===============================================================================

Let p_n be the nth prime: 2, 3, 5, 7, 11, ..., and let r_n be the remainder when
(p_n−1)^n + (p_n+1)^n is divided by p_n^2.

For example, when n = 3, p3 = 5, and 43 + 63 = 280 ≡ 5 mod 25.

The least value of n for which the remainder first exceeds 10^9 is 7037.

Find the least value of n for which the remainder first exceeds 10^10.

--------------------------------------------------------------------------------

It comes:
    r_n = 2 for n even
    r_n = 2 n p_n % p_n^2 = 2 n p_n as p_n > n

===============================================================================#

include("Euler.jl")

it = Euler.Primes.iter(Int)

st=start(it)
n=0

limit1 = true

while true
    (p, st) = next(it, st)
    n += 1
    # n is odd

    if limit1 && 2*n*p > 1_000_000_000
        limit1 = false
        println("10^9: n=$(n), p=$(p), r=$(2*p*n)")
    end

    if 2*n*p > 10_000_000_000
        println("10^10: n=$(n), p=$(p), r=$(2*p*n)")
        return
    end

    # skep n even
    (_, st) = next(it, st)
    n += 1
end


module test_stdlib_linearalgebra_det

using Test
using LinearAlgebra

A = [4 3;
     6 3]

@test det(A) == 4*3 - 3*6 == -6
@test det(exp(A)) ≈ exp(tr(A)) == 1096.6331584284585
@test sum(diag(A)) == tr(A) ≈ log(det(exp(A)))
@test tr(I - inv(A)) < tr(A - I)

A = [  4  12 -16;
      12  37 -43;
     -16 -43  98]
@test det(A) ≈ 36
@test det(2A) ≈ 288

end # module test_stdlib_linearalgebra_det

module test_stdlib_linearalgebra_eigen

using Test
using LinearAlgebra

A = [  4  12 -16;
      12  37 -43;
     -16 -43  98]

E1 = eigen(A)
E2 = eigen(2A)
@test E1.values ≈ [0.018804980460810916, 15.5039632294076, 123.47723179013143]
@test E2.values ≈ [0.03760996092162183 , 31.0079264588152, 246.95446358026285]
@test 2 * E1.values == E2.values
@test E1.vectors == E2.vectors

end # module test_stdlib_linearalgebra_eigen

module test_stdlib_linearalgebra_diagonal

using Test
using LinearAlgebra

A = [  4  12 -16;
      12  37 -43;
     -16 -43  98]
@test Diagonal(A) ==
      Diagonal(diag(A)) ==
      diagm(0=>diag(A)) ==
                           [  4   0   0;
                              0  37   0;
                              0   0  98]
@test I * A == A
@test I + A == Diagonal([1, 1, 1]) + A

end # module test_stdlib_linearalgebra_diagonal

module test_stdlib_linearalgebra_rank

using Test
using LinearAlgebra

@test rank(-5) == rank(5) == 1
@test rank(0) == 0
@test rank([ 0 0 0; 0 2 2; 0 3 3]) == 1
@test rank([ 0 0 0; 0 0 2; 0 3 3]) == 2

end # module test_stdlib_linearalgebra_rank

module test_stdlib_linearalgebra_cholesky

using LinearAlgebra
using Test

# https://en.wikipedia.org/wiki/Cholesky_decomposition#Example

A = [  4  12 -16;
      12  37 -43;
     -16 -43  98]

@test UpperTriangular(A) ==
      triu(A) ==
                            [  4  12 -16;
                               0  37 -43;
                               0   0  98]

@test LowerTriangular(A) ==
      tril(A) ==
                            [  4   0   0;
                              12  37   0;
                             -16 -43  98]
@test issymmetric(A)
@test A' == adjoint(A) == transpose(A) == A

C = cholesky(A)
@test C.L * C.U == A

end # module test_stdlib_linearalgebra_cholesky

module test_stdlib_linearalgebra_dot

using Test
using LinearAlgebra

@test dot([1, 3, -5], [4, -2, -1]) == 3
# 1 * 4 + 3 * -2 + -5 * -1

@test [1 2; 3 4] * [5 6; 7 8] == [19 22; 43 50]
# 1 * 5 + 2 * 7    1 * 6 + 2 * 8
# 3 * 5 + 4 * 7    3 * 6 + 4 * 8

end # module test_stdlib_linearalgebra_dot

module test_stdlib_linearalgebra_cross

using Test
using LinearAlgebra

@test cross([2, 3, 4], [5, 6, 7]) == [-3, 6, -3]
# 3 * 7 - 4 * 6
# 4 * 5 - 2 * 7
# 2 * 6 - 3 * 5

end # module test_stdlib_linearalgebra_cross

module test_stdlib_linearalgebra_norm

using Test
using LinearAlgebra

@test (norm ∘ normalize)(rand(10)) ≈ 1

end # module test_stdlib_linearalgebra_norm


@doc raw"""
    BasicStructural(y::Vector{Fl}, s::Int) where Fl

The basic structural state-space model consists of a trend (level + slope) and a seasonal
component. It is defined by:
```math
\begin{gather*}
    \begin{aligned}
        y_{t} &=  \mu_{t} + \gamma_{t} + \varepsilon_{t} \quad &\varepsilon_{t} \sim \mathcal{N}(0, \sigma^2_{\varepsilon})\\
        \mu_{t+1} &= \mu_{t} + \nu_{t} + \xi_{t} \quad &\xi_{t} \sim \mathcal{N}(0, \sigma^2_{\xi})\\
        \nu_{t+1} &= \nu_{t} + \zeta_{t} \quad &\zeta_{t} \sim \mathcal{N}(0, \sigma^2_{\zeta})\\
        \gamma_{t+1} &= -\sum_{j=1}^{s-1} \gamma_{t+1-j} + \omega_{t} \quad & \omega_{t} \sim \mathcal{N}(0, \sigma^2_{\omega})\\
    \end{aligned}
\end{gather*}
```

# Example
```jldoctest
julia> model = BasicStructural(rand(100), 12)
BasicStructural
```

# References
 * Durbin, James, & Siem Jan Koopman. (2012). "Time Series Analysis by State Space Methods: Second Edition." Oxford University Press.
"""
mutable struct BasicStructural <: StateSpaceModel
    hyperparameters::HyperParameters
    system::LinearUnivariateTimeInvariant
    seasonality::Int
    results::Results

    function BasicStructural(y::Vector{Fl}, s::Int) where Fl
        Z = [1; 0; 1; zeros(Fl, s - 2)]
        T = [
            1 1 zeros(Fl, 1, s - 1)
            0 1 zeros(Fl, 1, s - 1)
            0 0 -ones(Fl, 1, s - 1)
            zeros(Fl, s - 2, 2) Matrix{Fl}(I, s - 2, s - 2) zeros(Fl, s - 2)
        ]
        R = [
            Matrix{Fl}(I, 3, 3)
            zeros(Fl, s - 2, 3)
        ]
        d = zero(Fl)
        c = zeros(Fl, s + 1)
        H = one(Fl)
        Q = zeros(Fl, 3, 3)

        system = LinearUnivariateTimeInvariant{Fl}(y, Z, T, R, d, c, H, Q)

        names = ["sigma2_ε", "sigma2_ξ", "sigma2_ζ", "sigma2_ω"]
        hyperparameters = HyperParameters{Fl}(names)

        return new(hyperparameters, system, s, Results{Fl}())
    end
end

function default_filter(model::BasicStructural)
    Fl = typeof_model_elements(model)
    steadystate_tol = Fl(1e-5)
    a1 = zeros(Fl, num_states(model))
    P1 = Fl(1e6) .* Matrix{Fl}(I, num_states(model), num_states(model))
    return UnivariateKalmanFilter(a1, P1, num_states(model), steadystate_tol)
end

function initial_hyperparameters!(model::BasicStructural)
    Fl = typeof_model_elements(model)
    initial_hyperparameters = Dict{String,Fl}(
        "sigma2_ε" => one(Fl),
        "sigma2_ξ" => one(Fl),
        "sigma2_ζ" => one(Fl),
        "sigma2_ω" => one(Fl),
    )
    set_initial_hyperparameters!(model, initial_hyperparameters)
    return model
end

function constrain_hyperparameters!(model::BasicStructural)
    constrain_variance!(model, "sigma2_ε")
    constrain_variance!(model, "sigma2_ξ")
    constrain_variance!(model, "sigma2_ζ")
    constrain_variance!(model, "sigma2_ω")
    return model
end

function unconstrain_hyperparameters!(model::BasicStructural)
    unconstrain_variance!(model, "sigma2_ε")
    unconstrain_variance!(model, "sigma2_ξ")
    unconstrain_variance!(model, "sigma2_ζ")
    unconstrain_variance!(model, "sigma2_ω")
    return model
end

function fill_model_system!(model::BasicStructural)
    model.system.H = get_constrained_value(model, "sigma2_ε")
    model.system.Q[1] = get_constrained_value(model, "sigma2_ξ")
    model.system.Q[5] = get_constrained_value(model, "sigma2_ζ")
    model.system.Q[end] = get_constrained_value(model, "sigma2_ω")
    return model
end

function reinstantiate(model::BasicStructural, y::Vector{Fl}) where Fl
    return BasicStructural(y, model.seasonality)
end

has_exogenous(::BasicStructural) = false


@testset "utility.jl" begin
    p1 = rad2deg(Point(0.5, 1.0))
    p2 = rad2deg(Point(1.0, 0.5))
    p3 = rad2deg(Point(0.6, 1.0))  # above p1

    rs = RouteSection(p1,p2)

    @test (p1-p2).ϕ ≈ -28.6478897565 atol = 0.001

    @test Navigation.normalize(-270.0, -180.0, 180.0) ≈ 90.0 atol = 0.1
    @test Navigation.normalize(181.0, -180.0, 180.0) ≈ -179.0 atol = 0.1
    @test deg2rad(p1).ϕ ≈ 0.5 atol = 0.0001
    @test deg2rad(p2).λ ≈ 0.5 atol = 0.0001

    p3 = Point(80.0, 190.0)
    @test Navigation.normalize(p3).ϕ ≈ rad2deg(1.3962634016) atol = 0.0001
    @test Navigation.normalize(p3).λ ≈ rad2deg(-2.96705972839) atol = 0.0001
    @test Navigation.normalize(p3).λ ≈ -170.000 atol = 0.01

end


@testset "life expectancy" begin

    @testset "parametric" begin

        # ALMC 2.5
        m = MortalityTables.Gompertz(a=0.0003,b=log(1.07))

        @test life_expectancy(m,0) ≈ 71.938 atol=0.001
        @test life_expectancy(m,50) ≈ 26.691 atol=0.001
    end

    @testset "vector" begin
        # if no distribution of deaths given, assume curtate otherwise complete
        t = MortalityTables.table("1980 CSO Basic Table – Male, ANB")

        # calculate sum of tpx in Excel
        @test life_expectancy(t.ultimate,55) ≈ 22.16469212 atol=1e-6
        @test life_expectancy(t.ultimate,100) ≈ 0.0 atol=1e-6

        @test_throws ArgumentError life_expectancy(t,100)

        # relation of curtate to complete, ALMC 2.6.1
        @test life_expectancy(t.ultimate,55,MortalityTables.Uniform()) ≈ 22.16469212 + 0.5 atol=1e-3
    end

end


Q, R = qr([12 -51 4; 6 167 -68; -4 24 -41])


#=
A unit fraction contains 1 in the numerator. The decimal representation of the unit fractions with denominators 2 to 10 are given:

1/2	= 	0.5
1/3	= 	0.(3)
1/4	= 	0.25
1/5	= 	0.2
1/6	= 	0.1(6)
1/7	= 	0.(142857)
1/8	= 	0.125
1/9	= 	0.(1)
1/10	= 	0.1
Where 0.1(6) means 0.166666..., and has a 1-digit recurring cycle. It can be seen that 1/7 has a 6-digit recurring cycle.

Find the value of d < 1000 for which 1/d contains the longest recurring cycle in its decimal fraction part.
=#

# Multiplicative order
# http://mathworld.wolfram.com/MultiplicativeOrder.html
function multiplicative_order(a, n)
  if gcd(a,n) > 1
    return 0
  else
    order = 1
    mod_exp = a
    while mod_exp != 1
      order +=1
      mod_exp = (mod_exp * a) % n
    end
    return order
  end
end

function calc()
  largest = 0
  max_i = 0
  for i in 2:1000
    a = multiplicative_order(10, i)
    if a > largest
      largest = a
      max_i = i
    end
  end
  max_i
end
@time println(calc())


using FractionalCalculus
using Plots, LaTeXStrings

target = gamma(6)/gamma(2.4)*tspan.^1.4

s="\$D^{3.6}x^5\$"

tspan=collect(0:0.01:6)

result=fracdiff(x->x^5, 3.6, 6, 0.01, RLDiffMatrix())

plot(tspan, result, title=s, legend=:bottomright, label="Numerical")
plot!(tspan, target, lw=3, ls=:dash, label="Analytical")
savefig("./arbitrary_order_derivative.png")


import Statistics

"""
    simple_linear_regression(x::AbstractVector, y::AbstractVector)

Simple linear regression - given a set of two-dimensional points (x, y), use
the ordinary least squares method to find the best fit line of the form
y = a + b*x (where a and b are real numbers) and return the tuple (a, b).
"""
function simple_linear_regression(
        x::AbstractVector,
        y::AbstractVector,
        )::Tuple
    if length(x) != length(y)
        error("length(x) != length(y)")
    end
    if length(x) == 0
        error("length(x) == 0")
    end

    x_bar = Statistics.mean(x)
    y_bar = Statistics.mean(y)
    var_x = Statistics.var(x)
    cov_x_y = Statistics.cov(x,y)

    @assert(isfinite(x_bar))
    @assert(isfinite(y_bar))
    @assert(isfinite(var_x))
    @assert(isfinite(cov_x_y))

    coefficient = cov_x_y/var_x

    if isfinite(coefficient)
        intercept = y_bar - coefficient*x_bar
        @debug(
            string("Found best fit line: "),
            intercept,
            coefficient,
            )
    else
        @warn(
            string(
                "The best fit line does not have a finite slope. ",
                "I will ignore this result and will instead return ",
                "intercept = 0 and coefficient = 0",
                )
            )
        intercept = 0
        coefficient = 0
    end

    return intercept, coefficient
end


using LinearAlgebra
function Sparse_matrix_SINDYPI(Theta, dx, lambda, nvars, maxiter)

normLib = zeros(1, size(Theta,2))

Thetan = deepcopy(Theta)

    for norm_k=1:size(Theta,2)
            normLib[norm_k] = norm(Thetan[:,norm_k]);
            Thetan[:,norm_k] = Thetan[:,norm_k]/normLib[norm_k];
    end

   #Epsilon = Thetan\dx

#Below:Only to be used for ill conditioned matrices, like in the glycolysis example. Check on this
Epsilon = zeros(size(Thetan,2),1)
ldiv!(Epsilon, qr(Thetan), dx)

#=
   Epsilon_c = similar(Epsilon)
   Epsilon_c .= Epsilon
=#

    for i in 1:maxiter
        index_remove = abs.(Epsilon) .<= lambda
        Epsilon[index_remove] .= zero(eltype(Epsilon))

        for j in 1:nvars
            index_retain = @. !index_remove[:,j]
            Epsilon[index_retain, j] = Thetan[:, index_retain] \ dx[:,j]
        end
   #=
    if norm(Epsilon_c - Epsilon, 2) < eps()
        break
    else
        Epsilon_c .= Epsilon
    end
=#
    end

        for norm_k=1:length(Epsilon)
            Epsilon[norm_k,:] = Epsilon[norm_k,:]/normLib[norm_k];
        end

    return Epsilon
end

#=
A. Q factor

qrf.R

Ans = inv(qrf.R)*(qrf.Q)'*dx
=#


abstract type CovKer end

"""
    SqExp(σ)

Stores the bandwidth parameter `σ` of the squared exponential covariance kernel.

See also: [`(s_e::SqExp)(x, y)`](@ref)
"""
struct SqExp{F <: Real} <: CovKer
    neg_inv_double_σ_sq::F

    SqExp(σ::F = 1.0) where F <: Real = new{F}(-F(0.5)/(σ^2))
end

"""
    (s_e::SqExp)(x, y)

Evaluates the squared exponential covariance kernel at `(x, y)`.
"""
(s_e::SqExp)(x::V1, y::V2) where {V1 <: AbstractVector, V2 <: AbstractVector} =
    exp(dot((x - y),(x - y))*s_e.neg_inv_double_σ_sq)

"""
    (s_e::SqExp)(X)

Evaluates the s_e for all `(X_i, X_j)`.
"""

function (s_e::SqExp)(x_samples::AbstractMatrix{F}) where {F<:Real}
    num_samples = size(x_samples, 2)
    K = Array{F, 2}(undef, num_samples, num_samples)
    for i in 1:num_samples
        for j in i:num_samples
            K[i, j] = s_e(x_samples[:,i], x_samples[:,j])
        end
    end
    K
end


Metric(mat::T) where {N, T<:SMatrix{N, N, Float64}} = Metric{N, T}(Symmetric(mat))

function Metric(mat::AbstractMatrix{<:Real})
    N, M = size(mat)
    @assert N == M
    return Metric(SMatrix{N, N, Float64}(mat))
end

"""
    Metric(N::Int)

The Euclidean metric in `N` dimensions.
"""
Metric(N::Int) = Metric(SMatrix{N,N}(1.0I))

function Wedge(vectors::Vector{SVector{N, Float64}}) where N
    K = length(vectors)
    Wedge{N, K}(SVector{K, SVector{N, Float64}}(vectors))
end

"""
    Wedge(s::Simplex{N, K}) where {N, K}

The wedge product of all vectors emanating from the first vertex of a simplex.
"""
function Wedge(s::Simplex{N, K}) where {N, K}
    vectors = SVector{N, Float64}[p.coords - s.points[1].coords for p in s.points[2:end]]
    return Wedge(vectors)
end

export inner_product
"""
    inner_product(m::Metric{N}, v1::SVector{N, Float64}, v2::SVector{N, Float64}) where N
    inner_product(m::Metric{N}, w1::Wedge{N, K}, w2::Wedge{N, K}) where {N, K}

Compute the inner product of two vectors with respect to the given metric.
"""
inner_product(m::Metric{N}, v1::SVector{N, Float64}, v2::SVector{N, Float64}) where N =
    transpose(v1) * m.mat * v2

function inner_product(m::Metric{N}, w1::Wedge{N, K}, w2::Wedge{N, K}) where {N, K}
    if K == 0
        return 1.0
    else
        inner_products = hcat([[inner_product(m, v1, v2) for v1 in w1.vectors]
            for v2 in w2.vectors]...)
        return det(inner_products)
    end
end

export norm_square
"""
    norm_square(m::Metric{N}, v::SVector{N, Float64}) where N
    norm_square(m::Metric{N}, w::Wedge{N}) where N

Compute the inner product of a vector with itself with respect to the given metric. Note
that this value can be negative if the metric is not positive semi-definite.
"""
norm_square(m::Metric{N}, v::SVector{N, Float64}) where N = inner_product(m, v, v)

norm_square(m::Metric{N}, w::Wedge{N}) where N = inner_product(m, w, w)

import LinearAlgebra: norm
"""
    norm(m::Metric{N}, v::SVector{N, Float64}) where N
    norm(m::Metric{N}, w::Wedge{N}) where N

Compute the square root of the magnitude of the inner product of a vector with itself with
respect to the metric `m`.
"""
norm(m::Metric{N}, v::SVector{N, Float64}) where N = sqrt(abs(norm_square(m, v)))

norm(m::Metric{N}, w::Wedge{N}) where N = sqrt(abs(norm_square(m, w)))

export volume_square
"""
    volume_square(m::Metric{N}, s::Simplex{N, K}) where {N, K}

Compute the squared volume of a simplex with respect to the given metric. Note that this
value can be negative if the metric is not positive semi-definite.
"""
volume_square(m::Metric{N}, s::Simplex{N, K}) where {N, K} =
    norm_square(m, Wedge(s))/factorial(K-1)^2

export volume
"""
    volume(m::Metric{N}, s::Simplex{N, K}) where {N, K}

Compute the volume of a simplex with respect to the given metric.
"""
volume(m::Metric{N}, s::Simplex{N, K}) where {N, K} =
    sqrt(abs(norm_square(m, Wedge(s))))/factorial(K-1)


"""
Jarque–Bera Statistic as a normality measure for optimization
"""
function normality(x::AbstractVector{T}) where T<:Real

    P = Base.promote_op(/, T, T)

    # moments at origin
    n = P(length(x))
    m1 = mo2 = mo3 = mo4 = P(0)

    @inbounds for xi in x
        m1 += x1 = xi / n
        mo2 += x2 = x1 * x1 * n
        mo3 += x3 = x1 * x2 * n
        mo4 += x4 = x1 * x3 * n
    end

    # moments
    m2 = mo2 - m1^2
    m3 = mo3 - P(3)*m1*mo2 + P(2)*m1^3
    m4 = mo4 - P(4)*m1*mo3 + P(6)*m1^2*mo2 - P(3)*m1^2*m1^2

    S = m3 / (m2 * sqrt(m2))
    K = m4 / m2^2

    n/P(6) * (S^2 + P(1/4) * (K-P(3))^2)

end


"""
    volatility(returns; multiplier=1.0)

Calculates the volatility based on the standard deviation of the returns. The optional `multiplier` parameter allows for scaling the resulting volatility metric, i.e. for annualization.

# Formula

    Vol = std(returns) * sqrt(multiplier)

# Arguments
- `returns`:    Vector of asset returns (usually log-returns).
- `multiplier`: Optional scalar multiplier, i.e. use `12` to annualize monthly returns, and use `252` to annualize daily returns.
"""
function volatility(returns; multiplier=1.0)
    std(returns) * sqrt(multiplier)
end


module gauss_model

# Provide the functionality to model Gaussians as test cases
using DiskJockey.constants

export imageGauss, FTGauss

# Because of the flipped nature of the sky (but not flipped nature of the UV plane)
# there are some tricky conventions about how to pack the array.

# Given two arrays of l and m coordinates, fill an array of the Gaussian image
# following the MATLAB convention.
# p0 is a vector of [mu_RA, mu_DEC, sigma_x, sigma_y] in units of arcseconds
# mu_RA and mu_DEC are the locations of the centroid emission relative to the
# image origin (RA=0, DEC=0).
function imageGauss(ll::Vector{Float64}, mm::Vector{Float64}, p::Vector{Float64}, k::Int)

    # Both ll and mm increase with array index
    nx = length(ll)
    ny = length(mm)

    img = Array(Float64, ny, nx)
    mu = p[1:2] * arcsec #ll and mm shifts
    Sigma = Diagonal((p[3:4] * arcsec).^2) #Convert from arcsec to radians
    pre = 1. / (2pi * sqrt(det(Sigma))) * k
    for j=1:ny
        for i=1:nx
            R = Float64[ll[i] , mm[j]] - mu
            img[j, i] = pre * exp(-0.5 * (R' * (Sigma\R))[1])
        end
    end
    return img
end

# Given u and v coordinates in [kλ], evaluate the analytic FT of the
# aforementioned Gaussian
# N.B. Here Sigma refers to the (same) covariance matrix in the *image* domain
# always return a complex value
function FTGauss(uu::Float64, vv::Float64, p::Vector{Float64}, k::Int)
    uu = uu .* 1e3 #[λ]
    vv = vv .* 1e3 #[λ]
    mu_RA, mu_DEC = p[1:2]
    mu = Float64[mu_RA, mu_DEC] * arcsec #ll and mm shifts
    R = Float64[uu, vv]
    Sigma = Diagonal((p[3:4] * arcsec).^2) #Convert from arcsec to radians
    phase_shift = exp(-2pi * 1.0im * (R' * mu)[1]) # Not actually in polar phase form
    return k * exp(-2 * (pi^2) * (R' * Sigma * R)[1]) * phase_shift
    # in this case, Sigma serves as the inverse
end

# Given two arrays of u and v coordinates in [kλ], fill an array with the
# analytic FT of aforementioned Gaussian evaluated at every pairwise (u,v) pair
function FTGauss(uu::Vector{Float64}, vv::Vector{Float64}, p::Vector{Float64}, k::Int)
    nu = length(uu)
    nv = length(vv)
    # Both uu and vv increase with array index
    img = Array(Complex128, nv, nu)
    for j=1:nv
        for i=1:nu
            img[j, i] = FTGauss(uu[i], vv[j], p, k)
        end
    end
    return img
end

end # Module


import Base.cross, Base.dot, Base.-, Base.+, Base.*, Base./

type Vector
	x::Real
	y::Real
	z::Real
end

# Outer Constructors

Vector() = Vector(0, 0, 0)

# Methods on Vector

function toList(a::Vector)
	return [a.x, a.y, a.z]
end

function toVec(a::Array)
	return Vector(a[1], a[2], a[3])
end

function +(a::Vector, b::Vector)
	return toVec(toList(a) .+ toList(b))
end

function -(a::Vector, b::Vector)
	return toVec(toList(a) .- toList(b))
end

function *(a::Vector, b::Real)
	return toVec(toList(a) .* b)
end

function magnitude(a::Vector)
	return sqrt(a.x ^ 2 + a.y ^ 2 + a.z ^ 2)
end

function normal(a::Vector)
	mag = magnitude(a)
	return toVec(toList(a) ./ mag)
end

function dot(a::Vector, b::Vector)
	return dot(toList(a), toList(b))
end

function cross(a::Vector, b::Vector)
	return toVec(cross(toList(a), toList(b)))
end


export expMod, boundMod, fMod,identityMod

"""
	sigma,dsigma = expMod(model)

	maps model parameter to conductivity via

	sigma(m) = exp(m)

"""
function expMod(m)
	return exp.(m), sdiag(exp.(m))
end

"""
	sigma,dsigma = fMod(model;f::Function=identity,df::Function=m->sparse(1.0I,length(m),length(m)))

	maps model parameter to conductivity via

	sigma(m) = f(m) and dsigma(m) = sdiag(df(m))

"""
function fMod(m;f::Function=identity,df::Function=m->ones(eltype(m),length(m)))
	return f(m),sdiag(df(m))
end

function identityMod(m)
	return m, UniformScaling(one(eltype(m)))
end

export boundMod
"""
	sigma,dsigma = boundMod(m;boundLow=0.0,boundHigh=1.0)

	maps model parameter to conductivity via

		sigma = 0.5*(boundHigh-boundLow) * (tanh(m)+1.0 ) + boundLow

"""
function boundMod(m; boundLow = 0.0, boundHigh = 1.0)
	u        = tanh.(m)
	d        = 0.5 * (boundHigh - boundLow)
	sigma    = d * (u .+ 1.0) .+ boundLow
	dsigmadm = sdiag(d * (1.0 .- u .* u))
	return sigma, dsigmadm
end


using Measurements
include("/home/jefter66/Projects/Lablib/src/Stats.jl")

printstyled("Experimento 1 \n ", color =:green);
# densidade ρ da corda

## densidade pela geometria

m1 = 0.00552 ± 0.00001 # kg
l = 22.97 ± 0.01 # m

ρ1 = m1.val/l.val ± (m1.val*l.err + m1.err*l.val)/l.val^2
printstyled("Densidade linear calculada pela geometria \n"; color=:red);
println("ρ ± Δρ = (",ρ1, ") kg/m");

## densidade pela tração
m2 = 0.06067 ± 0.00001 #kg
v = [48.9, 49.5, 49.8, 49.7, 49.3]
vm = Statistics.mean(v);
v = round(vm,digits=3) ± round(standartDeviation(v),digits=2);
printstyled("Velocidade média e erro calculado pelo desvio padrão\n"; color=:red);
println("v_medio ± Δv = (",v, ") m/s")

# tração
g = 9.81
t = m2.val*g ± m2.err*g# Newtons
printstyled("Tração:\n"; color=:red);
println("T ± ΔT = (", t, ") N");

# densidade linear calculada a partir da tração
ρ2 = t.val/v.val^2 ± (2*t.val*v.val*v.err + t.err*v.val^2)/(v.val^4)
printstyled("Densidade linear \n "; color =:red);
println("ρ2 ± Δρ2= (", ρ2 , ") kg/m");

# equivalência

v1 = abs(ρ2.val - ρ1.val);
v2 = 2*(ρ1.err + ρ2.err);

printstyled("Checando equivalência", color=:red);
println("| x1 - x2 | < 2*(σ1 + σ2)");
println("| ",round(ρ1.val,digits=10) , " - ", round(ρ2.val,digits=10) , " | < 2*(", round(ρ1.err,digits=10), " + ", round(ρ2.err,digits=10), ")");
println(v1<v2);

# Ondas sonoras
printstyled("Experimento 2\n", color =:green);

## comprimento constante
printstyled("Comprimento constante \n ", color =:red);
L = (0.1100 ± 0.0001);
f = []
n = 5

## frequência constante
printstyled("Experimento 3\n", color =:green);

f = (2000±0.1);
L = [0.087,0.088,0.086,0.087];
ΔL = 0.1;

λ(L) = 2*L;

λs = Statistics.mean([λ(L[1]),λ(L[2]),λ(L[3]),λ(L[4])]) ± standartDeviation(L);

printstyled("Frequência constante \n ", color =:red);

println("Comprimento de onda médio: λ ± Δλ = ", λs);

v =  λs*f;
println("Velocidade média: v ± Δv = ",v);

## gás desconhecido
printstyled("Experimento 4 - Gás desconhecido 1° harmônico \n ", color =:red);
f = (2000±0.1);
n = 1
l = (0.23 ±0.0001);

v = 2*l*f;
println("Velocidade média: v ± Δv = ",v);


using Plots

include("QuadraturePoints.jl")
include("element_matrices.jl")

#Some Constants
DFloat = Float64
machine_zero=eps(DFloat)

#ipoints=1: Lobatto
#ipoints=2: Legendre
#ipoints=3: Chebyshev
#ipoints=4: Equi-spaced

#{{{ Main
function main()

    #Fix Interpolation and Integration Order
    N=1
    Q=N+1
    Np=N+1
    Nq=Q+1
    ipoints=1
    qpoints=1

    #Allocate Arrays
    M=zeros(DFloat,Np,Np)
    D=zeros(DFloat,Np,Np)

    #Select Interpolation Points
    if ipoints == 1
        (ξ,ω) = QuadraturePoints.lobatto_gauss(Np,DFloat) #Lobatto
    elseif ipoints == 2
        (ξ,ω) = QuadraturePoints.legendre_gauss(Np,DFloat) #Legendre
    elseif ipoints == 3
        (ξ,ω) = QuadraturePoints.chebyshev_gauss(Np,DFloat) #Chebyshev
    elseif ipoints == 4
        (ξ,ω) = QuadraturePoints.equispaced_points(Np,DFloat) #Equi-spaced
    end

    #Select Quadrature Points
    if qpoints == 1
        (ξq,ωq) = QuadraturePoints.lobatto_gauss(Nq,DFloat) #Lobatto
    elseif qpoints == 2
        (ξq,ωq) = QuadraturePoints.legendre_gauss(Nq,DFloat) #Legendre
    end

    #Construct Lagrange Polynomials
    (ψ,dψ) = QuadraturePoints.lagrange_basis(Np,Nq,ξ,ξq)

    (M,D) = element_matrices(ψ,dψ,Np,Nq,ωq,DFloat)

    #Check correctness
    for j=1:Np, i=1:Np
        @show(i,j,M[i,j])
    end
    row_sum=zeros(DFloat,Np)
    for i=1:Np
        row_sum[i]=sum(D[i,:])
    end
    @show(row_sum)
    @show(sum(M))

    #Plot Interpolation
    println("Done") #output

end
#}}} Main

#----------------------------------#
# Run the main function
#----------------------------------#
main()


export orthog!,
       svd_recursive

function orthog!(M::AbstractMatrix{T};
                 npass::Int=2) where {T}
  nkeep = min(size(M)...)
  dots = zeros(T,nkeep)
  for i=1:nkeep
    coli = view(M,:,i)
    nrm = norm(coli)
    if nrm < 1E-10
      rand!(coli)
      nrm = norm(coli)
    end
    coli ./= nrm
    (i==1) && continue

    Mcols = view(M,:,1:i-1)
    dotsref = view(dots,1:i-1)
    for pass=1:npass
      mul!(dotsref,Mcols',coli)
      #BLAS.gemv!('N',1.0,Mcols,dotsref,-1.0,coli)
      coli .-= Mcols*dotsref
      nrm = norm(coli)
      if nrm < 1E-3 #orthog is suspect
        pass = pass-1
      end
      if nrm < 1E-10
        rand!(coli)
        nrm = norm(coli)
      end
      coli ./= nrm
    end
  end
end

function pos_sqrt(x::Float64)::Float64
  (x < 0.0) && return 0.0
  return sqrt(x)
end

function checkSVDDone(S::Vector,
                      thresh::Float64)
  N = length(S)
  (N <= 1 || thresh < 0.0) && return (true,1)
  S1t = S[1]*thresh
  start = 2
  while start <= N
    (S[start] < S1t) && break
    start += 1
  end
  if start >= N
    return (true,N)
  end
  return (false,start)
end

function svd_recursive(M::AbstractMatrix;
                      thresh::Float64=1E-3,
                      north_pass::Int=2)
  Mr,Mc = size(M)

  if Mr > Mc
    V,S,U = svd_recursive(transpose(M))
    conj!(U)
    conj!(V)
    return U,S,V
  end

  #rho = BLAS.gemm('N','T',-1.0,M,M) #negative to sort eigenvalues greatest to smallest
  rho = -M*M' #negative to sort eigenvalues in decreasing order
  D,U = eigen(Hermitian(rho),1:size(rho,1))

  Nd = length(D)
  for n=1:Nd
    D[n] = pos_sqrt(-D[n])
  end

  V = M'*U
  orthog!(V,npass=north_pass)

  (done,start) = checkSVDDone(D,thresh)

  done && return U,D,V

  u = view(U,:,start:Nd)
  v = view(V,:,start:Nd)

  b = u'*(M*v)
  bu,bd,bv = svd_recursive(b,
                          thresh=thresh,
                          north_pass=north_pass)

  u .= u*bu
  v .= v*bv
  view(D,start:Nd) .= bd

  return U,D,V
end

# TODO: maybe move to another location?
function polar(M::AbstractMatrix)
  U,S,V = svd(M) # calls LinearAlgebra.svd()
  return U*V',V*Diagonal(S)*V'
end


# Dirichlet
struct Dirichlet{P<:Tuple{Vararg{Real}}} <: Distribution{Tuple{Vararg{Real}},P}
    params :: P
    Dirichlet(; α) = new{typeof(α)}(α)
end

# Dirichlet(α::Real...) = Dirichlet(α)
# Dirichlet(α::Integer...) = Dirichlet(Float64.(α))
# Dirichlet(n::Integer, α::Real)= Dirichlet(ntuple(_->α, n))

function randDirichlet(α::Tuple{Vararg{T}}) where T<:Real
    p = Tuple(randGamma(αi, one(T)) for αi in α)
    p ./ sum(p)
end
randDirichlet(α::Tuple{Vararg{Integer}}) = randDirichlet(Float64.(α))
randDirichlet(n::Integer, α::Real) = randDirichlet(Tuple(α for _ in 1:n))
randDirichlet(n::Integer, α::Integer) = randDirichlet(n, Float64(α))

@cufunc function logpdfDirichlet(x::NTuple{N,Real}, α::NTuple{N,Real}) where N
    # a, b = sum(u -> SVector(u,lgamma(u)), α)
    # s = sum(((u,v) -> (u-one(u))log(v)).(α, x))
    # s - b + lgamma(a)
    T = promote_type(typeof.(x)..., typeof.(α)...)
    a = b = s = zero(T)
    for (xi, αi) in zip(x, α)
        a += T(αi)
        b += lgamma(T(αi))
        s += (T(αi) - one(T))log(T(xi))
    end
    s - b + lgamma(a)
end


using CompScienceMeshes
using BEAST
using LinearAlgebra

trias = CompScienceMeshes.meshrectangle(1.0,1.0,0.05)
trias = CompScienceMeshes.meshcircle(1.0,0.5)
tetrs = CompScienceMeshes.tetmeshcuboid(1,1,1,0.2)

f = BEAST.ScalarTrace(x -> sin(pi*x[1]*x[2])*sin(pi*x[2]))
f2 = BEAST.ScalarTrace(x -> (-x[2]*sin(2*pi*(x[1]^2+x[2]^2))*sin(atan(x[2],x[1])), x[1]*sin(2*pi*(x[1]^2+x[2]^2))*sin(atan(x[2],x[1])), 0) )
g = BEAST.ScalarTrace(x -> (sin(pi*x[1])*cos(pi*x[2]), -cos(pi*x[1])*sin(pi*x[2]), 0 ))
g = BEAST.ScalarTrace(x -> (sin(pi*x[1])*cos(pi*x[2]), sin(pi*x[2])*cos(pi*x[2]), sin(pi*x[3])*cos(pi*x[2])))
g2 = BEAST.ScalarTrace(x -> ( sin(pi*x[1])*cos(pi*x[3]),  0, -cos(pi*x[1])*sin(pi*x[3])))

N = 4

h = Vector(undef, N)
errorL = Vector(undef, N)
errorBDM = Vector(undef, N)
errorRT = Vector(undef, N)

for n in 1:N

    h[n] = 0.5^(n)

    trias2 = CompScienceMeshes.meshrectangle(1.0,1.0, h[n])

    trias = CompScienceMeshes.meshcircle(1.0, h[n], 3)

    Z = BEAST.lagrangec0d1(trias, dirichlet=false)
    Y = BEAST.brezzidouglasmarini(trias)
    X = BEAST.raviartthomas(trias)

    u_exact = DofInterpolate(Z,f)
    b_exact = DofInterpolate(Y,f2)
    r_exact = DofInterpolate(X,f2)

    identity = BEAST.Identity()

    M = assemble(identity, Z, Z)
    b = assemble(f,Z)
    u_n = M \ b

    errorL[n] = sqrt((u_n-u_exact)'*M*(u_n-u_exact))

    M = assemble(identity, Y, Y)
    b = assemble(f2,Y)
    b_n = M \ b

    errorBDM[n] = sqrt((b_n-b_exact)'*M*(b_n-b_exact))

    M = assemble(identity, X, X)
    b = assemble(f2,X)
    r_n = M \ b

    errorRT[n] = sqrt((r_n-r_exact)'*M*(r_n-r_exact))

end

N = 15

delta = Vector(undef, N)
error = Vector(undef, N)

for i in 1:N

    delta[i] = 0.5-0.025*i

    tetrs =  tetmeshcuboid(1.0,1.0,1.0, delta[i])

    bndry = boundary(tetrs)
    faces = skeleton(tetrs, 2)

    bndry_faces = [sort(c) for c in cells(skeleton(bndry, 2))]
    function is_interior(faces)
        !(sort(faces) in bndry_faces)
    end

    interior_faces = submesh(is_interior, faces)

    W = BEAST.brezzidouglasmarini3d(tetrs, interior_faces)

    u_exact = DofInterpolate(W,g2)

    identity = BEAST.Identity()

    M = assemble(identity, W, W)

    b = assemble(g2,W)
    u_n = M \ b

    error[i] = real(sqrt((u_n-u_exact)'*M*(u_n-u_exact)))
end

N=15

delta2 = Vector(undef, N)
error2 = Vector(undef, N)

for i in 1:N

    delta2[i] =0.5-0.025*i

    tetrs =  tetmeshcuboid(1.0,1.0,1.0, delta2[i])

    bndry = boundary(tetrs)
    faces = skeleton(tetrs, 2)

    bndry_faces = [sort(c) for c in cells(skeleton(bndry, 2))]
    function is_interior(faces)
        !(sort(faces) in bndry_faces)
    end

    interior_faces = submesh(is_interior, faces)

    V = BEAST.nedelecd3d(tetrs, interior_faces)

    q_exact = DofInterpolate(V,g)

    identity = BEAST.Identity()

    M2 = assemble(identity, V, V)

    b2 = assemble(g,V)

    q_n = M2 \ b2

    error2[i] = real(sqrt((q_n-q_exact)'*M2*(q_n-q_exact)))
end

using Plots

p2 = plot(delta,error, label="BDM3D", title="L2-Error on [0,1]^3",xlabel="h", ylabel="Error", legend=:bottomright, xaxis=:log, yaxis=:log)
plot!(p2, delta2, error2, label="Nedelec Div", xlims=[0.075,0.55])

p1 = plot(h,real(errorL), label="Lagrange", title="L2-Error on [0,1]^2",xlabel="h", ylabel="Error", legend=:bottomright, xaxis=:log, yaxis=:log, xlims=[0.025,0.5])
plot!(p1,h,real(errorBDM),label="BDM")
plot!(p1,h,real(errorRT),label="Raviart-Thomas")

plot(p1,p2, layout=2)


# ---
# title: 892. Surface Area of 3D Shapes
# id: problem892
# author: Tian Jun
# date: 2020-10-31
# difficulty: Easy
# categories: Math, Geometry
# link: <https://leetcode.com/problems/surface-area-of-3d-shapes/description/>
# hidden: true
# ---
#
# On a `N * N` grid, we place some `1 * 1 * 1 `cubes.
#
# Each value `v = grid[i][j]` represents a tower of `v` cubes placed on top of
# grid cell `(i, j)`.
#
# Return the total surface area of the resulting shapes.
#
#
#
# **Example 1:**
#
#
#
#     Input: [[2]]
#     Output: 10
#
#
# **Example 2:**
#
#
#
#     Input: [[1,2],[3,4]]
#     Output: 34
#
#
# **Example 3:**
#
#
#
#     Input: [[1,0],[0,2]]
#     Output: 16
#
#
# **Example 4:**
#
#
#
#     Input: [[1,1,1],[1,0,1],[1,1,1]]
#     Output: 32
#
#
# **Example 5:**
#
#
#
#     Input: [[2,2,2],[2,1,2],[2,2,2]]
#     Output: 46
#
#
#
#
# **Note:**
#
#   * `1 <= N <= 50`
#   * `0 <= grid[i][j] <= 50`
#
#
## @lc code=start
using LeetCode

## add your code here:
## @lc code=end


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

"""
    sec2rad(sec) -> radians

### Purpose ###

Convert from seconds to radians.

### Argument ###

* `sec`: number of seconds.

### Output ###

The number of radians corresponding to `sec`.

### Example ###

```jldoctest
julia> using AstroLib

julia> sec2rad(3600 * 30)
0.5235987755982988
```

### Notes ###

Use `rad2sec` to convert radians to seconds.
"""
sec2rad(sec::Real) = deg2rad(sec / 3600)


# f.jl
# F-tests
#
# Copyright (C) 2019   Raphael Saavedra
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

export VarianceFTest

struct VarianceFTest <: HypothesisTest
    n_x::Int   # size of sample 1
    n_y::Int   # size of sample 2
    df_x::Int  # degrees of freedom 1
    df_y::Int  # degrees of freedom 2
    F::Real    # test statistic
end

"""
    VarianceFTest(x::AbstractVector{<:Real}, y::AbstractVector{<:Real})

Perform an F-test of the null hypothesis that two real-valued vectors `x` and `y` have equal variances.

Implements: [`pvalue`](@ref)

# References

  * George E. P. Box, "Non-Normality and Tests on Variances", Biometrika 40 (3/4): 318–335, 1953.

# External links

  * [F-test of equality of variances on Wikipedia](https://en.wikipedia.org/wiki/F-test_of_equality_of_variances)
"""
function VarianceFTest(x::AbstractVector{<:Real}, y::AbstractVector{<:Real})
    n1, n2 = length(x), length(y)
    F = var(x) / var(y)
    return VarianceFTest(n1, n2, n1-1, n2-1, F)
end

testname(::VarianceFTest) = "Variance F-test"
population_param_of_interest(x::VarianceFTest) = ("variance ratio", 1.0, x.F)
default_tail(test::VarianceFTest) = :both

function show_params(io::IO, x::VarianceFTest, ident)
    println(io, ident, "number of observations: [$(x.n_x), $(x.n_y)]")
    println(io, ident, "F statistic:            $(x.F)")
    println(io, ident, "degrees of freedom:     [$(x.df_x), $(x.df_y)]")
end

function pvalue(x::VarianceFTest; tail=:both)
    dist = FDist(x.df_x, x.df_y)
    if tail == :both
        return 1 - 2*abs(cdf(dist, x.F) - 0.5)
    elseif tail == :right
        return 1 - cdf(dist, x.F)
    elseif tail == :left
        return cdf(dist, x.F)
    else
        throw(ArgumentError("tail=$(tail) is invalid"))
    end
end


using OrdinaryDiffEq, Test, DiffEqDevTools, DiffEqOperators
using LinearAlgebra, Random

Random.seed!(0); u0 = rand(2)
A = DiffEqArrayOperator([2.0 -1.0; -1.0 2.0])

# B = DiffEqArrayOperator(ones(2,2))
# L = AffineDiffEqOperator{Float64}((A,B),(),rand(2))
# function (p::typeof(L))(::Type{Val{:analytic}},u0,p,t)
#     exp((p.As[1].A+p.As[2].A)*t)*u0
# end

# # Midpoint splitting

# prob = ODEProblem(L,u0,(0.0,1.0))
# sol = solve(prob,MidpointSplitting(),dt=1/10)
# # using Plots; pyplot; plot(sol)

# ## Midpoint splitting convergence
# ##
# ## We use the inhomogeneous Lorentz equation for an electron in a
# ## time-dependent field. To write this on matrix form and simplify
# ## comparison with the analytic solution, we introduce two dummy
# ## variables:
# ## 1) As the third component, a one is stored to allow the
# ##    inhomogeneous part to be expressed on matrix form.
# ## 2) As the fourth component, the initial time t_i is stored,
# ##    for use by the analytical formula.
# ## This wastes a lot of space, but simplifies the error analysis.
# ##
# ## We can then write the Lorentz equation as q̇ = [A + f(t)B]q.

# f = t -> -sin(2pi*t)
# F = t -> cos(2pi*t)/2pi # Primitive function of f(t)

# A = DiffEqArrayOperator([0 1 0 0
#                          0 0 0 0
#                          0 0 0 0
#                          0 0 0 0])

# B = DiffEqArrayOperator([0 0 0 0
#                          0 0 1 0
#                          0 0 0 0
#                          0 0 0 0], f)

# H = AffineDiffEqOperator{Float64}((A,B),(),rand(4))
# function (p::typeof(H))(::Type{Val{:analytic}},u0,p,t)
#     x0,v0 = u0[1:2]
#     ti = u0[end]
#     x = x0 + (t-ti)*v0 - (f.(t)-f(ti))/(2pi)^2 - (t-ti)*F(ti)
#     v = v0 + (F.(t)-F(ti))
#     [x, v, 1, ti]
# end

# x0,v0,ti = rand(3)
# prob = ODEProblem(H, [x0, v0, 1, ti], (ti, 5.))
# dts = 1./2.^(10:-1:1)
# sim  = test_convergence(dts,prob,MidpointSplitting())
# @test sim.𝒪est[:l2] ≈ 2 atol=0.2

# Linear exponential solvers
prob = ODEProblem(A,u0,(0.0,1.0))
sol1 = solve(prob, LinearExponential(krylov=:off))(1.0)
sol2 = solve(prob, LinearExponential(krylov=:simple))(1.0)
sol3 = solve(prob, LinearExponential(krylov=:adaptive))(1.0)
sol_analytic = exp(1.0 * Matrix(A)) * u0

@test isapprox(sol1, sol_analytic, rtol=1e-10)
@test isapprox(sol2, sol_analytic, rtol=1e-10)
@test isapprox(sol3, sol_analytic, rtol=1e-10)


"""
    capm(asset_returns, benchmark_returns; risk_free=0.0)

Calculates the CAPM α and β coefficients based on sample covariance statistics and a simple linear regression model.

The linear regression model looks as follows:

        r_a - r_f = α + β(r_b - r_f) + ϵ

The α coefficient in this model is also known as Jensen's alpha. β is the slope coefficient, and ϵ is an error term.

# Arguments
- `asset_returns`:      Vector of asset returns.
- `benchmark_returns`:  Vector of benchmark returns (e.g. market portfolio returns for CAPM beta).
- `risk_free`:          Optional vector or scalar value denoting the risk-free return (must have same frequency as the provided returns, e.g. daily).

# Returns
Tuple (α, β) with the estimated α and β coefficients of the CAPM model.
"""
function capm(asset_returns, benchmark_returns; risk_free=0.0)
    asset_returns_ex = asset_returns .- risk_free
    benchmark_returns_ex = benchmark_returns .- risk_free
    μ1 = mean(asset_returns_ex)
    μ2 = mean(benchmark_returns_ex)
    β = sum((asset_returns_ex .- μ1).*(benchmark_returns_ex .- μ2)) / sum((benchmark_returns_ex .- μ2).^2)
    α = μ1 - β*μ2
    (α, β)
end


function scalsq(A::AbstractArray{T,2}, b::AbstractVector{T}, w = nothing) where {T<:Number}
#
#  Solving the scaled least squares problem
#          W(Ax - b) = 0
#      with iterative refinement.
#
#  input  A --- the matrix
#         b --- the right-hand side vector
#         w --- the scaling vector (diagonal entries of W)
#
#
    m, n = size(A)
	E = real(one(T))

    if w == nothing
        w = ones(T, m)
        for j = 1:m
            abj = abs(b[j])
            if abj > E
                w[j] = E / abj
            end
        end
    end

    for j = 1:m
        A[j,1:end] = A[j,1:end] * w[j]
    end

    b = b .* w

    Q, S = qr(A)

    d = Q' * b
    x = backsub(S, d)

    # one step refinement
    bb = [b; zeros(T, n)]
	B = [I A; A' zeros(T, n, n)]
    r = b - A * x

    #for j = 1:3
    rr = bb - B * [r; x]; #disp(norm(rr));

    s = Q' * rr[1:m]
    c = forsub(S', rr[m+1:m+n])
	c2 = backsub(S, s[1:n] - c)
    #c1 = Q * [c; s[n+1:m]]
    #r = r + c1
    x = x + c2
    #%end;
    x
end


using LinearAlgebra, DiffEqOperators, Random, Test, BandedMatrices, SparseArrays

function fourth_deriv_approx_stencil(N)
    A = zeros(N,N+2)
    A[1,1:8] = [3.5 -56/3 42.5 -54.0 251/6 -20.0 5.5 -2/3]
    A[2,1:8] = [2/3 -11/6 0.0 31/6 -22/3 4.5 -4/3 1/6]
    A[N-1,N-5:end] = reverse([2/3 -11/6 0.0 31/6 -22/3 4.5 -4/3 1/6], dims=2)
    A[N,N-5:end] = reverse([3.5 -56/3 42.5 -54.0 251/6 -20.0 5.5 -2/3], dims=2)
    for i in 3:N-2
        A[i,i-2:i+4] = [-1/6 2.0 -13/2 28/3 -13/2 2.0 -1/6]
    end
    return A
end

function second_derivative_stencil(N)
  A = zeros(N,N+2)
  for i in 1:N, j in 1:N+2
      (j-i==0 || j-i==2) && (A[i,j]=1)
      j-i==1 && (A[i,j]=-2)
  end
  A
end

@testset "Differentiation on 2D array" begin
    M = zeros(22,22)
    M_temp = zeros(20,22)
    indices = Iterators.drop(CartesianIndices((22,22)), 0)
    for idx in indices
        M[idx] = sin(idx[1]*0.1)
    end
    L = CenteredDifference(4,4,0.1,20)

    #test mul!
    mul!(M_temp, L, M)

    correct_row = 10000.0*fourth_deriv_approx_stencil(20)*M[:,1]

    for i in 1:22
        @test M_temp[:,i] ≈ correct_row
    end

    # Test that * agrees will mul!
    @test M_temp == L*M

    # Differentiation along second dimension
    L = CenteredDifference{2}(4,4,0.1,20)
    M_temp_2 = zeros(22,20)
    indices = Iterators.drop(CartesianIndices((22,22)), 0)
    for idx in indices
        M[idx] = sin(idx[2]*0.1)
    end

    #test mul!
    mul!(M_temp_2, L, M)
    for i in 1:22
        @test M_temp_2[i,:] ≈ correct_row
    end

    # Test that * agrees will mul!
    @test M_temp_2 == L*M
end

@testset "Differenting on 3D array with L2" begin
    M = zeros(22,22,22)
    M_temp = zeros(20,22,22)
    indices = Iterators.drop(CartesianIndices((22,22,22)), 0)
    for idx in indices
        M[idx] = sin(idx[1]*0.1)
    end
    L = CenteredDifference(2,2,0.1,20)

    #test mul!
    mul!(M_temp, L, M)

    correct_row = 100.0*second_derivative_stencil(20)*M[:,1,1]

    for i in 1:22
        for j in 1:22
            @test M_temp[:,i,j] ≈ correct_row
        end
    end

    # Test that * agrees will mul!
    @test M_temp == L*M

    # Differentiation along second dimension
    L = CenteredDifference{2}(2,2,0.1,20)
    M_temp_2 = zeros(22,20,22)
    indices = Iterators.drop(CartesianIndices((22,22,22)), 0)
    for idx in indices
        M[idx] = sin(idx[2]*0.1)
    end

    #test mul!
    mul!(M_temp_2, L, M)
    for i in 1:22
        for j in 1:22
            @test M_temp_2[i,:,j] ≈ correct_row
        end
    end

    # Test that * agrees will mul!
    @test M_temp_2 == L*M

    # Differentiation along third dimension
    L = CenteredDifference{3}(2,2,0.1,20)
    M_temp_3 = zeros(22,22,20)
    indices = Iterators.drop(CartesianIndices((22,22,22)), 0)
    for idx in indices
        M[idx] = sin(idx[3]*0.1)
    end

    #test mul!
    mul!(M_temp_3, L, M)
    for i in 1:22
        for j in 1:22
            @test M_temp_3[i,j,:] ≈ correct_row
        end
    end

    # Test that * agrees will mul!
    @test M_temp_3 == L*M
end

@testset "Differentiation on 3D array with L4" begin
    M = zeros(22,22,22)
    M_temp = zeros(20,22,22)
    indices = Iterators.drop(CartesianIndices((22,22,22)), 0)
    for idx in indices
        M[idx] = sin(idx[1]*0.1)
    end
    L = CenteredDifference(4,4,0.1,20)

    #test mul!
    mul!(M_temp, L, M)

    correct_row = 10000.0*fourth_deriv_approx_stencil(20)*M[:,1,1]

    for i in 1:22
        for j in 1:22
            @test M_temp[:,i,j] ≈ correct_row
        end
    end

    # Test that * agrees will mul!
    @test M_temp == L*M

    # Differentiation along second dimension
    L = CenteredDifference{2}(4,4,0.1,20)
    M_temp_2 = zeros(22,20,22)
    for idx in indices
        M[idx] = sin(idx[2]*0.1)
    end

    #test mul!
    mul!(M_temp_2, L, M)
    for i in 1:22
        for j in 1:22
            @test M_temp_2[i,:,j] ≈ correct_row
        end
    end

    # Test that * agrees will mul!
    @test M_temp_2 == L*M

    # Differentiation along third dimension
    L = CenteredDifference{3}(4,4,0.1,20)
    M_temp_3 = zeros(22,22,20)
    for idx in indices
        M[idx] = sin(idx[3]*0.1)
    end

    #test mul!
    mul!(M_temp_3, L, M)
    for i in 1:22
        for j in 1:22
            @test M_temp_3[i,j,:] ≈ correct_row
        end
    end

    # Test that * agrees will mul!
    @test M_temp_3 == L*M
end

@testset "Differentiating an arbitrary higher dimension" begin
    N = 6
    L = CenteredDifference{N}(4,4,0.1,30)
    M = zeros(5,5,5,5,5,32,5);
    M_temp = zeros(5,5,5,5,5,30,5);
    indices = Iterators.drop(CartesianIndices((5,5,5,5,5,32,5)), 0);
    for idx in indices
        M[idx] = cos(idx[N]*0.1)
    end

    correct_row = (10.0^4)*fourth_deriv_approx_stencil(30)*M[1,1,1,1,1,:,1]

    #test mul!
    mul!(M_temp, L, M)
    indices = Iterators.drop(CartesianIndices((5,5,5,5,5,30,5)), 0);
    for idx in indices
        @test M_temp[idx] ≈ correct_row[idx[N]]
    end

    # Test that * agrees will mul!
    @test M_temp == L*M
end

@testset "Differentiating with non-symmetric interior stencil" begin

    # The following tests check that multiplication of an operator with a
    # non-symmetric interior stencil is consistent with what we expect

    N = 1
    L = CenteredDifference{N}(3,4,0.1,30)
    M = zeros(32,32)
    for i in 1:32
        for j in 1:32
            M[i,j] = cos(0.1i)
        end
    end

    M_temp = zeros(30,32)
    mul!(M_temp, L, M)

    @test M_temp ≈ Array(L)*M

    N = 2
    L = CenteredDifference{N}(3,4,0.1,30)
    M = zeros(32,32)
    for i in 1:32
        for j in 1:32
            M[i,j] = cos(0.1j)
        end
    end

    M_temp = zeros(32,30)
    mul!(M_temp, L, M)

    @test M_temp ≈ transpose(Array(L)*transpose(M))

    # Three dimensions

    N = 1
    L = CenteredDifference{N}(3,4,0.1,30)
    M = zeros(32,32,32)
    for i in 1:32
        for j in 1:32
            for k in 1:32
                M[i,j,k] = cos(0.1i)
            end
        end
    end

    M_temp = zeros(30,32,32)
    mul!(M_temp, L, M)
    for i in 1:32
        @test M_temp[:,:,i] ≈ Array(L)*M[:,:,i]
    end

    correct_row = L*M[:,1,1]

    N = 2
    L = CenteredDifference{N}(3,4,0.1,30)
    M = zeros(32,32,32)
    for i in 1:32
        for j in 1:32
            for k in 1:32
                M[i,j,k] = cos(0.1j)
            end
        end
    end

    M_temp = zeros(32,30,32)
    mul!(M_temp, L, M)

    for i in 1:32
        for j in 1:32
            @test M_temp[i,:,j] ≈ correct_row
        end
    end

    N = 3
    L = CenteredDifference{N}(3,4,0.1,30)
    M = zeros(32,32,32)
    for i in 1:32
        for j in 1:32
            for k in 1:32
                M[i,j,k] = cos(0.1k)
            end
        end
    end

    M_temp = zeros(32,32,30)
    mul!(M_temp, L, M)

    for i in 1:32
        for j in 1:32
            @test M_temp[i,j,:] ≈ correct_row
        end
    end
end

@testset "Differentiating with coefficients" begin

    # Three dimensions
    N = 1
    µ = 5.5
    L = CenteredDifference{N}(3,4,0.1,30)

    µL = µ*L
    M = zeros(32,32,32)
    for i in 1:32
        for j in 1:32
            for k in 1:32
                M[i,j,k] = cos(0.1i)
            end
        end
    end

    correct_slice = 5.5*(L*M[:,1,1])
    M_temp = zeros(30,32,32)
    mul!(M_temp, µL, M)

    for i in 1:32
        for j in 1:32
            @test M_temp[:,i,j] ≈ correct_slice
        end
    end

    N = 2
    µL = µ*CenteredDifference{N}(3,4,0.1,30)
    M = zeros(32,32,32)
    for i in 1:32
        for j in 1:32
            for k in 1:32
                M[i,j,k] = cos(0.1j)
            end
        end
    end

    M_temp = zeros(32,30,32)
    mul!(M_temp, µL, M)

    for i in 1:32
        for j in 1:32
            @test M_temp[i,:,j] ≈ correct_slice
        end
    end

    N = 3
    µL = µ*CenteredDifference{N}(3,4,0.1,30)
    M = zeros(32,32,32)
    for i in 1:32
        for j in 1:32
            for k in 1:32
                M[i,j,k] = cos(0.1k)
            end
        end
    end

    M_temp = zeros(32,32,30)
    mul!(M_temp, µL, M)

    for i in 1:32
        for j in 1:32
            @test M_temp[i,j,:] ≈ correct_slice
        end
    end
end


### A Pluto.jl notebook ###
# v0.14.5

using Markdown
using InteractiveUtils

# ╔═╡ d9cff120-aba1-11eb-0f83-db8e49ac6bab
md"# Chapter 03 from NNFS book"

# ╔═╡ 4a34dd2f-cbf5-4ac0-8f97-fd4050354547
md"### 2 Layers of Neurons (4 inputs, 3 hidden layers)"

# ╔═╡ ab14105e-a405-4d87-80d7-940ce87ef19a
md"""
**One observation:**
"""

# ╔═╡ fed9705c-b748-4cf9-827a-f10fd8073967
begin
	# first layer
	input1 = [1, 2, 3, 2.5]

	weight1 = [
		 0.2   0.8  -0.5   1.0
		 0.5  -0.91  0.26 -0.5
		-0.26 -0.27  0.17  0.87
	]

	bias1 = [2, 3, 0.5]

	# second layer
	weight2 = [
		 0.1   -0.14   0.5
		-0.5    0.12  -0.33
		-0.44   0.73  -0.13
	]

	bias2 = [-1, 2, -0.5]

	# calculating layers
	layer1 = weight1 * input1 + bias1
	layer2 = weight2 * layer1 + bias2
end

# ╔═╡ 7ec38404-9380-411d-8b0d-f073ba5c6cbc
md"""
**Multiple observations:**
"""

# ╔═╡ 773a22f6-99c3-4bf5-936b-0b22759038ed
begin
	# first layer
	inp01 = [
		[1, 2, 3, 2.5] [2, 5, -1, 2] [-1.5, 2.7, 3.3, -0.8]
	]

	w01 = [
		 0.2   0.8  -0.5   1.0
		 0.5  -0.91  0.26 -0.5
		-0.26 -0.27  0.17  0.87
	]

	b01 = [2, 3, 0.5]

	# second layer
	w02 = [
		 0.1   -0.14   0.5
		-0.5    0.12  -0.33
		-0.44   0.73  -0.13
	]

	b02 = [-1, 2, -0.5]

	# calculating layers
	layer01 = w01 * inp01 .+ b01
	layer02 = w02 * layer01 .+ b02
end

# ╔═╡ 6aabd18d-d0a1-4f7e-9019-92d7671e7265
md"### Creating the LayerDense Struct"

# ╔═╡ 50edc678-40c3-483c-8556-666f01553802
begin
	struct LayerDense
		w::Matrix{Float64}
		b::Vector{Float64}
		function LayerDense(n_in::Int, n_out::Int)
			w = 0.01 * randn(n_out, n_in)
			b = zeros(n_out)
			return new(w, b)
		end
	end

	(layer::LayerDense)(input) = layer.w * input .+ layer.b
end

# ╔═╡ da9d7fdb-8b5a-466c-9370-654c133e10a3
D = LayerDense(2, 3)

# ╔═╡ 173fe819-779e-4d8e-a2b3-e00fb9363cb2
imp01 = [3.5, 4.3]

# ╔═╡ c14fce98-4470-4153-ba62-75521c3e5ed8
imp02 = [[3.5, 4.3] [2.4, -2.8]]

# ╔═╡ f8915121-453b-49bc-83ed-07ea13541160
D(imp01)

# ╔═╡ ffe4c9ac-bbbf-49e0-b882-b6a8ed096622
D(imp02)

# ╔═╡ ae0e3918-c07e-40a5-880d-15cec3afce71
D(randn(2))

# ╔═╡ Cell order:
# ╟─d9cff120-aba1-11eb-0f83-db8e49ac6bab
# ╟─4a34dd2f-cbf5-4ac0-8f97-fd4050354547
# ╟─ab14105e-a405-4d87-80d7-940ce87ef19a
# ╠═fed9705c-b748-4cf9-827a-f10fd8073967
# ╟─7ec38404-9380-411d-8b0d-f073ba5c6cbc
# ╠═773a22f6-99c3-4bf5-936b-0b22759038ed
# ╟─6aabd18d-d0a1-4f7e-9019-92d7671e7265
# ╠═50edc678-40c3-483c-8556-666f01553802
# ╠═da9d7fdb-8b5a-466c-9370-654c133e10a3
# ╠═173fe819-779e-4d8e-a2b3-e00fb9363cb2
# ╠═c14fce98-4470-4153-ba62-75521c3e5ed8
# ╠═f8915121-453b-49bc-83ed-07ea13541160
# ╠═ffe4c9ac-bbbf-49e0-b882-b6a8ed096622
# ╠═ae0e3918-c07e-40a5-880d-15cec3afce71


"""

    euclideanDistances(dataMatrix)

Calculate Euclidean distances between pairs.

# Arguments
- `dataMatrix::Array{Float64, 1}`: Data matrix with dimensions n x p, where n is the number of observations and p is the number of variables.

# Notes
    This is the helper function for the dataimage() function defined in Marchette & Solka (2003).

# References
Marchette, David J., and Jeffrey L. Solka. "Using data images for outlier detection."
Computational Statistics & Data Analysis 43.4 (2003): 541-552.
"""
function euclideanDistances(dataMatrix::Array{Float64,2})::Array{Float64,2}
    n, p = size(dataMatrix)
    d = zeros(Float64, n, n)
    for i in 1:n
        for j in i:n
            if i != j
                @inbounds d[i, j] = sqrt(sum((dataMatrix[i,:] .- dataMatrix[j,:]).^2.0))
                @inbounds d[j, i] = d[i, j]
            end
        end
    end
    return d
end

"""

    mahalanobisBetweenPairs(dataMatrix)

Calculate Mahalanobis distances between pairs.

# Arguments
- `dataMatrix::Array{Float64, 1}`: Data matrix with dimensions n x p, where n is the number of observations and p is the number of variables.

# Notes
    Differently from Mahalabonis distances, this function calculates Mahalanobis distances between
    pairs, rather than the distances to center of the data. This is the helper function for the
    dataimage() function defined in Marchette & Solka (2003).

# References
Marchette, David J., and Jeffrey L. Solka. "Using data images for outlier detection."
Computational Statistics & Data Analysis 43.4 (2003): 541-552.
"""
function mahalanobisBetweenPairs(dataMatrix::Array{Float64,2})::Array{Float64,2}
    n, p = size(dataMatrix)
    d = zeros(Float64, n, n)
    covmat = cov(dataMatrix)
    if det(covmat) == 0.0
        @warn "Covariance matrix is singular, mahalanobis distances can not be calculated."
    end
    covinv = inv(covmat)
    for i in 1:n
        for j in i:n
            if i != j
                @inbounds d[i, j] = sqrt((dataMatrix[i,:] .- dataMatrix[j,:]) * covinv * (dataMatrix[i,:] .- dataMatrix[j,:])')
                @inbounds d[j, i] = d[i, j]
            end
        end
    end
    return d
end

"""

    dataimage(dataMatrix; distance = :mahalanobis)

Generate the Marchette & Solka (2003) data image for a given data matrix.

# Arguments
- `dataMatrix::Array{Float64, 1}`: Data matrix with dimensions n x p, where n is the number of observations and p is the number of variables.
- `distance::Symbol`: Optional argument for the distance function.

# Notes
    distance is :mahalanobis by default, for the Mahalanobis distances.
    use

        dataimage(mat, distance = :euclidean)

    to use Euclidean distances.

# Examples
```julia-repl
julia> x1 = hbk[:,"x1"];
julia> x2 = hbk[:,"x2"];
julia> x3 = hbk[:,"x3"];
julia> mat = hcat(x1, x2, x3);
julia> dataimage(mat)
```

# References
Marchette, David J., and Jeffrey L. Solka. "Using data images for outlier detection."
Computational Statistics & Data Analysis 43.4 (2003): 541-552.
"""
function dataimage(dataMatrix::Array{Float64,2}; distance=:mahalanobis)
    d = nothing
    if distance == :mahalanobis
        d = mahalanobisSquaredBetweenPairs(dataMatrix)
    elseif distance == :euclidean
        d = euclideanDistances(dataMatrix)
    else
        @error "Distance function unknown: " distance
        @error "Using mahalanobis instead"
        d = mahalanobisSquaredBetweenPairs(dataMatrix)
    end
    colours = 1.0 .- d / maximum(d)
    n, _ = size(d)
    colormatrix = Array{RGB{Float64}}(undef, n, n)
    for i in 1:n
        for j in 1:n
            @inbounds colormatrix[i, j] = RGB(colours[i, j])
        end
    end
    plot(colormatrix)
end


# This file is pat of RLinearAlgebra.jl
# 1. Specifies type
# 2. Implements rsubsolve function
# 3. Exports Type

#using LinearAlgebra

"""
    LinSysVecColProjPO <: LinSysVecColProjection

A mutable structure that represents the standard column projection method
    with orthogonalization of the current projection against a set of `m` previous
    projection directions, where `m` is specified by the user.

See Patel, Vivak, Mohammad Jahangoshahi, and Daniel Adrian Maldonado. "An Implicit
    Representation and Iterative Solution of Randomly Sketched Linear Systems."
    SIAM Journal on Matrix Analysis and Applications (2021) 42:2, 800-831.
    https://doi.org/10.1137/19M1259481

# Fields

- `α::Float64`, the relaxation parameter (usually between `0.0` and `2.0`)
- `m::Int64`, the number of previous directions against which to orthogonalize
- `Z::Union{Vector{Vector{Float64}}, Nothing}`, stores the `m` vectors against which the
    current projection is orthogonalized against

Calling `LinSysVecColProjPO()` defaults the relaxation parameter to `1.0`, the memory
    parameter `m` to `5`, and `Z` to `nothing`.
"""
mutable struct LinSysVecColProjPO <: LinSysVecColProjection
    α::Float64
    m::Int64
    Z::Union{Vector{Vector{Float64}}, Nothing}
end
LinSysVecColProjPO() = LinSysVecColProjPO(1.0, 5, nothing)

# Common rsubsolve interface for linear systems
function rsubsolve!(
    type::LinSysVecColProjPO,
    x::AbstractVector,
    samp::Tuple{U,V,W} where {U<:AbstractVector,V<:AbstractArray,W<:Real},
    iter::Int64,
)
    # samp[1] is the search direction
    # samp[2] is the coefficient matrix, A
    # samp[3] is a projection of the residual of normal system, samp[1]' * A' * (A * x - b)

    # Allocate space for orthogonalization set Z
    if iter == 1
        d = length(x)
        type.Z = Vector{Float64}[zeros(Float64, d) for i in 1:type.m]
    end

    # Compute orthogonal complement of A' * A * samp[1] using modified Gram-Schmidt
    u = samp[2]' * (samp[2] * samp[1])
    for z in type.Z
        u = u - dot(z, u) * z
    end

    # Check whether vector u is nearly zero
    nrmUsq = dot(u, u)
    if nrmUsq < eps() * length(x); return nothing; end

    # Otherwise, update iterate
    x .= x - u * (type.α * samp[3] / nrmUsq )

    # Update orthonormal set
    z = u / sqrt(nrmUsq)
    type.Z .= push!(type.Z[2:end], z)

    return nothing
end


function bounded_log_grid(xMin::Float64, xMax::Float64, steps::Int)
  result = zeros(steps + 1)
  gridLogSpacing = (log(xMax) - log(xMin)) / steps
  edx = exp(gridLogSpacing)
  result[1] = xMin
  for j = 2:steps+1
    result[j] = result[j-1] * edx
  end

  return result
end


using Jags, StatsPlots, Random, Distributions

cd(@__DIR__)
ProjDir = pwd()
Random.seed!(3431)

y = rand(Normal(0,1),50)

Model = "
model {
      for (i in 1:length(y)) {
            y[i] ~ dnorm(mu[i],sigma);
      }
      for(i in 1:length(y)){
            mu[i]  ~ dnorm(0, 1/sqrt(10));
        }

      sigma  ~ dt(0,1,1) T(0, );
  }
"

monitors = Dict(
  "mu" => true,
  "sigma" => true,
  )

inits = [
  Dict("mu"=>.0,"sigma"=>1.0)
]

jagsmodel = Jagsmodel(
  name="Gaussian",
  model=Model ,
  monitor=monitors,
  ncommands=1, nchains=4
  ,
  #deviance=true, dic=true, popt=true,
  pdir=ProjDir
  )

println("\nJagsmodel that will be used:")
jagsmodel |> display

data = Dict{String, Any}(
  "y" => y,
)

inits = [
  Dict("mu" => 0.0,"sigma" => 1.0,
  ".RNG.name" => "base::Mersenne-Twister")
]

println("Input observed data dictionary:")
data |> display
println("\nInput initial values dictionary:")
inits |> display
println()
#######################################################################################
#                                 Estimate Parameters
#######################################################################################
sim = jags(jagsmodel, data, inits, ProjDir)
sim = sim[5001:end,:,:]


# This script provides an example of simulating changepoint data,
# using PELT to find these changepoints, and plotting the results
#
# Requires Plots package

using Distributions, Plots
using Changepoints

#########################
# Simulate changepoints #
#########################

n = 1000        # Length of sample
λ = 100         # Frequencey of changes

# Mu parameter sampled from a N(0,10) distribution for each changepoint
μ = Normal(0,5)
σ = 1.0 # Sigma parameter fixed

# Construct sample with changepoints where
# at each changepoint a new value is sample for μ
sample, cps = @changepoint_sampler n λ Normal(μ, σ)

########################
# Finding changepoints #
########################

# Find changepoints via PELT
# assuming Normal likelihood with changing mean
# parameter and fixed standard deviation of σ
pelt_cps, pelt_cost = @PELT sample Normal(:?, σ)

# Find changepoints via Binary Segmentation
bs_cps, bs_cost = @BS sample Normal(:?, σ)

# If plotting multiple sets of changepoints which need to labelled
# separately, it is best not to use `changepoint_plot` function
# but to instead first plot data
# and then add changepoints with `vline!` function
p = plot(sample, label="Simulated data")
vline!(p, cps, label="True changepoints")
vline!(p, pelt_cps, label="PELT changepoints")
vline!(p, bs_cps, label="BS changepoints")
display(p)


export  calculateMultiIndices,
        findUnivariateIndices

function calculateMultiIndices(d::Int, n::Int)
    # d denotes dimension of random variables/number of sources of uncertainty,
    # n the maximum degree of multivariate basis
    # function to calculate indices of multivariate basis following the algorithm
    # from "Spectral Methods for Uncertainty Quantiﬁcation; Le Maitre, Knio;2014" p.516-517
    # No::BigInt = factorial(BigInt(d+n))/(factorial(Bigint(d))*factorial(BigInt(n)));  #number of polynomials of multivariate basis
  n < 0 && throw(DomainError(n, "maximum degree must be non-negative"))
  d <= 0 && throw(DomainError(d, "number of uncertainties must be positive"))
  # catch case n == 0 --> No-d==0
  n == 0 && return zeros(Int64,1,d)
  # non-pathological cases begin here
  No = numberPolynomials(d,n)
  inds = vcat(zeros(Int64,1,d),Matrix(1I,d,d),zeros(Int64, No-d-1, d));  #initiate index matrix for basis
  pi=ones(Int64,No,d);

  for k in 2:No
    g = 0;
    for l in 1:d
      pi[k,l]=sum(pi[k-1,:])-g;
      g=g+pi[k-1,l];
    end
  end

  P = d + 1;
  for k in 2:n
    L = P;
    for j in 1:d, m in L-pi[k,j]+1:L
        P += 1;
        inds[P,:] = inds[m,:];
        inds[P,j] = inds[P,j]+1;
    end
  end

  return inds
end

"""
    computes the number of polynomials with a multivariate basis
    `(d+n)!/(d!+n!)`
"""

function numberPolynomials(d::Int64, n::Int64)
    x, y = max(d,n), min(d,n)
    return UInt128(prod(UInt128(x+1):UInt128(d+n)) ÷ factorial(UInt128(y)))
end

"""
    findUnivariateIndices(i::Int,ind::AbstractMatrix{Int64,2})
Given the multi-index `ind` this function returns all entries of the multivariate basis
that correspond to the `i`th univariate basis.
"""
function findUnivariateIndices(i::Int,ind::AbstractMatrix{Int})
  l,p = size(ind)
  i > p && throw(DomainError((i,p), "basis is $p-variate, you requested $i-variate"))
  deg = ind[end,end]
  deg < 0 && throw(DomainError(deg, "invalid degree"))
  col = ind[:,i]
  myind = zeros(Int64,deg)
  for deg_ in 1:deg
      myind[deg_] = findfirst(x->x==deg_,col)
  end
  pushfirst!(myind,1)
end


# Implicitly solve heat equation: ∂_t T = α * ∇²(T) + β*sin(γ*z)
# Boundary conditions: ∇T(z=a) = ∇T_bottom, T(z=b) = T_top
import Plots
using LinearAlgebra
using DiffEqBase
using OrdinaryDiffEq: SplitODEProblem, solve, IMEXEuler
import SciMLBase

a,b, n = 0, 1, 10               # zmin, zmax, number of cells
n̂_min, n̂_max = -1, 1            # Outward facing unit vectors
α = 100;                        # thermal diffusivity, larger means more stiff
β, γ = 10000, π;                # source term coefficients
Δt = 1000;                      # timestep
N_t = 10;                       # number of timesteps
FT = Float64;                   # float type
Δz = FT(b-a)/FT(n)
Δz² = Δz^2;
∇²_op = [1/Δz², -2/Δz², 1/Δz²]; # row or Laplacian operator
∇T_bottom = 10;                 # Temperature gradient at the top
T_top = 1;                      # Temperature at the bottom
S(z) = β*sin(γ*z)               # source term, (sin for easy integration)
zf = range(a, b, length=n+1);
zc = map(i-> zf[i]+Δz/2, 1:n);

function T_analytic(z) # Analytic steady state solution
    c1 = ∇T_bottom-β*cos(γ*a)/(γ*α)
    c2 = T_top-(β*sin(γ*b)/(γ^2*α)+c1*b)
    return β*sin(γ*z)/(γ^2*α)+c1*z+c2
end

T = zeros(FT, n+1);

# Equations: derivation to matrix form
# ∂_t T = α * ∇²(T) + S
# (T^{n+1}-T^n) = Δt (α * ∇²(T^{n+1}) + S)
# (T^{n+1} - Δt α * ∇²(T^{n+1})) = T^n + Δt*S
# (I - Δt α * ∇²) T^{n+1} = T^n + Δt*S

# Derive Dirichlet boundary stencil & source:
# ∂_t T = α * (T[i-1]+T[b]-2*T[i])/Δz² + S
# ∂_t T = α * (T[i-1]-2*T[i])/Δz² + S + α * T[b] / Δz²

# Derive Neumann boundary stencil & source:
# ∇T_bottom*n̂= (T[g] - T[i])/(2Δz),     n̂ = [-1,1] ∈ [zmin,zmax]
# T[i] + 2*Δz*∇T_bottom*n̂ = T[g]
# ∂_t T = α * (((T[i] + 2*Δz*∇T_bottom*n̂) - T[b])/Δz - (T[b] - T[i])/Δz)/Δz + S
# ∂_t T = α * (((T[i]) - T[b])/Δz - (T[b] - T[i])/Δz)/Δz + S + α/Δz²*2*Δz*∇T_bottom*n̂
# ∂_t T = α * (2*T[i] - 2*T[b])/Δz² + S + 2α/Δz*∇T_bottom*n̂
∇² = Tridiagonal(
    ones(FT, n) .* ∇²_op[1],
    ones(FT, n+1)   .* ∇²_op[2],
    ones(FT, n) .* ∇²_op[3]
);

# Modify boundary stencil to account for BCs
∇².d[1] = -2/Δz²
∇².du[1] = +2/Δz²

# Modify boundary stencil to account for BCs
∇².du[n] = 0  # modified stencil
∇².d[n+1] = 0 # to ensure `∂_t T = 0` at `z=zmax`
∇².dl[n] = 0  # to ensure `∂_t T = 0` at `z=zmax`

# Compute boundary source: α * T[b] / Δz²
AT_b = zeros(FT, n+1);
AT_b[1] = α*2/Δz*∇T_bottom*n̂_min;
AT_b[end-1] = α*T_top/Δz²;

# Set initial condition:
T .= 1;
T[n+1] = T_top # set top BC

function rhs!(dT, T, params, t)
    n = params.n
    i = 1:n # interior domain
    dT[i] .= S.(zf[i]) .+ AT_b[i]
    return dT
end

D = α .* ∇²

params = (;n)

tspan = (FT(0), N_t*FT(Δt))

prob = SplitODEProblem(
    SciMLBase.DiffEqArrayOperator(
        D,
    ),
    rhs!,
    T,
    tspan,
    params
)

println("Solving...")

alg = IMEXEuler(linsolve=LinSolveFactorize(lu!))

sol = solve(
    prob,
    alg,
    dt = Δt,
    saveat = range(FT(0), N_t*FT(Δt), length=5),
    progress = true,
    progress_message = (dt, u, p, t) -> t,
);

T_end = sol.u[end]

p1 = Plots.plot(zf, T_analytic.(zf), label="analytic", markershape=:circle, markersize=6)
p1 = Plots.plot!(p1, zf, T_end, label="numerical", markershape=:diamond)
p1 = Plots.plot!(p1, title="T ∈ cell faces")

p2 = Plots.plot(zf, abs.(T_end .- T_analytic.(zf)), label="error", markershape=:circle, markersize=6)
p2 = Plots.plot!(p2, title="T ∈ cell faces")

Plots.plot(p1, p2)


using Printf

function multifact(n::Integer, k::Integer)
    n > 0 && k > 0 || throw(DomainError())
    k > 1 || factorial(n)
    return prod(n:-k:2)
end

const khi = 5
const nhi = 10
println("Showing multifactorial for n in [1, $nhi] and k in [1, $khi].")
for k = 1:khi
    a = multifact.(1:nhi, k)
    lab = "n" * "!" ^ k
    @printf("  %-6s →  %s\n", lab, a)
end


function pythagorean_triplets(N::Int)::Array{Tuple{Int,Int,Int},1}
    # Return all (a,b,c) such that:
    # a + b + c = N
    # a^2 + b^2 = c^2
    # a < b < c

    # Pythagorean triplets: Euclid did this a while ago
    # a = m^2 - n^2, b = 2mn, c = m^2 + n^2
    # so a + b + c = 2m^2 + 2mn = N
    # m(m + n) = N/2: not nice for generation, so let's give up and filter by it instead
    #
    # Need to take care of int multiples so also k.*(a,b,c) => km(m+n) = N/2 too
    # => N/2 / m(m+n) is an integer: divrem(N/2,m(m+n))

    # TODO / exercise for the reader: ensure bounds on N/2 are as tight as they can be
    sort!(triple.(Iterators.filter(x->conditions(x[1],x[2],N),Iterators.product(1:N/2,1:N/2)),N) |> unique; by=x->x[1])
end

function conditions(m,n,N)
    m > n > 0 &&
    rem(N/2,m*(m+n)) == 0
end

triple(t,N) = triple(t[1],t[2],N)

# We only make the triple of answers that we know will work so there's no point optimising this
triple(m,n,N) = div(N/2,m*(m+n)).*sort!((m^2 - n^2, 2m*n, m^2 + n^2) |> collect) |> Tuple{Int,Int,Int}

# This is vastly faster than mine, it's not fair
# https://exercism.io/tracks/julia/exercises/pythagorean-triplet/solutions/369f511a364d4ce0b9c054c55b49d8d8
function pythagorean_triplets(x::Int)

	triplets = Array{Tuple{Int64,Int64,Int64}}(undef, 0)
	for a in 1:(x ÷ 3)
		for b in (a+1):(x ÷ 2 + 1)

			c = x - a - b
			if (a*a + b*b == c*c)
				push!(triplets, (a, b, c))
			end
		end
	end
	return triplets
end


@testset "Curve struct" begin
@testset "Uniformly distributed values" begin
    n = 20
    data = rand(Uniform(0,10), n)
    curve = CalculatedABC.ABCcurve(data)
    @test curve.effort == 0:0.05:1.0
    expected_yield = [0,
                      0.10223370412290836,
                      0.19630374973228518,
                      0.2798337861487627,
                      0.35309289814499817,
                      0.4251124119444249,
                      0.49223359866263033,
                      0.5559340313195282,
                      0.6192318028485928,
                      0.6706485917196718,
                      0.7210686469145637,
                      0.7696104643520768,
                      0.812596101423477,
                      0.8466456273448749,
                      0.8777009051387628,
                      0.9076823618805581,
                      0.9368834040175215,
                      0.9647084964342312,
                      0.9824989229044542,
                      0.994333140487581,
                      1.0000000000000002]
    @test isapprox(curve.yield, expected_yield)
end

@testset "Exponentially distributed values" begin
    n = 20
    data = rand(Exponential(1), n)
    curve = CalculatedABC.ABCcurve(data)
    @test curve.effort == 0:0.05:1.0
    expected_yield = [0.0,
                      0.1559704817922999,
                      0.259107787045309,
                      0.36017085915674457,
                      0.4567531613173713,
                      0.530483819367366,
                      0.5905820392527759,
                      0.6503414525690904,
                      0.6912925133668514,
                      0.7318440937106174,
                      0.7704643555492486,
                      0.8031964684921609,
                      0.8353247928350085,
                      0.8670259991394021,
                      0.8976554662905372,
                      0.9212877248856802,
                      0.9431001183934831,
                      0.964704167187443,
                      0.9810003669812493,
                      0.9911110632975999,
                      1.0]
    @test isapprox(curve.yield, expected_yield)
end

@testset "SwissInhabitants data" begin
    data = readdlm("swissinhabitants")[:,1]
    curve = CalculatedABC.ABCcurve(data)
    @test curve.effort == 0:0.0003453038674033149:1.0
    expected_yield = readdlm("expected_yield")[:,1]
    @test isapprox(curve.yield, expected_yield)
end
end


using PlaneGeometry
using PlaneGeometry.Theorems.Napoleon
using SymPy
using Test

@testset "PlaneGeometry.jl" begin
    A = Point(0,0)
    B = Point(1, 3)
    C = Point(4,2)
    tri = Triangle(A, B, C)

    @test vertices(tri) == [A, B, C]
    @test edges(tri) == [Edge(A, B), Edge(B, C), Edge(C, A)]

    c = Circle(A, 2)
    @test PlaneGeometry.center(c) == A

    @test squaredist(A, B) == 10
    @test distance(A, B) == SymPy.sqrt(Sym(10))

    @test midpoint(Point(0, 0), Point(2,4)) == Point(1, 2)
    @test midpoint(A, B) == Point(1//2, 3//2)

    @test median(A, B, C) == Edge(Point(1//2, 3//2), C)

    @test concurrent(edges(tri)) == nothing

    @test concurrent(Edge(A, B), Edge(A, C)) == A

    elist = map(i->median(circshift([A, B, C], i)...), 0:2)
    @test concurrent(elist) == Point(5//3, 5//3)

    @test equipoints(A, B) ==
    [
    Point(1//2 - 3*sympy.sqrt(3)//2, sympy.sqrt(3)//2 + 3//2),
    Point(1//2 + 3*sympy.sqrt(3)//2, 3//2 - sympy.sqrt(3)//2)
    ]

    outer_tri = outer_equitriangles(tri)

    @test outer_tri ==
    [
    Triangle(A, B, Point(1//2 - 3*sympy.sqrt(3)//2, sympy.sqrt(3)//2 + 3//2)),
    Triangle(C, A, Point(sympy.sqrt(3) + 2, 1 - 2*sympy.sqrt(3))),
    Triangle(B, C, Point(sympy.sqrt(3)//2 + 5//2, 5//2 + 3*sympy.sqrt(3)//2))
    ]

    @test_throws ArgumentError circumcircle([A, B])

    outer_circles = circumcircle.(vertices.(outer_tri))

    @test outer_circles ==
    [
    Circle(Point(1//2 - sympy.sqrt(3)//2, sympy.sqrt(3)//6 + 3//2), sympy.sqrt(30)//3),
    Circle(Point(sympy.sqrt(3)//3 + 2, 1 - 2*sympy.sqrt(3)//3), 2*sympy.sqrt(15)//3),
    Circle(Point(sympy.sqrt(3)//6 + 5//2, sympy.sqrt(3)//2 + 5//2), sympy.sqrt(30)//3)
    ]

    @test isequilateral(tri) == false

    np =  napoleon_tri(tri)

    @test isequilateral(np)

    @vars a1 a2 b1 b2 c1 c2
    tri1 = Triangle(a1, a2, b1, b2, c1, c2)

    # This needs to fail.
    @test !isequilateral(tri1)

    @test np == Triangle(
        Point(1//2 - sympy.sqrt(3)//2, sympy.sqrt(3)//6 + 3//2),
        Point(sympy.sqrt(3)//3 + 2, 1 - 2*sympy.sqrt(3)//3),
        Point(sympy.sqrt(3)//6 + 5//2, sympy.sqrt(3)//2 + 5//2))

    @test ceva(tri, O) == (
        [Point(0, 0), Point(1, 3), Point(4, 2), Point(5//2, 5//2), Point(4//9, 2//9), Point(1//9, 1//3)],
        [Edge(Point(1, 3), Point(5//2, 5//2)),
            Edge(Point(5//2, 5//2), Point(4, 2)),
            Edge(Point(4, 2), Point(4//9, 2//9)),
            Edge(Point(4//9, 2//9), Point(0, 0)),
            Edge(Point(0, 0), Point(1//9, 1//3)),
            Edge(Point(1//9, 1//3), Point(1, 3))])

    @test napoleon_proof()

    @test centroid_proof()

    @test ceva_proof()
end


# ---
# title: 264. Ugly Number II
# id: problem264
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Math, Dynamic Programming, Heap
# link: <https://leetcode.com/problems/ugly-number-ii/description/>
# hidden: true
# ---
#
# Write a program to find the `n`-th ugly number.
#
# Ugly numbers are **positive numbers** whose prime factors only include `2, 3,
# 5`.
#
# **Example:**
#
#
#
#     Input: n = 10
#     Output: 12
#     Explanation:1, 2, 3, 4, 5, 6, 8, 9, 10, 12 is the sequence of the first 10 ugly numbers.
#
# **Note:**
#
#   1. `1` is typically treated as an ugly number.
#   2. `n` **does not exceed 1690**.
#
#
## @lc code=start
using LeetCode

## add your code here:
## @lc code=end


# This file investigates integration error with respect to free variables in 1D integration

using FractalTools
using Makie

# Construct interpolation data
f(x) = sin(2π * x) + 1
dx   = 0.1
xi   = 0.
xf   = 1.
x    = collect(xi : dx : xf)
y    = f.(x)
pts  = collect.(zip(x, y))

# Compute errors
fval = (cos(2π * xi) - cos(2π * xf)) / (2π) + (xf - xi)
freevars = 0.001 : 0.01 : 0.999
mse = map(freevars) do freevar
    ival = integrate(pts, Interp1D(freevar))
    abs(fval - ival)
end

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


using Revise
using AdFem
using LinearAlgebra
using PyPlot

m = 50
n = 50
h = 1/n

bdedge = bcedge("top", m, n, h)
bdnode = bcnode("left|right|bottom", m, n, h)

F1 = eval_f_on_gauss_pts((x,y)->3.0, m, n, h)
F2 = eval_f_on_gauss_pts((x,y)->-1.0, m, n, h)
F = compute_fem_source_term(F1, F2, m, n, h)

t1 = eval_f_on_boundary_edge((x,y)->-x-y, bdedge, m, n, h)
t2 = eval_f_on_boundary_edge((x,y)->2y, bdedge, m, n, h)
T = compute_fem_traction_term([t1 t2], bdedge, m, n, h)

D = constant(diagm(0=>[1,1,0.5]))
K = compute_fem_stiffness_matrix(D, m, n, h)
rhs = T - F
bdval = [eval_f_on_boundary_node((x,y)->x^2+y^2, bdnode, m, n, h);
        eval_f_on_boundary_node((x,y)->x^2-y^2, bdnode, m, n, h)]
DOF = [bdnode;bdnode .+ (m+1)*(n+1)]
K, rhs = impose_Dirichlet_boundary_conditions(K, rhs, DOF, bdval)
u = K\rhs
sess = Session(); init(sess)
S = run(sess, u)

figure(figsize=[10,4])
subplot(121)
visualize_scalar_on_fem_points(S[1:(m+1)*(n+1)], m, n, h)
subplot(122)
visualize_scalar_on_fem_points(S[(m+1)*(n+1)+1:2(m+1)*(n+1)], m, n, h)
savefig("numerical.png")

figure(figsize=[10,4])
x = LinRange(0, 1, 50)
X = zeros(50, 50)
Y = zeros(50, 50)
for i = 1:50
    for j = 1:50
        X[i,j] = x[i]
        Y[i,j] = x[j]
    end
end
subplot(121)
XY = fem_nodes(m, n, h)
X = XY[:,1]
Y = XY[:,2]
U = (@. X^2+Y^2)
V = (@. X^2-Y^2)
subplot(121)
visualize_scalar_on_fem_points(U, m, n, h)
subplot(122)
visualize_scalar_on_fem_points(V, m, n, h)
savefig("exact.png")

figure(figsize=[10,4])
subplot(121)
visualize_scalar_on_fem_points(U-S[1:(m+1)*(n+1)], m, n, h)
subplot(122)
visualize_scalar_on_fem_points(V-S[(m+1)*(n+1)+1:2(m+1)*(n+1)], m, n, h)
savefig("difference.png")


using CompScienceMeshes
using Test

p1 = point(1,0,0)
p2 = point(0,1,0)
p3 = point(0,0,1)
p4 = point(0,0,0)

tet = simplex(p1,p2,p3,p4)
c = neighborhood(tet, point(1,1,1)/4)

@test cartesian(c) ≈ point(0.25, 0.25, 0.25)
@test parametric(c) ≈ point(0.25, 0.25, 0.25)

@test length(c) == 3
@test c[1] ≈ 0.25
@test c[2] ≈ 0.25
@test c[3] ≈ 0.25

@test jacobian(1.234) ≈ 1

@test tangents(c,1) ≈ point(1,0,0)
@test tangents(c,2) ≈ point(0,1,0)
@test tangents(c,3) ≈ point(0,0,1)

tri = simplex(p1,p2,p4)
c_tri = neighborhood(tri, point(1,1)/3)

@test normal(c_tri) ≈ point(0,0,1)