module Isoband

using isoband_jll

export isobands, isolines

struct ReturnValue
    xs::Ptr{Cdouble}
    ys::Ptr{Cdouble}
    ids::Ptr{Cint}
    len::Cint
end

"""
    isobands(xs, ys, zs, low::Real, high::Real)

Create one isoband from the matrix `zs` for the boundaries `low` and `high`.
The rows of `zs` correspond to the linear spaced values in `ys` and the columns to `xs`.
Returns a NamedTuple with two Vector{Float64} fields `x` and `y`, and the
Vector{Int} field `id`. Each unique id marks one polygon, the polygons can be outer polygons or
holes and are given in no particular order. Therefore, they must probably be post-processed to
feed them to plotting packages.
"""
function isobands(xs, ys, zs, low::Real, high::Real)
    results = isobands(xs, ys, zs, Float64[low], Float64[high])
    results[1]
end

function isobands(xs::AbstractVector, ys::AbstractVector, zs::AbstractMatrix, lows::AbstractVector, highs::AbstractVector)
    isobands(Float64.(xs), Float64.(ys), Float64.(zs), Float64.(lows), Float64.(highs))
end

"""
    isobands(xs::Vector{Float64}, ys::Vector{Float64}, zs::Matrix{Float64}, low_values::Vector{Float64}, high_values::Vector{Float64})

Create a vector of isobands from the matrix `zs` for all pairs in `low_values` and `high_values`.
The rows of `zs` correspond to the linear spaced values in `ys` and the columns to `xs`.
Each entry of the return vector is a NamedTuple with two Vector{Float64} fields `x` and `y`, and the
Vector{Int} field `id`. Each unique id marks one polygon, the polygons can be outer polygons or
holes and are given in no particular order. Therefore, they must probably be post-processed to
feed them to plotting packages.
"""
function isobands(
        xs::Vector{Float64},
        ys::Vector{Float64},
        zs::Matrix{Float64},
        low_values::Vector{Float64},
        high_values::Vector{Float64})

    lenx = length(xs)
    leny = length(ys)
    nrow, ncol = size(zs)

    lenx != ncol && error("Length of x ($(length(xs))) must be equal to number of columns in z ($(size(zs, 2)))")
    leny != nrow && error("Length of y $(length(ys)) must be equal to number of rows in z ($(size(zs, 1))")

    length(low_values) != length(high_values) && error("Number of low values ($(length(low_values)))and high values ($(length(high_values))) must be equal.")

    nbands = length(low_values)

    result = ccall((:isobands_impl, libisoband),
        Ptr{ReturnValue},
        (Ptr{Cdouble},
            Cint,
            Ptr{Cdouble},
            Cint,
            Ptr{Cdouble},
            Cint,
            Cint,
            Ptr{Cdouble},
            Ptr{Cdouble},
            Cint),
        xs, length(xs),
        ys, length(ys),
        zs, size(zs, 1), size(zs, 2),
        low_values, high_values, nbands)

    returnvalues = unsafe_wrap(Vector{ReturnValue}, result, nbands, own = true)

    groups = map(returnvalues) do rv
        n = rv.len
        xsr = unsafe_wrap(Vector{Cdouble}, rv.xs, n, own = true)
        ysr = unsafe_wrap(Vector{Cdouble}, rv.ys, n, own = true)
        idr = unsafe_wrap(Vector{Cint}, rv.ids, n, own = true)
        (x = xsr, y = ysr, id = idr)
    end

    groups
end

"""
    isolines(xs, ys, zs, value::Real)

Create one isoline from the matrix `zs` for `value`.
The rows of `zs` correspond to the linear spaced values in `ys` and the columns to `xs`.
Returns a NamedTuple with two Vector{Float64} fields `x` and `y`, and the
Vector{Int} field `id`. Each unique id marks one line.
"""
function isolines(xs, ys, zs, value::Real)
    results = isolines(xs, ys, zs, Float64[value])
    results[1]
end

function isolines(xs::AbstractVector, ys::AbstractVector, zs::AbstractMatrix, values::AbstractVector)
    isolines(Float64.(xs), Float64.(ys), Float64.(zs), Float64.(values))
end

"""
    isolines(xs::Vector{Float64}, ys::Vector{Float64}, zs::Matrix{Float64}, values::Vector{Float64})

Create a vector of isolines from the matrix `zs` for all `values`.
The rows of `zs` correspond to the linear spaced values in `ys` and the columns to `xs`.
Each entry of the return vector is a NamedTuple with two Vector{Float64} fields `x` and `y`, and the
Vector{Int} field `id`. Each unique id marks one line.
"""
function isolines(
        xs::Vector{Float64},
        ys::Vector{Float64},
        zs::Matrix{Float64},
        values::Vector{Float64})

    lenx = length(xs)
    leny = length(ys)
    nrow, ncol = size(zs)

    lenx != ncol && error("Length of x ($(length(xs))) must be equal to number of columns in z ($(size(zs, 2)))")
    leny != nrow && error("Length of y $(length(ys)) must be equal to number of rows in z ($(size(zs, 1))")

    nvalues = length(values)

    result = ccall((:isolines_impl, libisoband),
        Ptr{ReturnValue},
        (Ptr{Cdouble},
            Cint,
            Ptr{Cdouble},
            Cint,
            Ptr{Cdouble},
            Cint,
            Cint,
            Ptr{Cdouble},
            Cint),
        xs, length(xs),
        ys, length(ys),
        zs, size(zs, 1), size(zs, 2),
        values, nvalues)

    returnvalues = unsafe_wrap(Vector{ReturnValue}, result, nvalues, own = true)

    groups = map(returnvalues) do rv
        n = rv.len
        xsr = unsafe_wrap(Vector{Cdouble}, rv.xs, n, own = true)
        ysr = unsafe_wrap(Vector{Cdouble}, rv.ys, n, own = true)
        idr = unsafe_wrap(Vector{Cint}, rv.ids, n, own = true)
        (x = xsr, y = ysr, id = idr)
    end

    groups
end

end


using Oceananigans.OutputReaders: FieldTimeSeries
using Statistics

import Oceananigans.Fields: interior
import Oceananigans.Grids: topology, interior_parent_indices

function variance(field)
    field_mean = mean(interior(field))
    variance = mean(f_ijk -> (f_ijk - field_mean)^2, interior(field))
    return variance
end

#=
function Base.iterate(field_time_series::FieldTimeSeries, state)
    if state >= length(field_time_series.times)
        return nothing
    else
        return field_time_series[state+1], state+1
    end
end
=#

mean_variance(field_time_series) = mean((variance(field_time_series[i]) for i = 1:length(field_time_series.times)))

#####
##### Normalization functionality for forward map output
#####

abstract type AbstractNormalization end

struct IdentityNormalization <: AbstractNormalization end

IdentityNormalization(field_time_series) = IdentityNormalization()

struct ZScore{T} <: AbstractNormalization
    μ :: T
    σ :: T
end

"""
    normalize!(field, normalization)

Normalize `field` using `normalization`.
"""
normalize!(field, ::IdentityNormalization) = nothing

function normalize!(field, normalization::ZScore)
    field .-= normalization.μ

    if normalization.σ != 0
        field ./= normalization.σ
    else
        @warn "Field data seems to be all zeros -- just saying."
    end

    return nothing
end

#=
"Returns a view of `f` that excludes halo points."
@inline interior(f::FieldTimeSeries{X, Y, Z}) where {X, Y, Z} =
    view(parent(f.data),
         interior_parent_indices(X, topology(f, 1), f.grid.Nx, f.grid.Hx),
         interior_parent_indices(Y, topology(f, 2), f.grid.Ny, f.grid.Hy),
         interior_parent_indices(Z, topology(f, 3), f.grid.Nz, f.grid.Hz),
         :)
=#

"""
    ZScore(field_time_series::FieldTimeSeries)

Return the `ZScore` normalization of a `FieldTimeSeries` after computing
its mean and its variance.
"""
function ZScore(field_time_series::FieldTimeSeries)
    μ = mean(interior(field_time_series))
    σ = sqrt(mean_variance(field_time_series))

    return ZScore(μ, σ)
end


### A Pluto.jl notebook ###
# v0.11.14

using Markdown
using InteractiveUtils

# ╔═╡ b36fb690-3447-11eb-3629-edbddae4ebd1
input = readlines("./input.txt");

# ╔═╡ 3d1fca00-3449-11eb-3e0e-5784380ebdbc
expenseReport = map(x -> parse(Int32,x),input)

# ╔═╡ 4ab2f908-3449-11eb-299d-53d5a0a3044e
len = size(expenseReport)[1]

# ╔═╡ 473a9452-3449-11eb-1dd1-25d770220737
let p1 = nothing
	for i ∈ 1:len, j ∈ i+1:len
		if expenseReport[j]+ expenseReport[i] == 2020
			p1= expenseReport[i]*expenseReport[j]
			break
		end
	end
	p1
end

# ╔═╡ 6e67d06a-344b-11eb-15c6-ed3cfcfd8a83
let p2 = nothing
	for i ∈ 1:len, j ∈ i+1:len
		if (2020-(expenseReport[j]+ expenseReport[i])) in expenseReport
			p2= expenseReport[i]*expenseReport[j]*(2020 - (expenseReport[j] + expenseReport[i] ))
			break
		end
	end
	p2
end

# ╔═╡ Cell order:
# ╠═b36fb690-3447-11eb-3629-edbddae4ebd1
# ╠═3d1fca00-3449-11eb-3e0e-5784380ebdbc
# ╠═4ab2f908-3449-11eb-299d-53d5a0a3044e
# ╠═473a9452-3449-11eb-1dd1-25d770220737
# ╠═6e67d06a-344b-11eb-15c6-ed3cfcfd8a83


using StatisticalRethinking, CmdStan, LinearAlgebra
#gr(size=(600,600));

ProjDir = rel_path("..", "scripts", "04")
cd(ProjDir)

howell1 = CSV.read(rel_path("..", "data", "Howell1.csv"), delim=';')
df = convert(DataFrame, howell1);

df2 = filter(row -> row[:age] >= 18, df);
first(df2, 5)

!isfile(joinpath(ProjDir, "m4.1s.jls")) && include(joinpath(ProjDir, "m4.1s.jl"))

chn = deserialize(joinpath(ProjDir, "m4.1s.jls"))

MCMCChains.describe(chn)

density(chn, lab="All heights", xlab="height [cm]", ylab="density")

mu_sigma = hcat(chn.value[:, 2, 1], chn.value[:,1, 1])
LinearAlgebra.diag(cov(mu_sigma))

cor(mu_sigma)

# This file was generated using Literate.jl, https://github.com/fredrikekre/Literate.jl


abstract type HamiltonianModel end

const σx = Float64[0 1; 1 0]
const σy = ComplexF64[0 -1im; 1im 0]
const σz = Float64[1 0; 0 -1]
const id2 = Float64[1 0; 0 1]

@doc raw"
    hamiltonian(model<:HamiltonianModel)

return the hamiltonian of the `model` as a two-site tensor operator.
"
function hamiltonian end

struct Ising <: HamiltonianModel end

struct diaglocal{T<:Vector} <: HamiltonianModel
    diag::T
end

"""
    diaglocal(diag::Vector)

return the 2-site Hamiltonian with single-body terms given
by the diagonal `diag`.
"""
function hamiltonian(model::diaglocal)
    diag = model.diag
    n = length(diag)
    h = ein"i -> ii"(diag)
    id = Matrix(I,n,n)
    reshape(h,n,n,1,1) .* reshape(id,1,1,n,n) .+ reshape(h,1,1,n,n) .* reshape(id,n,n,1,1)
end

@doc raw"
    TFIsing(hx::Real)

return a struct representing the transverse field ising model with magnetisation `hx`.
"
struct TFIsing{T<:Real} <: HamiltonianModel
    hx::T
end

"""
    hamiltonian(model::TFIsing)

return the transverse field ising hamiltonian for the provided `model` as a
two-site operator.
"""
function hamiltonian(model::TFIsing)
    hx = model.hx
    -2 * ein"ij,kl -> ijkl"(σz,σz) -
        hx/2 * ein"ij,kl -> ijkl"(σx, id2) -
        hx/2 * ein"ij,kl -> ijkl"(id2, σx)
end

@doc raw"
    Heisenberg(Jz::T,Jx::T,Jy::T) where {T<:Real}

return a struct representing the heisenberg model with magnetisation fields
`Jz`, `Jx` and `Jy`..
"
struct Heisenberg{T<:Real} <: HamiltonianModel
    Jz::T
    Jx::T
    Jy::T
end
Heisenberg() = Heisenberg(1.0,1.0,1.0)

"""
    hamiltonian(model::Heisenberg)

return the heisenberg hamiltonian for the `model` as a two-site operator.
"""
function hamiltonian(model::Heisenberg)
    h = model.Jz * ein"ij,kl -> ijkl"(σz, σz) -
        model.Jx * ein"ij,kl -> ijkl"(σx, σx) -
        model.Jy * ein"ij,kl -> ijkl"(σy, σy)
    h = ein"ijcd,kc,ld -> ijkl"(h,σx,σx')
    real(h ./ 2)
end


#=
BFSParentsMultiSource:
- Julia version: 1.6.2
- Author: samuel
- Date: 2021-09-10
=#

using SuiteSparseGraphBLAS
using SparseArrays
using LinearAlgebra

function insert(P, R, s, n)
    for i = 1:s
        for j = 1:n
            if(P[i, j]!=nothing && R[i, j]==nothing) #*
                R[i, j] = P[i, j]
            end
        end
    end

    return R

end

#A is the input matrix, S are the sources, n is the number of nodes, s is the number of sources, Output: Parents matrix
function ms_bfsp(A, S, n, s)

    index = GBMatrix{Int64}(s, n)
            for i = 1:n
                for j = 1:s
                    index[j, i] = i
                end
            end

    #frontier matrix -> F=S source nodes insrted in frontier
    F = GBMatrix{Int64}(n, n)
    F = S

    #parent matrix -> P[S]=S because the source is already visited
    P = GBMatrix{Int64}(n, n)
    P = S

    #result matrix -> R[S]=0 because the parents of the source are considered node 0
    R = GBMatrix{Int64}(s, n)
    LR = GBMatrix{Int64}(s, n)
    R = copy(S)
    for i = 1:n
        for j = 1:s
            if(R[j, i]!=nothing)
                R[j, i] = 0
            end
        end
    end

    print("STARTING ...\n\n")
    for i = 1:n-1
            F = mul(F, A, Semirings.MIN_FIRST, mask=P, desc=Descriptors.RC)
            P = F[:, :, mask=F, desc=Descriptors.S]
            F = index[:, :, mask=F, desc=Descriptors.S]
            R = insert(P, R, s, n)
            if(R == LR)
                break
            end
            print("\nFRONTIER UPDATED\n\n")
            print(F)
            LR = copy(R)
            print("\n\nITERATION FINISHED\n\n")

    end

    print("COMPLETED\n\n")
    print("RESULT\n\n")
    print(R)

    return R

end


atol = 1e-4
rtol = 1e-2
rtol_insol = 0.1

## Test zero insolation at night
# sunrise at equator
date = Dates.DateTime(2020, 1, 1, 6, 0, 0)
lon, lat = [FT(0.0), FT(0.0)]
sza, azi, d = instantaneous_zenith_angle(date, lon, lat, param_set)
F = insolation(sza, d, param_set)
@test F ≈ 0.0 atol=atol

S, mu = solar_flux_and_cos_sza(date, lon, lat, param_set)
@test S ≈ tot_solar_irrad(param_set) rtol=rtol_insol
@test mu ≈ 0.0 atol=atol

# polar night NH 1
date = Dates.DateTime(2020, 12, 20, 11, 0, 0)
lon, lat = [FT(0.0), FT(80.0)]
sza, azi, d = instantaneous_zenith_angle(date, lon, lat, param_set)
F = insolation(sza, d, param_set)
@test F ≈ 0.0 atol=atol

S, mu = solar_flux_and_cos_sza(date, lon, lat, param_set)
@test S ≈ tot_solar_irrad(param_set) rtol=rtol_insol
@test mu ≈ 0.0 atol=atol

# polar night NH 2
date = Dates.DateTime(2020, 12, 20, 23, 0, 0)
sza, azi, d = instantaneous_zenith_angle(date, lon, lat, param_set)
F = insolation(sza, d, param_set)
@test F ≈ 0.0 atol=atol

S, mu = solar_flux_and_cos_sza(date, lon, lat, param_set)
@test S ≈ tot_solar_irrad(param_set) rtol=rtol_insol
@test mu ≈ 0.0 atol=atol

## Test symmetry of insolation at equinox
nlats = 181
date = Dates.DateTime(2021, 3, 20, 9, 37, 0) # vernal equinox 2021
l_arr = FT.(collect(range(-90, stop = 90, length = nlats)))
F_arr = zeros(nlats)
for (i, lat) in enumerate(l_arr)
    θ, dist = daily_zenith_angle(date, lat, param_set)
    F_arr[i] = insolation(θ, dist, param_set)
end
F_NH = sort(F_arr[l_arr .>= 0])
F_SH = sort(F_arr[l_arr .<= 0])
@test F_NH ≈ F_SH rtol=rtol

## Test globally averaged insolation ≈ TSI
ndays, nlats = [365, 361]
d_arr = Array{Int}(round.(collect(range(0, stop = 365, length = ndays))))
l_arr = FT.(collect(range(-90, stop = 90, length = nlats)))
F_arr = zeros(ndays, nlats)

for (i, d) in enumerate(d_arr)
    for (j, lat) in enumerate(l_arr)
        datei = Dates.DateTime(2000,1,1) + Dates.Day(d)
        θ, dist = daily_zenith_angle(datei, lat, param_set)
        F_arr[i, j] = insolation(θ, dist, param_set)
    end
end

zonal_mean_insol = mean(F_arr, dims=1)
area_fac = abs.(cosd.(l_arr))
global_mean_insol = sum(zonal_mean_insol * area_fac) / sum(area_fac)
@test global_mean_insol ≈ tot_solar_irrad(param_set) / 4 rtol=rtol

## Test invariance of zonal-mean insolation under rotation of ϖ
ϖ0 = lon_perihelion_epoch(param_set)
CLIMAParameters.Planet.lon_perihelion_epoch(::EarthParameterSet) = ϖ0 + π

for (i, d) in enumerate(d_arr)
    for (j, lat) in enumerate(l_arr)
        datei = Dates.DateTime(2000,1,1) + Dates.Day(d)
        θ, dist = daily_zenith_angle(datei, lat, param_set)
        F_arr[i, j] = insolation(θ, dist, param_set)
    end
end

zonal_mean_insol_rotate = mean(F_arr, dims=1)
@test zonal_mean_insol_rotate ≈ zonal_mean_insol rtol=rtol

CLIMAParameters.Planet.lon_perihelion_epoch(::EarthParameterSet) = ϖ0


# Copyright 2020 Google LLC

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     https://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
function part1(filename = "day10input.txt")
    input = parse.(Int, eachline(filename))

    sorted = sort(input)
    sorted = vcat([0], sorted, [sorted[end]+3])
    diffs = (@view sorted[2:end]) .- (@view sorted[1:end-1])

    one_jumps = count(x->x==1, diffs)
    three_jumps = count(x->x==3, diffs)
    length(diffs)
    return one_jumps * three_jumps
end

function number_of_ways(voltages::AbstractArray{Int})
    if length(voltages) <= 2
        return 1
    elseif voltages[1] + 3 == voltages[2]
        return number_of_ways(@view voltages[2:end])
    else
        group_size = 1
        for i ∈ 2:length(voltages)
            if voltages[i-1] + 1 == voltages[i]
                group_size += 1
            else
                break
            end
        end
        # We're at an end and there's only one adjacent number
        if group_size == 1
            return number_of_ways(@view voltages[2:end])
        # Each is attached to 3-jumps on the ends
        elseif group_size == 2
            return number_of_ways(@view voltages[3:end])
        # One bit (populate or don't populate the middle one)
        elseif group_size == 3
            return 2 * number_of_ways(@view voltages[4:end])
        # Two bits (middle 2 can be present or absent)
        elseif group_size == 4
            return 4 * number_of_ways(@view voltages[5:end])
        # Would be 3 bits, except that at least one of the middle ones must be populated
        elseif group_size == 5
            return 7 * number_of_ways(@view voltages[6:end])
        end
    end
end

function kinds_of_groupings(sorted)
    consecutive_groupings = Set()
    group_num = 0
    prev = nothing
    for n ∈ sorted
        if n-1 == prev
            group_num+=1
        elseif group_num >= 1
            push!(consecutive_groupings, group_num)
            group_num = 1
        end
        prev = n
    end
    return consecutive_groupings
end

function part2(filename = "day10input.txt")
    sorted = parse.(Int, eachline(filename)) |> sort
    sorted = vcat([0], sorted, [sorted[end]])
    number_of_ways(sorted)
end

println("Part 1: ", part1())
println("Part 2: ", part2())


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

# %% [markdown]
# # Examples of marginal plots
#
# Extended version of https://discourse.julialang.org/t/multiple-plots-some-refinements/64800/5

# %%
using Plots
using Distributions
using KernelDensity
using LinearAlgebra

# true distribution of sample
dist_true = MvNormal([1.0, 2.0], [2.0 -1.0; -1.0 4.0])
mu_x, mu_y = mean(dist_true)
s_x, s_y = .√diag(cov(dist_true))
distx_true, disty_true = Normal(mu_x, s_x), Normal(mu_y, s_y)

# generate test sample
n = 2^10
sample = rand(dist_true, n)

# kernel density estimation
X, Y = sample[1, :], sample[2, :]
k, kx, ky = kde.(((X, Y), X, Y))
ik, ikx, iky = InterpKDE.((k, kx, ky));

# %% [markdown]
# ## Sample marginal plots

# %%
layout = @layout [
    a             _
    b{0.8w, 0.8h} c
]

xlim, ylim = extrema.((k.x, k.y))
legend = colorbar = false
a = plot(kx.x, kx.density; xlim, legend)
c = plot(ky.density, ky.x; ylim, legend, xrotation=90)
b = contour(k.x, k.y, k.density; xlim, ylim, legend)

plot(a, b, c; layout, link=:both, size=(500, 500))

# %%
layout = @layout [
    a             _
    b{0.8w, 0.8h} c
]

xlim, ylim = extrema.((k.x, k.y))
legend = colorbar = false
a = plot(kx.x, kx.density; xlim, legend)
c = plot(ky.density, ky.x; ylim, legend, xrotation=90)
b = contourf(k.x, k.y, k.density; xlim, ylim, legend)

plot(a, b, c; layout, link=:both, size=(500, 500))

# %%
layout = @layout [
    a             _
    b{0.8w, 0.8h} c
]

xlim, ylim = extrema.((k.x, k.y))
legend = colorbar = false
a = plot(kx.x, kx.density; xlim, legend)
c = plot(ky.density, ky.x; ylim, legend, xrotation=90)
b = heatmap(k.x, k.y, k.density; xlim, ylim, legend)

plot(a, b, c; layout, link=:both, size=(500, 500))

# %%
layout = @layout [
    a             _
    b{0.8w, 0.8h} c
]

xlim, ylim = extrema.((k.x, k.y))
legend = colorbar = false
a = plot(kx.x, kx.density; xlim, legend)
c = plot(ky.density, ky.x; ylim, legend, xrotation=90)
b = scatter(X, Y; xlim, ylim, legend, marker_z=pdf.(Ref(ik), X, Y), alpha=0.7, msw=0)

plot(a, b, c; layout, link=:both, size=(500, 500))

# %%
layout = @layout [
    a             _
    b{0.8w, 0.8h} c
]

xlim, ylim = extrema.((k.x, k.y))
legend = colorbar = false
a = plot(kx.x, kx.density; xlim, legend)
c = plot(ky.density, ky.x; ylim, legend, xrotation=90)
b = scatter(X, Y; xlim, ylim, legend, marker_z=pdf.(Ref(ik), X, Y), alpha=0.7, msw=0, color=:rainbow)

plot(a, b, c; layout, link=:both, size=(500, 500))

# %% [markdown]
# ## True distribution marginal plots

# %%
layout = @layout [
    a             _
    b{0.8w, 0.8h} c
]

xlim, ylim = extrema.((k.x, k.y))
legend = colorbar = false
a = plot(kx.x, pdf.(distx_true, kx.x); xlim, label="true")
c = plot(pdf.(disty_true, ky.x), ky.x; ylim, xrotation=90, label="true")
b = contour(k.x, k.y, (x, y)->pdf(dist_true, [x, y]); xlim, ylim, legend)

plot(a, b, c; layout, link=:both, size=(500, 500))

# %%
layout = @layout [
    a             _
    b{0.8w, 0.8h} c
]

xlim, ylim = extrema.((k.x, k.y))
legend = colorbar = false
a = plot(kx.x, pdf.(distx_true, kx.x); xlim, label="true")
c = plot(pdf.(disty_true, ky.x), ky.x; ylim, xrotation=90, label="true")
b = contourf(k.x, k.y, (x, y)->pdf(dist_true, [x, y]); xlim, ylim, legend)

plot(a, b, c; layout, link=:both, size=(500, 500))

# %%
layout = @layout [
    a             _
    b{0.8w, 0.8h} c
]

xlim, ylim = extrema.((k.x, k.y))
legend = colorbar = false
a = plot(kx.x, pdf.(distx_true, kx.x); xlim, label="true")
c = plot(pdf.(disty_true, ky.x), ky.x; ylim, xrotation=90, label="true")
b = heatmap(k.x, k.y, (x, y)->pdf(dist_true, [x, y]); xlim, ylim, legend)

plot(a, b, c; layout, link=:both, size=(500, 500))

# %% [markdown]
# ## Other examples

# %%
layout = @layout [
    a             _
    b{0.8w, 0.8h} c
]

xlim, ylim = extrema.((k.x, k.y))
legend = colorbar = false

a = plot(kx.x, kx.density; xlim, label="X")
plot!(kx.x, pdf.(distx_true, kx.x); label="true", ls=:dash)

c = plot(ky.density, ky.x; ylim, xrotation=90, label="Y")
plot!(pdf.(disty_true, ky.x), ky.x; label="true", ls=:dash)

b = contour(k.x, k.y, k.density; xlim, ylim, legend)

plot(a, b, c; layout, link=:both, size=(500, 500))

# %%
layout = @layout [
    a             _
    b{0.8w, 0.8h} c
]

xlim, ylim = extrema.((k.x, k.y))
legend = colorbar = false

a = plot(kx.x, kx.density; xlim, label="X")
plot!(kx.x, pdf.(distx_true, kx.x); label="true", ls=:dash)

c = plot(ky.density, ky.x; ylim, xrotation=90, label="Y")
plot!(pdf.(disty_true, ky.x), ky.x; label="true", ls=:dash)

b = contourf(k.x, k.y, k.density; xlim, ylim, legend)

plot(a, b, c; layout, link=:both, size=(500, 500))

# %%
layout = @layout [
    a             _
    b{0.8w, 0.8h} c
]

xlim, ylim = extrema.((k.x, k.y))
legend = colorbar = false

a = plot(kx.x, kx.density; xlim, label="X")
plot!(kx.x, pdf.(distx_true, kx.x); label="true", ls=:dash)

c = plot(ky.density, ky.x; ylim, xrotation=90, label="Y")
plot!(pdf.(disty_true, ky.x), ky.x; label="true", ls=:dash)

b = heatmap(k.x, k.y, k.density; xlim, ylim, legend)

plot(a, b, c; layout, link=:both, size=(500, 500))

# %%
layout = @layout [
    a             _
    b{0.8w, 0.8h} c
]

xlim, ylim = extrema.((k.x, k.y))
legend = colorbar = false

a = plot(kx.x, kx.density; xlim, label="X")
plot!(kx.x, pdf.(distx_true, kx.x); label="true", ls=:dash)

c = plot(ky.density, ky.x; ylim, xrotation=90, label="Y")
plot!(pdf.(disty_true, ky.x), ky.x; label="true", ls=:dash)

b = scatter(X, Y; xlim, ylim, colorbar, marker_z=pdf.(Ref(ik), X, Y), alpha=0.7, msw=0, label="(X, Y)")
contour!(k.x, k.y, (x, y)->pdf(dist_true, [x, y]); xlim, ylim, legend, ls=:dash, color=2)

plot(a, b, c; layout, link=:both, size=(500, 500))

# %%
layout = @layout [
    a             _
    b{0.8w, 0.8h} c
]

xlim, ylim = extrema.((k.x, k.y))
legend = colorbar = false

a = plot(kx.x, kx.density; xlim, label="X")
plot!(kx.x, pdf.(distx_true, kx.x); label="true", ls=:dash)

c = plot(ky.density, ky.x; ylim, xrotation=90, label="Y")
plot!(pdf.(disty_true, ky.x), ky.x; label="true", ls=:dash)

b = scatter(X, Y; xlim, ylim, colorbar, marker_z=pdf.(Ref(ik), X, Y), alpha=0.7, msw=0, color=:rainbow, label="(X, Y)")
contour!(k.x, k.y, (x, y)->pdf(dist_true, [x, y]); xlim, ylim, legend, ls=:dash, color=2)

plot(a, b, c; layout, link=:both, size=(500, 500))

# %%


c1 = [-1; -4]
c2 = [-2; -3]

dim_x = length(c1)
dim_u = length(c2)

b = [-2; -3]

A1 = [1 -3;
     -1 -3]
A2 = [1 -2;
     -1 -1]

M = 1000;

# Loading the necessary packages
#-------------------------------
using JuMP
using GLPK
using LinearAlgebra
using Test

function build_subproblem()
    sub_problem_model = Model(GLPK.Optimizer)
    @variable(sub_problem_model, u[1:dim_u] >= 0)
    @constraint(sub_problem_model, constr_ref_subproblem[j = 1:size(A2, 2)], dot(A2[:, j], u) >= c2[j])
    return (sub_problem_model, u)
end

# Master Problem Description
# --------------------------

master_problem_model = Model(GLPK.Optimizer)
(sub_problem_model, u) = build_subproblem()

# Variable Definition
# ----------------------------------------------------------------
@variable(master_problem_model, 0 <= x[1:dim_x] <= 1e6, Int)
@variable(master_problem_model, t <= 1e6)

# Objective Setting
# -----------------
@objective(master_problem_model, Max, t)

print(master_problem_model)

iter_num = 0

function benders_lazy_constraint_callback(cb_data)
    global iter_num
    iter_num += 1
    println("Iteration number = ", iter_num)

    x_current = [callback_value(cb_data, x[j]) for j in eachindex(x)]
    fm_current = callback_value(cb_data, t)

    c_sub = b - A1 * x_current
    @objective(sub_problem_model, Min, dot(c1, x_current) + dot(c_sub, u))
    optimize!(sub_problem_model)

    print("\nThe current subproblem model is \n", sub_problem_model)

    t_status_sub = termination_status(sub_problem_model)
    p_status_sub = primal_status(sub_problem_model)

    fs_x_current = objective_value(sub_problem_model)

    u_current = value.(u)

    γ = dot(b, u_current)

    if p_status_sub == MOI.FEASIBLE_POINT && fs_x_current  ≈  fm_current # we are done
        @info("No additional constraint from the subproblem")
        # println("Optimal solution of the original problem found")
        # println("The optimal objective value t is ", fm_current)
        # println("The optimal x is ", x_current)
        # println("The optimal v is ", dual.(constr_ref_subproblem))
    end

    if p_status_sub == MOI.FEASIBLE_POINT && fs_x_current < fm_current
        println("\nThere is a suboptimal vertex, add the corresponding constraint")
        cv = A1' * u_current - c1
        new_optimality_cons = @build_constraint(t + dot(cv, x) <= γ)
        MOI.submit(master_problem_model, MOI.LazyConstraint(cb_data), new_optimality_cons)
    end

    if t_status_sub == MOI.INFEASIBLE_OR_UNBOUNDED
        println("\nThere is an  extreme ray, adding the corresponding constraint")
        ce = A1' * u_current
        new_feasibility_cons = @build_constraint(dot(ce, x) <= γ)
        MOI.submit(master_problem_model, MOI.LazyConstraint(cb_data), new_feasibility_cons)
    end
end

MOI.set(master_problem_model, MOI.LazyConstraintCallback(), benders_lazy_constraint_callback)

optimize!(master_problem_model)

t_status = termination_status(master_problem_model)
p_status = primal_status(master_problem_model)

if p_status == MOI.INFEASIBLE_POINT
    println("The problem is infeasible :-(")
end

if t_status == MOI.INFEASIBLE_OR_UNBOUNDED
    fm_current = M
    x_current = M * ones(dim_x)
end

if p_status == MOI.FEASIBLE_POINT
    fm_current = value(t)
    x_current = Float64[]
        for i in 1:dim_x
        push!(x_current, value(x[i]))
    end
end

println("Status of the master problem is ", t_status,
        "\nwith fm_current = ", fm_current,
        "\nx_current = ", x_current)

@test value(t) ≈ -4


"""
    time_change_null(events, maxT)

Calculate the residuals for some events from a constant Poisson process.

# Arguments
* `events::Array{<:Number}` the events from the process.
* `maxT::Number` the maximum window time.

"""
function time_change_null(events::Array{<:Number}, maxT::Number)
    rate = length(events) / maxT
    Lambda = rate .* events
    _time_change(Lambda)
end

"""
    time_change_hawkes(events, bg, kappa, kern)

Calculate the residuals using the time change theorem for a Hawkes process.

# Arguments
* `events::Array{<:Number}` the events from the process.
* `bg` the background rate of the Hawkes process.
* `kappa` the kappa parameter of the Hawkes process.
* `kern` the kernel distriubtion of the Hawkes process.

"""
function time_change_hawkes(events::Array{<:Number}, bg::Number, kappa::Number, kern::Distributions.Distribution)

    Lambda = Array{Float64, 1}(undef, length(events))

    for (i, event) in enumerate(events)
        Lambda[i] = compensator(event, events, bg, kappa, kern)
    end

    _time_change(Lambda)
end

function _time_change(Lambda::Array{<:Number})

    tau = diff(Lambda)

    u = 1 .- exp.(-tau)
    b = (collect(1:length(u)) .- 0.5) / length(u)

    return (b, sort(u))
end


# jobsequencial.jl
"""
sequencial job table handling
"""

using LinearAlgebra, Statistics
using Random
using DataFrames

# create list of jobnumbers using proc table
# example
# lst = [2,3,3] ==> [1,1,2,2,2,3,3,3]
function joblist(lst)
    a = Array{Int,1}()
    for i in 1:length(lst)
        append!(a,repeat([i],lst[i]))
    end
    a
end

# make job time table base (ignoring sequence)
function jobtablebase(ptable::Array{Int,2})
    sz = size(ptable)
    lp = sum(ptable)

    tb = DVector{Int}()
    for i in 1:sz[1]
        jl = joblist(ptable[i,:])
        lst = vcat(jl,zeros(Int,lp - length(jl)))
        push!(tb,lst)
    end
    tb
end

# return set of grouping number for given list
function getgroups(lst)
    gs = Int[]
    for gp in grouptable
        g = gp[:grp]
        for x in lst
            if x in gp[:id]
                push!(gs,g)
            end
        end
    end
    Set(gs)
end

function evolutecross!(pptbl::Array{DVector{Int},1}, survival=0.8, elite=0.2, mutant=0.05)
    s0 = length(pptbl)
    sortpopulation!(pptbl)
    survive!(pptbl,survival)
    s = length(pptbl)
    en = Int(round(elite*s))

    k = (s0-s)
    while k > 0
        if rand() < elite
            i1 = rand(1:en)
            i2 = rand(1:en)
        else
            i1 = rand(1:s)
            i2 = rand(1:s)
        end

        c1,c2 = crossover(pptbl[i1],pptbl[i2])
        if rand() < mutant
            swapjobrow!(c1)
            swapjobrow!(c2)
        end
        push!(pptbl,c1)
        push!(pptbl,c2)
        k -= 2
    end
    v = penalty.(pptbl)
    [minimum(v),Int(round(median(v))),maximum(v)]
end

function seqevolution!(pptbl::Array{DVector{Int},1}, n::Int, nr=10, survival=0.8, elite=0.2, mutant=0.05; stopat = 0)
    rep = DVector{Int}()

    for i in 1:n
        # sortjob!.(pptbl)
        # sortjobcol!.(pptbl)
        dt = evolutecross!(pptbl,survival,elite,mutant)
        push!(rep,dt)

        if i % nr == 0
            println("i:$i => $dt")
        end
        if dt[1] <= stopat
            break
        end
    end
    rep
end

# give exclusive group duplication penalty
# function grdupenalty(jtbl::Array{Int,2})
#     p = 0
#     for j in 1:validlength(jtbl)
#         lst = jtbl[:,j]
#         x = length(getgroups(lst))
#         p += ( x>1 ? (x-1) : 0 )
#     end
#     p
# end

# give if duplicate occurs at not groupable item
# function listduplicate(jtbl::Array{Array{Int,1},1}, glst::Array{Int,1})

#     p = zeros(Int,length(jtbl[1]))
#     for i in 1:validlength(jtbl)
#         col = coltake(jtbl,i)
#         cc = []
#         for c in col
#             if !(c in glist)
#                 push!(cc,c)
#             end
#         end

#         cs = Set(cc)
#         p[i] = (length(cc) - length(cs))
#     end
#     [p for x in jtbl]
# end


include("./euler_ten_planar_polygons.jl")
include("./euler_two_concentric_cubes.jl")
include("./euler_two_cubes_common_vertex.jl")
include("./euler_two_random_cubes.jl")


function stoogesort!(a::Array, i::Int=1, j::Int=length(a))
    if a[j] < a[i]
        a[[i, j]] = a[[j, i]];
    end

    if (j - i) > 1
        t = round(Int, (j - i + 1) / 3)
        a = stoogesort!(a, i,     j - t)
        a = stoogesort!(a, i + t, j)
        a = stoogesort!(a, i,     j - t)
    end

    return a
end

x = randn(10)
@show x stoogesort!(x)


#=
∈
Low Rank Updates of A \in R^nxn

Sherman-Morrison formula

If x, y ∈ R^n & 1+y^T A^-1 x is nonzero then

TODO: check for  (A+x*y') != 0 , A != 0
=#

using LinearAlgebra
# as an expression
lhs = :((A + x * y')^-1)

rhs = :((A^-1) - [(A^-1 * (x * y') * A^-1) / (1 + y' * A^-1 * x)])
#Or Generic function

A = rand(10, 10)
x = rand(10, 10)
y = rand(10, 10)
I = ones(10, 10)
Lhs(A = A, x = x, y = y) = ((A + x * y')^-1)

Rhs(A = A, x = x, y = y) = (A^-1) - [(A^-1 * (x * y') * A^-1) / (I + y' * A^-1 * x')]

Rhs(A, x, y) ≈ Lhs(A, x, y)

y' * A^1 * x' ≈ y' * A^1 * x # beware while using transpose ' is false # false positive

numerator = (A^-1 * (x * y') * A^-1)
denominator = I + y' * A^1 * x'

numeratorSize = size(numerator)
denominatorSize = size(denominator)
Div = numerator / denominator # ok

Mul = x * y'
(A + Mul)^-1

(A + Mul)^-1 ≈ A^-1 - Div # final answer  #false (negative)
denominatorSize = size(I + y' * A^1 * x') #correct
numeratorSize = size((A^-1 * (x * y') * A^-1)) # correct

#--- Sherman-Morrison-Woodbury formula
#= Sherman-Morrison-Woodbury formula

if U,V ∈ R^nxp & Ip + V' A-1*U is non singular
=#
#=  warning U is already Used in another context in another file in different 'context'  <--- TODO a need for a context (logic ) =#

u = rand(10, 10)
v = rand(10, 10)
A + u * v
#=10×10 Matrix{Float64}:
 2.71503  3.87771  2.80252  …  3.10273  3.43842
 2.69587  2.54099  1.62384     1.73684  2.25296
 ⋮                          ⋱
 3.05006  3.93606  2.79434     2.61207  4.02894
 3.50059  3.93851  2.27809     2.68894  2.88773
 =#
A + u * v'
#=
10×10 Matrix{Float64}:
 2.12618  3.19865  2.75244  3.72066  …  2.21105  3.81644  2.81843  3.19734
 1.96465  1.76703  1.64296  3.0106      1.73512  2.2817   1.92534  2.07472
 ⋮                                   ⋱
 2.69908  3.21591  3.02653  4.24413     2.68673  4.60944  2.40217  3.60195
 3.35323  3.15063  2.7179   3.24876     1.50348  3.66244  2.49389  2.75832
 =#

left = v' * A^-1
#=10×10 Matrix{Float64}:
1.4968     7.37556  0.833061  …  0.584169  -1.06658   -2.45121
0.0867906  8.30224  0.652222     0.364652  -0.477872  -4.7667
⋮                             ⋱
1.62219    4.33597  0.576026     0.916961  -0.639694  -1.27006
2.14516    2.79338  0.335954     0.421141  -1.84897   -0.723347
=#

tmpmiddle = u * (I + (v' * A' * u))^-1
#=
10×10 Matrix{Float64}:
 -1.86419  -0.695083  -1.7452     …   2.54644   2.64085    1.49266
 -3.1713   -1.84768    1.1834         2.22572   5.69861    1.40546
  ⋮                               ⋱
  2.29853   6.16219    0.0294727     -5.62846   0.167164  -2.55069
  4.09594  -0.9933    -1.79248        1.63777  -5.71097   -1.05803
 =#
middle = -A^-1 * tmpmiddle
#=
  49.4646  57.4864    -9.72253  …  -53.8581   -68.3594  -32.7693
  27.8203  24.1974    -1.55644     -26.5274   -34.1993  -16.7565
   ⋮                            ⋱
 -13.4486   4.15526   10.7227       -8.40686   18.9022    2.26729
  40.2082  18.4852   -19.0904      -13.7932   -64.1186  -18.4974
  =#
right = A^-1

RHS = right * middle * left
#=
10×10 Matrix{Float64}:
 2.12618    8.08584   0.655802  …  0.719427  -4.17327   -2.74713
 1.52245    2.78087  -0.116915     0.85684   -2.58951   -1.93996
 ⋮                              ⋱
 0.756402  -1.35546   0.561576     0.32922    0.521806   1.98675
-0.663023   6.10921   1.78613      0.112573  -1.47111   -3.54095
  6.74992  2097.73  213.576   -772.089     13.7489
-513.715  -1168.69
  ⋮                                     ⋱            1762.06   -3871.44
211.53     2470.34  202.148   -776.942     69.1312   -513.715  -1168.69
-802.528  -1352.28
 56.3864   4963.32  524.213  -1765.25      58.9692  --802.528  -1352.28     1384.89   -2718.6
 =#
Lehs = (A + u * v')^-1
#=
 -1.64577     1.22028   …  -0.751388  -0.825928
 -0.457401    1.00314      -0.378621  -1.72199
  ⋮                     ⋱
  0.357082   -0.770006      1.3016     1.68018
 -0.0114964  -1.66291      -1.31537    1.18225
 =#

#---recalled lecture 1 | convex Optimization I (stanford)

#=
47:19
actually i know ever  know how to solve A
everyone here has done A/ B -probably only a few know actually what it did ( what it means)

47:45 https://youtu.be/McLq1hEq3UY?t=2865
" Believe it or not, they won't even know what I'm asking !"
     - Professor Stephen Boyd, of the Stanford Electrical Engineering Department
    Lecture 1 | Convex Optimization 1 (Standard)

  =#

Lehs - RHS
""" the Best we can get
10×10 Matrix{Float64}:
 22.2142     25.6118    -89.3328   -7.21748   -17.99     15.6037   12.0876   77.5545     3.49734  -43.1083
  0.472091  -35.6289   -125.656    17.9696     82.6787  -81.5007   53.3611  152.23       2.13005  -57.7565
  ⋮                                                       ⋮
  2.53801    -9.63932   -31.15      3.34303    18.2504  -20.8493   13.5364   38.1611     4.72605  -15.887
 51.7025    133.216     -80.0547  -47.635    -183.818   208.653   -45.3165   -2.99068  -11.0007   -49.3098
"""

function _span(arr = A, ; diagon = LinearAlgebra.diag(A))
  span = 0
  for i in enumerate(size(diagons))
    span += diagons[i]
  end
  return span
end

diagons = LinearAlgebra.diag(A) = LinearAlgebra.Diagonal(A)
span(A, diagons)

LinearAlgebra.span

#--- identity
A = rand(10, 10)
B = rand(10, 10)
I = ones(10, 10)
C = rand(10, 10)
_0 = zeros(10, 10)
_lhs = (I + A .* B)^-1
#=
 2.51162     1.06328     3.50709   -3.73767   -1.19645   -2.50288  -0.293292   0.961015   2.15519   -2.10134
 -6.03368    -1.3807     -8.14618    9.10683    4.05341    6.37086  -1.45176   -0.658709  -5.30112    2.65344
  ⋮                                                        ⋮
  4.9177      1.4219      6.38853   -7.89654   -1.68593   -5.32977   0.260283   0.159931   4.43551   -2.16398
 -0.0382185   0.0314968   0.316589  -0.994095  -0.307413   1.4129    0.126316  -0.345001   0.232051  -0.182067
=#
#_0 ≈ _lhs

(I + B * A)^-1 * B

#=
10×10 Matrix{Float64}:
  1.92712   -1.52044   …   2.88647    0.899755
  4.59499   -5.75992       6.18216    0.0528266
  ⋮                    ⋱
 -5.99075    6.25609      -9.001     -1.07983
 -0.834267   0.204393     -0.139767  -0.228184

=#

_rhs = I - A * (I + B * A)^-1 * B

_rhs ≈ _lhs  #false(bnot close (enough))

#=Key equations

A*B*A ≈ (B*A)
=#
A * B * A ≈ A * (B * A)  # true #finally
#=fundanetbal theory of matrix theory =#

(A * B) * C ≈ A * (B * C) # true

#=computing a product
if x, y,z ∈ R^n how should we compute
  x*y'*z
takeaway: put brackets starting from the Right, there's a massive difference in computational cost
1. (x*y') * z costs O(n^2) operations
2. x(y'*z) costs O(n) operations

#TODO: Search for references

#--- matrix chain(rule) multiplication
#=where to put the parentheses in multiplication
given k matricies

A1 * A2*...An [Warning: not squares, possibly different dimensions]
me: in Julia  matrix product * is a left operation
secondly, unsure the type of these matricies
solution solve by dynamic programming
in O(k^3) operations

paper: O(K Log K  Hu, Shing (1984)
why importanT ?
O(k log(k)) algorithm by Hu & shing (1984)

where should i put parentheses to geth
=#


# Randomized SVD

function rsvd(X,    # (m,n) array
              k::Int,           # target rank
              p::Int=1,         # oversampling parameter
              q::Int=5)         # power iterations parameter

    Q, B = rqb(X, k, p, q)
    U, S, V = svd(B)
    U = Q * U

    return U[:, 1:k], S[1:k], V[:, 1:k]
end


function NNPredict(θ,LayerStruct)
 k=size(LayerStruct,1)-1           #Number of additional layers ( It does not include the first layer)

    ΘNparams=ΘNparams=Array{Int64,1}(undef,k)

    for i=1:k
       ΘNparams[i]=(LayerStruct[i]+1)*LayerStruct[i+1]
    end
    p= function (X)
        m=size(X,1)
                    Θ=Array{Array{Float64,2},1}(undef, k)   #Initialize the shape of the Θ Array of Arrays
                    a=Array{Array{Float64,2},1}(undef, k+1)
                    z=Array{Array{Float64,2},1}(undef, k)
                    a[1]=[ones(m) X]
                    r=zeros(k)
                    c=0
                    for i=1:k
                        Θ[i]=reshape(θ[c+1:c+ΘNparams[i]],(LayerStruct[i+1],LayerStruct[i]+1))
                        c=ΘNparams[i]
                        z[i]=a[i]*Θ[i]'
                        a[i+1]=Sigmoid(z[i])
                        a[i+1]=[ones(m) a[i+1]]
                    end

        s=zeros(m)
        for i=1:m
            s[i]=argmax(a[end][i,2:end])
        end
        return s
    end
        return p
end


using LinearAlgebra
using StaticArrays
using ParametricSurfaces
using Random
using Nystrom
Random.seed!(1)

# parameters
k   = 4π
λ   = 2π/k
ppw = 16
dx  = λ/ppw

# select pde
pdes = [Laplace(dim=3),
        Helmholtz(dim=3,k=k),
        Elastostatic(dim=3,μ=1,λ=2),
        Maxwell(dim=3,k=k)]
pde = pdes[2]

# select operator
ops = [SingleLayerOperator,
       DoubleLayerOperator]
op = ops[1]

# geometry
clear_entities!()
geo = ParametricSurfaces.Sphere(;radius=1)
Ω   = Domain(geo)
Γ   = boundary(Ω)
np  = ceil(2/dx)
M   = meshgen(Γ,(np,np))
msh = NystromMesh(M,Γ;order=1)
nx = length(msh.dofs);
iop = op(pde,msh)

# full product estimation
T = Nystrom.default_density_eltype(pde)
I = rand(1:nx,1000)
B = rand(T,nx) # CHECK: randn or rand?
Xpts = msh.dofs
Ypts = msh.dofs
K = (x,y) -> iop.kernel(x,y)*Nystrom.weight(y)
tfull = @elapsed exa = [sum(K(Xpts[i],Ypts[j])*B[j] for j in 1:nx) for i in I]
@info "Estimated time for full product: $(tfull*nx/1000)"

# IFGF
nmax = 100   # max points per leaf box
p = (3,5,5)  # interpolation points per dimension
compress = Nystrom.IFGFCompressor(;p,nmax,_profile=true);
A = compress(iop);
C = zeros(T,nx)
Nystrom.IFGF.@hprofile C = A*B
er = norm(C[I]-exa,2) / norm(exa,2)
@info "" typeof(pde) op (er,nx)


using LinearAlgebra
using CUDA
using Distributions
using Random

struct OptimizerCmaEsCfg
    population_size::Int
    sigma::Float64

    function OptimizerCmaEsCfg(configuration::OrderedDict)
        new(configuration["population_size"], configuration["sigma"])
    end
end

mutable struct OptimizerCmaEs
    lambda_::Any
    dim::Any
    chiN::Any
    mu::Any
    weights::Any
    mueff::Any
    cc::Any
    cs::Any
    ps::Any
    pc::Any
    centroid::Any
    update_count::Any
    ccov1::Any
    ccovmu::Any
    C::Any
    sigma::Any
    damps::Any
    diagD::Any
    B::Any
    BD::Any
    genomes::Any

    function OptimizerCmaEs(individual_size::Int, optimizer_configuration::OrderedDict; test = false, eigenvectors1 = Nothing, indx1 = Nothing)

        config = OptimizerCmaEsCfg(optimizer_configuration)

        centroid = zeros(individual_size)

        dim = individual_size
        pc = zeros(dim)
        ps = zeros(dim)
        chiN = sqrt(dim) * (1 - 1 / (4 * dim) + 1 / (21 * dim^2))

        C = Matrix(1.0I, dim, dim)

        C_GPU = CuArray(C)
        val_GPU, vec_GPU = CUDA.CUSOLVER.syevd!('V', 'U', C_GPU)
        diagD = Array(val_GPU)
        B = Array(vec_GPU)

        indx = sortperm(diagD)

        if test == true
            eigenvectors_test = copy(B)
            B = copy(eigenvectors1)
            indx_test = copy(indx)
            indx = copy(indx1)
            diagD_test = copy(diagD)
        else
            eigenvectors_test = Nothing
            indx_test = Nothing
            diagD_test = Nothing
        end

        diagD = diagD[indx] .^ 0.5
        B = B[:, indx]
        BD = B .* diagD'

        lambda_ = config.population_size
        update_count = 0

        # Compute params
        mu = Int(lambda_ / 2)
        weights = log(mu + 0.5) .- log.(collect(1:mu))
        weights /= sum(weights)
        mueff = 1 / sum(weights .^ 2)
        cc = 4 / (dim + 4)
        cs = (mueff + 2) / (dim + mueff + 3)
        ccov1 = 2 / ((dim + 1.3)^2 + mueff)
        ccovmu = 2 * (mueff - 2 + 1 / mueff) / ((dim + 2)^2 + mueff)
        ccovmu = min(1 - ccov1, ccovmu)
        damps = 1 + 2 * max(0, sqrt((mueff - 1) / (dim + 1)) - 1) + cs

        genomes = zeros(lambda_, individual_size)

        optimizer = new(lambda_, dim, chiN, mu, weights, mueff, cc, cs, ps, pc, centroid, update_count, ccov1, ccovmu, C, config.sigma, damps, diagD, B, BD, genomes)

        if test == true
            return optimizer, eigenvectors_test, indx_test, diagD_test
        else
            return optimizer
        end
    end
end

function ask(optimizer::OptimizerCmaEs; test = false, randoms1 = Nothing)

    randoms = rand(Normal(), size(optimizer.genomes))

    if test == true
        randoms_test = copy(randoms)
        randoms = copy(randoms1)
    else
        randoms_test = Nothing
    end

    optimizer.genomes = optimizer.centroid' .+ (optimizer.sigma .* (randoms * optimizer.BD'))

    if test == true
        return optimizer.genomes, randoms_test
    else
        return optimizer.genomes
    end

end

function tell(optimizer::OptimizerCmaEs, rewards_training; test = false, eigenvectors1 = Nothing, indx1 = Nothing)

    genomes_sorted = optimizer.genomes[sortperm(rewards_training, rev = true), :]

    old_centroid = copy(optimizer.centroid)
    optimizer.centroid = genomes_sorted[1:optimizer.mu, :]' * optimizer.weights

    c_diff = optimizer.centroid - old_centroid

    # Cumulation : update evolution path
    optimizer.ps =
        (1 - optimizer.cs) .* optimizer.ps +
        sqrt(optimizer.cs * (2 - optimizer.cs) * optimizer.mueff) ./ optimizer.sigma *
        optimizer.B *
        ((1 ./ optimizer.diagD) .* optimizer.B' * c_diff)

    hsig = float(
        norm(optimizer.ps) /
        sqrt(1.0 - (1 - optimizer.cs)^(2 * (optimizer.update_count + 1))) /
        optimizer.chiN < (1.4 + 2 / (optimizer.dim + 1)),
    )

    optimizer.update_count += 1

    optimizer.pc =
        (1 - optimizer.cc) * optimizer.pc +
        hsig * sqrt(optimizer.cc * (2 - optimizer.cc) * optimizer.mueff) / optimizer.sigma *
        c_diff

    # Update covariance matrix
    artmp = genomes_sorted[1:optimizer.mu, :]' .- old_centroid

    optimizer.C =
        (
            1 - optimizer.ccov1 - optimizer.ccovmu +
            (1 - hsig) * optimizer.ccov1 * optimizer.cc * (2 - optimizer.cc)
        ) * optimizer.C +
        optimizer.ccov1 * optimizer.pc * optimizer.pc' +
        optimizer.ccovmu * (optimizer.weights' .* artmp) * artmp' / optimizer.sigma^2

    optimizer.sigma *=
        exp((norm(optimizer.ps) / optimizer.chiN - 1) * optimizer.cs / optimizer.damps)

    C_GPU = CuArray(optimizer.C)
    val_GPU, vec_GPU = CUDA.CUSOLVER.syevd!('V', 'U', C_GPU)
    optimizer.diagD = Array(val_GPU)
    optimizer.B = Array(vec_GPU)

    indx = sortperm(optimizer.diagD)

    if test == true
        eigenvectors_test = copy(optimizer.B)
        optimizer.B = copy(eigenvectors1)
        indx_test = copy(indx)
        indx = copy(indx1)
        diagD_test = copy(optimizer.diagD)
    else
        eigenvectors_test = Nothing
        indx_test = Nothing
        diagD_test = Nothing
    end

    optimizer.diagD = optimizer.diagD[indx] .^ 0.5
    optimizer.B = optimizer.B[:, indx]
    optimizer.BD = optimizer.B .* optimizer.diagD'

    if test == true
        return eigenvectors_test, indx_test, diagD_test
    end

end


module FluxReconstruction

const FR = FluxReconstruction

using Base.Threads: @threads
using CUDA
using GSL
using LinearAlgebra
using NonlinearSolve
using OrdinaryDiffEq
using PyCall
using KitBase
using KitBase.FastGaussQuadrature
using KitBase.OffsetArrays
using KitBase.SpecialFunctions

export FR
export AbstractElementShape,
       Line,
       Quad,
       Tri,
       Hex,
       Wed,
       Pyr,
       Tet
export shock_detector,
       positive_limiter
export legendre_point,
       lagrange_point,
       ∂legendre,
       ∂radau,
       ∂lagrange,
       standard_lagrange,
       simplex_basis,
       ∂simplex_basis,
       vandermonde_matrix,
       ∂vandermonde_matrix,
       correction_field,
       JacobiP,
       ∂JacobiP,
       modal_filter!
export tri_quadrature, triface_quadrature
export FRPSpace1D,
       FRPSpace2D,
       UnstructFRPSpace,
       TriFRPSpace,
       global_sp,
       global_fp,
       rs_ab,
       xy_rs,
       rs_xy,
       rs_jacobi,
       neighbor_fpidx
export interp_face!
export poly_derivative!
export FREulerProblem

include("data.jl")
include("dissipation.jl")
include("Polynomial/polynomial.jl")
include("Transform/transform.jl")
include("quadrature.jl")
include("struct.jl")
include("Geometry/geometry.jl")
include("tools.jl")
include("interpolate.jl")
include("derivative.jl")
include("Equation/equation.jl")
include("integrator.jl")

end


function dtd2uftau(t, fft_u, fft_v, fft_du, fft_dv,
                      fft_du2, fft_dv2,
                      A1, A2, matr, conjmatr,
                      sigma, llambda, Ntaumm)

    sigma = 1

    u     = ifft(exp(1im*t*A1) * fft_u * matr)
    v     = ifft(exp(1im*t*A1) * fft_v * matr)
    dtu   = ifft(exp(1im*t*A1) * (1im*A1) * fft_u) * matr
    dtv   = ifft(exp(1im*t*A1) * (1im*A1) * fft_v) * matr

    du    = ifft(exp(1im*t*A1) * fft_du * matr)
    dv    = ifft(exp(1im*t*A1) * fft_dv * matr)
    dtdu  = ifft(exp(1im*t*A1) * (1im*A1) * fft_du) * matr
    dtdv  = ifft(exp(1im*t*A1) * (1im*A1) * fft_dv) * matr

    du2   = ifft(exp(1im*t*A1) * fft_du2 * matr)
    dv2   = ifft(exp(1im*t*A1) * fft_dv2 * matr)
    dtdu2 = ifft(exp(1im*t*A1) * (1im*A1) * fft_du2) * matr
    dtdv2 = ifft(exp(1im*t*A1) * (1im*A1) * fft_dv2) * matr

    z     = (u+conjugate(v))/2
    dtz   = (dtu+conjugate(dtv))/2
    dz    = (du+conjugate(dv))/2
    dtdz  = (dtdu+conjugate(dtdv))/2
    dz2   = (du2+conjugate(dv2))/2
    dtdz2 = (dtdu2+conjugate(dtdv2))/2

    fz1 = (  2*dtdz2*conjugate(z)*dz
           + 2*dz2*conjugate(dtz)*dz
           + 2*dz2*conjugate(z)*dtdz
           + 2*dtz*conjugate(dz2)*dz
           + 2*z*conjugate(dtdz2)*dz
           + 2*z*conjugate(dz2)*dtdz
           + 2*dtz*dz2*conjugate(dz)
           + 2*z*dtdz2*conjugate(dz)
           + 2*z*dz2*conjugate(dtdz) )

    champu1 = -1im*llambda*A2*exp(-1im*t*A1)*fft(conjmatr*fz1)
    champv1 = -1im*llambda*A2*exp(-1im*t*A1)*fft(conjmatr*conjugate(fz1))

    fz1 = (  2*dz2*conjugate(z)*dz
           + 2*z*conjugate(dz2)*dz
           + 2*z*dz2*conjugate(dz))

    champu2 = -1im*llambda*A2*exp(-1im*t*A1)*(-1im*A1)*fft(conjmatr*fz1)
    champv2 = -1im*llambda*A2*exp(-1im*t*A1)*(-1im*A1)*fft(conjmatr*conjugate(fz1))

    champu=champu1+champu2
    champv=champv1+champv2

    champu,champv

end


using FractionalDiffEq, Plots

function test(du, u, p, t)
    du[1] = 1-4*u[1]+u[1]^2*u[2]
    du[2] = 3*u[1]-u[1]^2*u[2]
end

alpha = [0.8, 0.7]
t0=0; T=100
y0=[1.2; 2.8]
h=0.01
param=[1 3]
prob = FODESystem(test, alpha, y0, (t0, T))
#(t, y) = solve(prob, h, PECE())
(t, y) = solve(prob, h, PIEX())
plot(y[1, :], y[2, :])
#=
plot(t, y[1, :])
plot!(t, y[2, :])
=#


using NeuralNetDiffEq
using Flux
using DifferentialEquations
using LinearAlgebra
d = 100 # number of dimensions
X0 = fill(0.0f0, d) # initial value of stochastic control process
tspan = (0.0f0, 1.0f0)
λ = 1.0f0

g(X) = log(0.5f0 + 0.5f0 * sum(X.^2))
f(X,u,σᵀ∇u,p,t) = -λ * sum(σᵀ∇u.^2)
μ_f(X,p,t) = zero(X)  # Vector d x 1 λ
σ_f(X,p,t) = Diagonal(sqrt(2.0f0) * ones(Float32, d)) # Matrix d x d
prob = TerminalPDEProblem(g, f, μ_f, σ_f, X0, tspan)
hls = 10 + d # hidden layer size
opt = Flux.ADAM(0.01)  # optimizer
# sub-neural network approximating solutions at the desired point
u0 = Flux.Chain(Dense(d, hls, relu),
                Dense(hls, hls, relu),
                Dense(hls, 1))
# sub-neural network approximating the spatial gradients at time point
σᵀ∇u = Flux.Chain(Dense(d + 1, hls, relu),
                  Dense(hls, hls, relu),
                  Dense(hls, hls, relu),
                  Dense(hls, d))
pdealg = NNPDENS(u0, σᵀ∇u, opt=opt)
@time ans = solve(prob, pdealg, verbose=true, maxiters=100, trajectories=100,
                            alg=EM(), dt=1.2, pabstol=1f-2)


using MonteCarloMeasurements

# Expected value of x log-weighted by ℓ
function expect(x,ℓ)
    w = exp(ℓ - maximum(ℓ))
    mean(x*w) / mean(w)
end

x = Particles(5000,TDist(3))
ℓ = logpdf(Normal(),x) - logpdf(TDist(3), x)

expect(x,ℓ)
expect(x^2,ℓ)
expect(x^3,ℓ)
expect(x^4,ℓ)

# Now sample z ~ Normal()

z = Particles(5000,Normal())

expect(z,ℓ)
expect(z^2,ℓ)
expect(z^3,ℓ)
expect(z^4,ℓ)

# And now a new weighted variable

y = Particles(5000,TDist(3))
ℓ += logpdf(Normal(),y) - logpdf(TDist(3), y)

expect(y,ℓ)
expect(y^2,ℓ)
expect(y^3,ℓ)
expect(y^4,ℓ)

expect(x+y,ℓ)
expect((x+y)^2,ℓ)

expect(x+z,ℓ)
expect((x+z)^2,ℓ)


module ProblemSet

using DifferentialEquations
using Memoize
export DEProblem, get_ode_problem,
        get_problem_key, problem_info, problem_plot

struct DEProblem
    fun::Function
    phi::Vector
    bounds::Vector
    data::Vector
    t::AbstractArray
end

Base.copy(p::DEProblem) = DEProblem(p.fun, p.phi, p.bounds,
                                    p.data, p.t)

function problem_info(p::DEProblem)::String
    num_states = length(p.data[1])
    num_params = length(p.phi)
    sep = "\n--- // ---\n"

    rep = sep
    res *= "Number of State Variables:\n\t $(num_states)"
    res *= "\n\t Initial Condition: \n\t $(p.data[1])"
    res *= "\n\t Number of Parameters: \n\t $(num_params) "
    res *= "\n\t Parameter Values: \n\t $(p.phi)"
    res *= "\n\t Bounds: \n\t $(p.bounds)"
    res *= sep
    return res
end

function get_problem_key(i::Int=0)::String
    keys = ["fic","frc",
            "ccgo","bep",
            "mhp","lvp",
            "bbg","fhn",
            "mapk",
            "gosc",
            "tsmp","cho","exponential"]
    if i > 0
        return keys[i]
    else
        return keys
    end
end

@memoize function get_problem(p::String)::DEProblem
    if p == "exponential"
        # ----- Problem 0 -----

        function f_exp(dz_dt, z, phi, t)
            dz_dt[1] = z[1]*phi[1]
        end

        k1 = 1.0
        phi = [k1]
        bounds = [0., 100.]
        ini_cond = [1.]

        t = range(0.0, stop=10.0, length=10)
        de_prob = ODEProblem(f_exp, ini_cond, (t[1],t[end]), phi)
        de_sol = solve(de_prob, AutoVern7(Rodas5()), saveat=t)
        ode_data = de_sol.u

        return DEProblem(f_exp, phi, bounds, ode_data, t)

    elseif p == "fic"
        # ----- Problem 1 -----

        function fic(dz_dt, z, phi, t)
            r_1 = phi[1]*z[1]
            r_2 = phi[2]*z[2]

            dz_dt[1] = -r_1
            dz_dt[2] = r_1 - r_2
        end

        k1 = 5.0035
        k2 = 1.
        phi = [k1, k2]
        ini_cond = [1., 0.]
        bounds = [[0. for i in 1:length(phi)],
                [10. for i in 1:length(phi)]]
        #=>
        floudas_samples = [
                            1. 0.606 0.368 0.223 0.135 0.082 0.050 0.030 0.018 0.011 0.007;
                            0. 0.373 0.564 0.647 0.669 0.656 0.642 0.583 0.539 0.494 0.451
                            ]
        <=#
        t = [0.0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1]
        de_prob = ODEProblem(fic, ini_cond, (t[1],t[end]), phi)
        de_sol = solve(de_prob, AutoVern7(Rodas5()), saveat=t)
        ode_data = de_sol.u

        return DEProblem(fic, phi, bounds, ode_data, t)

    elseif p == "frc"
        # ----- Problem 2 -----

        function frc(dz_dt, z, phi, t)
            r_1 = phi[1]*z[1]
            r_2 = phi[2]*z[2]
            r_3 = phi[3]*z[2]
            r_4 = phi[4]*z[3]

            dz_dt[1] = - r_1 + r_2
            dz_dt[2] = r_1 - r_2 - r_3 + r_4
            dz_dt[3] = - r_4 + r_3
        end

        k1 = 4.
        k2 = 2.
        k3 = 40.013
        k4 = 20.007
        phi = [k1, k2, k3, k4]
        ini_cond = [1., 0., 0.]
        bounds = [[0., 0., 10., 10.],
                [10., 10., 50., 50.]]
        #=>
        floudas_samples = [
                            1. 0.8241 0.6852 0.5747 0.4867 0.4166 0.3608 0.3164 0.2810 0.2529 0.2304 0.2126 0.1984 0.1870 0.1870 0.1709 0.1651 0.1606 0.1570 0.1541 0.1518;
                            0. 0.0937 0.1345 0.1654 0.1899 0.2094 0.2249 0.2373 0.2472 0.2550 0.2613 0.2662 0.2702 0.2733 0.2759 0.2779 0.2794 0.2807 0.2817 0.2825 0.2832;
                            0. 0.0821 0.1802 0.2598 0.3233 0.3738 0.4141 0.4461 0.4717 0.4920 0.5082 0.5210 0.5313 0.5395 0.5460 0.5511 0.5553 0.5585 0.5612 0.5632 0.5649
                           ]
        <=#

        t = [0.0,0.05,0.1,0.15,0.2,0.25,0.3,0.35,0.4,0.45,0.5,0.55,0.6,0.65,0.7,0.75,0.8,0.85,0.9,0.95,1.0]
        de_prob = ODEProblem(frc, ini_cond, (t[1],t[end]), phi)
        de_sol = solve(de_prob, AutoVern7(Rodas5()), saveat=t)
        ode_data = de_sol.u
        return DEProblem(frc, phi, bounds, ode_data, t)

    elseif p == "ccgo"
        # ----- Problem 3 -----

        function ccgo(dz_dt, z, phi, t)
            r_1 = phi[1]*z[1]^2
            r_2 = phi[2]*z[2]
            r_3 = phi[3]*z[1]^2

            dz_dt[1] = - r_1 - r_3
            dz_dt[2] = r_1 - r_2
        end

        k1 = 12.214
        k2 = 7.9798
        k3 = 2.2216
        phi = [k1, k2, k3]
        ini_cond = [1., 0.]
        bounds = [[0. for i in 1:length(phi)],
                [20. for i in 1:length(phi)]]
        #=>
        floudas_samples = [
                            1. 0.7307 0.5982 0.4678 0.4267 0.3436 0.3126 0.2808 0.2692 0.2210 0.2122 0.1903 0.1735 0.1615 0.1240 0.1190 0.1109 0.0890 0.0820 0.0745 0.0639;
                            0. 0.1954 0.2808 0.3175 0.3047 0.2991 0.2619 0.2391 0.2210 0.1898 0.1801 0.1503 0.1030 0.0964 0.0581 0.0471 0.0413 0.0367 0.0219 0.0124 0.0089
                            ]
        <=#
        t =[0.0,.025,.05,.075,.1,.125,.150,.175,.2,.225,.25,.3,.35,.4,.45,.5,.55,.65,.75,.85,.95]
        de_prob = ODEProblem(ccgo, ini_cond, (t[1],t[end]), phi)
        de_sol = solve(de_prob, AutoVern7(Rodas5()), saveat=t)
        ode_data = de_sol.u
        return DEProblem(ccgo, phi, bounds, ode_data, t)

    elseif p == "bep"
        # ----- Problem 4 -----

        function bep(dz_dt, z, phi, t)
            dz_dt[1] = phi[1]*(126.2 - z[1])*(91.9 - z[1])^2 - phi[2]*z[1]^2
        end

        k1 = 4.5704*10^(-6)
        k2 = 2.7845*10^(-4)
        phi = [k1, k2]
        ini_cond = [0.]
        bounds = [[0. for i in 1:length(phi)],
                [.1 for i in 1:length(phi)]]
        #=>
        floudas_samples = [
                            0. 1.4 6.3 10.4 14.2 17.6 21.4 23.0 27.0 30.4 34.4 38.8 41.6 43.5 45.3
                            ]
        <=#
        t = [0.0,1.0,2.0,3.0,4.0,5.0,6.0,7.0,9.0,11.0,14.0,19.0,24.0,29.0,39.0]
        de_prob = ODEProblem(bep, ini_cond, (t[1],t[end]), phi)
        de_sol = solve(de_prob, AutoVern7(Rodas5()), saveat=t)
        ode_data = de_sol.u
        return DEProblem(bep, phi, bounds, ode_data, t)

    elseif p == "mhp"
        # ----- Problem 5 -----

        function mhp(dz_dt, z, phi, t)
            dz_dt[1] = - (2*phi[1] - ((phi[1]*z[2])/((phi[2] + phi[5])*z[1] + z[2])) + phi[3] + phi[4])*z[1]
            dz_dt[2] = ((phi[1]*z[1])*(phi[2]*z[1] - z[2]))/((phi[2] + phi[5])*z[1] + z[2]) + phi[3]*z[1]
            dz_dt[3] = ((phi[1]*z[1])*(z[2] + phi[5]*z[1]))/((phi[2] + phi[5])*z[1] + z[2]) + phi[4]*z[1]
        end

        k1 = 5.2407
        k2 = 1.2176
        k3 = 0.
        k4 = 0.
        k5 = 0.
        phi = [k1, k2, k3, k4, k5]
        ini_cond = [1., 0., 0.]
        bounds = [[0. for i in 1:length(phi)],
                [100. for i in 1:length(phi)]]
        #=>
        floudas_samples = [
                            1. 0.461 0.426 0.383 0.305 0.195 0.170 0.139 0.112  0.112 0.090 0.082 0.066 0.053 0.043 0.041 0.029;
                            0. 0.114 0.135 0.157 0.194 0.231 0.234 0.228 0.228  0.226 0.220 0.214 0.178 0.188 0.183 0.184 0.166;
                            0. 0.018 0.035 0.045 0.047 0.084 0.095 0.111 0.134 0.168 0.148 0.157 0.206 0.206 0.214 0.213 0.230
                            ]
        <=#
        t = [0.,0.050,0.065,0.080,0.123,0.233,0.273,0.354,0.397,0.418,0.502,0.553,0.681,0.750,0.916,0.937,1.122]
        de_prob = ODEProblem(mhp, ini_cond, (t[1],t[end]), phi)
        de_sol = solve(de_prob, AutoVern7(Rodas5()), saveat=t)
        ode_data = de_sol.u
        return DEProblem(mhp, phi, bounds, ode_data, t)

    elseif p == "lvp"
        # ----- Problem 6 -----

        function lvp(dz_dt, z, phi, t)
            dz_dt[1] = phi[1]*z[1]*(1-z[2])
            dz_dt[2] = phi[2]*z[2]*(z[1]-1)
        end

        k1 = 3.2434
        k2 = 0.9209
        phi = [k1, k2]
        ini_cond = [1.2, 1.1]
        bounds = [[0. for i in 1:length(phi)],
                [10. for i in 1:length(phi)]]
        #=>
        floudas_samples = [
                            1.2 0.7990 0.8731 1.2487 1.0362 0.7483 1.0024 1.2816 0.8944 0.7852 1.1527;
                            1.1 1.0758 0.8711 0.9393 1.1468 1.0027 0.8577 1.0274 1.1369 0.9325 0.9074
                            ]
        <=#
        t = [0.,1.,2.,3.,4.,5.,6.,7.,8.,9.,10.]
        de_prob = ODEProblem(lvp, ini_cond, (t[1],t[end]), phi)
        de_sol = solve(de_prob, AutoVern7(Rodas5()), saveat=t)
        ode_data = de_sol.u
        return DEProblem(lvp, phi, bounds, ode_data, t)

    elseif p == "bbg"
        # ----- BBG -----
        """
        Biomass Batch Growth
        Robust and Efficient Parameter Estimation in Dynamic
        Models of Biological Systems
        [ROEF]
        """

        phi = [0.4, 5, 0.05, 0.5]

        bounds = [[10^(-5) for i in 1:length(phi)],
                [10^(2) for i in 1:length(phi)]]

        ini_cond = [2.0, 30.0]
        t = range(0.0, stop=12.0, length=7)

        function bbg(dz_dt, z, phi, t)
            mi, Ks, Kd, yield = phi
            Cb, Cs = z
            dz_dt[1] = mi*(Cs*Cb/(Ks+Cs))-Kd*Cb
            dz_dt[2] = -(mi/yield)*(Cs*Cb/(Ks+Cs))
        end

        de_prob = ODEProblem(bbg, ini_cond, (t[1],t[end]), phi)
        de_sol = solve(de_prob, AutoVern7(Rodas5()), saveat=t)
        ode_data = de_sol.u
        return DEProblem(bbg, phi, bounds, ode_data, t)

    elseif p == "fhn"
        # ----- FHN -----
        """
        FitzHugh-Nagumo neural spike
        Robust and Efficient Parameter Estimation in Dynamic
        Models of Biological Systems
        [ROEF]
        """

        phi = [0.2, 0.2, 3.0]

        bounds = [[10^(-5) for i in 1:length(phi)],
                [10^(5) for i in 1:length(phi)]]

        ini_cond = [-1.0, 1.0]
        t = range(0.0, stop=20.0, length=7)

        function fhn(dz_dt, z, phi, t)
            a, b, c = phi
            V, R = z
            u = 0
            dz_dt[1] = c*(V - (V^3)/3 + R) + u
            dz_dt[2] = -(1/c)*(V - a + b*R)
        end

        de_prob = ODEProblem(fhn, ini_cond, (t[1],t[end]), phi)
        de_sol = solve(de_prob, AutoVern7(Rodas5()), saveat=t)
        ode_data = de_sol.u
        return DEProblem(fhn, phi, bounds, ode_data, t)

    elseif p == "mapk"
        # ----- MPK -----
        """
        Kholodenko MAPK signalling pathway (MAPK)
        ROEF
        """
        phi = [2.5, 0.25, 0.75, 0.75, 0.5, 0.5]

        bounds = [[0.01 for i in 1:length(phi)],
                [50.0 for i in 1:length(phi)]]

        ini_cond = [90.0, 10.0, 280.0, 10.0, 10.0, 280.0, 10.0, 10.0]
        #t = range(0.0, stop=20.0, length=7)
        t = [50.0, 100.0, 150.0, 200.0, 300.0,
            400.0, 500.0, 600.0, 800.0, 1000.0]

        function mapk(dz_dt, z, phi, t)
            J0V1, J1V2, J4V5, J5V6, J8V9, J9V10 = phi
            J0Ki, J0n, J0k1, J1KK2, J2k3, J2KK3, J3k4,
            J3KK4, J4KK5, J5KK6, J6k7, J6KK7, J7k8, J7KK8,
            J8KK9, J9KK10 = convert(Array{eltype(phi)},
                [9.0, 1.0, 10.0, 8.0, 0.025, 15.0, 0.025, 15.0, 15.0,
                15.0, 0.025, 15.0, 0.025, 15.0, 15.0, 15.0])

            RJ0 = J0V1*(z[1]/(1+((z[8]/J0Ki)^J0n)*(J0k1+z[1])))
            RJ1 = J1V2*(z[2]/(J1KK2+z[2]))
            RJ2 = J2k3*(z[2]*z[3]/(J2KK3+z[3]))
            RJ3 = J3k4*(z[2]*z[4]/(J3KK4+z[4]))
            RJ4 = J4V5*(z[5]/(J4KK5+z[5]))
            RJ5 = J5V6*(z[4]/(J5KK6+z[4]))
            RJ6 = J6k7*(z[5]*z[6]/(J6KK7+z[6]))
            RJ7 = J7k8*(z[5]*z[7]/(J7KK8+z[7]))
            RJ8 = J8V9*(z[8]/(J8KK9+z[8]))
            RJ9 = J9V10*(z[7]/(J9KK10+z[7]))

            dz_dt[1] = - RJ0 + RJ1
            dz_dt[2] = RJ0 - RJ1
            dz_dt[3] = - RJ2 + RJ5
            dz_dt[4] = RJ2 - RJ3 + RJ4 - RJ5
            dz_dt[5] = RJ3 - RJ4
            dz_dt[6] = - RJ6 + RJ9
            dz_dt[7] = RJ6 - RJ7 + RJ8 - RJ9
            dz_dt[8] = RJ7 - RJ8
        end

        de_prob = ODEProblem(mapk, ini_cond, (t[1],t[end]), phi)
        de_sol = solve(de_prob, AutoVern7(Rodas5()), saveat=t)
        ode_data = de_sol.u
        return DEProblem(mapk, phi, bounds, ode_data, t)

    elseif p == "gosc"
        # ----- GOsc -----
        """
        Goodwin Oscillator
        Robust and Efficient Parameter Estimation in Dynamic
        Models of Biological Systems
        [ROEF]
        """
        phi = [1.0, 0.1, 1.0, 0.1, 1.0, 0.1, 1.0, 10.0]

        bounds = [[10^(-2) for i in 1:length(phi)],
                [10^(2) for i in 1:length(phi)]]
        bounds[1][end] = 1.0
        bounds[2][end] = 12.0

        ini_cond = [0.1, 0.2, 2.5]
        t = range(0.0, stop=240.0, length=10)

        function gosc(dz_dt, z, phi, t)
            k1, k2, k3, k4, k5, k6, Ki, n = phi

            dz_dt[1] = k1*Ki^n/(Ki^n + z[3]^n) - k2*z[1]
            dz_dt[2] = k3*z[1] - k4*z[2]
            dz_dt[3] = k5*z[2] - k6*z[3]
        end

        de_prob = ODEProblem(gosc, ini_cond, (t[1],t[end]), phi)
        de_sol = solve(de_prob, AutoVern7(Rodas5()), saveat=t)
        ode_data = de_sol.u
        new_prob = DEProblem(gosc, phi, bounds, ode_data, t)

        #=>
        # ----- TGFB -----
        """
        TGF - β signalling pathway model
        Robust and Efficient Parameter Estimation in Dynamic
        Models of Biological Systems
        [ROEF]
        """
        phi = [0.00015, 0.023, 0.01, 0.01, 0.01, 0.1, 0.000403, 0.0026, 0.0056, 0.002, 0.016, 5.7, 0.00657, 0.0017, 1.0, 0.0008, 0.0001, 0.0021, 0.001, 9000.0, 1800.0]

        bounds = [[10^(-3) for i in 1:length(phi)],
                [10^(3) for i in 1:length(phi)]]
        bounds[:,end] = [1, 12]

        ini_cond = [0.1, 0.2, 2.5]
        t = range(0.0, stop=240.0, length=10)

        function f_gosc(dz_dt, z, phi, t)
            CTGFb, CTGFbR, CTGFbTGFbR, CTGFbTGFbRP, CISmadTGFbTGFbRP, CSmad, CSmadP, CCoSmad, CSmadPSmaP, CSmadPCoSmad, CSmadN, CSmadPSmadPN, CSmadPN, CSmadPCoSmadN, CCoSmadN, CISmadmRNA1, CISmadmRNA2, CISmad = z
            k1, k2, k4, k5, k6, k7, k8, k9, k10, k11, k12, k13, k14, k15, k16, k17, k18, k19 = phi
            k3 = 0.01
            k20 = 9000.0
            k21 = 1800.0
            r1 = k1*CTGFbTGFbR
            r2 = k2*CTGFbR*CTGFb
            r3 = k3*CTGFbTGFbR*(1-e^(-((t-k20)/(k21)))^10)
            r4 = k4*CTGFbTGFbRP
            r5 = k5*CTGFbTGFbRP*CISmad
            r6 = k6*CISmadTGFbTGFbRP
            r7 = k7*C Smad*CTGFbTGFbRP
            r8 = k8*CSmad
            r9 = k9*CSmadN
            r10 = k10*2*CSmadP*CSmadP
            r11 = k11*CSmadPCoSmad
            r12 = k12*CSmadP*CCoSmad
            r13 = k11*CSmadP*CoSmad
            r14 = k8*CCoSmad
            r15 = k9*CCoSmadN
            r16 = k12*k8*CSmadPSmaP
            r17 = k8*CSmadP
            r18 = k9*CSmadPN
            r19 = k12*k8*CSmadPCoSmad
            r20 = k13*CSmadPN
            r21 = k10*2*CSmadPNCSmadPN
            r22 = k11*CSmadPSmadPN
            r23 = k10*CSmadPN*CCoSmadN
            r24 = k11*CSmadPCoSmadN
            r25 = k14*((CSmadPCoSmadN^2)\(CSmadPCoSmadN^2+k15^2))
            r26 = k16*CISmadmRNA1
            r27 = k17*CISmadmRNA2
            r28 = k18*CISmadmRNA2
            r29 = k19*CISmad

            dz_dt[1] = r1 - r2
            dz_dt[1] = r1 - r2
            dz_dt[1] = - r1 + r2 - r3 + r4 + r6
            dz_dt[1] = r3 - r4 - r5
            dz_dt[1] = r5 - r6
            dz_dt[1] = - r7 - r8 + r9
            dz_dt[1] = r7 - r10 + r11 - r12 + r13 - r17 + r18
            dz_dt[1] = - r12 + r13 - r14 + r15
            dz_dt[1] = r10 - r11 - r16
            dz_dt[1] = r12 - r13 - r19
            dz_dt[1] = r8 - r9 + r20
            dz_dt[1] = r16 + r21 - r22
            dz_dt[1] = r17 - r18 - r20 - r21 + r22 - r23 + r24
            dz_dt[1] = r19 + r23 - r24
            dz_dt[1] = r14 - r15 - r23 + r24
            dz_dt[1] = r25 - r26
            dz_dt[1] = r26 - r27
            dz_dt[1] = r28 - r29 - r5 + r6
        end

        de_prob = ODEProblem(f_fhn, ini_cond, (t[1],t[end]), phi)
        de_sol = solve(de_prob, OwrenZen3(), saveat=t)
        ode_data = de_sol.u

        new_prob = DEProblem(f_gosc, phi, bounds, ode_data, t)
        push!(problem_set, new_prob)
        <=#

    elseif p == "tsmp"
        # ----- TSP -----
        """
        Three Step Metabolic Pathway from:
        Parameter Estimation in Biochemical Pathways:
        A Comparison of Global Optimization Methods
        [BioParamEst-GO]
        [ROEF]
        """

        s = [.1, .46416, 2.1544, 10]
        p = [.05, .13572, .36840, .1]

        phi = [1.0, 1.0, 2.0, 1.0, 2.0, 1.0, 1.0, 1.0, 2.0,
                1.0, 2.0, 1.0, 1.0, 1.0, 2.0, 1.0, 2.0, 1.0,
                .1, 1.0, .1, .1, 1.0, .1, .1, 1.0, .1, 1.0,
                1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]

        bounds = [[10^(-5) for i in 1:length(phi)],
                [10^(5) for i in 1:length(phi)]]
        for i in [2,4,8,10,14,16]
            bounds[1][i] = 0.1
            bounds[2][i] = 10
        end

        ini_cond = [.6667, .5725, .4176, .4, .3641, .2946, 1.419, .9346]
        t = range(0, stop=120, length=21)

        function tsmp(dz_dt, z, phi, t)
            G1 = z[1]
            G2 = z[2]
            G3 = z[3]

            E1 = z[4]
            E2 = z[5]
            E3 = z[6]

            M1 = z[7]
            M2 = z[8]

            S = s[3]
            P = p[4]

            V1 = phi[1]
            Ki1 = phi[2]
            ni1 = phi[3]
            Ka1 = phi[4]
            na1 = phi[5]
            k1 = phi[6]
            V2 = phi[7]
            Ki2 = phi[8]
            ni2 = phi[9]
            Ka2 = phi[10]
            na2 = phi[11]
            k2 = phi[12]
            V3 = phi[13]
            Ki3 = phi[14]
            ni3 = phi[15]
            Ka3 = phi[16]
            na3 = phi[17]
            k3 = phi[18]
            V4 = phi[19]
            K4 = phi[20]
            k4 = phi[21]
            V5 = phi[22]
            K5 = phi[23]
            k5 = phi[24]
            V6 = phi[25]
            K6 = phi[26]
            k6 = phi[27]
            kcat1 = phi[28]
            Km1 = phi[29]
            Km2 = phi[30]
            kcat2 = phi[31]
            Km3 = phi[32]
            Km4 = phi[33]
            kcat3 = phi[34]
            Km5 = phi[35]
            Km6 = phi[36]

            dz_dt[1] = (V1/(1+(P/Ki1)^ni1+(Ka1/S)^na1))-k1*G1
            dz_dt[2] = (V2/(1+(P/Ki2)^ni2+(Ka2/M1)^na2))-k2*G2
            dz_dt[3] = (V3/(1+(P/Ki3)^ni3+(Ka3/M2)^na3))-k3*G3

            dz_dt[4] = (V4*G1/(K4+G1))-k4*E1
            dz_dt[5] = (V5*G2/(K5+G2))-k5*E2
            dz_dt[6] = (V6*G3/(K6+G3))-k6*E3

            dz_dt[7] = ((kcat1*E1*(1/Km1)*(S-M1)))/(1+(S/Km1)+(M1/Km2)) - ((kcat2*E2*(1/Km3)*(M1-M2)))/(1+(M1/Km3)+(M2/Km4))
            dz_dt[8] = ((kcat2*E2*(1/Km3)*(M1-M2)))/(1+(M1/Km3)+(M2/Km4)) - ((kcat3*E3*(1/Km5)*(M2-P)))/(1+(M2/Km5)+(P/Km6))
        end

        de_prob = ODEProblem(tsmp, ini_cond, (t[1],t[end]), phi)
        de_sol = solve(de_prob, AutoVern7(Rodas5()), saveat=t)
        ode_data = de_sol.u
        return DEProblem(tsmp, phi, bounds, ode_data, t)

    elseif p == "cho"
    # ----- CHO -----
    # [BioPreDyn]

        x0 = zeros(Float64, 35)
        x0[1] = 5
        x0[2] = 1
        x0[3] = 5
        x0[4] = 5
        x0[5] = 1
        x0[6] = 1
        x0[7] = 1
        x0[8] = 1
        x0[9] = 1
        x0[10] = 1
        x0[11] = 1
        x0[12] = 1
        x0[13] = 1
        x0[14] = 1
        x0[15] = 1
        x0[16] = 1
        x0[17] = 1
        x0[18] = 1
        x0[19] = 1
        x0[20] = 1
        x0[21] = 1
        x0[22] = 1
        x0[23] = 1
        x0[24] = 1
        x0[25] = 1
        x0[26] = 1
        x0[27] = 1
        x0[28] = 1
        x0[29] = 1
        x0[30] = 1
        x0[31] = 1
        x0[32] = 1
        x0[33] = 1
        x0[34] = 1
        x0[35] = 6

        p = zeros(Float64, 117)
        p[1] = 1000
        p[2] = 1000
        p[3] = 1000
        p[4] = 1000
        p[5] = 1000
        p[6] = 1000
        p[7] = 1000
        p[8] = 1000
        p[9] = 1
        p[10] = 1
        p[11] = 0.7
        p[12] = 0.7
        p[13] = 0.7
        p[14] = 0.7
        p[15] = 0.2
        p[16] = 0.2
        p[17] = 1
        p[18] = 1
        p[19] = 0.7
        p[20] = 0.7
        p[21] = 0.05
        p[22] = 1
        p[23] = 0.5
        p[24] = 0.7
        p[25] = 0.5
        p[26] = 2
        p[27] = 0.7
        p[28] = 0.7
        p[29] = 2
        p[30] = 0.2
        p[31] = 0.2
        p[32] = 1
        p[33] = 0.7
        p[34] = 0.2
        p[35] = 0.21
        p[36] = 1
        p[37] = 0.7
        p[38] = 1
        p[39] = 0.2
        p[40] = 0.2
        p[41] = 0.05
        p[42] = 0.01
        p[43] = 0.7
        p[44] = 1
        p[45] = 0.5
        p[46] = 1
        p[47] = 0.04
        p[48] = 0.01
        p[49] = 0.01
        p[50] = 0.01
        p[51] = 1
        p[52] = 1
        p[53] = 0.5
        p[54] = 1
        p[55] = 1
        p[56] = 0.5
        p[57] = 0.1
        p[58] = 0.1
        p[59] = 0.5
        p[60] = 1
        p[61] = 1
        p[62] = 1
        p[63] = 0.3
        p[64] = 1
        p[65] = 1
        p[66] = 0.7
        p[67] = 0.4
        p[68] = 0.7
        p[69] = 1
        p[70] = 0.7
        p[71] = 0.7
        p[72] = 2
        p[73] = 0.2
        p[74] = 2
        p[75] = 1
        p[76] = 0.7
        p[77] = 0.7
        p[78] = 0.2
        p[79] = 0.7
        p[80] = 0.5
        p[81] = 0.21
        p[82] = 0.2
        p[83] = 0.2
        p[84] = 0.01
        p[85] = 2
        p[86] = 2
        p[87] = 2
        p[88] = 2
        p[89] = 2
        p[90] = 2
        p[91] = 2
        p[92] = 2
        p[93] = 1
        p[94] = 1
        p[95] = 1
        p[96] = 1
        p[97] = 1
        p[98] = 0.5
        p[99] = 0.7
        p[100] = 0.7
        p[101] = 2
        p[102] = 0.2
        p[103] = 2
        p[104] = 1
        p[105] = 1
        p[106] = 0.7
        p[107] = 1
        p[108] = 0.7
        p[109] = 1
        p[110] = 0.7
        p[111] = 1
        p[112] = 0.7
        p[113] = 2
        p[114] = 2
        p[115] = 0.7
        p[116] = 0.2
        p[117] = 0.2

        t = collect(1.0:20.0)
        bounds = [[10^-5 for i in 1:length(p)],
                [10^5 for i in 1:length(p)]]

        function cho(dx_dt, x, par, t)
            p = zeros(347)
            p[118] = 1
            p[119] = 1
            p[120] = 1
            p[121] = 1
            p[122] = 1
            p[123] = 1
            p[124] = 1
            p[125] = 1
            p[126] = 1
            p[127] = 1
            p[128] = 1
            p[129] = 1
            p[130] = 1
            p[131] = 1
            p[132] = 1
            p[133] = 1
            p[134] = 1
            p[135] = 1
            p[136] = 1
            p[137] = 1
            p[138] = 1
            p[139] = 1
            p[140] = 1
            p[141] = 1
            p[142] = 1
            p[143] = 1
            p[144] = 1
            p[145] = 1
            p[146] = 1
            p[147] = 1
            p[148] = 1
            p[149] = 1
            p[150] = 0
            p[151] = 141.471
            p[152] = 0.9
            p[153] = 0.1
            p[154] = 83402
            p[155] = 127877
            p[156] = 603.414
            p[157] = 603.414
            p[158] = 5.02845
            p[159] = 21722.9
            p[160] = 603.414
            p[161] = 361200
            p[162] = 673524
            p[163] = 1.62679e+06
            p[164] = 164390
            p[165] = 36512.9
            p[166] = 166804
            p[167] = 39530
            p[168] = 1810.24
            p[169] = 127877
            p[170] = 166201
            p[171] = 1.94454e+06
            p[172] = 5.02845
            p[173] = 317755
            p[174] = 0
            p[175] = 532531
            p[176] = 38926.6
            p[177] = 40133.4
            p[178] = 689982
            p[179] = 4.45873e+06
            p[180] = 2413.66
            p[181] = 83402
            p[182] = 603.414
            p[183] = 603.414
            p[184] = 2413.66
            p[185] = 533135
            p[186] = 100000
            p[187] = 1
            p[188] = 1000
            p[189] = 1000
            p[190] = 30
            p[191] = 1000
            p[192] = 1000
            p[193] = 1000
            p[194] = 1000
            p[195] = 1000
            p[196] = 1000
            p[197] = 1000
            p[198] = 3000
            p[199] = 1000
            p[200] = 1000
            p[201] = 1000
            p[202] = 100
            p[203] = 100
            p[204] = 100
            p[205] = 100
            p[206] = 1000
            p[207] = 1000
            p[208] = 1000
            p[209] = 1000
            p[210] = 1000
            p[211] = 1000
            p[212] = 1000
            p[213] = 1000
            p[214] = 1000
            p[215] = 3000
            p[216] = 100000
            p[217] = 1000
            p[218] = 1000
            p[219] = 1000
            p[220] = 1000
            p[221] = 1000
            p[222] = 1000
            p[223] = 1000
            p[224] = 1000
            p[225] = 1000
            p[226] = 1000
            p[227] = 1000
            p[228] = 0
            p[229] = 1
            p[230] = 0
            p[231] = 0
            p[232] = 1
            p[233] = 1
            p[234] = 1
            p[235] = -0.7
            p[236] = -0.7
            p[237] = 0.7
            p[238] = 0.7
            p[239] = -0.2
            p[240] = -0.2
            p[241] = 1
            p[242] = 1
            p[243] = -0.7
            p[244] = -0.7
            p[245] = 0.05
            p[246] = 1
            p[247] = 0.5
            p[248] = -0.7
            p[249] = -0.5
            p[250] = 2
            p[251] = 0.7
            p[252] = 0.7
            p[253] = -2
            p[254] = -0.2
            p[255] = -0.2
            p[256] = 1
            p[257] = 0.7
            p[258] = -0.2
            p[259] = -0.21
            p[260] = 0
            p[261] = 1
            p[262] = 0.7
            p[263] = 1
            p[264] = -0.2
            p[265] = -0.2
            p[266] = 0.05
            p[267] = 0
            p[268] = -0.01
            p[269] = 0.7
            p[270] = 1
            p[271] = -0.5
            p[272] = -1
            p[273] = 0.04
            p[274] = -0.01
            p[275] = -0.01
            p[276] = -0.01
            p[277] = 1
            p[278] = 1
            p[279] = 0.5
            p[280] = -1
            p[281] = -1
            p[282] = -0.5
            p[283] = 0.1
            p[284] = -0.1
            p[285] = 0.5
            p[286] = 1
            p[287] = 1
            p[288] = -1
            p[289] = -0.3
            p[290] = -1
            p[291] = 1
            p[292] = 0.7
            p[293] = -0.4
            p[294] = -0.7
            p[295] = 1
            p[296] = -0.7
            p[297] = 0.7
            p[298] = 2
            p[299] = -0.2
            p[300] = -2
            p[301] = 1
            p[302] = 0.7
            p[303] = 0.7
            p[304] = 0.2
            p[305] = 0.7
            p[306] = -0.5
            p[307] = -0.21
            p[308] = -0.2
            p[309] = -0.2
            p[310] = 0
            p[311] = 0
            p[312] = -0.01
            p[313] = 0.7
            p[314] = -0.2
            p[315] = 2
            p[316] = 2
            p[317] = -2
            p[318] = -2
            p[319] = 2
            p[320] = 2
            p[321] = -2
            p[322] = -2
            p[323] = 1
            p[324] = 1
            p[325] = -1
            p[326] = -1
            p[327] = 1
            p[328] = -0.5
            p[329] = 0.7
            p[330] = 0.7
            p[331] = 2
            p[332] = -0.2
            p[333] = -2
            p[334] = 1
            p[335] = 1
            p[336] = -0.7
            p[337] = 1
            p[338] = -0.7
            p[339] = 1
            p[340] = -0.7
            p[341] = 1
            p[342] = -0.7
            p[343] = 2
            p[344] = -2
            p[345] = 0.7
            p[346] = -0.2
            p[347] = -0.2

            for i in 1:length(par)
                p[i] = par[i]
            end

            y = zeros(promote_type(eltype(x),eltype(t)),102)
            y[1] = log(x[1])
            y[2] = log(x[2])
            y[3] = log(x[3])
            y[4] = log(x[4])
            y[5] = log(x[5])
            y[6] = log(x[6])
            y[7] = log(x[7])
            y[8] = log(x[8])
            y[9] = log(x[9])
            y[10] = log(x[10])
            y[11] = log(x[11])
            y[12] = log(x[12])
            y[13] = log(x[13])
            y[14] = log(x[14])
            y[15] = log(x[15])
            y[16] = log(x[16])
            y[17] = log(x[17])
            y[18] = log(x[18])
            y[19] = log(x[19])
            y[20] = log(x[20])
            y[21] = log(x[21])
            y[22] = log(x[22])
            y[23] = log(x[23])
            y[24] = log(x[24])
            y[25] = log(x[25])
            y[26] = log(x[26])
            y[27] = log(x[27])
            y[28] = log(x[28])
            y[29] = log(x[29])
            y[30] = log(x[30])
            y[31] = log(x[31])
            y[32] = log(x[32])
            y[33] = log(x[33])
            y[34] = log(x[34])
            y[35] = log(x[35])
            y[36] = x[1]*p[186]
            y[37] = x[2]*p[187]
            y[38] = x[3]*p[188]
            y[39] = x[4]*p[189]
            y[40] = x[5]*p[190]
            y[41] = x[6]*p[191]
            y[42] = x[7]*p[192]
            y[43] = x[8]*p[193]
            y[44] = x[9]*p[194]
            y[45] = x[10]*p[195]
            y[46] = x[11]*p[196]
            y[47] = x[12]*p[197]
            y[48] = x[13]*p[198]
            y[49] = x[14]*p[199]
            y[50] = x[15]*p[200]
            y[51] = x[16]*p[201]
            y[52] = x[17]*p[202]
            y[53] = x[18]*p[203]
            y[54] = x[19]*p[204]
            y[55] = x[20]*p[205]
            y[56] = x[21]*p[206]
            y[57] = x[22]*p[207]
            y[58] = x[23]*p[208]
            y[59] = x[24]*p[209]
            y[60] = x[25]*p[210]
            y[61] = x[26]*p[211]
            y[62] = x[27]*p[212]
            y[63] = x[28]*p[213]
            y[64] = x[29]*p[214]
            y[65] = x[30]*p[215]
            y[66] = x[31]*p[216]
            y[67] = x[32]*p[217]
            y[68] = x[33]*p[218]
            y[69] = x[34]*p[219]
            if t>5 && t<10
                y[70] = p[150]
            else
                y[70] = 0.00000000000000000
            end
            y[71] = 0.00000000000000000
            y[72] = 0.00000000000000000
            y[73] = 0.00000000000000000
            y[74] = 0.00000000000000000
            y[75] = 0.00000000000000000
            y[76] = p[123]*p[159]*(1.00000000000000000+p[233]*y[8]+p[234]*y[9]+p[235]*y[6]+p[236]*y[7])
            y[77] = p[124]*p[160]*(1.00000000000000000+p[237]*y[12]+p[238]*y[13]+p[239]*y[10]+p[240]*y[11])
            y[78] = p[125]*p[161]*(1.00000000000000000+p[241]*y[15]+p[242]*y[6]+p[243]*y[8]+p[244]*y[14]+p[245]*y[16])
            y[79] = p[126]*p[162]*(1.00000000000000000+p[246]*y[16]+p[247]*y[7]+p[248]*y[15]+p[249]*y[9])
            y[80] = p[127]*p[163]*(1.00000000000000000+p[250]*y[9]+p[251]*y[19]+p[252]*y[20]+p[253]*y[7]+p[254]*y[17]+p[255]*y[18])
            y[81] = p[128]*p[164]*(1.00000000000000000+p[256]*y[22]+p[257]*y[11]+p[258]*y[21]+p[259]*y[13]+p[260]*y[13])
            y[82] = p[129]*p[165]*(1.00000000000000000+p[261]*y[15]+p[262]*y[7]+p[263]*y[21]+p[264]*y[8]+p[265]*y[9]+p[266]*y[32]+p[267]*y[9]+p[268]*y[30])
            y[83] = p[130]*p[166]*(1.00000000000000000+p[269]*y[25]+p[270]*y[26]+p[271]*y[23]+p[272]*y[24]+p[273]*y[11]+p[274]*y[11]+p[275]*y[13]+p[276]*y[22])
            y[84] = p[131]*p[167]*(1.00000000000000000+p[277]*y[28]+p[278]*y[29]+p[279]*y[23]+p[280]*y[12]+p[281]*y[27]+p[282]*y[25]+p[283]*y[27]+p[284]*y[10])
            y[85] = p[132]*p[168]*(1.00000000000000000+p[285]*y[32]+p[286]*y[22]+p[287]*y[16]+p[288]*y[15]+p[289]*y[30]+p[290]*y[27])
            y[86] = p[133]*p[169]*(1.00000000000000000+p[291]*y[21]+p[292]*y[23]+p[293]*y[2]+p[294]*y[25])
            y[87] = p[134]*p[170]*(1.00000000000000000+p[295]*y[24]+p[296]*y[22])
            y[88] = p[135]*p[171]*(1.00000000000000000+p[297]*y[20]+p[298]*y[17]+p[299]*y[18]+p[300]*y[19])
            y[89] = p[136]*(p[172]/(p[209]/p[220]*(p[210]/p[221])*(p[197]/p[222])*(p[218]/p[223])*(p[219]/p[224])*(p[214]/p[225])*(p[195]/p[226])*(p[198]/p[227])/((1.00000000000000000+p[209]/p[220])*(1.00000000000000000+p[210]/p[221])*(1.00000000000000000+p[197]/p[222])*(1.00000000000000000+p[218]/p[223])*(1.00000000000000000+p[219]/p[224])*(1.00000000000000000+p[214]/p[225])*(1.00000000000000000+p[195]/p[226])*(1.00000000000000000+p[198]/p[227]))))*(x[24]*p[209]/p[220]*(x[25]*p[210]/p[221])*(x[12]*p[197]/p[222])*(x[33]*p[218]/p[223])*(x[34]*p[219]/p[224])*(x[29]*p[214]/p[225])*(x[10]*p[195]/p[226])*(x[13]*p[198]/p[227])/((1.00000000000000000+x[24]*p[209]/p[220])*(1.00000000000000000+x[25]*p[210]/p[221])*(1.00000000000000000+x[12]*p[197]/p[222])*(1.00000000000000000+x[33]*p[218]/p[223])*(1.00000000000000000+x[34]*p[219]/p[224])*(1.00000000000000000+x[29]*p[214]/p[225])*(1.00000000000000000+x[10]*p[195]/p[226])*(1.00000000000000000+x[13]*p[198]/p[227])))
            y[90] = p[137]*p[173]*(1.00000000000000000+p[301]*y[8]+p[302]*y[7]+p[303]*y[19]+p[304]*y[31]+p[305]*y[32]+p[306]*y[16]+p[307]*y[9]+p[308]*y[17]+p[309]*y[30]+p[310]*y[31]+p[311]*y[9]+p[312]*y[15])
            y[91] = p[138]*p[174]*(1.00000000000000000+p[313]*y[18]+p[314]*y[20])
            y[92] = p[139]*p[175]*(1.00000000000000000+p[315]*y[11]+p[316]*y[30]+p[317]*y[32]+p[318]*y[13])
            y[93] = p[140]*p[176]*(1.00000000000000000+p[319]*y[8]+p[320]*y[27]+p[321]*y[28]+p[322]*y[16])
            y[94] = p[141]*p[177]*(1.00000000000000000+p[323]*y[14]+p[324]*y[12]+p[325]*y[29]+p[326]*y[6])
            y[95] = p[142]*p[178]*(1.00000000000000000+p[327]*y[13]+p[328]*y[11])
            y[96] = p[143]*p[179]*(1.00000000000000000+p[329]*y[32]+p[330]*y[31]+p[331]*y[18]+p[332]*y[30]+p[333]*y[20])
            y[97] = p[144]*p[180]*(1.00000000000000000+p[334]*y[27]+p[335]*y[31]+p[336]*y[16])
            y[98] = p[145]*p[181]*(1.00000000000000000+p[337]*y[1]+p[338]*y[26])
            y[99] = p[146]*p[182]*(1.00000000000000000+p[339]*y[3]+p[340]*y[33])
            y[100] = p[147]*p[183]*(1.00000000000000000+p[341]*y[4]+p[342]*y[34])
            y[101] = p[148]*p[184]*(1.00000000000000000+p[343]*y[6]+p[344]*y[12])
            y[102] = p[149]*p[185]*(1.00000000000000000+p[345]*y[18]+p[346]*y[31]+p[347]*y[20])

            dx_dt[1] = (y[71]-y[98]*1.00000000000000000/p[151]-x[1]*p[186]/x[35]*y[70])/p[186]
            dx_dt[2] = ((-y[72])+y[86]*1.00000000000000000/p[151]-x[2]*p[187]/x[35]*y[70])/p[187]
            dx_dt[3] = (y[73]-y[99]*1.00000000000000000/p[151]-x[3]*p[188]/x[35]*y[70])/p[188]
            dx_dt[4] = (y[74]-y[100]*1.00000000000000000/p[151]-x[4]*p[189]/x[35]*y[70])/p[189]
            dx_dt[5] = ((-y[75])+y[89]*1.00000000000000000/p[151]-x[5]*p[190]/x[35]*y[70])/p[190]
            dx_dt[6] = (y[76]-y[78]+y[94]*p[152]/p[153]-y[101]*p[152]/p[153])/p[191]
            dx_dt[7] = (y[76]-y[79]+y[80]-2.00000000000000000*y[82]*p[152]/p[153]-y[90])/p[192]
            dx_dt[8] = ((-y[76])+y[78]+y[82]*p[152]/p[153]-y[90]-y[93]*p[152]/p[153])/p[193]
            dx_dt[9] = ((-y[76])+y[79]-y[80]+2.00000000000000000*y[82]*p[152]/p[153]+y[90])/p[194]
            dx_dt[10] = (y[77]-120.00000000000000000*y[89])/p[195]
            dx_dt[11] = (y[77]-y[81]+1260.00000000000000000*y[89]-y[92]+y[95])/p[196]
            dx_dt[12] = ((-y[77])+y[84]-240.00000000000000000*y[89]-y[94]+y[101])/p[197]
            dx_dt[13] = ((-y[77])+y[81]-1260.00000000000000000*y[89]+y[92]-y[95])/p[198]
            dx_dt[14] = (y[78]-y[94]*p[152]/p[153])/p[199]
            dx_dt[15] = ((-y[78])+y[79]-y[82]*p[152]/p[153]+y[85]*p[152]/p[153])/p[200]
            dx_dt[16] = ((-y[79])-y[85]*p[152]/p[153]+y[90]+y[93]*p[152]/p[153]+y[97]*p[152]/p[153])/p[201]
            dx_dt[17] = (2.00000000000000000*y[80]-2.00000000000000000*y[88]+2.00000000000000000*y[90])/p[202]
            dx_dt[18] = (4.00000000000000000*y[80]+6.00000000000000000*y[88]-y[91]-3.00000000000000000*y[96]-y[102]*p[152]/p[153])/p[203]
            dx_dt[19] = ((-2.00000000000000000)*y[80]+2.00000000000000000*y[88]-2.00000000000000000*y[90])/p[204]
            dx_dt[20] = ((-4.00000000000000000)*y[80]-6.00000000000000000*y[88]+y[91]+3.00000000000000000*y[96]+y[102]*p[152]/p[153])/p[205]
            dx_dt[21] = (y[81]-y[82]-y[86])/p[206]
            dx_dt[22] = ((-y[81])-y[85]+y[87])/p[207]
            dx_dt[23] = (y[83]-y[84]-y[86]+120.00000000000000000*y[89])/p[208]
            dx_dt[24] = (y[83]-y[87]-120.00000000000000000*y[89])/p[209]
            dx_dt[25] = ((-y[83])+y[84]+y[86]-120.00000000000000000*y[89])/p[210]
            dx_dt[26] = ((-0.50000000000000000)*y[83]+y[98])/p[211]
            dx_dt[27] = (y[84]+y[85]-y[93]-y[97])/p[212]
            dx_dt[28] = ((-y[84])+120.00000000000000000*y[89]+y[93])/p[213]
            dx_dt[29] = ((-y[84])-120.00000000000000000*y[89]+y[94])/p[214]
            dx_dt[30] = (y[85]*p[152]/p[153]+y[90]-y[92]*p[152]/p[153]+y[96])/p[215]
            dx_dt[31] = ((-y[90])-y[96]-y[97]*p[152]/p[153]+y[102]*p[152]/p[153])/p[216]
            dx_dt[32] = ((-y[85])*p[152]/p[153]-y[90]+y[92]*p[152]/p[153]-y[96])/p[217]
            dx_dt[33] = ((-120.00000000000000000)*y[89]+y[99])/p[218]
            dx_dt[34] = ((-120.00000000000000000)*y[89]+y[100])/p[219]
            dx_dt[35] = y[70]
        end

        de_prob = ODEProblem(cho, x0, (t[1],t[end]), p,
                       isoutofdomain=(u,p,t) -> any(x -> (x < 0), vcat(u,p)))
        de_sol = solve(de_prob, AutoVern7(Rodas5()), saveat=t)
        ode_data = de_sol.u
        return DEProblem(cho, p, bounds, ode_data, t)

    end
end

end


e = exp(1.0)

function area(radius)
    a = pi * radius^2
    return a
end

A = area(1)

@show e area(1);

# More Recursion
# Turing Thesis

# Checking Types


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

using Markdown
using InteractiveUtils

# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
    quote
        local el = $(esc(element))
        global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : missing
        el
    end
end

# ╔═╡ 920fb510-0ff1-11eb-0397-7524b4e260df
md"""
## Arrays Basics
-https://www.youtube.com/watch?v=CwDI-YOjWhc
"""

# ╔═╡ a2689530-0ff1-11eb-237b-2191e0d0e553
begin
	import Pkg
	Pkg.activate(mktempdir())
end

# ╔═╡ a23ddbb0-0ff1-11eb-13ed-39ac3383e3f7
begin
	Pkg.add(["Images", "ImageIO", "ImageMagick", "TestImages"])
	using Images
end

# ╔═╡ a22300b0-0ff1-11eb-0d31-7d1ee193c63c
	md"""Vector and array
- Defining, Type of and Size"""

# ╔═╡ a5f899c0-0ff1-11eb-33a9-c136cfa99b88
v = [1,2,3,4,5,6] # this is vector

# ╔═╡ 8b4e34c0-0ff3-11eb-1bcb-532ae49a59ed
typeof(v)

# ╔═╡ ac6b39f0-0ff3-11eb-19f3-dd8175b61e78
size(v)

# ╔═╡ 9369dbf2-0ff3-11eb-332f-cf47ac906f54
A = [4 5 6
	 9 8 3
     9 2 6]

# ╔═╡ 932e3280-0ff3-11eb-0ca8-596fcd6efe47
typeof(A)

# ╔═╡ cc5cb770-0ff3-11eb-3e5f-67172078918e
size(A)

# ╔═╡ a5e38b1e-0ff1-11eb-2d07-594e1017073f
md"""Below demonstrating selecint an emlement, range, row or column"""

# ╔═╡ a5cecaa0-0ff1-11eb-3b69-c9888018ec53
A[5] # picks element on a position

# ╔═╡ e7bdbae0-0ff4-11eb-2642-e193e1676378
A[:,2] # second column, all element

# ╔═╡ e75d9980-0ff4-11eb-0db5-29f2c9f220a4
A[3,:] # third row, all element

# ╔═╡ e70feeb0-0ff4-11eb-201a-1bce59f47b9b
A[2:3, :] # small matrix

# ╔═╡ 80066900-0ff5-11eb-1320-89d3e6476de4
A[:, 2:3] # small matrix

# ╔═╡ a5b96de2-0ff1-11eb-0e90-51478ea0c709
md"""
## Colors"""

# ╔═╡ a590b030-0ff1-11eb-1bc9-89f29eac464c
md"""Distinguishable Colors - provide the number to increase the radom"""

# ╔═╡ a57c64e0-0ff1-11eb-0d67-7390aed58a41
C1 = distinguishable_colors(3)

# ╔═╡ 2e4530f0-0ff6-11eb-283a-5fdcd7e53ef8
C2 = distinguishable_colors(7)

# ╔═╡ 2dfbcbe2-0ff6-11eb-37fe-fbf8dc60a211
C3 = distinguishable_colors(7)

# ╔═╡ a5672f30-0ff1-11eb-135b-4d6522f840ed
md"""Creating a Matrix of random Colors"""

# ╔═╡ a551ab60-0ff1-11eb-1f74-c36932fccd40
Z1 = rand(C2, 5,5)

# ╔═╡ 6c0ffa50-0ff6-11eb-0268-97c6f27d87f3
Z2 = rand(C3,5,5)

# ╔═╡ a5282a60-0ff1-11eb-3c52-97f5dc063a5f
md"""bind Slider"""

# ╔═╡ a51369e0-0ff1-11eb-35da-e5462d420967
@bind x html"<input type=range min=1 max=150>"

# ╔═╡ 50ace920-0ff7-11eb-205b-118b6c1d7e79
Z3 = rand(C3,x,x)

# ╔═╡ a5014170-0ff1-11eb-2bb3-a71e24fc2432
md"""Creating a Comprehension Table Matrix left, creating I and J loops, to create a matrix, double llop, watch the video in description at 3:35"""

# ╔═╡ a4d79960-0ff1-11eb-1016-f5306d2483e1
md"""Square of Matrix and Its Element Square"""

# ╔═╡ ca2232f0-1002-11eb-0969-eb0f53652baf
B = A

# ╔═╡ c9d09080-1002-11eb-387b-030058975f47
B^2

# ╔═╡ eec18110-1002-11eb-0713-45098e649683
md"""When ypu put a dot then its a element by element square as below:"""

# ╔═╡ e79c95f0-1002-11eb-2b73-dbbc15682a95
B.^2

# ╔═╡ a4953930-0ff1-11eb-05c5-7deb20dc7af1
md"""Concatenate matrix, vertical and horizontal as well, use semi column, thats the trick"""

# ╔═╡ b4889100-1002-11eb-272c-e721ee0984eb
 H1 = [Z1 ; Z2]

# ╔═╡ 7ac77390-1003-11eb-38ab-974ac8710b4f
H2 = [Z2 ; Z1]

# ╔═╡ c36d0420-1003-11eb-02b4-5778956fcd2c
Notice below and above, Verical concatenate happens with semi column and horizontal happens without semi columns

# ╔═╡ 8bfb2350-1003-11eb-05a1-8335c973c679
[H2  H1]

# ╔═╡ b4764180-1002-11eb-1512-0f80b099d65e
md"""--"""

# ╔═╡ b46a3390-1002-11eb-30b6-eb20022bcaa6

# ╔═╡ b4480590-1002-11eb-2d69-67d2be3a2f59
md"""--"""

# ╔═╡ b431975e-1002-11eb-2e91-3f5c9069231b

# ╔═╡ b3fdb620-1002-11eb-23d7-5d5220741f9d
md"""--"""

# ╔═╡ b3cfef60-1002-11eb-0962-6fa39009633f

# ╔═╡ Cell order:
# ╟─920fb510-0ff1-11eb-0397-7524b4e260df
# ╠═a2689530-0ff1-11eb-237b-2191e0d0e553
# ╠═a23ddbb0-0ff1-11eb-13ed-39ac3383e3f7
# ╟─a22300b0-0ff1-11eb-0d31-7d1ee193c63c
# ╠═a5f899c0-0ff1-11eb-33a9-c136cfa99b88
# ╠═8b4e34c0-0ff3-11eb-1bcb-532ae49a59ed
# ╠═ac6b39f0-0ff3-11eb-19f3-dd8175b61e78
# ╠═9369dbf2-0ff3-11eb-332f-cf47ac906f54
# ╠═932e3280-0ff3-11eb-0ca8-596fcd6efe47
# ╠═cc5cb770-0ff3-11eb-3e5f-67172078918e
# ╟─a5e38b1e-0ff1-11eb-2d07-594e1017073f
# ╠═a5cecaa0-0ff1-11eb-3b69-c9888018ec53
# ╠═e7bdbae0-0ff4-11eb-2642-e193e1676378
# ╠═e75d9980-0ff4-11eb-0db5-29f2c9f220a4
# ╠═e70feeb0-0ff4-11eb-201a-1bce59f47b9b
# ╠═80066900-0ff5-11eb-1320-89d3e6476de4
# ╟─a5b96de2-0ff1-11eb-0e90-51478ea0c709
# ╠═a590b030-0ff1-11eb-1bc9-89f29eac464c
# ╠═a57c64e0-0ff1-11eb-0d67-7390aed58a41
# ╠═2e4530f0-0ff6-11eb-283a-5fdcd7e53ef8
# ╠═2dfbcbe2-0ff6-11eb-37fe-fbf8dc60a211
# ╟─a5672f30-0ff1-11eb-135b-4d6522f840ed
# ╠═a551ab60-0ff1-11eb-1f74-c36932fccd40
# ╠═6c0ffa50-0ff6-11eb-0268-97c6f27d87f3
# ╟─a5282a60-0ff1-11eb-3c52-97f5dc063a5f
# ╠═a51369e0-0ff1-11eb-35da-e5462d420967
# ╠═50ace920-0ff7-11eb-205b-118b6c1d7e79
# ╟─a5014170-0ff1-11eb-2bb3-a71e24fc2432
# ╠═a4d79960-0ff1-11eb-1016-f5306d2483e1
# ╠═ca2232f0-1002-11eb-0969-eb0f53652baf
# ╠═c9d09080-1002-11eb-387b-030058975f47
# ╟─eec18110-1002-11eb-0713-45098e649683
# ╠═e79c95f0-1002-11eb-2b73-dbbc15682a95
# ╟─a4953930-0ff1-11eb-05c5-7deb20dc7af1
# ╠═b4889100-1002-11eb-272c-e721ee0984eb
# ╠═7ac77390-1003-11eb-38ab-974ac8710b4f
# ╠═c36d0420-1003-11eb-02b4-5778956fcd2c
# ╠═8bfb2350-1003-11eb-05a1-8335c973c679
# ╠═b4764180-1002-11eb-1512-0f80b099d65e
# ╠═b46a3390-1002-11eb-30b6-eb20022bcaa6
# ╠═b4480590-1002-11eb-2d69-67d2be3a2f59
# ╠═b431975e-1002-11eb-2e91-3f5c9069231b
# ╠═b3fdb620-1002-11eb-23d7-5d5220741f9d
# ╠═b3cfef60-1002-11eb-0962-6fa39009633f