Skip to content

Commit 0c1e983

Browse files
committed
allow np.array or np.matrix for state space matrices + unit tests
1 parent e2346cd commit 0c1e983

13 files changed

+1857
-166
lines changed

control/bdalg.py

Lines changed: 46 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -53,25 +53,25 @@
5353
5454
"""
5555

56-
import scipy as sp
5756
import numpy as np
5857
from . import xferfcn as tf
5958
from . import statesp as ss
6059
from . import frdata as frd
6160

6261
__all__ = ['series', 'parallel', 'negate', 'feedback', 'append', 'connect']
6362

63+
6464
def series(sys1, *sysn):
6565
"""Return the series connection (... \* sys3 \*) sys2 \* sys1
6666
6767
Parameters
6868
----------
69-
sys1: scalar, StateSpace, TransferFunction, or FRD
70-
sysn: other scalars, StateSpaces, TransferFunctions, or FRDs
69+
sys1 : scalar, StateSpace, TransferFunction, or FRD
70+
sysn : other scalars, StateSpaces, TransferFunctions, or FRDs
7171
7272
Returns
7373
-------
74-
out: scalar, StateSpace, or TransferFunction
74+
out : scalar, StateSpace, or TransferFunction
7575
7676
Raises
7777
------
@@ -105,18 +105,19 @@ def series(sys1, *sysn):
105105
from functools import reduce
106106
return reduce(lambda x, y:y*x, sysn, sys1)
107107

108+
108109
def parallel(sys1, *sysn):
109110
"""
110111
Return the parallel connection sys1 + sys2 (+ sys3 + ...)
111112
112113
Parameters
113114
----------
114-
sys1: scalar, StateSpace, TransferFunction, or FRD
115-
*sysn: other scalars, StateSpaces, TransferFunctions, or FRDs
115+
sys1 : scalar, StateSpace, TransferFunction, or FRD
116+
*sysn : other scalars, StateSpaces, TransferFunctions, or FRDs
116117
117118
Returns
118119
-------
119-
out: scalar, StateSpace, or TransferFunction
120+
out : scalar, StateSpace, or TransferFunction
120121
121122
Raises
122123
------
@@ -150,17 +151,18 @@ def parallel(sys1, *sysn):
150151
from functools import reduce
151152
return reduce(lambda x, y:x+y, sysn, sys1)
152153

154+
153155
def negate(sys):
154156
"""
155157
Return the negative of a system.
156158
157159
Parameters
158160
----------
159-
sys: StateSpace, TransferFunction or FRD
161+
sys : StateSpace, TransferFunction or FRD
160162
161163
Returns
162164
-------
163-
out: StateSpace or TransferFunction
165+
out : StateSpace or TransferFunction
164166
165167
Notes
166168
-----
@@ -177,7 +179,6 @@ def negate(sys):
177179
>>> sys2 = negate(sys1) # Same as sys2 = -sys1.
178180
179181
"""
180-
181182
return -sys;
182183

183184
#! TODO: expand to allow sys2 default to work in MIMO case?
@@ -187,18 +188,18 @@ def feedback(sys1, sys2=1, sign=-1):
187188
188189
Parameters
189190
----------
190-
sys1: scalar, StateSpace, TransferFunction, FRD
191-
The primary plant.
192-
sys2: scalar, StateSpace, TransferFunction, FRD
193-
The feedback plant (often a feedback controller).
191+
sys1 : scalar, StateSpace, TransferFunction, FRD
192+
The primary process.
193+
sys2 : scalar, StateSpace, TransferFunction, FRD
194+
The feedback process (often a feedback controller).
194195
sign: scalar
195196
The sign of feedback. `sign` = -1 indicates negative feedback, and
196197
`sign` = 1 indicates positive feedback. `sign` is an optional
197198
argument; it assumes a value of -1 if not specified.
198199
199200
Returns
200201
-------
201-
out: StateSpace or TransferFunction
202+
out : StateSpace or TransferFunction
202203
203204
Raises
204205
------
@@ -256,7 +257,7 @@ def feedback(sys1, sys2=1, sign=-1):
256257
return sys1.feedback(sys2, sign)
257258

258259
def append(*sys):
259-
'''append(sys1, sys2, ..., sysn)
260+
"""append(sys1, sys2, ..., sysn)
260261
261262
Group models by appending their inputs and outputs
262263
@@ -279,42 +280,40 @@ def append(*sys):
279280
280281
Examples
281282
--------
282-
>>> sys1 = ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.")
283-
>>> sys2 = ss("-1.", "1.", "1.", "0.")
283+
>>> sys1 = ss([[1., -2], [3., -4]], [[5.], [7]]", [[6., 8]], [[9.]])
284+
>>> sys2 = ss([[-1.]], [[1.]], [[1.]], [[0.]])
284285
>>> sys = append(sys1, sys2)
285286
286-
.. todo::
287-
also implement for transfer function, zpk, etc.
288-
'''
287+
"""
289288
s1 = sys[0]
290289
for s in sys[1:]:
291290
s1 = s1.append(s)
292291
return s1
293292

294293
def connect(sys, Q, inputv, outputv):
295-
'''
296-
Index-base interconnection of system
294+
"""Index-based interconnection of an LTI system.
297295
298-
The system sys is a system typically constructed with append, with
299-
multiple inputs and outputs. The inputs and outputs are connected
300-
according to the interconnection matrix Q, and then the final
301-
inputs and outputs are trimmed according to the inputs and outputs
302-
listed in inputv and outputv.
296+
The system `sys` is a system typically constructed with `append`, with
297+
multiple inputs and outputs. The inputs and outputs are connected
298+
according to the interconnection matrix `Q`, and then the final inputs and
299+
outputs are trimmed according to the inputs and outputs listed in `inputv`
300+
and `outputv`.
303301
304-
Note: to have this work, inputs and outputs start counting at 1!!!!
302+
NOTE: Inputs and outputs are indexed starting at 1 and negative values
303+
correspond to a negative feedback interconnection.
305304
306305
Parameters
307306
----------
308-
sys: StateSpace Transferfunction
307+
sys : StateSpace Transferfunction
309308
System to be connected
310-
Q: 2d array
309+
Q : 2D array
311310
Interconnection matrix. First column gives the input to be connected
312-
second column gives the output to be fed into this input. Negative
311+
second column gives the output to be fed into this input. Negative
313312
values for the second column mean the feedback is negative, 0 means
314-
no connection is made
315-
inputv: 1d array
313+
no connection is made. Inputs and outputs are indexed starting at 1.
314+
inputv : 1D array
316315
list of final external inputs
317-
outputv: 1d array
316+
outputv : 1D array
318317
list of final external outputs
319318
320319
Returns
@@ -324,28 +323,30 @@ def connect(sys, Q, inputv, outputv):
324323
325324
Examples
326325
--------
327-
>>> sys1 = ss("1. -2; 3. -4", "5.; 7", "6, 8", "9.")
328-
>>> sys2 = ss("-1.", "1.", "1.", "0.")
326+
>>> sys1 = ss([[1., -2], [3., -4]], [[5.], [7]], [[6, 8]], [[9.]])
327+
>>> sys2 = ss([[-1.]], [[1.]], [[1.]], [[0.]])
329328
>>> sys = append(sys1, sys2)
330-
>>> Q = sp.mat([ [ 1, 2], [2, -1] ]) # basically feedback, output 2 in 1
329+
>>> Q = [[1, 2], [2, -1]]) # negative feedback interconnection
331330
>>> sysc = connect(sys, Q, [2], [1, 2])
332-
'''
331+
332+
"""
333333
# first connect
334-
K = sp.zeros( (sys.inputs, sys.outputs) )
335-
for r in sp.array(Q).astype(int):
334+
K = np.zeros((sys.inputs, sys.outputs))
335+
for r in np.array(Q).astype(int):
336336
inp = r[0]-1
337337
for outp in r[1:]:
338338
if outp > 0 and outp <= sys.outputs:
339339
K[inp,outp-1] = 1.
340340
elif outp < 0 and -outp >= -sys.outputs:
341341
K[inp,-outp-1] = -1.
342-
sys = sys.feedback(sp.matrix(K), sign=1)
342+
sys = sys.feedback(np.array(K), sign=1)
343343

344344
# now trim
345-
Ytrim = sp.zeros( (len(outputv), sys.outputs) )
346-
Utrim = sp.zeros( (sys.inputs, len(inputv)) )
345+
Ytrim = np.zeros((len(outputv), sys.outputs))
346+
Utrim = np.zeros((sys.inputs, len(inputv)))
347347
for i,u in enumerate(inputv):
348348
Utrim[u-1,i] = 1.
349349
for i,y in enumerate(outputv):
350350
Ytrim[i,y-1] = 1.
351-
return sp.matrix(Ytrim)*sys*sp.matrix(Utrim)
351+
352+
return Ytrim * sys * Utrim

control/config.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@
77
# files. For now, you can just choose between MATLAB and FBS default
88
# values.
99

10+
import warnings
11+
1012
# Bode plot defaults
1113
bode_dB = False # Bode plot magnitude units
1214
bode_deg = True # Bode Plot phase units
1315
bode_Hz = False # Bode plot frequency units
1416
bode_number_of_samples = None # Bode plot number of samples
1517
bode_feature_periphery_decade = 1.0 # Bode plot feature periphery in decades
1618

19+
# State space module variables
20+
_use_numpy_matrix = True # Decide whether to use numpy.marix
1721

1822
def reset_defaults():
1923
"""Reset configuration values to their default values."""
@@ -22,6 +26,7 @@ def reset_defaults():
2226
global bode_Hz; bode_Hz = False
2327
global bode_number_of_samples; bode_number_of_samples = None
2428
global bode_feature_periphery_decade; bode_feature_periphery_decade = 1.0
29+
global _use_numpy_matrix; _use_numpy_matrix = True
2530

2631

2732
# Set defaults to match MATLAB
@@ -36,6 +41,7 @@ def use_matlab_defaults():
3641
global bode_dB; bode_dB = True
3742
global bode_deg; bode_deg = True
3843
global bode_Hz; bode_Hz = True
44+
global _use_numpy_matrix; _use_numpy_matrix = True
3945

4046

4147
# Set defaults to match FBS (Astrom and Murray)
@@ -52,3 +58,10 @@ def use_fbs_defaults():
5258
global bode_deg; bode_deg = True
5359
global bode_Hz; bode_Hz = False
5460

61+
62+
# Decide whether to use numpy.matrix for state space operations
63+
def use_numpy_matrix(flag=True, warn=True):
64+
if flag and warn:
65+
warnings.warn("Return type numpy.matrix is soon to be deprecated.",
66+
stacklevel=2)
67+
global _use_numpy_matrix; _use_numpy_matrix = flag

control/frdata.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
from warnings import warn
5353
import numpy as np
5454
from numpy import angle, array, empty, ones, \
55-
real, imag, matrix, absolute, eye, linalg, where, dot
55+
real, imag, absolute, eye, linalg, where, dot
5656
from scipy.interpolate import splprep, splev
5757
from .lti import LTI
5858

@@ -81,6 +81,10 @@ class FRD(LTI):
8181
8282
"""
8383

84+
# Allow NDarray * StateSpace to give StateSpace._rmul_() priority
85+
# https://docs.scipy.org/doc/numpy/reference/arrays.classes.html
86+
__array_priority__ = 11 # override ndarray and matrix types
87+
8488
epsw = 1e-8
8589

8690
def __init__(self, *args, **kwargs):
@@ -436,12 +440,13 @@ def feedback(self, other=1, sign=-1):
436440
# TODO: vectorize this
437441
# TODO: handle omega re-mapping
438442
for k, w in enumerate(other.omega):
439-
fresp[:, :, k] = self.fresp[:, :, k].view(type=matrix)* \
443+
fresp[:, :, k] = np.dot(
444+
self.fresp[:, :, k],
440445
linalg.solve(
441-
eye(self.inputs) +
442-
other.fresp[:, :, k].view(type=matrix) *
443-
self.fresp[:, :, k].view(type=matrix),
444-
eye(self.inputs))
446+
eye(self.inputs)
447+
+ np.dot(other.fresp[:, :, k], self.fresp[:, :, k]),
448+
eye(self.inputs))
449+
)
445450

446451
return FRD(fresp, other.omega, smooth=(self.ifunc is not None))
447452

0 commit comments

Comments
 (0)