export build_conv

function _compute_kernel_size(receptive_field, points_per_unit, num_layers)
    receptive_points = receptive_field * points_per_unit
    return ceil_odd(1 + (receptive_points - 1) / num_layers)
end

_compute_padding(kernel_size) = Integer(floor(kernel_size / 2))

_glorot_normal(dims...) = randn(Float32, dims...) .* sqrt(2f0 / sum(dims[1:2]) / dims[3])

_init_conv_fixed_bias(k, ch) =
    (_glorot_normal(k..., ch...), fill(1f-3, ch[2]))
_init_depthwiseconv_fixed_bias(k, ch) =
    (_glorot_normal(k..., div(ch[2], ch[1]), ch[1]), fill(1f-3, ch[2]))

_init_conv_random_bias(k, ch) =
    (_glorot_normal(k..., ch...), 1f-3 .* randn(Float32, ch[2]))
_init_depthwiseconv_random_bias(k, ch) =
    (_glorot_normal(k..., div(ch[2], ch[1]), ch[1]), 1f-3 .* randn(Float32, ch[2]))

_expand_kernel(n, ::Val{1}) = (n, 1)
_expand_kernel(n, ::Val{2}) = (n, n)

_expand_padding(n, ::Val{1}) = (n, 0)
_expand_padding(n, ::Val{2}) = (n, n)

"""
    build_conv(
        receptive_field::Float32,
        num_layers::Integer,
        num_channels::Integer;
        points_per_unit::Float32=30f0,
        multiple::Integer=1,
        num_in_channels::Integer=2,
        num_out_channels::Integer=2,
        dimensionality::Integer=1,
        init_conv::Function=_init_conv_fixed_bias,
        init_depthwiseconv::Function=_init_depthwiseconv_fixed_bias,
        act=x -> leakyrelu(x, 0.1f0)
    )

Build a CNN with a specified receptive field size.

# Arguments
- `receptive_field::Float32`: Width of the receptive field.
- `num_layers::Integer`: Number of layers of the CNN, excluding an initial
    and final pointwise convolutional layer to change the number of channels
    appropriately.
- `num_channels::Integer`: Number of channels of the CNN.

# Keywords
- `points_per_unit::Float32=30f0`: Points per unit for the discretisation. See
     `UniformDiscretisation1D`.
- `multiple::Integer=1`: Multiple for the discretisation. See `UniformDiscretisation1D`.
- `num_in_channels::Integer=2`: Number of input channels.
- `num_out_channels::Integer=2`: Number of output channels.
- `dimensionality::Integer=1`: Dimensionality of the filters.
- `init_conv::Function=_init_conv_fixed_bias`: Initialiser for dense convolutions.
- `init_depthwiseconv::Function=_init_depthwiseconv_fixed_bias`: Initialiser for depthwise
    separable convolutions.
- `act=x -> leakyrelu(x, 0.1f0)`: Activation function to use.

# Returns
- `BatchedConv`: CNN.
"""
function build_conv(
    receptive_field::Float32,
    num_layers::Integer,
    num_channels::Integer;
    points_per_unit::Float32=30f0,
    multiple::Integer=1,
    num_in_channels::Integer=2,
    num_out_channels::Integer=2,
    dimensionality::Integer=1,
    init_conv::Function=_init_conv_fixed_bias,
    init_depthwiseconv::Function=_init_depthwiseconv_fixed_bias,
    act=x -> leakyrelu(x, 0.1f0)
)
    # Appropriate expand the kernels to the right dimensionality.
    kernel = _expand_kernel(
        _compute_kernel_size(receptive_field, points_per_unit, num_layers),
        Val(dimensionality)
    )
    padding = _expand_padding(_compute_padding(kernel[1]), Val(dimensionality))

    # Build layers of the conv net.
    layers = Any[Conv(init_conv((1, 1), num_in_channels=>num_channels)..., act)]
    for i = 1:num_layers
        push!(layers, DepthwiseConv(
            init_depthwiseconv(kernel, num_channels=>num_channels)...,
            pad=padding
        ))
        push!(layers, Conv(
            init_conv((1, 1), num_channels=>num_channels)...,
            act
        ))
    end
    push!(layers, Conv(init_conv((1, 1), num_channels=>num_out_channels)...))

    return BatchedConv(
        Chain(layers...),
        points_per_unit,
        multiple,
        dimensionality
    )
end

"""
    struct BatchedConv

A batched CNN bundled with contextual information.

# Fields
- `conv`: Batched CNN.
- `points_per_unit::Float32`: Points per unit for the discretisation. See
     `UniformDiscretisation1D`.
- `multiple::Integer`: Multiple for the discretisation. See `UniformDiscretisation1D`.
- `dimensionality::Integer`: Dimensionality of the filters.
"""
struct BatchedConv
    conv
    points_per_unit::Float32
    multiple::Integer
    dimensionality::Integer
end

@Flux.functor BatchedConv

(layer::BatchedConv)(x) =  layer(x, Val(layer.dimensionality))

function (layer::BatchedConv)(x, dimensionality::Val{1})
    x, back = to_rank(3, x)        # Compress batch dimensions.
    x = with_dummy(layer.conv, x)  # Dummy dimension required!
    return back(x)                 # Uncompress batch dimensions
end

function (layer::BatchedConv)(x, dimensionality::Val{2})
    x, back = to_rank(4, x)  # Compress batch dimensions.
    x = layer.conv(x)        # No dummy dimension required!
    return back(x)           # Uncompress batch dimensions
end


################################################################################
#  Copyright 2021, Marta Vanin                                                 #
################################################################################
function add_some_noise!(data::Dict)
    Random.seed!(1)
    err_array = 1e-6:1e-6:2e-6
    for (_, meas) in data["meas"]
        σ = _DST.std.(meas["dst"])
        μ = _DST.mean.(meas["dst"])
        new_μ = [μ[i]*(1+rand(err_array)) for i in 1:length(meas["dst"])]
        meas["dst"] = [_DST.Normal(new_μ[i], σ[i]) for i in 1:length(meas["dst"])]
    end
end

function add_errors!(data::Dict; seed::Int64=1)
    for (m, meas) in data["meas"]
        σ = [_DST.std(meas["dst"][i]) for i in 1:length(meas["dst"])]
        new_μ = [_RAN.rand(_RAN.seed!(seed+parse(Int64,m)+i), _DST.Normal(_DST.mean(meas["dst"][i]), _DST.std(meas["dst"][i]))) for i in 1:length(meas["dst"])]
        meas["dst"] = [_DST.Normal(new_μ[i], σ[i]) for i in 1:length(σ)]
    end
end


################################################################################
#
#   fista : Accelerated Forward Backward splitting
#
################################################################################

function fista!{T<:FloatingPoint}(
    rmodel::SupervisedRiskModel,     # model definition (prediction model and loss function)
    θ::StridedArray{T},              # initial parameter guess
    x::StridedArray{T},              # inputs
    y::StridedArray{T};              # responses
    reg::Regularizer = ZeroReg(),    # regularizer
    λ::T             = one(T),       # step size
    cbinterval::Int  = 0,            # callback interval
    callback         = fista_trace,    # callback function
    maxsteps::Int    = 100,          # max number of steps
    maxtime::Int     = 0         )   # max calculation time (0 = no limit)

    g      = similar(θ)  # holds gradient
    dualθ  = copy(θ)     # dual θ
    prevθ  = copy(θ)     # θ of previous step
    deltaθ = similar(θ)  # variation of theta

    function fbstep!(θ1, g, θ0, λ)
        copy!(θ1, θ0)
        axpy!(-λ, g, θ1)
        prox!(reg, θ1, θ1, λ)
    end

    α    = 1.      # acceleration parameter
    starttime = time()
    for t in 1:maxsteps
        if (maxtime != 0) && (time() - starttime > maxtime)
            break
        end

        v_risk, _ = value_and_addgrad!(rmodel, 0., g, 1., dualθ, x, y)
        fbstep!(θ, g, dualθ, λ)
        deltaθ = θ - prevθ
        if dot(g, deltaθ) >= 0.
            α = 1.
            μ = 0.
        else
            prevα = α
            α = 1. + sqrt(1+4α*α)/2.
            μ = (prevα - 1.) / α
        end
        copy!(dualθ, θ)
        axpy!(μ, deltaθ, dualθ)
        copy!(prevθ, θ)

        # callback
        if cbinterval > 0 && t % cbinterval == 0
            callback(t, θ, v_risk, value(reg, θ), α)
        end
    end

    θ
end

# Non mutating version
function fista{T<:FloatingPoint}(
    rmodel::SupervisedRiskModel,     # model definition
    θ::StridedArray{T},              # initial parameter guess
    x::StridedArray{T},              # inputs
    y::StridedArray{T};              # responses
    vargs... )

    fista!(rmodel, copy(θ), x, y; vargs...)
end


@testset "fit moments numerical" begin
    DN = LogitNormal(1.3)
    x = rand(DN, 1_000_000);
    m = mean(DN)
    @test abs(m - mean(x))/mean(x) <= 1e-3
    s2 = var(DN)
    @test abs(s2 - var(x))/var(x) <= 1e-2 #1e-4 too strong for random numbers
    s2m = var(DN; mean=m) # specify so that not need to recompute
    @test abs(s2m - var(x))/var(x) <= 1e-2 #1e-4 too strong for random numbers
    sd = std(DN) # specify so that not need to recompute
    @test abs(sd - std(x))/std(x) <= 1e-2 #1e-4 too strong for random numbers
    # sd2 = std(DN, mean=m) # specify so that not need to recompute
    # @test abs(sd2 - std(x))/std(x) <= 1e-2 #1e-4 too strong for random numbers
end;

function test_lognormal_mode(g)
    mo = mode(g)
    @test pdf(g, mo-1e-6) < pdf(g,mo) && pdf(g, mo+1e-6) < pdf(g,mo)
end

###### numerical estimation of moments
@testset "Logitnormal seek mode" begin
    #plot(g); vline!([mode(g)])
    # median
    g = LogitNormal(); test_lognormal_mode(g)
    g = LogitNormal(0,1.4); test_lognormal_mode(g)
    # larger one of two modes, infer the lower one
    g = LogitNormal(0,1.6); test_lognormal_mode(g)
    # two modes, larger is on the right
    g = LogitNormal(0.1,1.6); test_lognormal_mode(g)
    # two modes, larger is on the left
    g = LogitNormal(-0.1,1.6); test_lognormal_mode(g)
end

@testset "fit two quantiles" begin
    qpl = @qp_m(0.3)
    qpu = @qp_u(0.9)
    d = fit(LogitNormal, qpl, qpu);
    @test quantile.(d, [qpl.p, qpu.p]) ≈ [qpl.q, qpu.q]
    d = fit(LogitNormal, qpu, qpl) # sort
    @test quantile.(d, [qpl.p, qpu.p]) ≈ [qpl.q, qpu.q]
end;

@testset "fit to quantilepoint and mean" begin
    # not implemented
end;

@testset "fit to quantilepoint and mode" begin
    # see LogitNormals.jl
    # d = LogitNormal(1,1)
    # m = log(mode(d))
    # qp = @qp(quantile(d,0.95),0.95)
    # dfit = fit_mode_quantile(LogitNormal, mode(d), qp)
    # @test dfit ≈ d
    # dfit = fit(LogitNormal, mode(d), qp, Val(:mode))
    # @test dfit ≈ d
    # # with lower quantile
    # qp = @qp(quantile(d,0.025),0.025)
    # dfit = fit_mode_quantile(LogitNormal, mode(d), qp)
    # @test mode(dfit) ≈ mode(d) && quantile(dfit, qp.p) ≈ qp.q
end;

@testset "fit to quantilepoint and median" begin
    d = LogitNormal(1,1)
    qp = @qp(quantile(d,0.95),0.95)
    dfit = fit(LogitNormal, median(d), qp, Val(:median))
    @test dfit ≈ d
end;

@testset "fit by mode and upper quantile" begin
    #plot(g); vline!([mode(g)])
    # median
    #g = fit_mode_quantile(LogitNormal, 0.68, @qp(0.71,0.99))
    g = fit(LogitNormal, 0.68, @qp_u(0.9), Val(:mode))
    @test mode(g) ≈ 0.68
    @test quantile(g, 0.95) ≈ 0.9
    #plot(g)
    # call with differt subtype of Real, here Rational
    g = fit(LogitNormal, 2//3, @qp_u(0.9), Val(:mode))
    @test mode(g) ≈ 2//3
    @test quantile(g, 0.95) ≈ 0.9
end

function is_logit_slope_monotone(d, upper=0.5, lower=0.0, decreasing = false)
  x = range(lower,upper;length=41)[2:40]	# plotting grid
  dx = pdf.(d, x)	#density function
  if decreasing
    all(diff(dx) .<= 0)
  else
    all(diff(dx) .>= 0)
  end
end

@testset "fit by single mode and flat" begin
    d9 = d = fit_mode_flat(LogitNormal, 0.9)
    @test isapprox( mode(d), 0.9, atol=1e-4)
    @test is_logit_slope_monotone(d, 0.9)
    @test is_logit_slope_monotone(d, 0.0, 0.9, true)
    d1 = d = fit_mode_flat(LogitNormal, 0.1)
    @test isapprox( mode(d), 0.1, atol=1e-4)
    @test is_logit_slope_monotone(d, 0.1)
    @test is_logit_slope_monotone(d, 0.0, 0.1, true)
    @test d1.σ == d9.σ
    @test d1.μ == -d9.μ
    d5 = d = fit_mode_flat(LogitNormal, 0.5)
    @test isapprox( mode(d), 0.5, atol=1e-4)
    @test is_logit_slope_monotone(d, 0.5)
    @test is_logit_slope_monotone(d, 0.0, 0.5, true)
    #plot(d1)
end

@testset "shifloNormal" begin
    d = shifloNormal(1,3)
    @test isapprox(mode(d), 2.0, atol=0.001)
    @test minimum(d) == 1.0
    @test maximum(d) == 3.0
    @test scale(d) == 3-1
    @test location(d) == 1.0
    #plot(d)
end;


"""
heqtest(n=100, c=.5, printh="yes"; jprecision=Float64, jmaxit=10)

Generates the examples for the SIREV paper.
"""
function heqtest(n = 100, c = 0.5, printh = "yes"; jprecision = Float64, jmaxit = 10)
    FS = ones(n)
    x0 = ones(n)
    hdata = heqinit(x0, n, c)
    FPS = ones(jprecision, n, n)
    nsoldoutfd = nsold01(
        x0,
        FS,
        FPS,
        heqf!;
        atol = 1.e-18,
        rtol = 1.e-18,
        pdata = hdata,
        maxit = jmaxit,
    )
    nsoldoutex = nsold01(
        x0,
        FS,
        FPS,
        heqf!,
        heqJ!;
        atol = 1.e-18,
        rtol = 1.e-18,
        pdata = hdata,
        maxit = jmaxit,
    )
    if printh == "yes"
        sol = nsoldoutex.solution
        cpout = chandprint(sol, hdata)
        for i = 1:21
            @printf("%1.2e      %1.5e \n", cpout[i, 1], cpout[i, 2])
        end
        @printf("\n \n \n")
        println(nsoldoutex.ithist)
    end
    return (exactout = nsoldoutex, fdout = nsoldoutfd)
end


#--- AbstractMesh

"""
Abastract type AbastractMesh

AstractMesh is a interface defined by overloading the following methods:

- [`get_coord_vector(trian::Triangulation)`](@ref)
- [`get_cell_vector`](trian::Triangulation)`](@ref)
"""

abstract type AbstractMesh <: JalerkinType end

"""
get_coord_vector(Mesh::AbstractMesh) -> AbstractArray{Vector{<:Point}}}
"""
function get_coord_vector(mesh::AbstractMesh)
    @abstractmethod
end

"""
get_cell_vector(Mesh::AbstractMesh) -> AbstractArray{Vector{<:Cell}}}
"""
function get_cell_vector(mesh::AbstractMesh)
    @abstractmethod
end

#--- Mesh
"""Type Point (given dimension, type of real numbers).

Static Vector of Dim real numbers
"""
Point = SVector{Dim, fp_precision()} where {Dim}

"""Type Cell (given number of vértices)

Static vector of indices (identifiers of the cell points)
"""
Cell = SVector{NumVertex, Int64} where {NumVertex}

"""Mesh will contain sets of Cells and Vertices (Points)"""
struct Mesh{CellType, VertexType}  <: AbstractMesh
    "Vector of cells (each cell contains a list of vertex indices)"
    cell_vector :: Vector{CellType}

    "Vector of coordinates of each vertex"
    coord_vector :: Vector{VertexType}
end

"""
This function is part of the interface AbstractMesh
"""
@inline function get_coord_vector(mesh::Mesh{CellType, VertexType}) where {CellType, VertexType}
    mesh.coord_vector
end

"""
This function is part of the interface AbstractMesh
"""
@inline function get_cell_vector(mesh::Mesh{CellType, VertexType}) where {CellType, VertexType}
    mesh.cell_vector
end

"Constructor for a 1D mesh in the interval [x1, x2]"
function Mesh1D(x1, x2, ncells)
    CellType = Cell{2}
    VertexType = Point{1}
    cells = [CellType(i, i+1) for i=1:ncells]
    h = (x2-x1)/ncells
    vertices = [VertexType(x1 + h*(i-1)) for i=1:ncells+1]
    Mesh{Cell{2}, Point{1}}(cells, vertices)
end


using DiffEqBase, OrdinaryDiffEq

type SimType{T} <: DEDataArray{T}
    x::Array{T,1}
    f1::T
end

f = function (t,u,du)
    du[1] = -0.5*u[1] + u.f1
    du[2] = -0.5*u[2]
end

const tstop1 = [5.]
const tstop2 = [8.]
const tstop = [5.;8.]

condition = function (t,u,integrator)
  t in tstop1
end

affect! = function (integrator)
  for c in user_cache(integrator)
    c.f1 = 1.5
  end
end

save_positions = (true,true)

cb = DiscreteCallback(condition, affect!, save_positions=save_positions)

condition2 = function (t,u,integrator)
  t in tstop2
end

affect2! = function (integrator)
  for c in user_cache(integrator)
    c.f1 = -1.5
  end
end

save_positions = (false,true)

cb2 = DiscreteCallback(condition2, affect2!, save_positions=save_positions)

cbs = CallbackSet(cb,cb2)

u0 = SimType{Float64}([10;10], 0.0)
prob = ODEProblem(f,u0,(0.0,10.0))
sol = solve(prob,Tsit5(),callback = cbs, tstops=tstop)

sol(1.5:0.5:2.5)

@test [sol[i].f1 for i in eachindex(sol)] == [zeros(9);1.5*ones(5);-1.5*ones(4)]


### A Pluto.jl notebook ###
# v0.16.1

using Markdown
using InteractiveUtils

# ╔═╡ 4b75cde6-db35-11eb-1b95-bba4e05edb2f
begin
    using VoronoiFVM
    using ExtendableGrids
    using GridVisualize
	using PlutoVista
	GridVisualize.default_plotter!(nothing)
    using PlutoUI
	using LinearAlgebra
    if isdefined(Main,:PlutoRunner)
        GridVisualize.default_plotter!(PlutoVista)
    end
end

# ╔═╡ 3fa189e4-9e1c-470c-bf26-15b631945d2d
md"""
# Interfaces conditions and APIs

## Two subdomains, species number based API
For a simple stationary diffusion equation with an interior interface, we discuss possible interface conditions between two subdomains.

Let ``\Omega=\Omega_1\cup\Omega_2`` where ``\Omega_1=(-1,0)`` and ``\Omega_2=(0,1)``.
Let ``\Gamma_1={-1}``,``\Gamma_2={1}``  and ``\Gamma_3={0}``.

Regard the following problem:

``\begin{aligned}
     -\Delta u_1 &= 0 & \text{in}\quad \Omega_1\\
     -\Delta u_2 &= 0 & \text{in}\quad \Omega_2\\
\end{aligned}
``

with exterior boundary conditions

``u_1|_{\Gamma_1} = g_1`` and ``u_2|_{\Gamma_2} = g_2``

For the interior boundary (interface) conditons we set

``\nabla u_1|_{\Gamma_3}+f_1(u_1,u_2)=0``

``-\nabla u_2|_{\Gamma_3}+f_2(u_1,u_2)=0``

where ``f_1``, ``f_2`` are discussed later.
"""

# ╔═╡ 327af4a8-74cc-4834-ab19-d3a1d6873982
TableOfContents(title="")

# ╔═╡ d5a0ee0d-959d-476b-b3c5-79b741059992
md"""
### Set up
"""

# ╔═╡ f03ff283-c989-4b1a-b73e-2e616054e3db
md"""
Create a grid with two subdomins and an interface in the center.
"""

# ╔═╡ 670c78c1-d0be-4362-975b-2c944620681f
nref=2

# ╔═╡ 79193d53-9dfa-47b7-aed2-c7eb43769b5f
begin
	hmax=0.2/2.0^nref
    hmin=0.05/2.0^nref
    X1=geomspace(-1.0,0.0, hmax,hmin)
    X2=geomspace(0.0,1.0, hmin,hmax)
    X=glue(X1,X2)
    grid=VoronoiFVM.Grid(X)

    bfacemask!(grid, [0.0],[0.0],3)
    ## Material 1 left of 0
    cellmask!(grid, [-1.0],[0.0],1)
    ## Material 2 right of 0
    cellmask!(grid, [0.0],[1.0],2)
end;

# ╔═╡ 4cb07222-587b-4a74-a444-43f5433d5b03
gridplot(grid,legend=:rt,resolution=(600,200))

# ╔═╡ 02ec3c0b-6e68-462b-84df-931370cbdcac
md"""
For later use (plotting) extract the two subgrids from the grid
"""

# ╔═╡ 592429a1-108c-4e9e-8961-497c2c31f319
subgrid1=subgrid(grid,[1]);

# ╔═╡ 1fa5d9b3-0558-4558-a5f1-f34f70c8d9a0
subgrid2=subgrid(grid,[2]);

# ╔═╡ 5b539ad2-46d6-43b0-8b3e-f8a7e1ae0a6d
md"""
Define the diffusion flux
"""

# ╔═╡ 6aabfbe1-de7d-49ba-8144-6d364b21b34f
    function flux!(f,u,edge)
        if edge.region==1
            f[1]=u[1,1]-u[1,2]
        end
        if edge.region==2
            f[2]=u[2,1]-u[2,2]
        end
    end

# ╔═╡ dbd27f10-9fd9-450d-9f78-89ea738d605b
md"""
Specify  the outer boundary values.
"""

# ╔═╡ e5b432b2-875e-49a5-8e78-68f7f47f06c3
g_1=1.0

# ╔═╡ 8ae32292-df3e-4190-83a5-ec5ba529299e
g_2=0.1

# ╔═╡ 677853a7-0c43-4d3c-bc74-799535f95aeb
md"""
Create the system. We pass the interface condition function as a parameter.
"""

# ╔═╡ 139058a9-44a0-43d5-a377-4fc72927fa28
function make_system(breaction)
	physics=VoronoiFVM.Physics(
        flux=flux!,
        breaction=breaction
    )

    ## Create system
    sys=VoronoiFVM.System(grid,physics,unknown_storage=:sparse)

    ##  put potential into both regions
    enable_species!(sys,1,[1])
    enable_species!(sys,2,[2])

    ## Set boundary conditions
    for ispec=1:2
        boundary_dirichlet!(sys,ispec,1,g_1)
        boundary_dirichlet!(sys,ispec,2,g_2)
    end
	sys
end

# ╔═╡ de251b03-dd3a-4a44-9440-b7e654c32dac
md"""
Stationary solution with zero initial value
"""

# ╔═╡ 5b3a49d6-30ce-4d6e-94b4-a76645d7d8ce
function mysolve(sys)
	U=solve(unknowns(sys,inival=0),sys)
	U1=view(U[1,:],subgrid1)
    U2=view(U[2,:],subgrid2)
	U1,U2
end

# ╔═╡ 0735c061-68e1-429f-80f1-d8410989a91d
md"""
Plot the results
"""

# ╔═╡ 467dc381-3b3d-4de7-a7f9-bfc51300832b
function plot(U1,U2;title="")
	vis=GridVisualizer(resolution=(600,300))
    scalarplot!(vis,subgrid1,U1,clear=false,show=false, color=:green, label="u1")
    scalarplot!(vis,subgrid2,U2,clear=false,show=true, color=:blue, label="u2",legend=:rt,title=title,flimits=(-0.5,1.5))
end

# ╔═╡ fa4fcc0d-1d3a-45a2-8857-50536bbe39cc
md"""
### No interface reaction

This means we set ``f_1(u_1,u_2)=0`` and ``f_2(u_1,u_2)=0``.
"""

# ╔═╡ 8f210696-fcf4-47bc-a5a2-c561ad7efcbd
function noreaction(f,u,node) end

# ╔═╡ 57e8515e-3be1-4478-af98-430501438ee7
system1=make_system(noreaction);

# ╔═╡ 56136cd1-0c01-449d-9297-68924ac99ee7
plot(mysolve(system1)...)

# ╔═╡ aad305a9-aac6-4aff-9f8e-08d6a2f756c8
md"""
### Mass action law reaction ``u_1 \leftrightharpoons u_2``

This is a rather general ansatz where we assume a backward-forward reaction at the interface with reaction constants ``k_1`` and ``k_2``, respectively.

According to the mass action law, this tranlates to a reaction rate

``r(u_1,u_2)=k_1u_1 - k_2u_2``

and correspondingly

``f_1(u_1,u_2)=r``

``f_2(u_1,u_2)=-r``

Note, that ``f_i`` is monotonically increasing in ``u_i`` and monotonically decreasing in the respective other argument, leading to an M-Property of the overall discretization matrix.

Note that the "no reaction" case is just a special case where ``k_1,k_2=0``.
"""

# ╔═╡ d027ff24-3ad1-4528-b5cf-10814caf30db
k1=0; k2=10;

# ╔═╡ 1328b4bf-2d64-4b02-a910-1995da8be28b
function mal_reaction(f,u,node)
        if node.region==3
            react=k1*u[1]-k2*u[2]
            f[1]= react
            f[2]= -react

        end
end

# ╔═╡ 610a0761-1c23-415d-a187-f7d93a1b7637
system2=make_system(mal_reaction)

# ╔═╡ 87edce1f-df6d-4cd8-bce5-24fb666cd6b5
begin
	k1,k2
	U1,U2=mysolve(system2)
    plot(U1,U2;title="k1=$(k1), k2=$(k2)")
end

# ╔═╡ b82fc6b2-eee1-4a91-a115-61b86621f686
md"""
### Penalty enforcing continuity

Setting ``k_1,k_2`` to a large number leads to another special case of the above reaction - similar to the penalty method to implement the Dirichlet boundary conditions, this lets the reaction equation dominate, which in this case forces
``u_1-u_2=0`` at the interface, and thus continuity.
"""

# ╔═╡ 9eaea813-2628-47d0-9d36-54c367689142
function penalty_reaction(f,u,node)
        if node.region==3
            react=1.0e10*(u[1]-u[2])
            f[1]= react
            f[2]= -react
        end
end

# ╔═╡ 817738c0-f1a3-4779-9075-7ea051a81e73
system3=make_system(penalty_reaction);

# ╔═╡ 80019ef1-bf41-4a55-9262-613a2d20be1f
plot(mysolve(system3)...)

# ╔═╡ 31e00855-8906-4be5-8e69-e2d8d9539e04
md"""
### Interface recombination

Here, we implement an annihilation reaction ``u_1 + u_2 \to \emptyset``
Acoording to the mass action law, this is implemented via

``r(u_1,u_2)=k_r u_1 u_2``

``f_1(u_1,u_2)=r``

``f_2(u_1,u_2)=r``

"""

# ╔═╡ f2490f99-04ca-4f42-af2a-53adae51ca68
k_r=1000

# ╔═╡ 39a0db1b-3a4e-4108-b43f-d4e578c92608
function recombination(f,u,node)
        if node.region==3
            react=k_r*(u[1]*u[2])
            f[1]= react
            f[2]= react
        end
end;

# ╔═╡ 644149fb-2264-42bd-92c9-193ab07c08f6
system4=make_system(recombination);

# ╔═╡ b479402f-ef00-4425-8b0f-45f2dae74d80
plot(mysolve(system4)...)

# ╔═╡ ed068b51-92af-48d5-9230-debc178ec827
md"""
### Thin  conductive interface layer

Let us assume that the interface is of thickness $d$ which is however small with respect to ``\Omega`` that we want to derive an interface condition from the assumption of an exact continuous solution within the interface.

So let ``\Omega_I=(x_l,x_r)`` be  the interface region where
we have ``-\Delta u_I=0`` with values ``u_l``, ``u_r`` at the boundaries.

Then we have for the flux  in the interface region, ``q_I=\nabla u = \frac1{d}(u_r - u_l)``

Continuity of fluxes then gives ``f_1=q_I`` and ``f_2=-q_I``.

Continuity of ``u`` gives ``u_{1,I}=u_l, u_{2,I}=u_r``
This gives

``r=q_I=\frac{1}{d}(u_1-u_{2})``

``f_1(u_1,v_1)=r``

``f_2(u_1,v_1)=-r``

and therefore another special case of the mass action law condition.
"""

# ╔═╡ a2d919a5-a395-40fb-8f93-db742f8a77c2
d=1

# ╔═╡ 58d8831b-ad66-4f77-a33a-933c15c46a52
function thinlayer(f,u,node)
        if node.region==3
            react=(u[1]-u[2])/d
            f[1]= react
            f[2]= -react
        end
end

# ╔═╡ 8c0b4ab5-09da-4d8f-b001-5e15f823423c
system5=make_system(thinlayer);

# ╔═╡ d3d99b9c-ad18-4a04-bb3e-f17dd542f9f3
plot(mysolve(system5)...)

# ╔═╡ eb9abf2e-372c-4f79-afbe-772b90eff9ad
md"""
## Quantity based API

From this discussion it seems that discontinuous interface conditions can be formulated in a rather general way via linear or nonlinear robin boundary conditions for each of the adjacent discontinuous species. Technically, it is necessary to be able to access the adjacent bulk data.
"""

# ╔═╡ 9f3ae7b5-51b3-48bc-b4db-b7236ba30682
md"""
Here, we propose an API layer on top  of the species handling of VoronoiFVM.
For a start, we call these "Meta species" "quantities". There may be a different name, however.
"""

# ╔═╡ 2da8a5b1-b168-41d9-baa8-d65a4ef5c4c0
md"""
We define a grid with N subregions
"""

# ╔═╡ d44407de-8c9c-42fa-b1a2-ae02b826eccc
N=6

# ╔═╡ ae268316-c058-4db8-9b71-57b0d9425274
begin
	XX=collect(0:0.1:1)
	local xcoord=XX
	for i=1:N-1
		xcoord=glue(xcoord,XX.+i)
	end
	grid2=simplexgrid(xcoord)
	for i=1:N
		cellmask!(grid2,[i-1],[i],i)
	end
	for i=1:N-1
		bfacemask!(grid2,[i],[i],i+2)
	end
end

# ╔═╡ b53b9d28-4c25-4fb8-a3e4-599b0e385121
gridplot(grid2,legend=:lt,resolution=(600,200))

# ╔═╡ e7ce7fd4-cfa4-4cc6-84a2-7e20ed2f4e5c
md"""
To work with quantities, we first introduce a new constructor call without the "physics" parameter:
"""

# ╔═╡ 29f36902-e355-4b02-b7b0-c4db12c47d33
system6=VoronoiFVM.System(grid2)

# ╔═╡ 673e9320-ea30-4831-ad85-ba7936293ee2
md"""
First, we introduce a continuous quantity which we name "cspec". Note that the "species number" can be assigned automatically if not given explicitely.
"""

# ╔═╡ f35f419a-94dd-4051-a533-4b1ec9a4c9ec
cspec=ContinuousQuantity(system6,1:N,ispec=1)

# ╔═╡ 9661e4fc-55e1-4c2c-a3ad-515cdac3b514
md"""
A discontinuous quantity can be introduced as well. by default, each reagion gets a new species number. This can be overwritten by the user.
"""

# ╔═╡ 90298676-fda7-4168-8a40-7ff53e7c761b
dspec=DiscontinuousQuantity(system6,1:N; regionspec=[2+i%2 for i=1:N])

# ╔═╡ 7a819522-55e4-4547-a3e0-e047e74cfb6b
system6

# ╔═╡ cebabf33-e769-47bd-b6f1-ddf525fea895
md"""
For both quantities, we define simple diffusion fluxes:
"""

# ╔═╡ 719f206a-5b9f-4d78-8778-1d89edb2bc4d
	function flux2(f,u,edge)
		f[dspec]=u[dspec,1]-u[dspec,2]
		f[cspec]=u[cspec,1]-u[cspec,2]
	end

# ╔═╡ 1d7f442f-c057-4379-8a40-c6ce3646ad5c
md"""
Define a thin layer inteface condition for `dspec` and an interface source for `cspec`.
"""

# ╔═╡ da41b22e-114d-4eee-81d0-73e6f3b45242
md"""
Add physics to the system, set dirichlet bc at both ends, and extract subgrids
for plotting (until there will be a plotting API for this...)
"""

# ╔═╡ 0c3ca093-e011-409c-be58-e617e02f5d4c
function plot2(U,subgrids,system6)
	dvws=VoronoiFVM.views(U,dspec,subgrids,system6)
	cvws=VoronoiFVM.views(U,cspec,subgrids,system6)
	vis=GridVisualizer(resolution=(600,300))
	for i=1:length(dvws)
		scalarplot!(vis,subgrids[i],dvws[i],flimits=(-0.5,1.5),clear=false,color=:red)
		scalarplot!(vis,subgrids[i],cvws[i],flimits=(-0.5,1.5),clear=false,color=:green)
 	end
	reveal(vis)
end

# ╔═╡ b8cd6ad1-d323-4888-bbd1-5deba5a5870d
d1=0.1

# ╔═╡ 441a39a0-a7de-47db-8539-12dee30b8312
q1=0.2

# ╔═╡ d6e1c6c7-060d-4c2f-8054-d8f33f54bd55
function breaction2(f,u,node)
	       if node.region>2
            react=(u[dspec,1]-u[dspec,2])/d1
            f[dspec,1]= react
            f[dspec,2]= -react

		    f[cspec]=-q1
        end
end

# ╔═╡ 59c83a22-a4cc-4b51-a1cc-5eb39588eacd
begin
	physics!(system6,VoronoiFVM.Physics(
        flux=flux2,
        breaction=breaction2
    ))

    ## Set boundary conditions
    boundary_dirichlet!(system6,dspec,1,g_1)
    boundary_dirichlet!(system6,dspec,2,g_2)
    boundary_dirichlet!(system6,cspec,1,0)
    boundary_dirichlet!(system6,cspec,2,0)
	subgrids=VoronoiFVM.subgrids(dspec,system6)
end;

# ╔═╡ de119a22-b695-4b4f-8e04-b7d68ec1e91b
subgrids;sol6=solve(unknowns(system6,inival=0),system6)

# ╔═╡ 5d8aca85-f12d-4a6e-84e4-781d45b65742
plot2(sol6,subgrids,system6)

# ╔═╡ 6203d95b-13d2-48a6-a69f-39e33e2edcdb
md"""

## Open problems with current implementation
- Testfunctions: this will be a another problem to be handeled
- Alternative is "glueing together" systems
- Forwarddiff always sees species all species, leading to some overhead
"""

# ╔═╡ 4d8e81c1-dbec-4379-9ab7-a585a369582d
if d1==0.1
@assert norm(system6,sol6,2)==7.0215437706445245
end

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
ExtendableGrids = "cfc395e8-590f-11e8-1f13-43a2532b2fa8"
GridVisualize = "5eed8a63-0fb0-45eb-886d-8d5a387d12b8"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
PlutoVista = "646e1f28-b900-46d7-9d87-d554eb38a413"
VoronoiFVM = "82b139dc-5afc-11e9-35da-9b9bdfd336f3"

[compat]
ExtendableGrids = "~0.8.3"
GridVisualize = "~0.3.4"
PlutoUI = "~0.7.16"
PlutoVista = "~0.8.1"
VoronoiFVM = "~0.13.1"
"""

# ╔═╡ 00000000-0000-0000-0000-000000000002
PLUTO_MANIFEST_TOML_CONTENTS = """
# This file is machine-generated - editing it directly is not advised

julia_version = "1.7.0-rc1"
manifest_format = "2.0"

[[deps.AbstractTrees]]
git-tree-sha1 = "03e0550477d86222521d254b741d470ba17ea0b5"
uuid = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
version = "0.3.4"

[[deps.Adapt]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "84918055d15b3114ede17ac6a7182f68870c16f7"
uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
version = "3.3.1"

[[deps.ArgTools]]
uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"

[[deps.ArnoldiMethod]]
deps = ["LinearAlgebra", "Random", "StaticArrays"]
git-tree-sha1 = "f87e559f87a45bece9c9ed97458d3afe98b1ebb9"
uuid = "ec485272-7323-5ecc-a04f-4719b315124d"
version = "0.1.0"

[[deps.ArrayInterface]]
deps = ["Compat", "IfElse", "LinearAlgebra", "Requires", "SparseArrays", "Static"]
git-tree-sha1 = "b8d49c34c3da35f220e7295659cd0bab8e739fed"
uuid = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9"
version = "3.1.33"

[[deps.Artifacts]]
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"

[[deps.Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"

[[deps.Cassette]]
git-tree-sha1 = "6ce3cd755d4130d43bab24ea5181e77b89b51839"
uuid = "7057c7e9-c182-5462-911a-8362d720325c"
version = "0.3.9"

[[deps.ChainRulesCore]]
deps = ["Compat", "LinearAlgebra", "SparseArrays"]
git-tree-sha1 = "74e8234fb738c45e2af55fdbcd9bfbe00c2446fa"
uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
version = "1.8.0"

[[deps.ColorSchemes]]
deps = ["ColorTypes", "Colors", "FixedPointNumbers", "Random"]
git-tree-sha1 = "a851fec56cb73cfdf43762999ec72eff5b86882a"
uuid = "35d6a980-a343-548e-a6ea-1d62b119f2f4"
version = "3.15.0"

[[deps.ColorTypes]]
deps = ["FixedPointNumbers", "Random"]
git-tree-sha1 = "024fe24d83e4a5bf5fc80501a314ce0d1aa35597"
uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
version = "0.11.0"

[[deps.Colors]]
deps = ["ColorTypes", "FixedPointNumbers", "Reexport"]
git-tree-sha1 = "417b0ed7b8b838aa6ca0a87aadf1bb9eb111ce40"
uuid = "5ae59095-9a9b-59fe-a467-6f913c188581"
version = "0.12.8"

[[deps.CommonSubexpressions]]
deps = ["MacroTools", "Test"]
git-tree-sha1 = "7b8a93dba8af7e3b42fecabf646260105ac373f7"
uuid = "bbf7d656-a473-5ed7-a52c-81e309532950"
version = "0.3.0"

[[deps.Compat]]
deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"]
git-tree-sha1 = "31d0151f5716b655421d9d75b7fa74cc4e744df2"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "3.39.0"

[[deps.CompilerSupportLibraries_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"

[[deps.DataAPI]]
git-tree-sha1 = "cc70b17275652eb47bc9e5f81635981f13cea5c8"
uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
version = "1.9.0"

[[deps.DataStructures]]
deps = ["Compat", "InteractiveUtils", "OrderedCollections"]
git-tree-sha1 = "7d9d316f04214f7efdbb6398d545446e246eff02"
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
version = "0.18.10"

[[deps.DataValueInterfaces]]
git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464"
version = "1.0.0"

[[deps.Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"

[[deps.DelimitedFiles]]
deps = ["Mmap"]
uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"

[[deps.DiffResults]]
deps = ["StaticArrays"]
git-tree-sha1 = "c18e98cba888c6c25d1c3b048e4b3380ca956805"
uuid = "163ba53b-c6d8-5494-b064-1a9d43ac40c5"
version = "1.0.3"

[[deps.DiffRules]]
deps = ["NaNMath", "Random", "SpecialFunctions"]
git-tree-sha1 = "7220bc21c33e990c14f4a9a319b1d242ebc5b269"
uuid = "b552c78f-8df3-52c6-915a-8e097449b14b"
version = "1.3.1"

[[deps.Distributed]]
deps = ["Random", "Serialization", "Sockets"]
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"

[[deps.DocStringExtensions]]
deps = ["LibGit2"]
git-tree-sha1 = "a32185f5428d3986f47c2ab78b1f216d5e6cc96f"
uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
version = "0.8.5"

[[deps.Downloads]]
deps = ["ArgTools", "LibCURL", "NetworkOptions"]
uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"

[[deps.EarCut_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "3f3a2501fa7236e9b911e0f7a588c657e822bb6d"
uuid = "5ae413db-bbd1-5e63-b57d-d24a61df00f5"
version = "2.2.3+0"

[[deps.ElasticArrays]]
deps = ["Adapt"]
git-tree-sha1 = "a0fcc1bb3c9ceaf07e1d0529c9806ce94be6adf9"
uuid = "fdbdab4c-e67f-52f5-8c3f-e7b388dad3d4"
version = "1.2.9"

[[deps.ExtendableGrids]]
deps = ["AbstractTrees", "Dates", "DocStringExtensions", "ElasticArrays", "InteractiveUtils", "LinearAlgebra", "Printf", "Random", "SparseArrays", "Test"]
git-tree-sha1 = "85dbe70afc7153ad510577f158214b45be22593b"
uuid = "cfc395e8-590f-11e8-1f13-43a2532b2fa8"
version = "0.8.3"

[[deps.ExtendableSparse]]
deps = ["DocStringExtensions", "LinearAlgebra", "Printf", "Requires", "SparseArrays", "SuiteSparse", "Test"]
git-tree-sha1 = "0341e41e45e6c5e46be89c33543082a0a867707c"
uuid = "95c220a8-a1cf-11e9-0c77-dbfce5f500b3"
version = "0.6.5"

[[deps.FileIO]]
deps = ["Pkg", "Requires", "UUIDs"]
git-tree-sha1 = "3c041d2ac0a52a12a27af2782b34900d9c3ee68c"
uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
version = "1.11.1"

[[deps.FillArrays]]
deps = ["LinearAlgebra", "Random", "SparseArrays", "Statistics"]
git-tree-sha1 = "8756f9935b7ccc9064c6eef0bff0ad643df733a3"
uuid = "1a297f60-69ca-5386-bcde-b61e274b549b"
version = "0.12.7"

[[deps.FiniteDiff]]
deps = ["ArrayInterface", "LinearAlgebra", "Requires", "SparseArrays", "StaticArrays"]
git-tree-sha1 = "8b3c09b56acaf3c0e581c66638b85c8650ee9dca"
uuid = "6a86dc24-6348-571c-b903-95158fe2bd41"
version = "2.8.1"

[[deps.FixedPointNumbers]]
deps = ["Statistics"]
git-tree-sha1 = "335bfdceacc84c5cdf16aadc768aa5ddfc5383cc"
uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
version = "0.8.4"

[[deps.ForwardDiff]]
deps = ["CommonSubexpressions", "DiffResults", "DiffRules", "LinearAlgebra", "NaNMath", "Printf", "Random", "SpecialFunctions", "StaticArrays"]
git-tree-sha1 = "c4203b60d37059462af370c4f3108fb5d155ff13"
uuid = "f6369f11-7733-5829-9624-2563aa707210"
version = "0.10.20"

[[deps.GeometryBasics]]
deps = ["EarCut_jll", "IterTools", "LinearAlgebra", "StaticArrays", "StructArrays", "Tables"]
git-tree-sha1 = "58bcdf5ebc057b085e58d95c138725628dd7453c"
uuid = "5c1252a2-5f33-56bf-86c9-59e7332b4326"
version = "0.4.1"

[[deps.GridVisualize]]
deps = ["ColorSchemes", "Colors", "DocStringExtensions", "ElasticArrays", "ExtendableGrids", "GeometryBasics", "LinearAlgebra", "Observables", "OrderedCollections", "PkgVersion", "Printf", "Requires", "StaticArrays"]
git-tree-sha1 = "0cf1ee93e7b6e88eb0b7e5122e72c7d88102bbc5"
uuid = "5eed8a63-0fb0-45eb-886d-8d5a387d12b8"
version = "0.3.4"

[[deps.Hyperscript]]
deps = ["Test"]
git-tree-sha1 = "8d511d5b81240fc8e6802386302675bdf47737b9"
uuid = "47d2ed2b-36de-50cf-bf87-49c2cf4b8b91"
version = "0.0.4"

[[deps.HypertextLiteral]]
git-tree-sha1 = "f6532909bf3d40b308a0f360b6a0e626c0e263a8"
uuid = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2"
version = "0.9.1"

[[deps.IOCapture]]
deps = ["Logging", "Random"]
git-tree-sha1 = "f7be53659ab06ddc986428d3a9dcc95f6fa6705a"
uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89"
version = "0.2.2"

[[deps.IfElse]]
git-tree-sha1 = "28e837ff3e7a6c3cdb252ce49fb412c8eb3caeef"
uuid = "615f187c-cbe4-4ef1-ba3b-2fcf58d6d173"
version = "0.1.0"

[[deps.Inflate]]
git-tree-sha1 = "f5fc07d4e706b84f72d54eedcc1c13d92fb0871c"
uuid = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9"
version = "0.1.2"

[[deps.InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"

[[deps.IrrationalConstants]]
git-tree-sha1 = "7fd44fd4ff43fc60815f8e764c0f352b83c49151"
uuid = "92d709cd-6900-40b7-9082-c6be49f344b6"
version = "0.1.1"

[[deps.IterTools]]
git-tree-sha1 = "05110a2ab1fc5f932622ffea2a003221f4782c18"
uuid = "c8e1da08-722c-5040-9ed9-7db0dc04731e"
version = "1.3.0"

[[deps.IterativeSolvers]]
deps = ["LinearAlgebra", "Printf", "Random", "RecipesBase", "SparseArrays"]
git-tree-sha1 = "1a8c6237e78b714e901e406c096fc8a65528af7d"
uuid = "42fd0dbc-a981-5370-80f2-aaf504508153"
version = "0.9.1"

[[deps.IteratorInterfaceExtensions]]
git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856"
uuid = "82899510-4779-5014-852e-03e436cf321d"
version = "1.0.0"

[[deps.JLD2]]
deps = ["DataStructures", "FileIO", "MacroTools", "Mmap", "Pkg", "Printf", "Reexport", "TranscodingStreams", "UUIDs"]
git-tree-sha1 = "46b7834ec8165c541b0b5d1c8ba63ec940723ffb"
uuid = "033835bb-8acc-5ee8-8aae-3f567f8a3819"
version = "0.4.15"

[[deps.JLLWrappers]]
deps = ["Preferences"]
git-tree-sha1 = "642a199af8b68253517b80bd3bfd17eb4e84df6e"
uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
version = "1.3.0"

[[deps.JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "8076680b162ada2a031f707ac7b4953e30667a37"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.2"

[[deps.LibCURL]]
deps = ["LibCURL_jll", "MozillaCACerts_jll"]
uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"

[[deps.LibCURL_jll]]
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"

[[deps.LibGit2]]
deps = ["Base64", "NetworkOptions", "Printf", "SHA"]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"

[[deps.LibSSH2_jll]]
deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"

[[deps.Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"

[[deps.LightGraphs]]
deps = ["ArnoldiMethod", "DataStructures", "Distributed", "Inflate", "LinearAlgebra", "Random", "SharedArrays", "SimpleTraits", "SparseArrays", "Statistics"]
git-tree-sha1 = "432428df5f360964040ed60418dd5601ecd240b6"
uuid = "093fc24a-ae57-5d10-9952-331d41423f4d"
version = "1.3.5"

[[deps.LinearAlgebra]]
deps = ["Libdl", "libblastrampoline_jll"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

[[deps.LogExpFunctions]]
deps = ["ChainRulesCore", "DocStringExtensions", "IrrationalConstants", "LinearAlgebra"]
git-tree-sha1 = "34dc30f868e368f8a17b728a1238f3fcda43931a"
uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
version = "0.3.3"

[[deps.Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"

[[deps.MacroTools]]
deps = ["Markdown", "Random"]
git-tree-sha1 = "5a5bc6bf062f0f95e62d0fe0a2d99699fed82dd9"
uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
version = "0.5.8"

[[deps.Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"

[[deps.MbedTLS_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"

[[deps.Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"

[[deps.MozillaCACerts_jll]]
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"

[[deps.NaNMath]]
git-tree-sha1 = "bfe47e760d60b82b66b61d2d44128b62e3a369fb"
uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
version = "0.3.5"

[[deps.NetworkOptions]]
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"

[[deps.Observables]]
git-tree-sha1 = "fe29afdef3d0c4a8286128d4e45cc50621b1e43d"
uuid = "510215fc-4207-5dde-b226-833fc4488ee2"
version = "0.4.0"

[[deps.OpenBLAS_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"]
uuid = "4536629a-c528-5b80-bd46-f80d51c5b363"

[[deps.OpenLibm_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "05823500-19ac-5b8b-9628-191a04bc5112"

[[deps.OpenSpecFun_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "13652491f6856acfd2db29360e1bbcd4565d04f1"
uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e"
version = "0.5.5+0"

[[deps.OrderedCollections]]
git-tree-sha1 = "85f8e6578bf1f9ee0d11e7bb1b1456435479d47c"
uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
version = "1.4.1"

[[deps.Parsers]]
deps = ["Dates"]
git-tree-sha1 = "98f59ff3639b3d9485a03a72f3ab35bab9465720"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.0.6"

[[deps.Pkg]]
deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"

[[deps.PkgVersion]]
deps = ["Pkg"]
git-tree-sha1 = "a7a7e1a88853564e551e4eba8650f8c38df79b37"
uuid = "eebad327-c553-4316-9ea0-9fa01ccd7688"
version = "0.1.1"

[[deps.PlutoUI]]
deps = ["Base64", "Dates", "Hyperscript", "HypertextLiteral", "IOCapture", "InteractiveUtils", "JSON", "Logging", "Markdown", "Random", "Reexport", "UUIDs"]
git-tree-sha1 = "4c8a7d080daca18545c56f1cac28710c362478f3"
uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
version = "0.7.16"

[[deps.PlutoVista]]
deps = ["ColorSchemes", "Colors", "GridVisualize", "UUIDs"]
git-tree-sha1 = "05c4f4f1a94ff2cfb6d442cced5965c8ab422e92"
uuid = "646e1f28-b900-46d7-9d87-d554eb38a413"
version = "0.8.1"

[[deps.Preferences]]
deps = ["TOML"]
git-tree-sha1 = "00cfd92944ca9c760982747e9a1d0d5d86ab1e5a"
uuid = "21216c6a-2e73-6563-6e65-726566657250"
version = "1.2.2"

[[deps.Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"

[[deps.REPL]]
deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"

[[deps.Random]]
deps = ["Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[[deps.RecipesBase]]
git-tree-sha1 = "44a75aa7a527910ee3d1751d1f0e4148698add9e"
uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
version = "1.1.2"

[[deps.RecursiveArrayTools]]
deps = ["ArrayInterface", "ChainRulesCore", "DocStringExtensions", "FillArrays", "LinearAlgebra", "RecipesBase", "Requires", "StaticArrays", "Statistics", "ZygoteRules"]
git-tree-sha1 = "ff7495c78a192ff7d59531d9f14db300c847a4bc"
uuid = "731186ca-8d62-57ce-b412-fbd966d074cd"
version = "2.19.1"

[[deps.Reexport]]
git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.2.2"

[[deps.Requires]]
deps = ["UUIDs"]
git-tree-sha1 = "4036a3bd08ac7e968e27c203d45f5fff15020621"
uuid = "ae029012-a4dd-5104-9daa-d747884805df"
version = "1.1.3"

[[deps.SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"

[[deps.Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"

[[deps.SharedArrays]]
deps = ["Distributed", "Mmap", "Random", "Serialization"]
uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383"

[[deps.SimpleTraits]]
deps = ["InteractiveUtils", "MacroTools"]
git-tree-sha1 = "5d7e3f4e11935503d3ecaf7186eac40602e7d231"
uuid = "699a6c99-e7fa-54fc-8d76-47d257e15c1d"
version = "0.9.4"

[[deps.Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"

[[deps.SparseArrays]]
deps = ["LinearAlgebra", "Random"]
uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"

[[deps.SparseDiffTools]]
deps = ["Adapt", "ArrayInterface", "Compat", "DataStructures", "FiniteDiff", "ForwardDiff", "LightGraphs", "LinearAlgebra", "Requires", "SparseArrays", "StaticArrays", "VertexSafeGraphs"]
git-tree-sha1 = "36a4d27a02af48a1eafd2baff58b32deeeb68926"
uuid = "47a9eef4-7e08-11e9-0b38-333d64bd3804"
version = "1.16.5"

[[deps.SparsityDetection]]
deps = ["Cassette", "DocStringExtensions", "LinearAlgebra", "SparseArrays", "SpecialFunctions"]
git-tree-sha1 = "9e182a311d169cb9fe0c6501aa252983215fe692"
uuid = "684fba80-ace3-11e9-3d08-3bc7ed6f96df"
version = "0.3.4"

[[deps.SpecialFunctions]]
deps = ["ChainRulesCore", "IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"]
git-tree-sha1 = "793793f1df98e3d7d554b65a107e9c9a6399a6ed"
uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
version = "1.7.0"

[[deps.Static]]
deps = ["IfElse"]
git-tree-sha1 = "a8f30abc7c64a39d389680b74e749cf33f872a70"
uuid = "aedffcd0-7271-4cad-89d0-dc628f76c6d3"
version = "0.3.3"

[[deps.StaticArrays]]
deps = ["LinearAlgebra", "Random", "Statistics"]
git-tree-sha1 = "3c76dde64d03699e074ac02eb2e8ba8254d428da"
uuid = "90137ffa-7385-5640-81b9-e52037218182"
version = "1.2.13"

[[deps.Statistics]]
deps = ["LinearAlgebra", "SparseArrays"]
uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[[deps.StructArrays]]
deps = ["Adapt", "DataAPI", "StaticArrays", "Tables"]
git-tree-sha1 = "2ce41e0d042c60ecd131e9fb7154a3bfadbf50d3"
uuid = "09ab397b-f2b6-538f-b94a-2f83cf4a842a"
version = "0.6.3"

[[deps.SuiteSparse]]
deps = ["Libdl", "LinearAlgebra", "Serialization", "SparseArrays"]
uuid = "4607b0f0-06f3-5cda-b6b1-a6196a1729e9"

[[deps.TOML]]
deps = ["Dates"]
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"

[[deps.TableTraits]]
deps = ["IteratorInterfaceExtensions"]
git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39"
uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c"
version = "1.0.1"

[[deps.Tables]]
deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "TableTraits", "Test"]
git-tree-sha1 = "fed34d0e71b91734bf0a7e10eb1bb05296ddbcd0"
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
version = "1.6.0"

[[deps.Tar]]
deps = ["ArgTools", "SHA"]
uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"

[[deps.Test]]
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[[deps.TranscodingStreams]]
deps = ["Random", "Test"]
git-tree-sha1 = "216b95ea110b5972db65aa90f88d8d89dcb8851c"
uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa"
version = "0.9.6"

[[deps.UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[[deps.Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"

[[deps.VertexSafeGraphs]]
deps = ["LightGraphs"]
git-tree-sha1 = "b9b450c99a3ca1cc1c6836f560d8d887bcbe356e"
uuid = "19fa3120-7c27-5ec5-8db8-b0b0aa330d6f"
version = "0.1.2"

[[deps.VoronoiFVM]]
deps = ["DiffResults", "DocStringExtensions", "ExtendableGrids", "ExtendableSparse", "ForwardDiff", "GridVisualize", "IterativeSolvers", "JLD2", "LinearAlgebra", "Printf", "RecursiveArrayTools", "SparseArrays", "SparseDiffTools", "SparsityDetection", "StaticArrays", "SuiteSparse", "Test"]
git-tree-sha1 = "db381645bec1d798c560fe415593663d4a2194ac"
uuid = "82b139dc-5afc-11e9-35da-9b9bdfd336f3"
version = "0.13.1"

[[deps.Zlib_jll]]
deps = ["Libdl"]
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"

[[deps.ZygoteRules]]
deps = ["MacroTools"]
git-tree-sha1 = "8c1a8e4dfacb1fd631745552c8db35d0deb09ea0"
uuid = "700de1a5-db45-46bc-99cf-38207098b444"
version = "0.2.2"

[[deps.libblastrampoline_jll]]
deps = ["Artifacts", "Libdl", "OpenBLAS_jll"]
uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"

[[deps.nghttp2_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"

[[deps.p7zip_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
"""

# ╔═╡ Cell order:
# ╟─3fa189e4-9e1c-470c-bf26-15b631945d2d
# ╟─327af4a8-74cc-4834-ab19-d3a1d6873982
# ╟─d5a0ee0d-959d-476b-b3c5-79b741059992
# ╟─f03ff283-c989-4b1a-b73e-2e616054e3db
# ╠═670c78c1-d0be-4362-975b-2c944620681f
# ╠═79193d53-9dfa-47b7-aed2-c7eb43769b5f
# ╠═4cb07222-587b-4a74-a444-43f5433d5b03
# ╟─02ec3c0b-6e68-462b-84df-931370cbdcac
# ╠═592429a1-108c-4e9e-8961-497c2c31f319
# ╠═1fa5d9b3-0558-4558-a5f1-f34f70c8d9a0
# ╟─5b539ad2-46d6-43b0-8b3e-f8a7e1ae0a6d
# ╠═6aabfbe1-de7d-49ba-8144-6d364b21b34f
# ╟─dbd27f10-9fd9-450d-9f78-89ea738d605b
# ╠═e5b432b2-875e-49a5-8e78-68f7f47f06c3
# ╠═8ae32292-df3e-4190-83a5-ec5ba529299e
# ╟─677853a7-0c43-4d3c-bc74-799535f95aeb
# ╠═139058a9-44a0-43d5-a377-4fc72927fa28
# ╟─de251b03-dd3a-4a44-9440-b7e654c32dac
# ╠═5b3a49d6-30ce-4d6e-94b4-a76645d7d8ce
# ╟─0735c061-68e1-429f-80f1-d8410989a91d
# ╠═467dc381-3b3d-4de7-a7f9-bfc51300832b
# ╟─fa4fcc0d-1d3a-45a2-8857-50536bbe39cc
# ╠═8f210696-fcf4-47bc-a5a2-c561ad7efcbd
# ╠═57e8515e-3be1-4478-af98-430501438ee7
# ╠═56136cd1-0c01-449d-9297-68924ac99ee7
# ╟─aad305a9-aac6-4aff-9f8e-08d6a2f756c8
# ╠═1328b4bf-2d64-4b02-a910-1995da8be28b
# ╠═610a0761-1c23-415d-a187-f7d93a1b7637
# ╠═d027ff24-3ad1-4528-b5cf-10814caf30db
# ╟─87edce1f-df6d-4cd8-bce5-24fb666cd6b5
# ╟─b82fc6b2-eee1-4a91-a115-61b86621f686
# ╠═9eaea813-2628-47d0-9d36-54c367689142
# ╠═817738c0-f1a3-4779-9075-7ea051a81e73
# ╠═80019ef1-bf41-4a55-9262-613a2d20be1f
# ╟─31e00855-8906-4be5-8e69-e2d8d9539e04
# ╠═39a0db1b-3a4e-4108-b43f-d4e578c92608
# ╠═644149fb-2264-42bd-92c9-193ab07c08f6
# ╠═f2490f99-04ca-4f42-af2a-53adae51ca68
# ╠═b479402f-ef00-4425-8b0f-45f2dae74d80
# ╟─ed068b51-92af-48d5-9230-debc178ec827
# ╠═58d8831b-ad66-4f77-a33a-933c15c46a52
# ╠═8c0b4ab5-09da-4d8f-b001-5e15f823423c
# ╠═d3d99b9c-ad18-4a04-bb3e-f17dd542f9f3
# ╠═a2d919a5-a395-40fb-8f93-db742f8a77c2
# ╟─eb9abf2e-372c-4f79-afbe-772b90eff9ad
# ╟─9f3ae7b5-51b3-48bc-b4db-b7236ba30682
# ╟─2da8a5b1-b168-41d9-baa8-d65a4ef5c4c0
# ╠═d44407de-8c9c-42fa-b1a2-ae02b826eccc
# ╠═ae268316-c058-4db8-9b71-57b0d9425274
# ╠═b53b9d28-4c25-4fb8-a3e4-599b0e385121
# ╟─e7ce7fd4-cfa4-4cc6-84a2-7e20ed2f4e5c
# ╠═29f36902-e355-4b02-b7b0-c4db12c47d33
# ╟─673e9320-ea30-4831-ad85-ba7936293ee2
# ╠═f35f419a-94dd-4051-a533-4b1ec9a4c9ec
# ╟─9661e4fc-55e1-4c2c-a3ad-515cdac3b514
# ╠═90298676-fda7-4168-8a40-7ff53e7c761b
# ╠═7a819522-55e4-4547-a3e0-e047e74cfb6b
# ╟─cebabf33-e769-47bd-b6f1-ddf525fea895
# ╠═719f206a-5b9f-4d78-8778-1d89edb2bc4d
# ╟─1d7f442f-c057-4379-8a40-c6ce3646ad5c
# ╠═d6e1c6c7-060d-4c2f-8054-d8f33f54bd55
# ╟─da41b22e-114d-4eee-81d0-73e6f3b45242
# ╠═59c83a22-a4cc-4b51-a1cc-5eb39588eacd
# ╠═0c3ca093-e011-409c-be58-e617e02f5d4c
# ╠═de119a22-b695-4b4f-8e04-b7d68ec1e91b
# ╠═5d8aca85-f12d-4a6e-84e4-781d45b65742
# ╠═b8cd6ad1-d323-4888-bbd1-5deba5a5870d
# ╠═441a39a0-a7de-47db-8539-12dee30b8312
# ╟─6203d95b-13d2-48a6-a69f-39e33e2edcdb
# ╠═4d8e81c1-dbec-4379-9ab7-a585a369582d
# ╠═4b75cde6-db35-11eb-1b95-bba4e05edb2f
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


# return the i-th bit
@inline bitat(chunk::UInt64, i::Int) = (chunk >>> (64 - i)) & 0x01 == 0x01
@inline bitat{T<:Unsigned}(::Type{T}, chunk::T, i::Int) = (chunk >>> (sizeof(T) * 8 - i)) & 1 == 1

# assume 1 byte = 8 bits
@inline bitsof{T<:Unsigned}(::Type{T}) = sizeof(T) * 8

@inline mod64(i) = i & 63

# make a bit mask
@inline lmask{T<:Unsigned}(typ::Type{T}, n::Int) = typemax(typ) << (bitsof(typ) - n)
@inline rmask{T<:Unsigned}(typ::Type{T}, n::Int) = typemax(typ) >> (bitsof(typ) - n)


module ProjectEuler

include("useful_functions.jl")
include("001.jl")
include("002.jl")
include("003.jl")
include("004.jl")
include("005.jl")

include("007.jl")
include("008.jl")
include("009.jl")
include("010.jl")

include("069.jl")

end


using Distributions

function gendata2(k, n, percentile_cutoff, rho)
    X = rand(1:k, n)
    if rho > 0
        Y = ifelse.(rand(Uniform(0,1),length(X)) .<= rho, X, rand(1:k,length(X)))
    else
        Y = rand(1:k, n)
    end
    A = Matrix(freqtable(X,Y))
    threshold = floor.(Int, percentile(vec(A),percentile_cutoff))
    observed_A = @. ifelse(A > threshold, A, 0)
    independent_A = sum(A,dims = 2) * sum(A,dims = 1) ./ sum(A)
    A = @. ifelse(A==0,1,A)
    (obs=observed_A, gen=A, ind = independent_A, threshold=threshold)
end

function mean_absolute_error(data, prediction)
    sum(abs.(data.gen .- prediction)) / count(ismissing.(data.obs))
end

function complete_trival(a, a_obs, threshold)

    if !all(a .== a_obs)

        a_row_sum = [sum(a[i,:]) for i in 1:size(a)[1]]
        a_col_sum = [sum(a[:,j]) for j in 1:size(a)[2]]

        singleton_row = [size(a)[2] - sum(sign.(a_obs[i,:])) for i in 1:size(a)[1]]
        singleton_col = [size(a)[1] - sum(sign.(a_obs[:,j])) for j in 1:size(a)[2]]

        singleton = any(vcat(singleton_row, singleton_col) .== 1)

        while singleton

        a_row_sum_current = [sum(a_obs[i,:]) for i in 1:size(a)[1]]
        a_col_sum_current = [sum(a_obs[:,j]) for j in 1:size(a)[2]]

            for i in 1:size(a_obs)[1]
                for j in 1:size(a_obs)[2]
                    if singleton_row[i] == 1 && a_obs[i,j] == 0
                        a_obs[i,j] = a_row_sum[i] - a_row_sum_current[i]
                        singleton_row[i] = 0
                        singleton_col[j] -= 1
                        a_row_sum_current = [sum(a_obs[i,:]) for i in 1:size(a)[1]]
                        a_col_sum_current = [sum(a_obs[:,j]) for j in 1:size(a)[2]]
                    end
                    if singleton_col[j] == 1 && a_obs[i,j] == 0
                        a_obs[i,j] = a_col_sum[j] - a_col_sum_current[j]
                        singleton_col[j] = 0
                        singleton_row[i] -= 1
                        a_row_sum_current = [sum(a_obs[i,:]) for i in 1:size(a)[1]]
                        a_col_sum_current = [sum(a_obs[:,j]) for j in 1:size(a)[2]]
                    end
                end
            end

            for i in 1:size(a_obs)[1]
                if (sum(1 .- sign.(a_obs[i,:])) * threshold) == (a_row_sum[i] - a_row_sum_current[i]) && (sum(sign.(a_obs[i,:])) < length(axes(a_obs,2)))
                    for j in 1:size(a_obs)[2]
                        if a_obs[i,j] == 0
                            a_obs[i,j] = threshold
                        end
                    end
                end
            end

            for j in 1:size(a_obs)[2]
                if (sum(1 .- sign.(a_obs[:,j])) * threshold) == (a_col_sum[j] - a_col_sum_current[j]) && (sum(sign.(a_obs[:,j])) < length(axes(a_obs,1)))
                    for i in 1:size(a_obs)[1]
                        if a_obs[i,j] == 0
                            a_obs[i,j] = threshold
                        end
                    end
                end
            end

        singleton_row = [size(a)[2] - sum(sign.(a_obs[i,:])) for i in 1:size(a)[1]]
        singleton_col = [size(a)[1] - sum(sign.(a_obs[:,j])) for j in 1:size(a)[2]]

            singleton = any(vcat(singleton_row, singleton_col) .== 1)

        end
    end

    a_obs

end

function compare_single(N, k, percentile_cutoff, rho = 0, correction = true)
    data_sampled = gendata(rand(2:k),N,percentile_cutoff,rho)
    if !(any(data_sampled.gen .== 0))
        #println((N,k,percentile_cutoff))
        mae_ipf = mean_absolute_error(data_sampled, IFP_with_threshold(data_sampled))
        #println(1)
        mae_mys = mean_absolute_error(data_sampled, mySampling2(data_sampled))
        #println(2)
        mae_knn = mean_absolute_error(data_sampled, knnSampling2(data_sampled))
        #println(3)
        (N, k, percentile_cutoff, mae_ipf, mae_mys, mae_knn, cramer_v(data_sampled.gen), rho)
    end
end

function compare_all(N_range, k_range, percentile_cutoff_range, rho_range, niter, correction)
    results = []
    println(length(N_range) * length(k_range) * length(percentile_cutoff_range) * length(rho_range) * niter)
    for N in N_range
        for k in k_range
            for percentile_cutoff in percentile_cutoff_range
                for rho in rho_range
                    for iter in 1:niter
                        try
                            push!(results, compare_single(N, k, percentile_cutoff, rho, correction))
                        catch e
                        end
                    end
                end
            end
        end
    end
    results
end

function create_correlated(N, k, rho)
    X_k = rand(2:k)
    Y_k = rand(2:k)
    X = rand(1:X_k, N)
    if rho > 0
        Y = generate_correlated(X, rho, Y_k)
    else
        Y = rand(1:Y_k, N)
    end
    A = FreqTables.freqtable(X,Y)
end

function cramer_v(a, corrected = true)
    n = sum(a)
    phi = chi_sq(a) / n
    k = size(a)[2]
    r = size(a)[1]
    if corrected
        phi_hat = max(0, phi - (k-1) * (r-1)/(n-1))
        k_hat = k - (k-1)^2 / (n-1)
        r_hat = r - (r-1)^2 / (n-1)
        sqrt(phi_hat / min(k_hat-1,r_hat-1))
    else
        sqrt(phi / min(k-1,r-1))
    end
end

function chi_sq(a)

    row_sums = [sum(a[i,:]) for i in 1:size(a)[1]]
    col_sums = [sum(a[:,j]) for j in 1:size(a)[2]]

    a_hat = row_sums * transpose(col_sums) ./ sum(a)

    sum((a .- a_hat).^2 ./ a_hat)

end

function tapply(x, index)
    res = Float64[]
    for i in sort(unique(index))
        push!(res, mean(x[(index .== i) .& (.!isnan.(x) )]))
    end
    res
end

function range_tapply(x, index, range)
        res = Float64[]
        ranges = collect(range)
        for r in 1:(length(ranges)-1)
            push!(res,mean(x[(index .>= ranges[r]) .& (index .< ranges[r+1]) .& .!(isnan.(x))]))
        end
        res
end


"""
Attitude objects contains Euler angles (rad) and quaternions.

# Constructors
    Attitude(psi, theta, phi)
    Attitude(q0, q1, q2, q3)
"""
struct Attitude
    # Euler angles
    psi::Number
    theta::Number
    phi::Number
    # Quaternions
    q0::Number
    q1::Number
    q2::Number
    q3::Number
end

show(io::IO, att::Attitude) = print(
    io,
    "Attitude: ψ=$(rad2deg(att.psi))º, θ=$(rad2deg(att.theta))º, ϕ=$(rad2deg(att.phi))º",
    )

# Constructor from Euler angles
Attitude(psi, theta, phi) = Attitude(psi, theta, phi,
                                     euler2quaternion(psi, theta, phi)...)

# Constructor from quaternions
Attitude(q0, q1, q2, q3) = Attitude(quaternion2euler(q0, q1, q2, q3)...,
                                    q0, q1, q2, q3)

# Getters
"""
    get_euler_angles(att::Attitude)

Return Euler angles ψ, θ, ϕ (yaw, pith, roll).
"""
get_euler_angles(att::Attitude) = [att.psi, att.theta, att.phi]

"""
    get_quaternions(att::Attitude)

Return quaternions.
"""
get_quaternions(att::Attitude) = [att.q0, att.q1, att.q2, att.q3]

function isapprox(x::Attitude, y::Attitude; rtol=1e-8, atol=0.0, nans=false)

    return isapprox(
        [x.psi, x.theta, x.phi, x.q0, x.q1, x.q2, x.q3],
        [y.psi, y.theta, y.phi, y.q0, y.q1, y.q2, y.q3],
        rtol=rtol, atol=atol, nans=nans,
        )
end


#
# Copyright (c) 2021 Tobias Thummerer, Lars Mikelsons, Josef Kircher
# Licensed under the MIT license. See LICENSE file in the project root for details.
#

using Plots

# our simulation setup
t_start = 0.0
t_stop = 8.0

# load the FMU container
myFMU = fmiLoad("SpringPendulum1D", ENV["EXPORTINGTOOL"], ENV["EXPORTINGVERSION"])

# print some useful FMU-information into the REPL
fmiInfo(myFMU)

# make an instance from the FMU
fmiInstantiate!(myFMU)

recordValues = ["mass.s", "mass.v"]
solutionME = fmiSimulateME(myFMU, t_start, t_stop; recordValues=recordValues)
solutionCS = fmiSimulateCS(myFMU, t_start, t_stop; recordValues=recordValues)

# plot the results
fig = fmiPlot(solutionME)

fig = Plots.plot()
fmiPlot!(fig, solutionME)

fig = fmiPlot(solutionCS)

fig = Plots.plot()
fmiPlot!(fig, solutionCS)

# unload the FMU, remove unpacked data on disc ("clean up")
fmiUnload(myFMU)


using POMDPModels
using POMDPs
using POMDPModelTools
using StaticArrays
using Random
using DiscreteValueIteration
using GridInterpolations
using LocalFunctionApproximation
using LocalApproximationValueIteration
using Test

@testset "all" begin

    println("Testing Requirements")
    gifa = LocalGIFunctionApproximator(RectangleGrid([0.0, 1.0], [0.0, 1.0]))
    @requirements_info LocalApproximationValueIterationSolver(gifa) SimpleGridWorld()

    @testset "integration" begin
        include("runtests_versus_discrete_vi.jl")
    end
end


function mass(u, q, w, v, x, t, mu, eta)
    m = 1.0;
    return m;
end
function flux(u, q, w, v, x, t, mu, eta)
    f = mu[1]*q;
    return f;
end
function source(u, q, w, v, x, t, mu, eta)
    s = 0.0;
    return s;
end
function ubou(u, q, w, v, x, t, mu, eta, uhat, n, tau)
    ub = mu[2];
    return ub;
end
function fbou(u, q, w, v, x, t, mu, eta, uhat, n, tau)
    f = flux(u, q, w, v, x, t, mu, eta);
    fb = f[1]*n[1] + f[2]*n[2] + tau[1]*(u[1]-0.0);
    return fb;
end
function initu(x, mu, eta)
    x1 = x[1];
    x2 = x[2];
    u0 = sin(pi*x1)*sin(pi*x2);
    return u0;
end


# Automatic lane change system

# ## Model

module automatic_lane_change_system #jl
using ReachabilityAnalysis, ModelingToolkit

vars = @variables vx, sx, ax, w, vy, sy

@taylorize function mode0!(du, u, params, t)
    vx, sx, ax, w, vy, sy = u
    du[1] = 0.1 * ax
    du[2] = vx - 2.5
    du[3] = -0.01 * sx - 0.103 + 0.84 - 0.3 * vx - 0.5 * ax
    du[4] = -2 * w
    du[5] = -2 * vy
    du[6] = 0.1 * vy
    return du
end

@taylorize function mode1!(du, u, params, t)
    vx, sx, ax, w, vy, sy = u
    du[1] = 0.1 * ax
    du[2] = vx - 2.5
    du[3] = -0.5 *1.4 - 0.5 * ax
    du[4] = 3 - 0.15 * w + 0.2 - 0.01 * sy
    du[5] = 7.5 - 0.45 * w + 0.5 - 0.025 * sy - 0.05 * vy
    du[6] = 0.1 * vy
    return du
end

@taylorize function mode2!(du, u, params, t)
    vx, sx, ax, w, vy, sy = u
    du[1] = 0.1 * ax
    du[2] = vx - 2.5
    du[3] = -0.5 * vx + 1.4 - 0.5 * ax
    du[4] = 0.15 * w + 0.2 - 0.01 * sy
    du[5] = 0.25 * w + 0.5 - 0.025 * sy - 0.05 * vy
    du[6] = 0.1 * vy
    return du
end

@taylorize function mode3!(du, u, params, t)
    vx, sx, ax, w, vy, sy = u
    du[1] = 0.1 * ax
    du[2] = vx - 2.5
    du[3] = -0.5 * vx + 1.4 - 0.5 * ax
    du[4] = -0.1 * w + 0.2 - 0.01 * sy
    du[5] = -0.25 * w + 0.5 - 0.025 * sy - 0.05 * vy
    du[6] = 0.1 * vy
    return du
end

@taylorize function mode4!(du, u, params, t)
    vx, sx, ax, w, vy, sy = u
    du[1] = 0.1 * ax
    du[2] = vx - 2.5
    du[3] = -0.5 * vx + 1.4 - 0.5 * ax
    du[4] = -3 - 0.15 * w + 0.2 - 0.01 * sy
    du[5] = -7.5 - 0.45 * w + 0.5 - 0.025 * sy - 0.05 * vy
    du[6] = 0.1 * vy
    return du
end

@taylorize function mode5!(du, u, params, t)
    vx, sx, ax, w, vy, sy = u
    du[1] = 0.1 * ax
    du[2] = vx - 2.5
    du[3] = -0.01 * sx - 0.103 + 0.84 - 0.3 * vx - 0.5 * ax
    du[4] = -2 * w
    du[5] = -2 * vy
    du[6] = 0.1 * vy
    return du
end

@taylorize function mode6!(du, u, params, t)
    vx, sx, ax, w, vy, sy = u
    du[1] = 0.1 * ax
    du[2] = vx - 2.5
    du[3] = -0.5 * vx + 1.4 - 0.5 * ax
    du[4] = -2 * w
    du[5] = -2 * vy
    du[6] = 0.1 * vy
    return du
end

function acc_model()
    HA = LightAutomaton(7)

    ## mode 0
    X = HalfSpace(sx + 10 < 0, vars)
    m0 = @system(x' = mode0!(x), dim: 6, x ∈ X)

    ## mode 1
    X = HalfSpace(sy < 12, vars)
    m1 = @system(x' = mode1!(x), dim: 6, x ∈ X)

    ## mode 2
    X = HalfSpace(vy > 0.05, vars)
    m2 = @system(x' = mode2!(x), dim: 6, x ∈ X)

    ## mode 3
    X = HalfSpace(vy + 0.05 < 0, vars)
    m3 = @system(x' = mode3!(x), dim: 6, x ∈ X)

    ## mode 4
    X = HalfSpace(sy > 3.5, vars)
    m4 = @system(x' = mode4!(x), dim: 6, x ∈ X)

    ## mode 5
    X = Universe(6)
    m5 = @system(x' = mode5!(x), dim: 6, x ∈ X)

    ## mode 6
    X = HalfSpace(vy < 0.05, vars)
    m6 = @system(x' = mode6!(x), dim: 6, x ∈ X)

    ## modo 0 → 1
    add_transition!(HA, 1, 2, 1)
    G = HalfSpace(sx + 10 >= 0, vars)
    R0 = ConstrainedIdentityMap(6, G)

    ## modo 1 → 2
    add_transition!(HA, 2, 3, 2)
    G = HalfSpace(sy >= 12, vars)
    R1 = ConstrainedIdentityMap(6, G)

    ## modo 4 → 3
    add_transition!(HA, 5, 4, 5)
    G = HalfSpace(sy <= 3.5, vars)
    R2 = ConstrainedIdentityMap(6, G)

    ## modo 5 → 4
    add_transition!(HA, 6, 5, 6)
    G = HalfSpace(sx >= 10, vars)
    R3 = ConstrainedIdentityMap(6, G)

    ## modo 2 → 5
    add_transition!(HA, 3, 6, 3)
    G = HalfSpace(sx >= 10, vars)
    R4 = ConstrainedIdentityMap(6, G)

    ## modo 3 → 6
    add_transition!(HA, 4, 7, 4)
    G = HalfSpace(vy + 0.07 >= 0, vars)
    R5 = ConstrainedIdentityMap(6, G)

    modes = [m1, m2, m3, m4, m5, m6]

    resets = [R0, R1, R2, R3, R4, R5]

    ## hybrid system
    S = HybridSystem(HA, modes, resets, fill(AutonomousSwitching(), 6))

    return S
end

function model(X0)
    H = acc_model()
    return IVP(H, X0)
end
end #jl

# ## References


module HPFEM

using Jacobi
using Base.LinAlg
using Polynomials
using JuBLAS

import Base.length
import Base.call
import Base.size

# package code goes here

include("mesh1d.jl")
include("localnum.jl")
include("basis_fun1d.jl")
include("quadrature1d.jl")
include("basis1d.jl")
include("element1d.jl")
include("operator.jl")
include("dofmap.jl")
include("dirichilet_lift.jl")
include("bbmatrix.jl")
include("static_cond_solver.jl")
include("plotmatrix.jl")

export isbndryint, ModalC01d, Legendre1d, Lagrange1d
export QuadType
export nbndry, ninterior, bndry_idx, interior_idx
export seq2bi!, seq2bi, bi2seq!, bi2seq, seq2b!, seq2b, seq2i!, seq2i
export Basis1d, qnodes, nmodes, nquad, qweights, basis, qbasis, dqbasis, basis1d, basis1d!
export SpecElem1d, project, project!
export mass_matrix, stiff_matrix, plot_matrix
end # module


# Run tests on the block SS contour integral method

using NonlinearEigenproblemsTest
using NonlinearEigenproblems
using Test
using LinearAlgebra

@testset "block SS" begin
    nep=nep_gallery("dep0",3)

    # Circle
    λ,V=contour_block_SS(nep,radius=1.0,N=1000,σ=0.1,k=3,K=3);
    @test norm(compute_Mlincomb(nep,λ[1],V[:,1])) < sqrt(eps())

    # Ellipse
    λ,V=contour_block_SS(nep,radius=[1.0,2.0],N=1000,σ=0.1,k=3,K=3);
    @test norm(compute_Mlincomb(nep,λ[1],V[:,1])) < sqrt(eps())

    # JSIAM Mode
    λ,V=contour_block_SS(nep,radius=1.0,N=1000,σ=0.1,k=3,K=4,Shat_mode=:JSIAM);
    @test norm(compute_Mlincomb(nep,λ[1],V[:,1])) < sqrt(eps())

end


function central_diff(Qbase, Qcon, cellxmax, cellymax, cellzmax, mu, lambda, vecAx, vecAy, vecAz, specific_heat_ratio, volume, Rd)
    E_vis_hat = zeros(cellxmax+1,   cellymax,   cellzmax, 5)
    F_vis_hat = zeros(  cellxmax, cellymax+1,   cellzmax, 5)
    G_vis_hat = zeros(  cellxmax,   cellymax, cellzmax+1, 5)

    for i in 2:cellxmax+1 -1
        for j in 2:cellymax -1
            for k in 2:cellzmax-1

                u_av = 0.5*(Qbase[i,j,k,2]+Qbase[i-1,j,k,2])
                v_av = 0.5*(Qbase[i,j,k,3]+Qbase[i-1,j,k,3])
                w_av = 0.5*(Qbase[i,j,k,4]+Qbase[i-1,j,k,4])

                mu_av     = 0.5*(mu[i-1,j,k]+mu[i,j,k])
                lambda_av = (0.5*(1/lambda[i-1,j,k]+1/lambda[i,j,k]))^(-1)

                volume_av = 0.5*(volume[i,j,k]+volume[i-1,j,k])

                dudxi = Qbase[i,j,k,2] - Qbase[i-1,j,k,2]
                dvdxi = Qbase[i,j,k,3] - Qbase[i-1,j,k,3]
                dwdxi = Qbase[i,j,k,4] - Qbase[i-1,j,k,4]
                dTdxi = Qbase[i,j,k,5]/(Qbase[i,j,k,1]*Rd) - Qbase[i-1,j,k,5]/(Qbase[i-1,j,k,1]*Rd)

                dudeta = 0.25*(Qbase[i,j+1,k,2] - Qbase[i,j-1,k,2] + Qbase[i-1,j+1,k,2] - Qbase[i-1,j-1,k,2])
                dvdeta = 0.25*(Qbase[i,j+1,k,3] - Qbase[i,j-1,k,3] + Qbase[i-1,j+1,k,3] - Qbase[i-1,j-1,k,3])
                dwdeta = 0.25*(Qbase[i,j+1,k,4] - Qbase[i,j-1,k,4] + Qbase[i-1,j+1,k,4] - Qbase[i-1,j-1,k,4])
                dTdeta = 0.25*( Qbase[i,j+1,k,5] /   (Qbase[i,j+1,k,1]*Rd) -
                                Qbase[i,j-1,k,5] /   (Qbase[i,j-1,k,1]*Rd) +
                                Qbase[i-1,j+1,k,5] / (Qbase[i-1,j+1,k,1]*Rd) -
                                Qbase[i-1,j-1,k,5] / (Qbase[i-1,j-1,k,1]*Rd) )

                dudzeta = 0.25*(Qbase[i,j,k+1,2] - Qbase[i,j,k-1,2] + Qbase[i-1,j,k+1,2] - Qbase[i-1,j,k-1,2])
                dvdzeta = 0.25*(Qbase[i,j,k+1,3] - Qbase[i,j,k-1,3] + Qbase[i-1,j,k+1,3] - Qbase[i-1,j,k-1,3])
                dwdzeta = 0.25*(Qbase[i,j,k+1,4] - Qbase[i,j,k-1,4] + Qbase[i-1,j,k+1,4] - Qbase[i-1,j,k-1,4])
                dTdzeta = 0.25*(Qbase[i,j,k+1,5] /   (Qbase[i,j,k+1,1]*Rd) -
                                Qbase[i,j,k-1,5] /   (Qbase[i,j,k-1,1]*Rd) +
                                Qbase[i-1,j,k+1,5] / (Qbase[i-1,j,k+1,1]*Rd) -
                                Qbase[i-1,j,k-1,5] / (Qbase[i-1,j,k-1,1]*Rd) )

                vecAx_xav = vecAx[i,j,k,1]
                vecAx_yav = vecAx[i,j,k,2]
                vecAx_zav = vecAx[i,j,k,3]

                vecAy_xav = 0.25*( vecAy[i,j,k,1] + vecAy[i,j+1,k,1] + vecAy[i-1,j,k,1] + vecAy[i-1,j+1,k,1] )
                vecAy_yav = 0.25*( vecAy[i,j,k,2] + vecAy[i,j+1,k,2] + vecAy[i-1,j,k,2] + vecAy[i-1,j+1,k,2] )
                vecAy_zav = 0.25*( vecAy[i,j,k,3] + vecAy[i,j+1,k,3] + vecAy[i-1,j,k,3] + vecAy[i-1,j+1,k,3] )

                vecAz_xav = 0.25*( vecAz[i,j,k,1] + vecAz[i,j,k+1,1] + vecAz[i-1,j,k,1] + vecAz[i-1,j,k+1,1] )
                vecAz_yav = 0.25*( vecAz[i,j,k,2] + vecAz[i,j,k+1,2] + vecAz[i-1,j,k,2] + vecAz[i-1,j,k+1,2] )
                vecAz_zav = 0.25*( vecAz[i,j,k,3] + vecAz[i,j,k+1,3] + vecAz[i-1,j,k,3] + vecAz[i-1,j,k+1,3] )

                dudx = vecAx_xav*dudxi + vecAy_xav*dudeta +vecAz_xav*dudzeta
                dudy = vecAx_yav*dudxi + vecAy_yav*dudeta +vecAz_yav*dudzeta
                dudz = vecAx_zav*dudxi + vecAy_zav*dudeta +vecAz_zav*dudzeta

                dvdx = vecAx_xav*dvdxi + vecAy_xav*dvdeta +vecAz_xav*dvdzeta
                dvdy = vecAx_yav*dvdxi + vecAy_yav*dvdeta +vecAz_yav*dvdzeta
                dvdz = vecAx_zav*dvdxi + vecAy_zav*dvdeta +vecAz_zav*dvdzeta

                dwdx = vecAx_xav*dwdxi + vecAy_xav*dwdeta +vecAz_xav*dwdzeta
                dwdy = vecAx_yav*dwdxi + vecAy_yav*dwdeta +vecAz_yav*dwdzeta
                dwdz = vecAx_zav*dwdxi + vecAy_zav*dwdeta +vecAz_zav*dwdzeta

                tau_xx = 2/3*mu_av*(2*dudx - dvdy - dwdz)
                tau_yy = 2/3*mu_av*(2*dvdy - dwdz - dudx)
                tau_zz = 2/3*mu_av*(2*dwdz - dudx - dvdy)

                tau_xy = mu_av*(dudy + dvdx)
                tau_yz = mu_av*(dvdz + dwdy)
                tau_zx = mu_av*(dwdx + dudz)

                dTdx = vecAx_xav*dTdxi + vecAy_xav*dTdeta + vecAz_xav*dTdzeta
                dTdy = vecAx_yav*dTdxi + vecAy_yav*dTdeta + vecAz_yav*dTdzeta
                dTdz = vecAx_zav*dTdxi + vecAy_zav*dTdeta + vecAz_zav*dTdzeta

                betax = u_av*tau_xx + v_av*tau_xy + w_av*tau_zx + lambda_av * dTdx
                betay = u_av*tau_xy + v_av*tau_yy + w_av*tau_yz + lambda_av * dTdy
                betaz = u_av*tau_zx + v_av*tau_yz + w_av*tau_zz + lambda_av * dTdz

                E_vis_hat[i,j,k,1] = 0.0
                E_vis_hat[i,j,k,2] = (vecAx_xav*tau_xx + vecAx_yav*tau_xy + vecAx_zav*tau_zx) / volume_av
                E_vis_hat[i,j,k,3] = (vecAx_xav*tau_xy + vecAx_yav*tau_yy + vecAx_zav*tau_yz) / volume_av
                E_vis_hat[i,j,k,4] = (vecAx_xav*tau_zx + vecAx_yav*tau_yz + vecAx_zav*tau_zz) / volume_av
                E_vis_hat[i,j,k,5] = (vecAx_xav*betax  + vecAx_yav*betay  + vecAx_zav*betaz )  / volume_av
            end
        end
    end

    for i in 2:cellxmax -1
        for j in 2:cellymax+1 -1
            for k in 2:cellzmax -1
                u_av = 0.5*(Qbase[i,j,k,2]+Qbase[i,j-1,k,2])
                v_av = 0.5*(Qbase[i,j,k,3]+Qbase[i,j-1,k,3])
                w_av = 0.5*(Qbase[i,j,k,4]+Qbase[i,j-1,k,4])

                mu_av     = 0.5*(mu[i,j-1,k]+mu[i,j,k])
                lambda_av = (0.5*(1/lambda[i,j-1,k]+1/lambda[i,j,k]))^(-1)

                volume_av = 0.5*(volume[i,j,k]+volume[i,j-1,k])

                dudxi = 0.25*(Qbase[i+1,j,k,2] - Qbase[i-1,j,k,2] + Qbase[i+1,j-1,k,2] - Qbase[i-1,j-1,k,2])
                dvdxi = 0.25*(Qbase[i+1,j,k,3] - Qbase[i-1,j,k,3] + Qbase[i+1,j-1,k,3] - Qbase[i-1,j-1,k,3])
                dwdxi = 0.25*(Qbase[i+1,j,k,4] - Qbase[i-1,j,k,4] + Qbase[i+1,j-1,k,4] - Qbase[i-1,j-1,k,4])
                dTdxi = 0.25*(  Qbase[i+1,j,k,4]/  (Qbase[i+1,j,k,1]*Rd)   -
                                Qbase[i-1,j,k,4]/  (Qbase[i-1,j,k,1]*Rd)   +
                                Qbase[i+1,j-1,k,4]/(Qbase[i+1,j-1,k,1]*Rd) -
                                Qbase[i-1,j-1,k,4]/(Qbase[i-1,j-1,k,1]*Rd) )

                dudeta = Qbase[i,j,k,2]-Qbase[i,j-1,k,2]
                dvdeta = Qbase[i,j,k,3]-Qbase[i,j-1,k,3]
                dwdeta = Qbase[i,j,k,4]-Qbase[i,j-1,k,4]
                dTdeta = (Qbase[i,j,k,5]/(Qbase[i,j,k,1]*Rd) - Qbase[i,j-1,k,5]/(Qbase[i,j-1,k,1]*Rd))

                dudzeta = 0.25*(Qbase[i,j,k+1,2] - Qbase[i,j,k-1,2] + Qbase[i,j-1,k+1,2] - Qbase[i,j-1,k-1,2])
                dvdzeta = 0.25*(Qbase[i,j,k+1,3] - Qbase[i,j,k-1,3] + Qbase[i,j-1,k+1,3] - Qbase[i,j-1,k-1,3])
                dwdzeta = 0.25*(Qbase[i,j,k+1,4] - Qbase[i,j,k-1,4] + Qbase[i,j-1,k+1,4] - Qbase[i,j-1,k-1,4])
                dTdzeta = 0.25*(Qbase[i,j,k+1,4]/  (Qbase[i,j,k+1,1]*Rd)   -
                                Qbase[i,j,k-1,4]/  (Qbase[i,j,k-1,1]*Rd)   +
                                Qbase[i,j-1,k+1,4]/(Qbase[i,j-1,k+1,1]*Rd) -
                                Qbase[i,j-1,k-1,4]/(Qbase[i,j-1,k-1,1]*Rd) )

                vecAx_xav = 0.25*( vecAx[i,j,k,1] + vecAx[i+1,j,k,1] + vecAx[i,j-1,k,1] + vecAx[i+1,j-1,k,1] )
                vecAx_yav = 0.25*( vecAx[i,j,k,2] + vecAx[i+1,j,k,2] + vecAx[i,j-1,k,2] + vecAx[i+1,j-1,k,2] )
                vecAx_zav = 0.25*( vecAx[i,j,k,3] + vecAx[i+1,j,k,3] + vecAx[i,j-1,k,3] + vecAx[i+1,j-1,k,3] )

                vecAy_xav = vecAy[i,j,k,1]
                vecAy_yav = vecAy[i,j,k,2]
                vecAy_zav = vecAy[i,j,k,3]

                vecAz_xav = 0.25*( vecAz[i,j,k,1] + vecAz[i,j,k+1,1] + vecAz[i,j-1,k,1] + vecAz[i,j-1,k+1,1] )
                vecAz_yav = 0.25*( vecAz[i,j,k,2] + vecAz[i,j,k+1,2] + vecAz[i,j-1,k,2] + vecAz[i,j-1,k+1,2] )
                vecAz_zav = 0.25*( vecAz[i,j,k,3] + vecAz[i,j,k+1,3] + vecAz[i,j-1,k,3] + vecAz[i,j-1,k+1,3] )

                dudx = vecAx_xav*dudxi + vecAy_xav*dudeta +vecAz_xav*dudzeta
                dudy = vecAx_yav*dudxi + vecAy_yav*dudeta +vecAz_yav*dudzeta
                dudz = vecAx_zav*dudxi + vecAy_zav*dudeta +vecAz_zav*dudzeta

                dvdx = vecAx_xav*dvdxi + vecAy_xav*dvdeta +vecAz_xav*dvdzeta
                dvdy = vecAx_yav*dvdxi + vecAy_yav*dvdeta +vecAz_yav*dvdzeta
                dvdz = vecAx_zav*dvdxi + vecAy_zav*dvdeta +vecAz_zav*dvdzeta

                dwdx = vecAx_xav*dwdxi + vecAy_xav*dwdeta +vecAz_xav*dwdzeta
                dwdy = vecAx_yav*dwdxi + vecAy_yav*dwdeta +vecAz_yav*dwdzeta
                dwdz = vecAx_zav*dwdxi + vecAy_zav*dwdeta +vecAz_zav*dwdzeta

                tau_xx = 2/3*mu_av*(2*dudx - dvdy - dwdz)
                tau_yy = 2/3*mu_av*(2*dvdy - dwdz - dudx)
                tau_zz = 2/3*mu_av*(2*dwdz - dudx - dvdy)

                tau_xy = mu_av*(dudy + dvdx)
                tau_yz = mu_av*(dvdz + dwdy)
                tau_zx = mu_av*(dwdx + dudz)

                dTdx = vecAx_xav*dTdxi + vecAy_xav*dTdeta + vecAz_xav*dTdzeta
                dTdy = vecAx_yav*dTdxi + vecAy_yav*dTdeta + vecAz_yav*dTdzeta
                dTdz = vecAx_zav*dTdxi + vecAy_zav*dTdeta + vecAz_zav*dTdzeta

                betax = u_av*tau_xx + v_av*tau_xy + w_av*tau_zx + lambda_av * dTdx
                betay = u_av*tau_xy + v_av*tau_yy + w_av*tau_yz + lambda_av * dTdy
                betaz = u_av*tau_zx + v_av*tau_yz + w_av*tau_zz + lambda_av * dTdz

                F_vis_hat[i,j,k,1] = 0.0
                F_vis_hat[i,j,k,2] = (vecAy_xav*tau_xx + vecAy_yav*tau_xy + vecAy_zav*tau_zx) / volume_av
                F_vis_hat[i,j,k,3] = (vecAy_xav*tau_xy + vecAy_yav*tau_yy + vecAy_zav*tau_yz) / volume_av
                F_vis_hat[i,j,k,4] = (vecAy_xav*tau_zx + vecAy_yav*tau_yz + vecAy_zav*tau_zz) / volume_av
                F_vis_hat[i,j,k,5] = (vecAy_xav*betax  + vecAy_yav*betay  + vecAy_zav*betaz ) / volume_av
            end
        end
    end

    for i in 2:cellxmax -1
        for j in 2:cellymax -1
            for k in 2:cellzmax+1 -1
                u_av = 0.5*(Qbase[i,j,k,2]+Qbase[i,j,k-1,2])
                v_av = 0.5*(Qbase[i,j,k,3]+Qbase[i,j,k-1,3])
                w_av = 0.5*(Qbase[i,j,k,4]+Qbase[i,j,k-1,4])

                mu_av     = 0.5*(mu[i,j,k-1]+mu[i,j,k])
                lambda_av = (0.5*(1/lambda[i,j,k-1]+1/lambda[i,j,k]))^(-1)

                volume_av = 0.5*(volume[i,j,k]+volume[i,j,k-1])

                dudxi = 0.25*(Qbase[i+1,j,k,2] - Qbase[i-1,j,k,2] + Qbase[i+1,j,k-1,2] - Qbase[i-1,j,k-1,2])
                dvdxi = 0.25*(Qbase[i+1,j,k,3] - Qbase[i-1,j,k,3] + Qbase[i+1,j,k-1,3] - Qbase[i-1,j,k-1,3])
                dwdxi = 0.25*(Qbase[i+1,j,k,4] - Qbase[i-1,j,k,4] + Qbase[i+1,j,k-1,4] - Qbase[i-1,j,k-1,4])
                dTdxi = 0.25*(  Qbase[i+1,j,k,4]/  (Qbase[i+1,j,k,1]*Rd)   -
                                Qbase[i-1,j,k,4]/  (Qbase[i-1,j,k,1]*Rd)   +
                                Qbase[i+1,j,k-1,4]/(Qbase[i+1,j,k-1,1]*Rd) -
                                Qbase[i-1,j,k-1,4]/(Qbase[i-1,j,k-1,1]*Rd) )

                dudeta = 0.25*(Qbase[i,j+1,k,2] - Qbase[i,j-1,k,2] + Qbase[i,j+1,k-1,2] - Qbase[i,j-1,k-1,2])
                dvdeta = 0.25*(Qbase[i,j+1,k,3] - Qbase[i,j-1,k,3] + Qbase[i,j+1,k-1,3] - Qbase[i,j-1,k-1,3])
                dwdeta = 0.25*(Qbase[i,j+1,k,4] - Qbase[i,j-1,k,4] + Qbase[i,j+1,k-1,4] - Qbase[i,j-1,k-1,4])
                dTdeta = 0.25*( Qbase[i,j+1,k,4]/  (Qbase[i,j+1,k,1]*Rd)   -
                                Qbase[i,j-1,k,4]/  (Qbase[i,j-1,k,1]*Rd)   +
                                Qbase[i,j+1,k-1,4]/(Qbase[i,j+1,k-1,1]*Rd) -
                                Qbase[i,j-1,k-1,4]/(Qbase[i,j-1,k-1,1]*Rd) )

                dudzeta = Qbase[i,j,k,2]-Qbase[i,j,k-1,2]
                dvdzeta = Qbase[i,j,k,3]-Qbase[i,j,k-1,3]
                dwdzeta = Qbase[i,j,k,4]-Qbase[i,j,k-1,4]
                dTdzeta = (Qbase[i,j,k,5]/(Qbase[i,j,k,1]*Rd) - Qbase[i,j,k-1,5]/(Qbase[i,j,k-1,1]*Rd))

                vecAx_xav = 0.25*( vecAx[i,j,k,1] + vecAx[i+1,j,k,1] + vecAx[i,j,k-1,1] + vecAx[i+1,j,k-1,1] )
                vecAx_yav = 0.25*( vecAx[i,j,k,2] + vecAx[i+1,j,k,2] + vecAx[i,j,k-1,2] + vecAx[i+1,j,k-1,2] )
                vecAx_zav = 0.25*( vecAx[i,j,k,3] + vecAx[i+1,j,k,3] + vecAx[i,j,k-1,3] + vecAx[i+1,j,k-1,3] )

                vecAy_xav = 0.25*( vecAy[i,j,k,1] + vecAy[i,j+1,k,1] + vecAy[i,j,k-1,1] + vecAy[i,j+1,k-1,1] )
                vecAy_yav = 0.25*( vecAy[i,j,k,2] + vecAy[i,j+1,k,2] + vecAy[i,j,k-1,2] + vecAy[i,j+1,k-1,2] )
                vecAy_zav = 0.25*( vecAy[i,j,k,3] + vecAy[i,j+1,k,3] + vecAy[i,j,k-1,3] + vecAy[i,j+1,k-1,3] )

                vecAz_xav = vecAz[i,j,k,1]
                vecAz_yav = vecAz[i,j,k,2]
                vecAz_zav = vecAz[i,j,k,3]

                dudx = vecAx_xav*dudxi + vecAy_xav*dudeta +vecAz_xav*dudzeta
                dudy = vecAx_yav*dudxi + vecAy_yav*dudeta +vecAz_yav*dudzeta
                dudz = vecAx_zav*dudxi + vecAy_zav*dudeta +vecAz_zav*dudzeta

                dvdx = vecAx_xav*dvdxi + vecAy_xav*dvdeta +vecAz_xav*dvdzeta
                dvdy = vecAx_yav*dvdxi + vecAy_yav*dvdeta +vecAz_yav*dvdzeta
                dvdz = vecAx_zav*dvdxi + vecAy_zav*dvdeta +vecAz_zav*dvdzeta

                dwdx = vecAx_xav*dwdxi + vecAy_xav*dwdeta +vecAz_xav*dwdzeta
                dwdy = vecAx_yav*dwdxi + vecAy_yav*dwdeta +vecAz_yav*dwdzeta
                dwdz = vecAx_zav*dwdxi + vecAy_zav*dwdeta +vecAz_zav*dwdzeta

                tau_xx = 2/3*mu_av*(2*dudx - dvdy - dwdz)
                tau_yy = 2/3*mu_av*(2*dvdy - dwdz - dudx)
                tau_zz = 2/3*mu_av*(2*dwdz - dudx - dvdy)

                tau_xy = mu_av*(dudy + dvdx)
                tau_yz = mu_av*(dvdz + dwdy)
                tau_zx = mu_av*(dwdx + dudz)

                dTdx = vecAx_xav*dTdxi + vecAy_xav*dTdeta + vecAz_xav*dTdzeta
                dTdy = vecAx_yav*dTdxi + vecAy_yav*dTdeta + vecAz_yav*dTdzeta
                dTdz = vecAx_zav*dTdxi + vecAy_zav*dTdeta + vecAz_zav*dTdzeta

                betax = u_av*tau_xx + v_av*tau_xy + w_av*tau_zx + lambda_av * dTdx
                betay = u_av*tau_xy + v_av*tau_yy + w_av*tau_yz + lambda_av * dTdy
                betaz = u_av*tau_zx + v_av*tau_yz + w_av*tau_zz + lambda_av * dTdz

                G_vis_hat[i,j,k,1] = 0.0
                G_vis_hat[i,j,k,2] = (vecAz_xav*tau_xx + vecAz_yav*tau_xy + vecAz_zav*tau_zx) / volume_av
                G_vis_hat[i,j,k,3] = (vecAz_xav*tau_xy + vecAz_yav*tau_yy + vecAz_zav*tau_yz) / volume_av
                G_vis_hat[i,j,k,4] = (vecAz_xav*tau_zx + vecAz_yav*tau_yz + vecAz_zav*tau_zz) / volume_av
                G_vis_hat[i,j,k,5] = (vecAz_xav*betax  + vecAz_yav*betay  + vecAz_zav*betaz ) / volume_av
            end
        end
    end

    return E_vis_hat, F_vis_hat, G_vis_hat
end


using Test

using EvenTotalPayment

# https://www.extension.iastate.edu/agdm/wholefarm/html/c5-93.html
@testset "Smoke" begin
    p = even_total_payment(10000.0, 0.07, 20)
    @test round(Int, p.payment) == 944
    @test round(Int, p.principals[10]) == 448
    @test round(Int, p.interests[10]) == 495
    @test round(Int, total_interest(p)) == 8879
    @test round(Int, total_payment(p)) == 18879
end

# 微博高利贷
@testset "Weibo Loan Shark" begin
    # 根据微博消费贷规则，借款8000元按等额本息半年还清，每月需还款1476.78元。(王高飞：“加点利息怎么
    # 了？”)
    rate = estimate_interest_rate(8000.0, 6, 1476.78)
    @test round(Int, rate * 100 * 12) == 36  # 年化百分比利率（Annual percentage rate）高达36%
    # 有效年利率（Effective annual rate）
    ear = round((1.0 + rate) ^ 12 - 1.0; sigdigits=3)
    println("Effective annual rate = $(ear * 100)%")
end


function solve_quasi{T<:AbstractFloat,S<:Int}(model::State_Space_Form{T},obj::State_Space_Objective{T},commit_prob::T,tol::T,maxiters::S)

  nx    = copy(model.nx)
  ny    = copy(model.ny)
  beta  = copy(obj.beta)
  sigma = copy(model.sigma)

  a  = copy(model.a)
  b  = copy(model.b)
  c  = copy(model.c)

  q = copy(obj.q)
  u = copy(obj.u)
  r = copy(obj.r)

  n  = nx+ny
  np = size(b,2)

  q11 = q[1:nx,1:nx]
  q12 = q[1:nx,(nx+1):n]
  q21 = q[(nx+1):n,1:nx]
  q22 = q[(nx+1):n,(nx+1):n]

  u1 = u[1:nx,:]
  u2 = u[(nx+1):n,:]

  a11 = a[1:nx,1:nx]
  a12 = a[1:nx,(nx+1):n]
  a21 = a[(nx+1):n,1:nx]
  a22 = a[(nx+1):n,(nx+1):n]

  b1 = b[1:nx,:]
  b2 = b[(nx+1):n,:]

  a_tilda = [[a11 zeros(nx,ny)]; zeros(ny,n)]
  b_tilda = [[a12 b1 zeros(nx,ny)]; [zeros(ny,ny+np) eye(ny)]]
  c_tilda = [c; zeros(ny,size(c,2))]

  v       = q+(a+a')/n
  h_reopt = zeros(ny,nx)

  s = [eye(nx) zeros(nx,ny)]  # x_{t} = s*z_{t}

  q_tilda = [[q11 zeros(nx,ny)]; zeros(ny,n)]

  len = Inf
  iters = 0
  while len > tol && iters < maxiters

    u_tilda = [[q12 u1 ((a21-(1-commit_prob)*h_reopt*a11))']; [-eye(ny)/beta zeros(ny,ny+np)]]
    r_tilda = [[q22 u2 ((a22-(1-commit_prob)*h_reopt*a12))']; [u2' r ((b2-(1-commit_prob)*h_reopt*b1))']; [(a22-(1-commit_prob)*h_reopt*a12) (b2-(1-commit_prob)*h_reopt*b1) zeros(ny,ny)]]

    f  = -(r_tilda+beta*b_tilda'*(commit_prob*v+(1-commit_prob)*(s'*pinv(s')*v*pinv(s)*s))*b_tilda)\(u_tilda'+beta*b_tilda'*(commit_prob*v+(1-commit_prob)*(s'*pinv(s')*v*pinv(s)*s))*a_tilda)
    vn = q_tilda+u_tilda*f+f'*u_tilda'+f'*r_tilda*f+beta*(a_tilda+b_tilda*f)'*(commit_prob*v+(1-commit_prob)*(s'*pinv(s')*v*pinv(s)*s))*(a_tilda+b_tilda*f)

    len = maxabs(vn-v)
    v = vn

    h_reopt = f[1:ny,1:nx]

  end

  if iters <= maxiters
    retcode = true
  else
    retcode = false
  end

  q_orig = q_tilda
  u_orig = [[q12 u1]; zeros(ny,ny+np)]
  r_orig = [[q22 u2]; [u2' r]]

  p = a_tilda+b_tilda*f
  k = c
  h = [[eye(nx) zeros(nx,ny)]; f[1:ny,:]]
  f = f[(ny+1):(ny+np),:]

  (v,retcode_doubling) = doubling(sqrt(beta)*p',sqrt(beta)*p,h'q*h+h'u*f+f'u'h+f'r*f,tol,maxiters)

  # v = dlyap(sqrt(beta)*p',h'q*h+h'u*f+f'u'h+f'r*f)

  soln = State_Space_Soln(p,k,h,f,v,sigma,retcode)

  return soln

end

function solve_quasi{T<:AbstractFloat,S<:Int}(model::Generalized_State_Space_Form{T},obj::State_Space_Objective{T},commit_prob::T,tol::T,maxiters::S)

  nx    = copy(model.nx)
  ny    = copy(model.ny)
  beta  = copy(obj.beta)
  sigma = copy(model.sigma)

  a0 = copy(model.a0)
  a  = copy(model.a)
  b  = copy(model.b)
  c  = copy(model.c)

  q = copy(obj.q)
  u = copy(obj.u)
  r = copy(obj.r)

  n  = nx+ny
  np = size(b,2)

  q11 = q[1:nx,1:nx]
  q12 = q[1:nx,(nx+1):n]
  q21 = q[(nx+1):n,1:nx]
  q22 = q[(nx+1):n,(nx+1):n]

  u1 = u[1:nx,:]
  u2 = u[(nx+1):n,:]

  a11 = a[1:nx,1:nx]
  a12 = a[1:nx,(nx+1):n]
  a21 = a[(nx+1):n,1:nx]
  a22 = a[(nx+1):n,(nx+1):n]

  b1 = b[1:nx,:]
  b2 = b[(nx+1):n,:]

  a_tilda = [[a11 zeros(nx,ny)]; zeros(ny,n)]
  b_tilda = [[a12 b1 zeros(nx,ny)]; [zeros(ny,ny+np) eye(ny)]]
  c_tilda = [c; zeros(ny,size(c,2))]

  v       = q+(a+a')/n
  h_reopt = zeros(ny,nx)

  s = [eye(nx) zeros(nx,ny)]  # x_{t} = s*z_{t}

  q_tilda = [[q11 zeros(nx,ny)]; zeros(ny,n)]

  len = Inf
  iters = 0
  while len > tol && iters < maxiters

    u_tilda = [[q12 u1 ((a21-(1-commit_prob)*a0*h_reopt*a11))']; [-a0/beta zeros(ny,ny+np)]]
    r_tilda = [[q22 u2 ((a22-(1-commit_prob)*a0*h_reopt*a12))']; [u2' r ((b2-(1-commit_prob)*a0*h_reopt*b1))']; [(a22-(1-commit_prob)*a0*h_reopt*a12) (b2-(1-commit_prob)*a0*h_reopt*b1) zeros(ny,ny)]]

    f  = -(r_tilda+beta*b_tilda'*(commit_prob*v+(1-commit_prob)*(s'*pinv(s')*v*pinv(s)*s))*b_tilda)\(u_tilda'+beta*b_tilda'*(commit_prob*v+(1-commit_prob)*(s'*pinv(s')*v*pinv(s)*s))*a_tilda)
    vn = q_tilda+u_tilda*f+f'*u_tilda'+f'*r_tilda*f+beta*(a_tilda+b_tilda*f)'*(commit_prob*v+(1-commit_prob)*(s'*pinv(s')*v*pinv(s)*s))*(a_tilda+b_tilda*f)

    len = maxabs(vn-v)
    v = vn

    h_reopt = f[1:ny,1:nx]

  end

  if iters <= maxiters
    retcode = true
  else
    retcode = false
  end

  q_orig = q_tilda
  u_orig = [[q12 u1]; zeros(ny,ny+np)]
  r_orig = [[q22 u2]; [u2' r]]

  p = a_tilda+b_tilda*f
  k = c
  h = [[eye(nx) zeros(nx,ny)]; f[1:ny,:]]
  f = f[(ny+1):(ny+np),:]

  (v,retcode_doubling) = doubling(sqrt(beta)*p',sqrt(beta)*p,h'q*h+h'u*f+f'u'h+f'r*f,tol,maxiters)

  # v = dlyap(sqrt(beta)*p',h'q*h+h'u*f+f'u'h+f'r*f)

  soln = State_Space_Soln(p,k,h,f,v,sigma,retcode)

  return soln

end


using ApproxFun, SingularIntegralEquations, DualNumbers, Holomorphic

Disk()

points(Circle()

x=Fun()
w=sqrt(1-x^2)
g=stieltjes(w)
g/(-2π*im)
g|(2Circle())
imag(g|+)
g|-
∂(g)

domain(g)

-∂
cauchy(+1

f(.1+.2im)

Γ=ComplexPlane()\Interval()
S=Space(Γ)

f(z)=stieltjes(w,z)

g=Fun(Fun(x->f(x+0im)-f(x-0im),S.space).coefficients/(-2π*im),S)

g(.1+.2im)
f(.1+.2im)

stieltjes(w).coefficients

g.coefficients

stieltjes(1/w,1.0001)

d=Interval(-1.-1im,1+1.im)

fromcanonical(d,0.1)-fromcanonical(d,0.1+0.im)
fromcanonical(d,0.1)

fromcanonical(d,0.-0.im)
fromcanonical(d,0.+0.im)

using DualNumbers

fromcanonical(d,dual(0.,1.im))
fromcanonical(d,dual(0.,-1.im))

Dual(a::Real,b::Complex)=Dual(a+0*b,b)

sqrt(Dual(-1.,1.im))
sqrt(Dual(

sqrt(-1.-0.im)
sqrt(dual(-1.,-1im))
sqrt(dual(-1.+0.im,+1im))
sqrt(dual(-1.-0.im,-1im))

realpart(sqrt(dual(-1.,-im)))
realpart(sqrt(dual(-1.,im)))

f(z)=sqrt((1.+1.im)*z)

realpart(f(dual(-1/(1.+1.im),-1.im)))

f(-1/(1.+1.im)-0.000001im)

sqrt(-1.)
Pkg.checkout("DualNumbers")


using Plots
R = 0.0821
T = 243.6
isoterm(x) = R * T / x
v = 1 : 0.1 : 10
p = isoterm.(v)
plot(xlabel = "V[L]", ylabel = "P[atm]", xlims = (0, 15), ylims = (0, 25),
		xticks = 0 : 1 : 15, yticks = 0 : 2 : 25)
plot!(reverse(v), reverse(p);
		color = :red, label = "Trinn 1", arrow = :arrow, linewidth = 2)
plot!([1, 10], [20, 20];
		color = :blue, label = "Trinn 2", arrow = :arrow, linewidth = 2)
plot!([10, 10], [20, 2];
		color = "#007f00", label = "Trinn 3", arrow = :arrow, linewidth = 2)
#


@doc raw"""
Tableau of a Partitioned Runge-Kutta method
```math
\begin{aligned}
Q_{n,i} &= q_{n} + h \sum \limits_{j=1}^{s} a_{ij} \, v(t_{n} + c_j \Delta t, Q_{n,j}, P_{n,j}) , &
q_{n+1} &= q_{n} + h \sum \limits_{i=1}^{s} b_{i}  \, v(t_{n} + c_j \Delta t, Q_{n,i}, P_{n,i}) , \\
P_{n,i} &= p_{n} + h  \sum \limits_{i=1}^{s} \bar{a}_{ij} \, f(t_{n} + c_j \Delta t, Q_{n,j}, P_{n,j}) , &
p_{n+1} &= p_{n} + h \sum \limits_{i=1}^{s} \bar{b}_{i}   \, f(t_{n} + c_j \Delta t, Q_{n,i}, P_{n,i}) .
\end{aligned}
```

Parameters:
 * `T`: datatype of coefficient arrays

Fields:
 * `name`: symbolic name of the tableau
 * `o`: order of the method
 * `s`: number of stages
 * `q`: Tableau for `q`
 * `p`: Tableau for `p`

The actual tableaus are stored in `q` and `p`:
 * `a`: coefficients $a_{ij}$ with $ 1 \le i,j \le s$
 * `b`: weights $b_{i}$  with $ 1 \le i \le s$
 * `c`: nodes $c_{i}$  with $ 1 \le i \le s$

Constructors:
```julia
PartitionedTableau{T}(name, o, s, q, p)
PartitionedTableau{T}(name, q, p)
PartitionedTableau(name::Symbol, q::Tableau, p::Tableau)
PartitionedTableau(name::Symbol, q::Tableau)
```
"""
struct PartitionedTableau{T}
    name::Symbol
    o::Int
    s::Int

    q::Tableau{T}
    p::Tableau{T}

    function PartitionedTableau{T}(name, o, s, q, p) where {T}
        @assert s == q.s == p.s
        new(name, o, s, q, p)
    end

    function PartitionedTableau{T}(name, q, p) where {T}
        new(name, min(q.o, p.o), q.s, q, p)
    end
end

PartitionedTableau(name::Symbol, q::Tableau{T}, p::Tableau{T}) where {T} = PartitionedTableau{T}(name, q, p)
PartitionedTableau(name::Symbol, q::Tableau) = PartitionedTableau(name, q, q)

Base.hash(tab::PartitionedTableau, h::UInt) = hash(tab.o, hash(tab.s, hash(tab.q, hash(tab.p, hash(:PartitionedTableau, h)))))

Base.:(==)(tab1::PartitionedTableau, tab2::PartitionedTableau) = (
                                            tab1.o == tab2.o
                                         && tab1.s == tab2.s
                                         && tab1.q == tab2.q
                                         && tab1.p == tab2.p)

Base.isapprox(tab1::PartitionedTableau, tab2::PartitionedTableau; kwargs...) = (
                                            tab1.o == tab2.o
                                         && tab1.s == tab2.s
                                         && isapprox(tab1.q, tab2.q; kwargs...)
                                         && isapprox(tab1.p, tab2.p; kwargs...))

Base.isequal(tab1::PartitionedTableau{T1}, tab2::PartitionedTableau{T2}) where {T1,T2} = (tab1 == tab2 && T1 == T2 && tab1.name == tab2.name)

Base.eltype(::PartitionedTableau{T}) where {T} = T

name(tab::PartitionedTableau) = tab.name
order(tab::PartitionedTableau) = tab.o
nstages(tab::PartitionedTableau) = tab.s
eachstage(tab::PartitionedTableau) = 1:tab.s

isexplicit(tab::PartitionedTableau) = istril(tab.q.a) && istril(tab.p.a) && all([tab.q.a[i,i] == 0 || tab.p.a[i,i] == 0 for i in 1:tab.s]) && (tab.q.c[1] == 0 || tab.p.c[1] == 0)
isimplicit(tab::PartitionedTableau) = !isexplicit(tab)
isdiagnonallyimplicit(tab::PartitionedTableau) = isimplicit(tab) && tab.s != 1 && istril(tab.q.a) && istril(tab.p.a)
isfullyimplicit(tab::PartitionedTableau) = !isexplicit(tab) && !(isdiagnonallyimplicit(tab))


using KLIEPInference
using LinearAlgebra, Random
using Distributions, JLD

Random.seed!(543)

m = parse(Int,ARGS[1])
sgn = parse(Int,ARGS[2])
numChanges = parse(Int,ARGS[3])
lbInd = parse(Int,ARGS[4])

lbArr = 0.0:.05:0.5
lb = lbArr[lbInd]

γx = chain(m; lenC=5, lb=0.2, ub=0.4, sgn=sgn)
γy = copy(γx)

ind_change = sample(1:length(γx), numChanges; replace=false)
du = Uniform(lb, lb+0.01)
for i in ind_change
    γy[i] -= rand(du)
end

θ = γx - γy
@show ind_nz = findall(!iszero, θ)
@show θ[ind_nz]

@save "graphs/params_exp4_$(m)_$(sgn)_$(numChanges)_$(lbInd).jld" m sgn γx γy


export ## Types
       CompositeQSystem,
       Control,
       Dissipation,
       Field,
       IndexSet,
       Interaction,
       ParametricInteraction,
       QSystem,
       ## Methods
       hamiltonian,
       dim,
       label,
       lowering,
       number,
       raising,
       strength,
       X,Y

abstract type QSystem end

label(q::QSystem) = q.label
dim(q::QSystem) = q.dim
raising(q::QSystem) = diagm(-1 => sqrt.(1:(dim(q)-1)))
lowering(q::QSystem) = diagm(1 => sqrt.(1:(dim(q)-1)))
number(q::QSystem) = raising(q) * lowering(q)
X(q::QSystem) = raising(q) + lowering(q)
Y(q::QSystem) = 1im*(raising(q) - lowering(q))
hamiltonian(q::QSystem, t) = hamiltonian(q)

abstract type Control end
label(c::Control) = c.label

abstract type Interaction end
strength(i::Interaction) = i.strength
hamiltonian(i::Interaction, t) = hamiltonian(i)

abstract type ParametricInteraction <: Interaction end

abstract type Dissipation end

const IndexSet = Vector{Int}
const ExpansionIndices = Tuple{Vector{IndexSet},Vector{IndexSet},Vector{IndexSet}}

# [todo] - Should COmpositeQSystem <: QSystem ?
mutable struct CompositeQSystem
    # [feature] - Use something like OrderedDict for component enumeration
    subSystems::Vector{QSystem}
    interactions::Vector{Interaction}
    parametericInteractions::Vector{ParametricInteraction}
    subSystemExpansions::Vector{ExpansionIndices}
    interactionExpansions::Vector{ExpansionIndices}
    dissipatorExpansions::Vector{ExpansionIndices}
    dissipators::Vector{Dissipation}
end


using Distributions
using ImageFiltering
using LACosmic
using PSFModels: Gaussian
using StableRNGs
using Test

rng = StableRNG(125588)

function make_data(rng, T, N; N_sources=100, N_cosmics=100)
    imdata = fill(200.0, (N, N))

    # Add some fake sources
    for _ in 1:N_sources
        x = rand(rng, Uniform(1, N + 1))
        y = rand(rng, Uniform(1, N + 1))
        brightness = rand(rng, Uniform(1000, 30000)) / (2π * 3.5^2)
		model = Gaussian(T; x=x, y=y, fwhm=3.5, amp=brightness)
        imdata .+= model[axes(imdata)...]
	end

    # Add the poisson noise
    imdata .= rand.(rng, Poisson.(imdata))

    # Add readnoise
    imdata .+= rand(rng, Normal(0, 10), (N, N))

	clean_image = T.(imdata)

    # Add Nc fake cosmic rays
    crmask = falses((N, N))
	for i in 1:N_cosmics
    	cr_x = round(Int, rand(rng, Uniform(6, N - 5)))
    	cr_y = round(Int, rand(rng, Uniform(6, N - 5)))
    	cr_brightnesses = rand(rng, Uniform(1000, 30000))
    	imdata[cr_y, cr_x] += cr_brightnesses
    	crmask[cr_y, cr_x] = true
	end

    # Make a mask where the detected cosmic rays should be
    return (image=T.(imdata), clean_image, mask=crmask)
end

make_data(T, N; kwargs...) = make_data(rng, T, N; kwargs...)
make_data(N; kwargs...) = make_data(Float32, N; kwargs...)

@testset "LACosmic.jl" begin
    @testset "subsample" for T in [Int, Float64, Float32, BigFloat, BigInt]
        data = T.(reshape(1:25, 5, 5))
        sub = @inferred LACosmic.subsample(data)

        @test size(sub) == (10, 10)
        @test parent(sub) === data

        @inbounds for idx in CartesianIndices(data)
            idx_11 = CartesianIndex(idx.I[1] * 2 - 1, idx.I[2] * 2 - 1)
            @test data[idx] === @inferred sub[idx_11]
            sub[idx_11] = 0
            @test data[idx] == 0

            idx_12 = CartesianIndex(idx.I[1] * 2 - 1, idx.I[2] * 2)
            @test data[idx] === @inferred sub[idx_12]
            sub[idx_12] = 0
            @test data[idx] == 0

            idx_21 = CartesianIndex(idx.I[1] * 2, idx.I[2] * 2 - 1)
            @test data[idx] === @inferred sub[idx_21]
            sub[idx_21] = 0
            @test data[idx] == 0

            idx_22 = CartesianIndex(idx.I[1] * 2, idx.I[2] * 2)
            @test data[idx] === @inferred sub[idx_22]
            sub[idx_22] = 0
            @test data[idx] == 0
        end

    end

    @testset "dilation - $k" for k in [3, 5]
        mask = randn(rng, 1001, 1001) .< 0.05
        kernel = ones(k, k)
        expected = mapwindow(any, mask, (k, k), border=Fill(false))
        dilated = @inferred LACosmic.dilate!(copy(mask), k)
        @test dilated == expected
    end

    @testset "rebin" for block_size in [2, 3]
        data = randn(rng, Float32, 512, 512) .+ 100
        sub = @inferred LACosmic.subsample(data, block_size)
        binned = @inferred LACosmic.rebin(sub, block_size)
        @test binned ≈ data

        binned_sum = @inferred LACosmic.rebin(sum, sub, block_size)
        @test binned_sum ≈ block_size^2 .* data
    end

    @testset "cleaning" begin
        input = make_data(1001)
        output, mask = lacosmic(input.image; sigma_clip=6, neighbor_thresh=1, readnoise=10)
        output2, mask2 = lacosmic(output; sigma_clip=6, neighbor_thresh=1, readnoise=10)
        @test iszero(count(mask2))
    end

    @testset "LACosmic algorithm" for T in [Float32, Float64]
        input = make_data(T, 1001)
        output, mask = @inferred lacosmic(input.image; sigma_clip=6, neighbor_thresh=1, readnoise=10)
        @test eltype(output) === T
        @test mask == input.mask
        @test all(output[input.mask] .!= input.image[input.mask])
    end
end


using SparseArrays
using LightGraphs

struct GraphIndex{T}
    n_nodes::T
    n_edges::T
    neighbors_per_node::Array{T}
    node_neighbors::Array{T, 2}
    population::Array{T}
    src_dst_to_edge::SparseMatrixCSC{T, T}
    edges::Array{T, 2}
end

function GraphIndex(graph::SimpleGraph, population::Array{Int64})::GraphIndex{Int64}
    n_nodes = nv(graph)
    n_edges = ne(graph)
    all_edges = zeros(Int64, 2, n_edges)
    cut_edges = BitSet(Int64[])

    src_dst_to_edge = spzeros(Int64, n_nodes, n_nodes)
    for (index, edge) in enumerate(edges(graph))
        all_edges[1, index] = src(edge)
        all_edges[2, index] = dst(edge)
        src_dst_to_edge[src(edge), dst(edge)] = index
        src_dst_to_edge[dst(edge), src(edge)] = index
    end

    node_neighbors = zeros(Int64, 5, n_nodes) # Every node in a planar graph has degree ≦5.
    neighbors_per_node = zeros(Int64, n_nodes)
    for index in 1:n_nodes
        for (neighbor_idx, neighbor) in enumerate(neighbors(graph, index))
            node_neighbors[neighbor_idx, index] = neighbor
            neighbors_per_node[index] += 1
        end
    end

    return GraphIndex{Int64}(n_nodes, n_edges, neighbors_per_node,
                      node_neighbors, population, src_dst_to_edge,
                      all_edges)
end


export ZeroOne
module ZeroOne

export zeroone

function zeroone(s::AbstractString, p = 10^9 + 7)
    nvariants = 1
    nones = 0
    ninvol = 0
    for c in s
        if c === '1'
            nones = mod(nones + nvariants, p)
        elseif c === '0'
            ninvol = mod(ninvol + nones, p)
        elseif c === '?'
            ninvol = mod(ninvol * 2 + nones, p)
            nones = mod(nones * 2 + nvariants, p)
            nvariants = mod(nvariants * 2, p)
        end
    end
    ninvol
end

end # module


using JuMP
using Combinatorics

# Inspired from Joey Huchette's test in ConvexHull.jl
function nonfulldimensionaltest(lib::Polyhedra.Library)
    m = Model()
    @variable(m, x[1:3] ≥ 1)
    @constraints(m, begin
        x[1] == 2
        x[2] ≤ 2
    end)

    poly = polyhedron(m, lib)

    V = [2 1 1;
         2 2 1]
    R = [0 0 1]
    generator_fulltest(poly, V, R)
end


" Surface area scaling rules "
abstract type AbstractShape end

struct Isomorph <: AbstractShape end

@columns struct V0morph{Mo} <: AbstractShape
    Vd::Mo  | 4.0 | mol | Gamma(2.0, 0.2) | [0.0, 1000.0] | _ | "reference"
end

@columns struct V1morph{Mo} <: AbstractShape
    Vd::Mo  | 4.0 | mol | Gamma(2.0, 0.2) | [0.0, 1000.0] | _ | "reference"
end

@columns struct V1V0morph{Mo,B} <: AbstractShape
    Vd::Mo   | 4.0 | mol | Gamma(2.0, 0.2) | [0.0, 1000.0] | _ | "reference"
    Vmax::Mo | 4.0 | mol | Gamma(2.0, 0.2) | [0.0, 1000.0] | _ | "reference"
    β::B     | 4.0 | mol | Gamma(2.0, 0.2) | [0.0, 10.0] | _ | "reference"
end

@columns struct Plantmorph{Mo} <: AbstractShape
    M_Vref::Mo     | 0.1 | mol | Gamma(2.0, 0.2) | [0.00001, 20.0]  | true | "Scaling reference"
    M_Vscaling::Mo | 1.0 | mol | Gamma(2.0, 0.2) | [0.0001, 2000.0] | true | "Scaling mass"
end

update_shape!(o, u) = set_shape!(o, shape_correction(shape_pars(o), u.V))

shape_correction(f::Nothing, V) = 1
shape_correction(f::Isomorph, V) = 1
shape_correction(f::V0morph, V) = (V / f.Vd)^(-2//3)
shape_correction(f::V1morph, V) = (V / f.Vd)^(1//3)
shape_correction(f::V1V0morph, V) = (V / f.Vd)^(1//3 - (V/f.Vmax)^f.β)
shape_correction(f::Plantmorph, V) = (V / f.M_Vref)^(-V / f.M_Vscaling)