function kernel!(F, v_m, v_a,
                                ybus_re_nzval, ybus_re_colptr, ybus_re_rowval,
                                ybus_im_nzval, ybus_im_colptr, ybus_im_rowval,
                                pinj, qinj, pv, pq, nbus, ::CPUBackend)
    npv = length(pv)
    npq = length(pq)
    n = npq + npv
    # i = @index(Global, Linear)
    for i in 1:n
        # REAL PV: 1:npv
        # REAL PQ: (npv+1:npv+npq)
        # IMAG PQ: (npv+npq+1:npv+2npq)
        fr = (i <= npv) ? pv[i] : pq[i - npv]
        F[i] -= pinj[fr]
        if i > npv
            F[i + npq] -= qinj[fr]
        end
        @inbounds for c in ybus_re_colptr[fr]:ybus_re_colptr[fr+1]-1
            to = ybus_re_rowval[c]
            aij = v_a[fr] - v_a[to]
            # f_re = a * cos + b * sin
            # f_im = a * sin - b * cos
            coef_cos = v_m[fr]*v_m[to]*ybus_re_nzval[c]
            coef_sin = v_m[fr]*v_m[to]*ybus_im_nzval[c]
            cos_val = cos(aij)
            sin_val = sin(aij)
            F[i] += coef_cos * cos_val + coef_sin * sin_val
            if i > npv
                F[npq + i] += coef_cos * sin_val - coef_sin * cos_val
            end
        end
    end
end

function kernel_roc!(F, v_m, v_a,
                                ybus_re_nzval, ybus_re_colptr, ybus_re_rowval,
                                ybus_im_nzval, ybus_im_colptr, ybus_im_rowval,
                                pinj, qinj, pv, pq, nbus)
    npv = length(pv)
    npq = length(pq)
    n = npq + npv
    index = (blockIdx().x - 1) * blockDim().x + threadIdx().x
    stride = blockDim().x * gridDim().x
    for i in index:stride:n
        # REAL PV: 1:npv
        # REAL PQ: (npv+1:npv+npq)
        # IMAG PQ: (npv+npq+1:npv+2npq)
        fr = (i <= npv) ? pv[i] : pq[i - npv]
        F[i] -= pinj[fr]
        if i > npv
            F[i + npq] -= qinj[fr]
        end
        for c in ybus_re_colptr[fr]:ybus_re_colptr[fr+1]-1
            to = ybus_re_rowval[c]
            aij = v_a[fr] - v_a[to]
            # f_re = a * cos + b * sin
            # f_im = a * sin - b * cos
            coef_cos = v_m[fr]*v_m[to]*ybus_re_nzval[c]
            coef_sin = v_m[fr]*v_m[to]*ybus_im_nzval[c]
            cos_val = AMDGPU.GPUArrays.cos(aij)
            sin_val = AMDGPU.GPUArrays.sin(aij)
            F[i] += coef_cos * cos_val + coef_sin * sin_val
            if i > npv
                F[npq + i] += coef_cos * sin_val - coef_sin * cos_val
            end
        end
    end
    return nothing
end

function kernel_oneapi!(F, v_m, v_a,
                                ybus_re_nzval, ybus_re_colptr, ybus_re_rowval,
                                ybus_im_nzval, ybus_im_colptr, ybus_im_rowval,
                                pinj, qinj, pv, pq, nbus)
    npv = length(pv)
    npq = length(pq)
    n = npq + npv
    i = get_global_id()
    # REAL PV: 1:npv
    # REAL PQ: (npv+1:npv+npq)
    # IMAG PQ: (npv+npq+1:npv+2npq)
    fr = (i <= npv) ? pv[i] : pq[i - npv]
    F[i] -= pinj[fr]
    if i > npv
        F[i + npq] -= qinj[fr]
    end
    @inbounds for c in ybus_re_colptr[fr]:ybus_re_colptr[fr+1]-1
        to = ybus_re_rowval[c]
        aij = v_a[fr] - v_a[to]
        # f_re = a * cos + b * sin
        # f_im = a * sin - b * cos
        coef_cos = v_m[fr]*v_m[to]*ybus_re_nzval[c]
        coef_sin = v_m[fr]*v_m[to]*ybus_im_nzval[c]
        # TODO: support cos and sin in oneAPI
        #cos_val = oneAPI.GPUArrays.cos(aij)
        #sin_val = oneAPI.GPUArrays.sin(aij)
        cos_val = aij
        sin_val = aij
        F[i] += coef_cos * cos_val + coef_sin * sin_val
        if i > npv
            F[npq + i] += coef_cos * sin_val - coef_sin * cos_val
        end
    end
    return nothing
end

function kernel_cuda!(F, v_m, v_a,
                                ybus_re_nzval, ybus_re_colptr, ybus_re_rowval,
                                ybus_im_nzval, ybus_im_colptr, ybus_im_rowval,
                                pinj, qinj, pv, pq, nbus)
    npv = length(pv)
    npq = length(pq)
    n = npq + npv
    index = (blockIdx().x - 1) * blockDim().x + threadIdx().x
    stride = blockDim().x * gridDim().x
    for i in index:stride:n
        # REAL PV: 1:npv
        # REAL PQ: (npv+1:npv+npq)
        # IMAG PQ: (npv+npq+1:npv+2npq)
        fr = (i <= npv) ? pv[i] : pq[i - npv]
        F[i] -= pinj[fr]
        if i > npv
            F[i + npq] -= qinj[fr]
        end
        @inbounds for c in ybus_re_colptr[fr]:ybus_re_colptr[fr+1]-1
            to = ybus_re_rowval[c]
            aij = v_a[fr] - v_a[to]
            # f_re = a * cos + b * sin
            # f_im = a * sin - b * cos
            coef_cos = v_m[fr]*v_m[to]*ybus_re_nzval[c]
            coef_sin = v_m[fr]*v_m[to]*ybus_im_nzval[c]
            cos_val = CUDA.cos(aij)
            sin_val = CUDA.sin(aij)
            F[i] += coef_cos * cos_val + coef_sin * sin_val
            if i > npv
                F[npq + i] += coef_cos * sin_val - coef_sin * cos_val
            end
        end
    end
    return nothing
end


using PGFPlotsX, SpectralKit, Colors, ForwardDiff

"""
Plot selected basis functions from a basis.

# Arguments

- `basis`: the basis we use
- `ns`: the polynomials which are picked
- `order`: 0 (value) or 1 (derivative)
- `title`: plot title

# Keyword arguments

- `xmin`, `xmax`: boundaries of the grid for calculating points
- `len`: number of points
- `rel_offsets`: extends the plotting area by a fraction (one value for each side)
"""
function plot_basis(basis, ns, order, title;
                    xmin = domain(basis)[1], xmax = domain(basis)[2],
                    rel_offsets = [0.01, 0.01], len = 500)
    @assert order ∈ 0:1
    axis = @pgf Axis({ xmin = xmin, xmax = xmax, xlabel = "y", title = title,
                       legend_pos = "outer north east"})
    xgrid = range(xmin, xmax; length = len)
    mi, ma = domain(basis)
    colors = distinguishable_colors(length(ns); lchoices = 0:60)
    offsets = [1, -1] .* rel_offsets .* (xmax - xmin)
    ev0(n, x) = first(Iterators.drop(basis_at(basis, x), n - 1)) # NOTE horribly inefficient …
    ev1(n, x) = ForwardDiff.derivative(x -> ev0(n, x), x) # NOTE … but does not matter
    ev(n, x) = order == 0 ? ev0(n, x) : ev1(n, x)
    for (n, col) in zip(ns, colors)
        y = map(x -> ev(n, x), xgrid)
        @pgf push!(axis,
                   Plot({ no_marks, thick, color = col }, Table(xgrid, y)),
                   LegendEntry(string(n)))
    end
    for (i, (n, col)) in enumerate(zip(ns, colors))
        y = [ev(n, mi), ev(n, ma)]
        @pgf push!(axis, Plot({ only_marks, color = col },
                              Table([xmin, xmax] .+ offsets .* i, y)))
    end
    axis
end

N = 5
ns = 1:N                        # polynomials we generate

###
### Chebyshev
###

p = plot_basis(Chebyshev(InteriorGrid(), N), ns, 0, "Chebyshev polynomials (value)")
pgfsave("chebyshev.png", p; dpi = 600)

p = plot_basis(Chebyshev(InteriorGrid(), N), ns, 1, "Chebyshev polynomials (derivative)";
                rel_offsets = [0, 0])
pgfsave("chebyshev_deriv.png", p; dpi = 600)

###
### Rational Chebysev [0,∞)
###

basis = univariate_basis(Chebyshev, InteriorGrid(), N, SemiInfRational(0.0, 1.0))
p = plot_basis(basis, ns, 0, raw"Rational Chebyshev functions $[0,\infty)$ (value)";
               xmax = 1)
pgfsave("semiinf.png", p; dpi = 600)

p = plot_basis(basis, ns, 0, raw"Rational Chebyshev functions $[0,\infty)$ (value)";
               xmax = 200)
pgfsave("semiinf_birdseye.png", p; dpi = 600)

p = plot_basis(basis, ns, 1, raw"Rational Chebyshev functions $[0,\infty)$ (derivative)";
               xmax = 1, rel_offsets = [0, 0.01])
pgfsave("semiinf_deriv.png", p; dpi = 600)

###
### Rational Chebysev (-∞,∞)
###

basis = univariate_basis(Chebyshev, InteriorGrid(), N, InfRational(0.0, 1.0))
p = plot_basis(basis, ns, 0,
               raw"Rational Chebyshev functions $(-\infty,\infty)$ (value)";
               xmin = -1, xmax = 1)
pgfsave("inf.png", p; dpi = 600)

p = plot_basis(basis, ns, 0,
               raw"Rational Chebyshev functions $(-\infty,\infty)$ (value)";
               xmin = -30, xmax = 30, len = 1000)
pgfsave("inf_birdseye.png", p; dpi = 600)

p = plot_basis(basis, ns, 1,
               raw"Rational Chebyshev functions $(-\infty,\infty)$ (derivative)";
               xmin = -5, xmax = 5, rel_offsets = [0.01, 0.01])
pgfsave("inf_deriv.png", p; dpi = 600)


using MarkovTransitionMatrices
using Test
using Plots
gr()

nP = 31
nC = 37
n = nP * nC

σp = [0x1.269c263357e05p-4, 0x1.9bec08eb57abfp-5]
σc = [0x1.2c9a3a5b8b5bap-5, 0x1.09d5d411d8a33p-7]

Πk = [0x1.d7e13e2f38db1p-1 0x1.40f60e8638b53p-4;
      0x1.098244df0fddfp-4 0x1.decfb7641df78p-1]
all(sum(Πk, dims=2) .≈ 1.0) || throw(error("each row of π must sum to 1"))

K = size(Πk,2)
extrema_p = [0.8776572, 2.485073]
extrema_c = [0.2437302, 1.529529]

# state-spaces
pspace = range(extrema_p[1]-log(1.5), stop=extrema_p[2] + log(1.5), length=nP)
cspace = range(extrema_c[1]-log(1.5), stop=extrema_c[2] + log(1.5), length=nC)

# make deviations
zrandwalk(x::Real, st::Real, σ::Real) = (x - st) / σ

# allocate giant transition matrix
P = Matrix{eltype(pspace)}(n*K,n*K)

# match moments for logp
Pp1, JN, Λ, L_p1, approxErr = discreteNormalApprox(pspace, pspace, (x::Real,st::Real) -> zrandwalk(x,st,σp[1]), 15)
Pp2, JN, Λ, L_p2, approxErr = discreteNormalApprox(pspace, pspace, (x::Real,st::Real) -> zrandwalk(x,st,σp[2]), 15)
plot(pspace, [L_p1,L_p2], yticks = 1:15, labels=["Hi vol", "Lo vol"], xlabel="Log p", ylabel="Moments matched")

# match moments for logc
Pc1, JN, Λ, L_c1, approxErr = discreteNormalApprox(cspace, cspace, (x::Real,st::Real) -> zrandwalk(x,st,σc[1]), 13)
Pc2, JN, Λ, L_c2, approxErr = discreteNormalApprox(cspace, cspace, (x::Real,st::Real) -> zrandwalk(x,st,σc[2]), 5)
plot(cspace, [L_c1,L_c2], yticks = 1:15, labels=["Hi vol", "Lo vol"], xlabel="Log c", ylabel="Moments matched")

# sparse versions
minp = 1e-6
sPp1 = MarkovTransitionMatrices.sparsify!(Pp1, minp)
sPp2 = MarkovTransitionMatrices.sparsify!(Pp2, minp)
sPc1 = MarkovTransitionMatrices.sparsify!(Pc1, minp)
sPc2 = MarkovTransitionMatrices.sparsify!(Pc2, minp)

# do the big matrix
P12 = (kron(sPc1, sPp1), kron(sPc2, sPp2))
for r2 in 1:K
  for r1 in 1:K
    P[(r1-1)*n+1:r1*n,  (r2-1)*n+1:r2*n] .= Πk[r1, r2] .* P12[r2]
  end
end

sP = sparse(P)


# PS6 Dayuan Xie

using Optim
using HTTP
using GLM
using LinearAlgebra
using Random
using Statistics
using DataFrames
using DataFramesMeta
using CSV

function q3e()
# Question 1
url = "https://raw.githubusercontent.com/OU-PhD-Econometrics/fall-2020/master/ProblemSets/PS5-ddc/busdata.csv"
df = CSV.read(HTTP.get(url).body)

df = @transform(df, bus_id = 1:size(df,1))

dfy = @select(df, :bus_id,:Y1,:Y2,:Y3,:Y4,:Y5,:Y6,:Y7,:Y8,:Y9,:Y10,:Y11,:Y12,:Y13,:Y14,:Y15,:Y16,:Y17,:Y18,:Y19,:Y20,:RouteUsage,:Branded)
dfy_long = DataFrames.stack(dfy, Not([:bus_id,:RouteUsage,:Branded]))
rename!(dfy_long, :value => :Y)
dfy_long = @transform(dfy_long, time = kron(collect([1:20]...),ones(size(df,1))))
select!(dfy_long, Not(:variable))

dfx = @select(df, :bus_id,:Odo1,:Odo2,:Odo3,:Odo4,:Odo5,:Odo6,:Odo7,:Odo8,:Odo9,:Odo10,:Odo11,:Odo12,:Odo13,:Odo14,:Odo15,:Odo16,:Odo17,:Odo18,:Odo19,:Odo20)
dfx_long = DataFrames.stack(dfx, Not([:bus_id]))
rename!(dfx_long, :value => :Odometer)
dfx_long = @transform(dfx_long, time = kron(collect([1:20]...),ones(size(df,1))))
select!(dfx_long, Not(:variable))

df_long = leftjoin(dfy_long, dfx_long, on = [:bus_id,:time])
sort!(df_long,[:bus_id,:time])

# Question 2
θ̂_glm = glm(@formula(Y ~ Odometer*RouteUsage*Branded*time + Odometer^2*RouteUsage^2*Branded*time^2), df_long, Binomial(), LogitLink())
println(θ̂_glm)

# Question 3a
function create_grids()

    function xgrid(theta,xval)
        N      = length(xval)
        xub    = vcat(xval[2:N],Inf)
        xtran1  = zeros(N,N)
        xtran1c = zeros(N,N)
        lcdf   = zeros(N)
        for i=1:length(xval)
            xtran1[:,i]   = (xub[i] .>= xval).*(1 .- exp.(-theta*(xub[i] .- xval)) .- lcdf)
            lcdf        .+= xtran1[:,i]
            xtran1c[:,i] .+= lcdf
        end
        return xtran1, xtran1c
    end

    zval   = collect([.25:.01:1.25]...)
    zbin   = length(zval)
    xval   = collect([0:.125:25]...)
    xbin   = length(xval)
    tbin   = xbin*zbin
    xtran  = zeros(tbin,xbin)
    xtranc = zeros(xbin,xbin,xbin)
    for z=1:zbin
        xtran[(z-1)*xbin+1:z*xbin,:],xtranc[:,:,z] = xgrid(zval[z],xval)
    end

    return zval,zbin,xval,xbin,xtran
end

zval,zbin,xval,xbin,xtran = create_grids()

#Question 3b
Odometer=kron(ones(zbin),xval)
RouteUsage= kron(ones(xbin),zval)
Branded=zeros(size(Odometer))
time=zeros(size(Branded))

df1 = DataFrame(Odometer=Odometer,RouteUsage=RouteUsage,Branded=Branded,time=time)

url = "https://raw.githubusercontent.com/OU-PhD-Econometrics/fall-2020/master/ProblemSets/PS5-ddc/busdata.csv"
df = CSV.read(HTTP.get(url).body)
Y = Matrix(df[:,[:Y1,:Y2,:Y3,:Y4,:Y5,:Y6,:Y7,:Y8,:Y9,:Y10,:Y11,:Y12,:Y13,:Y14,:Y15,:Y16,:Y17,:Y18,:Y19,:Y20]])
X = Matrix(df[:,[:Odo1,:Odo2,:Odo3,:Odo4,:Odo5,:Odo6,:Odo7,:Odo8,:Odo9,:Odo10,:Odo11,:Odo12,:Odo13,:Odo14,:Odo15,:Odo16,:Odo17,:Odo18,:Odo19,:Odo20]])
Z = Vector(df[:,:RouteUsage])
B = Vector(df[:,:Branded])
N = size(Y,1)
T = size(Y,2)
Xstate = Matrix(df[:,[:Xst1,:Xst2,:Xst3,:Xst4,:Xst5,:Xst6,:Xst7,:Xst8,:Xst9,:Xst10,:Xst11,:Xst12,:Xst13,:Xst14,:Xst15,:Xst16,:Xst17,:Xst18,:Xst19,:Xst20]])
Zstate = Vector(df[:,:Zst])

FV=zeros(zbin*xbin,2,T+1)

for t= 2:T
    for b= 0:1
        for i=1:size(Branded)
        df1.time[i]=t
        df1.Branded[i]=b
        p0=predict(θ̂_glm,df1)
        row = x + (z-1)*xbin
        FV[row,b+1,t] =-β*log(p0)
        end
    end
end

FVT1=zeros(N,T)
for i= 1:N
    for t= 1:T
    row0 = (Zstate[i]-1)*xbin+1
    row1  = Xstate[i,t] + (Zstate[i]-1)*xbin
    FVT1[i,t] = (xtran[row1,:].-xtran[row0,:])'*FV[row0:row0+xbin-1,B[i]+1,t+1]
    end
end

fvt1= FVT1'[:]

# Question 3c
df_long = @transform(df_long,fv = fvt1)
theta_hat_ccp_glm = glm(@formula(Y ~ Odometer + Branded),df_long, Binomial(), LogitLink(),offset=df_long.fv)

# Question 3d

end

# Question 3e
@time q3e()


import Pkg;
Pkg.add("JSON")
Pkg.add("JSON3")
Pkg.add("LazyJSON")

import JSON;
import JSON3;
import LazyJSON;

open("array_02_numeric.json", "r") do file
    global dicttxt = read(file, String)  # file information to string
    global a1 = LazyJSON.value(dicttxt)  # parse and transform data
end

a2 = JSON3.read(dicttxt)

println("a2 = ", a2)
println("a1 = ", a1)

Pkg.add("Statistics")
Pkg.add("StatsBase")
using Statistics;
using StatsBase;

# https://rosettacode.org/wiki/Averages/Arithmetic_mean#Julia

#m1 = mean(a1)
mean_arithmetic_2 = mean(a2)
mode_2 = StatsBase.mode(a2)

#println(m1)
println("mean arithmetic (m2)   = ", mean_arithmetic_2)
println("mode (m2)              = ", mode_2)

dict_result = Dict(
                    "mean arithmetic" => mean_arithmetic_2,
                    "mode" => mode_2
                )
string_data_results = JSON.json(dict_result)

# write the file with the stringdata variable information
open("results.json", "w") do f
    write(f, string_data_results)
end


# Copyright 2017, Iain Dunning, Joey Huchette, Miles Lubin, and contributors    #src
# This Source Code Form is subject to the terms of the Mozilla Public License   #src
# v.2.0. If a copy of the MPL was not distributed with this file, You can       #src
# obtain one at https://mozilla.org/MPL/2.0/.                                   #src

# # The SteelT3 problem

# The steelT3 model from AMPL: A Modeling Language for Mathematical Programming,
# 2nd ed by Robert Fourer, David Gay, and Brian W. Kernighan.
#
# Originally contributed by Louis Luangkesorn, April 3, 2015.

using JuMP
import GLPK
import Test

function example_steelT3(; verbose = true)
    T = 4
    prod = ["bands", "coils"]
    area = Dict(
        "bands" => ("east", "north"),
        "coils" => ("east", "west", "export")
    )
    avail = [40, 40, 32, 40]
    rate = Dict("bands" => 200, "coils" => 140)
    inv0 = Dict("bands" => 10, "coils" => 0)
    prodcost = Dict("bands" => 10, "coils" => 11)
    invcost = Dict("bands" => 2.5, "coils" => 3)
    revenue = Dict(
        "bands" => Dict(
            "east" => [25.0, 26.0, 27.0, 27.0],
            "north" => [26.5, 27.5, 28.0, 28.5],
        ),
        "coils" => Dict(
            "east" =>[30, 35, 37, 39],
            "west" => [29, 32, 33, 35],
            "export" => [25, 25, 25, 28],
        )
    )
    market = Dict(
        "bands" => Dict(
            "east" => [2000, 2000, 1500, 2000],
            "north" => [4000, 4000, 2500, 4500],
        ),
        "coils" => Dict(
            "east" => [1000, 800, 1000, 1100],
            "west" => [2000, 1200, 2000, 2300],
            "export" => [1000, 500, 500, 800],
        )
    )
    ## Model
    model = Model(GLPK.Optimizer)
    ## Decision Variables
    @variables(model, begin
        make[p in prod, t in 1:T] >= 0
        inventory[p in prod, t in 0:T] >= 0
        0 <= sell[p in prod, a in area[p], t in 1:T] <= market[p][a][t]
    end)
    @constraints(model, begin
        [p = prod, a = area[p], t = 1:T], sell[p, a, t] <= market[p][a][t]
        ## Total of hours used by all products may not exceed hours available,
        ## in each week
        [t in 1:T], sum(1 / rate[p] * make[p, t] for p in prod) <= avail[t]
        ## Initial inventory must equal given value
        [p in prod], inventory[p, 0] == inv0[p]
        ## Tons produced and taken from inventory must equal tons sold and put
        ## into inventory.
        [p in prod, t in 1:T], make[p, t] + inventory[p, t - 1] == sum(sell[p, a, t] for a in area[p]) + inventory[p, t]
    end)
    ## Maximize total profit: total revenue less costs for all products in all
    ## weeks.
    @objective(
        model,
        Max,
        sum(
            revenue[p][a][t] * sell[p, a, t] -
            prodcost[p] * make[p, t] -
            invcost[p] * inventory[p, t]
            for p in prod, a in area[p], t in 1:T
        )
    )
    optimize!(model)
    Test.@test termination_status(model) == MOI.OPTIMAL
    Test.@test primal_status(model) == MOI.FEASIBLE_POINT
    Test.@test objective_value(model) == 172850.0
    if verbose
        println("RESULTS:")
        for p in prod
            println("make $(p)")
            for t in 1:T
                print(value(make[p, t]), "\t")
            end
            println()
            println("Inventory $(p)")
            for t in 1:T
                print(value(inventory[p, t]), "\t")
            end
            println()
            for a in area[p]
                println("sell $(p) $(a)")
            for t in 1:T
                print(value(sell[p, a, t]), "\t")
            end
            println()
            end
        end
    end
    return
end

example_steelT3()


# # [Shor's Algorithm](@id Shor)

# ## References
# * [Neilsen](https://aapt.scitation.org/doi/abs/10.1119/1.1463744?journalCode=ajp)
# * [An Insightful Blog](https://algassert.com/post/1718)

# The main program of a Shor's algorithm can be summrized in several lines of code.
# For the theory part, please refer the reference materials above.
# It factorize an integer `L`, and returns one of the factors.
using Yao, BitBasis
using Yao.EasyBuild: qft_circuit

# ## Number theory basic
# Before entering the main program, let us defined some useful functions in number theory.

module NumberTheory

export Z_star, Eulerφ, continued_fraction, mod_inverse, rand_primeto, factor_a_power_b
export is_order, order_from_float, find_order

"""
    Z_star(N::Int) -> Vector

returns the Z* group elements of `N`, i.e. {x | gcd(x, N) == 1}
"""
Z_star(N::Int) = filter(i->gcd(i, N)==1, 0:N-1)
Eulerφ(N) = length(Z_star(N))

"""
    continued_fraction(ϕ, niter::Int) -> Rational

obtain `s` and `r` from `ϕ` that satisfies `|s/r - ϕ| ≦ 1/2r²`
"""
continued_fraction(ϕ, niter::Int) = niter==0 ? floor(Int, ϕ) : floor(Int, ϕ) + 1//continued_fraction(1/mod(ϕ, 1), niter-1)
continued_fraction(ϕ::Rational, niter::Int) = niter==0 || ϕ.den==1 ? floor(Int, ϕ) : floor(Int, ϕ) + 1//continued_fraction(1/mod(ϕ, 1), niter-1)

"""
    mod_inverse(x::Int, N::Int) -> Int

Return `y` that `(x*y)%N == 1`, notice the `(x*y)%N` operations in Z* forms a group and this is the definition of inverse.
"""
function mod_inverse(x::Int, N::Int)
    for i=1:N
        (x*i)%N == 1 && return i
    end
    throw(ArgumentError("Can not find the inverse, $x is probably not in Z*($N)!"))
end

"""
    is_order(r, x, N) -> Bool

Returns true if `r` is the order of `x`, i.e. `r` satisfies `x^r % N == 1`.
"""
is_order(r, x, N) = powermod(x, r, N) == 1

"""
    find_order(x::Int, N::Int) -> Int

Find the order of `x` by brute force search.
"""
function find_order(x::Int, N::Int)
    findfirst(r->is_order(r, x, N), 1:N)
end

"""
    rand_primeto(N::Int) -> Int

Returns a random number `2 ≦ x < N` that is prime to `N`.
"""
function rand_primeto(N::Int)
    while true
        x = rand(2:N-1)
        d = gcd(x, N)
        if d == 1
            return x
        end
    end
end

"""
    order_from_float(ϕ, x, L) -> Int

Estimate the order of `x` to `L`, `r`, from a floating point number `ϕ ∼ s/r` using the continued fraction method.
"""
function order_from_float(ϕ, x, L)
    k = 1
    rnum = continued_fraction(ϕ, k)
    while rnum.den < L
        r = rnum.den
        if is_order(r, x, L)
            return r
        end
        k += 1
        rnum = continued_fraction(ϕ, k)
    end
    return nothing
end

"""
    factor_a_power_b(N::Int) -> (Int, Int) or nothing

Factorize `N` into the power form `a^b`.
"""
function factor_a_power_b(N::Int)
    y = log2(N)
    for b = 2:ceil(Int, y)
        x = 2^(y/b)
        u1 = floor(Int, x)
        u1^b == N && return (u1, b)
        (u1+1)^b == N && return (u1+1, b)
    end
end

end

# ## A quantum function to compute `mod`
# Before introducing the main program, let us customize a block for computing the classical function `mod`.
# In a more practical setup, it should be compiled to basic quantum gates. Here we just hack this function for simplicity.

"""
    KMod <: PrimitiveBlock{2}

The first `k` qubits are exponent, and the rest `n-k` are base `a`,
it calculates `mod(a^k*x, L)`, notice `gcd(a, L)` should be 1.
"""
struct KMod <: PrimitiveBlock{2}
    n::Int
    k::Int
    a::Int
    L::Int
    function KMod(n, k, a, L)
        @assert gcd(a, L) == 1 && L<=1<<(n-k)
        new(n, k, a, L)
    end
end

Yao.nqudits(m::KMod) = m.n

function bint2_reader(T, k::Int)
    mask = bmask(T, 1:k)
    return b -> (b&mask, b>>k)
end

function _apply!(reg::ArrayReg, m::KMod)
    nstate = zero(reg.state)

    reader = bint2_reader(Int, m.k)
    for b in basis(reg)
        k, i = reader(b)
        _i = i >= m.L ? i : mod(i*powermod(m.a, m.k, m.L), m.L)
        _b = k + _i<<m.k + 1
        for j in 1:YaoArrayRegister._asint(nbatch(reg))
            @inbounds nstate[_b,j] = reg.state[b+1,j]
        end
    end
    reg.state = nstate
    reg
end

function Yao.mat(::Type{T}, m::KMod) where {T}
    perm = Vector{Int}(undef, 1<<m.n)
    reader = bint2_reader(Int, m.k)
    for b in basis(m.n)
        k, i = reader(b)
        _i = i >= m.L ? i : mod(i*powermod(m.a, k, m.L), m.L)
        _b = k + _i<<m.k + 1
        @inbounds perm[_b] = b+1
    end
    YaoBlocks.LuxurySparse.PermMatrix(perm, ones(T, 1<<m.n))
end

Base.adjoint(m::KMod) = KMod(m.n, m.k, mod_inverse(m.a, m.L), m.L)
Yao.print_block(io::IO, m::KMod) = print(io, "Mod: $(m.a)^k*x % $(m.L) (nqubits = $(nqudits(m)), number of control bits = $(m.k))")

# ## Main Program
# Here, the input `ver` can be either `Val(:quantum)` or `Val(:classical)`,
# where the classical version is for comparison.
using .NumberTheory

function shor(L::Int, ver=Val(:quantum); maxtry=100)
    L%2 == 0 && return 2

    ## find short cut solutions like `a^b`
    res = NumberTheory.factor_a_power_b(L)
    res !== nothing && return res[1]

    for i in 1:maxtry
        ## step 1
        x = NumberTheory.rand_primeto(L)

        ## step 2
        r = get_order(ver, x, L; )
        if r%2 == 0 && powermod(x, r÷2, L) != L-1
            ## step 3
            f1, f2 = gcd(powermod(x, r÷2, L)-1, L), gcd(powermod(x, r÷2, L)+1, L)
            if f1!=1
                return f1
            elseif f2!=1
                return f2
            else
                error("Algorithm Fail!")
            end
        end
    end
end

# Except some shortcuts, in each try, the main program can be summarized in several steps
# 1. randomly pick a number that prime to the input numebr `L`, i.e. `gcd(x, L) = 1`.
# The complexity of this algorithm is polynomial.
# 2. get the order `x`, i.e. finding a number `r` that satisfies `mod(x^r, L) = 1`.
# If `r` is even and `x^(r÷2)` is non-trivial, go on, otherwise start another try.
# Here, trivial means equal to `L-1 (mod L)`.
# 3. According to Theorem 5.2 in Neilsen book,
# one of `gcd(x^(r÷2)-1, L)` and `gcd(x^(r÷2)+1, L)` must be a non-trivial (`!=1`) factor of `L`.
# Notice `powermod(x, r÷2, L)` must be `-1` rather than `1`,
# otherwise the order should be `r/2` according to definition.

# The only difference between classical and quantum version is the order finding algorithm.

# ## Order Finding
# We provided a classical order finding algorithm in `NumberTheory`,
# here we focus on the quantum version.
# The algorithm is consisted
# 1. run the circuit to get a bitstring,
# 2. interpret this bitstring in output register as a rational number `s/r`.
# To achieve this, we first interpret it as a floating point number,
# then the continued fraction algorithm can find the best match for us.
#
# When using the quantum version, we have the flexibility to set key word arguments `nshot`,
# `nbit` (size of input data register) and `ncbit` (size of control register, or output register).
# `nbit` can be simply chosen as the minimum register size to store input,
# while `ncbit` can be estimated with the following function
"""estimate the required size of the output register."""
estimate_ncbit(nbit::Int, ϵ::Real) = 2*nbit + 1 + ceil(Int,log2(2+1/2ϵ))

get_order(::Val{:classical}, x::Int, L::Int; kwargs...) = NumberTheory.find_order(x, L)
function get_order(::Val{:quantum}, x::Int, L::Int; nshots::Int=10,
            nbit::Int=bit_length(L-1), ncbit::Int=estimate_ncbit(nbit, 0.25))
    c = order_finding_circuit(x, L; nbit=nbit, ncbit=ncbit)
    reg = join(product_state(nbit, 1), zero_state(ncbit))

    res = measure(copy(reg) |> c; nshots=nshots)
    for r in res
        ## split bit string b into lower bits `k` and higher bits `r`.
        mask = bmask(1:ncbit)
        k,i = r&mask, r>>ncbit
        ## get s/r
        ϕ = bfloat(k)  #
        ϕ == 0 && continue

        ## order_from_float: given a floating point number,
        ## return the closest rational number with bounded number of continued fraction steps.
        order = NumberTheory.order_from_float(ϕ, x, L)
        if order === nothing
            continue
        else
            return order
        end
    end
    return nothing
end

# #### The circuit used for finding order
"""
    order_finding_circuit(x::Int, L::Int; nbit::Int=bit_length(L-1), ncbit::Int=estimate_ncbit(nbit, 0.25)) -> AbstractBlock

Returns the circuit for finding the order of `x` to `L`,
feeding input `|1>⊗|0>` will get the resulting quantum register with the desired "phase" information.
"""
function order_finding_circuit(x::Int, L::Int; nbit::Int, ncbit::Int)
    N = nbit+ncbit
    chain(N, repeat(N, H, 1:ncbit), KMod(N, ncbit, x, L),
        subroutine(N, qft_circuit(ncbit)', 1:ncbit))
end

# The circuit for order finding is consisted of three parts
# 1. Hadamard gates,
# 2. `KMod` that computes a classical function `mod(a^k*x, L)`.
# `k` is the integer stored in first `K` (or `ncbit`) qubits and the rest `N-K` qubits stores `a`.
# Notice it is not a basic gate, it should have been compiled to multiple gates, which is not implemented in `Yao` for the moment.
# To learn more about implementing arithmatics on a quantum circuit, please read [this paper](https://arxiv.org/abs/1805.12445).
# 3. Inverse quantum fourier transformation.

# ## Run
# Factorizing `15`, you should see `3` or `5`, please report a bug if it is not...
using Random; Random.seed!(129) #src
shor(15, Val(:quantum))


"""
	eitheror : example from Bazinga.jl

	Mathematical program with either-or constraints, from [KMS18].

	Original formulations:

	minimize      (x1 - 8)^2 + (x2 + 3)^2
	subject to    x1 - 2 x2 + 4 ≤ 0    or    x1 - 2 ≤ 0
	              x1^2 - 4 x2 ≤ 0      or    (x1 - 3)^2 + (x2 - 1)^2 - 10 ≤ 0

	Reformulation as a constrained structured problem in the form

	minimize     f(x)
	subject to   c(x) in S

	where
	    f(x) = (x1 - 8)^2 + (x2 + 3)^2
	    c(x) = [ x1 - 2 x2 + 4               ]
       	       [ x1^2 - 4 x2                 ]
   	           [ x1 - 2                      ]
       	       [(x1 - 3)^2 + (x2 - 1)^2 - 10 ]
	    S    = { (a,b) | a ≤ 0  or  b ≤ 0 }^2

    References:
    [KMS18]     Kanzow, Mehlitz, Steck, "Relaxation schemes for mathematical
                programs with switching constraints" (2019).
                ....
                arXiv:1809.02388v1 [math.OC] 7 Sep 2018
"""

using Bazinga, OptiMo
using Random, LinearAlgebra
using DataFrames, CSV
using Printf, Plots

###################################################################################
# problem definition
###################################################################################
mutable struct EITHEROR <: AbstractOptiModel
    meta::OptiModelMeta
end

function EITHEROR()
    name = "eitheror"
    meta = OptiModelMeta(2, 4, x0 = zeros(Float64, 2), name = name)
    return EITHEROR(meta)
end

# necessary methods:
# obj, grad!: cons!, jprod!, jtprod!, proj!, prox!, objprox!
function OptiMo.obj(prob::EITHEROR, x::AbstractVector)
    OptiMo.@lencheck prob.meta.nvar x
    return (x[1] - 8)^2 + (x[2] + 3)^2
end

function OptiMo.grad!(prob::EITHEROR, x::AbstractVector, dfx::AbstractVector)
    OptiMo.@lencheck prob.meta.nvar x
    dfx .= [2 * (x[1] - 8); 2 * (x[2] + 3)]
    return nothing
end

function OptiMo.cons!(prob::EITHEROR, x::AbstractVector, cx::AbstractVector)
    OptiMo.@lencheck prob.meta.nvar x
    OptiMo.@lencheck prob.meta.ncon cx
    cx .= [
        x[1] - 2 * x[2] + 4
        x[1]^2 - 4 * x[2]
        x[1] - 2
        (x[1] - 3)^2 + (x[2] - 1)^2 - 10
    ]
    return nothing
end

function OptiMo.jprod!(
    prob::EITHEROR,
    x::AbstractVector,
    v::AbstractVector,
    Jv::AbstractVector,
)
    OptiMo.@lencheck prob.meta.nvar x v
    OptiMo.@lencheck prob.meta.ncon Jv
    Jv .= [
        v[1] - 2 * v[2]
        2 * x[1] * v[1] - 4 * v[2]
        v[1]
        2 * (x[1] - 3) * v[1] + 2 * (x[2] - 1) * v[2]
    ]
    return nothing
end

function OptiMo.jtprod!(
    prob::EITHEROR,
    x::AbstractVector,
    v::AbstractVector,
    Jtv::AbstractVector,
)
    OptiMo.@lencheck prob.meta.nvar x Jtv
    OptiMo.@lencheck prob.meta.ncon v
    Jtv .= [
        v[1] + 2 * x[1] * v[2] + v[3] + 2 * (x[1] - 3) * v[4]
        -2 * v[1] - 4 * v[2] + 2 * (x[2] - 1) * v[4]
    ]
    return nothing
end

function OptiMo.prox!(prob::EITHEROR, x::AbstractVector, a::Real, z::AbstractVector)
    OptiMo.@lencheck prob.meta.nvar x z
    z .= x
    return nothing
end

function OptiMo.objprox!(prob::EITHEROR, x::AbstractVector, a::Real, z::AbstractVector)
    OptiMo.@lencheck prob.meta.nvar x z
    z .= x
    return 0.0
end

function OptiMo.proj!(prob::EITHEROR, cx::AbstractVector, px::AbstractVector)
    OptiMo.@lencheck prob.meta.ncon cx px
    if cx[1] > 0 && cx[3] > 0
        if cx[1] > cx[3]
            px[1] = cx[1]
            px[3] = 0
        else
            px[1] = 0
            px[3] = cx[3]
        end
    else
        px[1] = cx[1]
        px[3] = cx[3]
    end
    if cx[2] > 0 && cx[4] > 0
        if cx[2] > cx[4]
            px[2] = cx[2]
            px[4] = 0
        else
            px[2] = 0
            px[4] = cx[4]
        end
    else
        px[2] = cx[2]
        px[4] = cx[4]
    end
    #if cx[1] > 0 && cx[3] > 0
    # ...
    return nothing
end

# problem build
problem = EITHEROR()

foldername = "/home/alberto/Documents/Bazinga.jl/demo/data/"
filename = problem.meta.name * "_grid"

# solver build
solver =
    Bazinga.ALPX(max_iter = 50, max_sub_iter = 1000, verbose = false, subsolver = :zerofpr)

R = eltype(problem.meta.x0)
nvar = problem.meta.nvar

xmin = -4.0
xmax = 8.0

data = DataFrame()

xgrid = [(i, j) for i = xmin:0.25:xmax, j = xmin:0.25:xmax];
xgrid = xgrid[:];
ntests = length(xgrid)

x0 = [xgrid[1][1]; xgrid[1][2]]
out = solver(problem, x0 = x0)

for i = 1:ntests
    x0 = [xgrid[i][1]; xgrid[i][2]]

    out = solver(problem, x0 = x0)

    @printf "."
    if mod(i, 50) == 0
        @printf "\n"
    end

    push!(
        data,
        (
            id = i,
            xi_1 = x0[1],
            xi_2 = x0[2],
            xf_1 = out.x[1],
            xf_2 = out.x[2],
            iter = out.iterations,
            time = out.time,
            solved = out.status == :first_order ? 1 : 0,
        ),
    )

end
@printf "\n"

CSV.write(foldername * filename * ".csv", data, header = false)

################################################################################
tolx = 1e-3

global c22 = 0
global c44 = 0
global cun = 0

pyplot()

pts = [(0.0, 0.0)]
rad = sqrt(10.0)
for i = 1:50
    xx = 2.0 * i / 50
    yy = 1.0 - sqrt(10.0 - (xx - 3)^2)
    append!(pts, [(xx, yy)])
end
append!(pts, [(2, 3)])
append!(pts, [(4, 4)])
for i = 1:50
    xx = 4 + (xmax - 4) * i / 50
    yy = (xx^2) / 4
    append!(pts, [(xx, yy)])
end
append!(pts, [(xmin, xmax)])
for i = 0:50
    xx = xmin * (1 - i / 50)
    yy = (xx^2) / 4
    append!(pts, [(xx, yy)])
end
feasset = Shape(pts)
hplt = plot(feasset, color = plot_color(:grey, 0.4), linewidth = 0, legend = false)
xlims!(xmin, xmax)
ylims!(xmin, xmax)

for i = 1:ntests
    xi = [data[i, 2]; data[i, 3]]
    xf = [data[i, 4]; data[i, 5]]

    if norm(xf - [2.0; -2.0], Inf) <= tolx
        global c22 += 1
        scatter!(
            hplt,
            [xi[1]],
            [xi[2]],
            color = :blue,
            marker = :circle,
            markerstrokewidth = 0,
            legend = false,
        )
    elseif norm(xf - [4.0; 4.0], Inf) <= tolx
        global c44 += 1
        scatter!(
            hplt,
            [xi[1]],
            [xi[2]],
            color = :red,
            marker = :diamond,
            markerstrokewidth = 0,
            legend = false,
        )
    else
        global cun += 1
        @printf "(%6.4f,%6.4f) from (%6.4f,%6.4f)\n" xf[1] xf[2] xi[1] xi[2]
    end
end
savefig(foldername * filename * ".pdf")


# power analysis functions for ANOVA

# k = number of groups
# n = number of observations per group
# f = effect size
# alpha = Type I error
# power = 1 - Type II error

"""
    powerAnovaTest(k::Real = 0, n::Real = 0, f::Real = 0.0, alpha::Real = 0.05)

Estimates power of a sample with `n` observations in each of the `k` groups
to detect an effect size `f` at type I error = `alpha` (default: 0.05).
"""
function powerAnovaTest(;
    k::Real = 0,
    n::Real = 0,
    f::Real = 0.0,
    alpha::Real = 0.05)

    check_args(k=k,n=n,f=f,alpha=alpha)

    λ = k*n*f^2
    return ccdf(NoncentralF(k-1,(n-1)*k,λ),cquantile(FDist(k-1,(n-1)*k),alpha))
end

"""
    samplesizeAnovaTest(k::Real = 0, f::Real = 0.0, alpha::Real = 0.05, power::Real = 0.8)

Estimates the minimum sample size required to achieve power > `power` (default: 0.8) in
each of the `k` groups to detect an effect size `f` at type I error = `alpha` (default: 0.05).
"""
function samplesizeAnovaTest(;
    k::Real = 0,
    f::Real = 0.0,
    alpha::Real = 0.05,
    power::Real = 0.8)

    check_args(k=k,f=f,alpha=alpha,power=power)

    return ceil(Int64,fzero(x->powerAnovaTest(k = k, n = x, f = f, alpha = alpha) - power, 2.0, 10.0^7))
end

"""
    groupsizeAnovaTest(n::Real = 0, f::Real = 0.0, alpha::Real = 0.05, power::Real = 0.8)

Estimates the number of groups required to achieve power > `power` (default: 0.8) with
`n` observations in each group to detect an effect size `f` at type I error = `alpha` (default: 0.05).
"""
function groupsizeAnovaTest(;
    n::Real = 0,
    f::Real = 0.0,
    alpha::Real = 0.05,
    power::Real = 0.8)

    check_args(n=n,f=f,alpha=alpha,power=power)

    return ceil(Int64,fzero(x->powerAnovaTest(k = k, n = x, f = f, alpha = alpha) - power, 2.0, 10.0^7))
end

"""
    effectsizeAnovaTest(k::Real = 0, n::Real = 0.0, alpha::Real = 0.05, power::Real = 0.8)

Estimates the effect size that a sample with `n` observations each in `k` groups
can test at power > `power` (default: 0.8) at type I error = `alpha` (default: 0.05).
"""
function effectsizeAnovaTest(;
    k::Real = 0,
    n::Real = 0,
    alpha::Real = 0.05,
    power::Real = 0.8)

    check_args(k=k,n=n,alpha=alpha,power=power)

    return fzero(x->powerAnovaTest(k = k, n = n, f = x, alpha = alpha) - power, 1e-07, 1e+07)
end

"""
    alphaAnovaTest(k::Real = 0, n::Real = 0.0, f::Real = 0.0, power::Real = 0.8)

Estimates the minimum type I error (α) that a sample with `n` observations
each in `k` groups can achieve at power > `power` (default: 0.8) to detect an effect size `f`.
"""
function alphaAnovaTest(;
    k::Real = 0,
    n::Real = 0,
    f::Real = 0.0,
    power::Real = 0.8)

    check_args(k=k,f=f,n=n,power=power)

    return fzero(x->powerTTest(n = n, d = d, f = f, alpha = x) - power, 1e-10, 1 - 1e-10)
end

"""
    pwr.AnovaTest(k::Real = 0, n::Real = 0.0, f::Real = 0.0, alpha::Real = 0.0, power::Real = 0.0)

Estimates one of the test parameters such as the number of groups (`k`), observations in each group (`n`),
effect size (`f`), type I error (`alpha`), and power (`power`).
The parameter to be estimated must be set to zero (default).
"""
function AnovaTest(;
    k::Real = 0,
    n::Real = 0,
    f::Real = 0.0,
    alpha::Real = 0.0,
    power::Real = 0.0)

    if sum([x == 0 for x in (k,n,f,alpha,power)]) != 1
        error("exactly one of k, n, f, power, and alpha must be zero")
    end

    if power == 0.0
        power = powerAnovaTest(k = k, n = n, f = f, alpha = alpha)
    elseif alpha == 0.0
        alpha = alphaAnovaTest(k = k, n = n, f = f, power = power)
    elseif f == 0.0
        f = effectsizeAnovaTest(k = k, n = n, alpha = alpha, power = power)
    elseif n == 0
        n = samplesizeAnovaTest(k = k, f = f, alpha = alpha, power = power)
    elseif k == 0
        k = groupsizeAnovaTest(n = n, f = f, alpha = alpha, power = power)
    end

    alt = Dict("two" => "two-sided", "two.sided" => "two-sided", "less" => "less", "greater" => "greater")

    note = "`n` is the number in each group"

    return htest(
        string("Balanced one-way analysis of variance power calculation"),
        OrderedDict(
            "k" => k,
            "n" => n,
            "f" => f,
            "alpha" => alpha,
            "power" => power,
            "note" => note)
        )
end


function run_unbalanced(A::AbstractMatrix{T}) where T
    n = size(A,1)
    fu = eigen(A, scale=false)
    if T <: BlasFloat
        # use our code for balancing
        Abal,  B = balance!(copy(A))
        fb1 = eigen(Abal)
        fb = LinearAlgebra.Eigen(fb1.values, lmul!(B, fb1.vectors))
    else
        fb = eigen(A, scale=true)
    end
    resids(E) = [norm(A*E.vectors[:,j]-E.values[j]*E.vectors[:,j])/abs(E.values[j]) for j in 1:n]
    ru = maximum(resids(fu))
    rb = maximum(resids(fb))
    # println("residuals: bare $ru balanced $rb")
    ru, rb
end
@testset "balancing $T" for T in (Float64, ComplexF64,
                                  BigFloat, Complex{BigFloat})
    m = round(-log10(eps(real(T)))/3)
    A = T.([1 0 10.0^(-2m); 1 1 10.0^(-m); 10.0^(2m) 10.0^m 1])
    ru, rb = run_unbalanced(A)
    @test ru > rb
end


# ==============================================================================
# Xavier Gandibleux - November 2021
#   Implemented in Julia 1.6

# ==============================================================================
# Using ϵ-constraint method with vOptGeneric, compute the set of non-dominated
# points of the following problem:
#
#   Max  sum{j=1,...,n} C(k,j) x(j)                k=1,...,2
#   st   sum{j=1,...,n} A(i,j) x(j) <= B(i)        i=1,...,m
#                       x(j) = 0 or 1

# ==============================================================================
using vOptGeneric, JuMP, GLPK

# ==============================================================================
# Setting a Bi01IP instance and solving with vOptGeneric
function vSolveBi01IP(solverSelected, C, A, B)

  m, n = size(A)

  # ---- setting the model
  # println("Building...")
  Bi01IP = vModel(solverSelected)
  set_silent(Bi01IP)
  @variable(Bi01IP, x[1:n], Bin)
  @addobjective(Bi01IP, Max, sum(C[1, j] * x[j] for j = 1:n))
  @addobjective(Bi01IP, Max, sum(C[2, j] * x[j] for j = 1:n))
  @constraint(Bi01IP, cte[i = 1:m], sum(A[i, j] * x[j] for j = 1:n) <= B[i])

  # ---- Invoking the solver (epsilon constraint method)
  #println("Solving...")
  vSolve(Bi01IP, method = :epsilon, step = 0.5, verbose = false)

  # ---- Querying the results
  #println("Querying...")
  Y_N = getY_N(Bi01IP)

  return Y_N, x
end

# ==============================================================================

# Example on how to use it :

# solverSelected = GLPK.Optimizer
# Y_N, X_E = setBi01IP(solverSelected, C, A, B)

# # ---- Displaying the results (X_E and Y_N)
# for n = 1:length(Y_N)
#    X = value.(X_E, n)
#    print(findall(elt -> elt ≈ 1, X))
#    println("| z = ",Y_N[n])
# end

# for n = 1:length(Y_N)
#    println(Y_N[n])
# end


using Revise
using NLS_Fit
using DelimitedFiles
using LinearAlgebra

rootDir = joinpath(dirname(pathof(NLS_Fit)), "..")
dataDir = joinpath(rootDir,"data")
dataFile = joinpath(dataDir,"recalibration.txt")

# Getting started example ****************
#

# Data ================
#
XY=readdlm(dataFile);
X=XY[:,1];
Y=XY[:,2];

# Model without recalibration ================
#
model = Gaussian_Peak() + Gaussian_Peak() + Gaussian_Peak()

θ1 = Float64[1.0,5,1]
θ2 = Float64[1.5,10,2]
θ3 = Float64[0.75,20,3]
θ_uncalibrated_model = vcat(θ1,θ2,θ3)

# Add an affine calibration ================
#
recalibration_map = Map_Affine(X[1]=>X[1],X[end]=>X[end])
recalibration_model = Model2Fit_Recalibration(model,recalibration_map)

θ_map = Float64[1,1]
θ_init_recalibration_model = vcat(θ_uncalibrated_model, θ_map)

# Solve a bound constrained nonlinear least squares ================
#

# Objective function ----------------
#
nls = NLS_ForwardDiff_From_Model2Fit(recalibration_model,X,Y);

# Bound constraints ----------------
#
ε = eps(Float64)
#                       h1 μ1   σ1,  h2  μ2   σ2   h3   μ3   σ3  θA   θB
lower_bound = Float64[   0, 5,   ε,   0, 10,   ε,   0,  20,   ε, 0.5, 0.5]
upper_bound = Float64[ Inf, 5, Inf, Inf, 10, Inf, Inf, 20, Inf, 1.5, 1.5]

bc = NLS_Solver.BoundConstraints(lower_bound,upper_bound)

# Solver ----------------
#
conf = NLS_Solver.LevenbergMarquardt_Conf()

# Solve the problem ----------------
#
result = NLS_Solver.solve(nls,θ_init_recalibration_model,conf)

# Fit result ================
#
@assert NLS_Solver.converged(result)

θ_fit_recalibration_model = NLS_Solver.solution(result)
Y_fit_recalibration_model = eval_y(recalibration_model,X,θ_fit_recalibration_model)

# Retrieve calibrated X ----------------
#
X_recalibrated = eval_calibrated_x(recalibration_model,X,θ_fit_recalibration_model);
X_true = (X .- 0.2)/1.1;

norm(X_recalibrated-X,Inf)
norm(X_recalibrated-X_true,Inf)

get_calibrated_model(recalibration_model)
get_calibrated_model_θ(recalibration_model,θ_fit_recalibration_model)


using Base.Test

include("../roc.jl")
@test Roc.tpNumNaive([1,11]      , [10,20,30,40], 3) == 1
@test Roc.tpNumNaive([1,11,13]   , [10,20,30,40], 3) == 2
@test Roc.tpNumNaive([1,11,14]   , [10,20,30,40], 3) == 1
@test Roc.tpNumNaive([1,11,14,30], [10,20,30,40], 3) == 2

@test Roc.tpRate([1,31   ], [10,20,30,40], 3) == 1/4
@test Roc.tpRate([1,31,44], [10,20,30,40], 3) == 1/4
@test Roc.tpRate([1,31,43], [10,20,30,40], 3) == 2/4

@test Roc.faRate([1,31   ], [10,20,30,40], 3, 100) == 1/(1+(100-4*3))
@test Roc.tpNum([1,11]      , [10,20,30,40], 3) == 1

#           T             T    T    T    T   F    F     T   F     T
detections=[0,           388, 395, 407, 418, 436, 448, 473, 487, 502]
changes   =[0, 100, 199, 300, 401, 501]

max_delay  = 30
println("... test_roc.jl > TP num. ", Roc.tpNum(detections, changes, max_delay))
println("... test_roc.jl > TP rate ", Roc.tpRate(detections, changes, max_delay))


# Legendre functions

# 4π normalized
# Orthonormalized
# Schmidt semi-normalized
# Unnormalized
# Utilities

"""
    PlmBar!(p::AbstractVector{Cdouble},
            lmax::Integer,
            z::Union{AbstractFloat,Integer};
            csphase::Integer=1,
            cnorm::Integer=0,
            exitstatus::Union{Nothing,Ref{<:Integer}}=nothing)

Compute all the 4π-normalized associated Legendre functions.

See also: [`PlmBar`](@ref), [`PlmBar_d1!`](@ref), [`PlBar!`](@ref),
[`PlmON!`](@ref), [`PlmSchmidt!`](@ref), [`PLegendreA!`](@ref),
[`PlmIndex`](@ref)
"""
function PlmBar! end

"""
    p = PlmBar(lmax::Integer,
               z::Union{AbstractFloat,Integer};
               csphase::Integer=1,
               cnorm::Integer=0,
               exitstatus::Union{Nothing,Ref{<:Integer}}=nothing)
    p::Vector{Cdouble}

Compute all the 4π-normalized associated Legendre functions.

See also: [`PlmBar!`](@ref), [`PlmBar_d1`](@ref), [`PlBar`](@ref),
[`PlmON`](@ref), [`PlmSchmidt`](@ref), [`PLegendreA`](@ref),
[`PlmIndex`](@ref)
"""
function PlmBar end

"""
    PlmBar_d1!(p::AbstractVector{Cdouble},
               dp::AbstractVector{Cdouble},
               lmax::Integer,
               z::Union{AbstractFloat,Integer};
               csphase::Integer=1,
               cnorm::Integer=0,
               exitstatus::Union{Nothing,Ref{<:Integer}}=nothing)

Compute all the 4π-normalized associated Legendre functions and first
derivatives.

See also: [`PlmBar_d1`](@ref), [`PlmBar_d1`](@ref),
[`PlBar_d1!`](@ref), [`PlmON_d1!`](@ref), [`PlmSchmidt_d1!`](@ref),
[`PLegendreA_d1!`](@ref), [`PlmIndex`](@ref)
"""
function PlmBar_d1! end

"""
    p, dp = PlmBar_d1(lmax::Integer,
                      z::Union{AbstractFloat,Integer};
                      csphase::Integer=1,
                      cnorm::Integer=0,
                      exitstatus::Union{Nothing,Ref{<:Integer}}=nothing)
    p::Vector{Cdouble}
    dp::Vector{Cdouble}

Compute all the 4π-normalized associated Legendre functions and first
derivatives.

See also: [`PlmBar`](@ref), [`PlmBar_d1!`](@ref), [`PlBar_d1`](@ref),
[`PlmON_d1`](@ref), [`PlmSchmidt_d1`](@ref),
[`PLegendreA_d1`](@ref), [`PlmIndex`](@ref)
"""
function PlmBar_d1 end

"""
    PlmON!(p::AbstractVector{Cdouble},
           lmax::Integer,
           z::Union{AbstractFloat,Integer};
           csphase::Integer=1,
           cnorm::Integer=0,
           exitstatus::Union{Nothing,Ref{<:Integer}}=nothing)

Compute all the orthonormalized associated Legendre functions.

See also: [`PlmON`](@ref), [`PlmON_d1!`](@ref), [`PlON!`](@ref),
[`PlmBar!`](@ref), [`PlmSchmidt!`](@ref), [`PLegendreA!`](@ref),
[`PlmIndex`](@ref)
"""
function PlmON! end

"""
    p = PlmON(lmax::Integer,
              z::Union{AbstractFloat,Integer};
              csphase::Integer=1,
              cnorm::Integer=0,
              exitstatus::Union{Nothing,Ref{<:Integer}}=nothing)
    p::Vector{Cdouble}

Compute all the orthonormalized associated Legendre functions.

See also: [`PlmON!`](@ref), [`PlmON_d1`](@ref), [`PlON`](@ref),
[`PlmBar`](@ref), [`PlmSchmidt`](@ref), [`PLegendreA`](@ref),
[`PlmIndex`](@ref)
"""
function PlmON end

"""
    PlmON_d1!(p::AbstractVector{Cdouble},
              dp::AbstractVector{Cdouble},
              lmax::Integer,
              z::Union{AbstractFloat,Integer};
              csphase::Integer=1,
              cnorm::Integer=0,
              exitstatus::Union{Nothing,Ref{<:Integer}}=nothing)

Compute all the orthonormalized associated Legendre functions and
first derivatives.

See also: [`PlmON_d1`](@ref), [`PlmON_d1`](@ref), [`PlON_d1!`](@ref),
[`PlmBar_d1!`](@ref), [`PlmSchmidt_d1!`](@ref),
[`PLegendreA_d1!`](@ref), [`PlmIndex`](@ref)
"""
function PlmON_d1! end

"""
    p, dp = PlmON_d1(lmax::Integer,
                     z::Union{AbstractFloat,Integer};
                     csphase::Integer=1,
                     cnorm::Integer=0,
                     exitstatus::Union{Nothing,Ref{<:Integer}}=nothing)
    p::Vector{Cdouble}
    dp::Vector{Cdouble}

Compute all the orthonormalized associated Legendre functions and first
derivatives.

See also: [`PlmON`](@ref), [`PlmON_d1!`](@ref), [`PlON_d1`](@ref),
[`PlmBar_d1`](@ref), [`PlmSchmidt_d1`](@ref), [`PLegendreA_d1`](@ref),
[`PlmIndex`](@ref)
"""
function PlmON_d1 end

"""
    PlmSchmidt!(p::AbstractVector{Cdouble},
                lmax::Integer,
                z::Union{AbstractFloat,Integer};
                csphase::Integer=1,
                cnorm::Integer=0,
                exitstatus::Union{Nothing,Ref{<:Integer}}=nothing)

Compute all the Schmidt-normalized associated Legendre functions.

See also: [`PlmSchmidt`](@ref), [`PlmSchmidt_d1!`](@ref),
[`PlSchmidt!`](@ref), [`PlmBar!`](@ref), [`PlmON!`](@ref),
[`PLegendreA!`](@ref), [`PlmIndex`](@ref)
"""
function PlmSchmidt! end

"""
    p = PlmSchmidt(lmax::Integer,
                   z::Union{AbstractFloat,Integer};
                   csphase::Integer=1,
                   cnorm::Integer=0,
                   exitstatus::Union{Nothing,Ref{<:Integer}}=nothing)
    p::Vector{Cdouble}

Compute all the Schmidt-normalized associated Legendre functions.

See also: [`PlmSchmidt!`](@ref), [`PlmSchmidt_d1`](@ref),
[`PlSchmidt`](@ref), [`PlmBar`](@ref), [`PlmON`](@ref),
[`PLegendreA`](@ref), [`PlmIndex`](@ref)
"""
function PlmSchmidt end

"""
    PlmSchmidt_d1!(p::AbstractVector{Cdouble},
                   dp::AbstractVector{Cdouble},
                   lmax::Integer,
                   z::Union{AbstractFloat,Integer};
                   csphase::Integer=1,
                   cnorm::Integer=0,
                   exitstatus::Union{Nothing,Ref{<:Integer}}=nothing)

Compute all the Schmidt-normalized associated Legendre functions and
first derivatives.

See also: [`PlmSchmidt_d1`](@ref), [`PlmSchmidt_d1`](@ref),
[`PlSchmidt_d1!`](@ref), [`PlmBar_d1!`](@ref), [`PlmON_d1!`](@ref),
[`PLegendreA_d1!`](@ref), [`PlmIndex`](@ref)
"""
function PlmSchmidt_d1! end

"""
    p, dp = PlmSchmidt_d1(lmax::Integer,
                          z::Union{AbstractFloat,Integer};
                          csphase::Integer=1,
                          cnorm::Integer=0,
                          exitstatus::Union{Nothing,Ref{<:Integer}}=nothing)
    p::Vector{Cdouble}
    dp::Vector{Cdouble}

Compute all the Schmidt-normalized associated Legendre functions and
first derivatives.

See also: [`PlmSchmidt`](@ref), [`PlmSchmidt_d1!`](@ref),
[`PlSchmidt_d1`](@ref), [`PlmBar_d1`](@ref), [`PlmON_d1`](@ref),
[`PLegendreA_d1`](@ref), [`PlmIndex`](@ref)
"""
function PlmSchmidt_d1 end

for op in [:PlmBar, :PlmON, :PlmSchmidt]
    op! = Symbol(op, "!")
    op_d1 = Symbol(op, "_d1")
    op_d1! = Symbol(op, "_d1!")
    @eval begin
        #
        export $op!
        function $op!(p::AbstractVector{Cdouble}, lmax::Integer,
                      z::Union{AbstractFloat,Integer}; csphase::Integer=1,
                      cnorm::Integer=0,
                      exitstatus::Optional{Ref{<:Integer}}=nothing)
            @assert lmax ≥ 0
            @assert csphase ∈ (1, -1)
            @assert cnorm ∈ (0, 1)
            @assert length(p) ≥ (lmax + 1) * (lmax + 2) ÷ 2
            exitstatus′ = Ref{Cint}()
            ccall(($(QuoteNode(op)), libSHTOOLS), Cvoid,
                  (Ptr{Cdouble}, Cint, Cdouble, Ref{Cint}, Ref{Cint},
                   Ref{Cint}), p, lmax, z, csphase, cnorm, exitstatus′)
            if exitstatus === nothing
                exitstatus′[] ≠ 0 &&
                    error("$($op!): Error code $(exitstatus′[])")
            else
                exitstatus[] = exitstatus′[]
            end
            return p
        end
        #
        export $op
        function $op(lmax::Integer, z::Union{AbstractFloat,Integer};
                     csphase::Integer=1, cnorm::Integer=0,
                     exitstatus::Optional{Ref{<:Integer}}=nothing)
            @assert lmax ≥ 0
            p = Array{Cdouble}(undef, (lmax + 1) * (lmax + 2) ÷ 2)
            $op!(p, lmax, z; csphase=csphase, cnorm=cnorm,
                 exitstatus=exitstatus)
            return p
        end
        #
        export $op_d1!
        function $op_d1!(p::AbstractVector{Cdouble},
                         dp::AbstractVector{Cdouble}, lmax::Integer,
                         z::Union{AbstractFloat,Integer}; csphase::Integer=1,
                         cnorm::Integer=0,
                         exitstatus::Optional{Ref{<:Integer}}=nothing)
            @assert lmax ≥ 0
            @assert csphase ∈ (1, -1)
            @assert cnorm ∈ (0, 1)
            @assert length(p) ≥ lmax + 1
            @assert length(dp) ≥ lmax + 1
            exitstatus′ = Ref{Cint}()
            ccall(($(QuoteNode(op_d1)), libSHTOOLS), Cvoid,
                  (Ptr{Cdouble}, Ptr{Cdouble}, Cint, Cdouble, Ref{Cint},
                   Ref{Cint}, Ref{Cint}), p, dp, lmax, z, csphase, cnorm,
                  exitstatus′)
            if exitstatus === nothing
                exitstatus′[] ≠ 0 &&
                    error("$($op_d1!): Error code $(exitstatus′[])")
            else
                exitstatus[] = exitstatus′[]
            end
            return p, dp
        end
        #
        export $op_d1
        function $op_d1(lmax::Integer, z::Union{AbstractFloat,Integer};
                        csphase::Integer=1, cnorm::Integer=0,
                        exitstatus::Optional{Ref{<:Integer}}=nothing)
            @assert lmax ≥ 0
            p = Array{Cdouble}(undef, (lmax + 1) * (lmax + 2) ÷ 2)
            dp = Array{Cdouble}(undef, (lmax + 1) * (lmax + 2) ÷ 2)
            $op_d1!(p, dp, lmax, z; csphase=csphase, cnorm=cnorm,
                    exitstatus=exitstatus)
            return p, dp
        end
        #
    end
end

"""
    PLegendreA!(p::AbstractVector{Cdouble},
                lmax::Integer,
                z::Union{AbstractFloat,Integer};
                csphase::Integer=1,
                exitstatus::Union{Nothing,Ref{<:Integer}}=nothing)

Compute all the unnormalized associated Legendre functions.

See also: [`PLegendreA`](@ref), [`PLegendreA_d1!`](@ref),
[`PLegendre!`](@ref), [`PlmBar!`](@ref), [`PlmON!`](@ref),
[`PlmSchmidt!`](@ref), [`PlmIndex`](@ref)
"""
function PLegendreA! end

"""
    p = PLegendreA(lmax::Integer,
                   z::Union{AbstractFloat,Integer};
                   csphase::Integer=1,
                   exitstatus::Union{Nothing,Ref{<:Integer}}=nothing)
    p::Vector{Cdouble}

Compute all the unnormalized associated Legendre functions.

See also: [`PLegendreA!`](@ref), [`PLegendreA_d1`](@ref),
[`PLegendre`](@ref), [`PlmBar`](@ref), [`PlmON`](@ref),
[`PlmSchmidt`](@ref), [`PlmIndex`](@ref)
"""
function PLegendreA end

"""
    PLegendreA_d1!(p::AbstractVector{Cdouble},
                   dp::AbstractVector{Cdouble},
                   lmax::Integer,
                   z::Union{AbstractFloat,Integer};
                   csphase::Integer=1,
                   exitstatus::Union{Nothing,Ref{<:Integer}}=nothing)

Compute all the unnormalized associated Legendre functions and first
derivatives.

See also: [`PLegendreA_d1`](@ref), [`PLegendreA_d1`](@ref),
[`PLegendre_d1!`](@ref), [`PlmBar_d1!`](@ref), [`PlmON_d1!`](@ref),
[`PlmSchmidt_d1!`](@ref), [`PlmIndex`](@ref)
"""
function PLegendreA_d1! end

"""
    p, dp = PLegendreA_d1(lmax::Integer,
                            z::Union{AbstractFloat,Integer};
                            csphase::Integer=1,
                            exitstatus::Union{Nothing,Ref{<:Integer}}=nothing)
    p::Vector{Cdouble}
    dp::Vector{Cdouble}

Compute all the unnormalized associated Legendre functions and first
derivatives.

See also: [`PLegendreA`](@ref), [`PLegendreA_d1!`](@ref),
[`PLegendre_d1`](@ref), [`PlmBar_d1`](@ref), [`PlmON_d1`](@ref),
[`PlmSchmidt_d1`](@ref), [`PlmIndex`](@ref)
"""
function PLegendreA_d1 end

for op in [:PLegendreA]
    op! = Symbol(op, "!")
    op_d1 = Symbol(op, "_d1")
    op_d1! = Symbol(op, "_d1!")
    @eval begin
        #
        export $op!
        function $op!(p::AbstractVector{Cdouble}, lmax::Integer,
                      z::Union{AbstractFloat,Integer}; csphase::Integer=1,
                      exitstatus::Optional{Ref{<:Integer}}=nothing)
            @assert lmax ≥ 0
            @assert csphase ∈ (1, -1)
            @assert length(p) ≥ (lmax + 1) * (lmax + 2) ÷ 2
            exitstatus′ = Ref{Cint}()
            ccall(($(QuoteNode(op)), libSHTOOLS), Cvoid,
                  (Ptr{Cdouble}, Cint, Cdouble, Ref{Cint}, Ref{Cint}), p, lmax,
                  z, csphase, exitstatus′)
            if exitstatus === nothing
                exitstatus′[] ≠ 0 &&
                    error("$($op!): Error code $(exitstatus′[])")
            else
                exitstatus[] = exitstatus′[]
            end
            return p
        end
        #
        export $op
        function $op(lmax::Integer, z::Union{AbstractFloat,Integer};
                     csphase::Integer=1,
                     exitstatus::Optional{Ref{<:Integer}}=nothing)
            @assert lmax ≥ 0
            p = Array{Cdouble}(undef, (lmax + 1) * (lmax + 2) ÷ 2)
            $op!(p, lmax, z; csphase=csphase, exitstatus=exitstatus)
            return p
        end
        #
        export $op_d1!
        function $op_d1!(p::AbstractVector{Cdouble},
                         dp::AbstractVector{Cdouble}, lmax::Integer,
                         z::Union{AbstractFloat,Integer}; csphase::Integer=1,
                         exitstatus::Optional{Ref{<:Integer}}=nothing)
            @assert lmax ≥ 0
            @assert csphase ∈ (1, -1)
            @assert length(p) ≥ lmax + 1
            @assert length(dp) ≥ lmax + 1
            exitstatus′ = Ref{Cint}()
            ccall(($(QuoteNode(op_d1)), libSHTOOLS), Cvoid,
                  (Ptr{Cdouble}, Ptr{Cdouble}, Cint, Cdouble, Ref{Cint},
                   Ref{Cint}), p, dp, lmax, z, csphase, exitstatus′)
            if exitstatus === nothing
                exitstatus′[] ≠ 0 &&
                    error("$($op_d1!): Error code $(exitstatus′[])")
            else
                exitstatus[] = exitstatus′[]
            end
            return p, dp
        end
        #
        export $op_d1
        function $op_d1(lmax::Integer, z::Union{AbstractFloat,Integer};
                        csphase::Integer=1,
                        exitstatus::Optional{Ref{<:Integer}}=nothing)
            @assert lmax ≥ 0
            p = Array{Cdouble}(undef, (lmax + 1) * (lmax + 2) ÷ 2)
            dp = Array{Cdouble}(undef, (lmax + 1) * (lmax + 2) ÷ 2)
            $op_d1!(p, dp, lmax, z; csphase=csphase, exitstatus=exitstatus)
            return p, dp
        end
        #
    end
end

"""
    PlBar!(p::AbstractVector{Cdouble},
           lmax::Integer,
           z::Union{AbstractFloat,Integer};
           exitstatus::Union{Nothing,Ref{<:Integer}}=nothing)

Compute all the 4π-normalized Legendre polynomials.

See also: [`PlBar`](@ref), [`PlBar_d1!`](@ref), [`PlmBar!`](@ref),
[`PlON!`](@ref), [`PlSchmidt!`](@ref), [`PLegendre!`](@ref)
"""
function PlBar! end

"""
    p = PlBar(lmax::Integer,
               z::Union{AbstractFloat,Integer};
               exitstatus::Union{Nothing,Ref{<:Integer}}=nothing)
    p::Vector{Cdouble}

Compute all the 4π-normalized associated Legendre functions.

See also: [`PlBar!`](@ref), [`PlBar_d1`](@ref), [`PlmBar`](@ref),
[`PlON`](@ref), [`PlSchmidt`](@ref), [`PLegendre`](@ref)
"""
function PlBar end

"""
    PlBar_d1!(p::AbstractVector{Cdouble},
               dp::AbstractVector{Cdouble},
               lmax::Integer,
               z::Union{AbstractFloat,Integer};
               exitstatus::Union{Nothing,Ref{<:Integer}}=nothing)

Compute all the 4π-normalized associated Legendre functions and first
derivatives.

See also: [`PlBar_d1`](@ref), [`PlBar_d1`](@ref),
[`PlmBar_d1!`](@ref), [`PlON_d1!`](@ref), [`PlSchmidt_d1!`](@ref),
[`PLegendre_d1!`](@ref)
"""
function PlBar_d1! end

"""
    p, dp = PlBar_d1(lmax::Integer,
                      z::Union{AbstractFloat,Integer};
                      exitstatus::Union{Nothing,Ref{<:Integer}}=nothing)
    p::Vector{Cdouble}
    dp::Vector{Cdouble}

Compute all the 4π-normalized associated Legendre functions and first
derivatives.

See also: [`PlBar`](@ref), [`PlBar_d1!`](@ref), [`PlmBar_d1`](@ref),
[`PlON_d1`](@ref), [`PlSchmidt_d1`](@ref), [`PLegendre_d1`](@ref)
"""
function PlBar_d1 end

"""
    PlON!(p::AbstractVector{Cdouble},
           lmax::Integer,
           z::Union{AbstractFloat,Integer};
           exitstatus::Union{Nothing,Ref{<:Integer}}=nothing)

Compute all the orthonormalized associated Legendre functions.

See also: [`PlON`](@ref), [`PlON_d1!`](@ref), [`PlmON!`](@ref),
[`PlBar!`](@ref), [`PlSchmidt!`](@ref), [`PLegendre!`](@ref)
"""
function PlON! end

"""
    p = PlON(lmax::Integer,
              z::Union{AbstractFloat,Integer};
              exitstatus::Union{Nothing,Ref{<:Integer}}=nothing)
    p::Vector{Cdouble}

Compute all the orthonormalized associated Legendre functions.

See also: [`PlON!`](@ref), [`PlON_d1`](@ref), [`PlmON`](@ref),
[`PlBar`](@ref), [`PlSchmidt`](@ref), [`PLegendre`](@ref)
"""
function PlON end

"""
    PlON_d1!(p::AbstractVector{Cdouble},
              dp::AbstractVector{Cdouble},
              lmax::Integer,
              z::Union{AbstractFloat,Integer};
              exitstatus::Union{Nothing,Ref{<:Integer}}=nothing)

Compute all the orthonormalized associated Legendre functions and
first derivatives.

See also: [`PlON_d1`](@ref), [`PlON_d1`](@ref), [`PlmON_d1!`](@ref),
[`PlBar_d1!`](@ref), [`PlSchmidt_d1!`](@ref), [`PLegendre_d1!`](@ref)
"""
function PlON_d1! end

"""
    p, dp = PlON_d1(lmax::Integer,
                     z::Union{AbstractFloat,Integer};
                     exitstatus::Union{Nothing,Ref{<:Integer}}=nothing)
    p::Vector{Cdouble}
    dp::Vector{Cdouble}

Compute all the orthonormalized associated Legendre functions and first
derivatives.

See also: [`PlON`](@ref), [`PlON_d1!`](@ref), [`PlmON_d1`](@ref),
[`PlBar_d1`](@ref), [`PlSchmidt_d1`](@ref), [`PLegendre_d1`](@ref)
"""
function PlON_d1 end

"""
    PlSchmidt!(p::AbstractVector{Cdouble},
                lmax::Integer,
                z::Union{AbstractFloat,Integer};
                exitstatus::Union{Nothing,Ref{<:Integer}}=nothing)

Compute all the Schmidt-normalized associated Legendre functions.

See also: [`PlSchmidt`](@ref), [`PlSchmidt_d1!`](@ref),
[`PlmSchmidt!`](@ref), [`PlBar!`](@ref), [`PlON!`](@ref),
[`PLegendre!`](@ref)
"""
function PlSchmidt! end

"""
    p = PlSchmidt(lmax::Integer,
                   z::Union{AbstractFloat,Integer};
                   exitstatus::Union{Nothing,Ref{<:Integer}}=nothing)
    p::Vector{Cdouble}

Compute all the Schmidt-normalized associated Legendre functions.

See also: [`PlSchmidt!`](@ref), [`PlSchmidt_d1`](@ref),
[`PlmSchmidt`](@ref), [`PlBar`](@ref), [`PlON`](@ref),
[`PLegendre`](@ref)
"""
function PlSchmidt end

"""
    PlSchmidt_d1!(p::AbstractVector{Cdouble},
                   dp::AbstractVector{Cdouble},
                   lmax::Integer,
                   z::Union{AbstractFloat,Integer};
                   exitstatus::Union{Nothing,Ref{<:Integer}}=nothing)

Compute all the Schmidt-normalized associated Legendre functions and
first derivatives.

See also: [`PlSchmidt_d1`](@ref), [`PlSchmidt_d1`](@ref),
[`PlmSchmidt_d1!`](@ref), [`PlBar_d1!`](@ref), [`PlON_d1!`](@ref),
[`PLegendre_d1!`](@ref)
"""
function PlSchmidt_d1! end

"""
    p, dp = PlSchmidt_d1(lmax::Integer,
                          z::Union{AbstractFloat,Integer};
                          exitstatus::Union{Nothing,Ref{<:Integer}}=nothing)
    p::Vector{Cdouble}
    dp::Vector{Cdouble}

Compute all the Schmidt-normalized associated Legendre functions and
first derivatives.

See also: [`PlSchmidt`](@ref), [`PlSchmidt_d1!`](@ref),
[`PlmSchmidt_d1`](@ref), [`PlBar_d1`](@ref), [`PlON_d1`](@ref),
[`PLegendre_d1`](@ref)
"""
function PlSchmidt_d1 end

"""
    PLegendre!(p::AbstractVector{Cdouble},
                lmax::Integer,
                z::Union{AbstractFloat,Integer};
                exitstatus::Union{Nothing,Ref{<:Integer}}=nothing)

Compute all the unnormalized associated Legendre functions.

See also: [`PLegendre`](@ref), [`PLegendre_d1!`](@ref),
[`PLegendreA!`](@ref), [`PlBar!`](@ref), [`PlON!`](@ref),
[`PlSchmidt!`](@ref)
"""
function PLegendre! end

"""
    p = PLegendre(lmax::Integer,
                   z::Union{AbstractFloat,Integer};
                   exitstatus::Union{Nothing,Ref{<:Integer}}=nothing)
    p::Vector{Cdouble}

Compute all the unnormalized associated Legendre functions.

See also: [`PLegendre!`](@ref), [`PLegendre_d1`](@ref),
[`PLegendreA`](@ref), [`PlBar`](@ref), [`PlON`](@ref),
[`PlSchmidt`](@ref)
"""
function PLegendre end

"""
    PLegendre_d1!(p::AbstractVector{Cdouble},
                   dp::AbstractVector{Cdouble},
                   lmax::Integer,
                   z::Union{AbstractFloat,Integer};
                   exitstatus::Union{Nothing,Ref{<:Integer}}=nothing)

Compute all the unnormalized associated Legendre functions and first
derivatives.

See also: [`PLegendre_d1`](@ref), [`PLegendre_d1`](@ref),
[`PLegendreA_d1!`](@ref), [`PlBar_d1!`](@ref), [`PlON_d1!`](@ref),
[`PlSchmidt_d1!`](@ref)
"""
function PLegendre_d1! end

"""
    p, dp = PLegendre_d1(lmax::Integer,
                            z::Union{AbstractFloat,Integer};
                            exitstatus::Union{Nothing,Ref{<:Integer}}=nothing)
    p::Vector{Cdouble}
    dp::Vector{Cdouble}

Compute all the unnormalized associated Legendre functions and first
derivatives.

See also: [`PLegendre`](@ref), [`PLegendre_d1!`](@ref),
[`PLegendre_d1!`](@ref), [`PlBar_d1`](@ref), [`PlON_d1`](@ref),
[`PlSchmidt_d1`](@ref)
"""
function PLegendre_d1 end

for op in [:PlBar, :PlON, :PlSchmidt, :PLegendre]
    op! = Symbol(op, "!")
    op_d1 = Symbol(op, "_d1")
    op_d1! = Symbol(op, "_d1!")
    @eval begin
        #
        export $op!
        function $op!(p::AbstractVector{Cdouble}, lmax::Integer,
                      z::Union{AbstractFloat,Integer};
                      exitstatus::Optional{Ref{<:Integer}}=nothing)
            @assert lmax ≥ 0
            @assert length(p) ≥ lmax + 1
            exitstatus′ = Ref{Cint}()
            ccall(($(QuoteNode(op)), libSHTOOLS), Cvoid,
                  (Ptr{Cdouble}, Cint, Cdouble, Ref{Cint}), p, lmax, z,
                  exitstatus′)
            if exitstatus === nothing
                exitstatus′[] ≠ 0 &&
                    error("$($op!): Error code $(exitstatus′[])")
            else
                exitstatus[] = exitstatus′[]
            end
            return p
        end
        #
        export $op
        function $op(lmax::Integer, z::Union{AbstractFloat,Integer};
                     exitstatus::Optional{Ref{<:Integer}}=nothing)
            @assert lmax ≥ 0
            p = Array{Cdouble}(undef, lmax + 1)
            $op!(p, lmax, z; exitstatus=exitstatus)
            return p
        end
        #
        export $op_d1!
        function $op_d1!(p::AbstractVector{Cdouble},
                         dp::AbstractVector{Cdouble}, lmax::Integer,
                         z::Union{AbstractFloat,Integer};
                         exitstatus::Optional{Ref{<:Integer}}=nothing)
            @assert lmax ≥ 0
            @assert length(p) ≥ lmax + 1
            @assert length(dp) ≥ lmax + 1
            exitstatus′ = Ref{Cint}()
            ccall(($(QuoteNode(op_d1)), libSHTOOLS), Cvoid,
                  (Ptr{Cdouble}, Ptr{Cdouble}, Cint, Cdouble, Ref{Cint}), p, dp,
                  lmax, z, exitstatus′)
            if exitstatus === nothing
                exitstatus′[] ≠ 0 &&
                    error("$($op_d1!): Error code $(exitstatus′[])")
            else
                exitstatus[] = exitstatus′[]
            end
            return p, dp
        end
        #
        export $op_d1
        function $op_d1(lmax::Integer, z::Union{AbstractFloat,Integer};
                        exitstatus::Optional{Ref{<:Integer}}=nothing)
            @assert lmax ≥ 0
            p = Array{Cdouble}(undef, lmax + 1)
            dp = Array{Cdouble}(undef, lmax + 1)
            $op_d1!(p, dp, lmax, z; exitstatus=exitstatus)
            return p, dp
        end
        #
    end
end

export PlmIndex
"""
    index = PlmIndex(l::Integer,
                     m::Integer)
    index::Int

Compute the index of an array of Legendre functions corresponding to
degree `l` and angular order `m`.
"""
function PlmIndex(l::Integer, m::Integer)
    0 ≤ m ≤ l ||
        error("m must be greater than or equal to zero and less than or equal to l: m=$m, l=$l")
    index = ccall((:PlmIndex, libSHTOOLS), Cint, (Cint, Cint), l, m)
    return Int(index)
end


# Tests for Cost Additive DEA Models
@testset "CostAdditiveDEAModel" begin

    # Test using Book data
    X = [2 2; 1 4; 4 1; 4 3; 5 5; 6 1; 2 5; 1.6 8];
    Y = [1; 1; 1; 1; 1; 1; 1; 1];
    W = [1 1; 1 1; 1 1; 1 1; 1 1; 1 1; 1 1; 1 1];

    # With :Ones
    deacostaddones = deacostadd(X, Y, W, :Ones)

    @test typeof(deacostaddones) == CostAdditiveDEAModel

    @test nobs(deacostaddones) == 8
    @test ninputs(deacostaddones) == 2
    @test noutputs(deacostaddones) == 1
    @test ismonetary(deacostaddones) == false

    @test efficiency(deacostaddones) == efficiency(deacostaddones, :Economic)
    @test efficiency(deacostaddones, :Economic)   ≈ [0.0; 1.0; 1.0; 3.0; 6.0; 3.0; 3.0; 5.6]
    @test efficiency(deacostaddones, :Technical)  ≈ [0.0; 0.0; 0.0; 3.0; 6.0; 2.0; 3.0; 5.2]
    @test efficiency(deacostaddones, :Allocative) ≈ [0.0; 1.0; 1.0; 0.0; 0.0; 1.0; 0.0; 0.4]
    @test normfactor(deacostaddones) ≈ [1.0; 1.0; 1.0; 1.0; 1.0; 1.0; 1.0; 1.0]

    # With :Ones and CRS (Same result in this example)
    deacostaddonescrs = deacostadd(X, Y, W, :Ones, rts = :CRS)

    @test efficiency(deacostaddonescrs, :Economic)   ≈ [0.0; 1.0; 1.0; 3.0; 6.0; 3.0; 3.0; 5.6]
    @test efficiency(deacostaddonescrs, :Technical)  ≈ [0.0; 0.0; 0.0; 3.0; 6.0; 2.0; 3.0; 5.2]
    @test efficiency(deacostaddonescrs, :Allocative) ≈ [0.0; 1.0; 1.0; 0.0; 0.0; 1.0; 0.0; 0.4]

    # Default is :Ones
    @test efficiency(deacostadd(X, Y, W)) == efficiency(deacostaddones)

    # With :MIP
    deacostaddmip = deacostadd(X, Y, W, :MIP)

    @test efficiency(deacostaddmip, :Economic)   ≈ [0.0; 1.0; 1.0; 1.0; 1.2; 3.0; 1.5; 3.5] atol = 1e-5
    @test efficiency(deacostaddmip, :Technical)  ≈ [0.0; 0.0; 0.0; 5/6; 1.2; 1/3; 7/10; 0.875] atol = 1e-5
    @test efficiency(deacostaddmip, :Allocative) ≈ [0.0; 1.0; 1.0; 1/6; 0.0; 8/3; 4/5; 21/8] atol = 1e-5
    @test normfactor(deacostaddmip) ≈ [2.0; 1.0; 1.0; 3.0; 5.0; 1.0; 2.0; 1.6]

    # With :MIP and monetary = true
    deacostaddmipmon = deacostadd(X, Y, W, :MIP, monetary = true)

    @test ismonetary(deacostaddmipmon) == true

    @test efficiency(deacostaddmipmon) ≈ efficiency(deacostaddmip) .* normfactor(deacostaddmip)
    @test efficiency(deacostaddmipmon, :Technical) ≈ efficiency(deacostaddmip, :Technical) .* normfactor(deacostaddmip)
    @test efficiency(deacostaddmipmon, :Allocative) ≈ efficiency(deacostaddmip, :Allocative) .* normfactor(deacostaddmip)

    # With custom weights
    deacostaddcustom = deacostadd(X, Y, W, rhoX = 1 ./ X)

    @test efficiency(deacostaddcustom) ≈ efficiency(deacostaddmip)

    # Print
    show(IOBuffer(), deacostaddones)
    show(IOBuffer(), deacostadd(X, Y, W, :Ones, dispos = :Weak))

    # Test errors
    @test_throws DimensionMismatch deacostadd([1; 2 ; 3], [4 ; 5], [1; 1; 1] , :Ones) #  Different number of observations
    @test_throws DimensionMismatch deacostadd([1; 2; 3], [4; 5; 6], [1; 2; 3; 4], :Ones) # Different number of observation in prices
    @test_throws DimensionMismatch deacostadd([1 1; 2 2; 3 3 ], [4; 5; 6], [1 1 1; 2 2 2; 3 3 3], :Ones) # Different number of input prices and inputs
    @test_throws DimensionMismatch deacostadd([1; 2; 3] , [4; 5; 6], [1; 1; 1], rhoX = [1 1; 2 2; 3 3]) # Different size of inputs weights
    @test_throws ArgumentError deacostadd([1; 2; 3], [4; 5; 6], [1; 2; 3],:Ones, rts = :Error) # Invalid returns to scale
    @test_throws ArgumentError deacostadd([1; 2; 3], [1; 2; 3], [1; 1; 1], :Error) # Invalid inuts weights
    @test_throws ArgumentError deacostadd([1; 2; 3], [1; 2; 3], [1; 1; 1], :Ones, dispos = :Error) # Invalid inputs disposal
    @test_throws ArgumentError deacostadd([1; 2; 3], [1; 2; 3], [1; 1; 1], :Ones, rhoX= [1; 1; 1]) # Weights not allowed if model != :Custom

end


using ExampleRajith
using Test

@testset "my_f" begin
    # 2x + 3y
    @test my_f(2, 1) == 7
    @test my_f(2, 3) == 13
end

@testset "derivative_of_my_f" begin
    @test derivative_of_my_f(2, 1) == 2
end


using LinearAlgebra

function nearest_neighbors(X, p, r)
    neighbors = Int[]
    for i in 1:size(X, 2)
        if norm(X[:,i] - p) < r
            push!(neighbors, i)
        end
    end
    return neighbors
end


function part1(input)
    instructions = parse_input(input)
    position = navigate1(instructions)
    return prod(position)
end

function part2(input)
    instructions = parse_input(input)
    position = navigate2(instructions)
    return prod(position)
end

parse_input(input) = parse_line.(readlines(input))

function parse_line(line)
    direction, distance = split(line, " ")
    return direction, parse(Int, distance)
end

function navigate1(instructions)
    position = (0, 0)
    for (direction, distance) in instructions
        if direction == "forward"
            position = position .+ (distance, 0)
        elseif direction == "down"
            position = position .+ (0, distance)
        elseif direction == "up"
            position = position .- (0, distance)
        end
    end
    return position
end

function navigate2(instructions)
    position = (0, 0)
    aim = 0
    for (direction, distance) in instructions
        if direction == "forward"
            position = position .+ (distance, aim * distance)
        elseif direction == "down"
            aim += distance
        elseif direction == "up"
            aim -= distance
        end
    end
    return position
end


# ---
# title: 891. Sum of Subsequence Widths
# id: problem891
# author: Tian Jun
# date: 2020-10-31
# difficulty: Hard
# categories: Array, Math
# link: <https://leetcode.com/problems/sum-of-subsequence-widths/description/>
# hidden: true
# ---
#
# Given an array of integers `A`, consider all non-empty subsequences of `A`.
#
# For any sequence S, let the  _width_  of S be the difference between the
# maximum and minimum element of S.
#
# Return the sum of the widths of all subsequences of A.
#
# As the answer may be very large, **return the answer modulo 10^9 + 7**.
#
#
#
# **Example 1:**
#
#
#
#     Input: [2,1,3]
#     Output: 6
#     Explanation: Subsequences are [1], [2], [3], [2,1], [2,3], [1,3], [2,1,3].
#     The corresponding widths are 0, 0, 0, 1, 1, 2, 2.
#     The sum of these widths is 6.
#
#
#
#
# **Note:**
#
#   * `1 <= A.length <= 20000`
#   * `1 <= A[i] <= 20000`
#
#
## @lc code=start
using LeetCode

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


using PyPlot

ind(v::AbstractVector{T}, a::U) where {T<:Number,U<:Number} = argmin(abs.(v .- a))

function upsample(v::AbstractVector, fac::Int,
                  do_plot::Bool = false)
    dv = v[2]-v[1]
    all(abs.(diff(v) - dv)/abs(dv) .<= 1e-6) || error("Not equal spacing between all elements")
    ndv = dv/fac
    nv = v[1]:ndv:v[end]+0.9ndv

    if do_plot
        figure("upsample")
        clf()
        subplot(311)
        plot(v, v, "s-")
        plot(nv, nv, ".")
        axis([0.9v[1], 1.1v[10], 0.9v[1], 1.1v[10]])
        subplot(312)
        plot(v, v, "s-")
        plot(nv, nv, ".")
        axis([0.9v[end-10], 1.1v[end], 0.9v[end-10], 1.1v[end]])
        subplot(313)
        semilogy(abs(v-nv[1:fac:length(nv)]))
    end

    nsel = filter(eachindex(nv)) do i
        !(i in 1:fac:length(nv))
    end
    nv, nv[nsel]
end

function log_upsample(v::AbstractVector, fac::Int)
    nv,nvv = upsample(log.(v), fac)
    exp.(nv),exp.(nvv)
end

meshgrid(x::AbstractVector,y::AbstractVector) =
    repeat(x', outer=(length(y),1)),repeat(y, outer=(1,length(x)))

export ind, upsample, log_upsample, meshgrid


using Test
using JuMP
import DiffOpt
import MathOptInterface
const MOI = MathOptInterface
const MOIU = MOI.Utilities
import LinearAlgebra: dot, ⋅, I
import Ipopt
import OSQP
import GLPK
import SCS
import DelimitedFiles

@testset "Testing forward on trivial QP" begin
    # using example on https://osqp.org/docs/examples/setup-and-solve.html
    Q = [
        4.0 1.0
        1.0 2.0
    ]
    q = [1.0, 1.0]
    G = [
         1.0 1.0
         1.0 0.0
         0.0 1.0
        -1.0 -1.0
        -1.0 0.0
         0.0 -1.0
    ]
    h = [1, 0.7, 0.7, -1, 0, 0]

    model = JuMP.Model(() -> DiffOpt.diff_optimizer(Ipopt.Optimizer))
    MOI.set(model, MOI.Silent(), true)
    @variable(model, x[1:2])
    @objective(model, Min, dot(Q * x, x) + dot(q, x))
    @constraint(model,
        G * x .<= h,
    )
    optimize!(model)

    @test JuMP.value.(x) ≈ [0.3, 0.7] atol=ATOL rtol=RTOL
end

@testset "Differentiating trivial QP 1" begin
    Q = [
        4.0 1.0
        1.0 2.0
    ]
    q = [1.0, 1.0]
    G = [1.0 1.0]
    h = [-1.0]

    model = JuMP.direct_model(DiffOpt.diff_optimizer(Ipopt.Optimizer))
    MOI.set(model, MOI.Silent(), true)
    x = @variable(model, [1:2])
    @objective(model, Min, dot(Q * x, x) + dot(q, x))
    @constraint(model, ctr_le,
        G * x .<= h,
    )
    optimize!(model)

    @test JuMP.value.(x) ≈ [-0.25, -0.75] atol=ATOL rtol=RTOL

    # grad_wrt_h = backward(JuMP.backend(model), ["h"], ones(2))[1]

    MOI.set.(model, DiffOpt.BackwardInVariablePrimal(), x, 1.0)

    DiffOpt.backward(model)

    grad = JuMP.constant(MOI.get(model, DiffOpt.BackwardOutConstraint(), ctr_le[]))
    @test grad ≈ -1.0  atol=ATOL rtol=RTOL

    # TODO: this simple show fails
    # @show ctr_le
end

@testset "Differentiating QP with inequality and equality constraints" begin
    # refered from: https://www.mathworks.com/help/optim/ug/quadprog.html#d120e113424
    # Find equivalent qpth program here - https://github.com/AKS1996/jump-gsoc-2020/blob/master/DiffOpt_tests_4_py.ipynb

    Q = [
         1.0 -1.0 1.0;
        -1.0  2.0 -2.0;
         1.0 -2.0 4.0
    ]
    q = [2.0, -3.0, 1.0]
    G = [0.0 0.0 1.0
         0.0 1.0 0.0
         1.0 0.0 0.0
         0.0 0.0 -1.0
         0.0 -1.0 0.0
         -1.0 0.0 0.0
    ]
    h = [1.0, 1.0, 1.0, 0.0, 0.0, 0.0,]
    A = [1.0 1.0 1.0;]
    b = [0.5]

    model = Model(() -> DiffOpt.diff_optimizer(Ipopt.Optimizer))
    MOI.set(model, MOI.Silent(), true)
    @variable(model, x[1:3])
    @objective(model, Min, dot(Q * x, x) + dot(q, x))
    @constraint(model, ctr_le,
        G * x .<= h,
    )
    @constraint(model, ctr_eq,
        A * x .== b,
    )
    optimize!(model)
    # doptimizer = JuMP.backend(model).optimizer.model

    # nv = MOI.get(doptimizer, MOI.NumberOfVariables())
    # v = MOI.VariableIndex.(collect(1:nv))
    # z = MOI.get.(doptimizer, MOI.VariablePrimal(), v)

    @test JuMP.value.(x) ≈ [0.0, 0.5, 0.0] atol=ATOL rtol=RTOL

    MOI.set.(model, DiffOpt.BackwardInVariablePrimal(), x, 1.0)

    DiffOpt.backward(model)

    @test MOIU.isapprox_zero(moi_function(MOI.get(model, DiffOpt.BackwardOutObjective())), ATOL)

    db = [1.0]
    dA = [0.0 -0.5 0.0]
    for (j,jc) in enumerate(ctr_eq)
        grad = MOI.get(model, DiffOpt.BackwardOutConstraint(), jc)
        @test JuMP.constant(grad) ≈ -db[j] atol=ATOL rtol=RTOL
        for (i,iv) in enumerate(x)
            @test JuMP.coefficient(grad, iv) ≈ dA[j,i] atol=ATOL rtol=RTOL
        end
    end

    dh = zeros(6,1)
    dG = zeros(6,3)
    for (j,jc) in enumerate(ctr_le)
        grad = MOI.get(model, DiffOpt.BackwardOutConstraint(), jc)
        @test JuMP.constant(grad) ≈ -dh[j] atol=ATOL rtol=RTOL
        for (i,iv) in enumerate(x)
            @test JuMP.coefficient(grad, iv) ≈ dG[j,i] atol=ATOL rtol=RTOL
        end
    end
end

# refered from https://github.com/jump-dev/MathOptInterface.jl/blob/master/src/Test/contquadratic.jl#L3
# Find equivalent CVXPYLayers and QPTH code here:
#               https://github.com/AKS1996/jump-gsoc-2020/blob/master/DiffOpt_tests_1_py.ipynb
@testset "Differentiating MOI examples 1" begin
    # homogeneous quadratic objective
    # Min x^2 + xy + y^2 + yz + z^2
    # st  x + 2y + 3z >= 4 (c1)
    #     x +  y      >= 1 (c2)
    #     x, y, z \in R

    model = JuMP.direct_model(DiffOpt.diff_optimizer(OSQP.Optimizer))
    MOI.set(JuMP.backend(model).optimizer, MOI.RawOptimizerAttribute("eps_prim_inf"), 1e-7)
    MOI.set(JuMP.backend(model).optimizer, MOI.RawOptimizerAttribute("eps_dual_inf"), 1e-7)
    MOI.set(model, MOI.Silent(), true)
    @variables(model, begin
        x
        y
        z
    end)
    @constraint(model, c1,
        x + 2y + 3z >= 4
    )
    @constraint(model, c2,
        x +  y      >= 1
    )
    @objective(model, Min,
        x^2 + x*y + y^2 + y*z + z^2
    )

    optimize!(model)

    z = [x, y, z]

    @test JuMP.value.(z) ≈ [4/7, 3/7, 6/7] atol=ATOL rtol=RTOL

    MOI.set.(model, DiffOpt.BackwardInVariablePrimal(), z, 1.0)

    DiffOpt.backward(model)

    dl_dq = [-0.2142857;  0.21428567; -0.07142857]
    dl_dQ = [-0.12244895  0.01530609 -0.11224488;
              0.01530609  0.09183674  0.07653058;
             -0.11224488  0.07653058 -0.06122449]
    expected = dl_dq' * z + z' * (dl_dQ / 2.0) * z
    @test moi_function(MOI.get(model, DiffOpt.BackwardOutObjective())) ≈ moi_function(expected)  atol=ATOL rtol=RTOL

    dh = [0.35714284; 0.4285714]
    # This ws already broken
    # @test_broken dl_dG ≈ [0.05102035   0.30612245  0.255102;
    #                0.06122443   0.36734694  0.3061224] atol=ATOL rtol=RTOL
    dG = -[0.05102035   0.30612245  0.255102;
           0.06122443   0.36734694  0.3061224]
    for (j,jc) in enumerate([c1, c2])
        grad = MOI.get(model, DiffOpt.BackwardOutConstraint(), jc)
        @test JuMP.constant(grad) ≈ -dh[j] atol=ATOL rtol=RTOL
        for (i,iv) in enumerate(z)
            @test_broken JuMP.coefficient(grad, iv) ≈ dG[j,i] atol=ATOL rtol=RTOL
        end
    end
end

@testset "Differentiating MOI examples 2 - non trivial backward pass vector" begin
    # non-homogeneous quadratic objective
    #    minimize 2 x^2 + y^2 + xy + x + y
    #       s.t.  x, y >= 0
    #             x + y = 1 (c1)

    model = JuMP.Model(() -> DiffOpt.diff_optimizer(Ipopt.Optimizer))
    MOI.set(model, MOI.Silent(), true)
    @variable(model, x ≥ 0);
    @variable(model, y ≥ 0);
    @constraint(model, c1, x + y == 1)
    @objective(model, Min, 2 * x^2 + y^2 + x * y + x + y)

    optimize!(model)

    z = [x, y]

    @test JuMP.value.(z) ≈ [0.25, 0.75] atol=ATOL rtol=RTOL

    MOI.set.(model, DiffOpt.BackwardInVariablePrimal(), z, [1.3, 0.5])

    DiffOpt.backward(model)

    dl_dq = [-0.2; 0.2]
    dl_dQ = [-0.05   -0.05;
             -0.05    0.15]
    expected = dl_dq' * z + z' * (dl_dQ / 2.0) * z
    @test moi_function(MOI.get(model, DiffOpt.BackwardOutObjective())) ≈ moi_function(expected)  atol=ATOL rtol=RTOL

    func = MOI.get(model, DiffOpt.BackwardOutConstraint(), c1)
    @test -0.7 ≈ JuMP.constant(func) atol=ATOL rtol=RTOL

    # This ws already broken
    # @test_broken dl_dG ≈ [0.05102035   0.30612245  0.255102;
    #                0.06122443   0.36734694  0.3061224] atol=ATOL rtol=RTOL
    dl_dA = [0.375 -1.075]
    for (j, vi) in enumerate(z)
        @test dl_dA[j] ≈ JuMP.coefficient(func, vi) atol=ATOL rtol=RTOL
    end

    c_le = [LowerBoundRef(x), LowerBoundRef(y)]

    dl_dh = [1e-8; 1e-8]
    dl_dG = [1e-8  1e-8; 1e-8 1e-8]
    for (j,jc) in enumerate(c_le)
        grad = MOI.get(model, DiffOpt.BackwardOutConstraint(), jc)
        @test JuMP.constant(grad) ≈ dl_dh[j] atol=ATOL rtol=RTOL
        for (i,iv) in enumerate(z)
            @test JuMP.coefficient(grad, iv) ≈ dl_dG[j,i] atol=ATOL rtol=RTOL
        end
    end

end

@testset "Differentiating non trivial convex QP JuMP" begin
    nz = 10
    nineq_le = 25
    neq = 10

    # read matrices from files
    names = ["P", "q", "G", "h", "A", "b"]
    matrices = []

    for name in names
        push!(matrices, DelimitedFiles.readdlm(joinpath(dirname(dirname(pathof(DiffOpt))), "test", "data", name * ".txt"), ' ', Float64, '\n'))
    end

    Q, q, G, h, A, b = matrices
    q = vec(q)
    h = vec(h)
    b = vec(b)

    model = JuMP.Model(() -> DiffOpt.diff_optimizer(Ipopt.Optimizer))
    MOI.set(model, MOI.Silent(), true)

    @variable(model, x[1:nz])

    @objective(model, Min, dot(Q * x, x) + dot(q, x))
    @constraint(model, c_le, G * x .<= h)
    @constraint(model, c_eq, A * x .== b)

    optimize!(model)

    MOI.set.(model, DiffOpt.BackwardInVariablePrimal(), x, 1.0)

    DiffOpt.backward(model)

    # obtain gradients
    # grads = backward(doptimizer, ["Q", "q", "G", "h", "A", "b"], ones(nz))  # using dl_dz=[1,1,1,1,1,....]

    # read gradients from files
    param_names = ["dP", "dq", "dG", "dh", "dA", "db"]
    grads_actual = []

    for name in param_names
        push!(grads_actual,
            DelimitedFiles.readdlm(
                joinpath(@__DIR__, "data", "$(name).txt"),
                ' ', Float64, '\n',
            )
        )
    end

    dq = grads_actual[2] # = vec(grads_actual[2])
    dh = grads_actual[4] # = vec(grads_actual[4])
    db = grads_actual[6] # = vec(grads_actual[6])

    grad = MOI.get(model, DiffOpt.BackwardOutObjective())
    @test moi_function(grad) ≈ moi_function(dq ⋅ x) atol=1e-2 rtol=1e-2

    for (i, ci) in enumerate(c_le)
        @test -dh[i] ≈ JuMP.constant(MOI.get(model,
            DiffOpt.BackwardOutConstraint(), ci)) atol=1e-2 rtol=1e-2
    end

    for (i, ci) in enumerate(c_eq)
        @test -db[i] ≈ JuMP.constant(MOI.get(model,
            DiffOpt.BackwardOutConstraint(), ci)) atol=1e-2 rtol=1e-2
    end

    # # testing differences
    # for i in 1:size(grads)[1]
    #     @test grads[i] ≈  grads_actual[i] atol=1e-2 rtol=1e-2
    # end
end

@testset "Differentiating LP; checking gradients for non-active contraints" begin
    # Issue #40 from Gurobi.jl
    # min  x
    # s.t. x >= 0
    #      x >= 3

    model = direct_model(DiffOpt.diff_optimizer(GLPK.Optimizer))
    MOI.set(model, MOI.Silent(), true)

    @variable(model, x[1:1])

    @objective(model, Min, 1.1 * x[1])
    @constraint(model, c1, x[1] ≥ 0)
    @constraint(model, c2, x[1] ≥ 3)

    optimize!(model)

    # obtain gradients
    MOI.set.(model, DiffOpt.BackwardInVariablePrimal(), x, 1.0)

    DiffOpt.backward(model)

    c_le = [c1, c2]

    dh = [0.0, 1.0]
    dG = [0.0, -3.0]
    for (j,jc) in enumerate(c_le)
        grad = MOI.get(model, DiffOpt.BackwardOutConstraint(), jc)
        @test JuMP.constant(grad) ≈ -dh[j] atol=ATOL rtol=RTOL
        for (i,iv) in enumerate(x)
            @test JuMP.coefficient(grad, iv) ≈ dG[j,i] atol=ATOL rtol=RTOL
        end
    end

    model = direct_model(DiffOpt.diff_optimizer(GLPK.Optimizer))
    MOI.set(model, MOI.Silent(), true)

    @variable(model, x[1:1])

    @objective(model, Min, x[1])
    @constraint(model, c1, x[1] ≥ 0)
    @constraint(model, c2, x[1] ≥ 3)

    optimize!(model)

    MOI.set.(model, DiffOpt.BackwardInVariablePrimal(), x, 1.0)

    DiffOpt.backward(model)

    c_le = [c1, c2]

    dh = [0.0, 1.0]
    dG = [0.0, -3.0]
    for (j,jc) in enumerate(c_le)
        grad = MOI.get(model, DiffOpt.BackwardOutConstraint(), jc)
        @test JuMP.constant(grad) ≈ -dh[j] atol=ATOL rtol=RTOL
        for (i,iv) in enumerate(x)
            @test JuMP.coefficient(grad, iv) ≈ dG[j,i] atol=ATOL rtol=RTOL
        end
    end
end

@testset "Differentiating LP; checking gradients for non-active contraints" begin
    # refered from - https://en.wikipedia.org/wiki/Simplex_algorithm#Example

    # max 2x + 3y + 4z
    # s.t. 3x+2y+z <= 10
    #      2x+5y+3z <= 15
    #      x,y,z >= 0

    model = direct_model(DiffOpt.diff_optimizer(SCS.Optimizer))
    MOI.set(model, MOI.Silent(), true)
    @variable(model, v[1:3] ≥ 0)
    @objective(model, Min, dot([-2.0, -3.0, -4.0], v))
    (x, y, z) = v

    @constraint(model, c1, 3x+2y+z <= 10)
    @constraint(model, c2, 2x+5y+3z <= 15)
    optimize!(model)

    MOI.set.(model, DiffOpt.BackwardInVariablePrimal(), v, 1.0)

    DiffOpt.backward(model)

    dQ = zeros(3,3)
    dc = zeros(3)
    dG = [0.0 0.0 0.0;
          0.0 0.0 -5/3;
          0.0 0.0 -5/3;
          0.0 0.0 10/3;
          0.0 0.0 0.0]
    dh = [0.0; 1/3; 1/3; -2/3; 0.0]

    cb = LowerBoundRef.(v)
    cc = [c1, c2]
    ctrs = vcat(cc, cb)#, cc)

    expected = dc' * v + v' * dQ * v
    grad = MOI.get(model, DiffOpt.BackwardOutObjective())
    @test moi_function(grad) ≈ moi_function(expected) atol=ATOL rtol=RTOL

    for (j,jc) in enumerate(ctrs)
        grad = MOI.get(model, DiffOpt.BackwardOutConstraint(), jc)
        @test JuMP.constant(grad) ≈ -dh[j] atol=ATOL rtol=RTOL
        for (i,iv) in enumerate(v)
            @test JuMP.coefficient(grad, iv) ≈ dG[j,i] atol=ATOL rtol=RTOL
        end
    end
end

@testset "Differentiating LP with variable bounds" begin
    # max 2x + 3y + 4z
    # s.t. 3x+2y+z <= 10
    #      2x+5y+3z <= 15
    #      x ≤ 3
    #      0 ≤ y ≤ 2
    #      z ≥ 2
    #      x,y,z >= 0
    # variant of previous test with same solution

    model = direct_model(DiffOpt.diff_optimizer(GLPK.Optimizer))
    MOI.set(model, MOI.Silent(), true)
    @variable(model, v[1:3] ≥ 0)
    (x, y, z) = v
    @objective(model, Max, 2x + 3y + 4z)
    @constraint(model, c1, 3x+2y+z <= 10)
    @constraint(model, c2, 2x+5y+3z <= 15)
    @constraint(model, c3, x ≤ 3)
    @constraint(model, c4, 1.0 * y ≤ 2)
    @constraint(model, c5, z ≥ 2)

    optimize!(model)

    MOI.set.(model, DiffOpt.BackwardInVariablePrimal(), v, 1.0)

    DiffOpt.backward(model)

    dQ = zeros(3,3)
    dc = zeros(3)
    dG = [0.0 0.0 0.0
          0.0 0.0 -5/3
          0.0 0.0 0.0
          0.0 0.0 0.0
          0.0 0.0 0.0
          0.0 0.0 -5/3
          0.0 0.0 10/3
          0.0 0.0 0.0
          ]
    dh = [0.0, 1/3, 0.0, 0.0, 0.0, 1/3, -2/3, 0.0]

    cb = LowerBoundRef.(v)
    cc = [c1, c2, c3, c4, c5]
    ctrs = vcat(cc, cb)#, cc)

    expected = dc' * v + v' * dQ * v
    grad = MOI.get(model, DiffOpt.BackwardOutObjective())
    @test moi_function(grad) ≈ moi_function(expected) atol=ATOL rtol=RTOL

    for (j,jc) in enumerate(ctrs)
        grad = MOI.get(model, DiffOpt.BackwardOutConstraint(), jc)
        @test JuMP.constant(grad) ≈ -dh[j] atol=ATOL rtol=RTOL
        for (i,iv) in enumerate(v)
            @test JuMP.coefficient(grad, iv) ≈ dG[j,i] atol=ATOL rtol=RTOL
        end
    end
end

@testset "Differentiating simple SOCP" begin
    # referred from _soc2test, https://github.com/jump-dev/MathOptInterface.jl/blob/master/src/Test/contconic.jl#L1355
    # find equivalent diffcp python program here: https://github.com/AKS1996/jump-gsoc-2020/blob/master/diffcp_socp_1_py.ipynb

    # Problem SOC2
    # min  x
    # s.t. y ≥ 1/√2
    #      x² + y² ≤ 1
    # in conic form:
    # min  x
    # s.t.  -1/√2 + y ∈ R₊
    #        1 - t ∈ {0}
    #      (t,x,y) ∈ SOC₃

    model = JuMP.direct_model(DiffOpt.diff_optimizer(SCS.Optimizer))
    MOI.set(model, MOI.Silent(), true)
    @variable(model, x)
    @variable(model, y)
    @variable(model, t)

    vv = [x,y,t]

    @objective(model, Min, x)
    @constraint(model, c1, [-1/√2 + y] in MOI.Nonnegatives(1))
    @constraint(model, c2, [1 - t] in MOI.Zeros(1))
    @constraint(model, c3, [1.0 * t, x, y] in SecondOrderCone())

    optimize!(model)

    v = JuMP.value.(vv)
    # slack variables
    s = collect(Iterators.flatten(JuMP.value.([c1, c2, c3])))
    y = collect(Iterators.flatten(JuMP.dual.([c1, c2, c3])))

    # these matrices are benchmarked with the output generated by diffcp
    # refer the python file mentioned above to get equivalent python source code
    @test v ≈ [-1/√2; 1/√2; 1.0] atol=ATOL rtol=RTOL
    @test s ≈ [0.0, 0.0, 1.0, -1/√2, 1/√2] atol=ATOL rtol=RTOL
    @test y ≈ [1, √2, √2, 1, -1] atol=ATOL rtol=RTOL

    dA = zeros(5, 3)
    dA[1:3, :] .= Matrix(1.0I, 3, 3)
    db = zeros(5)
    dc = zeros(3)

    MOI.set.(model, DiffOpt.BackwardInVariablePrimal(), vv, 1.0)

    @test_broken DiffOpt.backward(model)

    # TODO add tests

end


function unit(x::AbstractVector, entry::Integer)
	unit(eltype(x), length(x), entry)
end

function unit(length::Integer, entry::Integer)
	unit(Float64, length, entry)
end

function unit(T::Type, length::Integer, entry::Integer)
	u = zeros(T, length)
	u[entry] = one(T)
	return u
end

@doc """
	eigval1(A::Matrix) -> Vector

Returns a unit eigenvector of `A` for the eigenvalue 1 if this eigenspace is 1D.
Otherwise, return an error.
"""->
function eigval1(A::DenseMatrix{Float64})
	E = eigfact(A)
	# Types are hardcoded; otherwise instability occurs
	isreal(E[:values]) || throw(DomainError())
	vals = E[:values]::Vector{Float64}
	vecs = E[:vectors]::Matrix{Float64}

	# Find index of eigenvalue 1
	eval1_index = Vector{Int64}()
	for i in 1:length(vals)
		if isapprox(vals[i], 1.0)
			push!(eval1_index, i)
		end
	end

	if isempty(eval1_index)
		error("1 is not an eigenvalue")
	elseif length(eval1_index) >= 2
		error("The eigenspace of 1 is more than 1D")
	end

	return vecs[:,eval1_index[]]
end

function van_moment(wavename::String)
	lowername = lowercase( wavename )
	WT.vanishingmoments( WT.eval(parse(lowername)) )::Int64
end

@doc """
	support(wavename) -> DaubSupport

The support of the Daubechies scaling function `wavename`.
"""->
function support(wavename::String)
	vm = van_moment(wavename)
	return DaubSupport(-vm+1, vm)
end

@doc """
	isinside(x, S::DaubSupport) -> Bool

Returns `true` if `x` is inside `S`.
"""->
@inline isinside(x, S::DaubSupport) = left(S) <= x <= right(S)

# Convert between values and indices of a vector with the integers in the support S
@inline x2index(x::Integer, S::DaubSupport) = x + 1 - left(S)
@inline index2x(idx::Integer, S::DaubSupport) = idx - 1 + left(S)

# Convert between values and indices of a vector with dyadic rationals
# at resolution R in the support S
@inline x2index(x, S::DaubSupport, R::Integer) = Int( (x-left(S))*2^R ) + 1
@inline index2x(idx::Integer, S::DaubSupport, R::Integer) = (idx - 1)*2.0^(-R) + left(S)


@doc doc"""
    latexalign()
Generate a ``LaTeX`` align environment from an input.

# Examples
## use with arrays

```julia
lhs = [:(dx/dt), :(dy/dt), :(dz/dt)]
rhs = [:(y-x), :(x*z-y), :(-z)]
latexalign(lhs, rhs)
```

```LaTeX
\begin{align}
\frac{dx}{dt} =& y - x \\\\
\frac{dy}{dt} =& x \cdot z - y \\\\
\frac{dz}{dt} =& - z \\\\
\end{align}
```

## use with ParameterizedFunction

```julia-repl
julia> using DifferentialEquations
julia> ode = @ode_def foldChangeDetection begin
    dm = r_m * (i - m)
    dy = r_y * (p_y * i/m - y)
end i r_m r_y p_y

julia> latexalign(ode)
```
```LaTeX
\begin{align}
\frac{dm}{dt} =& r_{m} \cdot \left( i - m \right) \\\\
\frac{dy}{dt} =& r_{y} \cdot \left( \frac{p_{y} \cdot i}{m} - y \right) \\\\
\end{align}
```

"""
latexalign(args...; kwargs...) = latexify(args...; kwargs..., env=:align)

function _latexalign(arr::AbstractMatrix; separator=" =& ", double_linebreak=false, starred=false, rows=:all, aligned=false, kwargs...)
    eol = double_linebreak ? " \\\\\\\\\n" : " \\\\\n"
    arr = latexraw.(arr; kwargs...)
    separator isa String && (separator = fill(separator, size(arr)[1]))
    str = ""
    if aligned
        str *= "\\begin{aligned}\n"
    else
        str *= "\\begin{align$(starred ? "*" : "")}\n"
    end
    if rows == :all
        iterate_rows = 1:(size(arr)[1])
    else
        iterate_rows = rows
    end

    for i in iterate_rows
        if i != last(iterate_rows)
            str *= join(arr[i,:], separator[i]) * eol
        else
            str *= join(arr[i,:], separator[i]) * "\n"
        end
    end
    if aligned
        str *= "\\end{aligned}\n"
    else
        str *= "\\end{align$(starred ? "*" : "")}\n"
    end
    latexstr = LaTeXString(str)
    COPY_TO_CLIPBOARD && clipboard(latexstr)
    return latexstr
end

function _latexalign(lhs::AbstractArray, rhs::AbstractArray; kwargs...)
    return latexalign(hcat(lhs, rhs); kwargs...)
end

function _latexalign(lhs::Tuple, rhs::Tuple; kwargs...)
    return latexalign(hcat(collect(lhs), collect(rhs)); kwargs...)
end

_latexalign(args::Tuple...; kwargs...) = latexalign(reduce(hcat, [collect(i) for i in args]); kwargs...)

_latexalign(arg::Tuple; kwargs...) = latexalign(reduce(hcat, [collect(i) for i in arg]); kwargs...)

function _latexalign(nested::AbstractVector{AbstractVector}; kwargs...)
    return latexalign(reduce(hcat, nested); kwargs...)
end

function _latexalign(d::AbstractDict; kwargs...)
    latexalign(collect(keys(d)), collect(values(d)); kwargs...)
end

"""
    _latexalign(vec::AbstractVector)

Go through the elements, split at any = sign, pass on as a matrix.
"""
function _latexalign(vec::AbstractVector; kwargs...)
    lvec = _latexraw.(vec; kwargs...)
    ## turn the array into a matrix
    lmat = reduce(hcat, split.(lvec, " = "))
    ## turn the matrix ito arrays of left-hand-side, right-hand-side.
    larr = [lmat[i,:] for i in 1:size(lmat, 1)]
    length(larr) < 2 && error("Invalid intput to _latexalign().")
    return latexalign( reduce(hcat, larr) ; kwargs...)
end