### A Pluto.jl notebook ###
# v0.15.1

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

# ╔═╡ e5020bc7-a803-47c5-b38e-8879937c8a5f
using PlutoUI, CUDA, BenchmarkTools

# ╔═╡ 04b9a546-dc75-440e-bc74-9bbcc945f5f4
begin
	using Statistics
	matriz_datos = rand(Float32, 2500, 2500)
	matriz_datos_gpu = CuArray(matriz_datos)
end

# ╔═╡ f3a89703-2903-4874-9a57-5f84e401c970
using Tullio, CUDAKernels, KernelAbstractions

# ╔═╡ bbbb53fe-f599-11eb-02d3-915af1c0597f
md"""
# Uso de CUDA en Julia

El uso de CUDA en Julia se presenta mediante tres formas:
- Paralelismo implícito por drop-ins.
- Paralelismo explícito al escribir kernels.
- Interfaces con las bibliotecas y el API por medio de wrappers.
"""

# ╔═╡ 98c9d6f2-fa7a-4bb9-acee-02c4022ea03a
PlutoUI.TableOfContents(aside=true, title="CUDA")

# ╔═╡ c0a94327-49b0-426e-bb40-ad7224e80b43
with_terminal(CUDA.versioninfo)

# ╔═╡ d0c4915a-85e4-49fb-b931-c4be3ff0d142
md"""
# Programación con arreglos

Los arreglos del tipo `CuArray` están diseñados para manejar una gran parte de las operaciones típicas de arreglos en el GPU. De esta forma, el código generado en Julia puro puede ser utilizado tan solo redefiniendo los arreglos como CuArrays.

Detrás de las escenas, CUDA.jl se encarga de lo siguiente al generar un arreglo:
- Reservar la memoria en el GPU.
- Si es necesario, realizar la transferencia de memoria CPU ➡ GPU.
- Lanzar kernels para realizar la operación solicitada.
- Si el usuario lo solicita, realizar la transferencia de memoria CPU ⬅ GPU.

Por lo general, se recomienda solo utilizar operaciones vectorizadas. Algunas podrían requerir cargar el arreglo a CPU e iterarlo, y esto es lento (las denominadas operaciones escalares). CUDA.jl nos mostrará una advertencia al intentarlo para evitar malas prácticas, pero debido a que Pluto utiliza este tipo de operaciones para mostrar su salida, le diremos a CUDA.jl que permita operaciones escalares.
"""

# ╔═╡ 15050436-2551-4136-b30d-3d3346493c7b
CUDA.allowscalar(true)

# ╔═╡ 07d2caf0-f987-474d-a462-f452a42be99f
a = CuArray([1, 2, 3, 4])

# ╔═╡ ca37cd69-eb08-4002-80dd-857ef77c3689
md"""Realicemos la primera operación en el arreglo, demostrando el uso del broadcasting."""

# ╔═╡ 456b2857-c2e4-496c-8f36-c8b58fa4c497
a .+= 1

# ╔═╡ 25389b61-4e40-4c11-9d28-45d168e39214
md""" De esta forma, las operaciones diseñadas en paralelo se pueden utilizar como si se tratara de un arreglo normal de Julia, por ejemplo:"""

# ╔═╡ c5ffc0b4-d5d7-48fd-a518-7c2c98c45f82
map(x -> x^2 + 3x - 2, a)

# ╔═╡ c4b9b898-d1c2-4890-8804-c7258f0fdc94
md"""Nótese que no existe modificación alguna al arreglo original:"""

# ╔═╡ 5917412e-80a6-4d9d-b687-611b99e96dac
a

# ╔═╡ a3a61808-148b-465c-8f6f-2483cd6421bd
md"""
Ya que CUDA.jl y Julia siguen un paradigma funcional, en el cual es preferible que las estructuras creadas nunca se modifiquen.

Volviendo al tema del paralelismo, podemos comparar una implementación vectorizada con una secuencial:

**Implementación del algoritmo basado en map:**
"""

# ╔═╡ 61a34fe6-83a5-4508-80af-c449fdbba7d1
@bind tipo_algoritmo Radio(["vect" => "Map (CUDA)", "iter" => "Iterativo (CUDA)", "iter_j" => "Map (Julia)"], default="vect")

# ╔═╡ f5820a28-20ef-4fb3-a466-515ebedd0676
arr1 = collect(1:1_000_000)

# ╔═╡ 04b92cdc-f0d5-43a4-b0b6-31e044ee841b
arr1_gpu = CuArray(arr1)

# ╔═╡ 9407c96c-03bc-4066-a9ef-14ae7e5037e1
if tipo_algoritmo == "vect"
	#El uso de CUDA.@sync está recomendado para medir el tiempo real
	@benchmark CUDA.@sync map(x -> x^2 + 3x - 2, arr1_gpu)
elseif tipo_algoritmo == "iter"
	@benchmark for i in 1:1_000_000
			x = arr1_gpu[i]
			x^2 + 3x - 2
		end
else
	@benchmark map(x -> x^2 + 3x - 2, arr1)
end

# ╔═╡ 47501814-869e-4c2b-9918-323600dc3d63
md"""
Otro ejemplo:

**Implementación del algoritmo (cov):**
"""

# ╔═╡ 2680dbf0-4af4-4d18-beaf-8961203fc2cd
@bind tipo_algoritmo_2 Radio(["vect" => "CUDA", "iter" => "Julia"], default="vect")

# ╔═╡ 8c315cd4-946b-4c6e-9152-173c18efd7fa
if tipo_algoritmo_2 == "vect"
	@benchmark CUDA.@sync cov(matriz_datos_gpu)
else
	@benchmark cov(matriz_datos)
end

# ╔═╡ 61f7b6f6-698b-4f00-84c2-90b288a3c3a7
md"""
Como se puede notar, la implementación de CUDA con **operaciones fáciles de paralelizar** está en otro nivel de magnitud en cuanto a tiempo de ejecución cuando se le compara con una implementación en Julia corriendo en el CPU.
"""

# ╔═╡ 1f74f846-6d8d-48f5-9dc1-391d59cbc164
md"""
## Creación de arreglos

Como se mencionó antes, los arreglos están diseñados para ser reemplazos transparentes al usuario. Un arreglo puede iniciar su vida de la siguiente forma:
"""

# ╔═╡ 04987ac0-cf32-42c6-884f-5d19c4aafec9
arreglo_vacio = CuArray{Float32}(undef, 10)

# ╔═╡ 436a254a-124f-4686-8da9-9fcc560459eb
md"""
Como se puede notar, al estar indefinido no se realiza limpieza de la memoria que existe en la GPU, por lo que se encuentra lleno de datos basura. No obstante, es ligeramente más rápido reservarlo así que inicializándolo.
"""

# ╔═╡ a5a101bc-c5cc-445d-8cbd-181cf6d789d8
arreglo_inicializado = CuArray{Float32}([x^2 + 3x - 2 for x in 1:10])

# ╔═╡ bea5f1e1-e5ba-4f8e-848d-27313e0ae4aa
md"""
Se pueden copiar los contenidos entre arreglos:
"""

# ╔═╡ 4ed549ba-c6b9-4d75-9d2d-5fd2a35081cd
copyto!(arreglo_vacio, arreglo_inicializado)

# ╔═╡ ae014fc7-2c1a-43b9-8dac-faf4ebe14769
arreglo_vacio

# ╔═╡ 89329729-3f50-4e82-bc68-b59bcbc91835
md"""
Llenar un arreglo de un valor arbitrario:
"""

# ╔═╡ 42419f93-26a7-4c28-80d3-db7547dee130
fill!(arreglo_vacio, 7.0)

# ╔═╡ 9074daea-7744-4dd0-bba3-9c727ff93b7f
arreglo_vacio

# ╔═╡ 1b47496e-9f3f-4b4b-b19d-e7e5825869df
md"""
Y generar arreglos con las dimensiones de otro:
"""

# ╔═╡ 9337632e-7bbb-4346-acc8-d0c0eddfd1df
similar(arreglo_vacio)

# ╔═╡ e9982200-fa77-4f41-a201-493f5772fa2a
md"""
Por limitaciones de como funciona la arquitectura CUDA, algunas operaciones que no toman un tipo de arreglo en Julia no pueden ser traducidas directamente a una versión para GPU. Para estos, CUDA.jl implementa una versión propia:
"""

# ╔═╡ 4df35ae7-da44-437f-a762-03ad9b66a15a
md"""
Reemplazando a la función ones:
"""

# ╔═╡ 215d87c7-6ce8-434b-b1cc-6979b85ee2d2
arreglo_unos = CUDA.ones(length(arreglo_vacio))

# ╔═╡ 3234e223-7e03-4258-b7b1-f3fbaf7d4c49
md"""
Reemplazando a la función rand:
"""

# ╔═╡ ecee0116-16e2-41c7-971b-36ecde535260
arreglo_aleatorio = CUDA.rand(length(arreglo_vacio))

# ╔═╡ 4eab8c06-be7c-40d2-94e2-e23471b2a3f5
begin
	using Plots
	plot(arreglo_aleatorio)
	plot!(log.(accumulate(+, arreglo_unos)))
	title!("Un plot con CuArrays")
end

# ╔═╡ 90544984-f317-4e43-9f65-76a358f50e21
md"""
Reemplazando a un arreglo en uso diario:
"""

# ╔═╡ 9c8829c2-7601-48d3-aa58-071926870691
md"""
# Uso de bibliotecas de Nvidia

CUDA.jl provee acceso a las bibliotecas por medio de funciones al estilo de Julia en muchos casos. Para los restantes, da la posibilidad de llamar a la biblioteca en CUDA C directamente.
"""

# ╔═╡ 21257da0-d16b-48b3-8c66-4309cdaa6c7d
cuda_matriz_R = CuArray{Float32}(undef, (10,10))

# ╔═╡ d541be46-5341-4994-be70-cc2d95104d65
begin
	using AbstractFFTs
	fft(cuda_matriz_R)
end

# ╔═╡ fabbde47-37fa-4c5e-9dae-28a1bbc95d4a
cuda_matriz = CUDA.ones(10,10)

# ╔═╡ 32081f1d-7a00-490b-94db-a070e17a0b8a
begin
	using LinearAlgebra
	mul!(cuda_matriz_R, CUDA.rand(10,10), cuda_matriz)
end

# ╔═╡ 804c9f33-4dc0-4763-9137-125d14469bd0
begin
	using Random
	rand!(cuda_matriz)
end

# ╔═╡ 86d6ea0e-a64b-445c-86ae-1479a12519a5
md"""
**CUBLAS:**
"""

# ╔═╡ cd43ba66-5613-4501-b42c-4092fe4ce9e7
md"""
**CURAND:**
"""

# ╔═╡ 2f0edcd1-18b3-4b69-a2a1-278bfd343e3e
md"""
**CUSOLVER:**
"""

# ╔═╡ c9bbb09b-c515-4dcd-8a63-36ff47f5ed4f
qr(cuda_matriz_R)

# ╔═╡ e0261365-1cf5-415d-a556-5160fe949a7e
md"""
**CUFFT:**
"""

# ╔═╡ 985f163d-8404-4d26-ae5c-cb4422095eeb
md"""
Un ejemplo utilizando las APIs de C propias a las bibliotecas:
"""

# ╔═╡ 77c8d1b6-a90a-43c1-8110-20023450313c
begin
	out = Ref{Cint}()
	#Encontrar el índice con el valor mínimo en un arreglo dado
	CUBLAS.cublasIsamin_v2(CUBLAS.handle(), length(arreglo_inicializado),
		arreglo_inicializado, stride(arreglo_inicializado, 1), out)
	out[]
end

# ╔═╡ b4063f3e-2031-43ea-b0ad-a398c7f765d1
findmin(arreglo_inicializado)

# ╔═╡ f3457168-6da7-4daa-957e-bdf187d1aa29
md"""
Poder utilizar este tipo de llamadas a las bibliotecas permite tener la funcionalidad completa de las mismas.

# Abstracciones de alto nivel

La ventaja de CUDA.jl frente a implementaciones como las de Python (por ejemplo CuPy) es la flexibilidad de las abstracciones de Julia en conjunto a los CuArray:

"""

# ╔═╡ 0022e1db-f5e4-47f1-996e-ff12a3022e5a
begin
	arreglo_prueba = CUDA.ones(10)
	broadcast(arreglo_prueba) do x
		x+= 1
	end
end

# ╔═╡ 3c88f1f0-753c-4182-8458-ac97a7e375f3
#Equivalente a:
arreglo_prueba .+ 1

# ╔═╡ 877ca78d-d8c9-4a42-9332-a5c4979ac822
md"""
Al componer varias de estas abstracciones, es posible generar código muy poderoso y conciso:
"""

# ╔═╡ b2195cfe-043a-430f-9ee0-d6ac7f745d71
map(arreglo_prueba) do x
	x + log(x) - exp(x)^π
end

# ╔═╡ 1791ff51-f819-420a-bf03-820fc48313d7
reduce(+, arreglo_prueba)

# ╔═╡ a2843d0e-b520-4ab8-81ac-cf8b1ecf1d40
accumulate(+, arreglo_prueba)

# ╔═╡ bad0aa36-6cc1-49ff-ab3e-9db894055c64
md"""
Algo a tomar en cuenta es que CUDA.jl **no es un compilador de tensores**. Esto quiere decir que implementa las abstracciones directamente, y genera kernels escalares. Cuando se tienen operaciones múltiples, esstas no se suelen fusionar entre ellas.

Entre otras cosas, las operaciones genéricas que operan en otros arreglos no resultan directamente en un kernel para GPU:
"""

# ╔═╡ 80f29cd8-0c01-444c-8ea3-8f6655b99d05
broadcast(eachcol(cuda_matriz)) do x
	sum(x)
end

# ╔═╡ 68919e5f-18da-4bbf-ad66-2720dd556f32
md"""
Realizar la suma dentro del map resulta en lanzar un kernel distinto por columna, lo cuál es lento y no utiliza la GPU de forma eficiente.
"""

# ╔═╡ ce737b93-8409-4f2a-895c-e6dee7dcf570
@benchmark CUDA.@sync broadcast(eachcol($cuda_matriz)) do x
	sum(x)
end

# ╔═╡ f8719bda-5f64-4b2a-a5c3-93343116c0ff
md"""
Se debería entonces optar por una sola invocación:
"""

# ╔═╡ 8c7f0eb9-8db3-4e7d-a27e-a38d08f530b8
sum(cuda_matriz, dims=1)

# ╔═╡ fcadec48-a6cd-4471-95b2-4da9327eac37
@benchmark CUDA.@sync sum(cuda_matriz, dims=1)

# ╔═╡ 8e8ac21a-7310-445e-b515-d6e718f2877e
md"""
En otros casos, la operación no se compilará ya que pocas operaciones de arreglos están soportadas explícitamente en CUDA.jl:
"""

# ╔═╡ 78fab451-9329-41ea-b1df-97c57cb6c0a4
broadcast(cuda_matriz, eachcol(cuda_matriz)) do x, Y
	x + sum(Y)
end

# ╔═╡ 3370dabd-30ba-4233-a60d-0c563ffd82fe
md"""
Por suerte, existen otros paquetes desarrollados con soporte para CUDA.jl. Por ejemplo Tullio.jl, que permite generar expresiones complejas (utilizando notación de Einstein) de tal forma que se compilen como un solo kernel.
"""

# ╔═╡ 4855b2d0-3084-437a-9bff-d56bf5e4eef9
@tullio cuda_matriz_2[i,j] := cuda_matriz[i, j] + cuda_matriz[k, j]

# ╔═╡ 40ca06d9-6845-4af0-b5ed-4e3827156914
@benchmark @tullio cuda_matriz_3[i,j] := cuda_matriz[i, j] + cuda_matriz[k, j]

# ╔═╡ 3804d33f-4908-4ff8-841a-d80a9f53e6ee
md"""
Para casos en los que se requiere más control, se pueden escribir kernels utilizando Julia.

# Programación de kernels

Muchas operaciones con paralelísmo implícito se pueden ejecutar en paralelo dentro del GPU con la ayuda de CUDA.jl. En otros casos, la persona que programa puede crear un kernel para realizar la operación (y adquiere la responsabilidad que esto conlleva).

En un alto nivel, esto es sencillo. Se debe de escribir una función escalar, lanzándola en paralelo utilizando el macro @cuda y el argumento threads.
"""

# ╔═╡ 7bc4bffa-3faf-42f7-acc4-4e8c28181271
function vadd(v_c, v_a, v_b)
	i = threadIdx().x
	v_c[i] = v_a[i] + v_b[i]
	return
end

# ╔═╡ e6d23eb4-c10d-489f-8390-f344dd4b81db
let
	v_a = CuArray(1:10)
	v_b = CuArray(2:2:20)
	v_c = similar(v_a)

	@cuda threads = length(v_a) vadd(v_c, v_a, v_b)

	v_c
end

# ╔═╡ 1d2f4bc8-64bd-489e-ac68-f9d5c21f11b2
md"""
La programación de kernels rápidamente se vuelve más complicada debido a que:
- Se deben respetar las limitaciones del hardware.
- Se deben usar los recursos de hardware eficientemente.
- No todas las operaciones se pueden implementar fácilmente en un kernel escalar.
"""

# ╔═╡ 74fdc782-cba9-417a-b515-9ad8c38ad2b3
md"""
## Limitaciones de hardware

Por ejemplo, se puede exceder el número de hilos por bloque:
"""

# ╔═╡ 0b9eeec1-fbb6-4408-9b42-79f20017fb74
let
	v_a = CuArray(1:100_000)
	v_b = CuArray(2:2:200_000)
	v_c = similar(v_a)

	@cuda threads = length(v_a) vadd(v_c, v_a, v_b)
end

# ╔═╡ e40ffe89-816e-4e5e-996c-4fb2ba05d244
md"""
Podemos visualizar el número máximo de hilos por bloque:
"""

# ╔═╡ 40270d30-6259-4641-afee-d4b74571995c
CUDA.attribute(device(), CUDA.DEVICE_ATTRIBUTE_MAX_THREADS_PER_BLOCK)

# ╔═╡ 13e45f04-6626-46e8-9960-f63b0db6b01b
md"""
Podemos resolver este primer problema al utilizar más de un bloque. Para esto habrá que adaptar el cálculo de índice para que este sea válido.
"""

# ╔═╡ 4d83c990-81f2-4b94-8a8a-95905df93594
function vadd_bloques(c, a, b)
	i = threadIdx().x + (blockIdx().x - 1) * blockDim().x
	if i <= length(a)
		c[i] = a[i] + b[i]
	end
	return
end

# ╔═╡ 08a44e3e-daac-4a07-a1d8-a1bec02c0349
let
	v_a = CuArray(1:100_000)
	v_b = CuArray(2:2:200_000)
	v_c = similar(v_a)

	@cuda threads=1024 blocks=cld(length(v_a), 1024) vadd_bloques(v_c, v_a, v_b)

	v_c
end

# ╔═╡ 5e51ce22-ec1d-47c6-ab5c-eec254b0ec72
md"""
Una forma más sencilla de calcular los recursos necesarios: **Occupancy API**
"""

# ╔═╡ 6cc5f45d-a149-4603-b5f5-2b895e37d134
let
	v_a = CuArray(1:100_000)
	v_b = CuArray(2:2:200_000)
	v_c = similar(v_a)

	kernel = @cuda launch = false vadd_bloques(v_c, v_a, v_b)

	config = CUDA.launch_configuration(kernel.fun)
end

# ╔═╡ ee8da3f3-ca78-433c-8510-40c0ea8958ba
let
	v_a = CuArray(1:100_000)
	v_b = CuArray(2:2:200_000)
	v_c = similar(v_a)

	t = min(length(v_a), 1024)
	b = cld(length(v_a), t)

	@cuda threads=t blocks=b vadd_bloques(v_c, v_a, v_b)

	(t, b, v_c)
end

# ╔═╡ 5b8de2c3-23e3-4f80-b95a-d01b3397bb2b
md"""
En este caso, el API de CUDA calculó que lo más óptimo para nuestra operación sobre esas entradas sería lanzar 40 bloques de 1024 hilos cada uno. Realmente esto no sería suficiente para nuestra operación dado el kernel actual, pero el kernel se puede modificar para que funcione de esa manera.
"""

# ╔═╡ d9689381-94a4-45a6-812a-8ca6e3027c99
md"""
## Operaciones atómicas

CUDA permite operaciones atómicas en sus kernels, pero estas siguien siendo bastante costosas.
"""

# ╔═╡ 307933d4-af70-4fcc-a0b9-7818487e88e6
function reduce_atomic(op, a, b)
	i = threadIdx().x + (blockIdx().x - 1) * blockDim().x
	@atomic b[] = op(b[], a[i])
	return
end

# ╔═╡ 8df9cc05-c51b-4675-9441-12f9b992222e
let
	arreglo = CuArray(1:16)
	salida = CuArray([0])

	@cuda threads=length(arreglo) reduce_atomic(+, arreglo, salida)

	salida
end

# ╔═╡ 56c80ef5-d974-4a7c-9b2c-adf9bf3282b5
sum(1:16)

# ╔═╡ b6847097-a4aa-476a-b8d8-d354168553a5
md"""
Una ventaja clara de Julia se puede notar aquí. El código Julia toma una función como argumento y lo puede aplicar debido a la compilación del código a kernel.
"""

# ╔═╡ 87089c7e-db1d-42d2-bdec-57e3c42801b7
md"""
## Algunos ejemplos más avanzados

Este ejemplo muestra el cálculo de la distancia de Haversine (es decir, entre dos puntos de una esfera).

"""

# ╔═╡ bdd4e398-0049-4b1c-8114-bb7ab786d854
function haversine_gpu(lat1::Float32, lon1::Float32, lat2::Float32, lon2::Float32, radius::Float32)
    c1 = CUDA.cospi(lat1 / 180.0f0)
    c2 = CUDA.cospi(lat2 / 180.0f0)
    dlat = lat2 - lat1
    dlon = lon2 - lon1
    d1 = CUDA.sinpi(dlat / 360.0f0)
    d2 = CUDA.sinpi(dlon / 360.0f0)
    t = d2 * d2 * c1 * c2
    a = d1 * d1 + t
    c = 2.0f0 * CUDA.asin(CUDA.min(1.0f0, CUDA.sqrt(a)))
    return radius * c
end

# ╔═╡ 834a723b-52cf-4cc8-a815-9175be6370b6
md"""
En la siguiente función, se muestran algunas de las características más avanzadas de CUDA.jl:
- Usar índices bidimensionales para bloques e hilos.
- Utilizar memoria compartida dentro del bloque en vez de solo global.
- Funciones en dispositivo (llamadas dentro de la GPU para ejecutar en la GPU).
- Sincronización de hilos dentro de un kernel.

Esta se encarga de calcular las distancias de todos los puntos a todos los puntos.
"""

# ╔═╡ 0407b18f-216f-47d2-8102-ad58326f9baa
# pairwise distance calculation kernel
function pairwise_dist_kernel(lat::CuDeviceVector{Float32}, lon::CuDeviceVector{Float32},
                              rowresult::CuDeviceMatrix{Float32}, n)
    i = (blockIdx().x-1) * blockDim().x + threadIdx().x
    j = (blockIdx().y-1) * blockDim().y + threadIdx().y

    if i <= n && j <= n
        # store to shared memory
        shmem = @cuDynamicSharedMem(Float32, 2*blockDim().x + 2*blockDim().y)
        if threadIdx().y == 1
            shmem[threadIdx().x] = lat[i]
            shmem[blockDim().x + threadIdx().x] = lon[i]
        end
        if threadIdx().x == 1
            shmem[2*blockDim().x + threadIdx().y] = lat[j]
            shmem[2*blockDim().x + blockDim().y + threadIdx().y] = lon[j]
        end
        sync_threads()

        # load from shared memory
        lat_i = shmem[threadIdx().x]
        lon_i = shmem[blockDim().x + threadIdx().x]
        lat_j = shmem[2*blockDim().x + threadIdx().y]
        lon_j = shmem[2*blockDim().x + blockDim().y + threadIdx().y]

        @inbounds rowresult[i, j] = haversine_gpu(lat_i, lon_i, lat_j, lon_j, 6372.8f0)
    end

    return
end

# ╔═╡ f9ec57fc-f7d1-4231-a873-0371250d48fd
let
	n = 10_000

	lat_gpu = CUDA.rand(n) .* 45
    lon_gpu = CUDA.rand(n) .* -120

    #Matriz resultante
    rowresult_gpu = CUDA.zeros(n, n)

    # calculate a 2D block size from the suggested 1D configuration
    # NOTE: we want our launch configuration to be as square as possible,
    #       because that minimizes shared memory usage
    function get_threads(threads)
        threads_x = floor(Int, sqrt(threads))
        threads_y = threads ÷ threads_x
        return (threads_x, threads_y)
    end

    # calculate the amount of dynamic shared memory for a 2D block size
    get_shmem(threads) = 2 * sum(threads) * sizeof(Float32)

    kernel = @cuda launch=false pairwise_dist_kernel(lat_gpu, lon_gpu, rowresult_gpu, n)
    config = launch_configuration(kernel.fun, shmem=threads->get_shmem(get_threads(threads)))

    # convert to 2D block size and figure out appropriate grid size
    t = get_threads(config.threads)
    b = ceil.(Int, n ./ t)
    s = get_shmem(t)

    kernel(lat_gpu, lon_gpu, rowresult_gpu, n; threads=t, blocks=b, shmem=s)

	rowresult_gpu
end

# ╔═╡ 2597a206-5396-4c33-98d9-7ea6b2dbbeca
md"""
Como se puede notar, CUDA.jl tiene un grado de flexibilidad alto. Por un lado, puede servir como un reemplazo para operaciones complejas tan solo reemplazando el tipo de arreglo que se utiliza. Por el otro lado, puede utilizar optimizaciones de bajo nivel de CUDA, enriqueciéndolas con el poder del lenguaje puro.
"""

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
AbstractFFTs = "621f4979-c628-5d54-868e-fcf4e3e8185c"
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba"
CUDAKernels = "72cfdca4-0801-4ab0-bf6a-d52aa10adc57"
KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
Tullio = "bc48ee85-29a4-5162-ae0b-a64e1601d4bc"

[compat]
AbstractFFTs = "~1.0.1"
BenchmarkTools = "~1.1.1"
CUDA = "~3.3.4"
CUDAKernels = "~0.3.0"
KernelAbstractions = "~0.7.0"
Plots = "~1.20.0"
PlutoUI = "~0.7.9"
Tullio = "~0.3.2"
"""

# ╔═╡ 00000000-0000-0000-0000-000000000002
PLUTO_MANIFEST_TOML_CONTENTS = """
# This file is machine-generated - editing it directly is not advised

[[AbstractFFTs]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "485ee0867925449198280d4af84bdb46a2a404d0"
uuid = "621f4979-c628-5d54-868e-fcf4e3e8185c"
version = "1.0.1"

[[Adapt]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "84918055d15b3114ede17ac6a7182f68870c16f7"
uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
version = "3.3.1"

[[ArgTools]]
uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"

[[Artifacts]]
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"

[[BFloat16s]]
deps = ["LinearAlgebra", "Test"]
git-tree-sha1 = "4af69e205efc343068dc8722b8dfec1ade89254a"
uuid = "ab4f0b2a-ad5b-11e8-123f-65d77653426b"
version = "0.1.0"

[[Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"

[[BenchmarkTools]]
deps = ["JSON", "Logging", "Printf", "Statistics", "UUIDs"]
git-tree-sha1 = "c31ebabde28d102b602bada60ce8922c266d205b"
uuid = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
version = "1.1.1"

[[Bzip2_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "c3598e525718abcc440f69cc6d5f60dda0a1b61e"
uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0"
version = "1.0.6+5"

[[CEnum]]
git-tree-sha1 = "215a9aa4a1f23fbd05b92769fdd62559488d70e9"
uuid = "fa961155-64e5-5f13-b03f-caf6b980ea82"
version = "0.4.1"

[[CUDA]]
deps = ["AbstractFFTs", "Adapt", "BFloat16s", "CEnum", "CompilerSupportLibraries_jll", "DataStructures", "ExprTools", "GPUArrays", "GPUCompiler", "LLVM", "LazyArtifacts", "Libdl", "LinearAlgebra", "Logging", "Printf", "Random", "Random123", "RandomNumbers", "Reexport", "Requires", "SparseArrays", "SpecialFunctions", "TimerOutputs"]
git-tree-sha1 = "5e696e37e51b01ae07bd9f700afe6cbd55250bce"
uuid = "052768ef-5323-5732-b1bb-66c8b64840ba"
version = "3.3.4"

[[CUDAKernels]]
deps = ["Adapt", "CUDA", "Cassette", "KernelAbstractions", "SpecialFunctions", "StaticArrays"]
git-tree-sha1 = "81f76297b63c67723b1d60f5e7e002ae3393974b"
uuid = "72cfdca4-0801-4ab0-bf6a-d52aa10adc57"
version = "0.3.0"

[[Cairo_jll]]
deps = ["Artifacts", "Bzip2_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "JLLWrappers", "LZO_jll", "Libdl", "Pixman_jll", "Pkg", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Zlib_jll", "libpng_jll"]
git-tree-sha1 = "e2f47f6d8337369411569fd45ae5753ca10394c6"
uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a"
version = "1.16.0+6"

[[Cassette]]
git-tree-sha1 = "087e76b8d48c014112ba890892c33be42ad10504"
uuid = "7057c7e9-c182-5462-911a-8362d720325c"
version = "0.3.7"

[[ChainRulesCore]]
deps = ["Compat", "LinearAlgebra", "SparseArrays"]
git-tree-sha1 = "bdc0937269321858ab2a4f288486cb258b9a0af7"
uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
version = "1.3.0"

[[ColorSchemes]]
deps = ["ColorTypes", "Colors", "FixedPointNumbers", "Random", "StaticArrays"]
git-tree-sha1 = "ed268efe58512df8c7e224d2e170afd76dd6a417"
uuid = "35d6a980-a343-548e-a6ea-1d62b119f2f4"
version = "3.13.0"

[[ColorTypes]]
deps = ["FixedPointNumbers", "Random"]
git-tree-sha1 = "024fe24d83e4a5bf5fc80501a314ce0d1aa35597"
uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
version = "0.11.0"

[[Colors]]
deps = ["ColorTypes", "FixedPointNumbers", "Reexport"]
git-tree-sha1 = "417b0ed7b8b838aa6ca0a87aadf1bb9eb111ce40"
uuid = "5ae59095-9a9b-59fe-a467-6f913c188581"
version = "0.12.8"

[[Compat]]
deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"]
git-tree-sha1 = "344f143fa0ec67e47917848795ab19c6a455f32c"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "3.32.0"

[[CompilerSupportLibraries_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"

[[Contour]]
deps = ["StaticArrays"]
git-tree-sha1 = "9f02045d934dc030edad45944ea80dbd1f0ebea7"
uuid = "d38c429a-6771-53c6-b99e-75d170b6e991"
version = "0.5.7"

[[DataAPI]]
git-tree-sha1 = "ee400abb2298bd13bfc3df1c412ed228061a2385"
uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
version = "1.7.0"

[[DataStructures]]
deps = ["Compat", "InteractiveUtils", "OrderedCollections"]
git-tree-sha1 = "4437b64df1e0adccc3e5d1adbc3ac741095e4677"
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
version = "0.18.9"

[[DataValueInterfaces]]
git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464"
version = "1.0.0"

[[Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"

[[DelimitedFiles]]
deps = ["Mmap"]
uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"

[[DiffRules]]
deps = ["NaNMath", "Random", "SpecialFunctions"]
git-tree-sha1 = "85d2d9e2524da988bffaf2a381864e20d2dae08d"
uuid = "b552c78f-8df3-52c6-915a-8e097449b14b"
version = "1.2.1"

[[Distributed]]
deps = ["Random", "Serialization", "Sockets"]
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"

[[DocStringExtensions]]
deps = ["LibGit2"]
git-tree-sha1 = "a32185f5428d3986f47c2ab78b1f216d5e6cc96f"
uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
version = "0.8.5"

[[Downloads]]
deps = ["ArgTools", "LibCURL", "NetworkOptions"]
uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"

[[EarCut_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "92d8f9f208637e8d2d28c664051a00569c01493d"
uuid = "5ae413db-bbd1-5e63-b57d-d24a61df00f5"
version = "2.1.5+1"

[[Expat_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "b3bfd02e98aedfa5cf885665493c5598c350cd2f"
uuid = "2e619515-83b5-522b-bb60-26c02a35a201"
version = "2.2.10+0"

[[ExprTools]]
git-tree-sha1 = "b7e3d17636b348f005f11040025ae8c6f645fe92"
uuid = "e2ba6199-217a-4e67-a87a-7c52f15ade04"
version = "0.1.6"

[[FFMPEG]]
deps = ["FFMPEG_jll"]
git-tree-sha1 = "b57e3acbe22f8484b4b5ff66a7499717fe1a9cc8"
uuid = "c87230d0-a227-11e9-1b43-d7ebe4e7570a"
version = "0.4.1"

[[FFMPEG_jll]]
deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "JLLWrappers", "LAME_jll", "LibVPX_jll", "Libdl", "Ogg_jll", "OpenSSL_jll", "Opus_jll", "Pkg", "Zlib_jll", "libass_jll", "libfdk_aac_jll", "libvorbis_jll", "x264_jll", "x265_jll"]
git-tree-sha1 = "3cc57ad0a213808473eafef4845a74766242e05f"
uuid = "b22a6f82-2f65-5046-a5b2-351ab43fb4e5"
version = "4.3.1+4"

[[FixedPointNumbers]]
deps = ["Statistics"]
git-tree-sha1 = "335bfdceacc84c5cdf16aadc768aa5ddfc5383cc"
uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
version = "0.8.4"

[[Fontconfig_jll]]
deps = ["Artifacts", "Bzip2_jll", "Expat_jll", "FreeType2_jll", "JLLWrappers", "Libdl", "Libuuid_jll", "Pkg", "Zlib_jll"]
git-tree-sha1 = "35895cf184ceaab11fd778b4590144034a167a2f"
uuid = "a3f928ae-7b40-5064-980b-68af3947d34b"
version = "2.13.1+14"

[[Formatting]]
deps = ["Printf"]
git-tree-sha1 = "8339d61043228fdd3eb658d86c926cb282ae72a8"
uuid = "59287772-0a20-5a39-b81b-1366585eb4c0"
version = "0.4.2"

[[FreeType2_jll]]
deps = ["Artifacts", "Bzip2_jll", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"]
git-tree-sha1 = "cbd58c9deb1d304f5a245a0b7eb841a2560cfec6"
uuid = "d7e528f0-a631-5988-bf34-fe36492bcfd7"
version = "2.10.1+5"

[[FriBidi_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "aa31987c2ba8704e23c6c8ba8a4f769d5d7e4f91"
uuid = "559328eb-81f9-559d-9380-de523a88c83c"
version = "1.0.10+0"

[[GLFW_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Libglvnd_jll", "Pkg", "Xorg_libXcursor_jll", "Xorg_libXi_jll", "Xorg_libXinerama_jll", "Xorg_libXrandr_jll"]
git-tree-sha1 = "dba1e8614e98949abfa60480b13653813d8f0157"
uuid = "0656b61e-2033-5cc2-a64a-77c0f6c09b89"
version = "3.3.5+0"

[[GPUArrays]]
deps = ["AbstractFFTs", "Adapt", "LinearAlgebra", "Printf", "Random", "Serialization", "Statistics"]
git-tree-sha1 = "ececbf05f8904c92814bdbd0aafd5540b0bf2e9a"
uuid = "0c68f7d7-f131-5f86-a1c3-88cf8149b2d7"
version = "7.0.1"

[[GPUCompiler]]
deps = ["ExprTools", "InteractiveUtils", "LLVM", "Libdl", "Logging", "TimerOutputs", "UUIDs"]
git-tree-sha1 = "f26f15d9c353f7091065390ea826df9e03917e58"
uuid = "61eb1bfa-7361-4325-ad38-22787b887f55"
version = "0.12.8"

[[GR]]
deps = ["Base64", "DelimitedFiles", "GR_jll", "HTTP", "JSON", "Libdl", "LinearAlgebra", "Pkg", "Printf", "Random", "Serialization", "Sockets", "Test", "UUIDs"]
git-tree-sha1 = "182da592436e287758ded5be6e32c406de3a2e47"
uuid = "28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71"
version = "0.58.1"

[[GR_jll]]
deps = ["Artifacts", "Bzip2_jll", "Cairo_jll", "FFMPEG_jll", "Fontconfig_jll", "GLFW_jll", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll", "Pixman_jll", "Pkg", "Qt5Base_jll", "Zlib_jll", "libpng_jll"]
git-tree-sha1 = "d59e8320c2747553788e4fc42231489cc602fa50"
uuid = "d2c73de3-f751-5644-a686-071e5b155ba9"
version = "0.58.1+0"

[[GeometryBasics]]
deps = ["EarCut_jll", "IterTools", "LinearAlgebra", "StaticArrays", "StructArrays", "Tables"]
git-tree-sha1 = "58bcdf5ebc057b085e58d95c138725628dd7453c"
uuid = "5c1252a2-5f33-56bf-86c9-59e7332b4326"
version = "0.4.1"

[[Gettext_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Libiconv_jll", "Pkg", "XML2_jll"]
git-tree-sha1 = "9b02998aba7bf074d14de89f9d37ca24a1a0b046"
uuid = "78b55507-aeef-58d4-861c-77aaff3498b1"
version = "0.21.0+0"

[[Glib_jll]]
deps = ["Artifacts", "Gettext_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Libiconv_jll", "Libmount_jll", "PCRE_jll", "Pkg", "Zlib_jll"]
git-tree-sha1 = "7bf67e9a481712b3dbe9cb3dac852dc4b1162e02"
uuid = "7746bdde-850d-59dc-9ae8-88ece973131d"
version = "2.68.3+0"

[[Grisu]]
git-tree-sha1 = "53bb909d1151e57e2484c3d1b53e19552b887fb2"
uuid = "42e2da0e-8278-4e71-bc24-59509adca0fe"
version = "1.0.2"

[[HTTP]]
deps = ["Base64", "Dates", "IniFile", "Logging", "MbedTLS", "NetworkOptions", "Sockets", "URIs"]
git-tree-sha1 = "44e3b40da000eab4ccb1aecdc4801c040026aeb5"
uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3"
version = "0.9.13"

[[IniFile]]
deps = ["Test"]
git-tree-sha1 = "098e4d2c533924c921f9f9847274f2ad89e018b8"
uuid = "83e8ac13-25f8-5344-8a64-a9f2b223428f"
version = "0.5.0"

[[InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"

[[IterTools]]
git-tree-sha1 = "05110a2ab1fc5f932622ffea2a003221f4782c18"
uuid = "c8e1da08-722c-5040-9ed9-7db0dc04731e"
version = "1.3.0"

[[IteratorInterfaceExtensions]]
git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856"
uuid = "82899510-4779-5014-852e-03e436cf321d"
version = "1.0.0"

[[JLLWrappers]]
deps = ["Preferences"]
git-tree-sha1 = "642a199af8b68253517b80bd3bfd17eb4e84df6e"
uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
version = "1.3.0"

[[JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "81690084b6198a2e1da36fcfda16eeca9f9f24e4"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.1"

[[JpegTurbo_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "d735490ac75c5cb9f1b00d8b5509c11984dc6943"
uuid = "aacddb02-875f-59d6-b918-886e6ef4fbf8"
version = "2.1.0+0"

[[KernelAbstractions]]
deps = ["Adapt", "Cassette", "InteractiveUtils", "MacroTools", "SpecialFunctions", "StaticArrays", "UUIDs"]
git-tree-sha1 = "5e6c70389c1b1e40adb81664ca8cea6ce8127afc"
uuid = "63c18a36-062a-441e-b654-da1e3ab1ce7c"
version = "0.7.0"

[[LAME_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "f6250b16881adf048549549fba48b1161acdac8c"
uuid = "c1c5ebd0-6772-5130-a774-d5fcae4a789d"
version = "3.100.1+0"

[[LLVM]]
deps = ["CEnum", "LLVMExtra_jll", "Libdl", "Printf", "Unicode"]
git-tree-sha1 = "d6041ad706cf458b2c9f3e501152488a26451e9c"
uuid = "929cbde3-209d-540e-8aea-75f648917ca0"
version = "4.2.0"

[[LLVMExtra_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "a9b1130c4728b0e462a1c28772954650039eb847"
uuid = "dad2f222-ce93-54a1-a47d-0025e8a3acab"
version = "0.0.7+0"

[[LZO_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "e5b909bcf985c5e2605737d2ce278ed791b89be6"
uuid = "dd4b983a-f0e5-5f8d-a1b7-129d4a5fb1ac"
version = "2.10.1+0"

[[LaTeXStrings]]
git-tree-sha1 = "c7f1c695e06c01b95a67f0cd1d34994f3e7db104"
uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
version = "1.2.1"

[[Latexify]]
deps = ["Formatting", "InteractiveUtils", "LaTeXStrings", "MacroTools", "Markdown", "Printf", "Requires"]
git-tree-sha1 = "a4b12a1bd2ebade87891ab7e36fdbce582301a92"
uuid = "23fbe1c1-3f47-55db-b15f-69d7ec21a316"
version = "0.15.6"

[[LazyArtifacts]]
deps = ["Artifacts", "Pkg"]
uuid = "4af54fe1-eca0-43a8-85a7-787d91b784e3"

[[LibCURL]]
deps = ["LibCURL_jll", "MozillaCACerts_jll"]
uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"

[[LibCURL_jll]]
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"

[[LibGit2]]
deps = ["Base64", "NetworkOptions", "Printf", "SHA"]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"

[[LibSSH2_jll]]
deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"

[[LibVPX_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "12ee7e23fa4d18361e7c2cde8f8337d4c3101bc7"
uuid = "dd192d2f-8180-539f-9fb4-cc70b1dcf69a"
version = "1.10.0+0"

[[Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"

[[Libffi_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "761a393aeccd6aa92ec3515e428c26bf99575b3b"
uuid = "e9f186c6-92d2-5b65-8a66-fee21dc1b490"
version = "3.2.2+0"

[[Libgcrypt_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgpg_error_jll", "Pkg"]
git-tree-sha1 = "64613c82a59c120435c067c2b809fc61cf5166ae"
uuid = "d4300ac3-e22c-5743-9152-c294e39db1e4"
version = "1.8.7+0"

[[Libglvnd_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll", "Xorg_libXext_jll"]
git-tree-sha1 = "7739f837d6447403596a75d19ed01fd08d6f56bf"
uuid = "7e76a0d4-f3c7-5321-8279-8d96eeed0f29"
version = "1.3.0+3"

[[Libgpg_error_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "c333716e46366857753e273ce6a69ee0945a6db9"
uuid = "7add5ba3-2f88-524e-9cd5-f83b8a55f7b8"
version = "1.42.0+0"

[[Libiconv_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "42b62845d70a619f063a7da093d995ec8e15e778"
uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531"
version = "1.16.1+1"

[[Libmount_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "9c30530bf0effd46e15e0fdcf2b8636e78cbbd73"
uuid = "4b2f31a3-9ecc-558c-b454-b3730dcb73e9"
version = "2.35.0+0"

[[Libtiff_jll]]
deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Pkg", "Zlib_jll", "Zstd_jll"]
git-tree-sha1 = "340e257aada13f95f98ee352d316c3bed37c8ab9"
uuid = "89763e89-9b03-5906-acba-b20f662cd828"
version = "4.3.0+0"

[[Libuuid_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "7f3efec06033682db852f8b3bc3c1d2b0a0ab066"
uuid = "38a345b3-de98-5d2b-a5d3-14cd9215e700"
version = "2.36.0+0"

[[LinearAlgebra]]
deps = ["Libdl"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

[[LogExpFunctions]]
deps = ["DocStringExtensions", "LinearAlgebra"]
git-tree-sha1 = "7bd5f6565d80b6bf753738d2bc40a5dfea072070"
uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
version = "0.2.5"

[[Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"

[[MacroTools]]
deps = ["Markdown", "Random"]
git-tree-sha1 = "0fb723cd8c45858c22169b2e42269e53271a6df7"
uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
version = "0.5.7"

[[Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"

[[MbedTLS]]
deps = ["Dates", "MbedTLS_jll", "Random", "Sockets"]
git-tree-sha1 = "1c38e51c3d08ef2278062ebceade0e46cefc96fe"
uuid = "739be429-bea8-5141-9913-cc70e7f3736d"
version = "1.0.3"

[[MbedTLS_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"

[[Measures]]
git-tree-sha1 = "e498ddeee6f9fdb4551ce855a46f54dbd900245f"
uuid = "442fdcdd-2543-5da2-b0f3-8c86c306513e"
version = "0.3.1"

[[Missings]]
deps = ["DataAPI"]
git-tree-sha1 = "4ea90bd5d3985ae1f9a908bd4500ae88921c5ce7"
uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28"
version = "1.0.0"

[[Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"

[[MozillaCACerts_jll]]
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"

[[NaNMath]]
git-tree-sha1 = "bfe47e760d60b82b66b61d2d44128b62e3a369fb"
uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
version = "0.3.5"

[[NetworkOptions]]
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"

[[Ogg_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "7937eda4681660b4d6aeeecc2f7e1c81c8ee4e2f"
uuid = "e7412a2a-1a6e-54c0-be00-318e2571c051"
version = "1.3.5+0"

[[OpenSSL_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "15003dcb7d8db3c6c857fda14891a539a8f2705a"
uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95"
version = "1.1.10+0"

[[OpenSpecFun_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "13652491f6856acfd2db29360e1bbcd4565d04f1"
uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e"
version = "0.5.5+0"

[[Opus_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "51a08fb14ec28da2ec7a927c4337e4332c2a4720"
uuid = "91d4177d-7536-5919-b921-800302f37372"
version = "1.3.2+0"

[[OrderedCollections]]
git-tree-sha1 = "85f8e6578bf1f9ee0d11e7bb1b1456435479d47c"
uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
version = "1.4.1"

[[PCRE_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "b2a7af664e098055a7529ad1a900ded962bca488"
uuid = "2f80f16e-611a-54ab-bc61-aa92de5b98fc"
version = "8.44.0+0"

[[Parsers]]
deps = ["Dates"]
git-tree-sha1 = "bfd7d8c7fd87f04543810d9cbd3995972236ba1b"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "1.1.2"

[[Pixman_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "b4f5d02549a10e20780a24fce72bea96b6329e29"
uuid = "30392449-352a-5448-841d-b1acce4e97dc"
version = "0.40.1+0"

[[Pkg]]
deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"

[[PlotThemes]]
deps = ["PlotUtils", "Requires", "Statistics"]
git-tree-sha1 = "a3a964ce9dc7898193536002a6dd892b1b5a6f1d"
uuid = "ccf2f8ad-2431-5c83-bf29-c5338b663b6a"
version = "2.0.1"

[[PlotUtils]]
deps = ["ColorSchemes", "Colors", "Dates", "Printf", "Random", "Reexport", "Statistics"]
git-tree-sha1 = "501c20a63a34ac1d015d5304da0e645f42d91c9f"
uuid = "995b91a9-d308-5afd-9ec6-746e21dbc043"
version = "1.0.11"

[[Plots]]
deps = ["Base64", "Contour", "Dates", "FFMPEG", "FixedPointNumbers", "GR", "GeometryBasics", "JSON", "Latexify", "LinearAlgebra", "Measures", "NaNMath", "PlotThemes", "PlotUtils", "Printf", "REPL", "Random", "RecipesBase", "RecipesPipeline", "Reexport", "Requires", "Scratch", "Showoff", "SparseArrays", "Statistics", "StatsBase", "UUIDs"]
git-tree-sha1 = "e39bea10478c6aff5495ab522517fae5134b40e3"
uuid = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
version = "1.20.0"

[[PlutoUI]]
deps = ["Base64", "Dates", "InteractiveUtils", "JSON", "Logging", "Markdown", "Random", "Reexport", "Suppressor"]
git-tree-sha1 = "44e225d5837e2a2345e69a1d1e01ac2443ff9fcb"
uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
version = "0.7.9"

[[Preferences]]
deps = ["TOML"]
git-tree-sha1 = "00cfd92944ca9c760982747e9a1d0d5d86ab1e5a"
uuid = "21216c6a-2e73-6563-6e65-726566657250"
version = "1.2.2"

[[Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"

[[Qt5Base_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "Fontconfig_jll", "Glib_jll", "JLLWrappers", "Libdl", "Libglvnd_jll", "OpenSSL_jll", "Pkg", "Xorg_libXext_jll", "Xorg_libxcb_jll", "Xorg_xcb_util_image_jll", "Xorg_xcb_util_keysyms_jll", "Xorg_xcb_util_renderutil_jll", "Xorg_xcb_util_wm_jll", "Zlib_jll", "xkbcommon_jll"]
git-tree-sha1 = "ad368663a5e20dbb8d6dc2fddeefe4dae0781ae8"
uuid = "ea2cea3b-5b76-57ae-a6ef-0a8af62496e1"
version = "5.15.3+0"

[[REPL]]
deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"

[[Random]]
deps = ["Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[[Random123]]
deps = ["Libdl", "Random", "RandomNumbers"]
git-tree-sha1 = "0e8b146557ad1c6deb1367655e052276690e71a3"
uuid = "74087812-796a-5b5d-8853-05524746bad3"
version = "1.4.2"

[[RandomNumbers]]
deps = ["Random", "Requires"]
git-tree-sha1 = "a752043df7488ca8bcbe05fa82c831b5e2c67211"
uuid = "e6cf234a-135c-5ec9-84dd-332b85af5143"
version = "1.5.2"

[[RecipesBase]]
git-tree-sha1 = "b3fb709f3c97bfc6e948be68beeecb55a0b340ae"
uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
version = "1.1.1"

[[RecipesPipeline]]
deps = ["Dates", "NaNMath", "PlotUtils", "RecipesBase"]
git-tree-sha1 = "2a7a2469ed5d94a98dea0e85c46fa653d76be0cd"
uuid = "01d81517-befc-4cb6-b9ec-a95719d0359c"
version = "0.3.4"

[[Reexport]]
git-tree-sha1 = "5f6c21241f0f655da3952fd60aa18477cf96c220"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.1.0"

[[Requires]]
deps = ["UUIDs"]
git-tree-sha1 = "4036a3bd08ac7e968e27c203d45f5fff15020621"
uuid = "ae029012-a4dd-5104-9daa-d747884805df"
version = "1.1.3"

[[SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"

[[Scratch]]
deps = ["Dates"]
git-tree-sha1 = "0b4b7f1393cff97c33891da2a0bf69c6ed241fda"
uuid = "6c6a2e73-6563-6170-7368-637461726353"
version = "1.1.0"

[[Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"

[[SharedArrays]]
deps = ["Distributed", "Mmap", "Random", "Serialization"]
uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383"

[[Showoff]]
deps = ["Dates", "Grisu"]
git-tree-sha1 = "91eddf657aca81df9ae6ceb20b959ae5653ad1de"
uuid = "992d4aef-0814-514b-bc4d-f2e9a6c4116f"
version = "1.0.3"

[[Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"

[[SortingAlgorithms]]
deps = ["DataStructures"]
git-tree-sha1 = "b3363d7460f7d098ca0912c69b082f75625d7508"
uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c"
version = "1.0.1"

[[SparseArrays]]
deps = ["LinearAlgebra", "Random"]
uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"

[[SpecialFunctions]]
deps = ["ChainRulesCore", "LogExpFunctions", "OpenSpecFun_jll"]
git-tree-sha1 = "508822dca004bf62e210609148511ad03ce8f1d8"
uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
version = "1.6.0"

[[StaticArrays]]
deps = ["LinearAlgebra", "Random", "Statistics"]
git-tree-sha1 = "3fedeffc02e47d6e3eb479150c8e5cd8f15a77a0"
uuid = "90137ffa-7385-5640-81b9-e52037218182"
version = "1.2.10"

[[Statistics]]
deps = ["LinearAlgebra", "SparseArrays"]
uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[[StatsAPI]]
git-tree-sha1 = "1958272568dc176a1d881acb797beb909c785510"
uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0"
version = "1.0.0"

[[StatsBase]]
deps = ["DataAPI", "DataStructures", "LinearAlgebra", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"]
git-tree-sha1 = "fed1ec1e65749c4d96fc20dd13bea72b55457e62"
uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
version = "0.33.9"

[[StructArrays]]
deps = ["Adapt", "DataAPI", "StaticArrays", "Tables"]
git-tree-sha1 = "000e168f5cc9aded17b6999a560b7c11dda69095"
uuid = "09ab397b-f2b6-538f-b94a-2f83cf4a842a"
version = "0.6.0"

[[Suppressor]]
git-tree-sha1 = "a819d77f31f83e5792a76081eee1ea6342ab8787"
uuid = "fd094767-a336-5f1f-9728-57cf17d0bbfb"
version = "0.2.0"

[[TOML]]
deps = ["Dates"]
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"

[[TableTraits]]
deps = ["IteratorInterfaceExtensions"]
git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39"
uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c"
version = "1.0.1"

[[Tables]]
deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "TableTraits", "Test"]
git-tree-sha1 = "d0c690d37c73aeb5ca063056283fde5585a41710"
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
version = "1.5.0"

[[Tar]]
deps = ["ArgTools", "SHA"]
uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"

[[Test]]
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[[TimerOutputs]]
deps = ["ExprTools", "Printf"]
git-tree-sha1 = "209a8326c4f955e2442c07b56029e88bb48299c7"
uuid = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f"
version = "0.5.12"

[[Tullio]]
deps = ["ChainRulesCore", "DiffRules", "LinearAlgebra", "Requires"]
git-tree-sha1 = "0288b7a395fc412952baf756fac94e4f28bfec65"
uuid = "bc48ee85-29a4-5162-ae0b-a64e1601d4bc"
version = "0.3.2"

[[URIs]]
git-tree-sha1 = "97bbe755a53fe859669cd907f2d96aee8d2c1355"
uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
version = "1.3.0"

[[UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[[Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"

[[Wayland_jll]]
deps = ["Artifacts", "Expat_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Pkg", "XML2_jll"]
git-tree-sha1 = "3e61f0b86f90dacb0bc0e73a0c5a83f6a8636e23"
uuid = "a2964d1f-97da-50d4-b82a-358c7fce9d89"
version = "1.19.0+0"

[[Wayland_protocols_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Wayland_jll"]
git-tree-sha1 = "2839f1c1296940218e35df0bbb220f2a79686670"
uuid = "2381bf8a-dfd0-557d-9999-79630e7b1b91"
version = "1.18.0+4"

[[XML2_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Pkg", "Zlib_jll"]
git-tree-sha1 = "1acf5bdf07aa0907e0a37d3718bb88d4b687b74a"
uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a"
version = "2.9.12+0"

[[XSLT_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgcrypt_jll", "Libgpg_error_jll", "Libiconv_jll", "Pkg", "XML2_jll", "Zlib_jll"]
git-tree-sha1 = "91844873c4085240b95e795f692c4cec4d805f8a"
uuid = "aed1982a-8fda-507f-9586-7b0439959a61"
version = "1.1.34+0"

[[Xorg_libX11_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libxcb_jll", "Xorg_xtrans_jll"]
git-tree-sha1 = "5be649d550f3f4b95308bf0183b82e2582876527"
uuid = "4f6342f7-b3d2-589e-9d20-edeb45f2b2bc"
version = "1.6.9+4"

[[Xorg_libXau_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "4e490d5c960c314f33885790ed410ff3a94ce67e"
uuid = "0c0b7dd1-d40b-584c-a123-a41640f87eec"
version = "1.0.9+4"

[[Xorg_libXcursor_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXfixes_jll", "Xorg_libXrender_jll"]
git-tree-sha1 = "12e0eb3bc634fa2080c1c37fccf56f7c22989afd"
uuid = "935fb764-8cf2-53bf-bb30-45bb1f8bf724"
version = "1.2.0+4"

[[Xorg_libXdmcp_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "4fe47bd2247248125c428978740e18a681372dd4"
uuid = "a3789734-cfe1-5b06-b2d0-1dd0d9d62d05"
version = "1.1.3+4"

[[Xorg_libXext_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"]
git-tree-sha1 = "b7c0aa8c376b31e4852b360222848637f481f8c3"
uuid = "1082639a-0dae-5f34-9b06-72781eeb8cb3"
version = "1.3.4+4"

[[Xorg_libXfixes_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"]
git-tree-sha1 = "0e0dc7431e7a0587559f9294aeec269471c991a4"
uuid = "d091e8ba-531a-589c-9de9-94069b037ed8"
version = "5.0.3+4"

[[Xorg_libXi_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXext_jll", "Xorg_libXfixes_jll"]
git-tree-sha1 = "89b52bc2160aadc84d707093930ef0bffa641246"
uuid = "a51aa0fd-4e3c-5386-b890-e753decda492"
version = "1.7.10+4"

[[Xorg_libXinerama_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXext_jll"]
git-tree-sha1 = "26be8b1c342929259317d8b9f7b53bf2bb73b123"
uuid = "d1454406-59df-5ea1-beac-c340f2130bc3"
version = "1.1.4+4"

[[Xorg_libXrandr_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXext_jll", "Xorg_libXrender_jll"]
git-tree-sha1 = "34cea83cb726fb58f325887bf0612c6b3fb17631"
uuid = "ec84b674-ba8e-5d96-8ba1-2a689ba10484"
version = "1.5.2+4"

[[Xorg_libXrender_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"]
git-tree-sha1 = "19560f30fd49f4d4efbe7002a1037f8c43d43b96"
uuid = "ea2f1a96-1ddc-540d-b46f-429655e07cfa"
version = "0.9.10+4"

[[Xorg_libpthread_stubs_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "6783737e45d3c59a4a4c4091f5f88cdcf0908cbb"
uuid = "14d82f49-176c-5ed1-bb49-ad3f5cbd8c74"
version = "0.1.0+3"

[[Xorg_libxcb_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "XSLT_jll", "Xorg_libXau_jll", "Xorg_libXdmcp_jll", "Xorg_libpthread_stubs_jll"]
git-tree-sha1 = "daf17f441228e7a3833846cd048892861cff16d6"
uuid = "c7cfdc94-dc32-55de-ac96-5a1b8d977c5b"
version = "1.13.0+3"

[[Xorg_libxkbfile_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"]
git-tree-sha1 = "926af861744212db0eb001d9e40b5d16292080b2"
uuid = "cc61e674-0454-545c-8b26-ed2c68acab7a"
version = "1.1.0+4"

[[Xorg_xcb_util_image_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xcb_util_jll"]
git-tree-sha1 = "0fab0a40349ba1cba2c1da699243396ff8e94b97"
uuid = "12413925-8142-5f55-bb0e-6d7ca50bb09b"
version = "0.4.0+1"

[[Xorg_xcb_util_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libxcb_jll"]
git-tree-sha1 = "e7fd7b2881fa2eaa72717420894d3938177862d1"
uuid = "2def613f-5ad1-5310-b15b-b15d46f528f5"
version = "0.4.0+1"

[[Xorg_xcb_util_keysyms_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xcb_util_jll"]
git-tree-sha1 = "d1151e2c45a544f32441a567d1690e701ec89b00"
uuid = "975044d2-76e6-5fbe-bf08-97ce7c6574c7"
version = "0.4.0+1"

[[Xorg_xcb_util_renderutil_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xcb_util_jll"]
git-tree-sha1 = "dfd7a8f38d4613b6a575253b3174dd991ca6183e"
uuid = "0d47668e-0667-5a69-a72c-f761630bfb7e"
version = "0.3.9+1"

[[Xorg_xcb_util_wm_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xcb_util_jll"]
git-tree-sha1 = "e78d10aab01a4a154142c5006ed44fd9e8e31b67"
uuid = "c22f9ab0-d5fe-5066-847c-f4bb1cd4e361"
version = "0.4.1+1"

[[Xorg_xkbcomp_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libxkbfile_jll"]
git-tree-sha1 = "4bcbf660f6c2e714f87e960a171b119d06ee163b"
uuid = "35661453-b289-5fab-8a00-3d9160c6a3a4"
version = "1.4.2+4"

[[Xorg_xkeyboard_config_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xkbcomp_jll"]
git-tree-sha1 = "5c8424f8a67c3f2209646d4425f3d415fee5931d"
uuid = "33bec58e-1273-512f-9401-5d533626f822"
version = "2.27.0+4"

[[Xorg_xtrans_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "79c31e7844f6ecf779705fbc12146eb190b7d845"
uuid = "c5fb5394-a638-5e4d-96e5-b29de1b5cf10"
version = "1.4.0+3"

[[Zlib_jll]]
deps = ["Libdl"]
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"

[[Zstd_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "cc4bf3fdde8b7e3e9fa0351bdeedba1cf3b7f6e6"
uuid = "3161d3a3-bdf6-5164-811a-617609db77b4"
version = "1.5.0+0"

[[libass_jll]]
deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"]
git-tree-sha1 = "acc685bcf777b2202a904cdcb49ad34c2fa1880c"
uuid = "0ac62f75-1d6f-5e53-bd7c-93b484bb37c0"
version = "0.14.0+4"

[[libfdk_aac_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "7a5780a0d9c6864184b3a2eeeb833a0c871f00ab"
uuid = "f638f0a6-7fb0-5443-88ba-1cc74229b280"
version = "0.1.6+4"

[[libpng_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"]
git-tree-sha1 = "94d180a6d2b5e55e447e2d27a29ed04fe79eb30c"
uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f"
version = "1.6.38+0"

[[libvorbis_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Ogg_jll", "Pkg"]
git-tree-sha1 = "c45f4e40e7aafe9d086379e5578947ec8b95a8fb"
uuid = "f27f6e37-5d2b-51aa-960f-b287f2bc3b7a"
version = "1.3.7+0"

[[nghttp2_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"

[[p7zip_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"

[[x264_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "d713c1ce4deac133e3334ee12f4adff07f81778f"
uuid = "1270edf5-f2f9-52d2-97e9-ab00b5d0237a"
version = "2020.7.14+2"

[[x265_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "487da2f8f2f0c8ee0e83f39d13037d6bbf0a45ab"
uuid = "dfaa095f-4041-5dcd-9319-2fabd8486b76"
version = "3.0.0+3"

[[xkbcommon_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Wayland_jll", "Wayland_protocols_jll", "Xorg_libxcb_jll", "Xorg_xkeyboard_config_jll"]
git-tree-sha1 = "ece2350174195bb31de1a63bea3a41ae1aa593b6"
uuid = "d8fb68d0-12a3-5cfd-a85a-d49703b185fd"
version = "0.9.1+5"
"""

# ╔═╡ Cell order:
# ╟─bbbb53fe-f599-11eb-02d3-915af1c0597f
# ╠═e5020bc7-a803-47c5-b38e-8879937c8a5f
# ╠═98c9d6f2-fa7a-4bb9-acee-02c4022ea03a
# ╠═c0a94327-49b0-426e-bb40-ad7224e80b43
# ╟─d0c4915a-85e4-49fb-b931-c4be3ff0d142
# ╠═15050436-2551-4136-b30d-3d3346493c7b
# ╠═07d2caf0-f987-474d-a462-f452a42be99f
# ╟─ca37cd69-eb08-4002-80dd-857ef77c3689
# ╠═456b2857-c2e4-496c-8f36-c8b58fa4c497
# ╟─25389b61-4e40-4c11-9d28-45d168e39214
# ╠═c5ffc0b4-d5d7-48fd-a518-7c2c98c45f82
# ╟─c4b9b898-d1c2-4890-8804-c7258f0fdc94
# ╠═5917412e-80a6-4d9d-b687-611b99e96dac
# ╟─a3a61808-148b-465c-8f6f-2483cd6421bd
# ╟─61a34fe6-83a5-4508-80af-c449fdbba7d1
# ╠═f5820a28-20ef-4fb3-a466-515ebedd0676
# ╠═04b92cdc-f0d5-43a4-b0b6-31e044ee841b
# ╠═9407c96c-03bc-4066-a9ef-14ae7e5037e1
# ╟─47501814-869e-4c2b-9918-323600dc3d63
# ╟─2680dbf0-4af4-4d18-beaf-8961203fc2cd
# ╠═04b9a546-dc75-440e-bc74-9bbcc945f5f4
# ╠═8c315cd4-946b-4c6e-9152-173c18efd7fa
# ╟─61f7b6f6-698b-4f00-84c2-90b288a3c3a7
# ╟─1f74f846-6d8d-48f5-9dc1-391d59cbc164
# ╠═04987ac0-cf32-42c6-884f-5d19c4aafec9
# ╟─436a254a-124f-4686-8da9-9fcc560459eb
# ╠═a5a101bc-c5cc-445d-8cbd-181cf6d789d8
# ╟─bea5f1e1-e5ba-4f8e-848d-27313e0ae4aa
# ╠═4ed549ba-c6b9-4d75-9d2d-5fd2a35081cd
# ╠═ae014fc7-2c1a-43b9-8dac-faf4ebe14769
# ╟─89329729-3f50-4e82-bc68-b59bcbc91835
# ╠═42419f93-26a7-4c28-80d3-db7547dee130
# ╠═9074daea-7744-4dd0-bba3-9c727ff93b7f
# ╟─1b47496e-9f3f-4b4b-b19d-e7e5825869df
# ╠═9337632e-7bbb-4346-acc8-d0c0eddfd1df
# ╟─e9982200-fa77-4f41-a201-493f5772fa2a
# ╟─4df35ae7-da44-437f-a762-03ad9b66a15a
# ╠═215d87c7-6ce8-434b-b1cc-6979b85ee2d2
# ╟─3234e223-7e03-4258-b7b1-f3fbaf7d4c49
# ╠═ecee0116-16e2-41c7-971b-36ecde535260
# ╟─90544984-f317-4e43-9f65-76a358f50e21
# ╠═4eab8c06-be7c-40d2-94e2-e23471b2a3f5
# ╟─9c8829c2-7601-48d3-aa58-071926870691
# ╠═21257da0-d16b-48b3-8c66-4309cdaa6c7d
# ╠═fabbde47-37fa-4c5e-9dae-28a1bbc95d4a
# ╟─86d6ea0e-a64b-445c-86ae-1479a12519a5
# ╠═32081f1d-7a00-490b-94db-a070e17a0b8a
# ╟─cd43ba66-5613-4501-b42c-4092fe4ce9e7
# ╠═804c9f33-4dc0-4763-9137-125d14469bd0
# ╟─2f0edcd1-18b3-4b69-a2a1-278bfd343e3e
# ╠═c9bbb09b-c515-4dcd-8a63-36ff47f5ed4f
# ╟─e0261365-1cf5-415d-a556-5160fe949a7e
# ╠═d541be46-5341-4994-be70-cc2d95104d65
# ╟─985f163d-8404-4d26-ae5c-cb4422095eeb
# ╠═77c8d1b6-a90a-43c1-8110-20023450313c
# ╠═b4063f3e-2031-43ea-b0ad-a398c7f765d1
# ╟─f3457168-6da7-4daa-957e-bdf187d1aa29
# ╠═0022e1db-f5e4-47f1-996e-ff12a3022e5a
# ╠═3c88f1f0-753c-4182-8458-ac97a7e375f3
# ╟─877ca78d-d8c9-4a42-9332-a5c4979ac822
# ╠═b2195cfe-043a-430f-9ee0-d6ac7f745d71
# ╠═1791ff51-f819-420a-bf03-820fc48313d7
# ╠═a2843d0e-b520-4ab8-81ac-cf8b1ecf1d40
# ╟─bad0aa36-6cc1-49ff-ab3e-9db894055c64
# ╠═80f29cd8-0c01-444c-8ea3-8f6655b99d05
# ╟─68919e5f-18da-4bbf-ad66-2720dd556f32
# ╠═ce737b93-8409-4f2a-895c-e6dee7dcf570
# ╟─f8719bda-5f64-4b2a-a5c3-93343116c0ff
# ╠═8c7f0eb9-8db3-4e7d-a27e-a38d08f530b8
# ╠═fcadec48-a6cd-4471-95b2-4da9327eac37
# ╟─8e8ac21a-7310-445e-b515-d6e718f2877e
# ╠═78fab451-9329-41ea-b1df-97c57cb6c0a4
# ╟─3370dabd-30ba-4233-a60d-0c563ffd82fe
# ╠═f3a89703-2903-4874-9a57-5f84e401c970
# ╠═4855b2d0-3084-437a-9bff-d56bf5e4eef9
# ╠═40ca06d9-6845-4af0-b5ed-4e3827156914
# ╟─3804d33f-4908-4ff8-841a-d80a9f53e6ee
# ╠═7bc4bffa-3faf-42f7-acc4-4e8c28181271
# ╠═e6d23eb4-c10d-489f-8390-f344dd4b81db
# ╟─1d2f4bc8-64bd-489e-ac68-f9d5c21f11b2
# ╟─74fdc782-cba9-417a-b515-9ad8c38ad2b3
# ╠═0b9eeec1-fbb6-4408-9b42-79f20017fb74
# ╟─e40ffe89-816e-4e5e-996c-4fb2ba05d244
# ╠═40270d30-6259-4641-afee-d4b74571995c
# ╟─13e45f04-6626-46e8-9960-f63b0db6b01b
# ╠═4d83c990-81f2-4b94-8a8a-95905df93594
# ╠═08a44e3e-daac-4a07-a1d8-a1bec02c0349
# ╟─5e51ce22-ec1d-47c6-ab5c-eec254b0ec72
# ╠═6cc5f45d-a149-4603-b5f5-2b895e37d134
# ╠═ee8da3f3-ca78-433c-8510-40c0ea8958ba
# ╟─5b8de2c3-23e3-4f80-b95a-d01b3397bb2b
# ╟─d9689381-94a4-45a6-812a-8ca6e3027c99
# ╠═307933d4-af70-4fcc-a0b9-7818487e88e6
# ╠═8df9cc05-c51b-4675-9441-12f9b992222e
# ╠═56c80ef5-d974-4a7c-9b2c-adf9bf3282b5
# ╟─b6847097-a4aa-476a-b8d8-d354168553a5
# ╟─87089c7e-db1d-42d2-bdec-57e3c42801b7
# ╠═bdd4e398-0049-4b1c-8114-bb7ab786d854
# ╟─834a723b-52cf-4cc8-a815-9175be6370b6
# ╠═0407b18f-216f-47d2-8102-ad58326f9baa
# ╠═f9ec57fc-f7d1-4231-a873-0371250d48fd
# ╟─2597a206-5396-4c33-98d9-7ea6b2dbbeca
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002

Curso Julia/Github - Material/src/CUDA/CUDA.jl


include("linear_example.jl")
include("second_order_example.jl")


# # Rate Constants, T-dependence
# Here, we will just briefly summarize how temperature affects enzymatic rate constants and how much uncertainty there actually is in literature (in terms of how to best define them and how plastic some of the variables are)

## Add usual tools we use:
#using Revise
using BenchmarkTools

# Use PyPlot to plot figures
using PyPlot

#----------------------------------------------------------------------------

## load Photosynthesis module:
using Land.Photosynthesis
#----------------------------------------------------------------------------

## Specify Field Type
const FT = Float32
#----------------------------------------------------------------------------

# ## T-dependence of V$_{c,max}$
# In literature, there are different implementations of the temperature dependence of enzymatic reaction rates. Common among all of them is a typical Arrhenius formulation with Activation energy $E_a$, which leads to a temperature dependence of a quantity $V$ given the standard value defined at 25°C as $T_{ref}$.
#
# $$V(T)= V(T_{ref})\, \underbrace{\exp\left(\frac{E_a}{RT_{ref}}-\frac{E_a}{RT}\right)}_{\text{Activation}}$$
#
# Other formulations add de-activation of proteins due to denaturalization at higher temperatures:
# $$V(T)= V(T_{ref})\, \exp\left(\frac{E_a}{RT_{ref}}-\frac{E_a}{RT}\right) \underbrace{\frac{1+\exp\left((\Delta ST_{ref}-H_d)/RT_{ref}\right)}{1+\exp\left((\Delta ST-H_d)/RT\right)}}_{\text{de-activation}} $$
# which includes an entropy term $\Delta S$ and the energy for deactivation $H_d$.
#
# To illustrate the differences, we show two different implementations, one only using the activation part based on Bernacchi et al 2001[^1] and one with the typical CLM5 implementation.
#
# [^1]: Bernacchi, C.J., Singsaas, E.L., Pimentel, C., Portis Jr, A.R. and Long, S.P., 2001. Improved temperature response functions for models of Rubisco‐limited photosynthesis. Plant, Cell & Environment, 24(2), pp.253-259.
#

## This looks a bit more tedious here than it needs to but in reality

vcmax_Bernacchi = Float32[]
vcmax_CLM = Float32[]

## Define T-range
Tleaf = collect(FT,260.0:1.0:315.0)

## Run through temperatures and save Vcmax values:
td_vc_bernacchi = Photosynthesis.VcmaxTDBernacchi(FT)
td_vc_clm       = Photosynthesis.VcmaxTDCLM(FT)
for T in Tleaf
    _Vcmax = Photosynthesis.photo_TD_from_val(td_vc_bernacchi, FT(100.0), T)
    push!(vcmax_Bernacchi, _Vcmax)
    _Vcmax = Photosynthesis.photo_TD_from_val(td_vc_clm, FT(100.0), T)
    push!(vcmax_CLM, _Vcmax)
end
#----------------------------------------------------------------------------

figure()
plot(Tleaf .- 273.15, vcmax_Bernacchi, label="Bernacchi", lw=2)
plot(Tleaf .- 273.15, vcmax_CLM,  label="CLM5", lw=2)
ylabel("Vcmax (µmol/m²/s)")
xlabel("Leaf Temperature (°C)")
legend()
gcf()
#----------------------------------------------------------------------------

# ### CO$_2$ compensation point $\Gamma_\star$

## Here, we only have one implementation:
Γ_CLM = Float32[]
Tleaf = collect(FT,260.0:1.0:315.0)
td_gamma_clm = Photosynthesis.ΓStarTDCLM(FT)

for T in Tleaf
    _ΓStar = Photosynthesis.photo_TD_from_set(td_gamma_clm, T)
    push!(Γ_CLM, _ΓStar)
end
#----------------------------------------------------------------------------
figure()
plot(Tleaf .- 273.15, Γ_CLM, label="Γ_CLM")
ylabel("Γ⋆ (Pa)")
xlabel("Leaf Temperature (°C)")
legend()
gcf()
#----------------------------------------------------------------------------

# ## T-dependence of J$_{max}$

Jmax_Bernacchi = Float32[]
Jmax_CLM = Float32[]
Tleaf = collect(FT,260.0:1.0:315.0)

td_j_bernacchi = Photosynthesis.JmaxTDBernacchi(FT)
td_j_clm = Photosynthesis.JmaxTDCLM(FT)

for T in Tleaf
    _Jmax = Photosynthesis.photo_TD_from_val(td_j_bernacchi, FT(100.0), T)
    push!(Jmax_Bernacchi, _Jmax)
    _Jmax = Photosynthesis.photo_TD_from_val(td_j_clm, FT(100.0), T)
    push!(Jmax_CLM, _Jmax)
end
#----------------------------------------------------------------------------
figure()
plot(Tleaf .- 273.15, Jmax_Bernacchi, label="Bernacchi")
plot(Tleaf .- 273.15, Jmax_CLM,  label="CLM5")
ylabel("Jmax (µmol/m2/s)")
xlabel("Leaf Temperature (°C)")
legend()
gcf()
#----------------------------------------------------------------------------

# ---
# ## Summary
# The way land surface models implement temperature variations in V$_{c,max}$ is still rather variable and there is no clear consensus in what model is best. In addition, gas exchange datasets performed at different temperatures might actually alias different confounding factors into the V$_{c,max}$ determination, so that the actually fitted V$_{c,max}$ strongly depends on the model formulation. E.g. mesophyll conductance is usually ignore in deriving V$_{c,max}$ but might be temperature dependent, which can cause T-dependent errors in V$_{c,max}$.
#
# That said, having the option to use different model forumations and actually using the ones that are implemented in land surface models directly when fitting leaf level measurements seems to be a prudent way to improve models in the future. More leaf level data might help in the future, especially looking at temperature dependencies, mesophyll conductance, and fluorescence yields. So many chemical and phsyical aspects change with temperature that it is hard to isolate impacting factors. Now, at least, we can run the models directly in the REPL, which is great for prototyping anc validating the model on the individual module level without any effort in terms of additional coding.


"""
     isschur(A::AbstractMatrix) -> Bool

Test whether `A` is a square matrix in a real or complex Schur form.
In the real case, it is only tested whether A is a quasi upper triangular matrix,
which may have 2x2 diagonal blocks, which however must not correspond to
complex conjugate eigenvalues. In the complex case, it is tested if `A`
is upper triangular.
"""
function isschur(A)
   @assert !Base.has_offset_axes(A)
   m, n = size(A)
   if m != n
      return false
   end
   if m == 1
      return true
   end
   if eltype(A)<:Complex
      return istriu(A)
   else
      if m == 2
         return true
      end
      if istriu(A,-1)
         for i = 1:m-2
             if !iszero(A[i+1,i]) & !iszero(A[i+2,i+1])
                return false
             end
          end
          return true
      else
          return false
      end
   end
end
"""
     isschur(A::AbstractMatrix, B::AbstractMatrix) -> Bool

Test whether `(A,B)` is a pair of square matrices in a real or complex
generalized Schur form.
In the real case, it is only tested whether `B` is upper triangular and `A` is
a quasi upper triangular matrix, which may have 2x2 diagonal blocks, which
however must not correspond to complex conjugate eigenvalues.
In the complex case, it is tested if `A` and `B` are both upper triangular.

"""
function isschur(A,B)
   ma, na = size(A)
   mb, nb = size(B)
   if eltype(A) != eltype(B) || ma != na || mb != nb || na != nb
      return false
   end
   return isschur(A) && istriu(B)
end
"""
    utqu!(Q,U) -> Q

Compute efficiently the symmetric/hermitian product `U'QU -> Q`,
where `Q` is a symmetric/hermitian matrix and `U` is a square matrix.
The resulting product overwrites `Q`.
"""
function utqu!(Q,U)
   n = LinearAlgebra.checksquare(Q)
   if !ishermitian(Q)
      error("Q must be a symmetric/hermitian matrix")
   end
   if LinearAlgebra.checksquare(U) != n
      throw(DimensionMismatch("U must be a matrix of dimension $n x $n"))
   end

   T = promote_type(eltype(Q),eltype(U))
   if !(T <: BlasFloat)
      if T == eltype(Q)
         Q[:,:] = U'*Q*U
         return
      else
         error("TypeError: same type expected for Q and U ")
      end
   end

   idiag = diagind(Q)
   Q[idiag] = Q[idiag]/2
   if isa(U,Adjoint)
      tmp = U.parent*(UpperTriangular(Q)*U)
   else
      tmp = Adjoint(U)*(UpperTriangular(Q)*U)
   end
   #Q = tmp+tmp'
   for j = 1:n
      Q[j,j] = tmp[j,j]+tmp[j,j]'
      for i = j+1:n
         Q[i,j] = tmp[i,j]+tmp[j,i]'
         Q[j,i] = Q[i,j]'
      end
   end
   return Q
end
"""
    X = utqu(Q,U)

Compute efficiently the symmetric/hermitian product `X = U'QU`,
where Q is a symmetric/hermitian matrix.
"""
function utqu(Q,U)
   n = LinearAlgebra.checksquare(Q)
   if !ishermitian(Q)
      error("Q must be a symmetric/hermitian matrix")
   end
   adj = isa(U,Adjoint)
   if adj
      m, n1 = size(U.parent)
      if n1 != n
         throw(DimensionMismatch("U must be a matrix of column dimension $n"))
      end
   else
      n1, m = size(U)
      if n1 != n
         throw(DimensionMismatch("U must be a matrix of row dimension $n"))
      end
   end

   T = promote_type(eltype(Q),eltype(U))
   if !(T <: BlasFloat)
      return U'*Q*U
   end

   t = triu(Q)
   idiag = diagind(Q)
   t[idiag] = t[idiag]/2
   if adj
      t = U.parent*(UpperTriangular(t)*U)
   else
      t = Adjoint(U)*(UpperTriangular(t)*U)
   end
   #X = t+t'
   X = similar(t)
   for j = 1:m
      X[j,j] = t[j,j]+t[j,j]'
      for i = j+1:m
         X[i,j] = t[i,j]+t[j,i]'
         X[j,i] = X[i,j]'
      end
   end
   return X
end

"""
    qrupdate!(R, Y) -> R

Update the upper triangular factor `R` by the
upper triangular factor of the QR factorization  of `[ R; Y' ]`, where `Y` is a
low-rank matrix `Y` (typically with one or two columns). The computation of `R`
only uses `O(n^2)` operations (`n` is the size of `R`). The input matrix `R` is
updated in place and the matrix `Y` is destroyed during the computation.
"""
function qrupdate!(R, Y)
    n, m = size(Y)
    if size(R, 1) != n
      throw(DimensionMismatch("updating matrix must fit size of upper triangular matrix"))
    end
    #Y = conj(Y)
    for k = 1:m
        for i = 1:n

            # Compute Givens rotation
            #c, s, r = LinearAlgebra.givensAlgorithm(R[i,i], conj(Y[i,k]))
            c, s, r = LinearAlgebra.givensAlgorithm(R[i,i], Y[i,k])

            # Store new diagonal element
            R[i,i] = r

            # Update remaining elements in row/column
            for j = i + 1:n
                Rij = R[i,j]
                yjk  = Y[j,k]
                R[i,j]  =   c*Rij + s*yjk
                Y[j,k]    = -s'*Rij + c*yjk
            end
        end
    end
    return R
end
"""

    rqupdate!(R, Y) -> R

Update the upper triangular factor `R` by the
upper triangular factor of the RQ factorization  of `[ Y R]`, where `Y` is a
low-rank matrix `Y` (typically with one or two columns). The computation of `R`
only uses `O(n^2)` operations (`n` is the size of `R`). The input matrix `R` is
updated in place and the matrix `Y` is destroyed during the computation.
"""
function rqupdate!(R, Y)
    n, m = size(Y)
    if size(R, 1) != n
        throw(DimensionMismatch("updating matrix must fit size of upper triangular matrix"))
    end

    for k = 1:m
        for j = n:-1:1

            # Compute Givens rotation
            c, s, r = LinearAlgebra.givensAlgorithm(R[j,j], Y[j,k])

            # Store new diagonal element
            R[j,j] = r

            # Update remaining elements in row/column
            for i = 1: j - 1
                Rij = R[i,j]
                yik  = Y[i,k]
                R[i,j]  =   c*Rij + s*yik
                Y[i,k]    = -s'*Rij + c*yik
            end
        end
    end
    return R
end
"""
    x = triu2vec(Q; rowwise = false, her = false)

Reshape the upper triangular part of the `nxn` array `Q` as a one-dimensional column
vector `x` with `n(n+1)/2` elements. `Q` is assumed symmetric/hermitian if `her = true`.
The elements of `x` correspond to stacking the elements of successive columns
of the upper triangular part of `Q`, if `rowwise = false`, or stacking the elements
of successive rows of the upper triangular part of `Q`, if `rowwise = true`.
"""
function triu2vec(Q::AbstractArray; rowwise = false, her = false)
   n = LinearAlgebra.checksquare(Q)
   if her && !ishermitian(Q)
      error("Q must be a symmetric/hermitian matrix")
   end
   x = Array{eltype(Q),1}(undef,Int(n*(n+1)/2))
   k = 1
   if rowwise
      for i = 1:n
         for j = i:n
             x[k] = Q[i,j]
             k += 1
         end
      end
   else
      for j = 1:n
         for i = 1:j
             x[k] = Q[i,j]
             k += 1
         end
      end
   end
   return x
end
"""
    Q = vec2triu(x; rowwise = false, her = false)

Build from a one-dimensional column vector `x` with `n(n+1)/2` elements
an `nxn` upper triangular matrix `Q` if `her = false` or an `nxn` symetric/hermitian
array `Q` if `her = true`.
The elements of `x` correspond to stacking the elements of successive columns
of the upper triangular part of `Q`, if `rowwise = false`, or stacking the elements
of successive rows of the upper triangular part of `Q`, if `rowwise = true`.
"""
function vec2triu(x::AbstractVector; rowwise = false, her = false)
   k = length(x)
   n = (sqrt(1+8*k)-1)/2
   isinteger(n) ? n = Int(n) : error("The lenght of x must be of the form n(n+1)/2")
   her ? Q = Array{eltype(x),2}(undef,n,n) : Q = zeros(eltype(x),n,n)
   k = 1
   if rowwise
      for i = 1:n
         Q[i,i] = x[k]
         k += 1
         for j = i+1:n
            Q[i,j] = x[k]
            k += 1
         end
      end
   else
      for j = 1:n
         for i = 1:j-1
            Q[i,j] = x[k]
            k += 1
         end
         Q[j,j] = x[k]
         k += 1
      end
   end
   if her
      for j = 1:n
         for i = j+1:n
            Q[i,j] = Q[j,i]'
         end
      end
   end
   return Q
end


# Author: Lukasz Bratos

# Funkcja znajduje minimalna wartosc w przedziale [1, 2],
# ktora nie spelna rownania x * 1/x == 1 w arytmetyce zmiennopozycyjnej.
function betweenOneAndTwo()
    x = one(Float64)
    while nextfloat(x) * (one(Float64) / nextfloat(x)) == one(Float64) && x < 2.0
        x = nextfloat(x)
    end
    return nextfloat(x)
end

# Funkcja do znajdowania najmniejszej dodatniej liczby
# ktora nie spelnia rownania x * 1/x == 1
function theSmallestOne()
    x = zero(Float64)
    while nextfloat(x) * (one(Float64) / nextfloat(x)) == one(Float64)
        x = nextfloat(x)
    end
    return nextfloat(x)
end

println("The smallest number in [1, 2] that doesn't satisfy the quation x * 1/x = 1: ", betweenOneAndTwo())
#println("The smallest number in [-inf, +inf] that doesn't satisfy the quation x * 1/x = 1: ", theSmallestOne())


using Dijkstra
using Test

graph = Graph{Symbol}()
add_edge!(graph, :S, :A, 4)
add_edge!(graph, :S, :B, 3)
add_edge!(graph, :S, :D, 7)
add_edge!(graph, :A, :C, 1)
add_edge!(graph, :B, :S, 3)
add_edge!(graph, :B, :D, 4)
add_edge!(graph, :C, :E, 1)
add_edge!(graph, :C, :D, 3)
add_edge!(graph, :D, :E, 1)
add_edge!(graph, :D, :T, 3)
add_edge!(graph, :D, :F, 5)
add_edge!(graph, :E, :G, 2)
add_edge!(graph, :G, :E, 2)
add_edge!(graph, :G, :T, 3)
add_edge!(graph, :T, :F, 5)

solution = ShortestPath(graph, :S)
@test get_distance(solution, :T) == 10


using PyTensorFlow
using PyCall
using LinearAlgebra
using PyPlot
using Random
Random.seed!(233)

if Sys.islinux()
py"""
import tensorflow as tf
libPoissonOp = tf.load_op_library('build/libPoissonOp.so')
@tf.custom_gradient
def poisson_op(coef,g,h,rhograv,index):
    p = libPoissonOp.poisson_op(coef,g,h,rhograv,index)
    def grad(dy):
        return libPoissonOp.poisson_op_grad(dy, p, coef, g, h, rhograv, index)
    return p, grad
"""
elseif Sys.isapple()
py"""
import tensorflow as tf
libPoissonOp = tf.load_op_library('build/libPoissonOp.dylib')
@tf.custom_gradient
def poisson_op(coef,g,h,rhograv,index):
    p = libPoissonOp.poisson_op(coef,g,h,rhograv,index)
    def grad(dy):
        return libPoissonOp.poisson_op_grad(dy, p, coef, g, h, rhograv, index)
    return p, grad
"""
elseif Sys.iswindows()
py"""
import tensorflow as tf
libPoissonOp = tf.load_op_library('build/libPoissonOp.dll')
@tf.custom_gradient
def poisson_op(coef,g,h,rhograv,index):
    p = libPoissonOp.poisson_op(coef,g,h,rhograv,index)
    def grad(dy):
        return libPoissonOp.poisson_op_grad(dy, p, coef, g, h, rhograv, index)
    return p, grad
"""
end

poisson_op = py"poisson_op"

len_z = 4
len_x = 4

rho = 1000.0
G = 9.8

nScale = 5
tf_g = Array{Any}(undef, nScale)
tf_coef = Array{Any}(undef, nScale)
tf_h = Array{Any}(undef, nScale)
tf_p = Array{Any}(undef, nScale)
p_true_array = Array{Any}(undef, nScale)
p_inv_array = Array{Any}(undef, nScale)

h_array = @. 1 / 2^(1:nScale)

for iScale = 1:nScale
    h = h_array[iScale]
    nz = Int(len_z/h + 1)
    nx = Int(len_x/h + 1)
    g = zeros(nz, nx)
    coef = zeros(nz, nx)
    p_true = zeros(nz, nx)
    for i = 1:nz
        for j = 1:nx
            g[i,j] = -sin(2*pi/len_z*(i-1)*h) * sin(2*pi/len_x*(j-1)*h)
            coef[i,j] = 1.0 - cos(2*pi/len_z*(i-1)*h) * sin(2*pi/len_x*(j-1)*h) * len_z / (2*pi*rho*G)

            # g[i,j] = 2.0*(i-1)*h*exp(-(((i-1)*h)^2) -(((j-1)*h)^2)) * rho * G
            # coef[i,j] = 1.0 + exp(-(((i-1)*h)^2) -(((j-1)*h)^2))

            p_true[i,j] = rho*G*(i-1)*h
        end
    end
    p_true_array[iScale] = p_true .- mean(p_true)
    # p_true_array[iScale] = p_true
    tf_g[iScale] = constant(g)
    tf_coef[iScale] = constant(coef)
    tf_h[iScale] = constant(h)
    tf_p[iScale] = poisson_op(tf_coef[iScale], tf_g[iScale], tf_h[iScale], constant(rho*G), constant(1))
end

sess = Session()
init(sess)
p_inv_array = run(sess, tf_p)
for iScale = 1:nScale
    p_inv_array[iScale] = p_inv_array[iScale] .- mean(p_inv_array[iScale])
end

function l2_error(p_true, p_inv, iScale)
    l2_error = 0.0
    l2_norm = 0.0
    h = h_array[iScale]
    nz = size(p_true)[1]
    nx = size(p_true)[2]
    for i = 1:nz
        for j = 1:nx
            l2_error += (p_true[i,j]-p_inv[i,j])^2 * h^2
            l2_norm += (p_true[i,j])^2 * h^2
        end
    end
    return sqrt(l2_error)/sqrt(l2_norm)
end

Error_array = Array{Any}(undef, nScale)
for iScale = 1:nScale
    Error_array[iScale] = l2_error(p_true_array[iScale], p_inv_array[iScale], iScale)
end

loglog(h_array, Error_array, "*-", label="MMS convergence")
loglog(h_array, h_array.^2 * 0.5*Error_array[1]/h_array[1]^2, "--",label="\$\\mathcal{O}(h^2)\$")
loglog(h_array, h_array * 0.5*Error_array[1]/h_array[1], "-",label="\$\\mathcal{O}(h)\$")

plt.gca().invert_xaxis()
legend()
xlabel("\$h\$")
ylabel("Error")

# imshow(p)


#!/usr/bin/env julia

# Import libraries.
using Turing, StatsPlots, Random

# Set the true probability of heads in a coin.
p_true = 0.5

# Iterate from having seen 0 observations to 100 observations.
Ns = 0:100

# Draw data from a Bernoulli distribution, i.e. draw heads or tails.
Random.seed!(12)
data = rand(Bernoulli(p_true), last(Ns))

# Declare our Turing model.
@model function coinflip(y)
    # Our prior belief about the probability of heads in a coin.
    p ~ Beta(1, 1)

    # The number of observations.
    N = length(y)
    for n in 1:N
        # Heads or tails of a coin are drawn from a Bernoulli distribution.
        y[n] ~ Bernoulli(p)
    end
end

# Settings of the Hamiltonian Monte Carlo (HMC) sampler.
iterations = 1000
ϵ = 0.05
τ = 10

# Start sampling.
#@time chain = sample(coinflip(data), HMC(ϵ, τ), iterations)
@time chain = sample(coinflip(data), HMC(ϵ, τ),MCMCThreads(),iterations,1)

# Plot a summary of the sampling process for the parameter p, i.e. the probability of heads in a coin.
#histogram(chain[:p])


## Spatial
function inversefourier(nx::Int,ny::Int,u::Array{ComplexF64,2})
    umn = zeros(ComplexF64,2*ny-1,2*nx-1)
    uxy = zeros(Float64,2*ny-1,2*nx-1)
    for m1 = 0:1:nx-1
        n1min = m1 == 0 ? 1 : -ny + 1
        for n1 = n1min:1:ny-1
            umn[n1 + ny,m1+nx] = u[n1+ny,m1+1]
            umn[-n1 + ny,-m1+nx] = conj(u[n1+ny,m1+1])
        end
    end
    real(ifft(ifftshift(umn)))
end

function inversefourier(nx::Int,ny::Int,u::Array{Array{ComplexF64,2},1})
    umn = zeros(ComplexF64,2*ny-1,2*nx-1,length(u))
    uxy = zeros(Float64,2*ny-1,2*nx-1,length(u))
    for i in eachindex(u)
        for m1 = 0:1:nx-1
            n1min = m1 == 0 ? 1 : -ny + 1
            for n1 = n1min:1:ny-1
                umn[n1 + ny,m1+nx,i] = u[i][n1+ny,m1+1]
                umn[-n1 + ny,-m1+nx,i] = conj(u[i][n1+ny,m1+1])
            end
        end
        uxy[:,:,i] = real(ifft(ifftshift(umn[:,:,i])))
    end
    uxy
end

function inversefourier(nx::Int,ny::Int,Λ::Int,u::Array{ArrayPartition{Complex{Float64},Tuple{Array{Complex{Float64},2},Array{Complex{Float64},4}}},1})
    umn = zeros(ComplexF64,2*ny-1,2*nx-1,length(u))
    uxy = zeros(Float64,2*ny-1,2*nx-1,length(u))
    for i in eachindex(u)
        for m1 = 0:1:Λ
            n1min = m1 == 0 ? 1 : -ny + 1
            for n1 = n1min:1:ny-1
                umn[n1 + ny,m1+nx,i] = u[i].x[1][n1+ny,m1+1]
                umn[-n1 + ny,-m1+nx,i] = conj(u[i].x[1][n1+ny,m1+1])
            end
        end
        uxy[:,:,i] = real(ifft(ifftshift(umn[:,:,i])))
    end
    uxy
end

## Velocity
function velocity(lx::Float64,ly::Float64,nx::Int,ny::Int,u::Array{Array{ComplexF64,2},1})
    uk = zeros(ComplexF64,2*ny-1,2*nx-1,length(u))
    ux = zeros(Float64,2*ny-1,2*nx-1,length(u))
    for i in eachindex(u)
        for m1 = 0:nx-1
            n1min = m1 == 0 ? 1 : -(ny-1)
            for n1 = n1min:ny-1
                kx = 2.0*Float64(pi)/lx*m1
                ky = 2.0*Float64(pi)/ly*n1
                uk[n1 + ny,m1 + nx,i] = -ky*u[i][n1 + ny,m1 + 1]/(kx^2+ky^2)
                uk[-n1 + ny,-m1 + nx,i] = conj(uk[n1 + ny,m1 + nx,i])
            end
        end
        ux[:,:,i] = real(ifft(ifftshift(uk[:,:,i])))
    end
    ux
end

function zonalvelocity(lx::Float64,ly::Float64,nx::Int,ny::Int,Λ::Int,u::Array{Array{ComplexF64,2},1})
    uk = zeros(ComplexF64,2*ny-1,2*nx-1,length(u))
    ux = zeros(Float64,2*ny-1,2*nx-1,length(u))
    for i in eachindex(u)
        for m1 = 0:Λ
            n1min = m1 == 0 ? 1 : -(ny-1)
            for n1 = n1min:ny-1
                kx = 2.0*Float64(pi)/lx*m1
                ky = 2.0*Float64(pi)/ly*n1
                uk[n1 + ny,m1 + nx,i] = -ky*u[i][n1 + ny,m1 + 1]/(kx^2+ky^2)
                uk[-n1 + ny,-m1 + nx,i] = conj(uk[n1 + ny,m1 + nx,i])
            end
        end
        ux[:,:,i] = real(ifft(ifftshift(uk[:,:,i])))
    end
    ux
end

function zonalvelocity(lx::Float64,ly::Float64,nx::Int,ny::Int,Λ::Int,u::Array{ArrayPartition{Complex{Float64},Tuple{Array{Complex{Float64},2},Array{Complex{Float64},4}}},1})
    uk = zeros(ComplexF64,2*ny-1,2*nx-1,length(u))
    ux = zeros(Float64,2*ny-1,2*nx-1,length(u))
    for i in eachindex(u)
        for m1 = 0:Λ
            n1min = m1 == 0 ? 1 : -(ny-1)
            for n1 = n1min:ny-1
                kx = 2.0*Float64(pi)/lx*m1
                ky = 2.0*Float64(pi)/ly*n1
                uk[n1 + ny,m1 + nx,i] = -ky*u[i].x[1][n1 + ny,m1 + 1]/(kx^2+ky^2)
                uk[-n1 + ny,-m1 + nx,i] = conj(uk[n1 + ny,m1 + nx,i])
            end
        end
        ux[:,:,i] = real(ifft(ifftshift(uk[:,:,i])))
    end
    ux
end

## Energy for NL/GQL
function e_lohi(lx::Float64,ly::Float64,nx::Int,ny::Int,Λ::Int,u::Array{Array{ComplexF64,2},1})
    e_lo = zeros(Float64,length(u))
    e_hi = fill!(similar(e_lo),0)
    for i in eachindex(u)
        for m1 = 0:1:Λ
            n1min = m1 == 0 ? 1 : -(ny-1)
            for n1 = n1min:1:ny-1

                kx = 2.0*Float64(pi)/lx*m1
                ky = 2.0*Float64(pi)/ly*n1
                e_lo[i] += abs(u[i][n1 + ny,m1 + 1])^2/(kx^2 + ky^2)
            end
        end
        for m1 = Λ+1:1:nx-1
            for n1 = -(ny-1):1:ny-1
                kx = 2.0*Float64(pi)/lx*m1
                ky = 2.0*Float64(pi)/ly*n1
                e_hi[i] += abs(u[i][n1 + ny,m1 + 1])^2/(kx^2 + ky^2)
            end
        end
    end
    e_lo,e_hi
end

function e_lohi(lx::Float64,ly::Float64,nx::Int,ny::Int,Λ::Int,u::Array{ArrayPartition{Complex{Float64},Tuple{Array{Complex{Float64},2},Array{Complex{Float64},4}}},1})
    e_lo = zeros(Float64,length(u))
    e_hi = fill!(similar(e_lo),0)
    for i in eachindex(u)
        for m1 = 0:1:Λ
            n1min = m1 == 0 ? 1 : -(ny-1)
            for n1 = n1min:1:ny-1

                kx = 2.0*Float64(pi)/lx*m1
                ky = 2.0*Float64(pi)/ly*n1
                e_lo[i] += abs(u[i].x[1][n1 + ny,m1 + 1])^2/(kx^2 + ky^2)
            end
        end
        for m1 = Λ+1:1:nx-1
            for n1 = -(ny-1):1:ny-1
                kx = 2.0*Float64(pi)/lx*m1
                ky = 2.0*Float64(pi)/ly*n1
                e_hi[i] += abs(u[i].x[2][n1 + ny,m1 - Λ,n1 + ny,m1 - Λ])/(kx^2 + ky^2)
            end
        end
    end
    e_lo,e_hi
end

function fourierenergy(lx::Float64,ly::Float64,nx::Int,ny::Int,u::Array{Array{ComplexF64,2},1})
    E = zeros(Float64,2*ny-1,2*nx-1,length(u))
    for i in eachindex(u)
        for m1 = 0:1:nx-1
            n1min = m1 == 0 ? 1 : -ny + 1
            for n1 = n1min:1:ny-1
                kx = 2.0*Float64(pi)/lx*m1
                ky = 2.0*Float64(pi)/ly*n1
                E[n1 + ny,m1+nx,i] = abs(u[i][n1+ny,m1+1])^2/(kx^2 + ky^2)
                E[-n1 + ny,-m1+nx,i] = E[n1 + ny,m1+nx,i]
            end
        end
    end
    E
end

function fourierenergy(lx::Float64,ly::Float64,nx::Int,ny::Int,Λ::Int,u::Array{ArrayPartition{Complex{Float64},Tuple{Array{Complex{Float64},2},Array{Complex{Float64},4}}},1})
    E = zeros(Float64,2*ny-1,2*nx-1,length(u))
    for i in eachindex(u)
        for m1 = 0:1:Λ
            n1min = m1 == 0 ? 1 : -(ny-1)
            for n1 = n1min:1:ny-1

                kx = 2.0*Float64(pi)/lx*m1
                ky = 2.0*Float64(pi)/ly*n1
                E[n1 + ny,m1+nx,i] += abs(u[i].x[1][n1 + ny,m1 + 1])^2/(kx^2 + ky^2)
                E[-n1 + ny,-m1+nx,i] = E[n1 + ny,m1+nx,i]
            end
        end
        for m1 = Λ+1:1:nx-1
            for n1 = -(ny-1):1:ny-1
                kx = 2.0*Float64(pi)/lx*m1
                ky = 2.0*Float64(pi)/ly*n1
                E[n1 + ny,m1+nx,i] += abs(u[i].x[2][n1 + ny,m1 - Λ,n1 + ny,m1 - Λ])/(kx^2 + ky^2)
                E[-n1 + ny,-m1+nx,i] = E[n1 + ny,m1+nx,i]
            end
        end
    end
    E
end

function energy(lx::Float64,ly::Float64,nx::Int,ny::Int,u::Array{Array{ComplexF64,2},1})
    E = zeros(Float64,length(u))
    Z = fill!(similar(E),0)
    for i in eachindex(u)

        for m1 = 0:nx-1
            n1min = m1 == 0 ? 1 : -(ny-1)
            for n1 = n1min:ny-1

                kx = 2.0*Float64(pi)/lx*m1
                ky = 2.0*Float64(pi)/ly*n1

                E[i] += abs(u[i][n1 + ny,m1 + 1])^2/(kx^2 + ky^2)
                Z[i] += abs(u[i][n1 + ny,m1 + 1])^2

            end
        end
    end
    E,Z
end

# energy for GCE2
function energy(lx::Float64,ly::Float64,nx::Int,ny::Int,Λ::Int,u::Array{ArrayPartition{Complex{Float64},Tuple{Array{Complex{Float64},2},Array{Complex{Float64},4}}},1})

    E = zeros(Float64,length(u))
    Z = fill!(similar(E),0)

    for i in eachindex(u)

        for m1 = 0:1:Λ
            n1min = m1 == 0 ? 1 : -(ny-1)
            for n1 = n1min:1:ny-1

                kx = 2.0*Float64(pi)/lx*m1
                ky = 2.0*Float64(pi)/ly*n1

                E[i] += abs(u[i].x[1][n1 + ny,m1 + 1])^2/(kx^2 + ky^2)
                Z[i] += abs(u[i].x[1][n1 + ny,m1 + 1])^2

            end
        end

        for m1 = Λ+1:1:nx-1
            for n1 = -(ny-1):1:ny-1

                kx = 2.0*Float64(pi)/lx*m1
                ky = 2.0*Float64(pi)/ly*n1

                E[i] += abs(u[i].x[2][n1 + ny,m1 - Λ,n1 + ny,m1 - Λ])/(kx^2 + ky^2)
                Z[i] += abs(u[i].x[2][n1 + ny,m1 - Λ,n1 + ny,m1 - Λ])

            end
        end
    end
    E,Z
end

## modal strength
function modalstrength(lx::Float64,ly::Float64,nx::Int,ny::Int,u::Array{Array{ComplexF64,2},1})

    E = zeros(Float64,length(u),(2*ny-1)*nx)

    for i in eachindex(u)

        for m1 = 0:1:nx-1
            n1min = m1 == 0 ? 1 : -(ny-1)
            for n1 = n1min:1:ny-1

                kx = 2.0*Float64(pi)/lx*m1
                ky = 2.0*Float64(pi)/ly*n1

                E[i,m1*(2*ny-1) + n1+ny] = abs(u[i][n1 + ny,m1 + 1])

            end
        end

    end
    E
end

function modalstrength(lx::Float64,ly::Float64,nx::Int,ny::Int,Λ::Int,u::Array{ArrayPartition{Complex{Float64},Tuple{Array{Complex{Float64},2},Array{Complex{Float64},4}}},1})

    E = zeros(Float64,length(u),(2*ny-1)*nx)

    for i in eachindex(u)

        for m1 = 0:1:Λ

            n1min = m1 == 0 ? 1 : -(ny-1)
            for n1 = n1min:1:ny-1

                kx = 2.0*Float64(pi)/lx*m1
                ky = 2.0*Float64(pi)/ly*n1

                E[i,m1*(2*ny-1) + n1+ny] = abs(u[i].x[1][n1 + ny,m1 + 1])

            end
        end

        for m1 = Λ+1:1:nx-1
            for n1 = -(ny-1):1:ny-1

                kx = 2.0*Float64(pi)/lx*m1
                ky = 2.0*Float64(pi)/ly*n1

                E[i,m1*(2*ny-1) + n1+ny] = sqrt(abs(u[i].x[2][n1 + ny,m1 - Λ,n1 + ny,m1 - Λ]))

            end
        end

    end
    E
end

## zonal energy
function zonalenergy(lx::Float64,ly::Float64,nx::Int,ny::Int,u::Array{Array{ComplexF64,2},1})

    P = zeros(Float64,length(u),nx)
    O = fill!(similar(P),0)

    for i in eachindex(u)

        for m1 = 0:1:nx-1
            n1min = m1 == 0 ? 1 : -(ny-1)
            for n1 = n1min:1:ny-1

                kx = 2.0*Float64(pi)/lx*m1
                ky = 2.0*Float64(pi)/ly*n1

                P[i,m1+1] += abs(u[i][n1 + ny,m1 + 1])^2/(kx^2 + ky^2)
                O[i,m1+1] += abs(u[i][n1 + ny,m1 + 1])^2

            end
        end

    end
    P,O
end

function zonalenergy(lx::Float64,ly::Float64,nx::Int,ny::Int,Λ::Int,u::Array{ArrayPartition{Complex{Float64},Tuple{Array{Complex{Float64},2},Array{Complex{Float64},4}}},1})

    P = zeros(Float64,length(u),nx)
    O = fill!(similar(P),0)

    for i in eachindex(u)

        for m1 = 0:1:Λ

            n1min = m1 == 0 ? 1 : -(ny-1)
            for n1 = n1min:1:ny-1

                kx = 2.0*Float64(pi)/lx*m1
                ky = 2.0*Float64(pi)/ly*n1

                P[i,m1+1] += abs(u[i].x[1][n1 + ny,m1 + 1])^2/(kx^2 + ky^2)
                O[i,m1+1] += abs(u[i].x[1][n1 + ny,m1 + 1])^2

            end
        end

        for m1 = Λ+1:1:nx-1
            for n1 = -(ny-1):1:ny-1

                kx = 2.0*Float64(pi)/lx*m1
                ky = 2.0*Float64(pi)/ly*n1

                P[i,m1+1] += abs(u[i].x[2][n1 + ny,m1 - Λ,n1 + ny,m1 - Λ])/(kx^2 + ky^2)
                O[i,m1+1] += abs(u[i].x[2][n1 + ny,m1 - Λ,n1 + ny,m1 - Λ])

            end
        end

    end
    P,O
end

## mean vorticity NL/GQL
function meanvorticity(lx::Float64,ly::Float64,nx::Int,ny::Int,u::Array{Array{ComplexF64,2},1})

    ζf = zeros(ComplexF64,length(u),2*ny-1)
    ζy = zeros(Float64,length(u),2*ny-1)

    for i in eachindex(u)

        for n1 = 1:1:ny-1

            ζf[i,n1+ny] = u[i][n1+ny,1]
            ζf[i,-n1+ny] = conj(u[i][n1+ny,1])

        end

        ζf[i,ny] = 0.0 + 0.0im
        ζy[i,:] .= real(ifft(ifftshift(ζf[i,:])))

    end

    return ζy

end

# mean vorticity GCE2
function meanvorticity(lx::Float64,ly::Float64,nx::Int,ny::Int,u::Array{ArrayPartition{Complex{Float64},Tuple{Array{Complex{Float64},2},Array{Complex{Float64},4}}},1})

    ζf = zeros(ComplexF64,length(u),2*ny-1)
    ζy = zeros(Float64,length(u),2*ny-1)

    for i in eachindex(u)

        for n1 = 1:1:ny-1

            ζf[i,n1+ny] = u[i].x[1][n1+ny,1]
            ζf[i,-n1+ny] = conj(u[i].x[1][n1+ny,1])

        end

        ζf[i,ny] = 0.0 + 0.0im
        ζy[i,:] .= real(ifft(ifftshift(ζf[i,:])))

    end

    return ζy

end

## time averaged meanvorticity
function meanvorticity(lx::Float64,ly::Float64,nx::Int,ny::Int,t::Array{Float64,1},u::Array{Array{ComplexF64,2},1};t_begin::Float64=100.0)

    ζf = zeros(ComplexF64,length(u),2*ny-1)
    ζy = zeros(Float64,length(u),2*ny-1)

    i_begin = findall(x->x>t_begin,t)[1]

    for i in eachindex(u)

        for n1 = 1:1:ny-1
            if i > i_begin
                T = 0.0
                temp1 = 0.0
                temp2 = 0.0
                for j = i_begin+1:i
                    dt = t[j] - t[j-1]
                    T += dt
                    temp1 += u[j][n1+ny,1]*dt
                    temp2 += conj(u[j][n1+ny,1])*dt
                end
                ζf[i,n1+ny] = temp1/T
                ζf[i,-n1+ny] = temp2/T
            else
                ζf[i,n1+ny] = u[i][n1+ny,1]
                ζf[i,-n1+ny] = conj(u[i][n1+ny,1])
            end
        end

        ζf[i,ny] = 0.0 + 0.0im
        ζy[i,:] .= real(ifft(ifftshift(ζf[i,:])))

    end

    return ζy

end

function meanvorticity(lx::Float64,ly::Float64,nx::Int,ny::Int,t::Array{Float64,1},u::Array{ArrayPartition{Complex{Float64},Tuple{Array{Complex{Float64},2},Array{Complex{Float64},4}}},1};t_begin::Float64=100.0)

    ζf = zeros(ComplexF64,length(u),2*ny-1)
    ζy = zeros(Float64,length(u),2*ny-1)

    i_begin = findall(x->x>t_begin,t)[1]

    for i in eachindex(u)
        for n1 = 1:1:ny-1
            if i > i_begin
                T = 0.0
                temp1 = 0.0
                temp2 = 0.0
                for j = i_begin+1:i
                    dt = t[j] - t[j-1]
                    T += dt
                    temp1 += u[j].x[1][n1+ny,1]*dt
                    temp2 += conj(u[j].x[1][n1+ny,1])*dt
                end
                ζf[i,n1+ny] = temp1/T
                ζf[i,-n1+ny] = temp2/T
            else
                ζf[i,n1+ny] = u[i].x[1][n1+ny,1]
                ζf[i,-n1+ny] = conj(u[i].x[1][n1+ny,1])
            end
        end

        ζf[i,ny] = 0.0 + 0.0im
        ζy[i,:] .= real(ifft(ifftshift(ζf[i,:])))

    end

    return ζy

end


using JuMP, EAGO

                     m = Model()

                     EAGO.register_eago_operators!(m)

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

                     @variable(m, -2.3024137037198598 <= q <= 2.756640266423038)

                     add_NL_constraint(m, :(log(1 + exp(0.8745011162287222 + -0.4747792679508138*log(1 + exp(0.20979439694353186 + 0.2523963619940135*$(x[1]) + 0.6039440929888729*$(x[2]))) + 0.06822380173369114*log(1 + exp(-0.192203947089018 + 0.35618645260012016*$(x[1]) + -0.9380253880003773*$(x[2]))))) + log(1 + exp(-0.5284568968024108 + 0.8782860084347535*log(1 + exp(0.20979439694353186 + 0.2523963619940135*$(x[1]) + 0.6039440929888729*$(x[2]))) + 0.990986450920027*log(1 + exp(-0.192203947089018 + 0.35618645260012016*$(x[1]) + -0.9380253880003773*$(x[2]))))) - $q <= 0.0))

                     @objective(m, Min, q)

                     return m


#=
Given an array of time intervals (start, end) for classroom lectures (possibly overlapping), find the minimum number of rooms required.

For example, given [(30, 75), (0, 50), (60, 150)], you should return 2.
=#

function number_of_rooms_required(lecture_intervals::Array{Tuple{Int64,Int64},1})
    if length(lecture_intervals) == 0
        n_room = 0
    else
        room_reserved = [[0:0]]
        for lecture in lecture_intervals
            room_reserved = reserve_room(lecture, room_reserved)
        end
        n_room = length(room_reserved)
    end
    return n_room
end

function reserve_room(lecture::Tuple{Int64,Int64}, room_reserved::Array{Array{UnitRange{Int64},1},1})
    isbooked = false
    room = 1
    while !isbooked
        if room == length(room_reserved) && !isavailable(lecture, room_reserved[room])
            push!(room_reserved, [lecture[1]:lecture[end]])
            isbooked = true
        elseif isavailable(lecture, room_reserved[room])
            push!(room_reserved[room], lecture[1]:lecture[end])
            sort!(room_reserved[room])
            isbooked = true
        else
            room = room + 1
        end
    end
    return room_reserved
end

function isavailable(lecture::Tuple{Int64,Int64}, room_reserved::Array{UnitRange{Int64},1})
    room_available = false
    for lecture_in_room in 1:length(room_reserved)
        # before the first lecture?
        if lecture_in_room == 1 && minimum(room_reserved[lecture_in_room]) >= lecture[end]
            room_available = true
        elseif lecture_in_room == length(room_reserved) && maximum(room_reserved[lecture_in_room]) <= lecture[1] # after last lecture
            room_available = true
        elseif lecture_in_room != 1 && (minimum(room_reserved[lecture_in_room]) >= lecture[end] && maximum(room_reserved[lecture_in_room-1]) <= lecture[1])
            room_available = true
        end
    end
    return room_available
end


"""
    ScaleTransform(l::Real)

Transformation that multiplies the input elementwise with `l`.

# Examples

```jldoctest
julia> l = rand(); t = ScaleTransform(l); X = rand(100, 10);

julia> map(t, ColVecs(X)) == ColVecs(l .* X)
true
```
"""
struct ScaleTransform{T<:Real} <: Transform
    s::Vector{T}
end

function ScaleTransform(s::T=1.0) where {T<:Real}
    return ScaleTransform{T}([s])
end

@functor ScaleTransform

set!(t::ScaleTransform, ρ::Real) = t.s .= [ρ]

(t::ScaleTransform)(x) = only(t.s) * x

_map(t::ScaleTransform, x::AbstractVector{<:Real}) = only(t.s) .* x
_map(t::ScaleTransform, x::ColVecs) = ColVecs(only(t.s) .* x.X)
_map(t::ScaleTransform, x::RowVecs) = RowVecs(only(t.s) .* x.X)

Base.isequal(t::ScaleTransform, t2::ScaleTransform) = isequal(only(t.s), only(t2.s))

Base.show(io::IO, t::ScaleTransform) = print(io, "Scale Transform (s = ", only(t.s), ")")

# Helpers

"""
    median_heuristic_transform(distance, x::AbstractVector)

Create a [`ScaleTransform`](@ref) that divides the input elementwise by the median
`distance` of the data points in `x`.

The `distance` has to support pairwise evaluation with `KernelFunctions.pairwise`. All
`PreMetric`s of the package [Distances.jl](https://github.com/JuliaStats/Distances.jl) such
as `Euclidean` satisfy this requirement automatically.

# Examples

```jldoctest
julia> using Distances, Statistics

julia> x = ColVecs(rand(100, 10));

julia> t = median_heuristic_transform(Euclidean(), x);

julia> y = map(t, x);

julia> median(euclidean(y[i], y[j]) for i in 1:10, j in 1:10 if i != j) ≈ 1
true
```
"""
function median_heuristic_transform(f, x::AbstractVector)
    # Compute pairwise distances between **different** elements
    n = length(x)
    distances = vec(pairwise(f, x))
    deleteat!(distances, 1:(n + 1):(n^2))

    return ScaleTransform(inv(median!(distances)))
end


#Modules
using Distances

@everywhere function assign_label(x, y, k, i)
  #=
  This function assigns a label to the ith point according to
  the labels of the k nearest neighbors. The training
  data is stored in the X matrix, and its labels are stored in y.
  =#

  ###Initialize some variables
  #label with the highest count.
  highestCount = 0
  mostPopularLabel = 0

  #Dictionary to save counts of labels
  # Dict{}() is also right .
  # Int,Int indicates the dictionary to expect integer values
  counts = Dict{Int, Int}()

  # Get the k-nearest neighbors for each image
  kNearestNeighbors = get_k_nearest_neighbors(x, i, k, euclidean)

  #Iterating over the labels of the k nearest neighbors
  for n in kNearestNeighbors
    # get the label of this neighbor
    labelOfN = y[n]

    #Add the current label to our dictionary
    #if it's not already there
    if !haskey(counts, labelOfN)
      counts[labelOfN] = 0
    end

    #Add one to the count
    counts[labelOfN] += 1

    #Set the new highest count/most popular if it is the highest
    if counts[labelOfN] > highestCount
      highestCount = counts[labelOfN]
      mostPopularLabel = labelOfN
    end
  end

  return mostPopularLabel
end

@everywhere function get_k_nearest_neighbors(x, i, k, distance)
  #=
  Find the k nearest neighbors of data point i
  =#

  # number of rows and columns
  nRows, nCols = size(x)

  #=
  Let's initialize a vector image_i. We do this so that
  the image ith is accessed only once from the main X matrix.
  The program saves time because no repeated work is done.
  Also, creating an empty vector and filling it with each
  element at a time is faster than copying the entire vector at once.
  Creating empty array (vector) of nRows elements of type Float32(decimal)
  =#
  imageI = Array(Float32, nRows)

  for index in 1:nRows
    imageI[index] = x[index, i]
  end

  #For the same previous reasons, we initialize an empty vector
  #that will contain the jth data point
  imageJ = Array(Float32, nRows)

  #Distances between the ith data point and each data point in the X matrix
  distances = Array(Float32, nCols)

  for j in 1:nCols #iterating over columns is faster than rows
    #The next for loop fills the vector image_j with the jth data point
    #from the main matrix. Copying element one by one is faster
    #than copying the entire vector at once.
    for index in 1:nRows
      imageJ[index] = x[index, j]
    end

    #Calculate the n-dimensional distance between the two images
    distances[j] = distance(imageI, imageJ)
  end

  #Sort the distances
  sortedNeighbors = sortperm(distances)

  #Select the k nearest. We don't want the first one b/c it is the image itself
  kNearestNeighbors = sortedNeighbors[2:k+1]

  return kNearestNeighbors
end

@everywhere function knn(xTrain, yTrain, k)
  #=
  K-Nearest Neighbor(KNN) Algorithm
  =#
  # Transpose matrices (because iteration over columns is faster in Julia than over rows)
  xTrain = xTrain'
  yTrain = yTrain'

  # Get predictions
  # Non-parallel version:yPredictions = [assign_label(xTrain, yTrain, k, i) for i in 1:size(xTrain, 2)]
  yPredictions = @parallel (vcat) for i in 1:size(xTrain, 2)
    assign_label(xTrain, yTrain, k, i)
  end
  return yPredictions

  #Alternative non-vector implementation
  #=
  sumValues = @parallel (+) for i in 1:size(xTrain, 2)
    assign_label(xTrain, yTrain, k, i) == yTrain[i,1]
  end
  accuracy = sumValues / size(xTrain, 2)
  =#
  return yPredictions

end

@everywhere function loofcv(yPredictions, yTrain)
  #=
  Leave-One-Out-Fold Cross Validation (LOOF-CV)
  =#

  # the . makes it an element-wise comparison
  return mean(yPredictions .== yTrain)
end