Skip to content

Commit 2b1cd21

Browse files
committed
Change API to work with ndarray and TimeResponseData as input
1 parent ca37898 commit 2b1cd21

File tree

4 files changed

+114
-29
lines changed

4 files changed

+114
-29
lines changed

control/modelsimp.py

Lines changed: 55 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,7 @@
4343
# External packages and modules
4444
import numpy as np
4545
import warnings
46-
from .exception import ControlSlycot, ControlMIMONotImplemented, \
47-
ControlDimension
46+
from .exception import ControlSlycot, ControlArgument, ControlDimension
4847
from .iosys import isdtime, isctime
4948
from .statesp import StateSpace
5049
from .statefbk import gram
@@ -403,8 +402,10 @@ def era(YY, m, n, nin, nout, r):
403402
raise NotImplementedError('This function is not implemented yet.')
404403

405404

406-
def markov(data, m=None, dt=True, truncate=False):
407-
"""Calculate the first `m` Markov parameters [D CB CAB ...]
405+
def markov(*args, **kwargs):
406+
"""markov(Y, U, [, m])
407+
408+
Calculate the first `m` Markov parameters [D CB CAB ...]
408409
from data
409410
410411
This function computes the Markov parameters for a discrete time system
@@ -419,14 +420,31 @@ def markov(data, m=None, dt=True, truncate=False):
419420
the input data is less than the desired number of Markov parameters (a
420421
warning message is generated in this case).
421422
423+
The function can be called with either 1, 2, or 3 arguments:
424+
425+
* ``K, S, E = lqr(response)``
426+
* ``K, S, E = lqr(respnose, m)``
427+
* ``K, S, E = lqr(Y, U)``
428+
* ``K, S, E = lqr(Y, U, m)``
429+
430+
where `response` is an `TimeResponseData` object, and `Y`, `U`, are 1D or 2D
431+
array and m is an integer.
432+
422433
Parameters
423434
----------
435+
Y : array_like
436+
Output data. If the array is 1D, the system is assumed to be single
437+
input. If the array is 2D and transpose=False, the columns of `Y`
438+
are taken as time points, otherwise the rows of `Y` are taken as
439+
time points.
440+
U : array_like
441+
Input data, arranged in the same way as `Y`.
424442
data : TimeResponseData
425443
Response data from which the Markov parameters where estimated.
426444
Input and output data must be 1D or 2D array.
427445
m : int, optional
428446
Number of Markov parameters to output. Defaults to len(U).
429-
dt : (True of float, optional)
447+
dt : True of float, optional
430448
True indicates discrete time with unspecified sampling time,
431449
positive number is discrete time with specified sampling time.
432450
It can be used to scale the markov parameters in order to match
@@ -460,17 +478,41 @@ def markov(data, m=None, dt=True, truncate=False):
460478
--------
461479
>>> T = np.linspace(0, 10, 100)
462480
>>> U = np.ones((1, 100))
463-
>>> response = ct.forced_response(ct.tf([1], [1, 0.5], True), T, U)
464-
>>> H = ct.markov(response, 3)
481+
>>> T, Y = ct.forced_response(ct.tf([1], [1, 0.5], True), T, U)
482+
>>> H = ct.markov(Y, U, 3, transpose=False)
465483
466484
"""
467485
# Convert input parameters to 2D arrays (if they aren't already)
468-
Umat = np.array(data.inputs, ndmin=2)
469-
Ymat = np.array(data.outputs, ndmin=2)
470486

471-
# If data is in transposed format, switch it around
472-
if data.transpose and not data.issiso:
473-
Umat, Ymat = np.transpose(Umat), np.transpose(Ymat)
487+
# Get the system description
488+
if (len(args) < 1):
489+
raise ControlArgument("not enough input arguments")
490+
491+
if isinstance(args[0], TimeResponseData):
492+
Umat = np.array(args[0].inputs, ndmin=2)
493+
Ymat = np.array(args[0].outputs, ndmin=2)
494+
transpose = args[0].transpose
495+
if args[0].transpose and not args[0].issiso:
496+
Umat, Ymat = np.transpose(Umat), np.transpose(Ymat)
497+
index = 1
498+
else:
499+
if (len(args) < 2):
500+
raise ControlArgument("not enough input arguments")
501+
Umat = np.array(args[0], ndmin=2)
502+
Ymat = np.array(args[1], ndmin=2)
503+
transpose = kwargs.pop('transpose', False)
504+
if transpose:
505+
Umat, Ymat = np.transpose(Umat), np.transpose(Ymat)
506+
index = 2
507+
508+
509+
if (len(args) > index):
510+
m = args[index]
511+
else:
512+
m = None
513+
514+
dt = kwargs.pop('dt', True)
515+
truncate = kwargs.pop('truncate', False)
474516

475517
# Make sure the number of time points match
476518
if Umat.shape[1] != Ymat.shape[1]:
@@ -549,4 +591,4 @@ def markov(data, m=None, dt=True, truncate=False):
549591
H = np.squeeze(H)
550592

551593
# Return the first m Markov parameters
552-
return H if not data.transpose else np.transpose(H)
594+
return H if not transpose else np.transpose(H)

control/tests/kwargs_test.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ def test_response_plot_kwargs(data_fcn, plot_fcn, mimo):
252252
'linearize': test_unrecognized_kwargs,
253253
'lqe': test_unrecognized_kwargs,
254254
'lqr': test_unrecognized_kwargs,
255+
'markov': test_unrecognized_kwargs,
255256
'nichols_plot': test_matplotlib_kwargs,
256257
'nichols': test_matplotlib_kwargs,
257258
'nlsys': test_unrecognized_kwargs,

control/tests/modelsimp_test.py

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@
77
import pytest
88

99

10-
from control import StateSpace, forced_response, tf, rss, c2d, TimeResponseData
11-
from control.exception import ControlMIMONotImplemented
10+
from control import StateSpace, forced_response, impulse_response, tf, rss, c2d, TimeResponseData
1211
from control.tests.conftest import slycotonly
1312
from control.modelsimp import balred, hsvd, markov, modred
1413

@@ -33,44 +32,88 @@ def testHSVD(self):
3332
assert not isinstance(hsv, np.matrix)
3433

3534
def testMarkovSignature(self):
36-
U = np.array([1., 1., 1., 1., 1.])
35+
U = np.array([[1., 1., 1., 1., 1.]])
3736
Y = U
3837
response = TimeResponseData(time=np.arange(U.shape[-1]),
3938
outputs=Y,
4039
output_labels='y',
4140
inputs=U,
4241
input_labels='u',
4342
)
43+
44+
# Basic usage
4445
m = 3
46+
H = markov(Y, U, m, transpose=False)
47+
Htrue = np.array([1., 0., 0.])
48+
np.testing.assert_array_almost_equal(H, Htrue)
49+
50+
response.transpose=False
4551
H = markov(response, m)
4652
Htrue = np.array([1., 0., 0.])
4753
np.testing.assert_array_almost_equal(H, Htrue)
4854

4955
# Make sure that transposed data also works
56+
H = markov(Y.T, U.T, m, transpose=True)
57+
np.testing.assert_array_almost_equal(H, np.transpose(Htrue))
58+
5059
response.transpose=True
5160
H = markov(response, m)
5261
np.testing.assert_array_almost_equal(H, np.transpose(Htrue))
62+
response.transpose=False
5363

5464
# Generate Markov parameters without any arguments
55-
response.transpose=False
65+
H = markov(Y, U, m)
66+
np.testing.assert_array_almost_equal(H, Htrue)
67+
5668
H = markov(response, m)
5769
np.testing.assert_array_almost_equal(H, Htrue)
5870

5971
# Test example from docstring
72+
T = np.linspace(0, 10, 100)
73+
U = np.ones((1, 100))
74+
_, Y = forced_response(tf([1], [1, 0.5], True), T, U)
75+
H = markov(Y, U, 3)
76+
6077
T = np.linspace(0, 10, 100)
6178
U = np.ones((1, 100))
6279
response = forced_response(tf([1], [1, 0.5], True), T, U)
6380
H = markov(response, 3)
6481

6582
# Test example from issue #395
66-
#inp = np.array([1, 2])
67-
#outp = np.array([2, 4])
68-
#mrk = markov(outp, inp, 1, transpose=False)
69-
70-
# Make sure MIMO generates an error
71-
#U = np.ones((2, 100)) # 2 inputs (Y unchanged, with 1 output)
72-
#with pytest.raises(ControlMIMONotImplemented):
73-
# markov(Y, U, m)
83+
inp = np.array([1, 2])
84+
outp = np.array([2, 4])
85+
mrk = markov(outp, inp, 1, transpose=False)
86+
87+
# Test mimo example
88+
# Mechanical Vibrations: Theory and Application, SI Edition, 1st ed.
89+
# Figure 6.5 / Example 6.7
90+
m1, k1, c1 = 1., 4., 1.
91+
m2, k2, c2 = 2., 2., 1.
92+
k3, c3 = 6., 2.
93+
94+
A = np.array([
95+
[0., 0., 1., 0.],
96+
[0., 0., 0., 1.],
97+
[-(k1+k2)/m1, (k2)/m1, -(c1+c2)/m1, c2/m1],
98+
[(k2)/m2, -(k2+k3)/m2, c2/m2, -(c2+c3)/m2]
99+
])
100+
B = np.array([[0.,0.],[0.,0.],[1/m1,0.],[0.,1/m2]])
101+
C = np.array([[1.0, 0.0, 0.0, 0.0],[0.0, 1.0, 0.0, 0.0]])
102+
D = np.zeros((2,2))
103+
104+
sys = StateSpace(A, B, C, D)
105+
dt = 0.25
106+
sysd = sys.sample(dt, method='zoh')
107+
108+
t = np.arange(0,100,dt)
109+
u = np.random.randn(sysd.B.shape[-1], len(t))
110+
response = forced_response(sysd, U=u)
111+
112+
m = 100
113+
H = markov(response, m, dt=dt)
114+
_, Htrue = impulse_response(sysd, T=dt*(m-1))
115+
116+
np.testing.assert_array_almost_equal(H, Htrue)
74117

75118
# Make sure markov() returns the right answer
76119
@pytest.mark.parametrize("k, m, n",

examples/markov.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def create_impulse_response(H, time, transpose, dt):
4848
issiso=issiso)
4949

5050
# set up a mass spring damper system (2dof, MIMO case)
51-
# Mechanical Vibartions: Theory and Application, SI Edition, 1st ed.
51+
# Mechanical Vibrations: Theory and Application, SI Edition, 1st ed.
5252
# Figure 6.5 / Example 6.7
5353
# m q_dd + c q_d + k q = f
5454
m1, k1, c1 = 1., 4., 1.
@@ -91,10 +91,9 @@ def create_impulse_response(H, time, transpose, dt):
9191
plt.show()
9292

9393
m = 50
94-
ir_true = ct.impulse_response(sysd,T=dt*m)
95-
ir_true.tranpose = True
94+
ir_true = ct.impulse_response(sysd, T=dt*m)
9695

97-
H_est = ct.markov(response,m=m,dt=dt)
96+
H_est = ct.markov(response, m, dt=dt)
9897
# Helper function for plotting only
9998
ir_est = create_impulse_response(H_est,
10099
ir_true.time,

0 commit comments

Comments
 (0)