module xmlToJulia

    using EzXML
    using Base64
    using Inflate
    using HTTP
    export toJulia

    Parent = Dict()
    BlockLabel = Dict()
    Type = Dict()
    Label = Dict()
    Style = Dict()
    Vertex = Dict()
    Edge = Dict()
    Target = Dict()
    Source = Dict()
    AddChild = Dict()
    SubChild = Dict()
    ProductChild = Dict()
    DivisionChild = Dict()
    parameter = Dict()
    blk = Dict()
    Inblk = Dict()
    Outblk = Dict()
    connect = Dict()
    UpperLimit = Dict()
    LowerLimit = Dict()
    QuantizationInterval = Dict()
    ModChild = Dict()
    SystemChildIn = Dict()
    SystemChildOut = Dict()
    modelName = Dict()
    InitialCondition = Dict()
    Child = Dict()
    Slope = Dict()
    InitialOutput = Dict()
    StartTime = Dict()
    StepTime = Dict()
    InitialValue = Dict()
    FinalValue = Dict()
    Amplitude = Dict()
    Period = Dict()
    PulseWidth = Dict()
    PhaseDelay = Dict()

    #Option = Dict()

    ModelName = []
    Parameter = []
    Blk = []
    InBlock = []
    OutBlock = []
    Connect = []

    function GetId(data)
        data_Id = data["id"]
        return data_Id
    end

    function GetStyle(data)
        styles = Dict()
        if haskey(data, "style")
            data_Style = data["style"]
            #println("- ", mxCell_Style)
            for x in split(data_Style, ";")
                #println(x)
                e = split(x, "=")
                if length(e) == 1
                    #println("")
                    styles[e[1]] = ""
                elseif length(e) == 2
                    #println(e[2])
                    styles[e[1]] = e[2]
                else
                    println("Error: Style in Cell")
                end
            end
        end
        #println(styles)
        return styles
    end

    function GetData(data)
        id = data["id"]
        if haskey(data, "label")
            Label[id] = data["label"]
        end
        if haskey(data, "style")
            Style[id] = GetStyle(data)
        end
        if haskey(data, "blockLabel")
            BlockLabel[id] = data["blockLabel"]
        end
        if haskey(data, "type")
            Type[id] = data["type"]
            if Type[id] == "model"
                parameter[id] = data["parameter"]
            end
            if Type[id] == "input"
                BlockLabel[id] = "In" * BlockLabel[id]
                Inblk[string(data["blockLabel"])] = BlockLabel[id]
            end
            if Type[id] == "output"
                BlockLabel[id] = "Out" * BlockLabel[id]
                Outblk[string(data["blockLabel"])] = BlockLabel[id]
            end
            if Type[id] == "integrator"
                InitialCondition[id] = []
            end
            if Type[id] == "add"
                AddChild[id] = []
                Child[id] = []
            end
            if Type[id] == "sub"
                SubChild[id] = []
                Child[id] = []
            end
            if Type[id] == "product"
                ProductChild[id] = []
                Child[id] = []
            end
            if Type[id] == "division"
                DivisionChild[id] = []
                Child[id] = []
            end
            if Type[id] == "mod"
                ModChild[id] = []
                Child[id] = []
            end
            if Type[id] == "system"
                parameter[id] = []
                SystemChildIn[id] = []
                SystemChildOut[id] = []
                modelName[id] = data["modelName"]
                Child[id] = []
            end
        end
        if haskey(data, "parent")
            Parent[id] = data["parent"]
            if haskey(Type, Parent[id])
                if Type[Parent[id]] == "add"
                    if string(data["value"][1]) == "i"
                        newid = replace.(id, "-"=>"")
                        newid = "a" * newid
                        if string(data["value"][2]) == "1"
                            pushfirst!(AddChild[Parent[id]], newid)
                        else
                            push!(AddChild[Parent[id]], newid)
                        end
                    end
                end
                if Type[Parent[id]] == "sub"
                    if string(data["value"][1]) == "i"
                        newid = replace.(id, "-"=>"")
                        newid = "a" * newid
                        if string(data["value"][2]) == "1"
                            pushfirst!(SubChild[Parent[id]], newid)
                        else
                            push!(SubChild[Parent[id]], newid)
                        end
                    end
                end
                if Type[Parent[id]] == "product"
                    if string(data["value"][1]) == "i"
                        newid = replace.(id, "-"=>"")
                        newid = "a" * newid
                        if string(data["value"][2]) == "1"
                            pushfirst!(ProductChild[Parent[id]], newid)
                        else
                            push!(ProductChild[Parent[id]], newid)
                        end
                    end
                end
                if Type[Parent[id]] == "division"
                    if string(data["value"][1]) == "i"
                        newid = replace.(id, "-"=>"")
                        newid = "a" * newid
                        if string(data["value"][2]) == "1"
                            pushfirst!(DivisionChild[Parent[id]], newid)
                        else
                            push!(DivisionChild[Parent[id]], newid)
                        end
                    end
                end
                if Type[Parent[id]] == "mod"
                    if string(data["value"][1]) == "i"
                        newid = replace.(id, "-"=>"")
                        newid = "a" * newid
                        if string(data["value"][2]) == "1"
                            pushfirst!(ModChild[Parent[id]], newid)
                        else
                            push!(ModChild[Parent[id]], newid)
                        end
                    end
                end
                if Type[Parent[id]] == "system"
                    if string(data["value"][1]) == "i"
                        newid = replace.(id, "-"=>"")
                        newid = string(data["value"][2]) * "a" * newid
                        push!(SystemChildIn[Parent[id]], newid)
                    end
                    if string(data["value"][1]) == "o"
                        newid = replace.(id, "-"=>"")
                        newid = string(data["value"][2]) * "a" * newid
                        push!(SystemChildOut[Parent[id]], newid)
                    end
                end
            end
        end
        if haskey(data, "vertex")
            Vertex[id] = data["vertex"]
        end
        if haskey(data, "parameter")
            if Type[id] == "system"
                parameter[id] = split(data["parameter"])
            else
                parameter[id] = data["parameter"]
            end
        end
        if haskey(data, "edge")
            Edge[id] = data["edge"]
            if haskey(data, "target")
                Target[id] = data["target"]
            end
            if haskey(data, "source")
                Source[id] = data["source"]
            end
        end
        if haskey(data, "initialcondition")
                InitialCondition[id] = data["initialcondition"]
        end
        if haskey(data, "upperlimit")
                UpperLimit[id] = data["upperlimit"]
        end
        if haskey(data, "lowerlimit")
                LowerLimit[id] = data["lowerlimit"]
        end
        if haskey(data, "quantizationinterval")
                QuantizationInterval[id] = data["quantizationinterval"]
        end
        if haskey(data, "slope")
            Slope[id] = data["slope"]
        end
        if haskey(data, "initialoutput")
                InitialOutput[id] = data["initialoutput"]
        end
        if haskey(data, "starttime")
                StartTime[id] = data["starttime"]
        end
        if haskey(data, "steptime")
            StepTime[id] = data["steptime"]
        end
        if haskey(data, "initialvalue")
            InitialValue[id] = data["initialvalue"]
        end
        if haskey(data, "finalvalue")
            FinalValue[id] = data["finalvalue"]
        end
        if haskey(data, "amplitude")
            Amplitude[id] = data["amplitude"]
        end
        if haskey(data, "period")
            Period[id] = data["period"]
        end
        if haskey(data, "pulsewidth")
            PulseWidth[id] = data["pulsewidth"]
        end
        if haskey(data, "phasedelay")
            PhaseDelay[id] = data["phasedelay"]
        end
    end

    function GetDataChild(data, id)
        if haskey(data, "parent")
            Parent[id] = data["parent"]
        end
            if haskey(data, "style")
            Style[id] = GetStyle(data)
        end
        if haskey(data, "vertex")
            Vertex[id] = data["vertex"]
        end
    end

    function Parse(data, Id)
        if haskey(data, "edge")
            ParseEdge(data, Id)
        end
        if haskey(data, "type")
            if Type[Id] == "model"
                push!(ModelName, BlockLabel[Id])
            end
        end
    end

    function ParseChild(data, Id)
        if haskey(data, "vertex")
            ParseVertex(data, Id)
        end
    end

    function ParseEdge(data, Id)
        if haskey(Child, Parent[Target[Id]])
            newtarget = replace.(Target[Id], "-"=>"")
            if Type[Parent[Source[Id]]] == "system"
                newsource = replace.(Source[Id], "-"=>"")
                push!(Connect, "a" * newsource * " => " * "a" * newtarget)
            else
                push!(Connect, BlockLabel[Parent[Source[Id]]] * " => " * "a" * newtarget)
            end
        else
            if Type[Parent[Source[Id]]] == "system"
                newsource = replace.(Source[Id], "-"=>"")
                push!(Connect, "a" * newsource * " => " * BlockLabel[Parent[Target[Id]]])
            else
                push!(Connect, BlockLabel[Parent[Source[Id]]] * " => " * BlockLabel[Parent[Target[Id]]])
            end
        end
    end

    function ParseVertex(data, Id)
        if haskey(Style[Id], "text")
        elseif Type[Id] == "model"
            push!(Parameter, parameter[Id])
        else
            if Type[Id] == "constant"
                push!(Blk, BlockLabel[Id] * " = " * "ConstantBlock(" * parameter[Id] * ")")
            end
            if Type[Id] == "ramp"
                ramptext = BlockLabel[Id] * " = " * "RampBlock("
                if Slope[Id] != ""
                    ramptext = ramptext * "slope=" * Slope[Id]
                    if StartTime[Id] != "" || InitialOutput[Id] != ""
                        ramptext = ramptext * ", "
                    end
                end
                if StartTime[Id] != ""
                    ramptext = ramptext * "starttime=" * StartTime[Id]
                    if InitialOutput[Id] != ""
                        ramptext = ramptext * ", "
                    end
                end
                if InitialOutput[Id] != ""
                    ramptext = ramptext * "initialoutput=" * InitialOutput[Id]
                end
                ramptext = ramptext * ")"
                push!(Blk, ramptext)
            end
            if Type[Id] == "step"
                steptext = BlockLabel[Id] * " = " * "StepBlock("
                if StepTime[Id] != ""
                    steptext = steptext * "steptime=" * StepTime[Id]
                    if InitialValue[Id] != "" || FinalValue[Id] != ""
                        steptext = steptext * ", "
                    end
                end
                if InitialValue[Id] != ""
                    steptext = steptext * "initialvalue=" * InitialValue[Id]
                    if FinalValue[Id] != ""
                        steptext = steptext * ", "
                    end
                end
                if FinalValue[Id] != ""
                    steptext = steptext * "finalvalue=" * FinalValue[Id]
                end
                steptext = steptext * ")"
                push!(Blk, steptext)
            end
            if Type[Id] == "pulse"
                pulsetext = BlockLabel[Id] * " = " * "PulseGeneratorBlock("
                if Amplitude[Id] != ""
                    pulsetext = pulsetext * "amplitude=" * Amplitude[Id]
                    if Period[Id] != "" || PulseWidth[Id] != "" || PhaseDelay[Id] != ""
                        pulsetext = pulsetext * ", "
                    end
                end
                if Period[Id] != ""
                    pulsetext = pulsetext * "period=" * Period[Id]
                    if PulseWidth[Id] != "" || PhaseDelay[Id] != ""
                        pulsetext = pulsetext * ", "
                    end
                end
                if PulseWidth[Id] != ""
                    pulsetext = pulsetext * "pulsewidth=" * PulseWidth[Id]
                    if PhaseDelay[Id] != ""
                        pulsetext = pulsetext * ", "
                    end
                end
                if PhaseDelay[Id] != ""
                    pulsetext = pulsetext * "phasedelay=" * PhaseDelay[Id]
                end
                pulsetext = pulsetext * ")"
                push!(Blk, pulsetext)
            end
            if Type[Id] == "gain"
                push!(Blk, BlockLabel[Id] * " = " * "GainBlock(" * parameter[Id] * ")")
            end
            if Type[Id] == "integrator"
                integratortext = BlockLabel[Id] * " = " * "IntegratorBlock("
                if InitialCondition[Id] != ""
                    integratortext = integratortext * "initialcondition=" * InitialCondition[Id]
                end
                integratortext = integratortext * ")"
                push!(Blk, integratortext)
            end
            if Type[Id] == "add"
                addtext = BlockLabel[Id] * " = " * "AddBlock() "
                for i in 1:2
                    addtext = addtext * "inport[" * string(i) * "]:"
                    addtext = addtext * AddChild[Id][i] * " "
                end
                push!(Blk, addtext)
            end
            if Type[Id] == "sub"
                subtext = BlockLabel[Id] * " = " * "SubBlock() "
                for i in 1:2
                    subtext = subtext * "inport[" * string(i) * "]:"
                    subtext = subtext * SubChild[Id][i] * " "
                end
                push!(Blk, subtext)
            end
            if Type[Id] == "product"
                producttext = BlockLabel[Id] * " = " * "ProductBlock() "
                for i in 1:2
                    producttext = producttext * "inport[" * string(i) * "]:"
                    producttext = producttext * ProductChild[Id][i] * " "
                end
                push!(Blk, producttext)
            end
            if Type[Id] == "division"
                divisiontext = BlockLabel[Id] * " = " * "DivisionBlock() "
                for i in 1:2
                    divisiontext = divisiontext * "inport[" * string(i) * "]:"
                    divisiontext = divisiontext * DivisionChild[Id][i] * " "
                end
                push!(Blk, divisiontext)
            end
            if Type[Id] == "saturation"
                saturationtext = BlockLabel[Id] * " = " * "SaturationBlock("
                if UpperLimit[Id] != ""
                    saturationtext = saturationtext * "upperlimit=" * UpperLimit[Id]
                    if LowerLimit[Id] != ""
                        saturationtext = saturationtext * ", "
                    end
                end
                if LowerLimit[Id] != ""
                    saturationtext = saturationtext * "lowerlimit=" * LowerLimit[Id]
                end
                saturationtext = saturationtext * ")"
                push!(Blk, saturationtext)
            end
            if Type[Id] == "quantizer"
                quantizertext = BlockLabel[Id] * " = " * "QuantizerBlock("
                if QuantizationInterval[Id] != ""
                    quantizertext = quantizertext * "quantizationinterval=" * QuantizationInterval[Id]
                end
                quantizertext = quantizertext * ")"
                push!(Blk, quantizertext)
            end
            if Type[Id] == "mod"
                modtext = BlockLabel[Id] * " = " * "ModBlock() "
                for i in 1:2
                    modtext = modtext * "inport[" * string(i) * "]:"
                    modtext = modtext * ModChild[Id][i] * " "
                end
                push!(Blk, modtext)
            end
            if Type[Id] == "system"
                systemtext = BlockLabel[Id] * " = " * modelName[Id] * "("
                for i in 1:length(parameter[Id])
                    systemtext = systemtext * parameter[Id][i] * "=" * parameter[Id][i]
                    if i == length(parameter[Id])
                    else
                        systemtext = systemtext * ", "
                    end
                end
                systemtext = systemtext * ")"
                for i in 1:length(SystemChildIn[Id])
                    systemtext = systemtext * " inport[" * string(SystemChildIn[Id][i][1]) * "]:" * SystemChildIn[Id][i][2:end]
                end
                for i in 1:length(SystemChildOut[Id])
                    systemtext = systemtext * " outport[" * string(SystemChildOut[Id][i][1]) * "]:" * SystemChildOut[Id][i][2:end]
                end
                push!(Blk, systemtext)
            end
        end
    end

    function ParseVertexIn()
        for i in 1:length(Inblk)
            if haskey(Inblk, string(i))
                intext = Inblk[string(i)] * " = " * "InBlock()"
                push!(InBlock, intext)
            end
        end
    end

    function ParseVertexOut()
        for i in 1:length(Outblk)
            if haskey(Outblk, string(i))
                outtext = Outblk[string(i)] * " = " * "OutBlock()"
                push!(OutBlock, outtext)
            end
        end
    end

    function init()
        global Parent = Dict()
        global BlockLabel = Dict()
        global Type = Dict()
        global Label = Dict()
        global Style = Dict()
        global Vertex = Dict()
        global Edge = Dict()
        global Target = Dict()
        global Source = Dict()
        global AddChild = Dict()
        global ProductChild = Dict()
        global DivisionChild = Dict()
        global parameter = Dict()
        global blk = Dict()
        global Inblk = Dict()
        global Outblk = Dict()
        global connect = Dict()
        global UpperLimit = Dict()
        global LowerLimit = Dict()
        global QuantizationInterval = Dict()
        global ModChild = Dict()
        global SystemChildIn = Dict()
        global SystemChildOut = Dict()
        global modelName = Dict()
        global InitialCondition = Dict()
        global Child = Dict()
        global Slope = Dict()
        global InitialOutput = Dict()
        global StartTime = Dict()
        #Option = Dict()

        global ModelName = []
        global Parameter = []
        global Blk = []
        global InBlock = []
        global OutBlock = []
        global Connect = []
    end

    function toJulia(s)
        init()
        xml = parsexml(s)
        mx = xml.root
        if haskey(mx, "dx")
            ro = elements(mx)[1]
        else
            for com in eachelement(mx)
                global mxgraph = nodecontent(com)
            end
            dec = base64decode(mxgraph)
            if dec == UInt8[]
                dia = elements(mx)[1]
                mxg = elements(dia)[1]
                ro = elements(mxg)[1]
            else
                dec_inflate = inflate(dec)
                dec_string = String(dec_inflate)
                dec_uris = HTTP.URIs.unescapeuri(dec_string)
                xml_dec = parsexml(dec_uris)
                mx_dec = xml_dec.root
                ro = elements(mx_dec)[1]
            end
        end

        for cell in eachelement(ro)
            GetData(cell)
            Id = GetId(cell)
            for child in eachelement(cell)
                #print("-")
                GetDataChild(child, Id)
            end
            #println("-----")
        end

        for cell in eachelement(ro)
            Id = GetId(cell)
            Parse(cell, Id)
            for child in eachelement(cell)
                ParseChild(child, Id)
            end
        end
        ParseVertexIn()
        ParseVertexOut()

        io = IOBuffer()
        for j in 1:length(ModelName)
            write(io, "@model $(ModelName[j]) begin\n")
            for i in 1:length(Parameter)
                write(io, "@parameter $(Parameter[i])\n")
            end
            write(io, "\n")
            for i in 1:length(InBlock)
                write(io, "@blk $(InBlock[i])\n")
            end
            for i in 1:length(Blk)
                write(io, "@blk $(Blk[i])\n")
            end
            for i in 1:length(OutBlock)
                write(io, "@blk $(OutBlock[i])\n")
            end
            write(io, "\n")
            for i in 1:length(Connect)
                write(io, "@connect $(Connect[i])\n")
            end
            write(io, "end\n")
        end
        str = String(take!(io))
        close(io)
        str
    end

end


# using Grammatical Evolution to solve the Ant problem
using GrammaticalEvolution
import GrammaticalEvolution.evaluate!
import GrammaticalEvolution.isless
import Base.copy

const food = convert(Int64, '1')
const gap = convert(Int64, '0')
const empty = 46 #convert(Int64, '.')
const startpos = 88 #convert(Int64, 'X')

world1 =
"""
..#..
..#..
.##..
.#...
.X...
"""

function read_map(s)
#     data = [strip(line) for line in split(s)]
    data = [strip(line) for line in s]
    height = length(data)
    width = length(data[1])
    start = ones(Int64, 2)

    map = zeros(Int64, width, height)
    for i=1:height
      for j=1:width
        if data[i][j] != startpos
          map[i, j] = convert(Int64, data[i][j])
        else
          start = Any[i, j]
        end
      end
    end

    return (map, start)
end

# println(read_map(world1))

type World
  width::Int64
  height::Int64
  value::Array{Int64, 2}
  start::Array{Int64, 1}

  function World(width::Int64, height::Int64)
    value = zeros(width, height)
    return new(width, height, value)
  end

  function World(map::Array{Int64, 2}, start::Array)
    return new(size(map, 1), size(map, 2), map, start)
  end
end

copy(world::World) = World(copy(world.value), world.start)

immutable Direction
  value::Int64
end

import Base: +, -, isless
+{T <: Number}(d::Direction, v::T) = Direction(d.value + v)
-{T <: Number}(d::Direction, v::T) = Direction(d.value - v)
isless(d1::Direction, d2::Direction) = isless(d1.value, d2.value)
isless(d1::Direction, v::Int64) = isless(d1.value, value)

const north = Direction(1)
const east = Direction(2)
const south = Direction(3)
const west = Direction(4)

type AntIndividual <: Individual
  genome::Array{Int64, 1}
  fitness::Union{Float64,Void}
  code

  function AntIndividual(size::Int64, max_value::Int64)
    genome = rand(1:max_value, size)
    return new(genome, nothing, nothing)
  end

  AntIndividual(genome::Array{Int64, 1}) = new(genome, nothing, nothing)
end

type AntPopulation <: Population
  individuals::Array{AntIndividual, 1}

  function AntPopulation(population_size::Int64, genome_size::Int64)
    individuals = Array(AntIndividual, 0)
    for i=1:population_size
      push!(individuals, AntIndividual(genome_size, 1000))
    end

    return new(individuals)
  end
end

type Ant
  x::Int64
  y::Int64
  direction::Direction
  eaten::Int64

  function Ant(x::Int64, y::Int64)
    return new(x, y, north, 0)
  end
end

function calc_forward(world::World, ant::Ant)
  nx = ant.x
  ny = ant.y

  if ant.direction === north
    ny -= 1
    if ny < 1 ny = world.height end
  elseif ant.direction === east
    nx += 1
    if nx > world.width nx = 1 end
  elseif ant.direction === south
    ny += 1
    if ny > world.height ny = 1 end
  else
    nx -= 1
    if nx < 1 nx = world.width end
  end

  return (nx, ny)
end

function food_ahead(world::World, ant::Ant)
  (x, y) = calc_forward(world, ant)
  return world.value[x, y] == food
end

function forward(world::World, ant::Ant)
  (ant.x, ant.y) = calc_forward(world, ant)
  if world.value[ant.x, ant.y] == food
    world.value[ant.x, ant.y] = empty
    ant.eaten += 1
  elseif world.value[ant.x, ant.y] == empty
    ant.eaten -= 1
  end
end

function turn_right(ant::Ant)
  #ant.direction = ant.direction + 1 > south ? north : ant.direction+1
  ndir = ant.direction + 1
  ant.direction = ndir.value > south.value ? north : ndir;
end

function turn_left(ant::Ant)
  #ant.direction = ant.direction - 1 < 1 ? south : ant.direction-1
  ndir = ant.direction - 1
  ant.direction = ndir.value < 1 ? south : ndir
end

make_block(lst::Array) = Expr(:block, lst...)
make_loop(values::Array) = Expr(:for, :(=), :i, Expr(:(:), 1, values[1]), values[2])
make_if(values::Array) = Expr(:if, values[1], values[2], values[3])

macro make_if(condition, true_block, false_block)
  Expr(:if, condition, true_block, false_block)
end

macro make_call(fn, args...)
  Expr(:call, fn, args...)
end

macro make_for(start, stop, block)
  Expr(:for, Expr(:(=), :i, Expr(:(:), start, stop)), block)
end

@grammar ant_grammar begin
  start = block
  command = turnleft | turnright | forward | if_statement | loop
  turnleft = Expr(:call, :turn_left, :ant)
  turnright = Expr(:call, :turn_right, :ant)
  forward = Expr(:call, :forward, :world, :ant)
  if_statement = Expr(:if, condition, block, block)
  condition = food_ahead
  food_ahead = Expr(:call, :food_ahead, :world, :ant)
  loop = Expr(:for, Expr(:(=), :i, Expr(:(:), 1, digit)), block)
  block[make_block] = (command)^(1:5)
  digit = 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
end

function evaluate!(grammar::Grammar, ind::ExampleIndividual, pop::ExamplePopulation, world::World)
  # copy world
  world = copy(world)

  # create ant
  ant = Ant(world.start[1], world.start[2])

  # generate code
  try
    ind.code = transform(grammar, ind)
    @eval fn(world::World, ant::Ant) = $(ind.code)
    pop.totalevals += 1
    if ind.fitness < pop.best_fitness
      pop.best_fitness = ind.fitness
      pop.best_ind = ind
      pop.best_at_eval = pop.totalevals
    end
  catch e
    if typeof(e) !== MaxWrapException
      Base.error_show(STDERR, e, catch_backtrace())
    end
    ind.fitness = realmax(Float64)
    return
  end

  # run code
  fn(world, ant)

  # fitness is the amount of food eaten
  ind.fitness = -convert(Float64, ant.eaten)
end

# need to redefine 'isless' (eating more is better)
#Base.isless(ind1::ExampleIndividual, ind2::ExampleIndividual) = ind1.fitness > ind2.fitness

function main()
  data = open("../examples/santefe.trail")
  (map, start) = read_map(readlines(data))
  world = World(map, start)

  # create population
  pop = ExamplePopulation(500, 500)

  fitness = 0
  generation = 1

  evaluate!(ant_grammar, pop, world)
  while generation < 1000
    tic()
    # generate a new population (based off of fitness)
    pop = generate(ant_grammar, pop, 0.1, 0.2, 0.2, world)

    # population is sorted, so first entry it the best
    fitness = pop[1].fitness
    println("generation: $generation, $(length(pop)), max fitness=$fitness, time=$(signif(toq(),5))\n$(pop[1].code)")
    generation += 1
  end
end

main()


# boundingbox.jl
# contain all functions pertaining to the bounding box

@doc """
###PumiBoundingBox

Class that contains information necessary to create a bonding box of control
points. Use the inner constructor to create an object of this type. The
arguments to the constructor in sequence are as follows

* Number of dimensions
* Highest and lowest values of x,y,z coordinates along every dimension.
  dim1 = highest and lowest values, dim2 = x,y or z direction
* Offset from the geometry/mesh boundary for determining the size of the
  bounding box

The Definition of the function is subject to change in the future. Presently,
the unit vectors that define the parametric coordinates of the box are assumed
to be aligned with the physical coordinate system. They can be changed
separately if needed. There is also an implicit assumption that the box is an
othogonal parallelopiped, but, this assumption is not enforce anywhere. The
final shape of the BoundingBox object will depend on the geometric location of
the control points in the physical space.

**Members**

*  `ndim` : 2D or 3D
*  `origin` : intended origin of the (s,t,u) parametric coordinate system in the
              (x,y,z) space
*  `unitVector`: Unit vectors that define the (s,t,u) coordinate axes expressed
                 in (x,y,z) coordinate system. dim1 = x,y,z values, dim2 = s,t
                 or u vector
*  `geom_bound` : Highest and lowest values of x,y,z coordinates along every
                  dimension. dim1 = highest and lowest values, dim2 = x,y or z
                  direction
*  `offset` : Offset from the geometry/mesh boundary for determining the size of
              the bounding box
*  `box_bound` : Ordinates of the extremas of the bounding box. dim1 = upper
                 and lower bounds, dim2 = x,y or z direction.
*  `lengths` : lengths of the edges of the parallelopied being generated

The current assumption behind this type is that the parametric axes has the same
orientation as the physical coordinate system.

"""->

mutable struct PumiBoundingBox{Tffd} <: AbstractBoundingBox{Tffd}

  # Physical space
  ndim::Integer # 2D or 3D
  origin::Array{Tffd, 1}
  unitVector::Array{Tffd, 2}
  geom_bounds::Array{Tffd, 2}  # highest and lowst values of x, y, z
                                       # coordinates along every dimension
                                       # in the physical space
  offset::Array{Float64, 1} # Offset from the mesh for the bounding box dimensions
  box_bounds::Array{Tffd, 2}
  lengths::Array{Tffd, 1} # dimensions of the bounding box

  # Parametric space
  function PumiBoundingBox{Tffd}(map::PumiMapping, mesh::AbstractMesh,
                           sbp::AbstractSBP, offset) where {Tffd} # 3D

    # Check Inputs
    for i = 1:3
      @assert offset[i] >= 0 "The bounding box offsets need to be >= 0 in every direction"
    end
    if mesh.dim == 2
      @assert offset[3] > 0 "2D meshes requires offset along 3rd dimension > 0"
    end

    ndim = 3 # The Bounding box is always 3D

    # Allocate members
    origin = zeros(Tffd, ndim)
    unitVector = eye(Tffd, ndim) # Create default a unit vector aligned with the physical space
    geom_bounds = zeros(Tffd, 2, ndim) # each row = x_min, x_max,  each column = ndim
    box_bounds = zeros(Tffd, 2, ndim)  # same as above
    lengths = zeros(Tffd, ndim) # same as above

    calcSurfaceGeomBounds(mesh, sbp, geom_bounds,map.bc_nums)

    # Get the boumding box coordinates and dimensions
    for i = 1:size(geom_bounds,2)  # TODO: Come up with a better definition of box_bound and lengths
      box_bounds[1,i] = geom_bounds[1,i] - offset[i]
      box_bounds[2,i] = geom_bounds[2,i] + offset[i]
      lengths[i] = box_bounds[2,i] - box_bounds[1,i]
    end

    origin[:] = box_bounds[1,:] # Get the lower x,y,z ordinates to be defined as the
                               # origin

    return new(ndim, origin, unitVector, geom_bounds, offset, box_bounds, lengths)
  end
end

@doc """
### FreeFormDeformation.calcGeomBounds

Gets the geometric bounds of a pumi mesh object from its mesh coordinates. This
function works for both 2D and 3D pumi mesh objects

**Arguments**

* `coords` : Pumi Mesh coordinates
* `geom_bounds` : Highest and lowest values of x,y,z coordinates along every
                  dimension. dim1 = highest and lowest values, dim2 = x,y or z
                  direction i.e.
```
[xmin ymin zmin
 xmax ymax zmax]
```

"""->

function calcEntireGeometryBounds(coords::AbstractArray{Tffd,3},
                                  geom_bounds::AbstractArray{Tffd,2}) where Tffd

  fill!(geom_bounds, 0.0)

  xmin = zero(Tffd)
  xmax = zero(Tffd)
  ymin = zero(Tffd)
  ymax = zero(Tffd)
  zmin = zero(Tffd)
  zmax = zero(Tffd)

  if size(coords,1) == 2
    for i = 1:size(coords,3)
      for j = 1:size(coords,2)
        if xmin > coords[1,j,i]
          xmin = coords[1,j,i]
        elseif xmax < coords[1,j,i]
          xmax = coords[1,j,i]
        end # End if

        if ymin > coords[2,j,i]
          ymin = coords[2,j,i]
        elseif ymax < coords[2,j,i]
          ymax = coords[2,j,i]
        end # End if
      end   # End for j = 1:size(coords,2)
    end     # End for i = 1:size(coords,3)
  elseif size(coords,1) == 3
    for i = 1:size(coords,3)
      for j = 1:size(coords,2)
        if xmin > coords[1,j,i]
          xmin = coords[1,j,i]
        elseif xmax < coords[1,j,i]
          xmax = coords[1,j,i]
        end

        if ymin > coords[2,j,i]
          ymin = coords[2,j,i]
        elseif ymax < coords[2,j,i]
          ymax = coords[2,j,i]
        end

        if zmin > coords[3,j,i]
          zmin = coords[3,j,i]
        elseif zmax < coords[3,j,i]
          zmax = coords[3,j,i]
        end # End if
      end   # End for j = 1:size(coords,2)
    end     # End for i = 1:size(coords,3)
  end

  geom_bounds[1,1] = xmin
  geom_bounds[2,1] = xmax
  geom_bounds[1,2] = ymin
  geom_bounds[2,2] = ymax
  geom_bounds[1,3] = zmin
  geom_bounds[2,3] = zmax

  recv_buffer = MPI.Allgather(geom_bounds, MPI.COMM_WORLD)
  for i = 1:6:length(recv_buffer)
    if recv_buffer[i] < geom_bounds[1,1]
      geom_bounds[1,1] = recv_buffer[i]
    end
    if recv_buffer[i+1] > geom_bounds[2,1]
      geom_bounds[2,1] = recv_buffer[i+1]
    end
    if recv_buffer[i+2] < geom_bounds[1,2]
      geom_bounds[1,2] = recv_buffer[i+2]
    end
    if recv_buffer[i+3] > geom_bounds[2,2]
      geom_bounds[2,2] = recv_buffer[i+3]
    end
    if recv_buffer[i+4] < geom_bounds[1,3]
      geom_bounds[1,3] = recv_buffer[i+4]
    end
    if recv_buffer[i+5] > geom_bounds[2,3]
      geom_bounds[2,3] = recv_buffer[i+5]
    end
  end

  return nothing
end

function calcSurfaceGeomBounds(mesh::AbstractCGMesh, sbp::AbstractSBP,
                               geom_bounds::AbstractArray{Tffd,2},
                               bc_nums::AbstractArray{Int,1}) where Tffd

  fill!(geom_bounds, 0.0)

  xmin = zero(Tffd)
  xmax = zero(Tffd)
  ymin = zero(Tffd)
  ymax = zero(Tffd)
  zmin = zero(Tffd)
  zmax = zero(Tffd)

  if mesh.dim == 2
    for (idx, itr) in enumerate(bc_nums)
      start_index = mesh.bndry_offsets[itr]
      end_index = mesh.bndry_offsets[itr+1]
      idx_range = start_index:(end_index-1)
      bndry_facenums = sview(mesh.bndryfaces, idx_range)
      nfaces = length(bndry_facenums)
      for i = 1:nfaces
        bndry_i = bndry_facenums[i]
        for j = 1:sbp.numfacenodes
          k = sbp.facenodes[j, bndry_i.face]
          coords =sview(mesh.coords, :, k, bndry_i.element)
          if xmin > coords[1]
            xmin = coords[1]
          elseif xmax < coords[1]
            xmax = coords[1]
          end # End if

          if ymin > coords[2]
            ymin = coords[2]
          elseif ymax < coords[2]
            ymax = coords[2]
          end # End if
        end   # End for j = 1:sbp.facenode
      end     # End for i = 1:nfaces
    end       # End for itr = 1:length(bc_nums)
  else
    for (idx, itr) in enumerate(bc_nums)
      start_index = mesh.bndry_offsets[itr]
      end_index = mesh.bndry_offsets[itr+1]
      idx_range = start_index:end_index
      bndry_facenums = sview(mesh.bndryfaces, start_index:(end_index - 1))
      for i = 1:nfaces
        bndry_i = bndry_facenums[i]
        for j = 1:sbp.numfacenodes
          k = sbp.facenodes[j, bndry_i.face]
          coords =sview(mesh.coords, :, k, bndry_i.element)
          if xmin > coords[1]
            xmin = coords[1]
          elseif xmax < coords[1]
            xmax = coords[1]
          end # End if

          if ymin > coords[2]
            ymin = coords[2]
          elseif ymax < coords[2]
            ymax = coords[2]
          end # End if

          if zmin > coords[3]
            zmin = coords[3]
          elseif zmax < coords[3]
            zmax = coords[3]
          end # End if
        end   # End for j = 1:sbp.facenode
      end     # End for i = 1:nfaces
    end       # End for itr = 1:length(bc_nums)
  end

  geom_bounds[1,1] = xmin
  geom_bounds[2,1] = xmax
  geom_bounds[1,2] = ymin
  geom_bounds[2,2] = ymax
  geom_bounds[1,3] = zmin
  geom_bounds[2,3] = zmax

  recv_buffer = MPI.Allgather(geom_bounds, MPI.COMM_WORLD)
  for i = 1:6:length(recv_buffer)
    if recv_buffer[i] < geom_bounds[1,1]
      geom_bounds[1,1] = recv_buffer[i]
    end
    if recv_buffer[i+1] > geom_bounds[2,1]
      geom_bounds[2,1] = recv_buffer[i+1]
    end
    if recv_buffer[i+2] < geom_bounds[1,2]
      geom_bounds[1,2] = recv_buffer[i+2]
    end
    if recv_buffer[i+3] > geom_bounds[2,2]
      geom_bounds[2,2] = recv_buffer[i+3]
    end
    if recv_buffer[i+4] < geom_bounds[1,3]
      geom_bounds[1,3] = recv_buffer[i+4]
    end
    if recv_buffer[i+5] > geom_bounds[2,3]
      geom_bounds[2,3] = recv_buffer[i+5]
    end
  end

  return nothing
end

function calcSurfaceGeomBounds(mesh::AbstractDGMesh, sbp::AbstractSBP,
                               geom_bounds::AbstractArray{Tffd,2},
                               bc_nums::AbstractArray{Int,1}) where Tffd

  fill!(geom_bounds, 0.0)

  xmin = zero(Tffd)
  xmax = zero(Tffd)
  ymin = zero(Tffd)
  ymax = zero(Tffd)
  zmin = zero(Tffd)
  zmax = zero(Tffd)

  if mesh.dim == 2
    for (idx, itr) in enumerate(bc_nums)
      start_index = mesh.bndry_offsets[itr]
      end_index = mesh.bndry_offsets[itr+1]
      idx_range = start_index:(end_index-1)
      bndry_facenums = sview(mesh.bndryfaces, idx_range) # faces on geometric edge i
      nfaces = length(bndry_facenums)
      for i = 1:nfaces
        bndry_i = bndry_facenums[i]
        # get the local index of the vertices on the boundary face (local face number)
        vtx_arr = mesh.topo.face_verts[:,bndry_i.face]
        for j = 1:length(vtx_arr)
          coords =sview(mesh.vert_coords, :, vtx_arr[j], bndry_i.element)
          if xmin > coords[1]
            xmin = coords[1]
          elseif xmax < coords[1]
            xmax = coords[1]
          end # End if

          if ymin > coords[2]
            ymin = coords[2]
          elseif ymax < coords[2]
            ymax = coords[2]
          end # End if
        end   # End for j = 1:length(vtx_arr)
      end     # End for i = 1:nfaces
    end       # End for itr = 1:length(bc_nums)
  else
    for (idx, itr) in enumerate(bc_nums)
      start_index = mesh.bndry_offsets[itr]
      end_index = mesh.bndry_offsets[itr+1]
      idx_range = start_index:(end_index-1)
      bndry_facenums = sview(mesh.bndryfaces, idx_range) # faces on geometric edge i
      nfaces = length(bndry_facenums)
      for i = 1:nfaces
        bndry_i = bndry_facenums[i]
        # get the local index of the vertices on the boundary face (local face number)
        vtx_arr = mesh.topo.face_verts[:,bndry_i.face]
        for j = 1:length(vtx_arr)
          coords =sview(mesh.vert_coords, :, vtx_arr[j], bndry_i.element)
          if xmin > coords[1]
            xmin = coords[1]
          elseif xmax < coords[1]
            xmax = coords[1]
          end # End if

          if ymin > coords[2]
            ymin = coords[2]
          elseif ymax < coords[2]
            ymax = coords[2]
          end # End if

          if zmin > coords[3]
            zmin = coords[3]
          elseif zmax < coords[3]
            zmax = coords[3]
          end # End if
        end   # End for j = 1:length(vtx_arr)
      end     # End for i = 1:nfaces
    end       # End for itr = 1:length(bc_nums)
  end

  geom_bounds[1,1] = xmin
  geom_bounds[2,1] = xmax
  geom_bounds[1,2] = ymin
  geom_bounds[2,2] = ymax
  geom_bounds[1,3] = zmin
  geom_bounds[2,3] = zmax

  recv_buffer = MPI.Allgather(geom_bounds, MPI.COMM_WORLD)
  for i = 1:6:length(recv_buffer)
    if recv_buffer[i] < geom_bounds[1,1]
      geom_bounds[1,1] = recv_buffer[i]
    end
    if recv_buffer[i+1] > geom_bounds[2,1]
      geom_bounds[2,1] = recv_buffer[i+1]
    end
    if recv_buffer[i+2] < geom_bounds[1,2]
      geom_bounds[1,2] = recv_buffer[i+2]
    end
    if recv_buffer[i+3] > geom_bounds[2,2]
      geom_bounds[2,2] = recv_buffer[i+3]
    end
    if recv_buffer[i+4] < geom_bounds[1,3]
      geom_bounds[1,3] = recv_buffer[i+4]
    end
    if recv_buffer[i+5] > geom_bounds[2,3]
      geom_bounds[2,3] = recv_buffer[i+5]
    end
  end

  return nothing
end


# ******************************************************************************************
# Notices:
#
# Copyright © 2022 United States Government as represented by the Administrator of the
# National Aeronautics and Space Administration.  All Rights Reserved.
#
# Disclaimers
#
# No Warranty: THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY KIND,
# EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
# THAT THE SUBJECT SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY
# WARRANTY THAT THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT DOCUMENTATION,
# IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE. THIS AGREEMENT DOES NOT, IN ANY MANNER,
# CONSTITUTE AN ENDORSEMENT BY GOVERNMENT AGENCY OR ANY PRIOR RECIPIENT OF ANY RESULTS,
# RESULTING DESIGNS, HARDWARE, SOFTWARE PRODUCTS OR ANY OTHER APPLICATIONS RESULTING FROM
# USE OF THE SUBJECT SOFTWARE.  FURTHER, GOVERNMENT AGENCY DISCLAIMS ALL WARRANTIES AND
# LIABILITIES REGARDING THIRD-PARTY SOFTWARE, IF PRESENT IN THE ORIGINAL SOFTWARE, AND
# DISTRIBUTES IT "AS IS."
#
# Waiver and Indemnity:  RECIPIENT AGREES TO WAIVE ANY AND ALL CLAIMS AGAINST THE UNITED
# STATES GOVERNMENT, ITS CONTRACTORS AND SUBCONTRACTORS, AS WELL AS ANY PRIOR RECIPIENT.
# IF RECIPIENT'S USE OF THE SUBJECT SOFTWARE RESULTS IN ANY LIABILITIES, DEMANDS, DAMAGES,
# EXPENSES OR LOSSES ARISING FROM SUCH USE, INCLUDING ANY DAMAGES FROM PRODUCTS BASED ON,
# OR RESULTING FROM, RECIPIENT'S USE OF THE SUBJECT SOFTWARE, RECIPIENT SHALL INDEMNIFY AND
# HOLD HARMLESS THE UNITED STATES GOVERNMENT, ITS CONTRACTORS AND SUBCONTRACTORS, AS WELL
# AS ANY PRIOR RECIPIENT, TO THE EXTENT PERMITTED BY LAW.  RECIPIENT'S SOLE REMEDY FOR ANY
# SUCH MATTER SHALL BE THE IMMEDIATE, UNILATERAL TERMINATION OF THIS AGREEMENT.
# ******************************************************************************************

const LOG_STD_MAX = 2
const LOG_STD_MIN = -20

"""
Create multilayer perceptron with given parameters.
"""
function mlp(sizes::Vector{Int}, activation::Function, output_activation::Function=identity)
    layers = []
    for j = 1:length(sizes) - 1
        act = j < length(sizes) - 1 ? activation : output_activation
        push!(layers, Dense(sizes[j], sizes[j + 1], act))
    end
    return Chain(layers...) |> dev
end

"""
Gaussian MLP policy.
"""
mutable struct SquashedGaussianMLPActor
    net::Chain                              # primary network
    mu_layer::Dense                         # mean layer
    log_std_layer::Dense                    # log standard deviation layer
    act_mins::AbstractVector{Float32}       # minimum values of actions
    act_maxs::AbstractVector{Float32}       # maximum values of actions
    rng::AbstractRNG                        # internal RNG
    rng_gpu::Union{AbstractRNG, Nothing}    # internal RNG for GPU calculations
    linearized::Bool                        # linearized squashing (allows post-analysis)
end

function SquashedGaussianMLPActor(
	obs_dim::Int,
	act_dim::Int,
	hidden_sizes::Vector{Int},
	activation::Function,
	act_mins::Vector{Float64},
	act_maxs::Vector{Float64},
	rng::AbstractRNG,
    linearized::Bool
)
    net = mlp(vcat(obs_dim, hidden_sizes), activation, activation) |> dev
    mu_layer = Dense(hidden_sizes[end], act_dim) |> dev
    log_std_layer = Dense(hidden_sizes[end], act_dim) |> dev
    act_mins, act_maxs = dev(Float32.(act_mins)), dev(Float32.(act_maxs))
    if WITH_GPU[]
        rng_gpu = CURAND.RNG()
        seed = rand(rng, UInt32)
        Random.seed!(rng_gpu, seed)
    else
        rng_gpu = nothing
    end
    return SquashedGaussianMLPActor(net, mu_layer, log_std_layer, act_mins, act_maxs, rng, rng_gpu, linearized)
end

"""
Calculate log pdf of normal distribution.
"""
function normal_logpdf(μ, σ, x)
    lz = sum(-0.5f0 .* ((x .- μ) ./ σ).^2; dims=1)
    lden = (size(μ, 1) * Float32(log(2π)) / 2) .+ sum(log.(σ); dims=1)
    lpdf = lz .- lden
    lpdf = dropdims(lpdf; dims=1)
    return lpdf
end

"""
Calculate random normal deviates on GPU (in-place).
"""
function normal_deviates(rng::AbstractRNG, sz::NTuple{N, Int64}) where N
    z = CuArray{Float32}(undef, sz)
    randn!(rng, z)
    return z
end
Zygote.@nograd normal_deviates

"""
Retrieve action (and optional log probability) from policy.
"""
function (pi::SquashedGaussianMLPActor)(obs, deterministic::Bool=false, with_logprob::Bool=true)
    net_out = pi.net(obs)
    mu = pi.mu_layer(net_out)
    log_std = pi.log_std_layer(net_out)
    log_std = clamp.(log_std, LOG_STD_MIN, LOG_STD_MAX)
    std = exp.(log_std)

    # Pre-squash distribution and sample
    if deterministic
        pi_action = mu
    else
        if obs isa CuArray
            z = normal_deviates(pi.rng_gpu, size(mu))
        else
            z = randn(pi.rng, Float32, size(mu))
        end
        pi_action = mu .+ std .* Zygote.dropgrad(z)
    end

    if with_logprob
        logp_pi = normal_logpdf(mu, std, pi_action)
        logp_pi = logp_pi .- dropdims(sum((2.0f0 .* (log(2.0f0) .- pi_action .- softplus.(-2.0f0 .* pi_action))); dims=1); dims=1)
    else
        logp_pi = NaN
    end

    if pi.linearized # probabilities still correspond to non-linear squashing
        pi_action = clamp.(pi_action, pi.act_mins, pi.act_maxs)
    else
        pi_action = tanh.(pi_action)
        pi_action = @. pi.act_mins + (pi.act_maxs - pi.act_mins) * (pi_action / 2 + 0.5f0)
    end

    return pi_action, logp_pi
end

"""
Q-value function.
"""
mutable struct MLPQFunction
    q::Chain
end

function MLPQFunction(obs_dim::Int, act_dim::Int, hidden_sizes::Vector{Int}, activation::Function)
    q = mlp(vcat(obs_dim + act_dim, hidden_sizes, 1), activation)
    return MLPQFunction(q)
end

"""
Determine Q-value of observation and action.
"""
function (qf::MLPQFunction)(obs, act)
    q = qf.q(cat(obs, act; dims=1))
    q = dropdims(q; dims=1)
    return q
end

"""
Actor-critic agent.
"""
mutable struct MLPActorCritic <: GlobalResult
    pi::SquashedGaussianMLPActor
    qs::Vector{MLPQFunction}
end

function MLPActorCritic(
	obs_dim::Int,
	act_dim::Int,
	act_mins::Vector{Float64},
	act_maxs::Vector{Float64},
	hidden_sizes::Vector{Int}=[100,100,100],
	num_q::Int=2,
	activation::Function=SoftActorCritic.relu,
	rng::AbstractRNG=Random.GLOBAL_RNG,
    linearized::Bool=false
)
    pi = SquashedGaussianMLPActor(obs_dim, act_dim, hidden_sizes, activation, act_mins, act_maxs, rng, linearized)
    qs = [MLPQFunction(obs_dim, act_dim, hidden_sizes, activation) for _ in 1:num_q]
    return MLPActorCritic(pi, qs)
end

"""
Retrieve action from policy.
"""
function (ac::MLPActorCritic)(obs, deterministic::Bool=true)
    a, _ = ac.pi(obs, deterministic, false)
    return a
end

"""
Define native softplus function to avoid CUDA bugs. #TODO: removable?
"""
softplus(x) = x > 0 ? x + log(oftype(x, 1) + exp(-x)) : log(oftype(x, 1) + exp(x))

"""
Define native relu function to avoid Flux bugs.
"""
relu(x) = max(x, oftype(x, 0))


"""
    LiftingLineSectionControl <: AbstractModel

Lifting line section control input model with state variable ``\\delta``, zero
inputs, and zero parameters.  Two-dimensional control surface models may be
extended to three dimensional models by coupling with this model.  Note that this
model has no rate equations of its own since its state variables are defined as
functions of the 3D system's control inputs.
"""
struct LiftingLineSectionControl <: AbstractModel end

"""
    LiftingLineSectionControl()

Initialize an object of type [`LiftingLineSectionControl`](@ref)
"""

# --- Traits --- #

number_of_states(::Type{<:LiftingLineSectionControl}) = 1
number_of_inputs(::Type{<:LiftingLineSectionControl}) = 0
number_of_parameters(::Type{<:LiftingLineSectionControl}) = 0

inplaceness(::Type{<:LiftingLineSectionControl}) = OutOfPlace()

# --- Internal Methods for Couplings --- #


#################### MCMC Simulation Engine ####################

function mcmc(mc::ModelChains, iters::Integer; verbose::Bool=true)
  thin = step(mc)
  last(mc) == div(mc.model.iter, thin) * thin ||
    throw(ArgumentError("chain is missing its last iteration"))

  mm = deepcopy(mc.model)
  mc2 = mcmc_master!(mm, mm.iter .+ (1:iters), last(mc), thin, mc.chains,
                     verbose)
  if mc2.names != mc.names
    mc2 = mc2[:, mc.names, :]
  end

  ModelChains(vcat(mc, mc2), mc2.model)
end

function mcmc(m::Model, inputs::Dict{Symbol},
              inits::Vector{V} where V<:Dict{Symbol},
              iters::Integer; burnin::Integer=0, thin::Integer=1,
              chains::Integer=1, verbose::Bool=true)
  iters > burnin ||
    throw(ArgumentError("burnin is greater than or equal to iters"))
  length(inits) >= chains ||
    throw(ArgumentError("fewer initial values than chains"))

  mm = deepcopy(m)
  setinputs!(mm, inputs)
  setinits!(mm, inits[1:chains])
  mm.burnin = burnin

  mcmc_master!(mm, 1:iters, burnin, thin, 1:chains, verbose)
end

function mcmc_master!(m::Model, window::UnitRange{Int}, burnin::Integer,
                      thin::Integer, chains::AbstractArray{Int}, verbose::Bool)
  states = m.states
  m.states = ModelState[]

  N = length(window)
  K = length(chains)

  frame = ChainProgressFrame(
    "MCMC Simulation of $N Iterations x $K Chain" * "s"^(K > 1), verbose
  )

  lsts = [
    Any[m, states[k], window, burnin, thin, ChainProgress(frame, k, N)]
    for k in chains
  ]
  results = pmap2(mcmc_worker!, lsts)

  sims  = Chains[results[k][1] for k in 1:K]
  model = results[1][2]
  model.states = ModelState[results[k][3] for k in sortperm(chains)]

  ModelChains(cat(sims..., dims=3), model)
end

function mcmc_worker!(args::Vector)
  m, state, window, burnin, thin, meter = args

  m.iter = first(window) - 1
  relist!(m, state.value)
  settune!(m, state.tune)

  pnames = names(m, true)
  sim = Chains(last(window), length(pnames), start=burnin + thin, thin=thin,
               names=pnames)

  reset!(meter)
  for i in window
    sample!(m)
    if i > burnin && (i - burnin) % thin == 0
      sim[i, :, 1] = unlist(m, true)
    end
    next!(meter)
  end

  (sim, m, ModelState(unlist(m), gettune(m)))
end


include(joinpath("src", "ED-model", "glu_ed_model.jl"))
import .Glu_ED_Model

res_ed = []
for var in [0.0001:0.0005:0.005;]
    push!(res_ed, Glu_ED_Model.glu_ed_model(var))
end


#!/usr/bin/env julia

# For the files given in ARGS, create all the needed finalization jobs
# for the third phase. That is, continue them at T₁ until tw=3e8.

const param = "-m 33 -t 1e4 -T 1e8 -n 10 -L"

function get_fields(file)
    regex = r"continue_at_T2_([0-9.]+)_tw1_([0-9]+)_for_T1_([0-9.]+)_L_([0-9]+)_id_([a-z]+-R[AB]).net"
    fields = match(regex, basename(file)).captures
    length(fields) == 0 && error("Couldn't get metadata from $file")
    T2 = parse(Float64, fields[1])
    tw1 = parse(Int64, fields[2])
    T1 = parse(Float64, fields[3])
    L = parse(Int64, fields[4])
    id = fields[5]
    return T1, T2, tw1, L, id
end

# Return the string containing the finalization jobs for .net file
function get_the_job(file)
    T1, T2, tw1, L, id = get_fields(file)
    outname = "finish_T2_$(T2)_tw1_$(tw1)_from_T1_$(T1)_L_$(L)_id_$(id).net"
    needediters = 3e8 - (tw1+1e8)
    cmd = "./exe/annealer -c $(tw1+1e8) $param -p -l $L -i $needediters $T1"
    job = "cat $file | $cmd \"$(outname)\""
    cleanup = "lzma $(outname)"
    return job * "\n" * cleanup * "\n" * "echo 'All done! ($outname)'"
end

jobs = String[]
for file in ARGS
    push!(jobs,get_the_job(file))
end
info("$(length(jobs)) jobs processed.")

n = 1
for job in jobs
    header=
    """
    #!/bin/sh
    #\$ -o \$HOME/glass_finish_$(n).out -j y
    #\$ -N glass_$(n)

    cd tfg/

    """
    open("finish_$n.sh", "w") do io
        println(io,header)
        println(io,job)
    end
    n+=1
end


module Network

import Microwave: AbstractParams, AbstractData
using ..Circuit
import ..Circuit: check_frequency_identical
import Base: +, -, *, /, ^, convert, promote_rule, show, getindex

export NetworkParams, Sparams, Yparams, Zparams, TwoPortParams, ABCDparams
export Port, NetworkData, impedances, swap_ports!, permute_ports!, check_frequency_identical
export connect_ports, innerconnect_ports, cascade
export reflection_coefficient, transmission_coefficient, impedance_step
export series_network, parallel_network, terminated_network, π_network, t_network

include("NetworkParams.jl")
include("Port.jl")
include("NetworkData.jl")
include("convert.jl")
include("operations.jl")
include("connect.jl")
include("twoport.jl")

end


# Mu' = f
type ODEProblem{uType,tType,isinplace,F,C,MM} <: AbstractODEProblem{uType,tType,isinplace}
  f::F
  u0::uType
  tspan::Tuple{tType,tType}
  callback::C
  mass_matrix::MM
end

function ODEProblem(f,u0,tspan; iip = isinplace(f,3),callback=nothing,mass_matrix=I)
  ODEProblem{typeof(u0),eltype(tspan),iip,typeof(f),typeof(callback),typeof(mass_matrix)}(f,u0,tspan,callback,mass_matrix)
end

# Mu' = f[1] + f[2] + ...
type SplitODEProblem{uType,tType,isinplace,F,C,MM} <: AbstractODEProblem{uType,tType,isinplace}
  f::F
  u0::uType
  tspan::Tuple{tType,tType}
  callback::C
  mass_matrix::MM
end

function SplitODEProblem(f,u0,tspan; iip = isinplace(f[2],3),callback=nothing,mass_matrix=I)
  @assert typeof(f) <: Tuple
  SplitODEProblem{typeof(u0),eltype(tspan),iip,typeof(f),typeof(callback),typeof(mass_matrix)}(f,u0,tspan,callback,mass_matrix)
end

# M[i]*u[i]' = f[i]
type PartitionedODEProblem{uType,tType,isinplace,F,C,MM} <: AbstractODEProblem{uType,tType,isinplace}
  f::F
  u0::uType
  tspan::Tuple{tType,tType}
  callback::C
  mass_matrix::MM
end

function PartitionedODEProblem(f,u0,tspan; iip = isinplace(f[1],3),callback=nothing,mass_matrix=nothing)
  @assert typeof(f) <: Tuple
  @assert typeof(u0) <: Tuple
  if mass_matrix == nothing
    _mm = ((I for i in 1:length(f))...)
  end
  PartitionedODEProblem{typeof(u0),eltype(tspan),iip,typeof(f),typeof(callback),typeof(_mm)}(f,u0,tspan,callback,_mm)
end

# u'' = f(t,u,du,ddu)
function SecondOrderODEProblem(f,u0,du0,tspan; iip = isinplace(f,4),callback=nothing,mass_matrix=I)
  if iip
    f1 = function (t,u,v,du)
      du .= v
    end
  else
    f1 = function (t,u,v)
      v
    end
  end
  _f = (f1,f)
  _u0 = (u0,du0)
  _mass_matrix = (mass_matrix,I)
  PartitionedODEProblem{typeof(_u0),eltype(tspan),iip,typeof(_f),typeof(callback),typeof(_mass_matrix)}(_f,_u0,tspan,callback,_mass_matrix)
end


module Constraints

export AbstractConstraint, FixedDistanceConstraint

abstract type AbstractConstraint end

struct FixedDistanceConstraint <: AbstractConstraint
    i::Int
    j::Int
    distance::Float64
end

end


module Vlasiator

using Requires
using StaticArrays
using Printf: @sprintf
using UnPack
using LinearAlgebra: ×, dot, ⋅, norm, I
using Statistics: mean
using EzXML
using FLoops
using Mmap: mmap
using WriteVTK
using LazyGrids: ndgrid
using LaTeXStrings
using Dates

include("utility/rotation.jl")
include("utility/log.jl")
include("utility/curvature.jl")
include("vlsv/vlsvreader.jl")
include("vlsv/vlsvutility.jl")
include("utility/plot.jl")

export
   # vlsvreader
   MetaVLSV, VarInfo,
   load, readvariable, readparameter, readvariablemeta, readvcells,
   hasvariable, hasparameter, hasname,
   # vlsvutility
   getcell, getslicecell, getlevel, refineslice, getcellcoordinates, getvcellcoordinates,
   getchildren, getparent, isparent, getsiblings,
   getcellinline, getnearestcellwithvdf, getcellwithvdf, write_vtk, issame,
   # plot helper
   SI, RE, Log, Linear, SymLog,
   # log
   readlog

precompile(load, (String,))
precompile(readvariable, (MetaVLSV, String, Bool))

function __init__()
   @require PyPlot="d330b81b-6aea-500a-939a-2ce795aea3ee" begin
      include("plot/pyplot.jl")
   end
   @require Plots="91a5bcdd-55d7-5caf-9e0b-520d859cae80" begin
      include("plot/plots.jl")
   end
end

end


using OffsetArrays

#
#
#=========================================================================#
function bc1(meqn::Int, mbc::Int, mx::Int, xlower::Float64, dx::Float64,
             q::OffsetArray{Float64,2}, maux::Int, aux::OffsetArray{Float64,2},
             t::Float64, dt::Float64, mthbc::Array{Int,1})
#=========================================================================#
#
#     # Standard boundary condition choices for claw2
#
#     # At each boundary  k = 1 (left),  2 (right):
#     #   mthbc(k) =  0  for user-supplied BC's (must be inserted!)
#     #            =  1  for zero-order extrapolation
#     #            =  2  for periodic boundary coniditions
#     #            =  3  for solid walls, assuming this can be implemented
#     #                  by reflecting the data about the boundary and then
#     #                  negating the 2'nd component of q.
#     ------------------------------------------------
#
#     # Extend the data from the computational region
#     #      i = 1, 2, ..., mx2
#     # to the virtual cells outside the region, with
#     #      i = 1-ibc  and   i = mx+ibc   for ibc=1,...,mbc
#
#
#
#-------------------------------------------------------
#     # left boundary:
#-------------------------------------------------------
#
    range::UnitRange = 1:meqn
    if mthbc[1] == 0

        # user-specified boundary conditions go here in place of error output
        println("*** ERROR *** mthbc(1)=0 and no BCs specified in bc1")
        exit(-1)

    elseif mthbc[1] == 1
        # zero-order extrapolation:
        for ibc=1:mbc
            q[range, 1-ibc] = q[range, 1]
        end
    elseif mthbc[1] == 2
        # periodic:
        for ibc=1:mbc
            q[range, 1-ibc] = q[range, mx+1-ibc]
        end
    elseif mthbc[1] == 3
        # solid wall (assumes 2'nd component is velocity or momentum in x):
        for ibc=1:mbc
            q[range, 1-ibc] = q[range, ibc]
            q[2,1-ibc] = -q[2,ibc]
        end
        # negate the normal velocity:
    end # if mthbc[1] == ...

#
#-------------------------------------------------------
#     # right boundary:
#-------------------------------------------------------

    if mthbc[2] == 0
        # user-specified boundary conditions go here in place of error output
        println("*** ERROR *** mthbc(2)=0 and no BCs specified in bc2")
        exit(-1)

    elseif mthbc[2] == 1
        # zero-order extrapolation:
        for ibc=1:mbc
            q[range, mx+ibc] = q[range, mx]
        end
    elseif mthbc[2] == 2
        # periodic:
        for ibc=1:mbc
            q[range, mx+ibc] = q[range, ibc]
        end
    elseif mthbc[2] == 3
        # solid wall (assumes 2'nd component is velocity or momentum in x):
        for ibc=1:mbc
            q[range, mx+ibc] = q[range, mx+1-ibc]
            q[2,mx+ibc] = -q[2,mx+1-ibc]
        end
    end # if mthbc[2] == ...

end


struct Ginsu{N,JA,JB,JC,JD}
    A::JA
    B::JB
    C::JC
    D::JD
    rₒ::NTuple{N,Float64}
    δr::NTuple{N,Float64}
end

"""
    g = Ginsu(r0, dr, nr, sour, recr, padr, ndamp; dims=(:z,:y,:x), stencilhalfwidth=2, vector_width=8)

Create a Ginsu object that defines the model aperture for a given shot.  `g` is used to subset earth
models using the `sub` and `sub!` methods, and provides the inverse operation using the `super`,
`super!` and `super_accumulate!` methods.  The padding for the earth model subset is determined
from the source and receiver positions, along with the padding `padr` and damping region `ndamp`
parameters.

# required parameters[1,2]
* `r0::NTuple{N,Real}` model origin in each model dimension
* `dr::NTuple{N,Real}` model cell widths in each model dimension
* `nr::NTuple{N,Int}` model cell counts in each model dimension
* `sour::NTuple{N,Vector{Float}}` source locations in each model dimension
* `recr::NTuple{N,Vector{Float}}` receiver locations in each model dimension
* `padr::NTuple{N,NTuple{Real,Real}}` padding beyond the survey aperture in each model dimension[3,4]
* `ndamp::NTuple{N,NTuple{Int,Int}}` damping region for absorbing boundaies in each model dimension[3,4]

# named optional parameters
* `dim=(:z,:y,:x)` axis ordering with the default being `z` fast, and `x` slow
* `stencilhalfwidth=0` if there is a free-surface, set `stencilhalfwidth`, padr, and ndamp accordingly. This will add `stencilhalfwidth` cells above the free surface to allow copying the mirrored model to implement the free surface boundary condition.
* `vector_width=8` sets the width required for vectorization of the code, and ensures that the size(ginsu) divided by vector_width has remainder zero for each dimension.

# type specification
* `g.lextrng` is the logical (1-based) indices for the Ginsu model subset
* `g.lintrng` is the logical (1-based) indices for the Ginsu model subset interior
* `rₒ` is the physical origin of the Ginsu model subset
* `δr` is the grid cell sizes

# Notes
1. `N` is the number of model dimensions
2. `rₒ[i]` is the model origin along the ith model dimension
3. the model padding is the combination of `padr` and `ndamp`
4. `padr[i][1]` is the padding for the dimension start, and `padr[i][2]` for the end.  The same is true for `ndamp`.

## Example (free-surface)
```
o---------------c---x--------------x---c-------o
|               |   |              |   |       |
|               |   |              |   |       |
|               |   |              |   |       |
|               |   |              |   |       |
|               |   x--------------x   |       |
|               |                      |       |
o---------------c----------------------c-------o
```
* `o` denotes the original model
* `c` denotes the Ginsu module subset
* `x` denotes the Ginsu model subset interior
* the region falling in-between the Ginsu model subset and the Ginsu model subset interior is the absorbing boundary region.
"""
function Ginsu(
        r0::NTuple{N,Real},
        dr::NTuple{N,Real},
        nr::NTuple{N,Int64},
        sour::NTuple{N,AbstractArray{Float64,1}},
        recr::NTuple{N,AbstractArray{Float64,1}},
        padr::NTuple{N,Tuple{Real,Real}},
        ndamp::NTuple{N,Tuple{Int,Int}};
        T::DataType = Float32,
        dims::NTuple=(:z,:y,:x),
        stencilhalfwidth::Int=0,
        vector_width::Int = 8) where N
    nrec = length(recr[1])
    nsrc = length(sour[1])
    for idim = 2:N
        @assert length(recr[idim]) == nrec
        @assert length(sour[idim]) == nsrc
    end

    lextrng = Array{UnitRange{Int64}}(undef, N)
    lintrng = Array{UnitRange{Int64}}(undef, N)

    for idim = 1:N
        # source and receiver coords for this dimension:
        reci = recr[idim]
        soui = sour[idim]

        if dims[idim] == :z
            lb, ub = r0[idim] - padr[idim][1] - ndamp[idim][1]*dr[idim] - stencilhalfwidth*dr[idim], r0[idim] + (nr[idim]-1+ndamp[idim][2])*dr[idim] + padr[idim][2]
        else
            # midpoints for all sources:
            midi = Array{Float64}(undef, nsrc, nrec)
            for isrc = 1:nsrc
                midi[isrc,:] = (soui[isrc] .+ reci) / 2
            end

            # midpoint float range:
            lb, ub = minimum(midi) - padr[idim][1], maximum(midi) + padr[idim][2]

            # ensure that the range encloses the source:
            lb, ub = min(minimum(soui) - padr[idim][1], lb), max(maximum(soui) + padr[idim][2], ub)

            # ensure that the range encloses all receivers:
            lb, ub = min(minimum(reci) - padr[idim][1], lb), max(maximum(reci) + padr[idim][2], ub)

            # add damping region
            lb, ub = lb - ndamp[idim][1]*dr[idim], ub + ndamp[idim][2]*dr[idim]
        end

        # integer range:
        idx_lb, idx_ub = floor(Int, (lb - r0[idim]) / dr[idim]) + 1, ceil(Int, (ub - r0[idim]) / dr[idim]) + 1

        # ensure lengths are a scalar multiple of vector_width (for vector alignment)
        n = idx_ub - idx_lb + 1
        d,r = divrem(n,vector_width)
        if r != 0
            idx_ub = idx_lb + (d+1)*vector_width - 1
        end

        # interior/exterior ranges
        lextrng[idim] = idx_lb:idx_ub
        lintrng[idim] = (idx_lb+ndamp[idim][1]):(idx_ub-ndamp[idim][2])
    end

    A = JopPad(JetSpace(T,nr), ntuple(i->lintrng[i], N)..., extend=true)
    B = JopPad(JetSpace(T,nr), ntuple(i->lintrng[i], N)..., extend=false)
    C = JopPad(JetSpace(T,nr), ntuple(i->lextrng[i], N)..., extend=true)
    D = JopPad(JetSpace(T,nr), ntuple(i->lextrng[i], N)..., extend=false)

    Ginsu(A, B, C, D, ntuple(i->Float64(r0[i]), N), ntuple(i->Float64(dr[i]), N))
end

function Ginsu(
        r0::NTuple{N,Real},
        dr::NTuple{N,Real},
        nr::NTuple{N,Int64},
        sour::NTuple{N,AbstractArray{Array{Float64,1},1}},
        recr::NTuple{N,AbstractArray{Array{Float64,1},1}},
        padr::NTuple{N,Tuple{Real,Real}},
        ndamp::NTuple{N,Tuple{Int,Int}};
        T = Float32,
        dims::Tuple=(:z,:y,:x),
        stencilhalfwidth::Integer=0,
        vector_width::Int = 8) where N
    nshot = length(sour[1])

    ishot = 1
    g = Ginsu(r0, dr, nr, ntuple(idim->sour[idim][ishot], N), ntuple(idim->recr[idim][ishot], N), padr, ndamp, dims=dims, stencilhalfwidth=stencilhalfwidth, vector_width=vector_width)

    lextrng_beg = Int[lextents(g,interior=false)[idim][1] for idim=1:N]
    lextrng_end = Int[lextents(g,interior=false)[idim][end] for idim=1:N]
    lintrng_beg = Int[lextents(g,interior=true)[idim][1] for idim=1:N]
    lintrng_end = Int[lextents(g,interior=true)[idim][end] for idim=1:N]

    for ishot = 2:nshot
        g = Ginsu(r0, dr, nr, ntuple(idim->sour[idim][ishot], N), ntuple(idim->recr[idim][ishot], N), padr, ndamp, dims=dims, stencilhalfwidth=stencilhalfwidth, vector_width=vector_width)
        for idim = 1:N
            if lextents(g)[idim][1] < lextrng_beg[idim]
                lextrng_beg[idim] = lextents(g,interior=false)[idim][1]
                lintrng_beg[idim] = lextents(g,interior=true)[idim][1]
            end
            if lextents(g)[idim][end] > lextrng_end[idim]
                lextrng_end[idim] = lextents(g,interior=false)[idim][end]
                lintrng_end[idim] = lextents(g,interior=true)[idim][end]
            end
        end
    end

    A = JopPad(JetSpace(T,nr), ntuple(i->lintrng_beg[i]:lintrng_end[i], N)..., extend=true)
    B = JopPad(JetSpace(T,nr), ntuple(i->lintrng_beg[i]:lintrng_end[i], N)..., extend=false)
    C = JopPad(JetSpace(T,nr), ntuple(i->lextrng_beg[i]:lextrng_end[i], N)..., extend=true)
    D = JopPad(JetSpace(T,nr), ntuple(i->lextrng_beg[i]:lextrng_end[i], N)..., extend=false)

    Ginsu(A, B, C, D, r0, dr)
end

"""
    g = Ginsu(r0, dr, nr, aperturer, ndamp; T=Float32)

Create a Ginsu object from absolute aperture.

# required parameters[1,2]
* `aperturer::NTuple{Range}` aperture in each dimension
"""
function Ginsu(r0::NTuple{N,Real}, dr::NTuple{N,Real}, nr::NTuple{N,Int}, aperturer::NTuple{N,AbstractRange}, ndamp::NTuple{N,Tuple{Int,Int}}; T=Float32, stencilhalfwidth::Int=0, dims=(:z,:x,:y), vector_width::Int = 8) where N
    lextrng = Array{UnitRange{Int64}}(undef, N)
    lintrng = Array{UnitRange{Int64}}(undef, N)

    for idim = 1:N
        if dims[idim] == :z
            lb, ub = aperturer[idim][1] - (stencilhalfwidth+ndamp[idim][1])*dr[idim], aperturer[idim][end] + ndamp[idim][2]*dr[idim]
        else
            lb, ub = aperturer[idim][1] - ndamp[idim][1]*dr[idim], aperturer[idim][end] + ndamp[idim][2]*dr[idim]
        end

        # integer range:
        idx_lb, idx_ub = floor(Int, (lb - r0[idim]) / dr[idim]) + 1, ceil(Int, (ub - r0[idim]) / dr[idim]) + 1

        # ensure lengths are a scalar multiple of vector_width (for vector alignment)
        n = idx_ub - idx_lb + 1
        d,r = divrem(n,vector_width)
        if r != 0
            idx_ub = idx_lb + (d+1)*vector_width - 1
        end

        lextrng[idim] = idx_lb:idx_ub
        lintrng[idim] = (idx_lb+ndamp[idim][1]):(idx_ub-ndamp[idim][2])
    end

    A = JopPad(JetSpace(T,nr), ntuple(i->lintrng[i], N)..., extend=true)
    B = JopPad(JetSpace(T,nr), ntuple(i->lintrng[i], N)..., extend=false)
    C = JopPad(JetSpace(T,nr), ntuple(i->lextrng[i], N)..., extend=true)
    D = JopPad(JetSpace(T,nr), ntuple(i->lextrng[i], N)..., extend=false)

    Ginsu(A, B, C, D, r0, dr)
end

function _op(ginsu::Ginsu; extend=false, interior=false)
    interior  && extend  && return ginsu.A
    interior  && !extend && return ginsu.B
    !interior && extend  && return ginsu.C
    ginsu.D
end

Base.copy(ginsu::Ginsu) = Ginsu(ginsu.A, ginsu.B, ginsu.C, ginsu.D, ginsu.rₒ, ginsu.δr)

"""
    sub!(mg, ginsu, m; extend=true, interior=false)

Store the subset of `m` corresponding to `ginsu` in `mg`.
"""
sub!(prop_ginsu::AbstractArray, ginsu::Ginsu, prop::AbstractArray; extend=true, interior=false) = mul!(prop_ginsu, _op(ginsu;extend=extend,interior=interior), prop)

"""
    mg = sub(ginsu, m; extend=true, interior=false)

Store the subset of `m` corresponding to `ginsu` in `mg`.  New memory is allocated
for `mg`.  `interior=false` includes the sponge region.
"""
sub(ginsu::Ginsu, prop::AbstractArray; extend=true, interior=false) = _op(ginsu,extend=extend,interior=interior)*prop

"""
    super!(m, ginsu, mg; interior=false, accumulate=false)

Store the superset of `mg` corresponding to `ginsu` in `m`. `interior=false` includes the sponge
region.
"""
function super!(prop::AbstractArray, ginsu::Ginsu, prop_ginsu::AbstractArray; interior=false, accumulate=false)
    A = _op(ginsu, extend=false, interior=interior)
    state!(A, (accumulate=accumulate,))
    mul!(prop, A', prop_ginsu)
    prop
end

"""
    mi = interior(ginsu, mg)

Get a view of the Ginsu'd subset `mg` corresponding to its interior region which
excludes the sponge.
"""
function interior(ginsu::Ginsu{N}, prop_ginsu::AbstractArray{T,N}) where {T,N}
    rng = interior(ginsu)
    view(prop_ginsu, rng...)
end

"""
    I = interior(ginsu)

Get the index range corresponding to the interior region which excludes
the sponge.
"""
function interior(ginsu::Ginsu{N}) where {N}
    AI = _op(ginsu, extend=false, interior=true)
    AE = _op(ginsu, extend=false, interior=false)
    ntuple(i->begin
            strt = state(AI).pad[i][1] - state(AE).pad[i][1] + 1
            stop = strt + length(state(AI).pad[i]) - 1
            strt:stop
        end, Val{N}())
end

Base.size(gn::Ginsu, i::Int; interior=false) = length(state(_op(gn; interior=interior)).pad[i])
Base.size(gn::Ginsu{N}; interior=false) where {N} = ntuple(i->size(gn, i; interior=interior), Val{N}())

lextents(gn::Ginsu, i::Int; interior=false) = state(_op(gn; interior=interior)).pad[i]
lextents(gn::Ginsu{N}; interior=false) where {N} = ntuple(i->lextents(gn, i; interior=interior), Val{N}())

pextents(gn::Ginsu, i::Int; interior=false) = begin l=lextents(gn,i;interior=interior); (gn.rₒ[i] + (l[1]-1)*gn.δr[i]):(gn.δr[i]):(gn.rₒ[i] + (l[end]-1)*gn.δr[i]) end
pextents(gn::Ginsu{N}; interior=false) where {N} = ntuple(i->pextents(gn,i;interior=interior), Val{N}())

origin(gn::Ginsu, i::Int; interior=false) = gn.rₒ[i] + (first(lextents(gn,i;interior=interior))-1)*gn.δr[i]
origin(gn::Ginsu{N}; interior=false) where {N} = ntuple(i->origin(gn, i;interior=interior)[1], Val{N}())

export Ginsu, lextents, pextents, origin, interior, sub, sub!, super!


using Intervals: Endpoint, Left, Right, LeftEndpoint, RightEndpoint

@testset "Endpoint" begin
    @testset "constructors" begin
        for D in (Left, Right)
            @test Endpoint{Int, D, Closed}(0).endpoint == 0
            @test Endpoint{Int, D, Open}(0).endpoint == 0
            @test Endpoint{Int, D, Unbounded}(nothing).endpoint isa Int

            @test_throws MethodError Endpoint{Int, D, Closed}(nothing)
            @test_throws MethodError Endpoint{Int, D, Open}(nothing)
            @test_throws MethodError Endpoint{Int, D, Unbounded}(0)

            @test_throws MethodError Endpoint{Nothing, D, Closed}(nothing)
            @test_throws MethodError Endpoint{Nothing, D, Open}(nothing)
            @test Endpoint{Nothing, D, Unbounded}(nothing).endpoint === nothing

            @test Endpoint{Int, D, Closed}(0.0).endpoint == 0
            @test Endpoint{Int, D, Open}(0.0).endpoint == 0
            @test_throws MethodError Endpoint{Int, D, Unbounded}(0.0)
        end
    end

    @testset "bounded" begin
        @testset "LeftEndpoint < LeftEndpoint" begin
            @test LeftEndpoint{Open}(1) < LeftEndpoint{Open}(2.0)
            @test LeftEndpoint{Closed}(1) < LeftEndpoint{Open}(2.0)
            @test LeftEndpoint{Open}(1) < LeftEndpoint{Closed}(2.0)
            @test LeftEndpoint{Closed}(1) < LeftEndpoint{Closed}(2.0)

            @test !(LeftEndpoint{Open}(1) < LeftEndpoint{Open}(1.0))
            @test LeftEndpoint{Closed}(1) < LeftEndpoint{Open}(1.0)
            @test !(LeftEndpoint{Open}(1) < LeftEndpoint{Closed}(1.0))
            @test !(LeftEndpoint{Closed}(1) < LeftEndpoint{Closed}(1.0))

            @test !(LeftEndpoint{Open}(2) < LeftEndpoint{Open}(1.0))
            @test !(LeftEndpoint{Closed}(2) < LeftEndpoint{Open}(1.0))
            @test !(LeftEndpoint{Open}(2) < LeftEndpoint{Closed}(1.0))
            @test !(LeftEndpoint{Closed}(2) < LeftEndpoint{Closed}(1.0))
        end

        @testset "LeftEndpoint <= LeftEndpoint" begin
            @test LeftEndpoint{Open}(1) <= LeftEndpoint{Open}(2.0)
            @test LeftEndpoint{Closed}(1) <= LeftEndpoint{Open}(2.0)
            @test LeftEndpoint{Open}(1) <= LeftEndpoint{Closed}(2.0)
            @test LeftEndpoint{Closed}(1) <= LeftEndpoint{Closed}(2.0)

            @test LeftEndpoint{Open}(1) <= LeftEndpoint{Open}(1.0)
            @test LeftEndpoint{Closed}(1) <= LeftEndpoint{Open}(1.0)
            @test !(LeftEndpoint{Open}(1) <= LeftEndpoint{Closed}(1.0))
            @test LeftEndpoint{Closed}(1) <= LeftEndpoint{Closed}(1.0)

            @test !(LeftEndpoint{Open}(2) <= LeftEndpoint{Open}(1.0))
            @test !(LeftEndpoint{Closed}(2) <= LeftEndpoint{Open}(1.0))
            @test !(LeftEndpoint{Open}(2) <= LeftEndpoint{Closed}(1.0))
            @test !(LeftEndpoint{Closed}(2) <= LeftEndpoint{Closed}(1.0))
        end

        @testset "RightEndpoint < RightEndpoint" begin
            @test RightEndpoint{Open}(1) < RightEndpoint{Open}(2.0)
            @test RightEndpoint{Closed}(1) < RightEndpoint{Open}(2.0)
            @test RightEndpoint{Open}(1) < RightEndpoint{Closed}(2.0)
            @test RightEndpoint{Closed}(1) < RightEndpoint{Closed}(2.0)

            @test !(RightEndpoint{Open}(1) < RightEndpoint{Open}(1.0))
            @test !(RightEndpoint{Closed}(1) < RightEndpoint{Open}(1.0))
            @test RightEndpoint{Open}(1) < RightEndpoint{Closed}(1.0)
            @test !(RightEndpoint{Closed}(1) < RightEndpoint{Closed}(1.0))

            @test !(RightEndpoint{Open}(2) < RightEndpoint{Open}(1.0))
            @test !(RightEndpoint{Closed}(2) < RightEndpoint{Open}(1.0))
            @test !(RightEndpoint{Open}(2) < RightEndpoint{Closed}(1.0))
            @test !(RightEndpoint{Closed}(2) < RightEndpoint{Closed}(1.0))
        end

        @testset "RightEndpoint <= RightEndpoint" begin
            @test RightEndpoint{Open}(1) <= RightEndpoint{Open}(2.0)
            @test RightEndpoint{Closed}(1) <= RightEndpoint{Open}(2.0)
            @test RightEndpoint{Open}(1) <= RightEndpoint{Closed}(2.0)
            @test RightEndpoint{Closed}(1) <= RightEndpoint{Closed}(2.0)

            @test RightEndpoint{Open}(1) <= RightEndpoint{Open}(1.0)
            @test !(RightEndpoint{Closed}(1) <= RightEndpoint{Open}(1.0))
            @test RightEndpoint{Open}(1) <= RightEndpoint{Closed}(1.0)
            @test RightEndpoint{Closed}(1) <= RightEndpoint{Closed}(1.0)

            @test !(RightEndpoint{Open}(2) <= RightEndpoint{Open}(1.0))
            @test !(RightEndpoint{Closed}(2) <= RightEndpoint{Open}(1.0))
            @test !(RightEndpoint{Open}(2) <= RightEndpoint{Closed}(1.0))
            @test !(RightEndpoint{Closed}(2) <= RightEndpoint{Closed}(1.0))
        end

        @testset "LeftEndpoint < RightEndpoint" begin
            @test LeftEndpoint{Open}(1) < RightEndpoint{Open}(2.0)
            @test LeftEndpoint{Closed}(1) < RightEndpoint{Open}(2.0)
            @test LeftEndpoint{Open}(1) < RightEndpoint{Closed}(2.0)
            @test LeftEndpoint{Closed}(1) < RightEndpoint{Closed}(2.0)

            @test !(LeftEndpoint{Open}(1) < RightEndpoint{Open}(1.0))
            @test !(LeftEndpoint{Closed}(1) < RightEndpoint{Open}(1.0))
            @test !(LeftEndpoint{Open}(1) < RightEndpoint{Closed}(1.0))
            @test !(LeftEndpoint{Closed}(1) < RightEndpoint{Closed}(1.0))

            @test !(LeftEndpoint{Open}(2) < RightEndpoint{Open}(1.0))
            @test !(LeftEndpoint{Closed}(2) < RightEndpoint{Open}(1.0))
            @test !(LeftEndpoint{Open}(2) < RightEndpoint{Closed}(1.0))
            @test !(LeftEndpoint{Closed}(2) < RightEndpoint{Closed}(1.0))
        end

        @testset "LeftEndpoint <= RightEndpoint" begin
            @test LeftEndpoint{Open}(1) <= RightEndpoint{Open}(2.0)
            @test LeftEndpoint{Closed}(1) <= RightEndpoint{Open}(2.0)
            @test LeftEndpoint{Open}(1) <= RightEndpoint{Closed}(2.0)
            @test LeftEndpoint{Closed}(1) <= RightEndpoint{Closed}(2.0)

            @test !(LeftEndpoint{Open}(1) <= RightEndpoint{Open}(1.0))
            @test !(LeftEndpoint{Closed}(1) <= RightEndpoint{Open}(1.0))
            @test !(LeftEndpoint{Open}(1) <= RightEndpoint{Closed}(1.0))
            @test LeftEndpoint{Closed}(1) <= RightEndpoint{Closed}(1.0)

            @test !(LeftEndpoint{Open}(2) <= RightEndpoint{Open}(1.0))
            @test !(LeftEndpoint{Closed}(2) <= RightEndpoint{Open}(1.0))
            @test !(LeftEndpoint{Open}(2) <= RightEndpoint{Closed}(1.0))
            @test !(LeftEndpoint{Closed}(2) <= RightEndpoint{Closed}(1.0))
        end

        @testset "RightEndpoint < LeftEndpoint" begin
            @test RightEndpoint{Open}(1) < LeftEndpoint{Open}(2.0)
            @test RightEndpoint{Closed}(1) < LeftEndpoint{Open}(2.0)
            @test RightEndpoint{Open}(1) < LeftEndpoint{Closed}(2.0)
            @test RightEndpoint{Closed}(1) < LeftEndpoint{Closed}(2.0)

            @test RightEndpoint{Open}(1) < LeftEndpoint{Open}(1.0)
            @test RightEndpoint{Closed}(1) < LeftEndpoint{Open}(1.0)
            @test RightEndpoint{Open}(1) < LeftEndpoint{Closed}(1.0)
            @test !(RightEndpoint{Closed}(1) < LeftEndpoint{Closed}(1.0))

            @test !(RightEndpoint{Open}(2) < LeftEndpoint{Open}(1.0))
            @test !(RightEndpoint{Closed}(2) < LeftEndpoint{Open}(1.0))
            @test !(RightEndpoint{Open}(2) < LeftEndpoint{Closed}(1.0))
            @test !(RightEndpoint{Closed}(2) < LeftEndpoint{Closed}(1.0))
        end

        @testset "RightEndpoint <= LeftEndpoint" begin
            @test RightEndpoint{Open}(1) <= LeftEndpoint{Open}(2.0)
            @test RightEndpoint{Closed}(1) <= LeftEndpoint{Open}(2.0)
            @test RightEndpoint{Open}(1) <= LeftEndpoint{Closed}(2.0)
            @test RightEndpoint{Closed}(1) <= LeftEndpoint{Closed}(2.0)

            @test RightEndpoint{Open}(1) <= LeftEndpoint{Open}(1.0)
            @test RightEndpoint{Closed}(1) <= LeftEndpoint{Open}(1.0)
            @test RightEndpoint{Open}(1) <= LeftEndpoint{Closed}(1.0)
            @test RightEndpoint{Closed}(1) <= LeftEndpoint{Closed}(1.0)

            @test !(RightEndpoint{Open}(2) <= LeftEndpoint{Open}(1.0))
            @test !(RightEndpoint{Closed}(2) <= LeftEndpoint{Open}(1.0))
            @test !(RightEndpoint{Open}(2) <= LeftEndpoint{Closed}(1.0))
            @test !(RightEndpoint{Closed}(2) <= LeftEndpoint{Closed}(1.0))
        end

        @testset "$T < Scalar" for T in (LeftEndpoint, RightEndpoint)
            @test T{Open}(1) < 2.0
            @test T{Closed}(1) < 2.0

            @test (T{Open}(1) < 1.0) == (T === RightEndpoint)
            @test !(T{Closed}(1) < 1.0)

            @test !(T{Open}(1) < 0.0)
            @test !(T{Closed}(1) < 0.0)
        end

        @testset "$T <= Scalar" for T in (LeftEndpoint, RightEndpoint)
            @test T{Open}(1) <= 2.0
            @test T{Closed}(1) <= 2.0

            @test (T{Open}(1) <= 1.0) == (T === RightEndpoint)
            @test T{Closed}(1) <= 1.0

            @test !(T{Open}(1) <= 0.0)
            @test !(T{Closed}(1) <= 0.0)
        end

        @testset "Scalar < $T" for T in (LeftEndpoint, RightEndpoint)
            @test 0 < T{Open}(1.0)
            @test 0 < T{Closed}(1.0)

            @test (1 < T{Open}(1.0)) == (T === LeftEndpoint)
            @test !(1 < T{Closed}(1.0))

            @test !(2 < T{Open}(1.0))
            @test !(2 < T{Closed}(1.0))
        end

        @testset "Scalar <= $T" for T in (LeftEndpoint, RightEndpoint)
            @test 0 <= T{Open}(1.0)
            @test 0 <= T{Closed}(1.0)

            @test (1 <= T{Open}(1.0)) == (T === LeftEndpoint)
            @test 1 <= T{Closed}(1.0)

            @test !(2 <= T{Open}(1.0))
            @test !(2 <= T{Closed}(1.0))
        end

        @testset "LeftEndpoint == LeftEndpoint" begin
            @test LeftEndpoint{Open}(1) != LeftEndpoint{Open}(2.0)
            @test LeftEndpoint{Closed}(1) != LeftEndpoint{Open}(2.0)
            @test LeftEndpoint{Open}(1) != LeftEndpoint{Closed}(2.0)
            @test LeftEndpoint{Closed}(1) != LeftEndpoint{Closed}(2.0)

            @test LeftEndpoint{Open}(1) == LeftEndpoint{Open}(1.0)
            @test LeftEndpoint{Closed}(1) != LeftEndpoint{Open}(1.0)
            @test LeftEndpoint{Open}(1) != LeftEndpoint{Closed}(1.0)
            @test LeftEndpoint{Closed}(1) == LeftEndpoint{Closed}(1.0)
        end

        @testset "RightEndpoint == RightEndpoint" begin
            @test RightEndpoint{Open}(1) != RightEndpoint{Open}(2.0)
            @test RightEndpoint{Closed}(1) != RightEndpoint{Open}(2.0)
            @test RightEndpoint{Open}(1) != RightEndpoint{Closed}(2.0)
            @test RightEndpoint{Closed}(1) != RightEndpoint{Closed}(2.0)

            @test RightEndpoint{Open}(1) == RightEndpoint{Open}(1.0)
            @test RightEndpoint{Closed}(1) != RightEndpoint{Open}(1.0)
            @test RightEndpoint{Open}(1) != RightEndpoint{Closed}(1.0)
            @test RightEndpoint{Closed}(1) == RightEndpoint{Closed}(1.0)
        end

        @testset "LeftEndpoint == RightEndpoint" begin
            @test LeftEndpoint{Open}(1) != RightEndpoint{Open}(2.0)
            @test LeftEndpoint{Closed}(1) != RightEndpoint{Open}(2.0)
            @test LeftEndpoint{Open}(1) != RightEndpoint{Closed}(2.0)
            @test LeftEndpoint{Closed}(1) != RightEndpoint{Closed}(2.0)

            @test LeftEndpoint{Open}(1) != RightEndpoint{Open}(1.0)
            @test LeftEndpoint{Closed}(1) != RightEndpoint{Open}(1.0)
            @test LeftEndpoint{Open}(1) != RightEndpoint{Closed}(1.0)
            @test LeftEndpoint{Closed}(1) == RightEndpoint{Closed}(1.0)
        end

        @testset "RightEndpoint == LeftEndpoint" begin
            @test RightEndpoint{Open}(1) != LeftEndpoint{Open}(2.0)
            @test RightEndpoint{Closed}(1) != LeftEndpoint{Open}(2.0)
            @test RightEndpoint{Open}(1) != LeftEndpoint{Closed}(2.0)
            @test RightEndpoint{Closed}(1) != LeftEndpoint{Closed}(2.0)

            @test RightEndpoint{Open}(1) != LeftEndpoint{Open}(1.0)
            @test RightEndpoint{Closed}(1) != LeftEndpoint{Open}(1.0)
            @test RightEndpoint{Open}(1) != LeftEndpoint{Closed}(1.0)
            @test RightEndpoint{Closed}(1) == LeftEndpoint{Closed}(1.0)
        end

        @testset "$T == Scalar" for T in (LeftEndpoint, RightEndpoint)
            @test T{Open}(0) != 1.0
            @test T{Closed}(0) != 1.0

            @test T{Open}(1) != 1.0
            @test T{Closed}(1) == 1.0

            @test T{Open}(2) != 1.0
            @test T{Closed}(2) != 1.0
        end

        @testset "Scalar == $T" for T in (LeftEndpoint, RightEndpoint)
            @test 1.0 != T{Open}(0)
            @test 1.0 != T{Closed}(0)

            @test 1.0 != T{Open}(1)
            @test 1.0 == T{Closed}(1)

            @test 1.0 != T{Open}(2)
            @test 1.0 != T{Closed}(2)
        end

        @testset "isequal" begin
            @test isequal(LeftEndpoint{Closed}(0.0), LeftEndpoint{Closed}(0.0))
            @test isequal(LeftEndpoint{Open}(0.0), LeftEndpoint{Open}(0.0))
            @test !isequal(LeftEndpoint{Closed}(-0.0), LeftEndpoint{Open}(0.0))
            @test !isequal(LeftEndpoint{Open}(-0.0), LeftEndpoint{Closed}(0.0))
            @test !isequal(LeftEndpoint{Closed}(-0.0), LeftEndpoint{Closed}(0.0))
            @test !isequal(LeftEndpoint{Open}(-0.0), LeftEndpoint{Open}(0.0))

            @test isequal(RightEndpoint{Closed}(0.0), LeftEndpoint{Closed}(0.0))
            @test !isequal(LeftEndpoint{Closed}(-0.0), RightEndpoint{Open}(0.0))
            @test !isequal(RightEndpoint{Open}(-0.0), LeftEndpoint{Closed}(0.0))
            @test !isequal(LeftEndpoint{Closed}(-0.0), RightEndpoint{Closed}(0.0))
        end

        @testset "hash" begin
            # Need a complicated enough element type for this test to possibly fail. Using a
            # ZonedDateTime with a VariableTimeZone should do the trick.
            a = now(tz"Europe/London")
            b = deepcopy(a)
            @test hash(a) == hash(b)  # Double check

            @test hash(LeftEndpoint{Open}(a)) == hash(LeftEndpoint{Open}(b))
            @test hash(LeftEndpoint{Closed}(a)) != hash(LeftEndpoint{Open}(b))
            @test hash(LeftEndpoint{Open}(a)) != hash(LeftEndpoint{Closed}(b))
            @test hash(LeftEndpoint{Closed}(a)) == hash(LeftEndpoint{Closed}(b))

            @test hash(RightEndpoint{Open}(a)) == hash(RightEndpoint{Open}(b))
            @test hash(RightEndpoint{Closed}(a)) != hash(RightEndpoint{Open}(b))
            @test hash(RightEndpoint{Open}(a)) != hash(RightEndpoint{Closed}(b))
            @test hash(RightEndpoint{Closed}(a)) == hash(RightEndpoint{Closed}(b))

            @test hash(LeftEndpoint{Open}(a)) != hash(RightEndpoint{Open}(b))
            @test hash(LeftEndpoint{Closed}(a)) != hash(RightEndpoint{Open}(b))
            @test hash(LeftEndpoint{Open}(a)) != hash(RightEndpoint{Closed}(b))
            @test hash(LeftEndpoint{Closed}(a)) != hash(RightEndpoint{Closed}(b))
        end
    end

    # Note: The value for unbounded endpoints is irrelevant
    @testset "unbounded" begin
        @testset "LeftEndpoint < LeftEndpoint" begin
            @test !(LeftEndpoint{Unbounded}(nothing) < LeftEndpoint{Unbounded}(nothing))
            @test LeftEndpoint{Unbounded}(nothing) < LeftEndpoint{Open}(0.0)
            @test LeftEndpoint{Unbounded}(nothing) < LeftEndpoint{Closed}(0.0)
            @test !(LeftEndpoint{Open}(0) < LeftEndpoint{Unbounded}(nothing))
            @test !(LeftEndpoint{Closed}(0) < LeftEndpoint{Unbounded}(nothing))
        end

        @testset "LeftEndpoint <= LeftEndpoint" begin
            @test LeftEndpoint{Unbounded}(nothing) <= LeftEndpoint{Unbounded}(nothing)
            @test LeftEndpoint{Unbounded}(nothing) <= LeftEndpoint{Open}(0.0)
            @test LeftEndpoint{Unbounded}(nothing) <= LeftEndpoint{Closed}(0.0)
            @test !(LeftEndpoint{Open}(0) <= LeftEndpoint{Unbounded}(nothing))
            @test !(LeftEndpoint{Closed}(0) <= LeftEndpoint{Unbounded}(nothing))
        end

        @testset "RightEndpoint < RightEndpoint" begin
            @test !(RightEndpoint{Unbounded}(nothing) < RightEndpoint{Unbounded}(nothing))
            @test !(RightEndpoint{Unbounded}(nothing) < RightEndpoint{Open}(0.0))
            @test !(RightEndpoint{Unbounded}(nothing) < RightEndpoint{Closed}(0.0))
            @test RightEndpoint{Open}(0) < RightEndpoint{Unbounded}(nothing)
            @test RightEndpoint{Closed}(0) < RightEndpoint{Unbounded}(nothing)
        end

        @testset "RightEndpoint <= RightEndpoint" begin
            @test RightEndpoint{Unbounded}(nothing) <= RightEndpoint{Unbounded}(nothing)
            @test !(RightEndpoint{Unbounded}(nothing) <= RightEndpoint{Open}(0.0))
            @test !(RightEndpoint{Unbounded}(nothing) <= RightEndpoint{Closed}(0.0))
            @test RightEndpoint{Open}(0) <= RightEndpoint{Unbounded}(nothing)
            @test RightEndpoint{Closed}(0) <= RightEndpoint{Unbounded}(nothing)
        end

        @testset "LeftEndpoint < RightEndpoint" begin
            @test LeftEndpoint{Unbounded}(nothing) < RightEndpoint{Unbounded}(nothing)
            @test LeftEndpoint{Unbounded}(nothing) < RightEndpoint{Open}(0.0)
            @test LeftEndpoint{Unbounded}(nothing) < RightEndpoint{Closed}(0.0)
            @test LeftEndpoint{Open}(0) < RightEndpoint{Unbounded}(nothing)
            @test LeftEndpoint{Closed}(0) < RightEndpoint{Unbounded}(nothing)
        end

        @testset "LeftEndpoint <= RightEndpoint" begin
            @test LeftEndpoint{Unbounded}(nothing) <= RightEndpoint{Unbounded}(nothing)
            @test LeftEndpoint{Unbounded}(nothing) <= RightEndpoint{Open}(0.0)
            @test LeftEndpoint{Unbounded}(nothing) <= RightEndpoint{Closed}(0.0)
            @test LeftEndpoint{Open}(0) <= RightEndpoint{Unbounded}(nothing)
            @test LeftEndpoint{Closed}(0) <= RightEndpoint{Unbounded}(nothing)
        end

        @testset "RightEndpoint < LeftEndpoint" begin
            @test !(RightEndpoint{Unbounded}(nothing) < LeftEndpoint{Unbounded}(nothing))
            @test !(RightEndpoint{Unbounded}(nothing) < LeftEndpoint{Open}(0.0))
            @test !(RightEndpoint{Unbounded}(nothing) < LeftEndpoint{Closed}(0.0))
            @test !(RightEndpoint{Open}(0) < LeftEndpoint{Unbounded}(nothing))
            @test !(RightEndpoint{Closed}(0) < LeftEndpoint{Unbounded}(nothing))
        end

        @testset "RightEndpoint <= LeftEndpoint" begin
            @test !(RightEndpoint{Unbounded}(nothing) < LeftEndpoint{Unbounded}(nothing))
            @test !(RightEndpoint{Unbounded}(nothing) < LeftEndpoint{Open}(0.0))
            @test !(RightEndpoint{Unbounded}(nothing) < LeftEndpoint{Closed}(0.0))
            @test !(RightEndpoint{Open}(0) < LeftEndpoint{Unbounded}(nothing))
            @test !(RightEndpoint{Closed}(0) < LeftEndpoint{Unbounded}(nothing))
        end

        @testset "LeftEndpoint < Scalar" begin
            @test LeftEndpoint{Unbounded}(nothing) < -Inf
            @test LeftEndpoint{Unbounded}(nothing) < Inf
        end

         @testset "LeftEndpoint <= Scalar" begin
            @test LeftEndpoint{Unbounded}(nothing) <= -Inf
            @test LeftEndpoint{Unbounded}(nothing) <= Inf
        end

        @testset "RightEndpoint < Scalar" begin
            @test !(RightEndpoint{Unbounded}(nothing) < -Inf)
            @test !(RightEndpoint{Unbounded}(nothing) < Inf)
        end

        @testset "RightEndpoint <= Scalar" begin
            @test !(RightEndpoint{Unbounded}(nothing) <= -Inf)
            @test !(RightEndpoint{Unbounded}(nothing) <= Inf)
        end

        @testset "Scalar < LeftEndpoint" begin
            @test !(-Inf < LeftEndpoint{Unbounded}(nothing))
            @test !(Inf < LeftEndpoint{Unbounded}(nothing))
        end

        @testset "Scalar <= LeftEndpoint" begin
            @test !(-Inf <= LeftEndpoint{Unbounded}(nothing))
            @test !(Inf <= LeftEndpoint{Unbounded}(nothing))
        end

        @testset "Scalar < RightEndpoint" begin
            @test -Inf < RightEndpoint{Unbounded}(nothing)
            @test Inf < RightEndpoint{Unbounded}(nothing)
        end

        @testset "Scalar < RightEndpoint" begin
            @test -Inf <= RightEndpoint{Unbounded}(nothing)
            @test Inf <= RightEndpoint{Unbounded}(nothing)
        end

        @testset "LeftEndpoint == LeftEndpoint" begin
            @test LeftEndpoint{Unbounded}(nothing) == LeftEndpoint{Unbounded}(nothing)
            @test LeftEndpoint{Unbounded}(nothing) != LeftEndpoint{Open}(0.0)
            @test LeftEndpoint{Unbounded}(nothing) != LeftEndpoint{Closed}(0.0)
            @test LeftEndpoint{Open}(0) != LeftEndpoint{Unbounded}(nothing)
            @test LeftEndpoint{Closed}(0) != LeftEndpoint{Unbounded}(nothing)
        end

        @testset "RightEndpoint == RightEndpoint" begin
            @test RightEndpoint{Unbounded}(nothing) == RightEndpoint{Unbounded}(nothing)
            @test RightEndpoint{Unbounded}(nothing) != RightEndpoint{Open}(0.0)
            @test RightEndpoint{Unbounded}(nothing) != RightEndpoint{Closed}(0.0)
            @test RightEndpoint{Open}(0) != RightEndpoint{Unbounded}(nothing)
            @test RightEndpoint{Closed}(0) != RightEndpoint{Unbounded}(nothing)
        end

        @testset "LeftEndpoint == RightEndpoint" begin
            @test LeftEndpoint{Unbounded}(nothing) != RightEndpoint{Unbounded}(nothing)
            @test LeftEndpoint{Unbounded}(nothing) != RightEndpoint{Open}(0.0)
            @test LeftEndpoint{Unbounded}(nothing) != RightEndpoint{Closed}(0.0)
            @test LeftEndpoint{Open}(0) != RightEndpoint{Unbounded}(nothing)
            @test LeftEndpoint{Closed}(0) != RightEndpoint{Unbounded}(nothing)
        end

        @testset "RightEndpoint == LeftEndpoint" begin
            @test RightEndpoint{Unbounded}(nothing) != LeftEndpoint{Unbounded}(nothing)
            @test RightEndpoint{Unbounded}(nothing) != LeftEndpoint{Open}(0.0)
            @test RightEndpoint{Unbounded}(nothing) != LeftEndpoint{Closed}(0.0)
            @test RightEndpoint{Open}(0) != LeftEndpoint{Unbounded}(nothing)
            @test RightEndpoint{Closed}(0) != LeftEndpoint{Unbounded}(nothing)
        end

        @testset "isequal" begin
            T = Float64

            @test isequal(LeftEndpoint{T,Unbounded}(nothing), LeftEndpoint{T,Unbounded}(nothing))
            @test !isequal(LeftEndpoint{T,Unbounded}(nothing), LeftEndpoint{T,Open}(0.0))
            @test !isequal(LeftEndpoint{T,Unbounded}(nothing), LeftEndpoint{T,Closed}(0.0))
            @test !isequal(LeftEndpoint{T,Open}(-0.0), LeftEndpoint{T,Unbounded}(nothing))
            @test !isequal(LeftEndpoint{T,Closed}(-0.0), LeftEndpoint{T,Unbounded}(nothing))

            @test !isequal(RightEndpoint{Unbounded}(nothing), LeftEndpoint{Unbounded}(nothing))
            @test !isequal(LeftEndpoint{Unbounded}(nothing), RightEndpoint{Unbounded}(nothing))
        end

         @testset "hash" begin
            # Note: Unbounded endpoints should ignore the value
            T = Int

            left_unbounded = LeftEndpoint{T,Unbounded}(nothing)
            @test hash(left_unbounded) == hash(LeftEndpoint{T,Unbounded}(nothing))
            @test hash(left_unbounded) != hash(LeftEndpoint{Unbounded}(nothing))
            @test hash(left_unbounded) != hash(LeftEndpoint{Open}(left_unbounded.endpoint))
            @test hash(left_unbounded) != hash(LeftEndpoint{Closed}(left_unbounded.endpoint))

            right_unbounded = RightEndpoint{T,Unbounded}(nothing)
            @test hash(right_unbounded) == hash(RightEndpoint{T,Unbounded}(nothing))
            @test hash(right_unbounded) != hash(RightEndpoint{Unbounded}(nothing))
            @test hash(right_unbounded) != hash(RightEndpoint{Open}(right_unbounded.endpoint))
            @test hash(right_unbounded) != hash(RightEndpoint{Closed}(right_unbounded.endpoint))

            @test hash(LeftEndpoint{T,Unbounded}(nothing)) != hash(RightEndpoint{T,Unbounded}(nothing))
            @test hash(LeftEndpoint{T,Unbounded}(nothing)) != hash(RightEndpoint{T,Open}(0))
            @test hash(LeftEndpoint{T,Unbounded}(nothing)) != hash(RightEndpoint{T,Closed}(0))
            @test hash(LeftEndpoint{T,Open}(0)) != hash(RightEndpoint{T,Unbounded}(nothing))
            @test hash(LeftEndpoint{T,Closed}(0)) != hash(RightEndpoint{T,Unbounded}(nothing))
        end
    end

    @testset "broadcast" begin
        test = [
            LeftEndpoint{Open}(0),
            LeftEndpoint{Closed}(0),
            LeftEndpoint{Unbounded}(nothing),
            RightEndpoint{Open}(0),
            RightEndpoint{Closed}(0),
            RightEndpoint{Unbounded}(nothing),
        ]

        # Verify that Endpoint is treated as a scalar during broadcast
        result = test .== 0
        @test result == [false, true, false, false, true, false]
    end
end