@@ -644,14 +644,14 @@ def nyquist_plot(
644
644
Linestyles for mirror image of the Nyquist curve. The first element
645
645
is used for unscaled portions of the Nyquist curve, the second element
646
646
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
648
648
determined by config.defaults['nyquist.mirror_style'].
649
649
650
650
primary_style : [str, str], optional
651
651
Linestyles for primary image of the Nyquist curve. The first
652
652
element is used for unscaled portions of the Nyquist curve,
653
653
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
655
655
determined by config.defaults['nyquist.mirror_style'].
656
656
657
657
start_marker : str, optional
@@ -750,6 +750,9 @@ def _parse_linestyle(style_name, allow_false=False):
750
750
if isinstance (style , str ):
751
751
# Only one style provided, use the default for the other
752
752
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 )
753
756
if (allow_false and style is False ) or \
754
757
(isinstance (style , list ) and len (style ) == 2 ):
755
758
return style
@@ -765,7 +768,7 @@ def _parse_linestyle(style_name, allow_false=False):
765
768
766
769
# Determine the range of frequencies to use, based on args/features
767
770
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 )
769
772
770
773
# If omega was not specified explicitly, start at omega = 0
771
774
if not omega_range_given :
@@ -790,7 +793,7 @@ def _parse_linestyle(style_name, allow_false=False):
790
793
791
794
# Determine the contour used to evaluate the Nyquist curve
792
795
if sys .isdtime (strict = True ):
793
- # Transform frequencies in for discrete-time systems
796
+ # Restrict frequencies for discrete-time systems
794
797
nyquistfrq = math .pi / sys .dt
795
798
if not omega_range_given :
796
799
# limit up to and including nyquist frequency
@@ -817,12 +820,12 @@ def _parse_linestyle(style_name, allow_false=False):
817
820
# because we don't need to indent for them
818
821
zplane_poles = sys .poles ()
819
822
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
821
824
822
825
zplane_cl_poles = sys .feedback ().poles ()
823
826
zplane_cl_poles = zplane_cl_poles [
824
827
~ 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
826
829
827
830
#
828
831
# Check to make sure indent radius is small enough
@@ -851,8 +854,8 @@ def _parse_linestyle(style_name, allow_false=False):
851
854
# See if we should add some frequency points near imaginary poles
852
855
#
853
856
for p in splane_poles :
854
- # See if we need to process this pole (skip any that is on
855
- # the not near or on the negative omega axis + user override)
857
+ # See if we need to process this pole (skip if on the negative
858
+ # imaginary axis or not near imaginary axis + user override)
856
859
if p .imag < 0 or abs (p .real ) > indent_radius or \
857
860
omega_range_given :
858
861
continue
@@ -894,13 +897,13 @@ def _parse_linestyle(style_name, allow_false=False):
894
897
- (s - p ).real
895
898
896
899
# Figure out which way to offset the contour point
897
- if p .real < 0 or (np . isclose ( p .real , 0 )
898
- and indent_direction == 'right' ):
900
+ if p .real < 0 or (p .real == 0 and
901
+ indent_direction == 'right' ):
899
902
# Indent to the right
900
903
splane_contour [i ] += offset
901
904
902
- elif p .real > 0 or (np . isclose ( p .real , 0 )
903
- and indent_direction == 'left' ):
905
+ elif p .real > 0 or (p .real == 0 and
906
+ indent_direction == 'left' ):
904
907
# Indent to the left
905
908
splane_contour [i ] -= offset
906
909
@@ -937,9 +940,21 @@ def _parse_linestyle(style_name, allow_false=False):
937
940
# Nyquist criterion is actually satisfied.
938
941
#
939
942
if isinstance (sys , (StateSpace , TransferFunction )):
940
- P = (sys .poles ().real > 0 ).sum () if indent_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
+ if sys .isctime ():
945
+ if indent_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
+ if indent_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
943
958
if Z != count + P and warn_encirclements :
944
959
warnings .warn (
945
960
"number of encirclements does not match Nyquist criterion;"
@@ -976,7 +991,7 @@ def _parse_linestyle(style_name, allow_false=False):
976
991
# Find the different portions of the curve (with scaled pts marked)
977
992
reg_mask = np .logical_or (
978
993
np .abs (resp ) > max_curve_magnitude ,
979
- contour .real != 0 )
994
+ splane_contour .real != 0 )
980
995
# reg_mask = np.logical_or(
981
996
# np.abs(resp.real) > max_curve_magnitude,
982
997
# np.abs(resp.imag) > max_curve_magnitude)
@@ -1508,7 +1523,7 @@ def singular_values_plot(syslist, omega=None,
1508
1523
1509
1524
# Determine the frequency range to be used
1510
1525
def _determine_omega_vector (syslist , omega_in , omega_limits , omega_num ,
1511
- Hz = None ):
1526
+ Hz = None , feature_periphery_decades = None ):
1512
1527
"""Determine the frequency range for a frequency-domain plot
1513
1528
according to a standard logic.
1514
1529
@@ -1554,9 +1569,9 @@ def _determine_omega_vector(syslist, omega_in, omega_limits, omega_num,
1554
1569
if omega_limits is None :
1555
1570
omega_range_given = False
1556
1571
# 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 )
1560
1575
else :
1561
1576
omega_limits = np .asarray (omega_limits )
1562
1577
if len (omega_limits ) != 2 :
@@ -1640,7 +1655,7 @@ def _default_frequency_range(syslist, Hz=None, number_of_samples=None,
1640
1655
1641
1656
features_ = np .concatenate ((sys .poles (), sys .zeros ()))
1642
1657
# Get rid of poles and zeros on the real axis (imag==0)
1643
- # * origin and real < 0
1658
+ # * origin and real < 0
1644
1659
# * at 1.: would result in omega=0. (logaritmic plot!)
1645
1660
toreplace = np .isclose (features_ .imag , 0.0 ) & (
1646
1661
(features_ .real <= 0. ) |
0 commit comments