text:
import Base: position

export AbstractEphemeris,
    position,
    position!,
    velocity,
    velocity!,
    state,
    state!

abstract type AbstractEphemeris end

function position! end
function velocity end
function velocity! end
function state end
function state! end


text:
#module TOML
#export read_toml
#TODO: tags cant be identical to variable names
#TODO: optimize parsing of multiline lists
#TODO: nested data
#TODO: preallocation?
#TODO: put this in a module to correctly captur __data_TOML__
#TODO: write a TOPL type to conveniently access the dictionary with TOML-like syntax
"This is a minimal TOML parser. See https://en.wikipedia.org/wiki/TOML"
function read_toml(fname)
    tags=""
    multi=false #sentinel value for multiline input
    data,var="","" #make data and var global in loop
    D=Dict()
    entry=Dict() # make entry global in loop
    for (linenumber,line) in enumerate(eachline(fname))
        line=strip(line)#remove enclosing whitespace
        if isempty(line) || line[1]=="#"
            continue
        elseif !multi && line[1]=='['
            #tag
            #TODO: check whether last chracter is ] otherwise error
            #tags=split(strip(line,['[',']']),'.') #parse tag
            #TODO: remove enclosing intermediate whitespace?
            tags=split(line[2:end-1],'.')
            entry=D
            for tag in tags
                tag="/"*tag
                if tag in keys(entry)
                    entry=entry[tag]
                else
                    #TODO: consider signaling an error if tag is not the last entry in tags
                    entry[tag]=Dict()
                    entry=entry[tag]
                end
            end
        elseif !multi && isletter(line[1])
            idx=findfirst("=",line)
            var=line[1:idx.start-1]
            var=strip(var) #remove enclosing whitespace
            data=line[idx.stop+1:end]
            multi=data[end]==','
        elseif multi
            data*=line
            multi=data[end]==','
        else
            #TODO:Throw error
        end

        if !multi && data!=""
            #println(data)
            eval(Meta.parse("__data_TOML__="*data)) #evaluate data in global scope of module
            if tags ==""
                D[var]=__data_TOML__
            else
                entry[var]=__data_TOML__
            end
            data=""
        end
    end
    eval(Meta.parse("__data_TOML__=nothing"))
    return D
end
#end

ext: jl


text:
### A Pluto.jl notebook ###
# v0.12.21

using Markdown
using InteractiveUtils

# ╔═╡ b4f5c324-ff31-11ea-38d1-752f4fcf9800
using Pkg, DrWatson

# ╔═╡ b4f6069a-ff31-11ea-3a71-f3c373a846df
begin
	#@quickactivate "StatisticalRethinkingStan"
	using StanSample
	using StatisticalRethinking
end

# ╔═╡ 30fc3d94-ff33-11ea-1899-8dc818a46887
include(projectdir("models", "06", "m6.7s.jl"))

# ╔═╡ bee11e78-ff30-11ea-2603-2546c772ca4b
md"## Clip-06-17s.jl"

# ╔═╡ b4fae4da-ff31-11ea-372d-351491f76437
begin
	N = 100
	df = DataFrame(
	  :h0 => rand(Normal(10,2 ), N),
	  :treatment => vcat(zeros(Int, Int(N/2)), ones(Int, Int(N/2)))
	);
	df.fungus = [rand(Binomial(1, 0.5 - 0.4 * df[i, :treatment]), 1)[1] for i in 1:N]
	df.h1 = [df[i, :h0] + rand(Normal(5 - 3 * df[i, :fungus]), 1)[1] for i in 1:N]
end;

# ╔═╡ b5092d06-ff31-11ea-2a2e-b1318107f9d9
m6_8 = "
data {
  int <lower=1> N;
  vector[N] h0;
  vector[N] h1;
  vector[N] treatment;
}
parameters{
  real a;
  real bt;
  real<lower=0> sigma;
}
model {
  vector[N] mu;
  vector[N] p;
  a ~ lognormal(0, 0.2);
  bt ~ normal(0, 0.5);
  sigma ~ exponential(1);
  for ( i in 1:N ) {
    p[i] = a + bt*treatment[i];
    mu[i] = h0[i] * p[i];
  }
  h1 ~ normal(mu, sigma);
}
";

# ╔═╡ b50a23b4-ff31-11ea-023e-0f45c3ea4cbe
begin
	m6_8s = SampleModel("m6.8s", m6_8)
	m6_8_data = Dict(:N => nrow(df), :h0 => df.h0, :h1 => df.h1, :treatment => df.treatment)
	rc6_8s = stan_sample(m6_8s; data=m6_8_data)
	if success(rc6_8s)
		dfa6_8s = read_samples(m6_8s, :dataframe)
		p6_8s = Particles(dfa6_8s)
	end
end

# ╔═╡ b5156ee0-ff31-11ea-181a-0dcd53fea5bb
success(rc6_8s) && (Text(precis(dfa6_8s; io=String)))

# ╔═╡ b51605f8-ff31-11ea-2d88-9b14ae2767b9
if success(rc6_8s)
	(s1, p1) = plot_model_coef([m6_7s, m6_8s], [:a, :bt, :bf])
	p1
end

# ╔═╡ b521ed8c-ff31-11ea-0470-31ae8d96b8ea
success(rc6_8s) && s1

# ╔═╡ b5227b8a-ff31-11ea-2545-91e90db9d361
md"## End of clip-06-17s.jl"

# ╔═╡ Cell order:
# ╟─bee11e78-ff30-11ea-2603-2546c772ca4b
# ╠═b4f5c324-ff31-11ea-38d1-752f4fcf9800
# ╠═b4f6069a-ff31-11ea-3a71-f3c373a846df
# ╠═30fc3d94-ff33-11ea-1899-8dc818a46887
# ╠═b4fae4da-ff31-11ea-372d-351491f76437
# ╠═b5092d06-ff31-11ea-2a2e-b1318107f9d9
# ╠═b50a23b4-ff31-11ea-023e-0f45c3ea4cbe
# ╠═b5156ee0-ff31-11ea-181a-0dcd53fea5bb
# ╠═b51605f8-ff31-11ea-2d88-9b14ae2767b9
# ╠═b521ed8c-ff31-11ea-0470-31ae8d96b8ea
# ╟─b5227b8a-ff31-11ea-2545-91e90db9d361


text:
# © 2016 Massachusetts Institute of Technology.  See LICENSE file for details.
using HDF5, JLD

typealias File Union{JLD.JldFile, HDF5.HDF5File}

# load/save filename
load_snapshot(filename::AbstractString, args...) = jldopen(jld->load_snapshot(jld, args...), filename, "r")

function save_snapshot(filename::AbstractString, args...)
  tmppath,tmpio = mktemp(dirname(filename))
  close(tmpio)
  try
    jldopen(jld->save_snapshot(jld, args...), tmppath, "w")
    Base.FS.rename(tmppath, filename)
    return
  catch
    Base.FS.unlink(tmppath)
    rethrow()
  end
end

# load/save ANode...
save_snapshot(file::File, n::ANode...) = save_snapshot(file, collect(ANode, n))
load_snapshot(file::File, n::ANode...) = load_snapshot(file, collect(ANode, n))

save_snapshot(file::AbstractString, n::ANode...) = save_snapshot(file, collect(ANode, n))
load_snapshot(file::AbstractString, n::ANode...) = load_snapshot(file, collect(ANode, n))

# load/save Instance
function load_snapshot(jld::File, g::Instance)
  for fn in fieldnames(g)
    if startswith(string(fn), "_") continue end
    copy!(g.(fn), read(jld, string(fn)))
  end
end

function save_snapshot(jld::File, g::Instance)
  for fn in fieldnames(g)
    if startswith(string(fn), "_") continue end
    if isdefined(:CudaArray) && isa(g.(fn), CudaArray)
      write(jld, string(fn), to_host(g.(fn)))
    else
      write(jld, string(fn), g.(fn))
    end
  end
end

# load/save Vector{ANode}
function load_snapshot{A<:ANode}(jld::File, ns::Vector{A}, visited=Set{ANode}())
  for n in ns
    load_snapshot(jld, n, visited)
  end
end

function save_snapshot{A<:ANode}(jld::File, ns::Vector{A}, visited=Set{ANode}())
  for n in ns
    save_snapshot(jld, n, visited)
  end
end

# load/save one ANode
function load_snapshot(jld::File, n::ANode, visited=Set{ANode}())
  if n in visited return end
  push!(visited, n)

  if !isempty(n.name) && sym(n) == :load
    try
      copy!(arg(n), read(jld, n.name))
    catch e
      error("Unable to load $(n.name): $e")
    end
  end

  for ni in n.input
    load_snapshot(jld, ni, visited)
  end
end

function save_snapshot(jld::File, n::ANode, visited=Set{ANode}())
  if n in visited return end
  push!(visited, n)

  if !isempty(n.name) && sym(n) == :load
    if isdefined(:CudaArray) && isa(n.arg, CudaArray)
      write(jld, n.name, to_host(n.arg))
    else
      write(jld, n.name, n.arg)
    end
  end

  for ni in n.input
    save_snapshot(jld, ni, visited)
  end
end


text:
using Petkovsek
using SymPy

@syms n y

@test algpoly([3, n, n-1], 0*n, n) == 0

@test alghyper([2*n*(n+1), -(n^2 +3*n-2), n-1], n) == [n+1, 2]


text:
# This file was generated, do not modify it. # hide
figure(figsize=(8,6))
hist(res, density=true)
x = range(-20, 20, )

savefig("assets/literate/ISL-lab-3-res2.svg") # hide


text:
close("all")
transD_GP.getchi2forall(opt, alpha=0.5)
ax = gcf().axes;
χ² = length(F.d_log10_ρ)
ax[2].plot(xlim(), [χ², χ²], "--", color="gray")
ax[2].set_ylim(χ²-10, χ²+10)
## plot posterior
opt.xall[:] .= zall
transD_GP.plot_posterior(F, opt, burninfrac=0.5, figsize=(10,6), qp1=0.05, qp2=0.95, nbins=50, fsize=11)
## plot a few models
m = transD_GP.CommonToAll.assembleTat1(opt, :fstar, temperaturenum=1)
transD_GP.MT1DInversion.plot_posterior(F, m[1:40:end], lcolor="k", modelalpha=0.25)

max_stars_repo_name: GeoscienceAustralia/HiQGA

max_issues_repo_name: GeoscienceAustralia/HiQGA


text:
module TestSetProperty

include("preamble.jl")

struct ImmutableStruct
    a
    b
end

mutable struct MutableStruct
    a
    b
end

@testset begin
    @test setproperty!!((a=1, b=nothing), :b, 2) === (a=1, b=2)
    @test setproperty!!(ImmutableStruct(1, nothing), :b, 2) ==
        ImmutableStruct(1, 2)
    ms = MutableStruct(1, nothing)
    @test setproperty!!(ms, :b, 2) === ms
    @test ms.b == 2
end

@testset "inference" begin
    nt = (a = 1, b = 2)
    f!!(nt) = setproperty!!(nt, :a, 3)
    @test @inferred(f!!(nt)) == (a = 3, b = 2)
end

end  # module


text:
export Func, default_funcs, eval_func, lin_funcs
export Ones, AND, OR, XOR, Nand, NAND, Nor, NOR, NOT, Not, Zero, ZERO, One, ONE, In1, IN1, In2, IN2
export func_names, setup_funcs
using Main.CGP

mutable struct Func
    func::Function
    arity::Integer
    name::AbstractString
end

Func(f::Function, a::Integer) = Func(f, a, string(f))

#println("Func: p: ",p)
#println("Func: p: ",Main.CGP.p)
#numinputs = Main.CGP.numinputs
#Ones = Main.CGP.construct_ones(numinputs)[numinputs]
#println("Func:  Ones: ",Ones)
const AND = Func(&, 2, "AND")
const OR = Func(|, 2, "OR")
const XOR = Func(⊻, 2, "XOR")
Nand(x,y) = ~(x & y) & Main.Ones
const NAND = Func(Nand, 2, "NAND")
Nor(x,y) = ~(x | y) & Ones
const NOR = Func(Nor, 2, "NOR")
Not(x) = (~x) & Ones
const NOT = Func(Not, 1, "NOT")
In1(x,y) = x
const IN1 = Func(In1, 2, "IN1" )
In2(x,y) = y
const IN2 = Func(In2, 2, "IN1" )
Zero() = MyInt(0)
const ZERO = Func(Zero, 0, "0")
One() = Ones
const ONE = Func(One, 0, "1")
One() = Ones

global Ones
# Sets up Ones, ONE for the number of inputs
function setup_funcs( numinputs::Int64 )
  Ones = Main.CGP.construct_ones(numinputs)[numinputs]
  ONE = Func(One, 0, "1")
end

function default_funcs( numinputs::Int64 )
  Ones = Main.CGP.construct_ones(numinputs)[numinputs]
  ONE = Func(One, 0, "1")
  global Ones
#    return [NAND ]   # Macia's gate set
#    return [AND, XOR ]
#    return [AND, OR, XOR ]
#    return [AND, OR, NOT, ZERO, ONE]
#    return [ZERO, ONE]
#    return [AND, OR, XOR, NAND, NOR, NOT, ZERO, ONE]
    return [AND, OR, NAND, NOR]   # Hu's gate set
#    return [AND, OR, XOR, NAND, NOR]   # Raman's gate set
#    return [AND, OR, XOR, NAND, NOR, IN1, IN2 ]   # Raman's gate set plus IN1 and IN2
end

function lin_funcs( numinputs::Int64 )
  Ones = Main.CGP.construct_ones(numinputs)[numinputs]
  ONE = Func(One, 0, "1")
  global Ones
#     return [NAND ]   # Macia's gate set
#    return [AND, XOR ]
#    return [AND, OR, XOR ]
#    return [AND, OR, NOT, ZERO, ONE]
#    return [ZERO, ONE]
#    return [AND, OR, XOR, NAND, NOR, NOT, ZERO, ONE]
    return [AND, OR, NAND, NOR]   # Hu's gate set
#    return [AND, OR, XOR, NAND, NOR]   # Raman's gate set
#     return [AND, OR, XOR, NAND, NOR, IN1, IN2 ]   # Raman's gate set plus IN1 and IN2
end

# Evaluate a Func for debugging purposes
# Example:  eval_func(XOR)
function eval_func( f::Func )
    context = f.arity > 0 ? construct_contexts(f.arity)[f.arity] : []
    Main.CGP.apply( f.func, [context[i] for i = 1:f.arity ] )
end

function func_names( funcs::Vector{Func} )
  [ f.name for f in funcs ]
end


text:
# Karp, R. M.
# A characterization of the minimum cycle mean in a digraph
# Discrete Mathematics, 1978, 23, 309 - 311
function _karp_minimum_cycle_mean(
    g::AbstractGraph,
    distmx::AbstractMatrix{T},
    component::Vector{U}
    ) where T <: Real where U <: Integer

    v2j = Dict{U, Int}()
    for (j, v) in enumerate(component)
        v2j[v] = j
    end
    n = length(component)
    F = fill(Inf, n+1, n)
    F[1, 1] = 0.
    for i in 2:n+1
        for (j, v) in enumerate(component)
            for u in inneighbors(g, v)
                k = get(v2j, u, 0)
                if !iszero(k)
                    F[i, j] = min(F[i, j], F[i-1, k] + distmx[u, v])
                end
            end
        end
    end

    # Extracting the cycle of minimal mean is not explained in Karp's paper.
    # Let
    # V* = argmin_v max_k (F_n(v) - F_k(v)) / (n - k)
    # Intuitively, one would think that it can be found by walking backward
    # using the F matrix starting from a vertex v ∈ V*.
    # However, it may not work as shown by the example in the tests.
    #
    # Fortunately, one can show that it works if we pick the v ∈ argmin_{v ∈ V*} F_n(v)
    # Indeed, suppose we walk backward and find v_0, ..., v_n=v.
    # Since there are n+1 nodes, there must be a cycle.
    # If the cycle is not of minimum cycle mean, then if we remove this cycle
    # and appends nodes of one of the cycle of minimum cycle mean containing v such that
    # the length is again n+1, we get a new walk of length n+1 ending at a node v' of the cycle.
    # Moreover, we know that v' ∈ V* and F_n(v') < F_n(v). Therefore if v minimizes F_n(v) among the nodes of V*, this cannot happen.

    # Find jbest ∈ V*
    λmin = Inf
    jbest = 0

    for j in 1:n
        λ = maximum(map(i -> (F[n+1, j] - F[i, j]) / (n+1 - i), 1:n))
        if λ < λmin || (isfinite(λ) && λ ≈ λmin && F[n+1, j] < F[n+1, jbest])
            λmin = λ
            jbest = j
        end
    end

    iszero(jbest) && return U[], Inf

    # Backward walk from jbest
    walk = zeros(Int, n+1)
    walk[n+1] = jbest
    for i in n:-1:1
        v = component[walk[i+1]]
        dmin = Inf
        for u in inneighbors(g, v)
            j = get(v2j, u, 0)
            if !iszero(j)
                dcur = F[i, j] + distmx[u, v]
                if dcur < dmin
                    walk[i] = j
                    dmin = dcur
                end
            end
        end
    end

    # Extract cycle in the walk
    invmap = zeros(Int, n)
    I = 1:0
    for i in n+1:-1:1
        if iszero(invmap[walk[i]])
            invmap[walk[i]] = i
        else
            I = i+1:invmap[walk[i]]
            break
        end
    end
    return component[walk[I]], λmin
end

"""
    karp_minimum_cycle_mean(g[, distmx])

Return minimum cycle mean of the directed graph `g` with optional edge weights contained in `distmx`.

### References
- [Karp](http://dx.doi.org/10.1016/0012-365X(78)90011-0).
"""
function karp_minimum_cycle_mean end
@traitfn function karp_minimum_cycle_mean(
    g::::IsDirected,
    distmx::AbstractMatrix = weights(g)
    )
    cycle = Int[]
    λmin = Inf
    for component in strongly_connected_components(g)
        c, λ = _karp_minimum_cycle_mean(g, distmx, component)
        if λ < λmin
            cycle = c
            λmin = λ
        end
    end
    cycle, λmin
end


text:
const onevec = 1:1
const emptyvec = 1:0
@inline inbounds(itp::AbstractInterpolation, x...) = _inbounds.(bounds(itp), x)
_inbounds((l,u)::Tuple{Number,Number}, x::Number) = ifelse(l <= x <= u, onevec, emptyvec)
function _inbounds((l,u)::Tuple{Number,Number}, x::AbstractVector)
    ret = Int[]
    for i in eachindex(x)
        l <= x[i] <= u && push!(ret, i)
    end
    ret
end

function getindex!(dest, itp, xs...)
    for (i, x) in zip(eachindex(dest), Iterators.product(xs...))
        dest[i] = itp(x...)
    end
    return dest
end

max_stars_repo_name: trallard/Pitaya-milkshake

max_issues_repo_name: trallard/Pitaya-milkshake

max_forks_repo_name: trallard/Pitaya-milkshake


text:
export
    KnotPoint,
    state,
    control

"""
	AbstractKnotPoint{T,n,m}

Stores the states, controls, time, and time step at a single point along a trajectory of a
forced dynamical system with `n` states and `m` controls.

# Interface
All instances of `AbstractKnotPoint` should support the following methods:

	state(z)::StaticVector{n}     # state vector
	control(z)::StaticVector{m}   # control vector
	z.t::Real                     # time
	z.dt::Real                    # time to next point (time step)

By default, it is assumed that if `z.dt == 0` the point is the last point in the trajectory.

Alternatively, the methods `state` and `control` will be automatically defined if the
following fields are present:
- `z.z`: the stacked vector `[x;u]`
- `z._x`: the indices of the states, such that `x = z.z[z._x]`
- `z._u`: the indices of the controls, such that `x = u.z[z._u]`
"""
abstract type AbstractKnotPoint{T,N,M} end

"""
	state(::AbstractKnotPoint)

Return the `n`-dimensional state vector
"""
@inline state(z::AbstractKnotPoint) = z.z[z._x]

"""
	control(::AbstractKnotPoint)

Return the `m`-dimensional control vector
"""
@inline control(z::AbstractKnotPoint) = z.z[z._u]

"""
	is_terminal(::AbstractKnotPoint)::Bool

Determine if the knot point is the terminal knot point, which is the case when `z.dt == 0`.
"""
@inline is_terminal(z::AbstractKnotPoint) = z.dt == 0

"""
	get_z(::AbstractKnotPoint)

Returns the stacked state-control vector `z`, or just the state vector if `is_terminal(z) == true`.
"""
@inline get_z(z::AbstractKnotPoint) = is_terminal(z) ? state(z) : z.z

"""
	set_state!(z::AbstractKnotPoint, x::AbstractVector)

Set the state in `z` to `x`.
"""
set_state!(z::AbstractKnotPoint, x) = for i in z._x; z.z[i] = x[i]; end

"""
	set_control!(z::AbstractKnotPoint, u::AbstractVector)

Set the controls in `z` to `u`.
"""
set_control!(z::AbstractKnotPoint, u) = for (i,j) in enumerate(z._u); z.z[j] = u[i]; end

"""
	set_z!(z::AbstractKnotPoint, z_::AbstractVector)

Set both the states and controls in `z` from the stacked state-control vector `z_`, unless
`is_terminal(z)`, in which case `z_` is assumed to be the terminal states.
"""
set_z!(z::AbstractKnotPoint, z_) = is_terminal(z) ? set_state!(z, z_) : copyto!(z.z, z_)

function Base.isapprox(z1::AbstractKnotPoint, z2::AbstractKnotPoint)
    get_z(z1) ≈ get_z(z2) && z1.t ≈ z2.t && z1.dt ≈ z2.dt
end

"""
	GeneralKnotPoint{T,n,m,V} <: AbstractKnotPoint{T,n,m}

A mutable instantiation of the `AbstractKnotPoint` interface where the joint vector `z = [x;u]`
is represented by a type `V`.

# Constructors
	GeneralKnotPoint(n::Int, m::Int, z::AbstractVector, dt, [t=0])
	GeneralKnotPoint(z::V, _x::SVector{n,Int}, _u::SVector{m,Int}, dt::T, t::T)
	KnotPoint(z::V, _x::SVector{n,Int}, _u::SVector{m,Int}, dt::T, t::T)
"""
mutable struct GeneralKnotPoint{T,N,M,V} <: AbstractKnotPoint{T,N,M}
    z::V
    _x::SVector{N,Int}
    _u::SVector{M,Int}
    dt::T # time step
    t::T  # total time
end

function GeneralKnotPoint(n::Int, m::Int, z::AbstractVector, dt::T, t=zero(T)) where T
    _x = SVector{n}(1:n)
    _u = SVector{m}(n .+ (1:m))
    GeneralKnotPoint(z, _x, _u, dt, t)
end

function Base.copy(z::GeneralKnotPoint)
    GeneralKnotPoint(Base.copy(z.z), z._x, z._u, z.dt, z.t)
end

"""
	KnotPoint{T,n,m,nm}

A `GeneralKnotPoint` whose stacked vector `z = [x;u]` is represented by an `SVector{nm,T}`
where `nm = n+m`.

# Setters
Use the following methods to set values in a `KnotPoint`:
```julia
set_state!(z::KnotPoint, x)
set_control!(z::KnotPoint, u)
z.t = t
z.dt = dt
```

# Constructors
```julia
KnotPoint(x, u, dt, [t=0.0])
KnotPoint(x, m, [t=0.0])  # for terminal knot point
```
"""
const KnotPoint{T,N,M,NM} = GeneralKnotPoint{T,N,M,SVector{NM,T}} where {T,N,M,NM}

function KnotPoint(z::V, ix::SVector{n,Int}, iu::SVector{m,Int}, dt::T, t::T) where {n,m,T,V}
    GeneralKnotPoint{T,n,m,V}(z, ix, iu, dt, t)
end

function KnotPoint(x::AbstractVector, u::AbstractVector, dt::Float64, t=0.0)
    n = length(x)
    m = length(u)
    xinds = ones(Bool, n+m)
    xinds[n+1:end] .= 0
    _x = SVector{n}(1:n)
    _u = SVector{m}(n .+ (1:m))
    z = SVector{n+m}([x;u])
    KnotPoint(z, _x, _u, dt, t)
end

# Constructor for terminal time step
function KnotPoint(x::AbstractVector, m::Int, t=0.0)
    u = zeros(m)
    KnotPoint(x, u, 0., t)
end

set_state!(z::KnotPoint, x) = z.z = [x; control(z)]
set_control!(z::KnotPoint, u) = z.z = [state(z); u]
set_z!(z::KnotPoint, z_) = z.z = z_

"""
	StaticKnotPoint{T,n,m,nm} <: AbstractKnotPoint{T,n,m}

An immutable `AbstractKnotPoint` whose stacked vector is represented by an `SVector{nm,T}`
where `nm = n+m`. Since `isbits(z::StaticKnotPoint) = true`, these can be created very
efficiently and with zero allocations.

# Constructors

	StaticKnotPoint(z::SVector{nm}, _x::SVector{n,Int}, _u::SVector{m,Int}, dt::Float64, t::Float64)
	StaticKnotPoint(x::SVector{n}, u::SVector{m}, [dt::Real=0.0, t::Real=0.0])
	StaticKnotPoint(z0::AbstractKnotPoint, z::AbstractVector)

where the last constructor uses another `AbstractKnotPoint` to create a `StaticKnotPoint`
using the stacked state-control vector `z`. If `length(z) == n`, the constructor will
automatically append `m` zeros.

"""
struct StaticKnotPoint{T,N,M,NM} <: AbstractKnotPoint{T,N,M}
    z::SVector{NM,T}
    _x::SVector{N,Int}
    _u::SVector{M,Int}
    dt::Float64 # time step
    t::Float64  # total time
end

function StaticKnotPoint(x::SVector{n}, u::StaticVector{m}, dt=0.0, t=0.0) where {n,m}
    ix = SVector{n}(1:n)
    iu = n .+ SVector{m}(1:m)
    StaticKnotPoint([x; u], ix, iu, Float64(dt), Float64(t))
end

function StaticKnotPoint(z0::AbstractKnotPoint{T,n,m}, z::AbstractVector=z0.z) where {T,n,m}
	if length(z) == n
		z = [SVector{n}(z); @SVector zeros(m)]
	end
    StaticKnotPoint{eltype(z),n,m,n+m}(z, z0._x, z0._u, z0.dt, z0.t)
end

function Base.:+(z1::AbstractKnotPoint, z2::AbstractKnotPoint)
	StaticKnotPoint(z1.z + z2.z, z1._x, z1._u, z1.dt, z1.t)
end

function Base.:+(z1::AbstractKnotPoint, x::AbstractVector)
	StaticKnotPoint(z1.z + x, z1._x, z1._u, z1.dt, z1.t)
end
@inline Base.:+(x::AbstractVector, z1::AbstractKnotPoint) = z1 + x

function Base.:*(a::Real, z::AbstractKnotPoint{<:Any,n,m}) where {n,m}
	StaticKnotPoint(z.z*a, SVector{n,Int}(z._x), SVector{m,Int}(z._u), z.dt, z.t)
end

@inline Base.:*(z::AbstractKnotPoint, a::Real) = a*z


text:
"""
    pretty(doc)

Pretty print the parsed HTML `doc`.
"""
function pretty(doc)
    io = IOBuffer()
    print(io, doc; pretty=true)
    return String(take!(io))
end

function map!(f::Function, doc::HTMLDocument)
    for elem in PreOrderDFS(doc.root)
        if elem isa HTMLElement
            # Changing elem directly doesn't work, so we loop direct children.
            children = elem.children
            for i in 1:length(children)
                elem.children[i] = f(elem.children[i])
            end
        end
        # else (isa HTMLText) is handled by the fact that we loop direct children.
    end
    return doc
end

function clean(elem::HTMLElement{T}) where T
    children = elem.children
    parent = elem.parent
    attributes = Dict{AbstractString,AbstractString}()
    return HTMLElement{T}(children, parent, attributes)
end

function remove_extra_href_info(entry::Pair)
    url = last(entry)
    symbol_loc = findfirst(r"\?|#", url)
    if isnothing(symbol_loc)
        return entry
    else
        symbol_loc_start = first(findfirst(r"\?|#", url))
        stripped = url[1:symbol_loc_start] * "[...]"
        new_entry::Pair = first(entry) => stripped
        return new_entry
    end
end

function remove_extra_href_info(dic::Dict)::Dict
    pairs = [remove_extra_href_info(entry) for entry in dic]
    return Dict(pairs)
end

function clean(elem::HTMLElement{:a})
    children = elem.children
    parent = elem.parent
    attributes = filter(entry -> first(entry) == "href", elem.attributes)
    updated_attributes = remove_extra_href_info(attributes)
    return HTMLElement{:a}(children, parent, updated_attributes)
end

function clean(elem::T) where T<:Union{HTMLElement{:style},HTMLElement{:script}}
    children = []
    parent = elem.parent
    attributes = Dict{AbstractString,AbstractString}()
    return T(children, parent, attributes)
end

function contains_title_description(entry)
    text = string(entry)::String
    return contains(text, r"title|description")
end

function clean(elem::HTMLElement{:meta})
    children = elem.children
    parent = elem.parent
    A = elem.attributes
    K = keys(A)
    if !any(contains_title_description(A))
        A = Dict{AbstractString,AbstractString}()
    end
    return HTMLElement{:meta}(children, parent, A)
end

function clean_tree(elem::HTMLElement)
    return clean(elem)
end

function clean_tree(elem::HTMLText)
    return elem
end

"""
    body(content::String)

Return only the body from the HTML `content`.
"""
function body(content::String)
    doc = parsehtml(content)
    for elem in PreOrderDFS(doc.root)
        if elem isa HTMLElement && tag(elem) == :body
            return string(elem)::String
        end
    end
    @warn "Couldn't find body in content:\n$content"
    return content
end

"""
    clean(content::String)

Return only the parts of the HTML which are visible in the rendered page.
This assumes that a page has changed when a reader can see a change, which seems like a reasonable assumption.
Note that this assumption may be violated when a elements are updated on the fly via Javascript.
"""
function clean(content::String)
    doc = parsehtml(content)
    map!(clean_tree, doc)
    text = pretty(doc)
    return """
        <!-- This HTML document was cleaned up (simplified) by `Skans.clean`. -->
        $text
        """
end


text:
reduce_length(x) = all(<=(length(x)), length.(shrink(x)))

@testset "Shrink methods" begin
    qc = Quickcheck("shrink")

    @add_predicate qc "Reduce string length" s::String -> reduce_length(s)

    @add_predicate(qc,
                   "Reduce vector length",
                   v::Vector{Int} -> reduce_length(v))
    @add_predicate(qc,
                   "Reduce matrix length",
                   M::Matrix{Int} -> reduce_length(M))
    @add_predicate(qc,
                   "Reduce diagonal matrix length",
                   DM::Diagonal{Int} -> reduce_length(DM))
    @add_predicate(qc,
                   "Reduce UnitRange length",
                   UR::UnitRange{Int} -> reduce_length(UR))

    ## `AbstractArray`.
    vec = collect(1:5)
    mat = vcat(vec * vec', vec * vec', vec * vec')
    arr3 = reshape(range(1, 5 * 6 * 7), 5, 6, 7)
    arr4 = reshape(range(1, 5 * 2 * 3 * 4), 5, 2, 3, 4)
    dm = Diagonal(1:5)

    @test shrink(vec) == [collect(1:3), collect(4:5)]
    @test shrink(mat) ==
        [[1 2 3; 2 4 6; 3 6 9; 4 8 12; 5 10 15; 1 2 3; 2 4 6; 3 6 9],
         [4 8 12; 5 10 15; 1 2 3; 2 4 6; 3 6 9; 4 8 12; 5 10 15],
         [4 5; 8 10; 12 15; 16 20; 20 25; 4 5; 8 10; 12 15],
         [16 20; 20 25; 4 5; 8 10; 12 15; 16 20; 20 25]]
    @test shrink(arr3) ==
        [[1 6 11; 2 7 12; 3 8 13;;; 31 36 41; 32 37 42; 33 38 43;;;
          61 66 71; 62 67 72; 63 68 73;;; 91 96 101; 92 97 102; 93 98 103],
         [4 9 14; 5 10 15;;; 34 39 44; 35 40 45;;;
          64 69 74; 65 70 75;;; 94 99 104; 95 100 105],
         [16 21 26; 17 22 27; 18 23 28;;;
          46 51 56; 47 52 57; 48 53 58;;;
          76 81 86; 77 82 87; 78 83 88;;;
          106 111 116; 107 112 117; 108 113 118],
         [19 24 29; 20 25 30;;; 49 54 59; 50 55 60;;;
          79 84 89; 80 85 90;;; 109 114 119; 110 115 120],
         [121 126 131; 122 127 132; 123 128 133;;;
          151 156 161; 152 157 162; 153 158 163;;;
          181 186 191; 182 187 192; 183 188 193],
         [124 129 134; 125 130 135;;;
          154 159 164; 155 160 165;;;
          184 189 194; 185 190 195],
         [136 141 146; 137 142 147; 138 143 148;;;
          166 171 176; 167 172 177; 168 173 178;;;
          196 201 206; 197 202 207; 198 203 208],
         [139 144 149; 140 145 150;;;
          169 174 179; 170 175 180;;;
          199 204 209; 200 205 210]]

    @test shrink(arr4) ==
        [[1; 2; 3;;; 11; 12; 13;;;; 31; 32; 33;;; 41; 42; 43],
         [4; 5;;; 14; 15;;;; 34; 35;;; 44; 45],
         [6; 7; 8;;; 16; 17; 18;;;; 36; 37; 38;;; 46; 47; 48],
         [9; 10;;; 19; 20;;;; 39; 40;;; 49; 50],
         [21; 22; 23;;;; 51; 52; 53],
         [24; 25;;;; 54; 55],
         [26; 27; 28;;;; 56; 57; 58],
         [29; 30;;;; 59; 60],
         [61; 62; 63;;; 71; 72; 73;;;; 91; 92; 93;;; 101; 102; 103],
         [64; 65;;; 74; 75;;;; 94; 95;;; 104; 105],
         [66; 67; 68;;; 76; 77; 78;;;; 96; 97; 98;;; 106; 107; 108],
         [69; 70;;; 79; 80;;;; 99; 100;;; 109; 110],
         [81; 82; 83;;;; 111; 112; 113],
         [84; 85;;;; 114; 115],
         [86; 87; 88;;;; 116; 117; 118],
         [89; 90;;;; 119; 120]]

    @test shrink(dm) == [Diagonal(1:3), Diagonal(4:5)]

    ## `AbstractString`.
    s = "C'est pas de nos affaires, nous on est Iroquois."

    @test shrink(s) ==
        ["C'est pas de nos affaire", "s, nous on est Iroquois."]

    @quickcheck qc
end


text:
using Plots
using Reproduce
using JLD2
using FileIO
using Statistics

include("plot_utils.jl")

num_steps = 4000
log_interval = 10

function get_lines(data)
    means = []
    step = 10
    for start in 1:step:size(data)[2]
        print()
        chunk = data[:, start:min(start + step - 1, size(data)[2]),: ]
        # println(size(chunk))
        chunk_mean = mean(chunk, dims=2)
        push!(means, chunk_mean)
    end

    smoothed = cat(means..., dims=2)[:,:, :]
    sum_lines = sum(smoothed, dims=1)
    num_runs = size(sum_lines)[3]
    mean_line = dropdims(mean(sum_lines, dims=3), dims=(1, 3))
    std_err_line = dropdims(std(sum_lines, dims=3), dims=(1, 3)) / sqrt(num_runs)

    xs = 1:log_interval*step:num_steps + 1
    println(size(xs))
    println(size(mean_line))
    println(size(std_err_line))
    return xs, mean_line, std_err_line
end

sweep_params = ["demon_eta"]

p = plot()

ic = ItemCollection("experiment_data/EmphaticTest")

# EmphaticTest
print(diff(ic))

function plot_line(ic, demon_update_algo)
    filtered_ic = search(ic, Dict("demon_update" => "TD"))

    best_ic = get_best(ic, sweep_params, :ttmaze_uniform_error)
    print_params(best_ic, sweep_params, [])

    data = load_results(best_ic, :ttmaze_uniform_error)

    xs, mean_line, std_err_line = get_lines(data)
    plot!(p, xs, mean_line, ribbons=std_err_line, label="TD")
end

savefig("plotting/chunlok/generated_plots/test_plot_reproduce.svg")

sdfsdf
# best_ic = get_best(ic, sweep_params, :ttmaze_uniform_error)
# print_params(best_ic, sweep_params, [])

# best_ic = get_best(ic, sweep_params, :ttmaze_uniform_error, (0.9, 1))
# print_params(best_ic, sweep_params, [])

# best_ic = search(ic, Dict("demon_eta" => 0.5, "exploration_param" => 0.4, "behaviour_eta" => 0.5))
best_ic = search(ic, Dict("demon_eta" => 0.5, "exploration_param" => 0.1, "behaviour_eta" => 0.0625))

print_params(best_ic, sweep_params, [])

data = load_results(best_ic, :ttmaze_uniform_error)

xs, mean_line, std_err_line = get_lines(data)
plot!(p, xs, mean_line, ribbons=std_err_line, label="GPI")

ic = ItemCollection("M:/globus/Experiment2/Experiment2_ESarsa_NoLimit")

println(diff(ic))

# best_ic = get_best(ic, sweep_params, :ttmaze_uniform_error)
# print_params(best_ic, sweep_params, [])
# best_ic = get_best(ic, sweep_params, :ttmaze_uniform_error, (0.9, 1))
# print_params(best_ic, sweep_params, [])

#AUC
# best_ic = search(ic, Dict("demon_eta" => 0.5, "exploration_param" => 0.4, "behaviour_eta" => 0.25))
#AUC last 10%
best_ic = search(ic, Dict("demon_eta" => 0.5, "exploration_param" => 0.3, "behaviour_eta" => 0.03125))

# Old Params
# best_ic = search(ic, Dict("demon_eta" => 0.5, "exploration_param" => 0.1, "behaviour_eta" => 0.0625))

data = load_results(best_ic, :ttmaze_uniform_error)

println(size(data))

xs, mean_line, std_err_line = get_lines(data)
plot!(p, xs, mean_line, ribbons=std_err_line, label="ESarsa")

savefig("plotting/chunlok/generated_plots/test_plot_reproduce.svg")

max_stars_repo_name: MatthewMcLeod/curiosity

max_issues_repo_name: MatthewMcLeod/curiosity

max_forks_repo_name: MatthewMcLeod/curiosity


text:
"""
    prefix_serial!(y, +)

Lorem ipsum dolor sit amet.
"""
function prefix_serial!(y, +)
  @inbounds for i=2:length(y)
      y[i] = y[i-1] + y[i]
  end
  y
end


text:
# Note that this script can accept some limited command-line arguments, run
# `julia build_tarballs.jl --help` to see a usage message.
using BinaryBuilder

name = "Qt"
version = v"5.14.2"

# Collection of sources required to build qt5
sources = [
    ArchiveSource("https://download.qt.io/official_releases/qt/5.14/$version/single/qt-everywhere-src-$version.tar.xz",
                  "c6fcd53c744df89e7d3223c02838a33309bd1c291fcb6f9341505fe99f7f19fa"),
    ArchiveSource("https://github.com/phracker/MacOSX-SDKs/releases/download/10.15/MacOSX10.14.sdk.tar.xz",
                  "0f03869f72df8705b832910517b47dd5b79eb4e160512602f593ed243b28715f")
]

script = raw"""
cd $WORKSPACE/srcdir
mkdir build
cd build/

commonoptions=" \
-opensource -confirm-license \
-skip qtactiveqt -skip qtandroidextras -skip qtcanvas3d -skip qtconnectivity -skip qtdatavis3d -skip qtdoc -skip qtgamepad \
-skip qtnetworkauth -skip qtpurchasing -skip qtremoteobjects -skip qtscript -skip qtscxml -skip qtsensors -skip qtserialbus \
-skip qtserialport -skip qtspeech -skip qtvirtualkeyboard -skip qtlocation -skip qtwayland -skip qtwebchannel -skip qtwebengine \
-skip qtwebglplugin -skip qtwebsockets -skip qtwebview  -skip qttools -nomake examples -release \
"

case "$target" in

	*86*linux*)
        export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$prefix/share/pkgconfig
        ../qt-everywhere-src-*/configure -L $prefix/lib -I $prefix/include \
            -prefix $prefix $commonoptions \
            -skip qtwinextras -system-xcb -fontconfig
        ln -s /lib64/libc.so.6 /lib64/libc.so
        ;;

	*apple-darwin*)
        cd $WORKSPACE/srcdir/MacOSX10.14.sdk
        rm -rf /opt/$target/$target/sys-root/System
        rsync -a usr/* /opt/$target/$target/sys-root/usr/
        cp -a System /opt/$target/$target/sys-root/

        cd $WORKSPACE/srcdir/qt-everywhere-src-*/qtbase

        cat <<EOT > mkspecs/features/mac/default_pre.prf
CONFIG = asset_catalogs rez \$\$CONFIG
load(default_pre)

QMAKE_ASSET_CATALOGS_APP_ICON = AppIcon

macx-xcode:qtConfig(static): \\
    QMAKE_XCODE_DEBUG_INFORMATION_FORMAT = dwarf

QMAKE_XCODE_LIBRARY_SUFFIX_SETTING = QT_LIBRARY_SUFFIX

xcode_copy_phase_strip_setting.name = COPY_PHASE_STRIP
xcode_copy_phase_strip_setting.value = NO
QMAKE_MAC_XCODE_SETTINGS += xcode_copy_phase_strip_setting
EOT

        sed -i '1s;^;QMAKE_MAC_SDK.macosx.Path = '"/opt/$target/$target/sys-root"'\
        QMAKE_MAC_SDK.macosx.SDKVersion = '"10.14"'\
        QMAKE_MAC_SDK.macosx.PlatformPath = '"/opt/$target"'\n;' 'mkspecs/features/mac/sdk.prf'
        echo "" >  mkspecs/features/mac/no_warn_empty_obj_files.prf

        sed -i 's!-fuse-ld=x86_64-apple-darwin14!-fuse-ld=/opt/bin/x86_64-apple-darwin14-ld!g' /opt/bin/x86_64-apple-darwin14-clang++
        sed -i 's!-fuse-ld=x86_64-apple-darwin14!-fuse-ld=/opt/bin/x86_64-apple-darwin14-ld!g' /opt/bin/x86_64-apple-darwin14-clang

        cd $WORKSPACE/srcdir/build
        apk add g++ linux-headers
        export PATH=$(echo "$PATH" | sed -e 's!/opt/bin:!!')
        export QT_MAC_SDK_NO_VERSION_CHECK=1
        ../qt-everywhere-src-*/configure \
            QMAKE_CXXFLAGS+=-F/opt/$target/$target/sys-root/System/Library/Frameworks \
            QMAKE_RANLIB=/opt/bin/ranlib \
            QMAKE_MACOSX_DEPLOYMENT_TARGET=10.14 \
            -platform linux-g++ -xplatform macx-clang -device-option CROSS_COMPILE=/opt/bin/$target- \
            -prefix $prefix $commonoptions \
            -skip qtwinextras
        ;;

	*w64-mingw*)
        apk add g++ linux-headers
        export PATH=$(echo "$PATH" | sed -e 's!/opt/bin:!!')

        ../qt-everywhere-src-*/configure -platform linux-g++ -xplatform win32-g++ -device-option CROSS_COMPILE=/opt/bin/$target- \
            -prefix $prefix $commonoptions \
            -opengl desktop
		;;

    *arm-linux*)
        apk add g++ linux-headers
        export PATH=$(echo "$PATH" | sed -e 's!/opt/bin:!!')
        export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$prefix/share/pkgconfig
        export PKG_CONFIG_LIBDIR=$prefix/lib/pkgconfig

        ../qt-everywhere-src-*/configure QMAKE_LFLAGS=-liconv -platform linux-g++ -device linux-rasp-pi3-g++ -device-option CROSS_COMPILE=/opt/bin/$target- \
            -extprefix $prefix $commonoptions \
            -skip qtwinextras -system-xcb -fontconfig -sysroot /opt/$target/bin/../$target/sys-root
		;;
esac

make -j${nproc}
make install
"""

# These are the platforms we will build for by default, unless further
# platforms are passed in on the command line
platforms = [
    Linux(:armv7l, libc=:glibc, call_abi=:eabihf),
    Windows(:x86_64),
    Linux(:x86_64, libc=:glibc)
]
platforms = expand_cxxstring_abis(platforms)

platforms_macos = [ MacOS(:x86_64) ]

# The products that we will ensure are always built
products = [
    LibraryProduct(["Qt5Core", "libQt5Core", "QtCore"], :libqt5core),
    LibraryProduct(["Qt5Quick", "libQt5Quick", "QtQuick"], :libqt5quick),
    LibraryProduct(["Qt5QuickControls2", "libQt5QuickControls2", "QtQuickControls2"], :libqt5quickcontrols2),
    LibraryProduct(["Qt5Svg", "libQt5Svg", "QtSvg"], :libqt5svg),
    LibraryProduct(["Qt5Widgets", "libQt5Widgets", "QtWidgets"], :libqt5widgets),
]

products_macos = [
    FrameworkProduct("QtCore", :libqt5core),
    FrameworkProduct("QtQuick", :libqt5quick),
    FrameworkProduct("QtQuickControls2", :libqt5quickcontrols2),
    FrameworkProduct("QtSvg", :libqt5svg),
    FrameworkProduct("QtWidgets", :libqt5widgets),
]

# Dependencies that must be installed before this package can be built
dependencies = [
    Dependency("Xorg_libX11_jll"),
    Dependency("Xorg_libXext_jll"),
    BuildDependency("Xorg_glproto_jll"),
    Dependency("Xorg_libxcb_jll"),
    Dependency("Xorg_xcb_util_wm_jll"),
    Dependency("Xorg_xcb_util_image_jll"),
    Dependency("Xorg_xcb_util_keysyms_jll"),
    Dependency("Xorg_xcb_util_renderutil_jll"),
    Dependency("xkbcommon_jll"),
    Dependency("Libglvnd_jll"),
    Dependency("Fontconfig_jll"),
    BuildDependency("Glib_jll"),
]

include("../../fancy_toys.jl")

if any(should_build_platform.(triplet.(platforms)))
    build_tarballs(ARGS, name, version, sources, script, platforms, products, dependencies; preferred_gcc_version = v"7")
end
if any(should_build_platform.(triplet.(platforms_macos)))
    build_tarballs(ARGS, name, version, sources, script, platforms_macos, products_macos, dependencies)
end

max_stars_repo_name: jbrea/Yggdrasil

max_issues_repo_name: jbrea/Yggdrasil

max_forks_repo_name: jbrea/Yggdrasil


text:
###################################################
#
# Copyright © Akamai Technologies. All rights reserved.
#
# File: Alert.jl
#
# Functions to communicate with the mPulse Repository REST API regarding Alert Objects.
# This file MUST be `include()`d from `mPulseAPI.jl`
#
###################################################

export
    deleteRepositoryAlert,
    getRepositoryAlert,
    postRepositoryAlert

"""
Fetches an Alert object from the mPulse repository

At least one of `alertID` or `alertName` must be passed in to identify the alert.

The alert will be cached in memory for 1 hour, so subsequent calls using a matching `alertID` return
quickly without calling out to the API.  This can be a problem if the alert changes in the repository.
You can clear the cache for this tenant using [`mPulseAPI.clearAlertCache`](@ref) and passing in `alertID`.

#### Arguments
`token::AbstractString`
:    The Repository authentication token fetched by calling [`getRepositoryToken`](@ref)

#### Keyword Arguments
`alertID::Int64`
:    The ID of the alert to fetch.

`alertName::AbstractString`
:    The Alert name in mPulse. This is available from the mPulse domain configuration dialog.

#### Returns
`{Dict}` The `alert` object with the following fields:

`hidden::Bool`
:    Flag indicating whether the alert is visible to the user

`parentID::Int64`
:    The ID of the parent folder that this alert is in

`path::AbstractString`
:    The folder path that this alert is in

`readOnly::Bool`
:    Flag indicating whether the alert is able to be edited

`name::AbstractString`
:    The alert's name

`tenantID::Int64`
:    The ID of the tenant that the alert is in

`created::DateTime`
:    The timestamp when this object was created

`id::Int64`
:    The ID of the alert.

`description::AbstractString`
:    The description of this alert entered into mPulse

`lastCached::DateTime`
:    The timestamp when this object was last cached

`body::XMLElement`
:    An XML object representing the alert's XML definition or an empty node if you do not have permission to see the full alert

`references::Dict`
:    An array of `Dict`s with reference information such as `name`, `id`, `type`, and `path`.

`uid::AbstractString`
:    The encrypted uid associated with the alert

`deleted::Bool`
:    Flag indicating whether the alert has been deleted

`ownerID::Int64`
:    The ID of the alert's owner

`attributes::Dict`
:    A `Dict` containing whether the alert is active, version number, and the time(s) that the alert was last cleared, triggered, and updated.

`lastModified::DateTime`
:    The timestamp when this object was created

#### Throws
`ArgumentError`
:    if token is empty or alertID is empty

`mPulseAPIException`
:    if API access failed for some reason
"""
function getRepositoryAlert(token::AbstractString; alertID::Int64=0, alertName::AbstractString="")

    alert_list = getRepositoryObject(
                token,
                "alert",
                Dict{Symbol, Any}(:id => alertID, :name => alertName),
                filterRequired=false
        )

    # Always convert to an array for easier processing
    if !isa(alert_list, AbstractArray)
        alert_list = Dict{AbstractString, Any}[alert_list]
    end

    # Convert alert attribute dates to ZonedDateTime
    dateFormat = DateFormat("y-m-dTH:M:S.s+z")
    for alert in alert_list
        alertAttributes = alert["attributes"]
        for lastTimestamp in ["lastCleared", "lastTriggered", "lastUpdated"]
            alertAttributes[lastTimestamp] = isa(alertAttributes[lastTimestamp], AbstractString) ? ZonedDateTime(alertAttributes[lastTimestamp], dateFormat) : alertAttributes[lastTimestamp]
        end
    end

    # Return the first element only if the caller asked for a unique alert, else
    # return the list even if it only has one element in it
    if alertID != 0 || alertName != ""
        return alert_list[1]
    else
        return alert_list
    end
end

"""
Updates an Alert object from the mPulse repository

At least one of `alertID` or `alertName` must be passed in to update the alert object.

#### Arguments
`token::AbstractString`
:    The Repository authentication token fetched by calling [`getRepositoryToken`](@ref)

#### Keyword Arguments
`alertID::Int64`
:    The ID of the alert to update.

`alertName::AbstractString`
:    The Alert name in mPulse. This is available from the mPulse domain configuration dialog.

`attributes::Dict`
:    A `Dict` of alert attributes to update

`objectFields::Dict`
:    A `Dict` of alert object fields to update

`body::AbstractString|LightXML.XMLElement=""`
:    An XMLElement (if not empty) containing the body of the alert, containing pertinent information surrounding errors.

#### Returns
`{Dict}` The updated `alert` object with the following fields:

`hidden::Bool`
:    Flag indicating whether the alert is visible to the user

`parentID::Int64`
:    The ID of the parent folder that this alert is in

`path::AbstractString`
:    The folder path that this alert is in

`readOnly::Bool`
:    Flag indicating whether the alert is able to be edited

`name::AbstractString`
:    The alert's name

`tenantID::Int64`
:    The ID of the tenant in which the alert is in

`created::DateTime`
:    The timestamp when this object was created

`id::Int64`
:    The ID of the alert.

`description::AbstractString`
:    The description of this alert entered into mPulse

`lastCached::DateTime`
:    The timestamp when this object was last cached

`errorXML::Union{AbstractString, LightXML.XMLElement}`
:    An XML object representing the alert's XML error definition to be updated by the repository.

`references::Dict`
:    An array of `Dict`s with reference information such as `name`, `id`, `type`, and `path`.

`uid::AbstractString`
:    The encrypted uid associated with the alert

`deleted::Bool`
:    Flag indicating whether the alert has been deleted

`ownerID::Int64`
:    The ID of the alert's owner

`attributes::Dict`
:    A `Dict` containing whether the alert is active, version number, and the time(s) that the alert was last cleared, triggered, and updated.

`lastModified::DateTime`
:    The timestamp when this object was created

#### Throws
`ArgumentError`
:    if token is empty or alertID is empty

`mPulseAPIException`
:    if API access failed for some reason

"""

function postRepositoryAlert(token::AbstractString;
                            alertID::Int64=0,
                            alertName::AbstractString="",
                            attributes::Dict=Dict(),
                            objectFields::Dict=Dict(),
                            alertBody::Union{AbstractString, LightXML.XMLElement}="",
                            errorXML::Union{AbstractString, LightXML.XMLElement}="" # deprecated, remains here for backwards compatibility
)
    # Renaming errorXML argument to alertBody; safeguard until all code is updated
    if isempty(alertBody) && errorXML != ""
        alertBody = errorXML
    end

    postRepositoryObject(
        token,
        "alert",
        Dict{Symbol, Any}(:id => alertID, :name => alertName),
        attributes = attributes,
        objectFields = objectFields,
        body = alertBody
    )

    return nothing
end

"""
Deletes an Alert object from the mPulse repository

At least one of `alertID` or `alertName` must be passed in to delete the alert object.

#### Arguments
`token::AbstractString`
:    The Repository authentication token fetched by calling [`getRepositoryToken`](@ref)

#### Keyword Arguments
`alertID::Int64`
:    The ID of the alert to update.

`alertName::AbstractString`
:    The Alert name in mPulse. This is available from the mPulse domain configuration dialog.

#### Returns
Returns true if the delete is successful, else false.

#### Throws
`ArgumentError`
:    if token is empty or alertID is empty

`mPulseAPIException`
:    if API access failed for some reason

"""

function deleteRepositoryAlert(token::AbstractString;
                            alertID::Int64=0,
                            alertName::AbstractString=""
)

    resp = deleteRepositoryObject(
        token,
        "alert",
        Dict{Symbol, Any}(:id => alertID, :name => alertName)
    )

    return statuscode(resp) == 204
end


text:
include("../../module/CustomStencil.jl")
include("../../misc/misc.jl")
include("../../misc/cpu_stencils.jl")
## Star Stencil definition with radius = 4

##

for i = parse(Int,ARGS[1])

    star_coefs = zeros(Float64, 9,9,9)
    star_coefs[1:9,5,5] .= 1
    star_coefs[5,1:9,5] .= 1

    d = 5-i:5+i
    star_coefs[d,d,5] .= 1
    @show sum(star_coefs .== 1)/81
    st_def = CreateStencilDefinition(star_coefs)
    bdim = 32
    if length(ARGS) == 2
        bdim = 16
    end
    st_inst1 = NewStencilInstance(st_def, bdim=bdim, m_step=false)

    radius = 4
    nx = 12
    ny = 12
    nz = 0
    dx = 1<<(nx)
    dy = 1<<(ny)
    dz = 1<<(nz)

    data = CreateData(dx,dy,dz)

    t_steps = 16
    gpu_out = ApplyStencil(st_inst1, data, t_steps)
    CUDA.cuProfilerStart()
    NVTX.@range "standard" begin
        gpu_out = ApplyStencil(st_inst1, data, t_steps) end
    exit()

end


text:
begin
    local b = startboard()
    @test pieceon(b, SQ_E1) == PIECE_WK
    @test pieceon(b, FILE_B, RANK_8) == PIECE_BN
    @test pieceon(b, SQ_B5) == EMPTY
end

begin
    local b = startboard()
    local b2 = domove(b, movefromstring("e2e4"))
    @test sidetomove(b) == WHITE
    @test sidetomove(b2) == BLACK
end

begin
    local b = startboard()
    @test epsquare(b) == SQ_NONE
    local b2 = fromfen("rnbqkbnr/pppp1ppp/4p3/4P3/8/8/PPPP1PPP/RNBQKBNR b - -")
    @test epsquare(b2) == SQ_NONE
    local b3 = domove(b2, movefromstring("d7d5"))
    @test epsquare(b3) == SQ_D6
end

begin
    local b = startboard()
    @test pieces(b, WHITE) == SS_RANK_1 ∪ SS_RANK_2
    @test pieces(b, ROOK) == SquareSet(SQ_A1, SQ_H1, SQ_A8, SQ_H8)
    @test pieces(b, BLACK, PAWN) == SS_RANK_7
    @test pieces(b, PIECE_WB) == SquareSet(SQ_C1, SQ_F1)
    @test pawns(b) == SS_RANK_2 ∪ SS_RANK_7
    @test pawns(b, WHITE) == SS_RANK_2
    @test pawns(b, BLACK) == SS_RANK_7
    @test knights(b) == SquareSet(SQ_B1, SQ_G1, SQ_B8, SQ_G8)
    @test knights(b, WHITE) == SquareSet(SQ_B1, SQ_G1)
    @test knights(b, BLACK) == SquareSet(SQ_B8, SQ_G8)
    @test bishops(b) == SquareSet(SQ_C1, SQ_F1, SQ_C8, SQ_F8)
    @test bishops(b, WHITE) == SquareSet(SQ_C1, SQ_F1)
    @test bishops(b, BLACK) == SquareSet(SQ_C8, SQ_F8)
    @test rooks(b) == SquareSet(SQ_A1, SQ_H1, SQ_A8, SQ_H8)
    @test rooks(b, WHITE) == SquareSet(SQ_A1, SQ_H1)
    @test rooks(b, BLACK) == SquareSet(SQ_A8, SQ_H8)
    @test queens(b) == SquareSet(SQ_D1, SQ_D8)
    @test queens(b, WHITE) == SquareSet(SQ_D1)
    @test queens(b, BLACK) == SquareSet(SQ_D8)
    @test kings(b) == SquareSet(SQ_E1, SQ_E8)
    @test kings(b, WHITE) == SquareSet(SQ_E1)
    @test kings(b, BLACK) == SquareSet(SQ_E8)
    @test bishoplike(b) == SquareSet(SQ_C1, SQ_D1, SQ_F1, SQ_C8, SQ_D8, SQ_F8)
    @test bishoplike(b, WHITE) == SquareSet(SQ_C1, SQ_D1, SQ_F1)
    @test bishoplike(b, BLACK) == SquareSet(SQ_C8, SQ_D8, SQ_F8)
    @test rooklike(b) == SquareSet(SQ_A1, SQ_D1, SQ_H1, SQ_A8, SQ_D8, SQ_H8)
    @test rooklike(b, WHITE) == SquareSet(SQ_A1, SQ_D1, SQ_H1)
    @test rooklike(b, BLACK) == SquareSet(SQ_A8, SQ_D8, SQ_H8)
end

begin
    local b = fromfen("5k2/8/4q3/8/2B5/8/4P3/3K4 w - -")
    @test bishopattacks(b, SQ_C4) ==
          SquareSet(SQ_A2, SQ_B3, SQ_E2, SQ_D3, SQ_B5, SQ_A6, SQ_D5, SQ_E6)
    local b = fromfen("2r2k2/8/8/8/2R3P1/8/4P3/3K4 w - -")
    @test rookattacks(b, SQ_C4) == SquareSet(
        SQ_A4,
        SQ_B4,
        SQ_D4,
        SQ_E4,
        SQ_F4,
        SQ_G4,
        SQ_C1,
        SQ_C2,
        SQ_C3,
        SQ_C5,
        SQ_C6,
        SQ_C7,
        SQ_C8,
    )
    local b = fromfen("2r2k2/8/8/8/2Q3P1/8/4P3/3K4 w - -")
    @test queenattacks(b, SQ_C4) == SquareSet(
        SQ_C1,
        SQ_C2,
        SQ_C3,
        SQ_C5,
        SQ_C6,
        SQ_C7,
        SQ_C8,
        SQ_A4,
        SQ_B4,
        SQ_D4,
        SQ_E4,
        SQ_F4,
        SQ_G4,
        SQ_A2,
        SQ_B3,
        SQ_D5,
        SQ_E6,
        SQ_F7,
        SQ_G8,
        SQ_E2,
        SQ_D3,
        SQ_B5,
        SQ_A6,
    )
end

begin
    local b = startboard()
    @test isattacked(b, SQ_F3, WHITE)
    @test !isattacked(b, SQ_F3, BLACK)
end

begin
    local b = fromfen("r1bqkbnr/pppp1ppp/2n5/4p3/3PP3/5N2/PPP2PPP/RNBQKB1R b KQkq - 0 3")
    @test attacksto(b, SQ_D4) == SquareSet(SQ_D1, SQ_F3, SQ_E5, SQ_C6)
end

begin
    local b = fromfen("2r4b/1kp5/8/2P1Q3/1P6/2K1P2r/8/8 w - -")
    @test pinned(b) == SquareSet(SQ_E3, SQ_E5)
end

begin
    local b = startboard()
    @test perft(b, 0) == 1
    @test perft(b, 1) == 20
    @test perft(b, 2) == 400
    @test perft(b, 3) == 8902
    @test perft(b, 4) == 197281
    @test perft(b, 5) == 4865609
    # @test perft(b, 6) == 119060324
    # @test perft(b, 7) == 3195901860

    b = fromfen("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq -")
    @test perft(b, 0) == 1
    @test perft(b, 1) == 48
    @test perft(b, 2) == 2039
    @test perft(b, 3) == 97862
    @test perft(b, 4) == 4085603
    # @test perft(b, 5) == 193690690
    # @test perft(b, 6) == 8031647685

    b = fromfen("8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - -")
    @test perft(b, 0) == 1
    @test perft(b, 1) == 14
    @test perft(b, 2) == 191
    @test perft(b, 3) == 2812
    @test perft(b, 4) == 43238
    @test perft(b, 5) == 674624
    # @test perft(b, 6) == 11030083
    # @test perft(b, 7) == 178633661

    b = fromfen("bqnb1rkr/pp3ppp/3ppn2/2p5/5P2/P2P4/NPP1P1PP/BQ1BNRKR w HFhf -")
    @test perft(b, 0) == 1
    @test perft(b, 1) == 21
    @test perft(b, 2) == 528
    @test perft(b, 3) == 12189
    @test perft(b, 4) == 326672
    @test perft(b, 5) == 8146062

    b = fromfen("b1qr1krb/pp1ppppp/n2n4/8/2p5/2P3P1/PP1PPP1P/BNQRNKRB w GDgd -")
    @test perft(b, 0) == 1
    @test perft(b, 1) == 28
    @test perft(b, 2) == 707
    @test perft(b, 3) == 19721
    @test perft(b, 4) == 549506
    @test perft(b, 5) == 15583376

    b = fromfen("nrnbqkbr/2pp2pp/4pp2/pp6/8/1P3P2/P1PPPBPP/NRNBQ1KR w hb -")
    @test perft(b, 0) == 1
    @test perft(b, 1) == 25
    @test perft(b, 2) == 656
    @test perft(b, 3) == 16951
    @test perft(b, 4) == 466493
    @test perft(b, 5) == 12525939

    b = fromfen("qnbbrknr/1p1ppppp/8/p1p5/5P2/PP1P4/2P1P1PP/QNBBRKNR w HEhe -")
    @test perft(b, 0) == 1
    @test perft(b, 1) == 27
    @test perft(b, 2) == 573
    @test perft(b, 3) == 16331
    @test perft(b, 4) == 391656
    @test perft(b, 5) == 11562434

    b = fromfen("nrq1kbnr/p1pbpppp/3p4/1p6/6P1/1N3N2/PPPPPP1P/1RBQKB1R w HBhb -")
    @test perft(b, 0) == 1
    @test perft(b, 1) == 24
    @test perft(b, 2) == 648
    @test perft(b, 3) == 16640
    @test perft(b, 4) == 471192
    @test perft(b, 5) == 12871967

    b = fromfen("1qrkrnbb/1p1p1ppp/pnp1p3/8/3PP3/P6P/1PP2PP1/NQRKRNBB w ECec -")
    @test perft(b, 0) == 1
    @test perft(b, 1) == 24
    @test perft(b, 2) == 688
    @test perft(b, 3) == 17342
    @test perft(b, 4) == 511444
    @test perft(b, 5) == 13322502

    b = fromfen("1brnqknr/2p1pppp/p2p4/1P6/6P1/4Nb2/PP1PPP1P/BBR1QKNR w HChc -")
    @test perft(b, 0) == 1
    @test perft(b, 1) == 34
    @test perft(b, 2) == 1019
    @test perft(b, 3) == 32982
    @test perft(b, 4) == 1003103
    @test perft(b, 5) == 33322477

    b = fromfen("brn1kbrn/pp2p1pp/3p4/q1p2p2/2P4P/6P1/PP1PPP2/BRNQKBRN w GBgb -")
    @test perft(b, 0) == 1
    @test perft(b, 1) == 18
    @test perft(b, 2) == 477
    @test perft(b, 3) == 10205
    @test perft(b, 4) == 273925
    @test perft(b, 5) == 6720181

    b = fromfen("b1rknnrq/bpppp1p1/p6p/5p1P/6P1/4N3/PPPPPP2/BBRKN1RQ w GCgc -")
    @test perft(b, 0) == 1
    @test perft(b, 1) == 33
    @test perft(b, 2) == 851
    @test perft(b, 3) == 28888
    @test perft(b, 4) == 763967
    @test perft(b, 5) == 26686205

    b = fromfen("brkqnrnb/1p1pp1p1/p4p2/2p4p/8/P2PP3/1PP1QPPP/BRK1NRNB w FBfb -")
    @test perft(b, 0) == 1
    @test perft(b, 1) == 24
    @test perft(b, 2) == 479
    @test perft(b, 3) == 12584
    @test perft(b, 4) == 280081
    @test perft(b, 5) == 7830230

    b = fromfen("brkrqb1n/1pppp1pp/p7/3n1p2/P5P1/3PP3/1PP2P1P/BRKRQBNN w DBdb -")
    @test perft(b, 0) == 1
    @test perft(b, 1) == 27
    @test perft(b, 2) == 669
    @test perft(b, 3) == 18682
    @test perft(b, 4) == 484259
    @test perft(b, 5) == 13956472
end

begin
    fens = [
        "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -",
        "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w Kq -",
        "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w Qk -",
        "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w - -",
        "rnbqkbnr/pppp1ppp/4p3/4P3/8/8/PPPP1PPP/RNBQKBNR w KQkq d6",
    ]
    for f in fens
        @test fen(fromfen(f)) == f
    end
end

function computekey(b::Board)::UInt64
    result = UInt64(0)
    for s in occupiedsquares(b)
        p = pieceon(b, s)
        result ⊻= Chess.zobrist(p, s)
    end
    result ⊻= Chess.zobcastle(b.castlerights)
    if epsquare(b) != SQ_NONE
        result ⊻= Chess.zobep(epsquare(b))
    end
    if sidetomove(b) == BLACK
        result ⊻= Chess.zobsidetomove()
    end
    result
end

function keyisright(b::Board)::Bool
    b.key == computekey(b)
end

begin
    b = fromfen("r3k2r/1P6/8/3Pp3/5p2/8/4P3/R3K2R w KQkq e6")
    @test keyisright(b)

    ms = ["bxa8=R+", "b8=N", "dxe6", "e4", "O-O", "Rxh8+"]
    for m in ms
        @test keyisright(domove(b, m))
    end

    for m in ms
        u = domove!(b, m)
        @test keyisright(b)
        undomove!(b, u)
        @test keyisright(b)
    end
end

begin
    @test isstalemate(fromfen("b5bk/3r4/2PNP3/1rNKNr2/2PNP3/3r4/b5b1/8 w - -"))
    @test !isstalemate(fromfen("b5bk/3r4/2PNP3/1rNKNr2/2PNP3/3r4/b7/8 w - -"))
    @test ischeckmate(fromfen("b5bk/3r4/2P1P3/1rNKNr2/2PNP3/3r4/b5b1/8 w - -"))
    @test !ischeckmate(fromfen("6bk/3r4/2P1P3/1rNKNr2/2PNP3/3r4/b5b1/8 w - -"))
    @test ismaterialdraw(fromfen("8/8/3k4/8/3K4/8/8/8 w - -"))
    @test ismaterialdraw(fromfen("8/8/3k4/8/3K4/8/8/7N w - -"))
    @test ismaterialdraw(fromfen("8/8/3k4/8/3K4/8/8/7b w - -"))
    @test !ismaterialdraw(fromfen("8/8/3k4/8/3K4/8/8/7R w - -"))
    @test !ismaterialdraw(fromfen("8/8/3k4/8/3K4/8/8/7q w - -"))
    @test !ismaterialdraw(fromfen("8/8/3k4/8/3K4/8/7b/7N w - -"))
    @test !ismaterialdraw(fromfen("8/8/3k4/8/3K4/8/8/BB6 w - -"))
    @test !ismaterialdraw(fromfen("8/8/3k4/8/3K4/8/3P4/8 w - -"))
end

begin
    b = fromfen("8/4k3/3p4/4p3/8/2BK4/8/8 w - - 0 1")
    @test see(b, Move(SQ_C3, SQ_E5)) == -2
    b = fromfen("8/4k3/3p4/4p3/8/2BK4/8/Q7 w - - 0 1")
    @test see(b, Move(SQ_C3, SQ_E5)) == -1
    b = fromfen("8/4k3/8/4p3/8/2BK4/8/q7 w - - 0 1")
    @test see(b, Move(SQ_C3, SQ_E5)) == -2
    b = fromfen("8/4k3/8/4p3/8/2BK4/8/8 w - - 0 1")
    @test see(b, Move(SQ_C3, SQ_E5)) == 1
    b = fromfen("7q/4k1b1/3p4/4n3/8/2BK1N2/4R3/4R3 w - - 0 1")
    @test see(b, Move(SQ_F3, SQ_E5)) == 1
    b = fromfen("7q/4k1b1/3p4/4n3/8/2BK1N2/4R3/4R3 w - - 0 1")
    @test see(b, Move(SQ_E2, SQ_E5)) == -1
    b = fromfen("7q/4k1b1/3p4/4n3/8/2BK1N2/4R3/8 w - - 0 1")
    @test see(b, Move(SQ_E2, SQ_E5)) == -2
    b = fromfen("7q/4k1b1/3p4/4n3/8/2BK1N2/4R3/8 w - - 0 1")
    @test see(b, Move(SQ_C3, SQ_E5)) == 0
    b = fromfen("8/2b5/3k4/4p3/3K1P2/8/8/8 w - -")
    @test see(b, Move(SQ_F4, SQ_E5)) == 1
    b = fromfen("8/8/3k1p2/4p3/3K1P2/2B5/8/8 w - -")
    @test see(b, Move(SQ_F4, SQ_E5)) == 0
end

begin
    b = fromfen("r1bqk2r/p4pb1/2pp1np1/6P1/2PPp3/2N5/PP3PB1/R1BQK2R b KQkq -")
    domove!(b, "Rxh1+")
    @test fen(b) == "r1bqk3/p4pb1/2pp1np1/6P1/2PPp3/2N5/PP3PB1/R1BQK2r w Qq -"
end

begin
    b = startboard()
    @test fen(decompress(compress(b))) == fen(b)
    b = fromfen("4k3/8/8/pP6/8/8/8/4K3 w - a6")
    @test fen(decompress(compress(b))) == fen(b)
    b = fromfen("r3k2r/8/8/8/Pp6/8/8/4K3 b kq a3")
    @test fen(decompress(compress(b))) == fen(b)
end


text:
include("../notebook/TLines.jl") #Defines TLines.* & BodePlots.* utilities.
import InspectDR
using NumericIO
using Interact
using Blink

#NOTE: Zref=50Ω when computing reflection coefficient, Γ

#Convenience aliases:
SI(x) = formatted(x, :SI, ndigits=3)
_throt(x) = throttle(0.1, x) #Default throttling for this example.

#When MIME"image/svg+xml" is disabled, Jupyter eventually requests PNG inline graphics:
#(Not certain if PNG is faster than SVG)
InspectDR.defaults.rendersvg = false

ltype_opt = OrderedDict("Series RLC" => :series, "Shunt RLC" => :shunt)

#Create base Observable-s that control plot output:
ltype = Observable(Symbol(:shunt)) #Load type
fmin = Observable(Float64(1e7)) #Minimum plot frequency
fmax = Observable(Float64(10e9)) #Maximum plot frequency
R = Observable(Float64(40)) #Resistive load (Ω)
L = Observable(Float64(20e-9)) #Inductive load (H)
C = Observable(Float64(0.2e-12)) #Capacitive load (F)
ZC = Observable(Float64(75)) #Characteristic impedance of line (Ω)
α = Observable(Float64(0.2)) #Line attenuation (dB/m) - Nepers/m?
μ = Observable(Float64(TLines.μ0)) #Permeability for line (H/m)
ϵ = Observable(Float64(TLines.ϵ0)) #Permittivity for line (F/m)
ℓ = Observable(Float64(0.1)) #Line length (m)

dispinline = Observable(Bool(true)) #Display inline?

#Construct widgets used to control plots:
tgl_ltype = togglebuttons(ltype_opt, value=ltype, label="Load type")
txt_fmin = spinbox(value=fmin, label="min freq")
txt_fmax = spinbox(value=fmax, label="max freq")
sld_ZC = slider(1:1.0:377, value=ZC, label="ZC (Ω)")

sld_R = slider(10 .^ range(log10(1e-3), stop=log10(1e6), length=100), value=R, label="R-load (Ω)")
sld_L = slider(10 .^ range(log10(1e-15), stop=log10(1e-3), length=100), value=L, label="L-load (H)")
sld_C = slider(10 .^ range(log10(1e-18), stop=log10(1e-3), length=100), value=C, label="C-load (F)")
sld_α = slider(range(0, stop=1, length=100), value=α, label="α-DC line atten (dB/m)")
sld_ℓ = slider(10 .^ range(log10(1e-6), stop=log10(1), length=100), value=ℓ, label="ℓ-line length (m)")

chk_inline = checkbox(dispinline, label="Display inline")
btn_creategtkplot = button("Create GTK plot")

info_params = map(R, L, C, ℓ) do _R, _L, _C, _ℓ
    return HTML(
        "R=" * SI(_R) * "Ω" *
        ", L=" * SI(_L) * "H" *
        ", C=" * SI(_C) * "F" *
        ", ℓ=" * SI(_ℓ) * "m"
    )
end

#Build widget lists to control plot:
widgetlistA = [tgl_ltype, txt_fmin, txt_fmax, sld_ZC, info_params, chk_inline, btn_creategtkplot]
widgetlistB = [sld_R, sld_L, sld_C, sld_α, sld_ℓ]

#Widget-controlled plot:
pobj = TLines.newplot() #Create initial plot object
#Calculations are relatively expensive... throttle all inputs:
updater_plot = map(_throt(fmin), _throt(fmax), _throt(ltype), _throt(R), _throt(L), _throt(C), _throt(ℓ), _throt(ZC), _throt(α)) do _fmin, _fmax, _ltype, _R, _L, _C, _ℓ, _ZC, _α
    TLines.update(pobj, _fmin, _fmax, _ltype, _R, _L, _C, _ℓ, _ZC, _α)
    return pobj
end

#Conditionally display inline plot with throttling to reduce traffic:
inline_plot = map(updater_plot, dispinline) do _plot, _inline
	if _inline
		return _plot
	else
		return nothing
	end
end

#Provide means to spawn new InspectDR-GTK plot windows:
creator_gtkplot = map(observe(btn_creategtkplot)) do _s
	#Display plot in Gtk GUI:
	gtkgui = display(InspectDR.GtkDisplay(), pobj) #Use same plot object

	#Refresh GUI when plot changes:
	updater_gtkgui = map(updater_plot) do _up
		InspectDR.refresh(gtkgui)
	end
end

#Display main window:
wnd = Window()
body!(wnd, vbox()) #first call to body! does not always work.
body!(wnd, vbox(hbox(vbox(widgetlistA...), vbox(widgetlistB...)), vbox(inline_plot)))

:DEMO_READY


text:
# =============================================================================
# =
# = Export to the FlatZinc format.
# =
# =============================================================================

const START_REG = r"^[^a-zA-Z]"
const NAME_REG = r"[^A-Za-z0-9_]"

"""
    Base.write(io::IO, model::FlatZinc.Model)

Write `model` to `io` in the FlatZinc (fzn) file format.
"""
function Base.write(io::IO, model::Model)
    # Reset the data structures holding set and array names.
    empty!(model.sets_id)
    empty!(model.arrs_id)

    # Ensure variable names are unique.
    MOI.FileFormats.create_unique_variable_names(
        model,
        false,
        [
            s -> match(START_REG, s) !== nothing ? "x" * s : s,
            s -> replace(s, NAME_REG => "_"),
        ],
    )

    # Actually write the contents.
    write_variables(io, model)
    write_sets(io, model)
    write_arrays(io, model)
    write_constraints(io, model)
    write_objective(io, model)

    return
end

function write_variables(io::IO, model::Model)
    for var in values(model.variable_info)
        # Variables either start with "var" or "array of var", let
        # write_variable decide (even though only "var" s implemented for now).
        write_variable(io, var.name, var.set)
        println(io)
    end
    println(io)
    return nothing
end

function write_sets(io::IO, model::Model)
    for cons in model.constraint_info
        if !cons.output_as_part_of_variable
            write_set(io, model, cons, cons.s)
        end
    end
    println(io)
    return nothing
end

function write_arrays(io::IO, model::Model)
    for cons in model.constraint_info
        if !cons.output_as_part_of_variable
            write_array(io, model, cons, cons.s)
        end
    end
    println(io)
    return nothing
end

function write_constraints(io::IO, model::Model)
    for cons in model.constraint_info
        if !cons.output_as_part_of_variable
            print(io, "constraint ")
            write_constraint(io, model, cons.index, cons.f, cons.s)
            print(io, ";")
            println(io)
        end
    end
    println(io)
    return nothing
end

# Variable printing.

function write_variable(io::IO, name::String, s::MOI.EqualTo{Float64})
    print(io, "var float: $(name) :: output_var = $(s.value);")
    return nothing
end

function write_variable(io::IO, name::String, s::MOI.EqualTo{Int})
    print(io, "var int: $(name) :: output_var = $(s.value);")
    return nothing
end

function write_variable(io::IO, name::String, s::MOI.EqualTo{Bool})
    print(io, "var bool: $(name) :: output_var = $(s.value);")
    return nothing
end

function write_variable(io::IO, name::String, s::MOI.LessThan{Float64})
    # typemin(Float64) is -Inf, which is "-Inf" as a string (which is not
    # allowed by FlatZinc). Take the next smallest value as a proxy, because
    # it has a standard scientific notation (and this is allowed in FlatZinc).
    print(io, "var $(nextfloat(typemin(Float64)))..$(s.upper): $(name) :: output_var;")
    return nothing
end

function write_variable(io::IO, name::String, s::MOI.LessThan{Int})
    print(io, "var $(typemin(Int))..$(s.upper): $(name) :: output_var;")
    return nothing
end

function write_variable(io::IO, name::String, s::MOI.GreaterThan{Float64})
    # typemax(Float64) is Inf, which is "Inf" as a string (which is not
    # allowed by FlatZinc). Take the next largest value as a proxy, because
    # it has a standard scientific notation (and this is allowed in FlatZinc).
    print(io, "var $(s.lower)..$(prevfloat(typemax(Float64))): $(name) :: output_var;")
    return nothing
end

function write_variable(io::IO, name::String, s::MOI.GreaterThan{Int})
    print(io, "var $(s.lower)..$(typemax(Int)): $(name) :: output_var;")
    return nothing
end

function write_variable(
    io::IO,
    name::String,
    s::MOI.Interval{T},
) where {T <: Union{Int, Float64}}
    print(io, "var $(s.lower)..$(s.upper): $(name) :: output_var;")
    return nothing
end

function write_variable(io::IO, name::String, ::MOI.Reals)
    print(io, "var float: $(name) :: output_var;")
    return nothing
end

function write_variable(io::IO, name::String, ::MOI.ZeroOne)
    print(io, "var bool: $(name) :: output_var;")
    return nothing
end

function write_variable(io::IO, name::String, ::MOI.Integer)
    print(io, "var int: $(name) :: output_var;")
    return nothing
end

# Set printing.

function write_set(::IO, ::Model, ::ConstraintInfo, ::MOI.AbstractSet)
    # In general, nothing to do.
    return nothing
end

function write_set(
    io::IO,
    model::Model,
    con::ConstraintInfo,
    s::CP.Domain{Int},
)
    set_name = "SET" * string(length(model.sets_id))
    model.sets_id[con.index] = set_name
    set_value = join(collect(s.values), ", ")

    print(io, "set of int: $(set_name) = {$(set_value)};")
    println(io)
    return nothing
end

# Array printing.

function write_array(::IO, ::Model, ::ConstraintInfo, ::MOI.AbstractSet)
    # In general, nothing to do.
    return nothing
end

function write_array(
    io::IO,
    model::Model,
    con::ConstraintInfo,
    s::CP.Element{Bool},
)
    array_name = "ARRAY" * string(length(model.arrs_id))
    model.arrs_id[con.index] = array_name
    array_value = join(collect(s.values), ", ")
    array_length = length(s.values)

    values = join([ifelse(v, "1", "0") for v in s.values], ", ")

    print(
        io,
        "array [1..$(array_length)] of bool: $(array_name) = [$(array_value)];",
    )
    println(io)
    return nothing
end

function write_array(
    io::IO,
    model::Model,
    con::ConstraintInfo,
    s::CP.Element{Int},
)
    array_name = "ARRAY" * string(length(model.arrs_id))
    model.arrs_id[con.index] = array_name
    array_value = join(collect(s.values), ", ")
    array_length = length(s.values)

    print(
        io,
        "array [1..$(array_length)] of int: $(array_name) = [$(array_value)];",
    )
    println(io)
    return nothing
end

function write_array(
    io::IO,
    model::Model,
    con::ConstraintInfo,
    s::CP.Element{Float64},
)
    array_name = "ARRAY" * string(length(model.arrs_id))
    model.arrs_id[con.index] = array_name
    array_value = join(collect(s.values), ", ")
    array_length = length(s.values)

    print(
        io,
        "array [1..$(array_length)] of float: $(array_name) = [$(array_value)];",
    )
    println(io)
    return nothing
end

# Constraint printing.
# Based on the built-in predicates: https://www.minizinc.org/doc-2.5.5/en/lib-flatzinc.html
# In the same order as the documentation.

# - Dispatch on variable types, if needed.

function _promote_type(model::MOI.ModelLike, vars::Vector{MOI.VariableIndex})
    smallest = :bool

    for var in vars
        if smallest == :bool && !CP.is_binary(model, var)
            smallest = :int
        end
        if smallest == :int && !CP.is_integer(model, var)
            smallest = :float
            break
        end
    end

    return smallest
end

function write_constraint(
    io::IO,
    model::Model,
    index::MOI.ConstraintIndex,
    f::MOI.VariableIndex,
    s::Union{
        MOI.EqualTo{T},
        MOI.LessThan{T},
        CP.Strictly{MOI.LessThan{T}},
        CP.DifferentFrom{T},
    },
) where {T}
    # *_eq, *_le, *_lt, *_ne
    write_constraint(
        io,
        model,
        index,
        f,
        s,
        Val(_promote_type(model, [f])),
    )
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    index::MOI.ConstraintIndex,
    f::MOI.ScalarAffineFunction{T},
    s::Union{MOI.EqualTo{U}, MOI.LessThan{U}},
) where {T, U}
    # *_lin_eq, *_lin_le
    variables, _ = _saf_to_coef_vars(f)
    write_constraint(
        io,
        model,
        index,
        f,
        s,
        Val(_promote_type(model, variables)),
    )
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    index::MOI.ConstraintIndex,
    f::MOI.VectorOfVariables,
    s::Union{
        CP.Reification{MOI.EqualTo{T}},
        CP.Reification{MOI.LessThan{T}},
        CP.Reification{CP.Strictly{MOI.LessThan{T}, T}},
        CP.Reification{CP.DifferentFrom{T}},
    },
) where {T}
    # *_eq_reif, *_le_reif, *_lt_reif, *_ne_reif
    write_constraint(
        io,
        model,
        index,
        f,
        s,
        Val(_promote_type(model, f.variables)),
    )
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    index::MOI.ConstraintIndex,
    f::MOI.VectorAffineFunction{T},
    s::Union{
        CP.Reification{MOI.EqualTo{U}},
        CP.Reification{MOI.LessThan{U}},
        CP.Reification{CP.Strictly{MOI.LessThan{U}, U}},
        CP.Reification{CP.DifferentFrom{T}},
    },
) where {T, U}
    # *_lin_eq_reif, *_lin_le_reif, *_lin_lt_reif, *_lin_ne_reif
    variables = _vaf_to_vars(f)
    write_constraint(
        io,
        model,
        index,
        f,
        s,
        Val(_promote_type(model, variables)),
    )
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    index::MOI.ConstraintIndex,
    f::MOI.VectorOfVariables,
    s::Union{CP.MaximumAmong, CP.MinimumAmong},
)
    # array_*_maximum, array_*_minimum
    write_constraint(
        io,
        model,
        index,
        f,
        s,
        Val(_promote_type(model, f.variables)),
    )
    return nothing
end

# - Integer constraints.

function write_constraint(
    io::IO,
    model::Model,
    index::MOI.ConstraintIndex,
    f::MOI.VectorOfVariables,
    ::CP.Element{Int},
)
    @assert MOI.output_dimension(f) == 2
    @assert CP.is_integer(model, f.variables[1])
    @assert CP.is_integer(model, f.variables[2])

    array_name = model.arrs_id[index]
    value = f.variables[1]
    index = f.variables[2]
    print(
        io,
        "array_int_element($(_fzn_f(model, index)), $(array_name), $(_fzn_f(model, value)))",
    )
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.VectorOfVariables,
    ::CP.MaximumAmong,
    ::Val{:int},
)
    array = f.variables[2:end]
    value = f.variables[1]
    print(
        io,
        "array_int_maximum($(_fzn_f(model, value)), [$(_fzn_f(model, array))])",
    )
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.VectorOfVariables,
    ::CP.MinimumAmong,
    ::Val{:int},
)
    array = f.variables[2:end]
    value = f.variables[1]
    print(
        io,
        "array_int_minimum($(_fzn_f(model, value)), [$(_fzn_f(model, array))])",
    )
    return nothing
end

# TODO: absolute value. int_abs.
# TODO: integer division. int_div

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.VariableIndex,
    s::MOI.LessThan{Int},
    ::Val{:int},
)
    @assert CP.is_integer(model, f) || CP.is_binary(model, f)
    print(io, "int_le($(_fzn_f(model, f)), $(s.upper))")
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.VariableIndex,
    s::MOI.LessThan{Bool},
    ::Val{:bool},
)
    @assert CP.is_binary(model, f)
    print(io, "bool_le($(_fzn_f(model, f)), $(Bool(s.upper)))")
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.VectorOfVariables,
    s::CP.Reification{MOI.LessThan{Int}},
    ::Val{:int},
)
    @assert MOI.output_dimension(f) == 2
    @assert CP.is_binary(model, f.variables[1])
    @assert CP.is_integer(model, f.variables[2]) ||
            CP.is_binary(model, f.variables[2])

    print(
        io,
        "int_le_reif($(_fzn_f(model, f.variables[1])), $(s.set.upper), $(_fzn_f(model, f.variables[1])))",
    )
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.VariableIndex,
    s::MOI.EqualTo{Int},
    ::Val{:int},
)
    # Hypothesis: !cons.output_as_part_of_variable.
    print(io, "int_eq($(_fzn_f(model, f)), $(s.value))")
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.ScalarAffineFunction,
    s::MOI.EqualTo{Int},
    ::Val{:int},
)
    variables, coefficients = _saf_to_coef_vars(f)
    value = s.value - f.constant
    print(
        io,
        "int_lin_eq($(coefficients), [$(_fzn_f(model, variables))], $(value))",
    )
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.VectorOfVariables,
    s::CP.Reification{MOI.EqualTo{Int}},
    ::Val{:int},
)
    @assert MOI.output_dimension(f) == 2
    @assert CP.is_binary(model, f.variables[1])

    print(
        io,
        "int_eq_reif($(_fzn_f(model, f.variables[2])), $(s.set.value), $(_fzn_f(model, f.variables[1])))",
    )
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.VectorAffineFunction,
    s::CP.Reification{MOI.EqualTo{Int}},
    ::Val{:int},
)
    @assert MOI.output_dimension(f) == 2
    vars_1, coeffs_1 = _vaf_to_coef_vars(f, 1)
    @assert length(vars_1) == 1
    @assert length(coeffs_1) == 1
    @assert CP.is_binary(model, vars_1[1])
    for v in _vaf_to_vars(f, 2)
        @assert CP.is_integer(model, v) || CP.is_binary(model, v)
    end
    @assert coeffs_1[1] == 1

    variables, coefficients, constant = _vaf_to_coef_vars(f, 2)
    value = s.set.value - constant

    print(
        io,
        "int_lin_eq_reif($(coefficients), [$(_fzn_f(model, variables))], $(value), $(_fzn_f(model, vars_1[1])))",
    )
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.ScalarAffineFunction,
    s::MOI.LessThan{Int},
    ::Val{:int},
)
    variables, coefficients = _saf_to_coef_vars(f)
    value = s.upper - f.constant
    print(
        io,
        "int_lin_le($(coefficients), [$(_fzn_f(model, variables))], $(value))",
    )
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.VariableIndex,
    s::CP.DifferentFrom{Int},
)
    @assert CP.is_integer(model, f) || CP.is_binary(model, f)
    print(io, "int_ne($(_fzn_f(model, f)), $(s.value))")
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.VariableIndex,
    s::CP.DifferentFrom{Bool},
)
    @assert CP.is_binary(model, f)
    print(io, "int_ne($(_fzn_f(model, f)), $(s.value))")
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.ScalarAffineFunction,
    s::CP.DifferentFrom{Bool},
)
    variables, coefficients = _saf_to_coef_vars(f)
    for v in variables
        @assert CP.is_binary(model, v)
    end

    value = s.value - f.constant
    print(
        io,
        "int_lin_ne($(coefficients), [$(_fzn_f(model, variables))], $(value))",
    )
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.ScalarAffineFunction,
    s::CP.DifferentFrom{Int},
)
    variables, coefficients = _saf_to_coef_vars(f)
    for v in variables
        @assert CP.is_integer(model, v) || CP.is_binary(model, v)
    end

    value = s.value - f.constant
    print(
        io,
        "int_lin_ne($(coefficients), [$(_fzn_f(model, variables))], $(value))",
    )
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.VectorOfVariables,
    s::CP.Reification{CP.DifferentFrom{Int}},
    ::Val{:int},
)
    @assert MOI.output_dimension(f) == 2
    @assert CP.is_binary(model, f.variables[1])
    @assert CP.is_integer(model, f.variables[2])

    print(
        io,
        "int_ne_reif($(_fzn_f(model, f.variables[2])), $(s.set.value), $(_fzn_f(model, f.variables[1])))",
    )
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.VectorAffineFunction,
    s::CP.Reification{CP.DifferentFrom{Int}},
    ::Val{:int},
)
    @assert MOI.output_dimension(f) == 2
    vars_1, coeffs_1 = _vaf_to_coef_vars(f, 1)
    @assert length(vars_1) == 1
    @assert length(coeffs_1) == 1
    @assert CP.is_binary(model, vars_1[1])
    for v in _vaf_to_vars(f, 2)
        @assert CP.is_integer(model, v) || CP.is_binary(model, v)
    end
    @assert coeffs_1[1] == 1

    variables, coefficients, constant = _vaf_to_coef_vars(f, 2)
    value = s.set.value - constant

    print(
        io,
        "int_lin_ne_reif($(coefficients), [$(_fzn_f(model, variables))], $(value), $(_fzn_f(model, vars_1[1])))",
    )
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.VariableIndex,
    s::CP.Strictly{MOI.LessThan{Int}},
)
    @assert CP.is_integer(model, f) || CP.is_binary(model, f)
    print(io, "int_lt($(_fzn_f(model, f)), $(s.set.upper))")
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.VariableIndex,
    s::CP.Strictly{MOI.LessThan{Bool}},
)
    @assert CP.is_binary(model, f)
    print(io, "bool_lt($(_fzn_f(model, f)), $(s.set.upper))")
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.VectorOfVariables,
    s::CP.Reification{CP.Strictly{MOI.LessThan{Int}, Int}},
    ::Val{:int},
)
    @assert MOI.output_dimension(f) == 2
    @assert CP.is_binary(model, f.variables[1])
    @assert CP.is_integer(model, f.variables[2])

    print(
        io,
        "int_lt_reif($(_fzn_f(model, f.variables[2])), $(s.set.set.upper), $(_fzn_f(model, f.variables[1])))",
    )
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.VectorAffineFunction,
    s::CP.Reification{CP.Strictly{MOI.LessThan{Int}, Int}},
    ::Val{:int},
)
    @assert MOI.output_dimension(f) == 2
    vars_1, coeffs_1 = _vaf_to_coef_vars(f, 1)
    @assert length(vars_1) == 1
    @assert length(coeffs_1) == 1
    @assert CP.is_binary(model, vars_1[1])
    for v in _vaf_to_vars(f, 2)
        @assert CP.is_integer(model, v) || CP.is_binary(model, v)
    end
    @assert coeffs_1[1] == 1

    variables, coefficients, constant = _vaf_to_coef_vars(f, 2)
    value = s.set.set.upper - constant

    print(
        io,
        "int_lin_lt_reif($(coefficients), [$(_fzn_f(model, variables))], $(value), $(_fzn_f(model, vars_1[1])))",
    )
    return nothing
end

# TODO: int_max (CP equivalent!?)
# TODO: int_min (CP equivalent!?)
# TODO: int_mod, modulo

# int_ne, int_ne_reif: meaningless for MOI, no way to represent "x == y"
# natively (goes through affine expressions).

# TODO: int_pow.
# TODO: int_times.

function write_constraint(
    io::IO,
    model::Model,
    index::MOI.ConstraintIndex,
    f::MOI.VariableIndex,
    ::CP.Domain{Int},
)
    print(io, "set_in($(_fzn_f(model, f)), $(model.sets_id[index]))")
    return nothing
end

# - Boolean constraints.

# TODO: array_bool_and, no conjunction between variables for now in CP.

function write_constraint(
    io::IO,
    model::Model,
    index::MOI.ConstraintIndex,
    f::MOI.VectorOfVariables,
    ::CP.Element{Bool},
)
    @assert MOI.output_dimension(f) == 2
    @assert CP.is_binary(model, f.variables[1])
    @assert CP.is_binary(model, f.variables[2])

    array_name = model.arrs_id[index]
    value = f.variables[1]
    index = f.variables[2]

    print(
        io,
        "array_bool_element($(_fzn_f(model, index)), $(array_name), $(_fzn_f(model, value)))",
    )
    return nothing
end

# TODO: array_bool_or, no disjunction between variables for now in CP.
# TODO: array_bool_xor, no XOR for now in CP.
# TODO: array_var_bool_element, no CP.Element for array of variables.
# TODO: bool2int, not in CP for now.
# TODO: bool_and, like array_bool_and.
# TODO: bool_clause, not in CP for now.

# bool_eq, bool_eq_reif: meaningless for MOI, no way to represent "x == y"
# natively (goes through affine expressions).
# bool_le, bool_le_reif: meaningless for MOI, no way to represent "x <= y"
# natively (goes through affine expressions).

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.VariableIndex,
    s::MOI.EqualTo{T},
    ::Val{:bool},
) where {T <: Union{Int, Bool}}
    # Hypothesis: !cons.output_as_part_of_variable.
    print(io, "bool_eq($(_fzn_f(model, f)), $(Bool(s.value)))")
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.ScalarAffineFunction,
    s::MOI.EqualTo{T},
    ::Val{:bool},
) where {T <: Union{Int, Bool}}
    variables, coefficients = _saf_to_coef_vars(f)
    value = s.value - f.constant
    print(
        io,
        "bool_lin_eq($(coefficients), [$(_fzn_f(model, variables))], $(value))",
    )
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.ScalarAffineFunction,
    s::MOI.LessThan{Int},
    ::Val{:bool},
)
    variables, coefficients = _saf_to_coef_vars(f)
    value = s.upper - f.constant
    print(
        io,
        "bool_lin_le($(coefficients), [$(_fzn_f(model, variables))], $(value))",
    )
    return nothing
end

# bool_lt, bool_lt_reif: meaningless for MOI, no way to represent "x < y"
# natively (goes through affine expressions).
# bool_ne, bool_ne_reif: meaningless for MOI, no way to represent "x != y"
# natively (goes through affine expressions).

# TODO: bool_or, no disjunction between variables for now in CP.
# TODO: bool_xor, no XOR between variables for now in CP.

# - Set constraints.

# TODO: no notion of set in MOI!

# - Float constraints.

function write_constraint(
    io::IO,
    model::Model,
    index::MOI.ConstraintIndex,
    f::MOI.VectorOfVariables,
    ::CP.Element{Float64},
)
    @assert MOI.output_dimension(f) == 2

    array_name = model.arrs_id[index]
    value = f.variables[1]
    index = f.variables[2]

    print(
        io,
        "array_float_element($(_fzn_f(model, index)), $(array_name), $(_fzn_f(model, value)))",
    )
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.VectorOfVariables,
    ::CP.MaximumAmong,
    ::Val{:float},
)
    array = f.variables[2:end]
    value = f.variables[1]
    return print(
        io,
        "array_float_maximum($(_fzn_f(model, value)), [$(_fzn_f(model, array))])",
    )
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.VectorOfVariables,
    ::CP.MinimumAmong,
    ::Val{:float},
)
    array = f.variables[2:end]
    value = f.variables[1]
    return print(
        io,
        "array_float_minimum($(_fzn_f(model, value)), [$(_fzn_f(model, array))])",
    )
end

# TODO: array_var_float_element, i.e. CP.Element with a variable array.

# TODO: float_abs, float_acos, float_acosh, float_asin, float_asinh,
# float_atan, float_atanh, float_cos, float_cosh, float_div.

# float_dom: could be useful to merge several MOI.Interval as one constraint,
# for now several float_in.

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.VariableIndex,
    s::MOI.EqualTo{Float64},
    ::Val{:float},
)
    # Hypothesis: !cons.output_as_part_of_variable.
    print(io, "float_eq($(_fzn_f(model, f)), $(s.value))")
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.VectorOfVariables,
    s::CP.Reification{MOI.EqualTo{Float64}},
    ::Val{:float},
)
    @assert MOI.output_dimension(f) == 2
    @assert CP.is_binary(model, f.variables[1])

    print(
        io,
        "float_eq_reif($(_fzn_f(model, f.variables[2])), $(s.set.value), $(_fzn_f(model, f.variables[1])))",
    )
    return nothing
end

# TODO: float_exp

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.VariableIndex,
    s::MOI.Interval{Float64},
)
    print(io, "float_in($(_fzn_f(model, f)), $(s.lower), $(s.upper))")
    return nothing
end

# TODO: float_in_reif

# float_le, float_le_reif: meaningless for MOI, no way to represent "x <= y"
# natively (goes through affine expressions).

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.ScalarAffineFunction,
    s::MOI.EqualTo{Float64},
    ::Val{:float},
)
    variables, coefficients = _saf_to_coef_vars(f)
    value = Float64(s.value - f.constant)
    print(
        io,
        "float_lin_eq($(coefficients), [$(_fzn_f(model, variables))], $(value))",
    )
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.VectorAffineFunction,
    s::CP.Reification{MOI.EqualTo{Float64}},
    ::Val{:float},
)
    @assert MOI.output_dimension(f) == 2
    vars_1, coeffs_1 = _vaf_to_coef_vars(f, 1)
    @assert length(vars_1) == 1
    @assert length(coeffs_1) == 1
    @assert CP.is_binary(model, vars_1[1])
    @assert coeffs_1[1] == 1

    variables, coefficients, constant = _vaf_to_coef_vars(f, 2)
    value = Float64(s.set.value - constant)

    print(
        io,
        "float_lin_eq_reif($(coefficients), [$(_fzn_f(model, variables))], $(value), $(_fzn_f(model, vars_1[1])))",
    )
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.VariableIndex,
    s::MOI.LessThan{Float64},
    ::Val{:float},
)
    print(io, "float_le($(_fzn_f(model, f)), $(s.upper))")
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.VariableIndex,
    s::CP.Strictly{MOI.LessThan{Float64}, Float64},
    ::Val{:float},
)
    print(io, "float_lt($(_fzn_f(model, f)), $(s.set.upper))")
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.ScalarAffineFunction,
    s::MOI.LessThan{Float64},
)
    variables, coefficients = _saf_to_coef_vars(f)
    value = Float64(s.upper - f.constant)
    print(
        io,
        "float_lin_le($(coefficients), [$(_fzn_f(model, variables))], $(value))",
    )
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.VectorOfVariables,
    s::CP.Reification{MOI.LessThan{Float64}},
    ::Val{:float},
)
    @assert MOI.output_dimension(f) == 2
    @assert CP.is_binary(model, f.variables[1])

    print(
        io,
        "float_le_reif($(_fzn_f(model, f.variables[2])), $(s.set.upper), $(_fzn_f(model, f.variables[1])))",
    )
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.VectorAffineFunction,
    s::CP.Reification{MOI.LessThan{Int}},
    ::Val{:int},
)
    @assert MOI.output_dimension(f) == 2
    vars_1, coeffs_1 = _vaf_to_coef_vars(f, 1)
    @assert length(vars_1) == 1
    @assert length(coeffs_1) == 1
    @assert CP.is_binary(model, vars_1[1])
    for v in _vaf_to_vars(f, 2)
        @assert CP.is_integer(model, v) || CP.is_binary(model, v)
    end
    @assert coeffs_1[1] == 1

    variables, coefficients, constant = _vaf_to_coef_vars(f, 2)
    value = s.set.upper - constant

    print(
        io,
        "int_lin_le_reif($(coefficients), [$(_fzn_f(model, variables))], $(value), $(_fzn_f(model, vars_1[1])))",
    )
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.VectorAffineFunction,
    s::CP.Reification{MOI.LessThan{Float64}},
    ::Val{:float},
)
    @assert MOI.output_dimension(f) == 2
    vars_1, coeffs_1 = _vaf_to_coef_vars(f, 1)
    @assert length(vars_1) == 1
    @assert length(coeffs_1) == 1
    @assert CP.is_binary(model, vars_1[1])
    @assert coeffs_1[1] == 1

    variables, coefficients, constant = _vaf_to_coef_vars(f, 2)
    value = Float64(s.set.upper - constant)

    print(
        io,
        "float_lin_le_reif($(coefficients), [$(_fzn_f(model, variables))], $(value), $(_fzn_f(model, vars_1[1])))",
    )
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.ScalarAffineFunction,
    s::CP.Strictly{MOI.LessThan{Float64}},
)
    variables, coefficients = _saf_to_coef_vars(f)
    value = Float64(s.set.upper - f.constant)
    print(
        io,
        "float_lin_lt($(coefficients), [$(_fzn_f(model, variables))], $(value))",
    )
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.VectorOfVariables,
    s::CP.Reification{CP.Strictly{MOI.LessThan{Float64}, Float64}},
    ::Val{:float},
)
    @assert MOI.output_dimension(f) == 2
    @assert CP.is_binary(model, f.variables[1])

    print(
        io,
        "float_lt_reif($(_fzn_f(model, f.variables[2])), $(s.set.set.upper), $(_fzn_f(model, f.variables[1])))",
    )
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.VectorAffineFunction,
    s::CP.Reification{CP.Strictly{MOI.LessThan{Float64}, Float64}},
    ::Val{:float},
)
    @assert MOI.output_dimension(f) == 2
    vars_1, coeffs_1 = _vaf_to_coef_vars(f, 1)
    @assert length(vars_1) == 1
    @assert length(coeffs_1) == 1
    @assert CP.is_binary(model, vars_1[1])
    @assert coeffs_1[1] == 1

    variables, coefficients, constant = _vaf_to_coef_vars(f, 2)
    value = Float64(s.set.set.upper - constant)

    print(
        io,
        "float_lin_lt_reif($(coefficients), [$(_fzn_f(model, variables))], $(value), $(_fzn_f(model, vars_1[1])))",
    )
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.ScalarAffineFunction,
    s::CP.DifferentFrom{Float64},
)
    variables, coefficients = _saf_to_coef_vars(f)
    value = Float64(s.value - f.constant)
    print(
        io,
        "float_lin_ne($(coefficients), [$(_fzn_f(model, variables))], $(value))",
    )
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.VariableIndex,
    s::CP.DifferentFrom{Float64},
)
    print(io, "float_ne($(_fzn_f(model, f)), $(s.value))")
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.VectorOfVariables,
    s::CP.Reification{CP.DifferentFrom{Float64}},
    ::Val{:float},
)
    @assert MOI.output_dimension(f) == 2
    @assert CP.is_binary(model, f.variables[1])

    print(
        io,
        "float_ne_reif($(_fzn_f(model, f.variables[2])), $(s.set.value), $(_fzn_f(model, f.variables[1])))",
    )
    return nothing
end

function write_constraint(
    io::IO,
    model::Model,
    ::MOI.ConstraintIndex,
    f::MOI.VectorAffineFunction,
    s::CP.Reification{CP.DifferentFrom{Float64}},
    ::Val{:float},
)
    @assert MOI.output_dimension(f) == 2
    vars_1, coeffs_1 = _vaf_to_coef_vars(f, 1)
    @assert length(vars_1) == 1
    @assert length(coeffs_1) == 1
    @assert CP.is_binary(model, vars_1[1])
    @assert coeffs_1[1] == 1

    variables, coefficients, constant = _vaf_to_coef_vars(f, 2)
    value = Float64(s.set.value - constant)

    print(
        io,
        "float_lin_ne_reif($(coefficients), [$(_fzn_f(model, variables))], $(value), $(_fzn_f(model, vars_1[1])))",
    )
    return nothing
end

# TODO: float_ln, float_log10, float_log2

# float_lt, float_lt_reif: meaningless for MOI, no way to represent "x < y"
# natively (goes through affine expressions).

# TODO: float_max (CP equivalent!?)
# TODO: float_min (CP equivalent!?)

# float_net, float_ne_reif: meaningless for MOI, no way to represent "x != y"
# natively (goes through affine expressions).

# TODO: float_pow, float_sin, float_sinh, float_sqrt, float_tan, float_tanh, float_times
# TODO: int2float, not in CP for now.

# - MiniZinc 2.0.0
# TODO: bool_clause_reif.

# - MiniZinc 2.0.2
# TODO: array_var_bool_element_nonshifted, array_var_float_element_nonshifted,
# array_var_int_element_nonshifted, array_var_set_element_nonshifted

# - MiniZinc 2.1.0
# Already included above, like in the docs.

# - MiniZinc 2.2.1
# TODO: int_pow_fixed

# - MiniZinc 2.3.3
# TODO: float_set_in

# - MiniZinc 2.5.2
# TODO: array_var_bool_element2d_nonshifted, array_var_float_element2d_nonshifted,
# array_var_int_element2d_nonshifted, array_var_set_element2d_nonshifted

# Objective printing.

function write_objective(io::IO, model::Model)
    print(io, "solve ")
    if model.objective_sense == MOI.FEASIBILITY_SENSE &&
       model.objective_function === nothing
        print(io, "satisfy")
    elseif model.objective_sense == MOI.MIN_SENSE &&
           model.objective_function !== nothing
        print(io, "minimize $(_fzn_f(model, model.objective_function))")
    elseif model.objective_sense == MOI.MAX_SENSE &&
           model.objective_function !== nothing
        print(io, "maximize $(_fzn_f(model, model.objective_function))")
    else
        error(
            "Assertion failed when printing the objective. Sense: $(model.objective_sense). Function: $(model.objective_function).",
        )
    end
    print(io, ";")
    println(io)
    return nothing
end

# Function printing.

_fzn_f(model::Model, f::MOI.VariableIndex) = model.variable_info[f].name
function _fzn_f(model::Model, fs::Vector{MOI.VariableIndex})
    return join([_fzn_f(model, f) for f in fs], ", ")
end

# Destructuring.

function _saf_to_coef_vars(f::MOI.ScalarAffineFunction)
    MOIU.canonicalize!(f)
    variables = MOI.VariableIndex[t.variable for t in f.terms]
    coefficients = [t.coefficient for t in f.terms]

    return variables, coefficients
end

function _vaf_to_vars(f::MOI.VectorAffineFunction)
    MOIU.canonicalize!(f)
    variables = MOI.VariableIndex[t.scalar_term.variable for t in f.terms]
    return variables
end

function _vaf_to_vars(f::MOI.VectorAffineFunction, dim::Int)
    MOIU.canonicalize!(f)
    variables = MOI.VariableIndex[
        t.scalar_term.variable for t in f.terms if t.output_index == dim
    ]
    return variables
end

function _vaf_to_coef_vars(f::MOI.VectorAffineFunction, dim::Int)
    MOIU.canonicalize!(f)
    variables = MOI.VariableIndex[
        t.scalar_term.variable for t in f.terms if t.output_index == dim
    ]
    coefficients =
        [t.scalar_term.coefficient for t in f.terms if t.output_index == dim]
    constant = f.constants[dim]

    return variables, coefficients, constant
end