####
#### LinearRegressor
####

"""
    LinearRegressor(; bias::Bool=true)

$LINEAR_DESCR

# Keyword Parameters

- `bias::Bool=true`: if true includes a bias term else fits without bias term.
"""
@mlj_model mutable struct LinearRegressor <: MMI.Deterministic
    bias::Bool = true
end

struct LinearFitresult{T, F<:Real, M<:AbstractArray{F}} <: MMI.MLJType
    sol_matrix::M
    bias::Bool
    names::T
end

_convert(common_type, x::AbstractVector) = convert(AbstractVector{common_type}, x)
_convert(common_type, x::AbstractMatrix) = convert(AbstractMatrix{common_type}, MMI.matrix(x))
matrix_(X::AbstractVector) = X
matrix_(X) = MMI.matrix(X)
_names(y::AbstractVector) = nothing
_names(Y) = collect(MMI.schema(Y).names)

function _matrix(X, target)
    Xmatrix_ = MMI.matrix(X)
    Y_ = matrix_(target)
    common_type = promote_type(eltype(Xmatrix_), eltype(Y_))
    Xmatrix = _convert(common_type, Xmatrix_)
    Y = _convert(common_type, Y_)
    return Xmatrix, Y, _names(target)
end

function MMI.fit(model::LinearRegressor, verbosity::Int, X, y)
    Xmatrix, y_, target_header= _matrix(X, y)
    θ = MS.llsq(Xmatrix, y_; bias=model.bias)
    fitresult = LinearFitresult(θ, model.bias, target_header)
    report = NamedTuple()
    cache = nothing
    return fitresult, cache, report
end

function _regressor_fitted_params(fr::LinearFitresult{Nothing, <:Real, <:AbstractVector})
    return (
        coefficients=fr.sol_matrix[1:end-Int(fr.bias)],
        intercept=ifelse(fr.bias, fr.sol_matrix[end], nothing)
    )
end

function _regressor_fitted_params(fr::LinearFitresult{<:Vector, <:Real, <:AbstractMatrix})
    return (
        coefficients=fr.sol_matrix[1:end-Int(fr.bias), :],
        intercept=fr.bias ? fr.sol_matrix[end, :] : nothing
    )
end

function MMI.fitted_params(::LinearRegressor, fr)
    return _regressor_fitted_params(fr)
end

function _predict_regressor(
    fr::LinearFitresult{Nothing, <:Real, <:AbstractVector},
    Xmat_new::AbstractMatrix,
    prototype
)
    if fr.bias
        return @views Xmat_new * fr.sol_matrix[1:end-1] .+ transpose(fr.sol_matrix[end])
    else
        return @views Xmat_new * fr.sol_matrix[1:end-1]
    end
end

function _predict_regressor(
    fr::LinearFitresult{<:Vector, <:Real, <:AbstractMatrix},
    Xmat_new::AbstractMatrix,
    prototype
)
    if fr.bias
        return MMI.table(
            Xmat_new * @view(fr.sol_matrix[1:end-1, :]) .+ transpose(
                @view(fr.sol_matrix[end, :])
            );
            names=fr.names,
            prototype=prototype
        )
    else
        return MMI.table(
            Xmat_new * @view(fr.sol_matrix[1:end-1, :]);
            names=fr.names,
            prototype=prototype
        )
    end
end

function MMI.predict(::LinearRegressor, fr, Xnew)
    Xmat_new = MMI.matrix(Xnew)
    return _predict_regressor(fr, Xmat_new, Xnew)
end

metadata_model(
    LinearRegressor,
    input=Table(Continuous),
    target=Union{Table(Continuous), AbstractVector{Continuous}},
    weights=false,
    descr=LINEAR_DESCR,
    path="$(PKG).LinearRegressor"
)

####
#### RidgeRegressor
####

_check_typeof_lambda(x)= x isa AbstractVecOrMat || (x isa Real && x ≥ 0)

"""
    RidgeRegressor(; lambda::Union{Real, AbstractVecOrMat}=1.0, bias::Bool=true)

$RIDGE_DESCR

# Keyword Parameters

- `lambda::Union{Real, AbstractVecOrMat}=1.0`: non-negative parameter for the
    regularization strength.
- `bias::Bool=true`: if true includes a bias term else fits without bias term.
"""
@mlj_model mutable struct RidgeRegressor <: MMI.Deterministic
    lambda::Union{Real, AbstractVecOrMat} = 1.0::(_check_typeof_lambda(_))
    bias::Bool = true
end

function MMI.fit(model::RidgeRegressor, verbosity::Int, X, y)
    Xmatrix, y_, target_header = _matrix(X, y)
    θ = MS.ridge(Xmatrix, y_, model.lambda; bias=model.bias)
    fitresult = LinearFitresult(θ, model.bias, target_header)
    report = NamedTuple()
    cache = nothing
    return fitresult, cache, report
end

function MMI.fitted_params(::RidgeRegressor, fr)
    return _regressor_fitted_params(fr)
end

function MMI.predict(::RidgeRegressor, fr, Xnew)
    Xmat_new = MMI.matrix(Xnew)
    return _predict_regressor(fr, Xmat_new, Xnew)
end

metadata_model(
    RidgeRegressor,
    input=Table(Continuous),
    target=Union{Table(Continuous), AbstractVector{Continuous}},
    weights=false,
    descr=RIDGE_DESCR,
    path="$(PKG).RidgeRegressor"
)


function get_endpoints(N)
	inners = 2 .^ collect(0:N-1)
	outers = reverse(inners)
	combs = Float64[]
	for (inner, outer) in zip(inners, outers)
		endpoints = [1, 2]
		c = repeat(endpoints, inner=inner, outer=outer)
		if Base.isempty(combs)
			combs = c
		else
			combs = hcat(combs, c)
		end
	end
	return combs
end

get_sequential_endpoints(N, i) = digits(i-1, base=2, pad=N) .+ 1

function d_dsw(X⃗::Vector{Interval}, Y⃗::Vector{Interval}; squared::Bool=false)
	p = length(X⃗)
	num_endpoints = 2 * p

	d_min = Inf
	d_max = -Inf
	for idx_endpoint = 1:2^num_endpoints
		endpoints = get_sequential_endpoints(num_endpoints, idx_endpoint)
		dⱼᵢ = 0
		for i = 1:p
			dⱼᵢ += (X⃗[i][endpoints[i]] - Y⃗[i][endpoints[p + i]])^2
		end
		if (!squared) dⱼᵢ ^= 0.5 end

		d_min = min(d_min, dⱼᵢ)
		d_max = max(d_max, dⱼᵢ)
	end
	Interval(d_min, d_max)
end

function d_dsw(X⃗::FuzzyVector, Y⃗::FuzzyVector; squared::Bool=false)
	levels = X⃗[1].levels
	num_levels = length(levels)

	if X⃗ == Y⃗
		d = SingletonFuzzyNumber(levels, number=0)
	elseif length(X⃗) ≠ length(Y⃗)
		return false
	else
		grades = Vector{Interval}(undef, num_levels)
		for (lvl, α) in enumerate(levels)
			X⃗_cut = cut(X⃗, α)
			Y⃗_cut = cut(Y⃗, α)
			grades[lvl] = d_dsw(X⃗_cut, Y⃗_cut; squared=squared)
		end
		d = FuzzyNumber(levels, grades)
	end
	d
end

function u(X⃗::FuzzyVector, prototypes::Vector{FuzzyVector}; m::Real=2.0, method::String="lfcm")
	m > 1 || error("fuzzifier m ∈ (1, ∞)")
	levels = X⃗[1].levels
	num_levels = length(levels)
	c = length(prototypes)

	Iⱼ = [i for i = 1:c if X⃗ == prototypes[i]]
	uⱼ = Vector{FuzzyNumber}(undef, c)
	if isempty(Iⱼ) # Iⱼ = ∅
		for i = 1:c
			grades = Vector{Interval}(undef, num_levels)
			for (lvl, α) in enumerate(levels)
				X_cut = cut(X⃗, α)
				C_cut = cut(prototypes,  α)
				if method == "lfcm"
					grades[lvl] = u_lfcm(X_cut, C_cut, i, m=m)
				elseif method == "dsw"
					grades[lvl] = u_dsw(X_cut, C_cut, i, m=m)
				end
			end
			uⱼ[i] = FuzzyNumber(levels, grades)
		end
	else # Iⱼ ≠ ∅
		for i = 1:c
			if i ∉ Iⱼ
				uⱼ[i] = SingletonFuzzyNumber(levels, number=0)
			else
				uⱼ[i] = SingletonFuzzyNumber(levels, number=1 / length(Iⱼ))
			end
		end
	end
	uⱼ
end

function u_dsw(X⃗::Vector{Interval}, prototypes::Vector{Vector{Interval}}, i::Int; m::Real=2.0)
	m > 1 || error("fuzzifier m ∈ (1, ∞)")
	h = 1 / (1 - m)
	c = length(prototypes)
	p = length(X⃗)

	C⃗ᵢ = prototypes[i]
	num_endpoints = (c + 1) * p

	u_min = Inf
	u_max = -Inf
	for idx_endpoint = 1:2^num_endpoints
		endpoints = get_sequential_endpoints(num_endpoints, idx_endpoint)

		dⱼᵢ = 0
		for j = 1:p
			c_idx = p + (i-1)*p + j
			dⱼᵢ += (X⃗[j][endpoints[j]] - C⃗ᵢ[j][endpoints[c_idx]])^2
		end
		if dⱼᵢ != 0
			dⱼᵢ ^= h
		end

		∑ = 0
		for k = 1:c
			C⃗ₖ = prototypes[k]
			dⱼₖ = 0
			for j = 1:p
				c_idx = p + (k-1)*p + j
				dⱼₖ += (X⃗[j][endpoints[j]] - C⃗ₖ[j][endpoints[c_idx]])^2
			end
			if dⱼₖ != 0
				dⱼₖ ^= h
			end
			∑ += dⱼₖ
		end
		u = ∑ == 0 ? nothing : dⱼᵢ / ∑
		u_min = min(u_min, u)
		u_max = max(u_max, u)
	end
	Interval(u_min, u_max)
end

function u_lfcm(X⃗::Vector{Interval}, prototypes::Vector{Vector{Interval}}, i::Int; m::Real=2.0, d_method::String="interval")
	m > 1 || error("fuzzifier m ∈ (1, ∞)")
	c = length(prototypes)

	D_squared = Vector{Interval}(undef, c)

	for k = 1:c
		if d_method == "dsw"
			D_squared[k] = d_dsw(X⃗, prototypes[k], squared=true)
		else
			D_squared[k] = d_interval(X⃗, prototypes[k], squared=true)
		end
	end

	u_lfcm(D_squared, i; m=m)
end

function u_lfcm(D_squared::Vector{Interval}, i::Int; m::Real=2.0)
	m > 1 || error("fuzzifier m ∈ (1, ∞)")
	h = 1 / (1 - m)
	c = length(D_squared)

	u_a = -1
	u_b = -1

	# corollary 1
	if D_squared[i].right != 0
		for k = 1:c
			if k != i && D_squared[k].left == 0
				u_a = 0
				break
			end
		end
	end

	# corollary 2
	if D_squared[i].right == D_squared[i].left == 0
		for k = 1:c
			if k != i && D_squared[k].left != 0
				u_a = 1
				break
			end
		end
	end

	# corollary 3
	if D_squared[i].left != 0
		for k = 1:c
			if k != i && D_squared[k].right == 0
				u_b = 0
				break
			end
		end
	end

	# corollary 4
	if D_squared[i].left == 0
		for k = 1:c
			if k != i && D_squared[k].right != 0
				u_b = 1
				break
			end
		end
	end

	d2jih = (D_squared[i].right)^h
	d1jih = (D_squared[i].left)^h

	u_a_denom = d2jih
	u_b_denom = d1jih
	for k = 1:c
		if k != i
			u_a_denom += (D_squared[k].left)^h
			u_b_denom += (D_squared[k].right)^h
		end
	end
	if u_a == -1
		u_a = d2jih / u_a_denom
	end
	if u_b == -1
		u_b = d1jih / u_b_denom
	end

	Interval(u_a, u_b)
end

function c_dsw(X::Vector{FuzzyVector}, u::Matrix{FuzzyNumber}; m::Real=2.0)
	m > 1 || error("fuzzifier m ∈ (1, ∞)")
	levels = X[1][1].levels
	num_levels = length(levels)
	N, c = size(u)
	p = length(X[1])

	C = Vector{FuzzyVector}(undef, c)
	for i = 1:c
		C⃗ = Vector{FuzzyNumber}(undef, p)
		for j = 1:p
			grades = Vector{Interval}(undef, num_levels)
			for lvl = 1:num_levels
				num_endpoints = N + N

				c_min = Inf
				c_max = -Inf
				@floop for idx_endpoint = 1:2^num_endpoints
					endpoints = get_sequential_endpoints(num_endpoints, idx_endpoint)
					numerator = 0
					for k = 1:N
						u_i = N + k
						numerator += u[k, i][lvl][endpoints[u_i]]^m * X[k][j][lvl][endpoints[k]]
					end

					∑ = 0
					for k = 1:N
						u_i = N + k
						∑ += u[k, i][lvl][endpoints[u_i]]
					end
					cᵢⱼ = ∑ == 0 ? nothing : numerator / ∑
					@reduce() do (c_min; cᵢⱼ)
						if cᵢⱼ < c_min
							c_min = cᵢⱼ
						end
					end
					@reduce() do (c_max; cᵢⱼ)
						if cᵢⱼ > c_max
							c_max = cᵢⱼ
						end
					end
				end
				grades[lvl] = Interval(c_min, c_max)
			end
			C⃗[j] = FuzzyNumber(levels, grades)
		end
		C[i] = FuzzyVector(C⃗)
	end
	C
end

function km_iwa(X::Vector{Interval}, u::Vector{Interval}; bound::String, m::Real=2.0) # Karnik and Mendel's interval weighted average
    N = size(X, 1)
    x = Vector{Float64}(undef, N)
    for k = 1:N
		if bound == "lower"
			x[k] = round(X[k].left, digits=6) # a_i
		elseif bound == "upper"
			x[k] = round(X[k].right, digits=6) # b_i
		else
			throw(ArgumentError("bound must be \"lower\" or \"upper\""))
		end
    end
    x_sorted = sort(x)
    sorted_indices = sortperm(x)

    w = Vector{Float64}(undef, N)
    c = Vector{Float64}(undef, N)
    d = Vector{Float64}(undef, N)
    for k = 1:N
        w[k] = round(mid(u[k]), digits=6)
        c[k] = round(u[k].left, digits=6)
        d[k] = round(u[k].right, digits=6)
    end
    y′ = round(sum(w.^m .* x) / sum(w.^m), digits=6)

	for iteration = 1:N+1
		iteration <= N || error("KM IWA -> infinite loop")

		# println(x_sorted)
        _k = nothing
        for k = 1:N-1
            if x_sorted[k] <= y′ && y′ <= x_sorted[k+1]
				# println(x_sorted[k], " <= ", y′, " <= ", x_sorted[k+1], " | k=$k")
                _k = k
                break
            end
        end
		!isnothing(_k) || error("Can't find _k")

		# println("_k ", _k)
		left_indices = sorted_indices[1:_k]
		right_indices = sorted_indices[_k+1:end]
		if bound == "lower"
			w[left_indices] = d[left_indices]
			w[right_indices] = c[right_indices]
		elseif bound == "upper"
			w[left_indices] = c[left_indices]
			w[right_indices] = d[right_indices]
		end
        w = max.(w, 0) # handle the weird case that w is negative??
		# println(bound, " ", w)

        y_k = round(sum(w.^m .* x) / sum(w.^m), digits=6)
        if isapprox(y′, y_k, atol=1e-5)
            break
		# else
		# 	println("$y′ ≂̸ $y_k")
        end
        y′ = y_k
    end
    y′
end

function c_karnik(X::Vector{FuzzyVector}, u::Matrix{FuzzyNumber}; m::Real=2.0)
	m > 1 || error("fuzzifier m ∈ (1, ∞)")
	levels = X[1][1].levels
	num_levels = length(levels)
	N, c = size(u)
	p = length(X[1])

	C = Vector{FuzzyVector}(undef, c)
	for i = 1:c
		C⃗ = Vector{FuzzyNumber}(undef, p)
		for j = 1:p
			grades = Vector{Interval}(undef, num_levels)
			for (lvl, α) in enumerate(levels)
				X_cut = cut(X, j, α)
				u_cut = cut(FuzzyVector(u[:, i]), α)
				c_left = km_iwa(X_cut, u_cut; bound="lower", m=m)
				c_right = km_iwa(X_cut, u_cut; bound="upper", m=m)
				grades[lvl] = Interval(c_left, c_right)
			end
			C⃗[j] = FuzzyNumber(levels, grades)
		end
		C[i] = FuzzyVector(C⃗)
	end
	C
end

function c_karnik(X::Vector{FuzzyVector}, u::Vector{FuzzyNumber}; m::Real=2.0)
	m > 1 || error("fuzzifier m ∈ (1, ∞)")
	levels = X[1][1].levels
	num_levels = length(levels)
	c = 1
	p = length(X[1])

	C⃗ = Vector{FuzzyNumber}(undef, p)
	for j = 1:p
		grades = Vector{Interval}(undef, num_levels)
		for (lvl, α) in enumerate(levels)
			X_cut = cut(X, j, α)
			u_cut = cut(FuzzyVector(u), α)
			c_left = km_iwa(X_cut, u_cut; bound="lower", m=m)
			c_right = km_iwa(X_cut, u_cut; bound="upper", m=m)
			grades[lvl] = Interval(c_left, c_right)
		end
		C⃗[j] = FuzzyNumber(levels, grades)
	end
	FuzzyVector(C⃗)
end

function d_interval(A⃗::FuzzyVector, B⃗::FuzzyVector; squared::Bool=false)
	if A⃗ == B⃗
		𝟎 = SingletonFuzzyNumber(A⃗[1].levels, number=0)
		return 𝟎
	elseif length(A⃗) ≠ length(B⃗)
		return false
	end

	levels = A⃗[1].levels
	num_levels = length(levels)

	grades = Vector{Interval}(undef, num_levels)
	for (lvl, α) in enumerate(levels)
		A_cut = cut(A⃗, α)
		B_cut = cut(B⃗, α)
		grades[lvl] = d_interval(A_cut, B_cut; squared=squared)
	end
	FuzzyNumber(levels, grades)
end

function d_interval(A⃗::Vector{Interval}, B⃗::Vector{Interval}; squared::Bool=false)
	p = length(A⃗)

	d = Interval(0)
	for i = 1:p
		d += (A⃗[i] - B⃗[i])^2
	end
	if (!squared) d ^= 0.5 end

	d
end

function u_interval(squared_fuzzy_distances::Vector{FuzzyNumber}; m::Real=2.0)
	m > 1 || error("fuzzifier m ∈ (1, ∞)")
	c = length(squared_fuzzy_distances)
	levels = squared_fuzzy_distances[1].levels
	num_levels = length(levels)

	I = Int64[]
	𝟎 = SingletonFuzzyNumber(levels, number=0)
	for i = 1:c
		if squared_fuzzy_distances[i] == 𝟎
			push!(I, i)
		end
	end

	u⃗ = Vector{FuzzyNumber}(undef, c)
	if isempty(I)
		for i = 1:c
			grades = Vector{Interval}(undef, num_levels)
			for (lvl, α) in enumerate(levels)
				d²_cut = cut(squared_fuzzy_distances, α)
				grades[lvl] = u_interval(d²_cut, i; m=m)
			end
			u⃗[i] = FuzzyNumber(levels, grades)
		end
	else
		for i = 1:c
			if i ∉ I
				u⃗[i] = 𝟎
			else
				β = SingletonFuzzyNumber(levels, number=1 / length(I))
				u⃗[i] = β
			end
		end
	end

	u⃗
end

function u_interval(squared_fuzzy_distances::Vector{Interval}, i::Int; m::Real=2.0)
	h = 1 / (1 - m)
	squared_fuzzy_distances[i]^h / sum(squared_fuzzy_distances.^h)
end

function c_interval(X⃗::Vector{FuzzyVector}, u::Vector{FuzzyNumber}; m::Real=2.0)
	m > 1 || error("fuzzifier m ∈ (1, ∞)")
	levels = X⃗[1][1].levels
	num_levels = length(levels)
	N = length(X⃗)
	p = length(X⃗[1])

	C⃗ = Vector{FuzzyNumber}(undef, p)
	for j = 1:p # each dimension j
		membership_grades = Vector{Interval}(undef, num_levels)
		for lvl = 1:num_levels
			sum_numerator = Interval(0)
			sum_denominator = Interval(0)
			for k = 1:N
				sum_numerator += (u[k][lvl]^m * X⃗[k][j][lvl])
				sum_denominator += u[k][lvl]^m
			end
			membership_grades[lvl] = sum_numerator / sum_denominator
		end
		C⃗[j] = FuzzyNumber(levels, membership_grades)
	end
	C⃗ = FuzzyVector(C⃗)
	C⃗
end

function d2(X⃗::Vector{FuzzyVector}, C⃗::Vector{FuzzyVector})
	N = length(X⃗)
	c = length(C⃗)
	squared_fuzzy_distances = Matrix{FuzzyNumber}(undef, N, c)
	for i = 1:c
		for j = 1:N
			squared_fuzzy_distances[j, i] = FuzzySets.d_interval(X⃗[j], C⃗[i], squared=true)
		end
	end
	squared_fuzzy_distances
end


export RandomFourier, FT

"""
    RandomFourier(phases = true)

A surrogate that randomizes the Fourier components
of the signal in some manner. If `phases==true`, the phases are randomized,
otherwise the amplitudes are randomized. `FT` is an alias for `RandomFourier`.

Random Fourier phase surrogates[^Theiler1991] preserve the
autocorrelation function, or power spectrum, of the original signal.
Random Fourier amplitude surrogates preserve the mean and autocorrelation
function but do not preserve the variance of the original. Random
amplitude surrogates are not common in the literature, but are provided
for convenience.

Random phase surrogates can be used to test the null hypothesis that
the original signal was produced by a linear Gaussian process [^Theiler1991].

[^Theiler1991]: J. Theiler, S. Eubank, A. Longtin, B. Galdrikian, J. Farmer, Testing for nonlinearity in time series: The method of surrogate data, Physica D 58 (1–4) (1992) 77–94.
"""
struct RandomFourier <: Surrogate
    phases::Bool
end
RandomFourier() = RandomFourier(true)
const FT = RandomFourier

function surrogenerator(x::AbstractVector, rf::RandomFourier, rng = Random.default_rng())
    forward = plan_rfft(x)
    inverse = plan_irfft(forward*x, length(x))
    m = mean(x)
    𝓕 = forward*(x .- m)
    shuffled𝓕 = zero(𝓕)
    s = similar(x)
    n = length(𝓕)
    r = abs.(𝓕)
    ϕ = angle.(𝓕)
    coeffs = zero(r)

    init = (inverse = inverse, m = m, coeffs = coeffs, n = n, r = r,
            ϕ = ϕ, shuffled𝓕 = shuffled𝓕)
    return SurrogateGenerator(rf, x, s, init, rng)
end

function (sg::SurrogateGenerator{<:RandomFourier})()
    inverse, m, coeffs, n, r, ϕ, shuffled𝓕 =
        getfield.(Ref(sg.init),
        (:inverse, :m, :coeffs, :n, :r, :ϕ, :shuffled𝓕))
    s, rng, phases = sg.s, sg.rng, sg.method.phases

    if phases
        coeffs .= rand(rng, Uniform(0, 2π), n)
        shuffled𝓕 .= r .* exp.(coeffs .* 1im)
    else
        coeffs .= r .* rand(rng, Uniform(0, 2π), n)
        shuffled𝓕 .= coeffs .* exp.(ϕ .* 1im)
    end
    s .= inverse * shuffled𝓕 .+ m
    return s
end


using DrWatson
@quickactivate "NonlinearDynamicsTextbook"
include(srcdir("colorscheme.jl"))
using DynamicalSystems, InteractiveDynamics
import GLMakie
using AbstractPlotting
using LinearAlgebra
using Statistics

# Code for 3D animation
ds = Systems.lorenz()
u0 = trajectory(ds, 10; Ttr = 100)[end]
dt = 0.01
T = 5.0
framerate = 30
systemtitle = "Lorenz63"

ds = Systems.towel()
u0 = trajectory(ds, 10; Ttr = 100)[end]
dt = 1
T = 15 # total evolove time
framerate = 2
systemtitle = "towelmap"

# %%
ε = 1e-6 # sphere radius
N = 4000 # initial conditions nmaking hypersphere

function uniform_sphere(N, u0, ε)
    V = typeof(u0)
    us = Vector{V}(undef, N)
    i = 1
    while i ≤ N
        x = rand(V) .- 0.5
        if norm(x) ≤ 1/(2*√3)
            us[i] = u0 .+ (ε .* x)
            i += 1
        end
    end
    return us
end

us = uniform_sphere(N, u0, ε)
# I must renormalize point coordinates to a sphere of radius 1 always,
# because of the internal convertion to 32-bit accuracy, small numbers are bad!
usnormed = [Point3f0(2((u0 .- u)./ε)...,) for u in us]
colors = [RGBAf0(abs.(u)..., 1) for u in usnormed]
plotus = Observable(usnormed)

# fig = Figure(resolution = (1200, 600))
fig = Figure(resolution = (800, 600))
ax3D = fig[1,1] = Axis3(fig)
scatter!(ax3D, plotus; markersize = 2000, color = colors, strokewidth = 0.5)
# ax2 = fig[1, 2] = Axis(fig; width = 400) # this axis has values of principal lengths
# ax2.title = "logarithm of principal axes"
ax3D.xticklabelsvisible = true
ax3D.yticklabelsvisible = true
ax3D.zticklabelsvisible = true
ax3D.azimuth = 4.44
ax3D.elevation = 0.38
ax3D.title = rpad(systemtitle *", t = $(0)", 40, ' ')

# function principals(v)
#     M = transpose(reshape(reinterpret(Float64, v), 3, :))
#     S = SVD(M).S # rotation matrix
#     return S
# end
# s = Observable(principals(usnormed))
# barplot!(ax2, s, color = to_color.(COLORSCHEME[1:3]))

display(fig)

# %%
ut = trajectory(ds, T, u0; dt)
evolution = [trajectory(ds, T, u; dt) for u in us]
t = 0:dt:T

# TODO: Use absolute, realsize of pointcloud

record(
        fig, joinpath(@__DIR__, "volume_growth_$(systemtitle).mp4"), 1:length(evolution[1]);
        framerate
        ) do i
    newus = [Point3f0(2((ut[i] .- e[i])./ε)...,) for e in evolution]
    plotus[] = newus
    # s[] = principals(newus)
    # autolimits!(ax2)
    # must set new limits centered on u
    maxε = maximum(norm(u) for u in newus)
    ax3D.limits = map(j -> (-maxε, +maxε), (1,2,3))
    ax3D.title = systemtitle *", t = $(t[i])"
    ax3D.elevation = 0.38
    ax3D.azimuth = 4.44
end


#!/usr/bin/env julia

export pe16

pe16(n::Integer=big(2)^1000)=digits(n)|>sum

if !haskey(ENV,"PROJECT_EULER_WITHOUT_RESULT")
    pe16()|>print
end


using ExactD, LinearAlgebra, Test
@testset "basis of Fock states" begin
@testset "states with N particles and L sites" begin
    L = 4
    N = 2
    states = make_LN_basis(L, N)

    @test states[1] == 3
    @test states[3] == 6
    @test states[5] == 10
    @test states[6] == 12
end

@testset "states with N1, N2 particles and L sites" begin
    L = 4
    N1 = 2
    N2 = 3
    states = make_LN1N2_basis(L, N1, N2)

    @test states[1] == 16+32+64 + 1+2
    @test states[5] == 16+32+64 + 2+8
    @test states[9] == 16+32+128 + 2+4
    @test states[11] == 16+32+128 + 2+8
    @test states[18] == 16+64+128 + 4+8
    @test states[23] == 32+64+128 + 2+8
end

@testset "spin 1 states with L spins and Sz magnetization" begin
    L = 4
    Sz = 2
    states = make_spin1_LSz_basis(L, Sz)

    @test length(states) == 10
    @test states[1] == 5
    @test states[3] == 20
    @test states[5] == 68
    @test states[7] == 86
    @test states[10] == 149
    @test get_num_spin1_states(L, Sz) == 10

    L = 4
    Sz = 1
    states = make_spin1_LSz_basis(L, Sz)

    @test length(states) == 16
    @test states[1] == 1
    @test states[3] == 16
    @test states[5] == 25
    @test states[8] == 70
    @test states[10] == 82
    @test states[16] == 148
    @test get_num_spin1_states(L, Sz) == 16
end

@testset "spin 1 states with L spins" begin
    L = 3
    states = make_spin1_L_basis(L)

    @test length(states) == 27
    @test states[1] == 0
    @test states[3] == 2
    @test states[5] == 5
    @test states[7] == 8
    @test states[10] == 16
end
end  # testset "basis of Fock states"


# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/Mortar2D.jl/blob/master/LICENSE

"""
    calculate_normals(elements::Dict{Int, Vector{Int}},
                      element_types::Dict{Int, Symbol},
                      X::Dict{Int, Vector{Float64})

Given elements, element types and node locations, calculate nodal normals by
first calculating normal directions for each element and then averaging them
in nodes. As a result we get unique normal direction defined to each node.

# Notes
Only linear elements supported.

# Example

```jldoctest
X = Dict(1 => [7.0, 7.0], 2 => [4.0, 3.0], 3 => [0.0, 0.0])
elements = Dict(1 => [1, 2], 2 => [2, 3])
element_types = Dict(1 => :Seg2, 2 => :Seg2)
normals = calculate_normals(elements, element_types, X)

# output

Dict{Int64,Array{Float64,1}} with 3 entries:
  2 => [0.707107, -0.707107]
  3 => [0.6, -0.8]
  1 => [0.8, -0.6]

```

"""
function calculate_normals(elements, element_types, X)
    normals = empty(X)
    for (elid, elcon) in elements
        @assert element_types[elid] == :Seg2
        d = X[elcon[2]] - X[elcon[1]]
        n = [-d[2], d[1]]
        n /= norm(n)
        for nid in elcon
            haskey(normals, nid) || (normals[nid] = zeros(2))
            normals[nid] += n
        end
    end
    for (nid, n) in normals
        normals[nid] /= norm(normals[nid])
    end
    return normals
end


mutable struct ParallelJacobiPreconditioner{Tv, Ti} <: AbstractPreconditioner{Tv,Ti}
    A::ExtendableSparseMatrix{Tv,Ti}
    invdiag::Array{Tv,1}
    function ParallelJacobiPreconditioner{Tv,Ti}() where {Tv,Ti}
        p=new()
        p.invdiag=zeros(Tv,0)
        p
    end
end

function update!(precon::ParallelJacobiPreconditioner{Tv,Ti}) where {Tv,Ti}
    flush!(precon.A)
    cscmatrix=precon.A.cscmatrix
    if length(precon.invdiag)==0
        precon.invdiag=Array{Tv,1}(undef,cscmatrix.n)
    end
    invdiag=precon.invdiag
    n=cscmatrix.n
    Threads.@threads for i=1:n
        @inbounds  invdiag[i]=1.0/cscmatrix[i,i]
    end
    precon
end

"""
```
ParallelJacobiPreconditioner(;valuetype=Float64, indextype=Int64)
ParallelJacobiPreconditioner(matrix)
```

Parallel Jacobi preconditioner
"""
ParallelJacobiPreconditioner(;valuetype::Type=Float64, indextype::Type=Int64)=ParallelJacobiPreconditioner{valuetype,indextype}()

function  LinearAlgebra.ldiv!(u::AbstractArray{T,1} where T, precon::ParallelJacobiPreconditioner, v::AbstractArray{T,1} where T)
    invdiag=precon.invdiag
    n=length(invdiag)
    Threads.@threads for i=1:n
        @inbounds u[i]=invdiag[i]*v[i]
    end
end

function LinearAlgebra.ldiv!(precon::ParallelJacobiPreconditioner, v::AbstractArray{T,1} where T)
    ldiv!(v, precon, v)
end


#DEMO1: Sample Multi-Dimensional (Vectorized) calculation:
#-------------------------------------------------------------------------------

using MDDatasets
using InspectDR
using Colors

#==Constants
===============================================================================#
red = RGB24(1, 0, 0)
green = RGB24(0, 1, 0)
blue = RGB24(0, 0, 1)
black = RGB24(0, 0, 0)
grey = RGB24(0.5, 0.5, 0.5)

dfltcolorlist = [red, green, black, blue, grey] #Different color for each
dfltline = line(color=blue, width=3)
redline = line(color=red, width=3)

#==Helper functions
===============================================================================#
#Add DataF1 signal to plot:
function _add(plot, sig::DataF1, args...; line=dfltline, kwargs...)
	wfrm = add(plot, sig.x, sig.y, args...; kwargs...)
	wfrm.line = line
	return wfrm
end

#Add DataRS multi-dimensional signal to plot (1-D sweep only):
#TODO: Use EasyPlot module, once registered
function _add(plot, sig::DataRS, args...; id="", line=dfltline, clist=dfltcolorlist, kwargs...)
	for (data, sweepv, color) in zip(sig.elem, sig.sweep.v, clist)
		wfrm = add(plot, data.x, data.y, args..., id="$id[$sweepv]"; kwargs...)
			wfrm.line = deepcopy(line)
			wfrm.line.color = color
	end
	return
end

#Display plot using "do" syntax:
function displayplot(fn, striplist; title="Plotting variable.y vs variable.x")
	plot = InspectDR.transientplot(striplist, title=title)
	fn(plot)
	display(InspectDR.GtkDisplay(), plot)
end

#==DEMO: Sample Multi-Dimensional (Vectorized) Calculation:
===============================================================================#

#Create (x,y) container pair, and call it "x":
x = DataF1(0:.1:20)
#NOTE: Both x & y coordinates of "x" object initialized as y = x = [supplied range]

#“Extract” maximum x-value from data:
xmax = maximum(x)

#Construct noramlized ramp dataset:
unity_ramp = x/xmax

#Observe x & unity_ramp
#-------------------------------------------------------------------------------
displayplot([:lin, :lin]) do plot
	_add(plot, x, id="x", strip = 1)
	_add(plot, unity_ramp, id="unity_ramp", strip = 2)
	InspectDR.write_png("samplemdcalc_1.png", plot)
end

#Compute cos(kx) & ksinkx = cos'(kx):
coskx = cos((2.5pi/10)*x)
ksinkx = deriv(coskx)

#Compute ramps with different slopes using unity_ramp (previously computed):
#(NOTE: Inner-most sweep, we need to specify leaf element type (DataF1 here))
ramp = fill(DataRS{DataF1}, PSweep("slope", [-1, -0.5, 0, 0.5, 1])) do slope
	return unity_ramp * slope
end
#NOTE: Above expression constructs a multi-dimensional DataRS structure,
#      and fills it with (x,y) values for each of the desired parameter
#      values (the slope).

#Observe coskx, ksinkx & ramp
#-------------------------------------------------------------------------------
displayplot([:lin, :lin]) do plot
	_add(plot, coskx, id="coskx", strip = 1)
	_add(plot, ksinkx, id="ksinkx", line=redline, strip = 1)
	_add(plot, ramp, id="ramp", strip = 2)
	plot.strips[1].yext_full = InspectDR.PExtents1D(min=-1.2, max=1.2)
	plot.strips[2].yext_full = InspectDR.PExtents1D(min=-1.5, max=1.5)
	InspectDR.write_png("samplemdcalc_2.png", plot)
end

#Merge 2 datasets with different # of sweeps (coskx & ramp):
r_cos = coskx+ramp

#Observe r_cos
#-------------------------------------------------------------------------------
displayplot([:lin], title = "r_cos = coskx+ramp") do plot
	_add(plot, r_cos, id="r_cos")
	plot.strips[1].yext_full = InspectDR.PExtents1D(min=-2, max=2)
	InspectDR.write_png("samplemdcalc_3.png", plot)
end

#Shift all ramped sin(x) waveforms to be centered at their mid-points:
midval = value(ramp, x=xmax/2)
#midval = (minimum(ramp) + maximum(ramp)) / 2 #Also possible
c_cos = r_cos - midval #Shift by midval (different for each swept slope of "ramp")

#Observe c_cos
#-------------------------------------------------------------------------------
displayplot([:lin], title = "c_cos = r_cos - value(ramp, x=xmax/2)") do plot
	_add(plot, c_cos, id="c_cos")
	plot.strips[1].yext_full = InspectDR.PExtents1D(min=-1.5, max=1.5)
	InspectDR.write_png("samplemdcalc_4.png", plot)
end

#Last line


mas2rad(mas) = mas * pi / 648000000

function gauss(uv, inc, pa, fwhm, fluxg, fluxp)
    bl = baseline(uv, inc, pa)
    Θ = mas2rad(fwhm)
    return fluxg * exp.(-(π * Θ * bl).^2 / (4 * log(2))) .+ fluxp
end

function disk(uv, inc, pa, diam, fluxd, fluxp)
    bl = baseline(uv, inc, pa)
    Θ = mas2rad(diam)
    output = fill(1.0, size(bl))
    blmask = .!iszero.(bl)
    output[blmask] = 2 * besselj1.(π * Θ * bl[blmask]) ./ (π * Θ * bl[blmask])
    return fluxd * output .+ fluxp
end

function annulus(uv, inc, pa, diam, fluxa, fluxp)
    bl = baseline(uv, inc, pa)
    rho_0 = mas2rad(diam) / 2
    return fluxa * besselj0.(2 * π * rho_0 * bl) .+ fluxp
end

function pseudolorentzian(uv, inc, pa, hfdiam, fluxl, fluxp)
    bl = baseline(uv, inc, pa)
    theta = mas2rad(hfdiam)
    return fluxl * exp.(-π * theta * bl / sqrt(3)) .+ fluxp
end


const log2π = log(2*π)
const sqrt2π = √(2*π)
const sqrtπ = √π
# Euler–Mascheroni constant
const γ = 0.577215665

# Ramanujan's approximation to the Γ function:
Γ(x::Real) = sqrtπ*((x-1)/exp(1))^(x-1) * (8*(x-1)^3 + 4*(x-1)^2 + (x-1) + 1/30)^(1/6)


using UncertaintyQuantification, Random

Random.seed!(4183);

N = 10
normal = Normal(0, 0.005)

F = [5, 5, 10, 10, 15, 15, 20, 20, 25, 25]./1.02

noisesamples = [F[i]/387 + rand(normal) for i in 1:N]

observations = hcat(F,noisesamples)
Random.seed!()

function likelihood(x)
    rv = 0
    variation = var(observations[:,1]./observations[:,2])
    for i in 1:N
        rv += (observations[i,1]/observations[i,2] - x[1])^2
    end
    return exp(-rv/(2*variation))
 end

prior(x) = 1
priorsample(n) = rand(Uniform(1,1000),(n))

propdist = Normal(0, 1)
propdistsample() = [rand(propdist)]
propdistpdf(x) = pdf(propdist, x[1])

function term(sset, j)
    rv = true
    if j >= 2 rv = false end
    return rv
end

smcsample = smc(likelihood, prior, priorsample, propdistpdf, propdistsample, 5000, term)


inv2(A::Array{Float64,2}) = 1/(A[1]*A[4]-A[2]*A[3])*[A[4] -A[2];-A[3] A[1]]

inv3(A::Array{Float64,2}) = 1/det(A)*[(A[2,2]*A[3,3]-A[3,2]*A[2,3]) (A[1,3]*A[3,2]-A[3,3]*A[1,2]) (A[1,2]*A[2,3]-A[2,2]*A[1,3])
                    (A[2,3]*A[3,1]-A[3,3]*A[2,1]) (A[1,1]*A[3,3]-A[3,1]*A[1,3]) (A[1,3]*A[2,1]-A[2,3]*A[1,1])
                    (A[2,1]*A[3,2]-A[3,1]*A[2,2]) (A[1,2]*A[3,1]-A[3,2]*A[1,1]) (A[1,1]*A[2,2]-A[2,1]*A[1,2])]

function inv4(m::Array{Float64,2})
    iA = zeros(Float64,4,4)
    iA[1] = (((m[6] * m[11] * m[16] - m[6] * m[12] * m[15]) - m[10] * m[7] * m[16]) + m[10] * m[8] * m[15] + m[14] * m[7] * m[12]) - m[14] * m[8] * m[11]
    iA[5] = (((-(m[5]) * m[11] * m[16] + m[5] * m[12] * m[15] + m[9] * m[7] * m[16]) - m[9] * m[8] * m[15]) - m[13] * m[7] * m[12]) + m[13] * m[8] * m[11]
    iA[9] = (((m[5] * m[10] * m[16] - m[5] * m[12] * m[14]) - m[9] * m[6] * m[16]) + m[9] * m[8] * m[14] + m[13] * m[6] * m[12]) - m[13] * m[8] * m[10]
    iA[13] = (((-(m[5]) * m[10] * m[15] + m[5] * m[11] * m[14] + m[9] * m[6] * m[15]) - m[9] * m[7] * m[14]) - m[13] * m[6] * m[11]) + m[13] * m[7] * m[10]
    iA[2] = (((-(m[2]) * m[11] * m[16] + m[2] * m[12] * m[15] + m[10] * m[3] * m[16]) - m[10] * m[4] * m[15]) - m[14] * m[3] * m[12]) + m[14] * m[4] * m[11]
    iA[6] = (((m[1] * m[11] * m[16] - m[1] * m[12] * m[15]) - m[9] * m[3] * m[16]) + m[9] * m[4] * m[15] + m[13] * m[3] * m[12]) - m[13] * m[4] * m[11]
    iA[10] = (((-(m[1]) * m[10] * m[16] + m[1] * m[12] * m[14] + m[9] * m[2] * m[16]) - m[9] * m[4] * m[14]) - m[13] * m[2] * m[12]) + m[13] * m[4] * m[10]
    iA[14] = (((m[1] * m[10] * m[15] - m[1] * m[11] * m[14]) - m[9] * m[2] * m[15]) + m[9] * m[3] * m[14] + m[13] * m[2] * m[11]) - m[13] * m[3] * m[10]
    iA[3] = (((m[2] * m[7] * m[16] - m[2] * m[8] * m[15]) - m[6] * m[3] * m[16]) + m[6] * m[4] * m[15] + m[14] * m[3] * m[8]) - m[14] * m[4] * m[7]
    iA[7] = (((-(m[1]) * m[7] * m[16] + m[1] * m[8] * m[15] + m[5] * m[3] * m[16]) - m[5] * m[4] * m[15]) - m[13] * m[3] * m[8]) + m[13] * m[4] * m[7]
    iA[11] = (((m[1] * m[6] * m[16] - m[1] * m[8] * m[14]) - m[5] * m[2] * m[16]) + m[5] * m[4] * m[14] + m[13] * m[2] * m[8]) - m[13] * m[4] * m[6]
    iA[15] = (((-(m[1]) * m[6] * m[15] + m[1] * m[7] * m[14] + m[5] * m[2] * m[15]) - m[5] * m[3] * m[14]) - m[13] * m[2] * m[7]) + m[13] * m[3] * m[6]
    iA[4] = (((-(m[2]) * m[7] * m[12] + m[2] * m[8] * m[11] + m[6] * m[3] * m[12]) - m[6] * m[4] * m[11]) - m[10] * m[3] * m[8]) + m[10] * m[4] * m[7]
    iA[8] = (((m[1] * m[7] * m[12] - m[1] * m[8] * m[11]) - m[5] * m[3] * m[12]) + m[5] * m[4] * m[11] + m[9] * m[3] * m[8]) - m[9] * m[4] * m[7]
    iA[12] = (((-(m[1]) * m[6] * m[12] + m[1] * m[8] * m[10] + m[5] * m[2] * m[12]) - m[5] * m[4] * m[10]) - m[9] * m[2] * m[8]) + m[9] * m[4] * m[6]
    iA[16] = (((m[1] * m[6] * m[11] - m[1] * m[7] * m[10]) - m[5] * m[2] * m[11]) + m[5] * m[3] * m[10] + m[9] * m[2] * m[7]) - m[9] * m[3] * m[6]

    d = m[1] * iA[1] + m[2] * iA[5] + m[3] * iA[9] + m[4] * iA[13]
    d==0 && error("Matrix is singular")

    d = 1.0 / d
    for i = 1:16
        iA[i] *= d
    end
    return iA
end


using ApproxFun

x=Fun(identity)
D=Derivative()

u=[Dirichlet();
   1/70*D^2-x*D+I] \ [[1,2],0]


function simpleDFT(hk::Union{Array{T}, SubArray{T}}) where T
    N = length(hk)
    if N == 2
        return [Complex(hk[1] + hk[2]), Complex(hk[1] - hk[2])]
    end
    ev = simpleDFT(@view hk[1:2:end]) #even
    od = simpleDFT(@view hk[2:2:end]) #odd

    twiddle = exp(-2 * π * im / N)
    w = one(Complex{T}) #twiddle^0

    Hk = Array{Complex{T}}(undef, N)
    NHalf = Int(N / 2)
    @inbounds @simd for i = 1:NHalf
        Hk[i] = ev[i] + w * od[i]
        Hk[i + NHalf] = ev[i] - w * od[i] #took me longer than i care to admit - had to look it up
        w = w * twiddle
    end

    return Hk
end


dice_metric(ŷ, y) = 2 * sum((ŷ .& y)) / (sum(ŷ) + sum(y))


@testset "Fitting" begin
    ε = 1.0e-8

    @testset "Fitting-curve_R" begin
        Random.seed!(42)

        p1 = 2
        k1 = Knots(rand(3)) + (p1 + 1) * Knots([0, 1])
        P1 = FastBSplineSpace(p1, k1)
        n1 = dim(P1)
        a_org = [Point(i1, rand()) for i1 in 1:n1]
        M = BSplineCurve([P1], a_org)

        p1′ = p1 + 1
        k1′ = k1 + unique(k1) + Knots(rand(2))
        P1′ = FastBSplineSpace(p1′, k1′)

        M′ = refinement(M, [P1′])
        a_ref = M′.controlpoints

        a_fit = fittingcontrolpoints(M, [P1′])
        @test norm(a_fit - a_ref) < ε

        a_fit = fittingcontrolpoints(M, [P1′], domain=:R)
        @test norm(a_fit - a_ref) < ε
    end

    @testset "Fitting-curve_I" begin
        Random.seed!(42)

        p1 = 2
        k1 = Knots(rand(3)) + Knots([0, 1]) + p1 * Knots([-rand(), 1 + rand()])
        P1 = FastBSplineSpace(p1, k1)
        n1 = dim(P1)
        a_org = [Point(i1, rand()) for i1 in 1:n1]
        M = BSplineCurve([P1], a_org)

        p1′ = p1 + 1
        k1′ = k1 + unique(k1[1+p1:end-p1]) + Knots(rand(2))
        P1′ = FastBSplineSpace(p1′, k1′)

        M′ = refinement(M, [P1′])
        a_ref = M′.controlpoints

        a_fit = fittingcontrolpoints(M, [P1′])

        @test norm(a_fit - a_ref) < ε
    end

    @testset "Fitting-surface_R" begin
        Random.seed!(42)

        p1 = 2
        k1 = Knots(rand(3)) + (p1 + 1) * Knots([0, 1])
        P1 = FastBSplineSpace(p1, k1)
        n1 = dim(P1)
        p2 = 1
        k2 = Knots(rand(4)) + (p2 + 1) * Knots([0, 1])
        P2 = FastBSplineSpace(p2, k2)
        n2 = dim(P2)
        a_org = [Point(i1, i2, rand()) for i1 in 1:n1, i2 in 1:n2]
        M = BSplineSurface([P1, P2], a_org)

        p1′ = p1 + 1
        k1′ = k1 + unique(k1) + Knots(rand(2))
        P1′ = FastBSplineSpace(p1′, k1′)
        p2′ = p2 + 1
        k2′ = k2 + unique(k2) + Knots(rand(2))
        P2′ = FastBSplineSpace(p2′, k2′)

        M′ = refinement(M, [P1′, P2′])
        a_ref = M′.controlpoints

        a_fit = fittingcontrolpoints(M, [P1′, P2′])
        @test norm(a_fit - a_ref) < ε

        a_fit = fittingcontrolpoints(M, [P1′, P2′], domain=:R)
        @test norm(a_fit - a_ref) < ε
    end

    @testset "Fitting-surface_I" begin
        Random.seed!(42)

        p1 = 2
        k1 = Knots(rand(3)) + Knots([0, 1]) + p1 * Knots([-rand(), 1 + rand()])
        P1 = FastBSplineSpace(p1, k1)
        n1 = dim(P1)
        p2 = 1
        k2 = Knots(rand(4)) + Knots([0, 1]) + p2 * Knots([-rand(), 1 + rand()])
        P2 = FastBSplineSpace(p2, k2)
        n2 = dim(P2)
        a_org = [Point(i1, i2, rand()) for i1 in 1:n1, i2 in 1:n2]
        M = BSplineSurface([P1, P2], a_org)

        p1′ = p1 + 1
        k1′ = k1 + unique(k1[1+p1:end-p1]) + Knots(rand(2))
        P1′ = FastBSplineSpace(p1′, k1′)
        p2′ = p2 + 1
        k2′ = k2 + unique(k2[1+p2:end-p2]) + Knots(rand(2))
        P2′ = FastBSplineSpace(p2′, k2′)

        M′ = refinement(M, [P1′, P2′])
        a_ref = M′.controlpoints

        a_fit = fittingcontrolpoints(M, [P1′, P2′])
        @test norm(a_fit - a_ref) < ε
    end

    @testset "Fitting-solid_R" begin
        Random.seed!(42)

        p1 = 2
        k1 = Knots(rand(3)) + (p1 + 1) * Knots([0, 1])
        P1 = FastBSplineSpace(p1, k1)
        n1 = dim(P1)
        p2 = 1
        k2 = Knots(rand(4)) + (p2 + 1) * Knots([0, 1])
        P2 = FastBSplineSpace(p2, k2)
        n2 = dim(P2)
        p3 = 2
        k3 = Knots(rand(5)) + (p3 + 1) * Knots([0, 1])
        P3 = FastBSplineSpace(p3, k3)
        n3 = dim(P3)
        a_org = [Point(i1, i2, i3, rand()) for i1 in 1:n1, i2 in 1:n2, i3 in 1:n3]
        M = BSplineSolid([P1, P2, P3], a_org)

        p1′ = p1 + 1
        k1′ = k1 + unique(k1) + Knots(rand(2))
        P1′ = FastBSplineSpace(p1′, k1′)
        p2′ = p2 + 1
        k2′ = k2 + unique(k2) + Knots(rand(2))
        P2′ = FastBSplineSpace(p2′, k2′)
        p3′ = p3 + 1
        k3′ = k3 + unique(k3) + Knots(rand(2))
        P3′ = FastBSplineSpace(p3′, k3′)

        M′ = refinement(M, [P1′, P2′, P3′])
        a_ref = M′.controlpoints

        a_fit = fittingcontrolpoints(M, [P1′, P2′, P3′])
        @test norm(a_fit - a_ref) < ε

        a_fit = fittingcontrolpoints(M, [P1′, P2′, P3′], domain=:R)
        @test norm(a_fit - a_ref) < ε
    end

    @testset "Fitting-solid_I" begin
        Random.seed!(42)

        p1 = 2
        k1 = Knots(rand(3)) + Knots([0, 1]) + p1 * Knots([-rand(), 1 + rand()])
        P1 = FastBSplineSpace(p1, k1)
        n1 = dim(P1)
        p2 = 1
        k2 = Knots(rand(4)) + Knots([0, 1]) + p1 * Knots([-rand(), 1 + rand()])
        P2 = FastBSplineSpace(p2, k2)
        n2 = dim(P2)
        p3 = 2
        k3 = Knots(rand(5)) + Knots([0, 1]) + p1 * Knots([-rand(), 1 + rand()])
        P3 = FastBSplineSpace(p3, k3)
        n3 = dim(P3)
        a_org = [Point(i1, i2, i3, rand()) for i1 in 1:n1, i2 in 1:n2, i3 in 1:n3]
        M = BSplineSolid([P1, P2, P3], a_org)

        p1′ = p1 + 1
        k1′ = k1 + unique(k1[1+p1:end-p1]) + Knots(rand(2))
        P1′ = FastBSplineSpace(p1′, k1′)
        p2′ = p2 + 1
        k2′ = k2 + unique(k2[1+p2:end-p2]) + Knots(rand(2))
        P2′ = FastBSplineSpace(p2′, k2′)
        p3′ = p3 + 1
        k3′ = k3 + unique(k3[1+p3:end-p3]) + Knots(rand(2))
        P3′ = FastBSplineSpace(p3′, k3′)

        M′ = refinement(M, [P1′, P2′, P3′])
        a_ref = M′.controlpoints

        a_fit = fittingcontrolpoints(M, [P1′, P2′, P3′])
        @test norm(a_fit - a_ref) < 1.0e-6
    end
end


using Combinatorics, Polyhedra
using CDDLib

v = vrep(collect(permutations([0, 1, 2, 3])))

p4 = polyhedron(v, CDDLib.Library())

# orthogonal basis for projection hyperplane
v1 = [1, -1,  0,  0]
v2 = [1,  1, -2,  0]
v3 = [1,  1,  1, -3]

p3 = project(p4, [v1 v2 v3])

# using MeshCat
# vis = Visualizer()
# open(vis)
m = Polyhedra.Mesh(p3);
setobject!(vis, m,
           MeshPhongMaterial(color=RGBA{Float32}(1, 0, 0, 0.5)))

# using Makie
# sc = Scene()
# m = Polyhedra.Mesh(p3);
# mesh!(sc, m, color=:blue)
# display(sc)
# wireframe(m)


using Test, Random
function main()
    # OLS
    x = [ones(10,1) (1:10)]
    Random.seed!(1)
    y = rand(10,1)
    b = ols(y,x,silent=true)[1]
    @testset "ols" begin
        @test b[1,1] ≈ 0.07655377367377458
    end
    # samin
    xopt, fopt = samin()
    @testset "samin" begin
    @test xopt[1] ≈ 0.0 atol=1e-5
    @test fopt ≈ 2.0 atol=1e-5
    end
    # npreg
    @testset "npreg" begin
        yhat = npreg();
        @test yhat[1,1] ≈ -0.8108308248103517
    end
end
main()


"""
    random_micrm_params(N,M,leakage)

    Generates a parameter dictionary for micrm system with `N` consumers and `M` resources with random parameters. All values are drawn from uniform random distributions apart from uptake (u) and leakage (l) which are drawn from dirchlet distributions. This means overall uptake and leakage sum to 1 and the value set by the leakage parameter respectively.
"""
function random_micrm_params(N,M,leakage)
    #uptake
    du = Distributions.Dirichlet(M,1.0)
    u = rand(du, N)'

    #cost term
    m = rand(N)

    #inflow + outflow
    ρ,ω = rand(M),rand(M)

    #leakage
    l = rand(du,M)' .* leakage

    return Dict(:N => N, :M => M, :u => u, :m => m, :ρ => ρ, :ω => ω, :l => l)
end


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

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

# ╔═╡ 1d6260d4-f663-11ea-03da-efe9ed63f9bd
# Explicit use of own environment instead of a local one for each notebook
begin
	using Pkg
	cd(joinpath(dirname(@__FILE__),".."))
    Pkg.activate(pwd())
    using NativeSVG
	using Plots
end

# ╔═╡ e6ff0e98-f662-11ea-03a7-e3d09e6272a6
md"""# Physical Modelling

Port of [Think Complexity chapter 7](http://greenteapress.com/complexity2/html/index.html) by Allen Downey."""

# ╔═╡ 2abf49e0-f663-11ea-25f3-2f9229de732e
md"""## Diffusion

In 1952 Alan Turing published a paper called “The chemical basis of morphogenesis”, which describes the behavior of systems involving two chemicals that diffuse in space and react with each other. He showed that these systems produce a wide range of patterns, depending on the diffusion and reaction rates, and conjectured that systems like this might be an important mechanism in biological growth processes, particularly the development of animal coloration patterns.

Turing’s model is based on differential equations, but it can be implemented using a cellular automaton.

Before we get to Turing’s model, we’ll start with something simpler: a diffusion system with just one chemical. We’ll use a 2-D CA where the state of each cell is a continuous quantity (usually between 0 and 1) that represents the concentration of the chemical.

We’ll model the diffusion process by comparing each cell with the average of its neighbors. If the concentration of the center cell exceeds the neighborhood average, the chemical flows from the center to the neighbors. If the concentration of the center cell is lower, the chemical flows the other way.

We’ll use a diffusion constant, `r`, that relates the difference in concentration to the rate of flow:"""

# ╔═╡ 40373f12-f663-11ea-256c-abd1458a8e85
function applydiffusion(array::Array{Float64, 2}, r::Float64=0.1)
    nr_y, nr_x = size(array)
    out = deepcopy(array)
    for y in 2:nr_y-1
        for x in 2:nr_x-1
            c = array[y-1, x] + array[y, x-1] + array[y, x+1] + array[y+1, x] - 4*array[y, x]
            out[y, x] += r*c
        end
    end
    out
end

# ╔═╡ 52f4e280-f663-11ea-38a4-c52a7b06b564
md"""visualisation:"""

# ╔═╡ 61437b46-f663-11ea-12c6-2ff992812dca
function visualizearray(array::Array{Float64, 2}, dim)
    (nr_y, nr_x) = size(array)
    width = dim * (nr_x - 1)
    height = dim * (nr_y - 1)
    Drawing(width=width, height=height) do
		for (j, y) in enumerate(2:nr_y-1)
			for (i, x) in enumerate(2:nr_x-1)
				gray = 80*(1-array[y, x])+10
				fill = "rgb($gray%,$gray%,$gray%"
				rect(x=i*dim, y=j*dim, width=dim, height=dim, fill=fill)
			end
		end
	end
end

# ╔═╡ 358ac3e2-f666-11ea-2058-11eedeabff5f
mutable struct Diffusion
	array :: Array{Float64, 2}
	function Diffusion()
		diffusion = new(zeros(Float64, 11, 11))
		diffusion.array[5:7, 5:7] = ones(Float64, 3, 3)
		diffusion
	end
end

# ╔═╡ 863a3fa4-f664-11ea-2597-8f18f2a862ac
diffusion = Diffusion();

# ╔═╡ 966fc752-f666-11ea-1794-195433f4cce5
visualizearray(diffusion.array, 30)

# ╔═╡ b0148fb4-f664-11ea-2d64-976658b08661
@bind togglediffusion html"<input type=button value='Next'>"

# ╔═╡ b25969c4-f665-11ea-222b-df9786a710d2
if togglediffusion === "Next"
	diffusion.array = applydiffusion(diffusion.array)
	visualizearray(diffusion.array, 30)
end

# ╔═╡ 95dc78e8-f667-11ea-1c07-8f5cc11b011b
let
	diffusion2 = Diffusion();
	for _ in 1:1000
    	diffusion2.array = applydiffusion(diffusion2.array)
	end
	visualizearray(diffusion2.array, 30)
end

# ╔═╡ c03905f2-f667-11ea-3484-b111f7c14f60
md"""## Reaction-Diffusion

Now let’s add a second chemical."""

# ╔═╡ cf7c1a2c-f667-11ea-358f-df431ec27476
function applyreactiondiffusion(
        a::Array{Float64, 2},
        b::Array{Float64, 2},
        ra::Float64=0.5, rb::Float64=0.25,
        f::Float64=0.055, k::Float64=0.062)
    nr_y, nr_x = size(a)
    a_out = deepcopy(a)
    b_out = deepcopy(b)
    for y in 2:nr_y-1
        for x in 2:nr_x-1
            reaction = a[y, x] * b[y, x]^2
            ca = 0.25*(a[y-1, x] + a[y, x-1] + a[y, x+1] + a[y+1, x]) - a[y, x]
            cb = 0.25*(b[y-1, x] + b[y, x-1] + b[y, x+1] + b[y+1, x]) - b[y, x]
            a_out[y, x] += ra*ca - reaction + f * (1 - a[y, x])
            b_out[y, x] += rb*cb + reaction - (f+k) * b[y, x]
        end
    end
    a_out, b_out
end

# ╔═╡ 0a815d80-f668-11ea-0dd3-9155b36a4134
md"""- `ra`: The diffusion rate of A (analogous to `r` in the previous section).
- `rb`: The diffusion rate of B. In most versions of this model, `rb` is about half of `ra`.
- `f`: The “feed” rate, which controls how quickly A is added to the system.
- `k`: The “kill” rate, which controls how quickly B is removed from the system.

`ca` and `ca` are the result of applying a diffusion kernel to A and B. Multiplying by `ra` and `rb` yields the rate of diffusion into or out of each cell.

The term `a*b^2` represents the rate that A and B react with each other. Assuming that the reaction consumes A and produces B, we subtract this term in the first equation and add it in the second.

The term `f * (1-a)` determines the rate that A is added to the system. Where A is near 0, the maximum feed rate is `f`. Where A approaches 1, the feed rate drops off to zero.

Finally, the term `(f+k) * b` determines the rate that B is removed from the system. As B approaches 0, this rate goes to zero.

As long as the rate parameters are not too high, the values of A and B usually stay between 0 and 1."""

# ╔═╡ 23f98030-f668-11ea-389b-ff911adaadfd
mutable struct ReactionDiffusion
	a::Array{Float64, 2}
    b::Array{Float64, 2}
	function ReactionDiffusion()
		reactiondiffusion = new(ones(Float64, 258, 258), rand(Float64, 258, 258)*0.1)
		reactiondiffusion.b[129-12:129+12, 129-12:129+12] += ones(Float64, 25, 25)*0.1
		reactiondiffusion
	end
end

# ╔═╡ 3c706f38-f669-11ea-2266-f743535073b5
reactiondiffusion = ReactionDiffusion();

# ╔═╡ 1b551fc2-f669-11ea-0d2f-ad0e089592d8
visualizearray(reactiondiffusion.b, 2)

# ╔═╡ e1988846-f668-11ea-1b26-8f7dca1bb4e7
@bind togglereactiondiffusion html"<input type=button value='Next'>"

# ╔═╡ dda066c0-f668-11ea-3096-bdea16f40db7
# f = 0.035, 0.055, 0.039 k = 0.057, 0.062, 0.065
if togglereactiondiffusion === "Next"
	for _ in 1:500
		reactiondiffusion.a, reactiondiffusion.b = applyreactiondiffusion(reactiondiffusion.a, reactiondiffusion.b, 0.5, 0.25, 0.035, 0.057)
	end
	visualizearray(reactiondiffusion.b, 2)
end

# ╔═╡ 1734b35e-f66a-11ea-10b4-317d12008809
md"""Since 1952, observations and experiments have provided some support for Turing’s conjecture. At this point it seems likely, but not yet proven, that many animal patterns are actually formed by reaction-diffusion processes of some kind."""

# ╔═╡ 40f84b2e-f66a-11ea-1dc7-3ded9e11af06
md"""## Percolation

Percolation is a process in which a fluid flows through a semi-porous material. Examples include oil in rock formations, water in paper, and hydrogen gas in micropores. Percolation models are also used to study systems that are not literally percolation, including epidemics and networks of electrical resistors.

We’ll explore a 2-D CA that simulates percolation.

- Initially, each cell is either “porous” with probability `q` or “non-porous” with probability `1-q`.
- When the simulation begins, all cells are considered “dry” except the top row, which is “wet”.
- During each time step, if a porous cell has at least one wet neighbor, it becomes wet. Non-porous cells stay dry.
- The simulation runs until it reaches a “fixed point” where no more cells change state.

If there is a path of wet cells from the top to the bottom row, we say that the CA has a “percolating cluster”.

Two questions of interest regarding percolation are (1) the probability that a random array contains a percolating cluster, and (2) how that probability depends on `q`."""

# ╔═╡ 77dcd828-f66a-11ea-0942-c5d613d828b7
function applypercolation(array::Array{Float64, 2})
    nr_y, nr_x = size(array)
    out = deepcopy(array)
    for y in 2:nr_y-1
        for x in 2:nr_x-1
            if out[y, x] > 0.0
                c = array[y-1, x] + array[y, x-1] + array[y, x+1] + array[y+1, x]
                if c ≥ 0.5
                    out[y, x] = 0.5
                end
            end
        end
    end
    out
end

# ╔═╡ 818e5534-f66a-11ea-07b2-7722084e1e07
md"visualisation:"

# ╔═╡ 44c8b17a-f66b-11ea-39af-f554593e33eb
mutable struct Wall
	array::Array{Float64, 2}
	function Wall(n, q)
    	array = zeros(Float64, n+2, n+2)
		array[2, 2:n+1] = ones(Float64, n)*0.5
		array[3:n+1, 2:n+1] = rand(Float64, n-1, n)
		for y in 3:n+1
			for x in 2:n+1
				if array[y, x] < q
					array[y, x] = 0.1
				else
					array[y, x] = 0.0
				end
			end
		end
		new(array)
	end
end

# ╔═╡ d9ccf8f8-f66b-11ea-2c81-0d771c2e900e
wall = Wall(100, 0.62);

# ╔═╡ e492630e-f66b-11ea-3aed-f37a400243be
visualizearray(wall.array, 8)

# ╔═╡ e048231c-f66b-11ea-191d-8517a6c65bc5
@bind togglepercolation html"<input type=button value='Next'>"

# ╔═╡ 11847488-f66c-11ea-1aca-4b1613dbfb8e
if togglepercolation === "Next"
	wall.array = applypercolation(wall.array)
	visualizearray(wall.array, 8)
end

# ╔═╡ 3563b208-f66c-11ea-3c1a-77f799805d06
md"""## Phase Change

Now let’s test whether a random array contains a percolating cluster."""

# ╔═╡ 2b9c8a86-f66c-11ea-0447-1fb7ba83d3a1
function testpercolation(array::Array{Float64, 2}, vis=false)
    numberwet = count(x->x==0.5, array[3:101, 2:101])
    while true
        array = applypercolation(array)
        if count(x->x==0.5, array[101, 2:101]) > 0
			if vis
				return true, visualizearray(array, 8)
			else
            	return true
			end
        end
        newnumberwet = count(x->x==0.5, array[3:101, 2:101])
        if numberwet == newnumberwet
            if vis
				return false, visualizearray(array, 8)
			else
            	return false
			end
        end
        numberwet = newnumberwet
    end
end

# ╔═╡ c71f0f24-f66c-11ea-0ba7-d765f3998ed9
let
	wall = Wall(100, 0.5)
	result, drawing = testpercolation(wall.array, true)
	drawing
end

# ╔═╡ e51771ec-f66c-11ea-1606-fd159b8d216f
md"""To estimate the probability of a percolating cluster, we generate many random arrays and test them."""

# ╔═╡ 1254b21a-f66e-11ea-2b36-7985687edd02
function estimateprob(;n=100, q=0.5, iters=100)
    t = Bool[]
    for _ in 1:iters
        wall = Wall(n, q)
        push!(t, testpercolation(wall.array))
    end
    count(x->x, t) / iters
end

# ╔═╡ dda2cd12-f66c-11ea-3f31-837c9a749b5c
estimateprob(q = 0.50)

# ╔═╡ 2b8081ae-f66e-11ea-28dd-d160aa6c80ef
md"""We can estimate the critical value more precisely using a random walk. Starting from an initial value of `q`, we construct a wall and check whether it has a percolating cluster. If so, `q` is probably too high, so we decrease it. If not, `q` is probably too low, so we increase it."""

# ╔═╡ 3d494330-f66e-11ea-118c-7f36892d863e
function findcritical(;n=100, q=0.5, iters=100)
    qs = [q]
    for _ in 1:iters
        wall = Wall(n, q)
        if testpercolation(wall.array)
            q -= 0.004
        else
            q += 0.004
        end
        push!(qs, q)
    end
    qs
end

# ╔═╡ 501878b4-f66e-11ea-270f-d7542715acbb
findcritical()

# ╔═╡ 59540966-f66e-11ea-2cac-6fac6c62910b
md"""With `n=100` the mean of `qs` is about 0.59; this value does not seem to depend on `n`.

The rapid change in behavior near the critical value is called a phase change by analogy with phase changes in physical systems, like the way water changes from liquid to solid at its freezing point.

A wide variety of systems display a common set of behaviors and characteristics when they are at or near a critical point. These behaviors are known collectively as critical phenomena."""

# ╔═╡ 87c7816a-f66e-11ea-02fe-1752fee2c7eb
md"""## Fractals

To understand fractals, we have to start with dimensions.

For simple geometric objects, dimension is defined in terms of scaling behavior. For example, if the side of a square has length `l`, its area is `l^2`. The exponent, 2, indicates that a square is two-dimensional. Similarly, if the side of a cube has length `l`, its volume is `l^3`, which indicates that a cube is three-dimensional.

More generally, we can estimate the dimension of an object by measuring some kind of size (like area or volume) as a function of some kind of linear measure (like the length of a side).

As an example, I’ll estimate the dimension of a 1-D cellular automaton by measuring its area (total number of “on” cells) as a function of the number of rows."""

# ╔═╡ af69c480-f66e-11ea-3e4a-1db5d58bdd7b
function inttorule1dim(val::UInt8)
    digs = BitArray(digits(val, base=2))
    for i in length(digs):7
        push!(digs, false)
    end
    digs
end

# ╔═╡ d6cab994-f66e-11ea-1734-dd3ab2d22e92
function applyrule1dim(rule::BitArray{1}, bits::BitArray{1})
    val = 1 + bits[3] + 2*bits[2] + 4*bits[1]
    rule[val]
end

# ╔═╡ daa15a5a-f66e-11ea-1983-51732624404b
function step1dim(x₀::BitArray{1}, rule::BitArray{1}, steps::Int64)
    xs = [x₀]
    len = length(x₀)
    for i in 1:steps
        x = copy(x₀)
        for j in 2:len-1
            x[j] = applyrule1dim(rule, xs[end][j-1:j+1])
        end
        push!(xs, x)
    end
    xs
end

# ╔═╡ e047f8b8-f66e-11ea-05af-398e5d22ccf8
function visualize1dim(res, dim)
    width = dim * (length(res[1]) + 1)
    height = dim * (length(res) + 1)
    Drawing(width = width, height = height) do
        for (i, arr) in enumerate(res)
            for (j, val) in enumerate(arr)
                fill = if val "grey" else "lightgrey" end
                rect(x = j*dim, y = i*dim, width = dim, height = dim, fill = fill)
            end
        end
    end
end

# ╔═╡ ea25224a-f66e-11ea-1c90-b30dba111177
begin
	x₀ = falses(65)
	x₀[33] = true
	res = step1dim(x₀, inttorule1dim(UInt8(18)), 31) # 20, 50, 18
	visualize1dim(res, 10)
end

# ╔═╡ 070b57aa-f66f-11ea-1a12-bd1529268d4f
md"""I’ll estimate the dimension of these CAs with the following function, which counts the number of on cells after each time step."""

# ╔═╡ 041baf7c-f66f-11ea-270b-2179bd949a67
function countcells(rule, n=501)
    x₀ = falses(2*n+3)
    x₀[n+2] = true
    res = step1dim(x₀, inttorule1dim(UInt8(rule)), n)
    cells = [1]
    for i in 2:n
        push!(cells, cells[end]+sum(line->count(cell->cell, line), res[i]))
    end
    cells
end

# ╔═╡ 73fd7b5e-f66f-11ea-12cd-dd36181cf956
begin
	n = 501;
	rule = 20 # 20, 50, 18
	plot(1:n, 1:n, xaxis=:log, yaxis=:log, label="d = 1")
	plot!(1:n, (1:n).^2, xaxis=:log, yaxis=:log, label="d = 2")
	plot!(1:n, countcells(rule, n), xaxis=:log, yaxis=:log, label="rule $rule")
end

# ╔═╡ 81657f1a-f66f-11ea-183b-0ba3f1c3a38e
md"""Rule 20 (left) produces 3 cells every 2 time steps, so the total number of cells after $i$ steps is $y = 1.5i$. Taking the log of both sides, we have $\log y = \log 1.5 + \log i$, so on a log-log scale, we expect a line with slope 1. In fact, the estimated slope of the line is 1.01.

Rule 50 (center) produces i+1 new cells during the ith time step, so the total number of cells after $i$ steps is $y = i^2 + i$. If we ignore the second term and take the log of both sides, we have $\log y \approx 2 \log i$, so as $i$ gets large, we expect to see a line with slope 2. In fact, the estimated slope is 1.97.

Finally, for Rule 18 (right), the estimated slope is about 1.57, which is clearly not 1, 2, or any other integer. This suggests that the pattern generated by Rule 18 has a “fractional dimension”; that is, it is a fractal.

This way of estimating a fractal dimension is called box-counting."""

# ╔═╡ feefb6e4-f66f-11ea-2998-a30311bee9f4
md"""## Fractals and Percolation

Now let’s get back to percolation models.

To estimate their fractal dimension, we can run CAs with a range of sizes, count the number of wet cells in each percolating cluster, and then see how the cell counts scale as we increase the size of the array."""

# ╔═╡ 21d918ee-f670-11ea-0359-cbb1809bab9b
function percolationwet(array::Array{Float64, 2})
    numberwet = count(x->x==0.5, array[3:end-1, 2:end-1])
    while true
        array = applypercolation(array)
        if count(x->x==0.5, array[end-1, 2:end-1]) > 0
            break
        end
        newnumberwet = count(x->x==0.5, array[3:end-1, 2:end-1])
        if numberwet == newnumberwet
            break
        end
        numberwet = newnumberwet
    end
    numberwet
end

# ╔═╡ 3206914e-f670-11ea-3072-d162f584fbf0
let
	res = Float64[]
	sizes = 10:10:200
	q = 0.4 # 0.4, 0.8, 0.596
	for size in sizes
		wall = Wall(size, q)
		push!(res, percolationwet(wall.array))
	end
	plot(sizes, sizes, xaxis=:log, yaxis=:log, label="d = 1")
	plot!(sizes, (sizes).^2, xaxis=:log, yaxis=:log, label="d = 2")
	plot!(sizes, res, xaxis=:log, yaxis=:log, seriestype=:scatter, label="q = $q")
end

# ╔═╡ 4da9c3ba-f670-11ea-1627-d7c5e6b36742
md"""The dots show the number of cells in each percolating cluster. The slope of a line fitted to these dots is often near 1.85, which suggests that the percolating cluster is, in fact, fractal when `q` is near the critical value.

When `q` is larger than the critical value, nearly every porous cell gets filled, so the number of wet cells is close to `q * size^2`, which has dimension 2.

When `q` is substantially smaller than the critical value, the number of wet cells is proportional to the linear size of the array, so it has dimension 1."""

# ╔═╡ Cell order:
# ╟─e6ff0e98-f662-11ea-03a7-e3d09e6272a6
# ╟─1d6260d4-f663-11ea-03da-efe9ed63f9bd
# ╟─2abf49e0-f663-11ea-25f3-2f9229de732e
# ╠═40373f12-f663-11ea-256c-abd1458a8e85
# ╟─52f4e280-f663-11ea-38a4-c52a7b06b564
# ╠═61437b46-f663-11ea-12c6-2ff992812dca
# ╠═358ac3e2-f666-11ea-2058-11eedeabff5f
# ╠═863a3fa4-f664-11ea-2597-8f18f2a862ac
# ╠═966fc752-f666-11ea-1794-195433f4cce5
# ╠═b0148fb4-f664-11ea-2d64-976658b08661
# ╠═b25969c4-f665-11ea-222b-df9786a710d2
# ╠═95dc78e8-f667-11ea-1c07-8f5cc11b011b
# ╟─c03905f2-f667-11ea-3484-b111f7c14f60
# ╠═cf7c1a2c-f667-11ea-358f-df431ec27476
# ╟─0a815d80-f668-11ea-0dd3-9155b36a4134
# ╠═23f98030-f668-11ea-389b-ff911adaadfd
# ╠═3c706f38-f669-11ea-2266-f743535073b5
# ╠═1b551fc2-f669-11ea-0d2f-ad0e089592d8
# ╠═e1988846-f668-11ea-1b26-8f7dca1bb4e7
# ╠═dda066c0-f668-11ea-3096-bdea16f40db7
# ╟─1734b35e-f66a-11ea-10b4-317d12008809
# ╟─40f84b2e-f66a-11ea-1dc7-3ded9e11af06
# ╠═77dcd828-f66a-11ea-0942-c5d613d828b7
# ╟─818e5534-f66a-11ea-07b2-7722084e1e07
# ╠═44c8b17a-f66b-11ea-39af-f554593e33eb
# ╠═d9ccf8f8-f66b-11ea-2c81-0d771c2e900e
# ╠═e492630e-f66b-11ea-3aed-f37a400243be
# ╠═e048231c-f66b-11ea-191d-8517a6c65bc5
# ╠═11847488-f66c-11ea-1aca-4b1613dbfb8e
# ╟─3563b208-f66c-11ea-3c1a-77f799805d06
# ╠═2b9c8a86-f66c-11ea-0447-1fb7ba83d3a1
# ╠═c71f0f24-f66c-11ea-0ba7-d765f3998ed9
# ╟─e51771ec-f66c-11ea-1606-fd159b8d216f
# ╠═1254b21a-f66e-11ea-2b36-7985687edd02
# ╠═dda2cd12-f66c-11ea-3f31-837c9a749b5c
# ╟─2b8081ae-f66e-11ea-28dd-d160aa6c80ef
# ╠═3d494330-f66e-11ea-118c-7f36892d863e
# ╠═501878b4-f66e-11ea-270f-d7542715acbb
# ╟─59540966-f66e-11ea-2cac-6fac6c62910b
# ╟─87c7816a-f66e-11ea-02fe-1752fee2c7eb
# ╠═af69c480-f66e-11ea-3e4a-1db5d58bdd7b
# ╠═d6cab994-f66e-11ea-1734-dd3ab2d22e92
# ╠═daa15a5a-f66e-11ea-1983-51732624404b
# ╠═e047f8b8-f66e-11ea-05af-398e5d22ccf8
# ╠═ea25224a-f66e-11ea-1c90-b30dba111177
# ╟─070b57aa-f66f-11ea-1a12-bd1529268d4f
# ╠═041baf7c-f66f-11ea-270b-2179bd949a67
# ╠═73fd7b5e-f66f-11ea-12cd-dd36181cf956
# ╟─81657f1a-f66f-11ea-183b-0ba3f1c3a38e
# ╟─feefb6e4-f66f-11ea-2998-a30311bee9f4
# ╠═21d918ee-f670-11ea-0359-cbb1809bab9b
# ╠═3206914e-f670-11ea-3072-d162f584fbf0
# ╟─4da9c3ba-f670-11ea-1627-d7c5e6b36742


_cycle(λ0, λ1, x) = abs(λ0 - λ1) * x + min(λ0, λ1)
_tri(t, period) = (2 / π) * abs(asin(sin(π * (t - 1) / period)))
_sin(t, period) = abs(sin(π * (t - 1) / period))
_cos(t, period) = (1 + cos(π * mod(t - 1, period) / period)) / 2

@testset "Triangle" begin
    λ0 = rand()
    λ1 = rand()
    period = rand(1:10)
    s = Triangle(λ0 = λ0, λ1 = λ1, period = period)
    @test s == Triangle(λ0, λ1, period)
    @test all(_cycle(λ0, λ1, _tri(t, period)) == s(t) for t in 1:100)
    @test all(p == s(t) for (t, p) in zip(1:100, s))
    @test Base.IteratorEltype(typeof(s)) == Base.HasEltype()
    @test eltype(s) == eltype(λ0)
    @test Base.IteratorSize(typeof(s)) == Base.IsInfinite()
    @test axes(s) == (OneToInf(),)
end

@testset "TriangleDecay2" begin
    λ0 = rand()
    λ1 = rand()
    period = rand(1:10)
    s = TriangleDecay2(λ0 = λ0, λ1 = λ1, period = period)
    @test s == TriangleDecay2(λ0, λ1, period)
    @test all(_cycle(λ0, λ1, _tri(t, period) / (2^fld(t - 1, period))) == s(t) for t in 1:100)
    @test all(p == s(t) for (t, p) in zip(1:100, s))
    @test Base.IteratorEltype(typeof(s)) == Base.HasEltype()
    @test eltype(s) == eltype(λ0)
    @test Base.IteratorSize(typeof(s)) == Base.IsInfinite()
    @test axes(s) == (OneToInf(),)
end

@testset "TriangleExp" begin
    λ0 = rand()
    λ1 = rand()
    γ = rand()
    period = rand(1:10)
    s = TriangleExp(λ0 = λ0, λ1 = λ1, period = period, γ = γ)
    @test s == TriangleExp(λ0, λ1, period, γ)
    @test all(_cycle(λ0, λ1, _tri(t, period) * γ^(t - 1)) == s(t) for t in 1:100)
    @test all(p == s(t) for (t, p) in zip(1:100, s))
    @test Base.IteratorEltype(typeof(s)) == Base.HasEltype()
    @test eltype(s) == eltype(λ0)
    @test Base.IteratorSize(typeof(s)) == Base.IsInfinite()
end

@testset "Sin" begin
    λ0 = rand()
    λ1 = rand()
    period = rand(1:10)
    s = Sin(λ0 = λ0, λ1 = λ1, period = period)
    @test s == Sin(λ0, λ1, period)
    @test all(_cycle(λ0, λ1, _sin(t, period)) == s(t) for t in 1:100)
    @test all(p == s(t) for (t, p) in zip(1:100, s))
    @test Base.IteratorEltype(typeof(s)) == Base.HasEltype()
    @test eltype(s) == eltype(λ0)
    @test Base.IteratorSize(typeof(s)) == Base.IsInfinite()
    @test axes(s) == (OneToInf(),)
end

@testset "SinDecay2" begin
    λ0 = rand()
    λ1 = rand()
    period = rand(1:10)
    s = SinDecay2(λ0 = λ0, λ1 = λ1, period = period)
    @test s == SinDecay2(λ0, λ1, period)
    @test all(_cycle(λ0, λ1, _sin(t, period) / (2^fld(t - 1, period))) == s(t) for t in 1:100)
    @test all(p == s(t) for (t, p) in zip(1:100, s))
    @test Base.IteratorEltype(typeof(s)) == Base.HasEltype()
    @test eltype(s) == eltype(λ0)
    @test Base.IteratorSize(typeof(s)) == Base.IsInfinite()
    @test axes(s) == (OneToInf(),)
end

@testset "SinExp" begin
    λ0 = rand()
    λ1 = rand()
    γ = rand()
    period = rand(1:10)
    s = SinExp(λ0 = λ0, λ1 = λ1, period = period, γ = γ)
    @test s == SinExp(λ0, λ1, period, γ)
    @test all(_cycle(λ0, λ1, _sin(t, period) * γ^(t - 1)) == s(t) for t in 1:100)
    @test all(p == s(t) for (t, p) in zip(1:100, s))
    @test Base.IteratorEltype(typeof(s)) == Base.HasEltype()
    @test eltype(s) == eltype(λ0)
    @test Base.IteratorSize(typeof(s)) == Base.IsInfinite()
    @test axes(s) == (OneToInf(),)
end

@testset "Cos" begin
    λ0 = rand()
    λ1 = rand()
    period = rand(1:10)
    s = Cos(λ0 = λ0, λ1 = λ1, period = period)
    @test s == Cos(λ0, λ1, period)
    @test all(_cycle(λ0, λ1, _cos(t, period)) == s(t) for t in 1:100)
    @test all(p == s(t) for (t, p) in zip(1:100, s))
    @test Base.IteratorEltype(typeof(s)) == Base.HasEltype()
    @test eltype(s) == eltype(λ0)
    @test Base.IteratorSize(typeof(s)) == Base.IsInfinite()
    @test axes(s) == (OneToInf(),)
end


type FiniteDifferenceNewtonSafe <: Solver1D
  solverInfo::SolverInfo
end
FiniteDifferenceNewtonSafe(maxEvals::Int = 100, lowerBoundEnforced::Bool = false, upperBoundEnforced::Bool = false, lowerBound::Float64 = 0.0, upperBound::Float64 = 0.0) =
  FiniteDifferenceNewtonSafe(SolverInfo(maxEvals, lowerBoundEnforced, upperBoundEnforced, lowerBound, upperBound))

# Finite Differences Solver
function _solve(solver::FiniteDifferenceNewtonSafe, f::Function, accuracy::Float64, xMin::Float64, xMax::Float64, fxMin::Float64, fxMax::Float64,
                root::Float64, eval_num::Int)

  max_evals = solver.solverInfo.maxEvals
  # orienting search such that f(xl) < 0
  if fxMin < 0.0
    xl = xMin
    xh = xMax
  else
    xh = xMin
    xl = xMax
  end

  froot = f(root)
  eval_num += 1

  # first order finite difference derivative
  dfroot = xMax - root < root - xMin ? (fxMax - froot) / (xMax - root) : (fxMin - froot) / (xMin - root)

  dx = xMax - xMin
  while eval_num <= max_evals
    frootold = froot
    rootold = root
    dxold = dx
    # bisect if out of range or not decreasing fast enough
    if (((root - xh) * dfroot - froot) * ((root - xl) * dfroot - froot)) > 0.0 || abs(2.0 * froot) > abs(dxold * dfroot)
      dx = (xh - xl) / 2.0
      root = xl + dx
      # if root estimate computed is close to previous one, calculate dfroot at root ane xh rather than
      # root and rootold
      if is_close(root, rootold, 2500)
        rootold = xh
        frootold = f(xh)
      end
    else
      # Newton
      dx = froot / dfroot
      root -= dx
    end

    # Convergence check
    if abs(dx) < accuracy
      return root
    end

    froot = f(root)
    eval_num += 1
    dfroot = (frootold - froot) / (rootold - root)

    if (froot < 0.0)
      xl = root
    else
      xh = root
    end
  end

  error("Maximum number of function evals exceeded!")
end


using Simplices: subsample_coeffs
using Simplices: simplexintersection
using Simplices: intersectingvertices

function interiorpoints(s::SArray{D, T}, n::Int) where {D, T}
    R = rand(size(s, 2), n)
    convex_coeffs = ((1 ./ sum(R, dims = 1)) .* R)
    s * convex_coeffs
end

function interiorpoints(s, n::Int)
    R = rand(size(s, 2), n)
    convex_coeffs = ((1 ./ sum(R, dims = 1)) .* R)
    s * convex_coeffs
end

connectvertices(s) = hcat(s, hcat([s[:, i]
                    for i in [1, 3, 1, 4, 2]]...,))
splitaxes(x) = ([x[k, :] for k = 1:size(x, 1)]...,)
preparesimplex(s) = splitaxes(connectvertices(s))

"""
    getsimplex(r::Embeddings.AbstractEmbedding,
                t::DelaunayTriangulation,
                i::Int)

Get the vertices of the i-th simplex of the triangulation.
"""
getsimplex(E::Embeddings.AbstractEmbedding, DT::DelaunayTriangulation, i::Int) =
    E.points[:, DT.indices[:, i]]

getsimplex(d::Dataset, DT::DelaunayTriangulation, i::Int) =
    d.points[:, DT.indices[:, i]]

getsimplex(pts, DT::DelaunayTriangulation, i::Int) =
    pts[:, DT.indices[:, i]]

"""
    forwardmap(r::Embeddings.AbstractEmbedding,
                t::DelaunayTriangulation,
                i::Int)

Get the vertices of the i-th simplex of the triangulation projected
one step forward in time.
"""
forwardmap(r::Embeddings.AbstractEmbedding, t::DelaunayTriangulation, i::Int) =
    E.points[:, DT.indices[:, i] .+ 1]

forwardmap(r::Dataset, DT::DelaunayTriangulation, i::Int) =
    E.points[:, DT.indices[:, i] .+ 1]

forwardmap(pts, DT::DelaunayTriangulation, i::Int) =
    pts[:, DT.indices[:, i] .+ 1]

"""
Generate a total of `n_fillpoints` points lying inside the intersection
`sᵢ ∩ sⱼ` between simplices `sᵢ` and `sⱼ`.

This is done by first performing a Delaunay triangulation of the set of vertices
forming the intersecting volume. Then, points are inserted into the subsimplices
of the convex hull of the intersection.

If `sample_randomly = true`, then points are generated randomly inside each
subsimplex. If `sample_randomly = false` (default), then points are generated
as the centroids of the subsimplices resulting from a shape-preserving
refinement of the simplices in the convex hull.

## Arguments
- **``sᵢ``**: An array where each column is a simplex vertex.
- **``sⱼ``**: An array where each column is a simplex vertex.
- **``n_fillpoints``**: The total number of points to fill the convex
    hull of the intersection with. If the intersection is more complex than
    a single simplex, then the convex hull of the intersection is triangulated
    and `n_fillpoints` points are distributed evenly among the resulting
    subsimplices.
- **``sample_randomly``**: Should points be sampled randomly within the
    intersection? Default is `sample_randomly = false`, which inserts points
    inside the intersecting volume in a regular manner.
"""
function generate_fillpoints(sᵢ, sⱼ, n_fillpoints = 100, sample_randomly = true)

    intvol = simplexintersection(sᵢ, sⱼ)

    if intvol > 0 + 1e-9
        intersecting_vertices = intersectingvertices(sᵢ, sⱼ)
        if size(intersecting_vertices, 1) > 4
            triang = hcat(delaunay(intersecting_vertices)...,)
        else
            triang = zeros(Int, 4, 1)
            triang[:, 1] = 1:4
        end

        n_simplices = size(triang, 2)
        if n_simplices == 1
            npts_per_simplex = n_fillpoints
        elseif n_simplices > 1

            npts_per_simplex = ceil(Int, n_fillpoints/n_simplices)
        end
        C = transpose(subsample_coeffs(3, npts_per_simplex, sample_randomly))

        fillpts = Vector{Array{Float64, 2}}(undef, n_simplices)
        for k = 1:n_simplices
            fillpts[k] = transpose(C * intersecting_vertices[triang[:, k], :])
        end
        fillpts = hcat(fillpts...,)
        return intersecting_vertices, fillpts
    else
        return nothing
    end
end

export
interiorpoints,
connectvertices,
splitaxes,
preparesimplex,
getsimplex,
forwardmap,
generate_fillpoints


using DelayEmbeddings
using Neighborhood

"""
    predict_point!(predictions, i, source_values, u, w, dists, dim)

The prediction part of the convergent cross mapping algorithm.

## Algorithm

Consider the point in the delay embedding of `target` point with
time index `i`. Denote the time indices of its nearest
neighbors ``t_1, t_2, \\ldots, t_{dim+1}``. Denote the scalar values
of `source` at those time indices ``y_1, y_2, \\ldots, y_{dim+1}``.

Given `distances` ``d_1, d_2, \\ldots, d_{dim+1}`` from the `i`-th
point to its nearest neighbors, we compute the weights `w` from
the cross mapping algorithm ([Sugihara et al, 2012; supplementary
material, page 4](http://science.sciencemag.org/content/sci/suppl/2012/09/19/science.1227079.DC1/Sugihara.SM.pdf)). The weights `w` and coefficients `u` are stored in
pre-allocated vectors.

A prediction for the observation with time index `i` in the `source`
timeseries, call it ``\\hat{y}(i)``, is computed as the sum

```math
\\hat{y}(i) = \\sum_{j=1}^{dim+1} w_j y_j.
```

We store the prediction ``\\hat{y}(i)`` in position `i` of the pre-allocated vector
`predictions`.

## Arguments
- **`predictions`**: A pre-allocated vector in which to store the
    prediction for the scalar value of the `source` series.
- **`i`**: The time index of the point of the `source` series being
    predicted. The prediction is stored in `predictions[i]`.
- **`source_values`**: Let ``t_1, t_2, \\ldots, t_{dim + 1}`` be
    the time indices of
    the nearest neighbors to the delay embedding point with time
    index `i`. `source_values` contains the scalar values of the
    `source` series at those time indices.
- **`u`**: A pre-allocated vector of length `dim + 1` that holds the
    normalisation coefficients for computing the weights in the
    cross mapping algorithm.
- **`w`**: A pre-allocated vector of length `dim + 1` that holds
    the computed weights for the cross mapping algorithm.
- **`dists`**: The distances from delay embedding point with time index
    `i` to its `dim + 1` nearest neighbors, in order of increasing
    distances.
- **`dim`**: The dimension of the delay embedding.

## References
Sugihara, George, et al. "Detecting causality in complex ecosystems."
Science (2012): 1227079.
[http://science.sciencemag.org/content/early/2012/09/19/science.1227079](http://science.sciencemag.org/content/early/2012/09/19/science.1227079)

"""
function predict_point!(predictions, i, source_values, u, w, distances, dim)
    if distances[1] == 0
        @warn "The first distance is zero. Will yield division by zero"
    end
    for j in 1:(dim + 1)
        u[j] = exp(-(distances[j]/distances[1]))
    end
    # The `predictions` vector is pre-allocated and `predictions[i]` may
    # have a nonzero value if it has been visited in a previous iteration.
    # Reset, so that we are always starting the prediction from zero.
    predictions[i] = 0.0
    for j in 1:(dim + 1)
        #w[j] = u[j] / sum(u)
        predictions[i] =+ sum((u[j] / sum(u)) .* source_values)
    end
end

"""
    crossmap(source, target;
        dim::Int = 3,
        τ::Int = 1,
        libsize::Int = 10,
        replace::Bool = false,
        n_reps::Int = 100,
        theiler_window::Int = 0,
        tree_type = NearestNeighbors.KDTree,
        distance_metric = Distances.Euclidean(),
        correspondence_measure = StatsBase.cor,
        η::Int = 0)

## Algorithm

Compute the cross mapping between a `source` series and a `target` series.

## Arguments

- **`source`**: The data series representing the putative source process.
- **`target`**: The data series representing the putative target process.
- **`dim`**: The dimension of the state space reconstruction (delay embedding)
    constructed from the `target` series. Default is `dim = 3`.
- **`τ`**: The embedding lag for the delay embedding constructed from `target`.
    Default is `τ = 1`.
- **`η`**: The prediction lag to use when predicting scalar values of `source`
    fromthe delay embedding of `target`.
    `η > 0` are forward lags (causal; `source`'s past influences `target`'s future),
    and `η < 0` are backwards lags (non-causal; `source`'s' future influences
    `target`'s past). Adjust the prediction lag if you
    want to performed lagged ccm
    [(Ye et al., 2015)](https://www.nature.com/articles/srep14750).
    Default is `η = 0`, as in
    [Sugihara et al. (2012)](http://science.sciencemag.org/content/early/2012/09/19/science.1227079).
    *Note: The sign of the lag `η` is organized to conform with the conventions in
    [TransferEntropy.jl](), and is opposite to the convention used in the
    [`rEDM`](https://cran.r-project.org/web/packages/rEDM/index.html) package
    ([Ye et al., 2016](https://cran.r-project.org/web/packages/rEDM/index.html)).*
- **`libsize`**: Among how many delay embedding points should we sample time indices
    and look for nearest neighbours at each cross mapping realization (of which there
    are `n_reps`)?
- **`n_reps`**: The number of times we draw a library of `libsize` points from the
    delay embedding of `target` and try to predict `source` values. Equivalently,
    how many times do we cross map for this value of `libsize`?
    Default is `n_reps = 100`.
- **`replace`**: Sample delay embedding points with replacement? Default is `replace = true`.
- **`theiler_window`**: How many temporal neighbors of the delay embedding
    point `target_embedding(t)` to exclude when searching for neighbors to
    determine weights for predicting the scalar point `source(t + η)`.
    Default is `theiler_window = 0`.
- **`tree_type`**: The type of tree to build when looking for nearest neighbors.
    Must be a tree type from NearestNeighbors.jl. For now, this is either
    `BruteTree`, `KDTree` or `BallTree`.
- **`distance_metric`**: An instance of a `Metric` from Distances.jl. `BallTree` and `BruteTree` work with any `Metric`.
    `KDTree` only works with the axis aligned metrics `Euclidean`, `Chebyshev`,
    `Minkowski` and `Cityblock`. Default is `metric = Euclidean()` *(note the instantiation of the metric)*.
- **`correspondence_measure`**: The function that computes the correspondence
    between actual values of `source` and predicted values. Can be any
    function returning a similarity measure between two vectors of values.
    Default is `correspondence_measure = StatsBase.cor`, which returns values on ``[-1, 1]``.
    In this case, any negative values are usually filtered out (interpreted as zero coupling) and
    a value of ``1`` means perfect prediction.
    [Sugihara et al. (2012)](http://science.sciencemag.org/content/early/2012/09/19/science.1227079)
    also proposes to use the root mean square deviation, for which a value of ``0`` would
    be perfect prediction.

## References

Sugihara, George, et al. "Detecting causality in complex ecosystems."
Science (2012): 1227079.
[http://science.sciencemag.org/content/early/2012/09/19/science.1227079](http://science.sciencemag.org/content/early/2012/09/19/science.1227079)

Ye, Hao, et al. "Distinguishing time-delayed causal interactions using convergent cross mapping." Scientific Reports 5 (2015): 14750.
[https://www.nature.com/articles/srep14750](https://www.nature.com/articles/srep14750)

Ye, H., et al. "rEDM: Applications of empirical dynamic modeling from time series." R Package Version 0.4 7 (2016).
[https://cran.r-project.org/web/packages/rEDM/index.html](https://cran.r-project.org/web/packages/rEDM/index.html)
"""
function crossmap(source, target;
            dim::Int = 3,
            τ::Int = 1,
            η::Int = 0,
            libsize::Int = length(source) - dim*τ - abs(η),
            n_reps::Int = 100,
            replace::Bool = true,
            theiler_window::Int = 0,
            jitter::Float64 = 1e-3,
            tree_type = KDTree,
            distance_metric = Euclidean(),
            correspondence_measure = StatsBase.cor)
    points_available = length(target) - dim*τ

    validate_theiler_window!(theiler_window, points_available)
    validate_embedding_params(dim, τ, points_available, theiler_window)
    validate_libsize(libsize, source, dim, τ, η, replace)

    ######################################################################
    # Embedding and the nearest neighbor search.
    # ----------------------------------------------------------
    # All searches are done before the cross mapping repetitions,
    # because this is computationally cheaper for all but the
    # smallest library sizes and number of repetitions.
    ######################################################################
    τs = collect(1:-τ:-(τ*dim - 1))
    js = [1 for i in 1:dim]
    E = genembed(target, js, τs)

    #embedding = cembed([target], [1 for i in 1:dim], embedding_lags).points
    n_embedding_pts = length(E)

    # TODO: jitter points if many are equal. this would require using MVectors in
    # DelayEmbeddings
    #validate_embedding!(embedding, jitter)

    tree = searchstructure(KDTree, E, distance_metric)
    nearest_idxs, nearest_dists = bulksearch(tree, E.data, NeighborNumber(dim + 1), Theiler(theiler_window))

    ######################################################################
    # Cross mapping
    ######################################################################
    # Pre-allocate vectors to hold weights and coefficients, as well as
    # the predictions, for the prediction part of the algorithm.
    u = Vector{Float64}(undef, dim + 1)
    w = Vector{Float64}(undef, dim + 1)
    predictions =  zeros(Float64, libsize)

    # Pre-allocate vector to hold the time indices of the library points
    # we select at each cross map repetition (it is from these points
    # predictions are made).
    point_idxs = zeros(Int32, libsize)

    # Pre-allocate vector to hold the correspondences between actual
    # and predicted values of the source time series.
    correspondence = zeros(Float64, n_reps)

    for k in 1:n_reps
        if η >= 0
            sample!((1 + η):n_embedding_pts, point_idxs, replace = replace)
        else
            sample!(1:(n_embedding_pts - abs(η)), point_idxs, replace = replace)
        end

        # For every point selected at this repetition, make a prediction.
        @inbounds for i in 1:length(point_idxs)
            idx = point_idxs[i]
            predict_point!(predictions, i, source[nearest_idxs[idx]], u, w, nearest_dists[idx], dim)
        end

        correspondence[k] = correspondence_measure(predictions, source[point_idxs .- η])
    end

    return correspondence
end

export
predict_point!,
find_nearest!,
crossmap


# This example shows how to use your own field implementation for using them to compute on
# elliptic curves. For making things interesting we shall wrap GaloisFields implementeed binary extension field.
using Test
using CryptoGroups
import CryptoGroups: BinaryField, tobits, frombits, specialize
import GaloisFields: ExtensionField, GaloisField

struct GF₂{F <: ExtensionField} <: BinaryField
    x::F
    GF₂{F}(coeffs::NTuple{N, GaloisField(2)}) where {N, F <: ExtensionField} = new(F(coeffs))

    function GF₂{F}(x::BitVector) where F <: ExtensionField
        𝔽₂ = GaloisField(2)
        coeffs = Tuple(𝔽₂(i) for i in x) # May need a reverse
        return GF₂{F}(coeffs)
    end

    GF₂{F}(x::F) where F <: ExtensionField = new{F}(x)
    GF₂(x::F) where F <: ExtensionField = new{F}(x)
end

function specialize(::Type{GF₂}, f::BitVector)
    F, x = GaloisField(2, :x=>f)
    return GF₂{F}
end

specialize(::Type{GF₂}, poly::Vector{Int}) = specialize(GF₂, BitVector(i in poly for i in 0:maximum(poly)))

tobits(a::GF₂) = reverse(BitVector(i.n for i in a.x.coeffs))
frombits(::Type{F}, a::BitVector) where F <: GF₂ = F(reverse(a))

Base.:+(a::F, b::F) where F <: GF₂ = F(a.x + b.x)
Base.:*(a::F, b::F) where F <: GF₂ = F(a.x * b.x)

Base.inv(a::F) where F <: GF₂ = F(inv(a.x))

Base.zero(::Type{GF₂{F}}) where F <: ExtensionField = GF₂(zero(F))
Base.one(::Type{GF₂{F}}) where F <: ExtensionField = GF₂(one(F))

########################## This one we can test easally as follows ##################

import CryptoGroups: Curve_B_163_PB, Curve_K_163_PB, BinaryCurve, generator, oncurve, order, @def, AffinePoint, <|, ECPoint, validate

let
    B_163v3 = specialize(AffinePoint{BinaryCurve, GF₂}, Curve_B_163_PB)

    g = B_163v3 <| generator(Curve_B_163_PB)
    q = order(Curve_B_163_PB)

    @test oncurve(g)
    @test oncurve(g*3)
    @test g * q == zero(g)
end

@def K_163v3 ECPoint{AffinePoint{BinaryCurve, GF₂}} Curve_K_163_PB

let
    g = K_163v3 <| generator(Curve_K_163_PB)

    @test oncurve(g)
    @test oncurve(g*3)
    @test validate(g)
end


function convolve(A::SubArray{T,n}, B::Array{T,n}, dims) where {T,n}
    common_size = tuple(map(max, size(A), size(B))...)
    pA = zeros(Complex{T}, common_size)
    pB = zeros(Complex{T}, common_size)
    rangesA = [1:x for x in size(A)]
    rangesB = [1:x for x in size(B)]
    pA[rangesA...] = A
    pB[rangesB...] = B

    real(ifft!(fft!(pA, dims) .* fft!(pB, dims), dims))
end

function convolve_ComplexFloat64(A, B, dims)
    common_size = tuple(map(max, size(A), size(B))...)
    pA = zeros(Complex{Float64}, common_size)
    pB = zeros(Complex{Float64}, common_size)
    rangesA = [1:x for x in size(A)]
    rangesB = [1:x for x in size(B)]
    pA[rangesA...] = A
    pB[rangesB...] = B

    real(ifft!(fft!(pA, dims) .* fft!(pB, dims), dims))
end

# got rid of dims argument
# using real to complex fft and ifft
function convolve_Float64(A, B)
    common_size = tuple(map(max, size(A), size(B))...)
    pA = zeros(Float64, common_size)
    pB = zeros(Float64, common_size)
    rangesA = [1:x for x in size(A)]
    rangesB = [1:x for x in size(B)]
    pA[rangesA...] = A
    pB[rangesB...] = B

    irfft(rfft(pA) .* rfft(pB), common_size[1])
end

function valid_convolve(A, B)
    ranges = [min(a, b):max(a, b) for (a, b) in zip(size(A), size(B))]
    convolve_Float64(A, B)[ranges...]
end

function cumsum2(A::Array{T,ndim}) where {T,ndim}
    # cumulative sum in two dimensions
    if ndim != 2
        throw(ArgumentError("input must be two-dimensional array"))
    end
    B = similar(A)
    # first row and column are 1D cumulative sums
    B[:, 1] = cumsum(A[:, 1], 1)
    B[1, :] = cumsum(A[1, :], 2)      # B[1,1] is redundantly computed twice
    # compute rest of matrix from recursion
    (m, n) = size(A)
    if m > 1 && n > 1
        for j = 2:n, i = 2:m
            B[i, j] = A[i, j] + B[i-1, j] + B[i, j-1] - B[i-1, j-1]
        end
    end
    B
end

function optimize_normxcorr2(img)
    p = plan_rfft(img, flags = FFTW.MEASURE)
    q = plan_irfft(rfft(img), flags = FFTW.MEASURE, size(img, 1))
    return Void
end

# extension:
# Params_session.jl: optimize_all_cores(params::Params)
function optimize_all_cores(img_d::Int64)
    img = rand(img_d, img_d)
    @sync begin
        for p = 1:num_procs
            @async begin
                remotecall_fetch(p, optimize_normxcorr2, img)
            end
        end
    end
    return Void
end

"""
`NORMXCORR2` - normalized cross correlation
slide template across img, compute Pearson correlation coefficient for each
template location result has same size as MATLAB-style 'valid' convolution
efficient algorithm of J. P. Lewis
http://scribblethink.org/Work/nvisionInterface/nip.html

` correlogram = normxcorr2(template,img)`

return NaN for image patch or template with zero variance
Pearson correlation coefficient is undefined in this case

need some argument checking
e.g. sizes of template should be less than those of img
this works for arrays.  extend to Image defined in Holy's package?
"""
function normxcorr2(template, img)
    # sufficient to subtract mean from just one variable
    dt = template - mean(template)
    templatevariance = sum(dt.^2)
    numerator = valid_convolve(img, dt[end:-1:1, end:-1:1])

    ##### local statistics of img
    # zero pad image in first row and column
    # so that cumulative sums will have zeros in the same place
    (m1, m2) = size(img)
    imgpad = zeros(m1 + 1, m2 + 1)
    imgpad[2:end, 2:end] = img
    # define four combinations of Small and Large ranges
    (n1, n2) = size(template)
    if templatevariance == 0
        return zeros(m1 - n1 + 1, m2 - n2 + 1) * NaN
    end
    LL = Any[1+(n1:m1), 1+(n2:m2)]
    SL = LL - [n1; 0]
    LS = LL - [0; n2]
    SS = LL - [n1; n2]
    # sum of img and its square in template-sized neighborhoods
    s = cumsum2(imgpad)
    localsum = s[LL...] - s[SL...] - s[LS...] + s[SS...]
    s2 = cumsum2(imgpad.^2)
    localsum2 = s2[LL...] - s2[SL...] - s2[LS...] + s2[SS...]
    localvariance = localsum2 - localsum.^2 / prod(size(template))
    # localvariance is zero for image patches that are constant
    # leading to undefined Pearson correlation coefficient
    # should only be negative due to roundoff error
    localvariance[localvariance.<=0] *= NaN
    denominator = sqrt(localvariance * templatevariance)

    numerator ./ denominator
end


function randcoords(nx, ny)
  K = zeros(Float32, 2, nx, ny)
  for j in 1:ny, i in 1:nx
    K[1,i,j] = rand() - 0.5
    K[2,i,j] = rand() - 0.5
  end
  K
end

function radialcoords(nr, narms; golden=false, offset=0)
  K = zeros(Float32, 2, nr, narms)
  for j in 1:narms
    θ = (j - 1 + offset) * π / (golden ? φ : narms)
    for i in 1:nr
      r = (i-1) / nr - 0.5
      K[1,i,j] = r*cos(θ)
      K[2,i,j] = r*sin(θ)
    end
  end
  K
end
function radialcoords(nr, pct::Float32=100.0; kwargs...)
  # can call with a radial percentage as well
  narms = iround(nr*pct / 100.0)
  radialcoords(nr, narms; kwargs...)
end

function radialcoords(nr, nprof, nleaves; pct=100.0, golden=false,
                      fid=false, roalt=false)
  nprof = iround(nprof*sqrt(pct/100.0))
  nleaves = iround(nleaves*sqrt(pct/100.0))
  K = zeros(Float32, 3, nr, nprof, nleaves)
  if golden
    α = 0.4656
    β = 0.6823
    ϕ = 0.0
    μ = 0.0
    for k in 1:nleaves, j in 1:nprof
      st = sqrt(1.0 - μ*μ)
      for i in 1:nr
        r = (i-1) / nr - 0.5
        K[1,i,j,k] = r*cos(ϕ)*st
        K[2,i,j,k] = r*sin(ϕ)*st
        K[3,i,j,k] = r*μ
      end
      ϕ = mod(ϕ + 2π*β, 2π)
      μ += 2α
      μ = μ > 1.0 ? μ - 2.0 : μ
    end
  else
    for j in 1:nprof
      if fid
        z = (2j - 1) / float(nprof)  - 1.0 # not sure about this
	z = j % 2 ? z : -z
      else
        z = (j - 0.5) / float(nprof) - 1.0
      end
      for k in 1:nleaves
        ϕ = sqrt(nprof * π / nleaves) * asin(z) + (k-1) * 2π / nleaves
	#from mmiffe_mxg.c phi = sqrt( `MP_radial_nr_angles * PI / `MP_radial_3d_koosh_interleaves ) * asin( z ) + interleaf * 2.0 * PI / `MP_radial_3d_koosh_interleaves;
        st = sqrt(1.0 - z*z)
        for i in 1:nr
          r = (i-1) / nr - 0.5
          K[1,i,j,k] = r*cos(ϕ)*st
          K[2,i,j,k] = r*sin(ϕ)*st
          K[3,i,j,k] = r*z
        end
      end
    end
  end
  reshape(K, 3, nr, nprof*nleaves)
end

function radialsdc(nr, nt; golden::Bool=false, offset::Int=0)
  # First guess radial sample density compensation
  # Uses Ram-Lak.  If golden angle, then the Ram-Lak
  # ramp is adjusted by the average neighbor distance.
  # sort angles in increasing order
  θ = (π / φ)*[offset:nt-1+offset] % (2π)
  Δθavg = 4π / nt
  θhat = sort(θ)
  isort = sortperm(θ)
  # find angular distance to neighbors
  Δθ = zeros(nt)
  Δθ[1] = θhat[2] - θhat[end] + 2π
  for t in 2:nt-1
    Δθ[t] = θhat[t+1] - θhat[t-1]
  end
  Δθ[end] = θhat[1] - θhat[end-1] + 2π
  weights = similar(Δθ)
  weights[isort] = Δθ / Δθavg
  r = linspace(1.0, 1.0/nr, int(nr/2))
  ramp = vcat(r, r[end:-1:1])
  if golden
    sdc = broadcast(*, ramp, weights')
  else
    sdc = repmat(ramp, 1, nt)
  end
  float32(sdc)
end


# Crying baby problem described in DMU book
# State: hungry = true; not hungry = false
# Action: feed = true; do nothing = false
# Observation: crying = true; not crying = false

mutable struct BabyPOMDP <: POMDP{Bool, Bool, Bool}
    r_feed::Float64
    r_hungry::Float64
    p_become_hungry::Float64
    p_cry_when_hungry::Float64
    p_cry_when_not_hungry::Float64
    discount::Float64
end
BabyPOMDP(r_feed, r_hungry) = BabyPOMDP(r_feed, r_hungry, 0.1, 0.8, 0.1, 0.9)
BabyPOMDP() = BabyPOMDP(-5., -10.)

updater(problem::BabyPOMDP) = DiscreteUpdater(problem)

actions(::BabyPOMDP) = (true, false)
actionindex(::BabyPOMDP, a::Bool) = a + 1
states(::BabyPOMDP) = (true, false)
stateindex(::BabyPOMDP, s::Bool) = s + 1
observations(::BabyPOMDP) = (true, false)
obsindex(::BabyPOMDP, o::Bool) = o + 1

# start knowing baby is not not hungry
initialstate(::BabyPOMDP) = BoolDistribution(0.0)
initialobs(m::BabyPOMDP, s) = observation(m, s)

function transition(pomdp::BabyPOMDP, s::Bool, a::Bool)
    if a # fed
        return BoolDistribution(0.0)
    elseif s # did not feed when hungry
        return BoolDistribution(1.0)
    else # did not feed when not hungry
        return BoolDistribution(pomdp.p_become_hungry)
    end
end

function observation(pomdp::BabyPOMDP, sp::Bool)
    if sp # hungry
        return BoolDistribution(pomdp.p_cry_when_hungry)
    else
        return BoolDistribution(pomdp.p_cry_when_not_hungry)
    end
end

function reward(pomdp::BabyPOMDP, s::Bool, a::Bool)
    r = 0.0
    if s # hungry
        r += pomdp.r_hungry
    end
    if a # feed
        r += pomdp.r_feed
    end
    return r
end

discount(p::BabyPOMDP) = p.discount

# some example policies
struct Starve <: Policy end
action(::Starve, ::B) where {B} = false
updater(::Starve) = NothingUpdater()

struct AlwaysFeed <: Policy end
action(::AlwaysFeed, ::B) where {B} = true
updater(::AlwaysFeed) = NothingUpdater()

# feed when the previous observation was crying - this is nearly optimal
struct FeedWhenCrying <: Policy end
updater(::FeedWhenCrying) = PreviousObservationUpdater()
function action(::FeedWhenCrying, b::Union{Nothing, Bool})
    if b == nothing || b == false # not crying (or null)
        return false
    else # is crying
        return true
    end
end
action(::FeedWhenCrying, b::Bool) = b
action(p::FeedWhenCrying, b::Missing) = false
# assume the second argument is a distribution
action(::FeedWhenCrying, d::Any) = pdf(d, true) > 0.5

# deprecated in POMDPs 0.9
POMDPs.initialstate_distribution(::BabyPOMDP) = BoolDistribution(0.0)


using ITensors

# Define indices
a = Index(2,"a")
b = Index(2,"b")
c = Index(2,"c")

# Define 3 order 2 tensors
Z = ITensor(a,b)
X = ITensor(b,c)
Y = ITensor(b,c)

# Set some elements
Z[a(1), b(1)] = 1.0
Z[a(2), b(2)] = -1.0

X[b(1), c(2)] = 1.0
X[b(2), c(1)] = 1.0

Y[b(1), c(1)] = 1.0
Y[b(2), c(2)] = 1.0

# Operations with tensors
R = Z * X

S = Y + X

T = Y - X

# Print results
println("Z =\n", Z, "\n")
println("X =\n", X, "\n")
println("Y =\n", Y, "\n")
println("R = Z * X =\n", R, "\n")
println("S = Y + X =\n", S, "\n")
println("S = Y - X =\n", T, "\n")

# Check that incompatible tensors cause an error
try
    U = Z + X
catch
    println("Cannot add Z and X")
end

## Compare calculations to Julia arrays
jZ = [1.0 0.0;
      0.0 -1.0]
jX = [0.0 1.0;
      1.0 0.0]
jY = [1.0 0.0;
      0.0 1.0]
@assert Array(R) == jZ * jX
@assert Array(S) == jY + jX
@assert Array(T) == jY - jX


###### n = 2 ##########
puzz21 = [
    3 0 4 0
    0 1 0 2
    0 4 0 3
    2 0 1 0
];

puzz22 = [
    0 1 3 0
    0 0 0 0
    0 0 0 0
    3 4 2 1
];

puzz23 = [
    0 3 0 1
    0 0 2 0
    0 0 3 0
    0 4 0 2
];

###### n = 3 ###########
puzz1 = [
    9 3 0 0 0 0 0 4 0
    0 0 0 0 4 2 0 9 0
    8 0 0 1 9 6 7 0 0
    0 0 0 4 7 0 0 0 0
    0 2 0 0 0 0 0 6 0
    0 0 0 0 2 3 0 0 0
    0 0 8 5 3 1 0 0 2
    0 9 0 2 8 0 0 0 0
    0 7 0 0 0 0 0 5 3
];

puzz2 = [
    0 0 9 0 1 0 0 6 0
    0 0 0 0 6 7 0 0 3
    0 3 5 0 0 0 0 0 0
    3 0 0 0 0 2 0 0 0
    5 1 7 0 0 0 2 3 4
    0 0 0 3 0 0 0 0 7
    0 0 0 0 0 0 9 1 0
    1 0 0 6 8 0 0 0 0
    0 7 0 0 3 0 5 0 0
];

puzz3 = [
    0 3 7 8 6 0 0 4 0
    0 0 6 0 0 7 0 0 0
    0 2 0 0 3 0 0 0 6
    0 8 0 2 0 0 0 0 0
    9 0 0 0 0 0 0 0 1
    0 0 0 0 0 6 0 9 0
    5 0 0 0 7 0 0 3 0
    0 0 0 3 0 0 8 0 0
    0 1 0 0 8 4 2 6 0
];

#### n = 4 #####
puzz41 = [
    0 16 0 0 0 0 0 0 0 0 0 0 0 0 0 1
    10 0 0 14 2 0 5 7 15 0 0 11 0 0 13 9
    0 0 0 11 0 0 0 0 13 0 0 12 0 0 0 0
    0 0 3 0 0 10 14 0 0 0 0 0 0 0 5 0
    0 0 0 7 0 5 0 15 0 6 13 10 14 0 0 3
    9 11 0 0 3 0 12 1 0 14 0 16 7 6 0 8
    4 0 0 0 0 6 0 0 12 0 1 0 0 0 0 15
    0 0 12 0 0 0 0 11 0 0 0 0 0 0 0 16
    3 0 0 9 1 0 11 0 0 0 0 0 16 10 0 0
    0 0 0 0 7 4 0 9 0 2 0 5 0 0 0 11
    0 1 0 0 0 16 2 0 0 0 8 0 15 0 7 0
    0 0 0 0 0 0 0 8 3 0 9 0 4 0 14 0
    0 0 0 0 15 0 0 0 2 0 0 13 0 0 1 0
    1 0 0 5 0 8 0 0 0 10 0 14 11 0 0 12
    16 15 0 8 0 0 0 14 0 1 12 4 10 0 3 0
    0 0 11 0 5 0 0 0 0 0 0 15 0 8 9 2
];


import LazySets.Approximations.overapproximate

"""
    overapproximate(X::LazySet{N}, dirs::Vector{Vector{N}}) where {N}

 Compute the overapproximation of the lazy set `X` by evaluating the support values in the direction of `dirs` and adding the resulting constraints.

 ### Inputs
 - `X` -- lazy set
 - `dir` -- vector of direction vectors

 ### Output
 Over-approximation of the lazy set `X` in the directions of `dir`
"""
function LazySets.Approximations.overapproximate(X::LazySet{N}, dirs::Vector{Vector{N}}) where {N}
    halfspaces = Vector{LinearConstraint{N}}()
    sizehint!(halfspaces, length(dirs))
    H = HPolytope(halfspaces)
    for d in dirs
        addconstraint!(H, LinearConstraint(d, ρ(d, X)))
    end
    return H
end

"""
     refine(P::LazySet; eps=1e-5, ndirs=200, nsamples=2000)

 Compute an concrete overapproximation of the lazy set `P`.
 The accuracy depends on the hyperparameter:
 2D-set: eps       => ϵ-close approximation
 3D-set: n_dirs    => #directions for evaluation the support value
 nD-set: n_samples => #directions for evaluation the support value

 ### Inputs
 - `P` -- lazy set
 - `eps`-- (optional, default=1e-5) define error tolarance of ϵ-close overapproximation of 2D set
 - `n_dirs`-- (optional, default=200) number of direction vectors evaluated for 3D set
 - `n_samples`-- (optional, default=2000) number of direction vectors evaluated for nD set with n>3

 ### Output
 Overapproximation of lazy set `P`.
"""
function refine(P::LazySet; eps=1e-5, n_dirs=200, n_samples=2000)
    n = LazySets.dim(P)
    if n == 2
        return refine_2D(P, eps)
    elseif n == 3
        return refine_3D(P, n_dirs)
    else
        P = refine_nD(P, n_samples)
        # NOTE: Not sure about remove_redundant_constraints! functionalities!
        remove_redundant_constraints!(P)
        return P
    end
end

function refine_nD(X, nsamples)
    n = LazySets.dim(X)
    dirs = Vector{Vector{Float64}}(undef, nsamples)
    LazySets._sample_unit_nsphere_muller!(dirs, n, nsamples)
    return overapproximate(X, dirs)
end

function refine_3D(X, n_dirs)
    dirs = Approximations.SphericalDirections(n_dirs, n_dirs)
    return overapproximate(X, dirs)
end

function refine_2D(X, err_approx)
    return overapproximate(X, err_approx)
end