@@ -81,7 +81,7 @@ struct NotInferredTimeDomain end
8181struct InferEquationClosure
8282 varsbuf:: OrderedSet{SymbolicT}
8383 # variables in each argument to an operator
84- arg_varsbuf:: Set {SymbolicT}
84+ arg_varsbuf:: OrderedSet {SymbolicT}
8585 # hyperedge for each equation
8686 hyperedge:: Set{ClockVertex.Type}
8787 # hyperedge for each argument to an operator
@@ -95,51 +95,87 @@ struct InferEquationClosure
9595end
9696
9797function InferEquationClosure (var_to_idx, inference_graph)
98- InferEquationClosure (OrderedSet {SymbolicT} (), Set {SymbolicT} (), Set {ClockVertex.Type} (),
98+ InferEquationClosure (OrderedSet {SymbolicT} (), OrderedSet {SymbolicT} (), Set {ClockVertex.Type} (),
9999 Set {ClockVertex.Type} (), Dict {Int, Set{ClockVertex.Type}} (),
100100 Set {ClockVertex.Type} (), var_to_idx, inference_graph)
101101end
102102
103- function (iec:: InferEquationClosure )(ieq:: Int , eq:: Equation , is_initialization_equation:: Bool )
104- (; varsbuf, arg_varsbuf, hyperedge, arg_hyperedge, relative_hyperedges) = iec
105- (; must_be_discrete, var_to_idx, inference_graph) = iec
106- empty! (varsbuf)
107- empty! (hyperedge)
108- # get variables in equation
109- SU. search_variables! (varsbuf, eq)
110- # add the equation to the hyperedge
111- eq_node = if is_initialization_equation
112- ClockVertex. InitEquation (ieq)
113- else
114- ClockVertex. Equation (ieq)
115- end
116- push! (hyperedge, eq_node)
117- for var in varsbuf
118- idx = get (var_to_idx, var, nothing )
119- # if this is just a single variable, add it to the hyperedge
120- if idx isa Int
121- push! (hyperedge, ClockVertex. Variable (idx))
122- d = get_time_domain (var)
123- if is_concrete_time_domain (d)
124- push! (hyperedge, ClockVertex. Clock (d))
125- elseif d isa InferredTimeDomain
126- @match d begin
127- InferredClock. Inferred (id) => push! (hyperedge, ClockVertex. InferredClock (id))
128- _ => nothing
129- end
130- elseif d isa MTKBase. IntegerSequence
131- push! (hyperedge, ClockVertex. IntegerSequence ())
132- end
133-
134- # we don't immediately `continue` here because this variable might be a
135- # `Sample` or similar and we want the clock information from it if it is.
136- end
103+ struct InferVariableClosure
104+ # The hyperedge that this variable exists in. Could be the hyperedge for an equation,
105+ # or one for the argument of a clock operator. The parent is responsible for
106+ # adding this hyperedge to the inference graph.
107+ parent_hyperedge:: Set{ClockVertex.Type}
108+ # The list of variables to be explored in the parent. This allows
109+ # the variable to queue up additional variables to be searched
110+ # in the same context.
111+ vars_in_parent:: OrderedSet{SymbolicT}
112+ # Mapping from `i` in `InferredDiscrete(i)` to the vertices in that inferred partition.
113+ relative_hyperedges:: Dict{Int, Set{ClockVertex.Type}}
114+ # Variables in each argument to an operator.
115+ arg_varsbuf:: OrderedSet{SymbolicT}
116+ # Hyperedge for each argument to an operator. This closure is responsible for
117+ # adding this hyperedge to the inference graph.
118+ arg_hyperedge:: Set{ClockVertex.Type}
119+ var_to_idx:: Dict{SymbolicT, Int}
120+ must_be_discrete:: Set{ClockVertex.Type}
121+ inference_graph:: HyperGraph{ClockVertex.Type}
122+ end
123+
124+ # When searching a variable/operator application present directly in an equation
125+ function InferVariableClosure (iec:: InferEquationClosure )
126+ return InferVariableClosure (
127+ # This exists in the equation's hyperedge.
128+ iec. hyperedge,
129+ # Use the equation's queue for variables to explore.
130+ iec. varsbuf,
131+ # The next three are basically just preallocated buffers. Theoretically,
132+ # these could be omitted from both structs. However, it's very common to
133+ # have at least one clock operator in an expression, and unlikely to nest.
134+ # So this allows saving unnecessary allocations in each call to the
135+ # `InferVariableClosure`. It also allows the
136+ # `InferVariableClosure(::InferVariableClosure)` constructor to be simpler.
137+ iec. relative_hyperedges,
138+ iec. arg_varsbuf,
139+ iec. arg_hyperedge,
140+ # Shared information propagated across the hierarchy.
141+ iec. var_to_idx,
142+ iec. must_be_discrete,
143+ iec. inference_graph,
144+ )
145+ end
146+
147+ # When searching a variable/operator application present in the argument of
148+ # a clock operator. The returned `InferVariableClosure` will be called recursively
149+ # from the `ivc` passed in.
150+ function InferVariableClosure (ivc:: InferVariableClosure )
151+ return InferVariableClosure (
152+ # This exists in the clock operator argument's hyperedge.
153+ ivc. arg_hyperedge,
154+ # Use the operator argument's queue.
155+ ivc. arg_varsbuf,
156+ # Cannot reuse buffers, allocate new ones.
157+ Dict {Int, Set{ClockVertex.Type}} (),
158+ OrderedSet {SymbolicT} (),
159+ Set {ClockVertex.Type} (),
160+ # Same shared information.
161+ ivc. var_to_idx,
162+ ivc. must_be_discrete,
163+ ivc. inference_graph,
164+ )
165+ end
166+
167+ function (ivc:: InferVariableClosure )(var:: SymbolicT )
168+ # NOTE: Never add `hyperedge` to the graph. The parent is responsible for doing so.
169+ (; var_to_idx, parent_hyperedge, vars_in_parent, relative_hyperedges) = ivc
170+ (; arg_varsbuf, arg_hyperedge, must_be_discrete, inference_graph) = ivc
171+ hyperedge = parent_hyperedge
172+
173+ idx = get (var_to_idx, var, nothing )
174+ # if this is just a single variable, add it to the hyperedge
175+ if idx isa Int
176+ push! (hyperedge, ClockVertex. Variable (idx))
137177 d = get_time_domain (var)
138- if d === nothing
139- arrv, isarr = MTKBase. split_indexed_var (var)
140- d = get_time_domain (arrv)
141- end
142- if is_concrete_time_domain (d):: Bool
178+ if is_concrete_time_domain (d)
143179 push! (hyperedge, ClockVertex. Clock (d))
144180 elseif d isa InferredTimeDomain
145181 @match d begin
@@ -150,109 +186,158 @@ function (iec::InferEquationClosure)(ieq::Int, eq::Equation, is_initialization_e
150186 push! (hyperedge, ClockVertex. IntegerSequence ())
151187 end
152188
153- @match var begin
154- BSImpl. Term (; f, args) && if f isa Shift end => begin
155- d = get_time_domain (args[1 ])
156- if is_concrete_time_domain (d):: Bool
157- push! (hyperedge, ClockVertex. Clock (d))
158- elseif d isa InferredTimeDomain
159- @match d begin
160- InferredClock. Inferred (id) => push! (hyperedge, ClockVertex. InferredClock (id))
161- _ => nothing
162- end
163- elseif d isa MTKBase. IntegerSequence
164- push! (hyperedge, ClockVertex. IntegerSequence ())
165- end
166- end
189+ # we don't immediately `return` here because this variable might be a
190+ # `Sample` or similar and we want the clock information from it if it is.
191+ end
192+
193+ # Leverage concrete clock information about this variable/operator application
194+ d = get_time_domain (var)
195+ if d === nothing
196+ arrv, isarr = MTKBase. split_indexed_var (var)
197+ d = get_time_domain (arrv)
198+ end
199+ if is_concrete_time_domain (d):: Bool
200+ push! (hyperedge, ClockVertex. Clock (d))
201+ elseif d isa InferredTimeDomain
202+ @match d begin
203+ InferredClock. Inferred (id) => push! (hyperedge, ClockVertex. InferredClock (id))
167204 _ => nothing
168205 end
206+ elseif d isa MTKBase. IntegerSequence
207+ push! (hyperedge, ClockVertex. IntegerSequence ())
208+ end
169209
170- # now we only care about synchronous operators
171- op, args = @match var begin
172- BSImpl. Term (; f, args) && if is_timevarying_operator (f):: Bool end => (f, args)
173- if SU. is_array_shape (SU. shape (var)) end => begin
174- for i in SU. stable_eachindex (var)
175- push! (varsbuf, var[i])
210+ @match var begin
211+ # `Shift` sets the clock metadata of its input, so use that
212+ BSImpl. Term (; f, args) && if f isa Shift end => begin
213+ d = get_time_domain (args[1 ])
214+ if is_concrete_time_domain (d):: Bool
215+ push! (hyperedge, ClockVertex. Clock (d))
216+ elseif d isa InferredTimeDomain
217+ @match d begin
218+ InferredClock. Inferred (id) => push! (hyperedge, ClockVertex. InferredClock (id))
219+ _ => nothing
176220 end
177- continue
221+ elseif d isa MTKBase. IntegerSequence
222+ push! (hyperedge, ClockVertex. IntegerSequence ())
178223 end
179- _ => continue
180224 end
225+ _ => nothing
226+ end
181227
182- # arguments and corresponding time domains
183- tdomains = input_timedomain (op, args) :: Vector{InputTimeDomainElT}
184- nargs = length ( args)
185- ndoms = length (tdomains)
186- if nargs != ndoms
187- throw ( ArgumentError ( """
188- Operator $op applied to $nargs arguments $args but only returns $ndoms \
189- domains $tdomains from `input_timedomain`.
190- """ ))
228+ # now we only care about synchronous operators
229+ op, args = @match var begin
230+ BSImpl . Term (; f, args) && if is_timevarying_operator (f) :: Bool end => (f, args)
231+ # If we have a symbolic array variable, process each of its elements
232+ if SU . is_array_shape (SU . shape (var)) end => begin
233+ for i in SU . stable_eachindex (var)
234+ push! (vars_in_parent, var[i])
235+ end
236+ return
191237 end
238+ _ => return
239+ end
192240
193- # each relative clock mapping is only valid per operator application
194- empty! (relative_hyperedges)
195- for (arg, domain) in zip (args, tdomains)
196- empty! (arg_varsbuf)
197- empty! (arg_hyperedge)
198- # get variables in argument
199- SU. search_variables! (arg_varsbuf, arg; is_atomic = MTKBase. OperatorIsAtomic {Union{Differential, MTKBase.Shift}} ())
200- # get hyperedge for involved variables
201- for v in arg_varsbuf
202- vidx = get (var_to_idx, v, nothing )
203- vidx === nothing && continue
204- push! (arg_hyperedge, ClockVertex. Variable (vidx))
205- end
241+ # arguments and corresponding time domains
242+ tdomains = input_timedomain (op, args):: Vector{InputTimeDomainElT}
243+ nargs = length (args)
244+ ndoms = length (tdomains)
245+ if nargs != ndoms
246+ throw (ArgumentError ("""
247+ Operator $op applied to $nargs arguments $args but only returns $ndoms \
248+ domains $tdomains from `input_timedomain`.
249+ """ ))
250+ end
206251
207- @match domain begin
208- # If the time domain for this argument is a clock, then all variables in this edge have that clock.
209- x:: SciMLBase.AbstractClock => begin
210- # add the clock to the edge
211- push! (arg_hyperedge, ClockVertex. Clock (x))
212- # add the edge to the graph
213- add_edge! (inference_graph, arg_hyperedge)
214- end
215- x:: MTKBase.IntegerSequence => begin
216- push! (arg_hyperedge, ClockVertex. IntegerSequence ())
217- add_edge! (inference_graph, arg_hyperedge)
218- end
219- # We only know that this time domain is inferred. Treat it as a unique domain, all we know is that the
220- # involved variables have the same clock.
221- InferredClock. Inferred () => add_edge! (inference_graph, arg_hyperedge)
222- # All `InferredDiscrete` with the same `i` have the same clock (including output domain) so we don't
223- # add the edge, and instead add this to the `relative_hyperedges` mapping.
224- InferredClock. InferredDiscrete (i) => begin
225- relative_edge = get! (Set{ClockVertex. Type}, relative_hyperedges, i)
226- union! (relative_edge, arg_hyperedge)
227- # Ensure that this clock partition will be discrete.
228- union! (must_be_discrete, arg_hyperedge)
229- add_edge! (inference_graph, arg_hyperedge)
230- end
252+ nested_ivc = InferVariableClosure (ivc)
253+ # each relative clock mapping is only valid per operator application
254+ empty! (relative_hyperedges)
255+ for (arg, domain) in zip (args, tdomains)
256+ empty! (arg_varsbuf)
257+ empty! (arg_hyperedge)
258+ # get variables in argument
259+ SU. search_variables! (arg_varsbuf, arg)
260+ # get hyperedge for involved variables
261+ for v in arg_varsbuf
262+ nested_ivc (v)
263+ end
264+
265+ # NOTE: Ensure all branches here add `arg_hyperedge` to the inference graph. It
266+ # is the parent hyperedge for `nested_ivc`, so this closure is responsible for
267+ # adding it to the graph.
268+ @match domain begin
269+ # If the time domain for this argument is a clock, then all variables in this edge have that clock.
270+ x:: SciMLBase.AbstractClock => begin
271+ # add the clock to the edge
272+ push! (arg_hyperedge, ClockVertex. Clock (x))
273+ # add the edge to the graph
274+ add_edge! (inference_graph, arg_hyperedge)
275+ end
276+ x:: MTKBase.IntegerSequence => begin
277+ push! (arg_hyperedge, ClockVertex. IntegerSequence ())
278+ add_edge! (inference_graph, arg_hyperedge)
279+ end
280+ # We only know that this time domain is inferred. Treat it as a unique domain, all we know is that the
281+ # involved variables have the same clock.
282+ InferredClock. Inferred () => add_edge! (inference_graph, arg_hyperedge)
283+ # All `InferredDiscrete` with the same `i` have the same clock (including output domain) so we don't
284+ # add the edge, and instead add this to the `relative_hyperedges` mapping.
285+ InferredClock. InferredDiscrete (i) => begin
286+ relative_edge = get! (Set{ClockVertex. Type}, relative_hyperedges, i)
287+ union! (relative_edge, arg_hyperedge)
288+ # Ensure that this clock partition will be discrete.
289+ union! (must_be_discrete, arg_hyperedge)
290+ add_edge! (inference_graph, arg_hyperedge)
231291 end
232292 end
293+ end
233294
234- outdomain = output_timedomain (op, args)
235- if outdomain isa SciMLBase. AbstractClock
236- push! (hyperedge, ClockVertex. Clock (outdomain))
237- elseif outdomain isa InferredTimeDomain
238- @match outdomain begin
239- InferredClock. Inferred () => nothing
240- InferredClock. InferredDiscrete (i) => begin
241- buffer = get (relative_hyperedges, i, nothing )
242- if buffer != = nothing
243- union! (hyperedge, buffer)
244- delete! (relative_hyperedges, i)
245- end
246- union! (must_be_discrete, hyperedge)
295+ # Handle the output timedomain of this clock operator application.
296+ outdomain = output_timedomain (op, args)
297+ if outdomain isa SciMLBase. AbstractClock
298+ # If it is a clock we know, simply add it to the parent edge.
299+ push! (hyperedge, ClockVertex. Clock (outdomain))
300+ elseif outdomain isa InferredTimeDomain
301+ @match outdomain begin
302+ InferredClock. Inferred () => nothing
303+ InferredClock. InferredDiscrete (i) => begin
304+ # If we've encountered this relative inferreddiscrete index before,
305+ # we know that that relative hyperedge is the output. So include it
306+ # in the parent hyperedge.
307+ buffer = get (relative_hyperedges, i, nothing )
308+ if buffer != = nothing
309+ union! (hyperedge, buffer)
310+ delete! (relative_hyperedges, i)
247311 end
312+ # Everything here must be discrete.
313+ union! (must_be_discrete, hyperedge)
248314 end
249- else
250- error (" Unreachable reached!" )
251315 end
316+ else
317+ error (" Unreachable reached!" )
318+ end
252319
253- for (_, relative_edge) in relative_hyperedges
254- add_edge! (inference_graph, relative_edge)
255- end
320+ for (_, relative_edge) in relative_hyperedges
321+ add_edge! (inference_graph, relative_edge)
322+ end
323+ end
324+
325+ function (iec:: InferEquationClosure )(ieq:: Int , eq:: Equation , is_initialization_equation:: Bool )
326+ (; varsbuf, hyperedge, inference_graph) = iec
327+ empty! (varsbuf)
328+ empty! (hyperedge)
329+ # get variables in equation
330+ SU. search_variables! (varsbuf, eq)
331+ # add the equation to the hyperedge
332+ eq_node = if is_initialization_equation
333+ ClockVertex. InitEquation (ieq)
334+ else
335+ ClockVertex. Equation (ieq)
336+ end
337+ push! (hyperedge, eq_node)
338+ ivc = InferVariableClosure (iec)
339+ for var in varsbuf
340+ ivc (var)
256341 end
257342
258343 add_edge! (inference_graph, hyperedge)
0 commit comments