@@ -1100,13 +1100,14 @@ def gen_zero_centered_series(val_min, val_max, period):
11001100_nyquist_defaults = {
11011101 'nyquist.primary_style' : ['-' , '-.' ], # style for primary curve
11021102 'nyquist.mirror_style' : ['--' , ':' ], # style for mirror curve
1103- 'nyquist.arrows' : 2 , # number of arrows around curve
1103+ 'nyquist.arrows' : 3 , # number of arrows around curve
11041104 'nyquist.arrow_size' : 8 , # pixel size for arrows
11051105 'nyquist.encirclement_threshold' : 0.05 , # warning threshold
11061106 'nyquist.indent_radius' : 1e-4 , # indentation radius
11071107 'nyquist.indent_direction' : 'right' , # indentation direction
1108- 'nyquist.indent_points' : 50 , # number of points to insert
1109- 'nyquist.max_curve_magnitude' : 20 , # clip large values
1108+ 'nyquist.indent_points' : 200 , # number of points to insert
1109+ 'nyquist.max_curve_magnitude' : 15 , # rescale large values
1110+ 'nyquist.blend_fraction' : 0.15 , # when to start scaling
11101111 'nyquist.max_curve_offset' : 0.02 , # offset of primary/mirror
11111112 'nyquist.start_marker' : 'o' , # marker at start of curve
11121113 'nyquist.start_marker_size' : 4 , # size of the marker
@@ -1638,6 +1639,10 @@ def nyquist_plot(
16381639 The matplotlib axes to draw the figure on. If not specified and
16391640 the current figure has a single axes, that axes is used.
16401641 Otherwise, a new figure is created.
1642+ blend_fraction : float, optional
1643+ For portions of the Nyquist curve that are scaled to have a maximum
1644+ magnitude of `max_curve_magnitude`, begin a smooth rescaling at
1645+ this fraction of `max_curve_magnitude`. Default value is 0.15.
16411646 encirclement_threshold : float, optional
16421647 Define the threshold for generating a warning if the number of net
16431648 encirclements is a non-integer value. Default value is 0.05 and can
@@ -1751,8 +1756,8 @@ def nyquist_plot(
17511756 to avoid poles, resulting in a scaling of the Nyquist plot, the line
17521757 styles are according to the settings of the `primary_style` and
17531758 `mirror_style` keywords. By default the scaled portions of the primary
1754- curve use a dotted line style and the scaled portion of the mirror
1755- image use a dashdot line style.
1759+ curve use a dashdot line style and the scaled portions of the mirror
1760+ image use a dotted line style.
17561761
17571762 Examples
17581763 --------
@@ -1784,6 +1789,8 @@ def nyquist_plot(
17841789 ax_user = ax
17851790 max_curve_magnitude = config ._get_param (
17861791 'nyquist' , 'max_curve_magnitude' , kwargs , _nyquist_defaults , pop = True )
1792+ blend_fraction = config ._get_param (
1793+ 'nyquist' , 'blend_fraction' , kwargs , _nyquist_defaults , pop = True )
17871794 max_curve_offset = config ._get_param (
17881795 'nyquist' , 'max_curve_offset' , kwargs , _nyquist_defaults , pop = True )
17891796 rcParams = config ._get_param ('ctrlplot' , 'rcParams' , kwargs , pop = True )
@@ -1878,10 +1885,16 @@ def _parse_linestyle(style_name, allow_false=False):
18781885 legend_loc , _ , show_legend = _process_legend_keywords (
18791886 kwargs , None , 'upper right' )
18801887
1888+ # Figure out where the blended curve should start
1889+ if blend_fraction < 0 or blend_fraction > 1 :
1890+ raise ValueError ("blend_fraction must be between 0 and 1" )
1891+ blend_curve_start = (1 - blend_fraction ) * max_curve_magnitude
1892+
18811893 # Create a list of lines for the output
1882- out = np .empty (len (nyquist_responses ), dtype = object )
1883- for i in range (out .shape [0 ]):
1884- out [i ] = [] # unique list in each element
1894+ out = np .empty ((len (nyquist_responses ), 4 ), dtype = object )
1895+ for i in range (len (nyquist_responses )):
1896+ for j in range (4 ):
1897+ out [i , j ] = [] # unique list in each element
18851898
18861899 for idx , response in enumerate (nyquist_responses ):
18871900 resp = response .response
@@ -1892,20 +1905,31 @@ def _parse_linestyle(style_name, allow_false=False):
18921905
18931906 # Find the different portions of the curve (with scaled pts marked)
18941907 reg_mask = np .logical_or (
1895- np .abs (resp ) > max_curve_magnitude ,
1896- splane_contour .real != 0 )
1897- # reg_mask = np.logical_or(
1898- # np.abs(resp.real) > max_curve_magnitude,
1899- # np.abs(resp.imag) > max_curve_magnitude)
1908+ np .abs (resp ) > blend_curve_start ,
1909+ np .logical_not (np .isclose (splane_contour .real , 0 )))
19001910
19011911 scale_mask = ~ reg_mask \
19021912 & np .concatenate ((~ reg_mask [1 :], ~ reg_mask [- 1 :])) \
19031913 & np .concatenate ((~ reg_mask [0 :1 ], ~ reg_mask [:- 1 ]))
19041914
19051915 # Rescale the points with large magnitude
1906- rescale = np .logical_and (
1907- reg_mask , abs (resp ) > max_curve_magnitude )
1908- resp [rescale ] *= max_curve_magnitude / abs (resp [rescale ])
1916+ rescale_idx = (np .abs (resp ) > blend_curve_start )
1917+
1918+ if np .any (rescale_idx ): # Only process if rescaling is needed
1919+ subset = resp [rescale_idx ]
1920+ abs_subset = np .abs (subset )
1921+ unit_vectors = subset / abs_subset # Preserve phase/direction
1922+
1923+ if blend_curve_start == max_curve_magnitude :
1924+ # Clip at max_curve_magnitude
1925+ resp [rescale_idx ] = max_curve_magnitude * unit_vectors
1926+ else :
1927+ # Logistic scaling
1928+ newmag = blend_curve_start + \
1929+ (max_curve_magnitude - blend_curve_start ) * \
1930+ (abs_subset - blend_curve_start ) / \
1931+ (abs_subset + max_curve_magnitude - 2 * blend_curve_start )
1932+ resp [rescale_idx ] = newmag * unit_vectors
19091933
19101934 # Get the label to use for the line
19111935 label = response .sysname if line_labels is None else line_labels [idx ]
@@ -1916,7 +1940,7 @@ def _parse_linestyle(style_name, allow_false=False):
19161940 p = ax .plot (
19171941 x_reg , y_reg , primary_style [0 ], color = color , label = label , ** kwargs )
19181942 c = p [0 ].get_color ()
1919- out [idx ] += p
1943+ out [idx , 0 ] += p
19201944
19211945 # Figure out how much to offset the curve: the offset goes from
19221946 # zero at the start of the scaled section to max_curve_offset as
@@ -1928,12 +1952,12 @@ def _parse_linestyle(style_name, allow_false=False):
19281952 x_scl = np .ma .masked_where (scale_mask , resp .real )
19291953 y_scl = np .ma .masked_where (scale_mask , resp .imag )
19301954 if x_scl .count () >= 1 and y_scl .count () >= 1 :
1931- out [idx ] += ax .plot (
1955+ out [idx , 1 ] += ax .plot (
19321956 x_scl * (1 + curve_offset ),
19331957 y_scl * (1 + curve_offset ),
19341958 primary_style [1 ], color = c , ** kwargs )
19351959 else :
1936- out [idx ] += [None ]
1960+ out [idx , 1 ] += [None ]
19371961
19381962 # Plot the primary curve (invisible) for setting arrows
19391963 x , y = resp .real .copy (), resp .imag .copy ()
@@ -1948,15 +1972,15 @@ def _parse_linestyle(style_name, allow_false=False):
19481972 # Plot the mirror image
19491973 if mirror_style is not False :
19501974 # Plot the regular and scaled segments
1951- out [idx ] += ax .plot (
1975+ out [idx , 2 ] += ax .plot (
19521976 x_reg , - y_reg , mirror_style [0 ], color = c , ** kwargs )
19531977 if x_scl .count () >= 1 and y_scl .count () >= 1 :
1954- out [idx ] += ax .plot (
1978+ out [idx , 3 ] += ax .plot (
19551979 x_scl * (1 - curve_offset ),
19561980 - y_scl * (1 - curve_offset ),
19571981 mirror_style [1 ], color = c , ** kwargs )
19581982 else :
1959- out [idx ] += [None ]
1983+ out [idx , 3 ] += [None ]
19601984
19611985 # Add the arrows (on top of an invisible contour)
19621986 x , y = resp .real .copy (), resp .imag .copy ()
@@ -1966,12 +1990,15 @@ def _parse_linestyle(style_name, allow_false=False):
19661990 _add_arrows_to_line2D (
19671991 ax , p [0 ], arrow_pos , arrowstyle = arrow_style , dir = - 1 )
19681992 else :
1969- out [idx ] += [None , None ]
1993+ out [idx , 2 ] += [None ]
1994+ out [idx , 3 ] += [None ]
19701995
19711996 # Mark the start of the curve
19721997 if start_marker :
1973- ax .plot (resp [0 ].real , resp [0 ].imag , start_marker ,
1974- color = c , markersize = start_marker_size )
1998+ segment = 0 if 0 in rescale_idx else 1 # regular vs scaled
1999+ out [idx , segment ] += ax .plot (
2000+ resp [0 ].real , resp [0 ].imag , start_marker ,
2001+ color = c , markersize = start_marker_size )
19752002
19762003 # Mark the -1 point
19772004 ax .plot ([- 1 ], [0 ], 'r+' )
0 commit comments