@@ -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
234302def 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 )
0 commit comments