### A Pluto.jl notebook ###
# v0.12.11

using Markdown
using InteractiveUtils

# ╔═╡ 61dc86f0-238f-11eb-054f-41a45b78e94d
begin
	using  MLJ
	import RDatasets: dataset
    import DataFrames: describe, select, Not, rename!,head
	import Plots: plot,plot!,scatter,scatter!,histogram,histogram!
end

# ╔═╡ ff5bd480-29c0-11eb-3112-cb35c17acc60
md" ### Libs"

# ╔═╡ 8e226310-29c0-11eb-1093-f95e53c029b6
md"### Carga de Dados"

# ╔═╡ 114c91a0-2a90-11eb-365b-4d360bb51ebe
md" É interessante sempre mostrarmos os primeiros exemplos dos dados para ter uma ideia dos valores. Entretanto, como visto no curso, há várias análises possíveis para se fazer como identificar nulos, outliers, ... Tudo isto, é bom ser tratado."

# ╔═╡ b6faf1b0-294a-11eb-38ae-0924c4943d81
begin
	boston = dataset("MASS", "Boston")
	head(boston)
end

# ╔═╡ a2efe1c0-29c8-11eb-287d-49b90e597876
md"### Sobre o Problema

url: https://www.cs.toronto.edu/~delve/data/boston/bostonDetail.html
"

# ╔═╡ a04c5f12-29d3-11eb-12a6-3f1b8acc88dc
md"#### Atributos"

# ╔═╡ a417e700-29c8-11eb-3ee5-6d2c302d95ac
md"There are 14 attributes in each case of the dataset. They are:

CRIM - per capita crime rate by town

ZN - proportion of residential land zoned for lots over 25,000 sq.ft.

INDUS - proportion of non-retail business acres per town.

CHAS - Charles River dummy variable (1 if tract bounds river; 0 otherwise)

NOX - nitric oxides concentration (parts per 10 million)

RM - average number of rooms per dwelling

AGE - proportion of owner-occupied units built prior to 1940

DIS - weighted distances to five Boston employment centres

RAD - index of accessibility to radial highways

TAX - full-value property-tax rate per $10,000

PTRATIO - pupil-teacher ratio by town

B - 1000(Bk - 0.63)^2 where Bk is the proportion of blacks by town

LSTAT - % lower status of the population

MEDV - Median value of owner-occupied homes in $1000's"

# ╔═╡ 9a810b20-29c0-11eb-1ca3-d155108c6e52
md"### Estatísticas dos Dados"

# ╔═╡ 91187080-2a8f-11eb-3001-1973d885e3f0
md"É sempre bom tiramos umas estatísticas dos dados para entender melhor o comportamento das variáveis do nosso problema. Qual a variância de cada variável? Elas estão na mesma ordem de grandeza? Máixmo e minímo ?"

# ╔═╡ 2033e842-294f-11eb-2b38-1969d7440b88
describe(boston, :mean, :std, :min, :max, :eltype)

# ╔═╡ 2f06c4a0-294f-11eb-2cb1-2da452f98b0f
data = coerce(boston, autotype(boston, :discrete_to_continuous));

# ╔═╡ d2a9dfe0-29c0-11eb-36a9-e7e3c1d17717
md"### Selecionando Dados"

# ╔═╡ 49b99b50-2a90-11eb-2b54-2704b914334d
md"É nesta fase que você vai escolher seu target e vai selecionar os atributos para trabalhar."

# ╔═╡ 47a80140-294f-11eb-2880-ef7cfb0a8997
begin
	y = data.MedV;
    X = select(data, Not(:MedV));
end

# ╔═╡ e3d83ea0-29a3-11eb-10f5-b5f94102e1f1
md"### Verificando Algoritmos Disponíveis"

# ╔═╡ 60083560-2a90-11eb-373f-3b8a1ba538a0
md"Essa é uma característica bem legal do MLJ. Como nosso dados possuem atributos numéricos e o target numérico, ele me mostra todos os algoritmos que conseguem rodar para este tipo de dado."

# ╔═╡ 868b73b0-29a9-11eb-0ef8-971446a3c919
models(matching(X, y))

# ╔═╡ 87cedb30-2a90-11eb-26dd-6162d49edd88
md"Abaixo realizamos uma carga no nosso regressor linear."

# ╔═╡ 6b75f5a0-294f-11eb-0835-cfe4a412cffe
begin
	@load LinearRegressor pkg=MLJLinearModels
	mdl = LinearRegressor();
end

# ╔═╡ 9abff260-2a90-11eb-026f-59a85ec6b64c
md"O MLJ nos permite montar um pipeline. Pipeline é uma forma de montar uma sequência de operações poupando-lhe código. No nosso caso, queremos padronizar X e y antes de realizarmos o treino do regressor. Podemos fazer isso em uma única linha."

# ╔═╡ fcde2920-29a6-11eb-37f7-4befad3ee3d2
predictor = @pipeline Standardizer mdl target=Standardizer

# ╔═╡ 323d2620-29c0-11eb-258a-4703b4f5a0d2
md"### Partição dos Dados"

# ╔═╡ e57688a2-2a90-11eb-2272-3d9898bf4375
md"Realizamos aqui o que conhecemos como hold-out. Particionamos em treino e teste, que é a forma mais simples de se fazer uma validação. Sabemos que existem outras no curso. No caso, deixei 70% para treino e 30% para teste. Como podem notar, a função gera índices qu correspondem às linhas dos exemplos. Ele não tira cópia dos dados."

# ╔═╡ fca39122-29a6-11eb-3b25-c7f0df7fc68f
train, test  = partition(eachindex(y), 0.7, shuffle=true)

# ╔═╡ 287ae7e0-2a91-11eb-0d5b-b138443bdc29
md"Um conceito interessante do MLJ é o machine. Esta função faz uma mapeamento do preditor ao dado. Quando aplicamos machine, estamos gerando um nó num grafo de computação e um estado associado. Existem algmas vantagens neste paradigma como: associar um mesmo preditor a vários dados distintos que podem ser processados em diferentes recursos; evitar re execução de um mesmo preditor e um mesmo dado."

# ╔═╡ fc474050-29a6-11eb-1fbd-87a88d38525a
pred_machine = machine(predictor, X, y);

# ╔═╡ 4449f8d0-29b5-11eb-3f83-c352d034ecb2
md"### Treino da regressão linear "

# ╔═╡ 91918630-2a91-11eb-1113-43b18086f208
md" Aqui temos enfim, o treino do nosso regressor para o data X, apenas no conjunto de treino. Isto porque estamos passando os índices do exemplos."

# ╔═╡ 0de5e6e0-29a7-11eb-0558-2bb10a5fe1a5
fit!(pred_machine, rows=train);

# ╔═╡ 50f36580-29b5-11eb-06f2-cf4b9e142979
md"### Análise da Predição"

# ╔═╡ ef445730-2a91-11eb-1552-c3e7d6e5ff34
md"Aqui analisamos cada predição e cada target. Este tipo de análise é mais apropriada quando nosso conjunto de dados possui uma relação de ordem temporal entre os exemplos (que não é o caso). Entretanto, é uma análise boa a se fazer. Podemos notar que há um atendência de subestimar os valores mais altos do que valores mais baixos. Você pode analisar o que pode estar gerando dificldade nisto analisando alguns exemplos individualmente."

# ╔═╡ 152f7100-29a7-11eb-2300-735ca72b15c3
ŷ = predict(pred_machine, rows=test)

# ╔═╡ c29c1380-29b5-11eb-254c-2d026e15f903
begin
	plot(ŷ,label="Target")
	scatter!(y[test],label="Label")

end

# ╔═╡ 89d06380-29bf-11eb-0d5b-4533c94745d5
md" Distribuição da Predição"

# ╔═╡ 8b3ac8f0-29bf-11eb-274e-f7b07eb35e7c
begin
   histogram(ŷ,label="Target")
end

# ╔═╡ 1e35c150-29de-11eb-0069-cb75699b6d61
histogram(y[test],label="Label")

# ╔═╡ 76b4c570-29b5-11eb-32d5-75da0001a894
md"### Parâmetros encontrados"

# ╔═╡ 46a9fc40-2a8e-11eb-301f-077f9a6d50fe
md" Muitas vezes é interessante analisarmos a relevância dos coeficientes por váriosmotivos. Isto porque possibilita descobrir quais são as variáveis que ajudam mais num problema, ajudando inclusive na tomada de decisão de um ser humano. Adicionalmente, pode ser usado para compactar o modelo (ambientes com poucos recursos), eliminando atributos pouco relevantes. Finalmente, pode-se utilizar para verificar se seu modelo usa alguma variável que seja discrimnatória em termos de sociedade (ex. idade para tomada de crédito). "

# ╔═╡ 8d50cc90-294f-11eb-012b-3b973cbd1f1e
begin
	fp = fitted_params(pred_machine)
	coefs = fp.fitted_params_given_machine[fp.machines[2]]
	#fp.machines
end

# ╔═╡ 2d7233c0-29c4-11eb-0e0f-2f9b5773a062
begin
	names = [string(e[1]) for e in coefs[1] ]
	vals  = [e[2] for e in coefs[1] ]
	plot(names,vals,linetype = :bar,label="Coefs")
end

# ╔═╡ dd923000-2a8e-11eb-1100-11a1ff5510bc
md"Coeficientes positivos significam que contribuem positivamente para o incremento de y. Os negativos, de forma oposta. Já os com valores próximos de zero, são pouco relevantes ao probelma."

# ╔═╡ eaad8980-29ab-11eb-059a-4fe1c6b0bb2d
md"### Análise de Erro"

# ╔═╡ 7cc2ab20-2a8d-11eb-2a73-e384f6d3b477
md" Separei aqui duas métricas que já vimos no curso: mae e rmse. Com o mae, conseguimos ter uma ideia do quanto erramos para cima ou para baixo com relação ao $y$. Erramos $3.2$ em termo de dinheiro para cima e para baixo do gabarito. Já o rmse indica uma variabilidade de $4.4$. Note que podemos ter um mae baixo, porém, se tivermos muito outliers, o rmse aumenta."

# ╔═╡ 2dadd5c0-2a8b-11eb-077d-d174e5c3e4dc
md"$\frac{1}{n}\sum_{i=1}^{n}{|ŷ - y|}$"

# ╔═╡ 1b9e5290-29a7-11eb-1c8f-4f12216b4391
md"mae = $(round(mae(ŷ, y[test]), sigdigits=4))"

# ╔═╡ a0b1a6f0-2a8b-11eb-1c89-d534b1671eec
md"$\sqrt{\frac{1}{n}\sum_{i=1}^{n}{(ŷ - y)^2}}$"

# ╔═╡ c3fb2bc0-295c-11eb-0061-c3875f5cb01b
md"rmse = $(round(rms(ŷ, y[test]), sigdigits=4))"

# ╔═╡ 0ebfd9e0-29ac-11eb-055f-615e53896367
md" Abaixo uma análise muito utilizada quando há uma relação de ordem temporal entre os exemplos. Apesar de neste problema não termos uma ordem temporal, deixo aqui este tipo de análise.Neste gráfico podemos notar que existem certos exemplos muito difíceis que talvez seja interessante analisar no futuro olhando seus atributos."

# ╔═╡ e102cfc0-295c-11eb-2c5b-834000ca2077
begin
	res = ŷ .- y[test]
	plot(res)
	scatter!(res)
end

# ╔═╡ 2b478eb0-2a8c-11eb-0fe4-17dad212a408
md"Também é interessante analisarmos a distribuição do erro a partir de um histograma. Desta forma, temos uma noção maior da magnitude do erro e sinal. Da mesma forma que na análise anterior, vemos que existem exemplos que são bem mais difíceis que o normal. Cabe uma análise individual dos exemplos."

# ╔═╡ 845c9050-29aa-11eb-3587-1ff2b98c8905
histogram(res,label="Erro")

# ╔═╡ Cell order:
# ╟─ff5bd480-29c0-11eb-3112-cb35c17acc60
# ╠═61dc86f0-238f-11eb-054f-41a45b78e94d
# ╟─8e226310-29c0-11eb-1093-f95e53c029b6
# ╟─114c91a0-2a90-11eb-365b-4d360bb51ebe
# ╠═b6faf1b0-294a-11eb-38ae-0924c4943d81
# ╟─a2efe1c0-29c8-11eb-287d-49b90e597876
# ╟─a04c5f12-29d3-11eb-12a6-3f1b8acc88dc
# ╟─a417e700-29c8-11eb-3ee5-6d2c302d95ac
# ╟─9a810b20-29c0-11eb-1ca3-d155108c6e52
# ╟─91187080-2a8f-11eb-3001-1973d885e3f0
# ╠═2033e842-294f-11eb-2b38-1969d7440b88
# ╠═2f06c4a0-294f-11eb-2cb1-2da452f98b0f
# ╟─d2a9dfe0-29c0-11eb-36a9-e7e3c1d17717
# ╟─49b99b50-2a90-11eb-2b54-2704b914334d
# ╠═47a80140-294f-11eb-2880-ef7cfb0a8997
# ╟─e3d83ea0-29a3-11eb-10f5-b5f94102e1f1
# ╟─60083560-2a90-11eb-373f-3b8a1ba538a0
# ╠═868b73b0-29a9-11eb-0ef8-971446a3c919
# ╟─87cedb30-2a90-11eb-26dd-6162d49edd88
# ╠═6b75f5a0-294f-11eb-0835-cfe4a412cffe
# ╟─9abff260-2a90-11eb-026f-59a85ec6b64c
# ╠═fcde2920-29a6-11eb-37f7-4befad3ee3d2
# ╟─323d2620-29c0-11eb-258a-4703b4f5a0d2
# ╟─e57688a2-2a90-11eb-2272-3d9898bf4375
# ╠═fca39122-29a6-11eb-3b25-c7f0df7fc68f
# ╟─287ae7e0-2a91-11eb-0d5b-b138443bdc29
# ╠═fc474050-29a6-11eb-1fbd-87a88d38525a
# ╟─4449f8d0-29b5-11eb-3f83-c352d034ecb2
# ╟─91918630-2a91-11eb-1113-43b18086f208
# ╠═0de5e6e0-29a7-11eb-0558-2bb10a5fe1a5
# ╟─50f36580-29b5-11eb-06f2-cf4b9e142979
# ╟─ef445730-2a91-11eb-1552-c3e7d6e5ff34
# ╠═152f7100-29a7-11eb-2300-735ca72b15c3
# ╠═c29c1380-29b5-11eb-254c-2d026e15f903
# ╠═89d06380-29bf-11eb-0d5b-4533c94745d5
# ╠═8b3ac8f0-29bf-11eb-274e-f7b07eb35e7c
# ╠═1e35c150-29de-11eb-0069-cb75699b6d61
# ╟─76b4c570-29b5-11eb-32d5-75da0001a894
# ╟─46a9fc40-2a8e-11eb-301f-077f9a6d50fe
# ╟─8d50cc90-294f-11eb-012b-3b973cbd1f1e
# ╠═2d7233c0-29c4-11eb-0e0f-2f9b5773a062
# ╟─dd923000-2a8e-11eb-1100-11a1ff5510bc
# ╟─eaad8980-29ab-11eb-059a-4fe1c6b0bb2d
# ╟─7cc2ab20-2a8d-11eb-2a73-e384f6d3b477
# ╟─2dadd5c0-2a8b-11eb-077d-d174e5c3e4dc
# ╟─1b9e5290-29a7-11eb-1c8f-4f12216b4391
# ╟─a0b1a6f0-2a8b-11eb-1c89-d534b1671eec
# ╟─c3fb2bc0-295c-11eb-0061-c3875f5cb01b
# ╟─0ebfd9e0-29ac-11eb-055f-615e53896367
# ╟─e102cfc0-295c-11eb-2c5b-834000ca2077
# ╟─2b478eb0-2a8c-11eb-0fe4-17dad212a408
# ╟─845c9050-29aa-11eb-3587-1ff2b98c8905


# --- Day 1: Sonar Sweep ---
# You're minding your own business on a ship at sea when the overboard alarm
# goes off! You rush to see if you can help. Apparently, one of the Elves
# tripped and accidentally sent the sleigh keys flying into the ocean!
#
# Before you know it, you're inside a submarine the Elves keep ready for
# situations like this. It's covered in Christmas lights (because of course it
# is), and it even has an experimental antenna that should be able to track the
# keys if you can boost its signal strength high enough; there's a little meter
# that indicates the antenna's signal strength by displaying 0-50 stars.
#
# Your instincts tell you that in order to save Christmas, you'll need to get
# all fifty stars by December 25th.
#
# Collect stars by solving puzzles. Two puzzles will be made available on each
# day in the Advent calendar; the second puzzle is unlocked when you complete
# the first. Each puzzle grants one star. Good luck!
#
# As the submarine drops below the surface of the ocean, it automatically
# performs a sonar sweep of the nearby sea floor. On a small screen, the sonar
# sweep report (your puzzle input) appears: each line is a measurement of the
# sea floor depth as the sweep looks further and further away from the
# submarine.
#
# For example, suppose you had the following report:
#
# 199
# 200
# 208
# 210
# 200
# 207
# 240
# 269
# 260
# 263
#
# This report indicates that, scanning outward from the submarine, the sonar
# sweep found depths of 199, 200, 208, 210, and so on.
#
# The first order of business is to figure out how quickly the depth increases,
# just so you know what you're dealing with - you never know if the keys will
# get carried into deeper water by an ocean current or a fish or something.
#
# To do this, count the number of times a depth measurement increases from the
# previous measurement. (There is no measurement before the first measurement.)
# In the example above, the changes are as follows:
#
# 199 (N/A - no previous measurement)
# 200 (increased)
# 208 (increased)
# 210 (increased)
# 200 (decreased)
# 207 (increased)
# 240 (increased)
# 269 (increased)
# 260 (decreased)
# 263 (increased)
#
# In this example, there are 7 measurements that are larger than the previous
# measurement.
#
# How many measurements are larger than the previous measurement?

readings = map(l -> parse(Int, l), eachline("input.txt"))
lead = @view readings[2:end]
println("Count: ", count(((a, b),) -> a < b, zip(readings, lead)))


using BridgeDiffEq, StaticArrays
using Test

α=1
β=1
u0=1/2
f(u,p,t) = α*u
g(u,p,t) = β*u
dt = 1//2^(4)
tspan = (0.0,1.0)

prob = ODEProblem(f,u0,(0.0,1.0))
sol = solve(prob,BridgeR3(),dt=dt)
sol = solve(prob,BridgeBS3(),dt=dt)

prob = SDEProblem(f,g,u0,(0.0,1.0))
@time sol = solve(prob,BridgeEuler(),dt=dt)
sol = solve(prob,BridgeHeun(),dt=dt)
sol = solve(prob,BridgeSRK(),dt=dt)

u0 = @SVector [2.0,3.0]
prob = ODEProblem(f,u0,(0.0,1.0))
sol = solve(prob,BridgeR3(),dt=dt)
sol = solve(prob,BridgeBS3(),dt=dt)

prob = SDEProblem(f,g,u0,(0.0,1.0))
sol = solve(prob,BridgeEuler(),dt=dt)
sol = solve(prob,BridgeHeun(),dt=dt)


import Base: show, ==, push!, append!

## nice-valued ranges for histograms
function histrange{T<:FloatingPoint}(v::AbstractArray{T}, n::Integer, closed::Symbol)
    if length(v) == 0
        return 0.0:1.0:0.0
    end
    lo, hi = extrema(v)
    if hi == lo
        step = 1.0
    else
        bw = (hi - lo) / n
        e = 10.0^floor(log10(bw))
        r = bw / e
        if r <= 2
            step = 2*e
        elseif r <= 5
            step = 5*e
        else
            step = 10*e
        end
    end
    if closed == :right
        start = step*(ceil(lo/step)-1)
        nm1 = iceil((hi - start)/step)
    else
        start = step*floor(lo/step)
        nm1 = ifloor((hi - start)/step)+1
    end
    start:step:(start + nm1*step)
end

function histrange{T<:Integer}(v::AbstractArray{T}, n::Integer, closed::Symbol)
    if length(v) == 0
        return 0:1:0
    end
    lo, hi = extrema(v)
    if hi == lo
        step = 1
    else
        bw = (hi - lo) / n
        e = int(10^max(0,ifloor(log10(bw))))
        r = bw / e
        if r <= 1
            step = e
        elseif r <= 2
            step = 2*e
        elseif r <= 5
            step = 5*e
        else
            step = 10*e
        end
    end
    if closed == :right
        start = step*(iceil(lo/step)-1)
        nm1 = iceil((hi - start)/step)
    else
        start = step*ifloor(lo/step)
        nm1 = ifloor((hi - start)/step)+1
    end
    start:step:(start + nm1*step)
end

histrange{N}(vs::NTuple{N,AbstractVector},nbins::NTuple{N,Integer},closed::Symbol) = map((v,n) -> histrange(v,n,closed),vs,nbins)
histrange{N}(vs::NTuple{N,AbstractVector},nbins::Integer,closed::Symbol) = map(v -> histrange(v,nbins,closed),vs)

## midpoints of intervals
midpoints(r::Range) = r[1:length(r)-1] + 0.5*step(r)
midpoints(v::AbstractVector) = [0.5*(v[i] + v[i+1]) for i in 1:length(v)-1]

## histograms ##
function sturges(n)  # Sturges' formula
    n==0 && return one(n)
    iceil(log2(n))+1
end

# N-dimensional histogram object
type Histogram{T<:Real,N,E}
    edges::E
    weights::Array{T,N}
    closed::Symbol
    function Histogram(edges::NTuple{N,AbstractArray},weights::Array{T,N},closed::Symbol)
        closed == :right || closed == :left || error("closed must :left or :right")
        map(x -> length(x)-1,edges) == size(weights) || error("Histogram edge vectors must be 1 longer than corresponding weight dimensions")
        new(edges,weights,closed)
    end
end
Histogram{T,N}(edges::NTuple{N,AbstractVector},weights::AbstractArray{T,N},closed::Symbol=:right) = Histogram{T,N,typeof(edges)}(edges,weights,closed)
Histogram{T,N}(edges::NTuple{N,AbstractVector},::Type{T},closed::Symbol=:right) = Histogram(edges,zeros(T,map(x -> length(x)-1,edges)...),closed)
Histogram{N}(edges::NTuple{N,AbstractVector},closed::Symbol=:right) = Histogram(edges,Int,closed)

function show(io::IO, h::Histogram)
    println(io, typeof(h))
    println(io,"edges:")
    for e in h.edges
        println(io,"  ",e)
    end
    println(io,"weights: ",h.weights)
    print(io,"closed: ",h.closed)
end

(==)(h1::Histogram,h2::Histogram) = (==)(h1.edges,h2.edges) && (==)(h1.weights,h2.weights) && (==)(h1.closed,h2.closed)

# 1-dimensional
Histogram{T}(edge::AbstractVector,weights::AbstractVector{T},closed::Symbol=:right) = Histogram{T,1,(typeof(edge),)}((edge,),weights,closed)
Histogram{T}(edge::AbstractVector,::Type{T},closed::Symbol=:right) = Histogram(edge,zeros(T,length(edge)-1),closed)
Histogram(edge::AbstractVector,closed::Symbol=:right) = Histogram(edge,Int,closed)

function push!{T,E}(h::Histogram{T,1,E}, x::Real,w::Real)
    i = if h.closed == :right
        searchsortedfirst(h.edges[1], x) - 1
    else
        searchsortedlast(h.edges[1], x)
    end
    if 1 <= i <= length(h.weights)
        @inbounds h.weights[i] += w
    end
    h
end
push!{T,E}(h::Histogram{T,1,E}, x::Real) = push!(h,x,one(T))

function append!{T}(h::Histogram{T,1}, v::AbstractVector)
    for x in v
        push!(h,x)
    end
    h
end
function append!{T}(h::Histogram{T,1}, v::AbstractVector,wv::WeightVec)
    for (x,w) in zip(v,wv.values)
        push!(h,x,w)
    end
    h
end

fit(::Type{Histogram},v::AbstractVector, edg::AbstractVector; closed::Symbol=:right) =
    append!(Histogram(edg,closed), v)
fit(::Type{Histogram},v::AbstractVector; closed::Symbol=:right, nbins=sturges(length(v))) =
    fit(Histogram, v, histrange(v,nbins,closed); closed=closed)

fit{W}(::Type{Histogram},v::AbstractVector, wv::WeightVec{W}, edg::AbstractVector; closed::Symbol=:right) =
    append!(Histogram(edg,W,closed), v, wv)
fit(::Type{Histogram},v::AbstractVector, wv::WeightVec; closed::Symbol=:right, nbins=sturges(length(v))) =
    fit(Histogram, v, wv, histrange(v,nbins,closed); closed=closed)

# N-dimensional
function push!{T,N}(h::Histogram{T,N},xs::NTuple{N,Real},w::Real)
    is = if h.closed == :right
        map((edge, x) -> searchsortedfirst(edge,x) - 1, h.edges, xs)
    else
        map(searchsortedlast, h.edges, xs)
    end
    try
        h.weights[is...] += w
    catch e
        isa(e,BoundsError) || rethrow(e)
    end
    h
end
push!{T,N}(h::Histogram{T,N},xs::NTuple{N,Real}) = push!(h,xs,one(T))

function append!{T,N}(h::Histogram{T,N}, vs::NTuple{N,AbstractVector})
    for xs in zip(vs...)
        push!(h,xs)
    end
    h
end
function append!{T,N}(h::Histogram{T,N}, vs::NTuple{N,AbstractVector},wv::WeightVec)
    for (xs,w) in zip(zip(vs...),wv.values)
        push!(h,xs,w)
    end
    h
end

fit{N}(::Type{Histogram}, vs::NTuple{N,AbstractVector}, edges::NTuple{N,AbstractVector}; closed::Symbol=:right) =
    append!(Histogram(edges,closed), vs)
fit{N}(::Type{Histogram}, vs::NTuple{N,AbstractVector}; closed::Symbol=:right, nbins=sturges(length(vs[1]))) =
    fit(Histogram, vs, histrange(vs,nbins,closed); closed=closed)

fit{N,W}(::Type{Histogram}, vs::NTuple{N,AbstractVector}, wv::WeightVec{W}, edges::NTuple{N,AbstractVector}; closed::Symbol=:right) =
    append!(Histogram(edges,W,closed), vs, wv)
fit{N}(::Type{Histogram},vs::NTuple{N,AbstractVector}, wv::WeightVec; closed::Symbol=:right, nbins=sturges(length(vs[1]))) =
    fit(Histogram, vs, wv, histrange(vs,nbins,closed); closed=closed)


module LogExpFunctions

using DocStringExtensions: SIGNATURES
using Base: Math.@horner, @irrational

export loghalf, logtwo, logπ, log2π, log4π
export xlogx, xlogy, logistic, logit, log1psq, log1pexp, log1mexp, log2mexp, logexpm1,
    softplus, invsoftplus, log1pmx, logmxp1, logaddexp, logsubexp, logsumexp, softmax,
    softmax!

include("constants.jl")
include("basicfuns.jl")
include("logsumexp.jl")

end # module


function levendist1(s::AbstractString, t::AbstractString)
    ls, lt = length(s), length(t)
    if ls > lt
        s, t = t, s
        ls, lt = lt, ls
    end
    dist = collect(0:ls)
    for (ind2, chr2) in enumerate(t)
        newdist = Vector{Int}(ls+1)
        newdist[1] = ind2
        for (ind1, chr1) in enumerate(s)
            if chr1 == chr2
                newdist[ind1+1] = dist[ind1]
            else
                newdist[ind1+1] = 1 + min(dist[ind1], dist[ind1+1], newdist[end])
            end
        end
        dist = newdist
    end
    return dist[end]
end


s = SingleStock()
a = 100.00USD

d1 = Dates.today()
d2 = d1 + Dates.Day(120)

m = CoreModel(d1, a, 0.05, 0.0)

@test value(m, s) == a

@test value(m, Miletus.Zero()) == 0*a
@test value(m, Miletus.Receive(a)) == a

@test value(m, Pay(a)) == -a

@test value(m, Buy(s, a)) == 0USD
@test value(m, Sell(s, a)) == 0USD

@test value(m, Option(s)) == a

@test value(m, Forward(d2, s, 100.00USD)) ≈ 100.0USD * (1-exp(-0.05*120/365))

# assumes cost-of-carry
m = GeomBMModel(d1, a, 0.05, -0.1, 0.1)

@test value(m, Miletus.Zero()) == 0*a
@test value(m, Miletus.Receive(a)) == a

@test value(m, Pay(a)) == -a

@test value(m, Buy(s, a)) == 0USD
@test value(m, Sell(s, a)) == 0USD

@test value(m, Option(s)) == a

@test value(m, Scale(3, Option(s))) == 3*a

@test value(m, Forward(d2, s, 100.00USD)) ≈ 100.0USD * (exp(0.1*120/365)-exp(-0.05*120/365))

m = CoreForwardModel(d1,
                     [d1 + Dates.Day(30), d1 + Dates.Day(60), d1 + Dates.Day(120)],
                     [100.00USD, 110.00USD, 120.00USD],
                     0.05)

@test value(m, Forward(d2, s, 100.00USD)) ≈ (120.00USD - 100.0USD) * exp(-0.05*120/365)


using DelimitedFiles

input = joinpath(@__DIR__, "input")
raw = readdlm(input, String)

function decimalize(vec)
    reverse!(vec)
    return sum(2^i * x for (i, x) in zip(0:length(vec) - 1, vec))
end

function rating(diagnostics, mcf)
    for i in 1:length(first(diagnostics))
        mc = mcf(diagnostics)
        filter!(d -> d[i] == mc[i], diagnostics)
        if length(diagnostics) == 1
            return first(diagnostics)
        end
    end
end

diagnostics = map(vec(raw)) do diagnostic
    map(n -> parse(Int, n), collect(diagnostic))
end

sums = sum(diagnostics)

gamma = decimalize(sums .>= (length(diagnostics) / 2))
epsilon = decimalize(sums .< (length(diagnostics) / 2))

oxygen = decimalize(rating(copy(diagnostics), ds -> sum(ds) .>= length(ds) / 2))
scrubber = decimalize(rating(copy(diagnostics), ds -> sum(ds) .< length(ds) / 2))

p1 = gamma * epsilon
p2 = oxygen * scrubber

println("-----------------------------------------------------------------------")
println("binary diagnostic -- part one :: $p1")
println("binary diagnostic -- part two :: $p2")
println("-----------------------------------------------------------------------")

@assert(p1 == 738234)
@assert(p2 == 3969126)


#=
This file defines additional functions to return objects of type Distribution. This is
necessary because we specify prior distributions wrt mean and SD
(for beta and gamma-distributed parameters) and ν and σ (for inverse gamma-distributed
parameters). Note these functions are NOT new methods for the Distributions.Beta, etc.
functions, but rather new functions with the same names.
=#

import Distributions: params, mean, std, pdf, logpdf, rand, Distribution, Matrixvariate
import Base: length, rank

"""
```
BetaAlt(μ::AbstractFloat, σ::AbstractFloat)
```

Given μ and σ, calculate α and β and return a Distributions.Beta Distribution object.

### Arguments
`μ`: The mean of the desired distribution
`σ`: The standard deviation of the desired distribution
"""
function BetaAlt(μ::AbstractFloat, σ::AbstractFloat)
    α = (1-μ) * μ^2 / σ^2 - μ
    β = α * (1/μ - 1)
    return Distributions.Beta(α, β)
end

"""
```
GammaAlt(μ::AbstractFloat, σ::AbstractFloat)
```

Given μ and σ, calculate α and β and return a Distributions.Gamma object.

### Arguments
`μ`: The mean of the desired distribution
`σ`: The standard deviation of the desired distribution
"""
function GammaAlt(μ::AbstractFloat, σ::AbstractFloat)
    β = σ^2 / μ
    α = μ / β
    return Distributions.Gamma(α, β)
end

"""
```
type RootInverseGamma <: Distribution{Univariate, Continuous}
```

If x ~ RootInverseGamma(ν, τ²), then
  x² ~ ScaledInverseChiSquared(ν, τ²)
  x² ~ InverseGamma(ν/2, ντ²/2)

ν represents the degrees of freedom.
"""
type RootInverseGamma <: Distribution{Univariate, Continuous}
    ν::Float64
    τ::Float64
end

Distributions.params(d::RootInverseGamma) = (d.ν, d.τ)

"""
```
Distributions.pdf(d::RootInverseGamma, x::AbstractFloat)
```

Compute the pdf of a RootInverseGamma distribution at x.
"""
function Distributions.pdf(d::RootInverseGamma, x::AbstractFloat)
    (ν, τ) = params(d)
    return 2 * (ν*τ^2/2)^(ν/2) * exp((-ν*τ^2)/(2x^2)) / gamma(ν/2) / x^(ν+1)
end

"""
```
Distributions.logpdf(d::RootInverseGamma, x::AbstractFloat)
```

Compute the log pdf of a RootInverseGamma distribution at x.
"""
function Distributions.logpdf(d::RootInverseGamma, x::AbstractFloat)
    (ν, τ) = params(d)
    return log(2) - log(gamma(ν/2)) + (ν/2)*log(ν*τ^2/2) - ((ν+1)/2)*log(x^2) - ν*τ^2/(2x^2)
end

"""
```
Distributions.rand{T<:AbstractFloat}(d::RootInverseGamma; cc::T = 1.0)
```

Generate a draw from d with variance optionally scaled by cc^2 (for a RootInverseGamma)
"""
function Distributions.rand{T<:AbstractFloat}(d::RootInverseGamma; cc::T = 1.0)
    return sqrt(d.ν * d.τ^2 / sum(randn(round(Int,d.ν)).^2))
end

"""
```
DegenerateMvNormal <: Distribution{Multivariate, Continuous}
```

The `DegenerateMvNormal` type implements a degenerate multivariate normal
distribution. The covariance matrix may not be full rank (hence degenerate).

See [Multivariate normal distribution - Degenerate case](en.wikipedia.org/wiki/Multivariate_normal_distribution#Degenerate_case).
"""
type DegenerateMvNormal <: Distribution{Multivariate, Continuous}
    μ::Vector          # mean
    σ::Matrix          # standard deviation
end

"""
```
rank(d::DegenerateMvNormal)
```

Returns the rank of `d.σ`.
"""
function rank(d::DegenerateMvNormal)
    return rank(d.σ)
end

"""
```
length(d::DegenerateMvNormal)
```

Returns the dimension of `d`.
"""
Base.length(d::DegenerateMvNormal) = length(d.μ)

"""
```
Distributions.rand{T<:AbstractFloat}(d::DegenerateMvNormal; cc::T = 1.0)
```

Generate a draw from `d` with variance optionally scaled by `cc^2`.
"""
function Distributions.rand{T<:AbstractFloat}(d::DegenerateMvNormal; cc::T = 1.0)
    return d.μ + cc*d.σ*randn(length(d))
end

"""
```
Distributions.rand(d::DegenerateMvNormal, n::Int)
```

Generate `n` draws from `d`. This returns a matrix of size `(length(d), n)`,
where each column is a sample.
"""
function Distributions.rand(d::DegenerateMvNormal, n::Int)
    return d.μ .+ d.σ*randn(length(d), n)
end

"""
```
DegenerateDiagMvTDist <: Distribution{Multivariate, Continuous}
```

The `DegenerateDiagMvTDist` type implements a degenerate multivariate Student's t
distribution, where the covariance matrix is diagonal. The covariance matrix may
not be full rank (hence degenerate).
"""
type DegenerateDiagMvTDist <: Distribution{Multivariate, Continuous}
    μ::Vector          # mean
    σ::Matrix          # standard deviation
    ν::Int             # degrees of freedom

    function DegenerateDiagMvTDist(μ::Vector, σ::Matrix, ν::Int)
        ν > 0       ? nothing : error("Degrees of freedom (ν) must be positive")
        isdiag(σ^2) ? nothing : error("Covariance matrix (σ^2) must be diagonal")
        return new(μ, σ, ν)
    end
end

"""
```
rank(d::DegenerateDiagMvTDist)
```

Returns the rank of `d.σ`.
"""
function rank(d::DegenerateDiagMvTDist)
    return rank(d.σ)
end

"""
```
length(d::DegenerateDiagMvTDist)
```

Returns the dimension of `d`.
"""
Base.length(d::DegenerateDiagMvTDist) = length(d.μ)

"""
```
Distributions.rand(d::DegenerateDiagMvTDist)
```

Generate a draw from `d`.
"""
function Distributions.rand(d::DegenerateDiagMvTDist)
    return d.μ + d.σ*rand(TDist(d.ν), length(d))
end

"""
```
Distributions.rand(d::DegenerateDiagMvTDist, n::Int)
```

Generate `n` draws from `d`. This returns a matrix of size `(length(d), n)`,
where each column is a sample.
"""
function Distributions.rand(d::DegenerateDiagMvTDist, n::Int)
    return d.μ .+ d.σ*rand(TDist(d.ν), length(d), n)
end

# Compute the mean μ and standard deviation σ of a DSGE.RootInverseGamma object.

# A Root Inverse Gamma / Nagasaki Scaled Chi2 Distribution's mean and standard deviation
# can be computed as follows:

#     μ = √(β) * Γ(α - 0.5) / Γ(α)
#     σ = √(β / (α - 1) - μ²)

# where α = ν/2 and β = τ²*ν/2.
function mean(dist::RootInverseGamma)
    α = dist.ν/2
    β = dist.τ^2 * dist.ν/2

    μ = β^(.5) * gamma(α - 0.5) / gamma(α)
    return μ
end

function std(dist::RootInverseGamma)
    α = dist.ν/2
    β = dist.τ^2 * dist.ν/2

    σ = (β / (α - 1) - μ^2)^(0.5)
    return σ
end

"""
```
MatrixNormal <: Distribution{Matrixvariate, Continuous}
```

The `MatrixNormal` type implements a matrixvariate normal
distribution. Note that the matrix must be square.

See [Matrix normal distribution - Degenerate case](en.wikipedia.org/wiki/Matrix_normal_distribution).
"""
type MatrixNormal <: Distribution{Matrixvariate, Continuous}
    μ::Matrix # mean
    U::Matrix # row variance
    V::Matrix # col variance
    U_sqrt::Matrix # cholesky of U
    V_sqrt::Matrix # choleksy of V
    U_inv::Matrix # inverse of U
    V_inv::Matrix # inverse of V

    function MatrixNormal(μ::Matrix, U::Matrix, V::Matrix)
        size(μ,1) == size(U,1) || error("μ and U must have the same number of rows")
        size(μ,2) == size(V,1) || error("μ and V must have the same number of columns")
        isposdef(U) || error("U is not a positive definite matrix")
        isposdef(V) || error("V is not a positive definite matrix")

        U_sqrt = Matrix(chol(U))
        V_sqrt = Matrix(chol(V))
        U_inv = inv(U)
        V_inv = inv(V)

        return new(μ, U, V, U_sqrt, V_sqrt, U_inv, V_inv)
    end

    function MatrixNormal(μ::Matrix, Σ::Matrix)
        size(μ) == size(Σ) || error("μ and Σ must be the same size")
        isposdef(Σ) || error("Σ is not a positive definite matrix")
        Σ_sqrt = Matrix(chol(U))
        Σ_inv = inv(U)

        return new(μ, Σ, Σ, Σ_sqrt, Σ_sqrt, Σ_inv, Σ_inv)
    end

end

"""
```
size(d::MatrixNormal)
```

Returns the dimension of `d`.
"""
Base.size(d::MatrixNormal) = size(d.μ)

"""
```
Distributions.rand{T<:AbstractFloat}(d::MatrixNormal)
```

Generate a draw from `d`.
"""
function Distributions.rand(d::MatrixNormal)
    return d.μ + d.U_sqrt*randn(size(d.μ))*d.V_sqrt'
end

function mean(d::MatrixNormal)
    return d.μ
end

Distributions.params(d::MatrixNormal) = d.μ, d.U, d.V

"""
```
Distributions.pdf(d::MatrixNormal, x::Matrix)
```

Compute the pdf of a MatrixNormal distribution at x.
"""
function Distributions.pdf(d::MatrixNormal, x::Matrix)
    μ, U, V = params(d)
    n, p = size(μ)
    U_inv = d.U_inv
    V_inv = d.V_inv
    return exp(-.5*trace(V_inv * (x-μ)' * U_inv * (x-μ))) / ((2π)^(n*p/2) * det(U)^(p/2) * det(V)^(n/2))
end

"""
```
Distributions.logpdf(d::MatrixNormal, x::Matrix)
```

Compute the logpdf of a MatrixNormal distribution at x.
"""
function Distributions.logpdf(d::MatrixNormal, x::Matrix)
    μ, U, V = params(d)
    n, p = size(μ)
    U_inv = d.U_inv
    V_inv = d.V_inv
    return -.5*trace(V_inv * (x-μ)' * U_inv * (x-μ)) - log((2π)^(n*p/2) * det(U)^(p/2) * det(V)^(n/2))
end


using MDP
using Printf

# Only needed for rle function
using StatsBase

const L = 50
const M = 30

#alpha = [0.75 0.75]
alpha = [0.75 0.75]

const P = [ alpha[1]		1-alpha[1]
	         1 - alpha[2]  alpha[2]  ]

p1 = p2 = 0.3

const Q1 = [ 1 - p1   p1
              0        1 ]

const Q2 = [ 1 - p2   p2
              0        1 ]

const xi = [ P^n for n = 1:M+1]

function probabilityVector(Q, n)
    if n < L+2
        [1 0]*Q^n
    else # L+2 component corresponds to z_inf
        [0;1]
    end
end

z1 = [ probabilityVector(Q1, n)  for n = 1:L+2]
z2 = [ probabilityVector(Q2, n)  for n = 1:L+2]

r = 1.0
c = 0.4

function bellmanUpdate!(v_next, g_next, v; discount=1.0)
    # Assume that v is L+2 x L+2 x 2 x M+1

    # v_next(k, ell, s, m)
    # k   = last time state of user 1 was observed
    # ell = last time state of user 2 was observed
    #  s  = last observed state of channel {Idle, Busy}
    #  m  = last time the state of channel was observed
    for m = 1:M+1, s = 1:2, ell = 1:L+2, k = 1:L+2
        next_m = min(m+1, M+1)
        next_ell = (ell < L+1) ? ell+1 : ell
        next_k = (k < L+1) ? k+1 : k

        q00 = discount * v[next_k, next_ell, s, next_m]

        q10 = ( z1[k][2] * xi[m][s,1] * r - z1[k][2] * c
              + discount * ( z1[k][1] * v[1, next_ell, s, next_m]
                           + z1[k][2] * xi[m][s,1] * v[1, next_ell, 1, 1]
                           + z1[k][2] * xi[m][s,2] * v[L+2, next_ell, 2, 1]
                           )
              )

        q01 = ( z2[ell][2] * xi[m][s,1] * r - z2[ell][2] * c
              + discount * ( z2[ell][1] * v[next_k, 1, s, next_m]
                           + z2[ell][2] * xi[m][s,1] * v[next_k, 1, 1, 1]
                           + z2[ell][2] * xi[m][s,2] * v[next_k, L+2, 2, 1]
                           )
              )

        q11 = ( ( z1[k][2] * z2[ell][1] + z1[k][1] * z2[ell][2] ) * xi[m][s,1] * r
              - ( z1[k][2] + z2[ell][2] ) * c
              + discount * ( z1[k][1] * z2[ell][1] * v[1, 1, s, next_m]
                           + (z1[k][2] * z2[ell][1] + z1[k][1] * z2[ell][2]) * xi[m][s,1] * v[1, 1, 1, 1]
                           + z1[k][2] * z2[ell][2] * xi[m][s,1] * v[L+2, L+2, 1, 1]
                           + z1[k][2] * z2[ell][1] * xi[m][s,2] * v[L+2, 1, 2, 1]
                           + z1[k][1] * z2[ell][2] * xi[m][s,2] * v[1, L+2, 2, 1]
                           + z1[k][2] * z2[ell][2] * xi[m][s,2] * v[L+2, L+2, 2, 1]
                           )
              )

        next = [q00, q10, q01, q11]
        v_next[k, ell, s, m],  g_next[k, ell, s, m] = findmax(next)
    end

    @printf("Debug: v[1,1,1,1] = %0.8f\n", v_next[1,1,1,1])
    return (v_next, g_next)
end

model = DynamicModel(bellmanUpdate!; objective=:Max)
v_initial = zeros(L+2, L+2, 2, M+1)

@time (v,g) = valueIteration(model, v_initial; discount=0.9)
@printf("Value = %0.4f\n", v[1,1,1,1])

for s = 1:2
    for m = 1:5
        bottom  = vec(g[:,1,s,m])
        top     = vec(g[:,L+2,s,m])

        left    = vec(g[1,:,s,m])
        right   = vec(g[L+2,:,s,m])

        @printf(" ========= s = %d m = %d =============\n", s, m)
        println(rle(bottom))
        println(rle(top))
        println(rle(left))
        println(rle(right))
    end
end


module TestMult_GetDataframe

using Mimi
using NamedArrays
using Test

#####################################
#  LARGER MULTIREGIONAL TEST (2/3)  #
#####################################

#GROSS ECONOMY COMPONENT
@defcomp grosseconomy begin
    regions = Index()                           #Note that a regional index is defined here

    YGROSS  = Variable(index=[time, regions])   #Gross output
    K       = Variable(index=[time, regions])   #Capital
    l       = Parameter(index=[time, regions])  #Labor
    tfp     = Parameter(index=[time, regions])  #Total factor productivity
    s       = Parameter(index=[time, regions])  #Savings rate
    depk    = Parameter(index=[regions])        #Depreciation rate on capital - Note that it only has a region index
    k0      = Parameter(index=[regions])        #Initial level of capital
    share   = Parameter()                       #Capital share

    function run_timestep(p, v, d, t)
        #Define an equation for K
        for r in d.regions
            if is_first(t)
                v.K[t,r] = p.k0[r]
            else
                v.K[t,r] = (1 - p.depk[r])^5 * v.K[t-1,r] + v.YGROSS[t-1,r] * p.s[t-1,r] * 5
            end
        end

        #Define an equation for YGROSS
        for r in d.regions
            v.YGROSS[t,r] = p.tfp[t,r] * v.K[t,r]^p.share * p.l[t,r]^(1-p.share)
        end
    end
end

#EMISSIONS COMPONENT
@defcomp emissions begin
    regions     = Index()                           #The regions index must be specified for each component

    E           = Variable(index=[time, regions])   #Total greenhouse gas emissions
    E_Global    = Variable(index=[time])            #Global emissions (sum of regional emissions)
    sigma       = Parameter(index=[time, regions])  #Emissions output ratio
    YGROSS      = Parameter(index=[time, regions])  #Gross output - Note that YGROSS is now a parameter

    function run_timestep(p, v, d, t)
        #Define an eqation for E
        for r in d.regions
            v.E[t,r] = p.YGROSS[t,r] * p.sigma[t,r]
        end

        #Define an equation for E_Global
        for r in d.regions
            v.E_Global[t] = sum(v.E[t,:])
        end
    end
end

#DEFINE ALL THE PARAMETERS
l = Array{Float64}(undef,20,3)
for t in 1:20
    l[t,1] = (1. + 0.015)^t *2000
    l[t,2] = (1. + 0.02)^t * 1250
    l[t,3] = (1. + 0.03)^t * 1700
end

tfp = Array{Float64}(undef, 20,3)
for t in 1:20
    tfp[t,1] = (1 + 0.06)^t * 3.2
    tfp[t,2] = (1 + 0.03)^t * 1.8
    tfp[t,3] = (1 + 0.05)^t * 2.5
end

s = Array{Float64}(undef, 20,3)
for t in 1:20
    s[t,1] = 0.21
    s[t,2] = 0.15
    s[t,3] = 0.28
end

depk = [0.11, 0.135 ,0.15]
k0   = [50.5, 22., 33.5]

sigma = Array{Float64}(undef, 20,3)
for t in 1:20
    sigma[t,1] = (1. - 0.05)^t * 0.58
    sigma[t,2] = (1. - 0.04)^t * 0.5
    sigma[t,3] = (1. - 0.045)^t * 0.6
end

#FUNCTION TO RUN MY MODEL
function run_my_model()

    my_model = Model()

    set_dimension!(my_model, :time, collect(2015:5:2110))
    set_dimension!(my_model, :regions, ["Region1", "Region2", "Region3"])  #Note that the regions of your model must be specified here

    add_comp!(my_model, grosseconomy)
    add_comp!(my_model, emissions)

    set_param!(my_model, :grosseconomy, :l, l)
    set_param!(my_model, :grosseconomy, :tfp, tfp)
    set_param!(my_model, :grosseconomy, :s, s)
    set_param!(my_model, :grosseconomy, :depk,depk)
    set_param!(my_model, :grosseconomy, :k0, k0)
    set_param!(my_model, :grosseconomy, :share, 0.3)

    #set parameters for emissions component
    set_param!(my_model, :emissions, :sigma, sigma2)
    connect_param!(my_model, :emissions, :YGROSS, :grosseconomy, :YGROSS)

    run(my_model)
    return(my_model)
end

# DEFINE ALL THE PARAMETERS using NAMEDARRAYS
region_labels = ["Region1", "Region2", "Region3"]
time_labels = collect(2015:5:2110)
num_time_labels = length(time_labels)

l2 = NamedArray(Array{Float64}(undef, 20, 3), (time_labels, region_labels), (:time, :regions))
for t in 1:num_time_labels
    l2[t, 1] = (1. + 0.015)^t *2000
    l2[t, 2] = (1. + 0.02)^t * 1250
    l2[t, 3] = (1. + 0.03)^t * 1700
end

tfp2 = NamedArray(Array{Float64}(undef, 20, 3), (time_labels, region_labels), (:time, :regions))
for t in 1:num_time_labels
    tfp2[t, 1] = (1 + 0.06)^t * 3.2
    tfp2[t, 2] = (1 + 0.03)^t * 1.8
    tfp2[t, 3] = (1 + 0.05)^t * 2.5
end

s2 = NamedArray(Array{Float64}(undef, 20, 3), (time_labels, region_labels), (:time, :regions))
for t in 1:num_time_labels
    s2[t, 1] = 0.21
    s2[t, 2] = 0.15
    s2[t, 3] = 0.28
end

depk2 = NamedArray([0.11, 0.135 ,0.15], (region_labels,), (:regions,))
k02   = NamedArray([50.5, 22., 33.5], (region_labels,), (:regions,))

sigma2 = NamedArray(Array{Float64}(undef, 20, 3), (time_labels, region_labels), (:time, :regions))
for t in 1:num_time_labels
    sigma2[t, 1] = (1. - 0.05)^t * 0.58
    sigma2[t, 2] = (1. - 0.04)^t * 0.5
    sigma2[t, 3] = (1. - 0.045)^t * 0.6
end

run1 = run_my_model()

expanded_get_dataframe = getdataframe(run1, :grosseconomy =>(:YGROSS, :K))
curr_get_dataframe = getdataframe(run1, :grosseconomy, :YGROSS)
@test(expanded_get_dataframe[!, 3] == curr_get_dataframe[!, 3])

expanded_get_dataframe = getdataframe(run1, :grosseconomy => :YGROSS, :emissions => :E)
curr_get_dataframe = getdataframe(run1, :emissions, :E)
@test(expanded_get_dataframe[!, 4] == curr_get_dataframe[!, 3])

#################
## MORE TESTS ###
#################
my_model = Model()
set_dimension!(my_model, :time, collect(2015:5:2110))

#Testing that you cannot add two components of the same name
@defcomp testcomp1 begin
    var1 = Variable(index=[time])
    par1 = Parameter(index=[time])

    function run_timestep(p, v, d, t)
        v.var1[t] = p.par1[t]
    end
end

par = collect(2015:5:2110)
add_comp!(my_model, testcomp1)

set_param!(my_model, :testcomp1, :par1, par)
run(my_model)

#Regular getdataframe
dataframe = getdataframe(my_model, :testcomp1 => :var1)
@test(dataframe[!, 2] == par)

#Test trying to getdataframe from component that does not exist
@test_throws ErrorException getdataframe(my_model, :testcomp1 => :var2)

end #module


export iNALU

"""
    iNALU(in::Int, out::Int; initNAC=glorot_uniform, initG=glorot_uniform, ϵ=1e-7, ω=20)

Improved NALU that can process negative numbers by recovering the multiplication
sign. Implemented as suggested in: https://arxiv.org/abs/2003.07629
"""
struct iNALU{Tg<:AbstractMatrix,T<:Real}
    a_nac::NAC
    m_nac::NAC
    G::Tg
    ϵ::T
    ω::T
end

Flux.@functor iNALU

iNALU(a_nac::NAC, m_nac::NAC, G::AbstractMatrix) =
  iNALU(a_nac, m_nac, G{T}, T(1e-7), T(20)) where T

function iNALU(in::Int, out::Int;
              initNAC=glorot_uniform, initG=glorot_uniform, ϵ=1e-7, ω=20)
    a_nac = NAC(in, out, initW=initNAC, initM=initNAC)
    m_nac = NAC(in, out, initW=initNAC, initM=initNAC)
    G = initG(out, in)
    T = eltype(G)
    iNALU(a_nac,m_nac,G,T(ϵ),T(ω))
end

function Base.sign(nalu::iNALU, x::AbstractVector)
    W  = abs.(weights(nalu.m_nac))
    sm = reshape(sign.(x),1,:) .* W .+ 1 .- W
    s  = vec(prod(sm, dims=2))
end

function Base.sign(nalu::iNALU, x::AbstractMatrix)
    buf = Zygote.Buffer(x, size(nalu.G,1), size(x,2))
    for i in 1:size(x,2)
        buf[:,i] = sign(nalu, x[:,i])
    end
    copy(buf)
end

add(nalu::iNALU, x) = nalu.a_nac(x)
mult(nalu::iNALU, x) = exp.(min.(nalu.m_nac(log.(max.(abs.(x),nalu.ϵ))),nalu.ω))
gate(nalu::iNALU, x) = σ.(nalu.G*x)

function (nalu::iNALU)(x)
    a = add(nalu, x)
    m = mult(nalu, x)
    s = sign(nalu, x)
    g = gate(nalu, x)
    g .* a .+ (1.0 .- g) .* m .* s
end

Base.show(io::IO, l::iNALU) =
  print(io, "iNALU(in=$(size(l.G,2)), out=$(size(l.G,1)))")


using Test
using LinearAlgebra
using PolynomialBasis
# using Revise
using CutCellDG
include("../useful_routines.jl")

function polar_to_cartesian(radialposition, angularposition)
    x = radialposition .* cos.(angularposition)
    y = radialposition .* sin.(angularposition)
    return vcat(x', y')
end

L, W = 1.0, 1.0
nelmts = [4, 4]
center = [0.5, 0.5]
radius = 0.4
polyorder = 2

levelsetbasis = LagrangeTensorProductBasis(2, polyorder)
dim,nf = size(interpolation_points(levelsetbasis))
mesh = CutCellDG.CGMesh([0.0, 0.0], [L, W], nelmts, nf)
levelset = CutCellDG.LevelSet(
    x -> circle_distance_function(x, center, radius),
    mesh,
    levelsetbasis,
)
cutmesh = CutCellDG.CutMesh(mesh, levelset)

refseedpoints, seedcellids = CutCellDG.seed_zero_levelset(2, levelset, cutmesh)
seedpoints = CutCellDG.map_to_spatial(refseedpoints, seedcellids, mesh)

numquerypts = 100
queryangles = range(0, stop = 2pi, length = numquerypts)
queryradius = 0.1 .+ abs.(sin.(queryangles))
querypoints = center .+ polar_to_cartesian(queryradius, queryangles)

dx = CutCellDG.element_size(mesh)
tol = 1e3eps()
boundingradius = 1.5 * maximum(dx)
closestpoints, closestcellids, flags =
    CutCellDG.closest_points_on_zero_levelset(
        querypoints,
        seedpoints,
        seedcellids,
        levelset,
        tol,
        boundingradius,
    )

testcp = closest_point_on_arc(querypoints, center, radius)
err = norm(testcp - closestpoints, Inf)
# err = vec(mapslices(x->norm(x,Inf),closestpoints - testcp,dims=1))
# maxidx = argmax(err)
# maxerr = err[maxidx]

@test all(flags)
@test err < 0.02
################################################################################

################################################################################
levelsetbasis = HermiteTensorProductBasis(2)

dim,nf = size(interpolation_points(levelsetbasis))
mesh = CutCellDG.CGMesh([0.0, 0.0], [L, W], nelmts, nf)
levelset = CutCellDG.LevelSet(
    x -> circle_distance_function(x, center, radius)[1],
    mesh,
    levelsetbasis,
)
cutmesh = CutCellDG.CutMesh(mesh, levelset)

refseedpoints, seedcellids = CutCellDG.seed_zero_levelset(2, levelset, cutmesh)
seedpoints = CutCellDG.map_to_spatial(refseedpoints, seedcellids, mesh)

numquerypts = 100
queryangles = range(0, stop = 2pi, length = numquerypts)
queryradius = 0.1 .+ abs.(sin.(queryangles))
querypoints = center .+ polar_to_cartesian(queryradius, queryangles)

dx = CutCellDG.element_size(mesh)
tol = 1e3eps()
boundingradius = 1.5 * maximum(dx)
closestpoints, closestcellids, flags =
    CutCellDG.closest_points_on_zero_levelset(
        querypoints,
        seedpoints,
        seedcellids,
        levelset,
        tol,
        boundingradius,
    )

testcp = closest_point_on_arc(querypoints, center, radius)
err = norm(testcp - closestpoints, Inf)

@test all(flags)
@test err < 0.002


__precompile__()

module Primes

import Base

const offwheel = Array{Bool}(210)
fill!(offwheel, false)
for p in [2, 3, 5, 7], i in 1:210÷p
    offwheel[i*p] = true
end

const incs = Int[]

function gen_incs()
    c=1
    for i=2:210
        if offwheel[i]
            c+=1
        else
            push!(incs, c)
            c=1
        end
    end
    push!(incs,c)
end

gen_incs()

struct Iter{P <: Integer}
    small_primes :: Array{P}
    sieve :: Dict{P, P}
    offwheel :: Array{Bool}

end

function Iter{P}() where P <: Integer
    first_primes = P[   2,   3,   5,   7,  11,  13,  17,  19,  23,  29,  31
                    ,  37,  41,  43,  47,  53,  59,  61,  67,  71 , 73,  79
                    ,  83,  89,  97, 101, 103, 107, 109, 113, 127, 131, 137
                    , 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193
                    , 197, 199, 211]

    sieve = Dict{P, P}()

    res = Iter{P}(first_primes, sieve, offwheel)
end

const IterState{P} = Tuple{P, Int}

function Base.start(it::Iter{P})::IterState{P} where P <: Integer
    (1, length(incs)-1)
end

function is_wheeled_off(n :: P) :: Bool where P <: Integer
    offwheel[(n-1) % 210 + 1]
end

function Base.next(it::Iter{P}, state::IterState{P}) where P <: Integer
    previous, inc_idx = state

    if isempty(it.small_primes)
        candidate = previous + incs[inc_idx]

        inc_idx = inc_idx == length(incs) ? 1 : inc_idx+1

        period = pop!(it.sieve, candidate, 0)

        while period != 0
            scratched = candidate + period
            while is_wheeled_off(scratched) || haskey(it.sieve, scratched)
                scratched += period
            end
            it.sieve[scratched] = period

            candidate += incs[inc_idx]
            inc_idx = inc_idx == length(incs) ? 1 : inc_idx+1

            period = get(it.sieve, candidate, 0)
        end

        it.sieve[candidate^2] = candidate
        (candidate, (candidate, inc_idx))
    else
        prime = shift!(it.small_primes)

        if prime > 7
            scratched = prime^2
            while scratched < 210 || is_wheeled_off(scratched)
                scratched += prime
            end
            it.sieve[scratched] = prime
        end

        (prime, (prime, 1))
    end
end

Base.done(::Iter{P}, ::IterState{P}) where P <: Integer = false

function test(limit)
    it=Iter{Int}()
    i=1

    for p in it
        # println("$(i): $(p)")
        i += 1
        if i > limit
            return p
        end
    end
end

function iter(T)
    Iter{T}()
end

end # Module


# This file holds different types of investment functions
@inline function quadratic_investment(p::S, χ::S, δ::S) where {S <: Real}
    return (p .- 1.) .^ 2 ./ (2. .* χ) + (p .- 1.) ./ χ .+ δ
end

@inline function growth_quadratic_investment(p::S, χ::S, δ::S) where {S <: Real}
    return (p - 1.) / χ  + δ
end

@inline function derivative_quadratic_investment(p::S, χ::S) where {S <: Real}
    return p ./ χ + 1. ./ χ
end

@inline function derivative_quadratic_investment_li2020(p::S, χ::S) where {S <: Real}
    return p ./ χ
end


#Combined Gradient Descent with RMSprop element
#http://www.cs.toronto.edu/~tijmen/csc321/slides/lectureslideslec6.pdf

struct RMSprop{Tf,Tg,Th,Ti, IL,L} <: FirstOrderOptimizer
    mu::Tf
    alpha::Tg
	beta::Th
	epsilon::Ti
    alphaguess!::IL
    linesearch!::L
    manifold::Manifold
end

Base.summary(::RMSprop) = "Root Mean Square propagation"

"""
# Root Mean Square propagation (RMSprop)
## Constructor
```julia
RMSprop(; mu::Real = 0.9,
 alpha::Real = 0.00000001,
 beta::Real = 0.99,
 epsilon::Real = 0.000001,
 alphaguess = LineSearches.InitialPrevious(),
 linesearch = LineSearches.HagerZhang(),
```

## Description
RMSProp (for Root Mean Square Propagation) is a method in which the learning rate(here alpha) is adapted for
each of the parameters. The idea is to divide the learning rate for a weight by a running average of the
magnitudes of recent gradients for that function.

In this case, RMSprop is combined with simple gradient descent algorithm, which uses a line search step
to compute the next step.

## Formula
y = y_previous * beta + ( 1 - beta ) * g ^ 2

x = x_previous + mu * a * g + alpha * g / (sqrt(y) + epsilon)

parameters:

a= alpha calculated of line search

g= gradient of recent iteration

## References
 - RMSprop is an unpublished method proposed by Geoff Hinton in
  http://www.cs.toronto.edu/~tijmen/csc321/slides/lecture_slides_lec6.pdf
"""
function RMSprop(; mu::Real = 0.9,
								 alpha::Real = 0.00000001, # TODO: investigate good defaults
								 beta::Real = 0.99, # TODO: investigate good defaults
								 epsilon::Real = 0.000001, # TODO: investigate good defaults
                                 alphaguess = LineSearches.InitialPrevious(), # TODO: investigate good defaults
                                 linesearch = LineSearches.HagerZhang(),        # TODO: investigate good defaults
                                 manifold::Manifold=Flat())
    RMSprop(mu,alpha, beta,epsilon, alphaguess, linesearch, manifold)
end

mutable struct RMSpropState{Tx, T} <: AbstractOptimizerState
    x::Tx
    x_previous::Tx
	y::Tx
    f_x_previous::T
    s::Tx
    @add_linesearch_fields()
end

function initial_state(method::RMSprop, options, d, initial_x)
    T = eltype(initial_x)
    initial_x = copy(initial_x)
	initial_y = copy(initial_x)
	fill!(initial_y,0.0)
    retract!(method.manifold, initial_x)

    value_gradient!!(d, initial_x)

    project_tangent!(method.manifold, gradient(d), initial_x)

    RMSpropState(initial_x, # Maintain current state in state.x
                                 copy(initial_x), # Maintain previous state in state.x_previous
								 initial_y,
                                 real(T)(NaN), # Store previous f in state.f_x_previous
                                 similar(initial_x), # Maintain current search direction in state.s
                                 @initial_linesearch()...)
end

function update_state!(d, state::RMSpropState, method::RMSprop)
    project_tangent!(method.manifold, gradient(d), state.x)
    # Search direction is always the negative gradient
    state.s .= .-gradient(d)

    # Update moving average of squared gradients
    state.y .= state.y.*method.beta .+(1-method.beta).*NaNMath.pow.(state.s,2)

    # Determine the distance of movement along the search line
    lssuccess = perform_linesearch!(state, method, ManifoldObjective(method.manifold, d))

    # Update current position with alpha step + element calculated of RMSprop algorithm
    state.x .+= method.mu.*state.alpha.*state.s .+method.alpha.*state.s./(Base.sqrt.(state.y).+method.epsilon)
    retract!(method.manifold, state.x)
    lssuccess == false # break on linesearch error
end

function trace!(tr, d, state, iteration, method::RMSprop, options, curr_time=time())
  common_trace!(tr, d, state, iteration, method, options, curr_time)
end

function default_options(method::RMSprop)
    Dict(:allow_f_increases => true)
end


using LightGraphs
using MatrixMarket
using SimpleWeightedGraphs

name = splitext(splitdir(ARGS[1])[2])[1]
A = mmread(ARGS[1])
A = spdiagm(diag(A)) - A
g = SimpleWeightedGraph(A)

tris = Int(sum(triangles(g))//3)
clustering = global_clustering_coefficient(g)
lc = local_clustering_coefficient(g)
avg_clustering = mean(lc)
#= diam = parallel_diameter(g) =#

println("graph,triangles,clustering,avg_clustering")
@printf "%s,%s,%s,%s" name tris clustering avg_clustering


# Factorize two different matrices (one real, one complex) and solve for multiple rhs and free memory

using MUMPS
if VERSION >= v"0.5.0-dev+7720"
    using Base.Test
else
    using BaseTestNext
    const Test = BaseTestNext
end
include("getDivGrad.jl");

A  = getDivGrad(24,23,25);
A2 = getDivGrad(34,32,36);

n  = size(A,1);
n2 = size(A2,1);
A  = A + im*spdiagm(rand(n),0);

nrhs = 10;
rhs = randn(n,nrhs) + im*randn(n,nrhs);
rhs2 = randn(n2,nrhs);

println("Factorize complex matrix")
tic();
F1 = factorMUMPS(A,1);
toc();

println("Factorize real matrix")
tic();
F2 = factorMUMPS(A2,1);
toc();

println("Solve complex system")
tic();
x = applyMUMPS(F1,rhs);
toc();
err = zeros(nrhs)
for i=1:nrhs
        err[i] =  norm(A*x[:,i]-rhs[:,i]) / norm(rhs[:,i]);
end
@test maximum(err) < 1e-14

println("Solve real system")
tic();
x2 = applyMUMPS(F2,rhs2);
toc();
err = zeros(nrhs)
for i=1:nrhs
        err[i] =  norm(A2*x2[:,i]-rhs2[:,i]) / norm(rhs2[:,i]);
end
@test maximum(err) < 1e-14

println("Free memory")
destroyMUMPS(F1);
destroyMUMPS(F2);

println("DONE!")


using FiniteDiff
using StaticArrays
import Dolark
import Dolo

# @testset "Test the 𝒜 derivatives w.r.t. x, μ, z and y" begin

hmodel = Dolark.HModel("models/ayiagari.yaml")

sol_agent = Dolo.improved_time_iteration(hmodel.agent)
μ = Dolo.ergodic_distribution(hmodel.agent, sol_agent)
dmodel = Dolark.discretize(hmodel, sol_agent)

y0, z0 = hmodel.calibration[:aggregate, :exogenous]
y = SVector(y0...)
z = SVector(z0...)

A, R_A_mu, R_A_x, R_A_y, R_A_z = Dolark.𝒜(dmodel, μ, dmodel.F.x0, y, z; diff=true)

Jμ_exact = convert(Matrix, R_A_mu)
Jμ_num = FiniteDiff.finite_difference_jacobian(mu -> Dolark.𝒜(dmodel, mu, dmodel.F.x0, y, z), μ)

Jx_exact = convert(Matrix, R_A_x)

flatten(u::Dolo.MSM) = cat(u.data...;dims=1)                              # converts an MSM into a float64 vector
unflatten(u, x) = Dolo.MSM([reinterpret(eltype(x.data),u)...], x.sizes)   # converts a float64 vector into an MSM

x0_flat = flatten(dmodel.F.x0)

Jx_num = FiniteDiff.finite_difference_jacobian(X0 -> Dolark.𝒜(dmodel, μ, unflatten(X0, x0), y, z), x0_flat)

eltype(μ)
eltype(dmodel.F.x0)
typeof(dmodel.F.x0)

    R_A_x

    Jy_exact = convert(Matrix, R_A_y)
    Jy_num = FiniteDiff.finite_difference_jacobian(Y-> Dolark.𝒜(dmodel, μ, dmodel.F.x0, Y, z), y)

    Jz_exact = convert(Matrix, R_A_z)
    Jz_num = FiniteDiff.finite_difference_jacobian(Z-> Dolark.𝒜(dmodel, μ, dmodel.F.x0, y, Z), z)

    maximum(abs, Jμ_num - Jμ_exact) < 1e-8 #@assert

    maximum(abs, Jx_num - Jx_exact) < 1e-8 #@assert

    maximum(abs, Jy_num - Jy_exact) < 1e-8 #@assert

    maximum(abs, Jz_num - Jz_exact) < 1e-8 #@assert

v0 = []

# end


"""
Original publication:
L. Yin, H. Wang, and W. Fan. Active learning based
support vector data description method for robust
novelty detection. Knowledge-Based Systems, pages
40–52, Aug. 2018.
"""
struct BoundaryNeighborCombinationQs <: HybridQs
    occ::OCClassifier
    nn_dist::Array{Float64,1}
    η::Float64
    p::Float64
    BoundaryNeighborCombinationQs(occ::OCClassifier, x::Array{T, 2}; η=0.7, p=0.15) where T <: Real = new(occ, knn_mean_dist(x; k=1), η, p)
end

function qs_score(qs::BoundaryNeighborCombinationQs, x::Array{T, 2}, labels::Dict{Symbol, Array{Int, 1}})::Array{Float64, 1} where T <: Real
    @assert length(qs.nn_dist) == size(x, 2)
    if rand() < qs.p
        return rand(size(x,2))
    end
    prediction = abs.(predict(qs.occ, x))
    d_hyp = (prediction .- minimum(prediction[labels[:U]])) ./ maximum(prediction[labels[:U]])
    d_nn = (qs.nn_dist .- minimum(qs.nn_dist[labels[:U]])) ./ maximum(qs.nn_dist[labels[:U]])
    return qs.η * (-d_hyp) + (1 - qs.η) * (-d_nn)
end


@testset "synchronous.jl" begin
    x, A = demodata()
    Ã = A .- mean(A, dims=1)

    Φ = synchronous(Ã)
    @test Φ ≈ cov(Ã)

    @plottest begin
        heatmap(x, x, Φ)
        plot!(xflip=true, yflip=true)
        xlabel!("Peak position")
        ylabel!("Peak position")
        title!("Synchronous")
    end "synchronous.png"

    @plottest begin
        plot(x, diag(Φ), label=:none)
        xlabel!("x")
        ylabel!("I")
    end "autocorrelation.png"
end


module Mean
using ValidatedNumerics
using Distributions
using Dierckx
using StatsBase

abstract BaseEstimator

function estimate(values, method :: BaseEstimator)
    error("Unsupported method")
end

immutable byKnownVariance <: BaseEstimator
    confidence_probability :: Real
    variance :: Real
end

function estimate(values, method::byKnownVariance)
    interval_confidence = getIntervalConfidenceProbability(method.confidence_probability)
    average = mean(values)
    count = length(values)
    quantile = normalQuantile(interval_confidence)
    delta = quantile * sqrt(method.variance) / sqrt(count)
    average + @interval(-delta, delta)
end

immutable byStandardError <: BaseEstimator
end

function estimate(values, method::byStandardError)
    average, variance = mean_and_var(values)
    count = length(values)
    fixed_variance = variance*count/(count - 1)
    delta = sqrt(fixed_variance/count)
    average + @interval(-delta, delta)
end

immutable byUnknownVariance <: BaseEstimator
    confidence_probability :: Real
end

function estimate(values, method::byUnknownVariance)
    average = mean(values)
    count = length(values)
    interval_confidence = getIntervalConfidenceProbability(method.confidence_probability)
    variance = mapreduce((x) -> (x - average)^2, +, values) / (count - 1)
    quantile = studentQuatile(interval_confidence, count - 1)
    delta = quantile * sqrt(variance) / sqrt(count)
    average + @interval(-delta, delta)
end

immutable byMeanAbsDeviation <: BaseEstimator
    confidence_probability :: Real
end

function estimate(values, method :: byMeanAbsDeviation)
    average = mean(values)
    count = length(values)
    interval_confidence = getIntervalConfidenceProbability(method.confidence_probability)
    mean_abs_deviation = mapreduce((x) -> abs(x - average), +, values) / count
    quantile = getMeanAbsDeviationQuantile(interval_confidence, count)
    delta = mean_abs_deviation * quantile
    average + @interval(-delta, delta)
end

immutable byInterQuartileRange <: BaseEstimator
    confidence_probability :: Real
end

function estimate(values, method :: byInterQuartileRange)
    interval_confidence = getIntervalConfidenceProbability(method.confidence_probability)
    count = length(values)
    ordered_values = sort(values)
    interquartile_range = getInterQuartileRange(ordered_values, count)
    interquertile_quitile = getInterQuartileQuintile(interval_confidence, count)
    mediam = getMediamOfSorted(ordered_values, count)
    delta = interquartile_range * interquertile_quitile
    mediam + @interval(-delta, delta)
end

function getIntervalConfidenceProbability(confidence_probability)
    (1.0 + confidence_probability) * 0.5
end

function getInterQuartileRange(values, count)
    # TODO this can be replaced with StatsBase.iqr
    quartile_index = div((count + 1), 4)
    values[3*quartile_index] - values[quartile_index]
end

function getMediamOfSorted(values, count)
    if isodd(count)
        0.5*(values[div(count, 2)] + values[div(count, 2) + 1])
    else
        values[div((count + 1), 2)]
    end
end

function getInterQuartileQuintile(value, count)
    const knots = [
    11 0.470 0.623 0.876
    15 0.400 0.514 0.678
    19 0.354 0.448 0.573
    23 0.321 0.402 0.506
    27 0.296 0.367 0.458
    31 0.276 0.341 0.422
    35 0.260 0.319 0.393
    39 0.246 0.301 0.369
    43 0.234 0.289 0.349
    47 0.224 0.273 0.332
    51 0.215 0.261 0.317
    ]
    x_knots = knots[:, 1]
    if value == 0.95
        y_knots = knots[:, 2]
    elseif value == 0.975
        y_knots = knots[:, 3]
    elseif value == 0.99
        y_knots = knots[:, 4]
    else
        error("Value=$value is not allowed")
    end
    itp = Spline1D(x_knots, y_knots, k=1, bc="extrapolate")
    evaluate(itp, count)
end

function getMeanAbsDeviationQuantile(value, count)
    if value != 0.95 && value != 0.975
        error("Value=$value is not allowed")
    end
    const knots = [
    2 12.71;
    3 3.45;
    4 2.16;
    5 1.66;
    6 1.40;
    7 1.21;
    8 1.09;
    9 1.00;
    10 0.93;
    11 0.87;
    12 0.82;
    13 0.78;
    14 0.75;
    20 0.71;
    25 0.60;
    30 0.48;
    40 0.41;
    60 0.33;
    120 0.23;
    ]
    x_knots = knots[:, 1]
    y_knots = knots[:, 2]
    itp = Spline1D(x_knots, y_knots, k=1, bc="extrapolate")
    evaluate(itp, count)
end

function studentQuatile(value, freedomDegreeNumber)
    d = TDist(freedomDegreeNumber)
    quantile(d, value)
end

function normalQuantile(value)
    d = Normal()
    quantile(d, value)
end

end


# === PRIVATE FUNCTIONS THAT ARE NOT EXPORTED =============================================================== #
# Objective functions -
function _obj_function_logistics_regression(parameters::Array{Float64,1}, labels::Array{Int64,1},
    dataMatrix::Array{Float64,2}, bias::Float64 = 0.0)::Float64

    # initialize -
    (number_of_rows, number_of_cols) = size(dataMatrix)
    prob_array = Array{Float64,1}(undef,number_of_rows)
    term_array = Array{Float64,2}(undef,number_of_rows,3)

    # augment the dataMatrix -
    X = [ones(number_of_rows) dataMatrix]

    # ok, so let's compute the log liklehood -
    # start w/the prob -
    for row_index = 1:number_of_rows
        prob_array[row_index] = _logistics_classifier_logic(parameters, X[row_index,:], bias)
    end

    # compute the term array -
    for row_index = 1:number_of_rows

        # compute term 1 and 2 -
        term_1_value = labels[row_index]*log(prob_array[row_index])
        term_2_value = (1 - labels[row_index])*log((1 - prob_array[row_index]))

        if (isnan(term_1_value) == true)
            term_1_value = 0.0
        end

        if (isnan(term_2_value) == true)
            term_2_value = 0.0
        end

        # package -
        term_array[row_index,1] = term_1_value
        term_array[row_index,2] = term_2_value
        term_array[row_index,3] = term_1_value + term_2_value
    end

    # check for inf -
    tmp = sum(term_array[:,3])
    if (isinf(tmp) == true)
        tmp = 0.0
    end

    # compute log liklehood -
    LL = -1*tmp

    # return -
    return LL
end

function _obj_function_linear_regression(parameters::Array{Float64,1}, outputVector::Array{Float64,1},
    dataMatrix::Array{Float64,2}, lambda::Float64)::Float64

    # compute the Y_model -
    results_tuple = _evaluate_ols_linear_model(outputVector, dataMatrix, parameters)

    # the results_tuple contains the residual_value already (yes!)
    # we add a "regularization" term (L2 ridge regression) to avoid the overfitting issue -
    rms_error = results_tuple.residual + (lambda*sum(parameters.^2))

    # return -
    return rms_error
end

function _obj_function_linear_regression(parameters::Array{Float64,1}, outputVector::Array{Float64,1},
    dataMatrix::Array{Float64,1})::Float64

    # compute the Y_model -
    results_tuple = _evaluate_ols_linear_model(outputVector, dataMatrix, parameters)

    # the results_tuple contains the residual_value already (yes!)
    # we add a "regularization" term (L2 ridge regression) to avoid the overfitting issue -
    rms_error = results_tuple.residual + (lambda*sum(parameters.^2))

    # return -
    return rms_error
end

# evaluate linear model -
function _evaluate_ols_linear_model(dataMatrix::Array{Float64,1}, paramaterArray::Array{Float64,1})::Float64

    # initialize -
    number_of_rows = length(dataMatrix)

    # augement the data array 1
    X = [1.0 transpose(dataMatrix)]

    # compute the Y_model -
    Y_model = X*paramaterArray

    return Y_model
end

function _evaluate_ols_linear_model(outputArray::Array{Float64,1}, dataMatrix::Array{Float64,2},
    paramaterArray::Array{Float64,1})::NamedTuple

    # initialize -
    (number_of_rows, number_of_cols) = size(dataMatrix)
    Y_measured = outputArray

    # augement the data array 0
    ones_array = ones(number_of_rows)
    X = [ones_array dataMatrix]

    # compute the Y_model -
    Y_model = X*paramaterArray

    # what is the correlation?
    correlation_value = cor(Y_measured, Y_model)

    # what is the residual?
    residual_value = rmsd(Y_measured, Y_model)

    # package -
    results_tuple = (model_prediction=Y_model, residual=residual_value, correlation=correlation_value)

    # return -
    return results_tuple
end

function _evaluate_ols_linear_model(outputArray::Array{Float64,1}, dataMatrix::Array{Float64,1},
    paramaterArray::Array{Float64,1})::NamedTuple

    # initialize -
    number_of_rows = length(outputArray)
    Y_measured = outputArray

    # augement the data array 0
    ones_array = ones(number_of_rows)
    X = [ones_array dataMatrix]

    # compute the Y_model -
    Y_model = X*paramaterArray

    # what is the correlation?
    correlation_value = cor(Y_measured, Y_model)

    # what is the residual?
    residual_value = rmsd(Y_measured, Y_model)

    # package -
    results_tuple = (model_prediction=Y_model, residual=residual_value, correlation=correlation_value)

    # return -
    return results_tuple
end

# default: leave one out logic -
function _leave_one_out_logic(index::Int64, outputVector::Array{Float64,1}, dataMatrix::Array{Float64,2})::NamedTuple

    # ok, so need to impl leave one out -
    (number_of_rows, number_of_cols) = size(dataMatrix)

    # generate the "full" range -
    idx_full_index_array = range(1,stop=number_of_rows,step=1) |> collect

    # generate index array with a row = index missing - this is the training index array
    idx_missing_index_array = setdiff(idx_full_index_array, index)

    # what index's did we leave out?
    prediction_index_array = Array{Int64,1}()
    push!(prediction_index_array, index)

    # collect -
    Yhat = outputVector[idx_missing_index_array]
    Xhat = dataMatrix[idx_missing_index_array,:]

    # package and return -
    results_tuple = (output_vector=Yhat, input_matrix=Xhat,
        training_index_array=idx_missing_index_array,
        prediction_index_array=prediction_index_array)

    # return -
    return results_tuple
end
# =========================================================================================================== #

# === PUBLIC FUNCTIONS THAT ARE EXPORTED ==================================================================== #
"""
    mle_logistic_model_classifier_cross_validation(labelVector::Array{Int64,1}, dataMatrix::Array{Float64,2},
        numberOfGroups::Int64; selectionFunction::Union{Nothing, Function} = nothing)::VLResult
"""
function mle_logistic_model_classifier_cross_validation(labelVector::Array{Int64,1}, dataMatrix::Array{Float64,2},
    numberOfGroups::Int64; selectionFunction::Union{Nothing, Function} = nothing)::VLResult

    # initialize -
    (number_of_rows, number_of_cols) = size(dataMatrix)

    try

        ex = ErrorException("Ooops! mle_logistic_model_classifier_cross_validation not yet implemented.");
        throw(ex)

    catch error
        return VLResult(error)
    end
end

"""
    mle_fit_logistic_model_classifier(labelVector::Array{Int64,1}, dataMatrix::Array{Float64,2};
        initialParameterArray::Union{Nothing,Array{Float64,1}} = nothing, maxIterations::Int64=10000,
        showTrace::Bool = false, bias::Float64=0.0)::VLResult
"""
function mle_fit_logistic_model_classifier(labelVector::Array{Int64,1}, dataMatrix::Array{Float64,2};
    initialParameterArray::Union{Nothing,Array{Float64,1}} = nothing, maxIterations::Int64=10000,
    showTrace::Bool = false, bias::Float64=0.0)::VLResult

    # initialize -
    (number_of_rows, number_of_cols) = size(dataMatrix)

    try

        # setup the objective function -
        OF(p) = _obj_function_logistics_regression(p,labelVector,dataMatrix, bias)

        # setup initial guess -
        pinitial = 0.1*ones(number_of_cols+1)
        if (isnothing(initialParameterArray) == false)
            pinitial = initialParameterArray
        end

        # call the optimizer -
        opt_result = optimize(OF, pinitial, NelderMead(),
            Optim.Options(iterations=maxIterations, show_trace=showTrace))

        # get the optimal parameters -
        β = Optim.minimizer(opt_result)

        # return -
        return VLResult(β)
    catch error
        return VLResult(error)
    end
end

"""
    ols_fit_linear_model(outputVector::Array{Float64,1}, dataMatrix::Array{Float64,1})::VLResult
"""
function ols_fit_linear_model(outputVector::Array{Float64,1}, dataMatrix::Array{Float64,1};
    lambda::Float64 = 0.0, initialParameterArray::Union{Nothing,Array{Float64,1}} = nothing, maxIterations::Int64=10000,
    showTrace::Bool = false)::VLResult

    # initialize -
    number_of_cols = 1 # we have a single data col

    try

        # setup the obj function -
        OF(p) = _obj_function_linear_regression(p,outputVector,dataMatrix, lambda)

        # setup initial guess -
        pinitial = 0.1*ones(number_of_cols+1)
        if (isnothing(initialParameterArray) == false)
            pinitial = initialParameterArray
        end

        # call the optimizer -
        opt_result = optimize(OF, pinitial, NelderMead(),
            Optim.Options(iterations=maxIterations, show_trace=showTrace))

        # get the optimal parameters -
        β = Optim.minimizer(opt_result)

        # compute some performance stuff -
        performance_tuple = _evaluate_ols_linear_model(outputVector,dataMatrix,β)

        # setup results tuple -
        results_tuple = (model_prediction=performance_tuple.model_prediction,
            correlation=performance_tuple.correlation, residual=performance_tuple.residual,
            parameters=β)

        # return -
        return VLResult(results_tuple)
    catch error
        return VLResult(error)
    end
end

"""
    ols_fit_linear_model(outputVector::Array{Float64,1}, dataMatrix::Array{Float64,2})::VLResult
"""
function ols_fit_linear_model(outputVector::Array{Float64,1}, dataMatrix::Array{Float64,2}; lambda::Float64 = 0.0,
    initialParameterArray::Union{Nothing,Array{Float64,1}} = nothing, maxIterations::Int64=10000,
    showTrace::Bool = false)::VLResult

    # initialize -
    (number_of_rows, number_of_cols) = size(dataMatrix)

    try

        # setup the obj function -
        OF(p) = _obj_function_linear_regression(p,outputVector,dataMatrix)

        # setup initial guess -
        pinitial = 0.1*ones(number_of_cols+1)
        if (isnothing(initialParameterArray) == false)
            pinitial = initialParameterArray
        end

        # call the optimizer -
        opt_result = optimize(OF, pinitial, NelderMead(),
            Optim.Options(iterations=maxIterations, show_trace=showTrace))

        # get the optimal parameters -
        β = Optim.minimizer(opt_result)

        # compute some performance stuff -
        performance_tuple = _evaluate_ols_linear_model(outputVector,dataMatrix,β)

        # setup results tuple -
        results_tuple = (model_prediction=performance_tuple.model_prediction,
            correlation=performance_tuple.correlation, residual=performance_tuple.residual,
            parameters=β)

        # return -
        return VLResult(results_tuple)
    catch error
        return VLResult(error)
    end
end

"""
    ols_fit_linear_model_cross_validation(outputVector::Array{Float64,1},
        dataMatrix::Array{Float64,2}; numberOfGroups::Int64 = 0, selectionFunction::Union{Nothing, Function} = nothing)::VLResult
"""
function ols_fit_linear_model_cross_validation(outputVector::Array{Float64,1},
    dataMatrix::Array{Float64,2}; numberOfGroups::Int64 = 0, numberOfElementsPerGroup::Int64 = 0, lambda::Float64 = 0.0,
    selectionFunction::Union{Nothing, Function} = nothing)::VLResult

    # initialize -
    (number_of_rows, number_of_cols) = size(dataMatrix)

    try

        # check: if numberOfGroups = 0, then set to the number of rows -
        if (numberOfGroups == 0)
            numberOfGroups = number_of_rows
        end

        if (numberOfElementsPerGroup == 0)
            numberOfElementsPerGroup = 1
        end

        # check: if selectionFunction == nothing, then use _leave_one_out_logic -
        mySelectionFunction = _leave_one_out_logic
        if (isnothing(selectionFunction) == false)
            mySelectionFunction = selectionFunction
        end

        # initialize storage -
        parameter_storage_array = zeros((number_of_cols + 1), numberOfGroups)
        total_residual_array = Array{Float64,1}()
        total_correlation_array = Array{Float64,1}()
        model_training_array = zeros(number_of_rows, numberOfGroups)
        model_prediction_archive = Array{Float64,1}()
        measured_output_array = zeros(number_of_rows, numberOfGroups)
        selection_index_archive = Array{Array{Int64,1},1}()

        # ok, lets go ...
        for group_index = 1:numberOfGroups

            # pass the full data set, and the group index to the selection function
            # the selectionFunction gives back the output and input arrsy -
            selection_tuple = mySelectionFunction(group_index,outputVector,dataMatrix)
            Yhat = selection_tuple.output_vector
            Xhat = selection_tuple.input_matrix
            selection_index_array = selection_tuple.training_index_array
            prediction_index_array = selection_tuple.prediction_index_array

            # cache the selection index buffer -
            push!(selection_index_archive, selection_index_array)

            # YHat -
            # scale_result = z_score_transform_vector(Yhat)
            # if (isa(scale_result.value,Exception) == true)
            #     throw(scale_result.value)
            # end
            # Yhat_z_scaled = scale_result.value

            # # Input array -
            # scale_result = z_score_transform_array(Xhat)
            # if (isa(scale_result.value,Exception) == true)
            #     throw(scale_result.value)
            # end
            # Xhat_z_scaled = scale_result.value

            # fit the model -
            fit_model_result = ols_fit_linear_model(Yhat, Xhat; lambda)
            if (isa(fit_model_result.value,Exception) == true)
                throw(fit_model_result.value)
            end
            performance_tuple = fit_model_result.value
            theta_parameters = performance_tuple.parameters
            model_output = performance_tuple.model_prediction

            # compute the prediction - leave 1 out for now ...
            Y_prediction = _evaluate_ols_linear_model(dataMatrix[group_index,:], theta_parameters)

            # capture the model output -
            for (output_index, output_value) in enumerate(model_output)
                real_index = selection_index_array[output_index]
                model_training_array[real_index, group_index] = output_value
            end

            # capture the predicted output -
            push!(model_prediction_archive,Y_prediction)

            # capture the measured output -
            for (output_index, output_value) in enumerate(Yhat)
                real_index = selection_index_array[output_index]
                measured_output_array[real_index, group_index] = output_value
            end

            # put the parameters in storage -
            for (parameter_index,parameter_value) in enumerate(theta_parameters)
                parameter_storage_array[parameter_index, group_index] = parameter_value
            end

            # grab the residual and other stuff for this group -
            push!(total_residual_array, performance_tuple.residual)
            push!(total_correlation_array, performance_tuple.correlation)
        end

        # return the results in a NamedTuple -
        results_tuple = (correlation=total_correlation_array,
            residual=total_residual_array,parameters=parameter_storage_array,
            model_training_array=model_training_array,
            model_prediction_array=model_prediction_archive,
            measured_output_array=measured_output_array,
            selection_index_archive=selection_index_archive)

        # return -
        return VLResult(results_tuple)
    catch error
        return VLResult(error)
    end
end
# =========================================================================================================== #


using SupervisedLearning
using UnicodePlots

import EmpiricalRisks
import Regression

# using RDatasets

# dataFrame = dataset("datasets", "mtcars")
# formula = AM ~ DRat + WT

# data = dataSource(formula, dataFrame, SignedClassEncoding)
# model = Classifier.LogisticRegression(l2_coef = .001)
# solver = Solver.GradientDescent()

# train!(model, data, solver, max_iter = 1000, break_every = 10) do
#   println("Iterations: $(iterations(model))")
#   println("Cost: $(cost(model))")
# end

# print(lineplot(trainingCurve(model)...))

# d = 100
# n = 10000
# w = randn(d+1)
# X = randn(d, n)
# t = sign(X'w[1:d] + w[d+1] + 0.01 * randn(n))

# f1 = Regression.RegRiskFun(EmpiricalRisks.riskmodel(EmpiricalRisks.AffinePred(d, 1.), EmpiricalRisks.LogisticLoss()),
#                           Regression.SqrL2Reg(1.0e-4),
#                           X, t)

# buf = zeros(size(t))
# f(θ) = Regression.value!(buf, f1, θ)
# function g!{T<:FloatingPoint}(θ::StridedArray{T}, storage::StridedArray{T})
#   v_risk, _ = Regression.value_and_addgrad!(buf, f1.rmodel, zero(T), storage, one(T), θ, f1.X, f1.Y)
#   v_regr, _ = Regression.value_and_addgrad!(f1.reg, one(T), storage, one(T), θ)
#   storage
# end
# function f_and_g!{T<:FloatingPoint}(θ::StridedArray{T}, storage::StridedArray{T})
#   v_risk, _ = Regression.value_and_addgrad!(buf, f1.rmodel, zero(T), storage, one(T), θ, f1.X, f1.Y)
#   v_regr, _ = Regression.value_and_addgrad!(f1.reg, one(T), storage, one(T), θ)
#   v_risk + v_regr
# end

# using Optim

# dall = DifferentiableFunction(f, g!, f_and_g!)

# println("Optim")
# theta1 = zeros(d+1)
# @time theta1 = optimize(dall, theta1, method = :gradient_descent, iterations = 100, ftol = 1.0e-6, xtol = 1.0e-8, grtol = 1.0e-8)
# theta1 = zeros(d+1)
# @time theta1 = optimize(dall, theta1, method = :gradient_descent, iterations = 10000, ftol = 1.0e-6, xtol = 1.0e-8, grtol = 1.0e-8)

# println("Regression")
# theta2 = zeros(d+1)
# @time sol = Regression.solve!(Regression.GD(), f1, theta2,
#                     Regression.Options(maxiter = 100, ftol = 1.0e-6, xtol = 1.0e-8, grtol = 1.0e-8),
#                     Regression.no_op)
# theta2 = zeros(d+1)
# @time sol = Regression.solve!(Regression.GD(), f1, theta2,
#                     Regression.Options(maxiter = 10000, ftol = 1.0e-6, xtol = 1.0e-8, grtol = 1.0e-8),
#                     Regression.no_op)

# println()
# println(theta1)
# println()
# println(theta2)
# println(sol)
# data = dataSource(X, t, SignedClassEncoding(["no","yes"]))
# model = Classifier.LogisticRegression(l2_coef = .01)
# solver = Solver.GradientDescent()

# println("State: $(state(model))")
# println("Iterations: $(iterations(model))")
# println("Cost: $(cost(model))")

# @time train!(model, data, solver, max_iter = 100, break_every = 500)
# @time train!(model, data, solver, max_iter = 10000, break_every = 500)
# train!(model, data, solver, max_iter = 10000, break_every = 500) do
#   println("Iterations: $(iterations(model))")
#   println("Cost: $(cost(model))")
# end

# train!(model, data, solver, max_iter = 1000, break_every = 100) do
#   println("Iterations: $(iterations(model))")
# end
# #lineplot(trainingCurve(model)...)
# println()
# print(lineplot(trainingCurve(model)...))
# println("State: $(state(model))")
# println("Iterations: $(iterations(model))")
# println("Fitness: $(fitness(model))")
# println("Cost: $(cost(model))")
#@test_approx_eq w model._risk_learner._coefficients

# using EmpiricalRisks
# using ArrayViews

# pm = AffinePred(d, 1.)
# @time s = predict(pm, w, X)
# @time s = predict(pm, w, X)

# r = zeros(n)
# @time predict!(r, pm, w, X)
# @time predict!(r, pm, w, X)

# println("Loop with predict")
# @time for i = 1:1000
#   s = predict(pm, w, X)
# end

# println("Loop with predict!")
# @time for i = 1:1000
#   s = predict!(r, pm, w, X)
# end

# s = predict(pm, w, X)
# @test_approx_eq s r

# println("Risk models")
# rmodel = riskmodel(pm, LogisticLoss())
# value(rmodel, w, X, t)
# @time value(rmodel, w, X, t)

# rmodel = riskmodel(pm, LogisticLoss())
# value!(r, rmodel, w, X, t)
# @time value!(r, rmodel, w, X, t)

# println("Loop with value")
# @time for i = 1:100
#   s = value(rmodel, w, X, t)
# end

# println("Loop with value!")
# @time for i = 1:100
#   s = value!(r, rmodel, w, X, t)
# end

# data = dataSource(X, t, SignedClassEncoding(["no","yes"]))
# model = Classifier.LogisticRegression(l2_coef = .001)
# solver = Solver.GradientDescent()

# println("State: $(state(model))")
# println("Iterations: $(iterations(model))")
# println("Cost: $(cost(model))")

# train!(model, data, solver, max_iter = 1000, break_every = 100)
# @time train!(model, data, solver, max_iter = 1000, break_every = 100)

# # train!(model, data, solver, max_iter = 1000, break_every = 100) do
# #   println("Iterations: $(iterations(model))")
# # end
# #lineplot(trainingCurve(model)...)
# #@time print(lineplot(trainingCurve(model)...))
# println("State: $(state(model))")
# println("Iterations: $(iterations(model))")
# println("Fitness: $(fitness(model))")
# println("Cost: $(cost(model))")


using ModelingToolkit
using Base.Test

# `Expr`, `Number` -> `Operation`
@IVar a
@Param b
@DVar x(t)
@Var y
@test parse(Operation, 2) == 2
expr = :(-inv(2sqrt(+($a, $b))))
op   = Operation(-, [Operation(inv,
                [Operation(*, [2, Operation(sqrt,
                              [Operation(+, [a, b])])])])])
@test parse(Operation, expr) == op
expr1 = :($x^($y-1))
op1   = Operation(^, [x, Operation(-, [y, 1])])
@test parse(Operation, expr1) == op1


# Thompson sampling.
# Based on https://arxiv.org/abs/1111.1797.

# Argument is the type of conjugate distribution.
# Assumption if needed: unit variance. (Only useful for normal distribution/priors, for now.)
mutable struct ThompsonSampling{D <: Distribution} <: Policy end

ThompsonSampling() = ThompsonSampling{Normal}() # Default prior distribution: normal.

struct ThompsonSamplingDetails <: PolicyDetails
  solver_time::Float64
end

# Beta priors: assume initially α = β = 1 (i.e. the prior state is to have observed one failure and one success
# for each arm).
function _ts_sample(::ThompsonSampling{Beta}, state::State{T}) where T
  return Dict(arm => rand(Beta(state.arm_counts[arm] * state.arm_average_reward[arm] + 1,
                               state.arm_counts[arm] * (1 - state.arm_average_reward[arm]) + 1))
                     for arm in keys(state.arm_counts))
end

# Normal priors: assume that the arms have a normal distribution with unknown average and known unit variance.
# Assume that the initial average is zero. This simplifies the formulae a lot!
function _ts_sample(::ThompsonSampling{Normal}, state::State{T}) where T
  n(arm) = state.arm_counts[arm]
  tr(arm) = state.arm_reward[arm]
  return Dict(arm => rand(Normal(tr(arm) / (1 + n(arm)), 1 / (1 + n(arm)))) for arm in keys(state.arm_counts))
end

function choose_action(instance::CombinatorialInstance{T}, algo::ThompsonSampling{D}, state::State{T}; with_trace::Bool=false) where {T, D}
  weights = if any(v == 0 for v in values(state.arm_counts))
    # Initialisation step: for each random variable (i.e. reward source), try arms, preferring those that have never been tested.
    Dict(arm => (state.arm_counts[arm] == 0.0) ? 1.0 : 0.0 for arm in keys(state.arm_counts))
  else
    # Normal step: just sample the prior distribution.
    _ts_sample(algo, state)
  end

  # Avoid having all weights negative, this will yield a zero solution.
  if all(values(weights) .<= 0)
    min_val = maximum(values(weights))
    for k in keys(weights)
      weights[k] -= 2 * min_val
    end
  end

  # Actually find the solution to play.
  t0 = time_ns()
  sol = solve_linear(instance, weights)
  t1 = time_ns()

  if with_trace
    return sol, ThompsonSamplingDetails((t1 - t0) / 1_000_000_000)
  else
    return sol
  end
end


function find_number_of_assemblies(adapters)
    adapters = Set(adapters)
    push!(adapters, 0)
    device = maximum(adapters) + 3
    push!(adapters, device)

    assemblies = zeros(Int64, device + 1)
    assemblies[1] = 1
    assemblies[2] = 1 in adapters ? 1 : 0
    t = 0
    if 2 in adapters
        t += assemblies[1]
    end
    if 1 in adapters
        t += assemblies[2]
    end
    assemblies[3] = t

    for n in 3:device
        foreach(k -> begin
                        if n - k in adapters
                            assemblies[n + 1] += assemblies[n - k + 1]
                        end
                    end, 1:3)
    end
    assemblies[device + 1]
end

function main()
    adapters = map(x -> parse(Int64, x), readlines("../resources/input_10.txt"))
    result = find_number_of_assemblies(adapters)
    println(result)
end

main()


# LSTM model - input * weight, concatenated weights
function lstm(weight, bias, hidden, cell, input)
    gates   = hcat(input,hidden) * weight .+ bias
    hsize   = size(hidden,2)
    forget  = sigm(gates[:,1:hsize])
    ingate  = sigm(gates[:,1+hsize:2hsize])
    outgate = sigm(gates[:,1+2hsize:3hsize])
    change  = tanh(gates[:,1+3hsize:end])
    cell    = cell .* forget + ingate .* change
    hidden  = outgate .* tanh(cell)
    return (hidden,cell)
end

function logprob(output, ypred, mask=nothing)
    nrows,ncols = size(ypred)
    index = similar(output)
    @inbounds for i=1:length(output)
        index[i] = i + (output[i]-1)*nrows
    end
    o1 = logp(ypred,2)
    index = mask == nothing ? index : index[mask]
    o2 = o1[index]
    o3 = sum(o2)
    return o3
end

# attention mechanism
function att(w,a,h, o=Dict())
    # get sizes
    B,L,D  = size(a); H = size(h,2)
    attdrop = get(o, :attdrop, 0.0)

    # flatten region features
    aflat = reshape(a, B*L, D)

    # visual features projection
    aatt = aflat*w["wce"] # size(wce): (D,D), projection
    aatt = reshape(aatt, B, L, D)

    # hidden states projection
    hatt = h * w["whe"]
    hatt = reshape(hatt, B, 1, D) # size(whe): (H,D)

    # et calculation
    et = tanh(aatt .+ hatt)
    et = reshape(et, B*L, D)
    et = dropout(et, attdrop) * w["watt"] # watt: D,1
    et = reshape(et, B, L)

    # alpha = logp(et, 2)
    et1 = maximum(et, 2)
    et2 = et .- et1
    et3 = exp(et2)
    et4 = sum(et3, 2)
    et5 = et3 ./ et4

    alpha = exp(et5)
    alpha = alpha ./ sum(alpha,2)
    alpha = reshape(alpha, size(alpha)..., 1)

    context = alpha .* a # B,L,1 * B,L,D
    context = sum(context,2)
    context = reshape(context, size(context,1), size(context,3))
end


#!/usr/bin/env julia

using ArgParse

function compute_pi(a::Float64, b::Float64, n::Int64)::Float64
    result = zeros(Threads.nthreads())
    delta = (b - a)/n
    Threads.@threads for i in 0:(n - 1)
        result[Threads.threadid()] += sqrt(1.0 - (a + i*delta)^2)
    end
    return 2.0*delta*sum(result)
end

function main()
    arg_parser = ArgParseSettings()
    arg_parser.description = "Compute pi using a quadrature method on multiple threads"
    @add_arg_table arg_parser begin
        "n"
            help = "number of point for the quadrature method"
            arg_type = Int64
            required = true
    end
    options = parse_args(ARGS, arg_parser)
    nr_threads = Threads.nthreads()
    println("running with $nr_threads threads")
    nr_points = options["n"]
    println(compute_pi(-1.0, 1.0, nr_points))
end

main()


################################################################################
# Copyright (C) Marcus Greiff 2020
#
# @file example_attitude_sys_id.jl
# @author Marcus Greiff
# @date July 2020
#
# This scrips contains some data and methods used in the system ideantification
# of Patrik's home-made drone. The basic methods and ideas are quite genetral,
# but not documeneted here.
#
################################################################################
using LinearAlgebra
using LaTeXStrings

# In the first experiment, we measure the thrust of the rotors as a function of
# a normalized command sent to the motors (typically pwm duty cycle), here,
# pairs of (measured force, duty cycle) are given in F and D respectively, from
# which the a and b parameters of the polynomial map are using a LS-regression.

# Generate some synthetic data
Theta    = rand(2,1);
N        = 10^3;
D        = rand(N,1);
Phi      = [D D.^2];
F        =  Phi*Theta + 0.1*randn(N,1);

#length = 55.0;

D = hcat(collect(50:50:600))
W = hcat([
    6,
    19,
    37,
    60,
    87,
    119,
    148,
    180,
    211,
    242,
    271,
    305,
])

g = 9.81;
m = 560;

D = D./1000;
W = W./1000;

#f = ma

F = W.*g * 2 / 4.0;

Phi      = [D D.^2];
Cov      = inv(Phi'*Phi);
Thetahat = (Phi'*Phi)\(Phi'*F);

using Plots
dd = hcat(collect((0:N)/N))
scatter(D, F, marker=:circle, color=:green, markeralpha = 0.3, xaxis="Duty cycle", yaxis="Force", label="Measurements")
#plot!(dd, [dd dd.^2]*Theta, color=:red, linewidth=2, label="True function")
plot!(dd, [dd dd.^2]*Thetahat, color=:blue, linewidth=2, label="Estimated function")

#1.1885640614704445
#2.291161604353094

# In the first experiment, we estimate C using the above function and
# measurements of the rotational torque about the e3 axis as a function of the
# applied PWM duty cycle. Here, we whould expect a linear relationship between
# the rotor thrust and the generated torque. If this is not linear, then c
# should represent the linear approximation of this function at the single rotor
# thrust of mg/4.

tauz = 4*0.005*9.81*0.09; # Nm
fi   = 2*1.18701; # N
# tauz = c * fi where fi is the thrust of one of the rotors
c = tauz / fi;

# index 0 - front right - pos force, positive moment about z, negative moment x, negative moment y
# index 1 - back right  - pos force, negative moment about z, negative moment x, positive moment y
# index 2 - back left   - pos force, positive moment about z, positive moment x, positive moment y
# index 3 - front left  - pos force, negative moment about z, positive moment x, negative moment y
#
#     3       0
#       .   .
#         .
#       .   .
#     2       1
#
# long 15.5
# short 9.3

A = [+1.0, +1.0, +1.0, +1.0;
     -dy, -dy, +dy, +dy;
     -dx, +dx, +dx, -dx;
     + c, - c, + c, - c]

# Cylindrical cross (mass, length of one axis, radius of the cross)
MC = 0.284
LC = 0.24
RC = 0.01

ICX = MC * (RC^2 / 4 + LC^2 / 12) + MC*RC^2 / 2;
ICY = ICX;
ICZ = MC * (RC^2 / 4 + LC^2 / 12) + MC * (RC^2 / 4 + LC^2 / 12);

# Battery box (Mass, length (x), with (y), height (z), offset in z)
ME = 0.172
WE = 0.035
LE = 0.09
HE = 0.025
DE = 0.03

IEX = ME * (WE^2/12 + HE^2/12 + DE^2);
IEY = ME * (LE^2/12 + HE^2/12 + DE^2);
IEZ = ME * (LE^2/12 + HE^2/12);

IXX = ICX + IEX;
IYY = ICY + IEY;
IZZ = ICZ + IEZ;


function editDistance(s1, s2)
    m=length(s1)+1
    n=length(s2)+1
    i=0
    j=0
    tbl = Dict{Tuple{Int64,Int64},Int64}()
    for i=1:m
        tbl[i,1]=i-1
    end
    for j=1:n
        tbl[1,j]=j-1
    end
    for i=2:m
        for j=2:n
            cost = s1[i-1] == s2[j-1]?0:1
            tbl[i,j] = min(tbl[i, j-1]+1, tbl[i-1, j]+1, tbl[i-1, j-1]+cost)
        end
    end
    tbl[i,j]
end

d=-1
for i=1:1000000
    d=editDistance("AAAATTTTCCCCGGGGAAAANTTTTCCCCGGGG", "AAAATTTTCCCCGGGGAAAAMTTTTCCCCGGGG")
end
println(d)