Skip to content

Commit 8e3f8c1

Browse files
authored
Merge pull request #179 from JuliaComputing/better_lqg
update LQG tutorial
2 parents c3f8fd0 + d145f78 commit 8e3f8c1

File tree

3 files changed

+20
-13
lines changed

3 files changed

+20
-13
lines changed

docs/src/examples/pendulum.md

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -423,27 +423,33 @@ using ControlSystemsMTK
423423
lsys = named_ss(multibody(cp), inputs, outputs; op) # identical to linearize, but packages the resulting matrices in a named statespace object for convenience
424424
```
425425

426-
### LQR Control design
426+
### LQR and LQG Control design
427427
With a linear statespace object in hand, we can proceed to design an LQR or LQG controller. We will design both an LQR and an LQG controller in order to demonstrate two possible workflows.
428428

429-
The LQR contorller is designed using the function `ControlSystemsBase.lqr`, and it takes the two cost matrices `Q1` and `Q2` penalizing state deviation and control action respectively. The LQG controller is designed using [`RobustAndOptimalControl.LQGProblem`](https://juliacontrol.github.io/RobustAndOptimalControl.jl/dev/#LQG-design), and this function additionally takes the covariance matrices `r1, R2` for a Kalman filter. Before we call `LQGProblem` we partition the linearized system into an [`ExtendedStateSpace`](https://juliacontrol.github.io/RobustAndOptimalControl.jl/dev/api/#RobustAndOptimalControl.ExtendedStateSpace) object using the [`partition`](@https://juliacontrol.github.io/RobustAndOptimalControl.jl/dev/api/#RobustAndOptimalControl.partition-Tuple{AbstractStateSpace}) function, this indicates what inputs of the system are available for control and what are considered disturbances, and what outputs of the system are available for measurement. In this case, we assume that we have access to the cart position and the pendulum angle, and we control the cart position. The remaining two outputs are still important for the performance, but we cannot measure them and will rely on the Kalman filter to estimate them. When we call [`observer_controller`](https://juliacontrol.github.io/ControlSystems.jl/dev/lib/analysis/#ControlSystemsBase.observer_controller-Tuple{Any,%20AbstractMatrix,%20AbstractMatrix}) we get a linear system that represents the combined state estimator and state feedback controller. This linear system is then converted to an `ODESystem` by the function `LQGSystem`.
429+
The LQR controller is designed using the function `ControlSystemsBase.lqr`, and it takes the two cost matrices `Q1` and `Q2` penalizing state deviation and control action respectively. The LQG controller is designed using [`RobustAndOptimalControl.LQGProblem`](https://juliacontrol.github.io/RobustAndOptimalControl.jl/dev/#LQG-design), and this function additionally takes the covariance matrices `r1, R2` for a Kalman filter. Before we call `LQGProblem` we partition the linearized system into an [`ExtendedStateSpace`](https://juliacontrol.github.io/RobustAndOptimalControl.jl/dev/api/#RobustAndOptimalControl.ExtendedStateSpace) object, this indicates which inputs of the system are available for control and which are considered disturbances, and which outputs of the system are available for measurement. In this case, we assume that we have access to the cart position and the pendulum angle, and we control the cart position. The remaining two outputs are still important for the performance, but we cannot measure them and will rely on the Kalman filter to estimate them. When we call [`extended_controller`](https://juliacontrol.github.io/RobustAndOptimalControl.jl/dev/api/#RobustAndOptimalControl.extended_controller) we get a linear system that represents the combined state estimator and state feedback controller. This linear system is then converted to an `ODESystem` by the function `LQGSystem`.
430430

431431
Since the function `lqr` operates on the state vector, and we have access to the specified output vector, we make use of the system ``C`` matrix to reformulate the problem in terms of the outputs. This relies on the ``C`` matrix being full rank, which is the case here since our outputs include a complete state realization of the system. This is of no concern when using the `LQGProblem` structure since we penalize outputs rather than the state in this case.
432432

433433
To make the simulation interesting, we make a change in the reference position of the cart after a few seconds.
434434
```@example pendulum
435435
using ControlSystemsBase, RobustAndOptimalControl
436436
C = lsys.C
437-
Q1 = Diagonal([10, 10, 10, 1])
437+
Q1 = Diagonal([10, 10, 10, 1]) # LQR cost matrices
438438
Q2 = Diagonal([0.1])
439439
440-
R1 = Diagonal([1])
440+
R1 = lsys.B*Diagonal([1])*lsys.B' # Kalman covariance matrices
441441
R2 = Diagonal([0.01, 0.01])
442+
Pn = lsys[[:x, :phi], :] # Named system with only cart position and pendulum angle measurable
442443
443-
lqg = LQGProblem(partition(lsys, u = [:u], y = [:x, :phi]), Q1, Q2, R1, R2)
444+
lqg = LQGProblem(ss(Pn), Q1, Q2, R1, R2)
444445
Lmat = lqr(lsys, C'Q1*C, Q2)/C # Alternatively, compute LQR feedback gain. The multiplication by the C matrix is to handle the difference between state and output
445446
446-
LQGSystem(args...; kwargs...) = ODESystem(observer_controller(lqg); kwargs...)
447+
# Compute a static gain compensation
448+
z = [:x] # The output for which we want to have unit static gain
449+
Ce, cl = extended_controller(lqg, z = RobustAndOptimalControl.names2indices(z, Pn.y))
450+
dc_gain_compensation = inv((Pn[:x, :].C*dcgain(cl)')[]) # Multiplier that makes the static gain from references to cart position unity
451+
452+
LQGSystem(args...; kwargs...) = ODESystem(Ce; kwargs...)
447453
448454
@mtkmodel CartWithFeedback begin
449455
@components begin
@@ -458,10 +464,12 @@ LQGSystem(args...; kwargs...) = ODESystem(observer_controller(lqg); kwargs...)
458464
namespaced_outputs = ModelingToolkit.renamespace.(:cartpole, outputs) # Give outputs correct namespace, they are variables in the cartpole system
459465
end
460466
@equations begin
461-
controller.input.u[1] ~ reference.output.u - namespaced_outputs[1] # reference cart position - cartpole.x
462-
controller.input.u[2] ~ 0 - namespaced_outputs[2] # cartpole.phi
463-
# controller.input.u[3] ~ 0 - namespaced_outputs[3] # cartpole.v # uncomment if using LQR controller instead
464-
# controller.input.u[4] ~ 0 - namespaced_outputs[4] # cartpole.w
467+
controller.input.u[1] ~ 0
468+
controller.input.u[2] ~ reference.output.u * dc_gain_compensation
469+
controller.input.u[3] ~ 0
470+
controller.input.u[4] ~ 0
471+
controller.input.u[5] ~ namespaced_outputs[1]
472+
controller.input.u[6] ~ namespaced_outputs[2]
465473
466474
connect(controller.output, control_saturation.input)
467475
connect(control_saturation.output, cartpole.motor.f)
@@ -470,7 +478,7 @@ end
470478
@named model = CartWithFeedback()
471479
model = complete(model)
472480
ssys = structural_simplify(multibody(model))
473-
prob = ODEProblem(ssys, [model.cartpole.prismatic.s => 0.1, model.cartpole.revolute.phi => 0.35], (0, 10))
481+
prob = ODEProblem(ssys, [model.cartpole.prismatic.s => 0.1, model.cartpole.revolute.phi => 0.35], (0, 12))
474482
sol = solve(prob, Tsit5())
475483
cp = model.cartpole
476484
plot(sol, idxs=[cp.prismatic.s, cp.revolute.phi, cp.motor.f.u], layout=3)

docs/src/examples/spherical_pendulum.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ using JuliaSimCompiler
1313
using OrdinaryDiffEq
1414
1515
t = Multibody.t
16-
D = Differential(t)
1716
world = Multibody.world
1817
1918
systems = @named begin

src/components.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,7 @@ See also [`BodyCylinder`](@ref) and [`BodyBox`](@ref) for body components with p
541541
systems = @named begin
542542
translation = FixedTranslation(r = r, render=false)
543543
translation_cm = FixedTranslation(r = r_cm, render=false)
544-
body = Body(; m, r_cm, r_0, I_11, I_22, I_33, I_21, I_31, I_32, kwargs...)
544+
body = Body(; m, r_cm, r_0, I_11, I_22, I_33, I_21, I_31, I_32, render=false, kwargs...)
545545
frame_a = Frame()
546546
frame_b = Frame()
547547
frame_cm = Frame()

0 commit comments

Comments
 (0)