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='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+
# 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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy