#' ---
#' title: Conic Programming
#' ---

#' **Originally Contributed by**: Arpit Bhatia

#' This tutorial is aimed at providing a simplistic introduction to conic programming using JuMP.

#' ## What is a Cone?
#' A subset $C$ of a vector space $V$ is a cone if $\forall x \in C$ and positive scalars $\alpha$,
#' the product $\alpha x \in C$. A cone C is a convex cone if $\alpha x + \beta y \in C$,
#' for any positive scalars $\alpha, \beta$, and any $x, y \in C$.

#' ## Conic Programming
#' Conic programming problems are convex optimization problems in which a convex function is minimized
#' over the intersection of an affine subspace and a convex cone.
#' An example of a conic-form minimization problems, in the primal form is:

#' $$
#' \begin{align}
#' & \min_{x \in \mathbb{R}^n} & a_0^T x + b_0 \\
#' & \;\;\text{s.t.} & A_i x + b_i & \in \mathcal{C}_i & i = 1 \ldots m
#' \end{align}
#' $$

#' The corresponding dual problem is:

#' $$
#' \begin{align}
#' & \max_{y_1, \ldots, y_m} & -\sum_{i=1}^m b_i^T y_i + b_0 \\
#' & \;\;\text{s.t.} & a_0 - \sum_{i=1}^m A_i^T y_i & = 0 \\
#' & & y_i & \in \mathcal{C}_i^* & i = 1 \ldots m
#' \end{align}
#' $$

#' where each $\mathcal{C}_i$ is a closed convex cone and $\mathcal{C}_i^*$ is its dual cone.

#' ## Some of the Types of Cones Supported by JuMP

using JuMP
using ECOS
using LinearAlgebra
using Random

Random.seed!(1234);

#' By this point we have used quite a few different solvers.
#' To find out all the different solvers and their supported problem types, check out the
#' [solver table](http://www.juliaopt.org/JuMP.jl/v0.19.0/installation/#Getting-Solvers-1) in the docs.

#' ### Second-Order Cone
#' The Second-Order Cone (or Lorenz Cone) of dimension $n$ is of the form:

#' $$
#' Q^n = \{ (t,x) \in \mathbb{R}^\mbox{n} : t \ge ||x||_2 \}
#' $$

#' A Second-Order Cone rotated by $\pi/4$ in the $(x_1,x_2)$ plane is called a Rotated Second-Order Cone.
#' It is of the form:

#' $$
#' Q_r^n = \{ (t,u,x) \in \mathbb{R}^\mbox{n} : 2tu \ge ||x||_2^2, t,u \ge 0 \}
#' $$

#' These cones are represented in JuMP using the MOI sets `SecondOrderCone` and `RotatedSecondOrderCone`.

#' #### Example: Euclidean Projection on a Hyperplane
#' For a given point $u_{0}$ and a set $K$, we refer to any point $u \in K$
#' which is closest to $u_{0}$ as a projection of $u_{0}$ on $K$.
#' The projection of a point $u_{0}$ on a hyperplane $K = \{u | p' \cdot u = q\}$ is given by

#' $$
#' \begin{align*}
#' & \min & ||u - u_{0}|| \\
#' & \;\;\text{s.t.} & p' \cdot u = q  \\
#' \end{align*}
#' $$

u0 = rand(10)
p = rand(10)
q = rand();

#' We can model the above problem as the following conic program:

#' $$
#' \begin{align*}
#' & \min & t \\
#' & \;\;\text{s.t.} & p' \cdot u = q \\
#' & & (t, u - u_{0}) \in Q^{n+1}
#' \end{align*}
#' $$

#' On comparing this with the primal form of a conic problem we saw above,

#' $$
#' \begin{align*}
#' & x = (t , u) &\\
#' & a_0 = e_1 &\\
#' & b_0 = 0 &\\
#' & A_1 = (0, p) &\\
#' & b_1 = -q &\\
#' & C_1 = \mathbb{R}_- &\\
#' & A_2 = 1 &\\
#' & b_2 = -(0, u_0) &\\
#' & C_2 = Q^{n+1} &
#' \end{align*}
#' $$

#' Thus, we can obtain the dual problem as:

#' $$
#' \begin{align*}
#' & \max & y_1 + (0, u_0)^T y_2 \\
#' & \;\;\text{s.t.} & e_1 - (0,p)^T y_1 - y_2 = 0 \\
#' & & y_1 \in \mathbb{R}_- \\
#' & & y_2 \in Q^{n+1}
#' \end{align*}
#' $$

model = Model(optimizer_with_attributes(ECOS.Optimizer, "printlevel" => 0))
@variable(model, u[1:10])
@variable(model, t)
@objective(model, Min, t)
@constraint(model, [t, (u - u0)...] in SecondOrderCone())
@constraint(model, u' * p == q)
optimize!(model)

#+

@show objective_value(model);
@show value.(u);

#+

e1 = [1, zeros(10)...]
dual_model = Model(optimizer_with_attributes(ECOS.Optimizer, "printlevel" => 0))
@variable(dual_model, y1 <= 0)
@variable(dual_model, y2[1:11])
@objective(dual_model, Max, q * y1 + dot(vcat(0, u0), y2))
@constraint(dual_model, e1 - [0, p...] .* y1 - y2 .== 0)
@constraint(dual_model, y2 in SecondOrderCone())
optimize!(dual_model)

#+

@show objective_value(dual_model);

#' We can also have an equivalent formulation using a Rotated Second-Order Cone:

#' $$
#' \begin{align*}
#' & \min & t \\
#' & \;\;\text{s.t.} & p' \cdot u = q \\
#' & & (t, 1/2, u - u_{0})\in Q_r^{n+2}
#' \end{align*}
#' $$

model = Model(optimizer_with_attributes(ECOS.Optimizer, "printlevel" => 0))
@variable(model, u[1:10])
@variable(model, t)
@objective(model, Min, t)
@constraint(model, [t, 0.5, (u - u0)...] in RotatedSecondOrderCone())
@constraint(model, u' * p == q)
optimize!(model)

#+

@show value.(u);

#' The difference here is that the objective in the case of the Second-Order Cone is $||u - u_{0}||_2$,
#' while in the case of a Rotated Second-Order Cone is $||u - u_{0}||_2^2$.
#' However, the value of x is the same for both.

#' ### Exponential Cone

#' An Exponential Cone is a set of the form:

#' $$
#' K_{exp} = \{ (x,y,z) \in \mathbb{R}^3 : y \exp (x/y) \le z, y > 0 \}
#' $$

#' It is represented in JuMP using the MOI set `ExponentialCone`.

#' #### Example: Entropy Maximization
#' As the name suggests, the entropy maximization problem consists of maximizing the entropy function,
#' $H(x) = -x\log{x}$ subject to linear inequality constraints.

#' $$
#' \begin{align*}
#' & \max & - \sum_{i=1}^n x_i \log x_i \\
#' & \;\;\text{s.t.} & \mathbf{1}' x = 1 \\
#' & & Ax \leq b
#' \end{align*}
#' $$

#' We can model this problem using an exponential cone by using the following transformation:

#' $$
#' t\leq -x\log{x} \iff t\leq x\log(1/x)  \iff (1, x, t) \in K_{exp}
#' $$

#' Thus, our problem becomes,

#' $$
#' \begin{align*}
#' & \max & 1^Tt \\
#' & \;\;\text{s.t.} & Ax \leq b \\
#' & & 1^T x = 1 \\
#' & & (1, x_i, t_i) \in K_{exp} && \forall i = 1 \ldots n \\
#' \end{align*}
#' $$

n = 15;
m = 10;
A = randn(m, n);
b = rand(m, 1);

model = Model(optimizer_with_attributes(ECOS.Optimizer, "printlevel" => 0))
@variable(model, t[1:n])
@variable(model, x[1:n])
@objective(model, Max, sum(t))
@constraint(model, sum(x) == 1)
@constraint(model, A * x .<= b )
# Cannot use the exponential cone directly in JuMP, hence we use MOI to specify the set.
@constraint(model, con[i = 1:n], [1, x[i], t[i]] in MOI.ExponentialCone())

optimize!(model);

#+

@show objective_value(model);

#' ### Positive Semidefinite Cone
#' The set of Positive Semidefinite Matrices of dimension $n$ form a cone in $\mathbb{R}^n$.
#' We write this set mathematically as

#' $$
#' \mathcal{S}_{+}^n = \{ X \in \mathcal{S}^n \mid z^T X z \geq 0, \: \forall z\in \mathbb{R}^n \}.
#' $$

#' A PSD cone is represented in JuMP using the MOI sets
#' `PositiveSemidefiniteConeTriangle` (for upper triangle of a PSD matrix) and
#' `PositiveSemidefiniteConeSquare` (for a complete PSD matrix).
#' However, it is prefferable to use the `PSDCone` shortcut as illustrated below.

#' #### Example: Largest Eigenvalue of a Symmetric Matrix
#' Suppose $A$ has eigenvalues $\lambda_{1} \geq \lambda_{2} \ldots \geq \lambda_{n}$.
#' Then the matrix $t I-A$ has eigenvalues $t-\lambda_{1}, t-\lambda_{2}, \ldots, t-\lambda_{n}$.
#' Note that $t I-A$ is PSD exactly when all these eigenvalues are non-negative,
#' and this happens for values $t \geq \lambda_{1} .$
#' Thus, we can model the problem of finding the largest eigenvalue of a symmetric matrix as:

#' $$
#' \begin{align*}
#' \lambda_{1} = \max t \\
#' \text { s.t. } t I-A \succeq 0
#' \end{align*}
#' $$

#+ tangle = false

using LinearAlgebra
using SCS

A = [3 2 4;
     2 0 2;
     4 2 3]

model = Model(optimizer_with_attributes(SCS.Optimizer, "verbose" => 0))
@variable(model, t)
@objective(model, Min, t)
@constraint(model, t .* Matrix{Float64}(I, 3, 3) - A in PSDCone())

optimize!(model)

#+ tangle = false

@show objective_value(model);

#' ## Other Cones and Functions
#' For other cones supported by JuMP, check out the
#' [MathOptInterface Manual](http://www.juliaopt.org/MathOptInterface.jl/dev/apimanual/#Standard-form-problem-1).
#' A good resource for learning more about functions which can be modelled using cones is the
#' MOSEK Modeling Cookbook[[1]](#c1).

#' ### References
#'
#' 1. MOSEK Modeling Cookbook — MOSEK Modeling Cookbook 3.1. Available at: https://docs.mosek.com/modeling-cookbook/index.html.


function laplace_matrix(::Type{T}, n, dims) where T
    D = second_order_central_diff(T, n);
    A = copy(D);

    for idx = 2 : dims
        A = kron(A, speye(n)) + kron(speye(size(A, 1)), D);
    end

    A
end

second_order_central_diff(::Type{T}, dim) where {T} = convert(SparseMatrixCSC{T, Int}, SymTridiagonal(fill(2 * one(T), dim), fill(-one(T), dim - 1)))


"""
Verify, that |A * Q - Q * T| ≈ 0
A, Q and T must be Matrices with appropriate sizes.
"""
function checkfactors(A::AbstractMatrix{T1}, Q::AbstractMatrix{T2}, T::AbstractMatrix{T3}; atol::Real = zero(real(eltype(A))), rtol::Real = eps(real(eltype(A)))) where {T1<:Number, T2<:Number, T3<:Number}

  res = norm(A * Q - Q * T, 1)
  tol = rtol * norm(A, 1) + atol
  # println("checkfactors() = $(Float64(res)) < ! $(Float64(tol)) $(Float64(res/tol))")
  res <= rtol * norm(A, 1) + atol
end


### A Pluto.jl notebook ###
# v0.12.7

using Markdown
using InteractiveUtils

# ╔═╡ 6fd616f6-1ea1-11eb-3814-8bfb4a096c49
using Pkg, DrWatson

# ╔═╡ 6ffe2628-1ea1-11eb-24ea-57f985146a72
begin
	@quickactivate "StatisticsWithJuliaPlutoNotebooks"
	using Distributions, Random, StatsBase, DataFrames, Plots, LaTeXStrings
	Random.seed!(1)
end;

# ╔═╡ ed174bc4-1ea0-11eb-1e2f-a32874cec549
md"## Listing 3.26"

# ╔═╡ 70179b62-1ea1-11eb-13ce-7fb4d54bad4b
begin
	alphas = [0.5, 1.5, 1]
	lam = 2

	lambda(dist::Weibull) = shape(dist)*scale(dist)^(-shape(dist))
	theta(lam,alpha) = (alpha/lam)^(1/alpha)

	dists = [Weibull.(a,theta(lam,a)) for a in alphas]

	hA(dist,x) = pdf(dist,x)/ccdf(dist,x)
	hB(dist,x) = lambda(dist)*x^(shape(dist)-1)
end

# ╔═╡ 884176a0-21c0-11eb-06f8-55964a60d533
begin
	xGrid = 0.01:0.01:10
	hazardsA = [hA.(d,xGrid) for d in dists]
	hazardsB = [hB.(d,xGrid) for d in dists]

	Text("Maximum difference between implementations: $(maximum(maximum.(hazardsA-hazardsB)))")
end

# ╔═╡ 8841a7b0-21c0-11eb-313a-bfa4eb49f351
begin
	Cl = [:blue :red :green]
	Lb = [L"\lambda=" * string(lambda(d)) * ",   " * L"\alpha =" * string(shape(d))
			for d in dists]

	plot(xGrid, hazardsA, c=Cl, label=reshape(Lb, 1,:), xlabel="x",
		ylabel="Instantaneous failure rate", xlims=(0,10), ylims=(0,10))
end

# ╔═╡ 70182cd8-1ea1-11eb-094d-8d0d49cf15f3
md"## End of listing 3.26"

# ╔═╡ Cell order:
# ╟─ed174bc4-1ea0-11eb-1e2f-a32874cec549
# ╠═6fd616f6-1ea1-11eb-3814-8bfb4a096c49
# ╠═6ffe2628-1ea1-11eb-24ea-57f985146a72
# ╠═70179b62-1ea1-11eb-13ce-7fb4d54bad4b
# ╠═884176a0-21c0-11eb-06f8-55964a60d533
# ╠═8841a7b0-21c0-11eb-313a-bfa4eb49f351
# ╟─70182cd8-1ea1-11eb-094d-8d0d49cf15f3


abstract type Angle <: Number end

struct Radian <: Angle
    radians::Float64
end

struct DMS <: Angle
    seconds::Int
end

begin
    Degree(degrees::Integer) = Minute(degrees * 60)
    Degree(deg::Integer, min::Integer) = Degree(deg) + Minute(min)
    Degree(deg::Integer, min::Integer, secs::Integer) = Degree(deg, min) + Second(secs)

    function Minute(minutes::Integer)
        DMS(minutes * 60)
    end

    function Second(seconds::Integer)
        DMS(seconds)
    end

    import Base: -, +, show, convert, *, /, promote_rule
    +(Θ::DMS, α::DMS) = DMS(Θ.seconds + α.seconds)
    -(Θ::DMS, α::DMS) = DMS(Θ.seconds - α.seconds)

    +(Θ::Radian, α::Radian) = Radian(Θ.radians + α.radians)
    -(Θ::Radian, α::Radian) = Radian(Θ.radians - α.radians)

    function degrees(dms::DMS)
        minutes = dms.seconds ÷ 60
        minutes ÷ 60
    end

    function minutes(dms::DMS)
        minutes = dms.seconds ÷ 60
        minutes % 60
    end

    seconds(dms::DMS) = dms.seconds % 60

    function show(io::IO, dms::DMS)
    print(io, degrees(dms), "° ", minutes(dms), "' ", seconds(dms), "''")
    end

    function show(io::IO, rad::Radian)
    print(io, rad.radians, "rad")
    end

    # convert constructors
    Radian(dms::DMS) = Radian(deg2rad(dms.seconds/3600))
    DMS(rad::Radian) = DMS(floor(Int, rad2deg(rad.radians) * 3600))

    convert(::Type{Radian},  dms::DMS)    = Radian(dms)
    convert(::Type{DMS},     rad::Radian) = DMS(rad)

    sin(rad::Radian) = Base.sin(rad.radians)
    cos(rad::Radian) = Base.cos(rad.radians)

    sin(dms::DMS) = sin(Radian(dms))
    cos(dms::DMS) = cos(Radian(dms))

    *(coeff::Number, dms::DMS) = DMS(coeff * dms.seconds)
    *(dms::DMS, coeff::Number) = coeff * dms
    /(dms::DMS, denom::Number) = DMS(dms.seconds/denom)

    *(coeff::Number, rad::Radian) = Radian(coeff * rad.radians)
    *(rad::Radian, coeff::Number) = coeff * rad
    /(rad::Radian, denom::Number) = Radian(rad.radians/denom)

    const ° = Degree(1)
    const rad = Radian(1)

    Base.promote_rule(::Type{Radian}, ::Type{DMS}) = Radian
end
# Examples
# α = Degree(90, 30, 45)
# β = α + Degree(89, 29, 15)
# sin(Degree(90))
# sin(Radian(π/2))
# sin(90°)
# sin(1.5rad)

# Problem
# While this works:
# +(promote(90°,3.14rad/2)...)
# 3.140796326794897rad
# Doing this
# 90°+ 3.14rad/2
# gives this error
# ERROR: MethodError: no method matching +(::DMS, ::Radian)
# Closest candidates are:
#   +(::Any, ::Any, ::Any, ::Any...) at operators.jl:560
#   +(::DMS, ::DMS) at REPL[10]:1
#   +(::Radian, ::Radian) at REPL[12]:1
# Stacktrace:
#  [1] top-level scope
#    @ REPL[38]:1
# Solution
# For the solution to the problem see the discussion here
# https://discourse.julialang.org/t/type-promotion-not-working-as-expected/65944
# one solution to get the promotion to work is to subtype Angle from Number as above
# Adding +(Angle, Angle) is probably more in the spirit of what Engheim is trying to do


"""
`solve_system(f,u0,n)` #code

Solves the dynamical system

``u_{n+1} = f(u_n)`` #latex

for N steps. Returns the solution at step `n` with parameters `p`.

"""
function solve_system(f,u0,p,n)
  u = u0
  for i in 1:n-1
    u = f(u,p)
  end
  u
end

f(u,p) = u^2 - p*u
typeof(f)

function lorenz(u,p)
  α,σ,ρ,β = p
  du1 = u[1] + α*(σ*(u[2]-u[1]))
  du2 = u[2] + α*(u[1]*(ρ-u[3]) - u[2])
  du3 = u[3] + α*(u[1]*u[2] - β*u[3])
  [du1,du2,du3]
end
p = (0.02,10.0,28.0,8/3)

function solve_system_save(f,u0,p,n)
  u = Vector{typeof(u0)}(undef,n)
  u[1] = u0
  for i in 1:n-1
    u[i+1] = f(u[i],p)
  end
  u
end

a = solve_system_save(lorenz,[1.0,0.0,0.0],p,1000)

a[1]

reduce(hcat,a)

using BenchmarkTools
function solve_system_save_matrix(f,u0,p,n)
  u = Matrix{eltype(u0)}(undef,length(u0),n)
  u[:,1] = u0
  for i in 1:n-1
    u[:,i+1] = f(u[:,i],p)
  end
  u
end
@btime solve_system_save_matrix(lorenz,[1.0,0.0,0.0],p,1000)
@btime solve_system_save(lorenz,[1.0,0.0,0.0],p,1000)

function solve_system_save_matrix_view(f,u0,p,n)
  u = Matrix{eltype(u0)}(undef,length(u0),n)
  u[:,1] = u0
  for i in 1:n-1
    u[:,i+1] = f(@view(u[:,i]),p)
  end
  u
end
@btime  solve_system_save_matrix_view(lorenz,[1.0,0.0,0.0],p,1000)

function lorenz(du,u,p)
  α,σ,ρ,β = p
  du[1] = u[1] + α*(σ*(u[2]-u[1]))
  du[2] = u[2] + α*(u[1]*(ρ-u[3]) - u[2])
  du[3] = u[3] + α*(u[1]*u[2] - β*u[3])
end
p = (0.02,10.0,28.0,8/3)
function solve_system_save(f,u0,p,n)
  u = Vector{typeof(u0)}(undef,n)
  du = similar(u0) # create cache array, modify it, add to output
  u[1] = u0
  for i in 1:n-1
    f(du,u[i],p)
    u[i+1] = du
  end
  u
end
@btime solve_system_save(lorenz,[1.0,0.0,0.0],p,1000)

function solve_system_save(f,u0,p,n)
  u = Vector{typeof(u0)}(undef,n)
  du = similar(u0) # create cache array, modify it, add to output
  u[1] = u0
  for i in 1:n-1
    f(du,u[i],p)
    u[i+1] = du[:]
  end
  u
end
@btime solve_system_save(lorenz,[1.0,0.0,0.0],p,1000)

b = solve_system_save(lorenz,[1.0,0.0,0.0],p,1000)


# Examples presented in class - Lecture 1
# LP Resource allocation

using JuMP, Cbc
# JuMP is for implementing math. programming models;
# Cbc is for solving them.

# Example 1 - resource allocation

# Problem data
i = 1:2 # i=1: Seattle; i=2: San Diego
j = 1:3 # j=1: New York; j=2: Chicago; j=3: Miami

C = [350 600] # Capacities of the factories
D = [325 300 275] # Demand of clients
T = [2.5 1.7 1.8
     3.5 1.9 1.4] # Transportation costs

# Model implementation
# Creates a model and informs the solver to be used
m = Model(solver = CbcSolver())

# Decision variable for the total transported
@variable(m, x[i,j] >= 0)

# Capacity constraint
@constraint(m, cap[i = 1:2], sum(x[i,j] for j = 1:3) <= C[i])
# Demand constraint
@constraint(m, dem[j = 1:3], sum(x[i,j] for i = 1:2) >= D[j])

# Total distribution cost that we want to minimise
@objective(m, Min, sum(T[i,j]*x[i,j] for i=1:2, j=1:3))

println(m) # Prints the mathematical model for debugging
solve(m) # Solve the model

# Prints the optimal solution
println("\nDistribution plan: \n", getvalue(x))


struct Connection
    weight::AbstractArray
    adjacency::AbstractArray
end

function randomConnection(n_source::Int, n_target::Int, p::AbstractFloat;
    min_weight=0, max_weight=1, amplitude=nothing, seed=nothing)
    if !isnothing(seed)
        Random.seed!(seed)
    end
    adjacency = rand(n_source, n_target) .< p
    if isnothing(amplitude)
        weights = rand(n_source, n_target) .* (max_weight - min_weight) .+ min_weight
    else
        weights = ones(n_source, n_target) .* amplitude
    end
    weights .*= adjacency
    return Connection(weights, adjacency)
end

function randomConnection(n::Int, p::AbstractFloat; min_weight=0, max_weight=1,
    amplitude=nothing, seed=nothing)
    randomConnection(n, n, p; min_weight, max_weight, amplitude, seed)
end

function EI(Nₑ::Integer, Nᵢ::Integer;
    E_E::Tuple=(0, 1), E_I::Tuple=(0, 1), I_E::Tuple=(0, 1), I_I::Tuple=(0, 1),
    adjacency::Union{Nothing,AbstractMatrix}=nothing, shuffle::Bool=false, seed=nothing
)
    excitatory = [ones(Bool, Nₑ); zeros(Bool, Nᵢ)]
    if shuffle
        if !isnothing(seed)
            Random.seed!(seed)
        end
        shuffle!(excitatory)
    end
    inhibitory = .!excitatory

    min_weight = (excitatory * transpose(excitatory)) .* E_E[1]
    min_weight += (excitatory * transpose(inhibitory)) .* E_I[1]
    min_weight -= (inhibitory * transpose(excitatory)) .* abs(I_E[2])
    min_weight -= (inhibitory * transpose(inhibitory)) .* abs(I_I[2])

    max_weight = (excitatory * transpose(excitatory)) .* E_E[2]
    max_weight += (excitatory * transpose(inhibitory)) .* E_I[2]
    max_weight -= (inhibitory * transpose(excitatory)) .* abs(I_E[1])
    max_weight -= (inhibitory * transpose(inhibitory)) .* abs(I_I[1])

    if !isnothing(adjacency)
        min_weight .*= adjacency
        max_weight .*= adjacency
    end

    return excitatory, inhibitory, min_weight, max_weight
end

function EI(n::Integer, EI_rate::AbstractFloat;
    E_E::Tuple=(0, 1), E_I::Tuple=(0, 1), I_E::Tuple=(0, 1), I_I::Tuple=(0, 1),
    adjacency::Union{Nothing,AbstractMatrix}=nothing, shuffle::Bool=false, seed=nothing
)
    if EI_rate > 1
        Nₑ = Int(ceil(EI_rate * n / (EI_rate + 1)))
    else
        Nₑ = Int(ceil(EI_rate * n))
    end
    Nᵢ = n - Nₑ
    return EI(Nₑ, Nᵢ; E_E, E_I, I_E, I_I, adjacency, shuffle, seed)
end


@doc raw"""
Compute δL = L(0)-L(h) where L is the quadratic model

```math
L(h)=f(\theta) + \langle \nabla f, h \rangle + \frac{1}{2}\langle \nabla^2 f h, h \rangle
```

with ``f(\theta)=\frac{1}{2}\| r(θ) \|_2^2``, ``\nabla f = J^t r`` and ``\nabla^2 f = J^t J``

A direct computation gives:

```math
δL = L(0)-L(h) = -\left(  \langle J^tr, h \rangle + \frac{1}{2}\langle \nabla^2 f h, h \rangle \right)
```

However one can avoid the computation of ``\nabla^2 f h`` if one uses the
fact that ``h`` is solution of:

```math
(\nabla^2 f + \mu I)h + \nabla f = 0
```

With this hypothesis, one gets:

```math
δL = L(0)-L(h) = \frac{1}{2} \langle h, \mu h - \nabla f \rangle
```
"""
function compute_δL_unconstrained(∇f::AbstractVector,
                                  μ::Real,
                                  h::AbstractVector)

    # The syntax to desctructure is unexpected...
    # https://discourse.julialang.org/t/argument-destructuring-and-anonymous-functions/24893/2
    #
    mapreduce(((h_i,∇f_i),)->h_i*(μ*h_i-∇f_i),+,zip(h,∇f))/2
end

@doc raw"""

Same idea than [`compute_δL_unconstrained`](@ref), however when bound
constraints are present ``h`` is such that:

```math
(\nabla^2 f + \mu I)h + \nabla f + \tau = 0
```
it follows that:

```math
δL = L(0)-L(h) = \frac{1}{2} \langle h, \mu h + \tau - \nabla f \rangle
```
"""
function compute_δL_constrained(∇f::AbstractVector,
                                μ::Real,
                                τ::AbstractVector,
                                h::AbstractVector)
    mapreduce(((h_i,τ_i,∇f_i),)->h_i*(μ*h_i+τ_i-∇f_i),+,zip(h,τ,∇f))/2
end

@doc raw"""

Compute true variation of the real model: ``δf = \frac{1}{2}(r^t(θ)r(θ)-r^t(θ+h)r(θ+h))``

Contrary to ``δL`` things are simpler. However a trick is to use an equivalent formulation:

```math
δf = \frac{1}{2}(r^t(θ)r(θ)-r^t(θ+h)r(θ+h)) = \frac{1}{2}(r(θ)-r(θ+h))^t(r(θ)+r(θ+h))
```

that has a better numerical behavior.
"""
function compute_δf(r::AbstractVector,
                    r_new::AbstractVector)
    mapreduce(((r_i,r_new_i),)->(r_i-r_new_i)*(r_i+r_new_i),+,zip(r,r_new))/2
end


# indicator of the L0 norm ball with given (integer) radius

export IndBallL0

"""
**Indicator of a ``L_0`` pseudo-norm ball**

    IndBallL0(r=1)

Returns the indicator function of the set
```math
S = \\{ x : \\mathrm{nnz}(x) \\leq r \\}.
```
Parameter `r` must be a positive integer.
"""
struct IndBallL0{I <: Integer} <: ProximableFunction
  r::I
  function IndBallL0{I}(r::I) where {I <: Integer}
    if r <= 0
      error("parameter r must be a positive integer")
    else
      new(r)
    end
  end
end

is_set(f::IndBallL0) = true

IndBallL0(r::I) where {I <: Integer} = IndBallL0{I}(r)

function (f::IndBallL0)(x::AbstractArray{T}) where T <: RealOrComplex
  if count(!isequal(0), x) > f.r
    return +Inf
  end
  return zero(T)
end

function prox!(y::AbstractArray{T}, f::IndBallL0, x::AbstractArray{T}, gamma::Real=1.0) where T <: RealOrComplex
  p = []
  if ndims(x) == 1
    p = partialsortperm(x, 1:f.r, by=abs, rev=true)
  else
    p = partialsortperm(x[:], 1:f.r, by=abs, rev=true)
  end
  sort!(p)
  idx = 1
  for i = 1:length(p)
    y[idx:p[i]-1] .= zero(T)
    y[p[i]] = x[p[i]]
    idx = p[i]+1
  end
  y[idx:end] .= zero(T)
  return zero(T)
end

fun_name(f::IndBallL0) = "indicator of an L0 pseudo-norm ball"
fun_dom(f::IndBallL0) = "AbstractArray{Real}, AbstractArray{Complex}"
fun_expr(f::IndBallL0) = "x ↦ 0 if countnz(x) ⩽ r, +∞ otherwise"
fun_params(f::IndBallL0) = "r = $(f.r)"

function prox_naive(f::IndBallL0, x::AbstractArray{T}, gamma::Real=1.0) where T <: RealOrComplex
  p = sortperm(abs.(x)[:], rev=true)
  y = similar(x)
  y[p[1:f.r]] .= x[p[1:f.r]]
  y[p[f.r+1:end]] .= 0.0
  return y, 0.0
end


"The set of all natural numbers."
struct NaturalNumbers <: Domain{Int}
end

in(x, d::NaturalNumbers) = isinteger(x) && (x >= 0)
in(x::AbstractArray, d::NaturalNumbers) = false
==(::NaturalNumbers, ::NaturalNumbers) = true

approx_in(x::Real, d::NaturalNumbers, tol) =
    (abs(x-round(x)) < tol) && (round(Int, x) ∈ d)

"The set of all integers."
struct Integers <: Domain{Int}
end

in(x, d::Integers) = isinteger(x)
in(x::AbstractArray, d::Integers) = false
==(::Integers, ::Integers) = true

approx_in(x::Real, d::Integers, tol) =
    (abs(x-round(x)) < tol) && (round(Int, x) ∈ d)

"The set of all real numbers."
struct RealNumbers <: Domain{Float64}
end

in(x::Number, d::RealNumbers) = isreal(x)
# isreal also allows real arrays, we want to disallow that here:
in(x::AbstractArray, d::RealNumbers) = false

approx_in(x::Complex, d::RealNumbers, tol) = (imag(x) < tol) && (real(x) ∈ d)
==(::RealNumbers, ::RealNumbers) = true

"The set of all rationals."
struct Rationals <: Domain{Rational{Int}}
end

in(x, d::Rationals) = x ∈ Integers()
in(x::Rational, d::Rationals) = true
==(::Rationals, ::Rationals) = true

"The set of all complex numbers whose real and imaginary parts are real numbers."
struct ComplexNumbers <: Domain{Complex{Float64}}
end

in(x::Complex{T}, d::ComplexNumbers) where {T} =
    isreal(real(x)) && isreal(imag(x))
in(x::Complex{T}, d::ComplexNumbers) where {T<:Real} = true
in(x, d::ComplexNumbers) = x ∈ RealNumbers()
==(::ComplexNumbers, ::ComplexNumbers) = true

"The set of natural numbers."
const ℕ = NaturalNumbers()
"The set of integers."
const ℤ = Integers()
"The set of rational numbers."
const ℚ = Rationals()
"The set of real numbers."
const ℝ = RealNumbers()
"The set of complex numbers."
const ℂ = ComplexNumbers()

"The space ℝ^1."
const ℝ1 = VcatDomain(ℝ)
"The space ℝ^2."
const ℝ2 = VcatDomain(ℝ, ℝ)
"The space ℝ^3."
const ℝ3 = VcatDomain(ℝ, ℝ, ℝ)
"The space ℝ^4."
const ℝ4 = VcatDomain(ℝ, ℝ, ℝ, ℝ)


function tinterval(x, δ, op)
    μ = mean(x)
    σ = std(x, mean=μ)
    t = 0.0
    n = length(x)
    Zygote.ignore() do
        t = quantile(TDist(n-1), δ)
    end
    s = t * σ / √n
    return op(μ,s)
end

function tdist_ci(;δ=0.05, tail=:both)
    if tail == :right
        return x->tinterval(x,δ,(μ,s)->μ-s) # quantile of 0.05 is negative so need to subtract to get upper
    elseif tail == :left
        return x->tinterval(x,δ,(μ,s)->μ+s) # need to add to get lower bound
    else
        return x->tinterval(x,δ/2,(μ,s)->(μ+s,μ-s))
    end
end

function andersons_ucb(x, b, δ)
    n = length(x)
    ϵ = √(log(1/δ)/(2n))
    r = floor(Int, n*ϵ)
    upper = (1/n) * ((r+1)*x[r+1] + sum(view(x, r+1:n))) + ϵ * (b - x[r+1])
    return upper
end

function andersons_lcb(x, a, δ)
    n = length(x)
    ϵ = √(log(1/δ)/(2n))
    s = floor(Int, n*ϵ)
    lower = (1/n) * (sum(view(x, 1:n-s-1)) + (s+1)*x[n-s]) - ϵ * (x[n-s] - a)
    return lower
end

function andersons_ci(a, b; δ=0.05, tail=:both)
    if tail == :both
        function f(x)
            z = sort(x)
            upper = andersons_ucb(z, b, δ/2)
            lower = andersons_lcb(z, a, δ/2)
            return lower, upper
        end
        return f
    elseif tail == :left
        return x->andersons_lcb(sort(x), a, δ)
    elseif tail == :right
        return x->andersons_ucb(sort(x), b, δ)
    else
        throw(ArgumentError("tail=$(tail) is invalid"))
    end
end


# Squared Exponential Function with ARD

"""
    SEArd <: StationaryARD{WeightedSqEuclidean}

ARD Squared Exponential kernel (covariance)
```math
k(x,x') = σ²\\exp(- (x - x')ᵀL⁻²(x - x')/2)
```
with length scale ``ℓ = (ℓ₁, ℓ₂, …)`` and signal standard deviation ``σ`` where
``L = diag(ℓ₁, ℓ₂, …)``.
"""
mutable struct SEArd{T<:Real} <: StationaryARD{WeightedSqEuclidean}
    "Inverse squared length scale"
    iℓ2::Vector{T}
    "Signal variance"
    σ2::T
    "Priors for kernel parameters"
    priors::Array
end

"""
Squared Exponential Function with ARD

    SEArd(ll::Vector{Real}, lσ::Real)

# Arguments
  - `ll::Vector{Real}`: vector of length scales (given on log scale)
  - `lσ::Real`: signal standard deviation (given on log scale)
"""
SEArd(ll::Vector{T}, lσ::T) where T = SEArd{T}(exp.(-2 .* ll), exp(2 * lσ), [])

function set_params!(se::SEArd, hyp::AbstractVector)
    length(hyp) == num_params(se) || throw(ArgumentError("SEArd has $(num_params(se)) parameters, received $(length(hyp))."))
    @views @. se.iℓ2 = exp(-2 * hyp[1:(end-1)])
    se.σ2 = exp(2 * hyp[end])
end

get_params(se::SEArd) = [-log.(se.iℓ2) / 2 ; log(se.σ2) / 2]
get_param_names(k::SEArd) = [get_param_names(k.iℓ2, :ll); :lσ]
num_params(se::SEArd) = length(se.iℓ2) + 1

cov(se::SEArd, r::Number) = se.σ2*exp(-r / 2)

@inline dk_dll(se::SEArd, r::Real, wdiffp::Real) = wdiffp*cov(se,r)
@inline function dKij_dθp(se::SEArd, X1::AbstractMatrix, X2::AbstractMatrix, i::Int, j::Int, p::Int, dim::Int)
    if p <= dim
        return dk_dll(se, distij(metric(se),X1,X2,i,j,dim), distijk(metric(se),X1,X2,i,j,p))
    elseif p==dim+1
        return dk_dlσ(se, distij(metric(se),X1,X2,i,j,dim))
    else
        return NaN
    end
end


function bitCount(n::Int)
	count = 0
	while n>0
		count += n & 1
		n >>= 1
	end
	return count
end

function findFirstBit(n::Int)
	n > 0 || throw(DomainError(" n needs to be larger than 0"))

	breakcondition = 0
	searchposition = 1
	idx = 0
	while breakcondition==0
		breakcondition = n & searchposition
		searchposition <<= 1
		idx += 1
	end

	return idx
end

function getBitPos(n::Int)

	Pos = Vector{Int}([])
	while n > 0

		idx = findFirstBit(n)
		push!(Pos,idx)

		n -= 2^(idx-1)

	end

	return Pos
end

"""
    bitSwap(n, i, j)

swaps bit i with j in n, for e.g.

n = 001001 , i = 1, j = 2 results in bitSwap(n,i,j) = 001010

#Arguments
- n: bit-string represented as an integer
- i: position of the first bit which should be swaped with the second
- j: position of the second bit which should be swaped with the first

"""
function bitSwap(n::Int, i::Int, j::Int)
	i -= 1
	j -= 1

	bit1 = (n >> i) & 1
	bit2 = (n >> j) & 1

	x = bit1 ⊻ bit2

	x = (x << i) | (x << j)

	return (n ⊻ x)
end

function FermionicBitSwap(n::Int, j::Int, k::Int)
	n_new = bitSwap(n,j,k)
	untouchedBit =  n & n_new
	inbetween = j<k ? (j+1:k-1) : (k+1:j-1)
	c = Int(sum(2.0.^[x-1 for x in inbetween]))
	overjumped_fermions = bitCount(untouchedBit & c)
	return n_new, (-1)^overjumped_fermions
end


"""
  Rudimentary adaptive function sampling
  Adapted from https://stackoverflow.com/a/14084715
"""
function sample_function(func, nodes, values=func.(nodes), mask=Colon();
    depth=0, min_nodes=16, max_level=16, tol=0.01)

  if depth > max_level
    return nodes, values
  end

  # Calculate the function in between the nodes
  x_c = 0.5 * (nodes[1:end-1][mask] + nodes[2:end][mask])
  y_c = func.(x_c)

  x₂ = vcat(nodes, x_c)
  y₂ = vcat(values, y_c)
  is = sortperm(x₂)

  x₂ = x₂[is]
  y₂ = y₂[is]

  # Determine where to refine
  if length(x₂) < min_nodes
    mask = trues(length(x₂) - 1)
  else
    # Rescale and compute the length of each line segment in the path
    dx = diff(x₂) / ptp(x₂)
    dy = diff(y₂) / abs(ptp(y₂))
    ds = sqrt.(abs2.(dx) + abs2.(dy))

    # Compute the angle between consecutive line segments θ = acos(a⋅b / (|a||b|))
    dx ./= ds
    dy ./= ds

    if eltype(dy) <: Complex
      @views dy_re = reinterpret(Float64, dy)[1:2:end]
      @views dy_im = reinterpret(Float64, dy)[2:2:end]
      dθ = acos.(clamp.(dx[2:end] .* dx[1:end-1] + dy_re[2:end] .* dy_re[1:end-1] + dy_im[2:end] .* dy_im[1:end-1], -1, 1))
    else
      dθ = acos.(clamp.(dx[2:end] .* dx[1:end-1] + dy[2:end] .* dy[1:end-1], -1, 1))
    end

    # Determine where to subdivide (total length of the path is computed ≈ to accuracy `tol`)
    mask = 0.5 * dθ .* (ds[2:end] .+ ds[1:end-1]) .> tol * sum(ds)
    mask = [mask; false]
    mask[2:end] .|= mask[1:end-1]
  end

  # Refine!
  if any(mask)
    return sample_function(func, x₂, y₂, mask; tol=tol, depth=depth+1, min_nodes=min_nodes, max_level=max_level)
  else
    return x₂, y₂
  end
end


### A Pluto.jl notebook ###
# v0.17.2

using Markdown
using InteractiveUtils

# ╔═╡ 38fdd4ef-c383-4f97-8451-c6f602307e7d
using Images

# ╔═╡ 3dceca5d-7d1e-425b-9516-24e0a24adaff
using LinearAlgebra
# examples follow:

# ╔═╡ 3eb50f44-9091-45e8-a7e1-92d25b4b2090
begin
	using StaticArrays
	Option{T} = Union{Missing, T}

	squared_length(v::SVector{3,Float32}) = v ⋅ v
	near_zero(v::SVector) = squared_length(v) < 1e-5
end

# ╔═╡ 0866add2-9b95-45e7-8081-c01cd2a66911
md"""# Chapter 1: Overview"""

# ╔═╡ 611d5eae-4b09-11ec-27bf-ef4a1ecdcc41
md"Adapted from [Ray Tracing In One Weekend by Peter Shirley](https://raytracing.github.io/books/RayTracingInOneWeekend.html) and [cshenton's Julia implementation](https://github.com/cshenton/RayTracing.jl)"

# ╔═╡ 97bb4432-ed41-423b-b4d9-bafc519de641
md"# Chapter 2 - Manipulating images"

# ╔═╡ 84d29423-cf11-41c3-af4d-c5f63b1ef23e
img = rand(4, 3)

# ╔═╡ 7d26fef0-9a06-479a-ae08-e9d04e455767
img_rgb = rand(RGB{Float32}, 4, 4)

# ╔═╡ f29ad2c0-c3ff-484d-8fdd-dff34d2bb863
ex1 = [RGB{Float32}(1,0,0) RGB{Float32}(0,1,0) RGB{Float32}(0,0,1);
       RGB{Float32}(1,1,0) RGB{Float32}(1,1,1) RGB{Float32}(0,0,0)]

# ╔═╡ 2192e695-4378-4b47-8ce0-353636cd2cd1
begin
	ex2 = zeros(RGB{Float32}, 2, 3)
	ex2[1,1] = RGB{Float32}(1,0,0)
	ex2
end

# ╔═╡ 538d1aa5-07f9-4fca-8410-ef63b8a6857b
# TODO: save as image, e.g. PNG

# ╔═╡ 8aeb7373-6bb0-4544-8655-fa941561688c
# The "Hello World" of graphics
function gradient(nx::Int, ny::Int)
	img = zeros(RGB{Float32}, ny, nx)
	for i in 1:ny, j in 1:nx # Julia is column-major, i.e. iterate 1 column at a time
		x = j; y = (ny-i);
		r = x/nx
		g = y/ny
		b = 0.2
		img[i,j] = RGB{Float32}(r,g,b)
	end
	img
end

# ╔═╡ 154d736b-8fdc-44af-ae2a-9e5ba6d2c92e
gradient(200,100)

# ╔═╡ 216922d8-613a-4ac1-9559-40878e6587e2
md"""Unlike the C++ implementation:
- Julia uses i for row, j for column, so I inverted the C++ code's variable names.
- The C++ code used a Y-up coordinate system, so I used (ny-i) instead of i for the row number."""

# ╔═╡ 961fd749-d439-4dfa-ae21-b1659dc54511
md"# Chapter 3: Linear Algebra"

# ╔═╡ f8007c75-9487-414a-9592-138a696c2957
# Dot product (\cdot) is defined by LinearAlgebra...
[1; 1] ⋅ [2; 3]

# ╔═╡ 668030c8-24a7-4aa6-b858-cedf8ac5f988
# Cross product (\times)
[0;1;0] × [0;0;1]

# ╔═╡ 78f209df-d176-4711-80fc-a8054771f105
t_col = @SVector[0.4f0, 0.5f0, 0.1f0] # Float32 test color

# ╔═╡ e88de775-6904-4182-8209-06db22758470
t_col[1] # first, i.e. red, component

# ╔═╡ 5fd1ec87-3616-448a-ab4d-fede804b26d5
squared_length(t_col)

# ╔═╡ a0893bf4-9607-4853-8162-9f34d3337060
rgb(v::SVector{3,Float32}) = RGB(v[1], v[2], v[3]) # IIRC RGB(v...) was much slower...

# ╔═╡ cfbcb883-d12e-4ad3-a084-064749bddcdb
rgb(t_col)

# ╔═╡ 6348c03d-e8ec-4dbb-9f8a-8e4a48bb1cb3
rgb_gamma2(v::SVector{3,Float32}) = RGB(sqrt.(v)...) # claforte: try to expand?

# ╔═╡ dbc8fc2d-39c2-4ec9-b82d-7c6a8b12dde7
rgb_gamma2(t_col)

# ╔═╡ 53832af1-a9be-4e02-8b71-a70dae63c233
struct Ray
	origin::SVector{3,Float32}
	dir::SVector{3,Float32} # direction (unit vector)
end

# ╔═╡ 81b4c9e4-9f93-45ca-9fa0-7e9686a55e9a
# equivalent to C++'s ray.at()
#"3D Point of ray `r` evaluated at parameter t"
point(r::Ray, t::Float32)::SVector{3,Float32} = r.origin .+ t .* r.dir

# ╔═╡ 772674b0-5c40-4457-9526-5b4e81cca711
md"""# Random vectors
C++'s section 8.1"""

# ╔═╡ 709861a3-202a-4bff-a46d-b46c6e14b334
# equiv to random_double()
random_between(min=0.0f0, max=1.0f0) = rand(Float32)*(max-min) + min

# ╔═╡ 135000d6-a675-4d8b-9385-50853bf9a169
random_vec3(min::Float32, max::Float32) = @SVector[random_between(min,max) for i∈1:3]

# ╔═╡ 7d1146d7-74da-42e6-92c2-1312ed03f70d
random_vec2(min::Float32, max::Float32) = @SVector[random_between(min,max) for i∈1:2]

# ╔═╡ 46a4aa4b-82c1-4941-b36d-dc8c977af2bc
function random_vec3_in_sphere() # equiv to random_in_unit_sphere()
	while true
		p = random_vec3(-1f0, 1f0)
		if p⋅p <= 1
			return p
		end
	end
end

# ╔═╡ 203a9b25-742a-4d8b-adee-862ce4c67670
squared_length(random_vec3_in_sphere())

# ╔═╡ 2e98c6bd-0289-4105-ad25-24414ecf2750
#"Random unit vector. Equivalent to C++'s `unit_vector(random_in_unit_sphere())`"
random_vec3_on_sphere() = normalize(random_vec3_in_sphere())

# ╔═╡ c0c23cb3-6a57-487d-943a-af4330a94ffe
function random_vec2_in_disk() # equiv to random_in_unit_disk()
	while true
		p = random_vec2(-1f0, 1f0)
		if p⋅p <= 1
			return p
		end
	end
end

# ╔═╡ 678214c5-de81-489f-b002-c343d48071c9
md"# Chapter 4: Rays, simple camera, and background"

# ╔═╡ cbb6418c-79e9-4359-80a6-40a8fa40679e
function skycolor(ray::Ray)
	# NOTE: unlike in the C++ implem., we assume the ray direction is pre-normalized.
	white = @SVector[1.0f0,1.0f0,1.0f0]
	skyblue = @SVector[0.5f0,0.7f0,1.0f0]
	t = 0.5f0(ray.dir[2] + 1.0f0)
    (1.0f0-t)*white + t*skyblue
end

# ╔═╡ 14be6068-6a15-4fad-ac0a-f156da71f103
rgb(@SVector[0.5f0, 0.7f0, 1.0f0]), rgb(@SVector[1.0f0, 1.0f0, 1.0f0])

# ╔═╡ 64ef0313-2d2b-49d5-a1a1-3b04426a82f8
begin
	_origin = @SVector[0.0f0,0.0f0,0.0f0]
	_v3_minusY = @SVector[0.0f0,-1.0f0,0.0f0]
	_t_ray1 = Ray(_origin, _v3_minusY)
end

# ╔═╡ 971777a6-f269-4344-8dba-7a55118396e5
# """ Temporary function to shoot rays through each pixel. Later replaced by `render`
#
# 	Args:
# 		scene: a function that takes a ray, returns the color of any object it hit
# """
function main(nx::Int, ny::Int, scene)
	lower_left_corner = @SVector[-2,-1,-1]
	horizontal = @SVector[4,0,0]
	vertical = @SVector[0f0,2f0,0f0]
	origin = @SVector[0,0,0]

	img = zeros(RGB{Float32}, ny, nx)
	for i in 1:ny, j in 1:nx # Julia is column-major, i.e. iterate 1 column at a time
		u = j/nx
		v = (ny-i)/ny # Y-up!
		ray = Ray(origin, normalize(lower_left_corner + u*horizontal + v*vertical))
		#r = x/nx
		#g = y/ny
		#b = 0.2
		img[i,j] = rgb(scene(ray))
	end
	img
end

# ╔═╡ 655ffa6c-f1e9-4149-8f1d-51145c5a51e4
main(200,100, skycolor)

# ╔═╡ 9075f8ed-f319-486d-94b2-486806aba3fd
md"# Chapter 5: Add a sphere"

# ╔═╡ c00e2004-2002-4dd2-98ed-f898ef2c14f1
function hit_sphere1(center::SVector{3,Float32}, radius::Float32, r::Ray)
	oc = r.origin - center
	a = r.dir ⋅ r.dir
	b = 2oc ⋅ r.dir
	c = (oc ⋅ oc) - radius*radius
	discriminant = b*b - 4a*c
	discriminant > 0
end

# ╔═╡ b7399fb8-6205-41ea-9c70-eb62daedcefb
function sphere_scene1(r::Ray)
	# sphere of radius 0.5 centered at z=-1
	if hit_sphere1(@SVector[0f0,0f0,-1f0], 0.5f0, r)
		return @SVector[1f0,0f0,0f0] # red
	else
		skycolor(r)
	end
end

# ╔═╡ 1d04159d-87bd-4cf8-a73c-817f20ca1026
main(200,100,sphere_scene1)

# ╔═╡ fed81f09-e4a0-433f-99e8-261096114b7b
md"# Chapter 6: Surface normals and multiple objects"

# ╔═╡ 24e8740a-8e44-4206-b2b6-c4a55002dad8
function hit_sphere2(center::SVector{3,Float32}, radius::Float32, r::Ray)
	oc = r.origin - center
	a = r.dir ⋅ r.dir
	b = 2oc ⋅ r.dir
	c = (oc ⋅ oc) - radius*radius
	discriminant = b*b - 4a*c
	if discriminant < 0
		return -1
	else
		return (-b - sqrt(discriminant)) / 2a
	end
end

# ╔═╡ 359832af-7598-4c45-8033-c28cb0d86772
function sphere_scene2(r::Ray)
	sphere_center = SVector{3,Float32}(0f0,0f0,-1f0)
	t = hit_sphere2(sphere_center, 0.5f0, r) # sphere of radius 0.5 centered at z=-1
	if t > 0f0
		n⃗ = normalize(point(r, t) - sphere_center) # normal vector. typed n\vec
		return 0.5f0n⃗ + SVector{3,Float32}(0.5f0,0.5f0,0.5f0) # remap normal to rgb
	else
		skycolor(r)
	end
end

# ╔═╡ ed6ab8be-587c-4cb6-8172-618c74d3f9cc
main(200,100,sphere_scene2)

# ╔═╡ a65c68c9-e489-465a-9687-93ae9da14a5e
"An object that can be hit by Ray"
abstract type Hittable end

# ╔═╡ 2c4b4453-1a46-4889-9a14-16b18cc8c240
"""Materials tell us how rays interact with a surface"""
abstract type Material end

# ╔═╡ 98c43f3f-4bfc-49db-806a-850b7d75b5a4
begin
	struct NoMaterial <: Material end

	const _no_material = NoMaterial()
	const _y_up = @SVector[0f0,1f0,0f0]
end

# ╔═╡ 3b570d37-f407-41d8-b8a0-a0af4d85b14d
"Record a hit between a ray and an object's surface"
mutable struct HitRecord
	t::Float32 # vector from the ray's origin to the intersection with a surface.

	# If t==Inf32, there was no hit, and all following values are undefined!
	#
	p::SVector{3,Float32} # point of intersection between an object's surface and ray
	n⃗::SVector{3,Float32} # local normal (see diagram below)

	# If true, our ray hit from outside to the front of the surface.
	# If false, the ray hit from within.
	front_face::Bool
	mat::Material

	HitRecord() = new(Inf32) # no hit!
	HitRecord(t,p,n⃗,front_face,mat) = new(t,p,n⃗,front_face,mat)
end

# ╔═╡ 138bb5b6-0f45-4f13-8339-5110eb7cd1ff
struct Sphere <: Hittable
	center::SVector{3,Float32}
	radius::Float32
	mat::Material
end

# ╔═╡ 6b36d245-bf01-45a7-b119-8315226dd4a3
HTML("""The geometry defines an `outside normal`. A HitRecord stores the `local normal`.
 """)

# ╔═╡ 4a396b3f-f920-4ec2-91f6-7d61fe2b9699
#"""Equivalent to `hit_record.set_face_normal()`"""
function ray_to_HitRecord(t, p, outward_n⃗, r_dir::SVector{3,Float32}, mat::Material)
	front_face = r_dir ⋅ outward_n⃗ < 0
	n⃗ = front_face ? outward_n⃗ : -outward_n⃗
	rec = HitRecord(t,p,n⃗,front_face,mat)
end

# ╔═╡ 427f247c-055c-459e-9862-26e9f6f3e24f
struct Scatter
	r::Ray
	attenuation::SVector{3,Float32}

	# claforte: TODO: rename to "absorbed?", i.e. not reflected/refracted?
	reflected::Bool # whether the scattered ray was reflected, or fully absorbed
	Scatter(r,a,reflected=true) = new(r,a,reflected)
end

# ╔═╡ 88e51c27-0f28-4dcc-b9e9-ac44eeb876f5
#"Diffuse material"
mutable struct Lambertian<:Material
	albedo::SVector{3,Float32}
end

# ╔═╡ 7c4a67b2-8208-4cd4-b1ea-16f6f50adfe8
# """Compute reflection vector for v (pointing to surface) and normal n⃗.

# 	See [diagram](https://raytracing.github.io/books/RayTracingInOneWeekend.html#metal/mirroredlightreflection)"""
reflect(v::SVector{3,Float32}, n⃗::SVector{3,Float32}) = v - (2v⋅n⃗)*n⃗

# ╔═╡ ca649864-5a6d-4ca7-896e-e80e8a48443e
reflect(@SVector[0.6f0,-0.8f0,0f0], @SVector[0f0,1f0,0f0]) # diagram's example

# ╔═╡ 485f9c5b-4c5d-453c-b190-e84ae0cd1a21
# """Create a scattered ray emitted by `mat` from incident Ray `r`.

# 	Args:
# 		rec: the HitRecord of the surface from which to scatter the ray.

# 	Return missing if it's fully absorbed. """
function scatter(mat::Lambertian, r::Ray, rec::HitRecord)::Scatter
	scatter_dir = rec.n⃗ + random_vec3_on_sphere()
	if near_zero(scatter_dir) # Catch degenerate scatter direction
		scatter_dir = rec.n⃗
	else
		scatter_dir = normalize(scatter_dir)
	end
	scattered_r = Ray(rec.p, scatter_dir)
	attenuation = mat.albedo
	return Scatter(scattered_r, attenuation)
end

# ╔═╡ c63d10c2-dd43-4836-83ee-61b782545a02
const _no_hit = HitRecord() # will be reused

# ╔═╡ 78efebc5-53fd-417d-bd9e-667fd504e3fd
function hit(s::Sphere, r::Ray, tmin::Float32, tmax::Float32)
    oc = r.origin - s.center
    a = 1 #r.dir ⋅ r.dir # normalized vector - always 1
    half_b = oc ⋅ r.dir
    c = oc⋅oc - s.radius^2
    discriminant = half_b^2 - a*c
	if discriminant < 0 return _no_hit end
	sqrtd = √discriminant

	# Find the nearest root that lies in the acceptable range
	root = (-half_b - sqrtd) / a
	if root < tmin || tmax < root
		root = (-half_b + sqrtd) / a
		if root < tmin || tmax < root
			return _no_hit
		end
	end

	t = root
	p = point(r, t)
	n⃗ = (p - s.center) / s.radius
	return ray_to_HitRecord(t, p, n⃗, r.dir, s.mat)
end

# ╔═╡ 05e57afd-6eb9-42c5-9666-7be3771fa6b8
struct HittableList <: Hittable
    list::Vector{Hittable}
end

# ╔═╡ 08e18ae5-9927-485e-9644-552f03e06f27
#"""Find closest hit between `Ray r` and a list of Hittable objects `h`, within distance `tmin` < `tmax`"""
function hit(hittables::HittableList, r::Ray, tmin::Float32, tmax::Float32)
    closest = tmax # closest t so far
    rec = _no_hit
    for h in hittables.list
        temprec = hit(h, r, tmin, closest)
        if temprec !== _no_hit
            rec = temprec
            closest = rec.t # i.e. ignore any further hit > this one's.
        end
    end
    rec
end

# ╔═╡ 737e2f87-82f5-45b6-a76c-4f560c29f5b9
color_vec3_in_rgb(v::SVector{3,Float32}) = 0.5normalize(v) + @SVector[0.5f,0.5f,0.5f]

# ╔═╡ 0bf88264-d4c5-4d5a-babe-d2433e46024d
md"# Metal material"

# ╔═╡ 555bea1d-5178-48dd-87e6-4e2a2471a5dd
mutable struct Metal<:Material
	albedo::SVector{3,Float32}
	fuzz::Float32 # how big the sphere used to generate fuzzy reflection rays. 0=none
	Metal(a,f=0.0) = new(a,f)
end

# ╔═╡ c299ca47-46c4-42da-9f8b-ae3abbeb6e51
function scatter(mat::Metal, r_in::Ray, rec::HitRecord)::Scatter
	reflected = normalize(reflect(r_in.dir, rec.n⃗) + mat.fuzz*random_vec3_on_sphere())
	Scatter(Ray(rec.p, reflected), mat.albedo)
end

# ╔═╡ 851c002c-dc23-4999-b28c-a716c5d2d42c
md"# Scenes"

# ╔═╡ 70530f8e-1b29-4588-927f-d38d5d12d5c9
#"Scene with 2 Lambertian spheres"
function scene_2_spheres()::HittableList
	spheres = Sphere[]

	# small center sphere
	push!(spheres, Sphere(@SVector[0f0,0f0,-1f0], 0.5,
						  Lambertian(@SVector[0.7f0,0.3f0,0.3f0])))

	# ground sphere (planet?)
	push!(spheres, Sphere(@SVector[0f0,-100.5f0,-1f0], 100,
						  Lambertian(@SVector[0.8f0,0.8f0,0.0f0])))
	HittableList(spheres)
end

# ╔═╡ c5349670-4df4-421f-9d5a-b28c1b9040c2
#"""Scene with 2 Lambertian, 2 Metal spheres.
#
#	See https://raytracing.github.io/images/img-1.11-metal-shiny.png"""
function scene_4_spheres()::HittableList
	scene = scene_2_spheres()

	# left and right Metal spheres
	push!(scene.list, Sphere(@SVector[-1f0,0f0,-1f0], 0.5f0,
							 Metal(@SVector[0.8f0,0.8f0,0.8f0], 0.3f0)))
	push!(scene.list, Sphere(@SVector[1f0,0f0,-1f0], 0.5f0,
							 Metal(@SVector[0.8f0,0.6f0,0.2f0], 0.8f0)))
	return scene
end

# ╔═╡ 282a4912-7a6e-44ae-90eb-f2f7c8f3d0f4
md"""# Camera

Adapted from C++'s sections 7.2, 11.1 """

# ╔═╡ a0e5a1f3-244f-427b-a335-7e233af1d9d8
mutable struct Camera
	origin::SVector{3,Float32}
	lower_left_corner::SVector{3,Float32}
	horizontal::SVector{3,Float32}
	vertical::SVector{3,Float32}
	u::SVector{3,Float32}
	v::SVector{3,Float32}
	w::SVector{3,Float32}
	lens_radius::Float32
end

# ╔═╡ 5d00f26b-35f2-4071-8e04-227ffc25f184
# """
# 	Args:
# 		vfov: vertical field-of-view in degrees
# 		aspect_ratio: horizontal/vertical ratio of pixels
#       aperture: if 0 - no depth-of-field
# """
function default_camera(lookfrom::SVector{3,Float32}=@SVector[0f0,0f0,0f0],
			lookat::SVector{3,Float32}=@SVector[0f0,0f0,-1f0],
			vup::SVector{3,Float32}=@SVector[0f0,1f0,0f0], vfov=90.0f0, aspect_ratio=16.0f0/9.0f0,
						aperture=0.0f0, focus_dist=1.0f0)
	viewport_height = 2.0f0 * tand(vfov/2f0)
	viewport_width = aspect_ratio * viewport_height

	w = normalize(lookfrom - lookat)
	u = normalize(vup × w)
	v = w × u

	origin = lookfrom
	horizontal = focus_dist * viewport_width * u
	vertical = focus_dist * viewport_height * v
	lower_left_corner = origin - horizontal/2f0 - vertical/2f0 - focus_dist*w
	lens_radius = aperture/2f0
	Camera(origin, lower_left_corner, horizontal, vertical, u, v, w, lens_radius)
end

# ╔═╡ c1aef1be-79d4-4417-be36-ae8416465986
default_camera()

# ╔═╡ 94081092-afc6-4359-bd2c-15e8407bf70e
function get_ray(c::Camera, s::Float32, t::Float32)
	rd = SVector{2,Float32}(c.lens_radius * random_vec2_in_disk())
	offset = c.u * rd[1] + c.v * rd[2] #offset = c.u * rd.x + c.v * rd.y
    Ray(c.origin + offset, normalize(c.lower_left_corner + s*c.horizontal +
									 t*c.vertical - c.origin - offset))
end

# ╔═╡ 813eaa13-2eb2-4302-9e4d-5d1dab0ac7c4
get_ray(default_camera(), 0.0f0, 0.0f0)

# ╔═╡ 891ce2c8-f8b2-472b-a8d9-389dafddcf22
md"# Render

(equivalent to final `main`)"

# ╔═╡ 5f1bae02-d425-4a73-8668-d6383faba79d
md"""# Dielectrics

from Section 10.2 Snell's Law:"""

# ╔═╡ 71f3626c-e61d-4838-82b9-ac8a978c0cb4
HTML("""""")

# ╔═╡ fbba5135-7ea3-4471-938a-be13c764feff
md"""
Refracted angle `sinθ′ = (η/η′)⋅sinθ`, where η (\eta) are the refractive indices.

Split the parts of the ray into `R′=R′⊥+R′∥` (perpendicular and parallel to n⃗′)."""

# ╔═╡ 9dc64353-c41c-45b4-aacd-12d5d6117c58
# """
# 	Args:
# 		refraction_ratio: incident refraction index divided by refraction index of
# 			hit surface. i.e. η/η′ in the figure above"""
function refract(dir::SVector{3,Float32}, n⃗::SVector{3,Float32},
				 refraction_ratio::Float32)
	cosθ = min(-dir ⋅ n⃗, 1)
	r_out_perp = refraction_ratio * (dir + cosθ*n⃗)
	r_out_parallel = -√(abs(1-squared_length(r_out_perp))) * n⃗
	normalize(r_out_perp + r_out_parallel)
end

# ╔═╡ bbfd4db5-3650-4f27-9777-2ff981c3d28b
begin # optional tests
	# unchanged angle
	@assert refract(SVector{3,Float32}(0.6,-0.8,0),
		SVector{3,Float32}(0,1,0), 1.0f0) == SVector{3,Float32}(0.6,-0.8,0)

	# wider angle
	t_refract_widerθ = refract(SVector{3,Float32}(0.6,-0.8,0),
		SVector{3,Float32}(0,1,0), 2.0f0)
	@assert isapprox(t_refract_widerθ,
		SVector{3,Float32}(0.87519, -0.483779, 0.0); atol=1e-3)

	# narrower angle
	t_refract_narrowerθ = refract(SVector{3,Float32}(0.6,-0.8,0),
		SVector{3,Float32}(0,1,0), 0.5f0)
	@assert isapprox(t_refract_narrowerθ,
		SVector{3,Float32}(0.3, -0.953939, 0.0); atol=1e-3)
end

# ╔═╡ f5c4e502-048c-4fcd-879f-eaeb4430c012
mutable struct Dielectric <: Material
	ir::Float32 # index of refraction, i.e. η.
end

# ╔═╡ 167cc207-7be6-4624-8425-2df81b3f6c3b
function reflectance(cosθ::Float32, refraction_ratio::Float32)
	# Use Schlick's approximation for reflectance.
	# claforte: may be buggy? I'm getting black pixels in the Hollow Glass Sphere...
	r0 = (1f0-refraction_ratio) / (1f0+refraction_ratio)
	r0 = r0^2
	r0 + (1f0-r0)*((1f0-cosθ)^5)
end

# ╔═╡ ae3b8f15-985d-4f74-ac8c-86a3ffc3b8b1
function scatter(mat::Dielectric, r_in::Ray, rec::HitRecord)
	attenuation = @SVector[1f0,1f0,1f0]
	refraction_ratio = rec.front_face ? (1.0f0/mat.ir) : mat.ir # i.e. ηᵢ/ηₜ
	cosθ = min(-r_in.dir⋅rec.n⃗, 1.0f0)
	sinθ = √(1.0f0 - cosθ^2)
	cannot_refract = refraction_ratio * sinθ > 1.0
	if cannot_refract || reflectance(cosθ, refraction_ratio) > rand()
		dir = reflect(r_in.dir, rec.n⃗)
	else
		dir = refract(r_in.dir, rec.n⃗, refraction_ratio)
	end
	Scatter(Ray(rec.p, dir), attenuation) # TODO: rename reflected -> !absorbed?
end

# ╔═╡ f72214f9-03c4-4ba3-bb84-069256446b31
# """Compute color for a ray, recursively

# 	Args:
# 		depth: how many more levels of recursive ray bounces can we still compute?"""
function ray_color(r::Ray, world::HittableList, depth=4)
    if depth <= 0
		return @SVector[0f0,0f0,0f0]
	end

	rec = hit(world, r, 1f-4, Inf32)
    if rec !== _no_hit
		# For debugging, represent vectors as RGB:
		# return color_vec3_in_rgb(rec.p) # show the normalized hit point
		# return color_vec3_in_rgb(rec.n⃗) # show the normal in RGB
		# return color_vec3_in_rgb(rec.p + rec.n⃗)
		# return color_vec3_in_rgb(random_vec3_in_sphere())
		#return color_vec3_in_rgb(rec.n⃗ + random_vec3_in_sphere())

        s = scatter(rec.mat, r, rec)
		if s.reflected
			return s.attenuation .* ray_color(s.r, world, depth-1)
		else
			return @SVector[0f0,0f0,0f0]
		end
    else
        skycolor(r)
    end
end

# ╔═╡ 64104df6-4b79-4329-bfed-14619aa73e3c
# """Render an image of `scene` using the specified camera, number of samples.
#
# 	Args:
# 		scene: a HittableList, e.g. a list of spheres
# 		n_samples: number of samples per pixel, eq. to C++ samples_per_pixel
#
# 	Equivalent to C++'s `main` function."""
function render(scene::HittableList, cam::Camera, image_width=400,
				n_samples=1)
	# Image
	aspect_ratio = 16.0f0/9.0f0 # TODO: use cam.aspect_ratio for consistency
	image_height = convert(Int64, floor(image_width / aspect_ratio))

	# Render
	img = zeros(RGB{Float32}, image_height, image_width)
	# Compared to C++, Julia is:
	# 1. column-major, i.e. iterate 1 column at a time, so invert i,j compared to C++
	# 2. 1-based, so no need to subtract 1 from image_width, etc.
	# 3. The array is Y-down, but `v` is Y-up
	for i in 1:image_height, j in 1:image_width
		accum_color = @SVector[0f0,0f0,0f0]
		for s in 1:n_samples
			u = convert(Float32, j/image_width)
			v = convert(Float32, (image_height-i)/image_height) # i=Y-down, v=Y-up!
			if s != 1 # 1st sample is always centered, for 1-sample/pixel
				# claforte: I think the C++ version had a bug, the rand offset was
				# between [0,1] instead of centered at 0, e.g. [-0.5, 0.5].
				u += convert(Float32, (rand()-0.5f0) / image_width)
				v += convert(Float32 ,(rand()-0.5f0) / image_height)
			end
			ray = get_ray(cam, u, v)
			accum_color += ray_color(ray, scene)
		end
		img[i,j] = rgb_gamma2(accum_color / n_samples)
	end
	img
end

# ╔═╡ aa38117f-45e8-4070-a412-958f0ce19aa5
render(scene_2_spheres(), default_camera(), 96, 16)

# ╔═╡ 9fd417cc-afa9-4f12-9c29-748f0522554c
render(scene_4_spheres(), default_camera(), 96, 16)

# ╔═╡ a2221922-31be-42f3-8f70-845fae385d2c
render(scene_4_spheres(), default_camera(), 200, 256)

# ╔═╡ ddf5883c-036a-4a21-908d-bb7cec731f7f
#"From C++: Image 15: Glass sphere that sometimes refracts"
function scene_diel_spheres(left_radius=0.5f0)::HittableList # dielectric spheres
	spheres = Sphere[]

	# small center sphere
	push!(spheres, Sphere(SVector{3,Float32}(0f0,0f0,-1f0), 0.5f0,
						  Lambertian(SVector{3,Float32}(0.1f0,0.2f0,0.5f0))))

	# ground sphere (planet?)
	push!(spheres, Sphere(SVector{3,Float32}(0f0,-100.5f0,-1f0), 100f0,
						  Lambertian(SVector{3,Float32}(0.8f0,0.8f0,0.0f0))))

	# left and right spheres.
	# Use a negative radius on the left sphere to create a "thin bubble"
	push!(spheres, Sphere(SVector{3,Float32}(-1f0,0f0,-1f0), left_radius,
						  Dielectric(1.5f0)))
	push!(spheres, Sphere(SVector{3,Float32}( 1f0,0f0,-1f0), 0.5f0,
						  Metal(SVector{3,Float32}(0.8f0,0.6f0,0.2f0), 0.0f0)))
	HittableList(spheres)
end

# ╔═╡ a1564d79-3628-4121-99a9-d3674e16eb04
render(scene_diel_spheres(), default_camera(), 96, 16)

# ╔═╡ 330a8972-adbd-471b-ade1-15901a258cbb
render(scene_diel_spheres(), default_camera(), 200, 64)

# ╔═╡ 2e9672e3-f2b8-439e-b1f3-3cc60a459885
# Hollow Glass sphere using a negative radius
# claforte: getting a weird black halo in the glass sphere... might be due to my
# "fix" for previous black spots, by moving the RecordHit point a bit away from
# the hit surface...
render(scene_diel_spheres(-0.5), default_camera(), 200, 64)

# ╔═╡ 0587d381-b957-4c40-b6b7-e5e0fd46267b
md"# Positioning camera"

# ╔═╡ 7c75b0d8-578d-4ca9-8d74-935c1ac582b9
function scene_blue_red_spheres()::HittableList # dielectric spheres
	spheres = Sphere[]
	R = cos(pi/4)
	push!(spheres, Sphere(@SVector[-R,0f0,-1f0], R,
						  Lambertian(@SVector[0f0,0f0,1f0])))
	push!(spheres, Sphere(@SVector[R,0f0,-1f0], R,
						  Lambertian(@SVector[1f0,0f0,0f0])))
	HittableList(spheres)
end

# ╔═╡ dcde1539-23af-4abf-96d3-6a903add3ea8
render(scene_blue_red_spheres(), default_camera(), 96, 16)

# ╔═╡ e7f5c672-0bd9-4cfe-8a47-17cd67aa01f4
render(scene_diel_spheres(), default_camera(@SVector[-2f0,2f0,1f0],
	@SVector[0f0,0f0,-1f0], @SVector[0f0,1f0,0f0], 20.0f0), 200, 256)

# ╔═╡ e3ef265c-1911-429d-84be-5d5174d55fa1
md"# Spheres with depth-of-field"

# ╔═╡ a7d95d91-6571-4696-bad3-2979296d5f84
begin
	t_lookfrom2 = @SVector[3.0f0,3.0f0,2.0f0]
	t_lookat2 = @SVector[0.0f0,0.0f0,-1.0f0]
	dist_to_focus2 = norm(t_lookfrom2-t_lookat2)
	t_cam2 = default_camera(t_lookfrom2, t_lookat2, @SVector[0.0f0,1.0f0,0.0f0],
			20.0f0, 16.0f0/9.0f0, 2.0f0, dist_to_focus2)
end

# ╔═╡ 5bc1a6eb-8867-48b4-b141-fc0636d36694
render(scene_diel_spheres(), t_cam2, 200, 128)

# ╔═╡ 3c54cde0-6509-45e1-a4a0-26c6aa840b8e
md"# Random spheres"

# ╔═╡ a52911a2-1e24-4237-81ca-f613913d29c1
function scene_random_spheres()::HittableList # dielectric spheres
	spheres = Sphere[]

	# ground
	push!(spheres, Sphere(@SVector[0f0,-1000f0,-1f0], 1000f0,
						  Lambertian(@SVector[0.5f0,0.5f0,0.5f0])))

	for a in -11:10, b in -11:10
		choose_mat = rand()
		center = @SVector[a + 0.9f0*rand(), 0.2f0, b + 0.90f0*rand()]

		# skip spheres too close?
		if norm(center - @SVector[4f0,0.2f0,0f0]) < 0.9f0 continue end

		if choose_mat < 0.8f0
			# diffuse
			albedo = @SVector[rand() for i ∈ 1:3] .* @SVector[rand() for i ∈ 1:3]
			push!(spheres, Sphere(center, 0.2f0, Lambertian(albedo)))
		elseif choose_mat < 0.95f0
			# metal
			albedo = @SVector[random_between(0.5f0,1.0f0) for i ∈ 1:3]
			fuzz = random_between(0.0f0, 5.0f0)
			push!(spheres, Sphere(center, 0.2f0, Metal(albedo, fuzz)))
		else
			# glass
			push!(spheres, Sphere(center, 0.2f0, Dielectric(1.5f0)))
		end
	end

	push!(spheres, Sphere(@SVector[0f0,1f0,0f0], 1.0f0, Dielectric(1.5f0)))
	push!(spheres, Sphere(@SVector[-4f0,1f0,0f0], 1.0f0,
						  Lambertian(@SVector[0.4f0,0.2f0,0.1f0])))
	push!(spheres, Sphere(@SVector[4f0,1f0,0f0], 1.0f0,
						  Metal(@SVector[0.7f0,0.6f0,0.5f0], 0.0f0)))
	HittableList(spheres)
end

# ╔═╡ 541aa3e5-4632-4f74-8088-f08fe24e07f8
scene_random_spheres()

# ╔═╡ 840d2599-245c-4e6c-8813-4abdcd802b01
begin
	t_lookfrom1 = @SVector[13.0f0,2.0f0,3.0f0]
	t_lookat1 = @SVector[0.0f0,0.0f0,0.0f0]
	t_cam1 = default_camera(t_lookfrom1, t_lookat1, @SVector[0.0f0,1.0f0,0.0f0],
							20.0f0, 16.0f0/9.0f0, 0.1f0, 10.0f0)
end

# ╔═╡ da047747-1845-4c2b-b3cb-eaa6534ce5ff
render(scene_random_spheres(), t_cam1, 200, 32) # takes 3.2s

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
Images = "916415d5-f1e6-5110-898d-aaa5f9f070e0"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"

[compat]
Images = "~0.25.0"
StaticArrays = "~1.2.13"
"""

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

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

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

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

[[ArnoldiMethod]]
deps = ["LinearAlgebra", "Random", "StaticArrays"]
git-tree-sha1 = "62e51b39331de8911e4a7ff6f5aaf38a5f4cc0ae"
uuid = "ec485272-7323-5ecc-a04f-4719b315124d"
version = "0.2.0"

[[ArrayInterface]]
deps = ["Compat", "IfElse", "LinearAlgebra", "Requires", "SparseArrays", "Static"]
git-tree-sha1 = "e527b258413e0c6d4f66ade574744c94edef81f8"
uuid = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9"
version = "3.1.40"

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

[[AxisAlgorithms]]
deps = ["LinearAlgebra", "Random", "SparseArrays", "WoodburyMatrices"]
git-tree-sha1 = "66771c8d21c8ff5e3a93379480a2307ac36863f7"
uuid = "13072b0f-2c55-5437-9ae7-d433b7a33950"
version = "1.0.1"

[[AxisArrays]]
deps = ["Dates", "IntervalSets", "IterTools", "RangeArrays"]
git-tree-sha1 = "d127d5e4d86c7680b20c35d40b503c74b9a39b5e"
uuid = "39de3d68-74b9-583c-8d2d-e117c070f3a9"
version = "0.4.4"

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

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

[[CatIndices]]
deps = ["CustomUnitRanges", "OffsetArrays"]
git-tree-sha1 = "a0f80a09780eed9b1d106a1bf62041c2efc995bc"
uuid = "aafaddc9-749c-510e-ac4f-586e18779b91"
version = "0.2.2"

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

[[ChangesOfVariables]]
deps = ["LinearAlgebra", "Test"]
git-tree-sha1 = "9a1d594397670492219635b35a3d830b04730d62"
uuid = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0"
version = "0.1.1"

[[Clustering]]
deps = ["Distances", "LinearAlgebra", "NearestNeighbors", "Printf", "SparseArrays", "Statistics", "StatsBase"]
git-tree-sha1 = "75479b7df4167267d75294d14b58244695beb2ac"
uuid = "aaaa29a8-35af-508c-8bc3-b662a17a0fe5"
version = "0.14.2"

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

[[ColorVectorSpace]]
deps = ["ColorTypes", "FixedPointNumbers", "LinearAlgebra", "SpecialFunctions", "Statistics", "TensorCore"]
git-tree-sha1 = "3f1f500312161f1ae067abe07d13b40f78f32e07"
uuid = "c3611d14-8923-5661-9e6a-0046d554d3a4"
version = "0.9.8"

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

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

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

[[ComputationalResources]]
git-tree-sha1 = "52cb3ec90e8a8bea0e62e275ba577ad0f74821f7"
uuid = "ed09eef8-17a6-5b46-8889-db040fac31e3"
version = "0.3.2"

[[CoordinateTransformations]]
deps = ["LinearAlgebra", "StaticArrays"]
git-tree-sha1 = "681ea870b918e7cff7111da58791d7f718067a19"
uuid = "150eb455-5306-5404-9cee-2592286d6298"
version = "0.6.2"

[[CustomUnitRanges]]
git-tree-sha1 = "1a3f97f907e6dd8983b744d2642651bb162a3f7a"
uuid = "dc8bdbbb-1ca9-579f-8c36-e416f6a65cce"
version = "1.0.2"

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

[[DataStructures]]
deps = ["InteractiveUtils", "OrderedCollections"]
git-tree-sha1 = "88d48e133e6d3dd68183309877eac74393daa7eb"
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
version = "0.17.20"

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

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

[[Distances]]
deps = ["LinearAlgebra", "Statistics", "StatsAPI"]
git-tree-sha1 = "837c83e5574582e07662bbbba733964ff7c26b9d"
uuid = "b4f34e82-e78d-54a5-968a-f98e89d6e8f7"
version = "0.10.6"

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

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

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

[[EllipsisNotation]]
deps = ["ArrayInterface"]
git-tree-sha1 = "9aad812fb7c4c038da7cab5a069f502e6e3ae030"
uuid = "da5c29d0-fa7d-589e-88eb-ea29b0a81949"
version = "1.1.1"

[[FFTViews]]
deps = ["CustomUnitRanges", "FFTW"]
git-tree-sha1 = "cbdf14d1e8c7c8aacbe8b19862e0179fd08321c2"
uuid = "4f61f5a4-77b1-5117-aa51-3ab5ef4ef0cd"
version = "0.3.2"

[[FFTW]]
deps = ["AbstractFFTs", "FFTW_jll", "LinearAlgebra", "MKL_jll", "Preferences", "Reexport"]
git-tree-sha1 = "463cb335fa22c4ebacfd1faba5fde14edb80d96c"
uuid = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341"
version = "1.4.5"

[[FFTW_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "c6033cc3892d0ef5bb9cd29b7f2f0331ea5184ea"
uuid = "f5851436-0d7a-5f13-b9de-f02708fd171a"
version = "3.3.10+0"

[[FileIO]]
deps = ["Pkg", "Requires", "UUIDs"]
git-tree-sha1 = "2db648b6712831ecb333eae76dbfd1c156ca13bb"
uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
version = "1.11.2"

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

[[Graphics]]
deps = ["Colors", "LinearAlgebra", "NaNMath"]
git-tree-sha1 = "1c5a84319923bea76fa145d49e93aa4394c73fc2"
uuid = "a2bd30eb-e257-5431-a919-1863eab51364"
version = "1.1.1"

[[Graphs]]
deps = ["ArnoldiMethod", "DataStructures", "Distributed", "Inflate", "LinearAlgebra", "Random", "SharedArrays", "SimpleTraits", "SparseArrays", "Statistics"]
git-tree-sha1 = "92243c07e786ea3458532e199eb3feee0e7e08eb"
uuid = "86223c79-3864-5bf0-83f7-82e725a168b6"
version = "1.4.1"

[[IfElse]]
git-tree-sha1 = "debdd00ffef04665ccbb3e150747a77560e8fad1"
uuid = "615f187c-cbe4-4ef1-ba3b-2fcf58d6d173"
version = "0.1.1"

[[ImageAxes]]
deps = ["AxisArrays", "ImageBase", "ImageCore", "Reexport", "SimpleTraits"]
git-tree-sha1 = "c54b581a83008dc7f292e205f4c409ab5caa0f04"
uuid = "2803e5a7-5153-5ecf-9a86-9b4c37f5f5ac"
version = "0.6.10"

[[ImageBase]]
deps = ["ImageCore", "Reexport"]
git-tree-sha1 = "b51bb8cae22c66d0f6357e3bcb6363145ef20835"
uuid = "c817782e-172a-44cc-b673-b171935fbb9e"
version = "0.1.5"

[[ImageContrastAdjustment]]
deps = ["ImageCore", "ImageTransformations", "Parameters"]
git-tree-sha1 = "0d75cafa80cf22026cea21a8e6cf965295003edc"
uuid = "f332f351-ec65-5f6a-b3d1-319c6670881a"
version = "0.3.10"

[[ImageCore]]
deps = ["AbstractFFTs", "ColorVectorSpace", "Colors", "FixedPointNumbers", "Graphics", "MappedArrays", "MosaicViews", "OffsetArrays", "PaddedViews", "Reexport"]
git-tree-sha1 = "9a5c62f231e5bba35695a20988fc7cd6de7eeb5a"
uuid = "a09fc81d-aa75-5fe9-8630-4744c3626534"
version = "0.9.3"

[[ImageDistances]]
deps = ["Distances", "ImageCore", "ImageMorphology", "LinearAlgebra", "Statistics"]
git-tree-sha1 = "7a20463713d239a19cbad3f6991e404aca876bda"
uuid = "51556ac3-7006-55f5-8cb3-34580c88182d"
version = "0.2.15"

[[ImageFiltering]]
deps = ["CatIndices", "ComputationalResources", "DataStructures", "FFTViews", "FFTW", "ImageBase", "ImageCore", "LinearAlgebra", "OffsetArrays", "Reexport", "SparseArrays", "StaticArrays", "Statistics", "TiledIteration"]
git-tree-sha1 = "15bd05c1c0d5dbb32a9a3d7e0ad2d50dd6167189"
uuid = "6a3955dd-da59-5b1f-98d4-e7296123deb5"
version = "0.7.1"

[[ImageIO]]
deps = ["FileIO", "Netpbm", "OpenEXR", "PNGFiles", "TiffImages", "UUIDs"]
git-tree-sha1 = "a2951c93684551467265e0e32b577914f69532be"
uuid = "82e4d734-157c-48bb-816b-45c225c6df19"
version = "0.5.9"

[[ImageMagick]]
deps = ["FileIO", "ImageCore", "ImageMagick_jll", "InteractiveUtils", "Libdl", "Pkg", "Random"]
git-tree-sha1 = "5bc1cb62e0c5f1005868358db0692c994c3a13c6"
uuid = "6218d12a-5da1-5696-b52f-db25d2ecc6d1"
version = "1.2.1"

[[ImageMagick_jll]]
deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll", "Pkg", "Zlib_jll", "libpng_jll"]
git-tree-sha1 = "ea2b6fd947cdfc43c6b8c15cff982533ec1f72cd"
uuid = "c73af94c-d91f-53ed-93a7-00f77d67a9d7"
version = "6.9.12+0"

[[ImageMetadata]]
deps = ["AxisArrays", "ImageAxes", "ImageBase", "ImageCore"]
git-tree-sha1 = "36cbaebed194b292590cba2593da27b34763804a"
uuid = "bc367c6b-8a6b-528e-b4bd-a4b897500b49"
version = "0.9.8"

[[ImageMorphology]]
deps = ["ImageCore", "LinearAlgebra", "Requires", "TiledIteration"]
git-tree-sha1 = "5581e18a74a5838bd919294a7138c2663d065238"
uuid = "787d08f9-d448-5407-9aad-5290dd7ab264"
version = "0.3.0"

[[ImageQualityIndexes]]
deps = ["ImageContrastAdjustment", "ImageCore", "ImageDistances", "ImageFiltering", "OffsetArrays", "Statistics"]
git-tree-sha1 = "1d2d73b14198d10f7f12bf7f8481fd4b3ff5cd61"
uuid = "2996bd0c-7a13-11e9-2da2-2f5ce47296a9"
version = "0.3.0"

[[ImageSegmentation]]
deps = ["Clustering", "DataStructures", "Distances", "Graphs", "ImageCore", "ImageFiltering", "ImageMorphology", "LinearAlgebra", "MetaGraphs", "RegionTrees", "SimpleWeightedGraphs", "StaticArrays", "Statistics"]
git-tree-sha1 = "36832067ea220818d105d718527d6ed02385bf22"
uuid = "80713f31-8817-5129-9cf8-209ff8fb23e1"
version = "1.7.0"

[[ImageShow]]
deps = ["Base64", "FileIO", "ImageBase", "ImageCore", "OffsetArrays", "StackViews"]
git-tree-sha1 = "d0ac64c9bee0aed6fdbb2bc0e5dfa9a3a78e3acc"
uuid = "4e3cecfd-b093-5904-9786-8bbb286a6a31"
version = "0.3.3"

[[ImageTransformations]]
deps = ["AxisAlgorithms", "ColorVectorSpace", "CoordinateTransformations", "ImageBase", "ImageCore", "Interpolations", "OffsetArrays", "Rotations", "StaticArrays"]
git-tree-sha1 = "b4b161abc8252d68b13c5cc4a5f2ba711b61fec5"
uuid = "02fcd773-0e25-5acc-982a-7f6622650795"
version = "0.9.3"

[[Images]]
deps = ["Base64", "FileIO", "Graphics", "ImageAxes", "ImageBase", "ImageContrastAdjustment", "ImageCore", "ImageDistances", "ImageFiltering", "ImageIO", "ImageMagick", "ImageMetadata", "ImageMorphology", "ImageQualityIndexes", "ImageSegmentation", "ImageShow", "ImageTransformations", "IndirectArrays", "IntegralArrays", "Random", "Reexport", "SparseArrays", "StaticArrays", "Statistics", "StatsBase", "TiledIteration"]
git-tree-sha1 = "35dc1cd115c57ad705c7db9f6ef5cc14412e8f00"
uuid = "916415d5-f1e6-5110-898d-aaa5f9f070e0"
version = "0.25.0"

[[Imath_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "87f7662e03a649cffa2e05bf19c303e168732d3e"
uuid = "905a6f67-0a94-5f89-b386-d35d92009cd1"
version = "3.1.2+0"

[[IndirectArrays]]
git-tree-sha1 = "012e604e1c7458645cb8b436f8fba789a51b257f"
uuid = "9b13fd28-a010-5f03-acff-a1bbcff69959"
version = "1.0.0"

[[Inflate]]
git-tree-sha1 = "f5fc07d4e706b84f72d54eedcc1c13d92fb0871c"
uuid = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9"
version = "0.1.2"

[[IntegralArrays]]
deps = ["IntervalSets"]
git-tree-sha1 = "26c4df96fdf6127a92d53cb8ffb577104617adca"
uuid = "1d092043-8f09-5a30-832f-7509e371ab51"
version = "0.1.0"

[[IntelOpenMP_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "d979e54b71da82f3a65b62553da4fc3d18c9004c"
uuid = "1d5cc7b8-4909-519e-a0f8-d0f5ad9712d0"
version = "2018.0.3+2"

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

[[Interpolations]]
deps = ["AxisAlgorithms", "ChainRulesCore", "LinearAlgebra", "OffsetArrays", "Random", "Ratios", "Requires", "SharedArrays", "SparseArrays", "StaticArrays", "WoodburyMatrices"]
git-tree-sha1 = "61aa005707ea2cebf47c8d780da8dc9bc4e0c512"
uuid = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59"
version = "0.13.4"

[[IntervalSets]]
deps = ["Dates", "EllipsisNotation", "Statistics"]
git-tree-sha1 = "3cc368af3f110a767ac786560045dceddfc16758"
uuid = "8197267c-284f-5f27-9208-e0e47529a953"
version = "0.5.3"

[[InverseFunctions]]
deps = ["Test"]
git-tree-sha1 = "a7254c0acd8e62f1ac75ad24d5db43f5f19f3c65"
uuid = "3587e190-3f89-42d0-90ee-14403ec27112"
version = "0.1.2"

[[IrrationalConstants]]
git-tree-sha1 = "7fd44fd4ff43fc60815f8e764c0f352b83c49151"
uuid = "92d709cd-6900-40b7-9082-c6be49f344b6"
version = "0.1.1"

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

[[JLD2]]
deps = ["DataStructures", "FileIO", "MacroTools", "Mmap", "Pkg", "Printf", "Reexport", "TranscodingStreams", "UUIDs"]
git-tree-sha1 = "46b7834ec8165c541b0b5d1c8ba63ec940723ffb"
uuid = "033835bb-8acc-5ee8-8aae-3f567f8a3819"
version = "0.4.15"

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

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

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

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

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

[[LogExpFunctions]]
deps = ["ChainRulesCore", "ChangesOfVariables", "DocStringExtensions", "InverseFunctions", "IrrationalConstants", "LinearAlgebra"]
git-tree-sha1 = "be9eef9f9d78cecb6f262f3c10da151a6c5ab827"
uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
version = "0.3.5"

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

[[MKL_jll]]
deps = ["Artifacts", "IntelOpenMP_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "Pkg"]
git-tree-sha1 = "5455aef09b40e5020e1520f551fa3135040d4ed0"
uuid = "856f044c-d86e-5d09-b602-aeab76dc8ba7"
version = "2021.1.1+2"

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

[[MappedArrays]]
git-tree-sha1 = "e8b359ef06ec72e8c030463fe02efe5527ee5142"
uuid = "dbb5928d-eab1-5f90-85c2-b9b0edb7c900"
version = "0.4.1"

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

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

[[MetaGraphs]]
deps = ["Graphs", "JLD2", "Random"]
git-tree-sha1 = "2af69ff3c024d13bde52b34a2a7d6887d4e7b438"
uuid = "626554b9-1ddb-594c-aa3c-2596fe9399a5"
version = "0.7.1"

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

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

[[MosaicViews]]
deps = ["MappedArrays", "OffsetArrays", "PaddedViews", "StackViews"]
git-tree-sha1 = "b34e3bc3ca7c94914418637cb10cc4d1d80d877d"
uuid = "e94cdb99-869f-56ef-bcf0-1ae2bcbe0389"
version = "0.3.3"

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

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

[[NearestNeighbors]]
deps = ["Distances", "StaticArrays"]
git-tree-sha1 = "16baacfdc8758bc374882566c9187e785e85c2f0"
uuid = "b8a86587-4115-5ab1-83bc-aa920d37bbce"
version = "0.4.9"

[[Netpbm]]
deps = ["FileIO", "ImageCore"]
git-tree-sha1 = "18efc06f6ec36a8b801b23f076e3c6ac7c3bf153"
uuid = "f09324ee-3d7c-5217-9330-fc30815ba969"
version = "1.0.2"

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

[[OffsetArrays]]
deps = ["Adapt"]
git-tree-sha1 = "043017e0bdeff61cfbb7afeb558ab29536bbb5ed"
uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
version = "1.10.8"

[[OpenEXR]]
deps = ["Colors", "FileIO", "OpenEXR_jll"]
git-tree-sha1 = "327f53360fdb54df7ecd01e96ef1983536d1e633"
uuid = "52e1d378-f018-4a11-a4be-720524705ac7"
version = "0.3.2"

[[OpenEXR_jll]]
deps = ["Artifacts", "Imath_jll", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"]
git-tree-sha1 = "923319661e9a22712f24596ce81c54fc0366f304"
uuid = "18a262bb-aa17-5467-a713-aee519bc75cb"
version = "3.1.1+0"

[[OpenLibm_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "05823500-19ac-5b8b-9628-191a04bc5112"

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

[[PNGFiles]]
deps = ["Base64", "CEnum", "ImageCore", "IndirectArrays", "OffsetArrays", "libpng_jll"]
git-tree-sha1 = "6d105d40e30b635cfed9d52ec29cf456e27d38f8"
uuid = "f57f5aa1-a3ce-4bc8-8ab9-96f992907883"
version = "0.3.12"

[[PaddedViews]]
deps = ["OffsetArrays"]
git-tree-sha1 = "646eed6f6a5d8df6708f15ea7e02a7a2c4fe4800"
uuid = "5432bcbf-9aad-5242-b902-cca2824c8663"
version = "0.5.10"

[[Parameters]]
deps = ["OrderedCollections", "UnPack"]
git-tree-sha1 = "34c0e9ad262e5f7fc75b10a9952ca7692cfc5fbe"
uuid = "d96e819e-fc66-5662-9728-84c9c7592b0a"
version = "0.12.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"

[[PkgVersion]]
deps = ["Pkg"]
git-tree-sha1 = "a7a7e1a88853564e551e4eba8650f8c38df79b37"
uuid = "eebad327-c553-4316-9ea0-9fa01ccd7688"
version = "0.1.1"

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

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

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

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

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

[[RangeArrays]]
git-tree-sha1 = "b9039e93773ddcfc828f12aadf7115b4b4d225f5"
uuid = "b3c3ace0-ae52-54e7-9d0b-2c1406fd6b9d"
version = "0.3.2"

[[Ratios]]
deps = ["Requires"]
git-tree-sha1 = "01d341f502250e81f6fec0afe662aa861392a3aa"
uuid = "c84ed2f1-dad5-54f0-aa8e-dbefe2724439"
version = "0.4.2"

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

[[RegionTrees]]
deps = ["IterTools", "LinearAlgebra", "StaticArrays"]
git-tree-sha1 = "4618ed0da7a251c7f92e869ae1a19c74a7d2a7f9"
uuid = "dee08c22-ab7f-5625-9660-a9af2021b33f"
version = "0.3.2"

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

[[Rotations]]
deps = ["LinearAlgebra", "Random", "StaticArrays", "Statistics"]
git-tree-sha1 = "6a23472b6b097d66da87785b61137142ac104f94"
uuid = "6038ab10-8711-5258-84ad-4b1120ba62dc"
version = "1.0.4"

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

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

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

[[SimpleTraits]]
deps = ["InteractiveUtils", "MacroTools"]
git-tree-sha1 = "5d7e3f4e11935503d3ecaf7186eac40602e7d231"
uuid = "699a6c99-e7fa-54fc-8d76-47d257e15c1d"
version = "0.9.4"

[[SimpleWeightedGraphs]]
deps = ["Graphs", "LinearAlgebra", "Markdown", "SparseArrays", "Test"]
git-tree-sha1 = "a6f404cc44d3d3b28c793ec0eb59af709d827e4e"
uuid = "47aef6b3-ad0c-573a-a1e2-d07658019622"
version = "1.2.1"

[[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", "IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"]
git-tree-sha1 = "e08890d19787ec25029113e88c34ec20cac1c91e"
uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
version = "2.0.0"

[[StackViews]]
deps = ["OffsetArrays"]
git-tree-sha1 = "46e589465204cd0c08b4bd97385e4fa79a0c770c"
uuid = "cae243ae-269e-4f55-b966-ac2d0dc13c15"
version = "0.1.1"

[[Static]]
deps = ["IfElse"]
git-tree-sha1 = "e7bc80dc93f50857a5d1e3c8121495852f407e6a"
uuid = "aedffcd0-7271-4cad-89d0-dc628f76c6d3"
version = "0.4.0"

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

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

[[StatsAPI]]
git-tree-sha1 = "0f2aa8e32d511f758a2ce49208181f7733a0936a"
uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0"
version = "1.1.0"

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

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

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

[[TensorCore]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "1feb45f88d133a655e001435632f019a9a1bcdb6"
uuid = "62fd8b95-f654-4bbd-a8a5-9c27f68ccd50"
version = "0.1.1"

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

[[TiffImages]]
deps = ["ColorTypes", "DocStringExtensions", "FileIO", "FixedPointNumbers", "IndirectArrays", "Inflate", "OffsetArrays", "OrderedCollections", "PkgVersion", "ProgressMeter"]
git-tree-sha1 = "945b8d87c5e8d5e34e6207ee15edb9d11ae44716"
uuid = "731e570b-9d59-4bfa-96dc-6df516fadf69"
version = "0.4.3"

[[TiledIteration]]
deps = ["OffsetArrays"]
git-tree-sha1 = "5683455224ba92ef59db72d10690690f4a8dc297"
uuid = "06e1c1a7-607b-532d-9fad-de7d9aa2abac"
version = "0.3.1"

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

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

[[UnPack]]
git-tree-sha1 = "387c1f73762231e86e0c9c5443ce3b4a0a9a0c2b"
uuid = "3a884ed6-31ef-47d7-9d2a-63182c4928ed"
version = "1.0.2"

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

[[WoodburyMatrices]]
deps = ["LinearAlgebra", "SparseArrays"]
git-tree-sha1 = "de67fa59e33ad156a590055375a30b23c40299d3"
uuid = "efce3f68-66dc-5838-9240-27a6d6f5f9b6"
version = "0.5.5"

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

[[libpng_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"]
git-tree-sha1 = "94d180a6d2b5e55e447e2d27a29ed04fe79eb30c"
uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f"
version = "1.6.38+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:
# ╟─0866add2-9b95-45e7-8081-c01cd2a66911
# ╟─611d5eae-4b09-11ec-27bf-ef4a1ecdcc41
# ╟─97bb4432-ed41-423b-b4d9-bafc519de641
# ╠═38fdd4ef-c383-4f97-8451-c6f602307e7d
# ╠═84d29423-cf11-41c3-af4d-c5f63b1ef23e
# ╠═7d26fef0-9a06-479a-ae08-e9d04e455767
# ╠═f29ad2c0-c3ff-484d-8fdd-dff34d2bb863
# ╠═2192e695-4378-4b47-8ce0-353636cd2cd1
# ╠═538d1aa5-07f9-4fca-8410-ef63b8a6857b
# ╠═8aeb7373-6bb0-4544-8655-fa941561688c
# ╠═154d736b-8fdc-44af-ae2a-9e5ba6d2c92e
# ╟─216922d8-613a-4ac1-9559-40878e6587e2
# ╠═961fd749-d439-4dfa-ae21-b1659dc54511
# ╠═3dceca5d-7d1e-425b-9516-24e0a24adaff
# ╠═f8007c75-9487-414a-9592-138a696c2957
# ╠═668030c8-24a7-4aa6-b858-cedf8ac5f988
# ╠═3eb50f44-9091-45e8-a7e1-92d25b4b2090
# ╠═78f209df-d176-4711-80fc-a8054771f105
# ╠═e88de775-6904-4182-8209-06db22758470
# ╠═5fd1ec87-3616-448a-ab4d-fede804b26d5
# ╠═a0893bf4-9607-4853-8162-9f34d3337060
# ╠═cfbcb883-d12e-4ad3-a084-064749bddcdb
# ╠═6348c03d-e8ec-4dbb-9f8a-8e4a48bb1cb3
# ╠═dbc8fc2d-39c2-4ec9-b82d-7c6a8b12dde7
# ╠═53832af1-a9be-4e02-8b71-a70dae63c233
# ╠═81b4c9e4-9f93-45ca-9fa0-7e9686a55e9a
# ╟─772674b0-5c40-4457-9526-5b4e81cca711
# ╠═709861a3-202a-4bff-a46d-b46c6e14b334
# ╠═135000d6-a675-4d8b-9385-50853bf9a169
# ╠═7d1146d7-74da-42e6-92c2-1312ed03f70d
# ╠═46a4aa4b-82c1-4941-b36d-dc8c977af2bc
# ╠═203a9b25-742a-4d8b-adee-862ce4c67670
# ╠═2e98c6bd-0289-4105-ad25-24414ecf2750
# ╠═c0c23cb3-6a57-487d-943a-af4330a94ffe
# ╟─678214c5-de81-489f-b002-c343d48071c9
# ╠═cbb6418c-79e9-4359-80a6-40a8fa40679e
# ╠═14be6068-6a15-4fad-ac0a-f156da71f103
# ╠═64ef0313-2d2b-49d5-a1a1-3b04426a82f8
# ╠═971777a6-f269-4344-8dba-7a55118396e5
# ╠═655ffa6c-f1e9-4149-8f1d-51145c5a51e4
# ╟─9075f8ed-f319-486d-94b2-486806aba3fd
# ╠═c00e2004-2002-4dd2-98ed-f898ef2c14f1
# ╠═b7399fb8-6205-41ea-9c70-eb62daedcefb
# ╠═1d04159d-87bd-4cf8-a73c-817f20ca1026
# ╟─fed81f09-e4a0-433f-99e8-261096114b7b
# ╠═24e8740a-8e44-4206-b2b6-c4a55002dad8
# ╠═359832af-7598-4c45-8033-c28cb0d86772
# ╠═ed6ab8be-587c-4cb6-8172-618c74d3f9cc
# ╠═a65c68c9-e489-465a-9687-93ae9da14a5e
# ╠═2c4b4453-1a46-4889-9a14-16b18cc8c240
# ╠═98c43f3f-4bfc-49db-806a-850b7d75b5a4
# ╠═3b570d37-f407-41d8-b8a0-a0af4d85b14d
# ╠═138bb5b6-0f45-4f13-8339-5110eb7cd1ff
# ╟─6b36d245-bf01-45a7-b119-8315226dd4a3
# ╠═4a396b3f-f920-4ec2-91f6-7d61fe2b9699
# ╠═427f247c-055c-459e-9862-26e9f6f3e24f
# ╠═88e51c27-0f28-4dcc-b9e9-ac44eeb876f5
# ╠═7c4a67b2-8208-4cd4-b1ea-16f6f50adfe8
# ╠═ca649864-5a6d-4ca7-896e-e80e8a48443e
# ╠═485f9c5b-4c5d-453c-b190-e84ae0cd1a21
# ╠═c63d10c2-dd43-4836-83ee-61b782545a02
# ╠═78efebc5-53fd-417d-bd9e-667fd504e3fd
# ╠═05e57afd-6eb9-42c5-9666-7be3771fa6b8
# ╠═08e18ae5-9927-485e-9644-552f03e06f27
# ╠═737e2f87-82f5-45b6-a76c-4f560c29f5b9
# ╠═f72214f9-03c4-4ba3-bb84-069256446b31
# ╠═0bf88264-d4c5-4d5a-babe-d2433e46024d
# ╠═555bea1d-5178-48dd-87e6-4e2a2471a5dd
# ╠═c299ca47-46c4-42da-9f8b-ae3abbeb6e51
# ╟─851c002c-dc23-4999-b28c-a716c5d2d42c
# ╠═70530f8e-1b29-4588-927f-d38d5d12d5c9
# ╠═c5349670-4df4-421f-9d5a-b28c1b9040c2
# ╟─282a4912-7a6e-44ae-90eb-f2f7c8f3d0f4
# ╠═a0e5a1f3-244f-427b-a335-7e233af1d9d8
# ╠═5d00f26b-35f2-4071-8e04-227ffc25f184
# ╠═c1aef1be-79d4-4417-be36-ae8416465986
# ╠═94081092-afc6-4359-bd2c-15e8407bf70e
# ╠═813eaa13-2eb2-4302-9e4d-5d1dab0ac7c4
# ╟─891ce2c8-f8b2-472b-a8d9-389dafddcf22
# ╠═64104df6-4b79-4329-bfed-14619aa73e3c
# ╠═aa38117f-45e8-4070-a412-958f0ce19aa5
# ╠═9fd417cc-afa9-4f12-9c29-748f0522554c
# ╠═a2221922-31be-42f3-8f70-845fae385d2c
# ╟─5f1bae02-d425-4a73-8668-d6383faba79d
# ╟─71f3626c-e61d-4838-82b9-ac8a978c0cb4
# ╟─fbba5135-7ea3-4471-938a-be13c764feff
# ╠═9dc64353-c41c-45b4-aacd-12d5d6117c58
# ╠═bbfd4db5-3650-4f27-9777-2ff981c3d28b
# ╠═f5c4e502-048c-4fcd-879f-eaeb4430c012
# ╠═167cc207-7be6-4624-8425-2df81b3f6c3b
# ╠═ae3b8f15-985d-4f74-ac8c-86a3ffc3b8b1
# ╠═ddf5883c-036a-4a21-908d-bb7cec731f7f
# ╠═a1564d79-3628-4121-99a9-d3674e16eb04
# ╠═330a8972-adbd-471b-ade1-15901a258cbb
# ╠═2e9672e3-f2b8-439e-b1f3-3cc60a459885
# ╟─0587d381-b957-4c40-b6b7-e5e0fd46267b
# ╠═7c75b0d8-578d-4ca9-8d74-935c1ac582b9
# ╠═dcde1539-23af-4abf-96d3-6a903add3ea8
# ╠═e7f5c672-0bd9-4cfe-8a47-17cd67aa01f4
# ╟─e3ef265c-1911-429d-84be-5d5174d55fa1
# ╠═a7d95d91-6571-4696-bad3-2979296d5f84
# ╠═5bc1a6eb-8867-48b4-b141-fc0636d36694
# ╟─3c54cde0-6509-45e1-a4a0-26c6aa840b8e
# ╠═a52911a2-1e24-4237-81ca-f613913d29c1
# ╠═541aa3e5-4632-4f74-8088-f08fe24e07f8
# ╠═840d2599-245c-4e6c-8813-4abdcd802b01
# ╠═da047747-1845-4c2b-b3cb-eaa6534ce5ff
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


###############################################
#FEniCS tutorial demo program: Heat equation with Dirichlet conditions.
#Test problem is chosen to give an exact solution at all nodes of the mesh.
#  u'= Laplace(u) + f  in the unit square
#  u = u_D             on the boundary
#  u = u_0             at t = 0
#  u = 1 + x^2 + α*y^2 + β*t
#  f = β - 2 - 2*α
###############################################

module ft03

using FenicsPy

T = 2.0            # final time
num_steps = 10     # number of time steps
Δt = T / num_steps # time step size
α = 3          # parameter α
β = 1.2         # parameter β

# Create mesh and define function space
nx = ny = 8
mesh = UnitSquareMesh(nx, ny)
V = FunctionSpace(mesh, "P", 1)

# Define boundary condition
u_D = Expression("1 + x[0]*x[0] + alpha*x[1]*x[1] + beta*t",
                 degree=2, alpha=α, beta=β, t=0)

bc = DirichletBC(V, u_D, "on_boundary")

# Define initial value
u_n = interpolate(u_D, V)
#u_n = project(u_D, V)

# Define variational problem
u = TrialFunction(V)
v = TestFunction(V)
f = Constant(β - 2 - 2*α)

F = u*v*dx + Δt*dot(grad(u), grad(v))*dx - (u_n + Δt*f)*v*dx
a = lhs(F)
L = rhs(F)

# Time-stepping
u = FeFunction(V)

t = 0
for n = 1:num_steps

    # Update current time
    global t += Δt
    #u_D.t = t

    # Compute solution
    solve(a == L, u, bc)

    # Plot solution
    #plot(u)

    # Compute error at vertices
    u_e = interpolate(u_D, V)
    error = max(abs.(array(u_e)-array(u))...)
    println("t = ", t, "\t error = ", error)

    # Update previous solution
    u_n.assign(u)

end

end # module ft03


### A Pluto.jl notebook ###
# v0.12.20

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

# ╔═╡ e429e976-30d8-11eb-27dd-590119e09f74
using DifferentialEquations, PlutoUI

# ╔═╡ 167e7be6-30dc-11eb-15de-ad9ff925a787
using Plots; gr()

# ╔═╡ cdab643c-30da-11eb-29f6-53bdde7b82f5

# ╔═╡ d560f3f6-30da-11eb-31dc-f510ae97bada

# ╔═╡ c93b2276-30db-11eb-1669-27ae204265d1
f = function (du,u,p,t) # Define f as an in-place update into du
    a,b,c,d = p
 du[1] = a*u[1] - b*u[1]*u[2]
    du[2] = -c*u[2]+ d*u[1]*u[2]
end

# ╔═╡ 210ce916-30dd-11eb-0321-4fb5cae1d9f3
md"""
Enter the exponential growth of the prey:
$(@bind a Slider(0.00 : 0.1 : 10; default=3.0, show_value=true))

Enter the rate at which predator prey interactions will result in predator eating the prey:
$(@bind b Slider(0.00 : 0.1 : 10; default=2.0, show_value=true))

Enter the rate at which the predators growth is limited by eating only prey:
$(@bind c Slider(0.00 : 0.1 : 10; default=1.0, show_value=true))

Enter the exponential decline of the predator:
$(@bind d Slider(0.00 : 0.1 : 10; default=4.0, show_value=true))
"""

# ╔═╡ 3fa4a936-30dd-11eb-3ef1-8bb442940777
begin
p = (a,b,c,d)
end

# ╔═╡ 5a535432-30fe-11eb-3442-817440f3830e
md"Enter a value for x
$(@bind x Slider(0.00 : 0.1 : 10; default=1.0, show_value=true))

Enter a value for y
$(@bind y Slider(0.00 : 0.1 : 10; default=1.0, show_value=true))"

# ╔═╡ f6f9f872-30db-11eb-25d3-f18fdd57d80c
u0 = [x;y]; tspan = (0.0,10.0)

# ╔═╡ 986c0e84-31bd-11eb-27af-d1702ee5442e
prob = ODEProblem(f,u0,tspan,p);

# ╔═╡ a063e0d0-31bd-11eb-056b-cb400ad0e45b
sol = solve(prob);

# ╔═╡ 1ce67d12-30dc-11eb-3f11-4b72daa20236
plot(sol,title="Population of predator/prey over time")

# ╔═╡ d3028ce4-31bd-11eb-1710-d5a7ecba6bfe
md" ## Scenerio One
**How does changing the growth rate of the prey affect the population of the predators and prey over time?**

As the growth rate of the prey increases, the amount of population fluctuations increase. Looking at the graph, there are more relative maximums and minimums as the value of a increases, which means there are more rapid changes in populations.

## Scenerio Two
**How does changing the rate at which the predator eats the prey affect the overall population of the predators and preys over time?**

As the predation rate increases, the rate at which the prey populates decreases.

## Scenerio Three
**How does changing the limit on the predator affect the overall population of the predators and preys over time?**

As the limit on the predator increases, the frequency of population change increases. The graph squeezes and the number of periods(in the graph) increases. The maximum point in the population also increases.

## Scenerio Four
**How does changing the rate of predator decline affect the overall population of predator and prey over time?**

The course of the predator population stays the same over time, but the rate of population growth and decline for the prey as the maximums for the prey shrinks as the rate of predator decline increases.

"

# ╔═╡ 49c55e48-30dc-11eb-02be-398a6bd228eb
plot(sol,title="Phase Diagram",vars=(1,2))

# ╔═╡ a5c27f1a-31bc-11eb-2a2d-5fd911b57f2a
md" ## Acknowledgements
1. http://ucidatascienceinitiative.github.io/IntroToJulia/Html/DiffEq#Example:-Lotka-Volterra-ODE
2. https://en.wikipedia.org/wiki/Lotka–Volterra_equations
3. https://observablehq.com/@mbostock/predator-and-prey
4. https://www.pnas.org/content/6/7/410.full

"

# ╔═╡ Cell order:
# ╠═cdab643c-30da-11eb-29f6-53bdde7b82f5
# ╠═d560f3f6-30da-11eb-31dc-f510ae97bada
# ╠═e429e976-30d8-11eb-27dd-590119e09f74
# ╠═c93b2276-30db-11eb-1669-27ae204265d1
# ╠═3fa4a936-30dd-11eb-3ef1-8bb442940777
# ╠═210ce916-30dd-11eb-0321-4fb5cae1d9f3
# ╠═1ce67d12-30dc-11eb-3f11-4b72daa20236
# ╠═5a535432-30fe-11eb-3442-817440f3830e
# ╠═f6f9f872-30db-11eb-25d3-f18fdd57d80c
# ╠═986c0e84-31bd-11eb-27af-d1702ee5442e
# ╠═a063e0d0-31bd-11eb-056b-cb400ad0e45b
# ╟─167e7be6-30dc-11eb-15de-ad9ff925a787
# ╠═d3028ce4-31bd-11eb-1710-d5a7ecba6bfe
# ╠═49c55e48-30dc-11eb-02be-398a6bd228eb
# ╠═a5c27f1a-31bc-11eb-2a2d-5fd911b57f2a


#############################################################################
#############################################################################
#
# This file implements polynomial GCD
#
#############################################################################
#############################################################################

"""
The extended euclid algorithm for polynomials modulo prime.
"""
function extended_euclid_alg(a::Polynomial, b::Polynomial, prime::Int)
    old_r, r = mod(a,prime), mod(b,prime)
    old_s, s = one(Polynomial), zero(Polynomial)
    old_t, t = zero(Polynomial), one(Polynomial)

    while !iszero(mod(r,prime))
        q = divide(old_r, r)(prime) |> first
        old_r, r = r, mod(old_r - q*r, prime)
        old_s, s = s, mod(old_s - q*s, prime)
        old_t, t = t, mod(old_t - q*t, prime)
    end
    g, s, t = old_r, old_s, old_t
    @assert mod(s*a + t*b - g, prime) == 0
    return g, s, t
end

"""
The GCD of two polynomials modulo prime.
"""
gcd(a::Polynomial, b::Polynomial, prime::Int) = extended_euclid_alg(a,b,prime) |> first


function f(x)
    if x < 8
        return 0
    end
    ans = (x ÷ 3) - 2
    return ans + f((x ÷ 3) - 2)
end

a = [140170,75120,75645,134664,124948,137630,146662,116881,120030,94332,50473,59361,128237,84894,51368,128802,57275,129235,113481,66378,55842,90548,107696,53603,130458,80306,120820,131313,100303,59224,123369,140584,60642,68184,103101,82278,51968,51048,98139,60498,127082,71197,109478,71286,84840,141305,51800,72352,93147,73549,122739,62363,58453,59000,63564,63424,51053,120826,123337,130824,59053,77983,68977,67126,96051,53024,145647,139343,113236,59396,146174,148622,83384,86938,100673,80757,107675,147417,124538,136463,104609,149559,136037,54997,139674,101638,65739,70029,143847,122035,66256,78087,105045,108867,99630,127173,139021,139759,134171,104869]
println(sum(f.(a)))


import StatsBase: entropy
entropy(probs::ProbabilityWeights) = -sum(x->(x * log(x)), probs[probs .> 0])

function entropy(counts::FrequencyWeights; method=:Naive)
    method == :Naive && return entropy_naive(counts)
    method == :Shrink && return entropy_shrinkage(counts)
    method == :CS && return entropy_cs(counts)
    throw(ArgumentError("Unknown method $method"))
end

entropy_naive(counts::FrequencyWeights) =
    entropy(ProbabilityWeights(counts / sum(counts)))

"Chao-Shen (2003) entropy estimator."
function entropy_cs(counts::FrequencyWeights)
  n = sum(counts)
  θ_ML = counts / n

  f1 = sum(counts .== 1)
  f1 = (f1 == n) ? n - 1 : f1 # avoid C=0

  # Estimate coverage
  C = (1 - f1 / n)
  p_a = C * θ_ML
  l_a = (1 .- (1 .- p_a) .^ n)

  return - sum(p_a .* log.(p_a) ./ l_a)
end

#=
From http://www.jmlr.org/papers/volume10/hausser09a/hausser09a.pdf
=#
"Shrinkage entropy estimator."
function entropy_shrinkage(counts::FrequencyWeights)
    n = sum(counts)
    θ_ML = counts / n

    # Uniform distribution
    t_k = 1 / length(θ_ML)

    den = (n - 1) * sum((θ_ML .- t_k) .^2)
    if den < 1e-10
      return entropy(ProbabilityWeights(θ_ML))
    else
      # Regularization parameter
      λ = (1 - sum(θ_ML .^ 2)) / den
      return entropy(ProbabilityWeights(λ * t_k .+ (1 - λ) * θ_ML))
    end
end

"""
Estimate the entropy of an array using a naive (frequencies-based),
Chao-Shen, or shrinkage estimator. Chao-Shen and shrinkage estimators reduce
the bias for small samples and a large number of classes.
"""
function estimate_entropy(data::AbstractVector; method::Symbol=:Naive)
  count_values = values(countmap(data))
  freqs = FrequencyWeights(collect(count_values))
  return entropy(freqs; method=method)
end

function estimate_joint_entropy(x::AbstractVector, y::AbstractVector; method::Symbol=:Naive)
  @assert length(x) == length(y) "Vectors must be the same length"

  count = countmap([hash(yᵢ, hash(xᵢ)) for (xᵢ, yᵢ) ∈ zip(x, y)])
  freqs = FrequencyWeights(collect(values(count)))
  entropy(freqs; method=method)
end


# elastic-net regularization

export ElasticNet

"""
**Elastic-net regularization**

    ElasticNet(μ=1, λ=1)

Returns the function
```math
f(x) = μ\\|x\\|_1 + (λ/2)\\|x\\|^2,
```
for nonnegative parameters `μ` and `λ`.
"""
struct ElasticNet{R <: Real, S <: Real} <: ProximableFunction
    mu::R
    lambda::S
    function ElasticNet{R, S}(mu::R, lambda::S) where {R <: Real, S <: Real}
        if lambda < 0 || mu < 0
            error("parameters `μ` and `λ` must be nonnegative")
        else
            new(mu, lambda)
        end
    end
end

is_separable(f::ElasticNet) = true
is_prox_accurate(f::ElasticNet) = true
is_convex(f::ElasticNet) = true

ElasticNet(mu::R=1, lambda::S=1) where {R <: Real, S <: Real} = ElasticNet{R, S}(mu, lambda)

function (f::ElasticNet)(x::AbstractArray{T}) where {
    R <: Real, T <: RealOrComplex{R}
}
    return f.mu * norm(x, 1) + f.lambda / R(2) * norm(x, 2)^2
end

function prox!(y::AbstractArray{R}, f::ElasticNet, x::AbstractArray{R}, gamma::R=R(1)) where {
    R <: Real,
}
    sqnorm2x = R(0)
    norm1x = R(0)
    gm = gamma * f.mu
    gl = gamma * f.lambda
    for i in eachindex(x)
        y[i] = (x[i] + (x[i] <= -gm ? gm : (x[i] >= gm ? -gm : -x[i])))/(1 + gl)
        sqnorm2x += abs2(y[i])
        norm1x += abs(y[i])
    end
    return f.mu * norm1x + f.lambda / R(2) * sqnorm2x
end

function prox!(y::AbstractArray{R}, f::ElasticNet, x::AbstractArray{R}, gamma::AbstractArray{R}) where {
    R <: Real
}
    sqnorm2x = R(0)
    norm1x = R(0)
    for i in eachindex(x)
        gm = gamma[i] * f.mu
        gl = gamma[i] * f.lambda
        y[i] = (x[i] + (x[i] <= -gm ? gm : (x[i] >= gm ? -gm : -x[i])))/(1 + gl)
        sqnorm2x += abs2(y[i])
        norm1x += abs(y[i])
    end
    return f.mu * norm1x + f.lambda / R(2) * sqnorm2x
end

function prox!(y::AbstractArray{Complex{R}}, f::ElasticNet, x::AbstractArray{Complex{R}}, gamma::R=R(1)) where {
    R <: Real
}
    sqnorm2x = R(0)
    norm1x = R(0)
    gm = gamma * f.mu
    gl = gamma * f.lambda
    for i in eachindex(x)
        y[i] = sign(x[i]) * max(0, abs(x[i]) - gm)/(1 + gl)
        sqnorm2x += abs2(y[i])
        norm1x += abs(y[i])
    end
    return f.mu * norm1x + f.lambda / R(2) * sqnorm2x
end

function prox!(y::AbstractArray{Complex{R}}, f::ElasticNet, x::AbstractArray{Complex{R}}, gamma::AbstractArray{R}) where {
    R <: Real
}
    sqnorm2x = R(0)
    norm1x = R(0)
    for i in eachindex(x)
        gm = gamma[i] * f.mu
        gl = gamma[i] * f.lambda
        y[i] = sign(x[i]) * max(0, abs(x[i]) - gm)/(1 + gl)
        sqnorm2x += abs2(y[i])
        norm1x += abs(y[i])
    end
    return f.mu * norm1x + f.lambda / R(2) * sqnorm2x
end

function gradient!(y::AbstractArray{T}, f::ElasticNet, x::AbstractArray{T}) where {
    R <: Real, T <: RealOrComplex{R}
}
    # Gradient of 1 norm
    y .= f.mu .* sign.(x)
    # Gradient of 2 norm
    y .+= f.lambda .* x
    return f.mu * norm(x, 1) + f.lambda / R(2) * norm(x, 2)^2
end

fun_name(f::ElasticNet) = "elastic-net regularization"
fun_dom(f::ElasticNet) = "AbstractArray{Real}, AbstractArray{Complex}"
fun_expr(f::ElasticNet) = "x ↦ μ||x||_1 + (λ/2)||x||²"
fun_params(f::ElasticNet) = "μ = $(f.mu), λ = $(f.lambda)"

function prox_naive(f::ElasticNet, x::AbstractArray{T}, gamma::R=R(1)) where {
    R <: Real, T <: RealOrComplex{R}
}
    uz = max.(0, abs.(x) .- gamma * f.mu)/(1 + f.lambda * gamma)
    return sign.(x) .* uz, f.mu * norm(uz, 1) + f.lambda / R(2) * norm(uz)^2
end

function prox_naive(f::ElasticNet, x::AbstractArray{T}, gamma::AbstractArray{R}) where {
    R <: Real, T <: RealOrComplex{R}
}
    uz = max.(0, abs.(x) .- gamma .* f.mu)./(1 .+ f.lambda .* gamma)
    return sign.(x) .* uz, f.mu * norm(uz, 1) + f.lambda / R(2) * norm(uz)^2
end