Skip to content

Commit 3fbc191

Browse files
committed
merging changes from Rane van Paassen - see 2013-05-23 ChangeLog entry for details
2 parents 6cbe9ba + 4418e55 commit 3fbc191

17 files changed

Lines changed: 940 additions & 154 deletions

ChangeLog

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,24 @@
1+
2013-05-23 Rene van Paassen <rene.vanpaassen@gmail.com>
2+
3+
* src/margin.py: re-vamped stability_margins function. Now
4+
uses analytical method for TF and state-space, works on FRD
5+
objects too, and can optionally return all margins found for
6+
the analytical method.
7+
8+
* src/xferfcn.py and src/frdata.py: Now return array result
9+
for fresp when called with an iterable frequency vector
10+
11+
* src/xferfcn.py and src/statesp.py: added minreal functions
12+
13+
* src/bdalg.py: implemented append and connect functions (old
14+
version, with indexes, not with names)
15+
16+
* src/matlab.py: added connect, append, minreal
17+
18+
* src/xferfcn.py and src/statesp.py: added/corrected
19+
__rmul__. In most cases a gain matrix (np.matrix) may now be
20+
used in combination with ss or tf systems.
21+
122
2012-11-10 Richard Murray <murray@altura.local>
223

324
* src/canonical.py: new module implementing conversions to selected

src/bdalg.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
55
Routines in this module:
66
7+
append
78
series
89
parallel
910
negate
1011
feedback
12+
connect
1113
1214
"""
1315

@@ -236,3 +238,97 @@ def feedback(sys1, sys2, sign=-1):
236238
sys2 = tf._convertToTransferFunction(sys2)
237239

238240
return sys1.feedback(sys2, sign)
241+
242+
def append(*sys):
243+
'''
244+
Group models by appending their inputs and outputs
245+
246+
Forms an augmented system model, and appends the inputs and
247+
outputs together. The system type will be the type of the first
248+
system given; if you mix state-space systems and gain matrices,
249+
make sure the gain matrices are not first.
250+
251+
Parameters.
252+
-----------
253+
sys1, sys2, ... sysn: StateSpace or Transferfunction
254+
LTI systems to combine
255+
256+
257+
Returns
258+
-------
259+
sys: LTI system
260+
Combined LTI system, with input/output vectors consisting of all
261+
input/output vectors appended
262+
263+
Examples
264+
--------
265+
>>> sys1 = ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.")
266+
>>> sys2 = ss("-1.", "1.", "1.", "0.")
267+
>>> sys = append(sys1, sys2)
268+
269+
.. todo::
270+
also implement for transfer function, zpk, etc.
271+
'''
272+
s1 = sys[0]
273+
for s in sys[1:]:
274+
s1 = s1.append(s)
275+
return s1
276+
277+
def connect(sys, Q, inputv, outputv):
278+
'''
279+
Index-base interconnection of system
280+
281+
The system sys is a system typically constructed with append, with
282+
multiple inputs and outputs. The inputs and outputs are connected
283+
according to the interconnection matrix Q, and then the final
284+
inputs and outputs are trimmed according to the inputs and outputs
285+
listed in inputv and outputv.
286+
287+
Note: to have this work, inputs and outputs start counting at 1!!!!
288+
289+
Parameters.
290+
-----------
291+
sys: StateSpace Transferfunction
292+
System to be connected
293+
Q: 2d array
294+
Interconnection matrix. First column gives the input to be connected
295+
second column gives the output to be fed into this input. Negative
296+
values for the second column mean the feedback is negative, 0 means
297+
no connection is made
298+
inputv: 1d array
299+
list of final external inputs
300+
outputv: 1d array
301+
list of final external outputs
302+
303+
Returns
304+
-------
305+
sys: LTI system
306+
Connected and trimmed LTI system
307+
308+
Examples
309+
--------
310+
>>> sys1 = ss("1. -2; 3. -4", "5.; 7", "6, 8", "9.")
311+
>>> sys2 = ss("-1.", "1.", "1.", "0.")
312+
>>> sys = append(sys1, sys2)
313+
>>> Q = sp.mat([ [ 1, 2], [2, -1] ]) # basically feedback, output 2 in 1
314+
>>> sysc = connect(sys, Q, [2], [1, 2])
315+
'''
316+
# first connect
317+
K = sp.zeros( (sys.inputs, sys.outputs) )
318+
for r in sp.array(Q).astype(int):
319+
inp = r[0]-1
320+
for outp in r[1:]:
321+
if outp > 0 and outp <= sys.outputs:
322+
K[inp,outp-1] = 1.
323+
elif outp < 0 and -outp >= -sys.outputs:
324+
K[inp,-outp-1] = -1.
325+
sys = sys.feedback(sp.matrix(K), sign=1)
326+
327+
# now trim
328+
Ytrim = sp.zeros( (len(outputv), sys.outputs) )
329+
Utrim = sp.zeros( (sys.inputs, len(inputv)) )
330+
for i,u in enumerate(inputv):
331+
Utrim[u-1,i] = 1.
332+
for i,y in enumerate(outputv):
333+
Ytrim[i,y-1] = 1.
334+
return sp.matrix(Ytrim)*sys*sp.matrix(Utrim)

src/frdata.py

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ class FRD(Lti):
107107
def __init__(self, *args, **kwargs):
108108
"""Construct a transfer function.
109109
110-
The default constructor is FRD(w, d), where w is an iterable of
110+
The default constructor is FRD(d, w), where w is an iterable of
111111
frequency points, and d is the matching frequency data.
112112
113113
If d is a single list, 1d array, or tuple, a SISO system description
@@ -142,8 +142,8 @@ def __init__(self, *args, **kwargs):
142142
# The user provided a response and a freq vector
143143
self.fresp = array(args[0], dtype=complex)
144144
if len(self.fresp.shape) == 1:
145-
self.fresp.reshape(1, 1, len(args[0]))
146-
self.omega = array(args[1])
145+
self.fresp = self.fresp.reshape(1, 1, len(args[0]))
146+
self.omega = array(args[1], dtype=float)
147147
if len(self.fresp.shape) != 3 or \
148148
self.fresp.shape[-1] != self.omega.shape[-1] or \
149149
len(self.omega.shape) != 1:
@@ -189,9 +189,9 @@ def __str__(self):
189189
if mimo:
190190
outstr.append("Input %i to output %i:" % (i + 1, j + 1))
191191
outstr.append('Freq [rad/s] Response ')
192-
outstr.append('------------ ------------------------')
192+
outstr.append('------------ ---------------------')
193193
outstr.extend(
194-
[ '%12.3f %10.4g + %10.4g' % (w, m, p)
194+
[ '%12.3f %10.4g%+10.4gj' % (w, m, p)
195195
for m, p, w in zip(real(self.fresp[j,i,:]), imag(self.fresp[j,i,:]), wt) ])
196196

197197

@@ -340,7 +340,10 @@ def evalfr(self, omega):
340340
"""
341341

342342
# Preallocate the output.
343-
out = empty((self.outputs, self.inputs), dtype=complex)
343+
if getattr(omega, '__iter__', False):
344+
out = empty((self.outputs, self.inputs, len(omega)), dtype=complex)
345+
else:
346+
out = empty((self.outputs, self.inputs), dtype=complex)
344347

345348
if self.ifunc is None:
346349
try:
@@ -350,11 +353,18 @@ def evalfr(self, omega):
350353
"Frequency %f not in frequency list, try an interpolating"
351354
" FRD if you want additional points")
352355
else:
353-
for i in range(self.outputs):
354-
for j in range(self.inputs):
355-
frraw = splev(omega, self.ifunc[i,j], der=0)
356-
out[i,j] = frraw[0] + 1.0j*frraw[1]
357-
356+
if getattr(omega, '__iter__', False):
357+
for i in range(self.outputs):
358+
for j in range(self.inputs):
359+
for k,w in enumerate(omega):
360+
frraw = splev(w, self.ifunc[i,j], der=0)
361+
out[i,j,k] = frraw[0] + 1.0j*frraw[1]
362+
else:
363+
for i in range(self.outputs):
364+
for j in range(self.inputs):
365+
frraw = splev(omega, self.ifunc[i,j], der=0)
366+
out[i,j] = frraw[0] + 1.0j*frraw[1]
367+
358368
return out
359369

360370
# Method for generating the frequency response of the system

src/lti.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
timebaseEqual()
1313
"""
1414

15+
from numpy import absolute, real
16+
1517
class Lti:
1618
"""Lti is a parent class to linear time invariant control (LTI) objects.
1719
@@ -64,6 +66,12 @@ def __init__(self, inputs=1, outputs=1, dt=None):
6466
self.outputs = outputs
6567
self.dt = dt
6668

69+
def damp(self):
70+
poles = self.pole()
71+
wn = absolute(poles)
72+
Z = -real(poles)/wn
73+
return wn, Z, poles
74+
6775
# Test to see if a system is SISO
6876
def issiso(sys, strict=False):
6977
if isinstance(sys, (int, float, complex)) and not strict:

0 commit comments

Comments
 (0)