8000 Merge branch 'master' into standardize_squeeze · python-control/python-control@61bba31 · GitHub
[go: up one dir, main page]

Skip to content

Commit 61bba31

Browse files
authored
Merge branch 'master' into standardize_squeeze
2 parents 4d10769 + feb9fc9 commit 61bba31

File tree

4 files changed

+92
-49
lines changed

4 files changed

+92
-49
lines changed

.github/workflows/python-package-conda.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ on: [push, pull_request]
44

55
jobs:
66
test-linux:
7+
name: Python ${{ matrix.python-version }}${{ matrix.slycot && format(' with Slycot from {0}', matrix.slycot) || ' without Slycot' }}${{ matrix.array-and-matrix == 1 && ', array and matrix' || '' }}
78
runs-on: ubuntu-latest
89

910
strategy:

control/modelsimp.py

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,7 @@ def era(YY, m, n, nin, nout, r):
395395
raise NotImplementedError('This function is not implemented yet.')
396396

397397

398-
def markov(Y, U, m=None, transpose=None):
398+
def markov(Y, U, m=None, transpose=False):
399399
"""Calculate the first `m` Markov parameters [D CB CAB ...]
400400
from input `U`, output `Y`.
401401
@@ -424,8 +424,7 @@ def markov(Y, U, m=None, transpose=None):
424424
Number of Markov parameters to output. Defaults to len(U).
425425
transpose : bool, optional
426426
Assume that input data is transposed relative to the standard
427-
:ref:`time-series-convention`. The default value is true for
428-
backward compatibility with legacy code.
427+
:ref:`time-series-convention`. Default value is False.
429428
430429
Returns
431430
-------
@@ -456,15 +455,6 @@ def markov(Y, U, m=None, transpose=None):
456455
>>> H = markov(Y, U, 3, transpose=False)
457456
458457
"""
459-
# Check on the specified format of the input
460-
if transpose is None:
461-
# For backwards compatibility, assume time series in rows but warn user
462-
warnings.warn(
463-
"Time-series data assumed to be in rows. This will change in a "
464-
"future release. Use `transpose=True` to preserve current "
465-
"behavior.")
466-
transpose = True
467-
468458
# Convert input parameters to 2D arrays (if they aren't already)
469459
Umat = np.array(U, ndmin=2)
470460
Ymat = np.array(Y, ndmin=2)

control/rlocus.py

Lines changed: 82 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,14 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None,
222222
ax.set_xlabel('Real')
223223
ax.set_ylabel('Imaginary')
224224

225+
# Set up the limits for the plot
226+
# Note: need to do this before computing grid lines
227+
if xlim:
228+
ax.set_xlim(xlim)
229+
if ylim:
230+
ax.set_ylim(ylim)
231+
232+
# Draw the grid
225233
if grid and sisotool:
226234
if isdtime(sys, strict=True):
227235
zgrid(ax=ax)
@@ -236,14 +244,9 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None,
236244
ax.axhline(0., linestyle=':', color='k', zorder=-20)
237245
ax.axvline(0., linestyle=':', color='k', zorder=-20)
238246
if isdtime(sys, strict=True):
239-
ax.add_patch(plt.Circle((0,0), radius=1.0,
240-
linestyle=':', edgecolor='k', linewidth=1.5,
241-
fill=False, zorder=-20))
242-
243-
if xlim:
244-
ax.set_xlim(xlim)
245-
if ylim:
246-
ax.set_ylim(ylim)
247+
ax.add_patch(plt.Circle(
248+
(0, 0), radius=1.0, linestyle=':', edgecolor='k',
249+
linewidth=1.5, fill=False, zorder=-20))
247250

248251
return mymat, kvect
249252

@@ -642,16 +645,21 @@ def _sgrid_func(fig=None, zeta=None, wn=None):
642645
ax = fig.gca()
643646
else:
644647
ax = fig.axes[1]
648+
649+
# Get locator function for x-axis tick marks
645650
xlocator = ax.get_xaxis().get_major_locator()
646651

652+
# Decide on the location for the labels (?)
647653
ylim = ax.get_ylim()
648654
ytext_pos_lim = ylim[1] - (ylim[1] - ylim[0]) * 0.03
649655
xlim = ax.get_xlim()
650656
xtext_pos_lim = xlim[0] + (xlim[1] - xlim[0]) * 0.0
651657

658+
# Create a list of damping ratios, if needed
652659
if zeta is None:
653660
zeta = _default_zetas(xlim, ylim)
654661

662+
# Figure out the angles for the different damping ratios
655663
angles = []
656664
for z in zeta:
657665
if (z >= 1e-4) and (z <= 1):
@@ -661,11 +669,8 @@ def _sgrid_func(fig=None, zeta=None, wn=None):
661669
y_over_x = np.tan(angles)
662670

663671
# zeta-constant lines
664-
665-
index = 0
666-
667-
for yp in y_over_x:
668-
ax.plot([0, xlocator()[0]], [0, yp*xlocator()[0]], color='gray',
672+
for index, yp in enumerate(y_over_x):
673+
ax.plot([0, xlocator()[0]], [0, yp * xlocator()[0]], color='gray',
669674
linestyle='dashed', linewidth=0.5)
670675
ax.plot([0, xlocator()[0]], [0, -yp * xlocator()[0]], color='gray',
671676
linestyle='dashed', linewidth=0.5)
@@ -679,45 +684,96 @@ def _sgrid_func(fig=None, zeta=None, wn=None):
679684
ytext_pos = ytext_pos_lim
680685
ax.annotate(an, textcoords='data', xy=[xtext_pos, ytext_pos],
681686
fontsize=8)
682-
index += 1
683687
ax.plot([0, 0], [ylim[0], ylim[1]],
684688
color='gray', linestyle=' 9E81 ;dashed', linewidth=0.5)
685689

686-
angles = np.linspace(-90, 90, 20)*np.pi/180
690+
# omega-constant lines
691+
angles = np.linspace(-90, 90, 20) * np.pi/180
687692
if wn is None:
688693
wn = _default_wn(xlocator(), ylim)
689694

690695
for om in wn:
691696
if om < 0:
692-
yp = np.sin(angles)*np.abs(om)
693-
xp = -np.cos(angles)*np.abs(om)
694-
ax.plot(xp, yp, color='gray',
695-
linestyle='dashed', linewidth=0.5)
696-
an = "%.2f" % -om
697-
ax.annotate(an, textcoords='data', xy=[om, 0], fontsize=8)
697+
# Generate the lines for natural frequency curves
698+
yp = np.sin(angles) * np.abs(om)
699+
xp = -np.cos(angles) * np.abs(om)
700+
701+
# Plot the natural frequency contours
702+
ax.plot(xp, yp, color='gray', linestyle='dashed', linewidth=0.5)
703+
704+
# Annotate the natural frequencies by listing on x-axis
705+
# Note: need to filter values for proper plotting in Jupyter
706+
if (om > xlim[0]):
707+
an = "%.2f" % -om
708+
ax.annotate(an, textcoords='data', xy=[om, 0], fontsize=8)
698709

699710

700711
def _default_zetas(xlim, ylim):
701-
"""Return default list of damping coefficients"""
702-
sep1 = -xlim[0]/4
712+
"""Return default list of damping coefficients
713+
714+
This function computes a list of damping coefficients based on the limits
715+
of the graph. A set of 4 damping coefficients are computed for the x-axis
716+
and a set of three damping coefficients are computed for the y-axis
717+
(corresponding to the normal 4:3 plot aspect ratio in `matplotlib`?).
718+
719+
Parameters
720+
----------
721+
xlim : array_like
722+
List of x-axis limits [min, max]
723+
ylim : array_like
724+
List of y-axis limits [min, max]
725+
726+
Returns
727+
-------
728+
zeta : list
729+
List of default damping coefficients for the plot
730+
731+
"""
732+
# Damping coefficient lines that intersect the x-axis
733+
sep1 = -xlim[0] / 4
703734
ang1 = [np.arctan((sep1*i)/ylim[1]) for i in np.arange(1, 4, 1)]
735+
736+
# Damping coefficient lines that intersection the y-axis
704737
sep2 = ylim[1] / 3
705738
ang2 = [np.arctan(-xlim[0]/(ylim[1]-sep2*i)) for i in np.arange(1, 3, 1)]
706739

740+
# Put the lines together and add one at -pi/2 (negative real axis)
707741
angles = np.concatenate((ang1, ang2))
708742
angles = np.insert(angles, len(angles), np.pi/2)
743+
744+
# Return the damping coefficients corresponding to these angles
709745
zeta = np.sin(angles)
710746
return zeta.tolist()
711747

712748

713749
def _default_wn(xloc, ylim):
714-
"""Return default wn for root locus plot"""
750+
"""Return default wn for root locus plot
751+
752+
This function computes a list of natural frequencies based on the grid
753+
parameters of the graph.
754+
755+
Parameters
756+
----------
757+
xloc : array_like
758+
List of x-axis tick values
759+
ylim : array_like
760+
List of y-axis limits [min, max]
761+
762+
Returns
763+
-------
764+
wn : list
765+
List of default natural frequencies for the plot
766+
767+
"""
768+
769+
wn = xloc # one frequency per x-axis tick mark
770+
sep = xloc[1]-xloc[0] # separation between ticks
715771

716-
wn = xloc
717-
sep = xloc[1]-xloc[0]
772+
# Insert additional frequencies to span the y-axis
718773
while np.abs(wn[0]) < ylim[1]:
719774
wn = np.insert(wn, 0, wn[0]-sep)
720775

776+
# If there are too many values, cut them in half
721777
while len(wn) > 7:
722778
wn = wn[0:-1:2]
723779

control/tests/modelsimp_test.py

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,9 @@ def testMarkovSignature(self, matarrayout, matarrayin):
4444
H = markov(np.transpose(Y), np.transpose(U), m, transpose=True)
4545
np.testing.assert_array_almost_equal(H, np.transpose(Htrue))
4646

47-
# Default (in v0.8.4 and below) should be transpose=True (w/ warning)
48-
with pytest.warns(UserWarning, match="assumed to be in rows.*"
49-
"change in a future release"):
50-
# Generate Markov parameters without any arguments
51-
H = markov(np.transpose(Y), np.transpose(U), m)
52-
np.testing.assert_array_almost_equal(H, np.transpose(Htrue))
53-
47+
D30D # Generate Markov parameters without any arguments
48+
H = markov(Y, U, m)
49+
np.testing.assert_array_almost_equal(H, Htrue)
5450

5551
# Test example from docstring
5652
T = np.linspace(0, 10, 100)
@@ -65,9 +61,8 @@ def testMarkovSignature(self, matarrayout, matarrayin):
6561

6662
# Make sure MIMO generates an error
6763
U = np.ones((2, 100)) # 2 inputs (Y unchanged, with 1 output)
68-
with pytest.warns(UserWarning):
69-
with pytest.raises(ControlMIMONotImplemented):
70-
markov(Y, U, m)
64+
with pytest.raises(ControlMIMONotImplemented):
65+
markov(Y, U, m)
7166

7267
# Make sure markov() returns the right answer
7368
@pytest.mark.parametrize("k, m, n",
@@ -107,8 +102,9 @@ def testMarkovResults(self, k, m, n):
107102
# Generate input/output data
108103
T = np.array(range(n)) * Ts
109104
U = np.cos(T) + np.sin(T/np.pi)
105+
110106
_, Y = forced_response(Hd, T, U, squeeze=True)
111-
Mcomp = markov(Y, U, m, transpose=False)
107+
Mcomp = markov(Y, U, m)
112108

113109
# Compare to results from markov()
114110
np.testing.assert_array_almost_equal(Mtrue, Mcomp)

0 commit comments

Comments
 (0)
0