@testset "Site counting" begin

    dna_alphabets = (DNAAlphabet{4}, DNAAlphabet{2})
    rna_alphabets = (RNAAlphabet{4}, RNAAlphabet{2})

    @testset "Specific count methods" begin

        function generate_possibilities_tester(::Type{A}) where A<:NucAlphs
            symbols = BioSymbols.alphabet(A)
            arra = Vector{eltype(A)}()
            arrb = Vector{eltype(A)}()
            for i in 1:length(symbols), j in i:length(symbols)
                push!(arra, symbols[i])
                push!(arrb, symbols[j])
            end
            return BioSequence{A}(arra), BioSequence{A}(arrb)
        end

        for alphset in (dna_alphabets, rna_alphabets)
            # Answers to these tests were worked out manually to verify
            # counting is working correctly.
            # seqA and seqB contain all possible observations of sites.

            # 4 bit encoded sequences
            seqA, seqB = generate_possibilities_tester(alphset[1])
            @test count(Certain, seqA, seqB) == count(Certain, seqB, seqA) == 10
            @test count(Gap, seqA, seqB) == count(Gap, seqB, seqA) == 16
            @test count(Ambiguous, seqA, seqB) == count(Ambiguous, seqB, seqA) == 121
            @test count(Match, seqA, seqB) == count(Match, seqB, seqA) == length(BioSymbols.alphabet(alphset[1]))
            @test count(Mismatch, seqA, seqB) == count(Mismatch, seqB, seqA) == (length(seqA) - length(BioSymbols.alphabet(alphset[1])))

            # 2 bit encoded sequences
            seqA, seqB = generate_possibilities_tester(alphset[2])
            @test count(Certain, seqA, seqB) == count(Certain, seqB, seqA) == 10
            @test count(Gap, seqA, seqB) == count(Gap, seqB, seqA) == 0
            @test count(Ambiguous, seqA, seqB) == count(Ambiguous, seqB, seqA) == 0
            @test count(Match, seqA, seqB) == count(Match, seqB, seqA) == length(BioSymbols.alphabet(alphset[2]))
            @test count(Mismatch, seqA, seqB) == count(Mismatch, seqB, seqA) == (length(seqA) - length(BioSymbols.alphabet(alphset[2])))
        end
    end

    @testset "Randomized tests" begin

        # A test counting function which is naive.
        @inline function testcount(::Type{P}, a::BioSequence, b::BioSequence) where P<:BioSequences.Position
            k = 0
            @inbounds for idx in 1:min(lastindex(a), lastindex(b))
                k += issite(P, a, b, idx)
            end
            return k
        end
        issite(::Type{Ambiguous}, a::BioSequence, idx) = isambiguous(a[idx])
        @inline function issite(::Type{Ambiguous}, a::BioSequence, b::BioSequence, idx)
            return issite(Ambiguous, a, idx) | issite(Ambiguous, b, idx)
        end
        issite(::Type{Certain}, a::BioSequence, idx) = iscertain(a[idx])
        @inline function issite(::Type{Certain}, a::BioSequence, b::BioSequence, idx)
            return issite(Certain, a, idx) & issite(Certain, b, idx)
        end
        issite(::Type{Gap}, a::BioSequence, idx) = isgap(a[idx])
        @inline function issite(::Type{Gap}, a::BioSequence, b::BioSequence, idx)
            return issite(Gap, a, idx) | issite(Gap, b, idx)
        end
        issite(::Type{Match}, a::BioSequence, b::BioSequence, idx) = a[idx] == b[idx]
        issite(::Type{Mismatch}, a::BioSequence, b::BioSequence, idx) = a[idx] != b[idx]

        # Randomized tests get performed with a naive counting function
        # which is intuitive and works, but that is nowhere near as quick.

        function testcounting(::Type{S}, a, b) where S<:Site
            @test count(S, a, b) == count(S, b, a) == testcount(S, a, b)
        end

        function testforencs(a::Int, b::Int, subset::Bool)
            for alphabet in (DNAAlphabet, RNAAlphabet)
                for _ in  1:50
                    #println("TESTING SEQUENCES:")
                    seqA = random_seq(alphabet{a}, rand(10:100))
                    seqB = random_seq(alphabet{b}, rand(10:100))
                    #=
                    println("A seq: ", seqA)
                    println("B seq: ", seqB)
                    println("A length: ", length(seqA))
                    println("B length: ", length(seqB))
                    =#
                    sa = seqA
                    sb = seqB
                    if subset
                        intA = random_interval(1, length(seqA))
                        intB = random_interval(1, length(seqB))
                        subA = seqA[intA]
                        subB = seqB[intB]
                        #=
                        println("A subset: ", intA)
                        println("B subset: ", intB)
                        println("A subseq: ", subA)
                        println("B subseq: ", subB)
                        =#
                        sa = subA
                        sb = subB
                    end
                    #println("Testing Mismatch")
                    testcounting(Mismatch, sa, sb)
                    #println("Testing Match")
                    testcounting(Match, sa, sb)
                    #println("Testing Certain")
                    testcounting(Certain, sa, sb)
                    #println("Testing Gap")
                    testcounting(Gap, sa, sb)
                    #println("Testing Ambiguous")
                    testcounting(Ambiguous, sa, sb)
                end
            end
        end

        @testset "Testing 4-bit seq pairs" begin
            @testset "Full random sequences" begin
                testforencs(4, 4, false)
            end
            @testset "Subset random sequences" begin
                testforencs(4, 4, true)
            end
        end

        @testset "Testing 2-bit seq pairs" begin
            @testset "Full random sequences" begin
                testforencs(2, 2, false)
            end
            @testset "Subset random sequences" begin
                testforencs(2, 2, true)
            end
        end

        @testset "Testing mixed bit seq pairs" begin
            @testset "Full random sequences" begin
                testforencs(4, 2, false)
                testforencs(2, 4, false)
            end
            @testset "Subset random sequences" begin
                testforencs(4, 2, true)
                testforencs(2, 4, true)
            end
        end
    end

    @testset "Pairwise methods" begin
        @testset "4-bit encoded sequences" begin
            dnas = [dna"ATCGCCA-", dna"ATCGCCTA", dna"ATCGCCT-", dna"GTCGCCTA"]
            rnas = [rna"AUCGCCA-", rna"AUCGCCUA", rna"AUCGCCU-", rna"GUCGCCUA"]
            answer_mismatch = [0 2 1 3;
                               2 0 1 1;
                               1 1 0 2;
                               3 1 2 0]
            answer_match = [0 6 7 5;
                            6 0 7 7;
                            7 7 0 6;
                            5 7 6 0]
            for i in (dnas, rnas)
                @test count_pairwise(Mismatch, i...) == answer_mismatch
                @test count_pairwise(Match, i...) == answer_match
                @test count_pairwise(Certain, i...) == [0 7 7 7;
                                                        7 0 7 8;
                                                        7 7 0 7;
                                                        7 8 7 0]
                @test count_pairwise(Ambiguous, i...) == [0 0 0 0;
                                                          0 0 0 0;
                                                          0 0 0 0;
                                                          0 0 0 0]
                @test count_pairwise(Gap, i...) == [0 1 1 1;
                                                    1 0 1 0;
                                                    1 1 0 1;
                                                    1 0 1 0]
            end
        end
        @testset "2-bit encoded sequences" begin
            dnas = [BioSequence{DNAAlphabet{2}}("ATCGCCAC"),
                    BioSequence{DNAAlphabet{2}}("ATCGCCTA"),
                    BioSequence{DNAAlphabet{2}}("ATCGCCTT"),
                    BioSequence{DNAAlphabet{2}}("GTCGCCTA")]
            rnas = [BioSequence{RNAAlphabet{2}}("AUCGCCAC"),
                    BioSequence{RNAAlphabet{2}}("AUCGCCUA"),
                    BioSequence{RNAAlphabet{2}}("AUCGCCUU"),
                    BioSequence{RNAAlphabet{2}}("GUCGCCUA")]
            answer_mismatch = [0 2 2 3;
                               2 0 1 1;
                               2 1 0 2;
                               3 1 2 0]
            answer_match = [0 6 6 5;
                            6 0 7 7;
                            6 7 0 6;
                            5 7 6 0]
            for i in (dnas, rnas)
                @test count_pairwise(Mismatch, i...) == answer_mismatch
                @test count_pairwise(Match, i...) == answer_match
                @test count_pairwise(Certain, i...) == [0 8 8 8;
                                                        8 0 8 8;
                                                        8 8 0 8;
                                                        8 8 8 0]
                @test count_pairwise(Ambiguous, i...) == [0 0 0 0;
                                                          0 0 0 0;
                                                          0 0 0 0;
                                                          0 0 0 0]
                @test count_pairwise(Gap, i...) == [0 0 0 0;
                                                    0 0 0 0;
                                                    0 0 0 0;
                                                    0 0 0 0]
            end
        end
    end

    @testset "Windowed methods" begin
        @testset "4-bit encoded sequences" begin
            dnaA = dna"ATCGCCA-M"
            dnaB = dna"ATCGCCTAA"
            rnaA = rna"AUCGCCA-M"
            rnaB = rna"AUCGCCUAA"

            for seqs in ((dnaA, dnaB), (rnaA, rnaB))
                @test count(Certain, seqs[1], seqs[2], 3, 1) == [IntervalValue(1, 3, 3),
                                                                 IntervalValue(2, 4, 3),
                                                                 IntervalValue(3, 5, 3),
                                                                 IntervalValue(4, 6, 3),
                                                                 IntervalValue(5, 7, 3),
                                                                 IntervalValue(6, 8, 2),
                                                                 IntervalValue(7, 9, 1)]
                @test count(Ambiguous, seqs[1], seqs[2], 3, 1) == [IntervalValue(1, 3, 0),
                                                                   IntervalValue(2, 4, 0),
                                                                   IntervalValue(3, 5, 0),
                                                                   IntervalValue(4, 6, 0),
                                                                   IntervalValue(5, 7, 0),
                                                                   IntervalValue(6, 8, 0),
                                                                   IntervalValue(7, 9, 1)]
                @test count(Gap, seqs[1], seqs[2], 3, 1) == [IntervalValue(1, 3, 0),
                                                             IntervalValue(2, 4, 0),
                                                             IntervalValue(3, 5, 0),
                                                             IntervalValue(4, 6, 0),
                                                             IntervalValue(5, 7, 0),
                                                             IntervalValue(6, 8, 1),
                                                             IntervalValue(7, 9, 1)]
                @test count(Match, seqs[1], seqs[2], 3, 1) == [IntervalValue(1, 3, 3),
                                                               IntervalValue(2, 4, 3),
                                                               IntervalValue(3, 5, 3),
                                                               IntervalValue(4, 6, 3),
                                                               IntervalValue(5, 7, 2),
                                                               IntervalValue(6, 8, 1),
                                                               IntervalValue(7, 9, 0)]
                @test count(Mismatch, seqs[1], seqs[2], 3, 1) == [IntervalValue(1, 3, 0),
                                                                  IntervalValue(2, 4, 0),
                                                                  IntervalValue(3, 5, 0),
                                                                  IntervalValue(4, 6, 0),
                                                                  IntervalValue(5, 7, 1),
                                                                  IntervalValue(6, 8, 2),
                                                                  IntervalValue(7, 9, 3)]
            end
        end
        @testset "2-bit encoded sequences" begin
            dnaA = BioSequence{DNAAlphabet{2}}("ATCGCCATT")
            dnaB = BioSequence{DNAAlphabet{2}}("ATCGCCTAA")
            rnaA = BioSequence{RNAAlphabet{2}}("AUCGCCAUU")
            rnaB = BioSequence{RNAAlphabet{2}}("AUCGCCUAA")

            for seqs in ((dnaA, dnaB), (rnaA, rnaB))
                @test count(Certain, seqs[1], seqs[2], 3, 1) == [IntervalValue(1, 3, 3),
                                                                 IntervalValue(2, 4, 3),
                                                                 IntervalValue(3, 5, 3),
                                                                 IntervalValue(4, 6, 3),
                                                                 IntervalValue(5, 7, 3),
                                                                 IntervalValue(6, 8, 3),
                                                                 IntervalValue(7, 9, 3)]
                @test count(Ambiguous, seqs[1], seqs[2], 3, 1) == [IntervalValue(1, 3, 0),
                                                                   IntervalValue(2, 4, 0),
                                                                   IntervalValue(3, 5, 0),
                                                                   IntervalValue(4, 6, 0),
                                                                   IntervalValue(5, 7, 0),
                                                                   IntervalValue(6, 8, 0),
                                                                   IntervalValue(7, 9, 0)]
                @test count(Gap, seqs[1], seqs[2], 3, 1) == [IntervalValue(1, 3, 0),
                                                             IntervalValue(2, 4, 0),
                                                             IntervalValue(3, 5, 0),
                                                             IntervalValue(4, 6, 0),
                                                             IntervalValue(5, 7, 0),
                                                             IntervalValue(6, 8, 0),
                                                             IntervalValue(7, 9, 0)]
                @test count(Match, seqs[1], seqs[2], 3, 1) == [IntervalValue(1, 3, 3),
                                                               IntervalValue(2, 4, 3),
                                                               IntervalValue(3, 5, 3),
                                                               IntervalValue(4, 6, 3),
                                                               IntervalValue(5, 7, 2),
                                                               IntervalValue(6, 8, 1),
                                                               IntervalValue(7, 9, 0)]
                @test count(Mismatch, seqs[1], seqs[2], 3, 1) == [IntervalValue(1, 3, 0),
                                                                  IntervalValue(2, 4, 0),
                                                                  IntervalValue(3, 5, 0),
                                                                  IntervalValue(4, 6, 0),
                                                                  IntervalValue(5, 7, 1),
                                                                  IntervalValue(6, 8, 2),
                                                                  IntervalValue(7, 9, 3)]
            end
        end
        @testset "Mixed encodings" begin
            dnaA = dna"ATCGCCA-M"
            dnaB = BioSequence{DNAAlphabet{2}}("ATCGCCTAA")
            rnaA = rna"AUCGCCA-M"
            rnaB = BioSequence{RNAAlphabet{2}}("AUCGCCUAA")

            for seqs in ((dnaA, dnaB), (rnaA, rnaB))
                @test count(Certain, seqs[1], seqs[2], 3, 1) == [IntervalValue(1, 3, 3),
                                                                 IntervalValue(2, 4, 3),
                                                                 IntervalValue(3, 5, 3),
                                                                 IntervalValue(4, 6, 3),
                                                                 IntervalValue(5, 7, 3),
                                                                 IntervalValue(6, 8, 2),
                                                                 IntervalValue(7, 9, 1)]
                @test count(Ambiguous, seqs[1], seqs[2], 3, 1) == [IntervalValue(1, 3, 0),
                                                                   IntervalValue(2, 4, 0),
                                                                   IntervalValue(3, 5, 0),
                                                                   IntervalValue(4, 6, 0),
                                                                   IntervalValue(5, 7, 0),
                                                                   IntervalValue(6, 8, 0),
                                                                   IntervalValue(7, 9, 1)]
                @test count(Gap, seqs[1], seqs[2], 3, 1) == [IntervalValue(1, 3, 0),
                                                             IntervalValue(2, 4, 0),
                                                             IntervalValue(3, 5, 0),
                                                             IntervalValue(4, 6, 0),
                                                             IntervalValue(5, 7, 0),
                                                             IntervalValue(6, 8, 1),
                                                             IntervalValue(7, 9, 1)]
                @test count(Match, seqs[1], seqs[2], 3, 1) == [IntervalValue(1, 3, 3),
                                                               IntervalValue(2, 4, 3),
                                                               IntervalValue(3, 5, 3),
                                                               IntervalValue(4, 6, 3),
                                                               IntervalValue(5, 7, 2),
                                                               IntervalValue(6, 8, 1),
                                                               IntervalValue(7, 9, 0)]
                @test count(Mismatch, seqs[1], seqs[2], 3, 1) == [IntervalValue(1, 3, 0),
                                                                  IntervalValue(2, 4, 0),
                                                                  IntervalValue(3, 5, 0),
                                                                  IntervalValue(4, 6, 0),
                                                                  IntervalValue(5, 7, 1),
                                                                  IntervalValue(6, 8, 2),
                                                                  IntervalValue(7, 9, 3)]
            end
        end
    end
end


struct FakeDriveAction end
struct FakeDriverModel <: DriverModel{FakeDriveAction} end
@testset "driver model interface" begin

    roadway = get_test_roadway()
    trajdata = get_test_trajdata(roadway)
    veh = get(trajdata, 1, 1)

    model = FakeDriverModel()
    reset_hidden_state!(model)
    observe!(model, Scene(), roadway, 1)
    prime_with_history!(model, trajdata, roadway, 1, 2, 1)

    @test get_name(model) == "???"
    @test action_type(model) <: FakeDriveAction
    @test set_desired_speed!(model, 0.0) == FakeDriverModel()
    @test_throws ErrorException rand(model)
    @test_throws ErrorException pdf(model, FakeDriveAction())
    @test_throws ErrorException logpdf(model, FakeDriveAction())
end

@testset "IDM test" begin
    roadway = gen_straight_roadway(1, 500.0)

    models = Dict{Int, DriverModel}()
    models[1] = IntelligentDriverModel(k_spd = 1.0, v_des = 10.0)
    models[2] = IntelligentDriverModel(k_spd = 1.0)
    get_name(models[1]) == "IDM"
    set_desired_speed!(models[2], 5.0)
    @test models[2].v_des == 5.0
    veh_state = VehicleState(Frenet(roadway[LaneTag(1,1)], 0.0), roadway, 5.)
    veh1 = Vehicle(veh_state, VehicleDef(), 1)
    veh_state = VehicleState(Frenet(roadway[LaneTag(1,1)], 70.0), roadway, 5.)
    veh2 = Vehicle(veh_state, VehicleDef(), 2)

    scene = Scene()
    push!(scene, veh1)
    push!(scene, veh2)

    n_steps = 40
    dt = 0.1
    rec = SceneRecord(n_steps, dt)
    simulate!(rec, scene, roadway, models, n_steps)

    @test isapprox(get_by_id(scene, 2).state.v, models[2].v_des)

    # same with noise
    models = Dict{Int, DriverModel}()
    models[1] = IntelligentDriverModel(a = 0.0, k_spd = 1.0, v_des = 10.0, σ=1.0)
    models[2] = IntelligentDriverModel(a = 0.0, k_spd = 1.0, v_des = 5.0, σ=1.0)
    @test pdf(models[1], LaneFollowingAccel(0.0))  > 0.0
    @test logpdf(models[1], LaneFollowingAccel(0.0)) < 0.0
    n_steps = 40
    dt = 0.1
    rec = SceneRecord(n_steps, dt)
    simulate!(rec, scene, roadway, models, n_steps)

    prime_with_history!(IntelligentDriverModel(), rec, roadway, 2)

    println("There should be a warning here: ")

    # initializing vehicles too close
    veh_state = VehicleState(Frenet(roadway[LaneTag(1,1)], 0.0), roadway, 5.)
    veh1 = Vehicle(veh_state, VehicleDef(), 1)
    veh_state = VehicleState(Frenet(roadway[LaneTag(1,1)], 3.0), roadway, 5.)
    veh2 = Vehicle(veh_state, VehicleDef(), 2)

    scene = Scene()
    push!(scene, veh1)
    push!(scene, veh2)

    rec = SceneRecord(n_steps, dt)
    simulate!(rec, scene, roadway, models, 1)
end

struct FakeLaneChanger <: LaneChangeModel end
@testset "lane change interface" begin
    l = LaneChangeChoice(DIR_RIGHT)
    io = IOBuffer()
    show(io, l)
    close(io)
    roadway = get_test_roadway()
    trajdata = get_test_trajdata(roadway)
    veh = get(trajdata, 1, 1)

    model = FakeLaneChanger()
    reset_hidden_state!(model)
    observe!(model, Scene(), roadway, 1)

    @test get_name(model) == "???"
    @test set_desired_speed!(model, 0.0) == FakeLaneChanger()
    @test_throws ErrorException rand(model)
end

@testset "MOBIL" begin
    timestep = 0.1
    lanemodel = MOBIL(timestep)

    @test get_name(lanemodel) == "MOBIL"
    set_desired_speed!(lanemodel,20.0)
    @test lanemodel.mlon.v_des == 20.0

    roadway = gen_straight_roadway(3, 1000.0)
    veh_state = VehicleState(Frenet(roadway[LaneTag(1,2)], 0.0), roadway, 10.)
    veh1 = Vehicle(veh_state, VehicleDef(), 1)
    veh_state = VehicleState(Frenet(roadway[LaneTag(1,2)], 20.0), roadway, 2.)
    veh2 = Vehicle(veh_state, VehicleDef(), 2)

    dt = 0.5
    n_steps = 10
    models = Dict{Int, DriverModel}()
    models[1] = Tim2DDriver(dt, mlane=MOBIL(dt))
    set_desired_speed!(models[1], 10.0)
    models[2] = Tim2DDriver(dt, mlane=MOBIL(dt))
    set_desired_speed!(models[2], 2.0)

    scene = Scene([veh1, veh2])

    rec = SceneRecord(n_steps, dt)
    simulate!(rec, scene, roadway, models, n_steps)

    @test scene[1].state.posF.roadind.tag == LaneTag(1, 3)
    @test scene[2].state.posF.roadind.tag == LaneTag(1, 1)

end

@testset "Tim2DDriver" begin
    timestep = 0.1
    drivermodel = Tim2DDriver(timestep)
    @test get_name(drivermodel) == "Tim2DDriver"

    set_desired_speed!(drivermodel,20.0)
    @test drivermodel.mlon.v_des == 20.0
    @test drivermodel.mlane.v_des == 20.0

    roadway = gen_straight_roadway(3, 1000.0)
    veh_state = VehicleState(Frenet(roadway[LaneTag(1,2)], 0.0), roadway, 10.)
    veh1 = Vehicle(veh_state, VehicleDef(), 1)
    veh_state = VehicleState(Frenet(roadway[LaneTag(1,2)], 10.0), roadway, 2.)
    veh2 = Vehicle(veh_state, VehicleDef(), 2)

    dt = 0.5
    n_steps = 10
    models = Dict{Int, DriverModel}()
    models[1] = Tim2DDriver(dt)
    set_desired_speed!(models[1], 10.0)
    models[2] = Tim2DDriver(dt)
    set_desired_speed!(models[2], 2.0)

    scene = Scene([veh1, veh2])

    rec = SceneRecord(n_steps, dt)
    simulate!(rec, scene, roadway, models, n_steps)

    @test scene[1].state.posF.roadind.tag == LaneTag(1, 3)
    @test scene[2].state.posF.roadind.tag == LaneTag(1, 2)
end

@testset "lane following" begin
    roadway = gen_straight_roadway(1, 500.0)

    models = Dict{Int, DriverModel}()
    models[1] = StaticLaneFollowingDriver()
    @test get_name(models[1]) == "ProportionalSpeedTracker"
    @test pdf(models[1], LaneFollowingAccel(-1.0)) ≈ 0.0
    @test logpdf(models[1], LaneFollowingAccel(-1.0)) ≈ -Inf
    models[2] = PrincetonDriver(k = 1.0)
    @test get_name(models[2]) == "PrincetonDriver"
    @test pdf(models[2], LaneFollowingAccel(-1.0)) == Inf
    @test logpdf(models[2], LaneFollowingAccel(-1.0)) == Inf
    set_desired_speed!(models[2], 5.0)
    @test models[2].v_des == 5.0
    models[3] = ProportionalSpeedTracker(k = 1.0)
    @test get_name(models[3]) == "ProportionalSpeedTracker"
    @test pdf(models[3], LaneFollowingAccel(-1.0)) == Inf
    @test logpdf(models[3], LaneFollowingAccel(-1.0)) == Inf
    set_desired_speed!(models[3], 5.0)
    @test models[3].v_des == 5.0

    veh_state = VehicleState(Frenet(roadway[LaneTag(1,1)], 0.0), roadway, 5.)
    veh1 = Vehicle(veh_state, VehicleDef(), 1)
    veh_state = VehicleState(Frenet(roadway[LaneTag(1,1)], 70.0), roadway, 5.)
    veh2 = Vehicle(veh_state, VehicleDef(), 2)
    veh_state = VehicleState(Frenet(roadway[LaneTag(1,1)], 130.0), roadway, 5.)
    veh3 = Vehicle(veh_state, VehicleDef(), 3)

    scene = Scene()
    push!(scene, veh1)
    push!(scene, veh2)
    push!(scene, veh3)

    n_steps = 40
    dt = 0.1
    rec = SceneRecord(n_steps, dt)
    simulate!(rec, scene, roadway, models, n_steps)

    @test isapprox(get_by_id(scene, 2).state.v, models[2].v_des, atol=1e-3)
    @test isapprox(get_by_id(scene, 3).state.v, models[3].v_des)

    # same wth noise
    models = Dict{Int, DriverModel}()
    models[1] = StaticLaneFollowingDriver()
    models[2] = PrincetonDriver(k = 1.0, v_des=5.0, σ=1.0, a=0.0)
    models[3] = ProportionalSpeedTracker(k = 1.0, v_des=5.0, σ=1.0, a=0.0)

    @test pdf(models[3], LaneFollowingAccel(0.0)) > 0.0
    @test logpdf(models[3], LaneFollowingAccel(0.0)) < 0.0

    @test pdf(models[2], LaneFollowingAccel(0.0)) > 0.0
    @test logpdf(models[2], LaneFollowingAccel(0.0)) < 0.0

    n_steps = 40
    dt = 0.1
    rec = SceneRecord(n_steps, dt)
    simulate!(rec, scene, roadway, models, n_steps)

    @test isapprox(get_by_id(scene, 2).state.v, models[2].v_des, atol=1.0)
    @test isapprox(get_by_id(scene, 3).state.v, models[3].v_des, atol=1.0)
end

function generate_sidewalk_env()
    roadway_length = 100.
    crosswalk_length = 15.
    crosswalk_width = 6.0
    crosswalk_pos = roadway_length/2
    sidewalk_width = 3.0
    sidewalk_pos = crosswalk_length/2 - sidewalk_width / 2
    # Generate straight roadway of length roadway_length with 2 lanes.
    # Returns a Roadway type (Array of segments).
    # There is already a method to generate a simple straight roadway, which we use here.
    roadway = gen_straight_roadway(2, roadway_length)

    # Generate the crosswalk.
    # Our crosswalk does not have a predefined method for generation, so we define it with a LaneTag and a curve.
    n_samples = 2 # for curve generation
    crosswalk = Lane(LaneTag(2,1), gen_straight_curve(VecE2(crosswalk_pos, -crosswalk_length/2),
                                                    VecE2(crosswalk_pos, crosswalk_length/2),
                                                    n_samples), width = crosswalk_width)
    cw_segment = RoadSegment(2, [crosswalk])
    push!(roadway.segments, cw_segment) # Append the crosswalk to the roadway

    # Generate the sidewalk.
    top_sidewalk = Lane(LaneTag(3, 1), gen_straight_curve(VecE2(0., sidewalk_pos),
                                                        VecE2(roadway_length, sidewalk_pos),
                                                            n_samples), width = sidewalk_width)
    bottom_sidewalk = Lane(LaneTag(3, 2), gen_straight_curve(VecE2(0., -(sidewalk_pos - sidewalk_width)),
                                                            VecE2(roadway_length, -(sidewalk_pos - sidewalk_width)),
                                                                n_samples), width = sidewalk_width)
    # Note: we subtract the sidewalk_width from the sidewalk position so that the edge is flush with the road.
    sw_segment = RoadSegment(3, [top_sidewalk, bottom_sidewalk])
    push!(roadway.segments, sw_segment)
    sidewalk = [top_sidewalk, bottom_sidewalk]
    return roadway, crosswalk, sidewalk
end

@testset "sidewalk pedestrian" begin
    roadway, crosswalk, sidewalk = generate_sidewalk_env()
    # dummy test for the constructor
    ped=SidewalkPedestrianModel(timestep=0.1,
                                crosswalk= roadway[LaneTag(1,1)],
                                sw_origin = roadway[LaneTag(1,1)],
                                sw_dest = roadway[LaneTag(1,1)]
                                )
    @test ped.ttc_threshold >= 1.0
    @test get_name(ped) == "SidewalkPedestrianModel"

    timestep = 0.1

    # Crossing pedestrian definition
    ped_init_state = VehicleState(VecSE2(49.0,-3.0,0.), sidewalk[2], roadway, 1.3)
    ped = Vehicle(ped_init_state, VehicleDef(AgentClass.PEDESTRIAN, 1.0, 1.0), 1)

    # Car definition
    car_initial_state = VehicleState(VecSE2(0.0, 0., 0.), roadway.segments[1].lanes[1],roadway, 8.0)
    car = Vehicle(car_initial_state, VehicleDef(), 2)

    scene = Scene()
    push!(scene, ped)
    push!(scene, car)

    # Define a model for each entity present in the scene
    models = Dict{Int, DriverModel}()

    ped_id = 1
    car_id = 2

    models[ped_id] = SidewalkPedestrianModel(timestep=timestep,
                                                crosswalk= crosswalk,
                                                sw_origin = sidewalk[2],
                                                sw_dest = sidewalk[1]
                                                )

    models[car_id] =
    LatLonSeparableDriver( # produces LatLonAccels
            ProportionalLaneTracker(), # lateral model
            IntelligentDriverModel(), # longitudinal model
    )

    nticks = 300
    rec = SceneRecord(nticks+1, timestep)
    # Execute the simulation
    simulate!(rec, scene, roadway, models, nticks)

    ped = get_by_id(rec[0], ped_id)
end


TV.as(μ::AbstractWeightedMeasure) = TV.as(μ.base)


# using ScikitLearn
# @sk_import covariance: MinCovDet

import ..Utilities
abstract type SdErgm end

Base.string(x::SdErgm) = DynNets.name(x)
export string

function get_option(model::SdErgm, optName)

    defaultOptions = typeof(model)(model.staticModel).options

    if haskey(model.options, optName)
        return getindex(model.options, optName)

    elseif haskey(model.defaultOptions, optName)
        return getindex(defaultOptions, optName)
    else
        error("No option found")
    end
end

identify(model::SdErgm,UnPar::Array{<:Real,1}, idType ) = StaticNets.identify(model.staticModel, UnPar; idType = idType)

number_ergm_par(model::T where T <:SdErgm) = model.staticModel.nErgmPar

type_of_obs(model::SdErgm) =  StaticNets.type_of_obs(model.staticModel)

stats_from_mat(model::SdErgm, A ::Matrix{<:Real}) = StaticNets.stats_from_mat(model.staticModel, A ::Matrix{<:Real})

"""
Model to use for utilities and eventually fast sampling. The main use is to point pmle models to their mle counterpart, which can be used for sampling or for comparison purposes
"""
reference_model(model::SdErgm) = model

sample_ergm_sequence(model::SdErgm, N, parVecSeq_T::Matrix, nSample) = StaticNets.sample_ergm_sequence(model.staticModel, N, parVecSeq_T, nSample)

function seq_of_obs_from_seq_of_mats(model::T where T <:SdErgm, AT_in)

    AT = convert_to_array_of_mats(AT_in)
    T = length(AT)
    obsT = Array{type_of_obs(model), 1}(undef, T)
    for t in 1:T
        obsT[t] = DynNets.stats_from_mat(model, AT[t])
    end
    return obsT
end

#region options and conversions of parameters for optimization
function setOptionsOptim(model::T where T<: SdErgm; show_trace = false)
    "Set the options for the optimization required in the estimation of the model.
    For the optimization use the Optim package."
    tol = eps()*10
    maxIter = 300
    call_back = tr -> isnan(tr[end].value) |(!isfinite(tr[end].value))
    opt = Optim.Options(  g_tol = 1e-8,
                     x_tol = tol,
                     x_abstol = tol,
                     x_reltol = tol,
                     f_tol = tol,
                     f_reltol = tol,
                     f_abstol = tol,
                     iterations = maxIter,
                     show_trace = show_trace ,#false,#
                     store_trace = true ,#false,#
                     callback = call_back,
                     show_every=5)

    algo = NewtonTrustRegion(; initial_delta = 0.1,
                    delta_hat = 0.2,
                    eta = 0.1,
                    rho_lower = 0.25,
                    rho_upper = 0.75)
    algo = Newton(; alphaguess = LineSearches.InitialHagerZhang(),
    linesearch = LineSearches.BackTracking())
      return opt, algo
end

"""
Convert vector conaining both SD and constant parameters into array with one vector of static parameters for each ergm parameter. The vector can have eithre 1 or 3 elements, depending on wether the element is static or dynamical
"""
function vec_2_array_all_par(model::SdErgm, VecGasPar::Array{<:Real,1}, indTvPar :: BitArray{1})

    Npar = length(indTvPar)
    arrayAllPar = [zeros(Real, 1 + tv*2) for tv in indTvPar]#Array{Array{<:Real,1},1}(undef, Npar)
    last_i = 1

    for i=1:Npar
        nStatPerDyn = 1 + 2*indTvPar[i]
        arrayAllPar[i][:] = VecGasPar[last_i: last_i+nStatPerDyn-1]
            last_i = last_i + nStatPerDyn
    end
    return arrayAllPar
end

"""
The inverse of vec_2_array_all_par
"""
function array_2_vec_all_par(model::SdErgm, ArrayGasPar, indTvPar :: BitArray{1})

        NTvPar = sum(indTvPar)
        Npar = length(indTvPar)
        NgasPar = 2*NTvPar + Npar

        VecGasPar = zeros(NgasPar)
        last = 1
        for i=1:Npar
            VecGasPar[last:last+length(ArrayGasPar[i])-1] = ArrayGasPar[i]
            last = last + length(ArrayGasPar[i])
        end
        return VecGasPar
end

"""
Given vecAllPar divide it into a vector of Score Driven parameters and one of costant parameters
"""
function divide_SD_par_from_const(model::T where T <:SdErgm,  vecAllPar::Array{<:Real,1}; indTvPar = model.indTvPar)

    nTvPar = sum(indTvPar)
    nErgmPar = length(indTvPar)

    vecSDParAll = zeros(Real,3nTvPar )
    vConstPar = zeros(Real,nErgmPar-nTvPar)

    lastInputInd = 0
    lastIndSD = 0
    lastConstInd = 0
    #extract the vector of static SD parameters
    for i=1:nErgmPar
        if indTvPar[i]
            vecSDParAll[lastIndSD+1] = vecAllPar[lastInputInd + 1]
            vecSDParAll[lastIndSD+2] = vecAllPar[lastInputInd + 2]
            vecSDParAll[lastIndSD+3] = vecAllPar[lastInputInd + 3]
            lastInputInd +=3
            lastIndSD +=3
        else
            vConstPar[lastConstInd+1] = vecAllPar[lastInputInd  + 1]
            lastInputInd +=1
            lastConstInd +=1
        end
    end
    return vecSDParAll, vConstPar
end

function merge_SD_par_and_const(model::T where T <:SdErgm, vecSDPar::Array{<:Real,1}, vConstPar, indTvPar=model.indTvPar)

    nTvPar = sum(indTvPar)
    nConstPar = sum(.!indTvPar)
    nConstPar == length(vConstPar) ? () : error()

    nErgmPar = length(indTvPar)
    nAllPar = 3*nTvPar + nConstPar

    vecAllPar = zeros(Real, nAllPar)

    lastInputInd = 0
    lastConstInd = 0
    lastIndAll = 0
    lastIndSD = 0
    for i=1:nErgmPar
        if indTvPar[i]

            vecAllPar[lastIndAll+1] = vecSDPar[lastIndSD + 1]
            vecAllPar[lastIndAll+2] = vecSDPar[lastIndSD + 2]
            vecAllPar[lastIndAll+3] = vecSDPar[lastIndSD + 3]

            lastIndAll +=3
            lastIndSD +=3
        else
            vecAllPar[lastIndAll+1] = vConstPar[lastConstInd + 1]

            lastIndAll +=1
            lastInputInd +=1
            lastConstInd +=1
        end
    end
    return vecAllPar
end

"""
Restrict the  Score Driven parameters  to appropriate link functions to ensure that they remain in the region where the SD dynamics is well specified (basically 0<=B<1  A>=0)
"""
function restrict_SD_static_par(model::T where T <:SdErgm, vecUnSDPar::Array{<:Real,1})

    nSDPar = length(vecUnSDPar)
    nTvPar, rem = divrem(nSDPar,3)

    rem == 0 ? () : error()

    arrayOfVecsReSd = [ [vecUnSDPar[i], link_R_in_0_1(vecUnSDPar[i+1]), link_R_in_R_pos(vecUnSDPar[i+2]) ] for i in 1:3:nSDPar]

    vecReSDPar = reduce(vcat, arrayOfVecsReSd)

    return vecReSDPar
end

"""
    inverse of restrict_SD_static_par
"""
function unrestrict_SD_static_par(model::T where T <:SdErgm, vecReSDPar::Array{<:Real,1})

    nSDPar = length(vecReSDPar)
    nTvPar, rem = divrem(nSDPar,3)

    rem == 0 ? () : error()

    arrayOfVecsUnSd = [ [vecReSDPar[i], inv_link_R_in_0_1(vecReSDPar[i+1]), inv_link_R_in_R_pos(vecReSDPar[i+2]) ] for i in 1:3:nSDPar]

    vecUnSDPar = reduce(vcat, arrayOfVecsUnSd)

    return vecUnSDPar
end

"""
    separate SD parameters from constant ones, unrestrict SD and merge them back
"""
function unrestrict_all_par(model::T where T <:SdErgm, vAllPar)

    vSDRe, vConst = divide_SD_par_from_const(model, vAllPar)

    vSDUn = unrestrict_SD_static_par(model, vSDRe)

    merge_SD_par_and_const(model, vSDUn, vConst)
end

"""
    separate SD parameters from constant ones, restrict SD and merge them back
"""
function restrict_all_par(model::T where T <:SdErgm, vAllPar)

    vSDUn, vConst = divide_SD_par_from_const(model, vAllPar)

    vSDRe = restrict_SD_static_par(model, vSDUn)

    merge_SD_par_and_const(model, vSDRe, vConst)
end

"""
Map subset of parameters for integrated SD model (only A parameters and constant ergm parameters) to a full vector of parameters containing all static ergm parameters, w, B, and A. Setting w.=0 and B .=1
"""
function par_integr_2_full_unrestr(model, vecUnParIntegratedAll)
    NTvPar = sum(model.indTvPar)
    vecUnSDParA = vecUnParIntegratedAll[model.indTvPar]
    vConstPar = vecUnParIntegratedAll[.!model.indTvPar]

    vecUnSDPar = zeros(Real, 3*NTvPar)

    vecUnSDPar[1:3:end] .= 0
    vecUnSDPar[2:3:end] .= Inf
    vecUnSDPar[3:3:end] .= vecUnSDParA
    vecUnPar = merge_SD_par_and_const(model, vecUnSDPar, vConstPar)

    return vecUnPar
end

"""
Given the flag of constant parameters, a starting value for their unconditional means (their constant value, for those constant), return a starting point for the optimization
"""
function starting_point_optim(model::T where T <:SdErgm,  UM; indTargPar =  falses(100))
    indTvPar=model.indTvPar
    nTvPar = sum(indTvPar)
    nErgmPar = length(indTvPar)

    # #set the starting points for the optimizations
    B0_Re  = 0.98; B0_Un = log(B0_Re ./ (1 .- B0_Re ))
    ARe_min =0.0000000001
    A0_Re  = 0.01 ; A0_Un = log(A0_Re  .-  ARe_min)

    # starting values for the vector of parameters that have to be optimized
    vParOptim_0 = zeros(nErgmPar + nTvPar*2 )
    last = 0
    for i=1:nErgmPar
        if indTvPar[i]
            if indTargPar[i]
                vParOptim_0[last+1:last+2] = [ B0_Un; A0_Un]
                last+=2
            else
                vParOptim_0[last+1:last+3] = [UM[i]*(1 .- B0_Re) ; B0_Un; A0_Un]
                last+=3
            end
        else
            vParOptim_0[last+1] = UM[i]
            last+=1
        end
    end
    @debug "[starting_point_optim][UM = $UM, vParOptim_0 = $vParOptim_0, ARe_min = $ARe_min]"
    return vParOptim_0, ARe_min
end

convert_to_array_of_mats(AT::Array{Array{<:Any, 2}, 1}) = AT
convert_to_array_of_mats(AT::Array{<:Any, 1}) = [m for m in AT]
convert_to_array_of_mats(AT::Array{<:Any, 3}) = [AT[:,:,t] for t in 1:size(AT)[3]]

#endregion

target_function_t(model::SdErgm, obs_t, N, f_t) = StaticNets.obj_fun(model.staticModel, obs_t, N, f_t)

function target_function_t_grad(model::T where T<: SdErgm, obs_t, N, f_t)

    target_fun_t(x) = target_function_t(model, obs_t, N, x)

    grad_tot_t = ForwardDiff.gradient(target_fun_t, f_t)

    return grad_tot_t
end

function target_function_t_hess(model::T where T<: SdErgm, obs_t, N, f_t)

    target_fun_t(x) = target_function_t(model, obs_t, N, x)

    hess_tot_t = ForwardDiff.hessian(target_fun_t, f_t)

    return hess_tot_t
end

function target_function_t_fisher(model::T where T<: SdErgm, obs_t, N, f_t)

    error("need to code the fisher manually, cannot be obtained by Automatic Differentiation in the general case, only when the fisher equality holds")
    return fisher_info_t
end

function updatedGasPar( model::T where T<: SdErgm, obs_t, N, ftot_t::Array{<:Real,1}, I_tm1::Array{<:Real,2}, indTvPar::BitArray{1}, Wgas::Array{<:Real,1}, Bgas::Array{<:Real,1}, Agas::Array{<:Real,1})

    #= likelihood and gradients depend on all the parameters (ftot_t), but
    only the time vaying ones (f_t) are to be updated=#

    target_fun_val_t = target_function_t(model, obs_t, N, ftot_t)

    grad_tot_t = target_function_t_grad(model, obs_t, N, ftot_t)

    I_reg = I(size(I_tm1)[1])

    if model.scoreScalingType =="HESS_D"
        hess_tot_t = target_function_t_hess(model, obs_t, N, ftot_t)
        I_updt = -hess_tot_t
        I_t = I_updt
        s_t = (grad_tot_t./I_t[I(size(I_t)[1])])[indTvPar]
    elseif model.scoreScalingType =="MAX_LINKS"
        hess_tot_t = target_function_t_hess(model, obs_t, N, ftot_t)
        I_t = ones(size(grad_tot_t)).*(N^2-N)
        s_t = grad_tot_t[indTvPar]./I_t
    elseif model.scoreScalingType =="HESS"
        hess_tot_t = target_function_t_hess(model, obs_t, N, ftot_t)
        I_updt = -hess_tot_t
        I_t = I_updt
        s_t = (I_t\grad_tot_t)[indTvPar]

    elseif model.scoreScalingType =="FISH_D"
        extremeScaledScore = 200
        fish_tot_t = target_function_t_fisher(model, obs_t, N, ftot_t)
        I_updt = fish_tot_t
        I_t = I_updt
        s_t = clamp.( (grad_tot_t./sqrt.((I_t[I(size(I_t)[1])] )))[indTvPar], -extremeScaledScore, extremeScaledScore)

    end

    f_t = ftot_t[indTvPar] #Time varying ergm parameters
    f_tp1 = Wgas .+ Bgas.* f_t .+ Agas.*s_t

    ftot_tp1 = copy(ftot_t)
    ftot_tp1[indTvPar] = f_tp1 #of all parameters udate dynamic ones with GAS

    #any(isnan.(ftot_tp1)) ? error() : ()

    # I_t is the inverse of the S_t matrix in typical notations

    return ftot_tp1, target_fun_val_t, I_t, grad_tot_t
end

function score_driven_filter( model::T where T<: SdErgm, N, obsT, vResGasPar::Array{<:Real,1}, indTvPar::BitArray{1}; vConstPar ::Array{<:Real,1} = zeros(Real,2), ftot_0::Array{<:Real,1} = zeros(Real,2))

    nErgmPar = number_ergm_par(model)
    NTvPar   = sum(indTvPar)
    T= length(obsT)

    # Organize parameters of the GAS update equation
    Wvec = vResGasPar[1:3:3*NTvPar]
    Bvec = vResGasPar[2:3:3*NTvPar]
    Avec = vResGasPar[3:3:3*NTvPar]

    #compute unconditional means according to SD dgp
    UMallPar = zeros(Real,nErgmPar)
    UMallPar[indTvPar] =  Wvec ./ (1 .- Bvec)
    if !all(indTvPar) # if not all parameters are time varying
        UMallPar[.!indTvPar] = vConstPar
    end

    sum(ftot_0)==0 ? ftot_0 = UMallPar : ()# identify(model,UMallNodesIO)

    fVecT = ones(Real,nErgmPar,T)
    sVecT = ones(Real,nErgmPar,T)
    invScalMatT = ones(Real,nErgmPar, nErgmPar,T)
    logLikeVecT = ones(Real,T)

    if NTvPar==0
        I_tm1 = ones(1,1)
    else
        I_tm1 = Float64.(Diagonal{Real}(I,NTvPar))
    end

    fVecT[:,1] = ftot_0
    if sum(.!indTvPar) >0
        fVecT[.!indTvPar,1] = vConstPar
    end

    ftot_tp1 = zeros(nErgmPar)
    for t=1:T

        N_t = Utilities.get_N_t(N, t)

        obs_t = obsT[t]

        #obj fun at time t is objFun(obs_t, f_t)

        # predictive step
        ftot_tp1, logLikeVecT[t], invScalMatT[:,:,t], sVecT[:,t] = updatedGasPar(model, obs_t, N_t, fVecT[:, t], I_tm1, indTvPar, Wvec, Bvec, Avec)

        I_tm1 = invScalMatT[:,:,t]

        # would the following be the update step instead ??
        #fVecT[:,t], loglike_t, I_tm1, grad_t = updatedGasPar(model, obs_t, N, fVecT[:,t-1], I_tm1, indTvPar, Wvec, Bvec, Avec)

        if t!=T
            fVecT[:,t+1] = ftot_tp1
        end
    end

    return fVecT::Array{<:Real, 2}, logLikeVecT::Vector{<:Real}, sVecT::Array{<:Real,2}, invScalMatT::Array{<:Real, 3}
end

function score_driven_dgp( model::T where T<: SdErgm, N, dgpNT, vResGasPar::Array{<:Real,1}, indTvPar::BitArray{1}; vConstPar ::Array{<:Real,1} = zeros(Real,2), ftot_0::Array{<:Real,1} = zeros(Real,2))

    nErgmPar = number_ergm_par(model)
    NTvPar   = sum(indTvPar)

    T = dgpNT[2]
    N_const = dgpNT[1]
    N_T = ones(Int, T) .* N_const
    N = N_const

    # Organize parameters of the GAS update equation
    Wvec = vResGasPar[1:3:3*NTvPar]
    Bvec = vResGasPar[2:3:3*NTvPar]
    Avec = vResGasPar[3:3:3*NTvPar]

    # start values equal the unconditional mean,and  constant ones remain equal to the unconditional mean, hence initialize as:
    UMallPar = zeros(Real,nErgmPar)
    UMallPar[indTvPar] =  Wvec ./ (1 .- Bvec)
    if !all(indTvPar) # if not all parameters are time varying
        UMallPar[.!indTvPar] = vConstPar
    end

    fVecT = ones(Real,nErgmPar,T)
    sVecT = ones(Real,nErgmPar,T)
    invScalMatT = ones(Real,nErgmPar, nErgmPar,T)
    logLikeVecT = ones(Real,T)

    sum(ftot_0)==0 ? ftot_0 = UMallPar : ()# identify(model,UMallNodesIO)

    if NTvPar==0
        I_tm1 = ones(1,1)
    else
        I_tm1 = Float64.(Diagonal{Real}(I,NTvPar))
    end

    loglike = 0

    A_T = zeros(Int8, N, N, T)

    fVecT[:,1] = ftot_0

    ftot_tp1 = zeros(nErgmPar)
    for t=1:T
    #    println(t)

        diadProb = StaticNets.diadProbFromPars(StaticNets.ErgmDirBin0Rec0(), fVecT[:, t] )

        A_t = StaticNets.samplSingMatCan(StaticNets.ErgmDirBin0Rec0(), diadProb, N)

        A_T[:, : , t] = A_t

        obs_t = stats_from_mat(model, A_t)

        #obj fun at time t is objFun(obs_t, f_t)

        # predictive step
        ftot_tp1, logLikeVecT[t], invScalMatT[:,:,t], sVecT[:,t] = updatedGasPar(model, obs_t, N, fVecT[:, t], I_tm1, indTvPar, Wvec, Bvec, Avec)

        I_tm1 = invScalMatT[:,:,t]

        # would the following be the update step instead ??
        #fVecT[:,t], loglike_t, I_tm1, grad_t = updatedGasPar(model, obs_t,  N, fVecT[:,t-1], I_tm1, indTvPar, Wvec, Bvec, Avec)

        if t!=T
            fVecT[:,t+1] = ftot_tp1
        end
    end

    return fVecT::Array{Float64, 2}, A_T, sVecT, invScalMatT

end

estimate_single_snap_sequence(model::T where T<: SdErgm, obsT, aggregate=0) = StaticNets.estimate_sequence(model.staticModel, obsT)

"""
Sequence of loglikelihoods from SD filter as a function of unrestricted parameters (vector of both static ergm and static SD) and vector of initial parameters
"""
function seq_loglike_sd_filter(model::SdErgm, N, obsT,  vecUnPar::Array{<:Real,1}, ftot_0::T where T<:AbstractArray)

        vecUnSDPar, vConstPar = divide_SD_par_from_const(model, vecUnPar)

        oneInADterms  = (StaticNets.maxLargeVal + vecUnPar[1])/StaticNets.maxLargeVal

        vecReSDPar = restrict_SD_static_par(model, vecUnSDPar)

        foo, logLikeVecT, foo1 = score_driven_filter( model, N, obsT, vecReSDPar, model.indTvPar;  vConstPar =  vConstPar, ftot_0 = ftot_0 .* oneInADterms)

        return logLikeVecT
end

function seq_loglike_sd_filter(model, N, obsT,  vecUnPar::Array{<:Real,1}, ftot_0Fun::Function)
    @debug "[seq_loglike_sd_filter][N=$N, obs_1_sample = $(obsT[1][1:5,:]), vecUnPar=$vecUnPar, ftot_0Fun = $ftot_0Fun ]"

        vecUnSDPar, vConstPar = divide_SD_par_from_const(model, vecUnPar)

        vecReSDPar = restrict_SD_static_par(model, vecUnSDPar)

        ftot_0 = ftot_0Fun(vecReSDPar, vConstPar)

        return seq_loglike_sd_filter(model, N, obsT, vecUnPar, ftot_0)
end

"""
Estimate the SD and ergm static parameters
"""
function estimate(model::T where T<: SdErgm, N, obsT; indTvPar::BitArray{1}=model.indTvPar, indTargPar::BitArray{1} = falses(length(model.indTvPar)), initMeth :: String = get_option(model, "initMeth"), ftot_0Fixed :: Array{<:Real,1} = zeros(2), nObsFirstEst=10,  vParOptim_0 =zeros(2), shuffleObsInds::Union{Nothing, Vector{<:Int}} = nothing, show_trace = false )
    @debug "[estimate][start][model=$model, indTvPar=$indTvPar, indTargPar=$indTargPar, initMeth=$initMeth, ftot_0Fixed = $ftot_0Fixed,  vParOptim_0 = $vParOptim_0, shuffleObsInds=$shuffleObsInds, model.options = $(model.options)]"

    T = length(obsT);
    nErgmPar = number_ergm_par(model)
    NTvPar = sum(indTvPar)
    Logging.@debug( "[estimate][Estimating N = $N , T=$T]")

    optims_opt, algo = setOptionsOptim(model; show_trace = show_trace )

    staticPars = static_estimate(model, obsT)

    if sum(vParOptim_0) == 0
        vParOptim_0_tmp, ARe_min = starting_point_optim(model, staticPars; indTargPar = indTargPar)
        vParOptim_0 = vParOptim_0_tmp
        @debug "[estimate][using standard starting point][$(vParOptim_0_tmp)]"
    end

    ftot_0Fun(vecReSDPar, vConstPar) = ftot_0Fixed
    @show ftot_0Fixed

    # How should we handle the initialization of SD iteration ?
    if (initMeth == "uncMean") & (get_option(model,  "integrated") )

        error("Integrated filters cannot use the unconditional mean, using $initMeth instead")
    end

    if (initMeth == "uncMean") & (!get_option(model,  "integrated"))

        function ftot_0Fun(vecReSDPar, vConstPar)
            w = vecReSDPar[1:3:end]
            B = vecReSDPar[2:3:end]

            # unconditional mean of SD parameters
            UmSDPar  = unconditional_mean_auto_regr(w, B)

            ftot_0 = zeros(Real, nErgmPar)

            ftot_0[indTvPar] = UmSDPar
            ftot_0[.!indTvPar] = vConstPar
            return ftot_0
        end
    else
        if initMeth == "estimateFirstObs"
            ftot_0Fixed =  static_estimate(model, obsT[1:nObsFirstEst])
            ftot_0Fun(vecReSDPar, vConstPar) = ftot_0Fixed

        elseif typeof(initMeth) <: AbstractArray

            if length(ftot_0Fixed) == nErgmPar

                ftot_0Fixed =  static_estimate(model, obsT[1:nObsFirstEst])
                ftot_0Fun(vecReSDPar, vConstPar) = ftot_0Fixed
            else
                error("wrong length of initial ergm parameters")
            end
        end
    end

    #define the objective function for the optimization

    if !isnothing(shuffleObsInds)
       # was observations shuffing required ?  (used for explorative analysis on non-parametric bootstrap)
        length(shuffleObsInds) == T ? () : error("wrong lenght of shuffling indices")

    else
        # No shuffling required
        shuffleObsInds = collect(1:T)
    end

    #shuffled observations loglikelihood
    function objfunSdOptShuffled(vecUnPar::Array{<:Real,1})

        logLikeVecT = seq_loglike_sd_filter(model, N, obsT, vecUnPar, ftot_0Fun)
        return - sum(logLikeVecT[shuffleObsInds])
    end

    ADobjfunSdOpt = TwiceDifferentiable(objfunSdOptShuffled, vParOptim_0; autodiff = :forward);

    if haskey(model.options, "Firth")
        if model.options["Firth"]
            # log likelihood with firth additional term
            function objfunSdOptFirth(vecUnPar::Array{<:Real,1})
                objfunSdOpt(vecUnPar) -  0.5 * LinearAlgebra.logabsdet( FiniteDiff.finite_difference_hessian(objfunSdOpt, vecUnPar))[1]
            end
            ADobjfunSdOpt = TwiceDifferentiable(objfunSdOptFirth, vParOptim_0; autodiff = :forward);
        end
    end

    if get_option(model,  "integrated")
        # should we estimate  an integrated version of the SD filter?
        vParOptim_0 = ones(Real, NTvPar) * 0.001 #vParOptim_0[3:3:end]

        objfunSdOptInt(vecUnParIntegratedAll::Array{<:Real,1}) = objfunSdOptShuffled(par_integr_2_full_unrestr(model, vecUnParIntegratedAll))

        ADobjfunSdOpt = TwiceDifferentiable(objfunSdOptInt, vParOptim_0; autodiff = :forward);
    end

    #Run the optimization

    Logging.@debug("[estimate][Starting point for Optim $vParOptim_0]")
    Logging.@debug("[estimate][Starting point for Optim $(restrict_all_par(model, vParOptim_0))]")
    optim_out2  = optimize(ADobjfunSdOpt, vParOptim_0, algo, optims_opt)
    outParAllUn = Optim.minimizer(optim_out2)
    if get_option(model,  "integrated")
        outParAllUnFull = zeros(3*NTvPar)
        outParAllUnFull[1:3:end] .= 0
        outParAllUnFull[2:3:end] .= Inf
        outParAllUnFull[3:3:end] .= outParAllUn
        outParAllUn = deepcopy(outParAllUnFull)
    end

    Logging.@debug(optim_out2)

    vecAllReParHat = restrict_all_par(model, outParAllUn)

    vecParGasHat, vecParConstHat = divide_SD_par_from_const(model, vecAllReParHat)

    Logging.@debug("[estimate][Final paramters SD: $vecParGasHat , constant: $vecParConstHat ]")

    arrayAllParHat = vec_2_array_all_par(model, vecAllReParHat, indTvPar)

    ftot_0Hat = ftot_0Fun(vecParGasHat, vecParConstHat)

    conv_flag =  Optim.converged(optim_out2)

    @debug "[estimate][end]"
    return  (; arrayAllParHat, conv_flag, UM =zeros(2), ftot_0Hat)

end


function (store::CSVExporter{<:AbstractSchema})(path::Path, li::LabelledIntervals)
    # Convert labelled intervals to a dataframe.
    labelled_intervals = get_labelled_intervals(li)
    df = DataFrame(label = String[], start = Int64[], stop = Int64[], first = Float64[], step = Float64[], last  = Float64[])
    for (key, labelled_interval) in pairs(labelled_intervals)
        nested_interval = labelled_interval.nested_interval
        start = get_start(nested_interval)
        stop = get_stop(nested_interval)
        interval = get_interval(nested_interval)
        push!(df, [key start stop first(interval) step(interval) last(interval)])
    end
    Base.display(df)
    store(path, df)
end


# UnitCommitment.jl: Optimization Package for Security-Constrained Unit Commitment
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.

"""
    slice(instance, range)

Creates a new instance, with only a subset of the time periods.
This function does not modify the provided instance. The initial
conditions are also not modified.

Example
-------

    # Build a 2-hour UC instance
    instance = UnitCommitment.read_benchmark("test/case14")
    modified = UnitCommitment.slice(instance, 1:2)

"""
function slice(
    instance::UnitCommitmentInstance,
    range::UnitRange{Int},
)::UnitCommitmentInstance
    modified = deepcopy(instance)
    modified.time = length(range)
    modified.power_balance_penalty = modified.power_balance_penalty[range]
    modified.reserves.spinning = modified.reserves.spinning[range]
    for u in modified.units
        u.max_power = u.max_power[range]
        u.min_power = u.min_power[range]
        u.must_run = u.must_run[range]
        u.min_power_cost = u.min_power_cost[range]
        u.provides_spinning_reserves = u.provides_spinning_reserves[range]
        for s in u.cost_segments
            s.mw = s.mw[range]
            s.cost = s.cost[range]
        end
    end
    for b in modified.buses
        b.load = b.load[range]
    end
    for l in modified.lines
        l.normal_flow_limit = l.normal_flow_limit[range]
        l.emergency_flow_limit = l.emergency_flow_limit[range]
        l.flow_limit_penalty = l.flow_limit_penalty[range]
    end
    for ps in modified.price_sensitive_loads
        ps.demand = ps.demand[range]
        ps.revenue = ps.revenue[range]
    end
    return modified
end


module DiffCheckNumbers

using SpecialFunctions
import NaNMath
import Calculus

include("Constructor.jl")
include("ConversionPromotion.jl")
include("Helper.jl")
include("Arithmetic.jl")
include("Calculus.jl")

export DiffCheck, value, derivatives, StaticTuple

end # module


# TESTSET FOR ALL ABSTRACT TYPES
# mainly test dependencies of abstract types and availability
@testset "Abstract Type Definitions" begin

    #-----------------
    #   SITES
    #-----------------
    @testset "AbstractSite" begin

        @test AbstractSite{String, 2} <: AbstractSite{L,D} where {L,D}
        @test AbstractSite{Int64, 3}  <: AbstractSite{L,D} where {L,D}

    end

    #-----------------
    #   BONDS
    #-----------------
    @testset "AbstractBond" begin

        @test AbstractBond{String, 2} <: AbstractBond{L,N} where {L,N}
        @test AbstractBond{Int64, 3}  <: AbstractBond{L,N} where {L,N}

    end

    #-----------------
    #   UNITCELLS
    #-----------------
    @testset "AbstractUnitcell" begin

        @test AbstractUnitcell{AbstractSite{String,3}, AbstractBond{Int64, 3}}  <: AbstractUnitcell{S, B} where {S,B}
        @test AbstractUnitcell{AbstractSite{Int64,2}, AbstractBond{Float64, 2}} <: AbstractUnitcell{S, B} where {S,B}

    end

    #-----------------
    #   LATTICES
    #-----------------
    @testset "AbstractLattice" begin

        @test AbstractLattice{AbstractSite{String,3}, AbstractBond{Int64, 3}, AbstractUnitcell{AbstractSite{String,3}, AbstractBond{Int64, 3}}}  <: AbstractLattice{S, B, U} where {S,B,U}
        @test AbstractLattice{AbstractSite{Int64,2}, AbstractBond{Float64, 2}, AbstractUnitcell{AbstractSite{String,3}, AbstractBond{Int64, 3}}} <: AbstractLattice{S, B, U} where {S,B,U}

    end

# end the testset
end


abstract type AbstractProperties end

mutable struct Properties{T<:Any}<: AbstractProperties
    name::String
    value::T

    Properties() = new{Float64}(" ", 0.0)
    Properties(name::String) = new{Float64}(name, 0.0)
    function Properties{U}(name::String, val::U) where U<:Any
        new{U}(name, val)
    end
end

mutable struct Energy <: AbstractProperties
    name::String
    value::Float64

    Energy() = new("energy", 0.0)
    Energy(val::Float64) = new("energy", val)
    Energy(val::Real) = Energy(Float64(val))
end

function Base.eltype(prop::AbstractProperties)
    return typeof(prop.value)
end

function (prop::AbstractProperties)()
    return prop.value
end

function (prop::AbstractProperties)(val)
    if typeof(val) != eltype(prop)
        throw(ArgumentError("Wrong type for functor."))
    else
        prop.value = val
    end
end

function Base.show(io::IO, prop::AbstractProperties)
    print(io, prop.name, "=", prop.value)
end

mutable struct Atoms
    atom::Array{AbstractAtom, 1}
    number::Int64
    properties::Array{AbstractProperties, 1}

    Atoms() = new(Array{Atom, 1}(undef, 0), 0, Array{Properties, 1}(undef, 0))
    function Atoms(atom::Array{T1, 1}, num::Int64, prop::Array{T2, 1}) where T1<:AbstractAtom where T2<:AbstractProperties
        if num == length(atom)
            new(atom, num, prop)
        else
            throw(BoundsError(atom, num))
        end
    end

    function Atoms(atom::Array{T1, 1}, prop::Array{T2, 1}) where T1<:AbstractAtom where T2<:AbstractProperties
        Atoms(atom, length(atom), prop)
    end

    function Atoms(atom::Array{T, 1}) where T<:AbstractAtom
        Atoms(atom, length(atom), Array{Properties, 1}(undef, 0))
    end

    function Atoms(atom::T1, prop::Array{T2, 1}) where T1<:AbstractAtom where T2<:AbstractProperties
        Atoms([atom, ], 1, prop)
    end

    function Atoms(atom::AbstractAtom)
        Atoms([atom, ], 1, Array{Properties, 1}(undef, 0))
    end

    function Atoms(atom::Array{T, 1}, num::Int64, prop::AbstractProperties) where T<:AbstractAtom
        if num == length(atom)
            new(atom, num, [prop, ])
        else
            throw(BoundsError(atom, num))
        end
    end

    function Atoms(atom::Array{T, 1}, prop::AbstractProperties) where T<:AbstractAtom
        Atoms(atom, length(atom), [prop, ])
    end

    function Atoms(atom::AbstractAtom, prop::AbstractProperties)
        Atoms([atom, ], 1, [prop, ])
    end
end

# add new atom
function addAtom!(atoms::Atoms, atom::AbstractAtom)
    push!(atoms.atom, atom)
    atoms.number += 1
end

# add new property
function addProperty!(atoms::Atoms, prop::AbstractProperties)
    push!(atoms.properties, prop)
end

function Base.iterate(atoms::Atoms, state=1)
    state > atoms.number ? nothing : (atoms.atom[state], state+1)
end

function Base.length(atoms::Atoms)
    atoms.number
end

function Base.getindex(atoms::Atoms, i::Int)
    1 <= i <= atoms.number || throw(BoundsError(atoms, i))
    return atoms.atom[i]
end

Base.firstindex(atoms::Atoms) = 1
Base.lastindex(atoms::Atoms) = length(atoms)
Base.getindex(atoms::Atoms, i::Number) = atoms[convert(Int, i)]
Base.getindex(atoms::Atoms, I) = [atoms[i] for i in I]

function Base.show(io::IO, atoms::Atoms)
    println(io, atoms.number)
    for prop in atoms.properties
        print(io, prop, " ")
    end
    print(io, "\n")
    for atom in atoms
        println(io, atom)
    end
end


"""
    mutable struct FreeVariables <: MOI.ModelLike
        n::Int64
        FreeVariables() = new(0)
    end

A struct for storing free variables that can be used as the `variables` field
of [`GenericModel`](@ref) or [`GenericModel`](@ref). It represents a model
that does not support any constraint nor objective function.

## Example

The following model type represents a conic model in geometric form. As opposed
to [`VariablesContainer`](@ref), `FreeVariables` does not support constraint
bounds so they are bridged into an affine constraint in the
[`MathOptInterface.Nonnegatives`](@ref) cone as expected for the geometric
conic form.
```jldocstest
julia> MOI.Utilities.@product_of_sets(
    Cones,
    MOI.Zeros,
    MOI.Nonnegatives,
    MOI.SecondOrderCone,
    MOI.PositiveSemidefiniteConeTriangle,
);

julia> const ConicModel{T} = MOI.Utilities.GenericOptimizer{
    T,
    MOI.Utilities.ObjectiveContainer{T},
    MOI.Utilities.FreeVariables,
    MOI.Utilities.MatrixOfConstraints{
        T,
        MOI.Utilities.MutableSparseMatrixCSC{
            T,
            Int,
            MOI.Utilities.OneBasedIndexing,
        },
        Vector{T},
        Cones{T},
    },
};

julia> model = MOI.instantiate(ConicModel{Float64}, with_bridge_type=Float64);

julia> x = MOI.add_variable(model)
MathOptInterface.VariableIndex(1)

julia> c = MOI.add_constraint(model, x, MOI.GreaterThan(1.0))
MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.GreaterThan{Float64}}(1)

julia> MOI.Bridges.is_bridged(model, c)
true

julia> bridge = MOI.Bridges.bridge(model, c)
MathOptInterface.Bridges.Constraint.VectorizeBridge{Float64, MathOptInterface.VectorAffineFunction{Float64}, MathOptInterface.Nonnegatives, MathOptInterface.VariableIndex}(MathOptInterface.ConstraintIndex{MathOptInterface.VectorAffineFunction{Float64}, MathOptInterface.Nonnegatives}(1), 1.0)

julia> bridge.vector_constraint
MathOptInterface.ConstraintIndex{MathOptInterface.VectorAffineFunction{Float64}, MathOptInterface.Nonnegatives}(1)

julia> MOI.Bridges.is_bridged(model, bridge.vector_constraint)
false
```
"""
mutable struct FreeVariables <: MOI.ModelLike
    n::Int64
    FreeVariables() = new(0)
end

function MOI.empty!(model::FreeVariables)
    model.n = 0
    return
end

MOI.is_empty(model::FreeVariables) = iszero(model.n)

function MOI.add_variable(model::FreeVariables)
    model.n += 1
    return MOI.VariableIndex(model.n)
end

function MOI.get(model::FreeVariables, ::MOI.ListOfVariableIndices)
    return MOI.VariableIndex[MOI.VariableIndex(i) for i in 1:model.n]
end

function MOI.is_valid(model::FreeVariables, vi::MOI.VariableIndex)
    return 1 <= vi.value <= model.n
end

MOI.get(model::FreeVariables, ::MOI.NumberOfVariables) = model.n

function MOI.get(model::FreeVariables, ::MOI.ListOfConstraintTypesPresent)
    return Tuple{Type,Type}[]
end


# This is just an experiment with linear only tasks #

### General Utility Functions ###

# Data Read: Reads the structured data from the given instance file
# Input:  - filename: A string containing the file name with its relative path.
# Output: - n: An Integer containing the number of tasks;
#         - tasks_times: An (n) Array of Integers with each task's time;
#         - prec_relations: A (n,n) 2-dimensional Binary Array representing
#                           each task's Precedence Relation.
#         - times_sum: An Integer representing the sum of all the tasks' times.
function data_read(filename::String)
    # Gets every line in the instance file for processing
    lines = []
    open(filename,"r") do f
        lines = readlines(filename)
    end

    # The number n of tasks is given in the first line
    n = parse(Int64, lines[1])

    # Gets each task's time in the next n lines and the sum of all times
    task_times = Int64[]
    times_sum = 0
    for i = 2:(n+1)
        curr_time = parse(Int64, lines[i])
        append!(task_times, curr_time)
        times_sum += curr_time
    end

    # Gets the i,j Direct Precedence Relations until the "-1,-1" end mark
    # and transforms it into a Binary Matrix of the Relations
    prec_relations = zeros(Int64, (n,n))
    curr_line = n+2
    # Julia has no "Do While" so a first verification is necessary to avoid a break
    rs = split(lines[curr_line], ",")
    r = [parse(Int64, rs[1]), parse(Int64, rs[2])]
    while (r[1] > 0)
        prec_relations[r[1], r[2]] = 1

        curr_line += 1
        rs = split(lines[curr_line], ",")
        r = [parse(Int64, rs[1]), parse(Int64, rs[2])]
    end

    println("Data successfully read from file \"", filename, "\" containing ", n, " tasks.")
    println("")

    # Returns the structured data
    return n, task_times, prec_relations, times_sum
end

# Solution Write: Writes the final solution and time in a file with stdout format
# Input:  - filename: A string with the name for the file that will be written;
#         - solution: The final best solution encountered during execution;
#         - time: The time it took to find the given solution.
# Output: None.
function solution_write(filename::String, solution, time)
    open(filename, "w") do f
        if (time == 0)
            println(f, "No feasible solution found.\n")

            println("- FINAL RESULT: No feasible solution found.")
        else
            println(f, "Best solution: ", solution[2], "\r\n")
            println(f, "With score: ", solution[1], "\r\n")
            println(f, "Found in: ", time, " seconds.\r\n")

            println("- FINAL RESULT: ")
            println("- Best solution: ", solution[2])
            println("- With score: ", solution[1])
            println("- Found in: ", time, " seconds.")
        end
    end

    println("Solution saved in file: ", filename)
    println("")
end

# Args Read: Reads any (or provides default) Argments passed through command line
# Input:  - args: An Array of Strings containing the argments from the command line.
# Output: - file: A string with the name of the file for the instance given;
#         - stations: An Integer with the number of stations available, defaults to 1;
#         - population: An Integer with the size of the population, defaults to 100.
function args_read(args = ["instances/HAHN.IN2", "1", "500"])
    if (length(args) == 0)
        println("WARNING: No argument provided, using default instance Hahn and stations and population sizes of 1 and 100, respectively")
        file = "instances/HAHN.IN2"
        stations = 1
        population = 500
    elseif (length(args) == 1)
        println("WARNING: Only one argument provided, using default stations and population sizes of 1 and 100, respectively")
        file = args[1]
        stations = 1
        population = 500
    elseif (length(args) == 2)
        println("WARNING: Population size not provided, using default of 500")
        file = args[1]
        stations = parse(Int64, args[2])
        population = 500
    else
        file = args[1]
        stations = parse(Int64, args[2])
        population = parse(Int64, args[3])
    end

    println("Arguments successfully read. Using these parameters:")
    println("File Path and Name: \"", file, "\".")
    println("Number of Stations: ", stations, ".")
    println("Size of Population: ", population, ".")
    println("")

    return file, stations, population
end

### Genetic Algorithm Functions ###

# Rand Gene: Generates a random gene containing a solution for the problem
# Input:  - s: An Integer representing the given number of stations available;
#         - n: An Integer representing the number of tasks to be assigned.
# Output: - gene: A (s,n) 2-dimensional Integer Array representing the sequence
#                 of tasks n assigned to each station s.
function rand_gene(s::Int64, n::Int64)
    gene = Int[]
    task_counter = ones(Int64, s)

    if (s > 1)
        cut = rand(1:n)
        for i = 1:(s-1)
            append!(gene, cut)
            cut = rand(cut:n)
        end
    end

    return gene
end

# Rand Population:
# Input:  - size: An Integer representing the size of the generated population;
#         - s, n, task_times: Needed from gene and fitness functions.
# Output: - population: A random population containing "size"s [fitness, gene] pairs.
function rand_population(size::Int64, s::Int64, n::Int64, task_times)
    population = []
    for i = 1:size
        gene = rand_gene(s, n)
        fit = fitness(s, n, gene, task_times)
        curr_individual = [fit, gene]
        append!(population, [curr_individual])
    end

    return population
end

# Crossover: Creates a new Gene by doing a crossover between two parents
# Input:  - parent_1: A [fitness, gene] individual representing the first parent;
#         - parent_2: A [fitness, gene] individual representing the second parent.
#         - s, n, task_times: Needed from fitness function.
# Output: - new_individual: A new [fitness, gene] individual.
function crossover(s, n, parent1, parent2, task_times)
    gene1 = parent1[2]
    gene2 = parent2[2]

    new_gene = gene1
    rnd = rand(1:2)

    if (rnd == 1)
        for i = 1:(s-1)
            new_gene[i] = min(new_gene[i], gene2[i])
        end
    else
        for i = 1:(s-1)
            it = s - i
            new_gene[it] = max(new_gene[it], gene2[it])
        end
    end

    fit = fitness(s, n, new_gene, task_times)
    new_individual = [fit, new_gene]

    return new_individual
end

# Fitness: Calculates the fitness of the given gene
#          In SALBP, it's the longest cycle among all stations
#          A cycle of a station is the sum the times of all the tasks given to it
# Input:  - gene: A (s,n) 2-dimensional Integer Array representing a gene;
#         - task_times: An (n) Array of Integers with each task's time.
#         - s, n: Needed from gene function.
# Output: - max_cycle: An Integer representing the fitness of the given gene.
function fitness(s, n, gene, task_times)
    max_cycle = 0

    if (s == 1)
        max_cycle = sum(task_times)
    elseif (s == 2)
        max_cycle = max(task_times[1:gene[1]], task_times[(gene[1]+1):n])
    else
        curr_cut = 0
        for i = 1:(s-1)
            if(curr_cut != gene[i])
                curr_cut += 1
                curr_cycle = sum(task_times[curr_cut:gene[i]])
                curr_cut = gene[i]
                max_cycle = max(max_cycle, curr_cycle)
            end
        end
        curr_cut += 1
        if (curr_cut < n)
            curr_cycle = sum(task_times[curr_cut:n])
            max_cycle = max(max_cycle, curr_cycle)
        end
    end

    return max_cycle
end

# Genetic Algorithm:
# Input:  - init_pop: An array containing the initial [fitness, gene] pairs population
#                     of the previously defined size;
#         - pop_size, s, n, task_times, times_sum, prec_relations: Needed from gene, population
#                                                                  and fitness functions;
#         - max_gen: An integer containing the stop criteria of the GA, max generations.
# Output: - best_solution: The [fitness, gene] pair of the best solution found;
#         - time_taken: The time it took to find the best solution found;
function genetic_algorithm(init_pop, pop_size::Int64, s::Int64, n::Int64, task_times, times_sum::Int64, max_gen::Int64, prec_relations)
    tic()
    population = init_pop
    best_found_fit = times_sum + 1
    best_solution = [0, 0]
    time_taken = 0

    println("Genetic Algorithm started.")

    # Until the end criteria is hit
    for i = 1:max_gen
        population = sort(population, lt=lexless)
        curr_best = population[1][1]

        if (curr_best <= times_sum)
            if (curr_best < best_found_fit)
                best_found_fit = curr_best
                best_solution = population[1]
                time_taken += toc()
                tic()
                println("IT [", i, "] - ", "Found a better solution with score: ", best_solution[1])
            end
        end

        # Now to create a new population for the next generation
        class_A = ceil(Int64, pop_size * 0.2)
        class_B = ceil(Int64, pop_size * 0.3)
        class_C = ceil(Int64, pop_size * 0.5)

        # Class A stays the same, changes start in Class B
        for i = class_A:class_C-1
            # Class B crossover
            curr_parents = i - class_A + 1
            population[i] = crossover(s, n, population[curr_parents], population[curr_parents + 1], task_times)
        end
        # Class C just replaces the worst half of the population for new random genes
        population[(class_C+1):end,:] = rand_population(class_C, s, n, task_times)
    end

    return best_solution, time_taken
end

### Main Function ###
function main()
    max_gen = 500
    out_filename = "main_test.txt"

    println("- Reading arguments...")
    println("")
    file_name, num_stations, population_size = args_read(ARGS)

    println("- Setting up data from given file...")
    println("")
    num_tasks, task_times, prec_relations, times_sum = data_read(file_name)

    println("- Starting Genetic Algorithm execution...")
    println("")
    initial_population = rand_population(population_size, num_stations, num_tasks, task_times)
    best_solution, time_taken = genetic_algorithm(initial_population, population_size, num_stations, num_tasks, task_times, times_sum, max_gen, prec_relations)

    solution_write(out_filename, best_solution, time_taken)

    println("- Execution ended.")
    println("")
end

main()


Base.reverse(r::StepSRange) = srange(last(r), step=-step(r), stop=first(r))
Base.reverse(r::StepMRange) = mrange(last(r), step=-step(r), stop=first(r))
Base.reverse(r::AbstractLinRange) = similar_type(r)(last(r), first(r), length(r))

function Base.reverse(r::AbstractStepRangeLen)
    # If `r` is empty, `length(r) - r.offset + 1 will be nonpositive hence
    # invalid. As `reverse(r)` is also empty, any offset would work so we keep
    # `r.offset`
    offset = isempty(r) ? _offset(r) : length(r) - _offset(r) + 1
    return similar_type(r)(_ref(r), -step_hp(r), length(r), offset)
end

function Base.reverse!(r::StepMRange)
    setfield!(r, :step, -step(r))
    f = first(r)
    l = last(r)
    setfield!(r, :stop, f)
    setfield!(r, :start, l)
    return r
end

function Base.reverse!(r::StepMRangeLen)
    # If `r` is empty, `length(r) - r.offset + 1 will be nonpositive hence
    # invalid. As `reverse(r)` is also empty, any offset would work so we keep
    # `r.offset`
    setfield!(r, :offset, isempty(r) ? _offset(r) : length(r) - _offset(r) + 1)
    setfield!(r, :step, -step_hp(r))
    return r
end

function Base.reverse!(r::LinMRange)
    f = first(r)
    l = last(r)
    setfield!(r, :stop, f)
    setfield!(r, :start, l)
    return r
end

function Base.iterate(r::Union{AbstractLinRange,AbstractStepRangeLen}, i::Int=1)
    Base.@_inline_meta
    length(r) < i && return nothing
    unsafe_getindex(r, i), i + 1
end

Base.isempty(r::Union{AbstractLinRange,AbstractStepRangeLen}) = length(r) == 0

###
### ==(r1, r2)
###
Base.:(==)(r::OneToRange, s::OneToRange) = last(r) == last(s)

function Base.:(==)(r::StepMRangeLen{T,R,S}, s::StepMRangeLen{T,R,S}) where {T,R,S}
    return (first(r) == first(s)) & (length(r) == length(s)) & (last(r) == last(s))
end

function Base.:(==)(r::StepSRangeLen{T,R,S}, s::StepSRangeLen{T,R,S}) where {T,R,S}
    return (first(r) == first(s)) & (length(r) == length(s)) & (last(r) == last(s))
end

function Base.:(==)(r::AbstractLinRange{T}, s::AbstractLinRange{T}) where {T}
    return (first(r) == first(s)) & (length(r) == length(s)) & (last(r) == last(s))
end

###
### +(r1, r2)
###
Base.:(+)(r1::Union{AbstractStepRangeLen,AbstractLinRange}, r2::AbstractRange) = +(promote(r1, r2)...)
Base.:(+)(r1::AbstractRange, r2::Union{AbstractStepRangeLen,AbstractLinRange}) =  +(promote(r1, r2)...)
Base.:(+)(r1::Union{AbstractStepRangeLen,AbstractLinRange}, r2::Union{AbstractStepRangeLen,AbstractLinRange}) = +(promote(r1, r2)...)

function Base.:(+)(r1::StepMRangeLen{T,S}, r2::StepMRangeLen{T,S}) where {T,S}
    len = length(r1)
    (len == length(r2) ||
        throw(DimensionMismatch("argument dimensions must match")))
    return StepMRangeLen(first(r1)+first(r2), step(r1)+step(r2), len)
end

function Base.:(+)(r1::StepMRangeLen{T,TwicePrecision{T}}, r2::StepMRangeLen{T,TwicePrecision{T}}) where {T}
    len = length(r1)
    (len == length(r2) || throw(DimensionMismatch("argument dimensions must match")))
    if r1.offset == r2.offset
        imid = r1.offset
        ref = r1.ref + r2.ref
    else
        imid = round(Int, (r1.offset + r2.offset)/2)
        ref1mid = _getindex_hiprec(r1, imid)
        ref2mid = _getindex_hiprec(r2, imid)
        ref = ref1mid + ref2mid
    end
    step = twiceprecision(r1.step + r2.step, nbitslen(T, len, imid))
    return StepMRangeLen{T,typeof(ref),typeof(step)}(ref, step, len, imid)
end

function Base.:(+)(r1::StepSRangeLen{T,TwicePrecision{T}}, r2::StepSRangeLen{T,TwicePrecision{T}}) where {T}
    len = length(r1)
    (len == length(r2) ||
        throw(DimensionMismatch("argument dimensions must match")))
    if _offset(r1) == _offset(r2)
        imid = _offset(r1)
        ref = _ref(r1) + _ref(r2)
    else
        imid = round(Int, (_offset(r1)+_offset(r2))/2)
        ref1mid = _getindex_hiprec(r1, imid)
        ref2mid = _getindex_hiprec(r2, imid)
        ref = ref1mid + ref2mid
    end
    step = twiceprecision(r1.step + r2.step, nbitslen(T, len, imid))
    return StepSRangeLen{T,typeof(ref),typeof(step)}(ref, step, len, imid)
end

#_add(r1::StepSRangeLen{T,R,S}, r2::Union{OneToSRange,UnitSRange,StepSRange,LinSRange}) where {T,R,S} = +(r1, StepSRangeLen{T,R,S}(r2))
#_add(r2::Union{OneToSRange,UnitSRange,StepSRange,LinSRange}, r1::StepSRangeLen{T,R,S}) where {T,R,S} = +(r1, StepSRangeLen{T,R,S}(r2))

function Base.sum(r::AbstractStepRangeLen)
    l = length(r)
    # Compute the contribution of step over all indices.
    # Indexes on opposite side of r.offset contribute with opposite sign,
    #    r.step * (sum(1:np) - sum(1:nn))
    np, nn = l - r.offset, r.offset - 1  # positive, negative
    # To prevent overflow in sum(1:n), multiply its factors by the step
    sp, sn = sumpair(np), sumpair(nn)
    tp = Base._tp_prod(r.step, sp[1], sp[2])
    tn = Base._tp_prod(r.step, sn[1], sn[2])
    s_hi, s_lo = add12(tp.hi, -tn.hi)
    s_lo += tp.lo - tn.lo
    # Add in contributions of ref
    ref = r.ref * l
    sm_hi, sm_lo = add12(s_hi, ref.hi)
    return add12(sm_hi, sm_lo + ref.lo)[1]
end

###
### -(r1, r2)
###
Base.:(-)(r1::Union{AbstractStepRangeLen,AbstractLinRange}, r2::AbstractRange) = -(promote(r1, r2)...)
Base.:(-)(r1::AbstractRange, r2::Union{AbstractStepRangeLen,AbstractLinRange}) =  -(promote(r1, r2)...)
Base.:(-)(r1::Union{AbstractStepRangeLen,AbstractLinRange}, r2::Union{AbstractStepRangeLen,AbstractLinRange}) = -(promote(r1, r2)...)

function Base.:(-)(r::AbstractStepRangeLen)
    return similar_type(r)(-_ref(r), -step(r), length(r), _offset(r))
end
Base.:(-)(r1::StepMRangeLen{T,R,S}, r2::StepMRangeLen{T,R,S}) where {T,R,S} = +(r1, -r2)
Base.:(-)(r1::StepSRangeLen{T,R,S}, r2::StepSRangeLen{T,R,S}) where {T,R,S} = +(r1, -r2)

###
### *(r1, r2)
###
Base.:(*)(r::AbstractStepRangeLen{T,TwicePrecision{T}}, x::Real) where {T<:Real} = x*r
function Base.:(*)(x::Real, r::StepMRangeLen{T,TwicePrecision{T}}) where {T<:Real}
    return StepMRangeLen(x * _ref(r), Base.twiceprecision(x * step(r), nbitslen(r)), length(r), _offset(r))
end
function Base.:(*)(x::Real, r::StepSRangeLen{T,TwicePrecision{T}}) where {T<:Real}
    return StepSRangeLen(x * _ref(r), Base.twiceprecision(x * step(r), nbitslen(r)), length(r), _offset(r))
end

###
### /(r1, r2)
###
function Base.:(/)(r::StepMRangeLen{T,TwicePrecision{T}}, x::Real) where {T<:Real}
    return StepMRangeLen(_ref(r)/x, Base.twiceprecision(step(r)/x, Base.nbitslen(r)), length(r), _offset(r))
end
function Base.:(/)(r::StepSRangeLen{T,TwicePrecision{T}}, x::Real) where {T<:Real}
    return StepSRangeLen(_ref(r)/x, Base.twiceprecision(step(r)/x, Base.nbitslen(r)), length(r), _offset(r))
end

for (frange,R) in ((mrange, :StepMRange), (srange, :StepSRange))
    for f in (:+, :-)
        @eval begin
            Base.$(f)(r1::$R, r2::OrdinalRange) = $(f)(promote(r1, r2)...)
            Base.$(f)(r1::OrdinalRange, r2::$R) =  $(f)(promote(r1, r2)...)
            function Base.$(f)(r1::$R, r2::$R)
                r1l = length(r1)
                (r1l == length(r2) ||
                 throw(DimensionMismatch("argument dimensions must match: length of r1 is $r1l, length of r2 is $(length(r2))")))
                $(frange)($f(first(r1), first(r2)), step=$f(step(r1), step(r2)), length=r1l)
            end

        end
    end
end

#= TODO
float(r::StepRange) = float(r.start):float(r.step):float(last(r))
float(r::UnitRange) = float(r.start):float(last(r))
float(r::StepRangeLen{T}) where {T} =
    StepRangeLen{typeof(float(T(r.ref)))}(float(r.ref), float(r.step), length(r), r.offset)
function float(r::LinRange)
    LinRange(float(r.start), float(r.stop), length(r))
end
=#


module Bracket

include("genebracket.jl")

function side_effect(n)
    if n == 1
        println("Function 1")
    else
        println("Other ",n)
    end
end

function main(prog)
    println("Start Gene Bracket")
    vm = Vm(side_effect)
    #load prelude
    #vm.bra = load_file(vm,"prelude.clj")
    #eval_bra(vm)

    vm.bra = load_file(vm,prog)
    printBra(vm, vm.bra)
    vm.ket = NIL

    @time eval_bra(vm)

    printKet(vm, vm.ket)
    println("done")
end

function main()
    println("Start Bracket")
    vm = Vm(side_effect)
    #reset!(vm)
    #vm.env = cons(vm,NIL,NIL)
    #load prelude
    vm.bra = load_file(vm,"prelude.clj")
    #vm.ket = NIL
    eval_bra(vm)

    # repl(vm)    # repl should run in depth 0
    #vm.depth = 1  # normal programs start in depth 1

    #vm.ket = NIL

    #str = "1 3 foo [bar 4 dup]"
    #str = "eval [ rec gt 0 dup add 1 print dup] -20 trace 0"
    #str = "eval [ rec gt 0 dup add 1 ] -50000000"  # 5e7  # 2.8 sec
    #str = "eval [ rec gt 0 dup add 1 ] -500000000"  # 5e8  # 16-17.6sec

     # ***************************

    #str = "ack 3 10 def ack' \\[m n]
    # [eval if eq 0 m
    #    [+ n 1]
    # [eval if eq 0 n
    #     [ack - m 1 1]
    # [ack - m 1 ack m - n 1] ]] " # "125")

     # bench
     # ack 3 4 --> 0.028
     # ack 3 5 --> 0.038
     # ack 3 6 --> 0.086
     # ack 3 7 --> 0.26
     # ack 3 8 --> 0.92
     # ack 3 9 --> 2.87
     # ack 3 10 --> 10.7

     # ***************************

    #str = " x eval [x def x' 2] x def x' 1 trace 1"
    #str = "foo def x' 3 def foo' [x` def x'] 4"
    #str = "rnd 3 rnd 3 rnd 3 rnd 3 rnd 3 rnd 3 rnd 3 rnd 3"
    #str = "rnd [1 2 3] rnd [1 2 3] rnd [1 2 3] rnd [1 2 3] rnd [1 2 3] rnd [1 2 3] rnd [1 2 3] "
    #= str = "ack 3 10 def ack' \\[m n]
      [cond
        [ [ack - m 1 ack m - n 1]
          [ack - m 1 1]  [eq 0 n]
          [+ n 1]  [eq 0 m]] ]"
    =#

    #=str = "ack 3 11 def ack' \\[m n]
        [eval if
            [+ n 1]
            [ eval if
                [ack - m 1 1]
                [ack - m 1 ack m - n 1]
                eq 0 n
            ]
            eq 0 m
        ]"
    =#

    #str = "eval [rec gt 0 dup add 1 dup] -5"
    str  = "typ 20 trace 1"

    vm.bra = make_bra(vm, str)

    #vm.bra = cons(vm, boxInt(3), cons(vm,boxInt(4), boxInt(5)))
    #vm.bra = cons(vm, boxInt(2), vm.bra)
    #vm.bra = cons(vm, boxInt(3), cons(vm,DEF, NIL))
    #vm.bra = cons(vm, VAL, cons(vm,DEF, boxPrim(2)))
    #vm.bra = load_file("test.clj",vm)

    printBra(vm, vm.bra)
    #vm.bra = cons(box_int(1), NIL, vm)
    vm.ket = NIL

    @time eval_bra(vm)

    printKet(vm, vm.ket)
    println("done")

end

@time main("play.clj")

end # end module


# This file is a part of TextSearch.jl

export neardup

function neardup(X::AbstractVector{T}, α=0.1) where T
    idx = InvertedFile()
    res = KnnResult(1)
    L = zeros(Int, length(X))
    D = zeros(Float64, length(X))
    L[1] = 1
    D[1] = 0.0
    push!(idx, 1 => X[1])
    @inbounds for i in 2:length(X)
        empty!(res)
        x = X[i]
        search(idx, x, res)
        if length(res) == 0 || minimum(res) > α
            push!(idx, i => x)
            L[i] = i
            D[i] = 0.0
        else
            L[i], D[i] = first(res)
        end
    end

    (idx=idx, nn=L, dist=D)
end


"""
    AnchoredInterval{P,T,L,R}

`AnchoredInterval` is a subtype of `AbstractInterval` that represents a non-iterable range
or span of values defined not by two endpoints but instead by a single `anchor` point and
the value type `P` which represents the size of the range. When `P` is positive, the
`anchor` represents the lesser endpoint (the beginning of the range); when `P` is negative,
the `anchor` represents the greater endpoint (the end of the range).

The interval represented by an `AnchoredInterval` value may be closed (both endpoints are
included in the interval), open (neither endpoint is included), or half-open. This openness
is defined by the bounds types `L` and `R`, which defaults to half-open (with the lesser
endpoint included for positive values of `P` and the greater endpoint included for negative
values).

### Why?

`AnchoredIntervals` are most useful in cases where a single value is used to stand in for a
range of values. This happens most often with dates and times, where "HE15" is often used as
shorthand for (14:00..15:00].

To this end, `HourEnding` is a type alias for `AnchoredInterval{Hour(-1)}`. Similarly,
`HourBeginning` is a type alias for `AnchoredInterval{Hour(1)}`.

### Rounding

While the user may expect an `HourEnding` or `HourBeginning` value to be anchored to a
specific hour, the constructor makes no guarantees that the anchor provided is rounded:

```jldoctest; setup = :(using Intervals, Dates)
julia> HourEnding(DateTime(2016, 8, 11, 2, 30))
AnchoredInterval{-1 hour,DateTime,Open,Closed}(2016-08-11T02:30:00)
```

The `HE` and `HB` pseudoconstructors round the input up or down to the nearest hour, as
appropriate:

```jldoctest; setup = :(using Intervals, Dates)
julia> HE(DateTime(2016, 8, 11, 2, 30))
AnchoredInterval{-1 hour,DateTime,Open,Closed}(2016-08-11T03:00:00)

julia> HB(DateTime(2016, 8, 11, 2, 30))
AnchoredInterval{1 hour,DateTime,Closed,Open}(2016-08-11T02:00:00)
```

### Example

```jldoctest; setup = :(using Intervals, Dates)
julia> AnchoredInterval{Hour(-1)}(DateTime(2016, 8, 11, 12))
AnchoredInterval{-1 hour,DateTime,Open,Closed}(2016-08-11T12:00:00)

julia> AnchoredInterval{Day(1)}(DateTime(2016, 8, 11))
AnchoredInterval{1 day,DateTime,Closed,Open}(2016-08-11T00:00:00)

julia> AnchoredInterval{Minute(5),Closed,Closed}(DateTime(2016, 8, 11, 12, 30))
AnchoredInterval{5 minutes,DateTime,Closed,Closed}(2016-08-11T12:30:00)
```

See also: [`Interval`](@ref), [`HE`](@ref), [`HB`](@ref)
"""
struct AnchoredInterval{P, T, L <: Bounded, R <: Bounded} <: AbstractInterval{T,L,R}
    anchor::T

    function AnchoredInterval{P,T,L,R}(anchor::T) where {P, T, L <: Bounded, R <: Bounded}
        # A valid interval requires that neither endpoints or the span are nan. Typically,
        # we use `left <= right` to ensure a valid interval but for `AnchoredInterval`s
        # computing the other endpoint requires `anchor + P` which may fail with certain
        # types (e.g. ambiguous or non-existent ZonedDateTimes).
        #
        # We can skip computing the other endpoint if both the anchor and span are finite as
        # this ensures the computed endpoint is also finite.
        if !_isfinite(anchor) || !_isfinite(P)
            left, right = sign(P) < 0 ? (anchor + P, anchor) : (anchor, anchor + P)

            if !(left <= right)
                msg = if sign(P) < 0
                    "Unable to represent a right-anchored interval where the " *
                    "left ($anchor + $P) > right ($anchor)"
                else
                    "Unable to represent a left-anchored interval where the " *
                    "left ($anchor) > right ($anchor + $P)"
                end
                throw(ArgumentError(msg))
            end
        end

        return new{P,T,L,R}(anchor)
    end
end

_isfinite(x) = iszero(x - x)
_isfinite(x::Real) = Base.isfinite(x)

function AnchoredInterval{P,T,L,R}(anchor) where {P,T,L,R}
    AnchoredInterval{P,T,L,R}(convert(T, anchor))
end

AnchoredInterval{P,L,R}(anchor::T) where {P,T,L,R} = AnchoredInterval{P,T,L,R}(anchor)

# When an interval is anchored to the lesser endpoint, default to Inclusivity(false, true)
# When an interval is anchored to the greater endpoint, default to Inclusivity(true, false)
function AnchoredInterval{P,T}(anchor) where {P,T}
    L = bound_type(P ≥ zero(P))
    R = bound_type(P ≤ zero(P))
    return AnchoredInterval{P,T,L,R}(anchor)
end

AnchoredInterval{P}(anchor::T) where {P,T} = AnchoredInterval{P,T}(anchor)

# Note: Ideally we would define the restriction `T <: TimeType` but doing so interferes with
# the `HourEnding{L,R}` constructor.
"""
    HourEnding{T<:TimeType, L, R} <: AbstractInterval{T}

A type alias for `AnchoredInterval{Hour(-1), T}` which is used to denote a 1-hour period of
time which ends at a time instant (of type `T`).
"""
const HourEnding{T,L,R} = AnchoredInterval{Hour(-1), T, L, R} where {T, L <: Bounded, R <: Bounded}
HourEnding(anchor::T) where T = HourEnding{T}(anchor)

# Note: Ideally we would define the restriction `T <: TimeType` but doing so interferes with
# the `HourBeginning{L,R}` constructor.
"""
    HourBeginning{T<:TimeType, L, R} <: AbstractInterval{T}

A type alias for `AnchoredInterval{Hour(1), T}` which is used to denote a 1-hour period of
time which begins at a time instant (of type `T`).
"""
const HourBeginning{T,L,R} = AnchoredInterval{Hour(1), T, L, R} where {T, L <: Bounded, R <: Bounded}
HourBeginning(anchor::T) where T = HourBeginning{T}(anchor)

"""
    HE(anchor) -> HourEnding

`HE` is a pseudoconstructor for [`HourEnding`](@ref) that rounds the anchor provided up to the
nearest hour.
"""
HE(anchor) = HourEnding(ceil(anchor, Hour))

"""
    HB(anchor) -> HourBeginning

`HB` is a pseudoconstructor for [`HourBeginning`](@ref) that rounds the anchor provided down to the
nearest hour.
"""
HB(anchor) = HourBeginning(floor(anchor, Hour))

function Base.copy(x::AnchoredInterval{P,T,L,R}) where {P,T,L,R}
    return AnchoredInterval{P,T,L,R}(anchor(x))
end

##### ACCESSORS #####

# We would typically compute `first` and `last` using `min` and `max` respectively, but we
# can get unexpected behaviour if adding the span to the anchor endpoint produces a value
# that is no longer comparable (e.g., `NaN`).

function Base.first(interval::AnchoredInterval{P}) where P
    P < zero(P) ? (interval.anchor + P) : (interval.anchor)
end

function Base.last(interval::AnchoredInterval{P}) where P
    P < zero(P) ? (interval.anchor) : (interval.anchor + P)
end

anchor(interval::AnchoredInterval) = interval.anchor
span(interval::AnchoredInterval{P}) where P = abs(P)

##### CONVERSION #####

# Allows an interval to be converted to a scalar when the set contained by the interval only
# contains a single element.
function Base.convert(::Type{T}, interval::AnchoredInterval{P,T}) where {P,T}
    if isclosed(interval) && (iszero(P) || first(interval) == last(interval))
        return first(interval)
    else
        # Remove deprecation in version 2.0.0
        depwarn(
            "`convert(T, interval::AnchoredInterval{P,T})` is deprecated for " *
            "intervals which are not closed with coinciding endpoints. " *
            "Use `anchor(interval)` instead.",
            :convert,
        )
        return anchor(interval)

        # TODO: For when deprecation is removed
        # throw(DomainError(interval, "The interval is not closed with coinciding endpoints"))
    end
end

function Base.convert(::Type{Interval}, interval::AnchoredInterval{P,T,L,R}) where {P,T,L,R}
    return Interval{T,L,R}(first(interval), last(interval))
end

function Base.convert(::Type{Interval{T}}, interval::AnchoredInterval{P,T,L,R}) where {P,T,L,R}
    return Interval{T,L,R}(first(interval), last(interval))
end

# Conversion methods which currently aren't needed but could prove useful. Commented out
# since these are untested.

#=
function Base.convert(::Type{AnchoredInterval{P,T}}, interval::Interval{T}) where {P,T}
    @assert abs(P) == span(interval)
    anchor = P < zero(P) ? last(interval) : first(interval)
    AnchoredInterval{P,T}(last(interval), inclusivity(interval))
end

function Base.convert(::Type{AnchoredInterval{P}}, interval::Interval{T}) where {P,T}
    @assert abs(P) == span(interval)
    anchor = P < zero(P) ? last(interval) : first(interval)
    AnchoredInterval{P,T}(anchor, inclusivity(interval))
end
=#

function Base.convert(::Type{AnchoredInterval{Ending}}, interval::Interval{T,L,R}) where {T,L,R}
    if !isbounded(interval)
        throw(ArgumentError("Unable to represent a non-bounded interval using a `AnchoredInterval`"))
    end
    AnchoredInterval{-span(interval), T, L, R}(last(interval))
end

function Base.convert(::Type{AnchoredInterval{Beginning}}, interval::Interval{T,L,R}) where {T,L,R}
    if !isbounded(interval)
        throw(ArgumentError("Unable to represent a non-bounded interval using a `AnchoredInterval`"))
    end
    AnchoredInterval{span(interval), T, L, R}(first(interval))
end

##### DISPLAY #####

function Base.show(io::IO, interval::T) where T <: AnchoredInterval
    if get(io, :compact, false)
        print(io, interval)
    else
        print(io, "$T(")
        show(io, anchor(interval))
        print(io, ")")
    end
end

function Base.print(io::IO, interval::AnchoredInterval{P,T}) where {P, T <: Union{Date, AbstractDateTime}}
    # Print to io in order to keep properties like :limit and :compact
    if get(io, :compact, false)
        io = IOContext(io, :limit=>true)
    end

    print(io, description(interval))
end

##### ARITHMETIC #####

Base.:+(a::T, b) where {T <: AnchoredInterval} = T(anchor(a) + b)

Base.:+(a, b::AnchoredInterval) = b + a
Base.:-(a::AnchoredInterval, b) = a + -b

# Required for StepRange{<:AnchoredInterval}
Base.:-(a::AnchoredInterval, b::AnchoredInterval) = anchor(a) - anchor(b)

Base.:-(a::T, b::AnchoredInterval{P,T}) where {P, T <: Number} = a + -b

function Base.:-(a::AnchoredInterval{P,T,L,R}) where {P, T <: Number, L, R}
    return AnchoredInterval{-P, T, R, L}(-anchor(a))
end

##### EQUALITY #####

function Base.:(==)(a::AnchoredInterval{P,T}, b::AnchoredInterval{P,T}) where {P,T}
    return anchor(a) == anchor(b) && bounds_types(a) == bounds_types(b)
end

# Required for min/max of AnchoredInterval{LaxZonedDateTime} when the anchor is AMB or DNE
function Base.isless(a::AnchoredInterval{P,T,L1}, b::AnchoredInterval{P,T,L2}) where {P,T,L1,L2}
    return (
        anchor(a) < anchor(b) ||
        (anchor(a) == anchor(b) && L1 === Closed && L2 === Open)
    )
end

##### RANGE #####

function Base.:(:)(start::AnchoredInterval{P,T}, step::S, stop::AnchoredInterval{P,T}) where {P,T,S}
    return StepRange{AnchoredInterval{P,T}, S}(start, step, stop)
end

# Infer step for two-argument StepRange{<:AnchoredInterval}
function Base.:(:)(start::AnchoredInterval{P,T}, stop::AnchoredInterval{P,T}) where {P,T}
    return (:)(start, abs(P), stop)
end

function Base.steprange_last(start::T, step, stop::AnchoredInterval) where T <: AnchoredInterval
    T(Base.steprange_last(anchor(start), step, anchor(stop)))
end

function Base.length(r::StepRange{<:AnchoredInterval})
    return length(anchor(r.start):r.step:anchor(r.stop))
end

##### SET OPERATIONS #####

function Base.isempty(interval::AnchoredInterval{P,T}) where {P,T}
    return P == zero(P) && !isclosed(interval)
end

# When intersecting two `AnchoredInterval`s attempt to return an `AnchoredInterval`
function Base.intersect(a::AnchoredInterval{P,T}, b::AnchoredInterval{Q,T}) where {P,Q,T}
    interval = invoke(intersect, Tuple{AbstractInterval{T}, AbstractInterval{T}}, a, b)

    sp = isa(P, Period) ? canonicalize(typeof(P), span(interval)) : span(interval)
    if P ≤ zero(P)
        anchor = last(interval)
        new_P = -sp
    else
        anchor = first(interval)
        new_P = sp
    end

    L, R = bounds_types(interval)
    return AnchoredInterval{new_P, T, L, R}(anchor)
end

##### UTILITIES #####

function canonicalize(target_type::Type{<:Period}, p::P) where P <: Period
    Q, max_val = coarserperiod(P)
    if (value(p) % max_val == 0) && (max_val > 1)
        p = canonicalize(target_type, convert(Q, p))
    end
    return p
end

canonicalize(target_type::Type{P}, p::P) where P <: Period = p

##### TIME ZONES #####

function TimeZones.astimezone(i::AnchoredInterval{P, ZonedDateTime, L, R}, tz::TimeZone) where {P,L,R}
    return AnchoredInterval{P, ZonedDateTime, L, R}(astimezone(anchor(i), tz))
end

TimeZones.timezone(i::AnchoredInterval{P, ZonedDateTime}) where P = timezone(anchor(i))