Skip to content

Commit 6b82c3c

Browse files
committed
Merge commit '195bec3' into call-method-mergeinto
2 parents 8c9571d + 195bec3 commit 6b82c3c

64 files changed

Lines changed: 8128 additions & 7167 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: 15 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -12,73 +12,35 @@ cache:
1212
- $HOME/.cache/pip
1313
- $HOME/.local
1414

15+
# Test against earliest supported (Python 3) release and latest stable release
1516
python:
16-
- "3.7"
17+
- "3.9"
1718
- "3.6"
18-
- "2.7"
1919

20-
# Test against multiple version of SciPy, with and without slycot
21-
#
22-
# Because there were significant changes in SciPy between v0 and v1, we
23-
# test against both of these using the Travis CI environment capability
24-
#
25-
# We also want to test with and without slycot
2620
env:
2721
- SCIPY=scipy SLYCOT=conda # default, with slycot via conda
2822
- SCIPY=scipy SLYCOT= # default, w/out slycot
29-
- SCIPY="scipy==0.19.1" SLYCOT= # legacy support, w/out slycot
3023

3124
# Add optional builds that test against latest version of slycot, python
3225
jobs:
3326
include:
34-
- name: "linux, Python 2.7, slycot=source"
35-
os: linux
36-
dist: xenial
37-
services: xvfb
27+
- name: "Python 3.9, slycot=source, array and matrix"
28+
python: "3.9"
29+
env: SCIPY=scipy SLYCOT=source PYTHON_CONTROL_ARRAY_AND_MATRIX=1
30+
# Because there were significant changes in SciPy between v0 and v1, we
31+
# also test against the latest v0 (without Slycot) for old pythons.
32+
# newer pythons should always use newer SciPy.
33+
- name: "Python 2.7, Scipy 0.19.1"
3834
python: "2.7"
39-
env: SCIPY=scipy SLYCOT=source
40-
- name: "linux, Python 3.7, slycot=source"
41-
os: linux
42-
dist: xenial
43-
services: xvfb
44-
python: "3.7"
45-
env: SCIPY=scipy SLYCOT=source
46-
- name: "linux, Python 3.8, slycot=source"
47-
os: linux
48-
dist: xenial
49-
services: xvfb
50-
python: "3.8"
51-
env: SCIPY=scipy SLYCOT=source
52-
- name: "use numpy matrix"
53-
dist: xenial
54-
services: xvfb
55-
python: "3.8"
56-
env: SCIPY=scipy SLYCOT=source PYTHON_CONTROL_STATESPACE_ARRAY=1
57-
58-
# Exclude combinations that are very unlikely (and don't work)
59-
exclude:
60-
- python: "3.7" # python3.7 should use latest scipy
35+
env: SCIPY="scipy==0.19.1" SLYCOT=
36+
- name: "Python 3.6, Scipy 0.19.1"
37+
python: "3.6"
6138
env: SCIPY="scipy==0.19.1" SLYCOT=
6239

6340
allow_failures:
64-
- name: "linux, Python 2.7, slycot=source"
65-
os: linux
66-
dist: xenial
67-
services: xvfb
68-
python: "2.7"
69-
env: SCIPY=scipy SLYCOT=source
70-
- name: "linux, Python 3.7, slycot=source"
71-
os: linux
72-
dist: xenial
73-
services: xvfb
74-
python: "3.7"
75-
env: SCIPY=scipy SLYCOT=source
76-
- name: "linux, Python 3.8, slycot=source"
77-
os: linux
78-
dist: xenial
79-
services: xvfb
80-
python: "3.8"
81-
env: SCIPY=scipy SLYCOT=source
41+
- env: SCIPY=scipy SLYCOT=source
42+
- env: SCIPY=scipy SLYCOT=source PYTHON_CONTROL_ARRAY_AND_MATRIX=1
43+
8244

8345
# install required system libraries
8446
before_install:

control/bdalg.py

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -302,14 +302,16 @@ def connect(sys, Q, inputv, outputv):
302302
sys : StateSpace Transferfunction
303303
System to be connected
304304
Q : 2D array
305-
Interconnection matrix. First column gives the input to be connected
306-
second column gives the output to be fed into this input. Negative
307-
values for the second column mean the feedback is negative, 0 means
308-
no connection is made. Inputs and outputs are indexed starting at 1.
305+
Interconnection matrix. First column gives the input to be connected.
306+
The second column gives the index of an output that is to be fed into
307+
that input. Each additional column gives the index of an additional
308+
input that may be optionally added to that input. Negative
309+
values mean the feedback is negative. A zero value is ignored. Inputs
310+
and outputs are indexed starting at 1 to communicate sign information.
309311
inputv : 1D array
310-
list of final external inputs
312+
list of final external inputs, indexed starting at 1
311313
outputv : 1D array
312-
list of final external outputs
314+
list of final external outputs, indexed starting at 1
313315
314316
Returns
315317
-------
@@ -324,16 +326,42 @@ def connect(sys, Q, inputv, outputv):
324326
>>> Q = [[1, 2], [2, -1]] # negative feedback interconnection
325327
>>> sysc = connect(sys, Q, [2], [1, 2])
326328
329+
Notes
330+
-----
331+
The :func:`~control.interconnect` function in the
332+
:ref:`input/output systems <iosys-module>` module allows the use
333+
of named signals and provides an alternative method for
334+
interconnecting multiple systems.
335+
327336
"""
337+
inputv, outputv, Q = np.asarray(inputv), np.asarray(outputv), np.asarray(Q)
338+
# check indices
339+
index_errors = (inputv - 1 > sys.inputs) | (inputv < 1)
340+
if np.any(index_errors):
341+
raise IndexError(
342+
"inputv index %s out of bounds" % inputv[np.where(index_errors)])
343+
index_errors = (outputv - 1 > sys.outputs) | (outputv < 1)
344+
if np.any(index_errors):
345+
raise IndexError(
346+
"outputv index %s out of bounds" % outputv[np.where(index_errors)])
347+
index_errors = (Q[:,0:1] - 1 > sys.inputs) | (Q[:,0:1] < 1)
348+
if np.any(index_errors):
349+
raise IndexError(
350+
"Q input index %s out of bounds" % Q[np.where(index_errors)])
351+
index_errors = (np.abs(Q[:,1:]) - 1 > sys.outputs)
352+
if np.any(index_errors):
353+
raise IndexError(
354+
"Q output index %s out of bounds" % Q[np.where(index_errors)])
355+
328356
# first connect
329357
K = np.zeros((sys.inputs, sys.outputs))
330358
for r in np.array(Q).astype(int):
331359
inp = r[0]-1
332360
for outp in r[1:]:
333-
if outp > 0 and outp <= sys.outputs:
334-
K[inp,outp-1] = 1.
335-
elif outp < 0 and -outp >= -sys.outputs:
361+
if outp < 0:
336362
K[inp,-outp-1] = -1.
363+
elif outp > 0:
364+
K[inp,outp-1] = 1.
337365
sys = sys.feedback(np.array(K), sign=1)
338366

339367
# now trim

control/config.py

Lines changed: 64 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
# Package level default values
1717
_control_defaults = {
18-
# No package level defaults (yet)
18+
'control.default_dt':0
1919
}
2020
defaults = dict(_control_defaults)
2121

@@ -59,6 +59,9 @@ def reset_defaults():
5959
from .statesp import _statesp_defaults
6060
defaults.update(_statesp_defaults)
6161

62+
from .iosys import _iosys_defaults
63+
defaults.update(_iosys_defaults)
64+
6265

6366
def _get_param(module, param, argval=None, defval=None, pop=False):
6467
"""Return the default value for a configuration option.
@@ -144,7 +147,7 @@ def use_numpy_matrix(flag=True, warn=True):
144147
Parameters
145148
----------
146149
flag : bool
147-
If flag is `True` (default), use the Numpy (soon to be deprecated)
150+
If flag is `True` (default), use the deprecated Numpy
148151
`matrix` class to represent matrices in the `~control.StateSpace`
149152
class and functions. If flat is `False`, then matrices are
150153
represented by a 2D `ndarray` object.
@@ -154,10 +157,15 @@ class and functions. If flat is `False`, then matrices are
154157
of the Numpy `matrix` class. Set `warn` to false to omit display of
155158
the warning message.
156159
160+
Notes
161+
-----
162+
Prior to release 0.9.x, the default type for 2D arrays is the Numpy
163+
`matrix` class. Starting in release 0.9.0, the default type for state
164+
space operations is a 2D array.
157165
"""
158166
if flag and warn:
159-
warnings.warn("Return type numpy.matrix is soon to be deprecated.",
160-
stacklevel=2)
167+
warnings.warn("Return type numpy.matrix is deprecated.",
168+
stacklevel=2, category=DeprecationWarning)
161169
set_defaults('statesp', use_numpy_matrix=flag)
162170

163171
def use_legacy_defaults(version):
@@ -166,9 +174,56 @@ def use_legacy_defaults(version):
166174
Parameters
167175
----------
168176
version : string
169-
version number of the defaults desired. Currently only supports `0.8.3`.
177+
Version number of the defaults desired. Ranges from '0.1' to '0.8.4'.
170178
"""
171-
if version == '0.8.3':
172-
use_numpy_matrix(True) # alternatively: set_defaults('statesp', use_numpy_matrix=True)
173-
else:
174-
raise ValueError('''version number not recognized. Possible values are: ['0.8.3']''')
179+
import re
180+
(major, minor, patch) = (None, None, None) # default values
181+
182+
# Early release tag format: REL-0.N
183+
match = re.match("REL-0.([12])", version)
184+
if match: (major, minor, patch) = (0, int(match.group(1)), 0)
185+
186+
# Early release tag format: control-0.Np
187+
match = re.match("control-0.([3-6])([a-d])", version)
188+
if match: (major, minor, patch) = \
189+
(0, int(match.group(1)), ord(match.group(2)) - ord('a') + 1)
190+
191+
# Early release tag format: v0.Np
192+
match = re.match("[vV]?0.([3-6])([a-d])", version)
193+
if match: (major, minor, patch) = \
194+
(0, int(match.group(1)), ord(match.group(2)) - ord('a') + 1)
195+
196+
# Abbreviated version format: vM.N or M.N
197+
match = re.match("([vV]?[0-9]).([0-9])", version)
198+
if match: (major, minor, patch) = \
199+
(int(match.group(1)), int(match.group(2)), 0)
200+
201+
# Standard version format: vM.N.P or M.N.P
202+
match = re.match("[vV]?([0-9]).([0-9]).([0-9])", version)
203+
if match: (major, minor, patch) = \
204+
(int(match.group(1)), int(match.group(2)), int(match.group(3)))
205+
206+
# Make sure we found match
207+
if major is None or minor is None:
208+
raise ValueError("Version number not recognized. Try M.N.P format.")
209+
210+
#
211+
# Go backwards through releases and reset defaults
212+
#
213+
214+
# Version 0.9.0:
215+
if major == 0 and minor < 9:
216+
# switched to 'array' as default for state space objects
217+
set_defaults('statesp', use_numpy_matrix=True)
218+
219+
# switched to 0 (=continuous) as default timestep
220+
set_defaults('control', default_dt=None)
221+
222+
# changed iosys naming conventions
223+
set_defaults('iosys', state_name_delim='.',
224+
duplicate_system_name_prefix='copy of ',
225+
duplicate_system_name_suffix='',
226+
linearized_system_name_prefix='',
227+
linearized_system_name_suffix='_linearized')
228+
229+
return (major, minor, patch)

control/dtime.py

Lines changed: 33 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -53,21 +53,19 @@
5353

5454
# Sample a continuous time system
5555
def sample_system(sysc, Ts, method='zoh', alpha=None, prewarp_frequency=None):
56-
"""Convert a continuous time system to discrete time
57-
58-
Creates a discrete time system from a continuous time system by
59-
sampling. Multiple methods of conversion are supported.
56+
"""
57+
Convert a continuous time system to discrete time by sampling
6058
6159
Parameters
6260
----------
63-
sysc : linsys
61+
sysc : LTI (StateSpace or TransferFunction)
6462
Continuous time system to be converted
65-
Ts : real
63+
Ts : real > 0
6664
Sampling period
6765
method : string
68-
Method to use for conversion: 'matched', 'tustin', 'zoh' (default)
66+
Method to use for conversion, e.g. 'bilinear', 'zoh' (default)
6967
70-
prewarp_frequency : float within [0, infinity)
68+
prewarp_frequency : real within [0, infinity)
7169
The frequency [rad/s] at which to match with the input continuous-
7270
time system's magnitude and phase
7371
@@ -78,13 +76,13 @@ def sample_system(sysc, Ts, method='zoh', alpha=None, prewarp_frequency=None):
7876
7977
Notes
8078
-----
81-
See `TransferFunction.sample` and `StateSpace.sample` for
79+
See :meth:`StateSpace.sample` or :meth:`TransferFunction.sample`` for
8280
further details.
8381
8482
Examples
8583
--------
8684
>>> sysc = TransferFunction([1], [1, 2, 1])
87-
>>> sysd = sample_system(sysc, 1, method='matched')
85+
>>> sysd = sample_system(sysc, 1, method='bilinear')
8886
"""
8987

9088
# Make sure we have a continuous time system
@@ -95,35 +93,39 @@ def sample_system(sysc, Ts, method='zoh', alpha=None, prewarp_frequency=None):
9593

9694

9795
def c2d(sysc, Ts, method='zoh', prewarp_frequency=None):
98-
'''
99-
Return a discrete-time system
96+
"""
97+
Convert a continuous time system to discrete time by sampling
10098
10199
Parameters
102100
----------
103-
sysc: LTI (StateSpace or TransferFunction), continuous
104-
System to be converted
101+
sysc : LTI (StateSpace or TransferFunction)
102+
Continuous time system to be converted
103+
Ts : real > 0
104+
Sampling period
105+
method : string
106+
Method to use for conversion, e.g. 'bilinear', 'zoh' (default)
105107
106-
Ts: number
107-
Sample time for the conversion
108+
prewarp_frequency : real within [0, infinity)
109+
The frequency [rad/s] at which to match with the input continuous-
110+
time system's magnitude and phase
108111
109-
method: string, optional
110-
Method to be applied,
111-
'zoh' Zero-order hold on the inputs (default)
112-
'foh' First-order hold, currently not implemented
113-
'impulse' Impulse-invariant discretization, currently not implemented
114-
'tustin' Bilinear (Tustin) approximation, only SISO
115-
'matched' Matched pole-zero method, only SISO
112+
Returns
113+
-------
114+
sysd : linsys
115+
Discrete time system, with sampling rate Ts
116+
117+
Notes
118+
-----
119+
See :meth:`StateSpace.sample` or :meth:`TransferFunction.sample`` for
120+
further details.
116121
117-
prewarp_frequency : float within [0, infinity)
118-
The frequency [rad/s] at which to match with the input continuous-
119-
time system's magnitude and phase
122+
Examples
123+
--------
124+
>>> sysc = TransferFunction([1], [1, 2, 1])
125+
>>> sysd = sample_system(sysc, 1, method='bilinear')
126+
"""
120127

121-
'''
122128
# Call the sample_system() function to do the work
123129
sysd = sample_system(sysc, Ts, method, prewarp_frequency)
124130

125-
# TODO: is this check needed? If sysc is StateSpace, sysd is too?
126-
if isinstance(sysc, StateSpace) and not isinstance(sysd, StateSpace):
127-
return _convertToStateSpace(sysd) # pragma: no cover
128-
129131
return sysd

0 commit comments

Comments
 (0)