Skip to content

Commit 51fa137

Browse files
committed
update nyquist_plot lines output to match documentation
1 parent f37656b commit 51fa137

File tree

6 files changed

+59
-27
lines changed

6 files changed

+59
-27
lines changed

control/ctrlplot.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@
4141
# # Customize axes (curvilinear grids, shared axes, etc)
4242
#
4343
# # Plot the data
44-
# lines = np.full(ax_array.shape, [])
44+
# lines = np.empty(ax_array.shape, dtype=object)
45+
# for i in range(ax_array.shape[0]):
46+
# for j in range(ax_array.shape[1]):
47+
# lines[i, j] = []
4548
# line_labels = _process_line_labels(label, ntraces, nrows, ncols)
4649
# color_offset, color_cycle = _get_color_offset(ax)
4750
# for i, j in itertools.product(range(nrows), range(ncols)):

control/descfcn.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -521,7 +521,8 @@ def describing_function_plot(
521521
# Plot the Nyquist response
522522
cplt = dfresp.response.plot(**kwargs)
523523
ax = cplt.axes[0, 0] # Get the axes where the plot was made
524-
lines[0] = cplt.lines[0] # Return Nyquist lines for first system
524+
lines[0] = np.concatenate( # Return Nyquist lines for first system
525+
cplt.lines.flatten()).tolist()
525526

526527
# Add the describing function curve to the plot
527528
lines[1] = ax.plot(dfresp.N_vals.real, dfresp.N_vals.imag)

control/freqplot.py

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1886,9 +1886,10 @@ def _parse_linestyle(style_name, allow_false=False):
18861886
kwargs, None, 'upper right')
18871887

18881888
# Create a list of lines for the output
1889-
out = np.empty(len(nyquist_responses), dtype=object)
1890-
for i in range(out.shape[0]):
1891-
out[i] = [] # unique list in each element
1889+
out = np.empty((len(nyquist_responses), 4), dtype=object)
1890+
for i in range(len(nyquist_responses)):
1891+
for j in range(4):
1892+
out[i, j] = [] # unique list in each element
18921893

18931894
for idx, response in enumerate(nyquist_responses):
18941895
resp = response.response
@@ -1938,7 +1939,7 @@ def _parse_linestyle(style_name, allow_false=False):
19381939
p = ax.plot(
19391940
x_reg, y_reg, primary_style[0], color=color, label=label, **kwargs)
19401941
c = p[0].get_color()
1941-
out[idx] += p
1942+
out[idx, 0] += p
19421943

19431944
# Figure out how much to offset the curve: the offset goes from
19441945
# zero at the start of the scaled section to max_curve_offset as
@@ -1950,12 +1951,12 @@ def _parse_linestyle(style_name, allow_false=False):
19501951
x_scl = np.ma.masked_where(scale_mask, resp.real)
19511952
y_scl = np.ma.masked_where(scale_mask, resp.imag)
19521953
if x_scl.count() >= 1 and y_scl.count() >= 1:
1953-
out[idx] += ax.plot(
1954+
out[idx, 1] += ax.plot(
19541955
x_scl * (1 + curve_offset),
19551956
y_scl * (1 + curve_offset),
19561957
primary_style[1], color=c, **kwargs)
19571958
else:
1958-
out[idx] += [None]
1959+
out[idx, 1] += [None]
19591960

19601961
# Plot the primary curve (invisible) for setting arrows
19611962
x, y = resp.real.copy(), resp.imag.copy()
@@ -1970,15 +1971,15 @@ def _parse_linestyle(style_name, allow_false=False):
19701971
# Plot the mirror image
19711972
if mirror_style is not False:
19721973
# Plot the regular and scaled segments
1973-
out[idx] += ax.plot(
1974+
out[idx, 2] += ax.plot(
19741975
x_reg, -y_reg, mirror_style[0], color=c, **kwargs)
19751976
if x_scl.count() >= 1 and y_scl.count() >= 1:
1976-
out[idx] += ax.plot(
1977+
out[idx, 3] += ax.plot(
19771978
x_scl * (1 - curve_offset),
19781979
-y_scl * (1 - curve_offset),
19791980
mirror_style[1], color=c, **kwargs)
19801981
else:
1981-
out[idx] += [None]
1982+
out[idx, 3] += [None]
19821983

19831984
# Add the arrows (on top of an invisible contour)
19841985
x, y = resp.real.copy(), resp.imag.copy()
@@ -1988,12 +1989,15 @@ def _parse_linestyle(style_name, allow_false=False):
19881989
_add_arrows_to_line2D(
19891990
ax, p[0], arrow_pos, arrowstyle=arrow_style, dir=-1)
19901991
else:
1991-
out[idx] += [None, None]
1992+
out[idx, 2] += [None]
1993+
out[idx, 3] += [None]
19921994

19931995
# Mark the start of the curve
19941996
if start_marker:
1995-
ax.plot(resp[0].real, resp[0].imag, start_marker,
1996-
color=c, markersize=start_marker_size)
1997+
segment = 0 if 0 in rescale_idx else 1 # regular vs scaled
1998+
out[idx, segment] += ax.plot(
1999+
resp[0].real, resp[0].imag, start_marker,
2000+
color=c, markersize=start_marker_size)
19972001

19982002
# Mark the -1 point
19992003
ax.plot([-1], [0], 'r+')

control/tests/descfcn_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,11 +190,11 @@ def test_describing_function_plot():
190190

191191
cplt = response.plot()
192192
assert len(plt.gcf().get_axes()) == 1 # make sure there is a plot
193-
assert len(cplt.lines[0]) == 4 and len(cplt.lines[1]) == 1
193+
assert len(cplt.lines[0]) == 5 and len(cplt.lines[1]) == 1
194194

195195
# Call plot directly
196196
cplt = ct.describing_function_plot(H_larger, F_saturation, amp, omega)
197-
assert len(cplt.lines[0]) == 4 and len(cplt.lines[1]) == 1
197+
assert len(cplt.lines[0]) == 5 and len(cplt.lines[1]) == 1
198198

199199

200200
def test_describing_function_exceptions():

control/tests/nyquist_test.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -406,15 +406,16 @@ def test_linestyle_checks():
406406
# Set the line styles
407407
cplt = ct.nyquist_plot(
408408
sys, primary_style=[':', ':'], mirror_style=[':', ':'])
409-
assert all([line.get_linestyle() == ':' for line in cplt.lines[0]])
409+
assert all([lines[0].get_linestyle() == ':' for lines in cplt.lines[0, :]])
410410

411411
# Set the line colors
412412
cplt = ct.nyquist_plot(sys, color='g')
413-
assert all([line.get_color() == 'g' for line in cplt.lines[0]])
413+
assert all([line.get_color() == 'g' for line in cplt.lines[0, 0]])
414414

415415
# Turn off the mirror image
416416
cplt = ct.nyquist_plot(sys, mirror_style=False)
417-
assert cplt.lines[0][2:] == [None, None]
417+
assert cplt.lines[0, 2] == [None]
418+
assert cplt.lines[0, 3] == [None]
418419

419420
with pytest.raises(ValueError, match="invalid 'primary_style'"):
420421
ct.nyquist_plot(sys, primary_style=False)
@@ -532,22 +533,31 @@ def test_nyquist_rescale():
532533
sys.name = 'How example'
533534

534535
# Default case
535-
cplt = ct.nyquist_plot(sys, indent_direction='left', label='default [0.15]')
536+
resp = ct.nyquist_response(sys, indent_direction='left')
537+
cplt = resp.plot(label='default [0.15]')
538+
assert len(cplt.lines[0, 0]) == 2
539+
assert all([len(cplt.lines[0, i]) == 1 for i in range(1, 4)])
536540

537541
# Sharper corner
538542
cplt = ct.nyquist_plot(
539543
sys*4, indent_direction='left',
540544
max_curve_magnitude=17, blend_fraction=0.05, label='fraction=0.05')
545+
assert len(cplt.lines[0, 0]) == 2
546+
assert all([len(cplt.lines[0, i]) == 1 for i in range(1, 4)])
541547

542548
# More gradual corner
543549
cplt = ct.nyquist_plot(
544550
sys*0.25, indent_direction='left',
545551
max_curve_magnitude=13, blend_fraction=0.25, label='fraction=0.25')
552+
assert len(cplt.lines[0, 0]) == 2
553+
assert all([len(cplt.lines[0, i]) == 1 for i in range(1, 4)])
546554

547555
# No corner
548556
cplt = ct.nyquist_plot(
549557
sys*12, indent_direction='left',
550558
max_curve_magnitude=19, blend_fraction=0, label='fraction=0')
559+
assert len(cplt.lines[0, 0]) == 2
560+
assert all([len(cplt.lines[0, i]) == 1 for i in range(1, 4)])
551561

552562
# Bad value
553563
with pytest.raises(ValueError, match="blend_fraction must be between"):

examples/cds110-L8b_pvtol-complete-limits.ipynb

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -830,13 +830,13 @@
830830
"\n",
831831
"# Gain margin for Lx\n",
832832
"neg1overgm_x = -0.67 # vary this manually to find intersection with curve\n",
833-
"color = cplt.lines[0][0].get_color()\n",
833+
"color = cplt.lines[0, 0][0].get_color()\n",
834834
"plt.plot(neg1overgm_x, 0, color=color, marker='o', fillstyle='none')\n",
835835
"gm_x = -1/neg1overgm_x\n",
836836
"\n",
837837
"# Gain margin for Ly\n",
838838
"neg1overgm_y = -0.32 # vary this manually to find intersection with curve\n",
839-
"color = cplt.lines[1][0].get_color()\n",
839+
"color = cplt.lines[1, 0][0].get_color()\n",
840840
"plt.plot(neg1overgm_y, 0, color=color, marker='o', fillstyle='none')\n",
841841
"gm_y = -1/neg1overgm_y\n",
842842
"\n",
@@ -885,13 +885,13 @@
885885
"# Phase margin of Lx:\n",
886886
"th_pm_x = 0.14*np.pi\n",
887887
"th_plt_x = np.pi + th_pm_x\n",
888-
"color = cplt.lines[0][0].get_color()\n",
888+
"color = cplt.lines[0, 0][0].get_color()\n",
889889
"plt.plot(np.cos(th_plt_x), np.sin(th_plt_x), color=color, marker='o')\n",
890890
"\n",
891891
"# Phase margin of Ly\n",
892892
"th_pm_y = 0.19*np.pi\n",
893893
"th_plt_y = np.pi + th_pm_y\n",
894-
"color = cplt.lines[1][0].get_color()\n",
894+
"color = cplt.lines[1, 0][0].get_color()\n",
895895
"plt.plot(np.cos(th_plt_y), np.sin(th_plt_y), color=color, marker='o')\n",
896896
"\n",
897897
"print('Margins obtained visually:')\n",
@@ -936,12 +936,12 @@
936936
"\n",
937937
"# Stability margin:\n",
938938
"sm_x = 0.3 # vary this manually to find min which intersects\n",
939-
"color = cplt.lines[0][0].get_color()\n",
939+
"color = cplt.lines[0, 0][0].get_color()\n",
940940
"sm_circle = plt.Circle((-1, 0), sm_x, color=color, fill=False, ls=':')\n",
941941
"cplt.axes[0, 0].add_patch(sm_circle)\n",
942942
"\n",
943943
"sm_y = 0.5 # vary this manually to find min which intersects\n",
944-
"color = cplt.lines[1][0].get_color()\n",
944+
"color = cplt.lines[1, 0][0].get_color()\n",
945945
"sm_circle = plt.Circle((-1, 0), sm_y, color=color, fill=False, ls=':')\n",
946946
"cplt.axes[0, 0].add_patch(sm_circle)\n",
947947
"\n",
@@ -1023,8 +1023,22 @@
10231023
}
10241024
],
10251025
"metadata": {
1026+
"kernelspec": {
1027+
"display_name": "Python 3 (ipykernel)",
1028+
"language": "python",
1029+
"name": "python3"
1030+
},
10261031
"language_info": {
1027-
"name": "python"
1032+
"codemirror_mode": {
1033+
"name": "ipython",
1034+
"version": 3
1035+
},
1036+
"file_extension": ".py",
1037+
"mimetype": "text/x-python",
1038+
"name": "python",
1039+
"nbconvert_exporter": "python",
1040+
"pygments_lexer": "ipython3",
1041+
"version": "3.13.1"
10281042
}
10291043
},
10301044
"nbformat": 4,

0 commit comments

Comments
 (0)