using QuasiCopula, LinearAlgebra, Random, GLM
using DataFrames, Statistics
using BenchmarkTools, Test

BLAS.set_num_threads(1)
Threads.nthreads()

p = 3   # number of fixed effects, including intercept
m = 1    # number of variance components
# true parameter values
Random.seed!(12345)
βtrue = rand(Uniform(-2, 2), p)
θtrue = [0.5]
τtrue = 100.0
σ2 = inv(τtrue)
σ = sqrt(σ2)

#simulation parameters
samplesize = 10000
ni = 25

# true Gamma
Γ = θtrue[1] * ones(ni, ni)

T = Float64
gcs = Vector{GaussianCopulaVCObs{T}}(undef, samplesize)

# for reproducibility I will simulate all the design matrices here
Random.seed!(12345)
X_samplesize = [randn(ni, p - 1) for i in 1:samplesize]

for i in 1:samplesize
    X = [ones(ni) X_samplesize[i]]
    μ = X * βtrue
    vecd = Vector{ContinuousUnivariateDistribution}(undef, ni)
    for i in 1:ni
        vecd[i] = Normal(μ[i], σ)
    end
    nonmixed_multivariate_dist = NonMixedMultivariateDistribution(vecd, Γ)
    # simuate single vector y
    y = Vector{Float64}(undef, ni)
    res = Vector{Float64}(undef, ni)
    rand(nonmixed_multivariate_dist, y, res)
    V = [ones(ni, ni)]
    gcs[i] = GaussianCopulaVCObs(y, X, V)
end

# form VarLmmModel
gcm = GaussianCopulaVCModel(gcs)
# precompile
println("precompiling Gaussian VCM fit")
gcm2 = deepcopy(gcm);
QuasiCopula.fit!(gcm2, IpoptSolver(print_level = 0, max_iter = 20));

fittime = @elapsed QuasiCopula.fit!(gcm, IpoptSolver(print_level = 5, max_iter = 100, limited_memory_max_history = 50, accept_after_max_steps = 4, tol = 10^-8, hessian_approximation = "limited-memory"))
@show fittime
@show gcm.β
@show gcm.θ
@show gcm.τ
@show gcm.∇β
@show gcm.∇θ
@show gcm.∇τ

loglikelihood!(gcm, true, true)
vcov!(gcm)
@show QuasiCopula.confint(gcm)

mseβ, mseτ, mseθ = MSE(gcm, βtrue, τtrue[1], θtrue)
@show mseβ
@show mseτ
@show mseθ

@test mseβ < 0.01
@test mseθ < 0.01
@test mseτ < 0.01


#=
Visual illustration of the central limit theorem in 3d

@author : Spencer Lyon <spencer.lyon@nyu.edu>

References
----------
Based off the original python file clt3d.py
=#
using PyPlot
using Distributions
using KernelDensity

beta_dist = Beta(2.0, 2.0)

function gen_x_draws(k)
    bdraws = rand(beta_dist, (3, k))

    # == Transform rows, so each represents a different distribution == #
    bdraws[1, :] -= 0.5
    bdraws[2, :] += 0.6
    bdraws[3, :] -= 1.1

    # == Set X[i] = bdraws[j, i], where j is a random draw from {1, 2, 3} == #
    js = rand(1:3, k)
    X = Array(Float64, k)
    for i=1:k
        X[i]=  bdraws[js[i], i]
    end

    # == Rescale, so that the random variable is zero mean == #
    m, sigma = mean(X), std(X)
    return (X .- m) ./ sigma
end

nmax = 5
reps = 100000
ns = 1:nmax

# == Form a matrix Z such that each column is reps independent draws of X == #
Z = Array(Float64, reps, nmax)
for i=ns
    Z[:, i] = gen_x_draws(reps)
end

# == Take cumulative sum across columns
S = cumsum(Z, 2)

# == Multiply j-th column by sqrt j == #
Y = S .* (1. ./ sqrt(ns))'

# == Plot == #
fig = figure()
ax = fig[:gca](projection="3d")

a, b = -3, 3
gs = 100
xs = linspace(a, b, gs)

# == Build verts == #
greys = linspace(0.3, 0.7, nmax)
verts = {}
for n=ns
    kde_est = kde(Y[:, n])
    _xs, ys = kde_est.x, kde_est.density
    ax[:plot3D](_xs, ys, n, zdir="x", color=string(greys[n]),
                fillstyle="bottom")
end

ax[:set_xlim3d](1, nmax)
ax[:set_xticks](ns)
ax[:set_xlabel]("n")
ax[:set_yticks]((-3, 0, 3))
ax[:set_ylim3d](a, b)
ax[:set_zlim3d](0, 0.4)
ax[:set_zticks]((0.2, 0.4))


#--------------------------------------------------------------------
# Spacetime Discretization methods in Julia
# Soham 01-2018
#--------------------------------------------------------------------

function testrestrictmodes1D(fn::Function, Nx::Int, M::Int)::Float64
    fpatch  = Float64[fn(x) for x in chebgrid(Nx)]
    vndmx   = vandermonde(Nx)
    frpatch = vndmx*restrictmodes!(inv(vndmx)*fpatch, M)
    w       = chebweights(Nx)
    L2error = sqrt(w'*(fpatch - frpatch).^2)
    return L2error
end

function testrestrictmodes2D(fn::Function, Nx::Int, Ny::Int, M::Int, N::Int)::Float64
    fpatch  = Float64[fn(x,y) for x in chebgrid(Nx), y in chebgrid(Ny)]
    vndmx   = vandermonde(Nx)
    vndmy   = vandermonde(Ny)
    frpatch = vndmx*restrictmodes!(inv(vndmx)*fpatch*inv(vndmy)', M, N)*vndmy'
    return L2norm(fpatch, frpatch, chebweights(Nx), chebweights(Ny))
end

function testprolongatemodes1D(fn::Function, Nx::Int, M::Int)::Float64
    fpatch  = Float64[fn(x) for x in chebgrid(Nx)]
    vndmx   = vandermonde(Nx)
    vndmMx  = pseudovandermonde(M, chebgrid(Nx))
    fppatch = vndmMx*prolongatemodes(inv(vndmx)*fpatch, M)
    w       = chebweights(Nx)
    return  sqrt(w'*(fpatch - fppatch).^2)
end

function testprolongatemodes2D(fn::Function, Nx::Int, Ny::Int, M::Int, N::Int)::Float64
    fpatch  = Float64[fn(x,y) for x in chebgrid(Nx), y in chebgrid(Ny)]
    vndmx   = vandermonde(Nx)
    vndmy   = vandermonde(Ny)
    vndmMx  = pseudovandermonde(M, chebgrid(Nx))
    vndmNy  = pseudovandermonde(N, chebgrid(Ny))
    fppatch = vndmMx*prolongatemodes(inv(vndmx)*fpatch*inv(vndmy)', M, N)*vndmNy'
    return L2norm(fpatch, fppatch, chebweights(Nx), chebweights(Ny))
end

function testprolongate1Dx(fn::Function, Nx::Int, M::Int)::Float64
    patch1D = Float64[fn(x) for x in chebgrid(Nx)]
    patchMD = prolongateOP(Nx, M)*patch1D
    wx      = chebweights(Nx)
    L2error = sum(sqrt(wx'*(patchMD[1+(m-1)*(Nx+1):m*(Nx+1)] - Float64[fn(x) for x in chebgrid(Nx, M, m)]).^2) for m in 1:M)
    return L2error
end

function testprolongate1Dy(fn::Function, Ny::Int, M::Int)::Float64
    patch1D = Float64[fn(y) for y in chebgrid(Ny)]
    patchMD = patch1D'*prolongateOP(Ny, M)'
    wy      = chebweights(Ny)
    L2error = sum(sqrt(wy'*(patchMD'[1+(m-1)*(Ny+1):m*(Ny+1)] - Float64[fn(y) for y in chebgrid(Ny, M, m)]).^2) for m in 1:M)
    return L2error
end

function testrestrict1Dx(fn::Function, Nx::Int, M::Int)::Float64
    patch1D = Float64[fn(x) for x in chebgrid(Nx)]
    patchMD = prolongateOP(Nx, M)*patch1D
    patchSD = restrictOP(Nx, M)*patchMD
    wx      = chebweights(Nx)
    L2error = sqrt(wx'*(patch1D - patchSD).^2)
    return L2error
end

function testrestrict1Dy(fn::Function, Ny::Int, M::Int)::Float64
    patch1D = Float64[fn(y) for y in chebgrid(Ny)]
    patchMD = patch1D'*prolongateOP(Ny, M)'
    patchSD = patchMD*restrictOP(Ny,M)'
    wy      = chebweights(Ny)
    L2error = sqrt(wy'*(patch1D - patchSD').^2)
    return L2error
end

function testprolongatePatch(fn::Function, Nx::Int, Ny::Int, M::Int)::Float64
    sPatch = Float64[fn(x,y) for x in chebgrid(Nx), y in chebgrid(Ny)]
    mPatch = prolongatePatch(Patch([1,1], sPatch), M)
    L2err  = 0.0
    for m in 1:M, n in 1:M
        rPatch = Float64[fn(x,y) for x in chebgrid(Nx, M, m), y in chebgrid(Ny, M, n)]
        L2err += L2norm(mPatch[[m,n]].value, rPatch, chebweights(Nx)/M, chebweights(Ny)/M)
    end
    return L2err
end

function testProlongateThenRestrictPatch(fn::Function, Nx::Int, Ny::Int, M::Int)::Float64
    sPatch   = Patch([1,1], Float64[fn(x,y) for x in chebgrid(Nx), y in chebgrid(Ny)])
    mPatch   = prolongatePatch(sPatch, M)
    m2sPatch = restrictPatch(mPatch)
    return L2norm(sPatch.value, m2sPatch.value, chebweights(Nx), chebweights(Ny))
end

@test testprolongatePatch((x,y)->x^9+y^8, 12, 12, 4) < 1e-14
@test testrestrictmodes1D(x->x^9, 12, 4) > testrestrictmodes1D(x->x^9, 12, 5)
@test testrestrictmodes2D((x,y)->x^9+y^8, 12, 12, 4, 5) > testrestrictmodes2D((x,y)->x^9+y^8, 12, 12, 7, 8)
@test testprolongatemodes1D(x->x^9, 10, 12) == testprolongatemodes1D(x->x^9, 10, 13)
@test testprolongatemodes2D((x,y)->x^2+y^2, 9, 9, 10, 10) == testprolongatemodes2D((x,y)->x^2+y^2, 9, 9, 12, 12)
@test prolongateOP(4,2)*restrictOP(4,2)*prolongateOP(4,2) ≈ prolongateOP(4,2)
@test restrictOP(4,2)*prolongateOP(4,2)*restrictOP(4,2) ≈ restrictOP(4,2)
@test (prolongateOP(4,2)*restrictOP(4,2))' ≈ prolongateOP(4,2)*restrictOP(4,2)
@test (restrictOP(4,2)*prolongateOP(4,2))' ≈ restrictOP(4,2)*prolongateOP(4,2)
@test testprolongate1Dx(x->x.^3, 10, 3) < 1e-14
@test testprolongate1Dy(x->x.^5, 12, 2) < 1e-14
@test testrestrict1Dx(x->x.^5, 10, 4) < 1e-14
@test testrestrict1Dy(x->x.^2, 4, 2) < 1e-14
@test testProlongateThenRestrictPatch((x,y)->x^2 + y^3, 12, 8, 2) < 1e-14


####################################
# Rational Krylov utility functions
####################################

export lejabagby, scgendivdiffs, ratnewtoncoeffs, ratnewtoncoeffsm

"""
Generate Leja-Bagby points (a,b) on (A,B), with scaling factors β such that
the uniform norm on the control set C is 1. Greedy search for a minimum is
performed on B. If keepA is true then the points in the output a will be exactly
those of A, otherwise the points in a are also chosen via greedy search on A. If
forceInf is a positive integer, the first forceInf poles in b will be infinity.
"""
function lejabagby(A::AbstractVector{CT}, B::AbstractVector{T}, C::AbstractVector{CT}, m::Int, keepA::Bool=false, forceInf::Int=0) where {T<:Real, CT<:Complex{T}}
    if minimum(abs.(B)) < 1e-9
        @warn "There is at least one pole candidate in B being nearby zero. Consider shifting your problem for stability."
    end

    a = [A[1]]
    b = [forceInf > 0 ? T(Inf) : B[1]]
    β = [T(1.)]

    o = one(eltype(A))
    sA = fill(o, size(A))
    sB = fill(o, size(B))
    sC = fill(o, size(C))

    @inbounds for j = 1:m-1
        binv = 1 / b[j]
        βinv = 1 / β[j]
        for k = 1:length(A); sA[k] = sA[k] * βinv * (A[k] - a[j]) / (1 - A[k] * binv); end
        for k = 1:length(B); sB[k] = sB[k] * βinv * (B[k] - a[j]) / (1 - B[k] * binv); end
        for k = 1:length(C); sC[k] = sC[k] * βinv * (C[k] - a[j]) / (1 - C[k] * binv); end

        push!(a, A[keepA ? j+1 : argmax([isnan(x) ? -Inf : abs.(x) for x in sA])])
        push!(b, forceInf > j ? Inf : B[argmin([isnan(x) ? Inf : abs.(x) for x in sB])])
        push!(β, maximum(abs.(sC)))

        # treat single point case
        if β[j+1] < eps()
            β[j+1] = 1
        end
    end

    return a, b, β
end

"""
Compute scalar generalized divided differences.

# Arguments
- `σ`: Discretization of target set.
- `ξ`: Discretization of singularity set.
- `β`: Scaling factors.
"""
function scgendivdiffs(σ::AbstractVector{CT}, ξ, β, maxdgr, isfunm, pff) where CT<:Complex{<:Real}
    sgdd = zeros(CT, length(pff), maxdgr+2)
    for ii = 1:length(pff)
        if isfunm
            sgdd[ii,:] = ratnewtoncoeffsm(pff[ii], σ, ξ, β)
        else
            sgdd[ii,:] = map(m -> m[1], ratnewtoncoeffs(pff[ii], σ, ξ, β))
        end
    end
    return sgdd
end

"""
Compute rational divided differences for the function fun (can be matrix
valued), using differencing. The σ need to be distinct. For scalar
functions or non-distinct σ it may be better to use `ratnewtoncoeffsm`.
"""
function ratnewtoncoeffs(fun, σ::AbstractVector{CT}, ξ::AbstractVector{T}, β::AbstractVector{T}) where {T<:Real, CT<:Complex{T}}
    m = length(σ)
    D = Vector{Matrix{CT}}(undef, m)

    # compute divided differences D0,D1,...,Dm
    as_matrix(x::Number) = (M = Matrix{eltype(x)}(undef,1,1); M[1] = x; M)
    D[1] = fun(as_matrix(σ[1])) * β[1]

    for j = 2:m
        # evaluate current linearizaion at σ[j]
        Qj = zeros(T, size(D[1]))
        for k = 1:j-1
            Qj += D[k] * evalrat(σ[1:k-1], ξ[1:k-1], β[1:k], [σ[j]])[1]
        end

        # get divided difference from recursion (could be done via Horner)
        D[j] = (fun(as_matrix(σ[j])) .- Qj) / evalrat(σ[1:j-1], ξ[1:j-1], β[1:j], [σ[j]])[1]
    end

    return D
end

"""
Compute rational divided differences for the scalar function fun, using matrix
functions. `fm` has to be a function object representing a matrix function.
"""
function ratnewtoncoeffsm(fm, σ::AbstractVector{CT}, ξ::AbstractVector{T}, β::AbstractVector{T}) where {T<:Real, CT<:Complex{T}}
    m = length(σ) - 1

    σ = σ[:]
    ξ = ξ[:]
    β = β[:]

    # build Hessenberg matrices
    K = Bidiagonal(ones(m+1), β[2:m+1]./ξ[1:m], :L)
    H = Bidiagonal(σ[1:m+1], CT.(β[2:m+1]), :L)

    # column balancing
    P = Diagonal(1 ./ maximum(abs.(K), dims = 1)[:])
    K *= P
    H *= P
    H=Matrix(H) # this matrix is sparse, it is needed to convert before matrix-fun evaluation
    D = fm(H/CT.(K))[:,1] * β[1]
    D = copy(transpose(D))

    return D
end

"Evaluate nodal rational function at the points z."
function evalrat(σ::AbstractVector{CT}, ξ::AbstractVector{T}, β::AbstractVector{T}, z::AbstractVector{CT}) where {T<:Real, CT<:Complex{T}}
    r = ones(CT, size(z)) / β[1]
    for j = 1:length(σ)
        r .*= (z .- σ[j]) ./ (1 .- z/ξ[j]) / β[j+1]
    end
    return r
end


# mw_htest_i0_i1_pv - Construct H-test pvalue for the I(0) or Unit Root Null Model
# Syntax:  [output1,output2] = function_name(input1,input2,input3)
#
# Inputs:
#   x_t::Vector{Float64} - dct transformed data
#   param::Float64 - parameter for SD in H-test
#   itrend::Int64 - 1 for demean, 2 for detrend
#   ind_int::Int64 - 0 for I(0), 1 for I(1)
#   n_wsim::Int64 - number of simulated sd paths
#   rmatrix_string = name of matrix with standard random variables ... on disk ... w draws
#   nsim = number of simulations for p-value
# Outputs:
#    pv::FLoat64 - pvalue for h test
#
# Example:
#    Line 1 of example
#    Line 2 of example
#    Line 3 of example
#
# Other m-files required: none
# Subfunctions: none
# MAT-files required: none
#
# See also: OTHER_FUNCTION_NAME1,  OTHER_FUNCTION_NAME2

# Author: Pedro Chaim
# git repository: http://www.
# May 2004; Last revision: 12-May-2004
function mw_h_test_i0_i1_pv(x_t, param, itrend, ind_int, nwsim, rmatrix_string, nsim)
  q = size(x_t, 1)
  qq = q*(q+1)/2
  t = 100 # sample size for asymptotic approximations
  if itrend == 1
    psi = psi_compute(t, q)
  end
  # Construct Appropriate Covariance Matrices @
  cov_ha_i0, cov_ha_i1, cov_ha_i2 = mw_h_cov_ha_i0_i1_i2(psi, param, nwsim, rmatrix_string)
  if ind_int == 0
    cov_h0 = psi'psi
    cov_ha = cov_ha_i0
  elseif ind_int == 1
    cov_u = mw_rw_cov(t)
    cov_h0 = psi'cov_u*psi
    cov_ha = cov_ha_i1
  end
  # Construct Test Statistic @
  lr = mw_lrstat_vec(x_t, cov_h0, cov_ha_i0)
  # @ Compute Simulations for p-value @
  xsim = randn(q, nsim)
  a = chol(Hermitian(cov_h0, :L))
  xsim = a'xsim
  lr_sim = mw_lrstat_vec(xsim, cov_h0, cov_ha)
  pv = mean(lr_sim .> lr, 1)
return pv, lr#, lr, x_t, cov_h0, cov_ha_i0, cov_ha_i1, cov_ha_i2
end


using PolynomialBasis
using CartesianMesh
using CutCellDG
using Test
include("../useful_routines.jl")

x0 = [-1.0]
meshwidth = [2.0]
nelements = [3]
polyorder = 3
solverbasis = LagrangeTensorProductBasis(1, polyorder)
refpoints = interpolation_points(solverbasis)

dgmesh = CutCellDG.DGMesh(x0,meshwidth,nelements,refpoints)
nc = CutCellDG.nodal_connectivity(dgmesh)
testnc = reshape(1:12,4,:)
@test allequal(nc,testnc)


#helper functions
"""
    a = randnc(N...)

Return an array of dimension `length(N)`, containing samples of complex random
variables with mean zero and variance one:
```math
⟨|a|^2⟩ = 1; ⟨a^2⟩ = ⟨(a^*) ^2⟩ = 0,
```
Useful for creating a range of noises that show up in phase-space simulations.

Usage:

`a = randnc(10)` returns a `10-element Array{Complex{Float64},1}`.

`a = randnc(50,100)` returns a `50x100-element Array{Complex{Float64},2}`.
"""
randnc(args...) = randn(ComplexF64,args...)

"""
    x = reject(P,w,N,Pmax)

Generate `x` distributed according to a probability distribution by rejection sampling over a finite window.

`P`: probability distribution, normalized to 1.

`w=[w1,w2]`: window for sampling `x`.

`N`: number of samples.

`Pmax`: numerical upper bound for `P`: `Pmax ≧ max(P(w))`.

"""
function reject(P,w,N,Pmax)
    a,b = w
    samples = Array{Float64}(undef,0)
    sizehint!(samples,N)
    while length(samples) < N
        y = a + rand()*(b - a)
        z = rand()*Pmax
        z < P(y) && push!(samples,y)
    end
    return samples
end

# Laguerre methods for +W
plaguerre(x,n) = (4/3)*exp(-2*x^2/3 + log(x) + log(laguerren(-4*x^2/3,n)) - n*log(3))
plaguerre_asymptotic(x,n) = exp(-(x-sqrt(n+1))^2)/sqrt(pi)
laguerren(x,n) = sf_laguerre_n(n,0.0,x)

"""
Complex variable SDE's in `DifferentialEquations.jl`
have complex noises by default dispatch on complex fields.
This method defines a real noise for use with the library.
"""
function realnoise(rand_vec,W,dt,rng)
for i in eachindex(rand_vec)
    rand_vec[i] = randn(rng)
    end
    rand_vec .*= sqrt(abs(dt))
end

"""
Complex variable SDE's in `DifferentialEquations.jl`
have complex noises by default dispatch on complex fields.
This method defines a real brownian bridge for use with the library.
"""
function realbridge(rand_vec,W,W0,Wh,q,h,rng)
for i in eachindex(rand_vec)
    rand_vec[i] = randn(rng)
    end
    rand_vec .= sqrt((1 .- q).*q.*abs(h)).*rand_vec.+q.*Wh
end


using LinearAlgebra
using SparseArrays

lchol(A) = LowerTriangular(Matrix(LinearAlgebra._chol!(copy(A), UpperTriangular)[1])')
lchol!(A) = LowerTriangular(Matrix(LinearAlgebra._chol!(A, UpperTriangular)[1])')

"""
    cholinverse!(L, x)
Solve L L'y =x using two backsolves,
L should be lower triangular
"""
function cholinverse!(L, x)
    naivesub!(L, x) # triangular backsolves
    naivesub!(UpperTriangular(L'), x)
    x
end

function naivesub!(At::Adjoint{<:Any,<:LowerTriangular}, b::AbstractVector, x::AbstractVector = b)
    A = At.parent
    n = size(A, 2)
    if !(n == length(b) == length(x))
        throw(DimensionMismatch("second dimension of left hand side A, $n, length of output x, $(length(x)), and length of right hand side b, $(length(b)), must be equal"))
    end
    @inbounds for j in n:-1:1
        iszero(A.data[j,j]) && throw(SingularException(j))
        xj = x[j] = A.data[j,j] \ b[j]
        for i in j-1:-1:1 # counterintuitively 1:j-1 performs slightly better
            b[i] -= A.data[j,i] * xj
        end
    end
    x
end

function naivesub!(At::Adjoint{<:Any,<:LowerTriangular}, B::AbstractMatrix, X::AbstractMatrix = B)
    A = At.parent
    n = size(A, 2)
    if !(n == size(B,1) == size(B,2) == size(X,1) == size(X,2))
        throw(DimensionMismatch())
    end
    @inbounds for k in 1:n
        for j in n:-1:1
            iszero(A.data[j,j]) && throw(SingularException(j))
            xjk = X[j,k] = A.data[j,j] \ B[j,k]
            for i in j-1:-1:1 # counterintuitively 1:j-1 performs slightly better
                B[i,k] -= A.data[j,i] * xjk
            end
        end
    end
    X
end

function naivesub!(A::LowerTriangular, B::AbstractMatrix, X::AbstractMatrix = B)
    n = size(A,2)
    if !(n == size(B,1) == size(X,1))
        throw(DimensionMismatch())
    end
    if !(size(B,2) == size(X,2))
        throw(DimensionMismatch())
    end

    @inbounds for k in 1:size(B,2)
        for j in 1:n
            iszero(A.data[j,j]) && throw(SingularException(j))
            xjk = X[j,k] = A.data[j,j] \ B[j,k]
            for i in j+1:n
                B[i,k] -= A.data[i,j] * xjk
            end
        end
    end
    X
end

function naivesub!(A::UpperTriangular, B::AbstractMatrix, X::AbstractMatrix = B)
    n = size(A, 2)
    if !(n == size(B,1) == size(X,1))
        throw(DimensionMismatch())
    end
    if !(size(B,2) == size(X,2))
        throw(DimensionMismatch())
    end

    @inbounds for k in 1:size(B, 2)
        for j in n:-1:1
            iszero(A.data[j,j]) && throw(SingularException(j))
            xjk = X[j,k] = A.data[j,j] \ B[j,k]
            for i in j-1:-1:1 # counterintuitively 1:j-1 performs slightly better
                B[i,k] -= A.data[i,j] * xjk
            end
        end
    end
    X
end


"""
    BCDiag

A structure of diagnostic properties of a binary Classifier.
Facilitates summary plots and tables.
"""
struct BCDiag
    n::Int                  # Number of observations
    n1::Int                 # Number of Class 1
    n0::Int                 # Number of Class 0
    baserate::Float64       # Class 1 incidence rate
    ks::Float64             # Maximum separation
    ksarg::Float64          # max sep occurs at this predictor value
    ksdep::Float64          # max sep occurs at this depth

    concordant::Int         # concordant pairs
    tied::Int               # tied pairs
    discordant::Int         # discordant pairs
    auc::Float64            # Area under ROC = (concordant + 0.5tied)/ total pairs
    gini::Float64           # Gini = 2C - 1

    grp::Vector             # group number [0-groups]
    depth::Vector           # Depth [0, 1]
    cdf1::Vector            # cum1 / n1 = True Positive Rate
    cdf0::Vector            # cum0 / n0 = False Positive Rate

    cntg::Vector            # count this group
    cnt1::Vector            # count class 1 this group
    prd1::Vector            # predicted class 1 this group
    rrObs::Vector           # response rate actual
    rrPrd::Vector           # response rate Predicted

    cumg::Vector            # cum count up to this group
    cum1::Vector            # cum count class 1 up to this group
    cpr1::Vector            # cum predicted class 1 up to this group
    crObs::Vector           # cum response rate actual
    crPrd::Vector           # cum response rate predicted
end

function Base.show(io::IO, ::MIME"text/plain", x::BCDiag)
    print(io,
        "Base rate: ", round(x.baserate, digits = 4),"   n: ", x.n, "   n1: ", x.n1, "   n0: ", x.n0, "\n",
        "ks:        ", round(x.ks, digits = 4), "   occurs at value of ", x.ksarg, " depth of ", x.ksdep, "\n",
        "auroc:     ", round(x.auc, digits = 4), "   concordant: ", x.concordant,
        "   tied: ", x.tied, "   discordant: ", x.discordant, "\n",
        "Gini:      ", round(x.gini, digits = 4)
    )
end

function Base.show(io::IO, x::BCDiag)
    print(io, round(x.ks, digits = 4), "   ", round(x.auc, digits = 4) )
end

"""
    bcdiag(target, pred; groups = 100, rev = true, tie = 1e-6)

Perform diagnostics of a binary classifier.
`target` is a 2 level categorical variable, `pred` is probability of class 1.
`groups` is the number of bins to use for plotting/printing.
`rev` = true orders `pred` from high to low.
`tie` is the tolerance of `pred` where values are considered tied.

Returns a BCDiag struct which can be used for plotting or printing:
- `biasplot` is calibration plot of `target` response rate vs. `pred` response rate
- `ksplot` produces ksplot of cumulative distributions
- `rocplot` plots the Receiver Operating Characteristics curve
- `accuracyplot` plots the accuracy curve with adjustable utility
- `liftcurve` is the lift curve
- `cumliftsurve` is the cumulative lift surve
- `liftable` is the lift table as a DataFrame
- `cumliftable` is the cumulative lift table as a DataFrame
"""
function bcdiag(target::BitArray{1}, pred::Vector; groups = 100, rev = true, tie = 1e-6)
    ks = kstest(target, pred; rev = rev)
    roc = auroc(target, pred; tie = tie)

    grpid = ranks(pred, groups = groups, rev = rev)
    frqtbl = freqtable(grpid, target)
    frq = frqtbl.array
    grp = NamedArrays.names(frqtbl, 1)

    n0, n1 = sum(frq, dims = 1)
    n = n0 + n1

    cntg = vec(sum(frq, dims = 2))          # bin count
    cnt1 = @view frq[:, 2]                  # bin count class 1
    cnt0 = @view frq[:, 1]                  # bin count class 0
    cumg = cumsum(cntg)                     # cum bin count
    cum1 = cumsum(cnt1)                     # cum class 1
    cum0 = cumsum(cnt0)                     # cum class 0

    depth = cumg ./ n                       # Depth including this group
    cdf1  = cum1 ./ n1                      # TPR: True Positive Rate
    cdf0  = cum0 ./ n0                      # FPR: False Positive Rate

    rrObs = cnt1 ./ cntg                    # actual response rate
    crObs = cum1 ./ cumg                    # cum actual response rate

    prd1 = Vector{Float64}(undef, length(grp))  # predicted class 1
    for (i, g) in enumerate(grp)
        prd1[i] = sum(pred[grpid .== g])
    end
    cpr1 = cumsum(prd1)                     # cum predicted class 1

    rrPrd = prd1 ./ cntg                    # predicted response rate
    crPrd = cpr1 ./ cumg                    # cum predicted response rate

    BCDiag( ks..., roc..., grp, depth, cdf1, cdf0,
            cntg, cnt1, prd1, rrObs, rrPrd, cumg, cum1, cpr1, crObs, crPrd )
end

function bcdiag(target::Vector, pred::Vector; groups = 100, rev = true, tie = 1e-6)
    uc = sort(unique(target))
    length(uc) == 2 || error(ArgumentError("target should have 2 levels"))

    bcdiag(target .== uc[2], pred; rev = rev)
end

"""
    biasplot(x::BCDiag)

returns a bias calibration plot of `x` - actual response vs. predicted response
"""
function biasplot(x::BCDiag)
    dmin = min(minimum(x.rrPrd), minimum(x.rrObs))
    dmax = max(maximum(x.rrPrd), maximum(x.rrObs))

    plt = plot(size = (500, 500), aspect_ratio = :equal, legend = :bottomright,
        xguidefontsize = 10, yguidefontsize = 10, titlefontsize = 11)

    title!("Model Calibration Graph", titlefontsize = 11)
    xlabel!("Predicted Response Rate", xguidefontsize = 10)
    ylabel!("Observed Response Rate", yguidefontsize = 10)

    plot!(x.rrPrd, x.rrObs, label = nothing, seriescolor = :blue)
    plot!([dmin, dmax], [dmin, dmax], label = nothing, width = 0.5, seriescolor = :black)

    plt
end

"""
    ksplot(x::BCDiag)

returns a KS plot of `x` - CDF1 (True Positive) and CDF0 (False Positive) versus depth
"""
function ksplot(x::BCDiag)
    plt = plot(size = (500, 500), aspect_ratio = :equal, xlims = (0., 1.), ylims = (0., 1.),
        legend = :bottomright, xguidefontsize = 10, yguidefontsize = 10, titlefontsize = 11)

    title!("KS Graph", titlefontsize = 11)
    xlabel!("Depth", xguidefontsize = 10)
    ylabel!("Empirical CDF", yguidefontsize = 10)

    plot!([0.; x.depth], [0.; x.cdf1], label = "Class 1", width = 1.5, seriescolor = :blue)
    plot!([0.; x.depth], [0.; x.cdf0], label = "Class 0", width = 0.5, seriescolor = :black)
    plot!([0., x.baserate, 1.], [0., 1., 1.], label = "Perfect Class 1", width = 0.5, seriescolor = :blue, linestyle = :dash)
    plot!([0., x.baserate, 1.], [0., 0., 1.], label = "Perfect Class 0", width = 0.5, seriescolor = :black, linestyle = :dash)
    ksidx = findlast(x.depth .<= x.ksdep)
    plot!([x.depth[ksidx], x.depth[ksidx]], [x.cdf0[ksidx], x.cdf1[ksidx]], label = nothing, width = 0.8, linestyle = :dash)

    annotate!((0.03, 0.97, text("Base rate = $(round(x.baserate, digits = 4))", :black, :left, 9)))
    annotate!((0.03, 0.93, text("Depth = $(round(x.ksdep, digits = 4))", :black, :left, 9)))
    annotate!((0.03, 0.89, text("KSarg = $(round(x.ksarg, digits = 4))", :black, :left, 9)))
    annotate!((0.03, 0.85, text("KS = $(round(x.ks, digits = 4))", :black, :left, 9)))

    plt
end

"""
    rocplot(x::BCDiag)

returns a ROC plot of `x` - CDF1 (True Positive) vs. CDF0 (False Positive)
"""
function rocplot(x::BCDiag)
    plt = plot(size = (500, 500), aspect_ratio = :equal, xlims = (0., 1.), ylims = (0., 1.),
        legend = :bottomright, xguidefontsize = 10, yguidefontsize = 10, titlefontsize = 11)

    title!("ROC Graph", titlefontsize = 11)
    xlabel!("False Positive Rate", xguidefontsize = 10)
    ylabel!("True Positive Rate", yguidefontsize = 10)

    plot!([0.; x.cdf0], [0.; x.cdf1], label = "Model", seriescolor = :blue, width = 1.5)
    plot!([0., 1.], [0., 1.], label = "Random", seriescolor = :black, width = 0.3, linestyle = :dash)

    annotate!((0.75, 0.22, text("auc = $(round(x.auc, digits = 4))", :black, :left, 9)))
    annotate!((0.75, 0.18, text("Gini = $(round(2. * x.auc - 1., digits = 4))", :black, :left, 9)))

    plt
end

"""
    accuracyplot(x::BCDiag; util=[1, 0, 0, 1])

Using `util` values for [TP, FN, FP, TN], produce accuracy plot and its [max, argmax, argdep].
Default `util` values of [1, 0, 0, 1] gives the standard accuracy value of (TP+TN)/N.
"""
function accuracyplot(x::BCDiag; util = [1, 0, 0, 1])
    p = x.baserate
    q = 1. - p

    # perfect classifier
    up = dot([p, 0., 0., q], util)                  # TP, FN, FP, TN of perfect classifier

    # 0.5 / 0.5 random classifier
    # ur5 = dot([0.5p, 0.5p, 0.5q, 0.5q], util)

    # baserate random classifier
    urb = dot([p*p, p*q, p*q, q*q], util)

    # model utility value [TP, FN, FP, TN]
    uc = [p.*x.cdf1  p.*(1. .- x.cdf1)  q.*x.cdf0  q.*(1. .- x.cdf0)] * util
    ucmax = findmax(uc)

    plt = plot(size = (500, 500), xlims = (0., 1.), legend = :best,
        xguidefontsize = 10, yguidefontsize = 10, titlefontsize = 11)

    title!("Accuracy Plot", titlefontsize = 11)
    xlabel!("Depth", xguidefontsize = 10)
    ylabel!("Utility", yguidefontsize = 10)

    hline!([up], label = "Perfect $(round(up, sigdigits = 3))", linestyle = :dash)

    plot!(x.depth, uc, width = 1.5, seriescolor = :blue,
        label = "Model $(round(ucmax[1], sigdigits = 3))")
    vline!([x.depth[ucmax[2]]], label = "Depth $(x.depth[ucmax[2]])")

    hline!([urb], label = "Base Random $(round(urb, sigdigits = 3))", linestyle = :dash)

    plt
end

"""
    liftcurve(x::BCDiag)

returns a lift curve plot of `x` - actual and predicted versus depth
"""
function liftcurve(x::BCDiag)
    plt = Plots.plot(size = (500, 500), xlims = (0., 1.),
        legend = :topright, xguidefontsize = 10, yguidefontsize = 10, titlefontsize = 11)

    title!("Lift Curve", titlefontsize = 11)
    xlabel!("Depth", xguidefontsize = 10)
    ylabel!("Response Rate", yguidefontsize = 10)

    plot!(x.depth, x.rrObs, label = "Actual", width = 0.5, linestyle = :dash)
    plot!(x.depth, x.rrPrd, label = "Predicted", width = 1.5, seriescolor = :blue)
    hline!([x.baserate], label = "Base rate", linestyle = :dash)

    plt
end

"""
    cumliftcurve(x::BCDiag)

returns a cumulative lift curve plot of `x` - cumulative actual and predicted vs. depth
"""
function cumliftcurve(x::BCDiag)
    plt = Plots.plot(size = (500, 500), xlims = (0., 1.),
        legend = :topright, xguidefontsize = 10, yguidefontsize = 10, titlefontsize = 11)

    title!("Cumulative Lift Curve", titlefontsize = 11)
    xlabel!("Depth", xguidefontsize = 10)
    ylabel!("Response Rate", yguidefontsize = 10)

    plot!(x.depth, x.crObs, label = "Actual", width = 0.5, linestyle = :dash)
    plot!(x.depth, x.crPrd, label = "Predicted", width = 1.5, seriescolor = :blue)
    hline!([x.baserate], label = "Base rate", linestyle = :dash)

    plt
end

"""
    liftable(x::BCDiag)

returns a lift table of `x` as a DataFrame
"""
function liftable(x::BCDiag)
    out = DataFrame(grp = x.grp, depth = x.depth, count = x.cntg,
                    cntObs = x.cnt1, cntPrd = x.prd1,
                    rrObs = x.rrObs, rrPred = x.rrPrd,
                    liftObs = x.rrObs ./ x.baserate, liftPrd = x.rrPrd ./ x.baserate
                    )

    out
end

"""
    cumliftable(x::BCDiag)

returns a cumulative lift table of `x` as a DataFrame
"""
function cumliftable(x::BCDiag)
    out = DataFrame(grp = x.grp, depth = x.depth, count = x.cumg,
                    cumObs = x.cum1, cumPrd = x.cpr1,
                    crObs = x.cum1 ./ x.cumg,
                    crPrd = x.cpr1 ./ x.cumg,
                    )
    out.liftObs = out.crObs ./ x.baserate
    out.liftPrd = out.crPrd ./ x.baserate

    out
end


#=
Simple support for FFTs using:
https://github.com/JuliaMath/AbstractFFTs.jl

Does not (yet) cover plan_fft & friends,
because extracting the dimensions from those is tricky
=#

using AbstractFFTs

for fun in [:fft, :ifft, :bfft, :rfft]
    @eval function AbstractFFTs.$fun(A::Union{KeyedArray,NdaKa}, dims = ntuple(+,ndims(A)))
        numerical_dims = NamedDims.dim(A, dims)
        data = $fun(keyless(A), numerical_dims)
        keys = fft_keys($fun, A, numerical_dims, data)
        KeyedArray(data, keys)
    end
end

function AbstractFFTs.irfft(A::Union{KeyedArray,NdaKa}, len::Integer, dims = ntuple(+,ndims(A)))
    numerical_dims = NamedDims.dim(A, dims)
    data = irfft(keyless(A), len, numerical_dims)
    keys = fft_keys(irfft, A, numerical_dims, data)
    KeyedArray(data, keys)
end

for shift in [:fftshift, :ifftshift]
    @eval function AbstractFFTs.$shift(A::Union{KeyedArray,NdaKa}, dims = ntuple(+,ndims(A)))
        numerical_dims = NamedDims.dim(A, dims)
        data = $shift(keyless(A), numerical_dims)
        keys = ntuple(ndims(A)) do d
            k = axiskeys(A,d)
            d in numerical_dims || return k
            $shift(k)
        end
        KeyedArray(data, keys)
    end
end

# copy(fftfreq(10, 1)) isa Vector # perhaps that needs a method:
# Base.copy(x::Frequencies{<:Number}) = x  # fixed in AbstractFFts 1.0

fft_keys(f, A, dims, B) = ntuple(ndims(A)) do d
        k = axiskeys(A,d)
        d in dims || return k
        if k isa Frequencies
            return fft_un_freq(k)
        elseif k isa AbstractRange{<:Number}
            return fftfreq(length(k), inv(step(k)))
        end
        return k
    end

fft_keys(::typeof(rfft), A, dims, B) = ntuple(ndims(A)) do d
        k = axiskeys(A,d)
        d in dims || return k
        if k isa Frequencies || k isa AbstractRange{<:Number}
            return rfftfreq(length(k), inv(step(k)))
        end
        return axes(B,d)
    end

fft_keys(::typeof(irfft), A, dims, B) = ntuple(ndims(A)) do d
        k = axiskeys(A,d)
        d in dims || return k
        if k isa Frequencies || k isa AbstractRange{<:Number}
            return irfft_un_freq(k, size(B,d))
        end
        return axes(B,d)
    end

# This just chooses a nicer zero when inverting FFT:
function fft_un_freq(x::Frequencies)
    s = inv(x.multiplier * x.n)
    range(zero(s), step = s, length = x.n)
end

function irfft_un_freq(x, len)
    s = inv(step(x) * len)
    range(zero(s), step = s, length = len)
end


module TestUtils

using Bootstrap
using Test

@testset "Statistic functions" begin

    x = collect(100:-1:0) ## avoid sorting

    ## reference values, computed with boot:::norm_inter (R)
    qr = (
        (1.0,      Inf),
        (0.0,     -Inf),
        (0.5,     50.0),
        (0.4713,  47.07265),
        (0.1,      9.205641),
        (0.052,    4.319606)
    )

    @testset "Normal-interpolated quantiles" begin

        @testset "Reference values: alpha scalar" begin
            for (alpha, ref) in qr
                @test isapprox( Bootstrap.iquantile(x, alpha), ref, atol = 1e-5 )
            end
        end

        @testset "Reference values: alpha vector" begin
            a = Float64[x[1] for x in qr]
            r = Float64[x[2] for x in qr]
            y = Bootstrap.iquantile(x, a)
            @test isapprox(y, r, atol = 1e-4 )
        end

        @testset "Monotone quantiles" begin
            x = collect(0:100)
            ## check that quantiles are monotone
            y0 = -Inf
            for alpha in 0:0.001:1
                y1 = Bootstrap.iquantile(x, alpha)
                @test y1 >= y0
                y0 = y1
            end
        end

    end

end

@testset "Utility functions" begin

    @testset "Exact sampling" begin

        mss = collect(Bootstrap.exact(4))
        @test length(mss) == 35
        @test reduce(&, [issorted(c) for c in mss])

        for n in 1:6
            @test length(Bootstrap.exact(n)) == Bootstrap.nrun_exact(n)
        end

    end

end

end


using Pkg
Pkg.activate("./GraphBLASJuliaCvdlab/")

include("../../src/GraphBLAS_bfs_cvd.jl")
include("../../src/GraphBLAS_sparse_square_matrix_cvd.jl")

@testset "GraphBLASJuliaCvdlab" begin

### algorithm correctness test
    mat = [0 1 0 1 0 0 0; 0 0 0 0 1 0 1; 0 0 0 0 0 1 0; 1 0 1 0 0 0 0 ; 0 0 0 0 0 1 0; 0 0 1 0 0 0 0; 0 0 1 1 1 0 0]
    A = from_matrix(mat)

    sol = from_lists([1, 2, 3, 4, 5, 6, 7], [1, 2, 3, 2, 3, 4, 3])

    @test GraphBLAS_bfs_cvd(A, 1) == sol

### exception throw tests

    # Input matrix is not square
    B = from_matrix([1 0 1 0 ; 0 0 1 0 ; 0 0 0 1 ; 1 0 0 0 ; 0 0 1 0])
    @test_throws DimensionMismatch GraphBLAS_bfs_cvd(B, 1)

    # source is a not a positive Integer
    @test_throws ArgumentError GraphBLAS_bfs_cvd(A, -1)

    # source is not an Integer
    @test_throws MethodError GraphBLAS_bfs_cvd(A, 1.2)

    # Input matrix is not a GBMatrix
    @test_throws MethodError GraphBLAS_bfs_cvd(mat, 1)

end


module ParametricCurves

using ForwardDiff
using StaticArrays
using LinearAlgebra

abstract type AbstractCurve{N} end

abstract type AbstractParametricCurve{N} <: AbstractCurve{N} end

function derivatives(curve::AbstractParametricCurve{N}, n::Int) where {N}
    # default to AD
    return derivatives(curve, Val(n))
end

derivatives(curve::AbstractParametricCurve{N}) where {N} = derivatives(curve, 1)

function derivatives(curve::AbstractParametricCurve{N}, ::Val{n}) where {N,n}
    # concrete implementation of AbstractParametricCurve can define this function
    # so that AD wont be used.
    # this can be done by defining your own ParametricCurve type.
    return _derivatives(curve.coordinates, Val(n))
end
Base.getproperty(curve::AbstractParametricCurve, s::Symbol) = _getproperty(curve, Val(s))
_getproperty(curve::AbstractParametricCurve, ::Val{S}) where {S} = getfield(curve, S)
_getproperty(curve::AbstractParametricCurve, ::Val{:x}) = curve.coordinates[1]
_getproperty(curve::AbstractParametricCurve, ::Val{:y}) = curve.coordinates[2]
_getproperty(curve::AbstractParametricCurve, ::Val{:z}) = curve.coordinates[3]
_getproperty(curve::AbstractParametricCurve{2}, ::Val{:z}) = error("2D curve has no z coordinate.")

function _derivatives(fs, ::Val{1})
    return map(f -> (t -> ForwardDiff.derivative(f, t)), fs)
end
function _derivatives(fs, ::Val{n}) where {n}
    return map(f -> (t -> ForwardDiff.derivative(f, t)), _derivatives(fs, Val(n - 1)))
end
# function _derivatives(fs, n::Int)
#     return _derivatives(fs, Val(n))
# end

@inline function tangent(curve::AbstractParametricCurve{N}, t::T) where {N,T}
    # compute the tangent vector at the parameter location t
    dval = map((f) -> f(t), derivatives(curve))
    #convert tuple to SVector
    dval = SVector{N,T}(dval)
    return dval / norm(dval)
end

@inline function normal(curve::AbstractParametricCurve{2}, t::T) where {T}
    # in2D, can rotate tangent to get normal
    tvec = tangent(curve, t)
    return SVector{2,T}(-tvec[2], tvec[1])
end

@inline function normal(curve::AbstractParametricCurve{N}, t::T) where {N,T}
    # compute the tangent vector at the parameter location t
    dval = map((f) -> f(t), derivatives(curve, Val(2)))
    #convert tuple to SVector
    dval = SVector{N,T}(dval)
    return dval / norm(dval)
end

@generated function radius(curve::AbstractParametricCurve{N}, t::Real) where {N}
    ex = :(curve.coordinates[1](t)^2)
    for i = 2:N
        ex = :(curve.coordinates[$i](t)^2 + $ex)
    end
    return :(sqrt($ex))
end

@inline function cartesian(curve::AbstractParametricCurve{N}, t::T) where {N,T}
    x = map(f -> f(t), curve.coordinates)
    return SVector{N,T}(x)
end

"""
    explicit2parametric(f)
Convert an explicit 2D curve of the form `y=f(x)` to its parametric form.
"""
@inline function explicit2parametric(f)
    return (t -> t, t -> f(t))
end

struct ParametricCurve{N,C} <: AbstractParametricCurve{N}
    coordinates::C
    function ParametricCurve(coordinates)
        N = length(coordinates)
        return new{N,typeof(coordinates)}(coordinates)
    end
end

export ParametricCurve, tangent, normal, radius, cartesian, explicit2parametric

end # end module


# ---
# title: 13. Roman to Integer
# id: problem13
# author: Tian Jun
# date: 2020-10-31
# difficulty: Easy
# categories: Math, String
# link: <https://leetcode.com/problems/roman-to-integer/description/>
# hidden: true
# ---
#
# Roman numerals are represented by seven different symbols: `I`, `V`, `X`, `L`,
# `C`, `D` and `M`.
#
#
#
#     **Symbol**       **Value**
#     I             1
#     V             5
#     X             10
#     L             50
#     C             100
#     D             500
#     M             1000
#
# For example, `2` is written as `II` in Roman numeral, just two one's added
# together. `12` is written as `XII`, which is simply `X + II`. The number `27`
# is written as `XXVII`, which is `XX + V + II`.
#
# Roman numerals are usually written largest to smallest from left to right.
# However, the numeral for four is not `IIII`. Instead, the number four is
# written as `IV`. Because the one is before the five we subtract it making
# four. The same principle applies to the number nine, which is written as `IX`.
# There are six instances where subtraction is used:
#
#   * `I` can be placed before `V` (5) and `X` (10) to make 4 and 9.
#   * `X` can be placed before `L` (50) and `C` (100) to make 40 and 90.
#   * `C` can be placed before `D` (500) and `M` (1000) to make 400 and 900.
#
# Given a roman numeral, convert it to an integer.
#
#
#
# **Example 1:**
#
#
#
#     Input: s = "III"
#     Output: 3
#
#
# **Example 2:**
#
#
#
#     Input: s = "IV"
#     Output: 4
#
#
# **Example 3:**
#
#
#
#     Input: s = "IX"
#     Output: 9
#
#
# **Example 4:**
#
#
#
#     Input: s = "LVIII"
#     Output: 58
#     Explanation: L = 50, V= 5, III = 3.
#
#
# **Example 5:**
#
#
#
#     Input: s = "MCMXCIV"
#     Output: 1994
#     Explanation: M = 1000, CM = 900, XC = 90 and IV = 4.
#
#
#
#
# **Constraints:**
#
#   * `1 <= s.length <= 15`
#   * `s` contains only the characters `('I', 'V', 'X', 'L', 'C', 'D', 'M')`.
#   * It is **guaranteed**  that `s` is a valid roman numeral in the range `[1, 3999]`.
#
#
## @lc code=start
using LeetCode

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


using LinearAlgebra

"""
Symmetric Matrix which only stores upper triangle

data::Vector{T}
"""
struct SymDenseMat{T} <: AbstractMatrix{T}
    data::Vector{T}
    N::Int
end

"""
    function vec_tril(M::AbstractMatrix{T}) where T

Export Symmetri
"""
function SymDenseMat{T}(M::AbstractMatrix{T}) where T
    m, n = size(M)
    m == n || throw(error("not square"))
    nv = n*(n+1) ÷ 2
    v = SymDenseMat{T}(Vector{T}(undef,nv), n)
    k = 0
    for i in 1:n
        for j in i:n
            k += 1
            v.data[k] = M[j, i]
        end
    end
    return v
end

function Base.Matrix(A::SymDenseMat{T}) where {T}
    M = Matrix{T}(undef,A.N,A.N)
    k = 1
    for i in 1:A.N
        for j in i:A.N
            M[i,j] = A.data[k]
            M[j,i] = A.data[k]
            k += 1
        end
    end
    return M
end
Base.size(A::SymDenseMat{T}) where {T} = return (A.N, A.N)
function Base.getindex(A::SymDenseMat{T}, i::Integer, j::Integer) where {T}
    #i>=j || error("i<j")
    if i<j
        return A.data[A.N*(A.N+1)÷2 - (A.N-i+1)*(A.N-i+2)÷2 + j - i + 1]
    else
        return A.data[A.N*(A.N+1)÷2 - (A.N-j+1)*(A.N-j+2)÷2 + i - j + 1]
    end
end
function Base.display(A::SymDenseMat{T}) where {T}
    display(typeof(A))
    display(("N=",A.N))
    display(A.data)
end
function Base.:(*)(A::SymDenseMat{T}, v::AbstractVector{T}) where {T}
    A.N == length(v)  || throw(DimensionMismatch)
    out = zeros(T,A.N)
    for i in 1:A.N
        for j in 1:A.N
            out[i] += A[i,j]*v[j]
        end
    end
    return out
end


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

# using Random, Distributions

"""
    LinSysVecRowPropToNormSampler{T} <: LinSysVecRowSampler where T <: Categorical

A parametric mutable structure that specifies sampling from the rows of the equation where
the probability of selecting a given equation is proportional to the sum of squares of the
coefficients of the given equation. The solver will appropriately initialize the
distribution.

See Strohmer, T., Vershynin, R. A Randomized Kaczmarz Algorithm with Exponential
Convergence. J Fourier Anal Appl 15, 262 (2009). https://doi.org/10.1007/s00041-008-9030-4

# Aliases
- `LinSysVecRowSVSampler`

# Fields
- `dist::T`, a categorical probability distribution.

Calling `LinSysVecRowPropToNormSampler()` or `LinSysVecRowSVSampler()` defaults `dist` to
`Categorical(1.0)`.
"""
mutable struct LinSysVecRowPropToNormSampler{T} <: LinSysVecRowSampler where T <:
    Categorical

    dist::T
end
LinSysVecRowSVSampler = LinSysVecRowPropToNormSampler
LinSysVecRowPropToNormSampler() = LinSysVecRowPropToNormSampler(Categorical(1.0))

# Common sample interface for linear systems
function sample(
    type::LinSysVecRowSVSampler{T} where T <: Categorical,
    A::AbstractArray,
    b::AbstractVector,
    x::AbstractVector,
    iter::Int64
)
    if iter == 1
        prob = sum(A.^2, dims=2)[:,1]
        type.dist = Categorical(prob ./ sum(prob))
    end

    eqn_ind = rand(type.dist)

    return A[eqn_ind, :], b[eqn_ind]
end

#export LinSysVecRowPropToNormSampler, LinSysVecRowSVSampler


@doc "STANFORD UNIVERSITY INTERIM Propagation Model"
type SuiModel<:Model
  freq::Float64
  txH::Float64
  rxH::Float64
  terrainKind::Int
  shadowFading ::Float64
  checkFreqRange::Bool
  SuiModel() = new(2500,50,1.5,TerrainKind.A,8.2,true)
end

@doc "Calculate the path loss with the SUI Model"
function pathloss_op(model::SuiModel,dist::AbstractFloat)

  	d::Float64 = dist * 1000	#km to m
    f = model.freq
    if (model.checkFreqRange)
      if (f < 1900 || f > 11000)
    		error("The SUI model frequency range is  1.9-11GHz\n");
      end
    end
    txH::Float64 = model.txH
    rxH::Float64 = model.rxH
    # Terrain mode A
    mode = model.terrainKind
  	 a::Float64 = 4.6
  	 b::Float64 = 0.0075
  	 c::Float64 = 12.6
  	 s::Float64 = model.shadowFading  # shadow fading 8.2 dB and 10.6
  	 XhCF::Float64 = -10.8 #
     # Terrain mode B
  	if (mode == TerrainKind.B)
  		a = 4.0
  		b = 0.0065
  		c = 17.1
  	end
     # Terrain mode C
  	if (mode == TerrainKind.C)
  		a = 3.6
  		b = 0.005
  		c = 20
  			#// shadow fading 8.2 dB and 10.6c
  		XhCF = -20
  	end

  	d0::Float64 = 100;
  	A::Float64 = 20 * log10((4 * pi * d0) / (300 / f))
  	y::Float64 = (a - b * txH) + c / txH
  	Xf::Float64 = 6 * log10(f / 2000)
  	Xh::Float64 = XhCF * log10(rxH / 2)
  	return A + (10 * y * log10(d / d0)) + Xf + Xh + s
end


function scaledbessel(xij, qi, qj, ti, tj, mu, eta, kappa)

dim, N = size(xij);
rcut = eta[1]
alpha = eta[2]
nbf = kappa[1]

dij = sqrt.(xij[1,:].*xij[1,:] .+ xij[2,:].*xij[2,:] .+ xij[3,:].*xij[3,:]);
dr = xij./reshape(dij,(1,N));
x =  (1.0 .- exp.(-alpha*dij/rcut))/(1-exp(-alpha));
dx = -((alpha/rcut)*exp.(-(alpha*dij/rcut)))/(exp(-alpha) - 1);

rbf = zeros(N, nbf);
drbf = zeros(dim, N, nbf);
for i = 1:nbf
    a = i*pi;
    rbf[:,i] = (sqrt(2/rcut)/i)*(sin.(a*x)./dij);
    b = (sqrt(2/rcut)/i)*(sin.(a*x)./(dij.^2) .- (a*cos.(a*x).*dx)./dij);
    drbf[:,:,i] = -dr.*reshape(b,(1,N))
    # for j = 1:3
    #     drbf[j,:,i] = (xij[i,:].*b)./dij;
    # end
end

return rbf, drbf

end


mutable struct Vertex
    vector
    index
    body
    internal
    contact
end

Vertex(vector, index, body) = Vertex(vector, index, body, false, nothing)

mutable struct Contact
    vertex
    normal
    tangent
end

Contact(vertex) = Contact(vertex, 0., 0.)

function vertices(vectors, body)
    vertices = Array{Vertex,1}()
    for (index, vector) in enumerate(vectors)
        vertex = Vertex(vector, index, body)
        contact = Contact(vertex)
        vertex.contact = contact
        push!(vertices, vertex)
    end
    return vertices
end

"""
    center(vertices::Array{Vertex,1})

Compute the first moment ("center of mass") of the vertices.
"""
function center(vertices::Array{Vertex,1})
    A = area(vertices)
    M = Vector(0., 0.)
    for index in eachindex(vertices)
        vector = vertices[index].vector
        next_vector = vertices[index % length(vertices) + 1].vector
        cross_product = det(vector, next_vector)
        M += (vector + next_vector) * cross_product
    end
    return M / 6. / A
end

"""
    mean(vertices::Array{Vertex,1})

Compute the average position of the vertices.
"""
function mean(vertices::Array{Vertex,1})
    average = Vector(0., 0.)
    for vertex in vertices
        average += vertex.vector
    end
    return average / length(vertices)
end

"""
    area(vertices::Array{Vertex,1})

Compute the area bound by the convex hull of the vertices.
"""
function area(vertices::Array{Vertex,1})
    A = 0.
    prev_vector = vertices[end].vector
    for index in eachindex(vertices)
        vector = vertices[index].vector
        A += det(prev_vector, vector)
        prev_vector = vector
    end
    return A / 2.
end

"""
    inertia(vertices::Array{Vertex,1})

Compute the second moment ("moment of inertia") of the vertices.
"""
function inertia(vertices::Array{Vertex,1}, mass)
    numerator = 0.
    denominator = 0.
    for index in eachindex(vertices)
        vector = vertices[index].vector
        next_vector = vertices[index % length(vertices) + 1].vector
        cross_product = det(next_vector, vector)
        numerator += abs(cross_product) * (dot(next_vector, next_vector) + dot(next_vector, vector) + dot(vector, vector))
        denominator += cross_product
    end
    return mass / 6 * numerator / denominator
end

"""
    translate!(vertices::Array{Vertex,1}, translation::Vector)

Move vertices by translation.
"""
function translate!(vertices::Array{Vertex,1}, translation::Vector)
    for index in eachindex(vertices)
        vertices[index].vector += translation
    end
end

"""
    rotate!(vertices::Array{Vertex,1}, angle::Vector, point::Vector)

Rotate vertices by angle about a point.
"""
function rotate!(vertices::Array{Vertex,1}, angle, point::Vector)
    angle == 0. && return
    for index in eachindex(vertices)
        rotate!(vertices[index].vector, angle, point)
    end
end

"""
    contains(vertices::Array{Vertex,1}, point::Vector)

Check if a point is inside `vertices`.
"""
function contains(vertices::Array{Vertex,1}, point::Vector)
    for index in eachindex(vertices)
        vector = vertices[index].vector
        next_vector = vertices[index % length(vertices) + 1].vector
        if det(point - vector, next_vector - vector) > 0
            return false
        end
    end
    return true
end


#Wave equation Differential Equation
function square_grid_KG!(df,f,param,t)

    #Get Operators and variables
    Opps        = param[1]
    vars        = param[2]

    #Get numerical variables
    Nx  = vars.xnodes
    Ny  = vars.ynodes

    μ   = vars.μ

    #Get operators
    x   = Opps.xcoord
    y   = Opps.ycoord
    av  = Opps.aux_vex

    RHS     = Opps.DiffO_1
    BC      = Opps.BC_Mat
    Alpha   = Opps.Abs_Mat
    Lz      = Opps.Lz

    #Fields of our problem (Julia changes the rhs if you change the lhs)
    ψ   = f.x[1]
    ϕ   = f.x[2]

    dψ  = df.x[1]
    dϕ  = df.x[2]

    #Differential Equation
    if (vars.dissipation)

        #If Rotation is on,
        if( vars.Ω != 0.0 )

            #Update absorption matrix
            alpha!(Alpha , vars , Opps , t)

            #First Derivative
            dψ .=  ϕ

            #Second Derivative
            mul!(dϕ , RHS , ψ )
            av =  Alpha * (ϕ  + Lz * ψ)
            dϕ .=  dϕ .- av

        else

            # No need to get absorption matrix
            #alpha!( Alpha , vars , Opps , t)

            #First Derivative
            dψ .=  ϕ

            #Second derivative
            mul!(dϕ , RHS , ψ )
            av =  Alpha * (ϕ  + Lz * ψ)
            dϕ .=  dϕ .- av

        end

    else

        #First Derivative
        dψ .=  ϕ

        #Second Derivative
        mul!(dϕ , RHS , ψ )

    end

    #Boundary Conditions
    if(vars.Boundaries != :periodic )

        mul!(av , BC , dϕ )
       dϕ .= av

    end

end

function solve_wave_equation_2D(p::Param)

    #Welcome screen
    if( !setup_routine(p) )
        #End simulation
        println("Finished!")
        println(" ")
        println("Thank you for choosing KG Solver!")
        println("___________________________________________________________")
        print("Time Taken:  ")

        return
    end

    #Storage file
    println("Creating HDF5 file...")

    fdata   = "./data"
    fname   = fdata * p.folder *  "$(p.fname).h5"
    fid = h5open(fname, "w")

    HDF5.attributes(fid)["xmin"]      = p.xmin
    HDF5.attributes(fid)["xmax"]      = p.xmax
    HDF5.attributes(fid)["xnodes"]    = p.xnodes

    HDF5.attributes(fid)["ymin"]      = p.ymin
    HDF5.attributes(fid)["ymax"]      = p.ymax
    HDF5.attributes(fid)["ynodes"]    = p.ynodes

    if(p.Boundaries == :periodic )
        HDF5.attributes(fid)["Boundaries"]      = "periodic"
    else
        HDF5.attributes(fid)["Boundaries"]      = "radial"
    end

    if(p.dissipation == :true )
        HDF5.attributes(fid)["Dissipation"]     = "true"

        HDF5.attributes(fid)["R1"]          = p.R1
        HDF5.attributes(fid)["R2"]          = p.R2
        HDF5.attributes(fid)["Rorbit"]      = p.R_orbit

        HDF5.attributes(fid)["Omega"]           = p.Ω
        HDF5.attributes(fid)["alpha1"]          = p.alpha1
        HDF5.attributes(fid)["alpha2"]          = p.alpha2
    else
        HDF5.attributes(fid)["Dissipation"]     = "false"
    end

    println("OK")

    #Time variables
    tspan       = ( 0.0 , p.tmax )
    iter        = 0
    iter_save   = 0
    every       = floor(Int , p.out_every_t/p.deltat + 1.0)

    #Create operators structures
    println("Setting up Operators...")
    Operators = set_up_operator(p)
    println("OK")

    #Initial Conditions
    println("Setting up inicial configuration...")
    ψ , dψ = get_gaussian( p , Operators)

    U = ArrayPartition( Operators.BC_Mat * ψ , Operators.BC_Mat *dψ )
    println("OK")

    #Parameters
    my_params = ( Operators , p)

    #Problem ODE and integrator
    prob = ODEProblem(  square_grid_KG! , U , tspan , my_params )

    integrator = init( prob , RK4() , save_everystep=false , dt=p.deltat , adaptive=false )

    #Create a group for the functions
    g1 =  create_group(fid , "ψ");
    g2 =  create_group(fid , "dψ");

    #Save first configuration of field

    #data set names (first iteration is 0)
    dset_name = string("iteration_",iter_save)

    #data set for both displacement and derivative
    data1 = integrator.u.x[1]
    dset1, dtype1  = create_dataset(g1, dset_name , data1 )
    write_dataset(dset1, dtype1 , data1)

    data2 = integrator.u.x[2]
    dset2, dtype2  = create_dataset(g2, dset_name , data2 )
    write_dataset(dset2, dtype2 , data2)

    #Add HDF5.attributes time to both datasets
    HDF5.attributes(dset1)["time"] =  integrator.t
    HDF5.attributes(dset2)["time"] =  integrator.t

    #Progress bar and time evolution
    println("Starting up simulation...")
    prog1 = Progress( Int(floor(p.tmax))*100 )

    for (u,t) in tuples(integrator)

        #Increase iteration counter
        iter += 1

        #Step integrator
        DifferentialEquations.step!(integrator)

        #Update bar
        ProgressMeter.update!(prog1, Int(floor(integrator.t*100)))

        #If save iteration
        if iter % every == 0

            iter_save += 1

            #data set names
            dset_name = string("iteration_",iter_save)

            #data set for both displacement and derivative
            data1 = integrator.u.x[1]
            dset1, dtype1  = create_dataset(g1, dset_name , data1 )
            write_dataset(dset1, dtype1 , data1)

            data2 = integrator.u.x[2]
            dset2, dtype2  = create_dataset(g2, dset_name , data2 )
            write_dataset(dset2, dtype2 , data2)

            #Add HDF5.attributes time to both datasets
            HDF5.attributes(dset1)["time"] =  integrator.t
            HDF5.attributes(dset2)["time"] =  integrator.t

        end

    end

    #Add general information to file
    HDF5.attributes(fid)["max_iter"] = iter_save
    HDF5.attributes(fid)["max_time"] = integrator.t

    close(fid)
    println("Saving Files...")
    println("Finished!")

    #End simulation
    println(" ")
    println("Thank you for choosing KG Solver!")
    println("___________________________________________________________")

end


using FFTW, Parameters, DiffEqOperators, Setfield, Parameters
FFTW.set_provider!("mkl")
using LinearAlgebra, Plots, SparseArrays

#============== Exports =============#
export GenericParams, Params, Flags, State, getXs
export ∂ₓ, ∂ₓₓ, ∂ₓₓₓₓ, ∇², ∇⁴, LaplaceOperator

#================ Classes ===============#

#  PARAMS contain all relevant parameters
# of the state. They will later be stored
# in State.
struct GenericParams
    N::Int
    Δx::Real
    p::Dict
end

#  FLAGS contain all relevant flags of the
# state, i.e. wether to compute derivatives
# using FFT or FD, etc.
@with_kw struct Flags
    useFFT::Bool = true
    sym::Bool = false
    switch::Bool = false
end

#  STATE is the main class, it will contain
# the solution u, and relevant parameters
# in the form of a dictionary, it MUST contain
# N and Δx
@with_kw struct State
    gp::GenericParams
    u::Matrix
    flags::Flags = Flags()
end

#============== Base functions =============#

function LaplaceOperator(N, Δx)
    D2x = CenteredDifference(2, 2, Δx, N)
    Qx = PeriodicBC(Float64)
    sp = sparse(D2x * Qx)[1]
    kron(sparse(I,  N, N), sp) + kron(sp, sparse(I, N, N))
end

function LaplaceOperator1D(N, Δx)
    D2x = CenteredDifference(2, 2, Δx, N)
    Qx = Dirichlet0BC(typeof(Δx))
    sparse(D2x * Qx)[1]
end

#  Base function that computes matrix derivatives
# along x or y, for arbitrary order.
function deriv_mat(A::Matrix, Δx::Real; axis::Char='x', order::Int=1, useFFT::Bool=true)

    N = size(A)[1];
    if useFFT
        freqs = fftfreq(N) * N;
        L = (N-1) * Δx;

        if axis == 'y'
            dim = 1;
            k = [freqs[i] for i in 1:N, j in 1:N];
        elseif axis == 'x'
            dim = 2;
            k = [freqs[j] for i in 1:N, j in 1:N];
        else
            println("Axis not understood");
        end
        real.(ifft( (2π / L * im) ^ order * k .^ order .* fft(A) ))
    else
        B = similar(A) # assuming order = 2
        for i in 1:N, j in 1:N
            j₋ = if j > 1 j - 1 else N end
            j₊ = if j < N j + 1 else 1 end
            i₊ = if i < N i + 1 else 1 end
            i₋ = if i > 1 i - 1 else N end
            B[j, i] = A[j₋, i] + A[j₊, i] + A[j, i₋] + A[j, i₊] - 4 * A[j, i]
        end
        B
    end
end

function getXs(gp::GenericParams)
    (0:gp.N-1) * gp.Δx
end

function getXs(S::State)
    getXs(S.gp)
end

### Definitions to simplify the code

∂ₓ(S::State; axis='x') = deriv_mat(S.u, S.gp.Δx, axis=axis, order=1, useFFT=S.flags.useFFT);
∂ₓₓ(S::State; axis='x') = deriv_mat(S.u, S.gp.Δx, axis=axis, order=2, useFFT=S.flags.useFFT);
∂ₓₓₓₓ(S::State; axis='x') = deriv_mat(S.u, S.gp.Δx, axis=axis, order=4, useFFT=S.flags.useFFT);

∇²(S::State) = ∂ₓₓ(S) + ∂ₓₓ(S, axis='y');
∇⁴(S::State) = ∂ₓₓₓₓ(S) + ∂ₓₓₓₓ(S, axis='y');

function ∇²(N::Int, Δx::Real; dim::Int=2)
    if dim == 2
        LaplaceOperator(N, Δx)
    elseif dim == 1
        LaplaceOperator1D(N, Δx)
    end
end

∇⁴(N::Int, Δx::Real; dim::Int=2) = ∇²(N, Δx; dim)^2

∇²(V::Array, N::Int, Δx::Real; dim::Int=2) = ∇²(N, Δx; dim) * V
∇⁴(V::Array, N::Int, Δx::Real; dim::Int=2) = ∇⁴(N, Δx; dim) * V


using StochasticDiffEq
using Plots; pyplot()
using LaTeXStrings
f(u,p,t) = -1000.0u*(1-u)*(2-u)
g(u,p,t) = 10.0
prob = SDEProblem(f,g,2.0,(0.0,5.0))

sol = solve(prob,SOSRI(),abstol=1e-2,reltol=1e-2,saveat=0.025,seed=3)
plot(sol,legend=false,yaxis = L"X_t",title = "Pathwise Stiffness Example",
     xtickfont = font(16, "Latin Modern Roman"),titlefont = font(20, "Latin Modern Roman"),
     ytickfont = font(16, "Latin Modern Roman"),guidefont = font(18, "Latin Modern Roman"))

savefig("pathwise_stiffness.png")
savefig("pathwise_stiffness.pdf")


using ProfileView
using BenchmarkTools
using PolynomialBasis
using ImplicitDomainQuadrature

IDQ = ImplicitDomainQuadrature

function plane_distance_function(coords, normal, x0)
    return (coords .- x0)' * normal
end

x0 = [0.0,0.0]
normal = [1.0,0.0]
polyorder = 3
numqp = 5
levelset = InterpolatingPolynomial(1,2,polyorder)
levelsetcoeffs = plane_distance_function(levelset.basis.points,normal,x0)
update!(levelset,levelsetcoeffs)

# @profiler run_quadrature_iterations(levelset,numqp,100)


function generictest_dixon_Fp(G, p = SymbolicWedderburn.dixon_prime(G))
    F = SymbolicWedderburn.FiniteFields.GF{p}
    ccG = conjugacy_classes(G)
    Ns = [SymbolicWedderburn.CMMatrix(ccG, i) for i = 1:length(ccG)]
    @test isdiag(SymbolicWedderburn.common_esd(Ns, F))
    esd = SymbolicWedderburn.common_esd(Ns, F)

    let W = esd.basis
        @test inv(W) isa Matrix{F}
        @test isone(inv(W) * W)
        iW = inv(W)
        for N in Ns
            # basis actually diagonalizes all Ns
            m = W * N * iW
            @test isdiag(m)
        end
    end

    @test SymbolicWedderburn.characters_dixon(ccG, F) isa
          Vector{<:SymbolicWedderburn.Character}

    chars = SymbolicWedderburn.characters_dixon(ccG, F)

    # checking the degrees
    @test sum(degree.(chars) .^ 2) == order(G)

    # orthogonality of characters over F
    @test [dot(χ, ψ) for χ in chars, ψ in chars] == I
end

function generictest_dixon_C(G, p = SymbolicWedderburn.dixon_prime(G))
    F = SymbolicWedderburn.FiniteFields.GF{p}
    ccG = conjugacy_classes(G)
    chars_Fp = SymbolicWedderburn.characters_dixon(ccG, F)

    degrees = degree.(chars_Fp)

    m = SymbolicWedderburn._multiplicities(chars_Fp)
    for i = 1:size(m, 1)
        @test all(Int.(m[i, :, :]) .<= degrees[i])
    end

    @test SymbolicWedderburn.complex_characters(chars_Fp) isa
          Vector{<:SymbolicWedderburn.Character}
    @test length(SymbolicWedderburn.complex_characters(chars_Fp[1:1])) == 1

    chars_CC = SymbolicWedderburn.complex_characters(chars_Fp)
    id = one(first(first(ccG)))
    @test [ψ(id) for ψ in chars_CC] == degrees

    # orthogonality of characters over ℂ
    @test [dot(χ, ψ) for χ in chars_CC, ψ in chars_CC] == I
end

@testset "Dixon Algorithm" begin
    @testset "DixonPrimes" begin
        @test SymbolicWedderburn.dixon_prime(20, 20) == 41

        @testset "random" begin
            for n in rand(2:1000, 10)
                F = SymbolicWedderburn.Primes.factor(n)
                e = lcm(collect(keys(F)))
                p = SymbolicWedderburn.dixon_prime(n, e)
                @test mod(p, e) == 1
                @test p > 2 * floor(sqrt(n))
            end
        end

        @testset "DixonPrimeGroups" begin
            G = PermutationGroups.SymmetricGroup(4)
            ccG = conjugacy_classes(G)
            @test exponent(G) == 12
            @test exponent(ccG) == 12
            @test SymbolicWedderburn.dixon_prime(G) ==
                  SymbolicWedderburn.dixon_prime(ccG)
        end
    end

    @testset "Dixon over GF{p}" begin
        @testset "Example: Alt(4)" begin
            G = PermGroup([perm"(1,2,3)(4)", perm"(2,3,4)"])
            ccG = let S = gens(G)
                [
                    Orbit(S, one(G)),
                    Orbit(S, perm"(2,3,4)"),
                    Orbit(S, perm"(2,4,3)"),
                    Orbit(S, perm"(1,2)(3,4)"),
                ] # the order is taken from GAP
            end

            let ccG = ccG, p = 29
                F = SymbolicWedderburn.FiniteFields.GF{p}
                Ns = [SymbolicWedderburn.CMMatrix(ccG, i) for i = 1:length(ccG)]
                @test_throws AssertionError SymbolicWedderburn.common_esd(Ns, F)
            end

            let ccG = ccG, p = 31
                F = SymbolicWedderburn.FiniteFields.GF{p}
                Ns = [SymbolicWedderburn.CMMatrix(ccG, i) for i = 1:length(ccG)]
                esd = SymbolicWedderburn.EigenSpaceDecomposition(F.(Ns[1]))
                esd = SymbolicWedderburn.refine(esd, F.(Ns[2]))
                esd = SymbolicWedderburn.refine(esd, F.(Ns[3]))
                @test isdiag(esd)

                @test isdiag(SymbolicWedderburn.common_esd(Ns, F))
            end

            # generic tests
            generictest_dixon_Fp(G)

            # tests specific to G
            p = SymbolicWedderburn.dixon_prime(ccG)
            F = SymbolicWedderburn.FiniteFields.GF{p}

            chars = SymbolicWedderburn.characters_dixon(ccG, F)

            @test sort(degree.(chars)) == [1, 1, 1, 3]

            @test Set(Int.(χ.vals) for χ in chars) ==
                  Set([[3, 0, 0, 6], [1, 4, 2, 1], [1, 2, 4, 1], [1, 1, 1, 1]])

        end
    end

    @testset "Lifting characters to ℂ" begin
        @testset "Example: Alt(4)" begin
            G = PermGroup([perm"(1,2,3)(4)", perm"(2,3,4)"])
            ccG = let S = gens(G)
                [
                    Orbit(S, one(G)),
                    Orbit(S, perm"(2,3,4)"),
                    Orbit(S, perm"(2,4,3)"),
                    Orbit(S, perm"(1,2)(3,4)"),
                ] # the order of cclasses is taken from GAP
            end

            @testset "PowerMap" begin
                pmG = SymbolicWedderburn.PowerMap(ccG)
                @test size(pmG) == (4, 6)
                @test pmG[:, 0] == ones(Int, 4)
                @test pmG[:, 1] == 1:4
                @test pmG[:, 2] == [1, 3, 2, 1]
                @test pmG[:, 3] == [1, 1, 1, 4]
                @test pmG[:, 4] == [1, 2, 3, 1]
                @test pmG[:, 5] == [1, 3, 2, 4]
                @test all(isone.(first.(ccG) .^ 6))
            end

            # generic tests
            generictest_dixon_C(G)

            chars_C = SymbolicWedderburn.characters_dixon(G)
            E = SymbolicWedderburn.Cyclotomics.E

            @test [χ.vals for χ in chars_C] == [
                E(3, 0) .* [3, 0, 0, -1],
                E(3, 0) .* [1, E(3, 2), E(3, 1), 1],
                E(3, 0) .* [1, E(3, 1), E(3, 2), 1],
                E(3, 0) .* [1, 1, 1, 1],
            ]
        end

        @testset "Example: Sym(4)" begin
            G = PermGroup([perm"(1,2)", perm"(1,2,3,4)"])
            ccG = let S = gens(G)
                ccG = [
                    Orbit(S, one(G)),
                    Orbit(S, perm"(1,2)(4)"),
                    Orbit(S, perm"(1,2)(3,4)"),
                    Orbit(S, perm"(1,2,3)(4)"),
                    Orbit(S, perm"(1,2,3,4)"),
                ] # the order of cclasses is taken from GAP
            end
            generictest_dixon_Fp(G)
            generictest_dixon_C(G)

            chars = SymbolicWedderburn.characters_dixon(ccG)

            @test sort(degree.(chars)) == [1, 1, 2, 3, 3]
            @test [χ.vals for χ in chars] == [
                [2, 0, 2, -1, 0],
                [3, 1, -1, 0, -1],
                [1, 1, 1, 1, 1],
                [1, -1, 1, 1, -1],
                [3, -1, -1, 0, 1],
            ]
        end

        @testset "example: C₅ ⋊ C₄" begin
            S = [perm"(1,2,4,5,3)", perm"(2,5,3,4)"]
            G = PermGroup(S)

            ccG = [
                Orbit(S, one(G)),
                Orbit(S, perm"(2,3)(4,5)"),
                Orbit(S, perm"(2,4,3,5)"),
                Orbit(S, perm"(2,5,3,4)"),
                Orbit(S, perm"(1,2,4,5,3)"),
            ]
            # the order of cclasses is taken from GAP
            generictest_dixon_Fp(G)
            generictest_dixon_C(G)

            chars = SymbolicWedderburn.characters_dixon(ccG)

            @test sort(degree.(chars)) == [1, 1, 1, 1, 4]
            @test [χ.vals for χ in chars] == [
                E(4, 0) .* [4, 0, 0, 0, -1],
                E(4, 0) .* [1, 1, 1, 1, 1],
                E(4, 0) .* [1, 1, -1, -1, 1],
                E(4, 0) .* [1, -1, E(4), -E(4), 1],
                E(4, 0) .* [1, -1, -E(4), E(4), 1],
            ]
        end
    end

    @testset "SmallPermGroups" begin
        include("smallgroups.jl")
        for (ord, groups) in SmallPermGroups
            @testset "SmallGroup($ord, $n)" for (n, G) in enumerate(groups)
                @test SymbolicWedderburn.characters_dixon(G) isa
                      Vector{<:SymbolicWedderburn.Character}
                generictest_dixon_Fp(G)
                generictest_dixon_C(G)
            end
        end
    end
end

@testset "Characters io" begin
    G = PermGroup([perm"(2,3)(4,5)"])
    chars = SymbolicWedderburn.characters_dixon(G)

    @test sprint(show, chars[1]) == "SymbolicWedderburn.Character: [1, 1]"
    @test sprint(show, chars[2]) == "SymbolicWedderburn.Character: [1, -1]"

    @test sprint(show, MIME"text/plain"(), chars[1]) ==
        "SymbolicWedderburn.Character over Cyclotomic{Int64,SparseArrays.SparseVector{Int64,Int64}}\n()^G         → \t 1*E(1)^0\n(2,3)(4,5)^G → \t 1*E(1)^0"
    @test sprint(show, MIME"text/plain"(), chars[2]) ==
        "SymbolicWedderburn.Character over Cyclotomic{Int64,SparseArrays.SparseVector{Int64,Int64}}\n()^G         → \t 1*E(1)^0\n(2,3)(4,5)^G → \t-1*E(1)^0"
end


function getTarget(filename)
    values = readlines(filename*".txt")
    values = split(values[1], ':')
    values = values[2]
    values = split(values, ',')
    xstring = values[1]
    xstring = split(xstring, '=')
    xstring = xstring[2]
    xstring = split(xstring, "..")
    x1 = parse(Int, xstring[1])
    x2 = parse(Int, xstring[2])
    ystring = values[2]
    ystring = split(ystring, '=')
    ystring = ystring[2]
    ystring = split(ystring, "..")
    y1 = parse(Int, ystring[1])
    y2 = parse(Int, ystring[2])

    return (x1,x2), (y1, y2)
end

function step((x,y), (vx, vy))
    newx = x + vx
    newy = y + vy
    newvx = vx - sign(vx)
    newvy = vy - 1
    return (newx, newy), (newvx, newvy)
end

function checkTrajectory(startpos, startv, target)
    v = startv
    pos = startpos
    maxy = pos[2]
    valid = false
    while (pos[2] >= target[2][1] && pos[1] <= target[1][2])
        pos, v = step(pos, v)
        if pos[2] > maxy
            maxy = pos[2]
        end
        if pos[1] >= target[1][1] && pos[1] <= target[1][2] && pos[2] >= target[2][1] && pos[2] <= target[2][2]
            valid = true
        end
    end
    return valid, maxy
end

function solve(filename)
    xtarget, ytarget = getTarget(filename)
    pos = (0,0)
    maxy = 0
    optv = (1,1)
    valcount = 0
    for vx in -1000:1000
        for vy in -1000:1000
            v = (vx, vy)
            valid, my = checkTrajectory(pos, v, (xtarget, ytarget))
            if valid == true && my > maxy
                maxy = my
                optv = v
            end
            if valid == true
                valcount += 1
            end
        end
    end
    println(maxy)
    println(optv)
    println(valcount)
end

solve("input")
#solve("example")


# ogm_ls.jl

using MIRT: ogm_ls

using LinearAlgebra: norm, opnorm, I
using Random: seed!
using Plots: plot, plot!, scatter!, gui
using LaTeXStrings
using Test: @test, @test_throws, @inferred

seed!(0); M = 30; N = 6; A = randn(M,N); y = randn(M)
a2 = opnorm(A)^2
reg = 0.1 * a2
xh = (A'A + reg*I) \ A'y
cost = (x) -> 1/2 * norm(A * x - y)^2 + reg/2 * norm(x)^2
fun = (x,iter) -> (cost(x) - cost(xh), norm(x - xh) / norm(xh), time())
grad1 = (x) -> A' * (A * x - y) + reg * x
curv1 = (x) -> a2 + reg

B = [A, I] # matrix blocks
gradf = [u -> u - y, v -> reg * v] # f functions gradients
curvf = [v -> 1, v -> reg]

niter = 90
#x1, out1 = @inferred ogm_ls(   grad1, curv1, zeros(N), niter=niter, fun=fun) todo: fails
x1, out1 = ogm_ls(   grad1, curv1, zeros(N), niter=niter, fun=fun)
x2, out2 = ogm_ls(B, gradf, curvf, zeros(N), niter=niter, fun=fun)

#@test x1 ≈ xh # no, converges too slowly
@test x2 ≈ xh

if isinteractive()
	lf = x -> log10(max(x,1e-17))
	costk = out -> lf.([out[k][1] for k=1:niter+1])
	errk = out -> lf.([out[k][2] for k=1:niter+1])
	allk = out -> (costk(out), errk(out))
	cost1, err1 = allk(out1)
	cost2, err2 = allk(out2)

	k = 0:niter
	plot(xlabel="k", ylabel=L"\log(\Psi(x_k) - \Psi(x_*))")
	scatter!(k, cost1, color=:blue, label="cost1")
	scatter!(k, cost2, color=:red, marker=:x, label="cost2")
	p1 = plot!()

	plot(xlabel="k", ylabel=L"\log(\|x_k - x_*\|/\|x_*\|)")
	scatter!(k, err1, color=:blue, label="NRMSD1")
	scatter!(k, err2, color=:red, marker=:x, label="NRMSD2")
	p2 = plot!()
	plot(p1, p2)
	gui()
end


export fit,
       #fitA,
       #fitB,
       predict,
       FreeLSStateTomo,
       LSStateTomo,
       MLStateTomo

function build_state_predictor(obs::Vector{Matrix{T}}) where T
    return reduce(vcat,[vec(o)' for o in obs])
end

"""
`FreeLSStateTomo`

Free (unconstrained) least-squares state tomography algorithm. It is
constructed from a vector of observables corresponding to
measurements that are performed on the state being reconstructed.
"""
struct FreeLSStateTomo
    inputdim::Int
    outputdim::Int
    pred::Matrix{ComplexF64}
    function FreeLSStateTomo(obs::Vector)
        @assert all([LinearAlgebra.ishermitian(o) for o in obs]) "Observables must be Hermitian"
        pred = build_state_predictor(obs)
        outputdim = size(pred,1)
        inputdim = size(pred,2)
        new(inputdim,outputdim,pred)
    end
end

"""
`predict`

Predict outcomes of a tomography experiment for a given state (density matrix).
"""
function predict(method::FreeLSStateTomo, state)
    method.pred*vec(state)
end

"""
`fit(method, ...)`

Reconstruct a state from observations (i.e., perform state tomography). Exactly which fitting
routine is used depends on the type of the `method`. The possible types are

   + FreeLSStateTomo : unconstrained least-squares state tomography
   + LSStateTomo : least-squares state tomography constrained to physical states
   + MLStateTomo : maximum-likelihood, maximum-entropy state tomography constrained to physical states

"""
function fit(method::FreeLSStateTomo,
             means::Vector{Float64})
    if length(means) != method.outputdim
        error("The number of expected means does not match the required number of experiments")
    end
    d = round(Int, method.inputdim |> sqrt)
    reg = method.pred\means
    return reshape(reg,d,d), LinearAlgebra.norm(method.pred*reg-means,2)/length(means), :Optimal
end

function fit(method::FreeLSStateTomo,
             means::Vector{Float64},
             vars::Vector{Float64})
    if length(means) != method.outputdim
        error("The number of expected means does not match the required number of experiments")
    end
    d = round(Int, method.inputdim |> sqrt)
    if any(vars .< 0)
        error("Variances must be positive for generalized least squares.")
    end

    reg = (LinearAlgebra.Diagonal(1 ./ sqrt(vars)) * method.pred) \ (LinearAlgebra.Diagonal(1 ./ sqrt(vars)) * means)
    return reshape(reg,d,d),
           sqrt(dot(method.pred*reg-means,LinearAlgebra.Diagonal(vars)\(method.pred*reg-means)))/length(means),
           :Optimal
end

"""
`LSStateTomo`

Type corresponding to constrained least-squares state tomography
algorithm. It is constructed from a vector of observables
corresponding to measurements that are performed on the state being
reconstructed.

The outcome of said measurements for a given density matrix ρ can be
computed using the `predict` function , while the density matrix can
be estimated from observations (including variances) by using the
`fit` function.

"""
struct LSStateTomo
    inputdim::Int
    outputdim::Int
    realpred::Matrix{Float64}
    function LSStateTomo(obs::Vector)
        @assert all([LinearAlgebra.ishermitian(o) for o in obs]) "Observables must be Hermitian"
        pred = build_state_predictor(obs)
        outputdim = size(pred,1)
        inputdim = size(pred,2)
        realpred = [real(pred) imag(pred)];
        new(inputdim,outputdim,realpred)
    end
end

function predict(method::LSStateTomo, state)
    (method.realpred[:,1:round(Int,end/2)]+1im*method.realpred[:,round(Int,end/2)+1:end])*vec(state)
end

function fit(method::LSStateTomo,
             means::Vector{Float64},
             vars::Vector{Float64};
             #solver = MosekSolver(LOG=0))
             solver = SCS.SCSSolver(verbose=0, max_iters=10_000, eps = 1e-8))

    if length(means) != length(vars) || method.outputdim != length(means)
        error("Size of observations and/or predictons do not match.")
    end
    dsq = method.inputdim
    d = round(Int,sqrt(dsq))

    # We assume that the predictions are always real-valued
    # and we need to do the complex->real translation manually since
    # Convex.jl does not support complex numbers yet
    ivars = 1 ./ sqrt.(vars)

    ρr = Convex.Variable(d,d)
    ρi = Convex.Variable(d,d)

    constraints = Convex.tr(ρr) == 1
    constraints += Convex.tr(ρi) == 0
    constraints += Convex.isposdef([ρr ρi; -ρi ρr])

    # TODO: use quad_form instead of vecnorm? Have 1/vars are diagonal quadratic form
    problem = Convex.minimize( LinearAlgebra.norm( (means - method.realpred*[vec(ρr); vec(ρi)]) .* ivars, 2), constraints )

    Convex.solve!(problem, solver)

    return (ρr.value - 1im*ρi.value), problem.optval^2, problem.status
end

"""
`MLStateTomo`

Maximum-likelihood maximum-entropy quantum state tomography algorithm. It is
constructed from a collection of observables corresponding to
measurements that are performed on the state being reconstructed, as
well as a hedging factor β. If β=0, no hedging is applied. If β > 0
either a log determinant or log minimum eigenvalue penalty is applied.

**AT THE MOMENT HEDGING IS ONLY APPLIED IN fitA, AND IS NOT CURRENTLY WORKING**
"""
struct MLStateTomo
    effects::Vector{Matrix{ComplexF64}}
    dim::Int64
    β::Float64
    # TODO: perhaps for ML it is better to have the observables pecified as
    #       a vector of vectors of observables? Each vector of observable is a POVM
    #       that should add up to the identity
    function MLStateTomo(v::Vector,β=0.0)
        for e in v
            if !LinearAlgebra.ishermitian(e) || ! QuantumInfo.ispossemidef(e) || real(LinearAlgebra.tr(e))>1
                error("MLStateTomo state tomography is parameterized by POVM effects only.")
            end
        end
        sv = sum(v)
        if ! LinearAlgebra.isdiag(sv) || !isapprox(maximum(abs.(LinearAlgebra.diag(sv)))-minimum(abs.(LinearAlgebra.diag(sv))),0.0)
            error("POVM effects must add up to the identity.")
        end
        if !all([size(e,1)==size(e,2) for e in v]) || !all([size(v[1],1)==size(e,1) for e in v])
                error("All effects must be square matrices, and they must have have the same dimension.")
        end
        if β < 0
            error("Hedging penalty must be positive.")
        end
        new(v,size(v[1],1),β)
    end
end

function predict(method::MLStateTomo,ρ)
    Float64[ real(LinearAlgebra.tr(ρ*e)) for e in method.effects]
end

"""
`fitA(method::MLStateTomo, freq, solver=SCSSolver)`

This is a state tomography fitting routine using convex optimization. It's use is currently
discouraged simply because it is much slower than the iterative solver, and often does not
conver to a solution that meets the optimality criteria.
"""
function fitA(method::MLStateTomo,
             freq::Vector;
             #solver = MosekSolver(LOG=0))
             solver = SCSSolver(verbose=0, max_iters=100_000, eps = 1e-8))

    if length(method.effects) != length(freq)
        error("Vector of counts and vector of effects must have same length, but length(counts) == $(length(counts)) != $(length(method.effects))")
    end

    d = method.dim

    # ρr = Convex.Variable(d,d)
    ρr = Semidefinite(d)
    ρi = Convex.Variable(d,d)

    ϕ(m) = [real(m) -imag(m); imag(m) real(m)];
    ϕ(r,i) = [r i; -i r]

    obj = freq[1] * log(Convex.tr([ρr ρi; -ρi ρr]*ϕ(method.effects[1])))
    for i=2:length(method.effects)
        obj += freq[i] * log(Convex.tr([ρr ρi; -ρi ρr]*ϕ(method.effects[i])))
    end
    # add hedging regularization
    if method.β != 0.0
        obj += method.β*log(lambdamin([ρr ρi; -ρi ρr]))
    end

    constraints = Convex.tr(ρr) == 1
    constraints += Convex.tr(ρi) == 0
    constraints += Convex.isposdef([ρr ρi; -ρi ρr])

    problem = Convex.maximize(obj, constraints)

    Convex.solve!(problem, solver)

    return ρr.value-im*ρi.value, problem.optval, problem.status
end

"""
`fitB(method::MLStateTomo, freq, solver=SCSSolver)`

This is a state tomography fitting routine using convex optimization. It's use is currently
discouraged simply because it is much slower than the iterative solver, and often does not
conver to a solution that meets the optimality criteria, much like fitB.
"""
function fitB(method::MLStateTomo,
             freq::Vector;
             #solver = MosekSolver(LOG=0))
             solver = SCSSolver(verbose=0, max_iters=100_000, eps = 1e-8))

    if length(method.effects) != length(freq)
        error("Vector of counts and vector of effects must have same length, but length(counts) == $(length(counts)) != $(length(method.effects))")
    end

    d = method.dim

    ρr = Convex.Semidefinite(d)
    ρi = Convex.Variable(d,d)
    p  = Convex.Variable(length(method.effects),Positive())

    ϕ(m) = [real(m) -imag(m); imag(m) real(m)];
    ϕ(r,i) = [r i; -i r]
    obj = freq[1] * log(p[1])
    for i=2:length(method.effects)
        obj += freq[i] * log(p[i])
    end
    #obj += method.β!=0.0 ? method.β*logdet([ρr ρi; -ρi ρr]) : 0.0
    #obj += method.β!=0.0 ? method.β*log(lambdamin([ρr ρi; -ρi ρr])) : 0.0

    constraints = Convex.tr(ρr) == 1
    constraints += Convex.tr(ρi) == 0
    for i=1:length(method.effects)
        constraints += p[i] == Convex.tr([ρr ρi; -ρi ρr]*ϕ(method.effects[i]))/2
    end
    constraints += Convex.isposdef([ρr ρi; -ρi ρr])

    problem = Convex.maximize(obj, constraints)

    Convex.solve!(problem, solver)

    return ρr.value-im*ρi.value, problem.optval, problem.status
end

function R(ρ,E,obs)
    pr = Float64[real(LinearAlgebra.tr(ρ*Ei)) for Ei in E]
    R = obs[1]/pr[1]*E[1]
    for i in 2:length(E)
        R += obs[i]/pr[i]*E[i]
    end
    R
end

function LL(ρ,E,obs)
    pr = Float64[real(LinearAlgebra.tr(ρ*Ei)) for Ei in E]
    sum(obs.*log.(pr))
end

function fit(method::MLStateTomo,
             freq::Vector;
             δ=.005, # a more natural parameterization of "dilution"
             λ=0.0,  # entropy penalty
             tol=1e-9,
             maxiter=100_000,
             ρ0 = QuantumInfo.eye(ComplexF64,method.dim))
    ϵ=1/δ
    iter = 1
    ρk = copy(ρ0)
    ρktemp = similar(ρk)
    ρkm = similar(ρ0) #Array(ComplexF64,method.dim,method.dim)
    status = :Optimal
    while true
        copyto!(ρkm,ρk)
        if iter >= maxiter
            status = :MaxIter
            break
        end
        # diluted iterative scheme for ML (from PRA 75 042108 2007)
        Tk = λ > 0.0 ?
            (1 .+ ϵ * (R(ρk,method.effects,freq) .- QuantumInfo.eye(method.dim) .-
                       λ * (log(ρk) .- LinearAlgebra.tr(ρk*log(ρk)))))/(1+ϵ) :
                       (1 .+ ϵ * R(ρk,method.effects,freq)) / (1 + ϵ)
        ρktemp .= ρk * (1 .+ ϵ * Tk)/( 1 + ϵ)
        ρk .= (1 .+ ϵ * Tk) / (1 + ϵ) * ρktemp
        # LinearAlgebra.A_mul_B!(ρktemp, ρk, (1 .+ ϵ * Tk)/( 1 + ϵ)) # ρk = ρk * (1+ϵRk)/(1+ϵ)
        # LinearAlgebra.A_mul_B!(ρk, (1+ϵ*Tk) / (1+ϵ), ρktemp) # ρk = (1+ϵRk) * ρk/(1+ϵ)
        QuantumInfo.trnormalize!(ρk)
        if LinearAlgebra.norm(ρk-ρkm)/method.dim^2 < tol
            status = :Optimal
            break
        end
        iter += 1
    end
    return ρk, LL(ρk,method.effects,freq), status
end


function part1(input)
    bingo = parse_input(input)
    winner, last_number = play!(bingo, true)
    return last_number * sum(line.sum_left for line in winner.lines[1:5])
end

function part2(input)
    bingo = parse_input(input)
    last_winner, last_number = play!(bingo, false)
    return last_number * sum(line.sum_left for line in last_winner.lines[1:5])
end

mutable struct Line
    sum_left::Int
    num_left::Int
    board::Int
end

struct Board
    lines::Vector{Line}
end

struct Bingo
    numbers::Vector{Int}
    boards::Vector{Board}
    affected_lines::Dict{Int, Vector{Line}}
end

function parse_input(input)
    parts = split(readchomp(input), "\n\n")
    numbers = parse.(Int, split(first(parts), ","))
    affected_lines = Dict{Int, Vector{Line}}()
    boards = Board[]
    for i = 2:length(parts)
        part = parts[i]
        push!(boards, parse_board(i - 1, part, affected_lines))
    end
    return Bingo(numbers, boards, affected_lines)
end

function parse_board(n, board, affected_lines)
    board = reshape(parse.(Int, split(board)), 5, 5)
    lines = Line[]
    for line_numbers in vcat(collect(eachrow(board)), collect(eachcol(board)))
        line = Line(sum(line_numbers), length(line_numbers), n)
        push!(lines, line)
        for number in line_numbers
            push!(get!(affected_lines, number, Line[]), line)
        end
    end
    return Board(lines)
end

function play!(bingo, win)
    winners = Int[]
    for number in bingo.numbers
        for line in bingo.affected_lines[number]
            line.sum_left -= number
            line.num_left -= 1
            if line.num_left == 0
                union!(winners, line.board)
            end
        end
        if win && !isempty(winners)
            return bingo.boards[only(winners)], number
        end
        if !win && length(winners) == length(bingo.boards)
            return bingo.boards[last(winners)], number
        end
    end
    @assert false
end


# Test @assume and @predict macros on a model without conditioning.

using Turing
using Test

@model test_assume() = begin
  x ~ Bernoulli(1)
  y ~ Bernoulli(x / 2)
  x, y
end

smc = SMC(10000)
pg = PG(10,1000)

res = sample(test_assume(), smc)

@test reduce(&, res[:x]) == 1 # check that x is always 1
@test mean(res[:y]) ≈ 0.5 atol=0.1 # check that the mean of y is between 0.4 and 0.6

res = sample(test_assume(), pg)

@test reduce(&, res[:x]) == 1 # check that x is always 1
@test mean(res[:y]) ≈ 0.5 atol=0.1 # check that the mean of y is between 0.4 and 0.6


using JuMP
using ConstraintSolver
using JSON

CS = ConstraintSolver
include("../../test/sudoku_fcts.jl")

function parseJSON(json_sums)
    sums = []
    for s in json_sums
        indices = Tuple[]
        for ind in s["indices"]
            push!(indices, tuple(ind...))
        end

        push!(sums, (result=s["result"], indices=indices))
    end
    return sums
end

function solve_all(filenames; benchmark=false, single_times=true)
    ct = time()
    for (i,filename) in enumerate(filenames)
        sums = parseJSON(JSON.parsefile("data/$(filename)"))

        m = Model(with_optimizer(CS.Optimizer))
        @variable(m, 1 <= x[1:9,1:9] <= 9, Int)

        for s in sums
            @constraint(m, sum([x[ind...] for ind in s.indices]) == s.result)
            # @constraint(m, [x[ind...] for ind in s.indices] in CS.AllDifferentSet(length(s.indices)))
        end

        # sudoku constraints
        jump_add_sudoku_constr!(m, x)

        if single_times
            GC.enable(false)
            t = time()
            optimize!(m)
            status = JuMP.termination_status(m)
            t = time()-t
            GC.enable(true)
            println(i-1,", ", t)
        else
            GC.enable(false)
            optimize!(m)
            status = JuMP.termination_status(m)
            GC.enable(true)
        end
        if !benchmark
            @show JuMP.backend(m).optimizer.model.inner.info
            println("Status: ", status)
            @assert jump_fulfills_sudoku_constr(JuMP.value.(x))
        end
        com = nothing
        GC.gc()
    end
    println("")
    tt = time()-ct
    println("total time: ", tt)
    println("avg: ", tt/length(filenames))
end

function main(; benchmark=false, single_times=true)
    solve_all(["niallsudoku_5500", "niallsudoku_5501", "niallsudoku_5502", "niallsudoku_5503"]; benchmark=benchmark, single_times=single_times)
    # solve_all(from_file("hardest.txt"), "hardest")
end


import Base.-
import Base.getindex
import Base.setindex!
import Base.*
import Base.+
import Base./
import Base.copy
import Base.zero
using Distributions
mutable struct MvGaussianExponential
    n::Tuple{Vector{Float64},Matrix{Float64}} # Expectation Parameter
    gradG::Tuple{Vector{Float64},Matrix{Float64}}
    G::Float64
end
function MvGaussianExponential(data::Matrix)
    Σ = cov(data, dims=2)
    μ = vec(mean(data, dims=2))
    n = (μ, -(Σ + μ*μ'))
    (G, gradG) = compute_G(n[1], n[2])
    return MvGaussianExponential(
        n,
        gradG,
        G)
end
function sufficient_statistic(::Type{MvGaussianExponential}, x::Vector)
    x1 = zeros(1,size(x,1))
    x2 = zeros(1,1,size(x,1))
    for i=1:size(x,2)
        v = x[i]
        x2[1,1,i] = -v*v'
        x1[1,i] = v
    end
    return (x1, x2)
end

function sufficient_statistic(::Type{MvGaussianExponential}, x::AbstractArray{<:Number,2})
    x1 = zeros(size(x,1), size(x,2))
    x2 = zeros(size(x,1), size(x,1), size(x,2))
    for i=1:size(x,2)
        v = vec(x[:,i])
        x2[:,:,i] = -v*v'
        x1[:,i]  = v
    end
    return (x1, x2)
end
function dimension(g::MvGaussianExponential)
    c = length(g.n)
    c= round(Integer,(-1+sqrt(1+4*c))/2)
    return c
end
function get_parameters(v::Vector{Float64}, d::Integer)
     n = v[1:d]
     H = reshape(v[d+1:end],(d,d))
    return (n,H)
end

function compute_G(n::Vector, H::Matrix)
    d = size(n, 1)
    invH = inv(H)
    G =  -0.5 * real(log(Complex(1.0001+ n'*invH*n))) -
        0.5*log(det(-H)+eps()) -
        (d/2)*log(2*pi*2.71)
    #Satistical exponential families:  A digest with flash cards
    #Frank Nielsen,  and Vincent Garcia
    tmp = inv(H+ n*n')
    gradG = (tmp*(-n), -0.5*tmp)
    return (G, gradG)
end
"""
From expectation parameters to natural parameters
"""
function update_parameters!(g::MvGaussianExponential)
    d = dimension(g)
    (G, gradG) = compute_G(g.n[1], g.n[2])
    g.G = G
    g.gradG = gradG
end
function bregman_divergence(g::MvGaussianExponential, x1::Vector)
    #Simplification and hierarchical representations of mixtures    of exponential families.    V. Garcia, F. Nielsen
    n = g.n
    x = get_parameters(x1, dimension(g))
    return  g.G +  dot(x[1]-n[1], g.gradG[1]) + trace((x[2]-n[2])*g.gradG[2]')
end
function trace_axb(a, b)
    return dot(view(a,1,:),view(b,:,1)) + dot(view(a,2,:),view(b,:,2)) + dot(view(a,3,:),view(b,:,3))
end
function bregman_divergence(g::MvGaussianExponential, x1::Matrix, x2::Array{<:AbstractFloat,3})
    n = g.n
    D = size(x1,2)
    X1 = x1.-n[1]
    X2 = (x2.-n[2])
    trace_term = zeros(D)
    for i=1:D
	trace_term[i] = trace_axb(view(X2,:,:,i),g.gradG[2])
    end
    res =  g.G .+ (vec(g.gradG[1]'*X1)) + trace_term
    return res
end

function expectation_parameters(g::Type{MvGaussianExponential}, data::Matrix)
    μ = vec(mean(data, dims=2))
    Σ = cov(data, dims=2)
    return (μ, -(Σ + μ*μ'))
end


#FEAST for general nonlinear eigenvalue problems, using Beyn's algorithm for Rayleigh Ritz.
function nlfeast_beyn(Tf,x0,nc,emid,ra,rb,eps,maxit;log=false)
    #Tf is single value nonlinear residual T(z)

    #integrand = (I-T^-1(z)T(l))x(zI-l)^-1
    function integrand(z,x,lest,data,resvecs)
            (n,m0)=size(x)
            TinvTxl=zeros(ComplexF64,n,m0)
            Tfz=Tf(z)

            TinvTxl=\(Tfz,resvecs)

            gamma=spdiagm(0 => 1 ./(z.-lest))
            Qk=x*gamma-TinvTxl*gamma
            return Qk
    end

    #solve projected problem Q'*T(z)Q=0 using Beyn's algorithm
    function rrsolve(Q)
        (n,m0)=size(Q)

        v0=rand(ComplexF64,m0,m0)
        Tfr(z)=Q'*Tf(z)*Q

        (lest,xq)=beyn(Tfr,v0,5*nc,emid,ra,rb)

        return (lest,Q*xq)
    end

    #Block method for applying residual
    function blockTf(l,x)
        (n,m0)=size(x)
        out=zero(x)
        for i in 1:m0
            out[:,i]=Tf(l[i])*x[:,i]
        end
        return out
    end

    return feast_core(blockTf,integrand,rrsolve,x0,nc,emid,ra,rb,eps,maxit;log=log)
end

#iterative FEAST for general nonlinear eigenvalue problems, using Beyn's algorithm for Rayleigh Ritz.
function inlfeast_beyn(Tf,x0,alpha,isMaxit,nc,emid,ra,rb,eps,maxit;log=false)
    #Tf is single value nonlinear residual T(z)

    #integrand = (I-T^-1(z)T(l))x(zI-l)^-1
    function integrand(z,x,lest,data,resvecs)
            (n,m0)=size(x)
            TinvTxl=zeros(ComplexF64,n,m0)
            Tfz=Tf(z)

            maxits=0

            #TinvTxl=\(Tfz,resvecs)
            int=zeros(ComplexF64,n,m0)
            for i in 1:m0
                #(int[:,i],history)=bicgstabl(M,resvecs[:,i],1,max_mv_products=isMaxit,tol=alpha,initial_zero=true,log=true)
                #(int[:,i],history)=idrs(M,rhs[:,i];maxiter=isMaxit,tol=alpha,log=true)
                (int[:,i],history)=gmres(Tfz,resvecs[:,i],restart=isMaxit,tol=alpha,initially_zero=true,maxiter=isMaxit,log=true)
                #(int[:,i],history)=minres(M,resvecs[:,i],tol=alpha,initially_zero=true,maxiter=isMaxit,log=true)
                nlinits=size(history[:resnorm],1)
                data[:linIts][i,nc,data[:iterations]]=nlinits
                data[:linResiduals][i,nc,data[:iterations]]=history[:resnorm][nlinits]
                #data[:linResiduals][i,nc,data[:iterations]]=norm(resvecs[:,i]-M*int[:,i])/norm(resvecs[:,i])

                if(nlinits>maxits)
                    maxits=nlinits
                end
            end

            return (x-int)*spdiagm(0 => 1 ./(z.-lest))
    end

    #solve projected problem Q'*T(z)Q=0 using Beyn's algorithm
    function rrsolve(Q)
        (n,m0)=size(Q)

        v0=rand(ComplexF64,m0,m0)
        Tfr(z)=Q'*Tf(z)*Q

        (lest,xq)=beyn(Tfr,v0,5*nc,emid,ra,rb)

        return (lest,Q*xq)
    end

    #Block method for applying residual
    function blockTf(l,x)
        (n,m0)=size(x)
        out=zero(x)
        for i in 1:m0
            out[:,i]=Tf(l[i])*x[:,i]
        end
        return out
    end

    return feast_core(blockTf,integrand,rrsolve,x0,nc,emid,ra,rb,eps,maxit;log=log)
end

#FEAST for quadratic eigenvalue problem
function nlfeast_quad(M,D,K,x0,nc,emid,ra,rb,eps,maxit;log=false)
    (n,m0)=size(x0)

    #pardiso for sparse systems
    #ps=MKLPardisoSolver()

    Tfm(z)=M*z^2+z*D+K

    #Q=integral (I-T^-1(z)T(X,Lambda))X(zI-Lambda)^-1 dz
    function integrand(z,x,lest,data,resvecs)
            (n,m0)=size(x)

            Txl=M*x*diagm(0 => lest.^2)+D*x*diagm(0 => lest)+K*x
            #Txl=M*x2+D*x1+K*x
            #Txl=resvecs
            Tz=z^2*M+z*D+K

            TinvTxl=\(Tz,Txl)

            gamma=diagm(0 => 1 ./(z.-lest))
            Qk=x*gamma-TinvTxl*gamma
            return Qk
    end

    #solve reduced nonlinear problem with linearization
    #select the m0 solutions that are closest to the center of the contour
    rrsolve(Q)=Quadrrsolve(Q,M,D,K,emid,ra,rb)

    Tf(l,x)=M*x*diagm(0 => l.^2)+D*x*diagm(0 => l)+K*x

    return feast_core(Tf,integrand,rrsolve,x0,nc,emid,ra,rb,eps,maxit,log=log)
end


@doc raw"""
	Spinor(α,β)

Spinor(α,β) with Cayley-Klein parameters α and β.

Based on "Introduction to the Shinnar-Le Roux algorithm", Patrick Le Roux (1995).

A spinor is a way to represent 3D rotations, the underlying representation is a 2 X 2 complex unitary matrix (``\alpha,\beta\in\mathbb{C}``):

```math
R=\left[\begin{array}{cc}
\alpha & -\beta^{*}\\
\beta & \alpha^{*}
\end{array}\right],
```
with ``|\alpha|^2+|\beta|^2 = 1``.

This later operates on the ``2\times2`` representation of ``(x,y,z)`` as follows ``V^{+} = R V R^{*}``.
"""
struct Spinor
	α::Complex
	β::Complex
end
Base.show(io::IO,s::Spinor) = print(io, "Spinor(α = ",round(s.α,digits=3),", β = ",round(s.β,digits=3),")")

"""
Spinor multiplication identity.
	(α1,β1)×(α2,β2) = (α1 α2 - β2⋆ β1 , β2 α1 + α2⋆ β1)
"""
*(s1::Spinor,s2::Spinor) = begin
	Spinor(s1.α*s2.α - conj(s2.β)*s1.β,
		   s1.α*s2.β + conj(s2.α)*s1.β)
end

#Rotation matrices
"Spinor clockwise rotation matrix with angle φ with respect to z-axis."
Rz(φ) = Spinor(exp(-im*φ/2), 0)
"Spinor clockwise rotation matrix with angle Θ with respect to y-axis."
Ry(θ) = Spinor(cos(θ/2), sin(θ/2))
"""
Spinor clockwise rotation matrix with angle Θ with respect to x-axis.

Rx(θ) = Rz(-π/2) Ry(θ) Rz(π/2)
"""
Rx(θ) = Spinor(cos(θ/2), -im*sin(θ/2))
"""
Spinor rotation matrix.

Rg(φ1,θ,φ2) = Rz(φ2) Ry(θ) Rz(φ1)
"""
Rg(φ1,θ,φ2) = Spinor(cos(θ/2)*exp(-im*(φ1+φ2)/2), sin(θ/2)*exp(-im*(φ1-φ2)/2))
"""
Spinor rotation matrix with angle θ with axis in the xy plane u=(cosφ,sinφ).

Rφ(φ,θ) = Rg(-φ,θ,φ) = Rz(φ) Ry(θ) Rz(-φ)
"""
Rφ(φ,θ) = Spinor(cos(θ/2),exp(im*φ)*sin(θ/2))

@doc raw"""
Pauly, J., Le Roux, P., Nishimura, D., & Macovski, A. (1991).
Parameter relations for the Shinnar-Le Roux selective excitation pulse design algorithm (NMR imaging).
IEEE Transactions on Medical Imaging, 10(1), 53–65. doi:10.1109/42.75611

Spinor rotation matrix.

Rotation of φ with respect to the axis of rotation n=(nx,ny,nz).

```math
\varphi=-\gamma\Delta t\sqrt{\left|B_{1}\right|^{2}+\left(\boldsymbol{G}\cdot\boldsymbol{x}\right)^{2}}=-\gamma\Delta t\left\Vert \boldsymbol{B}\right\Vert
```
```math
\boldsymbol{n}=\boldsymbol{B}/\left\Vert \boldsymbol{B}\right\Vert
```
"""
Q(φ, nxy, nz) = begin
	Spinor( cos(φ/2)-im*nz*sin(φ/2), -im*nxy*sin(φ/2) )
end
"""
It calculates |\\alpha|^2+|\\beta|^2 of the Cayley-Klein parameters
"""
abs(s::Spinor) = abs(s.α)^2 + abs(s.β)^2

"""RF Object"""
mutable struct RF
	A # Amplitud/Phase B1x + i B1y [T]
	T # Durations [s]
	Δf::Float64    # Frequency offset [Hz]
	delay::Float64 # Delay [s]
	function RF(A,T,Δf,delay)
		@argcheck all(T .>= 0) && delay >= 0 "RF timings must be positive."
		new(A, T, Δf, delay)
    end
	function RF(A,T,Δf)
		@argcheck all(T .>= 0) "RF timings must be positive."
		new(A, T, Δf, 0.)
    end
	function RF(A,T)
		@argcheck all(T .>= 0) >= 0 "RF timings must be positive."
		new(A, T, 0., 0.)
    end
end
#Properties
*(α::ComplexF64, x::RF) = RF(α*x.A,x.T,x.Δf,x.delay)
"Duration `T` [s] of RF datatype."
dur(x::RF) = sum(x.T)
"Duration `T` [s] of the RF array Array{RF,1}."
dur(x::Array{RF,1}) = sum(sum(x[i].T) for i=1:size(x,1))
"Duration `T` [s] of the RF array Array{RF,2}."
dur(x::Array{RF,2}) = maximum(sum([sum(x[i,j].T) for i=1:size(x,1),j=1:size(x,2)],dims=2))
"Generate an RF sequence with amplitudes sampled from a function."
RF_fun(f::Function,T::Real,N::Int64=300) = begin
	t = range(0,stop=T,length=N)
	A = f.(t)
	RFs = RF(A,T)
end
"Calculates the flip-angle α [deg] of an RF object. α = γ ∫ B1(τ) dτ"
get_flip_angle(x::RF) = begin
	A, NA, T, NT = x.A, length(x.A), x.T, length(x.T)
	dT = T / NA * NT
	α = round(360 * γ * abs(sum(A .* dT)), digits=3) #Pulseq
	α
end
"Calculates the RF center. This calculation includes the RF delay."
get_RF_center(x::RF) = begin
	A, NA, T, NT, delay = x.A, length(x.A), x.T, length(x.T), x.delay
	dT = T / NA * NT .* ones(NA)
	t = cumsum([0; dT])[1:end-1]
	i_center = argmax(abs.(A))
	t_center = t[i_center] + dT[i_center]/2
	t_center + delay
end

one(T::Spinor) = Spinor(1,0)
getproperty(x::Vector{RF}, f::Symbol) = getproperty.(x,f)
getproperty(x::Matrix{RF}, f::Symbol) = begin
	if     f == :Bx
		real.(getproperty.(x,:A))
	elseif f == :By
		imag.(getproperty.(x,:A))
	elseif f == :Δf
		getproperty.(x,:Δf)
	elseif f == :T || f == :delay
		getproperty.(x[1,:],f)
	elseif f == :dur
		T, delay = x.T, x.delay
		ΔT = [sum(t) for t=T] .+ delay
		ΔT
	else
		getproperty.(x,f)
	end
end
#aux
Base.show(io::IO,x::RF) = begin
	r(x) = round.(x,digits=4)
	compact = get(io, :compact, false)
	if !compact
		wave = length(x.A) == 1 ? r(x.A*1e6) : "∿"
		print(io, (x.delay>0 ? "←$(r(x.delay*1e3)) ms→ " : "")*"RF($(wave) uT, $(r(sum(x.T)*1e3)) ms, $(r(x.Δf)) Hz)")
	else
		wave = length(x.A) == 1 ? "⊓" : "∿"
		print(io, (sum(abs.(x.A)) > 0 ? wave : "⇿")*"($(r((x.delay+sum(x.T))*1e3)) ms)")
	end
end


using BEAST
using Test
using LinearAlgebra

width = 4.0
delay = 6.0
g = BEAST.creategaussian(width, delay)
G = BEAST.fouriertransform(g)

dx = width / 15
x0 = 0.0
x = x0 : dx : 3*delay
n = length(x)
y = g.(x)

Y, dω, ω0 = BEAST.fouriertransform(y, dx, x0)
ω = collect(ω0 .+ (0:n-1)*dω)
Z = G.(ω)

# using Plots
# plot(ω, real(Y))
# scatter!(ω, real(Z))
# plot!(ω, imag(Y))
# scatter!(ω, imag(Z))

@test norm(Y-Z) < 1.e-10