Skip to content

Commit de2fc39

Browse files
authored
Merge branch 'master' into statesp-performance-improvements
2 parents c65d085 + 31e6944 commit de2fc39

37 files changed

Lines changed: 11643 additions & 9907 deletions

.travis.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,5 +76,18 @@ script:
7676
- 'if [ $SLYCOT != "" ]; then python -c "import slycot"; fi'
7777
- coverage run setup.py test
7878

79+
# only run examples if Slycot is install
80+
# set PYTHONPATH for examples
81+
# pmw needed for examples/tfvis.py
82+
# future is needed for Python 2, also for examples/tfvis.py
83+
84+
- if [[ "$SLYCOT" != "" ]]; then
85+
export PYTHONPATH=$PWD;
86+
conda install -c conda-forge pmw future;
87+
cd examples; bash run_examples.sh; cd ..;
88+
fi
89+
90+
# arbitrary change to try to trigger travis build
91+
7992
after_success:
8093
- coveralls

control/bdalg.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -239,14 +239,14 @@ def feedback(sys1, sys2=1, sign=-1):
239239
# its feedback member function.
240240
if isinstance(sys1, (int, float, complex, np.number)):
241241
if isinstance(sys2, tf.TransferFunction):
242-
sys1 = tf._convertToTransferFunction(sys1)
242+
sys1 = tf._convert_to_transfer_function(sys1)
243243
elif isinstance(sys2, ss.StateSpace):
244244
sys1 = ss._convertToStateSpace(sys1)
245245
elif isinstance(sys2, frd.FRD):
246246
sys1 = ss._convertToFRD(sys1)
247247
else: # sys2 is a scalar.
248-
sys1 = tf._convertToTransferFunction(sys1)
249-
sys2 = tf._convertToTransferFunction(sys2)
248+
sys1 = tf._convert_to_transfer_function(sys1)
249+
sys2 = tf._convert_to_transfer_function(sys2)
250250

251251
return sys1.feedback(sys2, sign)
252252

control/canonical.py

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
from .statesp import StateSpace
77
from .statefbk import ctrb, obsv
88

9-
from numpy import zeros, shape, poly
10-
from numpy.linalg import solve, matrix_rank
9+
from numpy import zeros, shape, poly, iscomplex, hstack
10+
from numpy.linalg import solve, matrix_rank, eig
1111

1212
__all__ = ['canonical_form', 'reachable_form', 'observable_form']
1313

@@ -22,7 +22,7 @@ def canonical_form(xsys, form='reachable'):
2222
Canonical form for transformation. Chosen from:
2323
* 'reachable' - reachable canonical form
2424
* 'observable' - observable canonical form
25-
* 'modal' - modal canonical form [not implemented]
25+
* 'modal' - modal canonical form
2626
2727
Returns
2828
-------
@@ -37,6 +37,8 @@ def canonical_form(xsys, form='reachable'):
3737
return reachable_form(xsys)
3838
elif form == 'observable':
3939
return observable_form(xsys)
40+
elif form == 'modal':
41+
return modal_form(xsys)
4042
else:
4143
raise ControlNotImplemented(
4244
"Canonical form '%s' not yet implemented" % form)
@@ -142,3 +144,69 @@ def observable_form(xsys):
142144
zsys.B = Tzx * xsys.B
143145

144146
return zsys, Tzx
147+
148+
def modal_form(xsys):
149+
"""Convert a system into modal canonical form
150+
151+
Parameters
152+
----------
153+
xsys : StateSpace object
154+
System to be transformed, with state `x`
155+
156+
Returns
157+
-------
158+
zsys : StateSpace object
159+
System in modal canonical form, with state `z`
160+
T : matrix
161+
Coordinate transformation: z = T * x
162+
"""
163+
# Check to make sure we have a SISO system
164+
if not issiso(xsys):
165+
raise ControlNotImplemented(
166+
"Canonical forms for MIMO systems not yet supported")
167+
168+
# Create a new system, starting with a copy of the old one
169+
zsys = StateSpace(xsys)
170+
171+
# Calculate eigenvalues and matrix of eigenvectors Tzx,
172+
eigval, eigvec = eig(xsys.A)
173+
174+
# Eigenvalues and according eigenvectors are not sorted,
175+
# thus modal transformation is ambiguous
176+
# Sorting eigenvalues and respective vectors by largest to smallest eigenvalue
177+
idx = eigval.argsort()[::-1]
178+
eigval = eigval[idx]
179+
eigvec = eigvec[:,idx]
180+
181+
# If all eigenvalues are real, the matrix of eigenvectors is Tzx directly
182+
if not iscomplex(eigval).any():
183+
Tzx = eigvec
184+
else:
185+
# A is an arbitrary semisimple matrix
186+
187+
# Keep track of complex conjugates (need only one)
188+
lst_conjugates = []
189+
Tzx = None
190+
for val, vec in zip(eigval, eigvec.T):
191+
if iscomplex(val):
192+
if val not in lst_conjugates:
193+
lst_conjugates.append(val.conjugate())
194+
if Tzx is not None:
195+
Tzx = hstack((Tzx, hstack((vec.real.T, vec.imag.T))))
196+
else:
197+
Tzx = hstack((vec.real.T, vec.imag.T))
198+
else:
199+
# if conjugate has already been seen, skip this eigenvalue
200+
lst_conjugates.remove(val)
201+
else:
202+
if Tzx is not None:
203+
Tzx = hstack((Tzx, vec.real.T))
204+
else:
205+
Tzx = vec.real.T
206+
207+
# Generate the system matrices for the desired canonical form
208+
zsys.A = solve(Tzx, xsys.A).dot(Tzx)
209+
zsys.B = solve(Tzx, xsys.B)
210+
zsys.C = xsys.C.dot(Tzx)
211+
212+
return zsys, Tzx

0 commit comments

Comments
 (0)