Skip to content

Commit b7bb95d

Browse files
Merge pull request #3469 from AayushSabharwal/as/fix-ignored-connections
fix: fix analysis point transform ignoring too many connections
2 parents 0b8f4d5 + 861abff commit b7bb95d

File tree

5 files changed

+231
-61
lines changed

5 files changed

+231
-61
lines changed

src/systems/abstractsystem.jl

+57-7
Original file line numberDiff line numberDiff line change
@@ -1421,9 +1421,34 @@ function assertions(sys::AbstractSystem)
14211421
return merge(asserts, namespaced_asserts)
14221422
end
14231423

1424+
"""
1425+
$(TYPEDEF)
1426+
1427+
Information about an `AnalysisPoint` for which the corresponding connection must be
1428+
ignored during `expand_connections`, since the analysis point has been transformed.
1429+
1430+
# Fields
1431+
1432+
$(TYPEDFIELDS)
1433+
"""
1434+
struct IgnoredAnalysisPoint
1435+
"""
1436+
The input variable/connector.
1437+
"""
1438+
input::Union{BasicSymbolic, AbstractSystem}
1439+
"""
1440+
The output variables/connectors.
1441+
"""
1442+
outputs::Vector{Union{BasicSymbolic, AbstractSystem}}
1443+
end
1444+
14241445
const HierarchyVariableT = Vector{Union{BasicSymbolic, Symbol}}
14251446
const HierarchySystemT = Vector{Union{AbstractSystem, Symbol}}
14261447
"""
1448+
The type returned from `analysis_point_common_hierarchy`.
1449+
"""
1450+
const HierarchyAnalysisPointT = Vector{Union{IgnoredAnalysisPoint, Symbol}}
1451+
"""
14271452
The type returned from `as_hierarchy`.
14281453
"""
14291454
const HierarchyT = Union{HierarchyVariableT, HierarchySystemT}
@@ -1440,6 +1465,29 @@ function from_hierarchy(hierarchy::HierarchyT)
14401465
end
14411466
end
14421467

1468+
"""
1469+
$(TYPEDSIGNATURES)
1470+
1471+
Represent an ignored analysis point as a namespaced hierarchy. The hierarchy is built
1472+
using the common hierarchy of all involved systems/variables.
1473+
"""
1474+
function analysis_point_common_hierarchy(ap::IgnoredAnalysisPoint)::HierarchyAnalysisPointT
1475+
isys = as_hierarchy(ap.input)
1476+
osyss = as_hierarchy.(ap.outputs)
1477+
suffix = Symbol[]
1478+
while isys[end] == osyss[1][end] && allequal(last.(osyss))
1479+
push!(suffix, isys[end])
1480+
pop!(isys)
1481+
pop!.(osyss)
1482+
end
1483+
isys = from_hierarchy(isys)
1484+
osyss = from_hierarchy.(osyss)
1485+
newap = IgnoredAnalysisPoint(isys, osyss)
1486+
hierarchy = HierarchyAnalysisPointT([suffix; newap])
1487+
reverse!(hierarchy)
1488+
return hierarchy
1489+
end
1490+
14431491
"""
14441492
$(TYPEDSIGNATURES)
14451493
@@ -1466,19 +1514,20 @@ end
14661514
"""
14671515
$(TYPEDSIGNATURES)
14681516
1469-
Get the connections to ignore for `sys` and its subsystems. The returned value is a
1470-
`Tuple` similar in structure to the `ignored_connections` field. Each system (variable)
1471-
in the first (second) element of the tuple is also passed through `as_hierarchy`.
1517+
Get the analysis points to ignore for `sys` and its subsystems. The returned value is a
1518+
`Tuple` similar in structure to the `ignored_connections` field.
14721519
"""
14731520
function ignored_connections(sys::AbstractSystem)
1474-
has_ignored_connections(sys) || return (HierarchySystemT[], HierarchyVariableT[])
1521+
has_ignored_connections(sys) ||
1522+
return (HierarchyAnalysisPointT[], HierarchyAnalysisPointT[])
14751523

14761524
ics = get_ignored_connections(sys)
14771525
if ics === nothing
1478-
ics = (HierarchySystemT[], HierarchyVariableT[])
1526+
ics = (HierarchyAnalysisPointT[], HierarchyAnalysisPointT[])
14791527
end
14801528
# turn into hierarchies
1481-
ics = (map(as_hierarchy, ics[1]), map(as_hierarchy, ics[2]))
1529+
ics = (map(analysis_point_common_hierarchy, ics[1]),
1530+
map(analysis_point_common_hierarchy, ics[2]))
14821531
systems = get_systems(sys)
14831532
# for each subsystem, get its ignored connections, add the name of the subsystem
14841533
# to the hierarchy and concatenate corresponding buffers of the result
@@ -1487,7 +1536,8 @@ function ignored_connections(sys::AbstractSystem)
14871536
(map(Base.Fix2(push!, nameof(subsys)), sub_ics[1]),
14881537
map(Base.Fix2(push!, nameof(subsys)), sub_ics[2]))
14891538
end
1490-
return (Vector{HierarchySystemT}(result[1]), Vector{HierarchyVariableT}(result[2]))
1539+
return (Vector{HierarchyAnalysisPointT}(result[1]),
1540+
Vector{HierarchyAnalysisPointT}(result[2]))
14911541
end
14921542

14931543
"""

src/systems/analysis_points.jl

+9-8
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,8 @@ function Symbolics.connect(
213213
outs::ConnectableSymbolicT...; verbose = true)
214214
allvars = (in, out, outs...)
215215
validate_causal_variables_connection(allvars)
216-
return AnalysisPoint() ~ AnalysisPoint(in, name, [out; collect(outs)]; verbose)
216+
return AnalysisPoint() ~ AnalysisPoint(
217+
unwrap(in), name, unwrap.([out; collect(outs)]); verbose)
217218
end
218219

219220
"""
@@ -416,20 +417,20 @@ function with_analysis_point_ignored(sys::AbstractSystem, ap::AnalysisPoint)
416417
has_ignored_connections(sys) || return sys
417418
ignored = get_ignored_connections(sys)
418419
if ignored === nothing
419-
ignored = (ODESystem[], BasicSymbolic[])
420+
ignored = (IgnoredAnalysisPoint[], IgnoredAnalysisPoint[])
420421
else
421422
ignored = copy.(ignored)
422423
end
423424
if ap.outputs === nothing
424425
error("Empty analysis point")
425426
end
426-
for x in ap.outputs
427-
if x isa ODESystem
428-
push!(ignored[1], x)
429-
else
430-
push!(ignored[2], unwrap(x))
431-
end
427+
428+
if ap.input isa AbstractSystem && all(x -> x isa AbstractSystem, ap.outputs)
429+
push!(ignored[1], IgnoredAnalysisPoint(ap.input, ap.outputs))
430+
else
431+
push!(ignored[2], IgnoredAnalysisPoint(unwrap(ap.input), unwrap.(ap.outputs)))
432432
end
433+
433434
return @set sys.ignored_connections = ignored
434435
end
435436

src/systems/connectors.jl

+104-42
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ function Symbolics.connect(var1::ConnectableSymbolicT, var2::ConnectableSymbolic
152152
vars::ConnectableSymbolicT...)
153153
allvars = (var1, var2, vars...)
154154
validate_causal_variables_connection(allvars)
155-
return Equation(Connection(), Connection(map(SymbolicWithNameof, allvars)))
155+
return Equation(Connection(), Connection(map(SymbolicWithNameof, unwrap.(allvars))))
156156
end
157157

158158
function flowvar(sys::AbstractSystem)
@@ -328,14 +328,12 @@ namespaced by `namespace`.
328328
`ignored_connects[1]`, purely to avoid unnecessary recomputation.
329329
"""
330330
function connection2set!(connectionsets, namespace, ss, isouter;
331-
ignored_connects = (HierarchySystemT[], HierarchyVariableT[]),
332-
namespaced_ignored_systems = ODESystem[])
333-
ignored_systems, ignored_variables = ignored_connects
331+
ignored_systems = HierarchySystemT[], ignored_variables = HierarchyVariableT[])
332+
ns_ignored_systems = from_hierarchy.(ignored_systems)
333+
ns_ignored_variables = from_hierarchy.(ignored_variables)
334334
# ignore specified systems
335335
ss = filter(ss) do s
336-
all(namespaced_ignored_systems) do igsys
337-
nameof(igsys) != nameof(s)
338-
end
336+
!any(x -> nameof(x) == nameof(s), ns_ignored_systems)
339337
end
340338
# `ignored_variables` for each `s` in `ss`
341339
corresponding_ignored_variables = map(
@@ -434,15 +432,95 @@ function generate_connection_set(
434432
connectionsets = ConnectionSet[]
435433
domain_csets = ConnectionSet[]
436434
sys = generate_connection_set!(
437-
connectionsets, domain_csets, sys, find, replace, scalarize, nothing,
438-
# include systems to be ignored
439-
ignored_connections(sys))
435+
connectionsets, domain_csets, sys, find, replace, scalarize, nothing, ignored_connections(sys))
440436
csets = merge(connectionsets)
441437
domain_csets = merge([csets; domain_csets], true)
442438

443439
sys, (csets, domain_csets)
444440
end
445441

442+
"""
443+
$(TYPEDSIGNATURES)
444+
445+
For a list of `systems` in a connect equation, return the subset of it to ignore (as a
446+
list of hierarchical systems) based on `ignored_system_aps`, the analysis points to be
447+
ignored. All analysis points in `ignored_system_aps` must contain systems (connectors)
448+
as their input/outputs.
449+
"""
450+
function systems_to_ignore(ignored_system_aps::Vector{HierarchyAnalysisPointT},
451+
systems::Union{Vector{<:AbstractSystem}, Tuple{Vararg{<:AbstractSystem}}})
452+
to_ignore = HierarchySystemT[]
453+
for ap in ignored_system_aps
454+
# if `systems` contains the input of the AP, ignore any outputs of the AP present in it.
455+
isys_hierarchy = HierarchySystemT([ap[1].input; @view ap[2:end]])
456+
isys = from_hierarchy(isys_hierarchy)
457+
any(x -> nameof(x) == nameof(isys), systems) || continue
458+
459+
for outsys in ap[1].outputs
460+
osys_hierarchy = HierarchySystemT([outsys; @view ap[2:end]])
461+
osys = from_hierarchy(osys_hierarchy)
462+
any(x -> nameof(x) == nameof(osys), systems) || continue
463+
push!(to_ignore, HierarchySystemT(osys_hierarchy))
464+
end
465+
end
466+
467+
return to_ignore
468+
end
469+
470+
"""
471+
$(TYPEDSIGNATURES)
472+
473+
For a list of `systems` in a connect equation, return the subset of their variables to
474+
ignore (as a list of hierarchical variables) based on `ignored_system_aps`, the analysis
475+
points to be ignored. All analysis points in `ignored_system_aps` must contain variables
476+
as their input/outputs.
477+
"""
478+
function variables_to_ignore(ignored_variable_aps::Vector{HierarchyAnalysisPointT},
479+
systems::Union{Vector{<:AbstractSystem}, Tuple{Vararg{<:AbstractSystem}}})
480+
to_ignore = HierarchyVariableT[]
481+
for ap in ignored_variable_aps
482+
ivar_hierarchy = HierarchyVariableT([ap[1].input; @view ap[2:end]])
483+
ivar = from_hierarchy(ivar_hierarchy)
484+
any(x -> any(isequal(ivar), renamespace.((x,), unknowns(x))), systems) || continue
485+
486+
for outvar in ap[1].outputs
487+
ovar_hierarchy = HierarchyVariableT([as_hierarchy(outvar); @view ap[2:end]])
488+
ovar = from_hierarchy(ovar_hierarchy)
489+
any(x -> any(isequal(ovar), renamespace.((x,), unknowns(x))), systems) ||
490+
continue
491+
push!(to_ignore, HierarchyVariableT(ovar_hierarchy))
492+
end
493+
end
494+
return to_ignore
495+
end
496+
497+
"""
498+
$(TYPEDSIGNATURES)
499+
500+
For a list of variables `vars` in a connect equation, return the subset of them ignore
501+
(as a list of symbolic variables) based on `ignored_system_aps`, the analysis points to
502+
be ignored. All analysis points in `ignored_system_aps` must contain variables as their
503+
input/outputs.
504+
"""
505+
function variables_to_ignore(ignored_variable_aps::Vector{HierarchyAnalysisPointT},
506+
vars::Union{Vector{<:BasicSymbolic}, Tuple{Vararg{<:BasicSymbolic}}})
507+
to_ignore = eltype(vars)[]
508+
for ap in ignored_variable_aps
509+
ivar_hierarchy = HierarchyVariableT([ap[1].input; @view ap[2:end]])
510+
ivar = from_hierarchy(ivar_hierarchy)
511+
any(isequal(ivar), vars) || continue
512+
513+
for outvar in ap[1].outputs
514+
ovar_hierarchy = HierarchyVariableT([outvar; @view ap[2:end]])
515+
ovar = from_hierarchy(ovar_hierarchy)
516+
any(isequal(ovar), vars) || continue
517+
push!(to_ignore, ovar)
518+
end
519+
end
520+
521+
return to_ignore
522+
end
523+
446524
"""
447525
$(TYPEDSIGNATURES)
448526
@@ -456,26 +534,12 @@ Generate connection sets from `connect` equations.
456534
- `sys` is the system whose equations are to be searched.
457535
- `namespace` is a system representing the namespace in which `sys` exists, or `nothing`
458536
for no namespace (if `sys` is top-level).
459-
- `ignored_connects` is a tuple. The first (second) element is a list of systems
460-
(variables) in the format returned by `as_hierarchy` to be ignored when generating
461-
connections. This is typically because the connections they are used in were removed by
462-
analysis point transformations.
463537
"""
464538
function generate_connection_set!(connectionsets, domain_csets,
465539
sys::AbstractSystem, find, replace, scalarize, namespace = nothing,
466-
ignored_connects = (HierarchySystemT[], HierarchyVariableT[]))
540+
ignored_connects = (HierarchyAnalysisPointT[], HierarchyAnalysisPointT[]))
467541
subsys = get_systems(sys)
468-
ignored_systems, ignored_variables = ignored_connects
469-
# turn hierarchies into namespaced systems
470-
namespaced_ignored_systems = from_hierarchy.(ignored_systems)
471-
namespaced_ignored_variables = from_hierarchy.(ignored_variables)
472-
namespaced_ignored = (namespaced_ignored_systems, namespaced_ignored_variables)
473-
# filter the subsystems of `sys` to exclude ignored ones
474-
filtered_subsys = filter(subsys) do ss
475-
all(namespaced_ignored_systems) do igsys
476-
nameof(igsys) != nameof(ss)
477-
end
478-
end
542+
ignored_system_aps, ignored_variable_aps = ignored_connects
479543

480544
isouter = generate_isouter(sys)
481545
eqs′ = get_eqs(sys)
@@ -501,8 +565,12 @@ function generate_connection_set!(connectionsets, domain_csets,
501565
neweq isa AbstractArray ? append!(eqs, neweq) : push!(eqs, neweq)
502566
else
503567
if lhs isa Connection && get_systems(lhs) === :domain
504-
connection2set!(domain_csets, namespace, get_systems(rhs), isouter;
505-
ignored_connects, namespaced_ignored_systems)
568+
connected_systems = get_systems(rhs)
569+
connection2set!(domain_csets, namespace, connected_systems, isouter;
570+
ignored_systems = systems_to_ignore(
571+
ignored_system_aps, connected_systems),
572+
ignored_variables = variables_to_ignore(
573+
ignored_variable_aps, connected_systems))
506574
elseif isconnection(rhs)
507575
push!(cts, get_systems(rhs))
508576
else
@@ -519,22 +587,19 @@ function generate_connection_set!(connectionsets, domain_csets,
519587
# all connectors are eventually inside connectors.
520588
T = ConnectionElement
521589
# only generate connection sets for systems that are not ignored
522-
for s in filtered_subsys
590+
for s in subsys
523591
isconnector(s) || continue
524592
is_domain_connector(s) && continue
525-
_ignored_variables = ignored_systems_for_subsystem(s, ignored_variables)
526-
_namespaced_ignored_variables = from_hierarchy.(_ignored_variables)
527593
for v in unknowns(s)
528594
Flow === get_connection_type(v) || continue
529-
# ignore specified variables
530-
any(isequal(v), _namespaced_ignored_variables) && continue
531595
push!(connectionsets, ConnectionSet([T(LazyNamespace(namespace, s), v, false)]))
532596
end
533597
end
534598

535599
for ct in cts
536600
connection2set!(connectionsets, namespace, ct, isouter;
537-
ignored_connects, namespaced_ignored_systems)
601+
ignored_systems = systems_to_ignore(ignored_system_aps, ct),
602+
ignored_variables = variables_to_ignore(ignored_variable_aps, ct))
538603
end
539604

540605
# pre order traversal
@@ -558,14 +623,15 @@ ignored by `generate_connection_set!` (`expand_variable_connections`), filter
558623
their hierarchy to not include `subsys`.
559624
"""
560625
function ignored_systems_for_subsystem(
561-
subsys::AbstractSystem, ignored_systems::Vector{<:HierarchyT})
626+
subsys::AbstractSystem, ignored_systems::Vector{<:Union{
627+
HierarchyT, HierarchyAnalysisPointT}})
562628
result = eltype(ignored_systems)[]
563629
# in case `subsys` is namespaced, get its hierarchy and compare suffixes
564630
# instead of the just the last element
565631
suffix = reverse!(namespace_hierarchy(nameof(subsys)))
566632
N = length(suffix)
567633
for igsys in ignored_systems
568-
if igsys[(end - N + 1):end] == suffix
634+
if length(igsys) > N && igsys[(end - N + 1):end] == suffix
569635
push!(result, copy(igsys))
570636
for i in 1:N
571637
pop!(result[end])
@@ -684,7 +750,6 @@ function expand_variable_connections(sys::AbstractSystem; ignored_variables = no
684750
if ignored_variables === nothing
685751
ignored_variables = ignored_connections(sys)[2]
686752
end
687-
namespaced_ignored = from_hierarchy.(ignored_variables)
688753
eqs = copy(get_eqs(sys))
689754
valid_idxs = trues(length(eqs))
690755
additional_eqs = Equation[]
@@ -694,14 +759,11 @@ function expand_variable_connections(sys::AbstractSystem; ignored_variables = no
694759
connection = eq.rhs
695760
elements = get_systems(connection)
696761
is_causal_variable_connection(connection) || continue
697-
elements = filter(elements) do el
698-
all(namespaced_ignored) do var
699-
getname(var) != getname(el.var)
700-
end
701-
end
702762

703763
valid_idxs[i] = false
704764
elements = map(x -> x.var, elements)
765+
to_ignore = variables_to_ignore(ignored_variables, elements)
766+
elements = setdiff(elements, to_ignore)
705767
outvar = first(elements)
706768
for invar in Iterators.drop(elements, 1)
707769
push!(additional_eqs, outvar ~ invar)

src/systems/diffeqs/odesystem.jl

+5-4
Original file line numberDiff line numberDiff line change
@@ -189,11 +189,12 @@ struct ODESystem <: AbstractODESystem
189189
"""
190190
split_idxs::Union{Nothing, Vector{Vector{Int}}}
191191
"""
192-
The connections to ignore (since they're removed by analysis point transformations).
193-
The first element of the tuple are systems that can't be in the same connection set,
194-
and the second are variables (for the trivial form of `connect`).
192+
The analysis points removed by transformations, representing connections to be
193+
ignored. The first element of the tuple analysis points connecting systems and
194+
the second are ones connecting variables (for the trivial form of `connect`).
195195
"""
196-
ignored_connections::Union{Nothing, Tuple{Vector{ODESystem}, Vector{BasicSymbolic}}}
196+
ignored_connections::Union{
197+
Nothing, Tuple{Vector{IgnoredAnalysisPoint}, Vector{IgnoredAnalysisPoint}}}
197198
"""
198199
The hierarchical parent system before simplification.
199200
"""

0 commit comments

Comments
 (0)