Skip to content

Commit e80343d

Browse files
committed
smooth curve offsets to avoid discontinuities
1 parent 16e5078 commit e80343d

1 file changed

Lines changed: 79 additions & 11 deletions

File tree

control/freqplot.py

Lines changed: 79 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -518,8 +518,8 @@ def gen_zero_centered_series(val_min, val_max, period):
518518

519519
# Default values for module parameter variables
520520
_nyquist_defaults = {
521-
'nyquist.primary_style': ['-', ':'], # style for primary curve
522-
'nyquist.mirror_style': ['--', '-.'], # style for mirror curve
521+
'nyquist.primary_style': ['-', '-.'], # style for primary curve
522+
'nyquist.mirror_style': ['--', ':'], # style for mirror curve
523523
'nyquist.arrows': 2,
524524
'nyquist.arrow_size': 8,
525525
'nyquist.indent_radius': 1e-6, # indentation radius
@@ -695,6 +695,7 @@ def nyquist_plot(syslist, omega=None, plot=True, omega_limits=None,
695695
kwargs.pop('arrow_length', False)
696696

697697
# Get values for params (and pop from list to allow keyword use in plot)
698+
omega_num_given = omega_num is not None
698699
omega_num = config._get_param('freqplot', 'number_of_samples', omega_num)
699700
arrows = config._get_param(
700701
'nyquist', 'arrows', kwargs, _nyquist_defaults, pop=True)
@@ -735,8 +736,13 @@ def _parse_linestyle(style_name, allow_false=False):
735736
omega, omega_range_given = _determine_omega_vector(
736737
syslist, omega, omega_limits, omega_num)
737738
if not omega_range_given:
738-
# Start contour at zero frequency
739-
omega[0] = 0.
739+
if omega_num_given:
740+
# Just reset the starting point
741+
omega[0] = 0.0
742+
else:
743+
# Insert points between the origin and the first frequency point
744+
omega = np.concatenate((
745+
np.linspace(0, omega[0], indent_points), omega[1:]))
740746

741747
# Go through each system and keep track of the results
742748
counts, contours = [], []
@@ -937,17 +943,23 @@ def _parse_linestyle(style_name, allow_false=False):
937943
x_reg, y_reg, primary_style[0], color=color, *args, **kwargs)
938944
c = p[0].get_color()
939945

946+
# Figure out how much to offset the curve: the offset goes from
947+
# zero at the start of the scaled section to max_curve_offset as
948+
# we move along the curve
949+
curve_offset = _compute_curve_offset(
950+
resp, scale_mask, max_curve_offset)
951+
940952
# Plot the scaled sections of the curve (changing linestyle)
941953
x_scl = np.ma.masked_where(scale_mask, resp.real)
942954
y_scl = np.ma.masked_where(scale_mask, resp.imag)
943955
plt.plot(
944-
x_scl * (1 + max_curve_offset), y_scl * (1 + max_curve_offset),
956+
x_scl * (1 + curve_offset), y_scl * (1 + curve_offset),
945957
primary_style[1], color=c, *args, **kwargs)
946958

947959
# Plot the primary curve (invisible) for setting arrows
948960
x, y = resp.real.copy(), resp.imag.copy()
949-
x[reg_mask] *= (1 + max_curve_offset)
950-
y[reg_mask] *= (1 + max_curve_offset)
961+
x[reg_mask] *= (1 + curve_offset[reg_mask])
962+
y[reg_mask] *= (1 + curve_offset[reg_mask])
951963
p = plt.plot(x, y, linestyle='None', color=c, *args, **kwargs)
952964

953965
# Add arrows
@@ -961,14 +973,14 @@ def _parse_linestyle(style_name, allow_false=False):
961973
plt.plot(
962974
x_reg, -y_reg, mirror_style[0], color=c, *args, **kwargs)
963975
plt.plot(
964-
x_scl * (1 - max_curve_offset),
965-
-y_scl * (1 - max_curve_offset),
976+
x_scl * (1 - curve_offset),
977+
-y_scl * (1 - curve_offset),
966978
mirror_style[1], color=c, *args, **kwargs)
967979

968980
# Add the arrows (on top of an invisible contour)
969981
x, y = resp.real.copy(), resp.imag.copy()
970-
x[reg_mask] *= (1 - max_curve_offset)
971-
y[reg_mask] *= (1 - max_curve_offset)
982+
x[reg_mask] *= (1 - curve_offset[reg_mask])
983+
y[reg_mask] *= (1 - curve_offset[reg_mask])
972984
p = plt.plot(x, -y, linestyle='None', color=c, *args, **kwargs)
973985
_add_arrows_to_line2D(
974986
ax, p[0], arrow_pos, arrowstyle=arrow_style, dir=-1)
@@ -1086,6 +1098,62 @@ def _add_arrows_to_line2D(
10861098
arrows.append(p)
10871099
return arrows
10881100

1101+
#
1102+
# Function to compute Nyquist curve offsets
1103+
#
1104+
# This function computes a smoothly varying offset that starts and ends at
1105+
# zero at the ends of a scaled segment.
1106+
#
1107+
def _compute_curve_offset(resp, mask, max_offset):
1108+
# Compute the arc length along the curve
1109+
s_curve = np.cumsum(
1110+
np.sqrt(np.diff(resp.real) ** 2 + np.diff(resp.imag) ** 2))
1111+
1112+
# Initialize the offset
1113+
offset = np.zeros(resp.size)
1114+
arclen = np.zeros(resp.size)
1115+
1116+
# Walk through the response and keep track of each continous component
1117+
i, nsegs = 0, 0
1118+
while i < resp.size:
1119+
# Skip the regular segment
1120+
while i < resp.size and mask[i]:
1121+
i += 1 # Increment the counter
1122+
if i == resp.size:
1123+
break;
1124+
# Keep track of the arclength
1125+
arclen[i] = arclen[i-1] + np.abs(resp[i] - resp[i-1])
1126+
1127+
nsegs += 0.5
1128+
if i == resp.size:
1129+
break;
1130+
1131+
# Save the starting offset of this segment
1132+
seg_start = i
1133+
1134+
# Walk through the scaled segment
1135+
while i < resp.size and not mask[i]:
1136+
i += 1
1137+
if i == resp.size: # See if we are done with this segment
1138+
break;
1139+
# Keep track of the arclength
1140+
arclen[i] = arclen[i-1] + np.abs(resp[i] - resp[i-1])
1141+
1142+
nsegs += 0.5
1143+
if i == resp.size:
1144+
break;
1145+
1146+
# Save the ending offset of this segment
1147+
seg_end = i
1148+
1149+
# Now compute the scaling for this segment
1150+
s_segment = arclen[seg_end-1] - arclen[seg_start]
1151+
offset[seg_start:seg_end] = max_offset * s_segment/s_curve[-1] * \
1152+
np.sin(np.pi * (arclen[seg_start:seg_end]
1153+
- arclen[seg_start])/s_segment)
1154+
1155+
return offset
1156+
10891157

10901158
#
10911159
# Gang of Four plot

0 commit comments

Comments
 (0)