function foundNear(n)
    near = true
    for j in 1:n
        psi,vort = randVortexField(1)
        vortfound = findvortices(psi)
        vfdata = rawData(vortfound)
        vdata = rawData(vort)
        near *= isapprox(vdata,vfdata,rtol = 0.2)
    end
    return near
end
function findwhere(A)
    I = findall(!iszero,A)
    v = A[I]
    ix = [I[i][1] for i in eachindex(I)]
    iy = [I[i][2] for i in eachindex(I)]
    return ix,iy,v
end

function findvortices_jumps(psi::Field;shift=true)
    @unpack x,y,ψ = psi
    phase = angle.(ψ)

    # Compute all nearest neighbour jumps
    diffx = phasejumps(phase,1); diffy = phasejumps(phase,2)

    # Compute all plaquette loop integrals with in-place memory recycling
    circshift!(phase,diffx,(0,1))
    diffx .-= phase; diffx .-= diffy
    circshift!(phase,diffy,(1,0))
    diffx .+= phase

    # Find all windings
    ixp,iyp,vp = findwhere(diffx .> 0.0)
    xp = x[ixp]; yp = y[iyp]; np = length(vp)
    ixn,iyn,vn = findwhere(diffx .< 0.0)
    xn = x[ixn]; yn = y[iyn]; nn = length(vn)

    if shift
        dx = x[2]-x[1]; dy = y[2] - y[1]
        xp .-= dx/2; yp .-= dy/2; xn .-= dx/2; yn .-= dy/2
    end

    vortices = [xn yn -vn; xp yp vp]

    return PointVortex(vortices)
end

function findvortices_grid(psi::Torus;shift=true)
    vort = findvortices_jumps(psi,shift=shift)
    rawvort = rawData(vort)
    vort = sortslices(rawvort,dims=1)
    return PointVortex(vort)
end

function findvortices_grid(psi::Sphere;shift=true)
    @unpack ψ = psi
    windvals = phasejumps(angle.(ψ),2)
    vort = findvortices_jumps(psi,shift=shift)
    rawvort = rawData(vort)

    # North pole winding. - sign means polar vortex co-rotating with w > 0
    w1 = sum(windvals[1,:])
    (sign(w1) != 0) && (rawvort = [rawvort; 0.0 0.0 -w1])
    # South pole winding. + sign means co-rotating with w > 0
    w2 = sum(windvals[end,:])
    (sign(w2) != 0) && (rawvort = [rawvort; pi 0.0 w2])

    vort = sortslices(vort,dims=1)
    return PointVortex(vort)
end

function findvortices_interp(psi::Field)
    vort = findvortices_grid(psi,shift=true)
    vort = removeedgevortices(vort,psi)
    #TODO: allow for interp with periodic data (here edges are stripped)

    for (j,vortex) in enumerate(vort)
        try
        vortz,psiz = corezoom(vortex,psi)
        vortz,psiz = corezoom(vortz,psiz)
        vortz,psiz = corezoom(vortz,psiz)
        vort[j] = vortz
        catch nothing
        end
        return vort
    end
end

"""
    vortices = findvortices(ψ<:Field,interp=true)

Locate vortices as `2π` phase windings around plaquettes on a cartesian spatial grid.

Requires a 2D wavefunction `ψ(x,y)` on a cartesian grid specified by vectors `x`, `y`.

`vortices` - array of vortex coordinates `xv,yv` and charges `qv`.

Each row is of the form `[xv, yv, cv]`, and the array is sorted into lexical order
according to the `xv` coordinates.
"""
function findvortices(psi::Field,interp=true)
    if interp
        return findvortices_interp(psi)
    else
        return findvortices_grid(psi)
    end
end

"""
    vortices = removeedgevortices(vort::Array{PointVortex,1},x,y,edge=1)

Strip artifact edgevortices arising from periodic phase differencing.
"""
function removeedgevortices(vort::Array{PointVortex,1},psi::Field,edge=1)
    @unpack x,y = psi; dx=x[2]-x[1]; dy=y[2]-y[1]
    keep = []
    for j = 1:length(vort)
        xi,yi,qi = rawData(vort[j])
        xedge = isapprox(xi,x[1],atol=edge*dx) || isapprox(xi,x[end],atol=edge*dx)
        yedge = isapprox(yi,y[1],atol=edge*dy) || isapprox(yi,y[end],atol=edge*dy)
        not_edge = !(xedge || yedge)
        not_edge && push!(keep,j)
    end
    return vort[keep]
end

"""
    vortz,psiz = corezoom(vortex,ψ<:Field,winhalf=2,Nz=30)

Uses local interpolation to resolve core location.
"""
function corezoom(vortex::PointVortex,psi::T,winhalf=2,Nz=30) where T<:Field
    @unpack ψ,x,y = psi
    xv,yv,qv = rawData(vortex)
    dx=x[2]-x[1]; dy=y[2]-y[1]
    ixv = isapprox.(x,xv,atol=dx) |> findlast
    iyv = isapprox.(y,yv,atol=dy) |> findlast
    ixwin = (ixv-winhalf):(ixv+winhalf-1)
    iywin = (iyv-winhalf):(iyv+winhalf-1)
    xw = x[ixwin]; yw = y[iywin]; psiw = ψ[ixwin,iywin]
    xz = LinRange(xw[1],xw[end],Nz)
    yz = LinRange(yw[1],yw[end],Nz)
    knots = (xw,yw)
    itp = interpolate(knots, psiw, Gridded(Linear()))
    psiz = itp(xz,yz)
    ψv = T(psiz,xz |> Vector,yz |> Vector)
    vortz = findvortices_grid(ψv,shift=false)
    vortz = removeedgevortices(vortz,ψv)[1]
    return vortz,ψv
end

corezoom(vortex::Array{PointVortex,1},psi::Field,winhalf=2,Nz=30) = corezoom(vortex[1],psi,2,30)

"""
    jumps = phasejumps(ϕ,dim)

Count phase jumps greater than `π` in phase `ϕ` along dimension `dim`.

See also: [`unwrap`](@ref), [`unwrap`](@ref), [`unwrap!`](@ref)
"""
function phasejumps(phase,dim=1)
    @assert (dim==1 || dim==2)
    Nx,Ny = size(phase)
    pdiff = zero(phase)

    if dim == 1
    @inbounds for j in 1:Ny
        @inbounds for i in 2:Nx
            Δ = phase[i,j] - phase[i-1,j]
            abs(Δ) > π && (pdiff[i,j] += sign(Δ))
        end
            Δ = phase[1,j] - phase[Nx,j]
            abs(Δ) > π && (pdiff[1,j] += sign(Δ))
    end

    elseif dim == 2
    @inbounds for j in 2:Ny
        @inbounds for i in 1:Nx
            Δ = phase[i,j] - phase[i,j-1]
            abs(Δ) > π && (pdiff[i,j] += sign(Δ))
        end
    end
        @inbounds for i in 1:Nx
            Δ = phase[i,1] - phase[i,Ny]
            abs(Δ) > π && (pdiff[i,1] += sign(Δ))
        end
    end
  return pdiff
end

function phasejumps!(pdiff,phase,dim=1)
    @assert (dim==1 || dim==2)
    Nx,Ny = size(phase)

    if dim == 1
    @inbounds for j in 1:Ny
        @inbounds for i in 2:Nx
            Δ = phase[i,j] - phase[i-1,j]
            abs(Δ) > π && (pdiff[i,j] += sign(Δ))
        end
            Δ = phase[1,j] - phase[Nx,j]
            abs(Δ) > π && (pdiff[1,j] += sign(Δ))
    end

    elseif dim == 2
    @inbounds for j in 2:Ny
        @inbounds for i in 1:Nx
            Δ = phase[i,j] - phase[i,j-1]
            abs(Δ) > π && (pdiff[i,j] += sign(Δ))
        end

    end
        @inbounds for i in 1:Nx
            Δ = phase[i,1] - phase[i,Ny]
            abs(Δ) > π && (pdiff[i,1] += sign(Δ))
        end
    end
end

"""
    unwrapped = unwrap(ϕ,dim=1)

Unwraps 2d array `ϕ` along dimension `dim`, acting periodically to give back array of same size as `ϕ`.

See also: [`unwrap!`](@ref)
"""
function unwrap(phase::Array{Float64,2},dim=1)
    @assert (dim==1 || dim==2)
    Nx,Ny = size(phase)
    uphase = copy(phase)

    if dim == 1
    @inbounds for j in 1:Ny
        @inbounds for i in 2:Nx
        (uphase[i,j] - uphase[i-1,j] >= π) && (uphase[i,j] -= 2π)
        (uphase[i,j] - uphase[i-1,j] <= -π) && (uphase[i,j] += 2π)
        end
        (uphase[1,j] - uphase[Nx,j] >= π) && (uphase[1,j] -= 2π)
        (uphase[1,j] - uphase[Nx,j] <= -π) && (uphase[1,j] += 2π)
    end

    elseif dim == 2
    @inbounds for j in 2:Ny
        @inbounds for i in 1:Nx
        (uphase[i,j] - uphase[i,j-1] >= π) && (uphase[i,j] -= 2π)
        (uphase[i,j] - uphase[i,j-1] <= -π) && (uphase[i,j] += 2π)
        end

    end
        @inbounds for i in 1:Nx
        (uphase[i,1] - uphase[i,Ny] >= π) && (uphase[i,1] -= 2π)
        (uphase[i,1] - uphase[i,Ny] <= -π) && (uphase[i,1] += 2π)
        end
    end

    return uphase
end

"""
    unwrap!(unwrapped,ϕ,dim)

Unwraps 2d phase array `ϕ` along dimension `dim`,
acting periodically and writing the unwrapped array `ϕ`
in place.

See also: [`unwrap`](@ref)
"""
function unwrap!(uphase::Array{Float64,2},phase::Array{Float64,2},dim=1)
    @assert (dim==1 || dim==2)
    Nx,Ny = size(phase)
    uphase .= phase

    if dim == 1
    @inbounds for j in 1:Ny
        @inbounds for i in 2:Nx
        (uphase[i,j] - uphase[i-1,j] >= π) && (uphase[i,j] -= 2π)
        (uphase[i,j] - uphase[i-1,j] <= -π) && (uphase[i,j] += 2π)
        end
        (uphase[1,j] - uphase[Nx,j] >= π) && (uphase[1,j] -= 2π)
        (uphase[1,j] - uphase[Nx,j] <= -π) && (uphase[1,j] += 2π)
    end

    elseif dim == 2
    @inbounds for j in 2:Ny
        @inbounds for i in 1:Nx
        (uphase[i,j] - uphase[i,j-1] >= π) && (uphase[i,j] -= 2π)
        (uphase[i,j] - uphase[i,j-1] <= -π) && (uphase[i,j] += 2π)
        end

    end
        @inbounds for i in 1:Nx
        (uphase[i,1] - uphase[i,Ny] >= π) && (uphase[i,1] -= 2π)
        (uphase[i,1] - uphase[i,Ny] <= -π) && (uphase[i,1] += 2π)
        end
    end
end

function unwrap(phase::Array{Float64,1})
    uphase = copy(phase)
    Nx = length(phase)
        @inbounds for i in 2:Nx
        (uphase[i] - uphase[i-1] >= π) && (uphase[i] -= 2π)
        (uphase[i] - uphase[i-1] <= -π) && (uphase[i] += 2π)
        end
        (uphase[1] - uphase[Nx] >= π) && (uphase[1] -= 2π)
        (uphase[1] - uphase[Nx] <= -π) && (uphase[1] += 2π)
        return uphase
end

function unwrap!(uphase::Array{Float64,1},phase::Array{Float64,1})
    Nx = length(phase)
        @inbounds for i in 2:Nx
        (uphase[i] - uphase[i-1] >= π) && (uphase[i] -= 2π)
        (uphase[i] - uphase[i-1] <= -π) && (uphase[i] += 2π)
        end
        (uphase[1] - uphase[Nx] >= π) && (uphase[1] -= 2π)
        (uphase[1] - uphase[Nx] <= -π) && (uphase[1] += 2π)
end


@testcase "subtyping" begin
    @lift abstract type A{a} end
    @lift abstract type B{a} <: A{a} end

    @testset "adt subtying" begin
        @lift @data C{A} <: B{A} begin
            C1(A, Int)
        end

        @test C1(1, 2) isa C{Int}
    end
end

@testcase "adt list" begin
    # 1. define the List data type
    @lift @data List{T} begin
        Nil()
        Cons(head :: T, tail :: List{T})
    end
    # 2. define interfaces
    len(xs::List{T}) where T = @match xs begin
        Nil{T}() => 0
        Cons{T}(_, tail) => 1 + len(tail)
    end
    @testset "adt List" begin
        @test len(Nil{Any}()) == 0
        xs = Cons(3,Cons(2, Cons(1, Nil{Int}())))
        @test len(xs) == 3
    end
end

@testcase "adt arith" begin
    # 1. define Arith
    @lift @data Arith begin
        Num(v :: Int)
        Minus(fst :: Arith, snd :: Arith)
        Add(fst :: Arith, snd :: Arith)
        Mult(fst :: Arith, snd :: Arith)
        Divide(fst :: Arith, snd :: Arith)
    end
    # 2. define interfaces
    function eval_arith(arith :: Arith)
        @match arith begin
            Num(v)       => v
            Add(fst, snd) => eval_arith(fst) + eval_arith(snd)
            Minus(fst, snd) => eval_arith(fst) - eval_arith(snd)
            Mult(fst, snd)   => eval_arith(fst) * eval_arith(snd)
            Divide(fst, snd) => eval_arith(fst) / eval_arith(snd)
        end
    end

    @testset "adt Arith" begin
        Number = Num
        @test eval_arith(
            Add(Number(1),
                Minus(Number(2),
                    Divide(Number(20),
                            Mult(Number(2),
                                Number(5)))))) == 1
    end
end

@testcase "share data with several modules" begin
    @lift @data CD begin
        D(a, b)
        C{T} :: (a :: Int, b::T) => CD
    end
    @lift @data A begin
        E()
    end

    @testset "case" begin
        @test E <: A
        @test fieldnames(D) == (:a, :b)
        @test_throws MethodError C(3.0, :abc)
    end

    @lift module ADummy
        using MLStyle
    end

    @lift module BDummy
        using MLStyle
    end

    @lift @data visible in [ADummy] SSS begin
        SSS_1(Int)
    end

    ADummy.eval(:(SSS_1 = $SSS_1; SSS = $SSS))

    @test ADummy.eval(quote
        @match SSS_1(2) begin
            SSS_1(_) => :ok
        end
    end) == :ok

    BDummy.eval(:(SSS_1 = $SSS_1; SSS = $SSS))

    @test_skip BDummy.eval(quote
        @match SSS_1(2) begin
            SSS_1(_) => :ok
        end
    end)

    @testset "enum: #85" begin
        @lift @data Es{T} begin
            E1 :: Es{Int}
            E2 :: Es{Char}
        end

        @lift @data Es′ begin
            E3
            E4
        end
        e1 = E1
        e2 = E2
        e3 = E3
        e4 = E4

        @test @match 1 begin
            E1 => false
            E2 => false
            E3 => false
            E4 => false
            _ => true
        end

        @test @match E1 begin
            E1 => true
            _ => false
        end

        @test @match E2 begin
            E2 => true
            _ => false
        end

        @test @match E3 begin
            E3 => true
            _ => false
        end

        @test @match E4 begin
            E4 => true
            _ => false
        end

        @test @match E1 begin
            E2 => false
            _ => true
        end

        @test @match E3 begin
            E4 => false
            _ => true
        end
    end
end


@testset "nystrom" begin
    dims = [10,5]
    X = rand(dims...)
    k = SqExponentialKernel()
    for obsdim in [1, 2]
        @test kernelmatrix(k, X; obsdim=obsdim) ≈ kernelmatrix(nystrom(k, X, 1.0; obsdim=obsdim))
        @test kernelmatrix(k, X; obsdim=obsdim) ≈ kernelmatrix(nystrom(k, X, collect(1:dims[obsdim]); obsdim=obsdim))
    end
end


module NedelecRefFEsTest

using Test
using Gridap.Polynomials
using Gridap.Fields
using Gridap.TensorValues
using Gridap.Fields: MockField
using Gridap.ReferenceFEs

p = QUAD
D = num_dims(QUAD)
et = Float64
order = 0

reffe = NedelecRefFE(et,p,order)
test_reference_fe(reffe)
@test num_terms(get_prebasis(reffe)) == 4
@test get_order(get_prebasis(reffe)) == 0
@test num_dofs(reffe) == 4

@test Conformity(reffe) == CurlConformity()

p = QUAD
D = num_dims(QUAD)
et = Float64
order = 1

reffe = NedelecRefFE(et,p,order)
test_reference_fe(reffe)
@test num_terms(get_prebasis(reffe)) == 12
@test num_dofs(reffe) == 12
@test get_order(get_prebasis(reffe)) == 1

prebasis = get_prebasis(reffe)
dof_basis = get_dof_basis(reffe)

v = VectorValue(3.0,0.0)
field = GenericField(x->v*x[1])

cache = return_cache(dof_basis,field)
r = evaluate!(cache, dof_basis, field)
test_dof_array(dof_basis,field,r)

cache = return_cache(dof_basis,prebasis)
r = evaluate!(cache, dof_basis, prebasis)
test_dof_array(dof_basis,prebasis,r)

# Factory function
reffe = ReferenceFE(QUAD,nedelec,0)
@test num_terms(get_prebasis(reffe)) == 4
@test get_order(get_prebasis(reffe)) == 0
@test num_dofs(reffe) == 4
@test Conformity(reffe) == CurlConformity()

reffe = ReferenceFE(QUAD,nedelec,Float64,0)
@test num_terms(get_prebasis(reffe)) == 4
@test get_order(get_prebasis(reffe)) == 0
@test num_dofs(reffe) == 4
@test Conformity(reffe) == CurlConformity()

@test Conformity(reffe,:L2) == L2Conformity()
@test Conformity(reffe,:Hcurl) == CurlConformity()
@test Conformity(reffe,:HCurl) == CurlConformity()

@test Nedelec() == nedelec

end # module


"""
    Fixed(;name, phi0=0.0)

Flange fixed in housing at a given angle.

# Parameters:
- `phi0`: [rad] Fixed offset angle of housing
"""
function Fixed(;name, phi0=0.0)
    @named flange = Flange()
    @parameters phi0=phi0
    eqs = [flange.phi ~ phi0]
    return compose(ODESystem(eqs, t, [], [phi0]; name=name), flange)
end

"""
    Inertia(;name, J=1.0, phi_start=0.0, w_start=0.0, a_start=0.0)

1D-rotational component with inertia.

# Parameters:
- `J`: [kg·m²] Moment of inertia
- `phi_start`: [rad] Initial value of absolute rotation angle of component
- `w_start`: [rad/s] Initial value of absolute angular velocity of component
- `a_start`: [rad/s²] Initial value of absolute angular acceleration of component

# States:
- `phi`: [rad] Absolute rotation angle of component
- `w`: [rad/s] Absolute angular velocity of component (= der(phi))
- `a`: [rad/s²] Absolute angular acceleration of component (= der(w))
"""
function Inertia(;name, J=1.0, phi_start=0.0, w_start=0.0, a_start=0.0)
    @named flange_a = Flange()
    @named flange_b = Flange()
    @parameters J=J
    sts = @variables begin
        phi(t)=phi_start
        w(t)=w_start
        a(t)=a_start
    end
    eqs = [
        phi ~ flange_a.phi
        phi ~ flange_b.phi
        D(phi) ~ w
        D(w) ~ a
        J*a ~ flange_a.tau + flange_b.tau
    ]
    return compose(ODESystem(eqs, t, sts, [J]; name=name), flange_a, flange_b)
end

"""
    Spring(;name, c, phi_rel0=0.0)

Linear 1D rotational spring

# Parameters:
- `c`: [N.m/rad] Spring constant
- `phi_rel0`: Unstretched spring angle
"""
function Spring(;name, c=1.0e5, phi_rel0=0.0)
    @named partial_comp = PartialCompliant()
    @unpack phi_rel, tau = partial_comp
    pars = @parameters begin
        c=c
        phi_rel0=phi_rel0
    end
    eqs = [tau ~ c*(phi_rel - phi_rel0)]
    extend(ODESystem(eqs, t, [], pars; name=name), partial_comp)
end

"""
    Damper(;name, d=0.0)

Linear 1D rotational damper

# Parameters:
- `d`: [N.m.s/rad] Damping constant
"""
function Damper(;name, d=0.0)
    @named partial_comp = PartialCompliantWithRelativeStates()
    @unpack w_rel, tau = partial_comp
    pars = @parameters d=d
    eqs = [tau ~ d*w_rel]
    extend(ODESystem(eqs, t, [], pars; name=name), partial_comp)
end

"""
    IdealGear(;name, ratio, use_support=false)

Ideal gear without inertia.

This element characterizes any type of gear box which is fixed in the ground and which has one driving shaft and one driven shaft.

# Parameters:
- `ratio`: Transmission ratio (flange_a.phi/flange_b.phi)
- `use_support`: If support flange enabled, otherwise implicitly grounded
"""
function IdealGear(;name, ratio, use_support=false)
    @named partial_element = PartialElementaryTwoFlangesAndSupport2(use_support=use_support)
    @unpack phi_support, flange_a, flange_b = partial_element
    @parameters ratio=ratio
    sts = @variables phi_a(t)=0.0 phi_b(t)=0.0
    eqs = [
        phi_a ~ flange_a.phi - phi_support
        phi_b ~ flange_b.phi - phi_support
        phi_a ~ ratio*phi_b
        0 ~ ratio*flange_a.tau + flange_b.tau
    ]
    extend(ODESystem(eqs, t, sts, [ratio]; name=name), partial_element)
end


function accuracy(y_pred, y)
    return mean(onecold(y_pred) .== onecold(y))
end


using LazySets

function lorenz_specification()
    # initial set
    X0 = Hyperrectangle(low=[0.9, 0.0, 0.0], high=[1.1, 0.0, 0.0])

    # time horizon: 10 time units
    time_horizon = 10.0

    # specification
    O = Dict{Symbol, Any}(:T => time_horizon)

    return X0, O
end


NonlinearEigenproblems.contour_beyn(nep::NEP,display::Bool;params...)=contour_beyn(ComplexF64,nep,display;params...)

# the same as NonlinearEigenproblems.contour_beyn but compatible with ProgressMeter, notice extra display argument to distinguish from main method
function NonlinearEigenproblems.contour_beyn(::Type{T},
            nep::NEP,
            display::Bool;
            tol::Real=sqrt(eps(real(T))), # Note tol is quite high for this method
            σ::Number=zero(complex(T)),
            displaylevel::Integer=0,
            linsolvercreator::Function=backslash_linsolvercreator,
            neigs::Integer=2, # Number of wanted eigvals
            k::Integer=neigs+1, # Columns in matrix to integrate
            radius::Union{Real,Tuple,Array}=1, # integration radius
            quad_method::Symbol=:ptrapz, # which method to run. :quadg, :quadg_parallel, :quadgk, :ptrapz
            N::Integer=1000,  # Nof quadrature nodes
            # errmeasure::Function = NonlinearEigenproblems.default_errmeasure(nep::NEP),
            sanity_check=true,
            rank_drop_tol=tol # Used in sanity checking
            )where{T<:Number}

    # Geometry
    length(radius)==1 ? radius=(radius,radius) : nothing
    g(t) = complex(radius[1]*cos(t),radius[2]*sin(t)) # ellipse
    gp(t) = complex(-radius[1]*sin(t),radius[2]*cos(t)) # derivative

    n=size(nep,1);

    if (k>n)
        error("Cannot compute more eigenvalues than the size of the NEP with contour_beyn() k=",k," n=",n);

    end
    if (k<=0)
        error("k must be positive, k=",k,
              neigs==typemax(Int) ? ". The kwarg k must be set if you use neigs=typemax" : ".")
    end

    Random.seed!(10); # Reproducability
    Vh=Array{T,2}(randn(real(T),n,k)) # randn only works for real

    function local_linsolve(λ::TT,V::Matrix{TT}) where {TT<:Number}
        # @ifd(print("."))
        local M0inv::LinSolver = linsolvercreator(nep,λ+σ);
        # This requires that lin_solve can handle rectangular
        # matrices as the RHS
        return lin_solve(M0inv,V);
    end

    # Constructing integrands
    Tv(λ) = local_linsolve(T(λ),Vh)
    f(t) = Tv(g(t))*gp(t)
    # @ifd(print("Computing integrals"))

    pg = Progress(N; dt=.1, desc="Contour integration...")
    local A0,A1
    if (quad_method == :quadg_parallel)
        # @ifd(print(" using quadg_parallel"))
        error("disabled");
    elseif (quad_method == :quadg)
        # @ifd(print(" using quadg"))
        error("disabled");
    elseif (quad_method == :ptrapz)
        # @ifd(print(" using ptrapz"))
        (A0,A1)=ptrapz(f,g,0,2*pi,N,pg);
    elseif (quad_method == :ptrapz_parallel)
        # @ifd(print(" using ptrapz_parallel"))
        channel = RemoteChannel(()->Channel{Bool}(pg.n+1),1)
        @sync begin
            @async begin
                (A0,A1)=ptrapz_parallel(f,g,0,2*pi,N,channel);
                put!(channel,false)
            end
            @async while take!(channel)
                next!(pg)
            end
        end
    elseif (quad_method == :quadgk)
        error("disabled");
    else
        error("Unknown quadrature method:"*String(quad_method));
    end

    # Don't forget scaling
    A0[:,:] = A0 ./(2im*pi);
    A1[:,:] = A1 ./(2im*pi);

    # @ifd(print("Computing SVD prepare for eigenvalue extraction "))
    V,S,W = svd(A0)
    p = count( S/S[1] .> rank_drop_tol);
    # @ifd(println(" p=",p));

    V0 = V[:,1:p]
    W0 = W[:,1:p]
    B = (copy(V0')*A1*W0) * Diagonal(1 ./ S[1:p])

    # Extract eigenval and eigvec approximations according to
    # step 6 on page 3849 in the reference
    # @ifd(println("Computing eigenvalues "))
    λ,VB=eigen(B)
    λ[:] = λ .+ σ
    # @ifd(println("Computing eigenvectors "))
    V = V0 * VB;
    for i = 1:p
        normalize!(V[:,i]);
    end

    if (!sanity_check)
        sorted_index = sortperm(map(x->abs(σ-x), λ));
        inside_bool = (real(λ[sorted_index].-σ)/radius[1]).^2 + (imag(λ[sorted_index].-σ)/radius[2]).^2 .≤ 1
        inside_perm = sortperm(.!inside_bool)
        return (λ[sorted_index[inside_perm]],V[:,sorted_index[inside_perm]])
    end

    # Compute all the errors
    errmeasures=zeros(real(T),p);
    # for i = 1:p
        # errmeasures[i]=errmeasure(λ[i],V[:,i]);
    # end

    good_index=findall(errmeasures .< tol);

    # Index vector for sorted to distance from σ
    sorted_good_index=
       good_index[sortperm(map(x->abs(σ-x), λ[good_index]))];

    # check that eigenvalues are inside contour, move them to the end if they are outside
   inside_bool = (real(λ[sorted_good_index].-σ)/radius[1]).^2 + (imag(λ[sorted_good_index].-σ)/radius[2]).^2 .≤ 1
   if any(.!inside_bool)
       if neigs ≤ sum(inside_bool)
           @warn "found $(sum(.!inside_bool)) evals outside contour, $p inside. all $neigs returned evals inside, but possibly inaccurate. try increasing N, decreasing tol, or changing radius"
       else
           @warn "found $(sum(.!inside_bool)) evals outside contour, $p inside. last $(neigs-sum(inside_bool)) returned evals outside contour. possible inaccuracy. try increasing N, decreasing tol, or changing radius"
       end
   end
   sorted_good_inside_perm = sortperm(.!inside_bool)

    # Remove all eigpairs not sufficiently accurate
    # and potentially remove eigenvalues if more than neigs.
    local Vgood,λgood
    if( size(sorted_good_index,1) > neigs)
        # @ifd(println("Removing unwanted eigvals: neigs=",neigs,"<",size(sorted_good_index,1),"=found_eigvals"))
        Vgood=V[:,sorted_good_index[sorted_good_inside_perm][1:neigs]];
        λgood=λ[sorted_good_index[sorted_good_inside_perm][1:neigs]];
    else
        Vgood=V[:,sorted_good_index[sorted_good_inside_perm]];
        λgood=λ[sorted_good_index[sorted_good_inside_perm]];
    end

    if (p==k)
       @warn "Rank-drop not detected, your eigvals may be correct, but the algorithm cannot verify. Try to increase k. This warning can be disabled with `sanity_check=false`." S
    end

    if (size(λgood,1)<neigs  && neigs < typemax(Int))
       @warn "We found fewer eigvals than requested. Try increasing domain, or decreasing `tol`. This warning can be disabled with `sanity_check=false`." S
    end

    return (λgood,Vgood)
end

# Trapezoidal rule for a periodic function f
function ptrapz(f,g,a,b,N,pg)
    h = (b-a)/N
    t = range(a, stop = b-h, length = N)
    S0 = zero(f(t[1])); S1 = zero(S0)
    for i = 1:N
        temp = f(t[i])
        S0 += temp
        S1 += temp*g(t[i])
        next!(pg)
    end
    return h*S0, h*S1;
end

function ptrapz_parallel(f,g,a,b,N,channel)
    h = (b-a)/N
    t = range(a, stop = b-h, length = N)
    S = @distributed (+) for i = 1:N
        temp = f(t[i])
        put!(channel,true)
        [temp,temp*g(t[i])]
    end
    return h*S[1], h*S[2];
end


using TypeClasses

# MonoidAlternative
# =================

TypeClasses.neutral(::Type{Writer{Accumulator, Value}}) where {Accumulator, Value} = neutral(Accumulator) => neutral(Value)
TypeClasses.combine(w1::Writer, w2::Writer) = Writer(combine(w1.acc, w2.acc), combine(w1.value, w2.value))

# we don't implement orelse, as it is commonly meant on container level, but there is no obvious failure semantics here

# FunctorApplicativeMonad
# =======================

TypeClasses.foreach(f, p::Writer) = f(p.value); nothing
TypeClasses.map(f, p::Writer) = Writer(p.acc, f(p.value))

# pure needs Neutral on First
function TypeClasses.pure(::Type{<:Writer{Acc}}, a) where {Acc}
  Writer(neutral(Acc), a)
end
# We fall back to assume that the general writer uses Option values in order to have a neutral value
# this should be okay, as it is canonical extension for any Semigroup to an Monoid
function TypeClasses.pure(::Type{<:Writer}, a)
  Writer(neutral, a)
end

# Writer always defines `combine` on `acc`
function TypeClasses.ap(f::Writer, a::Writer)
  Writer(combine(f.acc, a.acc), f.value(a.value))
end

# we cannot overload this generically, because `Base.map(f, ::Vector...)` would get overwritten as well (even without warning surprisingly)
TypeClasses.map(f, a::Writer, b::Writer, more::Writer...) = mapn(f, a, b, more...)

function TypeClasses.flatmap(f, a::Writer)
  # we intentionally only convert the container to Writer, and leave the responsibility for the accumulator to the `combine` function
  # (one reason is that all monads are only converted on the container side, another is that it seems quite difficult to convert MyType{Option} correctly, because it is a Union type.)
  nested_writer = convert(Writer, f(a.value))
  Writer(combine(a.acc, nested_writer.acc), nested_writer.value)
end

# flip_types
# ==========

function TypeClasses.flip_types(a::Writer)
  TypeClasses.map(x -> Writer(a.acc, x), a.value)
end


# # [Basic SIR example](@id sir_basic)
# Sean L. Wu (@slwu89), 2021-1-8

using Individual.Sampling
using Individual.SchemaBase

using Catlab.Present, Catlab.CSetDataStructures, Catlab.Theories, Catlab.CategoricalAlgebra, Catlab.Graphics, Catlab.Graphs
using Plots, GraphViz

# ## Introduction
# The SIR (Susceptible-Infected-Recovered) model is the "hello, world!" model of for infectious disease simulations,
# and here we describe how to use the basic schema for Markov models to build it in Individual.jl. We only use the
# base `TheoryIBM` schema, because the model is a Markov chain. This tutorial largely mirrors the SIR model tutorial from
# the [R package "individual"](https://mrc-ide.github.io/individual/articles/Tutorial.html), which inspired individual.jl.

# For more information about the SIR model and stochastic epidemic models in general, please see ["A primer on stochastic epidemic models: Formulation, numerical simulation, and analysis"](https://doi.org/10.1016/j.idm.2017.03.001).

# The schema looks like this:

to_graphviz(TheoryIBM)

# There are morphisms from the object `Person` into `State`. The first, `state` gives the current state of each individual in the
# simulation. The second `state_update` is where state updates can be queued; at the end of a time step, `state` is swapped
# with `state_update` and `state_update` is reset. This ensures that state updates obey a FIFO order, and that individuals
# update synchronously.

# There are also labels for states to make it easier to write self-documenting models.

# The basic schema could be extended with further `Attr`s if needing to model individual level heterogeneity, like immmune respose, etc.

# ## Parameters

# To start, we should define some parameters. The epidemic will be simulated in a population of 1000, where 8 persons are initially infectious, whose indices are randomly sampled.
# The effective contact rate β will be a function of the deterministic R0 and recovery rate γ. We also specify Δt, which is the size of the time step.
# Because individual’s time steps are all of unit length, we scale transition probabilities by Δt to create models with different sized steps, interpreting the discrete time model as a discretization of a continuous time model.
# If the maximum time is tmax then the overall number of time steps is tmax/Δt.

N = 1000
I0 = 8
S0 = N - I0
Δt = 0.1
tmax = 100
steps = Int(tmax/Δt)
γ = 1/10 # recovery rate
R0 = 2.5
β = R0 * γ # R0 for corresponding ODEs

initial_states = fill("S", N)
initial_states[rand(1:N, I0)] .= "I"
state_labels = ["S", "I", "R"];

# ## Model object

# The "IBM" (Individual Based Model) schema needs the type parameter `String` because it defines a
# single attribute, that giving names to the categorical set of states.

const SIR = IBM{String}()
initialize_states(SIR, initial_states, state_labels);

# ## Processes

# In order to model infection, we need a process. This is a function that takes only a single argument, `t`, for the current time step (unused here, but can model time-dependent processes, such as seasonality or school holiday).
# Within the function, we get the current number of infectious individuals, then calculate the per-capita force of infection on each susceptible person, λ=βI/N.
# Next we get the set of susceptible individuals and use the sample method to randomly select those who will be infected on this time step.
# The probability is given by $$ 1- e^{-\lambda \Delta t} $$. The method `bernoulli_sample` will automatically calculate that probability when given 3 arguments. Finally, we queue a state update for those individuals who were sampled.

function infection_process(t::Int)
    I = npeople(SIR, "I")
    N = npeople(SIR)
    λ = β * I/N
    S = get_index_state(SIR, "S")
    S = bernoulli_sample(S, λ, Δt)
    queue_state_update(SIR, S, "I")
end

# The recovery process is simpler, as the per-capita transition probability from I to R does not depend on the state of the system.

function recovery_process(t::Int)
    I = get_index_state(SIR, "I")
    I = bernoulli_sample(I, γ, Δt)
    queue_state_update(SIR, I, "R")
end

# ## Simulation

# We use `render_process` to create a rendering (output) process and
# a matrix giving state counts by time step.

# Then we draw a trajectory using `simulation_loop` and plot the results.
# Please note that `simulation_loop` is a simple wrapper around the correct updating order
# and that a user can always write a simple for loop to update the model.

state_out, render_process = render_states(SIR, steps)

simulation_loop(SIR, [infection_process, recovery_process, render_process], steps)

plot(
    (1:steps) * Δt,
    state_out,
    label=["S" "I" "R"],
    xlabel="Time",
    ylabel="Number"
)


#=
Filename: aiyagari_compute_policy.jl
Author: Victoria Gregory
Date: 8/29/2016

Computes and plots the optimal policy of a household
from the Aiyagari model, given prices.
=#

using LaTeXStrings
using Plots
pyplot()

# Example prices
r = 0.03
w = 0.956

# Create an instance of Household
am = Household(a_max=20.0, r=r, w=w)

# Use the instance to build a discrete dynamic program
am_ddp = DiscreteDP(am.R, am.Q, am.beta)

# Solve using policy function iteration
results = solve(am_ddp, PFI)

# Simplify names
z_size, a_size  = am.z_size, am.a_size
z_vals, a_vals = am.z_chain.state_values, am.a_vals
n = am.n

# Get all optimal actions across the set of
# a indices with z fixed in each column
a_star = reshape([a_vals[results.sigma[s_i]] for s_i in 1:n], a_size, z_size)

labels = [latexstring("\z = $(z_vals[1])") latexstring("\z = $(z_vals[2])")]
plot(a_vals, a_star, label=labels, lw=2, alpha=0.6)
plot!(a_vals, a_vals, label="", color=:black, linestyle=:dash)
plot!(xlabel="current assets", ylabel="next period assets", grid=false)


function local_solution(n, m, supp::Vector{Vector{Vector{UInt16}}}, coe; nb=0, numeq=0,
    startpoint=[], QUIET=false)
    model = Model(optimizer_with_attributes(Ipopt.Optimizer))
    set_optimizer_attribute(model, MOI.Silent(), QUIET)
    if QUIET == true
        set_optimizer_attribute(model, "print_level", 0)
    end
    if isempty(startpoint)
        @variable(model, x[1:n], start = 0)
    else
        @variable(model, x[i=1:n], start = startpoint[i])
    end
    @NLobjective(model, Min, sum(coe[1][j]*prod(x[supp[1][j][k]] for k=1:length(supp[1][j])) for j=1:length(supp[1])))
    for i = 1:nb
        @NLconstraint(model, x[i]^2-1==0)
    end
    for i in 1:m-numeq
        @NLconstraint(model, sum(coe[i+1][j]*prod(x[supp[i+1][j][k]] for k=1:length(supp[i+1][j])) for j=1:length(supp[i+1]))>=0)
    end
    for i in m-numeq+1:m
        @NLconstraint(model, sum(coe[i+1][j]*prod(x[supp[i+1][j][k]] for k=1:length(supp[i+1][j])) for j=1:length(supp[i+1]))==0)
    end
    optimize!(model)
    status = termination_status(model)
    objv = objective_value(model)
    if QUIET == false
        println("optimum = $objv")
    end
    return objv,value.(x),status
end

function local_solution(n, m, supp::Vector{Array{UInt8, 2}}, coe; nb=0, numeq=0,
    startpoint=[], QUIET=false)
    model = Model(optimizer_with_attributes(Ipopt.Optimizer))
    set_optimizer_attribute(model, MOI.Silent(), QUIET)
    if QUIET == true
        set_optimizer_attribute(model, "print_level", 0)
    end
    if isempty(startpoint)
        @variable(model, x[1:n], start = 0)
    else
        @variable(model, x[i=1:n], start = startpoint[i])
    end
    @NLobjective(model, Min, sum(coe[1][j]*prod(x[k]^supp[1][k,j] for k=1:n) for j=1:size(supp[1], 2)))
    for i = 1:nb
        @NLconstraint(model, x[i]^2-1==0)
    end
    for i in 1:m-numeq
        @NLconstraint(model, sum(coe[i+1][j]*prod(x[k]^supp[i+1][k,j] for k=1:n) for j=1:size(supp[i+1], 2))>=0)
    end
    for i in m-numeq+1:m
        @NLconstraint(model, sum(coe[i+1][j]*prod(x[k]^supp[i+1][k,j] for k=1:n) for j=1:size(supp[i+1], 2))==0)
    end
    optimize!(model)
    status = termination_status(model)
    objv = objective_value(model)
    if QUIET == false
        println("optimum = $objv")
    end
    return objv,value.(x),status
end

"""
    ref_sol,upper_bound,rel_gap = refine_sol(opt, sol, data, QUIET=false, tol=1e-4)

Refine the obtained solution by a local solver.
Return the refined solution, the upper bound given by the local solver and the
relative optimality gap.
"""
function refine_sol(opt, sol, data::upop_data; QUIET=false, tol=1e-4)
    n = data.n
    nb = data.nb
    supp = data.supp
    coe = data.coe
    for i = 1:n
        if sol[i] == 0
            sol[i] = 1e-10
        end
    end
    upper_bound,rsol,status = local_solution(n, 0, [supp], [coe], nb=nb, numeq=0, startpoint=sol, QUIET=QUIET)
    if status == MOI.LOCALLY_SOLVED
        gap = abs(upper_bound)>1 ? abs((opt-upper_bound)/upper_bound) : abs(opt-upper_bound)
        if gap < tol
            println("Global optimality certified!")
        end
    else
        rsol,upper_bound,gap = sol,nothing,nothing
        println("The local solver failed!")
    end
    return rsol,upper_bound,gap
end

function refine_sol(opt, sol, data::Union{cpop_data,mcpop_data}; QUIET=false, tol=1e-4)
    n = data.n
    nb = data.nb
    numeq = data.numeq
    if typeof(data) == cpop_data && !isempty(data.gb)
        m = length(data.pop)-1
        supp = Vector{Array{UInt8,2}}(undef, m+1)
        coe = Vector{Vector{Float64}}(undef, m+1)
        supp[2:m+1-numeq] = data.supp[2:end]
        coe[2:m+1-numeq] = data.coe[2:end]
        for k in [1; [k for k=m+2-numeq:m+1]]
            mon = monomials(data.pop[k])
            coe[k] = coefficients(data.pop[k])
            supp[k] = zeros(UInt8, n, length(mon))
            for i = 1:length(mon), j = 1:n
                @inbounds supp[k][j,i] = MultivariatePolynomials.degree(mon[i], data.x[j])
            end
        end
    else
        m = data.m
        supp = data.supp
        coe = data.coe
    end
    for i = 1:n
        if sol[i] == 0
            sol[i] = 1e-10
        end
    end
    upper_bound,rsol,status = local_solution(n, m, supp, coe, nb=nb, numeq=numeq, startpoint=sol, QUIET=QUIET)
    if status == MOI.LOCALLY_SOLVED
        gap = abs(upper_bound)>1 ? abs((opt-upper_bound)/upper_bound) : abs(opt-upper_bound)
        if gap < tol
            println("Global optimality certified!")
        end
    else
        rsol,upper_bound,gap = sol,nothing,nothing
        println("The local solver failed!")
    end
    return rsol,upper_bound,gap
end


# Tests for Hölder DEA Models
@testset "HolderDEAModel" begin

    # ------------------
    # Input oriented
    # ------------------
    X = [2 2; 1 4; 4 1; 4 3; 5 5; 6 1; 2 5; 1.6 8]
    Y = [1; 1; 1; 1; 1; 1; 1; 1]

    # Hölder l = 1
    holderio1 = deaholder(X, Y, l = 1, orient = :Input, rts = :VRS)

    @test typeof(holderio1) == HolderL1DEAModel

    @test nobs(holderio1) == 8
    @test ninputs(holderio1) == 2
    @test noutputs(holderio1) == 1
    @test efficiency(holderio1) ≈ [0; 0; 0; 2.0; 4.0; 0.0; 1.0; 0.6]
    @test efficiency(holderio1, :min) == [1; 1; 1; 2; 1; 2; 1; 1]
    @test convert(Matrix, peers(holderio1)) ≈
            [ 1.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0
            0.0  1.0  0.0  0.0  0.0  0.0  0.0  0.0
            0.0  0.0  1.0  0.0  0.0  0.0  0.0  0.0
            0.0  0.0  1.0  0.0  0.0  0.0  0.0  0.0
            0.0  1.0  0.0  0.0  0.0  0.0  0.0  0.0
            0.0  0.0  1.0  0.0  0.0  0.0  0.0  0.0
            0.0  1.0  0.0  0.0  0.0  0.0  0.0  0.0
            0.0  1.0  0.0  0.0  0.0  0.0  0.0  0.0]
    @test slacks(holderio1, :X) ≈ [0.0 0.0; 0.0 0.0; 0.0 0.0; 0.0 0.0; 0.0 1.0; 2.0 0.0; 0.0 1.0; 0.0 4.0]
    @test slacks(holderio1, :Y) ≈ zeros(8,1)

    @test efficiency(deaholder(targets(holderio1, :X), targets(holderio1, :Y), l = 1, orient = :Input, rts = :VRS)) ≈ zeros(8,1)

    # Test no slacks
    holderio1noslacks = deaholder(X, Y, l = 1, orient = :Input, rts = :VRS, slack = false)
    @test efficiency(holderio1noslacks) == efficiency(holderio1)
    @test isempty(slacks(holderio1noslacks, :X)) == 1
    @test isempty(slacks(holderio1noslacks, :Y)) == 1

    # Weighted (weakly)
    holderio1weight = deaholder(X, Y, l = 1, orient = :Input, rts = :VRS, weight = true)
    @test efficiency(holderio1weight) ≈ [0; 0; 0; 0.625; 0.8; 0.0; 0.5; 0.375]

    # Hölder l = 2
    logs, value = Test.collect_test_logs() do
        holderio2 = deaholder(X, Y, l = 2, orient = :Input, rts = :VRS, optimizer = DEAOptimizer(:NLP))
    end

    @test typeof(holderio2) == HolderL2DEAModel

    @test nobs(holderio2) == 8
    @test ninputs(holderio2) == 2
    @test noutputs(holderio2) == 1

    if (Base.find_package("Gurobi") !== nothing)
        using Gurobi
        @info ("Testing Hölder L2 with Gurobi")

        holderio2 = deaholder(X, Y, l = 2, orient = :Input, rts = :VRS, optimizer = DEAOptimizer(Gurobi.Optimizer))
        @test efficiency(holderio2) ≈ [0.0; 0.0; 0.0; 1.788854; 4.0; 0.0; 1.0; 0.6] atol = 1e-5
        @test slacks(holderio2, :X) ≈ [0.0 0.0; 0.0 0.0; 0.0 0.0; 0.0 0.0; 1.0 0.0; 0.0 0.0; 0.0 1.0; 0.0 4.0]
        @test slacks(holderio2, :Y) ≈ zeros(8,1)
        @test convert(Matrix, peers(holderio2)) ≈
            [ 1.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0
            0.0  1.0  0.0  0.0  0.0  0.0  0.0  0.0
            0.0  0.0  1.0  0.0  0.0  0.0  0.0  0.0
            0.4  0.0  0.6  0.0  0.0  0.0  0.0  0.0
            0.0  0.0  1.0  0.0  0.0  0.0  0.0  0.0
            0.0  0.0  0.0  0.0  0.0  1.0  0.0  0.0
            0.0  1.0  0.0  0.0  0.0  0.0  0.0  0.0
            0.0  1.0  0.0  0.0  0.0  0.0  0.0  0.0]

        @test efficiency(deaholder(targets(holderio2, :X), targets(holderio2, :Y), l = 2, orient = :Input, rts = :VRS, optimizer = DEAOptimizer(Gurobi.Optimizer))) ≈ zeros(8,1)

        # Weighted (weakly)
        holderio2weight = deaholder(X, Y, l = 2, orient = :Input, rts = :VRS, weight = true, optimizer = DEAOptimizer(Gurobi.Optimizer))
        @test efficiency(holderio2weight) ≈ [0.0; 0.0; 0.0; 0.554700; 0.8; 0.0; 0.468521; 0.375] atol = 1e-5
    else
        # Function runs without error
        @test occursin("Model solved with Ipopt.Optimizer is innacuate. Use a solver that supports SOS1 constraints.", string(logs))

        # Weighted (weakly)
        logs, value = Test.collect_test_logs() do
            holderio2weight = deaholder(X, Y, l = 2, orient = :Input, rts = :VRS, weight = true, optimizer = DEAOptimizer(:NLP))
        end
    end

    # Hölder l = Inf
    holderioinf = deaholder(X, Y, l = Inf, orient = :Input, rts = :VRS)

    @test typeof(holderioinf) == HolderLInfDEAModel

    @test nobs(holderioinf) == 8
    @test ninputs(holderioinf) == 2
    @test noutputs(holderioinf) == 1
    @test efficiency(holderioinf) ≈ [0.0; 0.0; 0.0; 4/3; 3.0; 0.0; 1.0; 0.6]
    @test convert(Matrix, peers(holderioinf)) ≈
            [ 1.0       0.0  0.0       0.0  0.0  0.0  0.0  0.0
            0.0       1.0  0.0       0.0  0.0  0.0  0.0  0.0
            0.0       0.0  1.0       0.0  0.0  0.0  0.0  0.0
            2/3       0.0  1/3       0.0  0.0  0.0  0.0  0.0
            1.0       0.0  0.0       0.0  0.0  0.0  0.0  0.0
            0.0       0.0  1.0       0.0  0.0  0.0  0.0  0.0
            0.0       1.0  0.0       0.0  0.0  0.0  0.0  0.0
            0.0       1.0  0.0       0.0  0.0  0.0  0.0  0.0]
    @test slacks(holderioinf, :X) ≈ [0.0 0.0; 0.0 0.0; 0.0 0.0; 0.0 0.0; 0.0 0.0; 2.0 0.0; 0.0 0.0; 0.0 3.4]
    @test slacks(holderioinf, :Y) ≈ zeros(8,1)

    @test efficiency(deaholder(targets(holderioinf, :X), targets(holderioinf, :Y), l = 1, orient = :Input, rts = :VRS)) ≈ zeros(8,1) atol = 1e-5

    # Test no slacks
    holderioinfnoslacks = deaholder(X, Y, l = Inf, orient = :Input, rts = :VRS, slack = false)
    @test efficiency(holderioinfnoslacks) == efficiency(holderioinf)
    @test isempty(slacks(holderioinfnoslacks, :X)) == 1
    @test isempty(slacks(holderioinfnoslacks, :Y)) == 1

    # Weighted (weakly)
    holderioinfweight = deaholder(X, Y, l = Inf, orient = :Input, rts = :VRS, weight = true)
    @test efficiency(holderioinfweight) ≈ [0; 0; 0; 0.4; 0.6; 0.0; 1/3; 0.375]

    # Print
    show(IOBuffer(), holderio1)
    show(IOBuffer(), holderio1noslacks)
    show(IOBuffer(), holderio1weight)
    show(IOBuffer(), holderio2)
    show(IOBuffer(), holderio2weight)
    show(IOBuffer(), holderioinf)
    show(IOBuffer(), holderioinfnoslacks)
    show(IOBuffer(), holderioinfweight)

    # ------------------
    # Output oriented
    # ------------------
    X = [1; 1; 1; 1; 1; 1; 1; 1]
    Y = [7 7; 4 8; 8 4; 3 5; 3 3; 8 2; 6 4; 1.5 5]

    # Hölder l = 1
    holderoo1 = deaholder(X, Y, l = 1, orient = :Output, rts = :VRS)

    @test typeof(holderoo1) == HolderL1DEAModel

    @test nobs(holderoo1) == 8
    @test ninputs(holderoo1) == 1
    @test noutputs(holderoo1) == 2
    @test efficiency(holderoo1) ≈ [0; 0; 0; 3.0; 5.0; 0.0; 2.0; 3.0]
    @test efficiency(holderoo1, :min) == [2; 2; 2; 3; 2; 2; 2; 3]
    @test convert(Matrix, peers(holderoo1)) ≈
            [ 1.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0
            0.0  1.0  0.0  0.0  0.0  0.0  0.0  0.0
            0.0  0.0  1.0  0.0  0.0  0.0  0.0  0.0
            0.0  1.0  0.0  0.0  0.0  0.0  0.0  0.0
            0.0  0.0  1.0  0.0  0.0  0.0  0.0  0.0
            0.0  0.0  1.0  0.0  0.0  0.0  0.0  0.0
            0.0  0.0  1.0  0.0  0.0  0.0  0.0  0.0
            0.0  1.0  0.0  0.0  0.0  0.0  0.0  0.0]
    @test slacks(holderoo1, :X) ≈ zeros(8,1)
    @test slacks(holderoo1, :Y) ≈ [0.0 0.0; 0.0 0.0; 0.0 0.0; 1.0 0.0; 0.0 1.0; 0.0 2.0; 0.0 0.0; 2.5 0.0]

    @test efficiency(deaholder(targets(holderoo1, :X), targets(holderoo1, :Y), l = 1, orient = :Output, rts = :VRS)) ≈ zeros(8,1) atol = 1e-5

    # Test no slacks
    holderoo1noslacks = deaholder(X, Y, l = 1, orient = :Output, rts = :VRS, slack = false)
    @test efficiency(holderoo1noslacks) == efficiency(holderoo1)
    @test isempty(slacks(holderoo1noslacks, :X)) == 1
    @test isempty(slacks(holderoo1noslacks, :Y)) == 1

    # Weighted (weakly)
    holderoo1weight = deaholder(X, Y, l = 1, orient = :Output, rts = :VRS, weight = true)
    @test efficiency(holderoo1weight) ≈ [0; 0; 0; 0.6; 5/3; 0.0; 1/3; 0.6]

    # Hölder l = 2
    logs, value = Test.collect_test_logs() do
        holderoo2 = deaholder(X, Y, l = 2, orient = :Output, rts = :VRS, optimizer = DEAOptimizer(:NLP))
    end

    @test typeof(holderoo2) == HolderL2DEAModel

    @test nobs(holderoo2) == 8
    @test ninputs(holderoo2) == 1
    @test noutputs(holderoo2) == 2

    if (Base.find_package("Gurobi") !== nothing)
        using Gurobi
        @info ("Testing Hölder L2 with Gurobi")

        holderoo2 = deaholder(X, Y, l = 2, orient = :Output, rts = :VRS, optimizer = DEAOptimizer(Gurobi.Optimizer))
        @test efficiency(holderoo2) ≈ [0.0; 0.0; 0.0; 3.0; 5.0; 0.0; 1.897367; 3.0] atol = 1e-5
        @test slacks(holderoo2, :X) ≈ zeros(8,1)
        @test slacks(holderoo2, :Y) ≈ [0.0 0.0; 0.0 0.0; 0.0 0.0; 1.0 0.0; 1.0 0.0; 0.0 0.0; 0.0 0.0; 2.5 0.0]
        @test convert(Matrix, peers(holderoo2)) ≈
            [ 1.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0
            0.0  1.0  0.0  0.0  0.0  0.0  0.0  0.0
            0.0  0.0  1.0  0.0  0.0  0.0  0.0  0.0
            0.0  1.0  0.0  0.0  0.0  0.0  0.0  0.0
            0.0  1.0  0.0  0.0  0.0  0.0  0.0  0.0
            0.0  0.0  0.0  0.0  0.0  1.0  0.0  0.0
            0.2  0.0  0.8  0.0  0.0  0.0  0.0  0.0
            0.0  1.0  0.0  0.0  0.0  0.0  0.0  0.0]

        @test efficiency(deaholder(targets(holderoo2, :X), targets(holderoo2, :Y), l = 2, orient = :Output, rts = :VRS, optimizer = DEAOptimizer(Gurobi.Optimizer))) ≈ zeros(8,1) atol = 1e-5

        # Weighted (weakly)
        holderoo2weight = deaholder(X, Y, l = 2, orient = :Output, rts = :VRS, weight = true, optimizer = DEAOptimizer(Gurobi.Optimizer))
        @test efficiency(holderoo2weight) ≈ [0.0; 0.0; 0.0; 0.6; 5/3; 0.0; 0.325396; 0.6] atol = 1e-5
    else
        # Function runs without error
        @test occursin("Model solved with Ipopt.Optimizer is innacuate. Use a solver that supports SOS1 constraints.", string(logs))

        # Weighted (weakly)
        logs, value = Test.collect_test_logs() do
            holderoo2weight = deaholder(X, Y, l = 2, orient = :Output, rts = :VRS, weight = true, optimizer = DEAOptimizer(:NLP))
        end
    end

    # Hölder l = Inf
    holderooinf = deaholder(X, Y, l = Inf, orient = :Output, rts = :VRS)

    @test typeof(holderooinf) == HolderLInfDEAModel

    @test nobs(holderooinf) == 8
    @test ninputs(holderooinf) == 1
    @test noutputs(holderooinf) == 2
    @test efficiency(holderooinf) ≈ [0.0; 0.0; 0.0; 2.5; 4.0; 0.0; 1.5; 2.875]
    @test convert(Matrix, peers(holderooinf)) ≈
            [ 1.0    0.0    0.0  0.0  0.0  0.0  0.0  0.0
            0.0    1.0    0.0  0.0  0.0  0.0  0.0  0.0
            0.0    0.0    1.0  0.0  0.0  0.0  0.0  0.0
            0.5    0.5    0.0  0.0  0.0  0.0  0.0  0.0
            1.0    0.0    0.0  0.0  0.0  0.0  0.0  0.0
            0.0    0.0    1.0  0.0  0.0  0.0  0.0  0.0
            0.5    0.0    0.5  0.0  0.0  0.0  0.0  0.0
            0.125  0.875  0.0  0.0  0.0  0.0  0.0  0.0]
    @test slacks(holderooinf, :X) ≈ zeros(8,1)
    @test slacks(holderooinf, :Y) ≈ [0.0 0.0; 0.0 0.0; 0.0 0.0; 0.0 0.0; 0.0 0.0; 0.0 2.0; 0.0 0.0; 0.0 0.0]

    @test efficiency(deaholder(targets(holderooinf, :X), targets(holderooinf, :Y), l = 1, orient = :Output, rts = :VRS)) ≈ zeros(8,1) atol = 1e-5

    # Test no slacks
    holderooinfnoslacks = deaholder(X, Y, l = Inf, orient = :Output, rts = :VRS, slack = false)
    @test efficiency(holderooinfnoslacks) == efficiency(holderooinf)
    @test isempty(slacks(holderooinfnoslacks, :X)) == 1
    @test isempty(slacks(holderooinfnoslacks, :Y)) == 1

    # Weighted (weakly)
    holderooinfweight = deaholder(X, Y, l = Inf, orient = :Output, rts = :VRS, weight = true)
    @test efficiency(holderooinfweight) ≈ [0.0; 0.0; 0.0; 5/9; 4/3; 0.0; 3/11; 0.6]

    # Print
    show(IOBuffer(), holderoo1)
    show(IOBuffer(), holderoo1noslacks)
    show(IOBuffer(), holderoo1weight)
    show(IOBuffer(), holderoo2)
    show(IOBuffer(), holderoo2weight)
    show(IOBuffer(), holderooinf)
    show(IOBuffer(), holderooinfnoslacks)
    show(IOBuffer(), holderooinfweight)

    # ------------------
    # Graph
    # ------------------
    X = [2; 4; 8; 12; 6; 14; 14; 9.412]
    Y = [1; 5; 8; 9; 3; 7; 9; 2.353]

    # Hölder l = 1
    holdergr1 = deaholder(X, Y, l = 1, orient = :Graph, rts = :VRS)

    @test typeof(holdergr1) == HolderL1DEAModel

    @test nobs(holdergr1) == 8
    @test ninputs(holdergr1) == 1
    @test noutputs(holdergr1) == 1
    @test efficiency(holdergr1) ≈ [0; 0; 0; 0.0; 3.0; 2.0; 0.0; 6.0]
    @test efficiency(holdergr1, :min) == [1; 1; 1; 1; 1; 2; 2; 2]
    @test convert(Matrix, peers(holdergr1)) ≈
            [1.0  0.0  0.0    0.0    0.0  0.0  0.0  0.0
            0.0  1.0  0.0    0.0    0.0  0.0  0.0  0.0
            0.0  0.0  1.0    0.0    0.0  0.0  0.0  0.0
            0.0  0.0  0.0    1.0    0.0  0.0  0.0  0.0
            0.5  0.5  0.0    0.0    0.0  0.0  0.0  0.0
            0.0  0.0  0.0    1.0    0.0  0.0  0.0  0.0
            0.0  0.0  0.0    1.0    0.0  0.0  0.0  0.0
            0.0  0.0  0.647  0.353  0.0  0.0  0.0  0.0]
    @test slacks(holdergr1, :X) ≈ [0; 0; 0; 0.0; 0.0; 2.0; 2.0; 0.0]
    @test slacks(holdergr1, :Y) ≈ zeros(8,1) atol = 1e-5

    @test efficiency(deaholder(targets(holdergr1, :X), targets(holdergr1, :Y), l = 1, orient = :Graph, rts = :VRS)) ≈ zeros(8,1) atol = 1e-5

    # Test no slacks
    holdergr1noslacks = deaholder(X, Y, l = 1, orient = :Graph, rts = :VRS, slack = false)
    @test efficiency(holdergr1noslacks) == efficiency(holdergr1)
    @test isempty(slacks(holdergr1noslacks, :X)) == 1
    @test isempty(slacks(holdergr1noslacks, :Y)) == 1

    # Weighted (weakly)
    holdergr1weight = deaholder(X, Y, l = 1, orient = :Graph, rts = :VRS, weight = true)
    @test efficiency(holdergr1weight) ≈ [0; 0; 0; 0.0; 0.5; 2/7; 0.0; 0.715629] atol = 1e-5

    # Hölder l = 2
    logs, value = Test.collect_test_logs() do
        holdergr2 = deaholder(X, Y, l = 2, orient = :Graph, rts = :VRS, optimizer = DEAOptimizer(:NLP))
    end

    @test typeof(holdergr2) == HolderL2DEAModel

    @test nobs(holdergr2) == 8
    @test ninputs(holdergr2) == 1
    @test noutputs(holdergr2) == 1

    if (Base.find_package("Gurobi") !== nothing)
        using Gurobi
        @info ("Testing Hölder L2 with Gurobi")

        holdergr2 = deaholder(X, Y, l = 2, orient = :Graph, rts = :VRS, optimizer = DEAOptimizer(Gurobi.Optimizer))
        @test efficiency(holdergr2) ≈ [0.0; 0.0; 0.0; 0.0; 2.683282; 2.0; 0.0; 5.3648] atol = 1e-5
        @test slacks(holdergr2, :X) ≈ [0.0; 0.0; 0.0; 0.0; 0.0; 2.0; 0.0; 0.0]
        @test slacks(holdergr2, :Y) ≈ [0.0; 0.0; 0.0; 0.0; 0.0; 0.0; 0.0; 0.0]
        @test convert(Matrix, peers(holdergr2)) ≈
            [1.0  0.0      0.0      0.0  0.0  0.0  0.0  0.0
            0.0  1.0      0.0      0.0  0.0  0.0  0.0  0.0
            0.0  0.0      1.0      0.0  0.0  0.0  0.0  0.0
            0.0  0.0      0.0      1.0  0.0  0.0  0.0  0.0
            0.2  0.8      0.0      0.0  0.0  0.0  0.0  0.0
            0.0  0.0      0.0      1.0  0.0  0.0  0.0  0.0
            0.0  0.0      0.0      0.0  0.0  0.0  1.0  0.0
            0.0  0.45172  0.54828  0.0  0.0  0.0  0.0  0.0]

        @test efficiency(deaholder(targets(holdergr2, :X), targets(holdergr2, :Y), l = 2, orient = :Graph, rts = :VRS, optimizer = DEAOptimizer(Gurobi.Optimizer))) ≈ zeros(8,1) atol = 1e-5

        # Weighted (weakly)
        holdergr2weight = deaholder(X, Y, l = 2, orient = :Graph, rts = :VRS, weight = true, optimizer = DEAOptimizer(Gurobi.Optimizer))
        @test efficiency(holdergr2weight) ≈  [0.0; 0.0; 0.0; 0.0; 0.485071; 2/7; 0.0; 0.710103] atol = 1e-5
    else
        # Function runs without error
        @test occursin("Model solved with Ipopt.Optimizer is innacuate. Use a solver that supports SOS1 constraints.", string(logs))

        # Weighted (weakly)
        logs, value = Test.collect_test_logs() do
            holdergr2weight = deaholder(X, Y, l = 2, orient = :Graph, rts = :VRS, weight = true, optimizer = DEAOptimizer(:NLP))
        end
    end

    # Hölder l = Inf
    holdergrinf = deaholder(X, Y, l = Inf, orient = :Graph, rts = :VRS)

    @test typeof(holdergrinf) == HolderLInfDEAModel

    @test nobs(holdergrinf) == 8
    @test ninputs(holdergrinf) == 1
    @test noutputs(holdergrinf) == 1
    @test efficiency(holdergrinf) ≈ [0.0; 0.0; 0.0; 0.0; 2.0; 2.0; 0.0; 3.832]
    @test convert(Matrix, peers(holdergrinf)) ≈
            [ 1.0  0.0    0.0    0.0  0.0  0.0  0.0  0.0
            0.0  1.0    0.0    0.0  0.0  0.0  0.0  0.0
            0.0  0.0    1.0    0.0  0.0  0.0  0.0  0.0
            0.0  0.0    0.0    1.0  0.0  0.0  0.0  0.0
            0.0  1.0    0.0    0.0  0.0  0.0  0.0  0.0
            0.0  0.0    0.0    1.0  0.0  0.0  0.0  0.0
            0.0  0.0    0.0    1.0  0.0  0.0  0.0  0.0
            0.0  0.605  0.395  0.0  0.0  0.0  0.0  0.0]
    @test slacks(holdergrinf, :X) ≈ [0.0; 0.0; 0.0; 0.0; 0.0; 0.0; 2.0; 0.0] atol = 1e-5
    @test slacks(holdergrinf, :Y) ≈ [0.0; 0.0; 0.0; 0.0; 0.0; 0.0; 0.0; 0.0] atol = 1e-5

    @test efficiency(deaholder(targets(holdergrinf, :X), targets(holdergrinf, :Y), l = 1, orient = :Graph, rts = :VRS)) ≈ zeros(8,1) atol = 1e-5

    # Test no slacks
    holdergrinfnoslacks = deaholder(X, Y, l = Inf, orient = :Graph, rts = :VRS, slack = false)
    @test efficiency(holdergrinfnoslacks) == efficiency(holdergrinf)
    @test isempty(slacks(holdergrinfnoslacks, :X)) == 1
    @test isempty(slacks(holdergrinfnoslacks, :Y)) == 1

    # Weighted (weakly)
    holdergrinfweight = deaholder(X, Y, l = Inf, orient = :Graph, rts = :VRS, weight = true)
    @test efficiency(holdergrinfweight) ≈ [0.0; 0.0; 0.0; 0.0; 0.4; 5/21; 0.0; 0.636115] atol = 1e-5

    # Print
    show(IOBuffer(), holdergr1)
    show(IOBuffer(), holdergr1noslacks)
    show(IOBuffer(), holdergr1weight)
    show(IOBuffer(), holdergr2)
    show(IOBuffer(), holdergr2weight)
    show(IOBuffer(), holdergrinf)
    show(IOBuffer(), holdergrinfnoslacks)
    show(IOBuffer(), holdergrinfweight)

    # ------------------
    # Test errors
    # ------------------
    @test_throws DimensionMismatch deaholder([1; 2 ; 3], [4 ; 5], l = 1) #  Different number of observations
    @test_throws DimensionMismatch deaholder([1; 2], [4 ; 5], l = 1, Xref = [1; 2; 3; 4]) # Different number of observations in reference sets
    @test_throws DimensionMismatch deaholder([1 1; 2 2], [4 4; 5 5], l = 1, Xref = [1 1 1; 2 2 2]) # Different number of inputs
    @test_throws DimensionMismatch deaholder([1 1; 2 2], [4 4; 5 5], l = 1, Yref = [4 4 4; 5 5 5]) # Different number of inputs
    @test_throws ArgumentError deaholder([1; 2; 3], [4; 5; 6], l = 1, orient = :Error) # Invalid orientation
    @test_throws ArgumentError deaholder([1; 2; 3], [4; 5; 6], l = 1, rts = :Error) # Invalid returns to scale
    @test_throws ArgumentError deaholder([1; 2; 3], [4; 5; 6], l = 2, rts = :Error) # CRS in L2
    @test_throws ArgumentError deaholder([1; 2; 3], [4; 5; 6], l = 5) # Invalid r
    @test_throws ArgumentError deaholder([1; 2; 3], [4; 5; 6], l = 2, rts = :VRS) # No solver
    @test_throws ArgumentError efficiency(holderio1, :Error) # Invalid efficiency type

end


import SpecialFunctions
import SpecialFunctions: besselj

@testset "Bessel Roots" begin
    @test_throws DomainError approx_besselroots(0.0, -1)

    msg = "`besselroots` has been renamed to `approx_besselroots`, and `besselroots` will be removed in the next major release."
    @test_logs (:warn, msg) besselroots(0.2,3)

    # Check if besselj(ν, approx_besselroots(ν, n) ) is small:
    tol = 1e-11

    for ν = 0.:0.1:5.
        n = 10
        @test norm( besselj.(ν, approx_besselroots(ν, n) ), Inf ) < tol
    end
end


import GuillotineModels
const IR = GuillotineModels.InstanceReader

function kp_ub(e, d_, p_, a_, A)
	se = sortperm(e; rev = true)
	d, p, a = getindex.((d_, p_, a_), (se, se, se))
	rem_A = A
	ub = 0.0
	for (i, qt) in pairs(IndexLinear(), d)
		for _ in 1:qt
			if rem_A < a[i]
				ub += floor((rem_A  * p[i]) / a[i])
				@goto out
			end
			rem_A -= a[i]
			ub += p[i]
		end
	end
	@label out
	return ub
end

function extract_inst_data(inst_name)
	N, L, W, l, w, p, d = IR.read_from_file(inst_name)
	n_ = sum(d)
	a = l .* w
	A = L .* W
	csv_lb = sum((d .* a) ./ A)
	e = p ./ (l .* w)
	kp_ub_ = kp_ub(e, d, p, a, A)
	min_l, min_w, min_a = minimum.((l, w, a))
	return inst_name, N, n_, L, W, A, min_l, min_w, min_a, kp_ub_, csv_lb
end

function create_csv_for(inst_names)
	header = "instance;N;n_;L;W;A;min_l;min_w;min_a;kp_ub;csv_lb\n"
	lines = map(inst_names) do inst
		values = extract_inst_data(inst)
		join(map(string, values), ";")
	end
	body = join(lines, "\n")
	csv = header * body

	return csv
end

const DATASET_C = vcat(
	"A" .* string.(1:5),
	"CHL" .* string.([1, 2, 5, 6, 7]),
	"CU" .* ["1", "2"],
	"CW" .* string.(1:3),
	"Hchl" .* ["2", "9"],
	"Hchl" .* string.([3, 4, 6, 7, 8]) .* "s",
	"HH",
	"OF" .* ["1", "2"],
	"STS" .* ["2", "4"],
	"W",
	"2",
	"3"
)

println(create_csv_for("../instances/" .* DATASET_C))


using VortexDistributions

# simple test field
Nx = 400; Ny = 400
Lx = 200; Ly = Lx
x = LinRange(-Lx/2,Lx/2, Nx+1)[1:end-1]; y = LinRange(-Ly/2,Ly/2, Ny+1)[1:end-1];
psi0 = one.(x*y') |> complex
psi1 = Torus(copy(psi0), x, y)
psi2 = Torus(copy(psi0), x, y)

# WEIRD BEHAVIOUR WHEN VORTICES ARE THIS CLOSE TO EACH OTHER, NEED TO INVESTIGATE
# vp = PointVortex(.1,.3,1)
# vn = PointVortex(-.2,.7,-1)

xp = 1.1; yp = .3;
xn = -2.23; yn = .7;
vp = PointVortex(xp, yp, 1)
vn = PointVortex(xn, yn, -1)
dip = Dipole(vp,vn)
@test typeof(dip) <: VortexGroup

sp = ScalarVortex([vp, vn])
vortex!(psi1, sp)
vfound1 = findvortices(psi1)

periodic_dipole!(psi2, sp)
vfound2 = findvortices(psi2, periodic=true)

vpf1 = vfound1[2];
vnf1 = vfound1[1];
vpf2 = vfound2[2];
vnf2 = vfound2[1];

dx = Δ(psi1.x)
@test isapprox(vpf1.xv, xp, atol=dx/4)
@test isapprox(vpf1.yv, yp, atol=dx/4)
@test vpf1.qv == 1

@test isapprox(vnf1.xv, xn, atol=dx/4)
@test isapprox(vnf1.yv, yn, atol=dx/4)
@test vnf1.qv == -1

@test isapprox(vpf2.xv, xp, atol=dx/4)
@test isapprox(vpf2.yv, yp, atol=dx/4)
@test vpf2.qv == 1

@test isapprox(vnf2.xv, xn, atol=dx/4)
@test isapprox(vnf2.yv, yn, atol=dx/4)
@test vnf2.qv == -1


function _test_normal_form(G, D, B, p, prec = 0)
  return _test_normal_form_isometry(G, D, p) &&
         _test_normal_form_congruence(G, D, B, p, prec) &&
         Hecke._ispadic_normal_form(D, p)
end

function _test_normal_form_isometry(M, D, p)
  return islocally_isometric(Zlattice(gram = M), Zlattice(gram = D), p)
end

function _test_normal_form_congruence(G, D, B, p, prec = 0)
  X = denominator(G) * B * G * transpose(B) - denominator(G) * D
  v = valuation(reduce(gcd, Hecke._eltseq(X)), p)
  if prec == 0
    return v >= 1
  else
    return v >= prec
  end
end

@testset "helpers" begin
  R = ResidueRing(ZZ, 9)
  @test Hecke._issquare(R(1), ZZ(3))
  R = ResidueRing(ZZ, ZZ(9))
  @test Hecke._issquare(R(1), ZZ(3))
end

@testset "NormalForm" begin
  for R in [ZZ, GF(3)]
    W = R[1;]
    V = R[0 1; 1 0]

    L = diagonal_matrix([W, V])
    @test (@inferred diagonal_matrix(Hecke.collect_small_blocks(L))) == L

    L = diagonal_matrix([W, W])
    @test diagonal_matrix(Hecke.collect_small_blocks(L)) == L

    L = diagonal_matrix([W, V, W])
    @test diagonal_matrix(Hecke.collect_small_blocks(L)) == L

    L = diagonal_matrix([W])
    @test diagonal_matrix(Hecke.collect_small_blocks(L)) == L

    L = diagonal_matrix([V])
    @test diagonal_matrix(Hecke.collect_small_blocks(L)) == L

    L = diagonal_matrix([V, W, W, V, V, W, W])
    @test diagonal_matrix(Hecke.collect_small_blocks(L)) == L

    L = zero_matrix(ZZ, 0, 0)
    @test Hecke.collect_small_blocks(L) == typeof(L)[]
  end

  G1 = matrix(FlintQQ, 4, 4, [2, 0, 0, 1,
                              0, 2, 0, 1,
                              0, 0, 4, 2,
                              1, 1, 2, 6])
  G2 = matrix(FlintQQ, 4, 4, [2, 1, 1, 0,
                              1, 2, 0, 0,
                              1, 0, 2, 0,
                              0, 0, 0, 16])

  D1, U1 = @inferred Hecke.padic_normal_form(G1, 2, prec = 30)
  @test _test_normal_form(G1, D1, U1, 2, 30)

  D2, U2 = @inferred Hecke.padic_normal_form(G2, 2, prec = 30)
  @test _test_normal_form(G2, D2, U2, 2, 30)

  DD = matrix(base_ring(D1), 4, 4, [2, 1, 0, 0,
                                    1, 2, 0, 0,
                                    0, 0, 4 + 8, 0,
                                    0, 0, 0, 16])
  @test DD == D1
  DD = matrix(base_ring(D2), 4, 4, [2, 1, 0, 0,
                                    1, 2, 0, 0,
                                    0, 0, 4 + 8, 0,
                                    0, 0, 0, 16])
  @test DD == D2

  D4 = matrix(FlintQQ,  4,  4,  [2, -1, -1, -1,
                                 -1, 2, 0, 0,
                                 -1, 0, 2, 0,
                                 -1, 0, 0, 2])

  D, B = Hecke.padic_normal_form(D4, 2)
  @test _test_normal_form(D4, D, B, 2)

  @test D == matrix(base_ring(D), 4, 4, [2, 1, 0, 0,
                                         1, 2, 0, 0,
                                         0, 0, 4, 2,
                                         0, 0, 2, 4])
  A4 = matrix(FlintQQ, 4, 4, [2, -1, 0, 0, -1, 2, -1, 0, 0, -1, 2, -1, 0, 0, -1, 2])
  D, B = Hecke.padic_normal_form(A4, 2)
  K = base_ring(D)
  @test D == matrix(K, 4, 4, [0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 2, 1, 0, 0, 1, 2])
  @test _test_normal_form(A4, D, B, 2)

  A4_extended = matrix(FlintQQ, 5, 5, [2, -1, 0, 0, -1,
                                       -1, 2, -1, 0, 0,
                                       0, -1, 2, -1, 0,
                                       0, 0, -1, 2, -1,
                                       -1, 0, 0, -1, 2])

  D, B = Hecke.padic_normal_form(A4_extended, 5)
  K = base_ring(D)
  @test D == matrix(K, 5, 5, [1, 0, 0, 0, 0,
                              0, 1, 0, 0, 0,
                              0, 0, 1, 0, 0,
                              0, 0, 0, 5, 0,
                              0, 0, 0, 0, 0])
  # isometry only works for non-degenerate lattices
  #@test _test_normal_form_isometry(A4_extended, D)
  @test  _test_normal_form_congruence(A4_extended, D, B, 5)
  #@test Hecke._ispadic_normal_form(D, 5)

  A4dual = inv(A4)
  D, B = Hecke.padic_normal_form(A4dual, 5)
  K = base_ring(D)
  @test D == matrix(K, 4, 4, [inv(K(5)), 0, 0, 0,
                              0, 1, 0, 0,
                              0, 0, 1, 0,
                              0, 0, 0, 1])
  @test _test_normal_form(A4dual, D, B, 5)

  # Corner cases
  Z = matrix(FlintQQ, 0, 0, [])
  B, D = Hecke.padic_normal_form(Z, 2)
  @test iszero(B)
  @test iszero(D)

  Z = zero_matrix(FlintQQ, 10, 10)
  B, D = Hecke.padic_normal_form(Z, 3)
  @test iszero(B)
  @test iszero(D)

  # 2-adic cases

  M = matrix(FlintQQ, 3, 3, fmpq[2, 1, 0, 1, 3, 1, 0, 1, 2])
  D, B = Hecke.padic_normal_form(M, 2)
  @test _test_normal_form(M, D, B, 2)

  M = matrix(FlintQQ, 3, 3, fmpq[400, 0, 0, 0, 160, 0, 0, 0, 80])
  D, B = Hecke.padic_normal_form(M, 2)
  @test _test_normal_form(M, D, B, 2)

  M = matrix(FlintQQ, 6, 6, fmpq[12544, 3584, 0, 896, 8064, 0, 3584, 1152, 896, 512, 2304, 1152, 0, 896, 18816, 5376, 3584, 8064, 896, 512, 5376, 1728, 1600, 2816, 8064, 2304, 3584, 1600, 6272, 0, 0, 1152, 8064, 2816, 0, 12544])
  D, B = Hecke.padic_normal_form(M, 2)
  @test _test_normal_form(M, D, B, 2)

  M = matrix(FlintQQ, 4, 4, fmpq[4, 0, 2, 1, 0, 4, 1, 2, 2, 1, 5, 1, 1, 2, 1, 5])
  D, B = Hecke.padic_normal_form(M, 2)
  @test _test_normal_form(M, D, B, 2)

  M = matrix(FlintQQ, 3, 3, fmpq[1, 0, 0, 0, 1, 0, 0, 0, 1])
  D, B = Hecke.padic_normal_form(M, 2)
  @test _test_normal_form(M, D, B, 2)

  M = matrix(FlintQQ, 6, 6, fmpq[2, 1, 0, 1, 1, 1, 1, 2, 1, 0, 0, 0, 0, 1, 2, 0, 0, 0, 1, 0, 0, 3, 0, 0, 1, 0, 0, 0, 3, 0, 1, 0, 0, 0, 0, 3])
  D, B = Hecke.padic_normal_form(M, 2)
  @test _test_normal_form(M, D, B, 2)

  M = matrix(FlintQQ, 10, 10, fmpq[3, 1, 0, 0, 0, -1, 1, -1, 0, 1, 1, 3, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 3, 1, 0, 1, -1, -1, -1, -1, 0, 1, 1, 3, -1, 0, -1, -1, 1, -1, 0, 0, 0, -1, 3, 1, 1, 2, 1, 2, -1, 0, 1, 0, 1, 3, 0, 1, 0, -1, 1, 1, -1, -1, 1, 0, 4, 1, -1, 2, -1, 0, -1, -1, 2, 1, 1, 4, 1, 2, 0, 0, -1, 1, 1, 0, -1, 1, 4, 1, 1, 1, -1, -1, 2, -1, 2, 2, 1, 5])
  D, B = Hecke.padic_normal_form(M, 2)
  @test _test_normal_form(M, D, B, 2)

  M = matrix(FlintQQ, 6, 6, fmpq[4, 1, -1, -1, 1, -1, 1, 4, 0, 1, 2, 1, -1, 0, 4, -1, 2, -1, -1, 1, -1, 4, -1, 0, 1, 2, 2, -1, 4, -1, -1, 1, -1, 0, -1, 4])
  D, B = Hecke.padic_normal_form(M, 2)
  @test _test_normal_form(M, D, B, 2)

  M = matrix(FlintQQ, 5, 5, fmpq[5, -1, -1, -1, -1, -1, 5, -1, -1, -1, -1, -1, 5, -1, -1, -1, -1, -1, 5, -1, -1, -1, -1, -1, 5])
  D, B = Hecke.padic_normal_form(M, 2)
  @test _test_normal_form(M, D, B, 2)

  M = matrix(FlintQQ, 3, 3, fmpq[2, 1, 0, 1, 4, 1, 0, 1, 4])
  D, B = Hecke.padic_normal_form(M, 2)
  @test _test_normal_form(M, D, B, 2)

  M = matrix(FlintQQ, 7, 7, fmpq[8, -4, 3, 4, 0, 1, 1, -4, 8, 1, 0, 4, 1, 1, 3, 1, 8, 4, 0, 1, 1, 4, 0, 4, 8, 3, 0, 4, 0, 4, 0, 3, 8, 4, 0, 1, 1, 1, 0, 4, 8, -4, 1, 1, 1, 4, 0, -4, 8])
  D, B = Hecke.padic_normal_form(M, 2)
  @test _test_normal_form(M, D, B, 2)

  M = matrix(FlintQQ, 24, 24, fmpq[2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18014398509481985//18014398509481984, -1//18014398509481984, -1//18014398509481984, -1//18014398509481984, 18014398509481983//18014398509481984, 18014398509481983//18014398509481984, 0, 0, 1, 1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1//36028797018963968, -1//18014398509481984, -1//18014398509481984, -1//18014398509481984, 0, 0, 0, 0, 0, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -36028797018963969//36028797018963968, -36028797018963969//36028797018963968, -36028797018963969//36028797018963968, 18014398509481983//18014398509481984, 18014398509481983//18014398509481984, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18014398509481985//18014398509481984, 0, 0, -18014398509481983//18014398509481984, 0, 18014398509481983//18014398509481984, 18014398509481983//18014398509481984, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18014398509481983//18014398509481984, 18014398509481983//18014398509481984, 0, 0, 0, 0, 0, 0, 2, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, -18014398509481983//18014398509481984, 18014398509481983//18014398509481984, 0, -18014398509481983//18014398509481984, 18014398509481983//18014398509481984, 0, 0, 0, 0, 0, 0, 0, 2, 0, -1, -1, 1, 0, 0, 0, 0, 18014398509481983//18014398509481984, 18014398509481983//18014398509481984, -18014398509481983//18014398509481984, 18014398509481983//18014398509481984, -18014398509481983//18014398509481984, -18014398509481983//18014398509481984, 18014398509481983//18014398509481984, -18014398509481983//18014398509481984, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, -1, 0, 0, 0, 0, -18014398509481983//18014398509481984, -18014398509481983//18014398509481984, 18014398509481983//18014398509481984, 0, 0, 18014398509481983//18014398509481984, 0, 0, 0, 0, 0, 0, 0, 0, 1, -1, 1, 2, 1, -1, 0, 0, 0, 0, -18014398509481983//18014398509481984, -18014398509481983//18014398509481984, 18014398509481983//18014398509481984, -18014398509481983//18014398509481984, 18014398509481983//18014398509481984, 18014398509481983//18014398509481984, -18014398509481983//18014398509481984, 18014398509481983//18014398509481984, 0, 0, 0, 0, 0, 0, 1, -1, 0, 1, 2, 0, 0, 0, 0, 0, -18014398509481983//18014398509481984, -18014398509481983//18014398509481984, 18014398509481983//18014398509481984, -18014398509481983//18014398509481984, 18014398509481983//18014398509481984, 18014398509481983//18014398509481984, -18014398509481983//18014398509481984, 18014398509481983//18014398509481984, 0, 0, 0, 0, 0, 0, 0, 1, -1, -1, 0, 2, 0, 0, 0, 0, 18014398509481983//18014398509481984, 18014398509481983//18014398509481984, -18014398509481983//18014398509481984, 18014398509481983//18014398509481984, -18014398509481983//18014398509481984, -18014398509481983//18014398509481984, 18014398509481983//18014398509481984, -18014398509481983//18014398509481984, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, -1, -1, 18014398509481983//18014398509481984, 0, -36028797018963969//36028797018963968, -18014398509481983//18014398509481984, 0, -18014398509481983//18014398509481984, 18014398509481983//18014398509481984, -18014398509481983//18014398509481984, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 18014398509481983//18014398509481984, 0, 0, -18014398509481983//18014398509481984, 0, -18014398509481983//18014398509481984, 18014398509481983//18014398509481984, -18014398509481983//18014398509481984, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 2, 1, -18014398509481983//18014398509481984, 0, 36028797018963969//36028797018963968, 18014398509481983//18014398509481984, 0, 18014398509481983//18014398509481984, -18014398509481983//18014398509481984, 18014398509481983//18014398509481984, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 1, 2, 0, 0, 36028797018963969//36028797018963968, 0, 0, 0, 0, 0, 1, 18014398509481985//18014398509481984, 1//36028797018963968, 0, 0, 0, 0, 18014398509481983//18014398509481984, -18014398509481983//18014398509481984, -18014398509481983//18014398509481984, -18014398509481983//18014398509481984, 18014398509481983//18014398509481984, 18014398509481983//18014398509481984, 18014398509481983//18014398509481984, -18014398509481983//18014398509481984, 0, 5192296858534827340300120177508381//1298074214633706907132624082305024, 649037107316853399523116512706557//324518553658426726783156020576256, -162259276829213336369980246065155//162259276829213363391578010288128, 18014398509481979//324518553658426726783156020576256, 54043195528445949//649037107316853453566312041152512, -1298074214633706708974240478003207//649037107316853453566312041152512, 324518553658426690754359001612289//162259276829213363391578010288128, -324518553658426690754359001612289//162259276829213363391578010288128, 1, -1//18014398509481984, -1//18014398509481984, -36028797018963969//36028797018963968, 18014398509481985//18014398509481984, 0, 0, 18014398509481983//18014398509481984, -18014398509481983//18014398509481984, -18014398509481983//18014398509481984, -18014398509481983//18014398509481984, 18014398509481983//18014398509481984, 0, 0, 0, 0, 649037107316853399523116512706557//324518553658426726783156020576256, 2596148429267413814265248164610069//649037107316853453566312041152512, 288230376151711757//1298074214633706907132624082305024, 2596148429267413814265248164610069//1298074214633706907132624082305024, -649037107316853435551913531670527//324518553658426726783156020576256, -1298074214633706817060631534895105//649037107316853453566312041152512, 324518553658426690754359001612289//324518553658426726783156020576256, 54043195528445949//649037107316853453566312041152512, 1, -1//18014398509481984, -1//18014398509481984, -36028797018963969//36028797018963968, 0, 0, 0, -18014398509481983//18014398509481984, 18014398509481983//18014398509481984, 18014398509481983//18014398509481984, 18014398509481983//18014398509481984, -18014398509481983//18014398509481984, -36028797018963969//36028797018963968, 0, 36028797018963969//36028797018963968, 36028797018963969//36028797018963968, -162259276829213336369980246065155//162259276829213363391578010288128, 288230376151711757//1298074214633706907132624082305024, 2596148429267413814265248164610069//649037107316853453566312041152512, 1298074214633707051247812158160913//1298074214633706907132624082305024, -54043195528445949//649037107316853453566312041152512, 649037107316853327465522474778629//649037107316853453566312041152512, -324518553658426690754359001612289//324518553658426726783156020576256, 324518553658426690754359001612289//324518553658426726783156020576256, 1, -1//18014398509481984, -1//18014398509481984, -36028797018963969//36028797018963968, 0, 0, -18014398509481983//18014398509481984, 18014398509481983//18014398509481984, 0, -18014398509481983//18014398509481984, -18014398509481983//18014398509481984, 18014398509481983//18014398509481984, -18014398509481983//18014398509481984, -18014398509481983//18014398509481984, 18014398509481983//18014398509481984, 0, 18014398509481979//324518553658426726783156020576256, 2596148429267413814265248164610069//1298074214633706907132624082305024, 1298074214633707051247812158160913//1298074214633706907132624082305024, 5192296858534827340300120177508381//1298074214633706907132624082305024, -1298074214633706817060631534895105//649037107316853453566312041152512, -54043195528445949//649037107316853453566312041152512, 0, 0, 0, 18014398509481983//18014398509481984, 0, 18014398509481983//18014398509481984, -18014398509481983//18014398509481984, 0, 18014398509481983//18014398509481984, -18014398509481983//18014398509481984, 0, 18014398509481983//18014398509481984, 18014398509481983//18014398509481984, -18014398509481983//18014398509481984, 0, 0, 0, 0, 54043195528445949//649037107316853453566312041152512, -649037107316853435551913531670527//324518553658426726783156020576256, -54043195528445949//649037107316853453566312041152512, -1298074214633706817060631534895105//649037107316853453566312041152512, 324518553658426690754359001612289//81129638414606681695789005144064, 324518553658426690754359001612289//162259276829213363391578010288128, -324518553658426690754359001612289//162259276829213363391578010288128, 324518553658426690754359001612289//324518553658426726783156020576256, 0, 18014398509481983//18014398509481984, 0, 18014398509481983//18014398509481984, 0, 0, 0, -18014398509481983//18014398509481984, 18014398509481983//18014398509481984, 18014398509481983//18014398509481984, 18014398509481983//18014398509481984, -18014398509481983//18014398509481984, -18014398509481983//18014398509481984, -18014398509481983//18014398509481984, 18014398509481983//18014398509481984, 0, -1298074214633706708974240478003207//649037107316853453566312041152512, -1298074214633706817060631534895105//649037107316853453566312041152512, 649037107316853327465522474778629//649037107316853453566312041152512, -54043195528445949//649037107316853453566312041152512, 324518553658426690754359001612289//162259276829213363391578010288128, 324518553658426690754359001612289//81129638414606681695789005144064, -324518553658426690754359001612289//162259276829213363391578010288128, 324518553658426690754359001612289//162259276829213363391578010288128, 0, 0, 0, 0, 18014398509481983//18014398509481984, 18014398509481983//18014398509481984, -18014398509481983//18014398509481984, 18014398509481983//18014398509481984, 0, -18014398509481983//18014398509481984, -18014398509481983//18014398509481984, 18014398509481983//18014398509481984, 18014398509481983//18014398509481984, 18014398509481983//18014398509481984, -18014398509481983//18014398509481984, 0, 324518553658426690754359001612289//162259276829213363391578010288128, 324518553658426690754359001612289//324518553658426726783156020576256, -324518553658426690754359001612289//324518553658426726783156020576256, 0, -324518553658426690754359001612289//162259276829213363391578010288128, -324518553658426690754359001612289//162259276829213363391578010288128, 324518553658426690754359001612289//81129638414606681695789005144064, -324518553658426690754359001612289//162259276829213363391578010288128, 0, 0, 0, 0, 18014398509481983//18014398509481984, 18014398509481983//18014398509481984, 18014398509481983//18014398509481984, -18014398509481983//18014398509481984, 0, 18014398509481983//18014398509481984, 18014398509481983//18014398509481984, -18014398509481983//18014398509481984, -18014398509481983//18014398509481984, -18014398509481983//18014398509481984, 18014398509481983//18014398509481984, 0, -324518553658426690754359001612289//162259276829213363391578010288128, 54043195528445949//649037107316853453566312041152512, 324518553658426690754359001612289//324518553658426726783156020576256, 0, 324518553658426690754359001612289//324518553658426726783156020576256, 324518553658426690754359001612289//162259276829213363391578010288128, -324518553658426690754359001612289//162259276829213363391578010288128, 324518553658426690754359001612289//81129638414606681695789005144064])
  D, B = Hecke.padic_normal_form(M, 2)
  @test _test_normal_form(M, D, B, 2)

  # non-dyadic tests
  M = matrix(FlintQQ, 2, 2, fmpq[2, -1, -1, 2])
  D, B = Hecke.padic_normal_form(M, 3)
  @test _test_normal_form(M, D, B, 3)

  M = matrix(FlintQQ, 2, 2, fmpq[2, -1, -1, 2])
  D, B = Hecke.padic_normal_form(M, 3)
  @test _test_normal_form(M, D, B, 3)

  M = matrix(FlintQQ, 3, 3, fmpq[2, -1, 0, -1, 2, -1, 0, -1, 2])
  D, B = Hecke.padic_normal_form(M, 3)
  @test _test_normal_form(M, D, B, 3)

  M = matrix(FlintQQ, 3, 3, fmpq[3, -1, -1, -1, 3, -1, -1, -1, 3])
  D, B = Hecke.padic_normal_form(M, 3)
  @test _test_normal_form(M, D, B, 3)

  R = ResidueRing(FlintZZ, fmpz(2)^30)
  U = matrix(R,2,2,[0,1,1,0])
  V = matrix(R,2,2,[2,1,1,2])
  W1 = matrix(R,1,1,[1])
  W3 = matrix(R,1,1,[3])
  W5 = matrix(R,1,1,[5])
  W7 = matrix(R,1,1,[7])

  p = fmpz(2)
   G = diagonal_matrix(W1,W1)
   b = Hecke._relations(G,1, p)
   @test b * G * transpose(b) == matrix(R, 2, 2, [5, 0,
                                         0, 5])

   G = diagonal_matrix(W1,W3)
   b = Hecke._relations(G,1, p)
   @test b * G * transpose(b) == matrix(R, 2, 2, [5, 0,
                                         0, 7])

   G = diagonal_matrix(W1,W5)
   b = Hecke._relations(G,1, p)
   @test b * G * transpose(b) == matrix(R, 2, 2, [5, 0,
                                        0, 1])
   G = diagonal_matrix(W1,W7)
   b = Hecke._relations(G,1, p)
   @test b * G * transpose(b) == matrix(R, 2, 2, [5, 0,
                                        0, 3])

   G = diagonal_matrix(W3,W3)
   b = Hecke._relations(G,1, p)
   @test b * G * transpose(b) == matrix(R, 2, 2, [7, 0,
                                        0, 7])

   G = diagonal_matrix(W3,W5)
   b = Hecke._relations(G,1, p)
   @test b * G * transpose(b) == matrix(R, 2, 2, [7, 0,
                                        0, 1])

   G = diagonal_matrix(W3,W7)
   b = Hecke._relations(G,1, p)
   @test b * G * transpose(b) == matrix(R, 2, 2, [7, 0,
                                        0, 3])

   G = diagonal_matrix(W5,W5)
   b = Hecke._relations(G,1, p)
   @test b * G * transpose(b) == matrix(R, 2, 2, [1, 0,
                                        0, 1])
   G = diagonal_matrix(W5,W7)
   b = Hecke._relations(G,1, p)
   @test b * G * transpose(b) == matrix(R, 2, 2, [1, 0,
                                        0, 3])
   G = diagonal_matrix(W7,W7)
   b = Hecke._relations(G,1, p)
   @test b * G * transpose(b) == matrix(R, 2, 2, [3, 0,
                                        0, 3])

   G = diagonal_matrix(V,V)
   b = Hecke._relations(G,3, p)
   @test b * G * transpose(b) == matrix(R, 4, 4, [0, 1, 0, 0,
                                        1, 0, 0, 0,
                                        0, 0, 0, 1,
                                        0, 0, 1, 0])
   G = diagonal_matrix(V,W1, W1)
   b = Hecke._relations(G,5, p)
   @test b * G * transpose(b) == matrix(R, 4, 4, [0, 1, 0, 0,
                                        1, 0, 0, 0,
                                        0, 0, 7, 0,
                                        0, 0, 0, 3])

   G = diagonal_matrix(V,W1, W5)
   b = Hecke._relations(G,5, p)
   @test b * G * transpose(b) == matrix(R, 4, 4, [0, 1, 0, 0,
                                        1, 0, 0, 0,
                                        0, 0, 3, 0,
                                        0, 0, 0, 3])
   G = diagonal_matrix(V,W3, W7)
   b = Hecke._relations(G,5, p)
   @test b * G * transpose(b) == matrix(R, 4, 4, [0, 1, 0, 0,
                                        1, 0, 0, 0,
                                        0, 0, 5, 0,
                                        0, 0, 0, 5])

   G = diagonal_matrix(W1,2*W1)
   b = Hecke._relations(G,6, p)
   @test b * G * transpose(b) == matrix(R, 2, 2, [3, 0,
                                        0, 6])

   G = diagonal_matrix(W1,2*W3)
   b = Hecke._relations(G,6, p)
   @test b * G * transpose(b) == matrix(R, 2, 2, [7, 0,
                                        0, 10])

   G = diagonal_matrix(W1,2*W5)
   b = Hecke._relations(G,6, p)
   @test b * G * transpose(b) == matrix(R, 2, 2, [3, 0,
                                        0, 14])

   G = diagonal_matrix(W1,2*W7)
   b = Hecke._relations(G,6, p)
   @test b * G * transpose(b) == matrix(R, 2, 2, [7, 0,
                                        0, 2])

   G = diagonal_matrix(W3,2*W5)
   b = Hecke._relations(G,6, p)
   @test b * G * transpose(b) == matrix(R, 2, 2, [5, 0,
                                        0, 6])

   G = diagonal_matrix(W3,2*W3)
   b = Hecke._relations(G,6, p)
   @test b * G * transpose(b) == matrix(R, 2, 2, [1, 0,
                                        0, 2])

   G = diagonal_matrix(2*W5, 4*W7)
   b = Hecke._relations(G,6, p)
   @test b * G * transpose(b) == matrix(R, 2, 2, [6, 0,
                                        0, 4])

   G = diagonal_matrix(W3, 2*V)
   b = Hecke._relations(G, 7, p)
   @test b * G * transpose(b) == matrix(R, 3, 3, [7, 0, 0,
                                        0, 0, 2,
                                        0, 2, 0])

   G = diagonal_matrix(W7, 2*V)
   b = Hecke._relations(G, 7, p)
   @test b * G * transpose(b) == matrix(R, 3, 3, [3, 0, 0,
                                        0, 0, 2,
                                        0, 2, 0])

   G = diagonal_matrix(U, 2*W1)
   b = Hecke._relations(G, 8, p)
   @test b * G * transpose(b) == matrix(R, 3, 3, [2, 1, 0,
                                        1, 2, 0,
                                        0, 0, 10])
   G = diagonal_matrix(U, 2*W5)
   b = Hecke._relations(G, 8, p)
   @test b * G * transpose(b) == matrix(R, 3, 3, [2, 1, 0,
                                        1, 2, 0,
                                        0, 0, 2])
   G = diagonal_matrix(V, 2*W1)
   b = Hecke._relations(G, 8, p)
   @test b * G * transpose(b) == matrix(R, 3, 3, [0, 1, 0,
                                        1, 0, 0,
                                        0, 0, 10])
   G = diagonal_matrix(V, 2*W7)
   b = Hecke._relations(G, 8, p)
   @test b * G * transpose(b) == matrix(R, 3, 3, [0, 1, 0,
                                        1, 0, 0,
                                        0, 0, 6])

   G = diagonal_matrix(W1, W5, 2*W5)
   b = Hecke._relations(G, 9, p)
   @test b * G * transpose(b) == matrix(R, 3, 3, [3, 0, 0,
                                        0, 3, 0,
                                        0, 0, 2])

   G = diagonal_matrix(W3, W3, 2*W5)
   b = Hecke._relations(G, 9, p)
   @test b * G * transpose(b) == matrix(R, 3, 3, [5, 0, 0,
                                        0, 1, 0,
                                        0, 0, 2])
   G = diagonal_matrix(W3, W3, 2*W1)
   b = Hecke._relations(G, 9, p)
   @test b * G * transpose(b) == matrix(R, 3, 3, [5, 0, 0,
                                        0, 1, 0,
                                        0, 0, 10])

   G = diagonal_matrix(W3, 4*W1)
   b = Hecke._relations(G, 10, p)
   @test b * G * transpose(b) == matrix(R, 2, 2, [7, 0,
                                        0, 20])

   G = diagonal_matrix(W5, 4*W5)
   b = Hecke._relations(G, 10, p)
   @test b * G * transpose(b) == matrix(R, 2, 2, [1, 0,
                                        0, 4])

   # _relations errors

   @test_throws ErrorException Hecke._relations(zero_matrix(R, 0, 0), 4, p)
   @test_throws ErrorException Hecke._relations(zero_matrix(R, 0, 0), 11, p)
   @test_throws ErrorException Hecke._relations(matrix(R, 4, 4, [1, 0, 0, 0,
                                                                 0, 1, 0, 0,
                                                                 0, 0, 1, 0,
                                                                 0, 0, 0, 3]), 5, p)
   @test_throws ErrorException Hecke._relations(matrix(R, 2, 2, [1, 0,
                                                                 0, 1]), 6, p)

   @test_throws ErrorException Hecke._normalize_twobytwo(matrix(R, 2, 2, [1, 2, 2, 1]), p)

   # _two_adic_normal_forms
   G = diagonal_matrix(2*W1,2*W1,4*V)
   D, B = Hecke._two_adic_normal_forms(G, p)
   @test D == matrix(R, 4, 4, [2, 0, 0, 0,
                               0, 10, 0, 0,
                               0, 0, 0, 4,
                               0, 0, 4, 0])
   @test B * G * transpose(B) == D

   G = diagonal_matrix(W1,2*V,2*W3,2*W5)
   D, B = Hecke._two_adic_normal_forms(G, p)
   @test D == matrix(R, 5, 5, [3, 0, 0, 0, 0,
                              0, 0, 2, 0, 0,
                              0, 2, 0, 0, 0,
                              0, 0, 0, 2, 0,
                              0, 0, 0, 0, 2])
   @test D == B * G * transpose(B)

   G = diagonal_matrix(U,2*V,2*W3,2*W5)
   D, B = Hecke._two_adic_normal_forms(G, p)
   @test D == matrix(R, 6, 6, [2, 1, 0, 0, 0, 0,
                               1, 2, 0, 0, 0, 0,
                               0, 0, 4, 2, 0, 0,
                               0, 0, 2, 4, 0, 0,
                               0, 0, 0, 0, 2, 0,
                               0, 0, 0, 0, 0, 6])
   @test D == B * G * transpose(B)

   @inferred Hecke._two_adic_normal_forms(G, p, partial = true)

   # test _ispadic_normal_form

   # not the right block structure
   @test !Hecke._ispadic_normal_form(matrix(FlintQQ, 3, 3, [1, 1, 1, 1, 1, 1, 1, 1, 1]), 2)
   @test !Hecke._ispadic_normal_form(matrix(FlintQQ, 3, 3, [1, 1, 1, 1, 1, 1, 1, 1, 1]), 3)
   @test !Hecke._ispadic_normal_form(matrix(FlintQQ, 2, 2, [1, 1, 1, 1]), 3)

   @test !Hecke._ispadic_normal_form(matrix(FlintQQ, 2, 2, [2, 0, 0, 1]), 3)
   @test !Hecke._ispadic_normal_form(matrix(FlintQQ, 2, 2, [1, 0, 0, 3]), 5)
   @test !Hecke._ispadic_normal_form(matrix(FlintQQ, 2, 2, [1, 0, 0, 4]), 5)
   @test !Hecke._ispadic_normal_form(matrix(FlintQQ, 2, 2, [1, 1, 1, 1]), 2)
   @test !Hecke._ispadic_normal_form(matrix(FlintQQ, 1, 1, [9]), 2)

 end


function P1(filename::String = "squaremesh", nc::Int64 = 1_00;
            resolution::Int64 = 0)
    # computes the virtual element solution of the Poisson problem on
    # the specified mesh
    # load the mesh  + ptri     : points triangulation
    #                + tri      : triangulation from `triangle``
    #                + pv       : vertices of the cells
    #                + cellsb   : polygonal cells (⚠ orientation is crucial)
    #                + celssbt  : triangulated cells
    #                + t        : all triangles
    #                + (pb, tb) : restricted delaunay mesh
    mesh_filename = "$(@__DIR__)/../test/data/$(filename)_$(nc).jld2"
    println("read file $(mesh_filename)")
    JLD2.@load(mesh_filename, ptri, tri ) #pv, cellsb, cellsbt, t, pb, tb)
    # boundary codition and source
    rhs, boundary_condition = if occursin("square", mesh_filename)
        rhs_sqr, boundary_condition_sqr
    else
        rhs_L, boundary_condition_L
    end
    # assemble matrices
    K, M = assembKM_P12D(ptri, tri)
     # Dirichlet conditions
    np, Ib = size(ptri, 1), unique(btri(tri)[:])
    pfact = 1e8
    K += sparse(Ib, Ib, fill(pfact, length(Ib)), np, np)
    F = M * rhs(ptri)
    vb = boundary_condition(ptri[Ib, :])
    F[Ib] .+= pfact * vb
    u = K \ F
    println("boundary conditions ", maximum(abs.(u[Ib] .- vb)))
    # plot (WARNING option not available (only for local use)
    if resolution > 0
        GLMakie.destroy!(GLMakie.global_gl_screen())
        f, L = Main.figure(1, fig3D = true, (resolution, resolution))
        h = Main.plot_t(hcat(ptri, u), tri, representation = "meshsurface",
                        scalars = u, colormap = "hsv", colorw = (0., 0, 0),
                        scene = L[1])
        Colorbar(f[1, 2], limits = (minimum(u), maximum(u)), colormap = :hsv,
                  size = 10,  height = Relative(3/4))
    end
    return u, ptri, tri, Ib
end


# Helper types to represent various combinations of vectors/matrices/scalings
const AbstractNumOrVector  = Union{Number, AbstractVector}
const AbstractNumOrMatrix  = Union{Number, AbstractVector, AbstractMatrix}
const AbstractNumOrUniform = Union{Number, AbstractVector, AbstractMatrix, UniformScaling}

const AbstractNumOrMatrixOrSymbol  = Union{Number, AbstractVector, AbstractMatrix, Symbol}
const AbstractNumOrUniformOrSymbol = Union{Number, AbstractVector, AbstractMatrix, UniformScaling, Symbol}

# Helper type to represent a Hermitian view of a block matrix
const HermitianBlockMatrix = Hermitian{T, M} where {T <: Number, M <: BlockMatrix{<:T}}

# Helper functions to convert various types to a 2d matrix
to_matrix(T, A::AbstractVector) = Matrix{T}(reshape(A, length(A), 1))
to_matrix(T, A::AbstractMatrix) = T.(A)  # Fallback
to_matrix(T, A::Number) = fill(T(A), 1, 1)
to_matrix(T, A::UniformScaling, s::Integer) = Matrix{T}(A*I, s, s)

function dftmatrix( n::Integer; unitary::Bool = false )

    M = fft( 1.0*I(n), 1 )

    # A scaling of 1/(√n) needs to be applied to make the DFT matrix unitary
    if( unitary )
        M = 1 / sqrt( n ) * M
    end

    return M
end


using SolveDSGE

filename = "model3b.txt"
path3 = joinpath(@__DIR__,filename)
process_model(path3)

processed_filename = "model3b_processed.txt"
processed_path3 =  joinpath(@__DIR__,processed_filename)

dsge3 = retrieve_processed_model(processed_path3)

x3 = [0.05, 3.05, 0.7, 0.7, 2.8]

tol = 1e-8
maxiters = 1000

ss3_obj = compute_steady_state(dsge3,x3,tol,maxiters)
ss3 = ss3_obj.zero

N   = PerturbationScheme(ss3,1.0,"first")
NN  = PerturbationScheme(ss3,1.0,"second")
NNN = PerturbationScheme(ss3,1.0,"third")

soln_fo3 = solve_model(dsge3,N)
soln_so3 = solve_model(dsge3,NN)
soln_to3 = solve_model(dsge3,NNN)

P = ChebyshevSchemeDet(ss3,chebyshev_nodes,[11,11,11],4,[0.07 3.2 0.8; -0.07 2.9 0.6],tol,1e-6,maxiters)
PP = ChebyshevSchemeDet(ss3,chebyshev_nodes,[21,21,21],4,[0.07 3.2 0.8; -0.07 2.9 0.6],tol,1e-6,maxiters)
PPP = ChebyshevSchemeDet(ss3,chebyshev_nodes,[31,31,31],5,[0.07 3.2 0.8; -0.07 2.9 0.6],tol,1e-6,maxiters)

soln_nl3a = solve_model(dsge3,P)
soln_nl3b = solve_model(dsge3,soln_so3,PP)
soln_nl3c = solve_model(dsge3,soln_nl3b,PPP)

L = SmolyakSchemeDet(ss3,chebyshev_gauss_lobatto,3,[0.07 3.2 0.8; -0.07 2.9 0.6],tol,1e-6,maxiters)
LL = SmolyakSchemeDet(ss3,chebyshev_gauss_lobatto,3,[0.07 3.2 0.8; -0.07 2.9 0.6],tol,1e-6,maxiters)
LLL = SmolyakSchemeDet(ss3,clenshaw_curtis_equidistant,3,[0.07 3.2 0.8; -0.07 2.9 0.6],tol,1e-6,maxiters)

soln_nl3d = solve_model(dsge3,L)
soln_nl3e = solve_model(dsge3,soln_to3,LL)
soln_nl3f = solve_model(dsge3,soln_nl3a,LLL)

M = PiecewiseLinearSchemeDet(ss3,[11,11,11],[0.07 3.2 0.8; -0.07 2.9 0.6],tol,1e-6,maxiters)

soln_nl3h = solve_model(dsge3,M)
soln_nl3i = solve_model(dsge3,soln_to3,M)
soln_nl3j = solve_model(dsge3,soln_nl3c,M)

simulated_data3 = simulate(soln_nl3f,ss3[1:3],100)


"""
    norm_spectrum( CR_N::Vector{<:Real}, CR_S::Vector{<:Real}, CR_C::Vector{<:Real},
                   pmin::Real, pmax::Real, mc::Real, mode::Integer=3)

Computes the distribution function with double boundaries.
"""
function norm_spectrum(CR_N::Vector{<:Real}, CR_S::Vector{<:Real}, CR_C::Real,
    pmin::Real, pmax::Real, mc::Real, mode::Integer = 3)

    Nbins = size(CR_N, 1)

    # transform the norm dependent on IO mode
    norm = transform_norm(CR_N, mode)

    bin_width = log10(pmax / pmin) / Nbins

    CR_dis = Array{Float64,1}(undef, Int(2 * Nbins))
    CR_bound = Array{Float64,1}(undef, Int(2 * Nbins + 1))

    # get zeroth bin
    CR_bound[1] = pmin
    CR_dis[1] = norm[1]

    # all other bins
    j = 2
    for i = 1:Nbins-1

        # upper boundary of bin
        CR_bound[j] = pmin * 10.0^(bin_width * i)
        CR_dis[j] = norm[i] * (CR_bound[j] / CR_bound[j-1])^(-CR_S[i])
        if CR_bound[j] > CR_C / mc
            CR_bound[j] = CR_C / mc
        end

        # lower bound of next bin
        CR_bound[j+1] = CR_bound[j]
        CR_dis[j+1] = norm[i+1]
        if CR_bound[j] == CR_C / mc
            CR_bound[j+1] = CR_C / mc
        end

        j += 2

    end

    # last boundary
    CR_bound[j] = pmax
    if CR_bound[j-1] < CR_C / mc
        CR_bound[j] = CR_C / mc
    end
    CR_dis[j] = norm[Nbins] * (CR_bound[j] / CR_bound[j-1])^(-CR_S[Nbins])
    CR_bound[j+1] = CR_bound[j]

    return CR_bound, CR_dis

end

function norm_spectrum(CR_N::Vector{T}, CR_S::Vector{T},
                        CR_Cut::T, ρ::T;
                        par::CRMomentumDistributionConfig,
                        mode::Integer = 3) where {T}

    # transform the norm dependent on IO mode
    norm = transform_norm(CR_N, mode)

    bounds = 10.0 .^ collect(log10(par.pmin):par.bin_width:log10(par.pmax))
    bound_up = Vector{Float64}(undef, par.Nbins)

    for i = 1:par.Nbins
        bound_up[i] = (bounds[i+1] > CR_Cut) ? CR_Cut : bounds[i+1]
    end

    bound_up[end] = (bounds[end] > CR_Cut) ? CR_Cut : bounds[end]

    bin_centers = Vector{Float64}(undef, par.Nbins)

    @inbounds for i = 1:par.Nbins
        bin_centers[i] = 10.0^(0.5 * (log10(bounds[i]) + log10(bound_up[i])))
    end

    return CR_NormSpectrum(bin_centers, norm)
end


@testset "aggregation" begin

    function test_mean(x)
        z = mean(x)
    end

    function test_mean2(x)
        z = mean(x, 1)
    end

    test_compare(test_mean; x=rand(3))
    test_compare(test_mean; x=rand(3,4))

    function test_sum_1(x)
        y = sum(x, dims=1)
        y2 = y .+ 2
        z = sum(y2)
    end

    function test_sum_2(x)
        y = sum(x, dims=2)
        y2 = y .+ 2
        z = sum(y2)
    end

    test_compare(test_sum_1; x=rand(3,4))
    test_compare(test_sum_2; x=rand(3,4))
end


# The Computer Language Benchmarks Game
# https://salsa.debian.org/benchmarksgame-team/benchmarksgame/

# contributed by Jarret Revels and Alex Arslan
# based on the Java version

module NBody

using Printf
using LinearAlgebra

# Constants
const solar_mass = 4 * pi * pi
const days_per_year = 365.24

struct Vec3
    x::NTuple{3, Float64}
end
Vec3(x, y, z) = Vec3((x,y,z))
Base.:/(v::Vec3, n::Number) = Vec3(1/n .* v.x)
Base.:*(v::Vec3, n::Number) = Vec3(n .* v.x)
Base.:-(v1::Vec3, v2::Vec3) = Vec3(v1.x .- v2.x)
Base.:+(v1::Vec3, v2::Vec3) = Vec3(v1.x .+ v2.x)
# Todo, prettify
squarednorm(v1::Vec3) = v1.x[1]^2 + v1.x[2]^2 + v1.x[3]^2

# A heavenly body in the system
mutable struct Body
    pos::Vec3
    v::Vec3
    mass::Float64
end

function offset_momentum!(b::Body, p::Vec3)
    b.v -= p / solar_mass
end

function init_sun!(bodies::Vector{Body})
    p = Vec3((0.0, 0.0, 0.0))
    for b in bodies
        p += b.v * b.mass
    end
    offset_momentum!(bodies[1], p)
end

function advance(bodies::Vector{Body}, dt::Number)
    for i = 1:length(bodies)
        @inbounds for j = i+1:length(bodies)
            delta = bodies[i].pos - bodies[j].pos
            dsq = squarednorm(delta)
            distance = sqrt(dsq)
            mag = dt / (dsq * distance)

            bodies[i].v -= delta * (bodies[j].mass * mag)
            bodies[j].v += delta * (bodies[i].mass * mag)
        end
    end

    for b in bodies
        b.pos += b.v * dt
    end
end

function energy(bodies::Vector{Body})
    e = 0.0
    for i = 1:length(bodies)
        e += 0.5 * bodies[i].mass * squarednorm(bodies[i].v)
        @inbounds for j = i+1:length(bodies)
            delta = bodies[i].pos - bodies[j].pos
            distance = sqrt(squarednorm(delta))
            e -= (bodies[i].mass * bodies[j].mass) / distance
        end
    end
    return e
end

function perf_nbody(N::Int=1000)
    jupiter = Body( Vec3(4.84143144246472090e+00,                  # pos[1] = x
                         -1.16032004402742839e+00,                 # pos[2] = y
                         -1.03622044471123109e-01),                # pos[3] = z
                   Vec3(1.66007664274403694e-03 * days_per_year,   # v[1] = vx
                        7.69901118419740425e-03 * days_per_year,   # v[2] = vy
                        -6.90460016972063023e-05 * days_per_year), # v[3] = vz
                   9.54791938424326609e-04 * solar_mass)       # mass

    saturn = Body(Vec3(8.34336671824457987e+00,
                       4.12479856412430479e+00,
                      -4.03523417114321381e-01),
                  Vec3(-2.76742510726862411e-03 * days_per_year,
                       4.99852801234917238e-03 * days_per_year,
                       2.30417297573763929e-05 * days_per_year),
                  2.85885980666130812e-04 * solar_mass)

    uranus = Body(Vec3(1.28943695621391310e+01,
                    -1.51111514016986312e+01,
                    -2.23307578892655734e-01),
                  Vec3(2.96460137564761618e-03 * days_per_year,
                       2.37847173959480950e-03 * days_per_year,
                       -2.96589568540237556e-05 * days_per_year),
                  4.36624404335156298e-05 * solar_mass)

    neptune = Body(Vec3(1.53796971148509165e+01,
                        -2.59193146099879641e+01,
                        1.79258772950371181e-01),
                   Vec3(2.68067772490389322e-03 * days_per_year,
                        1.62824170038242295e-03 * days_per_year,
                       -9.51592254519715870e-05 * days_per_year),
                   5.15138902046611451e-05 * solar_mass)

    sun = Body(Vec3(0.0, 0.0, 0.0), Vec3(0.0, 0.0, 0.0), solar_mass)

    bodies = [sun, jupiter, saturn, uranus, neptune]

    init_sun!(bodies)
    @printf("%.9f\n", energy(bodies))
    for i = 1:N
        advance(bodies, 0.01)
    end
    @printf("%.9f\n", energy(bodies))
end

end # module

n = parse(Int,ARGS[1])
NBody.perf_nbody(n)