Skip to content

Commit 306428f

Browse files
committed
added new ngrid() command from Allan McInnes
1 parent d7c606a commit 306428f

2 files changed

Lines changed: 130 additions & 0 deletions

File tree

src/freqplot.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,74 @@ def nichols(syslist, omega=None):
229229
# Mark the -180 point
230230
plt.plot([-180], [0], 'r+')
231231

232+
# Nichols grid
233+
def nichols_grid():
234+
"""Nichols chart grid
235+
236+
Usage
237+
=====
238+
nichols_grid()
239+
240+
Plots a Nichols chart grid on the current axis.
241+
242+
Parameters
243+
----------
244+
None
245+
246+
Return values
247+
-------------
248+
None
249+
"""
250+
gmin_default = -40.0 # dB
251+
gain_step = 20.0 # dB
252+
253+
if plt.gcf().gca().has_data():
254+
pmin, pmax, gmin, gmax = plt.axis()
255+
else:
256+
pmin, pmax = -360.0, 0.0
257+
gmin, gmax = gmin_default, 40.0
258+
259+
# Determine the bounds of the chart
260+
mags_low_end = np.min([gmin_default, gain_step*np.floor(gmin/gain_step)])
261+
phases_low_end = 360.0*np.ceil(pmin/360.0)
262+
phases_high_end = 360.0*(1.0 + np.ceil(pmax/360.0))
263+
264+
# M-circle magnitudes - we adjust the lower end of the range to match
265+
# any existing data
266+
mags_fixed = np.array([-20.0, -12.0, -6.0, -3.0, -1.0, -0.5, 0.0,
267+
0.25, 0.5, 1.0, 3.0, 6.0, 12.0])
268+
mags_adjustable = np.arange(mags_low_end, np.min(mags_fixed), gain_step)
269+
mags = np.concatenate((mags_adjustable, mags_fixed))
270+
271+
# N-circle phases (should be in the range -360 to 0)
272+
phases = np.array([-0.25, -10.0, -20.0, -30.0, -45.0, -60.0, -90.0,
273+
-120.0, -150.0, -180.0, -210.0, -240.0, -270.0,
274+
-310.0, -325.0, -340.0, -350.0, -359.75])
275+
# Find the M-contours
276+
mcs = m_circles(mags, pmin=np.min(phases), pmax=np.max(phases))
277+
mag_m = 20*sp.log10(np.abs(mcs))
278+
phase_m = sp.mod(sp.degrees(sp.angle(mcs)), -360.0) # Unwrap
279+
280+
# Find the N-contours
281+
ncs = n_circles(phases, gmin=np.min(mags), gmax=np.max(mags))
282+
mag_n = 20*sp.log10(np.abs(ncs))
283+
phase_n = sp.mod(sp.degrees(sp.angle(ncs)), -360.0) # Unwrap
284+
285+
# Plot the contours
286+
for phase_offset in np.arange(phases_low_end, phases_high_end, 360.0):
287+
plt.plot(phase_m + phase_offset, mag_m, color='gray',
288+
linestyle='dashed', zorder=0)
289+
plt.plot(phase_n + phase_offset, mag_n, color='gray',
290+
linestyle='dashed', zorder=0)
291+
292+
# Add magnitude labels
293+
for x, y, m in zip(phase_m[:][-1], mag_m[:][-1], mags):
294+
align = 'right' if m < 0.0 else 'left'
295+
plt.text(x, y, str(m) + ' dB', size='small', ha=align)
296+
297+
# Make sure axes conform to any pre-existing plot.
298+
plt.axis([pmin, pmax, gmin, gmax])
299+
232300
# Gang of Four
233301
#! TODO: think about how (and whether) to handle lists of systems
234302
def gangof4(P, C, omega=None):
@@ -333,3 +401,55 @@ def default_frequency_range(syslist):
333401
np.ceil(np.max(features))+1)
334402

335403
return omega
404+
405+
# M-circle
406+
def m_circles(mags, pmin=-359.75, pmax=-0.25):
407+
"""Constant-magnitude contours of the function H = G/(1+G).
408+
409+
Usage
410+
=====
411+
contour = m_circle(mags)
412+
413+
Parameters
414+
----------
415+
mags : array-like
416+
Array of magnitudes in dB of the M-circles
417+
418+
Return values
419+
-------------
420+
contour : complex array
421+
Array of complex numbers corresponding to the contour.
422+
"""
423+
# Compute the contours in H-space
424+
phases = sp.radians(sp.linspace(pmin, pmax, 500))
425+
Hmag, Hphase = sp.meshgrid(10.0**(mags/20.0), phases)
426+
H = Hmag*sp.exp(1.j*Hphase)
427+
428+
# Invert H = G/(1+G) to get an expression for the contour in G-space
429+
return H/(1.0 - H)
430+
431+
# N-circle
432+
def n_circles(phases, gmin=-40.0, gmax=12.0):
433+
"""Constant-phase contours of the function H = G/(1+G).
434+
435+
Usage
436+
=====
437+
contour = n_circle(angles)
438+
439+
Parameters
440+
----------
441+
phases : array-like
442+
Array of phases in degrees of the N-circle
443+
444+
Return values
445+
-------------
446+
contour : complex array
447+
Array of complex numbers corresponding to the contours.
448+
"""
449+
# Compute the contours in H-space
450+
mags = sp.linspace(10**(gmin/20.0), 10**(gmax/20.0), 2000)
451+
Hphase, Hmag = sp.meshgrid(sp.radians(phases), mags)
452+
H = Hmag*sp.exp(1.j*Hphase)
453+
454+
# Invert H = G/(1+G) to get an expression for the contours in G-space
455+
return H/(1.0 - H)

src/matlab.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,16 @@ def bode(*args, **keywords):
371371
# Call the bode command
372372
return freqplot.bode(syslist, omega, **keywords)
373373

374+
# Nichols chart grid
375+
def ngrid():
376+
"""Nichols chart grid.
377+
378+
Usage
379+
=====
380+
ngrid()
381+
"""
382+
freqplot.nichols_grid()
383+
374384
#
375385
# Modifications to scipy.signal functions
376386
#

0 commit comments

Comments
 (0)