import Pkg; Pkg.add(Pkg.PackageSpec(url="https://github.com/JuliaComputing/JuliaAcademyData.jl"))
using JuliaAcademyData; activate("Deep learning with Flux")

#

#
# # Intro to Flux.jl

#-

# In the previous course, we learned how machine learning allows us to classify data as apples or bananas with a single neuron. However, some of those details are pretty fiddly! Fortunately, Julia has a powerful package that does much of the heavy lifting for us, called [`Flux.jl`](https://fluxml.github.io/).
#
# *Using `Flux` will make classifying data and images much easier!*

#-

# ## Using `Flux.jl`
#
# We can get started with `Flux.jl` via:

## using Pkg; Pkg.add(["Flux", "Plots"])
using Flux, Plots

# #### Helpful built-in functions
#
# When working we'll `Flux`, we'll make use of built-in functionality that we've had to create for ourselves in previous notebooks.
#
# For example, the sigmoid function, σ, that we have been using already lives within `Flux`:

#nb ?σ
#jl @doc σ

#-

plot(σ, -5, 5, label="\\sigma", xlabel="x", ylabel="\\sigma\\(x\\)")

# Importantly, `Flux` allows us to *automatically create neurons* with the **`Dense`** function. For example, in the last notebook, we were looking at a neuron with 2 inputs and 1 output:
#
#
#
#  We could create a neuron with two inputs and one output via

model = Dense(2, 1, σ)

# This `model` object comes with places to store weights and biases:

model.W

#-

model.b

#-

typeof(model.W)

#-

x = rand(2)
model(x)

#-

σ.(model.W*x + model.b)

# Unlike in previous notebooks, note that `W` is no longer a `Vector` (1D `Array`) and `b` is no longer a number! Both are now stored in so-called `TrackedArray`s and `W` is effectively being treated as a matrix with a single row. We'll see why below.

#-

# Other helpful built-in functionality includes ways to automatically calculate gradients and also the cost function that we've used in the previous course -
#
# $$L(w, b) = \sum_i \left[y_i - f(x_i, w, b) \right]^2$$
#
# If you normalize by dividing by the total number of elements, this becomes the "mean square error" function, which in `Flux` is named **`Flux.mse`**.

methods(Flux.mse)

# ### Bringing it all together
#
# Load the datasets that contain the features of the apple and banana images.

using CSV, DataFrames

apples = DataFrame(CSV.File(datapath("data/apples.dat"), delim='\t', allowmissing=:none, normalizenames=true))
bananas = DataFrame(CSV.File(datapath("data/bananas.dat"), delim='\t', allowmissing=:none, normalizenames=true));

#-

x_apples  = [ [row.red, row.green] for row in eachrow(apples)]
x_bananas = [ [row.red, row.green] for row in eachrow(bananas)];

# Concatenate the x (features) together to create a vector of all our datapoints, and create the corresponding vector of known labels:

xs = [x_apples; x_bananas]
ys = [fill(0, size(x_apples)); fill(1, size(x_bananas))];

#-

model = Dense(2, 1, σ)

# We can evaluate the model (currently initialized with random weights) to see what the output value is for a given input:

model(xs[1])

# And of course we can examine the current loss value for that datapoint:

loss = Flux.mse(model(xs[1]), ys[1])

#-

typeof(loss)

# ### Backpropagation

model.W

#-

model.W.grad

#-

using Flux.Tracker
back!(loss)

#-

model.W.grad

# Now we have all the tools necessary to build a simple gradient descent algorithm!

#-

# ### The easy way
#
# You don't want to manually write out gradient descent algorithms every time! Flux, of course, also brings in lots of optimizers that can do this all for you.

#nb ?SGD
#jl @doc SGD

#-

#nb ?Flux.train!
#jl @doc Flux.train!

# So we can simply define our loss function, an optimizer, and then call `train!`. That's basic machine learning with Flux.jl.

model = Dense(2, 1, σ)
L(x,y) = Flux.mse(model(x), y)
opt = SGD(params(model))
Flux.train!(L, zip(xs, ys), opt)

# ## Visualize the result

contour(0:.1:1, 0:.1:1, (x, y) -> model([x,y])[].data, fill=true)
scatter!(first.(x_apples), last.(x_apples), label="apples")
scatter!(first.(x_bananas), last.(x_bananas), label="bananas")
xlabel!("mean red value")
ylabel!("mean green value")

Courses/Deep learning with Flux/1300.Intro-to-Flux.jl.jl


"""
This enum is used to select the distance calculation type.
We converted the name into a string, and the magic happens! :D

# DistanceType Possibilities:

    Euclidean, Cityblock, TotalVariation, Chebyshev,
    Jaccard, BrayCurtis, SpanNormDist

# Examples:

    Euclidean::DistanceType
    Cityblock::DistanceType
"""
@enum DistanceType begin

    Euclidean = 1
    Cityblock = 2
    TotalVariation = 3
    Chebyshev = 4
    Jaccard = 5
    BrayCurtis = 6
    SpanNormDist = 7

end

#=

    This dictionary is used with the enum DistanceType to
    select the distance calculation.
    After convert the enum in a string we applied in this dictionary. :)

=#

#add
# Creating the dictionary

distanceFormulas = Dict()

# Setting Euclidean
temp_euc(x, y) = sqrt(sum((x - y) .^ 2))
distanceFormulas["Euclidean"] = temp_euc

# Setting Cityblock
temp_cb(x, y) = sum(abs.(x - y))
distanceFormulas["Cityblock"] = temp_cb

# Setting TotalVariation
temp_tv(x, y) = sum(abs.(x - y)) / 2
distanceFormulas["TotalVariation"] = temp_tv

# Setting Chebyshev
temp_chby(x, y) = maximum(abs.(x - y))
distanceFormulas["Chebyshev"] = temp_chby

# Setting Jaccard
temp_jcc(x, y) = 1 - sum(min(x, y)) / sum(max(x, y))
distanceFormulas["Jaccard"] = temp_jcc

# Setting BrayCurtis
temp_bc(x, y) = sum(abs.(x - y)) / sum(abs.(x + y))
distanceFormulas["BrayCurtis"] = temp_bc

# Setting SpanNormDist
temp_snd(x, y) = maximum(x - y) - minimum(x - y)
distanceFormulas["SpanNormDist"] = temp_snd

"""
    GetDistance(distanceType::DistanceType,
    v1::AbstractArray{<: Number}, v2::AbstractArray{<: Number})

This function compute distance between two numeric vectors. You can use
diferents distance calculations selecting with "DistanceType".

# Examples:

    GetDistance(Euclidean::DistanceType, [2,2], [2,4])
    GetDistance(Cityblock::DistanceType, [2,2], [2,4])
    GetDistance(TotalVariation::DistanceType, [2,2], [2,4])
"""
function GetDistance(distanceType::DistanceType, v1::AbstractArray{<: Number}, v2::AbstractArray{<: Number})
    if length(v1) != length(v2)
        throw(DimensionMismatch("The dimensions of the vectors are different. Therefore, impossible to calculate!\n
        Vector1: $(length(v1)) // Vector2: $(length(v2))."))
    end

    return distanceFormulas[string(distanceType)](v1, v2)
end

# Formulas from: https://github.com/JuliaStats/Distances.jl


function rbc()
	∞ = Inf
	@variables α, β, ρ, δ, σ
	@variables t::Integer, k(..), z(..), c(..), q(..), i(..)
	@variables Ω_1
	x = [k, z]
	y = [c, q, i]
	p = [α, β, ρ, δ, σ, Ω_1]

	H = [1 / c(t) - (β / c(t + 1)) * (α * exp(z(t + 1)) * k(t + 1)^(α - 1) + (1 - δ)),
	c(t) + k(t + 1) - (1 - δ) * k(t) - q(t),
	q(t) - exp(z(t)) * k(t)^α,
	z(t + 1) - ρ * z(t),
	i(t) - (k(t + 1) - (1 - δ) * k(t))]

	steady_states = [k(∞) ~ (((1 / β) - 1 + δ) / α)^(1 / (α - 1)), z(∞) ~ 0,
				c(∞) ~ (((1 / β) - 1 + δ) / α)^(α / (α - 1)) -
						δ * (((1 / β) - 1 + δ) / α)^(1 / (α - 1)),
				q(∞) ~ (((1 / β) - 1 + δ) / α)^(α / (α - 1)),
				i(∞) ~ δ * (((1 / β) - 1 + δ) / α)^(1 / (α - 1))]

	steady_states_iv = [k(∞) ~ (((1 / β) - 1 + δ) / α)^(1 / (α - 1)), z(∞) ~ 0,
				c(∞) ~ (((1 / β) - 1 + δ) / α)^(α / (α - 1)) -
						δ * (((1 / β) - 1 + δ) / α)^(1 / (α - 1)),
				q(∞) ~ (((1 / β) - 1 + δ) / α)^(α / (α - 1)),
				i(∞) ~ δ * (((1 / β) - 1 + δ) / α)^(1 / (α - 1))]

	n_ϵ = 1
	n_x = length(x)
	n_y = length(y)
	n_p = length(p)
	Γ = reshape([σ], n_ϵ, n_ϵ)
	η = reshape([0; -1], n_x, n_ϵ) # η is n_x * n_ϵ matrix

	n_z = 2 # number of observables
	Q = zeros(n_z, n_x + n_y) # the order is [y, x]
	Q[1, 1] = 1.0 # c
	Q[2, 3] = 1.0 # i

	Ω = [Ω_1, Ω_1]

	return H, (; t, x, y, p, steady_states, steady_states_iv, Γ, η, Ω, Q), "rbc"
end


module APCA
# Write your package code here.
using DataFrames
using Wavelets
using StatsBase
using ShiftedArrays

function runAPCA(oC::Vector, M::Int)
     #K. Chakrabarti et al.
     #Table V. An Algorithm to Produce the APCA
     #Algorithm Compute APCA(C,M )2
     #begin
     #1. if length(C) is not a power of two, pad it with zeros to make it so.
     #2. Perform the Haar Discrete Wavelet Transform on C.
     #3. Sort coefficients in order of decreasing normalized magnitude, truncate after M.
     #4. Reconstruct approximation (APCA representation) of C from retained coeffs.
     #5. If C was padded with zeros, truncate it to the original length.
     #6. Replace approximate segment mean values with exact mean values.
     #7. while the number of segments is greater than M
     #8. Merge the pair of segments that can be merged with least rise in error
     #9. endwhile
     #end
    # test values
    #M  = 3
    #oC = [7.,5.,5.,3.,3.,3.,4.,6.] # example in paper
    #oC = vcat(rand(40).+3,rand(40).+2,rand(40).-1)

    # 1. If length of oC is not a power of two, pad it with zeros
    newexp = ceil(log(size(oC)[1])/ log(2) )
    padsize = Int32(2^newexp - size(oC)[1])
    oCp = vcat(oC,fill(0.,padsize))
    fvlength = log2(size(oCp)[1]) # length power

    # 2. Perform the Haar Discrete Wavelet Transform on C.
    Haar = dwt(oCp,wavelet(WT.haar))

    #  Transform into weighting and structure in paper
    transHaar = []
    for i in [1:1:Int32(fvlength);]
        j = i-1
        addvec = -Haar[2^j+1:2^i] ./ sqrt(2^(fvlength - i + 1)) # get diff coeffs
        addvec2 = addvec ./ 2^((i-1)/2) # normalize
        append!(transHaar,addvec2)
    end
    #add final mean to normalized and non-normalized
    transHaar=append!(first(Haar,1)/sqrt(2^fvlength),transHaar)

    #3. Sort coefficients in order of decreasing normalized magnitude, truncate after M
    # go to float32 because of rounding
    if size(transHaar)[1] > M
        inds = sort(DataFrame(t=abs.(Float32.(transHaar)), i=1:length(transHaar)),:t,rev=true)[!,:i][1:M,:]
    else
        inds = sort(DataFrame(t=abs.(Float32.(transHaar)), i=1:length(transHaar)),:t,rev=true)[!,:i]
    end
    # get non-normalized coefs
    #4. Reconstruct approximation (APCA representation) of C from retained coeffs.
    Haar[Not(inds)] .= 0
    newrep =  idwt(Haar,wavelet(WT.haar))

    #5. If C was padded with zeros, truncate it to the original length.
    newrep = newrep[1:size(oC)[1]]

    #6. Replace approximate segment mean values with exact mean values.
    repeatlengths = vcat(1,rle(newrep)[2]) # get repeat lengths

    exactrep=[]
    for k in 2:size(repeatlengths)[1]
        s =sum(repeatlengths[1:k-1])
        e =repeatlengths[k] + s - 1
        um =mean(oCp[s:e])

        append!(exactrep,fill(um,repeatlengths[k]))
    end

    #7. while the number of segments is greater than M
    while size(rle(exactrep)[2])[1] > M
        z=vcat(rle(exactrep)[1] .- lag(rle(exactrep)[1]))
        replace!(z, missing =>-999.)
        z=abs.(convert(Vector{Float64}, z))
        l=findfirst(minimum(z) .== z)

        newreplengths = vcat(1,rle(exactrep)[2])
        exactrep
        s =sum(newreplengths[1:l-1])
        e =newreplengths[l]+newreplengths[l+1] + s - 1
        um =mean(exactrep[s:e])
        exactrep[s:e] .= um

    end

    return exactrep

  end
export runAPCA

end


using BenchmarkTools
using Distributions
using GLM
using HypothesisTests
using LinearAlgebra
using Plots
using Random
using RDatasets
using SpecialFunctions
using Statistics

# Ridge regression

n = 10000
m = 1000

Random.seed!(666)
X = randn(n, m)

y = 10*X[:,1] + X[:,2] + randn(n)

# Exercise

# Comparison

@btime ridge_reg(X, y, 10);

@btime ridge_reg(X, y, 10, Q, Q_inv, λ);

# Dependence on mu

μs = range(0, 1000; length=50)

ws = hcat(ridge_reg.(Ref(X), Ref(y), μs, Ref(Q), Ref(Q_inv), Ref(λ))...)

plot(μs, abs.(ws');
    label="",
    yscale=:log10,
    xlabel="mu",
    ylabel="weights: log scale",
)

# LASSO

S(x, η) = max(x-η, 0) - max(-x-η, 0)

function lasso(X, y, μ, Q, Q_inv, λ;
        max_iter = 100,
        ρ = 1e3,
        w = zeros(size(X,2)),
        u = zeros(size(X,2)),
        z = zeros(size(X,2)),
    )

    for i in 1:max_iter
        w = Q * ( (Diagonal(1 ./ (λ .+ ρ)) * ( Q_inv * (X'*y + ρ*(z-u)))))
        z = S.(w + u, μ / ρ)
        u = u + w - z
    end
    return w, u, z
end

ws = zeros(size(X,2), length(μs))

for (i, μ) in enumerate(μs)
    global w, u, z
    w, u, z = i > 1 ? lasso(X, y, μ, Q, Q_inv, λ; w, u, z) : lasso(X, y, μ, Q, Q_inv, λ)
    ws[:,i] = w
end

plot(μs, abs.(ws');
    label="",
    yscale=:log10,
    xlabel="mu",
    ylabel="weights: log scale",
)

plot(0:0.1:10, gamma;
    xlabel="x",
    ylabel="gamma(x): log scale",
    label="",
    yscale=:log10,
)

# Exercise

# Exercise

# Volume of a ball

ms = 1:15
ns = Int64.([1e1; 1e3; 1e5])

Random.seed!(666)

plt = plot(ms, m -> volume_true(m, 1);
    xlabel="dimension",
    ylabel="unit ball volume",
    legend=:topleft,
    label="True",
    line=(4,:black),
)

for n in ns
    plot!(plt, ms, m -> volume_monte_carlo.(m; n=n); label="n = $n")
end

display(plt)

# Generating from the uniform distributions

rand(Uniform(-1, 1), 10, 5)

# Sampling from distributions

d1 = Normal()
d2 = Normal(1, 1)
d3 = Normal(0, 0.01)

f1(x) = pdf(d1, x)
f2(x) = pdf(d2, x)
f3(x) = pdf(d3, x)

function plot_histogram(xs, f; kwargs...)
    plt = histogram(xs;
        label="Sampled density",
        xlabel = "x",
        ylabel = "pdf(x)",
        nbins = 85,
        normalize = :pdf,
        opacity = 0.5,
        kwargs...
    )

    plot!(plt, range(minimum(xs), maximum(xs); length=100), f;
        label="True density",
        line=(4,:black),
    )

    return plt
end

plot_histogram(rand(d1, 1000000), f1)

# Exercise

# Rejection sampling

xlims = (-10, 10)

for (f, d) in zip((f1, f2, f3), (d1, d2, d3))
    Random.seed!(666)
    xs = rejection_sampling(f, f(d.μ), xlims...)

    pl = plot_histogram(xs, f)
    display(pl)
end

# Exercise

# Hypothesis testing

Random.seed!(666)

n = 1000
xs = randn(n)

# Exercise

# One-sample t-test

OneSampleTTest(xs)

# Linear models

wages = dataset("plm", "Snmesp")
wages.W = 2. .^ (wages.W)

X = Matrix(wages[:, [:N, :Y, :I, :K, :F]])
X = hcat(ones(size(X,1)), X)
y = wages[:, :W]

w0 = (X'*X) \ (X'*y)

model = lm(@formula(W ~ 1 + N + Y + I + K + F), wages)

# Exercise

# Plot histograms

y_hat = predict(model)

plot_histogram(y_hat, x -> pdf(Normal(mean(y_hat), std(y_hat)), x))

plot_histogram(y_hat, x -> pdf(fit(Normal, y_hat), x))

test_normality = ExactOneSampleKSTest(y_hat, Normal(mean(y_hat), std(y_hat)))

# Generalized linear models

model = glm(@formula(W ~ 1 + N + Y + I + K + F), wages, InverseGaussian(), SqrtLink())

# Exercise


"""
    IdentityMap

An identity map,

```math
    x ↦ x.
```

### Fields

- `dim` -- dimension
"""
struct IdentityMap <: AbstractMap
    dim::Int
end
statedim(m::IdentityMap) = m.dim
outputdim(m::IdentityMap) = m.dim
inputdim(::IdentityMap) = 0
islinear(::IdentityMap) = true
isaffine(::IdentityMap) = true
apply(m::IdentityMap, x) = x

"""
    ConstrainedIdentityMap

An identity map with state constraints of the form:

```math
    x ↦ x, x(t) ∈ \\mathcal{X}.
```

### Fields

- `dim` -- dimension
- `X`   -- state constraints
"""
struct ConstrainedIdentityMap{ST} <: AbstractMap
    dim::Int
    X::ST
end
statedim(m::ConstrainedIdentityMap) = m.dim
stateset(m::ConstrainedIdentityMap) = m.X
outputdim(m::ConstrainedIdentityMap) = m.dim
inputdim(::ConstrainedIdentityMap) = 0
islinear(::ConstrainedIdentityMap) = true
isaffine(::ConstrainedIdentityMap) = true
apply(::ConstrainedIdentityMap, x) = x

"""
    LinearMap

A linear map,

```math
    x ↦ Ax
```

### Fields

- `A` -- matrix
"""
struct LinearMap{T, MT<:AbstractMatrix{T}} <: AbstractMap
    A::MT
end
statedim(m::LinearMap) = size(m.A, 2)
outputdim(m::LinearMap) = size(m.A, 1)
inputdim(::LinearMap) = 0
islinear(::LinearMap) = true
isaffine(::LinearMap) = true
apply(m::LinearMap, x) = m.A * x

"""
    ConstrainedLinearMap

A linear map with state constraints of the form:

```math
    x ↦ Ax, x(t) ∈ \\mathcal{X}.
```

### Fields

- `A` -- matrix
- `X` -- state constraints
"""
struct ConstrainedLinearMap{T, MT<:AbstractMatrix{T}, ST} <: AbstractMap
    A::MT
    X::ST
end
statedim(m::ConstrainedLinearMap) = size(m.A, 2)
stateset(m::ConstrainedLinearMap) = m.X
outputdim(m::ConstrainedLinearMap) = size(m.A, 1)
inputdim(::ConstrainedLinearMap) = 0
islinear(::ConstrainedLinearMap) = true
isaffine(::ConstrainedLinearMap) = true
apply(m::ConstrainedLinearMap, x) = m.A * x

"""
    AffineMap

An affine map,

```math
    x ↦ Ax + b.
```

### Fields

- `A` -- matrix
- `b` -- vector
"""
struct AffineMap{T, MT<:AbstractMatrix{T}, VT<:AbstractVector{T}} <: AbstractMap
    A::MT
    b::VT
    function AffineMap(A::MT, b::VT) where {T, MT<:AbstractMatrix{T}, VT<:AbstractVector{T}}
        @assert size(A, 1) == length(b)
        return new{T, MT, VT}(A, b)
    end
end
statedim(m::AffineMap) = size(m.A, 2)
outputdim(m::AffineMap) = length(m.b)
inputdim(::AffineMap) = 0
islinear(::AffineMap) = false
isaffine(::AffineMap) = true
apply(m::AffineMap, x) = m.A * x + m.b

"""
    ConstrainedAffineMap

An affine map with state constraints of the form:

```math
    x ↦ Ax + b, x(t) ∈ \\mathcal{X}.
```

### Fields

- `A` -- matrix
- `b` -- vector
- `X` -- state constraints
"""
struct ConstrainedAffineMap{T, MT<:AbstractMatrix{T}, VT<:AbstractVector{T}, ST} <: AbstractMap
    A::MT
    b::VT
    X::ST
    function ConstrainedAffineMap(A::MT, b::VT, X::ST) where {T, MT<:AbstractMatrix{T}, VT<:AbstractVector{T}, ST}
        @assert size(A, 1) == length(b)
        return new{T, MT, VT, ST}(A, b, X)
    end
end
statedim(m::ConstrainedAffineMap) = size(m.A, 2)
stateset(m::ConstrainedAffineMap) = m.X
outputdim(m::ConstrainedAffineMap) = length(m.b)
inputdim(::ConstrainedAffineMap) = 0
islinear(::ConstrainedAffineMap) = false
isaffine(::ConstrainedAffineMap) = true
apply(m::ConstrainedAffineMap, x) = m.A * x + m.b

"""
    LinearControlMap

A linear control map,

```math
    (x, u) ↦ Ax + Bu.
```

### Fields

- `A` -- matrix
- `B` -- matrix
"""
struct LinearControlMap{T, MTA<:AbstractMatrix{T}, MTB<:AbstractMatrix{T}} <: AbstractMap
    A::MTA
    B::MTB
    function LinearControlMap(A::MTA, B::MTB) where {T, MTA<:AbstractMatrix{T}, MTB<:AbstractMatrix{T}}
        @assert size(A, 1) == size(B, 1)
        return new{T, MTA, MTB}(A, B)
    end
end
statedim(m::LinearControlMap) = size(m.A, 2)
inputdim(m::LinearControlMap) = size(m.B, 2)
outputdim(m::LinearControlMap) = size(m.A, 1)
islinear(::LinearControlMap) = true
isaffine(::LinearControlMap) = true
apply(m::LinearControlMap, x, u) = m.A * x + m.B * u

"""
    ConstrainedLinearControlMap

A linear control map with state and input constraints,

```math
    (x, u) ↦ Ax + Bu, x ∈ \\mathcal{X}, u ∈ \\mathcal{U}.
```

### Fields

- `A` -- matrix
- `B` -- matrix
- `X` -- state constraints
- `U` -- input constraints
"""
struct ConstrainedLinearControlMap{T, MTA <: AbstractMatrix{T}, MTB <: AbstractMatrix{T}, ST, UT} <: AbstractMap
    A::MTA
    B::MTB
    X::ST
    U::UT
    function ConstrainedLinearControlMap(A::MTA, B::MTB, X::ST, U::UT) where {T, MTA<:AbstractMatrix{T}, MTB<:AbstractMatrix{T}, ST, UT}
        @assert size(A, 1) == size(B, 1)
        return new{T, MTA, MTB, ST, UT}(A, B, X, U)
    end
end
statedim(m::ConstrainedLinearControlMap) = size(m.A, 2)
stateset(m::ConstrainedLinearControlMap) = m.X
outputdim(m::ConstrainedLinearControlMap) = size(m.A, 1)
inputdim(m::ConstrainedLinearControlMap) = size(m.B, 2)
inputset(m::ConstrainedLinearControlMap) = m.U
islinear(::ConstrainedLinearControlMap) = true
isaffine(::ConstrainedLinearControlMap) = true
apply(m::ConstrainedLinearControlMap, x, u) = m.A * x + m.B * u

"""
    AffineControlMap

An affine control map,

```math
    (x, u) ↦ Ax + Bu + c.
```

### Fields

- `A` -- matrix
- `B` -- matrix
- `c` -- vector
"""
struct AffineControlMap{T, MTA <: AbstractMatrix{T}, MTB <: AbstractMatrix{T}, VT<:AbstractVector{T}} <: AbstractMap
    A::MTA
    B::MTB
    c::VT
    function AffineControlMap(A::MTA, B::MTB, c::VT) where {T, MTA<:AbstractMatrix{T}, MTB<:AbstractMatrix{T}, VT<:AbstractVector{T}}
        @assert size(A, 1) == size(B, 1) == length(c)
        return new{T, MTA, MTB, VT}(A, B, c)
    end
end
statedim(m::AffineControlMap) = size(m.A, 2)
outputdim(m::AffineControlMap) = size(m.A, 1)
inputdim(m::AffineControlMap) = size(m.B, 1)
islinear(::AffineControlMap) = false
isaffine(::AffineControlMap) = true
apply(m::AffineControlMap, x, u) = m.A * x + m.B * u + m.c

"""
    ConstrainedAffineControlMap

An affine control map with state and input constraints,

```math
    (x, u) ↦ Ax + Bu + c, x ∈ \\mathcal{X}, u ∈ \\mathcal{U}.
```

### Fields

- `A` -- matrix
- `B` -- matrix
- `c` -- vector
- `X` -- state constraints
- `U` -- input constraints
"""
struct ConstrainedAffineControlMap{T, MTA<:AbstractMatrix{T}, MTB<:AbstractMatrix{T}, VT<:AbstractVector{T}, ST, UT} <: AbstractMap
    A::MTA
    B::MTB
    c::VT
    X::ST
    U::UT
    function ConstrainedAffineControlMap(A::MTA, B::MTB, c::VT, X::ST, U::UT) where {T, MTA<:AbstractMatrix{T}, MTB<:AbstractMatrix{T}, VT<:AbstractVector{T}, ST, UT}
        @assert size(A, 1) == size(B, 1) == length(c)
        return new{T, MTA, MTB, VT, ST, UT}(A, B, c, X, U)
    end
end
statedim(m::ConstrainedAffineControlMap) = size(m.A, 2)
stateset(m::ConstrainedAffineControlMap) = m.X
inputdim(m::ConstrainedAffineControlMap) = size(m.B, 2)
inputset(m::ConstrainedAffineControlMap) = m.U
outputdim(m::ConstrainedAffineControlMap) = size(m.A, 1)
islinear(::ConstrainedAffineControlMap) = false
isaffine(::ConstrainedAffineControlMap) = true
apply(m::ConstrainedAffineControlMap, x, u) = m.A * x + m.B * u + m.c

"""
    ResetMap

A reset map,

```math
    x ↦ R(x),
```
such that a subset of the variables is given a specified value, and the rest
are unchanged.

### Fields

- `dim`  -- dimension
- `dict` -- dictionary whose keys are the indices of the variables that are reset,
            and whose values are the new values
"""
struct ResetMap{N} <: AbstractMap
    dim::Int
    dict::Dict{Int, N}
end
statedim(m::ResetMap) = m.dim
inputdim(::ResetMap) = 0
outputdim(m::ResetMap) = m.dim
islinear(::ResetMap) = false
isaffine(::ResetMap) = true

# convenience constructor for a list of pairs instead of a dictionary
ResetMap(dim::Int, args::Pair{Int, <:N}...) where {N} = ResetMap(dim, Dict{Int, N}(args))

"""
    ConstrainedResetMap

A reset map with state constraints of the form:

```math
    x ↦ R(x), x ∈ \\mathcal{X},
```
such that the specified variables are assigned a given value, and the remaining
variables are unchanged.

### Fields

- `dim`  -- dimension
- `X`    -- state constraints
- `dict` -- dictionary whose keys are the indices of the variables that are
            reset, and whose values are the new values
"""
struct ConstrainedResetMap{N, ST} <: AbstractMap
    dim::Int
    X::ST
    dict::Dict{Int, N}
end
statedim(m::ConstrainedResetMap) = m.dim
stateset(m::ConstrainedResetMap) = m.X
inputdim(::ConstrainedResetMap) = 0
outputdim(m::ConstrainedResetMap) = m.dim
islinear(::ConstrainedResetMap) = false
isaffine(::ConstrainedResetMap) = true

# convenience constructor for a list of pairs instead of a dictionary
ConstrainedResetMap(dim::Int, X::ST, args::Pair{Int, <:N}...) where {N, ST} =
    ConstrainedResetMap(dim, X, Dict{Int, N}(args))

function apply(m::Union{ResetMap, ConstrainedResetMap}, x)
    y = copy(x)
    for (index, value) in pairs(m.dict)
        y[index] = value
    end
    return y
end


using Munkres
import Base: eye, rand, *, inv

# Type definitions

immutable SymmetricGroup{N} <: Group
    σ::Vector{Int}
end

Sn{N} = SymmetricGroup{N}
SymmetricGroup(σ::Vector{Int}) = SymmetricGroup{length(σ)}(σ)

# Identity element

eye{N}(::Type{Sn{N}}) = Sn{N}(1:N)

# Random group element

function rand{N}(::Type{Sn{N}})
    σ = shuffle(1:N)
    Sn(σ)
end

# Defining representation

@inbounds function defrep!{N, T}(M::Matrix{T}, g::Sn{N})
    σ = g.σ
    n = size(M, 1)
    for i in 1:n, j in 1:n
        if i == σ[j]
            M[i, j] = one(T)
        else
            M[i, j] = zero(T)
        end
    end

    return M
end

defrep{N}(g::Sn{N}) = defrep!(Matrix{Float64}(N, N), g)

function nearest{N, T}(::Type{Sn{N}}, M_defrep::Matrix{T})
    Sn{N}(munkres(-M_defrep'))
end

# Multiplication
function *{N}(g₁::Sn{N}, g₂::Sn{N})
    Sn{N}(g₁.σ[g₂.σ])
end

# Inversion
function inv{N}(g::Sn{N})
    Sn{N}(indexin(1:N, g.σ))
end


# Practice implementation of code blocks 4.1 through 4.6 in McElreath's Statistical
# Rethinking, 2nd ed.

using Random
using Distributions: Uniform, rand, pdf
using Plots: histogram

# Set up
Random.seed!(17)

# Code block 4.1
distro = Uniform(-1, 1)
steps = rand(distro, 1000, 16)
positions = sum(steps, dims=2)
display(histogram(positions, bins=:40, xlabel="Position", ylabel="Frequency"))

# Code blocks 4.2 and 4.3: Gaussian through multiplication
distro = Uniform(0, 0.1)
effects = rand(distro, 1000, 12) .+ 1
growth = prod(effects, dims=2)
display(histogram(growth, bins=:40, xlabel="Growth", ylabel="Frequency"))

# Code block 4.4
distro = Uniform(0, 0.5)
effects = rand(distro, 1000, 12) .+ 1
big_growth = prod(effects, dims=2)
display(histogram(big_growth, bins=:40, xlabel="Growth", ylabel="Frequency"))

distro = Uniform(0, 0.01)
effects = rand(distro, 1000, 12) .+ 1
small_growth = prod(effects, dims=2)
display(histogram(small_growth, bins=:40, xlabel="Growth", ylabel="Frequency"))

# Code block 4.5
log_big_growth = log.(big_growth)
display(histogram(log_big_growth, bins=:40, xlabel="Log(growth)", ylabel="Frequency"))

# Code block 4.6
w = 6
n = 9
param_grid = 0.0:1/100:1.0
posterior = [pdf(Binomial(n, p), w) * pdf(Uniform(0, 1), p) for p in param_grid]
posterior = posterior / sum(posterior)


@testset "α Filter" begin

	@testset "1D α Filter" begin
		# Input Data
		V = [ 1. 3. 2. 6. 7. 8. 12. 10. ]
		DT = AlphaShapes.delaunayTriangulation(V)

		# Expected Output
		VV = [[1],[2],[3],[4],[5],[6],[7],[8]]
		EV = [[1,3],[2,3],[2,4],[4,5],[5,6],[6,8],[7,8]]

		# Evaluation
		filter = AlphaShapes.alphaFilter(V, DT)

		@test length(unique(values(filter))) == 4
		@test sort(unique(values(filter))) == [0.0, 0.5, 1.0, 1.5]
		@test length(unique(keys(filter))) == 15
		@test sort([v for v in unique(keys(filter)) if length(v) == 2]) == EV
	end

	@testset "2D α Filter" begin
		# Input Data
		V = [
			0.0 1.0 0.0 2.0;
			0.0 0.0 1.0 2.0
		]

		# Expected Output
		VV = [[1],[2],[3],[4]]
		EV = [[1, 2],[1, 3],[2, 3],[2, 4],[3, 4]]
		FV = [[1, 2, 3],[2, 3, 4]]

		# Evaluation
		filter = AlphaShapes.alphaFilter(V)

		@test length(unique(values(filter))) == 5
		@test isapprox(sort(unique(values(filter))),
			[0.0, 0.5, 0.7071, 1.1180, 1.1785], atol=1e-4
		)
		@test length(unique(keys(filter))) == 11
		@test sort([v for v in unique(keys(filter)) if length(v) == 1]) == VV
		@test sort([v for v in unique(keys(filter)) if length(v) == 2]) == EV
		@test sort([v for v in unique(keys(filter)) if length(v) == 3]) == FV
	end

	# @testset "3D α Filter" begin
	# 	V = [
	# 		0.0 1.0 0.0 0.0 1.0 0.0;
	# 		0.0 0.0 1.0 0.0 0.0 1.0;
	# 		0.0 0.0 0.0 1.0 1.0 2.0
	# 	]
	#
	# 	# Expected Output
	# 	VV = [[1],[2],[3],[4],[5],[6]]
	# 	CV = [[1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 5, 6]]
	# 	# Evaluation
	# 	filter = AlphaShapes.alphaFilter(V, digits=4)
	#
	# 	@test length(unique(values(filter))) == 6
	# 	@test sort(unique(values(filter))) == [0.0, 0.5, 0.7071, 0.866, 1.0, 1.118]
	# 	@test length(unique(keys(filter))) == 31
	# 	@test sort([v for v in unique(keys(filter)) if length(v) == 1]) == VV
	# 	@test sort([v for v in unique(keys(filter)) if length(v) == 4]) == CV
	# end

end

@testset "α Simplices Evaluation" begin

	@testset "1D α Simplex" begin
		# Input Data
		V = [
			0.0 1.0 2.0 7.0 5.0 3.0 14.0 11.0
		]
		filtration = AlphaShapes.alphaFilter(V)

		# α = 0.0
		α_simplices = AlphaShapes.alphaSimplex(V, filtration, 0.0)
		@test α_simplices[1] == [[1],[2],[3],[4],[5],[6],[7],[8]]
		@test isempty(α_simplices[2])
		# α = 0.5
		α_simplices = AlphaShapes.alphaSimplex(V, filtration, 0.5)
		@test α_simplices[2] == [[1,2],[2,3],[3,6]]
		# α = 1.0
		α_simplices = AlphaShapes.alphaSimplex(V, filtration, 1.0)
		@test α_simplices[2] == [[1, 2], [2, 3], [3, 6], [4, 5], [5, 6]]
		# α = 1.5
		α_simplices = AlphaShapes.alphaSimplex(V, filtration, 1.5)
		@test α_simplices[2] == [[1, 2], [2, 3], [3, 6], [4, 5], [5, 6], [7, 8]]

	end

	@testset "2D α Simplex" begin
		# Input Data
		V = [
			0.0 1.0 0.0 2.0;
			0.0 0.0 1.0 0.3
		]
		filtration = AlphaShapes.alphaFilter(V)

		# α = 0.0
		α_simplices = AlphaShapes.alphaSimplex(V, filtration, 0.0)
		@test α_simplices[1] == [[1],[2],[3],[4]]
		@test isempty(α_simplices[2])
		@test isempty(α_simplices[3])
		# α = 0.5
		α_simplices = AlphaShapes.alphaSimplex(V, filtration, 0.5)
		@test α_simplices[2] == [[1,2],[1,3]]
		@test isempty(α_simplices[3])
		# α = 1.0
		α_simplices = AlphaShapes.alphaSimplex(V, filtration, 1.0)
		@test α_simplices[2] == [[1,2],[1,3],[2,3],[2,4]]
		@test α_simplices[3] == [[1,2,3]]
		# α = 1.5
		α_simplices = AlphaShapes.alphaSimplex(V, filtration, 1.5)
		@test α_simplices[2] == [[1,2],[1,3],[2,3],[2,4],[3,4]]
		@test α_simplices[3] == [[1,2,3],[2,3,4]]

	end

	@testset "3D α Simplex" begin
		# Input Data
		V = [
			0.0  1.0  0.0  0.0  2.0;
			0.0  0.0  1.0  0.0  2.0;
			0.0  0.0  0.0  1.0  2.0
		]
		filtration = AlphaShapes.alphaFilter(V)

		# α = 0.0
		α_simplices = AlphaShapes.alphaSimplex(V, filtration, 0.0)
		@test α_simplices[1] == [[1],[2],[3],[4],[5]]
		@test isempty(α_simplices[2])
		@test isempty(α_simplices[3])
		@test isempty(α_simplices[4])
		# α = 0.5
		α_simplices = AlphaShapes.alphaSimplex(V, filtration, 0.5)
		@test α_simplices[2] == [[1,2],[1,3],[1,4]]
		@test isempty(α_simplices[3])
		@test isempty(α_simplices[4])
		# α = 1.0
		α_simplices = AlphaShapes.alphaSimplex(V, filtration, 1.0)
		@test α_simplices[2] == [[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]]
		@test α_simplices[3] == [[1,2,3],[1,2,4],[1,3,4],[2,3,4]]
		@test α_simplices[4] == [[1,2,3,4]]

		# α = 1.5
		α_simplices = AlphaShapes.alphaSimplex(V, filtration, 1.6)
		@test α_simplices[4] == [[1,2,3,4],[2,3,4,5]]
	end

end


using ISA #References: Sandoval, Steven, and Phillip L. De Leon. "The Instantaneous Spectrum: A General Framework for Time-Frequency Analysis." IEEE Transactions on Signal Processing 66.21 (2018): 5679-5693.

#----------------------------
# BASIC EXAMPLES
#----------------------------

#DEFINE 0th CANONICAL TRIPLET
a₀(t) = exp(-t^2)
ω₀(t) = 100
φ₀ = 0
𝐶₀ = Tuple([a₀,ω₀,φ₀])

#DEFINE 0th COMPONENT
ψ₀ = AMFMcomp(𝐶₀)

#DEFINE 1st CANONICAL TRIPLET
a₁(t) = 1
ω₁(t) = 10*t
φ₁ = π
𝐶₁ = Tuple([a₁,ω₁,φ₁])

#DEFINE 2nd CANONICAL TRIPLET
a₂(t) = 0.8*cos(2t)
ω₂(t) = 10 + 7.5*sin(t)
φ₂ = π
𝐶₂ = Tuple([a₂,ω₂,φ₂])

#DEFINE THE COMPONENT SET
𝑆 = [𝐶₀,𝐶₁,𝐶₂]

#DEFINE THE AMFM MODEL
z = AMFMmodel(𝑆)

#DEFINE A TIME INDEX
t = Array(0.0:0.005:2.0)

#EVALUATE THE 0th COMPONENT AT THE POINTS IN THE TIME INDEX
ψ₀(t)

#EVALUATE THE AMFM MODEL AT THE POINTS IN THE TIME INDEX
z(t)

#----------------------------
# FOURIER SERIES
#----------------------------

T = 1
aₖ(k) = 1
kInds = Vector(-10:10)

z₀ = fourierSeries(T,aₖ,kInds)

#----------------------------
# PLOTS
#----------------------------

p1 = isaPlot3d(𝐶₁, t)

p2 = isaPlot3d(ψ₀, t)

p3 = isaPlot3d(z₀, t, FreqUnits="Hz")

p4 = isaPlot3d(𝑆, t, FreqUnits="Hz")

using Plots
Plots.plot(p1,p2,p3,p4)


using DataStructures
using Printf

m = [[0, Inf, Inf] [7, 0, 4] [2, Inf, 0]]
m2 = [[0, 7, 2] [Inf, 0, Inf] [Inf, 4, 0]]

g(x,y) = x+y
f(x,y) = min(x,y)

function floyd_warshall(adjacency_matrix, nodes, f, g)
    for k = 1:nodes
        for i = 1:nodes
            for j = 1:nodes
                adjacency_matrix[i,j] = f(adjacency_matrix[i,j], g(adjacency_matrix[i,k], adjacency_matrix[k,j]))
            end
        end
    end
    return adjacency_matrix
end

function transitive_closure(adjacency_matrix, nodes)
    M = copy(adjacency_matrix)
    T = zeros(Int64,nodes,nodes)
    for i = 1:nodes
        for j = 1:nodes
            if (i == j || ((M[i,j] > 0) & (M[i,j] != Inf)))
                println(M[i,j])
                T[i,j] = 1
            end
        end
    end
    println(T)
    for k = 1:nodes
        for i = 1:nodes
            for j = 1:nodes
                T[i,j] = (convert(Bool,T[i,j])|| (convert(Bool,T[i,k]) && convert(Bool,T[k,j])))
            end
        end
    end
    return T
end

ballots = ["ABC", "CBA", "BAC"]
voters = 3
candidates = 3

function create_preference_matrix(ballots, voters, candidates)
    P = zeros(candidates,candidates)
    for j = 1:candidates # iterating over each candidate
        c = string('A'+j-1) # character of current candidate
        for vote in ballots
            current = findfirst(c, vote)[1] # position of current candidate in current ballot
            println(vote)
            for k = 1:candidates # the position of each candidate in respect to current (j)
                other =  findfirst(string(vote[k]), vote)[1] # position of other candidates in current ballot
                # @printf "Comparing current %s at position %d with %c at position %d\n" c current vote[k] other
                if (current < other) # if index of current character is smaller than other in the ballot
                    u = Int(vote[k]-'A'+1) # the index of other candidate in respect to current
                    P[j,u] = P[j,u] + 1
                end
            end
            #println("")
        end
    end
    return P
end

function find_strongest_paths(preference_matrix, candidates)

end


using DifferentialEquations
using Distributions
using Distances
using Gadfly
using ApproxBayes
using DataFrames
using Plots

function getsolution(sol, times)
    x1 = map(x -> sol(x)[1], times)
    y1 = map(x -> sol(x)[2], times)
  return x1, y1
end

# define Lotka Voltera ODE where a and b can be modified
f = @ode_def LV begin
  dx = a*x - b*x*y
  dy = b*x*y - y
end a b

x0 = [1.0; 0.5]
tspan = (0.0, 15.0)
p = [2.0, 0.8]
prob = ODEProblem(f, x0, tspan, p)
sol = solve(prob)
Plots.plot(sol)

#generate target data by sampling 15 points and then adding Gaussian noise
times = 1.0:2.0:15.0
x, y = getsolution(sol, times)
x .+= rand(Normal(0.0, 1), length(times))
y .+= rand(Normal(0.0, 1), length(times))
targetdata = [x, y]

#simulations function for ABC. return distance (sum of squared distances) and solution
function simLV(params, constants, targetdata)
  a = params[1]
  b = params[2]
  x0 = [1.0; 0.5]
  tspan = (0.0, 15.0)
  times = 1.0:2.0:15.0
  prob = ODEProblem(f, x0, tspan, [a, b])
  sol = solve(prob)
  x1, y1 = getsolution(sol, times)
  d = sum((x1 .- targetdata[1]).^2 + (y1 .- targetdata[2]).^2)
  return d, sol
end

#define ABC setup type
setup = ABCSMC(simLV,
  2,
  0.1,
  Prior([Uniform(0.0, 5.0), Uniform(0.0, 5.0)]),
  maxiterations = 10^6,
  convergence = 0.001,
  nparticles = 1000
  )
  #run ABC SMC algorithm
@time ressmc = runabc(setup, targetdata, verbose = true, progress = true, parallel=true)

#show results
show(ressmc)

#plot posterior parameters
plotparameterposterior(ressmc)

#plots results using Gadfly
#Create data frame for target data
DFt = DataFrame(x = targetdata[1], y = targetdata[2], time = times)
d1 = stack(DFt, [:x, :y])

#get solution for
res = mean(ressmc.parameters, weights(ressmc.weights), 1)
x0 = [1.0; 0.5]
tspan = (0.0, 15.0)
prob = ODEProblem(f, x0, tspan, [res[1], res[2]])
sol = solve(prob)
times2 = 1.0:0.001:15.0
x2, y2 = getsolution(sol, times2)

DF1 = DataFrame(x = x2, y = y2, time = times2)
d2 = stack(DF1, [:x, :y])

l1 = layer(d1,x = :time, y = :value, color = :variable, Geom.point)
l2 = layer(d2,x = :time, y = :value, color = :variable, Geom.line)
Gadfly.plot(l1, l2)


export CnoidalWaveSerreGreenNaghdi, CnoidalSGN
using Elliptic

"""
    CnoidalWaveSerreGreenNaghdi(param; P=1)

Compute the Serre-Green-Naghdi cnoidal wave with prescribed `h₀<h₁<h₂`.
`h₁` is the minimum, `h₂` is the maximum of the wave.
As `h₀ -> h₁`, the cnoidal wave converges towards the solitary wave.
See for instance [Gavrilyuk, Nkonga, Shyue and Truskinovsky](https://doi.org/10.1088/1361-6544/ab95ac).

# Arguments
- `param :: NamedTuple`: parameters of the problem containing `h₀<h₁<h₂` and dimensionless parameters `ϵ` and `μ`, and number of collocation points `N`.
- `P :: Int`: (keyword, optional, default = 1) the number of periods of the cnoidal wave in the constructed mesh.

# Return values
`(η,u,v,mesh,param)` with
- `η :: Vector{Float64}`: surface deformation;
- `u :: Vector{Float64}`: layer-averaged velocity;
- `v :: Vector{Float64}`: derivative of the trace of the velocity potential at the surface;
- `mesh :: Mesh`: mesh collocation points;
- `param :: NamedTuple`: useful parameters
"""
function CnoidalWaveSerreGreenNaghdi(
                param :: NamedTuple;
                P = 1 :: Int)

        ϵ = param.ϵ
        μ = param.μ

        h₀=param.h₀
        h₁=param.h₁
        h₂=param.h₂
        c = sqrt(h₀*h₁*h₂)
        m = sqrt((h₂-h₁)/(h₂-h₀))
        κ = sqrt(3*(h₂-h₀))/(2*c)/sqrt(μ)
        λ = Elliptic.K(m^2)/κ
        mesh = Mesh((L=P*λ,N=param.N))
        formula = h₁ .-1 .+ (h₂-h₁)*(Jacobi.cn.(κ*mesh.x,m^2).^2)

        a₀ = h₀
        a₁ = h₂ - h₀
        formula2 = a₀ .-1 .+ a₁*(Jacobi.dn.(κ*mesh.x,m^2).^2)
        H₀= a₀+a₁*Elliptic.E(m^2)/Elliptic.K(m^2)
        u2 = c*(1 ./ H₀ .- 1 ./(1 .+ formula2))
        param = (h₀=h₀,h₁=h₁,h₂=h₂,a₀=a₀,a₁=a₁,H₀=H₀,c=c,λ=λ,m=m,κ=κ)

        η = formula/ϵ
        h = 1 .+ ϵ*η
        u = c*η./h
		k = mesh.k
        Dx=  1im * k
		F₀ = sqrt(μ)*Dx
        DxF(v) = real.(ifft(F₀ .* fft(v)))
		v = u - 1/3 ./h .* (DxF(h.^3 .*DxF(u)))
        #
        # h2 = 1 .+ ϵ*formula2
        # v2 = u2 - 1/3 ./h2 .* (DxF(h2.^3 .*DxF(u2)))

        return (η,u,v,mesh,param)

end

"""
    CnoidalSGN(param; P=1)

Build the initial data associated with `CnoidalWaveSerreGreenNaghdi(param; P=1)`, of type `InitialData`,
to be used in initial-value problems `Problem(model, initial::InitialData, param)`.
"""
struct CnoidalSGN <: InitialData

	η
	v
	label :: String
	info  :: String

	function CnoidalSGN(param; P=1)
		(η,u,v,mesh,para)=CnoidalWaveSerreGreenNaghdi(param; P)
		init = Init(mesh,η,v)
		label = "Green-Naghdi cnoidal wave"
		info = "Cnoidal travelling wave for the Serre-Green-Naghdi model.\n\
		├─velocity c = $(para.c)\n├───period P = $(2(para.λ))\n\
		├─maximum h₂ = $(para.h₂) (from bottom)\n└─minimum h₁ = $(para.h₁) (from bottom)."

		new( init.η,init.v,label,info  )
	end
end


module CDLagrangianFESpacesTests

using Test
using Gridap.Geometry
using Gridap.ReferenceFEs
using Gridap.FESpaces
using Gridap.CellData
using Gridap.TensorValues
using Gridap.Fields

domain =(0,1,0,1)
partition = (3,3)
model = CartesianDiscreteModel(domain,partition)

# order = 1
orders = (2,1)
order = 2*max(orders...)

Ω = Triangulation(model)
dΩ = Measure(Ω,order)

T = VectorValue{2,Float64}

V = FESpace(model,ReferenceFE(lagrangian,T,orders),conformity=CDConformity((DISC,CONT)))
test_single_field_fe_space(V)
u(x) = x
U = TrialFESpace(V,u)
uh = interpolate(u,U)
e = u - uh
el2 = sqrt(sum(∫(e⋅e)*dΩ))
@test el2 < 1.0e-10

reffe = LagrangianRefFE(T,QUAD,2)
V = FESpace(model,ReferenceFE(lagrangian,T,2),conformity=CDConformity((CONT,DISC)))
U = TrialFESpace(V,u)
uh = interpolate(u,U)
e = u - uh
el2 = sqrt(sum(∫(e⋅e)*dΩ))
@test el2 < 1.0e-10

V = FESpace(model,ReferenceFE(lagrangian,T,(2,0)),conformity=CDConformity((CONT,DISC)))
u(x) = VectorValue(x[1],0.0)
U = TrialFESpace(V,u)
uh = interpolate(u,U)
e = u - uh
el2 = sqrt(sum(∫(e⋅e)*dΩ))
@test el2 < 1.0e-10

V = FESpace(model,ReferenceFE(lagrangian,T,(2,0)),conformity=CDConformity((DISC,DISC)))
u(x) = VectorValue(x[1],0.0)
U = TrialFESpace(V,u)
uh = interpolate(u,U)
e = u - uh
el2 = sqrt(sum(∫(e⋅e)*dΩ))
@test el2 < 1.0e-10

# # Skeleton triangulation
# face_own_dofs = get_face_own_dofs(reffe)
# strian = SkeletonTriangulation(model,reffe,face_own_dofs)
# ns = get_normal_vector(strian)
# writevtk(strian,"strian",cellfields=["normal"=>ns])
#
# # Random function for visualization purposes
# model = CartesianDiscreteModel((0,1,0,1),(10,5))
# V = FESpace(model=model,reffe=reffe,conformity=true)
# trian = Triangulation(model)
# vh = FEFunction(V,rand(num_free_dofs(V)))
# writevtk(trian,"trian",nsubcells=20,cellfields=["vh"=>vh])

#using Gridap.Visualization
#
#writevtk(trian,"trian",nsubcells=10,cellfields=["uh"=>uh])

end # module


# Load package
using Pkg;
using Distributions, Statistics, LinearAlgebra
using Plots

# Define population, N()
n_pop = 2_500; # Population size
n_iter = 5000; # Iteration of collision

# Distribution of income
μ_income = 50_000; # Mean income
σ_income = 12_000; # Standard deviation of income
𝐃 = Normal(μ_income, σ_income)

# Decider for selecting two agents to collide
𝐔 = Uniform(1, n_pop)

# vector of initial income of individual
val_inc_ini = deepcopy(rand(𝐃, n_pop));
# Equal splits
val_inc_trc = zeros(n_pop, n_iter); # Trace the changes in distribuiton
val_inc_trc[:,1] = val_inc_ini; # the very first distributional look
# Uniform random split
val_inc_trc_rand = zeros(n_pop, n_iter); # Trace the changes in distribuiton
val_inc_trc_rand[:,1] = val_inc_ini; # the very first distributional look

# Agent collide-split economy: two agents meet at a time, their wealth is equally distributed

for iter ∈ 2:n_iter
    # initialize the starting wealth distributio of the period
    val_inc_trc[:, iter] = val_inc_trc[:, (iter - 1)];
    # For each iteration, we select two agents who will collide
    agt_1, agt_2 = sample(1:n_pop, 2; replace = false)
    # collect their financial values
    val_inc_agt_1, val_inc_agt_2 = val_inc_trc[agt_1, iter], val_inc_trc[agt_2, iter];
    # divide up their wealth equally, and save
    val_inc_distributed = mean([val_inc_agt_1, val_inc_agt_2]);
    val_inc_trc[agt_1, iter] = val_inc_distributed;
    val_inc_trc[agt_2, iter] = val_inc_distributed;

    # Iteration 2 - uniform wealth redistribution, ranom split =========================
    # initialize the starting wealth distributio of the period
    val_inc_trc_rand[:, iter] = val_inc_trc_rand[:, (iter - 1)];
    # For each iteration, we select two agents who will collide
    agt_1, agt_2 = sample(1:n_pop, 2; replace = false)
    # collect their financial values
    total_wealth = val_inc_trc_rand[agt_1, iter] + val_inc_trc_rand[agt_2, iter];
    # How to split the wealth ..........................................................
    ρ = rand(1)[1]; #proportion to be split`
    # Redistribute wealth
    #   because of randomization, no need for random in order,
    val_inc_agt_1, val_inc_agt_2 = total_wealth * ρ, total_wealth * (1 - ρ)
    val_inc_trc_rand[agt_1, iter] = val_inc_agt_1;
    val_inc_trc_rand[agt_2, iter] = val_inc_agt_2;
end

println("Mean - true: $(μ_income); std - true: $(σ_income)")
println("Mean: $(mean(val_inc_trc[:,end])); std: $(std(val_inc_trc[:,end]))")
@gif for i ∈ 1:10:n_iter
    histogram(val_inc_trc[:,i], title = "Iteration: $(i)", xlims = (0,1e5))
end every 5

println("Mean - true: $(μ_income); std - true: $(σ_income)")
println("Mean: $(mean(val_inc_trc_rand[:,end])); std: $(std(val_inc_trc_rand[:,end]))")
@gif for i ∈ 1:10:n_iter
    histogram(val_inc_trc_rand[:,i], title = "Iteration: $(i)", xlims = (0,2e5))
end every 5


function myfunc(x)
    x1, x2 = x[1], x[2]
    return x1^4 - 2*x2*x1^2 + x2^2 + x1^2 - 2*x1 + 5
end

lb = [-3.0, -1.0]
ub = [2.0, 6.0];

particles = Particles(100, lb, ub)
pso(particles, myfunc, minstep=1e-3)
# ou posso fazer a função criar um e depor retornar
particles2 = pso(myfunc, lb, ub, minstep=1e-3);

mínimo em [1,1] com valor 4


using SafeTestsets

@safetestset "Linear Solvers                                                                  " begin include("linear_solvers_tests.jl") end
@safetestset "Jacobians                                                                       " begin include("jacobian_tests.jl") end
@safetestset "Nonlinear Solvers                                                               " begin include("nonlinear_solvers_tests.jl") end


function __oddify(n_eval::Int)
    if n_eval % 2 == 0
        n_eval += 1
    end
    return n_eval
end

function __calculate_gamma(n_eval::Int)
    gamma_k = zeros(n_eval-1)
    gamma_k[1] = 1/2
    for kk = 2:(n_eval-1)
        val = kk + 1
        for ii = 2:(kk-1)
            nom = ii
            den = ii + kk
            val *= (nom / den)
        end
        val *= 1/2
        gamma_k[kk] = val
    end
    return gamma_k
end

function __select_noise_sigma(sigma_k, first_idx)
    noise_sigma = 0.0
    if first_idx != 0
        noise_sigma = sigma_k[first_idx]
    end
    return noise_sigma
end

function __selection_criterion(T_ij, sigma_k, eta = 4.0, window_size = 3)
    n_eval = length(sigma_k) + 1
    first_idx_sigma = 0
    for ii = 1:(n_eval-1-window_size+1)

        min_s = min( sigma_k[ii:(ii+window_size-1)]... )
        max_s = max( sigma_k[ii:(ii+window_size-1)]... )

        if max_s <= eta * min_s
            first_idx_sigma = ii
            break
        end
    end

    first_idx_sign = 0
    break_flag = false
    for ii = 2:(n_eval-2)

        sign_1 = sign( T_ij[1, ii+1] )
        for jj = 2:(n_eval - ii)
            sign_2 = sign( T_ij[jj, ii+1] )
            if sign_1 != sign_2
                first_idx_sign = ii
                break_flag = true
                break
            end
        end
        if break_flag
            break
        end

    end
    first_idx = min(first_idx_sign, first_idx_sigma)
    return first_idx
end

function __calculate_sigma(T_ij, gamma_k, n_eval)
    sigma_k = zeros(n_eval-1)
    for kk = 1:(n_eval-1)
        vi = view( T_ij, 1:(n_eval-kk), kk+1 )
        sum_T_ij = sum( vi.*vi )
        sigma_k[kk] = sqrt( gamma_k[kk] / (n_eval - kk) * sum_T_ij )
    end
    return sigma_k
end

# -------------------------- #
# dependent on the dimension #
# -------------------------- #

function __create_spacing(n_eval, x0::T, h::T) where {T <: Number}
    x_i = zeros(n_eval)

    x_i[ div(n_eval, 2)+1 ] = x0

    start_idx = div(n_eval, 2)
    for ii = start_idx:-1:1
        x_i[ii] = x0 - (start_idx + 1 - ii) * h
    end
    start_idx = div(n_eval, 2)+2
    for ii = start_idx:1:n_eval
        x_i[ii] = x0 + (ii - start_idx + 1) * h
    end

    return x_i
end

function __create_spacing(n_eval, x0::Vector{T}, h::Vector{T}) where {T <: Number}

    n_dim = length(x0)
    x_i = zeros(n_dim, n_eval)

    x_i[ :, div(n_eval, 2)+1 ] = x0

    start_idx = div(n_eval, 2)
    for ii = start_idx:-1:1
        x_i[:, ii] = x0 .- (start_idx + 1 - ii) .* h
    end
    start_idx = div(n_eval, 2)+2
    for ii = start_idx:1:n_eval
        x_i[:, ii] = x0 .+ (ii - start_idx + 1) .* h
    end

    return x_i
end

function __allocate_table(n_eval)
    T_ij = zeros(n_eval, n_eval)
    return T_ij
end

function __eval_function(fcn, x_i::Vector{T}, T_ij) where {T}
    for ii = 1:size(T_ij, 1)
        T_ij[ii, 1] = fcn( x_i[ii] )
    end
    return T_ij
end

function __eval_function(fcn, x_i::Matrix{T}, T_ij) where {T}
    n_eval = size(T_ij, 2)
    for ii = 1:n_eval
        T_ij[ii, 1] = fcn( x_i[:, ii] )
    end
    return T_ij
end

function __calculate_differences(T_ij, n_eval)
    for kk = 1:(n_eval-1)
        for ii = 1:(n_eval-kk)
            T_ij[ii, kk+1] = T_ij[ii+1, kk] - T_ij[ii, kk]
        end
    end

    return T_ij
end

function __difference_table(fcn, x_i::Vector{T}, n_eval::Int) where {T}
    # fcn: R -> R

    # allocate the table
    T_ij = __allocate_table(n_eval)

    # calculate the divided differences
    T_ij = __eval_function(fcn, x_i, T_ij)

    T_ij = __calculate_differences(T_ij, n_eval)

    return T_ij
end

function __difference_table(fcn, x_i::Matrix{T}, n_eval::Int) where {T}
    # fcn: R^n -> R

    # allocate the table
    T_ij = __allocate_table(n_eval)

    # calculate the divided differences
    T_ij = __eval_function(fcn, x_i, T_ij)

    T_ij = __calculate_differences(T_ij, n_eval)

    return T_ij
end

function estimate_noise(
    fcn,
    x0;
    n_eval = 7,
    h = 1e3 * sqrt.(eps.(x0))
    )
    # fcn : R -> R

    n_eval      = __oddify(n_eval)
    x_i         = __create_spacing(n_eval, x0, h)
    gamma_k     = __calculate_gamma(n_eval)
    T_ij        = __difference_table(fcn, x_i, n_eval)
    sigma_k     = __calculate_sigma(T_ij, gamma_k, n_eval)
    first_idx   = __selection_criterion(T_ij, sigma_k)
    noise_sigma = __select_noise_sigma(sigma_k, first_idx)

    return noise_sigma
end

function estimate_noise(
    fcn,
    x0::Vector{T};
    step_direction = normalize( 0.5.-rand(length(x0)) ),
    n_eval::Int = 7,
    h = 1e3 * sqrt.(eps.(x0))
    ) where {T}
    # fcn : R^n -> R

    n_eval      = __oddify(n_eval)                                      # indepedent of dim
    x_i         = __create_spacing(n_eval, x0, step_direction .* h)     # double implementation
    gamma_k     = __calculate_gamma(n_eval)                             # indepedent of dim
    T_ij        = __difference_table(fcn, x_i, n_eval)                  # double implementation
    sigma_k     = __calculate_sigma(T_ij, gamma_k, n_eval)              # indepedent of dim
    first_idx   = __selection_criterion(T_ij, sigma_k)                  # indepedent of dim
    noise_sigma = __select_noise_sigma(sigma_k, first_idx)              # indepedent of dim

    return noise_sigma
end

function noisy_numdiff_fw(fcn, x0)
    noise_est = estimate_noise(fcn, x0)
    h = max( sqrt(noise_est), sqrt.(eps.(x0)) )
    return numdiff_fw(fcn, x0, h)
end


@testset "790.domino-and-tromino-tiling.jl" begin
    let
        res = [1, 2, 5, 11, 24, 53, 117, 258, 569, 1255]
        for i in 1:10
            @test num_tilings(i) == res[i]
        end
    end
end


"""
Package: Forecast

    sma(x, n; center = true)

Smooth a vector of data using a simple moving average.

# Arguments
- `x`: Vector of data.
- `n`: Size of the moving average.
- `center`: centers the moving averaged values in the response.

# Returns
Vector of moving average smoothed values containing `missing` values to preserve the size of the original vector.

# Examples
```julia-repl
julia> sma(1:5,3;center=true)
5-element Array{Any,1}:
  missing
 2.0
 3.0
 4.0
  missing
```
"""
function sma(x, n; center = true)

    N = length(x)
    @assert 1 <= n & n <= N

    if n == 1
        return x
    end

    res = Vector{Any}(missing,N)

    # initial and final value positions
    ivp = findfirst(!ismissing, x)
    fvp = findlast(!ismissing, x)

    # using missing values to center ma
    a = center ? div(n,2) : 0

    # initial moving average value
    ma = sum(x[ivp:n+ivp-1])/n
    res[a+ivp] = ma
    for i in 1:(N-n-ivp-(N-fvp)+1)
        resai = ma + (x[n+ivp+i-1] - x[ivp+i-1])/n
        # missing values are imputed
        res[a+ivp+i] = ismissing(resai) ? ma : resai
        ma = res[a+ivp+i]
    end

    res

end


using Printf, Distributions

newgrid(p::Float64, M::Int=15, N::Int=15) = rand(Bernoulli(p), M, N)

function walkmaze!(grid::Matrix{Int}, r::Int, c::Int, indx::Int)
    NOT_VISITED = 1 # const
    N, M = size(grid)
    dirs = [[1, 0], [-1, 0], [0, 1], [1, 0]]
    # fill cell
    grid[r, c] = indx

    # is the bottom line?
    rst = r == N

    # for each direction, if has not reached the bottom yet and can continue go to that direction
    for d in dirs
        rr, cc = (r, c) .+ d
        if !rst && checkbounds(Bool, grid, rr, cc) && grid[rr, cc] == NOT_VISITED
            rst = walkmaze!(grid, rr, cc, indx)
        end
    end
    return rst
end

function checkpath!(grid::Matrix{Int})
    NOT_VISITED = 1 # const
    N, M = size(grid)
    walkind = 1
    for m in 1:M
        if grid[1, m] == NOT_VISITED
            walkind += 1
            if walkmaze!(grid, 1, m, walkind)
                return true
            end
        end
    end
    return false
end

function printgrid(G::Matrix{Int})
    LETTERS = vcat(' ', '#', 'A':'Z')
    for r in 1:size(G, 1)
        println(r % 10, ") ", join(LETTERS[G[r, :] .+ 1], ' '))
    end
    if any(G[end, :] .> 1)
        println("!) ", join((ifelse(c > 1, LETTERS[c+1], ' ') for c in G[end, :]), ' '))
    end
end

const nrep = 1000 # const
sampleprinted = false

p = collect(0.0:0.1:1.0)
f = similar(p)
for i in linearindices(f)
    c = 0
    for _ in 1:nrep
        G = newgrid(p[i])
        perc = checkpath!(G)
        if perc
            c += 1
            if !sampleprinted
                @printf("Sample percolation, %i×%i grid, p = %.2f\n\n", size(G, 1), size(G, 2), p[i])
                printgrid(G)
                sampleprinted = true
            end
        end
    end
    f[i] = c / nrep
end

println("\nFrequencies for $nrep tries that percolate through\n")
for (pi, fi) in zip(p, f)
    @printf("p = %.1f ⇛ f = %.3f\n", pi, fi)
end


# ---
# title: 1250. Check If It Is a Good Array
# id: problem1250
# author: Tian Jun
# date: 2020-10-31
# difficulty: Hard
# categories: Math
# link: <https://leetcode.com/problems/check-if-it-is-a-good-array/description/>
# hidden: true
# ---
#
# Given an array `nums` of positive integers. Your task is to select some subset
# of `nums`, multiply each element by an integer and add all these numbers. The
# array is said to be  **good  **if you can obtain a sum of `1` from the array
# by any possible subset and multiplicand.
#
# Return `True` if the array is **good  **otherwise return `False`.
#
#
#
# **Example 1:**
#
#
#
#     Input: nums = [12,5,7,23]
#     Output: true
#     Explanation: Pick numbers 5 and 7.
#     5*3 + 7*(-2) = 1
#
#
# **Example 2:**
#
#
#
#     Input: nums = [29,6,10]
#     Output: true
#     Explanation: Pick numbers 29, 6 and 10.
#     29*1 + 6*(-3) + 10*(-1) = 1
#
#
# **Example 3:**
#
#
#
#     Input: nums = [3,6]
#     Output: false
#
#
#
#
# **Constraints:**
#
#   * `1 <= nums.length <= 10^5`
#   * `1 <= nums[i] <= 10^9`
#
#
## @lc code=start
using LeetCode

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


#=
    The problems are taken from http://www.hakank.org/julia/constraints/

    Thanks a lot to Håkan Kjellerstrand
=#

function cumulative(model, start, duration, resource, limit)
    tasks = [i for i in 1:length(start) if resource[i] > 0 && duration[i] > 0]
    num_tasks = length(tasks)
    times_min_a = round.(Int,[JuMP.lower_bound(start[i]) for i in tasks])
    times_min = minimum(times_min_a)
    times_max_a = round.(Int,[JuMP.upper_bound(start[i])+duration[i] for i in tasks])
    times_max = maximum(times_max_a)
    for t in times_min:times_max
        b  = @variable(model, [1:num_tasks], Bin)
        for i in tasks
            # is this task active during this time t?
            @constraint(model, b[i] := { start[i] <= t && t < start[i]+duration[i]})
        end
        # Check that there's no conflicts in time t
        @constraint(model,sum(b[i]*resource[i] for i in tasks) <= limit)
    end
end

#
# no_overlap(model, begins,durations)
#
# Ensure that there is no overlap between the tasks.
#
function no_overlap(model, begins,durations)
    n = length(begins)
    for i in 1:n, j in i+1:n
        b = @variable(model,[1:2], Bin)
        @constraint(model,b[1] := {begins[i] + durations[i] <= begins[j]})
        @constraint(model,b[2] := {begins[j] + durations[j] <= begins[i]})
        @constraint(model, sum(b) >= 1)
    end
end

# From: http://www.hakank.org/julia/constraints/furniture_moving.jl
function furniture_moving()

    model = Model(optimizer_with_attributes(CS.Optimizer,
                                                            "logging"=>[],

                                                            "traverse_strategy"=>:BFS,
                                                            "branch_split"=>:InHalf, # <-

                                                            # "lp_optimizer" => cbc_optimizer,
                                                            # "lp_optimizer" => glpk_optimizer,
                                                            # "lp_optimizer" => ipopt_optimizer,
                                        ))

    # Furniture moving problem
    n = 4
    # [piano, chair, bed, table]
    durations = [30,10,15,15]
    resources = [3,1,3,2] # people needed per task
    @variable(model, 0 <= start_times[1:n] <= 60, Int)
    @variable(model, 0 <= end_times[1:n]   <= 60, Int)
    @variable(model, 1 <= limit <= 3, Int)
    @variable(model, 0 <= max_time <= 60, Int)
    @constraint(model, end_times .<= max_time)

    for i in 1:n
        @constraint(model,end_times[i] == start_times[i] + durations[i])
    end
    cumulative(model, start_times, durations, resources, limit)

    # @objective(model,Min,max_time)

    optimize!(model)

    status = JuMP.termination_status(model)
    @assert status == MOI.OPTIMAL
    @assert JuMP.value(limit) ≈ 3
end

# from http://www.hakank.org/julia/constraints/organize_day.jl
function organize_day(problem,all_solutions=true)
    # cbc_optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0)
    # glpk_optimizer = optimizer_with_attributes(GLPK.Optimizer)
    # ipopt_optimizer = optimizer_with_attributes(Ipopt.Optimizer)

    model = Model(optimizer_with_attributes(CS.Optimizer,   "all_solutions"=> all_solutions,
                                                            # "all_optimal_solutions"=>all_solutions,
                                                            "logging"=>[],

                                                            "traverse_strategy"=>:BFS,
                                                            # "traverse_strategy"=>:DFS,
                                                            # "traverse_strategy"=>:DBFS,

                                                            # "branch_split"=>:Smallest,
                                                            # "branch_split"=>:Biggest,
                                                            "branch_split"=>:InHalf,

                                                            # https://wikunia.github.io/ConstraintSolver.jl/stable/options/#branch_strategy-(:Auto)
                                                            "branch_strategy" => :IMPS, # default
                                        ))
    tasks = problem[:tasks]
    durations = problem[:durations]
    precedences = problem[:precedences]
    start_time = problem[:start_time]
    end_time = problem[:end_time]

    n = length(tasks)
    @variable(model, start_time <= begins[1:n] <= end_time, Int)
    @variable(model, start_time <= ends[1:n] <= end_time, Int)

    for i in 1:n
        @constraint(model,ends[i] == begins[i] + durations[i])
    end

    no_overlap(model,begins,durations)

    # precedences
    for (a,b) in eachrow(precedences)
        @constraint(model, ends[a] <= begins[b])
    end

    @constraint(model,begins[1] >= 11)

    # Solve the problem
    optimize!(model)

    status = JuMP.termination_status(model)
    # println("status:$status")
    @assert status == MOI.OPTIMAL
    if all_solutions
        @assert MOI.get(model, MOI.ResultCount()) == 5
    end
end


using CSV
using Plots
using Pkg
using Random

concdata = CSV.read("/home/puranjay/ubc/term3/SGDBabysitter.jl/test/Concrete_Data.csv", delim=',')

X = concdata[1:8]
y = concdata[9]
n = size(X)[1]
idxshuffle = shuffle(1:n)
X = X[idxshuffle, :]
y = y[idxshuffle, :]
splitval=750
xtrain = convert(Array{Float64}, X[1:splitval, :])
ytrain = convert(Array{Float64}, y[1:splitval, :])

xvalid = convert(Array{Float64}, X[splitval+1:end, :])
yvalid = convert(Array{Float64}, y[splitval+1:end, :])

xtrain = [ones(splitval,1) xtrain]
# xvalid = [ones(n-splitval,1) xvalid]
d = size(xtrain,2)

include("NeuralNet.jl")
nHidden = [3,3,4,3]
nParams = NeuralNet_nParams(d,nHidden)
w = randn(nParams,1)

include("sgdfunc.jl")
maxIter = 10000

aTest = [0.1, 0.01, 0.001, 0.0001, 0.00001]
BTest = [1, 5, 10, 20, 100] #200 500 1000

bestvalid = Inf
bestw = []
bestparams3 = [0,0]
for B in BTest
    for a in aTest
        global bestvalid, bestw, bestparams3
        wb, validerr = VanillaSGD(NeuralNet_backprop, NeuralNet_predict, maxIter, nHidden, nParams, xtrain, ytrain, xvalid, yvalid, a, B)
        if (validerr[end]<bestvalid)
            bestvalid=validerr[end]
            bestw=wb
            bestparams3 = [a,B]
        end
    end
end

#best hyperparams found to be [0.0001, 100]
w_vconc, valid_vconc = VanillaSGD(NeuralNet_backprop, NeuralNet_predict, maxIter, nHidden,nParams, xtrain, ytrain, xvalid, yvalid, bestparams3[1], convert(Int64, bestparams3[2]))
savefig("Vanilla_Concrete.pdf")
w_conc, valid_conc = SGDBabysitter(NeuralNet_backprop, NeuralNet_predict, maxIter, nHidden, nParams, xtrain, ytrain, xvalid, yvalid)
savefig("SGDB_Concrete.pdf")
yhat_conc = NeuralNet_predict(w_conc,[ones(size(xvalid,1)) xvalid],nHidden)

yhat_vconc = NeuralNet_predict(w_vconc,[ones(size(xvalid,1)) xvalid],nHidden)


#!/usr/bin/env julia

"""
# Problem 3: Largest prime factor

The prime factors of 13195 are 5, 7, 13 and 29.

What is the largest prime factor of the number `n`?
"""
module Problem003

include("Primes.jl")

"""
    solve(; n::Integer = 600851475143)

Returns the solution for [`Problem003`](@ref) with the given parameters.

## Preconditions
- `n ≥ 2`
"""
function solve(; n::Integer = 600851475143)
    return last(Primes.prime_factorization(n))[1]
end

end


using Flux, DiffEqFlux, OrdinaryDiffEq, DiffEqParamEstim, Plots, Optim, Dates
####################################################### Observation ###############################################################
# Start conditions for the two species in the system
u0 = Float32[2.; 0.]
# Number of evaluations of the neural ODE. It relates to the numbers of layers of the neural net (depth of network).
datasize = 30
# Time span in which of evaluation will be and actual timepoints of evaluations
tspan = (0.0f0, 1.5f0)
t = range(tspan[1], tspan[2], length = datasize)
# The true ODE (with the true parameters) which the neural net should learn
function trueODEfunc(du, u, p, t)
  true_A = [-0.1 2.0; -2.0 -0.1]
  du .= ((u.^3)'true_A)'
end
# Construction of the ODEProblem and solving the ODEProblem with Tsit5 solver
prob = ODEProblem(trueODEfunc, u0, tspan)
ode_data = Array(solve(prob,Tsit5(),saveat=t))
################################################### Building a neural ODE ##########################################################
# Derivative is modeled by a neural net. Chain concatinates the functions ode function and two dense layers.
dudt = Chain(x -> x.^3,
       Dense(2,50,tanh),
       Dense(50,2))
# Parameters of the model which are to be learnt. They are: W1 (2x50), b1 (50), W2 (50x2), b2 (2)
ps = Flux.params(dudt)
# Defining anonymous function for the neural ODE with the model. in: u0, out: solution with current params.
n_ode = x->neural_ode(dudt, x, tspan, Tsit5(), saveat=t, reltol=1e-7, abstol=1e-9)
#L2 loss
n_epochs = 1
mutable struct saver
    losses::Array{Float64,1}
    times::Array{Dates.Time,1}
    count_epochs::Int128
end
function saver(n_epochs)
    losses = zeros(n_epochs)
    times = fill(Dates.Time(Dates.now()),n_epochs)
    count_epochs = 0
    return saver(losses,times,count_epochs)
end
function update_saver(saver, loss_i, time_i)
    epoch_i = saver.count_epochs
    saver.losses[epoch_i] = loss_i
    saver.times[epoch_i] = time_i
end

sa_l2 = saver(n_epochs)

L2_loss_fct() = sum(abs2,ode_data .- n_ode(u0))
# Callback function to observe L2 training.
cb = function ()
    sa_l2.count_epochs = sa_l2.count_epochs +  1

    update_saver(sa_l2, Tracker.data(L2_loss_fct()),Dates.Time(Dates.now()))

    println("\"",Tracker.data(L2_loss_fct()),"\" \"",Dates.Time(Dates.now()),"\";")
end
#training call
opt = ADAM(0.1)
data = Iterators.repeated((), n_epochs)
@time Flux.train!(L2_loss_fct, ps, data, opt, cb = cb)

# Call n_ode to get first prediction and to show startpoint for training.
pred = n_ode(u0)
scatter(t, ode_data[1,:], label="data")
scatter!(t, Flux.data(pred[1,:]), label="prediction")
scatter!(t, ode_data[2,:], label="data")
scatter!(t, Flux.data(pred[2,:]), label="prediction")

#savefig("sogood.png")

using BSON: @save
@save "model_l2_1000_epochs.bson" dudt


import Splittings: UniformMesh, advection!, BSpline

@testset "BSL advections" begin

  p = 5
  n1, n2 = 128, 128
  x1min, x1max = -10, 10
  x2min, x2max = -10, 10
  mesh1 = UniformMesh(x1min, x1max, n1; endpoint=false)
  mesh2 = UniformMesh(x2min, x2max, n2; endpoint=false)

  f  = zeros(Complex{Float64},(n1,n2))
  f .= exp.(-mesh1.points.^2) * transpose(exp.(-mesh2.points.^2))
  fᵗ = zeros(Complex{Float64},(n2,n1))

  dt =  0.5

  e = ones(Float64, n1)
  v = ones(Float64, n2)

  advection!(f,  mesh1,  v, n2, dt, BSpline(p))
  transpose!(fᵗ, f)
  advection!(fᵗ, mesh2,  e, n1, dt, BSpline(p))
  transpose!(f,  fᵗ)
  advection!(f,  mesh1, -v, n2, dt, BSpline(p))
  transpose!(fᵗ, f)
  advection!(fᵗ, mesh2, -e, n1, dt, BSpline(p))
  transpose!(f,  fᵗ)

  f0 =  exp.(-mesh1.points.^2) * transpose(exp.(-mesh2.points.^2))
  println( maximum( abs.(real(f) -f0)))

  @test real(f) ≈ f0 atol=1e-6

end


include("../src/numint_module.jl")

module ntest
using ..numint
using BenchmarkTools

# fcn(x) = x^3
# fcn(x) = x^4 * log(x + sqrt(x^2 + 1))
fcn(x) = exp(-3*x) - cos(5*pi*x)

interval = numint.Interval(0.0, 8.0)

N = 10000
# I = numint.trapezoidal(fcn, interval, N)
bt = @benchmark I = numint.trapezoidal(fcn, interval, N)
show(stdout, MIME("text/plain"), bt)

integration_options = numint.RombergOptions(abs_tol = 1e-8, rel_tol = 1e-8, N = 250)
br = @benchmark stat_romb = numint.romberg($fcn, $interval, $integration_options)
show(stdout, MIME("text/plain"), br)

bgk = @benchmark stat_gk = numint.gauss_kronrod($fcn, $interval)
show(stdout, MIME("text/plain"), bgk)

end


"""
    ref_int(a=1, b=1; tmax=10000, rtol=1E-12)

Calculates Coulomb repulsion between two Gaussian charge distributions
that have same origin.

# Arguments
- `a=1`  :   with of first Gaussian exp(-ax^2)
- `b=1`  :   with of second Gaussian exp(-bx^2)

# Keywords
- `tmax=10000`   :  maximum t-value for integration
- `rtol=1E-12`   :  relative tolerance for t-integration
"""
function ref_int(a=1, b=1; tmax=10000, rtol=1E-12)
    _f(t) = 1/sqrt( (a+b)*t^2 + a*b)
    norm = a*b/π
    return 2*√π * norm * quadgk(_f, 0, tmax; rtol=rtol)[1]
end

function test_eri_accuracy(d, ne, np; a=1, b=1)
    basis = Basis(-0.5*d, 0.5*d, ne, np)
    ρ1 = exp.(-a.*basis.^2) .* sqrt(a/π)
    ρ2 = exp.(-b.*basis.^2) .* sqrt(b/π)
    ω = get_weight(basis)
    w = diagm(ω)
    l = length(basis)

    J = zeros(l,l)
    for i in 1:l, j in 1:l
        J[i,j] = eri(basis, i, j)
    end
    cal = ρ2'*w*J*ρ1

    ref = ref_int(a, b)

    return Dict("ref"=>ref, "cal"=>cal)
end


using ClassicalOrthogonalPolynomials, Test
import ClassicalOrthogonalPolynomials: orthogonalityweight

@testset "Laguerre" begin
    @testset "Laguerre weight" begin
        @test sum(LaguerreWeight()) ≡ 1.0
        @test LaguerreWeight()[0.1] ≈ orthogonalityweight(Laguerre())[0.1] ≈ exp(-0.1)
        @test LaguerreWeight(1/2)[0.1] ≈ sqrt(0.1)exp(-0.1)
    end

    @testset "Laguerre L" begin
        x = 0.1
        @test laguerrel.(0:3, 0, x) ≈ laguerrel.(0:3, x) ≈ [1, 1-x, (2 - 4x + x^2)/2, (6 - 18x + 9x^2 - x^3)/6]
        @test laguerrel.(0:3, 1/2, x) ≈ [1, 3/2 - x, (15 - 20x + 4x^2)/8, (105 - 210x + 84x^2 - 8x^3)/48]
    end
    @testset "Derivatives" begin
        L = Laguerre()
        D = Derivative(axes(L,1))
        x = 0.1
        @test (D*L)[x,1:4] ≈ [0,-1,x-2,-x^2/2 + 3x - 3]

        L = Laguerre(1/2)
        @test (D*L)[x,1:4] ≈ [0,-1,x-20/8,-x^2/2 + 7/2*x - 35/8]

        x = axes(L,1)
        X = L \ (x .* L)
        @test 0.1*L[0.1,1:10]' ≈ L[0.1,1:11]'*X[1:11,1:10]
    end
end


module DataWrangler

using FFTW, Optim, Smoothers, Statistics

include("impute.jl")
export impute, impute!

include("outlie.jl")
export outlie, outlie!

include("normalize.jl")
export normalize, normalize!

include("boxcox.jl")
include("normality.jl")
export boxcox, iboxcox

include("d.jl")
include("p.jl")
export d, p

end

"""
Data transformations tools for analytics. The current available methods are:

    boxcox, iboxcox:         Box-Cox and inverse Box-Cox transformation
    d, p:                    Finite lagged difference and partial difference and its inverse
    impute, impute!:         Data imputation (loess inter/extra-polation, random local density)
    normalize, normalize!:   Data normalization (z-score, min-max, softmax, sigmoid)
    outlie, outlie!:         Outlier detection and removal in time series

"""
DataWrangler


using Compose, Colors

# const padding = 0.5mm

function sierpinski(n::Int)
    if n == 0
        compose(context(), polygon([(1,1), (0,1), (1/2, 0)]))
    else
        t = sierpinski(n - 1)
        compose(context(),
                (context(1/4,   0, 1/2, 1/2), t),
                (context(  0, 1/2, 1/2, 1/2), t),
                (context(1/2, 1/2, 1/2, 1/2), t))
        # compose(context(),
        #         (context(0.25w + padding, padding,        0.5w - 2*padding, 0.5h - 2*padding), t),
        #         (context(padding,         0.5h + padding, 0.5w - 2*padding, 0.5h - 2*padding), t),
        #         (context(0.5w + padding,  0.5h + padding, 0.5w - 2*padding, 0.5h - 2*padding), t))
    end
end

img = SVG("sierpinski.svg", 4inch, 4(√3/2)inch)
draw(img, compose(sierpinski(2), linewidth(0.1mm), fill(nothing), stroke(colorant"black")))

img = SVG("sierpinski.svg", 4inch, 4(√3/2)inch)
@time draw(img, compose(sierpinski(8), linewidth(0.1mm), fill(nothing), stroke(colorant"black")))

#img = SVG("sierpinski.svg", 4inch, 4(√3/2)inch)
#@profile draw(img, compose(sierpinski(10), linewidth(0.1mm), fill(nothing), stroke(colorant"black")))
#Profile.print()


function normalize(x)
    mag = norm(x)
    if mag > 0.0
        return x ./ mag
    else
        return ones(length(x)) ./ length(x)
    end
end

function ∂normalize∂x(x)
    mag = norm(x)
    n = length(x)
    if mag > 0.0
        n = length(x)
        return 1.0 * I(n) ./ mag - x * transpose(x) ./ mag^3
    else
        return 1.0 * I(n) # TODO: confirm this is good choice
    end
end

function ∂norm∂x(x)
    mag = norm(x)
    if mag > 0.0
        return x' ./ mag
    else
        return ones(1, length(x))
    end
end


# Exercise 1
using LinearAlgebra, Random
using BenchmarkTools

N = 50
A = rand(N, N)
B = rand(N, N)
@btime $A * $B

# loop by the row first
function rowloop(a, b)
    @assert size(a)[2] == size(b)[1]
    c = zeros(size(a)[1], size(b)[2])
    for j in 1:size(b)[2]
        for i in 1:size(a)[1]
            c[i, j] = a[i, :]' * b[:, j]
        end
    end
    return c
end
@btime rowloop($A, $B)

# loop by the column first
function colloop(a, b)
    @assert size(a)[2] == size(b)[1]
    c = zeros(size(a)[1], size(b)[2])
    for i in 1:size(a)[1]
        for j in 1:size(b)[2]
            c[i, j] = a[i, :]' * b[:, j]
        end
    end
    return c
end
@btime colloop($A, $B)

# loop by the dot product
function rowloopdot(a, b)
    @assert size(a)[2] == size(b)[1]
    c = zeros(size(a)[1], size(b)[2])
    for j in 1:size(b)[2]
        for i in 1:size(a)[1]
            c[i, j] = dot(a[i, :], b[:, j])
        end
    end
    return c
end
@btime rowloopdot($A, $B)

function colloopdot(a, b)
    @assert size(a)[2] == size(b)[1]
    c = zeros(size(a)[1], size(b)[2])
    for i in 1:size(a)[1]
        for j in 1:size(b)[2]
            c[i, j] = dot(a[i, :], b[:, j])
        end
    end
    return c
end
@btime colloopdot($A, $B)

# Compare colloopdot(a, b) to the built-in function
A10 = rand(10, 10)
B10 = rand(10, 10)
A1000 = rand(1000, 1000)
B1000 = rand(1000, 1000)
@btime $A10 * $B10
@btime colloopdot($A10, $B10)
@btime $A1000 * $B1000
@btime colloopdot($A1000, $B1000)


function isequispaced(t)
    d1 = t[2] - t[1]
    for i in 3:length(t)
        d = t[i] - t[i-1]
        d == d1 || return false
    end
    return true
end

"""
    findnearest(val, A)
Return the index of `A` which has value nearest to `val`.
"""
function findnearest(val::Real, A::AbstractVector{<:Real})
    i = 1
    d = abs(val - A[i])
    @inbounds for j in 1:length(A)
        dd = abs(val - A[j])
        if dd < d
            i = j
            d = dd
        end
    end
    return i
end

export nrmse, rmse
function mse(x, y)
    m = length(x)
    @assert m == length(y)
    @inbounds mse = sum(abs2(x[i] - y[i]) for i in 1:m) / m
    return mse
end

using Statistics: mean

"""
    rmse(x, y) → e
Return the root mean square error `e` of the "fit" `y` into data `x`.
"""
rmse(x, y) = sqrt(mse(x, y))

"""
    nrmse(x, y) → e
Return the normalized root mean square error of the "fit" `y` into data `x`.
This number is the relative error of `y` to `x` versus `mean(x)` to `x`, i.e.
if `e < 1` the fit `y` is better than using `mean(x)` as a fit.
"""
function nrmse(x, y)
    m = length(x)
    mean_out = mean(x)
    _mse     = mse(x, y)
    @inbounds msemean = sum(abs2(x[i] - mean_out) for i in 1:m) / m
    nrmse   = sqrt(_mse / msemean)
    return nrmse
end

"""
    σrmse(x, y) = rmse(x, y)/std(x)
Relative error of the fit `y` to data `x` with respect to `std(x)`.
"""
σrmse(x, y) = rmse(x, y)/std(x)

export nrmse, rmse, σrmse


using BenchmarkTools

versioninfo(verbose=true)

# ## HWloc
using Pkg
Pkg.add("Hwloc")

using Hwloc
Hwloc.num_physical_cores()

# ##Starting Threads

using Base.Threads
nthreads()

threadid()

a=zeros(4)
for i in 1:nthreads()
     a[threadid()] = threadid()
end
a

# ## The @Threads macro
    @threads for i in 1:nthreads()
               a[threadid()] = threadid()
           end

# ### Prefix sum

function prefix_threads!(y)
    l=length(y)
    k=ceil(Int, log2(l))
    for j=1:k
        @threads for i=2^j:2^j:min(l, 2^k)
            @inbounds y[i] = y[i-2^(j-1)] + y[i]
        end
    end
    for j=(k-1):-1:1
        @threads for i=3*2^(j-1):2^j:min(l, 2^k)
            @inbounds y[i] = y[i-2^(j-1)] + y[i]
        end
    end
    y
end

# # Thread Safety

# ## Monte Carlo
using Random
function darts_in_circle(n, rng=Random.GLOBAL_RNG)
   inside = 0
   for i in 1:n
      if rand(rng)^2 + rand(rng)^2 < 1
         inside += 1
      end
   end
 return inside
end

function pi_serial(n)
    return 4 * darts_in_circle(n) / n
end

using Base.Threads

const rnglist = [MersenneTwister() for i in 1:nthreads()];

function pi_threads(n, loops)
   inside = zeros(Int, loops)
   @threads for i in 1:loops
      rng = rnglist[threadid()]
      inside[threadid()] = darts_in_circle(n, rng)
   end
   return 4 * sum(inside) / (n*loops)
end

pi_threads(2_500_000, 4)

 @btime pi_serial(10_000_000)

 @btime pi_threads(2_500_000, 4)

 # ## Atomics

 function sum_thread_base(x)
   r = zero(eltype(x))
   @threads for i in eachindex(x)
      @inbounds r += x[i]
   end
   return r
end

a=rand(10_000_000);

@btime sum($a)

@btime sum_thread_base($a)

function sum_thread_atomic(x)
   r = Atomic{eltype(x)}(zero(eltype(x)))
   @threads for i in eachindex(x)
      @inbounds atomic_add!(r, x[i])
   end
  return r[]
end

 @btime sum_thread_atomic($a)

 function sum_thread_split(A)
   r = Atomic{eltype(A)}(zero(eltype(A)))
   len, rem = divrem(length(A), nthreads())
   #Split the array equally among the threads
   @threads for t in 1:nthreads()
      r[] = zero(eltype(A))
      @simd for i in (1:len) .+ (t-1)*len
         @inbounds r[] += A[i]
      end
      atomic_add!(r, r[])
    end
   result = r[]
   #process up the remaining data
   @simd for i in length(A)-rem+1:length(A)
      @inbounds result += A[i]
   end
   return result
end

@btime sum_thread_split($a)

@btime sum_thread_split($a)

# ## Synchronisation primitives

const f = open(tempname(), "a+")

const mt = Base.Threads.Mutex();

@threads for i in 1:50
     r = pi_serial(10_000_000)
     lock(mt)
     write(f, "From $(threadid()), pi = $r\n")
     unlock(mt)
end

close(f)

const s = Base.Semaphore(2);
@threads for i in 1:nthreads()
   Base.acquire(s)
   r = pi_serial(10_000_000)
   Core.println("Calculated pi = $r in Thread $(threadid())")
   Base.release(s)
end

# # Threaded libraries

a = rand(1000, 1000);

b = rand(1000, 1000);

@btime $a*$b;

# ## Oversubscriptions

function matmul_serial(x)
   first_num = zeros(length(x))
   for i in eachindex(x)
      @inbounds first_num[i] = (x[i]'*x[i])[1]
   end
   return first_num
end

function matmul_thread(x)
   first_num = zeros(length(x))
   @threads for i in eachindex(x)
      @inbounds first_num[i] = (x[i]'*x[i])[1]
   end
   return first_num
end

m = [rand(1000, 1000) for _ in 1:10];

@btime matmul_serial(m);

@btime matmul_thread(m);

using LinearAlgebra
BLAS.set_num_threads(1)

@btime matmul_thread(m);


# This is to facilitate conversion of MATLAB codes to Julia, if needed.

@doc """
meshgrid is similar to MATLAB's function. It takes in a range, vector, matrix or similar
and makes an array of those values.
In the case of a cartesian grid, it would like an 'X' and 'Y' vectors and make a matrix filled with
the X and Y coordinates:
julia> X = 0:3
julia> Y = 0:3
julia> XX,YY = meshgrid(X,Y)
julia> XX
4×4 Array{Int64,2}:
 0  0  0  0
 1  1  1  1
 2  2  2  2
 3  3  3  3
 julia> YY
 4×4 Array{Int64,2}:
 0  1  2  3
 0  1  2  3
 0  1  2  3
 0  1  2  3

 If a single vector/range is inputted, for example X = 0:3 , it will output interpret it as meshgrid(X,X)

"""-> function meshgrid(xs, ys)
    [xs[i] for i in 1:length(xs), j in 1:length(ys)], [ys[j] for i in 1:length(xs), j in 1:length(ys)]
end

function meshgrid(xs;dims=3)
    if dims==3
        meshgrid(xs, xs,xs)
    elseif dims==2
        meshgrid(xs, xs)
    end

end

function meshgrid(xs,ys,zs)
    return ([xs[i] for i in 1:length(xs), j in 1:length(ys), k in 1:length(zs)],
    [ys[j] for i in 1:length(xs), j in 1:length(ys), k in 1:length(zs)],
    [zs[k] for i in 1:length(xs), j in 1:length(ys), k in 1:length(zs)])

end


# coding: utf-8

# In[1]:

#heap sort is the alternative version of selection sort
#heap sort is implemented on a binary heap instead of a list
#well, u can argue binary heap is expressed in list
#binary heap is a complete binary tree
#for each node it has no more than two children
#the tree is as far left as possible
#for this binary heap, we keep minimum at root
#similar to selection sort
#we only need to get the smallest number for each round
#and our list keeps shrinking until we are left with one element for final round
#however, the traversal is kinda different from selection sort
#what we do is to compare two children with parent node
#we keep the smallest among the family of three as parent node for each node
#until we reach the root and we successfully keep the smallest at root
#next step is to remove the root and we find the minimum for the new tree
#we would do it recursively in this script
#otherwise we would have to use loops in loops

#in this version we use min heap
#if u intend to use max heap,check out geeks for geeks
# https://www.geeksforgeeks.org/heap-sort/

# In[2]:

function heap_sort(arr)

    #denote layer as the height of the tree
    layer=0
    maxlen=length(arr)

    #when we are left with one element
    #that is the base case for recursion
    if maxlen<2

        return arr

    end

    #this part is to find out how many layers we got for binary heap
    #note that layer starts from 1 in julia
    while maxlen>2^layer

        maxlen-=2^layer
        layer+=1

    end

    #as layer starts from 1
    #we have to use layer+1 to make sure each layer has been traveled
    #until we reach the base case, layer 1, the root
    for i in layer+1:-1:1

        #a special property of binary heap
        #the beginning of each layer equals to 2^(i-1)
        #each layer contains 2^(i-1) elements
        for j in 2^(i-1):2^i-1

            #a special property of binary heap as well
            #the index of left child is always even number
            #and for the right child, always odd number
            right=j*2+1
            left=j*2

            #we use try function in case the node is the leaf
            #and if there is no left child
            #there wont be right child
            #according to the far left principle of binary heap
            #we compare the parent with two children nodes
            #we only keep the smallest as parent node
            try

                if arr[left]<arr[j]

                    arr[left],arr[j]=arr[j],arr[left]

                end

                try

                    if arr[right]<arr[j]

                        arr[right],arr[j]=arr[j],arr[right]

                    end

                    if arr[right]<arr[left]

                        arr[right],arr[left]=arr[left],arr[right]

                    end

                catch e

                    if !(isa(e,BoundsError))

                        throw(e)

                    end

                end

            catch e

                if !(isa(e,BoundsError))

                    throw(e)

                end

            end

        end

    end

    #recursively, we shrink the size of the list
    #by removing its root which is the smallest element for the current tree
    arr[2:end]=heap_sort(arr[2:end])

    return arr

end

# In[3]:

#heap sort s time complexity is o(nlogn)
#however, this script is written in recursive function
#it may not achieve the same speed as iterations

# In[4]:

for _ in 1:100

    test_arr=rand(100)

    if !(heap_sort(test_arr)==sort(test_arr))
        printstyled("Erreur",color=:red)
    end

end


using ThinkJulia

# Exercise 4-1
🐢 = Turtle() # \:turtle:
T = 🐢
# @svg begin
#     forward(T, 100)
#     turn(T, -90)
#     forward(T, 100)
#     turn(T, -90)
#     forward(T, 100)
#     turn(T, -90)
#     forward(T, 100)
# end

# Exercise 4-2, 4-4
function square(t, len = 100)
    for i in 1:4
        forward(t, len)
        turn(t, -90)
    end
end

# Exercise 4-5
function polygon(t, n, len = 100)
    degree = 360/n;
    for i in 1:n
        forward(t, len)
        turn(t, -degree)
    end
end

# Exercise 4-6
function circle(t, r)
    # Approximate by polygon of n-sides
    # circumference = n * len = pi * 2r, better one with tan?
    # len = pi*2r/n
    # len
    #
    # Could be better.

    n = 100 # sides of polygon
    circumference = pi * 2r
    len = circumference / n
    degree = 360/n

    polygon(t, n, len)
end

# Exercise 4-7
function arc(t, r, angle)
    n = 100 # sides of polygon
    circumference = pi * 2r * angle/360
    len = circumference / n
    degree = angle/n

    #polygon(t, n, len)
    for i in 1:n
        forward(t, len)
        turn(t, -degree)
    end
end

@svg begin
    # Exercise 4-1
    # for i in 1:4
    #     forward(T, 100)
    #     turn(T, -90)
    # end

    # Exercise 4-3
    square(T)

    # Exercise 4-4
    square(T, 10)

    # Exercise 4-5
    polygon(T, 7, 80)

    # Exercise 4-6
    circle(T, 30)

    # Exercise 4-7
    arc(T, 120, 270)
end

# TODO
# Exercise 4-8
# Exercise 4-12

# Encapsulation

println("DONE")