Skip to content

Commit a9e9d11

Browse files
committed
Add function returns linear statespace
1 parent e5adb17 commit a9e9d11

File tree

3 files changed

+166
-5
lines changed

3 files changed

+166
-5
lines changed

src/ModelingToolkit.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ export Term, Sym
181181
export SymScope, LocalScope, ParentScope, GlobalScope
182182
export independent_variables, independent_variable, states, parameters, equations, controls,
183183
observed, structure, full_equations
184-
export structural_simplify, expand_connections, linearize
184+
export structural_simplify, expand_connections, linearize, linear_statespace
185185
export DiscreteSystem, DiscreteProblem
186186

187187
export calculate_jacobian, generate_jacobian, generate_function

src/inputoutput.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ function inputs_to_parameters!(state::TransformationState, check_bound = true)
270270
push!(new_fullvars, v)
271271
end
272272
end
273-
ninputs == 0 && return state, 1:0
273+
ninputs == 0 && return (state, 1:0)
274274

275275
nvars = ndsts(graph) - ninputs
276276
new_graph = BipartiteGraph(nsrcs(graph), nvars, Val(false))

src/systems/abstractsystem.jl

Lines changed: 164 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -967,8 +967,32 @@ function structural_simplify(sys::AbstractSystem; simplify = false, kwargs...)
967967
return sys
968968
end
969969

970-
# TODO: The order of the states and equations should match so that the Jacobians are ẋ = Ax
971-
function linearize(sys::AbstractSystem, inputs, outputs; simplify = false, kwargs...)
970+
"""
971+
lin_fun, simplified_sys = linearization_function(sys::AbstractSystem, inputs, outputs; simplify = false, kwargs...)
972+
973+
Return a function that linearizes system `sys`.
974+
975+
`lin_fun` is a function `(u,p,t) -> (; f_x, f_z, g_x, g_z, f_u, g_u, h_x, h_z, h_u)`, i.e., it returns a NamedTuple with the Jacobians of `f,g,h` for the nonlinear `sys` on the form
976+
```math
977+
ẋ = f(x, z, u)
978+
0 = g(x, z, u)
979+
y = h(x, z, u)
980+
```
981+
where `x` are differential states, `z` algebraic states, `u` inputs and `y` outputs. To obtain a linear statespace representation, see [`linearize`](@ref).
982+
983+
The `simplified_sys` has undergone [`structural_simplify`](@ref) and had any occurring input or output variables replaced with the variables provided in arguments `inputs` and `outputs`. The states of this system also indicates the order of the states that holds for the linearized matrices.
984+
985+
# Arguments:
986+
- `sys`: An [`ODESystem`](@ref). This function will automatically apply simplification passes on `sys` and return the resulting `simplified_sys`.
987+
- `inputs`: A vector of variables that indicate the inputs of the linearized input-output model.
988+
- `outputs`: A vector of variables that indicate the outputs of the linearized input-output model.
989+
- `simplify`: Apply simplification in tearing.
990+
- `kwargs`: Are passed on to `find_solvables!`
991+
992+
See also [`linearize`](@ref) which provides a higher-level interface.
993+
"""
994+
function linearization_function(sys::AbstractSystem, inputs, outputs; simplify = false,
995+
kwargs...)
972996
sys = expand_connections(sys)
973997
state = TearingState(sys)
974998
markio!(state, inputs, outputs)
@@ -1022,7 +1046,7 @@ function linearize(sys::AbstractSystem, inputs, outputs; simplify = false, kwarg
10221046
h_u = h_u)
10231047
end
10241048
end
1025-
return sys, lin_fun
1049+
return lin_fun, sys
10261050
end
10271051

10281052
function markio!(state, inputs, outputs)
@@ -1047,6 +1071,143 @@ function markio!(state, inputs, outputs)
10471071
state
10481072
end
10491073

1074+
"""
1075+
(; A, B, C, D), simplified_sys = linearize(sys, inputs, outputs; op = Dict(), allow_input_derivatives = false, kwargs...)
1076+
(; A, B, C, D) = linearize(simplified_sys, lin_fun; op = Dict(), allow_input_derivatives = false)
1077+
1078+
Return a NamedTuple with the matrices of a linear statespace representation
1079+
on the form
1080+
```math
1081+
\\begin{aligned}
1082+
ẋ &= Ax + Bu\\\\
1083+
y &= Cx + Du
1084+
\\end{aligned}
1085+
```
1086+
1087+
The first signature automatically calls [`linearization_function`](@ref) internally,
1088+
while the second signature expects the outputs of [`linearization_function`](@ref) as input.
1089+
1090+
`op` denotes the operating point around which to linearize. If none is provided,
1091+
the default values of `sys` are used.
1092+
1093+
If `allow_input_derivatives = false`, an error will be thrown if input derivatives (``u̇``) appear as inputs in the linearized equations. If input derivatives are allowed, the returned `B` matrix will be of double width, corresponding to the input `[u; u̇]`.
1094+
1095+
See also [`linearization_function`](@ref) which provides a lower-level interface, and [`ModelingToolkit.reorder_states`](@ref).
1096+
1097+
See extended help for an example.
1098+
1099+
# Extended help
1100+
This example builds the following feedback interconnection and linearizes it from the input of `F` to the output of `P`.
1101+
```
1102+
1103+
r ┌─────┐ ┌─────┐ ┌─────┐
1104+
───►│ ├──────►│ │ u │ │
1105+
│ F │ │ C ├────►│ P │ y
1106+
└─────┘ ┌►│ │ │ ├─┬─►
1107+
│ └─────┘ └─────┘ │
1108+
│ │
1109+
└─────────────────────┘
1110+
```
1111+
```
1112+
using ModelingToolkit
1113+
@variables t
1114+
function plant(; name)
1115+
@variables x(t) = 1
1116+
@variables u(t)=0 y(t)=0
1117+
D = Differential(t)
1118+
eqs = [D(x) ~ -x + u
1119+
y ~ x]
1120+
ODESystem(eqs, t; name = name)
1121+
end
1122+
1123+
function ref_filt(; name)
1124+
@variables x(t)=0 y(t)=0
1125+
@variables u(t)=0 [input=true]
1126+
D = Differential(t)
1127+
eqs = [D(x) ~ -2 * x + u
1128+
y ~ x]
1129+
ODESystem(eqs, t, name = name)
1130+
end
1131+
1132+
function controller(kp; name)
1133+
@variables y(t)=0 r(t)=0 u(t)=0
1134+
@parameters kp = kp
1135+
eqs = [
1136+
u ~ kp * (r - y),
1137+
]
1138+
ODESystem(eqs, t; name = name)
1139+
end
1140+
1141+
@named f = ref_filt()
1142+
@named c = controller(1)
1143+
@named p = plant()
1144+
1145+
connections = [f.y ~ c.r # filtered reference to controller reference
1146+
c.u ~ p.u # controller output to plant input
1147+
p.y ~ c.y]
1148+
1149+
@named cl = ODESystem(connections, t, systems = [f, c, p])
1150+
1151+
lsys, ssys = linearize(cl, [f.u], [p.x])
1152+
desired_order = [f.x, p.x]
1153+
lsys = ModelingToolkit.reorder_states(lsys, states(ssys), desired_order)
1154+
1155+
@assert lsys.A == [-2 0; 1 -2]
1156+
@assert lsys.B == [1; 0;;]
1157+
@assert lsys.C == [0 1]
1158+
@assert lsys.D[] == 0
1159+
```
1160+
"""
1161+
function linearize(sys, lin_fun; op = Dict(), allow_input_derivatives = false)
1162+
x0 = merge(defaults(sys), op)
1163+
prob = ODEProblem(sys, x0, (0.0, 1.0))
1164+
linres = lin_fun(prob.u0, prob.p, 0.0)
1165+
f_x, f_z, g_x, g_z, f_u, g_u, h_x, h_z, h_u = linres
1166+
1167+
nx, nu = size(f_u)
1168+
nz = size(f_z, 2)
1169+
ny = size(h_x, 1)
1170+
1171+
if isempty(g_z)
1172+
A = f_x
1173+
B = f_u
1174+
C = h_x
1175+
@assert iszero(g_x)
1176+
@assert iszero(g_z)
1177+
@assert iszero(g_u)
1178+
else
1179+
gz = lu(g_z; check = false)
1180+
issuccess(gz) ||
1181+
error("g_z not invertible, this indicates that the DAE is of index > 1.")
1182+
gzgx = -(gz \ g_x)
1183+
A = [f_x f_z
1184+
gzgx*f_x gzgx*f_z]
1185+
B = [f_u
1186+
zeros(nz, nu)]
1187+
C = [
1188+
h_x h_z
1189+
]
1190+
Bs = -(gz \ (f_x * f_u + g_u))
1191+
if !iszero(Bs)
1192+
if !allow_input_derivatives
1193+
der_inds = findall(vec(any(!=(0), Bs, dims = 1)))
1194+
error("Input derivatives appeared in expressions (-g_z\\(f_x*f_u + g_u) != 0), the following inputs appeared differentiated: $(inputs(sys)[der_inds]). Call `linear_staespace` with keyword argument `allow_input_derivatives = true` to allow this and have the returned `B` matrix be of double width ($(2nu)), where the last $nu inputs are the derivatives of the first $nu inputs.")
1195+
end
1196+
B = [B Bs]
1197+
end
1198+
end
1199+
1200+
D = h_u
1201+
1202+
(; A, B, C, D)
1203+
end
1204+
1205+
function linearize(sys, inputs, outputs; op = Dict(), allow_input_derivatives = false,
1206+
kwargs...)
1207+
lin_fun, ssys = linearization_function(sys, inputs, outputs; kwargs...)
1208+
linearize(ssys, lin_fun; op, allow_input_derivatives), ssys
1209+
end
1210+
10501211
@latexrecipe function f(sys::AbstractSystem)
10511212
return latexify(equations(sys))
10521213
end

0 commit comments

Comments
 (0)