8000 nichols_grid returns artists of grid elements; further tighten phase … · python-control/python-control@3281a8d · GitHub
[go: up one dir, main page]

Skip to content 10000

Commit 3281a8d

Browse files
committed
nichols_grid returns artists of grid elements; further tighten phase extent
1 parent 782e526 commit 3281a8d

File tree

1 file changed

+47
-21
lines changed

1 file changed

+47
-21
lines changed

control/nichols.py

Lines changed: 47 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,19 @@ def nichols_grid(cl_mags=None, cl_phases=None, line_style='dotted', ax=None,
153153
Axes to add grid to. If ``None``, use ``plt.gca()``.
154154
label_cl_phases: bool, optional
155155
If True, closed-loop phase lines will be labelled.
156+
157+
Returns
158+
-------
159+
cl_mag_lines: list of `matplotlib.line.Line2D`
160+
The constant closed-loop gain contours
161+
cl_phase_lines: list of `matplotlib.line.Line2D`
162+
The constant closed-loop phase contours
163+
cl_mag_labels: list of `matplotlib.text.Text`
164+
mcontour labels; each entry corresponds to the respective entry
165+
in ``cl_mag_lines``
166+
cl_phase_labels: list of `matplotlib.text.Text`
167+
ncontour labels; each entry corresponds to the respective entry
168+
in ``cl_phase_lines``
156169
"""
157170
if ax is None:
158171
ax = plt.gca()
@@ -163,8 +176,8 @@ def nichols_grid(cl_mags=None, cl_phases=None, line_style='dotted', ax=None,
163176
ol_mag_min = -40.0
164177
ol_mag_max = default_ol_mag_max = 50.0
165178

166-
# Find bounds of the current dataset, if there is one.
167179
if ax.has_data():
180+
# Find extent of intersection the current dataset or view
168181
ol_phase_min, ol_mag_min, ol_phase_max, ol_mag_max = _inner_extents(ax)
169182

170183
# M-circle magnitudes.
@@ -184,20 +197,22 @@ def nichols_grid(cl_mags=None, cl_phases=None, line_style='dotted', ax=None,
184197
ol_mag_min + cl_mag_step, cl_mag_step)
185198
cl_mags = np.concatenate((extended_cl_mags, key_cl_mags))
186199

187-
phase_offset_min = 360.0*np.ceil(ol_phase_min/360.0)
188-
phase_offset_max = 360.0*np.ceil(ol_phase_max/360.0) + 360.0
200+
# a minimum 360deg extent containing the phases
201+
phase_round_max = 360.0*np.ceil(ol_phase_max/360.0)
202+
phase_round_min = min(phase_round_max-360,
203+
360.0*np.floor(ol_phase_min/360.0))
189204

190205
# N-circle phases (should be in the range -360 to 0)
191206
if cl_phases is None:
192207
# aim for 9 lines, but always show (-360+eps, -180, -eps)
193208
# smallest spacing is 45, biggest is 180
194-
phase_span = phase_offset_max - phase_offset_min
209+
phase_span = phase_round_max - phase_round_min
195210
spacing = np.clip(round(phase_span / 8 / 45) * 45, 45, 180)
196211
key_cl_phases = np.array([-0.25, -359.75])
197212
other_cl_phases = np.arange(-spacing, -360.0, -spacing)
198213
cl_phases = np.unique(np.concatenate((key_cl_phases, other_cl_phases)))
199-
else:
200-
assert ((-360.0 < np.min(cl_phases)) and (np.max(cl_phases) < 0.0))
214+
elif not ((-360 < np.min(cl_phases)) and (np.max(cl_phases) < 0.0)):
215+
raise ValueError('cl_phases must between -360 and 0, exclusive')
201216

202217
# Find the M-contours
203218
m = m_circles(cl_mags, phase_min=np.min(cl_phases),
@@ -216,21 +231,29 @@ def nichols_grid(cl_mags=None, cl_phases=None, line_style='dotted', ax=None,
216231
# over the range -360 < phase < 0. Given the range
217232
# the base chart is computed over, the phase offset should be 0
218233
# for -360 < ol_phase_min < 0.
219-
phase_offsets = np.arange(phase_offset_min, phase_offset_max, 360.0)
234+
phase_offsets = 360 + np.arange(phase_round_min, phase_round_max, 360.0)
235+
236+
cl_mag_lines = []
237+
cl_phase_lines = []
238+
cl_mag_labels = []
239+
cl_phase_labels = []
220240

221241
for phase_offset in phase_offsets:
222242
# Draw M and N contours
223-
ax.plot(m_phase + phase_offset, m_mag, color='lightgray',
224-
linestyle=line_style, zorder=0)
225-
ax.plot(n_phase + phase_offset, n_mag, color='lightgray',
226-
linestyle=line_style, zorder=0)
243+
cl_mag_lines.extend(
244+
ax.plot(m_phase + phase_offset, m_mag, color='lightgray',
245+
linestyle=line_style, zorder=0))
246+
cl_phase_lines.extend(
247+
ax.plot(n_phase + phase_offset, n_mag, color='lightgray',
248+
linestyle=line_style, zorder=0))
227249

228250
# Add magnitude labels
229251
for x, y, m in zip(m_phase[:][-1] + phase_offset, m_mag[:][-1],
230252
cl_mags):
231253
align = 'right' if m < 0.0 else 'left'
232-
ax.text(x, y, str(m) + ' dB', size='small', ha=align,
233-
color='gray', clip_on=True)
254+
cl_mag_labels.append(
255+
ax.text(x, y, str(m) + ' dB', size='small', ha=align,
256+
color='gray', clip_on=True))
234257

235258
# phase labels
236259
if label_cl_phases:
@@ -243,20 +266,23 @@ def nichols_grid(cl_mags=None, cl_phases=None, line_style='dotted', ax=None,
243266
align = 'center'
244267
else:
245268
align = 'left'
246-
ax.text(x, y, f'{round(p)}\N{DEGREE SIGN}',
247-
size='small',
248-
ha=align,
249-
va='bottom',
250-
color='gray',
251-
clip_on=True)
269+
cl_phase_labels.append(
270+
ax.text(x, y, f'{round(p)}\N{DEGREE SIGN}',
271+
size='small',
272+
ha=align,
273+
va='bottom',
274+
color='gray',
275+
clip_on=True))
252276

253277

254278
# Fit axes to generated chart
255-
ax.axis([phase_offset_min - 360.0,
256-
phase_offset_max - 360.0,
279+
ax.axis([phase_round_min,
280+
phase_round_max,
257281
np.min(np.concatenate([cl_mags,[ol_mag_min]])),
258282
np.max([ol_mag_max, default_ol_mag_max])])
259283

284+
return cl_mag_lines, cl_phase_lines, cl_mag_labels, cl_phase_labels
285+
260286
#
261287
# Utility functions
262288
#

0 commit comments

Comments
 (0)
0