# MIT License

#Copyright (c) 2021 hondoRandale <jules.rasetaharison@tutanota.com> and contributors

#Permission is hereby granted, free of charge, to any person obtaining a copy
#of this software and associated documentation files (the "Software"), to deal
#in the Software without restriction, including without limitation the rights
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#copies of the Software, and to permit persons to whom the Software is
#furnished to do so, subject to the following conditions:

#The above copyright notice and this permission notice shall be included in all
#copies or substantial portions of the Software.

#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
#SOFTWARE.

include( "benchmarkOptimProblems.jl" )

using SlimeMoldOptim
using Test

@testset "SlimeMoldOptim.jl" begin
    # 2-D objective functions
    @test Beale_2D( [3.0f0,0.5f0]; kwargs=() )                     ≈ 0.0f0
    @test typeof( Beale_2D( [3.0f0,0.5f0]; kwargs=() ) )          == Float32

    @test Himmelblau_2D( [ 3.0f0, 2.0f0 ]; kwargs=() )             ≈ 0.0f0
    @test typeof( Himmelblau_2D( [ 3.0f0, 2.0f0 ]; kwargs=() ) )  == Float32
    @test Himmelblau_2D( [ -2.805118f0,  3.131312f0 ]; kwargs=() ) ≈ 0.0f0 atol=1f-3
    @test Himmelblau_2D( [ -3.779310f0, -3.283186f0 ]; kwargs=() ) ≈ 0.0f0 atol=1f-10
    @test Himmelblau_2D( [ 3.584428f0, -1.848126f0 ]; kwargs=() )  ≈ 0.0f0 atol=1f-5

    @test Levi_no13_2D( [ 1.0f0, 1.0f0 ]; kwargs=() )                      ≈ 0.0f0 atol=1f-15
    @test typeof( Levi_no13_2D( [ 1.0f0, 1.0f0 ]; kwargs=() ) )           == Float32

    @test Ackley_2D( [ 0.0f0, 0.0f0 ]; kwargs=() )                         ≈ 0.0f0
    @test typeof( Ackley_2D( [ 0.0f0, 0.0f0 ]; kwargs=() ) )              == Float32

    @test Booth_2D(  [ 1.0f0, 3.0f0 ]; kwargs=() )                         ≈ 0.0f0
    @test typeof( Booth_2D(  [ 1.0f0, 3.0f0 ]; kwargs=() ) )              == Float32

    @test Rosenbrock_2D( [ 1.0f0, 1.0f0 ]; kwargs=() )                     ≈ 0.0f0
    @test typeof( Rosenbrock_2D( [ 1.0f0, 1.0f0 ]; kwargs=() ) )          == Float32

    @test Matyas_2D( [ 0.0f0, 0.0f0 ]; kwargs=() )                         ≈ 0.0f0
    @test typeof( Matyas_2D( [ 0.0f0, 0.0f0 ]; kwargs=() ) )              == Float32

    @test Three_hump_camel_2D( [ 0.0f0, 0.0f0 ]; kwargs=() )               ≈ 0.0f0
    @test typeof( Three_hump_camel_2D( [ 0.0f0, 0.0f0 ]; kwargs=() ) )    == Float32

    @test Easom_2D( [ Float32(π), Float32(π) ]; kwargs=() )                ≈ -1.0f0
    @test typeof( Easom_2D( [ Float32(π), Float32(π) ]; kwargs=() ) )     == Float32

    @test Sphere_2D( [0.0f0, 0.0f0]; kwargs=() )                           ≈ 0.0f0
    @test typeof( Sphere_2D( [0.0f0, 0.0f0]; kwargs=() ) )                == Float32

    @test Schaffer_function_no2_2D( [0.0f0, 0.0f0]; kwargs=() )            ≈ 0.0f0
    @test typeof( Schaffer_function_no2_2D( [0.0f0, 0.0f0]; kwargs=() ) ) == Float32

    @test Bukin_function_no6_2D( [-10.0f0, 1.0f0 ]; kwargs=() )            ≈ 0.0f0
    @test typeof( Bukin_function_no6_2D( [-10.0f0, 1.0f0 ]; kwargs=() ) ) == Float32

    # N-D objective functions
    @test Michalewicz_ND( [ 2.20f0, 1.57f0 ]; kwargs=() )                          ≈ -1.8081267f0
    @test typeof( Michalewicz_ND( [ 2.20f0, 1.57f0 ]; kwargs=() ) )               == Float32

    @test Zakharov_ND( [ 0.0f0, 0.0f0 ]; kwargs=() )                               ≈ 0.0f0
    @test typeof( Zakharov_ND( [ 0.0f0, 0.0f0 ]; kwargs=() ) )                    == Float32

    @test AxisParallelHyperEll_ND( [ 0.0f0, 0.0f0 ]; kwargs=() )                   ≈ 0.0f0
    @test typeof( AxisParallelHyperEll_ND( [ 0.0f0, 0.0f0 ]; kwargs=() ) )        == Float32

    @test Sphere_ND( [ 0.0f0, 0.0f0 ]; kwargs=() )                                 ≈ 0.0f0
    @test typeof( Sphere_ND( [ 0.0f0, 0.0f0 ]; kwargs=() ) )                      == Float32

    @test Rastrigin_ND( [ 0.0f0, 0.0f0 ]; kwargs=() )                              ≈ 0.0f0
    @test typeof( Rastrigin_ND( [ 0.0f0, 0.0f0 ]; kwargs=() ) )                   == Float32

    @test Styblinski_Tang_ND( [ -2.903534f0, -2.903534f0 ]; kwargs=() )            ≈ -78.33233f0
    @test typeof( Styblinski_Tang_ND( [ -2.903534f0, -2.903534f0 ]; kwargs=() ) ) == Float32

    @test Exponential_ND( [ 0.0f0, 0.0f0 ]; kwargs=() )                            ≈ -1.0f0
    @test typeof( Exponential_ND( [ 0.0f0, 0.0f0 ]; kwargs=() ) )                 == Float32

    @test Griewank_ND( [ 0.0f0, 0.0f0 ]; kwargs=() )                               ≈ 0.0f0
    @test typeof( Griewank_ND( [ 0.0f0, 0.0f0 ]; kwargs=() ) )                    == Float32

    lower    = [ -10.0f0, -10.0f0 ];
    upper    = [ 10.0f0, 10.0f0 ];

    sma( objFunction ) = slimeMoldAlgo( 30, 1000, ();
                                        lower       = lower,
                                        upper       = upper,
                                        objFunction = objFunction,
                                        maxiter     = 1000,
                                        ϵ_conv      = 1f-20,
                                        z           = 0.03f0 )

   @test sum( [ sma( fObj ).minFGlobal for fObj ∈ obj_functions_2D ] ) < 0.0f0
   @test sum( [ sma( fObj ).minFGlobal for fObj ∈ obj_functions_Nd ] ) < 750.0f0
end


import Base.rand

export BallInf

"""
    BallInf{N<:Real} <: AbstractHyperrectangle{N}

Type that represents a ball in the infinity norm.

### Fields

- `center` -- center of the ball as a real vector
- `radius` -- radius of the ball as a real scalar (``≥ 0``)

### Notes

Mathematically, a ball in the infinity norm is defined as the set

```math
\\mathcal{B}_∞^n(c, r) = \\{ x ∈ \\mathbb{R}^n : ‖ x - c ‖_∞ ≤ r \\},
```
where ``c ∈ \\mathbb{R}^n`` is its center and ``r ∈ \\mathbb{R}_+`` its radius.
Here ``‖ ⋅ ‖_∞`` denotes the infinity norm, defined as
``‖ x ‖_∞ = \\max\\limits_{i=1,…,n} \\vert x_i \\vert`` for any
``x ∈ \\mathbb{R}^n``.

### Examples

Create the two-dimensional unit ball and compute its support function along the
positive ``x=y`` direction:

```jldoctest
julia> B = BallInf(zeros(2), 1.0)
BallInf{Float64}([0.0, 0.0], 1.0)
julia> dim(B)
2
julia> ρ([1., 1.], B)
2.0
```
"""
struct BallInf{N<:Real} <: AbstractHyperrectangle{N}
    center::Vector{N}
    radius::N

    # default constructor with domain constraint for radius
    function BallInf{N}(center::Vector{N}, radius::N) where {N<:Real}
        @assert radius >= zero(N) "radius must not be negative"
        return new{N}(center, radius)
    end
end

# convenience constructor without type parameter
BallInf(center::Vector{N}, radius::N) where {N<:Real} =
    BallInf{N}(center, radius)

# --- AbstractHyperrectangle interface functions ---

"""
    radius_hyperrectangle(B::BallInf{N}, i::Int)::N where {N<:Real}

Return the box radius of a infinity norm ball in a given dimension.

### Input

- `B` -- infinity norm ball
- `i` -- dimension of interest

### Output

The box radius of the ball in the infinity norm in the given dimension.
"""
function radius_hyperrectangle(B::BallInf{N}, i::Int)::N where {N<:Real}
    return B.radius
end

"""
    radius_hyperrectangle(B::BallInf{N})::Vector{N} where {N<:Real}

Return the box radius of a infinity norm ball, which is the same in every
dimension.

### Input

- `B` -- infinity norm ball

### Output

The box radius of the ball in the infinity norm.
"""
function radius_hyperrectangle(B::BallInf{N})::Vector{N} where {N<:Real}
    return fill(B.radius, dim(B))
end

# --- AbstractCentrallySymmetric interface functions ---

"""
    center(B::BallInf{N})::Vector{N} where {N<:Real}

Return the center of a ball in the infinity norm.

### Input

- `B` -- ball in the infinity norm

### Output

The center of the ball in the infinity norm.
"""
function center(B::BallInf{N})::Vector{N} where {N<:Real}
    return B.center
end

# --- LazySet interface functions ---

"""
    radius(B::BallInf, [p]::Real=Inf)::Real

Return the radius of a ball in the infinity norm.

### Input

- `B` -- ball in the infinity norm
- `p` -- (optional, default: `Inf`) norm

### Output

A real number representing the radius.

### Notes

The radius is defined as the radius of the enclosing ball of the given
``p``-norm of minimal volume with the same center.
"""
function radius(B::BallInf, p::Real=Inf)::Real
    return (p == Inf) ? B.radius : norm(fill(B.radius, dim(B)), p)
end

"""
    rand(::Type{BallInf}; [N]::Type{<:Real}=Float64, [dim]::Int=2,
         [rng]::AbstractRNG=GLOBAL_RNG, [seed]::Union{Int, Nothing}=nothing
        )::BallInf{N}

Create a random ball in the infinity norm.

### Input

- `BallInf` -- type for dispatch
- `N`       -- (optional, default: `Float64`) numeric type
- `dim`     -- (optional, default: 2) dimension
- `rng`     -- (optional, default: `GLOBAL_RNG`) random number generator
- `seed`    -- (optional, default: `nothing`) seed for reseeding

### Output

A random ball in the infinity norm.

### Algorithm

All numbers are normally distributed with mean 0 and standard deviation 1.
Additionally, the radius is nonnegative.
"""
function rand(::Type{BallInf};
              N::Type{<:Real}=Float64,
              dim::Int=2,
              rng::AbstractRNG=GLOBAL_RNG,
              seed::Union{Int, Nothing}=nothing
             )::BallInf{N}
    rng = reseed(rng, seed)
    center = randn(rng, N, dim)
    radius = abs(randn(rng, N))
    return BallInf(center, radius)
end

"""
    translate(B::BallInf{N}, v::AbstractVector{N}) where {N<:Real}

Translate (i.e., shift) a ball in the infinity norm by a given vector.

### Input

- `B` -- ball in the infinity norm
- `v` -- translation vector

### Output

A translated ball in the infinity norm.

### Algorithm

We add the vector to the center of the ball.
"""
function translate(B::BallInf{N}, v::AbstractVector{N}) where {N<:Real}
    @assert length(v) == dim(B) "cannot translate a $(dim(B))-dimensional " *
                                "set by a $(length(v))-dimensional vector"
    return BallInf(center(B) + v, B.radius)
end


# This file is pat of RLinearAlgebra.jl
# 1. Specifies type
# 2. Implements rsubsolve function
# 3. Exports Type

#using LinearAlgebra

"""
    LinSysVecColProjStd <: LinSysVecColProjection

A mutable structure that represents a standard column projection method.

See Luo, Zhi-Quan, and Paul Tseng. "On the convergence of the coordinate descent method
    for convex differentiable minimization." Journal of Optimization Theory and
    Applications 72.1 (1992): 7-35.

For a generalization, see Patel, Vivak, Mohammad Jahangoshahi, and Daniel Adrian Maldonado.
    "An Implicit Representation and Iterative Solution of Randomly Sketched Linear Systems."
    SIAM Journal on Matrix Analysis and Applications (2021) 42:2, 800-831.
    https://doi.org/10.1137/19M1259481

# Aliases
- `CoordinateDescent`
- `GaussSeidel`

# Fields
- `alpha::Float64`, a relaxation parameter that should be set between `0.0` and `2.0`

Calling `LinSysVecColProjStd()` defaults the relaxatoin parameter to `1.0`.

"""
mutable struct LinSysVecColProjStd <: LinSysVecColProjection
    α::Float64
end
CoordinateDescent = LinSysVecColProjStd
GaussSeidel = LinSysVecColProjStd

LinSysVecColProjStd() = LinSysVecColProjStd(1.0)

# Common rsubsolve interface for linear systems
function rsubsolve!(
    type::LinSysVecColProjStd,
    x::AbstractVector,
    samp::Tuple{U,V,W} where {U<:AbstractVector,V<:AbstractArray,W<:Real},
    iter::Int64,
)
    # samp[1] is the search direction
    # samp[2] is the coefficient matrix A
    # samp[3] is the residual of normal system in A * samp[1], samp[1]' A' * (A * x - b)

    x .= x - samp[1] * (type.α * samp[3] / sum((samp[2] * samp[1]).^2))

    return nothing
end


# Interactive clickable FitzHughNagumo state space

using DrWatson
@quickactivate "NonlinearDynamicsTextbook"
include(srcdir("colorscheme.jl"))
using GLMakie, DynamicalSystems, InteractiveDynamics

a = 3.
b = -0.05
ε = 0.01
I = 0.0

fh = Systems.fitzhugh_nagumo(zeros(2); a, b, ε, I)

# Layout plot:
fig = Figure(resolution = (1000, 800))
ax = fig[1,1] = Axis(fig)
display(fig)
ax.xlabel = "u"
ax.ylabel = "w"
ax.title = "FitzHugh Nagumo, a=$a, b=$b"

# plot the two nullclines
us = -0.4:0.01:1.2
w1 = us
w2 = @. a*us*(us-b)*(1-us) + I
lines!(ax, us, w1; color = COLORS[1], linewidth = 2, linestyle = :dash)
lines!(ax, us, w2; color = COLORS[2], linewidth = 2, linestyle = :dot)

# Create trajectories on point selection
spoint = select_point(ax.scene)
on(spoint) do pos
    tr = trajectory(fh, 10000, SVector(pos...))
    lines!(ax, columns(tr)...;
        color = InteractiveDynamics.randomcolor(), linewidth = 4.0
    )
end

# %%
record_interaction(fig, string(@__FILE__)[1:end-2]*"mp4"; total_time = 10)


#
# Computes the moving average of the data
#

struct MovingAverage
  n :: Int64
  x :: Vector{Float64}
  R :: Float64
  residues :: Vector{Float64}
end

"""

`movingaverage(x,n)` or `movavg(x,n)`

Computes a moving average of `x[i]` in the range `i±(n-1)/2`. If `n` is even, we do `n ←  n + 1`.

# Example

```jldoctest
julia> x = rand(10);

julia> movingaverage(x,3)

 ------------------- Moving Average ----------

 Number of points averaged: 3 (± 1 points)

 Pearson correlation coefficient, R = 0.3532754137104625

 Averaged X: x = [0.5807828672543551, 0.40496733381946143...
 residues = [-0.22791917753944557, 0.4037347109743393...

 --------------------------------------------
```

"""
function movingaverage( X :: AbstractArray{<:Real}, n :: Int )
  if n == 0
    error(" Please set n with movavg(x,n=10), for example. ")
  end
  if ! isodd(n)
    n = n + 1
  end
  delta = round(Int64,(n-1)/2)
  nX = length(X)
  x = similar(X)
  for i in 1:nX
    jmin = max(i-delta,1)
    jmax = min(i+delta,nX)
    x[i] = 0.
    for j in jmin:jmax
      x[i] = x[i] + X[j]
    end
    x[i] = x[i] / (jmax-jmin+1)
  end
  residues = similar(X)
  for i in 1:nX
    residues[i] = X[i] - x[i]
  end
  R = Statistics.cor(x,X)
  return MovingAverage(n,x,R,residues)
end
movingaverage(X::AbstractArray{<:Real};n::Int=0) = movingaverage(X,n)
movavg = movingaverage

function Base.show( io :: IO, fit :: MovingAverage )
  println("")
  println(" ------------------- Moving Average ---------- ")
  println("")
  println(" Number of points averaged: $(fit.n) (± $(round(Int64,(fit.n-1)/2)) points)")
  println("")
  println(" Pearson correlation coefficient, R = ", fit.R)
  println("")
  println(" Averaged X: x = [",fit.x[1],", ",fit.x[2],"...")
  println(" residues = [", fit.residues[1],", ",fit.residues[2],"...")
  println("")
  println(" -------------------------------------------- ")
end

export movingaverage, movavg


using Test
using PETSc, MPI, LinearAlgebra, SparseArrays

MPI.Init()
PETSc.initialize()

# Constructs the central finite-difference approximation of -∇² for an lx×ly box of nx×ny points
# with Dirichlet boundary conditions. Based on the code from
# http://math.mit.edu/~stevenj/18.303/lecture-10.html
function laplace(T, nx::Integer, ny::Integer=nx, lx::Integer=1, ly::Integer=lx)
    dx = T(lx) / T(nx + 1)
    dy = T(ly) / T(ny + 1)
    Dx = [[one(T) spzeros(T, 1, nx - 1)]; spdiagm(1=>ones(T, nx - 1)) - I] / dx
    Dy = [[one(T) spzeros(T, 1, ny - 1)]; spdiagm(1=>ones(T, ny - 1)) - I] / dy
    Ax = Dx' * Dx
    Ay = Dy' * Dy
    A = kron(sparse(I, ny, ny), Ax) + kron(Ay, sparse(I, nx, nx))
    return A
end

T = Float64
nx = 20
S = laplace(T, nx)

m, n = size(S)
M = PETSc.MatSeqAIJ(S)

ksp = PETSc.KSP(
    M;
    ksp_monitor_true_residual = true,
    ksp_view = true,
    ksp_type = "cg",
    ksp_rtol = 1e-8,
    pc_type = "gamg",
    # I think these are not being set properly:
    mg_levels_ksp_type = "chebyshev",
    mg_levels_ksp_max_it = 3,
    mg_levels_pc_type = "bjacobi",
    mg_levels_sub_pc_type = "icc",
    mg_coarse_ksp_type = "preonly",
    mg_coarse_pc_type = "cholesky",
)

# initial guess
x = PETSc.VecSeq(zeros(m))
b = PETSc.VecSeq(randn(n))
PETSc.solve!(x, ksp, b)

PETSc.destroy(ksp)
PETSc.destroy(M)
PETSc.destroy(b)
PETSc.destroy(x)

PETSc.finalize()


function right_angle_check(x1::Int, y1::Int, x2::Int, y2::Int)
    if x1 == 0 && x2 == 0
        return 0
    end

    if x1 == x2
        if y1 == 0
            return 1
        end
        return 0
    end

    if x1 == 0
        if y2 == y1 || y2 == 0 || y2//x2 * (y2 - y1)//(x2 - x1) == -1
            return 1
        end
        return 0
    end

    if x2 == 0
        if y1 == y2 || y1 == 0 || y1//x1 * (y2 - y1)//(x2 - x1) == -1
            return 1
        end
        return 0
    end

    g_1 = y1//x1
    g_2 = y2//x2
    g_3 = (y2 - y1)//(x2 - x1)

    if g_1 * g_2 == -1 || g_1 * g_3 == -1 || g_2 * g_3 == -1
        return 1
    end

    return 0
end

function count_triangles(dim::Int)
    count = 0
    for p in 1:(dim ^ 2 - 2)
        for q in (p + 1):(dim ^ 2 - 1)
            count += right_angle_check(p % dim, Int(floor(p/dim)), q % dim, Int(floor(q/dim)))
        end
    end
    println(count)
end

count_triangles(51)


using Printf

f(x)  = x^2 + sin(x)
df(x) = 2x + cos(x)
a = 1.0

for k = 1:10
    h = 10.0^(-k)
    central_diff = ( f(a+h) - f(a-h) ) / (2h)
    complex_diff = imag( f(complex(a, h)) ) / h
    deriv = df(a)
    @printf("\$10^{-%d}\$& %10.2e& %10.2e\\\\\n",
            k, central_diff-deriv, complex_diff-deriv)
end


using Base.Test
using Astrodynamics

mu = 398600.448073446
rv = [8.59072560e+02, -4.13720368e+03, 5.29556871e+03, 7.37289205e+00, 2.08223573e+00, 4.39999794e-01]
rvmat = [rv';rv';rv']
ele = [6.78085866e+03,   1.30546073e-03,   9.00610999e-01, 3.46237544e+00,   6.85258061e-01,   8.13258968e-01]
elemat = [ele';ele';ele']
@test_approx_eq ele rvtokepler(rv, mu)
@test_approx_eq elemat rvtokepler(rvmat, mu)
@test_approx_eq rv keplertorv(ele, mu)
@test_approx_eq rvmat keplertorv(elemat, mu)


@polyvar x y
@variable(model, t)
S = @set x^2 + y^2 == 1 && y - x^2 == 0.5 && x >=0 && y>=0.5
@constraint(model, x + y >= t, domain = S, maxdegree = 2)
@objective(model, Max, t)
optimize!(model)
println("Solution: $(value(t))")
# Returns t ~ 1.3911


# Tests light-travel time delay: planets with
# semi-major axis as follows:
a = [0.232456517,1.162282585,2.32456517]
# with time offset between secondary eclipse
# and transit of:
dt = [0.5027,0.51337,0.52643]-0.5
# Convert to minutes:
dt *= 24.*60.
# for an orbital period of a day.

# Define some constants:

C  = 2.99792458e10
AU = 1.49598e13

# Expected time delay (for circular orbit) is 2a/c:
dt_mod = 2*a*AU/C/60.   # In minutes

using PyPlot
clf()
loglog(a,dt_mod,linewidth=3,label=L"$\Delta t = 2a/c$ ",c="r")
scatter(a,dt,s=50.,label=L"$\Delta t$ from starry")
legend(loc="upper left")
xlabel("Semi-major axis [AU]")
ylabel("Time delay [min]")
axis([.2,3,3.,40.])

println(dt - dt_mod)


using Slomo.CosmologyTools: Ωm, ρcrit, ρm, default_cosmo
using Slomo.Constants: rho_crit_h2

using Test

cosmo = default_cosmo

z = [0.0, 0.1, 0.5]
@test size(Ωm.(z; cosmo = cosmo)) == size(z)
@test size(ρcrit.(z; cosmo = cosmo)) == size(z)
@test size(ρm.(z; cosmo = cosmo)) == size(z)

z = 0
@test Ωm(z; cosmo = cosmo) == cosmo.Ω_m
@test ρcrit(z; cosmo = cosmo) == rho_crit_h2 * cosmo.h ^ 2
@test ρm(z; cosmo = cosmo) == cosmo.Ω_m * rho_crit_h2 * cosmo.h ^ 2

z = 0.0
@test Ωm(z; cosmo = cosmo) == cosmo.Ω_m
@test ρcrit(z; cosmo = cosmo) == rho_crit_h2 * cosmo.h ^ 2
@test ρm(z; cosmo = cosmo) == cosmo.Ω_m * rho_crit_h2 * cosmo.h ^ 2


#!/usr/bin/env julia

"""
Methods for manipulating and operating on numerical digits.
"""
module Digits

"""
    is_palindrome(n::Integer; base::Integer = 10)

Determines if the natural number `n` is a palindrome (reads the same forwards
and backwards) when written in base `base`.

## Preconditions
- `n > 0`
- `base ≥ 2`

## Examples
```jldoctest
julia> is_palindrome(1)
true

julia> is_palindrome(404)
true

julia> is_palindrome(146)
false

julia> is_palindrome(146, base = 8)
true
```
"""
function is_palindrome(n::Integer; base::Integer = 10)
    n_digits = digits(n, base = base)

    i = 1
    j = length(n_digits)
    while i < j
        if n_digits[i] != n_digits[j]
            return false
        end
        i += 1
        j -= 1
    end

    return true
end

end


function writeArrayForGnuplot(anArray, aFilename)
    writedlm(aFilename, anArray, '\t')
    anArray
end

type Clumped end
type Uniformed end

function drawBSpline(k, controlPoints, fittingPoints)

    if controlPoints[1,:] == controlPoints[end,:]

        m = length(controlPoints[:,1])
        newPartition = [i for i in (-k/(m-1):1/(m-1):(k+m-1)/(m-1))]

        M = sum(ones(m))
        matrix = buildFunctionsMatrix(k, M, newPartition)

        for i=1:k
            controlPoints = [controlPoints; controlPoints[i+1,:]]
        end

        return drawCurve(newPartition, k, M, controlPoints, matrix, fittingPoints)
    end

end

function drawCurve(extendedPartition, k, M, controlPoints, matrix, fittingPoints)

    ## paramRange = linspace(minimum(extendedPartition),
    ##                       maximum(extendedPartition),
    ##                       fittingPoints)

    ## fittingPoints += 1
    paramRange = linspace(0,
                          1-eps(Float64),
                          fittingPoints)

    bspline = zeros(fittingPoints, length(controlPoints[1,:]))
    for i=1:fittingPoints
        bspline[i,:] = bsplineAtParam(paramRange[i],
                                      k,
                                      M,
                                      controlPoints,
                                      matrix)
    end
    bspline
    ## bspline[1:end-1, :]
end

function bsplineAtParam(t, k, M, controlPoints, matrix)
    value = zeros(length(controlPoints[1,:]))'
    ## if length(controlPoints[:,1]) != (k+M)
    ##     error("Control points aren't enough")
    ## end
    for i=1:(k+M)
        v = controlPoints[i,:]
        N_ik = matrix[i,k]
        value += v*N_ik(t)
    end
    value
end

function extendedPartitionAttempt(k, partition, spacing::Clumped)
    previous_zeros = zeros(k)
    following_ones = ones(k)
    M = length(partition)
    middle = zeros(M)
    range = linspace(1/(M+1), M/(M+1), M)
    for i=1:length(range)
        middle[i] = range[i]
    end
    [previous_zeros; middle; following_ones;]
end

function extendedPartitionAttempt(k, partition, spacing::Uniformed)
    M = length(partition)
    range = (1-k)/(M+1):1/(M+1):(1+ (k-1)/(M+1))
    nel = length(range)
    extended = zeros(nel)
    for i=1:nel
        extended[i] = range[i]
    end
    extended
end

function buildFunctionsMatrix(k, M, extendedPartition)
    if (2*k + M) != length(extendedPartition)
        error("partition length mismatch dimension")
    end
    firstColumn = []
    for i=1:(k+M+1)
        fn = (t ->
              if (extendedPartition[i] <= t && t < extendedPartition[i+1])
                  1
              else
                  0
              end)
        firstColumn = [firstColumn; fn;]
    end
    ## firstColumn = [firstColumn; (t -> 0);]

    matrix = [firstColumn]
    for h=2:k
        column = []
        for i=1:(k+M)

            augend_coeff = 0
            augend_denominator = extendedPartition[i+h-1] - extendedPartition[i]
            if augend_denominator != 0
                augend_coeff =  1 / augend_denominator
            end

            addend_coeff = 0
            addend_denominator = extendedPartition[i+h] - extendedPartition[i+1]
            if addend_denominator != 0
                addend_coeff =  1 / addend_denominator
            end

            fn = t -> ((t - extendedPartition[i]) * augend_coeff * matrix[i,h-1](t) +
                       (extendedPartition[i+h] - t) * addend_coeff * matrix[i+1,h-1](t))

            column = [column; fn;]
        end
        lastFn = (t ->
              if (extendedPartition[k+M+1] <= t && t < extendedPartition[k+M+2])
                  0
              else
                  0
              end)

        column = [column; lastFn;]
        matrix = [matrix column]
    end

    matrix

end

type Maximum end

function normalizeRespect(vector, _::Maximum, k = 1)
    m = maximum(vector[1:end - (k-1)])
    return map(t -> t/m, vector)
end

function expandPartition(partition, multiplicityVector)

    extended = []

    for i=1:length(multiplicityVector)
        augmenting = [partition[i] for _=1:multiplicityVector[i] ]
        extended = [extended; augmenting;]
    end

    extended
end

function knotsInsertion_head(controlPoints, partition, n, L)

    fittingPoints = 100
    working_partition = partition[n:n+L]
    paramRange = linspace(working_partition[1],
                          working_partition[end],
                          fittingPoints)

    ## u = working_partition[1] + rand()*(working_partition[end]-working_partition[1])

    results = zeros(fittingPoints, length(controlPoints[1,:]))
    for j=1:fittingPoints-1

        u = paramRange[j]

        workingControlPoints = controlPoints
        workingPartition = partition
        workingL = L

        for i=1:n
            workingControlPoints, workingPartition, workingL =
                knotsInsertion(workingControlPoints, workingPartition, n, workingL, u)
        end

        ## for i=1:length(workingControlPoints[:,1])-1
        ##     if(workingControlPoints[i,:] == workingControlPoints[i+1,:])
                k = findfirst(x -> x == u, workingPartition)
                results[j,:] = workingControlPoints[k,:]
        ##         break
        ##     end
        ## end
    end

    results[1:end-1,:]
end

function knotsInsertion(controlPoints, partition, n, L, u)
    ## working_partition = partition[n:n+L]
    ## ## u = working_partition[1] + rand()*(working_partition[end]-working_partition[1])
    ## u = 2.5

    ## if (count(x -> x == u, partition) == n)
    ##     return controlPoints, partition, n, L
    ## end

    I = -1
    for i = length(partition):-1:(n+1)
        if(partition[i] > u && partition[i-1] <= u)
            I = i-1
            break
        end
    end

    last_index = L+n+1
    refined_partition = zeros(last_index+1)

    for i=1:I-n+2
        refined_partition[i] = partition[i]
    end

    for i=I-n+3:I+1
        refined_partition[i] = (1/n)*(sum(partition[i:i+n-2]) + u)
    end

    for i=I+2:last_index+1
        refined_partition[i] = partition[i-1]
    end

    refined_controlPoints = zeros(last_index+1, length(controlPoints[1,:]))

    for i=1:I-n+2
        refined_controlPoints[i,:] = controlPoints[i,:]
    end

    for i=I-n+3:I+1
        diff = partition[i+n-1]-partition[i-1]
        coeff = if (diff > 0) 1/diff else 0 end
        first_coeff = partition[i+n-1] - u
        second_coeff = u - partition[i-1]
        refined_controlPoints[i,:] = coeff*(first_coeff*controlPoints[i-1,:] +
                                            second_coeff*controlPoints[i,:])
    end

    for i=I+2:last_index+1
        refined_controlPoints[i,:] = controlPoints[i-1,:]
    end

    partition = [partition; u]
    refined_controlPoints, sort(partition), L+1
end

function deBoor (extendedBreakpointArray,
                 controlPointsMatrix,
                 tabulationArray)

    dimension, numberOfControlPoints = size (controlPointsMatrix)
    k = length (extendedBreakpointArray) - numberOfControlPoints

    C = zeros (dimension, length (tabulationArray))
    ind = 0

    for r = k:numberOfControlPoints

        comparison = r < n ? .< : .<= ;
        isd = (tabulationArray .>= extendedBreakpointArray [r]) & (comparison(tabulationArray, extendedBreakpointArray [r+1]))

        nloc = sum (isd)

        if nloc < 1
            continue
        end

        tloc = tabulationArray[isd]
        Qloc = zeros (k, nloc, dimension)

        ## the following is a one line in substitution of the next line of code
        ## broadcast(+, [[1 2 5]; [3 4 6];], zeros(2, 3, 2))
        [Qloc[:,:,i] = Q[i, r-k+1:r]' * ones(1,nloc) for i=1:dimension]

        for j=1:k-1
            alfa = zeros(k-1,nloc)
            for i=1:k-j
                alfa[i,:] = (tloc - t[i+r-k+j])/ (t[i+r] - t[i+r-k+j])
                [Qloc[i,:,s] = (1-alfa[i,:]) .* Qloc[i,:,s] + alfa[i,:] .* Qloc[i+1,:,s] for s=1:dimension]
            end
        end

        [C[i, ind+1:ind+nloc] = Qloc[1, 1:nloc, i] for i=1:dimension]
        ind += nloc

    end

end


if length(ARGS) < 1 error("L argument missing") end
L=parse(Int, ARGS[1])
# L = 4
N=2*L^2
nbonds = Int(3*N/2)

# Build honeycomb lattice
println("Building honeycomb lattice...")
basis = [[0.,0.],[0.,1.]] # bond length = 1
a1 = [cos(deg2rad(30)), sin(deg2rad(30)) + 1]
a2 = a1.*[-1,1]

# site positions
positions = Vector{Vector{Float64}}(undef, N)
pi = 1
for ix in 0:L-1
    for iy in 0:L-1
        global positions, pi
        positions[pi] = basis[1]+(iy)*a1+ix*(a1-a2)
        positions[pi+1] = basis[2]+(iy)*a1+ix*(a1-a2)
        pi += 2
    end
end

# unit cells neighbortable
ucs = reshape(1:L^2, (L,L)) # unitcells form square lattice
downright = circshift(ucs, (+1,-1)) # down right bond
up = circshift(ucs, (-1,0)) # up "right" bond
ucneighs = vcat(downright[:]', up[:]') # column = unitcell linidx

getsite(uc::Int, site::Int) = 2 * (uc-1) + site

# bonds
bonds = Vector{Vector{Int}}(undef, nbonds) # from, to, type
bondvecs = Vector{Vector{Float64}}(undef, nbonds) # to_pos - from_pos
ibond = 1
for uc in 1:L^2
    global bonds, bondvecs, ibond
    # inner bond
    s1 = getsite(uc, 1)
    s2 = getsite(uc, 2)
    bonds[ibond] = [s1, s2, 0]
    bondvecs[ibond] = [0.0, 1.0]
    ibond += 1

    # down-right bond
    trg = getsite(ucneighs[1,uc],2)
    bonds[ibond] = [s1, trg, 1]
    bondvecs[ibond] = [0.8660254037844387, 0.5]
    ibond += 1

    # up-"right" bond
    trg = getsite(ucneighs[2,uc],1)
    bonds[ibond] = [s2, trg, 2]
    bondvecs[ibond] = [0.8660254037844387, -0.5]
    ibond += 1
end

# Create ALPS like xml file
println("Creating ALPS like XML file...")
using LightXML

# create an empty XML document
xdoc = XMLDocument()

# create & attach a graph (root) node
graph = create_root(xdoc, "GRAPH")
nedges = Int(3*N/2)
set_attributes(graph, dimension=2, vertices=N, edges=nedges)

function addvertex!(node, id, typ, coordinates)
    v = new_child(node, "VERTEX")
    set_attributes(v, Dict("id"=>id, "type"=>typ))

    vc = new_child(v, "COORDINATE")
    add_text(vc, "$(join(coordinates, " "))")
end

function addedge!(node, src, trg, id, typ, vector)
    edge = new_child(node, "EDGE")
    set_attributes(edge, id=id)
    set_attributes(edge, source=src, target=trg)
    set_attributes(edge, Dict("type"=>typ, "vector"=>join(vector, " ")))
end

# add vertices to graph
for (vi, pos) in enumerate(positions)
    addvertex!(graph, vi, Int(iseven(vi)), pos)
end

# add edges to graph
for (ibond, b) in enumerate(bonds)
    addedge!(graph, b[1], b[2], ibond, b[3], bondvecs[ibond])
end

# s = string(xdoc);
# print(xdoc)

save_file(xdoc, "honeycomb_L_$(L)_W_$(L).xml");

# Create plot
println("Creating a plot of the lattice...")
using PyPlot

# bondcolors=["C7", "C8", "C9"]
bondcolors=fill("black", 3)
sitecolors=["white", "gray"]

# plot bonds
for (ibond, b) in enumerate(bonds)
    src = positions[b[1]]
    trg = positions[b[2]]

    # don't show pbc bonds
    b[2] > b[1] || continue
    b[2] - b[1] < 2*L || continue

    plot([src[1], trg[1]], [src[2], trg[2]], color=bondcolors[b[3]+1], linewidth=2.0)
    gca()[:set_axis_off]()
end

# plot sites
for (s, pos) in enumerate(positions)
    stype = Int(iseven(s))+1
#     plot(pos..., marker="o", markersize=13, color=sitecolors[stype])
    plot(pos..., marker="o", markersize=13, color=sitecolors[stype], mec="black", mew=1.5)
end
# scatter(getindex.(positions, 1), getindex.(positions, 2), c=1:length(positions), cmap="Blues")

axis("equal")
savefig("honeycomb_L_$(L)_W_$(L).pdf");

println("Done.")


function +(ps::MPoly...)
    pu = PolyUnion(ps)
    r = zero(pu)
    for pindex = 1:length(ps)
        for (m, c) in ps[pindex]
            r[newexps(pu, m, pindex)] += c
        end
    end
    r
end

function +(p1::MPoly, p2::MPoly)
    pu = PolyUnion([p1, p2])
    r = zero(pu)
    for (m, c) in p1
        r[newexps(pu, m, 1)] += c
    end
    for (m, c) in p2
        r[newexps(pu, m, 2)] += c
    end
    r
end

function +{T,U}(p::MPoly{T}, s::U)
    r = copy(convert(MPoly{promote_type(T,U)}, p))
    r[zeros(Int, nvars(p))] += s
    r
end

+(s, p::MPoly) = p + s

function -(p::MPoly, q::MPoly)
    pu = PolyUnion([p, q])
    r = zero(pu)
    for (m, c) in p
        r[newexps(pu, m, 1)] += c
    end
    for (m, c) in q
        r[newexps(pu, m, 2)] -= c
    end
    r
end

function -{T,U}(p::MPoly{T}, s::U)
    r = copy(convert(MPoly{promote_type(T, U)}, p))
    r[zeros(Int, nvars(p))] -= s
    r
end

function -(p::MPoly)
    r = zero(p)
    for (m, c) in p
        r[m] = -c
    end
    r
end

-(s, p::MPoly) = -p + s

function *(p1::MPoly, p2::MPoly)
    pu = PolyUnion([p1, p2])
    r = zero(pu)
    for (m1, c1) in p1
        for (m2, c2) in p2
            r[newexps(pu, m1, 1) + newexps(pu, m2, 2)] += c1 * c2
        end
    end
    r
end

function *{T,U}(s::T, p::MPoly{U})
    r = zero(MPoly{promote_type(T,U)}, vars=vars(p))
    for (m, c) in p
        r[m] = s * c
    end
    r
end

*(p::MPoly, s) = s * p

function Base.inv(p)
    q = copy(p)
    if length(q.terms) != 1
        throw(ArgumentError("Can only take inverse of a monomial"))
    end
    term = collect(keys(q.terms))[1]
    val = collect(values(q.terms))[1]
    delete!(q.terms, term)
    q[-term] = val
    q
end

function ^(p::MPoly, power::Integer)
    if power < 0
        return inv(p)^abs(power)
    elseif power == 0
        return one(p)
    elseif power == 1
        return p
    else
        f, r = divrem(power, 2)
        return p^(f+r) * p^f
    end
end

function /{T,U}(p::MPoly{T}, s::U)
    r = zero(MPoly{promote_type(T,U)}, vars=vars(p))
    for (m, c) in p
        r[m] = c/s
    end
    r
end

function conj(p::MPoly)
    r = zero(p)
    for (m, c) in p
        r[m] = conj(c)
    end
    r
end

function real{T<:Real}(p::MPoly{Complex{T}})
    r = zero(MPoly{T}, vars=vars(p))
    for (m, c) in p
        r[m] = real(c)
    end
    r
end

real{T<:Real}(p::MPoly{T}) = p

function imag{T<:Real}(p::MPoly{Complex{T}})
    r = zero(MPoly{T}, vars=vars(p))
    for (m, c) in p
        r[m] = imag(c)
    end
    r
end

imag{T<:Real}(p::MPoly{T}) = zero(p)


#=
   Provides calc_model_rv(theta, time)
   Computes the velocity of the star due to the perturbations of multiple planets, as the linear superposition of the circular orbit induced by each planet, i.e., neglecting eccentricity and mutual planet-planet interactions
=#

# Calculate Keplerian velocity of star due to one planet (with parameters displaced by offset)
function calc_rv_circ_one_planet{T1,T2}( theta::Array{T1,1}, time::T2; plid::Integer = 1 )
  (P,K,M0) = extract_PKM0(theta,plid=plid)
  #(P,Kc,Ks) = extract_PKcKs(theta,plid=plid)
  n = 2pi/P
  #M = mod2pi(time*n-M0)
  M = time*n-M0
  return K*cos(M)
  #return Kc*cos(M)+Ks*sin(M)
end

# Calculate Keplerian velocity of star due to num_pl planets
function calc_rv_circ_multi_planet{T1,T2}( theta::Array{T1,1}, time::T2)
  zdot = zero(T1)
  for plid in 1:num_planets(theta)
      zdot += calc_rv_circ_one_planet(theta,time,plid=plid)
  end
  return zdot
end

# Assumes model parameters = [ Period_1, K_1, M0_1,   Period_2, K_2, M0_2, ...  C ]
function calc_model_rv{T1,T2}( theta::Array{T1,1}, t::T2; obsid::Integer = 1, tol::Real = 1.e-8)
  Offset = extract_rvoffset(theta, obsid=obsid)
  calc_rv_circ_multi_planet(theta, t) + Offset
end

export calc_model_rv


"""
Computes the parameters of the inverse gamma distribution, given a mean and
standard deviation.
"""
function invgammamoment(m, s)
  v = s ^ 2
  a = (m / s) ^ 2 + 2
  b = m * (a - 1)
  return a, b
end

function generate_sample(; N::Int, gamma::Real, eta::AbstractVector{<:Real},
                         loc::AbstractVector{<:Real},
                         scale::AbstractVector{<:Real},
                         df::AbstractVector{<:Real},
                         skew::AbstractVector{<:Real})
  K = length(eta)
  Ninf= rand(Binomial(N, gamma))
  Nfinite = N - Ninf
  mm = MixtureModel([SkewT(loc[k], scale[k], df[k], skew[k]) for k in 1:K], eta)
  yfinite = rand(mm, Nfinite)
  return [yfinite; fill(-Inf, Ninf)], mm
end

function generate_samples(; NC, NT, gammaC, gammaT, etaC, etaT,
                          loc, scale, df, skew)
  K = length(loc)
  @assert K == length(etaC) == length(etaT)
  @assert K == length(scale) == length(df) == length(skew)
  yC, mmC = generate_sample(N=NC, gamma=gammaC, eta=etaC, loc=loc, scale=scale,
                            df=df, skew=skew)
  yT, mmT = generate_sample(N=NT, gamma=gammaT, eta=etaT, loc=loc, scale=scale,
                            df=df, skew=skew)
  return (yC=yC, yT=yT, gammaC=gammaC, gammaT=gammaT, etaC=etaC, etaT=etaT,
          loc=loc, scale=scale, df=df, skew=skew, mmC=mmC, mmT=mmT)
end


# The old-gdemo model.
@model function gdemo(x, y)
  s ~ InverseGamma(2, 3)
  m ~ Normal(0, sqrt(s))
  x ~ Normal(m, sqrt(s))
  y ~ Normal(m, sqrt(s))
  return s, m
end

@model function gdemo_d()
  s ~ InverseGamma(2, 3)
  m ~ Normal(0, sqrt(s))
  1.5 ~ Normal(m, sqrt(s))
  2.0 ~ Normal(m, sqrt(s))
  return s, m
end

gdemo_default = gdemo_d()

@model function MoGtest(D)
    mu1 ~ Normal(1, 1)
    mu2 ~ Normal(4, 1)
    z1 ~ Categorical(2)
    if z1 == 1
        D[1] ~ Normal(mu1, 1)
    else
        D[1] ~ Normal(mu2, 1)
    end
    z2 ~ Categorical(2)
    if z2 == 1
        D[2] ~ Normal(mu1, 1)
    else
        D[2] ~ Normal(mu2, 1)
    end
    z3 ~ Categorical(2)
    if z3 == 1
        D[3] ~ Normal(mu1, 1)
    else
        D[3] ~ Normal(mu2, 1)
    end
    z4 ~ Categorical(2)
    if z4 == 1
        D[4] ~ Normal(mu1, 1)
    else
        D[4] ~ Normal(mu2, 1)
    end
    z1, z2, z3, z4, mu1, mu2
end

MoGtest_default = MoGtest([1.0 1.0 4.0 4.0])

# Declare empty model to make the Sampler constructor work.
@model empty_model() = x = 1


using MyProject
using Test

@testset "MyProject.jl" begin
    @test my_calc(3,2)==9
    @test my_calc(4,4)==256
    @test my_calc(2,3)==8

end

@testset "myderivative Tests" begin
    @test my_deri(3,2)==6
end


#= As in Binary units, we prefer to encode binary states with
floats 0.0 / 1.0 instead of Booleans, because they hit faster
BLAS linear algebra routines. =#

"""
    Potts(θ)

Potts layer, with external fields `θ`.
Encodes categorical variables as one-hot vectors.
The number of classes is the size of the first dimension.
"""
struct Potts{N, T, A <: AbstractArray{T,N}} <: AbstractLayer{N}
    θ::A
end
Potts(::Type{T}, q::Int, n::Int...) where {T} = Potts(zeros(T, q, n...))
Potts(q::Int, n::Int...) = Potts(Float64, q, n...)

Base.repeat(l::Potts, n::Int...) = Potts(repeat(l.θ, n...))

free_energies(layer::Potts, inputs::Union{Real,AbstractArray} = 0) = -logsumexp(layer.θ .+ inputs; dims=1)
mean_from_inputs(layer::Potts, inputs::Union{Real,AbstractArray} = 0) = softmax(layer.θ .+ inputs; dims=1)
mean_abs_from_inputs(layer::Potts, inputs::Union{Real,AbstractArray} = 0) = mean_from_inputs(layer, inputs)
std_from_inputs(layer::Potts, inputs::Union{Real,AbstractArray} = 0) = sqrt.(var_from_inputs(layer, inputs))

function mode_from_inputs(layer::Potts, inputs::Union{Real,AbstractArray} = 0)
    return layer.θ .+ inputs .== maximum(layer.θ .+ inputs; dims=1)
end

function var_from_inputs(layer::Potts, inputs::Union{Real,AbstractArray} = 0)
    μ = mean_from_inputs(layer, inputs)
    return μ .* (1 .- μ)
end

function meanvar_from_inputs(layer::Potts, inputs::Union{Real,AbstractArray} = 0)
    μ = mean_from_inputs(layer, inputs)
    ν = μ .* (1 .- μ)
    return μ, ν
end

function sample_from_inputs(layer::Potts, inputs::Union{Real,AbstractArray} = 0)
    c = categorical_sample_from_logits(layer.θ .+ inputs)
    return onehot_encode(c, 1:size(layer, 1))
end


using .IntervalConstraintProgramming

"""
returns the unrolled expression for \$|a ⋅x - b|\$
\$a\$ and \$x\$ must be vectors of the same length and \$b\$ is scalar.
The absolue value in the equation is taken elementwise.
"""
function oettli_lhs(a, b, x)
    ex = :( $(a[1])*$(x[1]))
    for i = 2:length(x)
        ex = :( $ex + $(a[i])*$(x[i]))
    end
    return :(abs($ex - $b))
end

"""
returns the unrolled expression for \$a ⋅|x| + b\$
\$a\$ and \$x\$ must be vectors of the same length and \$b\$ is scalar.
The absolue value in the equation is taken elementwise.
"""
function oettli_rhs(a, b, x)
    ex = :( $(a[1])*abs($(x[1])))
    for i = 2:length(x)
        ex = :( $ex + $(a[i])*abs($(x[i])))
    end
    return :($ex + $b)
end

"""
Returns the separator for the constraint `|a_c ⋅x - b_c| - a_r ⋅|x| - b_r <= 0`.

`a` and `x` must be vectors of the same length and `b` is scalar.

The absolue values in the equation are taken elementwise.

`a_c` and `a_r` are vectors containing midpoints and radii of the intervals in `a`. Similar `b_c` and `b_r`.
"""
function oettli_eq(a, b, x)
    ac = mid.(a)
    ar = IntervalArithmetic.radius.(a)
    bc = mid(b)
    br = IntervalArithmetic.radius(b)
    lhs = oettli_lhs(ac, bc, x)
    rhs = oettli_rhs(ar, br, x)
    ex = :(@constraint $lhs - $rhs <= 0)
    @eval $ex

end

function (op::NonLinearOettliPrager)(A, b, X::IntervalBox)
    vars = ntuple(i -> Symbol(:x, i), length(b))
    separators = [oettli_eq(A[i,:], b[i], vars) for i in 1:length(b)]
    S = reduce(∩, separators)
    return Base.invokelatest(pave, S, X, op.tol)
end

(op::NonLinearOettliPrager)(A, b, X=enclose(A, b)) = op(A, b, IntervalBox(X))

_default_precondition(_, ::NonLinearOettliPrager) = NoPrecondition()


"Tableau for variational Gauss-Lobatto IIIA-IIIB method with two stages"
function getTableauVPRKpLegendreQLobIIIA2()
    ω = [0.5, 0.5]
    ω = reshape(ω_λ, 1, length(ω_λ))
    d = [+1.0, -1.0]
    R∞ = -1
    TableauVPRKpLegendreQ(:LobIIIA2, 2, getCoefficientsLobIIIA2(), R∞, ω, d)
end

"Tableau for variational Gauss-Lobatto IIIA-IIIB method with three stages"
function getTableauVPRKpLegendreQLobIIIA3()
    d = [+1.0, -2.0, +1.0]
    R∞ = +1
    TableauVPRKpLegendreQ(:LobIIIA3, 4, getCoefficientsLobIIIA3(), R∞, d)
end

"Tableau for variational Gauss-Lobatto IIIA-IIIB method with four stages"
function getTableauVPRKpLegendreQLobIIIA4()
    d = [+1.0, -√5, +√5, -1.0]
    R∞ = -1
    TableauVPRKpLegendreQ(:LobIIIA4, 6, getCoefficientsLobIIIA4(), R∞, d)
end

"Tableau for variational Gauss-Legendre method with s stages"
function getTableauVPRKpLegendreQGLRK(s; T=Float64)
    glrk = getCoefficientsGLRK(s, T=T)
    R∞ = (-1)^s
    TableauVPRKpLegendreQ(Symbol("GLRK", s), glrk.o, glrk, R∞)
end

"Tableau for variational Gauss-Lobatto IIIA-IIIB method with two stages"
function getTableauVPRKpLegendrePLobIIIA2()
    d = [+1.0, -1.0]
    R∞ = -1
    TableauVPRKpLegendreP(:LobIIIA2, 2, getCoefficientsLobIIIA2(), R∞, d)
end

"Tableau for variational Gauss-Lobatto IIIA-IIIB method with three stages"
function getTableauVPRKpLegendrePLobIIIA3()
    d = [+1.0, -2.0, +1.0]
    R∞ = +1
    TableauVPRKpLegendreP(:LobIIIA3, 4, getCoefficientsLobIIIA3(), R∞, d)
end

"Tableau for variational Gauss-Lobatto IIIA-IIIB method with four stages"
function getTableauVPRKpLegendrePLobIIIA4()
    d = [+1.0, -√5, +√5, -1.0]
    R∞ = -1
    TableauVPRKpLegendreP(:LobIIIA4, 6, getCoefficientsLobIIIA4(), R∞, d)
end

"Tableau for variational Gauss-Legendre method with s stages"
function getTableauVPRKpLegendrePGLRK(s; T=Float64)
    glrk = getCoefficientsGLRK(s, T=T)
    R∞ = (-1)^s
    TableauVPRKpLegendreP(Symbol("GLRK", s), glrk.o, glrk, R∞)
end


@testset "Poly" begin

  function _test_sturm()
    s = rand(1:20)
    Zx = Hecke.Globals.Zx
    M = random_symmetric_matrix(s)
    f = charpoly(Zx, M)

    while iszero(f)
      M = random_symmetric_matrix(s)
      f = charpoly(Zx, M)
    end

    npos = Hecke.n_positive_roots(f)
    nreal = Hecke.n_real_roots(f)

    p = 64

    local sgtpos
    local l

    while p < 4096
      l = roots(f, ArbField(p, cached = false))
      sgtpos = count(ispositive, l)
      sgtneg = count(isnegative, l)
      sgtz = count(iszero, l)
      if sgtpos + sgtneg + sgtz != length(l)
        p *= 2
      else
        break
      end
      if p > 4096
        error("Precision issue")
      end
    end
    return sgtpos == npos && nreal == length(l)
  end

  function random_symmetric_matrix(x::Int)
    M = zero_matrix(FlintZZ, x, x)
    for i = 1:x
      for j= i:x
        a = rand(1:5)
        M[i, j] = a
        M[j, i] = a
      end
    end
    return M
  end

  for i = 1:20
    @test _test_sturm()
  end

  for R in [ZZ, QQ]
    _, x = R["x"]
    f = x^5 + x^2 - x

    @test Hecke.n_real_roots(f) == 3
    @test Hecke.n_real_roots(f^2) == 3
    @test Hecke.n_positive_roots(f) == 1
    @test Hecke.n_positive_roots(f * x^3) == 1

    f = x^10 + x^9 - x^8 + x^7 - x^6 + x^5 - x^4 - x^3 - x
    @test Hecke.n_real_roots(f) == 4
    @test Hecke.n_real_roots(f^2) == 4
    @test Hecke.n_positive_roots(f) == 1
    @test Hecke.n_positive_roots(f^2 * x^3) == 1

    @test Hecke.n_positive_roots((x - 1)^2) == 1
    @test Hecke.n_positive_roots((x - 1)^2, multiplicities = true) == 2
  end
end

@testset "roots" begin
  o = CyclotomicField(2)[1](1)
  @test issetequal(roots(o, 2), [o, -o])
  o = CyclotomicField(1)[1](1)
  @test issetequal(roots(o, 2), [o, -o])

  o, a = CyclotomicField(4)
  _, x = o["x"]
  @test length(roots(x^2-a^2//4)) == 2

  Qx,x = PolynomialRing(QQ,"x")
  K, a = NumberField(x^4+2, "a")
  R, y = PolynomialRing(K,"y")
  f = y^2 + 2*y + 1
  @test roots(f) == [K(-1)]

  K, a = NumberField(x^2-5, "a")
  R, x = PolynomialRing(K)
  f = 3*x^4 + 5*x^3 - 15*x^2 + 15*x
  @test roots(f) == [K(0)]

  K, a = NumberField(x^4+2, "a") #relative
  R, y = PolynomialRing(K,"y")
  f = y^2 + 2*y + 1
  @test roots(f) == [K(-1)]

end


function polyMap(Θ,z)
  g = 0.
  for i in 1:1:length(Θ)
    g += Θ[i]⋅z^(i-1)
  end
  return g
end

# function noiseMix1()
#   u = rand()
#   z = 0.
#   if u < 0.25
#     z = rand(Normal(0,sqrt(0.0001)))
#   elseif u < 0.5
#     z = rand(Normal(0, sqrt(6*0.0001)))
#   elseif u < 0.75
#     z = rand(Normal(0,sqrt(11*0.0001)))
#   else
#     z = rand(Normal(0,sqrt(16*0.0001)))
#   end
#   return z
# end

function noiseMix3(p1::Float64, lam1::Float64, lam2::Float64)
  if rand() < p1
    z = rand(Normal(0, sqrt(1 / lam1)))
  else
    z = rand(Normal(0, sqrt(1 / lam2)))
  end
  return z
end

function genData(n,Θ,x₀,p1,lam1,lam2,seed)
  srand(seed)
  x = zeros(n)
  x[1] = polyMap(Θ,x₀) + noiseMix3(p1,lam1,lam2)
  for i in 2:1:n
    x[i] = polyMap(Θ,x[i-1]) + noiseMix3(p1,lam1,lam2)
  end
  return x
end

function tgeornd(p,k)
  return rand(Geometric(p)) + k
end

function range_intersection(A::Array{Float64}, B::Array{Float64})
#=
Purpose: Range/interval intersection

 A and B two ranges of closed intervals written
 as vectors [lowerbound1 upperbound1 lowerbound2 upperbound2]
 or as matrix [lowerbound1, lowerbound2, lowerboundn;
               upperbound1, upperbound2, upperboundn]
 A and B have to be sorted in ascending order

 out is the mathematical intersection A n B

 EXAMPLE USAGE:
   >> out=rangeIntersection([1 3 5 9],[2 9])
   	out =  [2 3 5 9]
   >> out=rangeIntersection([40 44 55 58], [42 49 50 52])
   	out =  [42 44]
=#

# Allocate, as we don't know yet the size, we assume the largest case
  out1 = zeros(length(B)+(length(A)-2))
  k = 1

  while isempty(A) .== 0 && isempty(B) .== 0
  # make sure that first is ahead second
    if A[1] .> B[1]
      temp = copy(B)
      B = copy(A)
      A = copy(temp)
    end

    if A[2] .< B[1]
      A = copy(A[3:end])
      continue
    elseif A[2] .== B[1]
      out1[k] = B[1]
      out1[k + 1] = B[1]
      k = k + 2

      A = A[3:end]
      continue
    else
      if A[2] .== B[2]
        out1[k] = B[1]
        out1[k+1] = B[2]
        k = k + 2

        A = A[3:end]
        B = B[3:end]

      elseif A[2] .< B[2]
        out1[k] = B[1]
        out1[k+1] = A[2]
        k = k + 2

        A = A[3:end]
      else
        out1[k] = B[1]
        out1[k+1] = B[2]
        k = k + 2

        B = B[3:end]
      end
     end
    end

  # Remove the tails
  out = copy(out1[1:k-1])
  return out
end

function unifmixrnd(x)
  n = length(x)
  nc = 0.0
  for i in 1:2:n
    nc += x[i + 1] - x[i]
  end

  U = 0.
  u = rand()
  prob = 0.
  for i in 1:2:n
    prob += (x[i + 1] - x[i]) / nc
    if u .< prob
      U = x[i] + (x[i + 1] - x[i]) * rand()
      break
    end
  end
  return U
end

function learnc(ac, bc, c, ss, k)
  z = 0.
  x = rand(Beta(c + 1, ss))
  px = (ac + k - 1.0) / (bc - log.(x))
  px = px / (px + ss)
  r = rand(Uniform(0.0, 1.0))
  if (r .< px)
    z = rand(Gamma(ac + k, 1.0 / (bc - log.(x))))
  else
    z = rand(Gamma(ac + k - 1.0, 1.0 / (bc - log.(x))))
  end
  return z
end


#############################################################################
#############################################################################
#
# This file defines the Term type with several operations
#
#############################################################################
#############################################################################

##############################
# Term type and construction #
##############################

"""
A term.
"""
struct Term  #structs are immutable by default
    coeff::Int
    degree::Int
    function Term(coeff::Int, degree::Int)
        degree < 0 && error("Degree must be non-negative")
        coeff != 0 ? new(coeff,degree) : new(coeff,0)
    end
end

"""
Creates the zero term.
"""
zero(::Type{Term})::Term = Term(0,0)

"""
Creates the unit term.
"""
one(::Type{Term})::Term = Term(1,0)

###########
# Display #
###########

"""
Show a term.
"""
show(io::IO, t::Term) = print(io, "$(t.coeff)⋅x^$(t.degree)") #\cdot + [TAB]

########################
# Queries about a term #
########################

"""
Check if a term is 0.
"""
iszero(t::Term)::Bool = iszero(t.coeff)

"""
Compare two terms.
"""
isless(t1::Term,t2::Term)::Bool =  t1.degree == t2.degree ? (t1.coeff < t2.coeff) : (t1.degree < t2.degree)

"""
Evaluate a term at a point x.
"""
evaluate(t::Term, x::T) where T <: Number = t.coeff * x^t.degree

##########################
# Operations with a term #
##########################

"""
Add two terms of the same degree.
"""
function +(t1::Term,t2::Term)::Term
    @assert t1.degree == t2.degree
    Term(t1.coeff + t2.coeff, t1.degree)
end

"""
Negate a term.
"""
-(t::Term,) = Term(-t.coeff,t.degree)

"""
Subtract two terms with the same degree.
"""
-(t1::Term, t2::Term)::Term = t1 + (-t2)

"""
Multiply two terms.
"""
*(t1::Term, t2::Term)::Term = Term(t1.coeff * t2.coeff, t1.degree + t2.degree)

"""
Compute the symmetric mod of a term with an integer.
"""
mod(t::Term, p::Int) = Term(mod(t.coeff,p), t.degree)

"""
Compute the derivative of a term.
"""
derivative(t::Term) = Term(t.coeff*t.degree,max(t.degree-1,0))

"""
Divide two terms. Returns a function of an integer.
"""
function ÷(t1::Term,t2::Term) #\div + [TAB]
    @assert t1.degree ≥ t2.degree
    f(p::Int)::Term = Term(mod((t1.coeff * int_inverse_mod(t2.coeff, p)), p), t1.degree - t2.degree)
end

"""
Integer divide a term by an integer.
"""
÷(t::Term, n::Int) = t ÷ Term(n,0)


import Distributions
import DataFrames
import GLM
import Random
import StatsBase

n = 500
p = 10
X = hcat(ones(n), randn(n, p-1))

noise_coefs = zeros(p - 5)
β = vcat([1.5, 1.1, -10.5, 4.5, 1.9], noise_coefs)
η = X*β                                                   # linear predictor
pr = 1 ./(1 + exp.(-η))                                     # inv-logit

# simulate outcome variable
y = map(π -> rand(Distributions.Binomial(1, π)), pr)
mean(y)

df = DataFrames.DataFrame(X)
df[:y] = y

# keep only a sub-sample of positive cases
keep_prop = 0.05
pos_cases = findall(y .== 1)
keep_indcs = vcat(StatsBase.sample(pos_cases, Int(round(2 * keep_prop * length(pos_cases)))), findall(y .== 0))
df2 = df[keep_indcs, :]

sum(df2[:y])/n

GLM.glm(y ~ 1 + x2 + x3 + x4 + x5 + x6 + x7, df2, Binomial())

# Simulation of class-imbalanced data requires data drawn from
# multivariate distribution (here we use MV Normal).

n = 100
p = 10
# X = hcat(ones(n), randn(n, p-1))

Σ = [1.0 0.8 0.4 0.0 0.0 0.0 0.0 0.0 0.0 0.0;
     0.8 1.0 0.6 0.2 0.0 0.0 0.0 0.0 0.0 0.0;
     0.4 0.6 1.0 0.6 0.4 0.0 0.0 0.0 0.0 0.0;
     0.0 0.2 0.6 1.0 0.8 0.0 0.0 0.0 0.0 0.0;
     0.0 0.0 0.4 0.8 1.0 0.0 0.0 0.0 0.0 0.0;
     0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0;
     0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0;
     0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0;
     0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0;
     0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0]

mvn = MvNormal(ones(p), Σ)

Random.seed!(round(Int, time()))

X = rand(mvn, n)'
cor(X)

ϵ = rand(Normal(2, 0.5), n)                                  # gives 10% minority class,
# ϵ = rand(Normal(-2.5, 0.5), n)                               # gives 5% minority class,
# ϵ = rand(Normal(4, 0.5), n)                                  # gives 15% minority class,

β = [-3, -4, 1, -5, -4, 0.0, 0.0, 0.0, 0.0, 0.0]
η = X*β + ϵ                                                    # linear predictor w/ error
pr = 1.0 ./ (1.0 + exp.(-η))                                    # inv-logit

# simulate outcome variable
y = map(π -> rand(Binomial(1, π)), pr)


using ITensors
using OptimKit
using Random
using Zygote
using Test

include("utils/circuit.jl")

@testset "optimization" begin
  @testset "Energy minimization" begin
    N = 3
    s = siteinds("S=1/2", N; conserve_qns=true)
    os = OpSum()
    for n in 1:(N - 1)
      os .+= 0.5, "S+", n, "S-", n + 1
      os .+= 0.5, "S-", n, "S+", n + 1
      os .+= "Sz", n, "Sz", n + 1
    end
    Hmpo = MPO(os, s)
    ψ₀mps = randomMPS(s, n -> isodd(n) ? "↑" : "↓")
    H = prod(Hmpo)
    ψ₀ = prod(ψ₀mps)
    # The Rayleigh quotient to minimize
    function E(H::ITensor, ψ::ITensor)
      ψdag = dag(ψ)
      return (ψdag' * H * ψ)[] / (ψdag * ψ)[]
    end
    E(ψ::ITensor) = E(H, ψ)
    ∇E(ψ::ITensor) = E'(ψ)
    fg(ψ::ITensor) = (E(ψ), ∇E(ψ))
    linesearch = HagerZhangLineSearch(;
      c₁=0.1, c₂=0.9, ϵ=1e-6, θ=1 / 2, γ=2 / 3, ρ=5.0, verbosity=0
    )
    algorithm = LBFGS(3; maxiter=20, gradtol=1e-8, linesearch=linesearch)
    ψ, fψ, gψ, numfg, normgradhistory = optimize(fg, ψ₀, algorithm)
    D, _ = eigen(H; ishermitian=true)
    @test E(H, ψ) < E(H, ψ₀)
    @test E(H, ψ) ≈ minimum(D)
  end

  @testset "Energy minimization (MPS)" begin
    N = 4
    χ = 4
    s = siteinds("S=1/2", N; conserve_qns=true)
    os = OpSum()
    for n in 1:(N - 1)
      os .+= 0.5, "S+", n, "S-", n + 1
      os .+= 0.5, "S-", n, "S+", n + 1
      os .+= "Sz", n, "Sz", n + 1
    end
    Hmpo = MPO(os, s)

    Random.seed!(1234)
    ψ₀mps = randomMPS(s, n -> isodd(n) ? "↑" : "↓"; linkdims=χ)

    H = ITensors.data(Hmpo)
    ψ₀ = ITensors.data(ψ₀mps)
    # The Rayleigh quotient to minimize
    function E(H::Vector{ITensor}, ψ::Vector{ITensor})
      N = length(ψ)
      ψdag = dag.(addtags.(ψ, "bra"; tags="Link"))
      ψ′dag = prime.(ψdag)
      e = ITensor(1.0)
      for n in 1:N
        e = e * ψ′dag[n] * H[n] * ψ[n]
      end
      norm = ITensor(1.0)
      for n in 1:N
        norm = norm * ψdag[n] * ψ[n]
      end
      return e[] / norm[]
    end
    E(ψ) = E(H, ψ)
    ∇E(ψ) = E'(ψ)
    fg(ψ) = (E(ψ), ∇E(ψ))
    linesearch = HagerZhangLineSearch(; c₁=0.1, c₂=0.9, ϵ=1e-6, θ=1 / 2, γ=2 / 3, ρ=5.0)
    algorithm = LBFGS(5; maxiter=50, gradtol=1e-4, linesearch=linesearch, verbosity=0)
    ψ, fψ, gψ, numfg, normgradhistory = optimize(fg, ψ₀, algorithm)
    sweeps = Sweeps(5)
    setmaxdim!(sweeps, χ)
    fψmps, ψmps = dmrg(Hmpo, ψ₀mps, sweeps; outputlevel=0)
    @test E(H, ψ) ≈ inner(ψmps', Hmpo, ψmps) / inner(ψmps, ψmps) rtol = 1e-2
  end

  @testset "State preparation (full state)" begin
    function Rylayer(N, θ⃗)
      return [("Ry", (n,), (θ=θ⃗[n],)) for n in 1:N]
    end

    function CXlayer(N)
      return [("CX", (n, n + 1)) for n in 1:2:(N - 1)]
    end

    # The variational circuit we want to optimize
    function variational_circuit(θ⃗)
      N = length(θ⃗)
      return vcat(Rylayer(N, θ⃗), CXlayer(N))
    end

    N = 4
    Random.seed!(1234)
    θ⃗ = 2π .* rand(N)
    gates = variational_circuit(θ⃗)

    s = siteinds("Qubit", N)
    ψₘₚₛ = MPS(s, "0")
    ψ = prod(ψₘₚₛ)
    U = buildcircuit(gates, s)
    # Create the target state
    Uψ = apply(U, ψ)

    @test inner_circuit(Uψ, U, ψ) ≈ 1

    function loss(θ⃗)
      gates = variational_circuit(θ⃗)
      U = buildcircuit(gates, s)
      return -abs(inner_circuit(Uψ, U, ψ))^2
    end

    θ⃗₀ = randn!(copy(θ⃗))
    fg(x) = (loss(x), convert(Vector, loss'(x)))
    θ⃗ₒₚₜ, fₒₚₜ, gₒₚₜ, numfg, normgradhistory = optimize(fg, θ⃗₀, GradientDescent())
    @test loss(θ⃗ₒₚₜ) ≈ loss(θ⃗) rtol = 1e-2
  end

  @testset "State preparation (MPS)" begin
    for gate in ["Ry"]#="Rx", =#
      nsites = 4 # Number of sites
      nlayers = 2 # Layers of gates in the ansatz
      gradtol = 1e-3 # Tolerance for stopping gradient descent

      # A layer of the circuit we want to optimize
      function layer(nsites, θ⃗)
        gate_layer = [(gate, (n,), (θ=θ⃗[n],)) for n in 1:nsites]
        CX_layer = [("CX", (n, n + 1)) for n in 1:2:(nsites - 1)]
        return [gate_layer; CX_layer]
      end

      # The variational circuit we want to optimize
      function variational_circuit(nsites, nlayers, θ⃗)
        range = 1:nsites
        circuit = layer(nsites, θ⃗[range])
        for n in 1:(nlayers - 1)
          circuit = [circuit; layer(nsites, θ⃗[range .+ n * nsites])]
        end
        return circuit
      end

      Random.seed!(1234)

      θ⃗ᵗᵃʳᵍᵉᵗ = 2π * rand(nsites * nlayers)
      𝒰ᵗᵃʳᵍᵉᵗ = variational_circuit(nsites, nlayers, θ⃗ᵗᵃʳᵍᵉᵗ)

      s = siteinds("Qubit", nsites)
      Uᵗᵃʳᵍᵉᵗ = ops(𝒰ᵗᵃʳᵍᵉᵗ, s)

      ψ0 = MPS(s, "0")

      # Create the random target state
      ψᵗᵃʳᵍᵉᵗ = apply(Uᵗᵃʳᵍᵉᵗ, ψ0; cutoff=1e-8)

      #
      # The loss function, a function of the gate parameters
      # and implicitly depending on the target state:
      #
      # loss(θ⃗) = -|⟨θ⃗ᵗᵃʳᵍᵉᵗ|U(θ⃗)|0⟩|² = -|⟨θ⃗ᵗᵃʳᵍᵉᵗ|θ⃗⟩|²
      #
      function loss(θ⃗)
        nsites = length(ψ0)
        s = siteinds(ψ0)
        𝒰θ⃗ = variational_circuit(nsites, nlayers, θ⃗)
        Uθ⃗ = ops(𝒰θ⃗, s)
        ψθ⃗ = apply(Uθ⃗, ψ0; cutoff=1e-8)
        return -abs(inner(ψᵗᵃʳᵍᵉᵗ, ψθ⃗))^2
      end

      θ⃗₀ = randn!(copy(θ⃗ᵗᵃʳᵍᵉᵗ))

      @test loss(θ⃗₀) ≉ loss(θ⃗ᵗᵃʳᵍᵉᵗ)

      loss_∇loss(x) = (loss(x), convert(Vector, loss'(x)))
      @show gate
      algorithm = LBFGS(; gradtol=gradtol, verbosity=2)
      θ⃗ₒₚₜ, lossₒₚₜ, ∇lossₒₚₜ, numfg, normgradhistory = optimize(
        loss_∇loss, θ⃗₀, algorithm
      )

      @test loss(θ⃗ₒₚₜ) ≈ loss(θ⃗ᵗᵃʳᵍᵉᵗ) rtol = 1e-5
    end
  end

  @testset "VQE (MPS)" begin
    nsites = 4 # Number of sites
    nlayers = 2 # Layers of gates in the ansatz
    gradtol = 1e-3 # Tolerance for stopping gradient descent

    # The Hamiltonian we are minimizing
    function ising_hamiltonian(nsites; h)
      ℋ = OpSum()
      for j in 1:(nsites - 1)
        ℋ += -1, "Z", j, "Z", j + 1
      end
      for j in 1:nsites
        ℋ += h, "X", j
      end
      return ℋ
    end

    # A layer of the circuit we want to optimize
    function layer(nsites, θ⃗)
      RY_layer = [("Ry", (n,), (θ=θ⃗[n],)) for n in 1:nsites]
      CX_layer = [("CX", (n, n + 1)) for n in 1:2:(nsites - 1)]
      return [RY_layer; CX_layer]
    end

    # The variational circuit we want to optimize
    function variational_circuit(nsites, nlayers, θ⃗)
      range = 1:nsites
      circuit = layer(nsites, θ⃗[range])
      for n in 1:(nlayers - 1)
        circuit = [circuit; layer(nsites, θ⃗[range .+ n * nsites])]
      end
      return circuit
    end

    s = siteinds("Qubit", nsites)

    h = 1.3
    ℋ = ising_hamiltonian(nsites; h=h)
    H = MPO(ℋ, s)
    ψ0 = MPS(s, "0")

    #
    # The loss function, a function of the gate parameters
    # and implicitly depending on the Hamiltonian and state:
    #
    # loss(θ⃗) = ⟨0|U(θ⃗)† H U(θ⃗)|0⟩ = ⟨θ⃗|H|θ⃗⟩
    #
    function loss(θ⃗)
      nsites = length(ψ0)
      s = siteinds(ψ0)
      𝒰θ⃗ = variational_circuit(nsites, nlayers, θ⃗)
      Uθ⃗ = ops(𝒰θ⃗, s)
      ψθ⃗ = apply(Uθ⃗, ψ0; cutoff=1e-8)
      return inner(ψθ⃗', H, ψθ⃗; cutoff=1e-8)
    end

    Random.seed!(1234)
    θ⃗₀ = 2π * rand(nsites * nlayers)

    loss_∇loss(x) = (loss(x), convert(Vector, loss'(x)))
    algorithm = LBFGS(; gradtol=gradtol, verbosity=0)
    θ⃗ₒₚₜ, lossₒₚₜ, ∇lossₒₚₜ, numfg, normgradhistory = optimize(loss_∇loss, θ⃗₀, algorithm)

    sweeps = Sweeps(5)
    setmaxdim!(sweeps, 10)
    e_dmrg, ψ_dmrg = dmrg(H, ψ0, sweeps; outputlevel=0)

    @test loss(θ⃗ₒₚₜ) ≈ e_dmrg rtol = 1e-1
  end
end


const ∫ = integrate

doc"""
Integrate a Taylor1Model.
`x0` is optional constant to add.
"""
function integrate(f::Taylor1Model, x0=0)

    p2 = integrate(f.p)

    Δ = integral_bound(f)

    g = Taylor1Model(f, [0; p2[1:end]], Δ)

    return g

end

function integral_bound(f::Taylor1Model)
    n = f.n
    high_order_term = f.p[n]

    coeff = bound(high_order_term)
    power = (f.I - f.x0)^n

    return ((coeff * power) + f.Δ) * diam(f.I)
end

"""
fs is an array of functions
This works in 2 variables currently.
"""
function Taylor_step(fs, n, u0, v0, t_interval)

    u = u0
    v = v0
    u_new = u0   # so exist outside loop
    v_new = v0

    # build up Taylor series by Picard:
    for i in 1:n+1   # how many iterations are required?
        u_new = u0 + ∫(fs[1](u, v))
        v_new = v0 + ∫(fs[2](u, v))

        u, v = u_new, v_new
    end

    # prepare Taylor Model:
    uu = Taylor1Model(n, Interval(t_interval.lo), t_interval, u, 0..0)
    vv = Taylor1Model(n, Interval(t_interval.lo), t_interval, v, 0..0)

    uΔ = integral_bound(fs[1](uu, vv))
    vΔ = integral_bound(fs[2](uu, vv))

    # make sure the intervals contain 0:
    m = mag(uΔ)
    uΔ = -m..m

    m = mag(vΔ)
    vΔ = -m..m

    @show uΔ, vΔ

    uu = Taylor1Model(n, Interval(t_interval.lo), t_interval, u, uΔ)
    vv = Taylor1Model(n, Interval(t_interval.lo), t_interval, v, vΔ)

    integ_u_bound = uΔ
    integ_v_bound = vΔ

    while ! ((integ_u_bound ⊆ uu.Δ) && (integ_v_bound ⊆ vv.Δ))
        uΔ *= 2
        vΔ *= 2

        @show uΔ, vΔ

        uu = Taylor1Model(n, Interval(t_interval.lo), t_interval, u, uΔ)
        vv = Taylor1Model(n, Interval(t_interval.lo), t_interval, v, vΔ)

        integ_u_bound = integral_bound(fs[1](uu, vv))
        integ_v_bound = integral_bound(fs[2](uu, vv))

    end

    # only need to bound the integral, not actually carry out
    # the whole integral

    # contract:

    uu_new = ∫( fs[1](uu, vv), u0[0] )
    vv_new = ∫( fs[2](uu, vv), v0[0] )

    for i in 1:10
       uu, vv = uu_new, vv_new
       uu_new = ∫( fs[1](uu, vv), u0[0] )
       vv_new = ∫( fs[2](uu, vv), v0[0] )
    end

    @show uu_new.Δ, vv_new.Δ

    U = uu_new(t_interval.hi)
    V = vv_new(t_interval.hi)

    return U, V, uu_new, vv_new
end


f = Open([1, 2], PetriNet(4, (1,3), (2,4)), [3, 4])

g = Open([1,2], PetriNet(3, ((1,2),3)), [3])

h = f ⋅ g

h′ = Open([1,2], PetriNet(5, (1,3), (2,4), ((3,4), 5)), [5])

h_id = h ⋅ id(OpenPetriNetOb(FinSet(1)))

@test dom(f) == OpenPetriNetOb(FinSet(2))
@test codom(f) == OpenPetriNetOb(FinSet(2))
@test dom(g) == codom(f)
@test codom(g) == OpenPetriNetOb(FinSet(1))
@test dom(h) == dom(f)
@test codom(h) == codom(g)

@test h == h′
@test h == h_id

# Test open petri net notations either fully open or by specifying legs

pn = Open(PetriNet(3, ((1,2), 3)), [1], [2], [3])
pn′ = Open(PetriNet(3, ((1,2), 3)))
@test pn == pn′

lpn = Open(LabelledPetriNet([:I, :R], (:rec, :I=>:R)))
lpn′ = Open([:I], LabelledPetriNet([:I, :R], (:rec, :I=>:R)), [:R])
@test lpn == lpn′

rn = Open(ReactionNet{Number,Int}([10, 0], (.25, 1=>2)))
rn′ = Open([1], ReactionNet{Number,Int}([10, 0], (.25, 1=>2)), [2])
@test rn == rn′

lrn = Open(LabelledReactionNet{Number,Int}([:I=>10, :R=>0], ((:rec=>.25), :I=>:R)))
lrn′ = Open([:I], LabelledReactionNet{Number,Int}([:I=>10, :R=>0], ((:rec=>.25), :I=>:R)), [:R])
@test lrn == lrn′


using LinearAlgebra
using Random

Random.seed!(843)

include("feast.jl")
using ..feastNonlinear,..feastUtil
using IterativeSolvers

using IterativeSolvers

#eigenvalue problem dimension:
n=500

#eigenvector subspaces:
R=rand(n,n)
(X1,R)=qr(R)
R=rand(n,n)
(X2,R)=qr(R)

#quadratic eigenvalues:
L1=zeros(n,n)
L2=zeros(n,n)
#eigenvalues are 1,2,3,...,2n
for i in 1:n
    L1[i,i]=i
    L2[i,i]=i+n
end

#generate quadratic factor matrices:
B1=X1*L1*X1'
B2=X2*L2*X2'

#quadratic eigenvcalue problem matrices: T(z)=z^2*M+z*D+K
M=Matrix(I,n,n)
D=-B1-B2
K=B1*B2

#FEAST parameters
m0=5 #subspace dimension
x0=rand(ComplexF64,n,m0) #eigenvector initial guess
nc=4 #number of integration quadrature points
emid=50 #center point of contour
ra=1.5 #contour radius 1
rb=1.5 #contour radius 2
eps=1e-8 #convergence residual tolerance
maxit=50 #maximum number of FEAST iterations

#use quadratic FEAST
println("Using quadratic feast")
(lest,xest)=nlfeast_quad(M,D,K,x0,nc,emid,ra,rb,eps,maxit)

#Using Beyn
println("Using Beyn")
Tf(z)=(z^2*M+z*D+K)
(lest,xest)=beyn(Tf,x0,nc,emid,ra,rb)
ncmax=100
res=M*xest*diagm(0 => lest.^2)+D*xest*diagm(0 => lest)+K*xest
maxres=getMaxResInside(lest,xest,res,emid,ra,rb)
println("res=$maxres")

#Using general nonlinear feast, i.e. feast with beyn
println("Using general nlfeast")
Tf(z)=(z^2*M+z*D+K)
(lest, xest)=nlfeast_beyn(Tf,x0,nc,emid,ra,rb,eps,maxit)

#using iterative general nonlinear FEAST with GMRES
println("Using general iterative nlfeast")
alpha=0.0 #target accuracy for linear system solutions
maxitls = 50 # maximum linear system iterations
(lest, xest)=inlfeast_beyn(Tf,x0,alpha,maxitls,nc,emid,ra,rb,eps,maxit)


"""
    half_mass_radius(
        pos::AbstractMatrix{<:Number},
        mass::Union{AbstractVector{<:Number},Nothing},
        [r²::AbstractVector{<:Number}];
        rmax::Union{Number,Nothing}=nothing,
    )

Returns the half-mass radius of the particles within radius `rmax`, or of all particles if `rmax`
is `nothing`.
"""
function half_mass_radius(
    pos::AbstractMatrix{<:Number},
    mass::AbstractVector{<:Number},
    r²::AbstractVector{<:Number};
    rmax::Union{Number,Nothing}=nothing,
)
    # check dimensions
    @assert size(pos, 1) == 3
    @assert length(mass) == size(pos, 2) == length(r²)

    if isnothing(rmax)
        mask = (:) # all particles
    else
        mask = r² .≤ rmax^2
    end

    mass_half = @views 1 // 2 * sum(mass[mask])

    # mass sorted by distance to center
    ind = @views sortperm(r²[mask])
    mass_sorted = mass[mask][ind]

    # traverse masses outwardly until half of the total mass is reached
    mass_cumul = zero(eltype(mass))
    i = 1
    @inbounds while mass_cumul < mass_half
        mass_cumul += mass_sorted[i]
        i += 1
    end

    r²_masked = @views r²[mask]
    return 1 // 2 * (sqrt(r²_masked[ind[i - 1]]) + sqrt(r²_masked[ind[i - 2]]))
end

function half_mass_radius(
    pos::AbstractMatrix{<:Number},
    mass::AbstractVector{<:Number};
    rmax::Union{Number,Nothing}=nothing,
)
    r² = r²_sphere(pos)
    half_mass_radius(pos, mass, r²; rmax)
end

"""
    half_mass_radius(pos::AbstractMatrix{<:Number}; rmax::Union{Number,Nothing}=nothing)

Returns the half-mass radius of the particles within radius `rmax` assuming equal masses of all
particles, or of all particles if `rmax` is `nothing`.
"""
function half_mass_radius(pos::AbstractMatrix{<:Number}; rmax::Union{Number,Nothing}=nothing)
    # check dimensions
    @assert size(pos, 1) == 3

    r² = r²_sphere(pos)

    if isnothing(rmax)
        return quantile(sqrt.(r²), 0.5)
    else
        mask = r² .≤ rmax^2
        return @views quantile(sqrt.(r²[mask]), 0.5)
    end
end

half_mass_radius(pos::AbstractMatrix{<:Number}, mass::Nothing; kwargs...) = half_mass_radius(pos, kwargs...)

"""
    half_mass_radius(g::AbstractGalaxy, ptype::Symbol; kwargs...)
    half_mass_radius(p::Particles; kwargs...)

Returns the particles' half-mass radius, depending on if a mass vector is available or not.
If the mass is not available, equal particle masses are assumed.
"""
function half_mass_radius(p::Particles; kwargs...)
    if haskey(p, :mass) && p.mass isa AbstractVector
        half_mass_radius(p.pos, p.mass; kwargs...)
    else
        half_mass_radius(p.pos; kwargs...)
    end
end

half_mass_radius(g::AbstractGalaxy, ptype::Symbol; kwargs...) = half_mass_radius(g[ptype]; kwargs...)

"""
    half_mass_radius_2D(
        pos::AbstractMatrix{<:Number},
        [mass::Union{AbstractVector{<:Number},Nothing}];
        rmax::Union{Number,Nothing}=nothing,
        perspective::Symbol=:edgeon,
    )

Returns the 2D half-mass radius of the particles within radius `rmax` when viewed from a given
`perspective` (`:edgeon`, `:sideon`, `:faceon`), or of all particles if `rmax` is `nothing`.
"""
function half_mass_radius_2D(
    pos::AbstractMatrix{<:Number},
    mass::AbstractVector{<:Number};
    rmax::Union{Number,Nothing}=nothing,
    perspective::Symbol=:edgeon,
)
    # check dimensions
    @assert size(pos, 1) == 3

    dims = get_dims(perspective)
    r² = @views r²_circle(pos[dims, :])

    return half_mass_radius(pos, mass, r²; rmax)
end

function half_mass_radius_2D(
    pos::AbstractMatrix{<:Number};
    rmax::Union{Number,Nothing}=nothing,
    perspective::Symbol=:edgeon,
)
    # check dimensions
    @assert size(pos, 1) == 3

    dims = get_dims(perspective)
    r² = @views r²_circle(pos[dims, :])

    if isnothing(rmax)
        return quantile(sqrt.(r²), 0.5)
    else
        mask = r² .≤ rmax^2
        return @views quantile(sqrt.(r²[mask]), 0.5)
    end
end

function half_mass_radius_2D(pos::AbstractMatrix{<:Number}, mass::Nothing; kwargs...)
    half_mass_radius_2D(pos; kwargs...)
end

"""
    half_mass_radius_2D(g::AbstractGalaxy, ptype::Symbol; kwargs...)
    half_mass_radius_2D(p::Particles; kwargs...)

Returns the particles' 2D half-mass radius, depending on if a mass vector is available or not.
If the mass is not available, equal particle masses are assumed.
"""
function half_mass_radius_2D(p::Particles; kwargs...)
    if haskey(p, :mass) && p.mass isa AbstractVector
        half_mass_radius_2D(p.pos, p.mass; kwargs...)
    else
        half_mass_radius_2D(p.pos; kwargs...)
    end
end

half_mass_radius_2D(g::AbstractGalaxy, ptype::Symbol; kwargs...) = half_mass_radius_2D(g[ptype]; kwargs...)


#isdefined(Main, :InterpolationKernels) || include("../src/InterpolationKernels.jl")

module InterpolationKernelsTests

using InterpolationKernels
using Test

shortname(::Nothing) = ""
shortname(m::RegexMatch) = m.captures[1]
shortname(::Type{T}) where {T} = shortname(string(T))
shortname(str::AbstractString) =
    shortname(match(r"([_A-Za-z][_A-Za-z0-9]*)([({]|$)", str))

@testset "Kernels" begin
    offsets = (0.0, 0.1, 0.2, 0.3, 0.4)
    tol = 1e-14
    conditions = (Flat, SafeFlat)
    types = (Float16, Float32, Float64)

    for (nam, ker, sup, nrml, card) in (
        ("Box", RectangularSpline(),
         1, true, true),
        ("Derivative of rectangular spline", RectangularSplinePrime(),
         1, false, false),
        ("Triangle", LinearSpline(),
         2, true, true),
        ("Derivative of Linear B-spline", LinearSplinePrime(),
         2, false, false),
        ("Quadratic B-spline", QuadraticSpline(),
         3, true, false),
        ("Derivative of Quadratic B-spline", QuadraticSplinePrime(),
         3, false, false),
        ("Cubic B-spline", CubicSpline(SafeFlat),
         4, true, false),
        ("Derivative of Cubic B-spline", CubicSplinePrime(),
         4, false, false),
        ("Catmull-Rom spline", CatmullRomSpline(),
         4, true, true),
        ("Derivative of Catmull-Rom spline", CatmullRomSplinePrime(),
         4, false, false),
        ("Cardinal cubic spline", CardinalCubicSpline(-1),
         4, true, true),
        ("Derivative of cardinal cubic spline", CardinalCubicSplinePrime(-1/2),
         4, false, false),
        ("Mitchell & Netravali spline", MitchellNetravaliSpline(),
         4, true, false),
        ("Duff's tensioned B-spline", MitchellNetravaliSpline(0.5, 0),
         4, true, false),
        ("Keys's (emulated)", MitchellNetravaliSpline(0, -0.7),
         4, true, true),
        ("Derivative of Mitchell & Netravali spline", MitchellNetravaliSplinePrime(),
         4, false, false),
        ("Derivative of Duff's tensioned B-spline", MitchellNetravaliSplinePrime(0.5, 0),
         4, false, false),
        ("Derivative of Keys's (emulated)", MitchellNetravaliSplinePrime(0, -0.7),
         4, false, false),
        ("Keys's cardinal cubics", KeysSpline(-0.7),
         4, true, true),
        ("Derivative of Keys's cardinal cubics", KeysSplinePrime(-0.6),
         4, false, false),
        ("Lanczos 2 kernel", LanczosKernel(2),
         2, false, true),
        ("Lanczos 4 kernel", LanczosKernel(Float64,4,SafeFlat),
         4, false, true),
        ("Lanczos 6 kernel", LanczosKernel(6),
         6, false, true),
        ("Lanczos 8 kernel", LanczosKernel(8),
         8, false, true),
        ("Derivative of Lanczos 4 kernel", LanczosKernelPrime(4),
         4, false, false),
        ("Derivative of Lanczos 6 kernel", LanczosKernelPrime(6),
         6, false, false))
        name = brief(ker)
        @testset "$nam" begin
            @test isnormalized(ker) == nrml
            @test iscardinal(ker) == card
            @test length(ker) == sup
            @test length(typeof(ker)) == sup
            @test size(ker) == (sup,)
            @test size(typeof(ker)) == (sup,)
            for T in types
                @test eltype(T(ker)) == T
                @test eltype(typeof(T(ker))) == T
            end
            for C in conditions
                @test boundaries(C(ker)) == C
                @test boundaries(typeof(C(ker))) == C
            end
            @test shortname(summary(ker)) == shortname(typeof(ker))
            if iscardinal(ker)
                @test ker(0) == 1
                @test maximum(abs.(ker([-3,-2,-1,1,2,3]))) ≤ tol
            end

            # S is the tuple of shifts applied in getweights.
            s = ntuple(i -> ((sup + 1) >> 1) - i, sup)
            err = 0.0
            for t in offsets
                dif = ker.(t .+ s) .- getweights(ker, t)
                err = max(err, maximum(abs.(dif)))
            end
            @test err ≤ tol
        end
    end

    # Check that type and boundaries are preserved when taking the
    # derivatives of a kernel.
    for ker in (RectangularSpline(Float32,SafeFlat),
                LinearSpline(Float32,SafeFlat),
                QuadraticSpline(Float32,SafeFlat),
                CubicSpline(Float32,SafeFlat),
                CardinalCubicSpline(Float32,-1,SafeFlat))
        kerp = ker'
        @test eltype(kerp) == eltype(ker)
        @test boundaries(kerp) == boundaries(ker)
    end

end

end # module


# varinteran.jl - operaciones básicas para computar variaciones interanuales de
# ìndices de precios

"""
    varinteran(idx::AbstractVector, base_index::Real = 100) -> Vector{<:AbstractFloat}
    varinteran(cpimat::AbstractMatrix, base_index::Real = 100) -> Matrix{<:AbstractFloat}
    varinteran(cpimat::AbstractMatrix, base_index::AbstractVector) -> Matrix{<:AbstractFloat}

Obtiene variaciones interanuales del vector `idx` o de la matriz `cpimat`
utilizando como índice base el número o vector `base_index`.

- Si `base_index` es un vector, se obtienen las variaciones interanuales
  utilizando diferentes índices base para cada columna de `cpimat`. El vector
  `base_index` debe tener la misma cantidad de columnas que `cpimat`.
"""
function varinteran end

"""
    varinteran!(v::AbstractVector, idx::AbstractVector, base_index::Real = 100) -> Vector{<:AbstractFloat}

Computa las variaciones interanuales del vector `idx` utilizando como índice
base `base_index`. Si se provee `v`, los resultados son guardados en este
vector, en vez del mismo `idx`.

- El vector `v` tiene 11 observaciones menos que `idx`.
"""
function varinteran!(v::AbstractVector, idx::AbstractVector, base_index::Real = 100)
    l = length(v)
    for i in l:-1:2
        @inbounds v[i] = 100 * (idx[i+11] / idx[i-12+11] - 1)
    end
    v[1] = 100 * (idx[12] / base_index - 1)
end

# Implementación sobre AbstractVector
function varinteran(idx::AbstractVector, base_index::Real = 100)
    r = length(idx)
    v = zeros(eltype(idx), r-11)
    varinteran!(v, idx, base_index)
    v
end

# Implementación sobre AbstractMatrix
function varinteran(cpimat::AbstractMatrix, base_index::Real = 100)
    r, c = size(cpimat)
    vmat = zeros(eltype(cpimat), r-11, c)
    for j in 1:c
        vcol = @view vmat[:, j]
        idxcol = @view cpimat[:, j]
        varinteran!(vcol, idxcol, base_index)
    end
    vmat
end

# Implementación que utiliza diferentes índices base
function varinteran(cpimat::AbstractMatrix, base_index::AbstractVector)
    r, c = size(cpimat)
    vmat = zeros(eltype(cpimat), r-11, c)
    for j in 1:c
        vcol = @view vmat[:, j]
        idxcol = @view cpimat[:, j]
        varinteran!(vcol, idxcol, base_index[j])
    end
    vmat
end


# ---
# title: 401. Binary Watch
# id: problem401
# author: Tian Jun
# date: 2020-10-31
# difficulty: Easy
# categories: Backtracking, Bit Manipulation
# link: <https://leetcode.com/problems/binary-watch/description/>
# hidden: true
# ---
#
# A binary watch has 4 LEDs on the top which represent the **hours** ( **0-11**
# ), and the 6 LEDs on the bottom represent the **minutes** ( **0-59** ).
#
# Each LED represents a zero or one, with the least significant bit on the
# right.
#
# ![](https://upload.wikimedia.org/wikipedia/commons/8/8b/Binary_clock_samui_moon.jpg)
#
# For example, the above binary watch reads "3:25".
#
# Given a non-negative integer _n_ which represents the number of LEDs that are
# currently on, return all possible times the watch could represent.
#
# **Example:**
#
#
#
#     Input: n = 1
#     Return: ["1:00", "2:00", "4:00", "8:00", "0:01", "0:02", "0:04", "0:08", "0:16", "0:32"]
#
# **Note:**
#
#   * The order of output does not matter.
#   * The hour must not contain a leading zero, for example "01:00" is not valid, it should be "1:00".
#   * The minute must be consist of two digits and may contain a leading zero, for example "10:2" is not valid, it should be "10:02".
#
#
## @lc code=start
using LeetCode

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


# ---
# title: 1404. Number of Steps to Reduce a Number in Binary Representation to One
# id: problem1404
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: String, Bit Manipulation
# link: <https://leetcode.com/problems/number-of-steps-to-reduce-a-number-in-binary-representation-to-one/description/>
# hidden: true
# ---
#
# Given a number `s` in their binary representation. Return the number of steps
# to reduce it to 1 under the following rules:
#
#   * If the current number is even, you have to divide it by 2.
#
#   * If the current number is odd, you have to add 1 to it.
#
# It's guaranteed that you can always reach to one for all testcases.
#
#
#
# **Example 1:**
#
#
#
#     Input: s = "1101"
#     Output: 6
#     Explanation: "1101" corressponds to number 13 in their decimal representation.
#     Step 1) 13 is odd, add 1 and obtain 14.
#     Step 2) 14 is even, divide by 2 and obtain 7.
#     Step 3) 7 is odd, add 1 and obtain 8.
#     Step 4) 8 is even, divide by 2 and obtain 4.
#     Step 5) 4 is even, divide by 2 and obtain 2.
#     Step 6) 2 is even, divide by 2 and obtain 1.
#
#
# **Example 2:**
#
#
#
#     Input: s = "10"
#     Output: 1
#     Explanation: "10" corressponds to number 2 in their decimal representation.
#     Step 1) 2 is even, divide by 2 and obtain 1.
#
#
# **Example 3:**
#
#
#
#     Input: s = "1"
#     Output: 0
#
#
#
#
# **Constraints:**
#
#   * `1 <= s.length <= 500`
#   * `s` consists of characters '0' or '1'
#   * `s[0] == '1'`
#
#
## @lc code=start
using LeetCode

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


# ---
# title: 479. Largest Palindrome Product
# id: problem479
# author: Tian Jun
# date: 2020-10-31
# difficulty: Hard
# categories:
# link: <https://leetcode.com/problems/largest-palindrome-product/description/>
# hidden: true
# ---
#
# Find the largest palindrome made from the product of two n-digit numbers.
#
# Since the result could be very large, you should return the largest palindrome
# mod 1337.
#
#
#
# **Example:**
#
# Input: 2
#
# Output: 987
#
# Explanation: 99 x 91 = 9009, 9009 % 1337 = 987
#
#
#
# **Note:**
#
# The range of n is [1,8].
#
#
## @lc code=start
using LeetCode

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


import Base: show, getindex, iterate, HasLength, HasEltype, length, sort

function sort(dag::DAG, bs::Vector{Vector{Symbol}})

  #topological_sort!(dag)

  dct = OrderedDict()
  for (i, s) in enumerate(dag.vars)
    dct[s] = i
  end

  # Arrange first sym < second sym

  for (i, s) in enumerate(bs)
    if isless(dct[s[2]], dct[s[1]])
      tmp = s[1]
      bs[i][1] = s[2]
      bs[i][2] = tmp
    end
  end
  fset = Symbol[]
  for (i, s) in enumerate(bs)
    push!(fset, s[1])
  end
  sort!(unique!(fset))
  bs2 = sort(bs; by = x -> x[1])

  # Now sort on second symbol within first symbol

  bs3 = Vector{Symbol}[]
  for s in fset
    indx = filter(x -> x[1] == s, bs2)
    sort!(indx; by = x -> dct[x[2]])
    for i in indx
      push!(bs3, i)
    end
  end
  bs3
end

function d_sep_combinations(dag::DAG, bs::Vector{Symbol})
  cs = Vector{Symbol}[Symbol[]]
  if length(bs) > 2
    for c in combinations(bs[3:end])
      push!(cs, c)
    end
  end
  bs_new = Vector{Symbol}[]
  for c in cs
    if length(c) == 0
      if d_separation(dag, bs[1], bs[2])
        push!(bs_new, [bs[1], bs[2]])
      end
    else
      if d_separation(dag, bs[1], bs[2]; c=c)
        push!(bs_new, [bs[1], bs[2], c...])
      end
    end
  end
  bs_new
end

struct BasisSet
  dag::DAG
  bs::Vector{Vector{Symbol}}

  # Inner constructor
  #BasisSet(dag, bs) = new(dag, sort(bs; by = x -> x[1]))

  function BasisSet(dag, bs)
    # Update,create and return the sorted BasisSet
    bs2 = sort(dag, bs)
    bs3 = Vector{Symbol}[]
    for b in bs2
      bs3 = vcat(bs3, d_sep_combinations(dag, b))
    end
    new(dag, bs3)
  end

end

iterate(b::BasisSet, state=1) =
  state > length(b.bs) ? nothing : (b.bs[state], state+1)

getindex(b::BasisSet, i::Int) = b.bs[i]

HasLength(b::BasisSet) = length(b.bs)

HasEltype(b::BasisSet) = eltype(b.bs)

length(b::BasisSet) = length(b.bs)

function bs_show(io::IO, bs::BasisSet)
  println("BasisSet[")
  for ci in bs.bs
      show(ConditionalIndependency(ci))
  end
  println("]")
end

show(io::IO, bs::BasisSet) = bs_show(io, bs)

export
  BasisSet


"""
General Interface for Pricing

		Price=pricer(mcProcess,rfCurve,mcBaseData,payoff_)

Where:\n
		mcProcess          = Process to be simulated.
		rfCurve  = Zero Rate Data.
		mcBaseData = Basic properties of MonteCarlo simulation
		payoff_ = Payoff(s) to be priced

		Price     = Price of the derivative

"""
function distribution(mcProcess::BaseProcess, rfCurve::AbstractZeroRateCurve, mcConfig::MonteCarloConfiguration, T::num_) where {num_ <: Number}
    set_seed(mcConfig)
    S = simulate(mcProcess, rfCurve, mcConfig, T)

    return S[:, end]
end


using BundleMethod
using SparseArrays
using JuMP
using Ipopt
using Random

const BM = BundleMethod

#=
This example considers:
    minimize \sum_{i=1}^N \sum_{j=1}^n b_i * (x_j - a_ij)^2
    subject to -1 <= x_j <= 1
where objective function is separable such that
    f_i(x) := \sum_{j=1}^n b_i * (x_j - a_ij)^2 =#

# Randomly generate problem data
Random.seed!(1)
n = 3
N = 2
a = rand(N, n)
b = rand(N)

#=
User-defined function:

This function takes a new trial point `y` of dimension `n`
  and returns the array of separable objective function values
  and gradient of the functions. =#
function evaluate_f(y)
    N, n = size(a)
    fvals = zeros(N)
    grads = Dict{Int,SparseVector{Float64}}()
    for i = 1:N
        grad = zeros(n)
        for j = 1:n
            fvals[i] += b[i] * (y[j] - a[i,j])^2
            grad[j] += 2 * b[i] * (y[j] - a[i,j])
        end
        grads[i] = grad
    end
    return fvals, grads
end

# This initializes the trust region bundle method with required arguments.
pm = BM.TrustRegionMethod(n, N, evaluate_f)
BM.set_bundle_tolerance!(pm, 1e-6)

# Set optimization solver to the internal JuMP.Model
model = BM.get_jump_model(pm)
set_optimizer(model, Ipopt.Optimizer)
set_optimizer_attribute(model, "print_level", 0)

# We overwrite the function to have column bounds.
function BM.add_variables!(method::BM.TrustRegionMethod)
    bundle = BM.get_model(method)
    model = BM.get_model(bundle)
    @variable(model, -1 <= x[i=1:bundle.n] <= 1)
    @variable(model, θ[j=1:bundle.N])
end

# This builds the bundle model.
BM.build_bundle_model!(pm)

# This runs the bundle method.
BM.run!(pm)


using Interpolations
using Tullio

##
x = 0:0.1:1 |> collect
f = exp.(x)

interp_lin = LinearInterpolation(x, f)

x_new = 0:(1//3):1 |> collect
f_new = interp_lin(x_new)

##
y = copy(x)
g = zeros(size(x, 1), size(y, 1))
@tullio g[i, j] = exp(x[i])

interp_lin_2d = LinearInterpolation((x, y), g)

y_new = copy(x_new)
g_new = interp_lin_2d(x_new, y_new)


x = randn(7,4,5)
k = randn(5,1,4)

## imfiltermtx

for border in ("replicate", "circular", "reflect", "symmetric", "value", "inner")
  @test_approx_eq  imfilter(x,k,border)  imfiltermtx(size(x),k,border)*vec(x)
end
@test_approx_eq  imfilter(x,k)  imfiltermtx(size(x),k)*vec(x)
@test_throws ArgumentError imfiltermtx(size(x),k,"value",1)

## psf2otf

@test_approx_eq  imfilter(x,Images.reflect(k),"circular")  real(ifft( fft(x) .* psf2otf(k,size(x)) ))
@test psf2otf(k) == psf2otf(k,size(k))
@test_throws DimensionMismatch psf2otf(k,(size(k)[1]-1,size(k)[2:end]...))

## padforfilter

for border in ("replicate", "circular", "reflect", "symmetric", "value", "inner")
  @test imfilter(x,k,border) == imfilter(padforfilter(x,size(k),border),k,"inner")
end


module MarkovChains_monotonicity
# Module for checking monotonicities in Markov chains.
# 2017: Pawel Lorek, Piotr Markowski

#
# All functions require order matrix C and transition matrix P
# Consult Lorek, P., Markowski, P. "Monotonicity requirements for efficient exact sampling with Markov chains"
# for details concerning monotonicities.
#
# Tested in Julia 0.5.0

export

  is_mobius_down,
  is_mobius_up,
  is_weak_down,
  is_weak_up,
  is_stochastic,
  which_is_not_real

function rationalize_matrix(P::Array{Float64,2})
    s=size(P)[1]
    PP=zeros(Rational{Int64},s,s)
    for i=1:s
      for j=1:s
        PP[i,j]=rationalize(P[i,j])
      end
    end
    return PP
end

# multiplying elements of matrix by lcm of their denominators
function convert_to_min_Int(P::Array{Rational{Int64},2})
   return convert(Array{Int64,2},lcm(map(den,P))*P)
end

# convert C into upper_triangular. Adjust P accordingly.
function upper_triangular(C::Array{Int64,2},P::Array{Rational{Int64},2})
  s=size(C)[1]
  c=zeros(Int64,s,2)
  for i=1:s
    c[i,:]=[i,sum(C[i,:])]
  end
  c=sortrows(c,by=x -> x[2],rev=true)
  CC=zeros(Int64,s,s)
  PP=zeros(P)
  for i=1:s
    for j=1:s
      CC[i,j]=C[c[i,1],c[j,1]]
      PP[i,j]=P[c[i,1],c[j,1]]
    end
  end
  return CC,PP
end

# create offspring matrix (consult the article)
function offspring_matrix(C::Array{Int64,2})
  D=deepcopy(C)
  s=size(D)[1]
  PP=zeros(Int64,0,s)
  for i=1:s
    for k=i+1:s
      if D[i,k]==1
        D[i,:]-=D[k,:]
        PP=[PP;sparse([1,1,1],[i,k,s],[1,-1,0])]
      end
    end
  end
  return PP
end

# create all upsets
function up_sets(C::Array{Int64,2})
  s=size(C)[1]
  D=zeros(Int,0,s)
  for i=1:s
    d=deepcopy(C[i,:])'
    for k=(i+1):s if d[1,k]==0
        for j=1:size(d)[1] if d[j,k]==0
            d=[d;d[j,:]'+C[k,:]']
        end end
    end end
    D=[D;d]
  end
  return sign(D)'
end

# check mobius_down monotonicity
function is_mobius_down(C::Array{Int64,2},P::Array{Rational{Int64},2})
  (C,P)=upper_triangular(C,P)
  R=convert_to_min_Int(P)
  return minimum(inv(C)*R*C .>= 0)
end

function is_mobius_down(C::Array{Int64,2},P::Array{Float64,2})
   return is_mobius_down(C,rationalize_matrix(P))
end

# check mobius_up monotonicity
function is_mobius_up(C::Array{Int64,2},P::Array{Rational{Int64},2})
  (C,P)=upper_triangular(C,P)
  R=convert_to_min_Int(P)
  return minimum(inv(C')*R*C' .>= 0)
end

function is_mobius_up(C::Array{Int64,2},P::Array{Float64,2})
   return is_mobius_up(C,rationalize_matrix(P))
end

# check weak_down monotonicity
function is_weak_down(C::Array{Int64,2},P::Array{Rational{Int64},2})
  (C,P)=upper_triangular(C,P)
  R=convert_to_min_Int(P)
  return minimum(offspring_matrix(C)*R*C .>= 0)
end

function is_weak_down(C::Array{Int64,2},P::Array{Float64,2})
   return is_weak_down(C,rationalize_matrix(P))
end

# check weak_up monotonicity
function is_weak_up(C::Array{Int64,2},P::Array{Rational{Int64},2})
  #(C,P)=upper_triangular(C,P)
  R=convert_to_min_Int(P)
  return minimum(offspring_matrix(C)*R*C' .<= 0)
end

function is_weak_up(C::Array{Int64,2},P::Array{Float64,2})
   return is_weak_up(C,rationalize_matrix(P))
end

# check stochastic monotonicity
function is_stochastic(C::Array{Int64,2},P::Array{Rational{Int64},2})
  (C,P)=upper_triangular(C,P)
  R=convert_to_min_Int(P)
  return minimum(offspring_matrix(C)*R*up_sets(C) .<= 0)
end

function is_stochastic(C::Array{Int64,2},P::Array{Float64,2})
   return is_stochastic(C,rationalize_matrix(P))
end

end


using DifferentialEquations
using ForwardDiff
using Random
using Plots

#=
Default forcing function
value for Q0 based on assuming flow in one cardiac cycle is 90 cm^3
    this can also be a learned parameter
We also assume that systole is 2/5 of a cardiac cycle
=#
function Q_def(t, HR=60., Q0=424.1)
    t = t[1]

    Tc = 60/HR
    Ts = Tc *2/5

    if (t%Tc) < Ts
        Q = Q0 * sin(π * (t % Tc)/(Ts))
    else
        Q = 0
    end
    Q
end

#=
2-Element Windkessel  model
p = parameters:
    R = vascular resistance
    C = vascular compliance
Q = forcing function (flow rate in the vessel, in mL/s)
=#
function wk2(u,p,t)
    R = p[3]
    C = p[2]
    (Q_def(t) - u/R)/C
end

function wk3(u,p,t)
    R₁ = p[1]
    C = p[2]
    R₂ = p[3]

    P = u

    Qt = Q_def(t)
    dQ = x -> ForwardDiff.derivative(Q_def,x)
    dQdt = dQ(t)

    dPdt = ((1 + R₁/R₂)*Qt + C*R₁*dQdt - P/R₂)/C
    dPdt

end

function wk4(u,p,t) #,Q=Q_def)
    R₁,C,R₂,L = p
    P = u

    Qt = Q_def(t)
    dQ = x -> ForwardDiff.derivative(Q_def,x)
    dQdt = dQ(t)
    d2Qdt2 = ForwardDiff.derivative(x -> ForwardDiff.derivative(Q_def, x), t)

    dPdt = ((1 + R₁/R₂)*Qt + (R₁*C + L/R₂)*dQdt + L*C*d2Qdt2 - P/R₂)/C
    dPdt
end


using ProjectEuler
using Test

@testset "ProjectEuler.jl" begin
    # Primality test
    @test ProjectEuler.isprime(37)
    @test ProjectEuler.isprime(7919)
    @test !ProjectEuler.isprime(4500)

    # Get smallest divisor
    @test ProjectEuler.get_smallest_divisor(155077777) == 7919
    @test ProjectEuler.get_smallest_divisor(100000000) == 2

    # Reverse digits of an integer
    @test ProjectEuler.reverse_int(155077777) == 777770551

    # Euler's totient
    @test ProjectEuler.totient(110) ≈ 40
    @test ProjectEuler.totient(10) ≈ 4
    @test ProjectEuler.totient(9) ≈ 6
end


#This program fits a regression model with one
#unknown change point using generated data
using DataFrames
using Gadfly
using Distributions
using Stan

dataFaked = false

function generateChangeData(ChangePointLengths,vecOfPoissonLambda) # Create a vector of poisson data of length = sum(ChangePointLengths)
	if length(ChangePointLengths) != length(vecOfPoissonLambda)
		error("length(ChangePointLengths) != length(vecOfPoissonLambda)")
	else
		asdf = [rand(Poisson(vecOfPoissonLambda[ii] ),ChangePointLengths[ii]) for ii = [1:length(vecOfPoissonLambda)]]
		reduce(vcat,asdf)
	end
end

if dataFaked
	ChangePointLengths = [20, 70]
	vecOfPoissonLambda = [1.2, 3.2]
	numobs = sum(ChangePointLengths)
	df = DataFrame(deaths = generateChangeData(ChangePointLengths,vecOfPoissonLambda))

else
	data = int(readdlm("coaldata.txt")[2:end])
	numobs = length(data)
end

const changePointModel = "
data {
	real<lower=0> r_e;
	real<lower=0> r_l;
	int<lower=1> T;
	int<lower=0> D[T];
}
transformed data {
	real log_unif;
	log_unif <- -log(T);
}
parameters {
	real<lower=0> e;
	real<lower=0> l;
}
transformed parameters {
	vector[T] lp;
	lp <- rep_vector(log_unif, T);
	for (s in 1:T)
	for (t in 1:T)
	lp[s] <- lp[s] + poisson_log(D[t], if_else(t < s, e, l));
}
model {
	e ~ exponential(r_e);
	l ~ exponential(r_l);
	increment_log_prob(log_sum_exp(lp));
}
"

stanmodel = Stanmodel(name="changePoint", model=changePointModel);
stanmodel |> display

const changedata = [
	@Compat.Dict("r_e" => 1111.1,"r_l" = 11110.2,"T" => numobs, "D" => data),
	@Compat.Dict("r_e" => 1111.1,"r_l" = 11110.2,"T" => numobs, "D" => data),
	@Compat.Dict("r_e" => 1111.1,"r_l" = 11110.2,"T" => numobs, "D" => data),
	@Compat.Dict("r_e" => 1111.1,"r_l" = 11110.2,"T" => numobs, "D" => data)
]

println("Input observed data, an array of dictionaries:")
changePointModel |> display
println()

## Simulate
sim1 = stan(stanmodel, changedata, CmdStanDir="/home/pcurry/Documents/cmdstan-2.6.2")
describe(sim1)


function avg(x)
    sum(x)/length(x)
end


using Test
import CryptoGroups.Fields: F2PB, F2GNB, print_poly, red!, mul, mul_gnb, construct_integer_order_prime, FP, order, reducer, tobits
import CryptoGroups: @bin_str

f = bin"10011"
c = reverse(bin"1100101")
@test red!(c, reverse(f)) == bin"1111" # I already changed rem and order functions and that this is the result

### Additional test for multiplication

@test mul(bin"0100", bin"0100") == bin"00100000"

a = reverse(bin"1101")
b = reverse(bin"1001")

c = mul(a, b)

@test red!(c, reverse(f)) == bin"1111"

@test mul(reverse(bin"1101"), reverse(bin"1001"), reverse(bin"10011")) == bin"1111"
@test mul(reverse(bin"0010"), reverse(bin"0010"), reverse(bin"10011")) == reverse(bin"0100")
@test mul(reverse(bin"0010"), reverse(bin"0100"), reverse(bin"10011")) == reverse(bin"1000")

a = F2PB(f)(bin"1101")
b = F2PB(f)(bin"1001")

c = F2PB(f)(bin"1111")

@test a*b == c

### Testing generation of basis elements

α = F2PB(f)(bin"0010")

@test α^(order(α) + 1) == α

@test α^8 * α^8 == α^16
@test α * α * α * α * α == α^5
@test α^14 * α == α^15

@test α^3 * α^5 == α^8

@test inv(α) * α == one(α)

@test zero(F2GNB{5, 1}) == F2GNB{5, 1}(bin"00000")
@test one(F2GNB{5, 1}) == F2GNB{5, 1}(bin"11111")

a = F2GNB{4, 1}(bin"1101")
b = F2GNB{4, 1}(bin"1011")

@test a + b == F2GNB{4, 1}(bin"0110")

@test construct_integer_order_prime(3, 3*4 + 1) in [3, 9, 1]

### Now the last bit of getting multiplication right

@test mul_gnb(bin"1000", bin"1101", 3) == bin"0010"

###

a = F2GNB{4, 3}(bin"1000")
b = F2GNB{4, 3}(bin"1101")

c = F2GNB{4, 3}(bin"0010")

@test a * b == c

@test c^(order(c) + 1) == c
@test inv(c) * c == one(c)

a = FP{23}(17)
b = FP{23}(3)

@test a * b == FP{23}(5)

@test b^11 == FP{23}(1)

#### More diligent testing for polynomial basis

# function field2poly(z::BitVector)
#     poly = Int[]
#     for (i, b) in enumerate(z)
#         if b==true
#             push!(poly, i - 1)
#         end
#     end
#     return poly
# end

#field2poly(z) = field2poly(z.x)

# Arguments are written with little endianess in mind
# let

#     F = F2PB([163, 7, 6, 3, 0])
#     f = reducer(F)

#     x19 = F(BitVector(i==20 for i in 1:163))
#     @test findfirst(x -> x== 1, mul(x19.x, x19.x)) == 39

#     x38 = F(BitVector(i==39 for i in 1:163))
#     @test findfirst(x -> x==1, red!(x38.x, f)) == 39

#     @test field2poly(x19 * x19) == [38]

#     x144 = F(BitVector(i==145 for i in 1:163))
#     x145 = F(BitVector(i==146 for i in 1:163))
#     @test field2poly(x145 * x144) == [126, 129, 132, 133]

#     x160 = F(BitVector(i==161 for i in 1:163))
#     x159 = F(BitVector(i==160 for i in 1:163))

#     @test field2poly(x160 * x159) == [0, 3, 6, 7, 156, 159, 162]

#     _x319 = BitVector(i==320 for i in 1:163*2)
#     @test field2poly(red!(_x319, f)) == [0, 3, 6, 7, 156, 159, 162]

# end


using ApproxFun

#The following samples a 2D Cauchy Distribution

f = Fun((x,y)->1/(2π*(x^2+y^2+1)^(3/2)),JacobiWeight(2.,2.,Line())^2)
x = ApproxFun.sample(f,100)


using Graphs

function MaxLambda2inEV(e::Int,v::Int)
    # finds the graph with maximum algebraic connectivity given v vertices and e edges
    # Input: Integer e number of edges, integer v number of vertices
    # Output: The value of maximum lambda and the graph object

    @assert v>=1
    if e > v^v
        # Check the number of edges
        error("Too many edges")
    end
    possible_edges = collect(combinations(collect([1:v]),2))
    possible_graphs = combinations(possible_edges,e)
    tot = length(possible_graphs)
    iter = 1
    λ_max = typemin(Float64)
    max_g = SimpleAdjacencyList
    for i in possible_graphs

        if mod(iter,25000) == 0
            # print progress
            @printf("processing graph %d/%d\n",iter,tot)
        end
        g = simple_adjlist(v,is_directed=false)
        for j in i
            add_edge!(g, j[1], j[2])   # add edge
        end

        laplacian = Symmetric(laplacian_matrix(g))
        eigs = eigvals(laplacian,v-1:v)

        e_vals = eigfact(L2,2:2)
        λ = e_vals.values
        ν = e_vals.vectors
        if λ > λ_max
            max_g = g
            λ_max = λ
        end
        iter += 1
    end
    return (λ_max, max_g)
end

#
# num_v = 3
# num_e = 8
#
# (λ_max, max_g) = MaxLambda2inEV(num_e,num_v)
#
# println(max_g)