### A Pluto.jl notebook ###
# v0.12.21

using Markdown
using InteractiveUtils

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

# ╔═╡ b5a8675e-0e2a-11eb-1787-55285590d7de
using Plots

# ╔═╡ bcd20d70-0e2a-11eb-34c1-5f49700df9cd
using LaTeXStrings

# ╔═╡ 55991350-0e2b-11eb-3419-0f9f9ac0293f
using PlutoUI

# ╔═╡ 0cbfa5d0-2506-11eb-1485-7733b85a975b
using SymEngine
#hay muchos paquete simbólicos, el más completo (límites integrales, sustiuciones...) es SymPy, Symbolics es nuevo y en desarrollo y SymEngine es simple y suficiente para lo que vamos a hacer

# ╔═╡ 5aded872-b8b4-11eb-0277-93d61bffe2fd
md"# Tema 5 Control en modo deslizante"

# ╔═╡ f8105520-250b-11eb-1a6b-0129a812bcee
md"""# Vamos a crear nuetro propio simulador con *Euler*
¿En serio? ¿Euler no era un método malo?
No es el mejor método pero... los otros tampoco funcionan bien si la función es discontinua y a nuestro *le podemos hacer perrerías* (usar funciones discontinuas, introducir ruido, etc...)
"""

# ╔═╡ 96dbe960-2509-11eb-1248-b7c75a8974dd
#uso euler por que en un sistema discontinuo el resto de solvers no ofrece mucha ventaja y así tenemos más control sobre lo que está pasando

function simular(x0,planta,control,parametros,tf,Δt)
	t=0:Δt:tf
	estado=copy(x0);
	x=[]
	push!(x,estado)
	u=Float64[control(estado,t[1],parametros)[1]]

	for n=1:(length(t)-1)
		señal_de_control=control(estado,t[n],parametros)[1] #el [1] se entenderá luego
		derivada=planta(estado,señal_de_control,t[n],parametros)
		estado=estado+derivada*Δt #euler
		push!(x,estado)
		push!(u,señal_de_control)
	end

	return (x,u,t)
end

# ╔═╡ ba989850-0a31-11eb-1f3a-e363a7e0207e
md"""# Primer ejemplo (primer orden):

$\dot x=sin(t)+acos(x)+u=f(x,t)+u$

$a \in [0.5,0.9]$

Luego sabemos que

$|f(x,t)|<|sin(t)|+|acos(x)| \le 1+|a| \le 1+0.9=1.9=\rho$

## Hay que aplicar un $u$ mayor que $\rho$ y con signo opuesto a $x$... luego haremos esto:

$u=-sign(x)(\rho+\epsilon)=-2sign(x)$

Donde hemos tomado $\epsilon=0.1$
"""

# ╔═╡ 94294500-0e2a-11eb-3a25-617242dfe932
function planta_ejemplo1(x,u,t,p)
	a=p[1]#parámetro desconocido
	dx=sin(t)+a*cos(x)+u
end

# ╔═╡ 1d402de0-0e2b-11eb-10e8-79bdc072ee9b
control_1a(x,t,p)=-2*sign(x)

# ╔═╡ 3b89eed0-0e2b-11eb-3cad-8d86b96accc5
@bind x1_0 Slider(-1:0.1:1)

# ╔═╡ 6b48fc60-0e2b-11eb-1ad1-075f865e4c78
@bind a1 Slider(0.5:0.01:0.9)

# ╔═╡ f77e1fc0-0e2c-11eb-0051-d7eee614b171
md"""# Hemos hecho *trampa*, ¿Dónde está el control equivalente?
El control equivalente es el que mantiene $s=x$ en cero es decir:

$x(t)=0 \to  \dot x = 0 =sin(t)+acos(x)+u=sin(t)+acos(0)+u$
$0=sin(t)+a+u \to u=-a-sin(t)$

No está ni hace falta, ¡Nunca se ejecuta! Vamos a verlo
"""

# ╔═╡ e3723e20-0efa-11eb-1331-e710fbcd289f
function control_1b(x,t,p)
	if x>0
		u=-2
	elseif x<0
		u=2
	else
		u=1000000 #NO es el control equivalente es para ver que sucede ;)
	end
	return u
end

# ╔═╡ 7cc77a40-0efb-11eb-145d-4b535e701cf8
md"""no ha pasado nada, u nunca vale 1000000
... a no ser que x sea exactamente cero lo que es muy raro a no ser que lo forcemos
(poniendo $x_0=0$) pero ni así tiene mucha improtancia

## Significado del control equivalente

Aunque no lo veamos el control equivalente es "en promedio" igual que el control que estamos aplicando, vamos a verlo
"""

# ╔═╡ 29de3480-25c7-11eb-1d21-2107f4712127
media_móvil(v,n) = [sum(@view v[i:(i+n-1)])/n for i in 1:(length(v)-(n-1))]

# ╔═╡ 1ce32ff0-25c8-11eb-1371-e360c5c3ed2b
@bind N Slider(1:30)

# ╔═╡ 674476e0-2810-11eb-000b-0742e1bce381
md"Ver real $(@bind ver CheckBox())"

# ╔═╡ e32619f0-0efa-11eb-020d-c9743701e335
let
	Δt=0.001
	tf=2
	x0=x1_0
	parametros=[a1]
	x,u,t=simular(x0,planta_ejemplo1,control_1a,parametros,tf,Δt)

	u_equivalente=-a1.-sin.(t)

	u_promedio=media_móvil(u,N)
	t_promedio=media_móvil(t,N)

	fig1=plot()
	if ver==true
	   fig1=plot!(fig1,t,u, xaxis=L"t",yaxis="ues",label="u_real")
	end
	fig1=plot!(fig1,t,u_equivalente,label="u_equivalente")
	fig1=plot!(fig1,t_promedio,u_promedio,label="u_promedio")
end

# ╔═╡ 14080630-0f07-11eb-068d-09c4055d081b
md"""# Regularización y control equivalente
En vez de usar el signo que es discontinuo podemos usar una versión continua del mismo
"""

# ╔═╡ f47ce110-24fe-11eb-036a-e5d8bad1955f
@bind ϵ Slider(0.01:0.01:0.2)   #\epsilon + TAB

# ╔═╡ 1f38d210-24ff-11eb-3cbe-9b1e70108439
let
	ordenadas=-1:0.01:1
	fig1=plot(ordenadas,sign.(ordenadas), xaxis=L"x",label="sign(x)")
	fig1=plot!(fig1,ordenadas,tanh.(ordenadas/ϵ), label="tahn(x/$ϵ)")

end

# ╔═╡ e71e9160-2500-11eb-1b6c-d59e43143b66
md"Veamos que efecto tiene camniar el signo por su vesion continua"

# ╔═╡ 24a65e50-2501-11eb-2363-67d7e5bcb405
control_1c(x,t,p)=-2*tanh(x/p[2])

# ╔═╡ e01fcbe0-2500-11eb-3b94-a7662624c5ed
md"""## Entonces lo mejor es disminuir $\epsilon$ todo lo posible ¿No?

*No exactamente*, en la práctica tiene sus problemas, por ejemplo si la medida del estado es ruidosa...
Veámoslo:
"""

# ╔═╡ 16e8dac2-25cb-11eb-33d1-a95dc487b34d
function control_1d(x,t,p)
	_,ϵ,nivel_ruido=p
	ruido=nivel_ruido*2*(rand()-0.5)
	x_medido=x+ruido
	u=-2*tanh(x_medido/ϵ)
end

# ╔═╡ 612d43c0-2506-11eb-3a14-8145b79e349f
@bind ϵ2 Slider(0.01:0.01:0.2)

# ╔═╡ d8e35250-25ca-11eb-3d59-b3c6396a4fb1
@bind nivel_ruido Slider(0:0.01:0.2)

# ╔═╡ f694c060-2505-11eb-26ef-1fc536e2260d
md"""# Ejemplo 2 vamos al tracking directamente ☺
La estabilización es como el tracking pero con $r=0$, como ya lo vimos en el tema anterior podemos ir más rápido en este.

$\dot x_1=sin(x_1)+x_2$

$\dot x_2=ax_1+bx_2^2+cu$

$ y=x_1 $

con

$ a \in [-1,7] $,  $ b \in [1,2] $ y $ c \in [1.5,2.8] $

Queremos seguir la referenica $ y_r=sin(t) $

"""

# ╔═╡ 595542f0-25cf-11eb-308d-bb524a624b04
md"Defimos las función igual que en el tema 4"

# ╔═╡ 41d2207e-25cf-11eb-1cbf-a599e3a5d2fc
begin
	function derivada(funcion,variables,derivadas)
		d=0
		for i=1:length(variables)
			d=d+diff(funcion,variables[i])*derivadas[i]
		end
		return d
	end

	function derivada(f,x,dx,n) #multiple distpach
		d=f
		for i=1:n
			d=derivada(d,x,dx)
		end
		return d
	end
end

# ╔═╡ 32f2cfd0-2505-11eb-36a3-cdaec55a91cc
x1,x2,u,a,b,c=symbols("x1,x2,u,a,b,c") #o @vars x1... pero inicializando antes

# ╔═╡ ff0eafe0-0e2a-11eb-3e1c-5dd70c43c3c1
let
	Δt=0.001
	tf=2
	x0=x1_0
	parametros=[a1]
	x,u,t=simular(x0,planta_ejemplo1,control_1a,parametros,tf,Δt)

	fig1=plot(t,x, xaxis=L"t",yaxis=L"x(t)",label=:none)
	title!(fig1,"a=$a1")
	fig2=plot(t,u, xaxis=L"t",yaxis=L"u(t)",legend=false)
	l = @layout [a ; b]
	plot(fig1,fig2, layout=l)
end

# ╔═╡ 155f6de0-25c6-11eb-3a8e-851538372b3e
let
	Δt=0.001
	tf=2
	x0=x1_0
	parametros=[a1]
	x,u,t=simular(x0,planta_ejemplo1,control_1b,parametros,tf,Δt)

	fig1=plot(t,x, xaxis=L"t",yaxis=L"x(t)",label=:none)
	fig1=hline!([0],line=:dot,label=:none)
	title!(fig1,"a=$a1")
	fig2=plot(t,u, xaxis=L"t",yaxis=L"u(t)",legend=false)
	l = @layout [a ; b]
	plot(fig1,fig2, layout=l)
end

# ╔═╡ e023ea92-2500-11eb-00ff-4f27947f46d4
let
	Δt=0.001
	tf=2
	x0=x1_0
	parametros=[a1,ϵ]
	x,u,t=simular(x0,planta_ejemplo1,control_1c,parametros,tf,Δt)

	u_equivalente=-a1.-sin.(t)

	fig1=plot(t,x, xaxis=L"t",yaxis=L"x(t)",label=:none)
	title!(fig1,"a=$a1 ϵ=$ϵ")
	fig1=hline!([0],line=:dot,label=:none)

	fig2=plot(t,u, xaxis=L"t",yaxis=L"u_{aplicado}",legend=false)
	fig2=plot!(fig2,t,u_equivalente,
		    xaxis=L"t",yaxis=L"u_{es}",label=L"u_{equivalente}")

	l = @layout [a ; b]
	plot(fig1,fig2, layout=l)
end

# ╔═╡ 5a31c47e-2504-11eb-37af-07b3b88360eb
let
	Δt=0.001
	tf=2
	x0=x1_0
	parametros=[a1,ϵ2,nivel_ruido]
	x,u,t=simular(x0,planta_ejemplo1,control_1d,parametros,tf,Δt)

	u_equivalente=-a1.-sin.(t)

	fig1=plot(t,x, xaxis=L"t",yaxis=L"x(t)",label=:none)
	title!(fig1,"a=$a1, ϵ=$ϵ, nivel del ruido=$nivel_ruido")
	fig1=hline!([0],line=:dot,label=:none)

	fig2=plot(t,u, xaxis=L"t",yaxis=L"u_{aplicado}",legend=false)
	fig2=plot!(fig2,t,u_equivalente,
		    xaxis=L"t",yaxis=L"u_{es}",label=L"u_{equivalente}")

	l = @layout [a ; b]
	plot(fig1,fig2, layout=l)
end

# ╔═╡ b1c43a50-25ce-11eb-3478-f13e9174ccc9
begin
    dx1=sin(x1)+x2
    dx2=a*x1+b*x2^2+c*u
	dx=[dx1 dx2]
	y=x1
end

# ╔═╡ 992e79f0-25cf-11eb-0885-9f431d9e2ed6
dy=derivada(y,[x1 x2],[dx1 dx2],1)

# ╔═╡ 806542f0-25cf-11eb-39c8-db55ed5e86a7
ddy=derivada(y,[x1 x2],[dx1 dx2],2)

# ╔═╡ c5f21a70-25d2-11eb-2af8-395671e44537
md"Ok tiene el grado correcto podemos usar la técnica"

# ╔═╡ 2cd935f0-25d0-11eb-1b07-a7002584aa16
md"""## Defimos ahora el error y su dinámica

Recordemos $e_1 = y - y_r$  y ahora a calcular sus derivadas:
"""

# ╔═╡ 77f468f0-2816-11eb-2dc5-25eb17f3a834
yr,dyr,ddyr=symbols("yr,dyr,ddyr")

# ╔═╡ e1f2879e-25d2-11eb-04d4-b53a77d90c5f
e1=yr-y

# ╔═╡ 14e23520-2817-11eb-2f93-0779085f77a5
md"""La teoría es que $S=(\frac{d}{dt}+k)^{n-1}e_1$

Para el caso n=2

$S=\dot e_1 +ke_1$"""

# ╔═╡ 37fe65b0-2817-11eb-2cdb-2921b513978c
k=symbols("k")

# ╔═╡ 9ac55fb0-2816-11eb-12da-a9ddac1c913c
de1=derivada(e1,[x1 x2 yr dyr],[dx1 dx2 dyr ddyr])

# ╔═╡ 0ed1a02e-2817-11eb-0b92-e5feb0f17c63
S=de1+k*e1

# ╔═╡ a3caae20-2817-11eb-09e6-59d6464cef2a
dS=derivada(S,[x1 x2 yr dyr],[dx1 dx2 dyr ddyr])

# ╔═╡ aff35570-25cf-11eb-1627-43fe902b9db5
md"Identificamos términos $\dot S=-f-gu$"

# ╔═╡ a8a18df2-25cf-11eb-38b7-e128e53ba029
g=-diff(dS,u)

# ╔═╡ 081bb16e-25d0-11eb-28e6-c3e5a042fce2
f=-(dS+g*u) #el resto vamos.... OJO A LOS SIGNOS

# ╔═╡ f77c9930-25d2-11eb-287f-655c63ea721e
md"""Acotar $|f/g|$ un proceso **artesanal** se puede hacer **de muchas formas**
recordemos que $ a \in [-1,7] $,  $ b \in [1,2] $ y $ c \in [1.5,2.8] $
"""

# ╔═╡ cf8b68e2-b8ba-11eb-0177-8fa5c7f332bd
expand(f/g)

# ╔═╡ 097834c0-b8bb-11eb-3e52-c1d543ef9fb6
md"Y ahora vamos a acotar"

# ╔═╡ 9c2a7480-25d2-11eb-0479-751c0dc8b88e
rho=(abs(ddyr) + 7*abs(x1) + 2*x2^2 + k*abs(dyr)+ k*abs(x2) + k + abs(x2) + 1)/1.5

# ╔═╡ abc0604e-2823-11eb-1708-83268e22b7bc
md"## Todo junto"

# ╔═╡ 2b9f72b0-281b-11eb-1fbc-15b3e67b017d
referencia(t)=0.0.*(sin(t),cos(t),-sin(t))

# ╔═╡ 42884660-281a-11eb-015e-29ff1b21dfe0
function control_seguimiento(x,t,p)
	ϵ=0.01;
    x1,x2=x
	ε=0.1;#el epsilon bonito es varepsilon
    k=1;#una elección arbitraria
    yr,dyr,ddyr=referencia(t)

	y=x1; #mucho cuidado si no se define puede usar las globales y se cuelga
	e1=yr-y
	de1=dyr - (x2 + sin(x1))

	S=de1+k*e1

	#RELLENAR ESTO
	u=tanh(S / ϵ)*((abs(ddyr) + 7*abs(x1) + 2*x2^2 + k*abs(dyr)+ k*abs(x2) + k + abs(x2) + 1)/1.5 + ε)

	return (u,S,e1,de1)  #Saco todo esto para pintarlo luego fácilmente
	#por eso la función simular se queda SOLO CON u
end

# ╔═╡ 9acf5880-281b-11eb-3aa5-9b6df9ac7501
function planta_ejemplo2(x,u,t,p)
	a,b,c=p
	x1,x2=x #OJO a las variables, si por ejemplo no las ponemos...
    dx1=sin(x1)+x2
    dx2=a*x1+b*x2^2+c*u
	dx=[dx1 dx2]
end

# ╔═╡ e8ee4712-281b-11eb-0fbf-7783935a0075
@bind a2 Slider(-1:0.1:7)

# ╔═╡ 0e04e810-281c-11eb-3e72-c58bf8d7ce0d
@bind b2 Slider(1:0.1:2)

# ╔═╡ 1e143c10-281c-11eb-1117-29f72834203e
@bind c2 Slider(1.5:0.1:2.8)

# ╔═╡ 756a3570-2833-11eb-2596-c37768e5a75b
@bind x1_0b Slider(-1:0.1:1)

# ╔═╡ be9d11e0-2833-11eb-1e9f-d3bfc428c893
@bind variables Select(["estados"=>"Ver estados", "errores"=>"Ver errores"])

# ╔═╡ 8bdbadb0-281b-11eb-1786-3bef9aee1d39
let
	Δt=0.001
	tf=10
	x0=[x1_0b 0]
	parametros=[a2,b2,c2]
	estados,u,t=simular(x0,planta_ejemplo2,control_seguimiento,parametros,tf,Δt)

	#hay que sacar los datos de los estados, es decir parsar de un vecror de vectores a vectores sencillos
	x1=[x[1] for x in estados]
	x2=[x[2] for x in estados]
	y=x1

	refs=referencia.(t)
	yr=[ref[1] for ref in refs]
	dyr=[ref[2] for ref in refs]

	#truco para pintar más cosas sin tener que reescribirlas
	Ss=[]
	e1s=[]
	de1s=[]
	for i=1:length(t)
		_,S,e1,de1=control_seguimiento(estados[i],t[i],parametros)
		push!(Ss,S)
		push!(e1s,e1)
        push!(de1s,de1)
	end

	if variables=="estados"
		plot(t,x1, xaxis=L"t",yaxis=L"x(t)",label=L"y=x_1")
	    plot!(t,x2, xaxis=L"t",label=L"x_2")
	    title!("a=$a2, b=$a2, c=$a2")
	    fig1=plot!(t,yr,line=:dot,label=L"y_r")
	else
		plot(t,e1s, xaxis=L"t",yaxis=L"e(t)",label=L"e_1")
	    plot!(t,de1s, xaxis=L"t",label=L"e_2=\dot e_1")
        title!("a=$a2, b=$a2, c=$a2")
		fig1=hline!([0],line=:dot,label=:none)
	end

	fig2=plot(t,Ss, xaxis=L"t",yaxis=L"s",legend=false)

	fig3=plot(t,u, xaxis=L"t",yaxis=L"u",legend=false)

    if variables=="estados"
		fig4=plot(x1,x2, xaxis=L"x_1", yaxis=L"x_2",label=:none)
	else
		fig4=plot(e1s,de1s, xaxis=L"e_1", yaxis=L"e_2",label=:none)
	end

	l = @layout [a ; b ;c ; d]
	plot(fig1,fig2,fig3, fig4, layout=l, size=(600,800))

end

# ╔═╡ 28d56f3e-b8c0-11eb-1329-21f533ccddd9

# ╔═╡ Cell order:
# ╟─5aded872-b8b4-11eb-0277-93d61bffe2fd
# ╟─f8105520-250b-11eb-1a6b-0129a812bcee
# ╠═96dbe960-2509-11eb-1248-b7c75a8974dd
# ╟─ba989850-0a31-11eb-1f3a-e363a7e0207e
# ╠═b5a8675e-0e2a-11eb-1787-55285590d7de
# ╠═bcd20d70-0e2a-11eb-34c1-5f49700df9cd
# ╠═94294500-0e2a-11eb-3a25-617242dfe932
# ╠═1d402de0-0e2b-11eb-10e8-79bdc072ee9b
# ╠═55991350-0e2b-11eb-3419-0f9f9ac0293f
# ╠═3b89eed0-0e2b-11eb-3cad-8d86b96accc5
# ╠═6b48fc60-0e2b-11eb-1ad1-075f865e4c78
# ╠═ff0eafe0-0e2a-11eb-3e1c-5dd70c43c3c1
# ╟─f77e1fc0-0e2c-11eb-0051-d7eee614b171
# ╠═e3723e20-0efa-11eb-1331-e710fbcd289f
# ╠═155f6de0-25c6-11eb-3a8e-851538372b3e
# ╟─7cc77a40-0efb-11eb-145d-4b535e701cf8
# ╠═29de3480-25c7-11eb-1d21-2107f4712127
# ╠═1ce32ff0-25c8-11eb-1371-e360c5c3ed2b
# ╟─674476e0-2810-11eb-000b-0742e1bce381
# ╠═e32619f0-0efa-11eb-020d-c9743701e335
# ╟─14080630-0f07-11eb-068d-09c4055d081b
# ╟─1f38d210-24ff-11eb-3cbe-9b1e70108439
# ╠═f47ce110-24fe-11eb-036a-e5d8bad1955f
# ╠═e71e9160-2500-11eb-1b6c-d59e43143b66
# ╠═24a65e50-2501-11eb-2363-67d7e5bcb405
# ╟─e023ea92-2500-11eb-00ff-4f27947f46d4
# ╟─e01fcbe0-2500-11eb-3b94-a7662624c5ed
# ╠═16e8dac2-25cb-11eb-33d1-a95dc487b34d
# ╟─5a31c47e-2504-11eb-37af-07b3b88360eb
# ╠═612d43c0-2506-11eb-3a14-8145b79e349f
# ╠═d8e35250-25ca-11eb-3d59-b3c6396a4fb1
# ╟─f694c060-2505-11eb-26ef-1fc536e2260d
# ╠═0cbfa5d0-2506-11eb-1485-7733b85a975b
# ╟─595542f0-25cf-11eb-308d-bb524a624b04
# ╟─41d2207e-25cf-11eb-1cbf-a599e3a5d2fc
# ╠═32f2cfd0-2505-11eb-36a3-cdaec55a91cc
# ╠═b1c43a50-25ce-11eb-3478-f13e9174ccc9
# ╠═992e79f0-25cf-11eb-0885-9f431d9e2ed6
# ╠═806542f0-25cf-11eb-39c8-db55ed5e86a7
# ╟─c5f21a70-25d2-11eb-2af8-395671e44537
# ╟─2cd935f0-25d0-11eb-1b07-a7002584aa16
# ╠═77f468f0-2816-11eb-2dc5-25eb17f3a834
# ╠═e1f2879e-25d2-11eb-04d4-b53a77d90c5f
# ╟─14e23520-2817-11eb-2f93-0779085f77a5
# ╠═37fe65b0-2817-11eb-2cdb-2921b513978c
# ╠═9ac55fb0-2816-11eb-12da-a9ddac1c913c
# ╠═0ed1a02e-2817-11eb-0b92-e5feb0f17c63
# ╠═a3caae20-2817-11eb-09e6-59d6464cef2a
# ╟─aff35570-25cf-11eb-1627-43fe902b9db5
# ╠═a8a18df2-25cf-11eb-38b7-e128e53ba029
# ╠═081bb16e-25d0-11eb-28e6-c3e5a042fce2
# ╟─f77c9930-25d2-11eb-287f-655c63ea721e
# ╠═cf8b68e2-b8ba-11eb-0177-8fa5c7f332bd
# ╟─097834c0-b8bb-11eb-3e52-c1d543ef9fb6
# ╠═9c2a7480-25d2-11eb-0479-751c0dc8b88e
# ╟─abc0604e-2823-11eb-1708-83268e22b7bc
# ╠═2b9f72b0-281b-11eb-1fbc-15b3e67b017d
# ╠═42884660-281a-11eb-015e-29ff1b21dfe0
# ╠═9acf5880-281b-11eb-3aa5-9b6df9ac7501
# ╠═e8ee4712-281b-11eb-0fbf-7783935a0075
# ╠═0e04e810-281c-11eb-3e72-c58bf8d7ce0d
# ╠═1e143c10-281c-11eb-1117-29f72834203e
# ╠═756a3570-2833-11eb-2596-c37768e5a75b
# ╠═be9d11e0-2833-11eb-1e9f-d3bfc428c893
# ╠═8bdbadb0-281b-11eb-1786-3bef9aee1d39
# ╠═28d56f3e-b8c0-11eb-1329-21f533ccddd9


using MatrixNetworks

"""

Ear Decomposition
-----------------

An ear decomposition of an undirected graph G is a partition of its set of edges into a sequence of ears, such that the one or two endpoints of each ear
belong to earlier ears in the sequence and such that the internal vertices of each ear do not belong to any earlier ear.

An open ear decomposition or a proper ear decomposition is an ear decomposition in which the two endpoints of each ear after the first are distinct from each other.

The code is based on the algorithm given by Schmidt et.al in  "A simple test on 2-vertex- and 2-edge-connectivity" (Information Processing Letters Journal'
2013).

This approach is an important subroutine in many graph problems like Planarity Testing, Identifying the Factor critical graphs, recognizing series parallel
graphs, identifying Tri connected components etc.

Input: An undirected graph with i--->j and j--->i present (NO self loops!)

Observed:

      1) Slower on random graphs generated using GT-graph Suite ( Erdos Reyni model - with 20K vertices and a sparse quotient of 10 ) by a factor 5-8%

TODO: 1) Add a function to test for bi-connectivity and 2-edge connectivity.
      2) Compare the Performance with C ( SNAP DATA set )

"""

I = [1,2,2,3,3,4,1,4,3,5,5,6,6,7,7,8,6,8,5,9,9,10,5,10,1,3,2,5]
J = [2,1,3,2,4,3,4,1,5,3,6,5,7,6,8,7,8,6,9,5,10,9,10,5,3,1,5,2]
V = [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]

A = sparse(I,J,V,10,10)

function ear(A::MatrixNetwork,u::Int64,full::Int64,target::Int64)

    (rp,ci) = (A.rp,A.ci)
    n=length(rp)-1
    d=-1*ones(Int64,n)
    dt=-1*ones(Int64,n)
    rev_dt= -1*ones(Int64,n)
    ft=-1*ones(Int64,n)
    pred=zeros(Int64,n)
    rs=zeros(Int64,2*n)
    rss=0 # recursion stack holds two nums (v,ri)
    be = Any[] #back edges array
    ears = zeros(Int64,2 * length(A.ci))
    ear_number = zeros(Int64,length(A.ci))
    ear_index = 0
    ear_visited = zeros(Int64, A.n)

    for i=1:n
        push!(be, [])
    end

    #start dfs at u
    t=1
    targethit=0
    for i=1:n
        if i==1
            v=u
        else
            v=mod(u+i-1,n)+1
            if d[v]>0
                continue
            end
        end
        d[v]=1
        dt[v]=t
        rev_dt[t]=v;
	t=t+1
        ri=rp[v]
        rss=rss+1
        rs[2*rss-1]=v
        rs[2*rss]=ri # add v to the stack

        while rss>0
            v=rs[2*rss-1]
            ri=rs[2*rss]
            rss=rss-1 # pop v from the stack
            if v==target || targethit == 1
                ri=rp[v+1]
                targethit=1 # end the algorithm if v is the target
            end
            while ri<rp[v+1]
                w=ci[ri]
                ri=ri+1
                if d[w]<0
                    d[w]=d[v]+1
                    pred[w]=v
                    rss=rss+1
                    rs[2*rss-1]=v
                    rs[2*rss]=ri # add v to the stack
                    v=w
                    ri=rp[w]
                    dt[v]=t
		    rev_dt[t]=v
                    t=t+1
                #    continue #discover a new vertex!
                elseif (pred[v]!=w)
		    if(dt[w]< dt[v])
		       push!(be[w], v)
		    end
		end
            end
        end
        if full == 0
            break
        end
    end
    cn=1
    for i=1:n
	startVertex = rev_dt[i]
	ear_visited[startVertex] = 1
	while !isempty(be[startVertex])  # A disjoint Path (or edge) is identified.
	    nextVertex = pop!(be[startVertex])
            ear_index += 1
            ears[2*ear_index-1] = startVertex
            ears[2*ear_index] = nextVertex
            ear_number[ear_index] = cn
            while (ear_visited[nextVertex] != 1) #Identify the ear (Chain) and number it
                ear_visited[nextVertex] = 1
                startVertex_n = nextVertex
                nextVertex = pred[startVertex_n]
                ear_index += 1
                ears[2*ear_index-1] = startVertex_n # Store the ear (Chain).
                ears[2*ear_index] = nextVertex
                ear_number[ear_index] = cn  # Provide numbering to that Edge (#ear)
            end
            cn += 1
	end
    end
    print(ears)
    return (ears,ear_number)
end

function dfs_test()
#print(A);
B = MatrixNetwork(A);
#print(B)
ear(B, 1,1,0)
end

dfs_test();


using Pkg
Pkg.activate(".")
using PlotlyJS
using Dash

plot_width = 500
plot_height = 500

sd = 1.1
layout = Layout(xaxis_title="x", yaxis_title="y",
    xaxis_range=[-sd,sd],yaxis_range=[-sd,sd], title="Lissajous Curve",
    width=plot_width, height=plot_height)

l = 1
t = LinRange(0.0,l*2*pi,500)

function myplot(ω1,ω2)
    x = sin.(ω1*t)
    y = cos.(ω2*t)
    plot_data =[scatter(;x=x, y=y, mode="lines", name="Solution")]
    plot(plot_data, layout)
end

mathjax = "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.4/MathJax.js?config=TeX-MML-AM_CHTML"
app = dash(external_scripts=[mathjax])
app.title = "DashLissajous"

curve = myplot(1,1)

curve_graph = dcc_graph(id="graph",figure=curve)

slider1 = dcc_slider(id="slider1",min=1, max=10,step=1,value=1, marks=Dict([i => ("$(i)") for i = 1:10]))

slider2 = dcc_slider(id="slider2",min=1,max=10, step=1,value=1, marks=Dict([i => ("$(i)") for i = 1:10]))

# App Layout
app.layout = html_div(
    [
        html_center(html_h1("Lissajous Curves")),
        html_center(curve_graph),
        html_center(html_div(style=Dict("width"=>"420px"),[slider1,html_p(raw"\(\omega_1\)"),slider2, html_p(raw"\(\omega_2\)")]))
    ]
)

callback!(app,Output("graph","figure"), Input("slider1","value"), Input("slider2","value")) do ω1, ω2
    global ω1_old, ω2_old
    myplot(ω1,ω2)
end

run_server(app, "0.0.0.0", debug=true)


testnx(g, nx) = isapprox(g.nx, nx)
testny(g, ny) = isapprox(g.ny, ny)

# Physical grid tests
function testdx(g::AbstractGrid{T}) where {T, Ta}
  dxgrid = @. g.x[2:end] - g.x[1:end-1]
  dxones = g.dx*ones(T, size(dxgrid))
  isapprox(dxgrid, dxones)
end

function testdy(g::AbstractGrid{T, Ta}) where {T, Ta}
  dygrid = @. g.y[2:end] - g.y[1:end-1]
  dyones = g.dy*ones(T, size(dygrid))
  isapprox(dygrid, dyones)
end

testx(g) = isapprox(g.x[end]-g.x[1], g.Lx-g.dx)
testy(g) = isapprox(g.y[end]-g.y[1], g.Ly-g.dy)

# Test proper arrangement of fft wavenumbers. k = [0:nx/2, -nx/2+1:-1].
flippednegatives(k, mid) = -reverse(k[mid+2:end])
function testwavenumberalignment(k, nx)
  mid = Int(nx/2)
  positives = k[2:mid]
  negatives = flippednegatives(k, mid)
  isapprox(reshape(positives, mid-1), reshape(negatives, mid-1))
end

testk(g) = testwavenumberalignment(g.k, g.nx)
testl(g) = testwavenumberalignment(g.l, g.ny)
testkr(g) = isapprox(g.k[1:g.nkr], g.kr)

#testk(g) = isapprox(g.k[2:g.nkr-1], flippednegatives(g.k, g.nkr, 1))
#testk(g) = sum(g.k[2:g.nkr-1] .+ reverse(g.k[g.nkr+1:end], dims=1)) == 0.0
#testl(g) = sum(g.l[:, 2:Int(g.ny/2)] .+ reverse(g.l[:, Int(g.ny/2+2):end], dims=2)) == 0.0

function testgridpoints(g::AbstractGrid{T, Ta}) where {T, Ta}
  X, Y = gridpoints(g)
  dXgrid = @. X[2:end, :] - X[1:end-1, :]
  dYgrid = @. Y[:, 2:end] - Y[:, 1:end-1]
  dXones = g.dx*ones(T, size(dXgrid))
  dYones = g.dy*ones(T, size(dYgrid))
  isapprox(dXgrid, dXones) && isapprox(dYgrid, dYones)
end

function testdealias(g::OneDGrid)
  T = typeof(g.Lx)
  fh = ones(Complex{T}, size(g.kr))
  dealias!(fh, g)
  kmax = round(maximum(g.kr)*2/3)
  isapprox(sum(abs.(fh[ g.kr .>= kmax ])), 0)
end

function testdealias(g::TwoDGrid)
  T = typeof(g.Lx)
  fh = ones(Complex{T}, size(g.Krsq))
  dealias!(fh, g)
  kmax = round(maximum(g.kr)*2/3)
  lmax = floor(maximum(g.l)*2/3)

  temp = 0.0
  for j = 1:g.nl, i=1:g.nkr
    if ( g.kr[i] >= kmax ) & ( g.l[j] >= lmax || g.l[j] < -lmax )
      temp += abs.(fh[i, j]) #temp = sum of |fh| for aliased wavenumbers
    end
  end
  isapprox(temp, 0)
end

function testtypedonedgrid(dev::Device, nx, Lx; T=Float64)
  gr = OneDGrid(nx, Lx, T=T)
  typeof(gr.dx)==T && typeof(gr.x[1])==T && typeof(gr.Lx)==T
end

function testtypedtwodgrid(dev::Device, nx, Lx, ny=Lx, Ly=Lx; T=Float64)
  gr = TwoDGrid(nx, Lx, ny, Ly, T=T)
  typeof(gr.dx)==T && typeof(gr.dy)==T && typeof(gr.x[1])==T && typeof(gr.y[1])==T && typeof(gr.Lx)==T && typeof(gr.Ly)==T
end


using Zygote, DiffEqSensitivity
println("Starting tests")
using OrdinaryDiffEq, ForwardDiff, Test

function lotka_volterra(u, p, t)
  x, y = u
  α, β, δ, γ = p
  [α * x - β * x * y,-δ * y + γ * x * y]
end

function lotka_volterra(du, u, p, t)
  x, y = u
  α, β, δ, γ = p
  du[1] = dx = α * x - β * x * y
  du[2] = dy = -δ * y + γ * x * y
end

u0 = [1.0,1.0];
tspan = (0.0,10.0);
p0 = [1.5,1.0,3.0,1.0];
prob0 = ODEProblem(lotka_volterra,u0,tspan,p0);
# Solve the ODE and collect solutions at fixed intervals
target_data = solve(prob0,RadauIIA5(), saveat =  0:0.5:10.0);

loss_function = function(p)
    prob = remake(prob0;u0=convert.(eltype(p),prob0.u0),p=p)
    prediction = solve(prob, RadauIIA5(); saveat = 0.0:0.5:10.0,abstol=1e-10,reltol=1e-10)

    tmpdata=prediction[[1,2],:];
    tdata=target_data[[1,2],:];

    # Calculate squared error
    return sum(abs2,tmpdata-tdata)
end
p=[1.5,1.2,1.4,1.6];
fdgrad = ForwardDiff.gradient(loss_function,p)
rdgrad = Zygote.gradient(loss_function,p)[1]

@test fdgrad ≈ rdgrad rtol=1e-5

loss_function = function(p)
    prob = remake(prob0;u0=convert.(eltype(p),prob0.u0),p=p)
    prediction = solve(prob, TRBDF2(); saveat = 0.0:0.5:10.0,abstol=1e-10,reltol=1e-10)

    tmpdata=prediction[[1,2],:];
    tdata=target_data[[1,2],:];

    # Calculate squared error
    return sum(abs2,tmpdata-tdata)
end

rdgrad = Zygote.gradient(loss_function,p)[1]
@test fdgrad ≈ rdgrad rtol=1e-3

loss_function = function(p)
    prob = remake(prob0;u0=convert.(eltype(p),prob0.u0),p=p)
    prediction = solve(prob, Rosenbrock23(); saveat = 0.0:0.5:10.0,abstol=1e-8,reltol=1e-8)

    tmpdata=prediction[[1,2],:];
    tdata=target_data[[1,2],:];

    # Calculate squared error
    return sum(abs2,tmpdata-tdata)
end

rdgrad = Zygote.gradient(loss_function,p)[1]
@test fdgrad ≈ rdgrad rtol=1e-3

loss_function = function(p)
    prob = remake(prob0;u0=convert.(eltype(p),prob0.u0),p=p)
    prediction = solve(prob, Rodas5(); saveat = 0.0:0.5:10.0,abstol=1e-8,reltol=1e-8)

    tmpdata=prediction[[1,2],:];
    tdata=target_data[[1,2],:];

    # Calculate squared error
    return sum(abs2,tmpdata-tdata)
end

rdgrad = Zygote.gradient(loss_function,p)[1]
@test fdgrad ≈ rdgrad rtol=1e-3

### OOP

prob0_oop = ODEProblem{false}(lotka_volterra,u0,tspan,p0);
# Solve the ODE and collect solutions at fixed intervals
target_data = solve(prob0,RadauIIA5(), saveat =  0:0.5:10.0);

loss_function = function(p)
    prob = remake(prob0_oop;u0=convert.(eltype(p),prob0.u0),p=p)
    prediction = solve(prob, RadauIIA5(); saveat = 0.0:0.5:10.0,abstol=1e-10,reltol=1e-10)

    tmpdata=prediction[[1,2],:];
    tdata=target_data[[1,2],:];

    # Calculate squared error
    return sum(abs2,tmpdata-tdata)
end
p=[1.5,1.2,1.4,1.6];

fdgrad = ForwardDiff.gradient(loss_function,p)
rdgrad = Zygote.gradient(loss_function,p)[1]

@test fdgrad ≈ rdgrad rtol=1e-4

loss_function = function(p)
    prob = remake(prob0_oop;u0=convert.(eltype(p),prob0.u0),p=p)
    prediction = solve(prob, TRBDF2(); saveat = 0.0:0.5:10.0,abstol=1e-10,reltol=1e-10)

    tmpdata=prediction[[1,2],:];
    tdata=target_data[[1,2],:];

    # Calculate squared error
    return sum(abs2,tmpdata-tdata)
end

rdgrad = Zygote.gradient(loss_function,p)[1]
@test fdgrad ≈ rdgrad rtol=1e-3

loss_function = function(p)
    prob = remake(prob0_oop;u0=convert.(eltype(p),prob0.u0),p=p)
    prediction = solve(prob, Rosenbrock23(); saveat = 0.0:0.5:10.0,abstol=1e-8,reltol=1e-8)

    tmpdata=prediction[[1,2],:];
    tdata=target_data[[1,2],:];

    # Calculate squared error
    return sum(abs2,tmpdata-tdata)
end

rdgrad = Zygote.gradient(loss_function,p)[1]
@test fdgrad ≈ rdgrad rtol=1e-4

loss_function = function(p)
    prob = remake(prob0_oop;u0=convert.(eltype(p),prob0.u0),p=p)
    prediction = solve(prob, Rodas5(); saveat = 0.0:0.5:10.0,abstol=1e-8,reltol=1e-8)

    tmpdata=prediction[[1,2],:];
    tdata=target_data[[1,2],:];

    # Calculate squared error
    return sum(abs2,tmpdata-tdata)
end

rdgrad = Zygote.gradient(loss_function,p)[1]
@test fdgrad ≈ rdgrad rtol=1e-4

# all implicit solvers
solvers = [
    # SDIRK Methods (ok)
    ImplicitEuler(),
    ImplicitMidpoint(),
    Trapezoid(),
    TRBDF2(),
    SDIRK2(),
    Kvaerno3(),
    KenCarp3(),
    Cash4(),
    Hairer4(),
    Hairer42(),
    Kvaerno4(),
    KenCarp4(),
    Kvaerno5(),
    KenCarp5(),
    # Fully-Implicit Runge-Kutta Methods (FIRK)
    RadauIIA5(),
    # Fully-Implicit Runge-Kutta Methods (FIRK)
    #PDIRK44(),
    # Rosenbrock Methods
    ROS3P(),
    Rodas3(),
    RosShamp4(),
    Veldd4(),
    Velds4(),
    GRK4T(),
    GRK4A(),
    Ros4LStab(),
    Rodas4(),
    Rodas42(),
    Rodas4P(),
    Rodas5(),
    # Rosenbrock-W Methods
    Rosenbrock23(),
    Rosenbrock32(),
    RosenbrockW6S4OS(),
    ROS34PW1a(),
    ROS34PW1b(),
    ROS34PW2(),
    ROS34PW3(),
    # Stabilized Explicit Methods (ok)
    ROCK2(),
    ROCK4(),
    RKC(),
    # SERK2v2(), not defined?
    ESERK5()];

p = rand(3)

function dudt(u,p,t)
    u .* p
end

for solver in solvers
    function loss(p)
        prob = ODEProblem(dudt, [3.0, 2.0, 1.0], (0.0, 1.0), p)
        sol = solve(prob, solver, dt=0.01)
        sum(abs2, Array(sol))
    end

    println(DiffEqBase.parameterless_type(solver))
    loss(p)
    Zygote.gradient(loss, p)
end


load("kernels.jl")

dest1 = Array(Int, (2,3))
dest2 = Array(Int, (2,3))
B = [1 2 3
     4 5 6]
C = [1 0 1
     0 1 0]
D = [ 0  0  0
     10 10 10]

# code = quote
#     A = B.*C + D
#     dest1[...] = A
#     dest2[...] = A + C
# end

@kernel let
    A = B.*C + D
    dest1[...] = A
    dest2[...] = A + C
end

function f(B,C,D)
    A = B.*C + D
    dest1 = A
    dest2 = A + C
    (dest1, dest2)
end
dest1f, dest2f = f(B,C,D)

println("kernel:     dest1 =\n$dest1")
println("Array ops:  dest1 =\n$dest1f")
println()
println("kernel:     dest2 =\n$dest2")
println("Array ops:  dest2 =\n$dest2f")
println()

function timeit(n::Int)
    A = Array(Float, n, n)
    temp = Array(Float, n, n)
    B = rand(n, n)
    C = rand(n, n)
    D = rand(n, n)

    print("Array routines:     "); tic()
    B.*C + D
    ta = toc()

    print("For loops:          "); tic()
    for j=1:n,i=1:n
        temp[i,j] = B[i,j].*C[i,j]
    end
    for j=1:n,i=1:n
        A[i,j] = temp[i,j] + D[i,j]
    end
    tfor = toc()

    print("Handwritten kernel: "); tic()
    for j=1:n,i=1:n
        A[i,j] = B[i,j].*C[i,j] + D[i,j]
    end
    tmk = toc()

    print("Kernel:             "); tic()
    @kernel let
        A[...] = B.*C + D
    end
    tk = toc()
    (ta, tmk, tfor, tk)
end

timeit(1000)


export Uniform, Log_uniform, rand_vec, rand_vecU, ParameterRandom

@with_kw struct ParameterRandom <: ParameterGenerator
    methods # Array of sampling generator. method should be callable
    len  # Total number of simulation
end

"""
Set all the variables with same method with defined ranges
"""
function ParameterRandom(param_ranges, method::RandomSampler, len)

    methods = map(x-> method(x), param_ranges)

    return ParameterRandom(methods=methods, len=len)
end

"""
Parameter Random sampling with `range`.
"""
function ParameterRandom(param_ranges::AbstractArray{T,1};method=Uniform()::RandomSampler, len=nothing) where T<:AbstractRange

    param_ranges_ = Array{Tuple{Number,Number},1}(undef,length(param_ranges)) # [(start,end),...]

    len_ = 1

    for (i, ran) in enumerate(param_ranges)
        par_rang = (ran[1], ran[end])
        len_ = len_ * length(ran)
        param_ranges_[i] = par_rang
    end

    len_ = isnothing(len) ? len_ : len # if user-defined len_

    return ParameterRandom(param_ranges_, method, len_)
end

"""
Reset one specified method
"""
function (par::ParameterRandom)(index::Integer, new_method::RandomSampler)
    methods = deepcopy(par.methods)
    methods[index] = new_method
    return ParamRandom(methods, par.len)
end

"""Length of random sampler"""
Base.length(self::ParameterRandom) = self.len

Base.size(self::ParameterRandom) = (self.len,)

## Iterator
"""Get the parameters that would be `ind` th in iteration

The total sample number is # to do

Paremeters
----------
- `ind` {int}: The iteration index

Returns
-------
- `params`: list of numbers in the range of `ParameterGrid.param_ranges`
"""
function Base.getindex(self::ParameterRandom, ind::Int)

    params = map(v-> v() ,self.methods )

    return params
end

## Uniform
"""RandomSampler for uniform distribution"""
@with_kw struct Uniform <: RandomSampler
    a=0.::Number #lower bound
    b=1.::Number # higher bound
    type= Float64
    function Uniform(a,b, type)
        if a>b
            @warn "b(=$b) should greater than a(=$a). Change to Uniform(a=$b,b=$a)"
            new(b,a, type)
        else
            new(a,b, type)
        end
    end
end

"""
    Uniform(a,b;type=Float64)
Create uniform sampler.
"""
function Uniform(a, b;type=Float64)
    return Uniform(a=a,b=b, type=type)
end

"""
    (par::Uniform)()

generate random variables.

See also
--------
`Random.rand`
"""
function (par::Uniform)() :: Number
    @unpack a, b, type = par
    num = rand() * (b - a) + a

    if type <: Integer
        samp = round(num) # convert to integer
    else
        samp =  num
    end

    return samp
end

function (par::Uniform)(len::Integer) :: Array
    @unpack a, b, type = par
    vec = rand(len)
    return vec .* (b - a) .+ a
end

"""Reset parameter range but fixed the rest."""
function (par::Uniform)(param_range)

    return Uniform(param_range[1], param_range[2], par.type)
end

## Log uniform
"""RandomSampler for log uniform distribution. a,b ∈ (0,∞)"""
@with_kw struct Log_uniform <: RandomSampler
    a = 1e-6:: Number # can not be zero
    b = 1. :: Number
    base = 10. :: Number
    type = Float64
    function Log_uniform(a,b, base, type)
        @assert base > 0.
        @assert (a>0.) & (b > 0.)
        if a>b
            @warn "b(=$b) should greater than a(=$a). Change to Log_uniform(a=$b,b=$a)"
            new(b,a,base, type)
        else
            new(a,b,base, type)
        end
    end
end

function Log_uniform(a,b;base=10., type=Float64)
    return Log_uniform(a=a, b=b, base=base, type=type)
end

function (par::Log_uniform)() :: Number
    @unpack a, b, base, type = par
    a_pow, b_pow = log.(base, [a, b])
    _pow = Uniform(a_pow, b_pow, type)()

    num = base^_pow

    if type <: Integer
        samp = round(num) # convert to integer
    else
        samp =  num
    end

    return samp

end

"""Generate a vector with same range."""
function (par::Log_uniform)(len::Integer) :: Array
    samp = Log_uniform(a=par.a, b=par.b, base=par.base, type=par.type)
    return [samp() for i in 1:1:len]
end

"""Renew the range and keep the rest."""
function (par::Log_uniform)(param_range)
    return Log_uniform(param_range[1], param_range[2], par.base, par.type)
end

## Sampling from de meta
"""generate random vector with uniform distribution `rand()`"""
function rand_vec(len::Integer, method::RandomSampler) :: Array{Float64,1}
    return method(len)
end

"""Sampling a vector with a list of sampler

REPL
----
```julia
unis = [Uniform(1,2) for i in 1:10]
vec = rand_vec(unis)
```
"""
function rand_vec(samplers::AbstractArray{T,1}) :: Array{Float64,1} where T<:RandomSampler
    return [samp() for samp in samplers]
end

"""General method for sampling a vector with individual samplers"""
function rand_vec(samplers)
    return [samp() for samp in samplers]
end

"""Uniform sampling of vector"""
rand_vecU(len::Integer, domain::Domain) = Uniform(domain.low, domain.high)(len)

rand_vecU(demeta::DEmeta, domain::Domain) = rand_vecU(length(demeta.u0), domain)

function rand_vecU(domains::Array{Domain, 1})
    vec = zeros(length(domains))
    for i in eachindex(domains)
        vec[i] = Uniform(domains[i].low, domains[i].high)()
    end
    return vec
end

"""General method. domains can be list of tuples"""
function rand_vecU(domains)
    vec = zeros(length(domains))
    for i in eachindex(domains)
        vec[i] = Uniform(domains[i]...)()
    end
    return vec
end

## Grid search


module Day11

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

export part1, part2

const matrix_size = 10

function string_block_to_matrix(string_block::AbstractString)
    as_arrays = split.(split(string_block, "\n"), "")
    as_int_arrays = [parse.(Int64, array) for array in as_arrays]
    # Transposing changes the type!
    Matrix(transpose(hcat(as_int_arrays...)))
end

function out_of_bounds(row::Int64, column::Int64)
    row < 1 || row > matrix_size || column < 1 || column > matrix_size
end

function neighbours(coordinates::CartesianIndex{2})
    row, column = coordinates.I
    candidates = [
        (row - 1, column), (row + 1, column),
        (row, column - 1), (row, column + 1),
        (row - 1, column - 1), (row - 1, column + 1),
        (row + 1, column - 1), (row + 1, column + 1),
    ]
    in_bounds = filter(candidate -> !out_of_bounds(candidate...), candidates)
    [CartesianIndex(p...) for p in in_bounds]
end

function neighbourhood(coordinate::CartesianIndex{2})
    as_matrix = zeros(Int64, matrix_size, matrix_size)
    for neighbour in neighbours(coordinate)
        as_matrix[neighbour] = 1
    end
    as_matrix
end

function neighbourhood(coordinates::Vector{CartesianIndex{2}})
    sum(neighbourhood.(coordinates))
end

increment(octopuses::Matrix{Int64}) = octopuses .+ 1
rollover(octopuses::Matrix{Int64}) = @. mod(min(octopuses, 10), 10)

function flash(
    octopuses::Matrix{Int64},
    flashed::Vector{CartesianIndex{2}} = Vector{CartesianIndex{2}}()
)
    above_9 = findall(x -> x > 9, octopuses)
    new_flashes = above_9[(!in).(above_9, Ref(flashed))]
    if length(new_flashes) == 0
        return rollover(octopuses), flashed
    end
    octopuses += neighbourhood(new_flashes)
    return flash(octopuses, [flashed; new_flashes])
end

step(octopuses::Matrix{Int64}) = octopuses |> increment |> flash

function part1(input = input)
    octopuses = string_block_to_matrix(input)
    n_flashes = 0
    for i = 1:100
        octopuses, flashed = step(octopuses)
        n_flashes += length(flashed)
    end
    n_flashes
end

function part2(input = input)
    octopuses = string_block_to_matrix(input)
    n_flashes = 0
    i = 1
    while true
        octopuses, flashed = step(octopuses)
        if length(flashed) == matrix_size^2
            return (i)
        end
        i += 1
    end
end

end #module


using Plots
x = 1:10; y = rand(10); # These are the plotting data
plot(x, y);

println("HELLO")

function f(z)
    a = sum(z.^2,dims=2)
    return a
end

println(f([1 2; 3 4]))

l = [1 2; 3 4]
println(l[1,1])
println(l[2,1])
println(l[1,2])

for a in 1:10
end

bbox = [0 0; 2 2]
h0 = 1
x = bbox[1,1]:h0:bbox[2,1]
y = bbox[1,2]:h0:bbox[2,2]
println(x)
println(y)

# Funkcja meshgrid znana z Matlaba jest niedostępna w Julii
# Tworzoymy jej podstawową implementację
# Obliczamy długość podanych w parametrach funkcji wektorów
# Reprezentują one wymiarny naszej siatki
# Nastepnie funkcja repeat tworzy wektor skladajacy sie z
# Elemenntów pierwotnego wektora, ale kazdy element zostal powtórzon????????????y m ilość razy
# Co finalnie daje nam długi wektor wspolrzednych x punktów naszej siatki
# Funkcja reshape łamie wektor do macierzy o wymiarach m na na
# Ta sama prodecura powtórzona jest dla punktów y tylko w tym przypadku
# Funkcja repeat powtarza cały wektor y n razy, na koniec łamie go do macierzy
# Zwracana jest para macierzy gx, gy

function meshgrid(vx::AbstractVector{T}, vy::AbstractVector{T}) where T
    m, n = length(vy), length(vx)

    gx = reshape(repeat(vx, inner = m, outer = 1), m, n)
    gy = reshape(repeat(vy, inner = 1, outer = n), m, n)

    return gx, gy
end

println(repeat([1 2; 3 4], inner=(2,1), outer=(1, 3)))
println(repeat([1 2; 3 4], inner=(2,3)))
#println(meshgrid(x))

p =


export MWFarField3D

"""
Operator to compute the far field of a current distribution. In particular, given the current distribution ``j`` this operator allows for the computation of

```math
A j = n × ∫_Γ e^{γ ̂x ⋅ y} dy
```

where ``̂x`` is the unit vector in the direction of observation. Note that the assembly routing expects the observation directions to be normalised by the caller.
"""
type MWFarField3D{K}
  gamma::K
end

quaddata(op::MWFarField3D,rs,els) = quadpoints(rs,els,(3,))
quadrule(op::MWFarField3D,refspace,p,y,q,el,qdata) = qdata[1,q]

kernelvals(op::MWFarField3D,y,p) = exp(op.gamma*dot(y,cartesian(p)))
function integrand(op::MWFarField3D,krn,y,f,p)
    y × (krn * f[1])
end


######## StanSample example  ###########

using StanSample, Test

bernoullimodel = "
data {
  int<lower=1> N;
  int<lower=0,upper=1> y[N];
}
parameters {
  real<lower=0,upper=1> theta;
}
model {
  theta ~ beta(1,1);
  y ~ bernoulli(theta);
}
"

bernoullidata = Dict("N" => 10, "y" => [0, 1, 0, 1, 0, 0, 0, 0, 0, 1])
inittheta = Dict("theta" => 0.60)

sm = SampleModel("init_dict", bernoullimodel,
  seed=StanSample.RandomSeed(seed=-1));

rc = stan_sample(sm, data=bernoullidata, init=inittheta)

if success(rc)
  samples = read_samples(sm)

  df = read_summary(sm)
  @test df[df.parameters .== :theta, :mean][1] ≈ 0.33 rtol=0.2

end


normalize(arr) = arr ./ sum(arr)

function generateAdjacencyMatrix(R_t::Array{Int,2}, C::Function)
	assert(size(R_t,2) == 2)
	A = zeros(Int,max(R_t[:,1]),max(R_t[:,2]))
	for tag = 1:size(R_t,1)
		u = R_t[tag,1]
		d = R_t[tag,2]
		A[A[:,d] .>= 1,d] += 1
		A[u,d] = 1
	end
	return sparse(C(A))
end

function runSpear(A::SparseMatrixCSC{Float64,Int64}, k = 10)
	M = size(A,1)    # number of unique users
	N = size(A,2)    # number of unique documents
	E = ones(M)'     # experience scors of users
	Q = ones(N)'     # qality scors of documents

	for i = 1:k # calculation of expertise and quality stores
		E = normalize(Q * A')
		Q = normalize(E * A)
	end

	return E, Q

end


function sample_field(N)
    uhat = zeros(ComplexF64,2*N); # preallocate space

    ξ = randn(N); # generate the random variables

    # construct the eigenvalues
    k = 1:N;
    λ = @. 1/(π*k)^2;

    # fill in the nonzero entries
    # NOTE we need to multiply by 2 *N for FFT scaling
    @. uhat[2:N+1] = 2 * N * sqrt(λ) * sqrt(2) * ξ;

    # invert and get the relevant imaginary part
    u = imag.(ifft(uhat))[N+2:end];
    return u
end


	#
	#	Script for calculation of far-field pattern in the liquid case (sphere)
	#

	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	# User configurable parameters
	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

	# Physical parameters
	rho10 = 5.00 ; # Density ratio
	k_0 = 4 ; # Wave number in media 0
	k_1 = 6 ; # Wave number in media 1
	a = 1 ; # Sphere radius
	theta_inc = pi/4 ; # Incidence angle in radians

	# Software parameters
	grid_size = 200 ; # Angular grid size
	N = 150 ; # Number of terms in the series solution

	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	# Calculation
	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

	# Far-Field pattern calculation
	Pattern = Pattern_LiquidSphere( rho10, k_0, k_1, a, grid_size, N, theta_inc ) ;

	# Saving to disk
	writedlm( "Out.Pattern.dat", Pattern , '\t' ) ;


#Initialize neural network --> Restricted Boltzmann machine (RBM)

using Random

#define structure type containing the visible biases a,
#the hidden biases b and the weights W of the RBM.
struct RBM_par
    a::Array{Complex{Float64},1}
    b::Array{Complex{Float64},1}
    W::Array{Complex{Float64},2}
end

#initialize the network parameters with random numbers
function init_RBM_par(alpha::Int64, nspins::Int64)
    #generate random seed dependent on the network size alpha and  number of spins nspins
    seed_ = alpha*nspins*rand(0:20000);
    Random.seed!(seed_)

    #initiialize visible biases as vector of zeros
    a_ = zeros(Complex,nspins)

    #iniztialize hiddens biasees as vector of complex random numbers
    b_ = rand(-0.01:0.0001:0.01,nspins*alpha) .+ im*rand(-0.01:0.0001:0.01,nspins*alpha)

    #iniztialize weights as matrix of complex random numbers
    W_ = rand(-0.01:0.0001:0.01,nspins,nspins*alpha) .+ im*rand(-0.01:0.0001:0.01,nspins,nspins*alpha)

    return RBM_par(a_,b_,W_)
end

#Calculate the wavefunction projected onto the input state
function RBM_wf(par::RBM_par,state::Array{Int64,1},alpha::Int64,nspins::Int64)

    rbm = sum(par.a .* state)

    for h=1:alpha*nspins
        @views thetah = par.b[h] .+ sum(state[v] .* par.W[:,h]);
        rbm += log(2cosh(thetah));
    end;

    return exp(rbm);
end;


module ZeroInflation

using Distributions
using LogExpFunctions
using Random
using Requires
using UnPack

export zeroinflated

struct ZeroInflated{D1<:UnivariateDistribution,D2<:UnivariateDistribution,S<:ValueSupport} <: UnivariateDistribution{S}
    original::D1
    mix::D2
    function ZeroInflated(d::UnivariateDistribution, m::UnivariateDistribution)
        new{typeof(d),typeof(m),Distributions.value_support(typeof(d))}(d, m)
    end
end

Distributions.params(d::ZeroInflated) = tuple(Distributions.params(d.original)..., Distributions.params(d.mix)...)
Distributions.minimum(d::ZeroInflated) = min(minimum(d.original), 0)
Distributions.maximum(d::ZeroInflated) = max(maximum(d.original), 0)

function Distributions.logpdf(d::ZeroInflated, x::Real)
    @unpack original, mix = d
    if x == 0
        loglik = [
            logpdf(mix, 1),
            logpdf(mix, 0) + logpdf(original, 0)
        ]
        return logsumexp(loglik)
    else
        return logpdf(mix, 0) + logpdf(original, x)
    end
end

function Distributions.logpdf(d::ZeroInflated, x::Vector{T}) where {T<:Real}
    @unpack original, mix = d
    N_zero = sum(x .== 0)
    N_nonzero = length(x) - N_zero

    loglik = N_zero * logsumexp([
        logpdf(mix, 1),
        logpdf(mix, 0) + logpdf(original, 0)
    ])
    loglik += N_nonzero * logpdf(mix, 0)
    loglik += sum(logpdf.(original, filter(!iszero, x)))
    return loglik
end

Distributions.pdf(d::ZeroInflated, x::Real) = exp(logpdf(d, x))

function Distributions.rand(rng::Random.AbstractRNG, d::ZeroInflated)
    @unpack original, mix = d
    c = rand(rng, mix)
    return c == 1 ? zero(eltype(original)) : rand(rng, original)
end

"""
    zeroinflated(d::UnivariateDistribution, p::Real)
    zeroinflated(d::UnivariateDistribution, m::UnivariateDistribution)

Construct a zero-inflated distribution. The mixing distribution can be specified using
a mixing probability *p* or a custom mixing distribution *m* (e.g. `Bernoulli(p)``).
"""

zeroinflated(d::UnivariateDistribution, p::Real) = ZeroInflated(d, Bernoulli(p))
zeroinflated(d::UnivariateDistribution, m::UnivariateDistribution) = ZeroInflated(d, m)

end


using LinearAlgebra, ForwardDiff, StaticArrays
using IterativeSolvers
using Plots

"""
    cones
    - nonnegative orthant (no)
        x >= 0

    - second-order (so)
        ||z1|| <= z2

    A Semismooth Newton Method for Fast, Generic Convex Programming
        https://arxiv.org/abs/1705.00772

    # todo: try backtracking linesearch for cone
        https://digital.lib.washington.edu/researchworks/bitstream/handle/1773/43279/Dueri_washington_0250E_19426.pdf?isAllowed=y&sequence=1
"""

# nonnegative orthant cone
function κ_no(z)
    max.(0.0, z)
end

# nonnegative orthant cone Jacobian
function Jκ_no(z)
    p = zero(z)
    for (i, pp) in enumerate(z)
        if pp >= 0.0
            p[i] = 1.0
        end
    end
    return Diagonal(p)
end

# second-order cone
function κ_so(z)
    z1 = z[1:end-1]
    z2 = z[end]

    z_proj = zero(z)

    if norm(z1) <= z2
        z_proj = copy(z)
    elseif norm(z1) <= -z2
        z_proj = zero(z)
    else
        a = 0.5 * (1.0 + z2 / norm(z1))
        z_proj[1:end-1] = a * z1
        z_proj[end] = a * norm(z1)
    end

    return z_proj
end

# second-order cone Jacobian
function Jκ_so(z)
    z1 = z[1:end-1]
    z2 = z[end]
    m = length(z)

    if norm(z1) <= z2
        return Diagonal(ones(m))
    elseif norm(z1) <= -z2
        return Diagonal(zeros(m))
    else
        D = zeros(m, m)
        for i = 1:m
            if i < m
                D[i, i] = 0.5 + 0.5 * z2 / norm(z1) - 0.5 * z2 * ((z1[i])^2.0) / norm(z1)^3.0
            else
                D[i, i] = 0.5
            end
            for j = 1:m
                if j > i
                    if j < m
                        D[i, j] = -0.5 * z2 * z1[i] * z1[j] / norm(z1)^3.0
                        D[j, i] = -0.5 * z2 * z1[i] * z1[j] / norm(z1)^3.0
                    elseif j == m
                        D[i, j] = 0.5 * z1[i] / norm(z1)
                        D[j, i] = 0.5 * z1[i] / norm(z1)
                    end
                end
            end
        end
        return D
    end
end

"""
    particle dynamics
    - 3D particle subject to contact forces

    - configuration: q = (x, y, z) ∈ R³
    - impact force (magnitude): n ∈ R₊
    - friction force: b ∈ R²
        - contact force: λ = (b, n) ∈ R² × R₊
        - friction coefficient: μ ∈ R₊

    Discrete Mechanics and Variational Integrators
        pg. 363
"""
struct Particle
      m # mass
      g # gravity
      μ # friction coefficient

      nq # configuration dimension
end

# mass matrix
function mass_matrix(model)
    m = model.m

    Diagonal(@SVector [m, m, m])
end

# gravity
function gravity(model, q)
    m = model.m
    g = model.g

    @SVector [0.0, 0.0, m * g]
end

# signed distance function
function signed_distance(model, q)
    q[3]
end

# contact force Jacobian
function jacobian(model, q)
    Diagonal(@SVector ones(model.nq))
end

# dynamics
function dynamics(model, q1, q2, q3, λ, h)
    nq = model.nq
    SVector{nq}(mass_matrix(model) * (2.0 * q2 - q1 - q3) / h
        - h * gravity(model, q2)
        + h * jacobian(model, q3)' * λ)
end

"""
    step particle system
        solves 1-step feasibility problem
"""
function _step(q1, q2, h;
    tol = 1.0e-8, z0_scale = 0.001, step_type = :gmres, max_iter = 100)
    """
        1-step optimization problem:
            find z
            s.t. r(z) = 0

        z = (q, n, b, ν, η)
            ν, η are slack variables for friction subproblem
    """
    function r(z)
        # system variables
        q3 = view(z, 1:3)
        n = z[4]
        b = view(z, 5:6)

        λ = [b; n]                     # contact forces
        ϕ = signed_distance(model, q3) # signed-distance function

        # friction subproblem variables
        b̄ = view(z, 5:7)
        ν = z[8]
        η = view(z, 9:11)

        # action optimality conditions
        [dynamics(model, q1, q2, q3, λ, h);
         ϕ - κ_no(ϕ - n);

         # maximum dissipation optimality conditions
         [(view(q3, 1:2) - view(q2, 1:2)) ./ h; -ν] - η;
          model.μ * n - b̄[end];
          b̄ - κ_so(b̄ - η)]
    end

    # Jacobian
    function R(z)
        # differentiate r
        _R = ForwardDiff.jacobian(r, z)

        # correct projection derivatives
        q3 = view(z, 1:3)
        ϕ = signed_distance(model, q3) # signed-distance function
        n = z[4]
        b̄ = view(z, 5:7)
        ν = z[8]
        η = view(z, 9:11)

        J_no = Jκ_no([ϕ - n])
        J_so = Jκ_so(b̄ - η)

        _R[4, 3] = 1.0 - J_no[1]
        _R[4, 4] = J_no[1]
        _R[(end-2):end, 5:11] = [(Diagonal(ones(3)) - J_so) zeros(3, 1) J_so]

        return _R
    end

    # initialize
    z = z0_scale * rand(11)
    z[1:3] = copy(q2)

    extra_iters = 0

    for i = 1:max_iter
        # compute residual, residual Jacobian
        res = r(z)

        if norm(res) < tol
            println("   iter ($i) - norm: $(norm(r(z)))")
            return z, true
        end

        jac = R(z)

        # compute step
        if step_type == :gmres
            Δ = gmres(jac, res, abstol = 1.0e-12, maxiter = i + extra_iters)
        else
            Δ = (jac' * jac + 1.0e-6 * I) \ (jac' * res) # damped least-squares direction
        end

        # line search the step direction
        iter = 0
        α = 1.0
        while norm(r(z - α * Δ))^2.0 >= (1.0 - 0.001 * α) * norm(res)^2.0 && α > 1.0e-4
            α = 0.5 * α
            # println("   α = $α")
            iter += 1
            if iter > 100
                @error "line search fail"

                return z, false
            end
        end

        if α <= 1.0e-4
            extra_iters += 1
        end

        println("   iter ($i) - norm: $(norm(r(z)))")

        # update
        z .-= α * Δ
    end

    return z, false
end

"""
    simulate
    - solves 1-step feasibility problem for T time steps
    - initial configurations: q1, q2 (note this can encode initial velocity)
    - time step: h
"""
function simulate(q1, q2, T, h;
    z0_scale = 0.0, step_type = :gmres)
    println("simulation")

    # initialize histories
    q = [q1, q2]
    n = [0.0]
    b = [zeros(2)]

    # step
    for t = 1:T
        println("   t = $t")
        z_sol, status = _step(q[end-1], q[end], h,
            z0_scale = z0_scale, step_type = step_type)

        if !status
            @error "failed step (t = $t)"
            return q, n, b
        else
            push!(q, view(z_sol, 1:3))
            push!(n, z_sol[4])
            push!(b, view(z_sol, 5:6))
        end
    end

    return q, n, b
end

# simulation setup
model = Particle(1.0, 9.81, 1.0, 3)
h = 0.05

# initial conditions
# v1 = [1.0; 1.0; 0.0]
# q1 = [0.0; 0.0; 1.0]

v1 = [-1.0; 10.0; 0.0]
q1 = [0.0; 0.0; 1.0]

v2 = v1 - gravity(model, q1) * h
q2 = q1 + 0.5 * (v1 + v2) * h

q_sol, y_sol, b_sol = simulate(q1, q2, 500, h,
    z0_scale = 0.0, step_type = :gmres)

plot(hcat(q_sol...)[3:3, :]', xlabel = "", label = "z")
plot!(h .* hcat(y_sol...)', xlabel = "", label = "n", linetype = :steppost)

plot(hcat(q_sol...)[1:2, :]', xlabel = "", label = ["x" "y"])
plot!(h * hcat(b_sol...)', label = ["b1" "b2"], linetype = :steppost)


using Images, FileIO, Colors, FixedPointNumbers

# global variables. as a matter of convention, each one ends with an
# underscore. this is to make it harder to use them within the main body of the
# code without explicitly doing so.
# (width, height) of system in voxels
sys_size_ = (140,210);
# (dx, dy) voxel lengths
voxel_lengths_ = (0.12,0.12);
#time steps
dt_ = 1.;
#max time of simulation
max_t_ = 10000;
#min and max values of k and F
# #goes along y axis
# k_ = (0.0, 0.07);
# #goes along x axis
# F_ = (0.04, 0.16);
#goes along y axis
F_ = (0.0, 0.04);
#goes along x axis
k_ = (0.03, 0.07);
# #(Du Dv): diffusion constants
# D_ = (2e-5, 1e-5);
#(Du Dv): diffusion constants
D_ = (2e-3, 1e-3);
#proportion of squares to initialise
random_init_ = 0.99;
#choose boundary conditions, either dirichlet or neumann
diff_mode_ = "neumann";
checkpoint_int_ = 5;
checkpoint_name_ = "./gray_scott"

function create_initial_conditions((num_x, num_y), random_init)
    u = rand(num_x, num_y)
    @. u[u < random_init] = 0
    @. u[u >= random_init] = 1
    v = 1 .- u
    (u,v)
end;

function create_reaction_rates((min_k,max_k), (min_F, max_F), (num_x, num_y))
    ks = reshape(repeat(range(min_k, stop=max_k, length=num_x), num_y), (num_x,num_y))
    Fs = transpose(reshape(repeat(range(min_F, stop=max_F, length=num_y), num_x), (num_y,num_x)))
    reaction_rates = zeros(2, num_x, num_y)
    reaction_rates[1,:,:] = ks
    reaction_rates[2,:,:] = Fs
    reaction_rates
end;

function diffusion_voxel(i,j, dx_diff, a, (dx,dy), (num_x, num_y), dt, D, diff_mode)
    if diff_mode=="neumann"
        #diff left
        if i - 1 >= 1
            dx_diff[i-1,j] += a[i,j] * dt * D / (dx^2)
            dx_diff[i,j] -= a[i,j] * dt * D / (dx^2)
        end
        #diff right
        if i + 1 <= num_x
            dx_diff[i+1,j] += a[i,j] * dt * D / (dx^2)
            dx_diff[i,j] -= a[i,j] * dt * D / (dx^2)
        end
        #diff up
        if j - 1 >= 1
            dx_diff[i,j-1] += a[i,j] * dt * D / (dy^2)
            dx_diff[i,j] -= a[i,j] * dt * D / (dy^2)
        end
        #diff down
        if j + 1 <= num_y
            dx_diff[i,j+1] += a[i,j] * dt * D / (dy^2)
            dx_diff[i,j] -= a[i,j] * dt * D / (dy^2)
        end
    elseif diff_mode=="dirichlet"
        #diff left
        if i - 1 >= 1
            dx_diff[i-1,j] += a[i,j] * dt * D / (dx^2)
        end
        #diff right
        if i + 1 <= num_x
            dx_diff[i+1,j] += a[i,j] * dt * D / (dx^2)
        end
        #diff up
        if j - 1 >= 1
            dx_diff[i,j-1] += a[i,j] * dt * D / (dy^2)
        end
        #diff down
        if j + 1 <= num_y
            dx_diff[i,j+1] += a[i,j] * dt * D / (dy^2)
        end
        dx_diff[i,j] -= 4 * a[i,j] * dt * D / (dy^2)
    end
    dx_diff
end

function diffusion_species(a, D, (dx, dy), (num_x, num_y), dt, diff_mode)
    dx_diff = zeros(num_x, num_y)
    for i in 1:num_x
        for j in 1:num_y
            dx_diff = diffusion_voxel(i,j, dx_diff, a, (dx,dy), (num_x, num_y), dt, D, diff_mode)
        end
    end
    dx_diff
end

function diffusion_step((u,v), (Du, Dv), (dx, dy), (num_x, num_y), dt, diff_mode)
    du_diff = diffusion_species(u, Du, (dx, dy), (num_x, num_y), dt, diff_mode)
    dv_diff = diffusion_species(v, Dv, (dx, dy), (num_x, num_y), dt, diff_mode)
    (du_diff, dv_diff)
end

function reaction_step((u,v), reaction_rates, (dx,dy), (num_x, num_y), dt)
    ks = reaction_rates[1,:,:]
    Fs = reaction_rates[2,:,:]
    non_linear = zeros(num_x, num_y)
    du_reac = zeros(num_x, num_y)
    dv_reac = zeros(num_x, num_y)
    @. non_linear = (v^2) * u * dt
    @. du_reac = -non_linear + (Fs*(1-u) * dt)
    @. dv_reac = non_linear - ((ks + Fs) * v * dt)
    (du_reac, dv_reac)
end;

function pde_step!((u, v), D, reaction_rates, voxel_lengths, sys_size, dt, diff_mode)
    du_reac, dv_reac = reaction_step((u,v), reaction_rates, voxel_lengths, sys_size, dt)
    du_diff, dv_diff = diffusion_step((u,v), D, voxel_lengths, sys_size, dt, diff_mode)
    @. u += du_diff + du_reac
    @. v += dv_diff + dv_reac
    (u, v)
end;

function pixel_color_transform(pixel)
    output = zeros(3)
    #red to blue colormap
    # if pixel <= 0
        # output = RGB(1,0,0)
    # elseif pixel < 0.25
        # output = RGB(1-4pixel, 4pixel, 0)
    # elseif pixel <=0.5
        # output = RGB(0, 1-4(pixel-0.25), 4(pixel-0.25))
    # else
        # output = RGB(0,0,1)
    # end

    #white to blue colormap
    if pixel <= 0
        output = RGB(1.,1.,1.)
    elseif pixel <=0.5
        output = RGB(1-2pixel,1-2pixel,1)
    else
        output = RGB(0.,0.,1.)
    end
    output
end

function render_img((u,v))
    x,y = size(u)
    canvas = zeros(RGB,x,y)
    for i in 1:x
        for j in 1:y
            canvas[i,j] = pixel_color_transform(v[i,j])
        end
    end
    canvas
end

function set_up_simulate(max_t, dt, sys_size, random_init, k, F)
    num_steps = round(max_t/dt)
    u, v = create_initial_conditions(sys_size, random_init)
    reaction_rates = create_reaction_rates(k, F, sys_size)
    (num_steps, (u,v), reaction_rates)
end

function simulate!((u,v), D, reaction_rates, voxel_lengths, sys_size, dt, num_steps, diff_mode, checkpoint_int, checkpoint_name)
    x,y = size(u)
    num_frames = floor(Int, num_steps/checkpoint_int)
    index_count = 1
    for i in 1:num_steps
        u,v = pde_step!((u,v), D, reaction_rates, voxel_lengths, sys_size, dt, diff_mode)
        if ((i%checkpoint_int) - 1) == 0
            println("Processing step ", i, " out of ", num_steps)
            img = render_img((u,v))
            save(string(checkpoint_name,"_",index_count,".jpg"), img);
            index_count += 1
        end
    end
    (u,v)
end

function main(max_t, dt, sys_size, random_init, k, F, D, voxel_lengths, diff_mode, checkpoint_int, checkpoint_name)
    num_steps, (u,v), reaction_rates = set_up_simulate(max_t, dt, sys_size, random_init, k, F)
    u, v = simulate!((u,v), D, reaction_rates, voxel_lengths, sys_size, dt, num_steps, diff_mode, checkpoint_int, checkpoint_name)
end

main(max_t_, dt_, sys_size_, random_init_, k_, F_, D_, voxel_lengths_, diff_mode_, checkpoint_int_, checkpoint_name_)


# This file contains the code for differentiating the Cholesky decomposition

function level2partition(A::AbstractMatrix, j::Int)
    @inbounds begin
        N = size(A, 1)
        r = view(A, j, 1:j-1)
        d = view(A, j, j)
        B = view(A, j+1:N , 1:j-1)
        c = view(A, j+1:N, j)
    end
    return r, d, B, c
end

# Derivative of Cholesky decomposition, see Murray(2016). Differentiation of the Cholesky decomposition. arXiv.1602.07527
function chol_unblocked_rev!(L::AbstractMatrix, A_bar::AbstractMatrix)
    N = size(A_bar, 1)
    @inbounds for j in N:-1:1
        r, d, B, c = level2partition(L, j)
        r_bar, d_bar, B_bar, c_bar = level2partition(A_bar, j)
        d_bar[1] -= dot(c_bar, c) / d[1]
        d_bar[1] /= d[1]

        for i in eachindex(c_bar)
            c_bar[i] /= d[1]
        end

        for i in eachindex(r_bar)
            r_bar[i] -= d_bar[1]*r[i]
        end

        # r_bar .-= B'c_bar
        BLAS.gemv!('T', -1.0, B, c_bar, 1.0, r_bar) # r_bar = r_bar - Bᵀ c_bar
        # B_bar .-= c_bar * r'
        BLAS.ger!(-1.0, c_bar, r, B_bar) # B_bar = B_bar - c_bar * r'
        d_bar[1] /= 2
    end

    return A_bar
end


@model lbaModel(data, N, Nc, ::Type{T}=Float64) where {T} = begin
     mn = minimum(x -> x[2], data)
     tau ~ TruncatedNormal(0.4, 0.1, 0, mn)
     A ~ TruncatedNormal(0.8, 0.4, 0, Inf)
     k ~ TruncatedNormal(0.2, 0.3, 0, Inf)
     v = Vector{T}(undef, Nc)
     for i in 1:Nc
          v[i] ~ TruncatedNormal(0, 3, 0, Inf)
     end
     data ~ [LBA(; ν=v, τ=tau, A=A, k=k)]
end

function AHMClba(choice,rt,N,Nc)
    data=[(c,r) for (c,r) in zip(choice,rt)]
    return lbaModel(data,N,Nc)
end

AHMCconfig = Turing.NUTS(1000,.85)

CmdStanLBA = "
functions{

     real lba_pdf(real t, real b, real A, real v, real s){
          //PDF of the LBA model

          real b_A_tv_ts;
          real b_tv_ts;
          real term_1;
          real term_2;
          real term_3;
          real term_4;
          real pdf;

          b_A_tv_ts = (b - A - t*v)/(t*s);
          b_tv_ts = (b - t*v)/(t*s);
          term_1 = v*Phi(b_A_tv_ts);
          term_2 = s*exp(normal_lpdf(b_A_tv_ts|0,1));
          term_3 = v*Phi(b_tv_ts);
          term_4 = s*exp(normal_lpdf(b_tv_ts|0,1));
          pdf = (1/A)*(-term_1 + term_2 + term_3 - term_4);

          return pdf;
     }

     real lba_cdf(real t, real b, real A, real v, real s){
          //CDF of the LBA model

          real b_A_tv;
          real b_tv;
          real ts;
          real term_1;
          real term_2;
          real term_3;
          real term_4;
          real cdf;

          b_A_tv = b - A - t*v;
          b_tv = b - t*v;
          ts = t*s;
          term_1 = b_A_tv/A * Phi(b_A_tv/ts);
          term_2 = b_tv/A   * Phi(b_tv/ts);
          term_3 = ts/A     * exp(normal_lpdf(b_A_tv/ts|0,1));
          term_4 = ts/A     * exp(normal_lpdf(b_tv/ts|0,1));
          cdf = 1 + term_1 - term_2 + term_3 - term_4;

          return cdf;

     }

     real lba_lpdf(matrix RT, real k, real A, vector v, real s, real tau){

          real t;
          real b;
          real cdf;
          real pdf;
          vector[rows(RT)] prob;
          real out;
          real prob_neg;

          b = A + k;
          for (i in 1:rows(RT)){
               t = RT[i,1] - tau;
               if(t > 0){
                    cdf = 1;

                    for(j in 1:num_elements(v)){
                         if(RT[i,2] == j){
                              pdf = lba_pdf(t, b, A, v[j], s);
                         }else{
                              cdf = (1-lba_cdf(t, b, A, v[j], s)) * cdf;
                         }
                    }
                    prob_neg = 1;
                    for(j in 1:num_elements(v)){
                         prob_neg = Phi(-v[j]/s) * prob_neg;
                    }
                    prob[i] = pdf*cdf;
                    prob[i] = prob[i]/(1-prob_neg);
                    if(prob[i] < 1e-10){
                         prob[i] = 1e-10;
                    }

               }else{
                    prob[i] = 1e-10;
               }
          }
          out = sum(log(prob));
          return out;
     }
}

data{
     int N;
     int Nc;
     vector[N] rt;
     vector[N] choice;
}

parameters {
     real<lower=0> k;
     real<lower=0> A;
     real<lower=0> tau;
     vector<lower=0>[Nc] v;
}

model {
     real s;
     real minRT;
     matrix[N,2] RT;
     s=1;
     minRT = min(rt);
     RT[:,1] = rt;
     RT[:,2] = choice;
     k ~ normal(.2,.3)T[0,];
     A ~ normal(.8,.4)T[0,];
     tau ~ normal(.4,.1)T[0,minRT];
     for(n in 1:Nc){
          v[n] ~ normal(0,3)T[0,];
     }
     RT ~ lba(k,A,v,s,tau);
}
"

CmdStanConfig = Stanmodel(name="CmdStanLBA", model=CmdStanLBA, nchains=1,
   Sample(num_samples=1000, num_warmup=1000, adapt=CmdStan.Adapt(delta=0.8),
   save_warmup=true))

   struct LBAProb{T}
      data::T
      N::Int
      Nc::Int
  end

  function (problem::LBAProb)(θ)
      @unpack data=problem
      @unpack v,A,k,tau=θ
      d=LBA(ν=v, A=A, k=k, τ=tau)
      minRT = minimum(x->x[2], data)
      logpdf(d,data)+sum(logpdf.(TruncatedNormal(0, 3, 0, Inf), v)) +
      logpdf(TruncatedNormal(.8, .4, 0, Inf),A)+logpdf(TruncatedNormal(.2, .3 ,0 ,Inf), k)+
      logpdf(TruncatedNormal(.4, .1, 0, minRT), tau)
  end

function sampleDHMC(choice, rt, N, Nc, nsamples)
    data = [(c, r) for (c, r) in zip(choice, rt)]
    return sampleDHMC(data, N, Nc, nsamples)
end

# Define problem with data and inits.
function sampleDHMC(data, N, Nc, nsamples, autodiff)
    p = LBAProb(data, N, Nc)
    p((v=fill(.5, Nc), A=.8, k=.2, tau=.4))
    # Write a function to return properly dimensioned transformation.
    trans = as((v=as(Array, asℝ₊, Nc),A=asℝ₊, k=asℝ₊, tau=asℝ₊))
    # Use Flux for the gradient.
    P = TransformedLogDensity(trans, p)
    ∇P = ADgradient(autodiff, P)
    # FSample from the posterior.
    n = dimension(trans)
    results = mcmc_with_warmup(Random.GLOBAL_RNG, ∇P, nsamples;
        q = zeros(n), p = ones(n),reporter = NoProgressReport())
    # Undo the transformation to obtain the posterior from the chain.
    posterior = transform.(trans, results.chain)
    chns = nptochain(results, posterior)
    return chns
end

function simulateLBA(;Nd, v=[1.0,1.5,2.0], A=.8, k=.2, tau=.4, kwargs...)
    return (rand(LBA(ν=v, A=A, k=k, τ=tau), Nd)..., N=Nd, Nc=length(v))
end


include("cg_sem.jl")
using StaticArrays: SVector

wave_speed(ρ, p, γ) = sqrt(γ * p ./ ρ)

Base.@kwdef struct Params{T}
    T_virt_surf::T = 280
    T_min_ref::T = 230
    MSLP::T = 10^5
    grav::T = 98 // 100
    R_d::T = 287
    γ::T = 7 // 5
end

# Taken from Daniel's code
function decaying_temperature_profile(params::Params, z)
    # Scale height for surface temperature
    H_sfc = params.R_d * params.T_virt_surf / params.grav
    H_t = H_sfc

    z′ = z / H_t
    tanh_z′ = tanh(z′)

    ΔTv = params.T_virt_surf - params.T_min_ref
    Tv = params.T_virt_surf - ΔTv * tanh_z′

    ΔTv′ = ΔTv / params.T_virt_surf
    p = -H_t * (z′ + ΔTv′ * (log(1 - ΔTv′ * tanh_z′) - log(1 + tanh_z′) + z′))
    p /= H_sfc * (1 - ΔTv′^2)
    p = params.MSLP * exp(p)
    ρ = p / (params.R_d * Tv)
    return (p = p, ρ = ρ)
end

function element_split_form!(∂q, q, O, H, z)
  Nq, K, nflds = size(q)

  Q = O.Q
  @inbounds for e in 1:K
    for i in 1:Nq, j in 1:Nq
      # get the two states
      qi = @view q[i, e, :]
      qj = @view q[j, e, :]
      zi = z[i, e]
      zj = z[j, e]
      # Evaluate the flux difference form:
      #   ((H ∘ Qᵀ) - (Q ∘ H)) 𝟙
      ∂q[i, e, :] += (Q[i, j] - Q[j, i]) * H(qi, qj, zi, zj)
    end
  end
end

pressure(ρ, w, ρe, z, g, γ) = (γ - 1) * (ρe - ρ * w^2 / 2 - ρ * g * z)

function KennedyGruber(q_i, q_j, z_i, z_j, grav, pressure)
  @inbounds (ρ_i, w_i, ρe_i) = q_i[1:3]
  @inbounds (ρ_j, w_j, ρe_j) = q_j[1:3]
  e_i, e_j = ρe_i / ρ_i, ρe_j / ρ_j
  p_i = pressure(ρ_i, w_i, ρe_i, z_i, grav)
  p_j = pressure(ρ_j, w_j, ρe_j, z_j, grav)
  ϕ_i = z_i * grav
  ϕ_j = z_j * grav
  (fρ, fρw, fρe) = KennedyGruber(ρ_i, w_i, e_i, p_i, ϕ_i, ρ_j, w_j, e_j, p_j, ϕ_j)
  return SVector(fρ, fρw / ρ_i, fρe)
end

function KennedyGruber(ρ_i, w_i, e_i, p_i, ϕ_i, ρ_j, w_j, e_j, p_j, ϕ_j)
  ρ̄ = (ρ_i + ρ_j) / 2
  w̄ = (w_i + w_j) / 2
  Δw = (w_j - w_i) / 2
  ē = (e_i + e_j) / 2
  Δϕ = (ϕ_j - ϕ_i) / 2

  p̄ = (p_i + p_j) / 2

  fρ  = ρ̄ * w̄
  fρw = ρ̄ * w̄ * Δw + p̄ + ρ̄ * Δϕ
  fρe = (ρ̄ * ē + p̄) * w̄
  return (fρ, fρw, fρe)
end

function tendency!(
    ∂q,
    q,
    t,
    H,
    mesh,
    elem,
    grav,
    p_,
    bc = (nothing, nothing),
    forcing = nothing,
)
    fill!(∂q, 0)

    # Evaluate the volume terms
    element_split_form!(∂q, q, elem, H, mesh.zdg)

    # XXX: Hack in the bcs
    ρ = q[1, 1, 1]
    w = q[1, 1, 2]
    ρe = q[1, 1, 3]
    p = p_(ρ, w, ρe, mesh.zdg[1], grav)
    ∂q[1, 1, 2] -= p / ρ
    ρ = q[end, end, 1]
    w = q[end, end, 2]
    ρe = q[end, end, 3]
    p = p_(ρ, w, ρe, mesh.zdg[end], grav)
    ∂q[end, end, 2] += p / ρ

    # Add in MMS formcing
    if !isnothing(forcing)
        error("not implemented yet")
    end

    # Use upwind boundary treatment
    if !isnothing(bc[1])
        error("not implemented yet")
    end

    if !isnothing(bc[2])
        error("not implemented yet")
    end

    # DG -> CG projection and then scatter for storage
    dg2cg_scatter(∂q, mesh)
end


"""
Time-lock Puzzle
================

This module reimplements Ron Rivest's "LCS35 Time Capsule" given at the 35th birthday of MIT's
Laboratory for Computer Science.

# What are missing in the code:

1. Estimate wall time needed to crack the puzzle according to Moore's law ("The free lunch is over",
   anyway).
2. Error checking during computation.
3. Attach information that allows one to factor `n`.

# References

- LCS35 Time Capsule Crypto-Puzzle (http://people.csail.mit.edu/rivest/lcs35-puzzle-description.txt)
- Time-lock encryption (http://www.gwern.net/Self-decrypting-files)
"""
module TimeLockPuzzle

using Primes: nextprime

PRIME_LENGTH = 1024
TWO_POWER = big(1) << PRIME_LENGTH

# The `Puzzle` struct can be (de)serialized but the code is omitted here.
"The output of the generated puzzle"
struct Puzzle
    "The modulo"
    n::BigInt

    "The time (steps of computation) expected to crack the puzzle"
    t::BigInt

    "Encrypted secret message, encoded as `BigInt`"
    enc_message::BigInt
end

# The string encoding is UTF-8. For secret messages written in Eastern-asian languages, UTF-16 is
# preferred, but the Julia community discriminates against the poor UTF-16 and puts it into
# "LegacyStrings.jl".

# Steps to create the puzzle:
#
# 1. Encrypt message `M` with key `K` using a symmetric encryption algorithm
#
#         C_M = Encrypt(K, M)
#
# 2. Encrypt the key
#
#         C_K = K + a ^ (2^t)  (mod n)
#
#     By Euler's theorem, to compute this efficiently, one can first compute
#
#         e = 2 ^ t  (mod phi(n))
#
#     and then
#
#         b = a ^ e  (mod n)
#
# 3. The output of the puzzle is
#
#         (n, a, t, C_M, C_K)
#
# --------------------------------------------------------------------------------------------------
#
# The implementation is slightly different from the description in the paper. Here `a` is always 2,
# `C_K` is always 0, and the encrytion algorithm is just XOR. Therefore the actual output tuple is
# `(n, t, C_M)`.
function create_puzzle(p_seed::BigInt,
                       q_seed::BigInt,
                       t::BigInt,
                       secret::String)::Union{Puzzle, Nothing}
    @assert p_seed != q_seed "Two identical seeds"

    # 5 has maximal order modulo 2^k (Let's assume it's right).
    p = nextprime(powermod(big(5), p_seed, TWO_POWER))
    q = nextprime(powermod(big(5), q_seed, TWO_POWER))

    n = p * q
    # Euler's totient function
    phi = (p - 1) * (q - 1)

    # Generates the puzzle
    u = powermod(big(2), t, phi)
    w = powermod(big(2), u, n)

    secret_bytes = Vector{UInt8}(secret)
    secret_big = convert(BigInt, secret_bytes)
    if secret_big > n
        return nothing
    end

    enc_secret_big = xor(secret_big, w)
    Puzzle(n, t, enc_secret_big)
end

# The puzzle can be solved by performing t successive squarings modulo n, beginning with 2.
#
#     W(0) = 2
#     W(i+1) = W(i)^2  (mod n)    for i>0
#
# There is no known way to perform this more quickly without knowing the factorization of n.
function solve_puzzle(puzzle::Puzzle)::String
    t = puzzle.t
    n = puzzle.n

    k = big(2)
    for _i in 1:t
        k = (k * k) % n
    end

    dec_message = xor(puzzle.enc_message, k)
    String(convert(Vector{UInt8}, dec_message))
end

import Base: convert

"`BigInt` -> `Vector{UInt8}` conversion"
function convert(::Type{Vector{UInt8}}, x::BigInt)
    sz = div(ndigits(x, base=2) + 8 - 1, 8)
    bytes = UInt8[]
    sizehint!(bytes, sz)
    while x != 0
        push!(bytes, x & 255)
        x >>= 8
    end
    reverse!(bytes)
end

"`Vector{UInt8}` -> `BigInt` conversion"
function convert(::Type{BigInt}, v::Vector{UInt8})
    foldl((acc, b) -> (acc << 8) + b, v; init=big(0))
end

function run()
    t = big(1048576)  # A distant future
    maybe_puzzle = create_puzzle(big(17070415), big(17830918), t,
        "I have proven Euler's sum of powers conjecture.")  # Some nonsense
    if maybe_puzzle == nothing
        error("Cannot create the puzzle")
    end

    dec_msg = solve_puzzle(maybe_puzzle)
    println("The secret message is: \"$dec_msg\"")
end

end  # module


# # Minimization of simple loss functions
#
# First we load the required packages.

using
    Distributions,
    LinearAlgebra,
    Random,
    Plots

using
    EnsembleKalmanProcesses.EnsembleKalmanProcessModule,
    EnsembleKalmanProcesses.ParameterDistributionStorage

# ## Loss function with single minimum
#
# Here, we minimize the loss function
# ```math
# G₁(u) = \|u - u_*\| ,
# ```
# where ``u`` is a 2-vector of parameters and ``u_*`` is given; here ``u_* = (-1, 1)``.
u_min = [0.2, 0.05] #minimizer is 0,0
function G₁(u)

    if (u[1]>=0) && (u[2] >=0)
        return sqrt((u[1]-u_min[1])^2 + (u[2]-u_min[2])^2)
    else
        return NaN
    end
end

# We set the seed for pseudo-random number generator for reproducibility.
rng_seed = 41
Random.seed!(rng_seed)

# We set a stabilization level, which can aid the algorithm convergence
dim_output = 1
stabilization_level = 1e-4
Γ_stabilization = stabilization_level * Matrix(I, dim_output, dim_output)

# The functional is positive so to minimize it we may set the target to be 0,
G_target  = [0]

# ### Prior distributions
#
# As we work with a Bayesian method, we define a prior. This will behave like an "initial guess"
# for the likely region of parameter space we expect the solution to live in.
prior_distributions = [Parameterized(Normal(0, 1)), Parameterized(Normal(0, 1))]

constraints = [[no_constraint()], [no_constraint()]]

parameter_names = ["u1", "u2"]

prior = ParameterDistribution(prior_distributions, constraints, parameter_names)

# ### Calibration
#
# We choose the number of ensemble members and the number of iterations of the algorithm
N_ensemble   = 30
N_iterations = 10
nothing # hide

# The initial ensemble is constructed by sampling the prior
initial_ensemble =
    EnsembleKalmanProcessModule.construct_initial_ensemble(prior, N_ensemble;
                                                           rng_seed=rng_seed)
# We then initialize the Ensemble Kalman Process algorithm, with the initial ensemble, the
# target, the stabilization and the process type (for EKI this is `Inversion`, initialized
# with `Inversion()`).
ensemble_kalman_process =
    EnsembleKalmanProcessModule.EnsembleKalmanProcess(initial_ensemble, G_target,
                                                      Γ_stabilization, Inversion())

# Then we calibrate by *(i)* obtaining the parameters, *(ii)* calculate the loss function on
# the parameters (and concatenate), and last *(iii)* generate a new set of parameters using
# the model outputs:
for i in 1:N_iterations
    params_i = get_u_final(ensemble_kalman_process)

    g_ens = hcat([G₁(params_i[:, i]) for i in 1:N_ensemble]...)
    println("Iteration: ",i," number of particle failures: ", sum([1 for i = 1:size(g_ens,2) if isnan(g_ens[1,i])]), "/", size(g_ens,2))

    EnsembleKalmanProcessModule.update_ensemble!(ensemble_kalman_process, g_ens)

end

# and visualize the results:
u_init = get_u_prior(ensemble_kalman_process)

anim_unique_minimum = @animate for i in 1:N_iterations
    u_i = get_u(ensemble_kalman_process, i)
    g_i = get_g(ensemble_kalman_process, i)
    failed_particles = [i for i = 1:size(g_i,2) if isnan(g_i[1,i])]
    successful_particles = filter(x-> !(x in failed_particles), collect(1:size(g_i,2)))
    u_i_succ = u_i[:,successful_particles]
    u_i_fail = u_i[:,failed_particles]

    plot([u_min[1]], [u_min[2]],
          seriestype = :scatter,
         markershape = :star5,
          markersize = 11,
         markercolor = :green,
               label = "optimum u⋆"
         )

    plot!(u_i_succ[1, :], u_i_succ[2, :],
             seriestype = :scatter,
                  xlims = extrema(u_init[1, :]),
                  ylims = extrema(u_init[2, :]),
                 xlabel = "u₁",
                 ylabel = "u₂",
             markersize = 5,
            markeralpha = 0.6,
            markercolor = :blue,
                  label = "successes",
                  title = "EKI iteration = " * string(i)
          )
        plot!(u_i_fail[1, :], u_i_fail[2, :],
             seriestype = :scatter,
                  xlims = extrema(u_init[1, :]),
                  ylims = extrema(u_init[2, :]),
             markersize = 5,
            markeralpha = 0.6,
              markercolor = :red,
              markershape = :x,
                  label = "failures",
         )

end
# The results show that the minimizer of ``G_1`` is ``u=u_*``.

gif(anim_unique_minimum, "unique_minimum.gif", fps = 1) # hide


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

using Markdown
using InteractiveUtils

# ╔═╡ 81cdc1aa-04fd-11eb-3235-bff0e3f52535
using Pkg, DrWatson

# ╔═╡ 81cdee8c-04fd-11eb-29f5-85927396b6dc
begin
	@quickactivate "SR2TuringPluto"
	using Turing
	using StatisticalRethinking
end

# ╔═╡ 5e06a11c-04f6-11eb-0116-1fd51d614f99
md"## clip-04-64-79t"

# ╔═╡ b8b8de5e-0512-11eb-0932-830c3055e00a
#include(srcdir("tools.jl"))

md"### snippet 4.64"

# ╔═╡ 81ce6d32-04fd-11eb-2c4f-77bdf6a3f9e2
begin
	df = CSV.read(sr_datadir("Howell1.csv"), DataFrame)
	scale!(df, [:weight])
end;

# ╔═╡ 81e16d54-04fd-11eb-26e6-5b62d66d3d34
f_parabola(weight_s, a, b1, b2) = a + b1 * weight_s + b2 * weight_s^2

# ╔═╡ 81ee40ec-04fd-11eb-1c71-6b4674695b56
@model function parabola(weight_s, heights)
    a ~ Normal(178, 20)
    b1 ~ LogNormal(0, 1)
    b2 ~ Normal(0, 1)
    σ ~ Uniform(0, 50)
    μ = f_parabola.(weight_s, a, b1, b2)
    heights ~ MvNormal(μ, σ)
end

# ╔═╡ 81f015b6-04fd-11eb-23c4-3f3f8a7201c8
begin
	quap4_5t = quap(parabola(df.weight_s, df.height), NelderMead())
	quap4_5t.coef
end

# ╔═╡ 960b36fe-0573-11eb-0e51-9f7bc0d175a5
quap4_5t.vcov

# ╔═╡ 81f6618c-04fd-11eb-27e7-dffa73914969
md"### snippet 4.67"

# ╔═╡ 3228440c-0527-11eb-00f0-9b38ae90372c
begin
	weight_seq_1 = range(-2.2, 2, length = 30)
	post_1 = DataFrame(rand(quap4_5t.distr, 1_000)', quap4_5t.params)
	mu_1 = f_parabola.(weight_seq_1, post_1.a', post_1.b1', post_1.b2')
	sim_1 = rand.(Normal.(mu_1, post_1.σ'))
	mu_1 = meanlowerupper(mu_1)
	sim_1 = meanlowerupper(sim_1)
end;

# ╔═╡ 82285bce-04fd-11eb-0bfd-27cbf60511ee
md"### snippet 4.68"

# ╔═╡ 93badee6-0527-11eb-16bc-b1c6541276ba
begin
	scatter(df.weight_s, df.height, ms = 3, alpha = 0.7, legend = false)
	plot!(weight_seq_1, mu_1.mean, ribbon = (mu_1.mean .- mu_1.lower, mu_1.upper .- mu_1.mean))
	plot!(weight_seq_1, sim_1.lower, fillrange = sim_1.upper, alpha = 0.3, linealpha = 0.0, c = 2)
end

# ╔═╡ 4f386d5c-0573-11eb-084c-1758217d06f9
md"## End of clip-04-64-68t.jl"

# ╔═╡ Cell order:
# ╟─5e06a11c-04f6-11eb-0116-1fd51d614f99
# ╠═81cdc1aa-04fd-11eb-3235-bff0e3f52535
# ╠═81cdee8c-04fd-11eb-29f5-85927396b6dc
# ╟─b8b8de5e-0512-11eb-0932-830c3055e00a
# ╠═81ce6d32-04fd-11eb-2c4f-77bdf6a3f9e2
# ╠═81e16d54-04fd-11eb-26e6-5b62d66d3d34
# ╠═81ee40ec-04fd-11eb-1c71-6b4674695b56
# ╠═81f015b6-04fd-11eb-23c4-3f3f8a7201c8
# ╠═960b36fe-0573-11eb-0e51-9f7bc0d175a5
# ╟─81f6618c-04fd-11eb-27e7-dffa73914969
# ╠═3228440c-0527-11eb-00f0-9b38ae90372c
# ╠═82285bce-04fd-11eb-0bfd-27cbf60511ee
# ╠═93badee6-0527-11eb-16bc-b1c6541276ba
# ╟─4f386d5c-0573-11eb-084c-1758217d06f9


using JuMP, ForwardDiff, Ipopt
using EAGO
using IntervalArithmetic
using DelimitedFiles
using Statistics
using LatinHypercubeSampling

function Ammonia_oxidation_rate(x)
    c_NH = x[1]
    c_O = x[2]
    r_AOmax = 0.67 # mg N-NH+ /g VSS_AO min
    K_OAO = 0.3 # mg O2/L
    K_SAO = 0.34 # mg N-NH4+/L
    K_IAO = 6200 # mg N-NH4+/L
    r_AO = r_AOmax*(c_NH/(K_SAO + c_NH + c_NH^2/K_IAO))*(c_O/(K_OAO + c_O)); # mg N-NH+ /g VSS_AO min
    return r_AO
end

##
#Data_LHC = randomLHC(100000,2)
#Data_input = scaleLHC(Data_LHC,[(0.0,40.0),(0.0,9.1)])
#Data_output = mapslices(Ammonia_oxidation_rate,Data_input; dims=2)
#out_r_AO = [Data_input Data_output]
#writedlm("TrainingData.csv",out_r_AO,',')

Data_LHC1 = randomLHC(10000,2)
Data_input1 = scaleLHC(Data_LHC1,[(0.0,4.0),(0.0,1.0)])
Data_output1 = mapslices(Ammonia_oxidation_rate, Data_input1; dims=2)
out_r_AO1 = [Data_input1 Data_output1]

Data_LHC2 = randomLHC(10000,2)
Data_input2 = scaleLHC(Data_LHC2,[(0.0,40.0),(0.0,9.1)])
Data_output2 = mapslices(Ammonia_oxidation_rate, Data_input2; dims=2)
out_r_AO2 = [Data_input2 Data_output2]

out_r_AO = [out_r_AO1; out_r_AO2]

writedlm("TrainingData2.csv",out_r_AO,',')


"""
	delaunayTriangulation(V::Lar.Points)::Lar.Cells

Return highest level simplices of Delaunay triangulation.

# Examples 1D
```jldoctest

julia> V = [1. 2. 5. 6. 0. 7.];

julia> DT = AlphaShapes.delaunayTriangulation(V)
5-element Array{Array{Int64,1},1}:
 [1, 5]
 [1, 2]
 [2, 3]
 [3, 4]
 [4, 6]
```

# Examples 2D
```jldoctest

julia> V = [
 1. 2. 1. 2. ;
 0. 0. 1. 2.
];

julia> DT = AlphaShapes.delaunayTriangulation(V)
2-element Array{Array{Int64,1},1}:
 [1, 2, 3]
 [2, 3, 4]
```

# Examples 3D
```jldoctest

julia> V = [
 1. 2. 1. 2. ;
 0. 0. 1. 2. ;
 3. 1. 0. 2.
];

julia> DT = AlphaShapes.delaunayTriangulation(V)
1-element Array{Array{Int64,1},1}:
 [1, 2, 3, 4]
```
"""
#function delaunayTriangulation(V::Lar.Points)::Lar.Cells
#	dim = size(V, 1)
#	@assert dim > 0 "delaunayTriangulation: V do not contains points."
#	@assert dim < 4 "delaunayTriangulation: Function not yet Programmed."
#
#
#	if dim == 1
#		vertices = vcat(V...)
#		p = sortperm(vertices)
#		upper_simplex = [[p[i],p[i+1]] for i=1:length(p)-1]
#		return sort(sort.(upper_simplex))
#	else
#		upper_simplex = delaunayMATLAB(V)
#		return sort(sort.(upper_simplex))
#	end
#
#end

"""
	delaunayMATLAB(V::Lar.Points)

Delaunay triangulation algorithm in MATLAB.
"""
#function delaunayMATLAB(V::Lar.Points)
#
#	dim = size(V,1)
#	@assert dim <=3 "delaunayMATLAB: input points have invalid dimension."
#
#	W = convert(Lar.Points,V')
#	@mput W
#	mat"DT = delaunay(W)"
#	@mget DT
#	DT = convert(Array{Int64,2},DT)
#	DT = [DT[i,:] for i in 1:size(DT,1)]
#
#	return DT
#end

# function delaunayTriangulation(points::Lar.Points)
#     V = convert(Lar.Points,points')
#     mesh = delaunay(V);
#     DT = [mesh.simplices[c,:] for c in 1:size(mesh.simplices,1)]
#     return sort.(DT)
# end

# function delaunayTriangulation(V::Lar.Points)::Lar.Cells
# 	return delaunayMATLAB(V)
# end

#---------------------------------- OLD --------------------------------------
@timeit to "delaunayTriangulation" function delaunayTriangulation(V::Lar.Points)::Lar.Cells
	dim = size(V, 1)
	@assert dim > 0 "delaunayTriangulation: V do not contains points."
	@assert dim < 4 "delaunayTriangulation: Function not yet Programmed."

	if dim == 1
		vertices = vcat(V...)
		p = sortperm(vertices)
		upper_simplex = [[p[i],p[i+1]] for i=1:length(p)-1]

	elseif dim == 2
		vertices = convert(Array{Float64,2},V')
		points_map = Array{Int64,1}(collect(1:1:size(vertices)[1]))
		@assert size(vertices, 1) > 2
		upper_simplex = @spawn Triangle.basic_triangulation(vertices, points_map)
		upper_simplex = fetch(upper_simplex)

	elseif dim == 3
		upper_simplex = @spawn AlphaShapes.delaunayWall(V)
		upper_simplex = fetch(upper_simplex)

	end

	sort!.(upper_simplex)
	return sort(upper_simplex)
end


using IntervalArithmetic
using IntervalArithmetic.Complementary
using Test

include("construction.jl")
@testset "internals" begin include("special.jl") end
include("rand.jl")
include("test_ITF1788/run_ITF1788.jl")


include("utils.jl")

@testset "Positive Numbers" begin
    M = PositiveNumbers()
    @testset "Positive Numbers Basics" begin
        @test repr(M) == "PositiveNumbers()"
        @test repr(PositiveVectors(2)) == "PositiveVectors(2)"
        @test repr(PositiveMatrices(2, 3)) == "PositiveMatrices(2, 3)"
        @test repr(PositiveArrays(2, 3, 4)) == "PositiveArrays(2, 3, 4)"
        @test representation_size(M) == ()
        @test manifold_dimension(M) == 1
        @test !is_point(M, -1.0)
        @test_throws DomainError is_point(M, -1.0, true)
        @test is_vector(M, 1.0, 0.0)
        @test vector_transport_to(M, 1.0, 3.0, 2.0, ParallelTransport()) == 6.0
        @test retract(M, 1.0, 1.0) == exp(M, 1.0, 1.0)
        @test isinf(injectivity_radius(M))
        @test isinf(injectivity_radius(M, -2.0))
        @test isinf(injectivity_radius(M, -2.0, ExponentialRetraction()))
        @test isinf(injectivity_radius(M, ExponentialRetraction()))
        @test project(M, 1.5, 1.0) == 1.0
        @test zero_vector(M, 1.0) == 0.0
        X = similar([1.0])
        zero_vector!(M, X, 1.0)
        @test X == [0.0]
    end
    types = [Float64]
    TEST_FLOAT32 && push!(types, Float32)
    for T in types
        @testset "Type $T" begin
            pts = convert.(Ref(T), [1.0, 4.0, 2.0])
            test_manifold(
                M,
                pts,
                test_forward_diff=false,
                test_reverse_diff=false,
                test_vector_spaces=false,
                test_project_tangent=true,
                test_musical_isomorphisms=true,
                test_default_vector_transport=true,
                test_vee_hat=false,
                is_mutating=false,
            )
        end
    end
    @testset "Power of Positive Numbers" begin
        M2 = PositiveVectors(2)
        for T in types
            pts2 = [convert.(Ref(T), v) for v in [[1.0, 1.1], [3.0, 3.3], [2.0, 2.2]]]
            test_manifold(
                M2,
                pts2,
                test_forward_diff=false,
                test_reverse_diff=false,
                test_vector_spaces=false,
                test_project_tangent=true,
                test_musical_isomorphisms=true,
                test_default_vector_transport=true,
                test_vee_hat=false,
            )
        end
    end
end


workspace()
include("../src/LargeMatrixEquations.jl")
using LargeMatrixEquations
using Base.Test

nh = 10
n = nh^2#nh^2
T = diagm(ones(nh)*2)-diagm(ones(nh-1),-1)-diagm(ones(nh-1),1)
I = eye(nh,nh)
A = -(diagm(ones(n)*2)-diagm(ones(n-1),-1)-diagm(ones(n-1),1))#-(kron(T,I)+kron(I,T))
srand(1234)
B = ones(n,1)

E = diagm(ones(n),0)
E[1,1]=2
E[n,n]=3

Z,er2=try
        kpik(A,B,E,infoV=false)
      catch
        error("kpik failed with full matrices")
      end
@test norm(A*Z*Z'*E + E*Z*Z'*A' + B*B') < 0.001

Z,er2=try
        kpik(sparse(A),B,sparse(E))
      catch
        error("kpik failed with sparse matrices")
      end

@test norm(A*Z*Z'*E + E*Z*Z'*A' + B*B') < 0.001

Z,resnorm=try
              rksm(A,B,E,infoV=false)
          catch
              error("rksm failed with full matrices")
          end
@test norm(A*Z*Z'*E + E*Z*Z'*A' + B*B') < 0.001

Z2,resnorm2=try
            rksm(sparse(A),B,sparse(E))
          catch
              error("rksm failed with sparse matrices")
          end
@test norm(A*Z2*Z2'*E + E*Z2*Z2'*A' + B*B') < 0.001

Z,flag,res =try
              lp_lradi(A,B)
            catch
              error("lp_lradi failed with full matricies")
            end
@test norm(A*Z*Z' + Z*Z'*A' + B*B') < 0.001

Z,flag,res =try
              lp_lradi(sparse(A),B,infoV=false)
            catch
              error("lp_lradi failed with sparse matricies")
            end
@test norm(A*Z*Z' + Z*Z'*A' + B*B') < 0.001


"""
    Student's t location scale

* Parametrization
parametrized in \\mu, \\sigma^2 and \\nu

* Score

* Fisher Information

* `time_varying_params` map.

* Default link
"""
TDistLocationScale

function score!(score_til::Matrix{T}, y::T, ::Type{TDistLocationScale}, param::Matrix{T}, t::Int) where T
    score_til[t, 1] = ((param[t, 3] + 1) * (y - param[t, 1])) / ((y - param[t, 1])^2 + param[t, 2] * param[t, 3])
    score_til[t, 2] = -(param[t, 3] * (param[t, 2] - (y - param[t, 1])^2)) / (2 * param[t, 2] * (param[t, 3] * param[t, 2] +
                        (y - param[t, 1])^2))
    score_til[t, 3] = ((((y - param[t, 1])^2)*(param[t, 3] + 1)/(param[t, 3] * (y - param[t, 1])^2 + param[t, 2] * param[t, 3]^2)) -
                      1/param[t, 3] -
                      log(((y - param[t, 1])^2)/(param[t, 2] * param[t, 3]) + 1) -
                      digamma(param[t, 3] / 2) + digamma((param[t, 3] + 1) / 2)) / 2
    return
end

function fisher_information!(aux::AuxiliaryLinAlg{T}, ::Type{TDistLocationScale}, param::Matrix{T}, t::Int) where T
    aux.fisher[1, 1] = (param[t, 3] + 1.0)/(param[t, 2] * (param[t, 3] + 3.0))
    aux.fisher[1, 2] = zero(T)
    aux.fisher[2, 1] = zero(T)
    aux.fisher[1, 3] = zero(T)
    aux.fisher[3, 1] = zero(T)
    aux.fisher[2, 2] = param[t, 3] / ((2 * param[t, 2]^2) * (param[t, 3] + 3.0))
    aux.fisher[2, 3] = -1.0 / (param[t, 2] * (param[t, 3] + 3.0)*(param[t, 3] + 1.0))
    aux.fisher[3, 2] = -1.0 / (param[t, 2] * (param[t, 3] + 3.0)*(param[t, 3] + 1.0))
    aux.fisher[3, 3] = 0.5 * (0.5 * trigamma(0.5 * param[t, 3]) - 0.5 * trigamma( 0.5 * (param[t, 3] + 1.0) ) -
                       (param[t, 3] + 5.0) / (param[t, 3] * (param[t, 3] + 3.0) * (param[t, 3] + 1.0)))
    return
end

function log_likelihood(::Type{TDistLocationScale}, y::Vector{T}, param::Matrix{T}, n::Int) where T
    loglik = zero(T)
    for t in 1:n
        loglik -= 0.5 * log(param[t, 2]*param[t, 3]) + logbeta(0.5, param[t, 3]/2) + ((param[t, 3] + 1)/2) *
                  log(1 + (1/(param[t, 3]*param[t, 2])) * (y[t] - param[t, 1])^2)
    end
    return -loglik
end

# Links
function link!(param_tilde::Matrix{T}, ::Type{TDistLocationScale}, param::Matrix{T}, t::Int) where T
    param_tilde[t, 1] = link(IdentityLink, param[t, 1])
    param_tilde[t, 2] = link(LogLink, param[t, 2], zero(T))
    param_tilde[t, 3] = link(LogLink, param[t, 3], zero(T))
    return
end
function unlink!(param::Matrix{T}, ::Type{TDistLocationScale}, param_tilde::Matrix{T}, t::Int) where T
    param[t, 1] = unlink(IdentityLink, param_tilde[t, 1])
    param[t, 2] = unlink(LogLink, param_tilde[t, 2], zero(T))
    param[t, 3] = unlink(LogLink, param_tilde[t, 3], zero(T))
    return
end
function jacobian_link!(aux::AuxiliaryLinAlg{T}, ::Type{TDistLocationScale}, param::Matrix{T}, t::Int) where T
    aux.jac[1] = jacobian_link(IdentityLink, param[t, 1])
    aux.jac[2] = jacobian_link(LogLink, param[t, 2], zero(T))
    aux.jac[3] = jacobian_link(LogLink, param[t, 3], zero(T))
    return
end

# utils
function update_dist(::Type{TDistLocationScale}, param::Matrix{T}, t::Int) where T
    tdist = TDist(param[t, 3])
    return LocationScale(param[t, 1], sqrt(param[t, 2]), tdist)
end

function params_sdm(d::TDistLocationScale)
    return (d.μ, d.σ^2, Distributions.params(d.ρ)...)
end

function num_params(::Type{TDistLocationScale})
    return 3
end


using SixelGraphics

# inspiration and some code from
# http://mathemartician.blogspot.nl/2012/07/julia-set-in-julia.html

function julia(z, maxiter::Int64)
    c=-0.75+0.11im
    for n = 1:maxiter
        if abs(z) > 2
            return n-1
        end
        z = z^2 + c
    end
    return maxiter
end

w = 384
h = 288

s = Screen(w,h)

clear(s.b,63)

# for every pixel
for y=1:h, x=1:w
    # translate numbers [1:w, 1:h] -> -2.5:2.5 + -1:1 im
    c = complex((x-w/2)/(h/2.5), (y-h/2)/(h/2))
    # call the julia function and use the distance as the color
    putpixel(s.b, div(julia(c,255),16), x-1, y-1)
end

sixels(s)


using DataFrames,Distributions,CairoMakie,StatsFuns

df1 = DataFrame( θ = 0.375:0.001:0.525)
α,β = 438,544
posterior = Beta(α,β)
# the pdf function 'knows' when it is supplied a Beta distribution
df1.p =pdf.(posterior,df1.θ);
df2 =DataFrame(θ = LinRange(quantile(posterior,[0.025,0.975])..., 100))# compute the posterior density
df2.p =pdf.(posterior,df2.θ);
fig, ax, post = lines(df1.θ, df1.p, color = :black, linewidth = 4,
axis = (title = "Posterior distribution given uniform prior",
titlesize = 24,))
# Add a layer of colorized 95% posterior interval
i95 = band!(df2.θ,fill(0.0,length(df2.p)), df2.p, color = (:blue,0.2))
# Add the proportion of girl babies in general population
pop = vlines!(ax, [0.488], color = :black, linestyle=:dash)
# Decorate the plot a little
Legend(fig[2,1], [post, i95, pop], ["Poseterior density = Beta($(α),$(β))",
"95% posterior interval", "population average"],
orientation = :horizontal,tellwidth = false, tellheight = true)
fig