Skip to content

Commit 67d6ef0

Browse files
committed
Add unit tests, change okid output for siso
1 parent 23c0a44 commit 67d6ef0

File tree

2 files changed

+142
-2
lines changed

2 files changed

+142
-2
lines changed

control/modelsimp.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,5 +764,9 @@ def okid(*args, m=None, transpose=False, dt=True, truncate=False):
764764
H[:,:,1:] = Y[:,:,:]
765765
H = H/dt # scaling
766766

767+
# for siso return a 1D array instead of a 3D array
768+
if q == 1 and p == 1:
769+
H = np.squeeze(H)
770+
767771
# Return the first m Markov parameters
768772
return H if not transpose else np.transpose(H)

control/tests/modelsimp_test.py

Lines changed: 138 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
import pytest
88

99

10-
from control import StateSpace, forced_response, tf, rss, c2d
10+
from control import StateSpace, impulse_response, forced_response, tf, rss, c2d
1111
from control.exception import ControlMIMONotImplemented
1212
from control.tests.conftest import slycotonly
13-
from control.modelsimp import balred, hsvd, markov, modred
13+
from control.modelsimp import balred, hsvd, markov, okid, modred
1414

1515

1616
class TestModelsimp:
@@ -111,6 +111,142 @@ def testMarkovResults(self, k, m, n):
111111
# for k=5, m=n=10: 0.015 %
112112
np.testing.assert_allclose(Mtrue, Mcomp, rtol=1e-6, atol=1e-8)
113113

114+
def testOKIDSignature(self):
115+
116+
# Example 6.1, Applied System Identification
117+
m1, k1, c1 = 1., 1., 0.01
118+
A = np.array([
119+
[0., 1.],
120+
[-k1/m1, -c1/m1],
121+
])
122+
B = np.array([[0.],[1./m1]])
123+
C = np.array([[-k1/m1, -c1/m1]])
124+
D = np.array([[1.]])
125+
sys = StateSpace(A, B, C, D)
126+
dt = 0.1
127+
sysd = sys.sample(dt, method='zoh')
128+
129+
T = np.arange(0,200,dt)
130+
U = np.random.randn(sysd.B.shape[-1], len(T))
131+
response = forced_response(sysd, U=U)
132+
Y = response.outputs
133+
134+
m = 5
135+
ir_true = impulse_response(sysd, T=T)
136+
Htrue = ir_true.outputs[:m+1]*dt
137+
H = okid(Y, U, m, dt=True)
138+
139+
np.testing.assert_allclose(Htrue, H, atol=1e-1)
140+
141+
# Test mimo example
142+
# Mechanical Vibrations: Theory and Application, SI Edition, 1st ed.
143+
# Figure 6.5 / Example 6.7
144+
m1, k1, c1 = 1., 4., 1.
145+
m2, k2, c2 = 2., 2., 1.
146+
k3, c3 = 6., 2.
147+
148+
A = np.array([
149+
[0., 0., 1., 0.],
150+
[0., 0., 0., 1.],
151+
[-(k1+k2)/m1, (k2)/m1, -(c1+c2)/m1, c2/m1],
152+
[(k2)/m2, -(k2+k3)/m2, c2/m2, -(c2+c3)/m2]
153+
])
154+
B = np.array([[0.,0.],[0.,0.],[1/m1,0.],[0.,1/m2]])
155+
C = np.array([[1.0, 0.0, 0.0, 0.0],[0.0, 1.0, 0.0, 0.0]])
156+
D = np.zeros((2,2))
157+
158+
sys = StateSpace(A, B, C, D)
159+
dt = 0.25
160+
sysd = sys.sample(dt, method='zoh')
161+
162+
T = np.arange(0,100,dt)
163+
U = np.random.randn(sysd.B.shape[-1], len(T))
164+
response = forced_response(sysd, U=U)
165+
Y = response.outputs
166+
167+
m = 100
168+
_, Htrue = impulse_response(sysd, T=dt*(m))
169+
170+
171+
# test array_like
172+
atol = 1e-1
173+
H = okid(Y, U, m, dt=dt)
174+
np.testing.assert_allclose(H, Htrue, atol=atol)
175+
176+
# test array_like, truncate
177+
H = okid(Y, U, m, dt=dt, truncate=True)
178+
np.testing.assert_allclose(H, Htrue, atol=atol)
179+
180+
# test array_like, transpose
181+
HT = okid(Y.T, U.T, m, dt=dt, transpose=True)
182+
np.testing.assert_allclose(HT, np.transpose(Htrue), atol=atol)
183+
184+
# test response data
185+
H = okid(response, m, dt=dt)
186+
np.testing.assert_allclose(H, Htrue, atol=atol)
187+
188+
# test response data
189+
H = okid(response, m, dt=dt, truncate=True)
190+
np.testing.assert_allclose(H, Htrue, atol=atol)
191+
192+
# test response data, transpose
193+
response.transpose = True
194+
HT = okid(response, m, dt=dt)
195+
np.testing.assert_allclose(HT, np.transpose(Htrue), atol=atol)
196+
197+
# Make sure markov() returns the right answer
198+
@pytest.mark.parametrize("k, m, n",
199+
[(2, 2, 2),
200+
(2, 5, 5),
201+
(5, 2, 2),
202+
(5, 5, 5),
203+
(5, 10, 10)])
204+
def testOkidResults(self, k, m, n):
205+
#
206+
# Test over a range of parameters
207+
#
208+
# k = order of the system
209+
# m = number of Markov parameters
210+
# n = size of the data vector
211+
#
212+
# Values *should* match exactly for n = m, otherewise you get a
213+
# close match but errors due to the assumption that C A^k B =
214+
# 0 for k > m-2 (see modelsimp.py).
215+
#
216+
217+
# Generate stable continuous time system
218+
Hc = rss(k, 1, 1)
219+
220+
# Choose sampling time based on fastest time constant / 10
221+
w, _ = np.linalg.eig(Hc.A)
222+
Ts = np.min(-np.real(w)) / 10.
223+
224+
# Convert to a discrete time system via sampling
225+
Hd = c2d(Hc, Ts, 'zoh')
226+
227+
# Compute the Markov parameters from state space
228+
Mtrue = np.hstack([Hd.D] + [
229+
Hd.C @ np.linalg.matrix_power(Hd.A, i) @ Hd.B
230+
for i in range(m-1)])
231+
232+
Mtrue = np.squeeze(Mtrue)
233+
234+
# Generate input/output data
235+
T = np.array(range(n)) * Ts
236+
U = np.cos(T) + np.sin(T/np.pi)
237+
_, Y = forced_response(Hd, T, U, squeeze=True)
238+
Mcomp = okid(Y, U, m-1)
239+
240+
241+
# TODO: Check tol
242+
# Compare to results from markov()
243+
# experimentally determined probability to get non matching results
244+
# with rtot=1e-6 and atol=1e-8 due to numerical errors
245+
# for k=5, m=n=10: 0.015 %
246+
np.testing.assert_allclose(Mtrue, Mcomp, atol=1e-1)
247+
248+
249+
114250
def testModredMatchDC(self):
115251
#balanced realization computed in matlab for the transfer function:
116252
# num = [1 11 45 32], den = [1 15 60 200 60]

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy