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

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

# ╔═╡ 9c8a7e5a-12dd-11eb-1b99-cd1d52aefa1d
begin
	import Pkg
	Pkg.activate(mktempdir())
	Pkg.add([
		"Plots",
		"PlutoUI",
		"Images",
		"FileIO",
		"ImageMagick",
		"ImageIO",
		"OffsetArrays",
		"PaddedViews",
		"ThreadsX",
		"BenchmarkTools",
	])
	using Statistics
	using Plots
	using PlutoUI
	using Images
	using OffsetArrays
	using PaddedViews
	using ThreadsX
	using BenchmarkTools
end

# ╔═╡ 67c3dcc0-2c05-11eb-3a84-9dfea24f95a8
md"_homework 10, version 1_"

# ╔═╡ 621230b0-2c05-11eb-2a98-5bd1d7be9038
md"""

# **Homework 10**: _Climate modeling II_
`18.S191`, fall 2020
"""

# ╔═╡ 6cb238d0-2c05-11eb-221e-d5df4c479302
# edit the code below to set your name and kerberos ID (i.e. email without @mit.edu)

student = (name = "Jazzy Doe", kerberos_id = "jazz")

# you might need to wait until all other cells in this notebook have completed running.
# scroll around the page to see what's up

# ╔═╡ 6a4641e0-2c05-11eb-3430-6f14650c2ad3
md"""

Submission by: **_$(student.name)_** ($(student.kerberos_id)@mit.edu)
"""

# ╔═╡ 70077e50-2c05-11eb-3d83-732b4b780d04
md"_Let's create a package environment:_"

# ╔═╡ ed741ec6-1f75-11eb-03be-ad6284abaab8
html"""

"""

# ╔═╡ 68c01d90-2cf6-11eb-0771-7b3c6db89ecb
md"""
In Lecture 23 (video above), we looked at a 2D ocean model that included two physical processes: **advection** (_flow of heat_) and **diffusion** (_spreading of heat_). This homework includes the model from the lecture, and you will be able to experiment with it yourself!

The model is written in a way that it can be **extended with more physical processes**. In this homework we will add two more effects, introduced in the Energy Balance Model from our last homework: _absorbed_ and _emitted_ radiation.
"""

# ╔═╡ 295af330-2cf8-11eb-1606-437e8f3c43fd
md"""
## **Exercise 1** - _Advection-diffusion_

Included below is the two-dimensional advection-diffusion model from Lecture 23. To keep this homework concise, we have only included the code. To see the original notebook with more detailed comments, use the link below:
"""

# ╔═╡ c33ebe40-2cf9-11eb-384c-432dc70497b0
let
	# We reference Pkg because running Pkg operations in two notebooks at
	# the same time will break Pkg. By referencing it, this link will only show
	# when Pkg is done.
	Pkg

	# This link was generated by calling
	# encodeURIComponent("the actual link")
	# in javascript (press F12 and look for the console)
	html"""

Click here to download and run the Lecture 23 notebook in a new tab.

"""
end

# ╔═╡ 83ad05a0-2cfb-11eb-1467-e1196985519a
md"""
#### Advection & diffusion

Notice that both functions have a main method with the following signature:

`(::Array{Float64,2}, ::ClimateModel)` _maps to_ `::Array{Float64,2}`.

As we will see later, `ClimateModel` contains the grid, the velocity vector field and the simulation parameters.
"""

# ╔═╡ 1e8d37ee-2a97-11eb-1d45-6b426b25d4eb
xgrad_kernel = OffsetArray(reshape([-1., 0, 1.], 1, 3),  0:0, -1:1);

# ╔═╡ 682f2530-2a97-11eb-3ee6-99a7c79b3767
ygrad_kernel = OffsetArray(reshape([-1., 0, 1.], 3, 1),  -1:1, 0:0);

# ╔═╡ b629d89a-2a95-11eb-2f27-3dfa45789be4
xdiff_kernel = OffsetArray(reshape([1., -2., 1.], 1, 3),  0:0, -1:1);

# ╔═╡ a8d8f8d2-2cfa-11eb-3c3e-d54f7b32e4a2
ydiff_kernel = OffsetArray(reshape([1., -2., 1.], 3, 1),  -1:1, 0:0);

# ╔═╡ 2beb6ec0-2dcc-11eb-1768-0b8e4fba1597
"Surround an array with one layer of zeros."
function pad_zeros(A::AbstractArray{T,N}) where {T,N}
	PaddedView(
		zero(T),
		A,
		size(A) .+ 2,
		Tuple(fill(2,N))
	)
end

# ╔═╡ 13eb3966-2a9a-11eb-086c-05510a3f5b80
md"""
#### Data structures

Let's look at our first type, `Grid`. Notice that it only has one 'constructor function', which takes `N` (number of longitudinal grid points) and `L` (longitudinal size in meters) as arguments. The struct contains more fields, these are precomputed and stored for performance.
"""

# ╔═╡ cd2ee4ca-2a06-11eb-0e61-e9a2ecf72bd6
begin
	struct Grid
		N::Int64
		L::Float64

		Δx::Float64
		Δy::Float64

		x::Array{Float64, 2}
		y::Array{Float64, 2}

		Nx::Int64
		Ny::Int64

		# constructor function:
		function Grid(N, L)
			Δx = L/N # [m]
			Δy = L/N # [m]

			x = 0. -Δx/2.:Δx:L +Δx/2.
			x = reshape(x, (1, size(x,1)))
			y = -L -Δy/2.:Δy:L +Δy/2.
			y = reshape(y, (size(y,1), 1))

			Nx, Ny = size(x, 2), size(y, 1)

			return new(N, L, Δx, Δy, x, y, Nx, Ny)
		end
	end

	Base.zeros(G::Grid) = zeros(G.Ny, G.Nx)
end

# ╔═╡ e7f563f0-2d04-11eb-036d-992da68470a6
Grid(5,300.0e3)

# ╔═╡ 39404240-2cfe-11eb-2e3c-710e37f8cd4b
md"""
Next, let's look at three types.

Two structs: `OceanModel` and `OceanModelParameters`, and an abstract type: `ClimateModel`.
"""

# ╔═╡ 0d63e6b2-2b49-11eb-3413-43977d299d90
Base.@kwdef struct OceanModelParameters

	κ::Float64=1.e4
end

# ╔═╡ c9171c56-2dd8-11eb-189b-95d964a9724a
abstract type ClimateModel end

# ╔═╡ f4c884fc-2a97-11eb-1ba9-01bf579f8b43
begin
	# main method:
	advect(T::Array{Float64,2}, O::ClimateModel) =
		advect(T, O.u, O.v, O.grid.Δy, O.grid.Δx)

	# helper methods:
	advect(T::Array{Float64,2}, u, v, Δy, Δx) = pad_zeros([
		advect(T, u, v, Δy, Δx, j, i)
		for j=2:size(T, 1)-1, i=2:size(T, 2)-1
	])

	advect(T::Array{Float64,2}, u, v, Δy, Δx, j, i) = .-(
		u[j, i].*sum(xgrad_kernel[0, -1:1].*T[j, i-1:i+1])/(2Δx) .+
		v[j, i].*sum(ygrad_kernel[-1:1, 0].*T[j-1:j+1, i])/(2Δy)
	)
end

# ╔═╡ ee6716c8-2a95-11eb-3a00-319ee69dd37f
begin
	# main method:
	diffuse(T::Array{Float64,2}, O::ClimateModel) =
		diffuse(T, O.params.κ, O.grid.Δy, O.grid.Δx)

	# helper methods:
	diffuse(T, κ, Δy, Δx) = pad_zeros([
		diffuse(T, κ, Δy, Δx, j, i) for j=2:size(T, 1)-1, i=2:size(T, 2)-1
	])
	diffuse(T, κ, Δy, Δx, j, i) = κ.*(
		sum(xdiff_kernel[0, -1:1].*T[j, i-1:i+1])/(Δx^2) +
		sum(ydiff_kernel[-1:1, 0].*T[j-1:j+1, i])/(Δy^2)
	)
end

# ╔═╡ d3796644-2a05-11eb-11b8-87b6e8c311f9
begin
	struct OceanModel <: ClimateModel
		grid::Grid
		params::OceanModelParameters

		u::Array{Float64, 2}
		v::Array{Float64, 2}
	end

	OceanModel(G::Grid, params::OceanModelParameters) =
		OceanModel(G, params, zeros(G), zeros(G))

	OceanModel(G::Grid) =
		OceanModel(G, OceanModelParameters(), zeros(G), zeros(G))
end;

# ╔═╡ 5f5e4120-2cfe-11eb-1fa7-99fdd734f7a7
OceanModel <: ClimateModel # it's a subtype!

# ╔═╡ 74aa7512-2a9c-11eb-118c-c7a5b60eac1b
md"""
#### Timestepping

The `OceanModel` struct contains a complete _description_ of the model being simulated. The next struct, `ClimateModelSimulation`, contains a _model_, together with the simulation results. It is mutable: timestepping the model means modifying a `ClimateModelSimulation` object.
"""

# ╔═╡ f92086c4-2a74-11eb-3c72-a1096667183b
begin
	mutable struct ClimateModelSimulation{ModelType<:ClimateModel}
		model::ModelType

		T::Array{Float64, 2}
		Δt::Float64

		iteration::Int64
	end

	ClimateModelSimulation(C::ModelType, T, Δt) where ModelType =
		ClimateModelSimulation{ModelType}(C, T, Δt, 0)
end

# ╔═╡ 7caca2fa-2a9a-11eb-373f-156a459a1637
function update_ghostcells!(A::Array{Float64,2}; option="no-flux")
	# Atmp = @view A[:,:]
	if option=="no-flux"
		A[1, :] .= A[2, :]; A[end, :] .= A[end-1, :]
		A[:, 1] .= A[:, 2]; A[:, end] .= A[:, end-1]
	end
end

# ╔═╡ 81bb6a4a-2a9c-11eb-38bb-f7701c79afa2
function timestep!(sim::ClimateModelSimulation{OceanModel})
	update_ghostcells!(sim.T)

	tendencies = advect(sim.T, sim.model) .+ diffuse(sim.T, sim.model)
	sim.T .+= sim.Δt*tendencies

	sim.iteration += 1
end;

# ╔═╡ 31cb0c2c-2a9a-11eb-10ba-d90a00d8e03a
md"""
#### Exercise 1.1 - _Running the model_

In the next few cells, we set up a simulation. We have included an interactive visualisation of the simulation.

👉 Familiarize yourself with the simulation through interaction. Get a sense for each parameter by changing their values.
"""

# ╔═╡ 9841ff20-2c06-11eb-3c4c-c34e465e1594
default_grid = Grid(10, 6000.0e3);

# ╔═╡ 31cb7aae-2d04-11eb-30cb-a365a6a4aa6b
md"""
Uncomment (`Ctrl+/` or `Cmd+/`) one of the lines below to choose between the different velocity fields:
"""

# ╔═╡ 50e89130-2d04-11eb-1c8e-a34775aec40c
md"""
Choose the initial temperature state:
"""

# ╔═╡ c7736640-2d04-11eb-1108-59a7446e244d
md"""
We define our ocean simulation. Run this cell again to reset the simulation to the initial state.
"""

# ╔═╡ 981ef38a-2a8b-11eb-08be-b94be2924366
md"**Simulation controls**"

# ╔═╡ d042d25a-2a62-11eb-33fe-65494bb2fad5
begin
	quiverBox = @bind show_quiver CheckBox(default=false)
	anomalyBox = @bind show_anomaly CheckBox(default=false)
	md"""
	*Click to show the velocity field* $(quiverBox) *or to show temperature **anomalies** instead of absolute values* $(anomalyBox)
	"""
end

# ╔═╡ c20b0e00-2a8a-11eb-045d-9db88411746f
begin
	U_ex_Slider = @bind U_ex Slider(-4:1:8, default=0, show_value=false);
	md"""
	$(U_ex_Slider)
	"""
end

# ╔═╡ 6dbc3d34-2a89-11eb-2c80-75459a8e237a
begin
	md"*Vary the current speed U:*  $(2. ^U_ex) [× reference]"
end

# ╔═╡ 933d42fa-2a67-11eb-07de-61cab7567d7d
begin
	κ_ex_Slider = @bind κ_ex Slider(0.:1.e3:1.e5, default=1.e4, show_value=true)
	md"""
	*Vary the diffusivity κ:* $(κ_ex_Slider) [m²/s]
	"""
end

# ╔═╡ c9ea0f72-2a67-11eb-20ba-376ca9c8014f
@bind go_ex Clock(0.1)

# ╔═╡ c0298712-2a88-11eb-09af-bf2c39167aa6
md"""##### Velocity field for a single circular vortex
"""

# ╔═╡ e3ee80c0-12dd-11eb-110a-c336bb978c51
begin
	∂x(ϕ, Δx) = (ϕ[:,2:end] - ϕ[:,1:end-1])/Δx
	∂y(ϕ, Δy) = (ϕ[2:end,:] - ϕ[1:end-1,:])/Δy

	xpad(ϕ) = hcat(zeros(size(ϕ,1)), ϕ, zeros(size(ϕ,1)))
	ypad(ϕ) = vcat(zeros(size(ϕ,2))', ϕ, zeros(size(ϕ,2))')

	xitp(ϕ) = 0.5*(ϕ[:,2:end]+ϕ[:,1:end-1])
	yitp(ϕ) = 0.5*(ϕ[2:end,:]+ϕ[1:end-1,:])

	function diagnose_velocities(ψ, G)
		u = xitp(∂y(ψ, G.Δy/G.L))
		v = yitp(-∂x(ψ, G.Δx/G.L))
		return u,v
	end
end

# ╔═╡ df706ebc-2a63-11eb-0b09-fd9f151cb5a8
function impose_no_flux!(u, v)
	u[1,:] .= 0.; v[1,:] .= 0.;
	u[end,:] .= 0.; v[end,:] .= 0.;
	u[:,1] .= 0.; v[:,1] .= 0.;
	u[:,end] .= 0.; v[:,end] .= 0.;
end

# ╔═╡ e2e4cfac-2a63-11eb-1b7f-9d8d5d304b43
function PointVortex(G; Ω=1., a=0.2, x0=0.5, y0=0.)
	x = reshape(0. -G.Δx/(G.L):G.Δx/G.L:1. +G.Δx/(G.L), (1, G.Nx+1))
	y = reshape(-1. -G.Δy/(G.L):G.Δy/G.L:1. +G.Δy/(G.L), (G.Ny+1, 1))

	function ψ̂(x,y)
		r = sqrt.((y .-y0).^2 .+ (x .-x0).^2)

		stream = -Ω/4*r.^2
		stream[r .> a] = -Ω*a^2/4*(1. .+ 2*log.(r[r .> a]/a))

		return stream
	end

	u, v = diagnose_velocities(ψ̂(x, y), G)
	impose_no_flux!(u, v)

	return u,v
end

# ╔═╡ 1dd3fc70-2c06-11eb-27fe-f325ca208504
# ocean_velocities = zeros(default_grid), zeros(default_grid);
ocean_velocities = PointVortex(default_grid, Ω=0.5);
# ocean_velocities = DoubleGyre(default_grid);

# ╔═╡ bb084ace-12e2-11eb-2dfc-111e90eabfdd
md"""##### Quasi-realistic ocean velocity field $\vec{u} = (u, v)$
Our velocity field is given by an analytical solution to the classic wind-driven gyre
problem, which is given by solving the fourth-order partial differential equation:

$- \epsilon_{M} \hat{\nabla}^{4} \hat{\Psi} + \frac{\partial \hat{\Psi} }{ \partial \hat{x}} = \nabla \times \hat{\tau} \mathbf{z},$

where the hats denote that all of the variables have been non-dimensionalized and all of their constant coefficients have been bundles into the single parameter $\epsilon_{M} \equiv \dfrac{\nu}{\beta L^3}$.

The solution makes use of an advanced *asymptotic method* (valid in the limit that $\epsilon \ll 1$) known as *boundary layer analysis* (see MIT course 18.305 to learn more).
"""

# ╔═╡ ecaab27e-2a16-11eb-0e99-87c91e659cf3
function DoubleGyre(G; β=2e-11, τ₀=0.1, ρ₀=1.e3, ν=1.e5, κ=1.e5, H=1000.)
	ϵM = ν/(β*G.L^3)
	ϵ = ϵM^(1/3.)
	x = reshape(0. -G.Δx/(G.L):G.Δx/G.L:1. +G.Δx/(G.L), (1, G.Nx+1))
	y = reshape(-1. -G.Δy/(G.L):G.Δy/G.L:1. +G.Δy/(G.L), (G.Ny+1, 1))

	ψ̂(x,y) = π*sin.(π*y) * (
		1 .- x - exp.(-x/(2*ϵ)) .* (
			cos.(√3*x/(2*ϵ)) .+
			(1. /√3)*sin.(√3*x/(2*ϵ))
			)
		.+ ϵ*exp.((x .- 1.)/ϵ)
	)

	u, v = (τ₀/ρ₀)/(β*G.L*H) .* diagnose_velocities(ψ̂(x, y), G)
	impose_no_flux!(u, v)

	return u, v
end

# ╔═╡ e59d869c-2a88-11eb-2511-5d5b4b380b80
md"""
##### Some simple initial temperature fields
"""

# ╔═╡ 0ae0bb70-2b8f-11eb-0104-93aa0e1c7a72
constantT(G; value) = zeros(G) .+ value

# ╔═╡ c4424838-12e2-11eb-25eb-058344b39c8b
linearT(G; value=50.0) = value*0.5*(1. .+[ -(y/G.L) for y in G.y[:, 1], x in G.x[1, :] ])

# ╔═╡ 3d12c114-2a0a-11eb-131e-d1a39b4f440b
function InitBox(G; value=50., nx=2, ny=2, xspan=false, yspan=false)
	T = zeros(G)
	T[G.Ny÷2-ny:G.Ny÷2+ny, G.Nx÷2-nx:G.Nx÷2+nx] .= value
	if xspan
		T[G.Ny÷2-ny:G.Ny÷2+ny, :] .= value
	end
	if yspan
		T[:, G.Nx÷2-nx:G.Nx÷2+nx] .= value
	end
	return T
end

# ╔═╡ 6f19cd80-2c06-11eb-278d-178c1590856f
# ocean_T_init = InitBox(default_grid; value=50);
ocean_T_init = InitBox(default_grid, value=50, xspan=true);
# ocean_T_init = linearT(default_grid);

# ╔═╡ 863a6330-2a08-11eb-3992-c3db439fb624
ocean_sim = let
	P = OceanModelParameters(κ=κ_ex)

	u, v = ocean_velocities
	model = OceanModel(default_grid, P, u*2. ^U_ex, v*2. ^U_ex)

	Δt = 12*60*60
	ClimateModelSimulation(model, copy(ocean_T_init), Δt)
end;

# ╔═╡ 6b3b6030-2066-11eb-3343-e19284638efb
plot_kernel(A) = heatmap(
	collect(A),
	color=:bluesreds, clims=(-maximum(abs.(A)), maximum(abs.(A))), colorbar=false,
	xticks=false, yticks=false, size=(30+30*size(A, 2), 30+30*size(A, 1)), xaxis=false, yaxis=false
) |> as_svg

# ╔═╡ 3dffa000-2db7-11eb-263b-57fa833d5785
md"""
👉 Some parameters have a physical meaning (`κ`, `u` and `v`), while other parameters control our numerical process. Choose two of these _numerical_ parameters, and describe their effect on the simulation's runtime and the simulation's accuracy.
"""

# ╔═╡ b952d290-2db7-11eb-3fa9-2bc8d77b9fd6
numerical_parameters_observation = md"""

Hello!
"""

# ╔═╡ 88c56350-2c08-11eb-14e9-77e71d749e6d
md"""
## **Exercise 2** - _Complexity_

In this class we have restricted ourself to small problems ($N_{t} < 100$ timesteps and $N_{x;\, y} < 30$ spatial grid-cells) so that they can be run interactively on an average computer. In state-of-the-art climate modelling however, the goal is to push the *numerical resolution* $N$ to be as large as possible (the *grid spacing* $\Delta t$ or $\Delta x$ as small as possible), to resolve physical processes that improve the realism of the simulation (see below).

"""

# ╔═╡ 014495d6-2cda-11eb-05d7-91e5a467647e
html""""""

# ╔═╡ d6a56496-2cda-11eb-3d54-d7141a49a446
md"""
Here, we provide a simple estimate of the *computational complexity* of climate models, which reveals a substantial challenge to the improvement of climate models.

Our climate model algorithm can be summarized by the recursive formula:

$T_{i,\, j}^{n+1} = T^{n}_{i,j} + \Delta t * \left( \text{tendencies} \right)$

for each time step ``n \in \left\{1, \dots, M \right\}``, and for each grid point ``i \in \left\{1, \dots, N_x \right\}, j \in \left\{1, \dots, N_y \right\}``.

Our goal is to simulate an ocean of fixed size (e.g. ``6000`` km), for a fixed amount of time (e.g. ``100`` years). By choosing smaller ``\Delta t``, ``\Delta x`` and ``\Delta y``, we get a more accurate simulation, but for the same _size_, it requires more steps. i.e.

$M = \mathcal{O}(\Delta t^{-1}) \qquad N_x = \mathcal{O}(\Delta x^{-1}) \qquad N_y = \mathcal{O}(\Delta y^{-1}).$

Now, the _total runtime_ of our simulation is proportional to the number of steps we need to take, which is ``M\cdot N_x \cdot N_y``. For a fixed aspect ratio $N_{y} = 2N_{x}$, we get

$\text{runtime} = \mathcal{O}(M)\mathcal{O}(2N_{x}^{2}) = \mathcal{O}(M)\mathcal{O}(N_{x}^{2}).$

#### Exercise 2.1

For constant ``M``, we want to verify that $\text{runtime} = \mathcal{O}(N_{x}^{2})$ holds for our numerical model.

👉 Write a function `model_runtime` that takes `N` as an argument, and sets up a model with grid of resolution `N`, and returns the runtime of a single `timestep!`.

"""

# ╔═╡ 126bffce-2d0b-11eb-2bfd-bb5d1ad1169b
function runtime(N)

	return missing
end

# ╔═╡ 923af680-2d0b-11eb-3f6a-db4bf29bb6a9
md"""
👉 Call your `runtime` function on a range of values for `N`, and use a plot to demonstrate that the predicted runtime complexity holds.
"""

# ╔═╡ af02d23e-2e93-11eb-3547-85d2aa07081b

# ╔═╡ a6811db2-2cdf-11eb-0aac-b1bf7b7d99eb
md"""
#### Exercise 2.2 - _The CFL condition on $\Delta t$_

In Exercise 1, look for the definition of `Δt`. It is currently set to `12*60*60` (12 hours).

👉 Double `Δt` and run the simulation again. You should see that it runs faster, great! Now, keep doubling `Δt` until you see something 'strange'. Describe what you see.

"""

# ╔═╡ 87de1c70-2d0c-11eb-2c22-f76eeca58f33
Δt_doubling_observations = md"""

Hi!

"""

# ╔═╡ 87e59680-2d0c-11eb-03c7-1d845ca6a1a5
md"""
What you experienced is a _numerical instability_ of the discretization method in our simulation. This is not caused by floating point errors -- it is a theoretical limitation of our method.

To ensure the stability of our finite-difference approximation for advection, heat should not be displaced more than one grid cell in a single timestep. Mathematically, we can ensure this by checking that the distance $L_{CFL} \equiv \max(|\vec{u}|) \Delta t$ is significantly less than the width $\Delta x = \Delta y$ of a single grid cell:

$L_{CFL} \equiv \max(|\vec{u}|) \Delta t \ll \Delta x$

or

$\Delta t \ll \frac{\Delta x}{\max(|\vec{u}|) },$

which is known as **the Courant-Freidrichs-Levy (CFL) condition**.

The exact meaning of ``\ll`` here depends on the simulation at hand, but in our case, it means that there is at least an order of magnitude difference:

$\Delta t < 0.1 \frac{\Delta x}{\max(|\vec{u}|) }.$

Given below is a function `CFL_advection` that takes a `ClimateModel` and computes the CFL value: ``{\Delta x} / {\max(|\vec{u}|) }``.
"""

# ╔═╡ ad7b7ed6-2a9c-11eb-06b7-0f5595167575
function CFL_advection(model::ClimateModel)
	model.grid.Δx / maximum(sqrt.(model.u.^2 + model.v.^2))
end

# ╔═╡ 7d3bf550-2e68-11eb-3526-cda9ff3f914e
ocean_sim.Δt, 0.1 * CFL_advection(ocean_sim.model)

# ╔═╡ 3e908bf0-2e94-11eb-28de-a3b1b7435492
md"""
👉 Using the interactive simulation of Exercise 1, verify that the CFL condition is (somewhat) true. Increase the magnitude of ``\vec{u}`` until you start to see numerical artefacts. Now, halve the value fo ``\Delta t``, and again, increase the magnitude of ``\vec{u}``. You should find that _halving ``\Delta t`` allows for twice the velocity magnitude_. The same link exists between ``\Delta t`` and ``\Delta x``.
"""

# ╔═╡ cb3e2990-2e67-11eb-2312-61395c479a15
md"""
The CFL inequality states that if we want to decrease the grid spacing $\Delta x$ (or increase the *resolution* $N_{x}$), we also have to decrease the timestep $\Delta t$ by the same factor. In other words, the timestep can not be thought of as fixed -- it depends on the spatial resolution: $\Delta t \equiv \Delta t_{0} \Delta x$. This means that ``M = \mathcal{O}(N_x)``.

Revisiting our complexity equation, we now have

$\text{runtime} = \mathcal{O}(M) \mathcal{O}(N_x^2) = \mathcal{O}(N_x^3).$

In other words, in a 2-D model, **2x the spatial resolution requires 8x the computational power**.
"""

# ╔═╡ 433a9c1e-2ce0-11eb-319c-e9c785b080ce
md"""
#### Exercise 2.3 - _Moore's Law_
In practice, state-of-the-art climate models are 3-D, not 2-D. It turns out that to preserve the aspect ratio of oceanic motions, the *vertical* grid resolution should also be increased $N_{z} \propto N_{x}$, such that in reality the computational complexity of climate models is:

$\text{runtime} = \mathcal{O}(N_{x}^4).$

This is the fundamental challenge of high-performance climate computing: to increase the resolution of the models by a factor of $2$, the model's run-time increases by a factor of $2^4 = 16$.

The figure below shows how the grid spacing of state-of-the-art climate models has decreased from $500$ km in 1990 (FAR) to $100$ km in the 2010s (AR4). In other words, grid resolution increased by a factor of $5$ in 20 years.
"""

# ╔═╡ 213f65ce-2ce1-11eb-19d6-5bf5c24d7ed7
html"""

"""

# ╔═╡ fced660c-2cd9-11eb-1737-0110789f429e
md"""
Moore's law is the observation that the number of transistors in a dense integrated circuit doubles about every two years. In the context of climate modelling, we can interpret this as meaning that the computational complexity allowed by our best high-performance computers $\mathcal{C}$ doubles every two years:

$\mathcal{C}(t) = \mathcal{C}(2020) * 2^{(t-2020)/2}$

Present-day simulations have a grid spacing of $\Delta x = 30$ km (or about $N_{x} = L/\Delta x \approx \frac{20000\text{ km}}{30\text{ km}} \approx 700$).

👉 By extrapolating Moore's law forward into the future, estimate how long it would take for $\Delta x$ to reach to the $500$ meter scale of clouds, one of the important climate processes ($N_{x} = L/\Delta x \approx 40 000$).

"""

# ╔═╡ 299d5540-2e6a-11eb-2698-05e889127454
cloud_resolution_possible_at = let

	missing
end

# ╔═╡ 545cf530-2b48-11eb-378c-3f8eeb89bcba
md"""
## **Exercise 3** - _Radiation_

In Homework 9, we used a **zero-dimensional (0-D)** Energy Balance Model (EBM) to understand how Earth's average radiative imbalance results in temperature changes:

$\begin{split}
C\frac{\partial T}{\partial t} =&\ \hphantom{+}
\frac{S(1 - \alpha)}{4}
&\qquad\text{(absorbed radiation)}
\\

&- (A - BT)
&\qquad\text{(outgoing radiation)}
\end{split}$

This week we will do the same, but now in **two-dimensions (2-D)**, where in addition to heat being added or removed from the system by radiation, heat can be *transported around the system* by oceanic **advection** and **diffusion** (see also Lectures 22 & 23 for 1-D and 2-D advection-diffusion). The governing equation for temperature $T(x,y,t)$ in our coupled climate model is:

$\begin{split}
\frac{\partial T}{\partial t} = &  \hphantom{+}
u(x,y) \frac{\partial T}{\partial x} + v(x,y) \frac{\partial T}{\partial y}
&\qquad\text{(advection)}
\\

& + \kappa \left( \frac{\partial^{2} T}{\partial x^{2}} + \frac{\partial^{2} T}{\partial y^{2}} \right)
&\qquad\text{(diffusion)}
\\

& + \frac{S(x,y)(1 - \alpha(T))}{4C}
&\qquad\text{(absorbed radiation)}
\\

& - \frac{(A - BT)}{C}.
&\qquad\text{(outgoing radiation)}
\end{split}$
"""

# ╔═╡ 705ddb90-2d8d-11eb-0c78-13eea6a2df38
md"""
##### Parameters

Below we define two new types: `RadiationOceanModel` and `RadiationOceanModelParameters`. Notice the similarities and differences with our original model. By making `RadiationOceanModel` also a subtype of `ClimateModel`, we will be able to re-use much of our original code.
"""

# ╔═╡ 57535c60-2b49-11eb-07cc-ffc5b4d1f13c
Base.@kwdef struct RadiationOceanModelParameters
	κ::Float64=4.e4

	C::Float64=51.0 * 60*60*24*365.25 # converted from [W*year/m^2/K] to [J/m^2/K]

	A::Float64=210
	B::Float64=-1.3

	S_mean::Float64 = 1380
	α0::Float64=0.3
	αi::Float64=0.55
	ΔT::Float64=2.0
end

# ╔═╡ 90e1aa00-2b48-11eb-1a2d-8701a3069e50
begin
	struct RadiationOceanModel <: ClimateModel
		grid::Grid
		params::RadiationOceanModelParameters

		u::Array{Float64, 2}
		v::Array{Float64, 2}
	end

	RadiationOceanModel(G::Grid, P::RadiationOceanModelParameters, u, v) =
		RadiationOceanModel(G, P, u, v)
	RadiationOceanModel(G::Grid, P::RadiationOceanModelParameters) =
		RadiationOceanModel(G, P, zeros(G), zeros(G))
	RadiationOceanModel(G::Grid) =
		RadiationOceanModel(G, RadiationOceanModelParameters(), zeros(G), zeros(G))
end;

# ╔═╡ e5b95760-2d98-11eb-0ea3-8bfcf07031d6
md"""

Notice that this struct has `Base.@kwdef` in front of its definition. This allows us to:
1. assign default values to the struct fields, directly inside the definition, and
2. automatically create an easier constructor function that takes _keyword arguments:_
"""

# ╔═╡ adf007c2-2d98-11eb-3004-dfe1b1303454
RadiationOceanModelParameters()

# ╔═╡ be456570-2d98-11eb-0c4e-cd92e0af728b
RadiationOceanModelParameters(S_mean=2000)

# ╔═╡ e80b0532-2b4b-11eb-26fa-cd09eca808bc
md"""
#### Exercise 3.1 - _Absorbed radiation_

The ocean absorbs solar radiation, increasing the temperature. Just like in our EBM model, we model the ocean as a surface with a _temperature-dependent albedo_: ``\alpha(T)``. A high albedo means that more sunlight is reflected, and less is absorbed. We model the albedo function as:
"""

# ╔═╡ 629454e0-2b48-11eb-2ff0-abed400c49f9
function α(T::Float64; α0, αi, ΔT)
	if T < -ΔT
		return αi
	elseif -ΔT <= T < ΔT
		return αi + (α0-αi)*(T+ΔT)/(2ΔT)
	elseif ΔT <= T
		return α0
	end
end

# ╔═╡ 287395d0-2dbb-11eb-3ca7-ddcea24a074f
md"""
An area of ocean below 0°C is covered in ice, which is more reflective, and therefore absorbs less solar radiation. In our EBM model, this _positive feedback_ leads to a bifurcation: under the same external conditions, the climate system has multiple equilibria.

In this week's two-dimensional model, the factor ``\alpha`` is also two-dimensional: instead of a global albedo, every grid cell has its own temperature, which determines its own albedo, $\alpha(T(x,y,t))$. We can now have an ocean with warm and cold regions, which absorb different amounts of radiation. The same positive feedback can have a _local_ effect.

Here is a second method for `α` that takes a 2D array `T` with the current ocean temperatures and a `RadiationOceanModel`, and returns the 2D array of albedos. We use the [dot operator](https://docs.julialang.org/en/v1/manual/mathematical-operations/#man-dot-operators) to apply `α` pointwise to `T`, also called _broadcasting_.
"""

# ╔═╡ d63c5fe0-2b49-11eb-07fd-a7ec98af3a89
function α(T::Array{Float64,2}, model::RadiationOceanModel)
	α.(T; α0=model.params.α0, αi=model.params.αi, ΔT=model.params.ΔT)
end

# ╔═╡ d9296db0-2dba-11eb-3bb5-9533a338dad8
let
	T = -10.0:30.0
	plot(
		T, α.(T; α0=0.3, αi=0.5, ΔT=2.0),
		ylim=(0,1),
		ylabel="α (albedo) [unitless]",
		xlabel="Temperature [°C]",
	)
end

# ╔═╡ 8d729390-2dbc-11eb-0628-f3ed9c9f5ffd
md"""
##### Solar insolation

Our rectangular grid represents the North Atlantic Ocean, stretching from the equator (latitude = 0°) to the North Pole (latitude = 90°) in the latitudinal direction (`y`), and from the east coast of North America to the west coast of Europe in the longitudinal direction (`x`). In reality, climate models have to explicitly deal with the curvature of the Earth when constructing their model grids. Here, we will just treat the North Atlantic Ocean as a rectangle with roughly the correct dimensions.

Just like the albedo, every grid cell will have a local amount of solar insolation. In our model, we use the **annual average** at the latitude of a grid cell $S(y)/4$. This is given by: ([_source_](http://www.atmos.albany.edu/facstaff/brose/classes/ATM623_Spring2015/Notes/Lectures/Lecture11%20--%20Insolation.html))

"""

# ╔═╡ 71f531ae-2dbf-11eb-1d0c-0758eb89bf1d
md"""
_Note that latitude is the horizontal axis in this graph, not the vertical._
"""

# ╔═╡ 0de643d0-2dbf-11eb-3a4c-538c176923f4
function y_to_lat(y::Real; grid::Grid)
	π/2 * 0.5 * (y / grid.L + 1.0)
end

# ╔═╡ 2ace4750-2dbe-11eb-0074-0f3a7a929176
function S_at(y::Float64; grid::Grid, S_mean::Float64)
	S_mean .* (1+0.5*cos(2 * y_to_lat(y; grid=grid)))
end

# ╔═╡ 5caa4172-2dbe-11eb-2d5a-f5fa621d21a8
let
	grid = Grid(50, 6000e3)
	params = RadiationOceanModelParameters()

	y = grid.y[:]
	λ = rad2deg.(y_to_lat.(y; grid=grid))
	S = S_at.(y; grid=grid, S_mean=params.S_mean)
	p = plot(
		λ, S/4,
		ylabel="Annual average solar insolation [W/m²]",
		xlabel="Latitude [°]",
		label=nothing,
		lw=3,
		ylim=(0,maximum(S/4)*1.05)
	)
	plot!(p,
		λ, fill(params.S_mean/4., size(y)),
		linestyle=:dash,
		label="Global mean S/4"
	)
end

# ╔═╡ 86a004ce-2dd5-11eb-1dca-5702d793ef39
md"""
👉 Write a method `absorbed_solar_radiation` that takes a 2D array `T` with the current ocean temperatures and a `RadiationOceanModel`, and returns the tendencies corresponding to absorbed radiation. This is the analogue of `advect` and `diffuse`.
"""

# ╔═╡ f24e8570-2e6c-11eb-2c21-d319af7cba81
function absorbed_solar_radiation(T::Array{Float64,2}, model::RadiationOceanModel)

	return missing
end

# ╔═╡ de7456c0-2b4b-11eb-13c8-01b196821de4
md"""
#### Exercise 3.2 - _Outgoing radiation_

Just like in our EBM from before, when our ocean heats up by absorbing solar radiation, it also _emits radiation back to space_. This **outgoing thermal radiation** is what allows the ocean to eventually come to an equilibrium. The difference here is that each individual grid cell of our model radiatives according to it's local temperature $T_{i,\,j}$.
"""

# ╔═╡ 6745f610-2b48-11eb-2f6c-79e0009dc9c3
function outgoing_thermal_radiation(T; C, A, B)
	(A .- B .* (T)) ./ C
end

# ╔═╡ 2274f6b0-2dc5-11eb-10a1-e980bd461ea0
md"""
👉 Write a method `outgoing_termal_radiation` that takes a 2D array `T` with the current ocean temperatures and a `RadiationOceanModel`, and returns the tendencies corresponding to outgoing radiation. This is the analogue of `advect` and `diffuse`.
"""

# ╔═╡ fc55f710-2e6c-11eb-3cea-cfc00c02fc26
function outgoing_thermal_radiation(T::Array{Float64,2}, model::RadiationOceanModel)

	return missing
end

# ╔═╡ 6c20ca1e-2b48-11eb-1c3c-418118408c4c
let
	params = RadiationOceanModelParameters()
	model = RadiationOceanModel(default_grid, params)
	plot(
		-10:40, outgoing_thermal_radiation(-10:40, A=params.A, B=params.B, C=params.C),
		xlabel="Temperature",
		ylabel="Outgoing radiation",
		label=nothing,
		size=(500,250)
	)
end

# ╔═╡ fe492480-2b4b-11eb-050e-9b9b2e2bf50f
md"""
#### Exercise 3.3 - _Running the model_

Let's define a new `timestep!` method for our new model. This is exactly the same as our advection-diffusion model, with the addition of the two radiation tendencies.
"""

# ╔═╡ 068795ee-2b4c-11eb-3e58-353eb8978c1c
function timestep!(sim::ClimateModelSimulation{RadiationOceanModel})
	update_ghostcells!(sim.T)
	tendencies =
		advect(sim.T, sim.model) .+
		diffuse(sim.T, sim.model) .+
		absorbed_solar_radiation(sim.T, sim.model) .-
		outgoing_thermal_radiation(sim.T, sim.model)

	sim.T .+= sim.Δt*tendencies

	sim.iteration += 1
end

# ╔═╡ ad95c4e0-2b4a-11eb-3584-dda89970ffdf
md"""
We can now simulate our radiation ocean model, reusing much of the code from our advection-diffusion simulation.
"""

# ╔═╡ b059c6e0-2b4a-11eb-216a-39bb43c7b423
radiation_sim = let
	grid = Grid(10, 6.e6)
	# you can specify non-default parameters like so:
	# params = RadiationOceanModelParameters(S_mean=1500, A=210, κ=2e4)
	params = RadiationOceanModelParameters()

	u, v = DoubleGyre(grid)

	T_init_value = 10
	T_init = constantT(grid; value=T_init_value)

	model = RadiationOceanModel(grid, params, u, v)
	Δt = 400*60*60

	ClimateModelSimulation(model, copy(T_init), Δt)
end

# ╔═╡ 5fd346d0-2b4d-11eb-066b-9ba9c9d97613
@bind go_radiation Clock(.1)

# ╔═╡ 6fc5b760-2e97-11eb-1d7f-0d666b0a41d5
md"""
👉 Play around with the simulation to find the effect of each parameter. In particular, discover the effect of the solar insolation, `S_mean`, the initial temperatures, `T_init`, the liquid and ice albedos: `α0` and `αi`, and the amount of emitted heat at 0°C, `A`.
"""

# ╔═╡ 5a755e00-2e98-11eb-0f83-997a60409484
md"""
#### Exercise 3.4 - _Stable states_

So far, we are able to set up a model and run it interactively. You see that the model quickly goes from the initial temperatures to a _stable state_: a state with balanced energy (radiation out, radiation in). Changing the initial state slightly will probably result in the same stable state.

But let's see what happens when we initialize with extremely high or low initial temperatures...

👉 For ``S=1380`` (present-day value) and default parameters, does `T_init_value=-50` give a different result than `T_init_value=+50`? What about `T_init_value=+55`? By trying various values for `T_init_value`, **how many stable states do you find?**
"""

# ╔═╡ 5294aad0-2d15-11eb-091d-59d7517c4dc2
S_1380_stable_states = md"""

I found ...

"""

# ╔═╡ abd2475e-2d15-11eb-26dc-05253cf65232
md"""
👉 Answer the same question for ``S=1000`` and ``S=2000``.
"""

# ╔═╡ bdd86250-2d15-11eb-0b62-7903ca714312
S_1000_stable_states = md"""

I found ...

"""

# ╔═╡ bed2c7e0-2d15-11eb-14e3-93f5d1b6f3a1
S_2000_stable_states = md"""

I found ...

"""

# ╔═╡ b5703af0-2e98-11eb-1e8c-3d2b51bd9995
md"""
#### Exercise 3.5

That's right, we have found a _bifurcation_! Under the right conditions, there are multiple stable states. But under different conditions, there is only one stable state.

**Hypothesis:** The multiple stable states at ``S = 1380`` are caused by the feedback effect of the ice albedo. A cold ocean reflects more heat and stays cold, a warm ocean absorbs more heat and stays warm.

👉 Find a way to confirm this hypothesis by changing the model parameters in the interactive model. Describe your findings.
"""

# ╔═╡ d9141ca0-2e99-11eb-3ef6-8591c16bb546
albedo_hypothesis_description = md"""

Bonjour !
"""

# ╔═╡ c40b7360-2d15-11eb-0558-d95d615f9b9b
md"""
👉 Why is the number of stable states different for a lower or higher value of ``S``?
"""

# ╔═╡ 02767460-2d16-11eb-2074-07657f84e22d
num_stable_states_answer = md"""

Hi! 🍪

"""

# ╔═╡ 127bcb0e-2c0a-11eb-23df-a75767910fcb
md"""
## **Exercise 4** - _Bifurcation diagram_

So far, we are able to set up a model and run it interactively, and we discovered that by changing the initial value of `S_mean`, we find a different number of stable states.

In this final exercise, we will generate a visualization to help us understand the relationship between `S_mean` and the number of stable states. Instead of running a single model interactively, we write a function that takes the model parameters as input, runs the model until equilibrium, and returns the final mean temperature. We will run this high-level function for various initial values, generating a single graph: the _bifurcation diagram_.

#### Exercise 4.1 - _Equilibrium temperature_

👉 Write a function `eq_T` that takes two argments, `S` and `T_init_value`, that sets up a radiation ocean model with `S` as `S_mean`, and with `T_init_value` as the constant initial temperature. Run the model until you have reached equilibrium (approximately), and return the average temperature.
"""

# ╔═╡ 0d197fe0-2e6d-11eb-2346-2daf4e80a9a7
function eq_T(S, T_init_value)

	return missing
end

# ╔═╡ f70f52f0-2e9a-11eb-1bab-13c3b7ad3ca4
md"""
👉 Use the function `eq_T` to verify your answer to Exercise 3.4.
"""

# ╔═╡ 59ecd040-2e9c-11eb-05e3-0bc96e9cddec

# ╔═╡ 2495e330-2c0a-11eb-3a10-530f8b87a4eb
md"""
#### Exercise 4.2

👉 Make a bifurcation diagram. For a couple of pairs `(S,T_init)`, calculate the equilibrium temperature. Create a scatter plot with ``S`` on the horizontal axis, and ``T_{\text{equilibrium}}`` on the vertical.

> This calculation can take quite some time. Some tips:
> 1. Start out with a small amount of points.
> 1. Run the `eq_T` calculations in one cell, and store the result as a vector. Generate the scatter plot in a different cell.
> 1. You can replace a `map` with `ThreadsX.map` to run it in parallel across multiple CPU cores. More on this [below](#threadsx).
"""

# ╔═╡ 59da0470-2b8f-11eb-098c-993effcedecf
# you can change me!
bifurcation_ST = [(S,T) for S in [1000, 1380, 2000] for T in [-50, 0, 50]]

# ╔═╡ 9eacf3d0-2e9d-11eb-3c19-1dcfadccf18d

# ╔═╡ 9e653c70-2e9d-11eb-0af5-25dc140d5824

# ╔═╡ 92ae22de-2d88-11eb-1d03-2977c539ba23
md"""
$(html"")
##### Parallelize a `map`

You use `map` to apply a function to each element of a vector. Why not use a `for` loop? One nice property of `map` code is that you only describe the operation for each element, not the order to run the operations in. This means that it can be _easily parallelized_ by the computer. For example, on a computer with 4 cores, the computation `map(sqrt, 1:100)` can be parallelized by handling `1:25` on the first core, `26:50` on the second, etc., at the same time.

In Julia, many functional primitives (`map`, `filter`, `sum`, `maximum`, `all`, and more) have an automatically multithreaded version, in the `ThreadsX.jl` package. As a demo, compare the two cells below. (You can see the runtime in the bottom right of a cell.) You might need to run the cells a second time, the first time includes Julia's compiler doing its thing.
"""

# ╔═╡ 547d92d0-2d88-11eb-18b5-0fc468ae0026
map(1:8) do i
	sleep(.1) # to simulate an expensive computation
	i ^ 2
end

# ╔═╡ 60bbba90-2d88-11eb-1616-87d6e15c0795
ThreadsX.map(1:8) do i
	sleep(.1) # to simulate an expensive computation
	i ^ 2
end

# ╔═╡ 57b6e7d0-2c07-11eb-16c1-0d058a34c7ee
md"""
## **Exercise XX:** _Lecture transcript_
_(MIT students only)_

Please see the link for the transcript document on [Canvas](https://canvas.mit.edu/courses/5637).
We want each of you to correct about 500 lines, but don’t spend more than 20 minutes on it.
See the the beginning of the document for more instructions.

👉 Please mention the name of the video(s) and the line ranges you edited:
"""

# ╔═╡ 57c0d2e0-2c07-11eb-1091-15fec09c4e8b
lines_i_edited = md"""
Abstraction, lines 1-219; Array Basics, lines 1-137; Course Intro, lines 1-144 (_for example_)
"""

# ╔═╡ 57cdcb30-2c07-11eb-39b2-2f225acf589d
if student.name == "Jazzy Doe" || student.kerberos_id == "jazz"
	md"""
	!!! danger "Before you submit"
	    Remember to fill in your **name** and **Kerberos ID** at the top of this notebook.
	"""
end

# ╔═╡ a04d3dee-2a9c-11eb-040e-7bd2facb2eaa
md"""
# Appendix
"""

# ╔═╡ 0a6e6ad2-2c01-11eb-3151-3d58bc09bc69
ice_gradient = PlotUtils.ContinuousColorGradient([
		RGB(0.95, 0.95, 1.0), # sliver of white
		RGB(0.05, 0.0, 0.3),
		RGB(0.1, 0.05, 0.4),
		RGB(0.4, 0.4, 0.5),
		RGB(0.95, 0.7, 0.4),
		RGB(1.0, 0.9, 0.3)
	], [0.0, 0.001, 0.2, 0.5, 0.8, 1.0])

# ╔═╡ c0e46442-27fb-11eb-2c94-15edbda3f84d
function plot_state(sim::ClimateModelSimulation; clims=(-1.1,1.1),
		show_quiver=true, show_anomaly=false, IC=nothing)

	model = sim.model
	grid = sim.model.grid

	p = plot(;
		xlabel="longitudinal distance [km]", ylabel="latitudinal distance [km]",
		clabel="Temperature",
		yticks=( (-grid.L:1000e3:grid.L), Int64.(1e-3*(-grid.L:1000e3:grid.L)) ),
		xticks=( (0:1000e3:grid.L), Int64.(1e-3*(0:1000e3:grid.L)) ),
		xlims=(0., grid.L), ylims=(-grid.L, grid.L),
		)

	X = repeat(grid.x, grid.Ny, 1)
	Y = repeat(grid.y, 1, grid.Nx)
	if show_anomaly
		arrow_col = :black
		maxdiff = maximum(abs.(sim.T .- IC))
		heatmap!(p, grid.x[:], grid.y[:], sim.T .- IC, clims=(-1.1, 1.1),
			color=:balance, colorbar_title="Temperature anomaly [°C]", linewidth=0.,
			size=(400,530)
		)
	else
		arrow_col = :white
		heatmap!(p, grid.x[:], grid.y[:], sim.T,
			color=ice_gradient, levels=clims[1]:(clims[2]-clims[1])/21.:clims[2],
			colorbar_title="Temperature [°C]", clims=clims,
			linewidth=0., size=(400,520)
		)
	end

	annotate!(p,
		50e3, 6170e3,
		text(
			string("t = ", Int64(round(sim.iteration*sim.Δt/(60*60*24))), " days"),
			color=:black, :left, 9
		)
	)
	annotate!(p,
		3000e3, 6170e3,
		text(
			"mean(T) = $(round(mean(sim.T), digits=1)) °C",
			color=:black, :left, 9
		)
	)

	if show_quiver
		Nq = grid.N ÷ 5
		quiver!(p,
			X[(Nq+1)÷2:Nq:end], Y[(Nq+1)÷2:Nq:end],
			quiver=grid.L*4 .*(model.u[(Nq+1)÷2:Nq:end], model.v[(Nq+1)÷2:Nq:end]),
			color=arrow_col, alpha=0.7
		)
	end

	as_png(p)
end

# ╔═╡ 3b24e1b0-2b46-11eb-383b-c57cbf3e68f1
let
	go_ex
	if ocean_sim.iteration == 0
		timestep!(ocean_sim)
	else
		for i in 1:50
			timestep!(ocean_sim)
		end
	end
	plot_state(ocean_sim, clims=(-10, 40), show_quiver=show_quiver, show_anomaly=show_anomaly, IC=ocean_T_init)
end

# ╔═╡ 6568b850-2b4d-11eb-02e9-696654ac2d37
let
	go_radiation
	for i in 1:100
		timestep!(radiation_sim)
	end
	plot_state(radiation_sim; clims=(-0,50))
end

# ╔═╡ 57e264a0-2c07-11eb-0e31-2b8fa01be2d1
md"## Function library

Just some helper functions used in the notebook."

# ╔═╡ 57f57770-2c07-11eb-1720-cf00aa7f597b
hint(text) = Markdown.MD(Markdown.Admonition("hint", "Hint", [text]))

# ╔═╡ 171c6880-2d0b-11eb-0180-454f2876cf51
hint(md"""
To measure the runtime of a Julia command, you can do:
```julia
runtime = @elapsed do_something()
```

To get a more precise benchmark, you can average a fixed number of runs, by putting `@elapsed` in front of a `for` loop, for example.
""")

# ╔═╡ ec39a792-2bf7-11eb-11e5-515b39f1adf6
md"""
**How do you determine that the model has reached equilibrium?**

The simplest way is to use the interactive simulation above to find a fixed time/number of steps, for which any initial value has reached equilibrium.

Using a long time period is more accurate, but it means that the runtime will be long. If this is a problem, you can use a dynamic stopping condition instead. For example, you can stop the simulation early when the total incoming and total outgoing radiation are roughly equal.
""" |> hint

# ╔═╡ 590c50c0-2e9b-11eb-2bb2-cf35f65a447e
md"""
Create an array of values for `T_init_value` (not too many!), and run `eq_T` for each. Find clusters in the results.

This is a good opportunity to check whether your implementation of `eq_T` is correct! Use the interactive simulation to check the results manually.
""" |> hint

# ╔═╡ 58094d90-2c07-11eb-2987-15c068fefd8f
almost(text) = Markdown.MD(Markdown.Admonition("warning", "Almost there!", [text]))

# ╔═╡ 581b9d10-2c07-11eb-1e60-c753aa19f4c3
still_missing(text=md"Replace `missing` with your answer.") = Markdown.MD(Markdown.Admonition("warning", "Here we go!", [text]))

# ╔═╡ 582dc580-2c07-11eb-37e3-c32590a0c325
keep_working(text=md"The answer is not quite right.") = Markdown.MD(Markdown.Admonition("danger", "Keep working on it!", [text]))

# ╔═╡ 58403c10-2c07-11eb-1f4b-f9ecb741d881
yays = [md"Fantastic!", md"Splendid!", md"Great!", md"Yay ❤", md"Great! 🎉", md"Well done!", md"Keep it up!", md"Good job!", md"Awesome!", md"You got the right answer!", md"Let's move on to the next section."]

# ╔═╡ 5853eb20-2c07-11eb-18bf-c14ed22ce153
correct(text=rand(yays)) = Markdown.MD(Markdown.Admonition("correct", "Got it!", [text]))

# ╔═╡ 5867e850-2c07-11eb-17d5-9dac155d381b
not_defined(variable_name) = Markdown.MD(Markdown.Admonition("danger", "Oopsie!", [md"Make sure that you define a variable called **$(Markdown.Code(string(variable_name)))**"]))

# ╔═╡ 587b9760-2c07-11eb-17ff-b9e950aa04ac
todo(text) = HTML("""

TODO
$(repr(MIME"text/html"(), text))
""")

# ╔═╡ Cell order:
# ╟─67c3dcc0-2c05-11eb-3a84-9dfea24f95a8
# ╟─6a4641e0-2c05-11eb-3430-6f14650c2ad3
# ╟─621230b0-2c05-11eb-2a98-5bd1d7be9038
# ╠═6cb238d0-2c05-11eb-221e-d5df4c479302
# ╟─70077e50-2c05-11eb-3d83-732b4b780d04
# ╠═9c8a7e5a-12dd-11eb-1b99-cd1d52aefa1d
# ╟─ed741ec6-1f75-11eb-03be-ad6284abaab8
# ╟─68c01d90-2cf6-11eb-0771-7b3c6db89ecb
# ╟─295af330-2cf8-11eb-1606-437e8f3c43fd
# ╟─c33ebe40-2cf9-11eb-384c-432dc70497b0
# ╟─83ad05a0-2cfb-11eb-1467-e1196985519a
# ╠═f4c884fc-2a97-11eb-1ba9-01bf579f8b43
# ╠═1e8d37ee-2a97-11eb-1d45-6b426b25d4eb
# ╠═682f2530-2a97-11eb-3ee6-99a7c79b3767
# ╠═ee6716c8-2a95-11eb-3a00-319ee69dd37f
# ╠═b629d89a-2a95-11eb-2f27-3dfa45789be4
# ╠═a8d8f8d2-2cfa-11eb-3c3e-d54f7b32e4a2
# ╟─2beb6ec0-2dcc-11eb-1768-0b8e4fba1597
# ╟─13eb3966-2a9a-11eb-086c-05510a3f5b80
# ╠═cd2ee4ca-2a06-11eb-0e61-e9a2ecf72bd6
# ╠═e7f563f0-2d04-11eb-036d-992da68470a6
# ╟─39404240-2cfe-11eb-2e3c-710e37f8cd4b
# ╠═0d63e6b2-2b49-11eb-3413-43977d299d90
# ╠═c9171c56-2dd8-11eb-189b-95d964a9724a
# ╠═d3796644-2a05-11eb-11b8-87b6e8c311f9
# ╠═5f5e4120-2cfe-11eb-1fa7-99fdd734f7a7
# ╟─74aa7512-2a9c-11eb-118c-c7a5b60eac1b
# ╠═f92086c4-2a74-11eb-3c72-a1096667183b
# ╠═81bb6a4a-2a9c-11eb-38bb-f7701c79afa2
# ╠═7caca2fa-2a9a-11eb-373f-156a459a1637
# ╟─31cb0c2c-2a9a-11eb-10ba-d90a00d8e03a
# ╠═9841ff20-2c06-11eb-3c4c-c34e465e1594
# ╟─31cb7aae-2d04-11eb-30cb-a365a6a4aa6b
# ╠═1dd3fc70-2c06-11eb-27fe-f325ca208504
# ╟─50e89130-2d04-11eb-1c8e-a34775aec40c
# ╠═6f19cd80-2c06-11eb-278d-178c1590856f
# ╟─c7736640-2d04-11eb-1108-59a7446e244d
# ╠═863a6330-2a08-11eb-3992-c3db439fb624
# ╟─981ef38a-2a8b-11eb-08be-b94be2924366
# ╟─d042d25a-2a62-11eb-33fe-65494bb2fad5
# ╟─6dbc3d34-2a89-11eb-2c80-75459a8e237a
# ╟─c20b0e00-2a8a-11eb-045d-9db88411746f
# ╟─933d42fa-2a67-11eb-07de-61cab7567d7d
# ╟─c9ea0f72-2a67-11eb-20ba-376ca9c8014f
# ╟─3b24e1b0-2b46-11eb-383b-c57cbf3e68f1
# ╟─c0298712-2a88-11eb-09af-bf2c39167aa6
# ╟─e2e4cfac-2a63-11eb-1b7f-9d8d5d304b43
# ╟─e3ee80c0-12dd-11eb-110a-c336bb978c51
# ╟─df706ebc-2a63-11eb-0b09-fd9f151cb5a8
# ╟─bb084ace-12e2-11eb-2dfc-111e90eabfdd
# ╟─ecaab27e-2a16-11eb-0e99-87c91e659cf3
# ╟─e59d869c-2a88-11eb-2511-5d5b4b380b80
# ╟─0ae0bb70-2b8f-11eb-0104-93aa0e1c7a72
# ╟─c4424838-12e2-11eb-25eb-058344b39c8b
# ╟─3d12c114-2a0a-11eb-131e-d1a39b4f440b
# ╟─6b3b6030-2066-11eb-3343-e19284638efb
# ╟─3dffa000-2db7-11eb-263b-57fa833d5785
# ╠═b952d290-2db7-11eb-3fa9-2bc8d77b9fd6
# ╟─88c56350-2c08-11eb-14e9-77e71d749e6d
# ╟─014495d6-2cda-11eb-05d7-91e5a467647e
# ╟─d6a56496-2cda-11eb-3d54-d7141a49a446
# ╠═126bffce-2d0b-11eb-2bfd-bb5d1ad1169b
# ╟─171c6880-2d0b-11eb-0180-454f2876cf51
# ╟─923af680-2d0b-11eb-3f6a-db4bf29bb6a9
# ╠═af02d23e-2e93-11eb-3547-85d2aa07081b
# ╟─a6811db2-2cdf-11eb-0aac-b1bf7b7d99eb
# ╠═87de1c70-2d0c-11eb-2c22-f76eeca58f33
# ╟─87e59680-2d0c-11eb-03c7-1d845ca6a1a5
# ╠═ad7b7ed6-2a9c-11eb-06b7-0f5595167575
# ╠═7d3bf550-2e68-11eb-3526-cda9ff3f914e
# ╟─3e908bf0-2e94-11eb-28de-a3b1b7435492
# ╟─cb3e2990-2e67-11eb-2312-61395c479a15
# ╟─433a9c1e-2ce0-11eb-319c-e9c785b080ce
# ╟─213f65ce-2ce1-11eb-19d6-5bf5c24d7ed7
# ╟─fced660c-2cd9-11eb-1737-0110789f429e
# ╠═299d5540-2e6a-11eb-2698-05e889127454
# ╟─545cf530-2b48-11eb-378c-3f8eeb89bcba
# ╟─705ddb90-2d8d-11eb-0c78-13eea6a2df38
# ╠═90e1aa00-2b48-11eb-1a2d-8701a3069e50
# ╠═57535c60-2b49-11eb-07cc-ffc5b4d1f13c
# ╟─e5b95760-2d98-11eb-0ea3-8bfcf07031d6
# ╠═adf007c2-2d98-11eb-3004-dfe1b1303454
# ╠═be456570-2d98-11eb-0c4e-cd92e0af728b
# ╟─e80b0532-2b4b-11eb-26fa-cd09eca808bc
# ╟─d9296db0-2dba-11eb-3bb5-9533a338dad8
# ╠═629454e0-2b48-11eb-2ff0-abed400c49f9
# ╟─287395d0-2dbb-11eb-3ca7-ddcea24a074f
# ╠═d63c5fe0-2b49-11eb-07fd-a7ec98af3a89
# ╟─8d729390-2dbc-11eb-0628-f3ed9c9f5ffd
# ╠═2ace4750-2dbe-11eb-0074-0f3a7a929176
# ╟─5caa4172-2dbe-11eb-2d5a-f5fa621d21a8
# ╟─71f531ae-2dbf-11eb-1d0c-0758eb89bf1d
# ╟─0de643d0-2dbf-11eb-3a4c-538c176923f4
# ╟─86a004ce-2dd5-11eb-1dca-5702d793ef39
# ╠═f24e8570-2e6c-11eb-2c21-d319af7cba81
# ╟─de7456c0-2b4b-11eb-13c8-01b196821de4
# ╠═6745f610-2b48-11eb-2f6c-79e0009dc9c3
# ╟─6c20ca1e-2b48-11eb-1c3c-418118408c4c
# ╟─2274f6b0-2dc5-11eb-10a1-e980bd461ea0
# ╠═fc55f710-2e6c-11eb-3cea-cfc00c02fc26
# ╟─fe492480-2b4b-11eb-050e-9b9b2e2bf50f
# ╠═068795ee-2b4c-11eb-3e58-353eb8978c1c
# ╟─ad95c4e0-2b4a-11eb-3584-dda89970ffdf
# ╠═b059c6e0-2b4a-11eb-216a-39bb43c7b423
# ╟─5fd346d0-2b4d-11eb-066b-9ba9c9d97613
# ╟─6568b850-2b4d-11eb-02e9-696654ac2d37
# ╟─6fc5b760-2e97-11eb-1d7f-0d666b0a41d5
# ╟─5a755e00-2e98-11eb-0f83-997a60409484
# ╠═5294aad0-2d15-11eb-091d-59d7517c4dc2
# ╟─abd2475e-2d15-11eb-26dc-05253cf65232
# ╠═bdd86250-2d15-11eb-0b62-7903ca714312
# ╠═bed2c7e0-2d15-11eb-14e3-93f5d1b6f3a1
# ╟─b5703af0-2e98-11eb-1e8c-3d2b51bd9995
# ╠═d9141ca0-2e99-11eb-3ef6-8591c16bb546
# ╟─c40b7360-2d15-11eb-0558-d95d615f9b9b
# ╠═02767460-2d16-11eb-2074-07657f84e22d
# ╟─127bcb0e-2c0a-11eb-23df-a75767910fcb
# ╠═0d197fe0-2e6d-11eb-2346-2daf4e80a9a7
# ╟─ec39a792-2bf7-11eb-11e5-515b39f1adf6
# ╟─f70f52f0-2e9a-11eb-1bab-13c3b7ad3ca4
# ╠═59ecd040-2e9c-11eb-05e3-0bc96e9cddec
# ╟─590c50c0-2e9b-11eb-2bb2-cf35f65a447e
# ╟─2495e330-2c0a-11eb-3a10-530f8b87a4eb
# ╠═59da0470-2b8f-11eb-098c-993effcedecf
# ╠═9eacf3d0-2e9d-11eb-3c19-1dcfadccf18d
# ╠═9e653c70-2e9d-11eb-0af5-25dc140d5824
# ╟─92ae22de-2d88-11eb-1d03-2977c539ba23
# ╠═547d92d0-2d88-11eb-18b5-0fc468ae0026
# ╠═60bbba90-2d88-11eb-1616-87d6e15c0795
# ╟─57b6e7d0-2c07-11eb-16c1-0d058a34c7ee
# ╠═57c0d2e0-2c07-11eb-1091-15fec09c4e8b
# ╟─57cdcb30-2c07-11eb-39b2-2f225acf589d
# ╟─a04d3dee-2a9c-11eb-040e-7bd2facb2eaa
# ╠═c0e46442-27fb-11eb-2c94-15edbda3f84d
# ╠═0a6e6ad2-2c01-11eb-3151-3d58bc09bc69
# ╟─57e264a0-2c07-11eb-0e31-2b8fa01be2d1
# ╟─57f57770-2c07-11eb-1720-cf00aa7f597b
# ╟─58094d90-2c07-11eb-2987-15c068fefd8f
# ╟─581b9d10-2c07-11eb-1e60-c753aa19f4c3
# ╟─582dc580-2c07-11eb-37e3-c32590a0c325
# ╟─58403c10-2c07-11eb-1f4b-f9ecb741d881
# ╟─5853eb20-2c07-11eb-18bf-c14ed22ce153
# ╟─5867e850-2c07-11eb-17d5-9dac155d381b
# ╟─587b9760-2c07-11eb-17ff-b9e950aa04ac


### A Pluto.jl notebook ###
# v0.14.5

using Markdown
using InteractiveUtils

# ╔═╡ 2c554897-e486-418f-9cd4-a3bb3f00fc31
using Pkg, DrWatson

# ╔═╡ 19cc5ec6-f7cb-4736-98c3-498d91eb2414
begin
	@quickactivate "NumericalMethodsforEngineers"
	using BarycentricInterpolation
end

# ╔═╡ 53285b2c-3704-4adc-9bdd-9dea619b5140
md" ## BarycentricInterpolation.jl"

# ╔═╡ 74272021-5f45-4161-bea4-8386697de280
begin
	x = [1.0, 3.0, 6.0, 5.0]
	y = [1.0, 5.0, 10.0, 9.0]
	xi = [2.0, 4.5]
end

# ╔═╡ 587d934e-161e-403c-831b-a61f313f5ecc
indxs = sortperm(x)

# ╔═╡ 7ba7dd56-4ee9-4c5c-97a8-5f99a9ba507e
p = ArbitraryPolynomial(x[indxs]);

# ╔═╡ 880d1ae8-5a5a-463a-8e35-a73bdc40208a
interpolate(p, y[indxs], xi)

# ╔═╡ c2fcf437-bd86-4b24-8c77-29702cf5373d
md" ## End of BarycentricInterpolation.jl"

# ╔═╡ Cell order:
# ╟─53285b2c-3704-4adc-9bdd-9dea619b5140
# ╠═2c554897-e486-418f-9cd4-a3bb3f00fc31
# ╠═19cc5ec6-f7cb-4736-98c3-498d91eb2414
# ╠═74272021-5f45-4161-bea4-8386697de280
# ╠═587d934e-161e-403c-831b-a61f313f5ecc
# ╠═7ba7dd56-4ee9-4c5c-97a8-5f99a9ba507e
# ╠═880d1ae8-5a5a-463a-8e35-a73bdc40208a
# ╟─c2fcf437-bd86-4b24-8c77-29702cf5373d


Z = Strang(1)
@test Matrix(Z) == reshape([2.0],(1,1))

n = rand(1:10)
Z = Strang(n)

for i in 1:n, j in 1:n
    i==j && @test Z[i,j] == 2
    abs(i-j)==1 && @test Z[i,j] == -1
    abs(i-j)>1 && @test Z[i,j] == 0
end

A = Strang(10)
u = ones(10)
@test A*u == [1.0;zeros(8);1.0]

#Matvec product
b = randn(n)
@test Z*b ≈ Matrix(Z)*b

m = rand(1:10)
A = randn(m, n)
@test A*Z ≈ A*Matrix(Z)


"""
    AbstractLinearSolver

Abstract type for solvers of interval linear systems.
"""
abstract type AbstractLinearSolver end

"""
    AbstractDirectSolver <: AbstractLinearSolver

Abstract type for direct solvers of interval linear systems, such as Gaussian elimination
and Hansen-Bliek-Rohn.
"""
abstract type AbstractDirectSolver <: AbstractLinearSolver end

"""
    AbstractIterativeSolver <: AbstractLinearSolver

Abstract type for iterative solvers of interval linear systems, such as Jacobi or
Gauss-Seidel.
"""
abstract type AbstractIterativeSolver <: AbstractLinearSolver end

"""
    HansenBliekRohn <: AbstractDirectSolver

Type for the `HansenBliekRohn` solver of the square interval linear system ``Ax=b``.
For more details see section 5.6.2 of [[HOR19]](@ref)

### Notes

- Hansen-Bliek-Rohn works with H-matrices without precondition and with strongly regular
  matrices using [`InverseMidpoint`](@ref) precondition
- If the midpoint of ``A`` is a diagonal matrix, then the algorithm returns the exact hull.
- An object of type Hansen-Bliek-Rohn is a callable function with method

        (hbr::HansenBliekRohn)(A::AbstractMatrix{T},
                               b::AbstractVector{T}) where {T<:Interval}

### Examples

```jldoctest
julia> A = [2..4 -1..1;-1..1 2..4]
2×2 Matrix{Interval{Float64}}:
  [2, 4]  [-1, 1]
 [-1, 1]   [2, 4]

julia> b = [-2..2, -1..1]
2-element Vector{Interval{Float64}}:
 [-2, 2]
 [-1, 1]

julia> hbr = HansenBliekRohn()
HansenBliekRohn linear solver

julia> hbr(A, b)
2-element Vector{Interval{Float64}}:
 [-1.66667, 1.66667]
 [-1.33334, 1.33334]
```
"""
struct HansenBliekRohn <: AbstractDirectSolver end

function (hbr::HansenBliekRohn)(A::AbstractMatrix{T},
                                b::AbstractVector{T}) where {T<:Interval}
    n = length(b)
    compA = comparison_matrix(A)
    compA_inv, cert = epsilon_inflation(compA, Diagonal(ones(n)))

    cert || @warn "Could not find a verified enclosure of ⟨A⟩⁻¹"

    all(sum(compA_inv; dims=2) .> 0) || throw(ArgumentError("applying Hanben-Bliek-Rohn to a non-H-matrix."))
    u = compA_inv * mag.(b)
    d = diag(compA_inv)

    _α = sup.(diag(compA) .- 1 ./ d)
    α = Interval.(-_α, _α)

    _β = @. sup(u/d - mag(b))
    β = Interval.(-_β, _β)

    return (b .+ β) ./ (diag(A) .+ α)

end

"""
    GaussianElimination <: AbstractDirectSolver

Type for the Gaussian elimination solver of the square interval linear system ``Ax=b``.
For more details see section 5.6.1 of [[HOR19]](@ref)

### Notes

- An object of type `GaussianElimination` is a callable function with method

        (ge::GaussianElimination)(A::AbstractMatrix{T},
                                  b::AbstractVector{T}) where {T<:Interval}

### Examples

```jldoctest
julia> A = [2..4 -1..1;-1..1 2..4]
2×2 Matrix{Interval{Float64}}:
  [2, 4]  [-1, 1]
 [-1, 1]   [2, 4]

julia> b = [-2..2, -1..1]
2-element Vector{Interval{Float64}}:
 [-2, 2]
 [-1, 1]

julia> ge = GaussianElimination()
GaussianElimination linear solver

julia> ge(A, b)
2-element Vector{Interval{Float64}}:
 [-1.66667, 1.66667]
 [-1.33334, 1.33334]
```
"""
struct GaussianElimination <: AbstractDirectSolver end

function (ge::GaussianElimination)(A::AbstractMatrix{T},
                                   b::AbstractVector{T}) where {T<:Interval}
    n = length(b)
    Abrref = rref([A b])

    # backsubstitution
    x = similar(b)
    x[end] = Abrref[n, n+1]/Abrref[n, n]
    @inbounds for i = n-1:-1:1
        x[i] = (Abrref[i, n+1] - sum(Abrref[i, j]*x[j] for j in i+1:n))/Abrref[i, i]
    end
    return x
end

## JACOBI
"""
    Jacobi <: AbstractIterativeSolver

Type for the Jacobi solver of the interval linear system ``Ax=b``.
For details see Section 5.7.4 of [[HOR19]](@ref)

### Fields

- `max_iterations` -- maximum number of iterations (default 20)
- `atol`           -- absolute tolerance (default 0), if at some point ``|xₖ - xₖ₊₁| < atol``
                      (elementwise), then stop and return ``xₖ₊₁``.
                      If `atol=0`, then `min(diam(A))*1e-5` is used.

### Notes

- An object of type `Jacobi` is a function with method

        (jac::Jacobi)(A::AbstractMatrix{T},
                      b::AbstractVector{T},
                      [x]::AbstractVector{T}=enclose(A, b)) where {T<:Interval}

    #### Input
    - `A`   -- N×N interval matrix
    - `b`   -- interval vector of length N
    - `x`   -- (optional) initial enclosure for the solution of ``Ax = b``. If not given,
               it is automatically computed using [`enclose`](@ref enclose)

### Examples

```jldoctest
julia> A = [2..4 -1..1;-1..1 2..4]
2×2 Matrix{Interval{Float64}}:
  [2, 4]  [-1, 1]
 [-1, 1]   [2, 4]

julia> b = [-2..2, -1..1]
2-element Vector{Interval{Float64}}:
 [-2, 2]
 [-1, 1]

julia> jac = Jacobi()
Jacobi linear solver
max_iterations = 20
atol = 0.0

julia> jac(A, b)
2-element Vector{Interval{Float64}}:
 [-1.66668, 1.66668]
 [-1.33335, 1.33335]
```
"""
struct Jacobi <: AbstractIterativeSolver
    max_iterations::Int
    atol::Float64
end

Jacobi() = Jacobi(20, 0.0)

function (jac::Jacobi)(A::AbstractMatrix{T},
                       b::AbstractVector{T},
                       x::AbstractVector{T}=enclose(A, b)) where {T<:Interval}

    n = length(b)
    atol = iszero(jac.atol) ? minimum(diam.(A))*1e-5 : jac.atol

    for _ in 1:jac.max_iterations
        xold = copy(x)
        @inbounds @simd for i in 1:n
            x[i] = b[i]
            for j in 1:n
                (i == j) || (x[i] -= A[i, j] * xold[j])
            end
            x[i] = (x[i]/A[i, i]) ∩ xold[i]
        end
        all(interval_isapprox.(x, xold; atol=atol)) && break
    end
    return x
end

## GAUSS SEIDEL
"""
    GaussSeidel <: AbstractIterativeSolver

Type for the Gauss-Seidel solver of the interval linear system ``Ax=b``.
For details see Section 5.7.4 of [[HOR19]](@ref)

### Fields

- `max_iterations` -- maximum number of iterations (default 20)
- `atol`           -- absolute tolerance (default 0), if at some point ``|xₖ - xₖ₊₁| < atol``
                      (elementwise), then stop and return ``xₖ₊₁``.
                      If `atol=0`, then `min(diam(A))*1e-5` is used.

### Notes

- An object of type `GaussSeidel` is a function with method

        (gs::GaussSeidel)(A::AbstractMatrix{T},
                          b::AbstractVector{T},
                          [x]::AbstractVector{T}=enclose(A, b)) where {T<:Interval}

    #### Input
    - `A`   -- N×N interval matrix
    - `b`   -- interval vector of length N
    - `x`   -- (optional) initial enclosure for the solution of ``Ax = b``. If not given,
               it is automatically computed using [`enclose`](@ref enclose)

### Examples

```jldoctest
julia> A = [2..4 -1..1;-1..1 2..4]
2×2 Matrix{Interval{Float64}}:
  [2, 4]  [-1, 1]
 [-1, 1]   [2, 4]

julia> b = [-2..2, -1..1]
2-element Vector{Interval{Float64}}:
 [-2, 2]
 [-1, 1]

julia> gs = GaussSeidel()
GaussSeidel linear solver
max_iterations = 20
atol = 0.0

julia> gs(A, b)
2-element Vector{Interval{Float64}}:
 [-1.66668, 1.66668]
 [-1.33334, 1.33334]
```
"""
struct GaussSeidel <: AbstractIterativeSolver
    max_iterations::Int
    atol::Float64
end

GaussSeidel() = GaussSeidel(20, 0.0)

function (gs::GaussSeidel)(A::AbstractMatrix{T},
                           b::AbstractVector{T},
                           x::AbstractVector{T}=enclose(A, b)) where {T<:Interval}
    n = length(b)

    atol = iszero(gs.atol) ? minimum(diam.(A))*1e-5 : gs.atol
    @inbounds for _ in 1:gs.max_iterations
        xold = copy(x)
        @inbounds for i in 1:n
            x[i] = b[i]
            @inbounds for j in 1:n
                (i == j) || (x[i] -= A[i, j] * x[j])
            end
            x[i] = (x[i]/A[i, i]) .∩ xold[i]
        end
        all(interval_isapprox.(x, xold; atol=atol)) && break
    end
    return x
end

## KRAWCZYK
"""
    LinearKrawczyk <: AbstractIterativeSolver

Type for the Krawczyk solver of the interval linear system ``Ax=b``.
For details see Section 5.7.3 of [[HOR19]](@ref)

### Fields

- `max_iterations` -- maximum number of iterations (default 20)
- `atol`           -- absolute tolerance (default 0), if at some point ``|xₖ - xₖ₊₁| < atol``
                      (elementwise), then stop and return ``xₖ₊₁``.
                      If `atol=0`, then `min(diam(A))*1e-5` is used.

### Notes

- An object of type `LinearKrawczyk` is a function with method

        (kra::LinearKrawczyk)(A::AbstractMatrix{T},
                              b::AbstractVector{T},
                              [x]::AbstractVector{T}=enclose(A, b)) where {T<:Interval}

    #### Input
    - `A`   -- N×N interval matrix
    - `b`   -- interval vector of length N
    - `x`   -- (optional) initial enclosure for the solution of ``Ax = b``. If not given,
               it is automatically computed using [`enclose`](@ref enclose)

### Examples

```jldoctest
julia> A = [2..4 -1..1;-1..1 2..4]
2×2 Matrix{Interval{Float64}}:
  [2, 4]  [-1, 1]
 [-1, 1]   [2, 4]

julia> b = [-2..2, -1..1]
2-element Vector{Interval{Float64}}:
 [-2, 2]
 [-1, 1]

julia> kra = LinearKrawczyk()
LinearKrawczyk linear solver
max_iterations = 20
atol = 0.0

julia> kra(A, b)
2-element Vector{Interval{Float64}}:
 [-2, 2]
 [-2, 2]
```
"""
struct LinearKrawczyk <: AbstractIterativeSolver
    max_iterations::Int
    atol::Float64
end

LinearKrawczyk() = LinearKrawczyk(20, 0.0)

function (kra::LinearKrawczyk)(A::AbstractMatrix{T},
                               b::AbstractVector{T},
                               x::AbstractVector{T}=enclose(A, b)) where {T<:Interval}

    atol = iszero(kra.atol) ? minimum(diam.(A))*1e-5 : kra.atol

    C = inv(mid.(A))
    for i = 1:kra.max_iterations
        xnew  = (C*b  - C*(A*x) + x) .∩ x
        all(interval_isapprox.(x, xnew; atol=atol)) && return xnew
        x = xnew
    end
    return x
end

# custom printing for solvers
function Base.string(s::AbstractLinearSolver)

    str="""$(typeof(s)) linear solver
    """

    fields = fieldnames(typeof(s))
    for field in fields
        str *= """$field = $(getfield(s, field))
        """
    end
    return str
end

Base.show(io::IO, s::AbstractLinearSolver) = print(io, string(s))


### A Pluto.jl notebook ###
# v0.16.0

using Markdown
using InteractiveUtils

# ╔═╡ 7a3de02b-c423-496e-bf92-9981d71e5eab
begin
    using Arrow
	using Chain
	using DataFrames
	using MixedModels
	using StatsBase
	using StatsModels
	using StatsModels: ContrastsCoding
end

# ╔═╡ 09a33b05-568e-427a-bd66-812d271d1791
md"""
# Contrast Coding of Visual Attention Effects
## Date: 2021-05-29 (revised: 2021-09-16)
"""

# ╔═╡ 28c37807-b83f-4c3e-848d-a7401b4eda29
md"""
## Example data
We take the `KWDYZ` dataset ([Kliegl et al., 2011; Frontiers](https://doi.org/10.3389/fpsyg.2010.00238)). This is an experiment looking at three effects of visual cueing under four different cue-target relations (CTRs). Two horizontal rectangles are displayed above and below a central fixation point or they displayed in vertical orientation to the left and right of the fixation point.  Subjects react to the onset of a small visual target occuring at one of the four ends of the two rectangles. The target is cued validly on 70% of trials by a brief flash of the corner of the rectangle at which it appears; it is cued invalidly at the three other locations 10% of the trials each.

We specify three contrasts for the four-level factor CTR that are derived from spatial, object-based, and attractor-like features of attention. They map onto sequential differences between appropriately ordered factor levels. Interestingly, a different theoretical perspective, derived from feature overlap, leads to a different set of contrasts. Can the results refute one of the theoretical perspectives?

We also have a dataset from a replication and extension of this study (Kliegl, Kuschela, & Laubrock, 2015). Both data sets are available in [R-package RePsychLing](https://github.com/dmbates/RePsychLing/tree/master/data/) (Baayen et al., 2014).

## Preprocessing
"""

# ╔═╡ ccf701bb-cf16-4339-bd93-64ffe87435bb
begin
	dat1 = DataFrame(Arrow.Table("./data/kwdyz11.arrow"))
	dat1 = select(dat1, :subj => :Subj, :tar => :CTR, :rt);

	# Descriptive statistics
	cellmeans = combine(groupby(dat1, [:CTR]),
                             :rt => mean, :rt  => std, :rt => length,
                             :rt => (x -> std(x)/sqrt(length(x))) => :rt_semean)
end

# ╔═╡ cb63bd4b-5f0e-49d1-a257-2b83c1687975
md"""## SeqDiffCoding
This contrast corresponds to `MASS::contr.sdif()` in R.
"""

# ╔═╡ 8ffba70f-fc97-47a2-b973-3c0f2047eb9b
begin
	cntr1 = Dict(
    :CTR  => SeqDiffCoding(levels=["val", "sod", "dos", "dod"]),
    :Subj => Grouping()
	);

	formula = @formula  rt ~ 1 + CTR + (1 + CTR | Subj);
	m1 = fit(MixedModel, formula, dat1, contrasts=cntr1)
end

# ╔═╡ 4e9359a3-2533-4ce8-b2fd-26411e84f59c
md"""
## HypothesisCoding
This contrast corresponds to `MASS::contr.sdif()` in R.
A general solution (not inverse of last contrast)
"""

# ╔═╡ b00e953a-b279-4ec3-8744-5518d1242d8e
begin
	cntr1b = Dict(
    :CTR => HypothesisCoding([-1  1  0  0
                               0 -1  1  0
                               0  0  1 -1],
            levels=["val", "sod",  "dos", "dod"],
            labels=["spt", "obj", "grv"])
	);

	m1b = fit(MixedModel, formula, dat1, contrasts=cntr1b)
end

# ╔═╡ 7a3749b3-d2e9-4b53-9e57-975ee428b416
md"""

Controlling the ordering of levels for contrasts:

1.  kwarg `levels=` to order the levels; the first is set as the baseline.
2.  kwarg `base=` to fix the baseline level.

The assignment of random factors such as `Subj` to `Grouping()` is only necessary when the sample size is very large and leads to an out-of-memory error; it is included only in the first example for reference.

## DummyCoding
Thi contrast corresponds to `contr.treatment()` in R
"""

# ╔═╡ 6393d21d-a3a6-4463-8981-052440ba7887
begin
	cntr2 = Dict(:CTR => DummyCoding(base= "val"));

	m2 = fit(MixedModel, formula, dat1, contrasts=cntr2)
end

# ╔═╡ d087afe8-9d2b-489e-9160-206f22486505
md"""
This contrast has the disadvantage that the intercept returns the mean of the level specified as `base`, default is the first level, not the GM.

## YchycaeitCoding

The contrasts returned by `DummyCoding` may be what you want. Can't we have them, but also the GM rather than the mean of the base level?  Yes, we can!  I call this "You can have your cake and it eat, too"-Coding (YchycaeitCoding).
"""

# ╔═╡ bb97e862-24ad-4ce5-9011-429a0e69e5ab
begin
	cntr2b = Dict(
    :CTR => HypothesisCoding([-1  1  0  0
                              -1  0  1  0
                              -1  0  0  1],
            levels=["val", "sod",  "dos", "dod"])
	);

	m2b = fit(MixedModel, formula, dat1, contrasts=cntr2b)
end

# ╔═╡ 71528268-41fe-4de5-8725-1c65c4839c65
md"""
Just relevel the factor or move the column with -1s for a different base.

## EffectsCoding - corresponds to `contr.sum()` in R
"""

# ╔═╡ e2372cb1-03d4-4404-bfef-640ad5653792
begin
	cntr3 = Dict(:CTR => EffectsCoding(base= "dod"));

	m3 = fit(MixedModel, formula, dat1, contrasts=cntr3)
end

# ╔═╡ 142c86d3-e340-494c-8b52-8f2013993a19
md"## HelmertCoding"

# ╔═╡ 0bb71abe-433c-44e3-bff4-eb26e63bee2d
begin
	cntr4 = Dict(:CTR => HelmertCoding());

	fit(MixedModel, formula, dat1, contrasts=cntr4)
end

# ╔═╡ 0cf43493-af05-4744-a154-d7209251ae8f
md"## Reverse HelmertCoding"

# ╔═╡ 2d778d8e-949c-4a7b-a774-770c6bc4bce6
begin
	cntr4b = Dict(:CTR => HelmertCoding(levels=reverse(levels(dat1.CTR))));

	fit(MixedModel, formula, dat1, contrasts=cntr4b)
end

# ╔═╡ a71103ca-a680-45ac-ba02-61a5660cc157
md"""
Helmert contrasts are othogonal.

## AnovaCoding - Anova contrasts are orthogonal.

### A(2) x B(2)

An A(2) x B(2) design can be recast as an F(4) design with the levels (A1-B1, A1-B2, A2-B1, A2-B2). The following contrast specifiction returns estimates for the main effect of A, the main effect of B, and the interaction of A and B. In a figure With A on the x-axis and the levels of B shown as two lines, the interaction tests the null hypothesis that the two lines are parallel. A positive coefficient implies overadditivity (diverging lines toward the right) and a negative coefficient underadditivity (converging lines).
"""

# ╔═╡ 00bedbae-0bc4-4e4e-85d5-91ffc8ea77ee
begin
	cntr5 = Dict(
    :CTR => HypothesisCoding([-1  -1 +1  +1          # A
                              -1  +1 -1  +1          # B
                              +1  -1 -1  +1],        # A x B
            levels=["val", "sod",  "dos", "dod"],
            labels=["A", "B", "AxB"])
	);
	m5 = fit(MixedModel, formula, dat1, contrasts=cntr5)
end

# ╔═╡ f504edce-35de-4938-a3c6-08d4fc8f2e66
md"""
It is also helpful to see the corresponding layout of the four means for the interaction of A and B (i.e., the third contrast)

```
        B1     B2
   A1   +1     -1
   A2   -1     +1
```

Thus, interaction tests whether the difference between main diagonal and minor diagonal is different from zero.
=#

### A(2) x B(2) x C(2)

Going beyond the four level factor; it is also helpful to see the corresponding layout of the eight means for the interaction of A and B and C.

```
          C1              C2
      B1     B2        B1     B2
 A1   +1     -1   A1   -1     +1
 A2   -1     +1   A2   +1     -1
```

### A(2) x B(2) x C(3)

TO BE DONE
"""

# ╔═╡ a12c0a44-f02e-479e-94f9-c5d09c3b25ae
md"""
## NestedCoding

An A(2) x B(2) design can be recast as an F(4) design with the levels (A1-B1, A1-B2, A2-B1, A2-B2).  The following contrast specifiction returns an estimate for the main effect of A and the effects of B nested in the two levels of A. In a figure With A on the x-axis and the levels of B shown as two lines, the second contrast tests whether A1-B1 is different from A1-B2 and the third contrast tests whether A2-B1 is different from A2-B2.
"""

# ╔═╡ ab8b2f3a-c8d0-4f74-943b-9426d1c65deb
begin
	cntr8 = Dict(
    :CTR => HypothesisCoding([-1  -1 +1  +1
                              -1  +1  0   0
                               0   0 +1  -1],
            levels=["val", "sod",  "dos", "dod"],
            labels=["do_so", "spt", "grv"])
	);
	m8 = fit(MixedModel, formula, dat1, contrasts=cntr8)
end

# ╔═╡ aef896f9-83d6-45f7-ac28-ba7d1ab7064b
md"""
The three contrasts for one main effect and two nested contrasts are orthogonal. There is no test of the interaction (parallelism).

## Other orthogonal contrasts

For factors with more than four levels there are many options for specifying orthogonal contrasts as long as one proceeds in a top-down strictly hiearchical fashion.

Suppose you have a factor with seven levels and let's ignore shifting colummns. In this case, you have six options for the first contrast, that is 6 vs. 1, 5 vs.2 , 4 vs. 3, 3 vs. 4, 2 vs. 5, and 1 vs. 6 levels.  Then, you specify orthogonal contrasts for partitions with more than 2 elements and so on. That is, you don't specify a contrast that crosses an earlier partition line.

In the following example, after an initial 4 vs 3 partitioning of levels, we specify `AnovaCoding` for the left and `HelmertCoding` for the right partition.
"""

# ╔═╡ 1a9a46c9-42fb-4e27-87af-e9278547eae6
begin
	cntr9 = Dict(
    :CTR => HypothesisCoding(
    [-1/4 -1/4 -1/4 -1/4 +1/3 +1/3 +1/3
     -1/2 -1/2 +1/2 +1/2   0    0    0
     -1/2 +1/2 -1/2 +1/2   0    0    0
     +1/2 -1/2 -1/2 +1/2   0    0    0
       0    0    0    0   -1   +1    0
       0    0    0    0  -1/2 -1/2   1
     ],
    levels=["A1", "A2",  "A3", "A4", "A5", "A6", "A7"],
    labels=["c567.1234", "B", "C", "BxC", "c6.5", "c6.56"])
	)
end

# ╔═╡ 93fcc1b6-98a8-41b5-a84d-46e5bd2df7b9
md"""
There are two rules that hold for all orthogonal contrasts:

1. The weights within rows sum to zero.
2. For all pairs of rows, the sum of the products of weights in the same columns sums to zero.

# Appendix: Summary (Dave Kleinschmidt)

[StatsModels](https://juliastats.org/StatsModels.jl/latest/contrasts/)

StatsModels.jl provides a few commonly used contrast coding schemes,
some less-commonly used schemes, and structs that allow you to manually
specify your own, custom schemes.

## Standard contrasts

The most commonly used contrasts are `DummyCoding` and `EffectsCoding`
(which are similar to `contr.treatment()` and `contr.sum()` in R,
respectively).

## "Exotic" contrasts (rk: well ...)

We also provide `HelmertCoding` and `SeqDiffCoding` (corresponding to
base R's `contr.helmert()` and `MASS::contr.sdif()`).

## Manual contrasts

**ContrastsCoding()**

There are two ways to manually specify contrasts. First, you can specify
them **directly** via `ContrastsCoding`. If you do, it's good practice
to specify the levels corresponding to the rows of the matrix, although
they can be omitted in which case they'll be inferred from the data.

**HypothesisCoding()**

A better way to specify manual contrasts is via `HypothesisCoding`, where each
row of the matrix corresponds to the weights given to the cell means of the
levels corresponding to each column (see [Schad et
al. 2020](https://doi.org/10.1016/j.jml.2019.104038) for more information).
"""

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
Arrow = "69666777-d1a9-59fb-9406-91d4454c9d45"
Chain = "8be319e6-bccf-4806-a6f7-6fae938471bc"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
MixedModels = "ff71e718-51f3-5ec2-a782-8ffcbfa3c316"
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
StatsModels = "3eaba693-59b7-5ba5-a881-562e759f1c8d"

[compat]
Arrow = "~1.6.2"
Chain = "~0.4.8"
DataFrames = "~1.2.2"
MixedModels = "~4.1.1"
StatsBase = "~0.33.10"
StatsModels = "~0.6.25"
"""

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

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

[[Arrow]]
deps = ["ArrowTypes", "BitIntegers", "CodecLz4", "CodecZstd", "DataAPI", "Dates", "Mmap", "PooledArrays", "SentinelArrays", "Tables", "TimeZones", "UUIDs"]
git-tree-sha1 = "b00e6eaba895683867728e73af78a00218f0db10"
uuid = "69666777-d1a9-59fb-9406-91d4454c9d45"
version = "1.6.2"

[[ArrowTypes]]
deps = ["UUIDs"]
git-tree-sha1 = "a0633b6d6efabf3f76dacd6eb1b3ec6c42ab0552"
uuid = "31f734f8-188a-4ce0-8406-c8a06bd891cd"
version = "1.2.1"

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

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

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

[[BitIntegers]]
deps = ["Random"]
git-tree-sha1 = "f50b5a99aa6ff9db7bf51255b5c21c8bc871ad54"
uuid = "c3b6d118-76ef-56ca-8cc7-ebb389d030a1"
version = "0.2.5"

[[Bzip2_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "19a35467a82e236ff51bc17a3a44b69ef35185a2"
uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0"
version = "1.0.8+0"

[[Chain]]
git-tree-sha1 = "cac464e71767e8a04ceee82a889ca56502795705"
uuid = "8be319e6-bccf-4806-a6f7-6fae938471bc"
version = "0.4.8"

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

[[CodecBzip2]]
deps = ["Bzip2_jll", "Libdl", "TranscodingStreams"]
git-tree-sha1 = "2e62a725210ce3c3c2e1a3080190e7ca491f18d7"
uuid = "523fee87-0ab8-5b00-afb7-3ecf72e48cfd"
version = "0.7.2"

[[CodecLz4]]
deps = ["Lz4_jll", "TranscodingStreams"]
git-tree-sha1 = "59fe0cb37784288d6b9f1baebddbf75457395d40"
uuid = "5ba52731-8f18-5e0d-9241-30f10d1ec561"
version = "0.4.0"

[[CodecZlib]]
deps = ["TranscodingStreams", "Zlib_jll"]
git-tree-sha1 = "ded953804d019afa9a3f98981d99b33e3db7b6da"
uuid = "944b1d66-785c-5afd-91f1-9de20f533193"
version = "0.7.0"

[[CodecZstd]]
deps = ["TranscodingStreams", "Zstd_jll"]
git-tree-sha1 = "d19cd9ae79ef31774151637492291d75194fc5fa"
uuid = "6b39b394-51ab-5f42-8807-6242bab2b4c2"
version = "0.7.0"

[[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 = "727e463cfebd0c7b999bbf3e9e7e16f254b94193"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "3.34.0"

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

[[Crayons]]
git-tree-sha1 = "3f71217b538d7aaee0b69ab47d9b7724ca8afa0d"
uuid = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f"
version = "4.0.4"

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

[[DataFrames]]
deps = ["Compat", "DataAPI", "Future", "InvertedIndices", "IteratorInterfaceExtensions", "LinearAlgebra", "Markdown", "Missings", "PooledArrays", "PrettyTables", "Printf", "REPL", "Reexport", "SortingAlgorithms", "Statistics", "TableTraits", "Tables", "Unicode"]
git-tree-sha1 = "d785f42445b63fc86caa08bb9a9351008be9b765"
uuid = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
version = "1.2.2"

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

[[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"

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

[[Distributions]]
deps = ["ChainRulesCore", "FillArrays", "LinearAlgebra", "PDMats", "Printf", "QuadGK", "Random", "SparseArrays", "SpecialFunctions", "Statistics", "StatsBase", "StatsFuns"]
git-tree-sha1 = "c2dbc7e0495c3f956e4615b78d03c7aa10091d0c"
uuid = "31c24e10-a181-5473-b8eb-7969acd0382f"
version = "0.25.15"

[[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"

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

[[FillArrays]]
deps = ["LinearAlgebra", "Random", "SparseArrays", "Statistics"]
git-tree-sha1 = "7c365bdef6380b29cfc5caaf99688cd7489f9b87"
uuid = "1a297f60-69ca-5386-bcde-b61e274b549b"
version = "0.12.2"

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

[[Future]]
deps = ["Random"]
uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820"

[[GLM]]
deps = ["Distributions", "LinearAlgebra", "Printf", "Reexport", "SparseArrays", "SpecialFunctions", "Statistics", "StatsBase", "StatsFuns", "StatsModels"]
git-tree-sha1 = "f564ce4af5e79bb88ff1f4488e64363487674278"
uuid = "38e38edf-8417-5370-95a0-9cbb8c7f171a"
version = "1.5.1"

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

[[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"

[[InvertedIndices]]
deps = ["Test"]
git-tree-sha1 = "15732c475062348b0165684ffe28e85ea8396afc"
uuid = "41ab1584-1d38-5bbf-9106-f11c6c58b48f"
version = "1.0.0"

[[IrrationalConstants]]
git-tree-sha1 = "f76424439413893a832026ca355fe273e93bce94"
uuid = "92d709cd-6900-40b7-9082-c6be49f344b6"
version = "0.1.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 = "8076680b162ada2a031f707ac7b4953e30667a37"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.2"

[[JSON3]]
deps = ["Dates", "Mmap", "Parsers", "StructTypes", "UUIDs"]
git-tree-sha1 = "b3e5984da3c6c95bcf6931760387ff2e64f508f3"
uuid = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
version = "1.9.1"

[[JSONSchema]]
deps = ["HTTP", "JSON", "ZipFile"]
git-tree-sha1 = "b84ab8139afde82c7c65ba2b792fe12e01dd7307"
uuid = "7d188eb4-7ad8-530c-ae41-71a32a6d4692"
version = "0.3.3"

[[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"

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

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

[[LogExpFunctions]]
deps = ["DocStringExtensions", "IrrationalConstants", "LinearAlgebra"]
git-tree-sha1 = "3d682c07e6dd250ed082f883dc88aee7996bf2cc"
uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
version = "0.3.0"

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

[[Lz4_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "5d494bc6e85c4c9b626ee0cab05daa4085486ab1"
uuid = "5ced341a-0733-55b8-9ab6-a4889d929147"
version = "1.9.3+0"

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

[[MathOptInterface]]
deps = ["BenchmarkTools", "CodecBzip2", "CodecZlib", "JSON", "JSONSchema", "LinearAlgebra", "MutableArithmetics", "OrderedCollections", "SparseArrays", "Test", "Unicode"]
git-tree-sha1 = "575644e3c05b258250bb599e57cf73bbf1062901"
uuid = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
version = "0.9.22"

[[MathProgBase]]
deps = ["LinearAlgebra", "SparseArrays"]
git-tree-sha1 = "9abbe463a1e9fc507f12a69e7f29346c2cdc472c"
uuid = "fdba3010-5040-5b88-9595-932c9decdf73"
version = "0.7.8"

[[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"

[[Missings]]
deps = ["DataAPI"]
git-tree-sha1 = "2ca267b08821e86c5ef4376cffed98a46c2cb205"
uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28"
version = "1.0.1"

[[MixedModels]]
deps = ["Arrow", "DataAPI", "Distributions", "GLM", "JSON3", "LazyArtifacts", "LinearAlgebra", "Markdown", "NLopt", "PooledArrays", "ProgressMeter", "Random", "SparseArrays", "StaticArrays", "Statistics", "StatsBase", "StatsFuns", "StatsModels", "StructTypes", "Tables"]
git-tree-sha1 = "f318e42a48ec0a856292bafeec6b07aed3f6d600"
uuid = "ff71e718-51f3-5ec2-a782-8ffcbfa3c316"
version = "4.1.1"

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

[[Mocking]]
deps = ["ExprTools"]
git-tree-sha1 = "748f6e1e4de814b101911e64cc12d83a6af66782"
uuid = "78c3b35d-d492-501b-9361-3d52fe80e533"
version = "0.7.2"

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

[[MutableArithmetics]]
deps = ["LinearAlgebra", "SparseArrays", "Test"]
git-tree-sha1 = "3927848ccebcc165952dc0d9ac9aa274a87bfe01"
uuid = "d8a4904e-b15c-11e9-3269-09a3773c0cb0"
version = "0.2.20"

[[NLopt]]
deps = ["MathOptInterface", "MathProgBase", "NLopt_jll"]
git-tree-sha1 = "d80cb3327d1aeef0f59eacf225e000f86e4eee0a"
uuid = "76087f3c-5699-56af-9a33-bf431cd00edd"
version = "0.6.3"

[[NLopt_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "2b597c46900f5f811bec31f0dcc88b45744a2a09"
uuid = "079eb43e-fd8e-5478-9966-2cf3e3edb778"
version = "2.7.0+0"

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

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

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

[[PDMats]]
deps = ["LinearAlgebra", "SparseArrays", "SuiteSparse"]
git-tree-sha1 = "4dd403333bcf0909341cfe57ec115152f937d7d8"
uuid = "90014a1f-27ba-587c-ab20-58faa44d9150"
version = "0.11.1"

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

[[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"

[[PooledArrays]]
deps = ["DataAPI", "Future"]
git-tree-sha1 = "cde4ce9d6f33219465b55162811d8de8139c0414"
uuid = "2dfb63ee-cc39-5dd5-95bd-886bf059d720"
version = "1.2.1"

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

[[PrettyTables]]
deps = ["Crayons", "Formatting", "Markdown", "Reexport", "Tables"]
git-tree-sha1 = "0d1245a357cc61c8cd61934c07447aa569ff22e6"
uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"
version = "1.1.0"

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

[[ProgressMeter]]
deps = ["Distributed", "Printf"]
git-tree-sha1 = "afadeba63d90ff223a6a48d2009434ecee2ec9e8"
uuid = "92933f4c-e287-5a05-a399-4b506db050ca"
version = "1.7.1"

[[QuadGK]]
deps = ["DataStructures", "LinearAlgebra"]
git-tree-sha1 = "12fbe86da16df6679be7521dfb39fbc861e1dc7b"
uuid = "1fd47b50-473d-5c70-9696-f719f8f3bcdc"
version = "2.4.1"

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

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

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

[[Reexport]]
git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.2.2"

[[Rmath]]
deps = ["Random", "Rmath_jll"]
git-tree-sha1 = "bf3188feca147ce108c76ad82c2792c57abe7b1f"
uuid = "79098fc4-a85e-5d69-aa6a-4863f24498fa"
version = "0.7.0"

[[Rmath_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "68db32dff12bb6127bac73c209881191bf0efbb7"
uuid = "f50d1b31-88e8-58de-be2c-1cc44531875f"
version = "0.3.0+0"

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

[[SentinelArrays]]
deps = ["Dates", "Random"]
git-tree-sha1 = "54f37736d8934a12a200edea2f9206b03bdf3159"
uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c"
version = "1.3.7"

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

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

[[ShiftedArrays]]
git-tree-sha1 = "22395afdcf37d6709a5a0766cc4a5ca52cb85ea0"
uuid = "1277b4bf-5013-50f5-be3d-901d8477a67a"
version = "1.0.0"

[[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 = "a322a9493e49c5f3a10b50df3aedaf1cdb3244b7"
uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
version = "1.6.1"

[[StaticArrays]]
deps = ["LinearAlgebra", "Random", "Statistics"]
git-tree-sha1 = "3240808c6d463ac46f1c1cd7638375cd22abbccb"
uuid = "90137ffa-7385-5640-81b9-e52037218182"
version = "1.2.12"

[[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 = "8cbbc098554648c84f79a463c9ff0fd277144b6c"
uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
version = "0.33.10"

[[StatsFuns]]
deps = ["ChainRulesCore", "IrrationalConstants", "LogExpFunctions", "Reexport", "Rmath", "SpecialFunctions"]
git-tree-sha1 = "46d7ccc7104860c38b11966dd1f72ff042f382e4"
uuid = "4c63d2b9-4356-54db-8cca-17b64c39e42c"
version = "0.9.10"

[[StatsModels]]
deps = ["DataAPI", "DataStructures", "LinearAlgebra", "Printf", "ShiftedArrays", "SparseArrays", "StatsBase", "StatsFuns", "Tables"]
git-tree-sha1 = "3fa15c1f8be168e76d59097f66970adc86bfeb95"
uuid = "3eaba693-59b7-5ba5-a881-562e759f1c8d"
version = "0.6.25"

[[StructTypes]]
deps = ["Dates", "UUIDs"]
git-tree-sha1 = "8445bf99a36d703a09c601f9a57e2f83000ef2ae"
uuid = "856f2bd8-1eba-4b0a-8007-ebc267875bd4"
version = "1.7.3"

[[SuiteSparse]]
deps = ["Libdl", "LinearAlgebra", "Serialization", "SparseArrays"]
uuid = "4607b0f0-06f3-5cda-b6b1-a6196a1729e9"

[[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"

[[TimeZones]]
deps = ["Dates", "Future", "LazyArtifacts", "Mocking", "Pkg", "Printf", "RecipesBase", "Serialization", "Unicode"]
git-tree-sha1 = "6c9040665b2da00d30143261aea22c7427aada1c"
uuid = "f269a46b-ccf7-5d73-abea-4c690281aa53"
version = "1.5.7"

[[TranscodingStreams]]
deps = ["Random", "Test"]
git-tree-sha1 = "216b95ea110b5972db65aa90f88d8d89dcb8851c"
uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa"
version = "0.9.6"

[[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"

[[ZipFile]]
deps = ["Libdl", "Printf", "Zlib_jll"]
git-tree-sha1 = "c3a5637e27e914a7a445b8d0ad063d701931e9f7"
uuid = "a5390f91-8eb1-5f08-bee0-b1d1ffed6cea"
version = "0.9.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"

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

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

# ╔═╡ Cell order:
# ╟─09a33b05-568e-427a-bd66-812d271d1791
# ╠═7a3de02b-c423-496e-bf92-9981d71e5eab
# ╟─28c37807-b83f-4c3e-848d-a7401b4eda29
# ╠═ccf701bb-cf16-4339-bd93-64ffe87435bb
# ╟─cb63bd4b-5f0e-49d1-a257-2b83c1687975
# ╠═8ffba70f-fc97-47a2-b973-3c0f2047eb9b
# ╟─4e9359a3-2533-4ce8-b2fd-26411e84f59c
# ╠═b00e953a-b279-4ec3-8744-5518d1242d8e
# ╟─7a3749b3-d2e9-4b53-9e57-975ee428b416
# ╠═6393d21d-a3a6-4463-8981-052440ba7887
# ╟─d087afe8-9d2b-489e-9160-206f22486505
# ╠═bb97e862-24ad-4ce5-9011-429a0e69e5ab
# ╟─71528268-41fe-4de5-8725-1c65c4839c65
# ╠═e2372cb1-03d4-4404-bfef-640ad5653792
# ╟─142c86d3-e340-494c-8b52-8f2013993a19
# ╠═0bb71abe-433c-44e3-bff4-eb26e63bee2d
# ╟─0cf43493-af05-4744-a154-d7209251ae8f
# ╠═2d778d8e-949c-4a7b-a774-770c6bc4bce6
# ╟─a71103ca-a680-45ac-ba02-61a5660cc157
# ╠═00bedbae-0bc4-4e4e-85d5-91ffc8ea77ee
# ╟─f504edce-35de-4938-a3c6-08d4fc8f2e66
# ╟─a12c0a44-f02e-479e-94f9-c5d09c3b25ae
# ╠═ab8b2f3a-c8d0-4f74-943b-9426d1c65deb
# ╟─aef896f9-83d6-45f7-ac28-ba7d1ab7064b
# ╠═1a9a46c9-42fb-4e27-87af-e9278547eae6
# ╟─93fcc1b6-98a8-41b5-a84d-46e5bd2df7b9
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


#
# Spline
#

struct Spline
  x :: Vector{Float64}
  y :: Vector{Float64}
end

"""

`fitspline(x,y)`

Computes a spline.

Use `fit = fitspline(x,y)`. `fit.x` and `fit.y` will contain
the data points of the spline.

# Example

```jldoctest
julia> x = sort(rand(10)); y = rand(10);

julia> fit = fitspline(x,y)

 -------- Spline fit ---------------------------

 x spline: x = [0.05986115284955605, 0.0755869317846746...
 y spline: y = [0.6749928175217872, 0.5711266179726132...

 -----------------------------------------------
```
"""
function fitspline(X :: AbstractArray{<:Real}, Y :: AbstractArray{<:Real}, options :: Options)
  X, Y = checkdata(X,Y,options)
  t = 1:length(X)
  A = hcat(X,Y)
  itp = Interpolations.scale(interpolate(A,
            (BSpline(Interpolations.Cubic(Natural(OnGrid()))), NoInterp())), t, 1:2)
  tfine = 1:length(X)/options.fine:length(X)
  x, y = [itp(t,1) for t in tfine], [itp(t,2) for t in tfine]
  return Spline(x,y)
end
fitspline(X :: AbstractArray{<:Real}, Y :: AbstractArray{<:Real}) = fitspline(X,Y,Options())

function Base.show( io :: IO, fit :: Spline )
  println("")
  println(" -------- Spline fit --------------------------- ")
  println("")
  println(" x spline: x = [",fit.x[1],", ",fit.x[2],"...")
  println(" y spline: y = [",fit.y[1],", ",fit.y[2],"...")
  println("")
  println(" ----------------------------------------------- ")
end

export fitspline


# ---
# title: 386. Lexicographical Numbers
# id: problem386
# author: Indigo
# date: 2021-06-15
# difficulty: Medium
# categories:
# link: <https://leetcode.com/problems/lexicographical-numbers/description/>
# hidden: true
# ---
#
# Given an integer _n_ , return 1 - _n_ in lexicographical order.
#
# For example, given 13, return: [1,10,11,12,13,2,3,4,5,6,7,8,9].
#
# Please optimize your algorithm to use less time and space. The input size may
# be as large as 5,000,000.
#
#
## @lc code=start
using LeetCode

function lexical_order(n)
    res = Vector{Int}(undef, n)
    cur = 1
    for i in 1:n
        res[i] = cur
        if 10cur <= n
            cur *= 10
        else
            (cur >= n) && (cur ÷= 10)
            cur += 1
            while cur % 10 == 0
                cur ÷= 10
            end
        end
    end
    return res
end
## @lc code=end