module NAFEMS_examples
using FinEtools
using FinEtools
using FinEtools.AlgoDeforLinearModule: ssit

function NAFEMS_FV32_algo()
    println("""
    FV32: Cantilevered tapered membrane
    This is a test recommended by the National Agency for Finite Element Methods and
    Standards (U.K.): Test FV32 from NAFEMS publication TNSB, Rev. 3, “The Standard
    NAFEMS Benchmarks,” October 1990.

    Reference solution: 44.623	130.03	162.70	246.05	379.90	391.44 for the first
    six modes.
    """)

    t0 = time()

    E = 200*phun("GPA");
    nu = 0.3;
    rho= 8000*phun("KG/M^3");
    L = 10*phun("M");
    W0 = 5*phun("M");
    WL = 1*phun("M");
    H = 0.05*phun("M");
    nL, nW, nH = 8, 4, 1;# How many element edges per side?
    neigvs=20                   # how many eigenvalues
    Reffs = [44.623	130.03	162.70	246.05	379.90	391.44]

    fens,fes =H20block(1.0, 2.0, 1.0, nL, nW, nH)
    for i = 1:count(fens)
        xi, eta, theta = fens.xyz[i,:];
        eta = eta - 1.0
        fens.xyz[i,:] = [xi*L eta*(1.0 - 0.8*xi)*W0/2 theta*H/2];
    end
    # File =  "mesh.vtk"
    # vtkexportmesh(File, fens, fes)
    # @async run(`"paraview.exe" $File`)

    # Make the region
    MR = DeforModelRed3D
    material = MatDeforElastIso(MR, rho, E, nu, 0.0)
    region1 = FDataDict("femm"=>FEMMDeforLinear(MR, IntegData(fes, GaussRule(3,2)),
    material), "femm_mass"=>FEMMDeforLinear(MR, IntegData(fes, GaussRule(3,3)),
    material))

    nl1 = selectnode(fens; plane=[1.0 0.0 0.0 0.0], thickness=H/1.0e4)
    ebc1 = FDataDict("node_list"=>nl1, "component"=>1, "displacement"=>0.0)
    ebc2 = FDataDict("node_list"=>nl1, "component"=>2, "displacement"=>0.0)
    ebc3 = FDataDict("node_list"=>nl1, "component"=>3, "displacement"=>0.0)

    nl4 = selectnode(fens; plane=[0.0 0.0 1.0 0.0], thickness=H/1.0e4)
    ebc4 = FDataDict("node_list"=>nl4, "component"=>3, "displacement"=>0.0)

    # Make model data
    modeldata =  FDataDict(
    "fens"=> fens, "regions"=>  [region1], "essential_bcs"=>[ebc1 ebc2 ebc3 ebc4],
    "neigvs"=>neigvs)

    # Solve
    modeldata = FinEtools.AlgoDeforLinearModule.modal(modeldata)

    fs = modeldata["omega"]/(2*pi)
    println("Eigenvalues: $fs [Hz]")
    println("Percentage frequency errors: $((vec(fs[1:6]) - vec(Reffs))./vec(Reffs)*100)")

    modeldata["postprocessing"] = FDataDict("file"=>"FV32-modes", "mode"=>1:10)
    modeldata=FinEtools.AlgoDeforLinearModule.exportmode(modeldata)
    @async run(`"paraview.exe" $(modeldata["postprocessing"]["file"]*"1.vtk")`)

    true

end # NAFEMS_FV32_algo

function NAFEMS_TEST13H_vib()
    # Harmonic forced vibration problem is solved for a homogeneous square plate,
    # simply-supported on the circumference.
    # This is the TEST 13H from the Abaqus v 6.12 Benchmarks manual.
    # The test is recommended by the National Agency for Finite Element Methods and Standards (U.K.):
    # Test 13 from NAFEMS “Selected Benchmarks for Forced Vibration,” R0016, March 1993.
    #
    #
    # The plate is discretized with hexahedral solid elements. The simple support
    # condition is approximated by distributed rollers on the boundary.
    # Because only the out of plane displacements are prevented, the structure
    # has three rigid body modes in the plane of the plate.
    #
    #
    # The nonzero benchmark frequencies are (in hertz): 2.377, 5.961, 5.961,
    # 9.483, 12.133, 12.133, 15.468, 15.468 [Hz].

    println("""
    Homogeneous square plate, simply-supported on the circumference from
    the test 13 from NAFEMS “Selected Benchmarks for Forced Vibration,” R0016, March 1993.
    The nonzero benchmark frequencies are (in hertz): 2.377, 5.961, 5.961,
    9.483, 12.133, 12.133, 15.468, 15.468 [Hz].
    """)

    # t0 = time()

    E = 200*phun("GPa");# Young's modulus
    nu = 0.3;# Poisson ratio
    rho = 8000*phun("KG*M^-3");# mass density
    L = 10.0*phun("M"); # side of the square plate
    t = 0.05*phun("M"); # thickness of the square plate
    nL = 8; nt = 4;
    tolerance = t/nt/100;
    neigvs = 11;
    OmegaShift = (2*pi*0.5) ^ 2; # to resolve rigid body modes

    MR = DeforModelRed3D
    fens,fes  = H8block(L, L, t, nL, nL, nt)

    geom = NodalField(fens.xyz)
    u = NodalField(zeros(size(fens.xyz,1),3)) # displacement field
    nl = selectnode(fens, box=[0.0 0.0 -Inf Inf -Inf Inf], inflate=tolerance)
    setebc!(u, nl, true, 3)
    nl = selectnode(fens, box=[L L -Inf Inf -Inf Inf], inflate=tolerance)
    setebc!(u, nl, true, 3)
    nl = selectnode(fens, box=[-Inf Inf 0.0 0.0 -Inf Inf], inflate=tolerance)
    setebc!(u, nl, true, 3)
    nl = selectnode(fens, box=[-Inf Inf L L -Inf Inf], inflate=tolerance)
    setebc!(u, nl, true, 3)
    applyebc!(u)
    numberdofs!(u)
    println("nfreedofs = $(u.nfreedofs)")

    material=MatDeforElastIso(MR, rho, E, nu, 0.0)

    femm = FEMMDeforLinearMSH8(MR, IntegData(fes, GaussRule(3,2)), material)
    femm = associategeometry!(femm, geom)
    K = stiffness(femm, geom, u)
    femm = FEMMDeforLinear(MR, IntegData(fes, GaussRule(3,3)), material)
    M = mass(femm, geom, u)

    if true
        t0 = time()
        d,v,nev,nconv = eigs(K+OmegaShift*M, M; nev=neigvs, which=:SM)
        d = d - OmegaShift;
        fs = real(sqrt.(complex(d)))/(2*pi)
        println("Reference Eigenvalues: $fs [Hz]")
        println("eigs solution ($(time() - t0) sec)")
    end
    true

end # NAFEMS_TEST13H_vib

function allrun()
    println("#####################################################")
    println("# NAFEMS_FV32_algo ")
    NAFEMS_FV32_algo()
    println("#####################################################")
    println("# NAFEMS_TEST13H_vib ")
    NAFEMS_TEST13H_vib()
    return true
end # function allrun

end # module NAFEMS_examples


# compare the time taken to do simple subtractions compared to <two different implementations of floor
# with a view to making some routiens that use subtraction a lot faster
using IndexedTables
using SurrealNumbers

# old code for reference, e.g., in case you want to rerun some timing tests to understand
# why current code is written the way it is

function floor2(s::SurrealFinite)
    if zero(s) <= s < one(s)
        return zero(s)
    elseif s < zero(s)
        return floor(s + one(s)) - one(s)
    elseif s >= one(s)
        return floor(s - one(s)) + one(s)
    end
end

function isinteger2(s::SurrealFinite)
    if s ≅ zero(s)
        return true
    elseif s <= -one(s)
        return isinteger(s + one(s))
    elseif s >= one(s)
        return isinteger(s - one(s))
    else
        return false
    end
end

# not the more general form of rounding defined in Julia -- should fix
function round2(s::SurrealFinite)
    if s ≅ zero(s)
        return s
    elseif s <= -one(s)
        return round(s + one(s)) - one(s)
    elseif s >= one(s)
        return round(s - one(s)) + one(s)
    elseif s >= convert(SurrealFinite, 1//2)
        return one(s)
    elseif s >= convert(SurrealFinite, -1//2)
        return zero(s)
    else
        return -one(s)
    end
end

convert(SurrealFinite, 2) * convert(SurrealFinite, 1)

#ns = 6:-1:0
ns = 0:4
n = length(ns)
m = 100 * ones(size(ns))
x1 = Array{SurrealFinite,1}(n)
x2 = Array{SurrealFinite,1}(n)
t1 = Array{Float64,1}(n)
t2 = Array{Float64,1}(n)
bytes2 = Array{Float64,1}(n)
bytes1 = Array{Float64,1}(n)
gctime1 = Array{Float64,1}(n)
gctime2 = Array{Float64,1}(n)
s = convert(SurrealFinite, 1)
floor(s)
floor2(s)
for i=1:n
    println("  working on ", ns[i])
    t1[i] = 0
    t2[i] = 0
    b1 = 0
    g1 = 0
    mem1 = 0
    val1 = 0
    b2 = 0
    g2 = 0
    mem2 = 0
    val2 = 0
    for j=1:m[i]
        s = convert(SurrealFinite, ns[i] + 0.5)
        # val, time, b, g, mem = @timed convert(Rational, s)
        val1, time1, b1, g1, mem1 = @timed floor( s )
        val2, time2, b2, g2, mem2 = @timed floor2( s )
        t1[i] += time1
        t2[i] += time2
    end
    x1[i] = val1
    bytes1[i] = b1
    gctime1[i] = g1
    x2[i] = val2
    bytes2[i] = b2
    gctime2[i] = g2
end
# s = size.(x)
# d = depth_av.(x)
# f = n_zeros.(x)
t1 = t1./m[1:n]
bytes1 = bytes1./m[1:n]
t2 = t2./m[1:n]
bytes2 = bytes2./m[1:n]

t1_p_ms = 1.0e6 * t1
t2_p_ms = 1.0e6 * t2

tab1 = table(@NT( i=0:n-1,
                time1=t1_p_ms,
                time2=t2_p_ms,
                bytes1=bytes1,
                bytes2=bytes2);
           pkey = [:i])


# Weights and points for Gauss-Legendre quadrature for a variety of shapes

# The weights and points for Gauss-Legendre quadrature on the interval F[0,1]
# ∑wᵢ= 1, rᵢ∈ F[0, 1]
#
# N that have entries in this function: N = F[ 1, 2, 3, 4, 5, 10, 15, 20].
function gauss_legendre_quadrature(::Type{F}, ::Val{1}) where {F <: AbstractFloat}
    weights = @SVector F[1.0]
    r = @SVector F[0.5]
    return weights, r
end

function gauss_legendre_quadrature(::Type{F}, ::Val{2}) where {F <: AbstractFloat}
    weights = @SVector F[0.5, 0.5]
    r = @SVector F[0.21132486540518713, 0.7886751345948129]
    return weights, r
end

function gauss_legendre_quadrature(::Type{F}, ::Val{3}) where {F <: AbstractFloat}
    weights = @SVector F[0.2777777777777776,
                        0.4444444444444444,
                        0.2777777777777776]
    r = @SVector F[0.1127016653792583,
                  0.5,
                  0.8872983346207417]
    return weights, r
end

function gauss_legendre_quadrature(::Type{F}, ::Val{4}) where {F <: AbstractFloat}
    weights = @SVector F[
                  0.17392742256872684,
                  0.3260725774312732,
                  0.3260725774312732,
                  0.17392742256872684
               ]
    r = @SVector F[
          0.06943184420297371,
          0.33000947820757187,
          0.6699905217924281,
          0.9305681557970262
         ]
    return weights, r
end

function gauss_legendre_quadrature(::Type{F}, ::Val{5}) where {F <: AbstractFloat}
    weights = @SVector F[
                  0.1184634425280945,
                  0.23931433524968326,
                  0.28444444444444444,
                  0.23931433524968326,
                  0.1184634425280945
               ]
    r = @SVector F[
          0.04691007703066802,
          0.23076534494715845,
          0.5,
          0.7692346550528415,
          0.9530899229693319,
         ]
    return weights, r
end

function gauss_legendre_quadrature(::Type{F}, ::Val{10}) where {F <: AbstractFloat}
    weights = @SVector F[
                  0.03333567215434387,
                  0.07472567457529025,
                  0.1095431812579911,
                  0.1346333596549981,
                  0.14776211235737646,
                  0.14776211235737646,
                  0.1346333596549981,
                  0.1095431812579911,
                  0.07472567457529025,
                  0.03333567215434387
               ]
    r = @SVector F[
          0.013046735741414184,
          0.06746831665550773,
          0.16029521585048778,
          0.2833023029353764,
          0.4255628305091844,
          0.5744371694908156,
          0.7166976970646236,
          0.8397047841495122,
          0.9325316833444923,
          0.9869532642585859
         ]
    return weights, r
end

function gauss_legendre_quadrature(::Type{F}, ::Val{15}) where {F <: AbstractFloat}
    weights = @SVector F[
                    0.015376620998058315,
                    0.03518302374405407,
                    0.053579610233586,
                    0.06978533896307715,
                    0.083134602908497,
                    0.0930805000077811,
                    0.0992157426635558,
                    0.10128912096278064,
                    0.0992157426635558,
                    0.0930805000077811,
                    0.083134602908497,
                    0.06978533896307715,
                    0.053579610233586,
                    0.03518302374405407,
                    0.015376620998058315
                ]
    r = @SVector F[
          0.006003740989757311,
          0.031363303799647024,
          0.0758967082947864,
          0.13779113431991497,
          0.21451391369573058,
          0.3029243264612183,
          0.39940295300128276,
          0.5,
          0.6005970469987172,
          0.6970756735387817,
          0.7854860863042694,
          0.862208865680085,
          0.9241032917052137,
          0.968636696200353,
          0.9939962590102427
         ]
    return weights, r
end

function gauss_legendre_quadrature(::Type{F}, ::Val{20}) where {F <: AbstractFloat}
    weights = @SVector F[
                  0.00880700356957606,
                  0.02030071490019347,
                  0.03133602416705453,
                  0.04163837078835238,
                  0.0509650599086202,
                  0.0590972659807592,
                  0.0658443192245883,
                  0.07104805465919105,
                  0.07458649323630186,
                  0.07637669356536295,
                  0.07637669356536295,
                  0.07458649323630186,
                  0.07104805465919105,
                  0.0658443192245883,
                  0.0590972659807592,
                  0.0509650599086202,
                  0.04163837078835238,
                  0.03133602416705453,
                  0.02030071490019347,
                  0.00880700356957606
               ]
    r = @SVector F[
          0.0034357004074525577,
          0.018014036361043095,
          0.04388278587433703,
          0.08044151408889061,
          0.1268340467699246,
          0.1819731596367425,
          0.24456649902458644,
          0.3131469556422902,
          0.38610707442917747,
          0.46173673943325133,
          0.5382632605667487,
          0.6138929255708225,
          0.6868530443577098,
          0.7554335009754136,
          0.8180268403632576,
          0.8731659532300754,
          0.9195584859111094,
          0.956117214125663,
          0.981985963638957,
          0.9965642995925474
         ]
    return weights, r
end

# # The weights and points for Gauss-Legendre quadrature on the parametric unit triangle
# # ∑wᵢ= 1/2, rᵢ∈ F[0, 1], sᵢ∈ F[0, 1], rᵢ + sᵢ ≤ 1
# # N that have entries in this function: N = F[3, 4, 6, 12, 27, 48, 79]
# function gauss_legendre_quadrature(tri::Triangle6_2D{F}, ::Val{1}) where {F <: AbstractFloat}
#     # P1. 0 negative weights, 0 points outside of the triangle
#     w = @SVector F[0.500000000000000]
#
#     r = @SVector F[0.333333333333333]
#
#     s = @SVector F[0.333333333333333]
#     return w, r, s
# end
#
# function gauss_legendre_quadrature(tri::Triangle6_2D{F}, ::Val{3}) where {F <: AbstractFloat}
#     # P2. 0 negative weights, 0 points outside of the triangle
#     w = @SVector F[0.166666666666667,
#                   0.166666666666667,
#                   0.166666666666667]
#
#     r = @SVector F[0.166666666666667,
#                   0.166666666666667,
#                   0.666666666666667]
#
#     s = @SVector F[0.166666666666667,
#                   0.666666666666667,
#                   0.166666666666667]
#     return w, r, s
# end
#
# function gauss_legendre_quadrature(tri::Triangle6_2D{F}, ::Val{4}) where {F <: AbstractFloat}
#     # P3. 1 negative weights, 0 points outside of the triangle
#     w = @SVectorF[-0.281250000000000,
#                   0.260416666666667,
#                   0.260416666666667,
#                   0.260416666666667]
#
#     r = @SVector F[0.333333333333333,
#                   0.200000000000000,
#                   0.200000000000000,
#                   0.600000000000000]
#
#     s = @SVectorF[ 0.333333333333333,
#                   0.200000000000000,
#                   0.600000000000000,
#                   0.200000000000000]
#     return w, r, s
# end
#
# function gauss_legendre_quadrature(tri::Triangle6_2D{F}, ::Val{6}) where {F <: AbstractFloat}
#     # P4. 0 negative weights, 0 points outside of the triangle
#     w = @SVector F[0.111690794839005,
#                   0.111690794839005,
#                   0.111690794839005,
#                   0.054975871827661,
#                   0.054975871827661,
#                   0.054975871827661]
#     r = @SVector F[0.445948490915965,
#                   0.445948490915965,
#                   0.108103018168070,
#                   0.091576213509771,
#                   0.091576213509771,
#                   0.816847572980459]
#
#     s = @SVector F[0.445948490915965,
#                   0.108103018168070,
#                   0.445948490915965,
#                   0.091576213509771,
#                   0.816847572980459,
#                   0.091576213509771]
#     return w, r, s
# end
#
# function gauss_legendre_quadrature(tri::Triangle6_2D{F}, ::Val{12}) where {F <: AbstractFloat}
#     # P6. 0 negative weights, 0 points outside of the triangle
#     w = @SVector F[0.058393137863189,
#           0.058393137863189,
#           0.058393137863189,
#           0.025422453185104,
#           0.025422453185104,
#           0.025422453185104,
#           0.041425537809187,
#           0.041425537809187,
#           0.041425537809187,
#           0.041425537809187,
#           0.041425537809187,
#           0.041425537809187]
#
#     r = @SVector F[0.249286745170910,
#           0.249286745170910,
#           0.501426509658179,
#           0.063089014491502,
#           0.063089014491502,
#           0.873821971016996,
#           0.310352451033785,
#           0.636502499121399,
#           0.053145049844816,
#           0.310352451033785,
#           0.636502499121399,
#           0.053145049844816]
#
#     s = @SVector F[0.249286745170910,
#           0.501426509658179,
#           0.249286745170910,
#           0.063089014491502,
#           0.873821971016996,
#           0.063089014491502,
#           0.636502499121399,
#           0.053145049844816,
#           0.310352451033785,
#           0.053145049844816,
#           0.310352451033785,
#           0.636502499121399]
#     return w, r, s
# end
#
# function gauss_legendre_quadrature(tri::Triangle6_2D{F}, ::Val{27}) where {F <: AbstractFloat}
#     # P11. 0 negative weights, 3 points outside of the triangle
#     w = @SVector F[0.00046350316448,
#           0.00046350316448,
#           0.00046350316448,
#           0.03857476745740,
#           0.03857476745740,
#           0.03857476745740,
#           0.02966148869040,
#           0.02966148869040,
#           0.02966148869040,
#           0.01809227025170,
#           0.01809227025170,
#           0.01809227025170,
#           0.00682986550135,
#           0.00682986550135,
#           0.00682986550135,
#           0.02616855598110,
#           0.02616855598110,
#           0.02616855598110,
#           0.02616855598110,
#           0.02616855598110,
#           0.02616855598110,
#           0.01035382981955,
#           0.01035382981955,
#           0.01035382981955,
#           0.01035382981955,
#           0.01035382981955,
#           0.01035382981955]
#
#     r = @SVector F[+0.5346110482710,
#           -0.0692220965415,
#           +0.5346110482710,
#           +0.3989693029660,
#           +0.2020613940680,
#           +0.3989693029660,
#           +0.2033099004310,
#           +0.5933801991370,
#           +0.2033099004310,
#           +0.1193509122830,
#           +0.7612981754350,
#           +0.1193509122830,
#           +0.0323649481113,
#           +0.9352701037770,
#           +0.0323649481113,
#           +0.5932012134280,
#           +0.0501781383105,
#           +0.3566206482610,
#           +0.0501781383105,
#           +0.3566206482610,
#           +0.5932012134280,
#           +0.8074890031600,
#           +0.0210220165362,
#           +0.1714889803040,
#           +0.0210220165362,
#           +0.1714889803040,
#           +0.8074890031600]
#
#     s = @SVector F[+0.5346110482710,
#           +0.5346110482710,
#           -0.0692220965415,
#           +0.3989693029660,
#           +0.3989693029660,
#           +0.2020613940680,
#           +0.2033099004310,
#           +0.2033099004310,
#           +0.5933801991370,
#           +0.1193509122830,
#           +0.1193509122830,
#           +0.7612981754350,
#           +0.0323649481113,
#           +0.0323649481113,
#           +0.9352701037770,
#           +0.3566206482610,
#           +0.5932012134280,
#           +0.0501781383105,
#           +0.3566206482610,
#           +0.5932012134280,
#           +0.0501781383105,
#           +0.1714889803040,
#           +0.8074890031600,
#           +0.0210220165362,
#           +0.1714889803040,
#           +0.8074890031600,
#           +0.0210220165362]
#     return w, r, s
# end
#
# function gauss_legendre_quadrature(tri::Triangle6_2D{F}, ::Val{48}) where {F <: AbstractFloat}
#     # P15. 0 negative weights, 9 points outside of the triangle
#     w = @SVector F[0.000958437821425,
#           0.000958437821425,
#           0.000958437821425,
#           0.022124513635550,
#           0.022124513635550,
#           0.022124513635550,
#           0.025593274359450,
#           0.025593274359450,
#           0.025593274359450,
#           0.011843867935350,
#           0.011843867935350,
#           0.011843867935350,
#           0.006644887845000,
#           0.006644887845000,
#           0.006644887845000,
#           0.002374458304095,
#           0.002374458304095,
#           0.002374458304095,
#           0.019275036299800,
#           0.019275036299800,
#           0.019275036299800,
#           0.019275036299800,
#           0.019275036299800,
#           0.019275036299800,
#           0.013607907160300,
#           0.013607907160300,
#           0.013607907160300,
#           0.013607907160300,
#           0.013607907160300,
#           0.013607907160300,
#           0.001091038683400,
#           0.001091038683400,
#           0.001091038683400,
#           0.001091038683400,
#           0.001091038683400,
#           0.001091038683400,
#           0.010752659923850,
#           0.010752659923850,
#           0.010752659923850,
#           0.010752659923850,
#           0.010752659923850,
#           0.010752659923850,
#           0.003836971315525,
#           0.003836971315525,
#           0.003836971315525,
#           0.003836971315525,
#           0.003836971315525,
#           0.003836971315525]
#
#     r = @SVector F[+0.5069729168580,
#           -0.0139458337165,
#           +0.5069729168580,
#           +0.4314063542830,
#           +0.1371872914340,
#           +0.4314063542830,
#           +0.2776936448470,
#           +0.4446127103060,
#           +0.2776936448470,
#           +0.1264648910410,
#           +0.7470702179170,
#           +0.1264648910410,
#           +0.0708083859747,
#           +0.8583832280510,
#           +0.0708083859747,
#           +0.0189651702411,
#           +0.9620696595180,
#           +0.0189651702411,
#           +0.6049544668930,
#           +0.1337341619670,
#           +0.2613113711400,
#           +0.1337341619670,
#           +0.2613113711400,
#           +0.6049544668930,
#           +0.5755865555130,
#           +0.0363666773969,
#           +0.3880467670900,
#           +0.0363666773969,
#           +0.3880467670900,
#           +0.5755865555130,
#           +0.7244626630770,
#           -0.0101748831266,
#           +0.2857122200500,
#           -0.0101748831266,
#           +0.2857122200500,
#           +0.7244626630770,
#           +0.7475564660520,
#           +0.0368438698759,
#           +0.2155996640720,
#           +0.0368438698759,
#           +0.2155996640720,
#           +0.7475564660520,
#           +0.8839645740920,
#           +0.0124598093312,
#           +0.1035756165760,
#           +0.0124598093312,
#           +0.1035756165760,
#           +0.8839645740920]
#
#     s = @SVector F[+0.5069729168580,
#           +0.5069729168580,
#           -0.0139458337165,
#           +0.4314063542830,
#           +0.4314063542830,
#           +0.1371872914340,
#           +0.2776936448470,
#           +0.2776936448470,
#           +0.4446127103060,
#           +0.1264648910410,
#           +0.1264648910410,
#           +0.7470702179170,
#           +0.0708083859747,
#           +0.0708083859747,
#           +0.8583832280510,
#           +0.0189651702411,
#           +0.0189651702411,
#           +0.9620696595180,
#           +0.2613113711400,
#           +0.6049544668930,
#           +0.1337341619670,
#           +0.2613113711400,
#           +0.6049544668930,
#           +0.1337341619670,
#           +0.3880467670900,
#           +0.5755865555130,
#           +0.0363666773969,
#           +0.3880467670900,
#           +0.5755865555130,
#           +0.0363666773969,
#           +0.2857122200500,
#           +0.7244626630770,
#           -0.0101748831266,
#           +0.2857122200500,
#           +0.7244626630770,
#           -0.0101748831266,
#           +0.2155996640720,
#           +0.7475564660520,
#           +0.0368438698759,
#           +0.2155996640720,
#           +0.7475564660520,
#           +0.0368438698759,
#           +0.1035756165760,
#           +0.8839645740920,
#           +0.0124598093312,
#           +0.1035756165760,
#           +0.8839645740920,
#           +0.0124598093312]
#     return w, r, s
# end
#
# function gauss_legendre_quadrature(tri::Triangle6_2D{F}, ::Val{79}) where {F <: AbstractFloat}
#     # P20. 3 negative weights, 9 points outside of the triangle
#     w = @SVector F[+0.016528527770800,
#           +0.000433509592831,
#           +0.000433509592831,
#           +0.000433509592831,
#           +0.005830026358200,
#           +0.005830026358200,
#           +0.005830026358200,
#           +0.011438468178200,
#           +0.011438468178200,
#           +0.011438468178200,
#           +0.015224491336950,
#           +0.015224491336950,
#           +0.015224491336950,
#           +0.015312445862700,
#           +0.015312445862700,
#           +0.015312445862700,
#           +0.012184028838400,
#           +0.012184028838400,
#           +0.012184028838400,
#           +0.007998716016000,
#           +0.007998716016000,
#           +0.007998716016000,
#           +0.003849150907800,
#           +0.003849150907800,
#           +0.003849150907800,
#           -0.000316030248744,
#           -0.000316030248744,
#           -0.000316030248744,
#           +0.000875567150595,
#           +0.000875567150595,
#           +0.000875567150595,
#           +0.008232919594800,
#           +0.008232919594800,
#           +0.008232919594800,
#           +0.008232919594800,
#           +0.008232919594800,
#           +0.008232919594800,
#           +0.002419516770245,
#           +0.002419516770245,
#           +0.002419516770245,
#           +0.002419516770245,
#           +0.002419516770245,
#           +0.002419516770245,
#           +0.012902453267350,
#           +0.012902453267350,
#           +0.012902453267350,
#           +0.012902453267350,
#           +0.012902453267350,
#           +0.012902453267350,
#           +0.004235545527220,
#           +0.004235545527220,
#           +0.004235545527220,
#           +0.004235545527220,
#           +0.004235545527220,
#           +0.004235545527220,
#           +0.009177457053150,
#           +0.009177457053150,
#           +0.009177457053150,
#           +0.009177457053150,
#           +0.009177457053150,
#           +0.009177457053150,
#           +0.000352202338954,
#           +0.000352202338954,
#           +0.000352202338954,
#           +0.000352202338954,
#           +0.000352202338954,
#           +0.000352202338954,
#           +0.005056342463750,
#           +0.005056342463750,
#           +0.005056342463750,
#           +0.005056342463750,
#           +0.005056342463750,
#           +0.005056342463750,
#           +0.001786954692975,
#           +0.001786954692975,
#           +0.001786954692975,
#           +0.001786954692975,
#           +0.001786954692975,
#           +0.001786954692975]
#
#     r = @SVector F[+0.3333333333333,
#           +0.5009504643520,
#           -0.0019009287044,
#           +0.5009504643520,
#           +0.4882129579350,
#           +0.0235740841305,
#           +0.4882129579350,
#           +0.4551366869500,
#           +0.0897266360994,
#           +0.4551366869500,
#           +0.4019962593180,
#           +0.1960074813630,
#           +0.4019962593180,
#           +0.2558929097590,
#           +0.4882141804810,
#           +0.2558929097590,
#           +0.1764882559950,
#           +0.6470234880100,
#           +0.1764882559950,
#           +0.1041708553370,
#           +0.7916582893260,
#           +0.1041708553370,
#           +0.0530689638409,
#           +0.8938620723180,
#           +0.0530689638409,
#           +0.0416187151960,
#           +0.9167625696080,
#           +0.0416187151960,
#           +0.0115819214068,
#           +0.9768361571860,
#           +0.0115819214068,
#           +0.6064026461060,
#           +0.0487415836648,
#           +0.3448557702290,
#           +0.0487415836648,
#           +0.3448557702290,
#           +0.6064026461060,
#           +0.6158426144570,
#           +0.0063141159486,
#           +0.3778432695950,
#           +0.0063141159486,
#           +0.3778432695950,
#           +0.6158426144570,
#           +0.5590480003900,
#           +0.1343165205470,
#           +0.3066354790620,
#           +0.1343165205470,
#           +0.3066354790620,
#           +0.5590480003900,
#           +0.7366067432630,
#           +0.0139738939624,
#           +0.2494193627750,
#           +0.0139738939624,
#           +0.2494193627750,
#           +0.7366067432630,
#           +0.7116751422870,
#           +0.0755491329098,
#           +0.2127757248030,
#           +0.0755491329098,
#           +0.2127757248030,
#           +0.7116751422870,
#           +0.8614027171550,
#           -0.0083681532082,
#           +0.1469654360530,
#           -0.0083681532082,
#           +0.1469654360530,
#           +0.8614027171550,
#           +0.8355869579120,
#           +0.0266860632587,
#           +0.1377269788290,
#           +0.0266860632587,
#           +0.1377269788290,
#           +0.8355869579120,
#           +0.9297561715570,
#           +0.0105477192941,
#           +0.0596961091490,
#           +0.0105477192941,
#           +0.0596961091490,
#           +0.9297561715570]
#
#     s = @SVector F[ +0.3333333333333,
#            +0.5009504643520,
#            +0.5009504643520,
#            -0.0019009287044,
#            +0.4882129579350,
#            +0.4882129579350,
#            +0.0235740841305,
#            +0.4551366869500,
#            +0.4551366869500,
#            +0.0897266360994,
#            +0.4019962593180,
#            +0.4019962593180,
#            +0.1960074813630,
#            +0.2558929097590,
#            +0.2558929097590,
#            +0.4882141804810,
#            +0.1764882559950,
#            +0.1764882559950,
#            +0.6470234880100,
#            +0.1041708553370,
#            +0.1041708553370,
#            +0.7916582893260,
#            +0.0530689638409,
#            +0.0530689638409,
#            +0.8938620723180,
#            +0.0416187151960,
#            +0.0416187151960,
#            +0.9167625696080,
#            +0.0115819214068,
#            +0.0115819214068,
#            +0.9768361571860,
#            +0.3448557702290,
#            +0.6064026461060,
#            +0.0487415836648,
#            +0.3448557702290,
#            +0.6064026461060,
#            +0.0487415836648,
#            +0.3778432695950,
#            +0.6158426144570,
#            +0.0063141159486,
#            +0.3778432695950,
#            +0.6158426144570,
#            +0.0063141159486,
#            +0.3066354790620,
#            +0.5590480003900,
#            +0.1343165205470,
#            +0.3066354790620,
#            +0.5590480003900,
#            +0.1343165205470,
#            +0.2494193627750,
#            +0.7366067432630,
#            +0.0139738939624,
#            +0.2494193627750,
#            +0.7366067432630,
#            +0.0139738939624,
#            +0.2127757248030,
#            +0.7116751422870,
#            +0.0755491329098,
#            +0.2127757248030,
#            +0.7116751422870,
#            +0.0755491329098,
#            +0.1469654360530,
#            +0.8614027171550,
#            -0.0083681532082,
#            +0.1469654360530,
#            +0.8614027171550,
#            -0.0083681532082,
#            +0.1377269788290,
#            +0.8355869579120,
#            +0.0266860632587,
#            +0.1377269788290,
#            +0.8355869579120,
#            +0.0266860632587,
#            +0.0596961091490,
#            +0.9297561715570,
#            +0.0105477192941,
#            +0.0596961091490,
#            +0.9297561715570,
#            +0.0105477192941]
#     return w, r, s
# end


"""
    chord_length(array, phase[; directions,] nbins = 10)

Calculate chord length correlation function for one-, two- or
three-dimensional multiphase system.

Cord length function `p(x)` equals to probability of finding a chord
whose length is in the range `[x, x+δx]` and which lies entirely in the
phase `phase`. A chord is a line segment which touches the boundary of
a same-phase cluster with its ends.

This implementation bins chord lengths into `nbins` bins and returns
normalized histogram on collected data and the mean chord length in a
tuple.

# Examples
```jldoctest
julia> chord_length([1, 0, 0, 0, 0, 1], 0)
(StatsBase.Histogram{Float64,1,Tuple{StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}}}
edges:
  4.0:1.0:5.0
weights: [1.0]
closed: left
isdensity: false, 4.0)
```

For a list of possible dimensions, see also: [`direction1Dp`](@ref),
[`direction2Dp`](@ref), [`direction3Dp`](@ref).
"""
function chord_length(array      :: AbstractArray,
                      phase;
                      directions :: Vector{Symbol} = array |> default_directions,
                      nbins      :: Integer       = 10)
    # Select needed phase by applying indicator function to array
    ph = map(x -> x == phase, array)

    # Find a new "bogus" space for the edge
    edge_phase = maximum(array) + 1

    # Arary of chord lengths
    lengths = Vector{Int}(undef, 0)

    # Poor man's edge detection
    dist = ph |> feature_transform |> distance_transform
    edge = edge_phase * map(x -> x == 1.0, dist)

    # Combine the edge with the original picture
    combo = min.(edge + array, edge_phase)
    for direction in directions
        slicer = slice_generators(combo, false, Val(direction))
        for slice in slicer
            len = 0
            startonedge = false
            for x in slice
                # Edge found
                if x == edge_phase
                    len > 1 && startonedge && push!(lengths, len - 1)
                    len = 0
                    startonedge = true
                # Not our phase
                elseif x != phase
                    startonedge = false
                end
                len += 1
            end
        end
    end

    hist = fit(Histogram, lengths; nbins = nbins)
    mean_len = sum(lengths) ./ length(lengths)
    return normalize(hist; mode = :probability), mean_len
end


import NLsolve

export CollocationResidual, solve_collocation

"""
Return an object that, when given parameters `θ`, construct a
parametric function using that an the family `fam`, and evaluates
`residual(model, f, x)` at the collocation points `x` of
`fam`. The two-argument version puts the residuals in the second
argument (should be a vector), while the first one returns them.

NOTE: Useful for passing to a solver such as `NLsolve.nlsolve`.
"""
immutable CollocationResidual{Tmodel, Tfam <: ParametricFamily, Txs, Tresidual} <: Function
    model::Tmodel
    fam::Tfam
    xs::Txs
    residual::Tresidual
    function CollocationResidual(model, fam, xs, residual)
        @argcheck degf(fam) == length(xs)
        new(model, fam, xs, residual)
    end
end

function CollocationResidual{Tmodel, Tfam, Txs, Tresidual}(model::Tmodel, fam::Tfam,
                                                           xs::Txs, residual::Tresidual)
    CollocationResidual{Tmodel, Tfam, Txs, Tresidual}(model, fam, xs, residual)
end

function CollocationResidual(model, fam, residual)
    CollocationResidual(model, fam, points(fam), residual)
end

@forward CollocationResidual.fam degf

function (cr::CollocationResidual)(θ, r)
    f = ParametricFunction(cr.fam, θ)
    r .= cr.residual.([cr.model], [f], cr.xs)
end

(cr::CollocationResidual)(θ) = cr(θ, Vector{eltype(cr.xs)}(degf(cr)))

function solve_collocation(cr::CollocationResidual, f₀; autodiff = true, options...)
    θ₀ = fit(cr.fam, f₀)
    o = NLsolve.nlsolve(cr, θ₀; autodiff = autodiff, options...)
    NLsolve.converged(o) || warn("optimization did not converge")
    ParametricFunction(cr.fam, o.zero), o
end


"""
    non_dominated_sort(population)

Return a vector of integers `r` containing in `r[i]` the rank for `population[i]`.
"""
function non_dominated_sort(pop)
    n = length(pop)

    dom_list = [ Int[] for i in 1:n ]
    rank = zeros(Int, n)
    dom_count = zeros(Int, n)

    for i in 1:n
        for j in i+1:n
            comparison = compare(pop[i], pop[j])
            if comparison == 1 #is_better(pop[i], pop[j])
                push!(dom_list[i], j)
                dom_count[j] += 1
            elseif comparison == 2 #is_better(pop[j], pop[i])
                push!(dom_list[j], i)
                dom_count[i] += 1
            end
        end
        if dom_count[i] == 0
            rank[i] = 1
        end
    end

    k = UInt16(2)
    while any(==(k-one(UInt16)), (rank[p] for p in 1:n)) #ugly workaround for #15276
        for p in 1:n
            if rank[p] == k-one(UInt16)
                for q in dom_list[p]
                    dom_count[q] -= one(UInt16)
                    if dom_count[q] == zero(UInt16)
                        rank[q] = k
                    end
                end
            end
        end
        k += one(UInt16)
    end

    return rank
end

"""
    get_fronts(population, computed_ranks = true)

Return each sub-front in an array. If `computed_ranks == true`, this method assumes that
`fast_non_dominated_sort!(population)` has been called before.
"""
function get_fronts(population, computed_ranks = true)
    if computed_ranks
        ranks = [s.rank for s in population]
    else
        ranks = non_dominated_sort(population)
    end

    fronts = [Int[] for i in eachindex(unique(ranks))]

    for (i, r) in enumerate(ranks)
        push!(fronts[r], i)
    end

    return fronts
end

"""
    fast_non_dominated_sort!(population)

Sort population using the fast non dominated sorting algorithm. Note that `s.rank` is
updated for each solution `s ∈ population`.
"""
function fast_non_dominated_sort!(pop)
    rank = non_dominated_sort(pop)

    for i in 1:length(pop)
        pop[i].rank = rank[i]
    end

    sort!(pop, by = s -> s.rank, alg = Base.Sort.QuickSort)
    pop
end

"""
    get_non_dominated_solutions_perm(population)

Return a vector of integers `v` such that `population[v]` are the non dominated
solutions contained in `population`.
"""
function get_non_dominated_solutions_perm(population)
    ids = Int[1]
    n = length(population)

    for i in 2:n
        j = 1
        while j <= length(ids)
            jj = ids[j]
            relation = compare(population[i], population[jj])

            if relation == 2 # j dominates i
                break
            elseif relation == 1 # i dominates j
                deleteat!(ids, j)
                continue
            end

            j += 1
        end

        if j > length(ids)
            push!(ids, i)
        end

    end

    return ids
end

"""
    get_non_dominated_solutions(population)

Return the non dominated solutions contained in `population`.
"""
function get_non_dominated_solutions(population)
    return population[get_non_dominated_solutions_perm(population)]
end


using TexTables, DataStructures, DataFrames
using StatsModels, GLM, RDatasets

@testset "Linear Model Examples No Stars" begin
    # Check that this code runs without errors
    df = dataset("datasets", "attitude")
    # Compute summary stats for each variable
    cols = []
    for header in names(df)
        x = df[header]
        stats = TableCol(header,
                         "N"     => length(x),
                         "Mean"  => mean(x),
                         "Std"   => std(x),
                         "Min"   => minimum(x),
                         "Max"   => maximum(x))
        push!(cols, stats)
    end
    tab = hcat(cols...)
    m1 = lm(@formula( Rating ~ 1 + Raises ), df)
    m2 = lm(@formula( Rating ~ 1 + Raises + Learning), df)
    m3 = lm(@formula( Rating ~ 1 + Raises + Learning + Privileges), df)
    m4 = lm(@formula( Rating ~ 1 + Raises + Learning + Privileges
                                 + Complaints), df)
    m5 = lm(@formula( Rating ~ 1 + Raises + Learning + Privileges
                                 + Complaints + Critical), df)
    reg_table = hcat(TableCol("(1)", m1),
                     TableCol("(2)", m2),
                     TableCol("(3)", m3),
                     TableCol("(4)", m4),
                     TableCol("(5)", m5))
    compare_file(2, to_ascii(reg_table, star=false))
    compare_file(3, to_tex(reg_table, star=false))

    # Check that regtable interface works
    reg_table_b = regtable(m1, m2, m3, m4, m5)
    compare_file(2, to_ascii(reg_table_b, star=false))
    compare_file(3, to_tex(reg_table_b, star=false))

    group1 = hcat(  TableCol("(1)", m1),
                    TableCol("(2)", m2),
                    TableCol("(3)", m3))
    compare_file(4, to_ascii(group1, star=false))
    compare_file(5, to_tex(group1, star=false))

    group2 = hcat(  TableCol("(1)", m4),
                    TableCol("(2)", m5))
    compare_file(6, to_ascii(group2, star=false))
    compare_file(7, to_tex(group2, star=false))

    grouped_table = join_table( "Group 1"=>group1,
                                "Group 2"=>group2)
    compare_file(8, to_ascii(grouped_table, star=false))
    compare_file(9, to_tex(grouped_table, star=false))

    # Check that regtable interface works
    grouped_table_b = regtable("Group 1"=>regtable(m1, m2, m3),
                               "Group 2"=>regtable(m4, m5))
    compare_file(8, to_ascii(grouped_table_b, star=false))
    compare_file(9, to_tex(grouped_table_b, star=false))

    grouped_table_c = regtable("Group 1"=>(m1, m2, m3),
                               "Group 2"=>(m4, m5))
    compare_file(32, to_ascii(grouped_table_c, star=false))
    compare_file(33, to_tex(grouped_table_c, star=false))

    # Compare against the original table for group1 again (to make sure
    # that all the join_methods are non-mutating)
    compare_file(4, to_ascii(group1, star=false))
    compare_file(5, to_tex(group1, star=false))
end

@testset "Linear Models With Stars" begin
    # Check that this code runs without errors
    df = dataset("datasets", "attitude")
    # Compute summary stats for each variable
    cols = []
    for header in names(df)
        x = df[header]
        stats = TableCol(header,
                         "N"     => length(x),
                         "Mean"  => mean(x),
                         "Std"   => std(x),
                         "Min"   => minimum(x),
                         "Max"   => maximum(x))
        push!(cols, stats)
    end
    tab = hcat(cols...)
    m1 = lm(@formula( Rating ~ 1 + Raises ), df)
    m2 = lm(@formula( Rating ~ 1 + Raises + Learning), df)
    m3 = lm(@formula( Rating ~ 1 + Raises + Learning + Privileges), df)
    m4 = lm(@formula( Rating ~ 1 + Raises + Learning + Privileges
                                 + Complaints), df)
    m5 = lm(@formula( Rating ~ 1 + Raises + Learning + Privileges
                                 + Complaints + Critical), df)
    reg_table = hcat(TableCol("(1)", m1),
                     TableCol("(2)", m2),
                     TableCol("(3)", m3),
                     TableCol("(4)", m4),
                     TableCol("(5)", m5))
    compare_file(22, to_ascii(reg_table, star=true))
    compare_file(23, to_tex(reg_table, star=true))

    # Check that regtable interface works
    reg_table_b = regtable(m1, m2, m3, m4, m5)
    compare_file(22, to_ascii(reg_table_b, star=true))
    compare_file(23, to_tex(reg_table_b, star=true))

    group1 = hcat(  TableCol("(1)", m1),
                    TableCol("(2)", m2),
                    TableCol("(3)", m3))
    compare_file(24, to_ascii(group1, star=true))
    compare_file(25, to_tex(group1, star=true))

    group2 = hcat(  TableCol("(1)", m4),
                    TableCol("(2)", m5))
    compare_file(26, to_ascii(group2, star=true))
    compare_file(27, to_tex(group2, star=true))

    grouped_table = join_table( "Group 1"=>group1,
                                "Group 2"=>group2)
    compare_file(28, to_ascii(grouped_table, star=true))
    compare_file(29, to_tex(grouped_table, star=true))

    # Check that regtable interface works
    grouped_table_b = regtable("Group 1"=>regtable(m1, m2, m3),
                               "Group 2"=>regtable(m4, m5))
    compare_file(28, to_ascii(grouped_table_b, star=true))
    compare_file(29, to_tex(grouped_table_b, star=true))

    grouped_table_c = regtable("Group 1"=>(m1, m2, m3),
                               "Group 2"=>(m4, m5))
    compare_file(30, to_ascii(grouped_table_c, star=true))
    compare_file(31, to_tex(grouped_table_c, star=true))

    # Compare against the original table for group1 again (to make sure
    # that all the join_methods are non-mutating)
    compare_file(24, to_ascii(group1, star=true))
    compare_file(25, to_tex(group1, star=true))
end

@testset "Summary Tables" begin
    iris = dataset("datasets", "iris")

    sum1 = summarize(iris)
    compare_file(10, to_ascii(sum1))
    compare_file(11, to_tex(sum1))

    sum2 = summarize(iris, detail=true)
    compare_file(12,to_ascii(sum2))
    compare_file(13, to_tex(sum2))

    sum3 = summarize_by(iris, :Species)
    compare_file(14, to_ascii(sum3))
    compare_file(15, to_tex(sum3))

    sum4 = summarize_by(iris, :Species, detail=true)
    compare_file(16, to_ascii(sum4))
    compare_file(17, to_tex(sum4))

    sum5 = tabulate(iris, :Species)
    compare_file(18, to_ascii(sum5))
    compare_file(19, to_tex(sum5))

    sum6 = tabulate(iris, :PetalWidth)
    compare_file(20, to_ascii(sum6))
    compare_file(21, to_tex(sum6))
end


function load(fs::Stream{format"IFS"}, MeshType = GLNormalMesh)
    io = stream(fs)
    function str()
        n = read(io, UInt32)
        String(read(io, UInt8, n))
    end
    name = str() # just skip name for now
    vertices = str()
    if vertices != "VERTICES\0"
        error("$(filename(fs)) does not seem to be of format IFS")
    end
    nverts = read(io, UInt32)
    verts_float = read(io, Float32, nverts * 3)
    verts = reinterpret(Point3f0, verts_float)
    tris = str()
    if tris != "TRIANGLES\0"
        error("$(filename(fs)) does not seem to be of format IFS")
    end
    nfaces = read(io, UInt32)
    faces_int = read(io, UInt32, nfaces * 3)
    faces = reinterpret(GLTriangle, faces_int)
    MeshType(vertices = verts, faces = faces)
end

function save(fs::Stream{format"IFS"}, msh::AbstractMesh; meshname = "mesh")
    io = stream(fs)
    function write0str(s)
        s0 = s * "\0"
        write(io, UInt32(length(s0)))
        write(io, s0)
    end
    vts = decompose(Point3f0, msh)
    fcs = decompose(GLTriangle, msh)

    # write the header
    write0str("IFS")
    write(io, 1f0)
    write0str(meshname)

    write0str("VERTICES")
    write(io, UInt32(length(vts)))
    write(io, reinterpret(Float32, vts))

    write0str("TRIANGLES")
    write(io, UInt32(length(fcs)))
    write(io, reinterpret(UInt32, fcs))
end


include("ProductDomain.jl")

## boundary

boundary(d::ProductDomain{Tuple{A,B}}) where {A<:IntervalOrSegment,B<:IntervalOrSegment} =
    PiecewiseSegment([Vec(leftendpoint(factor(d,1)),leftendpoint(factor(d,2))),
                      Vec(rightendpoint(factor(d,1)),leftendpoint(factor(d,2))),
                      Vec(rightendpoint(factor(d,1)),rightendpoint(factor(d,2))),
                      Vec(leftendpoint(factor(d,1)),rightendpoint(factor(d,2))),
                      Vec(leftendpoint(factor(d,1)),leftendpoint(factor(d,2)))])
boundary(d::ProductDomain{Tuple{A,B}}) where {A<:IntervalOrSegment,B<:PeriodicSegment} =
    UnionDomain((PeriodicSegment(Vec(rightendpoint(factor(d,1)),leftendpoint(factor(d,2))),Vec(rightendpoint(factor(d,1)),rightendpoint(factor(d,2)))),
        PeriodicSegment(Vec(leftendpoint(factor(d,1)),rightendpoint(factor(d,2))),Vec(leftendpoint(factor(d,1)),leftendpoint(factor(d,2))))))
boundary(d::ProductDomain{Tuple{A,B}}) where {A<:PeriodicSegment,B<:IntervalOrSegment} =
    UnionDomain((PeriodicSegment(Vec(leftendpoint(factor(d,1)),leftendpoint(factor(d,2))),Vec(rightendpoint(factor(d,1)),leftendpoint(factor(d,2)))),
        PeriodicSegment(Vec(rightendpoint(factor(d,1)),rightendpoint(factor(d,2))),Vec(leftendpoint(factor(d,1)),rightendpoint(factor(d,2))))))
boundary(d::ProductDomain{Tuple{A,B}}) where {A<:PeriodicSegment,B<:PeriodicSegment} = EmptyDomain()

## Union
function Base.join(p1::AbstractVector{IT},p2::AbstractVector{IT}) where IT<:IntervalOrSegment
    for k=length(p1):-1:1,j=length(p2):-1:1
        if p1[k]==reverse(p2[j])
            deleteat!(p1,k)
            deleteat!(p2,j)
            break
        end
    end
    [p1;p2]
end

function boundary(d::UnionDomain{Tuple{PD1,PD2}}) where {PD1<:ProductDomain,PD2<:ProductDomain}
    bnd=map(d->components(∂(d)),d.domains)
    PiecewiseSegment(reduce(join,bnd))
end


"""
    QuasiSteady

Two-dimensional aerodynamic model based on quasi-steady thin airfoil theory with parameters
``a, b, a_0, \\alpha_0, c_{d0}, c_{m0}``.
"""
struct QuasiSteady end

"""
    QuasiSteady()

Initialize a model of type [`QuasiSteady`](@ref)
"""
QuasiSteady()

# --- Submodel Creation --- #

function Submodel(::QuasiSteady)

    # number of parameters (use Val(N) to use inferrable dimensions)
    np = Val(6)

    # convenience function for setting parameters
    setparam = quasisteady_set_parameters!

    # convenience function for separating parameters
    sepparam = quasisteady_separate_parameters

    # model definition
    return Submodel(np; setparam, sepparam)
end

# --- Internal Methods --- #

# convenience function for defining this model's parameter vector
function quasisteady_set_parameters!(p; a, b, a0, alpha0, cd0, cm0)
    p[1] = a
    p[2] = b
    p[3] = a0
    p[4] = alpha0
    p[5] = cd0
    p[6] = cm0
    return p
end

# convenience function for separating this model's parameter vector
quasisteady_separate_parameters(p) = (a=p[1], b=p[2], a0=p[3], alpha0=p[4], cd0=p[5], cm0=p[6])

# aerodynamic loads per unit span
function quasisteady_loads(a, b, ρ, c, a0, α0, cd0, cm0, u, v, ω, vdot, ωdot)
    # circulatory load factor
    tmp1 = a0*ρ*u*b
    # non-circulatory load factor
    tmp2 = pi*ρ*b^3
    # constant based on geometry
    d = b/2 - a*b
    # Velocity Magnitude (squared)
    V2 = u^2 + v^2
    # Mach Number (squared)
    M2 = V2/c^2
    # Prandtl-Glauert correction factor
    beta = sqrt(1 - min(0.99, M2))
    # normal force at reference point
    N = tmp1*(v + d*ω - u*α0) + tmp2*(vdot/b + u/b*ω - a*ωdot)
    # axial force at reference point
    A = -a0*ρ*b*(v + d*ω - u*α0)^2
    # moment at reference point
    M = -tmp2*(vdot/2 + u*ω + (b/8 - a*b/2)*ωdot) + 2*ρ*b^2*u^2*cm0 + (b/2 + a*b)*N
    # apply compressibility correction
    N = N / beta
    A = A / beta
    M = M / beta
    # add skin friction drag
    A += ρ*b*u^2*cd0
    # return loads
    return SVector(N, A, M)
end


# Tests for the ways various types interface with eachother

# Test conversions
@test frequency(1.0m)*m ≈ c0
@test frequency(1nm)*nm ≈ c0
@test frequency(wavelength(100THz)) ≈ 100THz
@test wavelength(frequency(100nm)) ≈ 100nm
@test wavelength(100nm) ≈ wavelength(100e-9m)

attr = OpticalAttr(1.0, "Attribute")
@test attr(1.0THz) == attr(500nm)
@test attr(100THz) == attr(200THz)
@test attr(frequency(200nm)) == attr(frequency(150THz))
@test NonlinearOptics.der(attr, 100nm) == 0.0
@test NonlinearOptics.der(attr, 100nm; order=0) == 1.0
@test NonlinearOptics.der(attr, 1.0nm; order=3) == 0.0
@test NonlinearOptics.der(attr, 100THz; order=3) == 0.0

frequencies = linspace(100THz, 200THz, 20)
unitful_attr = frequencies./THz.*m
unitful_attr = OpticalAttr(frequencies, unitful_attr, "Attribute [m]")
@test unitful_attr(150THz) ≈ 150m
@test unitful_attr(frequency(c0/150THz)) ≈ 150m
@test unitful_attr(frequency(1998.6163866nm)) ≈ 150m
@test unitful_attr(1998.6163866nm) ≈ 150m
@test NonlinearOptics.der(unitful_attr, 150THz, order=0) == unitful_attr(150THz)
@test NonlinearOptics.der(unitful_attr, 150THz, order=1) ≈ 1m/THz
@test isapprox(NonlinearOptics.der(unitful_attr, 150THz, order=2), 0.0m/THz^2; atol=5e-15m/THz^2)


# Extend MathOptInterface Test for Knitro.
#
# This file is largely adapted from :
# MathOptInterface/src/Tests/contconic.jl
# https://github.com/JuliaOpt/MathOptInterface.jl/blob/master/src/Test/contconic.jl
#
# LICENSE:
# MIT "expat" license
# Copyright (c) 2017: Miles Lubin and contributors Copyright (c) 2017: Google Inc.

using MathOptInterface

const MOI = MathOptInterface

const CONIC_OPTIMIZER = KNITRO.Optimizer(outlev=0, presolve=0, opttol=1e-8)
const CONIC_BRIDGED = MOIB.full_bridge_optimizer(CONIC_OPTIMIZER, Float64)

@testset "MOI SOCP 1" begin
    # Derived from MOI's problem SOC1
    model = CONIC_BRIDGED
    atol = 1e-4
    rtol = 1e-4

    # Problem SOC1
    # max 0x + 1y + 1z
    #  st  x            == 1
    #      x >= ||(y,z)||

    @test MOIU.supports_default_copy_to(model, #=copy_names=# false)
    @test MOI.supports(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}())
    @test MOI.supports(model, MOI.ObjectiveSense())
    @test MOI.supports_constraint(model, MOI.VectorAffineFunction{Float64}, MOI.Zeros)
    @test MOI.supports_constraint(model, MOI.VectorOfVariables,MOI.SecondOrderCone)

    MOI.empty!(model)
    @test MOI.is_empty(model)

    x,y,z = MOI.add_variables(model, 3)

    MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(),
            MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0,1.0], [y,z]), 0.0))
    MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)

    ceq = MOI.add_constraint(model,
                             MOI.VectorAffineFunction([MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, x))], [-1.0]), MOI.Zeros(1))
    vov = MOI.VectorOfVariables([x,y,z])
    csoc = MOI.add_constraint(model,
                              MOI.VectorAffineFunction{Float64}(vov), MOI.SecondOrderCone(3))

    @test MOI.get(model, MOI.NumberOfConstraints{MOI.VectorAffineFunction{Float64},MOI.Zeros}()) == 1
    @test MOI.get(model, MOI.NumberOfConstraints{MOI.VectorAffineFunction{Float64},MOI.SecondOrderCone}()) == 1
    #= loc = MOI.get(model, MOI.ListOfConstraints()) =#
    #= @test length(loc) == 2 =#
    #= @test (MOI.VectorAffineFunction{Float64},MOI.Zeros) in loc =#
    #= @test (MOI.VectorAffineFunction{Float64},MOI.SecondOrderCone) in loc =#

    @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED

    MOI.optimize!(model)

    @test MOI.get(model, MOI.TerminationStatus()) == MOI.LOCALLY_SOLVED

    @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
    @test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT

    @test MOI.get(model, MOI.ObjectiveValue()) ≈ √2 atol=atol rtol=rtol

    @test MOI.get(model, MOI.VariablePrimal(), x) ≈ 1 atol=atol rtol=rtol
    @test MOI.get(model, MOI.VariablePrimal(), y) ≈ 1/√2 atol=atol rtol=rtol
    @test MOI.get(model, MOI.VariablePrimal(), z) ≈ 1/√2 atol=atol rtol=rtol

    @test MOI.get(model, MOI.ConstraintPrimal(), ceq) ≈ [0.] atol=atol rtol=rtol
    @test MOI.get(model, MOI.ConstraintPrimal(), csoc) ≈ [1., 1/√2, 1/√2] atol=atol rtol=rtol

    @test MOI.get(model, MOI.ConstraintDual(), ceq) ≈ [-√2] atol=atol rtol=rtol
    @test MOI.get(model, MOI.ConstraintDual(), csoc) ≈ [√2, -1.0, -1.0] atol=atol rtol=rtol
end

MOI.empty!(CONIC_BRIDGED)

@testset "MOI SOCP 2" begin
    model = CONIC_BRIDGED
    atol = 1e-4
    rtol = 1e-4
    # Problem SOC2
    # min  x
    # s.t. y ≥ 1/√2
    #      x² + y² ≤ 1
    # in conic form:
    # min  x
    # s.t.  -1/√2 + y ∈ R₊
    #        1 - t ∈ {0}
    #      (t,x,y) ∈ SOC₃

    @test MOIU.supports_default_copy_to(model, #=copy_names=# false)
    @test MOI.supports(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}())
    @test MOI.supports(model, MOI.ObjectiveSense())
    @test MOI.supports_constraint(model, MOI.VectorAffineFunction{Float64}, MOI.Zeros)
    @test MOI.supports_constraint(model, MOI.VectorAffineFunction{Float64}, MOI.Nonpositives)
    @test MOI.supports_constraint(model, MOI.VectorAffineFunction{Float64},MOI.SecondOrderCone)

    MOI.empty!(model)
    @test MOI.is_empty(model)

    x,y,t = MOI.add_variables(model, 3)

    MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 0.0))
    MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)

    cnon = MOI.add_constraint(model, MOI.VectorAffineFunction([MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(-1.0, y))], [1/√2]), MOI.Nonpositives(1))
    ceq = MOI.add_constraint(model, MOI.VectorAffineFunction([MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(-1.0, t))], [1.0]), MOI.Zeros(1))
    csoc = MOI.add_constraint(model, MOI.VectorAffineFunction(MOI.VectorAffineTerm.([1,2,3], MOI.ScalarAffineTerm.(1.0, [t,x,y])), zeros(3)), MOI.SecondOrderCone(3))

    @test MOI.get(model, MOI.NumberOfConstraints{MOI.VectorAffineFunction{Float64}, MOI.Nonpositives}()) == 1
    @test MOI.get(model, MOI.NumberOfConstraints{MOI.VectorAffineFunction{Float64},MOI.Zeros}()) == 1
    @test MOI.get(model, MOI.NumberOfConstraints{MOI.VectorAffineFunction{Float64},MOI.SecondOrderCone}()) == 1

    @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED

    MOI.optimize!(model)

    @test MOI.get(model, MOI.TerminationStatus()) == MOI.LOCALLY_SOLVED

    @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
    @test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT

    @test MOI.get(model, MOI.ObjectiveValue()) ≈ -1/√2 atol=atol rtol=rtol

    @test MOI.get(model, MOI.VariablePrimal(), x) ≈ -1/√2 atol=atol rtol=rtol
    @test MOI.get(model, MOI.VariablePrimal(), y) ≈ 1/√2 atol=atol rtol=rtol
    @test MOI.get(model, MOI.VariablePrimal(), t) ≈ 1 atol=atol rtol=rtol

    # Getter of Nonnegatives constraints is currently broken!!
    @test_broken MOI.get(model, MOI.ConstraintPrimal(), cnon) ≈ [0.0] atol=atol rtol=rtol
    @test MOI.get(model, MOI.ConstraintPrimal(), ceq) ≈ [0.0] atol=atol rtol=rtol
    @test MOI.get(model, MOI.ConstraintPrimal(), csoc) ≈ [1., -1/√2, 1/√2] atol=atol rtol=rtol

    @test MOI.get(model, MOI.ConstraintDual(), cnon) ≈ [-1.0] atol=atol rtol=rtol
    @test MOI.get(model, MOI.ConstraintDual(), ceq) ≈ [√2] atol=atol rtol=rtol
    @test MOI.get(model, MOI.ConstraintDual(), csoc) ≈ [√2, 1.0, -1.0] atol=atol rtol=rtol
end


@testset "kmers" begin
    @testset "kmer_count" begin
        input = "AAAAAC"
        bin = kmer_count(input, 5)
        @test bin[1] == 1
        @test bin[2] == 1
    end

    @testset "corrected_kmer_dist" begin
        input1 = [5.0, 5.0, 3.0, 2.0]
        input2 = [5.0, 6.0, 6.0, 2.0]
        k = 2
        ret = corrected_kmer_dist(input1, input2; k = 2)

        @test ret == sqeuclidean(input1, input2)/ (k*(sum(input1) + sum(input2)))
    end

end


using ManifoldsBase
using Test
using ManifoldsBase:
    combine_allocation_promotion_functions, allocation_promotion_function, ℝ

struct AllocManifold{𝔽} <: Manifold{𝔽} end
AllocManifold() = AllocManifold{ℝ}()

function ManifoldsBase.exp!(::AllocManifold, v, x, y)
    v[1] .= x[1] .+ y[1]
    v[2] .= x[2] .+ y[2]
    v[3] .= x[3] .+ y[3]
    return v
end

@testset "Allocation" begin
    a = [[1.0], [2.0], [3.0]]
    b = [[2.0], [3.0], [-3.0]]
    M = AllocManifold()
    v = exp(M, a, b)
    @test v ≈ [[3.0], [5.0], [0.0]]
    @test allocate(([1.0], [2.0])) isa Tuple{Vector{Float64},Vector{Float64}}
    @test allocate(([1.0], [2.0]), Int) isa Tuple{Vector{Int},Vector{Int}}
    @test allocate([[1.0], [2.0]]) isa Vector{Vector{Float64}}
    @test allocate([[1.0], [2.0]], Int) isa Vector{Vector{Int}}

    a1 = allocate([1], 2, 3)
    @test a1 isa Matrix{Int}
    @test size(a1) == (2, 3)
    a2 = allocate([1], (2, 3))
    @test a2 isa Matrix{Int}
    @test size(a2) == (2, 3)
    a3 = allocate([1], Float64, 2, 3)
    @test a3 isa Matrix{Float64}
    @test size(a3) == (2, 3)
    a4 = allocate([1], Float64, (2, 3))
    @test a4 isa Matrix{Float64}
    @test size(a4) == (2, 3)

    @test allocation_promotion_function(M, exp, (a, b)) === identity
    @test combine_allocation_promotion_functions(identity, identity) === identity
    @test combine_allocation_promotion_functions(identity, complex) === complex
    @test combine_allocation_promotion_functions(complex, identity) === complex
    @test combine_allocation_promotion_functions(complex, complex) === complex

    @test number_eltype([2.0]) === Float64
    @test number_eltype([[2.0], [3]]) === Float64
    @test number_eltype([[2], [3.0]]) === Float64
    @test number_eltype([[2], [3]]) === Int
    @test number_eltype(([2.0], [3])) === Float64
    @test number_eltype(([2], [3.0])) === Float64
    @test number_eltype(([2], [3])) === Int
    @test number_eltype(Any[[2.0], [3.0]]) === Float64
end


"""
	degree(s,node)

Return the degree of the given node in the givn stream.

```math
d \\left(v \\right)= \\left| \\mathcal{N} \\left(v\\right) \\right|
```

Where

```math
\\mathcal{N} \\left(v\\right)=\\left\\{ \\left(t,u\\right),\\ \\left(t,uv \\right) \\in E}
```

is the neighborhood of node v.
Note: If the stream has a duration of 0, this function returns 0.

### Reference
- Matthieu Latapy, Tiphaine Viard and Clémence Magnien Social
  Networks Analysis and Mining, 8: 61, 2018. "Stream Graphs and
  Link Streams for the Modeling of Interactions over Time".
  [(arXiv)](https://arxiv.org/pdf/1710.04073.pdf)
"""
function degree(s::AbstractUndirectedStream,node::AbstractString)
	duration(s)!=0 ? duration(neighborhood(s,node))/duration(s) : 0.0
end

"""
	degree(s,node,tc)

Return the degree of the given node in the given stream using a TimeCursor.
Note: Not implemented yet...
"""
function degree(s::AbstractUndirectedStream,node::AbstractString,tc::TimeCursor)
	throw("Not Implemented")
end

"""
	degree(s,node,t)

Return the instantaneous degree of the given node at the given time in the given stream.
```math
d_t \\left(v \\right)= \\left| \\mathcal{N}_t \\left(v\\right) \\right|
```

Where

```math
\\mathcal{N}_t\\left(v\\right)=\\left\\{ u,\\ \\left(t,uv \\right) \\in E}
```

is the instantaneous neighborhood of node v.

### Reference
- Matthieu Latapy, Tiphaine Viard and Clémence Magnien Social
  Networks Analysis and Mining, 8: 61, 2018. "Stream Graphs and
  Link Streams for the Modeling of Interactions over Time".
  [(arXiv)](https://arxiv.org/pdf/1710.04073.pdf)
"""
function degree(s::AbstractUndirectedStream,node::AbstractString,t::Float64)
	length(neighborhood(s,node,t))
end

"""
	degree(s,t)

Return the degree at t of the stream s.
```math
d\\left(t\\right) = \\sum_v \\frac{d_t \\left(v \\right)}{\\left|V\\right||}
```
Note: If the stream has no node, this function returns 0.

### Reference
- Matthieu Latapy, Tiphaine Viard and Clémence Magnien Social
  Networks Analysis and Mining, 8: 61, 2018. "Stream Graphs and
  Link Streams for the Modeling of Interactions over Time".
  [(arXiv)](https://arxiv.org/pdf/1710.04073.pdf)
"""
function degree(s::AbstractUndirectedStream,t::Float64)
	length(s.V) != 0 ? 1.0/length(s.V)*sum([degree(s,v,t) for v in s.V]) : 0.0
end

"""
	degree(s)

Return the degree of the stream s.
```math
d\\left(S\\right)= \\sum_v \\frac{1}{\\left|V\\right|}d\\left(v\\right)
```
Note: If the stream has no node, this function returns 0.

### Reference
- Matthieu Latapy, Tiphaine Viard and Clémence Magnien Social
  Networks Analysis and Mining, 8: 61, 2018. "Stream Graphs and
  Link Streams for the Modeling of Interactions over Time".
  [(arXiv)](https://arxiv.org/pdf/1710.04073.pdf)
"""
function degree(s::AbstractUndirectedStream)
	length(s.V) != 0 ? 1.0/length(s.V)*sum([degree(s,v) for v in s.V]) : 0.0
end

"""
	degree(s,tc)

Return the degree of stream s using a TimeCursor.
In this case, the degree is computed using:
```math
d\\left(S\\right)= \\int_t \\frac{1}{\\left|T\\right|}d\\left(t\\right) dt
```
Note: If the stream has no node or a duration of 0, this function returns 0.

### Reference
- Matthieu Latapy, Tiphaine Viard and Clémence Magnien Social
  Networks Analysis and Mining, 8: 61, 2018. "Stream Graphs and
  Link Streams for the Modeling of Interactions over Time".
  [(arXiv)](https://arxiv.org/pdf/1710.04073.pdf)
"""
function degree(s::AbstractUndirectedStream,tc::TimeCursor)
	((duration(s)==0) | (length(s.V)==0)) && 0.0
	start!(tc)
	δ=collect(values(degrees(tc.S)))
	d::Float64=sum(δ)*duration(tc.S)
	while tc.S.t1 < s.T.list[1][2]
        next!(tc)
        δ=collect(values(degrees(tc.S)))
        d+=sum(δ)*duration(tc.S)
    end
    1.0/(duration(s)*length(s.V))*d
end

"""
	average_node_degree(s)

Return the average node degree of the given stream graph.
```math
d\\left(V\\right)=\\frac{1}{n} \\sum_{v \\in V} n_v d\\left( v \\right)
```
Note: If card(W)=0, this function returns 0.

### Reference
- Matthieu Latapy, Tiphaine Viard and Clémence Magnien Social
  Networks Analysis and Mining, 8: 61, 2018. "Stream Graphs and
  Link Streams for the Modeling of Interactions over Time".
  [(arXiv)](https://arxiv.org/pdf/1710.04073.pdf)
"""
function average_node_degree(s::StreamGraph)
	duration(s.W)!=0 ? 1.0/duration(s.W)*sum([length(times(s,v))*degree(s,v) for v in s.V]) : 0.0
end

"""
	average_node_degree(ls)

Return the average node degree of the given link stream.
For link streams, the average node degree simplifies to:
```math
d\\left(L\\right) = \\frac{2 m}{n}
```
Note: If the link stream has no node, this function returns 0.

### Reference
- Matthieu Latapy, Tiphaine Viard and Clémence Magnien Social
  Networks Analysis and Mining, 8: 61, 2018. "Stream Graphs and
  Link Streams for the Modeling of Interactions over Time".
  [(arXiv)](https://arxiv.org/pdf/1710.04073.pdf)
"""
function average_node_degree(ls::LinkStream)
	number_of_nodes(ls)!=0 ? 2.0*number_of_links(ls)/number_of_nodes(ls) : 0.0
end

"""
	average_time_degree(s)

Return the average time degree of the given stream.
```math
d\\left(T\\right)=\\int_t \\frac{\\left|V_t\\right|}{\\left|W\\right|} d\\left(t\\right) dt
```
Note: If the node duration is 0, this function returns 0.

Warning: This function is very inefficient, use `average_time_degree(s,tc)` if possible.

### Reference
- Matthieu Latapy, Tiphaine Viard and Clémence Magnien Social
  Networks Analysis and Mining, 8: 61, 2018. "Stream Graphs and
  Link Streams for the Modeling of Interactions over Time".
  [(arXiv)](https://arxiv.org/pdf/1710.04073.pdf)
"""
function average_time_degree(s::AbstractStream)
    k = node_duration(s)
    τ = times(s)
    k != 0 ? 1.0/k*sum([node_contribution(s,0.5*(t[2]+t[1]))*degree(s,0.5*(t[2]+t[1]))*(t[2]-t[1]) for t in zip(τ[1:end-1],τ[2:end])]) : 0.0
end

"""
	average_time_degree(s,tc)

Return the average time degree of the given stream using a TimeCursor.
"""
function average_time_degree(s::AbstractStream,tc::TimeCursor)
	length(s.V)==0 && 0.0
	dW=duration(s.W)
	dW==0 && 0.0
	start!(tc)
	δ=collect(values(degrees(tc.S)))
	d::Float64=sum(δ)/length(s.V)*number_of_nodes(tc)*duration(tc.S)
    while tc.S.t1 < s.T.list[1][2]
        next!(tc)
        δ=collect(values(degrees(tc.S)))
        d+=sum(δ)/length(s.V)*number_of_nodes(tc)*duration(tc.S)
    end
    1.0/dW*d
end

"""
	degrees(s)

Return the degree distribution of the graph corresponding to the given state.
"""
function degrees(S::State)
	l=String[]
	for ll in S.links
    	push!(l,ll[1])
    	push!(l,ll[2])
	end
	ul=unique(l)
	Dict([(i,count(x->x==i,l)) for i in ul])
end

"""
	average_degree(s)

Return the average degree of the graph corresponding to the given state.
"""
function average_degree(S::State)
	d=collect(values(degrees(S)))
	length(d)==0 && 0.0
	sum(d)/length(d)
end

"""
	degrees(tc)

Return the degree distribution of the cursor's current state.
"""
degrees(tc::TimeCursor)=degrees(tc.S)

"""
	average_degree(tc)

Return the average degree of the cursor's current state.
"""
average_degree(tc::TimeCursor)=average_degree(tc.S)

"""
	expected_degree(s,node)

Return the expected degree of the given node in the given stream.
Note: Not implemented yet...
"""
function expected_degree(s::AbstractStream,node::AbstractString)
	throw("Not Implemented")
end

"""
	expected_degree(s,node,tc)

Return the expected degree of the given node in the given stream using a TimeCursor.
```math
\\hat{d}\\left(v\\right)=\\int_t \\frac{d_t \\left(v\\right)}{\\left|T_v\\right|} dt
```

### Reference
- Matthieu Latapy, Tiphaine Viard and Clémence Magnien Social
  Networks Analysis and Mining, 8: 61, 2018. "Stream Graphs and
  Link Streams for the Modeling of Interactions over Time".
  [(arXiv)](https://arxiv.org/pdf/1710.04073.pdf)
"""
function expected_degree(s::AbstractStream,node::AbstractString,tc::TimeCursor)
	Tv=times(s,node)
	length(Tv)==0 && 0.0
	start!(tc)
	δ=degrees(tc.S)
	d::Float64=0
	if haskey(δ,node)
		d+=δ[node]*duration(tc.S)
	end
    while tc.S.t1 < s.T.list[1][2]
        next!(tc)
        δ=degrees(tc.S)
        if haskey(δ,node)
        	d+=δ[node]*duration(tc.S)
        end
    end
    1.0/length(Tv)*d
end

"""
	expected_degree(s,node,t)

Return the expected degree of the given node at the given time in the given stream.
Note: Not implemented yet...
"""
function expected_degree(s::AbstractStream,node::AbstractString,t::Float64)
	throw("Not Implemented")
end

"""
	expected_degree(s,node,t,tc)

Return the expected degree of the given node at the given time in the given
stream using a TimeCursor.
Note: Not implemented yet...
"""
function expected_degree(s::AbstractStream,node::AbstractString,t::Float64,tc::TimeCursor)
	throw("Not Implemented")
end

"""
	average_expected_degree(s)

Return the average expected degree of the given stream.
```math
\\hat{d}\\left(S\\right)=\\frac{2 m}{n}
```
Note: If the stream has no node, this function returns 0.

### Reference
- Matthieu Latapy, Tiphaine Viard and Clémence Magnien Social
  Networks Analysis and Mining, 8: 61, 2018. "Stream Graphs and
  Link Streams for the Modeling of Interactions over Time".
  [(arXiv)](https://arxiv.org/pdf/1710.04073.pdf)
"""
function average_expected_degree(s::AbstractStream)
	number_of_nodes(s)!=0 ? 2 * number_of_links(s) / number_of_nodes(s) : 0.0
end


module Shazam
include("AudioDisplay.jl")
#import Pkg;
#Pkg.add("FFTW")
#Pkg. add("WAV")

using Plots
using FFTW
using WAV

export AB1Aufgabe1a
export AB1A1aPlay
export AB1A1bPlay
export AB1Aufgabe1b
export AB1A1c1Play
export AB1Aufgabe1c1
export AB1A1c2Play
export AB1Aufgabe1c2
export AB1A1dPlay
export AB1Aufgabe1d
export AB1A2aPlay
export AB1Aufgabe2a
export AB1A2bPlay
export AB1Aufgabe2b
export AB1ZAPlay
export AB1ZusatzaufgabeA
export AB1ZBPlay
export AB1ZusatzaufgabeB
export AB1ZCPlay
export AB1ZusatzaufgabeC
export AB1ZDPlay
export AB1ZusatzaufgabeD
export AB2A1Play
export AB2Aufgabe1
export AB2Aufgabe2b1
export AB2Aufgabe2b2
export AB2A31Play
export AB2Aufgabe31
export AB2A32Play
export AB2Aufgabe32
export AB2Zusatzaufgabe
export AB3Aufgabe1
export AB4Aufgabe1a
export AB4Aufgabe1b
export AB4Aufgabe1c

function AB1A1aPlay()
    eps = 1/44100
    x = range(0.0,stop=2,step=eps);
    y = zeros(Float64,length(x));
    for i = 1:length(y)
        y[i] = sin(2*pi*200*x[i]);
    end
    #display(y)
    wavwrite(y, "SinustonAB1A1a.wav", Fs=44100);
    #y, fs = wavread("SinustonAB1A1a.wav");
	#wavplay(y, fs)
	inline_audioplayer("SinustonAB1A1a.wav")
    plot(y)
end

function AB1Aufgabe1a(amplitude,frequenz)
    if amplitude!=1 || frequenz!=200
		@warn "Amplitude und/oder Frequenz falsch!"
    end
    x = 0:0.0001:0.1;
    y = zeros(1001);
    for i = 1:1001
        y[i] = amplitude*sin(2*pi*frequenz*x[i]);
    end
    labels = ["dein Ton"];
    plot(x,y, label = labels)
end

function AB1A1bPlay(amplitude_leiser, amplitude_lauter)
    eps = 1/44100
    x = range(0.0,stop=2,step=eps);
    w = zeros(Float64,length(x));
    for i = 1:length(w)
        w[i] = sin(2*pi*200*x[i]);
    end
    y = zeros(Float64,length(x));
    for i = 1:length(y)
        y[i] = amplitude_leiser*sin(2*pi*200*x[i]);
    end
    z = zeros(Float64,length(x));
    for i = 1:length(z)
        z[i] = amplitude_lauter*sin(2*pi*200*x[i]);
    end
    ton  = [w; y; z];
    #display(ton)
    wavwrite(ton, "SinustonAB1A1b.wav", Fs=44100);
    #ton, fs = wavread("SinustonAB1A1b.wav");
    #wavplay(ton, fs)
	inline_audioplayer("SinustonAB1A1b.wav")

end

function AB1Aufgabe1b(amplitude,amplitude_leiser,amplitude_lauter)
    if amplitude_leiser >= amplitude
		@warn "Der erste Ton ist nicht leiser als der Ton aus Teil a)!"
    end
    if amplitude_lauter <= amplitude
		@warn "Der zweite Ton ist nicht lauter als der Ton aus Teil a)!"
    end
    x = 0:0.0001:0.1;
    y = zeros(1001,3);
    for i = 1:1001
        y[i,1] = sin(2*pi*200*x[i]);
        y[i,2] = amplitude_leiser*sin(2*pi*200*x[i]);
        y[i,3] = amplitude_lauter*sin(2*pi*200*x[i]);
    end
    labels = ["Teil a)" "leiser" "lauter"];
    plot(x,y, label = labels)
end

function AB1A1c1Play(A)
    eps = 1/44100
    x = range(0.0,stop=2,step=eps);
    y = zeros(Float64,length(x));
    for i = 1:length(y)
        y[i] = A(x[i])*sin(2*pi*200*x[i]);
    end
    #display(ton)
    wavwrite(y, "SinustonAB1A1c1.wav", Fs=44100);
    #y, fs = wavread("SinustonAB1A1c1.wav");
    #wavplay(y, fs)
	inline_audioplayer("SinustonAB1A1c1.wav")
end

function AB1Aufgabe1c1(A)
    if A(0) != 0
		@warn "Die Amplitude hat zur Zeit t = 0 nicht den Wert 0!"
    end
    m1 = A(1)-A(0);
    m2 = A(2)-A(1);
    if m1 != m2
		@warn "Die Funktion A besitzt kein lineares Wachstum!"
    end
    labels = ["dein Ton"];
    x = 0:0.0001:0.1;
    y = zeros(1001);
    for i = 1:1001
        y[i] = A(x[i])*sin(2*pi*200*x[i]);
    end
    plot(x,y, label = labels)
end

function AB1A1c2Play(A, A2)
    eps = 1/44100
    x = range(0.0,stop=2,step=eps);
    y = zeros(Float64,length(x));
    for i = 1:length(y)
        y[i] = A(x[i])*sin(2*pi*200*x[i]);
    end
    z = zeros(Float64,length(x));
    for i = 1:length(z)
        z[i] = A2(x[i])*sin(2*pi*200*x[i]);
    end
    w = zeros(44100,1);
    ton = [y;w;z];
    #display(ton)
    wavwrite(ton, "SinustonAB1A1c2.wav", Fs=44100);
    #y, fs = wavread("SinustonAB1A1c2.wav");
    #wavplay(y, fs)
	inline_audioplayer("SinustonAB1A1c2.wav")
end

function AB1Aufgabe1c2(A,A2)
    if A2(0) != 0
		@warn "Die Amplitude hat bei t = 0 nicht den Wert 0!"
    end
    m1 = A2(1)-A2(0);
    m2 = A2(2)-A2(1);
    if m1 != m2
		@warn "Die Funktion A2 besitzt kein lineares Wachstum!"
    end
    if A(0.1) > A2(0.1)
		@warn "Die Funktion A2 wächst nicht schneller als die Funktion A!"
    end
    labels = ["Teil 1" "Teil 2"];
    x = 0:0.0001:0.1;
    y = zeros(1001,2);
    for i = 1:1001
        y[i,1] = A(x[i])*sin(2*pi*200*x[i]);
        y[i,2] = A2(x[i])*sin(2*pi*200*x[i]);
    end
    plot(x,y,label=labels)
end

function AB1A1dPlay(A)
    eps = 1/44100
    x = range(0.0,stop=1,step=eps);
    y = zeros(Float64,length(x));
    for i = 1:length(y)
        y[i] = A(x[i])*sin(2*pi*200*x[i]);
    end
    #display(y)
    wavwrite(y, "SinustonAB1A1d.wav", Fs=44100);
    #y, fs = wavread("SinustonAB1A1d.wav");
    #wavplay(y, fs)
	inline_audioplayer("SinustonAB1A1d.wav")
end

function AB1Aufgabe1d(A3)
    if A3(1) != 0
		@warn "Bei t = 1 hat die Amplitude nicht den Wert 0!"
    end
    m1 = A3(1)-A3(0);
    m2 = A3(2)-A3(1);
    if abs(m1 - m2) > 0.0000000001
		@warn "Die Funktion A3 besitzt kein lineares Abklingverhalten!"
    end
    labels = ["dein Ton"];
    x = 0:0.0001:1;
    y = zeros(length(x));
    for i = 1:length(y)
        y[i] = A3(x[i])*sin(2*pi*200*x[i]);
    end
    plot(x,y, label = labels)
end

function AB1A2aPlay(F_hoeher,F_tiefer)
    eps = 1/44100
    x = range(0.0,stop=2,step=eps);
    y = zeros(Float64,length(x));
    for i = 1:length(y)
        y[i] = sin(2*pi*200*x[i]);
    end
    z = zeros(Float64,length(x));
    for i = 1:length(z)
        z[i] = sin(F_hoeher*2*pi*200*x[i]);
    end
    w = zeros(Float64,length(x));
    for i = 1:length(w)
        w[i] = sin(F_tiefer*2*pi*200*x[i]);
    end
    ton = [y; z; w];
    #display(ton)
    wavwrite(ton, "SinustonAB1A2a.wav", Fs=44100);
    #ton, fs = wavread("SinustonAB1A2a.wav");
    #wavplay(ton, fs)
	inline_audioplayer("SinustonAB1A2a.wav")
end

function AB1Aufgabe2a(F_hoeher,F_tiefer)
    if F_hoeher >= 6 || F_hoeher <= 0
		@warn "F_hoeher liegt nicht zwischen 0 und 6!"
    end
    if F_tiefer >= 6 || F_tiefer <= 0
		@warn "F_tiefer liegt nicht zwischen 0 und 6!"
    end
    if F_hoeher <= 1
		@warn "Der erste Ton ist nicht höher als der Referenzton!"
    end
    if F_tiefer >= 1
		@warn "Der zweite Ton ist nicht tiefer als der Referenzton!"
    end
    x = 0:0.00001:0.01;
    y = zeros(length(x),3);
    for i = 1:length(x)
        y[i,1] = sin(2*pi*200*x[i]);
        y[i,2] = sin(F_hoeher*2*pi*200*x[i]);
        y[i,3] = sin(F_tiefer*2*pi*200*x[i]);
    end
    labels = ["Referenz" "hoeher" "tiefer"];
    plot(x,y, label = labels)
end

function AB1A2bPlay(F)
    eps = 1/44100
    x = range(0.0,stop=1,step=eps);
    y = zeros(Float64,length(x));
    for i = 1:length(y)
        y[i] = sin(F(x[i])*2*pi*200*x[i]);
    end
    #display(y)
    wavwrite(y, "SinustonAB1A2b.wav", Fs=44100);
    #y, fs = wavread("SinustonAB1A2b.wav");
    #wavplay(y, fs)
	inline_audioplayer("SinustonAB1A2b.wav")
end

function AB1Aufgabe2b(F)
    if F(0) != 1
		@warn "Zum Zeitpunkt t = 0 hat die Frequenz von g nicht den Wert 200 Hz!"
    end
    if F(0.1) != 2
		@warn "Zum Zeitpunkt t = 0.1 hat die Frequenz von g nicht den Wert 400Hz!"
    end
    m1 = F(1)-F(0);
    m2 = F(2)-F(1);
    if abs(m1-m2) > 0.00000001
		@warn "Die Funktion F besitzt kein lineares Wachstum!"
    end
    labels = ["dein Signal"];
    x = 0:0.0001:0.1;
    y = zeros(1001);
    for i = 1:1001
        y[i] = sin(F(x[i])*2*pi*200*x[i]);
    end
    plot(x,y, label = labels)
end

function AB1ZAPlay(A,a,b)
    eps = 1/44100
    a_ton = a - 1;
    b_ton = b + 1;
    x = range(a_ton,stop=b_ton,step=eps);
    y = zeros(Float64,length(x));
    for i = 1:length(y)
        y[i] = A(x[i])*sin(2*pi*200*x[i]);
    end
    #display(y)
    wavwrite(y, "SinustonAB1A2b.wav", Fs=44100);
    #y, fs = wavread("SinustonAB1A2b.wav");
    #wavplay(y, fs)
	inline_audioplayer("SinustonAB1A2b.wav")
end

function AB1ZusatzaufgabeA(A4,t_min,a,b)
    if t_min <= a || t_min >= b
		@warn " t_min liegt nicht zwischen a und b!"
    end
    min = 1;
    for i = -10:10
        if A4(t_min + i/100) < A4(t_min)
            min = 0;
        end
    end
    if min == 0
		@warn "Bei t = t_min ist die Amplitude nicht minimal!"
    end
    labels = ["dein Ton"];
    if t_min > a && t_min < b
        x = a:(b-a)/10000:b;
        y = zeros(length(x));
        for i = 1:length(y)
            y[i] = A4(x[i])*sin(2*pi*200*x[i]);
        end
        plot(x,y, label = labels)
    end
end

function AB1ZBPlay(A,a,b)
    eps = 1/44100
    x = range(a,stop=b,step=eps);
    y = zeros(Float64,length(x));
    for i = 1:length(y)
        y[i] = A(x[i])*sin(2*pi*50*x[i]);
    end
    #display(y)
    wavwrite(y, "SinustonAB1ZB.wav", Fs=44100);
    #y, fs = wavread("SinustonAB1ZB.wav");
    #wavplay(y, fs)
	inline_audioplayer("SinustonAB1ZB.wav")
end

function AB1ZusatzaufgabeB(A5,t_max,a,b)
    if t_max <= a || t_max >= b
		@warn "t_max liegt nicht zwischen a und b!"
    end
    if A5(a) != 0
		@warn "Bei t = a hat die Amplitude nicht den Wert 0!"
    end
    if A5(b) != 0
		@warn "Bei t = b hat die Amplitude nicht den Wert 0!"
    end
    max = 1;
    for i = -10:10
        if A5(t_max + i/100) > A5(t_max)
            max = 0;
        end
    end
    if max == 0
		@warn "Bei t = t_max ist die Amplitude nicht maximal!"
    end
    labels = ["dein Ton"];
    if t_max > a && t_max < b
        x = a:(b-a)/10000:b;
        y = zeros(length(x));
        for i = 1:length(y)
            y[i] = A5(x[i])*sin(2*pi*50x[i]);
        end
        plot(x,y, label = labels)
    end
end

function AB1ZCPlay(a,F)
    eps = 1/44100
    b = a + 1;
    x = range(a,stop=b,step=eps);
    y = zeros(Float64,length(x));
    for i = 1:length(y)
        y[i] = sin(F(x[i])*2*pi*200*x[i]);
    end
    #display(y)
    wavwrite(y, "SinustonAB1ZC.wav", Fs=44100);
    #y, fs = wavread("SinustonAB1ZC.wav");
    #wavplay(y, fs)
	inline_audioplayer("SinustonAB1ZC.wav")
end

function AB1ZusatzaufgabeC(a,b,F)
    if b <= a
		@warn "b muss größer sein als a!"
    end
    increase = 1;
    for i = 1:100
        if F(a+(b-a)/100*i) < F(a+(b-a)/100*(i+1))
            increase = 0;
        end
    end
    if increase == 0
		@warn "Die Frequenz fällt nicht auf dem Intervall [a,b]!"
    end
    labels = ["dein Signal"];
    if a < b
        x = a:(b-a)/10000:b;
        y = zeros(length(x));
        for i = 1:length(y)
            y[i] = sin(F(x[i])*2*pi*200*x[i]);
        end
        plot(x,y, label = labels)
    end
end

function AB1ZDPlay(a,b,F)
    eps = 1/44100
    x = range(a,stop=b,step=eps);
    y = zeros(Float64,length(x));
    for i = 1:length(y)
        y[i] = sin(F(x[i])*2*pi*200*x[i]);
    end
    #display(y)
    wavwrite(y, "SinustonAB1ZD.wav", Fs=44100);
    #y, fs = wavread("SinustonAB1ZD.wav");
    #wavplay(y, fs)
	inline_audioplayer("SinustonAB1ZD.wav")
end

function AB1ZusatzaufgabeD(a,b,F)
    if b <= a
		@warn "b muss größer sein als a!"
    end
    labels = ["dein Signal"];
    if a < b
        x = a:(b-a)/10000:b;
        y = zeros(length(x));
        for i = 1:length(y)
            y[i] = sin(F(x[i])*2*pi*200*x[i]);
        end
        plot(x,y, label = labels)
    end
end

function AB2A1Play(g)
    eps = 1/44100
    x = range(0.0,stop=2,step=eps);
    y = zeros(Float64,length(x));
    for i = 1:length(y)
        y[i] = g(x[i]);
    end
    #display(y)
    wavwrite(y, "SinustonAB2A1.wav", Fs=44100);
    #y, fs = wavread("SinustonAB2A1.wav");
    #wavplay(y, fs)
	inline_audioplayer("SinustonAB2A1.wav")
end

function AB2Aufgabe1(g)
    x = 0:0.0001:0.1;
    y = zeros(length(x));
    for i = 1:length(y)
        y[i] = g(x[i]);
    end
    labels = ["dein Dreiklang"];
    plot(x,y, label = labels)
end

function AB2Aufgabe2b1(g)
    samplingrate = 44100;
    x = 0:1/44100:6;
    y = zeros(length(x));
    for i = 1:length(y)
        y[i] = g(x[i]);
    end
    yfft = fft(y);
    f = zeros(length(yfft));
    for i = 1:length(yfft)
        f[i] = (i-1)*samplingrate/length(yfft);
    end
    fplot = f[1:5000];
    yplot = yfft[1:5000];
    yfind = abs.(yplot);
    m1, ind1 = findmax(yfind);
    yfind[ind1] = 0;
    m2, ind2 = findmax(yfind);
    yfind[ind2] = 0;
    m3, ind3 = findmax(yfind);
    labels = ["Frequenzspektrum"];
    plot(fplot,abs.(yplot)/m3, label = labels)
end

function AB2Aufgabe2b2(g)
    samplingrate = 44100;
    x = 0:1/44100:6;
    y = zeros(length(x));
    for i = 1:length(y)
        y[i] = g(x[i]);
    end
    yfft = fft(y);
    f = zeros(length(yfft));
    for i = 1:length(yfft)
        f[i] = (i-1)*samplingrate/length(yfft);
    end
    fplot = f[1:5000];
    yplot = yfft[1:5000];
    yfind = abs.(yplot);
    m1, ind1 = findmax(yfind);
    yfind[ind1] = 0;
    m2, ind2 = findmax(yfind);
    yfind[ind2] = 0;
    m3, ind3 = findmax(yfind);
    print("Frequenz 1 = ", f[ind1], "     Frequenz 2 = ", f[ind2], "     Frequenz 3 = ", f[ind3])
end

function AB2A31Play(y1,y2,y3)
    eps = 1/44100
    x = range(0.0,stop=2,step=eps);
    y = zeros(Float64,length(x));
    for i = 1:length(y)
        y[i] = y1(x[i]);
    end
    z = zeros(Float64,length(x));
    for i = 1:length(z)
        z[i] = y2(x[i]);
    end
    w = zeros(Float64,length(x));
    for i = 1:length(w)
        w[i] = y3(x[i]);
    end
    ton = [y; z; w];
    #display(ton)
    wavwrite(ton, "SinustonAB2A31.wav", Fs=44100);
    #ton, fs = wavread("SinustonAB2A31.wav");
    #wavplay(ton, fs)
	inline_audioplayer("SinustonAB2A31.wav")
end

function AB2Aufgabe31(y1,y2,y3)
    x = 0:0.00001:0.01;
    y = zeros(length(x),3);
    for i = 1:length(x)
        y[i,1] = y1(x[i]);
        y[i,2] = y2(x[i]);
        y[i,3] = y3(x[i]);
    end
    labels = ["y1" "y2" "y3"];
    plot(x,y, label = labels)
end

function AB2A32Play(S)
    eps = 1/44100
    x = range(0.0,stop=2,step=eps);
    y = zeros(Float64,length(x));
    for i = 1:length(y)
        y[i] = S(x[i]);
    end
    #display(y)
    wavwrite(y, "SinustonAB2A32.wav", Fs=44100);
    #y, fs = wavread("SinustonAB2A32.wav");
    #wavplay(y, fs)
	inline_audioplayer("SinustonAB2A32.wav")
end

function AB2Aufgabe32(S)
    x = 0:0.00001:0.01;
    y = zeros(length(x));
    for i = 1:length(x)
        y[i] = S(x[i]);
    end
    labels = ["S"];
    plot(x,y, label = labels)
end

function AB2Zusatzaufgabe(F)
    saeg(t) = mod2pi(t+pi)-pi;
    x = -2*pi:0.01:2*pi;
    y = zeros(length(x),2);
    for i = 1:length(x)
        y[i,1] = F(x[i]);
        y[i,2] = saeg(x[i]);
    end
    labels = ["F" "Saegezahnfunktion"];
    plot(x,y, label = labels)
end

function AB3Aufgabe1(pointFreq_8,T_delta_8,anchorFreq_3,pointFreq_10,anchorFreq_4,pointFreq_11_2,T_delta_11_2,anchorFreq_6,pointFreq_16_2,T_delta_16_2,pointFreq_17_2,T_delta_17_2,pointFreq_18,T_delta_18,pointFreq_19,T_delta_19,pointFreq_20,T_delta_20)
    if pointFreq_8 != 40
		@warn "Frequenz von Datenpunkt 8 ist falsch!"
    end
    if T_delta_8 != 4
		@warn "Abstand von Datenpunkt 8 zum Anchor Point 2 stimmt nicht!"
    end
    if anchorFreq_3 != 10
		@warn "Frequenz von Anchor Point 3 stimmt nicht!"
    end
    if pointFreq_10 != 50
		@warn "Frequenz von Datenpunkt 10 stimmt nicht!"
    end
    if anchorFreq_4 != 50
		@warn "Frequenz von Anchor Point 4 stimmt nicht!"
    end
    if pointFreq_11_2 != 10
		@warn "Frequenz von Datenpunkt 11 stimmt nicht!"
    end
    if T_delta_11_2 != 1
		@warn "Abstand von Datenpunkt 11 zu Anchor Point 4 stimmt nicht!"
    end
    if anchorFreq_6 != 30
		@warn "Frequenz von Anchor Point 6 stimmt nicht!"
    end
    if pointFreq_16_2 != 30
		@warn "Frequenz von Datenpunkt 16 stimmt nicht!"
    end
    if T_delta_16_2 != 0
		@warn "Abstand von Datenpunkt 16 zum Anchor Point 6 stimmt nicht!"
    end
    if pointFreq_17_2 != 20
		@warn "Frequenz von Datenpunkt 17 stimmt nicht!"
    end
    if T_delta_17_2 != 1
		@warn "Abstand von Datenpunkt 17 zu Anchor Point 6 stimmt nicht!"
    end
    if pointFreq_18 != 40
		@warn "Frequenz von Datenpunkt 18 stimmt nicht!"
    end
    if T_delta_18 != 2
		@warn "Abstand zwischen Datenpunkt 18 und Anchor Point 6 stimmt nicht!"
    end
    if pointFreq_19 != 10
		@warn "Frequenz von Datenpunkt 19 stimmt nicht!"
    end
    if T_delta_19 != 3
		@warn "Abstand zwischen Datenpunkt 19 und Anchor Point 6 stimmt nicht!"
    end
    if pointFreq_20 != 30
		@warn "Frequenz von Datenpunkt 20 stimmt nicht!"
    end
    if T_delta_20 != 4
		@warn "Abstand von Datenpunkt 20 zu Anchor Point 6 stimmt nicht!"
    end
end

function AB4Aufgabe1a(Spalte3,Spalte4)
    Spalte2 = [22; 22; 22; 49; 49; 34; 34; 34; 85; 49; 58; 58; 58; 58; 58; 19; 19; 19; 67; 67; 82; 82; 52; 70; 70; 25; 25; 25; 40; 40];
    if Spalte3 != [1; 1; 1; 1; 1; 4; 4; 4; 4; 4; 7; 7; 7; 7; 7; 10; 10; 10; 10; 10; 13; 13; 13; 13; 13; 16; 16; 16; 16; 16]
		@warn "Spalte3 ist falsch!"
    end
    Spalte4_temp = Spalte2 - Spalte3;
    if Spalte4 !=  Spalte4_temp
		@warn "Spalte4 ist falsch!"
    end
    print("Spalte4 = ", Spalte4)
end

function AB4Aufgabe1b(Spalte2,Spalte3,Spalte4)
    if Spalte2 != [52; 52; 52; 52; 52; 55; 55; 55; 55; 55; 58; 58; 58; 58; 58; 61; 61; 61; 61; 61; 64; 64; 64; 64; 64; 67; 67; 67; 67; 67];
		@warn "Spalte2 ist falsch!"
    end
    if Spalte3 != [1; 1; 1; 1; 1; 4; 4; 4; 4; 4; 7; 7; 7; 7; 7; 10; 10; 10; 10; 10; 13; 13; 13; 13; 13; 16; 16; 16; 16; 16]
		@warn "Spalte3 ist falsch!"
    end
    Spalte4_temp = Spalte2 - Spalte3;
    if Spalte4 !=  Spalte4_temp
		@warn "Spalte4 ist falsch!"
    end
    print("Spalte4 = ", Spalte4)
end

function AB4Aufgabe1c(N)
    if N != 2
		@warn "Falsch!"
    end
    if N == 2
        print("Richtig, Song02 ist der gesuchte Song!")
    end
end

end


Rectangular() = Uniform(-0.5, +0.5)

function _string(kernel::Uniform)
    a = kernel.a
    b = kernel.b
    "Rectangular kernel (U[$a,$b])"
end

const LocationScaleDists = Union{Cosine,Epanechnikov,SymTriangularDist,Triweight}

_string(kernel::SymTriangularDist) = "Triangular kernel"
_string(kernel::LocationScaleDists) = "$(Base.typename(typeof(kernel))) kernel"

const SupportedKernels = Union{LocationScaleDists,Uniform}

export Uniform, Epanechnikov, SymTriangularDist

function setbandwidth(kernel::LocationScaleDists, h::Number)
    typeof(kernel)(zero(kernel.μ), h * kernel.σ)
end

function setbandwidth(kernel::Uniform, h::Number)
    a = kernel.a
    b = kernel.b

    if a + b != 0
        error("Use symmetric Uniform/Rectangular kernel")
    end
    Uniform(a * h, b * h)
end

weights(::Uniform, ZsR::RunningVariable) = uweights(length(ZsR))
weights(D::Distribution, ZsR::RunningVariable) = pdf.(D, ZsR)

struct EquivalentKernel{K,T}
    kernel::K #only order 1 for now
    kernel_mult::T
end

function EquivalentKernel(kernel::SupportedKernels)
    ub = support(kernel).ub
    E(f) = quadgk(f, 0, ub)[1]
    cov_x = [x->1 x->x; x->x x->x^2]
    kernel_mult = [1 0] * inv(map(f -> E(u -> f(u) * pdf(kernel, u)), cov_x))
    EquivalentKernel(kernel, kernel_mult)
end

Distributions.support(eq::EquivalentKernel) = Distributions.support(eq.kernel)

function Distributions.pdf(eq::EquivalentKernel, u)
    kernel_mult = eq.kernel_mult
    kernel = eq.kernel
    dot(kernel_mult, [1; u]) * pdf(kernel, u)
end

function kernel_moment(kernel, ::Val{j}) where {j}
    ub = support(kernel).ub
    quadgk(x -> x^j * pdf(kernel, x), 0, ub)[1]
end

kernel_moment(::SupportedKernels, ::Val{0}) = 0.5
kernel_moment(::SupportedKernels, ::Val{1}) = 0
kernel_moment(::SupportedKernels, ::Val{3}) = 0

function kernel_moment(eq::EquivalentKernel, ::Val{j}) where {j}
    ub = support(eq).ub
    E(f) = quadgk(f, 0, ub)[1]
    E(x -> x^j * pdf(eq, x))
end

function squared_kernel_moment(kernel, ::Val{j}) where {j}
    ub = support(kernel).ub
    quadgk(x -> x^j * pdf(kernel, x)^2, 0, ub)[1]
end

squared_kernel_moment(::SupportedKernels, ::Val{1}) = 0
squared_kernel_moment(::SupportedKernels, ::Val{3}) = 0

function squared_kernel_moment(eq::EquivalentKernel, ::Val{j}) where {j}
    ub = support(eq).ub
    E(f) = quadgk(f, 0, ub)[1]
    E(x -> x^j * pdf(eq, x)^2)
end