Skip to content

Commit 4c897d6

Browse files
authored
Merge pull request #158 from JuliaComputing/lqg
add LQG design example
2 parents d8c7777 + 20847c2 commit 4c897d6

File tree

1 file changed

+29
-13
lines changed

1 file changed

+29
-13
lines changed

docs/src/examples/pendulum.md

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ the vector is now coinciding with `get_trans(sol, model.lower_arm.frame_b, 12)`.
315315
## Control-design example: Pendulum on cart
316316
We will now demonstrate a complete workflow including
317317
- Modeling
318-
- Linearizaiton
318+
- Linearization
319319
- Control design
320320

321321
We will continue the pendulum theme and design an inverted pendulum on cart. The cart is modeled as [`BodyShape`](@ref) with specified mass, and `shape = "box"` to render it as a box in animations. The cart is moving along the $x$-axis by means of a [`Prismatic`](@ref) joint, and the pendulum is attached to the cart by means of a [`Revolute`](@ref) joint. The pendulum is a [`BodyCylinder`](@ref) with a diameter of `0.015`, the mass and inertia properties are automatically computed using the geometrical dimensions and the density (which defaults to that of steel). A force is applied to the cart by means of a `TranslationalModelica.Force` component.
@@ -363,6 +363,7 @@ gray = [0.5, 0.5, 0.5, 1]
363363
end
364364
@mtkmodel CartWithInput begin
365365
@components begin
366+
world = W()
366367
cartpole = Cartpole()
367368
input = Blocks.Cosine(frequency=1, amplitude=1)
368369
end
@@ -413,32 +414,46 @@ lsys = named_ss(IRSystem(cp), inputs, outputs; op) # identical to linearize, but
413414
```
414415

415416
### LQR Control design
416-
With a linear statespace object in hand, we can proceed to design an LQR controller. 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.
417+
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.
418+
419+
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`.
420+
421+
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.
417422

418423
To make the simulation interesting, we make a change in the reference position of the cart after a few seconds.
419424
```@example pendulum
420-
using ControlSystemsBase
425+
using ControlSystemsBase, RobustAndOptimalControl
421426
C = lsys.C
422-
Q = Diagonal([1, 1, 1, 1])
423-
R = Diagonal([0.1])
424-
Lmat = lqr(lsys, C'Q*C, R)/C # Compute LQR feedback gain. The multiplication by the C matrix is to handle the difference between state and output
427+
Q1 = Diagonal([10, 10, 10, 1])
428+
Q2 = Diagonal([0.1])
429+
430+
R1 = Diagonal([1])
431+
R2 = Diagonal([0.01, 0.01])
432+
433+
lqg = LQGProblem(partition(lsys, u = [:u], y = [:x, :phi]), Q1, Q2, R1, R2)
434+
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
435+
436+
LQGSystem(args...; kwargs...) = ODESystem(observer_controller(lqg); kwargs...)
425437
426438
@mtkmodel CartWithFeedback begin
427439
@components begin
440+
world = W()
428441
cartpole = Cartpole()
429-
L = Blocks.MatrixGain(K = Lmat)
430442
reference = Blocks.Step(start_time = 5, height=0.5)
431443
control_saturation = Blocks.Limiter(y_max = 10) # To limit the control signal magnitude
444+
# controller = Blocks.MatrixGain(K = Lmat) # uncomment to use LQR controller instead
445+
controller = LQGSystem()
432446
end
433447
begin
434448
namespaced_outputs = ModelingToolkit.renamespace.(:cartpole, outputs) # Give outputs correct namespace, they are variables in the cartpole system
435449
end
436450
@equations begin
437-
L.input.u[1] ~ reference.output.u - namespaced_outputs[1] # reference cart position - cartpole.x
438-
L.input.u[2] ~ 0 - namespaced_outputs[2] # cartpole.phi
439-
L.input.u[3] ~ 0 - namespaced_outputs[3] # cartpole.v
440-
L.input.u[4] ~ 0 - namespaced_outputs[4] # cartpole.w
441-
connect(L.output, control_saturation.input)
451+
controller.input.u[1] ~ reference.output.u - namespaced_outputs[1] # reference cart position - cartpole.x
452+
controller.input.u[2] ~ 0 - namespaced_outputs[2] # cartpole.phi
453+
# controller.input.u[3] ~ 0 - namespaced_outputs[3] # cartpole.v # uncomment if using LQR controller instead
454+
# controller.input.u[4] ~ 0 - namespaced_outputs[4] # cartpole.w
455+
456+
connect(controller.output, control_saturation.input)
442457
connect(control_saturation.output, cartpole.motor.f)
443458
end
444459
end
@@ -476,8 +491,9 @@ normalize_angle(x::Number) = mod(x+3.1415, 2pi)-3.1415
476491
477492
@mtkmodel CartWithSwingup begin
478493
@components begin
494+
world = W()
479495
cartpole = Cartpole()
480-
L = Blocks.MatrixGain(K = Lmat)
496+
L = Blocks.MatrixGain(K = Lmat) # Here we use the LQR controller instead
481497
control_saturation = Blocks.Limiter(y_max = 12) # To limit the control signal magnitude
482498
end
483499
@variables begin

0 commit comments

Comments
 (0)