# Jorge Fernández de Cossío Díaz ( @cossio ) wrote the kabsch, rmsd and center! functions.

"""
`kabsch(A::AbstractMatrix{Float64}, B::AbstractMatrix{Float64})`

This function takes two sets of points, `A` (refrence) and `B` as NxD matrices, where D
is the dimension and N is the number of points.
Assumes that the centroids of `A` and `B` are at the origin of coordinates.
You can call `center!` on each matrix before calling `kabsch` to center the matrices
in the `(0.0, 0.0, 0.0)`.
Rotates `B` so that `rmsd(A,B)` is minimized.
Returns the rotation matrix. You should do `B * RotationMatrix` to get the rotated B.
"""
function kabsch(A::AbstractMatrix{Float64}, B::AbstractMatrix{Float64})
    @assert size(A) == size(B)
    M::AbstractMatrix{Float64} = B' * A
    χ = Matrix{Float64}(I, size(M, 1), size(M, 2))
    χ[end,end] = sign(det(M))
    u::AbstractMatrix{Float64}, σ::Vector{Float64}, v::AbstractMatrix{Float64} = svd(M)
    return u * χ * v'
end

"""
`center!(A::AbstractMatrix{Float64})`

Takes a set of points `A` as an NxD matrix (N: number of points, D: dimension).
Translates `A` in place so that its centroid is at the origin of coordinates
"""
function center!(A::AbstractMatrix{Float64})
    for i = 1:size(A)[2]
        A[:,i] .-= mean(A[:,i])
    end
end

"""
`rmsd(A::AbstractMatrix{Float64}, B::AbstractMatrix{Float64})`

Return RMSD between two sets of points `A` and `B`, given as NxD matrices
(N: number of points, D: dimension).
"""
function rmsd(A::AbstractMatrix{Float64}, B::AbstractMatrix{Float64})
    @assert size(A) == size(B)
    N, D = size(A)
    s::Float64 = 0.0
    for i=1:N, j=1:D
        s += (B[i,j]::Float64 - A[i,j]::Float64)^2
    end
    return sqrt(s / N)
end

"""
`rmsd(A::AbstractVector{PDBResidue}, B::AbstractVector{PDBResidue}; superimposed::Bool=false)`

Returns the Cα RMSD value between two PDB structures: `A` and `B`.
If the structures are already superimposed between them,
use `superimposed=true` to avoid a new superimposition (`superimposed` is `false` by default).
"""
function rmsd(A::AbstractVector{PDBResidue}, B::AbstractVector{PDBResidue};
                        superimposed::Bool=false)
    if superimposed
        rmsd(CAmatrix(A),CAmatrix(B))
    else
        superimpose(A, B)[end]::Float64
    end
end

# Kabsch for Vector{PDBResidues}
# ==============================

"Returns the Cα with best occupancy in the `PDBResidue`."
function getCA(res::PDBResidue)
    @assert length(res) != 0 "There is no atoms in the residue."
    CAs = findatoms(res, "CA")
    @assert length(CAs) != 0 "There is no alpha carbons in the residue."
    CAindex = selectbestoccupancy(res, CAs)
    res.atoms[CAindex]
end

"""
Returns a matrix with the x, y and z coordinates of the Cα with best occupancy for each
`PDBResidue` of the ATOM group. If a residue doesn't have a Cα, its Cα coordinates are NaNs.
"""
function CAmatrix(residues::AbstractVector{PDBResidue})
    len = length(residues)
    CAlist = Array{Float64}(undef, 3 * len)
    j = 0
    r = 0
    @inbounds for i in 1:len
        res = residues[i]
        if (res.id.group == "ATOM") && (length(res) > 0)
            r += 1
            CAs = findatoms(res, "CA")
            if length(CAs) != 0
                CAindex = selectbestoccupancy(res, CAs)
                coord = res.atoms[CAindex].coordinates
                CAlist[j+=1] = coord.x
                CAlist[j+=1] = coord.y
                CAlist[j+=1] = coord.z
            else
                CAlist[j+=1] = NaN
                CAlist[j+=1] = NaN
                CAlist[j+=1] = NaN
            end
        end
    end
    reshape(resize!(CAlist,j),(3,r))'
end

"Returns a matrix with the x, y, z coordinates of each atom in each `PDBResidue`"
function coordinatesmatrix(res::PDBResidue)
    atoms = res.atoms
    len = length(atoms)
    mat = Array{Float64}(undef, 3, len)
    for i in 1:len
        coord = atoms[i].coordinates
        mat[1,i] = coord.x
        mat[2,i] = coord.y
        mat[3,i] = coord.z
    end
    mat'
end

coordinatesmatrix(residues::AbstractVector{PDBResidue}) = vcat(map(coordinatesmatrix, residues)...)

"""
Returns a `Matrix{Float64}` with the centered coordinates of all the atoms in `residues`.
An optional positional argument `CA` (default: `true`) defines if only Cα carbons should
be used to center the matrix.
"""
function centeredcoordinates(residues::AbstractVector{PDBResidue}, CA::Bool=true)
    coordinates = PDB.coordinatesmatrix(residues)
    meancoord = CA ? mean(PDB.CAmatrix(residues), dims=1) : mean(coordinates, dims=1)
    coordinates .- meancoord
end

"""
Returns a new `Vector{PDBResidue}` with the `PDBResidue`s having centered coordinates.
An optional positional argument `CA` (default: `true`) defines if only Cα carbons should
be used to center the matrix.
"""
function centeredresidues(residues::AbstractVector{PDBResidue}, CA::Bool=true)
    coordinates = centeredcoordinates(residues, CA)
    change_coordinates(residues, coordinates)
end

"""
`change_coordinates(atom::PDBAtom, coordinates::Coordinates)`

Returns a new `PDBAtom` but with a new `coordinates`
"""
function change_coordinates(atom::PDBAtom, coordinates::Coordinates)
    PDBAtom(coordinates,
            identity(atom.atom),
            identity(atom.element),
            copy(atom.occupancy),
            identity(atom.B))
end

"""
`change_coordinates(residue::PDBResidue, coordinates::AbstractMatrix{Float64}, offset::Int=1)`

Returns a new `PDBResidues` with (x,y,z) from a coordinates `AbstractMatrix{Float64}`
You can give an `offset` indicating in wich matrix row starts the (x,y,z) coordinates
of the residue.
"""
function change_coordinates(residue::PDBResidue, coordinates::AbstractMatrix{Float64}, offset::Int=1)
    centeredatoms = map(residue.atoms) do atom
        atoms = change_coordinates(atom, Coordinates(vec(coordinates[offset,:])))
        offset += 1
        return atoms
    end
    PDBResidue(residue.id, centeredatoms)
end

"""
`change_coordinates(residues::AbstractVector{PDBResidue}, coordinates::AbstractMatrix{Float64})`

Returns a new `Vector{PDBResidues}` with (x,y,z) from a coordinates `Matrix{Float64}`
"""
function change_coordinates(residues::AbstractVector{PDBResidue}, coordinates::AbstractMatrix{Float64})
    nres = length(residues)
    updated = Array{PDBResidue}(undef, nres)
    j = 1
    for i in 1:nres
        residue = residues[i]
        updated[i] = change_coordinates(residue, coordinates, j)
        j += length(residue)
    end
    updated
end

"Returns a new `PDBAtom` but with a `B` as B-factor"
function _change_B(atom::PDBAtom, B::String)
    PDBAtom(copy(atom.coordinates),
            copy(atom.atom),
            copy(atom.element),
            copy(atom.occupancy),
            B)
end

_iscentered(x::Float64, y::Float64, z::Float64) = (abs(x) <= 1e-13) && (abs(y) <= 1e-13) && (abs(z) <= 1e-13)

_iscentered(meanCα::AbstractVector{Float64}) = _iscentered(meanCα[1], meanCα[2], meanCα[3])

_iscentered(CA::AbstractMatrix{Float64}) = _iscentered(vec(mean(CA, dims=1)))

"""
Return the matching CA matrices after deleting the rows/residues where the CA
is missing in at least one structure.
"""
function _get_matched_Cαs(A::AbstractVector{PDBResidue},
                          B::AbstractVector{PDBResidue})
    length_A = length(A)
    if length_A != length(B)
        throw(ArgumentError("PDBResidue vectors should have the same length."))
    end
    ACα = PDB.CAmatrix(A)
    BCα = PDB.CAmatrix(B)
    without_Cα = isnan.(ACα[:,1]) .| isnan.(BCα[:,1])
    if any(without_Cα)
        n_without_ca = sum(without_Cα)
        if length_A - n_without_ca == 0
            throw(ArgumentError("There are not alpha-carbons to align."))
        end
        @warn string("Using ", length_A - n_without_ca,
            " residues for RMSD calculation because there are ", n_without_ca,
            " residues without CA: ", findall(without_Cα))
        with_Cα = .!without_Cα
        return ACα[with_Cα, :], BCα[with_Cα, :]
    end
    ACα, BCα
end

"""
This function takes `A::AbstractVector{PDBResidue}` (reference) and
`B::AbstractVector{PDBResidue}`. Translates `A` and `B` to the origin of coordinates,
and rotates `B` so that `rmsd(A,B)` is minimized with the Kabsch algorithm
(using only their α carbons).
Returns the rotated and translated versions of `A` and `B`, and the RMSD value.
"""
function superimpose(A::AbstractVector{PDBResidue}, B::AbstractVector{PDBResidue})
    ACα, BCα = _get_matched_Cαs(A, B)
    Bxyz = PDB.coordinatesmatrix(B)
    meanACα = vec(mean(ACα, dims=1))
    meanBCα = vec(mean(BCα, dims=1))
    if !_iscentered(meanBCα)
        @inbounds BCα[:,1] .-= meanBCα[1]
        @inbounds BCα[:,2] .-= meanBCα[2]
        @inbounds BCα[:,3] .-= meanBCα[3]
        @inbounds Bxyz[:,1] .-= meanBCα[1]
        @inbounds Bxyz[:,2] .-= meanBCα[2]
        @inbounds Bxyz[:,3] .-= meanBCα[3]
    end
    if !_iscentered(meanACα)
        @inbounds ACα[:,1] .-= meanACα[1]
        @inbounds ACα[:,2] .-= meanACα[2]
        @inbounds ACα[:,3] .-= meanACα[3]
        Axyz = PDB.coordinatesmatrix(A)
        @inbounds Axyz[:,1] .-= meanACα[1]
        @inbounds Axyz[:,2] .-= meanACα[2]
        @inbounds Axyz[:,3] .-= meanACα[3]
        RotationMatrix = PDB.kabsch(ACα, BCα)
        return(
            change_coordinates(A, Axyz),
            change_coordinates(B, Bxyz * RotationMatrix),
            PDB.rmsd(ACα, BCα * RotationMatrix)
            )
    else
        RotationMatrix = PDB.kabsch(ACα, BCα)
        return(
            A,
            change_coordinates(B, Bxyz * RotationMatrix),
            PDB.rmsd(ACα, BCα * RotationMatrix)
            )
    end
end

# RMSF: Root Mean-Square-average distance (Fluctuation)
# -----------------------------------------------------

"This looks for errors in the input to rmsf methods"
function _rmsf_test(vector)
    n = length(vector)
    if n < 2
        throw(ArgumentError("You need at least two matrices/structures"))
    end
    sizes = unique(Tuple{Int,Int}[ size(s) for s in vector ])
    if length(sizes) > 1
        throw(ArgumentError("Matrices/Structures must have the same number of rows/atoms"))
    end
    if sizes[1][2] != 3
        throw(ArgumentError("Matrices should have 3 columns (x, y, z)"))
    end
end

"""
Calculates the average/mean position of each atom in a set of structure.
The function takes a vector (`AbstractVector`) of vectors (`AbstractVector{PDBResidue}`)
or matrices (`AbstractMatrix{Float64}`) as first argument. As second (optional) argument this
function can take an `AbstractVector{Float64}` of matrix/structure weights to return a
weighted mean. When a AbstractVector{PDBResidue} is used, if the keyword argument `calpha` is
`false` the RMSF is calculated for all the atoms. By default only alpha carbons are used
(default: `calpha=true`).
"""
function mean_coordinates(vec::AbstractVector{T}) where T <: AbstractMatrix{Float64}
    _rmsf_test(vec)
    n = length(vec)
    reduce(+, vec) ./ n
end

function mean_coordinates(vec::AbstractVector{T},
                          matrixweights::AbstractVector{Float64}) where T <: AbstractMatrix{Float64}
    _rmsf_test(vec)
    @assert length(vec) == length(matrixweights) "The number of matrix weights must be equal to the number of matrices."
    n = sum(matrixweights)
    reduce(+, (vec .* matrixweights)) ./ n
end

function mean_coordinates(vec::AbstractVector{T};
                          calpha::Bool=true) where T <: AbstractVector{PDBResidue}
    mean_coordinates(map(calpha ? CAmatrix : coordinatesmatrix, vec))
end

function mean_coordinates(vec::AbstractVector{T},
                          args...; calpha::Bool=true) where T <: AbstractVector{PDBResidue}
    mean_coordinates(map(calpha ? CAmatrix : coordinatesmatrix, vec), args...)
end

"""
Calculates the RMSF (Root Mean-Square-Fluctuation) between an atom and its average
position in a set of structures. The function takes a vector (`AbstractVector`) of
vectors (`AbstractVector{PDBResidue}`) or matrices (`AbstractMatrix{Float64}`) as first
argument. As second (optional) argument this function can take an `AbstractVector{Float64}`
of matrix/structure weights to return the root weighted mean-square-fluctuation around
the weighted mean structure. When a Vector{PDBResidue} is used, if the keyword argument
`calpha` is `false` the RMSF is calculated for all the atoms. By default only alpha
carbons are used (default: `calpha=true`).
"""
function rmsf(vector::AbstractVector{T}) where T <: AbstractMatrix{Float64}
    m = mean_coordinates(vector)
    # MIToS RMSF is calculated as in Eq. 6 from:
    # Kuzmanic, Antonija, and Bojan Zagrovic.
    # "Determination of ensemble-average pairwise root mean-square deviation from experimental B-factors."
    # Biophysical journal 98.5 (2010): 861-871.
    vec(sqrt.(mean(map( mat -> mapslices(x -> sum(abs2, x), mat .- m, dims=2), vector ))))
end

function rmsf(vector::AbstractVector{T},
              matrixweights::AbstractVector{Float64}) where T <: AbstractMatrix{Float64}
    m = mean_coordinates(vector, matrixweights)
    d = map( mat -> mapslices(x -> sum(abs2, x), mat .- m, dims=2), vector )
    vec(sqrt.(sum(d .* matrixweights)/sum(matrixweights)))
end

function rmsf(vec::AbstractVector{T}; calpha::Bool=true) where T <: AbstractVector{PDBResidue}
    rmsf(map(calpha ? CAmatrix : coordinatesmatrix, vec))
end

function rmsf(vec::AbstractVector{T},
              matrixweights::AbstractVector{Float64};
              calpha::Bool=true) where T <: AbstractVector{PDBResidue}
    rmsf(map(calpha ? CAmatrix : coordinatesmatrix, vec), matrixweights)
end


@testset "DouglasRachford" begin
    # Though this seems a strange way, it is a way to compute the mid point
    M = Sphere(2)
    p = [1.0, 0.0, 0.0]
    q = [0.0, 1.0, 0.0]
    r = [0.0, 0.0, 1.0]
    start = [0.0, 0.0, 1.0]
    result = result = geodesic(M, p, q, distance(M, p, q) / 2)
    F(M, x) = distance(M, x, p)^2 + distance(M, x, q)^2
    prox1 = (M, η, x) -> prox_distance(M, η, p, x)
    prox2 = (M, η, x) -> prox_distance(M, η, q, x)
    @test_throws ErrorException DouglasRachford(M, F, Array{Function,1}([prox1]), start) # we need more than one prox
    xHat = DouglasRachford(M, F, [prox1, prox2], start)
    @test F(M, start) > F(M, xHat)
    @test distance(M, xHat, result) ≈ 0
    # but we can also compute the riemannian center of mass (locally) on Sn
    # though also this is not that useful, but easy to test that DR works
    F2(M, x) = distance(M, x, p)^2 + distance(M, x, q)^2 + distance(M, x, r)^2
    prox1 = (M, η, x) -> prox_distance(M.manifold, η, p, x)
    prox2 = (M, η, x) -> prox_distance(M.manifold, η, q, x)
    prox3 = (M, η, x) -> prox_distance(M.manifold, η, r, x)
    o = DouglasRachford(
        M,
        F2,
        [prox1, prox2, prox3],
        start;
        debug=[DebugCost(), DebugIterate(), DebugProximalParameter(), 100],
        record=[RecordCost(), RecordProximalParameter()],
        return_options=true,
    )
    xHat2 = get_solver_result(o)
    drec2 = get_record(o)
    result2 = mean(M, [p, q, r])
    # since the default does not run that long -> rough estimate
    @test distance(M, xHat2, result2) ≈ 0
end


import MitosisStochasticDiffEq as MSDE
using Mitosis
using Test, Random
using LinearAlgebra
using OrdinaryDiffEq
Random.seed!(12345)
@testset "backward Lyapunov filtering tests" begin
  # define SDE function
  f(u,p,t) = p[1]*u .+ p[2]
  g(u,p,t) = p[3]

  # time span
  tstart = 0.0
  tend = 1.0
  dt = 0.001
  trange = tstart:dt:tend

  # set of linear parameters Eq.~(2.2)
  B, β, σ̃ = -0.1, 0.2, 1.3
  plin = [B, β, σ̃]

  # initial values for ODE
  mynames = (:logscale, :μ, :Σ);
  c = randn()
  ν = randn()
  P = 10*rand()
  myvalues = [c, ν, P]
  NT = NamedTuple{mynames}(myvalues)

  # covariance filter
  kernel = MSDE.SDEKernel(Mitosis.AffineMap(B, β), Mitosis.ConstantMap(σ̃), trange, plin)
  message, solend = MSDE.backwardfilter(kernel, NT, alg=OrdinaryDiffEq.Tsit5())

  # Lyapunov filter
  message2, solend2 = MSDE.backwardfilter(kernel, NT,
    filter=MSDE.LyapunovFilter())

  @test message.ts == message2.ts
  @test P == message2.sol.PT
  @test ν == message2.sol.νT
  @test c == message2.sol.cT

  @test getindex.(message.soldis,1) ≈ getindex.(message2.soldis,1) rtol=1e-10
  @test getindex.(message.soldis,2) ≈ getindex.(message2.soldis,2) rtol=1e-10
  @test getindex.(message.soldis,3) ≈ getindex.(message2.soldis,3) rtol=1e-10
  @test isapprox(solend, solend2, rtol=1e-10)

  dim = 1
  m = 1
  B, β, σ̃ = fill(B, 1, 1), fill(β, 1), fill(σ̃, 1, 1)
  kernel = MSDE.SDEKernel(
      Mitosis.AffineMap(B, β), Mitosis.ConstantMap(σ̃), trange, [B, β, σ̃]
  )

  ν = fill(ν, 1)
  P = fill(P, 1, 1)
  NT = NamedTuple{mynames}([c, ν, P])

  message, solend  = MSDE.backwardfilter(kernel, NT, alg=OrdinaryDiffEq.Tsit5())
  # Lyapunov filter
  message2, solend2 = MSDE.backwardfilter(kernel, NT,
    filter=MSDE.LyapunovFilter())

  @test getindex.(message.soldis,1) ≈ getindex.(message2.soldis,1) rtol=1e-10
  @test getindex.(message.soldis,2) ≈ getindex.(message2.soldis,2) rtol=1e-10
  @test getindex.(message.soldis,3) ≈ getindex.(message2.soldis,3) rtol=1e-10
  @test isapprox(solend, solend2, rtol=1e-10)

  dim = 4
  m = 3
  B, β, σ̃ = [randn(dim,dim)-I, randn(dim), randn(dim,m)] # B, β, σtil
  kernel = MSDE.SDEKernel(
      Mitosis.AffineMap(B, β), Mitosis.ConstantMap(σ̃), trange, [B, β, σ̃]
  )

  c = randn()
  ν = randn(dim)
  P = randn(dim,dim)
  P = I + 0.1*P*P'
  NT = NamedTuple{mynames}([c, ν, P])

  message, solend  = MSDE.backwardfilter(kernel, NT,  alg=OrdinaryDiffEq.Tsit5())
  # Lyapunov filter
  message2, solend2 = MSDE.backwardfilter(kernel, NT,
    filter=MSDE.LyapunovFilter())

  @test getindex.(message.soldis,1) ≈ getindex.(message2.soldis,1) rtol=1e-9
  @test getindex.(message.soldis,2) ≈ getindex.(message2.soldis,2) rtol=1e-9
  @test getindex.(message.soldis,3) ≈ getindex.(message2.soldis,3) rtol=1e-9
  @test solend2 ≈ solend rtol=1e-9
end


# # [Modeling with JuMP](@id modeling_with_jump)
#
# This page shows a minimal example of `PowerSystems.jl` used to develop and Economic Dispatch
# model. The code  shows the stages to develop modeling code
#
# 1. Make the data set from power flow and time series data,
# 2. Serialize the data,
# 3. Pass the data and algorithm to the model.
#
# One of the main uses of ``PowerSystems.jl` is not having re-run the data generation for every
# model execution. The model code shows an example of populating the constraints and cost
# functions using accessor functions inside the model function. The example concludes by
# reading the data created earlier and passing the algorithm with the data.

using PowerSystems
const PSY = PowerSystems
using JuMP
using Ipopt

DATA_DIR = "../../../data" #hide
system_data = System(joinpath(DATA_DIR, "matpower/case5_re.m"))
add_time_series!(system_data, joinpath(DATA_DIR,"forecasts/5bus_ts/timeseries_pointers_da.json"))
to_json(system_data, "system_data.json")

function ed_model(system::System, optimizer)
    ed_m = Model(optimizer)
    time_periods = 24
    thermal_gens_names = get_name.(get_components(ThermalStandard, system))
    @variable(ed_m, pg[g in thermal_gens_names, t in time_periods] >= 0)

    for g in get_components(ThermalStandard, system), t in time_periods
        name = get_name(g)
        @constraint(ed_m, pg[name, t] >= get_active_power_limits(g).min)
        @constraint(ed_m, pg[name, t] <= get_active_power_limits(g).max)
    end

    net_load = zeros(time_periods)
    for g in get_components(RenewableGen, system)
        net_load -= get_time_series_values(SingleTimeSeries, g, "max_active_power")
    end

    for g in get_components(StaticLoad, system)
        net_load += get_time_series_values(SingleTimeSeries, g, "max_active_power")
    end

    for t in time_periods
        @constraint(ed_m, sum(pg[g, t] for g in thermal_gens_names) == net_load[t])
    end

    @objective(ed_m, Min, sum(
            pg[get_name(g), t]^2*get_cost(get_variable(get_operation_cost(g)))[1] +
            pg[get_name(g), t]*get_cost(get_variable(get_operation_cost(g)))[2]
            for g in get_components(ThermalGen, system), t in time_periods
                )
            )

    return optimize!(ed_m)
end

system_data = System("system_data.json")
results = ed_model(system_data, Ipopt.Optimizer)


# Run tests on Beyns contour integral method

using NonlinearEigenproblemsTest
using NonlinearEigenproblems
using Test
using LinearAlgebra

push!(LOAD_PATH, string(@__DIR__, "/../src/gallery_extra"))
using GalleryNLEVP

@testset "GUN (NLEVP interface)" begin

    nep_org = nep_gallery(NLEVP_NEP,"gun")
    nep1 = nep_gallery("nlevp_native_gun")
    n=size(nep1,1);
    tol=1e-11;
    @bench @testset "Running alg" begin
        λ1,v1=quasinewton(nep1,λ=150^2+1im,v=ones(n),logger=displaylevel,tol=tol,maxit=500);
        λ1 = λ1[1]
        v1 = vec(v1)
        normalize!(v1)

        @test norm(compute_Mlincomb(nep1,λ1,v1))<tol*100
        @test norm(compute_Mder(nep1,λ1)*v1)<tol*100

        @test norm(compute_Mlincomb(nep_org,λ1,v1))<tol*100
        @test norm(compute_Mder(nep_org,λ1)*v1)<tol*100

    end
    @bench @testset "Compute derivatives" begin
        # Test compute_Mlincomb:
        λ=150^2+2im;
        V=randn(n,2);    v=V[:,1];
        z1=compute_Mlincomb(nep1,λ,V[:,1], [1.0], 1)
        z2=compute_Mlincomb(nep_org,λ,V[:,1], [1.0], 1)
        # Compare with divided difference
        ee=1e-4
        z3=(compute_Mlincomb(nep_org,λ+ee,v)-compute_Mlincomb(nep_org,λ-ee,v))/(2*ee);

        @test norm(z3-z1)<(ee^2)*1000  # Compare native and FD
        @test norm(z3-z2)<(ee^2)*1000 # Compare MATLAB and FD. This fails on NLEVP 3.0 due to a bug in gun.m in NLEVP
        @test norm(z1-z2)<sqrt(eps()) # Compare MATLAB and native.  This fails on NLEVP 3.0 due to a bug in gun.m in NLEVP
    end

    @bench @testset "Compare SPMF and Native in alg" begin
        # Check that two steps of quasinewton always gives the same result
        λ_org=0
        try
            quasinewton(nep_org,maxit=2,λ=150^2+1im,v=ones(n),logger=displaylevel)
        catch e
            λ_org=e.λ
        end

        λ1=0
        try
            quasinewton(nep1,maxit=2,λ=150^2+1im,v=ones(n),logger=displaylevel)
        catch e
            λ1=e.λ
        end

        @test abs(λ1-λ_org)<sqrt(eps())

    end

    @bench @testset "Compare MATLAB loaded vs MAT-loaded" begin
        nep2=nep_gallery("nlevp_native_gun");
        z=ones(n); λ=150^2;
        @test (norm(compute_Mlincomb(nep2,λ,z)-compute_Mlincomb(nep1,λ,z))/norm(compute_Mlincomb(nep1,λ,z)) < 1e-14)
    end

end


include("utils.jl")

include("test_kmeans.jl")
include("test_distances.jl")


using Test

@time @testset "Periodic Operators" begin include("periodic_operators_test.jl") end
@time @testset "Non-Periodic Operators" begin include("SBP_operators_test.jl") end
@time @testset "Dissipation Operators" begin include("dissipation_operators_test.jl") end
@time @testset "Variable Coefficient Operators" begin include("var_coef_operators_test.jl") end
@time @testset "Banded Matrices" begin include("banded_matrices_test.jl") end
@time @testset "Fourier Operators" begin include("fourier_operators_test.jl") end
@time @testset "Legendre Operators" begin include("legendre_operators_test.jl") end
@time @testset "Sum of Operators" begin include("sum_of_operators_test.jl") end
@time @testset "Conservation Laws" begin
    include("conservation_laws/burgers_test.jl")
    include("conservation_laws/cubic_test.jl")
    include("conservation_laws/variable_linear_advection_test.jl")
end
@time @testset "Second Order Equations" begin
    include("second_order_eqs/wave_eq_test.jl")
end


#=

    This is a port of the SPPL model two-dimensional-mixture-model.pynb

    The SPPL model returns the following probabilities for these
    conditions
                (((-4 < X) < 4) & ((1 < Y) < 2))
                (((-4 < X) < 4) & ((-2 < Y) < -1))
                (((-1 < X) < 1) & ((-1 <= Y) <= 1))
                (((-1 < X) < 1) & ((2 <= Y) < 6))
    prior:
    [0.09278584524638006, 0.04331568961023601, 0.06717622976807626, 0.023365316112895024]
    posterior
    [0.40939191677247666, 0.1911185175795767, 0.29639656127801434, 0.10309300436993234]

    They corresponds to t1..t4 in the Turing.jl model.

    This model:

    * Prior()
        Summary Statistics
        parameters      mean       std   naive_se      mcse          ess      rhat   ess_per_sec
            Symbol   Float64   Float64    Float64   Float64      Float64   Float64       Float64

                 X    0.0233    2.0143     0.0201    0.0200    9920.7405    1.0004    86267.3088
           cluster    1.3992    0.4898     0.0049    0.0048   10323.7943    0.9999    89772.1241
                 Y    4.7994    4.1470     0.0415    0.0409   10318.3909    0.9999    89725.1381
                t1    0.0917    0.2886     0.0029    0.0030    9980.2571    1.0000    86784.8442
                t2    0.0378    0.1907     0.0019    0.0018    9026.5068    0.9999    78491.3637
                t3    0.0716    0.2578     0.0026    0.0028    9501.1301    1.0002    82618.5228
                t4    0.0233    0.1509     0.0015    0.0014    9867.5527    0.9999    85804.8059

    * PG(5)
        Summary Statistics
        parameters      mean       std   naive_se      mcse         ess      rhat   ess_per_sec
            Symbol   Float64   Float64    Float64   Float64     Float64   Float64       Float64

                 X   -0.0303    1.4162     0.0142    0.0325   1748.1662    1.0006      142.2777
           cluster    1.9790    0.1434     0.0014    0.0034   1897.8769    1.0001      154.4622
                 Y   -0.0137    1.7171     0.0172    0.0405   1998.9606    1.0007      162.6891
                t1    0.4232    0.4941     0.0049    0.0111   1950.7745    0.9999      158.7674
                t2    0.1776    0.3822     0.0038    0.0093   1874.7156    1.0005      152.5772
                t3    0.2957    0.4564     0.0046    0.0100   1768.5983    1.0004      143.9406
                t4    0.1035    0.3046     0.0030    0.0068   1948.0961    1.0001      158.5494

=#

using Turing, StatsPlots, Distributions, StatsBase
using CSV
include("jl_utils.jl")

#=
# SPPL
X ~= norm(loc=0, scale=2)
Y ~= 0.6*norm(loc=8, scale=1) | 0.4*gamma(loc=-3, a=3)
=#
# It seems that 0.6 and 0.6 are the weights, i.e.
# the probability of each cluster
#
@model function two_dimensional_mixture_model()
    X ~ Normal(0, 2)

    # Probability of the clusters
    cluster ~ Categorical([0.6,0.4]) # [cluster1, cluster2]
    if cluster == 1
        Y ~ Normal(8, 1)
    else
        Y ~ Gamma(3) - 3
    end

    true ~ Dirac( ((-4 < X < 4) && (1 < Y^2 < 4))
                    ||
                  ((-1 < X < 1) && (-1.5 < Y < 6))
    )

    # The different conditions in the SPPL model
    t1 ~ Dirac((-4 < X < 4) && (-2 <  Y < -1))
    t2 ~ Dirac((-4 < X < 4) && ( 1 <  Y <  2))
    t3 ~ Dirac((-1 < X < 1) && (-1 <= Y <= 1))
    t4 ~ Dirac((-1 < X < 1) && ( 2 <= Y <  6))

    return [cluster,X,Y]

end

model = two_dimensional_mixture_model()

# chns = sample(model, Prior(), 10_000)
# chns = sample(model, MH(), 10_000)
chns = sample(model, PG(5),  10_000)
# chns = sample(model, PG(5),  MCMCThreads(), 10_000, 4)
# chns = sample(model, SMC(), 10_000)
# chns = sample(model, IS(), 10_000)

# chns = sample(model, HMC(0.1,6), 1_000)
# chns = sample(model, NUTS(), 1_000)

display(chns)
# display(plot(chns))

chains_params = Turing.MCMCChains.get_sections(chns, :parameters)
genq = generated_quantities(model, chains_params)
show_var_dist_pct(genq,20)


### A Pluto.jl notebook ###
# v0.12.20

using Markdown
using InteractiveUtils

# ╔═╡ fa0ed272-bccc-42a1-b8d5-23015f7a71e2
begin
	using Pkg
	Pkg.activate(mktempdir())
	Pkg.Registry.update()
	Pkg.add("Yao")
	Pkg.add("YaoPlots")
end

# ╔═╡ b978fdae-05ac-11eb-21b9-a584bead4705
using Yao, YaoPlots

# ╔═╡ efa8e4a8-05ab-11eb-2faa-bb86b93de1fb
md"# Assignment Sheet 1"

# ╔═╡ 1002aa7c-05ac-11eb-3b42-87809ac716c4
md" **_Assignment 1_:**
1. _Make the following circuit in Yao_.
2. _Make the qubits with state `` |0000〉 ``, and feed it to the circuit._ Hint: You can use either of ` zero_state(number of qubits) ` function or ` ArrayReg(bit\" \") ` function
3. _Measure the state of all the 4 qubits 1024 times._"

# ╔═╡ 98ceb0e4-05ac-11eb-3058-e59aaaf58e82
begin
	a1_1 = chain(4, put(1=>X), repeat(H, 2:4), control(2, 1=>X), control(4, 3=>X), control(3, 1=>X), control(4, 3=>X), repeat(H, 1:4))
plot(a1_1)
end

# ╔═╡ 25293028-05ad-11eb-1e99-7f91895810fb
begin
	a1circuit = chain(4) #Complete the circuit
	plot(a1circuit)
end

# ╔═╡ 6db30ade-05ae-11eb-2343-e1a2986b6b54
Matrix(a1circuit) == Matrix(a1_1) ? md"✅" : md"❌"

# ╔═╡ 7995ad56-05af-11eb-118a-c3ee4c34d824
fourzerostate = 0              #Remove that 0 and make the state "0000"

# ╔═╡ b816e74c-05b0-11eb-3f8c-09c2e70d3ac9
fourzerostate == zero_state(4) ? md"✅" : md"❌"

# ╔═╡ 16d5e24e-05b1-11eb-2352-8d897cc95f75
a1measuredstate = 0             #Remove the 0 and feed the qubits to the circuit you created, then measure them.

# ╔═╡ ce40470a-05c6-11eb-18b2-2996f097502f
let
	flag1 = sum(a1measuredstate .== bit"0000")
	flag2 = sum(a1measuredstate .== bit"1111")
	(flag1/10.24>38 && flag1/10.24<61) && (flag2/10.24>38 && flag2/10.24<61) && (flag1+flag2==1024) ? md"✅. The number of times 0000 comes as value after qubit's measurement is $(flag1/10.24) and number of times 1111 comes is $(flag2/10.24)" : md"❌"
end

# ╔═╡ 4c971cb6-0665-11eb-17b7-df3aa73ce750
md"**_Assignment 2:_** "

# ╔═╡ 7ec00838-0665-11eb-1d58-193cf8fcc24b
md"Did you know that you can use a circuit in another circuit, in Yao? What does that mean? Well..."

# ╔═╡ cc07db8c-0665-11eb-0d54-5b74156a06fc
#For example
let
	circuit1 = chain(2, put(1=>X), put(2=>Y))
	circuit2 = chain(4, put(1:2 => circuit1), put(3=>Z), put(4=>H))
	plot(circuit2)
end

# ╔═╡ 5cdf4a0c-0666-11eb-2ca9-c36074271ac2
md"1. _Make the Bell Circuit_
2. _Make the Reverse Bell Circuit_
3. _Make a circuit which takes two qubits and passes it through, first the Bell Circuit, and then the Reverse Bell Circuit. Use the two circuits you created above._
4. _Create the qubits_ `` |00〉, |01〉, |10〉 `` and `` |11〉 ``, _and pass them through the circuit you created in step 3. Measure each time you pass them through the circuit, 1024 times_."

# ╔═╡ 7ed55e02-0667-11eb-1570-212652d5fe18
begin
	bellcircuit = chain(2) #complete the bell circuit
	plot(bellcircuit)
end

# ╔═╡ 766552de-067d-11eb-34fb-bbc0fc471071
Matrix(bellcircuit) == Matrix(chain(2, put(1=>H), control(1,2=>X))) ? md"✅" : md"❌"

# ╔═╡ a59c3f42-0667-11eb-34e2-c9ab7e9080f2
begin
	reversebellcircuit = chain(2) #complete the reverse bell circuit
	plot(bellcircuit)
end

# ╔═╡ c2a9bb30-067d-11eb-0594-5d416dc1e763
Matrix(reversebellcircuit) == Matrix(chain(2, control(1,2=>X), put(1=>H))) ? md"✅" : md"❌"

# ╔═╡ b0bfd050-0667-11eb-2ed7-1b6d4ea15816
begin
	bell_and_reverse_bell_circuit = chain(2) #complete the circuit as stated in step 3
	plot(bell_and_reverse_bell_circuit)
end

# ╔═╡ 5d52aca4-067f-11eb-3174-a966da065d76
Matrix(bell_and_reverse_bell_circuit) == Matrix(chain(2, put(1=>H), control(1, 2=>H), control(1, 2=>H), put(1=>H))) ? md"✅" : md"❌"

# ╔═╡ d87106c6-067d-11eb-3beb-8bfea8d168a3
begin
	qubit00 = 0 #Remove the 0. Create qubits with state 00
	qubit01 = 0 #Remove the 0. Create qubits with state 01
	qubit10 = 0 #Remove the 0. Create qubits with state 10
	qubit11 = 0 #Remove the 0. Create qubits with state 11
end

# ╔═╡ d1019e06-067f-11eb-2d40-4d15fd34a149
(qubit00 == ArrayReg(bit"00")) && (qubit01 == ArrayReg(bit"10")) && (qubit10 == ArrayReg(bit"01")) && (qubit11 == ArrayReg(bit"11")) ? md"✅" : md"❌"

# ╔═╡ 1db39250-0681-11eb-30d5-2143291302de
md"Think of what the output will be, after measurement... Is the answer you're thinking of, same as the output you're getting after measurement?"

# ╔═╡ 4ccfcc26-067e-11eb-14c0-0fe93728efc2
begin
	measurement00 = 0 #Remove the 0. Feed the qubits 00, to the circuit created in step 3, and measure them 1000 times
	measurement01 = 0 #Remove the 0. Feed the qubits 01, to the circuit created in step 3, and measure them 1000 times
	measurement10 = 0 #Remove the 0. Feed the qubits 10, to the circuit created in step 3, and measure them 1000 times
	measurement11 = 0 #Remove the 0. Feed the qubits 11, to the circuit created in step 3, and measure them 1000 times
end

# ╔═╡ 479143ca-0680-11eb-13f2-6b4d089f2f37
(sum(measurement00 .== bit"00") + sum(measurement01 .== bit"10") + sum(measurement10 .== bit"01")  +  sum(measurement11 .== bit"11")) == 4000 ? md"✅" : md"❌"

# ╔═╡ c6efaa82-067e-11eb-0ad1-2518b517ea4e
md"_**Note:**_ If you're using ` ArrayReg(bit\" \") ` to create the qubits, the qubits are read by the circuit in reverse order. For example, if you enter ` ArrayReg(\"1101\") `, the circuit will take it as ` 1011 `. Also, the value of measurement is read in reverse order."

# ╔═╡ ff7b74ba-0680-11eb-2688-3fb96fc978c5
md"_**Assignment 3:**_"

# ╔═╡ 131c831a-0681-11eb-2bab-3d4cf5b23006
md"Suppose that Alice and Bob have 2 pairs of entangled qubits. Both the pairs are in the state $ \frac{|00> \;+\; |11>}{\sqrt2} $. Suppose Alice has an extra qubit, with completely random state."

# ╔═╡ c89d84b4-0681-11eb-3c22-4def72fc3477
md"1. _Make the circuit for quantum teleportation_
2. _Pass the first pair of Alice and Bob's entangled qubit, with Alice's extra qubit, into the quantum teleportation circuit. Then collapse the first two qubit's states by measuring them._ Hint: Use the ` measure!(RemoveMeasured(), input_qubits, [location_of_qubits]) ` function for this.
3. _Make the circuit for Superdense coding_
4. _Use the other pair of entangled qubits and superdense coding to convey the information Alice got from measuring her qubits from quantum teleportation circuit, to Bob._
5. _Use the above information to make Bob's qubit from first pair's state, to jump to Alice's extra qubit state._"

# ╔═╡ e59122aa-0687-11eb-02b0-a7f72525c5e7
begin
	Alice_and_Bobs_first_entangled_qubit = ArrayReg(bit"00") + ArrayReg(bit"11") |> normalize!
	Alice_and_Bobs_second_entangled_qubit = ArrayReg(bit"00") + ArrayReg(bit"11") |> normalize!
	Alices_extra_qubit = rand_state(1) |> normalize!
	state(Alices_extra_qubit)
end

# ╔═╡ b6670002-0688-11eb-3ecc-03476ce15431
begin
	quantumteleportationcircuit = chain(3) #Complete the circuit
	plot(quantumteleportationcircuit)
end

# ╔═╡ 458cf840-06e3-11eb-19fb-a5a4b0c70f0b
Matrix(quantumteleportationcircuit) == Matrix(chain(3, put(1=>H), control(1, 2=>X))) ? md"✅" : md"❌"

# ╔═╡ fc6e0da0-068c-11eb-1c31-e92bc7455530
begin
	input_to_teleportation_circuit = join(Alice_and_Bobs_first_entangled_qubit, Alices_extra_qubit)
	state(input_to_teleportation_circuit)
end

# ╔═╡ 88f1d456-06df-11eb-1827-912c481bb7a4
input = 0
#Remove the "0". Pass the above created qubit through the teleportation circuit, measuring the first two qubits using measure_remove!() function. Hint: qubit |> circuit |> r->measure_remove!(r, m:n)

# ╔═╡ 79280796-06e3-11eb-1e5c-b5cea995a88a
(input == bit"00" || input == bit"11" || input==bit"10" || input==bit"01") && (typeof(input) != Int64) ? md"✅" : md"❌"

# ╔═╡ e933ab46-06df-11eb-312a-6f2e934b059d
md"The above measurement will act as the information for superdense coding circuit."

# ╔═╡ 26cc4008-06e0-11eb-0cdf-51954af7254d
begin
	if(input==bit"00")
		superdense_coding_circuit = chain(2)
	elseif(input==bit"01")
		superdense_coding_circuit = chain(2)
	elseif(input==bit"10")
		superdense_coding_circuit = chain(2)
	elseif(input==bit"11")
		superdense_coding_circuit = chain(2)
	end
	#Remember the bits are read from right to left
	#Complete the circuits for Alice's qubit
	plot(superdense_coding_circuit)
end

# ╔═╡ dc0b86be-06e4-11eb-24db-4f56cb821e0f
begin
	if(input==bit"00")
		Matrix(superdense_coding_circuit) == Matrix(chain(2))
	elseif(input==bit"01")
		Matrix(superdense_coding_circuit) == Matrix(chain(2, put(1=>Z)))
	elseif(input==bit"10")
		Matrix(superdense_coding_circuit) == Matrix(chain(2, put(1=>X)))
	elseif(input==bit"11")
		Matrix(superdense_coding_circuit) == Matrix(chain(2, put(1=>Y)))
	end && typeof(input) != Int64 ? md"✅" : md"❌"
end

# ╔═╡ 28ccce76-06e1-11eb-1c70-b3d7779a062e
Bobs_part = ((Alice_and_Bobs_second_entangled_qubit |> superdense_coding_circuit) |> reversebellcircuit) |> r->measure(r, nshots=1000)

# ╔═╡ 2aaa0910-06e2-11eb-1d6c-0fae35763d48
#Bobs_part now contains the information Alice wanted to convey to Bob regarding his qubits. Use this information to make Bob's qubit's state jump to Alice's random qubit's state
begin
	if(Bobs_part[1]==bit"00")
		Bobs_qubit = input_to_teleportation_circuit |> chain(1)
	elseif(Bobs_part[1]==bit"01")
		Bobs_qubit = input_to_teleportation_circuit |> chain(1) #Complete the circuit
	elseif(Bobs_part[1]==bit"10")
		Bobs_qubit = input_to_teleportation_circuit |> chain(1) #Complete the circuit
	elseif(Bobs_part[1]==bit"11")
		Bobs_qubit = input_to_teleportation_circuit |> chain(1) #Complete the circuit
	end
	state(Bobs_qubit)
end

# ╔═╡ b1392da2-06e5-11eb-3632-7d96a1b81a24
begin
	log(2,length(state(input_to_teleportation_circuit))) == 2 && ((Bobs_part[1]==bit"00" && Bobs_qubit == (input_to_teleportation_circuit |> chain(1))) || (Bobs_part[1]==bit"01" && Bobs_qubit == (input_to_teleportation_circuit |> chain(1, put(1=>Z)))) || ( Bobs_part[1]==bit"10" && Bobs_qubit == (input_to_teleportation_circuit |> chain(1, put(1=>X))) ) || ( Bobs_part[1]==bit"11" && Bobs_qubit == (input_to_teleportation_circuit |> chain(1, put(1=>Y)))))  &&  (sum(Bobs_part .== Bobs_part[1]) == 1000) ? md"✅" : md"❌"
	#=elseif()
		Bobs_qubit ==
	elseif()
		Bobs_qubit ==
	elseif()
		Bobs_qubit == =#

end

# ╔═╡ 83df1840-06e7-11eb-2502-173dac5d4963
md"Seems to work... Although, wouldn't using the information from superdense coding make teleportation pointless."

# ╔═╡ Cell order:
# ╠═fa0ed272-bccc-42a1-b8d5-23015f7a71e2
# ╟─efa8e4a8-05ab-11eb-2faa-bb86b93de1fb
# ╠═b978fdae-05ac-11eb-21b9-a584bead4705
# ╟─1002aa7c-05ac-11eb-3b42-87809ac716c4
# ╟─98ceb0e4-05ac-11eb-3058-e59aaaf58e82
# ╠═25293028-05ad-11eb-1e99-7f91895810fb
# ╟─6db30ade-05ae-11eb-2343-e1a2986b6b54
# ╠═7995ad56-05af-11eb-118a-c3ee4c34d824
# ╟─b816e74c-05b0-11eb-3f8c-09c2e70d3ac9
# ╠═16d5e24e-05b1-11eb-2352-8d897cc95f75
# ╟─ce40470a-05c6-11eb-18b2-2996f097502f
# ╟─4c971cb6-0665-11eb-17b7-df3aa73ce750
# ╟─7ec00838-0665-11eb-1d58-193cf8fcc24b
# ╠═cc07db8c-0665-11eb-0d54-5b74156a06fc
# ╟─5cdf4a0c-0666-11eb-2ca9-c36074271ac2
# ╠═7ed55e02-0667-11eb-1570-212652d5fe18
# ╟─766552de-067d-11eb-34fb-bbc0fc471071
# ╠═a59c3f42-0667-11eb-34e2-c9ab7e9080f2
# ╟─c2a9bb30-067d-11eb-0594-5d416dc1e763
# ╠═b0bfd050-0667-11eb-2ed7-1b6d4ea15816
# ╟─5d52aca4-067f-11eb-3174-a966da065d76
# ╠═d87106c6-067d-11eb-3beb-8bfea8d168a3
# ╟─d1019e06-067f-11eb-2d40-4d15fd34a149
# ╟─1db39250-0681-11eb-30d5-2143291302de
# ╠═4ccfcc26-067e-11eb-14c0-0fe93728efc2
# ╟─479143ca-0680-11eb-13f2-6b4d089f2f37
# ╟─c6efaa82-067e-11eb-0ad1-2518b517ea4e
# ╟─ff7b74ba-0680-11eb-2688-3fb96fc978c5
# ╟─131c831a-0681-11eb-2bab-3d4cf5b23006
# ╟─c89d84b4-0681-11eb-3c22-4def72fc3477
# ╠═e59122aa-0687-11eb-02b0-a7f72525c5e7
# ╠═b6670002-0688-11eb-3ecc-03476ce15431
# ╟─458cf840-06e3-11eb-19fb-a5a4b0c70f0b
# ╠═fc6e0da0-068c-11eb-1c31-e92bc7455530
# ╠═88f1d456-06df-11eb-1827-912c481bb7a4
# ╟─79280796-06e3-11eb-1e5c-b5cea995a88a
# ╟─e933ab46-06df-11eb-312a-6f2e934b059d
# ╠═26cc4008-06e0-11eb-0cdf-51954af7254d
# ╟─dc0b86be-06e4-11eb-24db-4f56cb821e0f
# ╠═28ccce76-06e1-11eb-1c70-b3d7779a062e
# ╠═2aaa0910-06e2-11eb-1d6c-0fae35763d48
# ╟─b1392da2-06e5-11eb-3632-7d96a1b81a24
# ╟─83df1840-06e7-11eb-2502-173dac5d4963


using Test
using TightBindingLattice

using LinearAlgebra
using YAML

@testset "TranslationSymmetry" begin
    @testset "constructors" begin
        TranslationSymmetry(4*ones(Int, (1, 1)))
        TranslationSymmetry([4 0; 0 4])
        @test_throws ErrorException TranslationSymmetry([4 0 0; 0 4 0; 0 0 4])  # <- Temporary
        @test_throws ArgumentError TranslationSymmetry(OrthoCube([2 0; 0 2]), [2 0; 0 1])
    end

    @testset "properties" begin
        tsym = TranslationSymmetry([3 0; 0 1])
        mtab = [1 2 3; 2 3 1; 3 1 2]
        @test eltype(tsym) == TranslationOperation{Int}
        @test valtype(tsym) == TranslationOperation{Int}
        @test group(tsym) == FiniteGroup(mtab)
        @test group_order(tsym) == 3
        @test group_order(tsym, 1) == 1
        @test group_order(tsym, 2) == 3
        @test group_order(tsym, 3) == 3
        @test group_multiplication_table(tsym) == mtab
        @test element(tsym, 1) == TranslationOperation([0,0])
        @test element(tsym, 2) == TranslationOperation([1,0])
        @test element(tsym, 3) == TranslationOperation([2,0])
        @test elements(tsym) == TranslationOperation.([[0,0], [1,0], [2,0]])
        @test [element_name(tsym, i) for i in 1:3] == element_names(tsym)
        let ω = cis(2π/3)
            @test character_table(tsym) ≈ [1 1 1; 1 ω^2 ω; 1 ω ω^2]
        end
        @test length(irreps(tsym)) == 3
        @test num_irreps(tsym) == 3
        @test irrep(tsym, 1) == irreps(tsym)[1]
        @test irrep(tsym, 2) == irreps(tsym)[2]
        @test irrep(tsym, 3) == irreps(tsym)[3]
        @test all(irrep_dimension(tsym, i) == 1 for i in 1:3)
        @test generator_indices(tsym) == [2, 1]
        @test generator_elements(tsym) == [TranslationOperation([1,0]), TranslationOperation([0,0])]

        let n = lowercase(symmetry_name(tsym))
            @test occursin("translation", n)
            @test occursin("3", n)
            @test occursin("1", n)
            @test occursin("0", n)
        end
    end

    @testset "orthogonal lattice" begin
        tsym = TranslationSymmetry([3 0; 0 3])
        @test isabelian(tsym.group)

        @test length(tsym.generators) == 2
        idx_gen1 = tsym.generators[1]
        idx_gen2 = tsym.generators[2]

        @test group_order(tsym) == 9

        @testset "generators" begin
            # Generators completely generate the group
            @test generate_subgroup(tsym.group, tsym.generators) == BitSet(1:group_order(tsym))
            @test prod(tsym.group.period_lengths[x] for x in tsym.generators) == group_order(tsym) # since abelian group
        end

        @testset "elements" begin
            @test tsym.coordinates[idx_gen1] == [1, 0]
            @test tsym.coordinates[idx_gen2] == [0, 1]
            # elements ordered according to the "generator" (i.e. orthogonal order)
            @test tsym.element_names == [
                "[0, 0]", "[1, 0]", "[2, 0]",
                "[0, 1]", "[1, 1]", "[2, 1]",
                "[0, 2]", "[1, 2]", "[2, 2]",
            ]
            @test length(element_names(tsym)) == 9
            for i in 1:9
                @test element_names(tsym)[i] == element_name(tsym, i)
            end
        end

        @testset "multiplication table" begin
            mtab = zeros(Int, (9, 9))
            lookup = Dict(r => i for (i, r) in enumerate(tsym.coordinates))
            for (i, ri) in enumerate(tsym.coordinates)
                for (j, rj) in enumerate(tsym.coordinates)
                    _, ek = tsym.orthocube.wrap(ri + rj)
                    k = lookup[ek]
                    mtab[i,j] = k
                end # for j
            end # for i
            @test mtab == group_multiplication_table(tsym)
            @test mtab == group_multiplication_table(tsym.group)
        end

        @testset "coordinates" begin
            @test tsym.orthogonal_shape == [3, 3]
            @test tsym.orthogonal_coordinates == tsym.coordinates
            for cc in tsym.coordinates
                oc = tsym.coordinate_to_orthogonal_map[cc]
                cc2 = tsym.orthogonal_to_coordinate_map[oc]
                @test cc == cc2
            end

            for oc in tsym.orthogonal_coordinates
                cc = tsym.orthogonal_to_coordinate_map[oc]
                oc2 = tsym.coordinate_to_orthogonal_map[cc]
                @test oc == oc2
            end
        end

        @testset "symmetry_product" begin
            pro = symmetry_product(tsym)
            @test all(
                pro(x,y).displacement == tsym.orthocube.wrap(x.displacement + y.displacement)[2]
                for x in tsym, y in tsym
            )
        end

        @testset "iterate/membership" begin
            @test [x for x in tsym] == collect(elements(tsym))
            @test all(x ∈ tsym for x in tsym)
            @test 100 ∉ tsym
            @test IdentityOperation(Int, 2) ∈ tsym
            @test IdentityOperation(Int, 3) ∉ tsym
            @test TranslationOperation([100, 100]) ∈ tsym
            @test TranslationOperation([100, 100, 100]) ∉ tsym
            @test PointOperation([1 0; 0 1]) ∈ tsym
            @test PointOperation([0 1; 1 0]) ∉ tsym
            @test SpaceOperation([1 0; 0 1], [1, 0]) ∈ tsym
            @test SpaceOperation([0 1; 1 0], [1, 0]) ∉ tsym
        end

        @testset "irreps" begin
            @test tsym.conjugacy_classes == [[i] for (i, x) in enumerate(tsym.element_names)]
            @test size(character_table(tsym)) == (9, 9)
            @test length(irreps(tsym)) == 9
            @test num_irreps(tsym) == 9
            for i in 1:9
                @test irreps(tsym)[i] == irrep(tsym, i)
                @test irrep_dimension(tsym, i) == 1
            end
        end
    end # @testset "orthogonal lattice" begin

    @testset "non-orthogonal lattice" begin
        tsym = TranslationSymmetry([4 0; 0 3])

        @test length(tsym.generators) == 2
        # idx_gen = tsym.generators[1]
        @test tsym.coordinates[tsym.generators[1]] == [1, 0]
        @test tsym.coordinates[tsym.generators[2]] == [0, 1]
        # elements ordered according to the "generator" (i.e. orthogonal order)
        @test element_names(tsym) == [
            "[0, 0]", "[1, 0]", "[2, 0]", "[3, 0]",
            "[0, 1]", "[1, 1]", "[2, 1]", "[3, 1]",
            "[0, 2]", "[1, 2]", "[2, 2]", "[3, 2]",
        ]
        @test tsym.conjugacy_classes == [[i] for (i, x) in enumerate(tsym.element_names)]

        @test tsym.orthogonal_shape == [4,3]
        @test tsym.orthogonal_coordinates == [[0,0], [1,0], [2,0], [3,0], [0,1], [1,1], [2,1], [3,1], [0,2], [1,2], [2,2], [3,2]]
        for cc in tsym.coordinates
            oc = tsym.coordinate_to_orthogonal_map[cc]
            cc2 = tsym.orthogonal_to_coordinate_map[oc]
            @test cc == cc2
        end

        for oc in tsym.orthogonal_coordinates
            cc = tsym.orthogonal_to_coordinate_map[oc]
            oc2 = tsym.coordinate_to_orthogonal_map[cc]
            @test oc == oc2
        end
    end  # @testset "non-orthogonal lattice" begin

    @testset "lattice permutation" begin
        unitcell = make_unitcell([1.0 0.0; 0.0 1.0]; SiteType=String)
        addsite!(unitcell, "Ox", FractCoord([0,0], [0.5, 0.0]))
        addsite!(unitcell, "Oy", FractCoord([0,0], [0.0, 0.5]))
        lattice = make_lattice(unitcell, [4 0; 0 4])
        tsym = TranslationSymmetry(lattice)

        let
            lattice_failure = make_lattice(unitcell, [4 0; 0 3])
            #@test_throws ArgumentError get_site_permutations(lattice_failure, tsym)
            @test_throws ArgumentError embed(lattice_failure, tsym)
        end
        perms = [embed(lattice, op) for op in tsym.elements]
        #tsymbed = embed(lattice, tsym)
        @test length(perms) == 16
        @test perms[1].permutation.map == 1:32  # identity

        for (ir, r) in enumerate(lattice.bravais_coordinates)
            @test perms[ir].permutation.map == [
                let
                    ivec = [(i-1) % 4, (i-1) ÷ 4]
                    jvec = [(x + 16) % 4 for x in (ivec .+ r)]
                    j = jvec[1] + jvec[2]*4 + 1
                    2 * (j-1) + iorb
                end
                for i in 1:16 for iorb in 1:2
            ]
        end

        tsymbed = embed(lattice, tsym)
        @test_throws ArgumentError get_irrep_iterator(IrrepComponent(tsymbed, 1, 2))
        let phases = [cis(-2π * i / 4) for j in 0:3 for i in 0:3]
            tsic = IrrepComponent(tsymbed, 2, 1)
            irrep_list1 = collect(get_irrep_iterator(tsic))
            irrep_list2 = collect(zip(perms, phases))
            @test length(irrep_list1) == length(irrep_list2)
            for (x,y) in zip(irrep_list1, irrep_list2)
                @test x[1] == y[1]
                @test x[2] ≈ y[2]
            end
        end

        @test  isbragg(tsym.fractional_momenta[1], [1,0]) # Γ point
        @test !isbragg(tsym.fractional_momenta[2], [1,0]) # Γ point
        @test !isbragg(tsym.fractional_momenta[2], [[0,0], [1,0]])
    end # testset lattice permutation

    @testset "reduction" begin
        @test_throws DimensionMismatch isbragg([4, 0], [2,0,0], [2,0])
        @test_throws DimensionMismatch isbragg([4, 4], [2,0], [2,0,0])

        @test_throws ArgumentError isbragg([4, 0], [2,0], [2,0])
        @test_throws ArgumentError isbragg([-2, 2], [2,0], [2,0])

        tsym = TranslationSymmetry([3 0; 0 3])
        # Gamma point is always ok
        @test  isbragg([3,3], [0,0], [0,0])
        @test  isbragg([3,3], [0,0], [1,0])
        @test  isbragg([3,3], [0,0], [2,0])

        @test  isbragg(tsym, 1, TranslationOperation([0,0]))
        @test  isbragg(tsym, 1, TranslationOperation([1,0]))
        @test  isbragg(tsym, 1, TranslationOperation([2,0]))

        # non-zero momentum depends on what the identity translation is
        @test  isbragg([3,3], [1,0], [0,0]) # zero translation is always identity, so it's always fine
        @test !isbragg([3,3], [1,0], [1,0]) # these two translations are not compatible
        @test !isbragg([3,3], [1,0], [2,0]) #   with momentum [1,1]

        @test  isbragg(tsym, 2, TranslationOperation([0,0]))
        @test !isbragg(tsym, 2, TranslationOperation([1,0]))
        @test !isbragg(tsym, 2, TranslationOperation([2,0]))

        @test  isbragg([3,3], [0,0], [[0,0], [1,0], [2,0]])
        @test !isbragg([3,3], [1,0], [[0,0], [1,0], [2,0]])

        @test  isbragg(tsym, 1, TranslationOperation.([[0,0], [1,0], [2,0]]))
        @test !isbragg(tsym, 2, TranslationOperation.([[0,0], [1,0], [2,0]]))

        @test_throws DimensionMismatch isbragg([1//2, 2//4, 0//2], [2,0])

        # Gamma point is always ok
        @test  isbragg([0,0] .// [3,3], [0,0])
        @test  isbragg([0,0] .// [3,3], [1,0])
        @test  isbragg([0,0] .// [3,3], [2,0])

        # non-zero momentum depends on what the identity translation is
        @test  isbragg([1,0] .// [3,3], [0,0]) # zero translation is always identity, so it's always fine
        @test !isbragg([1,0] .// [3,3], [1,0]) # these two translations are not compatible
        @test !isbragg([1,0] .// [3,3], [2,0]) #   with momentum [1,1]

        @test  isbragg([0,0] .// [3,3], [[0,0], [1,0], [2,0]])
        @test !isbragg([1,0] .// [3,3], [[0,0], [1,0], [2,0]])

        tsym = TranslationSymmetry([4 0; 0 4])
        @test  isbragg(tsym, 11, TranslationOperation([2,0]))
        @test !isbragg(tsym, 10, TranslationOperation([2,0]))
        @test  isbragg(tsym, 11, TranslationOperation.([[0,0], [2,0], [0,2], [2,2]]))

        tsym = TranslationSymmetry([6 2; -2 6])

        # TODO more tests for isbragg

        for irrep_index in 1:num_irreps(tsym)
            k = tsym.fractional_momenta[irrep_index]
            k_ortho = tsym.orthogonal_coordinates[irrep_index] .// tsym.orthogonal_shape
            for (t, t_ortho) in zip(elements(tsym), tsym.orthogonal_coordinates)
                @test mod(k⋅t.displacement, 1) == mod(k_ortho⋅t_ortho, 1)
            end
        end
    end
end # @testset "TranslationSymmetry" begin

#
# @testset "translation" begin
#   @testset "constructor exceptions" begin
#     @test_throws ArgumentError TranslationGroup([Permutation([2,3,4,1]), Permutation([3,4,1,2])])
#     @test_throws ArgumentError TranslationGroup([Permutation([2,1,3,4]), Permutation([1,3,2,4])])
#   end
#
#   t1 = Permutation([2,3,1, 5,6,4])
#   t2 = Permutation([4,5,6, 1,2,3])
#   g = TranslationGroup([t1, t2])
#
#   @test g.generators == [t1, t2]
#   @test g.translations == [[0,0], [1,0], [2,0], [0,1], [1,1], [2,1]]
#   @test Set(g) == Set([t1^d1*t2^d2 for d1 in 0:2 for d2 in 0:1])
#   @test length(Set(g)) == 2*3
#   @test g.fractional_momenta == [[0//3, 0//2], [1//3, 0//2], [2//3, 0//2],
#                                  [0//3, 1//2], [1//3, 1//2], [2//3, 1//2]]
#   χ = [cis(-2π * (k ⋅ t)) for k in g.fractional_momenta, t in g.translations]
#   @test isapprox(g.character_table, χ; atol=Base.rtoldefault(Float64))
#
#   @test is_compatible([0//1, 0//1], [0,0])
#   @test !is_compatible([0//1, 1//2], [0,1])
#
#   @test is_compatible([0//1, 0//1], [[0,0]])
#   @test !is_compatible([0//1, 1//2], [[0,0], [0,1]])
# end
#
#
# @testset "non-orthogonal" begin
#   #=
#   . . . o .
#   o . . . .
#   . . . . o
#   . o . . .
#
#   2 3 4 1 5
#   1|5 6 7|2
#   7|2 3 4|1
#   4|1|5 6 7
#
#
#   =#
#   t1 = Permutation([5,3,4,1,6,7,2])
#   t2 = Permutation([2,5,6,7,3,4,1])
#   generators = [t1, t2]
#   #translation_group = TranslationGroup(t1, t2)
#   full_shape = [g.order for g in generators]
#   full_translations = vcat( collect( Iterators.product([0:g.order-1 for g in generators]...) )...)
#   full_translations = [ [x...] for x in full_translations]
#
#   elemtrans = Dict{Permutation, Vector{Int}}()
#   for (ig, dist) in enumerate(full_translations)
#     g = prod(gen^d for (gen, d) in zip(generators, dist))
#     if !haskey(elemtrans, g)
#       elemtrans[g] = [dist...]
#     end
#   end
#   elements = Permutation[]
#   translations = Vector{Int}[]
#   for (e, t) in elemtrans
#     push!(elements, e)
#     push!(translations, t)
#   end
#   idx = sortperm(translations)
#   translations = translations[idx]
#   elements = elements[idx]
# end


using ConicBenchmarkUtilities
using Mosek
using MathProgBase

# Usage: process_output.jl [--noconicsolve] RESULTFOLDERS... OUTPUT.csv

@assert length(ARGS) >= 2
NOCONIC = false
if ARGS[1] == "--noconicsolve"
    NOCONIC = true
    shift!(ARGS)
end
resultfiles = []
for i in 1:length(ARGS)-1
    append!(resultfiles,[joinpath(pwd(),ARGS[i],f) for f in readdir(joinpath(pwd(), ARGS[i]))])
end

fd = open(joinpath(pwd(), ARGS[end]), "w")

# Process into a CSV file with columns:
println(fd,"solver,instance,filename,sense,timelimit,solver_time,total_time,status,newstatus,objval,objbound,calc_objgap,objval_error,validator_status,validator_relobjdiff,iter_count,node_count,conic_count,conic_opt_count,conic_inf_count,relax_status,relax_time,conic_time,mip_time,int_viol,lin_viol,soc_viol,socrot_viol,exp_viol,psd_viol")

# From instance name to file name
function find_instance(name)
    instances = joinpath(dirname(@__FILE__),"..","cbfs")
    try
        match = "$name.*"
        f = readstring(`find $instances -name $match`)
        if length(split(f,"\n")) == 2
            return convert(String,chomp(f))
        end
    end
    return ""
end

function make_smat(svec::Vector{Float64})
    dim = Int(sqrt(1/4+2length(svec)) - 1/2)
    smat = Array{Float64}(dim,dim)
    k = 1

    for i in 1:dim, j in i:dim
        if i == j
            smat[i,j] = svec[k]
        else
            smat[i,j] = (1/sqrt(2))*svec[k]
        end
        k += 1
    end

    return smat
end

function violation_cone(subvec,cone)
    if cone == :Zero
        return :Linear, maximum(abs, subvec)
    elseif cone == :NonNeg
        return :Linear, -minimum(min.(subvec, 0.))
    elseif cone == :NonPos
        return :Linear, maximum(max.(subvec, 0.))
    elseif cone == :SOC
        return :SOC, max(0., vecnorm(subvec[2:end]) - subvec[1])
    elseif cone == :SOCRotated
        # (y,z,x) in RSOC <=> (sqrt2inv*(y+z),sqrt2inv*(-y+z),x) in SOC
        return :SOCRotated, max(0., sqrt(1/2*(subvec[1] - subvec[2])^2 + sum(abs2, subvec[3:end])) - 1/sqrt(2)*(subvec[1] + subvec[2]))
    elseif cone == :ExpPrimal
        return :Exp, max(subvec[2]*exp(subvec[1]/subvec[2]) - subvec[3], 0)
    elseif cone == :Free
        return :Linear, 0.0
    elseif cone == :SDP
        smat = make_smat(subvec)
        return :PSD, -min(eigmin(Symmetric(smat)), 0.0)
    else
        error("Unrecognized cone $cone")
    end
end

function compute_viols(dat, solution)
    c, A, b, con_cones, var_cones, vartypes, sense, objoffset = cbftompb(dat)

    if length(solution) > length(c)
        println("Solution is too long")
    end
    @assert length(solution) >= length(c)
    solution = solution[1:length(c)]
    objval = dot(c,solution)

    y = b - A*solution
    lin_viol = -Inf
    soc_viol = -Inf
    socrot_viol = -Inf
    exp_viol = -Inf
    psd_viol = -Inf
    for (cones,x) in [(var_cones,solution),(con_cones,y)]
        for (cone, idx) in cones
            t, viol = violation_cone(x[idx],cone)
            if t == :Linear
                lin_viol = max(lin_viol,viol)
            elseif t == :SOC
                soc_viol = max(soc_viol,viol)
            elseif t == :SOCRotated
                socrot_viol = max(socrot_viol,viol)
            elseif t == :Exp
                exp_viol = max(exp_viol,viol)
            elseif t == :PSD
                psd_viol = max(psd_viol,viol)
            end
        end
    end

    int_viol = 0.0
    for i in 1:length(vartypes)
        if vartypes[i] == :Int || vartypes[i] == :Bin
            int_viol = max(int_viol, abs(solution[i] - round(solution[i])))
        end
    end

    return objval, lin_viol, soc_viol, socrot_viol, exp_viol, psd_viol, int_viol
end

function validate_with_conic_solver(dat, solution)
    c, A, b, con_cones, var_cones, vartypes, sense, objoffset = cbftompb(dat)
    @assert objoffset == 0.0

    if sense == :Max
        c = -c
    end

    con_cones = copy(con_cones)
    I_eq = Int[]
    J_eq = Int[]
    b_eq = Float64[]
    intcount = 1
    for i in 1:length(vartypes)
        if vartypes[i] == :Int
            push!(I_eq,intcount)
            intcount += 1
            push!(J_eq,i)
            push!(b_eq, round(solution[i]))
        else
            @assert vartypes[i] == :Cont
        end
    end
    con_cones = vcat(con_cones, (:Zero,size(A,1)+1:size(A,1)+intcount-1))
    A = vcat(A, sparse(I_eq,J_eq,ones(length(I_eq)),length(I_eq),length(c)))
    b = vcat(b, b_eq)

    m = MathProgBase.ConicModel(MosekSolver(LOG=0))
    MathProgBase.loadproblem!(m, c, A, b, con_cones, var_cones)
    MathProgBase.optimize!(m)
    status = MathProgBase.status(m)
    if status == :Optimal
        objval = MathProgBase.getobjval(m)
        if sense == :Max
            objval *= -1
        end
    else
        objval = NaN
    end
    return status, objval
end

for (cnt, filename) in enumerate(resultfiles)
    if startswith(basename(filename), ".") || contains(filename,"META")
        continue
    end
    println("$cnt of $(length(resultfiles)): $(basename(filename))")

    solver = split(basename(filename),".")[1]
    # instance name may have '.' in it
    instance = join(split(basename(filename),".")[2:end-1],".")
    instance = string(instance,".cbf")

    timelimit = ""
    status = ""
    objval = Inf
    objbound = -Inf
    solver_time = ""
    total_time = ""
    node_count = ""
    lin_viol = ""
    soc_viol = ""
    socrot_viol = ""
    exp_viol = ""
    psd_viol = ""
    int_viol = ""
    conic_count = ""
    conic_opt_count = ""
    conic_inf_count = ""
    relax_status = ""
    conic_time = ""
    relax_time = ""
    mip_time = ""
    iter_count = ""
    solution = []

    for line in eachline(filename)
        if startswith(line, "#SOLVERNAME#")
            @assert solver == split(line)[2]
        elseif startswith(line, "#INSTANCE#")
            inst = split(line)[2]
            if endswith(inst, ".gz")
                @assert instance == inst[1:end-3]
            else
                @assert instance == inst
            end
        elseif startswith(line, "#TIMELIMIT#")
            timelimit = split(line)[2]
        elseif startswith(line, "#STATUS#")
            status = split(line)[2]
        elseif startswith(line, "#OBJVAL#")
            objval = parse(Float64,split(line)[2])
        elseif startswith(line, "#OBJBOUND#")
            objbound = parse(Float64,split(line)[2])
        elseif startswith(line, "#TIMESOLVER#")
            solver_time = split(line)[2]
        elseif startswith(line, "#TIMEALL#")
            total_time = split(line)[2]
        elseif startswith(line, "#NODECOUNT#")
            node_count = split(line)[2]
        elseif startswith(line, "#SOLUTION#")
            solutionvec = split(line)[2]
            if startswith(solutionvec,'[') && endswith(solutionvec,']') && !startswith(solutionvec, "[]")
                solution = [parse(Float64,x) for x in split(solutionvec[2:end-1],',')]
            end
        elseif startswith(line, " - Iterations           =")
            iter_count = split(line)[4]
        elseif startswith(line, " -- Conic subproblems   =")
            conic_count = split(line)[5]
        elseif startswith(line, " --- Optimal            =")
            conic_opt_count = split(line)[4]
        elseif startswith(line, " --- Infeasible         =")
            conic_inf_count = split(line)[4]
        elseif startswith(line, " - Relaxation status    =")
            relax_status = split(line)[5]
        elseif startswith(line, " -- Solve subproblems   =")
            conic_time = split(line)[5]
        elseif startswith(line, " -- Solve relaxation    =")
            relax_time = split(line)[5]
        elseif startswith(line, " -- Solve MIP models    =") || startswith(line, " -- MIP solver driving  =")
            mip_time = split(line)[6]
        end
    end

    instancefile = find_instance(instance)
    dat = readcbfdata(instancefile)
    sense = string(dat.sense)
    validator_status = ""
    validator_objval = ""
    validator_relobjdiff = ""
    objval_error = ""
    calc_objgap = ""

    # Decide status classification (more can later be excluded if objvals disagree)
    newstatus = ""

    if length(solution) > 0 && all(isfinite,solution)
        objval_sol, lin_viol, soc_viol, socrot_viol, exp_viol, psd_viol, int_viol = compute_viols(dat,solution)

        objval_error = abs(objval_sol - objval)/(abs(objval) + 1e-5)

        if startswith(solver, "BONMIN")
            if status == "Optimal"
                # Have to trust bonmin because cannot query objectivebound
                calc_objgap = 1e-5
            else
                calc_objgap = Inf
            end
        else
            calc_objgap = (objval_sol - objbound) / (abs(objval_sol) + 1e-5)
        end

        if !NOCONIC
            # use conic solver to calculate objval
            validator_status, validator_objval = validate_with_conic_solver(dat, solution)
            validator_relobjdiff = abs(objval_sol - validator_objval)/(abs(objval_sol) + 1e-5)
        end

        if status == "UserLimit" || status == "Suboptimal"
            if parse(Float64, solver_time) > 0.9*parse(Float64, timelimit)
                newstatus = "lim"
            else
                newstatus = "err"
            end
        elseif lin_viol > 1e-6 || soc_viol > 1e-5 || socrot_viol > 1e-5 || exp_viol > 1e-5 || psd_viol > 1e-4 || int_viol > 1e-6
            newstatus = "excl"
        elseif objval_error > 1e-8
            newstatus = "excl"
        elseif !NOCONIC && (validator_status == "Infeasible" || (isfinite(validator_relobjdiff) && validator_relobjdiff > 1e-5))
            newstatus = "excl"
        elseif status == "Optimal"
            if -1e-3 < calc_objgap < 1.05e-5
                newstatus = "conv"
            else
                newstatus = "excl"
            end
        end
    elseif status == "UserLimit"
        newstatus = "lim"
    end

    if status == "Infeasible"
        newstatus = "excl"
    elseif status == "KilledTime" || status == "KilledMemory"
        newstatus = "lim"
    elseif status == "" || status == "Error" || status == "FailedMIP" || status == "UnboundedOA" || status == "FailedOA"
        newstatus = "err"
    end

    if newstatus == ""
        error("new status not handled for $solver $instance with solver status $status")
    end

    println(fd,"$solver,$instance,$(basename(filename)),$sense,$timelimit,$solver_time,$total_time,$status,$newstatus,$objval,$objbound,$calc_objgap,$objval_error,$validator_status,$validator_relobjdiff,$iter_count,$node_count,$conic_count,$conic_opt_count,$conic_inf_count,$relax_status,$relax_time,$conic_time,$mip_time,$int_viol,$lin_viol,$soc_viol,$socrot_viol,$exp_viol,$psd_viol")
end

close(fd)


# Figure 5.3b in "Infinite GMRES for parameterized linear systems"
using SparseArrays
using LinearAlgebra
using PyPlot
using MAT
pygui(true)
include("iar.jl");
include("infgmres.jl");

# Problem represented with explicitly available derivatives
struct GeneralProblem
    sigma
    Aders
    Afun
    b
end

function compute_Mlincomb(prob::GeneralProblem,sigma,Y)
    if (prob.sigma != sigma)
        @show sigma
        @show prob.sigma
        error("Wrong sigma");
    end
    z=zeros(eltype(Y),size(Y,1));
    for i=1:size(Y,2)
        z += prob.Aders[i]*Y[:,i];
    end
    return z;
end
function compute_Mder(prob::GeneralProblem,mu)
    return prob.Afun(mu);
end

## Define problem
data=matread("delay_problem_matrices.mat")
A0=data["A0"];
A1=data["A1"];
b=vec(data["b"]);
n=length(b);

# Identity matrix
II=Matrix{Float64}(I,n,n);

Afun= s-> -s*II+A0+A1*exp(-s);
sigma=0;
N=100;
ders=Vector{Matrix{Float64}}(undef,N);

for i=1:N
    ders[i]=((-1)^(i+1))*A1;
end
ders[1] += A0;
ders[2] += -II;

myproblem=GeneralProblem(sigma,ders,Afun,b);
(f,hist1)=infgmres(myproblem,30,method=:iar);
max_it = 20;
v = range(-5,stop=-2,length=50);
mu_list = [10^i for i in v];
tol1 = 1.e-10;
obs = zeros(length(mu_list),1);
pred1 = zeros(length(mu_list),1);
r1 = 9.304668750449350; #This is taken from TDS Arnoldi

for j = 1:length(mu_list)
    global mu
    res = zeros(max_it,1);
    mu = mu_list[j];
    A_of_mu = myproblem.Afun(mu);

    for m=1:max_it
        global f
        (xx)=f(mu,m)
        res[m] = norm(A_of_mu*xx - myproblem.b)/norm(myproblem.b);
    end

    #Take the worst reducation in the residual
    ratio_list=zeros(max_it,1);
    end_m=0;
    for i=1:length(res)
        if res[i] < tol1
            end_m = i;
            break;
        end
    end
    for i=2:end_m
        ratio_list[i-1]=res[i]/res[i-1];
    end
    obs[j]=maximum(ratio_list);
    pred1[j]=mu*r1;

end

figure(1)
ax = PyPlot.gca()
ax.set_yscale("log")
ax.set_xscale("log")
xlabel("mu")
ylabel("rho")
plot(mu_list,obs,"o")
plot(mu_list,pred1)
legend(["Observed","Predicted"])


using Distributions: StatsBase
using LinearAlgebra: real
using Base: _maybetail

push!(LOAD_PATH, abspath(@__DIR__,".."))
using Pkg
pkg"activate ."

using Test
using LinearAlgebra,Statistics,StatsBase,Distributions
using Plots,NamedColors ; plotlyjs() ; theme(:dark)
using SparseArrays
using SpikingRNNs; const global S = SpikingRNNs
using BenchmarkTools
using FFTW
function onesparsemat(w::Real)
  mat=Matrix{Float64}(undef,1,1) ; mat[1,1]=w
  return sparse(mat)
end

##
# isolated, self-interacting , exp processes
# theory Vs numerical

myβ = 1.33
mywself = 0.7
myin = 3.7
tfake = NaN
p1 = S.PopulationHawkesExp(1,myβ)
ps1 = S.PSHawkes(p1)
conn1 = S.ConnectionHawkes(ps1,onesparsemat(mywself),ps1)
# rates to test
p1_in = S.PopInputStatic(ps1,[myin,])
myntw = S.RecurrentNetwork(tfake,(ps1,),(p1_in,),(conn1,) )

#
nspikes = 200_000
# initialize
S.hawkes_initialize!(myntw)

my_act = Vector{Tuple{Int64,Float64}}(undef,nspikes)
my_state = Vector{Float64}(undef,nspikes)
for k in 1:nspikes
  S.dynamics_step!(tfake,myntw)
  idx_fire = findfirst(ps1.isfiring)
  t_now = ps1.time_now[1]
  my_act[k] = (idx_fire,t_now)
  my_state[k] = ps1.state_now[1]
end

myspktimes = getindex.(my_act,2)
numrate = nspikes/myspktimes[end]
@info "Total duration $(round(myspktimes[end];digits=1)) s"
@info "Rate : $(round(numrate;digits=2)) Hz"

##
# now mean rate, covariance, etc

# numeric
# analytic from exponent
@show S.hawkes_exp_self_mean(myin,mywself,myβ)
# analytic given FFW of self-interaction kernel
# between eq 6 and eq 7 in Hawkes 1997
@show let mydt = 0.0001,
  myτmax = 50.0,
  ts = S.get_times(mydt,myτmax)
  gfou = fft(S.interaction_kernel.(ts,p1)) .* (mywself*mydt)
  myin/(1-real(gfou[1]))
end;
# or I can use the analytic transform in ω = 0. Samsies
ratefou = let gfou0 = mywself * S.fou_interaction_kernel.(0,p1)
  myin/(1-real(gfou0))
end

# this is to test analytic vs numeric fourier transform
# gfou = fft(mywself .* S.interaction_kernel.(mytaus,p1) ) .* mydt
# gfou2 = mywself .* S.fou_interaction_kernel.(myfreq,p1) |> ifftshift
##

# what about the covariance density
# First, compute it numerically for a reasonable time step
mydt = 0.2
myτmax = 30.0
mytaus = S.get_times(mydt,myτmax)
ntaus = length(mytaus)
cov_num = S.covariance_self_numerical(myspktimes,mydt,myτmax)
##
# now compute it analytically, at higher resolution

function four_high_res(dt::Real,Tmax::Real) # higher time resolution, longer time
  k=2
  myτmax = Tmax * k
  mytaus = S.get_times(dt,myτmax)
  nkeep = div(length(mytaus),k)
  myfreq = S.get_frequencies_centerzero(dt,myτmax)
  gfou = mywself .* S.fou_interaction_kernel.(myfreq,p1) |> ifftshift
  ffou = let r=ratefou
    covf(g) = r*(g+g'-g*g')/((1-g)*(1-g'))
    map(covf,gfou)
  end
  retf = real.(ifft(ffou)) ./ dt
  return mytaus[1:nkeep],retf[1:nkeep]
end

taush,covfou=four_high_res(0.1mydt,myτmax)

myplt = plot(mytaus[2:end], cov_num[2:end] ; linewidth=3, label="numerical" )
plot!(taush[1:end],covfou[1:end]; label="from Fourier",linewidth=3,linestyle=:dash)

##
# Hawkes 2D

myβ = 2.1
mywmat = [ 0.31   0.5
           0.8  0.15 ]
myin = [1.0,0.1]
tfake = NaN
p1 = S.PopulationHawkesExp(2,myβ)
ps1 = S.PSHawkes(p1)
conn1 = S.ConnectionHawkes(ps1,sparse(mywmat),ps1)
# rates to test
p1_in = S.PopInputStatic(ps1,myin)
myntw = S.RecurrentNetwork(tfake,(ps1,),(p1_in,),(conn1,) )

#
nspikes = 500_000
# initialize
S.hawkes_initialize!(myntw)

my_act = Vector{Tuple{Int64,Float64}}(undef,nspikes)
my_state1 = Vector{Float64}(undef,nspikes)
my_state2 = similar(my_state1)
for k in 1:nspikes
  S.dynamics_step!(tfake,myntw)
  idx_fire = findfirst(ps1.isfiring)
  t_now = ps1.time_now[1]
  my_act[k] = (idx_fire,t_now)
  my_state1[k] = ps1.state_now[1]
  my_state2[k] = ps1.state_now[2]
end

myspk1 = S.hawkes_get_spiketimes(1,my_act)
myspk2 = S.hawkes_get_spiketimes(2,my_act)
myspikes_both = [myspk1,myspk2]
myTmax = my_act[end][2]+eps()
@info "Total duration $(round(myTmax;digits=1)) s"
@info "Rates are $(round.([length(myspk1)/myTmax, length(myspk2)/myTmax];digits=2))"

# analytic rate from fourier Eq between 6 and  7 in Hawkes
ratefou = let G0 =  mywmat .* S.fou_interaction_kernel.(0,p1)
  inv(I-G0)*myin |> real
end

@info "With Fourier, rates are $(round.(ratefou;digits=2)) Hz"

##
# now mean rate, covariance, etc
# covariance numerical

mydt = 0.1
myτmax = 30.0
mytaus = S.get_times(mydt,myτmax)
ntaus = length(mytaus)
cov_num = S.covariance_density_numerical(myspikes_both,mydt,myτmax;verbose=true)

# plot numerical cov densities
plot(mytaus[2:end-1],cov_num[1,1,2:end-1] ; linewidth = 2)
plot!(mytaus[2:end-1],cov_num[1,2,2:end-1] ; linewidth = 2)
plot!(mytaus[2:end-1],cov_num[2,1,2:end-1] ; linewidth = 2)
plot!(mytaus[2:end-1],cov_num[2,2,2:end-1] ; linewidth = 2)

## now with fourier!
# Pick eq 12 from Hawkes
function four_high_res(dt::Real,Tmax::Real)
  k1 = 2
  k2 = 0.2
  myτmax,mydt = Tmax * k1, dt*k2
  mytaus = S.get_times(mydt,myτmax)
  nkeep = div(length(mytaus),k1)
  myfreq = S.get_frequencies_centerzero(mydt,myτmax)
  G_omega = map(mywmat) do w
    ifftshift( w .* S.fou_interaction_kernel.(myfreq,p1))
  end
  D = Diagonal(ratefou)
  M = Array{ComplexF64}(undef,2,2,length(myfreq))
  Mt = similar(M,Float64)
  for i in eachindex(myfreq)
    G = getindex.(G_omega,i)
    # M[:,:,i] = (I-G)\(G*D+D*G'-G*D*G')/(I-G')
    M[:,:,i] = (I-G)\D*(G+G'-G*G')/(I-G')
  end
  for i in 1:2,j in 1:2
    Mt[i,j,:] = real.(ifft(M[i,j,:])) ./ mydt
  end
  return mytaus[1:nkeep],Mt[:,:,1:nkeep]
end

# Pick eq 13 from Hawkes
# does not really work :-/
function four_high_res2(dt::Real,Tmax::Real)
  k=2
  myτmax = Tmax * k
  mytaus = S.get_times(dt,myτmax)
  nkeep = div(length(mytaus),k)
  myfreq = S.get_frequencies_centerzero(dt,myτmax)
  G_omega = map(mywmat) do w
    ifftshift( w .* S.fou_interaction_kernel.(myfreq,p1))
  end
  D = diagm(0=>ratefou)
  M = Array{ComplexF64}(undef,2,2,length(myfreq))
  for i in eachindex(myfreq)
    G = getindex.(G_omega,i)
    M[:,:,i] = (I-G)\D/(I-G')
  end
  Mt = similar(M,Float64)
  for i in 1:2,j in 1:2
    Mt[i,j,:] = real.(ifft(M[i,j,:])) ./ dt
  end
  return mytaus[1:nkeep],Mt[:,:,1:nkeep]
end

taush,Cfou=four_high_res(mydt,myτmax)

plot(mytaus[2:end],cov_num[1,1,2:end] ; linewidth = 3)
plot!(taush[2:end],Cfou[1,1,2:end]; linestyle=:dash, linewidth=3)

plot(mytaus[2:end],cov_num[2,2,2:end] ; linewidth = 3)
plot!(taush[2:end],Cfou[2,2,2:end]; linestyle=:dash, linewidth=3)

plot(mytaus[2:end],cov_num[1,2,2:end] ; linewidth = 3)
plot!(taush[3:end],Cfou[1,2,3:end]; linestyle=:dash, linewidth=3)

plot(mytaus[2:end],cov_num[2,1,2:end] ; linewidth = 3)
plot!(taush[3:end],Cfou[2,1,3:end]; linestyle=:dash, linewidth=3)


struct SimulatedAnnealing{Tn, Ttemp} <: RandomSearch
    neighbor::Tn
    temperature::Ttemp
    n_per_temperature::Int
end
Base.summary(::SimulatedAnnealing) = "Simulated Annealing"

"""
# SimulatedAnnealing
## Constructor
```julia
SimulatedAnnealing(; neighbor = default_neighbor, temperature = log_temperature)
```

The constructor takes two keywords:
* `neighbor = a(x_now, [x_candidate])`, a function that generates a new iterate based on the current
* `temperature = b(iteration)`, a function of the current iteration that returns a temperature

## Description
Simulated Annealing is a derivative free method for optimization. It is based on the
Metropolis-Hastings algorithm that was originally used to generate samples from a
thermodynamics system and is often used to generate draws from a posterior when doing
Bayesian inference. As such, it is a probabilistic method for finding the minimum of a
function, often over a quite large domains. For the historical reasons given above, the
algorithm uses terms such as cooling, temperature, and acceptance probabilities.
"""
SimulatedAnnealing(;
  neighbor = default_neighbor,
  temperature = log_temperature,
  n_per_temperature = 1) =
    SimulatedAnnealing(neighbor, temperature, n_per_temperature)

log_temperature(t) = 1 / log(t)^2

function default_neighbor(x_best)
  T = eltype(x_best)
  n = length(x_best)
  return x_best .+ T.(randn(n))
end

function solve(prob::OptimizationProblem, x0, method::SimulatedAnnealing, options::OptimizationOptions)
  T = eltype(x0)
  t0 = time()

  x_best = copy(x0)
  f_best = value(prob, x_best)
  x_now = copy(x0)
  f_now = f_best
  f0 = f_now
  temperature = f_best
  iter = 0
  is_converged = converged(method, f_now, options)
  while iter ≤ options.maxiter && !(is_converged)
    iter += 1
    # Determine the temperature for current iteration
    temperature = method.temperature(iter)

    for n_temp = 1:method.n_per_temperature
      # Randomly generate a neighbor of our current state
      x_candidate = method.neighbor(x_best)

      # Evaluate the cost function at the proposed state
      f_candidate = value(prob, x_candidate)

      if f_candidate <= f_now # this handles non-finite values as well
        # If proposal is superior, we always move to it
        x_now = copy(x_candidate)
        f_now = f_candidate

        # If the new state is the best state yet, keep a record of it
        if f_candidate < f_best
          x_best = copy(x_now)
          f_best = f_now
        end
      else
        # If proposal is inferior, we move to it with probability p
        p = exp(-(f_candidate - f_now) / temperature)
        if rand() <= p
          x_now = copy(x_candidate)
          f_now = f_candidate
        end
      end
    end # n_per_temperature
    is_converged = converged(method, f_now, options)
  end

  ConvergenceInfo(method, (solution=x_best, minimum=f_best, f_now=f_now, x_now=x_now, temperature=temperature, f0=f0, iter=iter, time=time()-t0), options)
end
function converged(method::SimulatedAnnealing, fz, options)
  f_converged = false
  f_converged = f_converged || fz ≤ options.f_limit
  f_converged = f_converged || isnan(fz)
end


using DataFrames
using CSV
using Lathe.preprocess:TrainTestSplit
using GLM
using StatsBase
using StatsModels
using ROC
using Plots

data = DataFrame(CSV.File("pacijenti1000a.csv"))

dataTrain, dataTest = TrainTestSplit(data, .80)

fm = @formula(bolest ~ visina + tezina + dbp + sbp)

logisticRegressor = glm(fm, dataTrain, Binomial(), ProbitLink())

dataPredictTest = predict(logisticRegressor, dataTest)

dataPredictTestClass = zeros(length(dataPredictTest))

for i in 1:length(dataPredictBoja)
    if dataPredictTest[i] < 0.5
        dataPredictTestClass[i] = 0
    else
        dataPredictTestClass[i] = 1
    end
end

brojObolelihOdDijabetesaPredict = sum(dataPredictTestClass)
brojObolelihOdDijabetesa = sum(dataTest.bolest)

brojZdravihPredict = length(dataPredictTestClass) - brojObolelihOdDijabetesaPredict
brojZdravih = length(data.bolest) - brojObolelihOdDijabetesa

println("Predvidjen broj obolelih: $brojObolelihOdDijabetesaPredict")
println("Broj osoba koje imaju dijabetes: $brojObolelihOdDijabetesa")

println("Predvidjen broj zdravih: $brojZdravihPredict")
println("Broj zdravih osoba: $brojZdravih")


using Thebes, Luxor

function makecube()

    cube = [
    Point3D(1,   1, -1),
    Point3D(1,  -1, -1),
    Point3D(-1, -1, -1),
    Point3D(-1,  1, -1),
    Point3D(1,   1,  1),
    Point3D(1,  -1,  1),
    Point3D(-1, -1,  1),
    Point3D(-1,  1,  1)]

    r = Point3D[]

    for e in [
        [1, 2], [2, 3], [3, 4], [4, 1],
        [5, 6], [6, 7], [7, 8], [8, 5],

        [1, 5], [5, 8], [8, 4], [4, 1],
        [1, 2], [2, 6], [6, 5], [5, 1],
        [3, 2], [2, 6], [6, 7], [7, 3],
        [4, 8], [8, 7], [7, 3], [3, 4],
        ]
        push!(r, cube[e][1])
        push!(r, cube[e][2])
    end
    return r
end

@draw begin
    setline(1.5)
    background("midnightblue")
    sethue("darkorange")
    eyepoint(Point3D(500, 500, 500))
    perspective(0)
    for θ in 0:π/48:4π
        p = Point3D(100cos(θ), 100sin(θ), 20θ)

        #=
        pin(p, gfunction = (p3, p2) -> begin
            d = distance(p3, eyepoint())
            circle(p2, rescale(d, 0, 300, 20, 5), :fill)
        end
        )
        =#

        #=
        pin(p, Point3D(50cos(θ), 50sin(θ), p.z), gfunction = (p3s, p2s) -> begin
            line(p2s..., :stroke)
        end)
        =#

        # pin(p, between(p, eyepoint(), 0.8))

        helix = [Point3D(100cos(θ), 100sin(θ), 20θ) for θ in 0:π/12:4π]
        eyepoint(Point3D(120, 120, 120))
        a_box =
        pin(helix,
        gfunction = (p3list, p2list) -> prettypoly(p2list, :stroke, () ->
        begin

            randomhue()
            circle(O, 2, :fill)
        end)
        )
    end

    axes3D()

    setline(0.5)

    #draw cubes, moving each one and scaling it
    for x in -200:40:200
        for y in -200:40:200
            randomhue()
            pin(moveby!(15makecube(), Point3D(x, y, 0)))
        end
    end

end


numbers,folds = split(read("input.txt", String), "\n\n")
re = r"(\w)=(\d+)"

xys = @. map(x -> parse(Int, x) + 1, Tuple(split($split(numbers, '\n'), ',')))
N = maximum(reduce(hcat, collect.(xys)))
w,h = maximum(map(t -> t[1], xys)), maximum(map(t -> t[2], xys))
field = falses((w,h))
field[CartesianIndex.(xys)] .= true

for fold = eachline(IOBuffer(folds))
    local w,h = size(field)
    axis,pos = match(re, fold)
    pos = parse(Int, pos) + 1
    if axis == "y"
        A,B = view(field, :, 1:pos-1), view(field, :, h:-1:(pos+1))
    elseif axis == "x"
        A,B = view(field, 1:pos-1, :), view(field, w:-1:(pos+1), :)
    end
    global field = A .| B
    println(sum(field))
    break
end


using Ipopt, MathProgBase
"""
    qreg(y,x,tau=0.5)

Estimate a quantile regression, quantile tau

"""
function qreg(y,x,q=0.5)
    # thanks to Paul Soderlind for suggesting this.
    # TBD: estimated standard errors.
    (T,K) = (size(x,1),size(x,2))
    c   = vcat(fill(q,T),fill(1-q,T),zeros(K))    #c'[u;v;bet]
    A   = hcat(Matrix(1.0I,T,T),-Matrix(1.0I,T,T),x)  #u[t] - v[t] + x[t,:]*b  = y[t]
    b   = copy(vec(y))
    lb  = vcat(zeros(T),zeros(T),fill(-Inf,K))        #u>=0,v>=0
    ub  = fill(Inf,2*T+K)
    Sol = linprog(c,A,'=',b,lb,ub,IpoptSolver(print_level=1))
    if Sol.status == :Optimal
    par = Sol.sol[2*T+1:end]          #extract regression coeffs
    else
    par = NaN
    end
    return par
end


using Test
using JToric
import JToric: Polymake

H5 = hirzebruch_surface( 5 )
P2 = JToric.projective_space( 2 )

@testset "Hirzebruch surface" begin
    @test JToric.isnormal( H5 ) == true
    @test JToric.isaffine( H5 ) == false
    @test JToric.isprojective( H5 ) == true
    @test JToric.issmooth( H5 ) == true
    @test JToric.iscomplete( H5 ) == true
    @test JToric.has_torusfactor( H5 ) == false
    @test JToric.is_orbifold( H5 ) == true
    @test JToric.issimplicial( H5 ) == true
    @test JToric.is_gorenstein( H5 ) == true
    @test JToric.is_q_gorenstein( H5 ) == true
    @test JToric.is_fano( H5 ) == false
    nef_cone( H5 )
    mori_cone( H5 )
    @test dim( H5 ) == 2
    @test dim_of_torusfactor( H5 ) == 0
    @test euler_characteristic( H5 ) == 4
    @test ith_betti_number( H5, 0 ) == 1
    @test ith_betti_number( H5, 1 ) == 0
    @test ith_betti_number( H5, 2 ) == 2
    @test ith_betti_number( H5, 3 ) == 0
    @test ith_betti_number( H5, 4 ) == 1
end

@testset "delPezzo surfaces" begin
    # Construct delPezzo surfaces
    @test_throws ArgumentError del_pezzo( -1 )
    del_pezzo( 0 )
    del_pezzo( 1 )
    del_pezzo( 2 )
    del_pezzo( 3 )
    @test_throws ArgumentError del_pezzo( 4 )
end

@testset "Projective space" begin
    # Perform tests for projective space
    @test JToric.isnormal( P2 ) == true
    @test JToric.isaffine( P2 ) == false
    @test JToric.isprojective( P2 ) == true
    @test JToric.issmooth( P2 ) == true
    @test JToric.iscomplete( P2 ) == true
    @test JToric.has_torusfactor( P2 ) == false
    @test JToric.is_orbifold( P2 ) == true
    @test JToric.issimplicial( P2 ) == true
    @test ith_betti_number( P2, 0 ) == 1
    @test ith_betti_number( P2, 1 ) == 0
    @test ith_betti_number( P2, 2 ) == 1
    @test ith_betti_number( P2, 3 ) == 0
    @test ith_betti_number( P2, 4 ) == 1
end

D=ToricDivisor( [ 0,0,0,0 ], H5 )

@testset "Divisors" begin
    # Compute properties of toric divisors on Hirzebruch surface
    @test JToric.iscartier( D ) == true
    @test JToric.isprincipal( D ) == true
    @test JToric.isbasepoint_free( D ) == true
    @test JToric.isample( D ) == false
    @test JToric.isvery_ample( D ) == false
    @test JToric.isnef( D ) == true
    @test JToric.isintegral( D ) == true
    @test JToric.isq_cartier( D ) == true
end

using Oscar

@testset "Polytopes of divisors" begin
    p = JToric.polyhedron_of_divisor( D )
    @test dim( p ) == 0
    @test ambient_dim( p ) == 2
end

@testset "Affine toric varieties" begin
    C = Oscar.positive_hull([1 1; -1 1])
    antv = AffineNormalToricVariety(C)
    JToric.show( antv )
    @test JToric.issmooth( antv ) == false
    @test JToric.is_orbifold( antv ) == true
    @test toric_ideal_binomial_generators( antv ) == [-1 -1 2]
    ntv = NormalToricVariety(C)
    @test JToric.isaffine( ntv ) == true
end

@testset "Toric varieties from polyhedral fans" begin
    square = Oscar.cube(2)
    nf = Oscar.normal_fan(square)
    ntv = NormalToricVariety(nf)
    @test JToric.iscomplete( ntv ) == true
    ntv2 = NormalToricVariety(square)
    @test JToric.iscomplete( ntv2 ) == true
end


# fg! -- objective function and gradient (avoiding recomputations)
# Hv! -- application of the Hessian
# smooth_fg! -- objective associated with the smooth part of the objective

# ----------------------- #
#  -- Ridge Regression -- #
# ----------------------- #
# ->  f(θ)  = |Xθ - y|₂²/2 + λ|θ|₂²/2
# -> ∇f(θ)  = X'(Xθ - y) + λθ
# -> ∇²f(θ) = X'X + λI
# NOTE:
# * Hv! used in iterative solution
# ---------------------------------------------------------

function Hv!(glr::GLR{L2Loss,<:L2R}, X, y, scratch)
    n, p = size(X)
    λ    = get_penalty_scale(glr, n)
    if glr.fit_intercept
        # H = [X 1]'[X 1] + λ I
        # rows a 1:p = [X'X + λI | X'1]
        # row  e end = [1'X      | n+λι] where ι is 1 if glr.penalize_intercept
        ι = float(glr.penalize_intercept)
        (Hv, v) -> begin
            # view on the first p rows
            a     = 1:p
            Hvₐ   = view(Hv, a)
            vₐ    = view(v,  a)
            Xt1   = view(scratch.p, a)
            Xt1 .*= 0
            @inbounds for i in a, j in 1:n
                Xt1[i] += X[j, i]           # -- X'1
            end
            vₑ  = v[end]
            # update for the first p rows   -- (X'X + λI)v[1:p] + (X'1)v[end]
            Xvₐ = scratch.n
            mul!(Xvₐ, X, vₐ)
            mul!(Hvₐ, X', Xvₐ)
            Hvₐ .+= λ .* vₐ .+ Xt1 .* vₑ
            # update for the last row       -- (X'1)'v + n v[end]
            Hv[end] = dot(Xt1, vₐ) + (n + λ_if_penalize_intercept(glr, λ)) * vₑ
        end
    else
        (Hv, v) -> begin
            Xv = scratch.n
            mul!(Xv, X, v)       # -- Xv
            mul!(Hv, X', Xv)     # -- X'Xv
            Hv .+= λ .* v        # -- X'Xv + λv
        end
    end
end

# ----------------------------- #
#  -- Lasso/Elnet Regression -- #
# ----------------------------- #
# ->  J(θ)  = f(θ) + r(θ)
# ->  f(θ)  = |Xθ - y|₂²/2 + λ|θ|₂²  // smooth
# ->  r(θ)  = γ|θ|₁                  // non-smooth with prox
# -> ∇f(θ)  = X'(Xθ - y) + λθ
# -> ∇²f(θ) = X'X + λI
# -> prox_r = soft-thresh
# ---------------------------------------------------------

function smooth_fg!(glr::GLR{L2Loss,<:ENR}, X, y, scratch)
    λ = get_penalty_scale_l2(glr, length(y))
    (g, θ) -> begin
        # cache contains the residuals (Xθ-y)
        r = scratch.n
        get_residuals!(r, X, θ, y) # -- r = Xθ-y
        apply_Xt!(g, X, r)
        g .+= λ .* θ
        glr.fit_intercept && (glr.penalize_intercept || (g[end] -= λ * θ[end]))
        return glr.loss(r) + get_l2(glr.penalty)(view_θ(glr, θ))
    end
end


using Revise
using AdFem
using LinearAlgebra

σ1 = rand(10, 3)
σ2 = zeros(10, 3)
ε1 = rand(10, 3)
ε2 = rand(10, 3)
μ = rand(10)
η = rand(10)
λ = rand(10)
Δt = rand()
for i = 1:10
    s = μ[i]*η[i]*Δt
    S = [
        1 + 2/3*s -1/3*s 0
        -1/3*s 1+2/3*s 0
        0 0 1+s
    ]
    S = inv(S)
    H = S * [
        2*μ[i]+λ[i] λ[i] 0
        λ[i] 2*μ[i]+λ[i] 0
        0 0 μ[i]
    ]
    σ2[i,:] = H * (ε2[i,:] - ε1[i,:]) + S*σ1[i,:]
end
σ22 = update_stress_viscosity(ε2, ε1, σ1, η, μ, λ, Δt)

@show norm(σ2 - σ22)


#!/usr/bin/julia

# Copyright (C) 2022 Matheus Fernandes Bigolin <mfrdrbigolin@disroot.org>
# SPDX-License-Identifier: MIT

"Day Twenty-One, Dirac Dice."

include("Utils.jl")

using .Utils

"All the possible rolls of the deterministic die."
const DET_DIE = @. mod((10:-1:1) - 4, 10)

"""Using the deterministic dice and provided the position, `(P₁, P₂)`, return
the product of the losing player’s score with the number of dice rolls."""
function solve1((P₁, P₂))
    S₁ = S₂ = 0
    rolled = 0

    while S₁ < 1000 && S₂ < 1000
        for i in eachindex(DET_DIE)
            if S₁ >= 1000 || S₂ >= 1000
                break
            end

            rolled += 1

            if !iseven(i)
                P₁ += DET_DIE[i]
                S₁ += mod1(P₁, 10)
            else
                P₂ += DET_DIE[i]
                S₂ += mod1(P₂, 10)
            end
        end
    end

    return min(S₁, S₂)*3rolled
end

"The distribution of the rolls of a dirac die in a single round."
const DIRAC_DIE = [3 => 1, 4 => 3, 5 => 6, 6 => 7, 7 => 6, 8 => 3, 9 => 1]

"Storage of game states."
GMS = Dict{Tuple{Tuple{Int, Int}, Tuple{Int, Int}}, Tuple{Int, Int}}()

"""Using the dirac dice and given the initial position, `(P₁, P₂)`, return the
number of victories of each player."""
function solve2((P₁, P₂), (S₁, S₂) = (0, 0))
    key = ((P₁, P₂), (S₁, S₂))

    if S₁ >= 21
        return (1, 0)
    elseif S₂ >= 21
        return (0, 1)
    elseif haskey(GMS, key)
        return GMS[key]
    end

    vic = (0, 0)

    for (roll, n) in DIRAC_DIE
        P_new = mod1(P₁ + roll, 10)
        S_new = S₁ + P_new

        v₂, v₁ = solve2((P₂, P_new), (S₂, S_new)) .* n
        vic = vic .+ (v₁, v₂)
    end

    GMS[key] = vic
    return vic
end

const PLAYER_REX = r"Player \d starting position: (\d+)"

function main()
    usage_and_exit(length(ARGS) != 1)

    data = readlines(first(ARGS))
    players = [parse(Int, pl.captures[1]) for pl in match.(PLAYER_REX, data)]

    println(solve1(players))
    println(first(solve2(players)))
end

if abspath(PROGRAM_FILE) == @__FILE__
    main()
end


module Day03

using Chain
using Statistics

import ..data_dir # from parent module

export part1, part2

const data_file = joinpath(data_dir, "day03")
const input = readlines(data_file) # read into an array, each line is an element

# Shared code between parts 1 and 2

function binary_string_to_bool_array(binary_string::String)
    parse.(Bool, split(binary_string, ""))
end

bitstring_to_int(x::String) = parse(Int, "0b" * x)

# Part 1

function part1(input = input)
    gamma_bitstring = @chain input begin
        map(x -> binary_string_to_bool_array(x), _) # take input as array of arrays
        transpose(hcat(_...)) # convert to matrix and transpose to rows * columns
        mean(_, dims = 1) # take column-wise means
        map(x -> round(Int, x, RoundNearestTiesUp), _) # round and convert to integers
        join(_[1, :], "") # extract first (and only) row and join to a string
    end

    gamma = bitstring_to_int(gamma_bitstring)
    # Equivalent to flipping the least significant bits
    epsilon_bitstring = last(bitstring(~gamma), length(gamma_bitstring))
    epsilon = bitstring_to_int(epsilon_bitstring)

    gamma * epsilon
end

# Part 2

function gas_rate(input::Vector{String}, rounding_function::Function)
    input_arrays = binary_string_to_bool_array.(input)

    gas_matrix = transpose(hcat(input_arrays...))
    column = 1
    while size(gas_matrix)[1] > 1
        average_value = mean(gas_matrix[:, column])
        target_bit = rounding_function(average_value)
        row_indices_to_keep = filter(i -> gas_matrix[i, column] == target_bit, 1:size(gas_matrix)[1])
        gas_matrix = gas_matrix[row_indices_to_keep, :]
        column += 1
    end

    # extract first (and only) row and join to a string
    remaining_row = Int.(gas_matrix[1, :])
    gas_bitstring = join(remaining_row, "")
    bitstring_to_int(gas_bitstring)
end

function part2(input = input)
    oxygen = gas_rate(
        input,
        x -> round(Int, x, RoundNearestTiesUp)
    )

    co2 = gas_rate(
        input,
        x -> round(Int, (1 - x), RoundNearest) # will round down
    )

    oxygen * co2
end

end # module


"""
Graphically represents a MixedDistribution structure, use the keyword func = :cdf
to represent the CDF instead of PDF
"""
function RecipesBase.plot(md::MixedDistribution; func = :pdf, fillalpha = 0.4, xlow = -Inf64, xhigh = Inf64)
    if func == :cdf
        return plot_cdf(md, xlow, xhigh)
    end
    p = RecipesBase.plot()
    for (wi,di) in zip(md.cont_weights, md.cont_dists)
        wf = x -> wi * Dst.pdf(di,x)
        m = mean(di)
        s = std(di)
        xaxis = max(m-5.0*s,minimum(di), xlow):0.01:min(m+5.0*s,maximum(di), xhigh) |> collect
        RecipesBase.plot!(p, xaxis, wf.(xaxis), fillalpha = fillalpha, fillcolor = :match, fillrange = 0)
    end

    for (xi,pi) in zip(md.mass_points,md.mass_probs)
        RecipesBase.plot!(p, [xi, xi], [0.0, pi], line = :arrow, color = :blue)
    end
    return p
end

function plot_cdf(md::MixedDistribution, xlow, xhigh; shallow = false)
    xlow_trunc  = max(xlow,  mean(md)-5.0*std(md))
    xhigh_trunc = min(xhigh, mean(md)+5.0*std(md))
    xaxis = collect(minimum(md.mass_points):0.01:maximum(md.mass_points))
    for itr in (max(minimum(md),xlow_trunc):0.01:first(md.mass_points),last(md.mass_points):0.01:min(maximum(md),xhigh_trunc))
        append!(xaxis, itr)
    end
    sort!(xaxis)
    cdf = x -> Dst.cdf(md, x)
    return RecipesBase.plot(xaxis, cdf.(xaxis))
end


# Create example data
x = randn(100_000)
y = randn(100_000)
# Draw the hexbin plot
hexbin(x, y)


__precompile__(true)

"""
Module `Approximations.jl` -- polygonal approximation of convex sets through
support vectors.
"""
module Approximations

using LazySets, Requires, LinearAlgebra, SparseArrays

export approximate,
       ballinf_approximation,
       box_approximation, interval_hull,
       decompose,
       overapproximate,
       box_approximation_symmetric, symmetric_interval_hull,
       BoxDirections,
       BoxDiagDirections,
       OctDirections,
       PolarDirections,
       SphericalDirections

const TOL(N::Type{Float64}) = eps(N)
const TOL(N::Type{Float32}) = eps(N)
const TOL(N::Type{Rational{INNER}}) where {INNER} = zero(N)

const DIR_EAST(N) = [one(N), zero(N)]
const DIR_NORTH(N) = [zero(N), one(N)]
const DIR_WEST(N) = [-one(N), zero(N)]
const DIR_SOUTH(N) = [zero(N), -one(N)]

include("iterative_refinement.jl")
include("box_approximations.jl")
include("template_directions.jl")
include("overapproximate.jl")
include("decompositions.jl")

end # module


# Uniform distribution

export Uniform

@parameterized Uniform() ≪ Lebesgue(𝕀)
@kwstruct Uniform()

# Standard Uniform

distproxy(::Uniform{()}) = Dists.Uniform()

logdensity(d::Uniform{()}, x) = 0.0

TV.as(::Uniform) = as𝕀

Base.rand(rng::AbstractRNG, T::Type, μ::Uniform{()}) = rand(rng, T)


# Use modeling and optimizer package
using JuMP
using GLPK

# Define the model
model = Model(GLPK.Optimizer)
@variable(model, x >= 0)
@variable(model, y >= 0)
@constraint(model, 6x + 8y >= 100)
@constraint(model, 7x + 12y >= 120)
@objective(model, Min, 12x + 20y)

# Optimize the model
optimize!(model)

# Show results
@show value(x);
@show value(y);
@show objective_value(model);


"""
RSA encryption

message: message to encrypt
key: public key to use for encryption
"""
function encrypt!(message::String, key::Tuple{BigInt, BigInt})
    e, n = key

    message = process_string!(message)
    CipherText(exp!(message, e, n), 1)
end

function encrypt!(message::Any, key::Tuple{BigInt, BigInt})
    e, n = key

    CipherText(exp!(message, e, n), 0)
end

"""RSA Decryption

ciphertext: Ciphertext object to decrypt
"""
function decrypt!(system::RSASystem, ciphertext::CipherText)
    d, n = system.private_key

    if (ciphertext.is_str == true)
        return (recover_string!(exp!(ciphertext.data, d, n)))
    end
    exp!(ciphertext.data, d, n)
end


using Plots, ApproxFun

x=Fun()
u0=0.x

N=u->[u(-1.),u(1.),0.001u''+6*(1-x^2)*u'+u^2-1.]
u=newton(N,u0)
plot(u)

examples/Newton iteration for Nonlinear BVPs.jl


"""
    $(TYPEDEF)
Kerr spacetime with spin `spin`. We assume that we are in unitless scales where
the black hole mass defines the length, so `M=1`.
"""
struct Kerr{S}
    spin::S
end

"""
    $(TYPEDEF)
For a spacetime `g` construct metric tensor at position `p`.
There is an optional cache that can be used to cache intermediate results for increased
speed.
"""

@inline function _Δ(g::Kerr, r)
    return r^2 - 2*r + g.spin^2
end

@inline function _Σ(g::Kerr, r, θ)
    return r^2 + (g.spin*cos(θ))^2
end

@inline function _Ξ(g::Kerr, r, θ, Δ)
    return (r^2 + g.spin^2)^2 - Δ*(g.spin*sin(θ))^2
end

@inline function _ωZamo(g::Kerr, r, Ξ)
    return 2*g.spin*r/Ξ
end
struct MetricTensor{M, P, C}
    g::M
    p::P
    cache::C
end

@inline function _ℛ(g, r, λ, η, Δ)
    a = g.spin
    return (r^2 + a^2 - a*λ)^2 - Δ*(η + (a-λ)^2)
end

"""
    $(SIGNATURES)
Construct the metric tensor for spacetime `g` at the location `t`, `r`, `θ`, `ϕ`.

Returns a `MetricTensor` object. To access the components of the tensor please
the components function.
"""
function metric(g::Kerr, t, r, θ, ϕ)
    Δ = _Δ(g, r)
    Σ = _Σ(g, r, θ)
    Ξ = _Ξ(g, r, θ,  Δ)
    Ξsin2 = Ξ*sin(θ)^2
    ω = _ωZamo(g, r, Ξ,)
    cache = (;Δ, Σ, Ξ, Ξsin2, ω)
    return MetricTensor(g, (t,r,θ,ϕ), cache)
end

"""
    $(SIGNATURES)
Returns the components of the metric tensor `g` in SMatrix.
"""
function components(g::MetricTensor{K, C}) where {K<:Kerr, C}
    Δ, Σ, Ξ, Ξsin2, ω = g.cache
    gtt = -Δ*Σ/Ξ + ω^2*Ξsin2/Σ
    gtϕ = -ω*Ξsin2/Σ
    grr = Σ/Δ
    gθθ = Σ
    gϕϕ = Ξsin2/Σ
    T = eltype(gtt)
    return @SMatrix [ gtt     zero(T) zero(T) gtϕ    ;
                      zero(T) grr     zero(T) zero(T);
                      zero(T) zero(T) gθθ     zero(T);
                      gtϕ     zero(T) zero(T) gϕϕ
                    ]
end

"""
    $(SIGNATURES)
Computes the radial potential of the metric tensor assuming the energy scaled angular momentum
`λ`` and carter constant `η``
"""
function ℛ(g::MetricTensor, λ, η)
    r = g.p[2]
    Δ = g.cache.Δ
    return _ℛ(g.g, r, λ, η, Δ)
end

struct Minkowski{T} end
Minkowski() = Minkowski{Float64}()
metric(m::Minkowski, t, r, θ, ϕ) = MetricTensor(m, (t,r,θ,ϕ), (;))
components(::MetricTensor{<:Minkowski{T}, P,C}) where {T,P,C} = Diagonal(SVector(-one(T), one(T), one(T), one(T)))


"""
    A = JopDerivative(R::JetSpace[; dim=1, accuracy=4, delta=1.0)

`A*m` is the centered finite different approximation to the derivative of `m`
along dimension `dim`.  The `accuracy` of the approximation is either `4` for
a 4th order accurate estimate of the derivative, or `8` for an 8th order accurate
derivative.
"""
function JopDerivative(sp::JetSpace{T}; dim=1, accuracy=4, delta=1.0) where {T}
    local a
    if accuracy == 4
        a = T[1/12, -2/3, 0, 2/3, -1/12]
    elseif accuracy == 8
        a = T[1/280, -4/105, 1/5, -4/5, 0, 4/5, -1/5, 4/105, -1/280]
    else
        error("order of accuracy must be either 4 or 8, got accuracy=$(accuracy)")
    end
    a ./= delta
    JopLn(df! = JopDerivative_df!, df′! = JopDerivative_df′!, dom = sp, rng = sp, s = (a=a,dim=dim))
end
export JopDerivative

JopDerivative_df!(d::AbstractArray, m::AbstractArray; a, dim, kwargs...) = derivative_helper(d, m, a, dim)
JopDerivative_df′!(m::AbstractArray, d::AbstractArray; a, dim, kwargs...) = derivative_helper(m, d, -a, dim)

function derivative_helper_dim(d, m, a, n, Rpre, Rpost)
    d .= 0
    M = div(length(a)-1,2)
    for Ipost in Rpost
        for i = (M+1):(n-M)
            for j = 1:length(a)
                k = i-M+j-1
                if M < k <= n-M
                    for Ipre in Rpre
                        d[Ipre, i, Ipost] += a[j] * m[Ipre, k, Ipost]
                    end
                end
            end
        end
    end
    d
end
function derivative_helper(d::AbstractArray, m::AbstractArray, a::Vector, dim::Integer)
    Rpre = CartesianIndices(size(m)[1:dim-1])
    Rpost = CartesianIndices(size(m)[dim+1:end])
    n = size(m, dim)
    derivative_helper_dim(d, m, a, n, Rpre, Rpost)
end