8000 additional updates based on @roryyorke review feedback · python-control/python-control@f5bca43 · GitHub
[go: up one dir, main page]

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)
0