using Mill, JSON, BSON, Flux, JsonGrinder, Test
using HierarchicalUtils
import HierarchicalUtils: NodeType, children, InnerNode, LeafNode, printtree
using JsonGrinder: DictEntry, suggestextractor, schema
using Mill: reflectinmodel

j1 = JSON.parse("""{"a": 4, "b": {"a":[1,2,3],    "b": 1},"c": { "a": {"a":[1,2,3],"b":[4,5,6]}}}""",inttype=Float64)
j2 = JSON.parse("""{"a": 4, "c": {"a":{"a":[2,3], "b":[5,6]}}}""")
j3 = JSON.parse("""{"a": 4, "b": {"a":[1,2,3,4],  "b": 1}}""")
j4 = JSON.parse("""{"a": 4, "b": {}}""")
j5 = JSON.parse("""{"b": {}}""")
j6 = JSON.parse("""{}""")

sch = schema([j1,j2,j3,j4,j5,j6])
ext = suggestextractor(sch)

@testset "printtree" begin
    @test buf_printtree(ext, trav=true) ==
    """
    Dict [""]
      ├── a: Float32 ["E"]
      ├── b: Dict ["U"]
      │        ├── a: Array of ["Y"]
      │        │        └── Float32 ["a"]
      │        └── b: Float32 ["c"]
      └── c: Dict ["k"]
               └── a: Dict ["s"]
                        ├── a: Array of ["u"]
                        │        └── Float32 ["v"]
                        └── b: Array of ["w"]
                                 └── Float32 ["x"]"""
end

@testset "nnodes" begin
    @test nnodes(ext) == 12
    @test nnodes(ext[:a]) == 1
    @test nnodes(ext[:b]) == 4
    @test nnodes(ext[:c]) == 6
end

@testset "nleafs" begin
    @test nleafs(ext[:a]) + nleafs(ext[:b]) + nleafs(ext[:c]) == nleafs(ext)
end

@testset "children" begin
    @test children(ext) == (; :a=>ext[:a], :b=>ext[:b], :c=>ext[:c])
    @test children(ext[:a]) == ()
    @test children(ext[:b]) == (; :a=>ext[:b][:a], :b=>ext[:b][:b])
    @test children(ext[:b][:a]) == (ext[:b][:a].item,)
    @test children(ext[:b][:b]) == ()
    @test children(ext[:c]) == (; :a=>ext[:c][:a])
    @test children(ext[:c][:a]) == (; :a=>ext[:c][:a][:a], :b=>ext[:c][:a][:b])
    @test children(ext[:c][:a][:a]) == (ext[:c][:a][:a].item,)
    @test children(ext[:c][:a][:b]) == (ext[:c][:a][:b].item,)
end

@testset "nchildren" begin
    @test nchildren(ext) == 3
    @test nchildren(ext[:a]) == 0
    @test nchildren(ext[:b]) == 2
    @test nchildren(ext[:b][:a]) == 1
    @test nchildren(ext[:b][:b]) == 0
    @test nchildren(ext[:c]) == 1
    @test nchildren(ext[:c][:a]) == 2
    @test nchildren(ext[:c][:a][:a]) == 1
    @test nchildren(ext[:c][:a][:b]) == 1
end

@testset "getindex on strings" begin
    @test ext[""] == ext
    @test ext["E"] == ext[:a]
    @test ext["U"] == ext[:b]
    @test ext["Y"] == ext[:b][:a]
    @test ext["a"] == ext[:b][:a].item
    @test ext["c"] == ext[:b][:b]
    @test ext["k"] == ext[:c]
    @test ext["s"] == ext[:c][:a]
    @test ext["u"] == ext[:c][:a][:a]
    @test ext["v"] == ext[:c][:a][:a].item
    @test ext["w"] == ext[:c][:a][:b]
    @test ext["x"] == ext[:c][:a][:b].item
end

@testset "NodeIterator" begin
    @test collect(NodeIterator(ext)) == [ext[""], ext["E"], ext["U"], ext["Y"], ext["a"], ext["c"],
        ext["k"], ext["s"], ext["u"], ext["v"], ext["w"], ext["x"]]
end

@testset "LeafIterator" begin
    @test collect(LeafIterator(ext)) == [ext["E"], ext["a"], ext["c"], ext["v"], ext["x"]]
end

@testset "TypeIterator" begin
    @test collect(TypeIterator(ExtractArray, ext)) == [ext["Y"], ext["u"], ext["w"]]
end

@testset "show" begin
    e = ExtractCategorical(["a","b"])
    @test buf_printtree(e) == """Categorical d = 3"""

    e = ExtractOneHot(["a","b"], "name", nothing)
    @test buf_printtree(e) == """OneHot d = 3"""

    dict = Dict("a" => ExtractArray(ExtractScalar(Float64,2,3)),"b" => ExtractArray(ExtractScalar(Float64,2,3)))
    br = ExtractDict(dict)
    @test buf_printtree(br, trav=true) ==
    """
    Dict [""]
      ├── a: Array of ["E"]
      │        └── Float64 ["M"]
      └── b: Array of ["U"]
               └── Float64 ["c"]"""

    dict = Dict("a" => ExtractScalar(Float64,2,3),"b" => ExtractScalar(Float64), "c" => ExtractArray(ExtractScalar(Float64,2,3)))
    br = ExtractDict(dict)
    @test buf_printtree(br, trav=true) ==
    """
    Dict [""]
      ├── a: Float64 ["E"]
      ├── b: Float64 ["U"]
      └── c: Array of ["k"]
               └── Float64 ["s"]"""

    other1 = Dict("a" => ExtractArray(ExtractScalar(Float64,2,3)),"b" => ExtractArray(ExtractScalar(Float64,2,3)))
    br1 = ExtractDict(other1)
    other = Dict("a" => ExtractArray(br1), "b" => ExtractScalar(Float64,2,3))
    br = ExtractDict(other)
    @test buf_printtree(br, trav=true) ==
    """
    Dict [""]
      ├── a: Array of ["E"]
      │        └── Dict ["M"]
      │              ├── a: Array of ["O"]
      │              │        └── Float64 ["P"]
      │              └── b: Array of ["Q"]
      │                       └── Float64 ["R"]
      └── b: Float64 ["U"]"""
end


module NineML

export ODE, Assignment, Relation, Alias, OnEvent, OnCondition, Regime, Group, Population, Projection
export parse_ComponentClass, parse_Component, parse_Components
export eval_Relation, eval_Alias, eval_Regime, eval_Dynamics

using Base.Collections
using Graphs
using Sims
using LightXML
using Winston

typealias Time Float64

type ODE
    quantity:: Symbol
    freevars:: Array{Symbol}
    rhs:: Union(Float64,Symbol,Expr)
end

type Assignment
    quantity:: Symbol
    freevars:: Array{Symbol}
    rhs:: Union(Symbol,Expr)
end

type Alias
    quantity:: Symbol
    freevars:: Array{Symbol}
    rhs:: Expr
end

type Relation
    quantity:: Symbol
    argument:: Symbol
    freevars:: Array{Symbol}
    rhs:: Expr
end

type OnEvent
    quantity:: Symbol
    asgnsys:: Array{Any}
end

type OnCondition
    target_regime:: Union(Symbol,Nothing)
    trigger:: Expr
    event_quantity:: Symbol
    asgns:: Array{Any}
end

type Regime
    name:: Symbol
    odes:: Vector{ODE}
    asgns:: Vector{Assignment}
    events:: Vector{OnEvent}
    structuralEvents:: Vector{OnCondition}

end

type Population
    label:: Symbol
    n::Int64   # size
    proto::Sim   # prototype
end

type Component
    name:: Symbol
    parameterValues:: Dict{Symbol,Union(Float64,Expr)}
    initialValues:: Dict{Symbol,Union(Float64,Expr)}
end

type ComponentClass
    name:: Symbol
    parameters:: Vector{Symbol}
    states:: Vector{Symbol}
    ports:: Vector{Symbol}
    dynamics:: (Vector{Alias},Vector{Relation},Vector{Regime})
end

typealias Group Set{Population}

type Projection
    label   :: Symbol
    source  :: Group
    target  :: Group
    rule    :: Function
    weight  :: Function
    delay   :: Union (Function, Time)
    psr     :: Function
end

abstract ProjectionGraph

type SimpleProjectionGraph <: ProjectionGraph
    label :: Symbol
    G :: SimpleGraph
end

type WeightDelayProjectionGraph <: ProjectionGraph
    label :: Symbol
    G :: SimpleGraph
    M :: SparseMatrixCSC{Float64,Int64}
    D :: SparseMatrixCSC{Float64,Int64}
end

type WeightProjectionGraph <: ProjectionGraph
    label :: Symbol
    G :: SimpleGraph
    M :: SparseMatrixCSC{Float64,Int64}
end

function make_graph(n)
    G = Graphs.simple_graph(n)
    return G
end

function build_weight_delay_projection_graph (projection)
    c = projection.rule
    w = projection.weight
    d = projection.delay
    G = make_graph (n)
    W::Array{Float64,2} = zeros(n,n)
    D::Array{Float64,2} = zeros(n,n)
    eind = 1
    for i = 1 : n
        for j = 1 : n
            if c(i,j)
                add_edge! (G, i, j)
                W[i,j] = w(i,j)
                D[i,j] = d(i,j)
                eind = eind + 1
            end
        end
    end
    return WeightDelayProjectionGraph(projection.label,G,sparse(M),sparse(D))
end

function build_weight_projection_graph (projection)
    c = projection.rule
    w = projection.weight
    G = make_graph (n)
    W::Array{Float64,2} = zeros(n,n)
    eind = 1
    for i = 1 : n
        for j = 1 : n
            if c(i,j)
                add_edge! (G, i, j)
                W[i,j] = w(i,j)
                eind = eind + 1
            end
        end
    end
    WeightProjectionGraph(projection.label,G,sparse(M))
end

type Network

    n :: Int64
    d :: Float64

    State :: Array{(Function,Sim,Array {Float64,1},Array {Float64,1}),1}

    Connections :: Vector {ProjectionGraph}

    # TODO: use type Unknown for synaptic weight
    PQ :: Array {PriorityQueue {Time,Float64},1}
end

unary_operations = [:heaviside]

find_vars(e) = find_vars(e, Any[], Any[])
function find_vars(e,lst,vars)
    if isa(e,Symbol)
        if current_module()===Main && !isdefined(e) && !(e in unary_operations) && !(e in vars) && !(e in lst)
            push!(lst, e)
        end
    elseif isa(e,Expr) && e.head !== :quote && e.head !== :top
        for x in e.args
            find_vars(x,lst,vars)
        end
    end
    lst
end

function replace_lift_vars(e, aliases, relations, parameters, states)

    if isa(e,Symbol)
        if e == :t
            return :t
        elseif current_module()===Main && !isdefined(e) && (haskey(parameters,e))
            return Expr(:ref,:p,Expr(:ref,Expr(:.,Expr(:.,:ss,:(:sm)),:(:parameter_idx_map)),
                                     Expr(:quote,parameters[e].sym)))
        elseif current_module()===Main && !isdefined(e) && (haskey(states,e))
            return Expr(:ref,:y,Expr(:ref,Expr(:.,Expr(:.,:ss,:(:sm)),:(:unknown_idx_map)),
                                     Expr(:quote,states[e].sym)))
        else
            return replace_vars(e,aliases,relations,parameters,states)
        end
    elseif isa(e,Expr) && e.head !== :quote && e.head !== :top
        begin
            e1 = Expr(e.head)
            e1.args = map((x) -> replace_lift_vars(x,aliases,relations,parameters,states), e.args)
            e1.typ = e.typ
            return e1
        end
    else
        return replace_vars(x,aliases,relations,parameters,states)
    end
end

function replace_vars(e, aliases, relations, parameters, states)
    if isa(e,Symbol)
        if e == :t
            return MTime
        #elseif current_module()===Main && !isdefined(e) && (haskey(ports,e))
        #    return ports[e]
        elseif current_module()===Main && !isdefined(e) && (haskey(parameters,e))
            return parameters[e]
        elseif current_module()===Main && !isdefined(e) && (haskey(states,e))
            return states[e]
        elseif current_module()===Main && !isdefined(e) && (haskey(aliases,e))
            begin
                a = aliases[e]
                e1 = Expr(:call)
                e1.args = [a[1], map((x) -> replace_vars(x,aliases,relations,parameters,states), a[2])...]
                return e1
            end
        elseif current_module()===Main && !isdefined(e) && (haskey(relations,e))
            begin
                r = relations[e]
                e1 = Expr(:call)
                e1.args = [r[1], map((x) -> replace_vars(x,aliases,relations,parameters,states), r[2])...]
                return e1
            end
        else
            return e
        end
    elseif isa(e,Expr) && e.head == :call && (e.args[1] == :heaviside)
        begin
            cond = MExpr(Expr(:comparison,replace_vars(e.args[2],aliases,relations,parameters,states), :<, 0.0))
            e1 = ifelse(cond, 0.0, 1.0)
            return e1
        end
    elseif isa(e,Expr) && e.head == :call && (haskey(relations,e.args[1]))
        begin
            r = relations[e.args[1]]
            e1 = Expr(e.head)
            e1.args = [r[1], map((x) -> replace_vars(x,aliases,relations,parameters,states), [e.args[2],r[2]]...)]
            e1.typ = e.typ
            return e1
        end
    elseif isa(e,Expr) && e.head !== :quote && e.head !== :top
        begin
            e1 = Expr(e.head)
            e1.args = map((x) -> replace_vars(x,aliases,relations,parameters,states), e.args)
            e1.typ = e.typ
            return e1
        end
    else
        return e
    end
end
replace_vars(e) = replace_vars (e, Dict{Symbol,Alias}(), Dict{Symbol,Relation}(),
                                Dict{Symbol,Parameter}(), Dict{Symbol,Unknown}())

function parse_MathInline (parent)
    math = find_element(parent,"MathInline")
    if math == nothing
        return nothing
    else
        e = parse(content(math))
        return e
    end
end

function parse_Regime (sysname,parameters,states,r)

    rname = symbol(attribute(r, "name"))
    timeDerivatives = get_elements_by_tagname (r, "TimeDerivative")
    stateAssignments =  get_elements_by_tagname (r, "StateAssignment")
    onEvents = get_elements_by_tagname (r, "OnEvent")
    onConditions = get_elements_by_tagname (r, "OnCondition")

    @assert (length (onConditions) <= 1)

    @assert ((!(isempty (timeDerivatives))) || (!(isempty (stateAssignments))))

    odes = map ((x) ->
                let
                    quantity = symbol(attribute(x, "variable"))
                    rhs = parse_MathInline(x)
                    fv = find_vars(rhs,Any[],Any[quantity])
                    return ODE (quantity, fv, rhs)
                end, timeDerivatives)

    asgns = map ((x) ->
                 let
                     quantity = symbol(attribute(x, "variable"))
                     rhs = parse_MathInline(x)
                     fv = find_vars(rhs,Any[],Any[quantity])
                     return Assignment (quantity, fv, rhs)
                 end, stateAssignments)

    events = map ((x) ->
                  let
                      quantity = symbol(attribute(x, "src_port"))
                      eventAssignments = get_elements_by_tagname (x, "StateAssignment")
                      easgns = map ((x) ->
                                    let
                                        quantity = symbol(attribute(x, "variable"))
                                        rhs = parse_MathInline(x)
                                        fv = find_vars(rhs,Any[],Any[quantity])
                                        return Assignment (quantity, fv, rhs)
                                    end, eventAssignments)
                      asgnsys = (map((x) ->
                                     let u = Unknown(x.quantity)
                                         reinit(u, MExpr(x.rhs))
                                     end, easgns))
                      return OnEvent (quantity, asgnsys)
                  end, onEvents)

    structuralEvents = map ((x) ->
                            let
                                target_regime = let a = attribute(x, "target_regime")
                                    if a == nothing
                                        nothing
                                    else
                                        symbol(a)
                                    end
                                end
                                trigger = parse_MathInline(find_element(x, "Trigger"))
                                eventAssignments = get_elements_by_tagname (x, "StateAssignment")
                                event_out = find_element (x, "EventOut")
                                event_quantity = symbol(attribute(event_out, "port"))
                                seasgns = map ((x) ->
                                               let
                                                   quantity = symbol(attribute(x, "variable"))
                                                   rhs = parse_MathInline(x)
                                                   fv = find_vars(rhs,Any[],Any[quantity])
                                                   return Assignment (quantity, fv, rhs)
                                               end, eventAssignments)

                                return OnCondition (target_regime, trigger, event_quantity, seasgns)
                            end, onConditions)

    Regime(rname,vec(odes),vec(asgns),vec(events),vec(structuralEvents))
end

function parse_Dynamics (sysname,parameters,dynamics)

    states = map((x)->symbol(attribute(x,"name")),
                 get_elements_by_tagname (dynamics, "StateVariable"))
    regimes   = get_elements_by_tagname (dynamics, "Regime")
    relations = get_elements_by_tagname (dynamics, "Relation")
    aliases   = get_elements_by_tagname (dynamics, "Alias")
    relations = map ((x) ->
                     let
                         quantity = symbol(attribute(x, "name"))
                         argument = symbol(attribute(x, "argument"))
                         fv = find_vars(rhs,Any[],Any[quantity,argument])
                         rhs = parse_MathInline(x)
                         return Relation (quantity, argument, fv, rhs)
                     end, relations)

    aliases = map ((x) ->
                   let
                       quantity = symbol(attribute(x, "name"))
                       rhs = parse_MathInline(x)
                       fv = find_vars(rhs,Any[],Any[quantity])
                       return Alias (quantity, fv, rhs)
                   end, aliases)

    return (states, vec(aliases), vec(relations), map((r) -> parse_Regime(sysname,parameters,states,r), regimes))
end

function parse_ComponentClass (filename)
    xdoc = parse_file(filename)

    xroot = root(xdoc)  # an instance of XMLElement
    #@assert(name(xroot) == "NineML")

    component = find_element(xroot, "ComponentClass")

    name = symbol(attribute(component, "name"))

    dynamics = find_element(component, "Dynamics")
    analog_send_ports = get_elements_by_tagname(component, "AnalogSendPort")
    analog_receive_ports = get_elements_by_tagname(component, "AnalogReceivePort")
    analog_reduce_ports = get_elements_by_tagname(component, "AnalogReducePort")
    event_send_ports = get_elements_by_tagname(component, "EventSendPort")
    event_receive_ports = get_elements_by_tagname(component, "EventReceivePort")

    parameters = map((x) -> symbol(attribute(x, "name")),
                     get_elements_by_tagname(component, "Parameter"))

    (states, aliases, relations, dynamics) = parse_Dynamics (name,parameters,dynamics)

    ports = setdiff (map((x) -> symbol(attribute(x,"name")),
                         [analog_receive_ports, analog_reduce_ports, event_receive_ports, analog_send_ports]),
                     states)

    ComponentClass(name, parameters, states, ports, (aliases, relations, dynamics))
end

function parse_Component (component)

    name = symbol(attribute(component, "name"))

    parameterValues = Dict{Symbol,Union(Float64,Expr)}()
    initialValues = Dict{Symbol,Union(Float64,Expr)}()

    for x in get_elements_by_tagname(component, "Property")
        let q = find_element(x,"Quantity")
            sv = find_element(q,"SingleValue")
            val = find_element(x,"MathInline") == nothing ? parse(content(sv)) : parse_MathInline(sv)

            parameterValues[symbol(attribute(x, "name"))] = val
        end
    end

    for x in get_elements_by_tagname(component, "Initial")
        let q = find_element(x,"Quantity")
            sv = find_element(q,"SingleValue")
            val = find_element(x,"MathInline") == nothing ? parse(content(sv)) : parse_MathInline(sv)

            initialValues[symbol(attribute(x, "name"))] = val
        end
    end

    Component(name, parameterValues, initialValues)

end

function parse_Components (filename)

    xdoc = parse_file(filename)

    xroot = root(xdoc)  # an instance of XMLElement
    #@assert(name(xroot) == "NineML")

    components = get_elements_by_tagname(xroot, "Component")
    return map(parse_Component, components)

end

function eval_Relation (r::Relation)

    Expr(:function,
         Expr(:call,r.quantity,[r.argument,r.freevars]),
         replace_vars(r.rhs))

end

function eval_Alias (a::Alias)

    Expr(:function,
         Expr(:call,a.quantity,a.freevars...),
         replace_vars(a.rhs))
end

function eval_Relation (r::Relation)

    Expr(:function,
         Expr(:call,r.quantity,r.freevars...),
         replace_vars(r.rhs))
end

function eval_Regime (regime::Regime,
                      name::Symbol,
                      parameters::Dict{Symbol,Parameter},
                      states::Dict{Symbol,Unknown},
                      aliases::Dict{Symbol,(Expr,Array{Symbol})},
                      relations::Dict{Symbol,(Expr,Array{Symbol})},
                      initialValues::Dict{Symbol,Union(Float64,Expr)})

    rname  = regime.name
    odes   = regime.odes
    asgns  = regime.asgns
    events = regime.events
    structuralEvents = regime.structuralEvents

    paramsys = (map((x) ->
                    let u = x[2]
                        InitialEquation(u - 0.0)
                    end, parameters))

    odesys = (map((x) ->
                  let u = states[x.quantity]
                      DerUnknown(x.quantity,0.0,false,u) -
                      (isa(x.rhs,Float64) ? x.rhs :
                       MExpr(replace_vars(x.rhs,aliases,relations,parameters,states)))
                  end, odes))

    asgnsys = (map((x) ->
                   let u = Unknown(x.quantity, 0.0)
                       u - MExpr(replace_vars(x.rhs,aliases,relations,parameters,states))
                   end, asgns))

    eventsys = (map((x) -> Event(x.quantity, x.asgnsys), events))

    conds = filter ((x) -> (x.target_regime == nothing) ? true : false, structuralEvents)
    condsys = map((x) ->
                  Event(MExpr(replace_vars(x.trigger,aliases,relations,parameters,states)),
                        map ((a) ->
                             reinit(states[a.quantity],
                                    replace_vars(a.rhs,aliases,relations,parameters,states)),
                             x.asgns)),
                      conds)

    strevs = filter ((x) -> (x.target_regime == nothing) ? false : true, structuralEvents)
    strevfns = map ((x) ->
                    let
                        target_regime_name = symbol(join([x.target_regime,name]))
                        asgns_exprs = map ((a) ->
                                           Expr(:(=),
                                                replace_lift_vars(a.quantity,aliases,relations,parameters,states),
                                                replace_lift_vars(a.rhs,aliases,relations,parameters,states)),
                                           x.asgns)
                        trigger_expr = replace_vars(x.trigger,aliases,relations,parameters,states)
                        response = quote
                            (t,y,yp,p,ss) -> begin
                                println("response: t = ", t)
                                $(asgns_exprs...)
                                println("response: p = ", p)
                                return (y,p)
                                end
                        end
                        return eval(Main, quote ((eqsys) -> StructuralEvent($trigger_expr, eqsys(),
                                                                            ()->$target_regime_name(),
                                                                            pos_response=$response))
                                                 end)
                    end,
                    strevs)

    if isempty(strevs)
        this_regime_name = symbol(join([rname,name]))
        expr = Expr(:function,
                    Expr(:call,this_regime_name),
                         ##(map ((x) -> Expr(:kw,x[1],x[2]), initialValues))...),
                    quote
                        Equation[$paramsys...,$asgnsys...,$odesys...,$eventsys...,$condsys...]
                    end)
        eval(Main,expr)
        return this_regime_name
    else
        this_regime_eqname = symbol(join([rname,name,"eq"]))
        expr1 = Expr(:function,
                     Expr(:call,this_regime_eqname),
                          ##(map ((x) -> Expr(:kw,x[1],x[2]), initialValues))...),
                     quote
                         Equation[$paramsys...,$asgnsys...,$odesys...,$eventsys...,$condsys...]
                     end)
        eqsys = eval(Main,expr1)

        this_regime_name = symbol(join([rname,name]))
        expr2 = Expr(:function,
                     Expr(:call,this_regime_name),
                          ##(map ((x) -> Expr(:kw,x[1],x[2]), initialValues))...),
                     quote
                         Equation[$(map ((x) -> x(eqsys), strevfns))...]
                     end)
        eval(Main,expr2)
        return this_regime_name
    end
end

function eval_Dynamics (dynamics,
                        name::Symbol,
                        parameterValues::Dict{Symbol,Union(Float64,Expr)},
                        initialValues::Dict{Symbol,Union(Float64,Expr)},
                        parameterNames::Vector{Symbol},
                        stateNames::Vector{Symbol},
                        portNames::Vector{Symbol})

    aliasDefs    = dynamics[1]
    relationDefs = dynamics[2]
    regimes      = dynamics[3]

    states = Dict{Symbol,Unknown}()
    ports  = Dict{Symbol,Parameter}()
    parameters = Dict{Symbol,Parameter}()
    for x in parameterValues
        if x[1] in parameterNames
            parameters[x[1]] = Parameter(x[2],string(x[1]))
        end
    end
    for x in portNames
        pp = Parameter(string(x))
        ports[x] = pp
        parameters[x] = pp
    end
    for x in initialValues
        states[x[1]] = Unknown(x[1],string(x[1]))
        states[x[1]].value = x[2]
    end
    aliases = Dict{Symbol,(Expr,Array{Symbol})}()
    relations = Dict{Symbol,(Expr,Array{Symbol})}()
    for a in aliasDefs
        aliases[a.quantity] = (NineML.eval_Alias(a), a.freevars)
    end
    for r in relations
        relations[r.quantity] = (NineML.eval_Relation(r), r.freevars)
    end

    regimeNames = map(r -> eval_Regime(r, name, parameters, states, aliases, relations, initialValues),
                      regimes)

    first_regime_name = regimeNames[1]

    expr = quote
            function $name ()
                $first_regime_name ()
            end
    end
    eval(Main,expr)

end

end


"""
    write2disk(filename::AbstractString, wv::WordVectors [;kind=:binary])

Writes embeddings to disk.

# Arguments
  * `filename::AbstractString` the embeddings file name
  * `wv::WordVectors` the embeddings

# Keyword arguments
  * `kind::Symbol` specifies whether the embeddings file is textual (`:text`)
or binary (`:binary`); default `:binary`
"""
function write2disk(fid::IO, wv::WordVectors{S,T,H};
                    kind::Symbol=:binary
                   ) where {S<:AbstractString, T<:Real, H<:Integer}
    if kind == :binary
        _write2disk_binary(fid, wv)
    elseif kind == :text
        _write2disk_text(fid, wv)
    else
        throw(ErrorException("Supported values for the kind keyword"*
                             "argument are :text and :binary."))
    end

end

function write2disk(file::AbstractString, wv::WordVectors{S,T,H};
                    kind::Symbol=:binary
                   ) where {S<:AbstractString, T<:Real, H<:Integer}
    open(file, "w") do fid
        write2disk(fid, wv, kind=kind)
    end
end

function _write2disk_binary(fid::IO, wv::WordVectors{S,T,H}
                           ) where {S<:AbstractString, T<:Real, H<:Integer}
    vector_size, vocab_size = size(wv.vectors)
    println(fid, "$vocab_size $vector_size")
    for i in 1:vocab_size
        write(fid, wv.vocab[i])
        write(fid, ' ')
        write(fid, Float32.(wv.vectors[:,i]))
        write(fid, '\n')
    end
end

function _write2disk_text(fid::IO, wv::WordVectors{S,T,H}
                         ) where {S<:AbstractString, T<:Real, H<:Integer}
    vector_size, vocab_size = size(wv.vectors)
    println(fid, "$vocab_size $vector_size")
    for i in 1:vocab_size
        nstr = join(map(string, wv.vectors[:,i]), " ")
        println(fid, "$(wv.vocab[i]) $(nstr)")
    end
end

"""
    write2disk(filename::AbstractString, wv::CompressedWordVectors [;kind=:binary])

Writes compressed embeddings to disk.

# Arguments
  * `filename::AbstractString` the embeddings file name
  * `wv::CompressedWordVectors` the embeddings

# Keyword arguments
  * `kind::Symbol` specifies whether the embeddings file is textual (`:text`)
or binary (`:binary`); default `:binary`
"""
function write2disk(fid::IO, cwv::CompressedWordVectors{Q,U,D,T,S,H};
                    kind::Symbol=:binary) where {Q,U,D,T,S,H}
    if kind == :binary
        _write2disk_binary(fid, cwv)
    elseif kind == :text
        _write2disk_text(fid, cwv)
    else
        throw(ErrorException("Supported values for the kind keyword"*
                             "argument are :text and :binary."))
    end
end

function write2disk(file::AbstractString,
                    cwv::CompressedWordVectors{Q,U,D,T,S,H};
                    kind::Symbol=:binary) where {Q,U,D,T,S,H}
    open(file, "w") do fid
        write2disk(fid, cwv, kind=kind)
    end
end

function _write2disk_binary(fid::IO,
                            cwv::CompressedWordVectors{Q,U,D,T,S,H}
                           ) where {Q,U,D,T,S,H}
    # Initialize all variables needed to write
    nrows, vocab_size = cwv.vectors.quantizer.dims
    d = size(cwv.vectors.quantizer.codebooks[1].vectors, 1)
    m = length(cwv.vectors.quantizer.codebooks)
    k = cwv.vectors.quantizer.k
    quanttype_str = string(Q)
    disttype_str = string(D)
    quanteltype_str = string(U)
    origeltype_str = string(T)

    # Start writing text information
    println(fid, "$nrows $vocab_size")
    println(fid, "$d $k $m")
    println(fid, "$quanttype_str")
    println(fid, "$disttype_str")
    println(fid, "$quanteltype_str")
    println(fid, "$origeltype_str")

    # Write vocabulary and compressed vectors
    for i in 1:vocab_size
        write(fid, cwv.vocab[i])
        write(fid, ' ')
        write(fid, cwv.vectors.data[:,i])
    end

    # Write codebooks
    for i in 1:m
        write(fid, cwv.vectors.quantizer.codebooks[i].codes)
        for j in 1:d
            write(fid, cwv.vectors.quantizer.codebooks[i].vectors[j,:])
        end
    end
    for i in 1:nrows  # rotation matrix
        write(fid, cwv.vectors.quantizer.rot[:,i])
    end
end

function _write2disk_text(fid::IO,
                          cwv::CompressedWordVectors{Q,U,D,T,S,H}
                         ) where {Q,U,D,T,S,H}
    # Initialize all variables needed to write
    nrows, vocab_size = cwv.vectors.quantizer.dims
    d = size(cwv.vectors.quantizer.codebooks[1].vectors, 1)
    m = length(cwv.vectors.quantizer.codebooks)
    k = cwv.vectors.quantizer.k
    quanttype_str = string(Q)
    disttype_str = string(D)
    quanteltype_str = string(U)
    origeltype_str = string(T)

    # Start writing text information
    println(fid, "$nrows $vocab_size")
    println(fid, "$d $k $m")
    println(fid, "$quanttype_str")
    println(fid, "$disttype_str")
    println(fid, "$quanteltype_str")
    println(fid, "$origeltype_str")

    # Write vocabulary and compressed vectors
    for i in 1:vocab_size
        nstr = join(map(string, cwv.vectors.data[:,i]), " ")
        println(fid, "$(cwv.vocab[i]) $nstr")
    end

    # Write codebooks
    for i in 1:m
        nstr = join(map(string, cwv.vectors.quantizer.codebooks[i].codes), " ")
        println(fid, "$nstr")
        for j in 1:d
            nstr2 = join(map(string, cwv.vectors.quantizer.codebooks[i].vectors[j,:]), " ")
            println(fid, "$nstr2")
        end
    end
    for i in 1:nrows  # rotation matrix
        nstr = join(map(string, cwv.vectors.quantizer.rot[:,i]), " ")
        println(fid, "$nstr")
    end
end


"""
    $(TYPEDEF)

This task involves a 3-link swimming robot in a viscous fluid, where the goal is to make
it swim forward as fast as possible, by actuating the two joints. The origins of task can
be traced back to Remi Coulom's thesis: ["Reinforcement Learning Using Neural Networks,
with Applications to Motor Control"] (https://tel.archives-ouvertes.fr/tel-00003985/file/tel-00003985.pdf)

* **State: (17, )**
* **Action: (2, )**
* **Observation: (8, )**
"""
mutable struct SwimmerV2{Sim, SSpace, OSpace} <: AbstractMuJoCoEnvironment
    sim::Sim
    statespace::SSpace
    observationspace::OSpace
    last_torso_x::Float64
    randreset_distribution::Uniform{Float64}

    function SwimmerV2(sim::MJSim)
        sspace = MultiShape(simstate=statespace(sim), last_torso_x=ScalarShape(Float64))
        ospace = MultiShape(
            qpos_cropped = VectorShape(Float64, sim.m.nq - 2),
            qvel=statespace(sim).qvel
        )
        env = new{typeof(sim), typeof(sspace), typeof(ospace)}(
            sim,
            sspace,
            ospace,
            0,
            Uniform(-0.1, 0.1)
        )
        reset!(env)
    end
end

SwimmerV2() = first(tconstruct(SwimmerV2, 1))

function LyceumBase.tconstruct(::Type{SwimmerV2}, n::Integer)
    modelpath = joinpath(@__DIR__, "swimmer.xml")
    Tuple(SwimmerV2(s) for s in tconstruct(MJSim, n, modelpath, skip=4))
end

@inline getsim(env::SwimmerV2) = env.sim

@inline LyceumBase.statespace(env::SwimmerV2) = env.statespace

function LyceumBase.getstate!(state, env::SwimmerV2)
    checkaxes(statespace(env), state)
    @uviews state begin
        shaped = statespace(env)(state)
        getstate!(shaped.simstate, env.sim)
        shaped.last_torso_x = env.last_torso_x
    end
    state
end

function LyceumBase.setstate!(env::SwimmerV2, state)
    checkaxes(statespace(env), state)
    @uviews state begin
        shaped = statespace(env)(state)
        setstate!(env.sim, shaped.simstate)
        env.last_torso_x = shaped.last_torso_x
    end
    env
end

@inline LyceumBase.observationspace(env::SwimmerV2) = env.observationspace

function LyceumBase.getobservation!(obs, env::SwimmerV2)
    checkaxes(observationspace(env), obs)
    qpos = env.sim.d.qpos
    @views @uviews obs qpos begin
        shaped = observationspace(env)(obs)
        copyto!(shaped.qpos_cropped, qpos[3:end])
        copyto!(shaped.qvel, env.sim.d.qvel)
    end
    obs
end

function LyceumBase.getreward(state, action, ::Any, env::SwimmerV2)
    checkaxes(statespace(env), state)
    checkaxes(actionspace(env), action)
    @uviews state begin
        shapedstate = statespace(env)(state)
        reward_fwd = (_torso_x(shapedstate, env) - shapedstate.last_torso_x) / timestep(env)
        reward_ctrl = -1e-4 * sum(x->x^2, action)
        reward_fwd + reward_ctrl
    end
end

function LyceumBase.reset!(env::SwimmerV2)
    reset!(env.sim)
    env.last_torso_x = _torso_x(env)
    env
end

function LyceumBase.randreset!(rng::AbstractRNG, env::SwimmerV2)
    reset_nofwd!(env.sim)
    perturb!(rng, env.randreset_distribution, env.sim.d.qpos)
    perturb!(rng, env.randreset_distribution, env.sim.d.qvel)
    forward!(env.sim)
    env.last_torso_x = _torso_x(env)
    env
end

function LyceumBase.step!(env::SwimmerV2)
    env.last_torso_x = _torso_x(env)
    step!(env.sim)
    env
end

@inline _torso_x(shapedstate::ShapedView, ::SwimmerV2) = shapedstate.simstate.qpos[1]
@inline _torso_x(env::SwimmerV2) = env.sim.d.qpos[1]


# Tue 23 Oct 2012 07:10:59 PM EDT
#
# NeHe Tut 4 - Rotate colored (rainbow) triangle and colored (cyan) square
#
# Q - quit

# load necessary GL/SDL routines

using OpenGL
@OpenGL.version "1.0"
@OpenGL.load
using SDL

# initialize variables

bpp                = 16
wintitle           = "NeHe Tut 4"
icontitle          = "NeHe Tut 4"
width              = 640
height             = 480

rtri               = 0.0
rquad              = 0.0

keystate_checked   = false
lastkeycheckTime   = 0
key_repeatinterval = 75 #ms

# open SDL window with an OpenGL context

SDL_Init(SDL_INIT_VIDEO)
#videoInfo = SDL_GetVideoInfo()
videoFlags = (SDL_OPENGL | SDL_GL_DOUBLEBUFFER | SDL_HWPALETTE | SDL_RESIZABLE)
#if videoInfo.hw_available
    videoFlags = (videoFlags | SDL_HWSURFACE)
#else
    #videoFlags = (videoFlags | SDL_SWSURFACE)
#end
#if videoInfo.blit_hw
    videoFlags = (videoFlags | SDL_HWACCEL)
#end
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1)
SDL_SetVideoMode(width, height, bpp, videoFlags)
SDL_WM_SetCaption(wintitle, icontitle)

glViewport(0, 0, width, height)
glClearColor(0.0, 0.0, 0.0, 0.0)
glClearDepth(1.0)
glDepthFunc(GL_LESS)
glEnable(GL_DEPTH_TEST)
glShadeModel(GL_SMOOTH)

glMatrixMode(GL_PROJECTION)
glLoadIdentity()

gluPerspective(45.0,width/height,0.1,100.0)

glMatrixMode(GL_MODELVIEW)

# main drawing loop

while true
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    glLoadIdentity()

    glTranslate(-1.5,0.0,-6.0)
    glRotate(rtri,0.0,1.0,0.0)

    glBegin(GL_POLYGON)
      glColor(1.0,0,0)
      glVertex(0.0,1.0,0.0)
      glColor(0,1.0,0)
      glVertex(1.0,-1.0,0.0)
      glColor(0,0,1.0)
      glVertex(-1.0,-1.0,0.0)
    glEnd()

    glLoadIdentity()

    glTranslate(1.5,0.0,-6.0)
    glRotate(rquad,1.0,0.0,0.0)

    glTranslate(0.8,0,0)

    glColor(0.5,0.5,1.0)
    glBegin(GL_QUADS)
        glVertex(-1.0,1.0,0.0)
        glVertex(1.0,1.0,0.0)
        glVertex(1.0,-1.0,0.0)
        glVertex(-1.0,-1.0,0.0)
    glEnd()

    rtri  +=0.2
    rquad -=0.2

    SDL_GL_SwapBuffers()

    SDL_PumpEvents()
    if SDL_GetTicks() - lastkeycheckTime >= key_repeatinterval
        keystate         = SDL_GetKeyState()
        keystate_checked = true
        lastkeycheckTime = SDL_GetTicks()
    end

    # Sampling rates for event processing are so fast that a single key press
    # lasts through several iterations of this loop.  This means that one press
    # can be seen as 50 or more presses by the SDL event system.  To correct
    # for this, we only check keypresses every 75ms.

    if keystate_checked == true
        if keystate[SDLK_q] == true
            break
        end
        keystate_checked = false
    end
end


# abstract supertype of specific semidiscretizations such as
# - SemidiscretizationHyperbolic for hyperbolic conservation laws
# - SemidiscretizationEulerGravity for Euler with self-gravity
abstract type AbstractSemidiscretization end

"""
    AbstractEquations{NDIMS, NVARS}

An abstract supertype of specific equations such as the compressible Euler equations.
The type parameters encode the number of spatial dimensions (`NDIMS`) and the
number of primary variables (`NVARS`) of the physics model.
"""
abstract type AbstractEquations{NDIMS, NVARS} end

"""
    AbstractMesh{NDIMS}

An abstract supertype of specific mesh types such as `TreeMesh` or `CurvedMesh`.
The type parameters encode the number of spatial dimensions (`NDIMS`).
"""
abstract type AbstractMesh{NDIMS} end

# abstract supertype of specific SBP bases such as a Lobatto-Legendre nodal basis
abstract type AbstractBasisSBP{RealT<:Real} end

# abstract supertype of mortar methods, e.g. using L² projections
abstract type AbstractMortar{RealT<:Real} end

# abstract supertype of mortar methods using L² projection
# which will be specialized for different SBP bases
abstract type AbstractMortarL2{RealT<:Real} <: AbstractMortar{RealT} end

# abstract supertype of functionality related to the analysis of
# numerical solutions, e.g. the calculation of errors
abstract type SolutionAnalyzer{RealT<:Real} end

# abstract supertype of grid-transfer methods used for AMR,
# e.g. refinement and coarsening based on L² projections
abstract type AdaptorAMR{RealT<:Real} end

# abstract supertype of AMR grid-transfer operations using L² projections
# which will be specialized for different SBP bases
abstract type AdaptorL2{RealT<:Real} <: AdaptorAMR{RealT} end

# TODO: Taal decide, which abstract types shall be defined here?

struct BoundaryConditionPeriodic end

"""
    boundary_condition_periodic = BoundaryConditionPeriodic()

A singleton struct indicating periodic boundary conditions.
"""
const boundary_condition_periodic = BoundaryConditionPeriodic()

Base.show(io::IO, ::BoundaryConditionPeriodic) = print(io, "boundary_condition_periodic")


#== # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Description
#
#   Tests related to TLE parser.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ==#

# Macros tle_str and tlenc_str
# ============================

@testset "Macros tle_str and tlenc_str" begin
    # Read the SCDs TLE from the file.
    tles_file = read_tle("./SCDs.tle")

    # Read the same TLEs from a string.
    tles_str = tle"""
        SCD 1
        1 22490U 93009B   18165.62596833  .00000225  00000-0  11410-4 0  9991
        2 22490  24.9690 231.7852 0042844 200.7311 292.7198 14.44524498338066

        SCD 2
        1 25504U 98060A   18165.15074951  .00000201  00000-0  55356-5 0  9994
        2 25504  24.9961  80.1303 0017060 224.4822 286.6438 14.44043397 37312
        """

    # Read the same TLES from a string with wrong checksums.
    # This should not output any exceptions.
    tles_str_nc = tlenc"""
        SCD 1
        1 22490U 93009B   18165.62596833  .00000225  00000-0  11410-4 0  9990
        2 22490  24.9690 231.7852 0042844 200.7311 292.7198 14.44524498338060

        SCD 2
        1 25504U 98060A   18165.15074951  .00000201  00000-0  55356-5 0  9990
        2 25504  24.9961  80.1303 0017060 224.4822 286.6438 14.44043397 37310
        """

    # Compare the TLEs.
    @test length(tles_file) == length(tles_str)
    @test length(tles_file) == length(tles_str_nc)

    for i = 1:length(tles_file)
        for sym in fieldnames(TLE)
            @test getfield(tles_file[i], sym) == getfield(tles_str[i], sym)

            # Skip the comparison of checksums for `tles_str_nc`.
            ( (sym == :checksum_l1) || (sym == :checksum_l2) ) && continue
            @test getfield(tles_file[i], sym) == getfield(tles_str_nc[i], sym)
        end
    end
end

# Function: read_tle_from_string
# ==============================

@testset "Function read_tle_from_string" begin
    # Read the SCD 1 TLE from the file.
    tle_scd1_expected = read_tle("./SCDs.tle")[1]

    # Parse the same TLE using the function `read_tle_from_string`.
    tle_scd1_result = read_tle_from_string(
        "1 22490U 93009B   18165.62596833  .00000225  00000-0  11410-4 0  9991",
        "2 22490  24.9690 231.7852 0042844 200.7311 292.7198 14.44524498338066")[1]

    # Compare the TLEs.
    for sym in fieldnames(TLE)
        # Skip name and satellite number.
        ( (sym == :name) || (sym == :sat_num) ) && continue

        @test getfield(tle_scd1_result, sym) == getfield(tle_scd1_expected, sym)
    end
end

# Date conversion
# ===============

@testset "Epoch year conversion" begin
    tles = tlenc"""
    1 22490U 93009B   75165.62596833  .00000225  00000-0  11410-4 0  9990
    2 22490  24.9690 231.7852 0042844 200.7311 292.7198 14.44524498338060

    1 22490U 93009B   76165.62596833  .00000225  00000-0  11410-4 0  9990
    2 22490  24.9690 231.7852 0042844 200.7311 292.7198 14.44524498338060
    """

    date_1 = JDtoDate(tles[1].epoch)
    date_2 = JDtoDate(tles[2].epoch)

    @test date_1[1] == 2075
    @test date_2[1] == 1976
end

# Test errors
# ===========

@testset "TLE format errors" begin

    @test_throws ErrorException read_tle_from_string("""
    SCD 1
    1 22490U 93009B   18165.62596833  .00000225  00000-0  11410-4 0  9991
    2 22490  24.9690 231.7852 0042844 200.7311 292.7198 14.44524498338066
    3 123
    """)

    # Errors in line 1
    # --------------------------------------------------------------------------

    @test_throws ErrorException read_tle_from_string("""
    SCD 1
    1 022490U 93009B   18165.62596833  .00000225  00000-0  11410-4 0  9991
    2 22490  24.9690 231.7852 0042844 200.7311 292.7198 14.44524498338066
    """)

    @test_throws ErrorException read_tle_from_string("""
    SCD 1
    1 022490U 93009B   181656.2596833  .00000225  00000-0  11410-4 0  9991
    2 22490  24.9690 231.7852 0042844 200.7311 292.7198 14.44524498338066
    """)

    # TODO: Errors in line 2.
end

@testset "Checksum errors" begin
    @test_throws ErrorException read_tle("./SCDs-wrong_checksum.tle")
    @test_throws LoadError @eval tle"""
    SCD 1
    1 22490U 93009B   18165.62596833  .00000225  00000-0  11410-4 0  9991
    2 22490  24.9690 231.7852 0042844 200.7311 292.7198 14.44524498338066

    SCD 2
    1 25504U 98060A   18165.15074951  .00000201  00000-0  55356-5 0  9994
    2 25504  24.9961  80.1303 0017060 224.4822 286.6438 14.44043397 37313
    """
    @test_throws ErrorException read_tle_from_string(
        "1 22490U 93009B   18165.62596833  .00000225  00000-0  11410-4 0  9991",
        "2 22490  24.9690 231.7852 0042844 200.7311 292.7198 14.44524498338063",
        verify_checksum = true)
end


abstract type Perturbation end

"""
    mutable struct BranchTrip <: Perturbation
        time::Float64
        branch_type::Type{<:PowerSystems.ACBranch}
        branch_name::String
    end

A BranchTrip completely disconnects a branch from the system. Currently there is only support for static branches disconnection, `PowerSystems.Line` and `PowerSystems.Transformer2W`.
Future releases will provide support for a Dynamic Line disconnection.
**Note:** Islanding is currently not supported in `PowerSimulationsDynamics.jl`. If a `BranchTrip` isolates a generation unit, the system may diverge due to the isolated generator.

# Arguments:

- `time::Float64` : Defines when the Branch Trip will happen. This time should be inside the time span considered in the Simulation
- `branch_tipe::Type{<:PowerSystems.ACBranch}` : Type of branch disconnected
- `branch_name::String` : User defined name for identifying the branch
"""
mutable struct BranchTrip <: Perturbation
    time::Float64
    branch_type::Type{<:PSY.ACBranch}
    branch_name::String
end

"""
    mutable struct BranchImpedanceChange <: Perturbation
        time::Float64
        branch_type::Type{<:PSY.ACBranch}
        branch_name::String
        multiplier::Float64
    end

A BranchImpedanceChange change the impedance of a branch by a user defined multiplier. Currently there is only support for static branches disconnection, `PowerSystems.Line` and `PowerSystems.Transformer2W`.
Future releases will provide support for a Dynamic Line disconnection.

# Arguments:

- `time::Float64` : Defines when the Branch Impedance Change will happen. This time should be inside the time span considered in the Simulation
- `branch_tipe::Type{<:PowerSystems.ACBranch}` : Type of branch modified
- `branch_name::String` : User defined name for identifying the branch
- `multiplier::Float64` : User defined value for impedance multiplier.
"""
mutable struct BranchImpedanceChange <: Perturbation
    time::Float64
    branch_type::Type{<:PSY.ACBranch}
    branch_name::String
    multiplier::Float64
end

function _get_branch_for_perturbation(
    sys::PSY.System,
    perturbation::T,
) where {T <: Perturbation}
    if perturbation.branch_type == PSY.DynamicBranch
        error("DynamicBranch is not supported currently with perturbation $T")
    end
    branch = PSY.get_component(perturbation.branch_type, sys, perturbation.branch_name)
    if branch === nothing || !PSY.get_available(branch)
        throw(
            IS.ConflictingInputsError(
                "Branch $(perturbation.branch_type) $(perturbation.branch_name) not existent or unavailable in the system. Use PSY.show_components(system, $(perturbation.branch_type)) to check valid component names",
            ),
        )
    end
    return branch
end

function get_affect(::SimulationInputs, sys::PSY.System, pert::BranchImpedanceChange)
    branch = _get_branch_for_perturbation(sys, pert)
    mult = 0.0
    if pert.multiplier < 0.0
        throw(
            IS.ConflictingInputsError(
                "Negative multipliers are not allowed for BranchImpedanceChange perturbations",
            ),
        )
    elseif pert.multiplier < 1.0
        mult = 1.0 - pert.multiplier
    elseif pert.multiplier > 1.0
        mult = pert.multiplier - 1.0
    else
        @assert pert.multiplier == 1.0
    end

    return (integrator) -> begin
        @debug "Changing impedance line $(PSY.get_name(branch)) by a factor of $(pert.multiplier)"
        ybus_update!(integrator.p, branch, mult)
    end

    return
end

function get_affect(::SimulationInputs, sys::PSY.System, pert::BranchTrip)
    branch = _get_branch_for_perturbation(sys, pert)
    return (integrator) -> begin
        @debug "Tripping line $(PSY.get_name(branch))"
        ybus_update!(integrator.p, branch, -1.0)
    end
    return
end

function _record_change!(
    ybus::SparseArrays.SparseMatrixCSC{Float64, Int},
    bus_from_no::Int,
    bus_to_no::Int,
    n_buses::Int,
    Y11_real::Float64,
    Y11_imag::Float64,
    Y22_real::Float64,
    Y22_imag::Float64,
    Y12_real::Float64,
    Y21_real::Float64,
    Y12_imag::Float64,
    Y21_imag::Float64,
)

    # First Quadrant Real Part Changes
    ybus[bus_from_no, bus_from_no] += Y11_real
    ybus[bus_from_no, bus_to_no] += Y12_real
    ybus[bus_to_no, bus_from_no] += Y21_real
    ybus[bus_to_no, bus_to_no] += Y22_real

    # Second Quadrant Imag Part changes
    ybus[bus_from_no, bus_from_no + n_buses] += Y11_imag
    ybus[bus_from_no, bus_to_no + n_buses] += Y12_imag
    ybus[bus_to_no, bus_from_no + n_buses] += Y21_imag
    ybus[bus_to_no, bus_to_no + n_buses] += Y22_imag

    # Third Quadrant -1*Imag Part changes
    ybus[bus_from_no + n_buses, bus_from_no] -= Y11_imag
    ybus[bus_from_no + n_buses, bus_to_no] -= Y12_imag
    ybus[bus_to_no + n_buses, bus_from_no] -= Y21_imag
    ybus[bus_to_no + n_buses, bus_to_no] -= Y22_imag

    # Fourth Quadrant Real Part Changes
    ybus[bus_from_no + n_buses, bus_from_no + n_buses] += Y11_real
    ybus[bus_from_no + n_buses, bus_to_no + n_buses] += Y12_real
    ybus[bus_to_no + n_buses, bus_from_no + n_buses] += Y21_real
    ybus[bus_to_no + n_buses, bus_to_no + n_buses] += Y22_real
    return
end

function ybus_update!(
    ybus::SparseArrays.SparseMatrixCSC{Float64, Int},
    b::PSY.ACBranch,
    num_bus::Dict{Int, Int},
    mult::Float64,
)
    arc = PSY.get_arc(b)
    bus_from_no = num_bus[PSY.get_number(arc.from)]
    bus_to_no = num_bus[arc.to.number]
    n_buses = length(num_bus)

    Y_l = (1 / (PSY.get_r(b) + PSY.get_x(b) * 1im))

    Y11_real = mult * real(Y_l)
    Y11_imag = mult * (imag(Y_l) + PSY.get_b(b).from)
    Y22_real = mult * real(Y_l)
    Y22_imag = mult * (imag(Y_l) + PSY.get_b(b).to)
    Y12_real = Y21_real = -mult * real(Y_l)
    Y12_imag = Y21_imag = -mult * imag(Y_l)

    _record_change!(
        ybus,
        bus_from_no,
        bus_to_no,
        n_buses,
        Y11_real,
        Y11_imag,
        Y22_real,
        Y22_imag,
        Y12_real,
        Y21_real,
        Y12_imag,
        Y21_imag,
    )
    return
end

function ybus_update!(
    ybus::SparseArrays.SparseMatrixCSC{Float64, Int},
    b::PSY.TapTransformer,
    num_bus::Dict{Int, Int},
    mult::Float64,
)
    arc = PSY.get_arc(b)
    bus_from_no = num_bus[PSY.get_number(arc.from)]
    bus_to_no = num_bus[arc.to.number]

    Y_t = 1 / (PSY.get_r(b) + PSY.get_x(b) * 1im)
    c = 1 / PSY.get_tap(b)
    b = PSY.get_primary_shunt(b)

    Y11 = (Y_t * c^2)
    Y21 = Y12 = (-Y_t * c)
    Y22 = Y_t + (1im * b)

    Y11_real = mult * real(Y11)
    Y11_imag = mult * imag(Y12)
    Y22_real = mult * real(Y22)
    Y22_imag = mult * imag(Y22)
    Y12_real = -mult * real(Y12)
    Y21_real = -mult * real(Y21)
    Y12_imag = -mult * imag(Y12)
    Y21_imag = -mult * imag(Y21)

    _record_change!(
        ybus,
        bus_from_no,
        bus_to_no,
        n_buses,
        Y11_real,
        Y11_imag,
        Y22_real,
        Y22_imag,
        Y12_real,
        Y21_real,
        Y12_imag,
        Y21_imag,
    )
    return
end

function ybus_update!(
    ybus::SparseArrays.SparseMatrixCSC{Float64, Int},
    b::PSY.PhaseShiftingTransformer,
    num_bus::Dict{Int, Int},
    mult::Float64,
)
    arc = PSY.get_arc(b)
    bus_from_no = num_bus[PSY.get_number(arc.from)]
    bus_to_no = num_bus[arc.to.number]

    Y_t = 1 / (PSY.get_r(b) + PSY.get_x(b) * 1im)
    tap = (PSY.get_tap(b) * exp(PSY.get_α(b) * 1im))
    c_tap = (PSY.get_tap(b) * exp(-1 * PSY.get_α(b) * 1im))
    b = PSY.get_primary_shunt(b)

    Y11 = (Y_t / abs(tap)^2)
    Y12 = (-Y_t / c_tap)
    Y21 = (-Y_t / tap)
    Y22 = Y_t

    Y11_real = mult * real(Y11)
    Y11_imag = mult * imag(Y12)
    Y22_real = mult * real(Y22)
    Y22_imag = mult * imag(Y22)
    Y12_real = -mult * real(Y12)
    Y21_real = -mult * real(Y21)
    Y12_imag = -mult * imag(Y12)
    Y21_imag = -mult * imag(Y21)

    _record_change!(
        ybus,
        bus_from_no,
        bus_to_no,
        n_buses,
        Y11_real,
        Y11_imag,
        Y22_real,
        Y22_imag,
        Y12_real,
        Y21_real,
        Y12_imag,
        Y21_imag,
    )

    return
end

function ybus_update!(
    ybus::SparseArrays.SparseMatrixCSC{Float64, Int},
    fa::PSY.FixedAdmittance,
    num_bus::Dict{Int, Int},
    mult::Float64,
)
    bus = PSY.get_bus(fa)
    bus_ix = num_bus[PSY.get_number(bus)]
    ybus[bus_ix, bus_ix] += mult * fa.Y
    return
end

function ybus_update!(integrator_params, branch::PSY.ACBranch, mult::Float64)
    ybus_update!(integrator_params.ybus_rectangular, branch, integrator_params.lookup, mult)
    return
end

"""
    function NetworkSwitch(
        time::Float64,
        ybus::SparseArrays.SparseMatrixCSC{Complex{Float64}, Int},
    )

Allows to modify directly the admittance matrix, Ybus, used in the Simulation.
This allows the user to perform branch modifications, three phase faults (with impedance larger than zero) or branch trips, as long as the new Ybus provided captures that perturbation.

# Arguments:

- `time::Float64` : Defines when the Network Switch will happen. This time should be inside the time span considered in the Simulation
- `ybus::SparseArrays.SparseMatrixCSC{Complex{Float64}, Int}` : Complex admittance matrix
"""
mutable struct NetworkSwitch <: Perturbation
    time::Float64
    ybus_rectangular::SparseArrays.SparseMatrixCSC{Float64, Int}
    function NetworkSwitch(
        time::Float64,
        ybus::SparseArrays.SparseMatrixCSC{Complex{Float64}, Int},
    )
        n_bus = size(ybus)[1]
        if n_bus < 15_000
            I = PSY._goderya(ybus)
            if length(Set(I)) != n_bus
                error("The Ybus provided has islands and can't be used in the simulation")
            end
        else
            @warn(
                "The number of buses in the Ybus provided is $n_bus and is too large to be verified for connectivity"
            )
        end
        # TODO: Improve performance here
        ybus_rect = transform_ybus_to_rectangular(ybus)
        new(time, ybus_rect)
    end
end

function NetworkSwitch(time::Float64, ybus::PSY.Ybus)
    return NetworkSwitch(time, ybus.data)
end

function get_affect(::SimulationInputs, ::PSY.System, pert::NetworkSwitch)
    return (integrator) -> begin
        # TODO: This code can be more performant using SparseMatrix methods
        for (i, v) in enumerate(pert.ybus_rectangular)
            @debug "Changing Ybus network"
            integrator.p.ybus_rectangular[i] = v
        end
        return
    end
end

"""
    mutable struct ControlReferenceChange <: Perturbation
        time::Float64
        device::PowerSystems.DynamicInjection
        signal::Symbol
        ref_value::Float64
    end

A ControlReferenceChange allows to change the reference setpoint provided by a generator/inverter.

# Arguments:

- `time::Float64` : Defines when the Control Reference Change will happen. This time should be inside the time span considered in the Simulation
- `device::Type{<:PowerSystems.DynamicInjection}` : Dynamic device modified
- `signal::Symbol` : determines which reference setpoint will be modified. The accepted signals are:
    - `:P_ref`: Modifies the active power reference setpoint.
    - `:V_ref`: Modifies the voltage magnitude reference setpoint (if used).
    - `:Q_ref`: Modifies the reactive power reference setpoint (if used).
    - `:ω_ref`: Modifies the frequency setpoint.
- `ref_value::Float64` : User defined value for setpoint reference.
"""
mutable struct ControlReferenceChange <: Perturbation
    time::Float64
    device::PSY.DynamicInjection
    signal::Symbol
    ref_value::Float64

    function ControlReferenceChange(
        time::Float64,
        device::PSY.DynamicInjection,
        signal::Symbol,
        ref_value::Float64,
    )
        if signal ∉ ACCEPTED_CONTROL_REFS
            error("Signal $signal not accepted as a control reference change")
        end
        new(time, device, signal, ref_value)
    end
end

function _is_same_device(
    device1::DynamicWrapper{T},
    device2::T,
) where {T <: PSY.DynamicInjection}
    return PSY.get_name(device1) == PSY.get_name(device2)
end

function _is_same_device(
    ::DynamicWrapper{T},
    ::U,
) where {T <: PSY.DynamicInjection, U <: PSY.DynamicInjection}
    return false
end

function _find_device_index(inputs::SimulationInputs, device::PSY.DynamicInjection)
    wrapped_devices = get_dynamic_injectors(inputs)
    wrapped_device_ixs = findall(x -> _is_same_device(x, device), wrapped_devices)
    if isempty(wrapped_device_ixs)
        error(
            "Device $(typeof(device))-$(PSY.get_name(device)) not found in the simulation inputs",
        )
    end
    return wrapped_device_ixs[1]
end

function _is_same_device(
    device1::StaticWrapper{T},
    device2::U,
) where {T <: PSY.StaticInjection, U <: PSY.StaticInjection}
    if T != U
        return false
    end
    if PSY.get_name(device1) == PSY.get_name(device2)
        return true
    elseif PSY.get_name(device1) != PSY.get_name(device2)
        return false
    else
        error("comparison failed for $device1 and $device2")
    end
end

function _find_device_index(inputs::SimulationInputs, device::PSY.StaticInjection)
    wrapped_devices = get_static_injectors(inputs)
    wrapped_device_ixs = findall(x -> _is_same_device(x, device), wrapped_devices)
    if isempty(wrapped_device_ixs)
        error(
            "Device $(typeof(device))-$(PSY.get_name(device)) not found in the simulation inputs",
        )
    end
    return wrapped_device_ixs[1]
end

function get_affect(inputs::SimulationInputs, ::PSY.System, pert::ControlReferenceChange)
    wrapped_device_ix = _find_device_index(inputs, pert.device)
    return (integrator) -> begin
        wrapped_device = get_dynamic_injectors(integrator.p)[wrapped_device_ix]
        @debug "Changing $(PSY.get_name(wrapped_device)) $(pert.signal) to $(pert.ref_value)"
        getfield(wrapped_device, pert.signal)[] = pert.ref_value
        return
    end
end

"""
    mutable struct SourceBusVoltageChange <: Perturbation
        time::Float64
        device::PSY.Source
        signal_index::Int
        ref_value::Float64
    end

A `SourceBusVoltageChange` allows to change the reference setpoint provided by a voltage source.

# Arguments:

- `time::Float64` : Defines when the Control Reference Change will happen. This time should be inside the time span considered in the Simulation
- `device::Type{<:PowerSystems.Source}` : Device modified
- `signal::Int` : determines which reference setpoint will be modified. The accepted signals are:
    - `1`: Modifies the internal voltage magnitude reference setpoint.
    - `2`: Modifies the internal voltage angle reference setpoint.
- `ref_value::Float64` : User defined value for setpoint reference.
"""
mutable struct SourceBusVoltageChange <: Perturbation
    time::Float64
    device::PSY.Source
    signal_index::Int
    ref_value::Float64
end

function get_affect(inputs::SimulationInputs, ::PSY.System, pert::SourceBusVoltageChange)
    wrapped_device_ix = _find_device_index(inputs, pert.device)
    return (integrator) -> begin
        wrapped_device = get_static_injectors(integrator.p)[wrapped_device_ix]
        set_V_ref(wrapped_device, pert.ref_value)
        return
    end
end

"""
    mutable struct GeneratorTrip <: Perturbation
        time::Float64
        device::PowerSystems.DynamicInjection
    end

A `GeneratorTrip` allows to disconnect a Dynamic Generation unit from the system at a specified time.

# Arguments:

- `time::Float64` : Defines when the Generator Trip will happen. This time should be inside the time span considered in the Simulation
- `device::Type{<:PowerSystems.DynamicInjection}` : Device to be disconnected
"""
mutable struct GeneratorTrip <: Perturbation
    time::Float64
    device::PSY.DynamicInjection
end

function get_affect(inputs::SimulationInputs, ::PSY.System, pert::GeneratorTrip)
    wrapped_device_ix = _find_device_index(inputs, pert.device)
    return (integrator) -> begin
        wrapped_device = get_dynamic_injectors(integrator.p)[wrapped_device_ix]
        ix_range = get_ix_range(wrapped_device)
        @debug "Changing connection status $(PSY.get_name(wrapped_device)), setting states $ix_range to 0.0"
        if integrator.du !== nothing
            @debug "setting du $ix_range to 0.0"
            integrator.du[ix_range] .= 0.0
        end
        integrator.u[ix_range] .= 0.0
        set_connection_status(wrapped_device, 0)
    end
end

"""
    mutable struct LoadChange <: Perturbation
        time::Float64
        device::PowerSystems.ElectricLoad
        signal::Symbol
        ref_value::Float64
    end

A LoadChange allows to change the active or reactive power setpoint from a load.

# Arguments:

- `time::Float64` : Defines when the Load Change will happen. This time should be inside the time span considered in the Simulation
- `device::Type{<:PowerSystems.ElectricLoad}` : Dynamic device modified
- `signal::Symbol` : determines which reference setpoint will be modified. The accepted signals are:
    - `:P_ref`: Modifies the active power reference setpoint.
    - `:Q_ref`: Modifies the reactive power reference setpoint.
- `ref_value::Float64` : User defined value for setpoint reference.
"""
mutable struct LoadChange <: Perturbation
    time::Float64
    device::PSY.ElectricLoad
    signal::Symbol
    ref_value::Float64

    function LoadChange(
        time::Float64,
        device::PSY.ElectricLoad,
        signal::Symbol,
        ref_value::Float64,
    )
        if signal ∉ [:P_ref, :Q_ref]
            error("Signal $signal not accepted as a control reference change in Loads")
        end
        new(time, device, signal, ref_value)
    end
end

function _find_device_index(inputs::SimulationInputs, device::PSY.ElectricLoad)
    wrapped_devices = get_static_loads(inputs)
    wrapped_device_ixs = findall(x -> _is_same_device(x, device), wrapped_devices)
    if isempty(wrapped_device_ixs)
        error(
            "Device $(typeof(device))-$(PSY.get_name(device)) not found in the simulation inputs",
        )
    end
    return wrapped_device_ixs[1]
end

function get_affect(inputs::SimulationInputs, ::PSY.System, pert::LoadChange)
    wrapped_device_ix = _find_device_index(inputs, pert.device)
    return (integrator) -> begin
        wrapped_device = get_static_loads(integrator.p)[wrapped_device_ix]
        @debug "Changing $(PSY.get_name(wrapped_device)) $(pert.signal) to $(pert.ref_value)"
        getfield(wrapped_device, pert.signal)[] = pert.ref_value
        return
    end
end

"""
    mutable struct LoadTrip <: Perturbation
        time::Float64
        device::PowerSystems.ElectricLoad
    end

A `LoadTrip` allows the user to disconnect a load from the system.

# Arguments:

- `time::Float64` : Defines when the Generator Trip will happen. This time should be inside the time span considered in the Simulation
- `device::Type{<:PowerSystems.ElectricLoad}` : Device to be disconnected
"""
mutable struct LoadTrip <: Perturbation
    time::Float64
    device::PSY.ElectricLoad
end

function get_affect(inputs::SimulationInputs, ::PSY.System, pert::LoadTrip)
    wrapped_device_ix = _find_device_index(inputs, pert.device)
    return (integrator) -> begin
        wrapped_device = get_static_loads(integrator.p)[wrapped_device_ix]
        @info "Changing connection status $(PSY.get_name(wrapped_device))"
        set_connection_status(wrapped_device, 0)
        return
    end
end


abstract type RpTAlgorithm end

function get_rpt_alg()
    implemented = [ijk(), ijk2(), abc()]
    N = Options.get("pt_alg")
    try
        return implemented[N]
    catch BoundsError
        throw(FermiException("implementation number $N not available for RCCSD(T)."))
    end
end
"""
    Fermi.CoupledCluster.RCCSDpT

Fermi struct that holds information about RCCSD(T) wavefunctions

_struct tree:_

**RCCSD(T)** <: AbstractCCWavefunction <: AbstractCorrelatedWavefunction <: AbstractWavefunction
"""
struct RCCSDpT{T} <: AbstractCCWavefunction
    CCSD::RCCSD{T}
    energy::T
    correction::T
end

"""
    Fermi.CoupledCluster.get_pt_alg

Returns a singleton type corresponding to a RCCSD(T) implementation based on the options.
"""
function get_pt_alg(N::Int = Options.get("pt_alg"))
    try
        return get_pt_alg(Val(N))
    catch MethodError
        throw(FermiException("implementation number $N not available for RCCSD(T)."))
    end
end

# Implementations
struct ijk <: RpTAlgorithm end
include("ijk.jl")
get_pt_alg(x::Val{1}) = ijk()
struct ijk2 <: RpTAlgorithm end
include("ijk2.jl")
get_pt_alg(x::Val{2}) = ijk2()
struct abc <: RpTAlgorithm end
include("abc.jl")
get_pt_alg(x::Val{3}) = abc()

function RCCSDpT(x...)
    if !any(i-> i isa RpTAlgorithm, x)
        RCCSDpT(x..., get_rpt_alg())
    else
        # Print the type of arguments given for a better feedback
        args = "("
        for a in x[1:end-1]
            args *= "$(typeof(a)), "
        end
        args = args[1:end-2]*")"
        throw(FermiException("invalid arguments for RCCSD(T) method: $args"))
    end
end


#i64o-helpers.jl
#various utility functions that operate on ArrayNums as well as metaprogramming
#macros to help duplicate functions for both standard 64-bit integers and
#arraynums.

doc"""
`Unums.max_fsize(::Int64)` retrieves the maximum possible fsize value based on
the FSS value.
"""
max_fsize(FSS::Int64) = to16((1 << FSS) - 1)

#two helper functions to enable fracproc.
function __ffcall(s::Symbol, inner_block, tname, p...)
  fracname = Symbol("frac_", s, "!")
  #outer_curly is the curly variadic notation for the function name.
  outer_curly = Expr(:curly)
  append!(outer_curly.args, [fracname, :ESS, :FSS])
  #build up the outer call, which is the representation for the full function definition
  outer_call = Expr(:call)
  append!(outer_call.args, [outer_curly, :(x::$tname{ESS,FSS}), p...])
  #build up the outer block, which is the actual code.
  outer_block = Expr(:block)
  push!(outer_block.args, inner_block)
  #build up the full outer function, which merges the call and the block.
  outer_function = Expr(:function)
  push!(outer_function.args, outer_call)
  push!(outer_function.args, outer_block)
  outer_function
end

function __smallexpr(s::Symbol, p...)
  #build up the inner call mechanism
  inner_call = Expr(:call)
  inner_call.args = [s, :(x.fraction), p...]
  :(x.fraction = $inner_call; x)
end

function __largeexpr(s::Symbol, p...)
  bangsym = Symbol(s, "!")
  inner_call = Expr(:call)
  inner_call.args = [bangsym, :(x.fraction), p...]
  :($inner_call; x)
end

doc"""
`Unums.@fracproc(fn)` generates two functions that takes Unum{ESS,FSS} and executes
fn on the fraction part of the unum, if FSS < 7.  if FSS >= 7 then it executes
fn! on the fraction part of the unum.
"""
macro fracproc(name, params...)
  fracname = Symbol("frac_", name, "!")
  bangname = Symbol(name, "!")
  smallexpr = __smallexpr(name, params...)
  largeexpr = __largeexpr(name, params...)
  smallcall = __ffcall(name, smallexpr, :UnumSmall, params...)
  largecall = __ffcall(name, largeexpr, :UnumLarge, params...)
  esc(quote
    Base.@__doc__ $smallcall
    $largecall
  end)
end


import DifferentialEquations
using ReachabilityAnalysis: _solve_ensemble, ReachSolution

@testset "Simulation traces using DifferentialEquations.jl solvers" begin
    prob = @ivp(x' = 1.0x, x(0) ∈ [1/2])

    # `solve` extends CommonSolve.jl
    sol = DifferentialEquations.solve(prob, tspan=(0.0, 1.0))
    @test sol isa ReachSolution

    # ensemble problems API
    sol = _solve_ensemble(prob, tspan=(0.0, 1.0))
    @test sol isa DifferentialEquations.EnsembleSolution
    sol = _solve_ensemble(prob, T=1.0)
    @test sol isa DifferentialEquations.EnsembleSolution

    # inplace (default)
    sol = _solve_ensemble(prob, T=1.0, inplace=true)

    # out-of-place
    sol = _solve_ensemble(prob, T=1.0, inplace=false)
end


@testset "get_quantified_variables_form" begin
    @customtest String(get_quantified_variables_form(Formula("*{foo}P(foo(foo), foo, bar) & E(baz, bar)"))) ==
        "?{baz}?{bar}(*{foo}P(foo(foo), foo, bar) & E(baz, bar))"
    @customtest String(get_quantified_variables_form(Formula("*{x}?{y}*{z}?{w}(~P(a, w) | Q(f(x), y))"))) ==
        "?{a}*{x}?{y}*{z}?{w}(~P(a, w) | Q(f(x), y))"
end


@enum generator_inner_vars begin
    τe_var = 1
    τm_var = 2
    Vf_var = 3
    V_pss_var = 4
    VR_gen_var = 5
    VI_gen_var = 6
    ψd_var = 7
    ψq_var = 8
end

Base.to_index(ix::generator_inner_vars) = Int(ix)
include("generator_components/machine.jl")
include("generator_components/shaft.jl")
include("generator_components/pss.jl")
include("generator_components/avr.jl")
include("generator_components/turbinegov.jl")

"""
"""
mutable struct DynGenerator{M <: Machine,
                            S <: Shaft,
                            A <: AVR,
                            TG<: TurbineGov,
                            P <: PSS} <: DynInjection
    number::Int64
    name::Symbol
    bus::PSY.Bus
    ω_ref::Float64
    V_ref::Float64
    P_ref::Float64
    machine::M
    shaft::S
    avr::A
    tg::TG
    pss::P
    n_states::Int64
    states::Vector{Symbol}
    inner_vars::Vector{Float64}
    local_state_ix::Dict{GeneratorComponent,Vector{Int64}}
    input_port_mapping::Dict{GeneratorComponent,Vector{Int64}}

        function DynGenerator(number::Int64,
                              name::Symbol,
                              bus::PSY.Bus,
                              ω_ref::Float64,
                              V_ref::Float64,
                              P_ref::Float64,
                              machine::M,
                              shaft::S,
                              avr::A,
                              tg::TG,
                              pss::P) where {M <: Machine,
                                             S <: Shaft,
                                             A <: AVR,
                                             TG <: TurbineGov,
                                             P <: PSS}

            n_states = (machine.n_states +
                       shaft.n_states +
                       avr.n_states +
                       tg.n_states +
                       pss.n_states)

            states = vcat(machine.states,
                          shaft.states,
                          avr.states,
                          tg.states,
                          pss.states)

            components = [machine, shaft, avr, tg, pss]

            new{M,S,A,TG,P}(number,
                        name,
                        bus,
                        ω_ref,
                        V_ref,
                        P_ref,
                        machine,
                        shaft,
                        avr,
                        tg,
                        pss,
                        n_states,
                        states,
                        zeros(8),
                        _make_state_mapping(components, states),
                        _make_port_mapping(components, states))

        end
  end


# This file is a part of Julia. License is MIT: https://julialang.org/license

module PkgToMaxSumInterface

using ...Types, ...Query, ..VersionWeights

export Interface, compute_output_dict, greedysolver,
       verify_solution, enforce_optimality!

# A collection of objects which allow interfacing external (Pkg) and
# internal (MaxSum) representation
mutable struct Interface
    # requirements and dependencies, in external representation
    reqs::Requires
    deps::Dict{String,Dict{VersionNumber,Available}}

    # packages list
    pkgs::Vector{String}

    # number of packages
    np::Int

    # states per package: one per version + uninstalled
    spp::Vector{Int}

    # pakage dict: associates an index to each package name
    pdict::Dict{String,Int}

    # package versions: for each package, keep the list of the
    #                   possible version numbers; this defines a
    #                   mapping from version numbers of a package
    #                   to indices
    pvers::Vector{Vector{VersionNumber}}

    # versions dict: associates a version index to each package
    #                version; such that
    #                  pvers[p0][vdict[p0][vn]] = vn
    vdict::Vector{Dict{VersionNumber,Int}}

    # version weights: the weight for each version of each package
    #                  (versions include the uninstalled state; the
    #                   higher the weight, the more favored the version)
    vweight::Vector{Vector{VersionWeight}}

    function Interface(reqs::Requires, deps::Dict{String,Dict{VersionNumber,Available}})
        # generate pkgs
        pkgs = sort!(String[keys(deps)...])

        np = length(pkgs)

        # generate pdict
        pdict = Dict{String,Int}(pkgs[i] => i for i = 1:np)

        # generate spp and pvers
        spp = Vector{Int}(uninitialized, np)

        pvers = [VersionNumber[] for i = 1:np]

        for (p,depsp) in deps, vn in keys(depsp)
            p0 = pdict[p]
            push!(pvers[p0], vn)
        end
        for p0 = 1:np
            sort!(pvers[p0])
            spp[p0] = length(pvers[p0]) + 1
        end

        # generate vdict
        vdict = [Dict{VersionNumber,Int}() for p0 = 1:np]
        for (p,depsp) in deps
            p0 = pdict[p]
            vdict0 = vdict[p0]
            pvers0 = pvers[p0]
            for vn in keys(depsp)
                for v0 in 1:length(pvers0)
                    if pvers0[v0] == vn
                        vdict0[vn] = v0
                        break
                    end
                end
            end
        end

        ## generate wveights:
        vweight = Vector{Vector{VersionWeight}}(uninitialized, np)
        for p0 = 1:np
            pvers0 = pvers[p0]
            spp0 = spp[p0]
            vweight0 = vweight[p0] = Vector{VersionWeight}(uninitialized, spp0)
            for v0 = 1:spp0-1
                vweight0[v0] = VersionWeight(pvers0[v0])
            end
            vweight0[spp0] = VersionWeight(v"0") # last version means uninstalled
        end

        return new(reqs, deps, pkgs, np, spp, pdict, pvers, vdict, vweight)
    end
end

# The output format is a Dict which associates a VersionNumber to each installed package name
function compute_output_dict(sol::Vector{Int}, interface::Interface)
    pkgs = interface.pkgs
    np = interface.np
    pvers = interface.pvers
    spp = interface.spp

    want = Dict{String,VersionNumber}()
    for p0 = 1:np
        p = pkgs[p0]
        s = sol[p0]
        if s != spp[p0]
            v = pvers[p0][s]
            want[p] = v
        end
    end

    return want
end

# Produce a trivial solution: try to maximize each version;
# bail out as soon as some non-trivial requirements are detected.
function greedysolver(interface::Interface)
    reqs = interface.reqs
    deps = interface.deps
    spp = interface.spp
    pdict = interface.pdict
    pvers = interface.pvers
    np = interface.np

    # initialize solution: all uninstalled
    sol = [spp[p0] for p0 = 1:np]

    # set up required packages to their highest allowed versions
    for (rp,rvs) in reqs
        rp0 = pdict[rp]
        # look for the highest version which satisfies the requirements
        rv = spp[rp0] - 1
        while rv > 0
            rvn = pvers[rp0][rv]
            rvn ∈ rvs && break
            rv -= 1
        end
        @assert rv > 0
        sol[rp0] = rv
    end

    # we start from required packages and explore the graph
    # following dependencies
    staged = Set{String}(keys(reqs))
    seen = copy(staged)

    while !isempty(staged)
        staged_next = Set{String}()
        for p in staged
            p0 = pdict[p]
            @assert sol[p0] < spp[p0]
            vn = pvers[p0][sol[p0]]
            a = deps[p][vn]

            # scan dependencies
            for (rp,rvs) in a.requires
                rp0 = pdict[rp]
                # look for the highest version which satisfies the requirements
                rv = spp[rp0] - 1
                while rv > 0
                    rvn = pvers[rp0][rv]
                    rvn ∈ rvs && break
                    rv -= 1
                end
                # if we found a version, and the package was uninstalled
                # or the same version was already selected, we're ok;
                # otherwise we can't be sure what the optimal configuration is
                # and we bail out
                if rv > 0 && (sol[rp0] == spp[rp0] || sol[rp0] == rv)
                    sol[rp0] = rv
                else
                    return (false, Int[])
                end

                rp ∈ seen || push!(staged_next, rp)
            end
        end
        union!(seen, staged_next)
        staged = staged_next
    end

    @assert verify_solution(sol, interface)

    return true, sol
end

# verifies that the solution fulfills all hard constraints
# (requirements and dependencies)
function verify_solution(sol::Vector{Int}, interface::Interface)
    reqs = interface.reqs
    deps = interface.deps
    spp = interface.spp
    pdict = interface.pdict
    pvers = interface.pvers
    vdict = interface.vdict

    # verify requirements
    for (p,vs) in reqs
        p0 = pdict[p]
        sol[p0] != spp[p0] || return false
        vn = pvers[p0][sol[p0]]
        vn ∈ vs || return false
    end

    # verify dependencies
    for (p,d) in deps
        p0 = pdict[p]
        vdict0 = vdict[p0]
        for (vn,a) in d
            v0 = vdict0[vn]
            if sol[p0] == v0
                for (rp, rvs) in a.requires
                    p1 = pdict[rp]
                    if sol[p1] == spp[p1]
                        println("""
                                VERIFICATION ERROR: REQUIRED DEPENDENCY NOT INSTALLED
                                    package p=$p (p0=$p0) version=$vn (v0=$v0) requires package rp=$rp in version set rvs=$rvs
                                    but package $rp is not being installed (p1=$p1 sol[p1]=$(sol[p1]) == spp[p1]=$(spp[p1]))
                                """)
                        return false
                    end
                    vn1 = pvers[p1][sol[p1]]
                    if vn1 ∉ rvs
                        println("""
                                VERIFICATION ERROR: INVALID VERSION
                                    package p=$p (p0=$p0) version=$vn (v0=$v0) requires package rp=$rp in version set rvs=$rvs
                                    but package $rp version is being set to $vn1 (p1=$p1 sol[p1]=$(sol[p1]) spp[p1]=$(spp[p1]))
                                """)
                        return false
                    end
                end
            end
        end
    end
    return true
end

# Push the given solution to a local optimium if needed
function enforce_optimality!(sol::Vector{Int}, interface::Interface)
    np = interface.np

    reqs = interface.reqs
    deps = interface.deps
    pkgs = interface.pkgs
    spp = interface.spp
    pdict = interface.pdict
    pvers = interface.pvers
    vdict = interface.vdict

    # prepare some useful structures
    # pdeps[p0][v0] has all dependencies of package p0 version v0
    pdeps = [Vector{Requires}(uninitialized, spp[p0]-1) for p0 = 1:np]
    # prevdeps[p1][p0][v0] is the VersionSet of package p1 which package p0 version v0
    # depends upon
    prevdeps = [Dict{Int,Dict{Int,VersionSet}}() for p0 = 1:np]

    for (p,d) in deps
        p0 = pdict[p]
        vdict0 = vdict[p0]
        for (vn,a) in d
            v0 = vdict0[vn]
            pdeps[p0][v0] = a.requires
            for (rp, rvs) in a.requires
                p1 = pdict[rp]
                if !haskey(prevdeps[p1], p0)
                    prevdeps[p1][p0] = Dict{Int,VersionSet}()
                end
                prevdeps[p1][p0][v0] = rvs
            end
        end
    end

    restart = true
    while restart
        restart = false
        for p0 = 1:np
            s0 = sol[p0]
            if s0 >= spp[p0] - 1
                # either the package is not installed,
                # or it's already at the maximum version
                continue
            end
            viol = false
            # check if the higher version has a depencency which
            # would be violated by the state of the remaining packages
            for (p,vs) in pdeps[p0][s0+1]
                p1 = pdict[p]
                if sol[p1] == spp[p1]
                    # the dependency is violated because
                    # the other package is not being installed
                    viol = true
                    break
                end
                vn = pvers[p1][sol[p1]]
                if vn ∉ vs
                    # the dependency is violated because
                    # the other package version is invalid
                    viol = true
                    break
                end
            end
            viol && continue

            # check if bumping the version would violate some
            # dependency of another package
            for (p1,d) in prevdeps[p0]
                vs = get(d, sol[p1], nothing)
                vs === nothing && continue
                vn = pvers[p0][s0+1]
                if vn ∉ vs
                    # bumping the version would violate
                    # the dependency
                    viol = true
                    break
                end
            end
            viol && continue
            # So the solution is non-optimal: we bump it manually
            #warn("nonoptimal solution for package $(interface.pkgs[p0]): sol=$s0")
            sol[p0] += 1
            restart = true
        end
    end

    # Finally uninstall unneeded packages:
    # start from the required ones and keep only
    # the packages reachable from them along the graph
    uninst = trues(np)
    staged = Set{String}(keys(reqs))
    seen = copy(staged)

    while !isempty(staged)
        staged_next = Set{String}()
        for p in staged
            p0 = pdict[p]
            uninst[p0] = false
            @assert sol[p0] < spp[p0]
            vn = pvers[p0][sol[p0]]
            a = deps[p][vn]

            # scan dependencies
            for (rp,rvs) in a.requires
                rp0 = pdict[rp]
                @assert sol[rp0] < spp[rp0] && pvers[rp0][sol[rp0]] ∈ rvs
                rp ∈ seen || push!(staged_next, rp)
            end
        end
        union!(seen, staged_next)
        staged = staged_next
    end

    for p0 in find(uninst)
        sol[p0] = spp[p0]
    end

    return
end

end


# Copyright 2018 IBM Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

module ConceptPROV
export presentation_to_prov

using Serd
using Catlab

using ...Doctrine, ...Ontology
using ..ConceptRDF

const R = RDF.Resource

""" Convert concepts into hierarchy of PROV-O classes.

Type concepts are not really OWL classes and function concepts are not even
close to OWL classes (nor are they particularly close to OWL properties).
However, for the sake of Semantic Web interoperability, we encode type concepts
as a hierarchy of classes below PROV's Entity and function concepts as a
hierarchy of classes below PROV's Activity.

In the primary export function (`presentation_to_rdf`), concepts are represented
as individuals, not classes, which is necessary because they have properties.
Thus, when used in conjunction with this function, concepts are simultaneously
classes and individuals. This is prohibited in OWL 1, but permitted in OWL 2 DL,
where is it called "punning".

References:
- https://www.w3.org/TR/owl2-new-features/#F12:_Punning
- https://www.w3.org/TR/prov-o/
"""
function presentation_to_prov(pres::Presentation, prefix::RDF.Prefix)
  stmts = RDF.Statement[
    RDF.Prefix("rdf"), RDF.Prefix("rdfs"), RDF.Prefix("owl"),
    RDF.Prefix("prov"),
    RDF.Prefix("monocl", "https://www.datascienceontology.org/ns/monocl/"),
    prefix
  ]

  # Create mapping from concept node to all its super concept nodes.
  super_map = Dict{RDF.Node,Vector{RDF.Node}}()
  for sub in [generators(pres, Monocl.SubOb); generators(pres, Monocl.SubHom)]
    push!(get!(super_map, generator_rdf_node(dom(sub), prefix), RDF.Node[]),
          generator_rdf_node(codom(sub), prefix))
  end

  # Generate RDF triples.
  for gen in [generators(pres, Monocl.Ob); generators(pres, Monocl.Hom)]
    node = generator_rdf_node(gen, prefix)
    super_nodes = get(super_map, node) do
      [ gen isa Monocl.Ob ? R("prov","Entity") : R("prov","Activity") ]
    end
    push!(stmts, RDF.Triple(node, R("rdf","type"), R("owl","Class")))
    for super_node in super_nodes
      push!(stmts, RDF.Triple(node, R("rdfs","subClassOf"), super_node))
    end
  end
  stmts
end

end


type CallSpecifiedPathwiseMultiProduct{M <: MarketModelPathwiseMultiProduct, E <: ExerciseStrategy, M2 <: MarketModelPathwiseMultiProduct} <: MarketModelPathwiseMultiProduct
  underlying::M
  strategy::E
  rebate::M2
  evolution::EvolutionDescription
  isPresent::Vector{BitArray{1}}
  cashFlowTimes::Vector{Float64}
  rebateOffset::Int
  wasCalled::Bool
  dummyCashFlowsThisStep::Vector{Int}
  dummyCashFlowsGenerated::Vector{Vector{MarketModelPathWiseCashFlow}}
  currentIndex::Int
  callable::Bool
end

function CallSpecifiedPathwiseMultiProduct(underlying::MarketModelPathwiseMultiProduct,
                                  strategy::ExerciseStrategy)
  underlying = clone(underlying)
  strategy = clone(strategy)

  products = number_of_products(underlying)
  d1 = get_evolution(underlying)
  rateTimes1 = d1.rateTimes
  evolutionTimes1 = d1.evolutionTimes
  exerciseTimes = strategy.exerciseTimes

  # TODO check if rebate is there
  description = EvolutionDescription(rateTimes1, exerciseTimes)
  amounts = zeros(products, length(exerciseTimes))
  rebate = MarketModelPathwiseCashRebate(description, exerciseTimes, amounts, products)

  allEvolutionTimes = Vector{Vector{Float64}}(4)
  allEvolutionTimes[1] = evolutionTimes1
  allEvolutionTimes[2] = exerciseTimes
  allEvolutionTimes[3] = rebate.evolution.evolutionTimes
  allEvolutionTimes[4] = relevant_times(strategy)

  mergedEvolutionTimes, isPresent = merge_times(allEvolutionTimes)

  # TODO Add relevant rates
  evolution = EvolutionDescription(rateTimes1, mergedEvolutionTimes)
  cashFlowTimes = possible_cash_flow_times(underlying)
  rebateOffset = length(cashFlowTimes)
  rebateTimes = possible_cash_flow_times(rebate)
  cashFlowTimes = vcat(cashFlowTimes, rebateTimes)

  dummyCashFlowsThisStep = zeros(Int, products)
  n = max_number_of_cashflows_per_product_per_step(rebate)

  dummyCashFlowsGenerated = fill(MarketModelPathWiseCashFlow[MarketModelPathWiseCashFlow(d1.numberOfRates + 1) for i = 1:n], products)

  return CallSpecifiedPathwiseMultiProduct(underlying, strategy, rebate, evolution, isPresent, cashFlowTimes, rebateOffset, false, dummyCashFlowsThisStep,
                                  dummyCashFlowsGenerated, 1, true)
end

function next_time_step!(cs::CallSpecifiedPathwiseMultiProduct, currentState::CurveState, numberCashFlowsThisStep::Vector{Int}, genCashFlows::Vector{Vector{MarketModelPathWiseCashFlow}})
  isUnderlyingTime = cs.isPresent[1][cs.currentIndex]
  isExerciseTime = cs.isPresent[2][cs.currentIndex]
  isRebateTime = cs.isPresent[3][cs.currentIndex]
  isStrategyRelevantTime = cs.isPresent[4][cs.currentIndex]

  isDone = false

  if ~cs.wasCalled && isStrategyRelevantTime
    next_step!(cs.strategy, currentState)
  end

  if ~cs.wasCalled && isExerciseTime && cs.callable
    cs.wasCalled = get_exercise!(cs.strategy, currentState)
  end

  if cs.wasCalled
    if isRebateTime
      isDone = next_time_step!(cs.rebate, currentState, numberCashFlowsThisStep, genCashFlows)
      for i in eachindex(numberCashFlowsThisStep)
        for j = 1:numberCashFlowsThisStep[i]
          genCashFlows[i][j].timeIndex += cs.rebateOffset
        end
      end
    end
  else
    if isRebateTime
      next_time_step!(cs.rebate, currentState, cs.dummyCashFlowsThisStep, cs.dummyCashFlowsGenerated)
    end

    if isUnderlyingTime
      isDone = next_time_step!(cs.underlying, currentState, numberCashFlowsThisStep, genCashFlows)
    end
  end

  cs.currentIndex += 1
  return isDone || cs.currentIndex == length(cs.evolution.evolutionTimes) + 1
end

number_of_products(cs::CallSpecifiedPathwiseMultiProduct) = number_of_products(cs.underlying)
already_deflated(cs::CallSpecifiedPathwiseMultiProduct) = already_deflated(cs.underlying)
possible_cash_flow_times(cs::CallSpecifiedPathwiseMultiProduct) = cs.cashFlowTimes
max_number_of_cashflows_per_product_per_step(cs::CallSpecifiedPathwiseMultiProduct) =
            max(max_number_of_cashflows_per_product_per_step(cs.underlying), max_number_of_cashflows_per_product_per_step(cs.rebate))

function reset!(cs::CallSpecifiedPathwiseMultiProduct)
  reset!(cs.underlying)
  reset!(cs.rebate)
  reset!(cs.strategy)
  cs.currentIndex = 1
  cs.wasCalled = false

  return cs
end

clone(cs::CallSpecifiedPathwiseMultiProduct) = CallSpecifiedPathwiseMultiProduct(clone(cs.underlying), clone(cs.strategy), clone(cs.rebate), clone(cs.evolution), deepcopy(cs.isPresent),
                                        copy(cs.cashFlowTimes), cs.rebateOffset, cs.wasCalled, copy(cs.dummyCashFlowsThisStep), deepcopy(cs.dummyCashFlowsGenerated),
                                        cs.currentIndex, cs.callable)


"""
Constraint handler solving the GBD subproblem (fixed z).
"""
mutable struct GBDSubHdlr <: SCIP.AbstractConstraintHandler
    scip::SCIP.Optimizer # of master problem
    subsolver            # for subproblems
    finaltime::Float64 # seconds
    addcritpath::Bool
    addnogoods::Bool
    counter::Int
    primal_bound::Float64
    best_solution::Union{CandSol, Nothing}

    function GBDSubHdlr(scip, subsolver, finaltime, addcritpaths, addnogoods)
        return new(scip, subsolver, finaltime, addcritpaths, addnogoods,
                   0, Inf, nothing)
    end
end

"""
Constraint data, referencing problem data and master variables.
"""
mutable struct GBDSubCons <: SCIP.AbstractConstraint{GBDSubHdlr}
    inst::Instance
    topo::Topology
    master::Master
end

function solve_gbd_sub(ch::GBDSubHdlr, cons::GBDSubCons,
                       sol::Ptr{SCIP.SCIP_SOL}=C_NULL; enforce=true)
    ch.counter += 1

    # fetch solution values
    ysol = SCIP.sol_values(ch.scip, cons.master.y, sol)
    zsol = SCIP.sol_values(ch.scip, cons.master.z, sol)
    qsol = SCIP.sol_values(ch.scip, cons.master.q, sol)
    ϕsol = SCIP.sol_values(ch.scip, cons.master.ϕ, sol)

    cand = CandSol(zsol .>= 0.5, qsol, ϕsol)

    # solve subproblem (from scratch, no warmstart)
    submodel, π, Δl, Δu, ploss, plb, pub =
        make_sub(cons.inst, cons.topo, cand, ch.subsolver)
    # TODO: solver.writemodels && writeLP(submodel, "sub_$(counter).lp", genericnames=false)
    settimelimit!(submodel, ch.subsolver, ch.finaltime - time())
    JuMP.optimize!(submodel)
    substatus = JuMP.termination_status(submodel)
    @assert substatus == MOI.OPTIMAL "Slack model is always feasible"
    totalslack = JuMP.objective_value(submodel)

    if totalslack ≈ 0.0
        # maybe only the relaxation is feasible, we have to check also the
        # "exact" subproblem with equations constraints.
        submodel2, _ = make_sub(cons.inst, cons.topo, cand, ch.subsolver,
                                relaxed=false)
        # TODO: solver.writemodels && writeLP(submodel2, "sub_exact_iter$(iter).lp", genericnames=false)
        settimelimit!(submodel2, ch.subsolver, ch.finaltime - time())
        JuMP.optimize!(submodel2)
        substatus2 = JuMP.termination_status(submodel2)
        @assert substatus2 == MOI.OPTIMAL "Slack model is always feasible"
        totalslack2 = JuMP.objective_value(submodel2)

        if totalslack2 ≈ 0.0
            # check whether this candidate is actually an improvement
            L = pipelengths(cons.topo)
            A = cons.topo.arcs
            D = cons.inst.diameters
            c = [diam.cost for diam in D]
            obj = [c[i] * L[a] for a=1:length(A), i=1:length(D)]
            candprimal = sum(obj .* zsol)
            if candprimal < ch.primal_bound
                @debug "  found improving solution :-)"
                ch.best_solution = cand
                ch.primal_bound = candprimal
            else
                @debug "  cand's obj val $(candprimal) no improvement over $(ch.primal_bound)!"
            end

            return SCIP.SCIP_FEASIBLE
        else
            @debug "  subproblem/relaxation gap!"
            if !enforce
                return SCIP.SCIP_INFEASIBLE
            end

            # cut off candidate with no-good on z
            active = zsol .> 0.5
            coefs = 2.0 * active .- 1.0
            @cb_constraint(ch.scip, coefs ⋅ cons.master.z ≤ sum(active) - 1)

            return SCIP.SCIP_CONSADDED
        end
    else # totalslack > 0
        if !enforce
            return SCIP.SCIP_INFEASIBLE
        end

        # generate cuts and add to master
        if ch.addnogoods
            # cut off candidate with no-good on z
            active = zsol .> 0.5
            coefs = 2.0 * active .- 1.0
            @cb_constraint(ch.scip, coefs ⋅ cons.master.z ≤ sum(active) - 1)
        end

        if ch.addcritpath
            dualsol = SubDualSol(
                JuMP.dual.(ploss), JuMP.dual.(plb), JuMP.dual.(pub))
            critpathcuts(cons.inst, cons.topo, cons.master, cand,
                         subdualsol, ch.subsolver, cb=ch.scip)
        end

        @debug "  added some(?) cuts."
        return SCIP.SCIP_CONSADDED
    end
end

function SCIP.check(ch::GBDSubHdlr,
                    constraints::Array{Ptr{SCIP.SCIP_CONS}},
                    sol::Ptr{SCIP.SCIP_SOL},
                    checkintegrality::Bool,
                    checklprows::Bool,
                    printreason::Bool,
                    completely::Bool)
    @assert length(constraints) == 1
    cons::GBDSubCons = SCIP.user_constraint(constraints[1])
    return solve_gbd_sub(ch, cons, sol, enforce=false)
end

function SCIP.enforce_lp_sol(ch::GBDSubHdlr,
                             constraints::Array{Ptr{SCIP.SCIP_CONS}},
                             nusefulconss::Cint,
                             solinfeasible::Bool)
    @assert length(constraints) == 1
    cons::GBDSubCons = SCIP.user_constraint(constraints[1])
    return solve_gbd_sub(ch, cons, enforce=true)
end

function SCIP.enforce_pseudo_sol(ch::GBDSubHdlr,
                                 constraints::Array{Ptr{SCIP.SCIP_CONS}},
                                 nusefulconss::Cint,
                                 solinfeasible::Bool,
                                 objinfeasible::Bool)
    @assert length(constraints) == 1
    cons::GBDSubCons = SCIP.user_constraint(constraints[1])
    return solve_gbd_sub(ch, cons, enforce=true)
end

function SCIP.lock(ch::GBDSubHdlr,
                   constraint::Ptr{SCIP.SCIP_CONS},
                   locktype::SCIP.SCIP_LOCKTYPE,
                   nlockspos::Cint,
                   nlocksneg::Cint)
    cons::GBDSubCons = SCIP.user_constraint(constraint)
    master::Master = cons.master
    for v in [master.y; master.z[:]; master.q; master.ϕ]
        # TODO: understand why lock is called during SCIPfree, after the
        # constraint should have been deleted already. Does it mean we should
        # implement CONSTRANS?
        var_::Ptr{SCIP.SCIP_VAR} = SCIP.var(ch.scip, JuMP.index(v))
        var_ != C_NULL || continue  # avoid segfault!

        SCIP.@SC SCIP.SCIPaddVarLocksType(
            ch.scip, var_, locktype, nlockspos + nlocksneg, nlockspos + nlocksneg)
    end
end