#=

  "A puzzle" in ConstraintSolver.jl

  From "God plays dice"
  "A puzzle"
  http://gottwurfelt.wordpress.com/2012/02/22/a-puzzle/

  And the sequel "Answer to a puzzle"
  http://gottwurfelt.wordpress.com/2012/02/24/an-answer-to-a-puzzle/

  This problem instance was taken from the latter blog post.

  """
  8809 = 6
  7111 = 0
  2172 = 0
  6666 = 4
  1111 = 0
  3213 = 0
  7662 = 2
  9312 = 1
  0000 = 4
  2222 = 0
  3333 = 0
  5555 = 0
  8193 = 3
  8096 = 5
  7777 = 0
  9999 = 4
  7756 = 1
  6855 = 3
  9881 = 5
  5531 = 0

  2581 = ?
  """

  Note:
  This model yields 10 solutions, since x[5] is not
  restricted in the constraints.
  All solutions has x assigned to the correct result.

  The problem stated in "A puzzle"
  http://gottwurfelt.wordpress.com/2012/02/22/a-puzzle/
  is
  """
  8809 = 6
  7662 = 2
  9312 = 1
  8193 = 3
  8096 = 5
  7756 = 1
  6855 = 3
  9881 = 5

  2581 = ?
  """

  This problem instance - using the same principle - yields
  two different solutions of x, one is the same (correct) as
  for the above problem instance, and one is not.
  This is because here both x[5] and x[2] are underdefined.

  Note:
  This problem has another non-algebraic and - let's say - topological
  approach which yield the same solution as the first problem and one
  of the two solutions of the second problem.

  Model created by Hakan Kjellerstrand, hakank@gmail.com
  See also my Julia page: http://www.hakank.org/julia/

=#

using ConstraintSolver, JuMP
using Cbc, GLPK, Ipopt
const CS = ConstraintSolver
include("constraints_utils.jl")

function a_puzzle(problem,print_solutions=true,all_solutions=true,timeout=6)

    cbc_optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0)
    glpk_optimizer = optimizer_with_attributes(GLPK.Optimizer)
    ipopt_optimizer = optimizer_with_attributes(Ipopt.Optimizer)

    model = Model(optimizer_with_attributes(CS.Optimizer,   "all_solutions"=> all_solutions,
                                                            # "all_optimal_solutions"=>all_solutions,
                                                            "logging"=>[],

                                                            "traverse_strategy"=>:BFS,
                                                            # "traverse_strategy"=>:DFS,
                                                            # "traverse_strategy"=>:DBFS,

                                                            # "branch_split"=>:Smallest,
                                                            # "branch_split"=>:Biggest,
                                                            "branch_split"=>:InHalf,

                                                            # https://wikunia.github.io/ConstraintSolver.jl/stable/options/#branch_strategy-(:Auto)
                                                            "branch_strategy" => :IMPS, # default
                                                            # "branch_strategy" => :ABS, # Activity Based Search
                                                            # "activity.decay" => 0.999, # default 0.999
                                                            # "activity.max_probes" => 10, # default, 10
                                                            # "activity.max_confidence_deviation" => 20, # default 20

                                                            # "simplify"=>false,
                                                            # "simplify"=>true, # default

                                                            "time_limit"=>timeout,

                                                            # "backtrack" => false, # default true
                                                            # "backtrack_sorting" => false, # default true

                                                            # "lp_optimizer" => cbc_optimizer,
                                                            # "lp_optimizer" => glpk_optimizer,
                                                            # "lp_optimizer" => ipopt_optimizer,
                                        ))
    m = problem[:m]
    p = problem[:p] # the unknown

    n = 10
    @variable(model, 0 <= x[1:n] <= 9, Int)
    @variable(model, 0 <= z <= 9, Int)

    for i in 1:length(m)
        num, a = m[i]
        ds = split(num,"").|>d->parse(Int,d)
        @constraint(model, a == sum([x[ds[j]+1] for j in 1:4 ]))
    end
    # Find the unknown
    @constraint(model, z == sum([x[p[j]+1] for j in 1:4 ]))

    # Solve the problem
    optimize!(model)

    status = JuMP.termination_status(model)
    # println("status:$status")
    num_sols = 0
    if status == MOI.OPTIMAL
        num_sols = MOI.get(model, MOI.ResultCount())
        println("num_sols:$num_sols\n")
        if print_solutions
            for sol in 1:num_sols
                # println("solution #$sol")
                x_val = convert.(Integer,JuMP.value.(x; result=sol))
                z_val = convert.(Integer,JuMP.value.(z; result=sol))
                println("x:$x_val  z:$z_val")

            end
        end
    else
        println("status:$status")
    end

    return status, num_sols
end

problems = Dict(
    :1 => Dict(
        :m =>  [["8809",6],
                ["7111", 0],
                ["2172", 0],
                ["6666", 4],
                ["1111", 0],
                ["3213", 0],
                ["7662", 2],
                ["9312", 1],
                ["0000", 4],
                ["2222", 0],
                ["3333", 0],
                ["5555", 0],
                ["8193", 3],
                ["8096", 5],
                ["7777", 0],
                ["9999", 4],
                ["7756", 1],
                ["6855", 3],
                ["9881", 5],
                ["5531", 0]],
        :p => [2,5,8,1], # The unknown

    ),

    :2 => Dict(
        :m => [
            ["8809",6],
            ["7662",2],
            ["9312",1],
            ["8193",3],
            ["8096",5],
            ["7756",1],
            ["6855",3],
            ["9881",5],
        ],
        :p => [2,5,8,1]

    )

)

println("Problem I:")
@time a_puzzle(problems[:1],true,true)

println("\nProblem II:")
@time a_puzzle(problems[:2],true,true)


using StatisticalRethinking, CmdStan, GLM
#gr(size=(600,600))

ProjDir = rel_path("..", "scripts", "00")
cd(ProjDir)

howell1 = CSV.read(rel_path("..", "data", "Howell1.csv"), delim=';')
df = convert(DataFrame, howell1);

data = filter(row -> row[:age] >= 18, df)

first(data, 6)

n = size(data, 1)
test_ind = sample(1:n, Int(floor(0.3*n)), replace=false);
train_ind = [(i) for i=1:n if !(i in test_ind)];
test = data[test_ind, :];
train = data[train_ind, :];

train_cut = DataFrame(train)
test_cut = DataFrame(test)

train_label = train[:, :height]
test_label = test[:, :height]

remove_names = filter(x->!in(x, [:height, :age, :male]), names(data))

train = Matrix(train[:, remove_names]);
test = Matrix(test[:, remove_names]);

function standardize(x)
    return (x .- mean(x, dims=1)) ./ std(x, dims=1), x
end

function unstandardize(x, orig)
    return x .* std(orig, dims=1) .+ mean(orig, dims=1)
end

(train, train_orig) = standardize(train)
(test, test_orig) = standardize(test)
(train_label, train_l_orig) = standardize(train_label)
(test_label, test_l_orig) = standardize(test_label);

dmat = [ones(size(train, 1)) train]

lrmodel = "
data {
  int N; //the number of observations
  int K; //the number of columns in the model matrix
  real y[N]; //the response
  matrix[N,K] X; //the model matrix
}
parameters {
  vector[K] beta; //the regression parameters
  real sigma; //the standard deviation
}
transformed parameters {
  vector[N] linpred;
  linpred <- X*beta;
}
model {
  beta[1] ~ cauchy(0,10); // prior for the intercept following Gelman 2008

  for(i in 2:K)
   beta[i] ~ cauchy(0,2.5); // prior for the slopes following Gelman 2008

  y ~ normal(linpred,sigma);
}
";

stanmodel = Stanmodel(name="linear_regression",
  model=lrmodel, output_format=:mcmcchains);

lrdata = Dict("N" => size(train, 1), "K" => size(dmat, 2), "y" => train_label, "X" => dmat);

rc, chain, cnames = stan(stanmodel, lrdata, ProjDir, diagnostics=false,
  summary=false, CmdStanDir=CMDSTAN_HOME);

chns = set_section(chain, Dict(
    :parameters => ["beta.1", "beta.2", "sigma"],
    :linpred => ["linpred.$i" for i in 1:247],
    :internals => ["lp__", "accept_stat__", "stepsize__", "treedepth__",
      "n_leapfrog__", "divergent__", "energy__"]
  )
)

MCMCChains.describe(chns)

ols = lm(@formula(height ~ weight), train_cut)

train_cut.OLSPrediction = predict(ols);
test_cut.OLSPrediction = predict(ols, test_cut);

function prediction(chn, x)
    α = Array(chn[Symbol("beta.1")]);
    β = Array(chn[Symbol("beta.2")]);
    return  mean(α) .+ x .* mean(β)
end

train_cut.BayesPredictions = unstandardize(prediction(chns, train), train_l_orig)[:,1];
test_cut.BayesPredictions = unstandardize(prediction(chns, test), test_l_orig)[:,1];

remove_names = filter(x->!in(x, [:age, :male]), names(test_cut));
test_cut = test_cut[remove_names];
first(test_cut, 6)

bayes_loss1 = sum((train_cut.BayesPredictions - train_cut.height).^2);
ols_loss1 = sum((train_cut.OLSPrediction - train_cut.height).^2);

bayes_loss2 = sum((test_cut.BayesPredictions - test_cut.height).^2);
ols_loss2 = sum((test_cut.OLSPrediction - test_cut.height).^2);

println("\nTraining set:")
println("  Bayes loss: $bayes_loss1")
println("  OLS loss: $ols_loss1")

println("Test set:")
println("  Bayes loss: $bayes_loss2")
println("  OLS loss: $ols_loss2")

#plot(chain)

# This file was generated using Literate.jl, https://github.com/fredrikekre/Literate.jl


"""
    HDirProdPs(x,y)

Calculates horizontal direct product of two matrices with equal number of rows.
              z[i,:] is the Kronecker product of x[i,:] and y[i,:]

# Usage
z = HDirProdPs(x,y)

# Input
- `x::Array`:      T x Kx matrix
- `y::Array`:      T x Ky matrix

# Output
- `z::Array`:      T x (Kx*Ky)

# Example
```julia
julia>  x = [ 1 2;
              3 4]
julia>  y = [5 6 1;
             7 8 1]
julia> HDirProdPs(x,y)
2x6 Array{Int64,2}:
  5   6  1  10  12  2
 21  24  3  28  32  4
```

"""
function HDirProdPs(x,y)

  Kx = size(x,2)       #columns in x
  Ky = size(y,2)       #columns in y

  z = repeat(y,1,Kx) .* kron(x,ones(Int,1,Ky))  #Int: more general, small perf penalty

  return z

end
#----------------------------------------------------------------------------


using RecyclePermanent
using RNGPool
import Statistics: mean, var
import NonUniformRandomVariateGeneration.sampleCategorical
import LinearAlgebra: mul!, det
using DataFrames

function kuznetsovLogPermanent(A::Matrix, ks::Vector{Int64}, probs::Vector{Float64})
  N, n = size(A)
  @assert N >= n
  rng = getRNG()
  v::Float64 = 0.0
  for p in 1:n
    for i in 1:N
      @inbounds probs[i] = A[i, p]
    end
    for j in 1:p-1
      @inbounds probs[ks[j]] = 0.0
    end
    sp::Float64 = sum(probs)
    if sp == 0 return -Inf end
    k::Int64 = sampleCategorical(probs, sp, rng)
    v += log(sp)
    p < n && (ks[p] = k)
  end
  return v
end

function _signedBernoulli(rng)
  return 2*(rand(rng) < 0.5) - 1
end

function GodsilGutman(A::Matrix, X::Matrix, Z::Matrix)
  m, n = size(A)
  rng = getRNG()
  for i in 1:m
    for j in 1:n
      X[i,j] = sqrt(A[i,j]) * _signedBernoulli(rng)
    end
  end
  mul!(Z, X', X)
  return det(Z)
end

function ggPermanent(A::Matrix, N::Int64)
  m, n = size(A)
  @assert m >= n
  vs::Vector{Float64} = Vector{Float64}(undef, N)
  X = Matrix{Float64}(undef, m, n)
  Z = Matrix{Float64}(undef, n, n)
  for i in 1:N
    vs[i] = GodsilGutman(A, X, Z)
  end
  return vs
end

function kmPermanent(A::Matrix, ϵ::Float64, N::Int64)
  m, n = size(A)
  @assert m >= n
  vs::Vector{Float64} = Vector{Float64}(undef, N)
  ks = Vector{Int64}(undef, n-1)
  Cs = Vector{Float64}(undef, m)
  probs = Vector{Float64}(undef, m)
  for i in 1:N
    vs[i] = exp(RecyclePermanent.kmLogPermanent(A, ks, Cs, probs, ϵ))
  end
  return vs
end

function kuznetsovPermanent(A::Matrix, N::Int64)
  m, n = size(A)
  @assert m >= n
  vs::Vector{Float64} = Vector{Float64}(undef, N)
  ks = Vector{Int64}(undef, n-1)
  probs = Vector{Float64}(undef, m)
  for i in 1:N
    vs[i] = exp(kuznetsovLogPermanent(A, ks, probs))
  end
  return vs
end

function makeBinaryMatrix(m, n)
  rng = getRNG()
  c = 1.0
  for p = 1:n
    c *= m/(m-p+1)
  end
  return (rand(rng, m, n) .< 0.5) .* (2/m * c^(1/n))
end

function experiment(m, n, trials)
  A = makeBinaryMatrix(m, n)
  x = maximum(A)
  multipliers = [1000000, 10000, 100, 1, .1]
  t = @elapsed (vs = kuznetsovPermanent(A, trials))
  df = DataFrame(Algorithm = "K", TimeMS = t/trials*1000, Mean = mean(vs), Variance = var(vs))
  t = @elapsed (vs = ggPermanent(A, trials))
  push!(df, ["GG", t/trials*1000, mean(vs), var(vs)])
  for multiplier in multipliers
    mytrials = multiplier >= 1 ? trials : trials*10
    t = @elapsed (vs = kmPermanent(A, x*multiplier, mytrials))
    push!(df, ["KM: "*string(multiplier), t/mytrials*1000, mean(vs), var(vs)])
  end
  println("Experiment: m = ", m, ", n = ", n)
  show(df)
  println()
end

setRNGs(12345)

experiment(1000, 10, 10000)
experiment(1000, 100, 10000)


function ten_to_two(n)
    b = zeros(Int8,15)
    temp_b = zeros(Int8,15)

    ite=1
    while n > 0
        b[ite] = n % 2
        n = Int((n-b[ite])/2)
        ite = ite +1
    end

    #=
    for i in 1:6
        temp_b[i] = b[6+1-i]
    end
    =#

    return b
end

function two_to_ten(b)
    n = Int8(0)

    for i in 1:length(b)
        n = n + 2^(i-1)*b[i]
    end
    return n
end

# ----------------------
# -- Inverse matrix   --
# ----------------------
function inverse_matrix(eu,ev)
# ----------------------
# -- A = ( eu[1]  ev[1])  --
# --     ( eu[2]  ev[2])  --
# ----------------------
    invA = zeros(2,2)
    detA = eu[1]*ev[2] - eu[2]*ev[1]

    invA[1,1] = (ev[2])/detA
    invA[1,2] = (-ev[1])/detA
    invA[2,1] = (-eu[2])/detA
    invA[2,2] = (eu[1])/detA
    return invA
end

function cal_morton(ulx,uly,ulz,lrx,lry,lrz,n_div) # 0<=s,tでreturn
    # 左上奥と右下前の座標を整数で
    morton_belong = 0
    morton_num = 0

    upl_morton = zeros(Int8,15)
    lowr_morton = zeros(Int8,15)
    temp_morton = zeros(Int8,15)

    # 左上の点のモートン空間番号
    x_two = ten_to_two(ulx)
    y_two = ten_to_two(uly)
    z_two = ten_to_two(ulz)

    for k in 1:5
        upl_morton[k*3-2] = x_two[k]
        upl_morton[k*3-1] = y_two[k]
        upl_morton[k*3]   = z_two[k]
    end

    # 右下の点のモートン空間番号
    x_two = ten_to_two(lrx)
    y_two = ten_to_two(lry)
    z_two = ten_to_two(lrz)

    for k in 1:5
        lowr_morton[k*3-2] = x_two[k]
        lowr_morton[k*3-1] = y_two[k]
        lowr_morton[k*3]   = z_two[k]
    end

    # 排他的論理和 xor
    for i in 1:15
        if upl_morton[i]+lowr_morton[i] == 2
            temp_morton[i] = 0
        else
            temp_morton[i] = upl_morton[i]+lowr_morton[i]
        end
    end

    # 所属空間のチェック

    ite = Int64(0)
    for i in 1:n_div
        s30 = 3*(n_div-(i-1))
        s31 = 3*(n_div-(i-1)) - 1
        s32 = 3*(n_div-(i-1)) - 2

        if temp_morton[s30] == 1 || temp_morton[s31] == 1 || temp_morton[s32] == 1
            morton_belong = ite                             # ルート空間
            break
        end
        #=
        if temp_morton[s_even] == 1 || temp_morton[s_odd] == 1
            morton_belong = 0                             # ルート空間
        elseif temp_morton[4] == 1 || temp_morton[3] == 1
            morton_belong = 1                             # 親空間
        elseif temp_morton[2] == 1 || temp_morton[1] == 1
            morton_belong = 2                             # 子空間
        else
            morton_belong = 3                             # 孫空間
        end
        =#
        ite += 1
    end

    # 所属空間番号
    temp_morton = zeros(Int8,15)

    s30 = 3*(n_div)
    s31 = 3*(n_div) - 1
    s32 = 3*(n_div) - 2
    if morton_belong == 0                             # ルート空間
        morton_num = 0
    elseif morton_belong == 1                         # 親空間
        # 4右シフト
        temp_morton[1] = lowr_morton[s32]
        temp_morton[2] = lowr_morton[s31]
        temp_morton[3] = lowr_morton[s30]

        morton_num = two_to_ten(temp_morton)

    elseif morton_belong == 2                         # 子空間
        # 2右シフト
        temp_morton[1] = lowr_morton[s32-3]
        temp_morton[2] = lowr_morton[s31-3]
        temp_morton[3] = lowr_morton[s30-3]
        temp_morton[4] = lowr_morton[s32]
        temp_morton[5] = lowr_morton[s31]
        temp_morton[6] = lowr_morton[s30]

        morton_num = two_to_ten(temp_morton)
    elseif morton_belong == 3                         # 孫空間

        # 0右シフト
        temp_morton[1] = lowr_morton[s32-6]
        temp_morton[2] = lowr_morton[s31-6]
        temp_morton[3] = lowr_morton[s30-6]
        temp_morton[4] = lowr_morton[s32-3]
        temp_morton[5] = lowr_morton[s31-3]
        temp_morton[6] = lowr_morton[s30-3]
        temp_morton[7] = lowr_morton[s32]
        temp_morton[8] = lowr_morton[s31]
        temp_morton[9] = lowr_morton[s30]

        morton_num = two_to_ten(temp_morton)

    elseif morton_belong == 4                         # 孫空間
        temp_morton = copy(lowr_morton)
        morton_num = two_to_ten(temp_morton)
    end

    return morton_belong,morton_num
end

function liner_morton(n_div)
    # 両方の探査を行うため，煩雑
    s = zeros(Int64,5)

    for j in 1:5
        for i in 1:(n_div+1)
            s[j] += floor(8.0^(i-j))
        end
        s[j] += (j-1)
    end

    search0 = zeros(Int64,8^0,s[1])
    search1 = zeros(Int64,8^1,s[2])
    search2 = zeros(Int64,8^2,s[3])
    search3 = zeros(Int64,8^3,s[4])
    search4 = zeros(Int64,8^4,s[5])

    # ルート空間：s0
    for i in 1:1
        for j in 1:s[1]
            search0[i,j] = j
        end
    end

    # 親空間：s1
    for i in 1:8
        search1[i,1] = 1　　　　　　　　　　　　　　　　# ルート空間
        search1[i,2] = i+1                           # 親空間
        for j in 1:8
            search1[i,j+2] = 10 + 8*(i-1) + (j-1)     # 子空間
        end
        for j in 1:64
            search1[i,j+10] = 73 + 8^2*(i-1) + (j-1)   # 孫空間
        end

        if n_div ==4
            for j in 1:512
                search1[i,j+74] = 1+8+64+512+1 + 8^3*(i-1) + (j-1)   # ひ孫空間
            end
        end
    end

    # 子空間：s2
    for i in 1:64
        search2[i,1] = 1                             # ルート空間
        search2[i,2] = div(i-1,8) + 2                # 親空間
        search2[i,3] = i+9                           # 子空間
        for j in 1:8
            search2[i,j+3] = 10 + 8*(i-1) + (j-1)    # 孫空間
        end
        if n_div ==4
            for j in 1:64
                search2[i,j+11] = 73 + 8^2*(i-1) + (j-1)    # 孫空間
            end
        end
    end

    # 孫空間
    for i in 1:512
        search3[i,1] = 1                             # ルート空間
        search3[i,2] = div(i-1,64) + 2               # 親空間
        search3[i,3] = div(i-1,8) + 10                # 子空間
        search3[i,4] = 73 + (i-1)                    # 孫空間
        if n_div >=4
            for j in 1:8
                search3[i,j+4] = 10 + 8*(i-1) + (j-1)    # 孫空間
            end
        end
    end

    # ひ孫空間
    if n_div >=4
        for i in 1:4096
            search4[i,1] = 1                             # ルート空間
            search4[i,2] = div(i-1,512) + 2               # 親空間
            search4[i,3] = div(i-1,64) + 10                # 子空間
            search4[i,4] = div(i-1,8) + 73               # 孫空間
            search4[i,5] = 585 + (i-1)                    # 孫空間
        end
    end

    return s,search0,search1,search2,search3,search4
end


using jlmie
using Plots

# settings
# structure
nmat = 4  # refractive index of a sphere
radius = 100*1e-9

# environment
nenv = 1.00

# wavelenth
# lbd0 = 618*1e-9  # ED
# lbd0 = 831*1e-9  # MD
lbd0 = 918*1e-9  # 1nd Kerker
# lbd0 = 756*1e-9  # 2nd Kerker
lbdp = lbd0.*1e9

# angular range
theta = range(0,2π,length=73) # per 5 degrees with length 37
phi = 0.0
# phi = pi/2

# other settings
nmax = -1  # -1: namx large enough (determined from x)

Isff = zeros(length(theta))
for i = 1:length(theta)
    Isff[i],_,_ = jlmie_Isff(nmat,radius,lbd0,nenv,theta[i],phi,nmax)
end
Isff = Isff/maximum(Isff)

# show results
plt = plot(theta, Isff,
           proj=:polar,
           legend=false
)
display(plt)

# save
if true
    runningfilename = splitext(splitpath(@__FILE__)[end])[1]
    outfilename = ".\\result\\" * runningfilename
    savefig(plt,outfilename)
    println("Graph has been saved in result directory")
end


push!(LOAD_PATH, "./femmodule/")
push!(LOAD_PATH, "./gridmodule/")

using BaseFEM
using BaseGrid

# m=grid1D("./mesh1d/mesh3")
# m=grid2D("./mesh2d/test")
m=grid3D("./mesh3d/ball")

top  = m.topology
ndof = ndof_P1(top)
geo  = restrain_geometry(m.geometry,ndof)

A,M=assembly_P1(geo,top)

# solve with homogeneous dirichlet boundary conditions
reduce = topology_id(geo.topology,1).==0

# elimination of boundary conditions
reduce = reduce[1:ndof]
u      = zeros(ndof)
rhs    = M*ones(ndof)

# solve reduced system
A_reduced    = A[reduce,reduce]
rhs_reduced  = rhs[reduce]
u_reduced    = A_reduced\rhs_reduced

# extend to full system again
u[reduce] = u_reduced

# output to ParaView vtk file
outvtk_scalar("output/example_poisson.vtk",geo,top,"sol",u)


@diffusion_process FitzHughNagumoAux{K,R} begin
    :dimensions
    process --> 2
    wiener --> 1

    :parameters
    (ϵ, s, γ, β, σ) --> K

    :auxiliary_info
    t0 --> Float64
    T --> Float64
    vT --> R

    :additional
    constdiff --> true
    linear --> true
end

B(t, P::FitzHughNagumoAux) = @SMatrix [
    1/P.ϵ-3*P.vT[1]^2/P.ϵ   -1/P.ϵ;
    P.γ                 -1.0
]

β(t, P::FitzHughNagumoAux) = @SVector [
    P.s/P.ϵ+2*P.vT[1]^3/P.ϵ,
    P.β
]

σ(t, P::FitzHughNagumoAux) = @SVector [0.0, P.σ]


"""
    ImplicitFunction{F,C,L}

Differentiable wrapper for an implicit function `x -> ŷ(x)` whose output is defined by explicit conditions `F(x,ŷ(x)) = 0`.

We can obtain the Jacobian of `ŷ` with the implicit function theorem:
```
∂₁F(x,ŷ(x)) + ∂₂F(x,ŷ(x)) * ∂ŷ(x) = 0
```
If `x ∈ ℝⁿ`, `y ∈ ℝᵐ` and `F(x,y) ∈ ℝᶜ`, this amounts to solving the linear system `A * J = B`, where `A ∈ ℝᶜᵐ`, `B ∈ ℝᶜⁿ` and `J ∈ ℝᵐⁿ`.

# Fields:
- `forward::F`: callable of the form `x -> ŷ(x)`
- `conditions::C`: callable of the form `(x,y) -> F(x,y)`
- `linear_solver::L`: callable of the form `(A,b) -> u` such that `A * u = b`
"""
struct ImplicitFunction{F,C,L}
    forward::F
    conditions::C
    linear_solver::L
end

"""
    ImplicitFunction(forward, conditions)

Construct an `ImplicitFunction` with `Krylov.gmres` as the default linear solver.

# See also
- [`ImplicitFunction{F,C,L}`](@ref)
"""
function ImplicitFunction(forward::F, conditions::C) where {F,C}
    return ImplicitFunction(forward, conditions, Krylov.gmres)
end

struct SolverFailureException <: Exception
    msg::String
end

"""
    implicit(x)

Make [`ImplicitFunction`](@ref) callable by applying `implicit.forward`.
"""
(implicit::ImplicitFunction)(x) = implicit.forward(x)

"""
    frule(rc, (_, dx), implicit, x)

Custom forward rule for [`ImplicitFunction`](@ref).

We compute the Jacobian-vector product `Jv` by solving `Au = Bv` and setting `Jv = u`.
"""
function ChainRulesCore.frule(
    rc::RuleConfig, (_, dx), implicit::ImplicitFunction, x::AbstractVector
)
    (; forward, conditions, linear_solver) = implicit

    y = forward(x)
    n, m = length(x), length(y)

    conditions_x(x̃) = conditions(x̃, y)
    conditions_y(ỹ) = -conditions(x, ỹ)

    pushforward_A(dỹ) = frule_via_ad(rc, (NoTangent(), dỹ), conditions_y, y)[2]
    pushforward_B(dx̃) = frule_via_ad(rc, (NoTangent(), dx̃), conditions_x, x)[2]

    mul_A!(res, v) = res .= pushforward_A(v)
    mul_B!(res, v) = res .= pushforward_B(v)

    A = LinearOperator(Float64, m, m, false, false, mul_A!)
    B = LinearOperator(Float64, m, n, false, false, mul_B!)

    dx_vec = Vector(unthunk(dx))
    b = B * dx_vec
    dy_vec, stats = linear_solver(A, b)
    if !stats.solved
        throw(SolverFailureException("The linear solver failed to converge"))
    end
    return y, dy_vec
end

"""
    rrule(rc, implicit, x)

Custom reverse rule for [`ImplicitFunction`](@ref).

We compute the vector-Jacobian product `Jᵀv` by solving `Aᵀu = v` and setting `Jᵀv = Bᵀu`.
"""
function ChainRulesCore.rrule(rc::RuleConfig, implicit::ImplicitFunction, x::AbstractVector)
    (; forward, conditions, linear_solver) = implicit

    y = forward(x)
    n, m = length(x), length(y)

    conditions_x(x̃) = conditions(x̃, y)
    conditions_y(ỹ) = -conditions(x, ỹ)

    pullback_Aᵀ = last ∘ rrule_via_ad(rc, conditions_y, y)[2]
    pullback_Bᵀ = last ∘ rrule_via_ad(rc, conditions_x, x)[2]

    mul_Aᵀ!(res, v) = res .= pullback_Aᵀ(v)
    mul_Bᵀ!(res, v) = res .= pullback_Bᵀ(v)

    Aᵀ = LinearOperator(Float64, m, m, false, false, mul_Aᵀ!)
    Bᵀ = LinearOperator(Float64, n, m, false, false, mul_Bᵀ!)

    function implicit_pullback(dy)
        dy_vec = Vector(unthunk(dy))
        u, stats = linear_solver(Aᵀ, dy_vec)
        if !stats.solved
            throw(SolverFailureException("The linear solver failed to converge"))
        end
        dx_vec = Bᵀ * u
        return (NoTangent(), dx_vec)
    end

    return y, implicit_pullback
end


module CellDofBasesTests

using Test
using FillArrays
using Gridap.Arrays
using Gridap.Fields
using Gridap.ReferenceFEs
using Gridap.CellData
using Gridap.Fields: MockField, OtherMockBasis
using LinearAlgebra

reffe = QUAD4
ndofs = 3
b = get_shapefuns(reffe)
d = get_dof_basis(reffe)
c = fill(1.0,ndofs)
f = OtherMockBasis{2}(ndofs)

l = 10
bl = fill(b,l)
dl = Fill(d,l)
cl = fill(c,l)
fl = Fill(f,l)
ϕl = lincomb(fl,cl)

ref_style = Val(true)
axs = (Base.OneTo(num_dofs(reffe)),)
cell_axes = Fill(axs,l)
cb = GenericCellField(bl,ϕl,ref_style,cell_axes)
cd = GenericCellDofBasis(ref_style,dl)
test_cell_dof_basis(cd,cb)

@test evaluate(cd,cb) ≈ Fill(Matrix(I,num_dofs(reffe),num_dofs(reffe)),l)

end # module


#types
export Term, LinearOperatorFamily, Solution, save
#solvers
export householder, beyn, solve, count_poles_and_zeros
export mslp, inveriter, lancaster, traceiter, rf2s, decode_error_flag
export nicoud, picard
#export mehrmann, juniper, lancaster, count_eigvals
#helper functions for beyn
export pos_test, moments2eigs, compute_moment_matrices, wn, inpoly
#perturbation stuff
export pade!, perturb!, perturb_fast!, perturb_norm!, estimate_pol, conv_radius
#export algebra
export pow0,pow1,pow2,pow_a,pow,exp_delay,z_exp_iaz,z_exp__iaz,exp_pm,exp_az2mzit
export Σnexp_az2mzit, exp_az, generate_stsp_z, generate_z_g_z
export generate_Σy_exp_ikx,generate_gz_hz, generate_exp_az
# export fitting staff
#export fit_ss_wrapper


#randomgrowth2.jl
using PyPlot
using ODE

function randomgrowth2()
    num_trials = 2000
    g = 1
    q = 0.7
    Ns = 80
    # Ns = [50, 100, 200]
    clf()
    # [-1.771086807411  08131947928329]
    # Gap = c d^N
    for jj = 1:length(Ns)
        N = Ns[jj]
        M = round(N*g)
        B = zeros(num_trials)
        @time begin
            for ii = 1:num_trials
                # G = G[M, N, q]
                # B[ii] = G[M,N]
                B[ii] = G(M, N, q)
            end
        end
        C = (B - N*om(g,q)) / (sigma_growth(g,q)*N^(1/3))
        d = 0.2
        subplot(1,length(Ns),jj)
        plt.hist(C - exp(-N*0.0025 - 3), normed = true)
        xlim(-6, 4)

        ## Theory
        t0 = 4
        tn = -6
        dx = 0.005
        deq = (t, y) -> [y[2]; t*y[1]+2*y[1]^3; y[4]; y[1]^2]
        y0 = [airy(t0); airy(1,t0); 0; airy(t0)^2]      # boundary conditions
        t, y = ode23(deq, y0, t0:-dx:tn)                # solve
        F2 = Float64[exp(-y[i][3]) for i = 1:length(y)]                               # the distribution
        f2 = gradient(F2, t)                            # the density

        # add(p, Curve(t, f2, "color", "red", "linewidth", 3))
        # Winston.display(p)
        subplot(1,length(Ns),jj)
        plot(t, f2, "r", linewidth = 3)
        ylim(0, 0.6)
        println(mean(C))
    end
end

function G(N, M, q)
    # computes matrix G[N,M] for a given q
    GG = floor(log(rand(N,M))/log(q))
    #float(rand(N,M) < q)

    # Compute the edges: the boundary
    for ii = 2:N
        GG[ii, 1] = GG[ii, 1] + GG[ii-1, 1]
        GG[1, ii] = GG[1, ii] + GG[1, ii-1]
    end
    # Compute the inside
    for ii = 2:N
        for jj = 2:M
            GG[ii, jj] = GG[ii, jj] + max(GG[ii, jj-1], GG[ii-1, jj])
        end
    end
    ## Plot
    # imagesc((0,N),(M,0),GG)
    # sleep(.01)

    return  GG[N, M]
end

# helper functions sigma.m and om, which describe mean and
# standard deviation of G[N, M]

# Computes G[qN,N]/N for N->inf
om(g,q) = ((1 + sqrt(g * q)) ^ 2) / (1 - q) - 1

sigma_growth(r, q) =
    q^(1/6) * r^(1/6) / (1 - q) *
    (sqrt(r) + sqrt(q))^(2/3) *
    (1 + sqrt(q * r))^(2/3)

randomgrowth2()


using MatrixCompletion.Utilities.FastEigen
using IterativeSolvers
using LinearAlgebra
using Test

function create_symmetric_matrix(n)
    a = rand(n,n)*5
    return a+a'
end

function correct_output_sparseeigen(input,k)
    eigen_dcp = LinearAlgebra.eigen(input);
    eigen_val = eigen_dcp.values;
    eigen_vec = eigen_dcp.vectors;
    first_k_idx = Base.sortperm(eigen_val,rev=true)[1:k];
    return eigen_val[first_k_idx],eigen_vec[:,first_k_idx];
end

function get_projection(v,e)
    return e * Diagonal(v) * e';
end

function test_native_eigen(;dim=500,nev=20,repeat=5)
    for i = 1:repeat
        input = create_symmetric_matrix(dim);
        @time λ,X = eigs(NativeEigen(),input;nev=nev);
        λ0,X0 = correct_output_sparseeigen(input,nev);
        @test norm(get_projection(λ,X) - get_projection(λ0,X0),2)<1e-3;
    end
end

function test_lobpcg_wrapper(;dim=1000,nev=20,repeat=5)
    input = create_symmetric_matrix(dim);
    for i = 1:repeat
        @time λ,X = eigs(NativeLOBPCG(),input;nev=nev);
    end
end

function test_lobpcg_via_import(;dim=1000,nev=20,repeat=5)
    input = create_symmetric_matrix(dim);
    for i = 1:5
        @time lobpcg(input,true,nev);
    end
end


# please instantiate ./test/Manifest.toml for this

using JLD2, RollingFunctions
import Statistics

# ========================================
# Asymmetric Truncated Fuzz Test Fixtures
# ========================================

# format: [(values, window_size, expected_medians)]

asym_trunc_fixtures = Vector{Tuple{Vector{Float64}, Integer, Vector{Float64}}}(undef, 0)

function naive_asymmetric_truncated_median(input, window_size)
    if window_size > length(input)
        window_size = length(input)
    end

    if window_size |> iseven
        alpha = (window_size / 2 + 1) |> Int
    else
        alpha = ((window_size + 1) / 2) |> Int
    end
    growing_phase_inds = [1:k for k in alpha:window_size]
    rolling_phase_inds = [k:k + window_size - 1 for k in 2:(length(input) - window_size + 1)]
    shrinking_phase_inds = [length(input) - k + 1:length(input) for k in window_size - 1:-1:alpha]
    phase_inds = [growing_phase_inds; rolling_phase_inds; shrinking_phase_inds]
    output = [Statistics.median(input[inds]) for inds in phase_inds]
    return output
end

for i in 1:100
    N = rand(1:40)
    window_size = rand(1:50)
    values = rand(N)
    expected_medians = naive_asymmetric_truncated_median(values, window_size)
    push!(asym_trunc_fixtures, (values, window_size, expected_medians))
end

asym_trunc_filename = dirname(@__FILE__) * "/asym_trunc.jld2"
@save asym_trunc_filename asym_trunc_fixtures


module NAFEMS_R0031_examples
using FinEtools
using FinEtoolsDeforLinear
using FinEtoolsDeforLinear.AlgoDeforLinearModule
using Statistics: mean
using LinearAlgebra: norm, cross
function NAFEMS_R0031_1()
    println("""
    Laminated Strip Under Three-Point Bending
    """)

    # Determine the central transverse displacement in a simply-supported seven
    # layer symmetric strip with a central line load. A 0/90/0/90/0/90/0
    # material lay-up is specified with the center ply being four times as
    # thick as the others.
    # Reference: NAFEMS Report R0031, Test No.1, 17-Dec-1998.

    # Because of the symmetries of the geometry and load, only the
    # first-quadrant   (in XY) quarter of the plate is modeled.

    # The coordinate system is centered at point E (at the difference with
    # respect to the original benchmark definition).  The  load is applied
    # along a curve passing through point C. The simple support is applied
    # along the curve passing through point B.

    # We realize the simple supports along the lines  A, B and the line load at
    # point C  are illegal from the point of view of convergence.  No
    # convergence can be hoped for as the stress underneath the load and above
    # the simple supports  is infinite in the limit (these locations are stress
    # singularities).   However, for relatively coarse meshes the results away
    # from the singularities are still meaningful.

    # The target quantities are displacement at the bottom surface at point E,
    # the tensile axial stress at the same point,  and of the transverse shear
    # stress at point D  in between the bottommost two layers (See figure 1).

    t0 = time()
    # Orthotropic material parameters of the material of the layers
    E1s = 100.0*phun("GPa")
    E2s = E3s = 5.0*phun("GPa")
    nu12s = 0.4
    nu13s = 0.3
    nu23s = 0.3
    G12s = 3.0*phun("GPa")
    G13s = G23s = 2.0*phun("GPa")
    CTE1 = 3.0e-6
    CTE2 = 2.0e-5
    CTE3 = 2.0e-5

    AB = 30.0*phun("mm") # span between simple supports
    CD = 4.0*phun("mm") # distance between the point D and the center
    OH = 10.0*phun("mm") # overhang
    W = 10.0*phun("mm") # width of the strip

    # Here we define the layout and the thicknesses of the layers.
    angles = vec([0 90 0 90 0 90 0]);
    ts = vec([0.1  0.1  0.1  0.4  0.1  0.1  0.1])*phun("mm"); # layer thicknesses
    TH = sum(ts); # total thickness of the plate

    tolerance = 0.0001*TH

    # The line load is in the negative Z direction.
    q0 = 10*phun("N/mm"); #    line load

    # Reference deflection under the load is
    wEref = -1.06*phun("mm");

    # The reference tensile stress at the bottom of the lowest layer is
    sigma11Eref = 684*phun("MPa");

    # Because we model the first-quadrant quarter of the plate using
    # coordinate axes centered  at the point E  the shear at the point D is
    # positive instead of negative as in the benchmark where the coordinate
    # system is located at the outer corner of the strip.
    sigma13Dref=4.1*phun("MPa");

    Refinement = 10
    # We select 8 elements spanwise and 2 elements widthwise.  The overhang
    # of the plate is given one element.
    nL = Refinement * 4; nO = Refinement * 1; nW = Refinement * 1;

    # Each layer is modeled with a single element.
    nts= Refinement * ones(Int, length(angles));# number of elements per layer

    xs = unique(vcat(collect(linearspace(0.0,AB/2,nL+1)), [CD],
    collect(linearspace(AB/2,AB/2+OH,nO+1))))
    xs = xs[sortperm(xs)]
    ys = collect(linearspace(0.0,W/2,nW+1));

    fens,fes = H8layeredplatex(xs, ys, ts, nts)

    # This is the material  model
    MR = DeforModelRed3D
    material = MatDeforElastOrtho(MR,
    0.0, E1s, E2s, E3s,
    nu12s, nu13s, nu23s,
    G12s, G13s, G23s,
    CTE1, CTE2, CTE3)

    # The material coordinate system function is defined as:
    function updatecs!(csmatout::FFltMat, XYZ::FFltMat, tangents::FFltMat, fe_label::FInt)
        rotmat3!(csmatout, angles[fe_label]/180.0*pi* [0.0; 0.0; 1.0]);
    end

    # The vvolume integrals are evaluated using this rule
    gr = GaussRule(3, 2)

    # We will create two regions, one for the layers with 0°  orientation,
    # and one for the layers with 90° orientation.
    rl1 = vcat(selectelem(fens, fes, label = 1), selectelem(fens, fes, label = 3),
    selectelem(fens, fes, label = 5), selectelem(fens, fes, label = 7))
    rl2 = vcat(selectelem(fens, fes, label = 2), selectelem(fens, fes, label = 4),
    selectelem(fens, fes, label = 6))
    region1 = FDataDict("femm"=>FEMMDeforLinearMSH8(MR, IntegDomain(subset(fes, rl1), gr), CSys(3, 3, updatecs!), material))
    region2 = FDataDict("femm"=>FEMMDeforLinearMSH8(MR, IntegDomain(subset(fes, rl2), gr), CSys(3, 3, updatecs!), material))

    # File =  "NAFEMS-R0031-1-plate-r1.vtk"
    # vtkexportmesh(File, region1["femm"].integdomain.fes.conn, fens.xyz, FinEtools.MeshExportModule.H8)
    # # @async run(`"paraview.exe" $File`)
    # File =  "NAFEMS-R0031-1-plate-r2.vtk"
    # vtkexportmesh(File, region2["femm"].integdomain.fes.conn, fens.xyz, FinEtools.MeshExportModule.H8)
    # @async run(`"paraview.exe" $File`)

    # The essential boundary conditions are applied on the symmetry planes.
    # First the plane X=0;...
    lx0 = selectnode(fens, box=[0.0 0.0 -Inf Inf -Inf Inf], inflate=tolerance)
    ex0 = FDataDict( "displacement"=>  0.0, "component"=> 1, "node_list"=>lx0 )
    # ... and then the plane Y=0.
    ly0 = selectnode(fens, box=[-Inf Inf 0.0 0.0 -Inf Inf], inflate=tolerance)
    ey0 = FDataDict( "displacement"=>  0.0, "component"=> 2, "node_list"=>ly0 )
    # The transverse displacement is fixed along the line  passing through
    # point B. The nodes are fixed in the box along this line in the Z
    # direction.
    lz0 = selectnode(fens, box=[AB/2 AB/2 -Inf Inf -Inf Inf], inflate=tolerance)
    ez0 = FDataDict( "displacement"=>  0.0, "component"=> 3, "node_list"=>lz0 )

    # The traction boundary condition is applied  along  the edge of the
    # mesh passing through point C at the top surface of the strip.   First
    # we extract the boundary of the hexahedral mesh.
    bfes = meshboundary(fes)
    # From  the entire boundary we select those quadrilaterals that lie on the plane
    # X = 0
    xl = selectelem(fens, bfes, box = [0.0 0.0 -Inf Inf -Inf Inf], inflate=tolerance)
    # Now we extract the boundary  of these selected quadrilaterals
    bbfes = meshboundary(subset(bfes, xl))
    # …  And from these  we extract the ones at the top
    zl = selectelem(fens, bbfes, box = [0.0 0.0 -Inf Inf TH TH], inflate=tolerance)
    # Note that  we have to apply only half of the line load given that
    # were modeling  just one quarter of the geometry and we are splitting
    # the line load  with the symmetry plane X=0. Also note that the
    # quadrature rule is one-dimensional  since we are integrating along
    # a curve.
    Trac = FDataDict("traction_vector"=>vec([0.0; 0.0; -q0/2]), "femm"=>FEMMBase(IntegDomain(subset(bbfes, zl), GaussRule(1, 3))))

    modeldata = FDataDict("fens"=>fens, "regions"=>[region1, region2], "essential_bcs"=>[ex0, ey0, ez0], "traction_bcs"=> [Trac])
    modeldata = AlgoDeforLinearModule.linearstatics(modeldata)

    modeldata["postprocessing"] = FDataDict("file"=>"NAFEMS-R0031-1-plate")
    modeldata = AlgoDeforLinearModule.exportdeformation(modeldata)

    u = modeldata["u"]
    geom = modeldata["geom"]

    # The results of the displacement and stresses will be reported at
    # nodes located at the appropriate points.
    nE = selectnode(fens, box=[0.0 0.0 0.0 0.0 0.0 0.0], inflate=tolerance)
    nC = selectnode(fens, box=[0.0 0.0 0.0 0.0 TH TH], inflate=tolerance)
    nD = selectnode(fens, box=[CD CD 0.0 0.0 ts[1] ts[1]], inflate=tolerance)
    n0z = selectnode(fens, box=[0.0 0.0 0.0 0.0 0.0 TH], inflate=tolerance)
    ix = sortperm(geom.values[n0z, 3])
    # println("ix = $(ix)")

    cdis = mean(u.values[nE, 3])
    println("")
    println("Normalized Center deflection: $(cdis/wEref)")

    extrap = :extraptrend
    # # extrap = :extrapmean
    inspectormeth = :averaging
    # extrap = :default
    # inspectormeth = :invdistance

    modeldata["postprocessing"] = FDataDict("file"=>"NAFEMS-R0031-1-plate-sx",
    "quantity"=>:Cauchy, "component"=>1, "outputcsys"=>CSys(3),
    "nodevalmethod"=>inspectormeth, "reportat"=>extrap)
    modeldata = AlgoDeforLinearModule.exportstress(modeldata)
    s = modeldata["postprocessing"]["exported"][1]["field"]
    println("sx@E = $(s.values[nE]/phun("MPa")) [MPa]")

    modeldata["postprocessing"] = FDataDict("file"=>"NAFEMS-R0031-1-plate-sxz",
    "quantity"=>:Cauchy, "component"=>5, "outputcsys"=>CSys(3),
    "nodevalmethod"=>inspectormeth, "reportat"=>extrap)
    modeldata = AlgoDeforLinearModule.exportstress(modeldata)
    s = modeldata["postprocessing"]["exported"][1]["field"]
    println("sxz@D_1 = $(s.values[nD]/phun("MPa")) [MPa]")
    s = modeldata["postprocessing"]["exported"][2]["field"]
    println("sxz@D_2 = $(s.values[nD]/phun("MPa")) [MPa]")

    #
    # s = fieldfromintegpoints(region1["femm"], geom, u, :Cauchy, 1;
    #     outputcsys = CSys(3), nodevalmethod = inspectormeth, reportat = extrap)
    # println("sx@E = $(s.values[nE]/phun("MPa")) [MPa]")
    # sx_z = s.values[n0z]/phun("MPa")
    # println("sx(z)_1 = $(sx_z)")
    #
    # s = fieldfromintegpoints(region1["femm"], geom, u, :Cauchy, 5;
    #     outputcsys = CSys(3), nodevalmethod = inspectormeth, reportat = extrap)
    # println("sxz@D_1 = $(s.values[nD]/phun("MPa")) [MPa]")
    # sxz_z_1 = s.values[n0z]/phun("MPa")
    # println("sxz(z)_1 = $(sxz_z_1)")
    # s = fieldfromintegpoints(region2["femm"], geom, u, :Cauchy, 5;
    #     outputcsys = CSys(3), nodevalmethod = inspectormeth, reportat = extrap)
    # println("sxz@D_2 = $(s.values[nD]/phun("MPa")) [MPa]")
    # sxz_z_2 = s.values[n0z]/phun("MPa")
    # println("sxz(z)_2 = $(sxz_z_2)")

    # function _inspector(idat, elnum, conn, xe,  out,  xq)
    #     # xe = coordinates of the nodes of the element
    #     # xq = coordinate of the quadrature point
    #     println("@$(xq): $(out/1.0e6)")
    #     return idat
    # end
    #
    # felist = selectelem(fens, region1["femm"].integdomain.fes,
    #     box=[0.0 0.0 0.0 0.0 0.0 0.0], inflate=tolerance, allin = false)
    #
    # inspectintegpoints(region1["femm"], geom, u, felist,
    #     _inspector, 0, quantity=:Cauchy, outputcsys = CSys(3))
    #
    # femm = deepcopy(region1["femm"])
    # femm.integdomain.fes = subset(femm.integdomain.fes, felist)
    # associategeometry!(femm, geom)
    # s = fieldfromintegpoints(femm, geom, u, :Cauchy, 5;
    #     outputcsys = CSys(3), nodevalmethod = inspectormeth, reportat = extrap)
    # println("sxz@D_1 = $(s.values[nD]/phun("MPa")) [MPa]")

    # felist = selectelem(fens, region2["femm"].integdomain.fes,
    #     box=[0.0 0.0 0.0 0.0 0.0 TH], inflate=tolerance, allin = false)
    #
    # inspectintegpoints(region2["femm"], geom, u, felist,
    #     _inspector, 0, quantity=:Cauchy, outputcsys = CSys(3))

    println("Done")
    true

end # NAFEMS_R0031_1

function NAFEMS_R0031_1_H20()
    println("""
    Laminated Strip Under Three-Point Bending
    """)

    # Determine the central transverse displacement in a simply-supported seven
    # layer symmetric strip with a central line load. A 0/90/0/90/0/90/0
    # material lay-up is specified with the center ply being four times as
    # thick as the others.
    # Reference: NAFEMS Report R0031, Test No.1, 17-Dec-1998.

    # Because of the symmetries of the geometry and load, only the
    # first-quadrant   (in XY) quarter of the plate is modeled.

    # The coordinate system is centered at point E (at the difference with
    # respect to the original benchmark definition).  The  load is applied
    # along a curve passing through point C. The simple support is applied
    # along the curve passing through point B.

    # We realize the simple supports along the lines  A, B and the line load at
    # point C  are illegal from the point of view of convergence.  No
    # convergence can be hoped for as the stress underneath the load and above
    # the simple supports  is infinite in the limit (these locations are stress
    # singularities).   However, for relatively coarse meshes the results away
    # from the singularities are still meaningful.

    # The target quantities are displacement at the bottom surface at point E,
    # the tensile axial stress at the same point,  and of the transverse shear
    # stress at point D  in between the bottommost two layers (See figure 1).

    t0 = time()
    # Orthotropic material parameters of the material of the layers
    E1s = 100.0*phun("GPa")
    E2s = E3s = 5.0*phun("GPa")
    nu12s = 0.4
    nu13s = 0.3
    nu23s = 0.3
    G12s = 3.0*phun("GPa")
    G13s = G23s = 2.0*phun("GPa")
    CTE1 = 3.0e-6
    CTE2 = 2.0e-5
    CTE3 = 2.0e-5

    AB = 30.0*phun("mm") # span between simple supports
    CD = 4.0*phun("mm") # distance between the point D and the center
    OH = 10.0*phun("mm") # overhang
    W = 10.0*phun("mm") # width of the strip

    # Here we define the layout and the thicknesses of the layers.
    angles = vec([0 90 0 90 0 90 0]);
    ts = vec([0.1  0.1  0.1  0.4  0.1  0.1  0.1])*phun("mm"); # layer thicknesses
    TH = sum(ts); # total thickness of the plate

    tolerance = 0.0001*TH

    # The line load is in the negative Z direction.
    q0 = 10*phun("N/mm"); #    line load

    # Reference deflection under the load is
    wEref = -1.06*phun("mm");

    # The reference tensile stress at the bottom of the lowest layer is
    sigma11Eref = 684*phun("MPa");

    # Because we model the first-quadrant quarter of the plate using
    # coordinate axes centered  at the point E  the shear at the point D is
    # positive instead of negative as in the benchmark where the coordinate
    # system is located at the outer corner of the strip.
    sigma13Dref=4.1*phun("MPa");

    Refinement = 8
    # We select 8 elements spanwise and 2 elements widthwise.  The overhang
    # of the plate is given one element.
    nL = Refinement * 4; nO = Refinement * 1; nW = Refinement * 1;

    # Each layer is modeled with a single element.
    nts= Refinement * ones(Int, length(angles));# number of elements per layer

    xs = unique(vcat(collect(linearspace(0.0,AB/2,nL+1)), [CD],
    collect(linearspace(AB/2,AB/2+OH,nO+1))))
    xs = xs[sortperm(xs)]
    ys = collect(linearspace(0.0,W/2,nW+1));

    fens,fes = H8layeredplatex(xs, ys, ts, nts)
    fens,fes = H8toH20(fens,fes)

    # This is the material  model
    MR = DeforModelRed3D
    material = MatDeforElastOrtho(MR,
    0.0, E1s, E2s, E3s,
    nu12s, nu13s, nu23s,
    G12s, G13s, G23s,
    CTE1, CTE2, CTE3)

    # The material coordinate system function is defined as:
    function updatecs!(csmatout::FFltMat, XYZ::FFltMat, tangents::FFltMat, fe_label::FInt)
        rotmat3!(csmatout, angles[fe_label]/180.0*pi* [0.0; 0.0; 1.0]);
    end

    # The vvolume integrals are evaluated using this rule
    gr = GaussRule(3, 3)

    # We will create two regions, one for the layers with 0°  orientation,
    # and one for the layers with 90° orientation.
    rl1 = vcat(selectelem(fens, fes, label = 1), selectelem(fens, fes, label = 3),
    selectelem(fens, fes, label = 5), selectelem(fens, fes, label = 7))
    rl2 = vcat(selectelem(fens, fes, label = 2), selectelem(fens, fes, label = 4),
    selectelem(fens, fes, label = 6))
    region1 = FDataDict("femm"=>FEMMDeforLinear(MR, IntegDomain(subset(fes, rl1), gr), CSys(3, 3, updatecs!), material))
    region2 = FDataDict("femm"=>FEMMDeforLinear(MR, IntegDomain(subset(fes, rl2), gr), CSys(3, 3, updatecs!), material))

    # File =  "NAFEMS-R0031-1-plate-r1.vtk"
    # vtkexportmesh(File, region1["femm"].integdomain.fes.conn, fens.xyz, FinEtools.MeshExportModule.H8)
    # # @async run(`"paraview.exe" $File`)
    # File =  "NAFEMS-R0031-1-plate-r2.vtk"
    # vtkexportmesh(File, region2["femm"].integdomain.fes.conn, fens.xyz, FinEtools.MeshExportModule.H8)
    # @async run(`"paraview.exe" $File`)

    # The essential boundary conditions are applied on the symmetry planes.
    # First the plane X=0;...
    lx0 = selectnode(fens, box=[0.0 0.0 -Inf Inf -Inf Inf], inflate=tolerance)
    ex0 = FDataDict( "displacement"=>  0.0, "component"=> 1, "node_list"=>lx0 )
    # ... and then the plane Y=0.
    ly0 = selectnode(fens, box=[-Inf Inf 0.0 0.0 -Inf Inf], inflate=tolerance)
    ey0 = FDataDict( "displacement"=>  0.0, "component"=> 2, "node_list"=>ly0 )
    # The transverse displacement is fixed along the line  passing through
    # point B. The nodes are fixed in the box along this line in the Z
    # direction.
    lz0 = selectnode(fens, box=[AB/2 AB/2 -Inf Inf 0.0 0.0], inflate=tolerance)
    ez0 = FDataDict( "displacement"=>  0.0, "component"=> 3, "node_list"=>lz0 )

    # The traction boundary condition is applied  along  the edge of the
    # mesh passing through point C at the top surface of the strip.   First
    # we extract the boundary of the hexahedral mesh.
    bfes = meshboundary(fes)
    # From  the entire boundary we select those quadrilaterals that lie on the plane
    # X = 0
    xl = selectelem(fens, bfes, box = [0.0 0.0 -Inf Inf -Inf Inf], inflate=tolerance)
    # Now we extract the boundary  of these selected quadrilaterals
    bbfes = meshboundary(subset(bfes, xl))
    # …  And from these  we extract the ones at the top
    zl = selectelem(fens, bbfes, box = [0.0 0.0 -Inf Inf TH TH], inflate=tolerance)
    # Note that  we have to apply only half of the line load given that
    # were modeling  just one quarter of the geometry and we are splitting
    # the line load  with the symmetry plane X=0. Also note that the
    # quadrature rule is one-dimensional  since we are integrating along
    # a curve.
    Trac = FDataDict("traction_vector"=>vec([0.0; 0.0; -q0/2]),    "femm"=>FEMMBase(IntegDomain(subset(bbfes, zl), GaussRule(1, 3))))

    modeldata = FDataDict("fens"=>fens, "regions"=>[region1, region2], "essential_bcs"=>[ex0, ey0, ez0], "traction_bcs"=> [Trac])
    modeldata = AlgoDeforLinearModule.linearstatics(modeldata)

    modeldata["postprocessing"] = FDataDict("file"=>"NAFEMS-R0031-1-plate")
    modeldata = AlgoDeforLinearModule.exportdeformation(modeldata)

    u = modeldata["u"]
    geom = modeldata["geom"]

    # The results of the displacement and stresses will be reported at
    # nodes located at the appropriate points.
    nE = selectnode(fens, box=[0.0 0.0 0.0 0.0 0.0 0.0], inflate=tolerance)
    nC = selectnode(fens, box=[0.0 0.0 0.0 0.0 TH TH], inflate=tolerance)
    nD = selectnode(fens, box=[CD CD 0.0 0.0 ts[1] ts[1]], inflate=tolerance)

    cdis = mean(u.values[nE, 3])
    println("")
    println("Normalized Center deflection: $(cdis/wEref)")

    modeldata["postprocessing"] = FDataDict("file"=>"NAFEMS-R0031-1-plate-sx",
    "quantity"=>:Cauchy, "component"=>1, "outputcsys"=>CSys(3))
    modeldata = AlgoDeforLinearModule.exportstress(modeldata)
    s = modeldata["postprocessing"]["exported"][1]["field"]
    println("sx@E = $(s.values[nE]/phun("MPa")) [MPa]")

    modeldata["postprocessing"] = FDataDict("file"=>"NAFEMS-R0031-1-plate-sxz",
    "quantity"=>:Cauchy, "component"=>5, "outputcsys"=>CSys(3))
    modeldata = AlgoDeforLinearModule.exportstress(modeldata)
    s = modeldata["postprocessing"]["exported"][1]["field"]
    println("sxz@D = $(s.values[nD]/phun("MPa")) [MPa]")
    s = modeldata["postprocessing"]["exported"][2]["field"]
    println("sxz@D = $(s.values[nD]/phun("MPa")) [MPa]")

    println("Done")
    true

end # NAFEMS_R0031_1_H20

function NAFEMS_R0031_2_both()
    println("""
    R0031(2): Wrapped thick cylinder under pressure and thermal loading.
    """)

    t0 = time()
    pu = ustring -> phun(ustring; system_of_units = :SIMM)
    # Orthotropic material parameters of the outer cylinder
    E1s = 130.0*pu("GPa")
    E2s = 5.0*pu("GPa")
    E3s = E2s
    nu12s = nu13s = 0.25
    nu23s = 0.0
    G12s = 10.0*pu("GPa")
    G13s = G12s
    G23s = 5.0*pu("GPa")
    CTE1 = 3.0e-6
    CTE2 = 2.0e-5
    CTE3 = 2.0e-5
    # Isotropic material parameters of the inner cylinder
    E = 2.1e5*pu("MPa")
    nu = 0.3
    CTE = 2.0e-5

    L = 200.0*pu("mm") # length of the cylinder
    ri = 23.0*pu("mm") # inner radius of the cylinder
    ti = 2.0*pu("mm") # thickness of the inner cylinder
    te = 2.0*pu("mm") # thickness of the outer cylinder
    q0 = 200.0*pu("MPa") # inner pressure
    dT = 130*pu("K") # temperature rise

    tolerance = 0.0001*ti

    # Generate mesh
    nL = 16 # number of elements lengthwise
    nc = 8 # number of elements circumferentially
    xs = collect(linearspace(0.0, L/2, nL+1))
    ys = collect(linearspace(0.0, pi/2, nc+1))
    ts = [ti; te];# layer thicknesses
    nts= 6*ones(Int, length(ts));# number of elements per layer
    fens,fes = H8layeredplatex(xs, ys, ts, nts)
    fens,fes = H8toH20(fens,fes)
    bfes = meshboundary(fes)
    # inner surface  for the pressure loading
    intl = selectelem(fens, bfes; facing=true, direction = [0.0 0.0 -1.0])
    # Shape into a cylinder
    for i = 1:count(fens)
        z = fens.xyz[i,1]; a = fens.xyz[i,2]; t = fens.xyz[i,3];
        fens.xyz[i,:] = [(ri+t)*cos(pi/2-a) (ri+t)*sin(pi/2-a) z];
    end

    MR = DeforModelRed3D
    outermaterial = MatDeforElastOrtho(MR,
    0.0, E1s, E2s, E3s,
    nu12s, nu13s, nu23s,
    G12s, G13s, G23s,
    CTE1, CTE2, CTE3)
    innermaterial = MatDeforElastIso(MR,
    0.0, E, nu, CTE)

    function cylcs!(csmatout::FFltMat, XYZ::FFltMat)
        csmatout[:, 2] = [0.0 0.0 1.0]
        csmatout[:, 3] = XYZ
        csmatout[3, 3] = 0.0
        csmatout[:, 3] = csmatout[:, 3]/norm(csmatout[:, 3])
        csmatout[:, 1] = cross(csmatout[:, 2], csmatout[:, 3])
    end

    function updatecs!(csmatout::FFltMat, XYZ::FFltMat, tangents::FFltMat, fe_label::FInt)
        cylcs!(csmatout, XYZ)
    end

    gr = GaussRule(3, 3)

    rli = selectelem(fens, fes, label=1)
    innerregion = FDataDict("femm"=>FEMMDeforLinear(MR, IntegDomain(subset(fes, rli), gr), innermaterial))
    rle = selectelem(fens, fes, label=2)
    outerregion = FDataDict("femm"=>FEMMDeforLinear(MR, IntegDomain(subset(fes, rle), gr), CSys(3, 3, updatecs!), outermaterial))

    lx0 = selectnode(fens, box=[0.0 0.0 -Inf Inf -Inf Inf], inflate=tolerance)
    ly0 = selectnode(fens, box=[-Inf Inf 0.0 0.0 -Inf Inf], inflate=tolerance)
    lz0 = selectnode(fens, box=[-Inf Inf -Inf Inf 0.0 0.0], inflate=tolerance)

    ex0 = FDataDict( "displacement"=>  0.0, "component"=> 1, "node_list"=>lx0 )
    ey0 = FDataDict( "displacement"=>  0.0, "component"=> 2, "node_list"=>ly0 )
    ez0 = FDataDict( "displacement"=>  0.0, "component"=> 3, "node_list"=>lz0 )

    function getpr!(forceout::FFltVec, XYZ::FFltMat, tangents::FFltMat, fe_label::FInt)
        csmatout = zeros(3, 3)
        cylcs!(csmatout, XYZ)
        copy!(forceout, q0*csmatout[:, 3])
    end

    Trac = FDataDict("traction_vector"=>getpr!, "femm"=>FEMMBase(IntegDomain(subset(bfes, intl), GaussRule(2, 3))))

    modeldata = FDataDict("fens"=>fens, "regions"=>[innerregion, outerregion], "essential_bcs"=>[ex0, ey0, ez0], "traction_bcs"=>[Trac], "temperature_change"=>FDataDict("temperature"=>dT))
    modeldata = AlgoDeforLinearModule.linearstatics(modeldata)

    u = modeldata["u"]
    geom = modeldata["geom"]
    # File =  "NAFEMS-R0031-2-plate.vtk"
    # vtkexportmesh(File, fes.conn, geom.values, FinEtools.MeshExportModule.H20;
    #     scalars = [("Layer", fes.label)], vectors = [("displacement", u.values)])
    # @async run(`"paraview.exe" $File`)

    modeldata["postprocessing"] = FDataDict("file"=>"NAFEMS-R0031-2", "outputcsys"=>CSys(3, 3, updatecs!), "quantity"=>:Cauchy, "component"=>6)
    modeldata = AlgoDeforLinearModule.exportstress(modeldata)
    modeldata["postprocessing"] = FDataDict("file"=>"NAFEMS-R0031-2-elem", "outputcsys"=>CSys(3, 3, updatecs!), "quantity"=>:Cauchy, "component"=>6)
    modeldata = AlgoDeforLinearModule.exportstresselementwise(modeldata)

    println("Done")
    true

end # NAFEMS_R0031_2_both

function NAFEMS_R0031_2_pressure()
    println("""
    R0031(2): Wrapped thick cylinder under pressure and thermal loading.
    """)

    t0 = time()
    # Orthotropic material parameters of the outer cylinder
    E1s = 130.0*phun("GPa")
    E2s = 5.0*phun("GPa")
    E3s = E2s
    nu12s = nu13s = 0.25
    nu23s = 0.0
    G12s = 10.0*phun("GPa")
    G13s = G12s
    G23s = 5.0*phun("GPa")
    CTE1 = 3.0e-6
    CTE2 = 2.0e-5
    CTE3 = 2.0e-5
    # Isotropic material parameters of the inner cylinder
    E = 2.1e5*phun("MPa")
    nu = 0.3
    CTE = 2.0e-5

    L = 200.0*phun("mm") # length of the cylinder
    ri = 23.0*phun("mm") # inner radius of the cylinder
    ti = 2.0*phun("mm") # thickness of the inner cylinder
    te = 2.0*phun("mm") # thickness of the outer cylinder
    q0 = 200.0*phun("MPa") # inner pressure
    dT = 0*phun("K") # NO temperature rise

    tolerance = 0.0001*ti

    # Generate mesh
    nL = 18 # number of elements lengthwise
    nc = 18 # number of elements circumferentially
    xs = collect(linearspace(0.0, L/2, nL+1))
    ys = collect(linearspace(0.0, pi/2, nc+1))
    ts = [ti; te];# layer thicknesses
    nts= 6*ones(Int, length(ts));# number of elements per layer
    fens,fes = H8layeredplatex(xs, ys, ts, nts)
    fens,fes = H8toH20(fens,fes)
    bfes = meshboundary(fes)
    # inner surface  for the pressure loading
    intl = selectelem(fens, bfes; facing=true, direction = [0.0 0.0 -1.0])
    # Shape into a cylinder
    for i = 1:count(fens)
        z = fens.xyz[i,1]; a = fens.xyz[i,2]; t = fens.xyz[i,3];
        fens.xyz[i,:] = [(ri+t)*cos(pi/2-a) (ri+t)*sin(pi/2-a) z];
    end

    MR = DeforModelRed3D
    outermaterial = MatDeforElastOrtho(MR,
    0.0, E1s, E2s, E3s,
    nu12s, nu13s, nu23s,
    G12s, G13s, G23s,
    CTE1, CTE2, CTE3)
    innermaterial = MatDeforElastIso(MR,
    0.0, E, nu, CTE)

    function cylcs!(csmatout::FFltMat, XYZ::FFltMat)
        csmatout[:, 2] = [0.0 0.0 1.0]
        radial = XYZ; radial[3] = 0.0
        csmatout[:, 3] = radial/norm(radial)
        csmatout[:, 1] = cross(csmatout[:, 2], csmatout[:, 3])
    end

    function updatecs!(csmatout::FFltMat, XYZ::FFltMat, tangents::FFltMat, fe_label::FInt)
        cylcs!(csmatout, XYZ)
    end

    gr = GaussRule(3, 3)

    rli = selectelem(fens, fes, label=1)
    innerregion = FDataDict("femm"=>FEMMDeforLinear(MR,
    IntegDomain(subset(fes, rli), gr), innermaterial))
    rle = selectelem(fens, fes, label=2)
    outerregion = FDataDict("femm"=>FEMMDeforLinear(MR,
    IntegDomain(subset(fes, rle), gr), CSys(3, 3, updatecs!), outermaterial))

    lx0 = selectnode(fens, box=[0.0 0.0 -Inf Inf -Inf Inf], inflate=tolerance)
    ly0 = selectnode(fens, box=[-Inf Inf 0.0 0.0 -Inf Inf], inflate=tolerance)
    lz0 = selectnode(fens, box=[-Inf Inf -Inf Inf 0.0 0.0], inflate=tolerance)

    ex0 = FDataDict( "displacement"=>  0.0, "component"=> 1, "node_list"=>lx0 )
    ey0 = FDataDict( "displacement"=>  0.0, "component"=> 2, "node_list"=>ly0 )
    ez0 = FDataDict( "displacement"=>  0.0, "component"=> 3, "node_list"=>lz0 )

    function getpr!(forceout::FFltVec, XYZ::FFltMat, tangents::FFltMat, fe_label::FInt)
        csmatout = zeros(3, 3)
        cylcs!(csmatout, XYZ)
        copy!(forceout, q0*csmatout[:, 3])
    end

    Trac = FDataDict("traction_vector"=>getpr!, "femm"=>FEMMBase(IntegDomain(subset(bfes, intl), GaussRule(2, 3))))

    modeldata = FDataDict("fens"=>fens,
    "regions"=>[innerregion, outerregion],
    "essential_bcs"=>[ex0, ey0, ez0],
    "traction_bcs"=>[Trac],
    "temperature_change"=>FDataDict("temperature"=>dT)
    )
    modeldata = AlgoDeforLinearModule.linearstatics(modeldata)

    u = modeldata["u"]
    geom = modeldata["geom"]
    # lcenter = selectnode(fens, box=[a/2 a/2  b/2 b/2 -Inf Inf], inflate=tolerance)
    # cdis = abs(mean(u.values[lcenter, 3]))
    # println("")
    # println("Normalized Center deflection: $(cdis/wc_analytical)")

    File =  "NAFEMS-R0031-2-plate.vtk"
    vtkexportmesh(File, fes.conn, geom.values, FinEtools.MeshExportModule.H20; scalars = [("Layer", fes.label)], vectors = [("displacement", u.values)])
    @async run(`"paraview.exe" $File`)

    println("Done")
    true

end # NAFEMS_R0031_2_pressure

function NAFEMS_R0031_3()
    println("""
    NAFEMS publication R0031/3 Composite plate test.
    Simply supported on all four edges.  Uniform transverse  loading.
    The modeled part is one quarter of the full plate here.
    """)

    # This is a test recommended by the National Agency for Finite Element Methods
    # and Standards (U.K.): Test R0031/3 from NAFEMS publication R0031, “Composites
    # Benchmarks,” February 1995.
    t0 = time()
    # Skin (face) material parameters
    E1s = 1.0e7*phun("psi")
    E2s = 0.4e7*phun("psi")
    E3s = 0.4e7*phun("psi")
    nu12s = 0.3
    nu13s = 0.3
    nu23s = 0.3
    G12s = 0.1875e7*phun("psi")
    G13s = 0.1875e7*phun("psi")
    G23s = 0.1875e7*phun("psi")
    # Core material parameters
    E1c = 10.0*phun("psi")
    E2c = 10.0*phun("psi")
    E3c = 10e4.*phun("psi")
    nu12c = 0.
    nu13c = 0.
    nu23c = 0.
    G12c = 10.0*phun("psi")
    G13c = 3.0e4*phun("psi")
    G23c = 1.2e4*phun("psi")
    L = 10.0*phun("in") # side of the square plate
    nL = 8 # number of elements along the side of the plate
    tolerance = 0.0001*phun("in")
    xs = collect(linearspace(0.0, L/2, nL+1))
    ys = collect(linearspace(0.0, L/2, nL+1))
    ts = [0.028; 0.75; 0.028]*phun("in")
    nts = [2; 3;  2; ] # number of elements through the thickness
    tmag = 100*phun("psi")

    # Generate mesh
    fens,fes = H8layeredplatex(xs, ys, ts, nts)
    fens,fes = H8toH20(fens,fes)

    MR = DeforModelRed3D
    skinmaterial = MatDeforElastOrtho(MR,
    0.0, E1s, E2s, E3s,
    nu12s, nu13s, nu23s,
    G12s, G13s, G23s,
    0.0, 0.0, 0.0)
    corematerial = MatDeforElastOrtho(MR,
    0.0, E1c, E2c, E3c,
    nu12c, nu13c, nu23c,
    G12c, G13c, G23c,
    0.0, 0.0, 0.0)

    gr = GaussRule(3, 3)

    rl1 = selectelem(fens, fes, label=1)
    skinbot = FDataDict("femm"=>FEMMDeforLinear(MR, IntegDomain(subset(fes, rl1), gr), skinmaterial))

    rl3 = selectelem(fens, fes, label=3)
    skintop = FDataDict("femm"=>FEMMDeforLinear(MR, IntegDomain(subset(fes, rl3), gr), skinmaterial))

    rl2 = selectelem(fens, fes, label=2)
    core = FDataDict("femm"=>FEMMDeforLinear(MR, IntegDomain(subset(fes, rl2), gr), corematerial))

    lx0 = selectnode(fens, box=[0.0 0.0 -Inf Inf -Inf Inf], inflate=tolerance)
    lxL2 = selectnode(fens, box=[L/2 L/2 -Inf Inf -Inf Inf], inflate=tolerance)
    ly0 = selectnode(fens, box=[-Inf Inf 0.0 0.0 -Inf Inf], inflate=tolerance)
    lyL2 = selectnode(fens, box=[-Inf Inf L/2 L/2 -Inf Inf], inflate=tolerance)

    ex0 = FDataDict( "displacement"=>  0.0, "component"=> 3, "node_list"=>lx0 )
    exL2 = FDataDict( "displacement"=>  0.0, "component"=> 1, "node_list"=>lxL2 )
    ey0 = FDataDict( "displacement"=>  0.0, "component"=> 3, "node_list"=>ly0 )
    eyL2 = FDataDict( "displacement"=>  0.0, "component"=> 2, "node_list"=>lyL2 )

    bfes = meshboundary(fes)
    ttopl = selectelem(fens, bfes; facing=true, direction = [0.0 0.0 1.0])
    Trac = FDataDict("traction_vector"=>[0.0; 0.0; -tmag], "femm"=>FEMMBase(IntegDomain(subset(bfes, ttopl), GaussRule(2, 3))))

    modeldata = FDataDict("fens"=>fens, "regions"=>[skinbot, core, skintop], "essential_bcs"=>[ex0, exL2, ey0, eyL2], "traction_bcs"=> [Trac])
    modeldata = AlgoDeforLinearModule.linearstatics(modeldata)

    u = modeldata["u"]
    geom = modeldata["geom"]
    lcenter = selectnode(fens, box=[L/2 L/2  L/2 L/2 -Inf Inf], inflate=tolerance)
    cdis = mean(u.values[lcenter, 3])/phun("in")
    println("Center node displacements $(cdis) [in]; NAFEMS-R0031-3 lists –0.123	[in]")
    println("")

    File =  "NAFEMS-R0031-3-plate.vtk"
    vtkexportmesh(File, fes.conn, geom.values, FinEtools.MeshExportModule.H20; scalars = [("Layer", fes.label)], vectors = [("displacement", u.values)])
    @async run(`"paraview.exe" $File`)

    println("Done")
    true

end # NAFEMS_R0031_3

function allrun()
    println("#####################################################")
    println("# NAFEMS_R0031_1 ")
    NAFEMS_R0031_1()
    println("#####################################################")
    println("# NAFEMS_R0031_1_H20 ")
    NAFEMS_R0031_1_H20()
    println("#####################################################")
    println("# NAFEMS_R0031_2_pressure ")
    NAFEMS_R0031_2_pressure()
    println("#####################################################")
    println("# NAFEMS_R0031_2_both ")
    NAFEMS_R0031_2_both()
    println("#####################################################")
    println("# NAFEMS_R0031_3 ")
    NAFEMS_R0031_3()
    return true
end # function allrun

end # module NAFEMS_R0031_examples


"""Symbolic one-hot Boolean encoding of unknown integer value in the range `1:k`.
The value is `i` if `vars[i]` is true and `vars[j]` is false for all j ≠ i.
"""
struct BoundedInteger{k,N,V}
    name::N
    vars::V
end

BoundedInteger{k}(name::N, vars::V) where {k,N,V} = BoundedInteger{k,N,V}(name, vars)

Base.max(x::BoundedInteger{k}) where {k} = k

function BoundedInteger{k}(name) where {k}
    vars = [Variable(Symbol(name, subscript(i))) for i in 1:k]

    return BoundedInteger{k}(name, vars)
end

function decode(var_dict, x::BoundedInteger)
    values = [var_dict[v] for v in x.vars]

    num_true = count(values)

    if num_true ≠ 1
        error("Variable $x has not been successfully solved: values $(x.vars .=> values)")
    end

    return findfirst(values)
end

Base.show(io::IO, i::BoundedInteger) = print(io, i.name)

Base.getindex(i::BoundedInteger, n) = i.vars[n]

function clauses(i::BoundedInteger{k}) where {k}
    vars = i.vars

    # takes one of the possible values:
    has_value = [∨(vars...)]

    # but not two simultaneously:
    unique_value =
        [¬vars[i] ∨ ¬vars[j] for i in 1:k for j in i+1:k]

    return has_value ∪ unique_value
end

subscript_digit(i) = '₀' + i
subscript(i::Integer) = join(subscript_digit.(reverse(digits(i))))


#===================================================================================================
  Composition Classes: Valid kernel transformations
===================================================================================================#

abstract CompositionClass{T<:AbstractFloat}

@inline eltype{T}(::CompositionClass{T}) = T

@inline iscomposable(::CompositionClass, ::Kernel) = false

@inline ismercer(::CompositionClass) = false
@inline isnegdef(::CompositionClass) = false

@inline attainszero(::CompositionClass)     = true
@inline attainspositive(::CompositionClass) = true
@inline attainsnegative(::CompositionClass) = true

function description_string(ϕ::CompositionClass, showtype::Bool = true)
    class = typeof(ϕ)
    fields = fieldnames(class)
    class_str = string(class.name.name) * (showtype ? string("{", eltype(ϕ), "}") : "")
    *(class_str, "(", join(["$field=$(getfield(ϕ,field).value)" for field in fields], ","), ")")
end

function show(io::IO, ϕ::CompositionClass)
    print(io, description_string(ϕ))
end

function convert{T<:AbstractFloat,K<:CompositionClass}(::Type{CompositionClass{T}}, ϕ::K)
    convert(K.name.primary{T}, ϕ)
end

function =={T<:CompositionClass}(ϕ1::T, ϕ2::T)
    all([getfield(ϕ1,field) == getfield(ϕ2,field) for field in fieldnames(T)])
end

#== Positive Mercer Classes ==#

abstract PositiveMercerClass{T<:AbstractFloat} <: CompositionClass{T}
@inline ismercer(::PositiveMercerClass) = true
@inline attainsnegative(::PositiveMercerClass) = false
@inline attainszero(::PositiveMercerClass) = false

doc"GammaExponentialClass(κ;α,γ) = exp(-α⋅κᵞ)"
immutable GammaExponentialClass{T<:AbstractFloat} <: PositiveMercerClass{T}
    alpha::HyperParameter{T}
    gamma::HyperParameter{T}
    GammaExponentialClass(α::Variable{T}, γ::Variable{T}) = new(
        HyperParameter(α, leftbounded(zero(T), :open)),
        HyperParameter(γ, Interval(Bound(zero(T), :open), Bound(one(T), :closed)))
    )
end
@outer_constructor(GammaExponentialClass, (1,0.5))
@inline iscomposable(::GammaExponentialClass, κ::Kernel) = isnegdef(κ) && isnonnegative(κ)
@inline phi{T<:AbstractFloat}(ϕ::GammaExponentialClass{T}, z::T) = exp(-ϕ.alpha * z^ϕ.gamma)

doc"ExponentialClass(κ;α) = exp(-α⋅κ²)"
immutable ExponentialClass{T<:AbstractFloat} <: PositiveMercerClass{T}
    alpha::HyperParameter{T}
    ExponentialClass(α::Variable{T}) = new(
        HyperParameter(α, leftbounded(zero(T), :open))
    )
end
@outer_constructor(ExponentialClass, (1,))
@inline iscomposable(::ExponentialClass, κ::Kernel) = isnegdef(κ) && isnonnegative(κ)
@inline phi{T<:AbstractFloat}(ϕ::ExponentialClass{T}, z::T) = exp(-ϕ.alpha * z)

doc"GammaRationalClass(κ;α,β,γ) = (1 + α⋅κᵞ)⁻ᵝ"
immutable GammaRationalClass{T<:AbstractFloat} <: PositiveMercerClass{T}
    alpha::HyperParameter{T}
    beta::HyperParameter{T}
    gamma::HyperParameter{T}
    GammaRationalClass(α::Variable{T}, β::Variable{T}, γ::Variable{T}) = new(
        HyperParameter(α, leftbounded(zero(T), :open)),
        HyperParameter(β, leftbounded(zero(T), :open)),
        HyperParameter(γ, Interval(Bound(zero(T), :open), Bound(one(T), :closed)))
    )
end
@outer_constructor(GammaRationalClass, (1,1,0.5))
@inline iscomposable(::GammaRationalClass, κ::Kernel) = isnegdef(κ) && isnonnegative(κ)
@inline phi{T<:AbstractFloat}(ϕ::GammaRationalClass{T}, z::T) = (1 + ϕ.alpha*z^ϕ.gamma)^(-ϕ.beta)

doc"RationalClass(κ;α,β,γ) = (1 + α⋅κ)⁻ᵝ"
immutable RationalClass{T<:AbstractFloat} <: PositiveMercerClass{T}
    alpha::HyperParameter{T}
    beta::HyperParameter{T}
    RationalClass(α::Variable{T}, β::Variable{T}) = new(
        HyperParameter(α, leftbounded(zero(T), :open)),
        HyperParameter(β, leftbounded(zero(T), :open))
    )
end
@outer_constructor(RationalClass, (1,1))
@inline iscomposable(::RationalClass, κ::Kernel) = isnegdef(κ) && isnonnegative(κ)
@inline phi{T<:AbstractFloat}(ϕ::RationalClass{T}, z::T) = (1 + ϕ.alpha*z)^(-ϕ.beta)

doc"MatérnClass(κ;ν,ρ) = 2ᵛ⁻¹(√(2ν)κ/ρ)ᵛKᵥ(√(2ν)κ/ρ)/Γ(ν)"
immutable MaternClass{T<:AbstractFloat} <: PositiveMercerClass{T}
    nu::HyperParameter{T}
    rho::HyperParameter{T}
    MaternClass(ν::Variable{T}, ρ::Variable{T}) = new(
        HyperParameter(ν, leftbounded(zero(T), :open)),
        HyperParameter(ρ, leftbounded(zero(T), :open))
    )
end
@outer_constructor(MaternClass, (1,1))
@inline iscomposable(::MaternClass, κ::Kernel) = isnegdef(κ) && isnonnegative(κ)
@inline function phi{T<:AbstractFloat}(ϕ::MaternClass{T}, z::T)
    v1 = sqrt(2ϕ.nu) * z / ϕ.rho
    v1 = v1 < eps(T) ? eps(T) : v1  # Overflow risk, z -> Inf
    2 * (v1/2)^(ϕ.nu) * besselk(ϕ.nu, v1) / gamma(ϕ.nu)
end

doc"ExponentiatedClass(κ;α) = exp(a⋅κ + c)"
immutable ExponentiatedClass{T<:AbstractFloat} <: PositiveMercerClass{T}
    a::HyperParameter{T}
    c::HyperParameter{T}
    ExponentiatedClass(a::Variable{T}, c::Variable{T}) = new(
        HyperParameter(a, leftbounded(zero(T), :open)),
        HyperParameter(c, leftbounded(zero(T), :closed))
    )
end
@outer_constructor(ExponentiatedClass, (1,0))
@inline iscomposable(::ExponentiatedClass, κ::Kernel) = ismercer(κ)
@inline phi{T<:AbstractFloat}(ϕ::ExponentiatedClass{T}, z::T) = exp(ϕ.a*z + ϕ.c)

#== Other Mercer Classes ==#

doc"PolynomialClass(κ;a,c,d) = (a⋅κ + c)ᵈ"
immutable PolynomialClass{T<:AbstractFloat,U<:Integer} <: CompositionClass{T}
    a::HyperParameter{T}
    c::HyperParameter{T}
    d::HyperParameter{U}
    PolynomialClass(a::Variable{T}, c::Variable{T}, d::Variable{U}) = new(
        HyperParameter(a, leftbounded(zero(T), :open)),
        HyperParameter(c, leftbounded(zero(T), :closed)),
        HyperParameter(d, leftbounded(one(U),  :closed))
    )
end
@outer_constructor(PolynomialClass, (1,0,3))
@inline iscomposable(::PolynomialClass, κ::Kernel) = ismercer(κ)
@inline phi{T<:AbstractFloat}(ϕ::PolynomialClass{T}, z::T) = (ϕ.a*z + ϕ.c)^ϕ.d
@inline ismercer(::PolynomialClass) = true

#== Non-Negative Negative Definite Kernel Classes ==#

abstract NonNegNegDefClass{T<:AbstractFloat} <: CompositionClass{T}
@inline isnegdef(::NonNegNegDefClass) = true
@inline attainsnegative(::NonNegNegDefClass) = false

doc"PowerClass(z;a,c,γ) = (az + c)ᵞ"
immutable PowerClass{T<:AbstractFloat} <: NonNegNegDefClass{T}
    a::HyperParameter{T}
    c::HyperParameter{T}
    gamma::HyperParameter{T}
    PowerClass(a::Variable{T}, c::Variable{T}, γ::Variable{T}) = new(
        HyperParameter(a, leftbounded(zero(T), :open)),
        HyperParameter(c, leftbounded(zero(T), :closed)),
        HyperParameter(γ, Interval(Bound(zero(T), :open), Bound(one(T), :closed)))
    )
end
@outer_constructor(PowerClass, (1,0,0.5))
@inline iscomposable(::PowerClass, κ::Kernel) = isnegdef(κ) && isnonnegative(κ)
@inline phi{T<:AbstractFloat}(ϕ::PowerClass{T}, z::T) = (ϕ.a*z + ϕ.c)^(ϕ.gamma)

doc"GammmaLogClass(z;α,γ) = log(1 + α⋅zᵞ)"
immutable GammaLogClass{T<:AbstractFloat} <: NonNegNegDefClass{T}
    alpha::HyperParameter{T}
    gamma::HyperParameter{T}
    GammaLogClass(α::Variable{T}, γ::Variable{T}) = new(
        HyperParameter(α, leftbounded(zero(T), :open)),
        HyperParameter(γ, Interval(Bound(zero(T), :open), Bound(one(T), :closed)))
    )
end
@outer_constructor(GammaLogClass, (1,0.5))
@inline iscomposable(::GammaLogClass, κ::Kernel) = isnegdef(κ) && isnonnegative(κ)
@inline phi{T<:AbstractFloat}(ϕ::GammaLogClass{T}, z::T) = log(ϕ.alpha*z^(ϕ.gamma) + 1)

doc"LogClass(z;α) = log(1 + α⋅z)"
immutable LogClass{T<:AbstractFloat} <: NonNegNegDefClass{T}
    alpha::HyperParameter{T}
    LogClass(α::Variable{T}) = new(
        HyperParameter(α, leftbounded(zero(T), :open))
    )
end
@outer_constructor(LogClass, (1,))
@inline iscomposable(::LogClass, κ::Kernel) = isnegdef(κ) && isnonnegative(κ)
@inline phi{T<:AbstractFloat}(ϕ::LogClass{T}, z::T) = log(ϕ.alpha*z + 1)

#== Non-Mercer, Non-Negative Definite Classes ==#

doc"SigmoidClass(κ;α,c) = tanh(a⋅κ + c)"
immutable SigmoidClass{T<:AbstractFloat} <: CompositionClass{T}
    a::HyperParameter{T}
    c::HyperParameter{T}
    SigmoidClass(a::Variable{T}, c::Variable{T}) = new(
        HyperParameter(a, leftbounded(zero(T), :open)),
        HyperParameter(c, leftbounded(zero(T), :closed))
    )
end
@outer_constructor(SigmoidClass, (1,0))
@inline iscomposable(::SigmoidClass, κ::Kernel) = ismercer(κ)
@inline phi{T<:AbstractFloat}(ϕ::SigmoidClass{T}, z::T) = tanh(ϕ.a*z + ϕ.c)

#===================================================================================================
  Kernel Composition ψ = ϕ(κ(x,y))
===================================================================================================#

doc"KernelComposition(ϕ,κ) = ϕ∘κ"
immutable KernelComposition{T<:AbstractFloat} <: StandardKernel{T}
    phi::CompositionClass{T}
    kappa::PairwiseKernel{T}
    function KernelComposition(ϕ::CompositionClass{T}, κ::PairwiseKernel{T})
        iscomposable(ϕ, κ) || error("Kernel is not composable.")
        new(ϕ, κ)
    end
end
function KernelComposition{T<:AbstractFloat}(ϕ::CompositionClass{T}, κ::PairwiseKernel{T})
    KernelComposition{T}(ϕ, κ)
end

function convert{T<:AbstractFloat}(::Type{KernelComposition{T}}, κ::KernelComposition)
    KernelComposition(convert(CompositionClass{T}, κ.phi), convert(Kernel{T}, κ.kappa))
end

function description_string(κ::KernelComposition, showtype::Bool = true)
    obj_str = string("KernelComposition", showtype ? string("{", eltype(κ), "}") : "")
    class_str = description_string(κ.phi, false)
    kernel_str = description_string(κ.kappa, false)
    string(obj_str, "(phi=", class_str, ",kappa=", kernel_str, ")")
end

function ==(ψ1::KernelComposition, ψ2::KernelComposition)
    (ψ1.phi == ψ2.phi) && (ψ1.kappa == ψ2.kappa)
end

ismercer(κ::KernelComposition) = ismercer(κ.phi)
isnegdef(κ::KernelComposition) = isnegdef(κ.phi)

attainszero(κ::KernelComposition)     = attainszero(κ.phi)
attainspositive(κ::KernelComposition) = attainspositive(κ.phi)
attainsnegative(κ::KernelComposition) = attainsnegative(κ.phi)

#== Composition Kernels ==#

doc"GaussianKernel(α) = exp(-α⋅‖x-y‖²)"
function GaussianKernel{T<:AbstractFloat}(α::Argument{T} = 1.0)
    KernelComposition(ExponentialClass(α), SquaredDistanceKernel{T}())
end
SquaredExponentialKernel = GaussianKernel
RadialBasisKernel = GaussianKernel

doc"LaplacianKernel(α) = exp(α⋅‖x-y‖)"
function LaplacianKernel{T<:AbstractFloat}(α::Argument{T} = 1.0)
    KernelComposition(GammaExponentialClass(α, convert(T, 0.5)), SquaredDistanceKernel{T}())
end

doc"PeriodicKernel(α,p) = exp(-α⋅Σⱼsin²(p(xⱼ-yⱼ)))"
function PeriodicKernel{T<:AbstractFloat}(α::Argument{T} = 1.0, p::Argument{T} = convert(T, π))
    KernelComposition(ExponentialClass(α), SineSquaredKernel(p))
end

doc"RationalQuadraticKernel(α,β) = (1 + α⋅‖x-y‖²)⁻ᵝ"
function RationalQuadraticKernel{T<:AbstractFloat}(α::Argument{T} = 1.0, β::Argument{T} = one(T))
    KernelComposition(RationalClass(α, β), SquaredDistanceKernel{T}())
end

doc"MatérnKernel(ν,θ) = 2ᵛ⁻¹(√(2ν)‖x-y‖²/θ)ᵛKᵥ(√(2ν)‖x-y‖²/θ)/Γ(ν)"
function MaternKernel{T<:AbstractFloat}(ν::Argument{T} = 1.0, θ::Argument{T} = one(T))
    KernelComposition(MaternClass(ν, θ), SquaredDistanceKernel{T}())
end
MatérnKernel = MaternKernel

doc"PolynomialKernel(a,c,d) = (a⋅xᵀy + c)ᵈ"
function PolynomialKernel{T<:AbstractFloat,U<:Integer}(
        a::Argument{T} = 1.0,
        c::Argument{T} = one(T),
        d::Argument{U} = 3
    )
    KernelComposition(PolynomialClass(a, c, d), ScalarProductKernel{T}())
end

doc"LinearKernel(α,c,d) = a⋅xᵀy + c"
function LinearKernel{T<:AbstractFloat}(a::Argument{T} = 1.0, c::Argument{T} = one(T))
    KernelComposition(PolynomialClass(a, c, 1), ScalarProductKernel{T}())
end

doc"SigmoidKernel(α,c) = tanh(a⋅xᵀy + c)"
function SigmoidKernel{T<:Real}(a::Argument{T} = 1.0, c::Argument{T} = one(T))
    KernelComposition(SigmoidClass(a, c), ScalarProductKernel{T}())
end

#== Special Compositions ==#

∘(ϕ::CompositionClass, κ::Kernel) = KernelComposition(ϕ, κ)

function ^{T<:AbstractFloat}(κ::PairwiseKernel{T}, d::Integer)
    KernelComposition(PolynomialClass(one(T), zero(T), d), κ)
end

function ^{T<:AbstractFloat}(κ::PairwiseKernel{T}, γ::T)
    KernelComposition(PowerClass(one(T), zero(T), γ), κ)
end

function exp{T<:AbstractFloat}(κ::PairwiseKernel{T})
    KernelComposition(ExponentiatedClass(one(T), zero(T)), κ)
end

function tanh{T<:AbstractFloat}(κ::PairwiseKernel{T})
    KernelComposition(SigmoidClass(one(T), zero(T)), κ)
end


#=
# 2D advection using DG
=#

### If the Finch package has already been added, use this line #########
using Finch # Note: to add the package, first do: ]add "https://github.com/paralab/Finch.git"

### If not, use these four lines (working from the examples directory) ###
# if !@isdefined(Finch)
#     include("../Finch.jl");
#     using .Finch
# end
##########################################################################

init_finch("advection2d");

useLog("advection2dlog")

domain(2)
solverType(DG)
functionSpace(order=3)
timeStepper(CRANK_NICHOLSON, cfl=0.1)

mesh(QUADMESH, elsperdim=8)

u = variable("u")
testSymbol("v")

T = 2;
timeInterval(T)
initial(u, "0")

### Add boundary regions and set BCs ###############
addBoundaryID(2, (x,y) -> (x <= 0 || y <= 0))
addBoundaryID(3, (x,y) -> (x < 0.2 && y < 0.2))
boundary(u, 1, NO_BC)
boundary(u, 2, DIRICHLET, 0)
boundary(u, 3, DIRICHLET, "sin(pi*(t - 0.7*(x+y)))^2")
####################################################

coefficient("a", type=VECTOR, [0.5, 0.5])
coefficient("beta", 0)
weakForm(u, "Dt(u*v) + a*grad(u)*v + surface(-dot(a, normal())*u*v + dot(a, normal())*ave(u)*v - 0.5*(1-beta)*dot(a, jump(u))*v)")

solve(u);

# Uncomment to plot
# using Plots
# pyplot();
# display(plot(Finch.grid_data.allnodes[1,:], Finch.grid_data.allnodes[2,:], u.values[:], st=:surface, reuse=false))

finalize_finch()


module Math2D

import Base: size, eltype, isempty, min, max, union, intersect, zero, one, typemin, typemax, +, -, *, /

export Vec2, Box, Rect
export size, eltype, len, len2, min, max, isempty, union, intersect, zero, one, normalize, orthogonal, dot, rect, typemin, typemax

struct Vec2{T}
	x::T
	y::T
end

size(::Type{Vec2{T}}) where T = (2,)
size(v::Vec2) = size(typeof(v))
eltype(::Type{Vec2{T}}) where T = T
eltype(v::Vec2) = eltype(typeof(v))

dot(v1::Vec2, v2::Vec2) = v1.x*v2.x + v1.y*v2.y

len2(v::Vec2) = v.x*v.x + v.y*v.y
len(v::Vec2) = sqrt(len2(v))

function normalize(v::Vec2{T}) where T
	l = len(v)
	l < eps(T) ? v : v / l
end

orthogonal(v::Vec2) = Vec2(v.y, -v.x)

zero(::Type{Vec2{T}}) where T = Vec2(zero(T), zero(T))
zero(v::Vec2) = zero(typeof(v))
one(::Type{Vec2{T}}) where T = Vec2(one(T), one(T))
one(v::Vec2) = one(typeof(v))
typemin(::Type{Vec2{T}}) where T = Vec2(typemin(T), typemin(T))
typemin(v::Vec2) = typemin(typeof(v))
typemax(::Type{Vec2{T}}) where T = Vec2(typemax(T), typemax(T))
typemax(v::Vec2) = typemax(typeof(v))

(+)(v::Vec2) = v
(-)(v::Vec2) = Vec2(-v.x, -v.y)

(+)(v1::Vec2, v2::Vec2) = Vec2(v1.x + v2.x, v1.y + v2.y)
(-)(v1::Vec2, v2::Vec2) = Vec2(v1.x - v2.x, v1.y - v2.y)
(*)(v1::Vec2, v2::Vec2) = Vec2(v1.x * v2.x, v1.y * v2.y)
(/)(v1::Vec2, v2::Vec2) = Vec2(v1.x / v2.x, v1.y / v2.y)

(*)(v::Vec2, n::Number) = Vec2(v.x * n, v.y * n)
(*)(n::Number, v::Vec2) = Vec2(n * v.x, n * v.y)
(/)(v::Vec2, n::Number) = Vec2(v.x / n, v.y / n)
(/)(n::Number, v::Vec2) = Vec2(n / v.x, n / v.y)

broadcast(::typeof(<), v1::Vec2, v2::Vec2) = v1.x < v2.x && v1.y < v2.y

min(v::Vec2) = v
min(v1::Vec2, v2::Vec2) = Vec2(min(v1.x, v2.x), min(v1.y, v2.y))
min(v::Vec2, vs::Vec2...) = min(v, min(vs...))

max(v::Vec2) = v
max(v1::Vec2, v2::Vec2) = Vec2(max(v1.x, v2.x), max(v1.y, v2.y))
max(v::Vec2, vs::Vec2...) = max(v, max(vs...))

mutable struct Box{V}
	min::V
	max::V
end

size(b::Box) = b.max - b.min
isempty(b::Box) = b.max .< b.min

union(bs::Box...) = Box(min([b.min for b in bs]), max([b.max for b in bs]))
intersect(bs::Box...) = Box(max([b.min for b in bs]), min([b.max for b in bs]))

const Rect{T} = Box{Vec2{T}}

rect(T, minX, minY, maxX, maxY) = Box(Vec2{T}(minX, minY), Vec2{T}(maxX, maxY))
rect(T) = Box(typemax(Vec2{T}), typemin(Vec2{T}))

end


#=
Two pooling layers - MeanPool and MaxPool
=#

########################################
# MeanPool
########################################

#=
Mean pooling function
 2D input -> 2D pooled output for each patch of each image
 Input:
 A - inputs from previous layer - dim x dim x num_patches x num_obs
 Output (updated in-place):
 Z - pooled features - pool_dim x pool_dim x num_patches x num_obs
=#
function meanpool!{F<:AbstractFloat}(Z::Array{F,4}, A::Array{F,4})

    stride = div(size(A, 1), size(Z, 1))
    t = stride * stride

    for img in indices(Z, 4)
        for patch in indices(Z, 3)
            col_start = 1
            col_end = stride
            for pcol in indices(Z, 2)
                row_start = 1
                row_end = stride
                for prow in indices(Z, 1)
                    s = F(0)
                    for incol = col_start:col_end
                        for inrow = row_start:row_end
                            @inbounds s += A[inrow, incol, patch, img]
                        end
                    end
                    @inbounds Z[prow, pcol, patch, img] = s / t
                    row_start += stride
                    row_end += stride
                end
                col_start += stride
                col_end += stride
            end
        end
    end
end

#=
Upsample function for mean pooling - uniformly distributes the error for a
single pooling unit among the units which feed into it in the previous layer
 Input:
 Δpool - delta of pool layer
       - pool_dim x pool_dim x num_patches x num_obs
 Output (updated in-place):
 Δconv - delta of previous layer (usually a convolution layer)
       - conv_dim x conv_dim x num_patches x num_obs
=#
function upsample_mean!{F<:AbstractFloat}(Δconv::Array{F,4}, Δpool::Array{F,4})

    stride = div(size(Δconv, 1), size(Δpool, 1))
    m = inv(stride^2)

    for img in indices(Δpool, 4)
        for patch in indices(Δpool, 3)
            start_col = 1
            stop_col = stride
            for pcol in indices(Δpool, 2)
                start_row = 1
                stop_row = stride
                for prow in indices(Δpool, 1)
                    @inbounds tmp = Δpool[prow, pcol, patch, img] * m
                    for col in start_col:stop_col
                        for row in start_row:stop_row
                            @inbounds Δconv[row, col, patch, img] = tmp
                        end
                    end
                    start_row += stride
                    stop_row += stride
                end
                start_col += stride
                stop_col += stride
            end
        end
    end
end

########################################
# MaxPool
########################################

#=
Max pooling function
 2D input -> 2D pooled output for each patch of each image
 Inputs:
 A - inputs from previous layer - dim x dim x num_patches x num_obs
 Output (updated in-place):
 Z - pooled features - pool_dim x pool_dim x num_patches x num_obs
 maxinds - linear index of the max value from each patch
=#
function maxpool!{F<:AbstractFloat, T<:Integer}(Z::Array{F,4}, A::Array{F,4},
                                                maxinds::Array{T,4})

    dims = size(A)
    stride = div(size(A, 1), size(Z, 1))

    for img in indices(Z, 4)
        for patch in indices(Z, 3)
            col_start = 1
            col_end = stride
            for pcol in indices(Z, 2)
                row_start = 1
                row_end = stride
                for prow in indices(Z, 1)
                    maxval = F(-Inf)
                    maxind = T(0)
                    for incol = col_start:col_end
                        for inrow = row_start:row_end
                            @inbounds val = A[inrow, incol, patch, img]
                            if val > maxval
                                maxval = val
                                maxind = sub2ind(dims, inrow, incol, patch, img)
                            end
                        end
                    end
                    @inbounds Z[prow, pcol, patch, img] = maxval
                    @inbounds maxinds[prow, pcol, patch, img] = maxind
                    row_start += stride
                    row_end += stride
                end
                col_start += stride
                col_end += stride
            end
        end
    end
end

#=
Upsample function for max pooling - the unit which was chosen as the max
receives all the error
 Input:
 Δpool - delta of pool layer
       - pool_dim x pool_dim x num_patches x num_obs
 maxinds - linear index of the max value from each patch
 Output (updated in-place):
 Δconv - delta of previous layer (usually a convolution layer)
       - conv_dim x conv_dim x num_patches x num_obs
=#
function upsample_max!(Δconv, Δpool, maxinds)
    fill!(Δconv, 0)
    for img in indices(Δpool, 4)
        for patch in indices(Δpool, 3)
            for pcol in indices(Δpool, 2)
                for prow in indices(Δpool, 1)
                    @inbounds ind = maxinds[prow, pcol, patch, img]
                    @inbounds Δconv[ind] = Δpool[prow, pcol, patch, img]
                end
            end
        end
    end
end


using OrdinaryDiffEq
prob = ODEProblem((u,p,t) -> -u,BigFloat(1.0),(0.0,1.0))
solve(prob,Tsit5())
solve(prob,KenCarp4())


# ------------------------------------------------------------------
# Licensed under the ISC License. See LICENSE in the project root.
# ------------------------------------------------------------------

"""
    BlockWeighter(sides)
    BlockWeighter(side₁, side₂, ...)

A weighting method that assigns weights to points in spatial object
based on blocks of given `sides`. The number `n` of points inside a
block determines the weights `1/n` of these points.
"""
struct BlockWeighter{T,N} <: AbstractWeighter
  sides::SVector{N,T}
end

BlockWeighter(sides::NTuple{N,T}) where {N,T} =
  BlockWeighter{T,N}(sides)

BlockWeighter(sides::Vararg) = BlockWeighter(sides)

weight(object, weighter::BlockWeighter) =
  weight(domain(object), weighter)

function weight(object::AbstractDomain, weighter::BlockWeighter)
  p = partition(object, BlockPartitioner(weighter.sides))

  weights = Vector{Float64}(undef, nelms(object))
  for s in subsets(p)
    n = length(s)
    weights[s] .= 1/n
  end

  SpatialWeights(object, weights)
end


# by Lazaro Alonso
# original post
# https://nextjournal.com/lazarus/extracting-dominant-colours-from-pictures
using CairoMakie, Clustering, Colors, Random
using Images: load, RGB, channelview, permutedims
using Downloads: download
CairoMakie.activate!()
#let
    """
    getImg(; url = "", file = "")
    """
    function getImg(; url = "", file = "")
        try
            out = file != "" ? RGB.(load(file)) : RGB.(load(download(url)))
            return out
        catch e
            out = [RGB{Float64}(rand(3)...) for i in 1:600, j in 1:400]
            return out
        end
    end

    function getColorPalette(image; ncolors = 5)
        img_CHWa = channelview(image)
        #img_CHW = permutedims(img_CHWa, (1,3,2))
        testmat = reshape(img_CHWa, (3, size(image)[1]*size(image)[2])) #input shape
        sol = kmeans(testmat, ncolors)
        csize = counts(sol) # get the cluster sizes
        colores = sol.centers # get the cluster centers, dominat colors
        indxc = sortperm(csize)
        colores, indxc
    end
    #https://commons.wikimedia.org/wiki/Category:Ceyx_erithaca?uselang=de
    #url = "https://upload.wikimedia.org/wikipedia/commons/b/b8/Oriental_dwarf_kingfisher.jpg"
    url = "https://upload.wikimedia.org/wikipedia/commons/thumb/1/12/Oriental_dwarf_kingfisher_a_Jewel_of_India%27s_Rainforests_07.jpg/1280px-Oriental_dwarf_kingfisher_a_Jewel_of_India%27s_Rainforests_07.jpg?uselang=de"
    #path = "/path/file/file.jpg"
    img = getImg(; url = url) # ;url= url, file = file
    #img = img[150:end-40,250:end-40]
    #img = img[550:end-560,1200:end-1400]
    img = img[150:end-100,300:end-350]
    # colores, indxc = getColorPalette(img; ncolors = ncolors)
    # color = [RGB(colores[:,indxc[i]]...) for i in 1:ncolors]

    function plotPalette(;img=img, marker = :diamond, ncolors = 8)
        Random.seed!(123)
        yshift = 0.04*size(img)[1]
        colores, indxc = getColorPalette(img; ncolors = ncolors)
        color = [RGB(colores[:,indxc[i]]...) for i in 1:ncolors]
        hexNames = [hex(color[i]) for i in 1:ncolors]

        xcolor = fill(1.1*size(img)[2], ncolors)
        ycolor = LinRange(yshift,size(img)[1]-yshift,ncolors)
        fig, ax, _= image(rotr90(img), figure = (resolution = (1.45size(img)[2], size(img)[1]),),
            axis = (aspect = DataAspect(),))
        scatterlines!(ax, xcolor, ycolor, color = color, markercolor = color, marker = marker, #'■',
            markersize = 0.1*size(img)[1], linewidth = 2)
        for i in 1:ncolors
            text!(ax, "\"#"*hexNames[i]*"\"", position = (1.07xcolor[i], ycolor[i]-5),
                textsize = 0.025*size(img)[1])
        end
        xlims!(nothing, 1.45*size(img)[2])
        hidedecorations!(ax)
        hidespines!(ax)
        fig
    end
    fig = plotPalette()
    # fig = with_theme(plotPalette, theme_black())
    save(joinpath(@__DIR__, "output", "getColorPalette.png"), fig, px_per_unit = 2.0) # HIDE
#end
# output image under License
# CC BY-SA 4.0
# https://creativecommons.org/licenses/by-sa/4.0/
# due to:
# https://commons.wikimedia.org/wiki/Category:Ceyx_erithaca?uselang=de#/media/File:Oriental_dwarf_kingfisher_a_Jewel_of_India's_Rainforests_07.jpg


function printUsage()
    @printf("Usage: \n")
    @printf("Unstructured grid:  %s -u <file.lmesh> \n", PROGRAM_FILE)
    @printf("Structured grid:    %s -s numEdgeElems \n", PROGRAM_FILE)
    @printf("\nExamples:\n")
    @printf("%s -s 45\n", PROGRAM_FILE)
    @printf("%s -u sedov15oct.lmesh\n", PROGRAM_FILE)
end

function getCacheCoherencePad(::T) where T
    return div(128, sizeof(T))
end

# PAD_DIV(nbytes, align) = nbytes + div(align - 1, align)
# PAD(nbytes, align) = PAD_DIV(nbytes,align) * align

function calcElemVolume(
    x0, x1,x2, x3, x4, x5, x6, x7, y0, y1, y2, y3, y4, y5, y6, y7,
    z0, z1, z2, z3, z4, z5, z6, z7
)
    triple_product(x1, y1, z1, x2, y2, z2, x3, y3, z3) = ((x1)*((y2)*(z3) - (z2)*(y3)) + (x2)*((z1)*(y3) - (y1)*(z3)) + (x3)*((y1)*(z2) - (z1)*(y2)))
    twelveth = 1.0/12.0

    dx61 = x6 - x1
    dy61 = y6 - y1
    dz61 = z6 - z1

    dx70 = x7 - x0
    dy70 = y7 - y0
    dz70 = z7 - z0

    dx63 = x6 - x3
    dy63 = y6 - y3
    dz63 = z6 - z3

    dx20 = x2 - x0
    dy20 = y2 - y0
    dz20 = z2 - z0

    dx50 = x5 - x0
    dy50 = y5 - y0
    dz50 = z5 - z0

    dx64 = x6 - x4
    dy64 = y6 - y4
    dz64 = z6 - z4

    dx31 = x3 - x1
    dy31 = y3 - y1
    dz31 = z3 - z1

    dx72 = x7 - x2
    dy72 = y7 - y2
    dz72 = z7 - z2

    dx43 = x4 - x3
    dy43 = y4 - y3
    dz43 = z4 - z3

    dx57 = x5 - x7
    dy57 = y5 - y7
    dz57 = z5 - z7

    dx14 = x1 - x4
    dy14 = y1 - y4
    dz14 = z1 - z4

    dx25 = x2 - x5
    dy25 = y2 - y5
    dz25 = z2 - z5

    # 11 + 3*14
    volume = (
        triple_product(
            dx31 + dx72, dx63, dx20,
            dy31 + dy72, dy63, dy20,
            dz31 + dz72, dz63, dz20
        ) +
        triple_product(
            dx43 + dx57, dx64, dx70,
            dy43 + dy57, dy64, dy70,
            dz43 + dz57, dz64, dz70
        ) +
        triple_product(
            dx14 + dx25, dx61, dx50,
            dy14 + dy25, dy61, dy50,
            dz14 + dz25, dz61, dz50
        )
    )
    volume *= twelveth;
    return volume
end

calcElemVolume(x, y, z) = calcElemVolume(
    x[1], x[2], x[3], x[4], x[5], x[6], x[7], x[8],
    y[1], y[2], y[3], y[4], y[5], y[6], y[7], y[8],
    z[1], z[2], z[3], z[4], z[5], z[6], z[7], z[8]
)


"""
    Position(llh, xzy_earth, xyz_ecef)

Position information:
    - llh (latitude, longitude, height) [rad, rad, m]
    - Earth (x, y, z) [m]
    - ECEF (x, y, z) [m]

# Constructors
    PositionLLH(lat, lon, h, xe=0., ye=0., ze=0.)
    PositionECEF(x, y, z, xe=0., ye=0., ze=0.)
    PositionEarth(x, y, z, lat=0., lon=0., h=0.)
"""
struct Position
    # latitude [rad], longitude [rad], height [m]
    llh::Array{T, 1} where T<:Number
    # x, y, z [m] Earth
    xyz_earth::Array{T, 1} where T<:Number
    # x, y, z [m] ECEF
    xyz_ecef::Array{T, 1} where T<:Number
end

# Constructors
PositionLLH(lat, lon, h, xe=0., ye=0.) = Position([lat, lon, h], [xe, ye, -h], llh2ecef(lat, lon, h))
PositionECEF(x, y, z, xe=0., ye=0.) = Position(ecef2llh(x, y, z), [xe, ye, -ecef2llh(x, y, z)[3]], [x, y, z])
PositionEarth(x, y, z, lat=0., lon=0.) = Position([lat, lon, -z], [x, y, z], llh2ecef(lat, lon, -z))
Position() = Position([0, 0, 0], [0, 0, 0], llh2ecef(0, 0, 0))

get_llh(pos::Position) = pos.llh
get_xyz_earth(pos::Position) = pos.xyz_earth
get_xyz_ecef(pos::Position) = pos.xyz_ecef
get_height(pos::Position) = pos.llh[3]


VERSION >= v"0.4.0-dev+6521" && __precompile__()

module ControlToolbox

using PyCall
using Polynomials
using OrdinaryDiffEq

import Base: +, -, *, /
import Base: display, print, step, show

export StateSpace
export TransferFunction
export change_lsim_tolerances

################################################################################
#                                    Types
################################################################################

#                                 State-Space
# ==============================================================================

"""
Space state definition.
"""
immutable StateSpace
    A::Array
    B::Array
    C::Array
    D::Array

    """
    ### function StateSpace(A::Matrix, B::Matrix, C::Matrix, D::Matrix)

    Initialize a state space with the following form:

    x = Ax + Bu
    y = Cx + Du

    ##### Args

    * A: Matrix A.
    * B: Matrix B.
    * C: Matrix C.
    * D: Matrix D.

    ##### Returns

    The new state space.

    """

    function StateSpace(A::Array, B::Array, C::Array, D::Array)

        # Check if the dimensions are consistent.
        #
        # dx/dt = Ax + Bu
        #     y = Cx + Du
        #

        num_states = size(A,1)
        num_measurements = size(C,1)
        num_inputs = size(B,2)

        ( isempty(B) ) && (B = zeros(num_states); num_inputs = 1)
        ( isempty(D) ) && (D = zeros(num_measurements, num_inputs) )

        ( num_states != size(A,2) ) &&
        error("Matrix A must be square.")

        ( num_states != size(B,1) ) &&
        error("Inconsistent dimensions between matrices A and B.")

        ( num_states != size(C,2) ) &&
        error("Inconsistent dimensions between matrices A and C.")

        ( num_measurements != size(D,1) ) &&
        error("Inconsistent dimensions between matrices C and D.")

        ( (num_inputs != size(D,2)) ) &&
        error("Inconsistent dimensions between matrices B and D.")

        new(A,B,C,D)
    end
end

#                              Transfer Function
# ==============================================================================

"""
Transfer function definition.
"""

immutable TransferFunction
    num::Poly
    den::Poly

    """
    ### function TransferFunction(num::Poly, den::Poly)

    Initialize a new transfer function.

    ##### Args

    * num: Numerator (see Poly).
    * den: Denominator (see Poly).

    ##### Returns

    The new transfer function.

    """

    function TransferFunction(num::Poly, den::Poly)
        ( ( length(num.a) == 0 ) || ( length(den.a) == 0 ) ) &&
        error("Numerator or denominator cannot be empty.")

        if (num.a[end] == 0)

            # Check if the numerator is 0.
            if length(num.a) == 1
                new(Poly(0), den)
            else
                TransferFunction(Poly(num.a[1:end-1]), den)
            end
        elseif (den.a[end] == 0)
            TransferFunction(num, Poly(den.a[1:end-1]))
        else
            new(num, den)
        end
    end
end

################################################################################
#                                   Includes
################################################################################

include("aux.jl")

include("analysis/bode.jl")
include("analysis/gain_poles_zeros.jl")
include("analysis/margin.jl")
include("analysis/minreal.jl")
include("analysis/nyquist.jl")
include("analysis/pole_info.jl")
include("analysis/rlocus.jl")
include("simulation/impulse.jl")
include("simulation/lsim.jl")
include("simulation/ramp.jl")
include("simulation/step.jl")
include("plots/plot.jl")
include("types/ss.jl")
include("types/tf.jl")

end # module