7
7
8
8
import control as ct
9
9
from control import lqe , dlqe , rss , drss , tf , ss , ControlArgument , slycot_check
10
+ from math import log , pi
10
11
11
12
# Utility function to check LQE answer
12
13
def check_LQE (L , P , poles , G , QN , RN ):
@@ -48,7 +49,7 @@ def test_lqe_call_format(cdlqe):
48
49
49
50
# Standard calling format
50
51
Lref , Pref , Eref = cdlqe (sys .A , sys .B , sys .C , Q , R )
51
-
52
+
52
53
# Call with system instead of matricees
53
54
L , P , E = cdlqe (sys , Q , R )
54
55
np .testing .assert_almost_equal (Lref , L )
@@ -58,15 +59,15 @@ def test_lqe_call_format(cdlqe):
58
59
# Make sure we get an error if we specify N
59
60
with pytest .raises (ct .ControlNotImplemented ):
60
61
L , P , E = cdlqe (sys , Q , R , N )
61
-
62
+
62
63
# Inconsistent system dimensions
63
64
with pytest .raises (ct .ControlDimension , match = "Incompatible" ):
64
65
L , P , E = cdlqe (sys .A , sys .C , sys .B , Q , R )
65
-
66
+
66
67
# Incorrect covariance matrix dimensions
67
68
with pytest .raises (ct .ControlDimension , match = "Incompatible" ):
68
69
L , P , E = cdlqe (sys .A , sys .B , sys .C , R , Q )
69
-
70
+
70
71
# Too few input arguments
71
72
with pytest .raises (ct .ControlArgument , match = "not enough input" ):
72
73
L , P , E = cdlqe (sys .A , sys .C )
@@ -99,26 +100,26 @@ def test_lqe_discrete():
99
100
np .testing .assert_almost_equal (K_csys , K_expl )
100
101
np .testing .assert_almost_equal (S_csys , S_expl )
101
102
np .testing .assert_almost_equal (E_csys , E_expl )
102
-
103
+
103
104
# Calling lqe() with a discrete time system should call dlqe()
104
105
K_lqe , S_lqe , E_lqe = ct .lqe (dsys , Q , R )
105
106
K_dlqe , S_dlqe , E_dlqe = ct .dlqe (dsys , Q , R )
106
107
np .testing .assert_almost_equal (K_lqe , K_dlqe )
107
108
np .testing .assert_almost_equal (S_lqe , S_dlqe )
108
109
np .testing .assert_almost_equal (E_lqe , E_dlqe )
109
-
110
+
110
111
# Calling lqe() with no timebase should call lqe()
111
112
asys = ct .ss (csys .A , csys .B , csys .C , csys .D , dt = None )
112
113
K_asys , S_asys , E_asys = ct .lqe (asys , Q , R )
113
114
K_expl , S_expl , E_expl = ct .lqe (csys .A , csys .B , csys .C , Q , R )
114
115
np .testing .assert_almost_equal (K_asys , K_expl )
115
116
np .testing .assert_almost_equal (S_asys , S_expl )
116
117
np .testing .assert_almost_equal (E_asys , E_expl )
117
-
118
+
118
119
# Calling dlqe() with a continuous time system should raise an error
119
120
with pytest .raises (ControlArgument , match = "called with a continuous" ):
120
121
K , S , E = ct .dlqe (csys , Q , R )
121
-
122
+
122
123
def test_estimator_iosys ():
123
124
sys = ct .drss (4 , 2 , 2 , strictly_proper = True )
124
125
@@ -129,7 +130,7 @@ def test_estimator_iosys():
129
130
QN = np .eye (sys .ninputs )
130
131
RN = np .eye (sys .noutputs )
131
132
estim = ct .create_estimator_iosystem (sys , QN , RN , P0 )
132
-
133
+
133
134
ctrl , clsys = ct .create_statefbk_iosystem (sys , K , estimator = estim )
134
135
135
136
# Extract the elements of the estimator
@@ -162,20 +163,75 @@ def test_estimator_iosys():
162
163
np .testing .assert_almost_equal (cls .D , D_clchk )
163
164
164
165
166
+ @pytest .mark .parametrize ("sys_args" , [
167
+ ([[- 1 ]], [[1 ]], [[1 ]], 0 ), # scalar system
168
+ ([[- 1 , 0.1 ], [0 , - 2 ]], [[0 ], [1 ]], [[1 , 0 ]], 0 ), # SISO, 2 state
169
+ ([[- 1 , 0.1 ], [0 , - 2 ]], [[1 , 0 ], [0 , 1 ]], [[1 , 0 ]], 0 ), # 2i, 1o, 2s
170
+ ([[- 1 , 0.1 , 0.1 ], [0 , - 2 , 0 ], [0.1 , 0 , - 3 ]], # 2i, 2o, 3s
171
+ [[1 , 0 ], [0 , 0.1 ], [0 , 1 ]],
172
+ [[1 , 0 , 0.1 ], [0 , 1 , 0.1 ]], 0 ),
173
+ ])
174
+ def test_estimator_iosys_ctime (sys_args ):
175
+ # Define the system we want to test
176
+ sys = ct .ss (* sys_args )
177
+ T = 10 * log (1e-2 ) / np .max (sys .poles ().real )
178
+ assert T > 0
179
+
180
+ # Create nonlinear version of the system to match integration methods
181
+ nl_sys = ct .NonlinearIOSystem (
182
+ lambda t , x , u , params : sys .A @ x + sys .B @ u ,
183
+ lambda t , x , u , params : sys .C @ x + sys .D @ u ,
184
+ inputs = sys .ninputs , outputs = sys .noutputs , states = sys .nstates )
185
+
186
+ # Define an initial condition, inputs (small, to avoid integration errors)
187
+ timepts = np .linspace (0 , T , 500 )
188
+ U = 2e-2 * np .array ([np .sin (timepts + i * pi / 3 ) for i in range (sys .ninputs )])
189
+ X0 = np .ones (sys .nstates )
190
+
191
+ # Set up the parameters for the filter
192
+ P0 = np .eye (sys .nstates )
193
+ QN = np .eye (sys .ninputs )
194
+ RN = np .eye (sys .noutputs )
195
+
196
+ # Construct the estimator
197
+ estim = ct .create_estimator_iosystem (sys , QN , RN )
198
+
199
+ # Compute the system response and the optimal covariance
200
+ sys_resp = ct .input_output_response (nl_sys , timepts , U , X0 )
201
+ _ , Pf , _ = ct .lqe (sys , QN , RN )
202
+
203
+ # Make sure that we converge to the optimal estimate
204
+ estim_resp = ct .input_output_response (
205
+ estim , timepts , [sys_resp .outputs , U ], [0 * X0 , P0 ])
206
+ np .testing .assert_allclose (
207
+ estim_resp .states [0 :sys .nstates , - 1 ], sys_resp .states [:, - 1 ],
208
+ atol = 1e-6 , rtol = 1e-3 )
209
+ np .testing .assert_allclose (
210
+ estim_resp .states [sys .nstates :, - 1 ], Pf .reshape (- 1 ),
211
+ atol = 1e-6 , rtol = 1e-3 )
212
+
213
+ # Make sure that optimal estimate is an eq pt
214
+ ss_resp = ct .input_output_response (
215
+ estim , timepts , [sys_resp .outputs , U ], [X0 , Pf ])
216
+ np .testing .assert_allclose (
217
+ ss_resp .states [sys .nstates :],
218
+ np .outer (Pf .reshape (- 1 ), np .ones_like (timepts )),
219
+ atol = 1e-4 , rtol = 1e-2 )
220
+ np .testing .assert_allclose (
221
+ ss_resp .states [0 :sys .nstates ], sys_resp .states ,
222
+ atol = 1e-4 , rtol = 1e-2 )
223
+
224
+
165
225
def test_estimator_errors ():
166
226
sys = ct .drss (4 , 2 , 2 , strictly_proper = True )
167
227
P0 = np .eye (sys .nstates )
168
228
QN = np .eye (sys .ninputs )
169
229
RN = np .eye (sys .noutputs )
170
230
171
- with pytest .raises (ct .ControlArgument , match = "Input system must be I/O " ):
231
+ with pytest .raises (ct .ControlArgument , match = ".* system must be a linear " ):
172
232
sys_tf = ct .tf ([1 ], [1 , 1 ], dt = True )
173
233
estim = ct .create_estimator_iosystem (sys_tf , QN , RN )
174
-
175
- with pytest .raises (NotImplementedError , match = "continuous time not" ):
176
- sys_ct = ct .rss (4 , 2 , 2 , strictly_proper = True )
177
- estim = ct .create_estimator_iosystem (sys_ct , QN , RN )
178
-
234
+
179
235
with pytest .raises (ValueError , match = "output must be full state" ):
180
236
C = np .eye (2 , 4 )
181
237
estim = ct .create_estimator_iosystem (sys , QN , RN , C = C )
@@ -246,7 +302,7 @@ def test_correlation():
246
302
# Try passing a second argument
247
303
tau , Rneg = ct .correlation (T , V , - V )
248
304
np .testing .assert_equal (Rtau , - Rneg )
249
-
305
+
250
306
# Test error conditions
251
307
with pytest .raises (ValueError , match = "Time vector T must be 1D" ):
252
308
tau , Rtau = ct .correlation (V , V )
0 commit comments