function test_derivatives_projections_1d(k::Int, N::Int, el::Int)
    f(x) = sin(2π * x)
    df(x) = 2π * cos(2π * x)
    interval = 0..1

    print("Periodic B-Spline Basis:\n")
    basis = PeriodicBSpline(interval, k, N)

    M = basis ⊙ basis
    M_d = mass_matrix(basis')
    D = der_mat(basis)

    fh = M \ (f ⊙ basis)
    dfh = M_d \ (df ⊙ basis')

    @show maximum(abs.( D * fh - dfh))

    x, colmat = col_mat(basis, 100)
    x, colmat_d = col_mat(basis', 100)

    @show maximum(abs.(colmat * fh - f.(x)))
    @show maximum(abs.(colmat_d * dfh - df.(x)))
    @show maximum(abs.(colmat_d * D * fh - df.(x)))

    S = stiffness_matrix(basis')
    @show maximum(abs.(D' * M_d * D - S))

    print("Dirichlet B-Spline Basis:\n")
    basis = DirichletBSpline(interval, k, N)

    M = mass_matrix(basis)
    M_d = mass_matrix(basis')
    D = SplineFEEC.der_mat(basis)

    fh = M \ (f ⊙ basis)
    dfh = M_d \ (df ⊙ basis')

    @show maximum(abs.( D * fh - dfh))

    x, colmat = col_mat(basis, 100)
    x, colmat_d = col_mat(basis', 100)

    @show maximum(abs.(colmat * fh - f.(x)))
    @show maximum(abs.(colmat_d * dfh - df.(x)))
    @show maximum(abs.(colmat_d * D * fh - df.(x)))

    S = stiffness_matrix(basis')
    @show maximum(abs.(D' * M_d * D - S))

    print("Multi-patch Dirichlet B-Spline Basis: \n")

    Δx = (interval.right - interval.left)/el
    basis = DirichletMultipatchBSpline(interval, k, N, el)

    M = mass_matrix(basis)
    M_d =  mass_matrix_d(basis')
    D = der_mat(basis)
    fh = M \ L2_prod(f, basis)
    dfh = M_d\ L2_prod_d(df, basis')

    @show maximum(abs.( D * fh - dfh))

    x, colmat = col_mat(basis, 100)
    x_d, colmat_d = col_mat_d(basis', 100)

    @show maximum(abs.(colmat * fh - f.(x)))
    @show maximum(abs.(colmat_d * dfh - df.(x_d)))
    @show maximum(abs.(colmat_d * D * fh - df.(x_d)))
    S = stiffness_matrix(basis')

    @show maximum(abs.( D' * M_d * D - S))

    print("Multi-patch Periodic B-Spline Basis: \n")

    Δx = (interval.right - interval.left)/el
    basis = PeriodicMultipatchBSpline(interval, k, N, el)

    M = mass_matrix(basis)
    M_d =  mass_matrix_d(basis')
    D = der_mat(basis)
    fh = M \ L2_prod(f, basis)
    dfh = M_d\ L2_prod_d(df, basis')

    @show maximum(abs.( D * fh - dfh))

    x, colmat = col_mat(basis, 100)
    x_d, colmat_d = col_mat_d(basis', 100)

    @show maximum(abs.(colmat * fh - f.(x)))
    @show maximum(abs.(colmat_d * dfh - df.(x_d)))
    @show maximum(abs.(colmat_d * D * fh - df.(x_d)))
    S = stiffness_matrix(basis')

    @show maximum(abs.( D' * M_d * D - S))
end

function test_derivatives_projections_2d(k::NTuple{2,Int}, N::NTuple{2,Int})
    f(x,y) = sin(2π * x)*sin(2π * y)
    gradf(x,y) = 2π * SVector(cos(2π * x)*sin(2π * y),
                                sin(2π * x)*cos(2π * y))

    g(x,y) = SVector(sin(2π * x)*sin(2π * y),
                        sin(2π * y)*sin(2π * x))
    curlg(x,y) = 2π * (sin(2π * y)*cos(2π * x) -  sin(2π * x)*cos(2π * y))
    domain = (0..1) × (0..1)

    print("Periodic B-Spline Basis: \n")
    X, Y = PeriodicBSplineTensorProductBasis(domain, k, N).B
    V0 = X ⊗ Y
    V1 = SVector(X' ⊗ Y , X ⊗ Y')
    V2 = X' ⊗ Y'

    G = disc_grad(V0)
    C = disc_curl(V0)

    xy0, colmat0 = col_mat(V0, 100)
    (xy1_1, colmat1_1), (xy1_2, colmat1_2) = col_mat.(V1, 100)
    xy2, colmat2 = col_mat(V2, 100)

    fh = L2_proj(f, V0)
    gradfh = L2_proj(gradf, V1)

    @show maximum(abs.(vcat((G * fh - gradfh)...)))

    val_f = (i -> f.(xy0[i][1], xy0[i][2])).(1:length(xy0))
    @show maximum(abs.(colmat0 * fh - val_f))

    val_gradf1 = (i -> gradf.(xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
    val_gradf2 = (i -> gradf.(xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2))
    #return fh, gradfh, colmat1_1, val_gradf1

    @show maximum(abs.(colmat1_1 * gradfh[1] - val_gradf1))
    @show maximum(abs.(colmat1_2 * gradfh[2] - val_gradf2))

    gh = L2_proj(g, V1)
    curlgh = L2_proj(curlg, V2)
    @show maximum(abs.((C * gh - curlgh)))

    val_g1 = (i -> g.(xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
    val_g2 = (i -> g.(xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2))
    @show maximum(abs.(colmat1_1 * gh[1] - val_g1))
    @show maximum(abs.(colmat1_2 * gh[2] - val_g2))

    val_curlg = (i -> curlg.(xy2[i][1], xy2[i][2])).(1:length(xy2))
    @show maximum(abs.(colmat2 * curlgh - val_curlg))

    print("Dirichlet B-Spline Basis: \n")
    X, Y = DirichletBSplineTensorProductBasis(domain, k, N).B
    V0 = X ⊗ Y
    V1 = SVector(X' ⊗ Y , X ⊗ Y')
    V2 = X' ⊗ Y'

    G = disc_grad(V0)
    C = disc_curl(V0)

    xy0, colmat0 = col_mat(V0, 100)
    (xy1_1, colmat1_1), (xy1_2, colmat1_2) = col_mat.(V1, 100)
    xy2, colmat2 = col_mat(V2, 100)

    fh = L2_proj(f, V0)
    gradfh = L2_proj(gradf, V1)

    @show maximum(abs.(vcat((G * fh - gradfh)...)))

    val_f = (i -> f.(xy0[i][1], xy0[i][2])).(1:length(xy0))
    @show maximum(abs.(colmat0 * fh - val_f))

    val_gradf1 = (i -> gradf.(xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
    val_gradf2 = (i -> gradf.(xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2))

    @show maximum(abs.(colmat1_1 * gradfh[1] - val_gradf1))
    @show maximum(abs.(colmat1_2 * gradfh[2] - val_gradf2))

    gh = L2_proj(g, V1)
    curlgh = L2_proj(curlg, V2)
    @show maximum(abs.((C * gh - curlgh)))

    val_g1 = (i -> g.(xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
    val_g2 = (i -> g.(xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2))
    @show maximum(abs.(colmat1_1 * gh[1] - val_g1))
    @show maximum(abs.(colmat1_2 * gh[2] - val_g2))

    val_curlg = (i -> curlg.(xy2[i][1], xy2[i][2])).(1:length(xy2))
    @show maximum(abs.(colmat2 * curlgh - val_curlg))

    #test collocation projections
    xy0, colmat0 = col_mat_proj(V0)
    (xy1_1, colmat1_1), (xy1_2, colmat1_2) = col_mat_proj.(V1)
    xy2, colmat2 = col_mat_proj(V2)

    val_f = (i -> f.(xy0[i][1], xy0[i][2])).(1:length(xy0))
    @show maximum(abs.(colmat0\val_f - fh))

    val_gradf1 = (i -> gradf.(xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
    val_gradf2 = (i -> gradf.(xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2))
    @show maximum(abs.(colmat1_1\val_gradf1 - gradfh[1]))
    @show maximum(abs.(colmat1_2\val_gradf2 - gradfh[2]))

    val_g1 = (i -> g.(xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
    val_g2 = (i -> g.(xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2))
    @show maximum(abs.(colmat1_1\val_g1 - gh[1]))
    @show maximum(abs.(colmat1_2\val_g2 - gh[2]))

    val_curlg = (i -> curlg.(xy2[i][1], xy2[i][2])).(1:length(xy2))
    @show maximum(abs.(colmat2\val_curlg - curlgh))

end

function test_derivatives_projections_Multipatch_2d(k::NTuple{2,Int}, N::NTuple{2,Int}, el::NTuple{2,Int})
    f(x,y) = sin(2π * x)*sin(2π * y)
    gradf(x,y) = 2π * SVector(cos(2π * x)*sin(2π * y),
                                sin(2π * x)*cos(2π * y))

    g(x,y) = SVector(sin(2π * x)*sin(2π * y),
                        sin(2π * y)*sin(2π * x))
    curlg(x,y) = 2π * (sin(2π * y)*cos(2π * x) -  sin(2π * x)*cos(2π * y))
    domain = (0..1) × (0..1)

    print("Multipatch Dirichlet B-Spline Basis: \n")
    X, Y = DirichletMultipatchTensorProductBSpline(domain, k, N, el).B
    V0 = X ⊗ Y
    V1 = SVector(X' ⊗ Y , X ⊗ Y')
    V2 = X' ⊗ Y'

    G = disc_diff(grad, V0)
    C = disc_diff(curl, V0)

    xy0, colmat0 = col_mat(V0, 100)

    #need a mixed setup
    x, cmx = col_mat_d(X', 100)
    y, cmy = col_mat(Y, 100)
    xy1_1 = [(a,b) for a in x for b in y]
    colmat1_1 = cmx ⊗ cmy

    x, cmx = col_mat(X, 100)
    y, cmy = col_mat_d(Y', 100)
    xy1_2 = [(a,b) for a in x for b in y]
    colmat1_2 = cmx ⊗ cmy

    xy2, colmat2 = col_mat_d(V2, 100)

    fh = L2_proj(f, V0)

    #also mixed setup TODO introduce better functions
    M1x = mass_matrix_d(X')
    M1y = mass_matrix(Y)
    M1_1 = M1x ⊗ M1y
    gradfh1 = M1_1\L2_prod_mixed_x((x,y) -> gradf(x,y)[1], X' ⊗ Y)

    M2x = mass_matrix(X)
    M2y = mass_matrix_d(Y')
    M1_2 = M2x ⊗ M2y
    gradfh2 = M1_2\L2_prod_mixed_y((x,y) -> gradf(x,y)[2], X ⊗ Y')
    gradfh = SVector(gradfh1, gradfh2)
    @show maximum(abs.(vcat((G * fh - gradfh)...)))

    val_f = (i -> f.(xy0[i][1], xy0[i][2])).(1:length(xy0))
    @show maximum(abs.(colmat0 * fh - val_f))

    val_gradf1 = (i -> gradf.(xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
    val_gradf2 = (i -> gradf.(xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2))

    @show maximum(abs.(colmat1_1 * gradfh[1] - val_gradf1))
    @show maximum(abs.(colmat1_2 * gradfh[2] - val_gradf2))

    #gh = L2_proj(g, V1)
    gh = SVector( M1_1\L2_prod_mixed_x((x,y) -> g(x,y)[1], X' ⊗ Y),
                  M1_2\L2_prod_mixed_y((x,y) -> g(x,y)[2], X ⊗ Y'))

    curlgh = L2_proj_d(curlg, V2)
    @show maximum(abs.((C * gh - curlgh)))

    val_g1 = (i -> g.(xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
    val_g2 = (i -> g.(xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2))
    @show maximum(abs.(colmat1_1 * gh[1] - val_g1))
    @show maximum(abs.(colmat1_2 * gh[2] - val_g2))

    val_curlg = (i -> curlg.(xy2[i][1], xy2[i][2])).(1:length(xy2))
    @show maximum(abs.(colmat2 * curlgh - val_curlg))

    #test collocation projections
    xy0, colmat0 = col_mat_proj(V0)
    xy1_1, colmat1_1 = col_mat_proj_mixed_x(V1[1])
    xy1_2, colmat1_2 = col_mat_proj_mixed_y(V1[2])
    xy2, colmat2 = col_mat_proj_d(V2)

    val_f = (i -> f.(xy0[i][1], xy0[i][2])).(1:length(xy0))
    @show maximum(abs.(colmat0\val_f - fh))

    val_gradf1 = (i -> gradf.(xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
    val_gradf2 = (i -> gradf.(xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2))
    @show maximum(abs.(colmat1_1\val_gradf1 - gradfh[1]))
    @show maximum(abs.(colmat1_2\val_gradf2 - gradfh[2]))

    val_g1 = (i -> g.(xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
    val_g2 = (i -> g.(xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2))
    @show maximum(abs.(colmat1_1\val_g1 - gh[1]))
    @show maximum(abs.(colmat1_2\val_g2 - gh[2]))

    val_curlg = (i -> curlg.(xy2[i][1], xy2[i][2])).(1:length(xy2))
    @show maximum(abs.(colmat2\val_curlg - curlgh))

    print("\nConga Dirichlet B-Spline Basis: \n")

    Δx = (domain.domains[1].right - domain.domains[1].left)/el[1]
    Δy = (domain.domains[2].right - domain.domains[2].left)/el[2]

    X, Y = DirichletMultipatchTensorProductBSpline(domain, k, N, el).B
    V0 = X ⊗ Y
    V1 = SVector(X' ⊗ Y , X ⊗ Y')
    V2 = X' ⊗ Y'

    G = disc_diff(grad, V0)
    C = disc_diff(curl, V0)
    P = conforming_averaging_projection_matrix(V0)
    Px = conforming_averaging_projection_matrix_x(V0)
    Py = conforming_averaging_projection_matrix_y(V0)

    xy0, colmat0 = col_mat(V0, 100)
    xy1_1, colmat1_1 = col_mat_mixed_x(V1[1], 100)
    xy1_2, colmat1_2 = col_mat_mixed_y(V1[2], 100)
    xy2, colmat2 = col_mat_d(V2, 100)

    fh = L2_proj_d(f, V0)
    fh *= 1/(Δx*Δy)

    gradfh = L2_proj_d(gradf, V1)
    gradfh = SVector(1/Δy *gradfh[1],1/Δx * gradfh[2])
    @show maximum(abs.(vcat(( G * SVector{size(P)[1]}(P * fh) - SVector(Py * gradfh[1],Px * gradfh[2]) )...)))

    val_f = (i -> f.(xy0[i][1], xy0[i][2])).(1:length(xy0))
    @show maximum(abs.(colmat0 * P * fh - val_f))

    val_gradf1 = (i -> gradf.(xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
    val_gradf2 = (i -> gradf.(xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2))
    @show maximum(abs.(colmat1_1 * Py * gradfh[1] - val_gradf1))
    @show maximum(abs.(colmat1_2 * Px * gradfh[2] - val_gradf2))

    gh = L2_proj_d(g, V1)
    gh = SVector(1/Δy * gh[1], 1/Δx * gh[2])
    curlgh = L2_proj_d(curlg, V2)
    @show maximum(abs.((C * SVector(Py * gh[1], Px * gh[2] ) - curlgh)))

    val_g1 = (i -> g.(xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
    val_g2 = (i -> g.(xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2))
    @show maximum(abs.(colmat1_1 * Py * gh[1] - val_g1))
    @show maximum(abs.(colmat1_2 * Px * gh[2] - val_g2))

    val_curlg = (i -> curlg.(xy2[i][1], xy2[i][2])).(1:length(xy2))
    @show maximum(abs.(colmat2 * curlgh - val_curlg))

    #testing the colocation projection
    xy0, colmat0 = col_mat_proj_d(V0)
    xy1_1, colmat1_1 = col_mat_proj_d(V1[1])
    xy1_2, colmat1_2 = col_mat_proj_d(V1[2])
    xy2, colmat2 = col_mat_proj_d(V2)

    val_f = (i -> f.(xy0[i][1], xy0[i][2])).(1:length(xy0))
    @show maximum(abs.((Δx*Δy) * colmat0\val_f - fh))

    val_gradf1 = (i -> gradf.(xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
    val_gradf2 = (i -> gradf.(xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2))
    @show maximum(abs.( Δy * colmat1_1\val_gradf1 -  gradfh[1]))
    @show maximum(abs.( Δx * colmat1_2\val_gradf2 -  gradfh[2]))

    val_g1 = (i -> g.(xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
    val_g2 = (i -> g.(xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2))
    @show maximum(abs.(Δy *colmat1_1\val_g1 - gh[1]))
    @show maximum(abs.(Δx *colmat1_2\val_g2 - gh[2]))

    val_curlg = (i -> curlg.(xy2[i][1], xy2[i][2])).(1:length(xy2))
    @show maximum(abs.(colmat2\val_curlg - curlgh))

    print("\nMultipatch Periodic B-Spline Basis: \n")
    X, Y = SplineFEEC.PeriodicMultipatchTensorProductBSpline(domain, k, N, el).B
    V0 = X ⊗ Y
    V1 = SVector(X' ⊗ Y , X ⊗ Y')
    V2 = X' ⊗ Y'

    G = disc_diff(grad, V0)
    C = disc_diff(curl, V0)

    xy0, colmat0 = col_mat(V0, 100)

    #need a mixed setup
    x, cmx = col_mat_d(X', 100)
    y, cmy = col_mat(Y, 100)
    xy1_1 = [(a,b) for a in x for b in y]
    colmat1_1 = cmx ⊗ cmy

    x, cmx = col_mat(X, 100)
    y, cmy = col_mat_d(Y', 100)
    xy1_2 = [(a,b) for a in x for b in y]
    colmat1_2 = cmx ⊗ cmy

    xy2, colmat2 = col_mat_d(V2, 100)

    fh = L2_proj(f, V0)

    #also mixed setup TODO introduce better functions
    M1x = mass_matrix_d(X')
    M1y = mass_matrix(Y)
    M1_1 = M1x ⊗ M1y
    gradfh1 = M1_1\SplineFEEC.L2_prod_mixed_x((x,y) -> gradf(x,y)[1], X' ⊗ Y)

    M2x = mass_matrix(X)
    M2y = mass_matrix_d(Y')
    M1_2 = M2x ⊗ M2y
    gradfh2 = M1_2\SplineFEEC.L2_prod_mixed_y((x,y) -> gradf(x,y)[2], X ⊗ Y')
    gradfh = SVector(gradfh1, gradfh2)
    @show maximum(abs.(vcat((G * fh - gradfh)...)))

    val_f = (i -> f.(xy0[i][1], xy0[i][2])).(1:length(xy0))
    @show maximum(abs.(colmat0 * fh - val_f))

    val_gradf1 = (i -> gradf.(xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
    val_gradf2 = (i -> gradf.(xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2))

    @show maximum(abs.(colmat1_1 * gradfh[1] - val_gradf1))
    @show maximum(abs.(colmat1_2 * gradfh[2] - val_gradf2))

    gh = SVector( M1_1\SplineFEEC.L2_prod_mixed_x((x,y) -> g(x,y)[1], X' ⊗ Y),
                  M1_2\SplineFEEC.L2_prod_mixed_y((x,y) -> g(x,y)[2], X ⊗ Y'))

    curlgh = L2_proj_d(curlg, V2)
    @show maximum(abs.((C * gh - curlgh)))

    val_g1 = (i -> g.(xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
    val_g2 = (i -> g.(xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2))
    @show maximum(abs.(colmat1_1 * gh[1] - val_g1))
    @show maximum(abs.(colmat1_2 * gh[2] - val_g2))

    val_curlg = (i -> curlg.(xy2[i][1], xy2[i][2])).(1:length(xy2))
    @show maximum(abs.(colmat2 * curlgh - val_curlg))

    #test collocation projections
    xy0, colmat0 = SplineFEEC.col_mat_proj(V0)
    xy1_1, colmat1_1 = SplineFEEC.col_mat_proj_mixed_x(V1[1])
    xy1_2, colmat1_2 = SplineFEEC.col_mat_proj_mixed_y(V1[2])
    xy2, colmat2 = SplineFEEC.col_mat_proj_d(V2)

    val_f = (i -> f.(xy0[i][1], xy0[i][2])).(1:length(xy0))
    @show maximum(abs.(colmat0\val_f - fh))

    val_gradf1 = (i -> gradf.(xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
    val_gradf2 = (i -> gradf.(xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2))
    @show maximum(abs.(colmat1_1\val_gradf1 - gradfh[1]))
    @show maximum(abs.(colmat1_2\val_gradf2 - gradfh[2]))

    val_g1 = (i -> g.(xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
    val_g2 = (i -> g.(xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2))
    @show maximum(abs.(colmat1_1\val_g1 - gh[1]))
    @show maximum(abs.(colmat1_2\val_g2 - gh[2]))

    val_curlg = (i -> curlg.(xy2[i][1], xy2[i][2])).(1:length(xy2))
    @show maximum(abs.(colmat2\val_curlg - curlgh))

    print("\nConga Periodic B-Spline Basis: \n")

    Δx = (domain.domains[1].right - domain.domains[1].left)/el[1]
    Δy = (domain.domains[2].right - domain.domains[2].left)/el[2]

    X, Y = SplineFEEC.PeriodicMultipatchTensorProductBSpline(domain, k, N, el).B
    V0 = X ⊗ Y
    V1 = SVector(X' ⊗ Y , X ⊗ Y')
    V2 = X' ⊗ Y'

    G = SplineFEEC.disc_diff(grad, V0)
    C = SplineFEEC.disc_diff(curl, V0)
    P = SplineFEEC.conforming_averaging_projection_matrix(V0)
    Px = SplineFEEC.conforming_averaging_projection_matrix_x(V0)
    Py = SplineFEEC.conforming_averaging_projection_matrix_y(V0)

    xy0, colmat0 = col_mat(V0, 100)
    xy1_1, colmat1_1 = SplineFEEC.col_mat_mixed_x(V1[1], 100)
    xy1_2, colmat1_2 = SplineFEEC.col_mat_mixed_y(V1[2], 100)
    xy2, colmat2 = col_mat_d(V2, 100)

    fh = L2_proj_d(f, V0)
    fh *= 1/(Δx*Δy)

    gradfh = L2_proj_d(gradf, V1)
    gradfh = SVector(1/Δy *gradfh[1],1/Δx * gradfh[2])
    @show maximum(abs.(vcat(( G * SVector{size(P)[1]}(P * fh) - SVector(Py * gradfh[1],Px * gradfh[2]) )...)))

    val_f = (i -> f.(xy0[i][1], xy0[i][2])).(1:length(xy0))
    @show maximum(abs.(colmat0 * P * fh - val_f))

    val_gradf1 = (i -> gradf.(xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
    val_gradf2 = (i -> gradf.(xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2))
    @show maximum(abs.(colmat1_1 * Py * gradfh[1] - val_gradf1))
    @show maximum(abs.(colmat1_2 * Px * gradfh[2] - val_gradf2))

    gh = L2_proj_d(g, V1)
    gh = SVector(1/Δy * gh[1], 1/Δx * gh[2])
    curlgh = L2_proj_d(curlg, V2)
    @show maximum(abs.((C * SVector(Py * gh[1], Px * gh[2] ) - curlgh)))

    val_g1 = (i -> g.(xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
    val_g2 = (i -> g.(xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2))
    @show maximum(abs.(colmat1_1 * Py * gh[1] - val_g1))
    @show maximum(abs.(colmat1_2 * Px * gh[2] - val_g2))

    val_curlg = (i -> curlg.(xy2[i][1], xy2[i][2])).(1:length(xy2))
    @show maximum(abs.(colmat2 * curlgh - val_curlg))

    #testing the colocation projection
    xy0, colmat0 = SplineFEEC.col_mat_proj_d(V0)
    xy1_1, colmat1_1 = SplineFEEC.col_mat_proj_d(V1[1])
    xy1_2, colmat1_2 = SplineFEEC.col_mat_proj_d(V1[2])
    xy2, colmat2 = SplineFEEC.col_mat_proj_d(V2)

    val_f = (i -> f.(xy0[i][1], xy0[i][2])).(1:length(xy0))
    @show maximum(abs.((Δx*Δy) * colmat0\val_f - fh))

    val_gradf1 = (i -> gradf.(xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
    val_gradf2 = (i -> gradf.(xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2))
    @show maximum(abs.( Δy * colmat1_1\val_gradf1 -  gradfh[1]))
    @show maximum(abs.( Δx * colmat1_2\val_gradf2 -  gradfh[2]))

    val_g1 = (i -> g.(xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
    val_g2 = (i -> g.(xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2))
    @show maximum(abs.(Δy *colmat1_1\val_g1 - gh[1]))
    @show maximum(abs.(Δx *colmat1_2\val_g2 - gh[2]))

    val_curlg = (i -> curlg.(xy2[i][1], xy2[i][2])).(1:length(xy2))
    @show maximum(abs.(colmat2\val_curlg - curlgh))
end

function test_Poisson_1d(k::Int, N::Int, el::Int)
    u(x) = sin(2π * x)
    f(x) = -4π^2 * sin(2π * x)
    interval = 0..1

    print("Periodic B-Spline Basis: \n")
    basis = PeriodicBSpline(interval, k, N)

    Δ = stiffness_matrix(basis')
    R = 1/N .* circ_ones(N)
    P = circ_id(N) - R
    fh = L2_prod(f, basis)
    uh = -(Δ + R) \ (P * fh)

    x, colmat = col_mat(basis, 200)
    @show maximum( abs.(colmat * uh .- u.(x) ))

    print("Dirichlet B-Spline Basis: \n")
    basis = DirichletBSpline(interval, k, N)

    fh = L2_prod(f, basis)
    # enforce homogenous boundary conditions
    Δ = stiffness_matrix(basis')
    ϵ = 1e-12
    Δ[1,1] *= (1 + 1/ϵ)
    Δ[end,end] *= (1 + 1/ϵ)

    uh = -Δ \ fh

    x, colmat = col_mat(basis, 200)
    @show maximum( abs.(colmat * uh .- u.(x) ))

    print("Multi-patch Dirichlet B-Spline Basis: \n")
    basis = SplineFEEC.DirichletMultipatchBSpline(interval, k, N, el)

    fh = L2_prod(f, basis)
    Δ = stiffness_matrix(basis')
    #dirichlet bc
    ϵ = 1e-12
    Δ[1,1] *= (1 +1/ϵ)
    Δ[end,end] *= (1 +1/ϵ)

    uh = -Δ \ fh

    x, colmat = col_mat(basis, 200)
    @show maximum( abs.(colmat * uh .- u.(x) ))
end

function test_Maxwells_1d(k::Int, N::Int, el::Int)
    # exact solution
    B = (t, x) -> cos(2π * t) * cos(2π * x)
    E = (t, x) -> sin(2π * t) * sin(2π * x)
    J = (t, x) -> 0
    domain = (0..1)
    it = 1234
    Δt = 0.001
    T = Δt * it
    Δx = (domain.right - domain.left)/el

    @show (k, N, el)
    @show Δt, it

    print("\nPeriodic B-Spline Basis: \n")
    # define the bases
    X = PeriodicBSpline(domain, k, N)
    V0 = X
    V1 = X'

    # calculate the mass matrices
    M0 = mass_matrix(V0)
    M0inv = inv(M0) # using the circulant inverse
    M1 = mass_matrix(V1)

    # calculate the discrete derivative
    G = disc_grad(V0)

    # discrete projections
    Bh = L2_proj(x -> B(0, x), V1)
    Eh = L2_proj(x -> E(0, x), V0)

    # simple leap frog scheme
    for i in 1:it
        Eh += Δt/2 * M0inv * (G' * (M1 * Bh))
        Bh -= Δt * (G * Eh)
        Eh += Δt/2 * M0inv * (G' * (M1 * Bh))
    end

    # collocation matrices
    x0, cm_0 = col_mat(V0, 100)
    x1, cm_1 = col_mat(V1, 100)

    @show maximum(abs.(cm_0 * Eh - E.(T, x0)))
    @show maximum(abs.(cm_1 * Bh - B.(T, x1)))

    print("\nDirichlet B-Spline Basis: \n")
    X = DirichletBSpline(domain, k, N)
    V0 = X
    V1 = X'

    # calculate the mass matrices
    M0 = mass_matrix(V0)

    ϵ = 1e-14
    M0[1,1] *= (1 + 1/ϵ)
    M0[end,end] *= (1 + 1/ϵ)

    M0inv = inv(M0) # using the circulant inverse
    M1 = mass_matrix(V1)

    # calculate the discrete derivative
    G = disc_grad(V0)

    # discrete projections
    Bh = L2_proj(x -> B(0, x), V1)
    Eh = L2_proj(x -> E(0, x), V0)

    # simple leap frog scheme
    for i in 1:it
        Eh += Δt/2 * M0inv * (G' * (M1 * Bh))
        Bh -= Δt * (G * Eh)
        Eh += Δt/2 * M0inv * (G' * (M1 * Bh))
    end

    # collocation matrices
    x0, cm_0 = col_mat(V0, 100)
    x1, cm_1 = col_mat(V1, 100)

    @show maximum(abs.(cm_0 * Eh - E.(T, x0)))
    @show maximum(abs.(cm_1 * Bh - B.(T, x1)))

    print("\nMulti-patch Dirichlet B-Spline Basis: \n")
    X = DirichletMultipatchBSpline(domain, k, N, el)
    V0 = X
    V1 = X'

    # calculate the mass matrices
    M0 = mass_matrix(V0)

    ϵ = 1e-14
    M0[1,1] *= (1 + 1/ϵ)
    M0[end,end] *= (1 + 1/ϵ)

    M0inv = inv(M0) # using the circulant inverse
    M1 = mass_matrix_d(V1) #special mass matrix

    # calculate the discrete derivative
    G = disc_grad(V0)

    # discrete projections
    Eh = L2_proj(x -> E(0, x), V0)
    Bh = L2_proj_d(x -> B(0, x), V1)

    # simple leap frog scheme
    for i in 1:it
        Eh += Δt/2 * M0inv * (G' * (M1 * Bh))
        Bh -= Δt * (G * Eh)
        Eh += Δt/2 * M0inv * (G' * (M1 * Bh))
    end

    # collocation matrices
    x0, cm_0 = col_mat(V0, 100)
    x1, cm_1 = col_mat_d(V1, 100)

    @show maximum(abs.(cm_0 * Eh - E.(T, x0)))
    @show maximum(abs.(cm_1 * Bh - B.(T, x1)))

    print("\nConga Dirichlet B-Spline Basis: \n")
    X = DirichletMultipatchBSpline(domain, k, N, el)
    V0 = X
    V1 = X'

    # calculate the mass matrices
    M0 = mass_matrix_d(V0)

    ϵ = 1e-14
    M0[1,1] *= (1 + 1/ϵ)
    M0[end,end] *= (1 + 1/ϵ)

    M0inv = 1/Δx^2 * inv(M0) # using the circulant inverse
    M1 = mass_matrix_d(V1) #special mass matrix

    # calculate the discrete derivative
    G = disc_grad(V0)
    P = conforming_averaging_projection_matrix(V0)

    # discrete projections
    Eh = 1/Δx * L2_proj_d(x -> E(0, x), V0)
    Bh = L2_proj_d(x -> B(0, x), V1)

    # simple leap frog scheme
    for i in 1:it
        Eh += Δt/2 * M0inv * (P' * G' * (M1 * Bh))
        Bh -= Δt * (G * P * Eh)
        Eh += Δt/2 * M0inv * (P' * G' * (M1 * Bh))
    end

    # collocation matrices
    x0, cm_0 = col_mat(V0, 100)
    x1, cm_1 = col_mat_d(V1, 100)

    @show maximum(abs.(cm_0 * P * Eh - E.(T, x0)))
    @show maximum(abs.(cm_1 * Bh - B.(T, x1)))

    print("\nMulti-patch Periodic B-Spline Basis: \n")
    X = PeriodicMultipatchBSpline(domain, k, N, el)
    V0 = X
    V1 = X'

    # calculate the mass matrices
    M0 = mass_matrix(V0)

    M0inv = inv(M0) # using the circulant inverse
    M1 = mass_matrix_d(V1) #special mass matrix

    # calculate the discrete derivative
    G = disc_grad(V0)

    # discrete projections
    Eh = L2_proj(x -> E(0, x), V0)
    Bh = L2_proj_d(x -> B(0, x), V1)

    # simple leap frog scheme
    for i in 1:it
        Eh += Δt/2 * M0inv * (G' * (M1 * Bh))
        Bh -= Δt * (G * Eh)
        Eh += Δt/2 * M0inv * (G' * (M1 * Bh))
    end

    # collocation matrices
    x0, cm_0 = col_mat(V0, 100)
    x1, cm_1 = col_mat_d(V1, 100)

    @show maximum(abs.(cm_0 * Eh - E.(T, x0)))
    @show maximum(abs.(cm_1 * Bh - B.(T, x1)))

    print("\nConga Periodic B-Spline Basis: \n")
    X = SplineFEEC.PeriodicMultipatchBSpline(domain, k, N, el)
    V0 = X
    V1 = X'

    # calculate the mass matrices
    M0 = mass_matrix_d(V0)

    M0inv = 1/Δx^2 * inv(M0) # using the circulant inverse
    M1 = mass_matrix_d(V1) #special mass matrix

    # calculate the discrete derivative
    G = disc_grad(V0)
    P = SplineFEEC.conforming_averaging_projection_matrix(V0)

    # discrete projections
    Eh = 1/Δx * L2_proj_d(x -> E(0, x), V0)
    Bh = L2_proj_d(x -> B(0, x), V1)

    # simple leap frog scheme
    for i in 1:it
        Eh += Δt/2 * M0inv * (P' * G' * (M1 * Bh))
        Bh -= Δt * (G * P * Eh)
        Eh += Δt/2 * M0inv * (P' * G' * (M1 * Bh))
    end

    # collocation matrices
    x0, cm_0 = col_mat(V0, 100)
    x1, cm_1 = col_mat_d(V1, 100)

    @show maximum(abs.(cm_0 * P * Eh - E.(T, x0)))
    @show maximum(abs.(cm_1 * Bh - B.(T, x1)))
end

function test_Issautier_1d(k::Int, N::Int, el::Int)
    # exact solution
    B = (t, x) -> (cos(t) - 1) * π * cos(π * x)
    E = (t, x) -> sin(t) * sin(π * x)
    J = (t, x) -> (cos(t) - 1) * π^2 * sin(π * x) - cos(t) * sin(π * x)
    domain = (0..1)
    it = 1234
    Δt = 0.01
    T = Δt * it
    Δx = (domain.right - domain.left)/el

    @show (k, N, el)
    @show Δt, it

    #exact E L2-norm
    tt = 0
    Enex = zeros(it)
    for i in 1:it
        tt += Δt
        Enex[i] = sqrt(hquadrature(x -> E(tt, x).^2, 0, 1)[1])
    end

    print("\nDirichlet B-Spline Basis: \n")
    X = DirichletBSpline(domain, k, N)
    V0 = X
    V1 = X'

    # calculate the mass matrices
    M0 = mass_matrix(V0)
    M0_old = deepcopy(M0)

    ϵ = 1e-13
    M0[1,1] *= (1 + 1/ϵ)
    M0[end,end] *= (1 + 1/ϵ)

    M0inv = inv(M0) # using the circulant inverse
    M1 = mass_matrix(V1)

    # calculate the discrete derivative
    G = disc_grad(V0)

    # discrete projections
    Bh = L2_proj(x -> B(0, x), V1)
    Eh = L2_proj(x -> E(0, x), V0)
    Jh = L2_prod(x -> J(0,x), V0)

    En1 = zeros(it)
    # simple leap frog scheme
    t = 0
    for i in 1:it
        Eh += Δt/2 * M0inv * (G' * (M1 * Bh) - Jh)

        Bh -= Δt * (G * Eh)
        t += Δt
        Jh =  L2_prod(x -> J(t, x), V0)

        Eh += Δt/2 * M0inv * (G' * (M1 * Bh) - Jh)
        En1[i] = 1/(2*X.N) * sqrt(dot(Eh, M0_old * Eh))
    end

    # collocation matrices
    x0, cm_0 = col_mat(V0, 100)
    x1, cm_1 = col_mat(V1, 100)

    @show maximum(abs.(cm_0 * Eh - E.(T, x0)))
    @show maximum(abs.(cm_1 * Bh - B.(T, x1)))
    @show maximum(abs.(En1 - Enex))

    print("\nMulti-patch Dirichlet B-Spline Basis: \n")
    X = DirichletMultipatchBSpline(domain, k, N, el)
    V0 = X
    V1 = X'

    # calculate the mass matrices
    M0 = mass_matrix(V0)
    M0_old = deepcopy(M0)

    ϵ = 1e-13
    M0[1,1] *= (1 + 1/ϵ)
    M0[end,end] *= (1 + 1/ϵ)

    M0inv = inv(M0) # using the circulant inverse
    M1 = mass_matrix_d(V1) #special mass matrix

    # calculate the discrete derivative
    G = disc_grad(V0)

    # discrete projections
    Eh = L2_proj(x -> E(0, x), V0)
    Bh = L2_proj_d(x -> B(0, x), V1)
    Jh = L2_prod(x -> J(0,x), V0)

    # simple leap frog scheme
    En2 = zeros(it)
    t = 0
    for i in 1:it
        Eh += Δt/2 * M0inv * (G' * (M1 * Bh) - Jh)
        Bh -= Δt * (G * Eh)
        t += Δt
        Jh =  L2_prod(x -> J(t, x), V0)
        Eh += Δt/2 * M0inv * (G' * (M1 * Bh) - Jh)
        En2[i] = 1/(2*X.B.N*el) * sqrt(dot(Eh, M0_old * Eh))
    end

    # collocation matrices
    x0, cm_0 = col_mat(V0, 100)
    x1, cm_1 = col_mat_d(V1, 100)

    @show maximum(abs.(cm_0 * Eh - E.(T, x0)))
    @show maximum(abs.(cm_1 * Bh - B.(T, x1)))
    @show maximum(abs.(En2 - Enex))

    print("\nConga Dirichlet B-Spline Basis: \n")
    X = DirichletMultipatchBSpline(domain, k, N, el)
    V0 = X
    V1 = X'

    # calculate the mass matrices
    M0 = mass_matrix_d(V0)
    M0_old = deepcopy(M0)

    ϵ = 1e-13
    M0[1,1] *= (1 + 1/ϵ)
    M0[end,end] *= (1 + 1/ϵ)

    M0inv = 1/Δx^2 * inv(M0) # using the circulant inverse
    M1 = mass_matrix_d(V1) #special mass matrix

    # calculate the discrete derivative
    G = disc_grad(V0)
    P = conforming_averaging_projection_matrix(V0)

    # discrete projections
    Eh = 1/Δx * L2_proj_d(x -> E(0, x), V0)
    Bh = L2_proj_d(x -> B(0, x), V1)
    #we could also directly project J on V^0_b
    Jh = L2_prod(x -> J(0, x), V0)

    En3 = zeros(it)
    # simple leap frog scheme
    t = 0
    for i in 1:it
        Eh += Δt/2 * M0inv * P' * (G' * (M1 * Bh) -   Jh)
        Bh -= Δt * (G * P * Eh)

        t += Δt
        Jh =  L2_prod(x -> J(t, x), V0)

        Eh += Δt/2 * M0inv * P' * (G' * (M1 * Bh) - Jh)
        En3[i] = 1/(2*X.B.N*el^2) * sqrt(dot(Eh, M0_old * Eh))
    end

    # collocation matrices
    x0, cm_0 = col_mat(V0, 100)
    x1, cm_1 = col_mat_d(V1, 100)

    @show maximum(abs.(cm_0 * P * Eh - E.(T, x0)))
    @show maximum(abs.(cm_1 * Bh - B.(T, x1)))
    @show maximum(abs.(En3 - Enex))

    return it, En1, En2, En3, Enex
end

# -Δu = f
function test_Poisson_2d(k::NTuple{2,Int}, N::NTuple{2,Int})
    u(x, y) = sin(2π * x) * sin(2π * y)
    f(x, y) = -4π^2 * sin(2π * x)* sin(2π * y) - 4π^2 * sin(2π * y) * sin(2π * x)
    domain = (0..1) × (0..1)

    print("Periodic B-Spline Basis: \n")
    basis = PeriodicBSplineTensorProductBasis(domain, k, N)

    Δ = ∇(basis) ⊙ ∇(basis)
    NN = prod(N)
    R = 1/NN .* SplineFEEC.circ_ones(NN)
    P = SplineFEEC.circ_id(NN) - R
    fh = L2_prod(f, basis)

    uh = -(Δ + R) \ (P * fh)

    xy, colmat = col_mat(basis, 100)
    val_u = (i -> u.(xy[i][1], xy[i][2])).(1:length(xy))
    @show maximum(abs.(val_u - colmat * uh))
    #return xy, colmat, fh, Δ, uh

    print("Dirichlet B-Spline Basis: \n")
    basis = DirichletBSplineTensorProductBasis(domain, k, N)

    #homogeneous bc built in
    Δ = ∇(basis) ⊙ ∇(basis)
    fh = L2_prod(f, basis)
    uh = -Δ\ fh

    xy, colmat = col_mat(basis, 100)
    val_u = (i -> u.(xy[i][1], xy[i][2])).(1:length(xy))
    @show maximum(abs.(val_u - colmat * uh))
end

function test_Maxwells_Issautier_2d(k::NTuple{2,Int}, N::NTuple{2,Int}, el::NTuple{2,Int})
    E₂(t, x, y) = sin(t) .* SVector( x * sin(π * y), y *  sin(π * x))
    B₂(t, x, y) = (cos(t) - 1) * π * ( y * cos(x*π) - x * cos(π * y))
    J₂(t, x, y) = (cos(t) - 1) .* SVector(π * cos(π * x) + π^2 * x * sin(π * y), π * cos(π * y) + π^2 * y * sin(π * x)) - cos(t) .* SVector( x * sin(π * y),  y * sin(π * x))
    domain = (0..1) × (0..1)

    @show k
    @show N
    @show el

    it = 1234
    Δt = 0.01
    #it = 1234
    #Δt = 0.00001
    T = Δt * it
    @show Δt, it

    tt = 0
    Enex = zeros(it)
    for i in 1:it
        tt += Δt
        Enex[i] = sqrt(hcubature(x -> dot(E₂(tt, x[1], x[2]), E₂(tt, x[1], x[2])), (0, 0), (1,1))[1])
    end

    print("Dirichlet B-Spline Basis: \n")
    # define bases
    X,Y = DirichletBSplineTensorProductBasis(domain, k, N).B
    V0 = X ⊗ Y
    V1 = SVector(X' ⊗ Y, X ⊗ Y')
    V2 = X' ⊗ Y'

    # calculate mass matrices
    M1 = mass_matrix.(V1) #Vector representing the diagonal of the mass blockmatrix
    M1_old = deepcopy(M1)

    ϵ = 1e-12
    M1[1].B[1,1] *= (1+1/ϵ)
    M1[1].B[end,end] *= (1+1/ϵ)
    M1[2].A[1,1] *= (1+1/ϵ)
    M1[2].A[end,end] *= (1+1/ϵ)

    M1inv = inv.(M1) #using the circulant inverse

    M2 = mass_matrix(V2)

    # calculate discrete curl
    C = disc_curl(V0)

    # or by colocation projection
    (xy1_1, colmat1_1), (xy1_2, colmat1_2) = col_mat_proj.(V1)
    xy2, colmat2 = col_mat_proj(V2)

    cmp_inv1 = inv(colmat1_1)
    cmp_inv2 = inv(colmat1_2)

    val_B = (i -> B₂.(0, xy2[i][1], xy2[i][2])).(1:length(xy2))
    val_E1 = (i -> E₂.(0, xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
    val_E2 = (i -> E₂.(0, xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2));
    val_J1 = (i -> J₂.(0, xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
    val_J2 = (i -> J₂.(0, xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2));

    Eh = SVector(cmp_inv1 * val_E1, cmp_inv2 * val_E2)
    Jh = SVector(cmp_inv1 * val_J1, cmp_inv2 * val_J2)
    Bh = colmat2\val_B;

    #prepare time stepping
    t = 0

    En1 = zeros(it)
    for i in 1:it
        Eh += Δt/2 * (M1inv .* (C' *  SVector{length(Bh)}(M2 * Bh) - Jh))
        Bh -= Δt * (C * Eh)

        t += Δt
        val_J1 = (i -> J₂.(t, xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
        val_J2 = (i -> J₂.(t, xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2))
        Jh = M1_old .* SVector(cmp_inv1 * val_J1, cmp_inv2 * val_J2)

        Eh += Δt/2 * (M1inv .* (C' *  SVector{length(Bh)}(M2 * Bh) - Jh))
        En1[i] =  1/(2*X.N) * 1/(2*Y.N) * sqrt(dot(Eh, M1_old .* Eh))
    end

    (xy1_1, colmat1_1), (xy1_2, colmat1_2) = col_mat.(V1, 10)
    xy2, colmat2 = col_mat(V2, 10)
    val_B = (i -> B₂.(T, xy2[i][1], xy2[i][2])).(1:length(xy2))
    val_E1 = (i -> E₂.(T, xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
    val_E2 = (i -> E₂.(T, xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2))

    @show maximum(abs.(colmat1_1 * Eh[1] - val_E1))
    @show maximum(abs.(colmat1_2 * Eh[2] - val_E2))
    @show maximum(abs.(colmat2 * Bh - val_B))
    @show maximum(abs.(En1 - Enex))

    print("Multipatch BSpline Basis:\n")
    # define bases
    X,Y = DirichletMultipatchTensorProductBSpline(domain, k, N, el).B
    V0 = X ⊗ Y
    V1 = SVector(X' ⊗ Y, X ⊗ Y')
    V2 = X' ⊗ Y'

    ϵ = 1e-12
    M1x = mass_matrix_d(X')
    M1y = mass_matrix(Y)
    M1_1_old = deepcopy(M1x ⊗ M1y)

    M1y[1,1] *= (1+1/ϵ)
    M1y[end,end] *= (1+1/ϵ)
    M1_1 = M1x ⊗ M1y

    M2x = mass_matrix(X)
    M2y = mass_matrix_d(Y')
    M1_2_old = deepcopy(M2x ⊗ M2y)

    M2x[1,1] *= (1+1/ϵ)
    M2x[end,end] *= (1+1/ϵ)
    M1_2 = M2x ⊗ M2y

    M1 = SVector(M1_1, M1_2)
    M1_old = SVector(M1_1_old, M1_2_old)

    M1inv = inv.(M1) #using the circulant inverse

    M2 = mass_matrix_d(V2)

    # calculate discrete curl
    C = disc_diff(curl, V0)

    # colocation projection
    xy1_1, colmat1_1 = SplineFEEC.col_mat_proj_mixed_x(V1[1])
    xy1_2, colmat1_2 = SplineFEEC.col_mat_proj_mixed_y(V1[2])
    xy2, colmat2 = SplineFEEC.col_mat_proj_d(V2)

    cmp_inv1 = inv(colmat1_1)
    cmp_inv2 = inv(colmat1_2)

    val_B = (i -> B₂.(0, xy2[i][1], xy2[i][2])).(1:length(xy2))
    val_E1 = (i -> E₂.(0, xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
    val_E2 = (i -> E₂.(0, xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2));
    val_J1 = (i -> J₂.(0, xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
    val_J2 = (i -> J₂.(0, xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2));

    Eh = SVector(cmp_inv1 * val_E1, cmp_inv2 * val_E2)
    Jh = SVector(cmp_inv1 * val_J1, cmp_inv2 * val_J2)
    Bh = colmat2\val_B;

    #prepare time stepping
    t = 0
    En2 = zeros(it)
    for i in 1:it
        Eh += Δt/2 * (M1inv .* (C' *  SVector{length(Bh)}(M2 * Bh) - Jh))
        Bh -= Δt * (C * Eh)

        t += Δt
        val_J1 = (i -> J₂.(t, xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
        val_J2 = (i -> J₂.(t, xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2))
        Jh = M1_old .* SVector(cmp_inv1 * val_J1, cmp_inv2 * val_J2)

        Eh += Δt/2 * (M1inv .* (C' *  SVector{length(Bh)}(M2 * Bh) - Jh))
        En2[i] =  1/(2*X.B.N*el[1]) * 1/(2 * Y.B.N*el[2]) * sqrt(dot(Eh, M1_old .* Eh))
    end

    x, cmx = col_mat_d(X', 10)
    y, cmy = col_mat(Y, 10)
    xy1_1 = [(a,b) for a in x for b in y]
    colmat1_1 = cmx ⊗ cmy

    x, cmx = col_mat(X, 10)
    y, cmy = col_mat_d(Y', 10)
    xy1_2 = [(a,b) for a in x for b in y]
    colmat1_2 = cmx ⊗ cmy

    xy2, colmat2 = col_mat_d(V2, 10)

    val_B = (i -> B₂.(T, xy2[i][1], xy2[i][2])).(1:length(xy2))
    val_E1 = (i -> E₂.(T, xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
    val_E2 = (i -> E₂.(T, xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2))

    @show maximum(abs.(colmat1_1 * Eh[1] - val_E1))
    @show maximum(abs.(colmat1_2 * Eh[2] - val_E2))
    @show maximum(abs.(colmat2 * Bh - val_B))
    @show maximum(abs.(En2 - Enex))

    print("Conga Multipatch BSpline Basis:\n")
    Δx = (domain.domains[1].right - domain.domains[1].left)/el[1]
    Δy = (domain.domains[2].right - domain.domains[2].left)/el[2]

    # define bases
    X,Y = SplineFEEC.DirichletMultipatchTensorProductBSpline(domain, k, N, el).B
    V0 = X ⊗ Y
    V1 = SVector(X' ⊗ Y, X ⊗ Y')
    V2 = X' ⊗ Y'

    M1 = mass_matrix_d.(V1)
    M1_old = deepcopy(M1)

    ϵ = 1e-12
    M1[1].B[1,1] *= (1+1/ϵ)
    M1[1].B[end,end] *= (1+1/ϵ)
    M1[2].A[1,1] *= (1+1/ϵ)
    M1[2].A[end,end] *= (1+1/ϵ)

    M1inv = inv.(M1)
    M1inv = SVector(1/(Δy)^2, 1/(Δx)^2) .* M1inv

    M2 = mass_matrix_d(V2)

    # calculate discrete curl
    C = disc_diff(curl, V0)

    P = SplineFEEC.conforming_averaging_projection_matrix(V0)
    Px = SplineFEEC.conforming_averaging_projection_matrix_x(V0)
    Py = SplineFEEC.conforming_averaging_projection_matrix_y(V0)

    # or by colocation projection
    (xy1_1, colmat1_1), (xy1_2, colmat1_2) = SplineFEEC.col_mat_proj_d.(V1)
    xy2, colmat2 = SplineFEEC.col_mat_proj_d(V2)

    cmp_inv1 = inv(colmat1_1)
    cmp_inv2 = inv(colmat1_2)

    val_B = (i -> B₂.(0, xy2[i][1], xy2[i][2])).(1:length(xy2))
    val_E1 = (i -> E₂.(0, xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
    val_E2 = (i -> E₂.(0, xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2));
    val_J1 = (i -> J₂.(0, xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
    val_J2 = (i -> J₂.(0, xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2));

    Eh = SVector(Δy *cmp_inv1 * val_E1, Δx *Δy *cmp_inv2 * val_E2)
    Jh = SVector(Δy *cmp_inv1 * val_J1, Δx *cmp_inv2 * val_J2)
    Bh = colmat2\val_B;

    #prepare time stepping
    t = 0
    En3 = zeros(it)
    for i in 1:it
        Eh += Δt/2 *  (M1inv .* (SVector(Py', Px') .* (C' *  SVector{length(Bh)}(M2 * Bh)) - Jh))
        Bh -= Δt *  (C * (SVector(Py , Px) .* Eh))

        t += Δt
        val_J1 = (i -> J₂.(t, xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
        val_J2 = (i -> J₂.(t, xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2))
        Jh =  M1_old .* SVector(Δy * cmp_inv1 * val_J1, Δx * cmp_inv2 * val_J2)

        Eh += Δt/2 *  (M1inv .* (SVector(Py', Px') .* (C' *  SVector{length(Bh)}(M2 * Bh)) - Jh))
        En3[i] =  1/(2*X.B.N*el[1]^2) * 1/(Y.B.N*el[2]^2) * sqrt(dot(Eh, M1_old .* Eh))
    end

    xy1_1, colmat1_1 = SplineFEEC.col_mat_mixed_x(V1[1], 10)
    xy1_2, colmat1_2 = SplineFEEC.col_mat_mixed_y(V1[2], 10)
    xy2, colmat2 = SplineFEEC.col_mat_d(V2, 10)

    val_B = (i -> B₂.(T, xy2[i][1], xy2[i][2])).(1:length(xy2))
    val_E1 = (i -> E₂.(T, xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
    val_E2 = (i -> E₂.(T, xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2))

    @show maximum(abs.(colmat1_1 * Py * Eh[1] - val_E1))
    @show maximum(abs.(colmat1_2 * Px * Eh[2] - val_E2))
    @show maximum(abs.(colmat2 * Bh - val_B))
    @show maximum(abs.(En3 - Enex))
    return it, En1, En2, En3, Enex
end

function test_cold_plasma_xwave_2d(k::NTuple{2,Int}, N::NTuple{2,Int}, el::NTuple{2,Int}, it, Δt)

    #todo fill in for energy conservation
    w_c = 2
    w_p = 1
    w = 0.5

    R = 1 - w_p^2/(w*(w-w_c))
    L = 1 - w_p^2/(w*(w+w_c))
    S = 1/2 * (R + L)

    #Langmuir test
    k2 = 0
    k1 = w * sqrt(R*L/S)
    E(t, x, y) = real.(SVector(1, -im * (R+L)/(R-L)) .* exp(-im*(w*t -k1*x -k2*y)))
    B(t, x, y) = real(-k1/w * im * (R+L)/(R-L) * exp(-im*(w*t -k1*x -k2*y)) )
    J(t, x, y) = real.( w_p^2 .* SVector(im * 1/(w+w_c), 1/(w-w_c) * (R+L)/(R-L)) .* exp(-im*(w*t -k1*x -k2*y)))

    E0(x,y) = E(0, x, y)
    B0(x,y) = B(0, x, y)
    J0(x,y) = J(0, x, y)

    ω_c(x,y) = w_c
    ω_p(x,y) = w_p

    domain = (0..2π/k1) × (0..2π)
    T = Δt * it
    Δx = (domain.domains[1].right - domain.domains[1].left)/el[1]
    Δy = (domain.domains[2].right - domain.domains[2].left)/el[2]

    # Periodic Single Patch
    X,Y = PeriodicBSplineTensorProductBasis(domain, k, N).B
    V0 = X ⊗ Y
    V1 = SVector(X' ⊗ Y, X ⊗ Y')
    V2 = X' ⊗ Y'

    M1 = mass_matrix.(V1) #Vector representing the diagonal of the mass blockmatrix
    M2 = mass_matrix(V2)
    M1inv = inv.(M1)

    C = disc_curl(V0)
    Cc = disc_grad(V2).D[1];
    Cc2 = disc_grad(V1[2]).D[1];

    Eh = SplineFEEC.L2_proj(E0, V1)
    Bh = SplineFEEC.L2_proj(B0, V2)
    Jh = SplineFEEC.L2_proj(J0, V1)

    (xy1_1, colmat1_1), (xy1_2, colmat1_2) = SplineFEEC.col_mat.(V1, 50)
    xy2, colmat2 = SplineFEEC.col_mat(V2, 50);

    t = 0;
    #keep track of energy
    En_E = []
    En_B = []
    En_J = []
    En = []
    Enex_E = []
    Enex_J = []
    Enex_B = []
    Enex = [];

    M_p = w_p .* mass_matrix.(V1)
    M_c = w_c * mass_matrix(X', X ) ⊗ mass_matrix(Y,  Y');

    A1 = [2/Δt .* M1[1]  zero(M1[2]) zero(M1[1]);
    zero(M1[1]) 2/Δt .* M1[2]  -1/2*Cc'*M2;
    zero(M1[1])  1/2*Cc2  2/Δt .* I ]

    A2 = [2/Δt .*M1[1] zero(M1[2]) zero(M1[1]);
    zero(M1[1]) 2/Δt .* M1[2]  1/2*Cc'*M2;
    zero(M1[1]) -1/2*Cc2  2/Δt .* I ];

    B11 = [1/Δt .* M1[1] zero(M1[2]) 1/2 .* M1[1] zero(M1[2]);
            zero(M1[1])  1/Δt .* M1[2] zero(M1[2]) 1/2 .* M1[2];
            -M1[1] * w_p^2/2 zero(M1[2]) 1/Δt*M1[1]  w_c/2*M_c;
            zero(M1[1]) -M1[2]*w_p^2/2  -w_c/2*M_c' 1/Δt*M1[2]]

    B2 = [1/Δt .* M1[1] zero(M1[2]) -1/2 .* M1[1] zero(M1[2]);
            zero(M1[1])  1/Δt .* M1[2] zero(M1[2]) -1/2 .* M1[2];
            M1[1] * w_p^2/2 zero(M1[2]) 1/Δt*M1[1]  -w_c/2*M_c;
            zero(M1[1]) M1[2]*w_p^2/2  w_c/2*M_c' 1/Δt*M1[2]];

    A1inv = inv(A1)
    B1inv = inv(B11);

    Eh1 = Eh[1]
    Eh2 = Eh[2]
    Jh1 = Jh[1]
    Jh2 = Jh[2]
    Bh = Bh

    U1 = [Eh1; Eh2; Bh]
    U2 = [Eh1; Eh2; Jh1; Jh2]
    for i in 1:it
            U1 = [Eh1; Eh2; Bh]
            U1 = A1inv * (A2 * U1)

            Eh1 = U1[1:length(Eh1)]
            Eh2 = U1[length(Eh1)+1:length(Eh1)+length(Eh2)]
            Bh = U1[length(Eh1)+length(Eh2)+1:end]

            U2 = [Eh1; Eh2; Jh1; Jh2]
            U2 = B1inv * (B2 * U2)

            Eh1 = U2[1:length(Eh1)]
            Eh2 = U2[length(Eh1)+1:length(Eh1)+length(Eh2)]
            Jh1 = U2[length(Eh1)+length(Eh2)+1:length(Eh1)+length(Eh2)+length(Jh1)]
            Jh2 = U2[length(Eh1)+length(Eh2)+length(Jh1)+1:end]

            U1 = [Eh1; Eh2; Bh]
            U1 = A1inv * (A2 * U1)

            Eh1 = U1[1:length(Eh1)]
            Eh2 = U1[length(Eh1)+1:length(Eh1)+length(Eh2)]
            Bh = U1[length(Eh1)+length(Eh2)+1:end]

            t += Δt
            push!(En_E, (2π/k1)/(2*X.N) * 2π/(2*Y.N) * sqrt.(Eh1'*M1[1]*Eh1 + Eh2'*M1[2]*Eh2))
            push!(En_J, (2π/k1)/(2*X.N) * 2π/(2*Y.N) * sqrt.(Jh1'*M1[1]*Jh1 + Jh2'*M1[2]*Jh2))
            push!(En_B, (2π/k1)/(2*X.N) * 2π/(2*Y.N) * sqrt.(Bh'*M2*Bh))
            push!(En, 1/2*En_E[end]^2 + 1/2*En_J[end]^2 + 1/2*En_B[end]^2 )

            push!(Enex_E, sqrt(hcubature(x -> dot(E(t, x[1], x[2]), E(t, x[1], x[2])), (0, 0), (2π/k1,2π))[1]))
            push!(Enex_J, sqrt(hcubature(x -> dot(J(t, x[1], x[2]), J(t, x[1], x[2])), (0, 0), (2π/k1,2π))[1]))
            push!(Enex_B, sqrt(hcubature(x -> dot(B(t, x[1], x[2]), B(t, x[1], x[2])), (0, 0), (2π/k1,2π))[1]))
            push!(Enex,  1/2*Enex_E[end]^2 + 1/2*Enex_B[end]^2 + 1/2*Enex_J[end]^2  )
    end

    Eh = SVector(Eh1, Eh2)
    Jh = SVector(Jh1, Jh2)
    Bh = Bh;

    t_n = t
    val_B = (i -> B.(t_n, xy2[i][1], xy2[i][2])).(1:length(xy2))
    val_E1 = (i -> E.(t_n, xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
    val_E2 = (i -> E.(t_n, xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2))
    val_J1 = (i -> J.(t_n, xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
    val_J2 = (i -> J.(t_n, xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2))

    err_per_E = max(maximum(abs.(colmat1_1 * Eh[1] - val_E1)), maximum(abs.(colmat1_2 * Eh[2] - val_E2)))
    err_per_J = max(maximum(abs.(colmat1_1 * Jh[1] - val_J1)), maximum(abs.(colmat1_2 * Jh[2] - val_J2)))
    err_per_B = maximum(abs.(colmat2 * Bh - val_B))
    En_per = En
    Enex_per = Enex

    #Periodic Multipatch
    X,Y = SplineFEEC.PeriodicMultipatchTensorProductBSpline(domain, k, N, el).B
            V0 = X ⊗ Y
            V1 = SVector(X' ⊗ Y, X ⊗ Y')
            V2 = X' ⊗ Y'

            M1x = mass_matrix_d(X')
            M1y = mass_matrix(Y)
            M1_1 = M1x ⊗ M1y

            M2x = mass_matrix(X)
            M2y = mass_matrix_d(Y')
            M1_2 = M2x ⊗ M2y

            M1 = SVector(M1_1, M1_2)
            M1inv = inv.(M1)

            M2 = mass_matrix_d(V2)

            Cc = SplineFEEC.disc_diff(curl, V0).D[1]
            Cc2 = disc_diff(curl, V0).D[1]

            Eh = SVector( M1_1\SplineFEEC.L2_prod_mixed_x((x,y) -> E0(x,y)[1], X' ⊗ Y),
                              M1_2\SplineFEEC.L2_prod_mixed_y((x,y) -> E0(x,y)[2], X ⊗ Y'))
            Jh = SVector( M1_1\SplineFEEC.L2_prod_mixed_x((x,y) -> J0(x,y)[1], X' ⊗ Y),
                              M1_2\SplineFEEC.L2_prod_mixed_y((x,y) -> J0(x,y)[2], X ⊗ Y'))
            Bh = SplineFEEC.L2_proj_d(B0, V2)

            xy0, colmat0 = col_mat(V0, 30)
            xy1_1, colmat1_1 = SplineFEEC.col_mat_mixed_x(V1[1], 30)
            xy1_2, colmat1_2 = SplineFEEC.col_mat_mixed_y(V1[2], 30)
            xy2, colmat2 = col_mat_d(V2, 30)

            t = 0;
            En_E = []
            En_B = []
            En_J = []
            En = []
            #Enex_E = []
            #Enex_J = []
            #Enex_B = []
            #Enex = [];

            M_p = w_p^2 .* M1
            M_c = w_c .* SplineFEEC.mixed_mass_matrix_x(V1[1], V1[2])

            A1 = [2/Δt .* M1[1]  zero(M1[2]) zero(M1[1]);
                    zero(M1[1]) 2/Δt .* M1[2]  -1/2*Cc'*M2;
                    zero(M1[1])  1/2*Cc2  2/Δt .* I ]

            A2 = [2/Δt .*M1[1] zero(M1[2]) zero(M1[1]);
                    zero(M1[1]) 2/Δt .* M1[2]  1/2*Cc'*M2;
                    zero(M1[1]) -1/2*Cc2  2/Δt .* I ];

            Bm1 = [1/Δt .* M1[1] zero(M1[2]) 1/2 .* M1[1] zero(M1[2]);
                    zero(M1[1])  1/Δt .* M1[2] zero(M1[2]) 1/2 .* M1[2];
                    -M1[1] * w_p^2/2 zero(M1[2]) 1/Δt*M1[1]  w_c/2*M_c;
                    zero(M1[1]) -M1[2]*w_p^2/2  -w_c/2*M_c' 1/Δt*M1[2]]

            Bm2 = [1/Δt .* M1[1] zero(M1[2]) -1/2 .* M1[1] zero(M1[2]);
                    zero(M1[1])  1/Δt .* M1[2] zero(M1[2]) -1/2 .* M1[2];
                    M1[1] * w_p^2/2 zero(M1[2]) 1/Δt*M1[1]  -w_c/2*M_c;
                    zero(M1[1]) M1[2]*w_p^2/2  w_c/2*M_c' 1/Δt*M1[2]];

            A1inv = inv(A1)
            Bm1inv = inv(Bm1);

            Eh1 = Eh[1]
            Eh2 = Eh[2]
            Jh1 = Jh[1]
            Jh2 = Jh[2]
            Bh = Bh

            U1 = [Eh1; Eh2; Bh]
            U2 = [Eh1; Eh2; Jh1; Jh2]
            for i in 1:it
                    U1 = [Eh1; Eh2; Bh]
                    U1 = A1inv*(A2 * U1)

                    Eh1 = U1[1:length(Eh1)]
                    Eh2 = U1[length(Eh1)+1:length(Eh1)+length(Eh2)]
                    Bh = U1[length(Eh1)+length(Eh2)+1:end]

                    U2 = [Eh1; Eh2; Jh1; Jh2]
                    U2 = Bm1inv*(Bm2 * U2)

                    Eh1 = U2[1:length(Eh1)]
                    Eh2 = U2[length(Eh1)+1:length(Eh1)+length(Eh2)]
                    Jh1 = U2[length(Eh1)+length(Eh2)+1:length(Eh1)+length(Eh2)+length(Jh1)]
                    Jh2 = U2[length(Eh1)+length(Eh2)+length(Jh1)+1:end]

                    U1 = [Eh1; Eh2; Bh]
                    U1 = A1inv*(A2 * U1)

                    Eh1 = U1[1:length(Eh1)]
                    Eh2 = U1[length(Eh1)+1:length(Eh1)+length(Eh2)]
                    Bh = U1[length(Eh1)+length(Eh2)+1:end]

                    t += Δt
                    push!(En_E, (2π/k1)/(2*X.B.N*el[1]) * 2π/(2*Y.B.N*el[2]) * sqrt.(Eh1'*M1[1]*Eh1 + Eh2'*M1[2]*Eh2))
                    push!(En_J, (2π/k1)/(2*X.B.N*el[1]) * 2π/(2*Y.B.N*el[2]) * sqrt.(Jh1'*M1[1]*Jh1 + Jh2'*M1[2]*Jh2))
                    push!(En_B, (2π/k1)/(2*X.B.N*el[1]) * 2π/(2*Y.B.N*el[2]) * sqrt.(Bh'*M2*Bh))
                    push!(En, 1/2*En_E[end]^2 + 1/2*En_J[end]^2 + 1/2*En_B[end]^2 )

                    #push!(Enex_E, sqrt(hcubature(x -> dot(E(tt, x[1], x[2]), E(tt, x[1], x[2])), (0, 0), (2π/k1,2π))[1]))
                    #push!(Enex_J, sqrt(hcubature(x -> dot(J(tt, x[1], x[2]), J(tt, x[1], x[2])), (0, 0), (2π/k1,2π))[1]))
                    #push!(Enex_B, sqrt(hcubature(x -> dot(B(tt, x[1], x[2]), B(tt, x[1], x[2])), (0, 0), (2π/k1,2π))[1]))
                    #push!(Enex,  1/2*Enex_E[end]^2 + 1/2*Enex_B[end]^2 + 1/2*Enex_J[end]^2  )
            end

            Eh = SVector(Eh1, Eh2)
            Jh = SVector(Jh1, Jh2)
            Bh = Bh;

            t_n = t
            val_B = (i -> B.(t_n, xy2[i][1], xy2[i][2])).(1:length(xy2))
            val_E1 = (i -> E.(t_n, xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
            val_E2 = (i -> E.(t_n, xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2))
            val_J1 = (i -> J.(t_n, xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
            val_J2 = (i -> J.(t_n, xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2))

            En_mp_per = En
            err_mp_E = max(maximum(abs.(colmat1_1 * Eh[1] - val_E1)), maximum(abs.(colmat1_2 * Eh[2] - val_E2)))
            err_mp_J = max(maximum(abs.(colmat1_1 * Jh[1] - val_J1)), maximum(abs.(colmat1_2 * Jh[2] - val_J2)))
            err_mp_B = maximum(abs.(colmat2 * Bh - val_B))

            #Cong Multipatch
            X,Y = SplineFEEC.PeriodicMultipatchTensorProductBSpline(domain, k, N, el).B
            V0 = X ⊗ Y
            V1 = SVector(X' ⊗ Y, X ⊗ Y')
            V2 = X' ⊗ Y'

            M1 = mass_matrix_d.(V1)
            M1inv = SVector(1/(Δy)^2, 1/(Δx)^2) .* inv.(M1)
            M1 = SVector((Δy)^2, (Δx)^2) .* M1
            M2 = mass_matrix_d(V2)

            #C = SplineFEEC.disc_diff(curl, V0)

            P = SplineFEEC.conforming_averaging_projection_matrix(V0)
            Px = SplineFEEC.conforming_averaging_projection_matrix_x(V0)
            Py = SplineFEEC.conforming_averaging_projection_matrix_y(V0)

            Cc = SplineFEEC.disc_diff(curl, V0).D[1] * Px
            Cc2 = disc_diff(curl, V0).D[1] * Px

            Eh = SVector(1/Δy, 1/Δx) .* L2_proj_d(E0, V1)
            Jh = SVector(1/Δy, 1/Δx) .* L2_proj_d(J0, V1)
            #SVector( M1_1\SplineFEEC.L2_prod_mixed_x((x,y) -> E0(x,y)[1], X' ⊗ Y),
            #                  M1_2\SplineFEEC.L2_prod_mixed_y((x,y) -> E0(x,y)[2], X ⊗ Y'))
            #Jh = SVector( M1_1\SplineFEEC.L2_prod_mixed_x((x,y) -> J0(x,y)[1], X' ⊗ Y),
            #                  M1_2\SplineFEEC.L2_prod_mixed_y((x,y) -> J0(x,y)[2], X ⊗ Y'))
            Bh = SplineFEEC.L2_proj_d(B0, V2)

            xy0, colmat0 = col_mat(V0, 10)
            xy1_1, colmat1_1 = SplineFEEC.col_mat_mixed_x(V1[1], 10)
            xy1_2, colmat1_2 = SplineFEEC.col_mat_mixed_y(V1[2], 10)
            xy2, colmat2 = col_mat_d(V2, 10)

            t = 0;
            En_E = []
            En_B = []
            En_J = []
            En = []
            #Enex_E = []
            #Enex_J = []
            #Enex_B = []
            #Enex = [];

            M_p = w_p^2 .* M1
            M_c = w_c .* SplineFEEC.mixed_mass_matrix_x_d(V1[1], V1[2]);

            A1 = [2/Δt .* M1[1]  zero(M1[2]) zero(Cc'*M2);
                    zero(M1[1]) 2/Δt .* M1[2]  -1/2*Cc'*M2;
                    zero(Cc2)  1/2*Cc2  2/Δt .* I ]

            A2 = [2/Δt .*M1[1] zero(M1[2]) zero(Cc'*M2);
                    zero(M1[1]) 2/Δt .* M1[2]  1/2*Cc'*M2;
                    zero(Cc2) -1/2*Cc2  2/Δt .* I ];

            Bm1 = [1/Δt .* M1[1] zero(M1[2]) 1/2 .* M1[1] zero(M1[2]);
                    zero(M1[1])  1/Δt .* M1[2] zero(M1[1]) 1/2 .* M1[2];
                    -M1[1] * w_p^2/2 zero(M1[2]) 1/Δt*M1[1]  w_c/2*M_c;
                    zero(M1[1]) -M1[2]*w_p^2/2  -w_c/2*M_c' 1/Δt*M1[2]]

            Bm2 = [1/Δt .* M1[1] zero(M1[2]) -1/2 .* M1[1] zero(M1[2]);
                    zero(M1[1])  1/Δt .* M1[2] zero(M1[1]) -1/2 .* M1[2];
                    M1[1] * w_p^2/2 zero(M1[2]) 1/Δt*M1[1]  -w_c/2*M_c;
                    zero(M1[1]) M1[2]*w_p^2/2  w_c/2*M_c' 1/Δt*M1[2]];

            A1inv = inv(A1)
            Bm1inv = inv(Bm1);

            Eh1 = Eh[1]
            Eh2 = Eh[2]
            Jh1 = Jh[1]
            Jh2 = Jh[2]
            Bh = Bh

            U1 = [Eh1; Eh2; Bh]
            U2 = [Eh1; Eh2; Jh1; Jh2]
            for i in 1:it
                    U1 = [Eh1; Eh2; Bh]
                    U1 = A1inv*(A2 * U1)

                    Eh1 = U1[1:length(Eh1)]
                    Eh2 = U1[length(Eh1)+1:length(Eh1)+length(Eh2)]
                    Bh = U1[length(Eh1)+length(Eh2)+1:end]

                    U2 = [Eh1; Eh2; Jh1; Jh2]
                    U2 = Bm1inv*(Bm2 * U2)

                    Eh1 = U2[1:length(Eh1)]
                    Eh2 = U2[length(Eh1)+1:length(Eh1)+length(Eh2)]
                    Jh1 = U2[length(Eh1)+length(Eh2)+1:length(Eh1)+length(Eh2)+length(Jh1)]
                    Jh2 = U2[length(Eh1)+length(Eh2)+length(Jh1)+1:end]

                    U1 = [Eh1; Eh2; Bh]
                    U1 = A1inv*(A2 * U1)

                    Eh1 = U1[1:length(Eh1)]
                    Eh2 = U1[length(Eh1)+1:length(Eh1)+length(Eh2)]
                    Bh = U1[length(Eh1)+length(Eh2)+1:end]

                    t += Δt
                    push!(En_E, (2π/k1)/(2*X.B.N*el[1]) * 2π/(2*Y.B.N*el[2]) * sqrt.(Eh1'*M1[1]*Eh1 + Eh2'*M1[2]*Eh2))
                    push!(En_J, (2π/k1)/(2*X.B.N*el[1]) * 2π/(2*Y.B.N*el[2]) * sqrt.(Jh1'*M1[1]*Jh1 + Jh2'*M1[2]*Jh2))
                    push!(En_B, (2π/k1)/(2*X.B.N*el[1]) * 2π/(2*Y.B.N*el[2]) * sqrt.(Bh'*M2*Bh))
                    push!(En, 1/2*En_E[end]^2 + 1/2*En_J[end]^2 + 1/2*En_B[end]^2 )

                   # push!(Enex_E, sqrt(hcubature(x -> dot(E(tt, x[1], x[2]), E(tt, x[1], x[2])), (0, 0), (2π/k1,2π))[1]))
                   # push!(Enex_J, sqrt(hcubature(x -> dot(J(tt, x[1], x[2]), J(tt, x[1], x[2])), (0, 0), (2π/k1,2π))[1]))
                   # push!(Enex_B, sqrt(hcubature(x -> dot(B(tt, x[1], x[2]), B(tt, x[1], x[2])), (0, 0), (2π/k1,2π))[1]))
                   # push!(Enex,  1/2*Enex_E[end]^2 + 1/2*Enex_B[end]^2 + 1/2*Enex_J[end]^2  )
            end

            Eh = SVector(Eh1, Eh2)
            Jh = SVector(Jh1, Jh2)
            Bh = Bh;

            t_n = t
            val_B = (i -> B.(t_n, xy2[i][1], xy2[i][2])).(1:length(xy2))
            val_E1 = (i -> E.(t_n, xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
            val_E2 = (i -> E.(t_n, xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2))
            val_J1 = (i -> J.(t_n, xy1_1[i][1], xy1_1[i][2])[1]).(1:length(xy1_1))
            val_J2 = (i -> J.(t_n, xy1_2[i][1], xy1_2[i][2])[2]).(1:length(xy1_2))

            En_conga_per = En
            err_conga_E = max(maximum(abs.(colmat1_1 * Py * Eh[1] - val_E1)), maximum(abs.(colmat1_2 * Px * Eh[2] - val_E2)))
            err_conga_J = max(maximum(abs.(colmat1_1 * Py * Jh[1] - val_J1)), maximum(abs.(colmat1_2 * Px * Jh[2] - val_J2)))
            err_conga_B = maximum(abs.(colmat2 * Bh - val_B))

    return it, En_per, En_mp_per, En_conga_per, Enex_per
end


module AlphaShapes
    import BlackBoxOptim.bboptimize, BlackBoxOptim.best_candidate
    import Distances.pairwise, Distances.Euclidean
    import LinearAlgebra.det, LinearAlgebra.inv, LinearAlgebra.norm

    using MiniQhull

    export AlphaShape, AlphaShapeVolume

    """
        CayleyMenger

    Compute the Cayley-Menger matrix from squared
    distances dij^2.

    https://mathworld.wolfram.com/Cayley-MengerDeterminant.html

    For a 2-d triangle the following example would symbolically
    represent the matrix for points x1, x2, and x3 where dij^2
    is the square Euclidean distance between i and j
    Example
```julia-repl
julia>:([0    1      1      1
         1    0      d12^2  d13^2
         1    d21^2  0      d23^2
         1    d32^2  d32^2    0])
:([0 1 1 1; 1 0 d12 ^ 2 d13 ^ 2; 1 d21 ^ 2 0 d23 ^ 2; 1 d32 ^ 2 d32 ^ 2 0])
```
    """
    function CayleyMenger(points::Array{Float64,2})::Array{Float64}
        d = pairwise(Euclidean(),points')
        n = size(points,1)
        CM = ones(n+1,n+1)
        CM[2:end,2:end] = d.^2.0
        CM[end,end] = 0.0
        CM[1,1] = 0.0
        return CM
    end

    """
        SimplexVolume

    Calculate the volume of a simplex using the
    Cayley Menger matrix

    https://westy31.home.xs4all.nl/Circumsphere/ncircumsphere.htm
    """
    function SimplexVolume(points::Array{Float64,2})::Float64

        CM = CayleyMenger(points)
        n = size(CM,1)-2
        return real(sqrt(Complex(((-1.0)^(n+1))/((2.0^n)*factorial(n)^2.0)*det(CM))))
    end

    """
        SimplexCircumSphere

    Find the centre and radius of the circumsphere of a simplex
    https://westy31.home.xs4all.nl/Circumsphere/ncircumsphere.htm
    """
    function SimplexCircumSphere(points::Array{Float64,2})::Tuple{Array{Float64,1},Float64}
        CM = CayleyMenger(points)
        cminv = inv(CM)
        R = real(sqrt(Complex(cminv[1,1]/(-2.0))))
        # convert for barycentric to cartesian coordinates
        λ = cminv[1,2:end]
        c = (λ'*points)[1,:]
        return c,R
    end

    """
        SimplexCircumSphere

    Find the centre and radius of the circumsphere of a simplex
    https://westy31.home.xs4all.nl/Circumsphere/ncircumsphere.htm
    """
    function SimplexCircumRadiusSquared(points::Array{Float64,2})::Float64
        CM = CayleyMenger(points)
        cminv = inv(CM)
        return cminv[1,1]/(-2.0)
    end

    function VertexInTriangle(x::Array{Float64,1},T::Array{Float64,2})::Bool
        if x == T[1,:] || x == T[2,:] || x == T[3,:]
            return true
        else
            return false
        end
    end
    function VertexInTriangulation(x::Array{Float64,1},T::Array{Float64,3})::Bool
        for i in 1:size(T,1)
            if VertexInTriangle(x,T[i,:,:])
                return true
            end
        end
        return false
    end
    function AllPointsInAlphaShape(X::Array{Float64,2},A::Array{Float64,3})::Bool
        for i in 1:size(X,1)
            if VertexInTriangulation(X[i,:],A) == false
                return false
            end
        end
        return true
    end

    """
        AlphaShapeVolume(A::Array{Float64,3})

    return the sum of volumes of all simplices in the alpha shapes A
    """
    function AlphaShapeVolume(A::Array{Float64,3})::Float64
        area = 0.0
        for t in 1:size(A,1)
            area += SimplexVolume(A[t,:,:])
        end
        return area
    end

    """
        GetDelaunayTriangulation(points::Array{Float64,2})::Array{Float64,3}

    Wrap MiniQhull.jl's delaunay to get a delaunay triangualation in any
    dimension
    """
    function GetDelaunayTriangulation(points::Array{Float64,2},indices=false)
        tess = delaunay(permutedims(points,(2,1)))
        Triangles = zeros(size(tess,2),size(tess,1),size(tess,1)-1)
        for i in 1:size(tess,2)
            for j in 1:size(tess,1)
                Triangles[i,j,:] = points[tess[j,i],:]
            end
        end
	if indices
	    return Triangles,tess
	end
        return Triangles
    end

    """
        FindAlpha(X::Array{Float64,2};
            search::Tuple{Float64,Float64}=(0.0, 10.0),
            MaxSteps::Int=100)::Float64

    Use BlackBocOptim to find the optimal alpha value (volume minimiser which cotains all
    points from the input).
    """
    function FindAlpha(X::Array{Float64,2};
        search::Tuple{Float64,Float64}=(0.0, 10.0),
        MaxSteps::Int=100)::Float64
        objective = function(α)
            α = α[1]
            A = AlphaShape(X,α=α);
            t = AllPointsInAlphaShape(X,A)
            o =  AlphaShapeVolume(A)
            # minimise the area but if not all points are
            # included then set to extreme value
            if t
                return o
            else
                return Inf
            end
        end
        res = bboptimize(objective; SearchRange = search, NumDimensions = 1,MaxSteps=MaxSteps);
        return best_candidate(res)[1]
    end

    """
        AlphaShape(X::Array{Float64,2};α::Union{Nothing,Float64}=nothing,
            search::Tuple{Float64,Float64}=(0.0, 10.0),
            MaxSteps::Int=100)::Array{Float64,3}

    Find the alpha shape corresponding to the 2D array of points
    X: [npoints,2], and the α value α.

    If α == nothing then a search over the range of values search is done
    to find the best value. The optimisation objective is the alpha shape
    area (minimise) subject to all points in X being included in the
    alpha shape triangulation.
    """
    function AlphaShape(X::Array{Float64,2};α::Union{Nothing,Float64}=nothing,
        search::Tuple{Float64,Float64}=(0.0, 10.0),
        MaxSteps::Int=100)::Array{Float64,3}
        T = GetDelaunayTriangulation(X)
        if α == nothing
            println("Finding the optimum α value...\n")
            α = FindAlpha(X;search=search,MaxSteps=MaxSteps)
        end
        α2 = α^2.0
        A = [SimplexCircumRadiusSquared(T[i,:,:]) < α2 for i in 1:size(T,1)]
        return T[A.==1,:,:]
    end

	include("utils.jl")
	include("Density.jl")
end # module AlphaShapes


# SOCP Lasso Testproblem

using COSMO, Test, LinearAlgebra, SparseArrays, Random

# This test is precision agnostic and will be run with T precision
if !@isdefined(UnitTestFloats)
    UnitTestFloats = [Float64] #if not run in full test setup, just do it for one float type
end

@testset "SOCP - Lasso" begin
  for T in UnitTestFloats
      if T != BigFloat
      # generate problem data
      rng = Random.MersenneTwister(12345)
      n = 8
      m = 50 * n
      F = rand(rng, T, m, n)

      vtrue = sprand(rng, T, n, 1, 0.1)
      noise = T(0.1) * rand(rng, T, m, 1)
      b = F * vtrue + noise
      μMax = norm(F' * b, Inf)
      μ = T(0.1) * μMax

      # define lasso problem as SOCP

      A1 = -sparse([1 zeros(T, 1, 2 * n + 1) 1 zeros(T, 1, m);
        -1 zeros(T, 1, 2 * n) 1 zeros(T, 1, m + 1);
        zeros(T, m, 1) -2 * F zeros(T, m, n + 2) diagm( 0 => ones(T, m))])

      A2 = -sparse([zeros(T, n, 1) diagm(0 => ones(T, n)) -diagm(0 => ones(T, n)) zeros(T, n, m + 2);
        zeros(T, n, 1) -diagm(0 => ones(T, n)) -diagm(0 => ones(T, n)) zeros(T, n, m + 2)])
      A3 = -sparse([zeros(T, 1, 2 * n + 1) -one(T) zeros(T, 1, m + 1);
       zeros(T, 1, 2 * n + 2) -one(T) zeros(T, 1, m);
       zeros(T, m, 2 * n + 3) -diagm( 0 => ones(T, m))])
      b1 = T[1; 1; -2 * b]
      b2 = zeros(T, 2 * n)
      b3 = zeros(T, m + 2)

      q = [one(T); zeros(T, n); μ * ones(T, n); zeros(T, m + 2)]
      P = spzeros(T, length(q), length(q))

      cs1 = COSMO.Constraint(A1 ,b1, COSMO.ZeroSet)
      cs2 = COSMO.Constraint(A2 ,b2, COSMO.Nonnegatives)
      cs3 = COSMO.Constraint(A3 ,b3, COSMO.SecondOrderCone)

      # Solve with OSSDP
      model = COSMO.Model{T}()
      assemble!(model, P, q, [cs1; cs2; cs3])
      res = COSMO.optimize!(model);

      @testset "SOCP - Lasso (T = $(T))" begin
      @test res.status == :Solved
      @test isapprox(res.obj_val, 0.4422849814458825,atol=1e-2)
      end
    end
  end
end
nothing


using Convex, FirstOrderSolvers, SCS, Test, ProximalOperators

ys = [-0.0064709 -0.22443;
     -0.22443    -1.02411]
y = Variable((2, 2))

#Solve with SCS
p = minimize(norm(vec(y-ys)), isposdef(y))
solve!(p, SCSSolver(eps=1e-8, verbose=0))

ysol  = copy(y.value)

#Solve with projection
Y = Symmetric(ys)
X = similar(Y)
prox!(X, IndPSD(), Y)

#Testing SCS and projection equal
@test X.data ≈ ysol atol=1e-8

#Solve with DR
p = minimize(norm(vec(y-ys)), isposdef(y))
solve!(p, DR(eps=1e-8, verbose=0))

@test y.value ≈ ysol atol=1e-8


@testset "FiniteDiff comparison" begin
    Random.seed!(100)
    x0 = srand(3)
    v0 = srand(3)
    q0 = rand(QuatRotation).q
    ϕ0 = srand(3)
    timestep0 = 0.01
    x1 = Dojo.next_position(x0, v0, timestep0)
    q1 = Dojo.next_orientation(q0, ϕ0, timestep0)

    # ∇v
    FD∂i∂v = FiniteDiff.finite_difference_jacobian(v0 -> Dojo.next_position(x0, v0, timestep0), v0)
    @test norm(FD∂i∂v - Dojo.linear_integrator_jacobian_velocity(timestep0), Inf) < 1.0e-6
    # ∇ϕ
    FD∂i∂ϕ = FiniteDiff.finite_difference_jacobian(ϕ0 -> Dojo.vector(Dojo.next_orientation(q0, ϕ0, timestep0)), ϕ0)
    @test norm(FD∂i∂ϕ - Dojo.rotational_integrator_jacobian_velocity(q0, ϕ0, timestep0), Inf) < 1.0e-6

    # ∇x
    FD∂i∂x = FiniteDiff.finite_difference_jacobian(x0 -> Dojo.next_position(x0, v0, timestep0), x0)
    @test norm(FD∂i∂x - Dojo.linear_integrator_jacobian_position(), Inf) < 1.0e-6
    # ∇q
    FD∂i∂q = FiniteDiff.finite_difference_jacobian(q0 ->
        Dojo.vector(Dojo.next_orientation(Quaternion(q0...), ϕ0, timestep0)),
        Dojo.vector(q0))
    @test norm(FD∂i∂q - Dojo.rotational_integrator_jacobian_orientation(q0, ϕ0, timestep0, attjac=false), Inf) < 1.0e-6

    # ∇vϕ
    vel0 = [v0; ϕ0]
    FDvel = FiniteDiff.finite_difference_jacobian(vel0 ->
        [Dojo.next_position(x0, SVector{3}(vel0[1:3]), timestep0);
        Dojo.vector(Dojo.next_orientation(q0, SVector{3}(vel0[4:6]), timestep0))], vel0)
    @test norm(FDvel - Dojo.integrator_jacobian_velocity(q0, ϕ0, timestep0), Inf) < 1.0e-6

    # ∇xq
    con0 = [x0; Dojo.vector(q0)]
    FDcon = FiniteDiff.finite_difference_jacobian(con0 ->
        [Dojo.next_position(SVector{3}(con0[1:3]), v0, timestep0);
        Dojo.vector(Dojo.next_orientation(Quaternion(con0[4:7]...), ϕ0, timestep0))], con0)
    @test norm(FDcon - Dojo.integrator_jacobian_configuration(q0, ϕ0, timestep0, attjac=false), Inf) < 1e-8
    @test norm(FDcon * cat(I(3), Dojo.LVᵀmat(q0), dims=(1,2)) - Dojo.integrator_jacobian_configuration(q0, ϕ0, timestep0, attjac=true), Inf) < 1e-8
end


using SDDiP, GLPKMathProgInterface, JuMP, SDDP, Base.Test
# using Ipopt  # uncomment for the Level Method

struct AllBlacksData
  T::Int                  # Number of time periods
  N::Int                  # Number of seats
  R::Array{Float64,2}     # R_ij = evenue from selling seat i at time j
  offer::Array{Float64,2} # offer_ij = whether an offer for seat i will come at time j
end

data = AllBlacksData(3, 2, [3 3 6; 3 3 6], [1 1 0; 1 0 1])

m=SDDPModel(stages=data.T, objective_bound=100.0, sense=:Max, solver=GLPKSolverMIP()) do sp, stage

    # Seat remaining?
    @binarystate(sp, x[i=1:data.N], x0==1, Bin)

    # Action: accept offer, or don't accept offer
    @variable(sp, accept_offer, Bin)

    @constraints(sp, begin
        # Balance on seats
        dynamics[i=1:data.N], x[i] == x0[i] - data.offer[i, stage] * accept_offer
    end)

    @stageobjective(sp, sum(data.R[i, stage] * data.offer[i, stage] * accept_offer for i=1:data.N))

    # Level method example:
    # setSDDiPsolver!(sp, method=LevelMethod(quadsolver=IpoptSolver(print_level=0)))
    # Subgradient descent example:
    setSDDiPsolver!(sp, method=SubgradientMethod())
    # setSDDiPsolver!(sp, method=KelleyMethod())

end

srand(11111)
solution = solve(m, max_iterations=8)
@test isapprox(getbound(m), 9.0)


function depthfirstsearch!(A::Matrix{Float64}, u::Int64,
			visited::Vector{Bool}, contents::Vector{Int64})
	n = size(A,1)
	for v=1:n
		adj = A[u, v]
		if (abs(adj) > 0.5) && (u != v)
			if visited[v] == false
				visited[v] = true
				push!(contents, v)
				contents = depthfirstsearch!(A, v, visited, contents)
			end
		end
	end
	return contents
end

function connectedcomponents(A::Matrix{Float64})
	n = size(A,1)
	components = 0
	visited = Array{Bool, 1}(undef, n)
	visited[:] .= false
	table = Array{Array{Int64, 1}, 1}([])
	for u=1:n
		if visited[u] == false
			visited[u] = true
			components += 1
			contents = Array{Int64, 1}([])
			push!(contents, u)
			contents = depthfirstsearch!(A, u, visited, contents)
			push!(table, contents)
		end
	end
	return components, table
end

function partitionadjacency(A::Matrix{Float64}, v::Vector{Int64})
	n = size(A,1)
	n1 = size(v,1)
	Atemp = zeros(Float64, n, n1)
	Afinal = zeros(Float64, n1, n1)
	for (i, val) ∈ enumerate(v)
		Atemp[:,i] = A[:,val]
	end
	for (i, val) ∈ enumerate(v)
		Afinal[i,:] = Atemp[val,:]
	end
	return Afinal
end

function avcompontentsize(A::Matrix{Float64})
	components, table = connectedcomponents(A)
	s = 0
	for i=1:components
		s += size(table[i],1)
	end
	return s/components
end

function clustersize(A::Matrix{Float64}, v::Vector{Int64})
	Anew = partitionadjacency(A, v)
	avs = avcompontentsize(Anew)
	return avs
end

function nodevoltages(L::Matrix{Float64}, P::Vector{Float64})
	n = size(L,1)
	Lred = L[2:end,2:end]
	C = zeros(Float64, n, n)
	C[2:end,2:end] = inv(Lred)
	return C*P
end

function edgecurrents(theta::Vector{Float64}, E::Matrix{Float64}, X::Vector{Float64})
	tmp = transpose(E)*theta
	return tmp./X
end

function fracture!(net::Graph, nodeset::Vector{Int64}, P::Vector{Float64}, X::Vector{Float64}, Finit::Vector{Float64}, info::NetworkRecorder, t::Int64, alpha::Float64, fmaxtemp::Float64, seq::Bool)

	tol = 1e-5
	simtime = t + 1

	#Check the status of P, see if has any sources or sinks. If not, halt recursion
	nodecardinality = length(nodeset)
	P1 = zeros(Float64, nodecardinality)
	sourcecounter = 0
	weighedsourcecounter = 0.0
	sinkcounter = 0
	for i=1:nodecardinality
		vi = nodeset[i]
		P1[i] = P[vi]
		if P1[i] > tol
			sourcecounter += 1
			weighedsourcecounter += P1[i]
		end
		if P1[i] < -tol
			sinkcounter += 1
		end
	end
	if (sourcecounter == 0) || (sinkcounter == 0)
		if simtime ∉ info.simulationtime
			push!(info.simulationtime, simtime)
			push!(info.liveedges, 0)
			push!(info.livesources, 0)
			push!(info.weightedlivesources, 0.0)
			push!(info.livesinks, 0)
			push!(info.totalpower, 0.0)
			if seq
				flowvec = zeros(Float64, info.liveedges[1])
				flowvec[:] .= NaN
				push!(info.flowsequence, flowvec)
			end
		end
		return info
	end

	# #Balance P1 - if there is power excess, increase loads
	# delta = sum(P1)
	# individualdelta = delta/Float64(sinkcounter)
	# for i=1:nodecardinality
	# 	if P1[i] < -tol
	# 		P1[i] -= individualdelta
	# 	end
	# end

	#Balance P1 homogeneously
	delta = sum(P1)/2.0
	individualdelta_sink = delta/Float64(sinkcounter)
	individualdelta_source = delta/Float64(sourcecounter)
	for i=1:nodecardinality
		if P1[i] < -tol
			P1[i] -= individualdelta_sink
		end
		if P1[i] > tol
			P1[i] -= individualdelta_source
		end
	end

	# # Balance P1 heterogeneously
	# delta = sum(P1)/2.0 #This is the amount of surplus given to sources (or sinks) to deal with
	# avP = 0.0
	# for i=1:nodecardinality
	# 	if P1[i] > tol
	# 		avP += P1[i]
	# 	end
	# end
	# avP /= sourcecounter
	# for i=1:nodecardinality
	# 	if P1[i] < -tol
	# 		P1[i] -=  abs(P1[i])*delta/(Float64(sinkcounter)*avP)
	# 	end
	# 	if P1[i] > tol
	# 		P1[i] -= abs(P1[i])*delta/(Float64(sourcecounter)*avP)
	# 	end
	# end
	# println("sum", "    ", sum(P1))

	#Find which edges are connected to the node-set
	edgeset = Array{Int64, 1}([])
	for vi ∈ nodeset
		v = net.nodelist[vi]
		for e ∈ v.outedges
			if (e ∉ edgeset) && (net.edgelist[e].active == true)
				push!(edgeset, e)
			end
		end
		for e ∈ v.inedges
			if (e ∉ edgeset) && (net.edgelist[e].active == true)
				push!(edgeset, e)
			end
		end
	end
	edgecardinality = length(edgeset)

	#Construct X
	X1 = zeros(Float64, edgecardinality)
	for i=1:edgecardinality
		X1[i] = X[edgeset[i]]
	end

	#Construct E1
	E1 = zeros(Float64, nodecardinality, edgecardinality)
	for i=1:nodecardinality
		vi = nodeset[i]
		v = net.nodelist[vi]
		for j=1:edgecardinality
			if edgeset[j] ∈ v.outedges
				E1[i,j] = 1.0
			end
			if edgeset[j] ∈ v.inedges
				E1[i,j] = -1.0
			end
		end
	end

	# A = adjacencymatrix(E1)
	# comt, tab = connectedcomponents(A)
	# println("com... = ", comt)
	#Solve the DC flow equations
	L = weightedlaplacianmatrix(E1, 1.0./X1)
	theta = nodevoltages(L, P1)
	F = edgecurrents(theta, E1, X1)./fmaxtemp

	#Record the number of edges in a tuple of recursion depth and number of edges
	if simtime ∈ info.simulationtime
		i = findall(y->y==simtime, info.simulationtime)
		info.liveedges[i] .+= edgecardinality
		info.livesources[i] .+= sourcecounter
		info.weightedlivesources .+= weighedsourcecounter
		info.livesinks[i] .+= sinkcounter
		info.totalpower[i] .+= sum(abs.(P1))/2.0
		if seq
			for j=1:edgecardinality
				ej = edgeset[j]
				info.flowsequence[i][ej] = abs(F[j])
			end
		end
	else
		push!(info.simulationtime, simtime)
		push!(info.liveedges, edgecardinality)
		push!(info.livesources, sourcecounter)
		push!(info.weightedlivesources, weighedsourcecounter)
		push!(info.livesinks, sinkcounter)
		push!(info.totalpower, sum(abs.(P1))/2.0)
		if seq
			flowvec = zeros(Float64, info.liveedges[1])
			flowvec[:] .= NaN
			for j=1:edgecardinality
				ej = edgeset[j]
				flowvec[ej] = abs(F[j])
			end
			push!(info.flowsequence, flowvec)
		end
	end

	#Scan through new flow vector and record any overloads
	#If no new overloads, halt recursion
	overloadcounter = 0
	survivors = Array{Int64, 1}([])
	for i=1:edgecardinality
		ei = edgeset[i]
		if abs(F[i]) > alpha #*abs(Finit[ei])
			net.edgelist[ei].active = false
			overloadcounter += 1
		else
			push!(survivors, i)
		end
	end
	if overloadcounter == 0
		return info
	end

	#Delete overloads from E1
	newedgecard = length(survivors)
	E2 = zeros(Float64, nodecardinality, newedgecard)
	for j=1:newedgecard
		E2[:,j] = E1[:,survivors[j]]
	end

	#Check to see if its broken
	Adj = adjacencymatrix(E2)
	components, table = connectedcomponents(Adj)

	#Clear any superfluous data
	E1 = nothing
	E2 = nothing
	Adj = nothing
	L = nothing
	F = nothing

	#Call fracture function on each of the components, passing down info
	for i=1:components
		chunk = table[i]
		nodesubset = zeros(Int64, length(chunk))
		for j=1:length(chunk)
			nodesubset[j] = nodeset[chunk[j]]
		end
		info = fracture!(net, nodesubset, P, X, Finit, info, simtime, alpha, fmaxtemp, seq)
	end

	return info
end

function fracture2!(net::Graph, nodeset::Vector{Int64}, P::Vector{Float64}, X::Vector{Float64}, Finit::Vector{Float64}, t::Int64, alpha::Float64, fmaxtemp::Float64)

	tol = 1e-5
	simtime = t + 1

	#Check the status of P, see if has any sources or sinks. If not, halt recursion
	nodecardinality = length(nodeset)
	P1 = zeros(Float64, nodecardinality)
	sourcecounter = 0
	weighedsourcecounter = 0.0
	sinkcounter = 0
	for i=1:nodecardinality
		vi = nodeset[i]
		P1[i] = P[vi]
		if P1[i] > tol
			sourcecounter += 1
			weighedsourcecounter += P1[i]
		end
		if P1[i] < -tol
			sinkcounter += 1
		end
	end
	if (sourcecounter == 0) || (sinkcounter == 0)
		return 0
	end

	# #Balance P1 - if there is power excess, increase loads
	# delta = sum(P1)
	# individualdelta = delta/Float64(sinkcounter)
	# for i=1:nodecardinality
	# 	if P1[i] < -tol
	# 		P1[i] -= individualdelta
	# 	end
	# end

	# #Balance P1 homogeneously
	# delta = sum(P1)/2.0
	# individualdelta_sink = delta/Float64(sinkcounter)
	# individualdelta_source = delta/Float64(sourcecounter)
	# for i=1:nodecardinality
	# 	if P1[i] < -tol
	# 		P1[i] -= individualdelta_sink
	# 	end
	# 	if P1[i] > tol
	# 		P1[i] -= individualdelta_source
	# 	end
	# end

	# Balance P1 heterogeneously
	delta = sum(P1)/2.0 #This is the amount of surplus given to sources (or sinks) to deal with
	avP = 0.0
	for i=1:nodecardinality
		if P1[i] > tol
			avP += P1[i]
		end
	end
	avP /= sourcecounter
	for i=1:nodecardinality
		if P1[i] < -tol
			P1[i] -=  abs(P1[i])*delta/(Float64(sinkcounter)*avP)
		end
		if P1[i] > tol
			P1[i] -= abs(P1[i])*delta/(Float64(sourcecounter)*avP)
		end
	end
	# println("sum", "    ", sum(P1))

	#Find which edges are connected to the node-set
	edgeset = Array{Int64, 1}([])
	for vi ∈ nodeset
		v = net.nodelist[vi]
		for e ∈ v.outedges
			if (e ∉ edgeset) && (net.edgelist[e].active == true)
				push!(edgeset, e)
			end
		end
		for e ∈ v.inedges
			if (e ∉ edgeset) && (net.edgelist[e].active == true)
				push!(edgeset, e)
			end
		end
	end
	edgecardinality = length(edgeset)

	#Construct X
	X1 = zeros(Float64, edgecardinality)
	for i=1:edgecardinality
		X1[i] = X[edgeset[i]]
	end

	#Construct E1
	E1 = zeros(Float64, nodecardinality, edgecardinality)
	for i=1:nodecardinality
		vi = nodeset[i]
		v = net.nodelist[vi]
		for j=1:edgecardinality
			if edgeset[j] ∈ v.outedges
				E1[i,j] = 1.0
			end
			if edgeset[j] ∈ v.inedges
				E1[i,j] = -1.0
			end
		end
	end

	#Solve the DC flow equations
	L = weightedlaplacianmatrix(E1, 1.0./X1)
	theta = nodevoltages(L, P1)
	F = edgecurrents(theta, E1, X1)./fmaxtemp

	#Scan through new flow vector and record any overloads
	overloadcounter = 0
	survivors = Array{Int64, 1}([])
	for i=1:edgecardinality
		ei = edgeset[i]
		if abs(F[i]) > alpha #*abs(Finit[ei])
			net.edgelist[ei].active = false
			overloadcounter += 1
		else
			push!(survivors, i)
		end
	end

	#If no new overloads, halt recursion
	if overloadcounter == 0
		return edgecardinality
	end

	#Delete overloads from E1
	newedgecard = length(survivors)
	E2 = zeros(Float64, nodecardinality, newedgecard)
	for j=1:newedgecard
		E2[:,j] = E1[:,survivors[j]]
	end

	#Check to see if its broken
	Adj = adjacencymatrix(E2)
	components, table = connectedcomponents(Adj)

	#Clear any superfluous data
	E1 = nothing
	E2 = nothing
	Adj = nothing
	L = nothing
	F = nothing

	#Call fracture function on each of the components, passing down info
	descendent_survivingedges = 0
	for i=1:components
		chunk = table[i]
		nodesubset = zeros(Int64, length(chunk))
		for j=1:length(chunk)
			nodesubset[j] = nodeset[chunk[j]]
		end
		survivingedges = fracture2!(net, nodesubset, P, X, Finit, simtime, alpha, fmaxtemp)
		descendent_survivingedges += survivingedges
	end

	return descendent_survivingedges
end

function fracturerand!(net::Graph, nodeset::Vector{Int64}, P::Vector{Float64}, X::Vector{Float64}, Finit::Vector{Float64}, capnoise::Vector{Float64}, t::Int64, alpha::Float64, fmaxtemp::Float64)

	tol = 1e-5
	simtime = t + 1

	#Check the status of P, see if has any sources or sinks. If not, halt recursion
	nodecardinality = length(nodeset)
	P1 = zeros(Float64, nodecardinality)
	sourcecounter = 0
	weighedsourcecounter = 0.0
	sinkcounter = 0
	for i=1:nodecardinality
		vi = nodeset[i]
		P1[i] = P[vi]
		if P1[i] > tol
			sourcecounter += 1
			weighedsourcecounter += P1[i]
		end
		if P1[i] < -tol
			sinkcounter += 1
		end
	end
	if (sourcecounter == 0) || (sinkcounter == 0)
		return 0
	end

	#Balance P1 homogeneously
	delta = sum(P1)/2.0
	individualdelta_sink = delta/Float64(sinkcounter)
	individualdelta_source = delta/Float64(sourcecounter)
	for i=1:nodecardinality
		if P1[i] < -tol
			P1[i] -= individualdelta_sink
		end
		if P1[i] > tol
			P1[i] -= individualdelta_source
		end
	end

	#Find which edges are connected to the node-set
	edgeset = Array{Int64, 1}([])
	for vi ∈ nodeset
		v = net.nodelist[vi]
		for e ∈ v.outedges
			if (e ∉ edgeset) && (net.edgelist[e].active == true)
				push!(edgeset, e)
			end
		end
		for e ∈ v.inedges
			if (e ∉ edgeset) && (net.edgelist[e].active == true)
				push!(edgeset, e)
			end
		end
	end
	edgecardinality = length(edgeset)

	#Construct X
	X1 = zeros(Float64, edgecardinality)
	for i=1:edgecardinality
		X1[i] = X[edgeset[i]]
	end

	#Construct E1
	E1 = zeros(Float64, nodecardinality, edgecardinality)
	for i=1:nodecardinality
		vi = nodeset[i]
		v = net.nodelist[vi]
		for j=1:edgecardinality
			if edgeset[j] ∈ v.outedges
				E1[i,j] = 1.0
			end
			if edgeset[j] ∈ v.inedges
				E1[i,j] = -1.0
			end
		end
	end

	#Solve the DC flow equations
	L = weightedlaplacianmatrix(E1, 1.0./X1)
	theta = nodevoltages(L, P1)
	F = edgecurrents(theta, E1, X1)./fmaxtemp

	#Scan through new flow vector and record any overloads
	overloadcounter = 0
	survivors = Array{Int64, 1}([])
	for i=1:edgecardinality
		ei = edgeset[i]
		if abs(F[i]) > alpha*(1.0 + capnoise[ei])
			net.edgelist[ei].active = false
			overloadcounter += 1
		else
			push!(survivors, i)
		end
	end

	#If no new overloads, halt recursion
	if overloadcounter == 0
		return edgecardinality
	end

	#Delete overloads from E1
	newedgecard = length(survivors)
	E2 = zeros(Float64, nodecardinality, newedgecard)
	for j=1:newedgecard
		E2[:,j] = E1[:,survivors[j]]
	end

	#Check to see if its broken
	Adj = adjacencymatrix(E2)
	components, table = connectedcomponents(Adj)

	#Clear any superfluous data
	E1 = nothing
	E2 = nothing
	Adj = nothing
	L = nothing
	F = nothing

	#Call fracture function on each of the components, passing down info
	descendent_survivingedges = 0
	for i=1:components
		chunk = table[i]
		nodesubset = zeros(Int64, length(chunk))
		for j=1:length(chunk)
			nodesubset[j] = nodeset[chunk[j]]
		end
		survivingedges = fracture2!(net, nodesubset, P, X, Finit, simtime, alpha, fmaxtemp)
		descendent_survivingedges += survivingedges
	end

	return descendent_survivingedges
end

function fracture3!(net::Graph, nodeset::Vector{Int64}, P::Vector{Float64}, X::Vector{Float64}, Finit::Vector{Float64}, t::Int64, alpha::Float64, fmaxtemp::Float64)

	tol = 1e-5
	simtime = t + 1

	#Check the status of P, see if has any sources or sinks. If not, halt recursion
	nodecardinality = length(nodeset)
	P1 = zeros(Float64, nodecardinality)
	sourcecounter = 0
	weighedsourcecounter = 0.0
	sinkcounter = 0
	for i=1:nodecardinality
		vi = nodeset[i]
		P1[i] = P[vi]
		if P1[i] > tol
			sourcecounter += 1
			weighedsourcecounter += P1[i]
		end
		if P1[i] < -tol
			sinkcounter += 1
		end
	end
	if (sourcecounter == 0) || (sinkcounter == 0)
		return 1
	end

	#Balance P1 homogeneously
	delta = sum(P1)/2.0
	individualdelta_sink = delta/Float64(sinkcounter)
	individualdelta_source = delta/Float64(sourcecounter)
	for i=1:nodecardinality
		if P1[i] < -tol
			P1[i] -= individualdelta_sink
		end
		if P1[i] > tol
			P1[i] -= individualdelta_source
		end
	end

	#Find which edges are connected to the node-set
	edgeset = Array{Int64, 1}([])
	for vi ∈ nodeset
		v = net.nodelist[vi]
		for e ∈ v.outedges
			if (e ∉ edgeset) && (net.edgelist[e].active == true)
				push!(edgeset, e)
			end
		end
		for e ∈ v.inedges
			if (e ∉ edgeset) && (net.edgelist[e].active == true)
				push!(edgeset, e)
			end
		end
	end
	edgecardinality = length(edgeset)

	#Construct X
	X1 = zeros(Float64, edgecardinality)
	for i=1:edgecardinality
		X1[i] = X[edgeset[i]]
	end

	#Construct E1
	E1 = zeros(Float64, nodecardinality, edgecardinality)
	for i=1:nodecardinality
		vi = nodeset[i]
		v = net.nodelist[vi]
		for j=1:edgecardinality
			if edgeset[j] ∈ v.outedges
				E1[i,j] = 1.0
			end
			if edgeset[j] ∈ v.inedges
				E1[i,j] = -1.0
			end
		end
	end

	#Solve the DC flow equations
	L = weightedlaplacianmatrix(E1, 1.0./X1)
	theta = nodevoltages(L, P1)
	F = edgecurrents(theta, E1, X1)./fmaxtemp

	#Scan through new flow vector and record any overloads
	overloadcounter = 0
	survivors = Array{Int64, 1}([])
	for i=1:edgecardinality
		ei = edgeset[i]
		if abs(F[i]) > alpha #*abs(Finit[ei])
			net.edgelist[ei].active = false
			overloadcounter += 1
		else
			push!(survivors, i)
		end
	end

	#If no new overloads, halt recursion
	if overloadcounter == 0
		return 1
	end

	#Delete overloads from E1
	newedgecard = length(survivors)
	E2 = zeros(Float64, nodecardinality, newedgecard)
	for j=1:newedgecard
		E2[:,j] = E1[:,survivors[j]]
	end

	#Check to see if its broken
	Adj = adjacencymatrix(E2)
	components, table = connectedcomponents(Adj)

	#Clear any superfluous data
	E1 = nothing
	E2 = nothing
	Adj = nothing
	L = nothing
	F = nothing

	#Call fracture function on each of the components, passing down info
	descendent_leaves = 0
	for i=1:components
		chunk = table[i]
		nodesubset = zeros(Int64, length(chunk))
		for j=1:length(chunk)
			nodesubset[j] = nodeset[chunk[j]]
		end
		leaves = fracture2!(net, nodesubset, P, X, Finit, simtime, alpha, fmaxtemp)
		descendent_leaves += leaves
	end

	return descendent_leaves
end


n = rand(1:10)

A = Array(Float32, n, n)
copy!(A, rand(n,n))

r1 = polarfact(A, alg =:newton);

r2 = polarfact(A, alg =:qdwh);

r3 = polarfact(A, alg =:halley);

r4 = polarfact(A, alg =:svd);

r5 = polarfact(A, alg =:hybrid);

@test_approx_eq r1.U r2.U
@test_approx_eq r2.U r3.U
@test_approx_eq r3.U r4.U
@test_approx_eq r4.U r5.U

println("Type Float32 passed test...")


@testset "Quotient orders" begin
  G = small_group(8, 4)
  QG = QQ[G]
  ZG = Order(QG, basis(QG); cached = false)
  N, NtoG = subgroups(G, order = 2)[1]
  I = sum([QG(NtoG(n)) for n in N]) * ZG
  OO, h = Hecke.quotient_order(ZG, I)

  for i in 1:10
    for j in 1:10
      a = rand(QG, -10:10)
      b = rand(QG, -10:10)
      @test h(a * b) == h(a) * h(b)
      @test h(a) + h(b) == h(a) + h(b)

      a = rand(algebra(OO), -10:10)
      b = rand(algebra(OO), -10:10)
      @test h(h\(a * b)) == a * b
      @test h(h\(a + b)) == a + b
    end
  end

  @test OO == Order(algebra(OO), [h(elem_in_algebra(b)) for b in basis(ZG)])
end


struct GraphConv{A<:AbstractArray{Float32},B<:AbstractArray{Float32},C<:AbstractArray{Float32}}
    weight::A # Fout x Fin x k
    bias::B # Fout
    L::C #Graph operator V x V
    k::Integer
    #in_channel::Integer
    #out_channel::Integer
end

function GraphConv(L::AbstractArray,ch::Pair{<:Integer,<:Integer}, k::Integer;
    init = glorot_normal, bias::Bool=false)
    b = bias ? init(ch[2]) : zeros(Float32,ch[2])
    GraphConv(init(ch[2], ch[1], k), b, L, k)
end

# see https://fluxml.ai/Flux.jl/stable/models/advanced/
# https://discourse.julialang.org/t/implementing-a-custom-layer/38632
@functor GraphConv #(weight, bias,)
Flux.trainable(c::GraphConv) = (c.weight,c.bias,)

function (c::GraphConv)(X::AbstractArray)
    fin, b, v = size(X) #Features_in, Batch, Vertices
    fout = size(c.weight,1)
    Y = view(c.weight,:,:,1) * reshape(X, fin, b*v) #Fout x B*V
    Z_prev = reshape(X, fin*b, v) #B*Fin x V
    Z = Z_prev * c.L #B*Fin x V
    Y += view(c.weight,:,:,2) * reshape(Z, fin, b*v)
    for k = 3:c.k
        Z, Z_prev = 2*Z*c.L - Z_prev, Z #B*Fin x V
        Y += view(c.weight,:,:,k) * reshape(Z, fin, b*v)
    end
    out = reshape(Y,fout, b, v) .+ c.bias
    relu.(out)
end

struct GraphMaxPool
    k::Integer
end

function (m::GraphMaxPool)(X::AbstractArray{T}) where {T<:Float32}
    #size(X) Feat x B x V
    permutedims(maxpool(permutedims(X,[3,1,2]),(m.k,)) , [2,3,1])#Feat x B x V/k
end

function graphflatten(x::AbstractArray)
    #size(x) Feat x B x V
    x = permutedims(x, [1,3,2])# Feat x V x B
    return reshape(x, :, size(x)[end])# Feat*V x B
end


using Catlab
using Catlab.Doctrines
using Catlab.Graphics
using Catlab.WiringDiagrams
using Catlab.Programs
import Base.Multimedia: display
import Catlab.Graphics: to_graphviz, LeftToRight
println("Done Importing Catlab")
import Base: (==), length, show
using Test
using Petri
using SemanticModels.CategoryTheory
import SemanticModels.CategoryTheory: undecorate, ⊔
using SemanticModels.PetriModels
using SemanticModels.PetriCospans
import SemanticModels.PetriCospans: otimes_ipm, compose_pushout
println("Done Importing SemanticModels")

import Catlab.Doctrines:
  Ob, Hom, dom, codom, compose, ⋅, ∘, id, oplus, otimes, ⊗, ⊕, munit, mzero, braid,
  dagger, dunit, dcounit, mcopy, Δ, delete, ◊, mmerge, ∇, create, □,
  plus, zero, coplus, cozero, meet, top, join, bottom

wd = to_wiring_diagram
draw(d::WiringDiagram) = to_graphviz(add_junctions(d), orientation=LeftToRight, labels=true)
draw(d::HomExpr) = draw(wd(d))

println("Making COVID Model")

@present Disease(FreeEpidemiology) begin
    S::Ob
    E::Ob
    I::Ob
    R::Ob
end

S,E,I,R = generators(Disease)
# draw(spontaneous(E,I)⋅spontaneous(I,R)⊗(spontaneous(E,I)))
sei = compose(exposure(S,I,E), otimes(spontaneous(E,I), id(I)), mmerge(I))
# draw(sei)
seir = sei⋅Δ(I)⋅(id(I)⊗spontaneous(I, R))
# draw(seir)
seir2 = compose(mcopy(S)⊗id(I), id(S)⊗seir)
# draw(seir2)

# draw(death(S))

d = @program Disease (s::S, e::E, i::I) begin
    e1, i1 = exposure{S,I,E}(s,i)
    i2 = spontaneous{E,I}(e1)
    e = [e, e1]
    e_out = spontaneous{E,E}(e)
    i1 = [i1, i2]
    r = spontaneous{I,R}(i1)
    s_out = spontaneous{S,S}(s)
    return s_out, e_out, spontaneous{I,I}(i1)
end
# draw(d)

# draw(d⋅d)

seirdef = to_hom_expr(FreeEpidemiology, d)
try
    add_definition!(Disease, :seir, seirdef)
catch
    println("INFO: definition already added.")
end

# if the disease is fatal, we need to add a death component
seird = @program Disease (s::S, e::E, i::I) begin
    e1, i1 = exposure{S,I,E}(s,i)
    i2 = spontaneous{E,I}(e1)
    e = [e, e1]
    e_out = spontaneous{E,E}(e)
    i1 = [i1, i2]
    r = spontaneous{I,R}(i1)
    s_out = spontaneous{S,S}(s)
    death{I}(i1)
    return s_out, e_out, spontaneous{I,I}(i1)
end
#TODO: This does not get translated correctly, bug?
seirddef = to_hom_expr(FreeEpidemiology, seird)
try
    add_definition!(Disease, :seird, seirddef)
catch
    println("INFO: definition already added.")
end

seirgen = generator(Disease, :seir)
seirdgen = generator(Disease, :seird)

ncities(city,n::Int) = compose([city for i in 1:n]...)
city³ = ncities(seirgen, 3)
# draw(city³)

dcity³ = wd(city³)
dc3 = substitute(dcity³, box_ids(dcity³), [d,d,d])
@show dc3 == ncities(d, 3)
# draw(dc3)

import Base: repeat
repeat(d::WiringDiagram, n::Int) = compose([d for i in 1:n]...)
repeat(d::FreeEpidemiology.Hom, n::Int) = compose([d for i in 1:n]...)

# draw(ncities(seirdgen, 3))
# draw(repeat(seird, 3))

# draw(seirddef)
#
# using TikzPictures
# using Catlab.Graphics.TikZWiringDiagrams
# using Convex
# using SCS
#
# to_tikz(seirddef, labels=true)
X = FinSet(1)
F(ex) = functor((FinSet, FinSetMorph),
                ex,
                generators=Dict(S=>X, I=>X, E=>X, R=>X))

Fseir = compose(exposure(X,X,X),otimes(spontaneous(X,X),id(X)),mmerge(X),mcopy(X),otimes(id(X),spontaneous(X,X)))
# states are [S, I, E, R]
seir_petri = left(Fseir.f).d[1]
f = FinSetMorph(1:4, [1, 2, 3])
g = FinSetMorph(1:4, [1, 2, 3])
Fseir′ = PetriCospan(Cospan(Decorated(f,seir_petri), Decorated(g, seir_petri)))
s = spontaneous(X,X)
Fflow = otimes(s, s, s)
Fcity = Fseir′ ⋅ Fflow
Fcity³ = Fcity⋅Fcity⋅Fcity
@test left(Fcity³.f).d[1].model.S == 1:15
@test left(Fcity³.f).d[1].model.Δ == [([1, 2], [3, 2]),
                                      ([3], [2]),
                                      ([2], [4]),
                                      #outflow 1
                                      ([1], [5]),
                                      ([2], [6]),
                                      ([3], [7]),
                                      # SEIR 2
                                      ([5, 6], [7, 6]),
                                      ([7], [6]),
                                      ([6], [8]),
                                      #outflow 2
                                      ([5], [9]),
                                      ([6], [10]),
                                      ([7], [11]),
                                      # SEIR 3
                                      ([9, 10], [11, 10]),
                                      ([11], [10]),
                                      ([10], [12]),
                                      #outflow 3
                                      ([9], [13]),
                                      ([10], [14]),
                                      ([11], [15])
                                      ]
Fcity₀ = Fseir′ ⋅ Fflow
Fcity₁ = Fflow ⋅ Fseir′ ⋅ Fflow
Fcityₑ = Fflow ⋅ Fseir′
Fcity³ = Fcity₀⋅Fcity₁⋅Fcityₑ

@test left(Fcity³.f).d[1].model.S == 1:18
@test left(Fcity³.f).d[1].model.Δ == [
    # SEIR 1
     ([1, 2], [3, 2]),
     ([3], [2]),
     ([2], [4]),
     # outflow 1→2
     ([1], [5]),
     ([2], [6]),
     ([3], [7]),
     # inflow 1→2
     ([5], [8]),
     ([6], [9]),
     ([7], [10]),
    # SEIR 2
     ([8, 9], [10, 9]),
     ([10], [9]),
     ([9], [11]),
     # outflow 2→3
     ([8], [12]),
     ([9], [13]),
     ([10], [14]),
     # inflow 2→3
     ([12], [15]),
     ([13], [16]),
     ([14], [17]),
    # SEIR 3
     ([15, 16], [17, 16]),
     ([17], [16]),
     ([16], [18])
]

Fcity₀ = Fseir′ ⋅ Fflow
Fcity₁ = Fseir′ ⋅ Fflow
Fcityₑ = Fseir′
Fcity³ = Fcity₀⋅Fcity₁⋅Fcityₑ

@test left(Fcity³.f).d[1].model.S == 1:12
@test left(Fcity³.f).d[1].model.Δ == [
    # City 1 SEIR
    ([1, 2], [3, 2]),
    ([3], [2]), # E→I
    ([2], [4]), # I→R
    # outflow 1→2
    ([1], [5]), # S→S′
    ([2], [6]), # I→I′
    ([3], [7]), # E→E′
    # City 2 SEIR
    ([5, 6], [7, 6]),
    ([7], [6]), # E→I
    ([6], [8]), # I→R
    # outflow 2→3
    ([5], [9]), # S→S′
    ([6], [10]),# I→I′
    ([7], [11]),# E→E′
    # City 3 SEIR
    ([9, 10], [11, 10]),
    ([11], [10]), # E→I
    ([10], [12]) # I→R
    ]

Pseird = PetriModel(
          Petri.Model(1:5,[
            ([1,2],[3,2]), # exposure
            ([3],[2]),     # onset
            ([2],[4]),     # recovery
            ([2],[5]),     # death
            ], missing, missing))
inputs = FinSetMorph(1:5, [1,2,3])
outputs = FinSetMorph(1:5, [1,2,3])
Fcityd = PetriCospan(Cospan(Decorated(inputs, Pseird),
                            Decorated(outputs, Pseird)))
Fcity₀ = Fcityd ⋅ Fflow
Fcity₁ = Fcityd ⋅ Fflow
Fcityₑ = Fcityd
Fcity³ = Fcity₀⋅Fcity₁⋅Fcityₑ

left(Fcity³.f).d[1].model.S == 1:15
length(left(Fcity³.f).d[1].model.Δ) == 18


# --------------------------------------------------------------------------
# ACE.jl and SHIPs.jl: Julia implementation of the Atomic Cluster Expansion
# Copyright (c) 2019 Christoph Ortner <christophortner0@gmail.com>
# All rights reserved.
# --------------------------------------------------------------------------

@testset "OneOrthogonal" begin

##
@info("--------- Testing OneOrthogonal ----------")
using LinearAlgebra, Test
using ACE
using ACE.OrthPolys: OrthPolyBasis, OneOrthogonal
using JuLIP: evaluate, evaluate_d, evaluate!, evaluate_d!
using JuLIP: evaluate
##
# Assemble basis
N = 10  # degree
Nquad = 1000
dt =  2 / Nquad
tdf = range(-1.0+dt/2, 1.0-dt/2, length=Nquad)
ww = ones(Nquad) / Nquad
P = OrthPolyBasis(N,  0, 1.0, 2, -1.0, tdf, ww)
Q = OneOrthogonal(P)

# Evaluate basis
p = Matrix{Float64}(undef,Nquad,N)
q = Matrix{Float64}(undef,Nquad,N)
for i = 1:Nquad
    evaluate!(@view(p[i,:]), nothing, P, tdf[i])
    evaluate!(@view(q[i,:]), nothing, Q, tdf[i])
end

@info("Test spanning property")
@test rank(p'*q) == N

@info("Test orthogonality properties")
@test q'*Diagonal(ww)*q ≈ I
@test norm(q[:,1:end-1]'*Diagonal(ww)*one.(tdf)) < sqrt(eps())

@info("Test derivative")
ε = sqrt(eps())
q1 = evaluate(Q, -ε/2)
q2 = evaluate(Q, ε/2)
dq = evaluate_d(Q,0.0)
@test (q2.-q1)./ε ≈ dq

##

@info("Generate many-body 1-orth basis")

deg = 10  # degree
r0, rcut = 0.5, 2.5
trans = PolyTransform(2, r0)
t0, tcut = trans(r0), trans(rcut)
tdf = range(t0, tcut, length=1000)
ww = ones(Nquad) / Nquad
J = OrthPolyBasis(deg,  0, trans(r0), 2, trans(rcut), tdf, ww)
J1 = OneOrthogonal(J)
P = ACE.TransformedPolys(J1, trans, r0, rcut)

# SHIPBasis(spec::BasisSpec, J; T=Float64, kwargs...)
spec = SparseSHIP(2, deg; wL = 1.5,
                  filterfcn = ν -> ACE.OrthPolys.filter_oneorth(ν, J1))
aceB = SHIPBasis(spec, P)
pureB = ACE.PureBasis(aceB)

I2 = findall(aceB.aalists[1].len .== 1)
B2 = ACE.Utils.SubBasis(aceB, I2)

##

pureB = ACE.PureBasis(aceB, 2)

##

Rs = ACE.rand_vec(aceB.J, 3)
Zs = zeros(Int16, 5)
b = evaluate(aceB, Rs, Zs, 0)
bp = pureB(Rs)
# @assert length(b) == length(aceB) == length(bp)

G, A_dot_1 = let nsamples = 10_000
   G = zeros(length(pureB), length(pureB))
   A_dot_1 = zeros(length(I2))
   for _ = 1:nsamples
      Rs = ACE.rand_vec(aceB.J, 2)
      b = pureB(Rs)
      G += b * b' / nsamples
      A_dot_1 += b[I2] / nsamples
   end
   G, A_dot_1
end

##
D = Diagonal( diag(G).^(-0.5) )
Gsc = D * G * D
@show cond(Gsc[1:9,1:9])  # this shows the A-basis is orthogonal
@show cond(Gsc)           # but not the ∏A basis
@show norm(A_dot_1, Inf)

end


# parallel park
T = Float64

# model
model = TrajectoryOptimization.Dynamics.car
n = model.n; m = model.m
model_d = rk3(model)

# cost
x0 = [0.; 0.; 0.]
xf = [0.; 1.; 0.]
tf =  3.
Qf = 100.0*Diagonal(I,n)
Q = (1e-2)*Diagonal(I,n)
R = (1e-2)*Diagonal(I,m)

# constraints
u_bnd = 2.
x_min = [-0.25; -0.001; -Inf]
x_max = [0.25; 1.001; Inf]
bnd1 = BoundConstraint(n,m,u_min=-u_bnd,u_max=u_bnd)
bnd2 = BoundConstraint(n,m,x_min=x_min,x_max=x_max,u_min=-u_bnd,u_max=u_bnd)

goal = goal_constraint(xf)

# problem
N = 51
dt = 0.06

U = [ones(m) for k = 1:N-1]
obj = LQRObjective(Q,R,Qf,xf,N)

constraints = Constraints(N)
constraints[1] += bnd1
for k = 2:N-1
    constraints[k] += bnd2
end
constraints[N] += goal

parallel_park = Problem(model_d,obj,U,constraints=constraints,dt=dt,x0=x0,xf=xf)