import Base.show

export Landscape, prob_neutral

@doc """ function makelinks(n::Int64, k::Int64, near::Bool)

Return an array of links (bit masks) that select the linked loci.
Thus, link[i] selects the loci that are linked to locus  `i`.
Each link contains `k + 1` one bits.  link[i] is guaranteed to have bit i
(counting from the right) set to one.  The positions of the remaining `k`
one bits are randomly selected from the set {1:n} \ {i} (where \ denotes
set difference).
"""
function makelinks(n::Int64, k::Int64, near::Bool)
  if k >= n
    error("k must be strictly less than n")
  end
  links = zeros(AlleleMask, n)
  if near
    # TODO: See if we can come up with a prettier algorithm here.
    if isodd(k)
      error("Periodic boundary requires an even-valued K.")
    end
    offset = div(k, 2)
    for i = 1:n
      mask = AlleleMask(0)
      for j = (i - offset):(i + offset)
        shift = if j < 1
          j + n - 1
        elseif j > n
          j - n - 1
        else
          j - 1
        end
        mask = mask | (AlleleMask(1) << shift)
      end
      links[i] = mask
    end
  else
    for i = 1:n
      linkedloci = sample(setdiff(0:(n - 1), [i - 1]), k, replace=false)
      mask = reduce((a, b) -> a | (AlleleMask(1) << b),
          AlleleMask(1) << (i - 1), linkedloci)
      links[i] = mask
    end
  end
  return links
end

# TODO: Provide a function to pre-fill contribs (for use with small k).

abstract type Landscape end

show(io::Base.IO, l::Landscape) = print(io, "$(typeof(l))($(l.n), $(l.k))")

include("landscape/nk.jl")
include("landscape/nkq.jl")
include("landscape/nkp.jl")


include("../src/components/damages_component.jl")

@testset "damages" begin

    Precision = 1.0e-11
    T = length(MimiDICE2010.model_years)
    f = readxlsx(joinpath(@__DIR__, "..", "data", "DICE2010_082710d.xlsx"))

    m = Model()

    set_dimension!(m, :time, MimiDICE2010.model_years)

    add_comp!(m, damages, :damages)

# Set the parameters that would normally be internal connection from their Excel values
    set_param!(m, :damages, :TATM, read_params(f, "B121:BI121", T))
    set_param!(m, :damages, :YGROSS, read_params(f, "B92:BI92", T))
    set_param!(m, :damages, :TotSLR, read_params(f, "B182:BI182", T))

# Load the rest of the external parameters
    p = dice2010_excel_parameters(joinpath(@__DIR__, "..", "data", "DICE2010_082710d.xlsx"))
    set_param!(m, :damages, :a1, p[:a1])
    set_param!(m, :damages, :a2, p[:a2])
    set_param!(m, :damages, :a3, p[:a3])
    set_param!(m, :damages, :b1, p[:b1])
    set_param!(m, :damages, :b2, p[:b2])
    set_param!(m, :damages, :b3, p[:b3])

# Run the one-component model
    run(m)

# Extract the generated variables
    DAMFRAC = m[:damages, :DAMFRAC]

# Extract the true values
    True_DAMFRAC    = read_params(f, "B93:BI93", T)

# Test that the values are the same
    @test maximum(abs, DAMFRAC .- True_DAMFRAC) ≈ 0. atol = Precision

end


# dependencies:
# - none

module util

function matlab_display(obj)
    class_name = string( typeof(obj) )
    props = propertynames(obj)
    sp = map(string, props)
    L = map(length, sp)
    Lmax = Base.maximum(L)

    props_vec = Vector{String}(undef, length(sp))
    for kk = 1:length(sp)
        props_vec[kk] = "\t" * lpad(sp[kk], Lmax) * ": " * string( getproperty(obj, props[kk]) ) * "\n"
    end

    str = class_name * " with properties:\n"
    str_end = reduce(*, props_vec)
    return str * str_end * "\n"
end

function swap!(array::AbstractArray, i1::Int, i2::Int)
    tmp = array[i1];
    array[i1] = array[i2];
    array[i2] = tmp;
    return array
end

function mid_point(a::T, b::T)::T where {T}
    return (b - a) / 2 + a
end

function mid_point(a::T, b::T)::T where {T<:Integer}
    return floor( (b - a) / 2 + a )
end

function swappop!(array::AbstractArray, idx::Int)
	tmp = array[end]
	array[end] = array[idx]
	array[idx] = tmp
	return pop!(array)
end

function modulo_index(n, L)
	if n <= 0
		n = n % L
		n = L + n
		#  # L in domain 0.. -L+1
	end

	return  (n-1) % L + 1
end

function next_index(index, L)
    # ring indexing
    if index >= L
        index = one(typeof(index))
    else
        index = index + one(typeof(index))
    end
    return index
end

function prev_index(index, L)
    # ring indexing
    if index <= 1
        index = L
    else
        index = index - one(typeof(index))
    end
    return index
end

function is_vector(A)
    return length(size(A)) == 1
end

function is_matrix(A)
    return length(size(A)) == 2
end

function maximum(a::AbstractArray{T}) where {T}
    n = length(a)
    if n < 1
        return (nothing, zero(Int64))
    end

    max_elem = a[1]
    max_idx = one(Int64)

    @inbounds @simd for kk = 2:n
        @inbounds a_ = a[kk]
        if isless(max_elem, a_)
            max_elem = a_
            max_idx = kk
        end
    end

    return (max_elem, max_idx)
end

function maximum(f, a::AbstractArray{T}) where {T}
    n = length(a)
    if n < 1
        return (nothing, zero(Int64))
    end

    max_elem = f(a[1])
    max_idx = one(Int64)

    @inbounds @simd for kk = 2:n
        @inbounds fa = f(a[kk])
        if isless( max_elem, fa )
            max_elem = fa
            max_idx = kk
        end
    end

    return (max_elem, max_idx)
end

function linspace(x_start, x_end, x_length::Int)
    if x_length <= 1
        return [x_end]
    end
    x_delta = (x_end - x_start) / (x_length - 1)
    x_ = collect( x_start:x_delta:x_end )
    return x_
end

function logspace(a, b, n::Int = 50)
	retval = 10 .^ collect( LinRange(a, b, n) )
end

function mesh_grid(x, y)
    X = [i for i in x, j in 1:length(y)]
    Y = [j for i in 1:length(x), j in y]
    return (X, Y)
end

function linear_search(
    x::Vector{T},
    xq::T;
    left_init_index = 1) where {T}

    # it is assumed that the x is sorted in increasing order
    idx = 0 # if zero -> no valid index found

    L = length(x)

    # handle extrapolation
    if xq <= x[1]
        return 0
    elseif xq >= x[L]
        return L+1
    end

    # handle interpolation
    ii = left_init_index
    while ii <= L
        if xq <= x[ii]
            idx = ii-1
            break
        end
        ii += 1
    end

    return idx

end

function binary_search(
    x,
    xq;
    left_init_index = 1,
    right_init_index = length(x))

    # it is assumed that the x is sorted in increasing order
    idx = 0 # if zero -> no valid index found
    # if -1     left extrapolated
    # if L+1    right extrapolated

    L = length(x)

    # handle extrapolation
    if xq < x[1]
        return 0
    elseif xq > x[L]
        return L+1
    end

    left_idx  = left_init_index
    right_idx = right_init_index
    mid_idx   = div(right_idx + left_idx, 2)

    while true

        if xq <= x[mid_idx]
            right_idx = mid_idx
        else # xq > curve.x[mid_idx]
            left_idx = mid_idx
        end

        mid_idx = div(right_idx + left_idx, 2)
        if mid_idx == left_idx
            break
        end
    end
    idx = left_idx

    return idx

end

end


# ---
# title: 1630. Arithmetic Subarrays
# id: problem1630
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Sort
# link: <https://leetcode.com/problems/arithmetic-subarrays/description/>
# hidden: true
# ---
#
# A sequence of numbers is called **arithmetic** if it consists of at least two
# elements, and the difference between every two consecutive elements is the
# same. More formally, a sequence `s` is arithmetic if and only if `s[i+1] -
# s[i] == s[1] - s[0] `for all valid `i`.
#
# For example, these are **arithmetic** sequences:
#
#
#
#     1, 3, 5, 7, 9
#     7, 7, 7, 7
#     3, -1, -5, -9
#
# The following sequence is not **arithmetic** :
#
#
#
#     1, 1, 2, 5, 7
#
# You are given an array of `n` integers, `nums`, and two arrays of `m` integers
# each, `l` and `r`, representing the `m` range queries, where the `ith` query
# is the range `[l[i], r[i]]`. All the arrays are **0-indexed**.
#
# Return _a list of_`boolean` _elements_ `answer` _, where_ `answer[i]` _is_
# `true` _if the subarray_ `nums[l[i]], nums[l[i]+1], ... , nums[r[i]]` _can be
# **rearranged** to form an **arithmetic** sequence, and_ `false` _otherwise._
#
#
#
# **Example 1:**
#
#
#
#     Input: nums = [4,6,5,9,3,7], l = [0,0,2], r = [2,3,5]
#     Output: [true,false,true]
#     Explanation:
#     In the 0th query, the subarray is [4,6,5]. This can be rearranged as [6,5,4], which is an arithmetic sequence.
#     In the 1st query, the subarray is [4,6,5,9]. This cannot be rearranged as an arithmetic sequence.
#     In the 2nd query, the subarray is [5,9,3,7]. This can be rearranged as [3,5,7,9], which is an arithmetic sequence.
#
# **Example 2:**
#
#
#
#     Input: nums = [-12,-9,-3,-12,-6,15,20,-25,-20,-15,-10], l = [0,1,6,4,8,7], r = [4,4,9,7,9,10]
#     Output: [false,true,false,false,true,true]
#
#
#
#
# **Constraints:**
#
#   * `n == nums.length`
#   * `m == l.length`
#   * `m == r.length`
#   * `2 <= n <= 500`
#   * `1 <= m <= 500`
#   * `0 <= l[i] < r[i] < n`
#   * `-105 <= nums[i] <= 105`
#
#
## @lc code=start
using LeetCode

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


"""
      optimize(
            f::Function, # objective function
            bounds::AbstractMatrix,
            method::AbstractAlgorithm = ECA();
            logger::Function = (status) -> nothing,
      )

Minimize a n-dimensional function `f` with domain `bounds` (2×n matrix) using `method = ECA()` by default.

# Example
Minimize f(x) = Σx² where x ∈ [-10, 10]³.

Solution:

```jldoctest
julia> f(x) = sum(x.^2)
f (generic function with 1 method)

julia> bounds = [  -10.0 -10 -10; # lower bounds
                    10.0  10 10 ] # upper bounds
2×3 Array{Float64,2}:
 -10.0  -10.0  -10.0
  10.0   10.0   10.0

julia> result = optimize(f, bounds)
+=========== RESULT ==========+
  iteration: 1429
    minimum: 2.5354499999999998e-222
  minimizer: [-1.5135301653303966e-111, 3.8688354844737692e-112, 3.082095708730726e-112]
    f calls: 29989
 total time: 0.1543 s
+============================+
```
"""
function optimize(
        f::Function, # objective function
        bounds::AbstractMatrix,
        method::AbstractAlgorithm = ECA();
        logger::Function = (status) -> nothing,
    )

    #####################################
    # common methods
    #####################################
    # status = method.status
    information = method.information
    options = method.options
    parameters = method.parameters
    ###################################

    problem = Problem(f, Array(bounds); parallel_evaluation=options.parallel_evaluation)
    seed!(options.seed)

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

    start_time = time()

    status = method.status
    options.debug && @info("Initializing population...")
    status = initialize!(status,parameters, problem, information, options)
    method.status = status

    show_status(status, parameters, options)

    status.iteration = 1

    status.start_time = start_time
    convergence = State{typeof(status.best_sol)}[]

    ###################################
    # store convergence
    ###################################
    if options.store_convergence
        update_convergence!(convergence, status)
    end

    options.debug && @info("Starting main loop...")

    logger(status)

    while !status.stop
        status.iteration += 1

        update_state!(status, parameters, problem, information, options)

        # store the number of fuction evaluations
        status.f_calls = problem.f_calls

        show_status(status, parameters, options)

        if options.store_convergence
            update_convergence!(convergence, status)
        end

        status.overall_time = time() - status.start_time
        logger(status)

        # common stop criteria
        status.stop = status.stop ||
        call_limit_stop_check(status, information, options) ||
        iteration_stop_check(status, information, options)  ||
        time_stop_check(status, information, options)

        # user defined stop criteria
        stop_criteria!(status, parameters, problem, information, options)

    end

    status.overall_time = time() - status.start_time

    final_stage!(
                 status,
                 parameters,
                 problem,
                 information,
                 options
                )

    status.convergence = convergence

    return status

end

function show_status(status, parameters, options)
    !options.debug && (return)
    status.final_time = time()
    msg = "Current Status of " * string(typeof(parameters))
    @info msg
    display(status)
end


"""
Parsing the JSON in `data`
"""
function extract_country_data(country, data)
    deaths = map(e -> e["deaths"], data[country])
    confirmed=  map(e -> e["confirmed"], data[country])
    dates=  map(e -> Date(e["date"],dateformat"y-m-d"), data[country])
    recovered=map(e -> e["recovered"], data[country])
    (deaths=deaths, confirmed=confirmed, dates=dates, recovered=recovered)
end

function days_till_target_number(target_number,d=deaths, v=v)
    c=d[end] # Number of infected today
    x=log(target_number/c)/log(2)
    doubling_time=log(2)/v[2]
    time=ceil(Int,x*doubling_time)
    doubling_time, time
end

function segmented_models(d,model_interval=5)
    segments=[i:i+model_interval-1 for i=1:model_interval:length(d)-model_interval+1]
    confirmed_segments=map(s->d[s], segments)
    v_interval=[[ones(model_interval) [segments[i]...]]\ log.(confirmed_segments[i]) for i=1:length(confirmed_segments)]
    (segments=segments, v_interval=v_interval)
end

function plot_doubling_times(d,v_interval,segments,dates, p_head="Confirmed cases"; st=:bar)
    d_times=[days_till_target_number(20_000,d,v)[1] for v in v_interval] # Doubling times
    plot(d_times, xticks=(1:length(segments),
            string.(dates[[last(s) for s in segments]])),
         xrotation=45, label="", title="$p_head doubling time",
         ylabel="days", xlabel="date", seriestype=st)
end

function plot_whisker_fit(d,v_interval,segments, dates, p_head="Confirmed cases"; model_interval=5, date_density=date_density)
    p=scatter(1:length(dates),d,
        label="data", title="$p_head of Covid-19",
        legend=:topleft, xrotation=45,
        xticks=(1:date_density:length(dates),string.(dates[1:date_density:length(dates)])))
    for i in eachindex(segments)
        plot!(p,first(segments[i]):last(segments[i])+model_interval,
            x->exp(v_interval[i][1]+v_interval[i][2]*x),
            label="")
    end
    p
end


centered(C::Type{HyperCube{N,T}}) where {N,T} = C(Vec{N,T}(-0.5), T(1))
function centered(::Type{T}) where T <: HyperCube
    centered(HyperCube{ndims_or(T, 3), eltype_or(T, Float32)})
end

centered(R::Type{HyperRectangle{N,T}}) where {N, T} = R(Vec{N,T}(-0.5), Vec{N,T}(1))
centered(::Type{T}) where {T <: HyperRectangle} = centered(HyperRectangle{ndims_or(T, 3), eltype_or(T, Float32)})

centered(S::Type{HyperSphere{N, T}}) where {N, T} = S(Vec{N,T}(0), T(0.5))
centered(::Type{T}) where {T <: HyperSphere} = centered(HyperSphere{ndims_or(T, 3), eltype_or(T, Float32)})


using Test, BenchmarkTools

using StaticArrays: SVector
using StaticKernels: Kernel
using NNlib: DenseConvDims, conv!
using ImageFiltering: centered, imfilter!, Inner
using LocalFilters: convolve!
import LocalFilters
using OffsetArrays: OffsetArray
using LoopVectorization: @avx

BenchmarkTools.DEFAULT_PARAMETERS.seconds = 0.5
pd = 19

kernel_axes(k) = map(n -> -n÷2:n÷2, size(k))
inner_axes(k, a) = map(axes(a), kernel_axes(k)) do ax, kx
        first(ax) - first(kx) : last(ax) - last(kx)
    end
inner_size(k, a) = length.(inner_axes(k, a))

function bench_linfilt(k, a, ::Val{:StaticKernels})
    print(rpad("  StaticKernels", pd))

    ktuple = Tuple(k)
    @inline function wf(w)
        return sum(SVector(Tuple(w)) .* SVector(ktuple))
    end
    @assert size(k, 1) == size(k, 2)
    K = size(k, 1)
    kern = Kernel{kernel_axes(k)}(wf)
    b1 = similar(a, size(kern, a))
    @btime map!($kern, $b1, $a)
    b1
end

function bench_linfilt(k, a, ::Val{:LoopVectorization})
    print(rpad("  LoopVectorization", pd))

    # from discourse forum:
    # https://discourse.julialang.org/t/ann-statickernels-jl-fast-kernel-operations-on-arrays/37658
    function filter2davx!(out::AbstractMatrix, A::AbstractMatrix, kern)
        @avx for J in CartesianIndices(out)
            tmp = zero(eltype(out))
            for I ∈ CartesianIndices(kern)
                tmp += A[I + J] * kern[I]
            end
            out[J] = tmp
        end
        out
    end

    b5 = similar(a, inner_axes(k, a))
    k5 = reshape(k, kernel_axes(k))
    @btime $filter2davx!($b5, $a, $k5)
    #filter2davx!(b5, a, k5)
    # weird OffsetArray broadcast problem
    b5 = parent(b5)
end

function bench_linfilt(k, a, ::Val{:NNlib})
    print(rpad("  NNlib", pd))

    ar = reshape(a, (size(a)..., 1, 1))
    b2 = similar(a, (inner_size(k, a)..., 1, 1))
    kr = reshape(k, (size(k)..., 1, 1))
    cdims = DenseConvDims(ar, kr; stride=1, padding=0, dilation=1, flipkernel=true)
    @btime conv!($b2, $ar, $kr, $cdims)
    b2
end

function bench_linfilt(k, a, ::Val{:ImageFiltering})
    print(rpad("  ImageFiltering", pd))

    b3 = similar(a, inner_axes(k, a))
    kc = centered(k)
    @btime imfilter!($b3, $a, $kc, Inner())
    # weird OffsetArray broadcast problem
    b3 = parent(b3)
end

function bench_linfilt(k, a, ::Val{:LocalFilters})
    print(rpad("  LocalFilters", pd))

    b4 = similar(a)
    # mathematical convolution
    kr = LocalFilters.Kernel(reverse(reverse(k, dims=1), dims=2))
    @btime convolve!($b4, $a, $kr)
    # no way to only compute inner part
    b4 = b4[inner_axes(k, a)...]
end

# Linear Filtering

for N in (100, 1000), K in (3, 5, 7)
    a = rand(N, N)
    k = rand(K, K)

    println("Array: $(size(a)), Kernel: $(size(k))")

    b = bench_linfilt(k, a, Val(:StaticKernels))

    # disabled: :LocalFilters
    for s in [:LoopVectorization, :NNlib, :ImageFiltering]
        @test b ≈ bench_linfilt(k, a, Val(s))
    end
end


#=
downsample.jl
Copyright 2019-03-05, Jeff Fessler, University of Michigan

see also MIRT/time/downsample.jl
=#

export downsample_dim1
export downsample1
export downsample2
export downsample3
export downsample

"""
    y = downsample_dim1(x, down ; warn::Bool)
downsample by factor `down` along first dimension by averaging

in
- `x [n1 (Nd)]`
- `down::Int` downsampling factor

option
- `warn::Bool` warn if noninteger multiple; default `isinteractive()`

out
- `y [n1/down (Nd)]`

Copyright 2019-03-05, Jeff Fessler, University of Michigan
"""
function downsample_dim1(
	x::AbstractArray{<:Number},
	down::Int ;
	warn::Bool = isinteractive(),
)

	dim = size(x)
	dim1 = dim[1]
	x = reshape(x, dim1, :) # [n1 *Nd]
	m1 = dim1 ÷ down
	if m1 * down < dim1
		warn && @warn("truncating input size $dim1 to $(m1 * down)")
		x = x[1:(m1*down),:]
	end
	y = reshape(x, down, :)
	y = sum(y, dims=1) / down # mean
	y = reshape(y, m1, dim[2:end]...)
#	out = similar(x, (m1, dim[2:end]...))
#	vec(out) .= vec(y) # failed attempt to help @inferred for 2D input arrays
	return y
end

"""
    y = downsample1(x, down ; warn=true)
downsample 1D vector by factor `down`

in
- `x [n1]`
- `down::Int` downsampling factor

option
- `warn::Bool` warn if noninteger multiple; default `isinteractive()`

out
- `y [n1/down]`

Copyright 2019-03-05, Jeff Fessler, University of Michigan
"""
function downsample1(
	x::AbstractVector{<:Number},
	down::Int ;
	warn::Bool = isinteractive(),
)

	dim = size(x)
	dim1 = dim[1]
	m1 = dim1 ÷ down
	if m1 * down < dim1
		warn && @warn("truncating input size $dim1 to $(m1 * down)")
		y = reshape((@view x[1:(m1*down)]), down, :)
	else
		y = reshape(x, down, :)
	end
	y = sum(y, dims=1) / down # mean
	y = reshape(y, m1, dim[2:end]...)
	return vec(y)
end

"""
    y = downsample2(x, down ; warn=true, T)

downsample by averaging by integer factors
in
- `x [nx ny]`
- `down` can be a scalar (same factor for both dimensions) or a `NTuple{2,Int}`

option
- `warn::Bool` warn if noninteger multiple; default `isinteractive()`
- `T::DataType` specify output eltype; default `eltype(x[1] / down[1])`

out
- `y [nx/down ny/down]`
"""
function downsample2(
	x::AbstractMatrix{<:Number},
	down::NTuple{2,Int} ;
	warn::Bool = isinteractive(),
	T::DataType = eltype(x[1] / down[1])
)

	idim = size(x)
	odim = idim .÷ down

	warn && any(odim .* down .!= idim) && @warn("truncating to $odim")

#	y = similar(x, odim) # fails!?
	y = Array{T}(undef, odim[1], odim[2])
	d1 = down[1]
	d2 = down[2]
	for i2=1:odim[2]
		for i1=1:odim[1]
			y[i1,i2] =
				sum(@view x[(i1-1)*d1 .+ (1:d1), (i2-1)*d2 .+ (1:d2)]) / d1 / d2
		end
	end

#=
	fun = (x, d) -> downsample_dim1(x, d, warn=warn)

#=
#	this old way returned an Adjoint type:
	y = fun(x, down[1])
	y = fun(y', down[end])'
=#

#	this way avoids the Adjoint:
	y = fun(x', down[end])' # doing adjoint first
	y = fun(y, down[1])

#=	failed attempt at helping @inferred:
	f1 = (x, d) -> downsample1(x, d, warn=warn)
	tmp = x'
	tmp = hcat([f1(tmp[:,n], down[end]) for n=1:size(tmp,2)]...)'
	y = hcat([f1(tmp[:,n], down[1]) for n=1:size(tmp,2)]...)
@shows y
=#
=#

	return y
end

downsample2(x::AbstractArray{<:Number,2}, down::Int ; args...) =
	downsample2(x, (down, down) ; args...)

"""
    y = downsample3(x, down ; warn=true, T)

downsample by averaging by integer factors
in
- `x [nx ny nz]`
- `down` can be a scalar (same factor for all dimensions) or a `NTuple{3,Int}`

option
- `warn::Bool` warn if noninteger multiple; default true
- `T::DataType` specify output eltype; default `eltype(x[1] / down[1])`

out
- `y [nx/down ny/down nz/down]`
"""
function downsample3(
	x::AbstractArray{<:Number,3},
	down::NTuple{3,Int} ;
	warn::Bool = isinteractive(),
	T::DataType = eltype(x[1] / down[1]),
)

	idim = size(x)
	odim = idim .÷ down

	warn && any(odim .* down .!= idim) && @warn("truncating to $odim")

	return downsample3_perm(x, Tuple(down)) # because it is faster
end

downsample3(x::AbstractArray{<:Number,3}, down::Int ; args...) =
	downsample3(x, (down, down, down) ; args...)

# this method is good for @inferred but is slower!
function downsample3_loop(
	x::AbstractArray{<:Number,3},
	down::NTuple{3,Int} ;
	T::DataType = eltype(x[1] / down[1]),
)

	odim = size(x) .÷ down

	y = Array{T}(undef, odim[1], odim[2], odim[3])
	d1 = down[1]
	d2 = down[2]
	d3 = down[3]
	d123 = d1 * d2 * d3

	for i3=1:odim[3]
		for i2=1:odim[2]
			for i1=1:odim[1]
				y[i1,i2,i3] = sum(@view x[(i1-1)*d1 .+ (1:d1),
					(i2-1)*d2 .+ (1:d2), (i3-1)*d3 .+ (1:d3)]) / d123
			end
		end
	end
	return y
end

# this method fails @inferred but is faster!
function downsample3_perm(x::AbstractArray{<:Number,3}, down::NTuple{3,Int})

	# down sample along each dimension
	y = downsample_dim1(x, down[1])
	y = downsample_dim1(permutedims(y, [2, 1, 3]), down[2])
	y = downsample_dim1(permutedims(y, [3, 2, 1]), down[3]) # [3 1 2] order
	y = permutedims(y, [2, 3, 1])

	return y
end


using DSGE
using BenchmarkTools, JLD
import DSGE: jacobian

path = dirname(@__FILE__)
filepath = "$path/../../reference/KrusellSmith/jacobian.jld"

# Benchmark current function
m = KrusellSmith()
jacobian(m)
trial = @benchmark jacobian($m) gcsample = true

# Optionally over-write the existing reference trial
# write_ref_trial(trial, "jacobian", filepath = filepath)

# Compute time differential
print_all_benchmarks(trial, filepath, "jacobian")


using FANN
using Base.Test

# desired input
X = [ 1.0 3.0 5.0 7.0;
	  2.0 4.0 6.0 8.0]

# desired ouputs
Y = [0.0 1.0 1.0 0.0]

# definitions
M = size(X, 2)
nin = size(X, 1)
nout = size(Y, 1)

# create dataset
dset = DataSet(X, Y)

# 1) try to load input data from the struct
inputs = pointer_to_array(unsafe_load(dset.data).input, M)
outputs = pointer_to_array(unsafe_load(dset.data).output, M)
for i = 1:M
	@test vec(X[:, i]) == pointer_to_array(inputs[i], nin)
	@test vec(Y[:, i]) == pointer_to_array(outputs[i], nout)
end

# 2)  try to save it and the read file
savedset(dset, "here")
data = readdlm("here")
rm("here")

# first row is header data
header = data[1, :]
@test header[1] == M
@test header[2] == nin
@test header[3] == nout

# then read rest
for i = 1:M
	actual = Float64[data[2*i, 1:nin]...] # very ugly
	expected = X[:, i]
	@test norm(actual - expected) < 1e-5

	actual = Float64[data[2*i + 1, 1:nout]...] # very ugly
	expected = Y[:, i]
	@test norm(actual - expected) < 1e-5
end


using Penrose
using Test

# Helper functions
# Returns length of an edge between 2 pts
edgelen_fcn(p) = sqrt((p[2][1]-p[1][1])*(p[2][1]-p[1][1])+(p[2][2]-p[1][2])*(p[2][2]-p[1][2]))
# Returns angle (in degrees) of an edge between 2 pts
angle_fcn(p) = (360 + atan(p[2][2]-p[1][2], p[2][1]-p[1][1]) * 180 / pi) % 360
# Returns the mid-point of an edge between 2 pts
midpt_fcn(p) = [(p[2][1]+p[1][1])/2, (p[2][2]+p[1][2])/2]
# Returns the distance between 2 pts
dist_fcn(p1,p2) = sqrt((p2[1]-p1[1])*(p2[1]-p1[1]) + (p2[2]-p1[2])*(p2[2]-p1[2]))

@testset "Dart angle" begin

    # Angles outside [0-360] should get fixed in ctor
    @testset "angle range" begin
        @test Dart(0,0,-40).angle == 320
        @test Dart(0,0,400).angle ==  40
    end

    @testset "angle internal" begin
        internal_angles = [36, 72, 36, 216]
        d = Dart(0,0,0)
        @test (edge_angle(d,1) + (540 - internal_angles[1]))%360 == edge_angle(d,2)
        @test (edge_angle(d,2) + (540 - internal_angles[2]))%360 == edge_angle(d,3)
        @test (edge_angle(d,3) + (540 - internal_angles[3]))%360 == edge_angle(d,4)
        @test (edge_angle(d,4) + (540 - internal_angles[4]))%360 == edge_angle(d,1)
    end

    @testset "angle 0" begin
        d0 = Dart(0,0,0)
        @test edge_angle(d0, 1) == 252
        @test edge_angle(d0, 2) ==  36
        @test edge_angle(d0, 3) == 144
        @test edge_angle(d0, 4) == 288
    end

    @testset "angle +90" begin
        d90 = Dart(0,0,90)
        @test edge_angle(d90, 1) == 342
        @test edge_angle(d90, 2) == 126
        @test edge_angle(d90, 3) == 234
        @test edge_angle(d90, 4) ==  18
    end

    # -270 should be the same as +90
    @testset "angle -270" begin
        m270 = Dart(0,0,-270)
        @test edge_angle(m270, 1) == 342
        @test edge_angle(m270, 2) == 126
        @test edge_angle(m270, 3) == 234
        @test edge_angle(m270, 4) ==  18
    end

    @testset "angle +180" begin
        d180 = Dart(0,0,180)
        @test edge_angle(d180, 1) ==  72
        @test edge_angle(d180, 2) == 216
        @test edge_angle(d180, 3) == 324
        @test edge_angle(d180, 4) == 108
    end

    # -180 should be the same as +180
    @testset "angle -180" begin
        m180 = Dart(0,0,-180)
        @test edge_angle(m180, 1) ==  72
        @test edge_angle(m180, 2) == 216
        @test edge_angle(m180, 3) == 324
        @test edge_angle(m180, 4) == 108
    end

end

@testset "Kite angle" begin

    @testset "angle internal" begin
        internal_angles = [72, 144, 72, 72]
        k = Kite(0,0,0)
        @test (edge_angle(k,1) + (540 - internal_angles[1]))%360 == edge_angle(k,2)
        @test (edge_angle(k,2) + (540 - internal_angles[2]))%360 == edge_angle(k,3)
        @test (edge_angle(k,3) + (540 - internal_angles[3]))%360 == edge_angle(k,4)
        @test (edge_angle(k,4) + (540 - internal_angles[4]))%360 == edge_angle(k,1)
    end

    @testset "angle 0" begin
        k0 = Kite(0,0,0)
        @test edge_angle(k0, 1) == 324
        @test edge_angle(k0, 2) ==  72
        @test edge_angle(k0, 3) == 108
        @test edge_angle(k0, 4) == 216
    end

    @testset "angle 90" begin
        k90 = Kite(0,0,90)
        @test edge_angle(k90, 1) ==  54
        @test edge_angle(k90, 2) == 162
        @test edge_angle(k90, 3) == 198
        @test edge_angle(k90, 4) == 306
    end

    @testset "angle -270" begin
        m0 = Kite(0,0,-270)
        @test edge_angle(m0, 1) ==  54
        @test edge_angle(m0, 2) == 162
        @test edge_angle(m0, 3) == 198
        @test edge_angle(m0, 4) == 306
    end

    @testset "angle 180" begin
        k180 = Kite(0,0,180)
        @test edge_angle(k180, 1) == 144
        @test edge_angle(k180, 2) == 252
        @test edge_angle(k180, 3) == 288
        @test edge_angle(k180, 4) ==  36
    end

    @testset "angle -180" begin
        m180 = Kite(0,0,-180)
        @test edge_angle(m180, 1) == 144
        @test edge_angle(m180, 2) == 252
        @test edge_angle(m180, 3) == 288
        @test edge_angle(m180, 4) ==  36
    end

end

@testset "Dart edge slopes" begin
    d = Dart(0,0,0)

    @test angle_fcn(edge_points(d,1)) == edge_angle(d,1)
    @test angle_fcn(edge_points(d,2)) == edge_angle(d,2)
    @test angle_fcn(edge_points(d,3)) == edge_angle(d,3)
    @test angle_fcn(edge_points(d,4)) == edge_angle(d,4)
end

@testset "Kite edge slopes" begin
    k = Kite(0,0,0)

    @test angle_fcn(edge_points(k,1)) == edge_angle(k,1)
    @test angle_fcn(edge_points(k,2)) == edge_angle(k,2)
    @test angle_fcn(edge_points(k,3)) == edge_angle(k,3)
    @test angle_fcn(edge_points(k,4)) == edge_angle(k,4)
end

@testset "Dart edge lengths" begin
    d = Dart(0,0,0)

    l1 = (1+sqrt(5))/2
    l2 = 1 + l1

    @test edgelen_fcn(edge_points(d,1)) == l1
    @test edgelen_fcn(edge_points(d,2)) == l2
    @test edgelen_fcn(edge_points(d,3)) == l2
    @test edgelen_fcn(edge_points(d,4)) == l1
end

@testset "Kite edge lengths" begin
    k = Kite(0,0,0)

    l1 = (1+sqrt(5))/2
    l2 = 1 + l1

    @test edgelen_fcn(edge_points(k,1)) == l2
    @test edgelen_fcn(edge_points(k,2)) == l1
    @test edgelen_fcn(edge_points(k,3)) == l1
    @test edgelen_fcn(edge_points(k,4)) == l2
end

@testset "Dart edge center" begin
    d = Dart(0,0,0)

    @test dist_fcn(midpt_fcn(edge_points(d,1)), edge_center(d,1)) == 0
    @test dist_fcn(midpt_fcn(edge_points(d,2)), edge_center(d,2)) == 0
    @test dist_fcn(midpt_fcn(edge_points(d,3)), edge_center(d,3)) == 0
    @test dist_fcn(midpt_fcn(edge_points(d,4)), edge_center(d,4)) == 0
end

@testset "Kite edge center" begin
    k = Kite(0,0,0)

    @test dist_fcn(midpt_fcn(edge_points(k,1)), edge_center(k,1)) == 0
    @test dist_fcn(midpt_fcn(edge_points(k,2)), edge_center(k,2)) == 0
    @test dist_fcn(midpt_fcn(edge_points(k,3)), edge_center(k,3)) == 0
    @test dist_fcn(midpt_fcn(edge_points(k,4)), edge_center(k,4)) == 0
end

@testset "Dart geometry" begin
    phi = (1+sqrt(5))/2
    h = sqrt(5+2*sqrt(5))/2

    @testset "Compare geometry to edge_points" begin
        d = Dart(0,0,0)

        pts = geometry(d)
        @test size(pts) == (4,)
        @test dist_fcn(pts[1], edge_points(d,1)[1]) < 5e-8
        @test dist_fcn(pts[2], edge_points(d,1)[2]) < 5e-8
        @test dist_fcn(pts[2], edge_points(d,2)[1]) < 5e-8
        @test dist_fcn(pts[3], edge_points(d,2)[2]) < 5e-8
        @test dist_fcn(pts[3], edge_points(d,3)[1]) < 5e-8
        @test dist_fcn(pts[4], edge_points(d,3)[2]) < 5e-8
        @test dist_fcn(pts[4], edge_points(d,4)[1]) < 5e-8
        @test dist_fcn(pts[1], edge_points(d,4)[2]) < 5e-8
    end

    @testset "Angle = 0" begin
        d = Dart(0,0,0)

        pts = geometry(d)

        @test size(pts) == (4,)
        @test dist_fcn(pts[1], [0, 0]) < 5e-8
        @test dist_fcn(pts[2], [-1/2, -h]) < 5e-8
        @test dist_fcn(pts[3], [phi, 0]) < 5e-8
        @test dist_fcn(pts[4], [-1/2, h]) < 5e-8
    end

    @testset "Angle = 90" begin
        d = Dart(0,0,90)

        pts = geometry(d)

        @test size(pts) == (4,)
        @test dist_fcn(pts[1], [0, 0]) < 5e-8
        @test dist_fcn(pts[2], [h, -1/2]) < 5e-8
        @test dist_fcn(pts[3], [0, phi]) < 5e-8
        @test dist_fcn(pts[4], [-h,-1/2]) < 5e-8
    end

    @testset "origin = 10,10" begin
        d = Dart(10,10,0)

        pts = geometry(d)
        @test size(pts) == (4,)
        # Why's the tolerance worse here?
        @test dist_fcn(pts[1], [10, 10]) < 5e-7
        @test dist_fcn(pts[2], [10-(1/2), 10-h]) < 5e-7
        @test dist_fcn(pts[3], [10+phi, 10]) < 5e-7
        @test dist_fcn(pts[4], [10-(1/2), 10+h]) < 5e-7
    end
end

@testset "Kite geometry" begin
    k = Kite(0,0,0)

    pts = geometry(k)
    @test size(pts) == (4,)
    @test dist_fcn(pts[1], edge_points(k,1)[1]) < 5e-8
    @test dist_fcn(pts[2], edge_points(k,1)[2]) < 5e-8
    @test dist_fcn(pts[2], edge_points(k,2)[1]) < 5e-8
    @test dist_fcn(pts[3], edge_points(k,2)[2]) < 5e-8
    @test dist_fcn(pts[3], edge_points(k,3)[1]) < 5e-8
    @test dist_fcn(pts[4], edge_points(k,3)[2]) < 5e-8
    @test dist_fcn(pts[4], edge_points(k,4)[1]) < 5e-8
    @test dist_fcn(pts[1], edge_points(k,4)[2]) < 5e-8
end

@testset "Vertex 1" begin
    phi = (1+sqrt(5))/2

    d1 = Dart(-phi, 0, 0)
    d2 = placeDartEdge(3, edge_center(d1, 2), edge_angle(d1, 2))
    d3 = placeDartEdge(3, edge_center(d2, 2), edge_angle(d2, 2))
    d4 = placeDartEdge(3, edge_center(d3, 2), edge_angle(d3, 2))
    d5 = placeDartEdge(3, edge_center(d4, 2), edge_angle(d4, 2))

    h = sqrt(5+2*sqrt(5))/2
    k = (2+sqrt(5)) / (1+sqrt(5))
    p = sqrt(10 + sqrt(20))/4

    @test dist_fcn([d1.cx, d1.cy], [-phi, 0]) < 5e-8
    @test d1.angle == 0

    @test dist_fcn([d2.cx, d2.cy], [-1/2,-h]) < 5e-8
    @test d2.angle == 72

    @test dist_fcn([d3.cx, d3.cy], [k,-p]) < 5e-8
    @test d3.angle == 144

    @test dist_fcn([d4.cx, d4.cy], [k,p]) < 5e-8
    @test d4.angle == 216

    @test dist_fcn([d5.cx, d5.cy], [-1/2, h]) < 5e-8
    @test d5.angle == 288

end

@testset "Vertex 2" begin
    d1 = Dart(0, 0, 0)
    k1 = placeKiteEdge(2, edge_center(d1, 4), edge_angle(d1, 4))
    k2 = placeKiteEdge(4, edge_center(k1, 1), edge_angle(k1, 1))

    phi = (1+sqrt(5))/2
    h = sqrt(5+2*sqrt(5))/2
    k = (2+sqrt(5)) / (1+sqrt(5))
    p = sqrt(10 + sqrt(20))/4

    @test dist_fcn([d1.cx, d1.cy], [0, 0]) < 5e-8
    @test d1.angle == 0

    @test dist_fcn([k1.cx, k1.cy], [-k, p]) < 5e-8
    @test k1.angle == 36

    @test dist_fcn([k2.cx, k2.cy], [-k,-p]) < 5e-8
    @test k2.angle == 324
end

@testset "Vertex 3" begin
    phi = (1+sqrt(5))/2

    k1 = Kite(phi, 0, 0)
    k2 = placeKiteEdge(1, edge_center(k1, 4), edge_angle(k1, 4))
    k3 = placeKiteEdge(1, edge_center(k2, 4), edge_angle(k2, 4))
    k4 = placeKiteEdge(1, edge_center(k3, 4), edge_angle(k3, 4))
    k5 = placeKiteEdge(1, edge_center(k4, 4), edge_angle(k4, 4))

    h = sqrt(5+2*sqrt(5))/2
    k = (2+sqrt(5)) / (1+sqrt(5))
    p = sqrt(10 + sqrt(20))/4

    @test dist_fcn([k1.cx, k1.cy], [phi, 0]) < 5e-8
    @test k1.angle == 0

    @test dist_fcn([k2.cx, k2.cy], [1/2, h]) < 5e-8
    @test k2.angle == 72

    @test dist_fcn([k3.cx, k3.cy], [-k, p]) < 5e-8
    @test k3.angle == 144

    @test dist_fcn([k4.cx, k4.cy], [-k,-p]) < 5e-8
    @test k4.angle == 216

    @test dist_fcn([k5.cx, k5.cy], [1/2,-h]) < 5e-8
    @test k5.angle == 288
end

@testset "Vertex 4" begin
    phi = (1+sqrt(5))/2

    d1 = Dart(-phi, 0, 0)
    d2 = placeDartEdge(3, edge_center(d1, 2), edge_angle(d1, 2))
    k1 = placeKiteEdge(1, edge_center(d2, 2), edge_angle(d2, 2))
    k2 = placeKiteEdge(1, edge_center(k1, 4), edge_angle(k1, 4))
    d3 = placeDartEdge(3, edge_center(k2, 4), edge_angle(k2, 4))

    h = sqrt(5+2*sqrt(5))/2
    k = (2+sqrt(5)) / (1+sqrt(5))
    p = sqrt(10 + sqrt(20))/4

    @test dist_fcn([d1.cx, d1.cy], [-phi, 0]) < 5e-8
    @test d1.angle == 0

    @test dist_fcn([d2.cx, d2.cy], [-1/2,-h]) < 5e-8
    @test d2.angle == 72

    @test dist_fcn([k1.cx, k1.cy], [k,-p]) < 5e-8
    @test k1.angle == 324

    @test dist_fcn([k2.cx, k2.cy], [k, p]) < 5e-8
    @test k2.angle == 36

    @test dist_fcn([d3.cx, d3.cy], [-1/2, h]) < 5e-8
    @test d3.angle == 288
end

@testset "Vertex 5" begin
    k1 = Kite(-1, 0, 0)
    d1 = placeDartEdge(4, edge_center(k1, 2), edge_angle(k1, 2))
    k2 = placeKiteEdge(1, edge_center(d1, 3), edge_angle(d1, 3))
    k3 = placeKiteEdge(1, edge_center(k2, 4), edge_angle(k2, 4))
    d2 = placeDartEdge(2, edge_center(k3, 4), edge_angle(k3, 4))

    h = sqrt(5+2*sqrt(5))/2
    k = (2+sqrt(5)) / (1+sqrt(5))
    p = sqrt(10 + sqrt(20))/4

    @test dist_fcn([k1.cx, k1.cy], [-1, 0]) < 5e-8
    @test k1.angle == 0

    @test dist_fcn([d1.cx, d1.cy], [-1/2, -h]) < 5e-8
    @test d1.angle == 324

    @test dist_fcn([k2.cx, k2.cy], [ k, -p]) < 5e-8
    @test k2.angle == 324

    @test dist_fcn([k3.cx, k3.cy], [ k, p]) < 5e-8
    @test k3.angle == 36

    @test dist_fcn([d2.cx, d2.cy], [-1/2, h]) < 5e-8
    @test d2.angle == 36
end

@testset "Vertex 6" begin
    phi = (1+sqrt(5))/2

    d1 = Dart(-phi, 0, 0)
    k1 = placeKiteEdge(4, edge_center(d1, 2), edge_angle(d1, 2))
    k2 = placeKiteEdge(2, edge_center(k1, 3), edge_angle(k1, 3))
    k3 = placeKiteEdge(4, edge_center(k2, 1), edge_angle(k2, 1))
    k4 = placeKiteEdge(2, edge_center(k3, 3), edge_angle(k3, 3))

    h = sqrt(5+2*sqrt(5))/2
    k = (2+sqrt(5)) / (1+sqrt(5))
    p = sqrt(10 + sqrt(20))/4

    @test dist_fcn([d1.cx, d1.cy], [-phi, 0]) < 5e-8
    @test d1.angle == 0

    @test dist_fcn([k1.cx, k1.cy], [-1/2,-h]) < 5e-8
    @test k1.angle == 0

    @test dist_fcn([k2.cx, k2.cy], [ k,-p]) < 5e-8
    @test k2.angle == 216

    @test dist_fcn([k3.cx, k3.cy], [ k, p]) < 5e-8
    @test k3.angle == 144

    @test dist_fcn([k4.cx, k4.cy], [-1/2, h]) < 5e-8
    @test k4.angle == 0
end

@testset "Vertex 7" begin
    k = (2+sqrt(5)) / (1+sqrt(5))
    p = sqrt(10 + sqrt(20))/4

    k1 = Kite(k-1,-p, 108)
    k2 = placeKiteEdge(3, edge_center(k1, 2), edge_angle(k1, 2))
    d1 = placeDartEdge(4, edge_center(k2, 2), edge_angle(k2, 2))
    d2 = placeDartEdge(2, edge_center(d1, 3), edge_angle(d1, 3))

    @test dist_fcn([k1.cx, k1.cy], [k-1, -p]) < 5e-8
    @test k1.angle == 108

    @test dist_fcn([k2.cx, k2.cy], [k-1, p]) < 5e-8
    @test k2.angle == 252

    @test dist_fcn([d1.cx, d1.cy], [-k, p]) < 5e-8
    @test d1.angle == 216

    @test dist_fcn([d2.cx, d2.cy], [-k,-p]) < 5e-8
    @test d2.angle == 144
end


# by Lazaro Alonso
using CairoMakie, LaTeXStrings, SpecialFunctions
CairoMakie.activate!() # HIDE
let
    cycle = Cycle([:color, :linestyle, :marker], covary = true)
    set_theme!(Lines = (cycle = cycle,), Scatter = (cycle = cycle,))
    #set_theme!()
    fig = Figure(resolution = (600,400), font = "CMU Serif") # probably you need to install this font in your system
    ax = Axis(fig, xlabel = L"x", ylabel = L"Y_{\nu}(x)", ylabelsize = 22,
        xlabelsize= 22, xgridstyle=:dash, ygridstyle=:dash, xtickalign = 1,
        xticksize=10, ytickalign=1, yticksize=10,  xlabelpadding = -10)
    x = 0.1:0.1:15
    for ν in 0:4
        lines!(ax, x, bessely.(ν, x), label = latexstring("Y_{$(ν)}(x)"), linewidth = 2)
        #scatter!(ax, x, bessely.(ν, x), label = latexstring("Y_{$(ν)}(x)"), linewidth = 2)
    end
    axislegend(; position = :rb, nbanks = 2, framecolor = (:grey,0.5))
    ylims!(-1.8,0.7)
    #xlims!(0.1,nothing)
    fig[1,1] = ax
    #save("filename.png", fig, px_per_unit = 2.0)
    save(joinpath(@__DIR__, "output", "besselSecondKind.png"), fig, px_per_unit = 2.0) # HIDE
    display(fig)
end
using Pkg # HIDE
Pkg.status() # HIDE


using DifferentialEquations
using IterableTables, DataFrames
using RCall

H_comps = 4
V_comps = 3

# in sub functions, du / u are the particular relevant slices only
function F1H(du, u, p, t, βslice, I_V, N_H)
    S_H, E_H, I_H, R_H = u

    # host dynamics
    host_infection = sum(βslice .* I_V)*S_H/N_H
    host_mortality = p.μ_H .* u # include S_H, so easier to remove mortality
    host_births = sum(host_mortality)
    host_progression = p.σ_H*E_H
    recovery = p.λ*I_H

    du[1] = -host_infection + host_births
    du[2] = host_infection - host_progression
    du[3] = host_progression - recovery
    du[4] = recovery
    du[1:end] -= host_mortality
end

# in sub functions, du / u are the particular relevant slices only
function F1V(du, u, p, t, βslice, I_H, N_H)
    S_V, E_V, I_V = u
    vec_infection = sum(βslice .* I_H)*S_V/N_H
    vec_mortality = p.μ_V .* u # include S_V, so easier to remove mortality
    vec_births = sum(vec_mortality)
    vec_progression = p.σ_V*E_V

    du[1] = -vec_infection + vec_births
    du[2] = vec_infection - vec_progression
    du[3] = vec_progression
    du[1:end] -= vec_mortality
end

function F(du,u,p,t)
    dH = @view(du[1:(p.nHosts*H_comps)])
    dV = @view(du[(p.nHosts*H_comps+1):end])
    Hs = @view(u[1:(p.nHosts*H_comps)])
    Vs = @view(u[(p.nHosts*H_comps+1):end])

    I_Vs = @view(Vs[3:V_comps:V_comps*p.nVecs])
    I_Hs = @view(Hs[3:H_comps:H_comps*p.nHosts])

    for host in 0:(p.nHosts-1)
        slice = (1:H_comps).+(H_comps*host)
        F1H(@view(dH[slice]), @view(Hs[slice]), p.host[host+1], t, @view(p.β[host+1,:]), I_Vs, p.N_H)
    end
    for vec in 0:(p.nVecs-1)
        slice = (1:V_comps).+(V_comps*vec)
        F1V(@view(dV[slice]), @view(Vs[slice]), p.vec[vec+1], t, @view(p.β[:,vec+1]), I_Hs, p.N_H)
    end
end

nH = 2
nV = 2
srand(0)

S_Hs = ones(nH) .* 100.0
E_Hs = zeros(nH)
I_Hs = shuffle(vcat(zeros(nH-1),[1.0]))
R_Hs = zeros(nH)
host0 = reshape(hcat(S_Hs,E_Hs,I_Hs,R_Hs)', nH*H_comps, 1)

S_Vs = ones(nV) .* 1000.0
E_Vs = zeros(nV)
I_Vs = zeros(nV)
vec0 = reshape(hcat(S_Vs,E_Vs,I_Vs)', nV*V_comps, 1)

u0 = vcat(host0, vec0)

srand(1)

μs = 1 ./ (rand(nH) .* 360)
σs = 1 ./ (rand(nH) .* 6)
μVs = 1 ./ (rand(nV) .* 60)
σVs = 1 ./ (rand(nV) .* 14)

λs = 1 ./ (rand(nH) .* 28)
βs = rand(nH*nV) ./ 10.0

using NamedTuples
# nb: in >= Julia v0.7, can eliminate this import
#  and the @NT syntax
p = @NT(
  nHosts = nH, nVecs = nV,
  N_H = sum(host0),
  β = reshape(βs,nH,nV), # information in hosts (rows) by vectors (cols)
  vec  = [@NT(μ_V=μVs[j], σ_V=σVs[j]) for j in 1:nV],
  host = [@NT(μ_H=μs[i], σ_H=σs[i], λ=λs[i]) for i in 1:nH]
  # just building up a random collection of params for demonstration
)

tspan = (0.0, 365.0)
prob = ODEProblem(F, u0, tspan, p)
sol = solve(prob,Tsit5(),reltol=1e-8,abstol=1e-8,saveat=linspace(0,365,365*10+1))

# rename!(df, Dict(:timestamp => :t,
#  :value1 => :S_H, :value2 => :E_H, :value3 => :I_H, :value4 => :R_H,
#  :value5 => :S_V, :value6 => :E_V, :value7 => :I_V
# ))
# mlt[:host] = contains.(string.(mlt[:variable]),"H"); # tag which entries are host vs vector
# df
df = DataFrame(sol)
mlt = melt(df,:timestamp) # convert results into long format for plotting
mlt[:index] = parse.(Int,replace.(string.(mlt[:variable]),r"[^\d]+"=>""))
namekey = hcat(
  reshape(["$(compartment)_H$species" for compartment in ["S","E","I","R"], species in 1:nH],1,:),
  reshape(["$(compartment)_V$species" for compartment in ["S","E","I"], species in 1:nV],1,:)
)

mlt[:name] = namekey[mlt[:index]]
mlt[:facet] = replace.(string.(mlt[:name]),r"\w+_"=>"")
mlt[:compartment] = replace.(string.(mlt[:name]),r"_\w+"=>"")
mlt

# current version RCall supports better transfers, which would simplify this mess
# but requires Julia v >= 0.7
vals = mlt[:value]
tstamps = mlt[:timestamp]
fcts = mlt[:facet]
comps = mlt[:compartment]
@rput vals tstamps fcts comps
R"
library(ggplot2)
suppressPackageStartupMessages(library(data.table))
dt <- data.table(t=tstamps, y=vals, species=fcts, compartment=comps)
ggplot(dt) + aes(x=t, y=y, color=compartment) + facet_grid(species ~ ., scale = 'free_y') +
  theme_minimal() +
  geom_line()
"


function δ(X, discrete = true)
    mn, mx = extrema(Iterators.flatten(X))
    return mx - mn + discrete
end

function δ(X, Y, discrete = true)
    mnx, mxx = extrema(Iterators.flatten(X))
    mny, mxy = extrema(Iterators.flatten(Y))
    return max(mxx, mxy) - min(mnx, mny) + discrete
end

to_domains(domain_sizes::Vector{Int}) = map(ds -> domain(0:ds), domain_sizes)

function to_domains(X, ds::Int = δ(X))
    d = domain(0:ds-1)
    return fill(d, length(first(X)))
end

function to_domains(X, d::D) where {D <: DiscreteDomain}
    n::Int = length(first(X)) / domain_size(d)
    return fill(d, n)
end


##################################################
##
## infix logical operators

## XXX Experimental! Not sure these are such a good idea ...
## but used with piecewise
Base.:&(x::Sym, y::Sym) = x.__and__(y)
Base.:|(x::Sym, y::Sym) = x.__or__(y)
Base.:!(x::Sym)         = x.__invert__()

## use ∨, ∧, ¬ for |,&,! (\vee<tab>, \wedge<tab>, \neg<tab>)
∨(x::Sym, y::Sym) = x | y
∧(x::Sym, y::Sym) = x & y
¬(x::Sym) = !x
export ∨, ∧, ¬

## In SymPy, symbolic equations are not represented by `=` or `==`
## rather ther function `Eq` is used. Here we use the unicode
## `\Equal<tab>` for an infix operator. There are also unicode values to represent analogs of `<`, `<=`, `>=`. `>`. These are

## * `<`  is `\ll<tab>`
## * `<=` is `\leqq<tab>
## * `==` is `\Equal<tab>`
## * `>=` is `\geqq<tab>`
## * `>`  is `\gg<tab>`

export ≪,≦,⩵,≧,≫, ≶, ≷ # from CommonEq


module Day07

using Statistics

import ..data_dir # from parent module
input = read(joinpath(data_dir, "day07"), String)

export part1, part2

"""
The total fuel required to align all crab submarines to `target`
This function accepts an optional `fuel_cost_function` which calculates the fuel
required to move a crab submarine from one location to another. This function
should act on non-negative integer values (ie. absolute difference). The default
is the `identity` function, in which the distance between the two locations is
also the fuel cost.
"""
function fuel_to_align(
    positions::Vector{Int64},
    target::Integer,
    fuel_cost_function::Function = identity
)
    absolute_movements = abs.(positions .- target)
    fuel_costs = fuel_cost_function.(absolute_movements)
    sum(fuel_costs)
end

"""
I'm calling this `brute_force` since it inelegantly searches the entire solution
space for a minimum cost. If this function isn't good enough for part 2 I can
look for leaner search strategies (starting from median position maybe?).

Starting with the assumption that the optimal position must be somewhere between
the minimum and maximum positions, we can check each possible position between
the two to determine the minimum fuel consumption.
"""
function brute_force(positions::Vector{Int64}, fuel_cost_function::Function = identity)
    minimum_fuel = Inf
    for position = minimum(positions):maximum(positions)
        required_fuel = fuel_to_align(positions, position, fuel_cost_function)
        minimum_fuel = min(required_fuel, minimum_fuel)
    end
    Integer(minimum_fuel)
end

function part1(input = input)
    positions = [parse(Int64, position) for position in split(input, ",")]
    brute_force(positions)
end

"""
I think this is an appropriate name? We're summing an arithmetic sequence.
"""
arithmetic_fuel_cost(movement::Int64) = movement * (movement + 1) / 2

function part2(input = input)
    positions = [parse(Int64, position) for position in split(input, ",")]
    brute_force(positions, arithmetic_fuel_cost)
end

end # module


using Test
using Dates

##################################
## Bruteforce version
##################################

function constructSet(line)
    path = []
    x = 0
    y = 0
    instructions = split(line, ",")
    for inst in instructions
        dir = inst[1]
        number = parse(Int, inst[2:end])

        if dir == 'R'
            positions = map(i -> (x, y + i), 1:number)
        elseif dir == 'L'
            positions = map(i -> (x, y - i), 1:number)
        elseif dir == 'U'
            positions = map(i -> (x + i , y), 1:number)
        elseif dir == 'D'
            positions = map(i -> (x - i , y), 1:number)
        end
        x = positions[end][1]
        y = positions[end][2]
        append!(path, positions)
    end
    return path
end

function run(input)
    input = split(input, "\n")
    set1 = constructSet(input[1])
    set2 = constructSet(input[2])
    inter = intersect(Set(set1), Set(set2))
    return  minimum(pos -> abs(pos[1]) + abs(pos[2]), inter)
end

##############################################################
### Another faster version
### We don't store all the points, but only the
### vertices (begin, end) and compute their intersections.
##############################################################

function getVertices(line)
    vertices = []
    (x, y) = (0, 0)
    instructions = split(line, ",")
    for inst in instructions
        dir = inst[1]
        steps = parse(Int, inst[2:end])
        if dir == 'R'
            append!(vertices, [((x, y), (x, y+steps))])
            y = y + steps
        elseif dir == 'L'
            append!(vertices,[((x, y-steps), (x, y))])
            y = y - steps
        elseif dir == 'U'
            append!(vertices, [((x, y), (x + steps, y))])
            x = x + steps
        elseif dir == 'D'
            append!(vertices, [((x - steps, y), (x, y))])
            x = x - steps
        end
    end
    return vertices
end

function getDir(ver)
    (x1, y1), (x2, y2) = ver
    if x1 == x2
        return "LR"
    elseif y1 == y2
        return "UD"
    end
end

@test getDir(((1, 2), (1, 3))) == "LR"
@test getDir(((1, 2), (3, 2))) == "UD"

function getIntersectionVertices(ver1, ver2)
    ((x1, y1), (x2, y2)) = ver1
    ((x3, y3), (x4, y4)) = ver2
    dir1 = getDir(ver1)
    dir2 = getDir(ver2)
    if dir1 == dir2 == "LR"
        if x1 == x3
            return [(x1, y) for y in max(y1, y3):min(y2, y4)]
        else
            return []
        end
    elseif dir1 == dir2 == "UD"
        if y1 == y3
            return [(x, y1) for x in max(x1, x3):min(x2, x4)]
        else
            return []
        end
    elseif dir1 == "UD" && dir2 == "LR"
        if x1 <= x3 <= x2 && y3 <= y1 <= y4
            return [(x3, y1)]
        else
            return []
        end
    elseif dir1 == "LR" && dir2 == "UD"
        # invert the two
        ((x1, y1), (x2, y2)) = ver2
        ((x3, y3), (x4, y4)) = ver1
        if x1 <= x3 <= x2 && y3 <= y1 <= y4
            return [(x3, y1)]
        else
            return []
        end
    end
end

@test getIntersectionVertices(
    ((1, 2), (1, 3)),
    ((1, 2), (1, 4))
) == [(1, 2), (1, 3)]

@test getIntersectionVertices(
    ((1, 2), (1, 7)),
    ((1, 4), (1, 20))
) == [(1, 4), (1, 5), (1, 6),  (1, 7)]

@test getIntersectionVertices(
    ((2, 1), (7, 1)),
    ((4, 1), (20, 1))
) == [(4, 1), (5, 1), (6, 1),  (7, 1)]

@test getIntersectionVertices(
    ((2, 5), (2, 10)),
    ((1, 6), (5, 6))
) == [(2, 6)]

@test getIntersectionVertices(
    ((1, 6), (5, 6)),
    ((2, 5), (2, 10))
) == [(2, 6)]

function getIntersections(vertices1, vertices2)
    intersections = []
    for ver1 in vertices1
        for ver2 in vertices2
            inter = getIntersectionVertices(ver1, ver2)
            append!(intersections, inter)
        end
    end
    return intersections
end

function run2(input)
    input = split(input, "\n")
    vertices1 = getVertices(input[1])
    vertices2 = getVertices(input[2])
    inter = getIntersections(vertices1, vertices2)
    inter = filter(x -> x ≠ (0, 0), inter)
    return minimum(pos -> abs(pos[1]) + abs(pos[2]), inter)
end

@test run2("R75,D30,R83,U83,L12,D49,R71,U7,L72\nU62,R66,U55,R34,D71,R55,D58,R83") == 159
@test run2("R98,U47,R26,D63,R33,U87,L62,D20,R33,U53,R51\nU98,R91,D20,R16,D67,R40,U7,R15,U6,R7") == 135

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

function main()
    input = ARGS[1]
    # input = split(input, "\n")
    res, time, memory = @timed run2(input)
    println("_duration:$(time*1000)")
    println(res)
end

main()


export SpiralTrajectoryDualDensity

"""
    SpiralTrajectoryDualDensity(numProfiles, numSamplingPerProfile
                  ; TE::Float64=0.0
                  , AQ::Float64=1.e-3
                  , windings::Real= 6.25
                  , angleOffset= :equispaced
                  , densityFactor=2.0
                  , kargs...)

returns a 2d spiral trajectory.

# Arguments
* `numProfiles::Int64`            - number of profiles
* `numSamplingPerProfile::Int64`  - number of sampling points per profile
* (`TE::Float64=0.0`)             - echo time in s
* (`AQ::Float64=1.e-3`)           - readout duration in s (per profile)
* (`windings::Real= 6.25`)        - number of windings of the spiral profiles
* (`angleOffset= :equispaced`)    - spacing of profile angles (`:equispaced` sampling, `:golden` angle sampling or `:random` sampling)
"""
function SpiralTrajectoryDualDens(::Type{T}, numProfiles, numSamplingPerProfile
                  ; TE=0.0
                  , AQ=1.e-3
                  , windings::Real= 6.25
                  , kmax::Real=0.5
                  , angleOffset::String="equispaced"
                  , densityFactor::Float64=2.0
                  , kargs...) where T
  nodes = spiralNodesDualDens(T, numProfiles, numSamplingPerProfile; windings=windings,
              angleOffset=angleOffset, densityFactor=densityFactor, kmax=kmax)
  times = readoutTimes(T, numProfiles, numSamplingPerProfile; TE=TE, AQ=AQ)
  return  Trajectory("SpiralDualDens", nodes, times, TE, AQ, numProfiles, numSamplingPerProfile, 1, false, true)
end

function spiralNodesDualDens(::Type{T}, numProfiles::Int64
                            , numSamplingPerProfile::Int64
                            ; windings::Real= 6.25
                            , kmax::Real=0.5
                            , angleOffset::String="equispaced"
                            , densityFactor::Float64
                            , kargs...) where T

  nodes = zeros(T, 2, numSamplingPerProfile, numProfiles)
  if angleOffset == "golden"
      angles = [i*(3-sqrt(5))/2  for i=0:numProfiles-1 ]
  elseif angleOffset == "random"
      angles = sort(rand(numProfiles))
  elseif angleOffset == "equispaced"
      angles = collect((0:numProfiles-1)/numProfiles)
  end

  w = windings
  for l = 1:numProfiles
    for k = 1:numSamplingPerProfile

      t = sqrt((k-1)/(numSamplingPerProfile-1))

      if t < 1/2
        tau = t/densityFactor
      else
        tau = 0.5/densityFactor + (t-0.5)*2*(1.0-0.5/densityFactor)
      end

      nodes[1,k,l] = kmax*tau*cos(2*pi*( w*t + angles[l] ))
      nodes[2,k,l] = kmax*tau*sin(2*pi*( w*t + angles[l] ))
    end
  end
  return reshape(nodes, 2, numSamplingPerProfile*numProfiles)
end


include("./incremental.jl")
include("./kernel.jl")
include("./kernel_system.jl")

using Plots

function get_out_of_bounds(y, ℓ, u)
    for i in length(y)
        if y[i] > u[i] + eps() * 10^4
            return i, u[i]
        elseif y[i] < ℓ[i] - eps() * 10^4
            return i, ℓ[i]
        end
    end
    return 0
end

function eval!(y, kd, y1, y2, x)
    for i in size(x, 2)
        y[i] = predict_point(kd, y1, y2, x[:, i], ones(1, 1))
    end
    return nothing
end

function active_set(k, x, y, ℓ, u)
    n = size(x, 2)
    kd = kernel_data(n, k, x[:, 1:1], ones(1, 1), x[:, n:n], ones(1, 1))
    active = zeros(Bool, n)
    active[1] = active[n] = true
    yhat = Array{Float64}(undef, n)
    eval!(yhat[.!active], kd, y[1], y[2:end][active[2:end]], x[:, .!active])
    out_of_bounds = get_out_of_bounds(yhat[.!active], ℓ[.!active], u[.!active])

    while out_of_bounds[1] >= 0
        i = out_of_bounds[1]
        add_point!(kd, x[:, .!active][:, i], ones(1))
        y[.!active][i] = out_of_bounds[2]
        active[.!active][i] = true
        eval!(yhat[.!active], kd, y[1], y[2:end][active[2:end]], x[:, .!active])
        out_of_bounds = get_out_of_bounds(yhat, ℓ, u)
    end
    return active, yhat
end

k = FixedParam(RBF(), 0.05)
x = collect(-1:0.1:1)
x = reshape(x, 1, length(x))

f(t) = exp(-t ^ 2) * cospi(2t)
y = f.(x)
y = reshape(y, length(y))
plt = plot([x' y])
savefig(plt, "base.png")

ℓ = y .- 10
u = y .+ 10
active, yhat = active_set(k, x, y, ℓ, u)
plot!(plt, [x yhat])
scatter!(plt, [x[active] yhat[active]])
savefig(plt, "plot1.png")

ℓ = y .- 0.2
u = y .+ 0.2
active, yhat = active_set(k, x, y, ℓ, u)
plot!(plt, [x yhat])
scatter!(plt, [x[active] yhat[active]])
savefig(plt, "plot2.png")

ℓ = y .- 0.1
u = y .+ 0.1
active, yhat = active_set(k, x, y, ℓ, u)
plot!(plt, [x yhat])
scatter!(plt, [x[active] yhat[active]])
savefig(plt, "plot3.png")


### A Pluto.jl notebook ###
# v0.16.4

using Markdown
using InteractiveUtils

# ╔═╡ 03c25682-4e46-11eb-1b97-33699faa080f
begin
	import Pkg
	Pkg.activate(Base.current_project())
	using ReinforcementLearning
	using Plots
	using Flux
	using Statistics
end

# ╔═╡ 1290b1fa-4e4d-11eb-111b-37ade59c8f30
md"""
# Chapter06 Temporal-Difference Learning (Cliff Walking)
"""

# ╔═╡ 0f4a2ba0-4e4d-11eb-0134-5d4fb9897b33
md"""
In Example 6.6, a gridworld example of **Cliff Walking** is introduced to compare the difference between on-policy (SARSA) and off-policy (Q-learning). Although there's a package of [GridWorlds.jl](https://github.com/JuliaReinforcementLearning/GridWorlds.jl) dedicated to 2-D environments, we decide to write an independent implementation here as a showcase.
"""

# ╔═╡ 63a013f0-4e4e-11eb-2737-ff5104b993f2
begin
	const NX = 4
	const NY = 12
	const Start = CartesianIndex(4, 1)
	const Goal = CartesianIndex(4, 12)
	const LRUD = [
		CartesianIndex(0, -1),  # left
		CartesianIndex(0, 1),   # right
		CartesianIndex(-1, 0),  # up
		CartesianIndex(1, 0),   # down
	]
	const LinearInds = LinearIndices((NX, NY))
end

# ╔═╡ 89c9010e-4e4e-11eb-2d97-05ee771fd5f4
function iscliff(p::CartesianIndex{2})
    x, y = Tuple(p)
    x == 4 && y > 1 && y < NY
end

# ╔═╡ eb5e16fc-4e4e-11eb-0ce7-5b5510c7924a
# take a look at the wordmap
heatmap((!iscliff).(CartesianIndices((NX, NY))); yflip = true)

# ╔═╡ 4e8e67ac-4e51-11eb-0742-ad46e685a921
begin
	Base.@kwdef mutable struct CliffWalkingEnv <: AbstractEnv
		position::CartesianIndex{2} = Start
	end
	function (env::CliffWalkingEnv)(a::Int)
		x, y = Tuple(env.position + LRUD[a])
		env.position = CartesianIndex(min(max(x, 1), NX), min(max(y, 1), NY))
	end
end

# ╔═╡ af4da300-4e51-11eb-3821-0522243eea6a
RLBase.state(env::CliffWalkingEnv) = LinearInds[env.position]

# ╔═╡ 94e72e6e-4e51-11eb-25e4-81c253969de6
RLBase.state_space(env::CliffWalkingEnv) = Base.OneTo(length(LinearInds))

# ╔═╡ be36d904-4e51-11eb-1ead-1d89513b68e5
RLBase.action_space(env::CliffWalkingEnv) = Base.OneTo(length(LRUD))

# ╔═╡ e087f574-4e51-11eb-1abf-795bdc85f7b1
RLBase.reward(env::CliffWalkingEnv) = env.position == Goal ? 0.0 : (iscliff(env.position) ? -100.0 : -1.0)

# ╔═╡ f97f149a-4e51-11eb-22d9-f9ec9ed837a8
RLBase.is_terminated(env::CliffWalkingEnv) = env.position == Goal || iscliff(env.position)

# ╔═╡ 8e26fbea-4e51-11eb-3ae8-312009b7c7c1
RLBase.reset!(env::CliffWalkingEnv) = env.position = Start

# ╔═╡ 3d8ee702-4e52-11eb-031a-bf64267362d3
world = CliffWalkingEnv()

# ╔═╡ 30b3604c-4e52-11eb-277f-d5c354e9ec66
md"""
Now we have a workable environment. Next we create several factories to generate different policies for comparison.
"""

# ╔═╡ fd5eb880-4e52-11eb-0c30-618e1e9f8f17
begin
	NS = length(state_space(world))
	NA = length(action_space(world))
end

# ╔═╡ 5da833fc-4e52-11eb-3f96-1d129eb12a00
create_agent(α, method) = Agent(
	policy = QBasedPolicy(
		learner=TDLearner(
			approximator=TabularQApproximator(
				;n_state=NS,
				n_action=NA,
				opt=Descent(α),
			),
			method=method,
			γ=1.0,
			n=0
		),
		explorer=EpsilonGreedyExplorer(0.1)
	),
	trajectory=VectorSARTTrajectory()
)

# ╔═╡ 95bf52b0-4e53-11eb-1740-2fff3b8a9fbe
function repeated_run(α, method, N, n_episode, is_mean=true)
	env = CliffWalkingEnv()
	rewards = []
	for _ in 1:N
		h = TotalRewardPerEpisode(;is_display_on_exit=false)
		run(
			create_agent(α, method),
			env,
			StopAfterEpisode(n_episode;is_show_progress=false),
			h
		)
		push!(rewards, is_mean ? mean(h.rewards) : h.rewards)
	end
	mean(rewards)
end

# ╔═╡ 33ab57f2-4e55-11eb-3b53-89492ca4b5f6
begin
	p = plot(legend=:bottomright, xlabel="Episodes", ylabel="Sum of rewards during episode")
	plot!(p, repeated_run(0.5, :SARS, 1000, 500, false), label="QLearning")
	plot!(p, repeated_run(0.5, :SARSA, 1000, 500, false), label="SARSA")
	p
end

# ╔═╡ a9f81264-4e5b-11eb-2634-cb2179ea9e6b
begin
	A = 0.1:0.05:0.95
	fig_6_3 = plot(;legend=:bottomright, xlabel="α", ylabel="Sum of rewards per episode")

	plot!(fig_6_3, A, [repeated_run(α, :SARS, 100, 100) for α in A], linestyle=:dash ,markershape=:rect, label="Interim Q")
	plot!(fig_6_3, A, [repeated_run(α, :SARSA, 100, 100) for α in A], linestyle=:dash, markershape=:dtriangle, label="Interim SARSA")
	plot!(fig_6_3, A, [repeated_run(α, :ExpectedSARSA, 100, 100) for α in A], linestyle=:dash, markershape=:cross, label="Interim ExpectedSARSA")

	plot!(fig_6_3, A, [repeated_run(α, :SARS, 10, 5000) for α in A], linestyle=:solid ,markershape=:rect, label="Asymptotic interim Q")
	plot!(fig_6_3, A, [repeated_run(α, :SARSA, 10, 5000) for α in A], linestyle=:solid, markershape=:dtriangle, label="Asymptotic SARSA")
	plot!(fig_6_3, A, [repeated_run(α, :ExpectedSARSA, 10, 5000) for α in A], linestyle=:solid, markershape=:cross, label="Asymptotic ExpectedSARSA")
	fig_6_3
end

# ╔═╡ Cell order:
# ╟─1290b1fa-4e4d-11eb-111b-37ade59c8f30
# ╠═03c25682-4e46-11eb-1b97-33699faa080f
# ╟─0f4a2ba0-4e4d-11eb-0134-5d4fb9897b33
# ╠═63a013f0-4e4e-11eb-2737-ff5104b993f2
# ╠═89c9010e-4e4e-11eb-2d97-05ee771fd5f4
# ╠═eb5e16fc-4e4e-11eb-0ce7-5b5510c7924a
# ╠═4e8e67ac-4e51-11eb-0742-ad46e685a921
# ╠═af4da300-4e51-11eb-3821-0522243eea6a
# ╠═94e72e6e-4e51-11eb-25e4-81c253969de6
# ╠═be36d904-4e51-11eb-1ead-1d89513b68e5
# ╠═e087f574-4e51-11eb-1abf-795bdc85f7b1
# ╠═f97f149a-4e51-11eb-22d9-f9ec9ed837a8
# ╠═8e26fbea-4e51-11eb-3ae8-312009b7c7c1
# ╠═3d8ee702-4e52-11eb-031a-bf64267362d3
# ╟─30b3604c-4e52-11eb-277f-d5c354e9ec66
# ╠═fd5eb880-4e52-11eb-0c30-618e1e9f8f17
# ╠═5da833fc-4e52-11eb-3f96-1d129eb12a00
# ╠═95bf52b0-4e53-11eb-1740-2fff3b8a9fbe
# ╠═33ab57f2-4e55-11eb-3b53-89492ca4b5f6
# ╠═a9f81264-4e5b-11eb-2634-cb2179ea9e6b


#####################
# STATIC SMALL GRAPHS
#####################

"""
    smallgraph(s)
    smallgraph(s)

Create a small graph of type `s`. Admissible values for `s` are:

| `s`                       | graph type                       |
|:------------------------|:---------------------------------|
| :bull                       | A [bull graph](https://en.wikipedia.org/wiki/Bull_graph).  |
| :chvatal                    | A [Chvátal graph](https://en.wikipedia.org/wiki/Chvátal_graph). |
| :cubical                    | A [Platonic cubical graph](https://en.wikipedia.org/wiki/Platonic_graph). |
| :desargues                |   A [Desarguesgraph](https://en.wikipedia.org/wiki/Desargues_graph).|
| :diamond                  |   A [diamond graph](http://en.wikipedia.org/wiki/Diamond_graph). |
| :dodecahedral             |   A [Platonic dodecahedral  graph](https://en.wikipedia.org/wiki/Platonic_graph). |
| :frucht                   |   A [Frucht graph](https://en.wikipedia.org/wiki/Frucht_graph). |
| :heawood                  |   A [Heawood graph](https://en.wikipedia.org/wiki/Heawood_graph). |
| :house                    |   A graph mimicing the classic outline of a house. |
| :housex                   |   A house graph, with two edges crossing the bottom square. |
| :icosahedral              |   A [Platonic icosahedral   graph](https://en.wikipedia.org/wiki/Platonic_graph). |
| :karate                   |   A social network graph called [Zachary's karate club](https://en.wikipedia.org/wiki/Zachary%27s_karate_club). |
| :krackhardtkite           |   A [Krackhardt-Kite social network  graph](http://mathworld.wolfram.com/KrackhardtKite.html). |
| :moebiuskantor            |   A [Möbius-Kantor graph](http://en.wikipedia.org/wiki/Möbius–Kantor_graph). |
| :octahedral               |   A [Platonic octahedral graph](https://en.wikipedia.org/wiki/Platonic_graph).
| :pappus                   |   A [Pappus graph](http://en.wikipedia.org/wiki/Pappus_graph). |
| :petersen                 |   A [Petersen graph](http://en.wikipedia.org/wiki/Petersen_graph). |
| :sedgewickmaze            |   A simple maze graph used in Sedgewick's *Algorithms in C++: Graph  Algorithms (3rd ed.)* |
| :tetrahedral              |   A [Platonic tetrahedral  graph](https://en.wikipedia.org/wiki/Platonic_graph). |
| :truncatedcube            |   A skeleton of the [truncated cube graph](https://en.wikipedia.org/wiki/Truncated_cube). |
| :truncatedtetrahedron     |   A skeleton of the [truncated tetrahedron  graph](https://en.wikipedia.org/wiki/Truncated_tetrahedron). |
| :truncatedtetrahedron_dir |   A skeleton of the [truncated tetrahedron digraph](https://en.wikipedia.org/wiki/Truncated_tetrahedron). |
| :tutte                    |   A [Tutte graph](https://en.wikipedia.org/wiki/Tutte_graph). |
"""
function smallgraph(s::Symbol)
    graphmap = Dict(
    :bull            => BullGraph,
    :chvatal         => ChvatalGraph,
    :cubical         => CubicalGraph,
    :desargues       => DesarguesGraph,
    :diamond         => DiamondGraph,
    :dodecahedral    => DodecahedralGraph,
    :frucht          => FruchtGraph,
    :heawood         => HeawoodGraph,
    :house           => HouseGraph,
    :housex          => HouseXGraph,
    :icosahedral     => IcosahedralGraph,
    :karate          => KarateGraph,
    :krackhardtkite  => KrackhardtKiteGraph,
    :moebiuskantor   => MoebiusKantorGraph,
    :octahedral      => OctahedralGraph,
    :pappus          => PappusGraph,
    :petersen        => PetersenGraph,
    :sedgewickmaze   => SedgewickMazeGraph,
    :tetrahedral     => TetrahedralGraph,
    :truncatedcube   => TruncatedCubeGraph,
    :truncatedtetrahedron        => TruncatedTetrahedronGraph,
    :truncatedtetrahedron_dir    => TruncatedTetrahedronDiGraph,
    :tutte           => TutteGraph
    )

    if (s in keys(graphmap))
        return graphmap[s]()
    end
    throw(ArgumentError("$s is not a valid graph"))
end

function smallgraph(s::AbstractString)
    ls = lowercase(s)
    if endswith(ls, "graph")
        ls = replace(ls, "graph" => "")
    end

    return smallgraph(Symbol(ls))
end

DiamondGraph() = SimpleGraph( SimpleEdge.([(1, 2), (1, 3), (2, 3), (2, 4), (3, 4)]) )

BullGraph() = SimpleGraph(SimpleEdge.([(1, 2), (1, 3), (2, 3), (2, 4), (3, 5)]))

function ChvatalGraph()
    e = SimpleEdge.([
    (1, 2), (1, 5), (1, 7), (1, 10),
    (2, 3), (2, 6), (2, 8),
    (3, 4), (3, 7), (3, 9),
    (4, 5), (4, 8), (4, 10),
    (5, 6), (5, 9),
    (6, 11), (6, 12),
    (7, 11), (7, 12),
    (8, 9), (8, 12),
    (9, 11),
    (10, 11), (10, 12)
   ])
    return SimpleGraph(e)
end

function CubicalGraph()
    e = SimpleEdge.([
    (1, 2), (1, 4), (1, 5),
    (2, 3), (2, 8),
    (3, 4), (3, 7),
    (4, 6), (5, 6), (5, 8),
    (6, 7),
    (7, 8)
    ])
    return SimpleGraph(e)
end

function DesarguesGraph()
    e = SimpleEdge.([
    (1, 2), (1, 6), (1, 20),
    (2, 3), (2, 17),
    (3, 4), (3, 12),
    (4, 5), (4, 15),
    (5, 6), (5, 10),
    (6, 7),
    (7, 8), (7, 16),
    (8, 9), (8, 19),
    (9, 10), (9, 14),
    (10, 11),
    (11, 12), (11, 20),
    (12, 13),
    (13, 14), (13, 18),
    (14, 15),
    (15, 16),
    (16, 17),
    (17, 18),
    (18, 19),
    (19, 20)
   ])
    return SimpleGraph(e)
end

function DodecahedralGraph()
    e = SimpleEdge.([
    (1, 2), (1, 11), (1, 20),
    (2, 3), (2, 9),
    (3, 4), (3, 7),
    (4, 5), (4, 20),
    (5, 6), (5, 18),
    (6, 7), (6, 16),
    (7, 8),
    (8, 9), (8, 15),
    (9, 10),
    (10, 11), (10, 14),
    (11, 12),
    (12, 13), (12, 19),
    (13, 14), (13, 17),
    (14, 15),
    (15, 16),
    (16, 17),
    (17, 18),
    (18, 19),
    (19, 20)
    ])
    return SimpleGraph(e)
end

function FruchtGraph()
    e = SimpleEdge.([
    (1, 2), (1, 7), (1, 8),
    (2, 3), (2, 8),
    (3, 4), (3, 9),
    (4, 5), (4, 10),
    (5, 6), (5, 10),
    (6, 7), (6, 11),
    (7, 11),
    (8, 12),
    (9, 10), (9, 12),
    (11, 12)
    ])
    return SimpleGraph(e)
end

function HeawoodGraph()
    e = SimpleEdge.([
    (1, 2), (1, 6), (1, 14),
    (2, 3), (2, 11),
    (3, 4), (3, 8),
    (4, 5), (4, 13),
    (5, 6), (5, 10),
    (6, 7),
    (7, 8), (7, 12),
    (8, 9),
    (9, 10), (9, 14),
    (10, 11),
    (11, 12),
    (12, 13),
    (13, 14)
    ])
    return SimpleGraph(e)
end

function HouseGraph()
    e = SimpleEdge.([(1, 2), (1, 3), (2, 4), (3, 4), (3, 5), (4, 5)])
    return SimpleGraph(e)
end

function HouseXGraph()
    g = HouseGraph()
    add_edge!(g, SimpleEdge(1, 4))
    add_edge!(g, SimpleEdge(2, 3))
    return g
end

function IcosahedralGraph()
    e = SimpleEdge.([
    (1, 2), (1, 6), (1, 8), (1, 9), (1, 12),
    (2, 3), (2, 6), (2, 7), (2, 9),
    (3, 4), (3, 7), (3, 9), (3, 10),
    (4, 5), (4, 7), (4, 10), (4, 11),
    (5, 6), (5, 7), (5, 11), (5, 12),
    (6, 7), (6, 12),
    (8, 9), (8, 10), (8, 11), (8, 12),
    (9, 10),
    (10, 11), (11, 12)
    ])
    return SimpleGraph(e)
end

function KarateGraph()
    e = SimpleEdge.([
         (1, 2),   (1, 3),   (1, 4),   (1, 5),   (1, 6),   (1, 7),
         (1, 8),   (1, 9),   (1, 11),  (1, 12),  (1, 13),  (1, 14),
         (1, 18),  (1, 20),  (1, 22),  (1, 32),  (2, 3),   (2, 4),
         (2, 8),   (2, 14),  (2, 18),  (2, 20),  (2, 22),  (2, 31),
         (3, 4),   (3, 8),   (3, 9),   (3, 10),  (3, 14),  (3, 28),
         (3, 29),  (3, 33),  (4, 8),   (4, 13) , (4, 14),  (5, 7),
         (5, 11),  (6, 7),   (6, 11),  (6, 17) , (7, 17),  (9, 31),
         (9, 33),  (9, 34),  (10, 34), (14, 34), (15, 33), (15, 34),
         (16, 33), (16, 34), (19, 33), (19, 34), (20, 34), (21, 33),
         (21, 34), (23, 33), (23, 34), (24, 26), (24, 28), (24, 30),
         (24, 33), (24, 34), (25, 26), (25, 28), (25, 32), (26, 32),
         (27, 30), (27, 34), (28, 34), (29, 32), (29, 34), (30, 33),
         (30, 34), (31, 33), (31, 34), (32, 33), (32, 34), (33, 34),
    ])
    return SimpleGraph(e)
end

function KrackhardtKiteGraph()
    e = SimpleEdge.([
    (1, 2), (1, 3), (1, 4), (1, 6),
    (2, 4), (2, 5), (2, 7),
    (3, 4), (3, 6),
    (4, 5), (4, 6), (4, 7),
    (5, 7),
    (6, 7), (6, 8),
    (7, 8),
    (8, 9),
    (9, 10)
    ])
    return SimpleGraph(e)
end

function MoebiusKantorGraph()
    e = SimpleEdge.([
    (1, 2), (1, 6), (1, 16),
    (2, 3), (2, 13),
    (3, 4), (3, 8),
    (4, 5), (4, 15),
    (5, 6), (5, 10),
    (6, 7),
    (7, 8), (7, 12),
    (8, 9),
    (9, 10), (9, 14),
    (10, 11),
    (11, 12), (11, 16),
    (12, 13),
    (13, 14),
    (14, 15),
    (15, 16)
    ])
    return SimpleGraph(e)
end

function OctahedralGraph()
    e = SimpleEdge.([
    (1, 2), (1, 3), (1, 4), (1, 5),
    (2, 3), (2, 4), (2, 6),
    (3, 5), (3, 6),
    (4, 5), (4, 6),
    (5, 6)
    ])
    return SimpleGraph(e)
end

function PappusGraph()
    e = SimpleEdge.([
    (1, 2), (1, 6), (1, 18),
    (2, 3), (2, 9),
    (3, 4), (3, 14),
    (4, 5), (4, 11),
    (5, 6), (5, 16),
    (6, 7),
    (7, 8), (7, 12),
    (8, 9), (8, 15),
    (9, 10),
    (10, 11), (10, 17),
    (11, 12),
    (12, 13),
    (13, 14), (13, 18),
    (14, 15),
    (15, 16),
    (16, 17),
    (17, 18)
    ])
    return SimpleGraph(e)
end

function PetersenGraph()
    e = SimpleEdge.([
    (1, 2), (1, 5), (1, 6),
    (2, 3), (2, 7),
    (3, 4), (3, 8),
    (4, 5), (4, 9),
    (5, 10),
    (6, 8), (6, 9),
    (7, 9), (7, 10),
    (8, 10)
    ])
    return SimpleGraph(e)
end

function SedgewickMazeGraph()
    e = SimpleEdge.([
    (1, 3),
    (1, 6), (1, 8),
    (2, 8),
    (3, 7),
    (4, 5), (4, 6),
    (5, 6), (5, 7), (5, 8)
    ])
    return SimpleGraph(e)
end

TetrahedralGraph() =
SimpleGraph(SimpleEdge.([(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]))

function TruncatedCubeGraph()
    e = SimpleEdge.([
    (1, 2), (1, 3), (1, 5),
    (2, 12), (2, 15),
    (3, 4), (3, 5),
    (4, 7), (4, 9),
    (5, 6),
    (6, 17), (6, 19),
    (7, 8), (7, 9),
    (8, 11), (8, 13),
    (9, 10),
    (10, 18), (10, 21),
    (11, 12), (11, 13),
    (12, 15), (13, 14),
    (14, 22), (14, 23),
    (15, 16),
    (16, 20), (16, 24),
    (17, 18), (17, 19),
    (18, 21),
    (19, 20),
    (20, 24),
    (21, 22),
    (22, 23),
    (23, 24)
    ])
    return SimpleGraph(e)
end

function TruncatedTetrahedronGraph()
    e = SimpleEdge.([
    (1, 2), (1, 3), (1, 10),
    (2, 3), (2, 7),
    (3, 4),
    (4, 5), (4, 12),
    (5, 6), (5, 12),
    (6, 7), (6, 8),
    (7, 8),
    (8, 9),
    (9, 10), (9, 11),
    (10, 11),
    (11, 12)
    ])
    return SimpleGraph(e)
end

function TruncatedTetrahedronDiGraph()
    e = SimpleEdge.([
    (1, 2), (1, 3), (1, 10),
    (2, 3), (2, 7),
    (3, 4),
    (4, 5), (4, 12),
    (5, 6), (5, 12),
    (6, 7), (6, 8),
    (7, 8),
    (8, 9),
    (9, 10), (9, 11),
    (10, 11),
    (11, 12)
    ])
    return SimpleDiGraph(e)
end

function TutteGraph()
    e = SimpleEdge.([
    (1, 2), (1, 3), (1, 4),
    (2, 5), (2, 27),
    (3, 11), (3, 12),
    (4, 19), (4, 20),
    (5, 6), (5, 34),
    (6, 7), (6, 30),
    (7, 8), (7, 28),
    (8, 9), (8, 15),
    (9, 10), (9, 39),
    (10, 11), (10, 38),
    (11, 40),
    (12, 13), (12, 40),
    (13, 14), (13, 36),
    (14, 15), (14, 16),
    (15, 35),
    (16, 17), (16, 23),
    (17, 18), (17, 45),
    (18, 19), (18, 44),
    (19, 46),
    (20, 21), (20, 46),
    (21, 22), (21, 42),
    (22, 23), (22, 24),
    (23, 41),
    (24, 25), (24, 28),
    (25, 26), (25, 33),
    (26, 27), (26, 32),
    (27, 34),
    (28, 29),
    (29, 30), (29, 33),
    (30, 31),
    (31, 32), (31, 34),
    (32, 33),
    (35, 36), (35, 39),
    (36, 37),
    (37, 38), (37, 40),
    (38, 39),
    (41, 42), (41, 45),
    (42, 43),
    (43, 44), (43, 46),
    (44, 45)
    ])
    return SimpleGraph(e)
end


module MatrixMarket

export mmread

"""
### mmread(filename, infoonly::Bool=false)

Read the contents of the Matrix Market file 'filename' into a matrix,
which will be either sparse or dense, depending on the Matrix Market format
indicated by 'coordinate' (coordinate sparse storage), or 'array' (dense
array storage).

If infoonly is true (default: false), only information on the size and
structure is returned from reading the header. The actual data for the
matrix elements are not parsed.
"""
function mmread(filename, infoonly::Bool=false)
    mmfile = open(filename,"r")
    # Read first line
    firstline = chomp(readline(mmfile))
    tokens = split(firstline)
    if length(tokens) != 5
        throw(ParseError(string("Not enough words on first line: ", ll)))
    end
    if tokens[1] != "%%MatrixMarket"
        throw(ParseError(string("Not a valid MatrixMarket header:", ll)))
    end
    (head1, rep, field, symm) = map(lowercase, tokens[2:5])
    if head1 != "matrix"
        throw(ParseError("Unknown MatrixMarket data type: $head1 (only \"matrix\" is supported)"))
    end

    eltype = field == "real" ? Float64 :
             field == "complex" ? Complex128 :
             field == "pattern" ? Bool :
             throw(ParseError("Unsupported field $field (only real and complex are supported)"))

    symlabel = symm == "general" ? identity :
               symm == "symmetric" ? symmetric! :
               symm == "hermitian" ? hermitian! :
               symm == "skew-symmetric" ? skewsymmetric! :
               throw(ParseError("Unknown matrix symmetry: $symm (only general, symmetric, skew-symmetric and hermitian are supported)"))

    # Skip all comments and empty lines
    ll   = readline(mmfile)
    while length(chomp(ll))==0 || (length(ll) > 0 && ll[1] == '%')
        ll = readline(mmfile)
    end
    # Read matrix dimensions (and number of entries) from first non-comment line
    dd = map(parseint, split(ll))
    if length(dd) < (rep == "coordinate" ? 3 : 2)
        throw(ParseError(string("Could not read in matrix dimensions from line: ", ll)))
    end
    rows = dd[1]
    cols = dd[2]
    entries = (rep == "coordinate") ? dd[3] : (rows * cols)
    if infoonly
        return (rows, cols, entries, rep, field, symm)
    end
    if rep == "coordinate"
        rr = Array(Int, entries)
        cc = Array(Int, entries)
        xx = Array(eltype, entries)
        for i in 1:entries
            flds = split(readline(mmfile))
            rr[i] = parseint(flds[1])
            cc[i] = parsefloat(flds[2])
            if eltype == Complex128
                xx[i] = Complex128(parsefloat(flds[3]), parsefloat(flds[4]))
            elseif eltype == Float64
                xx[i] = parsefloat(flds[3])
            else
                xx[i] = true
            end
        end
        return symlabel(sparse(rr, cc, xx, rows, cols))
    end
    return symlabel(reshape([parsefloat(readline(mmfile)) for i in 1:entries], (rows,cols)))
end

# Hack to represent skew-symmetric matrix as an ordinary matrix with duplicated elements
function skewsymmetric!(M::AbstractMatrix)
    m,n = size(M)
    m == n || throw(DimensionMismatch())
    for i=1:n, j=1:n
        if M[i,j] != 0
            M[j,i] = -M[i,j]
        end
    end
    return M
end

function symmetric!(M::AbstractMatrix)
    m,n = size(M)
    m == n || throw(DimensionMismatch())
    for i=1:n, j=1:n
        if M[i,j] != 0
            M[j,i] = M[i,j]
        end
    end
    return M
end

function hermitian!(M::AbstractMatrix)
    m,n = size(M)
    m == n || throw(DimensionMismatch())
    for i=1:n, j=1:n
        if M[i,j] != 0
            M[j,i] = conj(M[i,j])
        end
    end
    return M
end

end # module


using LinearAlgebraicRepresentation
Lar = LinearAlgebraicRepresentation
"""
This function check the presence of columns with an odd number of elements equal to 1 on the coboundary 1 matrix
"""
function check_odd_elements_columns_CSC(A::Lar.SparseMatrixCSC)#test HOMEWORK2
	sleep_time = 5 #seconds
	temp = 0
	result = []
	for i in range(1,length=size(A,2))
		push!(result, temp)
		temp = 0
		for j in range(1, length=size(A,1))
	        temp+=abs(A[j,i])
		end
	#println(temp)#show column sums
	end
	count = 0
	for k in result
		if isodd(k)
			count+=1
		end
	end
	if count!=0
		println("//////////////////////////////////////////////////////////////////////////////////////////////////////////")
		println("CHECKING COLUMNS WITH AN ODD NUMBER OF ELEMENTS")
		println("WARNING, THE MATRIX CONTAINS $count COLUMNS WITH AN ODD NUMBER OF ELEMENTS")
		println("the execution will sleep for $sleep_time seconds")
		println("//////////////////////////////////////////////////////////////////////////////////////////////////////////")
		sleep(sleep_time)
	end
end


# """
#     MCSampling

# Sampling method for the Monte Carlo integration.
# """
abstract type MCSampling{T} end
Base.eltype(::MCSampling{T}) where T = eltype(T)

"""
    UniformSampling(a, b)

Uniform sampling for the Monte Carlo integration, in the hypercube `[a, b]^2`.
"""
struct UniformSampling{A} <: MCSampling{A}
    a::A
    b::A
end
@functor UniformSampling

function (mc_sample::UniformSampling{T})(x_mc, kwargs...) where T
    Tel = eltype(T)
    rand!(x_mc)
    m = (mc_sample.b + mc_sample.a) ./ convert(Tel,2)
    x_mc .= (x_mc .- convert(Tel,0.5)) .* (mc_sample.b - mc_sample.a) .+ m
end

"""
    NormalSampling(σ)
    NormalSampling(σ, shifted)

Normal sampling method for the Monte Carlo integration.

# Arguments
* `σ`: the standard devation of the sampling
* `shifted` : if true, the integration is shifted by `x`. Defaults to false.
"""
struct NormalSampling{T} <: MCSampling{T}
    σ::T
    shifted::Bool # if true, we shift integration by x when invoking mc_sample::MCSampling(x)
end
@functor NormalSampling

NormalSampling(σ) = NormalSampling(σ,false)

function (mc_sample::NormalSampling)(x_mc)
    randn!(x_mc)
    x_mc .*=  mc_sample.σ
end

function (mc_sample::NormalSampling)(x_mc, x)
    mc_sample(x_mc)
    mc_sample.shifted ? x_mc .+= x : nothing
end

struct NoSampling <: MCSampling{Nothing} end

(mc_sample::NoSampling)(x...) = nothing

function _integrate(::MCS) where {MCS <: MCSampling}
    if MCS <: NoSampling
        return false
    else
        return true
    end
end


# Module that exports a few functions that when called return DataCell datasets
module DataGenerator
	import j4pr;
	export iris, quakes, boston, normal2d, fish, fishr, fnoise;

	"""
		iris()

	DataCell version of the `iris` dataset. The `RDatasets` package is required.
	"""
	# Iris dataset
	function iris()
		irisdf = j4pr.rdataset("iris");
		return j4pr.datacell(Array(irisdf[:,1:4])', Array(irisdf[:,5]), name="Iris Dataset")
	end

	"""
		quakes(thresh::Float64=5.0)

	DataCell version of the `quakes` dataset. The `thresh` parameter is used to threshold
	a continuous variable, in this case the quake magnitude to obtain the labels
	(e.g. 0 for < `thresh`, 1 >= `thresh`). The `RDatasets` package is required.
	"""
	# Quakes dataset
	function quakes(thresh=5.0)
		quakedf = j4pr.rdataset("quakes")
		labels = Array(quakedf[:,4].>=thresh)
		return j4pr.datacell(Array(quakedf[:,[1,2,3,5]])', labels, name="Quakes Dataset (threshold >=$thresh)")
	end

	"""
		boston()

	DataCell version of the `boston` dataset. The `RDatasets` package is required.
	"""
	# Boston dataset
	function boston()
		bostondf = j4pr.rdataset("Boston")
		return j4pr.datacell(Array{typeof(1.0)}(bostondf[:,1:13])', Array(bostondf[:,14]), name="Boston Dataset")
	end

	"""
		normal2d(N::Int=100)

	Dataset containing two classes and two variables  with `N` samples (default `100`), normally distributed
	so that the class distributions look perpendicular and overlapping; the mean vectors for each class
	and variable vary a little each time a new dataset is generated.
	"""
	# Two gaussians
	function normal2d(N::Int=100)
		N0 = round(Int, N/2)
		N1 = N - N0
		X = [	randn(N0)+round.(rand([1,2,3])) randn(N0)+round.(rand([1,2,3])); 		# class 0
       			5*randn(N1)+round.(rand([4,5,8])) 0.1*randn(N1)+round.(rand([1,2,3])); 		# class 1
		]'
		y = [zeros(N0);ones(N1)]
		order = shuffle(1:N)
		return j4pr.datacell(X[:,order], y[order], name="Two 2-D Gaussians")
	end

	"""
		fish(N::Int=100, noise::Float64=0.1)

	The `Fish dataset` is a 2-D dataset portraying a fish which represents one class, with the background as other class; noise
	can be added. `N` is the size of the grid (default `100`, e.g. grid of 100×100 points) and `noise` (default `0.1`) is the
	fraction of samples with their labels flipped (e.g. noisy).

	Dataset based on the "Fish data" from `Kuncheva L. "Combining Pattern Classifiers", 2-nd Ed. ISBN 978-1-118-31523-1`
	"""
	# Fish
	function fish(N::Int=100, noise::Float64=0.01)

		data = zeros(2,N^2)

		# generate the 2-D grid
		lab1 = BitVector(N*N);
		lab2 = BitVector(N*N);
		for (i,y) in enumerate(1:N), (j,x) in enumerate(1:N)
			xn = x/N; yn = y/N;
			lab1[(i-1)*N+j] = xn^3 -2xn*yn + 1.6yn^2 < 0.4
			lab2[(i-1)*N+j] = -xn^3 + 2*sin(xn)*yn + yn < 0.7
			data[1,(i-1)*N+j] = x	# x dimension 1'st variable
			data[2,(i-1)*N+j] = y	# y dimension 2'nd variable
		end
		l = xor.(lab1, lab2)
		nnoisy = round(Int, N^2*noise)
		if nnoisy > 0.0
			nidx = randperm(N^2)[1:nnoisy] # get indexes of noisy samples
			l[nidx]=.!l[nidx]
		end

		labels = map(x->x ? "fish" : "background", l)
		return j4pr.datacell(data, labels[:], name="Fish Dataset, classification")
	end

	"""
		fishr(N::Int=100, noise::Float64=0.1)

	Version of the `fish` dataset for regression problems.

	Dataset based on the "Fish data" from `Kuncheva L. "Combining Pattern Classifiers", 2-nd Ed. ISBN 978-1-118-31523-1`
	"""
	function fishr(N::Int=100, noise::Float64=0.01)

		data = zeros(2,N^2)

		# generate the 2-D grid
		lab1 = falses(N*N);
		lab2 = falses(N*N);
		for (i,y) in enumerate(1:N), (j,x) in enumerate(1:N)
			xn = x/N; yn = y/N;
			lab1[(i-1)*N+j] = xn^3 -2xn*yn + 1.6yn^2 < 0.4
			lab2[(i-1)*N+j] = -xn^3 + 2*sin(xn)*yn + yn < 0.7
			data[1,(i-1)*N+j] = x	# x dimension 1'st variable
			data[2,(i-1)*N+j] = y	# y dimension 2'nd variable
		end
		l = xor.(lab1, lab2)
		nnoisy = round(Int, N^2*noise)
		labels = map(x->x ? 1.0 : 0.0, l)
		if nnoisy > 0.0
			nidx = randperm(N^2)[1:nnoisy] # get indexes of noisy samples
			labels[nidx]+=rand(nnoisy)
		end

		return j4pr.datacell(data, labels[:], name="Fish Dataset, regression")
	end

	"""
		fnoise(points, f, fnoise)

	Generates a dataset containing the `points` as data and the function `f`
	applied to these points as values. Noise is added through `fnoise`. The
	full formula for data generation is `y = f.(points) + fnoise.(f.(points))`

	# Arguments
	  * `points::AbstractVector` is the vector of points
	  * `f` is a function applied to each point (default `sin`)
	  * `fnoise` is a function applied to each value of the function (default `x->x+rand()`)

	# Examples
	```
	julia> data = DataGenerator.fnoise(0:0.01:pi, x->sin(x), x->x*rand())
	Function #71, with noise, 315 obs, 1 vars, 1 target(s)/obs, 315 distinct values

	julia> data |> x->UnicodePlots.lineplot(collect(x.x),x.y)
	     ┌────────────────────────────────────────┐
	   2 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⢠⡆⢠⠀⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
	     │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⣾⡇⣸⢰⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
	     │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⡆⣇⣿⣧⣿⣾⣿⣴⣇⡄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
	     │⠀⠀⠀⠀⠀⠀⠀⠀⢠⣰⣿⣷⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
	     │⠀⠀⠀⠀⠀⠀⠀⢠⢸⣿⣿⣿⣿⡟⢿⣿⣿⣿⣿⣿⣿⣧⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
	     │⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⢻⣿⡇⢸⣿⣿⣿⣿⣿⣿⣿⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
	     │⠀⠀⠀⠀⠀⠀⣾⣿⣿⣿⣿⢸⢸⠇⠈⠉⡇⢿⣿⣿⣿⣿⣿⣿⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
	     │⠀⠀⠀⠀⠀⢠⣿⣿⣿⣿⢿⠀⢸⠀⠀⠀⠁⠈⠙⠻⠙⢹⣿⣿⣿⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
	     │⠀⠀⠀⠀⣠⣿⣿⣿⡿⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
	     │⠀⠀⠀⢀⣿⣿⠙⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⢻⣿⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
	     │⠀⠀⠀⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⠋⠏⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
	     │⠀⠀⣼⣿⡏⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
	     │⠀⢠⣿⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢷⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
	     │⢀⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠹⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀│
	   0 │⡼⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⡄⠀⠀⠀⠀⠀⠀⠀⠀│
	     └────────────────────────────────────────┘
	     0                                        4

	julia> data = DataGenerator.fnoise(0:0.01:2pi, x->cos(x), x->0)
	Function #59, with noise, 629 obs, 1 vars, 1 target(s)/obs, 629 distinct values

	julia> data |> x->UnicodePlots.lineplot(collect(x.x),x.y)
	      ┌────────────────────────────────────────┐
	    1 │⠉⠓⢦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡴⠋⠉⠀⠀⠀⠀│
	      │⠀⠀⠀⠳⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠞⠀⠀⠀⠀⠀⠀⠀│
	      │⠀⠀⠀⠀⠹⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠏⠀⠀⠀⠀⠀⠀⠀⠀│
	      │⠀⠀⠀⠀⠀⠱⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀│
	      │⠀⠀⠀⠀⠀⠀⢳⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡎⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
	      │⠀⠀⠀⠀⠀⠀⠀⢧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡜⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
	      │⠀⠀⠀⠀⠀⠀⠀⠈⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡸⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
	      │⠤⠤⠤⠤⠤⠤⠤⠤⠼⡤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⢴⠧⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠄│
	      │⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
	      │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢳⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡞⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
	      │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡼⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
	      │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡼⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
	      │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣆⠀⠀⠀⠀⠀⠀⠀⠀⡰⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
	      │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢦⠀⠀⠀⠀⠀⢀⡼⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
	   -1 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⢄⣀⣀⡤⠞⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
	      └────────────────────────────────────────┘
	      0                                        7
	```
	"""
	function fnoise(points::AbstractVector, f::Function=x->x, fnoise::Function=x->x+rand())
		X = points
		y = f.(points)
		y = y + fnoise.(y)
		return j4pr.datacell(X,y, name="Function with noise")
	end

end


using DynamicGrids, Dispersal, Test, Unitful, Dates

using DynamicGrids: applyrule, SimData, extent
using Unitful: d

@testset "exponential growth" begin
    init =  [1.0 4.0 7.0;
             2.0 5.0 8.0;
             3.0 6.0 9.0]

    test1 = [ 1.0  4.0  7.0;
              2.0  5.0  8.0;
              3.0  6.0  9.0]
    test2 = [ 2.0  8.0 14.0;
              4.0 10.0 16.0;
              6.0 12.0 18.0]
    test3 = [ 4.0 16.0 28.0;
              8.0 20.0 32.0;
             12.0 24.0 36.0]

    output = ArrayOutput(init; tspan=1:3)
    rule = Ruleset(ExponentialGrowth(rate=log(2.0), timestep=1))
    sim!(output, rule)

    @test output[1] == test1
    @test output[2] == test2
    @test output[3] == test3

    output = ArrayOutput(init; tspan=1d:1d:3d)
    rule = Ruleset(ExponentialGrowth(rate=log(2.0), timestep=1d); timestep=1d)
    sim!(output, rule)

    @test output[1] == test1
    @test output[2] == test2
    @test output[3] == test3

    output = ArrayOutput(init; tspan=DateTime(2001,1,1):Day(5):DateTime(2001,1,15))
    rule = Ruleset(ExponentialGrowth(rate=log(2.0)/5, timestep=Day(1)); timestep=Day(5))
    sim!(output, rule)

    @test output[1] == test1
    @test output[2] == test2
    @test output[3] == test3
end

@testset "exponential growth with rate from growth map" begin
    init = Float32[1.0 1.0 1.0;
                   1.0 1.0 1.0;
                   1.0 1.0 1.0]

    suit =  log.(Float32[1.0 1.0 2.0;
                         2.0 1.0 0.5;
                         1.0 1.0 0.5])

    output = ArrayOutput(init; tspan=1:3, aux=(suit=suit,))
    output.extent
    rule = Ruleset(ExponentialGrowth(rate=Aux(:suit), timestep=1, nsteps_type=Float32))
    sim!(output, rule)

    @test output[1] == Float32[1.0 1.0 1.0;
                               1.0 1.0 1.0;
                               1.0 1.0 1.0]
    @test output[2] ≈  Float32[1.0 1.0 2.0;
                               2.0 1.0 0.5;
                               1.0 1.0 0.5]
    @test output[3] ≈  Float32[1.0 1.0 4.0;
                               4.0 1.0 0.25;
                               1.0 1.0 0.25]

    @test DynamicGrids.normalise.(output[3], 0.25f0, 4) == Float32[0.2 0.2 1.0;
                                                                   1.0 0.2 0.0;
                                                                   0.2 0.2 0.0]
    @test isinferred(output, rule)
end

@testset "logistic growth" begin

    init =  [1.0 4.0 7.0;
             2.0 5.0 8.0;
             3.0 6.0 9.0]

    test1 = [1.0  4.0  7.0;
             2.0  5.0  8.0;
             3.0  6.0  9.0]

    test2 = [1.81818  5.71429  8.23529;
             3.33333  6.66667  8.88889;
             4.61538  7.5      9.47368]

    test3 = [3.07692  7.27273  9.03226;
             5.0      8.0      9.41176;
             6.31579  8.57143  9.72973]
    rule = Ruleset(LogisticGrowth(rate=log(2.0), carrycap=10, timestep=1))
    output = ArrayOutput(init; tspan=1:3)

    sim!(output, rule)

    @test output[1] == test1
    @test output[2] ≈ test2 atol=1e-4
    @test output[3] ≈ test3 atol=1e-4
end

@testset "logistic growth with rate from a growth map" begin

    init = [1.0 1.0 1.0;
            1.0 1.0 1.0;
            1.0 1.0 1.0]

    test1 = [1.0 1.0 1.0;
             1.0 1.0 1.0;
             1.0 1.0 1.0]

    test2 = [1.0     1.0  1.81818;
             1.81818 1.0  0.5    ;
             1.0     1.0  0.5    ]

    test3 = [1.0      1.0  3.07692;
             3.07692  1.0  0.25   ;
             1.0      1.0  0.25   ]

    suit =  log.([1.0 1.0 2.0;
                  2.0 1.0 0.5;
                  1.0 1.0 0.5])

    output = ArrayOutput(init; tspan=1:3, aux=(suit=suit,))
    rule = Ruleset(LogisticGrowth(rate=Aux(:suit), carrycap=10, timestep=1))
    sim!(output, rule)

    @test output[1] == test1
    @test output[2] ≈ test2 atol=1e-4
    @test output[3] ≈ test3 atol=1e-4

end

@testset "growth map masking" begin
    init =  [1.0 1.0 1.0;
             1.0 1.0 1.0;
             1.0 1.0 1.0]

    suit =  [1.0 1.0 2.0;
             2.0 2.0 0.5;
             1.0 1.0 0.5]

    test1 = [1.0 1.0 1.0;
             1.0 1.0 1.0;
             1.0 1.0 1.0]

    test2 = [0.0 0.0 1.0;
             1.0 1.0 0.0;
             0.0 0.0 0.0]

    output = ArrayOutput(init; tspan=1:3, aux=(suit=suit,))
    maskrule = ThresholdGrowth(rate=Aux(:suit), threshold=1.1)
    ruleset = Ruleset(maskrule)
    data = SimData(extent(output), ruleset)

    @test applyrule(data, maskrule, 5, (1, 1)) === 0
    @test applyrule(data, maskrule, 5, (2, 2)) === 5

    @test applyrule(data, maskrule, 5.0, (1, 1)) === 0.0
    @test applyrule(data, maskrule, 5.0, (2, 2)) === 5.0

    sim!(output, Ruleset(maskrule))

    @test output[1] == test1
    @test output[2] == test2
end


x = 3

y = 2x

typeof(y)

f(x) = 2 + x

f

f(10)

function g(x, y)
    z = x + y
    return z^2
end

g(1, 2)

let s = 0
    for i in 1:10
        s += i    # Equivalent to s = s + i
    end
    s
end

typeof(1:10)

function mysum(n)
    s = 0
    for i in 1:n
        s += i
    end
    return s
end

mysum(100)

a = 3

a < 5

if a < 5
    "small"
else
    "big"
end

v = [1, 2, 3]

typeof(v)

v[2]

v[2] = 10

v2 = [i^2 for i in 1:10]

M = [1 2
     3 4]

typeof(M)

zeros(5, 5)

zeros(Int, 4, 5)

[i + j for i in 1:5, j in 1:6]


# ---
# jupyter:
#   jupytext:
#     formats: ipynb,jl:light
#     text_representation:
#       extension: .jl
#       format_name: light
#       format_version: '1.4'
#       jupytext_version: 1.2.4
#   kernelspec:
#     display_name: Julia 1.1.0
#     language: julia
#     name: julia-1.1
# ---

# # This notebook
#
# _Notes:_ For documentation see <https://gaelforget.github.io/MeshArrays.jl/stable/>, <https://docs.juliadiffeq.org/latest/solvers/ode_solve.html> and <https://en.wikipedia.org/wiki/Displacement_(vector)>

# ## 1. import software

using IndividualDisplacements, MeshArrays, DifferentialEquations, Plots, Statistics
p=dirname(pathof(IndividualDisplacements))
include(joinpath(p,"PlotIndDisp.jl"))

# ## 2. Read gridded variables as `MeshArray`s

# Put grid variables in a dictionary.

# +
mygrid=gcmgrid("llc90_latlon/","ll",1,[(360,178)], [360 178], Float32, read, write)

GridVariables=Dict("XC" => read(mygrid.path*"XC.latlon.data",MeshArray(mygrid,Float32)),
"YC" => read(mygrid.path*"YC.latlon.data",MeshArray(mygrid,Float32)),
"XG" => read(mygrid.path*"XG.data",MeshArray(mygrid,Float32)),
"YG" => read(mygrid.path*"YG.data",MeshArray(mygrid,Float32)),
"DXC" => read(mygrid.path*"DXC.latlon.data",MeshArray(mygrid,Float32)),
"DYC" => read(mygrid.path*"DYC.latlon.data",MeshArray(mygrid,Float32)) );
# -

# Read velocity fields as `MeshArray`s.

# +
using MAT
file = matopen(mygrid.path*"uv_lonlat.mat")
u=read(file, "u")
v=read(file, "v")
close(file)

u=dropdims(mean(u,dims=3),dims=3)
v=dropdims(mean(v,dims=3),dims=3)

u=read(u,MeshArray(mygrid,Float32))
v=read(v,MeshArray(mygrid,Float32));

u[findall(isnan.(u))]=0.0
v[findall(isnan.(v))]=0.0

u0=u; u1=u;
v0=v; v1=v;
# -

# Put velocity fields and time range in a dictionary.

# +
t0=0.0; t1=86400*366*2.0; dt=3600;

u0=u0./GridVariables["DXC"]
u1=u1./GridVariables["DXC"]
v0=v0./GridVariables["DYC"]
v1=v1./GridVariables["DYC"]

uvt = Dict("u0" => u0, "u1" => u1, "v0" => v0, "v1" => v1, "t0" => t0, "t1" => t1, "dt" => dt) ;
# -

# Merge the two dictionaries and add masks

# +
uvetc=merge(uvt,GridVariables);

nr=50; kk=1;

mskW=read(mygrid.path*"hFacW.latlon.data",MeshArray(mygrid,Float32,nr))
mskW=1.0 .+ 0.0 * mask(mskW[:,kk],NaN,0.0)
mskS=read(mygrid.path*"hFacS.latlon.data",MeshArray(mygrid,Float32,nr))
mskS=1.0 .+ 0.0 * mask(mskS[:,kk],NaN,0.0)

msk=Dict("mskW" => mskW, "mskS" => mskS)

uvetc=merge(uvetc,msk);
# -

# ## 3. Compute trajectories from gridded flow fields

comp_vel=IndividualDisplacements.VelComp
get_vel=IndividualDisplacements.VelCopy

# +
ii1=0:5:360; ii2=20:2:150;

n1=length(ii1); n2=length(ii2);
uInitS=Array{Float64,2}(undef,(2,n1*n2))
for i1 in eachindex(ii1); for i2 in eachindex(ii2);
        i=i1+(i2-1)*n1
        uInitS[1,i]=ii1[i1]
        uInitS[2,i]=ii2[i2]
end; end;
du=fill(0.0,size(uInitS));
comp_vel(du,uInitS,uvetc,0.0)
du
# -

tspan = (0.0,uvt["t1"]-uvt["t0"])
prob = ODEProblem(comp_vel,uInitS,tspan,uvetc)
sol = solve(prob,Tsit5(),reltol=1e-4,abstol=1e-4)
size(sol)

# ## 4. Store trajectories in a `DataFrame` and plot

ID=collect(1:size(sol,2))*ones(1,size(sol,3))
lon=mod.(sol[1,:,:],360)
lat=mod.(sol[2,:,:],180)
df = DataFrame(ID=Int.(ID[:]), lon=lon[:], lat=lat[:])
size(df)

PyPlot.figure(); PlotBasic(df,size(sol,2),90.0)


using MOSLab
using Plots
using LaTeXStrings
T = 300.0 # Temperature Kelvin
N_a = 5e16 # Bulk Doping in cm^-3
t_ox = 4e-7 # oxide thickness in cm
t_si = 2.5e-5 # Silicon thickness
Vgv = 1.0 # Gate Voltage
PB = SemiconductorData(T,BoltzmanDist(),PSilicon(N_a))
MOS = MOSStructure(Alluminium(),SiO2(),PB,t_ox,t_si)
sol = PoissonVoronoi(Vgv,MOS,201)
BandDiagram(sol)
plot!(legend=:topright)
savefig("BandDiagram.png")
PBFermi = SemiconductorData(T,FermiDist(),PSilicon(N_a))
MOSFermi = MOSStructure(Alluminium(),SiO2(),PBFermi,t_ox,t_si)
plot(-2.3:0.05:2.3,x-> ψs(x,MOS),label="Boltzmann Distribution")
plot!(-2.3:0.05:2.3,x-> ψs(x,MOSFermi),label="Fermi Distribution",legend=:topleft)
xlabel!("Vg [V]")
ppsi = ylabel!(L"\psi_s \  [V]")
savefig("psi_comp.png")


using JuMP, EAGO

                     m = Model()

                     EAGO.register_eago_operators!(m)

                     @variable(m, -1 <= x[i=1:4] <= 1)

                     @variable(m, -8.186501529651974 <= q <= 9.622483160840265)

                     add_NL_constraint(m, :(gelu(-0.9342689023247241 + -0.06643065035242302*gelu(-0.6967397920955185 + 0.4612305646899797*gelu(-0.7651411836742423 + 0.736339264438953*$(x[1]) + 0.06323249947984699*$(x[2]) + 0.6326865446578034*$(x[3]) + 0.8935006364653741*$(x[4])) + 0.5917923558177942*gelu(0.8959549878207418 + 0.6412415470262633*$(x[1]) + 0.6383904188579423*$(x[2]) + 0.9706157764666923*$(x[3]) + 0.8108132764672078*$(x[4]))) + 0.13022424784864484*gelu(-0.8092681495291991 + -0.6577545463201151*gelu(-0.7651411836742423 + 0.736339264438953*$(x[1]) + 0.06323249947984699*$(x[2]) + 0.6326865446578034*$(x[3]) + 0.8935006364653741*$(x[4])) + 0.816846389391447*gelu(0.8959549878207418 + 0.6412415470262633*$(x[1]) + 0.6383904188579423*$(x[2]) + 0.9706157764666923*$(x[3]) + 0.8108132764672078*$(x[4])))) + gelu(0.9502905692637253 + 0.2731932353707758*gelu(-0.6967397920955185 + 0.4612305646899797*gelu(-0.7651411836742423 + 0.736339264438953*$(x[1]) + 0.06323249947984699*$(x[2]) + 0.6326865446578034*$(x[3]) + 0.8935006364653741*$(x[4])) + 0.5917923558177942*gelu(0.8959549878207418 + 0.6412415470262633*$(x[1]) + 0.6383904188579423*$(x[2]) + 0.9706157764666923*$(x[3]) + 0.8108132764672078*$(x[4]))) + 0.7525923835832526*gelu(-0.8092681495291991 + -0.6577545463201151*gelu(-0.7651411836742423 + 0.736339264438953*$(x[1]) + 0.06323249947984699*$(x[2]) + 0.6326865446578034*$(x[3]) + 0.8935006364653741*$(x[4])) + 0.816846389391447*gelu(0.8959549878207418 + 0.6412415470262633*$(x[1]) + 0.6383904188579423*$(x[2]) + 0.9706157764666923*$(x[3]) + 0.8108132764672078*$(x[4])))) - $q <= 0.0))

                     @objective(m, Min, q)

                     return m


function dtftau(t,
		fft_u, fft_v,
		A1, A2,
                matr, conjmatr,
		sigma, llambda, Ntaumm)

    # attention ici je n'ai code' que le cas sigma=1

    sigma = 1

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

    du = ifft(exp(1im * t .* A1) * (1im .* A1) .* fft_u) .* matr
    dv = ifft(exp(1im * t .* A1) * (1im .* A1) .* fft_v) .* matr

    z  = (u  + conj(v) ) / 2
    dz = (du + conj(dv)) / 2

    fz1 = 2 * abs(z) .^ 2 * dz + z .^ 2 * conj(dz)
    champu1 = -1im * llambda * A2 .* exp.(-1im * t .* A1) .* fft(conjmatr .* fz1)
    champv1 = -1im * llambda * A2 .* exp.(-1im * t .* A1) .* fft(conjmatr .* conj(fz1))

    fz1 = abs(z) ^ 2 * z
    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 * conj(fz1))

    champu = champu1 + champu2
    champv = champv1 + champv2

    return champu, champv

end