Skip to content

Commit b0497f4

Browse files
authored
Merge branch 'master' into master
2 parents 3ea4f26 + e3c0f79 commit b0497f4

45 files changed

Lines changed: 1219 additions & 522 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.travis.yml

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,35 +12,45 @@ python:
1212
- "2.7"
1313
- "3.3"
1414
- "3.4"
15+
- "3.5"
1516

1617
# install required system libraries
1718
before_install:
1819
- export DISPLAY=:99.0
1920
- sh -e /etc/init.d/xvfb start
2021
# use miniconda to install numpy/scipy, to avoid lengthy build from source
2122
- if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then
22-
wget http://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh -O miniconda.sh;
23+
wget https://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh -O miniconda.sh;
2324
else
24-
wget http://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh;
25+
wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh;
2526
fi
2627
- bash miniconda.sh -b -p $HOME/miniconda
2728
- export PATH="$HOME/miniconda/bin:$PATH"
2829
- hash -r
2930
- conda config --set always_yes yes --set changeps1 no
3031
- conda update -q conda
31-
- conda install --yes python=$TRAVIS_PYTHON_VERSION conda-build pip coverage
32-
- conda config --add channels http://conda.binstar.org/cwrowley
32+
# conda-build must be installed in the conda root environment
33+
- conda install conda-build
34+
- conda config --add channels python-control
3335
- conda info -a
36+
- conda create -q -n test-environment python="$TRAVIS_PYTHON_VERSION" pip coverage
37+
- source activate test-environment
38+
# coveralls not in conda repos
39+
- pip install coveralls
3440

3541
# Install packages
3642
install:
37-
- conda install slycot
38-
- conda build conda-recipe
43+
- conda build --python "$TRAVIS_PYTHON_VERSION" conda-recipe
3944
- conda install control --use-local
40-
- pip install coveralls
4145

4246
# command to run tests
4347
script:
48+
# Before installing Slycot
49+
- python setup.py test
50+
51+
# Now, get and use Slycot
52+
- conda install slycot
4453
- coverage run setup.py test
54+
4555
after_success:
4656
- coveralls

ChangeLog

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@
148148
2012-11-03 Richard Murray <murray@altura.local>
149149

150150
* src/rlocus.py (_RLSortRoots): convert output of range() to
151-
explicit list for python 3 compatability
151+
explicit list for python 3 compatibility
152152

153153
* tests/modelsimp_test.py, tests/slycot_convert_test.py,
154154
tests/mateqn_test.py, tests/statefbk_test.py: updated test suites to
@@ -604,7 +604,7 @@
604604
initial_response, impulse_response and step_response.
605605

606606
* src/rlocus.py: changed RootLocus to root_locus for better
607-
compatability with PEP 8. Also updated unit tests and examples.
607+
compatibility with PEP 8. Also updated unit tests and examples.
608608

609609
2011-07-25 Richard Murray <murray@malabar.local>
610610

@@ -994,7 +994,7 @@
994994
2010-09-02 Richard Murray <murray@sumatra.local>
995995

996996
* src/statefbk.py (place): Use np.size() instead of len() for
997-
finding length of placed_eigs for better compatability with
997+
finding length of placed_eigs for better compatibility with
998998
different python versions [courtesy of Roberto Bucher]
999999

10001000
* src/delay.py (pade): New file for delay-based computations +

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ functionality is limited or absent, and installation of slycot is recommended
4141
(see below). Note that in order to install slycot, you will need a FORTRAN
4242
compiler on your machine. The Slycot wrapper can be found at:
4343

44-
https://github.com/jgoppert/Slycot
44+
https://github.com/python-control/Slycot
4545

4646
Installation
4747
============

control/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@
6565
from .ctrlutil import *
6666
from .frdata import *
6767
from .canonical import *
68+
from .robust import *
69+
from .config import *
6870

6971
# Exceptions
7072
from .exception import *

control/canonical.py

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
from .exception import ControlNotImplemented
55
from .lti import issiso
66
from .statesp import StateSpace
7-
from .statefbk import ctrb
7+
from .statefbk import ctrb, obsv
88

99
from numpy import zeros, shape, poly
10-
from numpy.linalg import inv
10+
from numpy.linalg import solve, matrix_rank
1111

12-
__all__ = ['canonical_form', 'reachable_form']
12+
__all__ = ['canonical_form', 'reachable_form', 'observable_form']
1313

1414
def canonical_form(xsys, form='reachable'):
1515
"""Convert a system into canonical form
@@ -21,7 +21,7 @@ def canonical_form(xsys, form='reachable'):
2121
form : String
2222
Canonical form for transformation. Chosen from:
2323
* 'reachable' - reachable canonical form
24-
* 'observable' - observable canonical form [not implemented]
24+
* 'observable' - observable canonical form
2525
* 'modal' - modal canonical form [not implemented]
2626
2727
Returns
@@ -35,6 +35,8 @@ def canonical_form(xsys, form='reachable'):
3535
# Call the appropriate tranformation function
3636
if form == 'reachable':
3737
return reachable_form(xsys)
38+
elif form == 'observable':
39+
return observable_form(xsys)
3840
else:
3941
raise ControlNotImplemented(
4042
"Canonical form '%s' not yet implemented" % form)
@@ -66,22 +68,74 @@ def reachable_form(xsys):
6668

6769
# Generate the system matrices for the desired canonical form
6870
zsys.B = zeros(shape(xsys.B))
69-
zsys.B[0, 0] = 1
71+
zsys.B[0, 0] = 1.0
7072
zsys.A = zeros(shape(xsys.A))
7173
Apoly = poly(xsys.A) # characteristic polynomial
7274
for i in range(0, xsys.states):
7375
zsys.A[0, i] = -Apoly[i+1] / Apoly[0]
7476
if (i+1 < xsys.states):
75-
zsys.A[i+1, i] = 1
77+
zsys.A[i+1, i] = 1.0
7678

7779
# Compute the reachability matrices for each set of states
7880
Wrx = ctrb(xsys.A, xsys.B)
7981
Wrz = ctrb(zsys.A, zsys.B)
8082

83+
if matrix_rank(Wrx) != xsys.states:
84+
raise ValueError("System not controllable to working precision.")
85+
86+
# Transformation from one form to another
87+
Tzx = solve(Wrx.T, Wrz.T).T # matrix right division, Tzx = Wrz * inv(Wrx)
88+
89+
if matrix_rank(Tzx) != xsys.states:
90+
raise ValueError("Transformation matrix singular to working precision.")
91+
92+
# Finally, compute the output matrix
93+
zsys.C = solve(Tzx.T, xsys.C.T).T # matrix right division, zsys.C = xsys.C * inv(Tzx)
94+
95+
return zsys, Tzx
96+
97+
98+
def observable_form(xsys):
99+
"""Convert a system into observable canonical form
100+
101+
Parameters
102+
----------
103+
xsys : StateSpace object
104+
System to be transformed, with state `x`
105+
106+
Returns
107+
-------
108+
zsys : StateSpace object
109+
System in observable canonical form, with state `z`
110+
T : matrix
111+
Coordinate transformation: z = T * x
112+
"""
113+
# Check to make sure we have a SISO system
114+
if not issiso(xsys):
115+
raise ControlNotImplemented(
116+
"Canonical forms for MIMO systems not yet supported")
117+
118+
# Create a new system, starting with a copy of the old one
119+
zsys = StateSpace(xsys)
120+
121+
# Generate the system matrices for the desired canonical form
122+
zsys.C = zeros(shape(xsys.C))
123+
zsys.C[0, 0] = 1
124+
zsys.A = zeros(shape(xsys.A))
125+
Apoly = poly(xsys.A) # characteristic polynomial
126+
for i in range(0, xsys.states):
127+
zsys.A[i, 0] = -Apoly[i+1] / Apoly[0]
128+
if (i+1 < xsys.states):
129+
zsys.A[i, i+1] = 1
130+
131+
# Compute the observability matrices for each set of states
132+
Wrx = obsv(xsys.A, xsys.C)
133+
Wrz = obsv(zsys.A, zsys.C)
134+
81135
# Transformation from one form to another
82-
Tzx = Wrz * inv(Wrx)
136+
Tzx = inv(Wrz) * Wrx
83137

84138
# Finally, compute the output matrix
85-
zsys.C = xsys.C * inv(Tzx)
139+
zsys.B = Tzx * xsys.B
86140

87141
return zsys, Tzx

control/config.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,27 @@
1111
bode_dB = False # Bode plot magnitude units
1212
bode_deg = True # Bode Plot phase units
1313
bode_Hz = False # Bode plot frequency units
14-
bode_number_of_samples = None # Bode plot number of samples
14+
bode_number_of_samples = None # Bode plot number of samples
1515
bode_feature_periphery_decade = 1.0 # Bode plot feature periphery in decades
1616

1717
# Set defaults to match MATLAB
1818
def use_matlab_defaults():
19+
"""
20+
Use MATLAB compatible configuration settings
21+
* Bode plots plot gain in dB, phase in degrees, frequency in Hertz
22+
"""
1923
# Bode plot defaults
2024
global bode_dB; bode_dB = True
2125
global bode_deg; bode_deg = True
2226
global bode_Hz; bode_Hz = True
2327

2428
# Set defaults to match FBS (Astrom and Murray)
2529
def use_fbs_defaults():
30+
"""
31+
Use `Astrom and Murray <http://fbsbook.org>`_ compatible settings
32+
* Bode plots plot gain in powers of ten, phase in degrees,
33+
frequency in Hertz
34+
"""
2635
# Bode plot defaults
2736
global bode_dB; bode_dB = False
2837
global bode_deg; bode_deg = True

control/dtime.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def sample_system(sysc, Ts, method='zoh', alpha=None):
6565
Ts : real
6666
Sampling period
6767
method : string
68-
Method to use for conversion: 'matched' (default), 'tustin', 'zoh'
68+
Method to use for conversion: 'matched', 'tustin', 'zoh' (default)
6969
7070
Returns
7171
-------

control/exception.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#
33
# Author: Richard M. Murray
44
# Date: 31 May 2010
5-
#
5+
#
66
# This file contains definitions of standard exceptions for the control package
77
#
88
# Copyright (c) 2010 by California Institute of Technology
@@ -14,16 +14,16 @@
1414
#
1515
# 1. Redistributions of source code must retain the above copyright
1616
# notice, this list of conditions and the following disclaimer.
17-
#
17+
#
1818
# 2. Redistributions in binary form must reproduce the above copyright
1919
# notice, this list of conditions and the following disclaimer in the
2020
# documentation and/or other materials provided with the distribution.
21-
#
21+
#
2222
# 3. Neither the name of the California Institute of Technology nor
2323
# the names of its contributors may be used to endorse or promote
2424
# products derived from this software without specific prior
2525
# written permission.
26-
#
26+
#
2727
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
2828
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
2929
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
@@ -36,19 +36,19 @@
3636
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
3737
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3838
# SUCH DAMAGE.
39-
#
39+
#
4040
# $Id$
4141

42-
class ControlSlycot(Exception):
42+
class ControlSlycot(Exception):
4343
"""Exception for Slycot import. Used when we can't import a function
4444
from the slycot package"""
4545
pass
4646

47-
class ControlDimension(Exception):
47+
class ControlDimension(Exception):
4848
"""Raised when dimensions of system objects are not correct"""
4949
pass
5050

51-
class ControlArgument(Exception):
51+
class ControlArgument(Exception):
5252
"""Raised when arguments to a function are not correct"""
5353
pass
5454

control/frdata.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,7 @@ def _convertToFRD(sys, omega, inputs=1, outputs=1):
469469
sys.__class__)
470470

471471
def frd(*args):
472-
'''
472+
"""
473473
Construct a Frequency Response Data model, or convert a system
474474
475475
frd models store the (measured) frequency response of a system.
@@ -501,5 +501,5 @@ def frd(*args):
501501
See Also
502502
--------
503503
ss, tf
504-
'''
504+
"""
505505
return FRD(*args)

0 commit comments

Comments
 (0)