From 21962d2a99dd50f19b0d3504ee787f727cb48fd9 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 7 Jul 2025 13:12:45 +0530 Subject: [PATCH 1/4] feat: add `inputs` and `outputs` fields to `System` --- src/systems/abstractsystem.jl | 2 ++ src/systems/system.jl | 37 ++++++++++++++++++++++++++++++----- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 70e7b06bfe..792488f8d7 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -767,6 +767,8 @@ const SYS_PROPS = [:eqs :parent :is_dde :tstops + :inputs + :outputs :index_cache :isscheduled :costs diff --git a/src/systems/system.jl b/src/systems/system.jl index 2d38ab2dce..cd28971ea6 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -190,6 +190,16 @@ struct System <: AbstractSystem """ tstops::Vector{Any} """ + $INTERNAL_FIELD_WARNING + The list of input variables of the system. + """ + inputs::Set{BasicSymbolic} + """ + $INTERNAL_FIELD_WARNING + The list of output variables of the system. + """ + outputs::Set{BasicSymbolic} + """ The `TearingState` of the system post-simplification with `mtkcompile`. """ tearing_state::Any @@ -255,8 +265,9 @@ struct System <: AbstractSystem brownians, iv, observed, parameter_dependencies, var_to_name, name, description, defaults, guesses, systems, initialization_eqs, continuous_events, discrete_events, connector_type, assertions = Dict{BasicSymbolic, String}(), - metadata = MetadataT(), gui_metadata = nothing, - is_dde = false, tstops = [], tearing_state = nothing, namespacing = true, + metadata = MetadataT(), gui_metadata = nothing, is_dde = false, tstops = [], + inputs = Set{BasicSymbolic}(), outputs = Set{BasicSymbolic}(), + tearing_state = nothing, namespacing = true, complete = false, index_cache = nothing, ignored_connections = nothing, preface = nothing, parent = nothing, initializesystem = nothing, is_initializesystem = false, is_discrete = false, isscheduled = false, @@ -296,7 +307,8 @@ struct System <: AbstractSystem observed, parameter_dependencies, var_to_name, name, description, defaults, guesses, systems, initialization_eqs, continuous_events, discrete_events, connector_type, assertions, metadata, gui_metadata, is_dde, - tstops, tearing_state, namespacing, complete, index_cache, ignored_connections, + tstops, inputs, outputs, tearing_state, namespacing, + complete, index_cache, ignored_connections, preface, parent, initializesystem, is_initializesystem, is_discrete, isscheduled, schedule) end @@ -367,15 +379,27 @@ function System(eqs::Vector{Equation}, iv, dvs, ps, brownians = []; defaults = anydict(defaults) guesses = anydict(guesses) + inputs = Set{BasicSymbolic}() + outputs = Set{BasicSymbolic}() var_to_name = anydict() let defaults = discover_from_metadata ? defaults : Dict(), - guesses = discover_from_metadata ? guesses : Dict() + guesses = discover_from_metadata ? guesses : Dict(), + inputs = discover_from_metadata ? inputs : Set(), + outputs = discover_from_metadata ? outputs : Set() process_variables!(var_to_name, defaults, guesses, dvs) process_variables!(var_to_name, defaults, guesses, ps) process_variables!(var_to_name, defaults, guesses, [eq.lhs for eq in observed]) process_variables!(var_to_name, defaults, guesses, [eq.rhs for eq in observed]) + + for var in dvs + if isinput(var) + push!(inputs, var) + elseif isoutput(var) + push!(outputs, var) + end + end end filter!(!(isnothing ∘ last), defaults) filter!(!(isnothing ∘ last), guesses) @@ -416,7 +440,8 @@ function System(eqs::Vector{Equation}, iv, dvs, ps, brownians = []; costs, consolidate, dvs, ps, brownians, iv, observed, Equation[], var_to_name, name, description, defaults, guesses, systems, initialization_eqs, continuous_events, discrete_events, connector_type, assertions, metadata, gui_metadata, is_dde, - tstops, tearing_state, true, false, nothing, ignored_connections, preface, parent, + tstops, inputs, outputs, tearing_state, true, false, + nothing, ignored_connections, preface, parent, initializesystem, is_initializesystem, is_discrete; checks) end @@ -1141,6 +1166,8 @@ function Base.isapprox(sysa::System, sysb::System) isequal(get_metadata(sysa), get_metadata(sysb)) && isequal(get_is_dde(sysa), get_is_dde(sysb)) && issetequal(get_tstops(sysa), get_tstops(sysb)) && + issetequal(get_inputs(sysa), get_inputs(sysb)) && + issetequal(get_outputs(sysa), get_outputs(sysb)) && safe_issetequal(get_ignored_connections(sysa), get_ignored_connections(sysb)) && isequal(get_is_initializesystem(sysa), get_is_initializesystem(sysb)) && isequal(get_is_discrete(sysa), get_is_discrete(sysb)) && From afeeec5dc7c910a44b3dce0458040510c860b202 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 7 Jul 2025 13:13:23 +0530 Subject: [PATCH 2/4] feat: respect `inputs` and `outputs` fields in `inputs` and `outputs` functions --- src/inputoutput.jl | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/inputoutput.jl b/src/inputoutput.jl index ac022cbe2f..97dcdcf564 100644 --- a/src/inputoutput.jl +++ b/src/inputoutput.jl @@ -5,7 +5,7 @@ using Symbolics: get_variables Return all variables that mare marked as inputs. See also [`unbound_inputs`](@ref) See also [`bound_inputs`](@ref), [`unbound_inputs`](@ref) """ -inputs(sys) = [filter(isinput, unknowns(sys)); filter(isinput, parameters(sys))] +inputs(sys) = collect(get_inputs(sys)) """ outputs(sys) @@ -14,13 +14,7 @@ Return all variables that mare marked as outputs. See also [`unbound_outputs`](@ See also [`bound_outputs`](@ref), [`unbound_outputs`](@ref) """ function outputs(sys) - o = observed(sys) - rhss = [eq.rhs for eq in o] - lhss = [eq.lhs for eq in o] - unique([filter(isoutput, unknowns(sys)) - filter(isoutput, parameters(sys)) - filter(x -> iscall(x) && isoutput(x), rhss) # observed can return equations with complicated expressions, we are only looking for single Terms - filter(x -> iscall(x) && isoutput(x), lhss)]) + return collect(get_outputs(sys)) end """ From ef11db2054a82bd8e31fb6864b7356040f93ba73 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 7 Jul 2025 13:13:38 +0530 Subject: [PATCH 3/4] feat: update `inputs` and `outputs` fields in `inputs_to_parameters!` --- src/inputoutput.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/inputoutput.jl b/src/inputoutput.jl index 97dcdcf564..df245f88ca 100644 --- a/src/inputoutput.jl +++ b/src/inputoutput.jl @@ -310,6 +310,8 @@ function inputs_to_parameters!(state::TransformationState, inputsyms) ps = parameters(sys) @set! sys.ps = [ps; new_parameters] + @set! sys.inputs = Set{BasicSymbolic}(filter(isinput, fullvars)) + @set! sys.outputs = Set{BasicSymbolic}(filter(isoutput, fullvars)) @set! state.sys = sys @set! state.fullvars = Vector{BasicSymbolic}(new_fullvars) @set! state.structure = structure From c53968d0b49f8c5bb1eddad0c0543a43b22a4f76 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 7 Jul 2025 13:26:17 +0530 Subject: [PATCH 4/4] test: test new input output behavior --- test/input_output_handling.jl | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/input_output_handling.jl b/test/input_output_handling.jl index ed1fb5fc69..139530aad0 100644 --- a/test/input_output_handling.jl +++ b/test/input_output_handling.jl @@ -464,3 +464,20 @@ end x = [1.0] @test_nowarn f[1](x, u, p, 0.0) end + +@testset "Observed inputs and outputs" begin + @variables x(t) y(t) [input = true] z(t) [output = true] + eqs = [D(x) ~ x + y + z + y ~ z] + @named sys = System(eqs, t) + @test issetequal(ModelingToolkit.inputs(sys), [y]) + @test issetequal(ModelingToolkit.outputs(sys), [z]) + + ss1 = mtkcompile(sys, inputs = [y], outputs = [z]) + @test issetequal(ModelingToolkit.inputs(ss1), [y]) + @test issetequal(ModelingToolkit.outputs(ss1), [z]) + + ss2 = mtkcompile(sys, inputs = [z], outputs = [y]) + @test issetequal(ModelingToolkit.inputs(ss2), [z]) + @test issetequal(ModelingToolkit.outputs(ss2), [y]) +end