About @connectors

Ive got a model defined as a connector

@connector function Pin(iv::Num; name)
   ...
end

And another model composed of these connectors:

function ModelWithConnectors(iv::Num; name)
    @named a = Pin(iv)
    @named b = Pin(iv)
     ... 
end

Is there any way to get info on the connectors (similar to the way you can query model states, parameters etc)? Id like to be able to find out how many connectors, connector names, etc

What exactly does the @connector do anyways? I cant seem to find anything on them in the docs

In order to get the connector names inside of a vector I ended up doing this:

function get_connections(system::ModelingToolkit.AbstractSystem)::Vector{String}
    connections::Vector{String} = []
    for subsystem in ModelingToolkit.get_systems(system)
        if isequal(subsystem.connection_type, Pin)
            push!(connections, string(subsystem.name))
        end
    end
    return connections
end

You can always put @macroexpand before a macro call in the REPL to see exactly what it’s doing. Here is the result of that on for the Pin case from the MTK docs (I deleted line number comments to make it more readable):

julia> @macroexpand @connector function Pin(;name)
           sts = @variables v(t)=1.0 i(t)=1.0
           ODESystem(Equation[], t, sts, []; name=name)
       end
quote
    struct Pin
        var"##275"->begin
                1
            end
    end
    function Pin(; name)
        function f()
            begin
                sts = begin
                        v = (identity)((Symbolics.wrap)((Symbolics.setdefaultval)(((Sym){(SymbolicUtils.FnType){NTuple{1, Any}, Real}}(:v))((Symbolics.value)(t)), begin
                                            1.0
                                        end)))
                        i = (identity)((Symbolics.wrap)((Symbolics.setdefaultval)(((Sym){(SymbolicUtils.FnType){NTuple{1, Any}, Real}}(:i))((Symbolics.value)(t)), begin
                                            1.0
                                        end)))
                        [v, i]
                    end
                ODESystem(Equation[], t, sts, []; name = name)
            end
        end
        res = f()
        if (isdefined)(res, :connection_type)
            var"#131#lens" = (identity)((Setfield.compose)((Setfield.PropertyLens){:connection_type}()))
            res = (Setfield.set)(res, var"#131#lens", Pin)
        else
            res
        end
    end
end

So it’s creating a struct called Pin, doing some fancy trickery to delete it’s default constructor (I think?), then creating a Pin method that calls your code and then sets the connection_type of the output to the type Pin.

This can be especially useful if your model has different types of connections. Let’s say it has electrical Pins as defined above, but also rotational mechanical Flanges that are basically the same, but with angular velocity instead of current and torque instead of voltage. This might look like:

@connector function Flange(;name)
     sts = @variables τ(t)=1.0 ω(t)=1.0
     ODESystem(Equation[], t, sts, []; name=name)
end 

Now you can write pretty generic code that takes advantage of circuit analogies for other domains. For example, you can have generic functions to get the flow and effort variables from a connector as:

flow_variable(connector) = flow_variable(ModelingToolkit.get_connection_type(connector), connector)
flow_variable(::Type{Pin}, connector) = connector.i
flow_variable(::Type{Flange}, connector) = connector.ω

effort_variable(connector) = effort_variable(ModelingToolkit.get_connection_type(connector), connector)
effort_variable(::Type{Pin}, connector) = connector.v
effort_variable(::Type{Flange}, connector) = connector.τ 
4 Likes

Thank you! This was very helpful