Skip to content

Commit 80af1c9

Browse files
authored
add options and test to causal simplification (#77)
* add options and test to causal simplification * Update ode_system.jl * fix
1 parent ffb9916 commit 80af1c9

File tree

2 files changed

+71
-15
lines changed

2 files changed

+71
-15
lines changed

src/ode_system.jl

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,8 @@ end
5656

5757
symstr(x) = Symbol(x isa AnalysisPoint ? x.name : string(x))
5858

59-
6059
"""
61-
RobustAndOptimalControl.named_ss(sys::ModelingToolkit.AbstractSystem, inputs, outputs; descriptor=true, simple_infeigs=true, kwargs...)
60+
RobustAndOptimalControl.named_ss(sys::ModelingToolkit.AbstractSystem, inputs, outputs; descriptor=true, simple_infeigs=true, balance=false, kwargs...)
6261
6362
Convert an `System` to a `NamedStateSpace` using linearization. `inputs, outputs` are vectors of variables determining the inputs and outputs respectively. See docstring of `ModelingToolkit.linearize` for more info on `kwargs`.
6463
@@ -75,6 +74,8 @@ function RobustAndOptimalControl.named_ss(
7574
outputs;
7675
descriptor = true,
7776
simple_infeigs = true,
77+
balance = descriptor && !simple_infeigs, # balance only if descriptor is true and simple_infeigs is false
78+
big = false,
7879
kwargs...,
7980
)
8081

@@ -113,7 +114,7 @@ function RobustAndOptimalControl.named_ss(
113114
# This indicates that input derivatives are present
114115
duinds = findall(any(!iszero, eachcol(matrices.B[:, nu+1:end]))) .+ nu
115116
u2du = (1:nu) .=> duinds # This maps inputs to their derivatives
116-
lsys = causal_simplification(matrices, u2du; descriptor, simple_infeigs)
117+
lsys = causal_simplification(matrices, u2du; descriptor, simple_infeigs, big, balance)
117118
else
118119
lsys = ss(matrices...)
119120
end
@@ -123,21 +124,42 @@ function RobustAndOptimalControl.named_ss(
123124
xu = (; x = x0, u = u0)
124125
extra = Dict(:operating_point => xu)
125126
# If simple_infeigs=false, the system might have been reduced and the state names might not match the original system.
126-
x_names = simple_infeigs ? symstr.(unknowns(ssys)) : [Symbol(string(nameof(sys))*"_x$i") for i in 1:lsys.nx]
127-
named_ss(
127+
x_names = get_x_names(lsys, ssys; descriptor, simple_infeigs, balance)
128+
nsys = named_ss(
128129
lsys;
129130
x = x_names,
130131
u = unames,
131132
y = symstr.(outputs),
132133
name = string(Base.nameof(sys)),
133134
extra,
134135
)
136+
RobustAndOptimalControl.set_extra!(nsys, :ssys, ssys)
137+
nsys
138+
end
139+
140+
function get_x_names(lsys, sys; descriptor, simple_infeigs, balance)
141+
generic = if descriptor
142+
!simple_infeigs || balance
143+
else
144+
true
145+
end
146+
if generic
147+
[Symbol(string(nameof(sys))*"_x$i") for i in 1:lsys.nx]
148+
else
149+
symstr.(unknowns(sys))
150+
end
135151
end
136152

137153
"""
138-
causal_simplification(sys, u2duinds::Vector{Pair{Int, Int}}; descriptor=true, simple_infeigs = true)
154+
causal_simplification(sys, u2duinds::Vector{Pair{Int, Int}}; descriptor=true, simple_infeigs=true, balance=false)
155+
156+
If `descriptor = true`, the function `DescriptorSystems.dss2ss` is used. In this case,
157+
- `balance`: indicates whether to balance the system using `DescriptorSystems.gprescale` before conversion to `StateSpace`. Balancing changes the state realization (through scaling).
158+
- `simple_infeigs`: if set to false, further simplification may be performed in some cases.
159+
160+
If `descriptor = false`, the argument `big = true` performs computations in `BigFloat` precision, useful for poorly scaled systems.
139161
"""
140-
function causal_simplification(sys, u2duinds::Vector{Pair{Int, Int}}; descriptor=true, simple_infeigs = true)
162+
function causal_simplification(sys, u2duinds::Vector{Pair{Int, Int}}; balance=false, descriptor=true, simple_infeigs = true, big = false)
141163
fm(x) = convert(Matrix{Float64}, x)
142164
nx = size(sys.A, 1)
143165
ny = size(sys.C, 1)
@@ -160,12 +182,32 @@ function causal_simplification(sys, u2duinds::Vector{Pair{Int, Int}}; descriptor
160182
Ce = [fm(sys.C) zeros(ny, ndu)]
161183
De = fm(D)
162184
dsys = dss(Ae, E, Be, Ce, De)
163-
return ss(RobustAndOptimalControl.DescriptorSystems.dss2ss(dsys; simple_infeigs)[1])
185+
if balance
186+
dsys, T1, T2 = RobustAndOptimalControl.DescriptorSystems.gprescale(dsys)
187+
else
188+
bq = RobustAndOptimalControl.DescriptorSystems.gbalqual(dsys)
189+
bq > 10000 && @warn("The numerical balancing of the system is poor (gbalqual = $bq), consider using `balance=true` to balance the system before conversion to StateSpace to improve accuracy of the result.")
190+
end
191+
192+
return ss(RobustAndOptimalControl.DescriptorSystems.dss2ss(dsys; simple_infeigs, fast=false)[1])
164193
else
165-
ss(sys.A, B, sys.C, D) + ss(sys.A, B̄, sys.C, D̄)*tf('s')
194+
if big
195+
b1 = Base.big(1.0)
196+
ss(b1*sys.A, b1*B, b1*sys.C, b1*D) + diff_out(ss(b1*sys.A, b1*B̄, b1*sys.C, b1*D̄))
197+
else
198+
b = balance ? s->balance_statespace(sminreal(s))[1] : identity
199+
b(ss(sys.A, B, sys.C, D)) + b(ss(sys.A, B̄, sys.C, D̄))*tf('s')
200+
end
166201
end
167202
end
168203

204+
function diff_out(sys)
205+
A,B,C,D = ssdata(sys)
206+
iszero(D) || error("Nonzero feedthrough matrix not supported")
207+
ss(A,B,C*A, C*B, sys.timeevol)
208+
end
209+
210+
169211
for f in [:sensitivity, :comp_sensitivity, :looptransfer]
170212
fnn = Symbol("get_named_$f")
171213
fn = Symbol("get_$f")
@@ -205,6 +247,8 @@ function named_sensitivity_function(
205247
inputs, args...;
206248
descriptor = true,
207249
simple_infeigs = true,
250+
balance = descriptor && !simple_infeigs, # balance only if descriptor is true and simple_infeigs is false
251+
big = false,
208252
kwargs...,
209253
)
210254

@@ -230,18 +274,20 @@ function named_sensitivity_function(
230274
# This indicates that input derivatives are present
231275
duinds = findall(any(!iszero, eachcol(matrices.B[:, nu+1:end]))) .+ nu
232276
u2du = (1:nu) .=> duinds # This maps inputs to their derivatives
233-
lsys = causal_simplification(matrices, u2du; descriptor, simple_infeigs)
277+
lsys = causal_simplification(matrices, u2du; descriptor, simple_infeigs, big, balance)
234278
else
235279
lsys = ss(matrices...)
236280
end
237-
x_names = simple_infeigs ? symstr.(unknowns(ssys)) : [Symbol(string(nameof(sys))*"_x$i") for i in 1:lsys.nx]
238-
named_ss(
281+
x_names = get_x_names(lsys, ssys; descriptor, simple_infeigs, balance)
282+
nsys = named_ss(
239283
lsys;
240284
x = x_names,
241285
u = unames,
242286
y = unames, #Symbol.("out_" .* string.(inputs)),
243287
name = string(Base.nameof(sys)),
244288
)
289+
RobustAndOptimalControl.set_extra!(nsys, :ssys, ssys)
290+
nsys
245291
end
246292

247293
if isdefined(ModelingToolkit, :get_disturbance_system)

test/test_ODESystem.jl

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,17 @@ w = exp10.(LinRange(-12, 2, 2000))
364364
# ControlSystemsBase.bodeplot([G, G2, minreal(G, 1e-8)], w)
365365

366366

367-
##
367+
## artificial fully dense test
368+
369+
lsys = ssrand(2,2,3,proper=true)
370+
G = ControlSystemsMTK.causal_simplification(lsys, [1=>2], descriptor=false)
371+
G2 = ControlSystemsMTK.causal_simplification(lsys, [1=>2], descriptor=true)
372+
G3 = ControlSystemsMTK.causal_simplification(lsys, [1=>2], descriptor=false, big=true)
373+
G4 = ControlSystemsMTK.causal_simplification(lsys, [1=>2], descriptor=true, simple_infeigs=false, balance=true)
374+
375+
w_test = [1e-5, 1, 100]
376+
@test freqresp(G, w_test) freqresp(G2, w_test)
377+
@test freqresp(G, w_test) freqresp(G3, w_test)
378+
@test freqresp(G, w_test) freqresp(G4, w_test)
368379

369-
# S = schur(A,B)
370-
# V = eigvecs(S)
380+
# bodeplot([G, G2, G3, G4], exp10.(LinRange(-2, 2, 200)))

0 commit comments

Comments
 (0)