Skip to content

Commit e298c21

Browse files
authored
Merge pull request #1 from python-control/master
test
2 parents a4b4c43 + 66bee9d commit e298c21

54 files changed

Lines changed: 3281 additions & 401 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: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,23 +24,56 @@ python:
2424
#
2525
# We also want to test with and without slycot
2626
env:
27-
- SCIPY=scipy SLYCOT=slycot # default, with slycot
28-
- SCIPY=scipy SLYCOT= # default, w/out slycot
29-
- SCIPY="scipy==0.19.1" SLYCOT= # legacy support, w/out slycot
27+
- SCIPY=scipy SLYCOT=conda # default, with slycot via conda
28+
- SCIPY=scipy SLYCOT= # default, w/out slycot
29+
- SCIPY="scipy==0.19.1" SLYCOT= # legacy support, w/out slycot
30+
31+
# Add optional builds that test against latest version of slycot
32+
jobs:
33+
include:
34+
- name: "linux, Python 2.7, slycot=source"
35+
os: linux
36+
dist: xenial
37+
services: xvfb
38+
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
3046

31-
# Exclude combinations that are very unlikely (and don't work)
3247
matrix:
48+
# Exclude combinations that are very unlikely (and don't work)
3349
exclude:
34-
- python: "3.7" # python3.7 should use latest scipy
50+
- python: "3.7" # python3.7 should use latest scipy
3551
env: SCIPY="scipy==0.19.1" SLYCOT=
3652

53+
allow_failures:
54+
- name: "linux, Python 2.7, slycot=source"
55+
os: linux
56+
dist: xenial
57+
services: xvfb
58+
python: "2.7"
59+
env: SCIPY=scipy SLYCOT=source
60+
- name: "linux, Python 3.7, slycot=source"
61+
os: linux
62+
dist: xenial
63+
services: xvfb
64+
python: "3.7"
65+
env: SCIPY=scipy SLYCOT=source
66+
3767
# install required system libraries
3868
before_install:
3969
# Install gfortran for testing slycot; use apt-get instead of conda in
4070
# order to include the proper CXXABI dependency (updated in GCC 4.9)
41-
- if [[ "$SLYCOT" != "" ]]; then
71+
# Note: these commands should match the slycot .travis.yml configuration
72+
- if [[ "$SLYCOT" = "source" ]]; then
4273
sudo apt-get update -qq;
74+
sudo apt-get install liblapack-dev libblas-dev;
4375
sudo apt-get install gfortran;
76+
sudo apt-get install cmake;
4477
fi
4578
# use miniconda to install numpy/scipy, to avoid lengthy build from source
4679
- if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then
@@ -57,9 +90,8 @@ before_install:
5790
- conda info -a
5891
- conda create -q -n test-environment python="$TRAVIS_PYTHON_VERSION" pip coverage
5992
- source activate test-environment
60-
# Install openblas if slycot is being used
61-
# also install scikit-build for the build process
62-
- if [[ "$SLYCOT" != "" ]]; then
93+
# Install scikit-build for the build process if slycot is being used
94+
- if [[ "$SLYCOT" = "source" ]]; then
6395
conda install openblas;
6496
conda install -c conda-forge scikit-build;
6597
fi
@@ -72,13 +104,15 @@ before_install:
72104
install:
73105
# Install packages needed by python-control
74106
- conda install $SCIPY matplotlib
75-
# Build slycot from source
76-
# For python 3, need to provide pointer to python library
77-
# Use "Unix Makefiles" as generator, because Ninja cannot handle Fortran
78-
#! git clone https://github.com/repagh/Slycot.git slycot;
79-
- if [[ "$SLYCOT" != "" ]]; then
107+
108+
# Figure out how to build slycot
109+
# source: use "Unix Makefiles" as generator; Ninja cannot handle Fortran
110+
# conda: use pre-compiled version of slycot on conda-forge
111+
- if [[ "$SLYCOT" = "source" ]]; then
80112
git clone https://github.com/python-control/Slycot.git slycot;
81113
cd slycot; python setup.py install -G "Unix Makefiles"; cd ..;
114+
elif [[ "$SLYCOT" = "conda" ]]; then
115+
conda install -c conda-forge slycot;
82116
fi
83117

84118
# command to run tests

control/bdalg.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,12 @@
6262

6363

6464
def series(sys1, *sysn):
65-
"""Return the series connection (... \* sys3 \*) sys2 \* sys1
65+
"""Return the series connection (sysn \\* ... \\*) sys2 \\* sys1
6666
6767
Parameters
6868
----------
6969
sys1 : scalar, StateSpace, TransferFunction, or FRD
70-
sysn : other scalars, StateSpaces, TransferFunctions, or FRDs
70+
*sysn : other scalars, StateSpaces, TransferFunctions, or FRDs
7171
7272
Returns
7373
-------
@@ -108,7 +108,7 @@ def series(sys1, *sysn):
108108

109109
def parallel(sys1, *sysn):
110110
"""
111-
Return the parallel connection sys1 + sys2 (+ sys3 + ...)
111+
Return the parallel connection sys1 + sys2 (+ ... + sysn)
112112
113113
Parameters
114114
----------
@@ -263,7 +263,7 @@ def append(*sys):
263263
264264
Parameters
265265
----------
266-
sys1, sys2, ... sysn: StateSpace or Transferfunction
266+
sys1, sys2, ..., sysn: StateSpace or Transferfunction
267267
LTI systems to combine
268268
269269

control/freqplot.py

Lines changed: 83 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def bode_plot(syslist, omega=None,
101101
If True, plot phase in degrees (else radians). Default value (True)
102102
config.defaults['bode.deg']
103103
Plot : bool
104-
If True, plot magnitude and phase
104+
If True (default), plot magnitude and phase
105105
omega_limits: tuple, list, ... of two values
106106
Limits of the to generate frequency vector.
107107
If Hz=True the limits are in Hz otherwise in rad/s.
@@ -110,8 +110,10 @@ def bode_plot(syslist, omega=None,
110110
config.defaults['freqplot.number_of_samples'].
111111
margins : bool
112112
If True, plot gain and phase margin.
113-
\*args, \**kwargs:
114-
Additional options to matplotlib (color, linestyle, etc)
113+
*args
114+
Additional arguments for :func:`matplotlib.plot` (color, linestyle, etc)
115+
**kwargs:
116+
Additional keywords (passed to `matplotlib`)
115117
116118
Returns
117119
-------
@@ -139,7 +141,7 @@ def bode_plot(syslist, omega=None,
139141
140142
2. If a discrete time model is given, the frequency response is plotted
141143
along the upper branch of the unit circle, using the mapping z = exp(j
142-
\omega dt) where omega ranges from 0 to pi/dt and dt is the discrete
144+
\\omega dt) where omega ranges from 0 to pi/dt and dt is the discrete
143145
timebase. If not timebase is specified (dt = True), dt is set to 1.
144146
145147
Examples
@@ -450,8 +452,10 @@ def nyquist_plot(syslist, omega=None, Plot=True, color=None,
450452
Used to specify the color of the plot
451453
labelFreq : int
452454
Label every nth frequency on the plot
453-
\*args, \**kwargs:
454-
Additional options to matplotlib (color, linestyle, etc)
455+
*args
456+
Additional arguments for :func:`matplotlib.plot` (color, linestyle, etc)
457+
**kwargs:
458+
Additional keywords (passed to `matplotlib`)
455459
456460
Returns
457461
-------
@@ -554,7 +558,7 @@ def nyquist_plot(syslist, omega=None, Plot=True, color=None,
554558
#
555559

556560
# TODO: think about how (and whether) to handle lists of systems
557-
def gangof4_plot(P, C, omega=None):
561+
def gangof4_plot(P, C, omega=None, **kwargs):
558562
"""Plot the "Gang of 4" transfer functions for a system
559563
560564
Generates a 2x2 plot showing the "Gang of 4" sensitivity functions
@@ -575,58 +579,79 @@ def gangof4_plot(P, C, omega=None):
575579
# TODO: Add MIMO go4 plots.
576580
raise NotImplementedError(
577581
"Gang of four is currently only implemented for SISO systems.")
578-
else:
579582

580-
# Select a default range if none is provided
581-
# TODO: This needs to be made more intelligent
582-
if omega is None:
583-
omega = default_frequency_range((P, C))
584-
585-
# Compute the senstivity functions
586-
L = P * C
587-
S = feedback(1, L)
588-
T = L * S
589-
590-
# Set up the axes with labels so that multiple calls to
591-
# gangof4_plot will superimpose the data. See details in bode_plot.
592-
plot_axes = {'t': None, 's': None, 'ps': None, 'cs': None}
593-
for ax in plt.gcf().axes:
594-
label = ax.get_label()
595-
if label.startswith('control-gangof4-'):
596-
key = label[len('control-gangof4-'):]
597-
if key not in plot_axes:
598-
raise RuntimeError(
599-
"unknown gangof4 axis type '{}'".format(label))
600-
plot_axes[key] = ax
601-
602-
# if any of the axes are missing, start from scratch
603-
if any((ax is None for ax in plot_axes.values())):
604-
plt.clf()
605-
plot_axes = {'t': plt.subplot(221, label='control-gangof4-t'),
606-
'ps': plt.subplot(222, label='control-gangof4-ps'),
607-
'cs': plt.subplot(223, label='control-gangof4-cs'),
608-
's': plt.subplot(224, label='control-gangof4-s')}
609-
610-
#
611-
# Plot the four sensitivity functions
612-
#
613-
614-
# TODO: Need to add in the mag = 1 lines
615-
mag_tmp, phase_tmp, omega = T.freqresp(omega)
616-
mag = np.squeeze(mag_tmp)
617-
plot_axes['t'].loglog(omega, mag)
618-
619-
mag_tmp, phase_tmp, omega = (P * S).freqresp(omega)
620-
mag = np.squeeze(mag_tmp)
621-
plot_axes['ps'].loglog(omega, mag)
622-
623-
mag_tmp, phase_tmp, omega = (C * S).freqresp(omega)
624-
mag = np.squeeze(mag_tmp)
625-
plot_axes['cs'].loglog(omega, mag)
626-
627-
mag_tmp, phase_tmp, omega = S.freqresp(omega)
628-
mag = np.squeeze(mag_tmp)
629-
plot_axes['s'].loglog(omega, mag)
583+
# Get the default parameter values
584+
dB = config._get_param('bode', 'dB', kwargs, _bode_defaults, pop=True)
585+
Hz = config._get_param('bode', 'Hz', kwargs, _bode_defaults, pop=True)
586+
grid = config._get_param('bode', 'grid', kwargs, _bode_defaults, pop=True)
587+
588+
# Select a default range if none is provided
589+
# TODO: This needs to be made more intelligent
590+
if omega is None:
591+
omega = default_frequency_range((P, C))
592+
593+
# Compute the senstivity functions
594+
L = P * C
595+
S = feedback(1, L)
596+
T = L * S
597+
598+
# Set up the axes with labels so that multiple calls to
599+
# gangof4_plot will superimpose the data. See details in bode_plot.
600+
plot_axes = {'t': None, 's': None, 'ps': None, 'cs': None}
601+
for ax in plt.gcf().axes:
602+
label = ax.get_label()
603+
if label.startswith('control-gangof4-'):
604+
key = label[len('control-gangof4-'):]
605+
if key not in plot_axes:
606+
raise RuntimeError(
607+
"unknown gangof4 axis type '{}'".format(label))
608+
plot_axes[key] = ax
609+
610+
# if any of the axes are missing, start from scratch
611+
if any((ax is None for ax in plot_axes.values())):
612+
plt.clf()
613+
plot_axes = {'s': plt.subplot(221, label='control-gangof4-s'),
614+
'ps': plt.subplot(222, label='control-gangof4-ps'),
615+
'cs': plt.subplot(223, label='control-gangof4-cs'),
616+
't': plt.subplot(224, label='control-gangof4-t')}
617+
618+
#
619+
# Plot the four sensitivity functions
620+
#
621+
omega_plot = omega / (2. * math.pi) if Hz else omega
622+
623+
# TODO: Need to add in the mag = 1 lines
624+
mag_tmp, phase_tmp, omega = S.freqresp(omega)
625+
mag = np.squeeze(mag_tmp)
626+
plot_axes['s'].loglog(omega_plot, 20 * np.log10(mag) if dB else mag)
627+
plot_axes['s'].set_ylabel("$|S|$")
628+
plot_axes['s'].tick_params(labelbottom=False)
629+
plot_axes['s'].grid(grid, which='both')
630+
631+
mag_tmp, phase_tmp, omega = (P * S).freqresp(omega)
632+
mag = np.squeeze(mag_tmp)
633+
plot_axes['ps'].loglog(omega_plot, 20 * np.log10(mag) if dB else mag)
634+
plot_axes['ps'].tick_params(labelbottom=False)
635+
plot_axes['ps'].set_ylabel("$|PS|$")
636+
plot_axes['ps'].grid(grid, which='both')
637+
638+
mag_tmp, phase_tmp, omega = (C * S).freqresp(omega)
639+
mag = np.squeeze(mag_tmp)
640+
plot_axes['cs'].loglog(omega_plot, 20 * np.log10(mag) if dB else mag)
641+
plot_axes['cs'].set_xlabel(
642+
"Frequency (Hz)" if Hz else "Frequency (rad/sec)")
643+
plot_axes['cs'].set_ylabel("$|CS|$")
644+
plot_axes['cs'].grid(grid, which='both')
645+
646+
mag_tmp, phase_tmp, omega = T.freqresp(omega)
647+
mag = np.squeeze(mag_tmp)
648+
plot_axes['t'].loglog(omega_plot, 20 * np.log10(mag) if dB else mag)
649+
plot_axes['t'].set_xlabel(
650+
"Frequency (Hz)" if Hz else "Frequency (rad/sec)")
651+
plot_axes['t'].set_ylabel("$|T|$")
652+
plot_axes['t'].grid(grid, which='both')
653+
654+
plt.tight_layout()
630655

631656
#
632657
# Utility functions

control/grid.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ def zgrid(zetas=None, wns=None):
175175
an_x = xret[an_i]
176176
an_y = yret[an_i]
177177
num = '{:1.1f}'.format(a)
178-
ax.annotate("$\\frac{"+num+"\pi}{T}$", xy=(an_x, an_y), xytext=(an_x, an_y), size=9)
178+
ax.annotate(r"$\frac{"+num+r"\pi}{T}$", xy=(an_x, an_y), xytext=(an_x, an_y), size=9)
179179

180180
_final_setup(ax)
181181
return ax, fig

0 commit comments

Comments
 (0)