Skip to content

Commit 6e3c2dd

Browse files
authored
Merge branch 'master' into statesp_constructor_doc
2 parents aa6e0df + a5094e2 commit 6e3c2dd

11 files changed

Lines changed: 473 additions & 108 deletions

File tree

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ __conda_*.txt
99
record.txt
1010
build.log
1111
*.egg-info/
12+
.eggs/
1213
.coverage
1314
doc/_build
1415
doc/generated
@@ -18,3 +19,7 @@ examples/.ipynb_checkpoints/
1819
.project
1920
Untitled*.ipynb
2021
*.idea/
22+
23+
# Files created by or for emacs (RMM, 29 Dec 2017)
24+
*~
25+
TAGS

.travis.yml

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,31 @@ cache:
99
- $HOME/.local
1010

1111
python:
12-
- "2.7"
13-
- "3.3"
14-
- "3.4"
12+
- "3.6"
1513
- "3.5"
14+
- "2.7"
15+
16+
# Test against multiple version of SciPy, with and without slycot
17+
#
18+
# Because there were significant changes in SciPy between v0 and v1, we
19+
# test against both of these using the Travis CI environment capability
20+
#
21+
# We also want to test with and without slycot
22+
env:
23+
- SCIPY=scipy SLYCOT=slycot # default, with slycot
24+
- SCIPY=scipy SLYCOT= # default, w/out slycot
25+
- SCIPY="scipy==0.19.1" SLYCOT= # legacy support, w/out slycot
1626

1727
# install required system libraries
1828
before_install:
29+
# Install gfortran for testing slycot; use apt-get instead of conda in
30+
# order to include the proper CXXABI dependency (updated in GCC 4.9)
31+
# Also need to include liblapack here, to make sure paths are right
32+
- if [[ "$SLYCOT" != "" ]]; then
33+
sudo apt-get update -qq;
34+
sudo apt-get install gfortran liblapack-dev;
35+
fi
36+
# Install display manager to allow testing of plotting functions
1937
- export DISPLAY=:99.0
2038
- sh -e /etc/init.d/xvfb start
2139
# use miniconda to install numpy/scipy, to avoid lengthy build from source
@@ -29,27 +47,30 @@ before_install:
2947
- hash -r
3048
- conda config --set always_yes yes --set changeps1 no
3149
- conda update -q conda
32-
# conda-build must be installed in the conda root environment
33-
- conda install conda-build
3450
- conda config --add channels python-control
3551
- conda info -a
3652
- conda create -q -n test-environment python="$TRAVIS_PYTHON_VERSION" pip coverage
3753
- source activate test-environment
38-
# coveralls not in conda repos
54+
# Make sure to look in the right place for python libraries (for slycot)
55+
- export LIBRARY_PATH="$HOME/miniconda/envs/test-environment/lib"
56+
# coveralls not in conda repos => install via pip instead
3957
- pip install coveralls
4058

4159
# Install packages
4260
install:
43-
- conda build --python "$TRAVIS_PYTHON_VERSION" conda-recipe
44-
- conda install control --use-local
61+
# Install packages needed by python-control
62+
- conda install $SCIPY matplotlib
63+
# Build slycot from source
64+
# For python 3, need to provide pointer to python library
65+
#! git clone https://github.com/repagh/Slycot.git slycot;
66+
- if [[ "$SLYCOT" != "" ]]; then
67+
git clone https://github.com/python-control/Slycot.git slycot;
68+
cd slycot; python setup.py install; cd ..;
69+
fi
4570

4671
# command to run tests
4772
script:
48-
# Before installing Slycot
49-
- python setup.py test
50-
51-
# Now, get and use Slycot
52-
- conda install slycot
73+
- 'if [ $SLYCOT != "" ]; then python -c "import slycot"; fi'
5374
- coverage run setup.py test
5475

5576
after_success:

conda-recipe/meta.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
package:
22
name: control
3+
version: {{ GIT_DESCRIBE_TAG }}
4+
5+
source:
6+
git_url: ../
37

48
build:
9+
number: {{ GIT_DESCRIBE_NUMBER }}
510
script:
611
- cd $RECIPE_DIR/..
712
- $PYTHON make_version.py

control/bdalg.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"""
5555

5656
import scipy as sp
57+
import numpy as np
5758
from . import xferfcn as tf
5859
from . import statesp as ss
5960
from . import frdata as frd
@@ -221,18 +222,18 @@ def feedback(sys1, sys2=1, sign=-1):
221222
"""
222223

223224
# Check for correct input types.
224-
if not isinstance(sys1, (int, float, complex, tf.TransferFunction,
225-
ss.StateSpace, frd.FRD)):
225+
if not isinstance(sys1, (int, float, complex, np.number,
226+
tf.TransferFunction, ss.StateSpace, frd.FRD)):
226227
raise TypeError("sys1 must be a TransferFunction, StateSpace " +
227228
"or FRD object, or a scalar.")
228-
if not isinstance(sys2, (int, float, complex, tf.TransferFunction,
229-
ss.StateSpace, frd.FRD)):
229+
if not isinstance(sys2, (int, float, complex, np.number,
230+
tf.TransferFunction, ss.StateSpace, frd.FRD)):
230231
raise TypeError("sys2 must be a TransferFunction, StateSpace " +
231232
"or FRD object, or a scalar.")
232233

233234
# If sys1 is a scalar, convert it to the appropriate LTI type so that we can
234235
# its feedback member function.
235-
if isinstance(sys1, (int, float, complex)):
236+
if isinstance(sys1, (int, float, complex, np.number)):
236237
if isinstance(sys2, tf.TransferFunction):
237238
sys1 = tf._convertToTransferFunction(sys1)
238239
elif isinstance(sys2, ss.StateSpace):

control/frdata.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"""
5050

5151
# External function declarations
52+
import numpy as np
5253
from numpy import angle, array, empty, ones, \
5354
real, imag, matrix, absolute, eye, linalg, where, dot
5455
from scipy.interpolate import splprep, splev
@@ -223,7 +224,7 @@ def __mul__(self, other):
223224
"""Multiply two LTI objects (serial connection)."""
224225

225226
# Convert the second argument to a transfer function.
226-
if isinstance(other, (int, float, complex)):
227+
if isinstance(other, (int, float, complex, np.number)):
227228
return FRD(self.fresp * other, self.omega,
228229
smooth=(self.ifunc is not None))
229230
else:
@@ -249,7 +250,7 @@ def __rmul__(self, other):
249250
"""Right Multiply two LTI objects (serial connection)."""
250251

251252
# Convert the second argument to an frd function.
252-
if isinstance(other, (int, float, complex)):
253+
if isinstance(other, (int, float, complex, np.number)):
253254
return FRD(self.fresp * other, self.omega,
254255
smooth=(self.ifunc is not None))
255256
else:
@@ -276,7 +277,7 @@ def __rmul__(self, other):
276277
def __truediv__(self, other):
277278
"""Divide two LTI objects."""
278279

279-
if isinstance(other, (int, float, complex)):
280+
if isinstance(other, (int, float, complex, np.number)):
280281
return FRD(self.fresp * (1/other), self.omega,
281282
smooth=(self.ifunc is not None))
282283
else:
@@ -299,7 +300,7 @@ def __div__(self, other):
299300
# TODO: Division of MIMO transfer function objects is not written yet.
300301
def __rtruediv__(self, other):
301302
"""Right divide two LTI objects."""
302-
if isinstance(other, (int, float, complex)):
303+
if isinstance(other, (int, float, complex, np.number)):
303304
return FRD(other / self.fresp, self.omega,
304305
smooth=(self.ifunc is not None))
305306
else:
@@ -453,7 +454,7 @@ def _convertToFRD(sys, omega, inputs=1, outputs=1):
453454

454455
return FRD(fresp, omega, smooth=True)
455456

456-
elif isinstance(sys, (int, float, complex)):
457+
elif isinstance(sys, (int, float, complex, np.number)):
457458
fresp = ones((outputs, inputs, len(omega)), dtype=float)*sys
458459
return FRD(fresp, omega, smooth=True)
459460

control/lti.py

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

15+
import numpy as np
1516
from numpy import absolute, real
1617

1718
__all__ = ['issiso', 'timebase', 'timebaseEqual', 'isdtime', 'isctime',
@@ -96,7 +97,7 @@ def dcgain(self):
9697

9798
# Test to see if a system is SISO
9899
def issiso(sys, strict=False):
99-
if isinstance(sys, (int, float, complex)) and not strict:
100+
if isinstance(sys, (int, float, complex, np.number)) and not strict:
100101
return True
101102
elif not isinstance(sys, LTI):
102103
raise ValueError("Object is not an LTI system")
@@ -114,7 +115,7 @@ def timebase(sys, strict=True):
114115
set to False, dt = True will be returned as 1.
115116
"""
116117
# System needs to be either a constant or an LTI system
117-
if isinstance(sys, (int, float, complex)):
118+
if isinstance(sys, (int, float, complex, np.number)):
118119
return None
119120
elif not isinstance(sys, LTI):
120121
raise ValueError("Timebase not defined")
@@ -162,7 +163,7 @@ def isdtime(sys, strict=False):
162163
"""
163164

164165
# Check to see if this is a constant
165-
if isinstance(sys, (int, float, complex)):
166+
if isinstance(sys, (int, float, complex, np.number)):
166167
# OK as long as strict checking is off
167168
return True if not strict else False
168169

@@ -187,7 +188,7 @@ def isctime(sys, strict=False):
187188
"""
188189

189190
# Check to see if this is a constant
190-
if isinstance(sys, (int, float, complex)):
191+
if isinstance(sys, (int, float, complex, np.number)):
191192
# OK as long as strict checking is off
192193
return True if not strict else False
193194

control/statesp.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
from numpy.random import rand, randn
6060
from numpy.linalg import solve, eigvals, matrix_rank
6161
from numpy.linalg.linalg import LinAlgError
62+
import scipy as sp
6263
from scipy.signal import lti, cont2discrete
6364
# from exceptions import Exception
6465
import warnings
@@ -224,7 +225,7 @@ def __add__(self, other):
224225
"""Add two LTI systems (parallel connection)."""
225226

226227
# Check for a couple of special cases
227-
if (isinstance(other, (int, float, complex))):
228+
if (isinstance(other, (int, float, complex, np.number))):
228229
# Just adding a scalar; put it in the D matrix
229230
A, B, C = self.A, self.B, self.C;
230231
D = self.D + other;
@@ -281,7 +282,7 @@ def __mul__(self, other):
281282
"""Multiply two LTI objects (serial connection)."""
282283

283284
# Check for a couple of special cases
284-
if isinstance(other, (int, float, complex)):
285+
if isinstance(other, (int, float, complex, np.number)):
285286
# Just multiplying by a scalar; change the output
286287
A, B = self.A, self.B
287288
C = self.C * other
@@ -322,7 +323,7 @@ def __rmul__(self, other):
322323
"""Right multiply two LTI objects (serial connection)."""
323324

324325
# Check for a couple of special cases
325-
if isinstance(other, (int, float, complex)):
326+
if isinstance(other, (int, float, complex, np.number)):
326327
# Just multiplying by a scalar; change the input
327328
A, C = self.A, self.C;
328329
B = self.B * other;
@@ -705,11 +706,10 @@ def _convertToStateSpace(sys, **kw):
705706
# TODO: do we want to squeeze first and check dimenations?
706707
# I think this will fail if num and den aren't 1-D after
707708
# the squeeze
708-
lti_sys = lti(squeeze(sys.num), squeeze(sys.den))
709-
return StateSpace(lti_sys.A, lti_sys.B, lti_sys.C, lti_sys.D,
710-
sys.dt)
709+
A, B, C, D = sp.signal.tf2ss(squeeze(sys.num), squeeze(sys.den))
710+
return StateSpace(A, B, C, D, sys.dt)
711711

712-
elif isinstance(sys, (int, float, complex)):
712+
elif isinstance(sys, (int, float, complex, np.number)):
713713
if "inputs" in kw:
714714
inputs = kw["inputs"]
715715
else:
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# input_element_int_test.py
2+
#
3+
# Author: Kangwon Lee (kangwonlee)
4+
# Date: 22 Oct 2017
5+
#
6+
# Unit tests contributed as part of PR #158, "SISO tf() may not work
7+
# with numpy arrays with numpy.int elements"
8+
#
9+
# Modified:
10+
# * 29 Dec 2017, RMM - updated file name and added header
11+
12+
import unittest
13+
import numpy as np
14+
import control as ctl
15+
16+
class TestTfInputIntElement(unittest.TestCase):
17+
# currently these do not pass
18+
def test_tf_den_with_numpy_int_element(self):
19+
num = 1
20+
den = np.convolve([1, 2, 1], [1, 1, 1])
21+
22+
sys = ctl.tf(num, den)
23+
24+
self.assertAlmostEqual(1.0, ctl.dcgain(sys))
25+
26+
def test_tf_num_with_numpy_int_element(self):
27+
num = np.convolve([1], [1, 1])
28+
den = np.convolve([1, 2, 1], [1, 1, 1])
29+
30+
sys = ctl.tf(num, den)
31+
32+
self.assertAlmostEqual(1.0, ctl.dcgain(sys))
33+
34+
# currently these pass
35+
def test_tf_input_with_int_element_works(self):
36+
num = 1
37+
den = np.convolve([1.0, 2, 1], [1, 1, 1])
38+
39+
sys = ctl.tf(num, den)
40+
41+
self.assertAlmostEqual(1.0, ctl.dcgain(sys))
42+
43+
def test_ss_input_with_int_element(self):
44+
ident = np.matrix(np.identity(2), dtype=int)
45+
a = np.matrix([[0, 1],
46+
[-1, -2]], dtype=int) * ident
47+
b = np.matrix([[0],
48+
[1]], dtype=int)
49+
c = np.matrix([[0, 1]], dtype=int)
50+
d = 0
51+
52+
sys = ctl.ss(a, b, c, d)
53+
sys2 = ctl.ss2tf(sys)
54+
self.assertAlmostEqual(ctl.dcgain(sys), ctl.dcgain(sys2))

0 commit comments

Comments
 (0)