# ---
# title: 375. Guess Number Higher or Lower II
# id: problem375
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Dynamic Programming, Minimax
# link: <https://leetcode.com/problems/guess-number-higher-or-lower-ii/description/>
# hidden: true
# ---
#
# We are playing the Guessing Game. The game will work as follows:
#
#   1. I pick a number between `1` and `n`.
#   2. You guess a number.
#   3. If you guess the right number, **you win the game**.
#   4. If you guess the wrong number, then I will tell you whether the number I picked is **higher or lower** , and you will continue guessing.
#   5. Every time you guess a wrong number `x`, you will pay `x` dollars. If you run out of money, **you lose the game**.
#
# Given a particular `n`, return  _the minimum amount of money you need to
# **guarantee a win regardless of what number I pick**_.
#
#
#
# **Example 1:**
#
# ![](https://assets.leetcode.com/uploads/2020/09/10/graph.png)
#
#
#
#     Input: n = 10
#     Output: 16
#     Explanation: The winning strategy is as follows:
#     - The range is [1,10]. Guess 7.
#         - If this is my number, your total is /$/0. Otherwise, you pay /$/7.
#         - If my number is higher, the range is [8,10]. Guess 9.
#             - If this is my number, your total is /$/7. Otherwise, you pay /$/9.
#             - If my number is higher, it must be 10. Guess 10. Your total is /$/7 + /$/9 = /$/16.
#             - If my number is lower, it must be 8. Guess 8. Your total is /$/7 + /$/9 = /$/16.
#         - If my number is lower, the range is [1,6]. Guess 3.
#             - If this is my number, your total is /$/7. Otherwise, you pay /$/3.
#             - If my number is higher, the range is [4,6]. Guess 5.
#                 - If this is my number, your total is /$/7 + /$/3 = /$/10. Otherwise, you pay /$/5.
#                 - If my number is higher, it must be 6. Guess 6. Your total is /$/7 + /$/3 + /$/5 = /$/15.
#                 - If my number is lower, it must be 4. Guess 4. Your total is /$/7 + /$/3 + /$/5 = /$/15.
#             - If my number is lower, the range is [1,2]. Guess 1.
#                 - If this is my number, your total is /$/7 + /$/3 = /$/10. Otherwise, you pay /$/1.
#                 - If my number is higher, it must be 2. Guess 2. Your total is /$/7 + /$/3 + /$/1 = /$/11.
#     The worst case in all these scenarios is that you pay /$/16. Hence, you only need /$/16 to guarantee a win.
#
#
# **Example 2:**
#
#
#
#     Input: n = 1
#     Output: 0
#     Explanation:  There is only one possible number, so you can guess 1 and not have to pay anything.
#
#
# **Example 3:**
#
#
#
#     Input: n = 2
#     Output: 1
#     Explanation:  There are two possible numbers, 1 and 2.
#     - Guess 1.
#         - If this is my number, your total is /$/0. Otherwise, you pay /$/1.
#         - If my number is higher, it must be 2. Guess 2. Your total is /$/1.
#     The worst case is that you pay /$/1.
#
#
#
#
# **Constraints:**
#
#   * `1 <= n <= 200`
#
#
## @lc code=start
using LeetCode

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


# ==============================================================================
# Algorithme de Kung (extrait S_N d'un ensemble statique de points S de IR^2)

function getNonDominatedPoints(XFeas, YFeas)
    S = []
    for i=1:length(XFeas)
        push!(S, (XFeas[i] , YFeas[i]) )
    end
    sort!(S, by = x -> x[1])
    SN=[] ; push!(SN, S[1]) ; minYFeas = S[1][2]
    for i=2:length(XFeas)
        if S[i][2] < minYFeas
            push!(SN, S[i]) ; minYFeas = S[i][2]
        end
    end
    return SN
end

# ==============================================================================
# Extraction de l'ensemble bornant primal + sa frontiere de l'ensemble des points realisables

function ExtractEBP(XFeas, YFeas)
    X_EBP_frontiere = (Int64)[];   Y_EBP_frontiere = (Int64)[]
    X_EBP = (Int64)[] ;            Y_EBP = (Int64)[]

    if length(XFeas) > 0
        SN = getNonDominatedPoints(XFeas, YFeas)

        push!(X_EBP_frontiere, SN[1][1]) ;
        push!(Y_EBP_frontiere, ceil(Int64, 1.1 * maximum(YFeas)))

        for i in 1:length(SN)-1
            push!(X_EBP_frontiere, SN[i][1]);     push!(Y_EBP_frontiere, SN[i][2])
            push!(X_EBP_frontiere, SN[i+1][1]);   push!(Y_EBP_frontiere, SN[i][2])
            push!(X_EBP, SN[i][1]);               push!(Y_EBP, SN[i][2])
        end
        push!(X_EBP_frontiere, SN[end][1]);       push!(Y_EBP_frontiere, SN[end][2])

        push!(X_EBP_frontiere, ceil(Int64, 1.1 * maximum(XFeas)))
        push!(Y_EBP_frontiere, SN[end][2])

        push!(X_EBP, SN[end][1]);   push!(Y_EBP, SN[end][2])
    end
    return X_EBP_frontiere, Y_EBP_frontiere,   X_EBP, Y_EBP
end


#' ---
#' title: Solvers and Solutions
#' ---

#' **Originally Contributed by**: Arpit Bhatia

#' The purpose of this part of the tutorial is to introduce you to solvers and how to use them with JuMP. We'll also learn
#' what to do with a problem after the solver has finished optimizing it.

#' ## What is a Solver?
#' A solver is a software package that incorporates algorithms for finding solutions to one or more classes of problem.
#' For example, GLPK, which we used in the previous tutorials is a solver for linear programming (LP) and mixed integer
#' programming (MIP) problems. It incorporates algorithms such as the simplex method, interior-point method etc. JuMP
#' currently supports a number of open-source and commercial solvers which can be viewed
#' [here](http://www.juliaopt.org/JuMP.jl/v0.19.1/installation/#Getting-Solvers-1).

#' ## What is MathOptInterface?
#' Each mathematical optimization solver API has its own concepts and data structures for representing optimization models
#' and obtaining results. However, it is often desirable to represent an instance of an optimization problem at a higher
#' level so that it is easy to try using different solvers. MathOptInterface (MOI) is an abstraction layer designed to provide
#' an interface to mathematical optimization solvers so that users do not need to understand multiple solver-specific
#' APIs. MOI can be used directly, or through a higher-level modeling interface like JuMP.

#' Note that JuMP reexports MathOptInterface and
#' you can use the shortcut MOI to refer to MathOptInterface in your code.

#' ## Interacting with solvers
#' JuMP models can be created in three different modes: `AUTOMATIC`, `MANUAL` and `DIRECT`.
#' We'll use the following LP to illustrate them.

#' $$
#' \begin{align*}
#' & \max_{x,y} & x + 2y \\
#' & \;\;\text{s.t.} & x + y &\leq 1 \\
#' & & 0\leq x, y &\leq 1 \\
#' \end{align*}
#' $$

using JuMP
using GLPK

#' ### `AUTOMATIC` Mode
#' #### With Optimizer
#' This is the easiest method to use a solver in JuMP. In order to do so, we simply set the solver inside the Model constructor.

model_auto = Model(with_optimizer(GLPK.Optimizer))
@variable(model_auto, 0 <= x <= 1)
@variable(model_auto, 0 <= y <= 1)
@constraint(model_auto, x + y <= 1)
@objective(model_auto, Max, x + 2y)
optimize!(model_auto)
objective_value(model_auto)

#' #### No Optimizer (at first)
#' It is also possible to create a JuMP model with no optimizer attached. After the model object is initialized empty
#' and all its variables, constraints and objective are set, then we can attach the solver at `optimize!` time.

model_auto_no = Model()
@variable(model_auto_no, 0 <= x <= 1)
@variable(model_auto_no, 0 <= y <= 1)
@constraint(model_auto_no, x + y <= 1)
@objective(model_auto_no, Max, x + 2y)
optimize!(model_auto_no, with_optimizer(GLPK.Optimizer))
objective_value(model_auto_no)

#' Note that we can also enforce the automatic mode by passing `caching_mode = MOIU.AUTOMATIC` in the Model function call.
#' ### `MANUAL` Mode
#' This mode is similar to the `AUTOMATIC` mode, but there are less protections from the user getting errors from the solver
#' API. On the other side, nothing happens silently, which might give the user more control. It requires attaching the solver
#' before the solve step using the `MOIU.attach_optimizer()` function.

model_manual = Model(with_optimizer(GLPK.Optimizer),caching_mode = MOIU.MANUAL)
@variable(model_manual, 0 <= x <= 1)
@variable(model_manual, 0 <= y <= 1)
@constraint(model_manual, x + y <= 1)
@objective(model_manual, Max, x + 2y)
MOIU.attach_optimizer(model_manual)
optimize!(model_manual)
objective_value(model_manual)

#' ### `DIRECT` Mode
#' Some solvers are able to handle the problem data directly. This is common for LP/MIP solver but not very common for
#' open-source conic solvers. In this case we do not set a optimizer, we set a backend which is more generic and is able
#' to hold data and not only solving a model.

model_direct = direct_model(GLPK.Optimizer())
@variable(model_direct, 0 <= x <= 1)
@variable(model_direct, 0 <= y <= 1)
@constraint(model_direct, x + y <= 1)
@objective(model_direct, Max, x + 2y)
optimize!(model_direct)
objective_value(model_direct)

#' ### Solver Options
#' Many of the solvers also allow options to be passed in. However, these options are solver-specific. To find out the various
#' options available, please check out the individual solver packages. Some examples for the CBC solver are given below.

using Cbc

#' To turn off printing (i.e. silence the solver),

model = Model(with_optimizer(Cbc.Optimizer, logLevel = 0));

#' To increase the maximum number of iterations

model = Model(with_optimizer(Cbc.Optimizer, max_iters = 10000));

#' To set the solution timeout limit

model = Model(with_optimizer(Cbc.Optimizer, seconds = 5));

#' ## Querying Solutions
#' So far we have seen all the elements and constructs related to writing a JuMP optimization model. In this section we
#' reach the point of what to do with a solved problem. JuMP follows closely the concepts defined in MathOptInterface to
#' answer user questions about a finished call to `optimize!(model)`. The three main steps in querying a solution are
#' given below. We'll use the model we created in `AUTOMATIC` mode with an optimizer attached in this section.
#' ### Termination Statuses
#' Termination statuses are meant to explain the reason why the optimizer stopped executing in the most recent call
#' to `optimize!`.

termination_status(model_auto)

#' You can view the different termination status codes by referring to the docs or though checking the possible types using
#' the below command.

display(typeof(MOI.OPTIMAL))

#' ### Solution Statuses
#' These statuses indicate what kind of result is available to be queried from the model. It's possible that no result is
#' available to be queried. We shall discuss more on the dual status and solutions in the Duality tutorial.

primal_status(model_auto)

#+

dual_status(model_auto)

#' As we saw before, the result (solution) status codes can be viewed directly from Julia.

display(typeof(MOI.FEASIBLE_POINT))

#' ### Obtaining Solutions
#' Provided the primal status is not `MOI.NO_SOLUTION`, we can inspect the solution values and optimal cost.

@show value(x)
@show value(y)
@show objective_value(model_auto)

#' Since it is possible that no solution is available to be queried from the model, calls to `value` may throw errors.
#' Hence, it is recommended to check for the presence of solutions.
#+ tangle = false

model_no_solution = Model(with_optimizer(GLPK.Optimizer))
@variable(model_no_solution, 0 <= x <= 1)
@variable(model_no_solution, 0 <= y <= 1)
@constraint(model_no_solution, x + y >= 3)
@objective(model_no_solution, Max, x + 2y)

optimize!(model_no_solution)

if termination_status(model_no_solution) == MOI.OPTIMAL
    optimal_solution = value(x)
    optimal_objective = objective_value(model_no_solution)
elseif termination_status(model_no_solution) == MOI.TIME_LIMIT && has_values(model_no_solution)
    suboptimal_solution = value(x)
    suboptimal_objective = objective_value(model_no_solution)
else
    error("The model was not solved correctly.")
end


module Diffusion

export
  Problem,
  updatevars!,
  set_c!

using Reexport

@reexport using FourierFlows

using LinearAlgebra: mul!, ldiv!

"""
    Problem(; parameters...)

Construct a constant diffusivity problem.
"""
function Problem(;
            nx = 128,
            Lx = 2π,
             κ = 0,
            dt = 0.01,
       stepper = "RK4",
             T = Float64,
           dev = CPU()
  )

      grid = OneDGrid(dev, nx, Lx; T=T)
    params = Params(dev, κ)
      vars = Vars(dev, grid)
  equation = Equation(dev, κ, grid)

  return FourierFlows.Problem(equation, stepper, dt, grid, vars, params, dev)
end

struct Params{T} <: AbstractParams
  κ :: T
end

Params(dev, κ::Number) = Params(κ)
Params(dev, κ::AbstractArray) = Params(ArrayType(dev)(κ))

"""
    Equation(dev, κ, grid)

Returns the equation for a constant diffusivity problem on `grid` with diffusivity κ.
"""
function Equation(dev::Device, κ::T, grid) where T<:Number
  L = zeros(dev, T, grid.nkr)
  @. L = - κ * grid.kr^2

  return FourierFlows.Equation(L, calcN!, grid)
end

Equation(dev::Device, κ::T, grid::AbstractGrid{Tg}) where {T<:AbstractArray, Tg} =
  FourierFlows.Equation(0, calcN!, grid; dims=(grid.nkr,), T=cxtype(Tg))

struct Vars{Aphys, Atrans} <: AbstractVars
    c :: Aphys
   cx :: Aphys
   ch :: Atrans
  cxh :: Atrans
end

"""
    Vars(dev, grid)

Returns the variables `vars` for a constant diffusivity problem on `grid`.
"""
function Vars(::Dev, grid::AbstractGrid{T}) where {Dev, T}
  @devzeros Dev T grid.nx c cx
  @devzeros Dev Complex{T} grid.nkr ch cxh
  Vars(c, cx, ch, cxh)
end

"""
    calcN!(N, sol, t, clock, vars, params, grid)

Calculate the nonlinear term for the 1D heat equation.
"""
function calcN!(N, sol, t, clock, vars, params::Params{T}, grid) where T<:Number
  @. N = 0

  return nothing
end

function calcN!(N, sol, t, clock, vars, params::Params{T}, grid) where T<:AbstractArray
  @. vars.cxh = im * grid.kr * sol
  ldiv!(vars.cx, grid.rfftplan, vars.cxh)
  @. vars.cx *= params.κ
  mul!(vars.cxh, grid.rfftplan, vars.cx)
  @. N = im * grid.kr * vars.cxh

  return nothing
end

"""
    updatevars!(vars, grid, sol)

Update the variables in `vars` on the `grid` with the solution in `sol`.
"""
function updatevars!(vars, grid, sol)
  ldiv!(vars.c, grid.rfftplan, deepcopy(sol))
  @. vars.ch = sol

  return nothing
end

updatevars!(prob) = updatevars!(prob.vars, prob.grid, prob.sol)

"""
    set_c!(prob, c)

Set the solution as the transform of `c` and update `vars`.
"""
function set_c!(prob, c)
  T = typeof(prob.vars.c)
  prob.vars.c .= T(c) # this makes sure that c is converted to the ArrayType used in prob.vars.c (e.g., convert to CuArray if user gives c as Arrray)
  mul!(prob.sol, prob.grid.rfftplan, prob.vars.c)
  updatevars!(prob)

  return nothing
end

end # module


function rule(line)
    outer, inner = split(line, r" bags contain ")
    list = Dict{String,Int}()
    for m in eachmatch(r"(\d+) (.*?) bag", inner)
        n, bag = m.captures
        list[bag] = parse(Int, n)
    end
    return outer => list
end

function part1(rules)
    queue = [k for (k, v) in rules if haskey(v, "shiny gold")]
    found = Set(queue)
    while !isempty(queue)
        bag = pop!(queue)
        new = (k for (k, v) in rules if haskey(v, bag))
        union!(found, new)
        append!(queue, new)
    end
    return length(found)
end

function part2(rules)
    queue = ["shiny gold" => 1]
    found = Pair{String,Int}[]
    while !isempty(queue)
        bag, n = pop!(queue)
        new = (k => n * v for (k, v) in rules[bag])
        append!(found, new)
        append!(queue, new)
    end
    return sum(last, found)
end

rules = Dict(rule(line) for line in eachline("./2020/data/day_7.txt"))

queue = ["shiny gold" => 1]
found = Pair{String,Int}[]
while !isempty(queue)
    lq = length(queue)
    lf = length(found)
    println("#Queue: $lq -- #QFound: $lf")
    bag, n = pop!(queue)
    new = (k => n * v for (k, v) in rules[bag])
    println("$rules[bag]")
    append!(found, new)
    append!(queue, new)
end
println(sum(last, found))


cases = ["B-splines","Cheb","Lin"]

# construct basis
holder = (
    Basis(SplineParams(25, -1, 1, 1),SplineParams(20, -1, 2, 3)),
    Basis(ChebParams(25, -1, 1),ChebParams(20, -1, 2)),
    Basis(LinParams(25, -1, 1),LinParams(20, -1, 2))
)

@testset for (i, case) in enumerate(cases)
    basis = holder[i]

    # get nodes
    X, x12 = nodes(basis)

    # function to interpolate
    f(x1, x2) = cos.(x2) ./ exp.(x1)
    f(X::Matrix) = f.(X[:, 1], X[:, 2])

    # function at nodes
    y = f(X)
    yprime2 = - sin.(X[:, 2]) ./ exp.(X[:, 1])

    # benchmark coefficients
    c_direct, bs_direct = funfitxy(basis, X, y)

    # this order isn't included bs_direct
    @test_throws ErrorException funeval(c_direct, convert(Expanded, bs_direct), [0 1])

    @testset "test funfitxy for tensor and direct agree on coefs" begin
        c_tensor, bs_tensor = funfitxy(basis, x12, y)
        @test maximum(abs, c_tensor -  c_direct) <=  1e-12
    end

    @testset "test funfitf" begin
        c = funfitf(basis, f)
        @test maximum(abs, c -  c_direct) <=  1e-12
    end

    @testset "test funeval methods" begin
        # single point
        sp = @inferred funeval(c_direct, basis, X[5:5, :])[1]
        @test maximum(abs, sp -  y[5]) <= 1e-12

        # multiple points using tensor directly
        mp = @inferred funeval(c_direct, basis, x12)
        @test maximum(abs, mp -  y) <=  1e-12

        # multiple points using direct
        mp = @inferred funeval(c_direct, basis, X)
        @test maximum(abs, mp -  y) <=  1e-12

        # multiple points giving basis in direct form
        mpd = @inferred funeval(c_direct, bs_direct)
        @test maximum(abs, mpd -  y) <=  1e-12

        # multiple points giving basis in expanded form
        Phiexp = Base.convert(Expanded, bs_direct)
        mpe = @inferred funeval(c_direct, Phiexp)
        @test maximum(abs, mpe -  y) <=  1e-12

        # order != 0. Note for Spline err is 7e-5. For Cheb 9e-14 and Lin 2e-1
        if case != "Lin"
            der1 = @inferred funeval(c_direct, basis, X, [0 1])
            @test maximum(abs, der1 - yprime2) <= 2e-1
            der2 = @inferred funeval(c_direct, basis, x12, [0 1])
            @test maximum(abs, der1 - yprime2) <= 2e-1
            @test maximum(abs, der1 - der2) <= 1e-12
        end

    end

    @testset "other funeval methods" begin
        b1 = basis[1]
        y1 = sin.(x12[1])
        c = funfitxy(b1, x12[1], y1)[1]
        for (i, x) in enumerate(x12[1])
            @test ≈(y1[i], @inferred(funeval(c, b1, x)), atol=1e-14)
        end

        for (i, x) in enumerate(x12[1])
            @test ≈([y1[i], y1[i]], @inferred(funeval([c c], b1, x)), atol=1e-14)
        end

        @test ≈(y1, @inferred(funeval(c, b1, x12[1])), atol=1e-14)
        @test ≈([y1 y1], @inferred(funeval([c c], b1, x12[1])), atol=1e-14)

    end

    @testset "test interpoland methods" begin
        # (Basis, BasisMatrix, Array)
        intp1 = Interpoland(basis, BasisMatrix(basis, Tensor()), y)
        @test maximum(abs, intp1(X) - y) <= 1e-12

        # (Basis, Array,..)
        intp2 = Interpoland(basis, y)
        @test maximum(abs, intp2(X) - y) <= 1e-12

        # (BasisParams, Function)
        intp3 = Interpoland(basis, f)
        @test maximum(abs, intp3(X) - y) <= 1e-12
    end

    @testset "Printing" begin
        iob = IOBuffer()
        show(iob, Interpoland(basis, y))
    end

    # errors
    @test_throws ErrorException funeval(c_direct, basis, x12, 1)
    @test_throws ErrorException funeval(c_direct, basis, X, 1)

end # testset


"""
    s_per_degree(s_per_km, r_earth=$R_EARTH_KM_DEFAULT)

Convert horizontal slowness in s/km at the surface of the Earth to s/°.

`r_earth` is the radius of the spherical body in km.
"""
s_per_degree(s_km, r_earth=R_EARTH_KM_DEFAULT) = s_km*2π*r_earth/360

"""
    s_per_km(s_per_degree, r_earth=$R_EARTH_KM_DEFAULT)

Convert horizontal slowness in s/° at the surface of the Earth to s/km.

`r_earth` is the radius of the spherical body in km.
"""
s_per_km(s_degree, r_earth=R_EARTH_KM_DEFAULT) = s_degree*360/(2π*r_earth)


# ENV["JULIA_DEBUG"] = Metatheory
using Metatheory
using Metatheory.EGraphs
using Metatheory.EGraphs: in_same_set, find_root

@testset "Merging" begin
  testexpr = :((a * 2) / 2)
  testmatch = :(a << 1)
  G = EGraph(testexpr)
  t2, _ = addexpr!(G, testmatch)
  merge!(G, t2.id, EClassId(3))
  @test in_same_set(G.uf, t2.id, EClassId(3)) == true
  # DOES NOT UPWARD MERGE
end

# testexpr = :(42a + b * (foo($(Dict(:x => 2)), 42)))

@testset "Simple congruence - rebuilding" begin
  G = EGraph()
  ec1, _ = addexpr!(G, :(f(a, b)))
  ec2, _ = addexpr!(G, :(f(a, c)))

  testexpr = :(f(a, b) + f(a, c))

  testec, _ = addexpr!(G, testexpr)

  t1, _ = addexpr!(G, :b)
  t2, _ = addexpr!(G, :c)

  c_id = merge!(G, t2.id, t1.id)
  @test in_same_set(G.uf, c_id, t1.id)
  @test in_same_set(G.uf, t2.id, t1.id)
  rebuild!(G)
  @test in_same_set(G.uf, ec1.id, ec2.id)
end

@testset "Simple nested congruence" begin
  apply(n, f, x) = n == 0 ? x : apply(n - 1, f, f(x))
  f(x) = Expr(:call, :f, x)

  G = EGraph(:a)

  t1, _ = addexpr!(G, apply(6, f, :a))
  t2, _ = addexpr!(G, apply(9, f, :a))

  c_id = merge!(G, t1.id, EClassId(1)) # a == apply(6,f,a)
  c2_id = merge!(G, t2.id, EClassId(1)) # a == apply(9,f,a)

  rebuild!(G)

  t3, _ = addexpr!(G, apply(3, f, :a))
  t4, _ = addexpr!(G, apply(7, f, :a))

  # f^m(a) = a = f^n(a) ⟹ f^(gcd(m,n))(a) = a
  @test in_same_set(G.uf, t1.id, EClassId(1)) == true
  @test in_same_set(G.uf, t2.id, EClassId(1)) == true
  @test in_same_set(G.uf, t3.id, EClassId(1)) == true
  @test in_same_set(G.uf, t4.id, EClassId(1)) == false

  # if m or n is prime, f(a) = a
  t5, _ = addexpr!(G, apply(11, f, :a))
  t6, _ = addexpr!(G, apply(1, f, :a))
  c5_id = merge!(G, t5.id, EClassId(1)) # a == apply(11,f,a)

  rebuild!(G)

  @test in_same_set(G.uf, t5.id, EClassId(1)) == true
  @test in_same_set(G.uf, t6.id, EClassId(1)) == true
end


begin
	using StatsBase
	function coin_toss(n)
		toss = []
		head = 0
		for i in 1:n
			push!(toss, sample(["H", "T"], 1, replace = false))
		end
		for i in toss
			if i == ["H"]
				head += 1
			end
		end
		return head/n
	end
end


using NonUniformRandomVariateGeneration
using Test

import Random.seed!

include("goodness_of_fit.jl")

@testset "Binomial tests" begin
  include("binomial_test.jl")
end

@testset "Multinomial tests" begin
  include("multinomial_test.jl")
end

@testset "Poisson tests" begin
  include("poisson_test.jl")
end

@testset "Gamma tests" begin
  include("gamma_test.jl")
end

@testset "Beta tests" begin
  include("beta_test.jl")
end

@testset "Uniform tests" begin
  include("uniform_test.jl")
end

@testset "Categorical tests" begin
  include("categorical_test.jl")
end


@testset "Misc" begin
  include("Misc/psibound.jl")
  include("Misc/Primes.jl")
  include("Misc/Places.jl")
  include("Misc/meataxe.jl")
  include("Misc/stable_subgroups.jl")
  include("Misc/NumberField.jl")
end


# Author: Yoshimitsu Okada (https://www.researchgate.net/profile/Yoshimitsu_Okada)
# Translated by Pengcheng Shi (shipengcheng1230@gmail.com) 06/2018

# An example wrapper for DC3D in julia as below:
# function dc3d_fortran(x::T, y::T, z::T, α::T, dep::T, dip::T, al1::T, al2::T, aw1::T, aw2::T,
#     disl1::T, disl2::T, disl3::T) where {T <: AbstractFloat}
#
#     # initial return values
#     # `RefValue{T}` may be also viable other than `Array{T, 1}`
#     ux = Array{Float64}(1)
#     uy = Array{Float64}(1)
#     uz = Array{Float64}(1)
#     uxx = Array{Float64}(1)
#     uyx = Array{Float64}(1)
#     uzx = Array{Float64}(1)
#     uxy = Array{Float64}(1)
#     uyy = Array{Float64}(1)
#     uzy = Array{Float64}(1)
#     uxz = Array{Float64}(1)
#     uyz = Array{Float64}(1)
#     uzz = Array{Float64}(1)
#     iret = Array{Int64}(1)
#
#     # call okada's code which is renamed as "__dc3d__" (see binding rename shown below)
#     # input args tuple must be syntactically written instead of a variable assigned
#     # macros could be used to simplify this in the future
#     ccall((:__dc3d__, "dc3d.so"), Void,
#         (
#             Ref{Float64},
#             Ref{Float64}, Ref{Float64}, Ref{Float64}, Ref{Float64},
#             Ref{Float64}, Ref{Float64}, Ref{Float64}, Ref{Float64},
#             Ref{Float64}, Ref{Float64}, Ref{Float64}, Ref{Float64},
#             Ptr{Array{Float64,1}}, Ptr{Array{Float64,1}}, Ptr{Array{Float64,1}},
#             Ptr{Array{Float64,1}}, Ptr{Array{Float64,1}}, Ptr{Array{Float64,1}},
#             Ptr{Array{Float64,1}}, Ptr{Array{Float64,1}}, Ptr{Array{Float64,1}},
#             Ptr{Array{Float64,1}}, Ptr{Array{Float64,1}}, Ptr{Array{Float64,1}},
#             Ref{Int64},
#         ),
#         α, x, y, z, dep, dip, al1, al2, aw1, aw2, disl1, disl2, disl3,
#         ux, uy, uz, uxx, uyx, uzx, uxy, uyy, uzy, uxz, uyz, uzz,
#         iret,
#     )
#
#     # results valid iff iret[1] == 0
#     return (
#         iret[1],
#         ux[1], uy[1], uz[1],
#         uxx[1], uyx[1], uzx[1],
#         uxy[1], uyy[1], uzy[1],
#         uxz[1], uyz[1], uzz[1]
#     )
# end
# ```
#
#
# The corresponding fortran module is:
# ```fortran
# MODULE okada
#   USE, INTRINSIC :: iso_c_binding
#   IMPLICIT NONE
# CONTAINS
#
#   SUBROUTINE dc3d_wrapper(&
#        & alpha, &
#        & x, y, z, &
#        & depth, dip, &
#        & al1, al2, &
#        & aw1, aw2, &
#        & disl1, disl2, disl3, &
#        & ux, uy, uz, &
#        & uxx, uyx, uzx, &
#        & uxy, uyy, uzy, &
#        & uxz, uyz, uzz, &
#        & iret) BIND(C, NAME='__dc3d__')
#
#     REAL*8 :: &
#          & alpha, &
#          & x, y, z, &
#          & depth, dip, &
#          & al1, al2, &
#          & aw1, aw2, &
#          & disl1, disl2, disl3, &
#          & ux, uy, uz, &
#          & uxx, uyx, uzx, &
#          & uxy, uyy, uzy, &
#          & uxz, uyz, uzz
#
#     INTEGER*8 :: iret
#
#     CALL dc3d(&
#          & alpha, &
#          & x, y, z, &
#          & depth, dip, &
#          & al1, al2, &
#          & aw1, aw2, &
#          & disl1, disl2, disl3, &
#          & ux, uy, uz, &
#          & uxx, uyx, uzx, &
#          & uxy, uyy, uzy, &
#          & uxz, uyz, uzz, &
#          & iret)
#
#   END SUBROUTINE dc3d_wrapper
#
# END MODULE okada
# ```
#
#
# A sample of makefile is as below:
# ```make
# # Build Okada's code for calculating deformation due to a fault model
# #
# CC = gfortran
# CFLAGS = -fPIC -w -O3
# LDFLAGS = -shared
#
# SRCS = dc3d.f okada.f90
# OBJS = \$(SRCS:.c=.o)
#
# TARGET = dc3d.so
#
# \$(TARGET): \$(OBJS)
#     \$(CC) \$(CFLAGS) \$(LDFLAGS) -o \$(TARGET) \$(OBJS)
#

export dc3d

dc3d_cache(T) = ntuple(x -> zeros(T, 12), Val(10))

"""
    dc3d(x::T, y::T, z::T, α::T, dep::T, dip::T,
        al::Union{A, SubArray}, aw::Union{A, SubArray}, disl::A
        ) where {T <: Number, A <: AbstractVecOrMat{T}}

Calculate displacements and gradient of displacements due to a rectangular dislocation
    in an elastic isotropic halfspace.

Please see [dc3d](http://www.bosai.go.jp/study/application/dc3d/DC3Dhtml_E.html) for details.
    Also this fault coordinate system is widely used in this package.

## Arguments
- `x`, `y`, `z`: observational position, where ``z ≤ 0``
- `α`: elastic constant
- `dep`: depth of fault origin
- `dip`: dip angle in degree
- `al`: a vector of 2 numbers, indicating along strike (x-axis) spanning
- `aw`: a vector of 2 numbers, indicating along downdip (y-z plane) spanning
- `disl`: a vector of 3 numbers, indicating dislocation in along-strike,
    along-downdip and tensile respectively.

## Output
A vector of 12 numbers, each is ``u_{x}``, ``u_{y}``, ``u_{z}``, ``u_{x,x}``,
    ``u_{y,x}``, ``u_{z,x}``, ``u_{x,y}``, ``u_{y,y}``, ``u_{z,y}``, ``u_{x,z}``
    ``u_{y,z}``, ``u_{z,z}``.
"""
function dc3d(x::T, y::T, z::T, α::T, dep::T, dip::T, al::A, aw::A, disl::AbstractVector{<:Real},
    cache::U=dc3d_cache(T)) where {T<:Real, A<:AbstractVector, U}
    dc3d(x, y, z, α, dep, dip, al[1], al[2], aw[1], aw[2], disl[1], disl[2], disl[3], cache)
end

function dc3d(x::T, y::T, z::T, α::T, dep::T, dip::T, al1::T, al2::T, aw1::T, aw2::T, disl1::T, disl2::T, disl3::T,
    cache::U=dc3d_cache(T)) where {T<:Real, U}

    u, du, dua, dub, duc, zerosol, xi, et, kxi, ket = cache
    fill!(u, zero(T))
    fill!(kxi, zero(T))
    fill!(ket, zero(T))

    z > zero(T) && return zerosol

    xi[1] = x - al1
    xi[2] = x - al2
    d = dep + z
    sc1 = shared_constants_1(α, dip)
    sd, cd = sc1[6], sc1[7]
    p = y * cd + d * sd
    q = y * sd - d * cd
    et[1] = p - aw1
    et[2] = p - aw2

    if q ≈ zero(T) && ((xi[1] * xi[2] ≤ zero(T) && et[1] * et[2] ≈ zero(T)) ||	(et[1] * et[2] ≤ zero(T) && xi[1] * xi[2] ≈ zero(T)))
        return zerosol
    end

    r12 = hypot(xi[1], et[2], q)
    r21 = hypot(xi[2], et[1], q)
    r22 = hypot(xi[2], et[2], q)

    (xi[1] ≤ zero(T) && (r21 + xi[2]) ≈ zero(T)) && (kxi[1] = one(T))
    (xi[1] ≤ zero(T) && (r22 + xi[2]) ≈ zero(T)) && (kxi[2] = one(T))
    (et[1] ≤ zero(T) && (r12 + et[2]) ≈ zero(T)) && (ket[1] = one(T))
    (et[1] ≤ zero(T) && (r22 + et[2]) ≈ zero(T)) && (ket[2] = one(T))

    for k in 1:2, j in 1:2
        sc2 = shared_constants_2(xi[j], et[k], q, sd, cd, kxi[k], ket[j])
        ua(dua, sc1, sc2, xi[j], et[k], q, disl1, disl2, disl3)
        for i = 1:3:10
            du[i] = -dua[i]
            du[i + 1] = -dua[i + 1] * cd + dua[i + 2] * sd
            du[i + 2] = -dua[i + 1] * sd - dua[i + 2] * cd
            i < 10 && continue
            du[i] = -du[i]
            du[i + 1] = -du[i + 1]
            du[i + 2] = -du[i + 2]
        end
        for i = 1:12
            if j + k ≠ 3  u[i] += du[i] end
            if j + k == 3  u[i] -= du[i] end
        end
    end

    d = dep - z
    p = y * cd + d * sd
    q = y * sd - d * cd
    et[1] = p - aw1
    et[2] = p - aw2

    if q ≈ zero(T) && ((xi[1] * xi[2] ≤ zero(T) && et[1] * et[2] ≈ zero(T)) ||	(et[1] * et[2] ≤ zero(T) && xi[1] * xi[2] ≈ zero(T)))
        return zerosol
    end

    fill!(kxi, zero(T))
    fill!(ket, zero(T))
    r12 = hypot(xi[1], et[2], q)
    r21 = hypot(xi[2], et[1], q)
    r22 = hypot(xi[2], et[2], q)

    (xi[1] ≤ zero(T) && (r21 + xi[2]) ≈ zero(T)) && (kxi[1] = one(T))
    (xi[1] ≤ zero(T) && (r22 + xi[2]) ≈ zero(T)) && (kxi[2] = one(T))
    (et[1] ≤ zero(T) && (r12 + et[2]) ≈ zero(T)) && (ket[1] = one(T))
    (et[1] ≤ zero(T) && (r22 + et[2]) ≈ zero(T)) && (ket[2] = one(T))

    for k in 1:2, j in 1:2
        sc2 = shared_constants_2(xi[j], et[k], q, sd, cd, kxi[k], ket[j])
        ua(dua, sc1, sc2, xi[j], et[k], q, disl1, disl2, disl3)
        ub(dub, sc1, sc2, xi[j], et[k], q, disl1, disl2, disl3)
        uc(duc, sc1, sc2, xi[j], et[k], q, z, disl1, disl2, disl3)
        for i = 1:3:10
            du[i] = dua[i] + dub[i] + z * duc[i]
            du[i + 1] = (dua[i + 1] + dub[i + 1] + z * duc[i + 1]) * cd - (dua[i + 2] + dub[i + 2] + z * duc[i + 2]) * sd
            du[i + 2] = (dua[i + 1] + dub[i + 1] - z * duc[i + 1]) * sd + (dua[i + 2] + dub[i + 2] - z * duc[i + 2]) * cd
            i < 10 && continue
            du[10] += duc[1]
            du[11] += duc[2] * cd - duc[3] * sd
            du[12] -= duc[2] * sd + duc[3] * cd
        end
        for i = 1:12
            j + k ≠ 3 && (u[i] += du[i])
            j + k == 3 && (u[i] -= du[i])
        end
    end
    return u
end

@inline function ua(u::A, sc1::B1, sc2::B2, xi::T, et::T, q::T, disl1::T, disl2::T, disl3::T,
    ) where {T <: Number, A <: AbstractArray{T}, B1 <: NTuple{12,T}, B2 <: NTuple{24,T}}
    alp1, alp2, alp3, alp4, alp5, sd, cd, sdsd, cdcd, sdcd, s2d, sc2d = sc1
    xi2, et2, q2, r, r2, r3, r5, y, d, tt, alx, ale, x11, y11, x32, y32, ey, ez, fy, fz, gy, gz, hy, hz = sc2

    fill!(u, zero(T))
    xy = xi * y11
    qx = q * x11
    qy = q * y11

    coeff = disl1 / 2π
    u[1]  += coeff * (tt / 2 + alp2 * xi * qy)
    u[2]  += coeff * (alp2 * q / r)
    u[3]  += coeff * (alp1 * ale - alp2 * q * qy)
    u[4]  += coeff * (-alp1 * qy - alp2 * xi2 * q * y32)
    u[5]  += coeff * (-alp2 * xi * q / r3)
    u[6]  += coeff * (alp1 * xy + alp2 * xi * q2 * y32)
    u[7]  += coeff * (alp1 * xy * sd + alp2 * xi * fy + d / 2 * x11)
    u[8]  += coeff * (alp2 * ey)
    u[9]  += coeff * (alp1 * (cd / r + qy * sd) - alp2 * q * fy)
    u[10] += coeff * (alp1 * xy * cd + alp2 * xi * fz + y / 2 * x11)
    u[11] += coeff * (alp2 * ez)
    u[12] += coeff * (-alp1 * (sd / r - qy * cd) - alp2 * q * fz)

    coeff = disl2 / 2π
    u[1]  += coeff * (alp2 * q / r)
    u[2]  += coeff * (tt / 2 + alp2 * et * qx)
    u[3]  += coeff * (alp1 * alx - alp2 * q * qx)
    u[4]  += coeff * (-alp2 * xi * q / r3)
    u[5]  += coeff * (-qy / 2 - alp2 * et * q / r3)
    u[6]  += coeff * (alp1 / r + alp2 * q2 / r3)
    u[7]  += coeff * (alp2 * ey)
    u[8]  += coeff * (alp1 * d * x11 + xy / 2 * sd + alp2 * et * gy)
    u[9]  += coeff * (alp1 * y * x11 - alp2 * q * gy)
    u[10] += coeff * (alp2 * ez)
    u[11] += coeff * (alp1 * y * x11 + xy / 2 * cd + alp2 * et * gz)
    u[12] += coeff * (-alp1 * d * x11 - alp2 * q * gz)

    coeff = disl3 / 2π
    u[1]  += coeff * (-alp1 * ale - alp2 * q * qy)
    u[2]  += coeff * (-alp1 * alx - alp2 * q * qx)
    u[3]  += coeff * (tt / 2 - alp2 * (et * qx + xi * qy))
    u[4]  += coeff * (-alp1 * xy + alp2 * xi * q2 * y32)
    u[5]  += coeff * (-alp1 / r + alp2 * q2 / r3)
    u[6]  += coeff * (-alp1 * qy - alp2 * q * q2 * y32)
    u[7]  += coeff * (-alp1 * (cd / r + qy * sd) - alp2 * q * fy)
    u[8]  += coeff * (-alp1 * y * x11 - alp2 * q * gy)
    u[9]  += coeff * (alp1 * (d * x11 + xy * sd) + alp2 * q * hy)
    u[10] += coeff * (alp1 * (sd / r - qy * cd) - alp2 * q * fz)
    u[11] += coeff * (alp1 * d * x11 - alp2 * q * gz)
    u[12] += coeff * (alp1 * (y * x11 + xy * cd) + alp2 * q * hz)
end

@inline function ub(u::A, sc1::B1, sc2::B2, xi::T, et::T, q::T, disl1::T, disl2::T, disl3::T,
    ) where {T <: Number,A <: AbstractArray{T},B1 <: NTuple{12,T},B2 <: NTuple{24,T}}
    alp1, alp2, alp3, alp4, alp5, sd, cd, sdsd, cdcd, sdcd, s2d, sc2d = sc1
    xi2, et2, q2, r, r2, r3, r5, y, d, tt, alx, ale, x11, y11, x32, y32, ey, ez, fy, fz, gy, gz, hy, hz = sc2

    fill!(u, zero(T))
    rd = r + d
    d11 = one(T) / (r * rd)
    aj2 = xi * y / rd * d11
    aj5 = -(d + y * y / rd) * d11
    if (cd ≉ zero(T))
        if (xi ≈ zero(T))
            ai4 = zero(T)
        else
            x = sqrt(xi2 + q2)
            ai4 = 1 / cdcd * (xi / rd * sdcd + 2 * atan((et * (x + q * cd) + x * (r + x) * sd) / (xi * (r + x) * cd)))
        end
        ai3 = (y * cd / rd - ale + sd * log(rd)) / cdcd
        ak1 = xi * (d11 - y11 * sd) / cd
        ak3 = (q * y11 - y * d11) / cd
        aj3 = (ak1 - aj2 * sd) / cd
        aj6 = (ak3 - aj5 * sd) / cd
    else
        rd2 = rd * rd
        ai3 = (et / rd + y * q / rd2 - ale) / 2
        ai4 = xi * y / rd2 / 2
        ak1 = xi * q / rd * d11
        ak3 = sd / rd * (xi2 * d11 - 1)
        aj3 = -xi / rd2 * (q2 * d11 - 1 / 2)
        aj6 = -y / rd2 * (xi2 * d11 - 1 / 2)
    end

    xy = xi * y11
    ai1 = -xi / rd * cd - ai4 * sd
    ai2 = log(rd) + ai3 * sd
    ak2 = 1 / r + ak3 * sd
    ak4 = xy * cd - ak1 * sd
    aj1 = aj5 * cd - aj6 * sd
    aj4 = -xy - aj2 * cd + aj3 * sd

    qx = q * x11
    qy = q * y11

    coeff = disl1 / 2π
    u[1]  += coeff * (-xi * qy - tt - alp3 * ai1 * sd)
    u[2]  += coeff * (-q / r + alp3 * y / rd * sd)
    u[3]  += coeff * (q * qy - alp3 * ai2 * sd)
    u[4]  += coeff * (xi2 * q * y32 - alp3 * aj1 * sd)
    u[5]  += coeff * (xi * q / r3 - alp3 * aj2 * sd)
    u[6]  += coeff * (-xi * q2 * y32 - alp3 * aj3 * sd)
    u[7]  += coeff * (-xi * fy - d * x11 + alp3 * (xy + aj4) * sd)
    u[8]  += coeff * (-ey + alp3 * (1 / r + aj5) * sd)
    u[9]  += coeff * (q * fy - alp3 * (qy - aj6) * sd)
    u[10] += coeff * (-xi * fz - y * x11 + alp3 * ak1 * sd)
    u[11] += coeff * (-ez + alp3 * y * d11 * sd)
    u[12] += coeff * (q * fz + alp3 * ak2 * sd)

    coeff = disl2 / 2π
    u[1]  += coeff * (-q / r + alp3 * ai3 * sdcd)
    u[2]  += coeff * (-et * qx - tt - alp3 * xi / rd * sdcd)
    u[3]  += coeff * (q * qx + alp3 * ai4 * sdcd)
    u[4]  += coeff * (xi * q / r3 + alp3 * aj4 * sdcd)
    u[5]  += coeff * (et * q / r3 + qy + alp3 * aj5 * sdcd)
    u[6]  += coeff * (-q2 / r3 + alp3 * aj6 * sdcd)
    u[7]  += coeff * (-ey + alp3 * aj1 * sdcd)
    u[8]  += coeff * (-et * gy - xy * sd + alp3 * aj2 * sdcd)
    u[9]  += coeff * (q * gy + alp3 * aj3 * sdcd)
    u[10] += coeff * (-ez - alp3 * ak3 * sdcd)
    u[11] += coeff * (-et * gz - xy * cd - alp3 * xi * d11 * sdcd)
    u[12] += coeff * (q * gz - alp3 * ak4 * sdcd)

    coeff = disl3 / 2π
    u[1]  += coeff * (q * qy - alp3 * ai3 * sdsd)
    u[2]  += coeff * (q * qx + alp3 * xi / rd * sdsd)
    u[3]  += coeff * (et * qx + xi * qy - tt - alp3 * ai4 * sdsd)
    u[4]  += coeff * (-xi * q2 * y32 - alp3 * aj4 * sdsd)
    u[5]  += coeff * (-q2 / r3 - alp3 * aj5 * sdsd)
    u[6]  += coeff * (q * q2 * y32 - alp3 * aj6 * sdsd)
    u[7]  += coeff * (q * fy - alp3 * aj1 * sdsd)
    u[8]  += coeff * (q * gy - alp3 * aj2 * sdsd)
    u[9]  += coeff * (-q * hy - alp3 * aj3 * sdsd)
    u[10] += coeff * (q * fz + alp3 * ak3 * sdsd)
    u[11] += coeff * (q * gz + alp3 * xi * d11 * sdsd)
    u[12] += coeff * (-q * hz + alp3 * ak4 * sdsd)
end

@inline function uc(u::A, sc1::B1, sc2::B2, xi::T, et::T, q::T, z::T, disl1::T, disl2::T, disl3::T,
    ) where {T <: Number,A <: AbstractArray{T},B1 <: NTuple{12,T},B2 <: NTuple{24,T}}
    alp1, alp2, alp3, alp4, alp5, sd, cd, sdsd, cdcd, sdcd, s2d, sc2d = sc1
    xi2, et2, q2, r, r2, r3, r5, y, d, tt, alx, ale, x11, y11, x32, y32, ey, ez, fy, fz, gy, gz, hy, hz = sc2

    fill!(u, zero(T))
    c = d + z
    x53 = (8 * r2 + 9 * r * xi + 3 * xi2) * x11 * x11 * x11 / r2
    y53 = (8 * r2 + 9 * r * et + 3 * et2) * y11 * y11 * y11 / r2
    h = q * cd - z
    z32 = sd / r3 - h * y32
    z53 = 3 * sd / r5 - h * y53
    y0 = y11 - xi2 * y32
    z0 = z32 - xi2 * z53
    ppy = cd / r3 + q * y32 * sd
    ppz = sd / r3 - q * y32 * cd
    qq = z * y32 + z32 + z0
    qqy = 3 * c * d / r5 - qq * sd
    qqz = 3 * c * y / r5 - qq * cd + q * y32
    xy = xi * y11
    # qx = q * x11
    qy = q * y11
    qr = 3 * q / r5
    # cqx = c * q * x53
    cdr = (c + d) / r3
    yy0 = y / r3 - y0 * cd

    coeff = disl1 / 2π
    u[1]  += coeff * (alp4 * xy * cd - alp5 * xi * q * z32)
    u[2]  += coeff * (alp4 * (cd / r + 2 * qy * sd) - alp5 * c * q / r3)
    u[3]  += coeff * (alp4 * qy * cd - alp5 * (c * et / r3 - z * y11 + xi2 * z32))
    u[4]  += coeff * (alp4 * y0 * cd - alp5 * q * z0)
    u[5]  += coeff * (-alp4 * xi * (cd / r3 + 2 * q * y32 * sd) + alp5 * c * xi * qr)
    u[6]  += coeff * (-alp4 * xi * q * y32 * cd + alp5 * xi * (3 * c * et / r5 - qq))
    u[7]  += coeff * (-alp4 * xi * ppy * cd - alp5 * xi * qqy)
    u[8]  += coeff * (alp4 * 2 * (d / r3 - y0 * sd) * sd - y / r3 * cd - alp5 * (cdr * sd - et / r3 - c * y * qr))
    u[9]  += coeff * (-alp4 * q / r3 + yy0 * sd + alp5 * (cdr * cd + c * d * qr - (y0 * cd + q * z0) * sd))
    u[10] += coeff * (alp4 * xi * ppz * cd - alp5 * xi * qqz)
    u[11] += coeff * (alp4 * 2 * (y / r3 - y0 * cd) * sd + d / r3 * cd - alp5 * (cdr * cd + c * d * qr))
    u[12] += coeff * (yy0 * cd - alp5 * (cdr * sd - c * y * qr - y0 * sdsd + q * z0 * cd))

    coeff = disl2 / 2π
    u[1]  += coeff * (alp4 * cd / r - qy * sd - alp5 * c * q / r3)
    u[2]  += coeff * (alp4 * y * x11 - alp5 * c * et * q * x32)
    u[3]  += coeff * (-d * x11 - xy * sd - alp5 * c * (x11 - q2 * x32))
    u[4]  += coeff * (-alp4 * xi / r3 * cd + alp5 * c * xi * qr + xi * q * y32 * sd)
    u[5]  += coeff * (-alp4 * y / r3 + alp5 * c * et * qr)
    u[6]  += coeff * (d / r3 - y0 * sd + alp5 * c / r3 * (1 - 3 * q2 / r2))
    u[7]  += coeff * (-alp4 * et / r3 + y0 * sdsd - alp5 * (cdr * sd - c * y * qr))
    u[8]  += coeff * (alp4 * (x11 - y * y * x32) - alp5 * c * ((d + 2 * q * cd) * x32 - y * et * q * x53))
    u[9]  += coeff * (xi * ppy * sd + y * d * x32 + alp5 * c * ((y + 2 * q * sd) * x32 - y * q2 * x53))
    u[10] += coeff * (-q / r3 + y0 * sdcd - alp5 * (cdr * cd + c * d * qr))
    u[11] += coeff * (alp4 * y * d * x32 - alp5 * c * ((y - 2 * q * sd) * x32 + d * et * q * x53))
    u[12] += coeff * (-xi * ppz * sd + x11 - d * d * x32 - alp5 * c * ((d - 2 * q * cd) * x32 - d * q2 * x53))

    coeff = disl3 / 2π
    u[1]  += coeff * (-alp4 * (sd / r + qy * cd) - alp5 * (z * y11 - q2 * z32))
    u[2]  += coeff * (alp4 * 2 * xy * sd + d * x11 - alp5 * c * (x11 - q2 * x32))
    u[3]  += coeff * (alp4 * (y * x11 + xy * cd) + alp5 * q * (c * et * x32 + xi * z32))
    u[4]  += coeff * (alp4 * xi / r3 * sd + xi * q * y32 * cd + alp5 * xi * (3 * c * et / r5 - 2 * z32 - z0))
    u[5]  += coeff * (alp4 * 2 * y0 * sd - d / r3 + alp5 * c / r3 * (1 - 3 * q2 / r2))
    u[6]  += coeff * (-alp4 * yy0 - alp5 * (c * et * qr - q * z0))
    u[7]  += coeff * (alp4 * (q / r3 + y0 * sdcd) + alp5 * (z / r3 * cd + c * d * qr - q * z0 * sd))
    u[8]  += coeff * (-alp4 * 2 * xi * ppy * sd - y * d * x32 + alp5 * c * ((y + 2 * q * sd) * x32 - y * q2 * x53))
    u[9]  += coeff * (-alp4 * (xi * ppy * cd - x11 + y * y * x32) + alp5 * (c * ((d + 2 * q * cd) * x32 - y * et * q * x53) + xi * qqy))
    u[10] += coeff * (-et / r3 + y0 * cdcd - alp5 * (z / r3 * sd - c * y * qr - y0 * sdsd + q * z0 * cd))
    u[11] += coeff * (alp4 * 2 * xi * ppz * sd - x11 + d * d * x32 - alp5 * c * ((d - 2 * q * cd) * x32 - d * q2 * x53))
    u[12] += coeff * (alp4 * (xi * ppz * cd + y * d * x32) + alp5 * (c * ((y - 2 * q * sd) * x32 + d * et * q * x53) + xi * qqz))
end

@inline function shared_constants_1(α::T, dip::T) where {T <: Number}
    alp1 = (1 - α) / 2
    alp2 = α / 2
    alp3 = (1 - α) / α
    alp4 = 1 - α
    alp5 = α
    sd = sind(dip)
    cd = cosd(dip)
    sdsd = sd^2
    cdcd = cd^2
    sdcd = sd * cd
    s2d = 2 * sdcd
    sc2d = cdcd - sdsd
    (alp1, alp2, alp3, alp4, alp5, sd, cd, sdsd, cdcd, sdcd, s2d, sc2d)
end

@inline function shared_constants_2(xi::T, et::T, q::T, sd::T, cd::T, kxi::T, ket::T) where {T <: Number}
    xi2 = xi * xi
    et2 = et * et
    q2 = q * q
    r2 = xi2 + et2 + q2
    r = sqrt(r2)

    r3 = r * r2
    r5 = r3 * r2
    y = et * cd + q * sd
    d = et * sd - q * cd

    if q ≈ zero(T)
        tt = zero(T)
    else
        tt = atan(xi * et / (q * r))
    end

    if kxi ≈ one(T)
        alx = -log(r - xi)
        x11 = zero(T)
        x32 = zero(T)
    else
        rxi = r + xi
        alx = log(rxi)
        x11 = 1 / (r * rxi)
        x32 = (r + rxi) * x11 * x11 / r
    end

    if ket ≈ one(T)
        ale = -log(r - et)
        y11 = zero(T)
        y32 = zero(T)
    else
        ret = r + et
        ale = log(ret)
        y11 = 1 / (r * ret)
        y32 = (r + ret) * y11 * y11 / r
    end

    ey = sd / r - y * q / r3
    ez = cd / r + d * q / r3
    fy = d / r3 + xi2 * y32 * sd
    fz = y / r3 + xi2 * y32 * cd
    gy = 2 * x11 * sd - y * q * x32
    gz = 2 * x11 * cd + d * q * x32
    hy = d * q * x32 + xi * q * y32 * sd
    hz = y * q * x32 + xi * q * y32 * cd

    (xi2, et2, q2, r, r2, r3, r5, y, d, tt, alx, ale, x11, y11, x32, y32, ey, ez, fy, fz, gy, gz, hy, hz)
end


# generate the dyadic tree, suitable for output to DOT
using SurrealNumbers
# var_args = "color=red"
var_args = ""
n=3; x_scale = 2.25
# n=4; x_scale = 5.00
out_dir = "Data/"

######## FIRST PICTURE

file = "$(out_dir)dyadic_tree_$n.dot"
io = open(file, "w")
println(io, "digraph dyadic_tree {")

name_map = Dict{SurrealFinite, Integer}()

z = zero(SurrealFinite)
m = 0
name_map[z] = m
var_args = "pos=\"0,0!\""
surreal2node(io, z, 0, extra_args=var_args)
println(io, "node_day_0 [shape=none, pos=\"$(x_scale*(n+0.5)),$(0)!\", label=\"Day 0\", fontsize=18]")

for k=1:n
    println(io, "node_day_$k [shape=none, pos=\"$(x_scale*(n+0.5)),$(k)!\", label=\"Day $k\", fontsize=18]")

    var_args = "pos=\"-$(x_scale*k),$(k)!\""
    x = convert(SurrealFinite, -k)
    m += 1
    surreal2node(io, x, m, extra_args=var_args)
    name_map[x] = m
    println(" k = $k, x = $x")
    tmp = name_map[x.R[1]]
    println(io, "   node_$m:R -> node_$tmp [color=\"blue3\"];")

    var_args = "pos=\"$(x_scale*k),$(k)!\""
    x = convert(SurrealFinite, k)
    m += 1
    surreal2node(io, x, m, extra_args=var_args)
    name_map[x] = m
    println(" k = $k, x = $x")
    tmp = name_map[x.L[1]]
    println(io, "   node_$m:L -> node_$tmp [color=\"red3\"];")

    for f=2:k
        for j=(f-2)*2^(k-f+1) + (1:2:2^(k-f+1))
            x = convert(SurrealFinite, j // 2^(k-f+1))
            var_args = "pos=\"$(x_scale*convert(Float64, x)),$(k)!\""
            m += 1
            surreal2node(io, x, m, extra_args=var_args)
            name_map[x] = m
            println(" k = $k, f = $f, x = $x")
            tmp = name_map[x.L[1]]
            println(io, "   node_$m:L -> node_$tmp [color=\"red3\"];")
            tmp = name_map[x.R[1]]
            println(io, "   node_$m:R -> node_$tmp [color=\"blue3\"];")

            x = convert(SurrealFinite, -j // 2^(k-f+1))
            var_args = "pos=\"$(x_scale*convert(Float64, x)),$(k)!\""
            m += 1
            surreal2node(io, x, m, extra_args=var_args)
            name_map[x] = m
            println(" k = $k, f = $f, x = $x")
            tmp = name_map[x.L[1]]
            println(io, "   node_$m:L -> node_$tmp [color=\"red3\"];")
            tmp = name_map[x.R[1]]
            println(io, "   node_$m:R -> node_$tmp [color=\"blue3\"];")
        end
    end
end

println(io, "}")
close(io)
# run(`dot -n -Kfdp -Tpdf -O $file`)
run(`neato -Tpdf -O $file`)
run(`neato -Tsvg -O $file`)

######## Colouring

n=4; x_scale = 5.00
# n=3; x_scale = 2.25

file2 = "$(out_dir)dyadic_tree_chi_$n.dot"
io = open(file2, "w")
println(io, "digraph dyadic_tree {")

name_map2 = Dict{SurrealFinite, Integer}()
color = Dict{SurrealFinite, Integer}()
color_map = Dict(1=>"red3", 2=>"blue3", 3=>"green3")
C = Dict(1=>"R", 2=>"B", 3=>"G")
fs = 24

z = zero(SurrealFinite)
m = 0
name_map2[z] = m
var_args = "pos=\"0,0!\""
cn = 1
println(io, "   node_$m [label=0, fontsize=$fs, color=\"$(color_map[cn])\", $var_args];")
x = z
color[x] = cn

for k=1:n
    cn = mod(k,2) + 1

    var_args = "pos=\"-$(x_scale*k),$(k)!\""
    x = convert(SurrealFinite, -k)
    m += 1
    println(io, "   node_$m [label=$(-k), fontsize=$fs, color=\"$(color_map[cn])\", $var_args];")
    name_map2[x] = m
    color[x] = cn
    println(" k = $k, x = $x")
    tmp = name_map2[x.R[1]]
    println(io, "   node_$m -> node_$tmp;")

    var_args = "pos=\"$(x_scale*k),$(k)!\""
    x = convert(SurrealFinite, k)
    m += 1
    println(io, "   node_$m [label=$(k), fontsize=$fs, color=\"$(color_map[cn])\", $var_args];")
    name_map2[x] = m
    color[x] = cn
    println(" k = $k, x = $x")
    tmp = name_map2[x.L[1]]
    println(io, "   node_$m -> node_$tmp;")

    for f=2:k
        for j=(f-2)*2^(k-f+1) + (1:2:2^(k-f+1))
            v = j // 2^(k-f+1)
            s = string(v.num) * "/" * string(v.den)
            x = convert(SurrealFinite, v)
            cn = setdiff([1,2,3], [color[x.L[1]], color[x.R[1]]] )[1]
            color[x] = cn

            var_args = "pos=\"$(x_scale*convert(Float64, x)),$(k)!\""
            m += 1
            println(io, "   node_$m [label=\"$(s)\", fontsize=$fs, color=\"$(color_map[cn])\", $var_args];")
            name_map[x] = m
            println(" k = $k, f = $f, x = $x")
            tmp = name_map[x.L[1]]
            println(io, "   node_$m -> node_$tmp;")
            tmp = name_map[x.R[1]]
            println(io, "   node_$m -> node_$tmp;")

            v = -j // 2^(k-f+1)
            s = string(v.num) * "/" * string(v.den)
            x = convert(SurrealFinite, v)
            cn = setdiff([1,2,3], [color[x.L[1]], color[x.R[1]]] )[1]
            color[x] = cn
            var_args = "pos=\"$(x_scale*convert(Float64, x)),$(k)!\""
            m += 1
            println(io, "   node_$m [label=\"$(s)\", fontsize=$fs, color=\"$(color_map[cn])\", $var_args];")
            name_map[x] = m
            println(" k = $k, f = $f, x = $x")
            tmp = name_map[x.L[1]]
            println(io, "   node_$m -> node_$tmp;")
            tmp = name_map[x.R[1]]
            println(io, "   node_$m -> node_$tmp;")
        end
    end
end

println(io, "}")
close(io)
# run(`dot -n -Kfdp -Tpdf -O $file`)
run(`neato -Tpdf -O $file2`)
run(`neato -Tsvg -O $file2`)

X = sort( collect(keys(color)))
for x in X
    print(" , $(C[color[x]])")
end
println()


using EconLH.ProductionFunctionsLH
using Random, Test
# Tests that output is continuous around elasticity 1
function elast_close_to_one_test()
  n = 3;
  T = 5;
  xM = LinRange(0.5, 5.0, T) * LinRange(2.0, 0.1, n)';

  elastV = LinRange(0.95, 1.05, 50);
  y1V = zeros(length(elastV));
  for (j, substElast) in enumerate(elastV)
    fS = pf.make_test_ces(T, n, substElast);
    yV = output(fS, xM);
    y1V[j] = yV[1]
  end
  @test all(abs.(diff(y1V)) .< 0.01)
end

@testset "CES" begin
  elast_close_to_one_test();
end

# ------------------


module Verhoeff

using Test

export verhoeff_gencheck, verhoeff_verify, stripcheck

# Lookup tables for permutation and group multiplication
const verhoeff_mult =
[
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
    [1, 2, 3, 4, 0, 6, 7, 8, 9, 5],
    [2, 3, 4, 0, 1, 7, 8, 9, 5, 6],
    [3, 4, 0, 1, 2, 8, 9, 5, 6, 7],
    [4, 0, 1, 2, 3, 9, 5, 6, 7, 8],
    [5, 9, 8, 7, 6, 0, 4, 3, 2, 1],
    [6, 5, 9, 8, 7, 1, 0, 4, 3, 2],
    [7, 6, 5, 9, 8, 2, 1, 0, 4, 3],
    [8, 7, 6, 5, 9, 3, 2, 1, 0, 4],
    [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
]

const verhoeff_perm =
[
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
    [1, 5, 7, 6, 2, 8, 3, 0, 9, 4],
    [5, 8, 0, 3, 7, 9, 6, 1, 4, 2],
    [8, 9, 1, 6, 0, 4, 3, 5, 2, 7],
    [9, 4, 5, 3, 1, 2, 6, 8, 7, 0],
    [4, 2, 8, 6, 5, 7, 3, 9, 0, 1],
    [2, 7, 9, 3, 8, 0, 6, 4, 1, 5],
    [7, 0, 4, 6, 9, 1, 3, 2, 5, 8]
]

const verhoeff_inv = [0, 4, 3, 2, 1, 5, 6, 7, 8, 9]

# x=invdigits(xs) is the inverse of xs=digits(x, base=10)
invdigits(xs) = foldr((x, t) -> t*10+x, xs)
@test invdigits(digits(848584)) == 848584
@test invdigits(digits(0))      == 0

warnabs(x)    =
begin
    if x < 0
        @warn "Input $x is negative. Taking absolute value."
        UInt(abs(x))
    else
        UInt(    x)
    end
end

# verhoeff_onestep computes the multiplication and permutation step of the Verhoeff algorithm.
# verhoeff_onestep is exclusively left-associative
verhoeff_onestep(t::Integer, (p, x)) = verhoeff_mult[begin+t][begin+verhoeff_perm[begin+(p-1)%8][begin+x]]
@test verhoeff_onestep(0, (1, 0)) ==  0
@test verhoeff_onestep(0, (2, 6)) ==  3
@test verhoeff_onestep(3, (3, 3)) ==  1
@test verhoeff_onestep(1, (4, 2)) ==  2

# verhoeff_alg implements the recursive steps that are central to the Verhoeff algorithm.
verhoeff_alg(xs::Array)        = foldl(verhoeff_onestep, collect(enumerate(xs)), init=0)
verhoeff_alg(x::Unsigned)      = verhoeff_alg(digits(x))
verhoeff_alg(x::Signed)        = verhoeff_alg(warnabs(x))

# verhoeff_verify verifies a number with a Verhoeff algorithm check digit
verhoeff_verify(x)             = verhoeff_alg(x) == 0
@test verhoeff_verify(2363)
@test verhoeff_verify([3,6,3,2])
@test !verhoeff_verify(2364)

# verhoeff_check returns a raw number's Verhoeff algorithm check digit
verhoeff_check(xs::Array)      = verhoeff_inv[begin+verhoeff_alg([[0]; xs])]
verhoeff_check(x::Unsigned)    = verhoeff_check(digits(x))
verhoeff_check(x::Signed)      = verhoeff_check(warnabs(x))
@test verhoeff_check(236)     == 3
@test verhoeff_check([6,3,2]) == 3

# verhoeff_gencheck takes a raw number and returns that number with its check
# digit appended to it.
verhoeff_gencheck(x::Array)    = [[verhoeff_check(x)]; x]
verhoeff_gencheck(x::Unsigned) = invdigits(verhoeff_gencheck(digits(x)))
verhoeff_gencheck(x::Signed)   = verhoeff_gencheck(warnabs(x))
@test verhoeff_gencheck([6,3,2]) == [3,6,3,2]
@test verhoeff_gencheck(236)     == 2363

stripcheck(x)                  = Int(floor(x/10))
@test stripcheck(1234) == 123
end


mymean(x)=sum(x)/length(x)   # 定义一个函数，求平均值
a1=[1,2,3,4]                 # Vector{Int64}
@time mymean(a1)             # 0.012165 seconds，其中包含 JIT 编译时间
a2 = 2 * a1                  # Vector{Int64}
@time mymean(a2)             # 0.000005 seconds，由于 a2 和 a1 同为 Vector{Int64}
                             # 此次调用不需再次编译
a3=Float64[1,2,3,4]          # Vector{Float64}，不同的类型
@time mymean(a3)             # 0.030347 seconds，针对新的类型，编译出特定的版本
@time mymean(a3)             # 0.000005 seconds，不需编译第二次


module Example

using Glimmer, Glimmer.FlexUI
using Meshing
using FileIO # MeshIO should also be installed
using GeometryBasics
using StaticArrays
using Colors
using LinearAlgebra

println("Start [$(splitext(basename(@__FILE__))[1])]")

#---------------------------------------------------------------
# define the application and some basic properties such as title and initial window size
#---------------------------------------------------------------
app = App()
prop!(app, :title, "Glimmer Example - Meshing")
prop!(app, :winInitWidth, 1400)
prop!(app, :winInitHeight,1200)

#---------------------------------------------------------------
# create the MeshCat 3D viewer
#---------------------------------------------------------------
scene = Scene(openWindow = false)
set_Z_up!(scene)
grid!(scene, false)
axes!(scene, false)
cameraTransform!(scene, lookAt(SVector(-1.0, 1.0, 1.0)*10, zero3()))
cameraPlanes!(scene, 0.1, 1000.0)

#---------------------------------------------------------------
# Define Variables
#---------------------------------------------------------------
surface_code = addVariable!(app, Variable(name="surface_code", type="string",
        value="""begin
                    gyroid(v) = cos(v[1])*sin(v[2])+cos(v[2])*sin(v[3])+cos(v[3])*sin(v[1]);
                    gyroid_shell(v) = max(gyroid(v)-0.4,-gyroid(v)-0.4);
                end"""))
status = addVariable!(app, Variable(name="status", type="string", value = ""))

show_mesh =addVariable!(app, Variable(name="show_mesh", type="bool",value=true))
mesh_opacity = addVariable!(app, Variable(name="mesh_opacity", type="flota64",value=0.8))
mesh_wireframe =addVariable!(app, Variable(name="mesh_wireframe", type="bool",value=false))

show_pc =addVariable!(app, Variable(name="show_pc", type="bool",value=false))
pc_opacity = addVariable!(app, Variable(name="pc_opacity", type="flota64",value=0.5))

sample_resolution = addVariable!(app, Variable(name="sample_resolution", type="int",value=40))

#---------------------------------------------------------------
# Define Controls
#---------------------------------------------------------------
ui = VContainer(
    Card(
        title="Marching Cube",
        content=VContainer(
            Field(
                label = "Surface Definition",
                input = "multiline",
                style = "width: 100%; font-family: monospace; font-size: 20px;",
                hint = "defining the sureface to be constructed",
                variable="surface_code"
            ),
            Label(
                text = "\$(status)",
                class = "h2",
                style="color: red;"
            ),

            Card(
                # title="Controls",
                content=HContainer(
                    Card(
                        # title="Controls",
                        content=VContainer(
                            Card(
                                title="Mesh Controls",
                                content=VContainer(
                                    CheckBox(
                                        label="Show Mesh",
                                        variable="show_mesh"
                                    ),
                                    Slider(
                                        text="Mesh Opacity",
                                        trailingText="[\$()]",
                                        min=0,
                                        max=1,
                                        value=0.5,
                                        step=0.1,
                                        variable="mesh_opacity"
                                    ),
                                    CheckBox(
                                        label="Wireframe",
                                        variable="mesh_wireframe"
                                    ),
                                ),
                            ),

                            Card(
                                title="Points Controls",
                                content=VContainer(
                                    CheckBox(
                                        label="Show Points",
                                        variable="show_pc"
                                    ),
                                    Slider(
                                        text="Points Opacity",
                                        trailingText="[\$()]",
                                        min=0,
                                        max=1,
                                        value=0.5,
                                        step=0.1,
                                        variable="pc_opacity"
                                    ),
                                ),
                            ),

                            Card(
                                title="Sampling",
                                content=VContainer(
                                    Slider(
                                        text="Sample Resolution",
                                        trailingText="[\$()]",
                                        min=4,
                                        max=100,
                                        value=20,
                                        variable="sample_resolution"
                                    ),
                                ),
                            ),

                        ),
                    ),

                    Card(
                        title="Surface Visualization",
                        style="width: 50vw;",
                        content=VContainer(
                            MeshCatViewer(
                                url =  url(scene),
                                width = "100%",
                                height = "600px",
                            ),
                        ),
                    ),

                ),
            ),

        ),
    ),

    Glimmer.exampleSourceAsCard(@__FILE__),     # add the source code of the example as the last control
)
# set the controls for the application
controls!(app, ui)

#---------------------------------------------------------------
# the render function - preparing the image
#---------------------------------------------------------------
# gyroid(v) = cos(v[1])*sin(v[2])+cos(v[2])*sin(v[3])+cos(v[3])*sin(v[1])
# gyroid_shell(v) = max(gyroid(v)-0.4,-gyroid(v)-0.4)
function compile()
    try
        d = Meta.parse(surface_code[])
        eval(d)
        Base.invokelatest(gyroid_shell, [0.0, 0.0, 0.0])    # sanity check
        status[] = ""
    catch e
        status[] = "Encountered an error [$e]"
    end
end

function surface_function(v)
    Base.invokelatest(gyroid_shell, v)
end

# prepare a simple texture
img = zeros(RGB, 1, 4)
img[1,1] = RGB(1.0, 0.0, 0.0)
img[1,2] = RGB(0.0, 1.0, 0.0)
img[1,3] = RGB(0.0, 0.0, 1.0)
img[1,4] = RGB(1.0, 1.0, 0.0)

io = IOBuffer()
# save(io, img)
save(Stream(format"PNG", io), img)
res = String(take!(io))
tex = Glimmer.TextureData(Vector{UInt8}(res))

function render()
    # clear the scene
    clear(scene)

    # compile the UI code
    compile()

    # don't process if there is an error
    if (status[] != "")
        return;
    end

    # generate directly using GeometryBasics API
    # Rect specifies the sampling intervals
    gy_mesh = GeometryBasics.Mesh(
        surface_function,
        Rect(Vec(pi*4,pi*4,pi*4)*-0.5, Vec(pi*4,pi*4,pi*4)*1),
        MarchingCubes(),
        samples=(sample_resolution[], sample_resolution[], sample_resolution[])
    )

    # create the texture coordinates for the new mesh
    verts = coordinates(gy_mesh)
    faces = GeometryBasics.faces(gy_mesh)
    N = length(verts)
    point_attributes = Dict{Symbol, Any}()
    point_attributes[:uv] = Vector{SVector{2, Float32}}(undef, N)
    for (i, v) in enumerate(verts)
        x = norm(v)
        y = 0.1
        point_attributes[:uv][i] = SVector(x, y)
    end

    gy_mesh = GeometryBasics.Mesh(meta(verts; point_attributes...), faces)

    # origin axes
    origin = Axes(
        tr=identityTransform(),
        shaft_scale=0.1,
        axes_scale=5.0,
        name="Origin")
    parent!(origin , root(scene))

    # triangulated mesh
    if (show_mesh[])
        # mat = Material(color=RGBA(0.2, 0.8, 0.2, mesh_opacity[]), wireframe=mesh_wireframe[])
        mat = Material(color=RGBA(1.0, 1.0, 1.0, mesh_opacity[]), wireframe=mesh_wireframe[], texture=tex)
        mesh = Glimmer.Mesh(mesh=gy_mesh, material=mat, name="MC Mesh")
        parent!(mesh , root(scene))
    end

    # vertices
    if (show_pc[])
        points = [SVector{3, Float64}(p) for p in coordinates(gy_mesh)]
        pc_mat = Material(color=RGBA(0.7, 0.2, 0.1, pc_opacity[]), size=0.05)
        pc = PointCloud(points=points, tr=identityTransform(), material=pc_mat, name="Mesh Points")
        parent!(pc , root(scene))
    end
end
renderFunction!(app, render)

#---------------------------------------------------------------
# Run the application
#---------------------------------------------------------------
run(app)

println("End [$(splitext(basename(@__FILE__))[1])]")

GeometryBasics.normal_mesh

end # module

# # save("gyroid.ply", gy_mesh)


# Zero mean function

"""
    MeanZero <: Mean

Zero mean function
```math
m(x) = 0.
```
"""
struct MeanZero <: Mean end

num_params(::MeanZero) = 0
grad_mean(::MeanZero, ::VecF64) = Float64[]
mean(::MeanZero, ::VecF64) = 0.0
mean(mZero::MeanZero, X::MatF64) =  fill(0.0, size(X, 2))
get_params(::MeanZero) = Float64[]
get_param_names(::MeanZero) = Symbol[]

function set_params!(::MeanZero, hyp::VecF64)
    length(hyp) == 0 || throw(ArgumentError("Zero mean function has no parameters"))
end


type Pedigree{T<:Integer}
    sire::Vector{T}
    dam::Vector{T}
    perm::Vector{T}
    lappt::Vector{T}
end

function Pedigree{T<:Integer}(sire::Vector{T},dam::Vector{T})
    (n = length(sire)) == length(dam) || throw(DimensionMismatch(""))
    for i in 1:n
        zero(T) ≤ sire[i] ≤ n && zero(T) ≤ dam[i] ≤ n ||
        error("row $i: sire and dam must be in 0:$n")
        sire[i] == i && error("Malformed pedigree, sire[$i] == $i")
        dam[i] == i && error("Malformed pedigree, dam[$i] == $i")
        sire[i] < i && dam[i] < i || error("order failed at $i, with sire and dam, $(sire[i]), $(dam[i])")
    end
    Pedigree(sire,dam,T[],T[])
end

## Create an ordering by longest ancestral path
function laporder{T<:Integer}(sire::Vector{T},dam::Vector{T})
    (n = length(sire)) == length(dam) || throw(DimensionMismatch(""))
    anc = sizehint(IntSet(),n)  # current set of ancestors
    for i in 1:n
        0 ≤ sire[i] ≤ n && 0 ≤ dam[i] ≤ n || error("row $i: sire and dam must be in 0:$n")
        sire[i] == 0 && dam[i] == 0 && push!(anc,i)  # first generation
    end

    ord = sizehint(collect(anc),n)        # anc first in ord, also reserve space
    pop = setdiff!(IntSet(1:n),anc)       # animals who have not yet been sorted
    lappt = sizehint([0,length(anc)],20)  # pointer to start of each lap level
    push!(anc,0)                          # add zero to the set of ancestors already done
    nextgen = sizehint(IntSet(),n)
    while length(pop) > 0
        empty!(nextgen)
        for i in pop
            sire[i] ∈ anc && dam[i] ∈ anc && push!(nextgen,i)
        end
        length(nextgen) > 0 || error("algorithm failure, empty nextgen")
        append!(ord,collect(nextgen))
        push!(lappt,lappt[end]+length(nextgen))
        union!(anc,nextgen)
        setdiff!(pop,nextgen)
    end
    length(ord) == n || error("Logic error in laporder: ord is not length n = $n")
    invp = invperm(ord)                 # checks that ord is indeed a permutation
    ss = Array(T,(n,))
    dd = Array(T,(n,))
    for i in 1:n
        j = ord[i]
        ss[i] = sire[j] > 0 ? invp[sire[j]] : zero(T)
        dd[i] = dam[j] > 0 ? invp[dam[j]] : zero(T)
    end
    ord,lappt,ss,dd
end


function complex_test()
    c1 = (2 + 3im)
    c2 = (4 + 5im)
    c3 = (c1 + c2)
    @assert(c3 == (6 + 8im))
    c4 = (c1 + 3)
    @assert(c4 == (5 + 3im))
    c5 = (c1 + 4im)
    @assert(c5 == (2 + 7im))
    c6 = (c3 - 2.3)
    @assert(c6 == (3.7 + 8im))
end

function main()
    complex_test()
    println(join(["OK"], " "))
end

main()


"""
	pb = BorderedProblem(;F, dxF, dpF, g, ∇g, dpg)

This composite type encodes a bordered problem, one by which we add a scalar constraint `g(x, p) = 0` to an equation `F(x, p) = 0`. This composite type thus allows to define the functional `G((x, p)) = [F(x, p) g(x, p)]` and solve `G = 0`.

You can then evaluate the functional using or `pb(z)` where `z = BorderedArray(x, p)` or `z = vcat(x, p)`, i.e. the last component of the vector is the Lagrange Multiplier.

# Arguments
The arguments correspond to the functions `F, g` and their derivatives.

# Simplified constructor

You can create such functional as `pb = BorderedProblem(F, g)`.

!!! warning "Multidimensional constraint (Really Experimental)"
    It is in fact possible, using this composite type, to define a bordered problem with constraint of dimension `npar > 1`. One has to pass the dimension to `pb = BorderedProblem(F, g, npar)` and possibly the derivatives as well. The second argument of `F,g` is `npar` dimensional (for now an `AbstractVector`). Finally, the only possible linear (bordered) solver in this case is `::MatrixBLS`.

"""
@with_kw struct BorderedProblem{Tf, Tdf, TdpF, Tg, Tdg, Tdpg, Tlens}
	F::Tf		# functional
	dxF::Tdf = (x0, p0) -> finiteDifferences(x -> F(x, p0), x0)	# partial Derivative w.r.t. first variable
	dpF::TdpF = (x0, p0) -> (F(x0, p0 + 1e-8) .- F(x0, p0)) .* 1e8	# partial Derivative w.r.t. first variable
	g::Tg		# scalar constraint
	∇g::Tdg = (x0, p0) -> finiteDifferences(x -> g(x, p0), x0)		# partial Derivative w.r.t. first variable
	dpg::Tdpg = (x0, p0) -> (g(x0, p0 + 1e-8) - g(x0, p0)) .* 1e8	# partial Derivative w.r.t. first variable
	npar::Int = 1													# number of parameters, equals length(g(x,p))
	lens::Tlens = @lens _[1]
end

BorderedProblem(F, g, lens::Lens, npar = 1) = BorderedProblem(F = F, g = g, lens = lens, npar = 1)

getParameterDim(pb::BorderedProblem) = pb.npar

function extractParameter(pb::BorderedProblem, x::AbstractVector)
	if getParameterDim(pb) == 1
		return x[end]
	else
		return @view x[end-pb.npar+1:end]
	end
end

extractVector(pb::BorderedProblem, x::AbstractVector) = @view x[1:end-getParameterDim(pb)]
setVector!(pb::BorderedProblem, out::AbstractVector, x::AbstractVector) = out[1:end-getParameterDim(pb)] .= x

etParameter!(pb::BorderedProblem, out::BorderedArray, p::Number) = out[end] = p
setParameter!(pb::BorderedProblem, out::AbstractVector, p) = out[end-getParameterDim(pb)+1:end] .= p

extractParameter(pb::BorderedProblem, x::BorderedArray) = x.p
extractVector(pb::BorderedProblem, x::BorderedArray) = x.u
setVector!(pb::BorderedProblem, out::BorderedArray, x) = copyto!(out.u, x)
setParameter!(pb::BorderedProblem, out::BorderedArray, p::Number) = out.p = p
setParameter!(pb::BorderedProblem, out::BorderedArray, p) = copyto!(out.p, p)

function (pb::BorderedProblem)(xe, par)
	out = similar(xe)
	# extract variables
	p = extractParameter(pb, xe)
	x = extractVector(pb, xe)
	# compute the residual
	setVector!(pb, out, pb.F(x, set(par, pb.lens, p)))
	setParameter!(pb, out, pb.g(x, set(par, pb.lens, p)))
	out
end

function (pb::BorderedProblem)(xe, par, dxe)
	out = similar(dxe)
	# extract variables
	p = extractParameter(pb, xe)
	x = extractVector(pb, xe)

	# extract the jacobians
	J = pb.dxF(x, par)

	@error "WIP"
	out
end

# Structure to hold the jacobian of the bordered problem
# mutable struct JacobianBorderedProblem{Tpb, Tj, Tdpf, Tdg, Tdpg}
# 	pb::Tpb
# 	J::Tj
# 	dpF::Tdpf
# 	∇g::Tdg
# 	dpg::Tdpg
# end

# # simplified constructor
# JacobianBorderedProblem(pb, x, p) = JacobianBorderedProblem(pb, pb.dxF(x, p), pb.dpF(x, p), pb.∇g(x, p), pb.dpg(x, p))
#
# JacobianBorderedProblem(pb, x, p) = JacobianBorderedProblem(pb, extractVector(pb, x), extractParameter(pb, x))
#
#
# function (Jpb::JacobianBorderedProblem)(x, p)
# 	# computation of the jacobian of the Bordered problem
# 	pb = Jpb.pb
# 	if Jpb.J isa AbstractArray
# 		copyto!(Jpb.J, 	 pb.dxF(x, p))
# 	else
# 		Jpb.J = pb.dxF(x, p)
# 	end
#
# 	if Jpb.dpF isa AbstractArray
# 		copyto!(Jpb.dpF, pb.dpF(x, p))
# 	else
# 		Jpb.dpF = pb.dpF(x, p)
# 	end
#
# 	if Jpb.∇g isa AbstractArray
# 		copyto!(Jpb.∇g,  pb.∇g(x, p))
# 	else
# 		Jpb.∇g = pb.∇g(x, p)
# 	end
# 	if getParameterDim(pb) == 1
# 		Jpb.dpg = pb.dpg(x, p)
# 	else
# 		copyto!(Jpb.dpg, pb.dpg(x, p))
# 	end
# 	return Jpb
# end
#
# (Jbp::JacobianBorderedProblem)(x) = Jbp(extractVector(Jbp.pb, x), extractParameter(Jbp.pb, x))
#
# @with_kw struct LinearSolverBorderedProblem{T, Ts, L <: AbstractBorderedLinearSolver} <: AbstractLinearSolver
# 	ls::L
# 	# these are used to alter the second linear equation in the bordered system
# 	xiu::T = 1.0
# 	xip::T = 1.0
# 	shift::Ts = nothing
# end
#
# # simplified constructor
# LinearSolverBorderedProblem(ls) = LinearSolverBorderedProblem(ls = ls)
#
# function (lsbdp::LinearSolverBorderedProblem)(J::JacobianBorderedProblem, x)
# 	# call the bordered linear solver
# 	ou, op, flag, it = lsbdp.ls(J.J, J.dpF, J.∇g, J.dpg, extractVector(J.pb, x), extractParameter(J.pb, x), lsbdp.xiu, lsbdp.xip; shift = lsbdp.shift)
# 	if x isa BorderedArray
# 		return BorderedArray(ou, op), flag, it
# 	else
# 		return vcat(ou, op), flag, it
# 	end
# end
#
# ####################################################################################################
# # newton functions
# """
# 	newtonBordered(pb::BorderedProblem, z0, options::NewtonPar{T, L, S}; kwargs...)
#
# This function solves the equation associated with the functional `pb` with initial guess
# """
# function newtonBordered(pb::Tpb, z0, par, options::NewtonPar{T, L, S}; kwargs...) where {T, L <: AbstractBorderedLinearSolver, S, Tpb <: BorderedProblem}
# 	@show z0
# 	Jac   = JacobianBorderedProblem(pb, z0, par)
# 	lsbpb = LinearSolverBorderedProblem(options.linsolver)
# 	options2 = @set options.linsolver = lsbpb
# 	return newton(pb, Jac, z0, par, options2; kwargs...)
# end
#
# """
# This is the newton solver used to solve `F(x, p) = 0` together
# with the scalar condition `n(x, p) = (x - x0) * xp + (p - p0) * lp - n0`
# """
# function _newtonPALC(F, Jh,
# 					z0::BorderedArray{vectype, T},
# 					tau0::BorderedArray{vectype, T},
# 					z_pred::BorderedArray{vectype, T},
# 					options::ContinuationPar{T},
# 					dottheta::DotTheta;
# 					linearbdalgo = BorderingBLS(),
# 					normN = norm,
# 					callback = cbDefault, kwargs...) where {T, vectype}
# 	# Extract parameters
# 	@error "WIP - will replace newtonPALC once performances improve"
# 	newtonOpts = options.newtonOptions
# 	@unpack tol, maxIter, verbose, alpha, almin, linesearch = newtonOpts
# 	@unpack theta, ds, finDiffEps = options
#
# 	N = (x, p) -> arcLengthEq(dottheta, minus(x, z0.u), p - z0.p, tau0.u, tau0.p, theta, ds)
# 	normAC = (resf, resn) -> max(normN(resf), abs(resn))
#
# 	pb = BorderedProblem(F = F, dxF = Jh, g = N, ∇g = (x, p) -> tau0.u, dpg = (x, p) -> tau0.p)
# 	Jac   = JacobianBorderedProblem(pb, z0)
# 	lsbpb = LinearSolverBorderedProblem(ls = linearbdalgo, xiu = theta / length(z0.u), xip = one(T) - theta)
# 	options2 = @set newtonOpts.linsolver = lsbpb
# 	return newton(pb, Jac, z0, options2; kwargs...)
# end
# ####################################################################################################
# # continuation function
# function continuationBordered(pb, z0, p0::Real, contParams::ContinuationPar, linearAlgo::AbstractBorderedLinearSolver; kwargs...) where {T, L <: AbstractBorderedLinearSolver, S}
# 	Jac   = p -> JacobianBorderedProblem(pb(p), z0)
# 	lsbpb = LinearSolverBorderedProblem(contParams.newtonOptions.linsolver)
# 	contParams2 = @set contParams.newtonOptions.linsolver = lsbpb
# 	return continuation((z, p) -> pb(p)(z),
# 						(z, p) -> Jac(p)(z),
# 						z0, p0, contParams2; kwargs...)
# end
#
# """
# continuationBordered(prob, z0, p0::Real, contParams::ContinuationPar; kwargs...)
#
# This is the continuation routine for finding the curve of solutions of a family of Bordered problems `p->prob(p)`.
#
# # Arguments
# - `p -> prob(p)` is a family such that `prob(p)::BorderedProblem` encodes the functional G
# - `z0` a guess for the constrained problem.
# - `p0` initial parameter, must be a real number
# - `contParams` same as for the regular `continuation` method
# """
# function continuationBordered(prob, z0, p0::Real, contParams::ContinuationPar; linearAlgo = BorderingBLS(), kwargs...) where {T, L <: AbstractBorderedLinearSolver, S}
# 	linearAlgo = @set linearAlgo.solver = contParams.newtonOptions.linsolver
# 	return continuationBordered(prob, z0, p0, contParams, linearAlgo; kwargs...)
# end


  """
  Module containing a gallery of examples of nonlinear eigenvalue problems.\\
  Look at the function 'nep_gallery()' for further instructions.
  """
module Gallery
    using NonlinearEigenproblems.Serialization
    using ..NEPCore
    using ..NEPTypes
    using Random
    using LinearAlgebra
    using SparseArrays
    using SpecialFunctions

    export nep_gallery

    function __init__()
        # Add the search-path to the extra galleries - so that unloaded modules can be easily loaded from top level
        this_path = string(@__DIR__, "/gallery_extra")
        if !(this_path in LOAD_PATH)
            push!(LOAD_PATH, this_path)
        end
    end

    # Add the search-path to the extra galleries - temporarily so so needed files can be included
    push!(LOAD_PATH, string(@__DIR__, "/gallery_extra"))
    include("gallery_extra/basic_random_examples.jl")
    include("gallery_extra/gallery_examples.jl")
    include("gallery_extra/distributed_example.jl")
    include("gallery_extra/periodic_dde.jl")
    include("gallery_extra/NLEVP_native.jl")
    include("gallery_extra/bem_hardcoded/bem_hardcoded.jl");
    include("gallery_extra/load_dtn_dimer.jl");

  """
     nep=nep_gallery(name)
     nep=nep_gallery(name,params)
     nep=nep_gallery(name,params;kwargs)

Collection of nonlinear eigenvalue problems.
Returns a NEP object from a gallery of examples of nonlinear eigenvalue problems.
The parameter `name` decides which NEP.

# Supported problems:
The following list describes the NEP with a certain `name` and the associated parameters (`params`) and keyword arguments (`kwargs`), if any.\\

* `dep0`: A random [`DEP`](@ref) with one delay tau = 1, generated with a pseudorandom number generator. \\
    *params:* size (default = 5)

* `dep0_sparse`: A random [`DEP`](@ref) with sparse matrices and one delay tau = 1,  generated with a pseudorandom number generator.\\
    *Params:* Two optional params determining the size (default = 5) and the fill (default = 0.25)

* `dep0_tridiag`: A random [`DEP`](@ref) with sparse tridiagonal matrices and one delay tau = 1, generated with a pseudorandom number generator.\\
    *Params:* size (default = 100)

* `dep_symm_double`: A [`DEP`](@ref) with double eigenvalues and sparse symmetric matrices and one delay tau = 1.\\
    *Params:* size (default = 100)\\
    *Reference:* Example from [H. Voss and M. M. Betcke, Restarting iterative projection methods for Hermitian nonlinear eigenvalue problems with minmax property, Numer. Math., 2017](https://doi.org/10.1007/s00211-016-0804-3)

* `dep_double`: A [`DEP`](@ref) with a double non-semisimple eigenvalue in λ=3πi.\\
    *Reference:* Example from [E. Jarlebring, Convergence factors of Newton methods for nonlinear eigenvalue problems, LAA, 2012](https://doi.org/10.1016/j.laa.2010.08.045)

* `dep1`: A [`DEP`](@ref) with one eigenvalue equal to one.

* `pep0`: A random [`PEP`](@ref), generated with a pseudorandom number generator\\
    *Params:* size (default = 200)

* `pep0_sym`: A random symmetric [`PEP`](@ref), generated with a pseudorandom number generator\\
    *Params:* size (default = 200)

* `pep0_sparse`: A random [`PEP`](@ref) with sparse matrices, generated with a pseudorandom number generator\\
    *Params:* Two optional `params` determining the size (default = 200) and the fill (default = 0.03)

* `real_quadratic`: A quadratic [`PEP`](@ref) with real eigenvalues.\\
    *Solutions:*     Four smallest eigenvalues of the problem:\\
    ∘ `-2051.741417993845`\\
    ∘ ` -182.101627437811`\\
    ∘ `  -39.344930222838`\\
    ∘ `   -4.039879577113`\\

* `dep_distributed`: A NEP corresponding to a distributed delay time-delay system\\
    *Reference:* Example in [E. Jarlebring and W. Michiels and K. Meerbergen, The infinite Arnoldi method and an application to time-delay systems with distributed delays, Delay Systems - Methods, Applications and New Trends, 2012](https://doi.org/10.1007/978-3-642-25221-1_17)\\
    *Solutions:*  Some correct eigenvalues:\\
    ∘ `-0.400236388049641 + 0.970633098237807i`\\
    ∘ `-0.400236388049641 - 0.970633098237807i`\\
    ∘ `2.726146249832675 +  0.000000000000000i`\\
    ∘ `-1.955643591177653 + 3.364550574688863i`\\
    ∘ `-1.955643591177653 - 3.364550574688863i`\\
    ∘ `4.493937056300693 +  0.000000000000000i`\\
    ∘ `-1.631513006819252 + 4.555484848248613i`\\
    ∘ `-1.631513006819252 - 4.555484848248613i`\\
    ∘ `-1.677320660400946 + 7.496870451838560i`\\
    ∘ `-1.677320660400946 - 7.496870451838560i`

* `qdep0`:  A quadratic delay eigenvalue problem.\\
   *Reference:* [S. W. Gaaf and E. Jarlebring, The infinite Bi-Lanczos method for nonlinear eigenvalue problems, SIAM J. Sci. Comput., 2017](https://doi.org/10.1137/16M1084195)

* `qdep1`:  A quadratic delay eigenvalue problem.\\
   *Reference:* [E. Jarlebring and W. Michiels and K. Meerbergen, A linear eigenvalue algorithm for the  nonlinear eigenvalue problem, Numer. Math., 2011](https://doi.org/10.1007/s00211-012-0453-0)

* `qep_fixed_eig`: A quadratic eigenvalue problem with chosen eigenvalues.\\
   *Params:* Two optional `params` determining the size (default = 5) and a vector containing the eigenvalues (default = uniform [-1,1])

* `neuron0`: A [`DEP`](@ref) that stems from the modling of a coupled neuron.\\
   *Reference:* [L. P. Shayer and S. A. Campbell, Stability, bifurcation and multistability in a system of two coupled neurons with multiple time delays, SIAM J. Applied Mathematics, 2000](https://doi.org/10.1137/S0036139998344015). It is also a benchmark example in DDE-BIFTOOL.

* `schrodinger_movebc`: A NEP stemming from the discretization of a Schrödinger equation as described in the NEP-PACK online tutorial.  The nonlinearity contains ``sinh()``, ``cosh()`` and ``sqrt()``.\\
   *Params:* The optional parameters are size of discretization `n`  and domain and potential description `L0`,`L1`,`α` and `V0`.

* `beam`: A [`DEP`](@ref) modelling a beam with delayed stabilizing feedback described. The A1-term is rank one.\\
   *Params:*  size of the matrix (defalut = 100)\\
   *Reference:* [R. Van Beeumen, E. Jarlebring, and W. Michiels,  A rank-exploiting infinite Arnoldi algorithm for nonlinear eigenvalue problems, 2016.](https://doi.org/10.1002/nla.2043)

* `sine`: A NEP formed by the sum of a polynomial and a sine-function. The sine-term has a rank-one matrix coefficient.\\
   *Reference:*  [R. Van Beeumen, E. Jarlebring, and W. Michiels,  A rank-exploiting infinite Arnoldi algorithm for nonlinear eigenvalue problems, 2016.](https://doi.org/10.1002/nla.2043)

* `bem_fichera`:  Represents a boundary element discretization of Helmholtz equation for a domain consisting of the unit cube, except one removed corner (Fichera corner). The mesh is hardcoded.
    *Params:* The parameter `N` determines the size of the problem (default `N = 5`).\\
    *Reference:* The model stems from the model in these papers:  [A boundary element method for solving PDE eigenvalue problems, M. Steinlechner, bachelor thesis, ETH Zürich, 2010](http://sma.epfl.ch/~anchpcommon/students/steinlechner.pdf) and  [Effenberger and Kressner, Chebyshev interpolation for nonlinear eigenvalue problems, BIT Numerical Mathematics, December 2012, Volume 52, Issue 4, pp 933–951](https://doi.org/10.1007/s10543-012-0381-5)

* `dtn_dimer`: NEP with quotients of Bessel functions stemming from the modeling of resonances \\
    *Params:*  This NEP takes two parameters: `data_dir::String` and `l::Int`. The `data_dir` specifies the directory of the dowloaded FEM-matrices (available here [https://umu.app.box.com/s/b52yux3z9rcl8y0l7la22k0vi062cvu5](https://umu.app.box.com/s/b52yux3z9rcl8y0l7la22k0vi062cvu5)). The integer `l` specifies the number of DtN-terms: 2l+1.\\
    *Reference:* [J. Araujo-Cabarcas, C. Engström and E. Jarlebring, Efficient resonance computations for Helmholtz problems based on a Dirichlet-to-Neumann map, J. Comput. Appl. Math., 330:177-192, 2018](http://arxiv.org/pdf/1606.09547))

The MATLAB-package described in
[T. Betcke, N. J. Higham, V. Mehrmann, Ch. Schröder, F. Tisseur, NLEVP: A Collection of Nonlinear Eigenvalue Problems, ACM Transactions on Mathematical Software 39(2), January 2011](https://doi.org/10.1145/2427023.2427024)
provides a number of benchmark problems for NEPs. These are available in NEP-PACK in two different ways.
We have native implementations of some problems (referred to as `nlevp_native_`) and the separate `GalleryNLEVP`.
The native implementation is preferred since the `GalleryNLEVP`  interfaces with MATLAB and is therefore considerably slower.

* `nlevp_native_gun`:  The benchmark problem from the NLEVP-collection called "gun", represented in the native NEP-PACK format.
    [B.-S. Liao, Z. Bai, L.-Q. Lee, and K. Ko. Nonlinear Rayleigh-Ritz iterative method for solving large scale
    nonlinear eigenvalue problems.  Taiwan. Journal of Mathematics, 14(3):869–883, 2010](https://doi.org/10.11650/twjm/1500405872)

* `nlevp_native_cd_player`:  The benchmark problem from the NLEVP-collection called "cd\\_player", represented in the native NEP-PACK format.
    [Y. Chahlaoui, and P. M. Van Dooren, Benchmark examples for model reduction of linear time-
    invariant dynamical systems. In Dimension Reduction of Large-Scale Systems, P. Benner, V. Mehrmann,
    and D. C. Sorensen, Eds. Lecture Notes in Computational Science and Engineering Series, vol. 45.
    Springer-Verlag, Berlin, 380–392, 2005.](https://doi.org/10.1007/3-540-27909-1_24)
    and
    P. M. R. Wortelboer, M. Steinbuch, and  O. H. Bosgra, Closed-loop balanced reduction with
    application to a compact disc mechanism. In Selected Topics in Identification, Modeling and Control.
    Vol. 9. Delft University Press, 47–58, 1996.
    and
    [W. Draijer, M. Steinbuch, and  O. H. Bosgra, Adaptive control of the radial servo system of a
    compact disc player. Automatica 28, 3, 455–462. 1992.](https://doi.org/10.1016/0005-1098(92)90171-B)

* `nlevp_native_fiber`: The benchmark problem from the NLEVP-collection called "fiber", represented in the native NEP-PACK format.
    One of terms in this problem is approximated by interpolation, and may not always coincide with the benchmark.
    [L. Kaufman, Eigenvalue problems in fiber optic design. SIAM J. Matrix Anal. Appl. 28, 1, 105–117, 2006.](https://doi.org/10.1137/S0895479803432708)
    and
    [X. Huang, Z. Bai, and Y. Su, Nonlinear rank-one modification of the symmetric eigenvalue problem. J. Comput. Math. 28, 2, 218–234, 2010](https://doi.org/10.4208/jcm.2009.10-m1002)

* `nlevp_native_hadeler`: The benchmark problem from the NLEVP-collection called "hadeler", represented in the native NEP-PACK format. The problem is of the form ``M(λ)=(e^λ-1)B+A_0+A_2λ^2``. \\
    [Hadeler K.  P.  1967.  Mehrparametrige  und  nichtlineare  Eigenwertaufgaben. Arch.  Rational  Mech. Anal. 27, 4, 306–328.](https://doi.org/10.1007/BF00281717)

* `nlevp_native_pdde_stability`: The benchmark problem from the NLEVP-collection called "pdde_stability", represented in the native NEP-PACK format.
    This problem is a quadratic eigenvalue with arbitrary given size `n`. See
    [E. Jarlebring, The Spectrum of Delay-Differential Equations:
    Numerical Methods, Stability and Perturbation, PhD thesis,
    TU Braunschweig, Institut Computational Mathematics, Germany, 2008](https://nbn-resolving.org/urn:nbn:de:gbv:084-22041) and
    [H. Fassbender, N. Mackey, D. S. Mackey and C. Schroeder, Structured
    Polynomial Eigenproblems Related to Time-Delay Systems, ETNA, 2008, vol 31, pp 306-330](http://etna.mcs.kent.edu/vol.31.2008/pp306-330.dir/)

* `nlevp_native_loaded_string`:  The benchmark problem from the NLEVP-collection called "pdde_stability", represented in the native NEP-PACK format.
    The parameters are (n,kappa,m) where n is the size, and the NEP is a SPMF with rational terms and the coefficient
    matrices are rank one modifications of Toeplitz matrices.\\
    [S. I. Solov"ev. Preconditioned iterative methods for a class of nonlinear eigenvalue problems. Linear Algebra Appl., 415 (2006), pp.210-229.](https://doi.org/10.1016/j.laa.2005.03.034)

# Example
```julia-repl
julia> nep=nep_gallery("dep0",100);
julia> norm(compute_Mlincomb(nep,1.0+1.0im,ones(size(nep,1))))
57.498446538064954
```

# See also the following galleries:
* GalleryNLEVP
* GalleryWaveguide
"""
    nep_gallery(name::String, params...; kwargs...)=nep_gallery(NEP, name, params...; kwargs...)
    function nep_gallery(::Type{T}, name::String, params...; kwargs...) where T<:NEP
        global Gallery
        if !haskey(GALLERY, name)
            error("$name not supported")
        end

        return GALLERY[name](params...; kwargs...)
    end

    const GALLERY = Dict(
        "dep0" => dep0,
        "dep1" => dep1,
        "dep0_sparse" => dep0_sparse,
        "dep0_tridiag" => dep0_tridiag,
        "dep_symm_double" => dep_symm_double,
        "dep_double" => dep_double,
        "pep0" => pep0,
        "pep0_sym" => pep0_sym,
        "pep0_sparse" => pep0_sparse,
        "real_quadratic" => real_quadratic,
        "dep_distributed" => gallery_dep_distributed,
        "qdep0" => qdep0,
        "qdep1" => qdep1,
        "qep_fixed_eig" => qep_fixed_eig,
        "periodicdde" => (params...; kwargs...) -> periodic_dde_gallery(PeriodicDDE_NEP, params...; kwargs...),
        "schrodinger_movebc" => schrodinger_movebc,
        "neuron0" => neuron0,
        "nlevp_native_gun" => nlevp_native_gun,
        "nlevp_native_hadeler" => nlevp_native_hadeler,
        "nlevp_native_cd_player" => nlevp_native_cd_player,
        "nlevp_native_fiber" => nlevp_native_fiber,
        "nlevp_native_pdde_stability" => nlevp_native_pdde_stability,
        "nlevp_native_loaded_string" => nlevp_native_loaded_string,
        "beam" => beam,
        "sine" => sine_nep,
        "bem_fichera" => bem_fichera,
        "dtn_dimer" => load_dtn_dimer
    )

end # end of Gallery-module