Skip to content

Commit a10a1a2

Browse files
Merge pull request #4777 from SciML/as/isolate-subsys-clocks
fix: retain clock information after `isolate_subsystem`
2 parents b0340ae + 313cc25 commit a10a1a2

2 files changed

Lines changed: 104 additions & 4 deletions

File tree

lib/ModelingToolkitBase/test/analysis_points.jl

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1084,6 +1084,53 @@ if @isdefined(ModelingToolkit)
10841084
@test isempty(ModelingToolkit.get_unknowns(isolated))
10851085
@test isempty(ModelingToolkit.get_ps(isolated))
10861086
end
1087+
1088+
import ModelingToolkitTearing as MTKTearing
1089+
@testset "Clock information inferred from removed sections is retained" begin
1090+
# The model is such that the clocks of variables in `Middle` and `End` are
1091+
# only inferrable from the `Sample`s in `Provider`. Since `Provider` and `End`
1092+
# are removed by `isolate_subsystem`, ordinarily this would infer `Provider`'s
1093+
# variables as continuous. This is incorrect, and clock information from
1094+
# the full system should be used to ensure correct clock propagation in the
1095+
# isolated one.
1096+
@component function Provider(; name)
1097+
@variables x(t) xd(t) [output = true]
1098+
clk = Clock(0.1)
1099+
k = ShiftIndex(clk)
1100+
return System([D(x) ~ x, xd ~ Sample(clk)(x)], t, [x, xd], []; name)
1101+
end
1102+
1103+
@component function Middle(; name)
1104+
@variables xd(t) [input = true] yd(t) [output = true]
1105+
k = ShiftIndex()
1106+
return System([yd ~ 2xd], t, [xd, yd], []; name)
1107+
end
1108+
@component function End(; name)
1109+
@variables xd(t) [input = true]
1110+
k = ShiftIndex()
1111+
return System(Equation[], t, [xd], []; name)
1112+
end
1113+
1114+
@component function Model(; name)
1115+
@named begin
1116+
provider = Provider()
1117+
middle = Middle()
1118+
final = End()
1119+
end
1120+
eqs = [
1121+
connect(provider.xd, :A, middle.xd)
1122+
connect(middle.yd, :B, final.xd)
1123+
]
1124+
return System(eqs, t; name, systems = [provider, middle, final])
1125+
end
1126+
1127+
@named model = Model()
1128+
snippy, ips, ops = isolate_subsystem(model, model.A, model.B)
1129+
ts = TearingState(snippy)
1130+
ci = MTKTearing.ClockInference(ts)
1131+
MTKTearing.infer_clocks!(ci)
1132+
@test ci.var_domain == [Clock(0.1), Clock(0.1)]
1133+
end
10871134
end
10881135
end
10891136

src/systems/analysis_points.jl

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,29 @@ function isolate_subsystem(
204204
input_aps::Union{Symbol, Vector{Symbol}, AnalysisPoint, Vector{AnalysisPoint}},
205205
output_aps::Union{Symbol, Vector{Symbol}, AnalysisPoint, Vector{AnalysisPoint}}
206206
)
207+
# Run clock inference first to be able to port the results to the isolated subsystem
208+
ts = TearingState(expand_connections(sys))
209+
ci = MTKTearing.ClockInference(ts)
210+
MTKTearing.infer_clocks!(ci)
211+
all_clock_subs = Dict{SymbolicT, SymbolicT}()
212+
if length(ci.all_clocks) > 1
213+
sizehint!(all_clock_subs, length(ci.var_domain))
214+
for (var, clk) in zip(ts.fullvars, ci.var_domain)
215+
# Clock operators either define a clock or depend on the clock of their inputs.
216+
# Neither case needs to be propagated, and this simplifies downstream
217+
# implementation.
218+
Moshi.Match.@match var begin
219+
BSImpl.Term(; f) && if f isa Operator end => continue
220+
_ => nothing
221+
end
222+
all_clock_subs[var] = setmetadata(var, VariableTimeDomain, clk)
223+
arr, isidx = split_indexed_var(var)
224+
if isidx
225+
get!(() -> setmetadata(arr, VariableTimeDomain, clk), all_clock_subs, var)
226+
end
227+
end
228+
end
229+
207230
input_aps = canonicalize_ap(sys, input_aps)
208231
output_aps = canonicalize_ap(sys, output_aps)
209232

@@ -351,17 +374,43 @@ function isolate_subsystem(
351374
# as-is — their internal structure belongs to the isolated subsystem.
352375
# Systems that are containers (in inside_prefixes only because they wrap inside
353376
# components) have their equations filtered and their own variables/parameters cleared.
354-
function _reconstruct!(cur, parent_path)
377+
function _get_next_clock_substitutions(cur_subs::Dict{SymbolicT, SymbolicT}, name::Symbol)
378+
new_subs = empty(cur_subs)
379+
sizehint!(new_subs, length(cur_subs))
380+
for (k, v) in cur_subs
381+
hierarchy = namespace_hierarchy(getname(k))
382+
length(hierarchy) == 1 && continue
383+
first(hierarchy) == name || continue
384+
new_name = Symbol(join(Iterators.drop(hierarchy, 1), NAMESPACE_SEPARATOR_SYMBOL))
385+
new_k = rename(k, new_name)
386+
new_v = rename(v, new_name)
387+
new_subs[new_k] = new_v
388+
end
389+
390+
return new_subs
391+
end
392+
393+
function _recursively_clock_subsystems(cur, clock_subs)
394+
isempty(clock_subs) && return cur
395+
eqs = copy(get_eqs(cur))
396+
clock_subber = SU.IRSubstituter{false}(get_irstructure(sys), clock_subs)
397+
map!(clock_subber, eqs, eqs)
398+
syss = copy(get_systems(cur))
399+
map!(s -> _recursively_clock_subsystems(s, _get_next_clock_substitutions(clock_subs, nameof(s))), syss, syss)
400+
return ConstructionBase.setproperties(cur; eqs = eqs, systems = syss)
401+
end
402+
403+
function _reconstruct!(cur, parent_path, clock_subs)
355404
idx = get(path_to_idx, parent_path, nothing)
356405
if idx !== nothing && inside[idx]
357406
# Directly-inside component — preserve it entirely.
358-
return cur
407+
return _recursively_clock_subsystems(cur, clock_subs)
359408
end
360409

361410
# Container: filter subsystems and equations, clear own vars/params so that
362411
# nothing from outside the isolated region leaks into the result.
363412
new_systems = [
364-
_reconstruct!(s, [parent_path; nameof(s)])
413+
_reconstruct!(s, [parent_path; nameof(s)], _get_next_clock_substitutions(clock_subs, nameof(s)))
365414
for s in get_systems(cur) if has_inside([parent_path; nameof(s)])
366415
]
367416
new_eqs = filter(get_eqs(cur)) do eq
@@ -390,10 +439,14 @@ function isolate_subsystem(
390439
return false
391440
end
392441
end
442+
if !isempty(clock_subs)
443+
clock_subber = SU.IRSubstituter{false}(get_irstructure(sys), clock_subs)
444+
map!(clock_subber, new_eqs, new_eqs)
445+
end
393446
return System(new_eqs, get_iv(cur), SymbolicT[], SymbolicT[]; name = nameof(cur), systems = new_systems)
394447
end
395448

396-
return _reconstruct!(sys, Symbol[]), input_vars, output_vars
449+
return _reconstruct!(sys, Symbol[], all_clock_subs), input_vars, output_vars
397450
end
398451

399452
@doc """

0 commit comments

Comments
 (0)