@@ -212,138 +212,6 @@ def nyquist(syslist, omega=None, Plot=True):
212212 plt .plot ([- 1 ], [0 ], 'r+' )
213213
214214 return x , y , omega
215- # Nichols plot
216- # Contributed by Allan McInnes <Allan.McInnes@canterbury.ac.nz>
217- #! TODO: need unit test code
218- def nichols (syslist , omega = None ):
219- """Nichols plot for a system
220-
221- Usage
222- =====
223- magh = nichols(sys, omega=None)
224-
225- Plots a Nichols plot for the system over a (optional) frequency range.
226-
227- Parameters
228- ----------
229- syslist : linsys
230- List of linear input/output systems (single system is OK)
231- omega : freq_range
232- Range of frequencies (list or bounds) in rad/sec
233-
234- Return values
235- -------------
236- None
237- """
238-
239- # If argument was a singleton, turn it into a list
240- if (not getattr (syslist , '__iter__' , False )):
241- syslist = (syslist ,)
242-
243- # Select a default range if none is provided
244- if (omega == None ):
245- omega = default_frequency_range (syslist )
246-
247- for sys in syslist :
248- # Get the magnitude and phase of the system
249- mag , phase , omega = sys .freqresp (omega )
250-
251- # Convert to Nichols-plot format (phase in degrees,
252- # and magnitude in dB)
253- x = unwrap (sp .degrees (phase ), 360 )
254- y = 20 * sp .log10 (mag )
255-
256- # Generate the plot
257- plt .plot (x , y )
258-
259- plt .xlabel ('Phase (deg)' )
260- plt .ylabel ('Magnitude (dB)' )
261- plt .title ('Nichols Plot' )
262-
263- # Mark the -180 point
264- plt .plot ([- 180 ], [0 ], 'r+' )
265-
266- # Nichols grid
267- def nichols_grid ():
268- """Nichols chart grid
269-
270- Usage
271- =====
272- nichols_grid()
273-
274- Plots a Nichols chart grid on the current axis.
275-
276- Parameters
277- ----------
278- None
279-
280- Return values
281- -------------
282- None
283- """
284- mag_min_default = - 40.0 # dB
285- mag_step = 20.0 # dB
286-
287- # Chart defaults
288- phase_min , phase_max , mag_min , mag_max = - 360.0 , 0.0 , mag_min_default , 40.0
289-
290- # Set actual chart bounds based on current plot
291- if plt .gcf ().gca ().has_data ():
292- phase_min , phase_max , mag_min , mag_max = plt .axis ()
293-
294- # M-circle magnitudes.
295- # The "fixed" set are always generated, since this guarantees a recognizable
296- # Nichols chart grid.
297- mags_fixed = np .array ([- 40.0 , - 20.0 , - 12.0 , - 6.0 , - 3.0 , - 1.0 , - 0.5 , 0.0 ,
298- 0.25 , 0.5 , 1.0 , 3.0 , 6.0 , 12.0 ])
299-
300- if mag_min < mag_min_default :
301- # Outside the "fixed" set of magnitudes, the generated M-circles
302- # are extended in steps of 'mag_step' dB to cover anything made
303- # visible by the range of the existing plot
304- mags_adjust = np .arange (mag_step * np .floor (mag_min / mag_step ),
305- mag_min_default , mag_step )
306- mags = np .concatenate ((mags_adjust , mags_fixed ))
307- else :
308- mags = mags_fixed
309-
310- # N-circle phases (should be in the range -360 to 0)
311- phases = np .array ([- 0.25 , - 10.0 , - 20.0 , - 30.0 , - 45.0 , - 60.0 , - 90.0 ,
312- - 120.0 , - 150.0 , - 180.0 , - 210.0 , - 240.0 , - 270.0 ,
313- - 310.0 , - 325.0 , - 340.0 , - 350.0 , - 359.75 ])
314-
315- # Find the M-contours
316- m = m_circles (mags , phase_min = np .min (phases ), phase_max = np .max (phases ))
317- m_mag = 20 * sp .log10 (np .abs (m ))
318- m_phase = sp .mod (sp .degrees (sp .angle (m )), - 360.0 ) # Unwrap
319-
320- # Find the N-contours
321- n = n_circles (phases , mag_min = np .min (mags ), mag_max = np .max (mags ))
322- n_mag = 20 * sp .log10 (np .abs (n ))
323- n_phase = sp .mod (sp .degrees (sp .angle (n )), - 360.0 ) # Unwrap
324-
325- # Plot the contours behind other plot elements.
326- # The "phase offset" is used to produce copies of the chart that cover
327- # the entire range of the plotted data, starting from a base chart computed
328- # over the range -360 < phase < 0 (see above). Given the range
329- # the base chart is computed over, the phase offset should be 0
330- # for -360 < phase_min < 0.
331- phase_offset_min = 360.0 * np .ceil (phase_min / 360.0 )
332- phase_offset_max = 360.0 * np .ceil (phase_max / 360.0 ) + 360.0
333- phase_offsets = np .arange (phase_offset_min , phase_offset_max , 360.0 )
334- for phase_offset in phase_offsets :
335- plt .plot (m_phase + phase_offset , m_mag , color = 'gray' ,
336- linestyle = 'dashed' , zorder = 0 )
337- plt .plot (n_phase + phase_offset , n_mag , color = 'gray' ,
338- linestyle = 'dashed' , zorder = 0 )
339-
340- # Add magnitude labels
341- for x , y , m in zip (m_phase [:][- 1 ], m_mag [:][- 1 ], mags ):
342- align = 'right' if m < 0.0 else 'left'
343- plt .text (x , y , str (m ) + ' dB' , size = 'small' , ha = align )
344-
345- # Make sure axes conform to any pre-existing plot.
346- plt .axis ([phase_min , phase_max , mag_min , mag_max ])
347215
348216# Gang of Four
349217#! TODO: think about how (and whether) to handle lists of systems
@@ -462,84 +330,3 @@ def default_frequency_range(syslist):
462330
463331 return omega
464332
465- # Compute contours of a closed-loop transfer function
466- def closed_loop_contours (Hmag , Hphase ):
467- """Contours of the function H = G/(1+G).
468-
469- Usage
470- =====
471- contours = closed_loop_contours(mags, phases)
472-
473- Parameters
474- ----------
475- mags : array-like
476- Meshgrid array of magnitudes of the contours
477- phases : array-like
478- Meshgrid array of phases in radians of the contours
479-
480- Return values
481- -------------
482- contours : complex array
483- Array of complex numbers corresponding to the contours.
484- """
485- # Compute the contours in H-space
486- H = Hmag * sp .exp (1.j * Hphase )
487-
488- # Invert H = G/(1+G) to get an expression for the contours in G-space
489- return H / (1.0 - H )
490-
491- # M-circle
492- def m_circles (mags , phase_min = - 359.75 , phase_max = - 0.25 ):
493- """Constant-magnitude contours of the function H = G/(1+G).
494-
495- Usage
496- =====
497- contours = m_circles(mags)
498-
499- Parameters
500- ----------
501- mags : array-like
502- Array of magnitudes in dB of the M-circles
503- phase_min : degrees
504- Minimum phase in degrees of the N-circles
505- phase_max : degrees
506- Maximum phase in degrees of the N-circles
507-
508- Return values
509- -------------
510- contours : complex array
511- Array of complex numbers corresponding to the contours.
512- """
513- # Convert magnitudes and phase range into a grid suitable for
514- # building contours
515- phases = sp .radians (sp .linspace (phase_min , phase_max , 500 ))
516- Hmag , Hphase = sp .meshgrid (10.0 ** (mags / 20.0 ), phases )
517- return closed_loop_contours (Hmag , Hphase )
518-
519- # N-circle
520- def n_circles (phases , mag_min = - 40.0 , mag_max = 12.0 ):
521- """Constant-phase contours of the function H = G/(1+G).
522-
523- Usage
524- =====
525- contour = n_circles(angles)
526-
527- Parameters
528- ----------
529- phases : array-like
530- Array of phases in degrees of the N-circles
531- mag_min : dB
532- Minimum magnitude in dB of the N-circles
533- mag_max : dB
534- Maximum magnitude in dB of the N-circles
535-
536- Return values
537- -------------
538- contours : complex array
539- Array of complex numbers corresponding to the contours.
540- """
541- # Convert phases and magnitude range into a grid suitable for
542- # building contours
543- mags = sp .linspace (10 ** (mag_min / 20.0 ), 10 ** (mag_max / 20.0 ), 2000 )
544- Hphase , Hmag = sp .meshgrid (sp .radians (phases ), mags )
545- return closed_loop_contours (Hmag , Hphase )
0 commit comments