2121from .freqplot import nyquist_plot
2222
2323__all__ = ['describing_function' , 'describing_function_plot' ,
24- 'DescribingFunctionNonlinearity' , 'backlash_nonlinearity ' ,
24+ 'DescribingFunctionNonlinearity' , 'friction_backlash_nonlinearity ' ,
2525 'relay_hysteresis_nonlinearity' , 'saturation_nonlinearity' ]
2626
2727# Class for nonlinearities with a built-in describing function
@@ -95,7 +95,7 @@ def describing_function(
9595 use the :class:`~control.DescribingFunctionNonlinearity` class,
9696 which provides this functionality.
9797
98- A : float or array_like
98+ A : array_like
9999 The amplitude(s) at which the describing function should be calculated.
100100
101101 zero_check : bool, optional
@@ -112,25 +112,19 @@ def describing_function(
112112
113113 Returns
114114 -------
115- df : complex or array of complex
116- The (complex) value of the describing function at the given amplitude.
117- If the `A` parameter is an array of amplitudes, then an array of
118- corresponding describing function values is returned.
115+ df : array of complex
116+ The (complex) value of the describing function at the given amplitudes.
119117
120118 Raises
121119 ------
122120 TypeError
123- If A < 0 or if A = 0 and the function F(0) is non-zero.
121+ If A[i] < 0 or if A[i] = 0 and the function F(0) is non-zero.
124122
125123 """
126124 # If there is an analytical solution, trying using that first
127125 if try_method and hasattr (F , 'describing_function' ):
128126 try :
129- # Go through all of the amplitudes we were given
130- df = []
131- for a in np .atleast_1d (A ):
132- df .append (F .describing_function (a ))
133- return np .array (df ).reshape (np .shape (A ))
127+ return np .vectorize (F .describing_function , otypes = [complex ])(A )
134128 except NotImplementedError :
135129 # Drop through and do the numerical computation
136130 pass
@@ -170,17 +164,20 @@ def describing_function(
170164 # See if this is a static nonlinearity (assume not, just in case)
171165 if not hasattr (F , '_isstatic' ) or not F ._isstatic ():
172166 # Initialize any internal state by going through an initial cycle
173- [F (x ) for x in np .atleast_1d (A ).min () * sin_theta ]
167+ for x in np .atleast_1d (A ).min () * sin_theta :
168+ F (x ) # ignore the result
174169
175170 # Go through all of the amplitudes we were given
176- df = []
177- for a in np .atleast_1d (A ):
171+ retdf = np .empty (np .shape (A ), dtype = complex )
172+ df = retdf # Access to the return array
173+ df .shape = (- 1 , ) # as a 1D array
174+ for i , a in enumerate (np .atleast_1d (A )):
178175 # Make sure we got a valid argument
179176 if a == 0 :
180177 # Check to make sure the function has zero output with zero input
181178 if zero_check and np .squeeze (F (0. )) != 0 :
182179 raise ValueError ("function must evaluate to zero at zero" )
183- df . append ( 1. )
180+ df [ i ] = 1.
184181 continue
185182 elif a < 0 :
186183 raise ValueError ("cannot evaluate describing function for A < 0" )
@@ -195,10 +192,10 @@ def describing_function(
195192 df_real = (F_eval @ sin_theta ) * scale # = M_1 \cos\phi / a
196193 df_imag = (F_eval @ cos_theta ) * scale # = M_1 \sin\phi / a
197194
198- df . append ( df_real + 1j * df_imag )
195+ df [ i ] = df_real + 1j * df_imag
199196
200197 # Return the values in the same shape as they were requested
201- return np . array ( df ). reshape ( np . shape ( A ))
198+ return retdf
202199
203200
204201def describing_function_plot (
@@ -437,16 +434,16 @@ def describing_function(self, A):
437434 return df_real + 1j * df_imag
438435
439436
440- # Backlash nonlinearity (#48 in Gelb and Vander Velde, 1968)
441- class backlash_nonlinearity (DescribingFunctionNonlinearity ):
437+ # Friction-dominated backlash nonlinearity (#48 in Gelb and Vander Velde, 1968)
438+ class friction_backlash_nonlinearity (DescribingFunctionNonlinearity ):
442439 """Backlash nonlinearity for use in describing function analysis
443440
444- This class creates a nonlinear function representing a backlash
445- nonlinearity ,including the describing function for the nonlinearity. The
446- following call creates a nonlinear function suitable for describing
447- function analysis:
441+ This class creates a nonlinear function representing a friction-dominated
442+ backlash nonlinearity ,including the describing function for the
443+ nonlinearity. The following call creates a nonlinear function suitable
444+ for describing function analysis:
448445
449- F = backlash_nonlinearity (b)
446+ F = friction_backlash_nonlinearity (b)
450447
451448 This function maintains an internal state representing the 'center' of a
452449 mechanism with backlash. If the new input is within `b/2` of the current
@@ -457,7 +454,7 @@ class backlash_nonlinearity(DescribingFunctionNonlinearity):
457454
458455 def __init__ (self , b ):
459456 # Create the describing function nonlinearity object
460- super (backlash_nonlinearity , self ).__init__ ()
457+ super (friction_backlash_nonlinearity , self ).__init__ ()
461458
462459 self .b = b # backlash distance
463460 self .center = 0 # current center position
0 commit comments