Skip to content

Commit 4b2acb3

Browse files
committed
Add unit tests, change okid output for siso
1 parent 4338629 commit 4b2acb3

File tree

2 files changed

+148
-0
lines changed

2 files changed

+148
-0
lines changed

control/modelsimp.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,10 @@ def observer_kalman_identification(*args, m=None, transpose=False, dt=True, trun
905905
H[:,:,1:] = Y[:,:,:]
906906
H = H/dt # scaling
907907

908+
# for siso return a 1D array instead of a 3D array
909+
if q == 1 and p == 1:
910+
H = np.squeeze(H)
911+
908912
# Return the first m Markov parameters
909913
return H if not transpose else np.transpose(H)
910914

control/tests/modelsimp_test.py

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,20 @@
66
import numpy as np
77
import pytest
88

9+
<<<<<<< HEAD
910
from control import StateSpace, TimeResponseData, c2d, forced_response, \
1011
impulse_response, step_response, rss, tf
1112
from control.exception import ControlArgument, ControlDimension
1213
from control.modelsimp import balred, eigensys_realization, hsvd, markov, \
1314
modred
1415
from control.tests.conftest import slycotonly
16+
=======
17+
18+
from control import StateSpace, impulse_response, forced_response, tf, rss, c2d
19+
from control.exception import ControlMIMONotImplemented
20+
from control.tests.conftest import slycotonly
21+
from control.modelsimp import balred, hsvd, markov, okid, modred
22+
>>>>>>> 67d6ef0 (Add unit tests, change okid output for siso)
1523

1624

1725
class TestModelsimp:
@@ -320,6 +328,142 @@ def testERASignature(self):
320328
rtol=1e-6, atol=1e-8)
321329

322330

331+
def testOKIDSignature(self):
332+
333+
# Example 6.1, Applied System Identification
334+
m1, k1, c1 = 1., 1., 0.01
335+
A = np.array([
336+
[0., 1.],
337+
[-k1/m1, -c1/m1],
338+
])
339+
B = np.array([[0.],[1./m1]])
340+
C = np.array([[-k1/m1, -c1/m1]])
341+
D = np.array([[1.]])
342+
sys = StateSpace(A, B, C, D)
343+
dt = 0.1
344+
sysd = sys.sample(dt, method='zoh')
345+
346+
T = np.arange(0,200,dt)
347+
U = np.random.randn(sysd.B.shape[-1], len(T))
348+
response = forced_response(sysd, U=U)
349+
Y = response.outputs
350+
351+
m = 5
352+
ir_true = impulse_response(sysd, T=T)
353+
Htrue = ir_true.outputs[:m+1]*dt
354+
H = okid(Y, U, m, dt=True)
355+
356+
np.testing.assert_allclose(Htrue, H, atol=1e-1)
357+
358+
# Test mimo example
359+
# Mechanical Vibrations: Theory and Application, SI Edition, 1st ed.
360+
# Figure 6.5 / Example 6.7
361+
m1, k1, c1 = 1., 4., 1.
362+
m2, k2, c2 = 2., 2., 1.
363+
k3, c3 = 6., 2.
364+
365+
A = np.array([
366+
[0., 0., 1., 0.],
367+
[0., 0., 0., 1.],
368+
[-(k1+k2)/m1, (k2)/m1, -(c1+c2)/m1, c2/m1],
369+
[(k2)/m2, -(k2+k3)/m2, c2/m2, -(c2+c3)/m2]
370+
])
371+
B = np.array([[0.,0.],[0.,0.],[1/m1,0.],[0.,1/m2]])
372+
C = np.array([[1.0, 0.0, 0.0, 0.0],[0.0, 1.0, 0.0, 0.0]])
373+
D = np.zeros((2,2))
374+
375+
sys = StateSpace(A, B, C, D)
376+
dt = 0.25
377+
sysd = sys.sample(dt, method='zoh')
378+
379+
T = np.arange(0,100,dt)
380+
U = np.random.randn(sysd.B.shape[-1], len(T))
381+
response = forced_response(sysd, U=U)
382+
Y = response.outputs
383+
384+
m = 100
385+
_, Htrue = impulse_response(sysd, T=dt*(m))
386+
387+
388+
# test array_like
389+
atol = 1e-1
390+
H = okid(Y, U, m, dt=dt)
391+
np.testing.assert_allclose(H, Htrue, atol=atol)
392+
393+
# test array_like, truncate
394+
H = okid(Y, U, m, dt=dt, truncate=True)
395+
np.testing.assert_allclose(H, Htrue, atol=atol)
396+
397+
# test array_like, transpose
398+
HT = okid(Y.T, U.T, m, dt=dt, transpose=True)
399+
np.testing.assert_allclose(HT, np.transpose(Htrue), atol=atol)
400+
401+
# test response data
402+
H = okid(response, m, dt=dt)
403+
np.testing.assert_allclose(H, Htrue, atol=atol)
404+
405+
# test response data
406+
H = okid(response, m, dt=dt, truncate=True)
407+
np.testing.assert_allclose(H, Htrue, atol=atol)
408+
409+
# test response data, transpose
410+
response.transpose = True
411+
HT = okid(response, m, dt=dt)
412+
np.testing.assert_allclose(HT, np.transpose(Htrue), atol=atol)
413+
414+
# Make sure markov() returns the right answer
415+
@pytest.mark.parametrize("k, m, n",
416+
[(2, 2, 2),
417+
(2, 5, 5),
418+
(5, 2, 2),
419+
(5, 5, 5),
420+
(5, 10, 10)])
421+
def testOkidResults(self, k, m, n):
422+
#
423+
# Test over a range of parameters
424+
#
425+
# k = order of the system
426+
# m = number of Markov parameters
427+
# n = size of the data vector
428+
#
429+
# Values *should* match exactly for n = m, otherewise you get a
430+
# close match but errors due to the assumption that C A^k B =
431+
# 0 for k > m-2 (see modelsimp.py).
432+
#
433+
434+
# Generate stable continuous time system
435+
Hc = rss(k, 1, 1)
436+
437+
# Choose sampling time based on fastest time constant / 10
438+
w, _ = np.linalg.eig(Hc.A)
439+
Ts = np.min(-np.real(w)) / 10.
440+
441+
# Convert to a discrete time system via sampling
442+
Hd = c2d(Hc, Ts, 'zoh')
443+
444+
# Compute the Markov parameters from state space
445+
Mtrue = np.hstack([Hd.D] + [
446+
Hd.C @ np.linalg.matrix_power(Hd.A, i) @ Hd.B
447+
for i in range(m-1)])
448+
449+
Mtrue = np.squeeze(Mtrue)
450+
451+
# Generate input/output data
452+
T = np.array(range(n)) * Ts
453+
U = np.cos(T) + np.sin(T/np.pi)
454+
_, Y = forced_response(Hd, T, U, squeeze=True)
455+
Mcomp = okid(Y, U, m-1)
456+
457+
458+
# TODO: Check tol
459+
# Compare to results from markov()
460+
# experimentally determined probability to get non matching results
461+
# with rtot=1e-6 and atol=1e-8 due to numerical errors
462+
# for k=5, m=n=10: 0.015 %
463+
np.testing.assert_allclose(Mtrue, Mcomp, atol=1e-1)
464+
465+
466+
323467
def testModredMatchDC(self):
324468
#balanced realization computed in matlab for the transfer function:
325469
# num = [1 11 45 32], den = [1 15 60 200 60]

0 commit comments

Comments
 (0)