Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve interactive system browsing and add system description #3132

Merged
merged 17 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/src/basics/AbstractSystem.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ Optionally, a system could have:
- `get_defaults(sys)`: A `Dict` that maps variables into their default values
for the current-level system.
- `get_noiseeqs(sys)`: Noise equations of the current-level system.
- `get_description(sys)`: A string that describes what a system represents.
- `get_metadata(sys)`: Any metadata about the system or its origin to be used by downstream packages.

Note that if you know a system is an `AbstractTimeDependentSystem` you could use `get_iv` to get the
Expand Down
2 changes: 1 addition & 1 deletion src/ModelingToolkit.jl
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ export Equation, ConstrainedEquation
export Term, Sym
export SymScope, LocalScope, ParentScope, DelayParentScope, GlobalScope
export independent_variable, equations, controls, observed, full_equations
export initialization_equations, guesses, defaults, parameter_dependencies
export initialization_equations, guesses, defaults, parameter_dependencies, hierarchy
export structural_simplify, expand_connections, linearize, linearization_function

export calculate_jacobian, generate_jacobian, generate_function, generate_custom_function
Expand Down
171 changes: 98 additions & 73 deletions src/systems/abstractsystem.jl
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,7 @@ end
Substitutions(subs, deps) = Substitutions(subs, deps, nothing)

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

#Deprecated
function independent_variable(sys::AbstractSystem)
Expand Down Expand Up @@ -986,6 +987,7 @@ for prop in [:eqs
:ps
:tspan
:name
:description
:var_to_name
:ctrls
:defaults
Expand Down Expand Up @@ -1855,8 +1857,14 @@ function get_or_construct_tearing_state(sys)
state
end

# TODO: what about inputs?
function n_extra_equations(sys::AbstractSystem)
"""
n_expanded_connection_equations(sys::AbstractSystem)

Returns the number of equations that the connections in `sys` expands to.
Equivalent to `length(equations(expand_connections(sys))) - length(filter(eq -> !(eq.lhs isa Connection), equations(sys)))`.
"""
function n_expanded_connection_equations(sys::AbstractSystem)
# TODO: what about inputs?
isconnector(sys) && return length(get_unknowns(sys))
sys, (csets, _) = generate_connection_set(sys)
ceqs, instream_csets = generate_connection_equations_and_stream_connections(csets)
Expand All @@ -1883,84 +1891,89 @@ function n_extra_equations(sys::AbstractSystem)
nextras = n_outer_stream_variables + length(ceqs)
end

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

rows = first(displaysize(io)) ÷ 5
limit = get(io, :limit, false)
# Print name and description
desc = description(sys)
printstyled(io, "Model ", nameof(sys), ":"; bold)
!isempty(desc) && print(io, " ", desc)

Base.printstyled(io, "Unknowns ($nvars):"; bold = true)
nrows = min(nvars, limit ? rows : nvars)
limited = nrows < length(vars)
defs = has_defaults(sys) ? defaults(sys) : nothing
# Print subsystems
subs = get_systems(sys)
nsubs = length(subs)
nrows = min(nsubs, limit ? rows : nsubs)
nrows > 0 && printstyled(io, "\nSubsystems ($(nsubs)):"; bold)
nrows > 0 && hint && print(io, " see hierarchy(sys)")
for i in 1:nrows
s = vars[i]
print(io, "\n ", s)

if defs !== nothing
val = get(defs, s, nothing)
if val !== nothing
print(io, " [defaults to ")
show(
IOContext(io, :compact => true, :limit => true,
:displaysize => (1, displaysize(io)[2])),
val)
print(io, "]")
end
description = getdescription(s)
if description !== nothing && description != ""
print(io, ": ", description)
sub = subs[i]
name = String(nameof(sub))
print(io, "\n ", name)
desc = description(sub)
if !isempty(desc)
maxlen = displaysize(io)[2] - length(name) - 6 # remaining length of line
if limit && length(desc) > maxlen
desc = chop(desc, tail = length(desc) - maxlen) * "…" # too long
end
print(io, ": ", desc)
end
end
limited && print(io, "\n⋮")
println(io)
limited = nrows < nsubs
limited && print(io, "\n ⋮") # too many to print

vars = parameters(sys)
nvars = length(vars)
Base.printstyled(io, "Parameters ($nvars):"; bold = true)
nrows = min(nvars, limit ? rows : nvars)
limited = nrows < length(vars)
for i in 1:nrows
s = vars[i]
print(io, "\n ", s)

if defs !== nothing
val = get(defs, s, nothing)
if val !== nothing
print(io, " [defaults to ")
show(
IOContext(io, :compact => true, :limit => true,
:displaysize => (1, displaysize(io)[2])),
val)
print(io, "]")
# Print equations
eqs = equations(sys)
if eqs isa AbstractArray && eltype(eqs) <: Equation
neqs = count(eq -> !(eq.lhs isa Connection), eqs)
next = n_expanded_connection_equations(sys)
ntot = neqs + next
ntot > 0 && printstyled(io, "\nEquations ($ntot):"; bold)
neqs > 0 && print(io, "\n $neqs standard", hint ? ": see equations(sys)" : "")
next > 0 && print(io, "\n $next connecting",
hint ? ": see equations(expand_connections(sys))" : "")
#Base.print_matrix(io, eqs) # usually too long and not useful to print all equations
end

# Print variables
for varfunc in [unknowns, parameters]
vars = varfunc(sys)
nvars = length(vars)
nvars == 0 && continue # skip
header = titlecase(String(nameof(varfunc))) # e.g. "Unknowns"
printstyled(io, "\n$header ($nvars):"; bold)
hint && print(io, " see $(nameof(varfunc))(sys)")
nrows = min(nvars, limit ? rows : nvars)
defs = has_defaults(sys) ? defaults(sys) : nothing
for i in 1:nrows
s = vars[i]
print(io, "\n ", s)
if !isnothing(defs)
val = get(defs, s, nothing)
if !isnothing(val)
print(io, " [defaults to ")
show(
IOContext(io, :compact => true, :limit => true,
:displaysize => (1, displaysize(io)[2])),
val)
print(io, "]")
end
desc = getdescription(s)
end
description = getdescription(s)
if description !== nothing && description != ""
print(io, ": ", description)
if !isnothing(desc) && desc != ""
print(io, ": ", desc)
end
end
limited = nrows < nvars
limited && printstyled(io, "\n ⋮") # too many variables to print
end
limited && print(io, "\n⋮")

# Print observed
nobs = has_observed(sys) ? length(observed(sys)) : 0
nobs > 0 && printstyled(io, "\nObserved ($nobs):"; bold)
nobs > 0 && hint && print(io, " see observed(sys)")

return nothing
end

Expand Down Expand Up @@ -2900,12 +2913,23 @@ function Base.showerror(io::IO, e::HybridSystemNotSupportedException)
print(io, "HybridSystemNotSupportedException: ", e.msg)
end

function AbstractTrees.children(sys::ModelingToolkit.AbstractSystem)
function AbstractTrees.children(sys::AbstractSystem)
ModelingToolkit.get_systems(sys)
end
function AbstractTrees.printnode(io::IO, sys::ModelingToolkit.AbstractSystem)
print(io, nameof(sys))
function AbstractTrees.printnode(
io::IO, sys::AbstractSystem; describe = false, bold = false)
printstyled(io, nameof(sys); bold)
describe && !isempty(description(sys)) && print(io, ": ", description(sys))
end
"""
hierarchy(sys::AbstractSystem; describe = false, bold = describe, kwargs...)

Print a tree of a system's hierarchy of subsystems.
"""
function hierarchy(sys::AbstractSystem; describe = false, bold = describe, kwargs...)
print_tree(sys; printnode_kw = (describe = describe, bold = bold), kwargs...)
end

function Base.IteratorEltype(::Type{<:TreeIterator{ModelingToolkit.AbstractSystem}})
Base.HasEltype()
end
Expand Down Expand Up @@ -2988,12 +3012,13 @@ function extend(sys::AbstractSystem, basesys::AbstractSystem; name::Symbol = nam
cevs = union(get_continuous_events(basesys), get_continuous_events(sys))
devs = union(get_discrete_events(basesys), get_discrete_events(sys))
defs = merge(get_defaults(basesys), get_defaults(sys)) # prefer `sys`
desc = join(filter(desc -> !isempty(desc), description.([sys, basesys])), " ") # concatenate non-empty descriptions with space
meta = union_nothing(get_metadata(basesys), get_metadata(sys))
syss = union(get_systems(basesys), get_systems(sys))
args = length(ivs) == 0 ? (eqs, sts, ps) : (eqs, ivs[1], sts, ps)
kwargs = (parameter_dependencies = dep_ps, observed = obs, continuous_events = cevs,
discrete_events = devs, defaults = defs, systems = syss, metadata = meta,
name = name, gui_metadata = gui_metadata)
name = name, description = desc, gui_metadata = gui_metadata)

# collect fields specific to some system types
if basesys isa ODESystem
Expand Down
26 changes: 23 additions & 3 deletions src/systems/diffeqs/odesystem.jl
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ struct ODESystem <: AbstractODESystem
"""
name::Symbol
"""
A description of the system.
"""
description::String
"""
The internal systems. These are required to have unique names.
"""
systems::Vector{ODESystem}
Expand Down Expand Up @@ -178,7 +182,7 @@ struct ODESystem <: AbstractODESystem
parent::Any

function ODESystem(tag, deqs, iv, dvs, ps, tspan, var_to_name, ctrls, observed, tgrad,
jac, ctrl_jac, Wfact, Wfact_t, name, systems, defaults, guesses,
jac, ctrl_jac, Wfact, Wfact_t, name, description, systems, defaults, guesses,
torn_matching, initializesystem, initialization_eqs, schedule,
connector_type, preface, cevents,
devents, parameter_dependencies,
Expand All @@ -199,7 +203,7 @@ struct ODESystem <: AbstractODESystem
check_units(u, deqs)
end
new(tag, deqs, iv, dvs, ps, tspan, var_to_name, ctrls, observed, tgrad, jac,
ctrl_jac, Wfact, Wfact_t, name, systems, defaults, guesses, torn_matching,
ctrl_jac, Wfact, Wfact_t, name, description, systems, defaults, guesses, torn_matching,
initializesystem, initialization_eqs, schedule, connector_type, preface,
cevents, devents, parameter_dependencies, metadata,
gui_metadata, is_dde, tearing_state, substitutions, complete, index_cache,
Expand All @@ -213,6 +217,7 @@ function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps;
systems = ODESystem[],
tspan = nothing,
name = nothing,
description = "",
default_u0 = Dict(),
default_p = Dict(),
defaults = _merge(Dict(default_u0), Dict(default_p)),
Expand Down Expand Up @@ -290,7 +295,8 @@ function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps;
end
ODESystem(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)),
deqs, iv′, dvs′, ps′, tspan, var_to_name, ctrl′, observed, tgrad, jac,
ctrl_jac, Wfact, Wfact_t, name, systems, defaults, guesses, nothing, initializesystem,
ctrl_jac, Wfact, Wfact_t, name, description, systems,
defaults, guesses, nothing, initializesystem,
initialization_eqs, schedule, connector_type, preface, cont_callbacks,
disc_callbacks, parameter_dependencies,
metadata, gui_metadata, is_dde, checks = checks)
Expand Down Expand Up @@ -393,6 +399,7 @@ function flatten(sys::ODESystem, noeqs = false)
discrete_events = discrete_events(sys),
defaults = defaults(sys),
name = nameof(sys),
description = description(sys),
initialization_eqs = initialization_equations(sys),
is_dde = is_dde(sys),
checks = false)
Expand Down Expand Up @@ -697,3 +704,16 @@ function add_accumulations(sys::ODESystem, vars::Vector{<:Pair})
@set! sys.unknowns = [get_unknowns(sys); avars]
@set! sys.defaults = merge(get_defaults(sys), Dict(a => 0.0 for a in avars))
end

function Base.show(io::IO, mime::MIME"text/plain", sys::ODESystem; hint = true, bold = true)
# Print general AbstractSystem information
invoke(Base.show, Tuple{typeof(io), typeof(mime), AbstractSystem},
io, mime, sys; hint, bold)

# Print initialization equations (unique to ODESystems)
nini = length(initialization_equations(sys))
nini > 0 && printstyled(io, "\nInitialization equations ($nini):"; bold)
nini > 0 && hint && print(io, " see initialization_equations(sys)")

return nothing
end
18 changes: 13 additions & 5 deletions src/systems/diffeqs/sdesystem.jl
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ struct SDESystem <: AbstractODESystem
"""
name::Symbol
"""
A description of the system.
"""
description::String
"""
The internal systems. These are required to have unique names.
"""
systems::Vector{SDESystem}
Expand Down Expand Up @@ -142,7 +146,7 @@ struct SDESystem <: AbstractODESystem
function SDESystem(tag, deqs, neqs, iv, dvs, ps, tspan, var_to_name, ctrls, observed,
tgrad,
jac,
ctrl_jac, Wfact, Wfact_t, name, systems, defaults, connector_type,
ctrl_jac, Wfact, Wfact_t, name, description, systems, defaults, connector_type,
cevents, devents, parameter_dependencies, metadata = nothing, gui_metadata = nothing,
complete = false, index_cache = nothing, parent = nothing, is_scalar_noise = false,
is_dde = false,
Expand All @@ -168,7 +172,8 @@ struct SDESystem <: AbstractODESystem
end
new(tag, deqs, neqs, iv, dvs, ps, tspan, var_to_name, ctrls, observed, tgrad, jac,
ctrl_jac,
Wfact, Wfact_t, name, systems, defaults, connector_type, cevents, devents,
Wfact, Wfact_t, name, description, systems,
defaults, connector_type, cevents, devents,
parameter_dependencies, metadata, gui_metadata, complete, index_cache, parent, is_scalar_noise,
is_dde, isscheduled)
end
Expand All @@ -183,6 +188,7 @@ function SDESystem(deqs::AbstractVector{<:Equation}, neqs::AbstractArray, iv, dv
default_p = Dict(),
defaults = _merge(Dict(default_u0), Dict(default_p)),
name = nothing,
description = "",
connector_type = nothing,
checks = true,
continuous_events = nothing,
Expand Down Expand Up @@ -234,7 +240,7 @@ function SDESystem(deqs::AbstractVector{<:Equation}, neqs::AbstractArray, iv, dv
end
SDESystem(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)),
deqs, neqs, iv′, dvs′, ps′, tspan, var_to_name, ctrl′, observed, tgrad, jac,
ctrl_jac, Wfact, Wfact_t, name, systems, defaults, connector_type,
ctrl_jac, Wfact, Wfact_t, name, description, systems, defaults, connector_type,
cont_callbacks, disc_callbacks, parameter_dependencies, metadata, gui_metadata,
complete, index_cache, parent, is_scalar_noise, is_dde; checks = checks)
end
Expand Down Expand Up @@ -349,7 +355,8 @@ function stochastic_integral_transform(sys::SDESystem, correction_factor)
end

SDESystem(deqs, get_noiseeqs(sys), get_iv(sys), unknowns(sys), parameters(sys),
name = name, parameter_dependencies = parameter_dependencies(sys), checks = false)
name = name, description = description(sys),
parameter_dependencies = parameter_dependencies(sys), checks = false)
end

"""
Expand Down Expand Up @@ -457,7 +464,8 @@ function Girsanov_transform(sys::SDESystem, u; θ0 = 1.0)
# return modified SDE System
SDESystem(deqs, noiseeqs, get_iv(sys), unknown_vars, parameters(sys);
defaults = Dict(θ => θ0), observed = [weight ~ θ / θ0],
name = name, parameter_dependencies = parameter_dependencies(sys),
name = name, description = description(sys),
parameter_dependencies = parameter_dependencies(sys),
checks = false)
end

Expand Down
Loading
Loading