Skip to content

Commit 9a7c94e

Browse files
authored
Merge branch 'master' into implement_repr
2 parents 6a200db + 8366806 commit 9a7c94e

25 files changed

Lines changed: 876 additions & 530 deletions

.travis.yml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ python:
1919

2020
# Test against multiple version of SciPy, with and without slycot
2121
#
22-
# Because there were significant changes in SciPy between v0 and v1, we
22+
# Because there were significant changes in SciPy between v0 and v1, we
2323
# test against both of these using the Travis CI environment capability
2424
#
2525
# We also want to test with and without slycot
@@ -84,7 +84,6 @@ before_install:
8484
sudo apt-get update -qq;
8585
sudo apt-get install liblapack-dev libblas-dev;
8686
sudo apt-get install gfortran;
87-
sudo apt-get install cmake;
8887
fi
8988
# use miniconda to install numpy/scipy, to avoid lengthy build from source
9089
- if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then
@@ -104,7 +103,7 @@ before_install:
104103
# Install scikit-build for the build process if slycot is being used
105104
- if [[ "$SLYCOT" = "source" ]]; then
106105
conda install openblas;
107-
conda install -c conda-forge scikit-build;
106+
conda install -c conda-forge cmake scikit-build;
108107
fi
109108
# Make sure to look in the right place for python libraries (for slycot)
110109
- export LIBRARY_PATH="$HOME/miniconda/envs/test-environment/lib"
@@ -130,7 +129,7 @@ install:
130129
# command to run tests
131130
script:
132131
- 'if [ $SLYCOT != "" ]; then python -c "import slycot"; fi'
133-
- coverage run -m pytest --disable-warnings control/tests
132+
- coverage run -m pytest control/tests
134133

135134
# only run examples if Slycot is install
136135
# set PYTHONPATH for examples

control/config.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
__all__ = ['defaults', 'set_defaults', 'reset_defaults',
1313
'use_matlab_defaults', 'use_fbs_defaults',
14-
'use_numpy_matrix']
14+
'use_legacy_defaults', 'use_numpy_matrix']
1515

1616
# Package level default values
1717
_control_defaults = {
@@ -53,6 +53,9 @@ def reset_defaults():
5353
from .rlocus import _rlocus_defaults
5454
defaults.update(_rlocus_defaults)
5555

56+
from .xferfcn import _xferfcn_defaults
57+
defaults.update(_xferfcn_defaults)
58+
5659
from .statesp import _statesp_defaults
5760
defaults.update(_statesp_defaults)
5861

@@ -156,3 +159,16 @@ class and functions. If flat is `False`, then matrices are
156159
warnings.warn("Return type numpy.matrix is soon to be deprecated.",
157160
stacklevel=2)
158161
set_defaults('statesp', use_numpy_matrix=flag)
162+
163+
def use_legacy_defaults(version):
164+
""" Sets the defaults to whatever they were in a given release.
165+
166+
Parameters
167+
----------
168+
version : string
169+
version number of the defaults desired. Currently only supports `0.8.3`.
170+
"""
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']''')

control/dtime.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
__all__ = ['sample_system', 'c2d']
5353

5454
# Sample a continuous time system
55-
def sample_system(sysc, Ts, method='zoh', alpha=None):
55+
def sample_system(sysc, Ts, method='zoh', alpha=None, prewarp_frequency=None):
5656
"""Convert a continuous time system to discrete time
5757
5858
Creates a discrete time system from a continuous time system by
@@ -67,6 +67,10 @@ def sample_system(sysc, Ts, method='zoh', alpha=None):
6767
method : string
6868
Method to use for conversion: 'matched', 'tustin', 'zoh' (default)
6969
70+
prewarp_frequency : float within [0, infinity)
71+
The frequency [rad/s] at which to match with the input continuous-
72+
time system's magnitude and phase
73+
7074
Returns
7175
-------
7276
sysd : linsys
@@ -87,10 +91,10 @@ def sample_system(sysc, Ts, method='zoh', alpha=None):
8791
if not isctime(sysc):
8892
raise ValueError("First argument must be continuous time system")
8993

90-
return sysc.sample(Ts, method, alpha)
94+
return sysc.sample(Ts, method, alpha, prewarp_frequency)
9195

9296

93-
def c2d(sysc, Ts, method='zoh'):
97+
def c2d(sysc, Ts, method='zoh', prewarp_frequency=None):
9498
'''
9599
Return a discrete-time system
96100
@@ -109,9 +113,14 @@ def c2d(sysc, Ts, method='zoh'):
109113
'impulse' Impulse-invariant discretization, currently not implemented
110114
'tustin' Bilinear (Tustin) approximation, only SISO
111115
'matched' Matched pole-zero method, only SISO
116+
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
120+
112121
'''
113122
# Call the sample_system() function to do the work
114-
sysd = sample_system(sysc, Ts, method)
123+
sysd = sample_system(sysc, Ts, method, prewarp_frequency)
115124

116125
# TODO: is this check needed? If sysc is StateSpace, sysd is too?
117126
if isinstance(sysc, StateSpace) and not isinstance(sysd, StateSpace):

control/frdata.py

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -409,17 +409,30 @@ def _evalfr(self, omega):
409409

410410
# Method for generating the frequency response of the system
411411
def freqresp(self, omega):
412-
"""Evaluate a transfer function at a list of angular frequencies.
413-
414-
mag, phase, omega = self.freqresp(omega)
415-
416-
reports the value of the magnitude, phase, and angular frequency of
417-
the transfer function matrix evaluated at s = i * omega, where omega
418-
is a list of angular frequencies, and is a sorted version of the input
419-
omega.
420-
412+
"""Evaluate the frequency response at a list of angular frequencies.
413+
414+
Reports the value of the magnitude, phase, and angular frequency of
415+
the requency response evaluated at omega, where omega is a list of
416+
angular frequencies, and is a sorted version of the input omega.
417+
418+
Parameters
419+
----------
420+
omega : array_like
421+
A list of frequencies in radians/sec at which the system should be
422+
evaluated. The list can be either a python list or a numpy array
423+
and will be sorted before evaluation.
424+
425+
Returns
426+
-------
427+
mag : (self.outputs, self.inputs, len(omega)) ndarray
428+
The magnitude (absolute value, not dB or log10) of the system
429+
frequency response.
430+
phase : (self.outputs, self.inputs, len(omega)) ndarray
431+
The wrapped phase in radians of the system frequency response.
432+
omega : ndarray or list or tuple
433+
The list of sorted frequencies at which the response was
434+
evaluated.
421435
"""
422-
423436
# Preallocate outputs.
424437
numfreq = len(omega)
425438
mag = empty((self.outputs, self.inputs, numfreq))

control/freqplot.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -822,10 +822,10 @@ def default_frequency_range(syslist, Hz=None, number_of_samples=None,
822822

823823
# Set the range to be an order of magnitude beyond any features
824824
if number_of_samples:
825-
omega = sp.logspace(
825+
omega = np.logspace(
826826
lsp_min, lsp_max, num=number_of_samples, endpoint=True)
827827
else:
828-
omega = sp.logspace(lsp_min, lsp_max, endpoint=True)
828+
omega = np.logspace(lsp_min, lsp_max, endpoint=True)
829829
return omega
830830

831831

control/iosys.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1645,8 +1645,10 @@ def rootfun(z):
16451645
# and were processed above.
16461646

16471647
# Get the states and inputs that were not listed as fixed
1648-
state_vars = np.delete(np.array(range(nstates)), ix)
1649-
input_vars = np.delete(np.array(range(ninputs)), iu)
1648+
state_vars = (range(nstates) if not len(ix)
1649+
else np.delete(np.array(range(nstates)), ix))
1650+
input_vars = (range(ninputs) if not len(iu)
1651+
else np.delete(np.array(range(ninputs)), iu))
16501652

16511653
# Set the outputs and derivs that will serve as constraints
16521654
output_vars = np.array(iy)

control/lti.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ def isdtime(self, strict=False):
5555
Parameters
5656
----------
5757
strict: bool, optional
58-
If strict is True, make sure that timebase is not None. Default
59-
is False.
58+
If strict is True, make sure that timebase is not None. Default
59+
is False.
6060
"""
6161

6262
# If no timebase is given, answer depends on strict flag
@@ -75,8 +75,8 @@ def isctime(self, strict=False):
7575
sys : LTI system
7676
System to be checked
7777
strict: bool, optional
78-
If strict is True, make sure that timebase is not None. Default
79-
is False.
78+
If strict is True, make sure that timebase is not None. Default
79+
is False.
8080
"""
8181
# If no timebase is given, answer depends on strict flag
8282
if self.dt is None:
@@ -421,6 +421,7 @@ def evalfr(sys, x):
421421
return sys.horner(x)[0][0]
422422
return sys.horner(x)
423423

424+
424425
def freqresp(sys, omega):
425426
"""
426427
Frequency response of an LTI system at multiple angular frequencies.
@@ -430,13 +431,20 @@ def freqresp(sys, omega):
430431
sys: StateSpace or TransferFunction
431432
Linear system
432433
omega: array_like
433-
List of frequencies
434+
A list of frequencies in radians/sec at which the system should be
435+
evaluated. The list can be either a python list or a numpy array
436+
and will be sorted before evaluation.
434437
435438
Returns
436439
-------
437-
mag: ndarray
438-
phase: ndarray
439-
omega: list, tuple, or ndarray
440+
mag : (self.outputs, self.inputs, len(omega)) ndarray
441+
The magnitude (absolute value, not dB or log10) of the system
442+
frequency response.
443+
phase : (self.outputs, self.inputs, len(omega)) ndarray
444+
The wrapped phase in radians of the system frequency response.
445+
omega : ndarray or list or tuple
446+
The list of sorted frequencies at which the response was
447+
evaluated.
440448
441449
See Also
442450
--------
@@ -472,9 +480,9 @@ def freqresp(sys, omega):
472480
#>>> # frequency response from the 1st input to the 2nd output, for
473481
#>>> # s = 0.1i, i, 10i.
474482
"""
475-
476483
return sys.freqresp(omega)
477484

485+
478486
def dcgain(sys):
479487
"""Return the zero-frequency (or DC) gain of the given system
480488

control/margins.py

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
"""margin.py
1+
"""margins.py
22
33
Functions for computing stability margins and related functions.
44
55
Routines in this module:
66
7-
margin.stability_margins
8-
margin.phase_crossover_frequencies
9-
margin.margin
7+
margins.stability_margins
8+
margins.phase_crossover_frequencies
9+
margins.margin
1010
"""
1111

1212
# Python 3 compatibility (needs to go here)
@@ -211,41 +211,40 @@ def stability_margins(sysdata, returnall=False, epsw=0.0):
211211

212212
else:
213213
# a bit coarse, have the interpolated frd evaluated again
214-
def mod(w):
215-
"""to give the function to calculate |G(jw)| = 1"""
214+
def _mod(w):
215+
"""Calculate |G(jw)| - 1"""
216216
return np.abs(sys._evalfr(w)[0][0]) - 1
217217

218-
def arg(w):
219-
"""function to calculate the phase angle at -180 deg"""
218+
def _arg(w):
219+
"""Calculate the phase angle at -180 deg"""
220220
return np.angle(-sys._evalfr(w)[0][0])
221221

222-
def dstab(w):
223-
"""function to calculate the distance from -1 point"""
222+
def _dstab(w):
223+
"""Calculate the distance from -1 point"""
224224
return np.abs(sys._evalfr(w)[0][0] + 1.)
225225

226226
# Find all crossings, note that this depends on omega having
227227
# a correct range
228-
widx = np.where(np.diff(np.sign(mod(sys.omega))))[0]
228+
widx = np.where(np.diff(np.sign(_mod(sys.omega))))[0]
229229
wc = np.array(
230-
[ sp.optimize.brentq(mod, sys.omega[i], sys.omega[i+1])
231-
for i in widx if i+1 < len(sys.omega)])
230+
[sp.optimize.brentq(_mod, sys.omega[i], sys.omega[i+1])
231+
for i in widx])
232232

233233
# find the phase crossings ang(H(jw) == -180
234-
widx = np.where(np.diff(np.sign(arg(sys.omega))))[0]
234+
widx = np.where(np.diff(np.sign(_arg(sys.omega))))[0]
235235
widx = widx[np.real(sys._evalfr(sys.omega[widx])[0][0]) <= 0]
236236
w_180 = np.array(
237-
[ sp.optimize.brentq(arg, sys.omega[i], sys.omega[i+1])
238-
for i in widx if i+1 < len(sys.omega) ])
237+
[sp.optimize.brentq(_arg, sys.omega[i], sys.omega[i+1])
238+
for i in widx])
239239

240240
# find all stab margins?
241-
widx = np.where(np.diff(np.sign(np.diff(dstab(sys.omega)))))[0]
242-
wstab = np.array([ sp.optimize.minimize_scalar(
243-
dstab, bracket=(sys.omega[i], sys.omega[i+1])).x
244-
for i in widx if i+1 < len(sys.omega) and
245-
np.diff(np.diff(dstab(sys.omega[i-1:i+2])))[0] > 0 ])
246-
wstab = wstab[(wstab >= sys.omega[0]) *
247-
(wstab <= sys.omega[-1])]
248-
241+
widx, = np.where(np.diff(np.sign(np.diff(_dstab(sys.omega)))) > 0)
242+
wstab = np.array(
243+
[sp.optimize.minimize_scalar(_dstab,
244+
bracket=(sys.omega[i], sys.omega[i+1])
245+
).x
246+
for i in widx])
247+
wstab = wstab[(wstab >= sys.omega[0]) * (wstab <= sys.omega[-1])]
249248

250249
# margins, as iterables, converted frdata and xferfcn calculations to
251250
# vector for this
@@ -254,13 +253,13 @@ def dstab(w):
254253
GM = 1.0/gain_w_180
255254
SM = np.abs(sys._evalfr(wstab)[0][0]+1)
256255
PM = np.remainder(np.angle(sys._evalfr(wc)[0][0], deg=True), 360.0) - 180.0
257-
256+
258257
if returnall:
259258
return GM, PM, SM, w_180, wc, wstab
260259
else:
261260
if GM.shape[0] and not np.isinf(GM).all():
262261
with np.errstate(all='ignore'):
263-
gmidx = np.where(np.abs(np.log(GM)) ==
262+
gmidx = np.where(np.abs(np.log(GM)) ==
264263
np.min(np.abs(np.log(GM))))
265264
else:
266265
gmidx = -1
@@ -276,7 +275,6 @@ def dstab(w):
276275

277276

278277
# Contributed by Steffen Waldherr <waldherr@ist.uni-stuttgart.de>
279-
#! TODO - need to add test functions
280278
def phase_crossover_frequencies(sys):
281279
"""Compute frequencies and gains at intersections with real axis
282280
in Nyquist plot.

0 commit comments

Comments
 (0)