using PkgSRA
using Plots, Random, Distributions, Interpolations
pyplot()
Random.seed!(11)
using BSON: @save
include("../scripts/paper_settings.jl");
# include("../../utils/sindy_turing_utils.jl")
# include("../../utils/sindy_statistics_utils.jl")
# include("../../utils/main_algorithm_utils.jl")

# include("../../src/sra_model_object.jl");
# include("../../src/sra_model_functions.jl");
# include("../../src/sra_model_plotting.jl");

################################################################################
#####
##### SIR example: get data
#####
include(EXAMPLE_FOLDERNAME*"example_sir.jl")

# Define the multivariate forcing function
num_ctr = 3;
    U_starts = rand(1, num_ctr) .* tspan[2]/2
    U_widths = 0;#0.6;
    amplitude = 100.0
my_U_func_time2(t) = U_func_time(t, u0,
                        U_widths, U_starts,
                        F_dim=1,
                        amplitude=amplitude)

# Get data
sol = solve_sir_system(U_func_time=my_U_func_time2)
dat = Array(sol)
numerical_grad = numerical_derivative(dat, ts)
true_grad = core_dyn_true(dat)

U_true = zeros(size(dat))
for (i, t) in enumerate(ts)
    U_true[:,i] = my_U_func_time2(t)
end

# Intialize truth object
this_truth = sra_truth_object(true_grad, U_true, core_dyn_true)

#####
##### Build SRA object and analyze
#####
# Initialize
this_model = sra_stateful_object(ts, tspan, dat, u0, numerical_grad)
this_model.parameters.sindyc_ensemble_parameters[:selection_criterion] =
    sindy_cross_validate;
# this_model.parameters.variable_names = ["S", "I", "R"]
fit_first_model(this_model, 100);
print_current_equations(this_model, digits=5)

################################################################
### TESTING
opts = Dict(
    :library=>this_model.parameters.sindy_library,
    :use_lasso=>true,
    :quantile_threshold=>nothing,
    :num_terms=>[1, 2, 1],
    :var_names=>["S", "I", "R"])
m = sindyc(dat, numerical_grad, nothing, ts; opts...)

opts = Dict(
    :library=>this_model.parameters.sindy_library,
    :use_lasso=>false,
    :var_names=>["S", "I", "R"])
m2 = sindyc(dat, numerical_grad, nothing, ts; opts...)

lib = convert_string2function(opts[:library])
aug = calc_augmented_data(dat, lib)
condition_number = LinearAlgebra.cond(aug)
#################################################################

### Iterate
calculate_subsampled_ind(this_model);
all_models = fit_model(this_model);

# println("Iteration $i")
print_current_equations(this_model, digits=5)
print_true_equations(this_truth, digits=5)

plot_subsampled_points(this_model)
# plot_subsampled_simulation(this_model, 2)
plot_subsampled_derivatives(this_model, 1)
plot_residual(this_model)

# Cheating... this works perfectly!
this_model.sindy_model = core_dyn_true;
plot_subsampled_derivatives(this_model, 1)


using PyPlot, PyCall, IJulia

"""
    t = delaunay(p)

Delaunay triangulation `t` of N x 2 node array `p`.
"""
function delaunay(p)
    tri = pyimport("matplotlib.tri")
    t = tri[:Triangulation](p[:,1], p[:,2])
    return Int64.(t[:triangles] .+ 1)
end

"""
    edges, boundary_indices = all_edges(t)

Find all unique edges in the triangulation `t` (ne x 2 array)
Second output is indices to the boundary edges.
"""
function all_edges(t)
    edges = vcat(t[:,[1,2]], t[:,[2,3]], t[:,[3,1]])
    edges = sortslices(sort(edges, dims=2), dims=1)
    dup = all(edges[2:end,:] - edges[1:end-1,:] .== 0, dims=2)[:]
    keep = .![false;dup]
    edges = edges[keep,:]
    dup = [dup;false]
    dup = dup[keep]
    return edges, findall(.!dup)
end

"""
    e = boundary_nodes(t)

Find all boundary nodes in the triangulation `t`.
"""
function boundary_nodes(t)
    edges, boundary_indices = all_edges(t)
    return unique(edges[boundary_indices,:][:])
end

"""
    tplot(p, t, u=nothing)

If `u` == nothing: Plot triangular mesh with nodes `p` and triangles `t`.
If `u` == solution vector: Plot filled contour color plot of solution `u`.
"""
function tplot(p, t, u=nothing)
    clf()
    axis("equal")
    if u == nothing
        tripcolor(p[:,1], p[:,2], t .- 1, 0*t[:,1],
                  cmap="Set3", edgecolors="k", linewidth=1)
    else
        tricontourf(p[:,1], p[:,2], t .- 1, u, 20)
    end
    draw()
end

"""
    inside = inpolygon(p, pv)

Determine if each point in the N x 2 node array `p` is inside the polygon
described by the NE x 2 node array `pv`.
"""
function inpolygon(p::Array{Float64,2}, pv::Array{Float64,2})
    path = pyimport("matplotlib.path")
    poly = path[:Path](pv)
    inside = [poly[:contains_point](p[ip,:]) for ip = 1:size(p,1)]
end

function remove_outside_tris(p, t, pv)
    pmid = dropdims(sum(p[t,:], dims=2), dims=2) / 3
    is_inside = inpolygon(pmid, pv)
    t = t[is_inside,:]
end

function triarea(p, t)
    d12 = @. p[t[:,2],:] - p[t[:,1],:]
    d13 = @. p[t[:,3],:] - p[t[:,1],:]
    @. abs(d12[:,1] * d13[:,2] - d12[:,2] * d13[:,1]) / 2
end

function remove_tiny_tris(p, t)
    t = t[triarea(p,t) .> 1e-14,:]
end

function circumcenter(p)
    dp1 = @. p[2,:] - p[1,:]
    dp2 = @. p[3,:] - p[1,:]

    mid1 = @. (p[1,:] + p[2,:]) / 2
    mid2 = @. (p[1,:] + p[3,:]) / 2

    s = [ -dp1[2] dp2[2]; dp1[1] -dp2[1]] \ (-mid1 .+ mid2)
    pc = @. mid1' + s[1] * [-dp1[2] dp1[1]]
end

function edge_midpoints(p, t)
    pmid = reshape(p[t,:] + p[t[:,[2,3,1]],:], :, 2) / 2
    pmid = unique(pmid, dims=1)
end

function pmesh(pv, hmax, nref)
    p = zeros(Float64, 0, 2)
    for i = 1:size(pv,1) - 1
        pp = pv[i:i+1,:]
        L = sqrt(sum(diff(pp, dims=1).^2, dims=2))[1]
        if L > hmax
            n = ceil(Int, L / hmax)
            ss = (0:n) / n
            pp = [1 .- ss ss] * pp
        end
        p = [p; pp[1:end-1,:]]
    end

    t = zeros(Int64, 0, 3)
    while true
        t = delaunay(p)
        t = remove_tiny_tris(p, t)
        t = remove_outside_tris(p, t, pv)
        # tplot(p,t), pause(1e-3)

        area = triarea(p, t)
        maxarea, ix = findmax(area)
        if maxarea < hmax^2 / 2
            break
        end
        pc = circumcenter(p[t[ix,:],:])
        p = [p; pc]
    end

    for iref = 1:nref
        p = [p; edge_midpoints(p, t)]
        t = delaunay(p)
        t = remove_tiny_tris(p, t)
        t = remove_outside_tris(p, t, pv)
        # tplot(p, t), pause(1e-3)
    end

    e = boundary_nodes(t)
    p, t, e
end

function pmesh_test()
    pv = [0 0; 1 0; .5 .5; 1 1; 0 1; 0 0]
    p,t,e = pmesh(pv, 0.2, 1);
    tplot(p,t)
end

using SparseArrays
using LinearAlgebra
function coefficients(RHS, p, t, k)
    LHS = [1 p[t[k,1],1] p[t[k,1],2]; 1 p[t[k,2],1] p[t[k,2],2]; 1 p[t[k,3],1] p[t[k,3],2]]
    coeff = LHS\RHS'
    return coeff'
end

function element(coeffmatrix, a, b)
    return coeffmatrix[a,2]*coeffmatrix[b,2] + coeffmatrix[a,3]*coeffmatrix[b,3]
end

function fempoi(p, t, e)
    areas = triarea(p,t)
    n = size(p)[1]
    A = spzeros(n, n); b = zeros(n);
    for k = 1:size(t)[1]
        cm = [coefficients([1 0 0] , p, t, k) ;
              coefficients([0 1 0] , p, t, k) ;
              coefficients([0 0 1] , p, t, k) ]
        insert = [element(cm, 1, 1) element(cm, 1, 2) element(cm, 1, 3);
                  element(cm, 2, 1) element(cm, 2, 2) element(cm, 2, 3);
                  element(cm, 3, 1) element(cm, 3, 2) element(cm, 3, 3)]
        insert *=  areas[k]

        A[t[k,:],t[k,:]] = A[t[k,:],t[k,:]]+insert
        #aa = areas[k]/3.0
        b[t[k,:]] = b[t[k,:]].+ areas[k]/3.0
    end
    for k = 1:size(e)[1]
        i=e[k]
        A[i,:].=0.0
        A[i,i]=1.0
        b[i]=0.0
    end
    dropzeros!(A)
    println(b)
    println(A)

    return A \ b
end

function poiconv(pv, hmax, nrefmax)
   pmax, tmax, emax = pmesh(pv, hmax, nrefmax)
   umax = fempoi(pmax, tmax, emax)
   errors = zeros(nrefmax)
   for ncur = 0:(nrefmax-1)
       pprox, tprox, eprox = pmesh(pv, hmax, ncur)
       uprox = fempoi(pprox, tprox, eprox)
       errorarray = maximum(abs.(umax[1:size(uprox)[1]] - uprox))
       #errors = [errors ; errorarray]
       errors[ncur+1]=errorarray
   end

   return errors
end


# ---
# title: 1646. Get Maximum in Generated Array
# id: problem1646
# author: Tian Jun
# date: 2020-10-31
# difficulty: Easy
# categories: Array
# link: <https://leetcode.com/problems/get-maximum-in-generated-array/description/>
# hidden: true
# ---
#
# You are given an integer `n`. An array `nums` of length `n + 1` is generated
# in the following way:
#
#   * `nums[0] = 0`
#   * `nums[1] = 1`
#   * `nums[2 * i] = nums[i]` when `2 <= 2 * i <= n`
#   * `nums[2 * i + 1] = nums[i] + nums[i + 1]` when `2 <= 2 * i + 1 <= n`
#
# Return ****_the **maximum** integer in the array _`nums`.
#
#
#
# **Example 1:**
#
#
#
#     Input: n = 7
#     Output: 3
#     Explanation: According to the given rules:
#       nums[0] = 0
#       nums[1] = 1
#       nums[(1 * 2) = 2] = nums[1] = 1
#       nums[(1 * 2) + 1 = 3] = nums[1] + nums[2] = 1 + 1 = 2
#       nums[(2 * 2) = 4] = nums[2] = 1
#       nums[(2 * 2) + 1 = 5] = nums[2] + nums[3] = 1 + 2 = 3
#       nums[(3 * 2) = 6] = nums[3] = 2
#       nums[(3 * 2) + 1 = 7] = nums[3] + nums[4] = 2 + 1 = 3
#     Hence, nums = [0,1,1,2,1,3,2,3], and the maximum is 3.
#
#
# **Example 2:**
#
#
#
#     Input: n = 2
#     Output: 1
#     Explanation: According to the given rules, the maximum between nums[0], nums[1], and nums[2] is 1.
#
#
# **Example 3:**
#
#
#
#     Input: n = 3
#     Output: 2
#     Explanation: According to the given rules, the maximum between nums[0], nums[1], nums[2], and nums[3] is 2.
#
#
#
#
# **Constraints:**
#
#   * `0 <= n <= 100`
#
#
## @lc code=start
using LeetCode

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


function test_𝐼x(actual::Field1D, test::Field1D, input::Field1D)
    𝐼x!(test, input)
    return isapprox(test.data, actual.data; rtol = rtol_interpolation)
end

function test_𝐼x(actual::Field2D, test::Field2D, input::Field2D)
    𝐼x!(test, input)
    return isapprox(test.data, actual.data; rtol = rtol_interpolation)
end

function test_𝐼y(actual::Field2D, test::Field2D, input::Field2D)
    𝐼y!(test, input)
    return isapprox(test.data, actual.data; rtol = rtol_interpolation)
end

function test_∂x(actual::Field1D, test::Field1D, input::Field1D)
    ∂x!(test, input)
    return isapprox(test.data, actual.data; rtol = rtol_derivatives)
end

function test_∂x(actual::Field2D, test::Field2D, input::Field2D)
    ∂x!(test, input)
    return isapprox(test.data, actual.data, rtol = rtol_derivatives)
end

function test_∂y(actual::Field2D, test::Field2D, input::Field2D)
    ∂y!(test, input)
    return isapprox(test.data, actual.data, rtol = rtol_derivatives)
end


using BSplineKit

using BandedMatrices
using LinearAlgebra
using Random
using SparseArrays
using Test

import BSplineKit:
    AbstractDifferentialOp,
    DifferentialOpSum,
    LeftNormal,
    RightNormal

import BSplineKit.Recombinations:
    NoUniqueSolutionError,
    num_constraints,
    num_recombined

# Chebyshev (Gauss-Lobatto) points.
gauss_lobatto_points(N) = [-cos(π * n / N) for n = 0:N]

include("diffops.jl")
include("knots.jl")
include("bsplines.jl")
include("splines.jl")
include("natural.jl")
include("interpolation.jl")
include("approximation.jl")
include("recombination.jl")
include("collocation.jl")
include("galerkin.jl")
include("banded_tensors.jl")
include("airy.jl")


function get_affine_map(ds::ArchGDAL.IDataset)
    # ArchGDAL fails hard on datasets without
    # an affinemap. GDAL documents that on fail
    # a default affinemap should be returned.
    local gt
    try
        gt = ArchGDAL.getgeotransform(ds)
    catch y
        @warn y.msg
        gt = [0.,1.,0.,0.,0.,1.]
    end
    geotransform_to_affine(gt)
end

function geotransform_to_affine(gt::SVector{6,<:AbstractFloat})
    # See https://lists.osgeo.org/pipermail/gdal-dev/2011-July/029449.html
    # for an explanation of the geotransform format
    AffineMap(SMatrix{2,2}([gt[2] gt[3]; gt[5] gt[6]]), SVector{2}([gt[1], gt[4]]))
end
geotransform_to_affine(A::Vector{<:AbstractFloat}) = geotransform_to_affine(SVector{6}(A))

function affine_to_geotransform(am::AffineMap)
    l = am.linear
    t = am.translation
    (length(l) == 4 && length(t) == 2) || error("AffineMap has wrong dimensions.")
    [t[1], l[1], l[3], t[2], l[2], l[4]]
end

"""Check wether the AffineMap of a GeoArray contains rotations."""
function is_rotated(ga::GeoArray)
    ga.f.linear[2] != 0. || ga.f.linear[3] != 0.
end

function bbox(ga::GeoArray)
    ax, ay = ga.f(SVector(0, 0))
    bx, by = ga.f(SVector(size(ga)[1:2]))
    (min_x = min(ax, bx), min_y = min(ay, by), max_x = max(ax, bx), max_y = max(ay, by))
end

function unitrange_to_affine(x::StepRangeLen, y::StepRangeLen)
    δx, δy = float(step(x)), float(step(y))
    AffineMap(
        SMatrix{2,2}(δx, 0., 0., δy),
        SVector(x[1] - δx / 2, y[1] - δy / 2)
    )
end

function bbox_to_affine(size::Tuple{Integer,Integer}, bbox::NamedTuple{(:min_x, :min_y, :max_x, :max_y)})
    AffineMap(
        SMatrix{2,2}(float(bbox.max_x - bbox.min_x) / size[1], 0, 0, float(bbox.max_y - bbox.min_y) / size[2]),
        SVector(float(bbox.min_x), float(bbox.min_y))
    )
end

"""Set geotransform of `GeoArray` by specifying a bounding box.
Note that this only can result in a non-rotated or skewed `GeoArray`."""
function bbox!(ga::GeoArray, bbox::NamedTuple{(:min_x, :min_y, :max_x, :max_y)})
    ga.f = bbox_to_affine(size(ga)[1:2], bbox)
    ga
end

"""Generate bounding boxes for GeoArray cells."""
function bboxes(ga::GeoArray)
    c = coords(ga, Vertex())
    m, n = size(c)
    cellbounds = Matrix{NamedTuple}(undef, (m - 1, n - 1))
    for j in 1:n - 1, i in 1:m - 1
        v = c[i:i + 1, j:j + 1]
        minx, maxx = extrema(first.(v))
        miny, maxy = extrema(last.(v))
        cellbounds[i, j] = (min_x = minx, max_x = maxx, min_y = miny, max_y = maxy)
    end
    cellbounds
end

# Extend CoordinateTransformations
CoordinateTransformations.compose(ga::GeoArray, t2::AffineMap) = CoordinateTransformations.compose(ga.f, t2)
CoordinateTransformations.compose(ga::GeoArray, t2::LinearMap) = CoordinateTransformations.compose(ga.f, t2)
CoordinateTransformations.compose(ga::GeoArray, t2::Translation) = CoordinateTransformations.compose(ga.f, t2)

"""Transform an GeoArray by applying a Transformation."""
function compose!(ga::GeoArray, t2::AffineMap)
    ga.f = compose(ga, t2)
end
function compose!(ga::GeoArray, t2::LinearMap)
    ga.f = compose(ga, t2)
end
function compose!(ga::GeoArray, t2::Translation)
    ga.f = compose(ga, t2)
end

"""Function to flip GeoArray upside down to adjust to GDAL ecosystem."""
function flipud!(ga::GeoArray)
    # Flip data upside down
    ga.A = reverse(ga.A, dims=2)

    # Find new corner coordinates
    ux, uy = ga.f(SVector{2}([0, size(ga)[2]]))

    # Define y mirror and compose
    lm = LinearMap(SMatrix{2,2}([1.0 0.0; 0.0 -1.0]))
    am = compose(ga.f, lm)  # AffineMap
    translate = SVector{2}([ux, uy])
    f = AffineMap(am.linear, translate)

    ga.f = f
    ga
end

"""Check bbox overlapping

Return true if two bboxes overlap.
"""
function bbox_overlap(
    bbox_a::NamedTuple{(:min_x, :min_y, :max_x, :max_y)},
    bbox_b::NamedTuple{(:min_x, :min_y, :max_x, :max_y)})

    # Check if bboxes are valid
    if (bbox_a.min_x >= bbox_a.max_x) ||
        (bbox_a.min_y >= bbox_a.max_y)
        error("Invalid bbox (min >= max)", bbox_a)
    end
    if (bbox_b.min_x >= bbox_b.max_x) ||
        (bbox_b.min_y >= bbox_b.max_y)
        error("Invalid bbox (min >= max)", bbox_b)
    end

    # Check if bboxes overlap
    if (bbox_a.max_x < bbox_b.min_x) ||
        (bbox_a.max_y < bbox_b.min_y) ||
        (bbox_a.min_x > bbox_b.max_x) ||
        (bbox_a.min_y > bbox_b.max_y)
        return false
    else
        return true
    end
end

"""
    function crop(ga::GeoArray, cbox::NamedTuple{(:min_x, :min_y, :max_x, :max_y),Tuple{Float64,Float64,Float64,Float64}})

Crop input GeoArray by coordinates (box or another GeoArray). If the coordinates range is larger than the GeoArray, only the overlapping part is given in the result.
"""
function crop(ga::GeoArray, cbox::NamedTuple{(:min_x, :min_y, :max_x, :max_y)})
    # Check if ga and cbox overlap
    if !bbox_overlap(bbox(ga), cbox)
        error("GeoArray and crop box do not overlap")
    end
    # Check extent and get bbox indices
    ga_x, ga_y, = size(ga)
    i_min_x, i_min_y = indices(ga, [cbox.min_x, cbox.max_y])
    i_max_x, i_max_y = indices(ga, [cbox.max_x, cbox.min_y])

    # Determine indices for crop area
    i_min_x = max(i_min_x, 1)
    i_max_x = min(i_max_x, ga_x)
    i_min_y = max(i_min_y, 1)
    i_max_y = min(i_max_y, ga_y)

    # Subset and return GeoArray
    return ga[i_min_x:i_max_x,i_min_y:i_max_y,:]
end

function crop(ga::GeoArray, cga::GeoArray)
    # Check if ga and crop ga CRS are the same
    if ga.crs != cga.crs
        error("GeoArrays have different CRS")
    end

    # Crop
    return crop(ga, bbox(cga))
end


module GreyDecision

include("./greynumber.jl")
include("./utility.jl")
include("./stats.jl")
include("./random.jl")
include("./topsis.jl")
include("./vikor.jl")
include("./saw.jl")
include("./nds.jl")
include("./moora.jl")
include("./electre.jl")
include("./wpm.jl")
include("./marcos.jl")
include("./aras.jl")

import ..GreyNumbers: GreyNumber, kernel
import ..Topsis: topsis
import ..Vikor: vikor
import ..Saw: saw
import ..NDS: nds
import ..Moora: moora
import ..Electre: electre
import ..Wpm: wpm
import ..Marcos: marcos
import ..Aras: aras

export GreyNumbers
export Utility
export Random
export Stats

export GreyNumber
export topsis, vikor, saw, nds, moora, electre, wpm, marcos, aras

end # module GreyDecision


"""
 recognize

 """

"""
Functions for recognizing properties of the a LPModel

"""

export recognize

function recognize(::LPModel, ::AbstractRecognizeAttribute) <: Bool end

function recognize(lpmodel::LPModel, ::DifferenceConstraints)
    A, b = get_constraint_matrices(lpmodel)
    variable_count = size(A)[2]
    if (variable_count <= 1 || size(A)[1]==0)
        return false
    end
    compare = zeros(variable_count)
    compare[1] = 1
    compare[2] = -1
    for row in eachrow(A)
        if !issetequal(row, compare)
            return false
        end
    end
    return true
end

function recognize(lpmodel::LPModel, ::AllIntegerVariables)
    integers = get_integer_variables(lpmodel)
    # Filter out the NaNs:
    num_imntegers = length(filter(integers->!isnan(integers),integers))
    return num_imntegers == lpmodel.num_variables_created
end

function recognize(lpmodel::LPModel, ::AllIntegerConstraintBounds)
    A, b = get_constraint_matrices(lpmodel)
    # If after truncation the element is equal to itself, it is an integer:
    for element in b
        if !(trunc(Int, element) == element)
            return false
        end
    end
    return true
end

"""

function recognize(lpmodel::LPModel, ::ConstantObjective)
    c = get_objective_vector(lpmodel)
    return (c == zeros(size(c)[1]))
end"""


using Images
using FileIO

# load our mandel functions
include("mandel.jl")

# set parameters
h = 2500
w = 5000
iter = 10000

# time measurements
print("starting...\n")
tStart=time()

m_set = mandelbrot_set(-2.5,1.5,-1.25,1.25,w,h,iter)

tStop = time()

# write the image-file
img = colorview(RGB, m_set)
save("mandel.jpg", img)

print("done. took ", tStop-tStart, " seconds\n");


using Distributions
using Flux: _isactive
using Zygote: @adjoint
import Flux
import Base

export DropBlock, dropblock

"""
https://arxiv.org/pdf/1810.12890.pdf
"""
mutable struct DropBlock
    block_size::Integer
    keep_prop::AbstractFloat
    active::Union{Bool,Nothing}
    function DropBlock(block_size, keep_prop, active = nothing)
        @assert 0 ≤ keep_prop ≤ 1
        new(block_size, keep_prop, active)
    end
end

function _get_block_mask(block::DropBlock, x, mask = nothing)
    feat_size = size(x, 1) * size(x, 2)
    height, width, channel, batch = size(x)
    height_offset = width_offset = floor(Integer, block.block_size / 2)
    gamma =
        (1 - block.keep_prop) / block.block_size^2 * feat_size^2 /
        (feat_size - block.block_size + 1)^2
    if isnothing(mask)
        mask = zeros(size(x))
        mask[
            height_offset+1:height-height_offset,
            width_offset+1:width-width_offset,
            :,
            :,
        ] .= rand(
            Bernoulli(gamma),
            height - height_offset * 2,
            width - width_offset * 2,
            channel,
            batch,
        )
    end
    mask_pos = findall(x -> x > 0, mask)
    block_mask = ones(eltype(x), size(x))
    for pos in mask_pos
        c_height, c_width, c_channel, c_batch = pos.I
        height_start = c_height - height_offset
        height_end = c_height + height_offset
        width_start = c_width - width_offset
        width_end = c_width + width_offset
        block_mask[height_start:height_end, width_start:width_end, c_channel, c_batch] .= 0
    end
    return block_mask
end

function dropblock(x, block::DropBlock)
    block_mask = _get_block_mask(block, x)
    y = x .* block_mask .* (length(block_mask) / sum(block_mask))
    return y, block_mask
end

@adjoint function dropblock(x, block::DropBlock)
    y, block_mask = dropblock(x, block)
    function back(Δ)
        Δy, _ = Δ
        return (Δy .* block_mask, nothing)
    end
    return (y, block_mask), back
end

function (block::DropBlock)(x::AbstractArray{T,4}) where {T<:Real}
    _isactive(block) || return x
    return dropblock(x, block)[1]
end

Flux.testmode!(block::DropBlock, mode = true) =
    (block.active = (isnothing(mode) || mode == :auto) ? nothing : !mode; block)

function Base.show(io::IO, block::DropBlock)
    print(io, "DropBlock($(block.block_size), $(block.keep_prop), $(block.active))")
end


export get_Jacobian

"""
$(SIGNATURES)

Obtain the symbolic Jacobian matrix of `eom` (either a `HarmonicEquation` or a `DifferentialEquation`).
This is the linearised left-hand side of F(u) = du/dT.

"""
function get_Jacobian(eom::HarmonicEquation)

    rearr = !HarmonicBalance.is_rearranged(eom) ? HarmonicBalance.rearrange_standard(eom) : eom
    lhs = _remove_brackets(rearr)
    vars = _remove_brackets.(eom.variables)

    get_Jacobian(lhs, vars)

end

" Obtain a Jacobian from a `DifferentialEquation` by first converting it into a `HarmonicEquation`. "
function get_Jacobian(diff_eom::DifferentialEquation)
    @variables T
    harmonic_eq = get_harmonic_equations(diff_eom, slow_time=T, fast_time=first(get_independent_variables(diff_eom)))
    get_Jacobian(harmonic_eq)
end

" Get the Jacobian of a set of equations `eqs` with respect to the variables `vars`. "
function get_Jacobian(eqs::Vector{Num}, vars::Vector{Num})
    length(eqs) == length(vars) || error("Jacobians are only defined for square systems!")
    M = Matrix{Num}(undef, length(vars), length(vars))

    for idx in CartesianIndices(M)
        M[idx] = expand_derivatives(d(eqs[idx[1]], vars[idx[2]]))
    end
    M
end

get_Jacobian(eqs::Vector{Equation}, vars::Vector{Num}) = get_Jacobian(Num.(getfield.(eqs, :lhs) .- getfield.(eqs, :rhs)), vars)

# the Jacobian of some equations again
function get_Jacobian_steady(eom::HarmonicEquation; differential_order=0)
    Hopf_vars = first.(getfield.(filter(x -> x.type == "Hopf", eom.variables), :symbol))
    Hopf_idx = findall(x -> any(isequal.(x, Hopf_vars)) , get_variables(eom))
    nonsingular = filter( x -> x ∉ Hopf_idx, 1:length(get_variables(eom)))

    vars_simp = Dict([var => HarmonicBalance.declare_variable(var_name(var)) for var in get_variables(eom)])
    T = get_independent_variables(eom)[1]
    J = get_Jacobian(eom.equations, d(get_variables(eom), T, differential_order))[nonsingular, nonsingular]

    expand_derivatives.(HarmonicBalance.substitute_all(J, vars_simp))
end

# COMPILE THIS!
function get_implicit_Jacobian(eom::HarmonicEquation)::Function
    M = get_Jacobian_steady(eom, differential_order=0)
    Mp = get_Jacobian_steady(eom, differential_order=1)

    function J(soln::OrderedDict)
        -inv(ComplexF64.(substitute_all(Mp, soln))) * ComplexF64.(substitute_all(M, soln))
    end
    J
end

###
# STUFF BELOW IS FOR THE CORRECTED JACOBIAN METHOD
###

"""
    get_response_matrix(diff_eq::DifferentialEquation, freq::Num; order=2)

Obtain the symbolic linear response matrix of a `diff_eq` corresponding to a perturbation frequency `freq`.
This routine cannot accept a `HarmonicEquation` since there, some time-derivatives are already dropped.
`order` denotes the highest differential order to be considered.

"""
function get_response_matrix(diff_eq::DifferentialEquation, freq::Num; order=2)::Matrix
    @variables T, i
    time = get_independent_variables(diff_eq)[1]

    eom = HarmonicBalance.harmonic_ansatz(diff_eq, time)

    # replace the time-dependence of harmonic variables by slow time BUT do not drop any derivatives
    eom = HarmonicBalance.slow_flow(eom, fast_time=time, slow_time=T, degree=order+1)

    eom = HarmonicBalance.fourier_transform(eom, time)

    M = get_Jacobian(eom.equations, get_variables(eom))
    for n in 1:order
        M += (i * freq)^n * get_Jacobian(eom.equations, d(get_variables(eom), T, n))
    end
    M = substitute_all(M, [var => HarmonicBalance.declare_variable(var_name(var)) for var in get_variables(eom)])
    substitute_all(expand_derivatives.(M), i => im)
end


function probabilityM1(s₁::BigInt, t₁::BigFloat, n::BigInt)
    pM1 = factorial(s₁) ./ (exp(t₁) * (n + 1) ^(s₁ + 1))
    pM2 = (factorial(n) * factorial(s₁)) ./ factorial(n + s₁ + 1)
    p = pM1 ./(pM1 + pM2)
    p = Float64(p)
    return p
end

s1calc(x) = sum(x)
t1calc(x::Array{BigInt, 1}) = Float64(sum(log.(map(a -> factorial(a), x))))
t1calc(x::Array{Int64, 1}) = sum(log.(map(a -> factorial(a), x)))

function poissonsimulator(params, cst, targetdata)

    λ = params[1]
    simdata = rand(Poisson(λ), 100)
    s₁ = s1calc(simdata)
    if sum(simdata.>19) > 0
        t₁ = t1calc(map(x -> BigInt(x), simdata))
    else
        t₁ = t1calc(simdata)
    end
    euclidean(targetdata, [s₁, t₁]), 1, rand([true, false])
    #euclidean(targetdata[2], [s₁, t₁][2]), 1
end

function geometricsimulator(params, cst, targetdata)

    μ = params[1]
    simdata = rand(Geometric(μ), 100)
    s₁ = s1calc(simdata)
    if sum(simdata.>19) > 0
        t₁ = t1calc(map(x -> BigInt(x), simdata))
    else
        t₁ = t1calc(simdata)
    end
    euclidean(targetdata, [s₁, t₁]), 1, rand([true, false])
    #euclidean(targetdata[2], [s₁, t₁][2]), 1
end

function generatedata()
    λ = 0.5
    n = 100
    data = rand(Poisson(λ), n)
    s₁ = s1calc(data)
    t₁ = t1calc(data)
    pM1 = probabilityM1(BigInt(s₁), BigFloat(t₁), BigInt(n))

  return [s₁, t₁], pM1
end

################################################################
println("Test Bayes factors are calculated correctly (within 5% of true value)")
srand(1234)

cst = [[i] for i in 1:2]

ABCsetupsmc = ABCSMCModel([poissonsimulator, geometricsimulator], [1, 1], 1.0,
[Prior([Exponential(1)]), Prior([Uniform(0.0, 1.0)])];
nparticles = 500, maxiterations = 10^7)

ABCsetuprej = ABCRejectionModel([poissonsimulator, geometricsimulator], [1, 1], 1.0,
[Prior([Exponential(1)]), Prior([Uniform(0.0, 1.0)])];
nparticles = 500, maxiterations = 10^7)

td, pM1 = generatedata()
println("\t Checking ABC SMC model selection with algorithm that returns true or false for each simulations")
@time abcressmc = ApproxBayes.runabcCancer(ABCsetupsmc, td);
@test isapprox(pM1, abcressmc.modelprob[1], rtol = 0.05)

function getnormal2(params, constants, targetdata)

  simdata = rand(Normal(params...), 1000)
  ApproxBayes.ksdist(simdata, targetdata), 1, rand([true, false])
end

println("Test parameters of normal distribution are inferred correctly (mean within 5% of true value)")

srand(1)
p1 = 2.0
p2 = 0.4
targetdata = rand(Normal(p1, p2), 1000)

#setup ABC alogrithm specifications for Rejection algorithm
setup = ABCRejection(getnormal2,
  2,
  0.1,
  Prior([Uniform(0.0, 20.0), Uniform(0.0, 2.0)]);
  maxiterations = 10^6,
  )

#now run SMC algorithm
setup = ABCSMC(getnormal2,
  2,
  0.05,
  Prior([Uniform(0.0, 20.0), Uniform(0.0, 2.0)]),
  )
ressmc = ApproxBayes.runabcCancer(setup, targetdata, verbose = false);

println("\t Check ABC SMC with true/false returned from simulations correctly infers parameters")
# test that mean value of posterior is within 10% of true value
@test isapprox(mean(ressmc.parameters, weights(ressmc.weights), 1)[1], p1, rtol = 0.05)
@test isapprox(mean(ressmc.parameters, weights(ressmc.weights), 1)[2], p2, rtol = 0.05)


# module Dynamics
abstract type AbstractIonChannel end #TODO: generalize simpel and complex channel into generic channels.
abstract type AbstractKinetics end #TODO: extend to more kinetic parameters

@doc raw"""
## Kinetics of an ion channel variable

$m^n$

activation and inactivation function would be encoded
as Boltzmann function:

$$m_\infty(V) = \frac{1 - \zeta}{1 + \exp(\frac{V_{\text{half}} - V}{k})} + \zeta$$

the time constant would be encoded as guassian function:

$$\tau(V) = C_{\text{base}} + C_{\text{amp}} \exp(\frac{- (V_{\text{max}} - V) ^ 2}{\sigma ^ 2})$$

---

### Kinetics(n, Vhalf, k, Cbase, Camp, Vmax, sigma; zeta, state)
- regular variable as conductance model describes.

### Kinetics(n, Vhalf, k; zeta, state, _tau, _type)
- for variable with constant tau
- or instantaneous variable, which $m = m_\infty$ (with `_type = :instantaneous`)

### Kinetics(n, infty, tau)
- custom infty and tau functions

### Kinetics()
- returns an empty kinetic object
"""
mutable struct Kinetics <: AbstractKinetics
    _type::Symbol # :evolving, :intantaneous, :empty
    _state::Symbol # :activation, :inactivation, :custom
    n::Integer

    Vhalf::Union{Nothing, Real}
    k::Union{Nothing, Real}
    zeta::Union{Nothing, Real}

    Cbase::Union{Nothing, Real}
    Camp::Union{Nothing, Real}
    Vmax::Union{Nothing, Real}
    sigma::Union{Nothing, Real}

    infty::Function
    tau::Function

    "set kinetics"
    Kinetics(n::Integer, Vhalf::Real, k::Real, Cbase::Real, Camp::Real, Vmax::Real, sigma::Real; zeta::Real=0, state = :activation) = begin
        _sign = state == :activation ? 1 : -1
        _infty = (V) -> (1 - zeta) / (1 + exp(_sign * (Vhalf - V) / k)) + zeta
        _tau = (V) -> Cbase + Camp * exp(- ((Vmax - V) / sigma)^2)
        new(:evolving, state, n, Vhalf, k, zeta, Cbase, Camp, Vmax, sigma, _infty, _tau)
    end

    "kinetics with constant tau or instantaneous kinetic"
    Kinetics(n::Integer, Vhalf::Real, k::Real; zeta::Real=0, _tau = (x)->1, _type=:evolving, state = :activation) = begin
        _sign = state == :activation ? 1 : -1
        _infty = (V) -> (1 - zeta) / (1 + exp(_sign * (Vhalf - V) / k)) + zeta
        new(_type, state, n, Vhalf, k, zeta, nothing, nothing, nothing, nothing, _infty, _tau)
    end

    "custom infty and tau functions"
    Kinetics(n::Integer, infty::Function, tau::Function) = begin
        new(:evolving, :custom,  n, nothing, nothing, nothing, nothing, nothing, nothing, nothing, infty, tau)
    end

    "empty kinetics"
    Kinetics() = begin
        new(:empty, :custom, 0, nothing, nothing, nothing, nothing, nothing, nothing, nothing, (x)->1, (x)->1)
    end
end

function activation(knt::Kinetics, V::Real)
    if isnothing(knt.zeta)
        return knt.infty(V)
    else
        return (1 - knt.zeta) / (1 + exp((knt.Vhalf - V) / knt.k)) + knt.zeta
    end
end

function inactivation(knt::Kinetics, V::Real)
    if isnothing(knt.zeta)
        return knt.infty(V)
    else
        return (1 - knt.zeta) / (1 + exp(- (knt.Vhalf - V) / knt.k)) + knt.zeta
    end
end

function time_constant(knt::Kinetics, V::Real)
    if isnothing(knt.Cbase)
        return knt.tau(V)
    else
        return knt.Cbase + knt.Camp * exp(- ((knt.Vmax - V) / knt.sigma)^2)
    end
end

include("./Channel/SimpleChannel.jl")
include("./Channel/ComplexChannel.jl")
include("./Channel/GenericChannel.jl")

@doc raw"""
dof(channels::Vector{AbstractIonChannel})

get degree of freedom from model.
only variables of ion channels are counted.
`V` and `I` not included.
"""
function dof(channels::Vector{T}) where {T <: AbstractIonChannel}
    _ans = 0
    for item in channels
        _ans += dof(item) |> sum
    end
    _ans
end

@doc raw"""
simple conductance model, current clamp:

$$C\frac{dV}{dt} = I_{\text{stimulus}} - (\sum_i I_i) $$

using SimpleIonChannels to model a cell;
return an anonymous function that can be used by DifferentialEquations.jl.
"""
function simpleConductanceModel(channels::Vector{T}, stim::Function; C::Real=1) where {T <: AbstractIonChannel}
    nchannel = length(channels)
    nvar = dof(channels)

    return (du, u, p, t) -> begin
        not = (x) -> !x
        v = u[1]
        param = u[2:end-1]
        var_idx = 1

        _current = zeros(nchannel)
        for (idx, item) in enumerate(channels)
            _var_step = sum(dof(item))
            (_icurrent, _iderivitate) = step(item, V=v, var=param[var_idx:var_idx-1+_var_step], E=p.E[item.ion])
            _current[idx] = _icurrent
            du[1+var_idx:var_idx+_var_step] = _iderivitate[not.(isnothing.(_iderivitate))]
            var_idx += _var_step
        end

        I = stim(t, p.stim)
        du[1] = (I - sum(_current)) / C
        u[end] = I

        du, u, p, t
    end
end

function setup_init(channels::Vector{T}, v0::Real) where {T<:AbstractIonChannel}
    _init_val = zeros(dof(channels)+2)
    _init_val[1] = v0
    var_idx = 2

    for (idx, item) in enumerate(channels)
        for each in itr_kinetics(item)
            if each._type == :evolving
                _init_val[var_idx] = each.infty(v0)
                var_idx += 1
            end
        end
    end
    _init_val
end

function current_decompose(solution::ODESolution, model::Vector{T}, tspan::StepRangeLen, param::NamedTuple) where {T<:AbstractIonChannel}
    var = hcat(solution(tspan).u...)
    var_idx = 2
    result = Dict{String, Vector{Float64}}()
    for ch in model
        _var_step = sum(dof(ch))
        result[ch.name] = current(ch, V=var[1, :], var=var[var_idx:var_idx-1+_var_step, :], E=param.E[ch.ion])
        var_idx += _var_step
    end
    result["voltage"] = var[1, :]
    result
end

# voltage clamp
@doc raw"""
simple conductance model, simple voltage clamp:

$$I_{\text{stimulus}} = (\sum_i I_i) $$

return an anonymous function that can be used by DifferentialEquations.jl.
"""
function simpleVoltageClamp(channels::Vector{T}, stim::Function; C::Real=1) where {T <: AbstractIonChannel}
    nchannel = length(channels)
    nvar = dof(channels)

    return (du, u, p, t) -> begin
        not = (x) -> !x
        v = stim(t, p.stim)
        param = u[2:end-1]
        var_idx = 1

        _current = zeros(nchannel)
        for (idx, item) in enumerate(channels)
            _var_step = sum(dof(item))
            (_icurrent, _iderivitate) = step(item, V=v, var=param[var_idx:var_idx-1+_var_step], E=p.E[item.ion])
            _current[idx] = _icurrent
            du[1+var_idx:var_idx+_var_step] = _iderivitate[not.(isnothing.(_iderivitate))]
            var_idx += _var_step
        end

        u[end] = sum(_current)
        u[1] = v

        du, u, p, t
    end
end

#function run()

# end #module


struct SilvisModel{T}<:AbstractLESModel
    c::T
    cp::T
    Δ²::T
    tau::SymTrTenField{T,3,2,false}
    reduction::Vector{T}
end

function SilvisModel(c::T,cp::T,Δ::Real,dim::NTuple{3,Integer}) where {T<:Real}
    data = SymTrTenField{T}(dim,(LX,LY,LZ))
    #fill!(data,0)
    reduction = zeros(THR ? Threads.nthreads() : 1)
    return SilvisModel{T}(c,cp,Δ^2, data,reduction)
end

is_Silvis(a::Union{<:SilvisModel,Type{<:SilvisModel}}) = true
@inline @par is_Silvis(s::Type{T}) where {T<:@par(AbstractSimulation)} = is_Silvis(LESModelType)
@inline is_Silvis(s::T) where {T<:AbstractSimulation} = is_Silvis(T)
is_Silvis(a) = false

msg(a::SilvisModel) = "\nLES model: Silvis\nS Constant: $(a.c)\nP Constant: $(a.cp)\nFilter Width: $(sqrt(a.Δ²))\n"

fvs(S,w) = norm(S⋅w)/(fsqrt(S:S)*norm(w))

@inline function Silvis_eddy_viscosity(S::SymTen,f::Real,c::Real,Δ2::Real)
    return c*c*Δ2*f*f*f*fsqrt(S:S)/2
end

@inline function Silvis_P_coeff(cp::Real,f::Real,Δ2::Real)
    return cp*Δ2*f*f*f*f/4
end


"""
   Define the partial derivative operator for v_z with PML boundary
"""
function Mvz(gammaz_stagger::Vector{Tv}, buoy::Matrix{Tv},
         dz::Tv, dt::Tv, fd::Vector{Tv}) where {Tv<:AbstractFloat}

    # size of the model
    (nz, nx) = size(buoy)
    if nz != length(gammaz_stagger)
       error("size mismatch")
    end

    # precision order
    order = length(fd)

    # data format
    data_format = eltype(buoy)
    double      = data_format(2.0)

    # the coefficients in front of v_z
    MvzBvz = ones(data_format, nz)
    for i = 1 : nz
        MvzBvz[i] = (double - dt * gammaz_stagger[i]) / (double + dt * gammaz_stagger[i])
    end

    # the coefficients in front of p
    MvzBp  = ones(data_format, nz*nx)
    idx = 0
    for j = 1 : nx
        for i = 1 : nz
            idx = idx + 1

            # averge of density
            if i < nz
               bavg = (buoy[i,j] + buoy[i+1,j]) / double
            else
               bavg = buoy[i,j]
            end

            MvzBp[idx] = (double * bavg * dt) / (double+dt*gammaz_stagger[i])
        end
    end

    # spatial partial derivative
    dpdz = zeros(data_format, nz, nz)
    for ir = 1 : nz
        lower = ir - order + 1 >= 1  ? ir-order+1 : 1
        upper = ir + order     <= nz ? ir+order   : nz

        for ic = lower : upper
            if ic-ir <= 0
               idx = ir-ic + 1
               dpdz[ir,ic] = - fd[idx] / dz
            elseif ic-ir >= 1
               idx = ic-ir
               dpdz[ir,ic] =   fd[idx] / dz
            end
        end
    end

    return MvzBvz, MvzBp, sparse(dpdz)
end

"""
   Define the partial derivative operator for v_z with rigid boundary
"""
function Rvz(buoy::Matrix{Tv}, dz::Tv, dt::Tv, fd::Vector{Tv}) where {Tv<:AbstractFloat}

    # size of the model
    (nz, nx) = size(buoy)

    # precision order
    order = length(fd)

    # data format
    data_format = eltype(buoy)
    double      = data_format(2.0)

    # the coefficients in front of p
    RvzBp  = ones(data_format, nz*nx)
    idx = 0
    for j = 1 : nx
        for i = 1 : nz
            idx = idx + 1

            # averge of density
            if i < nz
               bavg = (buoy[i,j] + buoy[i+1,j]) / double
            else
               bavg = buoy[i,j]
            end

            RvzBp[idx] = bavg * dt
        end
    end

    # spatial partial derivative
    rpdz = zeros(data_format, nz, nz)
    for ir = 1 : nz
        lower = ir - order + 1 >= 1  ? ir-order+1 : 1
        upper = ir + order     <= nz ? ir+order   : nz

        for ic = lower : upper
            if ic-ir <= 0
               idx = ir-ic + 1
               rpdz[ir,ic] = - fd[idx] / dz
            elseif ic-ir >= 1
               idx = ic-ir
               rpdz[ir,ic] =   fd[idx] / dz
            end
        end
    end

    return RvzBp, sparse(rpdz)
end

"""
   Define the partial derivative operator for v_x with PML boundary
"""
function Mvx(gammax_stagger::Vector{Tv}, buoy::Matrix{Tv},
         dx::Tv, dt::Tv, fd::Vector{Tv}) where {Tv<:AbstractFloat}

    # size of the model
    (nz, nx) = size(buoy)
    if nx != length(gammax_stagger)
       error("size mismatch")
    end

    # precision order
    order = length(fd)

    # data format
    data_format = eltype(buoy)
    double      = data_format(2.0)

    # the coefficients in front of v_x
    MvxBvx = ones(data_format, nx)
    for j = 1 : nx
        MvxBvx[j] = (double - dt * gammax_stagger[j]) / (double + dt * gammax_stagger[j])
    end

    # the coefficients in front of p
    MvxBp  = ones(data_format, nz*nx)
    idx = 0
    for j = 1 : nx
        for i = 1 : nz
            idx = idx + 1

            # averge of density
            if j < nx
               bavg = (buoy[i,j] + buoy[i,j+1]) / double
            else
               bavg = buoy[i,j]
            end

            MvxBp[idx] = (double * bavg * dt) / (double + dt * gammax_stagger[j])
        end
    end

    # spatial partial derivative dpdx
    dpdx = zeros(data_format, nx, nx)
    for ir = 1 : nx
        lower = ir - order + 1 >= 1  ? ir-order+1 : 1
        upper = ir + order     <= nx ? ir+order   : nx

        for ic = lower : upper
            if ic-ir <= 0
               idx = ir - ic + 1
               dpdx[ir,ic] = - fd[idx] / dx
            elseif ic-ir >= 1
               idx = ic - ir
               dpdx[ir,ic] =   fd[idx] / dx
            end
        end
    end

    return MvxBvx, MvxBp, sparse(dpdx)
end

"""
   Define the partial derivative operator for v_x with rigid boundary
"""
function Rvx(buoy::Matrix{Tv}, dx::Tv, dt::Tv, fd::Vector{Tv}) where {Tv<:AbstractFloat}

    # size of the model
    (nz, nx) = size(buoy)

    # precision order
    order = length(fd)

    # data format
    data_format = eltype(buoy)
    double      = data_format(2.0)

    # the coefficients in front of p
    RvxBp  = ones(data_format, nz*nx)
    idx = 0
    for j = 1 : nx
        for i = 1 : nz
            idx = idx + 1

            # averge of density
            if j < nx
               bavg = (buoy[i,j] + buoy[i,j+1]) / double
            else
               bavg = buoy[i,j]
            end

            RvxBp[idx] = bavg * dt
        end
    end

    # spatial partial derivative dpdx
    rpdx = zeros(data_format, nx, nx)
    for ir = 1 : nx
        lower = ir - order + 1 >= 1  ? ir-order+1 : 1
        upper = ir + order     <= nx ? ir+order   : nx

        for ic = lower : upper
            if ic-ir <= 0
               idx = ir - ic + 1
               rpdx[ir,ic] = - fd[idx] / dx
            elseif ic-ir >= 1
               idx = ic - ir
               rpdx[ir,ic] =   fd[idx] / dx
            end
        end
    end

    return RvxBp, sparse(rpdx)
end

"""
   Define the partial derivative operator for p_z with PML boundary
"""
function Mpz(gammaz_integer::Vector{Tv}, bulk::Matrix{Tv},
         dz::Tv, dt::Tv, fd::Vector) where {Tv<:AbstractFloat}

    # size of the model
    (nz, nx) = size(bulk)
    if nz != length(gammaz_integer)
       error("size mismatch")
    end

    # precision order
    order = length(fd)

    # data format
    data_format = eltype(bulk)
    double      = data_format(2.0)

    # the coefficients in front of p_z
    MpzBpz = ones(data_format, nz)
    for i = 1 : nz
        MpzBpz[i] = (double - dt * gammaz_integer[i]) / (double + dt * gammaz_integer[i])
    end

    # the coefficients in front of v_z
    MpzBvz = ones(data_format, nz*nx)
    idx = 0
    for j = 1 : nx
        for i = 1 : nz
            idx = idx + 1
            MpzBvz[idx] = (double * bulk[i,j] * dt) / (double + dt * gammaz_integer[i])
        end
    end

    # spatial partial derivative dvdz
    dvdz = zeros(data_format, nz, nz)
    for ir = 1 : nz
        lower = ir - order     >= 1  ? ir-order   : 1
        upper = ir + order - 1 <= nz ? ir+order-1 : nz

        for ic = lower : upper
            if ic-ir <= -1
               idx = ir-ic
               dvdz[ir,ic] = - fd[idx] / dz
            elseif ic-ir >= 0
               idx = ic-ir+1
               dvdz[ir,ic] =   fd[idx] / dz
            end
        end
    end

    return MpzBpz, MpzBvz, sparse(dvdz)
end

"""
   Define the partial derivative operator for p_x with PML boundary
"""
function Mpx(gammax_integer::Vector{Tv}, bulk::Matrix{Tv},
         dx::Tv, dt::Tv, fd::Vector{Tv}) where {Tv<:AbstractFloat}

    # size of the model
    (nz, nx) = size(bulk)
    if nx != length(gammax_integer)
       error("size mismatch")
    end

    # precision order
    order = length(fd)

    # data format
    data_format = eltype(bulk)
    double      = data_format(2.0)

    # the coefficients in front of p_x
    MpxBpx = ones(data_format, nx)
    for j = 1 : nx
        MpxBpx[j] = (double - dt * gammax_integer[j]) / (double + dt * gammax_integer[j])
    end

    # the coefficients in front of v_x
    MpxBvx = ones(data_format, nz*nx)
    idx = 0
    for j = 1 : nx
        for i = 1 : nz
            idx = idx + 1
            MpxBvx[idx] = (double * bulk[i,j] * dt) / (double + dt * gammax_integer[j])
        end
    end

    # spatial partial derivative dvdx
    dvdx = zeros(data_format, nx, nx)
    for ir = 1 : nx
        lower = ir - order     >= 1  ? ir-order   : 1
        upper = ir + order - 1 <= nx ? ir+order-1 : nx

        for ic = lower : upper
            if ic-ir <= -1
               idx = ir - ic
               dvdx[ir,ic] = - fd[idx] / dx
            elseif ic-ir >= 0
               idx = ic - ir + 1
               dvdx[ir,ic] =   fd[idx] / dx
            end
        end
    end

    return MpxBpx, MpxBvx, sparse(dvdx)
end

"""
   Define the partial derivative operator for p_z with rigid boundary
"""
function Rpzx(bulk::Matrix{Tv}, dz::Tv, dx::Tv, dt::Tv, fd::Vector) where {Tv<:AbstractFloat}

    # size of the model
    (nz, nx) = size(bulk)

    # precision order
    order = length(fd)

    # data format
    data_format = eltype(bulk)
    double      = data_format(2.0)

    # the coefficients in front of v_z
    RpBv = ones(data_format, nz*nx)
    idx = 0
    for j = 1 : nx
        for i = 1 : nz
            idx = idx + 1
            RpBv[idx] = bulk[i,j] * dt
        end
    end

    # spatial partial derivative dvdz
    rvdz = zeros(data_format, nz, nz)
    for ir = 1 : nz
        lower = ir - order     >= 1  ? ir-order   : 1
        upper = ir + order - 1 <= nz ? ir+order-1 : nz

        for ic = lower : upper
            if ic-ir <= -1
               idx = ir-ic
               rvdz[ir,ic] = - fd[idx] / dz
            elseif ic-ir >= 0
               idx = ic-ir+1
               rvdz[ir,ic] =   fd[idx] / dz
            end
        end
    end

    # spatial partial derivative dvdx
    rvdx = zeros(data_format, nx, nx)
    for ir = 1 : nx
        lower = ir - order     >= 1  ? ir-order   : 1
        upper = ir + order - 1 <= nx ? ir+order-1 : nx

        for ic = lower : upper
            if ic-ir <= -1
               idx = ir - ic
               rvdx[ir,ic] = - fd[idx] / dx
            elseif ic-ir >= 0
               idx = ic - ir + 1
               rvdx[ir,ic] =   fd[idx] / dx
            end
        end
    end

    return RpBv, sparse(rvdz), sparse(rvdx)
end

# """
#    sparse matrix for finite-difference grid with rigid boundary
# """
# struct RigidFDStencil{Ti<:Int64, Tv<:AbstractFloat}
#     MvzBp  :: SparseMatrixCSC{Tv,Ti}
#     MvxBp  :: SparseMatrixCSC{Tv,Ti}
#     MpzBvz :: SparseMatrixCSC{Tv,Ti}
#     MpxBvx :: SparseMatrixCSC{Tv,Ti}
# end
#
# """
#    constructor for finite difference stencil with rigid boundary condition
# """
# function RigidFDStencil(params::TdParams)
#
#     # expand physical model parameters
#     lambda = vp2lambda(params.vel, params.rho)
#
#     MvzBp  = Mvz(params.rho, params.dz, params.dt, params.fd_coefficients)
#     MvxBp  = Mvx(params.rho, params.dx, params.dt, params.fd_coefficients)
#     MpzBvz = Mpz(lambda    , params.dz, params.dt, params.fd_coefficients)
#     MpxBvx = Mpx(lambda    , params.dx, params.dt, params.fd_coefficients)
#
#     return RigidFDStencil(MvzBp, MvxBp, MpzBvz, MpxBvx)
# end

# """
#    sparse matrix for finite-difference grid with PML boundary
# """
# struct ObsorbFDStencil{Ti<:Int64, Tv<:AbstractFloat}
#     MvzBvz :: Vector{Tv}
#     MvzBp  :: SparseMatrixCSC{Tv,Ti}
#     MvxBvx :: Vector{Tv}
#     MvxBp  :: SparseMatrixCSC{Tv,Ti}
#     MpzBpz :: Vector{Tv}
#     MpzBvz :: SparseMatrixCSC{Tv,Ti}
#     MpxBpx :: Vector{Tv}
#     MpxBvx :: SparseMatrixCSC{Tv,Ti}
# end
#
# """
#    sparse matrix for finite-difference grid with rigid boundary
# """
# struct RigidFDStencil{Ti<:Int64, Tv<:AbstractFloat}
#     MvzBp  :: SparseMatrixCSC{Tv,Ti}
#     MvxBp  :: SparseMatrixCSC{Tv,Ti}
#     MpzBvz :: SparseMatrixCSC{Tv,Ti}
#     MpxBvx :: SparseMatrixCSC{Tv,Ti}
# end
#
# """
#    compute lambda from velocity and density
# """
# function vp2lambda(vp::Matrix{Tv}, rho::Matrix{Tv}) where {Tv<:AbstractFloat}
#
#     (m , n ) = size(vp)
#     (m1, n1) = size(rho)
#     if m1 != m || n1 != n
#        error("size mismatch")
#     end
#
#     lambda = zeros(eltype(vp), m, n)
#     for j = 1 : n
#         for i = 1 : m
#             lambda[i,j] = rho[i,j] * vp[i,j]^2
#         end
#     end
#     return lambda
# end
#
# """
#    Define the partial derivative operator for v_z with PML boundary
# """
# function Mvz(vz_pml::SparseMatrixCSC{Tv,Ti}, rho::Matrix{Tv},
#          dz::Tv, dt::Tv, fd::Vector{Tv}) where {Ti<:Int64, Tv<:AbstractFloat}
#
#     # data format
#     data_format = eltype(rho)
#     double_dt   = data_format(2.0 * dt)
#
#     # finite-difference coefficients
#     order = length(fd)
#     c     = fd / dz
#
#     # model size
#     (m, n) = size(vz_pml)
#     c_diag = zeros(data_format, m*n)
#     denum  = zeros(data_format, m*n)
#     for ix = 1 : n
#         for iz = 1 : m
#
#             if iz < m
#                a = (rho[iz+1,ix]+rho[iz,ix]) / double_dt
#             else
#                a = rho[iz,ix] / dt
#             end
#             b = vz_pml[iz,ix] / 2
#             denum[(ix-1)*m+iz] = 1.0   / (a+b)
#             c_diag[(ix-1)*m+iz]= (a-b) / (a+b)
#         end
#     end
#
#     # spatial partial derivative
#     tmp = zeros(data_format, m, m)
#     for iz = 1 : m
#         lower = iz - order + 1 >= 1 ? iz-order+1 : 1
#         upper = iz + order     <= m ? iz+order   : m
#
#         for ix = lower : upper
#             if ix-iz <= 0
#                idx = iz-ix + 1
#                tmp[iz,ix] = - c[idx]
#             elseif ix-iz >= 1
#                idx = ix-iz
#                tmp[iz,ix] =   c[idx]
#             end
#         end
#     end
#
#     MvzBvz = c_diag
#     MvzBp  = spdiagm(0 => denum) * kron(spdiagm(0=>ones(data_format,n)), sparse(tmp))
#
#     return MvzBvz, MvzBp
#
# end
#
# """
#    Define the partial derivative operator for v_z with rigid boundary
# """
# function Mvz(rho::Matrix{Tv}, dz::Tv, dt::Tv, fd::Vector{Tv}) where {Tv<:AbstractFloat}
#
#     # data format
#     data_format = eltype(rho)
#     double_dt   = data_format(2.0 * dt)
#
#     # finite-difference coefficients
#     order = length(fd)
#     c     = fd / dz
#
#     # model size
#     (m, n) = size(rho)
#     denum  = zeros(data_format, m*n)
#     for ix = 1 : n
#         for iz = 1 : m
#
#             if iz < m
#                a = (rho[iz+1,ix]+rho[iz,ix]) / double_dt
#             else
#                a = rho[iz,ix] / dt
#             end
#             denum[(ix-1)*m+iz] = 1.0 / a
#         end
#     end
#
#     # spatial partial derivative
#     tmp = zeros(data_format, m, m)
#     for iz = 1 : m
#         lower = iz - order + 1 >= 1 ? iz-order+1 : 1
#         upper = iz + order     <= m ? iz+order   : m
#
#         for ix = lower : upper
#             if ix-iz <= 0
#                idx = iz-ix + 1
#                tmp[iz,ix] = - c[idx]
#             elseif ix-iz >= 1
#                idx = ix-iz
#                tmp[iz,ix] =   c[idx]
#             end
#         end
#     end
#
#     MvzBp  = spdiagm(0 => denum) * kron(spdiagm(0=>ones(data_format,n)), sparse(tmp))
#     return MvzBp
# end
#
# """
#    Define the partial derivative operator for v_x with PML boundary
# """
# function Mvx(vx_pml::SparseMatrixCSC{Tv,Ti}, rho::Matrix{Tv},
#          dx::Tv, dt::Tv, fd::Vector{Tv}) where {Ti<:Int64, Tv<:AbstractFloat}
#
#     # data format
#     data_format  = eltype(rho)
#     double_dt    = data_format(2.0 * dt)
#
#     # finite-difference coefficients
#     order = length(fd)
#     c     = fd / dx
#
#     # model size
#     (m, n) = size(vx_pml)
#     c_diag = zeros(data_format, m*n)
#     denum  = zeros(data_format, m*n)
#     for ix = 1 : n
#         for iz = 1 : m
#
#             if ix < n
#                a = (rho[iz,ix+1]+rho[iz,ix]) / double_dt
#             else
#                a = rho[iz,ix] / dt
#             end
#             b = vx_pml[iz,ix] / 2
#             denum[(ix-1)*m+iz] = 1     / (a+b)
#             c_diag[(ix-1)*m+iz]= (a-b) / (a+b)
#         end
#     end
#
#     # spatial partial derivative
#     tmp = zeros(data_format, n, n)
#     for iz = 1 : n
#         lower = iz - order + 1 >= 1 ? iz-order+1 : 1
#         upper = iz + order     <= n ? iz+order   : n
#
#         for ix = lower : upper
#             if ix-iz <= 0
#                idx = iz-ix + 1
#                tmp[iz,ix] = - c[idx]
#             elseif ix-iz >= 1
#                idx = ix-iz
#                tmp[iz,ix] =   c[idx]
#             end
#         end
#     end
#
#     MvxBvx = c_diag
#     MvxBp  = spdiagm(0 => denum) * kron(sparse(tmp), spdiagm(0=>ones(data_format,m)))
#
#     return MvxBvx, MvxBp
#
# end
#
# """
#    Define the partial derivative operator for v_x with rigid boundary
# """
# function Mvx(rho::Matrix{Tv}, dx::Tv, dt::Tv, fd::Vector{Tv}) where {Tv<:AbstractFloat}
#
#     # data format
#     data_format  = eltype(rho)
#     double_dt = data_format(2.0 * dt)
#
#     # finite-difference coefficients
#     order = length(fd)
#     c     = fd / dx
#
#     # model size
#     (m, n) = size(rho)
#     denum  = zeros(data_format, m*n)
#     for ix = 1 : n
#         for iz = 1 : m
#             # average density
#             if ix < n
#                a = (rho[iz,ix+1]+rho[iz,ix]) / double_dt
#             else
#                a = rho[iz,ix] / dt
#             end
#             denum[(ix-1)*m+iz] = 1.0 / a
#         end
#     end
#
#     # spatial partial derivative
#     tmp = zeros(data_format, n, n)
#     for iz = 1 : n
#         lower = iz - order + 1 >= 1 ? iz-order+1 : 1
#         upper = iz + order     <= n ? iz+order   : n
#
#         for ix = lower : upper
#             if ix-iz <= 0
#                idx = iz-ix + 1
#                tmp[iz,ix] = - c[idx]
#             elseif ix-iz >= 1
#                idx = ix-iz
#                tmp[iz,ix] =   c[idx]
#             end
#         end
#     end
#
#     MvxBp  = spdiagm(0 => denum) * kron(sparse(tmp), spdiagm(0=>ones(data_format,m)))
#     return MvxBp
# end
#
# """
#    Define the partial derivative operator for p_z with PML boundary
# """
# function Mpz(pz_pml::SparseMatrixCSC{Tv,Ti}, lambda::Matrix{Tv},
#          dz::Tv, dt::Tv, fd::Vector{Tv}) where {Ti<:Int64, Tv<:AbstractFloat}
#
#     # data format
#     data_format = eltype(lambda)
#
#     # finite-difference coefficients
#     order = length(fd)
#     c     = fd / dz
#
#     # model size
#     (m, n) = size(pz_pml)
#     c_diag = zeros(data_format, m*n)
#     denum  = zeros(data_format, m*n)
#     for ix = 1 : n
#         for iz = 1 : m
#             a = 1/dt + pz_pml[iz,ix]/2
#             b = 1/dt - pz_pml[iz,ix]/2
#             denum[(ix-1)*m+iz] = lambda[iz,ix] / a
#             c_diag[(ix-1)*m+iz]= b / a
#         end
#     end
#
#     # spatial partial derivative
#     tmp = zeros(data_format, m, m)
#     for iz = 1 : m
#         lower = iz - order     >= 1 ? iz-order   : 1
#         upper = iz + order - 1 <= m ? iz+order-1 : m
#
#         for ix = lower : upper
#             if ix-iz <= -1
#                idx = iz-ix
#                tmp[iz,ix] = - c[idx]
#             elseif ix-iz >= 0
#                idx = ix-iz+1
#                tmp[iz,ix] =   c[idx]
#             end
#         end
#     end
#
#     MpzBpz = c_diag
#     MpzBvz = spdiagm(0 => denum) * kron(spdiagm(0=>ones(data_format,n)), sparse(tmp))
#
#     return MpzBpz, MpzBvz
# end
#
# """
#    Define the partial derivative operator for p_z with rigid boundary
# """
# function Mpz(lambda::Matrix{Tv}, dz::Tv, dt::Tv, fd::Vector{Tv}) where {Tv<:AbstractFloat}
#
#     # data format
#     data_format = eltype(lambda)
#     c           = fd / dz
#
#     # model size
#     (m, n) = size(lambda)
#     denum  = zeros(data_format, m*n)
#     for ix = 1 : n
#         for iz = 1 : m
#             denum[(ix-1)*m+iz] = lambda[iz,ix] * dt
#         end
#     end
#
#     # spatial partial derivative
#     order = length(fd)
#     tmp   = zeros(data_format, m, m)
#     for iz = 1 : m
#         lower = iz - order     >= 1 ? iz-order   : 1
#         upper = iz + order - 1 <= m ? iz+order-1 : m
#
#         for ix = lower : upper
#             if ix-iz <= -1
#                idx = iz-ix
#                tmp[iz,ix] = - c[idx]
#             elseif ix-iz >= 0
#                idx = ix-iz+1
#                tmp[iz,ix] =   c[idx]
#             end
#         end
#     end
#
#     MpzBvz = spdiagm(0 => denum) * kron(spdiagm(0=>ones(data_format,n)), sparse(tmp))
#     return MpzBvz
#
# end
#
# """
#    Define the partial derivative operator for p_x with PML boundary
# """
# function Mpx(px_pml::SparseMatrixCSC{Tv,Ti}, lambda::Matrix{Tv},
#          dx::Tv, dt::Tv, fd::Vector{Tv}) where {Tv<:AbstractFloat, Ti<:Int64}
#
#     # data format
#     data_format = eltype(lambda)
#
#     # finite-difference coefficients
#     order = length(fd)
#     c     = fd / dx
#
#     (m, n) = size(px_pml)
#     c_diag = zeros(data_format, m*n)
#     denum  = zeros(data_format, m*n)
#     for ix = 1 : n
#         for iz = 1 : m
#             a = 1/dt + px_pml[iz,ix]/2
#             b = 1/dt - px_pml[iz,ix]/2
#             denum[(ix-1)*m+iz] = lambda[iz,ix] / a
#             c_diag[(ix-1)*m+iz]= b / a
#         end
#     end
#
#     # spatial partial derivative
#     tmp = zeros(data_format, n, n)
#     for iz = 1 : n
#         lower = iz - order     >= 1 ? iz-order   : 1
#         upper = iz + order - 1 <= n ? iz+order-1 : n
#
#         for ix = lower : upper
#             if ix-iz <= -1
#                idx = iz-ix
#                tmp[iz,ix] = - c[idx]
#             elseif ix-iz >= 0
#                idx = ix-iz+1
#                tmp[iz,ix] =   c[idx]
#             end
#         end
#     end
#
#     MpxBpx = c_diag
#     MpxBvx = spdiagm(0 => denum) * kron(sparse(tmp), spdiagm(0=>ones(data_format,m)))
#
#     return  MpxBpx, MpxBvx
# end
#
# """
#    Define the partial derivative operator for p_x with rigid boundary
# """
# function Mpx(lambda::Matrix{Tv}, dx::Tv, dt::Tv, fd::Vector{Tv}) where {Tv<:AbstractFloat}
#
#     # data format
#     data_format = eltype(lambda)
#
#     # finite-difference coefficients
#     order = length(fd)
#     c     = fd / dx
#
#     (m, n) = size(lambda)
#     denum  = zeros(data_format, m*n)
#     for ix = 1 : n
#         for iz = 1 : m
#             denum[(ix-1)*m+iz] = lambda[iz,ix] * dt
#         end
#     end
#
#     # spatial partial derivative
#     tmp = zeros(data_format, n, n)
#     for iz = 1 : n
#         lower = iz - order     >= 1 ? iz-order   : 1
#         upper = iz + order - 1 <= n ? iz+order-1 : n
#
#         for ix = lower : upper
#             if ix-iz <= -1
#                idx = iz-ix
#                tmp[iz,ix] = - c[idx]
#             elseif ix-iz >= 0
#                idx = ix-iz+1
#                tmp[iz,ix] =   c[idx]
#             end
#         end
#     end
#
#     MpxBvx = spdiagm(0 => denum) * kron(sparse(tmp), spdiagm(0=>ones(data_format,m)))
#
#     return MpxBvx
# end
#
# """
#    constructor for obsorbing finite difference stencil
# """
# function ObsorbFDStencil(params::TdParams)
#
#     # pml coefficient
#     pml = PMLCoefficients(params)
#
#     # padding physical model parameters
#     lambda = vp2lambda(params.vel, params.rho)
#     lambda = model_padding(lambda    , params.npml, params.free_surface)
#     rho    = model_padding(params.rho, params.npml, params.free_surface)
#
#     (MvzBvz, MvzBp ) = Mvz(pml.vz, rho   , params.dz, params.dt, params.fd_coefficients)
#     (MvxBvx, MvxBp ) = Mvx(pml.vx, rho   , params.dx, params.dt, params.fd_coefficients)
#     (MpzBpz, MpzBvz) = Mpz(pml.pz, lambda, params.dz, params.dt, params.fd_coefficients)
#     (MpxBpx, MpxBvx) = Mpx(pml.px, lambda, params.dx, params.dt, params.fd_coefficients)
#
#     return ObsorbFDStencil(MvzBvz, MvzBp , MvxBvx, MvxBp,
#                            MpzBpz, MpzBvz, MpxBpx, MpxBvx)
# end
#
# """
#    constructor for finite difference stencil with rigid boundary condition
# """
# function RigidFDStencil(params::TdParams)
#
#     # expand physical model parameters
#     lambda = vp2lambda(params.vel, params.rho)
#
#     MvzBp  = Mvz(params.rho, params.dz, params.dt, params.fd_coefficients)
#     MvxBp  = Mvx(params.rho, params.dx, params.dt, params.fd_coefficients)
#     MpzBvz = Mpz(lambda    , params.dz, params.dt, params.fd_coefficients)
#     MpxBvx = Mpx(lambda    , params.dx, params.dt, params.fd_coefficients)
#
#     return RigidFDStencil(MvzBp, MvxBp, MpzBvz, MpxBvx)
# end

# old version
# function compute_relative_error(fd::Vector{Tv}) where {Tv<:AbstractFloat}
#
#     M = length(fd)
#
#     interval = 0.001 * pi
#     beta = collect(interval : interval : pi)
#     N    = length(beta)
#     err  = zeros(N)
#
#     for i = 1 : N
#         tmp = 0.0
#         for m = 1 : M
#             tmp = tmp + fd[m] * sin((m-0.5)*beta[i])
#         end
#         err[i] = 2.0 * tmp / beta[i] - 1.0
#     end
#
#     return beta, err
# end

# the coefficients obtained by minimize absolute error
# fdc = Vector{Vector{Float64}}(9)
# fdc[1] = [0.1129136e1, -0.4304542e-1]
# fdc[2] = [0.1186247e1, -0.7266808e-1, 0.6351497e-2]
# fdc[3] = [0.1218159e1, -0.9397218e-1, 0.1519043e-1, -0.1742128e-2]
# fdc[4] = [0.1236425e1, -0.1081130   , 0.2339911e-1, -0.5061550e-2, 0.7054313e-3]
# fdc[5] = [0.1247576e1, -0.1174969   , 0.2997288e-1, -0.8741572e-2, 0.2262285e-2, -0.3745306e-3]
# fdc[6] = [0.1254380e1, -0.1235307   , 0.3467231e-1, -0.1192915e-1, 0.4057090e-2, -0.1191005e-2, 0.2263204e-3]
# fdc[7] = [0.1259012e1, -0.1277647   , 0.3820715e-1, -0.1458251e-1, 0.5845385e-2, -0.2213861e-2, 0.7243880e-3, -0.1566173e-3]
# fdc[8] = [0.1262147e1, -0.1306967   , 0.4075792e-1, -0.1665221e-1, 0.7377057e-2, -0.3258150e-2, 0.1336259e-2, -0.4775830e-3, 0.1151664e-3]
# fdc[9] = [0.1264362e1, -0.1327958   , 0.4264687e-1, -0.1824918e-1, 0.8656223e-2, -0.4200034e-2, 0.1989180e-2, -0.8686637e-3, 0.3342741e-3, -0.8854090e-4]

# M = 5
#
# (x_axis, err ) = compute_relative_error(fdc[M-1])
# (x_axis, err1) = compute_relative_error(rfdc[M-1])
#
# cfdc = finite_difference_coefficient(M)
# (x_axis, err2) = compute_relative_error(cfdc)
#
# plot(x_axis, err, label="new_a")
# plot(x_axis, err1, label="new_r")
# plot(x_axis, err2, label="old"); legend();

# """
#    finite difference stencil for updating vz
# """
# function Mvz(vz_pml::SparseMatrixCSC{Tv,Ti}, rho::Matrix{Tv},
#          dz::Tv, dt::Tv) where {Ti<:Int64, Tv<:AbstractFloat}
#
#     data_format  = eltype(rho)
#     (m, n) = size(vz_pml)
#     double_dt = data_format(2.0 * dt)
#
#     a1 = data_format( 9 / 8 )
#     a2 = data_format(-1 / 24)
#     c1 = a1 / dz
#     c2 = a2 / dz
#
#     c3    = zeros(data_format, m*n)
#     denum = zeros(data_format, m*n)
#
#     for ix = 1 : n
#         for iz = 1 : m
#             if iz < m
#                a = (rho[iz+1,ix]+rho[iz,ix]) / double_dt
#             else
#                a = rho[iz,ix] / dt
#             end
#             b = vz_pml[iz,ix] / 2
#
#             denum[(ix-1)*m+iz] = 1.0   / (a+b)
#             c3[(ix-1)*m+iz]    = (a-b) / (a+b)
#         end
#     end
#
#     tmp = zeros(data_format, m, m)
#     tmp[1,1] = -c1; tmp[1,2] = c1; tmp[1,3] = c2;
#
#     for iz = 2: m-2
#         tmp[iz,iz  ] = -c1; tmp[iz, iz+1] = c1;
#         tmp[iz,iz-1] = -c2; tmp[iz, iz+2] = c2;
#     end
#
#     tmp[m-1,m-1] = -c1; tmp[m-1, m  ] = c1; tmp[m-1, m-2] = -c2;
#     tmp[m  ,m  ] = -c1; tmp[m  , m-1] =-c2;
#
#     MvzBvz = c3
#     MvzBp  = spdiagm(denum) * kron(speye(data_format, n), sparse(tmp))
#
#     return MvzBvz, MvzBp
#
# end
#
# """
#    rigid boundary
# """
# function Mvz(rho::Matrix{Tv}, dz::Tv, dt::Tv) where {Tv<:AbstractFloat}
#
#     data_format  = eltype(rho)
#     (m, n) = size(rho)
#     double_dt = data_format(2.0 * dt)
#
#     a1 = data_format( 9 / 8 )
#     a2 = data_format(-1 / 24)
#     c1 = a1 / dz
#     c2 = a2 / dz
#
#     denum = zeros(data_format, m*n)
#
#     for ix = 1 : n
#         for iz = 1 : m
#             if iz < m
#                a = (rho[iz+1,ix]+rho[iz,ix]) / double_dt
#             else
#                a = rho[iz,ix] / dt
#             end
#             denum[(ix-1)*m+iz] = 1 / a
#         end
#     end
#
#     tmp = zeros(data_format, m, m)
#     tmp[1,1] = -c1; tmp[1,2] = c1; tmp[1,3] = c2;
#
#     for iz = 2: m-2
#         tmp[iz,iz  ] = -c1; tmp[iz, iz+1] = c1;
#         tmp[iz,iz-1] = -c2; tmp[iz, iz+2] = c2;
#     end
#
#     tmp[m-1,m-1] = -c1; tmp[m-1, m  ] = c1; tmp[m-1, m-2] = -c2;
#     tmp[m  ,m  ] = -c1; tmp[m  , m-1] =-c2;
#
#     MvzBp  = spdiagm(denum) * kron(speye(data_format, n), sparse(tmp))
#
#     return MvzBp
#
# end

# """
#    finite difference stencil for updating vx
# """
# function Mvx(vx_pml::SparseMatrixCSC{Tv,Ti}, rho::Matrix{Tv},
#          dx::Tv, dt::Tv) where {Ti<:Int64, Tv<:AbstractFloat}
#
#     data_format  = eltype(rho)
#     (m, n) = size(vx_pml)
#     double_dt = data_format(2.0 * dt)
#
#     a1 = data_format(9/8  )
#     a2 = data_format(-1/24)
#     c1 = a1 / dx
#     c2 = a2 / dx
#
#     c3    = zeros(data_format, m*n)
#     denum = zeros(data_format, m*n)
#
#     for ix = 1 : n
#         for iz = 1 : m
#             if ix < n
#                a = (rho[iz,ix+1]+rho[iz,ix]) / double_dt
#             else
#                a = rho[iz,ix] / dt
#             end
#             b = vx_pml[iz,ix] / 2
#
#             denum[(ix-1)*m+iz] = 1     / (a+b)
#             c3[(ix-1)*m+iz]    = (a-b) / (a+b)
#         end
#     end
#
#     tmp = zeros(data_format, n, n)
#     tmp[1,1] = -c1; tmp[1,2] = c1; tmp[1,3] = c2;
#
#     for ix = 2: n-2
#         tmp[ix,ix  ] = -c1; tmp[ix, ix+1] = c1;
#         tmp[ix,ix-1] = -c2; tmp[ix, ix+2] = c2;
#     end
#
#     tmp[n-1,n-1] = -c1; tmp[n-1, n  ] = c1; tmp[n-1,n-2] = -c2;
#     tmp[n  ,n  ] = -c1; tmp[n  , n-1] =-c2;
#
#     MvxBvx = c3
#     MvxBp = spdiagm(denum) * kron(sparse(tmp), speye(data_format, m))
#
#     return MvxBvx, MvxBp
#
# end
#
# """
#    rigid boundary Mvx
# """
# function Mvx(rho::Matrix{Tv}, dx::Tv, dt::Tv) where {Tv<:AbstractFloat}
#
#     data_format  = eltype(rho)
#     (m, n) = size(rho)
#     double_dt = data_format(2.0 * dt)
#
#     a1 = data_format(9/8  )
#     a2 = data_format(-1/24)
#     c1 = a1 / dx
#     c2 = a2 / dx
#     denum = zeros(data_format, m*n)
#
#     for ix = 1 : n
#         for iz = 1 : m
#             if ix < n
#                a = (rho[iz,ix+1]+rho[iz,ix]) / double_dt
#             else
#                a = rho[iz,ix] / dt
#             end
#             denum[(ix-1)*m+iz] = 1 / a
#         end
#     end
#
#     tmp = zeros(data_format, n, n)
#     tmp[1,1] = -c1; tmp[1,2] = c1; tmp[1,3] = c2;
#
#     for ix = 2: n-2
#         tmp[ix,ix  ] = -c1; tmp[ix, ix+1] = c1;
#         tmp[ix,ix-1] = -c2; tmp[ix, ix+2] = c2;
#     end
#
#     tmp[n-1,n-1] = -c1; tmp[n-1, n  ] = c1; tmp[n-1,n-2] = -c2;
#     tmp[n  ,n  ] = -c1; tmp[n  , n-1] =-c2;
#
#     MvxBp = spdiagm(denum) * kron(sparse(tmp), speye(data_format, m))
#
#     return MvxBp
#
# end

# """
#    finite difference stencil for updating pz
# """
# function Mpz(pz_pml::SparseMatrixCSC{Tv,Ti}, lambda::Matrix{Tv},
#          dz::Tv, dt::Tv) where {Ti<:Int64, Tv<:AbstractFloat}
#
#     data_format  = eltype(lambda)
#     (m, n) = size(pz_pml)
#
#     a1 = data_format( 9/8 )
#     a2 = data_format(-1/24)
#     c1 = a1 / dz
#     c2 = a2 / dz
#
#     c3    = zeros(data_format, m*n)
#     denum = zeros(data_format, m*n)
#
#     for ix = 1 : n
#         for iz = 1 : m
#             a = 1/dt + pz_pml[iz,ix]/2
#             b = 1/dt - pz_pml[iz,ix]/2
#             denum[(ix-1)*m+iz] = lambda[iz,ix] / a
#             c3[(ix-1)*m+iz]= b / a
#         end
#     end
#
#     tmp = zeros(data_format, m, m)
#     tmp[1,1] =  c1; tmp[1,2] = c2;
#     tmp[2,1] = -c1; tmp[2,2] = c1; tmp[2,3] = c2;
#
#     for iz = 3: m-1
#         tmp[iz,iz-1] = -c1; tmp[iz, iz  ] = c1;
#         tmp[iz,iz-2] = -c2; tmp[iz, iz+1] = c2;
#     end
#
#     tmp[m,m-1] = -c1; tmp[m,m] = c1; tmp[m, m-2] = -c2
#
#     MpzBpz = c3
#     MpzBvz = spdiagm(denum) * kron(speye(data_format, n), sparse(tmp))
#
#     return MpzBpz, MpzBvz
#
# end

# """
#    rigid boundary Mpz with high-order finite difference approximation
# """
# function Mpz(lambda::Matrix{Tv}, dz::Tv, dt::Tv) where {Tv<:AbstractFloat}
#
#     data_format  = eltype(lambda)
#     (m, n) = size(lambda)
#
#     a1 = data_format( 9/8 )
#     a2 = data_format(-1/24)
#     c1 = a1 / dz
#     c2 = a2 / dz
#
#     c3    = zeros(data_format, m*n)
#     denum = zeros(data_format, m*n)
#
#     for ix = 1 : n
#         for iz = 1 : m
#             denum[(ix-1)*m+iz] = lambda[iz,ix] * dt
#         end
#     end
#
#     tmp = zeros(data_format, m, m)
#     tmp[1,1] =  c1; tmp[1,2] = c2;
#     tmp[2,1] = -c1; tmp[2,2] = c1; tmp[2,3] = c2;
#
#     for iz = 3: m-1
#         tmp[iz,iz-1] = -c1; tmp[iz, iz  ] = c1;
#         tmp[iz,iz-2] = -c2; tmp[iz, iz+1] = c2;
#     end
#
#     tmp[m,m-1] = -c1; tmp[m,m] = c1; tmp[m, m-2] = -c2
#
#     MpzBvz = spdiagm(denum) * kron(speye(data_format, n), sparse(tmp))
#
#     return MpzBvz
# end

# """
#    finite difference stencil for updating px
# """
# function Mpx(px_pml::SparseMatrixCSC{Tv,Ti}, lambda::Matrix{Tv},
#          dx::Tv, dt::Tv) where {Tv<:AbstractFloat, Ti<:Int64}
#
#     data_format  = eltype(lambda)
#     (m, n) = size(px_pml)
#
#     a1 = data_format( 9/8 )
#     a2 = data_format(-1/24)
#     c1 = a1 / dx
#     c2 = a2 / dx
#
#     c3    = zeros(data_format, m*n)
#     denum = zeros(data_format, m*n)
#
#     for ix = 1 : n
#         for iz = 1 : m
#             a = 1/dt + px_pml[iz,ix]/2
#             b = 1/dt - px_pml[iz,ix]/2
#             denum[(ix-1)*m+iz] = lambda[iz,ix] / a
#             c3[(ix-1)*m+iz]= b / a
#         end
#     end
#
#     tmp = zeros(data_format, n, n)
#     tmp[1,1] =  c1; tmp[1,2] = c2;
#     tmp[2,1] = -c1; tmp[2,2] = c1; tmp[2,3] = c2;
#
#     for ix = 3: n-1
#         tmp[ix,ix-1] = -c1; tmp[ix, ix  ] = c1;
#         tmp[ix,ix-2] = -c2; tmp[ix, ix+1] = c2;
#     end
#
#     tmp[n,n-1] = -c1; tmp[n,n] = c1; tmp[n,n-2] = -c2;
#
#     MpxBpx = c3
#     MpxBvx = spdiagm(denum) * kron(sparse(tmp), speye(data_format, m))
#
#     return  MpxBpx, MpxBvx
# end
#
#
# """
#    rigid boundary Mpx
# """
# function Mpx(lambda::Matrix{Tv}, dx::Tv, dt::Tv) where {Tv<:AbstractFloat}
#
#     data_format  = eltype(lambda)
#     (m, n) = size(lambda)
#
#     a1 = data_format( 9/8 )
#     a2 = data_format(-1/24)
#     c1 = a1 / dx
#     c2 = a2 / dx
#
#     denum = zeros(data_format, m*n)
#
#     for ix = 1 : n
#         for iz = 1 : m
#             denum[(ix-1)*m+iz] = lambda[iz,ix] * dt
#         end
#     end
#
#     tmp = zeros(data_format, n, n)
#     tmp[1,1] =  c1; tmp[1,2] = c2;
#     tmp[2,1] = -c1; tmp[2,2] = c1; tmp[2,3] = c2;
#
#     for ix = 3: n-1
#         tmp[ix,ix-1] = -c1; tmp[ix, ix  ] = c1;
#         tmp[ix,ix-2] = -c2; tmp[ix, ix+1] = c2;
#     end
#
#     tmp[n,n-1] = -c1; tmp[n,n] = c1; tmp[n,n-2] = -c2;
#
#     MpxBvx = spdiagm(denum) * kron(sparse(tmp), speye(data_format, m))
#
#     return MpxBvx
# end

# """
#    constructor for finite difference sparse matrix
# """
# function FiniteDiffMatrix(par::PhysicalModel)
#
#     # pml coefficient
#     pml = PMLCoefficient(par)
#
#     # expand physical model parameters
#     lambda = vp2lambda(par.vel, par.rho)
#     lambda = model_expand(lambda , par.npml, par.free_surface)
#     rho    = model_expand(par.rho, par.npml, par.free_surface)
#
#     (MvzBvz, MvzBp ) = Mvz(pml.vz, rho   , par.dz, par.dt)
#     (MvxBvx, MvxBp ) = Mvx(pml.vx, rho   , par.dx, par.dt)
#     (MpzBpz, MpzBvz) = Mpz(pml.pz, lambda, par.dz, par.dt)
#     (MpxBpx, MpxBvx) = Mpx(pml.px, lambda, par.dx, par.dt)
#
#     return FiniteDiffMatrix(MvzBvz, MvzBp , MvxBvx, MvxBp,
#                             MpzBpz, MpzBvz, MpxBpx, MpxBvx)
#
# end
#
# """
#    finite difference sparse matrix with rigit boundary condition
# """
# function RigidFiniteDiffMatrix(par::PhysicalModel)
#
#     # expand physical model parameters
#     lambda = vp2lambda(par.vel, par.rho)
#
#     MvzBp  = Mvz(par.rho, par.dz, par.dt)
#     MvxBp  = Mvx(par.rho, par.dx, par.dt)
#     MpzBvz = Mpz(lambda , par.dz, par.dt)
#     MpxBvx = Mpx(lambda , par.dx, par.dt)
#
#     return RigidFiniteDiffMatrix(MvzBp , MvxBp,
#                                  MpzBvz, MpxBvx)
#
# end


struct ZeroPotential <: Potential end

UU(phi,  ::ZeroPotential) = 0
UUp(phi, ::ZeroPotential) = 0

parameters(potential::ZeroPotential) = ()

"""
* `oophiM2` : 1/ϕM^2
* `oophiQ`  : 1/ϕQ

Note that, in the notation used in the paper XXXX.XXXX,
```math
λ₄ = 1/(4 ϕM^2), λ₆ = 1/ϕQ
```
"""
Base.@kwdef struct Phi8Potential{T} <: Potential
    oophiM2 :: T = 0.0
    oophiQ  :: T = 0.0
end

parameters(potential::Phi8Potential) = (oophiM2=potential.oophiM2, oophiQ=potential.oophiQ)

@doc raw"""
```math
U = -\frac{1}{3} + \frac{ϕ^2}{3 ϕM^2} + \frac{ϕ^2}{2 ϕM^4} - \frac{2 ϕ^2}{ϕQ}
 + \frac{4 ϕ^4}{3 ϕQ} + \frac{6 ϕ^4}{ϕM^2 ϕQ} - \frac{ϕ^4}{12 ϕM^4}
 + \frac{18 ϕ^6}{ϕQ^2} - \frac{2 ϕ^6}{3 ϕM^2 ϕQ}
 - \frac{4ϕ^8}{3 ϕQ^2}
```
"""
function UU(phi, potential::Phi8Potential)
    phi2 = phi  * phi
    phi4 = phi2 * phi2
    phi6 = phi2 * phi4
    phi8 = phi4 * phi4

    oophiM2 = potential.oophiM2
    oophiM4 = oophiM2 * oophiM2
    oophiQ  = potential.oophiQ
    oophiQ2 = oophiQ * oophiQ

    -1/3 + phi2 * (oophiM4 / 2 + oophiM2 / 3 - 2 * oophiQ) +
        phi4 * ( -oophiM4 / 12 + 4 * oophiQ / 3 + 6 * oophiM2 * oophiQ) +
        phi6 * (18 * oophiQ2 - 2 * oophiM2 * oophiQ / 3) -
        phi8 * 4 * oophiQ2 / 3
end

@doc raw"""
```math
Up = \frac{dU}{dϕ}
```
"""
function UUp(phi, potential::Phi8Potential)
    phi2 = phi  * phi
    phi3 = phi  * phi2
    phi5 = phi3 * phi2
    phi7 = phi5 * phi2

    oophiM2 = potential.oophiM2
    oophiM4 = oophiM2 * oophiM2
    oophiQ  = potential.oophiQ
    oophiQ2 = oophiQ * oophiQ

    phi * (oophiM4 + 2 * oophiM2 / 3 - 4 * oophiQ) +
        phi3 * ( -oophiM4 / 3 + 16 * oophiQ / 3 + 24 * oophiM2 * oophiQ) +
        phi5 * (108 * oophiQ2 - 4 * oophiM2 * oophiQ) -
        phi7 * 32 * oophiQ2 / 3
end

#New potential coming from a superpotential with extra term alpha*phi^8

Base.@kwdef struct PhiAlphaBetaPotential{T} <: Potential
    oophiM2 :: T   = 0.0
    oophiQ  :: T   = 0.0
    alpha   :: T   = 0.0
    beta    :: Int = 1
end

parameters(potential::PhiAlphaBetaPotential) = (oophiM2=potential.oophiM2, oophiQ=potential.oophiQ, alpha=potential.alpha)

function UU(phi, potential::PhiAlphaBetaPotential)
    phi2  = phi  * phi
    phi4  = phi2 * phi2
    phi6  = phi2 * phi4
    phi8  = phi4 * phi4

    oophiM2 = potential.oophiM2
    oophiM4 = oophiM2 * oophiM2
    oophiQ  = potential.oophiQ
    oophiQ2 = oophiQ * oophiQ
    alpha   = potential.alpha
    alpha2  = alpha * alpha
    beta    = potential.beta
    beta2   = beta * beta

    return -1/3+phi2*(1/2*oophiM4+1/3*oophiM2-2*oophiQ)+
            phi4*(4/3*oophiQ+6*oophiM2*oophiQ-1/12*oophiM4)+
            phi6*(18*oophiQ2-2/3*oophiM2*oophiQ)-4/3*phi8*oophiQ2-
            phi^beta*(2/3*alpha*oophiM2+6*alpha*beta*oophiQ)+
            +1/2*alpha2*beta2*phi^(2*beta-6)+
            phi^(beta-4)*(4*alpha-alpha*beta)+
            4/3*phi^(2*beta-4)*alpha2+
            phi^(beta-2)*(4/3*alpha+alpha*beta*oophiM2)-
            8/3*alpha*oophiQ*phi^(2+beta)

end

@doc raw"""
```math
Up = \frac{dU}{dϕ}
```
"""
function UUp(phi, potential::PhiAlphaBetaPotential)
    phi2  = phi  * phi
    phi3  = phi  * phi2
    phi5  = phi3 * phi2
    phi7  = phi5 * phi2

    oophiM2 = potential.oophiM2
    oophiM4 = oophiM2 * oophiM2
    oophiQ  = potential.oophiQ
    oophiQ2 = oophiQ * oophiQ
    alpha   = potential.alpha
    alpha2  = alpha * alpha
    beta    = potential.beta
    beta2   = beta * beta

    return 2*phi*(1/2*oophiM4+1/3*oophiM2-2*oophiQ)+
            4*phi3*(4/3*oophiQ+6*oophiM2*oophiQ-1/12*oophiM4)+
            6*phi5*(18*oophiQ2-2/3*oophiM2*oophiQ)-32/3*phi7*oophiQ2-
            beta*phi^(beta-1)*(2/3*alpha*oophiM2+6*alpha*beta*oophiQ)+
            +1/2*alpha2*beta2*(2*beta-6)*phi^(2*beta-7)+
            (beta-4)*phi^(beta-5)*(4*alpha-alpha*beta)+
            4/3*(2*beta-4)*phi^(2*beta-5)*alpha2+
            (beta-2)*phi^(beta-3)*(4/3*alpha+alpha*beta*oophiM2)-
            8/3*alpha*oophiQ*(2+beta)*phi^(1+beta)

end


# scale the point set down such that the union of all circumcircles lies within the square frame.
# this does not only allow for arbitrary point distributions, but more importantly
# ensures convexity (and correctness) of the resulting Delaunay triangulation.

function scaleShiftPoints( points::Array{T,1} ) where T<:AbstractPoint2D
  # convex hull of point set to find the outer edges
  hull = quickHull( points )
  # union of the outer circumcircles
  ccu = circumcircleUnion( hull, points )
  # frame around the union of circumcircles
  ranges = frameRanges( ccu )
  # scale the point set down to the initial square
  scaledPoints = shrink( points, ranges )
  # do the tessellation on the scaled point set,
  # use the ranges to convert back to original scale afterwards
  return scaledPoints, ranges
end

# convert the edge points back to original scale after the tessellation

function expand( points::Array{Point2D,1}, ranges::NTuple{4,Float64} )
  xmin = ranges[1]
  ymin = ranges[3]
  scale = max( ranges[4] - ranges[3], ranges[2] - ranges[1] ) / 0.98
  offset = 1.01
  scaledPoints = [ Point2D( ( p._x - offset ) * scale + xmin, ( p._y - offset ) * scale + ymin ) for p in points ]
end

function shrink( points::Array{Point2D,1}, ranges::NTuple{4,Float64} )
  h = ranges[4] - ranges[3]
  b = ranges[2] - ranges[1]
  offset = 1.01
  scale = 0.98 / max( h, b )
  scaledPoints = [ Point2D( ( p._x - ranges[1] ) * scale + offset, ( p._y - ranges[3] ) * scale + offset ) for p in points ]
end

#=== auxiliary functions and structs ===#

# convex hull of point set

function quickHull(points::Array{T,1}) where T<:AbstractPoint2D
    return quickHull(Point2D.(points))
end

function quickHull( points::Array{Point2D,1} )
  ConvexHull = Array{Point2D,1}(undef,0)
  A = points[ argmin( getx.( points ) ) ]   # leftmost point
  B = points[ argmax( getx.( points ) ) ]   # rightmost point
  push!( ConvexHull, A )
  push!( ConvexHull, B )
  pr = Array{Point2D,1}(undef,0)            # points to the right of AB
  pl = Array{Point2D,1}(undef,0)            # points to the left of AB
  ( pl, pr ) = dividePointSet( Line2D( A, B ), setdiff( points, ConvexHull ) )
  findHull!( ConvexHull, pr, A, B )         # divide-and-conquer approach
  findHull!( ConvexHull, pl, B, A )
  return ConvexHull
end

function findHull!( ConvexHull::Array{T,1}, points::Array{T,1}, A::T, B::T ) where T<:AbstractPoint2D
  if isempty( points )
    return
  end
  C = findFarthestPoint( A, B, points )
  pos = findfirst( x -> x == A, ConvexHull ) + 1
  insert!( ConvexHull, pos, C )
  pAC = dividePointSet( Line2D(A,C), setdiff(points,[C]) )[2]
  pCB = dividePointSet( Line2D(C,B), setdiff(points,[C]) )[2]
  findHull!( ConvexHull, pAC, A, C )
  findHull!( ConvexHull, pCB, C, B )
end

function dividePointSet( l::Line2D, points::Array{T,1} ) where T <: AbstractPoint2D
  pr = Array{T,1}(undef,0)
  pl = Array{T,1}(undef,0)
  for i in 1:length(points)
    if orientation(l,points[i]) == -1
      push!(pr,points[i])
    else
      push!(pl,points[i])
    end
  end
  return (pl, pr)
end

function findFarthestPoint(a::T,b::T,points::Array{T,1}) where T<:AbstractPoint2D
  distances = Array{Float64,1}(undef,size(points,1))
  for i in 1:length(distances)
    distances[i] = pointLineDistance(a,b,points[i])
  end
  return points[argmax(distances)]
end

function pointLineDistance(a::T,b::T,c::T) where T<:AbstractPoint2D
  return abs( (gety(b)-gety(a))*getx(c) - (getx(b)-getx(a))*gety(c) + getx(b)*gety(a) - gety(b)*getx(a) ) / sqrt( (gety(b)-gety(a))^2 + (getx(b)-getx(a))^2 )
end

mutable struct Circle{T<:AbstractPoint2D}
  c::T
  r::Float64
end

function getcen(circ::Circle{T}) where T<:AbstractPoint2D
    return circ.c
end

function getcenx(circ::Circle{T}) where T<:AbstractPoint2D
    return getx( getcen( circ ) )
end
function getceny(circ::Circle{T}) where T<:AbstractPoint2D
    return gety( getcen( circ ) )
end
function getrad(circ::Circle{T}) where T<: AbstractPoint2D
    return circ.r
end

function circumcircleUnion( hull::Array{T,1}, points::Array{S,1} ) where {T<:AbstractPoint2D,S<:AbstractPoint2D}
  ccU = Array{Circle{Point2D},1}(undef,length(hull))
  hullcirc = copy(hull)
  push!( hullcirc, hull[1] )
  for i in 1:length( hull )
    cc2 = circumcircle( hullcirc[i], hullcirc[i+1] )
    ccU[i] = cc2
    for j in 1:length(points)
      if pointsDistance( points[j], cc2.c ) < cc2.r
        cc3 = circumcircle( hullcirc[i], hullcirc[i+1], points[j] )
        if getrad(cc3) > getrad(ccU[i])
          ccU[i] = cc3
        end
      end
    end
  end
  return ccU
end

function pointsDistance( p1::T, p2::S ) where {T<:AbstractPoint2D, S<:AbstractPoint2D}
  sqrt( ( getx(p1) - getx(p2) ) ^ 2 + ( gety(p1) - gety(p2) ) ^ 2 )
end

function circumcircle( a::T, b::S ) where {T<:AbstractPoint2D, S<:AbstractPoint2D}
  px = (getx(a) + getx(b)) / 2.0
  py = (gety(a) + gety(b)) / 2.0
  dx = (getx(b) - getx(a)) / 2.0
  dy = (gety(b) - gety(a)) / 2.0
  r = sqrt( dx^2 + dy^2 )
  return Circle{Point2D}( Point2D(px,py), r )
end

function circumcircle( a::T, b::S, c::U ) where {T<:AbstractPoint2D,S<:AbstractPoint2D, U<:AbstractPoint2D}
  la = getx(a)^2 + gety(a)^2
  lb = getx(b)^2 + gety(b)^2
  lc = getx(c)^2 + gety(c)^2
  xyy = getx(a) * (gety(b) - gety(c))
  yxx = gety(a) * (getx(b) - getx(c))
  xy = getx(b) * gety(c)
  yx = gety(b) * getx(c)
  z = 2 * ( xyy - yxx + xy - yx )
  px = ( la*(gety(b)-gety(c)) + lb*(gety(c)-gety(a)) + lc*(gety(a)-gety(b)) ) / z
  py = ( la*(getx(c)-getx(b)) + lb*(getx(a)-getx(c)) + lc*(getx(b)-getx(a)) ) / z
  r = sqrt( (px-getx(a))^2 + (py-gety(a))^2 )
  return Circle{Point2D}( Point2D(px,py), r )
end

function frameRanges( ccU::Array{Circle{T},1} ) where T<:AbstractPoint2D
  xmin = minimum( getcenx.(ccU) .- getrad.(ccU) )
  xmax = maximum( getcenx.(ccU) .+ getrad.(ccU) )
  ymin = minimum( getceny.(ccU) .- getrad.(ccU) )
  ymax = maximum( getceny.(ccU) .+ getrad.(ccU) )
  return xmin, xmax, ymin, ymax
end


using Gnuplot, ColorSchemes
#first plot
ψ1 = ψ2 = LinRange(0, 4π, 100)
function tα_qubit(β, ψ1, ψ2, fα, f)
    term1 = 2 .+ 2 .*β .- cos.(ψ1) .- cos.(ψ2)
    term2 = 2*β*cos.(π*fα) .* cos.(2*π*f .+ π*fα .- ψ1 .- ψ2)
    term1 .- term2
end
zq = [tα_qubit(0.61, i, j, 0.2, 0.1) for i in ψ1, j in ψ2]

@gsp xlabel = "ψ1" ylabel = "ψ2" title = "tunnel α_{q}" :-
@gsp :- "set size square" "set auto fix" :-
@gsp :- ψ1 ψ2 zq "w pm3d notit" palette(:thermal)
@gsp :- "set cbrange [$(minimum(zq)):$(maximum(zq))]"
@gsp :- "set xyplane 0"
levels = contourlines(ψ1, ψ2, zq, "levels 30")
zp = -8
for i in 1:length(levels)
    @gsp :- levels[i].data "u 1:2:($zp):($(levels[i].z)) w l notit lc pal" :-
end
@gsp
save(term ="pngcairo size 600,400", output ="./code/countour_mix3d.png") # hide


struct SineOperation{rank} <: Operation{rank}
    shape::Tuple{}
    children::Tuple{AbstractTensor}
    freeindices::Tuple{}
    function SineOperation(A::AbstractTensor{rank}) where rank
        new{rank}(A.shape, (A,), ())
    end
end

function Base.sin(s:: AbstractTensor{0})
    return SineOperation(Tensor(s))
end

struct CosineOperation{rank} <: Operation{rank}
    shape::Tuple{}
    children::Tuple{AbstractTensor}
    freeindices::Tuple{}
    function CosineOperation(A::AbstractTensor{rank}) where rank
        new{rank}(A.shape, (A,), ())
    end
end

function Base.cos(s::AbstractTensor{0})
    return CosineOperation(Tensor(s))
end

struct TangentOperation{rank} <: Operation{rank}
    shape::Tuple{}
    children::Tuple{AbstractTensor}
    freeindices::Tuple{}
    function TangentOperation(A::AbstractTensor{rank}) where rank
        new{rank}(A.shape, (A,),())
    end
end

function Base.tan(s::AbstractTensor{0})
    return TangentOperation(Tensor(s))
end

struct ArcsineOperation{rank} <: Operation{rank}
    shape::Tuple{}
    children::Tuple{AbstractTensor}
    freeindices::Tuple{}
    function ArcsineOperation(A::AbstractTensor{rank}) where rank
        if A isa ConstantTensor{T} where T<: Number && abs(A.value) > 1
            throw(DomainError(A, " asin is not defined for |x| > 1."))
        end
        new{rank}(A.shape, (A,),())
    end
end

function Base.asin(s::AbstractTensor{0})
    return ArcsineOperation(Tensor(s))
end

struct ArccosineOperation{rank} <: Operation{rank}
    shape::Tuple{}
    children::Tuple{AbstractTensor}
    freeindices::Tuple{}
    function ArccosineOperation(A::AbstractTensor{rank}) where rank
        if A isa ConstantTensor{T} where T<: Number && abs(A.value) > 1
            throw(DomainError(A, " acos is not defined for |x| > 1."))
        end
        new{rank}(A.shape, (A,),())
    end
end

function Base.acos(s::AbstractTensor{0})
    return ArccosineOperation(Tensor(s))
end

struct ArctangentOperation{rank} <: Operation{rank}
    shape::Tuple{}
    children::Tuple{AbstractTensor}
    freeindices::Tuple{}
    function ArctangentOperation(A::AbstractTensor{rank}) where rank
        new{rank}(A.shape, (A,),())
    end
end

function Base.atan(s::AbstractTensor{0})
    return ArctangentOperation(Tensor(s))
end


using Gadfly

DF68 = 8 # свободное расстояние кода, вариант 68

FCoefs = [3, 15, 58, 201, 655, 2052, 6255, 18687, 54974, 159765]
F68Powers = [8:2:26]

function Q(x)
    0.5 * erfc(x / sqrt(2))
end

function Peb(df, B, D0, F, p0)
    B(df, p0) * F(D0(p0))
end

function dskB(w, p0)
    (1 - p0) / (1 - 2p0) * sqrt(2 / (π * w))
end

function dskD0(p0)
    2 * sqrt(p0 * (1 - p0))
end

function abgshB(w, sg)
    Q(sqrt(2 * w * sg)) * exp(w * sg)
end

function abgshD0(sg)
    exp(-sg)
end

function F68(D)
    (D^8 * (D^4 - 3D^2 + 3)) / (D^4 - 3D^2 + 1)^2
end

function F68_5(D)
    dot(FCoefs[1:5], map(i -> D^i, F68Powers[1:5]))
end

function F68_10(D)
    dot(FCoefs, map(i -> D^i, F68Powers))
end

sdb = [-2:0.01:10] # SNR, dB per bit
sb = 10 .^ (sdb / 10) # SNR per bit
sig = sqrt(0.5 ./ sb)
sig1 = 1 ./ sig
p0 = Q(sig1)

dskPEB68      = map(p -> Peb(DF68, dskB, dskD0, F68, p), p0)
dskPEB68_5    = map(p -> Peb(DF68, dskB, dskD0, F68_5, p), p0)
dskPEB68_10   = map(p -> Peb(DF68, dskB, dskD0, F68_10, p), p0)

abgshPEB68      = map(p -> Peb(DF68, abgshB, abgshD0, F68, p), sb)
abgshPEB68_5    = map(p -> Peb(DF68, abgshB, abgshD0, F68_5, p), sb)
abgshPEB68_10   = map(p -> Peb(DF68, abgshB, abgshD0, F68_10, p), sb)

gplot = plot(layer(x=sdb, y=dskPEB68,      Theme(default_color=colorant"blue"), Geom.line),
             layer(x=sdb, y=dskPEB68_5,    Theme(default_color=colorant"red"), Geom.line),
             layer(x=sdb, y=dskPEB68_10,   Theme(default_color=colorant"green"), Geom.line),
             layer(x=sdb, y=abgshPEB68,    Theme(default_color=colorant"orange"), Geom.line),
             layer(x=sdb, y=abgshPEB68_5,  Theme(default_color=colorant"black"), Geom.line),
             layer(x=sdb, y=abgshPEB68_10, Theme(default_color=colorant"purple"), Geom.line),
             Guide.manual_color_key("Графики",
                                   ["ДСК, F", "ДСК, 5", "ДСК, 10", "АБГШ, F", "АБГШ, 5", "АБГШ, 10"],
                                   ["blue", "red", "green", "orange", "black", "purple"]),
             Theme(default_color=colorant"black"),
             Guide.xticks(ticks=[-2:1:10]), Scale.y_log10,
             Guide.xlabel("E/N0 (dB)"), Guide.ylabel("Вероятность ошибки на бит"))

draw(PDF("errors_all.pdf", 6inch, 4inch), gplot)


using Combinatorics

function part1(input)
    ingredients = reduce(hcat, parse_line.(eachline(input)))
    return foldl(max, (score1(ingredients[1:(end - 1), :], proportions)
                       for proportions1 in partitions(100, size(ingredients, 2))
                       for proportions in permutations(proportions1)))
end

function part2(input)
    ingredients = reduce(hcat, parse_line.(eachline(input)))
    return foldl(max, (score2(ingredients, proportions)
                       for proportions1 in partitions(100, size(ingredients, 2))
                       for proportions in permutations(proportions1)))
end

parse_line(line) = parse.(Int, rstrip.(split(line)[3:2:11], ','))

score1(ingredients, proportions) = prod(max.(0, ingredients * proportions))

function score2(ingredients, proportions)
    n = max.(0, ingredients * proportions)
    return prod(n[1:(end - 1)]) * (n[end] == 500)
end


using AbstractAlgebra

@testset "AbstractAlgebra io consistency" begin
    R, x = PolynomialRing(GF(2^31 - 1), "x")
    @test Groebner.groebner([x^2 - 4, x + 2]) == [x + 2]

    R, x = PolynomialRing(QQ, "x")
    @test Groebner.groebner([x^2 - 4, x + 2]) == [x + 2]

    R, x = PolynomialRing(ZZ, "x")
    @test Groebner.groebner([x^2 - 4, x + 2]) == [x + 2]
end


struct PolyaGammaGammaSumSamplerOld <:
       Distributions.Sampleable{Distributions.Univariate,Distributions.Continuous}
    h::Float64
    z::Float64
end

const bvec = map(x -> (x - 0.5)^2 * __PI^2 * 4, 1:_TERMS)

## draw sum of gammas
function Distributions.rand(rng::Distributions.AbstractRNG, s::PolyaGammaGammaSumSamplerOld)
    g = Distributions.rand(rng, Distributions.Gamma(s.h), _TERMS)
    # x = @distributed (+) for k in bvec
    # 2 * Distributions.rand(rng, g) / (s.z^2 + k)
    # end
    # return x
    # b =  mapreduce(k -> 1 / (k + s.z^2), +, bvec2)
    #return Distributions.rand(rng,Gamma(2 * s.h * b))#
    # return (2 * sum( map(bvec, g) do k, g_i
    #          g_i / (k + s.z^2)
    #         end )
    return 2 * mapreduce((k, g_i) -> Random.rand(rng, g) / (k + s.z^2), +, bvec, g)
end


using JuMP, Combinatorics, Gurobi

function printResults(o, a, b, edges, T, Tf)
    println("Objective value: ", o)

    for i in 1:size(b,1)
        println("bw flow ", i, " = ", getvalue(b[i]))
    end
    print("\n")
    println("-----------------------------------------")

    for i in 1:size(a,1)
        for j in 1:size(a,2)
            println("flow ",i, ", tunnel ", j, " = ", getvalue(a[i,j]))
            print("edges in use: ")
            for e in T[Tf[i][j]]
                print(edges[e])
            end
            println("\n")
        end
    end
end

function FFC(env, edges, capacity, flows, demand, k, T, Tf; minb=0)

    nedges = size(edges,1)
    nflows = size(flows,1)
    ntunnels = size(T,1)

    # CREATE ALL SCENARIOS
    scenarios = []
    for bits in collect(combinations(1:nedges,k))
        s = ones(nedges)
        for bit in bits
            s[bit] = 0
        end
        push!(scenarios, s)
    end

    #CREATE RESIDUAL TUNNELS BY SCENARIO BY FLOW (References Tf)
    Tsf = []
    for s in 1:size(scenarios,1)
        sft = []
        for f in 1:size(Tf,1)
            ft = []
            for t in 1:size(Tf[f],1)
                up = true
                if (length(T[Tf[f][t]]) == 0)
                    up = false
                end
                for e in T[Tf[f][t]]
                    if scenarios[s][e] == 0
                        up = false
                    end
                end
                if up
                    push!(ft, t)
                end
            end
            push!(sft,ft)
        end
        push!(Tsf,sft)
    end

    #CREATE TUNNEL EDGE MATRIX
    L = zeros(ntunnels, nedges)
    for t in 1:ntunnels
        for e in 1:nedges
            if in(e, T[t])
                L[t,e] = 1
            end
        end
    end

    model = Model(solver=GurobiSolver(env, OutputFlag=0))
    @variable(model, b[1:nflows] >= 0, basename="b")
    @variable(model, a[1:nflows,1:size(Tf[1],1)] >= 0, basename="a")
    @variable(model, u >= 0, basename="u")

    for f in 1:nflows
        @constraint(model, sum(a[f,t] for t in 1:size(Tf[f],1)) >= b[f])   #the sum of all allocated bandwidths on every flow must be >= the total bandwidth for that flow
    end

    for e in 1:nedges
        @constraint(model, sum(a[f,t] * L[Tf[f][t],e] for f in 1:nflows for t in 1:size(Tf[f],1)) <= capacity[e])   #overlapping flows cannot add up to the capacity of that link
    end

    for f in 1:nflows
        for s in 1:size(scenarios,1)
            @constraint(model, sum(a[f,t] for t in Tsf[s][f]) >= b[f])   #residual tunnels must be able to carry bandwidth
        end
    end

    for f in 1:nflows
        @constraint(model, b[f] >= minb)
    end

    for f in 1:nflows
        @constraint(model, b[f] <= demand[f])   #all allocated bandwidths must be less than the demand for that flow
        for t in 1:size(Tf[f],1)
            @constraint(model, a[f,t] >= 0)     #each allocated bandwidth on for flow f on tunnel t >= 0
        end
    end

    for f in 1:nflows
        # @constraint(model, u <= b[f]/demand[f])
    end
  #   @objective(model, Max, u)

    @objective(model, Max, sum((b[i] for i in 1:size(b,1))))
    solve(model)
    # printResults(getobjectivevalue(model), a, b, edges, T, Tf)
    return getvalue(a), getvalue(b)
end


### townsend.jl ---  Perform original Lomb–Scargle periodogram
#
# Copyright (C) 2017 Mosè Giordano.
#
# Maintainer: Mosè Giordano <mose AT gnu DOT org>
# Keywords: periodogram, lomb scargle
#
# This file is a part of LombScargle.jl.
#
# License is BSD 3-clause "New" or "Revised".
#
### Code:

struct LSPlan{T,A,B<:AbstractVector{T},C,D,E,F,G} <: PeriodogramPlan
    times::A
    signal::B
    freq::C
    sumw::D
    w::E
    X::B
    YY::T
    noise::F
    norm::Symbol
    P::G
end

# Original algorithm that doesn't take into account uncertainties and doesn't
# fit the mean of the signal.  This is implemented following the recipe by
# * Townsend, R. H. D. 2010, ApJS, 191, 247 (URL:
#   http://dx.doi.org/10.1088/0067-0049/191/2/247,
#   Bibcode: http://adsabs.harvard.edu/abs/2010ApJS..191..247T)
function _lombscargle_orig!(P::AbstractVector{T}, times::AbstractVector{<:Real},
                            X::AbstractVector{<:Real}, freqs::AbstractVector{<:Real},
                            XX::Real) where {T<:Complex}
    N = length(X)
    @inbounds Threads.@threads for n in eachindex(freqs)
        ω = freqs[n] * 2 * pi
        XC = XS = CC = CS = zero(T)
        @inbounds for j in eachindex(times)
            S, C = sincos(ω * times[j])
            XC  += X[j]*C
            XS  += X[j]*S
            CC  += C*C
            CS  += C*S
        end
        SS       = N - CC
        s_τ, c_τ = sincos(atan(CS, CC - N / 2) / 2)
        c_τ2     = c_τ*c_τ
        s_τ2     = s_τ*s_τ
        cs_τ_CS  = 2c_τ*s_τ*CS
        #=
        P[n] = sign(CC_τ)*(YC_τ)/sqrt(abs(2*CC_τ)) + 1.0im*sign(SS_τ)*(YS_τ)/sqrt(abs(2*SS_τ))
        P[n]     = (c_τ*XC + s_τ*XS)/sqrt(2*((c_τ2*CC + cs_τ_CS + s_τ2*SS))) +
                    1.0im*(c_τ*XS - s_τ*XC)/sqrt(2*(c_τ2*SS - cs_τ_CS + s_τ2*CC))
        =#
        P[n]     = (c_τ*XC + s_τ*XS)/sqrt(2*((c_τ2*CC + cs_τ_CS + s_τ2*SS))) +
                    1.0im*(c_τ*XS - s_τ*XC)/sqrt(2*(c_τ2*SS - cs_τ_CS + s_τ2*CC))
    end
    return P
end

_periodogram!(p::LSPlan) = _lombscargle_orig!(p.P, p.times, p.X, p.freq, p.YY)
_periodogram!(times, p::LSPlan) = _lombscargle_orig!(p.P, times, p.X, p.freq, p.YY)