using JuMP
using Cbc
using Test

export sudoku_model
export sudoku_fix_cells
export sudoku_table
export test_sudoku_model

const I = 1:9
const J = 1:9
const K = 1:9

function sudoku_model()
    model = Model()
    @variable(model, x[I, J, K], Bin)
    # --- Student's task ---
    # TODO: Implement constraints
    @constraint(model, [i in I, j in J], sum(x[i, j, k] for k in K) == 1)
    @constraint(model, [j in J, k in K], sum(x[i, j, k] for i in I) == 1)
    @constraint(model, [i in I, k in K], sum(x[i, j, k] for j in J) == 1)
    @constraint(model, [k in K, m in 1:3, n in 1:3],
        sum(sum(x[i, j, k] for j in (3m-2):(3m)) for i in (3n-2):3n) == 1)
    # TODO: implement objective function
    @objective(model, Min, 1)
    # --- end of task ---
    return model
end

function sudoku_fix_cells(model, fixed::Array{NTuple{3, Int}})
    x = model[:x]
    @constraint(model, [(i, j, k) in fixed], x[i, j, k] == 1)
end

function sudoku_table(model)
    x_value = value.(model[:x])
    return [findmax(collect(x_value[i, j, :]))[2] for i in I, j in J]
end

function test_sudoku_model()
    @info "Creating the sudoku model"
    model = sudoku_model()
    sudoku_fix_cells(model, [
        (1,2,6), (1,4,1), (1,6,4), (1,8,5), (2,3,8), (2,4,3), (2,6,5), (2,7,6),
        (3,1,2), (3,7,7), (4,1,8), (4,4,4), (4,6,7), (4,9,6), (5,3,6), (5,7,3),
        (6,1,7), (6,4,9), (6,6,1), (6,9,4), (7,1,5), (7,9,2), (8,3,7), (8,4,2),
        (8,6,6), (8,7,9), (9,2,4), (9,4,5), (9,6,8), (9,8,7)
    ])

    @info "Solving the sudoku"
    optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0)
    set_optimizer(model, optimizer)
    optimize!(model)

    answer = sudoku_table(model)
    solution = [9 6 3 1 7 4 2 5 8;
                1 7 8 3 2 5 6 4 9;
                2 5 4 6 8 9 7 3 1;
                8 2 1 4 3 7 5 9 6;
                4 9 6 8 5 2 3 1 7;
                7 3 5 9 6 1 8 2 4;
                5 8 9 7 1 3 4 6 2;
                3 1 7 2 4 6 9 8 5;
                6 4 2 5 9 8 1 7 3]
    @info "Printing the result"
    @info answer
    @test answer == solution
end


using jFEMTools
import Tensors

jF = jFEMTools

k = [Tensors.Vec{2}([1.0, 0.0]), Tensors.Vec{2}([0.0, 0.0]), Tensors.Vec{2}([0.0, 1.0])]

points = [Tensors.Vec{2}([1.0/6.0, 1.0/6.0]),
          Tensors.Vec{2}([1.0/6.0, 2.0/3.0]),
          Tensors.Vec{2}([2.0/3.0, 1.0/6.0])]

ref_verts = jF.reference_coordinates(jF.RefSimplex, Val{2})

pt = jF.mapPointsFromReference(jF.RefSimplex,k,points)

A,b = jF.get_affine_map(ref_verts, k)

x = Tensors.Vec{2}([0.0,1.0])
A*x + b


using JuMP, EAGO

                     m = Model()

                     EAGO.register_eago_operators!(m)

                     @variable(m, -1 <= x[i=1:2] <= 1)

                     @variable(m, -2.3024137037198598 <= q <= 2.756640266423038)

                     add_NL_constraint(m, :(sigmoid(0.8745011162287222 + -0.4747792679508138*sigmoid(0.20979439694353186 + 0.2523963619940135*$(x[1]) + 0.6039440929888729*$(x[2])) + 0.06822380173369114*sigmoid(-0.192203947089018 + 0.35618645260012016*$(x[1]) + -0.9380253880003773*$(x[2]))) + sigmoid(-0.5284568968024108 + 0.8782860084347535*sigmoid(0.20979439694353186 + 0.2523963619940135*$(x[1]) + 0.6039440929888729*$(x[2])) + 0.990986450920027*sigmoid(-0.192203947089018 + 0.35618645260012016*$(x[1]) + -0.9380253880003773*$(x[2]))) - $q <= 0.0))

                     @objective(m, Min, q)

                     return m


slim = 200

ht = IntegerTriangle[]

for a in 1:slim, b in a:slim, c in b:slim
    isprimheronian(a, b, c) || continue
    push!(ht, IntegerTriangle(a, b, c))
end

sort!(ht, by=x->(x.σ, x.p, x.c))

print("The number of primitive Hernonian triangles having sides ≤ ")
println(slim, " is ", length(ht))

tlim = 10
tlim = min(tlim, length(ht))

println()
println("Tabulating the first (by σ, p, c) ", tlim, " of these:")
println("    a   b   c    σ    p")
for t in ht[1:tlim]
    println(@sprintf "%6d %3d %3d %4d %4d" t.a t.b t.c t.σ t.p)
end

tlim = 210
println()
println("Tabulating those having σ = ", tlim, ":")
println("    a   b   c    σ    p")
for t in ht
    t.σ == tlim || continue
    t.σ == tlim || break
    println(@sprintf "%6d %3d %3d %4d %4d" t.a t.b t.c t.σ t.p)
end


# ---
# jupyter:
#   jupytext:
#     formats: ipynb,jl:hydrogen
#     text_representation:
#       extension: .jl
#       format_name: hydrogen
#       format_version: '1.3'
#       jupytext_version: 1.11.2
#   kernelspec:
#     display_name: Julia 1.6.1
#     language: julia
#     name: julia-1.6
# ---

# %% [markdown]
# https://discourse.julialang.org/t/ode-solvers-why-is-matlab-ode45-uncannily-stable/63052/15

# %%
using DifferentialEquations
using Plots

# %%
function wk5!(dP, P, params, t)

    #=

    The 5-Element WK with serial L
    set Ls = 0 for 4-Element WK parallel

    Formulation for DifferentialEquations.jl

    P:          solution vector (pressures p1 and p2)
    params: parameter tuple
                (Rc, Rp, C, Lp, Ls, I, q)

    I need to find a way to tranfer the function name as well
    for the time being we have to have the function in "I"

    =#

    # Split parameter tuple:
    Rc, Rp, C, Lp, Ls, I, q = params

    dP[1] = (
        -Rc / Lp * P[1]
        + (Rc / Lp - 1 / Rp / C) * P[2]
        + Rc * (1 + Ls / Lp) * didt(I, t, q)
        + I(t, q) / C
        )

    dP[2] = -1 / Rp / C * P[2] + I(t, q) / C

    return

end

# %%
# Generic Input Waveform
# max volume flow in ml/s
max_i = 425

# min volume flow in m^3/s
min_i = 0.0

T = 0.9

# Syst. Time in s
systTime = 2 / 5 * T

# Dicrotic notch time
dicrTime = 0.02

q_generic = (max_i, min_i, T, systTime, dicrTime)

function I_generic(t, q_generic)
    max_i, min_i, T, systTime, dicrTime = q_generic
    # implicit conditional using boolean multiplicator
    # sine waveform
    (
        (max_i - min_i) * sin(pi / systTime * (t % T))
        * (t % T < (systTime + dicrTime) )
        + min_i
    )
end

# %%
function didt(I, t, q)
    dt = 1e-3;
    didt = (I(t+dt, q) - I(t-dt, q)) / (2 * dt)
    return didt
end

# %%
plot(range(0, 2; length=2000), t -> didt(I_generic, t, q_generic); label="didt")

# %%
# Initial condition and time span
P0 = [0.0, 0.0]
tspan = (0.0, 30.0)

# Set parameters for Windkessel Model
Rc = 0.033
Rp = 0.6
C = 1.25
# L for serial model!
Ls = 0.01
# L for parallel
Lp = 0.02

I = I_generic
q = q_generic

p5 = (Rc, Rp, C, Lp, Ls, I, q)

problem = ODEProblem(wk5!, P0, tspan, p5)

dtmax = 1e-4

@time solutionTsit = solve(problem); GC.gc()
@time solutionTsitLowdt = solve(problem, Tsit5(), dtmax=dtmax); GC.gc()
@time solutionBS3 = solve(problem, BS3()); GC.gc()
@time solutionBS3Lowdt = solve(problem, BS3(), dtmax=dtmax); GC.gc()
@time solutionDP5 = solve(problem,DP5()); GC.gc()
@time solutionDP5Lowdt = solve(problem,DP5(), dtmax=dtmax); GC.gc()
@time solutionStiff = solve(problem, alg_hints=[:stiff]); GC.gc()

# %%
@time solutionTsit = solve(problem); GC.gc()
@time solutionTsitLowdt = solve(problem, Tsit5(), dtmax=dtmax); GC.gc()
@time solutionBS3 = solve(problem, BS3()); GC.gc()
@time solutionBS3Lowdt = solve(problem, BS3(), dtmax=dtmax); GC.gc()
@time solutionDP5 = solve(problem,DP5()); GC.gc()
@time solutionDP5Lowdt = solve(problem,DP5(), dtmax=dtmax); GC.gc()
@time solutionStiff = solve(problem, alg_hints=[:stiff]); GC.gc()

# %%
@show length(solutionTsit.t)
@show length(solutionTsitLowdt.t)
@show length(solutionBS3.t)
@show length(solutionBS3Lowdt.t)
@show length(solutionDP5.t)
@show length(solutionDP5Lowdt.t)
@show length(solutionStiff.t);

# %%
a, b = 0, 2

plot()
#plot!(t -> solutionTsit(t; idxs=1), a, b; label="Tsit")
plot!(t -> solutionTsitLowdt(t; idxs=1), a, b; label="TsitLowdt")
#plot!(t -> solutionBS3(t; idxs=1), a, b; label="BS3", ls=:dash)
plot!(t -> solutionBS3Lowdt(t; idxs=1), a, b; label="BS3Lowdt", ls=:dash)
#plot!(t -> solutionDP5(t; idxs=1), a, b; label="DP5", ls=:dashdot)
plot!(t -> solutionDP5Lowdt(t; idxs=1), a, b; label="DP5Lowdt", ls=:dashdot)
plot!(t -> solutionStiff(t; idxs=1), a, b; label="Stiff", ls=:dot, lw=1.5)

# %%
a, b = 0, 2

plot()
plot!(t -> solutionTsit(t; idxs=1), a, b; label="Tsit")
#plot!(t -> solutionTsitLowdt(t; idxs=1), a, b; label="TsitLowdt")
plot!(t -> solutionBS3(t; idxs=1), a, b; label="BS3", ls=:dash)
#plot!(t -> solutionBS3Lowdt(t; idxs=1), a, b; label="BS3Lowdt", ls=:dash)
plot!(t -> solutionDP5(t; idxs=1), a, b; label="DP5", ls=:dashdot)
#plot!(t -> solutionDP5Lowdt(t; idxs=1), a, b; label="DP5Lowdt", ls=:dashdot)
plot!(t -> solutionStiff(t; idxs=1), a, b; label="Stiff", ls=:dot, lw=1.5)

# %%
a, b = 28, 30

plot()
plot!(t -> solutionTsit(t; idxs=1), a, b; label="Tsit")
#plot!(t -> solutionTsitLowdt(t; idxs=1), a, b; label="TsitLowdt")
plot!(t -> solutionBS3(t; idxs=1), a, b; label="BS3", ls=:dash)
#plot!(t -> solutionBS3Lowdt(t; idxs=1), a, b; label="BS3Lowdt", ls=:dash)
plot!(t -> solutionDP5(t; idxs=1), a, b; label="DP5", ls=:dashdot)
#plot!(t -> solutionDP5Lowdt(t; idxs=1), a, b; label="DP5Lowdt", ls=:dashdot)
plot!(t -> solutionStiff(t; idxs=1), a, b; label="Stiff", ls=:dot, lw=1.5)

# %%


module DS

export data_splitting, DS_CI

using Random
using Distributions
using IntervalSets
using LinearAlgebra: I
include("../data.jl")
using .DataUtil
include("sfs.jl")
using .SFS

function data_splitting(𝐗, 𝐲, k, 𝚺; ratio=0.5)
    # data splitting
    idx = shuffle(1:length(𝐲)) # randomize data
    𝐗_FS, 𝐗_SI = train_val_split(𝐗[idx, :]; ratio=ratio)
    𝐲_FS, 𝐲_SI = train_val_split(𝐲[idx]; ratio=ratio)

    # feature selection
    𝐴_obs, _ = sfs(𝐗_FS, 𝐲_FS, k)

    # statistical inference (hypothesis test)
    𝐗_obs = 𝐗_SI[:, 𝐴_obs]
    𝜼 = 𝐗_obs / (𝐗_obs' * 𝐗_obs)
    p_values = Vector{Float64}(undef, length(𝐴_obs)) # p-value list
    for j = 1:length(𝐴_obs)
        𝜼ⱼ = 𝜼[:, j]
        z_obs = 𝜼ⱼ' * 𝐲_SI
        σ²_z = 𝜼ⱼ' * 𝚺 * 𝜼ⱼ # variance of z_obs
        𝜋_j = cdf(Normal(0, √(σ²_z)), z_obs)
        p_values[j] = 2 * min(𝜋_j, 1 - 𝜋_j)
    end
    return (𝐴_obs, p_values)
end

function DS_CI(𝐗, 𝐲, k, 𝚺; ratio=0.5, α=0.05)
    # data splitting
    idx = shuffle(1:length(𝐲)) # randomize data
    𝐗_FS, 𝐗_SI = train_val_split(𝐗[idx, :]; ratio=ratio)
    𝐲_FS, 𝐲_SI = train_val_split(𝐲[idx]; ratio=ratio)

    # feature selection
    𝐴_obs, _ = sfs(𝐗_FS, 𝐲_FS, k)

    # statistical inference (estimate confidence interval)
    Φ = cquantile(Normal(0, 1), α / 2)
    𝐗_obs = 𝐗_SI[:, 𝐴_obs]
    𝜼 = 𝐗_obs / (𝐗_obs' * 𝐗_obs)
    CIs = Vector{ClosedInterval{Float64}}(undef, length(𝐴_obs)) # CI list
    for j = 1:length(𝐴_obs)
        𝜼ⱼ = 𝜼[:, j]
        z_obs = 𝜼ⱼ' * 𝐲_SI
        σ_z = √(𝜼ⱼ' * 𝚺 * 𝜼ⱼ) # standard deviation of z_obs
        CIs[j] = (z_obs - σ_z * Φ)..(z_obs + σ_z * Φ)
    end
    return (𝐴_obs, CIs)
end

function FPR_experiment(β₀, 𝛃, N, k; α=0.05, denominator_upper=100, iter_size=5, ratio=0.5)
    H_0 = filter(i -> iszero(𝛃[i]), 1:length(𝛃)) # {j ∈ {1,...,d} | H_{0,j} is true}
    FPR = Array{Float64}(undef, length(H_0), iter_size)
    for l = 1:iter_size
        numerator = zeros(Int, length(H_0))
        denominator = zeros(Int, length(H_0))
        for _ = 1:denominator_upper
            𝐗, 𝐲_obs = centering(make_dataset(β₀, 𝛃, N)...)
            𝐴_obs, p_values = data_splitting(𝐗, 𝐲_obs, k, I; ratio=ratio)
            for (j, p_value) in zip(𝐴_obs, p_values)
                for (i, j′) in enumerate(H_0)
                    if j == j′ # H_{0,j} is true
                        numerator[i] += p_value < α ? 1 : 0
                        denominator[i] += 1
                        break
                    end
                end
            end
        end
        FPR[:, l] = numerator ./ denominator
    end
    return FPR
end

function TPR_experiment(β₀, 𝛃, N, k; α=0.05, denominator_upper=100, iter_size=5, ratio=0.5)
    H_1 = filter(i -> !iszero(𝛃[i]), 1:length(𝛃)) # {j ∈ {1,...,d} | H_{1,j} is true}
    TPR = Array{Float64}(undef, length(H_1), iter_size)
    for l = 1:iter_size
        numerator = zeros(Int, length(H_1))
        denominator = zeros(Int, length(H_1))
        for _ = 1:denominator_upper
            𝐗, 𝐲_obs = centering(make_dataset(β₀, 𝛃, N)...)
            𝐴_obs, p_values = data_splitting(𝐗, 𝐲_obs, k, I; ratio=ratio)
            for (j, p_value) in zip(𝐴_obs, p_values)
                for (i, j′) in enumerate(H_1)
                    if j == j′ # H_{1,j} is true
                        numerator[i] += p_value < α ? 1 : 0
                        denominator[i] += 1
                        break
                    end
                end
            end
        end
        TPR[:, l] = numerator ./ denominator
    end
    return TPR
end

end # module DS


using BenchmarkTools
using MarchingCubes
using PlyIO
using Test

@testset "march" begin
    mc = MarchingCubes.scenario()

    march(mc)
    @test length(mc.vertices) == 11_333
    @test length(mc.triangles) == 22_620
    MarchingCubes.output(PlyIO, mc, tempname())

    march_legacy(mc)
    @test length(mc.vertices) == 11_333
    @test length(mc.triangles) == 22_732
    MarchingCubes.output(PlyIO, mc, tempname())
end

@testset "normalize" begin
    nx, ny, nz = 10, 20, 30
    start_x, stop_x = -0.1, 0.1
    start_y, stop_y = -10, 10
    start_z, stop_z = 100, 200
    mc = MC(
        rand(nx, ny, nz) .- 0.5,
        Int;
        x = collect(Float64, range(start_x, stop_x, length = nx)),
        y = collect(Float64, range(start_y, stop_y, length = ny)),
        z = collect(Float64, range(start_z, stop_z, length = nz)),
    )
    for callable ∈ (march, march_legacy)
        callable(mc)
        @test all(map(v -> start_x ≤ v[1] ≤ stop_x, mc.vertices))
        @test all(map(v -> start_y ≤ v[2] ≤ stop_y, mc.vertices))
        @test all(map(v -> start_z ≤ v[3] ≤ stop_z, mc.vertices))
    end
end

@testset "plane" begin
    mc = MarchingCubes.scenario(case = :plane)
    bytes = @allocated march(mc)
    @test bytes == 0
    @test length(mc.vertices) == 7_038
    @test length(mc.triangles) == 13_720
end

@testset "sphere" begin
    mc = MarchingCubes.scenario(case = :sphere)
    bytes = @allocated march(mc)
    @test bytes == 0
    @test length(mc.vertices) == 792
    @test length(mc.triangles) == 1572
end

@testset "hyperboloid" begin
    mc = MarchingCubes.scenario(case = :hyperboloid)
    bytes = @allocated march(mc)
    @test bytes == 0
    @test length(mc.vertices) == 12_297
    @test length(mc.triangles) == 24_118
end

@testset "isovalue" begin
    dat = Float32[(x - 3)^2 + (y - 3)^2 + (z - 3)^2 for x ∈ 1:5, y ∈ 1:5, z ∈ 1:5]

    m1 = MC(dat)
    march(m1, 5.0)

    m2 = MC(dat .- 5.0)
    march(m2)

    @test all(m1.triangles .≈ m2.triangles)
    @test all(m1.vertices .≈ m2.vertices)
    @test all(m1.normals .≈ m2.normals)
end


"""
    mlaplacian(img; dims=coords_spatial(img), r=1)
    mlaplacian(img, se)

Calculate morphological laplacian of the image.

The morphological lapalacian operator is defined as `∇⁺A - ∇⁻A` where `∇⁺A` is the external
gradient `A - erode(A, se)` and `∇⁻A` is the internal gradient `dilate(A, se) - A`. Thus is
`dilate(A, se) + erode(A, se) - 2A`.

`se` is the structuring element that defines the neighborhood of the image. See
[`strel`](@ref) for more details. If `se` is not specified, then it will use the
[`strel_box`](@ref) with an extra keyword `dims` to control the dimensions to filter.

# Examples

```jldoctest; setup = :(using ImageMorphology)
julia> img = falses(7, 7); img[3:5, 3:5] .= true; img[4, 4] = false; img
7×7 BitMatrix:
 0  0  0  0  0  0  0
 0  0  0  0  0  0  0
 0  0  1  1  1  0  0
 0  0  1  0  1  0  0
 0  0  1  1  1  0  0
 0  0  0  0  0  0  0
 0  0  0  0  0  0  0

julia> Int.(mlaplacian(img))
7×7 Matrix{$Int}:
 0  0   0   0   0  0  0
 0  1   1   1   1  1  0
 0  1  -1  -1  -1  1  0
 0  1  -1   1  -1  1  0
 0  1  -1  -1  -1  1  0
 0  1   1   1   1  1  0
 0  0   0   0   0  0  0

julia> Int.(mlaplacian(img, strel_diamond(img))) # use diamond shape SE
7×7 Matrix{$Int}:
 0  0   0   0   0  0  0
 0  0   1   1   1  0  0
 0  1  -1  -1  -1  1  0
 0  1  -1   1  -1  1  0
 0  1  -1  -1  -1  1  0
 0  0   1   1   1  0  0
 0  0   0   0   0  0  0
```

## See also

- [`mlaplacian!`](@ref) is the in-place version of this function.
- [`mgradient`](@ref) for the gradient operator.
- `ImageBase.FiniteDiff` also provides a few finite difference operators, including `fdiff`,
  `fgradient`, etc.
"""
function mlaplacian(img; dims=coords_spatial(img), r=nothing)
    return mlaplacian(img, strel_box(img, dims; r))
end
function mlaplacian(img::AbstractArray{T}, se) where {T}
    out = similar(img, maybe_floattype(T))
    buffer = similar(img, maybe_floattype(T))
    return mlaplacian!(out, img, se, buffer)
end

"""
    mlaplacian!(out, img, buffer; [dims], [r])
    mlaplacian!(out, img, se, buffer)

The in-place version of [`mlaplacian`](@ref) with input image `img` and output image `out`.
The intermediate erosion result is stored in `buffer`.
"""
function mlaplacian!(out, img, buffer; dims=coords_spatial(img), r=nothing)
    return mlaplacian!(out, img, strel_box(img, dims; r), buffer)
end
function mlaplacian!(out, img, se, buffer)
    require_symmetric_strel(se)
    dilate!(out, img, se)
    erode!(buffer, img, se)
    @. out = out + buffer - 2img
    return out
end
mlaplacian!(out::AbstractArray{<:Color3}, img, se, buffer=nothing) = throw(ArgumentError("color image is not supported"))


# Copyright 2021 Gravifer
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

using Pkg
Pkg.activate(normpath(joinpath(@__DIR__, "../..")))
# using Revise
using BenchmarkTools
using Plots
using OffsetArrays

myblue = RGB(0.368417, 0.506779, 0.709798) # <==> 94, 129, 181 (the default style in Wolfram Mathematica)

"""
    yt0012(ξ)

Compute ``yₜ`` (i.e., the half-thickness relative to the chord)
of NACA0012 airfoils from the normalized chord position ``ξ=x/c``.

Additionally defined upon ``[1, 1.005]`` for a 2-order smooth closure.

See also: https://en.wikipedia.org/wiki/NACA_airfoil

TODO: generalize for 00xx airfoils; furthermore, for all 4-digit airfoils.

# Examples
```jldoctest
julia> yt0012(0.13)
0.051193437032115506
```
"""
Base.@pure function yt0012(ξ::T where {T<:Real})
    ε = 0.0050544107511712
    return 5 * 0.12 *  (
        if    0 <= ξ <= 1
        0.2969 * sqrt(ξ)  -
        0.1260 *      ξ   -
        0.3516 *      ξ^2 +
        0.2843 *      ξ^3 -
        0.1015 *      ξ^4
        elseif 1 < ξ <= 1+ε # 2-order smooth joining of the tail edge.
        0.03423616658680458 * sqrt(1+ε - ξ)   +
        0.12491972188290168 *     (1+ε - ξ)   +
        11.579398976610927   *     (1+ε - ξ)^2 +
        12.21202728162769    *     (1+ε - ξ)^3
        else
            0
        end)
end

const Mx, My = 100+1, 20+1

include(normpath(joinpath(@__DIR__, "../../src/misc-util.jl")))
using .__CFD2021__misc_util__: tuplejoin
"""
- usage of ``tuplejoin``:
```jldoctest
  julia> temp_v = ([0,0.5,1], [0,0.5,1])
  julia> temp_t = (axes(v) for v in temp_v)
  julia> @btime tuplejoin(temp_t...)
```
"""

include(normpath(joinpath(@__DIR__, "../src/transfinite-interpolate.jl")))
import .transfinite_interpolate: transfinite_interpolate_2d!, transfinite_interpolate_2d

"""
- Try to use the in-place version when applicable;
  there is a tremendous performance boost.
```jldoctest
  julia> v_lo = ([LinRange(0, 1, 10^4)...], [LinRange(0, 1, 10^4)...]); v_hi = ([LinRange(1, 2, 10^4)...], [LinRange(1, 2, 10^4)...])

  julia> interpolated_array = @btime Array{Float64}(undef, (length(v) for v in v_lo)...)
  20.500 μs (7 allocations: 762.94 MiB)

  julia> @btime transfinite_interpolate_2d!(interpolated_array, v_lo, v_hi)
  1.535 s (1 allocation: 96 bytes)

  julia> res = @btime transfinite_interpolate_2d(v_lo, v_hi)
  7.581 s (100000005 allocations: 2.98 GiB)
```
"""

"""
- example of a cartesian grid;
```jldoctest
  x_lo = ([LinRange(0, 1, Mx)...], [LinRange(0, 0, My)...]); x_hi = ([LinRange(0, 1, Mx)...], [LinRange(1, 1, My)...]);
  y_lo = ([LinRange(0, 0, Mx)...], [LinRange(0, 1, My)...]); y_hi = ([LinRange(1, 1, Mx)...], [LinRange(0, 1, My)...]);
```
"""

"""
- example of a half-O grid aroundNACA0012;
"""
x_lo = ([sinpi(ξ)^2 for ξ in LinRange(0, 0.5, Mx)], -exp.(LinRange(0, log(6), My)) .+ 1);
y_lo = (yt0012.(x_lo[1]), [LinRange(0, 0, My)...]);
x_hi = (5cospi.(LinRange(1, 0, Mx)), +exp.(LinRange(0, log(5), My)));
y_hi = (5sinpi.(LinRange(1, 0, Mx)), [LinRange(0, 0, My)...]);

"""
- example of a whole-O grid aroundNACA0012;
```jldoctest
  x_lo = ([         sinpi.(LinRange(-.5,0, Mx)).^2;          sinpi.(LinRange(0, .5, Mx)).^2],
             exp.(LinRange(0, log(5), My)                  )   );
  y_lo = ([-yt0012.(sinpi.(LinRange(-.5,0, Mx)).^2); yt0012.(sinpi.(LinRange(0, .5, Mx)).^2)],
          [LinRange(0, 0, My)...]);
  x_hi = (5cospi.(LinRange( 2, 0, 2Mx)),
             exp.(LinRange(0, log(5), My)                  )   );
  y_hi = (5sinpi.(LinRange( 2, 0, 2Mx)),
          [LinRange(0, 0, My)...]);
```
"""

p = plot(; xlims=(-5, 5), ylims=(-5, 5), aspect_ratio=:equal)
# p = plot(xlims = (-.6, .6), ylims = (0, .5), aspect_ratio = :equal)
interpolated_x = Array{Float64}(undef, (length(v) for v in x_lo)...);
interpolated_y = Array{Float64}(undef, (length(v) for v in y_lo)...);
@btime transfinite_interpolate_2d!(interpolated_x, x_lo, x_hi)
@btime transfinite_interpolate_2d!(interpolated_y, y_lo, y_hi)
for u in axes(interpolated_x)[begin]  plot!(p,interpolated_x[u,:], interpolated_y[u,:], label = nothing, color = myblue); end
for u in axes(interpolated_x)[ end ]  plot!(p,interpolated_x[:,u], interpolated_y[:,u], label = nothing, color = myblue); end
# for u in axes(interpolated_x)[begin]  plot!(p,interpolated_x[u,:],-interpolated_y[u,:], label = nothing, color = myblue); end
# for u in axes(interpolated_x)[ end ]  plot!(p,interpolated_x[:,u],-interpolated_y[:,u], label = nothing, color = myblue); end
plot!(p, yt0012, 0, 1; label=nothing, color=:red)
display(p) # savefig(normpath(joinpath(@__DIR__, "img/tf-hO.svg")))


abstract type AbstractInverseLaplaceMethod end
struct CME <: AbstractInverseLaplaceMethod end
struct Talbot <: AbstractInverseLaplaceMethod end
struct Euler <: AbstractInverseLaplaceMethod end
struct Gaver <: AbstractInverseLaplaceMethod end

const cme = CME()
const talbot = Talbot()
const euler = Euler()
const gaver = Gaver()

function ilt(m::AbstractInverseLaplaceMethod, f_s, t, M)
    error("This inverse laplace transform method is unknown. Supported ones: cme, euler, gaver, talbot");
end

ilt(f_s::Function, T::Real, maxFnEvals::Int) = ilt(cme,f_s,T,maxFnEvals)

function ilt(m::CME,f_s::Function, T::Real, maxFnEvals::Int)
    # find the most steep CME satisfying maxFnEvals
    params = CMEParams[1];
    for i=2:length(CMEParams)
        if CMEParams[i]["cv2"]<params["cv2"] && (CMEParams[i]["n"]+1)<=maxFnEvals
            params = CMEParams[i];
        end
    end
    # compute eta and beta parameters
    eta = [params["c"]*params["mu1"]  params["a"]'*params["mu1"] + 1im*params["b"]'*params["mu1"]][:];
    beta = [1; (1).+1im*collect(1:params["n"])*params["omega"]][:] * params["mu1"];
    return 1 ./T .* sum(real(eta .* f_s.(beta./T)))
end

function ilt(m::Euler, f_s::Function, T::Real, maxFnEvals::Int)
    n_euler = Int(floor((maxFnEvals-1)/2))
    eta = [0.5 ones(1, n_euler) zeros(1, n_euler-1) 2^-Float64(n_euler)]
    for k = 1:n_euler-1
#        eta(2*n_euler-k + 1) = eta(2*n_euler-k + 2) + 2^-n_euler * nchoosek(n_euler, k);
        eta[2*n_euler-k + 1] = eta[2*n_euler-k + 2] + exp(sum(log.(1:n_euler)) .- n_euler*log(2) .- sum(log.(1:k)) - sum(log.(1:(n_euler-k))))
    end
    k = collect(0:2*n_euler)'
    beta = n_euler*log(10)/3 .+ 1im*pi*k
    eta  = (10^((n_euler)/3))*(1 .-mod.(k, 2)*2) .* eta
    return 1 ./T .* sum(real(eta .* f_s.(beta./T)))
end

function ilt(m::Gaver,f_s::Function, T::Real, maxFnEvals::Int)
    if mod(maxFnEvals,2)==1
        maxFnEvals = maxFnEvals - 1
    end
    ndiv2 = maxFnEvals/2
    eta = zeros(1,maxFnEvals)
    beta = zeros(1,maxFnEvals)
    for k = 1:maxFnEvals # itration index
        inside_sum = 0.0
        for j = floor((k+1)/2):min(k,ndiv2) #  eta summation index
#            inside_sum=inside_sum+((j^((ndiv2+1))/factorial(ndiv2))*(nchoosek(ndiv2, j)*nchoosek(2*j, j)*nchoosek(j, k-j)));
            inside_sum=inside_sum+exp((ndiv2+1)*log(j) - sum(log.(1:(ndiv2-j))) + sum(log.(1:2*j)) - 2*sum(log.(1:j)) - sum(log.(1:(k-j))) - sum(log.(1:(2*j-k))))
        end
        eta[k]=log(2.0)*(-1)^(k+ndiv2)*inside_sum
        beta[k] = k * log(2.0)
    end
    return 1 ./T .* sum(real(eta .* f_s.(beta./T)))
end

function ilt(m::Talbot, f_s::Function, t::Real, M::Int)

    # ilt = talbot_inversion(f_s, t, [M])
    #
    # Returns an approximation to the inverse Laplace transform of function
    # handle f_s evaluated at a value t using Talbot's method as
    # summarized in the source below.
    #
    # f_s: a function of s
    # t:   a time at which to evaluate f(t)
    # M:   Optional, number of terms to sum for each t (64 is a good guess);
    #      highly oscillatory functions require higher M, but this can grow
    #      unstable; see test_talbot.m for an example of stability.
    #
    # Abate, Joseph, and Ward Whitt. "A Unified Framework for Numerically
    # Inverting Laplace Transforms." INFORMS Journal of Computing, vol. 18.4
    # (2006): 408-421. Print.
    #
    # Also online: http://www.columbia.edu/~ww2040/allpapers.html.

        # Vectorized Talbot's algorithm

        k = 1:(M-1); # Iteration index

        # Calculate delta for every index.
        delta = zeros(Complex,1, M);
        delta[1] = 2*M/5;
        delta[2:end] = 2*pi/5 * k .* (cot.(pi/M*k).+1im);

        # Calculate gamma for every index.
        gamma = zeros(Complex,1, M);
        gamma[1] = 0.5*exp.(delta[1]);
        gamma[2:end] =    (1 .+ 1im*pi/M*k.*(1 .+cot.(pi/M*k).^2)-1im*cot.(pi/M*k)) .* exp.(delta[2:end]);

        # Finally, calculate the inverse Laplace transform for each given time.
        ilt = 0.4./t .* sum(real( gamma .* f_s.(delta./t)));

        return ilt
end


using BenchmarkTools
#
#	tool to replace params with values in an expression
#

expr_replace!(ex, s, v) = ex
expr_replace!(ex::Symbol, s, v) = s == ex ? v : ex

function expr_replace!(ex::Expr, s, v)
    for i=1:length(ex.args)
        ex.args[i] = expr_replace!(ex.args[i], s, v)
    end
    return ex
end

expr_replace(ex, s, v) = expr_replace!(copy(ex), s, v)

function expr_replace(ex, nt)
    e = copy(ex)
    k=keys(nt)
    v=values(nt)
    for i in 1:length(nt)
        expr_replace!(e, k[i], v[i])
    end
    return e
end

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

t = collect(0.0:0.1:10000)
params = [1.0, 1.0, 1.0, 1.0, 1.0]
nt=(G₀=1., G₁=1., η₁=1., G₂=1., η₂=1.)

#
#	Vectorised function
#

function G_vec(t::Vector{T}, params::Vector{T}) where T<:Real
   G₀, G₁, η₁, G₂, η₂ = params
   G = G₀ .+ G₁*exp.(-t*G₁/η₁) .+ G₂*exp.(-t*G₂/η₂)
end

println("test vectorised version of function")
@btime G_vec($t, $params)
println("\n")

#
#	Broadcasting functions
#

function G_single(t::T, params::Array{T,1}) where T<:Real
   G₀, G₁, η₁, G₂, η₂ = params
   G = G₀ + G₁*exp(-t*G₁/η₁) + G₂*exp(-t*G₂/η₂)
end

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

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

#
#	Array comprehension
#

function G_arraycomp(t::Vector{T}, params::Vector{T}) where T<:Real
    [G_single(ti, params) for ti in t]
end

println("test using array comprehension")
@btime G_arraycomp($t, $params)
println("\n")

#
#	parameter substitution
#

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

function G_arrayexp(ta::Vector{T}, nt) where T<:Real
	e2 = expr_replace(e, nt)
    @eval f(t::T) where T<:Real = begin  $e2   end
    #@eval [f(t) for t in $ta]
    @eval broadcast(f,$ta)
end

println("test using local function from expression with parameter replacement")
@btime G_arrayexp($t, $nt)
println("\n")

println("test using array comprehension in external function from expression with parameter replacement")

function paramsubst(e,nt)
	e2 = expr_replace(e, nt)
    @eval f(t::T) where T<:Real = begin  $e2   end
    @eval g(ta::Vector{T}) where T<:Real = begin [f(t) for t in ta] end
    #g = ta -> [f(t) for t in ta]
    return g
end

@btime paramsubst(e,nt)
g=paramsubst(e,nt)

@btime g($t)
println("\n")

println("test using array broadcast in external function from expression with parameter replacement")
    @eval g2(ta::Vector{T}) where T<:Real = begin broadcast(f, ta) end
    #g2 = t -> broadcast(f, t)

@btime g2($t)
println("\n")

println("test using array comprehension with external lambda function")
    gl = ta -> G_arraycomp(  ta, params)

@btime gl($t)
println("\n")

println("test using array braodcast with external lambda function")
    gl2 = ta -> broadcast(G_single, ta, (params,))

@btime gl2($t)
println("\n")

#
#	for loop
#

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

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

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

    out
end

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

println("Benchmarks complete")


module Day2

export day2

struct PassWdRules
    range::Tuple{Int,Int}
    ch::Char
    passwd::String
end

function count(char, str)
    counter = 0
    for c in str
        if c == char
            counter += 1
        end
    end
    counter
end

function parse_rule(str)
    v = split(str,' ')
    r = map(t -> parse(Int,t), split(v[1],'-'))
    PassWdRules(tuple(r...), v[2][1], strip(v[3]))
end

function day2(::Val{:a}, filename)
    w = map(parse_rule, readlines(filename))
    counter = 0
    for p in w
        if p.range[1] <= count(p.ch, p.passwd) <= p.range[2]
            counter += 1
        end
    end
    counter
end

function day2(::Val{:b}, filename)
    w = map(parse_rule, readlines(filename))
    counter = 0
    for p in w
        if xor(p.passwd[p.range[1]] == p.ch, p.passwd[p.range[2]] == p.ch)
            counter += 1
        end
    end
    counter
end

end


## --- Load (and install if necessary) the Chron.jl package

    try
        using Chron
    catch
        using Pkg
        Pkg.add("Chron")
        using Chron
    end

    using Statistics, StatsBase, DelimitedFiles, SpecialFunctions
    using KernelDensity: kde
    using Plots; gr(); default(fmt = :png)

## --- Test Bayesian eruption age estimation with a synthetic dataset

    dt_sigma = 30; # Timescale relative to analytical uncertainty
    N = 42; # Number of zircons

    # Draw set of pseudorandom ages from MELTS volcanic zircon distribution,
    # with true minimum == 0 and analytical sigma == 1
    ages = draw_from_distribution(MeltsVolcanicZirconDistribution,N).*dt_sigma + randn(N);
    uncert = ones(N)

    # Calculate the weighted mean age
    (wx, wsigma, mswd) = awmean(ages, uncert)

    h = plot(ylabel="Time before eruption (sigma)", xlabel="N", fg_color_legend=:white, legend=:topleft)
    plot!(h,1:length(ages),sort(ages),yerror=uncert*2,seriestype=:scatter, label="Synthetic zircon ages")
    plot!(h,collect(xlims()), [0,0], label="Eruption age")
    plot!(h,collect(xlims()), [wx,wx], label="Weighted mean, MSWD $(round(mswd,sigdigits=2))")

## --- Calculate bootstrapped $\ \mathcal{\vec{f}}_{xtal}(t_r)$

    # Bootstrap the crystallization distribution,
    # accounting for any expected analytical "tail" beyond eruption/deposition
    dist = BootstrapCrystDistributionKDE(ages, uncert)
    dist ./= mean(dist) # Normalize

    # Plot bootstrapped distribution
    plot(range(0,1.3,length=length(dist)),dist, label="bootstrapped", ylabel="Probability Density", xlabel="Time before eruption (scaled)", legend=:bottomleft, fg_color_legend=:white)
    plot!(range(0,1,length=100),MeltsVolcanicZirconDistribution,label="original")

## ---  Run MCMC to estimate eruption/deposition age distribution of synthetic dataset
    # Configure model
    nsteps = 200000 # Length of Markov chain
    burnin = 100000 # Number of steps to discard at beginning of Markov chain

    # Run MCMC
    tminDist = metropolis_min(nsteps,dist,ages,uncert)

    # Print results
    AgeEst = mean(tminDist[burnin:end]);
    AgeEst_sigma = std(tminDist[burnin:end]);
    print("\nEstimated eruption age of synthetic dataset:\n $AgeEst +/- $(2*AgeEst_sigma) Ma (2σ)\n (True synthetic age 0 Ma)")

    # Plot results
    h = histogram(tminDist[burnin:end],nbins=50,label="Posterior distribution",xlabel="Eruption Age (Ma)",ylabel="N")
    plot!(h,[0,0],collect(ylims()),line=(3),label="True (synthetic) age",fg_color_legend=:white)
    plot!(h,[wx,wx],collect(ylims()),line=(3),label="Weighted mean age",legend=:topright)
    display(h)

## --- Estimate eruption age for real zircon data
    # The example dataset here is from [Wotzlaw et al., 2013](https://doi.org/10.1130/G34366.1) FCT+MLX
    # Age and one-sigma uncertainty.
    ages = [28.196, 28.206, 28.215, 28.224, 28.232, 28.241, 28.246, 28.289, 28.308, 28.332, 28.341, 28.359, 28.379, 28.383, 28.395, 28.4, 28.405, 28.413, 28.415, 28.418, 28.42, 28.422, 28.428, 28.452, 28.454, 28.454, 28.458, 28.468, 28.471, 28.475, 28.482, 28.485, 28.502, 28.52, 28.551, 28.561, 28.565, 28.582, 28.584, 28.586, 28.611, 28.638, 28.655]
    uncert = [0.019, 0.0155, 0.019, 0.0215, 0.018, 0.023, 0.013, 0.029, 0.0175, 0.0315, 0.0095, 0.0245, 0.0255, 0.0175, 0.0235, 0.014, 0.021, 0.022, 0.0125, 0.0135, 0.016, 0.0195, 0.0175, 0.0125, 0.01, 0.014, 0.015, 0.0205, 0.0155, 0.011, 0.0115, 0.0185, 0.0255, 0.014, 0.0125, 0.013, 0.015, 0.014, 0.012, 0.016, 0.0215, 0.0125, 0.0215]

    # Sort by age (just to make rank-order plots prettier)
    t = sortperm(ages)
    ages = ages[t];
    uncert = uncert[t];

## --- Calculate bootstrapped $\ \mathcal{\vec{f}}_{xtal}(t_r)$
    # Bootstrap the crystallization distribution,
    # accounting for any expected analytical "tail" beyond eruption/deposition
    dist = BootstrapCrystDistributionKDE(ages, uncert)
    dist ./= mean(dist) # Normalize

    # Plot bootstrapped distribution
    plot(range(0,1,length=length(dist)),dist, label="bootstrapped f_xtal", ylabel="Probability Density", xlabel="Time before eruption (unitless)", fg_color_legend=:white)

## --  Run MCMC to estimate eruption age
    # Configure model
    nsteps = 400000; # Length of Markov chain
    burnin = 150000; # Number of steps to discard at beginning of Markov chain

    # Run MCMC
    tminDist = metropolis_min(nsteps,dist,ages,uncert);
    # Consider also:
    # tminDist = metropolis_min(nsteps,UniformDistribution,ages,uncert);
    # tminDist = metropolis_min(nsteps,TriangularDistribution,ages,uncert);
    # tminDist = metropolis_min(nsteps,HalfNormalDistribution,ages,uncert);
    # tminDist = metropolis_min(nsteps,MeltsVolcanicZirconDistribution,ages,uncert);

    # Print results
    AgeEst = mean(tminDist[burnin:end]);
    AgeEst_sigma = std(tminDist[burnin:end]);
    print("\nEstimated eruption age:\n $AgeEst +/- $(2*AgeEst_sigma) Ma (2σ)\n")

    # Plot results
    h = histogram(tminDist[burnin:end],nbins=100,label="Posterior distribution",xlabel="Eruption Age (Ma)",ylabel="N",legend=:topleft,fg_color_legend=:white)
    # plot!(h,[wx,wx],collect(ylims()),line=(3),label="Weighted mean age",legend=:topleft)
    display(h)

## ---  Plot eruption age estimate relative to rank-order plot of raw zircon ages
    h = plot(ylabel="Age (Ma)", xlabel="N", legend=:topleft, fg_color_legend=:white)
    plot!(h,1:length(ages),ages,yerror=uncert*2,seriestype=:scatter, markerstrokecolor=:auto, label="Observed ages")
    plot!(h,[length(ages)],[AgeEst],yerror=2*AgeEst_sigma, markerstrokecolor=:auto, label="Bayesian eruption age estimate",color=:red)
    plot!(h,collect(xlims()),[AgeEst,AgeEst],color=:red, label="")

## ---


struct Domain{FT<:Real}
    α :: FT
    β :: FT
    g :: FT
    H :: FT
end

function DomainProperties(
	FT = Float64;
	α :: Real = 0.2,
	β :: Real = 0.5,
	g :: Real = 1,
	H :: Real = 1
)
	return Domain{FT}(α,β,g,H)

end

function show(io::IO, D::Domain{FT}) where FT <: Real
    print(
		io,
		"The Domain{$FT} properties are as follows:\n",
		"     Rayleigh Damping (α) : ", D.α, '\n',
		"    Coriolis Gradient (β) : ", D.β, '\n',
		"              Gravity (g) : ", D.g, '\n',
		"       Surface Height (H) : ", D.H, '\n'
	)
end


# print arrays in pretty way

function printarr(io::IO, a::AbstractArray)
    Base.with_output_limit(()->Base.showarray(io, a, header=false, repr=false))
end

printvec(io::IO, a::AbstractVector) = printarr(io, a')

printarrln(io::IO, a::AbstractArray) = (printarr(io, a); println(io))
printvecln(io::IO, a::AbstractVector) = (printvec(io, a); println(io))

# centralize

centralize(x::AbstractVector, m::AbstractVector) = (isempty(m) ? x : x - m)
centralize(x::AbstractMatrix, m::AbstractVector) = (isempty(m) ? x : x .- m)

decentralize(x::AbstractVector, m::AbstractVector) = (isempty(m) ? x : x + m)
decentralize(x::AbstractMatrix, m::AbstractVector) = (isempty(m) ? x : x .+ m)

# get a full mean vector

fullmean(d::Int, mv::Vector{T}) where T = (isempty(mv) ? zeros(T, d) : mv)

preprocess_mean(X::AbstractMatrix{T}, m) where T<:Real =
    (m === nothing ? vec(mean(X, dims=2)) : m == 0 ? T[] :  m)

# choose the first k values and columns
#
# S must have fields: values & vectors

function extract_kv(fac::Factorization{T}, ord::AbstractVector{Int}, k::Int) where T
    si = ord[1:k]
    vals = fac.values[si]::Vector{T}
    vecs = fac.vectors[:, si]::Matrix{T}
    return (vals, vecs)
end

# symmmetrize a matrix

function symmetrize!(A::Matrix)
    n = size(A, 1)
    @assert size(A, 2) == n
    for j = 1:n
        for i = 1:j-1
            @inbounds A[i,j] = A[j,i]
        end
        for i = j+1:n
            @inbounds A[i,j] = middle(A[i,j], A[j,i])
        end
    end
    return A
end

# percolumn dot

function coldot(X::AbstractMatrix{T}, Y::AbstractMatrix{T}) where T<:Real
    m = size(X, 1)
    n = size(X, 2)
    @assert size(Y) == (m, n)
    R = zeros(T, n)
    for j = 1:n
        R[j] = dot(view(X,:,j), view(Y,:,j))
    end
    return R
end

# qnormalize!

function qnormalize!(X, C)
    # normalize each column of X (say x), such that x'Cx = 1
    m = size(X, 1)
    n = size(X, 2)
    CX = C * X
    for j = 1:n
        x = view(X,:,j)
        cx = view(CX,:,j)
        rmul!(x, inv(sqrt(dot(x, cx))))
    end
    return X
end

# add_diag!

function add_diag!(A::AbstractMatrix, v::Real)
    # add v to diagonal of A
    m = size(A, 1)
    n = size(A, 2)
    @assert m == n
    if v != zero(v)
        for i = 1:n
            @inbounds A[i,i] += v
        end
    end
    return A
end

# regularize a symmetric matrix
function regularize_symmat!(A::AbstractMatrix{T}, lambda::Real) where T<:Real
    if lambda > 0
        emax = eigmax(Symmetric(A))
        add_diag!(A, emax * lambda)
    end
    return A
end

"""
    calcscattermat([covestimator::CovarianceEstimator], Z::DenseMatrix)

Calculate the scatter matrix of centered data `Z` based on a covariance
matrix calculated using covariance estimator `covestimator` (by default,
`SimpleCovariance()`).
"""
function calcscattermat(covestimator::CovarianceEstimator, Z::DenseMatrix{T}) where T<:Real
    return cov(covestimator, Z; dims=2, mean=zeros(T, size(Z, 1)))*size(Z, 2)
end

function calcscattermat(Z::DenseMatrix)
    return calcscattermat(SimpleCovariance(), Z)
end

# calculate pairwise kernel
function pairwise!(K::AbstractVecOrMat{T}, kernel::Function,
                   X::AbstractVecOrMat{T}, Y::AbstractVecOrMat{T}) where T<:AbstractFloat
    n = size(X, 2)
    m = size(Y, 2)
    for j = 1:m
        aj = view(Y, :, j)
        for i in j:n
            @inbounds K[i, j] = kernel(view(X, :, i), aj)[]
        end
        j <= n && for i in 1:(j - 1)
            @inbounds K[i, j] = K[j, i]   # leveraging the symmetry
        end
    end
    K
end

pairwise!(K::AbstractVecOrMat{T}, kernel::Function, X::AbstractVecOrMat{T}) where T<:AbstractFloat =
    pairwise!(K, kernel, X, X)

function pairwise(kernel::Function, X::AbstractVecOrMat{T}, Y::AbstractVecOrMat{T}) where T<:AbstractFloat
    n = size(X, 2)
    m = size(Y, 2)
    K = similar(X, n, m)
    pairwise!(K, kernel, X, Y)
end

pairwise(kernel::Function, X::AbstractVecOrMat{T}) where T<:AbstractFloat =
    pairwise(kernel, X, X)


using Test
using SafeTestsets

@safetestset "Legendre tests" begin include("legendre.jl") end
@safetestset "Bessel tests" begin include("bessel.jl") end
@safetestset "Mie series tests" begin include("scattering.jl") end


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

using Markdown
using InteractiveUtils

# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
    quote
        local el = $(esc(element))
        global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : missing
        el
    end
end

# ╔═╡ 3f8349ba-8be8-11eb-32b9-55388c7242aa
using Plots, Colors, PlutoUI, StatsBase

# ╔═╡ 5d3d4988-8be8-11eb-1de8-3b114233e526
function simulate(N,p)
	v = fill(0,N,N)
	time = 0
	while any( v .== 0 ) && time<100
		time += 1
		for i= 1:N, j=1:N
		     if rand()<p && v[i,j]==0
				   v[i,j] = time
			 end
		end
	end
	v
end

# ╔═╡ 5d3d7b56-8be8-11eb-1bb1-ddecbefefc49
md"""
N= $(@bind N Slider(2:20, show_value=true, default=8))
"""

# ╔═╡ 5d4409e4-8be8-11eb-2d06-03e4aa311fc0
md"""
p= $(@bind prob Slider(0.01:.01:1, show_value=true, default=.1))

t = $(@bind tt Slider(1:100, show_value=true, default=1))
"""

# ╔═╡ 5d414452-8be8-11eb-233e-2d81eaacbfb6
simulation = simulate(N, prob)

# ╔═╡ 82299e50-8bec-11eb-3591-6b02b1b2a7de
begin

	plot()
	cdf= [ count(simulation .≤ i) for i=0:100]
	bar(cdf, c=:purple, legend=false, xlim=(0,tt),alpha=0.8)
end

# ╔═╡ 075669c8-8bef-11eb-288e-791816cc0d5b
begin
	newcdf = [ count(simulation .> i) for i=0:100]
	bar!( newcdf, c=RGB(0,1,0), legend=false, xlim=(0,tt),alpha=0.8)
end

# ╔═╡ 482ebeb0-8bec-11eb-2aaf-b5522fa606d4
bar(countmap(simulation[:]), c=:red, legend=false, xlim=(0,tt+.5))

# ╔═╡ c1e720f4-8bf5-11eb-386b-b32d313a2996
@bind bernoulliwidth Slider(10:10:500, show_value=true)

# ╔═╡ 3b02051e-8bf4-11eb-011b-3b7131b245a6
HTML("""<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/b/b3/ETH-BIB-Bernoulli%2C_Daniel_%281700-1782%29-Portrait-Portr_10971.tif_%28cropped%29.jpg/440px-ETH-BIB-Bernoulli%2C_Daniel_%281700-1782%29-Portrait-Portr_10971.tif_%28cropped%29.jpg" alt="" width=$(bernoulliwidth))>""")

# ╔═╡ 5f5a0caa-8bf6-11eb-1242-a91551de2922
begin
	b = "hello"
	HTML(b)
end

# ╔═╡ 5d460802-8be8-11eb-164e-71074e4e4b66
begin
	rectangle(w, h, x, y) = Shape(x .+ [0,w,w,0], y .+ [0,0,h,h])
	circle(r,x,y) = (θ = LinRange(0,2π,500); (x.+r.*cos.(θ), y.+r.*sin.(θ)))
end

# ╔═╡ 5d44663c-8be8-11eb-0986-bfc7546ee2ab
begin
	w = .9
	h = .9
	c = [RGB(0,1,0),RGB(1,0,0),:purple][1 .+ (simulation .< tt) .+ (simulation .<  (tt.-1))]

	plot()

	for i=1:N, j=1:N
		plot!( rectangle(w,h, i, j), c=:black)
		plot!( circle(.3,i+.45,j+.45), c=c[i,j], fill=true)
	end

	for i=1:N, j=1:N
		if simulation[i,j] < tt
	       annotate!(i+.45,j+.5,text("$(simulation[i,j])",font(7),:white))
		end
	end

	plot!(legend=false,ratio=1, axis=false, lims=(0.5,N+1.1), ticks=false,title="time = $(tt-1)  red count: $(sum(simulation.<tt))")

end

# ╔═╡ Cell order:
# ╠═3f8349ba-8be8-11eb-32b9-55388c7242aa
# ╠═5d3d4988-8be8-11eb-1de8-3b114233e526
# ╠═5d3d7b56-8be8-11eb-1bb1-ddecbefefc49
# ╠═5d414452-8be8-11eb-233e-2d81eaacbfb6
# ╟─5d4409e4-8be8-11eb-2d06-03e4aa311fc0
# ╠═5d44663c-8be8-11eb-0986-bfc7546ee2ab
# ╠═82299e50-8bec-11eb-3591-6b02b1b2a7de
# ╠═075669c8-8bef-11eb-288e-791816cc0d5b
# ╠═482ebeb0-8bec-11eb-2aaf-b5522fa606d4
# ╠═c1e720f4-8bf5-11eb-386b-b32d313a2996
# ╠═3b02051e-8bf4-11eb-011b-3b7131b245a6
# ╠═5f5a0caa-8bf6-11eb-1242-a91551de2922
# ╠═5d460802-8be8-11eb-164e-71074e4e4b66


using DitherPunk
using Images
using TestImages

# # Test image gallery
# This gallery uses images from [*TestImages.jl*](https://testimages.juliaimages.org).
function preprocess(img)
    img = Gray.(img)
    return imresize(img; ratio=1 / 2)
end

file_names = [
    "cameraman", "lake_gray", "house", "fabio_gray_512", "mandril_gray", "peppers_gray"
]
imgs = [preprocess(testimage(file)) for file in file_names]
mosaic(imgs; ncol=3)

# Our test function `test_on_images` just runs a dithering algorithm on all six images
# in linear color space (`to_liner=true`).
function test_on_images(alg; to_linear=false)
    dithers = [dither(img, alg; to_linear) for img in imgs]
    return mosaic(dithers; ncol=3)
end

# ## Threshold dithering
# #### `ConstantThreshold`
test_on_images(ConstantThreshold())

# #### `WhiteNoiseThreshold`
test_on_images(WhiteNoiseThreshold())

# ## Ordered dithering
# #### Bayer matrices
# **Level 1**
test_on_images(Bayer())
# **Level 2**
test_on_images(Bayer(; level=2))
# **Level 3**
test_on_images(Bayer(; level=3))
# **Level 4**
test_on_images(Bayer(; level=4))

# #### `ClusteredDots`
test_on_images(ClusteredDots())

# #### `CentralWhitePoint`
test_on_images(CentralWhitePoint())

# #### `BalancedCenteredPoint`
test_on_images(BalancedCenteredPoint())

# #### `Rhombus`
test_on_images(Rhombus())

# #### ImageMagick methods
test_on_images(IM_checks())
#
test_on_images(IM_h4x4a())
#
test_on_images(IM_h6x6a())
#
test_on_images(IM_h8x8a())
#
test_on_images(IM_h4x4o())
#
test_on_images(IM_h6x6o())
#
test_on_images(IM_h8x8o())
#
test_on_images(IM_c5x5())
#
test_on_images(IM_c6x6())
#
test_on_images(IM_c7x7())

# ## Error diffusion
# #### `SimpleErrorDiffusion`
test_on_images(SimpleErrorDiffusion())

# #### `FloydSteinberg`
test_on_images(FloydSteinberg())

# #### `JarvisJudice`
test_on_images(JarvisJudice())

# #### `Stucki`
test_on_images(Stucki())

# #### `Burkes`
test_on_images(Burkes())

# #### `Sierra`
test_on_images(Sierra())

# #### `TwoRowSierra`
test_on_images(TwoRowSierra())

# #### `SierraLite`
test_on_images(SierraLite())

# #### `Fan93()`
test_on_images(Fan93())

# #### `ShiauFan()`
test_on_images(ShiauFan())

# #### `ShiauFan2()`
test_on_images(ShiauFan2())

# #### `Atkinson()`
test_on_images(Atkinson())


using OrdinaryDiffEq, ModelingToolkit, CardiovascularModels

@variables t
D = Differential(t)

#left Ventricle
@named l_ventricle = Ventricle(Ees = 200e6,
    Vd = 0., V0 = 0., λ = 33e3, P0 = 10.)

@named card_driver = automaticDriver()

@named aortic_valve = Valve(R = 6e6)

# Aorta
@named aorta = ElasticVessel(Ees = 200e6, Vd = 100e-6)

@named systemic_resistance = Resistor(R = 50e6)

eqs_driver = [
    l_ventricle.drv ~ card_driver.contraction
]

@named vein = ElasticVessel(Ees = 5e6, Vd = 500e-6)
@named mitral_valve = Valve(R = 10e6)

eqs_con = serial_connect(vein,
    mitral_valve,
    l_ventricle,
    aortic_valve,
    aorta,
    systemic_resistance,
    vein)

eqs_comb = [eqs_driver; eqs_con]

# ODE parameters
volume_start = [l_ventricle.V => 1e-4,
    aorta.V => 1.5e-4,
    vein.V => 2e-3]
time_span = (0.0, 10.0)

@named hemo_sys = ODESystem(eqs_comb)

@named connected = compose(hemo_sys, l_ventricle, card_driver,
                            aortic_valve, aorta, systemic_resistance, vein, mitral_valve)

problem = ODEProblem(structural_simplify(connected), volume_start, time_span, [])

sol = solve(problem, Tsit5(), dtmax = 0.01, reltol = 1e-6)

# Plots.plot(sol)


using Distributions
export MarkovChain, DirichletRows
export draw_sample, draw_sample!
export get_transition_matrix

struct MarkovChain
    P::Matrix{Float64}
    P_c::Matrix{Float64}
    mu::Categorical
end
MarkovChain(P::Matrix{Float64}, x::Int) = MarkovChain(
    P,
    cumsum(P, dims=2),
    Categorical([i==x ? 1.0 : 0.0 for i in 1:size(P,1)])
)

MarkovChain(P::Matrix{Float64}, μ::Categorical) = MarkovChain(
    P,
    cumsum(P, dims=2),
    μ
)

MarkovChain(P::Matrix{Float64}) = MarkovChain(
    P,
    cumsum(P, dims=2),
    Categorical([1/size(P,1) for i in 1:size(P,1)])
)

function rand_categorical(μ_cusum::AbstractArray{Float64})
    β = rand()
    for i in 1:length(μ_cusum)
        if β < μ_cusum[i]
            if i == 1
                return i
            else
                return i
            end
        else
            continue
        end
    end
end

function rand_categorical_with_prob(μ_cusum::AbstractArray{Float64})
    β = rand()
    for i in 1:length(μ_cusum)
        if β < μ_cusum[i]
            if i == 1
                return i, μ_cusum[i]
            else
                return i, μ_cusum[i] - μ_cusum[i-1]
            end
        else
            continue
        end
    end
end

function draw_sample!(
    out::Vector{Int},
    model::MarkovChain,
    n::Int)

    # Sample initial value
    curr = rand(model.mu)
    out[1] = curr
    for i in 2:n
        @views curr = rand_categorical(model.P_c[curr,:])
        out[i] = curr
    end
end

function draw_sample(
    model::MarkovChain,
    n::Int)

    out = zeros(Int, n)
    draw_sample!(out, model, n)
    return out
end

struct DirichletRows
    A::Matrix{Float64}
end

function draw_sample(model::DirichletRows)
    P = [rand(Gamma(a)) for a in model.A]
    P ./= sum(P, dims=2)
    return P
end

function draw_sample!(
    out::Vector{Matrix{Float64}},
    model::DirichletRows
    )

    for P in out
        for j in 1:size(P,2)
            for i in 1:size(P,1)
                P[i,j] = rand(Gamma(model.A[i,j]))
            end
        end
        P ./= sum(P, dims=2)
    end
end

function draw_sample(
    model::DirichletRows,
    n::Int
    )

    out = [similar(model.A) for i in 1:n]
    draw_sample!(out,model)
    return out
end

function get_transition_matrix(
    data::Vector{Int},
    V::Int
    )

    T = zeros(Int, V, V)
    @inbounds for i in Iterators.rest(eachindex(data),1)
        T[data[i-1], data[i]] += 1
    end
    return T
end

function get_transition_matrix(
    data::Vector{Int}
    )

    V = maximum(data)
    return get_transition_matrix(data, V)
end


using RecyclePermanent
using RNGPool
using JLD2
using BenchmarkTools

include("../models/bpModel.jl")
include("compare.jl")

@load "data/bpData.jld" ys θ
model = makeBPModel(θ, ys)

N = 40*model.n

trials = 1000

setRNGs(12345)

n = 10
N = 40*n

rpio = RPIO{model.Tx}(N, n, 1)

# simpleOutput = testEstimator(() -> simpleEstimate(model, rpio), trials)
biasedOutput = testEstimator(() -> biasedEstimateDiscrete(model, rpio), trials)
rpOutput = testEstimator(() -> rpEstimateDiscrete(model, rpio), trials)

ϵ = 1e-5
kmio = KMIO{model.Tx}(N, n)
kmOutput = testEstimator(() -> kmEstimate(model, kmio, ϵ), trials)

@btime rpEstimateDiscrete($model, $rpio)


using Plots

include("models/Vibron.jl")
include("modules/ClassicalDynamics.jl")

vibronParameters = [0.1, -0.8, 0.7]
energy = 0.1
numTrajectories = 10
PSPoints = 1000

savePath = "d:\\results\\Vibron"

pyplot(size = (1200,1000))
fig = PoincareSection(energy, vibronParameters, numTrajectories; maxPSPoints=PSPoints, verbose=true, sectionPlane=4, min=-sqrt(2.0), max=sqrt(2.0), tolerance=1e-8)
#savefig(fig, savePath * "_PS_E=$(energy)_$vibronParameters.png")

# The following trajectory seems regular, but has an enormous LE
vibronParameters = [0.5,0.5,0.5]
energy = 0.5
initialCondition = [0.0, 1.0203574072397172, -0.9189955424295329, 0.23490092884445612]
#SolveTrajectory(initialCondition, vibronParameters; section=2, verbose=true, showFigures=true)

#initialCondition = [0.0, -0.7862068849266208, 0.7769513368418827, 0.7172308051665519]
#TestTrajectory(initialCondition, vibronParameters)


using COSMO
using SketchyCGAL
using LinearAlgebra, SparseArrays
using BSON
using Printf

include("../utils.jl")

# Run experiments single threaded
blas_threads = BLAS.set_num_threads(1)

## Data Load
function setup_maxcut_scgal(G)
    # Parameters
    n = size(G, 1)
    d = n

    # Data
    # objective = min -1/4( ∑_ij w_ij*(1-yi*yj) ) = -1/4⟨diag(∑ᵢ w_ij) - W), Y⟩
    # note that this reformulation uses the fact that Tr(Y) = 1
    C = -0.25*(Diagonal(G*ones(n)) - G)
    b = ones(n)

    # Scaling variables -- so trace is bounded by 1
    scale_C = 1 / norm(C)
    scale_X = 1 / n
    # b .= b .* scale_X
    # const C_const = C * scale_C

    # Linear map
    # AX = diag(X)
    function A!(y, X)
        n = size(X, 1)
        for i in 1:n
            y[i] = X[i,i]
        end
        return nothing
    end

    # Adjoint
    # A*z = Diagonal(z)
    function A_adj!(S::SparseMatrixCSC, z)
        for (i, j, v) ∈ zip(findnz(S)...)
            S[i,j] = 0.0
        end
        S[diagind(S)] .= z
        return nothing
     end

    # primative 1: u -> C*u
    function C_u!(v, u)
        mul!(v, C_const, u)
    end

    #primative 2: (u, z) -> (A*z)u
    function Aadj_zu!(v, u, z)
        v .= u .* z
    end

    #primative 3: u -> A(uu^T)
    function A_uu!(z, u)
        z .= u.*u
    end

    return C, b, A!, A_adj!, n, d, scale_X, scale_C
end

function maxcut_scgal(G, filename, trial_data; R=10)
    trial_data[filename] = Dict()
    C, b, A!, A_adj!, n, d, scale_X, scale_C = setup_maxcut_scgal(G)

    ηt(t) = 2.0/(t + 1.0)
    δt(t) = 1.0
    tt = @timed scgal_full(
        C, b, A!, A_adj!; n=n, d=d, scale_X=scale_X, scale_C=scale_C,
        max_iters=2_000,
        print_iter=1_000,
        R=R,
        logging=true,
        # logging_primal=true,
        # ηt=ηt,
        # δt=δt
    )

    trial_data[filename]["dim"] = (
        n=n,
        nonzeros=nnz(G)
    )
    trial_data[filename]["stats"] = tt

    return nothing
end

function run_trials_size()
    filenames = readdir(joinpath(dirname(@__DIR__), "data/gset"))
    trial_data = Dict()

    for filename in filenames
        G = graph_from_file(joinpath(dirname(@__DIR__), "data/gset/$filename"))
        @printf("On %3s: %8s %12s\n", filename, size(G,1), nnz(G))
        maxcut_scgal(G, filename, trial_data)
    end
    return trial_data
end

trial_data = run_trials_size()
BSON.bson(joinpath(@__DIR__, "output/trials_size.bson"), trial_data)


import TPT

using Base.Test

# using Plots; pyplot()

println("--- Unit NFE ---")

#
# Single component: Liquid Na
# Ref: I. H. Umar et al.: J. Phys. F: Metal Phys., Vol. 4 (1974), 1691-1706.
#
ahs1 = TPT.AHS(σ = 6.087, ρ = 3.564e-3)
pp1 = TPT.Ashcroft(1.0, 1.67)
nfe1 = TPT.NFE(ahs1, pp1)
sys1 = TPT.TPTSystem(ahs1, nfe1, T = 373, m = 4.191e+4)

kF1 = TPT.fermiwavenumber(nfe1)
rs = TPT.electrondistance(nfe1)

G1_IU = TPT.localfield_IU(nfe1)
G1_VS = TPT.localfield_VS(nfe1)
# plot([q -> G1_IU(q*kF1), q -> G1_VS(q*kF1)], 0, 10, ylims=(0, 1.5))

ϵ1 = TPT.dielectric(nfe1)
# plot(q -> ϵ(q*kF), 0, 6, ylims=(-1, 10))

ω1₀ = TPT.formfactor(nfe1)[1]
# plot(q -> ω1₀(q*kF), 0, 6, ylims=(-1,1))

F1 = TPT.wnechar(nfe1)[1,1]
# plot(q -> 27.21*F(q*kF), 0, 6, ylims = (-0.1, 0))

u1 = TPT.pairpotential(nfe1)[1,1]
u1′ = TPT.pairpotential_derivative(nfe1)[1,1]
# plot([u1, u1′], 2, 20, ylims = (-2.1e-3, 2.1e-3))

g = TPT.paircorrelation(sys1)[1,1]
# plot(g, 1, 20)

T = 373.
kB = TPT.kB
U1_eg = TPT.internal_eg(nfe1)
S1_eg = TPT.entropy(nfe1, ahs1, T)
U1_es = TPT.internal_es(nfe1, ahs1)
U1_bs = TPT.internal(ahs1, nfe1)
K1 = TPT.kinetic(sys1)
S1_hs = TPT.entropy(sys1) - S1_eg
F1_hs = -T*kB*S1_hs

F1_total = TPT.helmholtz(sys1)

#
# Binary
#
σ_Na = 6.235
σ_K = 7.354

ahs2 = TPT.AHS(σ = [σ_Na, σ_K], ρ = 2.461e-3, c = [0.5, 0.5])
pp2 = TPT.Ashcroft([1.0, 1.0], [1.67, 2.12])
nfe2 = TPT.NFE(ahs2, pp2)
sys2 = TPT.TPTSystem(ahs2, nfe2, T = 373, m = [4.191e+4, 4.191e+4])

ω2₀ = TPT.formfactor(nfe2)
# plot(q -> ω2₀(q*kF), 0, 6, ylims=(-1,1))

F2 = TPT.wnechar(nfe2)
# plot(q -> 27.21*F(q*kF), 0, 6, ylims = (-0.1, 0))

u2 = TPT.pairpotential(nfe2)
# plot(u2, 2, 20, ylims = (-2.1e-3, 2.1e-3))

F2_total = TPT.helmholtz(sys2)

@testset "Unit NFE" begin
  @test isapprox(kF1, 0.473, atol=1e-3)
  @test isapprox(G1_VS(kF1), 0.267, atol=1e-3)
  @test isapprox(ϵ1(kF1), 3.46, atol=1e-2)
  @test isapprox(ω1₀(kF1), -0.141, atol=1e-3)
  @test isapprox(F1(kF1), -0.0437, atol=1e-4)
  @test isapprox(u1(6.087), -0.00078, atol=1e-5)

  @test isapprox(U1_eg, -0.0816, atol=1e-4)
  @test isapprox(S1_eg, 0.0522, atol=1e-4)
  @test isapprox(U1_es, -0.147, atol=1e-3)
  @test isapprox(U1_bs, -0.00833, atol=1e-5)
  @test isapprox(K1, 0.00177, atol=1e-5)
  @test isapprox(F1_hs, -0.00921, atol=1e-5)
end


#!/usr/bin/env julia

using Revise,SEM
using FastGaussQuadrature
using Plots,LinearAlgebra

n = 16
x,w = gausslobatto(n)
xo  = linspace(-1,1,20*n)

J = interpMat(xo,x)
D = derivMat(x)

# neumann operators
B = diagm(w)
A = D'*B *D
C(v) = B * diagm(v) * D

## CASE SETUP
function utrue(x,v,t)
    xx = @. x - v*t
    return @. sin(2pi*xx)
end
f = @. 0+0*x
vl= 1.0
v = @. vl+0*x
ν = 0e-0
u = utrue(x,vl,0.0)
id= Matrix(I,n,n)

#R = id[2:end-1,:] # double dirichlet
R = id[1:end-0,:] # periodic
Q = semq(1,n,true)

CFL = 0.1
dx = minimum(diff(x))
dt = dx * CFL / vl
println("CFL=$CFL, dt=$dt")

## global system (full rank)
xx = R*x
AA = R*A*R' .* ν
BB = R*B
CC = R*C(v)*R'
bb = BB*f
AC = AA + CC

## local system (rank deficient)
b  = R'*bb
Al = R'*AA*R
Bl = R'*BB
Cl = R'*CC*R
Hl = Al + Bl

#println("er: ",norm(u - ut,Inf))
p=plot(xo,J*u,width=3,ylims=(-1.5,1.5))
display(p)

uh = [zero(u) for i in 1:3] # histories
gh = [zero(u) for i in 1:3] # explicit term

T = 1.0
t = zeros(4,1)
nstep = Int(floor(T/dt))

for istep=1:nstep
    global u,rhs,Hl,bdfA,bdfB

    updateHist!(t)
    updateHist!(u,uh)

    t[1] += dt
    bdfA,bdfB = bdfExtK(t)

    gh[1] = -Cl*uh[1] # convection

    rhs = Bl * f
    for i=1:3
        gh[i] = -Cl*uh[i]                 # convection
        rhs  += -bdfB[1+i] .* (Bl *uh[i]) # histories
        rhs  +=  bdfA[i]   .* gh[i]       # explicit term
    end

    Hl = Al + bdfB[1]*Bl

    Hl  = Q*Q'*Hl
    rhs = Q*Q'*rhs
    u = pcg(rhs,Hl)

    ut = utrue(x,vl,t[1])
    er = norm(u .- ut,Inf)
    println("Pointwise error: $er")

    plt = plot(xo,J*u,width=3)
    plt = plot!(title="Step $istep, Time $(t[1])")
    plt = plot!(ylims=(-1.5,1.5))
    display(plt)
end
#----------------------------------------------------------------------#
nothing


#using Revise
using LinearAlgebra
using CartesianFDM

n = (17, 16)
top = (nonperiodic(), periodic())

ctx = cartesianfdmcontext(top, n)

# square domain (0, 1) x (0, 1)
h = spacing.(top, n)
x = coordinate.(top, n)

# cylinder centered at (0.5, 0.5) with radius 0.25
cylinder(x, y, z...) = 0.25 - (2x - 1) ^ 2 - (2y - 1) ^ 2

# capacities
using Vofinit

h₀ = Cdouble.((h..., sum(h) / 2))
xₑ = Cdouble[0, 0, 0, 0]

using Base.Iterators

# volume
V = map(zip(CartesianIndices(n), product(x...))) do (ind, el)
    x₀ = Cdouble.((el..., 0.))
    prod(h) * getcc(cylinder, x₀, h₀, xₑ)
end

V = map(mask(ctx, reshape(V, :))) do val
    iszero(val) ? eps(val) : val
end

A₁ = map(zip(CartesianIndices(n), product(x...))) do (ind, el)
    x₀ = Cdouble.((el..., 0.))
    h[2] * getcc(x₀, h₀, xₑ) do y...
        cylinder(x₀[1], y[2])
    end
end

A₂ = map(zip(CartesianIndices(n), product(x...))) do (ind, el)
    x₀ = Cdouble.((el..., 0.))
    h[1] * getcc(x₀, h₀, xₑ) do y...
        cylinder(y[1], x₀[2])
    end
end

A = mask(ctx, [reshape(A₁, :), reshape(A₂, :)])

P = scalar(:P, n)
U = vector(:U, n)
N = reshape(map(CartesianIndices(n)) do ind
                ind[1] ≤ 4 || ind[1] ≥ n[1] - 4 ? 1. : 0.
            end, :)

# divergence and gradient
cont = divergence(neu, ctx, A, U, N)
grad = -gradient(neu, ctx, A, 0, P, 0)

@assert begin
    mapreduce(*, eachindex(U)) do i
        D = Symbolics.jacobian(cont, U[i])
        G = Symbolics.jacobian(grad[i], P)
        all(iszero, D' - G)
    end
end

W = [reshape(map(CartesianIndices(n)) do ind
                ind[1] ≤ 4 || ind[1] ≥ n[1] - 4 ? 1. : 0.
            end, :), zeros(prod(n))]

#conv =
E = strainrate(dir, ctx, A, V, U, W)
visc = divergence(dir, ctx, A, E)

# diagonal
@assert begin
    mapreduce(*, eachindex(U)) do i
        D = Symbolics.jacobian(visc[i], U[i])
        all(≤(100eps(1.)), D-D')
    end
end

# off-diagonal
@assert begin
    B₁ = Symbolics.jacobian(visc[1], U[2])
    B₂ = Symbolics.jacobian(visc[2], U[1])
    all(≤(100eps(1.)), B₁-B₂')
end

Re = 10.

mom = grad ./ 2 .+ visc ./ 8Re
rhs = vcat(mom..., cont ./ 2)
var = vcat(U..., P)

filename = "stokes.jl"
(fun, fun!) = build_function(rhs, var)
open(filename, "w") do file
    write(file, string(fun))
    write(file, "\n")
    write(file, string(fun!))
end
#include("model.jl")

(σ⁻, _) = getproperty(ctx, :σ)

Mᵥ = map(σ⁻) do σ
    σ * V / 2
end

reshape(Mᵥ[2], n...)[n[1], :] .= prod(h)

Mₚ = map(cont) do el
    iszero(el) ? prod(h) : zero(first(h))
end

M = Diagonal(vcat(Mᵥ..., Mₚ))

diffvar = (!iszero).(vcat(Mᵥ..., Mₚ))

using DifferentialEquations

u₀ = zeros((length(n)+1)*prod(n))
du₀ = zero(u₀)
tspan = (0.0, 1.0)

### option 2
#=
function dae(out, du, u, M, t)
    stokes!(out, u, nothing, t)
    out .= out - M * du
    nothing
end

prob = DAEProblem(dae, du₀, u₀, tspan, M, differential_vars=diffvar)

using Sundials
sol = solve(prob,IDA())
=#

### option 1
#=
f = ODEFunction(stokes!, mass_matrix=M)
prob = ODEProblem(f, x₀, tspan)

sol = solve(prob, Rodas5(), reltol=1e-8, abstol=1e-8)
=#

# test divergence
#=
V̂ = map(mask(ctx, fill(prod(h), prod(n)))) do x
    iszero(x) ? eps(x) : x
end
=#

#=
val = map(eachindex(h)) do i
    prod(h[j] for j in eachindex(h) if j ≠ i)
end

Â = mask(ctx, [fill(val[i], prod(n)) for i in eachindex(n)])

Ŵ = mask(ctx, ones(prod(n)))
Û = [ones(prod(n)), zeros(prod(n))]

@assert all(iszero, divergence(neu, ctx, Â, Û, Ŵ))
foo = divergence(neu, ctx, mask(ctx, vector(:A, n)), Û, 0)
bar = divergence(neu, ctx, mask(ctx, vector(:A, n)), 0, Ŵ)
=#
#=
W =
=#


"""
---
Apply OQAM demapping to incoming OQAM matrix qamMat of size nbSubcarriers x 2nbSymb
# --- Syntax
qamMat = oqamDemapping(qamMat)
# --- Input parameters
- oqamMat : OQAM matrix (pure real and pure imag. alterns) [Array{Complex{Float64,nbSubcarriers,nbSymb*2}}]
# --- Output parameters
- qamMat : Output complex qam Matrix [Array{Complex{Float64,nbSubcarriers,nbSymb}}]
# ---
# v 1.0 - Robin Gerzaguet.
"""
function oqamDemapping(oQamMat)
	# --- Size of incoming matrix
	(nbSubcarriers,nbOQAM)	  = size(oQamMat);
	nbSymb	  = nbOQAM ÷ 2;
	# Real and imag split:
	qamMat	  = zeros(Complex{Float64},nbSubcarriers,nbSymb);
	# --- Init matrix
	for iS = 1 : 1 : nbSubcarriers
		# Mask to keep real or imag part
		pattern		= (-1im)^( mod( mod(iS,2) + 1,2));
		pattern2	= (-1im)^( mod( mod(iS,2) + 2,2));
		# 1 // 1i shift
		scaleReIm	= (1im)^( mod( mod(iS,2) + 1,2));
		scaleReIm2	= (1im)^( mod( mod(iS,2) + 2,2));
		for iN = 1 : 1 : nbSymb
			# OQAM processing
			qamMat[iS,iN]	= scaleReIm*real(pattern*oQamMat[iS,2*(iN-1)+1])+scaleReIm2*real(pattern2*oQamMat[iS,2*(iN-1)+2]);
		end
	end
	return qamMat;
end

"""
---
Demodulate FBMC waveform based on the dual operation of fbmcSigGen

FBMC is parametrized by its FFT size, its cyclic prefix length (in samples) and the allocSubcarriers vector
# Syntax
sigId	= fbmcSigDecode(sigRx,nFFT,K,allocSubcarriers)
# ---  Input parameters
- sigRx	  : Time domain FBMC signal  [Array{Complex{Float64},nbEch}]
- nFFT	  : FFT size [Int]
- K		  : Overlapping factor [Int]
allocSubcarriers : Vector of index of allocated subcarriers [Array{Int,nbSubcarriers}]
# ---  Output parameters
- qamMat  : Time frequency matrix : [Array{Complex{Float64},nbSubcarriers,nbSymb}]
# ---
# v 1.0 - Robin Gerzaguet.
"""
function fbmcSigDecode(sigRx,nFFT,K,allocatedSubcarriers)
	# Set 4 core for FFT computation
	FFTW.set_num_threads(4)
	# --- Getting symbol size
	sizeSymb	   = nFFT * K;
	# --- Init FBMC filter
	p			   = getFBMCFilter(K,nFFT);
	# --- Getting number of symbol to decode
	# Here, symbol are QAM symbols (need to demodulate twice of that)
	nbSymbDecode   = Int(floor(length(sigRx)/ nFFT - K + 1/2));
	sBeforeFFT	   = zeros(Complex{Float64},nFFT,2*nbSymbDecode);
	# --- Iterative decoding with overlap and sum undoing
	for n = 1 : 1 : 2*nbSymbDecode
		# --- Received signal and Rx apodisation
		# Output is a KN vector multiplied by the filter
		yTmp	= p  .* sigRx[(n-1)*nFFT÷2 .+ collect(1:sizeSymb)];
		# From this convert to N sequence. All samples delayed by nFFT are sumed
		sBeforeFFT[:,n] = sum(reshape(yTmp,nFFT,K),dims=2);
	end
	# FFT
	oQamMat	= fft(sBeforeFFT,1);
	# --- OQAM to QAM conversion
	qamMat	= oqamDemapping(oQamMat[allocatedSubcarriers,:]);
end

# MD for input structure
function fbmcSigDecode(sigChan,fbmc::StrucFBMC)
	return fbmcSigDecode(sigChan,fbmc.nFFT,fbmc.K,fbmc.allocatedSubcarriers);
end


##############
# RTN | LVLH #
##############

export rRTNtoECI
"""
Compute the radial, along-track, cross-track (RTN) to Earth-centered inertial
rotation matrix. If applied to a position vector in the RTN frame, it will
transform that vector to into the equivalent position vector in the ECI frame.

The RTN frame is also commonly refered to as the local-vertical, local-horizontal (LVLH) frame.

Arguments:
- `x::AbstractVector{<:Real}`: Inertial state (position and velocity) of primary (observing) satellite

Returns:
- `R_rtn2eci::SMatrix{3,3}`: Rotation matrix transforming _from_ the RTN frame _to_ the ECI frame
"""
function rRTNtoECI(x::AbstractVector{<:Real})
    r = x[idx1t3]
    v = x[idx4t6]
    n = cross(r, v)

    R = normalize(r)
    N = normalize(n)
    T = cross(N, R)

    R_rtn2eci = hcat(R, T, N)

    return R_rtn2eci
end

export rECItoRTN
"""
Compute the Earth-centered inertial to radial, along-track, cross-track (RTN)
rotation matrix. If applied to a position vector in the ECI frame, it will
transform that vector into the equivalent position vector in the RTN frame.

The RTN frame is also commonly refered to as the local-vertical, local-horizontal (LVLH) frame.

Arguments:
- `x::AbstractVector{<:Real}`: Inertial state (position and velocity) of primary (observing) satellite

Returns:
- `R_eci2rtn::SMatrix{3,3}`: Rotation matrix transforming _from_ the ECI frame _to_ the RTN frame
"""
function rECItoRTN(x::AbstractVector{<:Real})
    r = x[idx1t3]
    v = x[idx4t6]
    n = cross(r, v)

    R = normalize(r)
    N = normalize(n)
    T = cross(N, R)

    R_eci2rtn = hcat(R, T, N)'

    return R_eci2rtn
end

export sECItoRTN
"""
Compute the radial, along-track, cross-track (RTN) coordinates of a target satellite in the primary satellites RTN frame.

The RTN frame is also commonly refered to as the local-vertical, local-horizontal (LVLH) frame.

Arguments:
- `x::AbstractVector{<:Real}`: Inertial state (position and velocity) of primary (observing) satellite
- `xt::AbstractVector{<:Real}`: Inertial state (position and velocity) of the target satellite

Returns:
- `xrtn::AbstractVector{<:Real}`: Position and velocity of the target relative of the observing satellite in the RTN.
"""
function sECItoRTN(x::AbstractVector{<:Real}, xt::AbstractVector{<:Real})
    if length(xt) >= 6
        return sECItoRTN(x, xt[idx1t6])
    else
        return sECItoRTN(x, xt[idx1t3])
    end
end

"""
Arguments:
- `x::AbstractVector{<:Real}`: Inertial state (position and velocity) of primary (observing) satellite
- `xt::SVector{3,<:Real}`: Inertial state (position) of the target satellite

Returns:
- `xrtn::SVector{3,<:Real}`: Position of the target relative of the observing satellite in the RTN.
"""
function sECItoRTN(x::AbstractVector{<:Real}, xt::SVector{3,<:Real})
    # Create RTN rotation matrix
    R_eci2rtn = rECItoRTN(x)

    # Transform Position
    r     = x[idx1t3]
    rho   = xt[idx1t3] - r
    r_rtn = R_eci2rtn*rho

    return r_rtn
end

"""
Arguments:
- `x::AbstractVector{<:Real}`: Inertial state (position and velocity) of primary (observing) satellite
- `xt::SVector{6,<:Real}`: Inertial state (position and velocity) of the target satellite

Returns:
- `xrtn::SVector{6,<:Real}`: Position and velocity of the target relative of the observing satellite in the RTN.
"""
function sECItoRTN(x::AbstractVector{<:Real}, xt::SVector{6,<:Real})
    # Create RTN rotation matrix
    R_eci2rtn = rECItoRTN(x)

    # Transform Position
    r     = x[idx1t3]
    rho   = xt[idx1t3] - r
    r_rtn = R_eci2rtn*rho

    v       = x[idx4t6]
    f_dot   = norm(cross(r, v)) / norm(r)^2
    omega   = SVector(0, 0, f_dot)
    rho_dot = xt[idx4t6] - v
    v_rtn   = R_eci2rtn*rho_dot - cross(omega, r_rtn)

    return vcat(r_rtn, v_rtn)
end

export sRTNtoECI
"""
Compute the Earth-center

The RTN frame is also commonly refered to as the local-vertical, local-horizontal (LVLH) frame.

Arguments:
- `x::AbstractVector{<:Real}`: Inertial state (position and velocity) of primary (observing) satellite
- `xrtn::AbstractVector{<:Real}`: Inertial state (position and velocity) of the target satellite

Returns:
- `xt::AbstractVector{<:Real}`: Position and velocity of the target relative of the observing satellite in the RTN.
"""
function sRTNtoECI(x::AbstractVector{<:Real}, xrtn::AbstractVector{<:Real})
    if length(xrtn) >= 6
        return sRTNtoECI(x, xrtn[idx1t6])
    else
        return sRTNtoECI(x, xrtn[idx1t3])
    end
end

"""
Arguments:
- `x::AbstractVector{<:Real}`: Inertial state (position and velocity) of primary (observing) satellite
- `xrtn::SVector{3,<:Real}`: Inertial state (position) of the target satellite

Returns:
- `xt::SVector{3,<:Real}`: Position of the target relative of the observing satellite in the RTN.
"""
function sRTNtoECI(x::AbstractVector{<:Real}, xrtn::SVector{3,<:Real})
    # Create RTN rotation matrix
    R_rtn2eci = rRTNtoECI(x)

    # Transform position
    r   = x[idx1t3]
    r_t = R_rtn2eci*xrtn + r

    return r_t
end

"""
Arguments:
- `x::AbstractVector{<:Real}`: Inertial state (position and velocity) of primary (observing) satellite
- `xrtn::SVector{6,<:Real}`: Inertial state (position and velocity) of the target satellite

Returns:
- `xt::SVector{6,<:Real}`: Position and velocity of the target relative of the observing satellite in the RTN.
"""
function sRTNtoECI(x::AbstractVector{<:Real}, xrtn::SVector{6,<:Real})
    # Create RTN rotation matrix
    R_rtn2eci = rRTNtoECI(x)

    # Transform position
    r     = x[idx1t3]
    r_rtn = xrtn[idx1t3]
    r_t   = R_rtn2eci*r_rtn + r

    # Transform velocity
    v     = x[idx4t6]
    v_rtn = xrtn[idx4t6]
    f_dot = norm(cross(r, v)) / norm(r)^2
    omega = SVector(0, 0, f_dot)
    v_t   = R_rtn2eci*(v_rtn + cross(omega, r_rtn)) + v

    return vcat(r_t, v_t)
end

#######################################
# IAU 2010 | Inertial <-> Earth-Fixed #
#######################################

export bias_precession_nutation
"""
Computes the Bias-Precession-Nutation matrix transforming the GCRS to the
CIRS intermediate reference frame. This transformation corrects for the
bias, precession, and nutation of Celestial Intermediate Origin (CIO) with
respect to inertial space.

Arguments:
- `epc::Epoch`: Epoch of transformation

Returns:
- `rc2i::Matrix{<:Real}`: 3x3 Rotation matrix transforming GCRS -> CIRS
"""
function bias_precession_nutation(epc::Epoch)
    # Constants of IAU 2006A transofrmation
    DMAS2R =  4.848136811095359935899141e-6 / 1.0e3
    dx06   =  0.0001750*DMAS2R
    dy06   = -0.0002259*DMAS2R

    # Compute X, Y, s terms using low-precision series terms
    x, y, s = iauXys00b(MJD_ZERO, mjd(epc, tsys="TT"))

    # Apply IAU2006 Offsets
    x += dx06
    y += dy06

    # Compute transformation and return
    rc2i = iauC2ixys(x, y, s)

    return rc2i
end

export earth_rotation
"""
Computes the Earth rotation matrix transforming the CIRS to the TIRS
intermediate reference frame. This transformation corrects for the Earth
rotation.

Arguments:
- `epc::Epoch`: Epoch of transformation

Returns:
- `r::Matrix{<:Real}`: 3x3 Rotation matrix transforming CIRS -> TIRS
"""
function earth_rotation(epc::Epoch)
    # Compute Earth rotation angle
    era = iauEra00(MJD_ZERO, mjd(epc, tsys="UT1"))

    # Rotate Matrix and return
    r = iauRz(era, Matrix{Float64}(I, 3, 3))

    return r
end

export polar_motion
"""
Computes the Earth rotation matrix transforming the TIRS to the ITRF reference
frame.

# Arguments
- `epc::Epoch`: Epoch of transformation

# Returns
- `rpm::Matrix{<:Real}`: 3x3 Rotation matrix transforming TIRS -> ITRF
"""
function polar_motion(epc::Epoch)
    xp, yp = POLE_LOCATOR(mjd(epc, tsys="UTC"))

    # Compute transformation and return
    rpm = iauPom00(xp, yp, iauSp00(MJD_ZERO, mjd(epc, tsys="TT")))

    return rpm
end

export rECItoECEF
"""
Computes the combined rotation matrix from the inertial to the Earth-fixed
reference frame. Applies corrections for bias, precession, nutation,
Earth-rotation, and polar motion.

The transformation is accomplished using the IAU 2006/2000A, CIO-based
theory using classical angles. The method as described in section 5.5 of
the SOFA C transformation cookbook.

# Arguments
- `epc::Epoch`: Epoch of transformation

# Returns
- `r::Matrix{<:Real}`: 3x3 Rotation matrix transforming GCRF -> ITRF
"""
function rECItoECEF(epc::Epoch)
    # Compute intermediate transformations
    rc2i = bias_precession_nutation(epc)
    r    = earth_rotation(epc)
    rpm  = polar_motion(epc)

    return rpm * r * rc2i
end

export rECEFtoECI
"""
Computes the combined rotation matrix from the Earth-fixed to the inertial
reference frame. Applies corrections for bias, precession, nutation,
Earth-rotation, and polar motion.

The transformation is accomplished using the IAU 2006/2000A, CIO-based
theory using classical angles. The method as described in section 5.5 of
the SOFA C transformation cookbook.

# Arguments
- `epc::Epoch`: Epoch of transformation

# Returns
- `r::Matrix{<:Real}`: 3x3 Rotation matrix transforming ITRF -> GCRF
"""
function rECEFtoECI(epc::Epoch)
    # Compute intermediate transformations
    rc2i = bias_precession_nutation(epc)
    r    = earth_rotation(epc)
    rpm  = polar_motion(epc)

    return rc2i' * r' * rpm'
end

export sECItoECEF
"""
Transforms an Earth inertial state into an Earth fixed state

The transformation is accomplished using the IAU 2006/2000A, CIO-based
theory using classical angles. The method as described in section 5.5 of
the SOFA C transformation cookbook.

# Arguments
- `epc::Epoch`: Epoch of transformation
- `x::AbstractVector{<:Real}`: Inertial state (position, velocity) [m; m/s]

# Returns
- `x_ecef::Vector{<:Real}`: Earth-fixed state (position, velocity)
"""
function sECItoECEF(epc::Epoch, x::AbstractVector{<:Real})
    dim_x  = length(x)
    x_ecef = zeros(Float64, dim_x)

    # Extract State Components
    r_eci = x[1:3]

    if dim_x >= 6
        v_eci = x[4:6]
    end

    if dim_x == 9
        a_eci = x[7:9]
    end

    # Compute Sequential Transformation Matrices
    rc2i = bias_precession_nutation(epc)
    r    = earth_rotation(epc)
    pm   = polar_motion(epc)

    # Create Earth's Angular Rotation Vector
    omega_vec = [0, 0, OMEGA_EARTH] # Neglect LOD effect

    # Calculate ECEF State
    x_ecef[1:3] = pm *  r * rc2i * r_eci

    if dim_x == 6
        x_ecef[4:6] = pm * (r * rc2i * v_eci - cross(omega_vec, r * rc2i * r_eci))
    end

    if dim_x == 9
        x_ecef[7:9] = pm * (r * rc2i * a_eci - cross(omega_vec, cross(omega_vec, r * rc2i * r_eci))
                                         - 2 * cross(omega_vec, r * rc2i * v_eci))
    end

    return x_ecef
end

export sECEFtoECI
"""
Transforms an Earth fixed state into an Inertial state

The transformation is accomplished using the IAU 2006/2000A, CIO-based
theory using classical angles. The method as described in section 5.5 of
the SOFA C transformation cookbook.

# Arguments
- `epc::Epoch`: Epoch of transformation
- `x::AbstractVector{<:Real}`: Earth-fixed state (position, velocity) [m; m/s]

# Returns
- `x_eci::Vector{<:Real}`: Inertial state (position, velocity)
"""
function sECEFtoECI(epc::Epoch, x::AbstractVector{<:Real})
    # Set state variable size
    dim_x = length(x)
    x_eci = zeros(Float64, dim_x)

    # Extract State Components
    r_ecef = x[1:3]

    if dim_x >= 6
        v_ecef = x[4:6]
    end

    if dim_x == 9
        a_ecef = x[7:9]
    end

    # Compute Sequential Transformation Matrices
    bpn = bias_precession_nutation(epc)
    rot = earth_rotation(epc)
    pm  = polar_motion(epc)

    # Create Earth's Angular Rotation Vector
    omega_vec = [0, 0, OMEGA_EARTH] # Neglect LOD effect

    # Calculate ECEF State
    x_eci[1:3] = (pm * rot * bpn)' * r_ecef

    if dim_x >= 6
        x_eci[4:6] = (rot * bpn)' * ((pm)' * v_ecef + cross(omega_vec, (pm)' * r_ecef))
    end

    if dim_x >= 9
        x_eci[7:9] = (rot * bpn)' * ((pm)' * a_ecef + cross(omega_vec, cross(omega_vec, (pm)' * x_eci[1:3]))
                                 + 2 * cross(omega_vec, (pm)' * x_eci[4:6]))
    end

    return x_eci
end


function solve_for_velocities!(state::MechanismState, momentum::Momentum, fixedJoints::Vector{GenericJoint{Float64}})
    solver = Gurobi.GurobiSolver()
    Gurobi.setparameters!(solver, Silent = true)
    model = Model(solver = solver)
    nv = num_velocities(state)
    A = momentum_matrix(state)

    momentum = transform(state, momentum, A.frame)

    @variable(model, v[1 : nv])
    @constraint(model, Array(A.linear) * v .== Array(momentum.linear))
    @constraint(model, Array(A.angular) * v .== Array(momentum.angular))
    for joint in fixedJoints
        @constraint(model, v[velocity_range(state, joint)] .== 0)
    end
    @objective(model, Min, sum(v[i]^2 for i = 1 : nv))

    status = solve(model)
    set_velocity!(state, getvalue(v))
end

function initialize_state!(state::MechanismState, xd::Float64, positionControlledJoints)
    setdirty!(state)
    com = center_of_mass(state)
    centroidalFrame = CartesianFrame3D()
    comdot = FreeVector3D(centroidalFrame, SVector(xd, 0.0, 0.0))
    linearMomentum = mass(state.mechanism) * comdot
    initialMomentum = Momentum{Float64}(centroidalFrame, zero(SVector{3}), linearMomentum.v)
    centroidalToWorld = Transform3D(centroidalFrame, root_frame(state.mechanism), com.v)
    initialMomentum = transform(initialMomentum, centroidalToWorld)
    solve_for_velocities!(state, initialMomentum, positionControlledJoints)
end


include("helmholtzhodge.jl")

using Random
Random.seed!(0)

function sphrandn(::Type{T}, m::Int, n::Int) where T
    A = zeros(T, m, 2n-1)
    for i = 1:m
        A[i,1] = randn(T)
    end
    for j = 1:n-1
        for i = 1:m-j
            A[i,2j] = randn(T)
            A[i,2j+1] = randn(T)
        end
    end
    A
end

#=
N = 2 .^(5:13)
t = zeros(length(N), 2)
err = zeros(length(N))

j = 1
for n in N
    println("This is n: ", n)
    U1 = sphrandn(Float64, n, n)
    U2 = sphrandn(Float64, n, n)
    U1[1] = 0
    U2[1] = 0
    Us = zero(U1); Ut = zero(U1);
    V1 = zero(U1); V2 = zero(U1); V3 = zero(U1); V4 = zero(U1);

    HH = HelmholtzHodge(Float64, n)
    t[j, 1] = @elapsed for k = 1:10
        HelmholtzHodge(Float64, n)
    end
    t[j, 1] /= 10

    gradient!(U1, V1, V2)
    curl!(U2, V3, V4)

    V5 = V1 + V3
    V6 = V2 + V4

    helmholtzhodge!(HH, Us, Ut, V5, V6)

    t[j, 2] = @elapsed for k = 1:10
        helmholtzhodge!(HH, Us, Ut, V5, V6)
    end
    t[j, 2] /= 10
    global j += 1
end

j = 1
for n in N
    println("This is n: ", n)
    HH = HelmholtzHodge(Float64, n)
    for k = 1:10
        U1 = sphrandn(Float64, n, n)
        U2 = sphrandn(Float64, n, n)
        U1[1] = 0
        U2[1] = 0
        Us = zero(U1); Ut = zero(U1);
        V1 = zero(U1); V2 = zero(U1); V3 = zero(U1); V4 = zero(U1);

        gradient!(U1, V1, V2)
        curl!(U2, V3, V4)

        V5 = V1 + V3
        V6 = V2 + V4

        helmholtzhodge!(HH, Us, Ut, V5, V6)

        println("This is the spheroidal relative backward error: ", norm(Us-U1)/norm(U1))
        println("This is the toroidal relative backward error: ", norm(Ut-U2)/norm(U2))
        err[j] += norm(Us-U1)/norm(U1) + norm(Ut-U2)/norm(U2)
    end
    err[j] /= 10
    global j += 1
end
=#

using PyPlot

cd("/Users/Mikael/Dropbox/Helmholtz")

t = [0.000301073 8.59417e-5; 0.000870661 0.000313344; 0.00371459 0.0013411; 0.0158751 0.00519203; 0.0629211 0.0214159; 0.272275 0.0841661; 1.18461 0.338013; 5.22898 1.34888; 20.2509 5.41399]
err = [9.73175783254202e-16, 1.1848048133640504e-15, 1.2864591012075482e-15, 1.4887354817191668e-15, 2.0250488931546365e-15, 2.714940169354596e-15, 3.426270329252532e-15, 4.804155288069517e-15, 6.620111175740055e-15]

clf()
loglog(N.-1, t[:, 1], "xk", N.-1, t[:, 2], "+k", N.-1, (N.-1).^2/2e6, "-k")
xlabel("Degree \$n\$"); ylabel("Execution Time (s)"); grid()
legend(["Pre-computation","Execution","\$\\mathcal{O}(n^2)\$"])
savefig("helmholtzhodgetime.pdf")

clf()
loglog(N.-1, err, "xk", N.-1, sqrt.(N.*log.(N))*eps()/7, "-k")
xlabel("Degree \$n\$"); ylabel("Relative Error"); grid()
ylim((6e-16,1.2e-14))
legend(["Error","\$\\mathcal{O}(\\sqrt{n \\log(n)}\\epsilon)\$"])
savefig("helmholtzhodgeerror.pdf")


#!/usr/bin/env julia
#
# Jerzy Wroczyński (nr. indeksu 250075)
# Obliczenia Naukowe, Laboratorium
# Lista 2, Zadanie 6.
#

# to avoid writing `global` wrap the entire program inside of a function
function program()

    # default case
    k = "1"

    # the first argument correlates to the considered case in the exercise
    if length(ARGS) != 1
        println("usage: ./ex-6.jl <number between 1-7>")
        exit(1)
    end
    k = ARGS[1]

    # if k == "1"
    c = -2
    x₀= 1

    if k == "2"
        c = -2
        x₀= 2
    elseif k == "3"
        c = -2
        x₀= 1.99999999999999
    elseif k == "4"
        c = -1
        x₀= 1
    elseif k == "5"
        c = -1
        x₀= -1
    elseif k == "6"
        c = -1
        x₀= 0.75
    elseif k == "7"
        c = -1
        x₀= 0.25
    end

    # iterate and print out the results
    x = x₀
    for n in 1:40

        x = x^2 + c

        print(
            "(", n, ",", x, ") "
        )

    end
    # put a newline at the end of the program
    println()

end

program()


# Utils
# #+HTML: Source
# #+HTML:

# [[file:~/.julia/dev/MatrixProductStates/README.org::*Utils][Utils:1]]
export ⊗, realize

abstract type Direction end

struct Left  <: Direction end # Often useful to dispatch on direction an algorithm is going
struct Right <: Direction end

const left  = Left()
const right = Right()

A ⊗ B = kron(A, B)

realize(x::Number) = error("Unrecognized numerical type")
realize(x::Real) = x
function realize(x::Complex; ϵ=1e-10)
    abs(imag(x)) < ϵ || error("Non-zero imaginary component, $(imag(x))")
    real(x)
end

dg(M::Array{T, 4}) where {T} = permutedims(conj.(M), (2, 1, 3, 4))
dg(M::Array{T, 3}) where {T} = permutedims(conj.(M), (2, 1, 3))

not(x) = ~x
# Utils:1 ends here


export same_sign,
       rectify

"""
    same_sign(A::AbstractArray{N}; [optimistic]::Bool=false) where {N}

Check whether all elements of the given array have the same sign.

### Input

- `A`          -- array
- `optimistic` -- (optional; default: `false`) flag for expressing that the
                  expected result is `true`

### Output

`true` if and only if all elements in `M` have the same sign.

### Algorithm

If `optimistic` is `false`, we check the sign of the first element and compare
to the sign of all elements.

If `optimistic` is `true`, we compare the absolute element sum with the sum of
the absolute of the elements; this is faster if the result is `true` because
there is no branching.

```math
    |\\sum_i A_i| = \\sum_i |A_i|
```
"""
function same_sign(A::AbstractArray{N}; optimistic::Bool=false) where {N}
    if optimistic
        return sum(abs, A) == abs(sum(A))
    else
        if isempty(A)
            return true
        end
        @inbounds if first(A) >= zero(N)
            return all(e -> e >= zero(N), A)
        else
            return all(e -> e <= zero(N), A)
        end
    end
end

"""
    _rationalize(::Type{T}, x::AbstractVecOrMat{N}, tol::Real) where {T<:Integer, N<:AbstractFloat}

Approximate an array of floating point numbers as a rational vector with entries
of the given integer type.

### Input

- `T`   -- (optional, default: `Int`) integer type to represent the rationals
- `x`   -- vector of floating point numbers
- `tol` -- (optional, default: `eps(N)`) tolerance the result at entry `i`
           will differ from `x[i]` by no more than `tol`

### Output

An array of type `Rational{T}` where the `i`-th entry is the rationalization
of the `i`-th component of `x`.

### Notes

See also [`Base.rationalize`](@ref).
"""
function _rationalize(::Type{T}, x::AbstractVecOrMat{N}, tol::Real) where {T<:Integer, N<:AbstractFloat}
    return rationalize.(Ref(T), x, Ref(tol))
end

# method extensions
_rationalize(::Type{T}, x::AbstractVecOrMat{N}; tol::Real=eps(N)) where {T<:Integer, N<:AbstractFloat} =  _rationalize(T, x, tol)
_rationalize(x::AbstractVecOrMat{N}; kwargs...) where {N<:AbstractFloat} = _rationalize(Int, x; kwargs...)

# fallback for scalars
_rationalize(::Type{T}, x::AbstractFloat, tol::Real) where {T<:Integer} = rationalize(T, x, tol)

# nested vectors
function _rationalize(::Type{T}, x::AbstractVecOrMat{<:AbstractVecOrMat{N}}, tol::Real) where {T<:Integer, N<:AbstractFloat}
    return _rationalize.(Ref(T), x, Ref(tol))
end

"""
    rectify(x::AbstractArray{N}) where {N<:Real}

Rectify an array, i.e., take the element-wise maximum with zero.

### Input

- `x` -- array

### Output

A copy of the array where each negative entry is replaced by zero.
"""
function rectify(x::AbstractArray{N}) where {N<:Real}
    return map(xi -> max(xi, zero(N)), x)
end


# Types
include("QuadForm/Types.jl")
include("QuadForm/Quad/Types.jl")
include("QuadForm/Herm/Types.jl")

# Binary quadratic forms
include("QuadForm/QuadBin.jl")

# Functionality for spaces
include("QuadForm/Spaces.jl")

# Functionality for lattices
include("QuadForm/Lattices.jl")

# Misc things that need to be sorted
include("QuadForm/Misc.jl")

# Functionality for pseudo matrices
include("QuadForm/PseudoMatrices.jl")

# Functionality for IO with Hecke/Magma
include("QuadForm/IO.jl")

# Quadratic
include("QuadForm/Quad/Spaces.jl")
include("QuadForm/Quad/Lattices.jl")
include("QuadForm/Quad/ZLattices.jl")
include("QuadForm/Quad/Genus.jl")
include("QuadForm/Quad/GenusRep.jl")
include("QuadForm/Quad/Legacy.jl")

# Hermitian
include("QuadForm/Herm/Spaces.jl")
include("QuadForm/Herm/Lattices.jl")
include("QuadForm/Herm/Genus.jl")
include("QuadForm/Herm/GenusRep.jl")
include("QuadForm/Herm/Mass.jl")
include("QuadForm/Herm/Legacy.jl")
include("QuadForm/Herm/LocallyIsometricSublattice.jl")

include("QuadForm/Morphism.jl")
include("QuadForm/Database.jl")
include("QuadForm/Enumeration.jl")
include("QuadForm/LineOrbits.jl")
include("QuadForm/MassQuad.jl")

# Torsion
include("QuadForm/Torsion.jl")


module Id

include("Sphere.jl")
import .Sphere: swal

import Polynomials: ChebyshevT
import HDF5: h5read

export set_gaussian!, set_qnm!

"""
Initial gaussian initial data for psi.

function set_gaussian(
      f,
      p,
      spin::Int64,
      mv::Int64,
      l_ang::Int64,
      ru::Float64,
      rl::Float64,
      width::Float64,
      amp::ComplexF64,
      cl::Float64,
      Rv::Vector{Float64},
      Yv::Vector{Float64}
   )
"""
function set_gaussian!(
      f,
      p,
      spin::Int64,
      mv::Int64,
      l_ang::Int64,
      ru::Float64,
      rl::Float64,
      width::Float64,
      amp::ComplexF64,
      cl::Float64,
      Rv::Vector{Float64},
      Yv::Vector{Float64}
   )
   @assert f.mv == mv
   @assert p.mv == mv

   nx, ny = f.nx, f.ny

   max_val = 0.0

   for j=1:ny
      for i=1:nx
         r = (cl^2)/Rv[i]

         bump = 0.0
         if ((r<ru) && (r>rl))
            bump = exp(-1.0*width/(r-rl))*exp(-2.0*width/(ru-r))
         end

         f.n[i,j]  = (((r-rl)/width)^2) * (((ru-r)/width)^2) * bump
         f.n[i,j] *= swal(spin,mv,l_ang,Yv[j])

         p.n[i,j] = 0.0

         max_val = max(abs(f.n[i,j]),max_val)
      end
   end

   ## rescale

   for j=1:ny
      for i=1:nx
         f.n[i,j] *= amp / max_val

         f.np1[i,j] = f.n[i,j]
         p.np1[i,j] = p.n[i,j]
      end
   end
   return nothing
end

"""
Initial qnm initial data for psi, read in
from HDF5 file.

function set_qnm(
      f,
      p,
      spin::Int64,
      mv::Int64,
      l_ang::Int64,
      ru::Float64,
      rl::Float64,
      width::Float64,
      amp::ComplexF64,
      cl::Float64,
      Rv::Vector{Float64},
      Yv::Vector{Float64}
   )
"""
function set_qnm(
      f,
      p,
      s::Int64,
      l::Int64,
      mv::Int64,
      n::Int64,
      a::Float64,
      amp::Float64,
      Rv::Vector{Float64},
      Yv::Vector{Float64}
   )
   @assert f.mv == mv
   @assert p.mv == mv
   nx, ny = f.nx, f.ny

   aval = round(digits=12,a)

   qnmpath = dirname(pwd())*"/qnm"
   h5f = h5read(
         qnmpath*"/prec1024_nr$(nr)_s$(s)_m$(mv)_n$(n).h5",
         "[a=$(aval),l=$(l)]"
        )
   rpoly = ChebyshevT(h5f["radial_coef"])
   lpoly = ChebyshevT(h5f["angular_coef"])
   lmin  = max(abs(s),abs(mv))
   lvals = [i+lmin for i in range(length(lpoly))]
   for j=1:ny
      for i=1:nx
         f.n[i,j]  = roply(Rv[i])
         f.n[i,j] *= sum(
            [lpoly[i]*swal(spin,mv,(i-1)+lmin,Yv[j])
             for i in 0:length(lpoly)
            ]
         )
      end
   end
   ## rescale
   for j=1:ny
      for i=1:nx
         f.n[i,j] *= amp / max_val
         f.np1[i,j] = f.n[i,j]
      end
   end
   ## p = f,t = -iωf
   omega = h5f["omega"]
   for j=1:ny
      for i=1:nx
         p.n[i,j]   = -im*omega*f.n[i,j]
         p.np1[i,j] = p.n[i,j]
      end
   end
   return nothing
end

end


using LiftedMaps
using Test

using LiftedMaps.LinearMaps

A = rand(10,10)
B = rand(3,3)

Q = rand(10,10)
W = LinearMap(A) + 2 * LinearMap(Q)

U = 1:13
V = 1:13

I = 1:10
J = 11:13

Alifted = LiftedMap(A, 1:10, 1:10, U, V)
Blifted = LiftedMap(B, 11:13, 11:13, U, V)

@test size(Alifted) == (13,13)
@test size(Blifted) == (13,13)

C = Alifted + Blifted

@test size(C) == (13,13)
@test C isa LinearMaps.LinearMap
@test C isa LinearMaps.LinearCombination
@test isblockdiagonal(C)

D = Alifted + 2 * Blifted
@test D isa LinearMaps.LinearMap
@test D isa LinearMaps.LinearCombination
@test isblockdiagonal(D)

x = rand(13)
x1 = x[I]
x2 = x[J]

y1 = A * x1
y2 = 2 * B * x2
y = [y1; y2]

z = D * x
@assert y ≈ z


using WaterLily
using Test

@testset "util.jl" begin
    @test L₂(2ones(4,4)) == 16
    a = Float64[i+j+k  for i ∈ 1:4, j ∈ 1:4, k ∈ 1:2]
    BC!(a,[0. 0.])
    @test a == cat([0. 0. 0. 0.
                    0. 0. 0. 0.
                    6. 6. 7. 7.
                    0. 0. 0. 0.],
                   [0. 0. 7. 0.
                    0. 0. 7. 0.
                    0. 0. 8. 0.
                    0. 0. 8. 0.],dims=3)
    u = apply((i,x)->x[i],4,4,2)
    @test [u[i,j,1].-(i-0.5) for i in 1:4, j in 1:4]==zeros(4,4)
end

function Poisson_test_2D(f,n)
    c = ones(n+2,n+2,2); BC!(c,[0. 0.])
    p = f(c)
    soln = Float64[ i for i ∈ 1:n+2, j ∈ 1:n+2]
    b = mult(p,soln)
    x = zeros(n+2,n+2)
    solver!(x,p,b)
    x .-= (x[2,2]-soln[2,2])
    return L₂(x.-soln)/L₂(soln)
end
function Poisson_test_3D(f,n)
    c = ones(n+2,n+2,n+2,3); BC!(c,[0. 0. 0.])
    p = f(c)
    soln = Float64[ i for i ∈ 1:n+2, j ∈ 1:n+2, k ∈ 1:n+2]
    b = mult(p,soln)
    x = zeros(n+2,n+2,n+2)
    solver!(x,p,b)
    x .-= (x[2,2,2]-soln[2,2,2])
    return L₂(x.-soln)/L₂(soln)
end

@testset "Poisson.jl" begin
    @test Poisson_test_2D(Poisson,2^6) < 1e-5
    @test Poisson_test_3D(Poisson,2^4) < 1e-5
end
@testset "MultiLevelPoisson.jl" begin
    @test_throws AssertionError("MultiLevelPoisson requires size=a2ⁿ, where a<31, n>2") Poisson_test_2D(MultiLevelPoisson,67)
    @test_throws AssertionError("MultiLevelPoisson requires size=a2ⁿ, where a<31, n>2") Poisson_test_3D(MultiLevelPoisson,3^4)
    @test Poisson_test_2D(MultiLevelPoisson,2^6) < 1e-5
    @test Poisson_test_3D(MultiLevelPoisson,2^4) < 1e-5
end

@testset "Body.jl" begin
    @test WaterLily.μ₀(3;ϵ=6)==WaterLily.μ₀(0.5)
    @test WaterLily.μ₀(0)==0.5
    @test WaterLily.μ₁(0;ϵ=2)==2*(1/4-1/π^2)
end

@testset "AutoBody.jl" begin
    using LinearAlgebra: norm2
    body = AutoBody((x,t)->norm2(x)-2-t)
    @test all(measure(body,[√2.,√2.],0.).≈(0,[√.5,√.5],[0.,0.],0.25,0.))
    @test all(measure(body,[2.,0.,0.],1.).≈(-1.,[1.,0.,0.],[0.,0.,0.],0.5,0.25))
    body = AutoBody((x,t)->norm2(x)-2,(x,t)->x.*(1+t))
    @test all(measure(body,[√2.,√2.],0.).≈(0,[√.5,√.5],[-√2.,-√2.],0.25,0.))
    @test all(measure(body,[1.,0.,0.],1.).≈(0.,[1.,0.,0.],[-1.,0.,0.],1.,1.))
end

@testset "Flow.jl" begin
    # Horizontally moving body
    using LinearAlgebra: norm2
    a = Flow((20,20),[1.,0.])
    center = [10.58,10.65] # worst case - not sure why
    measure!(a,AutoBody((x,t)->norm2(x.-center)-5,(x,t)->x.-[t,0.]))
    mom_step!(a,Poisson(a.μ₀))
    @test sum(abs2,a.u[:,5,1].-1) < 2e-5

    # Impulsive flow in a box
    U = [2/3,-1/3]
    a = Flow((14,10),U)
    mom_step!(a,MultiLevelPoisson(a.μ₀))
    @test L₂(a.u[:,:,1].-U[1]) < 2e-5
    @test L₂(a.u[:,:,2].-U[2]) < 1e-5
end

@testset "Metrics.jl" begin
    u = apply((i,x)->x[i]+prod(x),3,4,5,3)
    @test WaterLily.ke(CartesianIndex(2,3,4),u)==0.5*(26^2+27^2+28^2)
    @test WaterLily.ke(CartesianIndex(2,3,4),u,[2,3,4])===1.5*24^2
    @test [WaterLily.∂(i,j,CartesianIndex(2,3,4),u)
            for i in 1:3, j in 1:3] == [13 8 6; 12 9 6; 12 8 7]
    @test WaterLily.λ₂(CartesianIndex(2,3,4),u)≈1
    ω = [8-6,6-12,12-8]
    @test WaterLily.curl(2,CartesianIndex(2,3,4),u)==ω[2]
    @test WaterLily.ω(CartesianIndex(2,3,4),u)==ω
    @test WaterLily.ω_mag(CartesianIndex(2,3,4),u)==sqrt(sum(abs2,ω))
    @test WaterLily.ω_θ(CartesianIndex(2,3,4),[0,0,1],[2,2,2],u)==-ω[1]
end


module GraphAlgorithms

using LightGraphs
using SimpleWeightedGraphs
using GLPK
using JuMP

export

# Operators
edges_of,unweighted_complement,

# Fractional numbers
fractional_covering_number, fractional_coloring_number,
fractional_matching_number,

# Sets
largest_clique_number, largest_cliques_set,
largest_independence_number, largest_independents_set,

# Diversity
diversity

GraphAlgorithms
include("diversity.jl")
include("fractional_numbers.jl")
include("operators.jl")
include("sets.jl")

end # module


module TestModularityBrim
using Test
using EcologicalNetworks
using LinearAlgebra

    N = web_of_life("M_PL_026")
    modules = lp(N)[2]
    m = brim(N, modules)

    @test Q(m...) ≈ 0.5 atol = 0.3
    @test Qr(m...) ≈ 0.5 atol = 0.3

    N = BipartiteProbabilisticNetwork(Matrix{Float64}(I, (50, 50)))
    modules = brim(lp(N)...)
    @test Q(modules...) > 0.95

    N = BipartiteNetwork(Matrix{Bool}(I, (50, 50)))
    modules = brim(lp(N)...)
    @test Q(modules...) > 0.95

end


include(joinpath(@__DIR__, "../../src/FLS.jl"))

"""
3-ary Cartesian product
"""

tripschema = @acset LabeledGraph begin
    V = 2
    E = 3
    vlabel = [:s, :s3]
    elabel = [:p1, :p2, :p3]
    src = [2,2,2]
    tgt = [1,1,1]
end

tripd = @acset LabeledGraph begin
    V = 4
    vlabel = [:s, :s, :s,]
end

trips = FLS(:trips, tripschema, [
    Cone(lconed, :s2, [1=>:p1,2=>:p2, 3=>:p3])], [])


@snn_kw struct ADEXParameter{FT=Float32}
    a::FT = 4.0
    b::FT = 0.0805
    cm::FT = 0.281
    v_rest::FT = -70.6
    tau_m::FT = 9.3667
    tau_w::FT = 144.0
    v_thresh::FT = -50.4
    delta_T::FT = 2.0
    v_spike::FT = -40.0
    v_reset::FT = -70.6
    spike_delta::FT = 30
end
@snn_kw mutable struct AD{VFT=Vector{Float32},VBT=Vector{Bool}}
    param::ADEXParameter = ADEXParameter()

    N::Int32 = 1
    cnt::Int32 = 1
    v::VFT = fill(param.v_rest, N)
    w::VFT = zeros(N)
    fire::VBT = zeros(Bool, N)
    I::VFT = zeros(N)
    sized::Int32 = 1
    spike_raster::Vector{Int32} = zeros(N)
    records::Dict = Dict()
end

function integrate!(p::AD, param::ADEXParameter, dt::Float32)
    @unpack N, cnt, v, w, fire, I,spike_raster,sized = p
    @unpack a,b,cm,v_rest,tau_m,tau_w,v_thresh,delta_T,v_spike,v_reset,spike_delta = param
    @inbounds for i = 1:N
        if spike_raster[cnt] == 1 || fire[i]
          v[i] = v_reset
          w[i] += b
        end
        dv  = (((v_rest-v[i]) +
                delta_T*exp((v[i] - v_thresh)/delta_T))/tau_m +
                (I[i] - w[i])/cm) *dt
        v[i] += dv
        w[i] += dt * (a*(v[i] - v_rest) - w[i])/tau_w * dt
        fire[i] = v[i] > v_thresh
        if v[i]>v_thresh
            fire[i] = 1
            v[i] = spike_delta
            spike_raster[cnt] = 1

        else
            spike_raster[cnt] = 0
        end
    end
    cnt+=1
end


function sumbinom(K::Int)
    res = 0
    for k = 0:K
        res += binomial(K, k)^2
    end
    return res, binomial(2K, K)
end


using TopOpt, LinearAlgebra, StatsFuns
using TopOpt.TrussTopOptProblems
using TopOptMakie
using VTKDataIO
using PyCall
using VTKDataTypes
using DashVtk

using Dash
using DashHtmlComponents

#dash = pyimport("dash")
#dash_html = pyimport("dash_html_components")
dash_vtk = pyimport("dash_vtk")

# 2D
ndim = 2
node_points, elements, mats, crosssecs, fixities, load_cases =
    parse_truss_json(joinpath(@__DIR__, "tim_$(ndim)d.json"));
ndim, nnodes, ncells = length(node_points[1]), length(node_points), length(elements)
loads = load_cases["0"]
problem = TrussProblem(
    Val{:Linear}, node_points, elements,
    loads, fixities, mats, crosssecs,
);

xmin = 0.0001 # minimum density
x0 = fill(1.0, ncells) # initial design
p = 4.0 # penalty
V = 0.5 # maximum volume fraction

solver = FEASolver(Displacement, Direct, problem, xmin = xmin)
comp = TopOpt.Compliance(problem, solver)

function obj(x)
    # minimize compliance
    return comp(x)
end
function constr(x)
    # volume fraction constraint
    return sum(x) / length(x) - V
end

m = Model(obj)
addvar!(m, zeros(length(x0)), ones(length(x0)))
Nonconvex.add_ineq_constraint!(m, constr)

options = MMAOptions(
    maxiter=1000, tol = Tolerance(kkt = 1e-4, f = 1e-4),
)
TopOpt.setpenalty!(solver, p)
@time r = Nonconvex.optimize(
    m, MMA87(dualoptimizer = ConjugateGradient()),
    x0, options = options,
);

@show obj(r.minimizer)
@show constr(r.minimizer)

topology = r.minimizer;

function fg(topology, problem)
    mesh = VTKUnstructuredData(problem)
    topology = round.(topology)
    inds = findall(isequal(0), topology)
    deleteat!(mesh.cell_connectivity, inds)
    deleteat!(mesh.cell_types, inds)
    return mesh
end

pymesh = PyVTK(fg(topology, problem))
dash_vtk_utils = pyimport("dash_vtk.utils")
dash_vtk_utils.to_mesh_state
state = dash_vtk_utils.to_mesh_state(pymesh)

content = vtk_view([
        vtk_geometryrepresentation([
        vtk_mesh(state=state)
    ]),
])

app = dash()

app.layout = html_div() do
    html_div(
    style = Dict(
        "width" => "100%",
        "height" => "400px"
    ),
    children=[content],
    )
end

run_server(app, "0.0.0.0", debug = true)


"""
*Bayesian Neural Networks for Julia.*

# Introduction

Formulates and fits Bayesian Neural Network with Variational Inference.

# Examples

```julia
using Distributions, BayesNN, StatsFuns

# data size
N = 1000  # number of observations
D = 3     # number of covariates including intercept

# generate data
srand(1)                  # insure reproducibility
X = rand(Normal(), N, D)  # generate covariates
X[:, 1] = 1               # make first column intercept
b = rand(Normal(), D)     # coefficients
θ = logistic.(X * b)      # simulate dependent variables
y = rand.(Bernoulli.(θ))  # simulate dependent variables

net = Nnet(y, X)
```

"""
module BayesNN

using StatsFuns
using Distributions
using GradDescent
using ForwardDiff

export
    Nnet,
    fit,
    predict

include("helper.jl")
include("propagate.jl")
include("Nnet.jl")

end # module