function dump_state_and_aux_init(dgngrp, currtime)
    @assert dgngrp.interpol !== nothing
end

function get_dims(dgngrp)
    if dgngrp.interpol !== nothing
        if dgngrp.interpol isa InterpolationBrick
            if Array ∈ typeof(dgngrp.interpol.x1g).parameters
                h_x1g = dgngrp.interpol.x1g
                h_x2g = dgngrp.interpol.x2g
                h_x3g = dgngrp.interpol.x3g
            else
                h_x1g = Array(dgngrp.interpol.x1g)
                h_x2g = Array(dgngrp.interpol.x2g)
                h_x3g = Array(dgngrp.interpol.x3g)
            end
            dims = OrderedDict("x" => h_x1g, "y" => h_x2g, "z" => h_x3g)
        elseif dgngrp.interpol isa InterpolationCubedSphere
            if Array ∈ typeof(dgngrp.interpol.rad_grd).parameters
                h_rad_grd = dgngrp.interpol.rad_grd
                h_lat_grd = dgngrp.interpol.lat_grd
                h_long_grd = dgngrp.interpol.long_grd
            else
                h_rad_grd = Array(dgngrp.interpol.rad_grd)
                h_lat_grd = Array(dgngrp.interpol.lat_grd)
                h_long_grd = Array(dgngrp.interpol.long_grd)
            end
            dims = OrderedDict(
                "rad" => h_rad_grd,
                "lat" => h_lat_grd,
                "long" => h_long_grd,
            )
        else
            error("Unsupported interpolation topology $(dgngrp.interpol)")
        end
    else
        error("Dump of non-interpolated data currently unsupported")
    end

    return dims
end

function dump_state_and_aux_collect(dgngrp, currtime)
    DA = CLIMA.array_type()
    mpicomm = Settings.mpicomm
    dg = Settings.dg
    Q = Settings.Q
    FT = eltype(Q.data)
    bl = dg.balancelaw
    mpirank = MPI.Comm_rank(mpicomm)

    # filename (may also want to take out)
    dprefix = @sprintf(
        "%s_%s-%s-num%04d",
        dgngrp.out_prefix,
        dgngrp.name,
        Settings.starttime,
        dgngrp.num
    )
    statefilename = joinpath(Settings.output_dir, dprefix)
    auxfilename = joinpath(Settings.output_dir, "$(dprefix)_aux")

    statenames = flattenednames(vars_state(bl, FT))
    #statenames = ("rho", "rhou", "rhov", "rhow", "rhoe")
    auxnames = flattenednames(vars_aux(bl, FT))

    all_state_data = nothing
    all_aux_data = nothing
    if dgngrp.interpol !== nothing
        istate = DA(Array{FT}(undef, dgngrp.interpol.Npl, num_state(bl, FT)))
        interpolate_local!(dgngrp.interpol, Q.data, istate)

        if dgngrp.project
            if dgngrp.interpol isa InterpolationCubedSphere
                # TODO: get indices here without hard-coding them
                _ρu, _ρv, _ρw = 2, 3, 4
                project_cubed_sphere!(dgngrp.interpol, istate, (_ρu, _ρv, _ρw))
            else
                error("Can only project for InterpolationCubedSphere")
            end
        end

        all_state_data =
            accumulate_interpolated_data(mpicomm, dgngrp.interpol, istate)

        iaux = DA(Array{FT}(undef, dgngrp.interpol.Npl, num_aux(bl, FT)))
        interpolate_local!(dgngrp.interpol, dg.auxstate.data, iaux)

        all_aux_data =
            accumulate_interpolated_data(mpicomm, dgngrp.interpol, iaux)
    else
        error("Dump of non-interpolated data currently unsupported")
    end

    dims = get_dims(dgngrp)

    statevarvals = OrderedDict()
    for i in 1:num_state(bl, FT)
        statevarvals[statenames[i]] = all_state_data[:, :, :, i]
    end
    write_data(dgngrp.writer, statefilename, dims, statevarvals, currtime)

    auxvarvals = OrderedDict()
    for i in 1:num_aux(bl, FT)
        auxvarvals[auxnames[i]] = all_aux_data[:, :, :, i]
    end
    write_data(dgngrp.writer, auxfilename, dims, auxvarvals, currtime)

    return nothing
end

function dump_state_and_aux_fini(dgngrp, currtime) end


import  NLsolve

using TimerOutputs
using Test
using Modia: SimulationOptions, emptyResult!, derivatives!, timeEventCondition!, affectTimeEvent!, outputs!,  reinitEventHandler, propagateEvaluateAndInstantiate!, eventIteration!, get_xe, hasParticles, resizeLinearEquations!, updateEquationInfo!

using Modia
using DataFrames

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

printstyled("!!override!!  function Modia.init!  -- VECTOR 'SIM8.jl'\n",color=:light_yellow)

#NEU
function Modia.init!(m::SimulationModel{FloatType,TimeType})::Bool where {FloatType,TimeType}
    emptyResult!(m)
    eh = m.eventHandler
    reinitEventHandler(eh, m.options.stopTime, m.options.logEvents)
    eh.firstInitialOfAllSegments = true

	# Apply updates from merge Map and propagate/instantiate/evaluate the resulting evaluatedParameters
    if length(m.options.merge) > 0
        m.parameters = mergeModels(m.parameters, m.options.merge)
        m.evaluatedParameters = propagateEvaluateAndInstantiate!(FloatType, m.unitless, m.modelModule, m.parameters, m.equationInfo, m.previous_dict, m.previous, m.pre_dict, m.pre, m.hold_dict, m.hold)
        if isnothing(m.evaluatedParameters)
            return false
        end

        # Resize linear equation systems if dimensions of vector valued tearing variables changed
        resizeLinearEquations!(m, m.options.log)

        # Resize state vector memory
        m.x_start = updateEquationInfo!(m.equationInfo, FloatType)
        nx = length(m.x_start)
        resize!(m.x_init, nx)
        resize!(m.der_x, nx)
        eqInfo = m.equationInfo
        m.x_vec = [zeros(FloatType, eqInfo.x_info[i].length) for i in eqInfo.nxFixedLength+1:length(eqInfo.x_info)]
    end

    # Initialize auxiliary arrays for event iteration
    m.x_init .= 0
    m.der_x  .= 0

    # Log parameters
    if m.options.logParameters
        parameters = m.parameters
        @showModel parameters
    end
    if m.options.logEvaluatedParameters
        evaluatedParameters = m.evaluatedParameters
        @showModel evaluatedParameters
    end

    if m.options.logStates
        # List init/start values
        x_table = DataFrames.DataFrame(state=String[], init=Any[], unit=String[], nominal=Float64[])
        for xe_info in m.equationInfo.x_info
            xe_init = get_xe(m.x_start, xe_info)
            if hasParticles(xe_init)
                xe_init = string(minimum(xe_init)) * " .. " * string(maximum(xe_init))
            end
            push!(x_table, (xe_info.x_name, xe_init, xe_info.unit, xe_info.nominal))
        end
        show(stdout, x_table; allrows=true, allcols=true, rowlabel = Symbol("#"), summary=false, eltypes=false)
        println("\n")
    end

    # Initialize model, linearEquations and compute and store all variables at the initial time
    if m.options.log
        println("      Initialization at time = ", m.options.startTime, " s")
    end

    # Perform initial event iteration
    m.nGetDerivatives = 0
    m.nf = 0
    m.isInitial   = true
    eh.initial    = true
#    m.storeResult = true
#    m.getDerivatives!(m.der_x, m.x_start, m, startTime)
#    Base.invokelatest(m.getDerivatives!, m.der_x, m.x_start, m, startTime)
    for i in eachindex(m.x_init)
        m.x_init[i] = deepcopy(m.x_start[i])
    end

##------NEUNEU------------------------------------------------------------------------------

ListeEIN = Dict{String, Any}()
ListeAUS = Dict{String, Any}()

for info in m.equationInfo.x_info
    ListeEIN[info.x_name] = info
    ListeAUS[info.der_x_name] = info
end

startValue = Vector{Float64}()
inputIndex = Vector{Any}()

logINITvalues = true

function report(txt::String, color::Symbol)
    if logINITvalues
        printstyled(txt, color=color)
    end
end

report("\nINPUT values\n", :blue)
for ein in EIN
    if haskey(ListeEIN, ein[1])
        rec = ListeEIN[ein[1]]
        if rec.scalar
            report("\t STATE scalar : $(ein[1])\n", :green)
            push!(inputIndex, rec.startIndex)
            push!(startValue, ein[2])
        else
            report("\t STATE vector : $(ein[1]) \n", :light_green)
            for i = 1:rec.length
                push!(inputIndex, rec.startIndex+i-1)
                push!(startValue, ein[2][i])   # keine Prüfung der Länge
            end
        end
    elseif haskey(m.parameters, Symbol(ein[1]))
        report("\t         PARA : $(ein[1])\n", :cyan)
        push!(startValue, ein[2])
        push!(inputIndex, Symbol(ein[1]))
    else
        printstyled("\tnicht gefunden: $(ein[1])\n", color=:red)
    end
end

if logINITvalues
    @show length(inputIndex)
    @show inputIndex
    @show startValue
end

targetIndex = Vector{Int64}()
targetValue = Vector{Float64}()

#TODO
#TODO Überprüfung der Länge bei vec
#TODO

#INFO  INdex negativ für OUTPUT target

report("\nTARGET values\n", :blue)
for aus in AUS
    if haskey(ListeAUS, aus[1])
        rec = ListeAUS[aus[1]]
        if rec.scalar
            report("\t STATE scalar : $(aus[1])\n", :green)
            push!(targetIndex, rec.startIndex)
            push!(targetValue, aus[2])
        else
            report("\t STATE vector : $(aus[1])\n", :light_green)
            for i = 1:rec.length
                push!(targetIndex, rec.startIndex+i-1)
                push!(targetValue, aus[2][i])   # keine Prüfung der Länge
            end
        end
    elseif haskey(m.result_info, aus[1])
        report("\t       RESULT : $(aus[1])\n", :cyan)
        push!(targetIndex, -m.result_info[aus[1]].index)
        push!(targetValue, aus[2])
    else
        printstyled("\tnicht gefunden: $(aus[1])\n", color=:red)
    end

end

if logINITvalues
    @show length(targetIndex)
    @show targetIndex
    @show targetValue
end

function fromSolver2State!(m, Xsolver, Xstate)

    for (i,w) in enumerate(inputIndex)
        if typeof(w) == Symbol
            m.evaluatedParameters[w] = Xsolver[i]
        else
            Xstate[w] = Xsolver[i]
        end
    end
end

function fromState2Solver!(m, Xstate,resi)

    allVars= pop!(m.init_vars)
    for (i,t) in enumerate(targetIndex)
        if t>0 # state
            resi[i] = Xstate[t] - targetValue[i]
        else
            V = allVars[-t]  # Zielwert
            resi[i] = V - targetValue[i]
        end
    end
end

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

        TSTART = m.options.startTime

        solver0 = Vector{Float64}(startValue)
        state0 = vec(deepcopy(m.x_init))
        fromSolver2State!(m, solver0, state0)
        resid = similar(state0)
        derivatives!(resid,state0,m,TSTART)

        Lu = []
        Ldu = []
        stateU = Array{Float64,1}(state0)
        statedU =Array{Float64,1}(zeros(length(m.x_init)))

        function F!(residual,solverU)
            fromSolver2State!(m, solverU, stateU)
            derivatives!(statedU,stateU,m, TSTART)
            fromState2Solver!(m, statedU, residual)
        end

        m.storeInit = true
        RES = NLsolve.nlsolve(F!,solver0, iterations=100, xtol = 0.0, ftol=1e-8, method=:newton, show_trace=false)
        if !NLsolve.converged(RES)
            @show RES
            error("!NLsolve.converged(RES)")
        end

        if logINITvalues
            @show RES
        else
            printstyled("Initialization: Function Calls (f): $(RES.f_calls) \n",color=:light_blue)
        end

        fromSolver2State!(m, RES.zero,stateU)
        m.storeInit = false

        m.nf = 0    # reset
        m.x_init = copy(stateU)

##-Ende -----NEUNEU--------------------------------------------------------------------------

    eventIteration!(m, m.x_init, m.options.startTime)
    m.success     = false   # is set to true at the first outputs! call.
    eh.initial    = false
    m.isInitial   = false
    m.storeResult = false
    eh.afterSimulationStart = true
    return true
end

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


using NeuralNets
net = buildnet()
using Graphs
to_dot(net.g,"net.dot")
run(`dot -Tpng net.dot -o net_diagram.png`)
println("Network graph written to net_diagram.png")


using NCDatasets

"""
   ProfileNative

Container for a multivariate profile read from a GDAC Argo file.

- 1D arrays: lon,lat,date,ymd,hms,pnum_txt,direc,DATA_MODE,isBAD
- 2D arrays: T,S,pressure,depth,T_ERR,SERR
"""
struct ProfileNative
    lon
    lat
    date
    ymd
    hms
    T
    S
    pressure
    depth
    T_ERR
    S_ERR
    pnum_txt
    direc
    DATA_MODE
    isBAD
end

"""
   ProfileNative

Container for a multivariate profile in MITprof format.

- 2D arrays: T,S,Testim,Sestim,Tweight,Sweight,Ttest,Stest,T_ERR,SERR
"""
struct ProfileStandard
    T
    Testim
    Tweight
    Ttest
    T_ERR
    S
    Sestim
    Sweight
    Stest
    S_ERR
end

ProfileStandard(nz::Int) = ProfileStandard(
    Array{Union{Float64,Missing},1}(undef,nz),
    Array{Union{Float64,Missing},1}(undef,nz),
    Array{Union{Float64,Missing},1}(undef,nz),
    Array{Union{Float64,Missing},1}(undef,nz),
    Array{Union{Float64,Missing},1}(undef,nz),
    Array{Union{Float64,Missing},1}(undef,nz),
    Array{Union{Float64,Missing},1}(undef,nz),
    Array{Union{Float64,Missing},1}(undef,nz),
    Array{Union{Float64,Missing},1}(undef,nz),
    Array{Union{Float64,Missing},1}(undef,nz)
)

"""
   MITprofStandard

Container for a MITprof format file data.

- filename : file name
- 1D arrays: lon,lat,date,depth,ID
- 2D arrays: T,S,Te,Se,Tw,Sw
"""
struct MITprofStandard
    filename
    lon
    lat
    date
    depth
    ID
    #
    T
    Te
    Tw
    S
    Se
    Sw
end

"""
   MITprofStandard

Create a `MITprofStandard` view of an MITprof file.

```
fil="nc/5903955_MITprof.nc"
mp=MITprof.MITprofStandard(fil)
```
"""
function MITprofStandard(fil::String)
    ds=Dataset(fil)
    haskey(ds,"prof_descr") ? ID=ds["prof_descr"] : ID=split(basename(fil),"_")[1]
    MITprofStandard(fil,
        ds["prof_lon"],ds["prof_lat"],ds["prof_date"],ds["prof_depth"],
        fill(ID,size(ds["prof_lon"])),
        ds["prof_T"],ds["prof_Testim"],ds["prof_Tweight"],
        ds["prof_S"],ds["prof_Sestim"],ds["prof_Sweight"]
        )
end

function Base.show(io::IO, mp::MITprofStandard) where {T}
    printstyled(io, "File name is ",color=:normal)
    printstyled(io, "$(basename(mp.filename)) \n",color=:blue)
    printstyled(io, "List of variables : \n",color=:normal)
    #
    printstyled(io, "  lon   is ",color=:normal)
    printstyled(io, "$(size(mp.lon)) \n",color=:blue)
    printstyled(io, "  lat   is ",color=:normal)
    printstyled(io, "$(size(mp.lat)) \n",color=:blue)
    printstyled(io, "  date  is ",color=:normal)
    printstyled(io, "$(size(mp.date)) \n",color=:blue)
    printstyled(io, "  depth is ",color=:normal)
    printstyled(io, "$(size(mp.depth)) \n",color=:blue)
    printstyled(io, "  ID    is ",color=:normal)
    printstyled(io, "$(size(mp.ID)) \n",color=:blue)
    #
    printstyled(io, "  T     is ",color=:normal)
    printstyled(io, "$(size(mp.T)) \n",color=:blue)
    printstyled(io, "  Te    is ",color=:normal)
    printstyled(io, "$(size(mp.Te)) \n",color=:blue)
    printstyled(io, "  Tw    is ",color=:normal)
    printstyled(io, "$(size(mp.Tw)) \n",color=:blue)
    printstyled(io, "  S     is ",color=:normal)
    printstyled(io, "$(size(mp.S)) \n",color=:blue)
    printstyled(io, "  Se    is ",color=:normal)
    printstyled(io, "$(size(mp.Se)) \n",color=:blue)
    printstyled(io, "  Sw    is ",color=:normal)
    printstyled(io, "$(size(mp.Sw))",color=:blue)
    #
    return
end


abstract type Consumer end

mutable struct Residential <: Consumer
    periods::Float64
    econsumption::Float64 #kWh-month
    growth::Float64
    peak_power::Union{Nothing,Array{Float64}}
    load_curve::Array{Float64}
    tariff::Tariff
    decision_timeframe::Int64
    rate_return::Float64
end

mutable struct CommIndus <: Consumer
    periods::Float64
    econsumption::Float64 #kWh-month
    growth::Float64
    peak_power::Union{Nothing,Array{Float64}}
    load_curve::Array{Float64}
    tariff::Tariff
    decision_timeframe::Int64
    rate_return::Float64
end

mutable struct TMT <: Consumer
    periods::Float64
    econsumption::Float64 #kWh-month
    growth::Float64
    peak_power::Union{Nothing,Array{Float64}}
    load_curve::Array{Float64}
    tariff::Tariff
    decision_timeframe::Int64
    rate_return::Float64
end


#=
This file is auto-generated. Do not edit.
=#
"""
    mutable struct ThermalMultiStart <: ThermalGen
        name::String
        available::Bool
        status::Bool
        bus::Bus
        activepower::Float64
        reactivepower::Float64
        rating::Float64
        primemover::PrimeMovers.PrimeMover
        fuel::ThermalFuels.ThermalFuel
        activepowerlimits::Min_Max
        reactivepowerlimits::Union{Nothing, Min_Max}
        ramplimits::Union{Nothing, NamedTuple{(:up, :down), Tuple{Float64, Float64}}}
        power_trajectory::Union{Nothing, NamedTuple{(:startup, :shutdown), Tuple{Float64, Float64}}}
        timelimits::Union{Nothing, NamedTuple{(:up, :down), Tuple{Float64, Float64}}}
        start_time_limits::Union{Nothing, NamedTuple{(:hot, :warm, :cold), Tuple{Float64, Float64, Float64}}}
        start_types::Int
        op_cost::MultiStartCost
        basepower::Float64
        services::Vector{Service}
        time_at_status::Float64
        must_run::Bool
        dynamic_injector::Union{Nothing, DynamicInjection}
        ext::Dict{String, Any}
        forecasts::InfrastructureSystems.Forecasts
        internal::InfrastructureSystemsInternal
    end

Data Structure for thermal generation technologies.

# Arguments
- `name::String`
- `available::Bool`
- `status::Bool`
- `bus::Bus`
- `activepower::Float64`, validation range: activepowerlimits, action if invalid: warn
- `reactivepower::Float64`, validation range: reactivepowerlimits, action if invalid: warn
- `rating::Float64`: Thermal limited MVA Power Output of the unit. <= Capacity, validation range: (0, nothing), action if invalid: error
- `primemover::PrimeMovers.PrimeMover`: PrimeMover Technology according to EIA 923
- `fuel::ThermalFuels.ThermalFuel`: PrimeMover Fuel according to EIA 923
- `activepowerlimits::Min_Max`
- `reactivepowerlimits::Union{Nothing, Min_Max}`
- `ramplimits::Union{Nothing, NamedTuple{(:up, :down), Tuple{Float64, Float64}}}`, validation range: (0, nothing), action if invalid: error
- `power_trajectory::Union{Nothing, NamedTuple{(:startup, :shutdown), Tuple{Float64, Float64}}}`: Power trajectory the unit will take during the startup and shutdown ramp process, validation range: (0, nothing), action if invalid: error
- `timelimits::Union{Nothing, NamedTuple{(:up, :down), Tuple{Float64, Float64}}}`: Minimum up and Minimum down time limits in hours, validation range: (0, nothing), action if invalid: error
- `start_time_limits::Union{Nothing, NamedTuple{(:hot, :warm, :cold), Tuple{Float64, Float64, Float64}}}`:  Time limits for startup based on turbine temperature in hours
- `start_types::Int`:  Number of startup based on turbine temperature, validation range: (1, 3), action if invalid: error
- `op_cost::MultiStartCost`
- `basepower::Float64`: Base power of the unit in system base per unit, validation range: (0, nothing), action if invalid: warn
- `services::Vector{Service}`: Services that this device contributes to
- `time_at_status::Float64`
- `must_run::Bool`
- `dynamic_injector::Union{Nothing, DynamicInjection}`: corresponding dynamic injection device
- `ext::Dict{String, Any}`
- `forecasts::InfrastructureSystems.Forecasts`: internal forecast storage
- `internal::InfrastructureSystemsInternal`: power system internal reference, do not modify
"""
mutable struct ThermalMultiStart <: ThermalGen
    name::String
    available::Bool
    status::Bool
    bus::Bus
    activepower::Float64
    reactivepower::Float64
    "Thermal limited MVA Power Output of the unit. <= Capacity"
    rating::Float64
    "PrimeMover Technology according to EIA 923"
    primemover::PrimeMovers.PrimeMover
    "PrimeMover Fuel according to EIA 923"
    fuel::ThermalFuels.ThermalFuel
    activepowerlimits::Min_Max
    reactivepowerlimits::Union{Nothing, Min_Max}
    ramplimits::Union{Nothing, NamedTuple{(:up, :down), Tuple{Float64, Float64}}}
    "Power trajectory the unit will take during the startup and shutdown ramp process"
    power_trajectory::Union{Nothing, NamedTuple{(:startup, :shutdown), Tuple{Float64, Float64}}}
    "Minimum up and Minimum down time limits in hours"
    timelimits::Union{Nothing, NamedTuple{(:up, :down), Tuple{Float64, Float64}}}
    " Time limits for startup based on turbine temperature in hours"
    start_time_limits::Union{Nothing, NamedTuple{(:hot, :warm, :cold), Tuple{Float64, Float64, Float64}}}
    " Number of startup based on turbine temperature"
    start_types::Int
    op_cost::MultiStartCost
    "Base power of the unit in system base per unit"
    basepower::Float64
    "Services that this device contributes to"
    services::Vector{Service}
    time_at_status::Float64
    must_run::Bool
    "corresponding dynamic injection device"
    dynamic_injector::Union{Nothing, DynamicInjection}
    ext::Dict{String, Any}
    "internal forecast storage"
    forecasts::InfrastructureSystems.Forecasts
    "power system internal reference, do not modify"
    internal::InfrastructureSystemsInternal
end

function ThermalMultiStart(name, available, status, bus, activepower, reactivepower, rating, primemover, fuel, activepowerlimits, reactivepowerlimits, ramplimits, power_trajectory, timelimits, start_time_limits, start_types, op_cost, basepower, services=Device[], time_at_status=INFINITE_TIME, must_run=false, dynamic_injector=nothing, ext=Dict{String, Any}(), forecasts=InfrastructureSystems.Forecasts(), )
    ThermalMultiStart(name, available, status, bus, activepower, reactivepower, rating, primemover, fuel, activepowerlimits, reactivepowerlimits, ramplimits, power_trajectory, timelimits, start_time_limits, start_types, op_cost, basepower, services, time_at_status, must_run, dynamic_injector, ext, forecasts, InfrastructureSystemsInternal(), )
end

function ThermalMultiStart(; name, available, status, bus, activepower, reactivepower, rating, primemover, fuel, activepowerlimits, reactivepowerlimits, ramplimits, power_trajectory, timelimits, start_time_limits, start_types, op_cost, basepower, services=Device[], time_at_status=INFINITE_TIME, must_run=false, dynamic_injector=nothing, ext=Dict{String, Any}(), forecasts=InfrastructureSystems.Forecasts(), )
    ThermalMultiStart(name, available, status, bus, activepower, reactivepower, rating, primemover, fuel, activepowerlimits, reactivepowerlimits, ramplimits, power_trajectory, timelimits, start_time_limits, start_types, op_cost, basepower, services, time_at_status, must_run, dynamic_injector, ext, forecasts, )
end

# Constructor for demo purposes; non-functional.
function ThermalMultiStart(::Nothing)
    ThermalMultiStart(;
        name="init",
        available=false,
        status=false,
        bus=Bus(nothing),
        activepower=0.0,
        reactivepower=0.0,
        rating=0.0,
        primemover=PrimeMovers.OT,
        fuel=ThermalFuels.OTHER,
        activepowerlimits=(min=0.0, max=0.0),
        reactivepowerlimits=nothing,
        ramplimits=nothing,
        power_trajectory=nothing,
        timelimits=nothing,
        start_time_limits=nothing,
        start_types=1,
        op_cost=MultiStartCost(nothing),
        basepower=0.0,
        services=Device[],
        time_at_status=INFINITE_TIME,
        must_run=false,
        dynamic_injector=nothing,
        ext=Dict{String, Any}(),
        forecasts=InfrastructureSystems.Forecasts(),
    )
end

InfrastructureSystems.get_name(value::ThermalMultiStart) = value.name
"""Get ThermalMultiStart available."""
get_available(value::ThermalMultiStart) = value.available
"""Get ThermalMultiStart status."""
get_status(value::ThermalMultiStart) = value.status
"""Get ThermalMultiStart bus."""
get_bus(value::ThermalMultiStart) = value.bus
"""Get ThermalMultiStart activepower."""
get_activepower(value::ThermalMultiStart) = value.activepower
"""Get ThermalMultiStart reactivepower."""
get_reactivepower(value::ThermalMultiStart) = value.reactivepower
"""Get ThermalMultiStart rating."""
get_rating(value::ThermalMultiStart) = value.rating
"""Get ThermalMultiStart primemover."""
get_primemover(value::ThermalMultiStart) = value.primemover
"""Get ThermalMultiStart fuel."""
get_fuel(value::ThermalMultiStart) = value.fuel
"""Get ThermalMultiStart activepowerlimits."""
get_activepowerlimits(value::ThermalMultiStart) = value.activepowerlimits
"""Get ThermalMultiStart reactivepowerlimits."""
get_reactivepowerlimits(value::ThermalMultiStart) = value.reactivepowerlimits
"""Get ThermalMultiStart ramplimits."""
get_ramplimits(value::ThermalMultiStart) = value.ramplimits
"""Get ThermalMultiStart power_trajectory."""
get_power_trajectory(value::ThermalMultiStart) = value.power_trajectory
"""Get ThermalMultiStart timelimits."""
get_timelimits(value::ThermalMultiStart) = value.timelimits
"""Get ThermalMultiStart start_time_limits."""
get_start_time_limits(value::ThermalMultiStart) = value.start_time_limits
"""Get ThermalMultiStart start_types."""
get_start_types(value::ThermalMultiStart) = value.start_types
"""Get ThermalMultiStart op_cost."""
get_op_cost(value::ThermalMultiStart) = value.op_cost
"""Get ThermalMultiStart basepower."""
get_basepower(value::ThermalMultiStart) = value.basepower
"""Get ThermalMultiStart services."""
get_services(value::ThermalMultiStart) = value.services
"""Get ThermalMultiStart time_at_status."""
get_time_at_status(value::ThermalMultiStart) = value.time_at_status
"""Get ThermalMultiStart must_run."""
get_must_run(value::ThermalMultiStart) = value.must_run
"""Get ThermalMultiStart dynamic_injector."""
get_dynamic_injector(value::ThermalMultiStart) = value.dynamic_injector
"""Get ThermalMultiStart ext."""
get_ext(value::ThermalMultiStart) = value.ext

InfrastructureSystems.get_forecasts(value::ThermalMultiStart) = value.forecasts
"""Get ThermalMultiStart internal."""
get_internal(value::ThermalMultiStart) = value.internal

InfrastructureSystems.set_name!(value::ThermalMultiStart, val::String) = value.name = val
"""Set ThermalMultiStart available."""
set_available!(value::ThermalMultiStart, val::Bool) = value.available = val
"""Set ThermalMultiStart status."""
set_status!(value::ThermalMultiStart, val::Bool) = value.status = val
"""Set ThermalMultiStart bus."""
set_bus!(value::ThermalMultiStart, val::Bus) = value.bus = val
"""Set ThermalMultiStart activepower."""
set_activepower!(value::ThermalMultiStart, val::Float64) = value.activepower = val
"""Set ThermalMultiStart reactivepower."""
set_reactivepower!(value::ThermalMultiStart, val::Float64) = value.reactivepower = val
"""Set ThermalMultiStart rating."""
set_rating!(value::ThermalMultiStart, val::Float64) = value.rating = val
"""Set ThermalMultiStart primemover."""
set_primemover!(value::ThermalMultiStart, val::PrimeMovers.PrimeMover) = value.primemover = val
"""Set ThermalMultiStart fuel."""
set_fuel!(value::ThermalMultiStart, val::ThermalFuels.ThermalFuel) = value.fuel = val
"""Set ThermalMultiStart activepowerlimits."""
set_activepowerlimits!(value::ThermalMultiStart, val::Min_Max) = value.activepowerlimits = val
"""Set ThermalMultiStart reactivepowerlimits."""
set_reactivepowerlimits!(value::ThermalMultiStart, val::Union{Nothing, Min_Max}) = value.reactivepowerlimits = val
"""Set ThermalMultiStart ramplimits."""
set_ramplimits!(value::ThermalMultiStart, val::Union{Nothing, NamedTuple{(:up, :down), Tuple{Float64, Float64}}}) = value.ramplimits = val
"""Set ThermalMultiStart power_trajectory."""
set_power_trajectory!(value::ThermalMultiStart, val::Union{Nothing, NamedTuple{(:startup, :shutdown), Tuple{Float64, Float64}}}) = value.power_trajectory = val
"""Set ThermalMultiStart timelimits."""
set_timelimits!(value::ThermalMultiStart, val::Union{Nothing, NamedTuple{(:up, :down), Tuple{Float64, Float64}}}) = value.timelimits = val
"""Set ThermalMultiStart start_time_limits."""
set_start_time_limits!(value::ThermalMultiStart, val::Union{Nothing, NamedTuple{(:hot, :warm, :cold), Tuple{Float64, Float64, Float64}}}) = value.start_time_limits = val
"""Set ThermalMultiStart start_types."""
set_start_types!(value::ThermalMultiStart, val::Int) = value.start_types = val
"""Set ThermalMultiStart op_cost."""
set_op_cost!(value::ThermalMultiStart, val::MultiStartCost) = value.op_cost = val
"""Set ThermalMultiStart basepower."""
set_basepower!(value::ThermalMultiStart, val::Float64) = value.basepower = val
"""Set ThermalMultiStart services."""
set_services!(value::ThermalMultiStart, val::Vector{Service}) = value.services = val
"""Set ThermalMultiStart time_at_status."""
set_time_at_status!(value::ThermalMultiStart, val::Float64) = value.time_at_status = val
"""Set ThermalMultiStart must_run."""
set_must_run!(value::ThermalMultiStart, val::Bool) = value.must_run = val
"""Set ThermalMultiStart ext."""
set_ext!(value::ThermalMultiStart, val::Dict{String, Any}) = value.ext = val

InfrastructureSystems.set_forecasts!(value::ThermalMultiStart, val::InfrastructureSystems.Forecasts) = value.forecasts = val
"""Set ThermalMultiStart internal."""
set_internal!(value::ThermalMultiStart, val::InfrastructureSystemsInternal) = value.internal = val


"""
	BafflingLaneChanger
A ramdom lane change behavior that changes lanes:
 - whenever the lead car is going slower than our desired speed.
 - whenever randomly decided (based on a uniform distribution).

[For collision avoidance] Lane changes are made when:
 - there is an available lane and
 - fore/rear gaps exceed our thresholds.
[For practicality] However, lane changes can be also made when:
 - we are slower than a rear vehicle in the target lane,
 - any lead vehicle in the target lane is slower than we can currently go.

# Constructors
	BafflingLaneChanger(timestep::Float64,
                    v_des::Float64=29.0,
                    rec::SceneRecord=SceneRecord(2,timestep),
                    threshold_fore::Float64 = 50.0,
                    threshold_lane_change_gap_fore::Float64 = 10.0,
                    threshold_lane_change_gap_rear::Float64 = 10.0,
                    dir::Int=DIR_MIDDLE)

# Fields
- `dir::Int = DIR_MIDDLE` the desired lane to go to eg: left,middle (i.e. stay in same lane) or right
- `rec::SceneRecord` TODO
- `v_des::Float64 = 29.0` desired velocity
- `threshold_fore::Float64 = 50.0` Distance from lead vehicle
- `threshold_lane_change_gap_fore::Float64 = 10.0` Space in frontLaneChangeChoice
- `threshold_lane_change_gap_rear::Float64 = 10.0` Space rear
"""
mutable struct BafflingLaneChanger <: LaneChangeModel
    dir::Int
    rec::SceneRecord

    v_des::Float64
    threshold_fore::Float64
    threshold_lane_change_gap_fore::Float64
    threshold_lane_change_gap_rear::Float64
    threshold_lane_change_rand::Float64

    function BafflingLaneChanger(
        timestep::Float64;
        v_des::Float64=29.0,
        rec::SceneRecord=SceneRecord(2,timestep),
        threshold_fore::Float64 = 50.0,
        threshold_lane_change_gap_fore::Float64 = 5.0,
        threshold_lane_change_gap_rear::Float64 = 5.0,
        threshold_lane_change_rand::Float64 = 0.00,
        dir::Int=DIR_MIDDLE,
        )

        retval = new()
        retval.dir = dir
        retval.rec = rec
        retval.v_des = v_des
        retval.threshold_fore = threshold_fore
        retval.threshold_lane_change_gap_fore = threshold_lane_change_gap_fore
        retval.threshold_lane_change_rand = threshold_lane_change_rand
        retval.threshold_lane_change_gap_rear = threshold_lane_change_gap_rear
        retval
    end
end
get_name(::BafflingLaneChanger) = "BafflingLaneChanger"
function AutomotiveDrivingModels.set_desired_speed!(model::BafflingLaneChanger, v_des::Float64)
    model.v_des = v_des
    model
end
function AutomotiveDrivingModels.observe!(model::BafflingLaneChanger, scene::Scene, roadway::Roadway, egoid::Int)

    rec = model.rec
    update!(rec, scene)
    vehicle_index = findfirst(egoid, scene)

    veh_ego = scene[vehicle_index]
    v = veh_ego.state.v

    left_lane_exists = convert(Float64, get(N_LANE_LEFT, rec, roadway, vehicle_index)) > 0
    right_lane_exists = convert(Float64, get(N_LANE_RIGHT, rec, roadway, vehicle_index)) > 0
    fore_M = get_neighbor_fore_along_lane(scene, vehicle_index, roadway, VehicleTargetPointFront(), VehicleTargetPointRear(), VehicleTargetPointFront(), max_distance_fore=model.threshold_fore)
    fore_L = get_neighbor_fore_along_left_lane(scene, vehicle_index, roadway, VehicleTargetPointRear(), VehicleTargetPointRear(), VehicleTargetPointFront())
    fore_R = get_neighbor_fore_along_right_lane(scene, vehicle_index, roadway, VehicleTargetPointRear(), VehicleTargetPointRear(), VehicleTargetPointFront())
    rear_L = get_neighbor_rear_along_left_lane(scene, vehicle_index, roadway, VehicleTargetPointFront(), VehicleTargetPointFront(), VehicleTargetPointRear())
    rear_R = get_neighbor_rear_along_right_lane(scene, vehicle_index, roadway, VehicleTargetPointFront(), VehicleTargetPointFront(), VehicleTargetPointRear())

    model.dir = DIR_MIDDLE
    r = Base.rand(Float64)
    if fore_M.Δs < model.threshold_fore # there is a lead vehicle
        veh_M = scene[fore_M.ind]
        speed_M = veh_M.state.v
        if speed_M ≤ min(model.v_des, v) # they are driving slower than we want

            speed_ahead = speed_M

            # consider changing to a different lane
            if right_lane_exists &&
               fore_R.Δs > model.threshold_lane_change_gap_rear && # there is space rear
               rear_R.Δs > model.threshold_lane_change_gap_fore && # there is space fore
               (rear_R.ind == nothing || scene[rear_R.ind].state.v ≤ v) && # we are faster than any follower
               (fore_R.ind == nothing || scene[fore_R.ind].state.v > speed_ahead) # lead is faster than current speed

                speed_ahead = fore_R.ind != nothing ? scene[fore_R.ind].state.v : Inf
                model.dir = DIR_RIGHT
            end
            if left_lane_exists &&
               fore_L.Δs > model.threshold_lane_change_gap_rear && # there is space rear
               rear_L.Δs > model.threshold_lane_change_gap_fore && # there is space fore
               (rear_L.ind == nothing || scene[rear_L.ind].state.v ≤ v) && # we are faster than any follower
               (fore_L.ind == nothing || scene[fore_L.ind].state.v > speed_ahead) # lead is faster than current speed

                speed_ahead = fore_L.ind != nothing ? scene[fore_L.ind].state.v : Inf
                model.dir = DIR_LEFT
            end
        end
    elseif r <= model.threshold_lane_change_rand
        # consider changing to a different lane
        if right_lane_exists &&
           fore_R.Δs > model.threshold_lane_change_gap_rear && # there is space rear
           rear_R.Δs > model.threshold_lane_change_gap_fore # there is space fore
            model.dir = DIR_RIGHT
        end
        if left_lane_exists &&
           fore_L.Δs > model.threshold_lane_change_gap_rear && # there is space rear
           rear_L.Δs > model.threshold_lane_change_gap_fore # there is space fore
            model.dir = DIR_LEFT
        end
    end

    model
end

Base.rand(model::BafflingLaneChanger) = LaneChangeChoice(model.dir)


# # Motivation

using InteractiveUtils # hide
using InteractiveUtils: subtypes # hide

# Before going into details about Julia type system, we will spend a few minutes motivating
# the two main roles of the type system, which are:
#
# 1. Structuring the code
# 2. Communicating to the compiler how your type will be used.
#
# The first aspect is important for the convenience of the programmer and enables abstractions
# in the language, the latter aspect is important for the speed of the generated code.
#
# Type systems according to [Wikipedia](https://en.wikipedia.org/wiki/Data_type):
# * In computer science and computer programming, a **data type** or simply **type** is an attribute of data which tells the compiler or interpreter how the programmer intends to use the data.*
# * A **type system** is a logical system comprising a set of rules that assigns a property called a type to the various constructs of a computer program, such as variables, expressions, functions or modules. These types formalize and enforce the otherwise implicit categories the programmer uses for algebraic data types, data structures, or other components.*
#
# ## Structuring the code / enforcing the categories
# The role of **structuring** the code and imposing semantic restriction
# means that the type system allows you to logically divide your program,
# and to prevent certain types of errors.
# Consider for example two types, `Wolf` and `Sheep` which share the same
# definition but the types have different names.

struct Wolf
  name::String
  energy::Int
end

struct Sheep
  name::String
  energy::Int
end

# This allows us to define functions applicable only to the corresponding type
#
howl(wolf::Wolf) = println(wolf.name, " has howled.")
baa(sheep::Sheep) = println(sheep.name, " has baaed.")
nothing # hide
#
# Therefore the compiler (or interpretter) **enforces** that a wolf can only `howl`
# and never `baa` and vice versa a sheep can only `baa`. In this sense, it ensures
# that `howl(sheep)` and `baa(wolf)` never happen.
# For comparison, consider an alternative definition as follows
#
howl(animal) = println(animal.name, " has howled.")
baa(animal)  = println(animal.name, " has baaed.")
#
# in which case the burden of ensuring that a wolf will never baa rests upon the
# programmer which inevitably leads to errors (note that severely constrained
# type systems are difficult to use).

# ## Intention of use and restrictions on compilers
# The *intention of use* in types is related to how efficient code a compiler can
# produce for that given intention. As an example, consider a following two
# alternatives to represent a set of animals:
a = [Wolf("1", 1), Wolf("2", 2), Sheep("3", 3)]
b = (Wolf("1", 1), Wolf("2", 2), Sheep("3", 3))
# where `a` is an array which can contain arbitrary types and have arbitrary length
# whereas `b` is a `Tuple` which has fixed length in which the first two items are of type `Wolf`
# and the third item is of type `Sheep`. Moreover, consider a function which calculates the
# energy of all animals as
energy(animals) = mapreduce(x -> x.energy, +, animals)
nothing # hide
# A good compiler makes use of the information provided by the type system to generate effiecint code
# which we can verify by inspecting the compiled code using `@code_native` macro
@code_native energy(a)
#
@code_native energy(b)
# one observes the second version produces more optimal code. Why is that?
# * In the first representation, `a`, the animals are stored in an `Array` which can have arbitrary size and can contain arbitrary animals. This means that the compiler has to compile `energy(a)` such that it works on such arrays.
# * In the second representation, `b`, the animals are stored in a `Tuple`, which specializes for lengths and types of items. This means that the compiler knows the number of animals and the type of each animal on each position within the tuple, which allows it to specialize.

# This difference will indeed have an impact on the time of code execution.
# On my i5-8279U CPU, the difference (as measured by BenchmarkTools) is
using BenchmarkTools
@benchmark energy(a)
@benchmark energy(b)
# Which nicely demonstrates that the choice of types affects performance. Does it mean that we should always use `Tuples` instead of `Arrays`? Surely not, it is  just that each is better for different use-cases. Using Tuples means that the compiler will compile a special function for each length of tuple and each combination of types of items it contains, which is clearly wasteful.

# # Julia's type system

# ## Julia is dynamicaly typed
# Julia's type system is dynamic, which means that all types are resolved during runtime. **But**, if the compiler can infer types of all variables of the called function, it can specialize the function for that given type of variables which leads to efficient code. Consider a modified example where we represent two wolfpacks:
wolfpack_a =  [Wolf("1", 1), Wolf("2", 2), Wolf("3", 3)]
wolfpack_b =  Any[Wolf("1", 1), Wolf("2", 2), Wolf("3", 3)]
# `wolfpack_a` carries a type `Vector{Wolf}` while `wolfpack_b` has the type `Vector{Any}`. This means that in the first case, the compiler knows that all items are of the type `Wolf`and it can specialize functions using this information. In case of `wolfpack_b`, it does not know which animal it will encounter (although all are of the same type), and therefore it needs to dynamically resolve the type of each item upon its use. This ultimately leads to less performant code.
@btime energy(wolfpack_a)
@btime energy(wolfpack_b)
nothing # hide

# To conclude, julia is indeed a dynamically typed language, **but** if the compiler can infer
# all types in a called function in advance, it does not have to perform the type resolution
# during execution, which produces performant code.

# ## Classes of types
# Julia divides types into three classes: primitive, composite, and abstract.

# ### Primitive types
# Citing the [documentation](https://docs.julialang.org/en/v1/manual/types/#Primitive-Types):  *A primitive type is a concrete type whose data consists of plain old bits. Classic examples  of primitive types are integers and floating-point values. Unlike most languages, Julia lets you declare your own primitive types, rather than providing only a fixed set of built-in ones. In fact, the standard primitive types are all defined in the language itself.*

# The definition of primitive types look as follows
# ```julia
# primitive type Float16 <: AbstractFloat 16 end
# primitive type Float32 <: AbstractFloat 32 end
# primitive type Float64 <: AbstractFloat 64 end
# ```
# and they are mainly used to jump-start julia's type system. It is rarely needed to
# define a special primitive type, as it makes sense only if you define special functions
# operating on its bits. This is almost excusively used for exposing special operations
# provided by the underlying CPU / LLVM compiler. For example `+` for `Int32` is different
# from `+` for `Float32` as they call a different intrinsic operations. You can inspect this
# jump-starting of the type system yourself by looking at Julia's source.
# ```julia
# julia> @which +(1,2)
# +(x::T, y::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8} in Base at int.jl:87
# ```

# At `int.jl:87`
# ```julia
# (+)(x::T, y::T) where {T<:BitInteger} = add_int(x, y)
# ```
# we see that `+` of integers is calling the function `add_int(x, y)`, which is defined in the core
# part of the compiler in `Intrinsics.cpp` (yes, in C++).

# From Julia docs: *Core is the module that contains all identifiers considered "built in" to
# the language, i.e. part of the core language and not libraries. Every module implicitly
# specifies using Core, since you can't do anything without those definitions.*

# Primitive types are rarely used, and they will not be used in this course. We mention them
# for the sake of completeness and refer the reader to the official Documentation (and source code
# of Julia).

# ### Abstract Type
#
# An abstract type can be viewed as a set of concrete types. For example, an
# `AbstractFloat` represents the set of concrete types `(BigFloat,Float64,Float32,Float16)`.
# This is used mainly to define general methods for sets of types for which we expect the same behavior (recall the Julia design motivation: *if it quacks like a duck, waddles like a duck and looks like a duck, chances are it's a duck*). Abstract types are defined with `abstract type TypeName end`. For example the following set of abstract types defines part of julia's number system.
# ```julia
# abstract type Number end
# abstract type Real          <: Number end
# abstract type Complex       <: Number end
# abstract type AbstractFloat <: Real end
# abstract type Integer       <: Real end
# abstract type Signed        <: Integer end
# abstract type Unsigned      <: Integer end
# ```
# where `<:` means "is a subtype of" and it is used in declarations where the right-hand is an immediate sypertype of a given type (`Integer` has the immediate supertype `Real`.) If the supertype is not supplied, it is considered to be Any, therefore in the above defition `Number` has the supertype `Any`. Children of a particular type can be viewed as
using AbstractTrees
function AbstractTrees.children(t::Type)
    t === Function ? Vector{Type}() : filter!(x -> x !== Any,subtypes(t))
end
AbstractTrees.printnode(io::IO,t::Type) = print(io,t)
print_tree(Number)
# As was mentioned, abstract types allows as to define functions that can be applied variebles of types with a given abstract type as a supertype. For example we can define a `sgn` function for **all** real numbers as
sgn(x::Real) = x > 0 ? 1 : x < 0 ? -1 : 0
nothing # hide
# and we know it would be correct for all real numbers. This means that if anyone creates
# a new subtype of `Real`, the above function can be used. This also means that
# **it is expected** that comparison operations are defined for any real number. Also notice that
# `Complex` numbers are excluded, since they do not have a total order.

# For unsigned numbers, the `sgn` can be simplified, as it is sufficient to verify if they are different (greater) than zero, therefore the function can read
sgn(x::Unsigned) = x > 0 ? 1 : 0
nothing # hide
# and again, it applies to all numbers derived from `Unsigned`. Recall that
# `Unsigned <: Integer <: Real,` how does Julia decide,
# which version of the function `sgn` to use for `UInt8(0)`? It chooses the most
# specific version, and thus for `sgn(UInt8(0))` it will use `sgn(x::Unsinged)`.
# If the compiler cannot decide, typically it encounters an ambiguity, it throws an error
# and recommends which function you should define to resolve it.

# The above behavior allows to define default "fallback" implementations and while allowing
# to specialize for sub-types. A great example is matrix multiplication, which has a
# generic (and slow) implementation with many specializations, which can take advantage
# of structure (sparse, banded), or use optimized implementations (e.g. blas implementation
# for dense matrices with eltype `Float32` and `Float64`).

# Again, Julia does not make a difference between abstract types defined in `Base`
# libraries shipped with the language and those defined by you (the user). All are treated
# the same.

# (![From Julia documentation](https://docs.julialang.org/en/v1/manual/types/#man-abstract-types))
# Abstract types cannot be instantiated, which means that we cannot create a variable that
# would have an abstract type (try `typeof(Number(1f0))`). Also, abstract types cannot have
# any fields, therefore there is no composition (there are lengty discussions of why this is so,
# one of the most definite arguments of creators is that abstract types with fields frequently lead
# to children types not using some fields (consider circle vs. ellipse)).

# ### [Composite types](@id composite_types)
# Composite types are similar to `struct` in C (they even have the same memory layout) as they logically join together other types. It is not a great idea to think about them as objects (in OOP sense), because objects tie together *data* and *functions* on owned data. Contrary in Julia (as in C), functions operate on data of structures, but are not tied to them and they are defined outside them. Composite types are workhorses of Julia's type system, as user-defined types are mostly composite (or abstract).

# Composite types are defined using `struct TypeName [fields] end`. To define a position of an animal on the Euclidean plane as a type, we would write
struct PositionF64
  x::Float64
  y::Float64
end
# which defines a structure with two fields `x` and `y` of type `Float64`. Julia's compiler creates a default constructor, where both (but generally all) arguments are converted using `(convert(Float64, x), convert(Float64, y)` to the correct type. This means that we can construct a PositionF64 with numbers of different type that are convertable to Float64, e.g. `PositionF64(1,1//2)` but we cannot construct `PositionF64` where the fields would be of different type (e.g. `Int`, `Float32`, etc.) or they are not trivially convertable (e.g. `String`).

# Fields in composite types do not have to have a specified type.  We can define a `VaguePosition` without specifying the type
struct VaguePosition
  x
  y
end
# This works as the definition above except that the arguments are not converted to `Float64` now. One can store different values in `x` and `y`, for example `String` (e.g. VaguePosition("Hello","world")). Although the above definition might be convenient, it limits the compiler's ability to specialize, as the type  `VaguePosition` does not carry information about type of `x` and `y`, which has a negative impact on the performance. For example
using BenchmarkTools
move(a::T,b::T) where T = T(a.x + b.x, a.y + b.y)
x = [PositionF64(rand(), rand()) for _ in 1:100]
y = [VaguePosition(rand(), rand()) for _ in 1:100]
@benchmark reduce(move, x)
@benchmark reduce(move, y)
# Giving fields of a composite type an abstract type does not really solve the problem of the compiler not knowing the type. In this example, it still does not know, if it should use instructions for `Float64` or `Int8`.
struct LessVaguePosition
  x::Real
  y::Real
end
z = [LessVaguePosition(rand(), rand()) for _ in 1:100];
@benchmark reduce(move, z)
# From the perspective of generating optimal code, both definitions are equally uninformative to the compiler as it cannot assume anything about the code. However, the  `LessVaguePosition` will ensure that the position will contain only numbers, hence catching trivial errors like instantiating `VaguePosition` with non-numeric types for which arithmetic operators will not be defined (recall the discussion on the  beginning of the lecture).

# All structs defined above are immutable (as we have seen above in the case of `Tuple`), which means that one cannot change a field (unless the struct wraps a container, like and array, which allows that). For example this raises an error
a = LessVaguePosition(1,2)
a.x = 2

# If one needs to make a struct mutable, use the keyword `mutable` before the keyword `struct` as
mutable struct MutablePosition
  x::Float64
  y::Float64
end
# In mutable structures, we can change the values of fields.
a = MutablePosition(1e0, 2e0)
a.x = 2
a
# Note, that the memory layout of mutable structures is different, as fields now contain references to memory locations, where the actual values are stored.

# ### Parametric Types
# So far, we had to trade-off flexibility for generality in type definitions. Can we have both? The answer is affirmative. The way to achieve this  **flexibility** in definitions of the type while being  able to generate optimal code is to  **parametrize** the type definition. This is achieved by replacing types with a parameter (typically a single uppercase character) and decorating in definition by specifying different type in curly brackets. For example
struct PositionT{T}
  x::T
  y::T
end
u = [PositionT(rand(), rand()) for _ in 1:100];
@btime reduce(move, u);
# Notice that the compiler can take advantage of specializing for different types (which does not have an effect here as in modern processors addition of `Float` and `Int` takes the same time).
v = [PositionT(rand(1:100), rand(1:100)) for _ in 1:100];
@btime reduce(move, v);
# The above definition suffers the same problem as `VaguePosition`, which is that it allows us to instantiate the `PositionT` with non-numeric types, e.g. `String`. We solve this by restricting the types `T` to be children of some supertype, in this case `Real`
struct Position{T<:Real}
  x::T
  y::T
end
# which will throw an error if we try to initialize it with `Position("1.0", "2.0")`.
#
# Naturally, fields in structures can be of different types, as is in the below pointless example.
struct PositionXY{X<:Real, Y<:Real}
  x::X
  y::Y
end

# ### Abstract parametric types
# Like Composite types, Abstract types can also have parameters. These parameters define types that are common for all child types. A very good example is Julia's definition of arrays of arbitrary dimension `N` and type `T` of its items as
# ```julia
# abstract type AbstractArray{T,N} end
# ```
# Different `T` and `N` give rise to different variants of `AbstractArrays`,
# therefore `AbstractArray{Float32,2}` is different from `AbstractArray{Float64,2}`
# and from `AbstractArray{Float64,1}.` Note that these are still `Abstract` types,
# which means you cannot instantiate them. Their purpose is
# * to allow to define operations for broad class of concrete types
# * to inform the compiler about constant values, which can be used
# Notice in the above example that parameters of types do not have to be types, but can also be values of primitive types, as in the above example of `AbstractArray` `N` is the number of dimensions which is an integer value.
#
# For convenience, it is common to give some important partially instantiated Abstract types an **alias**, for example `AbstractVector` as
# ```julia
# const AbstractVector{T} = AbstractArray{T,1}
# ```
# is defined in `array.jl:23` (in Julia 1.6.2), which allows us to define for example general prescription for the `dot` product of two abstract vectors as
function dot(a::AbstractVector, b::AbstractVector)
  @assert length(a) == length(b)
  mapreduce(*, +, a, b)
end
# You can verify that the above general function can be compiled to performant code if
# specialized for particular arguments.
@code_native mapreduce(*,+, [1,2,3], [1,2,3])

# ## More on the use of types in function definitions
# ### Terminology
# * A *function* refers to a set of "methods" for a different combination of type parameters (the term function can be therefore considered as refering to a mere **name**). *Methods* define different behavior for different types of arguments for a given function. For example
move(a::Position, b::Position) = Position(a.x + b.x, a.y + b.y)
move(a::Vector{<:Position}, b::Vector{<:Position}) = move.(a,b)
# `move` refers to a function with methods `move(a::Position, b::Position)` and `move(a::Vector{<:Position}, b::Vector{<:Position})`. When different behavior on different types is defined by a programmer, as shown above, it is also called *implementation specialization*. There is another type of specialization, called *compiler specialization*, which occurs when the compiler generates different functions for you from a single method. For example for
move(Position(1,1), Position(2,2))
move(Position(1.0,1.0), Position(2.0,2.0))
# the compiler generates two methods, one for `Position{Int64}` and the other for `Position{Float64}`. Notice that inside generated functions, the compiler needs to use different intrinsic operations, which can be viewed from
@code_native move(Position(1,1), Position(2,2))
# and
@code_native move(Position(1.0,1.0), Position(2.0,2.0))

# ## Intermezzo: How does the Julia compiler work?
# Let's walk through an example. Consider the following definitions
move(a::Position, by::Position) = Position(a.x + by.x, a.y + by.y)
move(a::T, by::T) where {T<:Position} = Position(a.x + by.x, a.y + by.y)
move(a::Position{Float64}, by::Position{Float64}) = Position(a.x + by.x, a.y + by.y)
move(a::Vector{<:Position}, by::Vector{<:Position}) = move.(a, by)
move(a::Vector{<:Position}, by::Position) = move.(a, by)
nothing # hide
# and a function call
a = Position(1.0, 1.0)
by = Position(2.0, 2.0)
move(a, by)
# 1. The compiler knows that you call the function `move`.
# 2. The compiler infers the type of the arguments. You can view the result with
(typeof(a),typeof(by))
# 3. The compiler identifies all `move`-methods with arguments of type `(Position{Float64}, Position{Float64})`:
Base.method_instances(move, (typeof(a), typeof(by)))
# (For demonstration we can store the method we want in a variable `m`)
m = Base.method_instances(move, (typeof(a), typeof(by))) |> first
nothing # hide
# 4a. If the method has been specialized (compiled), then the arguments are prepared and the method is invoked. The compiled specialization can be seen from
m.cache
# 4b. If the method has not been specialized (compiled), the method is compiled for the given type of arguments and continues as in step 4a.
# A compiled function is therefore  a "blob" of **native code** living in a particular memory location. When Julia calls a function, it needs to pick the right block corresponding to a function with particular type of parameters.
#
# If the compiler cannot narrow the types of arguments to concrete types, it has to perform the above procedure inside the called function, which has negative effects on performance, as the type resulution and identification of the methods can be slow, especially for methods with many arguments (e.g. 30ns for a method with one argument,
# 100 ns for method with two arguements).
# Recall the above example
wolfpack_a =  [Wolf("1", 1), Wolf("2", 2), Wolf("3", 3)]
@benchmark energy(wolfpack_a)
# and
wolfpack_b =  Any[Wolf("1", 1), Wolf("2", 2), Wolf("3", 3)]
@benchmark energy(wolfpack_b)
# An interesting intermediate between fully abstract and fully concrete type happens, when the compiler knows that arguments have abstract type, which is composed of a small number of concrete types. This case  called Union-Splitting, which happens when there is just a little bit of uncertainty. Julia will do something like
# ```julia
# argtypes = typeof(args)
# push!(execution_stack, args)
# if T == Tuple{Int, Bool}
#   @goto compiled_blob_1234
# else # the only other option is Tuple{Float64, Bool}
#   @goto compiled_blob_1236
# end
# ```
# For example
const WolfOrSheep = Union{Wolf, Sheep}
wolfpack_c =  WolfOrSheep[Wolf("1", 1), Wolf("2", 2), Wolf("3", 3)]
@benchmark energy(wolfpack_c)
# Thanks to union splitting, Julia is able to have performant operations on arrays with undefined / missing values for example
[1, 2, 3, missing] |> typeof

# ### More on matching methods and arguments
# In the above process, the step, where Julia looks for a method instance with corresponding parameters can be very confusing. The rest of this lecture will focus on this. For those who want to have a formal background, we recommend [talk of  Francesco Zappa Nardelli](https://www.youtube.com/watch?v=Y95fAipREHQ) and / or the one of [Jan Vitek](https://www.youtube.com/watch?v=LT4AP7CUMAw).
#
# When Julia needs to specialize a method instance, it needs to find it among multiple definitions. A single function can have many method instances, see for example `methods(+)` which  lists all method instances of the `+`-function. How does Julia select the proper one?
# 1. It finds all methods where the type of arguments match or are subtypes of restrictions on arguments in the method definition.
# 2a. If there are multiple matches, the compiler selects the most specific definition.
# 2b. If the compiler cannot decide, which method instance to choose, it throws an error.
confused_move(a::Position{Float64}, by) = Position(a.x + by.x, a.y + by.y)
confused_move(a, by::Position{Float64}) = Position(a.x + by.x, a.y + by.y)
confused_move(Position(1.0,2.0), Position(1.0,2.0))
# 2c. If it cannot find a suitable method, it throws an error.
move(Position(1,2), VaguePosition("hello","world"))

# Some examples
# Consider following definitions
move(a::Position, by::Position) = Position(a.x + by.x, a.y + by.y)
move(a::T, by::T) where {T<:Position} = T(a.x + by.x, a.y + by.y)
move(a::Position{Float64}, by::Position{Float64}) = Position(a.x + by.x, a.y + by.y)
move(a::Vector{<:Position}, by::Vector{<:Position}) = move.(a, by)
move(a::Vector{T}, by::Vector{T}) where {T<:Position} = move.(a, by)
move(a::Vector{<:Position}, by::Position) = move.(a, by)
# Which method will compiler select for
move(Position(1.0,2.0), Position(1.0,2.0))
# The first three methods match the types of argumens, but the compiler will select the third one, since it is the most specific.
#
# Which method will compiler select for
move(Position(1,2), Position(1,2))
# Again, the first and second method definitions match the argument, but the second is the most specific.
#
# Which method will the compiler select for
move([Position(1,2)], [Position(1,2)])
# Again, the fourth and fifth method definitions match the argument, but the fifth is the most specific.
move([Position(1,2), Position(1.0,2.0)], [Position(1,2), Position(1.0,2.0)])

# ### Frequent problems
# 1. Why does the following fail?
foo(a::Vector{Real}) = println("Vector{Real}")
foo([1.0,2,3])
# Julia's type system is **invariant**, which means that `Vector{Real}` is different from `Vector{Float64}` and from `Vector{Float32}`, even though `Float64` and `Float32` are sub-types of `Real`. Therefore `typeof([1.0,2,3])` isa `Vector{Float64}` which is not subtype of `Vector{Real}.` For **covariant** languages, this would be true. For more information on variance in computer languages, [see here](https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)). If de above definition of `foo` should be applicable to all vectors which has elements of subtype of `Real` we have define it as
foo(a::Vector{T}) where {T<:Real} = println("Vector{T} where {T<:Real}")
# or equivalently but more tersely as
foo(a::Vector{<:Real}) = println("Vector{T} where {T<:Real}")
# 2. Diagonal rule says that a repeated type in a method signature has to be a concrete type. Consider for example the function below
move(a::T, b::T) where {T<:Position}
# we cannot call it with `move(Position(1.0,2.0), Position(1,2))`, since in this case `Position(1.0,2.0)` is of type `Position{Float64}` while `Position(1,2)` is of type `Position{Int64}`.
# 3. When debugging why arguments do not match a particular method definition, it is useful to use `typeof`, `isa`, and `<:` commands. For example
typeof(Position(1.0,2.0))
#
typeof(Position(1,2))
#
Position(1,2) isa Position{Float64}
#
Position(1,2) isa Position{Real}
#
Position(1,2) isa Position{<:Real}
#
typeof(Position(1,2)) <: Position{<:Float64}
#
typeof(Position(1,2)) <: Position{<:Real}

# ### A bizzare definition which you can encounter
# The following definition of a one-hot matrix is taken from [Flux.jl](https://github.com/FluxML/Flux.jl/blob/1a0b51938b9a3d679c6950eece214cd18108395f/src/onehot.jl#L10-L12)
struct OneHotArray{T<:Integer, L, N, var"N+1", I<:Union{T,AbstractArray{T, N}}} <: AbstractArray{Bool, var"N+1"}
  indices::I
end
# The parameters of the type carry information about the type used to encode the position of `one` in each column in `T`, the dimension of one-hot vectors in `L`, the dimension of the storage of `indices` in `N` (which is zero for `OneHotVector` and one for `OneHotMatrix`), number of dimensions of the `OneHotArray` in `var"N+1"` and the type of underlying storage of indicies `I`.


struct RealizedMeta
    start_time::Dates.DateTime
    resolution::Dates.TimePeriod
    len::Int
    start_offset::Int
    end_offset::Int
    interval_len::Int
    realized_timestamps::AbstractVector{Dates.DateTime}
end

function RealizedMeta(
    res::SimulationProblemResults;
    start_time::Union{Nothing, Dates.DateTime}=nothing,
    len::Union{Int, Nothing}=nothing,
)
    existing_timestamps = get_timestamps(res)
    interval = existing_timestamps.step
    resolution = get_resolution(res)
    interval_len = Int(interval / resolution)
    realized_timestamps = get_realized_timestamps(res, start_time=start_time, len=len)

    result_start_time = existing_timestamps[findlast(
        x -> x .<= first(realized_timestamps),
        existing_timestamps,
    )]
    result_end_time = existing_timestamps[findlast(
        x -> x .<= last(realized_timestamps),
        existing_timestamps,
    )]

    len = length(result_start_time:interval:result_end_time)

    start_offset = length(result_start_time:resolution:first(realized_timestamps))
    end_offset = length(
        (last(realized_timestamps) + resolution):resolution:(result_end_time + interval - resolution),
    )

    return RealizedMeta(
        result_start_time,
        resolution,
        len,
        start_offset,
        end_offset,
        interval_len,
        realized_timestamps,
    )
end

function get_realization(
    result_values::Dict{
        OptimizationContainerKey,
        SortedDict{Dates.DateTime, DataFrames.DataFrame},
    },
    meta::RealizedMeta,
)
    realized_values = Dict{OptimizationContainerKey, DataFrames.DataFrame}()
    for (key, result_value) in result_values
        results_concat = Dict{Symbol, Vector{Float64}}()
        for (step, (t, df)) in enumerate(result_value)
            first_id = step > 1 ? 1 : meta.start_offset
            last_id =
                step == meta.len ? meta.interval_len - meta.end_offset : meta.interval_len
            for colname in propertynames(df)
                colname == :DateTime && continue
                col = df[!, colname][first_id:last_id]
                if !haskey(results_concat, colname)
                    results_concat[colname] = col
                else
                    results_concat[colname] = vcat(results_concat[colname], col)
                end
            end
        end
        realized_values[key] = DataFrames.DataFrame(results_concat, copycols=false)
        DataFrames.insertcols!(
            realized_values[key],
            1,
            :DateTime => meta.realized_timestamps,
        )
    end
    return realized_values
end


include("lidar_sensors.jl")

type MultiFeatureExtractor <: AbstractFeatureExtractor
    extract_core::Bool # [8] if true, extract lane offset, lane relative heading, speed, vehicle width, vehicle length, marker dist left+right, lane curvature
    extract_temporal::Bool # [6]  if true, extract accel, jerk, turnrate G, angular rate G, turnrate F, angular rate F
    extract_well_behaved::Bool # [3] if true, extract is_colliding, is_offroad, is_reversing
    extract_neighbor_features::Bool # [28] if true, extract everything that can be missing in tim2d features (ie, speed/dist fore, etc.)
    extract_carlidar_rangerate::Bool # [nbeams] if true, extract range rate as well as range

    carlidar::LidarSensor # [2*nbeams] NOTE: range rate can be turned off.
    roadlidar::RoadlineLidarSensor # [nbeams × depth]
    road_lidar_culling::RoadwayLidarCulling
end
function MultiFeatureExtractor(
    extract_core::Bool,
    extract_temporal::Bool,
    extract_well_behaved::Bool,
    extract_neighbor_features::Bool,
    extract_carlidar_rangerate::Bool,
    carlidar_nbeams::Int,
    roadlidar_nbeams::Int,
    roadlidar_nlanes::Int,
    ;
    carlidar_max_range::Float64 = 100.0, # [m]
    roadlidar_max_range::Float64 = 100.0, # [m]
    )

    carlidar = LidarSensor(carlidar_nbeams, max_range=carlidar_max_range, angle_offset=-π)
    roadlidar = RoadlineLidarSensor(roadlidar_nbeams, max_range=roadlidar_max_range, angle_offset=-π, max_depth=roadlidar_nlanes)

    MultiFeatureExtractor(
        extract_core,
        extract_temporal,
        extract_well_behaved,
        extract_neighbor_features,
        extract_carlidar_rangerate,
        carlidar,
        roadlidar,
        RoadwayLidarCulling(),
        )
end

AutomotiveDrivingModels.rec_length(ext::MultiFeatureExtractor) = ext.extract_temporal ? 3 : 1
function Base.length(ext::MultiFeatureExtractor)
    nbeams_carlidar = nbeams(ext.carlidar)
    nbeams_roadlidar = nbeams(ext.roadlidar)
    nlanes_roadlidar = nlanes(ext.roadlidar)

    8 * ext.extract_core +
    6 * ext.extract_temporal +
    3 * ext.extract_well_behaved +
    28 * ext.extract_neighbor_features +
    nbeams_carlidar * (1 + ext.extract_carlidar_rangerate) +
    nbeams_roadlidar * nlanes_roadlidar
end
function get_start_of_feature_section(ext::MultiFeatureExtractor)

    nbeams_carlidar = nbeams(ext.carlidar)
    nbeams_roadlidar = nbeams(ext.roadlidar)
    nlanes_roadlidar = nlanes(ext.roadlidar)

    i = 0
    core = temporal = well_behaved = neighbor = carlidar_range = carlidar_range_rate = roadlidar_range = -1

    if ext.extract_core
        core = 1
        i += 8
    end
    if ext.extract_temporal
        temporal = i
        i += 6
    end
    if ext.extract_well_behaved
        well_behaved = i
        i += 3
    end
    if ext.extract_neighbor_features
        neighbor = i
        i += 28
    end
    if nbeams_carlidar > 0
        carlidar_range = i
        i += nbeams_carlidar

        if ext.extract_carlidar_rangerate
            carlidar_range_rate = i
            i += nbeams_carlidar
        end
    end
    if nbeams_roadlidar > 0
        roadlidar_range = i
        i += nbeams_roadlidar * nlanes_roadlidar
    end

    (core, temporal, well_behaved, neighbor, carlidar_range, carlidar_range_rate, roadlidar_range, i)
end
function AutomotiveDrivingModels.pull_features!{F<:AbstractFloat}(ext::MultiFeatureExtractor,
    features::Vector{F},
    rec::SceneRecord,
    roadway::Roadway,
    vehicle_index::Int,
    pastframe::Int=0,
    )

    feature_index = 0
    scene = get_scene(rec, pastframe)
    veh_ego = scene[vehicle_index]

    d_ml = convert(Float64, get(MARKERDIST_LEFT, rec, roadway, vehicle_index, pastframe))
    d_mr = convert(Float64, get(MARKERDIST_RIGHT, rec, roadway, vehicle_index, pastframe))

    if ext.extract_core
        #=
        1 - lane offset (positive to left) [m]
        2 - lane relative heading (positive to left) [rad]
        3 - speed [m/s]
        4 - vehicle length [m]
        5 - vehicle width [m]
        6 - lane curvature [1/m]
        7 - Marker Dist left
        8 - Marker Dist right
        =#

        features[feature_index+=1] = veh_ego.state.posF.t
        features[feature_index+=1] = veh_ego.state.posF.ϕ
        features[feature_index+=1] = veh_ego.state.v
        features[feature_index+=1] = veh_ego.def.length
        features[feature_index+=1] = veh_ego.def.width
        features[feature_index+=1] = convert(Float64, get(LANECURVATURE, rec, roadway, vehicle_index, pastframe))
        features[feature_index+=1] = d_ml
        features[feature_index+=1] = d_mr
    end

    if ext.extract_temporal
        #=
        1 - previous acceleration [m/s²]
        2 - previous jerk [m/s³]
        3 - previous turnrate G [rad/s]
        4 - previous angular rate G [rad/s²]
        5 - previous turnrate F [rad/s]
        6 - previous angular rate F [rad/s²]
        =#

        features[feature_index+=1] = convert(Float64, get(ACC, rec, roadway, vehicle_index, pastframe))
        features[feature_index+=1] = convert(Float64, get(JERK, rec, roadway, vehicle_index, pastframe))
        features[feature_index+=1] = convert(Float64, get(TURNRATEG, rec, roadway, vehicle_index, pastframe))
        features[feature_index+=1] = convert(Float64, get(ANGULARRATEG, rec, roadway, vehicle_index, pastframe))
        features[feature_index+=1] = convert(Float64, get(TURNRATEF, rec, roadway, vehicle_index, pastframe))
        features[feature_index+=1] = convert(Float64, get(ANGULARRATEF, rec, roadway, vehicle_index, pastframe))
    end

    if ext.extract_well_behaved
        #=
        1 - is colliding
        2 - is offroad
        3 - is reversing
        =#

        features[feature_index+=1] = convert(Float64, get(IS_COLLIDING, rec, roadway, vehicle_index, pastframe))
        features[feature_index+=1] = convert(Float64, d_ml < -1.0 || d_mr < -1.0)
        features[feature_index+=1] = convert(Float64, veh_ego.state.v < 0.0)
    end

    if ext.extract_neighbor_features
        #=
        can be missing: - first is feature, 2nd is indicator whether it is missing (0 if exists, 1 if missing)
        (1, 2) - lane offset from left lane
        (3, 4) - lane offset from right lane
        (5, 6) - speed fore middle
        (7, 8) - dist fore middle (distance along the lane from the front of the ego car to the rear of the ego car in the same lane)
        (9, 10) - speed fore left
        (11,12) - dist fore left
        (13,14) - speed fore right
        (15,16) - dist fore right
        (17,18) - speed rear middle
        (19,20) - dist fore middle
        (21,22) - speed rear left
        (23,24) - dist rear left
        (25,26) - speed rear right
        (27,28) - dist rear right
        =#

        vtpf = VehicleTargetPointFront()
        vtpr = VehicleTargetPointRear()
        fore_M = get_neighbor_fore_along_lane(      scene, vehicle_index, roadway, vtpf, vtpr, vtpf)
        fore_L = get_neighbor_fore_along_left_lane( scene, vehicle_index, roadway, vtpf, vtpr, vtpf)
        fore_R = get_neighbor_fore_along_right_lane(scene, vehicle_index, roadway, vtpf, vtpr, vtpf)
        rear_M = get_neighbor_rear_along_lane(      scene, vehicle_index, roadway, vtpr, vtpf, vtpr)
        rear_L = get_neighbor_rear_along_left_lane( scene, vehicle_index, roadway, vtpr, vtpf, vtpr)
        rear_R = get_neighbor_rear_along_right_lane(scene, vehicle_index, roadway, vtpr, vtpf, vtpr)

        function set_feature_missing!(features::Vector, i::Int)
            features[i] = 0.0
            features[i+1] = 1.0
            features
        end
        function set_feature!(features::Vector, i::Int, v::Float64)
            features[i] = v
            features[i+1] = 0.0
            features
        end
        function set_dual_feature!(features::Vector, i::Int, f::FeatureValue)
            if f.i == FeatureState.MISSING
                set_feature_missing!(features, i)
            else
                set_feature!(features, i, f.v)
            end
            features
        end
        function set_speed_and_distance!(features::Vector, i::Int, neigh::NeighborLongitudinalResult)
            neigh.ind != 0 ? set_feature!(features, i, scene[neigh.ind].state.v) :
                              set_feature_missing!(features, i)
            neigh.ind != 0 ? set_feature!(features, i+2, neigh.Δs) :
                              set_feature_missing!(features, i+2)
            features
        end

        set_dual_feature!(features, feature_index+=1, get(LANEOFFSETLEFT, rec, roadway, vehicle_index, pastframe))
        feature_index+=1
        set_dual_feature!(features, feature_index+=1, get(LANEOFFSETRIGHT, rec, roadway, vehicle_index, pastframe))
        feature_index+=1

        set_speed_and_distance!(features, feature_index+=1, fore_M); feature_index+=3
        set_speed_and_distance!(features, feature_index+=1, fore_L); feature_index+=3
        set_speed_and_distance!(features, feature_index+=1, fore_R); feature_index+=3
        set_speed_and_distance!(features, feature_index+=1, rear_M); feature_index+=3
        set_speed_and_distance!(features, feature_index+=1, rear_L); feature_index+=3
        set_speed_and_distance!(features, feature_index+=1, rear_R); feature_index+=3
    end

    nbeams_carlidar = nbeams(ext.carlidar)
    if nbeams_carlidar > 0
        observe!(ext.carlidar, scene, roadway, vehicle_index)
        copy!(features, feature_index+=1, ext.carlidar.ranges, 1)
        feature_index += nbeams_carlidar - 1
        if ext.extract_carlidar_rangerate
            copy!(features, feature_index+=1, ext.carlidar.range_rates, 1)
            feature_index += nbeams_carlidar - 1
        end
    end

    nbeams_roadlidar = nbeams(ext.roadlidar)
    if nbeams_roadlidar > 0
        if ext.road_lidar_culling.is_leaf
            observe!(ext.roadlidar, scene, roadway, vehicle_index)
        else
            observe!(ext.roadlidar, scene, roadway, vehicle_index, ext.road_lidar_culling)
        end
        copy!(features, feature_index+=1, ext.roadlidar.ranges)
        feature_index += length(ext.roadlidar.ranges) - 1
    end

    @assert(feature_index == length(features))
    if findfirst(v->isnan(v), features) != 0
        error("feature $(findfirst(v->isnan(v), features)) is nan")
    end

    features
end


#=
This file is auto-generated. Do not edit.
=#

#! format: off

"""
    mutable struct GenericDER <: DynamicInjection
        name::String
        Qref_Flag::Int
        PQ_Flag::Int
        Gen_Flag::Int
        PerOp_Flag::Int
        Recon_Flag::Int
        Trv::Float64
        VV_pnts::NamedTuple{(:V1, :V2, :V3, :V4), Tuple{Float64, Float64, Float64, Float64}}
        Q_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}
        Tp::Float64
        e_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}
        Kpq::Float64
        Kiq::Float64
        Iqr_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}
        I_max::Float64
        Tg::Float64
        kWh_Cap::Float64
        SOC_ini::Float64
        SOC_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}
        Trf::Float64
        fdbd_pnts::NamedTuple{(:fdbd1, :fdbd2), Tuple{Float64, Float64}}
        D_dn::Float64
        D_up::Float64
        fe_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}
        Kpp::Float64
        Kip::Float64
        P_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}
        dP_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}
        T_pord::Float64
        rrpwr::Float64
        VRT_pnts::NamedTuple{(:vrt1, :vrt2, :vrt3, :vrt4, :vrt5), Tuple{Float64, Float64, Float64, Float64, Float64}}
        TVRT_pnts::NamedTuple{(:tvrt1, :tvrt2, :tvrt3), Tuple{Float64, Float64, Float64}}
        tV_delay::Float64
        VES_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}
        FRT_pnts::NamedTuple{(:frt1, :frt2, :frt3, :frt4), Tuple{Float64, Float64, Float64, Float64}}
        TFRT_pnts::NamedTuple{(:tfrt1, :tfrt2), Tuple{Float64, Float64}}
        tF_delay::Float64
        FES_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}
        Pfa_ref::Float64
        Q_ref::Float64
        P_ref::Float64
        base_power::Float64
        states::Vector{Symbol}
        n_states::Int
        ext::Dict{String, Any}
        internal::InfrastructureSystemsInternal
    end

Parameters of a Generic Distributed Energy Resource Model. Based on https://scholarspace.manoa.hawaii.edu/bitstream/10125/70994/0304.pdf

# Arguments
- `name::String`
- `Qref_Flag::Int`: Reactive Power Control Mode. 1 VoltVar Control, 2 Constant Q Control, 3 Constant PF Control, validation range: `(1, 3)`
- `PQ_Flag::Int`: Active and reactive power priority mode. 0 for Q priority, 1 for P priority, validation range: `(0, 1)`
- `Gen_Flag::Int`: Define generator or storage system. 0 unit is a storage device, 1 unit is a generator, validation range: `(0, 1)`
- `PerOp_Flag::Int`: Defines operation of permisible region in VRT characteristic. 0 for cease, 1 for continuous operation, validation range: `(0, 1)`
- `Recon_Flag::Int`: Defines if DER can reconnect after voltage ride-through disconnection, validation range: `(0, 1)`
- `Trv::Float64`: Voltage measurement transducer's time constant, in s, validation range: `(0, nothing)`
- `VV_pnts::NamedTuple{(:V1, :V2, :V3, :V4), Tuple{Float64, Float64, Float64, Float64}}`: Y-axis Volt-var curve points (V1,V2,V3,V4)
- `Q_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}`: Reactive power limits in pu (Q_min, Q_max)
- `Tp::Float64`: Power measurement transducer's time constant, in s, validation range: `(0, nothing)`
- `e_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}`: Error limit in PI controller for q control (e_min, e_max)
- `Kpq::Float64`: PI controller proportional gain for q control, validation range: `(0, nothing)`
- `Kiq::Float64`: PI controller integral gain for q control, validation range: `(0, nothing)`
- `Iqr_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}`: Limit on rate of change for reactive current (pu/s) (Iqr_min, Iqr_max)
- `I_max::Float64`: Max. inverter's current, validation range: `(0, nothing)`
- `Tg::Float64`: Current control's time constant, in s, validation range: `(0, nothing)`
- `kWh_Cap::Float64`: BESS capacity in kWh, validation range: `(0, nothing)`
- `SOC_ini::Float64`: Initial state of charge (SOC) in pu, validation range: `(0, 1)`
- `SOC_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}`: Battery's SOC limits (SOC_min, SOC_max)
- `Trf::Float64`: Time constant to estimate system frequency, in s, validation range: `(0, nothing)`
- `fdbd_pnts::NamedTuple{(:fdbd1, :fdbd2), Tuple{Float64, Float64}}`: Frequency error dead band thresholds `(fdbd1, fdbd2)`
- `D_dn::Float64`: reciprocal of droop for over-frequency conditions, in pu, validation range: `(0, nothing)`
- `D_up::Float64`: reciprocal of droop for under-frequency conditions, in pu, validation range: `(0, nothing)`
- `fe_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}`: Frequency error limits in pu (fe_min, fe_max)
- `Kpp::Float64`: PI controller proportional gain for p control, validation range: `(0, nothing)`
- `Kip::Float64`: PI controller integral gain for p control, validation range: `(0, nothing)`
- `P_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}`: Active power limits in pu (P_min, P_max)
- `dP_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}`: Ramp rate limits for active power in pu/s (dP_min, dP_max)
- `T_pord::Float64`: Power filter time constant in s, validation range: `(0, nothing)`
- `rrpwr::Float64`: Ramp rate for real power increase following a fault, in pu/s, validation range: `(0, nothing)`
- `VRT_pnts::NamedTuple{(:vrt1, :vrt2, :vrt3, :vrt4, :vrt5), Tuple{Float64, Float64, Float64, Float64, Float64}}`: Voltage ride through v points (vrt1,vrt2,vrt3,vrt4,vrt5)
- `TVRT_pnts::NamedTuple{(:tvrt1, :tvrt2, :tvrt3), Tuple{Float64, Float64, Float64}}`: Voltage ride through time points (tvrt1,tvrt2,tvrt3)
- `tV_delay::Float64`: Time delay for reconnection after voltage ride-through disconnection, validation range: `(0, nothing)`
- `VES_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}`: Min and max voltage for entering service (VES_min,VES_max)
- `FRT_pnts::NamedTuple{(:frt1, :frt2, :frt3, :frt4), Tuple{Float64, Float64, Float64, Float64}}`: Frequency ride through v points (frt1,frt2,frt3,frt4)
- `TFRT_pnts::NamedTuple{(:tfrt1, :tfrt2), Tuple{Float64, Float64}}`: Frequency ride through time points (tfrt1,tfrt2)
- `tF_delay::Float64`: Time delay for reconnection after frequency ride-through disconnection, validation range: `(0, nothing)`
- `FES_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}`: Min and max frequency for entering service (FES_min,FES_max)
- `Pfa_ref::Float64`: Reference power factor, validation range: `(0, nothing)`
- `Q_ref::Float64`: Reference reactive power, in pu, validation range: `(0, nothing)`
- `P_ref::Float64`: Reference active power, in pu, validation range: `(0, nothing)`
- `base_power::Float64`: Base power
- `states::Vector{Symbol}`: The states of GenericDER depend on the Flags
- `n_states::Int`: The states of GenericDER depend on the Flags
- `ext::Dict{String, Any}`
- `internal::InfrastructureSystemsInternal`: power system internal reference, do not modify
"""
mutable struct GenericDER <: DynamicInjection
    name::String
    "Reactive Power Control Mode. 1 VoltVar Control, 2 Constant Q Control, 3 Constant PF Control"
    Qref_Flag::Int
    "Active and reactive power priority mode. 0 for Q priority, 1 for P priority"
    PQ_Flag::Int
    "Define generator or storage system. 0 unit is a storage device, 1 unit is a generator"
    Gen_Flag::Int
    "Defines operation of permisible region in VRT characteristic. 0 for cease, 1 for continuous operation"
    PerOp_Flag::Int
    "Defines if DER can reconnect after voltage ride-through disconnection"
    Recon_Flag::Int
    "Voltage measurement transducer's time constant, in s"
    Trv::Float64
    "Y-axis Volt-var curve points (V1,V2,V3,V4)"
    VV_pnts::NamedTuple{(:V1, :V2, :V3, :V4), Tuple{Float64, Float64, Float64, Float64}}
    "Reactive power limits in pu (Q_min, Q_max)"
    Q_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}
    "Power measurement transducer's time constant, in s"
    Tp::Float64
    "Error limit in PI controller for q control (e_min, e_max)"
    e_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}
    "PI controller proportional gain for q control"
    Kpq::Float64
    "PI controller integral gain for q control"
    Kiq::Float64
    "Limit on rate of change for reactive current (pu/s) (Iqr_min, Iqr_max)"
    Iqr_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}
    "Max. inverter's current"
    I_max::Float64
    "Current control's time constant, in s"
    Tg::Float64
    "BESS capacity in kWh"
    kWh_Cap::Float64
    "Initial state of charge (SOC) in pu"
    SOC_ini::Float64
    "Battery's SOC limits (SOC_min, SOC_max)"
    SOC_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}
    "Time constant to estimate system frequency, in s"
    Trf::Float64
    "Frequency error dead band thresholds `(fdbd1, fdbd2)`"
    fdbd_pnts::NamedTuple{(:fdbd1, :fdbd2), Tuple{Float64, Float64}}
    "reciprocal of droop for over-frequency conditions, in pu"
    D_dn::Float64
    "reciprocal of droop for under-frequency conditions, in pu"
    D_up::Float64
    "Frequency error limits in pu (fe_min, fe_max)"
    fe_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}
    "PI controller proportional gain for p control"
    Kpp::Float64
    "PI controller integral gain for p control"
    Kip::Float64
    "Active power limits in pu (P_min, P_max)"
    P_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}
    "Ramp rate limits for active power in pu/s (dP_min, dP_max)"
    dP_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}
    "Power filter time constant in s"
    T_pord::Float64
    "Ramp rate for real power increase following a fault, in pu/s"
    rrpwr::Float64
    "Voltage ride through v points (vrt1,vrt2,vrt3,vrt4,vrt5)"
    VRT_pnts::NamedTuple{(:vrt1, :vrt2, :vrt3, :vrt4, :vrt5), Tuple{Float64, Float64, Float64, Float64, Float64}}
    "Voltage ride through time points (tvrt1,tvrt2,tvrt3)"
    TVRT_pnts::NamedTuple{(:tvrt1, :tvrt2, :tvrt3), Tuple{Float64, Float64, Float64}}
    "Time delay for reconnection after voltage ride-through disconnection"
    tV_delay::Float64
    "Min and max voltage for entering service (VES_min,VES_max)"
    VES_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}
    "Frequency ride through v points (frt1,frt2,frt3,frt4)"
    FRT_pnts::NamedTuple{(:frt1, :frt2, :frt3, :frt4), Tuple{Float64, Float64, Float64, Float64}}
    "Frequency ride through time points (tfrt1,tfrt2)"
    TFRT_pnts::NamedTuple{(:tfrt1, :tfrt2), Tuple{Float64, Float64}}
    "Time delay for reconnection after frequency ride-through disconnection"
    tF_delay::Float64
    "Min and max frequency for entering service (FES_min,FES_max)"
    FES_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}
    "Reference power factor"
    Pfa_ref::Float64
    "Reference reactive power, in pu"
    Q_ref::Float64
    "Reference active power, in pu"
    P_ref::Float64
    "Base power"
    base_power::Float64
    "The states of GenericDER depend on the Flags"
    states::Vector{Symbol}
    "The states of GenericDER depend on the Flags"
    n_states::Int
    ext::Dict{String, Any}
    "power system internal reference, do not modify"
    internal::InfrastructureSystemsInternal
end

function GenericDER(name, Qref_Flag, PQ_Flag, Gen_Flag, PerOp_Flag, Recon_Flag, Trv, VV_pnts, Q_lim, Tp, e_lim, Kpq, Kiq, Iqr_lim, I_max, Tg, kWh_Cap, SOC_ini, SOC_lim, Trf, fdbd_pnts, D_dn, D_up, fe_lim, Kpp, Kip, P_lim, dP_lim, T_pord, rrpwr, VRT_pnts, TVRT_pnts, tV_delay, VES_lim, FRT_pnts, TFRT_pnts, tF_delay, FES_lim, Pfa_ref=0.0, Q_ref=0.0, P_ref=1.0, base_power=100.0, ext=Dict{String, Any}(), )
    GenericDER(name, Qref_Flag, PQ_Flag, Gen_Flag, PerOp_Flag, Recon_Flag, Trv, VV_pnts, Q_lim, Tp, e_lim, Kpq, Kiq, Iqr_lim, I_max, Tg, kWh_Cap, SOC_ini, SOC_lim, Trf, fdbd_pnts, D_dn, D_up, fe_lim, Kpp, Kip, P_lim, dP_lim, T_pord, rrpwr, VRT_pnts, TVRT_pnts, tV_delay, VES_lim, FRT_pnts, TFRT_pnts, tF_delay, FES_lim, Pfa_ref, Q_ref, P_ref, base_power, ext, PowerSystems.get_GenericDER_states(Qref_Flag)[1], PowerSystems.get_GenericDER_states(Qref_Flag)[2], InfrastructureSystemsInternal(), )
end

function GenericDER(; name, Qref_Flag, PQ_Flag, Gen_Flag, PerOp_Flag, Recon_Flag, Trv, VV_pnts, Q_lim, Tp, e_lim, Kpq, Kiq, Iqr_lim, I_max, Tg, kWh_Cap, SOC_ini, SOC_lim, Trf, fdbd_pnts, D_dn, D_up, fe_lim, Kpp, Kip, P_lim, dP_lim, T_pord, rrpwr, VRT_pnts, TVRT_pnts, tV_delay, VES_lim, FRT_pnts, TFRT_pnts, tF_delay, FES_lim, Pfa_ref=0.0, Q_ref=0.0, P_ref=1.0, base_power=100.0, states=PowerSystems.get_GenericDER_states(Qref_Flag)[1], n_states=PowerSystems.get_GenericDER_states(Qref_Flag)[2], ext=Dict{String, Any}(), internal=InfrastructureSystemsInternal(), )
    GenericDER(name, Qref_Flag, PQ_Flag, Gen_Flag, PerOp_Flag, Recon_Flag, Trv, VV_pnts, Q_lim, Tp, e_lim, Kpq, Kiq, Iqr_lim, I_max, Tg, kWh_Cap, SOC_ini, SOC_lim, Trf, fdbd_pnts, D_dn, D_up, fe_lim, Kpp, Kip, P_lim, dP_lim, T_pord, rrpwr, VRT_pnts, TVRT_pnts, tV_delay, VES_lim, FRT_pnts, TFRT_pnts, tF_delay, FES_lim, Pfa_ref, Q_ref, P_ref, base_power, states, n_states, ext, internal, )
end

# Constructor for demo purposes; non-functional.
function GenericDER(::Nothing)
    GenericDER(;
        name="init",
        Qref_Flag=1,
        PQ_Flag=0,
        Gen_Flag=0,
        PerOp_Flag=0,
        Recon_Flag=0,
        Trv=0,
        VV_pnts=(V1=0.0, V2=0.0, V3=0.0, V4=0.0),
        Q_lim=(min=0.0, max=0.0),
        Tp=0,
        e_lim=(min=0.0, max=0.0),
        Kpq=0,
        Kiq=0,
        Iqr_lim=(min=0.0, max=0.0),
        I_max=0,
        Tg=0,
        kWh_Cap=0,
        SOC_ini=0,
        SOC_lim=(min=0.0, max=0.0),
        Trf=0,
        fdbd_pnts=(fdbd1=0.0, fdbd2=0.0),
        D_dn=0,
        D_up=0,
        fe_lim=(min=0.0, max=0.0),
        Kpp=0,
        Kip=0,
        P_lim=(min=0.0, max=0.0),
        dP_lim=(min=0.0, max=0.0),
        T_pord=0,
        rrpwr=0,
        VRT_pnts=(vrt1=0.0, vrt2=0.0, vrt3=0.0, vrt4=0.0, vrt5=0.0),
        TVRT_pnts=(tvrt1=0.0, tvrt2=0.0, tvrt3=0.0),
        tV_delay=0,
        VES_lim=(min=0.0, max=0.0),
        FRT_pnts=(frt1=0.0, frt2=0.0, frt3=0.0, frt4=0.0),
        TFRT_pnts=(tfrt1=0.0, tfrt2=0.0),
        tF_delay=0,
        FES_lim=(min=0.0, max=0.0),
        Pfa_ref=0,
        Q_ref=0,
        P_ref=0,
        base_power=0,
        ext=Dict{String, Any}(),
    )
end

"""Get [`GenericDER`](@ref) `name`."""
get_name(value::GenericDER) = value.name
"""Get [`GenericDER`](@ref) `Qref_Flag`."""
get_Qref_Flag(value::GenericDER) = value.Qref_Flag
"""Get [`GenericDER`](@ref) `PQ_Flag`."""
get_PQ_Flag(value::GenericDER) = value.PQ_Flag
"""Get [`GenericDER`](@ref) `Gen_Flag`."""
get_Gen_Flag(value::GenericDER) = value.Gen_Flag
"""Get [`GenericDER`](@ref) `PerOp_Flag`."""
get_PerOp_Flag(value::GenericDER) = value.PerOp_Flag
"""Get [`GenericDER`](@ref) `Recon_Flag`."""
get_Recon_Flag(value::GenericDER) = value.Recon_Flag
"""Get [`GenericDER`](@ref) `Trv`."""
get_Trv(value::GenericDER) = value.Trv
"""Get [`GenericDER`](@ref) `VV_pnts`."""
get_VV_pnts(value::GenericDER) = value.VV_pnts
"""Get [`GenericDER`](@ref) `Q_lim`."""
get_Q_lim(value::GenericDER) = value.Q_lim
"""Get [`GenericDER`](@ref) `Tp`."""
get_Tp(value::GenericDER) = value.Tp
"""Get [`GenericDER`](@ref) `e_lim`."""
get_e_lim(value::GenericDER) = value.e_lim
"""Get [`GenericDER`](@ref) `Kpq`."""
get_Kpq(value::GenericDER) = value.Kpq
"""Get [`GenericDER`](@ref) `Kiq`."""
get_Kiq(value::GenericDER) = value.Kiq
"""Get [`GenericDER`](@ref) `Iqr_lim`."""
get_Iqr_lim(value::GenericDER) = value.Iqr_lim
"""Get [`GenericDER`](@ref) `I_max`."""
get_I_max(value::GenericDER) = value.I_max
"""Get [`GenericDER`](@ref) `Tg`."""
get_Tg(value::GenericDER) = value.Tg
"""Get [`GenericDER`](@ref) `kWh_Cap`."""
get_kWh_Cap(value::GenericDER) = value.kWh_Cap
"""Get [`GenericDER`](@ref) `SOC_ini`."""
get_SOC_ini(value::GenericDER) = value.SOC_ini
"""Get [`GenericDER`](@ref) `SOC_lim`."""
get_SOC_lim(value::GenericDER) = value.SOC_lim
"""Get [`GenericDER`](@ref) `Trf`."""
get_Trf(value::GenericDER) = value.Trf
"""Get [`GenericDER`](@ref) `fdbd_pnts`."""
get_fdbd_pnts(value::GenericDER) = value.fdbd_pnts
"""Get [`GenericDER`](@ref) `D_dn`."""
get_D_dn(value::GenericDER) = value.D_dn
"""Get [`GenericDER`](@ref) `D_up`."""
get_D_up(value::GenericDER) = value.D_up
"""Get [`GenericDER`](@ref) `fe_lim`."""
get_fe_lim(value::GenericDER) = value.fe_lim
"""Get [`GenericDER`](@ref) `Kpp`."""
get_Kpp(value::GenericDER) = value.Kpp
"""Get [`GenericDER`](@ref) `Kip`."""
get_Kip(value::GenericDER) = value.Kip
"""Get [`GenericDER`](@ref) `P_lim`."""
get_P_lim(value::GenericDER) = value.P_lim
"""Get [`GenericDER`](@ref) `dP_lim`."""
get_dP_lim(value::GenericDER) = value.dP_lim
"""Get [`GenericDER`](@ref) `T_pord`."""
get_T_pord(value::GenericDER) = value.T_pord
"""Get [`GenericDER`](@ref) `rrpwr`."""
get_rrpwr(value::GenericDER) = value.rrpwr
"""Get [`GenericDER`](@ref) `VRT_pnts`."""
get_VRT_pnts(value::GenericDER) = value.VRT_pnts
"""Get [`GenericDER`](@ref) `TVRT_pnts`."""
get_TVRT_pnts(value::GenericDER) = value.TVRT_pnts
"""Get [`GenericDER`](@ref) `tV_delay`."""
get_tV_delay(value::GenericDER) = value.tV_delay
"""Get [`GenericDER`](@ref) `VES_lim`."""
get_VES_lim(value::GenericDER) = value.VES_lim
"""Get [`GenericDER`](@ref) `FRT_pnts`."""
get_FRT_pnts(value::GenericDER) = value.FRT_pnts
"""Get [`GenericDER`](@ref) `TFRT_pnts`."""
get_TFRT_pnts(value::GenericDER) = value.TFRT_pnts
"""Get [`GenericDER`](@ref) `tF_delay`."""
get_tF_delay(value::GenericDER) = value.tF_delay
"""Get [`GenericDER`](@ref) `FES_lim`."""
get_FES_lim(value::GenericDER) = value.FES_lim
"""Get [`GenericDER`](@ref) `Pfa_ref`."""
get_Pfa_ref(value::GenericDER) = value.Pfa_ref
"""Get [`GenericDER`](@ref) `Q_ref`."""
get_Q_ref(value::GenericDER) = value.Q_ref
"""Get [`GenericDER`](@ref) `P_ref`."""
get_P_ref(value::GenericDER) = value.P_ref
"""Get [`GenericDER`](@ref) `base_power`."""
get_base_power(value::GenericDER) = value.base_power
"""Get [`GenericDER`](@ref) `states`."""
get_states(value::GenericDER) = value.states
"""Get [`GenericDER`](@ref) `n_states`."""
get_n_states(value::GenericDER) = value.n_states
"""Get [`GenericDER`](@ref) `ext`."""
get_ext(value::GenericDER) = value.ext
"""Get [`GenericDER`](@ref) `internal`."""
get_internal(value::GenericDER) = value.internal

"""Set [`GenericDER`](@ref) `Qref_Flag`."""
set_Qref_Flag!(value::GenericDER, val) = value.Qref_Flag = val
"""Set [`GenericDER`](@ref) `PQ_Flag`."""
set_PQ_Flag!(value::GenericDER, val) = value.PQ_Flag = val
"""Set [`GenericDER`](@ref) `Gen_Flag`."""
set_Gen_Flag!(value::GenericDER, val) = value.Gen_Flag = val
"""Set [`GenericDER`](@ref) `PerOp_Flag`."""
set_PerOp_Flag!(value::GenericDER, val) = value.PerOp_Flag = val
"""Set [`GenericDER`](@ref) `Recon_Flag`."""
set_Recon_Flag!(value::GenericDER, val) = value.Recon_Flag = val
"""Set [`GenericDER`](@ref) `Trv`."""
set_Trv!(value::GenericDER, val) = value.Trv = val
"""Set [`GenericDER`](@ref) `VV_pnts`."""
set_VV_pnts!(value::GenericDER, val) = value.VV_pnts = val
"""Set [`GenericDER`](@ref) `Q_lim`."""
set_Q_lim!(value::GenericDER, val) = value.Q_lim = val
"""Set [`GenericDER`](@ref) `Tp`."""
set_Tp!(value::GenericDER, val) = value.Tp = val
"""Set [`GenericDER`](@ref) `e_lim`."""
set_e_lim!(value::GenericDER, val) = value.e_lim = val
"""Set [`GenericDER`](@ref) `Kpq`."""
set_Kpq!(value::GenericDER, val) = value.Kpq = val
"""Set [`GenericDER`](@ref) `Kiq`."""
set_Kiq!(value::GenericDER, val) = value.Kiq = val
"""Set [`GenericDER`](@ref) `Iqr_lim`."""
set_Iqr_lim!(value::GenericDER, val) = value.Iqr_lim = val
"""Set [`GenericDER`](@ref) `I_max`."""
set_I_max!(value::GenericDER, val) = value.I_max = val
"""Set [`GenericDER`](@ref) `Tg`."""
set_Tg!(value::GenericDER, val) = value.Tg = val
"""Set [`GenericDER`](@ref) `kWh_Cap`."""
set_kWh_Cap!(value::GenericDER, val) = value.kWh_Cap = val
"""Set [`GenericDER`](@ref) `SOC_ini`."""
set_SOC_ini!(value::GenericDER, val) = value.SOC_ini = val
"""Set [`GenericDER`](@ref) `SOC_lim`."""
set_SOC_lim!(value::GenericDER, val) = value.SOC_lim = val
"""Set [`GenericDER`](@ref) `Trf`."""
set_Trf!(value::GenericDER, val) = value.Trf = val
"""Set [`GenericDER`](@ref) `fdbd_pnts`."""
set_fdbd_pnts!(value::GenericDER, val) = value.fdbd_pnts = val
"""Set [`GenericDER`](@ref) `D_dn`."""
set_D_dn!(value::GenericDER, val) = value.D_dn = val
"""Set [`GenericDER`](@ref) `D_up`."""
set_D_up!(value::GenericDER, val) = value.D_up = val
"""Set [`GenericDER`](@ref) `fe_lim`."""
set_fe_lim!(value::GenericDER, val) = value.fe_lim = val
"""Set [`GenericDER`](@ref) `Kpp`."""
set_Kpp!(value::GenericDER, val) = value.Kpp = val
"""Set [`GenericDER`](@ref) `Kip`."""
set_Kip!(value::GenericDER, val) = value.Kip = val
"""Set [`GenericDER`](@ref) `P_lim`."""
set_P_lim!(value::GenericDER, val) = value.P_lim = val
"""Set [`GenericDER`](@ref) `dP_lim`."""
set_dP_lim!(value::GenericDER, val) = value.dP_lim = val
"""Set [`GenericDER`](@ref) `T_pord`."""
set_T_pord!(value::GenericDER, val) = value.T_pord = val
"""Set [`GenericDER`](@ref) `rrpwr`."""
set_rrpwr!(value::GenericDER, val) = value.rrpwr = val
"""Set [`GenericDER`](@ref) `VRT_pnts`."""
set_VRT_pnts!(value::GenericDER, val) = value.VRT_pnts = val
"""Set [`GenericDER`](@ref) `TVRT_pnts`."""
set_TVRT_pnts!(value::GenericDER, val) = value.TVRT_pnts = val
"""Set [`GenericDER`](@ref) `tV_delay`."""
set_tV_delay!(value::GenericDER, val) = value.tV_delay = val
"""Set [`GenericDER`](@ref) `VES_lim`."""
set_VES_lim!(value::GenericDER, val) = value.VES_lim = val
"""Set [`GenericDER`](@ref) `FRT_pnts`."""
set_FRT_pnts!(value::GenericDER, val) = value.FRT_pnts = val
"""Set [`GenericDER`](@ref) `TFRT_pnts`."""
set_TFRT_pnts!(value::GenericDER, val) = value.TFRT_pnts = val
"""Set [`GenericDER`](@ref) `tF_delay`."""
set_tF_delay!(value::GenericDER, val) = value.tF_delay = val
"""Set [`GenericDER`](@ref) `FES_lim`."""
set_FES_lim!(value::GenericDER, val) = value.FES_lim = val
"""Set [`GenericDER`](@ref) `Pfa_ref`."""
set_Pfa_ref!(value::GenericDER, val) = value.Pfa_ref = val
"""Set [`GenericDER`](@ref) `Q_ref`."""
set_Q_ref!(value::GenericDER, val) = value.Q_ref = val
"""Set [`GenericDER`](@ref) `P_ref`."""
set_P_ref!(value::GenericDER, val) = value.P_ref = val
"""Set [`GenericDER`](@ref) `base_power`."""
set_base_power!(value::GenericDER, val) = value.base_power = val
"""Set [`GenericDER`](@ref) `ext`."""
set_ext!(value::GenericDER, val) = value.ext = val