Skip to content

Commit 1b8b19a

Browse files
authored
Merge pull request #2009 from Keno/kf/pantelidesvarwhitelist
Fix pantelides-with-ag corner case
2 parents 1146ba6 + c92980e commit 1b8b19a

File tree

1 file changed

+90
-21
lines changed

1 file changed

+90
-21
lines changed

src/structural_transformation/pantelides.jl

Lines changed: 90 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,95 @@ function pantelides_reassemble(state::TearingState, var_eq_matching)
6969
return sys
7070
end
7171

72+
"""
73+
computed_highest_diff_variables(var_to_diff, ag)
74+
75+
Computes which variables are the "highest-differentiated" for purposes of
76+
pantelides. Ordinarily this is relatively straightforward. However, in our
77+
case, there are two complicating conditions:
78+
79+
1. We allow variables in the structure graph that don't appear in the
80+
system at all. What we are interested in is the highest-differentiated
81+
variable that actually appears in the system.
82+
83+
2. We have an alias graph. The alias graph implicitly contributes an
84+
alias equation, so it doesn't actually whitelist any additional variables,
85+
but it may change which variable is considered the highest differentiated one.
86+
Consider the following situation:
87+
88+
Vars: x, y
89+
Eqs: 0 = f(x)
90+
Alias: ẋ = ẏ
91+
92+
In the absence of the alias, we would consider `x` to be the highest
93+
differentiated variable. However, because of the alias (and because there
94+
is no alias for `x=y`), we actually need to take `ẋ` as the highest
95+
differentiated variable.
96+
97+
This function takes care of these complications are returns a boolean array
98+
for every variable, indicating whether it is considered "highest-differentiated".
99+
"""
100+
function computed_highest_diff_variables(structure, ag::Union{AliasGraph, Nothing})
101+
@unpack graph, var_to_diff = structure
102+
103+
nvars = length(var_to_diff)
104+
varwhitelist = falses(nvars)
105+
for var in 1:nvars
106+
if var_to_diff[var] === nothing && !varwhitelist[var]
107+
# This variable is structurally highest-differentiated, but may not actually appear in the
108+
# system (complication 1 above). Ascend the differentiation graph to find the highest
109+
# differentiated variable that does appear in the system or the alias graph).
110+
while isempty(𝑑neighbors(graph, var)) && (ag === nothing || !haskey(ag, var))
111+
var′ = invview(var_to_diff)[var]
112+
var′ === nothing && break
113+
var = var′
114+
end
115+
# If we don't have an alias graph, we are done. If we do have an alias graph, we may
116+
# have to keep going along the stem, for as long as our differentiation path
117+
# matches that of the stem (see complication 2 above). Note that we may end up
118+
# whitelisting multiple differentiation levels of the stem here from different
119+
# starting points that all map to the same stem. We clean that up in a post-processing
120+
# pass below.
121+
if ag !== nothing && haskey(ag, var)
122+
(_, stem) = ag[var]
123+
stem == 0 && continue
124+
# Ascend the stem
125+
while isempty(𝑑neighbors(graph, var))
126+
var′ = invview(var_to_diff)[var]
127+
var′ === nothing && break
128+
stem′ = invview(var_to_diff)[var]
129+
# Invariant from alias elimination: Stem is chosen to have
130+
# the highest differentiation order.
131+
@assert stem′ !== nothing
132+
if !haskey(ag, var′) || ag[var′][2] != stem′
133+
varwhitelist[stem] = true
134+
break
135+
end
136+
stem = stem′
137+
var = var′
138+
end
139+
else
140+
varwhitelist[var] = true
141+
end
142+
end
143+
end
144+
145+
# Remove any variables from the varwhitelist for whom a higher-differentiated
146+
# var is already on the whitelist.
147+
for var in 1:nvars
148+
varwhitelist[var] || continue
149+
var′ = var
150+
while (var′ = var_to_diff[var′]) !== nothing
151+
if varwhitelist[var′]
152+
varwhitelist[var] = false
153+
break
154+
end
155+
end
156+
end
157+
158+
return varwhitelist
159+
end
160+
72161
"""
73162
pantelides!(state::TransformationState; kwargs...)
74163
@@ -86,27 +175,7 @@ function pantelides!(state::TransformationState, ag::Union{AliasGraph, Nothing}
86175
nnonemptyeqs = count(eq -> !isempty(𝑠neighbors(graph, eq)) && eq_to_diff[eq] === nothing,
87176
1:neqs′)
88177

89-
# Allow matching for the highest differentiated variable that
90-
# currently appears in an equation (or implicit equation in a side ag)
91-
varwhitelist = falses(nvars)
92-
for var in 1:nvars
93-
if var_to_diff[var] === nothing && !varwhitelist[var]
94-
while isempty(𝑑neighbors(graph, var)) && (ag === nothing || !haskey(ag, var))
95-
var′ = invview(var_to_diff)[var]
96-
var′ === nothing && break
97-
var = var′
98-
end
99-
if !isempty(𝑑neighbors(graph, var))
100-
if ag !== nothing && haskey(ag, var)
101-
# TODO: remove lower diff vars from whitelist
102-
c, a = ag[var]
103-
iszero(c) || (varwhitelist[a] = true)
104-
else
105-
varwhitelist[var] = true
106-
end
107-
end
108-
end
109-
end
178+
varwhitelist = computed_highest_diff_variables(state.structure, ag)
110179

111180
if nnonemptyeqs > count(varwhitelist)
112181
throw(InvalidSystemException("System is structurally singular"))

0 commit comments

Comments
 (0)