Skip to content

Commit a2f3d70

Browse files
authored
Merge pull request #1568 from SciML/myb/error_check
Rudimentary model balancing check
2 parents a07aacd + 8d49c83 commit a2f3d70

File tree

4 files changed

+63
-69
lines changed

4 files changed

+63
-69
lines changed

examples/electrical_components.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using Test
22
using ModelingToolkit, OrdinaryDiffEq
33

4-
@parameters t
4+
@isdefined(t) || @parameters t
55
@connector function Pin(;name)
66
sts = @variables v(t)=1.0 i(t)=1.0 [connect = Flow]
77
ODESystem(Equation[], t, sts, []; name=name)

src/systems/abstractsystem.jl

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -704,10 +704,48 @@ function get_or_construct_tearing_state(sys)
704704
state
705705
end
706706

707+
# TODO: what about inputs?
708+
function n_extra_equations(sys::AbstractSystem)
709+
isconnector(sys) && return length(get_states(sys))
710+
sys, csets = generate_connection_set(sys)
711+
ceqs, instream_csets = generate_connection_equations_and_stream_connections(csets)
712+
n_outer_stream_variables = 0
713+
for cset in instream_csets
714+
n_outer_stream_variables += count(x->x.isouter, cset.set)
715+
end
716+
717+
#n_toplevel_unused_flows = 0
718+
#toplevel_flows = Set()
719+
#for cset in csets
720+
# e1 = first(cset.set)
721+
# e1.sys.namespace === nothing || continue
722+
# for e in cset.set
723+
# get_connection_type(e.v) === Flow || continue
724+
# push!(toplevel_flows, e.v)
725+
# end
726+
#end
727+
#for m in get_systems(sys)
728+
# isconnector(m) || continue
729+
# n_toplevel_unused_flows += count(x->get_connection_type(x) === Flow && !(x in toplevel_flows), get_states(m))
730+
#end
731+
732+
733+
nextras = n_outer_stream_variables + length(ceqs)
734+
end
735+
707736
function Base.show(io::IO, ::MIME"text/plain", sys::AbstractSystem)
708737
eqs = equations(sys)
738+
vars = states(sys); nvars = length(vars)
709739
if eqs isa AbstractArray
710-
Base.printstyled(io, "Model $(nameof(sys)) with $(length(eqs)) equations\n"; bold=true)
740+
neqs = count(eq->!(eq.lhs isa Connection), eqs)
741+
Base.printstyled(io, "Model $(nameof(sys)) with $neqs "; bold=true)
742+
nextras = n_extra_equations(sys)
743+
if nextras > 0
744+
Base.printstyled(io, "("; bold=true)
745+
Base.printstyled(io, neqs + nextras; bold=true, color=:magenta)
746+
Base.printstyled(io, ") "; bold=true)
747+
end
748+
Base.printstyled(io, "equations\n"; bold=true)
711749
else
712750
Base.printstyled(io, "Model $(nameof(sys))\n"; bold=true)
713751
end
@@ -719,7 +757,6 @@ function Base.show(io::IO, ::MIME"text/plain", sys::AbstractSystem)
719757
rows = first(displaysize(io)) ÷ 5
720758
limit = get(io, :limit, false)
721759

722-
vars = states(sys); nvars = length(vars)
723760
Base.printstyled(io, "States ($nvars):"; bold=true)
724761
nrows = min(nvars, limit ? rows : nvars)
725762
limited = nrows < length(vars)

src/systems/connectors.jl

Lines changed: 22 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -32,59 +32,35 @@ struct RegularConnector <: AbstractConnectorType end
3232

3333
function connector_type(sys::AbstractSystem)
3434
sts = get_states(sys)
35-
#TODO: check the criteria for stream connectors
3635
n_stream = 0
3736
n_flow = 0
37+
n_regular = 0 # state that is not input, output, stream, or flow.
3838
for s in sts
3939
vtype = get_connection_type(s)
4040
if vtype === Stream
4141
isarray(s) && error("Array stream variables are not supported. Got $s.")
4242
n_stream += 1
43+
elseif vtype === Flow
44+
n_flow += 1
45+
elseif !(isinput(s) || isoutput(s))
46+
n_regular += 1
4347
end
44-
vtype === Flow && (n_flow += 1)
4548
end
4649
(n_stream > 0 && n_flow > 1) && error("There are multiple flow variables in $(nameof(sys))!")
47-
n_stream > 0 ? StreamConnector() : RegularConnector()
48-
end
49-
50-
Base.@kwdef struct Connection
51-
inners = nothing
52-
outers = nothing
53-
end
54-
55-
# everything is inner by default until we expand the connections
56-
Connection(syss) = Connection(inners=syss)
57-
get_systems(c::Connection) = c.inners
58-
function Base.in(e::Symbol, c::Connection)
59-
(c.inners !== nothing && any(k->nameof(k) === e, c.inners)) ||
60-
(c.outers !== nothing && any(k->nameof(k) === e, c.outers))
61-
end
62-
63-
function renamespace(sym::Symbol, connection::Connection)
64-
inners = connection.inners === nothing ? [] : renamespace.(sym, connection.inners)
65-
if connection.outers !== nothing
66-
for o in connection.outers
67-
push!(inners, renamespace(sym, o))
68-
end
50+
if n_flow != n_regular
51+
@warn "$(nameof(sys)) contains $n_flow variables, yet $n_regular regular " *
52+
"(non-flow, non-stream, non-input, non-output) variables." *
53+
"This could lead to imbalanced model that are difficult to debug." *
54+
"Consider marking some of the regular variables as input/output variables."
6955
end
70-
Connection(;inners=inners)
56+
n_stream > 0 ? StreamConnector() : RegularConnector()
7157
end
7258

73-
const EMPTY_VEC = []
74-
75-
function Base.show(io::IO, ::MIME"text/plain", c::Connection)
76-
# It is a bit unfortunate that the display of an array of `Equation`s won't
77-
# call this.
78-
@unpack outers, inners = c
79-
if outers === nothing && inners === nothing
80-
print(io, "<Connection>")
81-
else
82-
syss = Iterators.flatten((something(inners, EMPTY_VEC), something(outers, EMPTY_VEC)))
83-
splitting_idx = length(inners)
84-
sys_str = join((string(nameof(s)) * (i <= splitting_idx ? ("::inner") : ("::outer")) for (i, s) in enumerate(syss)), ", ")
85-
print(io, "<", sys_str, ">")
86-
end
59+
struct Connection
60+
systems
8761
end
62+
Connection() = Connection(nothing)
63+
get_systems(c::Connection) = c.systems
8864

8965
# symbolic `connect`
9066
function connect(sys1::AbstractSystem, sys2::AbstractSystem, syss::AbstractSystem...)
@@ -97,23 +73,6 @@ instream(a) = term(instream, unwrap(a), type=symtype(a))
9773
SymbolicUtils.promote_symtype(::typeof(instream), _) = Real
9874

9975
isconnector(s::AbstractSystem) = has_connector_type(s) && get_connector_type(s) !== nothing
100-
isstreamconnector(s::AbstractSystem) = isconnector(s) && get_connector_type(s) isa StreamConnector
101-
isstreamconnection(c::Connection) = any(isstreamconnector, c.inners) || any(isstreamconnector, c.outers)
102-
103-
function print_with_indent(n, x)
104-
print(" " ^ n)
105-
show(stdout, MIME"text/plain"(), x)
106-
println()
107-
end
108-
109-
function split_sys_var(var)
110-
var_name = string(getname(var))
111-
sidx = findlast(isequal(''), var_name)
112-
sidx === nothing && error("$var is not a namespaced variable")
113-
connector_name = Symbol(var_name[1:prevind(var_name, sidx)])
114-
streamvar_name = Symbol(var_name[nextind(var_name, sidx):end])
115-
connector_name, streamvar_name
116-
end
11776

11877
function flowvar(sys::AbstractSystem)
11978
sts = get_states(sys)
@@ -259,16 +218,13 @@ function generate_connection_set!(connectionsets, sys::AbstractSystem, namespace
259218
end
260219
end
261220

262-
if namespace !== nothing
263-
# Except for the top level, all connectors are eventually inside
264-
# connectors.
265-
T = ConnectionElement
266-
for s in subsys
267-
isconnector(s) || continue
268-
for v in states(s)
269-
Flow === get_connection_type(v) || continue
270-
push!(connectionsets, ConnectionSet([T(LazyNamespace(namespace, s), v, false)]))
271-
end
221+
# all connectors are eventually inside connectors.
222+
T = ConnectionElement
223+
for s in subsys
224+
isconnector(s) || continue
225+
for v in states(s)
226+
Flow === get_connection_type(v) || continue
227+
push!(connectionsets, ConnectionSet([T(LazyNamespace(namespace, s), v, false)]))
272228
end
273229
end
274230

test/components.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ end
3030

3131
include("../examples/rc_model.jl")
3232

33+
@test ModelingToolkit.n_extra_equations(capacitor) == 2
3334
@test length(equations(structural_simplify(rc_model, allow_parameter=false))) > 1
3435
sys = structural_simplify(rc_model)
3536
check_contract(sys)

0 commit comments

Comments
 (0)