Skip to content

Commit 61b79a8

Browse files
committed
add option to include disturbance arguments in generate_control_function
1 parent ba842c2 commit 61b79a8

File tree

2 files changed

+51
-7
lines changed

2 files changed

+51
-7
lines changed

src/inputoutput.jl

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ has_var(ex, x) = x ∈ Set(get_variables(ex))
160160
# Build control function
161161

162162
"""
163-
(f_oop, f_ip), x_sym, p, io_sys = generate_control_function(
163+
(f_oop, f_ip), x_sym, p_sym, io_sys = generate_control_function(
164164
sys::AbstractODESystem,
165165
inputs = unbound_inputs(sys),
166166
disturbance_inputs = nothing;
@@ -177,8 +177,7 @@ f_ip : (xout,x,u,p,t) -> nothing
177177
178178
The return values also include the chosen state-realization (the remaining unknowns) `x_sym` and parameters, in the order they appear as arguments to `f`.
179179
180-
If `disturbance_inputs` is an array of variables, the generated dynamics function will preserve any state and dynamics associated with disturbance inputs, but the disturbance inputs themselves will not be included as inputs to the generated function. The use case for this is to generate dynamics for state observers that estimate the influence of unmeasured disturbances, and thus require unknown variables for the disturbance model, but without disturbance inputs since the disturbances are not available for measurement.
181-
See [`add_input_disturbance`](@ref) for a higher-level interface to this functionality.
180+
If `disturbance_inputs` is an array of variables, the generated dynamics function will preserve any state and dynamics associated with disturbance inputs, but the disturbance inputs themselves will (by default) not be included as inputs to the generated function. The use case for this is to generate dynamics for state observers that estimate the influence of unmeasured disturbances, and thus require unknown variables for the disturbance model, but without disturbance inputs since the disturbances are not available for measurement. To add an input argument corresponding to the disturbance inputs, either include the disturbance inputs among the control inputs, or set `disturbance_argument=true`, in which case an additional input argument `w` is added to the generated function `(x,u,p,t,w)->rhs`.
182181
183182
!!! note "Un-simplified system"
184183
This function expects `sys` to be un-simplified, i.e., `structural_simplify` or `@mtkbuild` should not be called on the system before passing it into this function. `generate_control_function` calls a special version of `structural_simplify` internally.
@@ -196,6 +195,7 @@ f[1](x, inputs, p, t)
196195
"""
197196
function generate_control_function(sys::AbstractODESystem, inputs = unbound_inputs(sys),
198197
disturbance_inputs = disturbances(sys);
198+
disturbance_argument = false,
199199
implicit_dae = false,
200200
simplify = false,
201201
eval_expression = false,
@@ -219,10 +219,11 @@ function generate_control_function(sys::AbstractODESystem, inputs = unbound_inpu
219219
# ps = [ps; disturbance_inputs]
220220
end
221221
inputs = map(x -> time_varying_as_func(value(x), sys), inputs)
222+
disturbance_inputs = map(x -> time_varying_as_func(value(x), sys), disturbance_inputs)
222223

223224
eqs = [eq for eq in full_equations(sys)]
224225
eqs = map(subs_constants, eqs)
225-
if disturbance_inputs !== nothing
226+
if disturbance_inputs !== nothing && !disturbance_argument
226227
# Set all disturbance *inputs* to zero (we just want to keep the disturbance state)
227228
subs = Dict(disturbance_inputs .=> 0)
228229
eqs = [eq.lhs ~ substitute(eq.rhs, subs) for eq in eqs]
@@ -239,16 +240,22 @@ function generate_control_function(sys::AbstractODESystem, inputs = unbound_inpu
239240
t = get_iv(sys)
240241

241242
# pre = has_difference ? (ex -> ex) : get_postprocess_fbody(sys)
242-
243-
args = (u, inputs, p..., t)
243+
if disturbance_argument
244+
args = (u, inputs, p..., t, disturbance_inputs)
245+
else
246+
args = (u, inputs, p..., t)
247+
end
244248
if implicit_dae
245249
ddvs = map(Differential(get_iv(sys)), dvs)
246250
args = (ddvs, args...)
247251
end
248252
process = get_postprocess_fbody(sys)
253+
wrapped_arrays_vars = disturbance_argument ?
254+
wrap_array_vars(sys, rhss; dvs, ps, inputs, disturbance_inputs) :
255+
wrap_array_vars(sys, rhss; dvs, ps, inputs)
249256
f = build_function(rhss, args...; postprocess_fbody = process,
250257
expression = Val{true}, wrap_code = wrap_mtkparameters(sys, false, 3) .∘
251-
wrap_array_vars(sys, rhss; dvs, ps, inputs) .∘
258+
wrapped_arrays_vars .∘
252259
wrap_parameter_dependencies(sys, false),
253260
kwargs...)
254261
f = eval_or_rgf.(f; eval_expression, eval_module)

test/input_output_handling.jl

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,43 @@ x = [rand()]
170170
u = [rand()]
171171
@test f[1](x, u, p, 1) == -x + u
172172

173+
# With disturbance inputs
174+
@variables x(t)=0 u(t)=0 [input = true] d(t)=0
175+
eqs = [
176+
D(x) ~ -x + u + d^2
177+
]
178+
179+
@named sys = ODESystem(eqs, t)
180+
f, dvs, ps, io_sys = ModelingToolkit.generate_control_function(
181+
sys, [u], [d], simplify = true)
182+
183+
@test isequal(dvs[], x)
184+
@test isempty(ps)
185+
186+
p = nothing
187+
x = [rand()]
188+
u = [rand()]
189+
@test f[1](x, u, p, 1) == -x + u
190+
191+
# With added d argument
192+
@variables x(t)=0 u(t)=0 [input = true] d(t)=0
193+
eqs = [
194+
D(x) ~ -x + u + d^2
195+
]
196+
197+
@named sys = ODESystem(eqs, t)
198+
f, dvs, ps, io_sys = ModelingToolkit.generate_control_function(
199+
sys, [u], [d], simplify = true, disturbance_argument = true)
200+
201+
@test isequal(dvs[], x)
202+
@test isempty(ps)
203+
204+
p = nothing
205+
x = [rand()]
206+
u = [rand()]
207+
d = [rand()]
208+
@test f[1](x, u, p, 1, d) == -x + u + [d[]^2]
209+
173210
# more complicated system
174211

175212
@variables u(t) [input = true]

0 commit comments

Comments
 (0)