Project2 Ipynb
Project2 Ipynb
ipynb
rmcsqrd / [Link]
Last active 4 years ago • Report abuse
Code Revisions 5
[Link]
[Link] 1/26
11/21/24, 4:19 PM [Link]
For my system I selected the "Multirotor Aircraft" system. This report is organized
into several sections found in the table of contents below. Code and figures are
interspersed in-line within the report.
Given the heavy emphasis on programming in this project, I decided to forgo the
conventional report format and instead opted to use a Jupyter notebook. I am
submitting this as a PDF to conform with submittal guidelines although I encourage
graders to view this at the following link where it is rendered in its native HTML
(some odd formatting occurred during the conversion from HTML to PDF). The
content is the same.
Table of Contents
Part 1: Plant Observability
Part 2: Luenberger Observer Placement
Part 3: Closed Loop Observer/Controller Simulation - Reference Input
Part 4: Closed Loop Observer/Controller Simulation - Non-Zero IC
Part 5: LQR Controller Design
In [21]:
# import some stuff
import os
import numpy as np
import control
import time
import projectutils.plot_utils2 as pltutls
from [Link] import expm
from [Link] import loadmat
[Link]()
# define matrices
A = [Link](
[[0, 1, 0, 0, 0, 0],
[0, -0.0104, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0],
[0, 0, 0, -0.0104, 0, 0],
[0, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 0, -0.0208]]
)
B = [Link](
[[0, 0, 0, 0],
[-0.04167, 0, 0.04167, 0],
[0, 0, 0, 0],
[0, -0.04167, 0, 0.04167],
[0, 0, 0, 0],
[0 4 0 4 0 4 0 4]]
[Link] 2/26
11/21/24, 4:19 PM
)
[0.4, 0.4, 0.4, 0.4]]
C = [Link](
[[1, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 1, 0]]
D = [Link](
)
[[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]]
Q =
Q ∈ R
⎢⎥
⎡
⎣
CA
[Link]
CA
C
n−1 ⎦
nl×n
⋮
x ∈ R
n
C ∈ R
l
,y ∈ R ,A ∈ R
⟹
l×n
⎤
A completely observable state space is one such that any internal state x(t
be distinguished from 0 by the examination of y(t) on a finite interval [t
n×n
Note that the relationship between the plant output y(t) is directly related to
internal plant state x(t) and is agnostic of any inputs u(t) into the plant;
Although e
Theorem:
A(t−t 0 )
x(t) = e
y(t) = Cx(t)
y(t) = Ce
A(t−t 0 )
A(t−t 0 )
x(t 0 )
x(t 0 )
0,
observability is a measure of the internal state x(t) and the output y(t) - any input
u(t) are realized in x(t). Thus, when evaluating observability it is convenient to
take u(t) = 0
–
; this does not have an implication on the observed state by the
0
t]
)
[
n 1
[Link]
]
can
3/26
11/21/24, 4:19 PM
In [22]:
If rank(Q)
mapping of x(t
[ 0.000e+00 0.000e+00
[ 0.000e+00 1.000e+00
[ 0.000e+00 0.000e+00
[ 0.000e+00 0.000e+00
[ 0.000e+00 -1.040e-02
[ 0.000e+00 0.000e+00
[ 0.000e+00 0.000e+00
[ 0.000e+00 1.082e-04
[ 0.000e+00 0.000e+00
[ 0.000e+00 0.000e+00
[ 0.000e+00 -1.125e-06
[ 0.000e+00 0.000e+00
[ 0.000e+00 0.000e+00
[ 0.000e+00 1.170e-08
[ 0.000e+00 0.000e+00
[ 0.000e+00 0.000e+00
Rank(Q) = 6
= n
n−1
y(t) = C[ ∑ α k (t − t 0 )A ]x(t 0 )
k=0
y(t) = [α 0 (t − t 0 )I l×l
0
⟹
…
RN (Q) = 0
) → y(t)
k
0.000e+00 0.000e+00
0.000e+00 0.000e+00
0.000e+00 1.000e+00
0.000e+00 0.000e+00
0.000e+00 0.000e+00
0.000e+00 -1.040e-02
0.000e+00 0.000e+00
0.000e+00 0.000e+00
0.000e+00 1.082e-04
0.000e+00 0.000e+00
0.000e+00 0.000e+00
0.000e+00 -1.125e-06
0.000e+00 0.000e+00
0.000e+00 0.000e+00
0.000e+00 1.170e-08
0.000e+00 0.000e+00
α n−1 (t − t 0 )I l×l ]
= n ⟹
⎢⎥
⎡
⎣
CA
In this expanded form, recognize the observability matrix Q defined previously. The
rank of this matrix has implications on the overall mapping from x(t
C
n−1 ⎦
definition for observability is fulfilled and the state space is fully observable.
If rank(Q)
x(t 0 )
≠ n ⟹ RN (Q) ≠ 0
0.000e+00 0.000e+00]
1.000e+00 0.000e+00]
0.000e+00 0.000e+00]
0.000e+00 0.000e+00]
0.000e+00 1.000e+00]
0.000e+00 0.000e+00]
0.000e+00 0.000e+00]
0)
0.000e+00 -2.080e-02]
0.000e+00 0.000e+00]
0.000e+00 0.000e+00]
0.000e+00 4.326e-04]
0.000e+00 0.000e+00]
0.000e+00 0.000e+00]
0.000e+00 -8.999e-06]
0.000e+00 0.000e+00]
0.000e+00 0.000e+00]
0.000e+00 1.872e-07]]
RN (Q) = 0
state space is fully observable by the previous logic. Further, because RN (Q) is
trivial, this means that any x(t
l
0)
[Link]
∈ R
n
is mappable from a corresponding
x(t 0 )
→ y(t)
In the following code, the observability matrix Q is constructed and its rank is
evaluated using the rank() function.
with [Link](precision=3):
print("Observability Matrix Q:")
print(Qobs, "\n")
print("Rank(Q) = ", [Link].matrix_rank(Qobs), " = n (implies full r
Observability Matrix Q:
[[ 1.000e+00 0.000e+00
[ 0.000e+00 0.000e+00
0.000e+00 0.000e+00
1.000e+00 0.000e+00
. This means that
cannot be distinguished from 0 and thus the state space is not fully
observable.
–
0.000e+00 0.000e+00]
:
4/26
11/21/24, 4:19 PM [Link]
y(t) ∈ R
l
(1-1 mapping) further implying that the un-observable subspace is trivial.
t 1 −t 0
T
2 T A (τ −t 0 ) T A(τ −t 0 )
||y|| = x (t 0 )[ ∫ e C Ce dτ ]x(t 0 )
T
0
trapezoidal rule.
In [23]:
# define a range of t1-t0, then find the observability gramian for each of
delt = 1
tmax = 400
trange = [Link](delt,tmax, delt)
[Link]([Link]()+"/projectutils/data/Gorange", Gorange)
Recall that in Project 1 it was shown that A is fully diagonalizable such that
A = T ΛT
−1
which implies that the matrix of eigenvectors T is invertible. This can
be used as a change of basis between the state space representation and the
modal space of A. This is convenient when perturbing the different plant modes
[Link] 5/26
11/21/24, 4:19 PM [Link]
oda space o . s s co e e t e pe tu b g t e d e e t p a t odes
and is thus used here. Recall that the change of basis takes the following form:
x(t) = T α(0)
Where x(t) is in the original coordinates of the state space and α(t) is the
equivalent representation using the eigenvectors as a basis set. This means that the
output energy can be represented in the following form:
t 1 −t 0
T T
2 A (τ −t 0 ) T A(τ −t 0 )
||y|| = (T α(t 0 )) [∫ e C Ce dτ ](T α(t 0 ))
T
0
Each of the modal spaces is given a unit perturbation and the output energy is
determined based on a range of [0, t 1
− t0 ] intervals. The unit perturbation takes
the form of the identity matrix which means that the perturbation condition for
each modal space is just the eigenvector of A. The following snippet of code takes
the range of G Observability gramians that were previously calculated for different
0
time intervals and solves for the L2-norm of the output using the above
equation/ICs.
In [24]:
eigvals, eigvects = [Link](A)
# plot stuff
pltutls.Energy_Plot(trange, delt, output_energy)
[Link] 6/26
11/21/24, 4:19 PM [Link]
The above plots show the output energy ||y|| in response to a unit perturbation in
2
each of the modal spaces for different time intervals. To evaluate them, first recall
the description of the eigenspaces from project 1:
n
R = N1 ⊕ N2 ⊕ N3
N 1 = λ 1,3,5 = 0
N 2 = λ 2,4 = −0.0104
N 3 = λ 6 = −0.0208
The plots associated with N (plots 1,3,5) are linearly increasing. N and N have
1 2 3
similar response characteristics to different time intervals but with slightly different
parameters (the output associated with N converges faster).
3
In the output energy plot, two main trends appear for the range of G observability o
linearly increase as the time interval linearly increases. This means that for a
unit magnitude input, a longer time interval [t 0, t1 ] corresponds to a larger
output energy. More simply put, the longer the time interval, the larger the
corresponding output y(t) is for a given internal state regardless of the relative
"magnitude" of the internal state. Plant modal spaces 1,3,5 show similar linear
trends, all of which show energies in the range ||y|| 2
.
∈ [0, 400]
the decaying modes, modes 2,4 finish decaying at t 1 − t 0 ≈ 150 whereas mode 6
decays more quickly on the order of t 1 − t 0 ≈ 75 . Plant modal spaces 2,4,5 show
decaying increases with plant modes 2,4 "topping out" at ||y|| 2
= 40 whereas plant
mode 6 hits an upper limit of ||y|| 2
= 20 which is half as much as modes 2,4.
plant modal IC's for a common time interval - the above plot implies that plant
modal spaces 1,3,5 are equally observable relative to one another. It also shows
that plant modes 1 and 3 are approximately twice as observable as mode 6
although the variable rates of decay may yield a different ratio for smaller time
intervals.
If x(t) T
G o x(t) yields a small corresponding y(t), this implies that it is "harder"
to observe x(t) because it requires a large internal state shift to observe a
small shift in output. This corresponds to small eigenvalues in the G matrix. o
Conversely, if x(t) T
G o x(t) yields a large corresponding y(t), this implies that
x(t) is more "easily" observable because a relatively small internal state shift
yields a large shift in the realized y(t). This corresponds to large eigenvalues in
To evaluate the eigenvalues of G it is convenient to find them and plot them. The
0
In [25]:
# compute evolution of eigenvalues
eigval_observability = [Link](([Link][1], [Link][0]))
for ti in range(0, [Link][0]):
eigval_observability[:, ti] = [Link](Gorange[ti])
# plot eigenvalues
pltutls.ObsEigValsEnergy_Plot(trange, delt, eigval_observability)
[Link] 8/26
11/21/24, 4:19 PM [Link]
As can be seen in the above plot, all eigenvalues of G increase as the time interval
0
[t 0 , t 1 ] grows larger. This implies that the linear transformation G will illicit a 0
relative larger scaling along the eigenvectors of G for larger time intervals. In the
0
context of observability, because all eigenvalues are increasing with respect to time,
the system will become more observable in all modes as time increases.
The rate of growth/second order rate of growth/decay is less clear because of the
exponential/exponential decay trends seen in the plot above. Because the
eigenvectors of G are not-necessarily parallel to the eigenvectors of A it is difficult
0
to infer directly about the observability of specific plant modes based on this
eigendecomposition; that said the positive correlation between time interval and
eigenvalue magnitude generally implies an increase in observability of all plant
modes as the time interval increases.
T T T T
(A − LC) = A − C L
Earlier, Q was shown to be full rank. Note that because the rank of a matrix does
not change when the matrix is transposed (rank(A) = rank(A
T
), this implies
)
T
λ i (A − LC) = λ i ((A − LC) )
This placement is done using Matlab's place() function in Part 2. The place()
takes an input vector of desired pole locations and returns a gain matrix K such
that the eigenvalues of A − BK reflect the inputted pole locations. When placing
the observer poles, place() can also be used, however modifications must be
made because it is set up for A − BK not A − LC . Inputs can be modified such
that:
T T T T
(A − LC) = A − C L
T T T
L = place(A ,C , p)
^ = Ax
x ^ + Bu + L(y − y)
^
Recall that y
^ = Cx
^
x
^ = Ax
^ + Bu + Ly − LC x
^
= (A − LC)x
^ + Bu + Ly
Recall in Project 1 that the following response times were desired for the closed
loop eigenvalues and thus the corresponding eigenvalues were placed using
Matlab's place function.
τ 6 = 0.05s ⟹ λ 6 = −20
τ 1,3,5 = 1s ⟹ λ 1,3,5 = −1
In all of the following observer cases, observer poles are placed using Matlab's
[Link] 10/26
11/21/24, 4:19 PM [Link]
place function. Python's pole placement was not working as well in Project 1 so
Matlab's placement was used and just copied over. Discussion of the fact that L is T
returned from the place function is provided at the end of Part 1. In all cases the
poles were placed using the following syntax:
A = [...];
C = [...];
p = [-1, -10, -1, -10, -1, -20];
Lslow = place(A',C', p*0.2);
Lequal = place(A',C', p);
Lfast = place(A',C', p*5);
The Luenberger observer gain matrices L were then saved into a matlab file and
read in the code below. Note that the returned L matrices are actually L as
T
discussed in part 1.
In [26]:
Lgains = loadmat([Link]()+"/projectutils/data/L_gains.mat")
Lslow = [Link](Lgains['Lslow'])
Lequal = [Link](Lgains['Lequal'])
Lfast = [Link](Lgains['Lfast'])
Slow Observer
Per the problem statement, a slow observer is defined as 0.2 times as fast as the
placed closed loop eigenvalues. The corresponding observer eigenvalues are:
τ 6 = 0.05s × 0.2 ⟹ λ 6 = −4
In [27]:
print("L slow = ")
with [Link](precision=3):
print(Lslow)
L slow =
[[ 4.071e+00 7.340e-01 -4.717e-01 -8.943e-02 1.116e-15 2.323e-16]
[-4.717e-01 -8.943e-02 2.308e+00 3.996e-01 -2.892e-16 -4.780e-17]
[ 1.311e-15 7.902e-16 -6.942e-17 8.952e-17 2.179e+00 3.547e-01]]
Equal Observer
Per the problem statement, an equal observer is defined as 1 times as fast as the
placed closed loop eigenvalues. The corresponding observer eigenvalues are:
τ 6 = 0.05s ⟹ λ 6 = −20
τ 1,3,5 = 1s ⟹ λ 1,3,5 = −1
In [28]:
print("L equal = ")
with [Link](precision=3):
print(Lequal)
L equal =
[Link] 11/26
11/21/24, 4:19 PM [Link]
L equal =
[[ 2.096e+01 1.976e+01 -4.990e-01 -4.938e-01 6.035e-16 4.736e-16]
[-4.990e-01 -4.938e-01 1.101e+01 9.910e+00 3.190e-17 6.212e-16]
[ 7.673e-16 2.931e-15 8.598e-16 3.000e-15 1.098e+01 9.772e+00]]
Fast Observer
Per the problem statement, a fast observer is defined as 5 times as fast as the
placed closed loop eigenvalues. The corresponding observer eigenvalues are:
τ 6 = 0.05s × 5 ⟹ λ 6 = −100
τ 1,3,5 = 1s × 5 ⟹ λ 1,3,5 = −5
In [29]:
print("L fast = ")
with [Link](precision=3):
print(Lfast)
L fast =
[[ 1.050e+02 4.989e+02 -5.000e-01 -2.495e+00 -5.674e-16 -3.440e-15]
[-5.000e-01 -2.495e+00 5.499e+01 2.495e+02 1.230e-16 5.740e-15]
[-7.071e-16 -2.162e-14 3.495e-15 -7.285e-14 5.498e+01 2.489e+02]]
ẋ A − BK BF x BF
ẇ = [ ] = [ ][ ] + [ ]r
ė 0 A − LC e 0
x
y = [C 0] [ ]
e
In [30]:
# define function for closed loop reference input
def ClosedLoopRefInput(A, B, C, K, F, L_list, trange):
# pack arrays
Ap[0:6, 0:6] = [Link](K)
Ap[0:6, 6:] = [Link](K)
Ap[6:, 6:] = [Link](C)
Bp[0:6, :] = [Link](F)
Cp[:, 0:6] = C
# recall the gain matrix K computed in project 1 - also bring in the assoc
K = [Link]([
[-119.9904, -131.8647, 0.0174, 0.0174, 6.2500, 6.8620],
[0.0174, 0.0174, -239.9808, -251.8550, 6.2500, 6.8620],
[119.9904, 131.8647, -0.0174, -0.0174, 6.2500, 6.8620],
[-0.0174, -0.0174, 239.9808, 251.8550, 6.2500, 6.8620]])
F = [Link]([[-119.99040,0.01740,6.25000],
[0.01740,-239.98080,6.25000],
[119.99040,-0.01740,6.25000],
[-0.01740,239.98080,6.25000]])
tmax = 10
tstep = 0.01
tmin = 0
trange = [Link](tmin, tmax, tstep)
# plot stuff
pltutls.Unit_step_plot(trange_plot, prestep_resarray, step_ref)
[Link] 13/26
11/21/24, 4:19 PM [Link]
Observer Comparisons
Referencing the Closed Loop Control Observer/Controller Response plot, note that
all three observers had identical responses to the unit step reference inputs with
zero IC in both x and x
^. The explanation for this identical response is contingent on
Expanding on this, recognize that because A is identical for the system dynamics
and the model dynamics, the error (e = x − x
^ ) can be represented in the following
form:
ẋ = Ax + Bu
y = Cx
˙
^ = Ax
x ^ + Bu + L(y − y)
^
y
^ = Cx
^
˙
^ = Ax + Bu − (Ax
^ + Bu + L(y − y))
^
ė = ẋ − x
= Ae − LCe
= (A − LC)e
(A−LC)(t−t 0 )
e(t) = e e(0)
Plugging this into the expression for e(t), it can be clearly seen that for an initial
condition e(0) ,
= 0 e(t) will evolve constantly over time and remain at a value of 0.
"Flipping this around", recognize that because e(t) = 0, ∀t , as the system evolves
from this initial condition, x(t) = 0, x(t)
^ = 0, ∀t . Further, recalling that y = Cx
and y^ = Cx
^ , this implies that y(t) = y(t),
^ ∀t .
Further, this means that the model input from the Luenberger gain L (in the form
L(y − y)
^ ) is also zero for the entire time interval because L(0 − 0) = 0 . This
means that regardless of the placed pole positions that generated L, the system
response/outputs are the same which is reflected in the zero IC simulation/plots.
This is also reflected in the separation principle which can be used to show that the
error states are not reachable via some reference input. Accordingly, the system
response is identical to "controller only" closed loop control that is shown in Part 5
of Project 1.
Note that for these simulations, each initial condition is simulated independently.
Because the model initial condition x
^ = 0 , this means that the error is just the
system state initial condition x. This is because e = x
^ − x where
x = 0 ⟹ ^
e = x .
In [31]:
# define the initial condition
ICx = [Link]([[1, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0],
[0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 1],
])
tmax = 20
tstep = 0.01
tmin = 0
trange = [Link](tmin, tmax, tstep)
# d fi f ti f l d l i iti l diti
[Link] 15/26
11/21/24, 4:19 PM [Link]
# define function for closed loop initial condition response
def ClosedLoopICResponse(A, B, C, K, F, L, ICmat, trange):
# pack arrays
Ap[0:6, 0:6] = [Link](K)
Ap[0:6, 6:] = [Link](K)
Ap[6:, 6:] = [Link](C)
Bp[0:6, :] = [Link](F)
Cp[:, 0:6] = C
return resarray
In [32]:
# compute response and plot - slow
resarray = ClosedLoopICResponse(A, B, C, K, F, Lslow, ICx, trange)
pltutls.Non_Zero_IC_Plot(trange, resarray, "L slow")
[Link] 16/26
11/21/24, 4:19 PM [Link]
[Link] 17/26
11/21/24, 4:19 PM [Link]
The above plots shows the system response to the slow, equal, and fast observers
developed in Part 2. Although there were 6 total initial conditions and 3 different
observer "speeds" (based on placed observer poles) two main response modalities
emerged due to the inherent similarities in the internal states and system dynamics
in general. These response similarities are seen in all three of the system output
plots for all three observer types - the different observer types have the behavior
occur in different time scales. The two response modalities can be characterized as:
then begins to decay back to equilibrium. Again each IC/observer gain shows
similar responses.
Response to Position IC: In this response modality, the system starts at a non-
zero position and must return to equilibrium. The system changes position and
reaches an inflection point with equilibrium before "overshooting" equilibrium.
This maximum overcorrection occurs similarly for all IC's/observer gain
matrices. It then decays to equilibrium.
Response to Velocity IC: In this response modality, the system starts at the
equilibrium position but departs due to the non-zero velocity (which is not part
of the output, thus not directly shown). It reaches a maximum departure from
equilibrium
The above plots graphically display the maximum velocity achieved during the
response to velocity IC and the minimum position achieved during the response to
the position IC. Because the different observers show similar response modalities
with differences in the timing of the inflection points, these inflection points
represent a convenient basis for comparison. The inflection points are labeled on
the plots above and summarized below - the pairs indicate the x,y coordinates of
the max/min position for the different IC's.
[Link] 18/26
11/21/24, 4:19 PM [Link]
L SLOW
IC V max P os min
L EQUAL
IC V max P os min
y (1.03,0.478) (2.04,-0.172)
L FAST
IC V max P os min
z (0.47,0.225) (0.88,-0.146)
Interpreting the data above, a few comparisons can be drawn between the different
observer speeds: the average minimum position appears to be trending upward
with a maximum average position occurring for the equal observer.
Interestingly, the equal observer has the largest average minimum position. All of
the average minimum positions are less than 20% of the initial condition (< |0.2|) .
The other area of interest is potential space constraints and the implications of the
minimum position response. For example, if the quadrotor was trying to fly close to
a wall without touching it, the "overshooting" effect seen in the minimum position
response could cause a crash. The equal observer had the largest "overshoot"
(smallest minimum position) and thus is not a good choice. Without more specific
design guidelines it is difficult to say how much "overshoot" is too much but the
selected design should minimize it without compromising speed.
Thus, the fast observer appears to be a good choice because it maximizes speed of
response without incurring too much positional deviation.
∞
T T
J = ∫ (x (t)Qx(t) + u (t)Ru(t))dt
0
T
x (t)Qx(t) = Quadratic weighting of state
T
u (t)Ru(t) = Quadratic weighting of input
In this form, the LQR cost function J has two parameters to set:
Q : The state cost weight matrix dictating how "expensive" the state deviation
from equilibrium is.
R : The control cost weight matrix dictating how "expensive" control input into
the system is.
Note that J is an integral so we are measuring the area under the curve of the
state/input costs. If Q is large and R is small, the system will respond quickly to any
perturbation from equilibrium because control is much "cheaper" than the
deviation from equilibrium. Conversely, if Q is small and R is large, the system will
respond slowly because control input is more expensive than a long term deviation
from equilibrium.
Begin with the design process for R. In the quadcopter problem formulation, the
system inputs u(t) are:
T1
⎡ ⎤
T2
u(t) =
T3
⎣ ⎦
T4
Where T indicates the input from each of the 4 rotors. Assuming that this is a
i
conventional quadrotor, these motors are identical and it is assumed that actuating
different rotors or different combinations of rotors does not incur different costs.
This assumption is probably valid for the actuation of different rotors
independently. For the actuation of different combinations of rotors this probably is
not totally valid because actuating all four motors at once as opposed to a single
rotor will incur different current draws on the power supply - for simplicity and
given the context of the problem these implications are left for exploration in future
work.
Given the assumption that different rotors are similarly expensive, it becomes clear
that each of the expenses incurred by the actuators should be similar in the
resultant R matrix. To do this, a scalar factor is multiplied by an appropriately sized
identity matrix:
[Link] 21/26
11/21/24, 4:19 PM
In [33]:
Q and R can be compared directly:
(t)Ru(T )
Q =
⎢⎥
⎣
0
R =
1
Frobenius norm of Q:
Frobenius norm of R:
2.0
⎡
⎣
[Link]
R = R scale I p×p
Frobenius norm of Q and R as a basis for comparison to see what the resultant
impacts of different Q, R selections yield. Assuming that R
⎡
1 0 0
# setup Q, R
Q = [Link]([Link][0])
Rfact = 1
R = [Link]([Link][1])
# compare norms
0
0
0
0
0
In the above snippet of code, the Frobenius norm of Q and R was determined to
⎤
serve as a back of the envelope comparison of the relative contribution to the cost
function J . The Frobenius norm of Q
This means that without any scaling, Q
≈ R
scale
, the norms of
= 1
Now that this baseline comparison between Q, R has been established, Q, R are
selected. The problem statement gives a design requirement that the LQR selected
K has eigenvalues at least as short as the placed eigenvalues in Project 1. Recall
that the placed eigenvalues in project 1 and their associated time constants:
[Link]
= 2 .
22/26
11/21/24, 4:19 PM [Link]
τ 6 = 0.05s ⟹ λ 6 = −20
τ 1,3,5 = 1s ⟹ λ 1,3,5 = −1
This means that the minimum design requirement for the LQR placed eigenvalues is
λ LQR ≤ −1 . Several R scale values were tested iteratively - the final selection is
shown in the code snippet below and discussed.
In [34]:
# define Q, R matrices
# Q = [Link]((25, 5, 25, 5, 20, 0.052)) # seems slightly more realistic
Q = [Link]((50, 1, 50, 1, 50, 1)) # very fast performance
Q = [Link]((2,2,2,2,1,0.05)) # gives good tracking except in Z
# Q = [Link]((25, 10, 25, 10, 25, 0.5))
Rfact = 0.0001
R = Rfact*[Link]([Link][1])
Qeig = [Link](Q)
Reig = [Link](R)
with [Link](precision=6):
print("Q eigenvalues: ")
print(Qeig, "\n")
print("R eigenvalues: ")
print(Reig, "\n")
Q eigenvalues:
[2. 2. 2. 2. 1. 0.05]
R eigenvalues:
[0.0001 0.0001 0.0001 0.0001]
In the above output the eigenvalues of Q and R are shown to show conformance
with project/system specifications:
All eigenvalues of both matrices are greater than zero which implies that both
[Link] 23/26
11/21/24, 4:19 PM [Link]
All eigenvalues of both matrices are greater than zero which implies that both
Q and R are positive definite matrices. This satisfies the condition that R is
positive definite and Q is positive semi-definite.
All eigenvalues of A − BK LQR
≤ −1 which satisfies the condition that the
LQR placed eigenvalues have time constants at least as fast as their slowest
Project 1 placed counterpart.
In the end, the selected R needed to be ≈ 4-5 orders of magnitude smaller than Q
to have real eigenvalues that are appropriately sized. Q was chosen using an
iterative process that basically reduced to shaking the magic LQR eight-ball and
until λ i (A − BK LQR ) ≈ λ i (A − BK placed ) . The eigenvalues that were eventually
selected are shown in the output of the code snippet above. Note that both the
placed and LQR eigenvalues are non-imaginary and negative. Additionally note that
the LQR eigenvalues attempted to preserve the ratio found in the placed
eigenvalues.
The following code snippet takes the selected observer from Part 4 (fast observer)
and simulates the controller/observer response to unit step reference inputs. It then
brings in the simulation data from the similar controller only simulation from
Project 1.
In [35]:
# compute updated F
F_lqr = [Link](
[Link](
[Link](
-A+[Link](K_lqr)
)
).dot(B)
)
# don't change this stuff otherwise you'll need to resave project 1 stuff
tmax = 10
tstep = 0.01
tmin = 0
trange = [Link](tmin, tmax, tstep)
[Link] 24/26
11/21/24, 4:19 PM [Link]
# add in computed results from LQR
prestep_resarray[0, :, prestep_size:] = resarray
xhist_prestep[:, :, prestep_size:] = xhist_lqr
xhist_lqr = xhist_prestep
step_ref = [Link]([Link][2]+prestep_size)
step_ref[prestep_size:] = 1
trange_plot = [Link](tmin-prestep_size*tstep, tmax, tstep)
# plot stuff
pltutls.Unit_step_plot_LQR(trange_plot, prestep_resarray, step_ref)
The above plot shows the output responses of the controller only (Project 1 placed)
and controller/observer (LQR placed) response to a unit step input with zero initial
conditions. Visually, the response to the x, y reference inputs tracks well while there
is some deviation in the z response. This is quantified in the following section
where the energy input is compared between the two different controllers.
Control Law
^
u(t) = −K x(t) + F r(t)
T
2 T
||u|| = ∫ u (t)u(t)dt
0
To compute this integral, the control law recalled above is used. As stated in the
problem specification, T = 3 closed loop time constants. Because there are several
time constants at play, the smallest is used ⟹ . In practice this is
T = 3 × 1s
sufficient for comparison because generally control energy approaches zero once
3s has passed.
outputted by the step_response() function that was used for simulation. Also
note that the simulation started with zero initial conditions such that
x(0)
^ = x(0) = 0 . Recall from the discussion in Part 3 regarding the observer
comparisions that if the simulation starts with zero error, it will stay at zero error.
The step_response() function returns x(t); this is acceptable because
^ − x = 0
e = x ⟹ ^
x = x which means that the returned x(t) can be used
directly in the calculation of u(t):
u(t) = −K x(t)
^ + F r(t)
[Link] 26/26