"""
    SSIM([kernel], [(α, β, γ)]; crop=false) <: FullReferenceIQI
    assess(iqi::SSIM, img, ref)
    assess_ssim(img, ref; crop=false)

Structural similarity (SSIM) index is an image quality assessment method based
on degradation of structural information.

The SSIM index is composed of three components: luminance, contrast, and
structure; `ssim = 𝐿ᵅ * 𝐶ᵝ * 𝑆ᵞ`, where `W := (α, β, γ)` controls relative
importance of each components. By default `W = (1.0, 1.0, 1.0)`.

In practice, a mean version SSIM is used. At each pixel, SSIM is calculated
locally with neighborhoods weighted by `kernel`, returning a ssim map;
`ssim` is actaully `mean(ssim_map)`.
By default `kernel = KernelFactors.gaussian(1.5, 11)`.

!!! tip

    The default parameters comes from [1]. For benchmark usage, it is recommended to not
    change the parameters, because most other SSIM implementations follows the same settings.
    Keyword `crop` controls whether the boundary of ssim map should be dropped; you need
    to set it `true` to reproduce the result in [1].

# Example

`assess_ssim(img, ref)` should be sufficient to get a benchmark for algorithms. One
could also instantiate a customed SSIM, then pass it to `assess` or use it as a
function. For example:

```julia
iqi = SSIM(KernelFactors.gaussian(2.5, 17), (1.0, 1.0, 2.0))
assess(iqi, img, ref)
iqi(img, ref) # both usages are equivalent
```

!!! info

    SSIM is defined only for gray images. How `RGB` and other `Color3` images are handled may vary
    in different implementations. For `RGB` images, channels are handled seperately when calculating
    𝐿, 𝐶, and 𝑆. Generic `Color3` images are converted to `RGB` first before calculation.

# Reference

[1] Wang, Z., Bovik, A. C., Sheikh, H. R., & Simoncelli, E. P. (2004). Image quality assessment: from error visibility to structural similarity. _IEEE transactions on image processing_, 13(4), 600-612.

[2] Wang, Z., Bovik, A. C., Sheikh, H. R., & Simoncelli, E. P. (2003). The SSIM Index for Image Quality Assessment. Retrived May 30, 2019, from http://www.cns.nyu.edu/~lcv/ssim/
"""
struct SSIM{A<:AbstractVector} <: FullReferenceIQI
    kernel::A
    W::NTuple{3, Float64}
    crop::Bool
    function SSIM(kernel::AbstractVector=SSIM_KERNEL, W::NTuple=SSIM_W; crop=false)
        ndims(kernel) == 1 || throw(ArgumentError("only 1-d kernel is valid"))
        issymetric(kernel) || @warn "SSIM kernel is assumed to be symmetric"
        all(W .>= 0) || throw(ArgumentError("(α, β, γ) should be non-negative, instead it's $(W)"))
        kernel = centered(kernel)
        new{typeof(kernel)}(kernel, W, crop)
    end
end

# default values from [1]
const SSIM_KERNEL = KernelFactors.gaussian(1.5, 11) # kernel
const SSIM_W = (1.0, 1.0, 1.0) # (α, β, γ)

Base.:(==)(ia::SSIM, ib::SSIM) = ia.kernel == ib.kernel && ia.W == ib.W && ia.crop == ib.crop

# api
# By default we don't crop the padding boundary to match the ssim result from
# MATLAB Image Processing Toolbox, which is used more broadly than the original
# implementaion [2] (written in MATLAB as well).
# -- Johnny Chen <johnnychen94@hotmail.com>
(iqi::SSIM)(x, ref) = mean(_ssim_map(iqi, x, ref))

@doc (@doc SSIM)
assess_ssim(x, ref; crop=false) = SSIM(;crop=crop)(x, ref)

# Parameters `(K₁, K₂)` are used to avoid instability when denominator is very
# close to zero. Different from origianl implementation [2], we don't make it
# public since the ssim result is insensitive to these parameters according to
# [1].
# -- Johnny Chen <johnnychen94@hotmail.com>
const SSIM_K = (0.01, 0.03)

# SSIM is defined only for gray images,
# RGB images are treated as 3d gray images,
# other Color3 images are converted to RGB first.
function _ssim_map(iqi::SSIM,
                   x::Union{GenericGrayImage, AbstractArray{<:AbstractRGB}},
                   ref::Union{GenericGrayImage, AbstractArray{<:AbstractRGB}},
                   peakval = 1.0,
                   K = SSIM_K)
    if size(x) ≠ size(ref)
        err = ArgumentError("images should be the same size, instead they're $(size(x))-$(size(ref))")
        throw(err)
    end
    if axes(x) ≠ axes(ref)
        x = OffsetArrays.no_offset_view(x)
        ref = OffsetArrays.no_offset_view(ref)
    end
    α, β, γ = iqi.W
    C₁, C₂ = @. (peakval * K)^2
    C₃ = C₂/2

    T = promote_type(float(eltype(ref)), float(eltype(x)))
    x = of_eltype(T, x)
    ref = of_eltype(T, ref)

    if [α, β, γ] ≈ [1.0, 1.0, 1.0]
        ssim_map = __ssim_map_fast(x, ref, iqi.kernel, C₁, C₂; crop=iqi.crop)
    else
        l, c, s = __ssim_map_general(x, ref, iqi.kernel, C₁, C₂, C₃; crop=iqi.crop)
        # ensure that negative numbers in s are not being raised to powers less than 1
        # this is a non-standard implementation and it could vary from implementaion to implementaion
        γ < 1.0 && (s = max.(s, zero(eltype(s))))
        ssim_map = @. l^α * c^β * s^γ # equation (12) in [1]
    end
    return ssim_map
end

function _ssim_map(iqi::SSIM,
          x::AbstractArray{<:Color3},
          ref::AbstractArray{<:Color3},
          peakval = 1.0, K = SSIM_K)
    C = ccolor(RGB, floattype(eltype(x)))
    _ssim_map(iqi, of_eltype(C, x), of_eltype(C, ref), peakval, K)
end

# helpers
function issymetric(kernel)
    origin = first(axes(kernel, 1))
    center = (length(kernel)-1) ÷ 2 + origin
    kernel[origin:center] ≈ kernel[end:-1:center]
end

function __ssim_map_fast(x, ref, window, C₁, C₂; crop)
    μx², μxy, μy², σx², σxy, σy² = _ssim_statistics(x, ref, window; crop=crop)
    # this is a special case when [α, β, γ] ≈ [1.0, 1.0, 1.0]
    # equation (13) in [1]
    return @. ((2μxy + C₁)*(2σxy + C₂))/((μx²+μy²+C₁)*(σx² + σy² + C₂))
end

function __ssim_map_general(x, ref, window, C₁, C₂, C₃; crop)
    μx², μxy, μy², σx², σxy, σy² = _ssim_statistics(x, ref, window; crop = crop)

    T = eltype(σx²)
    σx_σy = @. sqrt( max(σx²*σy², zero(T) ))
    l = @. (2μxy + C₁)/(μx² + μy²) # equation (6) in [1]
    c = @. (2σx_σy + C₂)/(σx² + σy² + C₂) # equation (9) in [1]
    s = @. (σxy + C₃)/(σx_σy + C₃) # equation (10) in [1]

    # MS-SSIM needs these, so we don't multiply them together here
    return l, c, s
end

function _ssim_statistics(x::GenericImage, ref::GenericImage, window; crop)
    # For RGB and other Color3 images, we don't slide the window at the color channel.
    # In other words, these characters will be calculated channelwisely
    window = kernelfactors(Tuple(repeated(window, ndims(ref))))

    region = map(window, axes(x)) do w, a
        o = length(w) ÷ 2
        # Even if crop=true, it crops only when image is larger than window
        length(a) > length(w) ? (first(a)+o:last(a)-o) : a
    end
    R = crop ? CartesianIndices(region) : CartesianIndices(x)

    # don't slide the window in the channel dimension
    μx = view(imfilter(x,   window, "symmetric"), R) # equation (14) in [1]
    μy = view(imfilter(ref, window, "symmetric"), R) # equation (14) in [1]
    μx² = _mul.(μx, μx)
    μy² = _mul.(μy, μy)
    μxy = _mul.(μx, μy)
    σx² = view(imfilter(_mul.(x,   x  ), window, "symmetric"), R) .- μx² # equation (15) in [1]
    σy² = view(imfilter(_mul.(ref, ref), window, "symmetric"), R) .- μy² # equation (15) in [1]
    σxy = view(imfilter(_mul.(x,   ref), window, "symmetric"), R) .- μxy # equation (16) in [1]

    # after that, channel dimension can be treated generically so we expand them here
    return channelview.((μx², μxy, μy², σx², σxy, σy²))
end

# _ssim_statistics does not expected to work with other Color3 types
@inline _mul(x::Number, y::Number) = x * y
@inline _mul(x::C, y::C) where C <: Union{AbstractGray, AbstractRGB} = mapc(*, x, y)


using Pkg
Pkg.activate(@__DIR__)
using RCall
using SigmaRidgeRegression
using StatsBase
using Random
using MLJ
using Tables
using LaTeXTabulars
using LaTeXStrings
using BenchmarkTools

# Workaround until the following is resolved
# https://github.com/alan-turing-institute/MLJBase.jl/issues/428
# Else the session will hang upon printing the X matrix
# and wrapping it as Tables.MatrixTable
function Base.show(io::IO, tbl::Tables.MatrixTable)
    return Base.show(io, typeof(tbl))
end

# Load CLL dataset
cll_data = CLLData.load();
foldid = cll_data.foldid;
X = cll_data.X;
y = cll_data.y;
ngr = cll_data.ngr

resample_ids = [(findall(foldid .!= k), findall(foldid .== k)) for k in 1:10]

groups = GroupedFeatures(ngr)

Random.seed!(1)
rand_idx = sample(1:121, 121; replace=false)
Xdrug_resample = X[rand_idx, 1:305]
Xgaussian = randn(121, 100)
Xnoise = [X Xdrug_resample Xgaussian]

groups_noise = GroupedFeatures([ngr; 305; 100])

X_table = MLJ.table(X)
X_plus_noise = MLJ.table(Xnoise)

function single_table_line(
    X, y, resampling, _model, model_name; tuning_name=nothing, sigdigits=3
)
    ridge_machine = machine(_model, X, y)
    fit!(ridge_machine)
    best_param = ridge_machine.report.best_param
    if isnothing(tuning_name)
        tuning_string = ""
    else
        best_param = round(best_param; sigdigits=sigdigits)
        tuning_string = L"%$(tuning_name) = %$(best_param)"
    end

    λs = round.(deepcopy(ridge_machine.report.best_λs), sigdigits=sigdigits)

    ridge_benchmark = @benchmark fit!(machine($(_model), $(X), $(y)))

    time_loo = round(mean(ridge_benchmark.times) / (1e9); sigdigits=sigdigits)

    eval_ridge = evaluate!(ridge_machine; resampling=resampling, measure=rms)
    @show eval_ridge.measurement
    _rmse = round(eval_ridge.measurement[1]; sigdigits=sigdigits)

    return [model_name, tuning_string, λs..., time_loo, _rmse]
end

loo_single_ridge = LooRidgeRegressor(;
    ridge=SingleGroupRidgeRegressor(; groups=groups, center=true, scale=true)
)
loo_single_ridge_noise = LooRidgeRegressor(;
    ridge=SingleGroupRidgeRegressor(; groups=groups_noise, center=true, scale=true)
)

loo_sigmaridge = LooSigmaRidgeRegressor(; groups=groups, center=true, scale=true)
loo_sigmaridge_noise = LooSigmaRidgeRegressor(;
    groups=groups_noise, center=true, scale=true
)

loo_multi_ridge = LooRidgeRegressor(;
    ridge=MultiGroupRidgeRegressor(; groups=groups, center=true, scale=true),
    rng=MersenneTwister(1),
)

loo_multi_ridge_noise = LooRidgeRegressor(;
    ridge=MultiGroupRidgeRegressor(; groups=groups_noise, center=true, scale=true),
    rng=MersenneTwister(1),
)

cv_glasso = TunedSeagull(;groups=groups, center=true, scale=true)

cv_glasso_noise = TunedSeagull(;groups=groups_noise, center=true, scale=true)

line_single_ridge = single_table_line(
    X_table, y, resample_ids, loo_single_ridge, "\textbf{Single Ridge}"
)
line_single_ridge_noise = single_table_line(
    X_plus_noise, y, resample_ids, loo_single_ridge_noise, "\textbf{Single Ridge}"
)

line_sigma_ridge = single_table_line(
    X_table,
    y,
    resample_ids,
    loo_sigmaridge,
    L"$\sigmacv$\textbf{-Ridge}";
    tuning_name=L"\sigmacv",
)
line_sigma_ridge_noise = single_table_line(
    X_plus_noise,
    y,
    resample_ids,
    loo_sigmaridge_noise,
    L"$\sigmacv$\textbf{-Ridge}";
    tuning_name=L"\sigmacv",
)

line_multi_ridge = single_table_line(
    X_table, y, resample_ids, loo_multi_ridge, "\textbf{Multi Ridge}"
)

line_multi_ridge_noise = single_table_line(
    X_plus_noise, y, resample_ids, loo_multi_ridge_noise, "\textbf{Multi Ridge}"
)

line_glasso = single_table_line(
    X_table,
    y,
    resample_ids,
    cv_glasso,
    L"\textbf{Group Lasso}";
    tuning_name=L"$\widehat{\lambda}^{gl}",
)

line_glasso_noise = single_table_line(
    X_plus_noise,
    y,
    resample_ids,
    cv_glasso_noise,
    L"\textbf{Group Lasso}";
    tuning_name=L"$\widehat{\lambda}^{gl}",
)

tbl_spec = Tabular("lllllll")
line1 = [
    "",
    "Tuning",
    L"$\widehat{\lambda}_{\text{Drugs}}$",
    L"\widehat{\lambda}_{\text{Methyl}}",
    L"\widehat{\lambda}_{\text{RNA}}",
    "Time (s)",
    "10-fold RMSE",
]
lines = [line1, Rule(), line_sigma_ridge, line_single_ridge, line_multi_ridge, line_glasso]
latex_tabular("cll_analysis.tex", tbl_spec, lines)

tbl_spec2 = Tabular("lllllllll")
line1 = [
    "",
    "Tuning",
    L"$\widehat{\lambda}_{\text{Drugs}}$",
    L"\widehat{\lambda}_{\text{Methyl}}",
    L"\widehat{\lambda}_{\text{RNA}}",
    L"\widehat{\lambda}_{\text{Noise1}}",
    L"\widehat{\lambda}_{\text{Noise2}}",
    "Time (s)",
    "10-fold RMSE",
]
lines = [
    line1,
    Rule(),
    line_sigma_ridge_noise,
    line_single_ridge_noise,
    line_multi_ridge_noise,
    line_glasso_noise,
]
latex_tabular("cll_analysis_noise.tex", tbl_spec2, lines)


export ConstantHomotopy

"""
    ConstantHomotopy(F::AbstractSystem)

Intepret the system `F` as the (constant) homotopy ``H(x,t)=F(x)``.
"""
struct ConstantHomotopy{S<:AbstractSystem} <: AbstractHomotopy
    system::S
end

Base.size(H::ConstantHomotopy) = size(H.system)

struct ConstantHomotopyCache{HC<:AbstractSystemCache} <: AbstractHomotopyCache
    cache::HC
end

cache(H::ConstantHomotopy, x, t) = ConstantHomotopyCache(cache(H.system, x))

const CH = ConstantHomotopy
const CHC = ConstantHomotopyCache

@propagate_inbounds evaluate!(u, H::CH, x, t, c::CHC) = evaluate!(u, H.system, x, c.cache)
@propagate_inbounds jacobian!(U, H::CH, x, t, c::CHC) = jacobian!(U, H.system, x, c.cache)
@propagate_inbounds function dt!(u, H::CH, x, t, c::CHC)
    u .= zero(eltype(u))
    u
end
@propagate_inbounds function evaluate_and_jacobian!(u, U, H::CH, x, t, c::CHC)
    evaluate_and_jacobian!(u, U, H.system, x, c.cache)
end
@propagate_inbounds function jacobian_and_dt!(U, u, H::CH, x, t, c::CHC)
    jacobian!(U, H.system, x, c.cache)
    u .= zero(eltype(u))
    nothing
end
evaluate(H::CH, x, t, c::CHC) = evaluate(H.system, x, c.cache)
jacobian(H::CH, x, t, c::CHC) = jacobian(H.system, x, c.cache)
function dt(H::CH, x, t, c::CHC)
    u = evaluate(H.system, x, c.cache)
    u .= zero(eltype(u))
    u
end


#---------------------------------------------------------
# # [SPECTrecon rotation](@id 2-rotate)
#---------------------------------------------------------

# This page explains the image rotation portion of the Julia package
# [`SPECTrecon.jl`](https://github.com/JeffFessler/SPECTrecon.jl).

# ### Setup

# Packages needed here.

using SPECTrecon
using MIRTjim: jim, prompt
using Plots: scatter, scatter!, plot!, default
default(markerstrokecolor=:auto, markersize=3)

# The following line is helpful when running this example.jl file as a script;
# this way it will prompt user to hit a key after each figure is displayed.

isinteractive() ? jim(:prompt, true) : prompt(:draw);

# ### Overview

# The first step in SPECT image forward projection
# is to rotate each slice of a 3D image volume
# to the appropriate view angle.
# In principle
# one could use any of numerous candidate interpolation methods
# for this task.
# However, because emission images are nonnegative
# and maximum likelihood methods
# for SPECT image reconstruction
# exploit that nonnegativity,
# it is desirable to use interpolators
# that preserve nonnegativity.
# This constraint rules out quadratic and higher B-splines,
# including the otherwise attractive cubic B-spline methods.
# On the other hand, nearest-neighbor interpolation
# (equivalent to 0th-order B-splines)
# does not provide adequate image quality.
# This leaves 1st-order interpolation methods
# as the most viable options.

# This package supports two 1st-order linear interpolators:
# * 2D bilinear interpolation,
# * a 3-pass rotation method based on 1D linear interpolation.

# Because image rotation is done repeatedly
# (for every slice of both the emission image and the attenuation map,
# for both projection and back-projection,
# and for multiple iterations),
# it is important for efficiency
# to use mutating methods
# rather than to repeatedly make heap allocations.

# Following other libraries like
# [FFTW.jl](https://github.com/JuliaMath/FFTW.jl),
# the rotation operations herein start with a `plan`
# where work arrays are preallocated
# for subsequent use.
# The `plan` is a `Vector` of `PlanRotate` objects:
# one for each thread.
# (Parallelism is across slices for a 3D image volume.)
# The number of threads defaults to `Threads.nthreads()`.

# ### Example

# Start with a 3D image volume (just 2 slices here for simplicity).

T = Float32 # work with single precision to save memory
image = zeros(T, 64, 64, 2)
image[30:50,20:30,1] .= 1
image[25:28,20:40,2] .= 1
jim(image, "Original image")

# Now plan the rotation
# by specifying
# * the image size `nx` (it must be square, so `ny=nx` implicitly)
# * the `DataType` used for the work arrays.

plan2 = plan_rotate(size(image, 1); T)

# Here are the internals for the plan for the first thread:

plan2[1]

# With this `plan` preallocated, now we can rotate the image volume,
# specifying the rotation angle in radians:

result2 = similar(image) # allocate memory for the result
imrotate!(result2, image, π/6, plan2) # mutates the first argument
jim(result2, "Rotated image by π/6 (2D bilinear)")

# The default, shown above, uses 2D bilinear interpolation for rotation.
# That default is the recommended approach because it is faster.

# Here is the 3-pass 1D interpolation approach,
# included mainly for checking consistency
# with the historical ASPIRE approach used in Matlab version of MIRT.

plan1 = plan_rotate(size(image, 1); T, method=:one)

# Here are the plan internals for the first thread:

plan1[1]

# The results of rotation using 3-pass 1D interpolation look quite similar:

result1 = similar(image)
imrotate!(result1, image, π/6, plan1)
jim(result1, "Rotated image by π/6 (3-pass 1D)")

# Here are the difference images for comparison.

jim(result1 - result2, "Difference images")

# ### Adjoint

# To ensure adjoint consistency between SPECT forward- and back-projection,
# there is also an adjoint routine:

adj2 = similar(result2)
imrotate_adj!(adj2, result2, π/6, plan2)
jim(adj2, "Adjoint image rotation (2D)")

adj1 = similar(result1)
imrotate_adj!(adj1, result1, π/6, plan1)
jim(adj1, "Adjoint image rotation (3-pass 1D)")

# The adjoint is *not* the same as the inverse
# so one does not expect the output here to match the original image!

# ### LinearMap

# One can form a linear map corresponding to image rotation using `LinearMapAA`.
# An operator like this may be useful
# as part of a motion-compensated image reconstruction method.

using LinearMapsAA: LinearMapAA

nx = 20 # small size for illustration
r1 = plan_rotate(nx; T, nthread = 1, method=:two)[1]
r2 = plan_rotate(nx; T, nthread = 1, method=:one)[1]
idim = (nx,nx)
odim = (nx,nx)
forw! = (y,x) -> imrotate!(y, x, π/6, r1)
back! = (x,y) -> imrotate_adj!(x, y, π/6, r1)
A1 = LinearMapAA(forw!, back!, (prod(odim),prod(idim)); T, odim, idim)
forw! = (y,x) -> imrotate!(y, x, π/6, r2)
back! = (x,y) -> imrotate_adj!(x, y, π/6, r2)
A2 = LinearMapAA(forw!, back!, (prod(odim),prod(idim)); T, odim, idim)

Afull1 = Matrix(A1)
Aadj1 = Matrix(A1')
Afull2 = Matrix(A2)
Aadj2 = Matrix(A2')
jim(cat(dims=3, Afull1', Aadj1', Afull2', Aadj2'), "Linear map for 2D rotation and its adjoint")

# The following verify adjoint consistency:
@assert Afull1' ≈ Aadj1
@assert Afull2' ≈ Aadj2

# Applying this linear map to a 2D or 3D image performs rotation:

image2 = zeros(nx,nx); image2[4:6, 5:13] .= 1
jim(cat(dims=3, image2, A2 * image2), "Rotation via linear map: 2D")

# Here is 3D too.
# The `A2 * image3` here uses the advanced "operator" feature of
# [LinearMapsAA.jl](https://github.com/JeffFessler/LinearMapsAA.jl).

image3 = cat(dims=3, image2, image2')
jim(cat(dims=4, image3, A2 * image3), "Rotation via linear map: 3D")

# Examine row and column sums of linear map

scatter(xlabel="pixel index", ylabel="row or col sum")
scatter!(vec(sum(Afull1, dims=1)), label="dim1 sum1", marker=:x)
scatter!(vec(sum(Afull1, dims=2)), label="dim2 sum1", marker=:square)
scatter!(vec(sum(Afull2, dims=1)), label="dim1 sum2")
scatter!(vec(sum(Afull2, dims=2)), label="dim2 sum2")


# Main GaussianProcess type

abstract type GPBase end

"""
    predict_f(gp::GPBase, X::Matrix{Float64}[; full_cov::Bool = false])

Return posterior mean and variance of the Gaussian Process `gp` at specfic points which are
given as columns of matrix `X`. If `full_cov` is `true`, the full covariance matrix is
returned instead of only variances.
"""
function predict_f(gp::GPBase, x::MatF64; full_cov::Bool=false)
    size(x,1) == gp.dim || throw(ArgumentError("Gaussian Process object and input observations do not have consistent dimensions"))
    if full_cov
        return _predict(gp, x)
    else
        ## Calculate prediction for each point independently
            μ = Array{Float64}(undef, size(x,2))
            σ2 = similar(μ)
        for k in 1:size(x,2)
            m, sig = _predict(gp, x[:,k:k])
            μ[k] = m[1]
            σ2[k] = max(diag(sig)[1], 0.0)
        end
        return μ, σ2
    end
end

# 1D Case for prediction
predict_f(gp::GPBase, x::VecF64; full_cov::Bool=false) = predict_f(gp, x'; full_cov=full_cov)

function make_posdef!(m::MatF64)
    n = size(m, 1)
    size(m, 2) == n || throw(ArgumentError("Covariance matrix must be square"))
    for _ in 1:10 # 10 chances
        try
            return m, cholesky(m)
        catch
            # that wasn't (numerically) positive definite,
            # so let's add some weight to the diagonal
            ϵ = 1e-6 * tr(m) / n
            @inbounds for i in 1:n
                m[i, i] += ϵ
            end
        end
    end
    m, cholesky(m)
end

"""
    tolerant_PDMat(Σ::Matrix{Float64})

Try to encode covariance matrix `Σ` as positive definite matrix of type `PDMat`.

Sometimes covariance matrices of Gaussian processes are positive definite mathematically
but have negative eigenvalues numerically. To resolve this issue, small weights are added
to the diagonal (and hereby all eigenvalues are raised by that amount mathematically)
until all eigenvalues are positive numerically.
"""
function tolerant_PDMat(Sigma_raw::MatF64)
    Sigma_raw, chol = make_posdef!(Sigma_raw)
    PDMat(Sigma_raw, chol)
end


import Flux

"""
    Dense(in::Integer, out::Integer; omega_0 = 30, is_first = false)

Create a `Dense` layer with SIREN initialization, parameters `W` and `b`, and scaling factor omega_0.

    y = sin.(omega_0 *(W * x .+ b))

The input `x` must be a vector of length `in`, or a batch of vectors represented
as an `in × N` matrix. The out `y` will be a vector or batch of length `out`.

# Examples
```jldoctest; setup = :(using Random; Random.seed!(0))
julia> d = SIREN.Dense(5, 2)
SIREN.Dense(5, 2, omega_0=30)

julia> d(rand(5))
2-element Array{Float64,1}:
 -0.9926777049551361
 -0.9019978122219484```
"""
struct Dense{F,S,T}
    omega_0::F
    W::S
    b::T
end

function Dense(in::Integer, out::Integer;
               omega_0=30, is_first::Bool = false)
  return Dense(omega_0, SIREN_init(omega_0, is_first, out, in), zeros(out))
end

Flux.@functor Dense

function (a::Dense)(x::AbstractArray)
  W, b, omega_0 = a.W, a.b, a.omega_0
  sin.(omega_0 * (W*x .+ b))
end

function Base.show(io::IO, l::Dense)
  print(io, "SIREN.Dense(", size(l.W, 2), ", ", size(l.W, 1))
  print(io, ", omega_0=", l.omega_0)
  print(io, ")")
end

## FIGURE OUT WHAT THIS MEANS
# Try to avoid hitting generic matmul in some simple cases
# Base's matmul is so slow that it's worth the extra conversion to hit BLAS
(a::Dense{<:Any,W})(x::AbstractArray{T}) where {T <: Union{Float32,Float64}, W <: AbstractArray{T}} =
  invoke(a, Tuple{AbstractArray}, x)

(a::Dense{<:Any,W})(x::AbstractArray{<:AbstractFloat}) where {T <: Union{Float32,Float64}, W <: AbstractArray{T}} =
  a(T.(x))
##
"""
    outdims(l::Dense, isize)

Calculate the output dimensions given the input dimensions, `isize`.

```julia
m = Dense(10, 5)
outdims(m, (5, 2)) == (5,)
outdims(m, (10,)) == (5,)
```
"""
outdims(l::Dense, isize) = (size(l.W)[1],)


function parse_map(s)
    lines = readlines(IOBuffer(s))
    is_seat = falses((length(lines) + 2, length(lines[1]) + 2))
    for i in 1:length(lines)
        for j in 1:length(lines[1])
            is_seat[i + 1, j + 1] = lines[i][j] == 'L'
        end
    end
    return is_seat
end

function count_neighbors(is_occupied::BitArray{2}, i::Int, j::Int)::Int
    count::Int = 0
    for i_offset in (-1, 0, 1)
        for j_offset in (-1, 0, 1)
            if i_offset == 0 && j_offset == 0
            elseif is_occupied[i + i_offset, j + j_offset]
                count += 1
            end
        end
    end
    return count
end

function run(s)
    # Your code here
    is_seat = parse_map(s)
    is_occupied = copy(is_seat)
    old_is_occupied = copy(is_seat)
    n_rows, n_cols = size(is_seat)

    has_changed::Bool = true
    new_free::Bool = false
    new_occupied::Bool = false
    n_neighbors::Int = 0
    while has_changed
        # is_seat .& .!is_occupied = not_occupied
        has_changed = false
        old_is_occupied .= is_occupied
        for i in 2:n_rows - 1
            for j in 2:n_cols - 1
                if is_seat[i, j]
                    n_neighbors = count_neighbors(old_is_occupied, i, j)
                    new_occupied = is_seat[i, j] & !old_is_occupied[i, j] & (n_neighbors == 0)
                    new_free = old_is_occupied[i, j] & (n_neighbors >= 4)
                    if new_occupied
                        has_changed = true
                        is_occupied[i, j] = true
                    elseif new_free
                        has_changed = true
                        is_occupied[i, j] = false
                    end
                end
            end
        end
    end
    return sum(is_occupied)
end

#########################################

function main()
    res, time, memory = @timed run(ARGS[1])
    println("_duration:$(time * 1000)")
    println(res)
end

main()


using Printf

function parse_map(s)
    lines = split(s, "\n")
    map_arr = zeros(Bool, length(lines), length(lines[1]))
    for (row_idx, l) in enumerate(lines)
        for (col_idx, c) in enumerate(l)
            map_arr[row_idx, col_idx] = (c == '#')
        end
    end
    return map_arr
end

function count_trees_on_slope(map_arr, down, right)
    tree_counter = 0
    x, y = 1, 1
    n_rows, n_cols = size(map_arr)
    while x <= n_rows
        if map_arr[x, y]
            tree_counter += 1
        end
        x += down
        y = (y + right) % n_cols
        if y == 0
            y = n_cols
        end
    end
    return tree_counter
end

function run(s)
    # Your code here
    map_arr = parse_map(s)
    return count_trees_on_slope(map_arr, 1, 3)
end

#########################################

function main()
    res, time, memory = @timed run(ARGS[1])
    println("_duration:$(time * 1000)")
    println(res)
end

main()


# random initialization

function randinit(X, k::Integer; normalize::Bool=false, zeroh::Bool=false)
    p, n = size(X)
    T = eltype(X)

    W = rand(T, p, k)
    if normalize
        normalize1_cols!(W)
    end

    H = zeroh ? zeros(T, k, n) : rand(T, k, n)
    return W, H
end

# NNDSVD: Non-Negative Double Singular Value Decomposition
#
# Reference
# ----------
#   C. Boutsidis, and E. Gallopoulos. SVD based initialization: A head
#   start for nonnegative matrix factorization. Pattern Recognition, 2007.
#
function _nndsvd!(X, W, Ht, inith::Bool, variant::Int)

    p, n = size(X)
    k = size(W, 2)
    T = eltype(W)

    # compute SVD
    (U, s, V) = svd(X, thin=true)

    # main loop
    v0 = variant == 0 ? zero(T) :
         variant == 1 ? convert(T, mean(X)) : convert(T, mean(X) * 0.01)

    for j = 1:k
        x = view(U,:,j)
        y = view(V,:,j)
        xpnrm, xnnrm = posnegnorm(x)
        ypnrm, ynnrm = posnegnorm(y)
        mp = xpnrm * ypnrm
        mn = xnnrm * ynnrm

        vj = v0
        if variant == 2
            vj *= rand(T)
        end

        if inith
            if mp >= mn
                scalepos!(view(W,:,j), x, 1 / xpnrm, vj)
                scalepos!(view(Ht,:,j), y, s[j] * mp / ypnrm, vj)
            else
                ss = sqrt(s[j] * mn)
                scaleneg!(view(W,:,j), x, 1 / xnnrm, vj)
                scaleneg!(view(Ht,:,j), y, s[j] * mn / ynnrm, vj)
            end
        else
            if mp >= mn
                scalepos!(view(W,:,j), x, 1 / xpnrm, vj)
            else
                scaleneg!(view(W,:,j), x, 1 / xnnrm, vj)
            end
        end
    end
end

function nndsvd(X, k::Integer; zeroh::Bool=false, variant::Symbol=:std)

    p, n = size(X)
    T = eltype(X)
    ivar = variant == :std ? 0 :
           variant == :a   ? 1 :
           variant == :ar  ? 2 :
           throw(ArgumentError("Invalid value for variant"))

    W = Array{T,2}(p, k)
    H = Array{T,2}(k, n)
    if zeroh
        Ht = reshape(view(H,:,:), (n, k))
        _nndsvd!(X, W, Ht, false, ivar)
        fill!(H, 0)
    else
        Ht = Array{T,2}(n, k)
        _nndsvd!(X, W, Ht, true, ivar)
        for j = 1:k
            for i = 1:n
                H[j,i] = Ht[i,j]
            end
        end
    end
    return (W, H)
end

function posnegnorm(x::AbstractArray{T}) where T
    pn = zero(T)
    nn = zero(T)
    for i = 1:length(x)
        @inbounds xi = x[i]
        if xi > zero(T)
            pn += abs2(xi)
        else
            nn += abs2(xi)
        end
    end
    return (sqrt(pn), sqrt(nn))
end

function scalepos!(y, x, c::T, v0::T) where T<:Number
    @inbounds for i = 1:length(y)
        xi = x[i]
        if xi > zero(T)
            y[i] = xi * c
        else
            y[i] = v0
        end
    end
end

function scaleneg!(y, x, c::T, v0::T) where T<:Number
    @inbounds for i = 1:length(y)
        xi = x[i]
        if xi < zero(T)
            y[i] = - (xi * c)
        else
            y[i] = v0
        end
    end
end


x = create_var(3, 2)
r = repmat(x, 4, 5)
test_expr(r, (x,))

x = create_var(1, 1)
r = repmat(x, 1, 1)
test_expr(r, (x,))

x = create_var(3, 3)
y = x + [1 2 3; 4 5 6; 7 8 9]
r = repmat(y, 1, 3)
test_expr(r, (x,))

x = create_var(3, 3)
y = x + [1 2 3; 4 5 6; 7 8 9]
r = repmat(y, 4, 1)
test_expr(r, (x,))

info("All repmat tests passed")


@testset verbose = true "utility tests" begin
    @safetestset "Discard Burnin" begin
        using DifferentialEvolutionMCMC, Test, Random, Parameters, Distributions
        Random.seed!(29542)
        N = 10
        k = rand(Binomial(N, .5))
        data = (N = N,k = k)

        prior_loglike(θ) = logpdf(Beta(1, 1), θ)

        sample_prior() = rand(Beta(1, 1))

        bounds = ((0,1),)

        function loglike(data, θ)
            return logpdf(Binomial(data.N, θ), data.k)
        end

        names = (:θ,)

        model = DEModel(;
            sample_prior,
            prior_loglike,
            loglike,
            data,
            names
        )

        burnin = 1500
        n_iter = 3000

        de = DE(; sample_prior, Np=4, bounds, burnin, discard_burnin=false)

        chains = sample(model, de, n_iter)
        @test length(chains) == n_iter

        de = DE(; sample_prior, Np=4, bounds, burnin)
        chains = sample(model, de, n_iter)
        @test length(chains) == burnin
    end

    @safetestset "reset!" begin
        using DifferentialEvolutionMCMC, Test
        import DifferentialEvolutionMCMC: reset!

        p1 = Particle(Θ = [[.7,.5,.1],.4,.6])
        p2 = Particle(Θ = [[.9,.8,.5],.7,.8])
        idx = [[true,false,false],false,true]
        reset!(p1, p2, idx)

        @test p1.Θ[1][1] ≠ p2.Θ[1][1]
        @test p1.Θ[1][2] == p2.Θ[1][2]
        @test p1.Θ[1][3] == p2.Θ[1][3]
        @test p1.Θ[2] == p2.Θ[2]
        @test p1.Θ[3] ≠ p2.Θ[3]

        p1 = Particle(Θ = [[.7 .5;.1 .3],.4,.6])
        p2 = Particle(Θ = [[.9 .8;.5 .2],.7,.8])
        idx = [[true false; false true],false,true]
        reset!(p1, p2, idx)

        @test p1.Θ[1][1,1] ≠ p2.Θ[1][1,1]
        @test p1.Θ[1][1,2] == p2.Θ[1][1,2]
        @test p1.Θ[1][2,1] == p2.Θ[1][2,1]
        @test p1.Θ[1][2,2] ≠ p2.Θ[1][2,2]
        @test p1.Θ[1][3] == p2.Θ[1][3]
        @test p1.Θ[2] == p2.Θ[2]
        @test p1.Θ[3] ≠ p2.Θ[3]
    end

    @testset "projection" begin
        using Test, DifferentialEvolutionMCMC
        import DifferentialEvolutionMCMC: project

        # for example, see: https://www.youtube.com/watch?v=xSu-0xcRBo8&ab_channel=FireflyLectures
        proj(x1, x2) = (x1' * x2) / (x2' * x2) * x2
        x1 = [-1.0,4.0]
        x2 = [2.0,7.0]
        p1 = Particle(Θ = x1)
        p2 = Particle(Θ = x2)
        p3 = project(p1, p2)
        correct = proj(x1, x2)

        @test correct ≈ [52/53,182/53]
        @test p3.Θ ≈ correct

        x1 = [[-1.0,],4.0]
        x2 = [[2.0,],7.0]
        p1 = Particle(Θ = x1)
        p2 = Particle(Θ = x2)
        p3 = project(p1, p2)
        @test vcat(p3.Θ...) ≈ correct
    end

    @safetestset "Migration" begin
        using DifferentialEvolutionMCMC, Test, Random, Parameters, Distributions
        import DifferentialEvolutionMCMC: select_groups, select_particles, shift_particles!, sample_init

        Random.seed!(459) #Random.seed!(0451) #

        function equal(p1::Particle, p2::Particle)
            fields = fieldnames(Particle)
            for field in fields
                if getfield(p1, field) != getfield(p2, field)
                    println(field)
                    return false
                end
            end
            return true
        end

        N = 10
        k = rand(Binomial(N, .5))
        data = (N = N,k = k)

        prior_loglike(θ) = logpdf(Beta(1, 1), θ)

        sample_prior() = rand(Beta(1, 1))

        bounds = ((0,1),)

        function loglike(data, θ)
            return logpdf(Binomial(data.N, θ), data.k)
        end

        names = (:θ,)

        model = DEModel(;
            sample_prior,
            prior_loglike,
            loglike,
            data,
            names
        )

        burnin = 1500
        n_iter = 3000

        de = DE(; sample_prior, Np=4, bounds, burnin, discard_burnin=false)

        groups = sample_init(model, de, n_iter)
        sub_group = select_groups(de, groups)
        c_groups = deepcopy(groups)
        c_sub_group = deepcopy(sub_group)
        p_idx,particles = select_particles(sub_group)
        c_particles = deepcopy(particles)
        shift_particles!(sub_group, p_idx, particles)
        gidx = 1:length(sub_group)
        cidx = circshift(gidx, 1)
        cp_idx = circshift(p_idx, 1)
        for (i,c,p,cp) in zip(gidx, p_idx, cidx, cp_idx)
            @test sub_group[i][c].Θ == c_sub_group[p][cp].Θ
        end
        ridx = [4,3]
        for (i,c,r) in zip(1:2, p_idx[1:2], ridx)
            @test sub_group[i][c].Θ == groups[r][c].Θ
        end
    end

    @safetestset "particle operations" begin
        using DifferentialEvolutionMCMC, Test, Random, Distributions
        Random.seed!(29542)

        p1 = Particle(Θ = [1.,2.0])
        pr = p1 + 2
        @test pr.Θ ≈ [3,4]

        p1 = Particle(Θ = [1.,2.0])
        pr = p1 * 4
        @test pr.Θ ≈ [4,8]

        p1 = Particle(Θ = [1.,2.0])
        p2 = Particle(Θ = [1.,2.0])
        pr = p1 + p2
        @test pr.Θ ≈ [2,4]

        p1 = Particle(Θ = [1.,2.0])
        p2 = Particle(Θ = [1.,2.0])
        pr = 3 * (p1 + p2)
        @test pr.Θ ≈ [6,12]

        p1 = Particle(Θ = [1.,2.0])
        p2 = Particle(Θ = [-2.,3.0])
        pr = 3 * (p1 - p2)
        @test pr.Θ ≈ [9,-3]

        p1 = Particle(Θ = [1.,2.0])
        p2 = Particle(Θ = [-2.,3.0])
        p3 = Particle(Θ = [-2.,3.0])
        pr = 3 * (p1 - p2) + p3
        @test pr.Θ ≈ [7,0]

        p1 = Particle(Θ = [1.,2.0])
        b = Uniform(-.1, .1)
        pr = p1 + b
        @test p1.Θ ≈ pr.Θ atol = .2 # cummulative error
        @test p1.Θ ≠ pr.Θ
    end
end


include("utils.jl")

m = 1
n = 21

a = constant(0.01*Array(LinRange(1,2,n+1)))
b = constant(0.02*Array(LinRange(1,2,n+1)))
σn = 50*Array(LinRange(1,2,n+1))
τ = 30*Array(LinRange(1,2,n+1))
η = 4.7
v0 = 1e-6
f0 = 0.6
Ψ0 = Array(LinRange(1,2,n+1)) * 1.01 * f0
Dc = 0.01

u_left_bd_S2 = Array(LinRange(1,2,n+1))
v_bd_S2 = rate_state_friction(a, u_left_bd_S2, v0, Ψ0, σn, τ, η, 1.0) # t+ Δt + δΔt

# du is defined on the left side
function StateRate(Ψ, v)
    v = abs(v)
    s = v/(2v0)
    o = exp(-Ψ/a)
    f = a * (Ψ/a + log(s+sqrt(s^2+o^2)))
    # f = a* tf.asinh(v/(2v0)*exp(Ψ/a))
    Δψ = -v/Dc*(f-f0+(b-a)*log(v/v0))
end

dpsi_S3  = StateRate(Ψ0, v_bd_S2) # t + Δt

sess = Session();
run(sess, v_bd_S2)

# run(sess, dpsi_S3)


function svdtrunc(A,eps,mR)

U,S,V = svd(full(A));
if min(size(A))>1 && S(1) > 1e-15
    idx = find(find(diag(S)/S(1)>eps)<=mR);
    U = U(:,idx);
    S = S(idx,idx);
    V = V(:,idx);
else
    U = zeros(size(A,1),0);
    S = zeros(0);
    V = zeros(size(A,2),0);
end

end


function toh06a()
#
#  generalization of K.C. Toh and L. N. Trefethen
#
    p = ones(11)
    p = conv(p,p)
    z = [0.84125353283118 + 0.54064081745560im
         0.84125353283118 - 0.54064081745560im
         0.41541501300189 + 0.90963199535452im
         0.41541501300189 - 0.90963199535452im
        -0.95949297361449 + 0.28173255684143im
        -0.95949297361449 - 0.28173255684143im
        -0.65486073394529 + 0.75574957435426im
        -0.65486073394529 - 0.75574957435426im
        -0.14231483827329 + 0.98982144188093im
        -0.14231483827329 - 0.98982144188093im];
    z = [z 2*ones(10)]

    p, PolyZeros(z)
end


using Flux, DiffEqFlux, DifferentialEquations, Plots

function lotka_volterra(du,u,p,t)
  x, y = u
  α, β, δ, γ = p
  du[1] = dx = α*x - β*x*y
  du[2] = dy = -δ*y + γ*x*y
end
function lotka_volterra_noise(du,u,p,t)
  du[1] = 0.1u[1]
  du[2] = 0.1u[2]
end
prob = SDEProblem(lotka_volterra,lotka_volterra_noise,[1.0,1.0],(0.0,10.0))
p = param([2.2, 1.0, 2.0, 0.4])
function predict_fd_sde()
  diffeq_fd(p,sol->sol[1,:],101,prob,SOSRI(),saveat=0.1)
end
loss_fd_sde() = sum(abs2,x-1 for x in predict_fd_sde())

data = Iterators.repeated((), 100)
opt = ADAM(0.1)
cb = function ()
  display(loss_fd_sde())
  display(plot(solve(remake(prob,p=Flux.data(p)),SOSRI(),saveat=0.1),ylim=(0,6)))
end
# Display the SDE with the current parameter values.
cb()
Flux.train!(loss_fd_sde, [p], data, opt, cb = cb)


using Printf
struct ContinuousHopfieldNet <: HopfieldNet
    s::Vector{Float64} # State
    W::Matrix{Float64} # Weights
end

function ContinuousHopfieldNet(n::Integer)
    s = ones(n)
    W = zeros(n, n)
    ContinuousHopfieldNet(s, W)
end

# Perform one asynchronous update on randomly selected neuron
function update!(net::ContinuousHopfieldNet)
    i = rand(1:length(net.s))
    net.s[i] = tanh(dot(net.W[:, i], net.s))
    return
end

function Base.show(io::IO, net::ContinuousHopfieldNet)
    @printf io "A continuous Hopfield net with %d neurons\n" length(net.s)
end


using Test
using Dates
using Statistics

# @testset "evaporation" begin
#     for i in 1:5000
#         Ra = rand(10:0.1:40)
#         Tmin= rand(0.1:0.1:20)
#         Tmax = rand(Tmin:0.1:25)
#         Kt = 0.17
#         Evap = 0.0135 * Kt * ((Tmin + Tmax)/2 + 17.8) * (Tmax - Tmin)^0.5 * Ra
#
#         @test round(epot_hargreaves(Tmin, Tmax, Kt, Ra), digits=12) == round(Evap, digits=12)
#     end
# end

@testset "evaporation_thorn" begin
    for i in 1:100000
        year = rand(1980:2020)[1]
        month = rand(1:12)[1]
        Begin_Month = Dates.firstdayofmonth(Date(year,month,1))
        End_Month = Dates.lastdayofmonth(Date(year,month,1))
        Timeseries = Begin_Month:Day(1):End_Month
        Temp = rand(length(Timeseries)) * 10
        # check if function for monthly mean temp works
        mean_Temp = monthlytemp(Timeseries, Temp)[1]
        @test round(mean_Temp, digits =10) == round(mean(Temp), digits = 10)
        #check if it also works for negative temperatures
        Temp = rand(length(Timeseries)) * -10
        # check if function for monthly mean temp works
        mean_Temp = monthlytemp(Timeseries, Temp)[1]
        @test round(mean_Temp, digits =10) == round(mean(Temp), digits = 10)

        # #check if calculation for annual heat index works
        i = rand(1:10)
        Temp = rand(12*i) * 10
        annual_heatindex = heatindex(Temp)
        @test length(annual_heatindex) == i
        Temp = rand(12) * 10
        Heatindex = sum((Temp ./ 5).^1.514)
        @test Heatindex == heatindex(Temp)[1]

        #for negative values

        i = rand(1:10)
        Temp = rand(12*i) * -10
        annual_heatindex = heatindex(Temp)
        @test length(annual_heatindex) == i
        Temp = rand(12) * 10
        Heatindex = sum((Temp ./ 5).^1.514)
        @test Heatindex == heatindex(Temp)[1]

        #check if total calculation for potential evaporation at each timestep is correct
        Heatindex = rand(1:0.01:10)[1]
        Sunshine = rand(9:0.1:15)[1]
        days = Int(rand(28:31)[1])
        Temp = rand(0:0.1:30)[1]
        alpha = 675 * 10^(-9) * Heatindex^3 - 771 * 10^(-7) * Heatindex^2 + 1792 * 10^(-5) * Heatindex + 0.49239
        # monthly Epot in mm per month
        Epot = 16 * ((10 * Temp) /Heatindex )^alpha * Sunshine/12 * days/ 30
        # has to be converted to daily values
        @test round(Epot/days, digits =10) == round(epot_thornthwaite(Temp, Heatindex, days, Sunshine), digits = 10)
        # if temperature below zero EPot = 0
        Temp = rand(-20:0.1:0)[]
        @test epot_thornthwaite(Temp, Heatindex, days, Sunshine) == 0

        # test the whole potential evaporation function
        # year = rand(1980:2020)[1]
        # Begin_Month = Dates.firstdayofyear(Date(year,1,1))
        # End_Month = Dates.lastdayofyear(Date(year,1,1))
        # Timeseries = Begin_Month:Day(1):End_Month
        # Temp = rand(length(Timeseries)) * 10
        # sunhours = rand(12) * 10
        # Evaporation = getEpot_Daily_thornthwaite(Temp, Timeseries, sunhours)
        # # calculation here
        # mean_Temp = monthlytemp(Timeseries, Temp)[1]
        # annual_heatindex = heatindex(mean_Temp)
        # epot_thornthwaite(Temp, Heatindex, days, Sunshine)
    end
end


using LinearAlgebra
using CompScienceMeshes
using SauterSchwab3D

const pI = point(1,0,0)
const pII = point(0,0,0)
const pIII = point(0.5,0.7,0)
const pIV = point(0.8,-0.3,0)

const P = simplex(pI,pIII,pII)
const Q = simplex(pI,pIV,pII)

const sing = SauterSchwab3D.singularity_detection(P,Q)

Accuracy2 = 30
ce_ref = CommonEdge4D(sing,SauterSchwab3D._legendre(Accuracy2,0.0,1.0))

function integrand(x,y)
      return ((x-pI)'*(y-pII))*exp(-im*1*norm(x-y))/(4pi*norm(x-y))
end

function INTEGRAND(u,v)
   if u[2]< 0.0 || u[2]>1-u[1] || u[1]<0.0 || u[1]>1.0
      println(u)
   end
   if v[2]< 0.0 || v[2]>1-v[1] || v[1]<0.0 || v[1]>1.0
      println(v)
   end
   n1 = neighborhood(P,u)
   n2 = neighborhood(Q,v)
   x = cartesian(n1)
   y = cartesian(n2)
   output = integrand(x,y)*jacobian(n1)*jacobian(n2)
   return(output)
end

print("Ref: ")
ref = sauterschwab_parameterized(INTEGRAND, ce_ref)
println(ref)
println()

res_tp =[]
res_sp =[]
res_gm = []
n1 = []
n2 = []
n3 = []
for i in 2:1:20
   Accuracy = i
   ce = CommonEdge4D(sing,SauterSchwab3D._legendre(Accuracy,0.0,1.0))

   int_tp = sauterschwab_parameterized(INTEGRAND, ce)

   num_pts = 5*length(ce.qps)^4
   push!(n1,num_pts)

   push!(res_tp,int_tp)
end

for i in 2:1:7
   Accuracy = i
   ce_s = CommonEdge4D_S(sing,(SauterSchwab3D._legendre(Accuracy,0.0,1.0),
                          SauterSchwab3D._shunnham2D(Accuracy)))

   int_sp = sauterschwab_parameterized(INTEGRAND, ce_s)

   num_pts = length(ce_s.qps[1])^2*length(ce_s.qps[2]) + 4*length(ce_s.qps[2])^2
   push!(n2,num_pts)
   push!(res_sp,int_sp)
end

for i in 2:1:12
   Accuracy = i
   ce_gm = CommonEdge4D_S(sing,(SauterSchwab3D._legendre(Accuracy,0.0,1.0),
                          SauterSchwab3D._grundmannMoeller2D(2*Accuracy-1)))

   int_gm = sauterschwab_parameterized(INTEGRAND, ce_gm)

   num_pts = length(ce_gm.qps[1])^2*length(ce_gm.qps[2]) + 4*length(ce_gm.qps[2])^2
   push!(n3,num_pts)
   push!(res_gm,int_gm)
end

err_tp = norm.(res_tp.-ref)/norm(ref)
err_sp = norm.(res_sp.-ref)/norm(ref)
err_gm = norm.(res_gm.-ref)/norm(ref)

println(err_tp)
println(err_sp)
println(err_gm)

using Plots
plot( yaxis=:log,xaxis=:log, fontfamily="Times")
plot!(n1,err_tp, label="Gauss Tensor-Product",markershape=:circle)
plot!(n2,err_sp, label="Simplex Tensor-Product",markershape=:rect)
#plot!(n3,err_gm, label="Simplex-Product GM",markershape=:x)
plot!(xlims=(3e1,1e5),ylims=(1e-7,1e-1))
plot!(xlabel="Quad. points/Func. evals.", ylabel="Rel. Error.", title="Common Edge 4D",legend=:bottomleft)

using BenchmarkTools

ref = -0.0028991793711368725 + 0.0012868587156426375im

ce = CommonEdge4D(sing,SauterSchwab3D._legendre(5,0.0,1.0))

int_tp = sauterschwab_parameterized(INTEGRAND, ce)

num_pts = 5*length(ce.qps)^4
println("#Pts: ",num_pts)
err_tp = norm.(int_tp.-ref)/norm(ref)
println("Rel Err: ",err_tp)
@time for i in 1:100 sauterschwab_parameterized(INTEGRAND, ce) end

ce_s = CommonEdge4D_S(sing,(SauterSchwab3D._legendre(5,0.0,1.0),
SauterSchwab3D._shunnham2D(5)))

int_sp = sauterschwab_parameterized(INTEGRAND, ce_s)

num_pts = length(ce_s.qps[1])^2*length(ce_s.qps[2]) + 4*length(ce_s.qps[2])^2

println("#Pts: ",num_pts)
err_sp = norm.(int_sp.-ref)/norm(ref)
println("Rel Err: ",err_sp)
@time   for i in 1:100  sauterschwab_parameterized(INTEGRAND, ce_s) end

#=

ce_gm = CommonEdge4D_S((SauterSchwab3D._legendre(5,0.0,1.0),
SauterSchwab3D._grundmannMoeller2D(2*5-1)))

int_gm = sauterschwab_parameterized(INTEGRAND, ce_gm)

num_pts = length(ce_gm.qps[1])^2*length(ce_gm.qps[2]) + 4*length(ce_gm.qps[2])^2

println("#Pts: ",num_pts)
err_gm = norm.(int_gm.-ref)/norm(ref)
println("Rel Err: ",err_gm)
=#


# This is part of Calendars.jl. See the copyright note there.
# ======================== Islamic dates ====================

# The start of the Islamic calendar is AH 1, Muharram 1.
# That is equivalent to JD 622, July 16. We do not support
# dates prior to this date (no 'proleptic' Islamic calendar).
# Day number of the start of the Islamic calendar.
# AH-0001-01-01 = JD-0622-07-16 ~ DN#227015 = EpochIslamic
# AH-1111-11-11 = JD-1700-04-19 ~ DN#620667
# AH-6666-12-29 = JD-7089-11-28 ~ DN#2589222
const EpochIslamic = 227015
const ValidYearsIslamic = (1, 9666) # by convention

# True if year is an Islamic leap year.
function isLeapYearIslamic(year::DPart)
    return rem(11 * year + 14, 30) < 11
end

# Last month of Islamic year.
function LastMonthOfYearIslamic(year::DPart)
    isLeapYearIslamic(year) ? 13 : 12
end

# Last day in month during year on the Islamic calendar.
function LastDayOfMonthIslamic(year::DPart, month::DPart)
    b = (rem(month, 2) == 1
      || (month == 12 && isLeapYearIslamic(year)))
    return b ? 30 : 29
end

# Is the date a valid Islamic date?
function isValidDateIslamic(cd::CDate, warn=true)
    cal, year, month, day = cd
    println(cd)
    val = ( cal == AH
        && (ValidYearsIslamic[1] <= year && year <= ValidYearsIslamic[2])
        && (1 <= month && month <= LastMonthOfYearIslamic(year))
        && (1 <= day && day <= LastDayOfMonthIslamic(year, month)))
    ! val && warn && @warn(Warning(cd))
    return val
end

# Represents the day number a valid Islamic date?
function isValidDateIslamic(dn::DPart, warn=true)
    val = EpochIslamic <= dn
    ! val && warn && @warn(Warning(dn))
    return val
end

# Return the days this year so far.
# Does not depend on 'year' but kept in the signature.
function DayOfYearIslamic(year::DPart, month::DPart, day::DPart)
    # days so far this month plus ...
    day += ( 29 * (month - 1)   # days so far...
        + div(month, 2)         #   ...this year
    )
    return day
end

# Computes the day number from a valid Islamic date.
function DayNumberIslamic(year::DPart, month::DPart, day::DPart)

    day = DayOfYearIslamic(year, month, day)
    days = (EpochIslamic - 1      # days before start of calendar
         + 354 * (year - 1)       # non-leap days in prior years
         + div(3 + 11 * year, 30) # leap days in prior years
    )
    return day + days
end

# Computes the day number from a valid Islamic date.
DayNumberIslamic(d::CDate) = DayNumberIslamic(d[2], d[3], d[4])

# Computes the Islamic date from the day number.
function DateIslamic(dn::DPart)
    ! isValidDateIslamic(dn, false) && return InvalidDate

    # Search forward year by year from approximate year.
    year = div(dn - EpochIslamic, 355)
    while dn >= DayNumberIslamic(year + 1, 1, 1)
        year += 1
    end

    # Search forward month by month from Muharram.
    month = 1
    while dn > DayNumberIslamic(year, month,
                  LastDayOfMonthIslamic(year, month))
        month += 1
    end

    day = dn - DayNumberIslamic(year, month, 1) + 1

    return (AH, year, month, day)::CDate
end

# Return the day number of the first day in the given Islamic year.
function YearStartIslamic(year::DPart)
    return DayNumberIslamic(year, 1, 1)
end

# Return the day number of the last day in the given Islamic year.
function YearEndIslamic(year::DPart)
    return DayNumberIslamic(year + 1, 1, 1) - 1
end

# Return the number of months in the given Islamic year.
function MonthsInYearIslamic(year::DPart)
    return LastMonthOfYearIslamic(year)
end

# Return the number of days in the given Islamic year.
function DaysInYearIslamic(year::DPart)
    return YearStartIslamic(year + 1) - YearStartIslamic(year)
end


@doc doc"""
sma{Float64}(x::Vector{Float64}, n::Int64=10)

Simple moving average
""" ->
function sma{Float64}(x::Vector{Float64}, n::Int64=10)
	return runmean(x, n, false)
end

@doc doc"""
trima{Float64}(x::Vector{Float64}, n::Int64=10; ma::Function=sma, args...)

Triangular moving average
""" ->
function trima{Float64}(x::Vector{Float64}, n::Int64=10; ma::Function=sma)
    return ma(ma(x, n), n)
end

@doc doc"""
wma{Float64}(x::Vector{Float64}, n::Int64=10; wts::Vector{Float64}=collect(1:n)/sum(1:n))

Weighted moving average
""" ->
function wma{Float64}(x::Vector{Float64}, n::Int64=10; wts::Vector{Float64}=collect(1:n)/sum(1:n))
    @assert n<size(x,1) && n>0 "Argument n out of bounds"
	out = fill(NaN, size(x,1))
    @inbounds for i = n:size(x,1)
        out[i] = (wts' * x[i-n+1:i])[1]
    end
    return out
end

@doc doc"""
ema{Float64}(x::Vector{Float64}, n::Int64=10; alpha::Float64=2.0/(n+1.0), wilder::Bool=false)

Exponential moving average
""" ->
function ema{Float64}(x::Vector{Float64}, n::Int64=10; alpha::Float64=2.0/(n+1), wilder::Bool=false)
    @assert n<size(x,1) && n>0 "Argument n out of bounds."
    if wilder
        alpha = 1.0/n
    end
    out = zeros(x)
    i = first(find(!isnan(x)))
    out[1:n+i-2] = NaN
    out[n+i-1] = mean(x[i:n+i-1])
    @inbounds for i = n+i:size(x,1)
        out[i] = alpha * (x[i] - out[i-1]) + out[i-1]
    end
    return out
end

@doc doc"""
mma{Float64}(x::Vector{Float64}, n::Int64=10)

Modified moving average
""" ->
function mma{Float64}(x::Vector{Float64}, n::Int64=10)
    return ema(x, n, alpha=1.0/n)
end

@doc doc"""
dema{Float64}(x::Vector{Float64}, n::Int64=10; alpha=2.0/(n+1), wilder::Bool=false)

Double exponential moving average
""" ->
function dema{Float64}(x::Vector{Float64}, n::Int64=10; alpha=2.0/(n+1), wilder::Bool=false)
    return 2.0 * ema(x, n, alpha=alpha, wilder=wilder) -
        ema(ema(x, n, alpha=alpha, wilder=wilder),
            n, alpha=alpha, wilder=wilder)
end

@doc doc"""
tema{Float64}(x::Vector{Float64}, n::Int64=10; alpha=2.0/(n+1), wilder::Bool=false)

Triple exponential moving average
""" ->
function tema{Float64}(x::Vector{Float64}, n::Int64=10; alpha=2.0/(n+1), wilder::Bool=false)
    return 3.0 * ema(x, n, alpha=alpha, wilder=wilder) -
        3.0 * ema(ema(x, n, alpha=alpha, wilder=wilder),
                  n, alpha=alpha, wilder=wilder) +
        ema(ema(ema(x, n, alpha=alpha, wilder=wilder),
                n, alpha=alpha, wilder=wilder),
            n, alpha=alpha, wilder=wilder)
end

@doc doc"""
mama{Float64}(x::Vector{Float64}, fastlimit::Float64=0.5, slowlimit::Float64=0.05)

MESA adaptive moving average (developed by John Ehlers)
""" ->
function mama{Float64}(x::Vector{Float64}, fastlimit::Float64=0.5, slowlimit::Float64=0.05)
    n = size(x,1)
    out = zeros(n,2)
    smooth = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
    detrend = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
    Q1 = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
    I1 = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
    I2 = [0.0, 0.0]
    Q2 = [0.0, 0.0]
    Re = [0.0, 0.0]
    Im = [0.0, 0.0]
    per = [0.0, 0.0]
    sper = [0.0, 0.0]
    phase = [0.0, 0.0]
    jI = 0.0
    jQ = 0.0
    dphase = 0.0
    alpha = 0.0
    a = 0.0962
    b = 0.5769
    @inbounds for i = 13:n
        # Smooth and detrend price movement ====================================
        smooth[7] = (4*x[i] + 3*x[i-1] + 2*x[i-2] + x[i-3]) * 0.1
        detrend[7] = (0.0962*smooth[7]+0.5769*smooth[5]-0.5769*smooth[3]-0.0962*smooth[1]) * (0.075*per[1]+0.54)
        # Compute InPhase and Quandrature components ===========================
        Q1[7] = (0.0962*detrend[7]+0.5769*detrend[5]-0.5769*detrend[3]-0.0962*detrend[1]) * (0.075*per[1]+0.54)
        I1[7] = detrend[4]
        # Advance phase of I1 and Q1 by 90 degrees =============================
        jQ = (0.0962*Q1[7]+0.5769*Q1[5]-0.5769*Q1[3]-0.0962*Q1[1]) * (0.075*per[1]+0.54)
        jI = (0.0962*I1[7]+0.5769*I1[5]-0.5769*I1[3]-0.0962*I1[1]) * (0.075*per[1]+0.54)
        # Phasor addition for 3 bar averaging ==================================
        Q2[2] = Q1[7] + jI
        I2[2] = I1[7] - jQ
        # Smooth I & Q components before applying the discriminator ============
        Q2[2] = 0.2 * Q2[2] + 0.8 * Q2[1]
        I2[2] = 0.2 * I2[2] + 0.8 * I2[1]
        # Homodyne discriminator ===============================================
        Re[2] = I2[2] * I2[1] + Q2[2]*Q2[1]
        Im[2] = I2[2] * Q2[1] - Q2[2]*I2[1]
        Re[2] = 0.2 * Re[2] + 0.8*Re[1]
        Im[2] = 0.2 * Im[2] + 0.8*Im[1]
        if (Im[2] != 0.0) & (Re[2] != 0.0)
            per[2] = 360.0/atan(Im[2]/Re[2])
        end
        if per[2] > 1.5 * per[1]
            per[2] = 1.5*per[1]
        elseif per[2] < 0.67 * per[1]
            per[2] = 0.67 * per[1]
        end
        if per[2] < 6.0
            per[2] = 6.0
        elseif per[2] > 50.0
            per[2] = 50.0
        end
        per[2] = 0.2*per[2] + 0.8*per[1]
        sper[2] = 0.33*per[2] + 0.67*sper[1]
        if I1[7] != 0.0
            phase[2] = atan(Q1[7]/I1[7])
        end
        dphase = phase[1] - phase[2]
        if dphase < 1.0
            dphase = 1.0
        end
        alpha = fastlimit / dphase
        if alpha < slowlimit
            alpha = slowlimit
        end
        out[i,1] = alpha*x[i] + (1.0-alpha)*out[i-1,1]
        out[i,2] = 0.5*alpha*out[i,1] + (1.0-0.5*alpha)*out[i-1,2]
        # Reset/increment array variables
        smooth = [smooth[2:7]; smooth[7]]
        detrend = [detrend[2:7]; detrend[7]]
        Q1 = [Q1[2:7]; Q1[7]]
        I1 = [I1[2:7]; I1[7]]
        I2[1] = I2[2]
        Q2[1] = Q2[2]
        Re[1] = Re[2]
        Im[1] = Im[2]
        per[1] = per[2]
        sper[1] = sper[2]
        phase[1] = phase[2]
    end
    out[1:32,:] = NaN
    return out
end

@doc doc"""
hma{Float64}(x::Vector{Float64}, n::Int64=20)

Hull moving average
""" ->
function hma{Float64}(x::Vector{Float64}, n::Int64=20)
    return wma(2 * wma(x, Int64(round(n/2.0))) - wma(x, n), Int64(trunc(sqrt(n))))
end

@doc doc"""
swma{Float64}(x::Vector{Float64}, n::Int64)

Sine-weighted moving average
""" ->
function swma{Float64}(x::Vector{Float64}, n::Int64=10)
    @assert n<size(x,1) && n>0 "Argument n out of bounds."
    w = sin(collect(1:n) * 180.0/6.0)  # numerator weights
    d = sum(w)  # denominator = sum(numerator weights)
    out = zeros(x)
    out[1:n-1] = NaN
    @inbounds for i = n:size(x,1)
        out[i] = sum(w .* x[i-n+1:i]) / d
    end
    return out
end

@doc doc"""
kama{Float64}(x::Vector{Float64}, n::Int64=10, nfast::Float64=0.6667, nslow::Float64=0.0645)

Kaufman adaptive moving average
""" ->
function kama{Float64}(x::Vector{Float64}, n::Int64=10, nfast::Float64=0.6667, nslow::Float64=0.0645)
    @assert n<size(x,1) && n>0 "Argument n out of bounds."
    @assert nfast>0.0 && nfast<1.0 "Argument nfast out of bounds."
    @assert nslow>0.0 && nslow<1.0 "Argument nslow out of bounds."
    dir = diffn(x, n)  # price direction := net change in price over past n periods
    vol = runsum(abs(diffn(x,1)), n, false)  # volatility/noise
    er = abs(dir) ./ vol  # efficiency ratio
    ssc = er * (nfast-nslow) + nslow  # scaled smoothing constant
    sc = ssc .^ 2  # smoothing constant
    # initiliaze result variable
    out = zeros(x)
    i = first(find(!isnan(x)))
    out[1:n+i-2] = NaN
    out[n+i-1] = mean(x[i:n+i-1])
    @inbounds for i = n+1:size(x,1)
        out[i] = out[i-1] + sc[i]*(x[i]-out[i-1])
    end
    return out
end

@doc doc"""
alma{Float64}(x::Vector{Float64}, n::Int64=9, offset::Float64=0.85, sigma::Float64=6.0)

Arnaud-Legoux moving average
""" ->
function alma{Float64}(x::Vector{Float64}, n::Int64=9, offset::Float64=0.85, sigma::Float64=6.0)
    @assert n<size(x,1) && n>0 "Argument n out of bounds."
    @assert sigma>0.0 "Argument sigma must be greater than 0."
    @assert offset>=0.0 && offset<=1 "Argument offset must be in (0,1)."
    out = zeros(x)
    out[1:n-1] = NaN
    m = floor(offset*(float(n)-1.0))
    s = float(n) / sigma
    w = exp(-(((0.0:-1.0:-float(n)+1.0)-m).^2.0) / (2.0*s*s))
    wsum = sum(w)
    if wsum != 0.0
        w = w ./ wsum
    end
    @inbounds for i = n:length(x)
        out[i] = sum(x[i-n+1] .* w)
    end
    return out
end


function solve()
    k = parse(Int, readline())
    a, b = [parse(Int, x) for x in split(readline())]

    if (a ÷ k == b ÷ k) && a % k != 0
        "NG"
    else
        "OK"
    end
end

println(solve())


abstract type ABasic end

struct Node
    data::String
    class::Type{<:ABasic}
    args::NTuple{N,Node} where N  # array does not work since Node([]]) == Node([]]) returns false
end
Node(data::String, class::Type{<:ABasic}) = Node(data, class, ())
data(self::Node) = self.data
class(self::Node) = self.class
args(self::Node) = self.args

function Base.show(io::IO, self::Node)
    _show(io, self, class(self))
end
_show(io::IO, self::Node, ::Type{<:Any}) = print(io, "$(Meta.parse("($(args(self)[1])) $(data(self)) ($(args(self)[2]))"))")

abstract type ANode <: ABasic end
abstract type AAtom <: ANode end
abstract type AOperator <: ANode end

abstract type AUnknown <: AAtom end
abstract type AVariable <: AUnknown end
struct Variable{T} <: AVariable where T
end
_show(io::IO, self::Node, ::Type{<:AAtom}) = print(io, data(self))

# Only used for truth table purposes
abstract type AWild <: AUnknown end
struct Wild{T} <: AWild where T
end

abstract type ACall <: AOperator end
function (f::Node)(x::Node)
    return Node("()", ACall, (f, x))
end
function (f::Node)(Vararg...)
    return Node("()", f, Vararg)
end
_show(io::IO, self::Node, ::Type{<:ACall}) = print(io, "$(Meta.parse("$(args(self)[1])($(args(self)[2]))"))")

abstract type ASingleton <: AAtom end
abstract type ASingletonStatement <: ASingleton end
abstract type ASingletonSet <: ASingleton end

abstract type ASingletonExpression <: ASingleton end

wild_symbol(name::String, set::Type{<:ASingletonSet}) = Node(name, Wild{set})
wild_symbol(name::String, set::Node) = Node(name, Wild{class(set)})

symbol(name::String, set::Type{<:ASingletonSet}) = Node(name, Variable{set})
symbol(name::String, set::Node) = Node(name, Variable{class(set)})

# External types
abstract type ANonBasic <: ABasic end
convert(::Type{Node}, x::Number) = Node(String(x), ANonBasic)

abstract type ALogicOperator <: AOperator end
abstract type AAnd <: ALogicOperator end
struct And <: AAnd
end
∧(x::Node, y::Node) = Node("∧", AAnd, (x, y))

abstract type AForAll <: ALogicOperator end
ForAll(variable::Node, set::Node, statement::Node) = Node("A", AForAll, (variable, set, statement))

abstract type AOr <: ALogicOperator end
struct Or <: AOr
end
∨(x::Node, y::Node) = Node("∨", AOr, (x, y))

abstract type AExists <: ALogicOperator end
Exists(variable::Node, set::Node, statement::Node) = Node("E", AOr, (variable, set, statement))

abstract type ANot <: ALogicOperator end
struct Not <: ANot
end
~(x::Node) = Node("~", ANot, (x,))
_show(io::IO, self::Node, ::Type{<:ANot}) = print(io, "$(Meta.parse("$(data(self))($(args(self)[1]))"))")

abstract type AImplies <: ALogicOperator end
struct Implies <: AImplies
end
⟹(x::Node, y::Node) = Node("⟹", AImplies, (x, y))

abstract type AEquivalent <: ALogicOperator end
struct Equivalent <: AEquivalent
end
≡(x::Node, y::Node) = Node("≡", AEquivalent, (x, y))

abstract type ASubset <: ALogicOperator end
struct Subset <: ASubset
end
⊂(x::Node, y::Node) = Node("⊂", ASubset, (x, y))

abstract type AElementOf <: ALogicOperator end
struct ElementOf <: AElementOf
end
∈(x::Node, y::Node) = Node("∈", AElementOf, (x, y))

abstract type ASetOperator <: AOperator end
abstract type ASetUnion <: ASetOperator end
struct SetUnion <: ASetUnion
end
∪(x::Node, y::Node) = Node("∪", ASetUnion, (x, y))

abstract type ASetIntersection <: ASetOperator end
struct SetIntersection <: ASetIntersection
end
∩(x::Node, y::Node) = Node("∩", ASetIntersection, (x, y))

abstract type ASetDifference <: ASetOperator end
struct SetDifference <: ASetDifference
end

abstract type AConditionSet <: ASetOperator end
struct ConditionSet <: AConditionSet
end
ConditionSet(x::Node, condition::Node) = Node("|", AConditionSet, (x, condition))  # {x | condition}
_show(io::IO, self::Node, ::Type{<:AConditionSet}) = print(io, "$(Meta.parse("ConditionSet($(args(self)[1]), $(args(self)[2]))"))")

abstract type AExpressionOperator <: AOperator end


function loadgrid(filename::AbstractString)
    len = length(readline(filename))
    A = falses(len, len)
    for (y, line) in Iterators.enumerate(eachline(filename))
        for (x, c) in Iterators.enumerate(line)
            if c == '#'
                A[y, x] = true
            end
        end
    end
    A
end

function prettyprint(A)
    for row in eachrow(A)
        println((b ? '#' : '.' for b in row)...)
    end
end

function eachneighbour(A, index)
    (
        A[i+index]
        for i in CartesianIndices(((-1:1), (-1:1)))
        if !iszero(i) && checkbounds(Bool, A, i + index)
    )
end

function step(A, len = 1)
    for _ in 1:len
        A = [
            begin
                ns = sum(eachneighbour(A, i))
                ns == 3 || ns == 2 && A[i]
            end
            for i in CartesianIndices(A)
        ]
    end
    A
end

part1() = count(step(loadgrid("input.txt"), 100))

function step2(A, len = 1)
    h, w = size(A)
    A[1, 1] = true
    A[1, w] = true
    A[h, 1] = true
    A[h, w] = true
    for _ in 1:len
        A = [
            begin
                ns = sum(eachneighbour(A, i))
                ns == 3 || ns == 2 && A[i]
            end
            for i in CartesianIndices(A)
        ]
        A[1, 1] = true
        A[1, w] = true
        A[h, 1] = true
        A[h, w] = true
    end
    A
end

part2() = count(step2(loadgrid("input.txt"), 100))


using DelimitedFiles

# slist = [
    #     "BFFFBBFRRR",
    #     "FFFBBBFRRR",
    #     "BBFFBBFRLL"
    # ]

function to_rowcol(pass, nrow, ncol)
    r1 = 0
    r2 = nrow - 1
    c1 = 0
    c2 = ncol - 1
    # println(seat)
    for d in pass[1:c2]
        dr = r2 - r1

        if d == 'F'
            r2 = floor(r2 - dr/2)
        elseif d == 'B'
            r1 = ceil(r1 + dr/2)
        end
    end

    for d in pass[c2+1:10]
        dc = c2 - c1
        if d == 'L'
            c2 = floor(c2 - dc/2)
        elseif d == 'R'
            c1 = ceil(c1 + dc/2)
        end
    end

    return((r1, c1, (r1*8 + c1)))
end

function process_seat(chart, r, c)
    r = Int(r) + 1
    c = Int(c) + 1

    chart[r,c] = false
    return(chart)
end

function find_seat(chart, nrows)
    for r in 1:nrows
        # println(chart[i,:])
        row = chart[r,:]
        if count(row) == 1
            c = findfirst(x->x==true, row)
            return(r-1 , c-1)
        end
    end
end

nrow = 128
ncol = 8

global chart = trues((nrow, ncol))

slist = readdlm("./seats.dat")

global max_id = 0
for pass in slist
    x,y,id = to_rowcol(pass, nrow, ncol)
    global chart = process_seat(chart, x,y)
    global max_id = max(id, max_id)
end

println("Maximum ", max_id)
myr, myc = find_seat(chart, nrow)
println(myr*8 + myc)


abstract type CalciumScore end

"""
    function Integrated(
        vol,
        I=sum(vol),
        N=length(vol)
        )

# Arguments
- vol: region of interest
- I: integrated intensity of the entire `vol`
- N: total number of voxels contained in the entire `vol`

# Citation
'Accurate quantification of vessel cross-sectional area using CT
angiography: a simulation study' [Molloi, Johnson, Ding, Lipinski]
(DOI: 10.1007/s10554-016-1007-9)
"""
struct Integrated{T1<:AbstractArray,T2<:AbstractFloat} <: CalciumScore
    vol::T1
    I::T2
    N::T2
end

function Integrated(
    vol::AbstractArray,
    I::AbstractFloat=Float64.(sum(vol)),
    N::AbstractFloat=Float64.(length(vol)),
)
    return Integrated(I, N)
end

"""
    score(vol, S_Bkg, S_Obj, algorithm::Integrated)
    score(vol, S_Bkg, S_Obj, size, algorithm::Integrated)
    score(vol, S_Bkg, S_Obj, size, ρ, algorithm::Integrated)

Given a volume `vol` of interest, use the integrated intensity
approach to find the total number of object voxels/volume/mass
contained in `vol`

# Arguments
- vol: region of interest
- S_Bkg: pure background signal intensity in the `vol`
- S_Obj: pure object signal intensity in the `vol`
- size: size of the voxels in the given `vol`
- ρ: density of the given object of interest contained within
    the `vol`
- algorithm: the `Integrated` algorithm which accounts for
    noise and partial volume effect by integrating the
    the intensity of the entire volume `vol`

# Citation
'Accurate quantification of vessel cross-sectional area using CT
angiography: a simulation study' [Molloi, Johnson, Ding, Lipinski]
(DOI: 10.1007/s10554-016-1007-9)
"""
function score(S_Bkg, S_Obj, algorithm::Integrated)
    I = algorithm.I
    N = algorithm.N

    N_Obj = (I - (N * S_Bkg)) / (S_Obj - S_Bkg)
    return N_Obj
end

function score(S_Bkg, S_Obj, size, algorithm::Integrated)
    I = algorithm.I
    N = algorithm.N

    N_Obj = (I - (N * S_Bkg)) / (S_Obj - S_Bkg)
    Vol_Obj = N_Obj * size[1] * size[2] * size[3]
    return Vol_Obj
end

function score(S_Bkg, S_Obj, size, ρ, algorithm::Integrated)
    I = algorithm.I
    N = algorithm.N

    N_Obj = (I - (N * S_Bkg)) / (S_Obj - S_Bkg)
    Vol_Obj = N_Obj * size[1] * size[2] * size[3]
    Mass_Obj = Vol_Obj * ρ
    return Mass_Obj
end


# check the Harmonic numbers
#    this is mostly just a check that code is correct
#    most errors are arising because of errors in the functions being used
using Polylogarithms
using BenchmarkTools
using DataFrames, CSV
using PyPlot

H = Symbol("real(H_n)")

##############################################
# integer arguments
data1 = CSV.read("../data/harmonic_test_data_1.csv", DataFrame)
m = size(data1,1)
H1 = zeros(Float64, m)
error1 = zeros(Float64, m)
rel_error1 = zeros(Float64, m)
for i=1:m
    H1[i] = harmonic( data1[i,:n] )
    error1[i] = H1[i] -  data1[i,H]
    rel_error1[i] = error1[i]/data1[i,H]
end
figure(1)
clf()
semilogy( data1[!,:n], abs.(rel_error1) )
xlabel("n")
ylabel("relative absolute error")
ylim( 1.0e-17, 1.0e-15)
title("Harmonic numbers")
savefig("plots/harmonic_bench_1.pdf")

##############################################
# real arguments
data2 = CSV.read("../data/harmonic_test_data_2.csv", DataFrame)
m = size(data2,1)
H2 = zeros(Float64, m)
error2 = zeros(Float64, m)
rel_error2 = zeros(Float64, m)
for i=1:m
    H2[i] = harmonic( data2[i,:x] )
    error2[i] = H2[i] -  data2[i,H]
    rel_error2[i] = error2[i]/data2[i,H]
end
figure(2)
clf()
# plot( data2[!,:x], rel_error2 )
semilogy( data2[!,:x], abs.(rel_error2) )
k2 = findall( abs.(rel_error2) .> 1.0e-13 )
plot( data2[k2,:x], abs.(rel_error2[k2]), "r" )
xlabel("x")
ylabel("relative absolute error")
title("Harmonic numbers (real arguments)")
savefig("plots/harmonic_bench_2.pdf")

##############################################
# complex arguments
zr = Symbol("Re(z)")
zi = Symbol("Im(z)")
Hr = Symbol("Re(H(z))")
Hi = Symbol("Im(H(z))")
data3 = CSV.read("../data/harmonic_test_data_3.csv", DataFrame)
m = size(data3,1)
H3 = zeros(Complex{Float64}, m)
error3 = zeros(Complex{Float64}, m)
rel_error3 = zeros(Float64, m)
for i=1:m
    z = data3[i,zr] + im*data3[i,zi]
    Hz = data3[i,Hr] + im*data3[i,Hi]
    # println(" i = $i, z = $z, typeof(z)=$(typeof(z)), H = $(Hz)" )
    H3[i] = harmonic( z )
    error3[i] = H3[i] - Hz
    rel_error3[i] = abs(error3[i])/ abs( Hz )
end
figure(3)
clf()
# plot( data3[!,:x], rel_error3 )
semilogy( atan.(data3[!,zi],data3[!,zr])/pi, abs.(rel_error3) )
k3 = findall( abs.(rel_error3) .> 1.0e-13 )
plot( data3[k3,:x], abs.(rel_error3[k3]), "r" )
xlabel("θ/π")
ylabel("relative absolute error")
title("Harmonic numbers (complex arguments)")
savefig("plots/harmonic_bench_3.pdf")

##############################################
# integer arguments for generalize H
H = Symbol("real(H_{n,r})")
data4 = CSV.read("../data/harmonic_test_data_4.csv", DataFrame)
m = size(data4,1)
H4 = zeros(Float64, m)
error4 = zeros(Float64, m)
rel_error4 = zeros(Float64, m)
for i=1:m
    H4[i] = harmonic( data4[i,:n], data4[i,:r] )
    error4[i] = H4[i] -  data4[i,H]
    rel_error4[i] = error4[i]/data4[i,H]
end
figure(4)
clf()
semilogy( data4[!,:n] + 0.1*data4[!,:r], abs.(rel_error4) )
xlabel("n + 0.1*r")
ylabel("relative absolute error")
ylim( 1.0e-17, 1.0e-15)
title("Generalised harmonic numbers")
savefig("plots/harmonic_bench_4.pdf")

##############################################
# integer n, real r for generalized H
H = Symbol("real(H_{n,r})")
data5 = CSV.read("../data/harmonic_test_data_5.csv", DataFrame)
m = size(data5,1)
H5 = zeros(Float64, m)
error5 = zeros(Float64, m)
rel_error5 = zeros(Float64, m)
for i=1:m
    H5[i] = harmonic( data5[i,:n], data5[i,:r] )
    error5[i] = H5[i] -  data5[i,H]
    rel_error5[i] = error5[i]/data5[i,H]
end
figure(5)
clf()
semilogy( data5[!,:n] + 0.1*data5[!,:r], abs.(rel_error5) )
xlabel("n + 0.1*r")
ylabel("relative absolute error")
ylim( 1.0e-17, 1.0e-15)
title("Generalised harmonic numbers")
savefig("plots/harmonic_bench_5.pdf")


### ============================================================================
### A Lanczos Algorithm that uses the Dynamic Tridiagonal Matrices.
### Dynamic Iterative Lanczos: DIL
### ============================================================================
mutable struct DIL
    A::Function
    betas::Dict    # The sub and super diagonal of the matrix
    alphas::Dict   # The diagonal of the matrix
    k::Int64       # when k=1, the algorithm haven't started yet.
    q_store::Int64
    Aq
    Q::Dict
    dynamic_symtridiagonal::DynamicSymTridiagonal
    dynamic::Bool

    function DIL(
        A::Function,
        q1;
        q_store::Int64=typemax(Int64),
        dynamic::Bool=false
    )
        @assert q_store > 1 "storage must be at least 2. "
        this = new()
        this.A = A
        this.betas = Dict()
        this.alphas = Dict()
        this.k = 1
        this.q_store = q_store
        q1 = q1/norm(q1)
        this.Aq = A(q1)
        this.Q = Dict{Int64, typeof(this.Aq)}()
        this.Q[1] = q1
        this.alphas[1] = dot(this.Q[1], this.Aq)  # Alpha is computed in Advance!
        this.dynamic = dynamic
        if this.dynamic
            this.dynamic_symtridiagonal = DynamicSymTridiagonal(this.alphas[1])
        end
        return this
    end

    function DIL(A::AbstractArray, q1, q_store::Int64=typemax(Int64), dynamic::Bool=false)
        return DIL((x) -> A*x, q1, q_store=q_store, dynamic=dynamic)
    end

end

# Override the () operator.
"""
    It performs one iteration of the Lanczos Orthogonalization.
"""
function (this::DIL)()
    q = this.Q[this.k]
    Aq = this.Aq
    alphas = this.alphas
    if this.k == 1
        qNew = Aq
    else
        qPre = this.Q[this.k - 1]
        qNew = Aq - this.betas[this.k - 1]*qPre
    end
    qNew -= alphas[this.k]*q
    betaNew = norm(qNew)
    qNew /= betaNew

    if length(this.Q) > this.q_store
        delete!(this.Q, this.k + 1 - length(this.Q))
    end

    this.Q[this.k + 1] = qNew
    this.Aq = this.A(qNew)
    alphaNew = dot(qNew, this.Aq)
    this.alphas[this.k + 1] = alphaNew
    this.betas[this.k] = betaNew
    if this.dynamic
        this.dynamic_symtridiagonal(alphaNew, betaNew)
        EigenvaluesUpdate(this.dynamic_symtridiagonal)
        for (ritzVal, ritzVec) in this.dynamic_symtridiagonal.converged_this_step

        end
    end
    this.k += 1
    return qNew
end

"""
    It calls the () operator 3 j times, performing j iterations.
"""
function (this::DIL)(j::Int64)
    betas = Vector()
    for _ in 1: j
        push!(betas, this())
    end
    return betas
end

"""
    Get the Q matrix from the Lanczos Iterations, the number of columnos of the
    matrix depends on this.q_store.
    * k by k matrix returned.

"""
function GetQMatrix(this::DIL)
    Q = this.Q
    qs = Vector{typeof(Q[1])}()
    for Idx in 1: this.k
        push!(qs, this.Q[Idx])
    end
    return hcat(qs...)
end

"""
    Return the Tridiagonal matrix, take note thta, the matrix only supports
    floating point, and it's returned as a sparse matrix.
    * A (k - 1) by (k - 1) matrix is returned.

"""
function GetTMatrix(this::DIL)
    if this.k == 1
        return this.alphas[1]
    end
    return SymTridiagonal{Float64}(
        [real(this.alphas[Idx]) for Idx in 1: this.k],
        [real(this.betas[Idx]) for Idx in 1:this.k - 1]
    )
end

"""
    Get the Hessenberg form of the Tridiagonal matrix, the shape of the
    matrix is (k,k - 1).
"""
function GetHMatrix(this::DIL)
    T = GetTMatrix(this)
    return T[:, 1: end - 1]
end

"""
    Returns Q, T matrices such that QTQ^T approximates A
"""
function DecompositionQTQ(this::DIL)
    return GetQMatrix(this), GetTMatrix(this)
end

"""
    Returns H, Q Matrix such that AQ = QH.
"""
function DecompositionQH(this::DIL)

    return GetQMatrix(this)[:, 1: end - 1], GetHMatrix(this)
end

"""
    Get the previous 3 Orthogonal vector from the Lanczos Algorithm.
    * If we haven' generated 3 orthogonal vector 3, then
    this will just return the first 2, or 1 orthgonal matrix.
"""
function GetPrevious3OrthogonalVec(this::DIL)
    if this.k == 1
        return this.Q[1]
    end
    if this.k == 2
        return hcat(this.Q[1], this.Q[2])
    end
    return hcat(this.Q[this.k], this.Q[this.k - 1], this.Q[this.k - 2])
end

function GetRitzValues(this::DIL)
    if this.dynamic
        return this.dynamic_symtridiagonal.thetas
    end
    return nothing
end


Random.seed!(42)

@testset "Fourier shifting methods" begin

    @testset "Empty shifts" begin
        x = randn(ComplexF32, (11, 12, 13))
        @test FourierTools.shift(x, []) == x

        x = randn(Float32, (11, 12, 13))
        @test FourierTools.shift(x, []) == x
    end

    @testset "Integer shifts for complex and real arrays" begin
        x = randn(ComplexF32, (11, 12, 13))

        s = (2,2,2)
        @test FourierTools.shift(x, s) ≈ circshift(x, s)
        s = (3,2,1)
        @test FourierTools.shift(x, s) ≈ circshift(x, s)

        @test FourierTools.shift(x, (0,0,0)) == x
        x = randn(Float32, (11, 12, 13))

        s = (2,2,2)
        @test FourierTools.shift!(copy(x), s) ≈ circshift(x, s)
        s = (3,2,1)
        @test FourierTools.shift!(copy(x), s) ≈ circshift(x, s)

        @test sum(x) ≈ sum(FourierTools.shift!(copy(x), s))

    end

    @testset "Half integer shifts" begin

        x = [0.0, 1.0, 0.0, 1.0]
        xc = ComplexF32.(x)

        s = [0.5]
        @test FourierTools.shift!(copy(x), s) ≈ real(FourierTools.shift!(copy(xc), s))
        @test FourierTools.shift!(copy(x), s) ≈ real(FourierTools.shift!(copy(xc), 0.5))
        @test sum(x) ≈ sum(FourierTools.shift!(copy(x), s))

        @test sum(xc) ≈ sum(FourierTools.shift!(copy(xc), s))
    end

    @testset "Check shifts with soft_fraction" begin
        a = shift(delta((255,255)), (1.5,1.25),soft_fraction=0.1);
        @test abs(sum(a[real(a).<0])) < 3.0
        a = shift(delta((255,255)), (1.5,1.25),soft_fraction=0.0);
        @test abs(sum(a[real(a).<0])) > 5.0
    end

    @testset "Random shifts consistency between both methods" begin
        x = randn((11, 12, 13))
        s = randn((3,)) .* 10
        @test sum(x) ≈ sum(FourierTools.shift!(copy(x), s))
        @test FourierTools.shift!(copy(x), s) ≈ real(FourierTools.shift!(copy(x) .+ 0im, s))
        x = randn((11, 12, 13))
        s = randn((3,)) .* 10
        @test FourierTools.shift!(copy(x), s) ≈ real(FourierTools.shift!(copy(x) .+ 0im, s))
        @test sum(x) ≈ sum(FourierTools.shift!(copy(x), s))
    end

    @testset "Check revertibility for complex and real data" begin
        @testset "Complex data" begin
            x = randn(ComplexF32, (11, 12, 13))
            s = (-1.1, 12.123, 0.21)
            @test x ≈ shift(shift(x, s), .- s, fix_nyquist_frequency=true)
        end
        @testset "Real data" begin
            x = randn(Float32, (11, 12, 13))
            s = (-1.1, 12.123, 0.21)
            @test x ≈ shift(shift(x, s), .- s, fix_nyquist_frequency=true)
        end
    end

end


# nanmean.jl
#
#   Mean, ignoring NaNs
#
# Jeremy Rüffer
# Thünen Institut
# Institut für Agrarklimaschutz
# Junior Research Group NITROSPHERE
# Julia 1.6.0
# 02.04.2020
# Last Edit: 08.04.2021

nanmean(x::Array{T}) where T <: Number = mean(filter(!isnan,x))

nanmean(x::Array{Union{T,Missing}}) where T <: Number = mean(filter(!isnan,filter(!ismissing,x)))

function nanmean(x::Matrix{T},y::Integer) where T Union{S, Union{S, Missing}} where S <: Number
	return mapslices(nanmean,x,dims=y)
end


lm = LakeModel()
e_0 = 0.92     # Initial employment rate
u_0 = 1 - e_0  # Initial unemployment rate
T = 50         # Simulation length

xbar = rate_steady_state(lm)
x_0 = [u_0; e_0]
x_path = simulate_rate_path(lm, x_0, T)

titles = ["Unmployment rate" "Employment rate"]

fig, axes = subplots(2, 1, figsize=(10, 8))

for (i, ax) in enumerate(axes)
    ax[:plot](1:T, x_path[i, :], c="blue", lw=2, alpha=0.5)
    ax[:hlines](xbar[i], 0, T, "r", "--")
    ax[:set](title=titles[i])
    ax[:grid]("on")
end


using debspkg
using Test

@testset "debspkg.jl" begin
    # Write your tests here.
    @test f(3,2)==25
    @test f(2,2)==16
    @test f(3,1)==16
    @test f(1,1)==4
    @test f(0,0)==0
    @test f(1,0)==1
    @test f(0,1)==1
end

@testset "derivative tests" begin
    @test derivative_of_f1(2,4)==12
    @test derivative_of_f1(2,3)==10
    @test derivative_of_f1(2,2)==8
end


function mm_spec_kj6_dimspec()::MPCCDimSpec
    dimspec = MPCCDimSpec(2, 1, 2, 0, 2, 1, 0)
    return dimspec
end

function mm_spec_kj6_defn(x, pr, ps)
    f = exp(-x[1]+x[2])

    ce = Vector{Num}()

    ci = Vector{Num}(undef, 2)
    ci[1] = ((x[1]-2)^2 + (x[2]+1)^2 -2pr[1] -6)
    ci[2] = (1 - x[1])

    F = Matrix{Num}(undef, 2, 1)
    F[1,1] = x[1]
    F[2,1] = x[2]

    return MPCCDefinition(f, ce, ci, F)
end

function mm_spec_kj6_nzmask()::MPCCModelNZMask
    nz_mask = MPCCModelNZMask(
        # ce
        [],
        # ci
        [   Set{Int64}([1, 2]),
            Set{Int64}([1]) ],
        # F
        [
            # F1
            [   Set{Int64}([1]) ],
            # F2
            [   Set{Int64}([2]) ]
        ] )
    return nz_mask
end

function mm_spec_kj6_testvectors()
    return Vector{MPCCModelTestVector}(undef, 0)
end

function mm_spec_kj6_knownsols()

    function local_kj6_knownsol(pr::Vector{R}, ps::Vector{Int64}) where {R <: Real}
        @assert ( 1 == length(pr) && 0 == length(ps) )
        t = pr[1]
        if ( t <= -2 )
            x_sol = [1.0, 0.0]
            f_sol = exp(-1)
        elseif ( t <= -(1//2) && t > -2 )
            x_sol = [ (-sqrt(5 + 2t) + 2), 0]
            f_sol = exp(-x_sol[1])
        else
            x_sol = [ 0, (sqrt(6 + 2*t - (0-2)^2) - 1) ]
            f_sol = exp(x_sol[2])
        end

        return (x_sol, f_sol)
    end

    return [ local_kj6_knownsol ]
end

function mm_spec_kj6_parameterisations(t)
    defn1 = MPCCParameterisationDefn(
            Vector{Num}([ t ]),
            (-5.0, 2.0),
            "Standard"
        )

    return  Vector{MPCCParameterisationDefn}( [ defn1 ] )
end


export funLS

"""
f,g = funLS(r::AbstractArray; params::Dict)\n

Normalize r using the 2 norm. This function uses no params\n

Returns: f: 2-Norm of r\n
         g: Normalized r\n
"""
function funLS(r::Tr,params::Dict{String,Any}) where {ETr<:Real, Tr<:Union{AbstractArray{ETr}, AbstractArray{Complex{ETr}}}}

    f = norm(r, 2)
    g = r/f

    return f, g
end


#=
Created on Friday 15 November 2019
Last update: -

@author: Michiel Stock
michielfmstock@gmail.com

Simulation for 1D CA.
=#

abstract type CARule end

struct DetCARule <: CARule
    rulenr::Int8
    rulestr::BitArray
end

function CARule(rulenr::Int)
    @assert 0 ≤ rulenr < 2^8
    rulenr = Int8(rulenr)
    bstr = bitstring(rulenr)
    rulestr = reverse([c=='1' for c in bstr])
    return DetCARule(rulenr, rulestr)
end

nextstate(x::Bool, y::Bool, z::Bool, rule::CARule) = rule.rulestr[4x+2y+1z+1]

function update!(x, xnew, rule::DetCARule)
    xnew[1] = nextstate(x[end], x[1], x[2], rule)
    @simd for i in 2:length(x)-1
        @inbounds xnew[i] = nextstate(x[i-1], x[i], x[i+1], rule)
    end
    xnew[1] = nextstate(x[end-1], x[end], x[1], rule)
    return xnew
end

function simulate(x₀, rule::CARule; nsteps::Integer=100)
    n = length(x₀)
    X = Array{Bool,2}(undef, nsteps+1, n)
    X[1,:] .= x₀
    x = copy(x₀)
    xnew = similar(x₀)
    for t in 1:nsteps
        update!(x, xnew, rule)
        X[t+1,:] = xnew
        x = xnew
    end
    return X
end

using Plots

x₀ = zeros(Bool, 20001)

x₀[10001] = true

#x₀ = rand(Bool, 2001)

rule = CARule(110)

X = simulate(x₀, rule, nsteps=1000)
heatmap(1X)


"""
    hash(α::Cyclotomic[, h::UInt])

A basic hashing function for cyclotomic elements; Note that unlike the `Base` types hashing of `Cyclotomic`s is expensive as it necessitates reducing to minimal embeding.
This is to keep `hash`ing consistent and reliable with respect to `==`, i.e. that the equality of elements implies the equality of `hash`es.
"""
function Base.hash(α::Cyclotomic, h::UInt)
    β = reduced_embedding(α)
    return hash(coeffs(β), hash(conductor(β), hash(Cyclotomic, h)))
end

function Base.:(==)(α::Cyclotomic, β::Cyclotomic)
    coeffs(α) == coeffs(β) && return true

    if conductor(α) == conductor(β)
        normalform!(α)
        normalform!(β)
        return coeffs(α) == coeffs(β)
    else
        l = lcm(conductor(α), conductor(β))
        return embed(α, l) == embed(β, l)
    end
end

Base.:(==)(α::Cyclotomic{T}, x::R) where {T,R<:Real} = α == Cyclotomic(1, [x])
Base.:(==)(x::Real, α::Cyclotomic) = α == x

function Base.isapprox(α::Cyclotomic, a::Real; kwargs...)
    x, y = reim(Complex{Float64}(α))
    return isapprox(real(a), x; kwargs...) &&
           isapprox(one(a), 1.0 + y; kwargs...)
end

function Base.isapprox(α::Cyclotomic{T}, β::Cyclotomic{T}; kwargs...) where T<:AbstractFloat
    x, y = reim(Complex{T}(α))
    z, w = reim(Complex{T}(β))
    return isapprox(x, z; kwargs...) && isapprox(y, w; kwargs...)
end

Base.isapprox(a::Number, α::Cyclotomic; kwargs...) = isapprox(α, a; kwargs...)

Base.iszero(α::Cyclotomic) =
    all(iszero, values(α)) || (normalform!(α); all(iszero, values(α)))

Base.isreal(α::Cyclotomic) =
    α == conj(α) || conductor(reduced_embedding(α)) == 1

Base.isreal(α::Cyclotomic{T}) where T<:AbstractFloat =
    α ≈ conj(α) || conductor(reduced_embedding(α)) == 1

function Base.isone(α::Cyclotomic)
    β = reduced_embedding(α)
    conductor(β) == 1 || return false
    return isone(β[0])
end

"""
    isnormalized(α::Cyclotomic, basis)
Check if `α` is already in normal form with respect to the given basis.
"""
function isnormalized(α::Cyclotomic, basis = zumbroich_basis(conductor(α)))
    # return all(in(basis), exponents(α))
    for e in exponents(α)
        e in basis || return false
    end
    return true
end

function _all_equal(α::Cyclotomic, exps, value = α[first(exps)])
    # return all(e -> α[e] == val, exps)
    for e in exps
        α[e] == value || return false
    end
    return true
end


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

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

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

@testset "fit by mode and upper quantile" begin
    #plot(g); vline!([mode(g)])
    # median
    #g = fit_mode_quantile(LogitNormal, 0.68, @qp(0.71,0.99))
    g = fit(LogitNormal, 0.68, @qp_u(0.9), Val(:mode))
    @test mode(g) ≈ 0.68
    @test quantile(g, 0.95) ≈ 0.9
    #plot(g)
end

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

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


using Printf

# Convert some native Julia data types to MathML
const _INT_FMT=String("%i")
const _FLT_FMT=String("%.2f")

_num_format(::Type{<:Integer}) = _INT_FMT
_num_format(::Type{<:Rational}) = _INT_FMT
_num_format(::Type{<:AbstractFloat}) = _FLT_FMT
_num_format(::Type{Complex{T}}) where T <: Integer = _INT_FMT
_num_format(::Type{Complex{T}}) where T <: Rational = _INT_FMT
_num_format(::Type{Complex{T}}) where T <: AbstractFloat = _FLT_FMT
_num_format(x) = _FLT_FMT

function num_to_expr(x::Integer; fmt::String=_INT_FMT)
    if x >= 0
        return NUM(@eval @sprintf($fmt, $x))
    else
        return Group(0, OPID('-'), NUM(@eval @sprintf($fmt, $(-x))))
    end
end

function num_to_expr(x::AbstractFloat; fmt::String=_FLT_FMT)
    if x >= 0
        return NUM(@eval @sprintf($fmt, $x))
    else
        return Group(0, OPID('-'), NUM(@eval @sprintf($fmt, $(-x))))
    end
end

function num_to_expr(x::Rational{T}; fmt::String=_num_format(T)) where T
    x == 0 && return NUM("0")
    r = Int(sign(x))
    if (x < 0) x = -x end
    if x.den != 1
        if r > 0
            return Frac(num_to_expr(x.num; fmt), num_to_expr(x.den; fmt))
        else
            return Group(0, OPID('-'), Frac(num_to_expr(x.num; fmt), num_to_expr(x.den; fmt)))
        end
    else
        if r > 0
            return num_to_expr(x.num; fmt)
        else
            return Group(0, OPID('-'), num_to_expr(x.num; fmt))
        end
    end
end

function num_to_expr(x::Complex{T}; fmt::String=_num_format(T)) where T
    return Group(0, num_to_expr(x.re; fmt), OPID('+'), num_to_expr(x.im; fmt), ID("𝑖"))
end

function vector_to_expr(
    x::Vector{T};
    mtype="pmatrix",
    fmt::String=_num_format(T)
) where T<:Number

    N = length(x)
    v = CompoExpr[]

    for (i, a) in enumerate(x)
        push!(v, num_to_expr(a; fmt))
        i < N && push!(v, Newline())
    end
    return Environment(mtype, v)
end

function matrix_to_expr(
    x::Matrix{T};
    mtype="pmatrix",
    fmt::String=_num_format(T)
) where T<:Number

    sz, N = size(x), length(x)
    v = CompoExpr[]

    for i in 1:sz[1], j in 1:sz[2]
        push!(v, num_to_expr(x[i,j]; fmt))
        if j < sz[2]
            push!(v, Ampersand())
        elseif i < sz[1]
            push!(v, Newline())
        end
    end
    return Environment(mtype, v)
end

# for LinearAlgebra.Adjoint type matrix
matrix_to_expr(x::AbstractMatrix) = matrix_to_expr(Matrix{eltype(x)}(x))

# Convert some native Julia data types to TeX/LaTeX
num_to_tex(x::Integer; fmt::String=_INT_FMT) = @eval @sprintf($fmt, $x)
num_to_tex(x::AbstractFloat; fmt::String=_FLT_FMT) = @eval @sprintf($fmt, $x)

function num_to_tex(x::Rational{T}; fmt::String=_num_format(T)) where T
    x == 0 && return "0"
    r = Int(sign(x))
    if (x < 0) x = -x end
    if x.den != 1
        if r > 0
            return "\\frac{$(num_to_tex(x.num; fmt))}{$(num_to_tex(x.den; fmt))}"
        else
            return "-\\frac{$(num_to_tex(x.num; fmt))}{$(num_to_tex(x.den; fmt))}"
        end
    else
        if r > 0
            return "$(num_to_tex(x.num; fmt))"
        else
            return "-$(num_to_tex(x.num; fmt))"
        end
    end
end

function num_to_tex(x::Complex{T}; fmt::String=_num_format(T)) where T
    return "$(num_to_tex(x.re; fmt))+$(num_to_tex(x.im; fmt))i"
end

function vector_to_tex(
    x::Vector{T};
    mtype::String="pmatrix",
    fmt::String=_num_format(T)
) where T<:Number

    N = length(x)
    s = "\\begin{$(mtype)}"

    for (i, a) in enumerate(x)
        s *= num_to_tex(a; fmt)
        if i < N
            s *= "\\\\"
        end
    end
    return s*"\\end{$(mtype)}"
end

function matrix_to_tex(
    x::Matrix{T};
    mtype::String="pmatrix",
    fmt::String=_num_format(T)
) where T<:Number

    sz, N = size(x), length(x)
    s = "\\begin{$(mtype)}"

    for i in 1:sz[1], j in 1:sz[2]
        s *= num_to_tex(x[i,j]; fmt)
        if j < sz[2]
            s *= "&"
        elseif i < sz[1]
            s *= "\\\\"
        end
    end
    return s*"\\end{$(mtype)}"
end
# for LinearAlgebra.Adjoint type matrix
matrix_to_tex(x::AbstractMatrix) = matrix_to_tex(Matrix{eltype(x)}(x))


using Test
import MathOptInterface
const MOI = MathOptInterface

include("../model.jl")

@testset "Scalar" begin
    model = Model{Float64}()
    x = MOI.add_variable(model)
    @testset "SingleVariable" begin
        f = MOI.SingleVariable(x)
        ci = MOIU.add_scalar_constraint(model, f, MOI.EqualTo(1.0),
                                        allow_modify_function = false)
        @test MOI.get(model, MOI.ConstraintFunction(), ci) == f
        @test MOI.get(model, MOI.ConstraintSet(), ci) == MOI.EqualTo(1.0)
    end
    @testset "ScalarAffineFunction" begin
        f = MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 2.0)
        g = MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 0.0)
        ci = MOIU.add_scalar_constraint(model, f, MOI.EqualTo(3.0))
        @test f.constant == 2.0
        @test MOI.get(model, MOI.ConstraintFunction(), ci) ≈ g
        @test MOI.get(model, MOI.ConstraintSet(), ci) == MOI.EqualTo(1.0)
        ci = MOIU.add_scalar_constraint(model, f, MOI.Interval(-1.0, 1.0),
                                        allow_modify_function = true)
        @test f.constant == 0.0
        @test MOI.get(model, MOI.ConstraintFunction(), ci) ≈ g
        @test MOI.get(model, MOI.ConstraintSet(), ci) == MOI.Interval(-3.0,
                                                                      -1.0)
    end
end


"""
Computes the maximum flow between the source and target vertexes in a flow
graph using the [Edmonds-Karp algorithm](https://en.wikipedia.org/wiki/Edmondss%E2%80%93Karp_algorithm).
Returns the value of the maximum flow as well as the final flow matrix.
Use a default capacity of 1 when the capacity matrix isn\'t specified.
Requires arguments:
- residual_graph::ADiGraph                # the input graph
- source                            # the source vertex
- target                            # the target vertex
- capacity_matrix::AbstractMatrix{T}    # edge flow capacities
"""
function edmonds_karp_impl(
        residual_graph::ADiGraph,               # the input graph
        source,                           # the source vertex
        target,                           # the target vertex
        capacity_matrix::AbstractMatrix{T}    # edge flow capacities
    ) where T<:Number
    n = nv(residual_graph)                     # number of vertexes
    flow = 0
    flow_matrix = zeros(T, n, n)           # initialize flow matrix

    P = zeros(Int, n)
    S = zeros(Int, n)
    while true
        fill!(P, -1)
        fill!(S, -1)
        v, P, S, flag = fetch_path!(residual_graph, source, target, flow_matrix, capacity_matrix, P,S)

        if flag != 0                       # no more valid paths
            break
        else
            path = [v]                     # initialize path
            sizehint!(path, n)

            u = v
            while u!=source                # trace path from v to source
                u = P[u]
                push!(path, u)
            end
            reverse!(path)

            u = v                          # trace path from v to target
            while u!=target
                u = S[u]
                push!(path, u)
            end
                                           # augment flow along path
            flow += augment_path!(path, flow_matrix, capacity_matrix)
        end
    end

    return flow, flow_matrix
end

"""
Calculates the amount by which flow can be augmented in the given path.
Augments the flow and returns the augment value.
Requires arguments:
- path::Vector{Int}                      # input path
- flow_matrix::AbstractMatrix{T}        # the current flow matrix
- capacity_matrix::AbstractMatrix{T}    # edge flow capacities
"""
function augment_path!(
    path::Vector,                     # input path
    flow_matrix::AbstractMatrix{T},       # the current flow matrix
    capacity_matrix::AbstractMatrix{T}    # edge flow capacities
    ) where T<:Number
    augment = typemax(T)                   # initialize augment
    for i in 1:length(path)-1              # calculate min capacity along path
        u = path[i]
        v = path[i+1]
        augment = min(augment,capacity_matrix[u,v] - flow_matrix[u,v])
    end

    for i in 1:length(path)-1              # augment flow along path
        u = path[i]
        v = path[i+1]
        flow_matrix[u,v] += augment
        flow_matrix[v,u] -= augment
    end

    return augment
end

"""
Uses Bidirectional BFS to look for augmentable-paths. Returns the vertex where
the two BFS searches intersect, the Parent table of the path, the
Successor table of the path found, and a flag indicating success
Flag Values:
0 => success
1 => No Path to target
2 => No Path to source
"""
function fetch_path(
    residual_graph::ADiGraph,               # the input graph
    source,                           # the source vertex
    target,                           # the target vertex
    flow_matrix::AbstractMatrix{T},       # the current flow matrix
    capacity_matrix::AbstractMatrix{T}    # edge flow capacities
    ) where T<:Number
    n = nv(residual_graph)
    P = -1 * ones(Int, n)
    S = -1 * ones(Int, n)
    return fetch_path!(residual_graph,
                       source,
                       target,
                       flow_matrix,
                       capacity_matrix,
                       P,
                       S)
end

"""
A preallocated version of fetch_paths. The parent and successor tables are pre-allocated.
Uses Bidirectional BFS to look for augmentable-paths. Returns the vertex where
the two BFS searches intersect, the Parent table of the path, the
Successor table of the path found, and a flag indicating success
Flag Values:
0 => success
1 => No Path to target
2 => No Path to source
Requires arguments:
    residual_graph::ADiGraph                # the input graph
    source                            # the source vertex
    target                            # the target vertex
    flow_matrix::AbstractMatrix{T}        # the current flow matrix
    capacity_matrix::AbstractMatrix{T}    # edge flow capacities
    P::Vector{Int}                         # parent table of path init to -1s
    S::Vector{Int}                         # successor table of path init to -1s
"""
function fetch_path!(
    residual_graph::ADiGraph,               # the input graph
    source,                           # the source vertex
    target,                           # the target vertex
    flow_matrix::AbstractMatrix{T},       # the current flow matrix
    capacity_matrix::AbstractMatrix{T},   # edge flow capacities
    P::Vector{Int},                        # parent table of path init to -1s
    S::Vector{Int}                         # successor table of path init to -1s
    ) where T<:Number
    n = nv(residual_graph)

    P[source] = -2
    S[target] = -2

    Q_f = [source]                         # forward queue
    sizehint!(Q_f, n)

    Q_r = [target]                         # reverse queue
    sizehint!(Q_r, n)

    while true

        if length(Q_f) <= length(Q_r)
            u = pop!(Q_f)
            for v in out_neighbors(residual_graph, u)
                if capacity_matrix[u,v] - flow_matrix[u,v] > 0 && P[v] == -1
                    P[v] = u
                    if S[v] == -1
                        pushfirst!(Q_f, v)
                    else
                        return v, P, S, 0 # 0 indicates success
                    end
                end
            end

            length(Q_f) == 0 && return 0, P, S, 1 # No paths to target
        else
            v = pop!(Q_r)
            for u in in_neighbors(residual_graph, v)
                if capacity_matrix[u,v] - flow_matrix[u,v] > 0 && S[u] == -1
                    S[u] = v
                    P[u] != -1 && return  u, P, S, 0 # 0 indicates success

                    pushfirst!(Q_r, u)
                end

            end

            length(Q_r) == 0 && return 0, P, S, 2 # No paths to source
        end
    end
end