Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit7407265

Browse files
committed
Fixed docstrings per roryyorke plus discrete-time corrections
* changed return type for poles() and zeros() to complex* updated (missing) discrete-time tests for stability warnings* changed processing of near imaginary poles to pure imaginary poles
1 parent5d9c7f0 commit7407265

File tree

4 files changed

+62
-30
lines changed

4 files changed

+62
-30
lines changed

‎control/freqplot.py

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -644,14 +644,14 @@ def nyquist_plot(
644644
Linestyles for mirror image of the Nyquist curve. The first element
645645
is used for unscaled portions of the Nyquist curve, the second element
646646
is used for portions that are scaled (using max_curve_magnitude). If
647-
`False` then omit completely. Default linestyle (['--', '-.']) is
647+
`False` then omit completely. Default linestyle (['--', ':']) is
648648
determined by config.defaults['nyquist.mirror_style'].
649649
650650
primary_style : [str, str], optional
651651
Linestyles for primary image of the Nyquist curve. The first
652652
element is used for unscaled portions of the Nyquist curve,
653653
the second element is used for portions that are scaled (using
654-
max_curve_magnitude). Default linestyle (['-', ':']) is
654+
max_curve_magnitude).Default linestyle (['-', '-.']) is
655655
determined by config.defaults['nyquist.mirror_style'].
656656
657657
start_marker : str, optional
@@ -750,6 +750,9 @@ def _parse_linestyle(style_name, allow_false=False):
750750
ifisinstance(style,str):
751751
# Only one style provided, use the default for the other
752752
style= [style,_nyquist_defaults['nyquist.'+style_name][1]]
753+
warnings.warn(
754+
"use of a single string for linestyle will be deprecated "
755+
" in a future release",PendingDeprecationWarning)
753756
if (allow_falseandstyleisFalse)or \
754757
(isinstance(style,list)andlen(style)==2):
755758
returnstyle
@@ -765,7 +768,7 @@ def _parse_linestyle(style_name, allow_false=False):
765768

766769
# Determine the range of frequencies to use, based on args/features
767770
omega,omega_range_given=_determine_omega_vector(
768-
syslist,omega,omega_limits,omega_num)
771+
syslist,omega,omega_limits,omega_num,feature_periphery_decades=2)
769772

770773
# If omega was not specified explicitly, start at omega = 0
771774
ifnotomega_range_given:
@@ -790,7 +793,7 @@ def _parse_linestyle(style_name, allow_false=False):
790793

791794
# Determine the contour used to evaluate the Nyquist curve
792795
ifsys.isdtime(strict=True):
793-
#Transform frequencies in for discrete-time systems
796+
#Restrict frequencies for discrete-time systems
794797
nyquistfrq=math.pi/sys.dt
795798
ifnotomega_range_given:
796799
# limit up to and including nyquist frequency
@@ -817,12 +820,12 @@ def _parse_linestyle(style_name, allow_false=False):
817820
# because we don't need to indent for them
818821
zplane_poles=sys.poles()
819822
zplane_poles=zplane_poles[~np.isclose(abs(zplane_poles),0.)]
820-
splane_poles=np.log(zplane_poles)/sys.dt
823+
splane_poles=np.log(zplane_poles)/sys.dt
821824

822825
zplane_cl_poles=sys.feedback().poles()
823826
zplane_cl_poles=zplane_cl_poles[
824827
~np.isclose(abs(zplane_poles),0.)]
825-
splane_cl_poles=np.log(zplane_cl_poles)/sys.dt
828+
splane_cl_poles=np.log(zplane_cl_poles)/sys.dt
826829

827830
#
828831
# Check to make sure indent radius is small enough
@@ -851,8 +854,8 @@ def _parse_linestyle(style_name, allow_false=False):
851854
# See if we should add some frequency points near imaginary poles
852855
#
853856
forpinsplane_poles:
854-
# See if we need to process this pole (skipany that is on
855-
#the not nearoron the negative omega axis + user override)
857+
# See if we need to process this pole (skipif on the negative
858+
#imaginary axisornot near imaginary axis + user override)
856859
ifp.imag<0orabs(p.real)>indent_radiusor \
857860
omega_range_given:
858861
continue
@@ -894,13 +897,13 @@ def _parse_linestyle(style_name, allow_false=False):
894897
- (s-p).real
895898

896899
# Figure out which way to offset the contour point
897-
ifp.real<0or (np.isclose(p.real,0)
898-
andindent_direction=='right'):
900+
ifp.real<0or (p.real==0and
901+
indent_direction=='right'):
899902
# Indent to the right
900903
splane_contour[i]+=offset
901904

902-
elifp.real>0or (np.isclose(p.real,0)
903-
andindent_direction=='left'):
905+
elifp.real>0or (p.real==0and
906+
indent_direction=='left'):
904907
# Indent to the left
905908
splane_contour[i]-=offset
906909

@@ -937,9 +940,21 @@ def _parse_linestyle(style_name, allow_false=False):
937940
# Nyquist criterion is actually satisfied.
938941
#
939942
ifisinstance(sys, (StateSpace,TransferFunction)):
940-
P= (sys.poles().real>0).sum()ifindent_direction=='right' \
941-
else (sys.poles().real>=0).sum()
942-
Z= (sys.feedback().poles().real>=0).sum()
943+
# Count the number of open/closed loop RHP poles
944+
ifsys.isctime():
945+
ifindent_direction=='right':
946+
P= (sys.poles().real>0).sum()
947+
else:
948+
P= (sys.poles().real>=0).sum()
949+
Z= (sys.feedback().poles().real>=0).sum()
950+
else:
951+
ifindent_direction=='right':
952+
P= (np.abs(sys.poles())>1).sum()
953+
else:
954+
P= (np.abs(sys.poles())>=1).sum()
955+
Z= (np.abs(sys.feedback().poles())>=1).sum()
956+
957+
# Check to make sure the results make sense; warn if not
943958
ifZ!=count+Pandwarn_encirclements:
944959
warnings.warn(
945960
"number of encirclements does not match Nyquist criterion;"
@@ -976,7 +991,7 @@ def _parse_linestyle(style_name, allow_false=False):
976991
# Find the different portions of the curve (with scaled pts marked)
977992
reg_mask=np.logical_or(
978993
np.abs(resp)>max_curve_magnitude,
979-
contour.real!=0)
994+
splane_contour.real!=0)
980995
# reg_mask = np.logical_or(
981996
# np.abs(resp.real) > max_curve_magnitude,
982997
# np.abs(resp.imag) > max_curve_magnitude)
@@ -1508,7 +1523,7 @@ def singular_values_plot(syslist, omega=None,
15081523

15091524
# Determine the frequency range to be used
15101525
def_determine_omega_vector(syslist,omega_in,omega_limits,omega_num,
1511-
Hz=None):
1526+
Hz=None,feature_periphery_decades=None):
15121527
"""Determine the frequency range for a frequency-domain plot
15131528
according to a standard logic.
15141529
@@ -1554,9 +1569,9 @@ def _determine_omega_vector(syslist, omega_in, omega_limits, omega_num,
15541569
ifomega_limitsisNone:
15551570
omega_range_given=False
15561571
# Select a default range if none is provided
1557-
omega_out=_default_frequency_range(syslist,
1558-
number_of_samples=omega_num,
1559-
Hz=Hz)
1572+
omega_out=_default_frequency_range(
1573+
syslist,number_of_samples=omega_num,Hz=Hz,
1574+
feature_periphery_decades=feature_periphery_decades)
15601575
else:
15611576
omega_limits=np.asarray(omega_limits)
15621577
iflen(omega_limits)!=2:
@@ -1640,7 +1655,7 @@ def _default_frequency_range(syslist, Hz=None, number_of_samples=None,
16401655

16411656
features_=np.concatenate((sys.poles(),sys.zeros()))
16421657
# Get rid of poles and zeros on the real axis (imag==0)
1643-
# * origin and real < 0
1658+
# * origin and real < 0
16441659
# * at 1.: would result in omega=0. (logaritmic plot!)
16451660
toreplace=np.isclose(features_.imag,0.0)& (
16461661
(features_.real<=0.)|

‎control/statesp.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -987,7 +987,8 @@ def freqresp(self, omega):
987987
defpoles(self):
988988
"""Compute the poles of a state space system."""
989989

990-
returneigvals(self.A)ifself.nstateselsenp.array([])
990+
returneigvals(self.A).astype(complex)ifself.nstates \
991+
elsenp.array([])
991992

992993
defzeros(self):
993994
"""Compute the zeros of a state space system."""
@@ -1006,8 +1007,9 @@ def zeros(self):
10061007
ifnu==0:
10071008
returnnp.array([])
10081009
else:
1010+
# Use SciPy generalized eigenvalue fucntion
10091011
returnsp.linalg.eigvals(out[8][0:nu,0:nu],
1010-
out[9][0:nu,0:nu])
1012+
out[9][0:nu,0:nu]).astype(complex)
10111013

10121014
exceptImportError:# Slycot unavailable. Fall back to scipy.
10131015
ifself.C.shape[0]!=self.D.shape[1]:
@@ -1031,7 +1033,7 @@ def zeros(self):
10311033
(0,self.B.shape[1])),"constant")
10321034
returnnp.array([xforxinsp.linalg.eigvals(L,M,
10331035
overwrite_a=True)
1034-
ifnotisinf(x)])
1036+
ifnotisinf(x)],dtype=complex)
10351037

10361038
# Feedback around a state space system
10371039
deffeedback(self,other=1,sign=-1):

‎control/tests/nyquist_test.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,8 @@ def test_linestyle_checks():
350350

351351
# If only one line style is given use, the default value for the other
352352
# TODO: for now, just make sure the signature works; no correct check yet
353-
ct.nyquist_plot(sys,primary_style=':',mirror_style='-.')
353+
withpytest.warns(PendingDeprecationWarning,match="single string"):
354+
ct.nyquist_plot(sys,primary_style=':',mirror_style='-.')
354355

355356
@pytest.mark.usefixtures("editsdefaults")
356357
deftest_nyquist_legacy():
@@ -363,13 +364,17 @@ def test_nyquist_legacy():
363364
withpytest.warns(UserWarning,match="indented contour may miss"):
364365
count=ct.nyquist_plot(sys)
365366

366-
367+
deftest_discrete_nyquist():
368+
# Make sure we can handle discrete time systems with negative poles
369+
sys=ct.tf(1, [1,-0.1],dt=1)*ct.tf(1, [1,0.1],dt=1)
370+
ct.nyquist_plot(sys)
371+
367372
if__name__=="__main__":
368373
#
369374
# Interactive mode: generate plots for manual viewing
370375
#
371-
# Running this script in python (or better ipython) will show a collection of
372-
# figures that should all look OK on the screeen.
376+
# Running this script in python (or better ipython) will show a
377+
#collection offigures that should all look OK on the screeen.
373378
#
374379

375380
# In interactive mode, turn on ipython interactive graphics
@@ -411,3 +416,13 @@ def test_nyquist_legacy():
411416
np.array2string(sys.poles(),precision=2,separator=','))
412417
count=ct.nyquist_plot(sys)
413418
assert_Z(sys)==count+_P(sys)
419+
420+
print("Discrete time systems")
421+
sys=ct.c2d(sys,0.01)
422+
plt.figure()
423+
plt.title("Discrete-time; poles: %s"%
424+
np.array2string(sys.poles(),precision=2,separator=','))
425+
count=ct.nyquist_plot(sys)
426+
427+
428+

‎control/xferfcn.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -798,7 +798,7 @@ def poles(self):
798798
rts= []
799799
ford,oinzip(den,denorder):
800800
rts.extend(roots(d[:o+1]))
801-
returnnp.array(rts)
801+
returnnp.array(rts).astype(complex)
802802

803803
defzeros(self):
804804
"""Compute the zeros of a transfer function."""
@@ -808,7 +808,7 @@ def zeros(self):
808808
"for SISO systems.")
809809
else:
810810
# for now, just give zeros of a SISO tf
811-
returnroots(self.num[0][0])
811+
returnroots(self.num[0][0]).astype(complex)
812812

813813
deffeedback(self,other=1,sign=-1):
814814
"""Feedback interconnection between two LTI objects."""

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp