Skip to content

Commit a0fe7c3

Browse files
Merge pull request #3132 from hersle/show
Improve interactive system browsing and add system description
2 parents 5031a9d + efad630 commit a0fe7c3

File tree

12 files changed

+186
-99
lines changed

12 files changed

+186
-99
lines changed

docs/src/basics/AbstractSystem.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ Optionally, a system could have:
6363
- `get_defaults(sys)`: A `Dict` that maps variables into their default values
6464
for the current-level system.
6565
- `get_noiseeqs(sys)`: Noise equations of the current-level system.
66+
- `get_description(sys)`: A string that describes what a system represents.
6667
- `get_metadata(sys)`: Any metadata about the system or its origin to be used by downstream packages.
6768

6869
Note that if you know a system is an `AbstractTimeDependentSystem` you could use `get_iv` to get the

src/ModelingToolkit.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ export Equation, ConstrainedEquation
244244
export Term, Sym
245245
export SymScope, LocalScope, ParentScope, DelayParentScope, GlobalScope
246246
export independent_variable, equations, controls, observed, full_equations
247-
export initialization_equations, guesses, defaults, parameter_dependencies
247+
export initialization_equations, guesses, defaults, parameter_dependencies, hierarchy
248248
export structural_simplify, expand_connections, linearize, linearization_function
249249

250250
export calculate_jacobian, generate_jacobian, generate_function, generate_custom_function

src/systems/abstractsystem.jl

Lines changed: 98 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,7 @@ end
498498
Substitutions(subs, deps) = Substitutions(subs, deps, nothing)
499499

500500
Base.nameof(sys::AbstractSystem) = getfield(sys, :name)
501+
description(sys::AbstractSystem) = has_description(sys) ? get_description(sys) : ""
501502

502503
#Deprecated
503504
function independent_variable(sys::AbstractSystem)
@@ -999,6 +1000,7 @@ for prop in [:eqs
9991000
:ps
10001001
:tspan
10011002
:name
1003+
:description
10021004
:var_to_name
10031005
:ctrls
10041006
:defaults
@@ -1865,8 +1867,14 @@ function get_or_construct_tearing_state(sys)
18651867
state
18661868
end
18671869

1868-
# TODO: what about inputs?
1869-
function n_extra_equations(sys::AbstractSystem)
1870+
"""
1871+
n_expanded_connection_equations(sys::AbstractSystem)
1872+
1873+
Returns the number of equations that the connections in `sys` expands to.
1874+
Equivalent to `length(equations(expand_connections(sys))) - length(filter(eq -> !(eq.lhs isa Connection), equations(sys)))`.
1875+
"""
1876+
function n_expanded_connection_equations(sys::AbstractSystem)
1877+
# TODO: what about inputs?
18701878
isconnector(sys) && return length(get_unknowns(sys))
18711879
sys, (csets, _) = generate_connection_set(sys)
18721880
ceqs, instream_csets = generate_connection_equations_and_stream_connections(csets)
@@ -1893,84 +1901,89 @@ function n_extra_equations(sys::AbstractSystem)
18931901
nextras = n_outer_stream_variables + length(ceqs)
18941902
end
18951903

1896-
function Base.show(io::IO, mime::MIME"text/plain", sys::AbstractSystem)
1897-
eqs = equations(sys)
1898-
vars = unknowns(sys)
1899-
nvars = length(vars)
1900-
if eqs isa AbstractArray && eltype(eqs) <: Equation
1901-
neqs = count(eq -> !(eq.lhs isa Connection), eqs)
1902-
Base.printstyled(io, "Model $(nameof(sys)) with $neqs "; bold = true)
1903-
nextras = n_extra_equations(sys)
1904-
if nextras > 0
1905-
Base.printstyled(io, "("; bold = true)
1906-
Base.printstyled(io, neqs + nextras; bold = true, color = :magenta)
1907-
Base.printstyled(io, ") "; bold = true)
1908-
end
1909-
Base.printstyled(io, "equations\n"; bold = true)
1910-
else
1911-
Base.printstyled(io, "Model $(nameof(sys))\n"; bold = true)
1912-
end
1913-
# The reduced equations are usually very long. It's not that useful to print
1914-
# them.
1915-
#Base.print_matrix(io, eqs)
1916-
#println(io)
1904+
function Base.show(
1905+
io::IO, mime::MIME"text/plain", sys::AbstractSystem; hint = true, bold = true)
1906+
limit = get(io, :limit, false) # if output should be limited,
1907+
rows = first(displaysize(io)) ÷ 5 # then allocate ≈1/5 of display height to each list
19171908

1918-
rows = first(displaysize(io)) ÷ 5
1919-
limit = get(io, :limit, false)
1909+
# Print name and description
1910+
desc = description(sys)
1911+
printstyled(io, "Model ", nameof(sys), ":"; bold)
1912+
!isempty(desc) && print(io, " ", desc)
19201913

1921-
Base.printstyled(io, "Unknowns ($nvars):"; bold = true)
1922-
nrows = min(nvars, limit ? rows : nvars)
1923-
limited = nrows < length(vars)
1924-
defs = has_defaults(sys) ? defaults(sys) : nothing
1914+
# Print subsystems
1915+
subs = get_systems(sys)
1916+
nsubs = length(subs)
1917+
nrows = min(nsubs, limit ? rows : nsubs)
1918+
nrows > 0 && printstyled(io, "\nSubsystems ($(nsubs)):"; bold)
1919+
nrows > 0 && hint && print(io, " see hierarchy(sys)")
19251920
for i in 1:nrows
1926-
s = vars[i]
1927-
print(io, "\n ", s)
1928-
1929-
if defs !== nothing
1930-
val = get(defs, s, nothing)
1931-
if val !== nothing
1932-
print(io, " [defaults to ")
1933-
show(
1934-
IOContext(io, :compact => true, :limit => true,
1935-
:displaysize => (1, displaysize(io)[2])),
1936-
val)
1937-
print(io, "]")
1938-
end
1939-
description = getdescription(s)
1940-
if description !== nothing && description != ""
1941-
print(io, ": ", description)
1921+
sub = subs[i]
1922+
name = String(nameof(sub))
1923+
print(io, "\n ", name)
1924+
desc = description(sub)
1925+
if !isempty(desc)
1926+
maxlen = displaysize(io)[2] - length(name) - 6 # remaining length of line
1927+
if limit && length(desc) > maxlen
1928+
desc = chop(desc, tail = length(desc) - maxlen) * "" # too long
19421929
end
1930+
print(io, ": ", desc)
19431931
end
19441932
end
1945-
limited && print(io, "\n")
1946-
println(io)
1933+
limited = nrows < nsubs
1934+
limited && print(io, "\n") # too many to print
19471935

1948-
vars = parameters(sys)
1949-
nvars = length(vars)
1950-
Base.printstyled(io, "Parameters ($nvars):"; bold = true)
1951-
nrows = min(nvars, limit ? rows : nvars)
1952-
limited = nrows < length(vars)
1953-
for i in 1:nrows
1954-
s = vars[i]
1955-
print(io, "\n ", s)
1956-
1957-
if defs !== nothing
1958-
val = get(defs, s, nothing)
1959-
if val !== nothing
1960-
print(io, " [defaults to ")
1961-
show(
1962-
IOContext(io, :compact => true, :limit => true,
1963-
:displaysize => (1, displaysize(io)[2])),
1964-
val)
1965-
print(io, "]")
1936+
# Print equations
1937+
eqs = equations(sys)
1938+
if eqs isa AbstractArray && eltype(eqs) <: Equation
1939+
neqs = count(eq -> !(eq.lhs isa Connection), eqs)
1940+
next = n_expanded_connection_equations(sys)
1941+
ntot = neqs + next
1942+
ntot > 0 && printstyled(io, "\nEquations ($ntot):"; bold)
1943+
neqs > 0 && print(io, "\n $neqs standard", hint ? ": see equations(sys)" : "")
1944+
next > 0 && print(io, "\n $next connecting",
1945+
hint ? ": see equations(expand_connections(sys))" : "")
1946+
#Base.print_matrix(io, eqs) # usually too long and not useful to print all equations
1947+
end
1948+
1949+
# Print variables
1950+
for varfunc in [unknowns, parameters]
1951+
vars = varfunc(sys)
1952+
nvars = length(vars)
1953+
nvars == 0 && continue # skip
1954+
header = titlecase(String(nameof(varfunc))) # e.g. "Unknowns"
1955+
printstyled(io, "\n$header ($nvars):"; bold)
1956+
hint && print(io, " see $(nameof(varfunc))(sys)")
1957+
nrows = min(nvars, limit ? rows : nvars)
1958+
defs = has_defaults(sys) ? defaults(sys) : nothing
1959+
for i in 1:nrows
1960+
s = vars[i]
1961+
print(io, "\n ", s)
1962+
if !isnothing(defs)
1963+
val = get(defs, s, nothing)
1964+
if !isnothing(val)
1965+
print(io, " [defaults to ")
1966+
show(
1967+
IOContext(io, :compact => true, :limit => true,
1968+
:displaysize => (1, displaysize(io)[2])),
1969+
val)
1970+
print(io, "]")
1971+
end
1972+
desc = getdescription(s)
19661973
end
1967-
description = getdescription(s)
1968-
if description !== nothing && description != ""
1969-
print(io, ": ", description)
1974+
if !isnothing(desc) && desc != ""
1975+
print(io, ": ", desc)
19701976
end
19711977
end
1978+
limited = nrows < nvars
1979+
limited && printstyled(io, "\n") # too many variables to print
19721980
end
1973-
limited && print(io, "\n")
1981+
1982+
# Print observed
1983+
nobs = has_observed(sys) ? length(observed(sys)) : 0
1984+
nobs > 0 && printstyled(io, "\nObserved ($nobs):"; bold)
1985+
nobs > 0 && hint && print(io, " see observed(sys)")
1986+
19741987
return nothing
19751988
end
19761989

@@ -2911,12 +2924,23 @@ function Base.showerror(io::IO, e::HybridSystemNotSupportedException)
29112924
print(io, "HybridSystemNotSupportedException: ", e.msg)
29122925
end
29132926

2914-
function AbstractTrees.children(sys::ModelingToolkit.AbstractSystem)
2927+
function AbstractTrees.children(sys::AbstractSystem)
29152928
ModelingToolkit.get_systems(sys)
29162929
end
2917-
function AbstractTrees.printnode(io::IO, sys::ModelingToolkit.AbstractSystem)
2918-
print(io, nameof(sys))
2930+
function AbstractTrees.printnode(
2931+
io::IO, sys::AbstractSystem; describe = false, bold = false)
2932+
printstyled(io, nameof(sys); bold)
2933+
describe && !isempty(description(sys)) && print(io, ": ", description(sys))
29192934
end
2935+
"""
2936+
hierarchy(sys::AbstractSystem; describe = false, bold = describe, kwargs...)
2937+
2938+
Print a tree of a system's hierarchy of subsystems.
2939+
"""
2940+
function hierarchy(sys::AbstractSystem; describe = false, bold = describe, kwargs...)
2941+
print_tree(sys; printnode_kw = (describe = describe, bold = bold), kwargs...)
2942+
end
2943+
29202944
function Base.IteratorEltype(::Type{<:TreeIterator{ModelingToolkit.AbstractSystem}})
29212945
Base.HasEltype()
29222946
end
@@ -2999,12 +3023,13 @@ function extend(sys::AbstractSystem, basesys::AbstractSystem; name::Symbol = nam
29993023
cevs = union(get_continuous_events(basesys), get_continuous_events(sys))
30003024
devs = union(get_discrete_events(basesys), get_discrete_events(sys))
30013025
defs = merge(get_defaults(basesys), get_defaults(sys)) # prefer `sys`
3026+
desc = join(filter(desc -> !isempty(desc), description.([sys, basesys])), " ") # concatenate non-empty descriptions with space
30023027
meta = union_nothing(get_metadata(basesys), get_metadata(sys))
30033028
syss = union(get_systems(basesys), get_systems(sys))
30043029
args = length(ivs) == 0 ? (eqs, sts, ps) : (eqs, ivs[1], sts, ps)
30053030
kwargs = (parameter_dependencies = dep_ps, observed = obs, continuous_events = cevs,
30063031
discrete_events = devs, defaults = defs, systems = syss, metadata = meta,
3007-
name = name, gui_metadata = gui_metadata)
3032+
name = name, description = desc, gui_metadata = gui_metadata)
30083033

30093034
# collect fields specific to some system types
30103035
if basesys isa ODESystem

src/systems/diffeqs/odesystem.jl

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ struct ODESystem <: AbstractODESystem
7979
"""
8080
name::Symbol
8181
"""
82+
A description of the system.
83+
"""
84+
description::String
85+
"""
8286
The internal systems. These are required to have unique names.
8387
"""
8488
systems::Vector{ODESystem}
@@ -178,7 +182,7 @@ struct ODESystem <: AbstractODESystem
178182
parent::Any
179183

180184
function ODESystem(tag, deqs, iv, dvs, ps, tspan, var_to_name, ctrls, observed, tgrad,
181-
jac, ctrl_jac, Wfact, Wfact_t, name, systems, defaults, guesses,
185+
jac, ctrl_jac, Wfact, Wfact_t, name, description, systems, defaults, guesses,
182186
torn_matching, initializesystem, initialization_eqs, schedule,
183187
connector_type, preface, cevents,
184188
devents, parameter_dependencies,
@@ -199,7 +203,7 @@ struct ODESystem <: AbstractODESystem
199203
check_units(u, deqs)
200204
end
201205
new(tag, deqs, iv, dvs, ps, tspan, var_to_name, ctrls, observed, tgrad, jac,
202-
ctrl_jac, Wfact, Wfact_t, name, systems, defaults, guesses, torn_matching,
206+
ctrl_jac, Wfact, Wfact_t, name, description, systems, defaults, guesses, torn_matching,
203207
initializesystem, initialization_eqs, schedule, connector_type, preface,
204208
cevents, devents, parameter_dependencies, metadata,
205209
gui_metadata, is_dde, tearing_state, substitutions, complete, index_cache,
@@ -213,6 +217,7 @@ function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps;
213217
systems = ODESystem[],
214218
tspan = nothing,
215219
name = nothing,
220+
description = "",
216221
default_u0 = Dict(),
217222
default_p = Dict(),
218223
defaults = _merge(Dict(default_u0), Dict(default_p)),
@@ -290,7 +295,8 @@ function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps;
290295
end
291296
ODESystem(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)),
292297
deqs, iv′, dvs′, ps′, tspan, var_to_name, ctrl′, observed, tgrad, jac,
293-
ctrl_jac, Wfact, Wfact_t, name, systems, defaults, guesses, nothing, initializesystem,
298+
ctrl_jac, Wfact, Wfact_t, name, description, systems,
299+
defaults, guesses, nothing, initializesystem,
294300
initialization_eqs, schedule, connector_type, preface, cont_callbacks,
295301
disc_callbacks, parameter_dependencies,
296302
metadata, gui_metadata, is_dde, checks = checks)
@@ -393,6 +399,7 @@ function flatten(sys::ODESystem, noeqs = false)
393399
discrete_events = discrete_events(sys),
394400
defaults = defaults(sys),
395401
name = nameof(sys),
402+
description = description(sys),
396403
initialization_eqs = initialization_equations(sys),
397404
is_dde = is_dde(sys),
398405
checks = false)
@@ -697,3 +704,16 @@ function add_accumulations(sys::ODESystem, vars::Vector{<:Pair})
697704
@set! sys.unknowns = [get_unknowns(sys); avars]
698705
@set! sys.defaults = merge(get_defaults(sys), Dict(a => 0.0 for a in avars))
699706
end
707+
708+
function Base.show(io::IO, mime::MIME"text/plain", sys::ODESystem; hint = true, bold = true)
709+
# Print general AbstractSystem information
710+
invoke(Base.show, Tuple{typeof(io), typeof(mime), AbstractSystem},
711+
io, mime, sys; hint, bold)
712+
713+
# Print initialization equations (unique to ODESystems)
714+
nini = length(initialization_equations(sys))
715+
nini > 0 && printstyled(io, "\nInitialization equations ($nini):"; bold)
716+
nini > 0 && hint && print(io, " see initialization_equations(sys)")
717+
718+
return nothing
719+
end

src/systems/diffeqs/sdesystem.jl

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ struct SDESystem <: AbstractODESystem
8080
"""
8181
name::Symbol
8282
"""
83+
A description of the system.
84+
"""
85+
description::String
86+
"""
8387
The internal systems. These are required to have unique names.
8488
"""
8589
systems::Vector{SDESystem}
@@ -142,7 +146,7 @@ struct SDESystem <: AbstractODESystem
142146
function SDESystem(tag, deqs, neqs, iv, dvs, ps, tspan, var_to_name, ctrls, observed,
143147
tgrad,
144148
jac,
145-
ctrl_jac, Wfact, Wfact_t, name, systems, defaults, connector_type,
149+
ctrl_jac, Wfact, Wfact_t, name, description, systems, defaults, connector_type,
146150
cevents, devents, parameter_dependencies, metadata = nothing, gui_metadata = nothing,
147151
complete = false, index_cache = nothing, parent = nothing, is_scalar_noise = false,
148152
is_dde = false,
@@ -168,7 +172,8 @@ struct SDESystem <: AbstractODESystem
168172
end
169173
new(tag, deqs, neqs, iv, dvs, ps, tspan, var_to_name, ctrls, observed, tgrad, jac,
170174
ctrl_jac,
171-
Wfact, Wfact_t, name, systems, defaults, connector_type, cevents, devents,
175+
Wfact, Wfact_t, name, description, systems,
176+
defaults, connector_type, cevents, devents,
172177
parameter_dependencies, metadata, gui_metadata, complete, index_cache, parent, is_scalar_noise,
173178
is_dde, isscheduled)
174179
end
@@ -183,6 +188,7 @@ function SDESystem(deqs::AbstractVector{<:Equation}, neqs::AbstractArray, iv, dv
183188
default_p = Dict(),
184189
defaults = _merge(Dict(default_u0), Dict(default_p)),
185190
name = nothing,
191+
description = "",
186192
connector_type = nothing,
187193
checks = true,
188194
continuous_events = nothing,
@@ -234,7 +240,7 @@ function SDESystem(deqs::AbstractVector{<:Equation}, neqs::AbstractArray, iv, dv
234240
end
235241
SDESystem(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)),
236242
deqs, neqs, iv′, dvs′, ps′, tspan, var_to_name, ctrl′, observed, tgrad, jac,
237-
ctrl_jac, Wfact, Wfact_t, name, systems, defaults, connector_type,
243+
ctrl_jac, Wfact, Wfact_t, name, description, systems, defaults, connector_type,
238244
cont_callbacks, disc_callbacks, parameter_dependencies, metadata, gui_metadata,
239245
complete, index_cache, parent, is_scalar_noise, is_dde; checks = checks)
240246
end
@@ -349,7 +355,8 @@ function stochastic_integral_transform(sys::SDESystem, correction_factor)
349355
end
350356

351357
SDESystem(deqs, get_noiseeqs(sys), get_iv(sys), unknowns(sys), parameters(sys),
352-
name = name, parameter_dependencies = parameter_dependencies(sys), checks = false)
358+
name = name, description = description(sys),
359+
parameter_dependencies = parameter_dependencies(sys), checks = false)
353360
end
354361

355362
"""
@@ -457,7 +464,8 @@ function Girsanov_transform(sys::SDESystem, u; θ0 = 1.0)
457464
# return modified SDE System
458465
SDESystem(deqs, noiseeqs, get_iv(sys), unknown_vars, parameters(sys);
459466
defaults = Dict=> θ0), observed = [weight ~ θ / θ0],
460-
name = name, parameter_dependencies = parameter_dependencies(sys),
467+
name = name, description = description(sys),
468+
parameter_dependencies = parameter_dependencies(sys),
461469
checks = false)
462470
end
463471

0 commit comments

Comments
 (0)