Skip to content

Commit f0d7d93

Browse files
committed
add options and test to causal simplification
1 parent 78f50ad commit f0d7d93

File tree

2 files changed

+71
-14
lines changed

2 files changed

+71
-14
lines changed

src/ode_system.jl

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ end
154154

155155

156156
"""
157-
RobustAndOptimalControl.named_ss(sys::ModelingToolkit.AbstractSystem, inputs, outputs; descriptor=true, simple_infeigs=true, kwargs...)
157+
RobustAndOptimalControl.named_ss(sys::ModelingToolkit.AbstractSystem, inputs, outputs; descriptor=true, simple_infeigs=true, balance=false, kwargs...)
158158
159159
Convert an `ODESystem` 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`.
160160
@@ -171,6 +171,8 @@ function RobustAndOptimalControl.named_ss(
171171
outputs;
172172
descriptor = true,
173173
simple_infeigs = true,
174+
balance = descriptor && !simple_infeigs, # balance only if descriptor is true and simple_infeigs is false
175+
big = false,
174176
kwargs...,
175177
)
176178

@@ -210,25 +212,46 @@ function RobustAndOptimalControl.named_ss(
210212
# This indicates that input derivatives are present
211213
duinds = findall(any(!iszero, eachcol(matrices.B[:, nu+1:end]))) .+ nu
212214
u2du = (1:nu) .=> duinds # This maps inputs to their derivatives
213-
lsys = causal_simplification(matrices, u2du; descriptor, simple_infeigs)
215+
lsys = causal_simplification(matrices, u2du; descriptor, simple_infeigs, big, balance)
214216
else
215217
lsys = ss(matrices...)
216218
end
217219
# If simple_infeigs=false, the system might have been reduced and the state names might not match the original system.
218-
x_names = simple_infeigs ? symstr.(unknowns(ssys)) : [Symbol(string(nameof(sys))*"_x$i") for i in 1:lsys.nx]
219-
named_ss(
220+
x_names = get_x_names(lsys, sys; descriptor, simple_infeigs, balance)
221+
nsys = named_ss(
220222
lsys;
221223
x = x_names,
222224
u = unames,
223225
y = symstr.(outputs),
224226
name = string(Base.nameof(sys)),
225227
)
228+
RobustAndOptimalControl.set_extra!(nsys, :ssys, ssys)
229+
nsys
230+
end
231+
232+
function get_x_names(lsys, sys; descriptor, simple_infeigs, balance)
233+
generic = if descriptor
234+
!simple_infeigs || balance
235+
else
236+
true
237+
end
238+
if generic
239+
[Symbol(string(nameof(sys))*"_x$i") for i in 1:lsys.nx]
240+
else
241+
symstr.(unknowns(sys))
242+
end
226243
end
227244

228245
"""
229-
causal_simplification(sys, u2duinds::Vector{Pair{Int, Int}}; descriptor=true, simple_infeigs = true)
246+
causal_simplification(sys, u2duinds::Vector{Pair{Int, Int}}; descriptor=true, simple_infeigs=true, balance=false)
247+
248+
If `descriptor = true`, the function `DescriptorSystems.dss2ss` is used. In this case,
249+
- `balance`: indicates whether to balance the system using `DescriptorSystems.gprescale` before conversion to `StateSpace`. Balancing changes the state realization (through scaling).
250+
- `simple_infeigs`: if set to false, further simplification may be performed in some cases.
251+
252+
If `descriptor = false`, the argument `big = true` performs computations in `BigFloat` precision, useful for poorly scaled systems.
230253
"""
231-
function causal_simplification(sys, u2duinds::Vector{Pair{Int, Int}}; descriptor=true, simple_infeigs = true)
254+
function causal_simplification(sys, u2duinds::Vector{Pair{Int, Int}}; balance=false, descriptor=true, simple_infeigs = true, big = false)
232255
fm(x) = convert(Matrix{Float64}, x)
233256
nx = size(sys.A, 1)
234257
ny = size(sys.C, 1)
@@ -251,12 +274,32 @@ function causal_simplification(sys, u2duinds::Vector{Pair{Int, Int}}; descriptor
251274
Ce = [fm(sys.C) zeros(ny, ndu)]
252275
De = fm(D)
253276
dsys = dss(Ae, E, Be, Ce, De)
254-
return ss(RobustAndOptimalControl.DescriptorSystems.dss2ss(dsys; simple_infeigs)[1])
277+
if balance
278+
dsys, T1, T2 = RobustAndOptimalControl.DescriptorSystems.gprescale(dsys)
279+
else
280+
bq = RobustAndOptimalControl.DescriptorSystems.gbalqual(dsys)
281+
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.")
282+
end
283+
284+
return ss(RobustAndOptimalControl.DescriptorSystems.dss2ss(dsys; simple_infeigs, fast=false)[1])
255285
else
256-
ss(sys.A, B, sys.C, D) + ss(sys.A, B̄, sys.C, D̄)*tf('s')
286+
if big
287+
b1 = Base.big(1.0)
288+
ss(b1*sys.A, b1*B, b1*sys.C, b1*D) + diff_out(ss(b1*sys.A, b1*B̄, b1*sys.C, b1*D̄))
289+
else
290+
b = balance ? s->balance_statespace(sminreal(s))[1] : identity
291+
b(ss(sys.A, B, sys.C, D)) + b(ss(sys.A, B̄, sys.C, D̄))*tf('s')
292+
end
257293
end
258294
end
259295

296+
function diff_out(sys)
297+
A,B,C,D = ssdata(sys)
298+
iszero(D) || error("Nonzero feedthrough matrix not supported")
299+
ss(A,B,C*A, C*B, sys.timeevol)
300+
end
301+
302+
260303
for f in [:sensitivity, :comp_sensitivity, :looptransfer]
261304
fnn = Symbol("get_named_$f")
262305
fn = Symbol("get_$f")
@@ -296,6 +339,8 @@ function named_sensitivity_function(
296339
inputs, args...;
297340
descriptor = true,
298341
simple_infeigs = true,
342+
balance = descriptor && !simple_infeigs, # balance only if descriptor is true and simple_infeigs is false
343+
big = false,
299344
kwargs...,
300345
)
301346

@@ -321,18 +366,20 @@ function named_sensitivity_function(
321366
# This indicates that input derivatives are present
322367
duinds = findall(any(!iszero, eachcol(matrices.B[:, nu+1:end]))) .+ nu
323368
u2du = (1:nu) .=> duinds # This maps inputs to their derivatives
324-
lsys = causal_simplification(matrices, u2du; descriptor, simple_infeigs)
369+
lsys = causal_simplification(matrices, u2du; descriptor, simple_infeigs, big, balance)
325370
else
326371
lsys = ss(matrices...)
327372
end
328-
x_names = simple_infeigs ? symstr.(unknowns(ssys)) : [Symbol(string(nameof(sys))*"_x$i") for i in 1:lsys.nx]
329-
named_ss(
373+
x_names = get_x_names(lsys, sys; descriptor, simple_infeigs, balance)
374+
nsys = named_ss(
330375
lsys;
331376
x = x_names,
332377
u = unames,
333378
y = unames, #Symbol.("out_" .* string.(inputs)),
334379
name = string(Base.nameof(sys)),
335380
)
381+
RobustAndOptimalControl.set_extra!(nsys, :ssys, ssys)
382+
nsys
336383
end
337384

338385
if isdefined(ModelingToolkit, :get_disturbance_system)

test/test_ODESystem.jl

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

384384

385-
##
385+
## artificial fully dense test
386+
387+
lsys = ssrand(2,2,3,proper=true)
388+
G = ControlSystemsMTK.causal_simplification(lsys, [1=>2], descriptor=false)
389+
G2 = ControlSystemsMTK.causal_simplification(lsys, [1=>2], descriptor=true)
390+
G3 = ControlSystemsMTK.causal_simplification(lsys, [1=>2], descriptor=false, big=true)
391+
G4 = ControlSystemsMTK.causal_simplification(lsys, [1=>2], descriptor=true, simple_infeigs=false, balance=true)
392+
393+
w_test = [1e-5, 1, 100]
394+
@test freqresp(G, w_test) freqresp(G2, w_test)
395+
@test freqresp(G, w_test) freqresp(G3, w_test)
396+
@test freqresp(G, w_test) freqresp(G4, w_test)
386397

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

0 commit comments

Comments
 (0)