9696
9797Connect `output_connector` and `input_connector` with an [`AnalysisPoint`](@ref) inbetween.
9898The incoming connection `output_connector` is expected to be of type [`RealOutput`](@ref), and vice versa.
99- NOTE: The connection is assumed to be *causal*, meaning that
99+
100+ *PLEASE NOTE*: The connection is assumed to be *causal*, meaning that
100101```
101102connect(C.output, :plant_input, P.input)
102103```
@@ -152,28 +153,44 @@ function Base.:(==)(ap1::AnalysisPoint, ap2::AnalysisPoint)
152153 return ap1. in == ap2. in && ap1. out == ap2. out # Name doesn't really matter if inputs and outputs are the same
153154end
154155
155- function get_sensitivity_function (sys, ap_name:: Symbol ; kwargs... )
156- find = function (x, ns)
157- x isa AnalysisPoint || return false
158- if ns === nothing
159- nameof (x) === ap_name
160- else
161- Symbol (ns, :_ , nameof (x)) === ap_name
162- end
156+ function namespaced_ap_match (ap_name, loop_openings)
157+ (x, ns) -> namespaced_ap_match (x, ns, ap_name, loop_openings)
158+ end # 🍛
159+
160+ """
161+ namespaced_ap_match(x, ns, ap_name, loop_openings)
162+
163+ Returns true if `x` is an AnalysisPoint and matches either `ap_name` or any of the loop openings if namedspaced with `ns`.
164+ """
165+ function namespaced_ap_match (x, ns, ap_name, loop_openings)
166+ x isa AnalysisPoint || return false
167+ if ns === nothing
168+ nameof (x) === ap_name || (loop_openings != = nothing && nameof (x) ∈ loop_openings)
169+ else
170+ xx = Symbol (ns, :_ , nameof (x))
171+ xx === ap_name || (loop_openings != = nothing && xx ∈ loop_openings)
163172 end
173+ end
174+
175+ function get_sensitivity_function (sys, ap_name:: Symbol ; loop_openings = nothing , kwargs... )
176+ find = namespaced_ap_match (ap_name, loop_openings)
164177 t = get_iv (sys)
165178 @variables d (t) = 0 # Perturbation serving as input to sensitivity transfer function
166179 namespace = Ref {Union{Nothing, Symbol}} (nothing )
167180 apr = Ref {Union{Nothing, AnalysisPoint}} (nothing )
168181 replace = let d = d, namespace = namespace, apr = apr
169182 function (ap, ns)
170- namespace[] = ns # Save the namespace to make it available for renamespace below
171- apr[] = ap
172- (ap. out. u ~ ap. in. u + d), d
183+ if namespaced_ap_match (ap, ns, ap_name, nothing )
184+ namespace[] = ns
185+ apr[] = ap
186+ (ap. out. u ~ ap. in. u + d), d
187+ else # loop opening
188+ [ap. out. u ~ 0 ], []
189+ end
173190 end
174191 end
175192 sys = expand_connections (sys, find, replace)
176- (ap = apr[]) === nothing && error (" Did not find analysis point $ap " )
193+ (ap = apr[]) === nothing && error (" Did not find analysis point $ap_name " )
177194 u = ap. out. u
178195 if (ns = namespace[]) != = nothing
179196 d = ModelingToolkit. renamespace (ns, d)
@@ -182,28 +199,26 @@ function get_sensitivity_function(sys, ap_name::Symbol; kwargs...)
182199 ModelingToolkit. linearization_function (sys, [d], [u]; kwargs... )
183200end
184201
185- function get_comp_sensitivity_function (sys, ap_name:: Symbol ; kwargs... )
186- find = function (x, ns)
187- x isa AnalysisPoint || return false
188- if ns === nothing
189- nameof (x) === ap_name
190- else
191- Symbol (ns, :_ , nameof (x)) === ap_name
192- end
193- end
202+ function get_comp_sensitivity_function (sys, ap_name:: Symbol ; loop_openings = nothing ,
203+ kwargs... )
204+ find = namespaced_ap_match (ap_name, loop_openings)
194205 t = get_iv (sys)
195206 @variables d (t) = 0 # Perturbation serving as input to sensitivity transfer function
196207 namespace = Ref {Union{Nothing, Symbol}} (nothing )
197208 apr = Ref {Union{Nothing, AnalysisPoint}} (nothing )
198209 replace = let d = d, namespace = namespace, apr = apr
199210 function (ap, ns)
200- namespace[] = ns # Save the namespace to make it available for renamespace below
201- apr[] = ap
202- (ap. out. u + d ~ ap. in. u), d
211+ if namespaced_ap_match (ap, ns, ap_name, nothing )
212+ namespace[] = ns
213+ apr[] = ap
214+ (ap. out. u + d ~ ap. in. u), d
215+ else # loop opening
216+ [ap. out. u ~ 0 ], []
217+ end
203218 end
204219 end
205220 sys = expand_connections (sys, find, replace)
206- (ap = apr[]) === nothing && error (" Did not find analysis point $ap " )
221+ (ap = apr[]) === nothing && error (" Did not find analysis point $ap_name " )
207222 u = ap. in. u
208223 if (ns = namespace[]) != = nothing
209224 d = ModelingToolkit. renamespace (ns, d)
@@ -212,27 +227,24 @@ function get_comp_sensitivity_function(sys, ap_name::Symbol; kwargs...)
212227 ModelingToolkit. linearization_function (sys, [d], [u]; kwargs... )
213228end
214229
215- function get_looptransfer_function (sys, ap_name:: Symbol ; kwargs... )
216- find = function (x, ns)
217- x isa AnalysisPoint || return false
218- if ns === nothing
219- nameof (x) === ap_name
220- else
221- Symbol (ns, :_ , nameof (x)) === ap_name
222- end
223- end
230+ function get_looptransfer_function (sys, ap_name:: Symbol ; loop_openings = nothing , kwargs... )
231+ find = namespaced_ap_match (ap_name, loop_openings)
224232 t = get_iv (sys)
225233 namespace = Ref {Union{Nothing, Symbol}} (nothing )
226234 apr = Ref {Union{Nothing, AnalysisPoint}} (nothing )
227235 replace = let namespace = namespace, apr = apr
228236 function (ap, ns)
229- namespace[] = ns # Save the namespace to make it available for renamespace below
230- apr[] = ap
231- (0 ~ 0 ), nothing
237+ if namespaced_ap_match (ap, ns, ap_name, nothing )
238+ namespace[] = ns
239+ apr[] = ap
240+ (0 ~ 0 ), nothing
241+ else # loop opening
242+ [ap. out. u ~ 0 ], []
243+ end
232244 end
233245 end
234246 sys = expand_connections (sys, find, replace)
235- (ap = apr[]) === nothing && error (" Did not find analysis point $ap " )
247+ (ap = apr[]) === nothing && error (" Did not find analysis point $ap_name " )
236248 u = ap. out. u
237249 y = ap. in. u
238250 if (ns = namespace[]) != = nothing
@@ -258,15 +270,8 @@ Open the loop at analysis point `ap` by breaking the connection through `ap`.
258270
259271See also [`get_sensitivity`](@ref), [`get_comp_sensitivity`](@ref), [`get_looptransfer`](@ref).
260272"""
261- function open_loop (sys, ap_name:: Symbol ; kwargs... )
262- find = function (x, ns)
263- x isa AnalysisPoint || return false
264- if ns === nothing
265- nameof (x) === ap_name
266- else
267- Symbol (ns, :_ , nameof (x)) === ap_name
268- end
269- end
273+ function open_loop (sys, ap_name:: Symbol ; ground_input = false , kwargs... )
274+ find = namespaced_ap_match (ap_name, nothing )
270275 t = get_iv (sys)
271276 @variables u (t)= 0 [input = true ]
272277 @variables y (t)= 0 [output = true ]
@@ -276,55 +281,61 @@ function open_loop(sys, ap_name::Symbol; kwargs...)
276281 function (ap, ns)
277282 namespace[] = ns # Save the namespace to make it available for renamespace below
278283 apr[] = ap
279- [ap. out. u ~ u, ap. in. u ~ y], [u, y]
284+ if ground_input
285+ [ap. out. u ~ 0 , ap. in. u ~ y], [y]
286+ else
287+ [ap. out. u ~ u, ap. in. u ~ y], [u, y]
288+ end
280289 end
281290 end
282- if (ns = namespace[]) != = nothing
283- y = ModelingToolkit. renamespace (ns, y)
284- u = ModelingToolkit. renamespace (ns, u)
285- end
286291 sys = expand_connections (sys, find, replace)
287- (ap = apr[]) === nothing && error (" Did not find analysis point $ap " )
292+ (ap = apr[]) === nothing && error (" Did not find analysis point $ap_name " )
288293 sys
289294end
290295
291296function ModelingToolkit. linearization_function (sys:: ModelingToolkit.AbstractSystem ,
292297 input_name:: Symbol , output_name:: Symbol ;
298+ loop_openings = nothing ,
293299 kwargs... )
294- find = function (x, ns)
295- x isa AnalysisPoint || return false
296- if ns === nothing
297- nameof (x) ∈ (input_name, output_name)
298- else
299- Symbol (ns, :_ , nameof (x)) ∈ (input_name, output_name)
300- end
301- end
302300 t = get_iv (sys)
303301 @variables u (t)= 0 [input = true ]
304302 @variables y (t)= 0 [output = true ]
305- namespace = Ref {Union{Nothing, Symbol}} (nothing )
306- apr = Ref {Union{Nothing, AnalysisPoint}} (nothing )
307- replace = let u = u, y = y, namespace = namespace, apr = apr
303+ find_input = namespaced_ap_match (input_name, loop_openings)
304+ find_output = namespaced_ap_match (output_name, loop_openings)
305+ find = (x, ns) -> find_input (x, ns) || find_output (x, ns)
306+
307+ namespace_u = Ref {Union{Nothing, Symbol}} (nothing )
308+ namespace_y = Ref {Union{Nothing, Symbol}} (nothing )
309+ apr_u = Ref {Union{Nothing, AnalysisPoint}} (nothing )
310+ apr_y = Ref {Union{Nothing, AnalysisPoint}} (nothing )
311+ replace = let u = u, y = y, namespace_u = namespace_u, apr_u = apr_u,
312+ namespace_y = namespace_y, apr_y = apr_y
313+
308314 function (ap, ns)
309- namespace[] = ns # Save the namespace to make it available for renamespace below
310- apr [] = ap
311- if nameof (ap) === input_name
315+ if namespaced_ap_match (ap, ns, input_name, nothing )
316+ namespace_u [] = ns # Save the namespace to make it available for renamespace below
317+ apr_u[] = ap
312318 [ap. out. u ~ ap. in. u + u], u
313319 # input.in.u ~ 0] # We only need to ground one of the ends, hence not including this equation
314- elseif nameof (ap) === output_name
320+ elseif namespaced_ap_match (ap, ns, output_name, nothing )
321+ namespace_y[] = ns # Save the namespace to make it available for renamespace below
322+ apr_y[] = ap
315323 [ap. in. u ~ y
316324 ap. out. u ~ ap. in. u], y
317- else
318- error ( " This should never happen " )
325+ else # loop opening
326+ [ap . out . u ~ 0 ], []
319327 end
320328 end
321329 end
322330 sys = expand_connections (sys, find, replace)
323- (ap = apr []) === nothing && error (" Did not find analysis point $ap " )
324- if (ns = namespace []) != = nothing
325- y = ModelingToolkit . renamespace (ns, y)
331+ (ap = apr_u []) === nothing && error (" Did not find analysis point $input_name " )
332+ (ap = apr_y []) === nothing && error ( " Did not find analysis point $output_name " )
333+ if (ns = namespace_u[]) != = nothing
326334 u = ModelingToolkit. renamespace (ns, u)
327335 end
336+ if (ns = namespace_y[]) != = nothing
337+ y = ModelingToolkit. renamespace (ns, y)
338+ end
328339 ModelingToolkit. linearization_function (sys, [u], [y]; kwargs... )
329340end
330341
342353
343354# Methods above are implemented in terms of linearization_function, the method below creates wrappers for linearize
344355for f in [:get_sensitivity , :get_comp_sensitivity , :get_looptransfer ]
345- @eval function $f (sys, ap:: Symbol , args... ; kwargs... )
346- lin_fun, ssys = $ (Symbol (string (f) * " _function" ))(sys, ap, args... ; kwargs... )
356+ @eval function $f (sys, ap:: Symbol , args... ; loop_openings = nothing , kwargs... )
357+ lin_fun, ssys = $ (Symbol (string (f) * " _function" ))(sys, ap, args... ; loop_openings,
358+ kwargs... )
347359 ModelingToolkit. linearize (ssys, lin_fun; kwargs... ), ssys
348360 end
349361end
353365
354366Linearize a system between two analysis points. To get a loop-transfer function, see [`get_looptransfer`](@ref)
355367"""
356- function ModelingToolkit. linearize (sys, input_name:: Symbol , output_name:: Symbol ; kwargs... )
357- lin_fun, ssys = linearization_function (sys, input_name, output_name; kwargs... )
368+ function ModelingToolkit. linearize (sys, input_name:: Symbol , output_name:: Symbol ;
369+ loop_openings = nothing , kwargs... )
370+ lin_fun, ssys = linearization_function (sys, input_name, output_name; loop_openings,
371+ kwargs... )
358372 ModelingToolkit. linearize (ssys, lin_fun; kwargs... ), ssys
359373end
360374
0 commit comments