Skip to content

Commit f5bca43

Browse files
committed
additional updates based on @roryyorke review feedback
1 parent 1965eb3 commit f5bca43

File tree

3 files changed

+35
-38
lines changed

3 files changed

+35
-38
lines changed

control/descfcn.py

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from .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

204201
def 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

control/tests/descfcn_test.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
import numpy as np
1313
import control as ct
1414
import math
15-
from control.descfcn import saturation_nonlinearity, backlash_nonlinearity, \
16-
relay_hysteresis_nonlinearity
15+
from control.descfcn import saturation_nonlinearity, \
16+
friction_backlash_nonlinearity, relay_hysteresis_nonlinearity
1717

1818

1919
# Static function via a class
@@ -84,15 +84,15 @@ def test_saturation_describing_function(satsys):
8484
df_anal = [satfcn.describing_function(a) for a in amprange]
8585

8686
# Compute describing function for a static function
87-
df_fcn = [ct.describing_function(saturation, a) for a in amprange]
87+
df_fcn = ct.describing_function(saturation, amprange)
8888
np.testing.assert_almost_equal(df_fcn, df_anal, decimal=3)
8989

9090
# Compute describing function for a describing function nonlinearity
91-
df_fcn = [ct.describing_function(satfcn, a) for a in amprange]
91+
df_fcn = ct.describing_function(satfcn, amprange)
9292
np.testing.assert_almost_equal(df_fcn, df_anal, decimal=3)
9393

9494
# Compute describing function for a static I/O system
95-
df_sys = [ct.describing_function(satsys, a) for a in amprange]
95+
df_sys = ct.describing_function(satsys, amprange)
9696
np.testing.assert_almost_equal(df_sys, df_anal, decimal=3)
9797

9898
# Compute describing function on an array of values
@@ -109,13 +109,13 @@ class my_saturation(ct.DescribingFunctionNonlinearity):
109109
def __call__(self, x):
110110
return saturation(x)
111111
satfcn_nometh = my_saturation()
112-
df_nometh = [ct.describing_function(satfcn_nometh, a) for a in amprange]
112+
df_nometh = ct.describing_function(satfcn_nometh, amprange)
113113
np.testing.assert_almost_equal(df_nometh, df_anal, decimal=3)
114114

115115

116116
@pytest.mark.parametrize("fcn, amin, amax", [
117117
[saturation_nonlinearity(1), 0, 10],
118-
[backlash_nonlinearity(2), 1, 10],
118+
[friction_backlash_nonlinearity(2), 1, 10],
119119
[relay_hysteresis_nonlinearity(1, 1), 3, 10],
120120
])
121121
def test_describing_function(fcn, amin, amax):
@@ -161,7 +161,7 @@ def test_describing_function_plot():
161161
# Multiple intersections
162162
H_multiple = H_simple * ct.tf(*ct.pade(5, 4)) * 4
163163
omega = np.logspace(-1, 3, 50)
164-
F_backlash = ct.descfcn.backlash_nonlinearity(1)
164+
F_backlash = ct.descfcn.friction_backlash_nonlinearity(1)
165165
amp = np.linspace(0.6, 5, 50)
166166
xsects = ct.describing_function_plot(H_multiple, F_backlash, amp, omega)
167167
for a, w in xsects:

examples/describing_functions.ipynb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@
9797
"metadata": {},
9898
"source": [
9999
"### Backlash nonlinearity\n",
100-
"A backlash nonlinearity can be obtained using the `ct.backlash_nonlinearity` function. This function takes as is argument the size of the backlash region."
100+
"A friction-dominated backlash nonlinearity can be obtained using the `ct.friction_backlash_nonlinearity` function. This function takes as is argument the size of the backlash region."
101101
]
102102
},
103103
{
@@ -119,13 +119,13 @@
119119
}
120120
],
121121
"source": [
122-
"backlash = ct.backlash_nonlinearity(0.5)\n",
122+
"backlash = ct.friction_backlash_nonlinearity(0.5)\n",
123123
"theta = np.linspace(0, 2*np.pi, 50)\n",
124124
"x = np.sin(theta)\n",
125125
"plt.plot(x, [backlash(z) for z in x])\n",
126126
"plt.xlabel(\"Input, x\")\n",
127127
"plt.ylabel(\"Output, y = backlash(x)\")\n",
128-
"plt.title(\"Input/output map for a backlash nonlinearity\");"
128+
"plt.title(\"Input/output map for a friction-dominated backlash nonlinearity\");"
129129
]
130130
},
131131
{
@@ -365,7 +365,7 @@
365365
"omega = np.logspace(-3, 3, 500)\n",
366366
"\n",
367367
"# Nonlinearity\n",
368-
"F_backlash = ct.backlash_nonlinearity(1)\n",
368+
"F_backlash = ct.friction_backlash_nonlinearity(1)\n",
369369
"amp = np.linspace(0.6, 5, 50)\n",
370370
"\n",
371371
"# Describing function plot\n",

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy