21
21
from .freqplot import nyquist_plot
22
22
23
23
__all__ = ['describing_function' , 'describing_function_plot' ,
24
- 'DescribingFunctionNonlinearity' , 'backlash_nonlinearity ' ,
24
+ 'DescribingFunctionNonlinearity' , 'friction_backlash_nonlinearity ' ,
25
25
'relay_hysteresis_nonlinearity' , 'saturation_nonlinearity' ]
26
26
27
27
# Class for nonlinearities with a built-in describing function
@@ -95,7 +95,7 @@ def describing_function(
95
95
use the :class:`~control.DescribingFunctionNonlinearity` class,
96
96
which provides this functionality.
97
97
98
- A : float or array_like
98
+ A : array_like
99
99
The amplitude(s) at which the describing function should be calculated.
100
100
101
101
zero_check : bool, optional
@@ -112,25 +112,19 @@ def describing_function(
112
112
113
113
Returns
114
114
-------
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.
119
117
120
118
Raises
121
119
------
122
120
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.
124
122
125
123
"""
126
124
# If there is an analytical solution, trying using that first
127
125
if try_method and hasattr (F , 'describing_function' ):
128
126
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 )
134
128
except NotImplementedError :
135
129
# Drop through and do the numerical computation
136
130
pass
@@ -170,17 +164,20 @@ def describing_function(
170
164
# See if this is a static nonlinearity (assume not, just in case)
171
165
if not hasattr (F , '_isstatic' ) or not F ._isstatic ():
172
166
# 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
174
169
175
170
# 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 )):
178
175
# Make sure we got a valid argument
179
176
if a == 0 :
180
177
# Check to make sure the function has zero output with zero input
181
178
if zero_check and np .squeeze (F (0. )) != 0 :
182
179
raise ValueError ("function must evaluate to zero at zero" )
183
- df . append ( 1. )
180
+ df [ i ] = 1.
184
181
continue
185
182
elif a < 0 :
186
183
raise ValueError ("cannot evaluate describing function for A < 0" )
@@ -195,10 +192,10 @@ def describing_function(
195
192
df_real = (F_eval @ sin_theta ) * scale # = M_1 \cos\phi / a
196
193
df_imag = (F_eval @ cos_theta ) * scale # = M_1 \sin\phi / a
197
194
198
- df . append ( df_real + 1j * df_imag )
195
+ df [ i ] = df_real + 1j * df_imag
199
196
200
197
# Return the values in the same shape as they were requested
201
- return np . array ( df ). reshape ( np . shape ( A ))
198
+ return retdf
202
199
203
200
204
201
def describing_function_plot (
@@ -437,16 +434,16 @@ def describing_function(self, A):
437
434
return df_real + 1j * df_imag
438
435
439
436
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 ):
442
439
"""Backlash nonlinearity for use in describing function analysis
443
440
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:
448
445
449
- F = backlash_nonlinearity (b)
446
+ F = friction_backlash_nonlinearity (b)
450
447
451
448
This function maintains an internal state representing the 'center' of a
452
449
mechanism with backlash. If the new input is within `b/2` of the current
@@ -457,7 +454,7 @@ class backlash_nonlinearity(DescribingFunctionNonlinearity):
457
454
458
455
def __init__ (self , b ):
459
456
# Create the describing function nonlinearity object
460
- super (backlash_nonlinearity , self ).__init__ ()
457
+ super (friction_backlash_nonlinearity , self ).__init__ ()
461
458
462
459
self .b = b # backlash distance
463
460
self .center = 0 # current center position
0 commit comments