Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions docs/src/tutorials/disturbance_modeling.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,11 +203,7 @@ using Test

but we may also generate the functions ``f`` and ``g`` for state estimation:

!!! warning "Example currently disabled"

This example is currently disabled due to compatibility issues with `generate_control_function` and analysis points in the current ModelingToolkit stack.

```julia
```@example DISTURBANCE_MODELING
inputs = [ssys.u]
disturbance_inputs = [ssys.d1, ssys.d2]
P = ssys.system_model
Expand Down
42 changes: 33 additions & 9 deletions lib/ModelingToolkitBase/src/systems/analysis_points.jl
Original file line number Diff line number Diff line change
Expand Up @@ -865,9 +865,11 @@ function open_loop(sys, ap::Union{Symbol, AnalysisPoint}; system_modifier = iden
end

"""
generate_control_function(sys::ModelingToolkitBase.AbstractSystem, input_ap_name::Union{Symbol, Vector{Symbol}, AnalysisPoint, Vector{AnalysisPoint}}, dist_ap_name::Union{Symbol, Vector{Symbol}, AnalysisPoint, Vector{AnalysisPoint}}; system_modifier = identity, kwargs)
generate_control_function(sys::ModelingToolkitBase.AbstractSystem, input_ap_name::Union{Symbol, Vector{Symbol}, AnalysisPoint, Vector{AnalysisPoint}}, dist_ap_name::Union{Symbol, Vector{Symbol}, AnalysisPoint, Vector{AnalysisPoint}}; system_modifier = identity, known_disturbance_inputs = nothing, kwargs)

When called with analysis points as input arguments, we assume that all analysis points corresponds to connections that should be opened (broken). The use case for this is to get rid of input signal blocks, such as `Step` or `Sine`, since these are useful for simulation but are not needed when using the plant model in a controller or state estimator.

The `known_disturbance_inputs` keyword accepts analysis points (or symbols) for disturbances that should be treated as known (i.e., provided as an additional function argument `w`). The loops at these analysis points are opened and the resulting variables are forwarded to the base `generate_control_function`.
"""
function generate_control_function(
sys::ModelingToolkitBase.AbstractSystem, input_ap_name::Union{
Expand All @@ -877,6 +879,9 @@ function generate_control_function(
Nothing, Symbol, Vector{Symbol}, AnalysisPoint, Vector{AnalysisPoint},
} = nothing;
system_modifier = identity,
known_disturbance_inputs::Union{
Nothing, Symbol, Vector{Symbol}, AnalysisPoint, Vector{AnalysisPoint},
} = nothing,
kwargs...
)
input_ap_name = canonicalize_ap(sys, input_ap_name)
Expand All @@ -885,16 +890,35 @@ function generate_control_function(
sys, (du, _) = open_loop(sys, input_ap)
push!(u, du)
end
if dist_ap_name === nothing
return ModelingToolkitBase.generate_control_function(system_modifier(sys), u; kwargs...)

# Open loops for known disturbance APs and collect their variables.
# Use append! (not push!) because open_loop returns a Vector{SymbolicT}
# from Break, and the base function expects a flat vector of symbolic vars
known_dist_vars = nothing
if known_disturbance_inputs !== nothing
known_disturbance_inputs = canonicalize_ap(sys, known_disturbance_inputs)
known_dist_vars = []
for kd_ap in known_disturbance_inputs
sys, (du, _) = open_loop(sys, kd_ap)
append!(known_dist_vars, du)
end
end

dist_ap_name = canonicalize_ap(sys, dist_ap_name)
d = []
for dist_ap in dist_ap_name
sys, (du, _) = open_loop(sys, dist_ap)
push!(d, du)
# Open loops for unknown disturbance APs (positional dist_ap_name)
d = nothing
if dist_ap_name !== nothing
dist_ap_name = canonicalize_ap(sys, dist_ap_name)
d = []
for dist_ap in dist_ap_name
sys, (du, _) = open_loop(sys, dist_ap)
push!(d, du)
end
end

return ModelingToolkitBase.generate_control_function(system_modifier(sys), u, d; kwargs...)
# Always pass disturbance_inputs explicitly to prevent the base function
# from defaulting to disturbances(sys), which would overlap with variables
# we already opened and get substituted to zero.
return ModelingToolkitBase.generate_control_function(
system_modifier(sys), u, d;
known_disturbance_inputs = known_dist_vars, kwargs...)
end
21 changes: 21 additions & 0 deletions test/downstream/test_disturbance_model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,27 @@ d = [0, 0, 1]
@test measurement2(x, u, p, 0.0, d) == [1] # We have now disturbed the output
@test measurement3(x, u, p, 0.0, d) == [1] # Test new interface

## Test known_disturbance_inputs with analysis points (issue #4215) ================
# This exercises the AP override's known_disturbance_inputs keyword using symbols
f_known, x_sym_known,
p_sym_known,
io_sys_known = ModelingToolkit.generate_control_function(
model_with_disturbance, [:u];
known_disturbance_inputs = [:d1, :d2, :dy], split = false
)

op_known = ModelingToolkit.inputs(io_sys_known) .=> 0
x0_known = ModelingToolkit.get_u0(io_sys_known, op_known)
p_known = ModelingToolkit.get_p(io_sys_known, op_known)
x_known = zeros(length(x_sym_known))
u_known = zeros(1)
d_known = zeros(3)
@test f_known[1](x_known, u_known, p_known, t, d_known) == zeros(length(x_sym_known))

# Non-zero known disturbance
d_known = [1, 0, 0]
@test sort(f_known[1](x_known, u_known, p_known, 0.0, d_known)) == [0, 0, 0, 1, 1]

## Further downstream tests that the functions generated above actually have the properties required to use for state estimation
#
# using LowLevelParticleFilters, SeeToDee
Expand Down
Loading