# include("hyperelasticity.jl")

ts = ExplicitSolverTime(Δt, NT)
ubd, abd = compute_boundary_info(domain, globaldata, ts)
Fext = compute_external_force(domain, globaldata, ts)

d0 = zeros(2domain.nnodes)
v0 = zeros(2domain.nnodes)
a0 = zeros(2domain.nnodes)

# general neural network
function nn_law(ε)
  ae(ε, [20,20,20,3])
end

# free energy
function nn_law(ε)
  φ = squeeze(ae(ε, [20,20,20,1]))
  tf.gradients(φ, ε)
end

# parameter
c1 = Variable(1.0) * 1e-1
c2 = Variable(1.0) * 1e-3
function nn_law(ε)
  compute_stress_rivlin_saunders(ε, c1, c2)
end

d, v, a= ExplicitSolver(globaldata, domain, d0, v0, a0, Δt, NT, nn_law, Fext, ubd, abd; strain_type="finite")

idx = @. (0:n)*m + 1
idx = [idx; (@. idx + domain.nnodes)]
loss = sum((d[:, idx] - d_[:, idx])^2)
sess = Session(); init(sess)
@info run(sess, loss)
# error()

BFGS!(sess, loss)

# for visualizing the parameters
# sol = []
# cb = (vs, iter, loss)->begin
#   push!(sol, vs)
#   printstyled("[#iter $iter] a = $vs, loss=$loss\n", color=:green)
# end
# BFGS!(sess, loss, callback = cb, vars = [c1, c2])

# plot([x[1] for x in sol], "+--", color="red", label="\$C1\$")
# plot(ones(length(sol))*1e-1, "-", color="k", alpha=0.3, label="Reference Value (C1 and C2)")
# plot([x[2] for x in sol], "x--", color ="green", label="\$C2\$")
# xlabel("Iterations")
# legend()


# 3 dimensionsal evaluation functions.
# Transforms code is scattered, and will be moved to better location with stable 3D Examples
# also worth it to consolidate if other affine transforms package is available

# using Euler angles for linear sampling in product of potentials
# Gaussian model for prior
function evalPotential(obs::PriorPose3, Xi::Array{Graphs.ExVertex,1}; N::Int64=200)
  mu = veeEuler(obs.Zi)
  return rand( MvNormal(mu, obs.Cov), N)
end

# Project all particles (columns) Xval with Z, that is for all  SE3(Xval[:,i])*Z
function projectParticles(Xval::Array{Float64,2}, Z::SE3, Cov::Array{Float64,2})
  # TODO optimize for more speed with threads and better memory management
  r,c = size(Xval)
  RES = zeros(r,c) #*cz

  ent, x = SE3(0), SE3(0)
  ENT = rand( MvNormal(zeros(6), Cov), c )

  j=1
  # for j in 1:cz
    for i in 1:c
      x.R, x.t = convert(SO3,Euler(Xval[4:6,i])), Xval[1:3,i]
      ent.R, ent.t = convert(SO3,so3(ENT[4:6,i])), ENT[1:3,i]
      newval = x*Z*ent
      RES[1:r,i*j] = veeEuler(newval)
    end
  # end
  #
  return RES
end

# Still limited to linear sampler, then reprojected onto ball -- TODO upgrade manifold sampler
function evalPotential(odom::Pose3Pose3, Xi::Array{Graphs.ExVertex,1}, Xid::Int64; N::Int64=200)
    # rz,cz = size(odom.Zij)
    Xval = Array{Float64,2}()
    # implicit equation portion -- bi-directional pairwise function made explicit here
    if Xid == Xi[1].index #odom.
        # reverse direction
        println(" reverse direction")
        Z = inverse(odom.Zij)
        Xval = getVal(Xi[2])
    elseif Xid == Xi[2].index
        # forward direction
        println(" forward direction")
        Z = odom.Zij
        Xval = getVal(Xi[1])
    else
        error("Bad evalPairwise Pose3Pose3")
    end

    return projectParticles(Xval, Z, odom.Cov)
end


#Sparse Ensemble Kalman Inversion: specific structures and function definitions
using Convex, SCS
using SparseArrays

"""
    SparseInversion <: Process

A sparse ensemble Kalman Inversion process
"""
struct SparseInversion{FT <: AbstractFloat, IT <: Int} <: Process
    "upper limit of l1-norm"
    γ::FT
    "the flag to enable thresholding"
    threshold_eki::Bool
    "a small value to threshold results that are close to zero"
    threshold_value::FT
    "a small value for regularization to enhance robustness of convex optimization"
    reg::FT
    "a list of index to indicate the parameters included in the evaluation of l1-norm"
    uc_idx::Vector{IT}
end

"""
    sparse_qp(
        ekp::EnsembleKalmanProcess{FT, IT, SparseInversion{FT,IT}},
        v_j::Vector{FT},
        cov_vv_inv::Array{FT, 2},
        H_u::SparseArrays.SparseMatrixCSC{FT},
        H_g::SparseArrays.SparseMatrixCSC{FT},
        y_j::Vector{FT};
        H_uc::SparseArrays.SparseMatrixCSC{FT} = H_u,
    ) where {FT, IT}

Solving quadratic programming problem with sparsity constraint.
"""
function sparse_qp(
    ekp::EnsembleKalmanProcess{FT, IT, SparseInversion{FT, IT}},
    v_j::Vector{FT},
    cov_vv_inv::Array{FT, 2},
    H_u::SparseArrays.SparseMatrixCSC{FT},
    H_g::SparseArrays.SparseMatrixCSC{FT},
    y_j::Vector{FT};
    H_uc::SparseArrays.SparseMatrixCSC{FT} = H_u,
) where {FT, IT}

    P = H_g' * (ekp.obs_noise_cov \ H_g) + cov_vv_inv
    P = 0.5 * (P + P')
    q = -(cov_vv_inv * v_j + H_g' * (ekp.obs_noise_cov \ y_j))
    N_params = size(H_uc)[1]
    P1 = vcat(
        hcat(P, fill(FT(0), size(P)[1], N_params)),
        hcat(fill(FT(0), N_params, size(P)[1]), fill(FT(0), N_params, N_params)),
    )
    q1 = vcat(q, fill(FT(0), N_params, 1))
    H_uc_abs = 1.0 * I(N_params)
    G = hcat(vcat(H_uc, -1.0 * H_uc), vcat(-1.0 * H_uc_abs, -1.0 * H_uc_abs))
    h = fill(FT(0), 2 * N_params, 1)
    G1 = vcat(G, hcat(fill(FT(0), 1, size(P)[1]), fill(FT(1), 1, N_params)))
    h1 = vcat(h, ekp.process.γ)
    x = Variable(size(P1)[1])
    problem = minimize(0.5 * quadform(x, P1; assume_psd = true) + q1' * x, [G1 * x <= h1])
    solve!(problem, () -> SCS.Optimizer(verbose = false))

    return hcat(H_u, fill(FT(0), size(H_u)[1], N_params)) * evaluate(x)
end

"""
    update_ensemble!(
        ekp::EnsembleKalmanProcess{FT, IT, <:SparseInversion{FT,IT}},
        g::Array{FT,2};
        cov_threshold::FT=0.01,
        Δt_new=nothing
    ) where {FT, IT}

Updates the ensemble according to which type of Process we have. Model outputs `g` need to be a `N_obs × N_ens` array (i.e data are columms).
"""
function update_ensemble!(
    ekp::EnsembleKalmanProcess{FT, IT, SparseInversion{FT, IT}},
    g::Array{FT, 2};
    cov_threshold::FT = 0.01,
    Δt_new = nothing,
    deterministic_forward_map = true,
) where {FT, IT}

    # Update follows eqns. (4) and (5) of Schillings and Stuart (2017)

    #catch works when g non-square
    if !(size(g)[2] == ekp.N_ens)
        throw(DimensionMismatch("ensemble size in EnsembleKalmanProcess and g do not match, try transposing g or check ensemble size"))
    end

    # u: N_par × N_ens
    # g: N_obs × N_ens
    u = get_u_final(ekp)
    N_obs = size(g, 1)

    cov_init = cov(u, dims = 2)
    cov_ug = cov(u, g, dims = 2, corrected = false) # [N_par × N_obs]
    cov_gg = cov(g, g, dims = 2, corrected = false) # [N_obs × N_obs]
    cov_uu = cov(u, u, dims = 2, corrected = false) # [N_par × N_par]

    if !isnothing(Δt_new)
        push!(ekp.Δt, Δt_new)
    elseif isnothing(Δt_new) && isempty(ekp.Δt)
        push!(ekp.Δt, FT(1))
    else
        push!(ekp.Δt, ekp.Δt[end])
    end

    v = hcat(u', g')

    # Scale noise using Δt
    scaled_obs_noise_cov = ekp.obs_noise_cov / ekp.Δt[end]
    noise = rand(MvNormal(zeros(N_obs), scaled_obs_noise_cov), ekp.N_ens)

    # Add obs_mean (N_obs) to each column of noise (N_obs × N_ens) if
    # G is deterministic
    y = deterministic_forward_map ? (ekp.obs_mean .+ noise) : (ekp.obs_mean .+ zero(noise))

    # N_obs × N_obs \ [N_obs × N_ens]
    # --> tmp is [N_obs × N_ens]
    tmp = (cov_gg + scaled_obs_noise_cov) \ (y - g)
    u += (cov_ug * tmp) # [N_par × N_ens]

    # store new parameters (and model outputs)
    push!(ekp.u, DataContainer(u, data_are_columns = true))
    push!(ekp.g, DataContainer(g, data_are_columns = true))

    # Sparse EKI
    cov_vv = vcat(hcat(cov_uu, cov_ug), hcat(cov_ug', cov_gg))
    H_u = hcat(1.0 * I(size(u)[1]), fill(FT(0), size(u)[1], size(g)[1]))
    H_g = hcat(fill(FT(0), size(g)[1], size(u)[1]), 1.0 * I(size(g)[1]))

    H_uc = H_u[ekp.process.uc_idx, :]

    cov_vv_inv = cov_vv \ (1.0 * I(size(cov_vv)[1]))

    # Loop over ensemble members to impose sparsity
    Threads.@threads for j in 1:(ekp.N_ens)
        # Solve a quadratic programming problem
        u[:, j] = sparse_qp(ekp, v[j, :], cov_vv_inv, H_u, H_g, y[:, j], H_uc = H_uc)

        # Threshold the results if needed
        if ekp.process.threshold_eki
            u[ekp.process.uc_idx, j] =
                u[ekp.process.uc_idx, j] .* (abs.(u[ekp.process.uc_idx, j]) .> ekp.process.threshold_value)
        end

        # Add small noise to constrained elements of u
        u[ekp.process.uc_idx, j] +=
            rand(MvNormal(zeros(size(ekp.process.uc_idx)[1]), ekp.process.reg * I(size(ekp.process.uc_idx)[1])))
    end

    # Store error
    compute_error!(ekp)

    # Check convergence
    cov_new = cov(get_u_final(ekp), dims = 2)
    cov_ratio = det(cov_new) / det(cov_init)
    if cov_ratio < cov_threshold
        @warn string(
            "New ensemble covariance determinant is less than ",
            cov_threshold,
            " times its previous value.",
            "\nConsider reducing the EK time step.",
        )
    end
end


function cartesianmoveindex(direction)
    directiondict = Dict("UP" => (-1, 0), "DOWN" => (1, 0), "LEFT" => (0, -1), "RIGHT" => (0, 1))

    return CartesianIndex(directiondict[direction])
end

function swaptiles!(board, x, y)
    temp = board[x]
    board[x] = board[y]
    board[y] = temp
end

function moves(board)
    first = findfirst(iszero, board)
    movedict = Dict("UP" => "DOWN", "DOWN" => "UP", "LEFT" => "RIGHT", "RIGHT" => "LEFT")

    directions = []
    for i in ("UP", "DOWN", "LEFT", "RIGHT")
        index = first + cartesianmoveindex(i)

        if checkbounds(Bool, board, index)
            action = (index, movedict[i])
            push!(directions, action)
        end
    end

    return directions
end

function copyboard(action, board)
    newboard = copy(board)
    index1 = action[1]
    index2 = index1 + cartesianmoveindex(action[2])
    swaptiles!(newboard, index1, index2)

    return newboard
end

## determine all options for move states
function expansion(board)
    states = []
    possacts = moves(board)

    for act in possacts
        push!(states, copyboard(act, board))
    end

    return states
end


function parse_line(l)
    name, spd, dur, rst = match(r"(\w+)[ \D]+(\d+)[ \D]+(\d+)[ \D]+(\d+).*", l).captures
    (name, parse(Int, spd), parse(Int, dur), parse(Int, rst))
end

const data = parse_line.(readlines())
const time = 2503

function distance(spd, dur, rst, time)
    dst = zeros(Int, time+1)
    run = dur
    slp = 0
    for i in 2:time+1
        if run > 0
            dst[i] = dst[i-1] + spd
            run -= 1
            run == 0 && (slp = rst)
        else
            dst[i] = dst[i-1]
            slp -= 1
            slp == 0 && (run = dur)
        end
    end
    # @show dst
    dst
end

# Part 1
maximum(data) do (_, spd, dur, rst)
    distance(spd, dur, rst, time)[end]
end |> println

all_scores = reduce(hcat, [distance(spd, dur, rst, time) for (_, spd, dur, rst) in data])
best_score = maximum(all_scores; dims=2)

# Part 2
maximum(data) do (_, spd, dur, rst)
    (distance(spd, dur, rst, time) .== best_score) |> sum
end - 1 |> println


# ------------------------------------------------------------------------------------------
# # Introduction to Recurrent Neural Networks
# (c) Deniz Yuret, 2019
# * Objectives: learn about RNNs, the RNN layer, compare with MLP on a tagging task.
# * Prerequisites: [MLP models](40.mlp.ipynb)
# * New functions:
# [RNN](http://denizyuret.github.io/Knet.jl/latest/reference/#Knet.RNN),
# [adam](http://denizyuret.github.io/Knet.jl/latest/reference/#Knet.adam)
#
# ![image](https://github.com/denizyuret/Knet.jl/blob/master/docs/src/images/RNN-
# unrolled.png?raw=true)([image
# source](http://colah.github.io/posts/2015-08-Understanding-LSTMs))
#
# In this notebook we will see how to implement a recurrent neural network (RNN) in Knet. In
# RNNs, connections between units form a directed cycle, which allows them to keep a
# persistent state over time. This gives them the ability to process sequences of arbitrary
# length one element at a time, while keeping track of what happened at previous elements.
# One can view the current state of the RNN as a representation for the sequence processed
# so far.
#
# We will build a part-of-speech tagger using a large annotated corpus of English. We will
# represent words with numeric vectors appropriate as inputs to a neural network. These word
# vectors will be initialized randomly and learned during training just like other model
# parameters. We will compare three network architectures: (1) an MLP which tags each word
# independently of its neighbors, (2) a simple RNN that can represent the neighboring words
# to the left, (3) a bidirectional RNN that can represent both left and right contexts. As
# can be expected 1 < 2 < 3 in performance. More surprisingly, the three models are very
# similar to each other: we will see their model diagrams are identical except for the
# horizontal connections that carry information across the sequence.
# ------------------------------------------------------------------------------------------

# Setup display width, load packages, import symbols
ENV["COLUMNS"] = 72
using Pkg; for p in ("Knet","Plots"); haskey(Pkg.installed(),p) || Pkg.add(p); end
using Random: shuffle!
using Base.Iterators: flatten
using Knet: Knet, AutoGrad, param, param0, mat, RNN, relu, Data, adam, progress, nll, zeroone

# ------------------------------------------------------------------------------------------
# ## The Brown Corpus
# To introduce recurrent neural networks (RNNs) we will train a part-of-speech tagger using
# the [Brown Corpus](https://en.wikipedia.org/wiki/Brown_Corpus). We will train three
# models: a MLP, a unidirectional RNN, a bidirectional RNN and observe significant
# performance differences.
# ------------------------------------------------------------------------------------------

include(Knet.dir("data/nltk.jl"))
(data,words,tags) = brown()
println("The Brown Corpus has $(length(data)) sentences, $(sum(length(p[1]) for p in data)) tokens, with a word vocabulary of $(length(words)) and a tag vocabulary of $(length(tags)).")

# ------------------------------------------------------------------------------------------
# `data` is an array of `(w,t)` pairs each representing a sentence, where `w` is a sequence
# of word ids, and `t` is a sequence of tag ids. `words` and `tags` contain the strings for
# the ids.
# ------------------------------------------------------------------------------------------

println.(summary.((data,words,tags)));

# ------------------------------------------------------------------------------------------
# Here is what the first sentence looks like with ids and with strings:
# ------------------------------------------------------------------------------------------

(w,t) = first(data)
display(permutedims(Int[w t]))
display(permutedims([words[w] tags[t]]))

# ------------------------------------------------------------------------------------------
# ## Chain of layers
# ------------------------------------------------------------------------------------------

# Let's define a chain of layers
struct Chain
    layers
    Chain(layers...) = new(layers)
end
(c::Chain)(x) = (for l in c.layers; x = l(x); end; x)
(c::Chain)(x,y) = nll(c(x),y)

# ------------------------------------------------------------------------------------------
# ## Dense layers
# ------------------------------------------------------------------------------------------

# Redefine dense layer (See mlp.ipynb):
struct Dense; w; b; f; end
Dense(i::Int,o::Int,f=identity) = Dense(param(o,i), param0(o), f)
(d::Dense)(x) = d.f.(d.w * mat(x,dims=1) .+ d.b)

# ------------------------------------------------------------------------------------------
# ## Word Embeddings
# `data` has each sentence tokenized into an array of words and each word mapped to a
# `UInt16` id. To use these words as inputs to a neural network we further map each word to
# a Float32 vector. We will keep these vectors in the columns of a size (X,V) matrix where X
# is the embedding dimension and V is the vocabulary size. The vectors will be initialized
# randomly, and trained just like any other network parameter. Let's define an embedding
# layer for this purpose:
# ------------------------------------------------------------------------------------------

struct Embed; w; end
Embed(vocabsize::Int,embedsize::Int) = Embed(param(embedsize,vocabsize))
(e::Embed)(x) = e.w[:,x]

# ------------------------------------------------------------------------------------------
# This is what the words, word ids and embeddings for a sentence looks like: (note the
# identical id and embedding for the 2nd and 5th words)
# ------------------------------------------------------------------------------------------

embedlayer = Embed(length(words),8)
(w,t) = data[52855]
display(permutedims(words[w]))
display(permutedims(Int.(w)))
display(embedlayer(w))

# ------------------------------------------------------------------------------------------
# ## RNN layers
# ------------------------------------------------------------------------------------------

@doc RNN

# ------------------------------------------------------------------------------------------
# ## The three taggers: MLP, RNN, biRNN
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# ## Tagger0 (MLP)
# This is what Tagger0 looks like. Every tag is predicted independently. The prediction of
# each tag only depends on the corresponding word.
#
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# ## Tagger1 (RNN)
# In Tagger1, the RNN layer takes its previous output as an additional input. The prediction
# of each tag is based on words to the left.
#
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# ## Tagger2 (biRNN)
# In Tagger2 there are two RNNs: the forward RNN reads the sequence from left to right, the
# backward RNN reads it from right to left. The prediction of each tag is dependent on all
# the words in the sentence.
#
# ------------------------------------------------------------------------------------------

Tagger0(vocab,embed,hidden,output)=  # MLP Tagger
    Chain(Embed(vocab,embed),Dense(embed,hidden,relu),Dense(hidden,output))
Tagger1(vocab,embed,hidden,output)=  # RNN Tagger
    Chain(Embed(vocab,embed),RNN(embed,hidden,rnnType=:relu),Dense(hidden,output))
Tagger2(vocab,embed,hidden,output)=  # biRNN Tagger
    Chain(Embed(vocab,embed),RNN(embed,hidden,rnnType=:relu,bidirectional=true),Dense(2hidden,output));

# ------------------------------------------------------------------------------------------
# ## Sequence Minibatching
# Minibatching is a bit more complicated with sequences compared to simple classification
# problems, this section can be skipped on a first reading. In addition to the input and
# minibatch sizes, there is also the time dimension to consider. To keep things simple we
# will concatenate all sentences into one big sequence, then split this sequence into equal
# sized chunks. The input to the tagger will be size (B,T) where B is the minibatch size,
# and T is the chunk size. The input to the RNN layer will be size (X,B,T) where X is the
# embedding size.
# ------------------------------------------------------------------------------------------

BATCHSIZE = 64
SEQLENGTH = 32;

function seqbatch(x,y,B,T)
    N = length(x) ÷ B
    x = permutedims(reshape(x[1:N*B],N,B))
    y = permutedims(reshape(y[1:N*B],N,B))
    d = []; for i in 0:T:N-T
        push!(d, (x[:,i+1:i+T], y[:,i+1:i+T]))
    end
    return d
end
allw = vcat((x->x[1]).(data)...)
allt = vcat((x->x[2]).(data)...)
d = seqbatch(allw, allt, BATCHSIZE, SEQLENGTH);

# ------------------------------------------------------------------------------------------
# This may be a bit more clear if we look at an example minibatch:
# ------------------------------------------------------------------------------------------

(x,y) = first(d)
words[x]

# ------------------------------------------------------------------------------------------
# ## Embedding a minibatch
# Julia indexing allows us to get the embeddings for this minibatch in one go as an (X,B,T)
# array where X is the embedding size, B is the minibatch size, and T is the subsequence
# length.
# ------------------------------------------------------------------------------------------

embedlayer = Embed(length(words),128)
summary(embedlayer(x))

# ------------------------------------------------------------------------------------------
# ## Experiments
# ------------------------------------------------------------------------------------------

# shuffle and split minibatches into train and test portions
shuffle!(d)
dtst = d[1:10]
dtrn = d[11:end]
length.((dtrn,dtst))

# For running experiments we will use the Adam algorithm which typically converges faster than SGD.
function trainresults(file,maker,savemodel)
    if (print("Train from scratch? "); readline()[1]=='y')
        model = maker()
        takeevery(n,itr) = (x for (i,x) in enumerate(itr) if i % n == 1)
        results = ((nll(model,dtst), zeroone(model,dtst))
                   for x in takeevery(100, progress(adam(model,repeat(dtrn,5)))))
        results = reshape(collect(Float32,flatten(results)),(2,:))
        Knet.save(file,"model",(savemodel ? model : nothing),"results",results)
        Knet.gc() # To save gpu memory
    else
        isfile(file) || download("http://people.csail.mit.edu/deniz/models/tutorial/$file",file)
        model,results = Knet.load(file,"model","results")
    end
    println(minimum(results,dims=2))
    return model,results
end

VOCABSIZE = length(words)
EMBEDSIZE = 128
HIDDENSIZE = 128
OUTPUTSIZE = length(tags);

# 2.35e-01  100.00%┣┫ 2780/2780 [00:13/00:13, 216.36i/s] [0.295007; 0.0972656]
t0maker() = Tagger0(VOCABSIZE,EMBEDSIZE,HIDDENSIZE,OUTPUTSIZE)
(t0,r0) = trainresults("tagger113a.jld2",t0maker,false);

# 1.49e-01  100.00%┣┫ 2780/2780 [00:19/00:19, 142.58i/s] [0.21358; 0.0616211]
t1maker() = Tagger1(VOCABSIZE,EMBEDSIZE,HIDDENSIZE,OUTPUTSIZE)
(t1,r1) = trainresults("tagger113b.jld2",t1maker,false);

# 9.37e-02  100.00%┣┫ 2780/2780 [00:25/00:25, 109.77i/s] [0.156669; 0.044043]
t2maker() = Tagger2(VOCABSIZE,EMBEDSIZE,HIDDENSIZE,OUTPUTSIZE)
(t2,r2) = trainresults("tagger113c.jld2",t2maker,true);

using Plots; default(fmt=:png,ls=:auto,ymirror=true)

plot([r0[2,:], r1[2,:], r2[2,:]]; xlabel="x100 updates", ylabel="error",
    ylim=(0,0.15), yticks=0:0.01:0.15, labels=["MLP","RNN","biRNN"])

plot([r0[1,:], r1[1,:], r2[1,:]]; xlabel="x100 updates", ylabel="loss",
    ylim=(0,.5), yticks=0:0.1:.5, labels=["MLP","RNN","biRNN"])

# ------------------------------------------------------------------------------------------
# ## Playground
# Below, you can type and tag your own sentences:
# ------------------------------------------------------------------------------------------

wdict=Dict{String,UInt16}(); for (i,w) in enumerate(words); wdict[w]=i; end
unk = UInt16(length(words))
wid(w) = get(wdict,w,unk)
function tag(tagger,s::String)
    w = permutedims(split(s))
    t = tags[(x->x[1]).(argmax(Array(tagger(wid.(w))),dims=1))]
    vcat(w,t)
end

tag(t2,readline())


# Particularly useful or beautiful grammar of graphics invocations.

## Return a DataFrame with x, y column suitable for plotting a function.
#
# Args:
#  f: Function/Expression to be evaluated.
#  a: Lower bound.
#  b: Upper bound.
#  n: Number of points to evaluate the function at.
#
# Returns:
#  A data frame with "x" and "f(x)" columns.
#
function evalfunc(f::Function, a, b, n)
    xs = [x for x in a:(b - a)/n:b]
    df = DataFrame(xs, map(f, xs))
    colnames!(df, ["x", "f(x)"])
    df
end

evalfunc(f::Expr, a, b, n) = evalfunc(eval(:(x -> $f)), a, b, n)

# A convenience plot function for quickly plotting functions are expressions.
#
# Args:
#   fs: An array in which each object is either a single argument function or an
#       expression computing some value on x.
#   a: Lower bound on x.
#   b: Upper bound on x.
#   elements: One ore more grammar elements.
#
# Returns:
#   A plot objects.
#
function plot(fs::Array, a, b, elements::Element...)
    df = DataFrame()
    for (i, f) in enumerate(fs)
        df_i = evalfunc(f, a, b, 250)
        name = typeof(f) == Expr ? string(f) : @sprintf("f<sub>%d</sub>", i)
        df_i = cbind(df_i, [name for _ in 1:size(df_i)[1]])
        colnames!(df_i, ["x", "f(x)", "f"])
        df = rbind(df, df_i)
    end

    mapping = {:x => "x", :y => "f(x)"}
    if length(fs) > 1
        mapping[:color] = "f"
    end

    plot(df, mapping, Geom.line, elements...)
end

# Plot a single function.
function plot(f::Function, a, b, elements::Element...)
    plot([f], a, b, elements...)
end

# Plot a single expression.
function plot(f::Expr, a, b, elements::Element...)
    plot([f], a, b, elements...)
end

# Plot an expression from a to b.
macro plot(expr, a, b)
    quote
        plot(x -> $(expr), $(a), $(b))
    end
end

# Simple heatmap plots of matrices.
#
# Args:
#   M: A matrix.
#
# Returns:
#   A plot object.
#
function spy(M)
    is, js, values = findn_nzs(M)
    df = DataFrame({"i" => is, "j" => js, "value" => values})
    plot(df, x="j", y="i", color="value",
         Scale.color_gradient,
         Geom.rectbin, Stat.identity)
end


### A Pluto.jl notebook ###
# v0.14.6

using Markdown
using InteractiveUtils

# ╔═╡ 5f780c3c-27d7-492d-b7f3-76bf4d74f0b8
begin
    import Pkg
    Pkg.activate(mktempdir())
    Pkg.add([
        Pkg.PackageSpec(name="Plots", version="1"),
        Pkg.PackageSpec(name="PlutoUI", version="0.7"),
	    Pkg.PackageSpec(name="DifferentialEquations", version="6"),
	    Pkg.PackageSpec(name="Parameters", version="0.12"),
        Pkg.PackageSpec(name="LabelledArrays", version="1"),
        Pkg.PackageSpec(name="Setfield", version="0.7"),
    ])
    using Plots, PlutoUI, LinearAlgebra, DifferentialEquations, Parameters, LabelledArrays, Setfield

	Plots.gr(fmt=:png, lw=2)

	PlutoUI.TableOfContents()
end

# ╔═╡ eb59c8c0-bdcc-11eb-2d91-69018c56b6b6
md"""
# Fig 2.09 Numerical Simulation of a metabolic network
"""

# ╔═╡ 2a4dd196-427f-4077-8c98-ed8b6ac8ad3b
function model!(du, u, p, t)
	@unpack a, b, c, d = u
	v1 = 2.0a
	v2 = 2.5*a*b
	v3 = 3.0c
	v4 = 3.0d

	du.a = 3.0 - v1 - v2
	du.b = v1 - v2
	du.c = v2 - v3
	du.d = v2 - v4
	return du
end

# ╔═╡ 5b04efb9-71f5-44eb-bda6-ef454e506303
u0 = LVector(a=0.0, b=0.0, c=0.0, d=0.0)

# ╔═╡ 5c846441-ee6f-4c0b-a2b3-7e8d8b2e2cf8
tend = 10.0

# ╔═╡ 01aed17a-ea97-414e-b3cf-011d0dee8ede
sol = solve(ODEProblem(model!, u0, tend))

# ╔═╡ feb6ae90-53a9-485f-a088-2f868400574e
plot(sol, xlims=(0.0, 4.0), ylims=(0.0, 1.0),
     xlabel="Time (sec)", ylabel="Concentration (mM)", title="Figure 2.09",
     label=["A" "B" "C" "D"], legend=:bottomright)

# ╔═╡ 8fae9318-8099-41fa-a074-66bd778d37a1
md"""
## Appendix
Running environment and some auxillary functions.

**You can use [this helper tool](https://fonsp.com/article-test-3/pkghelper.html) to generate these commands!**
"""

# ╔═╡ a4d8bd30-d791-46ff-ad17-7737f8e5852a
begin
	# Convenience functions
    hill(x, k) = x / (x + k)
    hill(x, k, n) = hill(x^n, k^n)
    exprel(x) = ifelse(x≈zero(x), one(x), x / expm1(x))
end

# ╔═╡ Cell order:
# ╠═eb59c8c0-bdcc-11eb-2d91-69018c56b6b6
# ╠═2a4dd196-427f-4077-8c98-ed8b6ac8ad3b
# ╠═5b04efb9-71f5-44eb-bda6-ef454e506303
# ╠═5c846441-ee6f-4c0b-a2b3-7e8d8b2e2cf8
# ╠═01aed17a-ea97-414e-b3cf-011d0dee8ede
# ╠═feb6ae90-53a9-485f-a088-2f868400574e
# ╠═8fae9318-8099-41fa-a074-66bd778d37a1
# ╠═a4d8bd30-d791-46ff-ad17-7737f8e5852a
# ╠═5f780c3c-27d7-492d-b7f3-76bf4d74f0b8


function boundaries_to_sparse(boundary)
    n = length(boundary)
    I = Vector{Int}(undef, n)
    J = Vector{Int}(undef, n)
    V = Vector{Bool}(undef, n)
    for (idx, el) in enumerate(boundary)
        cell, face = el
        I[idx] = face
        J[idx] = cell
        V[idx] = true
    end
    return sparse(I, J, V)
end

"""
    generate_grid(celltype::Cell, nel::NTuple, [left::Vec, right::Vec)

Return a `Grid` for a rectangle in 1, 2 or 3 dimensions. `celltype` defined the type of cells,
e.g. `Triangle` or `Hexahedron`. `nel` is a tuple of the number of elements in each direction.
`left` and `right` are optional endpoints of the domain. Defaults to -1 and 1 in all directions.
"""
generate_grid

# Line
function generate_grid(::Type{Line}, nel::NTuple{1,Int}, left::Vec{1,T}=Vec{1}((-1.0,)), right::Vec{1,T}=Vec{1}((1.0,))) where {T}
    nel_x = nel[1]
    n_nodes = nel_x + 1

    # Generate nodes
    coords_x = collect(range(left[1], stop=right[1], length=n_nodes))
    nodes = Node{1,T}[]
    for i in 1:n_nodes
        push!(nodes, Node((coords_x[i],)))
    end

    # Generate cells

    cells = Line[]
    for i in 1:nel_x
        push!(cells, Line((i, i+1)))
    end

    # Cell faces
    boundary = Vector([(1, 1),
                       (nel_x, 2)])

    boundary_matrix = boundaries_to_sparse(boundary)

    # Cell face sets
    facesets = Dict("left"  => Set{Tuple{Int,Int}}([boundary[1]]),
                    "right" => Set{Tuple{Int,Int}}([boundary[2]]))
    return Grid(cells, nodes, facesets=facesets, boundary_matrix=boundary_matrix)
end

# QuadraticLine
function generate_grid(::Type{QuadraticLine}, nel::NTuple{1,Int}, left::Vec{1,T}=Vec{1}((-1.0,)), right::Vec{1,T}=Vec{1}((1.0,))) where {T}
    nel_x = nel[1]
    n_nodes = 2*nel_x + 1

    # Generate nodes
    coords_x = collect(range(left[1], stop=right[1], length=n_nodes))
    nodes = Node{1,T}[]
    for i in 1:n_nodes
        push!(nodes, Node((coords_x[i],)))
    end

    # Generate cells
    cells = QuadraticLine[]
    for i in 1:nel_x
        push!(cells, QuadraticLine((2*i-1, 2*i+1, 2*i)))
    end

    # Cell faces
    boundary = Tuple{Int,Int}[(1, 1),
                         (nel_x, 2)]

    boundary_matrix = boundaries_to_sparse(boundary)

    # Cell face sets
    facesets = Dict("left"  => Set{Tuple{Int,Int}}([boundary[1]]),
                    "right" => Set{Tuple{Int,Int}}([boundary[2]]))
    return Grid(cells, nodes, facesets=facesets, boundary_matrix=boundary_matrix)
end

function _generate_2d_nodes!(nodes, nx, ny, LL, LR, UR, UL)
      for i in 0:ny-1
        ratio_bounds = i / (ny-1)

        x0 = LL[1] * (1 - ratio_bounds) + ratio_bounds * UL[1]
        x1 = LR[1] * (1 - ratio_bounds) + ratio_bounds * UR[1]

        y0 = LL[2] * (1 - ratio_bounds) + ratio_bounds * UL[2]
        y1 = LR[2] * (1 - ratio_bounds) + ratio_bounds * UR[2]

        for j in 0:nx-1
            ratio = j / (nx-1)
            x = x0 * (1 - ratio) + ratio * x1
            y = y0 * (1 - ratio) + ratio * y1
            push!(nodes, Node((x, y)))
        end
    end
end

function generate_grid(C::Type{Cell{2,M,N}}, nel::NTuple{2,Int}, X::Vector{Vec{2,T}}) where {M,N,T}
    @assert length(X) == 4
    generate_grid(C, nel, X[1], X[2], X[3], X[4])
end

function generate_grid(C::Type{Cell{2,M,N}}, nel::NTuple{2,Int}, left::Vec{2,T}=Vec{2}((-1.0,-1.0)), right::Vec{2,T}=Vec{2}((1.0,1.0))) where {M,N,T}
    LL = left
    UR = right
    LR = Vec{2}((UR[1], LL[2]))
    UL = Vec{2}((LL[1], UR[2]))
    generate_grid(C, nel, LL, LR, UR, UL)
end

# Quadrilateral
function generate_grid(C::Type{Quadrilateral}, nel::NTuple{2,Int}, LL::Vec{2,T}, LR::Vec{2,T}, UR::Vec{2,T}, UL::Vec{2,T}) where {T}
    nel_x = nel[1]; nel_y = nel[2]; nel_tot = nel_x*nel_y
    n_nodes_x = nel_x + 1; n_nodes_y = nel_y + 1
    n_nodes = n_nodes_x * n_nodes_y

    # Generate nodes
    nodes = Node{2,T}[]
    _generate_2d_nodes!(nodes, n_nodes_x, n_nodes_y, LL, LR, UR, UL)

    # Generate cells
    node_array = reshape(collect(1:n_nodes), (n_nodes_x, n_nodes_y))
    cells = Quadrilateral[]
    for j in 1:nel_y, i in 1:nel_x
        push!(cells, Quadrilateral((node_array[i,j], node_array[i+1,j], node_array[i+1,j+1], node_array[i,j+1])))
    end

    # Cell faces
    cell_array = reshape(collect(1:nel_tot),(nel_x, nel_y))
    boundary = Tuple{Int,Int}[[(cl, 1) for cl in cell_array[:,1]];
                              [(cl, 2) for cl in cell_array[end,:]];
                              [(cl, 3) for cl in cell_array[:,end]];
                              [(cl, 4) for cl in cell_array[1,:]]]

    boundary_matrix = boundaries_to_sparse(boundary)

    # Cell face sets
    offset = 0
    facesets = Dict{String, Set{Tuple{Int,Int}}}()
    facesets["bottom"] = Set{Tuple{Int,Int}}(boundary[(1:length(cell_array[:,1]))   .+ offset]); offset += length(cell_array[:,1])
    facesets["right"]  = Set{Tuple{Int,Int}}(boundary[(1:length(cell_array[end,:])) .+ offset]); offset += length(cell_array[end,:])
    facesets["top"]    = Set{Tuple{Int,Int}}(boundary[(1:length(cell_array[:,end])) .+ offset]); offset += length(cell_array[:,end])
    facesets["left"]   = Set{Tuple{Int,Int}}(boundary[(1:length(cell_array[1,:]))   .+ offset]); offset += length(cell_array[1,:])

    return Grid(cells, nodes, facesets=facesets, boundary_matrix=boundary_matrix)
end

# QuadraticQuadrilateral
function generate_grid(::Type{QuadraticQuadrilateral}, nel::NTuple{2,Int}, LL::Vec{2,T}, LR::Vec{2,T}, UR::Vec{2,T}, UL::Vec{2,T}) where {T}
    nel_x = nel[1]; nel_y = nel[2]; nel_tot = nel_x*nel_y
    n_nodes_x = 2*nel_x + 1; n_nodes_y = 2*nel_y + 1
    n_nodes = n_nodes_x * n_nodes_y

    # Generate nodes
    nodes = Node{2,T}[]
    _generate_2d_nodes!(nodes, n_nodes_x, n_nodes_y, LL, LR, UR, UL)

    # Generate cells
    node_array = reshape(collect(1:n_nodes), (n_nodes_x, n_nodes_y))
    cells = QuadraticQuadrilateral[]
    for j in 1:nel_y, i in 1:nel_x
        push!(cells, QuadraticQuadrilateral((node_array[2*i-1,2*j-1],node_array[2*i+1,2*j-1],node_array[2*i+1,2*j+1],node_array[2*i-1,2*j+1],
                                             node_array[2*i,2*j-1],node_array[2*i+1,2*j],node_array[2*i,2*j+1],node_array[2*i-1,2*j],
                                             node_array[2*i,2*j])))
    end

    # Cell faces
    cell_array = reshape(collect(1:nel_tot),(nel_x, nel_y))
    boundary = Tuple{Int,Int}[[(cl, 1) for cl in cell_array[:,1]];
                              [(cl, 2) for cl in cell_array[end,:]];
                              [(cl, 3) for cl in cell_array[:,end]];
                              [(cl, 4) for cl in cell_array[1,:]]]

    boundary_matrix = boundaries_to_sparse(boundary)

    # Cell face sets
    offset = 0
    facesets = Dict{String, Set{Tuple{Int,Int}}}()
    facesets["bottom"] = Set{Tuple{Int,Int}}(boundary[(1:length(cell_array[:,1]))   .+ offset]); offset += length(cell_array[:,1])
    facesets["right"]  = Set{Tuple{Int,Int}}(boundary[(1:length(cell_array[end,:])) .+ offset]); offset += length(cell_array[end,:])
    facesets["top"]    = Set{Tuple{Int,Int}}(boundary[(1:length(cell_array[:,end])) .+ offset]); offset += length(cell_array[:,end])
    facesets["left"]   = Set{Tuple{Int,Int}}(boundary[(1:length(cell_array[1,:]))   .+ offset]); offset += length(cell_array[1,:])

    return Grid(cells, nodes, facesets=facesets, boundary_matrix=boundary_matrix)
end

# Hexahedron
function generate_grid(::Type{Hexahedron}, nel::NTuple{3,Int}, left::Vec{3,T}=Vec{3}((-1.0,-1.0,-1.0)), right::Vec{3,T}=Vec{3}((1.0,1.0,1.0))) where {T}
    nel_x = nel[1]; nel_y = nel[2]; nel_z = nel[3]; nel_tot = nel_x*nel_y*nel_z
    n_nodes_x = nel_x + 1; n_nodes_y = nel_y + 1; n_nodes_z = nel_z + 1
    n_nodes = n_nodes_x * n_nodes_y * n_nodes_z

    # Generate nodes
    coords_x = range(left[1], stop=right[1], length=n_nodes_x)
    coords_y = range(left[2], stop=right[2], length=n_nodes_y)
    coords_z = range(left[3], stop=right[3], length=n_nodes_z)
    nodes = Node{3,T}[]
    for k in 1:n_nodes_z, j in 1:n_nodes_y, i in 1:n_nodes_x
        push!(nodes, Node((coords_x[i], coords_y[j], coords_z[k])))
    end

    # Generate cells
    node_array = reshape(collect(1:n_nodes), (n_nodes_x, n_nodes_y, n_nodes_z))
    cells = Hexahedron[]
    for k in 1:nel_z, j in 1:nel_y, i in 1:nel_x
        push!(cells, Hexahedron((node_array[i,j,k], node_array[i+1,j,k], node_array[i+1,j+1,k], node_array[i,j+1,k],
                                 node_array[i,j,k+1], node_array[i+1,j,k+1], node_array[i+1,j+1,k+1], node_array[i,j+1,k+1])))
    end

    # Cell faces
    cell_array = reshape(collect(1:nel_tot),(nel_x, nel_y, nel_z))
    boundary = Tuple{Int,Int}[[(cl, 1) for cl in cell_array[:,:,1][:]];
                              [(cl, 2) for cl in cell_array[:,1,:][:]];
                              [(cl, 3) for cl in cell_array[end,:,:][:]];
                              [(cl, 4) for cl in cell_array[:,end,:][:]];
                              [(cl, 5) for cl in cell_array[1,:,:][:]];
                              [(cl, 6) for cl in cell_array[:,:,end][:]]]

    boundary_matrix = boundaries_to_sparse(boundary)

    # Cell face sets
    offset = 0
    facesets = Dict{String,Set{Tuple{Int,Int}}}()
    facesets["bottom"] = Set{Tuple{Int,Int}}(boundary[(1:length(cell_array[:,:,1][:]))   .+ offset]); offset += length(cell_array[:,:,1][:])
    facesets["front"]  = Set{Tuple{Int,Int}}(boundary[(1:length(cell_array[:,1,:][:]))   .+ offset]); offset += length(cell_array[:,1,:][:])
    facesets["right"]  = Set{Tuple{Int,Int}}(boundary[(1:length(cell_array[end,:,:][:])) .+ offset]); offset += length(cell_array[end,:,:][:])
    facesets["back"]   = Set{Tuple{Int,Int}}(boundary[(1:length(cell_array[:,end,:][:])) .+ offset]); offset += length(cell_array[:,end,:][:])
    facesets["left"]   = Set{Tuple{Int,Int}}(boundary[(1:length(cell_array[1,:,:][:]))   .+ offset]); offset += length(cell_array[1,:,:][:])
    facesets["top"]    = Set{Tuple{Int,Int}}(boundary[(1:length(cell_array[:,:,end][:])) .+ offset]); offset += length(cell_array[:,:,end][:])

    return Grid(cells, nodes, facesets=facesets, boundary_matrix=boundary_matrix)
end

# Triangle
function generate_grid(::Type{Triangle}, nel::NTuple{2,Int}, LL::Vec{2,T}, LR::Vec{2,T}, UR::Vec{2,T}, UL::Vec{2,T}) where {T}
    nel_x = nel[1]; nel_y = nel[2]; nel_tot = 2*nel_x*nel_y
    n_nodes_x = nel_x + 1; n_nodes_y = nel_y + 1
    n_nodes = n_nodes_x * n_nodes_y

    # Generate nodes
    nodes = Node{2,T}[]
    _generate_2d_nodes!(nodes, n_nodes_x, n_nodes_y, LL, LR, UR, UL)

    # Generate cells
    node_array = reshape(collect(1:n_nodes), (n_nodes_x, n_nodes_y))
    cells = Triangle[]
    for j in 1:nel_y, i in 1:nel_x
        push!(cells, Triangle((node_array[i,j], node_array[i+1,j], node_array[i,j+1]))) # ◺
        push!(cells, Triangle((node_array[i+1,j], node_array[i+1,j+1], node_array[i,j+1]))) # ◹
    end

    # Cell faces
    cell_array = reshape(collect(1:nel_tot),(2, nel_x, nel_y))
    boundary = Tuple{Int,Int}[[(cl, 1) for cl in cell_array[1,:,1]];
                               [(cl, 1) for cl in cell_array[2,end,:]];
                               [(cl, 2) for cl in cell_array[2,:,end]];
                               [(cl, 3) for cl in cell_array[1,1,:]]]

    boundary_matrix = boundaries_to_sparse(boundary)

    # Cell face sets
    offset = 0
    facesets = Dict{String,Set{Tuple{Int,Int}}}()
    facesets["bottom"] = Set{Tuple{Int,Int}}(boundary[(1:length(cell_array[1,:,1]))   .+ offset]); offset += length(cell_array[1,:,1])
    facesets["right"]  = Set{Tuple{Int,Int}}(boundary[(1:length(cell_array[2,end,:])) .+ offset]); offset += length(cell_array[2,end,:])
    facesets["top"]    = Set{Tuple{Int,Int}}(boundary[(1:length(cell_array[2,:,end])) .+ offset]); offset += length(cell_array[2,:,end])
    facesets["left"]   = Set{Tuple{Int,Int}}(boundary[(1:length(cell_array[1,1,:]))   .+ offset]); offset += length(cell_array[1,1,:])

    return Grid(cells, nodes, facesets=facesets, boundary_matrix=boundary_matrix)
end

# QuadraticTriangle
function generate_grid(::Type{QuadraticTriangle}, nel::NTuple{2,Int}, LL::Vec{2,T}, LR::Vec{2,T}, UR::Vec{2,T}, UL::Vec{2,T}) where {T}
    nel_x = nel[1]; nel_y = nel[2]; nel_tot = 2*nel_x*nel_y
    n_nodes_x = 2*nel_x + 1; n_nodes_y = 2*nel_y + 1
    n_nodes = n_nodes_x * n_nodes_y

    # Generate nodes
    nodes = Node{2,T}[]
    _generate_2d_nodes!(nodes, n_nodes_x, n_nodes_y, LL, LR, UR, UL)

    # Generate cells
    node_array = reshape(collect(1:n_nodes), (n_nodes_x, n_nodes_y))
    cells = QuadraticTriangle[]
    for j in 1:nel_y, i in 1:nel_x
        push!(cells, QuadraticTriangle((node_array[2*i-1,2*j-1], node_array[2*i+1,2*j-1], node_array[2*i-1,2*j+1],
                                        node_array[2*i,2*j-1], node_array[2*i,2*j], node_array[2*i-1,2*j]))) # ◺
        push!(cells, QuadraticTriangle((node_array[2*i+1,2*j-1], node_array[2*i+1,2*j+1], node_array[2*i-1,2*j+1],
                                        node_array[2*i+1,2*j], node_array[2*i,2*j+1], node_array[2*i,2*j]))) # ◹
    end

    # Cell faces
    cell_array = reshape(collect(1:nel_tot),(2, nel_x, nel_y))
    boundary = Tuple{Int,Int}[[(cl, 1) for cl in cell_array[1,:,1]];
                              [(cl, 1) for cl in cell_array[2,end,:]];
                              [(cl, 2) for cl in cell_array[2,:,end]];
                              [(cl, 3) for cl in cell_array[1,1,:]]]

    boundary_matrix = boundaries_to_sparse(boundary)

    # Cell face sets
    offset = 0
    facesets = Dict{String,Set{Tuple{Int,Int}}}()
    facesets["bottom"] = Set{Tuple{Int,Int}}(boundary[(1:length(cell_array[1,:,1]))   .+ offset]); offset += length(cell_array[1,:,1])
    facesets["right"]  = Set{Tuple{Int,Int}}(boundary[(1:length(cell_array[2,end,:])) .+ offset]); offset += length(cell_array[2,end,:])
    facesets["top"]    = Set{Tuple{Int,Int}}(boundary[(1:length(cell_array[2,:,end])) .+ offset]); offset += length(cell_array[2,:,end])
    facesets["left"]   = Set{Tuple{Int,Int}}(boundary[(1:length(cell_array[1,1,:]))   .+ offset]); offset += length(cell_array[1,1,:])

    return Grid(cells, nodes, facesets=facesets, boundary_matrix=boundary_matrix)
end

# Tetrahedron
function generate_grid(::Type{Tetrahedron}, cells_per_dim::NTuple{3,Int}, left::Vec{3,T}=Vec{3}((-1.0,-1.0,-1.0)), right::Vec{3,T}=Vec{3}((1.0,1.0,1.0))) where {T}
    nodes_per_dim = cells_per_dim .+ 1

    cells_per_cube = 6
    total_nodes = prod(nodes_per_dim)
    total_elements = cells_per_cube * prod(cells_per_dim)

    n_nodes_x, n_nodes_y, n_nodes_z = nodes_per_dim
    n_cells_x, n_cells_y, n_cells_z = cells_per_dim

    # Generate nodes
    coords_x = range(left[1], stop=right[1], length=n_nodes_x)
    coords_y = range(left[2], stop=right[2], length=n_nodes_y)
    coords_z = range(left[3], stop=right[3], length=n_nodes_z)
    numbering = reshape(1:total_nodes, nodes_per_dim)

    # Pre-allocate the nodes & cells
    nodes = Vector{Node{3,T}}(undef, total_nodes)
    cells = Vector{Tetrahedron}(undef, total_elements)

    # Generate nodes
    node_idx = 1
    @inbounds for k in 1:n_nodes_z, j in 1:n_nodes_y, i in 1:n_nodes_x
        nodes[node_idx] = Node((coords_x[i], coords_y[j], coords_z[k]))
        node_idx += 1
    end

    # Generate cells, case 1 from: http://www.baumanneduard.ch/Splitting%20a%20cube%20in%20tetrahedras2.htm
    # cube = (1, 2, 3, 4, 5, 6, 7, 8)
    # left = (1, 4, 5, 8), right = (2, 3, 6, 7)
    # front = (1, 2, 5, 6), back = (3, 4, 7, 8)
    # bottom = (1, 2, 3, 4), top = (5, 6, 7, 8)
    cell_idx = 0
    @inbounds for k in 1:n_cells_z, j in 1:n_cells_y, i in 1:n_cells_x
        cell = (
            numbering[i  , j  , k],
            numbering[i+1, j  , k],
            numbering[i+1, j+1, k],
            numbering[i  , j+1, k],
            numbering[i  , j  , k+1],
            numbering[i+1, j  , k+1],
            numbering[i+1, j+1, k+1],
            numbering[i  , j+1, k+1]
        )

        cells[cell_idx + 1] = Tetrahedron((cell[1], cell[2], cell[4], cell[8]))
        cells[cell_idx + 2] = Tetrahedron((cell[1], cell[5], cell[2], cell[8]))
        cells[cell_idx + 3] = Tetrahedron((cell[2], cell[3], cell[4], cell[8]))
        cells[cell_idx + 4] = Tetrahedron((cell[2], cell[7], cell[3], cell[8]))
        cells[cell_idx + 5] = Tetrahedron((cell[2], cell[5], cell[6], cell[8]))
        cells[cell_idx + 6] = Tetrahedron((cell[2], cell[6], cell[7], cell[8]))

        cell_idx += cells_per_cube
    end

    # Order the cells as c_nxyz[n, x, y, z] such that we can look up boundary cells
    c_nxyz = reshape(1:total_elements, (cells_per_cube, cells_per_dim...))

    @views le = [map(x -> (x,4), c_nxyz[1, 1, :, :][:])   ; map(x -> (x,2), c_nxyz[2, 1, :, :][:])]
    @views ri = [map(x -> (x,1), c_nxyz[4, end, :, :][:]) ; map(x -> (x,1), c_nxyz[6, end, :, :][:])]
    @views fr = [map(x -> (x,1), c_nxyz[2, :, 1, :][:])   ; map(x -> (x,1), c_nxyz[5, :, 1, :][:])]
    @views ba = [map(x -> (x,3), c_nxyz[3, :, end, :][:]) ; map(x -> (x,3), c_nxyz[4, :, end, :][:])]
    @views bo = [map(x -> (x,1), c_nxyz[1, :, :, 1][:])   ; map(x -> (x,1), c_nxyz[3, :, :, 1][:])]
    @views to = [map(x -> (x,3), c_nxyz[5, :, :, end][:]) ; map(x -> (x,3), c_nxyz[6, :, :, end][:])]

    boundary_matrix = boundaries_to_sparse([le; ri; bo; to; fr; ba])

    facesets = Dict(
        "left" => Set(le),
        "right" => Set(ri),
        "front" => Set(fr),
        "back" => Set(ba),
        "bottom" => Set(bo),
        "top" => Set(to),
    )

    return Grid(cells, nodes, facesets=facesets, boundary_matrix=boundary_matrix)
end


#=
Copyright (c) 2015, Intel Corporation
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice,
  this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
=#

using ParallelAccelerator
using DocOpt

#ParallelAccelerator.set_debug_level(3)
#ParallelAccelerator.DomainIR.set_debug_level(3)
#ParallelAccelerator.CGen.set_debug_level(3)

@acc function calcPi(n)
    x = rand(n) * 2.0 - 1.0
    y = rand(n) * 2.0 - 1.0
    return 4.0*sum(x.^2 + y.^2 .< 1.0)/n
end

function main()
    doc = """pi.jl

Estimate Pi using Monte Carlo method.

Usage:
  pi.jl -h | --help
  pi.jl [--points=<points>]

Options:
  -h --help          Show this screen.
  --points=<points>  Specify number of generated random points [default: 10000000].
"""
    arguments = docopt(doc)

    points = parse(Int, arguments["--points"])

    println("points = ", points)

    tic()
    calcPi(100)
    println("SELFPRIMED ", toq())

    tic()
    pi_val = calcPi(points)
    time = toq()
    println("pi = ", pi_val)
    println("SELFTIMED ", time)

end

main()


using BenchmarkTools

# see this section of docs https://docs.julialang.org/en/v1/base/arrays/#Broadcast-and-vectorization-1

function G_single(t::T, params::Vector{T}) where T<:Real

   G₀, G₁, η₁, G₂, η₂ = params

   G = G₀ + G₁*exp(-t*G₁/η₁) + G₂*exp(-t*G₂/η₂)

end

function G_vec(t::Vector{T}, params::Vector{T}) where T<:Real

   G₀, G₁, η₁, G₂, η₂ = params

   G = G₀ .+ G₁*exp.(-t*G₁/η₁) .+ G₂*exp.(-t*G₂/η₂)

end

function G_loop(t::Vector{T}, params::Vector{T}) where T<:Real

    out = Array{T}(undef, length(t))

    @inbounds for (i,ti) in enumerate(t)
        out[i] = G_single(ti, params)
    end

    out
end

t = collect(0.0:0.1:10000)
params = [1.0, 1.0, 1.0, 1.0, 1.0]

# always worst by far, ignore for now
# println("test vectorised version of function")
# @btime G_vec($t, $params)
# println("\n")

# println("test broadcasting using . notation")
# @btime G_single.($t, $(params,))
# println("\n")

# println("test broadcast function explicitly (should be the same as . notation)")
# @btime broadcast($G_single, $t, $(params,))
# println("\n")

# println("test using array comprehension")
# @btime [G_single(ti, $params) for ti in $t]
# println("\n")

println("test using map")
@btime map(G_single, t, (params,))
println("\n")

# println("test using for loop")
# @btime G_loop($t, $params)
# println("\n")

println("Benchmarks complete")


#=Copyright (c) 2017 Gabriel Goh

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.=#

srand(1)

n = 2
U = [1 -1;1 1]/sqrt(2); L = [1 0; 0 2];
A = U*L*inv(U)
b = -ones(n)

β = 0.4
α = 1/max(eig(A)[1]...)

function runmomentum(A,b,k)
  z = zeros(length(b))
  w = zeros(length(b))
  for i = 1:k
    z = β*z + (A*w - b)
    w = w - α*z
  end
  return (z,w)
end

function geosumgen(R)
  Λ, U = eig(R)
  (b,k) -> begin;
    Λsum = (1./(1 - Λ)).*(1 - Λ.^k);
    real(U*(Λsum.*(U\b)));
  end
end

function runmomentumcheat(A,b,k)
  Λ, U = eig(A)
  Rmat(i) = [    β        Λ[i]  ;
              -α*β  (1- α*Λ[i]) ]
  S = inv([ 1  0 ;
            α  1 ])
  c = -U'b
  fcoll = [geosumgen(Rmat(i)) for i = 1:length(b)]
  iters = [fcoll[i](S*[c[i];0],k) for i = 1:length(b)]
  catiter = cat(2,iters...)
  return (U*catiter[1,:], U*catiter[2,:])
end

(a1,b1) = runmomentum(A,b,4)
(a2,b2) = runmomentumcheat(A,b,4)

norm(a1)


#=Copyright (c) 2017 Gabriel Goh

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.=#

using Plots

function getiterk(Q,b,α)
  Σ,U = eig(Q)
  c = U'b
  lambdak(k,λ) = (1 - (1 - α*λ).^k)./λ
  return k -> U*(lambdak(k,Σ).*c)
end

Box = zeros(16,16)
Cavity = ones(16,16)
Cavity[8:9,:] = 0
R = [Box Cavity Box Cavity Box Cavity Box]
A = ones(size(R,1)+2, size(R,2) + 2)
A[2:end-1,2:end-1] = R

n = size(A,1) - 2
m = size(A,2) - 2

Ind = zeros(Integer, size(A))
IndInv = Tuple{Int64,Int64}[]
count = 1
for i = 1:size(A,1), j = 1:size(A,2)
  if A[i,j] == 0
    Ind[i,j] = count
    push!(IndInv, (i,j))
    count = count + 1
  end
end

fp = open("Uval.json","w")
JSON.print(fp, convert(Array{Float16,2},U))
close(fp)

fp = open("Sigma.json","w")
JSON.print(fp, Σ)
close(fp)
# ==================================================
# Generate Laplacian
# ==================================================

Q = zeros(length(IndInv), length(IndInv))
for i = 2:(size(A,1)-1), j = 2:(size(A,2)-1)
  k = Ind[i,j]
  if A[i,j] == 0
    A[i,j+1] == 0 ? Q[Ind[i,j+1],k] = 1 : nothing
    A[i+1,j] == 0 ? Q[Ind[i+1,j],k] = 1 : nothing
    A[i,j-1] == 0 ? Q[Ind[i,j-1],k] = 1 : nothing
    A[i-1,j] == 0 ? Q[Ind[i-1,j],k] = 1 : nothing
  end
end

Q = (Q + Q')/2
L = diagm(sum(Q,1)[:]) - Q
L[449,449] = L[449,449] + 0.1
L[561,561] = L[561,561] + 0.1

Σ,U = eig(L)

function arrange(x)
  A = zeros(n,m)
  for ind = 1:length(x)
    i,j = IndInv[ind]
    A[i-1,j-1] = x[ind]
  end
  return A
end

plot(size = (880,140)); heatmap!(arrange(U[:,10]), map = "seismic")

imshow(arrange(U[:,100]), interpolation = "None", cmap = "spectral"); show()

pyplot(leg=false, ticks=nothing)

@gif for i in 2:500
  plot(size = (880,140)); heatmap!(reshape(U[:,224*3+i],n,m))
end

e1 = zeros(length(IndInv))
e1[449] = 1
e1[561] = 1

α = 1/max(Σ...)

iterk = getiterk(L,e1,α)

imshow(arrange(iterk(10000)))
show()

# α = 1/max(eig(L)[1]...)
# iters = 5000
# X = zeros(n*m,iters)
# x = 10*zeros(n*m)
# for i = 1:iters
#   X[:,i] = x
#   for i = 1:100
#     x = x - (1*α)*(L*x - e1)
#   end
#   println(norm(L*x - e1))
# end

# plot(size = (880,140)); heatmap!(reshape(X[:,100],n,m))
# pyplot(leg=false, ticks=nothing)

# @gif for i in 10:10:2000
#   println(i)
#   xi = reshape(X[:,i],n,m)
#   plot(size = (880,140)); heatmap!(xi)
# end

# lambdak(k,λ) = α*(1 - (1 - α*λ).^k)./(α*λ)
# c = U'e1

# @gif for i i guein 10:500:300000
#   println(i)
#   z = U*(lambdak(i,Σ).*(c)
#   plot(size = (880,140)); heatmap!(reshape(z, n, m))
# end

# A = randn(10,10); A = A*A'

# b = rand(10)
# x = zeros(10)
# for i = 1:1000
#   x = x - 0.01*(A*x - b)
# end
# x - getiterk(A,b,0.01)(1000)


using Random

Random.seed!(1974)
println("Seed 1974: ",rand(),"\t", rand(), "\t", rand())
Random.seed!(1975)
println("Seed 1975: ",rand(),"\t", rand(), "\t", rand())
Random.seed!(1974)
println("Seed 1974: ",rand(),"\t", rand(), "\t", rand())


using OrdinaryDiffEq, DiffEqSensitivity

function growth(du, u, p, t)
    @. du = p * u * (1 - u)
end
u0 = [0.1]
tspan = (0.0, 2.0)
prob = ODEProblem(growth, u0, tspan, [1.0])
sol = solve(prob, Tsit5(), reltol = 1e-8, abstol = 1e-8)

savetimes = [0.0, 1.0, 1.9]

function f(a)
    _prob = remake(prob,p=[a[1]],saveat=savetimes)
    predicted = solve(_prob, Tsit5(), sensealg=InterpolatingAdjoint(), abstol=1e-12, reltol=1e-12)
    sum(predicted[end])
end

function f2(a)
    _prob = remake(prob,p=[a[1]],saveat=savetimes)
    predicted = solve(_prob, Tsit5(), sensealg=ForwardDiffSensitivity(), abstol=1e-12, reltol=1e-12)
    sum(predicted[end])
end

using Zygote
a = ones(3)
@test Zygote.gradient(f,a)[1][1] ≈ Zygote.gradient(f2,a)[1][1]
@test Zygote.gradient(f,a)[1][2] == Zygote.gradient(f2,a)[1][2] == 0
@test Zygote.gradient(f,a)[1][3] == Zygote.gradient(f2,a)[1][3] == 0


#    Copyright (c) 2021 J.A. Duffek
#    Copyright (c) 2000 D.M. Spink
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, see <http://www.gnu.org/licenses/>.

using Plots;

"""
    demodercrv()

Demonstrates the construction of a general curve and determine of the
derivative.

# Examples:
```julia
julia> demodercrv()
```
"""
function demodercrv()
# make and draw nurbs test curve
crv = nrbtestcrv();
nrbplot(crv,48,
        title = "First derivatives along a test curve.",
        lw =1.0,
        c = :green,
        framestyle=:box,
        legend = false);

npts = 9;
tt = collect(range(0.0,1.0,length=npts));

# first derivative
dcrv = nrbderiv(crv);
p1, dp = nrbdeval(crv,dcrv,tt);

p2 = vecnorm_toolbox(dp);

plot!(p1[1,:],p1[2,:],
      m = (3, :transparent, stroke(1, :red)),
      st = :scatter,
      legend = false);
plot!(p1[1,:],p1[2,:],
      st=:quiver,
      quiver=(p2[1,:],p2[2,:]),
      c=:red);
end # demodercrv


module Models

using ImageBase
using ImageBase.ImageCore.MappedArrays: of_eltype
using ImageBase.FiniteDiff

# Introduced in ColorVectorSpace v0.9.3
# https://github.com/JuliaGraphics/ColorVectorSpace.jl/pull/172
using ImageBase.ImageCore.ColorVectorSpace.Future: abs2

"""
This submodule provides predefined image-related models and its solvers that can be reused
by many image processing tasks.

- solve the Rudin Osher Fatemi (ROF) model using the primal-dual method: [`solve_ROF_PD`](@ref) and [`solve_ROF_PD!`](@ref)
"""
Models

export solve_ROF_PD, solve_ROF_PD!

##### implementation details

"""
    solve_ROF_PD([T], img::AbstractArray, λ; kwargs...)

Return a smoothed version of `img`, using Rudin-Osher-Fatemi (ROF) filtering, more commonly
known as Total Variation (TV) denoising or TV regularization. This algorithm is based on the
primal-dual method.

This function applies to generic N-dimensional colorant array and is also CUDA-compatible.
See also [`solve_ROF_PD!`](@ref) for the in-place version.

# Arguments

- `T`: the output element type. By default it is `float32(eltype(img))`.
- `img`: the input image, usually a noisy image.
- `λ`: the regularization coefficient. Larger `λ` results in more smoothing.

# Parameters

- `num_iters::Int`: The number of iterations before stopping.

# Examples

```julia
using ImageFiltering
using ImageFiltering.Models: solve_ROF_PD
using ImageQualityIndexes
using TestImages

img_ori = float.(testimage("cameraman"))
img_noisy = img_ori .+ 0.1 .* randn(size(img_ori))
assess_psnr(img_noisy, img_ori) # ~20 dB

img_smoothed = solve_ROF_PD(img_noisy, 0.015, 50)
assess_psnr(img_smoothed, img_ori) # ~27 dB

# larger λ produces over-smoothed result
img_smoothed = solve_ROF_PD(img_noisy, 5, 50)
assess_psnr(img_smoothed, img_ori) # ~21 dB
```

# Extended help

Mathematically, this function solves the following ROF model using the primal-dual method:

```math
\\min_u \\lVert u - g \\rVert^2 + \\lambda\\lvert\\nabla u\\rvert
```

# References

- [1] Chambolle, A. (2004). "An algorithm for total variation minimization and applications". _Journal of Mathematical Imaging and Vision_. 20: 89–97
- [2] [Wikipedia: Total Variation Denoising](https://en.wikipedia.org/wiki/Total_variation_denoising)
"""
solve_ROF_PD(img::AbstractArray{T}, args...) where T = solve_ROF_PD(float32(T), img, args...)
function solve_ROF_PD(::Type{T}, img::AbstractArray, args...) where T
    u = similar(img, T)
    buffer = preallocate_solve_ROF_PD(T, img)
    solve_ROF_PD!(u, buffer, img, args...)
end

# non-exported helper
preallocate_solve_ROF_PD(img::AbstractArray{T}) where T = preallocate_solve_ROF_PD(float32(T), img)
function preallocate_solve_ROF_PD(::Type{T}, img) where T
    div_p = similar(img, T)
    p = ntuple(i->similar(img, T), ndims(img))
    ∇u = ntuple(i->similar(img, T), ndims(img))
    ∇u_mag = similar(img, eltype(T))
    return div_p, p, ∇u, ∇u_mag
end

"""
    solve_ROF_PD!(out, buffer, img, λ, num_iters)

The in-place version of [`solve_ROF_PD`](@ref).

It is not uncommon to use ROF solver in a higher-level loop, in which case it makes sense to
preallocate the output and intermediate arrays to make it faster.

!!! note "Buffer"
    The content and meaning of `buffer` might change without any notice if the internal
    implementation is changed. Use `preallocate_solve_ROF_PD` helper function to avoid
    potential changes.

# Examples

```julia
using ImageFiltering.Models: preallocate_solve_ROF_PD

out = similar(img)
buffer = preallocate_solve_ROF_PD(img)
solve_ROF_PD!(out, buffer, img, 0.2, 30)
```

"""
function solve_ROF_PD!(
        out::AbstractArray{T},
        buffer::Tuple,
        img::AbstractArray,
        λ::Real,
        num_iters::Integer) where T
    # seperate a stub method to reduce latency
    FT = float32(T)
    if FT == T
        solve_ROF_PD!(out, buffer, img, Float32(λ), Int(num_iters))
    else
        solve_ROF_PD!(out, buffer, FT.(img), Float32(λ), Int(num_iters))
    end
end
function solve_ROF_PD!(
        out::AbstractArray,
        (div_p, p, ∇u, ∇u_mag)::Tuple,
        img::AbstractArray,
        λ::Float32,
        num_iters::Int)
    # Total Variation regularized image denoising using the primal dual algorithm
    # Implement according to reference [1]
    τ = 1//4 # see 2nd remark after proof of Theorem 3.1.

    # use the same symbol in the paper
    u, g = out, img

    fgradient!(p, g)
    # This iterates Eq. (9) of [1]
    # TODO(johnnychen94): set better stop criterion
    for _ in 1:num_iters
        fdiv!(div_p, p)
        # multiply term inside ∇ by -λ. Thm. 3.1 relates this to `u` via Eq. 7.
        @. u = g - λ*div_p
        fgradient!(∇u, u)
        _l2norm_vec!(∇u_mag, ∇u) # |∇(g - λdiv p)|
        # Eq. (9): update p
        for i in 1:length(p)
            @. p[i] = (p[i] - (τ/λ)*∇u[i])/(1 + (τ/λ) * ∇u_mag)
        end
    end
    return u
end

function _l2norm_vec!(out, Vs::Tuple)
    all(v->axes(out) == axes(v), Vs) || throw(ArgumentError("All axes of input data should be the same."))
    @. out = abs2(Vs[1])
    for v in Vs[2:end]
        @. out += abs2(v)
    end
    @. out = sqrt(out)
    return out
end

end # module


"""
    zero_rate(curve, maturity)

Returns the *zero-coupon* rate to `maturity`.
"""
function zero_rate end

"""
    forward_rate(curve, forward_date, maturity)

Returns the *forward rate* from a future date
`forward_date` to `maturity`.
"""
function forward_rate end

"""
    discountfactor(curve, maturity)
    discountfactor(curve, forward_date, maturity)

Returns the discount factor to `maturity`.
Or a forward discount factor from `forward_date` to `maturity`.
"""
function discountfactor end


module FieldArithmetic

export Ring, RingElem, CRing, CRingElem, Field, FieldElem, Zmod, ZmodElem, PolyRing, PolyRingElem,
QuotientRing, QuotientRingElem, Fpsquare, order, squareroot, cuberoot, cuberootOfOne, allcuberoots,
allElements

##########################################
#         For overwriting
##########################################

import Base: +,-,*,/,^,==,inv,show,zeros,gcd,gcdx,iszero,isone,zero,one
import Primes: isprime

##########################################
# Abstract types for defining domains
##########################################

abstract type Ring end

abstract type RingElem end

abstract type CRing <: Ring end

abstract type CRingElem <: RingElem end

##########################################
#        Generic helper functions
##########################################

function sameRing(x::RingElem, y::RingElem)
    return x.ring == y.ring
end

function zeros(R::T, len::Integer) where T <: Ring
    return [zero(R) for i in 1:len]
end

##########################################
#
#         Ring of Integers mod n
#
##########################################

struct Zmod{T <: Integer} <: CRing
    n::T
end

struct ZmodElem{T <: Integer} <: CRingElem
    d::T
    ring::Zmod{T}
end

# Ring-Constructor
function Zmod(n::T) where T <: Integer
    return Zmod{T}(n)
end

# Element-Constructor
function (R::Zmod{T})(a::Integer) where T <: Integer
    a = T(a)
    n = R.n
    d = a%n
    if d < 0
        d += n
    end
    return ZmodElem{T}(d, R)
end

###### Printing ######
function show(io::IO, R::Zmod{T}) where T <: Integer
    print(io, "Z_$(R.n)")
end

function show(io::IO, x::ZmodElem{T}) where T <: Integer
    print(io, "$(x.d)")
end

###### Helper functions ######

function zero(R::Zmod{T}) where T <: Integer
    return R(T(0))
end

function one(R::Zmod{T}) where T <: Integer
    return R(T(1))
end

function iszero(x::ZmodElem{T}) where T <: Integer
    return x.d == 0
end

function isone(x::ZmodElem{T}) where T <: Integer
    return x.d == 1
end

function order(R::Zmod{T}) where T <: Integer
    return R.n
end

function allElements(R::Zmod{T}) where T <: Integer
    return [R(i) for i in 0:R.n-1]
end

###### Basic operators #######
function -(x::ZmodElem{T}) where T <: Integer
    if x.d == 0
        return x
    else
        R = x.ring
        return R(R.n - x.d)
    end
end

function +(x::ZmodElem{T}, y::ZmodElem{T}) where T <: Integer
    if !sameRing(x, y)
        error("Cannot add elements of different rings")
    end
    z = x.d + y.d
    R = x.ring
    return R(z)
end

function -(x::ZmodElem{T}, y::ZmodElem{T}) where T <: Integer
    return x+(-y)
end

function *(x::ZmodElem{T}, y::ZmodElem{T}) where T <: Integer
    if !sameRing(x, y)
        error("Cannot multiply elements of different rings")
    end
    z = x.d * y.d
    R = x.ring
    return R(z)
end

function *(x::Integer, y::ZmodElem{T}) where T <: Integer
    return y.ring(x)*y
end

function *(x::ZmodElem{T}, y::Integer) where T <: Integer
    return x.ring(y)*x
end

function inv(x::ZmodElem{T}) where T <: Integer
    R = x.ring
    g, a, b = gcdx(x.d, R.n)
    g != 1 && error("Element $x is not invertible in $(x.ring)")
    return R(a)
end

#exponensiation uses the simple square and multiply algorithm
function ^(x::ZmodElem{T}, y::Integer) where T <: Integer
    if x.d == 0 || y == 1
        return x
    end
    R = x.ring
    if y == 0
        return R(T(1))
    end
    if y < 0
        x = inv(x)
        y = -y
    end
    # Should include some reduction of y modulo order if order is known
    bit = T(1) << (ndigits(y, base = 2) - 1)
    z = x
    bit >>= 1
    while bit != 0
        z = z*z
        if (bit & y) != 0
            z *= x
        end
        bit >>=1
    end
    return z
end

function ==(x::ZmodElem{T}, y::ZmodElem{T}) where T <: Integer
    return sameRing(x, y) && (x.d == y.d)
end

function ==(R1::Zmod{T}, R2::Zmod{T}) where T <: Integer
    return R1.n == R2.n
end

##########################################
#
#         Polynomial Ring
#
##########################################

struct PolyRing{T <: CRing} <: CRing
    parent::T
    variable::String
end

struct PolyRingElem{T <: CRingElem, S <: CRing} <: CRingElem
    d::Array{T}
    ring::PolyRing{S}
end

# Ring-Constructor
function PolyRing(R::T,var::String) where T <: CRing
    return PolyRing{T}(R,var)
end

function PolyRing(R::T) where T <: CRing
    return PolyRing{T}(R,"X")
end

# Element-Constructor
function (R::PolyRing{S})(a::Array{T}) where {T <: CRingElem, S <: CRing}
    while length(a) > 0
        if !(iszero(a[length(a)]))
            return PolyRingElem{T,S}(a, R)
        end
        pop!(a)
    end
    return PolyRingElem{T,S}([zero(R.parent)], R)
end

function (R::PolyRing{Zmod{T}})(a::Array{S}) where {T <: Integer, S <: Integer}
    Z = R.parent
    a = [Z(i) for i in a]
    return R(a)
end

###### Printing ######
function show(io::IO, R::PolyRing{T}) where T <: CRing
    print(io, "$(R.parent)[$(R.variable)]")
end

function show(io::IO, x::PolyRingElem{T,S}) where {T <: CRingElem, S <: CRing}
    s = "$(x.d[1])"
    l = length(x.d)
    R = x.ring
    if l > 1
        s *= " + $(x.d[2])*$(R.variable)"
        if l > 2
            for i in 3:l
                s *= " + $(x.d[i])*$(R.variable)^$(i-1)"
            end
        end
    end
    print(io, s)
end

###### Useful helper functions #######
function zero(R::PolyRing{T}) where T <: CRing
    return R([zero(R.parent)])
end

function one(R::PolyRing{T}) where T <: CRing
    return R([one(R.parent)])
end

function iszero(a::PolyRingElem{T,S}) where {T <: CRingElem, S <: CRing}
    return a==zero(a.ring)
end

function isone(a::PolyRingElem{T,S}) where {T <: CRingElem, S <: CRing}
    return a==one(a.ring)
end

function degree(a::PolyRingElem{T,S}) where {T <: CRingElem, S <: CRing}
    if iszero(a)
        return -1
    else
        return length(a.d)-1
    end
end

function LC(a::PolyRingElem{T,S}) where {T <: CRingElem, S <: CRing}
    return a.d[length(a.d)]
end

function monic(a::PolyRingElem{T,S}) where {T <: CRingElem, S <: CRing}
    x = inv(LC(a))
    return a*x
end

###### Basic operators #######
function -(a::PolyRingElem{T,S}) where {T <: CRingElem, S <: CRing}
    d = deepcopy(a.d)
    l = length(d)
    R = a.ring
    z = zeros(R.parent, l)
    for i in 1:l
        z[i] = -d[i]
    end
    return R(z)
end

function +(a::PolyRingElem{T,S}, b::PolyRingElem{T,S}) where {T <: CRingElem, S <: CRing}
    if !sameRing(a, b)
        error("Cannot add elements from different rings")
    end
    R = a.ring
    d1 = deepcopy(a.d)
    d2 = deepcopy(b.d)
    lena = length(d1)
    lenb = length(d2)
    lenz = max(lena,lenb)
    lenmin = min(lena,lenb)
    z = zeros(R.parent, lenz)
    for i in 1:lenmin
        z[i] = d1[i] + d2[i]
    end
    if lena > lenmin
        for i in lenmin+1:lena
            z[i] = d1[i]
        end
    end
    if lenb > lenmin
        for i in lenmin+1:lenb
            z[i] = d2[i]
        end
    end
    return R(z)
end

function -(a::PolyRingElem{T,S}, b::PolyRingElem{T,S}) where {T <: CRingElem, S <: CRing}
    return a + (-b)
end

#Might be subject to more effective implementations later...
function *(a::PolyRingElem{T,S}, b::PolyRingElem{T,S}) where {T <: CRingElem, S <: CRing}
    if !sameRing(a, b)
        error("Cannot multiply elements from different rings")
    end
    R = a.ring
    d1 = deepcopy(a.d)
    d2 = deepcopy(b.d)
    lena = length(d1)
    lenb = length(d2)
    if lena == 0 || lenb == 0
        return R([zero(R.parent)])
    end
    lenz = lena + lenb - 1
    z = zeros(R.parent, lenz)
    k = lenz-lena
    for i in 1:k
        push!(d1,zero(R.parent))
    end
    k = lenz-lenb
    for i in 1:k
        push!(d2,zero(R.parent))
    end
    for i in 1:lenz
        s = zero(R.parent)
        for j in 1:i
            s += d1[j] * d2[i+1-j]
        end
        z[i] = s
    end
    return R(z)
end

function *(a::PolyRingElem{T,S}, b::T) where {T <: CRingElem, S <: CRing}
    R = a.ring
    z = deepcopy(a.d)
    z = [k*b for k in z]
    return R(z)
end

function *(b::PolyRingElem{T,S}, a::T) where {T <: CRingElem, S <: CRing}
    R = b.ring
    z = deepcopy(b.d)
    z = [k*a for k in z]
    return R(z)
end

function *(n::U, a::PolyRingElem{T,S}) where {T <: CRingElem, S <: CRing, U <:Integer}
    if n < 0
        a = -a
        n = -n
    end
    if n == 1
        return a
    end
    Rpoly = a.ring
    R = Rpoly.parent
    if n == 0
        return zero(Rpoly)
    end
    bit = U(1) << (ndigits(n, base = 2) - 1)
    z = a
    bit >>= 1
    while bit != 0
        z = z+z
        if (bit & n) != 0
            z += a
        end
        bit >>=1
    end
    return z
end

function *(a::PolyRingElem{T,S}, n::U) where {T <: CRingElem, S <: CRing, U <:Integer}
    return n*a
end

#Again, just using square and multiply algorithm
function ^(a::PolyRingElem{T,S}, y::U) where {T <: CRingElem, S <: CRing, U <: Integer}
    if y < 0
        #Will implement invert function latert
        error("Not yet supported")
    end
    if y == 1
        return x
    end
    Rpoly = a.ring
    R = Rpoly.parent
    if y == 0
        return one(Rpoly)
    end
    # Should include some reduction of y modulo order if order is known
    bit = U(1) << (ndigits(y, base = 2) - 1)
    z = a
    bit >>= 1
    while bit != 0
        z = z*z
        if (bit & y) != 0
            z *= a
        end
        bit >>=1
    end
    return z
end

function ==(a::PolyRingElem{T,S}, b::PolyRingElem{T,S}) where {T <: CRingElem, S <: CRing}
    return sameRing(a, b) && (a.d == b.d)
end

####### GCD (Very much needed for constructing quotient rings) #######
function gcd(a::PolyRingElem{T,S}, b::PolyRingElem{T,S}) where {T <: CRingElem, S <: CRing}
    if !sameRing(a, b)
        error("Elements must be elements of same ring")
    end
    if degree(a) < degree(b)
        return gcd(b,a)
    end
    if iszero(a) && iszero(b)
        return zero(a.ring)
    end
    while degree(b) > -1
        q,r = divmod(a,b)
        a = b
        b = r
    end
    return monic(a)
end

function gcdx(a::PolyRingElem{T,S}, b::PolyRingElem{T,S}) where {T <: CRingElem, S <: CRing}
    if !sameRing(a, b)
        error("Elements must be elements of same ring")
    end
    if degree(a) < degree(b)
        (a, y1, x1) = gcdx(b,a)
        return (a, x1, y1)
    end
    Rpol = a.ring
    if iszero(b)
        factor = inv(LC(a))
        return (a*factor, one(Rpol)*factor, zero(Rpol))
    end
    x0, x1 = zero(Rpol), one(Rpol)
    y1, y0 = zero(Rpol), one(Rpol)
    while degree(b) > -1
        q, r = divmod(a,b)
        x = x1 - q*x0
        y = y1 - q*y0
        a, b = b, r
        x0, x1, y0, y1 = x, x0, y, y0
    end
    factor = inv(LC(a))
    return (a*factor, x1*factor, y1*factor)
end

#gcdx subroutine
function divmod(a::PolyRingElem{T,S}, b::PolyRingElem{T,S}) where {T <: CRingElem, S <: CRing}
    if !sameRing(a, b)
        error("Elements must be elements of same ring")
    end
    Rpol = a.ring
    R = Rpol.parent
    lcdiv = LC(b)
    quotient = zero(Rpol)
    remainder = a
    while degree(remainder) >= degree(b)
        z = zeros(R, degree(remainder)-degree(b))
        push!(z,LC(remainder)*(inv(lcdiv)))
        z = Rpol(z)
        quotient += z
        remainder -= z*b
    end
    return quotient, remainder
end

##########################################
#
#         Quotient Ring
#
##########################################

struct QuotientRing{T <: CRing, S <: CRingElem} <: CRing
    parent::T
    modulo::S
end

struct QuotientRingElem{T <: CRing, S <: CRingElem} <: CRingElem
    d::S
    ring::QuotientRing{T,S}
end

# Ring-Constructor
function QuotientRing(R::T, m::S) where {T <: CRing, S <: CRingElem}
    return QuotientRing{T,S}(R,m)
end

# Element-Constructor
function (R::QuotientRing{T,S})(a::S) where {T <: CRing, S <: CRingElem}
    m = R.modulo
    q,r = divmod(a,m)
    return QuotientRingElem{T,S}(r, R)
end

function (R::QuotientRing{PolyRing{T},PolyRingElem{S,T}})(a::Array{S}) where {T <: CRing, S <: CRingElem}
    a = R.parent(a)
    m = R.modulo
    q,r = divmod(a,m)
    return QuotientRingElem{PolyRing{T},PolyRingElem{S,T}}(r, R)
end

###### Printing ######
function show(io::IO, R::QuotientRing{T,S}) where {T <: CRing, S <: CRingElem}
    print(io, "$(R.parent)/<$(R.modulo)>")
end

function show(io::IO, a::QuotientRingElem{T,S}) where {T <: CRing, S <: CRingElem}
    print(io, "$(a.d)")
end

###### Helper functions #######

function zero(R::QuotientRing{T,S}) where {T <: CRing, S <: CRingElem}
    return R(zero(R.parent))
end

function one(R::QuotientRing{T,S}) where {T <: CRing, S <: CRingElem}
    return R(one(R.parent))
end

function iszero(a::QuotientRingElem{T,S}) where {T <: CRing, S <: CRingElem}
    return iszero(a.d)
end

function isone(a::QuotientRingElem{T,S}) where {T <: CRing, S <: CRingElem}
    return isone(a.d)
end

function order(R::QuotientRing{PolyRing{T},PolyRingElem{S,T}}) where {T <: CRing, S <: CRingElem}
    return order(R.parent.parent)^degree(R.modulo)
end

###### Basic operators #######
function -(a::QuotientRingElem{T,S}) where {T <: CRing, S <: CRingElem}
    if iszero(a)
        return a
    else
        R = a.ring
        return R(-a.d)
    end
end

function +(a::QuotientRingElem{T,S}, b::QuotientRingElem{T,S}) where {T <: CRing, S <: CRingElem}
    if !sameRing(a, b)
        error("Cannot add elements of different rings")
    end
    z = a.d + b.d
    R = a.ring
    return R(z)
end

function -(a::QuotientRingElem{T,S}, b::QuotientRingElem{T,S}) where {T <: CRing, S <: CRingElem}
    return a+(-b)
end

function *(a::QuotientRingElem{T,S}, b::QuotientRingElem{T,S}) where {T <: CRing, S <: CRingElem}
    if !sameRing(a, b)
        error("Cannot multiply elements of different rings")
    end
    z = a.d * b.d
    R = a.ring
    return R(z)
end

function *(n::Integer, a::QuotientRingElem{T,S}) where {T <: CRing, S <: CRingElem}
    z = n*a.d
    R = a.ring
    return R(z)
end

function *(a::QuotientRingElem{T,S}, n::Integer) where {T <: CRing, S <: CRingElem}
    return n*a
end

function inv(x::QuotientRingElem{T,S}) where {T <: CRing, S <: CRingElem}
    R = x.ring
    g, a, b = gcdx(x.d, R.modulo)
    (!isone(g)) && error("Element $x is not invertible in $(x.ring)")
    return R(a)
end

#yet again exponensiation uses the simple square and multiply algorithm
function ^(x::QuotientRingElem{T,S}, y::U) where {T <: CRing, S <: CRingElem, U <: Integer}
    if iszero(x) || y == 1
        return x
    end
    R = x.ring
    if y == 0
        return one(R)
    end
    if y < 0
        x = inv(x)
        y = -y
    end
    # Should include some reduction of y modulo order if order is known
    bit = U(1) << (ndigits(y, base = 2) - 1)
    z = x
    bit >>= 1
    while bit != 0
        z = z*z
        if (bit & y) != 0
            z *= x
        end
        bit >>=1
    end
    return z
end

function ==(a::QuotientRingElem{T,S}, b::QuotientRingElem{T,S}) where {T <: CRing, S <: CRingElem}
    return sameRing(a, b) && (a.d == b.d)
end

##########################################
#
#      Finite field of order p^2
#
##########################################
# Just a special case of the construction above for easy usage
# If p = 3 (mod 4), then -1 is a quadratic non-residue and x^2 + 1 is irredusible
function Fpsquare(p::T) where T <: Integer
    Zp = Zmod(p)
    ZpPol = PolyRing(Zp, "i")
    if !isprime(p)
        error("$p is not a prime!")
    end
    if p%4 != 3
        error("Only supported for p ≡ 3 (mod 4)")
    end
    m = ZpPol([Zp(T(1)),Zp(T(0)),Zp(T(1))])
    return QuotientRing(ZpPol, m)
end

# Element-Constructor
function (R::QuotientRing{PolyRing{Zmod{T}},PolyRingElem{ZmodElem{T},Zmod{T}}})(a::Array{S}) where {T <: Integer, S <: Integer}
    a = [T(x) for x in a]
    a = R.parent([R.parent.parent(i) for i in a])
    m = R.modulo
    q,r = divmod(a,m)
    return QuotientRingElem{PolyRing{Zmod{T}},PolyRingElem{ZmodElem{T},Zmod{T}}}(r, R)
end

#################################
#
# Dangerous or specific functions
#
#################################

function squareroot(a::ZmodElem{T}) where {T <: Integer}
    return a.ring(TShanks(a.d, a.ring.n)[1])
end

# This is just for the special case, Zp[X]/<f> where f is like x^2+1...
function cuberootOfOne(R::QuotientRing{PolyRing{Zmod{T}},PolyRingElem{ZmodElem{T},Zmod{T}}}) where {T <: Integer, S <: Integer}
    #idea is that the non trivial cube roots of 1 are -1±sqrt(-3)/2 as x^3-1 = (x-1)(x^2+x+1)
    Z = R.parent.parent
    if isone(Z(-3)^((Z.n-1)÷2))
        d = squareroot(Z(-3))
        u = R([d])
    elseif isone(Z(3)^((Z.n-1)÷2))
        d = squareroot(Z(3))
        u = R([zero(Z), d])
    else
        error("Probably no cuberoot of 1 in the field")
    end
    id = one(R)
    z = inv(2*id)
    return (-id+u)*z
end

# Make sure that the ring is actually a field before using this... Further 3|(q-1)
function cuberoot(a::CRingElem)
    q = order(a.ring)
    if q%9 != 7
        error("Currently only supported for q ≡ 7 (mod 9)")
    end
    #general euler criterion
    k = (q-1)÷3
    if !isone(a^k)
        error("$a is not a cube in $(a.ring)")
    end
    k = (q+2)÷9
    return a^k
end

function allcuberoots(a::CRingElem)
    w = cuberootOfOne(a.ring)
    k = cuberoot(a)
    return (k, k*w, k*w^2)
end

function allElements(R::QuotientRing{PolyRing{Zmod{T}},PolyRingElem{ZmodElem{T},Zmod{T}}}) where T <: Integer
    deg = degree(R.modulo)
    if deg != 2
        println("Too many!")
    end
    Z = R.parent.parent
    l = QuotientRingElem{PolyRing{Zmod{T}},PolyRingElem{ZmodElem{T},Zmod{T}}}[]
    for i1 in allElements(Z)
        for i2 in allElements(Z)
            push!(l,R([i1,i2]))
        end
    end
    return l
end

#################################
#
#            Other
#
#################################

legendre(a, p) = powermod(a, (p - 1) ÷ 2, p)

function TShanks(n::T, p::T) where T <: Union{Int, Int128, BigInt}
    legendre(n, p) != 1 && throw(ArgumentError("$n not a square (mod $p)"))
    local q::T = p - one(p)
    local s::T = 0
    while iszero(q % 2)
        q ÷= 2
        s += one(s)
    end
    if s == one(s)
        r = powermod(n, (p + 1) >> 2, p)
        return r, p - r
    end
    local z::T
    for z in 2:(p - 1)
        p - 1 == legendre(z, p) && break
    end
    local c::T = powermod(z, q, p)
    local r::T = powermod(n, (q + 1) >> 1, p)
    local t::T = powermod(n, q, p)
    local m::T = s
    local t2::T = zero(p)
    while !iszero((t - 1) % p)
        t2 = (t * t) % p
        local i::T
        for i in Base.OneTo(m)
            iszero((t2 - 1) % p) && break
            t2 = (t2 * t2) % p
        end
        b = powermod(c, 1 << (m - i - 1), p)
        r = (r * b) % p
        c = (b * b) % p
        t = (t * c) % p
        m = i
    end
    return r, p - r
end

end


using LinearAlgebraicRepresentation
using Base.Test

@testset "LarGrid-0-1 Tests" begin
   @testset "grid_0" begin
      @test LinearAlgebraicRepresentation.grid_0(1) == [0  1]
      @test LinearAlgebraicRepresentation.grid_0(2) == [0  1  2]
      @test LinearAlgebraicRepresentation.grid_0(3) == [0  1  2  3]
   end

   @testset "Grid_1 Tests" begin
      @test repr(LinearAlgebraicRepresentation.grid_1(1)) == "[0; 1]"
      @test LinearAlgebraicRepresentation.grid_1(2) == [0 1; 1 2]
      @test LinearAlgebraicRepresentation.grid_1(3) == [0 1 2; 1 2 3]
   end

   @testset "LarGrid Tests" begin
      @test repr(LinearAlgebraicRepresentation.larGrid(1)(0)) == "[0 1]"
      @test repr(LinearAlgebraicRepresentation.larGrid(1)(1)) == "[0; 1]"
      @test LinearAlgebraicRepresentation.larGrid(2)(0) == [0  1  2]
      @test LinearAlgebraicRepresentation.larGrid(2)(1) == [0 1; 1 2]
      @test LinearAlgebraicRepresentation.larGrid(3)(0) == [0  1  2  3]
      @test LinearAlgebraicRepresentation.larGrid(3)(1) == [0  1  2; 1  2  3]
   end
end;

@testset "LarVertProd Tests" begin
	vertexDomain(n) = hcat([k for k in 0:n-1]...)
	@testset "LarVertProd 1D" begin
      shape = [3]
	vertLists = [vertexDomain(k+1) for k in shape]
      @test typeof(LinearAlgebraicRepresentation.larVertProd(vertLists))==Array{Int64,2}
      @test size(LinearAlgebraicRepresentation.larVertProd(vertLists))==(1, 4)
      @test LinearAlgebraicRepresentation.larVertProd(vertLists)[:,1]==[0]
      @test LinearAlgebraicRepresentation.larVertProd(vertLists)[:,4]==[3]
   end

   @testset "LarVertProd 2D" begin
      shape = [3,2]
	vertLists = [vertexDomain(k+1) for k in shape]
      @test typeof(LinearAlgebraicRepresentation.larVertProd(vertLists))==Array{Int64,2}
      @test size(LinearAlgebraicRepresentation.larVertProd(vertLists))==(2, 12)
      @test LinearAlgebraicRepresentation.larVertProd(vertLists)[:,1]==[0;0]
      @test LinearAlgebraicRepresentation.larVertProd(vertLists)[:,12]==[3;2]
   end

   @testset "LarVertProd 3D" begin
      shape = [3,2,1]
	vertLists = [vertexDomain(k+1) for k in shape]
      @test typeof(LinearAlgebraicRepresentation.larVertProd(vertLists))==Array{Int64,2}
      @test size(LinearAlgebraicRepresentation.larVertProd(vertLists))==(3, 24)
      @test LinearAlgebraicRepresentation.larVertProd(vertLists)[:,1]==[0;0;0]
      @test LinearAlgebraicRepresentation.larVertProd(vertLists)[:,24]==[3;2;1]
   end
end

@testset "Index2addr Tests" begin
   @testset "Shape 1D Tests" begin
      @test LinearAlgebraicRepresentation.index2addr([10])([0])==1
      @test LinearAlgebraicRepresentation.index2addr([10])([9])==10
      @test [LinearAlgebraicRepresentation.index2addr([10])([index]) for index in collect(0:9)]==collect(1:10)
   end

   @testset "Shape 2d Tests" begin
      aa = LinearAlgebraicRepresentation.cart([[0;1;2],[0;1]])
      bb = LinearAlgebraicRepresentation.cart([collect(0:9),collect(0:1)])
      cc = LinearAlgebraicRepresentation.cart([collect(0:2),collect(0:2)])
      dd = "Tuple{Int64,Int64}[(0,0),(0,1),(1,0),(1,1),(2,0),(2,1)]"
      ee = "Tuple{Int64,Int64}[(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1)]"
      ff = repr( LinearAlgebraicRepresentation.cart([collect(0:2),collect(0:1)]) )
      @test [ LinearAlgebraicRepresentation.index2addr([3,2])(collect(index)) for index in aa ]==collect(1:6)
      @test [ LinearAlgebraicRepresentation.index2addr([10,2])(collect(index)) for index in bb ] == collect(1:20)
      @test [ LinearAlgebraicRepresentation.index2addr([3,3])(collect(index)) for index in cc] == collect(1:9)
   end

   @testset "Shape 3d Tests" begin
      aaa = LinearAlgebraicRepresentation.cart([collect(0:3),collect(0:2),collect(0:1)])
      bbb = LinearAlgebraicRepresentation.cart([[0;1;2],[0;1],[0;1]])
      ccc = LinearAlgebraicRepresentation.cart([[0;1],[0;1],[0;1;2]])
      @test LinearAlgebraicRepresentation.index2addr([3,2,1])([0,0,0]) == 1
      @test [LinearAlgebraicRepresentation.index2addr([4,3,2])(collect(index))
      	for index in aaa ] == collect(1:24)
      @test [LinearAlgebraicRepresentation.index2addr([3,2,2])(collect(index))
      	for index in bbb ] == collect(1:12)
      @test [LinearAlgebraicRepresentation.index2addr([2,2,3])(collect(index))
      	for index in ccc ] == collect(1:12)
   end
end

@testset "BinaryRange Tests" begin
	binaryRange(n) = bin.(range(0,2^n),n)
   @test binaryRange(1)==["0";"1"]
   @test binaryRange(2)==["00","01","10","11"]
   @test binaryRange(3)==["000","001","010","011","100","101","110","111"]
end

@testset "LarCellProd Tests" begin
   @testset "$shape" for shape in [[3,2,1],[3,2],[3],[1,1,1]]
      @testset "$d" for d in 0:length(shape)
         @test typeof(LinearAlgebraicRepresentation.larGridSkeleton(shape)(d)) == Array{Array{Int64,1},1}
      end
   end
end

@testset "FilterByOrder Tests" begin
	binaryRange(n) = bin.(range(0,2^n),n)
   term = "000"
   bit = '0'
   theTerm = convert(Array{Char,1},term)
   @test typeof(theTerm) == Array{Char,1}
   @test parse(Int8,bit) == 0
   @test [parse(Int8,bit) for bit in theTerm] == zeros(3)
   out = hcat([[parse(Int8,bit) for bit in term] for term in binaryRange(3)]...)
   @test typeof(out) == Array{Int8,2}
   @test size(out) == (3,8)
   @test repr(out) == "Int8[0 0 0 0 1 1 1 1; 0 0 1 1 0 0 1 1; 0 1 0 1 0 1 0 1]"

   @testset  "$n" for n in 1:4
      data = [LinearAlgebraicRepresentation.filterByOrder(n)[k] for (k,el) in enumerate(LinearAlgebraicRepresentation.filterByOrder(n))]
      @test sum(map(length,data)) == 2^n
   end

   @testset "$n,$k" for n in 1:4, k in 0:n
      @test length(LinearAlgebraicRepresentation.filterByOrder(n)[k+1]) == binomial(n,k)
   end
end

@testset "LarImageVerts Tests" "$shape" for shape in [[3,2,1],[3,2],[10,10,10]]
   @test size(LinearAlgebraicRepresentation.larImageVerts(shape)) == (length(shape),prod(shape + 1))
end
@testset "LarCuboids Tests" begin
    shape = [3,2,1]
    cubes = LinearAlgebraicRepresentation.larCuboids(shape,true)
    verts, cells = cubes
    @test typeof(verts) == Array{Float64,2}
    VV,EV,FV,CV = cells
    @testset "$basis" for basis in [VV,EV,FV,CV]
      @test typeof(basis) == Array{Array{Int64,1},1}
    end
end

@testset "LarImageVerts Tests" "$shape" for shape in [[3,2,1],[3,2],[10,10,10]]
   @test size(LinearAlgebraicRepresentation.larImageVerts(shape)) == (length(shape),prod(shape + 1))
end

@testset "LarCuboids Tests" begin
   @testset "LarCuboids Tests" "$shape" for shape in [[3,2,1],[1,1,1],[3,3,10]]
      cubes = LinearAlgebraicRepresentation.larCuboids(shape,true);
      verts, cells = cubes;
      VV,EV,FV,CV = cells;
      @test size(LinearAlgebraicRepresentation.larCuboids(shape)[1],2) == prod(collect(shape) + 1)
      @test length(LinearAlgebraicRepresentation.larCuboids(shape)[2]) == prod(shape)
      @test typeof(verts) == Array{Float64,2}
	  @test typeof(VV) == Array{Array{Int64,1},1}
	  @test typeof(EV) == Array{Array{Int64,1},1}
	  @test typeof(FV) == Array{Array{Int64,1},1}
	  @test typeof(FV) == Array{Array{Int64,1},1}
   end

   @testset "LarCuboids Tests" "$shape" for shape in [[3,2],[1,1],[3,10]]
      @test size(LinearAlgebraicRepresentation.larCuboids(shape)[1],2) == prod(collect(shape) + 1)
      @test length(LinearAlgebraicRepresentation.larCuboids(shape)[2]) == prod(shape)
      cubes = LinearAlgebraicRepresentation.larCuboids(shape,true)
      verts, cells = cubes
      @test typeof(verts) == Array{Float64,2}
      VV,EV,FV = cells
	  @test typeof(VV) == Array{Array{Int64,1},1}
	  @test typeof(EV) == Array{Array{Int64,1},1}
	  @test typeof(FV) == Array{Array{Int64,1},1}
   end

   @testset "LarCuboids Tests" "$shape" for shape in [[3],[1],[10]]
      @test size(LinearAlgebraicRepresentation.larCuboids(shape)[1],2) == prod(collect(shape) + 1)
      @test length(LinearAlgebraicRepresentation.larCuboids(shape)[2]) == prod(shape)
      cubes = LinearAlgebraicRepresentation.larCuboids(shape,true)
      verts, cells = cubes
      @test typeof(verts) == Array{Float64,2}
      VV,EV = cells
	  @test typeof(VV) == Array{Array{Int64,1},1}
	  @test typeof(EV) == Array{Array{Int64,1},1}
   end
end

@testset "LinearAlgebraicRepresentation.LarModelProduct Tests" begin
   geom_0 = hcat([[x] for x=0.:10]...)
   topol_0 = [[i,i+1] for i=1:9]
   geom_1 = hcat([[0.],[1.],[2.]]...)
   topol_1 = [[1,2],[2,3]]
   model_0 = (geom_0, topol_0)
   model_1 = (geom_1, topol_1)
   model_2 = LinearAlgebraicRepresentation.larModelProduct(model_0, model_1)
   model_3 = LinearAlgebraicRepresentation.larModelProduct(model_2, model_1)
   @test size(model_2[1],2) == size(model_0[1],2) * size(model_1[1],2)
   @test length(model_2[2]) == length(model_0[2]) * length(model_1[2])
   @test size(model_3[1],2) == size(model_2[1],2) * size(model_1[1],2)
   @test length(model_3[2]) == length(model_2[2]) * length(model_1[2])
   V,EV = model_0
   @test typeof(V) == Array{Float64,2}
   @test typeof(EV) == Array{Array{Int64,1},1}
   V,EV = model_1
   @test typeof(V) == Array{Float64,2}
   @test typeof(EV) == Array{Array{Int64,1},1}
   V,FV = model_2
   @test typeof(V) ==  Array{Float64,2}
   @test typeof(FV) == Array{Array{Int64,1},1}
   V,CV = model_3
   @test typeof(V) ==  Array{Float64,2}
   @test typeof(CV) == Array{Array{Int64,1},1}

   modelOne = ([0 0 1 1; 0 1 0 1], Array{Int64,1}[[1, 2, 3, 4]])
   modelTwo = ([0 1], Array{Int64,1}[[1, 2]])
   @test LinearAlgebraicRepresentation.larModelProduct(modelOne, modelTwo) == LinearAlgebraicRepresentation.larModelProduct(modelTwo, modelOne)
   @test LinearAlgebraicRepresentation.larModelProduct(modelTwo, modelTwo)[1] == [0  0  1  1; 0  1  0  1]
   @test LinearAlgebraicRepresentation.larModelProduct(modelOne,modelOne)[2][1] == Any[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
end


"""
モザイクプロットは，の帯グラフにおいて，帯の幅をグループの相対度数に反映させたものである。

帯グラフ（積み上げ相対度数分布グラフ）は帯の幅を変えることができないので，以下のような関数を書く。
"""

using FreqTables
using Plots
function mosaicplot(t; xlabel="", ylabel="",
            color=1:size(t, 1), disp=:num, alpha=0.6, plotsize=(400, 400))
      function rect(x, i, y, j, color)
            plot!([x[i], x[i+1], x[i+1], x[i], x[i]],
                  [y[j], y[j], y[j+1], y[j+1], y[j]],
                  seriestype=:shape, linecolor=:yellow,
                  fillcolor=color, alpha=alpha, label="")
      end
      t = reverse(t, dims=1)
      dispt = disp == :num ? t : round.(t ./ sum(t), digits=3)
      rownames, colnames = names(t)
      color = reverse(color)
      nrow, ncol = size(t)
      n = sum(t)
      colsums = vec(sum(t, dims=1));
      colcumsums = cumsum(colsums) ./ n;
      pushfirst!(colcumsums, 0);
      colmid = (colcumsums[1:ncol] .+ colcumsums[2:end]) ./ 2;
      p = plot(grid=false, tick_direction=:out,
            xlabel=xlabel, xlims=(0,1),
            ylabel=ylabel, ylims=(0,1),
            aspect_ratio=1, size=plotsize, label="");
      for col = 1:ncol # col = 1
            x1, x2 = colcumsums[col:col+1]
            rowsums = t[:, col] ./ sum(t[:, col])
            rowcumsums = vec(cumsum(rowsums))
            pushfirst!(rowcumsums, 0)
            rowmid = (rowcumsums[1:end-1] .+ rowcumsums[2:end]) ./ 2
            for row = 1:nrow
                  rect(colcumsums, col, rowcumsums, row, color[row])
                  annotate!(colmid[col], rowmid[row],
                        text("$(colnames[col])\n$(rownames[row])\n$(dispt[row, col])", :white, 8,),
                        label="")
            end
      end
      display(p)
end

#=
使用例

てんとう虫の住んでいる場所と色についてのデータをモザイクプロットで描いてみる。

ladybirds_morph_colour.csv
https://github.com/R4All/datasets/blob/master/ladybirds_morph_colour.csv

julia> io = IOBuffer("""
           Habitat,Site,morph_colour,number
           Rural,R1,black,10
           Rural,R2,black,3
           Rural,R3,black,4
           Rural,R4,black,7
           Rural,R5,black,6
           Rural,R1,red,15
           Rural,R2,red,18
           Rural,R3,red,9
           Rural,R4,red,12
           Rural,R5,red,16
           Industrial,U1,black,32
           Industrial,U2,black,25
           Industrial,U3,black,25
           Industrial,U4,black,17
           Industrial,U5,black,16
           Industrial,U1,red,17
           Industrial,U2,red,23
           Industrial,U3,red,21
           Industrial,U4,red,9
           Industrial,U5,red,15""")
IOBuffer(data=UInt8[...], readable=true, writable=false, seekable=true, append=false, size=416, maxsize=Inf, ptr=1, mark=-1)

julia> using CSV, DataFrames, FreqTables

julia> dat = CSV.read(io, DataFrame);

julia> table = freqtable(dat.morph_colour, dat.Habitat, weights=dat.number)
2×2 Named Matrix{Int64}
Dim1 ╲ Dim2 │ "Industrial"       "Rural"
────────────┼───────────────────────────
"black"     │          115            30
"red"       │           85            70

julia> mosaicplot(table; xlabel="Havitat", ylabel="morph_colour", color=[:black, :red])
=#