text:
"""
    linspace(start, stop, n)

Python linspace function
"""
linspace(start, stop, n::T) where {T<:Integer} =
    collect(range(start, stop = stop, length = n))

"""
    heaviside(x)

Heaviside step function
"""
heaviside(x::T) where {T<:Real} = ifelse(x >= 0, one(x), zero(x))

"""
    fortsign(x, y)

Fortran sign function
"""
fortsign(x, y) = abs(x) * sign(y)

"""
    mat_split(m)

Split matrix into row vectors
"""
function mat_split(m::AbstractMatrix{T}) where {T<:Number}

    if length(m[1, :]) == 2
        nx = eltype(m).([1.0 0.0])
        ny = eltype(m).([0.0 1.0])

        return nx * m, ny * m
    elseif length(m[1, :]) == 3
        nx = eltype(m).([1.0 0.0 0.0])
        ny = eltype(m).([0.0 1.0 0.0])
        nz = eltype(m).([0.0 0.0 1.0])

        return nx * m, ny * m, nz * m
    end

end

"""
    central_diff(y, x)
    central_diff(y, dx)

Central difference
"""
function central_diff(y::T, x::T) where {T<:AbstractVector{<:Number}}

    dy = zeros(eltype(y), axes(y))

    idx = eachindex(y) |> collect
    i0 = idx[1]
    i1 = idx[end]

    dy[i0] = (y[i0+1] - y[i0]) / (x[i0+1] - x[i0] + 1e-7)
    dy[i1] = (y[i1] - y[i1-1]) / (x[i1] - x[i1-1] + 1e-7)
    for i = i0+1:i1-1
        dy[i] = (y[i+1] - y[i-1]) / (x[i+1] - x[i-1] + 1e-7)
    end

    return dy

end

function central_diff(y::AbstractVector{T}, dx) where T
    x = ones(eltype(y), axes(y)) .* dx
    dy = central_diff(y, x)

    return dy
end

"""
    central_diff2(y, x)
    central_diff2(y, dx)

Central difference for second-order derivatives
"""
function central_diff2(y::T, x::T) where {T<:AbstractVector{<:Number}}
    dy = zeros(eltype(y), axes(y))

    i0 = eachindex(y) |> first
    i1 = eachindex(y) |> last

    for i = i0+1:i1-1
        dy[i] = (y[i+1] - 2.0 * y[i] + y[i-1]) / (x[i+1] - x[i-1])^2
    end

    return dy
end

function central_diff2(y::AbstractVector{T}, dx) where T
    x = ones(eltype(y), axes(y)) .* dx
    dy = central_diff2(y, x)

    return dy
end

"""
    central_diff!(dy, y, x)
    central_diff!(dy, y, dx)

Central difference
"""
function central_diff!(dy::AbstractVector{T1}, y::T2, x::T2) where {T1,T2<:AbstractVector{<:Number}}

    @assert axes(dy) == axes(y) == axes(x)

    idx = eachindex(y) |> collect
    i0 = idx[1]
    i1 = idx[end]

    dy[i0] = (y[i0+1] - y[i0]) / (x[i0+1] - x[i0] + 1e-7)
    dy[i1] = (y[i1] - y[i1-1]) / (x[i1] - x[i1-1] + 1e-7)
    for i = i0+1:i1-1
        dy[i] = (y[i+1] - y[i-1]) / (x[i+1] - x[i-1] + 1e-7)
    end

end

function central_diff!(dy::AbstractVector{T1}, y::AbstractVector{T2}, dx) where {T1,T2}
    x = ones(eltype(y), axes(y)) .* dx
    central_diff!(dy, y, x)
end

"""
    central_diff2!(dy, y, x)
    central_diff2!(dy, y, dx)

Central difference
"""
function central_diff2!(dy::AbstractVector{T1}, y::T2, x::T2) where {T1,T2<:AbstractVector{<:Number}}

    @assert axes(dy) == axes(y) == axes(x)

    i0 = eachindex(y) |> first
    i1 = eachindex(y) |> last

    dy[i0] = 0.0
    dy[i1] = 0.0
    for i = i0+1:i1-1
        dy[i] = (y[i+1] - 2.0 * y[i] + y[i-1]) / (x[i+1] - x[i-1])^2
    end

end

function central_diff2!(dy::AbstractVector{T1}, y::AbstractVector{T2}, dx) where {T1,T2}
    x = ones(eltype(y), axes(y)) .* dx
    central_diff2!(dy, y, x)
end

"""
    upwind_diff(y, x; stream)
    upwind_diff(y, dx; stream)

Upwind difference
"""
function upwind_diff(
    y::AbstractVector{T1},
    x::AbstractVector{T2};
    stream = :right::Symbol,
) where {T1,T2}

    dy = zeros(eltype(y), axes(y))

    idx = eachindex(y) |> collect
    i0 = idx[1]
    i1 = idx[end]

    if stream == :right
        dy[i0] = 0.0
        for i = i0+1:i1
            dy[i] = (y[i] - y[i-1]) / (x[i] - x[i-1] + 1e-7)
        end
    elseif stream == :left
        dy[i1] = 0.0
        for i = i0:i1-1
            dy[i] = (y[i+1] - y[i]) / (x[i+1] - x[i] + 1e-7)
        end
    else
        throw("streaming direction should be :left or :right")
    end

    return dy

end

function upwind_diff(y::AbstractVector{T}, dx; stream = :right::Symbol) where T
    x = ones(eltype(y), axes(y)) .* dx
    dy = upwind_diff(y, x, stream = stream)

    return dy
end

"""
    upwind_diff!(dy, y, x; stream)
    upwind_diff!(dy, y, dx; stream)

Upwind difference
"""
function upwind_diff!(
    dy::AbstractVector{T1},
    y::AbstractVector{T2},
    x::AbstractVector{T3};
    stream = :right::Symbol,
) where {T1,T2,T3}

    @assert axes(dy) == axes(y) == axes(x)

    idx = eachindex(y) |> collect
    i0 = idx[1]
    i1 = idx[end]

    if stream == :right
        dy[i0] = 0.0
        for i = i0+1:i1
            dy[i] = (y[i] - y[i-1]) / (x[i] - x[i-1] + 1e-7)
        end
    elseif stream == :left
        dy[i1] = 0.0
        for i = i0:i1-1
            dy[i] = (y[i+1] - y[i]) / (x[i+1] - x[i] + 1e-7)
        end
    else
        throw("streaming direction should be :left or :right")
    end

    return dy

end

function upwind_diff!(
    dy::AbstractVector{T1},
    y::AbstractVector{T2},
    dx;
    stream = :right::Symbol,
) where {T1,T2}

    x = ones(eltype(y), axes(y)) .* dx
    upwind_diff!(dy, y, x, stream = stream)

end

"""
    unstruct_diff(u, x, nx; mode)
    unstruct_diff(u, x, nx, dim; mode)

Finite difference for pseudo-unstructured mesh
"""
function unstruct_diff(
    u::AbstractVector{T1},
    x::AbstractVector{T2},
    nx::Integer;
    mode = :central::Symbol,
) where {T1,T2}

    uu = reshape(u, (nx, :))
    xx = reshape(x, (nx, :))

    dux = similar(xx)
    for i = 1:nx
        if mode == :central
            dux[i, :] .= central_diff(uu[i, :], xx[i, :])
        elseif mode == :upwind
            dux[i, :] .= upwind_diff(uu[i, :], xx[i, :])
        else
            throw("difference mode should be central or upwind")
        end
    end

    return reshape(dux, (1, :))

end

function unstruct_diff(
    u::Function,
    x::AbstractVector{T},
    nx::Integer,
    dim::Integer;
    mode = :central::Symbol,
) where T

    uu = reshape(u.(x), (nx, :))
    xx = reshape(x, (nx, :))
    dux = zeros(eltype(x), axes(xx))

    if dim == 1
        for j in axes(xx, 2)
            if mode == :central
                dux[:, j] .= central_diff(uu[:, j], xx[:, j])
            elseif mode == :upwind
                dux[:, j] .= upwind_diff(uu[:, j], xx[:, j])
            end
        end
    elseif dim == 2
        for i in axes(xx, 1)
            if mode == :central
                dux[i, :] .= central_diff(uu[i, :], xx[i, :])
            elseif mode == :upwind
                dux[i, :] .= upwind_diff(uu[i, :], xx[i, :])
            end
        end
    end

    return dux[:]

end

"""
    lgwt(, a, b)

Gauss Legendre integral for fast spectral method

* @args: number of quadrature points N, integral range [a, b]
* @args: quadrature points x & weights w

"""
function lgwt(N::Integer, a::Real, b::Real)
    x = zeros(N)
    w = zeros(N)

    N1 = N
    N2 = N + 1

    y = zeros(N1)
    y0 = zeros(N1)
    Lp = zeros(N1)
    L = zeros(N1, N2)

    # initial guess
    for i = 1:N1
        y[i] =
            cos((2.0 * (i - 1.0) + 1.0) * 4.0 * atan(1.0) / (2.0 * (N - 1.0) + 2.0)) +
            0.27 / N1 *
            sin(4.0 * atan(1.0) * (-1.0 + i * 2.0 / (N1 - 1.0)) * (N - 1.0) / N2)
        y0[i] = 2.0
    end

    # compute the zeros of the N+1 legendre Polynomial
    # using the recursion relation and the Newton method
    while maximum(abs.(y .- y0)) > 0.0000000000001
        L[:, 1] .= 1.0
        L[:, 2] .= y
        for k = 2:N1
            @. L[:, k+1] = ((2.0 * k - 1.0) * y * L[:, k] - (k - 1) * L[:, k-1]) / k
        end
        @. Lp = N2 * (L[:, N1] - y * L[:, N2]) / (1.0 - y^2)
        @. y0 = y
        @. y = y0 - L[:, N2] / Lp
    end

    # linear map from [-1 1] to [a,b]
    @. x = (a * (1.0 - y) + b * (1.0 + y)) / 2.0
    @. w = N2^2 * (b - a) / ((1.0 - y^2) * Lp^2) / N1^2

    return x, w
end

"""
    extract_last(a, idx; mode)

Extract subarray except the last column
"""
function extract_last(a::AbstractArray{T}, idx::Integer; mode = :view::Symbol) where T
    if mode == :copy

        if ndims(a) == 2
            sw = a[:, idx]
        elseif ndims(a) == 3
            sw = a[:, :, idx]
        elseif ndims(a) == 4
            sw = a[:, :, :, idx]
        elseif ndims(a) == 5
            sw = a[:, :, :, :, idx]
        end

    elseif mode == :view

        if ndims(a) == 2
            sw = @view a[:, idx]
        elseif ndims(a) == 3
            sw = @view a[:, :, idx]
        elseif ndims(a) == 4
            sw = @view a[:, :, :, idx]
        elseif ndims(a) == 5
            sw = @view a[:, :, :, :, idx]
        end

    else

        throw("Error in extraction mode setup")

    end

    return sw
end


text:
module FloatingDebris

using ..Ahorn, Maple
using Random

const placements = Ahorn.PlacementDict(
    "Floating Debris" => Ahorn.EntityPlacement(
        Maple.FloatingDebris
    )
)

function Ahorn.selection(entity::Maple.FloatingDebris)
    x, y = Ahorn.position(entity)

    return Ahorn.Rectangle(x - 6, y - 6, 12, 12)
end

debrisName = "scenery/debris"

function Ahorn.render(ctx::Ahorn.Cairo.CairoContext, entity::Maple.FloatingDebris, room::Maple.Room)
    rng = Ahorn.getSimpleEntityRng(entity)
    debrisSprite = Ahorn.getSprite(debrisName, "Gameplay")

    offset = rand(rng, 0:7)

    Ahorn.drawImage(ctx, debrisSprite, -4, -4, offset * 8, 0, 8, 8)
end

end

ext: jl


text:
#####
##### Counter gradient flux model proposed by Large et al (1994)
#####

Base.@kwdef struct LMDCounterGradientFlux{T} <: AbstractParameters
    CNL :: T = 6.33 # Mass flux proportionality constant
end

const CGModel = Model{K, <:LMDCounterGradientFlux} where K

update_nonlocal_flux!(model::CGModel) = nothing

instantiate_plume(::LMDCounterGradientFlux, grid) = (T=nothing, S=nothing, W²=nothing)

∂z_explicit_nonlocal_flux_T(m::Model{K, <:LMDCounterGradientFlux}, i) where K =
    KPP.∂NL∂z(m.nonlocalflux.CNL, m.state.Qθ, d(m, i+1), d(m, i), Δf(m.grid, i), m)

∂z_explicit_nonlocal_flux_S(m::Model{K, <:LMDCounterGradientFlux}, i) where K =
    KPP.∂NL∂z(m.nonlocalflux.CNL, m.state.Qs, d(m, i+1), d(m, i), Δf(m.grid, i), m)

#####
##### Diagnostic plume model
#####

abstract type AbstractDiagnosticPlumeModel <: AbstractParameters end

Base.@kwdef struct SiebesmaDiagnosticPlumeModel{T} <: AbstractDiagnosticPlumeModel
     Ca :: T = 0.1
    Cbw :: T = 2.86
     Ce :: T = 0.4
    Cew :: T = 0.572
     Cα :: T = 1.0
    Cστ :: T = 2.2
    Cσb :: T = 1.32
end

const DiagnosticPlumeModel = SiebesmaDiagnosticPlumeModel

instantiate_plume(::AbstractDiagnosticPlumeModel, grid) =
    (T=CellField(grid), S=CellField(grid), W²=FaceField(grid))

#####
##### Empirical standard deviation
#####

@inline function w_standard_dev(w★, u★, Cστ, Cσb, d::T) where T
    if 0 < d < 1
        return (Cστ * u★^3 + Cσb * w★^3 * d)^(1/3) * (1 - d)^(1/2)
    else
        return zero(T)
    end
end

@inline w_standard_dev(m, i) = @inbounds w_standard_dev(w★(m), u★(m), m.nonlocalflux.Cστ,
                                                        m.nonlocalflux.Cσb, -m.grid.zc[i] / m.state.h)

#####
##### Entrainment
#####

@inline function siebesma_entrainment(z, Δz, Ce, h::T) where T
    ϵ = -Ce * (1 / (Δz - z) + 1 / (Δz + z + h))
    return ifelse(ϵ < 0, ϵ, -Ce / Δz)
end

@inline entrainment(z, model::Model{K, <:SiebesmaDiagnosticPlumeModel}) where K =
    @inbounds siebesma_entrainment(z, Δc(model.grid, model.grid.N), model.nonlocalflux.Ce, model.state.h)

#####
##### Plume boundary conditions
#####

function set_tracer_plume_bc!(ϕ̆, Φ, Qϕ, Cα, model)
    n = ϕ̆.grid.N

    # Surface layer model: √w² Δϕ̆ = - C Qϕ, where Δϕ̆ is plume excess.
    @inbounds ϕ̆[n] = Φ[n] - Cα * Qϕ / w_standard_dev(model, n)

    return nothing
end

function set_vertical_momentum_plume_bc!(W)
    @inbounds W[W.grid.N+1] = 0
    return nothing
end

#####
##### Plume equations
#####

#####
##### Buoyancy
#####

@inline plume_buoyancy_excess(i, grid, T̆, S̆, T, S, α, β, g) =
    @inbounds g * (α*(T̆[i] - T[i]) - β*(S̆[i] - S[i]))

@inline plume_buoyancy_excess(i, grid, model) =
    plume_buoyancy_excess(i, grid, model.state.plume.T, model.state.plume.S,
                                   model.solution.T, model.solution.S,
                                   model.constants.α, model.constants.β, model.constants.g)

function clip_infinite!(ϕ)
    for i in eachindex(ϕ)
        @inbounds begin
            ϕᵢ = ϕ[i]
            if !(isfinite(ϕᵢ))
                 ϕ[i] = 0
            end
        end
    end
    return nothing
end

function clip_positive!(ϕ)
    for i in eachindex(ϕ)
        @inbounds begin
            ϕᵢ = ϕ[i]
            if ϕᵢ > 0
                ϕ[i] = 0
            end
        end
    end
    return nothing
end

function clip_negative!(ϕ)
    for i in eachindex(ϕ)
        @inbounds begin
            ϕᵢ = ϕ[i]
            if ϕᵢ < 0
                ϕ[i] = 0
            end
        end
    end
    return nothing
end

function update_nonlocal_flux!(model::Model{K, <:AbstractDiagnosticPlumeModel}) where K

    set_tracer_plume_bc!(model.state.plume.T, model.solution.T,
                         model.state.Qθ, model.nonlocalflux.Cα, model)

    set_tracer_plume_bc!(model.state.plume.S, model.solution.S,
                         model.state.Qs, model.nonlocalflux.Cα, model)

    set_vertical_momentum_plume_bc!(model.state.plume.W²)

    integrate_plume_equations!(model.state.plume.T, model.state.plume.S, model.state.plume.W²,
                               model.solution.T, model.solution.S, model.grid, model)

    clip_infinite!(model.state.plume.W²)
    clip_negative!(model.state.plume.W²)
    clip_infinite!(model.state.plume.T)
    clip_infinite!(model.state.plume.S)

    return nothing
end

function integrate_plume_equations!(T̆, S̆, W̆², T, S, grid, model)

    n = grid.N

    # Vertical momentum at the nᵗʰ cell interface, approximating excess buoyancy at
    # interface with excess at top cell center:
    ΔB̆ᵢ₊₁ = plume_buoyancy_excess(n, grid, model)
    @inbounds W̆²[n] = -model.nonlocalflux.Cbw * Δf(grid, n) * ΔB̆ᵢ₊₁

    # Integrate from surface cell `N-1` downwards
    for i in n-1 : -1 : 1
        if W̆²[i+1] <= 0 # plume is stopped
            @inbounds W̆²[i] = 0
            @inbounds T̆[i] = T[i]
            @inbounds S̆[i] = S[i]
        else # plume still lives

            # Integrate temperature and salinity
            @inbounds T̆[i] = T̆[i+1] + Δc(grid, i+1) * entrainment(grid.zc[i+1], model) * (T̆[i+1] - T[i+1])
            @inbounds S̆[i] = S̆[i+1] + Δc(grid, i+1) * entrainment(grid.zc[i+1], model) * (S̆[i+1] - S[i+1])

            # Integrate vertical momentum
            ΔB̆ᵢ₊₁ = onface(i+1, grid, plume_buoyancy_excess, model)

            @inbounds W̆²[i] = W̆²[i+1] - Δf(grid, i+1) * (
                               model.nonlocalflux.Cbw * ΔB̆ᵢ₊₁
                             - model.nonlocalflux.Cew * entrainment(grid.zf[i+1], model) * W̆²[i+1])
        end
    end

    return nothing
end

#####
##### Mass flux contribution to tracer budgets
#####

maxzero(ϕ::T) where T = max(zero(T), ϕ)

@inline M(m, i) = @inbounds -m.nonlocalflux.Ca * maxsqrt(m.state.plume.W², i)

@inline function ∂z_explicit_nonlocal_flux_T(m::Model{K, <:AbstractDiagnosticPlumeModel}, i) where K
    ΔTᵢ₊₁ = onface(m.state.plume.T, i+1) - onface(m.solution.T, i+1)
    ΔTᵢ   = onface(m.state.plume.T, i)   - onface(m.solution.T, i)

    # Centered-difference advective flux for `M` at cell interfaces:
    return 1 / (2 * Δf(m.grid, i)) * (M(m, i+1) * ΔTᵢ₊₁ - M(m, i) * ΔTᵢ)
end

@inline function ∂z_explicit_nonlocal_flux_S(m::Model{K, <:AbstractDiagnosticPlumeModel}, i) where K
    ΔSᵢ₊₁ = onface(m.state.plume.S, i+1) - onface(m.solution.S, i+1)
    ΔSᵢ   = onface(m.state.plume.S, i)   - onface(m.solution.S, i)

    # Centered-difference advective flux for `M` at cell interfaces:
    return 1 / (2 * Δf(m.grid, i)) * (M(m, i+1) * ΔSᵢ₊₁ - M(m, i) * ΔSᵢ)
end


text:
abstract type BinaryTree end

struct OrderedBinaryTree{PROCS <: AbstractVector{<:Integer}, PARENT <: Union{Integer, Nothing}} <: BinaryTree
	#= Tree of the form

							8
				4						9
		2				6
	1		3		5		7

	The left branch has smaller numbers than the node, and the right
	branch has larger numbers

    A parent of nothing implies that the top node is its own parent
	=#

	N :: Int
	procs :: PROCS
	topnode_parent :: PARENT

	function OrderedBinaryTree(procs::AbstractVector{<:Integer}, p = nothing)
		N = length(procs)
		N >= 1 || throw(DomainError(N, "need at least one node to create a BinaryTree"))

		new{typeof(procs), typeof(p)}(N, procs, p)
	end
end
Base.length(tree::OrderedBinaryTree) = length(tree.procs)

# Special type for the top tree that correctly returns nchildren for the leaves
struct ConnectedOrderedBinaryTree{OBT <: OrderedBinaryTree, D <: AbstractDict} <: BinaryTree
	tree :: OBT
	workersonhosts :: D

	function ConnectedOrderedBinaryTree(tree::OBT, workersonhosts::D) where {OBT <: OrderedBinaryTree, D <: AbstractDict}
		new{OBT, D}(tree, workersonhosts)
	end
end
Base.length(tree::ConnectedOrderedBinaryTree) = length(tree.tree)
workersonhosts(tree::ConnectedOrderedBinaryTree) = tree.workersonhosts

struct SegmentedOrderedBinaryTree{PROCS <: AbstractVector{<:Integer}, TREE <: ConnectedOrderedBinaryTree} <: BinaryTree
	#=
		Each node on the cluster will have its own tree that carries out
		a local reduction. There will  be one master node on the cluster that
		will acquire the reduced value on each node. This will be followed
		by a tree to carry out reduction among the master nodes. The
		eventual reduced result will be returned to the calling process.
	=#
	N :: Int
	procs :: PROCS
	toptree :: TREE
	nodetreestartindices :: Vector{Int}

	function SegmentedOrderedBinaryTree(N::Int, procs::PROCS,
		toptree::TREE, nodetreestartindices::Vector{Int}) where {PROCS, TREE <: ConnectedOrderedBinaryTree}

		# check that the reduction nodes of the top tree have children
		all(i -> nchildren(toptree[i]) == 2, 2:2:length(toptree)) || throw(ArgumentError("reduction nodes on the top tree must have 2 children each"))

		new{PROCS, TREE}(N, procs, toptree, nodetreestartindices)
	end
end

workersonhosts(tree::SegmentedOrderedBinaryTree) = workersonhosts(tree.toptree)

function leafrankfoldedtree(::OrderedBinaryTree, Nleaves, leafno)
	@assert(leafno <= Nleaves, "leafno needs to be ⩽ Nleaves")
	leafrank = 2leafno - 1
end
leafrankfoldedtree(tree::ConnectedOrderedBinaryTree, args...) = leafrankfoldedtree(tree.tree, args...)

function foldedbinarytreefromleaves(leaves)
	Nleaves = length(leaves)
	Nnodes = 2Nleaves - 1

	allnodes = Vector{Int}(undef, Nnodes)
	foldedbinarytreefromleaves!(allnodes, leaves)

	OrderedBinaryTree(allnodes)
end

function foldedbinarytreefromleaves!(allnodes, leaves)
	top = topnoderank(OrderedBinaryTree(1:length(allnodes)))
	allnodes[top] = first(leaves)

	length(allnodes) == 1 && return

	Nnodes_left = top - 1
	Nleaves_left = div(Nnodes_left + 1 , 2)
	Nleaves_right = length(leaves) - Nleaves_left

	if Nleaves_left > 0
		leaves_left = @view leaves[1:Nleaves_left]
		leftnodes = @view allnodes[1:Nnodes_left]
		foldedbinarytreefromleaves!(leftnodes, leaves_left)
	end

	if Nleaves_right > 0
		leaves_right = @view leaves[end - Nleaves_right + 1:end]
		rightnodes = @view allnodes[top + 1:end]
		foldedbinarytreefromleaves!(rightnodes, leaves_right)
	end

    return allnodes
end

function SegmentedOrderedBinaryTree(procs::AbstractVector{<:Integer}, workersonhosts::AbstractDict = procs_node(procs))
	Np = length(procs)
	Np >= 1 || throw(DomainError(Np, "need at least one node to create a BinaryTree"))

    sum(length, values(workersonhosts)) == length(procs) || throw(ArgumentError("procs $procs do not match workersonhosts $workersonhosts"))

	nodes = collect(keys(workersonhosts))
	masternodes = Vector{Int}(undef, length(nodes))
	for (nodeind, node) in enumerate(nodes)
		workersnode = workersonhosts[node]
		nodetree = OrderedBinaryTree(workersnode)
		masternodes[nodeind] = topnode(nodetree).p
	end
	Nleaves = length(masternodes)
	toptree_inner = foldedbinarytreefromleaves(masternodes)
	toptree = ConnectedOrderedBinaryTree(toptree_inner, workersonhosts)

	toptreenonleafnodes = length(toptree) - Nleaves
	Nnodestotal = toptreenonleafnodes + length(procs)

	nodetreestartindices = Vector{Int}(undef, length(nodes))
	nodetreestartindices[1] = toptreenonleafnodes + 1
	for (nodeno, node) in enumerate(nodes)
		nodeno == 1 && continue
		prevnode = nodes[nodeno - 1]
		nodetreestartindices[nodeno] = nodetreestartindices[nodeno - 1] + length(workersonhosts[prevnode])
	end

	SegmentedOrderedBinaryTree(Nnodestotal, procs, toptree, nodetreestartindices)
end

# for a single host there are no segments
function unsegmentedtree(tree::SegmentedOrderedBinaryTree)
	OrderedBinaryTree(workers(tree))
end

Base.length(tree::SegmentedOrderedBinaryTree) = tree.N
levels(tree::OrderedBinaryTree) = levels(length(tree))
levels(n::Integer) = floor(Int, log2(n)) + 1

function Base.summary(io::IO, tree::SegmentedOrderedBinaryTree)
	Nmasternodes = length(keys(workersonhosts(tree)))
	toptreenonleafnodes = length(toptree(tree)) - Nmasternodes
	mapnodes = length(tree) - toptreenonleafnodes
	print(io, length(tree), "-node ", Base.nameof(typeof(tree)))
	print(io, " with ", mapnodes, " workers and ", toptreenonleafnodes, " extra reduction node",
        ifelse(toptreenonleafnodes > 1, "s", ""))
end
Base.summary(io::IO, tree::BinaryTree) = print(io, length(tree),"-element ", nameof(typeof(tree)))

function Base.show(io::IO, b::OrderedBinaryTree)
	print(io, summary(b), "(", workers(b), ") with top node = ", topnode(b))
end
function Base.show(io::IO, b::ConnectedOrderedBinaryTree)
	print(io, summary(b), "(", workers(b), ", ", workersonhosts(b), ")")
end

function Base.show(io::IO, b::SegmentedOrderedBinaryTree)
	summary(io, b)
	println(io)
	println(io, "toptree => ", toptree(b))
	println(io, "subtrees start from indices ", b.nodetreestartindices)
	tt = toptree(b)
	for (ind, (host, w)) in enumerate(workersonhosts(b))
		node = tt[2ind - 1]
		print(io, host, " => ",  OrderedBinaryTree(w, node.parent))
		if ind != length(workersonhosts(b))
			println(io)
		end
	end
end

toptree(tree::SegmentedOrderedBinaryTree) = tree.toptree

function levelfromtop(tree::OrderedBinaryTree, i::Integer)
	1 <= i <= length(tree) || throw(BoundsError(tree, i))

	top = topnoderank(tree)
	if i == top
		return 1
	elseif i < top
		subrange = 1:top - 1
	else
		subrange = top + 1:length(tree)
	end
	subtree = OrderedBinaryTree(subrange)
	subindex = searchsortedfirst(subrange, i)
	1 + levelfromtop(subtree, subindex)
end

function parentnoderank(tree::OrderedBinaryTree, i::Integer)
	1 <= i <= length(tree) || throw(BoundsError(tree, i))

	# The topmost node is its own parent
	length(tree) == 1 && return 1
	top = topnoderank(tree)
	length(tree) > 1 && i == top && return top

	if i < top
		# left branch, fully formed
		level = trailing_zeros(i)
		ired = i >> level # i / 2^level
		# ired is necessarily an odd number
		pow2level = 1 << level # 2^level

		# sgn is +1 if mod(ired, 4) = 1, -1 if mod(ired, 4) = 3
		sgn = 2 - mod(ired, 4)
		return i + sgn * pow2level
	elseif i > top
		# right branch, possibly partially formed
		# Carry out a recursive search
		subtreeprocs = top + 1:length(tree)
		subtree = OrderedBinaryTree(subtreeprocs)
		subind = searchsortedfirst(subtreeprocs, i)
		if subind == topnoderank(subtree)
			# This catches the case of there only being a leaf node
			# in the sub - tree
			return top
		elseif length(subtreeprocs) == 3
			# don't subdivide to 1 - node trees
			# this lets us avoid confusing this with the case of
			# the entire tree having only 1 node
			return subtreeprocs[2]
		end
		pid = parentnoderank(subtree, subind)
		return subtreeprocs[pid]
	end
end
parentnoderank(tree::ConnectedOrderedBinaryTree, i::Integer) = parentnoderank(tree.tree, i)

function subtree_rank(tree::SegmentedOrderedBinaryTree, i::Integer)
	Nmasternodes = length(keys(workersonhosts(tree)))
	toptreenonleafnodes = length(tree.toptree) - Nmasternodes

	# node on a subtree at a host
	subnodeno = i - toptreenonleafnodes

	@assert(subnodeno > 0, "i needs to be greater than $(toptreenonleafnodes)")

	# find out which node this lies on
	nptotalprevhosts = 0
	for (host, procs) in workersonhosts(tree)
		np = length(procs)
		if subnodeno <= nptotalprevhosts + np
			rankinsubtree = subnodeno - nptotalprevhosts
            w_host = workersonhosts(tree)[host]
			subtree = OrderedBinaryTree(w_host)
			return subtree, rankinsubtree, nptotalprevhosts
		end
		nptotalprevhosts += np
	end
end

"""
	masternodeindex(tree::SegmentedOrderedBinaryTree, p)

Given the top worker `p` on one node, compute the serial order of the host that it corresponds to.
"""
function masternodeindex(tree::SegmentedOrderedBinaryTree, p)
	leafno = nothing
	for (ind, w) in enumerate(values(workersonhosts(tree)))
		subtree = OrderedBinaryTree(w)
		top = topnoderank(subtree)
		if w[top] == p
			leafno = ind
			break
		end
	end
	return leafno
end

toptree_to_fulltree_index(::OrderedBinaryTree, i) = div(i, 2)
toptree_to_fulltree_index(tree::ConnectedOrderedBinaryTree, i) = toptree_to_fulltree_index(tree.tree, i)

fulltree_to_toptree_index(::OrderedBinaryTree, i) = 2i
fulltree_to_toptree_index(tree::ConnectedOrderedBinaryTree, i) = fulltree_to_toptree_index(tree.tree, i)

function nchildren(tree::OrderedBinaryTree, i::Integer)
	1 <= i <= length(tree) || throw(BoundsError(tree, i))

	if isodd(i)
		0
	elseif i == length(tree)
		1
	else
		2
	end
end
function nchildren(tree::SegmentedOrderedBinaryTree, i::Integer)
	1 <= i <= length(tree) || throw(BoundsError(tree, i))

	Nmasternodes = length(keys(workersonhosts(tree)))
	toptreenonleafnodes = length(tree.toptree) - Nmasternodes

	if toptreenonleafnodes == 0
		n = nchildren(unsegmentedtree(tree), i)

	elseif i <= toptreenonleafnodes
		# The top - tree is a full binary tree.
		# Since the leaves aren't stored, every parent node
		# has 2 children
		n = 2
	else
		subtree, rankinsubtree = subtree_rank(tree, i)
		n = nchildren(subtree, rankinsubtree)
	end

	return n
end
function nchildren(tree::ConnectedOrderedBinaryTree, i::Integer)
	1 <= i <= length(tree) || throw(BoundsError(tree, i))
	if isodd(i)
		host = ""
		for (ind, h) in enumerate(keys(workersonhosts(tree)))
			if ind == i ÷ 2 + 1
				host = h
			end
		end
		st = OrderedBinaryTree(workersonhosts(tree)[host])
		nchildren(topnode(st))
	else
		2
	end
end

topnoderank(tree::ConnectedOrderedBinaryTree) = topnoderank(tree.tree)
function topnoderank(tree::OrderedBinaryTree)
	1 << (levels(tree) - 1)
end
function topnoderank(tree::SegmentedOrderedBinaryTree)
	Nmasternodes = length(keys(workersonhosts(tree)))
	toptreenonleafnodes = length(tree.toptree) - Nmasternodes

	if toptreenonleafnodes > 0
		tnr_top = topnoderank(tree.toptree)
		tnr = toptree_to_fulltree_index(tree.toptree, tnr_top)
	else
		tnr = topnoderank(OrderedBinaryTree(workers(tree)))
	end
	return tnr
end

topnode(tree::BinaryTree) = tree[topnoderank(tree)]
function topnode(tree::OrderedBinaryTree)
	node = tree[topnoderank(tree)]
    if tree.topnode_parent === nothing
	    BinaryTreeNode(node.p, node.p, node.nchildren)
    else
        BinaryTreeNode(node.p, tree.topnode_parent, node.nchildren)
    end
end

# Indexing into a OrderedBinaryTree produces a BinaryTreeNode
struct BinaryTreeNode
	p :: Int
	parent :: Int
	nchildren :: Int

	function BinaryTreeNode(p::Int, p_parent::Int, nchildren::Int)
		(0 <= nchildren <= 2) ||
		throw(DomainError(nchildren,
			"attempt to construct a binary tree with $nchildren children"))

		new(p, p_parent, nchildren)
	end
end

function Base.show(io::IO, b::BinaryTreeNode)
	print(io,
		"BinaryTreeNode(p = $(b.p),"*
		" parent = $(b.parent), nchildren = $(b.nchildren))")
end

nchildren(b::BinaryTreeNode) = b.nchildren

Distributed.workers(tree::OrderedBinaryTree) = tree.procs
Distributed.workers(tree::ConnectedOrderedBinaryTree) = workers(tree.tree)
Distributed.workers(tree::SegmentedOrderedBinaryTree) = tree.procs

Distributed.nworkers(tree::BinaryTree) = length(workers(tree))

function Base.getindex(tree::BinaryTree, i::Integer)
	1 <= i <= length(tree) || throw(BoundsError(tree, i))

	procs = workers(tree)

	p = procs[i]
	pr = parentnoderank(tree, i)
	p_parent = procs[pr]
	n = nchildren(tree, i)

	BinaryTreeNode(p, p_parent, n)
end

function Base.getindex(tree::SegmentedOrderedBinaryTree, i::Integer)
	1 <= i <= length(tree) || throw(BoundsError(tree, i))

	Nmasternodes = length(keys(workersonhosts(tree)))
	toptreenonleafnodes = length(toptree(tree)) - Nmasternodes

	if toptreenonleafnodes == 0
		return unsegmentedtree(tree)[i]

	elseif i <= toptreenonleafnodes
		#= In a SegmentedSequentialBinaryTree the leading indices
		are the parent nodes of the top tree, so ind = i
		In a SegmentedOrderedBinaryTree, the leaves are removed
		from the top tree, so only even numbers are left.
		In this case, index i of the full tree refers to index 2i of the
		top tree, so ind = 2i
		=#
		ind = fulltree_to_toptree_index(tree.toptree, i)
		p = tree.toptree[ind].p
		pr_top = parentnoderank(tree.toptree, ind)
		p_parent = tree.toptree[pr_top].p
		n = 2
		return BinaryTreeNode(p, p_parent, n)
	else
		subtree, rankinsubtree = subtree_rank(tree, i)

		p = subtree[rankinsubtree].p
		n = nchildren(subtree, rankinsubtree)

		if rankinsubtree == topnoderank(subtree)
			# masternode
			# parent will be on the top tree
			Nmasternodes = length(keys(workersonhosts(tree)))
			leafno = masternodeindex(tree, p)
			leafrank = leafrankfoldedtree(tree.toptree, Nmasternodes, leafno)
			pr_top = parentnoderank(tree.toptree, leafrank)
			p_parent = tree.toptree[pr_top].p
		else
			# node on a sub - tree
			pr = parentnoderank(subtree, rankinsubtree)
			p_parent = subtree[pr].p
		end
		return BinaryTreeNode(p, p_parent, n)
	end
end

# Branches between nodes

struct BranchChannel
	p :: Int
	parentchannel :: RemoteChannel{Channel{Any}}
	childrenchannel :: RemoteChannel{Channel{Any}}
	nchildren :: Int

	function BranchChannel(p::Int, parentchannel::RemoteChannel, childrenchannel::RemoteChannel, nchildren::Int)

		(0 <= nchildren <= 2) ||
		throw(DomainError(nchildren,
			"attempt to construct a binary tree with $nchildren children"))

		new(p, parentchannel, childrenchannel, nchildren)
	end
end
nchildren(b::BranchChannel) = b.nchildren

childrenerror(nchildren) = throw(DomainError(nchildren,
	"attempt to construct a binary tree with $nchildren children"))

function BranchChannel(p::Integer, parentchannel::RemoteChannel, nchildren::Integer)

	(0 <= nchildren <= 2) || childrenerror(nchildren)

	childrenchannel = RemoteChannel(() -> Channel(nchildren), p)
	BranchChannel(p, parentchannel, childrenchannel, nchildren)
end

function BranchChannel(p::Integer, nchildren::Integer)

	(0 <= nchildren <= 2) || childrenerror(nchildren)

	parentchannel, childrenchannel = @sync begin
		parenttask = @async RemoteChannel(() -> Channel(1), p)
		childtask = @async RemoteChannel(() -> Channel(nchildren), p)
		asyncmap(fetch, (parenttask, childtask))
	end
	BranchChannel(p, parentchannel, childrenchannel, nchildren)
end

function Base.show(io::IO, b::BranchChannel)
	N = nchildren(b)
	p_parent = b.parentchannel.where
	p = b.p

	if N == 2
		str = "Branch: "*string(p_parent)*" ← "*string(p)*" ⇇ 2 children"
	elseif N == 1
		str = "Branch: "*string(p_parent)*" ← "*string(p)*" ← 1 child"
	else
		str = "Leaf  : "*string(p_parent)*" ← "*string(p)
	end

	print(io, str)
end

function createbranchchannels!(branches, tree::OrderedBinaryTree, superbranch::BranchChannel)
	top = topnoderank(tree)
	topnode = tree[top]

	topbranchchannels = BranchChannel(topnode.p, superbranch.childrenchannel, nchildren(topnode))
	branches[top] = topbranchchannels

	length(tree) == 1 && return nothing

	left_inds = 1:top - 1
	right_inds = top + 1:length(tree)

	@sync begin
		@async if !isempty(left_inds)
			left_child = OrderedBinaryTree(@view workers(tree)[left_inds])
			createbranchchannels!(@view(branches[left_inds]), left_child, topbranchchannels)
		end
		@async if !isempty(right_inds)
			right_child = OrderedBinaryTree(@view workers(tree)[right_inds])
			createbranchchannels!(@view(branches[right_inds]), right_child, topbranchchannels)
		end
	end
	return nothing
end

function createbranchchannels(tree::SegmentedOrderedBinaryTree)

	nodes = keys(workersonhosts(tree))
	toptree = tree.toptree
	Nmasternodes = length(nodes)
	toptreenonleafnodes = length(toptree) - Nmasternodes

	branches = Vector{BranchChannel}(undef, length(tree))

	# populate the top tree other than the masternodes
	# This is only run if there are multiple hosts
	if toptreenonleafnodes > 0
		topnoderank_toptree = topnoderank(toptree)
		topnode_toptree = toptree[topnoderank_toptree]
		N = nchildren(topnode_toptree)
		topmostbranch = BranchChannel(topnode_toptree.p, N)
		branches[topnoderank_toptree] = topmostbranch

		left_inds = 1:(topnoderank_toptree - 1)
		right_inds = (topnoderank_toptree + 1):length(toptree)

		@sync begin
			@async if !isempty(left_inds)
				left_child = OrderedBinaryTree(@view workers(toptree)[left_inds])
				createbranchchannels!(@view(branches[left_inds]), left_child, topmostbranch)
			end

			@async if !isempty(right_inds)
				right_child = OrderedBinaryTree(@view workers(toptree)[right_inds])
				createbranchchannels!(@view(branches[right_inds]), right_child, topmostbranch)
			end
		end

		#= Remove the leaves from the top tree (masternodes).
		They are the top nodes of the individual trees at the hosts.
		They will be created separately and linked to the top tree.
		=#
		for i = 1:toptreenonleafnodes
			branches[i] = branches[2i]
		end
	end

	@sync for (nodeno, node) in enumerate(nodes)
		@async begin
			# Top node for each subtree (a masternode)
			workersnode = workersonhosts(tree)[node]
			nodetree = OrderedBinaryTree(workersnode)
			top = topnoderank(nodetree)
			topnode = nodetree[top]
			p = topnode.p

			if toptreenonleafnodes > 0
				# inherit from the parent node
				leafno = masternodeindex(tree, p)
				leafrank = leafrankfoldedtree(tree.toptree, Nmasternodes, leafno)
				parentrank = parentnoderank(toptree, leafrank)
				parentrankfulltree = toptree_to_fulltree_index(toptree, parentrank)
				parentnodebranches = branches[parentrankfulltree]
				parentchannel = parentnodebranches.childrenchannel
			else
				#= This happens if there is only one host,
				in which case there's nothing to inherit.
				In this case there's no difference between a
				SegmentedOrderedBinaryTree and an OrderedBinaryTree
				The top node is created separately as it is its own parent
				=#
				parentchannel = RemoteChannel(() -> Channel(1), p)
			end

			topbranchnode = BranchChannel(p, parentchannel, nchildren(topnode))
			nodetreestartindex = tree.nodetreestartindices[nodeno]
			branches[nodetreestartindex + top - 1] = topbranchnode

			# Populate the rest of the tree
			left_inds_nodetree = (1:top - 1)
			left_inds_fulltree = (nodetreestartindex - 1) .+ left_inds_nodetree
			right_inds_nodetree = top + 1:length(nodetree)
			right_inds_fulltree = (nodetreestartindex - 1) .+ right_inds_nodetree

			@async if !isempty(left_inds_nodetree)
				left_child = OrderedBinaryTree(@view workers(nodetree)[left_inds_nodetree])
				createbranchchannels!(@view(branches[left_inds_fulltree]), left_child, topbranchnode)
			end

			@async if !isempty(right_inds_nodetree)
				right_child = OrderedBinaryTree(@view workers(nodetree)[right_inds_nodetree])
				createbranchchannels!(@view(branches[right_inds_fulltree]), right_child, topbranchnode)
			end
		end
	end

	return branches
end

function createbranchchannels(pool::AbstractWorkerPool, len::Integer)
	w = workersactive(pool, len)
	tree = SegmentedOrderedBinaryTree(w)
	branches = createbranchchannels(tree)
	tree, branches
end

topbranch(tree::BinaryTree, branches::AbstractVector{<:BranchChannel}) = branches[topnoderank(tree)]


text:
using Documenter, SpinGlassPEPS

format = Documenter.HTML(
    edit_link = "master",
    prettyurls = !("local" in ARGS),
    mathengine = mathengine = MathJax3(Dict(
        :tex=>Dict(
            "inlineMath"=>[ ["\$","\$"], ["\\(","\\)"] ],
            "processEscapes"=>true,
            "macros"=>Dict(
                "bra"=> ["{\\langle #1|}",1],
                "ket"=> ["{| #1\\rangle}",1],
                "ketbra"=> ["{\\left| #1 \\rangle \\langle #2 \\right|}",2],
                "braket"=> ["{\\langle #1|#2\\rangle}",2],
                "Tr"=> ["\\mathrm{Tr}",0],
                "tr"=> ["\\Tr",0],
                "ee"=> ["\\mathrm{e}"],
                "ii"=> ["\\mathrm{i}"],
                "dd"=> ["\\mathrm{d}"],
                "1"=> ["{\\mathbb{1}}"]
            )
        )
    ))
)

makedocs(
    clean = true,
    format = format,
    sitename = "SpinGlassPEPS.jl",
    authors = "Krzysztof Domino, Bartłomiej Gardas, Konrad Jałowiecki, Łukasz Pawela, Marek Rams",
    pages = [
        "Home" => "index.md",
        "Library" => "lib/SpinGlassPEPS.md"
    ]
)

deploydocs(
    repo = "github.com/iitis/SpinGlassPEPS.jl.git",
    devbranch = "lp/docs-example"
)


text:
open("day06/day06.in") do io
    answers = Dict()
    answers_count = 0
    for line in readlines(io)
        line = line * " "
        if cmp(line, " ") == 0
            answers_count += length(answers)
            answers = Dict()
        else
            for ch in line
                if ch != ' '
                    answers[ch] = get(answers, ch, 0) + 1
                end
            end
        end
    end
    answers_count += length(answers)
    println(answers_count)
end

max_stars_repo_name: detrin/advent-of-code

max_issues_repo_name: detrin/advent-of-code

max_forks_repo_name: detrin/advent-of-code


text:
# This file was generated by the Julia Swagger Code Generator
# Do not modify this file directly. Modify the swagger specification instead.

@doc raw"""VolumeError captures an error encountered during a volume operation.

    IoK8sApiStorageV1VolumeError(;
        message=nothing,
        time=nothing,
    )

    - message::String : String detailing the error encountered during Attach or Detach operation. This string may be logged, so it should not contain sensitive information.
    - time::IoK8sApimachineryPkgApisMetaV1Time : Time the error was encountered.
"""
mutable struct IoK8sApiStorageV1VolumeError <: SwaggerModel
    message::Any # spec type: Union{ Nothing, String } # spec name: message
    time::Any # spec type: Union{ Nothing, IoK8sApimachineryPkgApisMetaV1Time } # spec name: time

    function IoK8sApiStorageV1VolumeError(;message=nothing, time=nothing)
        o = new()
        validate_property(IoK8sApiStorageV1VolumeError, Symbol("message"), message)
        setfield!(o, Symbol("message"), message)
        validate_property(IoK8sApiStorageV1VolumeError, Symbol("time"), time)
        setfield!(o, Symbol("time"), time)
        o
    end
end # type IoK8sApiStorageV1VolumeError

const _property_map_IoK8sApiStorageV1VolumeError = Dict{Symbol,Symbol}(Symbol("message")=>Symbol("message"), Symbol("time")=>Symbol("time"))
const _property_types_IoK8sApiStorageV1VolumeError = Dict{Symbol,String}(Symbol("message")=>"String", Symbol("time")=>"IoK8sApimachineryPkgApisMetaV1Time")
Base.propertynames(::Type{ IoK8sApiStorageV1VolumeError }) = collect(keys(_property_map_IoK8sApiStorageV1VolumeError))
Swagger.property_type(::Type{ IoK8sApiStorageV1VolumeError }, name::Symbol) = Union{Nothing,eval(Base.Meta.parse(_property_types_IoK8sApiStorageV1VolumeError[name]))}
Swagger.field_name(::Type{ IoK8sApiStorageV1VolumeError }, property_name::Symbol) =  _property_map_IoK8sApiStorageV1VolumeError[property_name]

function check_required(o::IoK8sApiStorageV1VolumeError)
    true
end

function validate_property(::Type{ IoK8sApiStorageV1VolumeError }, name::Symbol, val)
end


text:
include("dm.jl")
include("tau.jl")

function _sim!(sys::System, pathEnsemble::Ensemble, i::Int64)
    for k in eachindex(pathEnsemble)
        _directMethod!(pathEnsemble[k], sys.model.F, sys.model.ν, sys.times[i], sys.model.nr)
    end
end

function _sim!(sys::System, pathEnsemble::Ensemble, i::Int64, tauVar::TauVar)
    for k in eachindex(pathEnsemble)
        _optimisedTauLeap!(sys.model, pathEnsemble[k], sys.times[i], sys.routine.tauOpt, tauVar)
    end
end

max_stars_repo_name: pzuliani/SPICE

max_issues_repo_name: pzuliani/SPICE

max_forks_repo_name: pzuliani/SPICE


text:
using PlotlyJS, HTTP, CSV, DataFrames

function ohlc1()
    t = ohlc(open=[33.0, 33.3, 33.5, 33.0, 34.1],
             high=[33.1, 33.3, 33.6, 33.2, 34.8],
             low=[32.7, 32.7, 32.8, 32.6, 32.8],
             close=[33.0, 32.9, 33.3, 33.1, 33.1])
    plot(t)
end

function ohlc2()
    function get_ohlc(ticker; kwargs...)
        res = HTTP.get("https://www.quandl.com/api/v3/datasets/WIKI/$(ticker)/data.csv?start_date=2017-01-01")
        df = DataFrame(CSV.File(res.body))
        ohlc(df, x=:Date, open=:Open, high=:High, low=:Low, close=:Close; kwargs...)
    end

    p1 = plot(get_ohlc("AAPL", name="Apple"), Layout(title="Apple"))
    p2 = plot(get_ohlc("GOOG", name="Google"), Layout(title="Google"))

    [p1 p2]
end

function candlestick1()
    t = candlestick(open=[33.0, 33.3, 33.5, 33.0, 34.1],
                    high=[33.1, 33.3, 33.6, 33.2, 34.8],
                    low=[32.7, 32.7, 32.8, 32.6, 32.8],
                    close=[33.0, 32.9, 33.3, 33.1, 33.1])
    plot(t)
end

function candlestick2()
    function get_candlestick(ticker; kwargs...)
        res = HTTP.get("https://www.quandl.com/api/v3/datasets/WIKI/$(ticker)/data.csv?start_date=2017-01-01")
        df = DataFrame(CSV.File(res.body))
        candlestick(df, x=:Date, open=:Open, high=:High, low=:Low, close=:Close; kwargs...)
    end

    p1 = plot(get_candlestick("AAPL", name="Apple"), Layout(title="Apple"))
    p2 = plot(get_candlestick("GOOG", name="Google"), Layout(title="Google"))

    [p1 p2]
end


text:
_πACGT(mod::NASM) = 0.25
_πACGU(mod) = _πACGT(mod)
_πA(mod) = _πACGT(mod)
_πC(mod) = _πACGT(mod)
_πG(mod) = _πACGT(mod)
_πT(mod) = _πACGT(mod)
_π(mod::NASM) = SVector(_πA(mod), _πC(mod), _πG(mod), _πT(mod))
_πR(mod::NASM) = _πA(mod) + _πG(mod)
_πY(mod::NASM) = _πT(mod) + _πC(mod)

"""
Generate a Q matrix for a `NucleicAcidSubstitutionModel`, of the form:

\$Q = \\begin{bmatrix}
Q_{A, A} & Q_{A, C} & Q_{A, G} & Q_{A, T} \\\\
Q_{C, A} & Q_{C, C} & Q_{C, G} & Q_{C, T} \\\\
Q_{G, A} & Q_{G, C} & Q_{G, G} & Q_{G, T} \\\\
Q_{T, A} & Q_{T, C} & Q_{T, G} & Q_{T, T} \\end{bmatrix}\$

Call as either
1) `Q(model)`, or
2) `Q(model, bool)`
Form (2) scales the matrix so that ``_π(model) ⋅ -diag(Q(model)) = 1``
when bool=true. `Q(model, false)` is equivalent to `Q(model)`.
"""
@inline function Q(mod::NASM)
    α = _α(mod)
    β = _β(mod)
    γ = _γ(mod)
    δ = _δ(mod)
    ϵ = _ϵ(mod)
    η = _η(mod)
    πA = _πA(mod)
    πC = _πC(mod)
    πG = _πG(mod)
    πT = _πT(mod)

    return Qmatrix(-(δ * πC + η * πG + β * πT), δ * πA, η * πA, β * πA,
                   δ * πC, -(δ * πA + ϵ * πG + α * πT), ϵ * πC, α * πC,
                   η * πG, ϵ * πG, -(η * πA + ϵ * πC + γ * πT), γ * πG,
                   β * πT, α * πT, γ * πT, -(β * πA + α * πC + γ * πG))
end

function Q(mod::NASM, scale::Bool)
  if scale
    q = Q(mod)
    scale = 1/(-diag(q)' * _π(mod))
    return q * scale
  else
    Q(mod)
  end
end

scale_generic(mod::NASM) = 1/(-diag(Q(mod))' * _π(mod))

_scale(mod::NASM) = scale_generic(mod)

@inline function P_generic(mod::NASM, t::Float64)
    if t < 0.0
        error("t must be positive")
    end
    return exp(Q(mod) * t)
end

function P_generic(mod::NASM, t, scale::Bool)
    if scale
        P(mod, t * _scale(mod))
    else
        P(mod, t)
    end
end

function P_generic(mod::NASM, t::Array{Float64})
    if any(t .< 0.0)
        error("t must be positive")
    end

    try
        q = Q(mod)

        # Use symmetrical similar matrix if possible:
        # B is similar (has same eigenvalues) to Q, but is symmetrical if the model is reversible.
        # Eigenvalue decomposition is easier with symmetrical matrix
        # NB: computation of B described in Inferring Phylogenies, Felsenstein, p.206

        rootπ = sqrt.(_π(mod))
        b = diagm(0 => rootπ) * q * diagm(0 => 1.0 ./ rootπ)

        # If B is symmetrical, then do eigenvalue decomposition on B. The resulting
        # eigenvalues are the same as for Q, then Q's eigenvectors can be obtained by
        # a simple conversion.
        if ishermitian(round.(b, digits=12))
            eig_vals, r = eigen(b)
            eig_vecs = diagm(0 => 1.0 ./ rootπ) * r # eig_vecs are the eigenvectors of Q
            eig_vecs_inv = r' * diagm(0 => rootπ)

        # If B is not symmetrical, fall back to directly obtaining eigenvalues from Q
        else
            eig_vals, eig_vecs = eigen(Array(q))
            eig_vecs_inv = inv(eig_vecs)
        end

        return [SMatrix{size(q)...}(eig_vecs * diagm(0 => exp.(eig_vals .* i)) * eig_vecs_inv) for i in t]

    catch
        # Any errors, fall back to direct use of matrix exponential
        return [P_generic(mod, i) for i in t]

    end
end

"""
Generate a P matrix for a `NucleicAcidSubstitutionModel`, of the form:

\$P = \\begin{bmatrix}
P_{A, A} & P_{A, C} & P_{A, G} & P_{A, T} \\\\
P_{C, A} & P_{C, C} & P_{C, G} & P_{C, T} \\\\
P_{G, A} & P_{G, C} & P_{G, G} & P_{G, T} \\\\
P_{T, A} & P_{T, C} & P_{T, G} & P_{T, T} \\end{bmatrix}\$

for specified time

Call as either
1) `P(model, t)`, or
2) `P(model, t, bool)`
Form (2) obtains its probabilities from the scaled Q matrix if bool=true.
Branch lengths estimated from a scaled P matrix are in units of expected
number of substitutions per site. `P(model, t, false)` is equivalent to
`P(model, t)`.
"""
P(mod::NASM, t) = P_generic(mod, t)

P(mod::NASM, t, scale) = P_generic(mod, t, scale)


text:
module Tricks

    using Base: rewrap_unionall, unwrap_unionall, uncompressed_ast
    using Base: CodeInfo

    export static_hasmethod

    # This is used to create the CodeInfo returned by static_hasmethod.
    _hasmethod_false(@nospecialize(f), @nospecialize(t)) = false
    _hasmethod_true(@nospecialize(f), @nospecialize(t)) = true

    """
        static_hasmethod(f, type_tuple::Type{<:Tuple)

    Like `hasmethod` but runs at compile-time (and does not accept a worldage argument).

    !!! Note
        This absolutely must *not* be called dynamically. Else it will fail to update
        when new methods are declared.
        If you do not know how to ensure that it is not called dynamically,
        do not use this.
    """
    @generated function static_hasmethod(@nospecialize(f), @nospecialize(t::Type{T}),) where {T<:Tuple}
        # The signature type:
        world = typemax(UInt)
        method_insts = Core.Compiler.method_instances(f.instance, T, world)
        method_doesnot_exist = isempty(method_insts)
        ret_func = method_doesnot_exist ? _hasmethod_false : _hasmethod_true
        ci_orig = uncompressed_ast(typeof(ret_func).name.mt.defs.func)
        ci = ccall(:jl_copy_code_info, Ref{CodeInfo}, (Any,), ci_orig)

        # Now we add the edges so if a method is defined this recompiles
        if method_doesnot_exist
            # No method so attach to method table
            mt = f.name.mt
            typ = rewrap_unionall(Tuple{f, unwrap_unionall(T).parameters...}, T)
            ci.edges = Core.Compiler.vect(mt, typ)
        else  # method exists, attach edges to all instances
            ci.edges = method_insts
        end
        return ci
    end

end


text:
#!/usr/bin/env julia -

using Luxor

using Test

using Random
Random.seed!(42)

function star_test(fname)
    Drawing(1600, 1600, fname)
    origin()
    background("white")
    setopacity(0.5)
    sethue("grey40")
    fontsize(4)
    setline(0.5)
    tiles = Tiler(1600, 1600, 10, 10, margin=50)
    for (pos, n) in tiles
           randomsides = rand(3:8)
           randomratio = rand(2:8)/10
           randomsmoothing = rand(5:20)
           p = star(pos, tiles.tilewidth/2 - 10, randomsides, randomratio, 0, vertices=true)
           prettypoly(p, close=true, :stroke)
           polysmooth(p, randomsmoothing, :fill, debug=true)
     end
    @test finish() == true
    println("...finished test: output in $(fname)")
end

function random_test(fname)
    Drawing(1600, 1600, fname)
    origin()
    background("white")
    setopacity(0.5)
    sethue("black")
    fontsize(4)
    setline(0.25)
    tiles = Tiler(1600, 1600, 5, 5, margin=50)
    for (pos, n) in tiles
       randomsides = rand(3:8)
       randomratio = 0.5 * rand()
       randomsmoothradius = rand(5:20)
       p = randompointarray(pos.x - tiles.tilewidth/2 - 10, pos.y - tiles.tilewidth/2, pos.x + tiles.tilewidth/2, pos.y + tiles.tilewidth/2, 10)
       p = polysortbydistance(p, pos)
       p = simplify(p, 5)
       sethue("red")
       prettypoly(p, close=true, :stroke)
       randomhue()
       polysmooth(p, randomsmoothradius, :fill, debug=true)
       sethue("red")
    end
    @test finish() == true
    println("...finished test: output in $(fname)")
end

star_test("polysmooth-stars.pdf")
random_test("polysmooth-random.pdf")


text:
module Text

using ColorTypes, Colors

import SenseHat.LED: RGB565, led_matrix, led_clear
import Colors.@colorant_str

export show_char, show_message

"""
    show_char(c::Char, color::ColorTypes.AbstractRGB = colorant"white")

Display a single character `c` on the `SenseHat` LED Matrix.
"""
function show_char(c::Char, color::ColorTypes.AbstractRGB = colorant"white")
    if haskey(font, c)
        tocolor(b) = b ? RGB565(color) : RGB565(colorant"black")
        L = PermutedDimsArray(led_matrix(), (2, 1))
        L[:] .= colorant"black"
        L[:,2:6] .= tocolor.(font[c])
    else
        error("Character font for $c not available \n")
    end
    return
end

"""
    show_message(s::String, speed::Real = 0.2, color::ColorTypes.AbstractRGB = colorant"white")

Display a scrolling message `s` on the `SenseHat` LED Matrix. `speed` is the time in seconds per frame and `color` is the color of text.

# Example

```
using SenseHat

show_message("Hello, World!", 0.2, colorant"purple")
```
"""
function show_message(s::String, speed::Real = 0.2, color::ColorTypes.AbstractRGB = colorant"white")
    for c in s
        if haskey(font, c) == false
            error("Character font for $c not available \n")
            return
        end
        img = Array{Bool, 2}(undef, 8, 16 + 5*length(s))
        img[:] .= 0
        for i in 1:length(s)
            img[1:8, (4 + 5*i):(8 + 5*i)] = font[s[i]]
        end
        tocolor(b) = b ? RGB565(color) : RGB565(colorant"black")
        for i in 1:(size(img,2) - 7)
            frame = tocolor.(img[1:8, i:(i + 7)])
            led_matrix()[:] = permutedims(frame, (2,1))
            sleep(speed)
        end
    return
    end
end

show_message(s::String, color::ColorTypes.AbstractRGB) = show_message(s, 0.2, color)

font = Dict(
# English
' ' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; ]),
'+' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 1 1 1 1 1 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; ]),
'-' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; ]),
'*' => Bool.([0 0 1 0 0 ; 1 0 1 0 1 ; 0 1 1 1 0 ; 1 0 1 0 1 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; ]),
'/' => Bool.([0 0 0 0 1 ; 0 0 0 0 1 ; 0 0 0 1 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 1 0 0 0 ; 1 0 0 0 0 ; 1 0 0 0 0 ; ]),
'!' => Bool.([0 0 0 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; ]),
'"' => Bool.([0 1 0 0 1 ; 0 1 0 0 1 ; 1 0 0 1 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; ]),
'#' => Bool.([0 0 0 0 0 ; 0 1 0 1 0 ; 0 1 0 1 0 ; 1 1 1 1 1 ; 0 1 0 1 0 ; 1 1 1 1 1 ; 0 1 0 1 0 ; 0 1 0 1 0 ; ]),
'$' => Bool.([0 0 0 0 0 ; 0 0 1 0 0 ; 1 1 1 1 1 ; 1 0 1 0 0 ; 1 1 1 1 1 ; 0 0 1 0 1 ; 1 1 1 1 1 ; 0 0 1 0 0 ; ]),
'>' => Bool.([0 0 0 0 0 ; 0 1 0 0 0 ; 0 0 1 0 0 ; 0 0 0 1 0 ; 0 0 0 0 1 ; 0 0 0 1 0 ; 0 0 1 0 0 ; 0 1 0 0 0 ; ]),
'<' => Bool.([0 0 0 0 0 ; 0 0 0 1 0 ; 0 0 1 0 0 ; 0 1 0 0 0 ; 1 0 0 0 0 ; 0 1 0 0 0 ; 0 0 1 0 0 ; 0 0 0 1 0 ; ]),
'0' => Bool.([0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 1 0 0 1 ; 1 0 1 0 1 ; 1 0 0 1 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'1' => Bool.([0 0 0 0 0 ; 0 0 1 0 0 ; 0 1 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 1 1 1 0 ; ]),
'2' => Bool.([0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 0 0 0 0 1 ; 0 0 0 1 0 ; 0 0 1 0 0 ; 0 1 0 0 0 ; 1 1 1 1 1 ; ]),
'3' => Bool.([0 0 0 0 0 ; 1 1 1 1 1 ; 0 0 0 1 0 ; 0 0 1 0 0 ; 0 0 0 1 0 ; 0 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'4' => Bool.([0 0 0 0 0 ; 0 0 0 1 0 ; 0 0 1 1 0 ; 0 1 0 1 0 ; 1 0 0 1 0 ; 1 1 1 1 1 ; 0 0 0 1 0 ; 0 0 0 1 0 ; ]),
'5' => Bool.([0 0 0 0 0 ; 1 1 1 1 1 ; 1 0 0 0 0 ; 1 1 1 1 0 ; 0 0 0 0 1 ; 0 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'6' => Bool.([0 0 0 0 0 ; 0 0 1 1 0 ; 0 1 0 0 0 ; 1 0 0 0 0 ; 1 1 1 1 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'7' => Bool.([0 0 0 0 0 ; 1 1 1 1 1 ; 0 0 0 0 1 ; 0 0 0 1 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; ]),
'8' => Bool.([0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'9' => Bool.([0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 1 ; 0 0 0 0 1 ; 0 0 0 1 0 ; 0 1 1 0 0 ; ]),
'.' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 1 1 0 0 ; 0 1 1 0 0 ; ]),
'=' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 1 1 1 1 1 ; 0 0 0 0 0 ; 1 1 1 1 1 ; 0 0 0 0 0 ; 0 0 0 0 0 ; ]),
')' => Bool.([0 0 0 0 0 ; 1 0 0 0 0 ; 0 1 0 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 1 0 0 0 ; 1 0 0 0 0 ; ]),
'(' => Bool.([0 0 0 0 1 ; 0 0 0 1 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 0 1 0 ; 0 0 0 0 1 ; 0 0 0 0 0 ; ]),
'A' => Bool.([0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 1 1 1 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; ]),
'B' => Bool.([0 0 0 0 0 ; 1 1 1 1 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 1 1 1 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 1 1 1 0 ; ]),
'C' => Bool.([0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 0 0 0 0 ; 1 0 0 0 0 ; 1 0 0 0 0 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'D' => Bool.([0 0 0 0 0 ; 1 1 1 0 0 ; 1 0 0 1 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 1 0 ; 1 1 1 0 0 ; ]),
'E' => Bool.([0 0 0 0 0 ; 1 1 1 1 1 ; 1 0 0 0 1 ; 1 0 0 0 0 ; 1 1 1 1 0 ; 1 0 0 0 0 ; 1 0 0 0 1 ; 1 1 1 1 1 ; ]),
'F' => Bool.([0 0 0 0 0 ; 1 1 1 1 1 ; 1 0 0 0 0 ; 1 0 0 0 0 ; 1 1 1 1 0 ; 1 0 0 0 0 ; 1 0 0 0 0 ; 1 1 0 0 0 ; ]),
'G' => Bool.([0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 0 0 0 0 ; 1 0 1 1 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 1 ; ]),
'H' => Bool.([0 0 0 0 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 1 1 1 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; ]),
'I' => Bool.([0 0 0 0 0 ; 0 1 1 1 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 1 1 1 0 ; ]),
'J' => Bool.([0 0 0 0 0 ; 0 0 1 1 1 ; 0 0 0 1 0 ; 0 0 0 1 0 ; 0 0 0 1 0 ; 0 0 0 1 0 ; 1 0 0 1 0 ; 0 1 1 0 0 ; ]),
'K' => Bool.([0 0 0 0 0 ; 1 0 0 0 1 ; 1 0 0 1 1 ; 1 0 1 1 0 ; 1 1 1 0 0 ; 1 0 1 1 0 ; 1 0 0 1 1 ; 1 0 0 0 1 ; ]),
'L' => Bool.([0 0 0 0 0 ; 1 0 0 0 0 ; 1 0 0 0 0 ; 1 0 0 0 0 ; 1 0 0 0 0 ; 1 0 0 0 0 ; 1 0 0 0 0 ; 1 1 1 1 1 ; ]),
'M' => Bool.([0 0 0 0 0 ; 1 0 0 0 1 ; 1 1 0 1 1 ; 1 0 1 0 1 ; 1 0 1 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; ]),
'N' => Bool.([0 0 0 0 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 1 0 0 1 ; 1 0 1 0 1 ; 1 0 0 1 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; ]),
'O' => Bool.([0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'P' => Bool.([0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 1 1 1 0 ; 1 0 0 0 0 ; 1 0 0 0 0 ; 1 0 0 0 0 ; ]),
'Q' => Bool.([0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 1 0 1 ; 0 1 1 1 0 ; 0 0 0 0 1 ; ]),
'R' => Bool.([0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 1 1 1 0 ; 1 0 1 0 0 ; 1 0 0 1 0 ; 1 0 0 0 1 ; ]),
'S' => Bool.([0 0 0 0 0 ; 0 1 1 1 1 ; 1 0 0 0 0 ; 1 0 0 0 0 ; 0 1 1 1 0 ; 0 0 0 0 1 ; 0 0 0 0 1 ; 1 1 1 1 0 ; ]),
'T' => Bool.([0 0 0 0 0 ; 1 1 1 1 1 ; 1 0 1 0 1 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; ]),
'U' => Bool.([0 0 0 0 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'V' => Bool.([0 0 0 0 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 0 1 0 ; 0 0 1 0 0 ; ]),
'W' => Bool.([0 0 0 0 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 1 0 1 ; 1 0 1 0 1 ; 1 1 1 1 1 ; 1 0 0 0 1 ; ]),
'X' => Bool.([0 0 0 0 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 0 1 0 ; 0 0 1 0 0 ; 0 1 0 1 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; ]),
'Y' => Bool.([0 0 0 0 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 0 1 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; ]),
'Z' => Bool.([0 0 0 0 0 ; 1 1 1 1 1 ; 0 0 0 0 1 ; 0 0 0 1 0 ; 0 0 1 0 0 ; 0 1 0 0 0 ; 1 0 0 0 0 ; 1 1 1 1 1 ; ]),
'a' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 0 0 0 0 1 ; 0 1 1 1 1 ; 1 0 0 0 1 ; 0 1 1 1 1 ; ]),
'b' => Bool.([0 0 0 0 0 ; 1 0 0 0 0 ; 1 0 0 0 0 ; 1 0 1 1 0 ; 1 1 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'c' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 0 ; 1 0 0 0 0 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'd' => Bool.([0 0 0 0 0 ; 0 0 0 0 1 ; 0 0 0 0 1 ; 0 1 1 0 1 ; 1 0 0 1 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 1 ; ]),
'e' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 1 1 1 1 ; 1 0 0 0 0 ; 0 1 1 1 0 ; ]),
'f' => Bool.([0 0 0 0 0 ; 0 0 0 1 0 ; 0 0 1 0 1 ; 0 0 1 0 0 ; 0 1 1 1 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; ]),
'g' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 1 1 1 1 ; 1 0 0 0 1 ; 0 1 1 1 1 ; 0 0 0 0 1 ; 0 1 1 1 0 ; ]),
'h' => Bool.([0 0 0 0 0 ; 1 0 0 0 0 ; 1 0 0 0 0 ; 1 0 1 1 0 ; 1 1 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; ]),
'i' => Bool.([0 0 0 0 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 0 1 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 1 1 1 0 ; ]),
'j' => Bool.([0 0 0 0 0 ; 0 0 0 1 0 ; 0 0 0 0 0 ; 0 0 0 1 0 ; 0 0 0 1 0 ; 0 0 0 1 0 ; 1 0 0 1 0 ; 0 1 1 0 0 ; ]),
'k' => Bool.([0 0 0 0 0 ; 0 1 0 0 0 ; 0 1 0 0 0 ; 0 1 0 0 1 ; 0 1 0 1 0 ; 0 1 1 0 0 ; 0 1 0 1 0 ; 0 1 0 0 1 ; ]),
'l' => Bool.([0 0 0 0 0 ; 0 1 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 1 1 1 0 ; ]),
'm' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 1 1 0 1 0 ; 1 0 1 0 1 ; 1 0 1 0 1 ; 1 0 1 0 1 ; 1 0 1 0 1 ; ]),
'n' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 1 0 1 1 0 ; 1 1 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; ]),
'o' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'p' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 1 1 1 1 0 ; 1 0 0 0 1 ; 1 1 1 1 0 ; 1 0 0 0 0 ; 1 0 0 0 0 ; ]),
'q' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 1 1 1 1 ; 1 0 0 0 1 ; 0 1 1 1 1 ; 0 0 0 0 1 ; 0 0 0 0 1 ; ]),
'r' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 1 0 1 1 ; 0 1 1 0 0 ; 0 1 0 0 0 ; 0 1 0 0 0 ; 0 1 0 0 0 ; ]),
's' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 1 1 1 1 ; 1 0 0 0 0 ; 0 1 1 1 0 ; 0 0 0 0 1 ; 1 1 1 1 0 ; ]),
't' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 1 0 0 ; 0 1 1 1 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 1 ; 0 0 0 1 0 ; ]),
'u' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 1 1 ; 0 1 1 0 1 ; ]),
'v' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 0 1 0 ; 0 0 1 0 0 ; ]),
'w' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 1 0 1 ; 1 0 1 0 1 ; 0 1 0 1 0 ; ]),
'x' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 1 1 0 0 1 ; 0 0 1 1 0 ; 0 0 1 0 0 ; 0 1 1 0 0 ; 1 0 0 1 1 ; ]),
'y' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 1 0 0 0 1 ; 0 1 0 0 1 ; 0 0 1 1 0 ; 0 0 1 0 0 ; 1 1 0 0 0 ; ]),
'z' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 1 1 1 1 1 ; 0 0 0 1 0 ; 0 0 1 0 0 ; 0 1 0 0 0 ; 1 1 1 1 1 ; ]),
'?' => Bool.([0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 0 0 0 0 1 ; 0 0 0 1 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 0 0 1 0 0 ; ]),
',' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 1 1 0 0 ; 0 0 1 0 0 ; 0 1 0 0 0 ; ]),
';' => Bool.([0 0 0 0 0 ; 0 1 1 0 0 ; 0 1 1 0 0 ; 0 0 0 0 0 ; 0 1 1 0 0 ; 0 1 1 0 0 ; 0 0 1 0 0 ; 0 1 0 0 0 ; ]),
':' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 1 1 0 0 ; 0 1 1 0 0 ; 0 0 0 0 0 ; 0 1 1 0 0 ; 0 1 1 0 0 ; 0 0 0 0 0 ; ]),
'|' => Bool.([0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; ]),
'@' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 0 1 1 0 1 ; 1 0 1 0 1 ; 1 0 1 0 1 ; 0 1 1 1 0 ; ]),
'%' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 1 0 0 1 ; 0 0 0 1 0 ; 0 0 1 0 0 ; 0 1 0 0 0 ; 1 0 0 1 0 ; 0 0 0 0 0 ; ]),
'[' => Bool.([0 0 0 0 0 ; 0 1 1 1 0 ; 0 1 0 0 0 ; 0 1 0 0 0 ; 0 1 0 0 0 ; 0 1 0 0 0 ; 0 1 0 0 0 ; 0 1 1 1 0 ; ]),
'&' => Bool.([0 0 0 0 0 ; 0 1 1 0 0 ; 1 0 0 1 0 ; 1 0 1 0 0 ; 0 1 0 0 0 ; 1 0 1 0 1 ; 1 0 0 1 0 ; 0 1 1 0 1 ; ]),
'_' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 1 1 1 1 1 ; ]),
'\'' => Bool.([0 0 0 0 0 ; 0 1 1 0 0 ; 0 1 1 1 0 ; 0 0 0 1 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; ]),
']' => Bool.([0 0 0 0 0 ; 0 1 1 1 0 ; 0 0 0 1 0 ; 0 0 0 1 0 ; 0 0 0 1 0 ; 0 0 0 1 0 ; 0 0 0 1 0 ; 0 1 1 1 0 ; ]),
'\\' => Bool.([1 0 1 0 1 ; 1 0 1 0 1 ; 1 0 0 1 0 ; 0 1 0 1 0 ; 0 1 0 1 0 ; 0 1 0 0 1 ; 0 0 1 0 1 ; 0 0 1 0 1 ; ]),
'~' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 1 0 0 0 ; 1 0 1 0 1 ; 0 0 0 1 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; ]),
# Greek
'Γ' => Bool.([0 0 0 0 0 ; 1 1 1 1 1 ; 1 0 0 0 0 ; 1 0 0 0 0 ; 1 0 0 0 0 ; 1 0 0 0 0 ; 1 0 0 0 0 ; 1 0 0 0 0 ; ]),
'Δ' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 0 1 0 1 0 ; 0 0 0 0 0 ; 1 0 0 0 1 ; 1 1 1 1 1 ; ]),
'Θ' => Bool.([0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 1 1 1 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'Ι' => Bool.([0 0 0 0 0 ; 0 0 1 0 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 1 1 1 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 0 1 0 0 ; ]),
'Λ' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 0 1 0 1 0 ; 0 0 0 0 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; ]),
'Ξ' => Bool.([0 0 0 0 0 ; 1 1 1 1 1 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 1 1 1 1 1 ; ]),
'Π' => Bool.([0 0 0 0 0 ; 1 1 1 1 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; ]),
'Σ' => Bool.([0 0 0 0 0 ; 1 1 1 1 1 ; 1 0 0 0 0 ; 0 1 0 0 0 ; 0 0 1 0 0 ; 0 1 0 0 0 ; 1 0 0 0 0 ; 1 1 1 1 1 ; ]),
'Φ' => Bool.([0 0 0 0 0 ; 0 0 1 0 0 ; 0 1 1 1 0 ; 1 0 1 0 1 ; 1 0 1 0 1 ; 1 0 1 0 1 ; 0 1 1 1 0 ; 0 0 1 0 0 ; ]),
'Ψ' => Bool.([0 0 0 0 0 ; 1 0 1 0 1 ; 1 0 1 0 1 ; 1 0 1 0 1 ; 1 0 1 0 1 ; 0 1 1 1 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; ]),
'Ω' => Bool.([0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 0 1 0 ; 1 1 0 1 1 ; ]),
'α' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 1 1 0 1 ; 1 0 0 1 0 ; 1 0 0 1 0 ; 1 0 0 1 0 ; 0 1 1 0 1 ; ]),
'β' => Bool.([0 0 0 0 0 ; 1 1 1 0 0 ; 1 0 0 1 0 ; 1 1 1 0 0 ; 1 1 1 1 0 ; 1 0 0 0 1 ; 1 1 1 1 0 ; 1 0 0 0 0 ; ]),
'γ' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 1 0 0 0 1 ; 0 1 0 1 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; ]),
'δ' => Bool.([0 0 0 0 0 ; 0 1 1 1 1 ; 0 0 1 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'ε' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 0 1 1 0 0 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'ζ' => Bool.([0 0 0 0 0 ; 1 1 1 1 1 ; 0 0 0 1 0 ; 0 0 1 0 0 ; 0 1 0 0 0 ; 0 0 1 1 0 ; 0 0 0 0 1 ; 0 0 0 1 0 ; ]),
'η' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 1 0 1 1 0 ; 1 1 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 0 0 0 1 ; ]),
'θ' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 1 1 1 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'ι' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 1 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 1 ; 0 0 0 1 0 ; ]),
'κ' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 1 0 0 1 0 ; 1 0 1 0 0 ; 1 1 0 0 0 ; 1 0 1 0 0 ; 1 0 0 1 0 ; ]),
'λ' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 1 0 0 0 0 ; 0 1 0 0 0 ; 0 0 1 0 0 ; 0 1 0 1 0 ; 1 0 0 0 1 ; ]),
'μ' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 1 1 ; 1 1 1 0 1 ; 1 0 0 0 0 ; ]),
'ν' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 0 1 0 ; 0 0 1 0 0 ; ]),
'ξ' => Bool.([0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 0 ; 0 1 1 0 1 ; 1 0 0 0 0 ; 0 1 1 1 0 ; 0 0 0 0 1 ; 0 0 0 1 0 ; ]),
'ο' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'π' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 1 1 1 1 1 ; 0 1 0 1 0 ; 0 1 0 1 0 ; 0 1 0 1 0 ; 0 1 0 1 0 ; ]),
'ρ' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 1 1 1 0 ; 1 0 0 0 0 ; 1 0 0 0 0 ; ]),
'σ' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 1 1 1 1 ; 1 0 0 1 0 ; 1 0 0 1 0 ; 1 0 0 1 0 ; 0 1 1 0 0 ; ]),
'τ' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 1 1 1 1 1 ; 0 1 0 0 0 ; 0 1 0 0 0 ; 0 1 0 1 0 ; 0 0 1 0 0 ; ]),
'υ' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 1 0 0 1 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'φ' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 1 0 0 1 0 ; 1 0 1 0 1 ; 1 0 1 0 1 ; 0 1 1 1 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; ]),
'χ' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 1 1 0 0 1 ; 0 0 1 1 0 ; 0 0 1 0 0 ; 0 1 1 0 0 ; 1 0 0 1 1 ; ]),
'ψ' => Bool.([0 0 0 0 0 ; 0 0 1 0 0 ; 1 0 1 0 1 ; 1 0 1 0 1 ; 1 0 1 0 1 ; 0 1 1 1 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; ]),
'ω' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 1 0 1 0 ; 1 0 0 0 1 ; 1 0 1 0 1 ; 1 0 1 0 1 ; 0 1 0 1 0 ; ]),
'ά' => Bool.([0 0 0 1 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 0 1 1 0 1 ; 1 0 0 1 0 ; 1 0 0 1 0 ; 1 0 0 1 0 ; 0 1 1 0 1 ; ]),
'έ' => Bool.([0 0 0 1 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 0 1 1 0 0 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'ή' => Bool.([0 0 0 1 0 ; 0 0 1 0 0 ; 1 0 1 1 0 ; 1 1 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 0 0 0 1 ; ]),
'ί' => Bool.([0 0 0 1 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 0 1 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 1 ; 0 0 0 1 0 ; ]),
'ό' => Bool.([0 0 0 1 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'ύ' => Bool.([0 0 0 1 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 1 0 0 1 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'ώ' => Bool.([0 0 0 1 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 0 1 0 1 0 ; 1 0 0 0 1 ; 1 0 1 0 1 ; 1 0 1 0 1 ; 0 1 0 1 0 ; ]),
# Accented Vowels
'Á' => Bool.([0 0 0 1 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 1 1 1 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; ]),
'É' => Bool.([0 0 0 1 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 1 1 1 1 1 ; 1 0 0 0 0 ; 1 1 1 1 0 ; 1 0 0 0 0 ; 1 1 1 1 1 ; ]),
'Í' => Bool.([0 0 0 1 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 1 1 1 0 ; ]),
'Ó' => Bool.([0 0 0 1 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'Ú' => Bool.([0 0 0 1 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'Ý' => Bool.([0 0 0 1 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 1 0 0 0 1 ; 0 1 0 1 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; ]),
'á' => Bool.([0 0 0 1 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 0 0 0 0 1 ; 0 1 1 1 1 ; 1 0 0 0 1 ; 0 1 1 1 1 ; ]),
'é' => Bool.([0 0 0 1 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 1 1 1 1 ; 1 0 0 0 0 ; 0 1 1 1 0 ; ]),
'í' => Bool.([0 0 0 1 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 0 1 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 1 1 1 0 ; ]),
'ó' => Bool.([0 0 0 1 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'ú' => Bool.([0 0 0 1 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 1 1 ; 0 1 1 0 1 ; ]),
'ý' => Bool.([0 0 0 1 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 1 0 0 0 1 ; 0 1 0 0 1 ; 0 0 1 1 0 ; 0 0 1 0 0 ; 1 1 0 0 0 ; ]),
'À' => Bool.([0 1 0 0 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 1 1 1 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; ]),
'È' => Bool.([0 1 0 0 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 1 1 1 1 1 ; 1 0 0 0 0 ; 1 1 1 1 0 ; 1 0 0 0 0 ; 1 1 1 1 1 ; ]),
'Ì' => Bool.([0 1 0 0 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 1 1 1 0 ; ]),
'Ò' => Bool.([0 1 0 0 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'Ù' => Bool.([0 1 0 0 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'à' => Bool.([0 1 0 0 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 0 0 0 0 1 ; 0 1 1 1 1 ; 1 0 0 0 1 ; 0 1 1 1 1 ; ]),
'è' => Bool.([0 1 0 0 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 1 1 1 1 ; 1 0 0 0 0 ; 0 1 1 1 0 ; ]),
'ì' => Bool.([0 1 0 0 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 0 1 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 1 1 1 0 ; ]),
'ò' => Bool.([0 1 0 0 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'ù' => Bool.([0 1 0 0 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 1 1 ; 0 1 1 0 1 ; ]),
'Â' => Bool.([0 0 1 0 0 ; 0 1 0 1 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 1 1 1 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; ]),
'Ê' => Bool.([0 0 1 0 0 ; 0 1 0 1 0 ; 0 0 0 0 0 ; 1 1 1 1 1 ; 1 0 0 0 0 ; 1 1 1 1 0 ; 1 0 0 0 0 ; 1 1 1 1 1 ; ]),
'Î' => Bool.([0 0 1 0 0 ; 0 1 0 1 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 1 1 1 0 ; ]),
'Ô' => Bool.([0 0 1 0 0 ; 0 1 0 1 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'Û' => Bool.([0 0 1 0 0 ; 0 1 0 1 0 ; 0 0 0 0 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'â' => Bool.([0 0 1 0 0 ; 0 1 0 1 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 0 0 0 0 1 ; 0 1 1 1 1 ; 1 0 0 0 1 ; 0 1 1 1 1 ; ]),
'ê' => Bool.([0 0 1 0 0 ; 0 1 0 1 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 1 1 1 1 ; 1 0 0 0 0 ; 0 1 1 1 0 ; ]),
'î' => Bool.([0 0 1 0 0 ; 0 1 0 1 0 ; 0 0 0 0 0 ; 0 1 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 1 1 1 0 ; ]),
'ô' => Bool.([0 0 1 0 0 ; 0 1 0 1 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'û' => Bool.([0 0 1 0 0 ; 0 1 0 1 0 ; 0 0 0 0 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 1 1 ; 0 1 1 0 1 ; ]),
'Ä' => Bool.([0 0 0 0 0 ; 0 1 0 1 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 1 1 1 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; ]),
'Ë' => Bool.([0 0 0 0 0 ; 0 1 0 1 0 ; 0 0 0 0 0 ; 1 1 1 1 1 ; 1 0 0 0 0 ; 1 1 1 1 0 ; 1 0 0 0 0 ; 1 1 1 1 1 ; ]),
'Ï' => Bool.([0 0 0 0 0 ; 0 1 0 1 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 1 1 1 0 ; ]),
'Ö' => Bool.([0 0 0 0 0 ; 0 1 0 1 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'Ü' => Bool.([0 0 0 0 0 ; 0 1 0 1 0 ; 0 0 0 0 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'Ÿ' => Bool.([0 0 0 0 0 ; 0 1 0 1 0 ; 0 0 0 0 0 ; 1 0 0 0 1 ; 0 1 0 1 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; ]),
'ä' => Bool.([0 0 0 0 0 ; 0 1 0 1 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 0 0 0 0 1 ; 0 1 1 1 1 ; 1 0 0 0 1 ; 0 1 1 1 1 ; ]),
'ë' => Bool.([0 0 0 0 0 ; 0 1 0 1 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 1 1 1 1 ; 1 0 0 0 0 ; 0 1 1 1 0 ; ]),
'ï' => Bool.([0 0 0 0 0 ; 0 1 0 1 0 ; 0 0 0 0 0 ; 0 1 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 1 1 1 0 ; ]),
'ö' => Bool.([0 0 0 0 0 ; 0 1 0 1 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'ü' => Bool.([0 0 0 0 0 ; 0 1 0 1 0 ; 0 0 0 0 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 1 1 ; 0 1 1 0 1 ; ]),
'ÿ' => Bool.([0 0 0 0 0 ; 0 1 0 1 0 ; 0 0 0 0 0 ; 1 0 0 0 1 ; 0 1 0 0 1 ; 0 0 1 1 0 ; 0 0 1 0 0 ; 1 1 0 0 0 ; ]),
'Ã' => Bool.([0 1 1 0 1 ; 1 0 1 1 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 1 1 1 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; ]),
'Ñ' => Bool.([0 1 1 0 1 ; 1 0 1 1 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 1 1 1 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; ]),
'Õ' => Bool.([0 1 1 0 1 ; 1 0 1 1 0 ; 0 0 0 0 0 ; 1 0 0 0 1 ; 1 1 0 0 1 ; 1 0 1 0 1 ; 1 0 0 1 1 ; 1 0 0 0 1 ; ]),
'ã' => Bool.([0 1 1 0 1 ; 1 0 1 1 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 0 0 0 0 1 ; 0 1 1 1 1 ; 1 0 0 0 1 ; 0 1 1 1 1 ; ]),
'ñ' => Bool.([0 1 1 0 1 ; 1 0 1 1 0 ; 0 0 0 0 0 ; 1 0 1 1 0 ; 1 1 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; ]),
'õ' => Bool.([0 1 1 0 1 ; 1 0 1 1 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
# Accented Consonants and Other Foreign Characters
'Ç' => Bool.([0 0 0 0 0 ; 0 1 1 1 1 ; 1 0 0 0 0 ; 1 0 0 0 0 ; 1 0 0 0 0 ; 0 1 1 1 1 ; 0 0 1 0 0 ; 0 1 0 0 0 ; ]),
'ç' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 1 1 1 1 ; 1 0 0 0 0 ; 1 0 0 0 0 ; 0 1 1 1 1 ; 0 0 1 0 0 ; 0 1 0 0 0 ; ]),
'Œ' => Bool.([0 0 0 0 0 ; 0 1 1 1 1 ; 1 0 1 0 0 ; 1 0 1 0 0 ; 1 0 1 1 0 ; 1 0 1 0 0 ; 1 0 1 0 0 ; 0 1 1 1 1 ; ]),
'œ' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 1 0 1 0 ; 1 0 1 0 1 ; 1 0 1 1 1 ; 1 0 1 0 0 ; 0 1 0 1 1 ; ]),
'Æ' => Bool.([0 0 0 0 0 ; 0 1 1 1 1 ; 1 0 1 0 0 ; 1 0 1 0 0 ; 1 1 1 1 0 ; 1 0 1 0 0 ; 1 0 1 0 0 ; 1 0 1 1 1 ; ]),
'æ' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 1 1 0 1 0 ; 0 0 1 0 1 ; 0 1 1 1 1 ; 1 0 1 0 0 ; 1 1 1 1 1 ; ]),
'ß' => Bool.([0 0 0 0 0 ; 0 1 1 0 0 ; 1 0 0 1 0 ; 1 0 1 0 0 ; 1 0 1 1 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 1 1 0 ; ]),
'º' => Bool.([0 0 0 0 0 ; 0 1 1 1 0 ; 0 1 0 1 0 ; 0 1 1 1 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; ]),
'ª' => Bool.([0 0 0 0 0 ; 0 0 1 1 0 ; 0 1 0 1 0 ; 0 0 1 1 0 ; 0 0 0 0 0 ; 0 1 1 1 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; ]),
'ø' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 1 1 1 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 1 1 1 0 ; ]),
'Ø' => Bool.([0 0 0 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 0 0 1 1 ; 1 0 1 0 1 ; 1 1 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'Å' => Bool.([0 0 1 0 0 ; 0 1 0 1 0 ; 0 0 1 0 0 ; 0 1 1 1 0 ; 1 0 0 0 1 ; 1 1 1 1 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; ]),
'å' => Bool.([0 0 1 0 0 ; 0 1 0 1 0 ; 0 0 1 0 0 ; 0 1 1 1 0 ; 0 0 0 0 1 ; 0 1 1 1 1 ; 1 0 0 0 1 ; 0 1 1 1 1 ; ]),
'Þ' => Bool.([0 0 0 0 0 ; 1 0 0 0 0 ; 1 1 1 1 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 1 1 1 0 ; 1 0 0 0 0 ; ]),
'þ' => Bool.([0 0 0 0 0 ; 1 0 0 0 0 ; 1 0 1 1 0 ; 1 1 0 0 1 ; 1 0 0 0 1 ; 1 1 0 0 1 ; 1 0 1 1 0 ; 1 0 0 0 0 ; ]),
'Ð' => Bool.([0 0 0 0 0 ; 0 1 1 1 0 ; 0 1 0 0 1 ; 0 1 0 0 1 ; 1 1 1 0 1 ; 0 1 0 0 1 ; 0 1 0 0 1 ; 0 1 1 1 0 ; ]),
'ð' => Bool.([0 0 0 0 0 ; 0 1 0 1 0 ; 0 0 1 0 0 ; 0 1 0 1 0 ; 0 0 1 1 1 ; 0 1 0 0 1 ; 0 1 0 0 1 ; 0 0 1 1 0 ; ]),
'Š' => Bool.([0 1 0 1 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 0 1 1 1 1 ; 1 0 0 0 0 ; 0 1 1 1 0 ; 0 0 0 0 1 ; 1 1 1 1 0 ; ]),
'š' => Bool.([0 1 0 1 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 0 1 1 1 1 ; 1 0 0 0 0 ; 0 1 1 1 0 ; 0 0 0 0 1 ; 1 1 1 1 0 ; ]),
'Ž' => Bool.([0 1 0 1 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 1 1 1 1 1 ; 0 0 0 1 0 ; 0 0 1 0 0 ; 0 1 0 0 0 ; 1 1 1 1 1 ; ]),
'ž' => Bool.([0 1 0 1 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 1 1 1 1 1 ; 0 0 0 1 0 ; 0 0 1 0 0 ; 0 1 0 0 0 ; 1 1 1 1 1 ; ]),
# Devanagari
'क' => Bool.([1 1 1 1 1;  0 0 1 0 0; 1 1 1 1 1; 1 0 1 0 1; 1 0 1 0 1; 1 1 1 0 1; 0 0 1 0 0; 0 0 1 0 0]),
'ख' => Bool.([1 1 1 1 1; 0 1 0 0 1; 0 1 1 1 1; 1 0 1 0 1; 1 0 1 1 1; 0 1 0 0 1; 0 0 1 0 1; 0 0 0 0 0 ]),
'ग' => Bool.([1 1 1 1 1; 0 1 0 1 0; 0 1 0 1 0; 1 1 0 1 0; 1 1 0 1 0; 0 0 0 1 0; 0 0 0 1 0; 0 0 0 1 0; ]),
'घ' => Bool.([1 1 1 1 1; 1 0 0 1 0; 1 0 0 1 0; 0 1 0 1 0; 1 0 0 1 0; 1 1 1 1 0; 0 0 0 1 0; 0 0 0 1 0; ]),
'ङ' => Bool.([1 1 1 1 1; 0 0 0 1 0; 0 1 1 1 0; 0 1 0 0 1; 0 1 1 1 0; 0 0 0 1 0; 0 1 1 1 0; 0 0 0 0 0; ]),
'च' => Bool.([1 1 1 1 1; 0 0 0 1 0; 0 0 0 1 0; 1 1 0 1 0; 0 1 0 1 0; 0 0 1 1 0; 0 0 0 1 0; 0 0 0 1 0; ]),
'छ' => Bool.([1 1 1 1 0; 1 0 0 0 0; 1 0 0 0 0; 0 1 0 0 0; 1 0 0 1 1; 1 0 0 1 0; 0 1 1 1 0; 0 0 0 0 0; ]),
'ज' => Bool.([1 1 1 1 1; 0 0 0 0 1; 0 0 0 0 1; 1 0 1 1 1; 1 0 1 0 1; 1 1 1 0 1; 0 0 0 0 1; 0 0 0 0 1; ]),
'झ' => Bool.([1 1 1 1 1; 0 0 1 0 1; 1 1 1 0 1; 1 0 0 0 1; 1 1 1 0 1; 0 0 1 1 1; 1 1 1 0 1; 1 0 0 0 1; ]),
'ञ' => Bool.([1 1 1 1 1; 0 0 0 1 0; 1 1 0 1 0; 0 1 0 1 0; 0 1 1 1 0; 0 1 0 1 0; 1 1 0 1 0; 0 0 0 1 0; ]),
'ट' => Bool.([1 1 1 1 1; 0 0 0 1 0; 1 1 1 1 0; 1 0 0 0 0; 1 0 0 0 0; 1 0 0 0 0; 1 1 1 1 0; 0 0 0 0 0; ]),
'ठ' => Bool.([1 1 1 1 1; 0 0 1 0 0; 1 1 1 1 1; 1 0 0 0 1; 1 0 0 0 1; 1 0 0 0 1; 1 1 1 1 1; 0 0 0 0 0]),
'ड' => Bool.([1 1 1 1 1; 0 0 0 1 0; 0 1 1 1 0; 0 1 0 0 0; 0 1 1 1 0; 0 0 0 1 0; 0 1 1 1 0; 0 0 0 0 0; ]),
'ढ' => Bool.([1 1 1 1 1; 0 0 0 1 0; 1 1 1 1 0; 1 0 0 0 0; 1 0 1 1 0; 1 0 1 1 0; 1 0 0 1 0; 1 1 1 1 0; ]),
'ण' => Bool.([1 1 1 1 1; 1 0 1 0 1; 1 0 1 0 1; 1 0 1 0 1; 1 1 1 0 1; 0 0 0 0 1; 0 0 0 0 1; 0 0 0 0 1; ]),
'त' => Bool.([1 1 1 1 1; 0 0 0 1 0; 0 0 0 1 0; 0 0 0 1 0; 1 1 1 1 0; 1 0 0 1 0; 1 0 0 1 0; 1 0 0 1 0; ]),
'थ' => Bool.([1 1 0 1 1; 1 1 0 1 0; 0 1 0 1 0; 0 1 0 1 0; 1 0 0 1 0; 0 1 1 1 0; 0 0 0 1 0; 0 0 0 1 0; ]),
'द' => Bool.([1 1 1 1 1; 0 0 1 0 0; 1 1 1 0 0; 1 0 0 0 0; 1 0 1 1 0; 1 0 1 1 0; 1 1 1 1 0; 0 0 1 0 0; ]),
'ध' => Bool.([1 1 0 1 1; 1 1 0 1 0; 1 0 0 1 0; 0 1 0 1 0; 1 0 0 1 0; 1 1 1 1 0; 0 0 0 1 0; 0 0 0 1 0; ]),
'न' => Bool.([1 1 1 1 1; 0 0 0 1 0; 0 0 0 1 0; 1 1 1 1 0; 1 0 0 1 0; 0 0 0 1 0; 0 0 0 1 0; 0 0 0 1 0; ]),
'प' => Bool.([1 1 1 1 1; 0 1 0 1 0; 0 1 0 1 0; 0 1 0 1 0; 0 1 1 1 0; 0 0 0 1 0; 0 0 0 1 0; 0 0 0 1 0; ]),
'फ' => Bool.([1 1 1 1 1; 1 0 1 0 0; 1 0 1 0 0; 1 0 1 0 0; 1 1 1 1 1; 0 0 1 0 1; 0 0 1 0 1; 0 0 1 0 0; ]),
'ब' => Bool.([1 1 1 1 1; 0 0 0 1 0; 0 1 1 1 0; 0 1 0 1 0; 0 1 1 1 0; 0 1 0 1 0; 0 1 1 1 0; 0 0 0 1 0; ]),
'भ' => Bool.([1 1 0 1 1; 1 1 0 1 0; 0 1 0 1 0; 0 1 0 1 0; 1 1 1 1 0; 1 1 0 1 0; 0 0 0 1 0; 0 0 0 1 0; ]),
'म' => Bool.([1 1 1 1 1; 0 1 0 1 0; 0 1 0 1 0; 0 1 0 1 0; 1 1 1 1 0; 1 1 0 1 0; 0 0 0 1 0; 0 0 0 1 0; ]),
'य'=> Bool.([1 1 1 1 1; 0 1 0 1 0; 0 1 0 1 0; 0 1 0 1 0; 1 0 0 1 0; 0 1 1 1 0; 0 0 0 1 0; 0 0 0 1 0; ]),
'र' => Bool.([1 1 1 1 1; 0 0 1 0 0; 0 0 1 0 0; 1 1 0 0 0; 1 0 0 0 0; 0 1 0 0 0; 0 0 1 0 0; 0 0 0 1 0; ]),
'ल' => Bool.([1 1 1 1 1; 0 0 0 0 1; 0 0 0 0 1; 1 1 1 1 1; 1 0 1 0 1; 1 0 1 0 1; 1 0 0 0 1; 1 0 0 0 1; ]),
'व' => Bool.([1 1 1 1 1; 0 0 0 1 0; 0 1 1 1 0; 0 1 0 1 0; 0 1 0 1 0; 0 1 1 1 0; 0 0 0 1 0; 0 0 0 1 0; ]),
'श' => Bool.([1 1 1 1 1; 1 1 0 0 1; 1 1 0 0 1; 0 1 0 0 1; 1 1 0 0 1; 1 0 0 0 1; 0 1 0 0 1; 0 0 1 0 1; ]),
'ष' => Bool.([1 1 1 1 1; 0 1 0 1 0; 0 1 1 1 0; 0 1 0 1 0; 0 1 1 1 0; 0 0 0 1 0; 0 0 0 1 0; 0 0 0 1 0; ]),
'स' => Bool.([1 1 1 1 1; 0 1 0 0 1; 0 1 0 0 1; 1 1 1 1 1; 1 0 0 0 1; 0 1 0 0 1; 0 0 1 0 1; 0 0 0 0 0; ]),
'ह' => Bool.([1 1 1 1 1; 0 0 0 1 0; 0 1 1 1 0; 0 1 0 0 0; 0 1 1 1 0; 0 1 0 1 0; 0 1 0 0 0; 0 1 1 1 0; ]),
# Matras for Devanagari (मात्रा )
'ऀ' => Bool.([0 1 1 1 0; 0 1 0 1 0; 0 0 1 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; ]),
'ँ' => Bool.([0 0 1 0 0; 0 1 0 1 0; 0 1 1 1 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; ]),
'ं' => Bool.([0 0 1 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; ]),
'ः' => Bool.([0 0 0 0 0; 0 0 0 0 0; 0 0 0 1 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 1 0; 0 0 0 0 0; 0 0 0 0 0; ]),
'ऺ' => Bool.([0 0 0 0 0; 0 0 1 0 0; 0 0 1 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; ]),
'ऻ' => Bool.([0 0 0 1 0; 0 0 0 0 0; 0 0 1 1 1; 0 0 0 1 0; 0 0 0 1 0; 0 0 0 1 0; 0 0 0 1 0; 0 0 0 1 0; ]),
'़' => Bool.([0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 1 0; 0 0 0 0 0; ]),
'ऽ' => Bool.([0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 1 0 1 1 0; 1 0 1 0 0; 1 1 1 0 0; 0 0 0 0 0; ]),
'ा' => Bool.([0 0 0 0 0; 0 0 0 0 0; 0 0 1 1 1; 0 0 0 1 0; 0 0 0 1 0; 0 0 0 1 0; 0 0 0 1 0; 0 0 0 1 0; ]),
'ि' => Bool.([0 1 1 1 0; 0 1 0 1 0; 1 1 1 1 1; 0 1 0 0 0; 0 1 0 0 0; 0 1 0 0 0; 0 1 0 0 0; 0 0 0 0 0; ]),
'ु' => Bool.([0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 1 1 0; 0 0 0 1 0; 0 1 1 1 0; ]),
'ू' => Bool.([0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 1 1 0 0; 0 1 0 0 0; 0 1 1 1 0; ]),
'ृ' => Bool.([0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 1 1 0 0; 0 1 0 0 0; 0 1 1 0 0; ]),
'ॅ' => Bool.([0 0 0 0 0; 0 1 0 1 0; 0 1 1 1 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; ]),
'ै' => Bool.([0 1 0 1 0; 0 1 0 1 0; 1 1 1 1 1; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; ]),
'े' => Bool.([0 0 1 0 0; 0 0 1 0 0; 1 1 1 1 1; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; ]),
'ॉ' => Bool.([0 0 1 0 1; 0 0 1 1 1; 0 0 0 0 0; 0 0 1 1 1; 0 0 0 1 0; 0 0 0 1 0; 0 0 0 1 0; 0 0 0 1 0; ]),
'ो' => Bool.([ 0 0 1 0 0; 0 0 0 1 0; 0 1 1 1 1; 0 0 0 1 0; 0 0 0 1 0; 0 0 0 1 0; 0 0 0 1 0; 0 0 0 1 0; ]),
'ौ' => Bool.([ 0 1 0 1 0; 0 0 1 0 1; 0 1 1 1 1; 0 0 0 1 0; 0 0 0 1 0; 0 0 0 1 0; 0 0 0 1 0; 0 0 0 1 0; ]),
'्' => Bool.([0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0;  0 0 1 0 0; 0 0 0 1 0; 0 0 0 0 0; ]),
'ॎ' => Bool.([1 1 0 0 0; 1 0 0 0 0; 1 1 1 0 0; 1 0 0 0 0; 1 0 0 0 0; 1 0 0 0 0; 1 0 0 0 0; 1 0 0 0 0; ]),
'।' => Bool.([0 0 0 0 0; 1 0 0 0 0; 1 0 0 0 0; 1 0 0 0 0; 1 0 0 0 0; 1 0 0 0 0; 1 0 0 0 0; 0 0 0 0 0; ]),
'॥' => Bool.([0 0 0 0 0; 1 0 1 0 0; 1 0 1 0 0; 1 0 1 0 0; 1 0 1 0 0; 1 0 1 0 0; 1 0 1 0 0; 0 0 0 0 0; ]),
'०' => Bool.([0 1 1 1 0; 0 1 0 1 0; 0 1 1 1 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; ]),
'ॖ' => Bool.([0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 1 1 0 0; 0 0 0 0 0; ]),
'ॗ' => Bool.([0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; 0 1 1 0 0; 0 0 0 0 0; 0 1 1 0 0; ]),
'ॕ' => Bool.([0 1 0 1 0; 0 1 1 1 0; 0 0 0 0 0; 0 1 1 1 0; 0 0 0 0 0 ; 0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0; ]),
# Foreign Punctuation
'¿' => Bool.([0 0 0 0 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 0 0 1 0 0 ; 0 1 0 0 0 ; 1 0 0 0 0 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'¡' => Bool.([0 0 0 0 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; ]),
'«' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 1 0 1 ; 0 1 0 1 0 ; 1 0 1 0 0 ; 0 1 0 1 0 ; 0 0 1 0 1 ; 0 0 0 0 0 ; ]),
'»' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 1 0 1 0 0 ; 0 1 0 1 0 ; 0 0 1 0 1 ; 0 1 0 1 0 ; 1 0 1 0 0 ; 0 0 0 0 0 ; ]),
#Arabic
'أ' => Bool.([0 1 1 0 0 ; 0 1 1 0 0 ; 0 0 0 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; ]),
'ب' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 1 0 0 0 1 ; 1 1 1 1 1 ; 0 0 0 0 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; ]),
'ج' => Bool.([0 0 0 0 0 ; 1 1 1 1 1 ; 0 1 1 0 0 ; 1 0 0 0 0 ; 1 0 1 0 0 ; 1 0 0 0 1 ; 0 1 1 1 0 ; 0 0 0 0 0 ; ]),
'د' => Bool.([0 0 0 0 0 ; 0 0 0 1 0 ; 0 0 0 0 1 ; 0 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; ]),
'ه' => Bool.([0 0 0 0 0 ; 0 0 1 0 0 ; 0 1 1 1 0 ; 1 0 0 1 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; ]),
'و' => Bool.([0 0 0 0 0 ; 0 0 1 0 0 ; 0 1 0 1 0 ; 0 0 1 1 0 ; 1 0 0 1 0 ; 0 1 1 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; ]),
'ز' => Bool.([0 0 0 0 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 0 0 0 1 0 ; 0 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; 0 0 0 0 0 ; ]),
'ح' => Bool.([0 0 0 0 0 ; 1 1 1 1 1 ; 0 1 1 0 0 ; 1 0 0 0 0 ; 1 0 0 0 0 ; 1 0 0 0 1 ; 0 1 1 1 0 ; 0 0 0 0 0 ; ]),
'ط' => Bool.([0 1 0 0 0 ; 0 1 1 0 0 ; 0 1 0 0 0 ; 0 1 0 0 0 ; 0 1 1 1 0 ; 0 1 0 0 1 ; 1 1 1 1 1 ; 0 0 0 0 0 ; ]),
'ي' => Bool.([0 0 0 1 1 ; 1 0 1 0 0 ; 1 0 0 1 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; 0 0 0 0 0 ; 0 1 1 0 0 ; 0 0 0 0 0 ; ]),
'ك' => Bool.([0 0 0 1 1 ; 0 0 0 0 1 ; 0 0 0 0 1 ; 0 0 1 0 1 ; 1 0 0 0 1 ; 0 1 1 1 1 ; 0 0 0 0 0 ; 0 0 0 0 0 ; ]),
'ل' => Bool.([0 0 0 0 0 ; 0 0 0 0 1 ; 0 0 0 0 1 ; 0 0 0 0 1 ; 0 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 1 0 ; 0 1 1 0 0 ; ]),
'م' => Bool.([0 0 0 0 0 ; 0 0 1 0 0 ; 0 1 0 1 0 ; 0 0 0 1 0 ; 1 1 1 1 1 ; 1 0 0 0 0 ; 0 1 0 0 0 ; 0 1 0 0 0 ; ]),
'ن' => Bool.([0 0 0 0 0 ; 0 0 1 0 0 ; 0 0 0 0 1 ; 0 0 0 0 1 ; 0 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'س' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 1 0 1 1 1 ; 1 0 1 1 1 ; 1 1 1 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; ]),
'ع' => Bool.([0 0 0 0 0 ; 1 1 0 0 0 ; 1 0 0 0 0 ; 1 1 1 0 0 ; 0 1 0 0 0 ; 1 0 0 0 0 ; 1 0 0 0 0 ; 0 1 1 1 1 ; ]),
'ف' => Bool.([0 0 0 0 0 ; 0 0 0 1 0 ; 0 0 0 0 0 ; 0 0 0 1 1 ; 1 0 0 1 1 ; 0 1 1 1 1 ; 0 0 0 0 0 ; 0 0 0 0 0 ; ]),
'ص' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 1 1 1 ; 1 1 1 1 1 ; 1 1 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; ]),
'ق' => Bool.([0 0 0 1 1 ; 0 0 0 0 0 ; 0 0 0 1 0 ; 0 0 1 0 1 ; 0 0 0 1 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'ر' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 1 0 ; 0 0 0 0 1 ; 0 0 0 0 1 ; 0 1 1 1 0 ; 0 0 0 0 0 ; ]),
'ش' => Bool.([0 0 0 0 0 ; 0 0 0 1 0 ; 0 0 0 1 1 ; 0 0 0 0 0 ; 1 0 1 1 1 ; 1 0 1 1 1 ; 1 1 1 0 0 ; 0 0 0 0 0 ; ]),
'ت' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 1 0 1 0 ; 0 0 0 0 0 ; 1 0 0 0 1 ; 0 1 1 1 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; ]),
'ث' => Bool.([0 0 0 0 0 ; 0 0 1 0 0 ; 0 1 0 1 0 ; 0 0 0 0 0 ; 1 0 0 0 1 ; 0 1 1 1 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; ]),
'خ' => Bool.([0 0 1 0 0 ; 0 0 0 0 0 ; 1 1 1 1 1 ; 0 1 1 0 0 ; 1 0 0 0 0 ; 1 0 0 0 0 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'ذ' => Bool.([0 0 0 0 0 ; 0 0 0 1 0 ; 0 0 0 0 0 ; 0 0 0 1 0 ; 0 0 0 1 0 ; 0 0 0 1 0 ; 0 1 1 1 0 ; 0 0 0 0 0 ; ]),
'ض' => Bool.([0 0 0 0 0 ; 0 0 0 1 0 ; 0 0 0 0 0 ; 0 0 0 1 0 ; 1 0 1 0 1 ; 1 1 1 1 1 ; 0 0 0 0 0 ; 0 0 0 0 0 ; ]),
'ظ' => Bool.([0 0 0 0 0 ; 0 1 0 0 0 ; 0 1 0 1 0 ; 0 1 0 0 0 ; 0 1 1 1 0 ; 0 1 0 0 1 ; 1 1 1 1 1 ; 0 0 0 0 0 ; ]),
'غ' => Bool.([0 1 0 0 0 ; 0 0 0 0 0 ; 0 1 1 0 0 ; 1 0 0 0 0 ; 0 1 1 0 0 ; 1 0 0 0 0 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'٠' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; ]),
'١' => Bool.([0 0 0 0 0 ; 0 1 0 0 0 ; 0 1 1 0 0 ; 0 0 1 0 0 ; 0 0 1 1 0 ; 0 0 0 1 0 ; 0 0 0 1 0 ; 0 0 0 0 0 ; ]),
'٢' => Bool.([0 0 0 0 0 ; 1 0 0 0 1 ; 0 1 0 1 0 ; 0 1 1 0 0 ; 0 0 1 0 0 ; 0 0 1 1 0 ; 0 0 0 1 0 ; 0 0 0 1 0 ; ]),
'٣' => Bool.([1 0 1 0 1 ; 1 0 1 0 1 ; 1 1 0 1 0 ; 0 1 0 0 0 ; 0 1 1 0 0 ; 0 0 1 0 0 ; 0 0 1 1 0 ; 0 0 0 1 0 ; ]),
'٤' => Bool.([0 0 1 1 0 ; 0 1 0 0 0 ; 1 0 0 0 0 ; 0 1 1 1 0 ; 0 1 1 0 0 ; 1 0 0 0 0 ; 1 0 0 0 1 ; 0 1 1 1 0 ; ]),
'٥' => Bool.([0 0 0 0 0 ; 0 0 1 0 0 ; 0 1 0 1 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 1 1 0 ; 0 0 0 0 0 ; ]),
'٦' => Bool.([0 0 0 0 0 ; 1 0 1 0 0 ; 0 1 1 0 0 ; 0 0 1 0 0 ; 0 0 1 1 0 ; 0 0 0 1 0 ; 0 0 0 0 1 ; 0 0 0 0 0 ; ]),
'٧' => Bool.([0 0 0 0 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 1 0 1 0 ; 0 1 0 1 0 ; 0 1 0 1 0 ; 0 0 1 0 0 ; 0 0 0 0 0 ; ]),
'٨' => Bool.([0 0 0 0 0 ; 0 0 1 0 0 ; 0 1 0 1 0 ; 0 1 0 1 0 ; 0 1 0 1 0 ; 1 0 0 0 1 ; 1 0 0 0 1 ; 0 0 0 0 0 ; ]),
'٩' => Bool.([0 0 0 0 0 ; 0 1 0 0 0 ; 1 0 1 0 0 ; 0 1 1 0 0 ; 0 0 1 0 0 ; 0 0 1 0 0 ; 0 0 0 1 0 ; 0 0 0 0 0 ; ]),
'٫' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 1 1 0 0 ; 0 0 1 0 0 ; 0 1 0 0 0 ; ]),
'٬' => Bool.([0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 0 0 0 0 ; 0 1 1 0 0 ; 0 0 1 0 0 ; 0 1 0 0 0 ; ]));
end #module


text:
# NB: ϕ is scaled by √2π compared to standard definition
ϕ(x) = exp(-x^2/2)
ϕ(x,D) = exp((D-x)*(D+x)/2) # rescaling that can be used for improved numerical precision

# ϕ2(x) = (x*x-1)*ϕ(x) # For reference: used to find intervals where ϕ is convex or concave.

ϕ4(x) = (x2=x*x; (x2*x2 - 6x2 + 3) * ϕ(x))
# ϕ5(x) = (x2=x*x; x4=x2*x2; -x*(x4 - 10x2 + 15) * ϕ(x)) # For reference: used to find local extrema of ϕ4
ϕ6(x) = (x2=x*x; x4=x2*x2; (x4*x2 - 15x4 + 45x2 - 15) * ϕ(x))
# ϕ7(x) = (x2=x*x; x4=x2*x2; x6=x4*x2; x*(x6 - 21x4 + 105x2 - 105)*ϕ(x))                 # For reference: used to find local extrema of ϕ6
# ϕ8(x) = (x2=x*x; x4=x2*x2; x6=x4*x2; x8=x4*x4; (x8 - 28x6 + 210x4 - 420x2 + 105)*ϕ(x)) # For reference: used to find intervals where ϕ6 is convex or concave.

ϕ(::Val{4},x) = ϕ4(x)
ϕ(::Val{6},x) = ϕ6(x)

"""
	ϕbounds(a,b,x,D)

Let `x` be the mean of a set of points {xᵢ} such that xᵢ∈[`a`,`b`] ∀i.
ϕbounds computes lower and upper bounds for ∑ᵢϕ(xᵢ)/n, where n is the number of points.
`D` is a rescaling that can be used for better numerical precision. ϕ(x) := 1/√(2π) * exp(D^2/2 - x^2/2)
"""
function ϕbounds(a,b,x,D)::Tuple{Float64,Float64}
	if b-a < 1e-9 # to avoid div by zero
		(abs(x-a)<1e-9 && abs(b-x)<1e-9) || throw(ArgumentError("x=$x must be in interval [a=$a, b=$b]"))
		y = ϕ((a+b)/2,D)
		return y,y
	end
	0<=a<=x<=b || throw(ArgumentError("x=$x must be in interval [a=$a, b=$b]"))

	ya,yb = ϕ(a,D),ϕ(b,D)
	if a<1 && b>1 # not in an interval where the function is convex/concave.
		# TODO: improve fallback
		return yb,ya
	end

	# bound by line above (convex) or below (concave) ϕ in the interval
	y2 = (yb-ya)*((x-a)/(b-a)) + ya

	# lower bound (convex) or upper bound (concave) using Jensen's inqueality.
	y1 = ϕ(x,D)

	if b<=1 # concave
		y2, y1
	else # convex
		y1, y2
	end
end

"""
	ϕ4bounds(a,b,x)

Let `x` be the mean of a set of points {xᵢ} such that xᵢ∈[`a`,`b`] ∀i.
ϕ4bounds computes lower and upper bounds for ∑ᵢϕ⁽⁴⁾(xᵢ)/n, where n is the number of points.
"""
function ϕ4bounds(a,b,x)::Tuple{Float64,Float64}
	breakpoints = (0.6167065901925941, 1.8891758777537109, 3.3242574335521193)
	if b-a < 1e-9 # to avoid div by zero
		(abs(x-a)<1e-9 && abs(b-x)<1e-9) || throw(ArgumentError("x=$x must be in interval [a=$a, b=$b]"))
		y = ϕ4((a+b)/2)
		return y,y
	end
	0<=a<=x<=b || throw(ArgumentError("x=$x must be in interval [a=$a, b=$b]"))

	ai = (a>breakpoints[1]) + (a>breakpoints[2]) + (a>breakpoints[3])
	bi = (b>breakpoints[1]) + (b>breakpoints[2]) + (b>breakpoints[3])

	if ai!=bi # not in an interval where the function is convex/concave.
		# TODO: improve fallback.
		return ϕ4extrema(a,b)
	end

	ya,yb = ϕ4(a),ϕ4(b)

	# bound by line above (convex) or below (concave) ϕ4 in the interval
	y2 = (yb-ya)*((x-a)/(b-a)) + ya

	# lower bound (convex) or upper bound (concave) using Jensen's inqueality.
	y1 = ϕ4(x)

	if ai&1 == 0 # concave
		y2, y1
	else # convex
		y1, y2
	end
end

"""
	ϕ6bounds(a,b,x)

Let `x` be the mean of a set of points {xᵢ} such that xᵢ∈[`a`,`b`] ∀i.
ϕ6bounds computes lower and upper bounds for ∑ᵢϕ⁽⁶⁾(xᵢ)/n.
"""
function ϕ6bounds(a,b,x)::Tuple{Float64,Float64}
	breakpoints = (0.5390798113513751, 1.636519042435108, 2.8024858612875416, 4.144547186125894)
	if b-a < 1e-9 # to avoid div by zero
		(abs(x-a)<1e-9 && abs(b-x)<1e-9) || throw(ArgumentError("x=$x must be in interval [a=$a, b=$b]"))
		y = ϕ6((a+b)/2)
		return y,y
	end
	0<=a<=x<=b || throw(ArgumentError("x=$x must be in interval [a=$a, b=$b]"))

	ai = (a>breakpoints[1]) + (a>breakpoints[2]) + (a>breakpoints[3]) + (a>breakpoints[4])
	bi = (b>breakpoints[1]) + (b>breakpoints[2]) + (b>breakpoints[3]) + (b>breakpoints[4])

	if ai!=bi # not in an interval where the function is convex/concave.
		# TODO: improve fallback.
		return ϕ6extrema(a,b)
	end

	ya,yb = ϕ6(a),ϕ6(b)

	# bound by line above (convex) or below (concave) ϕ6 in the interval
	y2 = (yb-ya)*((x-a)/(b-a)) + ya

	# lower bound (convex) or upper bound (concave) using Jensen's inqueality.
	y1 = ϕ6(x)

	if ai&1 == 1 # concave
		y2, y1
	else # convex
		y1, y2
	end
end

ϕbounds(::Val{4},a,b,x) = ϕ4bounds(a,b,x)
ϕbounds(::Val{6},a,b,x) = ϕ6bounds(a,b,x)

function ϕ4extrema(a,b)
	# x at extrema: -√(5+√10), √(5-√10), 0, √(5-√10), √(5+√10)
	# we only need to consider x>0 since the function is even
	xExt = (1.355626179974266, 2.8569700138728056)
	yExt = (-1.8548702941445978, 0.34872676216452453)

	# the implementation assumes 0<=a<=b
	if b<=xExt[1] || a>=xExt[2]
		ϕ4(b),ϕ4(a) # decreasing function in these intervals
	elseif a>=xExt[1]
		if b<=xExt[2]
			ϕ4(a),ϕ4(b) # increasing function in this interval
		else
			min(ϕ4(a),ϕ4(b)),yExt[2] # the function covers a local maximum
		end
	elseif b<=xExt[2]
		yExt[1],max(ϕ4(a),ϕ4(b)) # the function covers the global minimum
	else
		yExt[1], max(ϕ4(a),yExt[2]) # the function covers the global minimum and a local maximum
	end
end

function ϕ6extrema(a,b)
	# ϕ6 has global minimum at 0, global maximum at x≈1.15, two more local extrema and ϕ6(x)→0 as x→∞.
	# we only need to consider x>0 since the function is even
	xExt = (1.154405394739968, 2.366759410734541, 3.750439717725742)
	yExt = (10.62963914930061, -3.513849978144481, 0.3821828509277058)
	# the implementation assumes 0<=a<=b
	if xExt[3]<=a || (xExt[1]<=a && b<=xExt[2])
		ϕ6(b),ϕ6(a) # decreasing function in these intervals
	elseif b<=xExt[1] || (xExt[2]<=a && b<=xExt[3])
		ϕ6(a),ϕ6(b) # increasing function in these intervals
	elseif b<=xExt[2]
		min(ϕ6(a),ϕ6(b)),yExt[1] # covers global maximum
	elseif xExt[1]<=a && b<=xExt[3]
		yExt[2],max(ϕ6(a),ϕ6(b)) # covers local minimum
	elseif xExt[2]<=a
		min(ϕ6(a),ϕ6(b)),yExt[3] # covers second local maximum
	elseif xExt[1]<=a
		yExt[2],max(ϕ6(a),yExt[3])  # covers the local minimum and the second local maximum
	# elseif b<=xExt[3] # same as last case
	# 	min(ϕ6(a),yExt[2]),yExt[1] # covers global maximum and local minimum
	else
		min(ϕ6(a),yExt[2]),yExt[1] # covers global maximum and local minimum
	end
end

ϕextrema(::Val{4},a,b) = ϕ4extrema(a,b)
ϕextrema(::Val{6},a,b) = ϕ6extrema(a,b)


text:
# The purpose of this script is to compute the empirical mean squared error of
# various algorithms for line spetral estimation for varying signal-to-noise
# ratios via Monte Carlo iterations.
#
# Parameters:
# N: number of measurements per snapshot
# N2: ambient dimension when N \leq N2 sparse measurements are used
# L: number of snapshots
# Niters: number of Monte Carlo iterations
# SNR_db: vector of signal-to-noise-ratios [dB] of the AWGN model
# f_true: location of the frequencies of the sinusoids

include("MUSIC.jl")
include("rootMUSIC.jl")
include("ESPRIT.jl")
include("TLSesprit.jl")
include("MPM.jl")
include("ANM.jl")
include("EMAC.jl")
include("periodogram.jl")
include("findMinWrapDist.jl")
include("findMinWrapDistUnique.jl")
include("WrapDist.jl")

using Plots, Random

N = 15
N2 = 30
L = 1
SNR_db = range(0,30,length=10)
Niters = 1
f_true = [0.3 0.35 0.40]

K = length(f_true)
MSE = zeros(length(SNR_db),3)

A(f,N) = exp.(2*pi*1im*kron(f,(0:N-1)))
SNR = 10 .^(SNR_db./10)

for jj = 1:Niters
        for ii = 1:length(SNR)
        noise = (randn(N,L)+1im*randn(N,L))/ (sqrt(2)*SNR[ii])
        omega = sort(randperm(N2)[1:N])
        S = exp.(2*pi*rand(K,L)*1im)

        Y1 = A(f_true,N)*S + noise
        Y2 = A(f_true,N2)[omega,:]*S + noise

        t0 = time_ns()
        f_MUSIC = MPM(Y1,K)
        t1 = (time_ns()-t0)/1e9
        MinWrapDist = findMinWrapDistUnique(f_true,f_MUSIC)
        MSE[ii,1] += norm(MinWrapDist)^2

        f_EMAC = EMAC(Y2,N2,K,omega,1/SNR[ii])
        MinWrapDist = findMinWrapDistUnique(f_true,f_EMAC)
        MSE[ii,2] += norm(MinWrapDist)^2

        f_ANM = ANM(Y2,N2,K,omega,1/SNR[ii])
        MinWrapDist = findMinWrapDistUnique(f_true,f_ANM)
        MSE[ii,3] += norm(MinWrapDist)^2

    end
end

MSE /= Niters

plot(SNR_db, MSE[:,1],yaxis=:log,tickfontsize = 6,
label="Matrix Pencil Method",
#xlims = (0,1),
#xticks = round.(f_range.-f1; digits=2),
xlabel = "SNR",
ylabel = "MSE"
)
plot!(SNR_db,MSE[:,2],label="EMAC")
plot!(SNR_db,MSE[:,3],label="ANM")
#savefig("/home/thomas/atom-amd64/usr/share/atom/example/mse_N_N2=30.png")