function layoutable(::Type{IntervalSlider}, fig_or_scene; bbox = nothing, kwargs...)

    topscene = get_topscene(fig_or_scene)

    default_attrs = default_attributes(IntervalSlider, topscene).attributes
    theme_attrs = subtheme(topscene, :IntervalSlider)
    attrs = merge!(merge!(Attributes(kwargs), theme_attrs), default_attrs)

    decorations = Dict{Symbol, Any}()

    @extract attrs (
        halign, valign, horizontal, linewidth, snap,
        startvalues, interval, color_active, color_active_dimmed, color_inactive
    )

    sliderrange = attrs.range

    protrusions = Node(GridLayoutBase.RectSides{Float32}(0, 0, 0, 0))
    layoutobservables = LayoutObservables{IntervalSlider}(attrs.width, attrs.height, attrs.tellwidth, attrs.tellheight,
        halign, valign, attrs.alignmode; suggestedbbox = bbox, protrusions = protrusions)

    onany(linewidth, horizontal) do lw, horizontal
        if horizontal
            layoutobservables.autosize[] = (nothing, Float32(lw))
        else
            layoutobservables.autosize[] = (Float32(lw), nothing)
        end
    end

    sliderbox = lift(identity, layoutobservables.computedbbox)

    endpoints = lift(sliderbox, horizontal) do bb, horizontal

        h = height(bb)
        w = width(bb)

        if horizontal
            y = bottom(bb) + h / 2
            [Point2f(left(bb) + h/2, y),
             Point2f(right(bb) - h/2, y)]
        else
            x = left(bb) + w / 2
            [Point2f(x, bottom(bb) + w/2),
             Point2f(x, top(bb) - w/2)]
        end
    end

    # this is the index of the selected value in the slider's range
    # selected_index = Node(1)
    # add the selected index to the attributes so it can be manipulated later
    attrs.selected_indices = (1, 1)
    selected_indices = attrs.selected_indices

    # the fraction on the slider corresponding to the selected_indices
    # this is only used after dragging
    sliderfractions = lift(selected_indices, sliderrange) do is, r
        map(is) do i
            (i - 1) / (length(r) - 1)
        end
    end

    dragging = Node(false)

    # what the slider actually displays currently (also during dragging when
    # the slider position is in an "invalid" position given the slider's range)
    displayed_sliderfractions = Node((0.0, 0.0))

    on(sliderfractions) do fracs
        # only update displayed fraction through sliderfraction if not dragging
        # dragging overrides the value so there is clear mouse interaction
        if !dragging[]
            displayed_sliderfractions[] = fracs
        end
    end

    # when the range is changed, switch to closest interval
    on(sliderrange) do rng
        selected_indices[] = closest_index.(Ref(rng), interval[])
    end

    on(selected_indices) do is
        interval[] = getindex.(Ref(sliderrange[]), is)
    end

    # initialize slider value with closest from range
    selected_indices[] = if startvalues[] === Makie.automatic
        (1, lastindex(sliderrange[]))
    else
        closest_index.(Ref(sliderrange[]), startvalues[])
    end

    middlepoints = lift(endpoints, displayed_sliderfractions) do ep, sfs
        [Point2f(ep[1] .+ sf .* (ep[2] .- ep[1])) for sf in sfs]
    end

    linepoints = lift(endpoints, middlepoints) do eps, middles
        [eps[1], middles[1], middles[1], middles[2], middles[2], eps[2]]
    end

    linecolors = lift(color_active_dimmed, color_inactive) do ca, ci
        [ci, ca, ci]
    end

    endbuttoncolors = lift(color_active_dimmed, color_inactive) do ca, ci
        [ci, ci]
    end

    endbuttons = scatter!(topscene, endpoints, color = endbuttoncolors,
        markersize = linewidth, strokewidth = 0, raw = true, inspectable = false)
    decorations[:endbuttons] = endbuttons

    linesegs = linesegments!(topscene, linepoints, color = linecolors,
        linewidth = linewidth, raw = true, inspectable = false)
    decorations[:linesegments] = linesegs

    state = Node(:none)
    button_magnifications = lift(state) do state
        if state == :none
            [1.0, 1.0]
        elseif state == :min
            [1.25, 1.0]
        elseif state == :both
            [1.25, 1.25]
        else
            [1.0, 1.25]
        end
    end
    buttonsizes = @lift($linewidth .* $button_magnifications)
    buttons = scatter!(topscene, middlepoints, color = color_active, strokewidth = 0,
        markersize = buttonsizes, raw = true, inspectable = false)
    decorations[:buttons] = buttons

    mouseevents = addmouseevents!(topscene, layoutobservables.computedbbox)

    # we need to record where a drag started for the case where the center of the
    # range is shifted, because the difference in indices always needs to stay the same
    # and the slider is moved relative to this start position
    startfraction = Ref(0.0)
    start_disp_fractions = Ref((0.0, 0.0))
    startindices = Ref((1, 1))

    onmouseleftdrag(mouseevents) do event

        dragging[] = true
        fraction = if horizontal[]
            (event.px[1] - endpoints[][1][1]) / (endpoints[][2][1] - endpoints[][1][1])
        else
            (event.px[2] - endpoints[][1][2]) / (endpoints[][2][2] - endpoints[][1][2])
        end
        fraction = clamp(fraction, 0, 1)

        if state[] in (:min, :max)
            if snap[]
                snapindex = closest_fractionindex(sliderrange[], fraction)
                fraction = (snapindex - 1) / (length(sliderrange[]) - 1)
            end
            if state[] == :min
                # if the mouse crosses over the current max, reverse
                if fraction > displayed_sliderfractions[][2]
                    state[] = :max
                    displayed_sliderfractions[] = (displayed_sliderfractions[][2], fraction)
                else
                    displayed_sliderfractions[] = (fraction, displayed_sliderfractions[][2])
                end
            else
                # if the mouse crosses over the current min, reverse
                if fraction < displayed_sliderfractions[][1]
                    state[] = :min
                    displayed_sliderfractions[] = (fraction, displayed_sliderfractions[][1])
                else
                    displayed_sliderfractions[] = (displayed_sliderfractions[][1], fraction)
                end
            end
            newindices = closest_fractionindex.(Ref(sliderrange[]), displayed_sliderfractions[])

            if selected_indices[] != newindices
                selected_indices[] = newindices
            end
        elseif state[] == :both
            fracdif = fraction - startfraction[]

            clamped_fracdif = clamp(fracdif, -start_disp_fractions[][1], 1 - start_disp_fractions[][2])

            ntarget = round(Int, length(sliderrange[]) * clamped_fracdif)

            nlow = -startindices[][1] + 1
            nhigh = length(sliderrange[]) - startindices[][2]
            nchange = clamp(ntarget, nlow, nhigh)

            newindices = startindices[] .+ nchange

            displayed_sliderfractions[] = if snap[]
                (newindices .- 1) ./ (length(sliderrange[]) - 1)
            else
                start_disp_fractions[] .+ clamped_fracdif
            end

            if selected_indices[] != newindices
                selected_indices[] = newindices
            end
        end

        return Consume(true)
    end

    onmouseleftdragstop(mouseevents) do event
        dragging[] = false
        # adjust slider to closest legal value
        sliderfractions[] = sliderfractions[]
        return Consume(true)
    end

    onmouseleftdown(mouseevents) do event

        pos = event.px

        dim = horizontal[] ? 1 : 2
        frac = clamp(
            (pos[dim] - endpoints[][1][dim]) / (endpoints[][2][dim] - endpoints[][1][dim]),
            0, 1
        )

        startfraction[] = frac
        startindices[] = selected_indices[]
        start_disp_fractions[] = displayed_sliderfractions[]

        if state[] in (:both, :none)
            return Consume(true)
        end

        newindex = closest_fractionindex(sliderrange[], frac)
        if abs(newindex - selected_indices[][1]) < abs(newindex - selected_indices[][2])
            selected_indices[] = (newindex, selected_indices[][2])
        else
            selected_indices[] = (selected_indices[][1], newindex)
        end
        # linecolors[] = [color_active[], color_inactive[]]

        return Consume(true)
    end

    onmouseleftdoubleclick(mouseevents) do event
        selected_indices[] = selected_indices[] = if startvalues[] === Makie.automatic
            (1, lastindex(sliderrange[]))
        else
            closest_index.(Ref(sliderrange[]), startvalues[])
        end

        return Consume(true)
    end

    onmouseover(mouseevents) do event
        fraction = if horizontal[]
            (event.px[1] - endpoints[][1][1]) / (endpoints[][2][1] - endpoints[][1][1])
        else
            (event.px[2] - endpoints[][1][2]) / (endpoints[][2][2] - endpoints[][1][2])
        end
        fraction = clamp(fraction, 0, 1)

        buttondistance = displayed_sliderfractions[][2] - displayed_sliderfractions[][1]

        state[] = if fraction < displayed_sliderfractions[][1] + 0.25 * buttondistance
            :min
        elseif fraction < displayed_sliderfractions[][1] + 0.75 * buttondistance
            :both
        else
            :max
        end

        return Consume(false)
    end

    onmouseout(mouseevents) do event
        state[] = :none
        return Consume(false)
    end

    # trigger autosize through linewidth for first layout
    linewidth[] = linewidth[]

    IntervalSlider(fig_or_scene, layoutobservables, attrs, decorations)
end

"""
Set the `slider` to the values in the slider's range that are closest to `v1` and `v2`, and return those values ordered min, max.
"""
function set_close_to!(intervalslider::IntervalSlider, v1, v2)
    mima = minmax(v1, v2)
    indices = closest_index.(Ref(intervalslider.range[]), mima)
    intervalslider.selected_indices[] = indices
    getindex.(Ref(intervalslider.range[]), indices)
end


@testset "TAI<->TT" begin
    ep = TAIEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = TTEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 3
    @test second(Float64, out_ep) ≈ 2.3735247436378273e+01
    in_ep = TAIEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01
end
@testset "TAI<->UTC" begin
    ep = TAIEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = UTCEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 2
    @test second(Float64, out_ep) ≈ 1.4551247436378276e+01
    in_ep = TAIEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01
end
@testset "TAI<->TDB" begin
    ep = TAIEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = TDBEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 3
    @test second(Float64, out_ep) ≈ 2.3734205844955390e+01 atol=1e-6
    in_ep = TAIEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01 atol=1e-6
end
@testset "TAI<->TCB" begin
    ep = TAIEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = TCBEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 3
    @test second(Float64, out_ep) ≈ 4.4097432701198270e+01 atol=1e-6
    in_ep = TAIEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01 atol=1e-6
end
@testset "TAI<->TCG" begin
    ep = TAIEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = TCGEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 3
    @test second(Float64, out_ep) ≈ 2.4650535580099750e+01
    in_ep = TAIEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01
end
@testset "TAI<->UT1" begin
    ep = TAIEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = UT1Epoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 2
    @test second(Float64, out_ep) ≈ 1.4617328996852756e+01 atol=1e-5
    in_ep = TAIEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01 atol=1e-5
end
@testset "TT<->TAI" begin
    ep = TTEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = TAIEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 2
    @test second(Float64, out_ep) ≈ 1.9367247436378280e+01
    in_ep = TTEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01
end
@testset "TT<->UTC" begin
    ep = TTEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = UTCEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 1
    @test second(Float64, out_ep) ≈ 4.2367247436378280e+01
    in_ep = TTEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01
end
@testset "TT<->TDB" begin
    ep = TTEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = TDBEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 2
    @test second(Float64, out_ep) ≈ 5.1550205853115330e+01 atol=1e-6
    in_ep = TTEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01 atol=1e-6
end
@testset "TT<->TCB" begin
    ep = TTEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = TCBEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 3
    @test second(Float64, out_ep) ≈ 1.1913432210338932e+01 atol=1e-6
    in_ep = TTEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01 atol=1e-6
end
@testset "TT<->TCG" begin
    ep = TTEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = TCGEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 2
    @test second(Float64, out_ep) ≈ 5.2466535557669786e+01
    in_ep = TTEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01
end
@testset "TT<->UT1" begin
    ep = TTEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = UT1Epoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 1
    @test second(Float64, out_ep) ≈ 4.2433329223481640e+01 atol=1e-5
    in_ep = TTEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01 atol=1e-5
end
@testset "UTC<->TAI" begin
    ep = UTCEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = TAIEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 3
    @test second(Float64, out_ep) ≈ 2.8551247436378276e+01
    in_ep = UTCEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01
end
@testset "UTC<->TT" begin
    ep = UTCEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = TTEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 4
    @test second(Float64, out_ep) ≈ 7.3524743637827330e-01
    in_ep = UTCEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01
end
@testset "UTC<->TDB" begin
    ep = UTCEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = TDBEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 4
    @test second(Float64, out_ep) ≈ 7.3420583557444980e-01 atol=1e-6
    in_ep = UTCEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01 atol=1e-6
end
@testset "UTC<->TCB" begin
    ep = UTCEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = TCBEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 4
    @test second(Float64, out_ep) ≈ 2.1097433265509650e+01 atol=1e-6
    in_ep = UTCEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01 atol=1e-6
end
@testset "UTC<->TCG" begin
    ep = UTCEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = TCGEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 4
    @test second(Float64, out_ep) ≈ 1.6505356058861196e+00
    in_ep = UTCEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01
end
@testset "UTC<->UT1" begin
    ep = UTCEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = UT1Epoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 2
    @test second(Float64, out_ep) ≈ 5.1617328736316390e+01 atol=1e-5
    in_ep = UTCEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01 atol=1e-5
end
@testset "TDB<->TAI" begin
    ep = TDBEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = TAIEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 2
    @test second(Float64, out_ep) ≈ 1.9368289019641487e+01 atol=1e-6
    in_ep = TDBEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01 atol=1e-6
end
@testset "TDB<->TT" begin
    ep = TDBEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = TTEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 2
    @test second(Float64, out_ep) ≈ 5.1552289019641485e+01 atol=1e-6
    in_ep = TDBEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01 atol=1e-6
end
@testset "TDB<->UTC" begin
    ep = TDBEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = UTCEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 1
    @test second(Float64, out_ep) ≈ 4.2368289019641490e+01 atol=1e-6
    in_ep = TDBEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01 atol=1e-6
end
@testset "TDB<->TCB" begin
    ep = TDBEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = TCBEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 3
    @test second(Float64, out_ep) ≈ 1.1914473793618030e+01 atol=1e-6
    in_ep = TDBEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01 atol=1e-6
end
@testset "TDB<->TCG" begin
    ep = TDBEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = TCGEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 2
    @test second(Float64, out_ep) ≈ 5.2467577140933720e+01 atol=1e-6
    in_ep = TDBEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01 atol=1e-6
end
@testset "TDB<->UT1" begin
    ep = TDBEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = UT1Epoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 1
    @test second(Float64, out_ep) ≈ 4.2434370806737520e+01 atol=1e-5
    in_ep = TDBEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01 atol=1e-5
end
@testset "TCB<->TAI" begin
    ep = TCBEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = TAIEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 1
    @test second(Float64, out_ep) ≈ 5.9005062972974656e+01 atol=1e-6
    in_ep = TCBEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01 atol=1e-6
end
@testset "TCB<->TT" begin
    ep = TCBEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = TTEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 2
    @test second(Float64, out_ep) ≈ 3.1189062972974654e+01 atol=1e-6
    in_ep = TCBEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01 atol=1e-6
end
@testset "TCB<->UTC" begin
    ep = TCBEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = UTCEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 1
    @test second(Float64, out_ep) ≈ 2.2005062972974656e+01 atol=1e-6
    in_ep = TCBEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01 atol=1e-6
end
@testset "TCB<->TDB" begin
    ep = TCBEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = TDBEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 2
    @test second(Float64, out_ep) ≈ 3.1188021394874360e+01 atol=1e-6
    in_ep = TCBEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01 atol=1e-6
end
@testset "TCB<->TCG" begin
    ep = TCBEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = TCGEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 2
    @test second(Float64, out_ep) ≈ 3.2104351080075170e+01 atol=1e-6
    in_ep = TCBEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01 atol=1e-6
end
@testset "TCB<->UT1" begin
    ep = TCBEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = UT1Epoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 1
    @test second(Float64, out_ep) ≈ 2.2071144903463825e+01 atol=1e-5
    in_ep = TCBEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01 atol=1e-5
end
@testset "TCG<->TAI" begin
    ep = TCGEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = TAIEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 2
    @test second(Float64, out_ep) ≈ 1.8451959315724658e+01
    in_ep = TCGEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01
end
@testset "TCG<->TT" begin
    ep = TCGEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = TTEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 2
    @test second(Float64, out_ep) ≈ 5.0635959315724655e+01
    in_ep = TCGEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01
end
@testset "TCG<->UTC" begin
    ep = TCGEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = UTCEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 1
    @test second(Float64, out_ep) ≈ 4.1451959315724660e+01
    in_ep = TCGEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01
end
@testset "TCG<->TDB" begin
    ep = TCGEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = TDBEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 2
    @test second(Float64, out_ep) ≈ 5.0634917732693770e+01 atol=1e-6
    in_ep = TCGEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01 atol=1e-6
end
@testset "TCG<->TCB" begin
    ep = TCGEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = TCBEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 3
    @test second(Float64, out_ep) ≈ 1.0998144075725655e+01 atol=1e-6
    in_ep = TCGEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01 atol=1e-6
end
@testset "TCG<->UT1" begin
    ep = TCGEpoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = UT1Epoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 1
    @test second(Float64, out_ep) ≈ 4.1518041109273234e+01 atol=1e-5
    in_ep = TCGEpoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01 atol=1e-5
end
@testset "UT1<->TAI" begin
    ep = UT1Epoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = TAIEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 3
    @test second(Float64, out_ep) ≈ 2.8485166135974850e+01 atol=1e-5
    in_ep = UT1Epoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01 atol=1e-5
end
@testset "UT1<->TT" begin
    ep = UT1Epoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = TTEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 4
    @test second(Float64, out_ep) ≈ 6.6916613597484800e-01 atol=1e-5
    in_ep = UT1Epoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01 atol=1e-5
end
@testset "UT1<->UTC" begin
    ep = UT1Epoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = UTCEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 2
    @test second(Float64, out_ep) ≈ 5.1485166135974850e+01 atol=1e-5
    in_ep = UT1Epoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01 atol=1e-5
end
@testset "UT1<->TDB" begin
    ep = UT1Epoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = TDBEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 4
    @test second(Float64, out_ep) ≈ 6.6812453518777910e-01 atol=1e-5
    in_ep = UT1Epoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01 atol=1e-5
end
@testset "UT1<->TCB" begin
    ep = UT1Epoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = TCBEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 4
    @test second(Float64, out_ep) ≈ 2.1031351964098377e+01 atol=1e-5
    in_ep = UT1Epoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01 atol=1e-5
end
@testset "UT1<->TCG" begin
    ep = UT1Epoch(2018, 8, 14, 10, 2, 5.1551247436378276e+01)
    out_ep = TCGEpoch(ep)
    @test year(out_ep) == 2018
    @test month(out_ep) == 8
    @test day(out_ep) == 14
    @test hour(out_ep) == 10
    @test minute(out_ep) == 4
    @test second(Float64, out_ep) ≈ 1.5844543054366440e+00 atol=1e-5
    in_ep = UT1Epoch(out_ep)
    @test year(in_ep) == 2018
    @test month(in_ep) == 8
    @test day(in_ep) == 14
    @test hour(in_ep) == 10
    @test minute(in_ep) == 2
    @test second(Float64, in_ep) ≈ 5.1551247436378276e+01 atol=1e-5
end


#
# Module to load the files to compute energies of PDB files
#

module PDBEnergy

  include("./src/Atom.jl")
  export Atom

  include("./src/Bond.jl")
  export Bond

  include("./src/Angle.jl")
  export Angle

  include("./src/Dihedral.jl")
  export Dihedral

  include("./src/Improper.jl")
  export Improper

  include("./src/readpsf.jl")
  export readpsf

  include("./src/readprm.jl")
  export readprm!

  include("./src/getpdbcoords.jl")
  export getpdbcoords!

  include("./src/coulomb.jl")
  export coulomb

  include("./src/vdw.jl")
  export vdw

  include("./src/nhbonds.jl")
  export nhbonds

end


using PrettyPlots, Plots, DataFrames
#gr();
#pgfplots();
pyplot();

# TODO consider moving this to PrettyPlots
default(guidefont = font(17), tickfont = font(15), legendfont = font(12), titlefont = font(20))
c=defineCase(;(:mode=>:caseStudyPath));

plotSettings(;(:simulate=>true),(:plant=>true),(:plantOnly=>false));

description = string(
"In this test: \n",c.m.name,"\n
* m.Ni=",c.m.Ni," \n
* m.Nck=",c.m.Nck,"\n
* m.tp=",c.m.tp," \n
* m.tex=",c.m.tex,"\n
* m.max_cpu_time=",c.m.max_cpu_time," \n
* m.Nck=",c.m.Nck,"\n
* m.PredictX0=",c.m.PredictX0," \n
* m.FixedTp=",c.m.FixedTp,"\n
")
results_dir=string("test1_10_obstacles")
resultsDir!(r,results_dir;description=description);
savePlantData(n,r)

if _pretty_defaults[:simulate];
  println("Plotting the Final Results!")
  mainSim(n,r,c,pa;(:mode=>:path3))
end

# TODO make this an example
if n.r.status==:Infeasible
  #Settings(;evalConstraints=true,save=true,MPC=false);
  n.s.evalConstraints=true;n.s.save=true;n.s.MPC=false;
  postProcess!(n)
  # trouble getting a feasible solution? -> look at the constraints
  print(n.r.constraint.value)
end # then consider relaxing tolerances etc. to make it work
# then looking at the output
# These are the dual infeasibilities
#=
│ Row │ step │ x0_con   │
├─────┼──────┼──────────┤
│ 1   │ 1    │ -267.777 │
│ 2   │ 2    │ 64.6356  │
│ 3   │ 3    │ 271.738  │
│ 4   │ 4    │ 2.44213  │
│ 5   │ 5    │ 3496.5   │
=#
# These are the dual infeasibilities

# we cam try to relax the constraint on the

#=
# define tolerances
XF_tol=[NaN,NaN,NaN,NaN,NaN];
X0_tol=[0.05,0.05,0.05,0.05,0.01];
defineTolerances(n;X0_tol=X0_tol,XF_tol=XF_tol);
=#

#using PrettyPlots
#s=Settings(;save=true,MPC=false,simulate=false,format=:png);
#pSimPath(n,r,s,c,r.eval_num)


using Random
using NamedArrays

#=
#   Write the team names of the teams that finished first in each group in `seeded`.
#   Do this in order of groups from A to H.
#
#   Then write the names of the teams who finished second in `unseeded` similarly.
#
#   Finally, make sure the teams you listed are included in the dictionaries containing
#   the teams from each country (i.e. `Spain`, `France`, etc.)
#
#   `simdraw(n)` generates an array where the j-th row and k-th column shows the number
#   of times in `n` simulations that the j-th seeded team meets the k-th unseeded team.
#   The i-th seeded (unseeded) team is the winner (runner-up) of the i-th group.
#
#   Run these simulations in Julia using
#
#       include("CLDraw.jl"); n = 10000; simdraw(n)
#
#   `simdraw_multhr(n)` is a multithreaded version of `simdraw(n)`. To simulate a single
#   draw, you can use `getdraw(seeded, unseeded, group, assoc)`.
=#
##
seeded = ["Bayern", "Real Madrid", "Man City", "Liverpool",
          "Chelsea", "Dortmund", "Juventus", "PSG"]
shortseed = ["BAY", "RMA", "MCI", "LIV", "CHE", "DOR", "JUV", "PSG"] # short names for seeded
unseeded = ["Atletico", "Monchengladbach", "Porto", "Atalanta",
            "Sevilla", "Lazio", "Barcelona", "RB Leipzig"]
shortuns = ["ATM", "MGB", "POR", "ATL", "SEV", "LAZ", "BAR", "RBL"] # short names for unseeded
groups = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']

Spain = Dict(team => "ESP" for team in ["Real Madrid", "Barcelona", "Atletico", "Valencia", "Sevilla"])
France = Dict(team => "FRA" for team in ["PSG", "Lyon"])
Germany = Dict(team => "GER" for team in ["Bayern", "RB Leipzig", "Dortmund", "Monchengladbach"])
England = Dict(team => "ENG" for team in  ["Man City", "Liverpool", "Tottenham", "Chelsea"])
Italy = Dict(team => "ITA" for team in ["Juventus", "Napoli", "Atalanta", "Lazio"])
Portugal = Dict(team => "POR" for team in ["Porto"])

group = merge(Dict(seeded[i] => groups[i] for i in eachindex(seeded)),
              Dict(unseeded[i] => groups[i] for i in eachindex(unseeded)))
assoc = merge(Spain, France, Germany, England, Italy, Portugal)

##
function matchable(team, opponent, group, assoc)
    # Checks if `team` and `opponent` are from different groups and associations.
    diffgroup = group[team] != group[opponent]
    diffassoc = assoc[team] != assoc[opponent]
    return diffgroup && diffassoc
end

##
function getmatches(team, seeded, unseeded, group, assoc)
    # Returns the possible opponents of `team`
    opponents = team in seeded ? unseeded : seeded
    # Check if `team` is `seeded` or `unseeded`
    matches = [opp for opp in opponents if matchable(team, opp, group, assoc)]
    return matches
end

##
function draw(team, seeded, unseeded, group, assoc)
    # Chooses an opponent randomly from the potential opponents
    possible = getmatches(team, seeded, unseeded, group, assoc)
    return rand(possible)
end

##
function getdraw(seeded, unseeded, group, assoc)
    # Takes the unmatched teams in `seeded` and `unseeded` and generates a random draw
    # according to CL rules.
    #
    # When the unmatched teams impose no restrictions on the remaining matches,
    # an unseeded team is choosen (uniformly) at random. Then an opponent is chosen
    # (uniformly) at random from the eligible seeded teams.
    #
    # If there are two unseeded teams who can only face the same two seeded teams,
    # these two unseeded teams are matched first.
    if length(seeded) == 1 || length(unseeded) == 1
        # Chooses a random match if there is only one seeded or unseeded team remaining.
        # We have no guarantee that the match is legal (different groups / associations).
        # Uncomment the line before `return Dict(home => away)` to generate a warning
        # for illegal matches.
        home = rand(unseeded)
        away = rand(seeded)
        #matchable(home, away, group, assoc) || @warn("Illegal match.")
        return Dict(home => away)
    end
    seedopps = map(x -> getmatches(x, seeded, unseeded, group, assoc), seeded)
    # Create a vector of the possible opponents of unmatched seeded teams.
    unopps = map(x -> getmatches(x, seeded, unseeded, group, assoc), unseeded)
    # Create a vector of possible opponents of unmatched unseeded teams.
    nseedopps = size.(seedopps, 1)
    # Count the possible opponents of each seeded team.
    nunopps = size.(unopps, 1)
    # Count the possible opponents of each unseeded team.
    oneseedopp = findfirst(isequal(1), nseedopps)
    # Checks if a seeded team has only one possible opponent.
    # Returns `nothing` if a seeded team has multiple possible opponents.
    oneunopp = findfirst(isequal(1), nunopps)
    # Checks if an unseeded team has only one possible opponent.
    # Returns `nothing` if an unseeded team has multiple possible opponents.
    if isnothing(oneseedopp) && isnothing(oneunopp)
        # Checks whether each team has multiple possible opponents.
        twounopps = findall(isequal(2), nunopps)
        if !allunique(unopps[twounopps])
            # If two teams have the same two possible opponents,
            # then these two pairs must face each other.
            #twounopp = findfirst(isequal(2), nunopps)
            #home = unseeded[twounopp]
            # Choose a team at random from the pair of unseeded teams.
            home = rand(unseeded[twounopps])
        else
            # Choose one team randomly from `unseeded`.
            home = rand(unseeded)
        end
        away = draw(home, seeded, unseeded, group, assoc)
    elseif isnothing(oneseedopp)
        # An unseeded team has only one possible opponent.
        home = unseeded[oneunopp]
        away = draw(home, seeded, unseeded, group, assoc)
    else
        # A seeded team has only one possible opponent.
        away = seeded[oneseedopp]
        home = draw(away, seeded, unseeded, group, assoc)
    end
    newunseed = filter(!isequal(home), unseeded)
    newseed = filter(!isequal(away), seeded)
    # Removes the matched teams from `seeded` and `unseeded`.
    tie = Dict(home => away)
    return merge(tie, getdraw(newseed, newunseed, group, assoc))
    # Recursively calls itself on the remaining unmatched teams.
end

##
function simdraw(n, seeded=seeded, unseeded=unseeded, group=group, assoc=assoc)
    count = zeros(Int, length(seeded), length(unseeded))
    for ii in 1:n
        #tempseeded = copy(seeded)
        #tempun = copy(unseeded)
        draw = getdraw(seeded, unseeded, group, assoc)
        for jj in eachindex(unseeded)
            count[:,jj] += draw[unseeded[jj]] .== seeded
        end
    end
    return count
end

##
function simdraw_multhr(n, seeded=seeded, unseeded=unseeded, group=group, assoc=assoc)
    nseeds = length(seeded)
    nunseeds = length(unseeded)
    count = zeros(Int, nseeds, nunseeds, Threads.nthreads())
    Threads.@threads for ii in 1:n
        draw = getdraw(seeded, unseeded, group, assoc)
        for jj in eachindex(unseeded)
            count[:,jj,Threads.threadid()] += draw[unseeded[jj]] .== seeded
        end
    end
    return dropdims(sum(count, dims=3); dims=3)
end

##
function showteams(data, seeded=shortseed, unseeded=shortuns)
    return NamedArray(data, (seeded, unseeded), ("Away", "Home"))
end


#!/usr/bin/env julia --project
# # [Example - Rising Thermal Bubble](@id EX-RTB-docs)
#
# In this example, we demonstrate the usage of the ClimateMachine [AtmosModel](@ref AtmosModel-docs)
# machinery to solve for a the fluid dynamics of a thermal perturbation
# in a neutrally stratified background state defined by its uniform
# potential temperature. We solve a flow in a [`FlatOrientation`](@ref LESConfig) (Box) configuration - this is representative of a large-eddy simulation. Several versions of the problem setup
# may be found in literature, but the general idea is to examine the vertical ascent of a
# thermal `bubble` (we can interpret these as simple representation of convective updrafts).
# The example is essentially a lineb-by-line walkthrough.
#
# ## Description of experiment
# 1) Dry Rising Bubble (circular potential temperature perturbation)
# 2) Boundaries
#    - `Impenetrable(FreeSlip())` - no momentum flux, no mass flux through walls.
#    - `Impermeable()` - non-porous walls, i.e. no diffusive fluxes through walls.
#    - Laterally periodic
# 3) Domain - 2500m (horizontal) x 2500m (horizontal) x 2500m (vertical)
# 4) Resolution - 50m effective resolution
# 5) Total simulation time - 1000s
# 6) Mesh Aspect Ratio (Effective resolution) 1:1
# 7) Overrides defaults for
#    - CPU Initialisation
#    - Time integrator
#    - Sources
#    - Smagorinsky Coefficient
# 8) Default settings can be found in `src/Driver/<files>.jl`

#md # !!! note
#md #     This experiment setup assumes that you have installed ClimateMachine according to
#md #     the instructions on the landing page. We suggest that you attempt to run the
#md #     `runtests.jl` file in the main `ClimateMachine` directory to ensure that packages and
#md #     `git` histories are consistently installed / managed. We assume the users' familiarity
#md #     with the conservative form of the equations of motion for a compressible fluid (see the [`AtmosModel`](@ref AtmosModel-docs) page).
#md #
#md #     The following topics are covered in this example
#md #     - Package requirements
#md #     - Defining a `model` subtype for the set of conservation equations
#md #     - Defining the initial conditions
#md #     - Applying boundary conditions
#md #     - Applying source terms
#md #     - Choosing a turbulence model
#md #     - Adding tracers to the model
#md #     - Choosing a time-integrator
#md #     - Choosing diagnostics (output) configurations
#md #
#md #     The following topics are not covered in this example
#md #     - Defining new boundary conditions
#md #     - Defining new turbulence models
#md #     - Building new time-integrators
#md #     - Adding diagnostic variables (beyond a standard pre-defined list of variables)
#
# ## Boilerplate (Using Modules)
#
# #### [Skip Section](@ref init)
#
# Before setting up our experiment, we recognize that we need to import
# some pre-defined functions from other packages. Julia allows us to use existing modules (variable workspaces), or write our own to do so.
# Complete documentation for the Julia module system can be found [here](https://docs.julialang.org/en/v1/manual/modules/#).

# We need to use the ClimateMachine module! This imports all functions specific to atmospheric and ocean flow modelling.
# While we do not cover the ins-and-outs of the contents of each of these we provide brief descriptions of the utility of each of the loaded packages.
using ClimateMachine
ClimateMachine.init()

using ClimateMachine.Atmos
# - Required so that we inherit the appropriate model types for the large-eddy simulation (LES) and global-circulation-model (GCM) configurations.
using ClimateMachine.ConfigTypes
# - Required so that we may define diagnostics configurations, e.g. choice of file-writer, choice of output variable sets, output-frequency and directory,
using ClimateMachine.Diagnostics
# - Required so that we may define (or utilise existing functions) functions that are `called-back` or executed at frequencies of either timesteps, simulation-time, or wall-clock time.
using ClimateMachine.GenericCallbacks
# - Required so we load the appropriate functions for the time-integration component. Contains ODESolver methods.
using ClimateMachine.ODESolvers
# - Required for utility of spatial filtering functions (e.g. positivity preservation)
using ClimateMachine.Mesh.Filters
# - Required so functions for computation of moist thermodynamic quantities is enabled.
using ClimateMachine.MoistThermodynamics
# - Required so we may access our variable arrays by a sensible naming convention rather than by numerical array indices.
using ClimateMachine.VariableTemplates
# - Required so we may access planet parameters ([CLIMAParameters](https://CliMA.github.io/CLIMAParameters.jl/latest/) specific to this problem include the gas constant, specific heats, mean-sea-level pressure, gravity and the Smagorinsky coefficient)

# In ClimateMachine we use `StaticArrays` for our variable arrays.
using StaticArrays
# We also use the `Test` package to help with unit tests and continuous integration systems to design sensible tests
# for our experiment to ensure new / modified blocks of code don't damage the fidelity of the physics. The test defined within this experiment is not a unit test for a specific subcomponent, but ensures time-integration of the defined problem conditions within a reasonable tolerance. Immediately useful macros and functions from this include `@test` and `@testset` which will allow us to define the testing parameter sets.
using Test

using CLIMAParameters
using CLIMAParameters.Atmos.SubgridScale: C_smag
using CLIMAParameters.Planet: R_d, cp_d, cv_d, MSLP, grav
struct EarthParameterSet <: AbstractEarthParameterSet end
const param_set = EarthParameterSet()

# ## [Initial Conditions](@id init)
# This example of a rising thermal bubble can be classified as an initial value problem. We must (at the very least) assign values for the initial variables in a sensible manner. This example demonstrates the use of functions defined in the [`MoistThermodynamics`](@ref MoistThermodynamics-docs) package to generate the appropriate initial state for our problem.

#md # !!! note
#md #     The following variables are assigned in the initial condition
#md #     - `state.ρ` = Scalar quantity for initial density profile
#md #     - `state.ρu`= 3-component vector for initial momentum profile
#md #     - `state.ρe`= Scalar quantity for initial total-energy profile
#md #     - `state.moisture.ρq_tot` = Scalar quantity for the total specific humidity
#md #     - `state.tracers.ρχ` = Vector of four tracers (here, for demonstration only; we can interpret these as dye injections for visualisation purposes)

"""
    init_risingbubble!(bl, state, aux, (x,y,z), t)

Arguments

- bl = `BalanceLaw` (for this example, this is the `AtmosModel <: BalanceLaw`)
- state = Array of prognostic (or state) variables. These are conserved quantities. Indices accessed using the `VariableTemplates` system. (elements are floating-point values)
- aux = Array of auxiliary (or helper) variables. These may include auxiliary thermodynamic variables / diagnostics / variables for debug assistance (and other variables for which we don't need time-integration) (elements are floating-point values)
- (x,y,z) = `Tuple` specifying the `local Cartesian coordinates`. (elements are floating point values)
- t = Simulation time in seconds, a `Real` value.

Returns
- Updated values for `state.<variable_name>`
"""
function init_risingbubble!(bl, state, aux, (x, y, z), t)
    # Problem float-type
    FT = eltype(state)

    # Unpack constant parameters
    R_gas::FT = R_d(bl.param_set)
    c_p::FT = cp_d(bl.param_set)
    c_v::FT = cv_d(bl.param_set)
    p0::FT = MSLP(bl.param_set)
    _grav::FT = grav(bl.param_set)
    γ::FT = c_p / c_v

    # Define bubble center and background potential temperature
    xc::FT = 1250
    yc::FT = 1250
    zc::FT = 1000
    r = sqrt((x - xc)^2 + (y - yc)^2 + (z - zc)^2)
    rc::FT = 500
    θ_ref::FT = 300
    Δθ::FT = 0

    # Compute temperature difference over bubble region
    if r <= rc
        Δθ = FT(5) * cospi(r / rc / 2)
    end

    # Compute perturbed thermodynamic state:
    θ = θ_ref + Δθ                                      # potential temperature
    π_exner = FT(1) - _grav / (c_p * θ) * z             # exner pressure
    ρ = p0 / (R_gas * θ) * (π_exner)^(c_v / R_gas)      # density
    q_tot = FT(0)                                       # total specific humidity
    ts = LiquidIcePotTempSHumEquil(bl.param_set, θ, ρ, q_tot) # thermodynamic state
    q_pt = PhasePartition(ts)                           # phase partition
    ρu = SVector(FT(0), FT(0), FT(0))                   # momentum
    #State (prognostic) variable assignment
    e_kin = FT(0)                                       # kinetic energy
    e_pot = gravitational_potential(bl.orientation, aux)# potential energy
    ρe_tot = ρ * total_energy(e_kin, e_pot, ts)         # total energy

    ρχ = FT(0)                                          # tracer

    # We inject tracers at the initial condition at some specified z coordinates
    if 500 < z <= 550
        ρχ += FT(0.05)
    end

    # We want 4 tracers
    ntracers = 4

    # Define 4 tracers, (arbitrary scaling for this demo problem)
    ρχ = SVector{ntracers, FT}(ρχ, ρχ / 2, ρχ / 3, ρχ / 4)

    # Assign State Variables
    state.ρ = ρ
    state.ρu = ρu
    state.ρe = ρe_tot
    state.moisture.ρq_tot = ρ * q_pt.tot
    state.tracers.ρχ = ρχ
end

# ## [Model Configuration](@id config-helper)
# We define a configuration function to assist in prescribing the physical model. The purpose of this is to populate the [`ClimateMachine.AtmosLESConfiguration`](@ref LESConfig) with arguments appropriate to the problem being considered.
"""
    config_risingbubble(FT, N, resolution, xmax, ymax, zmax)

Arguments
- FT = Floating-point type. Currently `Float64` or `Float32`
- N  = DG Polynomial order
- resolution = 3-component Tuple (Δx, Δy, Δz) with effective resolutions for Cartesian directions
- xmax, ymax, zmax = Domain maximum extents. Assumes (0,0,0) to be the domain minimum extents unless otherwise specified.

Returns
- `config` = Object using the constructor for the `AtmosLESConfiguration`
"""
function config_risingbubble(FT, N, resolution, xmax, ymax, zmax)

    # Choose an Explicit Multi-rate Solver from the existing [ODESolvers](@ref ODESolvers-docs) options
    # Apply the outer constructor to define the `ode_solver`. Here
    # `AtmosAcousticGravityLinearModel` splits the acoustic-gravity wave components
    # from the advection-diffusion dynamics. The 1D-IMEX method is less appropriate for the problem given the current mesh aspect ratio (1:1)
    ode_solver = ClimateMachine.MultirateSolverType(
        linear_model = AtmosAcousticGravityLinearModel,
        slow_method = LSRK144NiegemannDiehlBusch,
        fast_method = LSRK144NiegemannDiehlBusch,
        timestep_ratio = 10,
    )

    # Since we want four tracers, we specify this and include
    # the appropriate diffusivity scaling coefficients (normally these
    # would be physically informed but for this demonstration we use
    # integers corresponding to the tracer index identifier)
    ntracers = 4
    δ_χ = SVector{ntracers, FT}(1, 2, 3, 4)

    # The model coefficient for the turbulence closure is defined via the [CLIMAParameters
    # package](https://CliMA.github.io/CLIMAParameters.jl/latest/)
    # A reference state for the linearisation step is also defined.
    ref_state =
        HydrostaticState(DryAdiabaticProfile(typemin(FT), FT(300)), FT(0))

    # The fun part! Here we assemble the `AtmosModel`.
    #md # !!! note
    #md #     Docs on model subcomponent options can be found here:
    #md #     - [`param_set`](https://CliMA.github.io/CLIMAParameters.jl/latest/)
    #md #     - [`turbulence`](@ref Turbulence-Closures-docs)
    #md #     - [`hyperdiffusion`](@ref Hyperdiffusion-docs)
    #md #     - [`source`](@ref atmos-sources)
    #md #     - [`tracers`](@ref Tracers-docs)
    #md #     - [`init_state`](@ref init)

    _C_smag = FT(C_smag(param_set))
    model = AtmosModel{FT}(
        AtmosLESConfigType,                          # Flow in a box, requires the AtmosLESConfigType
        param_set;                                   # Parameter set corresponding to earth parameters
        turbulence = SmagorinskyLilly(_C_smag),       # Turbulence closure model
        hyperdiffusion = StandardHyperDiffusion(60), # Hyperdiffusion (4th order) model
        source = (Gravity(),),                       # Gravity is the only source term here
        tracers = NTracers{ntracers, FT}(δ_χ),       # Tracer model with diffusivity coefficients
        ref_state = ref_state,                       # Reference state
        init_state_conservative = init_risingbubble!,             # Apply the initial condition
    )

    # Finally,  we pass a `Problem Name` string, the mesh information, and the model type to  the [`AtmosLESConfiguration`] object.
    config = ClimateMachine.AtmosLESConfiguration(
        "DryRisingBubble",       # Problem title [String]
        N,                       # Polynomial order [Int]
        resolution,              # (Δx, Δy, Δz) effective resolution [m]
        xmax,                    # Domain maximum size [m]
        ymax,                    # Domain maximum size [m]
        zmax,                    # Domain maximum size [m]
        param_set,               # Parameter set.
        init_risingbubble!,      # Function specifying initial condition
        solver_type = ode_solver,# Time-integrator type
        model = model,           # Model type
    )
    return config
end

#md # !!! note
#md #     `Keywords` are used to specify some arguments (see appropriate source files).

# ## [Diagnostics](@id config_diagnostics)
# Here we define the diagnostic configuration specific to this problem.
function config_diagnostics(driver_config)
    interval = "10000steps"
    dgngrp = setup_atmos_default_diagnostics(interval, driver_config.name)
    return ClimateMachine.DiagnosticsConfiguration([dgngrp])
end

function main()
    # These are essentially arguments passed to the [`config_risingbubble`](@ref config-helper) function.
    # For type consistency we explicitly define the problem floating-precision.
    FT = Float64
    # We need to specify the polynomial order for the DG discretization, effective resolution, simulation end-time,
    # the domain bounds, and the courant-number for the time-integrator. Note how the time-integration components
    # `solver_config` are distinct from the spatial / model components in `driver_config`. `init_on_cpu` is a helper
    # keyword argument that forces problem initialisation on CPU (thereby allowing the use of random seeds, spline interpolants and other special functions at the initialisation step.)
    N = 4
    Δh = FT(50)
    Δv = FT(50)
    resolution = (Δh, Δh, Δv)
    xmax = FT(2500)
    ymax = FT(2500)
    zmax = FT(2500)
    t0 = FT(0)
    timeend = FT(1000)
    CFL = FT(20)

    # Assign configurations so they can be passed to the `invoke!` function
    driver_config = config_risingbubble(FT, N, resolution, xmax, ymax, zmax)
    solver_config = ClimateMachine.SolverConfiguration(
        t0,
        timeend,
        driver_config,
        init_on_cpu = true,
        Courant_number = CFL,
    )
    dgn_config = config_diagnostics(driver_config)

    # User defined filter (TMAR positivity preserving filter)
    cbtmarfilter = GenericCallbacks.EveryXSimulationSteps(1) do (init = false)
        Filters.apply!(solver_config.Q, 6, solver_config.dg.grid, TMARFilter())
        nothing
    end

    # Invoke solver (calls `solve!` function for time-integrator), pass the driver, solver and diagnostic config
    # information.
    result = ClimateMachine.invoke!(
        solver_config;
        diagnostics_config = dgn_config,
        user_callbacks = (cbtmarfilter,),
        check_euclidean_distance = true,
    )

    # Check that the solution norm is reasonable.
    @test isapprox(result, FT(1); atol = 1.5e-3)
end

# The experiment definition is now complete. Time to run it.

# ## Running the Experiment
# `julia --project /experiments/AtmosLES/risingbubble.jl` will run the experiment from the main ClimateMachine directory,
# with diagnostics output at the intervals specified in [`config_diagnostics`](@ref config_diagnostics). You
# can also prescribe command line arguments (docs pending, `Driver.jl`) for simulation update and output specifications.
# For rapid turnaround, we recommend that you run this experiment on a GPU.

# ## [Output Visualisation](@id output-viz)
#md # See command line output arguments listed [here](https://github.com/CliMA/ClimateMachine.jl/wiki/ClimateMachine-command-line-arguments).
#md # For VTK output,
#md # - [VisIt](https://wci.llnl.gov/simulation/computer-codes/visit/)
#md # - [Paraview](https://wci.llnl.gov/simulation/computer-codes/visit/)
#md # are two commonly used programs for `.vtu` files.
#md # For NetCDF or JLD2 diagnostics you may use Julia's [`NCDatasets`](https://github.com/Alexander-Barth/NCDatasets.jl) and [`JLD2`](https://github.com/JuliaIO/JLD2.jl) packages with
#md # a suitable plotting program.
#
#

main()


# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Description
# ==============================================================================
#
#   Tests related to equinox-based IAU-2006 transformations.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# References
# ==============================================================================
#
#   [1] Vallado, D. A (2013). Fundamentals of Astrodynamics and Applications.
#       Microcosm Press, Hawthorn, CA, USA.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

# File: ./src/transformations/iau2006/iau2006_equinox.jl
# ======================================================

# Functions r_tirs_to_ers_iau2006 and r_ers_to_tirs_iau2006
# ---------------------------------------------------------

################################################################################
#                                 Test Results
################################################################################
#
# Scenario 01
# ===========
#
# Example 3-14: Performing an IAU-2000 reduction [1, p. 220]
#
# According to this example and Table 3-6, using:
#
#   JD_UT1 = 2453101.827406783
#   JD_TT  = 2453101.828154745
#   r_tirs  = -1033.47503120   i + 7901.30558560   j + 6380.34453270   k [km]
#   v_tirs  =    -3.2256327470 i -    2.8724425110 j +    5.5319312880 k [km/s]
#
# one gets the following:
#
#   r_ers  = +5094.51462800   i + 6127.36658790   j + 6380.34453270   k [km]
#   v_ers  =    -4.7460885870 i -    0.7860771040 j +    5.5319312880 k [km/s]
#
################################################################################

@testset "Functions r_tirs_to_ers_iau2006 and r_ers_to_tirs_iau2006" begin
    JD_UT1 = 2453101.827406783
    JD_TT  = 2453101.828154745
    LOD    = 0.0015563
    w      = 7.292115146706979e-5 * (1 - LOD / 86400)

    # r_tirs_to_ers_iau2006
    # ==================

    r_tirs  = [-1033.47503120; 7901.30558560; 6380.34453270]
    v_tirs  = [-3.2256327470; -2.8724425110; +5.5319312880]

    # DCM
    # ---

    D_ERS_TIRS = r_tirs_to_ers_iau2006(JD_UT1, JD_TT)

    r_ers = D_ERS_TIRS * r_tirs
    v_ers = D_ERS_TIRS * (v_tirs + [0, 0, w] × r_tirs)

    @test r_ers[1] ≈ +5094.51462800 atol=5e-6
    @test r_ers[2] ≈ +6127.36658790 atol=5e-6
    @test r_ers[3] ≈ +6380.34453270 atol=5e-6

    @test v_ers[1] ≈ -4.7460885870  atol=1e-9
    @test v_ers[2] ≈ +0.7860771040  atol=1e-9
    @test v_ers[3] ≈ +5.5319312880  atol=1e-9

    # Quaternion
    # ----------

    q_ERS_TIRS = r_tirs_to_ers_iau2006(Quaternion, JD_UT1, JD_TT)

    r_ers = vect(q_ERS_TIRS \ r_tirs * q_ERS_TIRS)
    v_ers = vect(q_ERS_TIRS \ (v_tirs + [0, 0, w] × r_tirs) * q_ERS_TIRS)

    @test r_ers[1] ≈ +5094.51462800 atol=5e-6
    @test r_ers[2] ≈ +6127.36658790 atol=5e-6
    @test r_ers[3] ≈ +6380.34453270 atol=5e-6

    @test v_ers[1] ≈ -4.7460885870  atol=1e-9
    @test v_ers[2] ≈ +0.7860771040  atol=1e-9
    @test v_ers[3] ≈ +5.5319312880  atol=1e-9

    # r_ers_to_tirs_iau2006
    # ==================

    r_ers = [+5094.51462800; +6127.36658790; +6380.34453270]
    v_ers = [-4.7460885870; +0.7860771040; +5.5319312880]

    # DCM
    # ---

    D_TIRS_ERS = r_ers_to_tirs_iau2006(JD_UT1, JD_TT)

    r_tirs = D_TIRS_ERS * r_ers
    v_tirs = D_TIRS_ERS * v_ers - [0, 0, w] × r_tirs

    @test r_tirs[1] ≈ -1033.47503120 atol=5e-6
    @test r_tirs[2] ≈ +7901.30558560 atol=5e-6
    @test r_tirs[3] ≈ +6380.34453270 atol=5e-6

    @test v_tirs[1] ≈ -3.2256327470  atol=1e-9
    @test v_tirs[2] ≈ -2.8724425110  atol=1e-9
    @test v_tirs[3] ≈ +5.5319312880  atol=1e-9

    # Quaternion
    # ----------

    q_TIRS_ERS = r_ers_to_tirs_iau2006(Quaternion, JD_UT1, JD_TT)

    r_tirs = vect(q_TIRS_ERS\r_ers*q_TIRS_ERS)
    v_tirs = vect(q_TIRS_ERS\v_ers*q_TIRS_ERS) - [0;0;w] × r_tirs

    @test r_tirs[1] ≈ -1033.47503120 atol=5e-6
    @test r_tirs[2] ≈ +7901.30558560 atol=5e-6
    @test r_tirs[3] ≈ +6380.34453270 atol=5e-6

    @test v_tirs[1] ≈ -3.2256327470  atol=1e-9
    @test v_tirs[2] ≈ -2.8724425110  atol=1e-9
    @test v_tirs[3] ≈ +5.5319312880  atol=1e-9
end

# Functions r_ers_to_mod_iau2006 and r_mod_to_ers_iau2006
# -------------------------------------------------------

################################################################################
#                                 Test Results
################################################################################
#
# Scenario 01
# ===========
#
# Example 3-14: Performing an IAU-2000 reduction [1, p. 220]
#
# According to this example and Table 3-6, using:
#
#   JD_TT  = 2453101.828154745
#   r_ers  = +5094.51462800   i + 6127.36658790   j + 6380.34453270   k [km]
#   v_ers  =    -4.7460885870 i -    0.7860771040 j +    5.5319312880 k [km/s]
#
# one gets the following:
#
#   r_mod  = +5094.02896110   i + 6127.87113500   j + 6380.24774200   k [km]
#   v_mod  =    -4.7462624800 i +    0.7860141930 j +    5.5317910320 k [km/s]
#
################################################################################

@testset "Functions r_ers_to_mod_iau2006 and r_mod_to_ers_iau2006" begin
    JD_TT  = 2453101.828154745

    # r_ers_to_mod_iau2006
    # ====================

    r_ers = [+5094.51462800; +6127.36658790; +6380.34453270]
    v_ers = [-4.7460885870; +0.7860771040; +5.5319312880]

    # DCM
    # ---

    D_MOD_ERS = r_ers_to_mod_iau2006(JD_TT)

    r_mod = D_MOD_ERS * r_ers
    v_mod = D_MOD_ERS * v_ers

    @test r_mod[1] ≈ +5094.02896110 atol=1e-7
    @test r_mod[2] ≈ +6127.87113500 atol=1e-7
    @test r_mod[3] ≈ +6380.24774200 atol=1e-7

    @test v_mod[1] ≈ -4.7462624800  atol=1e-9
    @test v_mod[2] ≈ +0.7860141930  atol=1e-9
    @test v_mod[3] ≈ +5.5317910320  atol=1e-9

    # Quaternion
    # ----------

    q_MOD_ERS = r_ers_to_mod_iau2006(Quaternion, JD_TT)

    r_mod = vect(q_MOD_ERS \ r_ers * q_MOD_ERS)
    v_mod = vect(q_MOD_ERS \ v_ers * q_MOD_ERS)

    @test r_mod[1] ≈ +5094.02896110 atol=1e-7
    @test r_mod[2] ≈ +6127.87113500 atol=1e-7
    @test r_mod[3] ≈ +6380.24774200 atol=1e-7

    @test v_mod[1] ≈ -4.7462624800  atol=1e-9
    @test v_mod[2] ≈ +0.7860141930  atol=1e-9
    @test v_mod[3] ≈ +5.5317910320  atol=1e-9

    # r_mod_to_ers_iau2006
    # ====================

    r_mod = [+5094.02896110; +6127.87113500; +6380.24774200]
    v_mod = [-4.7462624800; +0.7860141930; +5.5317910320]

    # DCM
    # ---

    D_ERS_MOD = r_mod_to_ers_iau2006(JD_TT)

    r_ers = D_ERS_MOD * r_mod
    v_ers = D_ERS_MOD * v_mod

    @test r_ers[1] ≈ +5094.51462800 atol=1e-7
    @test r_ers[2] ≈ +6127.36658790 atol=1e-7
    @test r_ers[3] ≈ +6380.34453270 atol=1e-7

    @test v_ers[1] ≈ -4.7460885870  atol=1e-9
    @test v_ers[2] ≈ +0.7860771040  atol=1e-9
    @test v_ers[3] ≈ +5.5319312880  atol=1e-9

    # Quaternion
    # ----------

    q_ERS_MOD = r_mod_to_ers_iau2006(Quaternion, JD_TT)

    r_ers = vect(q_ERS_MOD \ r_mod * q_ERS_MOD)
    v_ers = vect(q_ERS_MOD \ v_mod * q_ERS_MOD)

    @test r_ers[1] ≈ +5094.51462800 atol=1e-7
    @test r_ers[2] ≈ +6127.36658790 atol=1e-7
    @test r_ers[3] ≈ +6380.34453270 atol=1e-7

    @test v_ers[1] ≈ -4.7460885870  atol=1e-9
    @test v_ers[2] ≈ +0.7860771040  atol=1e-9
    @test v_ers[3] ≈ +5.5319312880  atol=1e-9
end

# Functions r_mod_to_mj2000_iau2006, r_mj2000_to_mod_iau2006, r_mj2000_to_gcrf_iau2006, and r_gcrf_to_mj2000_iau2006
# ------------------------------------------------------------------------------------------------------------------

# We are testing all these functions together because we have in [1] the result
# MOD <=> GCRF using equinox-based IAU-2006 theory, but we do not have the
# intermediate result MOD <=> MJ2000.

################################################################################
#                                 Test Results
################################################################################
#
# Scenario 01
# ===========
#
# Example 3-14: Performing an IAU-2000 reduction [1, p. 220]
#
# According to this example and Table 3-6, using:
#
#   JD_TT  = 2453101.828154745
#   r_mod  = +5094.02896110   i + 6127.87113500   j + 6380.24774200   k [km]
#   v_mod  =    -4.7462624800 i +    0.7860141930 j +    5.5317910320 k [km/s]
#
# one gets the following:
#
#   r_gcrf = 5102.50895780  i + 6123.01140380 j + 6378.13692530 k [km]
#   v_gcrf =  -4.7432201560 i + 0.7905364970  j + 5.5337557280  k [km/s]
#
################################################################################

@testset "Functions r_mod_to_mj2000_iau2006, r_mj2000_to_mod_iau2006, r_mj2000_to_gcrf_iau2006, and r_gcrf_to_mj2000_iau2006" begin
    JD_TT  = 2453101.828154745

    # MOD to GCRF
    # ===========

    r_mod = [+5094.02896110; +6127.87113500; +6380.24774200]
    v_mod = [-4.7462624800; +0.7860141930; +5.5317910320]

    # DCM
    # ---

    D_GCRF_MOD = r_mj2000_to_gcrf_iau2006(JD_TT) * r_mod_to_mj2000_iau2006(JD_TT)

    r_gcrf = D_GCRF_MOD * r_mod
    v_gcrf = D_GCRF_MOD * v_mod

    @test r_gcrf[1] ≈ +5102.50895780 atol=1e-7
    @test r_gcrf[2] ≈ +6123.01140380 atol=1e-7
    @test r_gcrf[3] ≈ +6378.13692530 atol=1e-7

    @test v_gcrf[1] ≈ -4.7432201560  atol=1e-9
    @test v_gcrf[2] ≈ +0.7905364970  atol=1e-9
    @test v_gcrf[3] ≈ +5.5337557280  atol=1e-9

    # Quaternion
    # ----------

    q_GCRF_MOD = r_mod_to_mj2000_iau2006(Quaternion, JD_TT) *
                 r_mj2000_to_gcrf_iau2006(Quaternion, JD_TT)

    r_gcrf = vect(q_GCRF_MOD \ r_mod * q_GCRF_MOD)
    v_gcrf = vect(q_GCRF_MOD \ v_mod * q_GCRF_MOD)

    @test r_gcrf[1] ≈ +5102.50895780 atol=1e-7
    @test r_gcrf[2] ≈ +6123.01140380 atol=1e-7
    @test r_gcrf[3] ≈ +6378.13692530 atol=1e-7

    @test v_gcrf[1] ≈ -4.7432201560  atol=1e-9
    @test v_gcrf[2] ≈ +0.7905364970  atol=1e-9
    @test v_gcrf[3] ≈ +5.5337557280  atol=1e-9

    # GCRF to MOD
    # ===========

    r_gcrf = [+5102.50895780; +6123.01140380; +6378.13692530]
    v_gcrf = [-4.7432201560; +0.7905364970; +5.5337557280]

    # DCM
    # ---

    D_MOD_GCRF = r_mj2000_to_mod_iau2006(JD_TT) * r_gcrf_to_mj2000_iau2006(JD_TT)

    r_mod = D_MOD_GCRF * r_gcrf
    v_mod = D_MOD_GCRF * v_gcrf

    @test r_mod[1] ≈ +5094.02896110 atol=1e-7
    @test r_mod[2] ≈ +6127.87113500 atol=1e-7
    @test r_mod[3] ≈ +6380.24774200 atol=1e-7

    @test v_mod[1] ≈ -4.7462624800  atol=1e-9
    @test v_mod[2] ≈ +0.7860141930  atol=1e-9
    @test v_mod[3] ≈ +5.5317910320  atol=1e-9

    # Quaternion
    # ----------

    q_MOD_GCRF = r_gcrf_to_mj2000_iau2006(Quaternion, JD_TT) *
                 r_mj2000_to_mod_iau2006(Quaternion, JD_TT)

    r_mod = vect(q_MOD_GCRF \ r_gcrf * q_MOD_GCRF)
    v_mod = vect(q_MOD_GCRF \ v_gcrf * q_MOD_GCRF)

    @test r_mod[1] ≈ +5094.02896110 atol=1e-7
    @test r_mod[2] ≈ +6127.87113500 atol=1e-7
    @test r_mod[3] ≈ +6380.24774200 atol=1e-7

    @test v_mod[1] ≈ -4.7462624800  atol=1e-9
    @test v_mod[2] ≈ +0.7860141930  atol=1e-9
    @test v_mod[3] ≈ +5.5317910320  atol=1e-9
end

# Functions r_tirs_to_mod_iau2006 and r_mod_to_tirs_iau2006
# ---------------------------------------------------------

################################################################################
#                                 Test Results
################################################################################
#
# Scenario 01
# ===========
#
# Example 3-14: Performing an IAU-2000 reduction [1, p. 220]
#
# According to this example and Table 3-6, using:
#
#   JD_UT1 = 2453101.827406783
#   JD_TT  = 2453101.828154745
#   r_tirs  = -1033.47503120   i + 7901.30558560   j + 6380.34453270   k [km]
#   v_tirs  =    -3.2256327470 i -    2.8724425110 j +    5.5319312880 k [km/s]
#
# one gets the following:
#
#   r_mod  = +5094.02896110   i + 6127.87113500   j + 6380.24774200   k [km]
#   v_mod  =    -4.7462624800 i +    0.7860141930 j +    5.5317910320 k [km/s]
#
################################################################################

@testset "Functions r_tirs_to_mod_iau2006 and r_mod_to_tirs_iau2006" begin
    JD_UT1 = 2453101.827406783
    JD_TT  = 2453101.828154745
    LOD    = 0.0015563
    w      = 7.292115146706979e-5 * (1 - LOD / 86400)

    # r_tirs_to_mod_iau2006
    # ==================

    r_tirs  = [-1033.47503120; 7901.30558560; 6380.34453270]
    v_tirs  = [-3.2256327470; -2.8724425110; +5.5319312880]

    # DCM
    # ---

    D_MOD_TIRS = r_tirs_to_mod_iau2006(JD_UT1, JD_TT)

    r_mod = D_MOD_TIRS * r_tirs
    v_mod = D_MOD_TIRS * (v_tirs + [0, 0, w] × r_tirs)

    @test r_mod[1] ≈ +5094.02896110 atol=5e-6
    @test r_mod[2] ≈ +6127.87113500 atol=5e-6
    @test r_mod[3] ≈ +6380.24774200 atol=5e-6

    @test v_mod[1] ≈ -4.7462624800  atol=5e-9
    @test v_mod[2] ≈ +0.7860141930  atol=5e-9
    @test v_mod[3] ≈ +5.5317910320  atol=5e-9

    # Quaternion
    # ----------

    q_MOD_TIRS = r_tirs_to_mod_iau2006(Quaternion, JD_UT1, JD_TT)

    r_mod = vect(q_MOD_TIRS \ r_tirs * q_MOD_TIRS)
    v_mod = vect(q_MOD_TIRS \ (v_tirs + [0, 0, w] × r_tirs) * q_MOD_TIRS)

    @test r_mod[1] ≈ +5094.02896110 atol=5e-6
    @test r_mod[2] ≈ +6127.87113500 atol=5e-6
    @test r_mod[3] ≈ +6380.24774200 atol=5e-6

    @test v_mod[1] ≈ -4.7462624800  atol=5e-9
    @test v_mod[2] ≈ +0.7860141930  atol=5e-9
    @test v_mod[3] ≈ +5.5317910320  atol=5e-9

    # r_ers_to_tirs_iau2006
    # ==================

    r_mod = [+5094.02896110; +6127.87113500; +6380.24774200]
    v_mod = [-4.7462624800; +0.7860141930; +5.5317910320]

    # DCM
    # ---

    D_TIRS_MOD = r_mod_to_tirs_iau2006(JD_UT1, JD_TT)

    r_tirs = D_TIRS_MOD * r_mod
    v_tirs = D_TIRS_MOD * v_mod - [0, 0, w] × r_tirs

    @test r_tirs[1] ≈ -1033.47503120 atol=5e-6
    @test r_tirs[2] ≈ +7901.30558560 atol=5e-6
    @test r_tirs[3] ≈ +6380.34453270 atol=5e-6

    @test v_tirs[1] ≈ -3.2256327470  atol=5e-9
    @test v_tirs[2] ≈ -2.8724425110  atol=5e-9
    @test v_tirs[3] ≈ +5.5319312880  atol=5e-9

    # Quaternion
    # ----------

    q_TIRS_MOD = r_mod_to_tirs_iau2006(Quaternion, JD_UT1, JD_TT)

    r_tirs = vect(q_TIRS_MOD \ r_mod * q_TIRS_MOD)
    v_tirs = vect(q_TIRS_MOD \ v_mod * q_TIRS_MOD) - [0, 0, w] × r_tirs

    @test r_tirs[1] ≈ -1033.47503120 atol=5e-6
    @test r_tirs[2] ≈ +7901.30558560 atol=5e-6
    @test r_tirs[3] ≈ +6380.34453270 atol=5e-6

    @test v_tirs[1] ≈ -3.2256327470  atol=5e-9
    @test v_tirs[2] ≈ -2.8724425110  atol=5e-9
    @test v_tirs[3] ≈ +5.5319312880  atol=5e-9
end


function IntegerLeastSquareSolution(arg0::Vector{jlong}, arg1::jdouble)
    return IntegerLeastSquareSolution((Vector{jlong}, jdouble), arg0, arg1)
end

function compare_to(obj::IntegerLeastSquareSolution, arg0::IntegerLeastSquareSolution)
    return jcall(obj, "compareTo", jint, (IntegerLeastSquareSolution,), arg0)
end

function equals(obj::IntegerLeastSquareSolution, arg0::Object)
    return jcall(obj, "equals", jboolean, (Object,), arg0)
end

function get_solution(obj::IntegerLeastSquareSolution)
    return jcall(obj, "getSolution", Vector{jlong}, ())
end

function get_squared_distance(obj::IntegerLeastSquareSolution)
    return jcall(obj, "getSquaredDistance", jdouble, ())
end

function hash_code(obj::IntegerLeastSquareSolution)
    return jcall(obj, "hashCode", jint, ())
end


module MPC_Module

using JuMP
using OrdinaryDiffEq
using DiffEqBase

include("Base.jl")
using .Base

export
     MPC,
     defineMPC!,
     initOpt!,
     defineIP!,
     mapNames!,
     simIPlant!,
     updateX0!,
     currentIPState,
     goalReached!,
     simMPC!,
     predictX0!

########################################################################################
# MPC structs
########################################################################################

mutable struct IP
 control::Control
 state::State
end

function IP()
 IP(
  Control(),
  State()
  )
end

mutable struct EP
 control::Control
 state::State
end

function EP()
 EP(
  Control(),
  State()
 )
end

mutable struct MPCvariables
 # variables
 t::Float64           # current simulation time (s)
 tp::Any              # prediction time (if finalTimeDV == true -> this is not known before optimization)
 tex::Float64         # execution horizon time
 t0Actual                    # actual initial time TODO ?
 t0::Float64                  # mpc initial time TODO ?
 tf::Float64                  # mpc final time TODO ?
 t0Param::Any        # parameter for mpc t0  TODO ?
 evalNum::Int64       # parameter for keeping track of number of MPC evaluations
 goal                 # goal location w.r.t OCP
 goalTol             # tolerance on goal location
 initOptNum::Int64  # number of initial optimization
 previousSolutionNum::Int64  # number of times the previous solution should be used
end

function MPCvariables()
 MPCvariables(
              0.0,    # t
              Any,    # tp (might be a variable)
              0.5,    # tex
              0.0,
              0.0,
              0.0,
              Any,
              1,
              [],
              [],
              3,
              3
 )
end

mutable struct MPC
 v::MPCvariables
 ip::IP
 ep::EP
end

function MPC()
 MPC(
     MPCvariables(),
     IP(),
     EP()
     )
end

########################################################################################
# MPC functions
########################################################################################
"""
defineMPC!(n)
--------------------------------------------------------------------------------------\n
Author: Huckleberry Febbo, Graduate Student, University of Michigan
Date Create: 4/7/2017, Last Modified: 4/8/2018 \n
--------------------------------------------------------------------------------------\n
"""
function defineMPC!(n;
                   mode::Symbol=:OCP,
                   predictX0::Bool=true,
                   fixedTp::Bool=true,
                   tp::Any=Any,
                   tex::Float64=0.5,
                   IPKnown::Bool=true,
                   saveMode::Symbol=:all,
                   maxSim::Int64=100,
                   goal=n.ocp.XF,
                   goalTol=0.1*abs.(n.ocp.X0 - n.ocp.XF),
                   lastOptimal::Bool=true,
                   printLevel::Int64=2,
                   onlyOptimal::Bool=false)
 n.s.mpc.on = true
 n.mpc::MPC = MPC()
 n.s.mpc.mode = mode
 n.s.mpc.predictX0 = predictX0
 n.s.mpc.fixedTp = fixedTp
 n.mpc.v.tp = tp
 n.mpc.v.tex = tex
 n.s.mpc.IPKnown = IPKnown
 n.s.mpc.saveMode = saveMode
 n.s.mpc.maxSim = maxSim
 n.mpc.v.goal = goal
 n.mpc.v.goalTol = goalTol
 n.s.mpc.lastOptimal = lastOptimal
 n.s.mpc.printLevel = printLevel
 n.s.mpc.onlyOptimal = onlyOptimal

 n.f.mpc.simFailed[1] = false # for some reason it is getting defined as 0.0, not false during initialization
 n.f.mpc.defined = true
 return nothing
end

"""
# TODO consider letting user pass options
--------------------------------------------------------------------------------------\n
Author: Huckleberry Febbo, Graduate Student, University of Michigan
Date Create: 4/08/2018, Last Modified: 12/06/2019 \n
--------------------------------------------------------------------------------------\n
"""
function initOpt!(n;save::Bool=true, evalConstraints::Bool=false)
  if n.s.mpc.on
   error("call initOpt!() before defineMPC!(). initOpt!() will destroy n")
  end
  n.s.ocp.save = false
  n.s.mpc.on = false
  n.s.ocp.evalConstraints = false
  n.s.ocp.cacheOnly = true
  if n.s.ocp.save
   @warn "saving initial optimization results where functions where cached!"
  end
  for k in 1:n.mpc.v.initOptNum # initial optimization (s)
   status = optimize!(n)
   if status==:Optimal; break; end
  end
  # defineSolver!(n,solverConfig(c)) # modifying solver settings NOTE currently not in use
  n.s.ocp.save = save  # set to false if running in parallel to save time
  n.s.ocp.cacheOnly = false
  n.s.ocp.evalConstraints = evalConstraints # set to true to investigate infeasibilities
  return nothing
end

"""
# add a mode that solves as quickly as possible
# consider using the IP always.
defineModel!(n)
--------------------------------------------------------------------------------------\n
Author: Huckleberry Febbo, Graduate Student, University of Michigan
Date Create: 4/12/2018, Last Modified: 4/12/2018 \n
--------------------------------------------------------------------------------------\n
"""
function defineIP!(n,model;stateNames=[],controlNames=[],X0a=[])

   if isequal(n.s.mpc.mode,:OCP) # this function is called automatically for this mode
    if !isempty(stateNames)
     error("stateNames are set automatically for :mode == :OCP and cannot be provided.")
    end
    if !isempty(controlNames)
     error("controlNames are set automatically for :mode == :OCP and cannot be provided.")
    end
    if !isempty(X0a)
     error("X0a is set automatically for :mode == :OCP and cannot be provided.")
    end
    n.r.ip.X0a = copy(n.ocp.X0)  # NEED to append time
    n.mpc.ip.state.model = model
    n.mpc.ip.state.name = n.ocp.state.name
    n.mpc.ip.state.description = n.ocp.state.description
    n.mpc.ip.state.num = n.ocp.state.num
    n.mpc.ip.state.pts = n.ocp.state.pts

    n.mpc.ip.control.name = n.ocp.control.name
    n.mpc.ip.control.description = n.ocp.control.description
    n.mpc.ip.control.num = n.ocp.control.num
    n.mpc.ip.control.pts = n.ocp.control.pts

    # add X0 t0 plant dfs
    n.r.ip.plant[:t] = n.mpc.v.t0
    for st in 1:n.mpc.ip.state.num
      n.r.ip.plant[n.mpc.ip.state.name[st]] = copy(n.ocp.X0)[st]
    end
    for ctr in 1:n.mpc.ip.control.num
      n.r.ip.plant[n.mpc.ip.control.name[ctr]] = 0
    end

   elseif isequal(n.s.mpc.mode,:IP)
    if isempty(stateNames)
     error("unless :mode == :OCP the stateNames must be provided.")
    end
    if isempty(controlNames)
     error("unless :mode == :OCP the controlNames must be provided.")
    end
    if isempty(X0a)
     error("unless :mode == :OCP X0a must be provided.")
    end
    if isempty(model)
     error("A model needs to be passed for the IP mode.")
    else
    if isequal(length(X0a),length(stateNames))
      error(string("\n Length of X0a must match length(stateNames) \n"))
    end

     n.mpc.ip.state::State = State() # reset
     n.mpc.ip.state.num = length(stateNames)
     for i in 1:n.mpc.ip.state.num
       if stateNames[i]==:xxx
         error("xxx is OFF limits for a state name; please choose something else. \n")
       end
       push!(n.mpc.ip.state.name,stateNames[i])
     end

     n.mpc.ip.control::Control = Control() # reset
     n.mpc.ip.control.num = length(controlNames)
     for i in 1:n.mpc.ip.control.num
       if controlNames[i]==:xxx
         error("xxx is OFF limits for a control name; please choose something else. \n")
       end
       push!(n.mpc.ip.control.name,controlNames[i])
     end
     n.mpc.r.ip.X0a = X0a
     n.mpc.ip.state.model = model # TODO validate typeof model
    end
   elseif isequal(n.s.mpc.mode,:EP)
    error("not setup for :EP")
   else
    error("n.mpc.s.mode = ",n.s.mpc.mode," not defined." )
   end

   # consider calling mapNames
   return nothing
end

"""
mapNames!(n)
--------------------------------------------------------------------------------------\n
Author: Huckleberry Febbo, Graduate Student, University of Michigan
Date Create: 4/9/2018, Last Modified: 4/12/2018 \n
--------------------------------------------------------------------------------------\n
"""
function mapNames!(n)
  if isequal(n.s.mpc.mode,:IP)
    s1 = n.ocp.state.name
    c1 = n.ocp.control.name
    s2 = n.mpc.ip.state.name
    c2 = n.mpc.ip.control.name
  elseif isequal(n.s.mpc.mode,:EP)
    error(":EP function not ready")
  else
    error("mode must be either :IP or :EP")
  end

  m = []
  # go through all states in OCP
  idxOCP = 1
  for var in s1
    # go through all states in IP
    idxIP = findall(var.==s2)
    if !isempty(idxIP)
      push!(m, [var; :stOCP; idxOCP; :stIP; idxIP[1]])
    end

    # go through all controls in IP
    idxIP = findall(var.==c2)
    if !isempty(idxIP)
      push!(m, [var; :stOCP; idxOCP; :ctrIP; idxIP[1]])
    end
    idxOCP = idxOCP + 1
  end

  # go through all controls in OCP
  idxOCP = 1
  for var in c1
    # go through all states in IP
    idxIP = findall(var.==s2)
    if !isempty(idxIP)
      push!(m, [var; :ctrOCP; idxOCP; :stIP; idxIP[1]])
    end

    # go through all controls in IP
    idxIP = findall(var.==c2)
    if !isempty(idxIP)
      push!(m, [var; :ctrOCP; idxOCP; :ctrIP; idxIP[1]])
    end
    idxOCP = idxOCP + 1
  end

  if isequal(n.s.mpc.mode,:IP)
    n.mpc.mIP = m
  elseif isequal(n.s.mpc.mode,:EP)
    error(":EP function not ready")
  else
    error("mode must be either :IP or :EP")
  end

  return nothing
end

"""
# TODO fix this so that prediction simulation time is ahead. May not effect results.
# NOTE this may be ok... we are getting X0p for initialization of the OCP
# as long as the OCP pushes the time ahead. which it does then everything is fine!!
# consider making user pass X0, t0, tf
--------------------------------------------------------------------------------------\n
Author: Huckleberry Febbo, Graduate Student, University of Michigan
Date Create: 2/14/2017, Last Modified: 12/06/2019 \n
--------------------------------------------------------------------------------------\n
"""
function simIPlant!(n)
  if isequal(n.mpc.ip.state.pts,0)
   error("isqual(n.mpc.ip.state.pts,0), cannot simulate with zero points.")
  end
  X0 = currentIPState(n)[1]
  t0 = round(n.mpc.v.t, digits = 3) # if rounding is too rough, then the tex will be essentially 0!
  tf = round(n.mpc.v.t + n.mpc.v.tex,digits = 3)

  if isequal(n.s.mpc.mode,:OCP)
   if isequal(n.mpc.v.evalNum,1)
    U = 0*Matrix{Float64}(undef, n.ocp.control.pts,n.ocp.control.num)
    t = Vector(range(t0,tf,length=n.ocp.control.pts))
   elseif n.s.ocp.interpolationOn
    U = n.r.ocp.Upts
    t = n.r.ocp.tpts
   else
    U = n.r.ocp.U
    t = n.r.ocp.tctr
   end
  else
   error("TODO")
  end
  # chop of first control point for bkwEuler as it is typically 0
  if isequal(n.s.ocp.integrationScheme,:bkwEuler)
   U = U[2:end,:]
   t = t[2:end]
  end

  sol, U = n.mpc.ip.state.model(n,X0,t,U,t0,tf)
  return sol, U
end

"""
--------------------------------------------------------------------------------------\n
Author: Huckleberry Febbo, Graduate Student, University of Michigan
Date Create: 4/08/2018, Last Modified: 4/08/2018 \n
--------------------------------------------------------------------------------------\n
"""
function currentIPState(n)
  if isempty(n.r.ip.plant)
    error("there is no data in n.r.ip.plant")
  end

  # even though may have solution for plant ahead of time
  # can only get the state up to n.mpc.v.t
  idx = findall((n.mpc.v.t .- n.r.ip.plant[:t]) .>= 0)
  if isempty(idx)
    error("(n.mpc.v.t - n.r.ip.plant[:t]) .>= 0) is empty.")
  else
    X0 = [zeros(n.mpc.ip.state.num),n.mpc.v.t]
    for st in 1:n.mpc.ip.state.num
      X0[1][st] = n.r.ip.plant[n.mpc.ip.state.name[st]][idx[end]]
    end
  end
  return X0
end

"""
# TODO eventually the "plant" will be different from the "model"
predictX0!(n)
--------------------------------------------------------------------------------------\n
Author: Huckleberry Febbo, Graduate Student, University of Michigan
Date Create: 4/7/2017, Last Modified: 4/25/2018 \n
--------------------------------------------------------------------------------------\n
"""
function predictX0!(n)

  if n.s.mpc.fixedTp
   # NOTE consider passing back (n.mpc.v.t + n.mpc.v.tex) from simIPlant!()
   tp = round(n.mpc.v.t + n.mpc.v.tex, digits = 1)  # TODO add 1 as an MPCparamss
  else
   error("TODO")
  end

  if isequal(n.s.mpc.mode,:OCP)
   sol, U = simIPlant!(n)
   X0p = [sol(sol.t[end])[:],tp]
   push!(n.r.ip.X0p,X0p)
  else
    error("TODO")
  end
 # else
   # with no control signals to follow, X0p is simply the current known location of the plant
 #  X0 = currentIPState(n)
 #  X0p = [X0[1], tp]  # modify X0 to predict the time
  # push!(n.r.ip.X0p,X0p)
 # end
  return nothing
end

"""
--------------------------------------------------------------------------------------\n
Author: Huckleberry Febbo, Graduate Student, University of Michigan
Date Create: 3/06/2018, Last Modified: 4/08/2018 \n
--------------------------------------------------------------------------------------\n
"""
function updateX0!(n,args...)
 # need to map n.r.ip.X0p to n.X0 (states may be different)
 # NOTE for the :OCP mode this is OK

 if !n.s.mpc.predictX0 #  use the current known plant state to update OCP
   push!(n.r.ip.X0p,currentIPState(n))
 else
   predictX0!(n)
 end

 if isequal(n.s.mpc.mode,:OCP)
   if !isequal(length(args),0)
    X0 = args[1]
    if length(X0)!=n.ocp.state.num
      error(string("\n Length of X0 must match number of states \n"));
    end
    n.ocp.X0 = X0
   else
    n.ocp.X0 = n.r.ip.X0p[end][1] # the n.ocp. structure is for running things
   end
   push!(n.r.ocp.X0,n.ocp.X0)    # NOTE this may be for saving data
   setvalue(n.ocp.t0,copy(n.r.ip.X0p[end][2]))
 else
  error("not set up for this mode")
 end

  if n.s.mpc.shiftX0 # TODO consider saving linear shifting occurances
    for st in 1:n.ocp.state.num
      if n.ocp.X0[st] < n.ocp.XL[st]
        n.ocp.X0[st] = n.ocp.XL[st]
      end
      if n.ocp.X0[st] > n.ocp.XU[st]
        n.ocp.X0[st] = n.ocp.XU[st]
      end
    end
  end
  # update states with n.ocp.X0
  for st in 1:n.ocp.state.num
    if n.s.ocp.x0slackVariables
     JuMP.setRHS(n.r.ocp.x0Con[st,1], n.ocp.X0[st])
     JuMP.setRHS(n.r.ocp.x0Con[st,2],-n.ocp.X0[st])
    else
      JuMP.setRHS(n.r.ocp.x0Con[st],n.ocp.X0[st])
    end
  end
  return nothing
end

"""
--------------------------------------------------------------------------------------\n
Author: Huckleberry Febbo, Graduate Student, University of Michigan
Date Create: 4/08/2018, Last Modified: 4/08/2018 \n
--------------------------------------------------------------------------------------\n
"""
function goalReached!(n,args...)
  if isequal(n.s.mpc.mode,:OCP)
    X = currentIPState(n)[1]
  else
    X = args[1]
    error("TODO")
  end
  A = (abs.(X - n.mpc.v.goal) .<= n.mpc.v.goalTol)
  B = isnan.(n.mpc.v.goal)
  C = [A[i]||B[i] for i in 1:length(A)]

  if all(C)
   if isequal(n.s.mpc.printLevel,2)
    println("Goal Attained! \n")
   end
    n.f.mpc.goalReached = true
  elseif n.s.mpc.expandGoal && (getvalue(n.ocp.tf) < n.mpc.v.tex)
    A =( abs.(X - n.mpc.v.goal) .<= n.s.mpc.enlargeGoalTolFactor*n.mpc.v.goalTol)
    C = [A[i]||B[i] for i in 1:length(A)]
    if all(C)
     if isequal(n.s.mpc.printLevel,2)
      println("Expanded Goal Attained! \n")
     end
     n.f.mpc.goalReached = true
    else
     println("Expanded Goal Not Attained! \n
              Stopping Simulation")
     n.f.mpc.simFailed = [true, :expandedGoal]
    end
  end

 return n.f.mpc.goalReached
end
# if the vehicle is very close to the goal sometimes the optimization returns with a small final time
# and it can even be negative (due to tolerances in NLP solver). If this is the case, the goal is slightly
# expanded from the previous check and one final check is performed otherwise the run is failed
#if getvalue(n.ocp.tf) < 0.01
#  if ((n.r.ip.dfplant[end][:x][end]-c["goal"]["x"])^2 + (n.r..ip.dfplant[end][:y][end]-c["goal"]["yVal"])^2)^0.5 < 2*c["goal"]["tol"]
#  println("Expanded Goal Attained! \n"); n.f.mpc.goal_reached=true;
#  break;/
#  else
#  warn("Expanded Goal Not Attained! -> stopping simulation! \n"); break;
#  end
#elseif getvalue(n.ocp.tf) < 0.5 # if the vehicle is near the goal => tf may be less then 0.5 s
#  tf = (n.r.evalNum-1)*n.mpc.v.tex + getvalue(n.ocp.tf)
#else
#  tf = (n.r.evalNum)*n.mpc.v.tex
#end

"""
--------------------------------------------------------------------------------------\n
Author: Huckleberry Febbo, Graduate Student, University of Michigan
Date Create: 2/06/2018, Last Modified: 4/08/2018 \n
--------------------------------------------------------------------------------------\n
"""
function simMPC!(n;updateFunction::Any=[],checkFunction::Any=[])
  for ii = 1:n.s.mpc.maxSim
    if isequal(n.s.mpc.printLevel,2)
     println("Running model for the: ",n.mpc.v.evalNum," time")
    end
    #############################
    # (A) and (B) in "parallel"
    #############################

    # (B) simulate plant
    sol, U = simIPlant!(n) # the plant simulation time will lead the actual time
    plant2dfs!(n,sol,U)

    # check to see if the simulation failed (i.e. plant crashed)
    if !isequal(typeof(checkFunction),Array{Any,1})
     val, sym = checkFunction(n)
     if val
       n.f.mpc.simFailed = [val, sym]
      break
     end
    end

    # check to see if the goal has been reached
    if goalReached!(n); break; end
    if n.f.mpc.simFailed[1]; break; end

    # (A) solve OCP  TODO the time should be ahead here as it runs
    updateX0!(n)  # before updateFunction()
    if !isequal(typeof(updateFunction),Array{Any,1})
      updateFunction(n)
    end

    optimize!(n)
    if n.f.mpc.simFailed[1]; break; end

    # advance time
    n.mpc.v.t = n.mpc.v.t + n.mpc.v.tex
    n.mpc.v.evalNum = n.mpc.v.evalNum + 1
  end
end
# practical concerns/questions
#################################
# 1) predict X0
# 2) shift X0 for NLP feasibility
# 3) ensuring that U passed to the plant is feasible
     # effected by interpolation, demonstrate by varying numPts
     # seems to be a major problem with LGR nodes, possibly due to Runge effect
# 4) fixedTp or variableTp
# 5) usePrevious optimal.
     # at some point will be unable to do this
# 6) infeasibilities, soft constraint on inital conditions

# TODO
# 1) plot the goal, the tolerances on X0p
# 2) calculate the error and plot
end # module


using MPIFiles
using Test

fnMeasBruker = "./data/measurement"
tfpath = "./data/transferFunction/example.s1p"
tfh5path = "./data/transferFunction/example.h5"

@testset "Testing TransferFunction submodule" begin
  a = TransferFunction(tfpath, frequencyWeighting=true)
  if isfile(tfh5path)
    rm(tfh5path)
  end
  MPIFiles.save(tfh5path, a)
  b = TransferFunction(tfh5path)

  @test a.freq == b.freq
  @test a.data == b.data
  @test a[[1],1] == a[[0.0],1]

  c = MPIFiles.combine(MPIFiles.combine(a,a),a)
  rm(tfh5path)
  MPIFiles.save(tfh5path, c)
  @test a[[1],1] == c[[1],2]
  @test a[[1],1] == c[[1],3]

  measBruker = MPIFile(fnMeasBruker)
  tf = sampleTF(c, measBruker)
  @test size(tf) == (rxNumFrequencies(measBruker), rxNumChannels(measBruker))
end


#==#
#Ratios.BisectSort()
@propagate_inbounds function div(n=10,m=6)   #inline's maximum is slower its counterparty by factor of 2.6928
   k=m
    @inbounds for i ∈ 1:k
    n /= m
    end
end
#=
maybe the point of inline is not the speed at all, but
only the stability of your code? #research
=#
#inline is not the best  for speed
  @inline function div2(n=10,m=6)    # the faster # depreciate
   k=m
    @inbounds for i ∈ 1:k
    n /= m
    end
end

@propagate_inbounds function mul(n=10,m=6)
    k = m
   @inbounds for i ∈ 1:k
        n *= m
    end
end
#-----------------------
t1 = @benchmark div()
t2 = @benchmark div2()

maxt1 = maximum(t1)
maxt2   = maximum(t2)

maxt1.time/mint1.time #
mint1.time/maxt1.time

maxratio = maxt1.time / maxt2.time #max time of div1  is larger  than max time of div2 by factor of 2.6928
minratio = mint1.time/mint2.time  # minimum is equal for div1 & div2
#Idea: min-max matrix
#vcat([maxt1/maxt2,maxt1/mint2])

#meanDiv2ns = median(t2)
#modeDiv2ns = mode(t2)
#stdDiv2ns = std(t2)

mint1.time = minimum(t1)

#--------------

avg = 0
tmul = @benchmark mul()#10^6

maxi = 100
count=0
tmul = @benchmark mul()#

#---------------------------------------------------
@inbounds for i ∈ 1:maxi #is 10_000
    tmul = @benchmark mul()#
    tmul
    maxm = maximum(tmul.times)
    count += maxm
end
count #count = 55975
avg = count/maxi #population MostLikelihoodEstimae =  E(x)=avg =55.975
sampleMax=maximum(tmul)
#tmul.median
sampletime = sampleMax.time #143.3 # is it higher or lower of the 'Normal'?
sampletime/avg #2.56 Infer: sample is higher by factor of 2.56 # standard deviant got to be high as well
dispersion = sampletime- avg# 87.32499
dispersion/sampletime/avg #idea 0.01 (pvalue significant)

mint2 = minimum(t2)

mint1.time/mint2.time
mint1.time ≈ mint2.time
mint1.time == mint2.time

#=
BenchmarkTools.Trial: 10000 samples with 1000 evaluations.
 Range (min … max):  1.400 ns … 143.300 ns  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     1.500 ns               ┊ GC (median):    0.00%
 Time  (mean ± σ):   1.637 ns ±   2.125 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

  ██ ▃  ▂ ▁                                                   ▂
  ██▁█▁██▁█▁▇▁▇▄▁▅▁▄▁▄▃▁▁▁▁▁▃▁▁▁▁▄▄▁▁▁▃▁▅▃▁▄▁▁▁▃▃▁▁▁▃▁▄▃▁▄▁▁▃ █
  1.4 ns       Histogram: log(frequency) by time       4.8 ns <

julia> dump(tmul)

  params: BenchmarkTools.Parameters
.....
     seconds: Float64 5.0
    time_tolerance: Float64 0.05
    memory_tolerance: Float64 0.01
....
julia>     gcsample: Bool falseBenchmarkTools.Trial: 10000 samples with 1000 evaluations.
 Range (min … max):  1.400 ns … 143.300 ns  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     1.500 ns               ┊ GC (median):    0.00%
 Time  (mean ± σ):   1.637 ns ±   2.125 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

  ██ ▃  ▂ ▁                                                   ▂
  ██▁█▁██▁█▁▇▁▇▄▁▅▁▄▁▄▃▁▁▁▁▁▃▁▁▁▁▄▄▁▁▁▃▁▅▃▁▄▁▁▁▃▃▁▁▁▃▁▄▃▁▄▁▁▃ █
  1.4 ns       Histogram: log(frequency) by time       4.8 ns <
vial info:
    seconds: Float64 5.0

    time_tolerance: Float64 0.05
    memory_tolerance: Float64 0.01

    -----------
Lesson learned:population (of the poulation) : returned time could be dubious, depending on the distribution of variance (which is nasty indee d)
#=stil, giving emphasis on just the mean, could mean turning a blind eye on possible meaningful anomalies
most data is ranged differently according to its interval (interval analysis ?)
    a fast fourier  would enhance analysis but the best approach would be a "weighted kernel approach"
that too may not possible even explain the existence of such anomalies, in general-  not only in this one
mint2 = minimum(t2)

mint1.time/mint2.time
mint1.time ≈ mint2.time
mint1.time == mint2.time

------------------