using MesocosmMetabolism, CSV, Turing, DataFrames, MCMCChains, StatsPlots, Interpolations, ReverseDiff

data = CSV.read("/Users/elizabethmohr/Documents/MSU/RProjects/stream-analogue-mesocosms/data/DO.csv", DataFrame)
Q = 1.794 #L/hr
V = 27.0 # L
dayStart = 4

# Note: these priors are taken from the default values in streamMetabolizer,
# converted to units of hours and converted to a per volume basis
# by dividing by mean depth in SAMS of 0.15 m.
GPP_daily_mu = 1.72 # g/(L*hr) - only applies to 12 hours when lights are on
GPP_daily_sigma = 3.33
ER_daily_mu = -2.0 # g/(L*hr)
ER_daily_sigma = 2.0
k600_meanlog = -0.69 # hr^-1
k600_sdlog = 0.53
K02_conv = 1.0
err_obs_iid_sigma_scale = 0.03
err_proc_iid_sigma_scale = 5.0

setadbackend(:reversediff)

results = Dict()
for i in unique(data[:,:loggerID])[1:4]
    subset = filter(r -> r[:loggerID] == i, data)
    times = subset.modelTime
    PAR = Interpolations.ConstantInterpolation((times,), subset.PAR)
    OSat = Interpolations.interpolate((times,), subset."DO.sat", Interpolations.Gridded(Interpolations.Linear()))
    days = [convert(Int64, floor(times[i]/24.0 - (dayStart/24.0)) + 1) for i in 1:length(times)]
    day = Interpolations.ConstantInterpolation((times,), days)
    u0 = [subset.DO_mgL[1]]
    nDays = convert(Int64,ceil((maximum(times) - minimum(times))/24))
    problem = initialize_process_model(
        times = times,
        PAR = PAR,
        OSat = OSat,
        day = day,
        GPP_daily = fill(GPP_daily_mu, nDays),
        ER_daily = fill(ER_daily_mu, nDays),
        kGas = exp(k600_meanlog),
        Q = Q,
        V = V,
        dayStart = dayStart,
        u0 = u0)
    model = statisticalModel(
        nDays,
        GPP_daily_mu,
        GPP_daily_sigma,
        ER_daily_mu,
        ER_daily_sigma,
        k600_meanlog,
        k600_sdlog,
        K02_conv,
        err_obs_iid_sigma_scale,
        err_proc_iid_sigma_scale)

    sampleMe = model(DOdata = subset.DO_mgL, ODE = problem,times = times)
    chains = sample(sampleMe, NUTS(), MCMCThreads(), 1000, 3)
    results[i] = chains
end

gelmandiag(chains)

df = DataFrame(chains)

# GPP trace plot
@df df plot(
    :iteration,
    cols(columnindex(df,"GPP_daily[1]")),
    group = (:chain),
    lw = 3,
    xlabel = "Iteration",
    ylabel = "GPP",
    legendtitle = "Chain",
    xguidefontsize=15,
    yguidefontsize = 15)

# GPP posterior and prior
@df df density(
    cols(columnindex(df,"GPP_daily[1]")),
    group = (:chain),
    lw = 3,
    xlabel = "GPP",
    ylabel = "Probability Density",
    legendtitle = "Chain",
    xguidefontsize=15,
    yguidefontsize = 15)

plot!(Normal(GPP_daily_mu, GPP_daily_sigma),lw = 3)

# ER trace plot
@df df plot(
    :iteration,
    cols(columnindex(df,"ER_daily[1]")),
    group = (:chain),
    lw = 3,
    xlabel = "Iteration",
    ylabel = "ER",
    legendtitle = "Chain",
    xguidefontsize=15,
    yguidefontsize = 15)

# ER posterior and prior
@df df density(
    cols(columnindex(df,"ER_daily[1]")),
    group = (:chain),
    lw = 3,
    xlabel = "ER",
    ylabel = "Probability Density",
    legendtitle = "Chain",
    xguidefontsize= 15,
    yguidefontsize = 15)

plot!(Normal(ER_daily_mu, ER_daily_sigma),lw = 3)

# k600 trace plot
@df df plot(
    :iteration,
    cols(columnindex(df,"k600")),
    group = (:chain),
    lw = 3,
    xlabel = "Iteration",
    ylabel = "k600",
    legendtitle = "Chain",
    xguidefontsize=15,
    yguidefontsize = 15)

# k600 posterior and prior
@df df density(
    cols(columnindex(df,"k600")),
    group = (:chain),
    lw = 3,
    xlabel = "k600",
    ylabel = "Probability Density",
    legendtitle = "Chain",
    xguidefontsize= 15,
    yguidefontsize = 15)

plot!(LogNormal(k600_meanlog, k600_sdlog),lw = 3)

# Plot data with modeled DO from multiple samples
using DifferentialEquations
logger = 415148
MesocosmData = filter(r -> r[:loggerID] == logger, data)
df = DataFrame(results[logger])
times = MesocosmData.modelTime
PAR = Interpolations.ConstantInterpolation((times,), MesocosmData.PAR)
OSat = Interpolations.interpolate((times,), MesocosmData."DO.sat", Interpolations.Gridded(Interpolations.Linear()))
days = [convert(Int64, floor(times[i]/24.0 - (dayStart/24.0)) + 1) for i in 1:length(times)]
day = Interpolations.ConstantInterpolation((times,), days)
u0 = [MesocosmData.DO_mgL[1]]
nDays = convert(Int64,ceil((maximum(times) - minimum(times))/24))

DOPlot = StatsPlots.scatter(
    MesocosmData.modelTime,
    MesocosmData.DO_mgL,
    ylabel = "DO (mg/L)",
    xguidefontsize= 15,
    yguidefontsize = 15);

n = 100
rows = rand(1:nrow(df), n)

for k in 1:n
    GPP = Array(df[k, r"^GPP_daily"])
    ER = Array(df[k, r"^ER_daily"])
    k600 = df[k, "k600"]
    problem = initialize_process_model(
        times = MesocosmData.modelTime,
        PAR = PAR,
        OSat = OSat,
        day = day,
        GPP_daily = GPP,
        ER_daily = ER,
        kGas = k600,
        Q = Q,
        V = V,
        dayStart = dayStart,
        u0 = u0)
    sol = solve(problem, alg = Euler(), dt = 0.01)
    plot!(sol, alpha=0.3, color = "#BBBBBB", legend = :false)
end
xlabel!("Hour of day")

display(DOPlot)

# Plot prior distributions
using Plots, Distributions, StatsPlots
plot(Normal(GPP_daily_mu, GPP_daily_sigma))
plot(Normal(ER_daily_mu, ER_daily_sigma))
plot(LogNormal(k600_meanlog, k600_sdlog))
plot(truncated(Cauchy(0, err_obs_iid_sigma_scale), 0, Inf))
plot(truncated(Cauchy(0, err_proc_iid_sigma_scale), 0, Inf))


"""
integrate a TaylorNModel with respect to the variable number `which`.
Optionally adds `x0` to the result.
"""
function integrate(f::TaylorNModel, which=1, x0=0)

    p = integrate(f.p, which)  # not necessary if an already complete Taylor series, in which case p2 == f.p

    Δ = integral_bound(f, which)

    g = TaylorNModel(f.n, f.x0, f.I, p, Δ)

    g.p[0] += x0  # constant term

    # for k in 0:(x0.order)
    #     TaylorSeries.add!(g.p, g.p, x0, k)
    # end

    return g

end

doc"""
Bound the integral of a `TaylorNModel` `f` with respect to the variable `which`.
"""
function integral_bound(f::TaylorNModel, which)

    high_order_term = f.p[end]  # a HomogeneousPolynomial

    Δ = ( bound(high_order_term, f.x0, f.I) + f.Δ ) * diam(f.I[which])

    return Δ
end

bound(f::HomogeneousPolynomial, x0, I) = f( [(I - x0)...] )
# applies the hom poly to the bounds


# routines for front end.

# fits a positive function via SDP2.
function fitnDdensity(  y::Vector{T},
                        X::Vector{Vector{T}},
                        μ::T, # regularization weight.
                        θ::KT,
                        zero_tol::T,
                        max_iters::Int ) where {T,KT}
    #
    # fit.
    K = constructkernelmatrix(X, θ)
    α_solution = fitpdfviaSDP(y, K, μ, max_iters)
    α = clamp.(α_solution, zero_tol, Inf)
    η = RKHSProblemType(α, X, θ, μ)

    return η, α
end

# function fitnDdensityRiemannian(  y::Vector{T},
#                                 X::Vector{Vector{T}},
#                                 μ::T, # regularization weight.
#                                 θ::KT,
#                                 zero_tol::T,
#                                 max_iter::Int;
#                                 α_initial = ones(T, length(y)),     ## initial guess.
#                                 verbose_flag = false,
#                                 max_iter_tCG = 100,
#                                 ρ_lower_acceptance = 0.2, # recommended to be less than 0.25
#                                 ρ_upper_acceptance = 5.0,
#                                 minimum_TR_radius::T = 1e-3,
#                                 maximum_TR_radius::T = 10.0,
#                                 norm_df_tol = 1e-5,
#                                 objective_tol = 1e-5,
#                                 avg_Δf_tol = 0.0, #1e-12 #1e-5
#                                 avg_Δf_window = 10,
#                                 max_idle_update_count = 50,
#                                 g::Function = pp->1.0/(dot(pp,pp)+1.0),
#                                 𝑟 = 1e-2 ) where {T,KT}
#     #
#     # fit.
#     K = constructkernelmatrix(X, θ)
#     α_solution, f_α_array_unused, norm_df_array_unused,
#         num_iters_unused = RiemannianOptim.solveRKHSℝpproblem(y, K, μ;
#                             max_iter = max_iter,
#                             α_initial = α_initial,
#                             verbose_flag = verbose_flag,
#                             max_iter_tCG = max_iter_tCG,
#                             ρ_lower_acceptance = ρ_lower_acceptance,
#                             ρ_upper_acceptance = ρ_upper_acceptance,
#                             minimum_TR_radius = minimum_TR_radius,
#                             maximum_TR_radius = maximum_TR_radius,
#                             norm_df_tol = norm_df_tol,
#                             objective_tol = objective_tol,
#                             avg_Δf_tol = avg_Δf_tol,
#                             avg_Δf_window = avg_Δf_window,
#                             max_idle_update_count = max_idle_update_count,
#                             𝑟 = 𝑟,
#                             g = g)

#     α = clamp.(α_solution, zero_tol, Inf)
#     η = RKHSProblemType(α, X, θ, μ)

#     return η, α
# end


# This z resolution is get from
zs_NCAR_LENS = - [
    0, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000,
    11000, 12000, 13000, 14000, 15000, 16000, 17019.681640625,
    18076.12890625, 19182.125, 20349.931640625, 21592.34375, 22923.3125,
    24358.453125, 25915.580078125, 27615.259765625, 29481.470703125,
    31542.373046875, 33831.2265625, 36387.47265625, 39258.046875,
    42498.88671875, 46176.65625, 50370.6875, 55174.91015625, 60699.66796875,
    67072.859375, 74439.8046875, 82960.6953125, 92804.3515625,
    104136.8203125, 117104.015625, 131809.359375, 148290.078125,
    166499.203125, 186301.4375, 207487.390625, 229803.90625, 252990.40625,
    276809.84375, 301067.0625, 325613.84375, 350344.875, 375189.1875,
    400101.15625, 425052.46875, 450026.0625, 475012, 500004.6875,
    525000.9375, 549999.0625
] / 100.0;

#############################################################################
# 2019/05/09
# I want to double the resultion below 2000 meters

# zs_NCAR_LENS[45] = 1863.0
# zs_NCAR_LENS[46] = 2074.9
cut = 45

zs_SSM = []
for i = 1:cut
    append!(zs_SSM, zs_NCAR_LENS[i])
    append!(zs_SSM, (zs_NCAR_LENS[i] + zs_NCAR_LENS[i+1]) / 2.0)
end

for i = cut+1:length(zs_NCAR_LENS)
    append!(zs_SSM, zs_NCAR_LENS[i])
end

#############################################################################

# 2019/05/10
# Make 40 layers (41 points). 5 meters increament in top 100m (20 layers),
# and a linearly increasing increament in the next 20 layers until 1000m.
zs_SSM = zeros(Float64, 41)
zs_SSM[1] = 0.0

dh = zeros(Float64, 40)
dh[ 1:20] .= 5.0
dh[21:40] = 5.0 .+ collect(1:20) * 80.0/21.0
for i = 1:length(dh)
    zs_SSM[i+1] = zs_SSM[i] - dh[i]
end

#############################################################################

# 2019/07/11
# Make 40 layers (41 points). Simply use the same zs as LENS (top 41 pts).

zs_SSM[:] = zs_NCAR_LENS[1:41]

#############################################################################

# 2019/11/03
zs_SSM = collect(Float64, 0:-50:-1000)

#############################################################################

zs_mid_NCAR_LENS = (zs_NCAR_LENS[2:end] + zs_NCAR_LENS[1:end-1] ) / 2.0
zs_mid_SSM = (zs_SSM[2:end] + zs_SSM[1:end-1] ) / 2.0


_pr(t::Dict, x::Int, y::Int, z::Int) = join((rstrip(join(t[(n, m)] for n in range(0, 3+x+z))) for m in reverse(range(0, 3+y+z))), "\n")

function cuboid(x::Int, y::Int, z::Int)
    t = Dict((n, m) => " " for n in range(0, 3 + x + z), m in range(0, 3 + y + z))
    xrow = vcat("+", collect("$(i % 10)" for i in range(0, x)), "+")
    for (i, ch) in enumerate(xrow) t[(i, 0)] = t[(i, 1+y)] = t[(1+z+i, 2+y+z)] = ch end
    yrow = vcat("+", collect("$(j % 10)" for j in range(0, y)), "+")
    for (j, ch) in enumerate(yrow) t[(0, j)] = t[(x+1, j)] = t[(2+x+z, 1+z+j)] = ch end
    zdep = vcat("+", collect("$(k % 10)" for k in range(0, y)), "+")
    for (k, ch) in enumerate(xrow) t[(k, 1+y+k)] = t[(1+x+k, 1+y+k)] = t[(1+x+k, k)] = ch end

    return _pr(t, x, y, z)
end

for (x, y, z) in [(2, 3, 4), (3, 4, 2), (4, 2, 3), (5, 5, 6)]
    println("\nCUBOID($x, $y, $z)\n")
    println(cuboid(x, y, z))
end


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

using Markdown
using InteractiveUtils

# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
    quote
        local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end
        local el = $(esc(element))
        global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el)
        el
    end
end

# ╔═╡ 2f0ce4ef-4c45-4175-a9a7-df70cb268629
begin
    CURRENT_DIR = @__DIR__
    ROOT_DIR = if basename(CURRENT_DIR) == "pluto"
        dirname(dirname(CURRENT_DIR))
    else
        CURRENT_DIR
    end

    using Pkg
    Pkg.activate(ROOT_DIR; io = devnull)

    using Logging
    disable_logging(Logging.Warn)

    println('\n', " "^4, "> Loading the packages...")

    using LaTeXStrings
    using Plots
    using PlutoUI

    # Use the GR backend for plots
    gr()

    # Change some of the default parameters for plots
    default(fontfamily = "Computer Modern", dpi = 300, legend = nothing)

    # Define the paths to output directories
    PLOTS_DIR = joinpath(ROOT_DIR, "plots")
    NOTEBOOKS_PLOTS_DIR = joinpath(PLOTS_DIR, "notebooks")
    M1_DIR = joinpath(NOTEBOOKS_PLOTS_DIR, "m1")

    # Make sure the needed directories exist
    mkpath(M1_DIR)

    # An alias for a floating point type
    F = Float64

    md"This is a Pluto notebook to test the parameters of the first model."
end

# ╔═╡ 52342993-f23a-4a50-b3e0-e9e4b032f1cf
md"""
Masses are in units of ``M_0 = 2.325 \times 10^7 M_\odot``, other parameters are in ``\text{kpc}``.

Initial values are taken from Bajkova, Bobylev (2020), ranges are taken from Granados et al. (2021).
"""

# ╔═╡ 1fa86bf7-e1dc-4cd1-9ff0-73c14225ef2c
begin
    # Bulge
    M_B_S = @bind M_B NumberField(
        43.0:0.001:2580.0,
        default = 443.0,
    )
    A_B_S = @bind A_B NumberField(0.0:0.001:0.05, default = 0.0)
    B_B_S = @bind B_B NumberField(0.0:0.001:2.0, default = 0.2672)

    # Disk
    M_D_S = @bind M_D NumberField(
        1500.0:0.001:12903.0,
        default = 2798.0,
    )
    A_D_S = @bind A_D NumberField(1.0:0.001:20.0, default = 4.40)
    B_D_S = @bind B_D NumberField(0.1:0.001:16.0, default = 0.3084)

    # Halo
    M_H_S = @bind M_H NumberField(
        43.0:0.001:12903.0,
        default = 12474.0,
    )
    A_H_S = @bind A_H NumberField(0.1:0.001:30.0, default = 7.7)

    md"""
    ``M_b``: $(M_B_S) ``a_b``: $(A_B_S) ``b_b``: $(B_B_S) \
    ``M_d``: $(M_D_S) ``a_d``: $(A_D_S) ``b_d``: $(B_D_S) \
    ``M_h``: $(M_H_S) ``a_h``: $(A_H_S)
    """
end

# ╔═╡ a5d896ff-570f-49a9-adf0-19f11cb899e0
begin
    "Calculate the value of the R derivative of the Miyamoto & Nagai potential
    ``[ 100 \\, \\text{km} \\, \\text{s}^{-2} ]``"
    function miyamoto_nagai_phi_dr(r, z, m, a, b)::F
        return m * r / (r^2 + (a + √(z^2 + b^2))^2)^(1.5)
    end

    "Calculate the value of the R derivative of the Navarro-Frenk-White potential
    ``[ 100 \\, \\text{km} \\, \\text{s}^{-2} ]``"
    function navarro_frenk_white_phi_dr(r, z, m, a)::F
        sq_sum = r^2 + z^2
        k_1 = √(sq_sum) / a + 1.0
        return m * r * log(k_1) / sq_sum^(1.5) - m * r / a / sq_sum / k_1
    end

    "Calculate the value of circular velocity according to
    ``v_c^2(r) = R \\left. \\frac{\\partial \\Phi(R, Z)}{\\partial R} \\right|_{Z=0}``
    ``[ \\text{km}^2 \\, \\text{s}^{-2} ]``"
    circ_vel(r) = √(r * phi_dr(r, 0) * 100)

    "Calculate the value of ``\\partial \\Phi(R, Z) / \\partial R``
    ``[ 100 \\, \\text{km} \\, \\text{s}^{-2} ]``"
    function phi_dr(r, z)::F
        return miyamoto_nagai_phi_dr(r, z, M_B, A_B, B_B) +
               miyamoto_nagai_phi_dr(r, z, M_D, A_D, B_D) +
               navarro_frenk_white_phi_dr(r, z, M_H, A_H)
    end

    println(" "^4, "> Plotting the rotation curve...")

    # Plot the rotation curve
    p1 = plot(
        circ_vel, 0.2, 20;
        title = "Rotation curve",
        xlabel = L"R \;\, [\mathrm{kpc}]",
        ylabel = L"v_c \; [\mathrm{km^2 \, s^{-2}}]",
        xminorticks = 8,
        yminorticks = 8
    )

    # Save the figure
    savefig(p1, joinpath(M1_DIR, "Rotation curve.pdf"))
    savefig(p1, joinpath(M1_DIR, "Rotation curve.png"))

    p1
end

# ╔═╡ d4ba276c-a578-4a64-9f54-c33c81225a0b
begin
    # Plot the rotation curve
    p2 = plot(
        circ_vel, 0.2, 200;
        title = "Rotation curve",
        xlabel = L"R \;\, [\mathrm{kpc}]",
        ylabel = L"v_c \; [\mathrm{km^2 \, s^{-2}}]",
        xticks = [1e-1, 1, 1e1, 1e2],
        xlims = (0.1, 390),
        xscale = :log10,
        xminorticks = 9,
        yminorticks = 8
    )

    # Save the figure
    savefig(p2, joinpath(M1_DIR, "Rotation curve (log).pdf"))
    savefig(p2, joinpath(M1_DIR, "Rotation curve (log).png"))

    println()

    p2
end

# ╔═╡ bb727eda-9d7c-48e2-bac6-23e90aa6aab1
md"""
From Bajkova, Bobylev (2020):

$(PlutoUI.LocalResource(joinpath(ROOT_DIR, "assets", "Rotation curve of the NFWBB model (Bajkova, Bobylev, 2020).png")))
"""

# ╔═╡ Cell order:
# ╟─2f0ce4ef-4c45-4175-a9a7-df70cb268629
# ╟─52342993-f23a-4a50-b3e0-e9e4b032f1cf
# ╟─1fa86bf7-e1dc-4cd1-9ff0-73c14225ef2c
# ╟─a5d896ff-570f-49a9-adf0-19f11cb899e0
# ╟─d4ba276c-a578-4a64-9f54-c33c81225a0b
# ╟─bb727eda-9d7c-48e2-bac6-23e90aa6aab1


# ---
# title: 1365. How Many Numbers Are Smaller Than the Current Number
# id: problem1365
# author: Tian Jun
# date: 2020-10-31
# difficulty: Easy
# categories: Array, Hash Table
# link: <https://leetcode.com/problems/how-many-numbers-are-smaller-than-the-current-number/description/>
# hidden: true
# ---
#
# Given the array `nums`, for each `nums[i]` find out how many numbers in the
# array are smaller than it. That is, for each `nums[i]` you have to count the
# number of valid `j's` such that `j != i` **and** `nums[j] < nums[i]`.
#
# Return the answer in an array.
#
#
#
# **Example 1:**
#
#
#
#     Input: nums = [8,1,2,2,3]
#     Output: [4,0,1,1,3]
#     Explanation:
#     For nums[0]=8 there exist four smaller numbers than it (1, 2, 2 and 3).
#     For nums[1]=1 does not exist any smaller number than it.
#     For nums[2]=2 there exist one smaller number than it (1).
#     For nums[3]=2 there exist one smaller number than it (1).
#     For nums[4]=3 there exist three smaller numbers than it (1, 2 and 2).
#
#
# **Example 2:**
#
#
#
#     Input: nums = [6,5,4,8]
#     Output: [2,1,0,3]
#
#
# **Example 3:**
#
#
#
#     Input: nums = [7,7,7,7]
#     Output: [0,0,0,0]
#
#
#
#
# **Constraints:**
#
#   * `2 <= nums.length <= 500`
#   * `0 <= nums[i] <= 100`
#
#
## @lc code=start
using LeetCode

## add your code here:
## @lc code=end


#!/usr/bin/julia
#
# File containing inner product operations on the grid space.
#
export ip, norm, norm2, normalise!, orthog!, orthog;

"""
    do_ip(v::AbstractVector, w::AbstractVector, Δ::Number)

Implementation of inner product calculation.
"""
function do_ip(v::AbstractVector, w::AbstractVector, Δ::Number)
  # Approximate integral over the discrete space.
  return sum(v' * w) * Δ;
end

"""
    check_ip_compatable(ψ::WaveObject{T,N,S,M,G},
                        ϕ::WaveObject{T,K,S,M,G}) where {T,N,S,M,K,G<:UniformGrid}

Check the lengths and uniform grid spacings of the `WaveObjects` are compatible
to allow the calculation of an inner product.
"""
function check_ip_compatable(ψ::WaveObject{T,N,S,M,G},
                             ϕ::WaveObject{P,K,S,M,G}) where {T,P,N,S,M,K,G<:UniformGrid}

  if (N === K) check_same_size(ψ, ϕ);
  else check_ip_compatable_dim(ψ,ϕ); end

  if (ψ.Δ != ϕ.Δ) error("grid spacings of WaveObjects are different"); end
end

"""
    check_same_size(ψ::WaveObject{T,N}, ϕ::WaveObject{P,N}) where {T,P,N}

Check `WaveObjects` of the same dimension have the same size.
"""
function check_same_size(ψ::WaveObject{T,N}, ϕ::WaveObject{P,N}) where {T,P,N}
  if (size(ψ) != size(ϕ))
    throw((DimensionMismatch, "wave functions must be of same size."));
  end
end
function check_same_size(ψ::WaveObject{T,1}, ϕ::WaveObject{P,1}) where {T,P}
  if (length(ψ) != length(ϕ))
    throw(DimensionMismatch("wave functions must be of same size."));
  end
end

"""
    check_ip_compatable_dim(ψ::WaveObject{T,1}, ϕ::WaveObject{P,2}) where {T,P}

Check that the length of the one-dimensional `WaveObject` is the same as the
length of a column of a two-dimensional `WaveObject`.
"""
function check_ip_compatable_dim(ψ::WaveObject{T,1}, ϕ::WaveObject{P,2}) where {T,P}
  if (length(ψ) != length(ϕ[:,1]))
    throw(DimensionMismatch("wave function must be same length as matrix column."));
  end
end
function check_ip_compatable_dim(ϕ::WaveObject{P,2}, ψ::WaveObject{T,1}) where {T,P}
  check_ip_compatable_dim(ψ, ϕ);
end

"""
    ip(ψ::O, ϕ::O) where {T,S,G<:UniformGrid,O<:WaveFunction{T,1,S,G}}

Inner product between two wave functions on the same one-dimensional
`UniformGrid` system.
"""
function ip(ψ::O, ϕ::O) where {T,S,G<:UniformGrid,O<:WaveFunction{T,1,S,G}}
  check_ip_compatable(ψ, ϕ);
  return do_ip(ψ, ϕ, ψ.Δ);
end

"""
    ip(ψ::WaveFunction{T,1,S,G}, ϕ::WaveMatrix{T,S,G}, pos::Int) where
            {T,S,G<:UniformGrid}

Inner product between `WaveFunction` and wave function in column `pos` of the
matrix-like object `ϕ`.

Objects are defined on a one-dimensional `UniformGrid`.
"""
function ip(ψ::WaveFunction{T,1,S,G}, ϕ::WaveMatrix{T,S,G}, pos::Int) where
            {T,S,G<:UniformGrid}
  check_ip_compatable(ψ, ϕ);
  return do_ip(ψ, ϕ[:,pos], ψ.Δ);
end
function ip(ϕ::WaveMatrix{T,S,G}, pos::Int, ψ::WaveFunction{T,1,S,G}) where
            {T,S,G<:UniformGrid}
  ip(ψ, ϕ, pos);
end

"""
    ip(ψ::O, pos::Int, ϕ::O, pos2::Int) where
          {T,S,G<:UniformGrid,O<:WaveMatrix{T,2,S,1,G}}

Inner product between columns of two wave matrices.
"""
function ip(ψ::O, pos::Int, ϕ::O, pos2::Int) where
          {T,S,G<:UniformGrid,O<:WaveMatrix{T,S,G}}
  check_ip_compatable(ψ, ϕ);
  return do_ip(ψ[:,pos], ϕ[:,pos2], ψ.Δ);
end

"""
    norm2(ψ::WaveFunction{T,1,S,G}) where {T,S,G<:UniformGrid}

Calculate norm² of a one-dimensional wave function on a `UniformGrid`.
"""
function norm2(ψ::WaveFunction{T,1,S,G}) where {T,S,G<:UniformGrid}
  return sum(abs2.(ψ) * ψ.Δ);
end

"""
    norm(ψ::WaveFunction{T,1,S,G}) where {T,S,G<:UniformGrid}

Calculate norm of a one-dimensional wave function on a `UniformGrid`.
"""
norm(ψ::WaveFunction{T,1,S,G}) where {T,S,G<:UniformGrid} = √norm2(ψ);

LinearAlgebra.norm(ψ::WaveFunction{T,1,S,G}) where {T,S,G<:UniformGrid} =
  QuantumSimulation.norm(ψ);

"""
    norm2(ψ::WaveMatrix{T,S,G}, pos::Int) where {T,S,G<:UniformGrid}

Calculate norm² of a matrix `WaveMatrix` element in column `pos` defined on a
one-dimensional `UniformGrid`.
"""
function norm2(ϕ::WaveMatrix{T,S,G}, pos::Int) where {T,S,G<:UniformGrid}
  return sum(abs2.(ϕ[:,pos]) * ϕ.Δ);
end

"""
    norm(ψ::WaveMatrix{T,S,G}, pos::Int) where {T,S,G<:UniformGrid}

Calculate norm of a matrix `WaveMatrix` element in column `pos` defined on a
one-dimensional `UniformGrid`.
"""
function norm(ϕ::WaveMatrix{T,S,G}, pos::Int) where {T,S,G<:UniformGrid}
  return √norm2(ϕ, pos);
end

"""
    normalise!(ψ::WaveFunction{T,1,S,G}) where {T,S,G<:UniformGrid}

Normalise the passed wave function defined on a one-dimensional `UniformGrid`.
"""
function normalise!(ψ::WaveFunction{T,1,S,G}) where {T,S,G<:UniformGrid}
  ψ ./= norm(ψ);
end

"""
    orthog!(ψ::WaveFunction{T,1,S,G}, ϕ::WaveMatrix{T,S,G},
                 max::Int=length(ϕ[1,:])) where {T,S,G<:UniformGrid}

Orthogonalise `ψ` with respect to the first `max` unit normalised wave functions
in ϕ.

Both `ψ` and `ϕ` are defined on a one-dimensional `UniformGrid`.

See also: [`orthog`](@ref)
"""
function orthog!(ψ::WaveFunction{T,1,S,G}, ϕ::WaveMatrix{T,S,G},
                 max::Int=length(ϕ[1,:])) where {T,S,G<:UniformGrid}
  # Implement modified Gram Schmidt to prevent accumulation of round-off errors.
  for i ∈ 1:max
    ψ[:] -= ip(ψ, ϕ, i) * ϕ[:,i];
  end
end
"""
    orthog(ψ::WaveFunction{T,1,S,G}, ϕ::WaveMatrix{T,S,G},
                 max::Int=length(ϕ[1,:])) where {T,S,G<:UniformGrid}

Return a copy of `ψ` orthogonalised with respect to the first `max` unit
normalised wave functions in ϕ.

Both `ψ` and `ϕ` are defined on a one-dimensional `UniformGrid`.

See also: [`orthog!`](@ref)
"""
function orthog(ψ::WaveFunction{T,1,S,G}, ϕ::WaveMatrix{T,S,G},
                 max::Int=length(ϕ[1,:])) where {T,S,G<:UniformGrid}
  ψcopy = deepcopy(ψ);
  orthog!(ψcopy, ϕ, max);
  return ψcopy;
end


using LinearAlgebra, SparseArrays, DifferentialEquations

#=
    Scenario 1: Nominal actuators
    Scenario 2: Realistic actuators
=#
const scenario = 1;

# Configuration of physical properties λ and c
function property(temperature :: T , a₀ :: T, a₁ :: T) where T <: Real
    Θ = temperature

    return a₀ + a₁ * Θ
end

# Emitted heat flux on boundary
function boundary_heatflux(boundary_temperature :: T, ambient_temperature ::T, transfer_coefficient :: T, radiation_coefficient :: T) where T <: Real
    θbound = boundary_temperature;
    θamb = ambient_temperature
    h = transfer_coefficient
    k = radiation_coefficient

    return -1*h *(θbound - θamb) -  k *(θbound^4 - θamb^4)
end

# Emitted heat flux on boundary
function boundary_heatflux(boundary_temperature :: Array{T}, ambient_temperature :: Array{T}, transfer_coefficient :: T, radiation_coefficient :: T) where T <: Real
    θbound = boundary_temperature;
    θamb = ambient_temperature

    if size(θbound) != size(θamb)
        error("Boundary temperature array and ambient temperature array have different sizes: size(boundary_temperature) = $(size(θbound)) ≠ $(size(θamb)) = size(ambient_temperature)!")
    end

    h = transfer_coefficient
    k = radiation_coefficient

    return -1*h *(θbound .- θamb) -  k *(θbound.^4 .- θamb.^4)
end

# Spatial approximation
const L = 300.0*10^(-3) # Length of rod
const H = 10.0*10^(-3)  # Height of rod

const N₁ = 100; # Number of cells in x₁ direction
const N₂ = 40;  # Number of cells in x₂ direction
const Δx₁ = L/N₁; # Discretizaion
const Δx₂ = H/N₂;
const x1span = Δx₁/2 : Δx₁ : L; # Coordinates of cell centers
const x2span = Δx₂/2 : Δx₂ : H;

# Heat transfer + radiation
const h = 10.0             # Heat transfer coefficient
const ϵ = 0.6;             # Emissivity
const sb = 5.67*10^(-8);   # Stefan-Boltzmann constant
const k = ϵ*sb;            # Heat radiation coefficient

const ρ = 7800.0;       # Density
const ρinv = inv(ρ);
const Id12 = sparse(I, N₁*N₂, N₁*N₂) # Template matrix for specific heat capacity

const θamb = 300.0; # Ambient temperature

# Initial noisy data
const a₀ = 3;   # Scaling
const a₁ = 10;
const a₂ = 5;
const noise = a₀ * cos.(a₁*2*pi/L * x1span) .* transpose(cos.(a₂ *2*pi/H * x2span))
θinit = ones(N₁, N₂) * 300.0 .+ noise;
θinit = reshape(θinit, N₁*N₂, 1 )

# Input / Output
const Nu = 5; # Number of inputs
const Ny = 5; # Number of outputs

b_in = spzeros(N₁, Nu)          # Actuators' spatial characterization
g_out = spzeros(N₁, Ny)         # Sensors' spatial characterization

const β_in = spzeros(Int64,N₁, Nu)    # Input partition
const γ_out = spzeros(Int64, N₁, Ny   )# Output partition

const xspan = Δx₁/2  : Δx₁ : L;           # Centers of cells
const xc_in = (L/Nu)*((1/2) : 1 : Nu)     # Centers of actuators
const xc_out = (L/Ny)*((1/2) : 1 : Ny)    # Centers of sensors

# Decision of scenario: actuators' spatial characterization
if scenario == 1
    in_coeff = (m = 1.0, M = 0.0, ν = 4.0 )
else
    in_coeff = (m = 1.0, M = 30.0, ν = 4.0 )
end

# Sensors' spatial characterization
out_coeff = (m = 1.0, M = 10.0, ν = 4.0 )

function characterization(x, xcenter, m::Number=1.0, M::Number=1.0, ν::Number=1.0)
    return  m*exp(-1 * norm( M * (x - xcenter) )^ν )
end

# Input characterization
for i = 1 : Nu
    β_in[round(Int, (i-1)*N₁/Nu)+1:round(Int, i*N₁/Nu), i] .= 1;
    b_in[:,i] = characterization.(xspan, xc_in[i], in_coeff.m, in_coeff.M , in_coeff.ν)
end

b_in = β_in .* b_in;

# Output characterization
for i = 1 : Ny
    γ_out[round(Int, (i-1)*N₁/Ny)+1:round(Int, i*N₁/Ny), i] .= 1;
    g_out[:,i] = characterization.(xspan, xc_in[i], out_coeff.m, out_coeff.M , out_coeff.ν)
end

g_out = γ_out .* g_out;
const g_weight = inv.(sum(transpose(g_out), dims=2))

# Controller
const Kp = ones(Nu) * 10^(4);     # Controller gain
const u_in  = zeros(Nu)           # System input signals
const y_out = zeros(Ny)           # System output signals
const y_ref = ones(Ny) * 400.0;   # Reference temperatures

# History of input signals
global u_hist
u_hist = zeros(Nu,0);

# History of measurements
global y_hist
y_hist = zeros(Ny,0)

# History of time steps
const t_hist = Real[];

# System dynamics: approximated spatial derivatives
const Mx1_lo = spdiagm(-1 => ones(N₁*N₂-1), 0 => -1*ones(N₁*N₂))
const Mx1_hi = spdiagm( 0 => -1*ones(N₁*N₂), 1 => ones(N₁*N₂-1))

const Mx2_lo = spdiagm(-1*N₁ => ones( N₁*(N₂-1) ), 0 => -1*ones(N₁*N₂))
const Mx2_hi = spdiagm( 0 => -1*ones(N₁*N₂), N₁ => ones( N₁*(N₂-1) ))

# Boundary value description
const bound_under = sparse(I,N₁*N₂, N₁)
const bound_top = spzeros(N₁*N₂, N₁)
bound_top[(N₂-1)*N₁+1 : N₁*N₂,1:N₁] = sparse(I, N₁, N₁)

const bound_left = spzeros(N₁*N₂, N₂)
const bound_right = spzeros(N₁*N₂, N₂)

for k = 1 : N₂
    bound_left[1 + (k-1)*N₁, k] = 1
    bound_right[k*N₁, k] = 1
end

# Approximated PDE as large-scale ODE
function heat_eq(dθ,θ,p,t)
    global y_hist
    global u_hist
    push!(t_hist, t);

    # Reshape temperature vector to matrix (2-dimensional temperature field)
    θ = reshape(θ, N₁, N₂)

    # Computation of system output  signals
    y_out = g_weight .* (transpose(g_out) * θ[:,end]);

    # Controller
    u = Kp .* (y_ref - y_out)
    u = u .* (0.5*(sign.(u) .+ 1.0));

    # Saving input and output signals
    y_hist = hcat(y_hist, Matrix(y_out))
    u_hist = hcat(u_hist, u)

    # Averaging temperatures at internal cell boundaries
    θ1m = (θ[1:end-1,:] + θ[2:end,:])/2.0;          # x₁-direction
    θ2m = (θ[:, 1:end-1] + θ[:, 2:end])/2.0;        # x₂-direction

    # Specific heat capacity
    c = property.(θ, 330.0, 0.4)
    Cinv = reshape(inv.(c), N₁* N₂,1) .* Id12

    # Thermal conductivity
    λ₁ = property.( θ1m , 10., 0.1); # x₁-direction
    λ₂ = property.( θ2m , 10., 0.1); # x₂-direction

    # Add addional "virtual" cell at left and right boundary
    λ₁_ext1 = cat( zeros(1,N₂), λ₁, dims=1)
    λ₁_ext2 = cat( λ₁, zeros(1,N₂), dims=1)

    # Reshape λ matrix of 2-dimensional temperature field to vector
    λ₁_ext1 = reshape(λ₁_ext1, N₁*N₂, 1 )
    λ₁_ext2 = reshape(λ₁_ext2, N₁*N₂, 1 )

    # Add addional "virtual" cell at underside and topside boundary
    λ₂_ext1 = cat( zeros(N₁,1), λ₂, dims=2 )
    λ₂_ext2 = cat( λ₂, zeros(N₁,1), dims=2 )

    # Reshape λ matrix of 2-dimensional temperature field to vector
    λ₂_ext1 = reshape(λ₂_ext1, N₁*N₂, 1 )
    λ₂_ext2 = reshape(λ₂_ext2, N₁*N₂, 1 )

    # Calculate diffusion
    diff_x1 = 1/(Δx₁^2) * (λ₁_ext1 .* Mx1_lo +  λ₁_ext2 .* Mx1_hi)
    diff_x2 = 1/(Δx₂^2) * (λ₂_ext1 .* Mx2_lo +  λ₂_ext2 .* Mx2_hi)

    # Emitted heat flux
    ϕout1 = bound_left * boundary_heatflux.(θ[1,:], θamb, h, k) + bound_right * boundary_heatflux.(θ[end,:], θamb, h, k) # x₁-direction
    ϕout2 = bound_top * boundary_heatflux.(θ[:,end], θamb, h, k) # x₂-direction

    # Induced heat flux
    ϕin = bound_under * b_in * u

    # Boundary conditions as vectors
    in_out_x1 = 1/Δx₁ * ϕout1
    in_out_x2 = 1/Δx₂ * (ϕout2 + ϕin)

    # Reshape matrix (2D temperature field) to vector
    θ = reshape(θ, N₁*N₂, 1 )

    # Calculate temporal derivative
    dθ .=  ρinv * Cinv * ( (diff_x1 + diff_x2) * θ + in_out_x1 + in_out_x2 )
end

# Sampling time and final time
const Δt = 10^(-3);
const Tfinal = 10.;
const tspan = (0, Tfinal)

# Solve the ODE
prob = ODEProblem( heat_eq, θinit,tspan )
sol = solve(prob,Euler(),dt=Δt,progress=true,save_everystep=false,save_start=true)

# Save and export results
import DelimitedFiles
basepath        = "results/";

input_filename  = basepath * "input_history_scenario_" * string(scenario) * ".txt";
output_filename = basepath *  "output_history_scenario_" * string(scenario) * ".txt";
temperature2D_filename = basepath *  "temperature2D_scenario_" * string(scenario) * ".txt";

# Save signals with time steps (t_hist)
u_io_data = [t_hist transpose(u_hist)]
y_io_data = [t_hist transpose(y_hist)]

# Save temperature as matrix (2D temperature field)
temperature2D = reshape(sol.u[2], N₁, N₂);

# Export files
DelimitedFiles.writedlm(input_filename, u_io_data, "\t");
DelimitedFiles.writedlm(output_filename, y_io_data, "\t");
DelimitedFiles.writedlm(temperature2D_filename, temperature2D, "\t");


using Random
using Plots
using Statistics

const MAX_GEN = 1000
const DNA_LEN = 25
const POP_SIZE = 20
const MUTATION_RATE = 0.001
const EXPERIMENTS = 20;
const EXPERIMENT_NAME = "pop20mut0001";

mutable struct Individial
    fitness::Int64
    dna::Vector{Bool}
    Individial() = begin
        ind = new(0, rand(Bool, DNA_LEN))
        eval!(ind)
        #eval2!(ind)
        return ind
    end
end

const Population = Vector{Individial}

function eval!(ind::Individial)
    ind.fitness = sum(ind.dna)
end

#version that seek 10101010...
function eval2!(ind::Individial)
    ind.fitness = 0;
    for k in 1:DNA_LEN
        ind.fitness += (k % 2 == 1) ? ind.dna[k] : 1-ind.dna[k]
    end
end

function mutate!(ind::Individial)
    for k in 1:DNA_LEN
        ind.dna[k] = rand() > MUTATION_RATE ? ind.dna[k] : !ind.dna[k]
    end
end

function breed!(parents::Population, childs::Population)
    tot_fitness = sum(ind -> ind.fitness, parents)
    npairs = div(POP_SIZE, 2)
    Threads.@threads for n in 1:npairs
        parent1 = roulette_wheel(parents, rand(1:tot_fitness))
        parent2 = roulette_wheel(parents, rand(1:tot_fitness))
        child1 = childs[n]
        child2 = childs[n + npairs]
        cross!(parent1, parent2, child1, child2)
    end
end

function cross!(
        parent1::Individial, parent2::Individial,
        child1::Individial, child2::Individial
        )
    cross_point = rand(0:DNA_LEN)
    for k in 1:DNA_LEN
        child1.dna[k] = (k <= cross_point ? parent1.dna[k] : parent2.dna[k])
        child2.dna[k] = (k <= cross_point ? parent2.dna[k] : parent1.dna[k])
    end
end

function roulette_wheel(pop::Population, x::Int64)::Individial
    n = 0
    while x > 0 && n+1 < length(pop)
        n += 1
        x -= pop[n].fitness
    end
    return pop[n]
end

function eva()
    log_best = Vector{Int64}()
    log_avg = Vector{Float64}()
    log_q25 = Vector{Float64}()
    log_q75 = Vector{Float64}()
    best = 1
    #random initial population
    pop = [Individial() for k in 1:POP_SIZE]
    #buffer for next population
    childs = [Individial() for k in 1:POP_SIZE]
    #then iterate:
    for gen in 1:MAX_GEN
        #generate offspring
        breed!(pop, childs)
        #mutate and calculate fitness
        Threads.@threads for ind in childs
            mutate!(ind)
            eval!(ind)
            #eval2!(ind)
        end
        #kill the parents
        begin
            temp = pop
            pop = childs
            childs = temp
        end
        #find the best guy
        best = 1
        for k in 1:POP_SIZE
            best = (pop[k].fitness > pop[best].fitness) ? k : best
        end
        #save results
        push!(log_best, pop[best].fitness)
        push!(log_avg , sum(ind -> ind.fitness/length(pop), pop))
        push!(log_q25, quantile!([p.fitness for p in pop], 0.25))
        push!(log_q75, quantile!([p.fitness for p in pop], 0.75))
    end
    return (pop[best], log_best, log_avg, log_q25, log_q75)
end

function main()
    log_avg = zeros(MAX_GEN)
    log_best = zeros(MAX_GEN)
    log_q25 = zeros(MAX_GEN)
    log_q75 = zeros(MAX_GEN)
    for k in 1:EXPERIMENTS
        println("experiment #", k)
        @time (result, _log_best, _log_avg, _log_q25, _log_q75) = eva()
        println(result);
        log_avg += _log_avg/EXPERIMENTS
        log_best += _log_best/EXPERIMENTS
        log_q25 += _log_q25/EXPERIMENTS
        log_q75 += _log_q75/EXPERIMENTS
    end
    p = plot(ylims = (12.5,DNA_LEN), legend = :bottomright)
    plot!(log_avg, label = "average")
    plot!(log_best, label = "best")
    plot!(log_q25, fillrange = log_q75, alpha = 0.3, label = "first-third quantile")
    savefig(p, "sga-"*EXPERIMENT_NAME*".png")
end

main()


@doc """
  An axis aligned hyperbox - a set of intervals
  """ ->
type HyperBox{T} <: Domain{T}
  intervals::Vector{Interval{T}}
end

@doc doc"""A LazyBox is a hyperbox whose dimensions can be
  created dynamically and do not have to be contiguous""" ->
immutable LazyBox{T} <: Domain{T}
  intervals::Dict{Int64,Interval{T}}
end

@doc "Boxes are abstractions of Vector{T<:Real}" ->
typealias Boxes{T} Union{LazyBox{T},HyperBox{T}}

## Domain Operations
## =================
isrelational{T<:Boxes}(::Type{T}) = false
domaineq(x::Boxes,y::Boxes) = isequal(x,y)
function isequal(x::Boxes,y::Boxes)
  for dim in union(dims(x),dims(y))
    if !(hasdim(x,dim) && hasdim(y,dim) && isequal(x[dim],y[dim]))
      return false
    end
  end
  true
end

## Box Domain General functions
## ============================
function product_boxes{T}(b::Boxes, split_point::Dict{Int, T})
  @assert(length(keys(split_point)) <= ndims(b))

  splits = Vector{Interval{T}}[]
  for dim in dims(b)
    if haskey(split_point, dim)
      push!(splits, split_box(b[dim],split_point[dim]))
    else # Don't split along this dimension
      push!(splits, [b[dim]])
    end
  end
  prodsubboxes(splits)
end

# Find cartesian product of a bunch of subboxes (vector of intervals)
function prodsubboxes{T}(splits::Vector{Vector{Interval{T}}})
  @compat boxes = Tuple{Vararg{Interval}}[]
  for subbox in product(splits...)
    push!(boxes, subbox) # product returns tuple
  end
  return boxes
end

mid{T}(b::Boxes{T}) = T[mid(b[dim]) for dim in dims(b)]

@doc "Split box into 2^d equally sized boxes by cutting down middle of each axis" ->
mid_split(b::Boxes) = split_box(b, mid(b))

@doc "Do a partial split at the midpoints of dimensions `dims`" ->
mid_partial_split(b::Boxes, partial_dims::Vector{Int}) =
  @compat partial_split_box(b,Dict([dim => mid(b[dim]) for dim in partial_dims]))

## Sampling
## ========
rand(b::Boxes) = [rand(b[dim]) for dim in dims(b)]

include("boxes/hyperbox.jl")
include("boxes/lazybox.jl")


module test_Interpolation

import ModiaMath

#  Desired:
#    using Test
#    using LinearAlgebra
#    using Unitful
#
#  In order that these packages need not to be defined in the user environment, they are included via ModiaMath:
@static if VERSION < v"0.7.0-DEV.2005"
    using Base.Test
else
    using ModiaMath.Test
    using ModiaMath.LinearAlgebra
end

using  ModiaMath.Unitful

r = [ ModiaMath.Vector3D(1, 0, 0),
      ModiaMath.Vector3D(0, 1, 0),
      ModiaMath.Vector3D(0, 0, 1) ]

q = [ ModiaMath.NullQuaternion,
      ModiaMath.qrot1(45u"°"),
      ModiaMath.qrot2(60u"°")  ]

path     = ModiaMath.Path(r, q)
t_end    = ModiaMath.t_pathEnd(path)
println("t_end = ", t_end)
println("path.t = ", path.t)

@testset "ModiaMath.Frame: test interpolation" begin
    dist1 = norm(r[2] - r[1])
    dist2 = norm(r[3] - r[2])

    @test isapprox(ModiaMath.t_pathEnd(path), dist1 + dist2)

    (rt2, qt2) = ModiaMath.interpolate(path, dist1)
    @test isapprox(r[2], rt2)
    @test isapprox(q[2], qt2)

    rt3 = ModiaMath.interpolate_r(path, dist1 / 2.0)
    @test isapprox(rt3, r[1] + (r[2] - r[1]) / 2)
end

function checkInterpolation(path, t_end)
    dt       = 0.1
    stopTime = 2.0
    time     = 0.0

    while time <= stopTime + 1e-7
        (rt, qt) = ModiaMath.interpolate(path, time * t_end / stopTime)
        println("... time = ", time, ", rt = ", rt)
        time += dt
    end
end

checkInterpolation(path, t_end)

end


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

using Markdown
using InteractiveUtils

# ╔═╡ 43e8fa28-fda8-11ea-0051-dd3db29697b8
using Pkg, DrWatson

# ╔═╡ 43e9386e-fda8-11ea-245e-57c3be7b7977
begin
	#@quickactivate "StatisticalRethinkingStan"
	using StanSample
	using StatisticalRethinking
end

# ╔═╡ e0197532-fda6-11ea-2023-27973dec779c
md"## Clip-05-37s.jl"

# ╔═╡ 43e9b9ea-fda8-11ea-06af-87a022dcae90
md"### snippet 5.29"

# ╔═╡ 43f71298-fda8-11ea-31ae-1136eb790910
begin
	df = CSV.read(sr_datadir("milk.csv"), DataFrame; delim=';');
	df = filter(row -> !(row[:neocortex_perc] == "NA"), df);
	df.neocortex_perc = parse.(Float64, df.neocortex_perc)
	df.lmass = log.(df.mass)
	scale!(df, [:kcal_per_g, :neocortex_perc, :lmass])
end;

# ╔═╡ 43f87ef8-fda8-11ea-1764-9b59eb6c3e8b
md"### snippet 5.1"

# ╔═╡ 44037e3e-fda8-11ea-0d38-bdae456569f2
stan5_6 = "
data {
 int < lower = 1 > N; // Sample size
 vector[N] K; // Outcome
 vector[N] M; // Predictor
}

parameters {
 real a; // Intercept
 real bM; // Slope (regression coefficients)
 real < lower = 0 > sigma;    // Error SD
}

model {
  vector[N] mu;               // mu is a vector
  a ~ normal(0, 0.2);         //Priors
  bM ~ normal(0, 0.5);
  sigma ~ exponential(1);
  mu = a + bM * M;
  K ~ normal(mu , sigma);     // Likelihood
}
";

# ╔═╡ 44041268-fda8-11ea-3918-c1a03b0e6936
md"## Define the SampleModel, etc."

# ╔═╡ 440ddc4e-fda8-11ea-218b-e9d90d16c8d6
begin
	m5_6s = SampleModel("m5.6", stan5_6);
	m5_6_data = Dict("N" => size(df, 1), "M" => df.lmass_s, "K" => df.kcal_per_g_s);
	rc5_6s = stan_sample(m5_6s, data=m5_6_data);
end;

# ╔═╡ 4414afce-fda8-11ea-04bc-c743afb894bc
if success(rc5_6s)

  # Describe the draws

  post5_6s_df = read_samples(m5_6s, :dataframe)

  title = "Kcal_per_g vs. log mass" * "\nshowing 89% predicted and hpd range"
  plotbounds(
    df, :lmass, :kcal_per_g,
    post5_6s_df, [:a, :bM, :sigma];
    title=title
  )
end

# ╔═╡ 4415fff0-fda8-11ea-3a82-332931a21614
md"## End of clip-05-37s.jl"

# ╔═╡ Cell order:
# ╟─e0197532-fda6-11ea-2023-27973dec779c
# ╠═43e8fa28-fda8-11ea-0051-dd3db29697b8
# ╠═43e9386e-fda8-11ea-245e-57c3be7b7977
# ╟─43e9b9ea-fda8-11ea-06af-87a022dcae90
# ╠═43f71298-fda8-11ea-31ae-1136eb790910
# ╟─43f87ef8-fda8-11ea-1764-9b59eb6c3e8b
# ╠═44037e3e-fda8-11ea-0d38-bdae456569f2
# ╟─44041268-fda8-11ea-3918-c1a03b0e6936
# ╠═440ddc4e-fda8-11ea-218b-e9d90d16c8d6
# ╠═4414afce-fda8-11ea-04bc-c743afb894bc
# ╟─4415fff0-fda8-11ea-3a82-332931a21614


module BoundaryCellFieldsTests

using Test
using Gridap
using Gridap.CellArrayApply: IndexCellArrayFromKernel
import Gridap: ∇

tags = [23,24,25]
model = CartesianDiscreteModel(partition=(3,4,2))

trian = Triangulation(model)
btrian = BoundaryTriangulation(model,tags)

ufun(x) = 2*x[1] + x[2]
grad_ufun(x) = VectorValue(2.0,1.0)
∇(::typeof(ufun)) = grad_ufun

u = CellField(trian,ufun)

bu = restrict(u,btrian)
bu_grad = ∇(bu)

bquad = CellQuadrature(btrian,degree=0)
q = coordinates(bquad)

quad = CellQuadrature(trian,degree=2)
s = coordinates(quad)

bphi = CellGeomap(btrian)

x = evaluate(bphi,q)

buq = evaluate(bu,q)
@test isa(buq,IndexCellArrayFromKernel)
bu_grad_q = evaluate(bu_grad,q)

phi = CellGeomap(trian)
bphi2 = restrict(phi,btrian)

x2 = evaluate(bphi2,q)

@test x ≈ x2

#writevtk(btrian,"btrian",cellfields=["u"=>bu,"u_grad"=>bu_grad])

#writevtk(x,"x",pointdata=["u"=>buq])

cn = integrate(bu,btrian,bquad)
@test isa(cn,CellNumber)
_ = collect(cn)

v = CellBasis(trian)
bv = restrict(v,btrian)
cm = varinner(bv,bu)

ca = integrate(cm,btrian,bquad)
@test isa(ca,CellArray{Float64,1})
_ = collect(ca)

cm = varinner(bv,bv)
ca = integrate(cm,btrian,bquad)
@test isa(ca,CellArray{Float64,2})
_ = collect(ca)

end # module


export unfold, fold

"""
    unfold(X, W; stride=1, padding=0, dilation=1)
Extracts sliding local blocks from a batched input tensor. X is the input 5d vector of size
`(spatial_dims... , channels, batch_size)`. W is the size of kernel, in format
`(spatial_dims... , channels)`. Output has the size of `(L, channels*kernel_w*kernel_h*kernel_d, batch_size)`,
where L is the total number of blocks.

"""
function unfold(X::AbstractArray{T,M} where T, w_dim::NTuple{K}; stride=1, padding=0, dilation=1) where M where K
    x_dim = size(X)
    if ndims(X) > 5 || ndims(X) < 3
        throw(DimensionMismatch("X and W must be 3d/4d/5d for 1d/2d/3d image. got $(ndims(X))d input"))
    end

    if ndims(X)-2 != length(w_dim)-1
        throw(DimensionMismatch("spatial dimentions of image and kernel must be equal, got $(ndims(X)-2),$(length(w_dim)-1)"))
    end

    # reassign x_dim after converting it to a 3d image type input
    x_dim = ( x_dim[1:end-2]... , fill(1,5-ndims(X))... , x_dim[end-1:end]... )
    # w_dim must be in following format: (spatial_dims..., channels_in, channels_out)
    w_dim = ( w_dim[1:end-1]... , fill(1,4-length(w_dim))... , w_dim[end], w_dim[end] )
    X = reshape(X, x_dim)

    # Make DenseConvDims object
    cdims = DenseConvDims(x_dim, w_dim; stride=stride, padding=padding, dilation=dilation)

    # Calculate the total number of sliding blocks
    col_dim = (im2col_dims(cdims))[1:2] # im2col_dims() returns (col_dim_x, col_dim_y, thread_num)
    col = fill(0., col_dim[1],col_dim[2],x_dim[end]) # x_dim[end] is number of batches

    # Iterate through all batchs
    @views for i = 1:x_dim[end]
        im2col!(col[:,:,i], X[:,:,:,:,i], cdims)
    end
    return col
end

"""
    fold(col, out_dim, W; stride=1, padding=0, dilation=1)
Does the opposite of `unfold()`, Combines an array of sliding local blocks into a large containing
tensor. `col` is a 3d array of shape `(L, channels*kernel_w*kernel_h*kernel_d, batch_size)`, where,
L is the total number of blocks. out_dim is the spatial dimention of the required image. W is the
spatial dimentions of the kernel.

"""
function fold(col::AbstractArray{T,3} where T, out_dim::NTuple{M}, w_dim::NTuple{M}; stride=1, padding=0, dilation=1) where M
    # Validate input
    if length(out_dim) > 3
        throw(DimensionMismatch("output dimentions cannot be greater than 3, got $(ndims(out_dim))"))
    end

    # Create DenseConvDims object
    col_dim = size(col)
    channels = col_dim[2]÷prod(w_dim)
    x_dim = (out_dim... , fill(3-length(out_dim))... , channels,col_dim[3])
    w_dim = (w_dim... , fill(3-length(w_dim))... , channels,channels)
    cdims = DenseConvDims(x_dim,w_dim; stride=stride, padding=padding, dilation=dilation)

    img = fill(0., x_dim)

    # Iterate through all batchs
    @views for i = 1:x_dim[end]
        col2im!(img[:,:,:,:,i], col[:,:,i], cdims)
    end

    return reshape(img, (out_dim... , channels,col_dim[3]))
end


using StabTrussTopOpt
sto = StabTrussTopOpt

import JSON
using Dates
using Printf
using LinearAlgebra: norm

function parse_TO_problem(ground_fp, load_supp_fp)::sto.TOProblemPar
    ndim, n, m, X, T, B = parse_TO_ground_json(ground_fp)
    F, Bound = parse_TO_support_load_json(load_supp_fp)

    dof_map = zeros(Int, m, 2*ndim) # e_id -> nodal dof
    id_minus = collect(ndim-1:-1:0)
    for i=1:m
        dof_map[i, 1:ndim] = ndim*T[i,1] * ones(ndim) - id_minus
        dof_map[i, ndim+1:2*ndim] = ndim*T[i,2] * ones(ndim) - id_minus
    end

    return sto.TOProblemPar(ndim, n, m, X, T, B, F, Bound, dof_map)
end

function parse_TO_ground_json(file_path::String)
    data = Dict()
    open(file_path, "r") do f
        data_txt = read(f, String)
        data = JSON.parse(data_txt)
    end
    ndim = data["dimension"]
    n = data["node_num"]
    m = data["element_num"]

    X = Matrix{Float64}(undef, n, ndim)
    T = Matrix{Int}(undef, m, 2)
    B = Matrix{Float64}(undef, m, ndim*2)

    # get node coord
    for i=1:n
        X[i,:] = data["node_list"][i]["point"]'
    end

    # get element node ids
    for i=1:m
        T[i,:] = data["element_list"][i]'
    end

    # build compatability matrix
    for i=1:m
        u_id = T[i,1]
        v_id = T[i,2]
        # compute length
        L = norm(X[v_id,:] - X[u_id,:])
        # compute direction cosine
        for k=1:ndim
            B[i, 1:ndim] = (X[v_id,:] - X[u_id,:]) ./ L
            B[i, ndim+1:2*ndim] = -B[i, 1:ndim]
        end
    end

    return ndim, n, m, X, T, B
end

function parse_TO_support_load_json(file_path::String)
    data = Dict()
    open(file_path, "r") do f
        data_txt = read(f, String)
        data = JSON.parse(data_txt)
    end

    ndim = data["dimension"]
    n = data["node_num"]

    n_load_nodes = length(data["point_load_list"])
    @assert(n_load_nodes > 0)
    F = zeros(Float64, n, ndim) # load
    for i=1:n_load_nodes
        load_v = data["point_load_list"][i]["node_id"]
        F[load_v, :] = data["point_load_list"][i]["force"]'
    end

    n_supp_nodes = length(data["support_node_list"])
    @assert(n_supp_nodes > 0)
    Bound = zeros(Int, n, ndim)
    for i=1:n_supp_nodes
        supp_v = data["support_node_list"][i]["node_id"]
        Bound[supp_v, :] = data["support_node_list"][i]["fixities"]'
    end

    # TODO: self-weight
    return F, Bound
end


using Test, StaticArrays, DelayEmbeddings

println("\nTesting Dataset...")

@testset "Dataset" begin
  data = Dataset(rand(1001,3))
  @testset "Methods & Indexing" begin
    a = data[:, 1]
    b = data[:, 2]
    c = data[:, 3]

    @test Dataset(a, b, c) == data
    @test size(Dataset(a, b)) == (1001, 2)

    @test data[:, 2:3][:, 1] == data[:, 2]

    @test size(data[1:10,1:2]) == (10,2)
    @test data[1:10,1:2] == Dataset(a[1:10], b[1:10])
    @test data[SVector{10}(1:10), SVector(1, 2)] == data[1:10, 1:2]
  end

  @testset "minmax" begin
    mi = minima(data)
    ma = maxima(data)
    mimi, mama = minmaxima(data)
    @test mimi == mi
    @test mama == ma
    for i in 1:3
      @test mi[i] < ma[i]
    end
  end

  @testset "Conversions" begin
    m = Matrix(data)
    @test Dataset(m) == data

    m = rand(1000, 4)
    @test Matrix(Dataset(m)) == m
  end

end


using Laguerre
using Test
using LinearAlgebra

include("test_lpoly.jl")
include("test_lfunction.jl")
include("test_poly_fun.jl")
include("test_integration.jl")


using SymEngine_jll: libsymengine

function __init__()
    __init_constants()
    __init_type_ids()
end

###########
## TYPES ##
###########

"""
    Expression <: Number

A symbolic expression.

```julia-repl
julia> expr = (Variable(:x) + 1)^2
(1 + x)^2

julia> Expression(2)
2

julia> Expression(Variable(:x))
x
```
"""
mutable struct Expression <: Number
    ptr::Ptr{Cvoid}

    function Expression()
        z = new(C_NULL)
        ccall((:basic_new_stack, libsymengine), Nothing, (Ref{Expression},), z)
        finalizer(free!, z)
        return z
    end

    function Expression(v::Ptr{Cvoid})
        z = new(v)
        return z
    end
end

Expression(ex::Expression) = ex
Expression(T) = convert(Expression, T)

free!(b::Expression) =
    ccall((:basic_free_stack, libsymengine), Nothing, (Ref{Expression},), b)

"""
    ExpressionRef

Basically an `Expression` with the difference that it internally only contains
a reference to an `Expression`. If the underlying reference is freed there is
no guarantee that `ExpressionRef` points to valid memory.
"""
struct ExpressionRef
    ptr::Ptr{Cvoid}

    ExpressionRef(ptr::Ptr{Cvoid}) = new(ptr)
end
ExpressionRef() = ExpressionRef(Ptr{Cvoid}())
ExpressionRef(ex::Expression) = ExpressionRef(ex.ptr)
ExpressionRef(x) = convert(ExpressionRef, x)

"""
    Variable(name::Union{String,Symbol}, indices...) <: Number

A data structure representing a variable.

```julia-repl
julia> Variable(:a)
a

julia> Variable(:x, 1)
x₁

julia> Variable(:x, 10, 5)
x₁₀₋₅
```

### Equality and ordering

Variables are identified by their `name` and `indices`.
That is, two variables are equal if and only if they have the same `name` and `indices`.

```julia-repl
julia> Variable(:a) == Variable(:a)
true

julia> Variable(:a, 1) == Variable(:a, 2)
false
```

Similarly, variables are first ordered lexicographically by their name and then by their indices.
```julia-repl
julia> Variable(:a, 1) < Variable(:a, 2)
true

julia> Variable(:a, 1) < Variable(:b, 1)
true

julia> a = [Variable(:a, i, j) for i in 1:2, j in 1:2]
2×2 Array{Variable,2}:
 a₁₋₁  a₁₋₂
 a₂₋₁  a₂₋₂

julia> sort(vec(a))
4-element Array{Variable,1}:
 a₁₋₁
 a₂₋₁
 a₁₋₂
 a₂₋₂
```
"""
struct Variable <: Number
    ex::Expression

    function Variable(ex::Expression)
        @assert class(ex) == :Symbol
        new(ex)
    end
    Variable(s::Union{String,Symbol}) = new(Expression(s))
end

name(v::Variable) = Symbol(to_string(v))

const Basic = Union{Expression,ExpressionRef,Variable}

Base.convert(::Type{Expression}, v::Variable) = v.ex
Base.convert(::Type{Expression}, e::ExpressionRef) = copy(e)
Base.convert(::Type{ExpressionRef}, e::Expression) = ExpressionRef(e)
Base.convert(::Type{ExpressionRef}, v::Variable) = ExpressionRef(v.ex)

Base.promote_rule(::Type{<:Basic}, ::Type{<:Number}) = Expression
Base.promote_rule(::Type{Expression}, ::Type{Variable}) = Expression
Base.promote_rule(::Type{Expression}, ::Type{ExpressionRef}) = Expression
Base.promote_rule(::Type{Variable}, ::Type{ExpressionRef}) = Expression

function to_string(x::Basic)
    b = ExpressionRef(x)
    if b.ptr == C_NULL
        return ""
    end
    a = ccall((:basic_str_julia, libsymengine), Cstring, (Ref{ExpressionRef},), b)
    string = unsafe_string(a)
    ccall((:basic_str_free, libsymengine), Nothing, (Cstring,), a)
    return string
end

Base.show(io::IO, b::Basic) = print(io, to_string(b))
function Base.show(io::IO, mime::MIME"text/latex", b::Basic)
    print(io, "\$\$ ")
    print(io, to_string(b))
    print(io, " \$\$")
end

function Base.hash(ex::Basic, h::UInt)
    h2 = ccall((:basic_hash, libsymengine), UInt, (Ref{ExpressionRef},), ex)
    Base.hash_uint(3h - h2)
end

function _copy(x::Basic)
    y = Expression()
    ccall(
        (:basic_assign, libsymengine),
        Nothing,
        (Ref{Expression}, Ref{ExpressionRef}),
        y,
        x,
    )
    y
end
Base.copy(x::Basic) = _copy(x)
Base.copy(x::Variable) = Variable(_copy(x))

function Base.:(==)(b1::Basic, b2::Basic)
    ccall(
        (:basic_eq, libsymengine),
        Int,
        (Ref{ExpressionRef}, Ref{ExpressionRef}),
        b1,
        b2,
    ) == 1
end

const EXPR_1 = Expression(C_NULL)
const EXPR_0 = Expression(C_NULL)
const EXPR_M1 = Expression(C_NULL)

Base.zero(::Basic) = zero(Expression)
Base.zero(::Type{<:Basic}) = Expression(0)
Base.iszero(x::Basic) = x == EXPR_0
Base.one(::Basic) = one(Expression)
Base.one(::Type{<:Basic}) = Expression(1)
Base.isone(x::Basic) = x == EXPR_1

is_one(x::Union{Number,Basic}) = isone(x)
is_one(x) = false
is_zero(x::Union{Number,Basic}) = iszero(x)
is_zero(x) = false
is_minus_one(x::Basic) = x == EXPR_M1
is_minus_one(x) = x == -1

for op in [:im, :π, :ℯ, :γ, :catalan]
    @eval begin
        const $(Symbol("__", op)) = Expression(C_NULL)
    end
end
const SYMENGINE_CONSTANTS = Dict{Expression,Irrational}()

macro init_constant(op, libnm)
    tup = (Base.Symbol("basic_const_$libnm"), libsymengine)
    alloc_tup = (:basic_new_stack, libsymengine)
    op_name = Symbol("__", op)
    c = quote
        ccall($alloc_tup, Nothing, (Ref{Expression},), $op_name)
        ccall($tup, Nothing, (Ref{Expression},), $op_name)
        finalizer(free!, $op_name)
        $(
            op != :im ? :(SYMENGINE_CONSTANTS[$op_name] = Base.MathConstants.$op) :
            :(nothing)
        )
    end
    c
end

function __init_constants()
    @init_constant im I
    @init_constant π pi
    @init_constant ℯ E
    @init_constant γ EulerGamma
    @init_constant catalan Catalan

    ccall((:basic_new_stack, libsymengine), Nothing, (Ref{Expression},), EXPR_1)
    ccall((:integer_set_si, libsymengine), Nothing, (Ref{Expression}, Int), EXPR_1, 1)
    finalizer(free!, EXPR_1)

    ccall((:basic_new_stack, libsymengine), Nothing, (Ref{Expression},), EXPR_0)
    ccall((:integer_set_si, libsymengine), Nothing, (Ref{Expression}, Int), EXPR_0, 0)
    finalizer(free!, EXPR_0)

    ccall((:basic_new_stack, libsymengine), Nothing, (Ref{Expression},), EXPR_M1)
    ccall((:integer_set_si, libsymengine), Nothing, (Ref{Expression}, Int), EXPR_M1, -1)
    finalizer(free!, EXPR_M1)
end

################
## arithmetic ##
################

## main ops
for (op, inplace, libnm) in [(:+, :add!, :add), (:-, :sub!, :sub), (:*, :mul!, :mul)]
    # $(Expr(:., :Base, QuoteNode(op)))
    @eval begin
        function $(inplace)(a::Expression, b1::Basic, b2::Basic)
            ccall(
                $((Symbol("basic_", libnm), libsymengine)),
                Nothing,
                (Ref{Expression}, Ref{ExpressionRef}, Ref{ExpressionRef}),
                a,
                b1,
                b2,
            )
            return a
        end
        function Base.$op(b1::Basic, b2::Basic)
            $inplace(Expression(), b1, b2)
        end
    end
end

function div!(a::Expression, b1::Basic, b2::Basic)
    # class(b2) ∈ NUMBER_TYPES || throw(ArgumentError("Cannot divide by " * string(b2)))
    ccall(
        (:basic_div, libsymengine),
        Nothing,
        (Ref{Expression}, Ref{ExpressionRef}, Ref{ExpressionRef}),
        a,
        b1,
        b2,
    )
    return a
end
Base.:(/)(b1::Basic, b2::Basic) = div!(Expression(), b1, b2)

Base.:(//)(x::Basic, y::Basic) = x / y
Base.:(//)(x::Basic, y::Integer) = x / Expression(y)

function Base.:(^)(x::Basic, k::Integer)
    a = Expression()
    ccall(
        (:basic_pow, libsymengine),
        Nothing,
        (Ref{Expression}, Ref{ExpressionRef}, Ref{Expression}),
        a,
        x,
        Expression(k),
    )
    return a
end
Base.:+(b::Basic) = b
Base.:-(b::Basic) = Expression(0) - b

# Functions
macro make_func(arg1, arg2)
    quote
        function $(esc(arg1))(b::Basic)
            a = Expression()
            ccall(
                ($(QuoteNode(arg2)), libsymengine),
                Base.Nothing,
                (Base.Ref{Expression}, Base.Ref{ExpressionRef}),
                a,
                b,
            )
            return a
        end
    end
end

@make_func symengine_expand basic_expand

function Base.sin(b::Basic)
    a = Expression()
    ccall((:basic_sin, libsymengine), Nothing, (Ref{Expression}, Ref{ExpressionRef}), a, b)
    return a
end
function Base.cos(b::Basic)
    a = Expression()
    ccall((:basic_cos, libsymengine), Nothing, (Ref{Expression}, Ref{ExpressionRef}), a, b)
    return a
end
Base.sincos(x::Basic) = (sin(x), cos(x))

function Base.log(b::ModelKit.Basic)
    a = Expression()
    ccall((:basic_log, libsymengine), Nothing, (Ref{Expression}, Ref{ExpressionRef}), a, b)
    return a
end
##############################
## conversion to Expression ##
##############################

function Base.convert(::Type{Expression}, s::String)
    a = Expression()
    ccall((:symbol_set, libsymengine), Nothing, (Ref{Expression}, Ptr{Int8}), a, s)
    return a
end
Base.convert(::Type{Expression}, s::Symbol) = convert(Expression, string(s))

Base.convert(::Type{Expression}, x::Irrational{:γ}) = __γ
Base.convert(::Type{Expression}, x::Irrational{:π}) = __π
Base.convert(::Type{Expression}, x::Irrational{:ℯ}) = __ℯ
Base.convert(::Type{Expression}, x::Irrational{:catalan}) = __catalan

@static if Int == Int64 && Sys.iswindows()
    # basic number types
    for (f, T) in
        [(:integer_set_si, Int32), (:integer_set_ui, UInt32), (:real_double_set_d, Float64)]
        @eval begin
            function Base.convert(::Type{Expression}, x::$T)
                a = Expression()
                ccall(($(QuoteNode(f)), libsymengine), Nothing, (Ref{Expression}, $T), a, x)
                return a
            end
        end
    end

    Base.convert(::Type{Expression}, x::Int64) = convert(Expression, BigInt(x))
    Base.convert(::Type{Expression}, x::UInt64) = convert(Expression, BigInt(x))
else
    # basic number types
    for (f, T) in
        [(:integer_set_si, Int), (:integer_set_ui, UInt), (:real_double_set_d, Float64)]
        @eval begin
            function Base.convert(::Type{Expression}, x::$T)
                a = Expression()
                ccall(($(QuoteNode(f)), libsymengine), Nothing, (Ref{Expression}, $T), a, x)
                return a
            end
        end
    end
    Base.convert(::Type{Expression}, x::Int32) = convert(Expression, convert(Int, x))
    Base.convert(::Type{Expression}, x::UInt32) = convert(Expression, convert(UInt, x))
end

function Base.convert(::Type{Expression}, x::BigInt)
    a = Expression()
    ccall((:integer_set_mpz, libsymengine), Nothing, (Ref{Expression}, Ref{BigInt}), a, x)
    return a
end

Base.convert(::Type{Expression}, x::Union{Float16,Float32}) =
    convert(Expression, convert(Float64, x))
Base.convert(::Type{Expression}, x::AbstractFloat) =
    convert(Expression, convert(BigFloat, x))
function Base.convert(::Type{Expression}, x::BigFloat)
    if (x.prec <= 53)
        return convert(Expression, Float64(x))
    else
        a = Expression()
        ccall(
            (:real_mpfr_set, libsymengine),
            Nothing,
            (Ref{Expression}, Ref{BigFloat}),
            a,
            x,
        )
        return a
    end
end

Base.convert(::Type{Expression}, x::Integer) = Expression(BigInt(x))
Base.convert(::Type{Expression}, x::Rational) =
    Expression(numerator(x)) / Expression(denominator(x))
Base.convert(::Type{Expression}, x::Complex) =
    Expression(real(x)) + Expression(imag(x)) * __im

Base.complex(x::Expression, y::Expression) = x + y * __im
Base.complex(x::Expression, y::Real) = x + y * __im
Base.complex(x::Real, y::Expression) = x + y * __im

################################
## Iterating over expressions ##
################################

mutable struct ExpressionSet
    ptr::Ptr{Cvoid}

    function ExpressionSet()
        z = new(ccall((:setbasic_new, libsymengine), Ptr{Cvoid}, ()))
        finalizer(free!, z)
        z
    end
end

function free!(x::ExpressionSet)
    if x.ptr != C_NULL
        ccall((:setbasic_free, libsymengine), Nothing, (Ptr{Cvoid},), x.ptr)
        x.ptr = C_NULL
    end
    nothing
end

Base.length(s::ExpressionSet) =
    ccall((:setbasic_size, libsymengine), Int, (Ptr{Cvoid},), s.ptr)

function Base.getindex(s::ExpressionSet, n::Int)
    result = Expression()
    ccall(
        (:setbasic_get, libsymengine),
        Nothing,
        (Ptr{Cvoid}, Int, Ref{Expression}),
        s.ptr,
        n - 1,
        result,
    )
    result
end

_variables(ex::Variable) = [ex]
function _variables(ex::Expression)
    syms = ExpressionSet()
    ccall(
        (:basic_free_symbols, libsymengine),
        Nothing,
        (Ref{ExpressionRef}, Ptr{Cvoid}),
        ex,
        syms.ptr,
    )
    sort!([Variable(syms[i]) for i = 1:length(syms)])
end

function differentiate(f::Basic, v::Variable)
    a = Expression()
    ret = ccall(
        (:basic_diff, libsymengine),
        Int,
        (Ref{Basic}, Ref{ExpressionRef}, Ref{ExpressionRef}),
        a,
        f,
        v,
    )
    return a
end
function differentiate(f::Basic, v::Variable, n::Int)
    n < 0 && throw(DomainError("n must be non-negative integer"))
    n == 0 && return f
    n == 1 && return differentiate(f, v)
    n > 1 && return differentiate(differentiate(f, v), v, n - 1)
end

# Get class of an Expression

const REAL_NUMBER_TYPES = [:Integer, :RealDouble, :Rational, :RealMPFR]
const COMPLEX_NUMBER_TYPES = [:Complex, :ComplexDouble, :ComplexMPC]
const NUMBER_TYPES = [
    REAL_NUMBER_TYPES
    COMPLEX_NUMBER_TYPES
]

function type_id(s::Basic)
    ccall((:basic_get_type, libsymengine), UInt, (Ref{ExpressionRef},), s)
end

function get_class_from_type_id(id::UInt)
    a = ccall((:basic_get_class_from_id, libsymengine), Ptr{UInt8}, (Int,), id)
    str = unsafe_string(a)
    ccall((:basic_str_free, libsymengine), Nothing, (Ptr{UInt8},), a)
    Symbol(str)
end

# prepopulate the dict
const TYPE_IDS = Dict{UInt,Symbol}()

function __init_type_ids()
    x = Expression("x")
    values = [
        x,
        Expression(1),
        Expression(0.5),
        Expression(2 // 3),
        Expression(big(0.5)),
        Expression(5 + 3im),
        Expression(0.5 + 0.2im),
        Expression(0.5 + big(0.2) * im),
        2x,
        x + 2,
        x^2,
    ]
    for v in values
        class(v)
    end
    nothing
end

function class(e::Basic)
    id = type_id(e)
    if haskey(TYPE_IDS, id)
        TYPE_IDS[id]
    else
        # add for futurue fast lookup
        cls = get_class_from_type_id(id)
        TYPE_IDS[id] = cls
        cls
    end
end

mutable struct ExprVec <: AbstractVector{ExpressionRef}
    ptr::Ptr{Cvoid}
    m::Union{Nothing,Ptr{ModelKit.ExpressionRef}}

    function ExprVec()
        ptr = ccall((:vecbasic_new, libsymengine), Ptr{Cvoid}, ())
        z = new(ptr, nothing)
        finalizer(free!, z)
        z
    end
end
function vec_set_ptr!(v::ExprVec)
    v.m = unsafe_load(Ptr{Ptr{ModelKit.ExpressionRef}}(v.ptr))
    v
end

function free!(x::ExprVec)
    if x.ptr != C_NULL
        ccall((:vecbasic_free, libsymengine), Nothing, (Ptr{Cvoid},), x.ptr)
        x.ptr = C_NULL
    end
end

Base.eltype(v::ExprVec) = ExpressionRef
Base.length(s::ExprVec) = ccall((:vecbasic_size, libsymengine), UInt, (Ptr{Cvoid},), s.ptr)
Base.size(s::ExprVec) = (length(s),)

function Base.getindex(v::ExprVec, n)
    @boundscheck checkbounds(v, n)
    if v.m === nothing
        vec_set_ptr!(v)
        unsafe_load(v.m, n)
    else
        unsafe_load(v.m, n)
    end
end

function Base.push!(v::ExprVec, x::Basic)
    ccall(
        (:vecbasic_push_back, libsymengine),
        Nothing,
        (Ptr{Cvoid}, Ref{ExpressionRef}),
        v.ptr,
        x,
    )
    v
end

args(ex::Basic) = args!(ExprVec(), ex)
function args!(vec::ExprVec, ex::Basic)
    ccall(
        (:basic_get_args, libsymengine),
        Nothing,
        (Ref{ExpressionRef}, Ptr{Cvoid}),
        ex,
        vec.ptr,
    )
    vec_set_ptr!(vec)
end

################################
## conversion from Expression ##
################################

@static if Int == Int64 && Sys.iswindows()
    function Base.convert(::Type{Int32}, n::Basic)
        @assert class(n) == :Integer
        ccall((:integer_get_si, libsymengine), Int32, (Ref{ExpressionRef},), n)
    end
    Base.convert(::Type{Int64}, n::Basic) = convert(Int64, convert(BigInt, n))
else
    function Base.convert(::Type{Int}, n::Basic)
        @assert class(n) == :Integer
        ccall((:integer_get_si, libsymengine), Int, (Ref{ExpressionRef},), n)
    end
    Base.convert(::Type{Int32}, n::Basic) = convert(Int32, convert(Int, n))
end

function Base.convert(::Type{BigInt}, c::Basic)
    a = BigInt()
    ccall(
        (:integer_get_mpz, libsymengine),
        Nothing,
        (Ref{BigInt}, Ref{ExpressionRef}),
        a,
        c,
    )
    return a
end

function Base.convert(::Type{BigFloat}, c::Basic)
    a = BigFloat()
    ccall(
        (:real_mpfr_get, libsymengine),
        Nothing,
        (Ref{BigFloat}, Ref{ExpressionRef}),
        a,
        c,
    )
    return a
end

function Base.convert(::Type{Float64}, c::Basic)
    return ccall((:real_double_get_d, libsymengine), Cdouble, (Ref{ExpressionRef},), c)
end

function Base.real(x::Basic)
    if class(x) ∈ COMPLEX_NUMBER_TYPES
        a = Expression()
        ccall(
            (:complex_base_real_part, libsymengine),
            Nothing,
            (Ref{Expression}, Ref{ExpressionRef}),
            a,
            x,
        )
        return a
    else
        return x
    end
end
function Base.imag(x::Basic)
    if class(x) ∈ COMPLEX_NUMBER_TYPES
        a = Expression()
        ccall(
            (:complex_base_imaginary_part, libsymengine),
            Nothing,
            (Ref{Expression}, Ref{ExpressionRef}),
            a,
            x,
        )
        return a
    else
        return Expression(0)
    end
end

function _numer_denom(x::Basic)
    a, b = Expression(), Expression()
    ccall(
        (:basic_as_numer_denom, libsymengine),
        Nothing,
        (Ref{Expression}, Ref{Expression}, Ref{ExpressionRef}),
        a,
        b,
        x,
    )
    return a, b
end

is_number(ex::Expression) = class(ex) in NUMBER_TYPES

"""
    to_number(x::Expression)

Tries to unpack the `Expression` `x` to a native number type.

```julia-repl
julia> x = to_number(Expression(2))
2

julia> typeof(x)
Int64
"""
function to_number(x::Basic)
    cls = class(x)

    if cls == :Integer
        n = convert(BigInt, x)
        if typemin(Int32) ≤ n ≤ typemax(Int32)
            return convert(Int32, n)
        elseif typemin(Int64) ≤ n ≤ typemax(Int64)
            return convert(Int64, n)
        elseif typemin(Int128) ≤ n ≤ typemax(Int128)
            return convert(Int128, n)
        else
            return n
        end
    elseif cls == :RealDouble
        return convert(Float64, x)
    elseif cls == :Rational
        a, b = _numer_denom(x)
        return to_number(a) // to_number(b)
    elseif cls == :RealMPFR
        return convert(BigFloat, x)
    elseif cls == :Constant
        return SYMENGINE_CONSTANTS[x]
    elseif cls in COMPLEX_NUMBER_TYPES
        a, b = reim(x)
        return complex(to_number(a), to_number(b))
    else
        return x
    end
end

function (::Type{T})(x::Basic) where {T<:Union{AbstractFloat,Rational,Complex,Integer}}
    y = to_number(x)
    if y isa Basic
        # Throw error to avoid endless loop
        # The T.name.name part is done the same way in Base
        throw(InexactError(T.name.name, T, x))
    else
        T(y)
    end
end

##########
## SUBS ##
##########

mutable struct ExpressionMap
    ptr::Ptr{Cvoid}

    function ExpressionMap()
        x = new(ccall((:mapbasicbasic_new, libsymengine), Ptr{Cvoid}, ()))
        finalizer(free!, x)
        x
    end
end

function free!(x::ExpressionMap)
    if x.ptr != C_NULL
        ccall((:mapbasicbasic_free, libsymengine), Nothing, (Ptr{Cvoid},), x.ptr)
        x.ptr = C_NULL
    end
end

ExpressionMap(args...) = ExpressionMap(ExpressionMap(), args...)
ExpressionMap(D::ExpressionMap) = D
function ExpressionMap(dict::Dict)
    c = ExpressionMap()
    for (key, value) in dict
        c[Expression(key)] = Expression(value)
    end
    return c
end
function ExpressionMap(
    D::ExpressionMap,
    (xs, ys)::Pair{<:AbstractArray{<:Basic},<:AbstractArray},
    args...,
)
    size(xs) == size(ys) ||
        throw(ArgumentError("Substitution arguments don't have the same size."))
    for (x, y) in zip(xs, ys)
        D[x] = y
    end
    ExpressionMap(D, args...)
end
function ExpressionMap(D::ExpressionMap, (x, y), args...)
    D[Expression(x)] = Expression(y)
    ExpressionMap(D, args...)
end

function Base.length(s::ExpressionMap)
    ccall((:mapbasicbasic_size, libsymengine), Int, (Ptr{Cvoid},), s.ptr)
end

Base.setindex!(s::ExpressionMap, v::Number, k::Basic) = setindex!(s, Expression(v), k)
function Base.setindex!(s::ExpressionMap, v::Basic, k::Basic)
    ccall(
        (:mapbasicbasic_insert, libsymengine),
        Nothing,
        (Ptr{Cvoid}, Ref{ExpressionRef}, Ref{ExpressionRef}),
        s.ptr,
        k,
        v,
    )
    v
end

function subs(ex::Basic, d::ExpressionMap)
    s = Expression()
    ccall(
        (:basic_subs, libsymengine),
        Nothing,
        (Ref{Expression}, Ref{ExpressionRef}, Ptr{Cvoid}),
        s,
        ex,
        d.ptr,
    )
    return s
end

function cse(exprs::Vector{Expression})
    vec = ExprVec()
    for ex in exprs
        push!(vec, ex)
    end

    replacement_syms = ExprVec()
    replacement_exprs = ExprVec()
    reduced_exprs = ExprVec()

    ccall(
        (:basic_cse, libsymengine),
        Nothing,
        (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}),
        replacement_syms.ptr,
        replacement_exprs.ptr,
        reduced_exprs.ptr,
        vec.ptr,
    )

    # The symengine cse alg also considers in (x - a) * (y - a) the -a as a cse.
    # We don't want these since we can handle them with no overhead directly,
    # so we remove these again.
    bad_subs = ExpressionMap()
    substs = Dict{Expression,Expression}()
    for (sᵢ, exᵢ) in zip(replacement_syms, replacement_exprs)
        if class(exᵢ) == :Mul
            exs = args(exᵢ)
            if length(exs) == 2 && (is_minus_one(exs[1]) || is_one(exs[1]))
                bad_subs[sᵢ] = exᵢ
            else
                substs[sᵢ] = subs(exᵢ, bad_subs)
            end
        else
            substs[sᵢ] = subs(exᵢ, bad_subs)
        end
    end

    map(e -> subs(e, bad_subs), reduced_exprs), substs
end


function test_diffusion_simple(fld)
    Nx, Ny, Nz = 1, 1, 16
    Lx, Ly, Lz = 1, 1, 1
    κ = 1
    eos = LinearEquationOfState(βS=0, βT=0)

    model = Model(N=(Nx, Ny, Nz), L=(Lx, Ly, Lz), ν=κ, κ=κ, eos=eos)

    if fld ∈ (:u, :v, :w)
        field = getfield(model.velocities, fld)
    else
        field = getfield(model.tracers, fld)
    end

    value = π
    field.data .= value

    Δt = 0.01 # time-step much less than diffusion time-scale
    Nt = 10
    time_step!(model, Nt, Δt)

    !any(@. !isapprox(value, field.data))
end

function test_diffusion_budget(field_name)
    Nx, Ny, Nz = 1, 1, 16
    Lx, Ly, Lz = 1, 1, 1
    κ = 1
    eos = LinearEquationOfState(βS=0, βT=0)

    model = Model(N=(Nx, Ny, Nz), L=(Lx, Ly, Lz), ν=κ, κ=κ, eos=eos)

    if field_name ∈ (:u, :v, :w)
        field = getfield(model.velocities, field_name)
    else
        field = getfield(model.tracers, field_name)
    end

    half_Nz = floor(Int, Nz/2)
    @views @. field.data[:, :, 1:half_Nz]  = -1
    @views @. field.data[:, :, half_Nz:end] =  1

    mean_init = mean(field.data)
    τκ = Lz^2 / κ # diffusion time-scale
    Δt = 0.0001 * τκ # time-step much less than diffusion time-scale
    Nt = 100

    time_step!(model, Nt, Δt)
    isapprox(mean_init, mean(field.data))
end

function test_diffusion_cosine(fld)
    Nx, Ny, Nz = 1, 1, 128
    Lx, Ly, Lz = 1, 1, π/2
    κ = 1
    eos = LinearEquationOfState(βS=0, βT=0)

    model = Model(N=(Nx, Ny, Nz), L=(Lx, Ly, Lz), ν=κ, κ=κ, eos=eos)

    if fld == :w
        throw("There are no boundary condition tests for w yet.")
    elseif fld ∈ (:u, :v)
        field = getfield(model.velocities, fld)
    else
        field = getfield(model.tracers, fld)
    end

    zC = model.grid.zC
    m = 2
    @views @. field.data[1, 1, :] = cos.(m*zC)

    diffusing_cosine(κ, m, z, t) = exp(-κ*m^2*t) * cos(m*z)

    τκ = Lz^2 / κ # diffusion time-scale
    Δt = 1e-6 * τκ # time-step much less than diffusion time-scale
    Nt = 100

    time_step!(model, Nt, Δt)

    field_numerical = dropdims(field.data, dims=(1, 2))

    !any(@. !isapprox(field_numerical, diffusing_cosine(κ, m, zC, model.clock.time), atol=1e-6, rtol=1e-6))
end

function test_z_boundary_condition_simple(field_name, bctype, bc, Nx, Ny, Nz)
    Lx, Ly, Lz = 0.1, 0.2, 0.3
    model = Model(N=(Nx, Ny, Nz), L=(Lx, Ly, Lz))

    bc = BoundaryCondition(bctype, bc)
    bcs = getfield(model.boundary_conditions, field_name)
    bcs.z.right = bc # just set a boundary condition somewhere

    time_step!(model, 1, 1e-16)
    typeof(model) <: Model
end

function test_flux_budget(field_name)
    Nx, Ny, Nz = 1, 1, 16
    Lx, Ly, Lz = 1, 1, 0.7
    κ = 1
    eos = LinearEquationOfState(βS=0, βT=0)

    model = Model(N=(Nx, Ny, Nz), L=(Lx, Ly, Lz), ν=κ, κ=κ, eos=eos)

    if field_name ∈ (:u, :v, :w)
        field = getfield(model.velocities, field_name)
    else
        field = getfield(model.tracers, field_name)
    end

    @. field.data = 0

    bottom_flux = 0.3
    flux_bc = BoundaryCondition(Flux, bottom_flux)
    bcs = getfield(model.boundary_conditions, field_name)
    bcs.z.right = flux_bc # "right" = "bottom" in the convention where k=Nz is the bottom.

    mean_init = mean(field.data)

    τκ = Lz^2 / κ # diffusion time-scale
    Δt = 1e-6 * τκ # time-step much less than diffusion time-scale
    Nt = 100

    time_step!(model, Nt, Δt)

    # budget is Lz * ∂<ϕ>/∂t = -Δflux = -top_flux/Lz (left) + bottom_flux/Lz (right);
    # therefore <ϕ> = bottom_flux * t / Lz
    isapprox(mean(field.data) - mean_init, bottom_flux*model.clock.time/Lz)
end


using ASCIIPlots
using Base.Test

x = randn(20)
y = 21.0 * x + randn(20)
lineplot(x, y)


"""
  punch_holes_2D(centers, radius, xpoints, ypoints)

...
# Arguments

  - `centers::Union{Vector}`: the vector containing the punch centers
  - `radius::Vector`: the tuple containing the punch radii
  - `Nx::Int64`: the number of points in the x direction
  - `Ny::Int64`: the number of points in the y direction
...

...
# Outputs
  - `absolute_indices::Vector{Int64}`: vector containing the indices of coordinates
  inside the punch
...

"""
function punch_holes_2D(centers, radius::Union{T, Vector{T}},
                        Nx, Ny) where T<:Number
    clen = (typeof(centers) <: Vector) ? length(centers) : 1
    rad  = (typeof(radius) <: Vector) ? radius : radius * ones(clen)
    masking_data_points = []
    absolute_indices = Int64[]
    for a = 1:clen
        c = centers[a]
        count = 1
        for j = 1:Ny
            for h = 1:Nx
                if (((h-c[1]))^2 + ((j-c[2]))^2  <= radius[a]^2)
                    append!(masking_data_points,[(h,j)])
                    append!(absolute_indices, count)
                end
                count = count + 1
            end
        end
    end
    return absolute_indices
end

"""
  punch_holes_3D(centers, radius, xpoints, ypoints, zpoints)

...
# Arguments

  - `centers::Vector{T}`: the vector containing the centers of the punches
  - `radius::Float64`: the radius of the punch
  - `Nx::Int64`: the number of points in the x-direction, this code is hard-coded to start from one.
  - `Ny::Int64`: the number of points in the y-direction
  - `Nz::Int64`: the number of points in the z-direction
...

...
# Outputs
  - `absolute_indices::Vector{Int64}`: vector containing the indices of coordinates
  inside the punch
...

"""
function punch_holes_3D(centers, radius, Nx, Ny, Nz)
    clen = length(centers)
    masking_data_points = []
    absolute_indices = Int64[]
    for a = 1:clen
        c = centers[a]
        count = 1
        for i = 1:Nz
            for j = 1:Ny
                for h = 1:Nx
                    if((h-c[1])^2 + (j-c[2])^2 + (i - c[3])^2 <= radius^2)
                        append!(masking_data_points, [(h, j, i)])
                        append!(absolute_indices, count)
                    end
                    count = count +1
                end
            end
        end
    end
    return absolute_indices
end

"""
  punch_3D_cart(center, radius, xpoints, ypoints, zpoints; <kwargs>)

...
# Arguments

  - `center::Tuple{T}`: the tuple containing the center of a round punch
  - `radius::Union{Tuple{Float64},Float64}`: the radii/radius of the punch
  - `x::Vector{T} where T<:Real`: the vector containing the x coordinate
  - `y::Vector{T} where T<:Real`: the vector containing the y coordinate
  - `z::Vector{T} where T<:Real`: the vector containing the z coordinate

# Optional

  - `linear::Bool = false` can return a linear index
...

...
# Outputs
  - `inds::Vector{Int64}`: vector containing the indices of coordinates
  inside the punch
...

"""
function punch_3D_cart(center, radius, x, y, z; linear = false)
    radius_x, radius_y, radius_z = (typeof(radius) <: Tuple) ? radius :
                                                (radius, radius, radius)
    inds = filter(i -> (((x[i[1]]-center[1])/radius_x)^2
                        + ((y[i[2]]-center[2])/radius_y)^2
                        + ((z[i[3]] - center[3])/radius_z)^2 <= 1.0),
                  CartesianIndices((1:length(x), 1:length(y), 1:length(z))))
    (length(inds) == 0) && error("Empty punch.")
    if linear == false
      return inds
    else
      return LinearIndices(zeros(length(x), length(y), length(z)))[inds]
    end
end

function bounding_box(x::Array{CartesianIndex{3},1})
    #for i in 1:p
    xa = map(t -> t[1], x); xa_min, xa_max = (minimum(xa), maximum(xa))
    xb = map(t -> t[2], x); xb_min, xb_max = (minimum(xb), maximum(xb))
    xc = map(t -> t[3], x); xc_min, xc_max = (minimum(xc), maximum(xc))
    # This requires julia v 1.6
    # return CartesianIndices((xa_min:xa_max, xb_min:xb_max, xc_min:xc_max))
    list = [CartesianIndex(x,y,z) for x in xa_min:xa_max for y in xb_min:xb_max for z in xc_min:xc_max]
    return list
end

intersect_box(A, B) = maximum([minimum(A), minimum(B)]):minimum([maximum(A), maximum(B)])

my_floor(x) = (x>0) ? floor(x) : -floor(abs(x))

"""

Generate a list of centers

# Example

```<julia-repl>
xmin = 0
xmax = 6
ymin = 0
ymax = 8
zmin = 0
zmax = 8

centers = LaplaceInterpolation.center_list('A', xmin, xmax, ymin, ymax, zmin,
zmax)
```
"""
function center_list(symm, Qh_min, Qh_max, Qk_min, Qk_max, Ql_min, Ql_max)
    hs = my_floor(Qh_min):my_floor(Qh_max)
    ks = my_floor(Qk_min):my_floor(Qk_max)
    ls = my_floor(Ql_min):my_floor(Ql_max)
    hkl = [(h,k,l) for h in hs for k in ks for l in ls]
    if symm == 'P'
        centers = filter(i -> ~P(i...), hkl)
    elseif symm == 'A'
        centers = filter(i -> ~A(i...), hkl)
    elseif symm == 'B'
        centers = filter(i -> ~B(i...), hkl)
    elseif symm == 'C'
        centers = filter(i -> ~C(i...), hkl)
    elseif symm == 'I'
        centers = filter(i -> ~Ii(i...), hkl)
    elseif symm == 'F'
        centers = filter(i -> ~F(i...), hkl)
    elseif symm == 'R'
        centers = filter(i -> ~R(i...), hkl)
    else
        centers = hkl
    end
    return centers
end

function center_check(symm)
    if symm == 'P'
        tof = (h, k, l) -> ~P(h, k, l)
    elseif symm == 'A'
        tof = (h, k, l) -> ~A(h, k, l)
    elseif symm == 'B'
        tof = (h, k, l) -> ~B(h, k, l)
    elseif symm == 'C'
        tof = (h, k, l)-> ~C(h, k, l)
    elseif symm == 'I'
        tof = (h, k, l) -> ~Ii(h, k, l)
    elseif symm == 'F'
        tof = (h, k, l) -> ~F(h, k, l)
    elseif symm == 'R'
        tof = (h, k, l) -> ~R(h, k, l)
    else
        tof = (h, k, l) -> true
    end
    return tof
end

# Systematic absences

function P(h,k,l)
    return false
end

function A(h,k,l)
    return (k+l)%2 != 0
end

function B(h,k,l)
    return (h+l)%2 != 0
end

function C(h,k,l)
    return (h+k)%2 != 0
end

function Ii(h,k,l)
    return (h+k+l)%2 != 0
end

function F(h,k,l)
    return ((h+k)%2!=0)||((h+l)%2!=0)||((k+l)%2!=0)
end

function R(h,k,l)
    return (-h+k+l)%3 != 0
end