@doc raw"""
    solve_(prop:OptimalEstimation)

Rodgers-style linear optimal estimation, solves
```math
\begin{pmatrix} S_\epsilon^{-\frac{1}{2}}K \\ S_a^{-\frac{1}{2}} \end{pmatrix} x = \begin{pmatrix} S_\epsilon^{-\frac{1}{2}}y \\ 0 \end{pmatrix}
```
"""
function solve_stable(prob::OptimalEstimationProblem{FT}; errorAnalysis::Bool=true) where {FT}
    @unpack K, Sₑ, Sₐ, δy = prob
    n,m = size(K)
    # Compute S^{-0.5} using cholesky decomposition
    sqrtSₑ⁻¹ = inv(cholesky!(Sₑ).L);
    sqrtSₐ⁻¹ = inv(cholesky!(Sₐ).L);

    # A bit tediuous that way but reduces memory allocation:
    lhs = Array{FT}(undef,n+m,m)
    rhs = Array{FT}(undef,n+m)
    # K̃ = [sqrtSₑ⁻¹ * K; sqrtSₐ⁻¹]; ỹ = [sqrtSₑ⁻¹ * δy; 0]
    @views lhs[1:n,1:m]     = sqrtSₑ⁻¹ * K
    @views lhs[n+1:n+m,1:m] = sqrtSₐ⁻¹
    @views rhs[1:n]         = sqrtSₑ⁻¹ * δy
    @views rhs[n+1:end]    .= 0

    Kqr = qr!(lhs)

    # Solve like in Rodgers using QR decomposition, eq. 5.43,
    Δx̂ = Kqr \ rhs

    # Perform error analysis:
    if errorAnalysis
        Sₑ⁻¹ = sqrtSₑ⁻¹' * sqrtSₑ⁻¹
        # Compute Ŝ from K̃ = [sqrtSₑ⁻¹ * K; sqrtSₐ⁻¹]
        L = LinearAlgebra.inv!(UpperTriangular(Kqr.R));#UpperTriangular(Kqr.R)\I
        Ŝ = L * L'
        A = Ŝ * K' * Sₑ⁻¹ * K
        # residual r and reduced Χᵣ²
        r  = K*Δx̂ - δy
        # Reduced χ²
        χ² =  (r'* Sₑ⁻¹ *r)/(n-m)
        return OEsolution(Δx̂,Ŝ,A,r,χ²)
    end
    # Add xₐ here later?
    return Δx̂
end

@doc raw"""
    solve(prop:OptimalEstimation)

Rodgers-style linear optimal estimation, solves
\hat{x} = x_a + (K^TS_\epsilon^{-1} K + S_a^{-1})^{-1} K^T S_\epsilon^{-1}\delta y
"""
function solve(prob::OptimalEstimationProblem; errorAnalysis::Bool=true)
    @unpack K, Sₑ, Sₐ, δy = prob
    n,m = size(K)
    # Inverse(s)
    Sₑ⁻¹ = inv(Sₑ)
    Sₐ⁻¹ = inv(Sₐ)
    # Will be re-used:
    KᵀSₑ⁻¹K = K' * Sₑ⁻¹ * K

    # Posterior covariance matrix:
    Ŝ = inv(KᵀSₑ⁻¹K + Sₐ⁻¹)

    # Estimated state vector (eq 4.5):
    Δx̂ = Ŝ * K'Sₑ⁻¹ * δy
    if errorAnalysis
        # Averaging kernel matrix:
        A = Ŝ * KᵀSₑ⁻¹K
        # residual r and reduced Χᵣ²
        r  = K*Δx̂ - δy
        # Reduced χ²
        χ² =  (r'*Sₑ⁻¹*r)/(n-m)
        return OEsolution(Δx̂,Ŝ,A,r,χ²)
    end
    return Δx̂
end

# sqrtSₑ⁻¹ = sqrt.(inv(Sₑ));
# Solve like in Rodgers, eq. 5.43, supposedly more stable
#    lh = [sqrtSₑ⁻¹ * K; Sₐ]
#    rh = [sqrtSₑ⁻¹ * δy; zeros(nx)]
#    return lh \ rh


using Distributions
using Base.Test

# Test the special base where PoissonBinomial distribution reduces
# to Binomial distribution
for (p, n) in [(0.8, 6), (0.5, 10), (0.04, 20)]

    d = PoissonBinomial(fill(p, n))
    dref = Binomial(n, p)
    println("   testing PoissonBinomial p=$p, n=$n")

    @test isa(d, PoissonBinomial)
    @test minimum(d) == 0
    @test maximum(d) == n
    @test ntrials(d) == n
    @test_approx_eq entropy(d) entropy(dref)
    @test_approx_eq median(d) median(dref)
    @test_approx_eq mean(d) mean(dref)
    @test_approx_eq var(d) var(dref)
    @test_approx_eq kurtosis(d) kurtosis(dref)
    @test_approx_eq skewness(d) skewness(dref)

    for t=0:5
        @test_approx_eq mgf(d, t) mgf(dref, t)
        @test_approx_eq cf(d, t) cf(dref, t)
    end
    for i=0.1:0.1:.9
        @test_approx_eq quantile(d, i) quantile(dref, i)
    end
    for i=0:n
        @test_approx_eq_eps cdf(d, i) cdf(dref, i) 1e-15
        @test_approx_eq_eps pdf(d, i) pdf(dref, i) 1e-15
    end

end


using QuadGK
using SpecialFunctions
using Roots
using RCall

function Gammadist(gamma)
	return ((adap.be^adap.al)/SpecialFunctions.gamma(adap.al))*(gamma^(adap.al-1))*exp(-adap.be*gamma)
end

function pip0(gamma)
	U = 4*adap.theta_f*adap.Lf/(2.0*adap.NN)
	R = 2*adap.Lf*adap.rho/(2.0*adap.NN)
	return Gammadist(gamma)*exp(-(Gammadist(gamma)*U/(2.0*adap.NN))/(gamma/(adap.NN+0.0)+R/(2.0*adap.NN)))
end

function intpip0()

	f(gam) = pip0(gam)
	return QuadGK.quadgk(f,0,1000)[1]
end

function calcBGam(L,alpha,beta,theta)
	# not sure whats going on here, seems to overestimate or under sometimes
	u = 2*theta/(2.0*adap.NN)
	r = adap.rho/(2.0*adap.NN)

	a = -1.0*(2^(alpha+1))*exp(L*adap.NN*r*beta)*L*adap.N*((1.0/(L*adap.N*r))^(1-alpha))*u*(beta^alpha)
	b =  convert(Float64,SpecialFunctions.gamma_inc(1-alpha,L*adap.NN*r*beta,1)[2])
	#fudge = 1-gamInt.cdf(0.2,a=adap.al2,scale=1./adap.be2)
	#fudge = 1.
	fudge = 0.25

	c = exp(a*b*fudge)

	return c
end

function set_theta_f_gam()
	i(theta) = calcBGam(adap.Lf,adap.al2,adap.be2,theta)-adap.B
	# theta_f  = fsolve(lambda theta ,0.0000001)
	theta_f  = Roots.find_zero(i,0.0000001)
	adap.theta_f = theta_f
end

function calcB(L,theta)
	t = -1.0*adap.gam_neg/(adap.NN+0.)
	u = 2.0*theta/(2.0*adap.NN)
	r = adap.rho/(2.0*adap.NN)

	#return u*t/((t+r*L)^2)
	#return u*t/(2*(t+(1.-np.exp(-2*r*L))/2.)^2)

	# Nordborg models
	#####return u/(2*r*t*(1+((1.-np.exp(-2*r*L))/2.)*(1-t)/t)^2)
	#return u/(t*(1+((1-np.exp(-2.0*r*L))/2.)*(1-t)/t)^2)
	#return u/(t*(1+r*L*(1-t)/t)^2)
	#####return u/(t*(1+(np.exp(-2*r*L)/2)/t)^2)
end

# Br() needs L and theta, not working right now
function get_B_vals()
	ret = Array{Float64}(undef,30)
	for i in 20:50
		L = convert(Int64,round(1.3^i,digits=0))
		ret[i] = (Br(L),L)
	end
	return ret
end

"""
	Estimating and plotting MAP using locfit and ggplot2 in R. It assume your folder contains the posterior estimated through ABCreg
"""
function plotMap(;analysis::String,output::String)

	try
		@eval using RCall
		@eval R"""library(ggplot2);library(abc)"""

		out = filter(x -> occursin("post",x), readdir(analysis,join=true))
		out = filter(x -> !occursin(".1.",x),out)

		open(x) = Array(CSV.read(GZip.open(x),DataFrame,header=false))
		posteriors = open.(out)

		maxp = DataFrame(Array{Float64,2}(undef,size(posteriors,1),5),[:aw,:as,:a,:gamNeg,:shape])
		R"""getmap <- function(df){
				temp = as.data.frame(df)
			    d <-locfit(~temp[,1],temp);
			    map<-temp[,1][which.max(predict(d,newdata=temp))]
			}"""
		getmap(x) = rcopy(R"""matrix(apply($x,2,getmap),nrow=1)""")
		tmp = getmap.(posteriors)
		maxp = DataFrame(vcat(tmp...),[:aw,:as,:a,:gamNeg,:shape])

		al  = maxp[:,1:3]
		gam  = maxp[:,4:end]
		p = rcopy(R"""al = $al
			dal = melt(al)
			pal = ggplot(dal) + geom_density(aes(x=value,fill=variable),alpha=0.5) + scale_fill_manual(values=c('#30504f', '#e2bd9a', '#ab2710'))
			ggsave(pal,filename=paste0($output))
			""")
		return(maxp)
	catch
		println("Please install R, ggplot2 and abc in your system before execute this function")
	end
end


# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Description
# ==============================================================================
#
#   Two Body orbit propagator.
#
#   This algorithm considers a perfect Keplerian orbit. In other words, no
#   perturbation is considered during the propagation and the Earth is modeled
#   as a perfect sphere.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# References
# ==============================================================================
#
#   [1] Vallado, D. A (2013). Fundamentals of Astrodynamics and Applications.
#       Microcosm Press, Hawthorn, CA, USA.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

export TwoBody_Structure
export twobody_init, twobody!

################################################################################
#                                  Functions
################################################################################

"""
    twobody_init(epoch::Tepoch, a_0::Number, e_0::Number, i_0::Number, Ω_0::Number, ω_0::Number, f_0::Number; μ::T = m0) where {Tepoch, T}

Initialize the data structure of two body orbit propagator algorithm.

!!! note
    The type used in the propagation will be the same as used to define the
    gravitational constant `μ`.

# Args

- `epoch::Number`: Epoch of the initial mean orbital elements [Julian Day].
- `a_0::Number`: Initial mean semi-major axis [m].
- `e_0::Number`: Initial mean eccentricity.
- `i_0::Number`: Initial mean inclination [rad].
- `Ω_0::Number`: Initial mean right ascension of the ascending node [rad].
- `ω_0::Number`: Initial mean argument of perigee [rad].
- `f_0::Number`: Initial mean true anomaly [rad].

# Keywords

- `μ::T`: Standard gravitational parameter of the central body [m^3/s^2].
    (**Default** = `m0`)

# Returns

The structure [`TwoBody_Structure`](@ref) with the initialized parameters.
"""
function twobody_init(
    epoch::Tepoch,
    a_0::Number,
    e_0::Number,
    i_0::Number,
    Ω_0::Number,
    ω_0::Number,
    f_0::Number;
    μ::T = m0
) where {Tepoch, T}
    # The propagator is only defined for 0 <= e < 1.
    if !(0 <= e_0 < 1)
        throw(ArgumentError("The two body propagator only supports eccentricities in the interval [0,1)"))
    end

    # Compute the mean motion using the semi-major axis.
    n_0 = sqrt(μ / T(a_0)^3)

    # Compute the initial mean anomaly.
    M_0 = f_to_M(T(e_0), T(f_0))

    # Create and return the Two Body orbit propagator structure.
    TwoBody_Structure{Tepoch, T}(
        epoch = epoch,
        a_0   = a_0,
        n_0   = n_0,
        e_0   = e_0,
        i_0   = i_0,
        Ω_0   = Ω_0,
        ω_0   = ω_0,
        M_0   = M_0,
        f_0   = f_0,
        μ     = μ,
        Δt    = 0,
        f_k   = f_0,
        M_k   = M_0
    )
end

"""
    twobody!(tbd::TwoBody_Structure{Tepoch, T}, t::Number) where {Tepoch, T}

Propagate the orbit defined in `tbd` (see [`TwoBody_Structure`](@ref)) until the
time `t` [s].

!!! note
    The internal values in `tbd` will be modified.

# Returns

- The position vector represented in the inertial frame at time `t` [m].
- The velocity vector represented in the inertial frame at time `t` [m/s]

# Remarks

The inertial frame in which the output is represented depends on which frame it
was used to generate the orbit parameters.
"""
function twobody!(tbd::TwoBody_Structure{Tepoch, T}, t::Number) where {Tepoch, T}
    # Time elapsed since epoch.
    Δt     = T(t)
    tbd.Δt = Δt

    # Update the mean anomaly.
    tbd.M_k = tbd.M_0 + tbd.n_0 * Δt

    # Convert the mean anomaly to true anomaly.
    tbd.f_k = M_to_f(tbd.e_0, tbd.M_k)

    # Compute the position and velocity vectors given the orbital elements.
    r_i_k, v_i_k = kepler_to_rv(tbd.a_0, tbd.e_0, tbd.i_0, tbd.Ω_0, tbd.ω_0, tbd.f_k)

    # Return the position and velocity vector represented in the inertial
    # reference frame.
    return r_i_k, v_i_k
end


#=
   Port of my the Or-tools/Python Sudoku solver.

   After warmup:
   julia> @time include("sudoku_or_tools.jl")
    num_solutions: 1
    [8, 6, 1, 4, 5, 9, 7, 2, 3]
    [4, 5, 2, 3, 1, 7, 6, 9, 8]
    [7, 9, 3, 6, 8, 2, 5, 1, 4]
    [2, 1, 6, 8, 3, 5, 4, 7, 9]
    [9, 8, 4, 2, 7, 6, 1, 3, 5]
    [3, 7, 5, 1, 9, 4, 8, 6, 2]
    [5, 4, 7, 9, 2, 1, 3, 8, 6]
    [1, 3, 9, 5, 6, 8, 2, 4, 7]
    [6, 2, 8, 7, 4, 3, 9, 5, 1]

    num_solutions:1
    failures:0
    branches:0
    WallTime:291
    0.297074 seconds (571.88 k allocations: 29.771 MiB, 3.09% gc time)

   Via command line:
   $ time julia sudoku_or_tools.jl
    num_solutions: 1
    [8, 6, 1, 4, 5, 9, 7, 2, 3]
    [4, 5, 2, 3, 1, 7, 6, 9, 8]
    [7, 9, 3, 6, 8, 2, 5, 1, 4]
    [2, 1, 6, 8, 3, 5, 4, 7, 9]
    [9, 8, 4, 2, 7, 6, 1, 3, 5]
    [3, 7, 5, 1, 9, 4, 8, 6, 2]
    [5, 4, 7, 9, 2, 1, 3, 8, 6]
    [1, 3, 9, 5, 6, 8, 2, 4, 7]
    [6, 2, 8, 7, 4, 3, 9, 5, 1]

    num_solutions:1
    failures:0
    branches:0
    WallTime:839
    julia sudoku_or_tools.jl  2,63s user 0,78s system 155% cpu 2,193 total

=#
using PyCall

# Resize matrix to "normal" form
function resize_matrix(grid)
    rows = size(grid)
    transpose(reduce(hcat,grid)) # Suggestion by @Mason Protter @ Zulip
end

# Be sure that ortools are installed correctly
# Note that we are using the old CP solver (not the new hot SAT solver)
ot = pyimport("ortools.constraint_solver.pywrapcp")

# Create the solver
solver = ot.Solver("Sudoku")

function sudoku(initial_grid)
    cell_size = 3
    line_size = cell_size^2
    line = 1:line_size
    cell = 1:cell_size

    # Decision variables
    grid = [solver.IntVar(1, line_size, "grid[$i,$j]") for i in line, j in line]

    # AllDifferent on rows.
    for i in line
        solver.Add(solver.AllDifferent([grid[i, j] for j in line]))
    end

    # AllDifferent on columns.
    for j in line
        solver.Add(solver.AllDifferent([grid[i, j] for i in line]))
    end

    # AllDifferent on cells.
    for i in cell, j in cell
        solver.Add(solver.AllDifferent(
                        vec([grid[(i-1) * cell_size + di, (j-1) * cell_size + dj] for di in cell, dj in cell] )
                    ))
    end

    # Initial values.
    for i in line
        for j in line
            if initial_grid[i,j] > 0
                solver.Add(grid[i, j] == initial_grid[i,j])
            end
        end
    end

    # Regroup all variables into a list.
    all_vars = [grid[i, j] for i in line for j in line]

    # Create search phases.
    vars_phase = solver.Phase(all_vars,
                                solver.INT_VAR_SIMPLE,
                                solver.INT_VALUE_SIMPLE)

    solution = solver.Assignment()
    solution.Add(all_vars)
    # Note: This give just one solution, even if there are many others
    collector = solver.FirstSolutionCollector(solution)

    # And solve.
    solver.Solve(vars_phase, [collector])

    num_solutions = collector.SolutionCount()
    println("num_solutions: $num_solutions")
    if num_solutions == 1
        for i in line
            println([collector.Value(0, grid[i, j]) for j in line])
        end
    end
    println("\nnum_solutions:", num_solutions)
    println("failures:", solver.Failures())
    println("branches:", solver.Branches())
    println("WallTime:", solver.WallTime())

end

initial_grid = resize_matrix([[0, 6, 0, 0, 5, 0, 0, 2, 0],
    [0, 0, 0, 3, 0, 0, 0, 9, 0],
    [7, 0, 0, 6, 0, 0, 0, 1, 0],
    [0, 0, 6, 0, 3, 0, 4, 0, 0],
    [0, 0, 4, 0, 7, 0, 1, 0, 0],
    [0, 0, 5, 0, 9, 0, 8, 0, 0],
    [0, 4, 0, 0, 0, 1, 0, 0, 6],
    [0, 3, 0, 0, 0, 8, 0, 0, 0],
    [0, 2, 0, 0, 4, 0, 0, 5, 0]])

sudoku(initial_grid)


# Copyright (c) 2016, Jernej Kovacic
# Licensed under the MIT "Expat" License.
#
# See LICENSE.md for more details.

#
# Note: code in this file is based on the C++ library "Math", available at:
# https://github.com/jkovacic/math and
# https://github.com/jkovacic/math/blob/master/lib/specfun/CtdFracGeneric.cpp
#

#include("settings.jl")
#include("exception.jl")

#
# Evaluates the continued fraction:
#
#                        a1
#    f = b0 + -------------------------
#                           a2
#               b1 + -----------------
#                              a3
#                     b2 + ----------
#                           b3 + ...
#
# where ai and bi are functions of 'i'.
#
# Arguments:
# * `fa::Function`: function `a(i)`
# * `fb::Function`: function `b(i)`
#
# The function throws `NoConvergenceError` if the internal
# algorithm does not converge.
#

function ctdfrac_eval(fa::Function, fb::Function)
    #
    # The Lentz's algorithm (modified by I. J. Thompson and A. R. Barnett)
    # is applied to evaluate the continued fraction. The algorithm is
    # presented in detail in:
    #
    #   William H. Press, Saul A. Teukolsky, William T. Vetterling, Brian P. Flannery
    #   Numerical Recipes, The Art of Scientific Computing, 3rd Edition,
    #   Cambridge University Press, 2007
    #
    #   https://books.google.com/books?id=1aAOdzK3FegC&lpg=PA207&ots=3jNoK9Crpj&pg=PA208#v=onepage&f=false
    #
    # The procedure of the algorithm is as follows:
    #
    # - f0 = b0, if b0==0 then f0 = eps
    # - C0 = f0
    # - D0 = 0
    # - for j = 1, 2, 3, ...
    #   -- Dj = bj + aj * D_j-1, if Dj==0 then Dj = eps
    #   -- Cj = bj + aj / C_j-1, if Cj==0 then Cj = eps
    #   -- Dj = 1 / Dj
    #   -- Delta_j = Cj * Dj
    #   -- fj = f_j-1 * Delta_j
    #   -- if abs(Delta_j-1) < TOL then exit for loop
    # - return fj
    #

    # f0 = b0
    f = fb(0)

    const T = typeof(f)
    const EPS = eps(T)

    # adjust f0 to eps if necessary
    if abs(f) < EPS
        f = EPS
    end

    # c0 = f0,  d0 = 0
    c = f
    d = T(0)

    # Initially Delta should not be equal to 1
    Delta = T(0)

    const TOL = SPECFUN_ITER_TOL(T)
    j = 1
    while ( abs(Delta-1) > TOL && j < SPECFUN_MAXITER )
        # obtain 'aj' and 'bj'
        a = fa(j)
        b = fb(j)

        # dj = bj + aj * d_j-1
        d = b + a * d
        # adjust dj to eps if necessary
        if ( abs(d) < EPS )
            d = EPS
        end

        # cj = bj + aj/c_j-1
        c = b + a /c
        # adjust cj to eps if necessary
        if ( abs(c) < EPS )
            c = EPS
        end

        # dj = 1 / dj
        d = T(1) / d

        # Delta_j = cj * dj
        Delta = c * d

        # fj = f_j-1 * Delta_j
        f *= Delta

        # for loop's condition will check, if abs(Delta_j-1)
        # is less than the tolerance

        j += 1
    end  # while

    # check if the algorithm has converged:
    if ( j >= SPECFUN_MAXITER )
        throw(NoConvergenceError)
    end

    # ... if yes, return the fj
    return f;

end


### If the Finch package has already been added, use this line #########
using Finch # Note: to add the package, first do: ]add "https://github.com/paralab/Finch.git"

### If not, use these four lines (working from the examples directory) ###
# if !@isdefined(Finch)
#     include("../Finch.jl");
#     using .Finch
# end
##########################################################################

init_finch("FVheat1d");

useLog("FVheat1dlog")

# Configuration setup
domain(1)
solverType(FV)
timeStepper(RK4)

# Mesh
n = 40 # number of elements
mesh(LINEMESH, elsperdim=n)

# Variables and BCs
u = variable("u", location=CELL)
boundary(u, 1, FLUX, "0")

# Time interval and initial condition
T = 0.1;
timeInterval(T)
initial(u, "x<0.5 ? sin(pi*x)^4 : 0")

# The flux and source terms of the conservation equation
# F and S in the following equation:
# Dt(int(u dx)) = int(S dx) - int(F ds)
coefficient("D", 0.1) # Diffusion rate
flux(u, "-D*dot(grad(u),normal())") # in 1D -> -D * dx(u) * normal

exportCode("fvheat1dcode") # uncomment to export generated code to a file
#importCode("fvheat1dcode") # uncomment to import code from a file

solve(u)

finalize_finch()

##### Uncomment below to plot
# The initial condition
# u0 = zeros(n);
# x = Finch.fv_info.cellCenters[:]
# for i=1:n
#     u0[i] = x[i]<0.5 ? sin(pi*x[i])^4 : 0
# end

# using Plots
# pyplot();
# display(plot([x x], [u0 u.values[:]], markershape=:circle, label=["initial" "t="*string(T)]))


using Revise
using Plots
using Basins
using DynamicalSystems
using RegionTrees

# C. Grebogi, S. W. McDonald, E. Ott, J. A. Yorke, Final state sensitivity: An obstruction to predictability, Physics Letters A, 99, 9, 1983
ds = Systems.grebogi_map(rand(2))
integ  = integrator(ds)

θg=range(0,2π,length=1000)
xg=range(-1,1,length=1000)

@time bsn=Basins.basins_map2D_tree(θg, xg, integ; r_init=0.2, r_max=0.01)

function evaluate(basin,v)
    return findleaf(basin,v).data
end

function compute_frac_dim(bsn)
    root_n(x,n) = foldl((y,_) -> parent(y), 1:n; init=x)
    N = zeros(Int, 20)
    wd=1e3
    k_mx = 0
    for lf in allcells(bsn.basin)
        k = 1
        while !isnothing(root_n(lf,k))
            k += 1
        end
        N[k] += 1
        wd = min(wd,lf.boundary.widths[1])
        k_mx = max(k_mx,k)
    end
    @show N
    ε = reverse(wd * 2 .^ range(0,k_mx-1,step=1))
    return ε,N[1:k_mx]
end

ε,N = compute_frac_dim(bsn)

elog = log10.(1 ./ ε)
Nlog = log10.(N)
D = linear_region(elog[4:end],Nlog[4:end])
@show D

plt = plot(xlim=(θg[1], θg[end]), ylim=(xg[1], xg[end]), legend=nothing)
heatmap!(plt, θg, xg, (x, y) -> evaluate(bsn.basin, SVector(x, y)), fill=true)

#
#
# # plt = plot(xlim=(-1.5, 1.5), ylim=(-1.5, 1.5), legend=nothing)
#  for leaf in allleaves(bsn.basin)
#      v = hcat(collect(vertices(leaf.boundary))...)
#      #plot!(plt, v[1,[1,2,4,3,1]], v[2,[1,2,4,3,1]])
#      #@show leaf.data
#      plot!(plt, v[1,[1,2,4,3,1]], v[2,[1,2,4,3,1]], linecolor=:black, lw=0.4)
#
#  end
#  plt


# ------------------------------------------------------------------
# Licensed under the MIT License. See LICENSE in the project root.
# ------------------------------------------------------------------

# Local IDW estimator using local anisotropies


"""
**Momentum constructor**

```julia
    Momentum(; η::Real=0.01, γ::Real=0.9)
```

Algorithm :

```math
\\begin{align*}
v_t =& \\gamma v_{t-1} + \\eta g_t\\\\
\\Delta x_t =& v_t
\\end{align*}
```
"""
mutable struct Momentum <: Optimizer
    opt_type::String
    t::Int64
    η::Float64
    γ::Float64
    v_t::AbstractArray
end

## Note the algorithm seems flawed, \η should be 1-\β and a supplementary global learning rate would be nice

function Momentum(; η::Real=0.01, γ::Real=0.9)
    @assert η > 0.0 "η must be greater than 0"
    @assert γ > 0.0 "γ must be greater than 0"

    Momentum("Momentum", 0, η, γ, [])
end

params(opt::Momentum) = "η=$(opt.η), γ=$(opt.γ)"

function update(opt::Momentum, g_t::AbstractArray{T}) where {T<:Real}
    # resize squares of gradients
    if opt.t == 0
        opt.v_t = zero(g_t)
    end

    # update timestep
    opt.t += 1

    opt.v_t = opt.γ * opt.v_t + opt.η * g_t

    return opt.v_t
end


function det_by_lu(y, x, N)
    y[1] = 1.

    for k = 1:N
        y[1] *= x[k,k]
        for i = k+1:N
            x[i,k] /= x[k,k]
	    end
        for j = k+1:N
            for i = k+1:N
                x[i,j] -= x[i,k] * x[k,j]
            end
        end
    end
end

nb_loops = []

function run_julia(y,A,B,N)
    loops = max(10000000 // (N*N), 1)
    append!(nb_loops, loops)
    print(loops, '\n')
    for l in 1:loops
        B[:,:] = A
        det_by_lu(y, B, N)
    end
end

y = [0.0]

times = []

N=5
A = rand(N,N)
B = zeros(N,N)
# Warm up
@time run_julia(y,A,B,N)

N=5
A = rand(N,N)
B = zeros(N,N)
append!(times, @elapsed run_julia(y,A,B,N))
N=10
A = rand(N,N)
B = zeros(N,N)
append!(times, @elapsed run_julia(y,A,B,N))
N=30
A = rand(N,N)
B = zeros(N,N)
append!(times, @elapsed run_julia(y,A,B,N))
N=100
A = rand(N,N)
B = zeros(N,N)
append!(times, @elapsed run_julia(y,A,B,N))
N=200
A = rand(N,N)
B = zeros(N,N)
append!(times, @elapsed run_julia(y,A,B,N))
N=300
A = rand(N,N)
B = zeros(N,N)
append!(times, @elapsed run_julia(y,A,B,N))
N=400
A = rand(N,N)
B = zeros(N,N)
append!(times, @elapsed run_julia(y,A,B,N))
N=600
A = rand(N,N)
B = zeros(N,N)
append!(times, @elapsed run_julia(y,A,B,N))
N=1000
A = rand(N,N)
B = zeros(N,N)
append!(times, @elapsed run_julia(y,A,B,N))

print("\ntime per loop=\n", times ./ nb_loops[2:end])
# [5, 10, 30, 100, 200, 300, 400, 600, 1000])


# Various Julia implementations of SOUP-DIL[LO].

module SOUPDIL

export soupdillo

"""
    soupdillo(Y::Matrix{Float64},J::Int,λ::Float64,K::Int,L::Float64)

Compute a synthesis dictionary and associated sparse coefficients for
the columns of Y using the SOUP-DIL[L0] algorithm.
"""
function soupdillo(Y::Matrix{Float64},J::Int,λ::Float64,K::Int,L::Float64)

  # Useful definitions
  n,N = size(Y)
  v₁ = eye(n)[:,1]

  # Initial estimates
  D = gen_odct(n,J)
  C = zeros(N,J)

  b₁ = Vector{Float64}(N)
  b₂ = Vector{Float64}(J)
  b₃ = Vector{Float64}(N)
  h₁ = Vector{Float64}(n)
  h₂ = Vector{Float64}(J)
  h₃ = Vector{Float64}(n)
  for t = 1:K
    for j = 1:J
      # 1) C = [c_1^t,...,c_{j-1}^t,c_j^{t-1},...,c_J^{t-1}]
      #    D = [d_1^t,...,d_{j-1}^t,d_j^{t-1},...,d_J^{t-1}]

      # 2) Sparse coding
      @inbounds At_mul_B!(b₁,Y,D[:,j])
      @inbounds At_mul_B!(b₂,D,D[:,j])
      A_mul_B!(b₃,C,b₂)
      @inbounds bₜ = b₁ - b₃ + C[:,j]
      for i in eachindex(bₜ)
        @inbounds abs(bₜ[i]) < λ && (bₜ[i] = 0)
        @inbounds abs(bₜ[i]) > L && (bₜ[i] = L*sign(bₜ[i]))
      end
      cⱼₜ = bₜ

      # 3) Dictionary atom update
      A_mul_B!(h₁,Y,cⱼₜ)
      At_mul_B!(h₂,C,cⱼₜ)
      A_mul_B!(h₃,D,h₂)
      @inbounds hₜ  = h₁ - h₃ + D[:,j]*dot(C[:,j],cⱼₜ)
      dⱼₜ = !iszero(cⱼₜ) ? hₜ/norm(hₜ,2) : v₁

      @inbounds C[:,j] = cⱼₜ
      @inbounds D[:,j] = dⱼₜ
    end
  end

  return D, C
end

function iszero(B)
  for b in B
    b != 0 && (return false)
  end
  return true
end

function gen_odct(n::Int,J::Int)
  sqrtn,sqrtJ = ceil(Int,sqrt([n,J]))

  odct = zeros(sqrtn,sqrtJ)
  odct[:,1] = 1. ./ sqrt(sqrtn)
  for j = 2:sqrtJ
    v = cos(π*(j-1)/sqrtJ * (0:sqrtn-1))'; v -= mean(v)
    odct[:,j] = v/norm(v,2)
  end
  odct = kron(odct,odct)

  return odct[1:n,1:J]
end

end


"""
Snape's Riddle - Author JK Rowling
Danger lies before you, while safety lies behind,
Two of us will help you, whichever you would find,
One among us seven will let you move ahead,
Another will transport the drinker back instead,
Two among our number hold only nettle wine,
Three of us are killers, waiting hidden in line.
Choose, unless you wish to stay here for evermore,
To help you in your choice, we give you these clues four:
First, however slyly the poison tries to hide
You will always find some on nettle wine’s left side;
Second, different are those who stand at either end,
But if you would move onwards, neither is your friend;
Third, as you see clearly, all are different size,
Neither dwarf nor giant holds death in their insides;
Fourth, the second left and the second on the right
Are twins once you taste them, though different at first sight.
"""

# Codes:
# 1 is netted wine
# 2 move ahead
# 3 move back
# 4 kill

# Seven Potions a,b,c,d,e,f,g each have 1 of 4 possible values
# a is left of b which is left of c etc.
Ω,℧ = ABparse(["a, b, c, d, e, f, g  ∈  [1,2,3,4]"])

# Two among our number hold only nettle wine, #code 1
Ω,℧ = ABparse(["{{i}} == 1 {{2}}"], Ω=Ω,℧=℧); Ω[℧]
# Equivalent to ABparse(["a|b|c|d|e|f|g == 1 {2}"], Ω=Ω,℧=℧)

# One among us seven will let you move ahead, #code 2
Ω,℧ = ABparse(["{{i}} == 2 {{1}}"], Ω=Ω,℧=℧)
#Ω,℧ = ABparse(["a|b|c|d|e|f|g == 2 {1}"], Ω=Ω,℧=℧)

#Another will transport the drinker back instead, #code 3
Ω,℧ = ABparse(["{{i}} == 3 {{1}}"], Ω=Ω,℧=℧)

# Three of us are killers #code 4
Ω,℧ = ABparse(["{{i}} == 4 {{3}}"], Ω=Ω,℧=℧)
# This last specification does not reduce the feasible set

Ω[℧] # 420 possible outcomes

# First, however slyly the poison tries to hide
# You will always find some on nettle wine’s left side;
# So if b is poison then a is nettle wine
Ω,℧ = ABparse(["{{j+1}} == 1 ==> {{j}} == 4"], Ω=Ω,℧=℧); Ω[℧]

# Equivalent to:
# ABparse(["b == 1 ==> a == 4","c == 1 ==> b == 4","d == 1 ==> c == 4",
#          "e == 1 ==> d == 4","f == 1 ==> e == 4","g == 1 ==> f == 4"], Ω=Ω,℧=℧)
#
# Wine cannot be in the first bottle since poison is to the left
Ω,℧ = ABparse(["a != 1"], Ω=Ω,℧=℧)

Ω[℧] # 60 possible outcomes

# Second, different are those who stand at either end,
Ω,℧ = ABparse(["a != g"], Ω=Ω,℧=℧)

# But if you would move onwards, neither is your friend;
Ω,℧ = ABparse(["a|g == 2 {0}"], Ω=Ω,℧=℧)

Ω[℧] # 30 possible outcomes

# But if you would move onwards, neither is your friend;
# Third, as you see clearly, all are different size,
# Neither dwarf nor giant holds death in their insides;
# c dwarf, f are giant
Ω,℧ = ABparse(["c|f == 4 {0}"], Ω=Ω,℧=℧)

Ω[℧] # 5 possible outcomes

# Fourth, the second left and the second on the right
# Are twins once you taste them, though different at first sight.
Ω,℧ = ABparse(["b == f"], Ω=Ω, ℧=℧)

Ω[℧] # 1 possible outcome
# You pick the 3rd (move ahead) and the 7th (move back)


using PkgName, Base.Test

function equal(A::AbstractMatrix{T},V::AbstractMatrix{T},H::AbstractMatrix{T},m::Int) where{T}
    x = norm(A*V[:,1:m]-V*H)
    y = 10*eps(T)
    if x < y
        return true
    else
        return false
    end
end

A = sprand(100,100,0.3)
H, V = hessenberg(A,30)
@test equal(A,V,H,30) == true


using NLSolvers
using LinearAlgebra
using SparseDiffTools
using SparseArrays

# Start with a stupid example
n = 10
A = sprand(10, 10, 0.1)
A = A*A' + I
F(Fx, x) = mul!(Fx, A, x)

x0, = rand(10)
xp = copy(x0)
Fx = copy(xp)

function theta(x)
   if x[1] > 0
       return atan(x[2] / x[1]) / (2.0 * pi)
   else
       return (pi + atan(x[2] / x[1])) / (2.0 * pi)
   end
end

function F_powell!(Fx, x)
        if ( x[1]^2 + x[2]^2 == 0 )
            dtdx1 = 0;
            dtdx2 = 0;
        else
            dtdx1 = - x[2] / ( 2 * pi * ( x[1]^2 + x[2]^2 ) );
            dtdx2 =   x[1] / ( 2 * pi * ( x[1]^2 + x[2]^2 ) );
        end
        Fx[1] = -2000.0*(x[3]-10.0*theta(x))*dtdx1 +
            200.0*(sqrt(x[1]^2+x[2]^2)-1)*x[1]/sqrt( x[1]^2+x[2]^2 );
        Fx[2] = -2000.0*(x[3]-10.0*theta(x))*dtdx2 +
            200.0*(sqrt(x[1]^2+x[2]^2)-1)*x[2]/sqrt( x[1]^2+x[2]^2 );
        Fx[3] =  200.0*(x[3]-10.0*theta(x)) + 2.0*x[3];
    Fx
end

krylov_problem = ResidualKrylovProblem(F_powell!; seed=rand(3))
nlsolve!(krylov_problem, rand(3), ResidualKrylov(FixedForceTerm(0.4)))

function f_2by2!(F, x)
    F[1] = (x[1]+3)*(x[2]^3-7)+18
    F[2] = sin(x[2]*exp(x[1])-1)
    F
end

function g_2by2!(J, x)
    J[1, 1] = x[2]^3-7
    J[1, 2] = 3*x[2]^2*(x[1]+3)
    u = exp(x[1])*cos(x[2]*exp(x[1])-1)
    J[2, 1] = x[2]*u
    J[2, 2] = u
    J
end

nlsolve!(f_2by2!, rand(2), Krylov(FixedForceTerm(0.4)))

function f_sparse!(F, x)
    F[1] = (x[1]+3)*(x[2]^3-7)+18
    F[2] = sin(x[2]*exp(x[1])-1)
end

function g_sparse!(J, x)
    J[1, 1] = x[2]^3-7
    J[1, 2] = 3*x[2]^2*(x[1]+3)
    u = exp(x[1])*cos(x[2]*exp(x[1])-1)
    J[2, 1] = x[2]*u
    J[2, 2] = u
end

function helical_valley()
    tpi = 8*atan(1)
    c7 = 2.5e-1
    c8 = 5e-1

    function f!(fvec, x)
        if x[1] > 0
            temp1 = atan(x[2]/x[1])/tpi
        elseif x[1] < 0
            temp1 = atan(x[2]/x[1])/tpi + c8
        else
            temp1 = c7*sign(x[2])
        end
        temp2 = sqrt(x[1]^2+x[2]^2)
        fvec[1] = 10(x[3] - 10*temp1)
        fvec[2] = 10(temp2 - 1)
        fvec[3] = x[3]
    end
    function fj!(F, J, x)
        if x[1] > 0
            temp1 = atan(x[2]/x[1])/tpi
        elseif x[1] < 0
            temp1 = atan(x[2]/x[1])/tpi + c8
        else
            temp1 = c7*sign(x[2])
        end
        temp2 = sqrt(x[1]^2+x[2]^2)
        if !isa(F, Nothing)
            F[1] = 10*(x[3] - 10*temp1)
            F[2] = 10*(temp2 - 1)
            F[3] = x[3]
        end
        if !isa(J, Nothing)
            temp = x[1]^2 + x[2]^2
            temp1 = tpi*temp
            temp2 = sqrt(temp)
            J[1,1] = 100*x[2]/temp1
            J[1,2] = -100*x[1]/temp1
            J[1,3] = 10
            J[2,1] = 10*x[1]/temp2
            J[2,2] = 10*x[2]/temp2
            J[2,3] = 0
            J[3,1] = 0
            J[3,2] = 0
            J[3,3] = 1
            if !isa(F, nothing)
                return F, J
            else
                return J
            end
        end

    end
    f!, fj!, rand(3), [-1.; 0; 0]), [-1.; 0; 0], "Helical Valley")
end


using LinearAlgebra, ForwardDiff, NLPModels, Random
Random.seed!(0)

export LBFGS_StrongWolfe

include("StrongWolfe.jl")

function LBFGS_StrongWolfe(
  nlp::AbstractNLPModel;
  m = 5,  # Memory reserved
  a1 = 1.0, # Initial Step
  η1::Float64 = 1e-4, # Armijo Condition
  η2::Float64 = 0.8, # StrongWolfe
  p::Float64 = 0.9, # Line Search Step
  atol::Real = 1e-6,
  rtol::Real = 1e-6,
  max_eval::Int = 1000,
  max_iter::Int = 0,
  max_time::Float64 = 10.0,
  )

  if !unconstrained(nlp)
    error("Problem is not unconstrained")
  end

  x = copy(nlp.meta.x0)
  n = nlp.meta.nvar

  f(x) = obj(nlp, x)
  ∇f(x) = grad(nlp, x)
  fx = f(x)
  ∇fx = ∇f(x)

  ϵ = atol + rtol * norm(∇fx)
  t₀ = time()

  iter = 0
  Δt = time() - t₀
  solved = norm(∇fx) < ϵ # First order stationary
  tired = neval_obj(nlp) ≥ max_eval > 0|| iter ≥ max_iter > 0 || Δt ≥ max_time > 0 # Excess time, iteration, evaluations

  status = :unknown

  @info log_header(
  [:iter, :fx, :ngx, :nf, :Δt],
  [Int, Float64, Float64, Int, Float64],
  hdr_override=Dict(:fx => "f(x)", :ngx => "‖∇f(x)‖", :nf => "#f")
  )
  @info log_row(
  Any[iter, fx, norm(∇fx), neval_obj(nlp), Δt]
  )

  Id = Matrix(1.0I, n, n)# Defino uma H0
  γ = 1
  r = - ∇fx
  y = zeros(n ,m)
  s = zeros(n, m)
  a = zeros(m)

  α, iteracao = StrongWolfe_LineSearch(nlp, x, r, a1, η1, η2, p) #Calcular um passo com α de Wolfe
  xt = x + α * r
  s[:, 1] = xt - x
  y[:, 1] = ∇f(xt) - ∇fx
  γ = dot(s[:, 1], y[:, 1]) / dot(y[:, 1], y[:, 1])
  H0 = γ * Id
  x = xt

  while !(solved || tired)

    r = - H0 * ∇fx
    α, iteracao = StrongWolfe_LineSearch(nlp, x, r, a1, η1, η2, p) #Calcular um passo com α de Wolfe
    xt = x + α * r #Atualiza o passo xₖ₊₁ = xₖ - αₖ r

    #calcular sₖ e yₖ
    s[:, iter%m+1] = xt - x
    y[:, iter%m+1] = ∇f(xt) - ∇fx
    x = xt

    H0 += ((s[:, iter%m+1] - H0 * y[:, iter%m+1]) * (s[:, iter%m+1] - H0 * y[:, iter%m+1])') / ((s[:, iter%m+1] - H0 * y[:, iter%m+1])' * y[:, iter%m+1])

    if iter > m
      q = ∇f(x)
      for i = ((iter - 1) % m + 1):-1:((iter - m) % m + 1)
        ρ = 1 / dot(y[:,i],s[:,i])
        a[i] = ρ * dot(s[:,i], q)
        q -= a[i] * y[:,i]
      end
      r = γ * H0 * q
      for i = ((iter - m) % m + 1):((iter - 1) % m + 1)
        ρ = 1 / dot(y[:,i],s[:,i])
        β = ρ * dot(y[:,i],r)
        r += s[:,i] * (a[i] - β) # retorna a direção r = Hₖ∇f
        r = -r
      end
    end

    fx = f(x)
    ∇fx = ∇f(x)

    iter += 1

    Δt = time() - t₀
    solved = norm(∇fx) < ϵ # First order stationary
    tired = neval_obj(nlp) ≥ max_eval > 0 || iter ≥ max_iter > 0 || Δt ≥ max_time > 0 # Excess time, iteration, evaluations

    @info log_row(
    Any[iter, fx, norm(∇fx), neval_obj(nlp), Δt]
    )
  end

  if solved
    status = :first_order
  elseif tired
    if neval_obj(nlp) ≥ max_eval > 0
      status = :max_eval
    elseif iter ≥ max_iter > 0
      status = :max_iter
    elseif Δt ≥ max_time > 0
      status = :max_time
    end
  end

  return GenericExecutionStats(
  status,
  nlp,
  solution=x,
  objective=f(x),
  dual_feas=norm(∇fx),
  elapsed_time=Δt,
  iter=iter
  )
end


include("fbase.jl")
include("quadrature.jl")
include("utils.jl")

include("melem_rt0.jl")

using LinearAlgebra

function melemk0P2(verts::Array{Float64, 2})
    #= Compute the A = ∫div(v) div(u) and the B = ∫v u element-matrices when the
        approximation order is k=0. In that case, there are no interior degrees
        of freedom and the amount of dofs is exactly the number of edges =#
    nvtx = size(verts, 2)
    ndof = nvtx
    # 0  : preliminaries
    E  = polygonArea(verts)         # area
    xE = polygonCentroid(verts)     # centroid
    hE = polygonDiameter(verts)     # diameter

    # 1st: compute A; it is easily done thanks to k=0
    DIV = ones(Float64, ndof, ndof) / E

    # 2nd: compute B; somehow a little more difficult as a projection needs to
    # be computed
    gmα = getGradientMonomialsP2(xE, hE)
    ref2d = quadGL2d(3)

    Παβ = zeros(Float64, 5, 5)
    for α=1:5
        for β = 1:5
            Παβ[α,β] = integratePolygon(verts, xE, ref2d,
                x -> dot(gmα[α+1](x), gmα[β+1](x)))
        end
    end
    # RHS: first term: null!
    # RHS: second term
    # initialization of the interior basis functions
    mα = getMonomialsP2(xE, hE)
    Cs = (-1/E)*integratePolygon(verts, xE, ref2d, mα) # average over polygon
    mα = getMonomialsP2(xE, hE, Cs) # 0-averaged monomials
    #
    rhs = zeros(Float64, 5, ndof)
    ref1d = quadGL1d(2, 0., 1.)
    # rhs[α, i] = int_{e_i}{((φ_i⋅n) Φ_α)} where φ_i⋅n = 1
    for i=1:ndof
        ip  = mod_wrap(i+1, ndof)        # return the next index modulo ndof
        e_v = verts[:, ip] - verts[:, i] # tangent to edge
        le  = norm(e_v)                  # length of edge
        for α=1:5
            # integration with quadrature rule
            rhs[α, i] = integrate(ref1d, t -> mα[α+1](verts[:, i] + t*e_v)/le)*le
        end
    end
    Π = Παβ \ rhs # Π is the projection matrix
    # now the Di,α = dof_i(m_α) = ∫(∇mα . n) q^i  matrix
    D = zeros(Float64, ndof, 5)
    for i=1:ndof
       ip  = mod_wrap(i+1, ndof)
       e_v = verts[:, ip] - verts[:, i]
       le  = norm(e_v)
       n_v = e_v / le          # normalized tangent
       n_v = [n_v[2], -n_v[1]] # outside pointing normal vector
       for α = 1:5
           D[i,α] = integrate(ref1d,
            t -> dot(gmα[α+1]( verts[:, i] + t*e_v ), n_v))*le
       end
    end
    # stability constant
    σ    = 1.
    F    = Matrix(1.0I, ndof, ndof) - D*Π
    MASS = Π'*(Παβ*Π) + σ*(F' * F)

    # 3rd:  both parts have been computed
    return DIV, MASS
end

function melemk0P1(verts::Array{Float64, 2})
    #= Compute the A = ∫div(v) div(u) and the B = ∫v u element-matrices when the
        approximation order is k=0. In that case, there are no interior degrees
        of freedom and the amount of dofs is exactly the number of edges =#
    nvtx = size(verts, 2)
    ndof = nvtx
    # 0  : preliminaries
    E  = polygonArea(verts)         # area
    xE = polygonCentroid(verts)     # centroid
    hE = polygonDiameter(verts)     # diameter

    # 1st: compute A; it is easily done thanks to k=0
    DIV = ones(Float64, ndof, ndof) / E

    # 2nd: compute B; somehow a little more difficult as a projection needs to
    # be computed
    gmα = getGradientMonomialsP1(xE, hE)
    ref2d = quadGL2d(1)

    Παβ = zeros(Float64, 2, 2)
    for α=1:2
        for β = 1:2
            # quadrature rule not really necessary as everythin is constant
            # but it mimics the code of melemk0P2
            Παβ[α,β] = integratePolygon(verts, xE, ref2d,
                x -> dot(gmα[α+1](x), gmα[β+1](x)))
        end
    end
    # RHS: first term: null!
    # RHS: second term
    # initialization of the interior basis functions
    mα = getMonomialsP1(xE, hE)
    Cs = (-1/E)*integratePolygon(verts, xE, ref2d, mα) # average over polygon
    mα = getMonomialsP1(xE, hE, Cs) # 0-averaged monomials
    #
    rhs = zeros(Float64, 2, ndof)
    ref1d = quadGL1d(2, 0., 1.)
    # rhs[α, i] = int_{e_i}{((φ_i⋅n) Φ_α)} where φ_i⋅n = 1/le
    for i=1:ndof
        ip  = mod_wrap(i+1, ndof)
        e_v = verts[:, ip] - verts[:, i]
        le  = norm(e_v)
        for α=1:2
            # rhs[α, i] = integrate(ref1d, t -> mα[α+1](verts[:, i] + t*e_v)/le)*le
            rhs[α, i] = mα[α+1]((verts[:, ip] + verts[:, i])/2); # with the midpoint rule
        end
    end
    Π = Παβ \ rhs # Π is the projection matrix
    # now the Di,α = dof_i(m_α) = ∫(∇mα . n) q^i  matrix
    D = zeros(Float64, ndof, 2)
    for i=1:ndof
       ip  = mod_wrap(i+1, ndof)
       e_v = verts[:, ip] - verts[:, i]
       le  = norm(e_v)
       n_v = e_v / le
       n_v = [n_v[2], -n_v[1]]
       for α = 1:2
            D[i,α] = integrate(ref1d,
            t -> dot(gmα[α+1]( verts[:, i] + t*e_v ), n_v))*le
            # D[i,α] = dot(gmα[α+1](0.), n_v)*le # if you wish the midpoint rule, comment out
       end
    end
    # stability constant
    σ    = 1.
    F    = Matrix(1.0I, ndof, ndof) - D*Π
    MASS = Π'*(Παβ*Π) + σ*(F' * F)

    # 3rd:  both parts have been computed
    return DIV, MASS
end


function QUBO_base(n, weight = 1)
    N = n^2
    M = zeros(N, N)
    for k in 1:n, j in ((k-1)*n+1):(k*n), i in ((k-1)*n+1):(k*n)
        M[j,i] = weight
    end
    return M - 2 * weight * I
end


#!/usr/bin/env julia
#=
# Searching all vertices in graph/tree in a breadth-first motion,
# marking each vertex as visited/not-visited while avoiding cycles,
# uses queue to remember next vertex to start search, iteration on dead-end
#
# We've implemented in 3 ways here
# * xbfs() uses a clever single queue based approach
# * ybfs() uses recursion with 2 queues
# * zbfs() uses external 2 queues
=#

function mark_neighbours(vertices, linkings)
  result = Dict(v => Set{eltype(vertices)}() for v in vertices)
  for (vertex,neighbour) in linkings
    push!(result[vertex], neighbour)
    push!(result[neighbour], vertex)
  end
  result
end

function xbfs(adjlist, source, dest)
    distances = Dict(source => 0)
    queue = [source]
    route = [source]

    while !isempty(queue)
        current = pop!(queue)

        if current == dest
                push!(route,current)
                println(route)
            return distances[dest]
        end

        for neighbour in adjlist[current]
            if !haskey(distances, neighbour)
                distances[neighbour] = distances[current] + 1
                push!(queue, neighbour)
              else
                if !in(current, route)
                  push!(route,current)
                end
            end
        end
    end

   -1
end
xbfs(people, friendships, source, dest) = xbfs(mark_neighbours(people, friendships), source, dest)

function ybfs(neighbour_map, source, destination, link = [], marked = [])
  if in(source, zmarked)
    return []
  end
  append!(link, [ source ])
  if source == destination
    return link
  end

  append!(marked, [ source ])

  for neighbour in neighbour_map[source]
    if neighbour == destination
      append!(link, [ neighbour ])
      return link
    end
  end

  for neighbour in neighbour_map[source]
    if in(destination, neighbour_map[neighbour])
      append!(link, [ neighbour, destination ])
      return link
    end
  end

  for neighbour in neighbour_map[source]
    if in(neighbour, marked)
      continue
    end
    tmp_link = ybfs(neighbour_map, neighbour, destination, link, marked)
    if length(tmp_link) != 0
      return tmp_link
    end
  end
  []
end

zlink, zmarked, bfs_fin = [], [], false
function zbfs(neighbour_map, source, destination)
  global zlink
  global zmarked
  if in(source, zmarked)
    return []
  end
  append!(zlink, [ source ])
  if source == destination
    return zlink
  end

  append!(zmarked, [ source ])

  for neighbour in neighbour_map[source]
    if neighbour == destination
      append!(zlink, [ neighbour ])
      return zlink
    end
  end

  for neighbour in neighbour_map[source]
    if in(destination, neighbour_map[neighbour])
      append!(zlink, [ neighbour, destination ])
      return zlink
    end
  end

  for neighbour in neighbour_map[source]
    if in(neighbour, zmarked)
      continue
    end
    tmp_link = zbfs(neighbour_map, neighbour, destination)
    if length(tmp_link) != 0
      return tmp_link
    end
  end
  []
end

const people = Set([:jean, :javert, :cosette, :gavroche, :éponine, :marius, :alice])
const friendships = [
    (:jean, :cosette),
    (:jean, :marius),
    (:cosette, :éponine),
    (:cosette, :marius),
    (:gavroche, :éponine),
    (:gavroche, :alice),
]

println("-------------------------------------------")

@time println(xbfs(people, friendships, :jean, :jean))
@time println(xbfs(people, friendships, :jean, :cosette))
@time println(xbfs(people, friendships, :jean, :gavroche))
@time println(xbfs(people, friendships, :jean, :javert))
@time println(xbfs(people, friendships, :jean, :alice))

println("-------------------------------------------")

@time println(ybfs(mark_neighbours(people, friendships), :jean, :jean))
@time println(ybfs(mark_neighbours(people, friendships), :jean, :cosette))
@time println(ybfs(mark_neighbours(people, friendships), :jean, :gavroche))
@time println(ybfs(mark_neighbours(people, friendships), :jean, :javert))
@time println(ybfs(mark_neighbours(people, friendships), :jean, :alice))

println("-------------------------------------------")

zlink, zmarked, bfs_fin = [], [], false
@time println(zbfs(mark_neighbours(people, friendships), :jean, :jean))
zlink, zmarked, bfs_fin = [], [], false
@time println(zbfs(mark_neighbours(people, friendships), :jean, :cosette))
zlink, zmarked, bfs_fin = [], [], false
@time println(zbfs(mark_neighbours(people, friendships), :jean, :gavroche))
zlink, zmarked, bfs_fin = [], [], false
@time println(zbfs(mark_neighbours(people, friendships), :jean, :javert))
zlink, zmarked, bfs_fin = [], [], false
@time println(zbfs(mark_neighbours(people, friendships), :jean, :alice))

println("-------------------------------------------")


### A Pluto.jl notebook ###
# v0.14.9

using Markdown
using InteractiveUtils

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

# ╔═╡ a760fb60-3f05-11ec-1040-258671c4c205
begin
	using Plots
	using DifferentialEquations
	using PlutoUI
end

# ╔═╡ 3d606a54-b43e-4846-a06e-8028f1576267
md"# Climate Change Modelling"

# ╔═╡ c101002d-6918-4f6d-af97-8eebcd609be9
md"""
#### 1) Unrealistic Heating Model(**Earth Baking Model**)
"""

# ╔═╡ d8ddb39f-08f6-450a-ab57-b47aa0251492
md"There are many Climate Change Models This is a simple graph of one, where we assume that from each year after 1850 we emmit green house gases"

# ╔═╡ 0f8cc3ec-c355-4f6e-801b-f660250b727b
md"""
This model can be expressed as
> **Cₜ = C₀ * ϵ (simplified Equation)**
where Cₜ is the temperature at time 𝓉 C₀ is the temperature at 1850 and ϵ is the factor by which the temperature increases
"""

# ╔═╡ 34ea6628-81bc-4b78-bf09-8394f326543c
md"""
This model is completly unrealistic as it says that we would have a temperature of around 800°C which cannot hold life
"""

# ╔═╡ 4bb137bd-1ab5-4ee2-9371-050c206e5577
md"""
#### 2) **Healthy Earth Model** (With no human induced green house gases)
"""

# ╔═╡ ee7eb18a-2978-49c8-a3a4-de76cb877d9d
md"""
Another way to model the earth climate is by introducing the cooling mechanisim of earth where a sun ray hits the earth temperature (**heating**)(𝒮) and the earth reflects the sun rays back to space(**cooling**)(α)
"""

# ╔═╡ c0a9fd3c-a601-447a-8ac6-32091303a2d5
md"""
Model can be expressed as
> **cooling of earth = 𝒮(1 - α)/4**
where 𝒮 is the incoming solar radiation with a value of **1368 W/m²** and
α is the reflectivity of the earth with a value of **0.3** \
\
So combing The heating and cooling equations we get the equation for a healthy
Earth Simulation
> **Health Earth Simulation = Cooling Of Earth - Heating of Earth**
"""

# ╔═╡ dcb1a918-cbfb-47e4-ad75-9827e46735c3
md"""
> Below here is a simulation of a **Healthly Earth Temperature Simulation** where if you adjust the slider which the starting point of Earth's temperature, Earth will always cool itself down This process occured for millions and millions of years without the intervention of **human induced green house gases**
"""

# ╔═╡ d2860b33-0434-4e33-8817-e2e35b958992
md"Earth Start Temperature $(@bind start_temp Slider(0.1:0.01:25.1,show_value=true)) °C"

# ╔═╡ ef7e8293-bc42-4b38-81b2-c4a1d2ca7235
md"""
# Climate Change!
"""

# ╔═╡ d35ee881-7353-46e1-8e9f-15a5ecbc12ce
md"""
#### 3) Temperature Rise With **Human Induced Green House Gases Model**
"""

# ╔═╡ 223453e4-3642-4953-a942-53effa6c126b
md"""
We can model climate change by equation
> **Cₜ = (cooling effect - heating effect) + human induced effect**
"""

# ╔═╡ 16488cb8-a129-4dff-a575-887407ed316c
md"""
> Slider Controls The Start **Temperature At 1850**, As you can see a **cooling effect occurs in the early 1850s** and after the industrial revolution the **human induced effect take place**
"""

# ╔═╡ 628ece4f-024e-440a-9183-e5d8011ecfcc
md"Human induced Climate Change Modelling °C  = $(@bind human_temp Slider(0.1:0.01:25.1,show_value=true)) °C"

# ╔═╡ b53a82f4-45a3-4657-8cb9-e4f09715570c

# ╔═╡ 4e88e20b-066c-4144-a913-e9be59a2395d
gr();

# ╔═╡ 26940047-17c7-4c55-9e94-27149af0e4f5
begin
	S = 1368.0
	α = 0.3
	C =  51.0
	temp0 = 14.10
	B = 1.5
	forcing_coef = 5.0
	CO2_PreIndustrial = 280.0
end;

# ╔═╡ 3f866044-279c-41c4-a34f-ba505332813d
p2 = ODEProblem((temp,p,t) -> (1/C) * B * (temp0 - temp),start_temp,(0.0,180.0));

# ╔═╡ 51482521-ef60-4cab-80ea-d68f21879f2e
begin
	plot(solve(p2),ylim=(0,35.0),label= "Healthy Earth Temperature",xlabel="Number Year",ylabel = "Number Temperature Scale")
	hline!( [14.1,14.1] ,c=:black,ls=:dash,label="Earth Temperature At 1850 PreIndustrial Age 14°C")
	title!("Healthy Earth Temperature Simulation")
	# annotate!(91.0,15.8,text("Global Temperature At 1850"))
end

# ╔═╡ 5aeef5ee-866b-488a-ab8e-757f9224b086
greenhouse_effect(CO2) = forcing_coef * log(CO2/CO2_PreIndustrial);

# ╔═╡ 14d6487f-62fc-40e6-9802-b0f6476e28e9
CO2(t) = CO2_PreIndustrial * (1 + (t/220)^3);

# ╔═╡ a32cbb16-29b9-4cb6-acec-9345a47e0c94
p3 = ODEProblem((temp,p,t) -> (1/C) * (B * (temp0 - temp) +  greenhouse_effect(CO2(t)) ),human_temp,(0.0,200.0));

# ╔═╡ 4c626313-91b7-42d2-bc1e-32ac704dd749
begin
	plot(solve(p3),xlabel="Number Year",ylabel="Temperature",label="Earth Temperature",ylims=(10.1,21.5),xlims=(0.0,181.0))
	hline!([temp0,temp0],c = :black,ls = :dash,label="Global Temperature At 1850 14°C")
	plot!([0, 51], [0.0, 0.5], fillrange=[25.1, 25.1], color=:lightblue, alpha=0.2, label="Pre Industrial Time")
	plot!([100.1, 181.3], [0.0, 0.5], fillrange=[25.1, 25.1], color=:red, alpha=0.2, label="Industrial Time Green House Gas Emission")
	# annotate!(53,15.8,text("Global Temperature At 1850"))
end

# ╔═╡ 670ec61d-0175-4f1d-9b52-a31576824c77
greenhouse_effect(CO2(159));

# ╔═╡ a17a3b04-4a46-446d-bf4b-00e119c0ef2a
absorbed_solar_radiation = S * (1 - α)/4;

# ╔═╡ 01d5f55c-452d-4848-966a-be20cc1ae0f4
p1 = ODEProblem((temp, p, t) -> (1/C) * absorbed_solar_radiation,temp0,(0.0,350.0));

# ╔═╡ 69f3aadc-21c4-46d1-8d7d-818057d144cc
begin
	plot(solve(p1),xlabel="years since 1850",xlims=(0.0,201.1),ylabel="temperature",label="Earth Temperature")
	hline!( [14.1,14.1] ,c=:black,ls=:dash,label="Global Earth Temperature At 1850 14°C")
end

# ╔═╡ 8d4e7794-bdf9-4ffa-8716-aebef40cfc0e
temp_human = @bind start_temp_human Slider(0.1:0.01:25.1,show_value=true);

# ╔═╡ 54cafc8c-aac2-40b5-ba23-ef93045ba60e
gr();

# ╔═╡ Cell order:
# ╟─a760fb60-3f05-11ec-1040-258671c4c205
# ╟─3d606a54-b43e-4846-a06e-8028f1576267
# ╟─c101002d-6918-4f6d-af97-8eebcd609be9
# ╟─d8ddb39f-08f6-450a-ab57-b47aa0251492
# ╟─0f8cc3ec-c355-4f6e-801b-f660250b727b
# ╟─69f3aadc-21c4-46d1-8d7d-818057d144cc
# ╟─34ea6628-81bc-4b78-bf09-8394f326543c
# ╟─4bb137bd-1ab5-4ee2-9371-050c206e5577
# ╟─ee7eb18a-2978-49c8-a3a4-de76cb877d9d
# ╟─c0a9fd3c-a601-447a-8ac6-32091303a2d5
# ╟─dcb1a918-cbfb-47e4-ad75-9827e46735c3
# ╟─d2860b33-0434-4e33-8817-e2e35b958992
# ╟─51482521-ef60-4cab-80ea-d68f21879f2e
# ╟─ef7e8293-bc42-4b38-81b2-c4a1d2ca7235
# ╟─d35ee881-7353-46e1-8e9f-15a5ecbc12ce
# ╟─223453e4-3642-4953-a942-53effa6c126b
# ╟─16488cb8-a129-4dff-a575-887407ed316c
# ╟─628ece4f-024e-440a-9183-e5d8011ecfcc
# ╟─4c626313-91b7-42d2-bc1e-32ac704dd749
# ╟─b53a82f4-45a3-4657-8cb9-e4f09715570c
# ╟─3f866044-279c-41c4-a34f-ba505332813d
# ╟─5aeef5ee-866b-488a-ab8e-757f9224b086
# ╟─4e88e20b-066c-4144-a913-e9be59a2395d
# ╟─a32cbb16-29b9-4cb6-acec-9345a47e0c94
# ╟─670ec61d-0175-4f1d-9b52-a31576824c77
# ╟─14d6487f-62fc-40e6-9802-b0f6476e28e9
# ╟─26940047-17c7-4c55-9e94-27149af0e4f5
# ╟─a17a3b04-4a46-446d-bf4b-00e119c0ef2a
# ╟─01d5f55c-452d-4848-966a-be20cc1ae0f4
# ╟─8d4e7794-bdf9-4ffa-8716-aebef40cfc0e
# ╟─54cafc8c-aac2-40b5-ba23-ef93045ba60e


# SPDX-License-Identifier: X11
# 2020-08-30
# Multiplication 4 (500pt)

const M = 1000000007

function main()
  N, K = parse.(Int, split(readline(), " "))
  A = parse.(Int, split(readline(), " "))
  T = 1

  X = maximum(A)
  if X == 0 && K & 1 == 1
    println(0)
    return
  elseif N == K
    for i ∈ A
      T *= i ⨳ M
      T = T ⨳ M
    end
    println(T)
    return
  end

  sort!(A; lt = test, rev = true)

  if X < 0 && K & 1 == 1
    B = A[end + 1 - K:end]
  elseif X <= 0 && K & 1 == 0
    B = A[1:K]
    if B[K] == 0
      println(0)
      return
    end
  else
    B = A[1:K]
    if oddneg(B)
      if A[K + 1] == 0
        println(0)
        return
      end

      R = A[K + 1:end]
      rfp = firstpos(R) === nothing ? -1 : firstpos(R)
      rfpv = rfp == -1 ? rfp : R[rfp]
      rfn = firstneg(R) === nothing ? -1 : firstneg(R)
      rfnv = rfn == -1 ? rfn : abs(R[rfn])
      blp = lastpos(B)
      blpv = blp === nothing ? blp : B[blp]
      bln = lastneg(B)
      blnv = abs(B[bln])

      if blp === nothing || rfpv//blnv > rfnv//blpv
        B[bln] = R[rfp]
      elseif rfpv//blnv < rfnv//blpv
        B[blp] = R[rfn]
      elseif rfpv//blnv == rfnv//blpv
        B[K] = B[K] < 0 ? R[rfp] : R[rfn]
      end
    end
  end

  for i ∈ B
    T *= i ⨳ M
    T = T ⨳ M
  end

  println(T)
end

function test(x::Int, y::Int)
  return abs(x) == abs(y) ? x < y : abs(x) < abs(y);
end

⨳(x, y) = mod(x, y)
oddneg(A) = count(x -> x < 0, A) & 1 == 1
firstneg(A) = findfirst(x -> x < 0, A)
firstpos(A) = findfirst(x -> x > 0, A)
lastneg(A) = findlast(x -> x < 0, A)
lastpos(A) = findlast(x -> x > 0, A)

main()