Skip to content

Commit 2ac5d9d

Browse files
committed
make _isstatic internal; updated doc/ for descfcn
1 parent 776a814 commit 2ac5d9d

8 files changed

Lines changed: 122 additions & 41 deletions

File tree

control/descfcn.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
from .freqplot import nyquist_plot
2222

2323
__all__ = ['describing_function', 'describing_function_plot',
24-
'DescribingFunctionNonlinearity']
24+
'DescribingFunctionNonlinearity', 'backlash_nonlinearity',
25+
'relay_hysteresis_nonlinearity', 'saturation_nonlinearity']
2526

2627
# Class for nonlinearities with a built-in describing function
2728
class DescribingFunctionNonlinearity():
@@ -31,7 +32,7 @@ class DescribingFunctionNonlinearity():
3132
that have a analytically defined describing function (accessed via the
3233
:meth:`describing_function` method). Objects using this class should also
3334
implement a `call` method that evaluates the nonlinearity at a given point
34-
and an `isstatic` method that is `True` if the nonlinearity has no
35+
and an `_isstatic` method that is `True` if the nonlinearity has no
3536
internal state.
3637
3738
"""
@@ -54,10 +55,10 @@ def describing_function(self, A):
5455
raise NotImplementedError(
5556
"describing function not implemented for this function")
5657

57-
def isstatic(self):
58+
def _isstatic(self):
5859
"""Return True if the function has not internal state"""
5960
raise NotImplementedError(
60-
"isstatic() not implemented for this function (internal error)")
61+
"_isstatic() not implemented for this function (internal error)")
6162

6263
# Utility function used to compute common describing functions
6364
def _f(self, x):
@@ -157,7 +158,7 @@ def describing_function(
157158
cos_theta = np.cos(theta)
158159

159160
# See if this is a static nonlinearity (assume not, just in case)
160-
if not hasattr(F, 'isstatic') or not F.isstatic():
161+
if not hasattr(F, '_isstatic') or not F._isstatic():
161162
# Initialize any internal state by going through an initial cycle
162163
[F(x) for x in np.atleast_1d(A).min() * sin_theta]
163164

@@ -223,6 +224,14 @@ def describing_function_plot(
223224
given by the first value of the tuple and frequency given by the
224225
second value.
225226
227+
Example
228+
-------
229+
>>> H_simple = ct.tf([8], [1, 2, 2, 1])
230+
>>> F_saturation = ct.descfcn.saturation_nonlinearity(1)
231+
>>> amp = np.linspace(1, 4, 10)
232+
>>> ct.describing_function_plot(H_simple, F_saturation, amp)
233+
[(3.344008947853124, 1.414213099755523)]
234+
226235
"""
227236
# Start by drawing a Nyquist curve
228237
H_real, H_imag, H_omega = nyquist_plot(H, omega, plot=True, **kwargs)
@@ -346,7 +355,7 @@ def __init__(self, ub=1, lb=None):
346355
def __call__(self, x):
347356
return np.maximum(self.lb, np.minimum(x, self.ub))
348357

349-
def isstatic(self):
358+
def _isstatic(self):
350359
return True
351360

352361
def describing_function(self, A):
@@ -369,8 +378,8 @@ class relay_hysteresis_nonlinearity(DescribingFunctionNonlinearity):
369378
This class creates a nonlinear function representing a a relay with
370379
symmetric upper and lower bounds of magnitude `b` and a hysteretic region
371380
of width `c` (using the notation from [FBS2e](https://fbsbook.org),
372-
Example 10.12,including the describing function for the nonlinearity. The
373-
following call creates a nonlinear function suitable for describing
381+
Example 10.12, including the describing function for the nonlinearity.
382+
The following call creates a nonlinear function suitable for describing
374383
function analysis:
375384
376385
F = relay_hysteresis_nonlinearity(b, c)
@@ -402,7 +411,7 @@ def __call__(self, x):
402411
y = self.b
403412
return y
404413

405-
def isstatic(self):
414+
def _isstatic(self):
406415
return False
407416

408417
def describing_function(self, A):
@@ -457,7 +466,7 @@ def __call__(self, x):
457466
y.append(self.center)
458467
return(np.array(y).reshape(x_array.shape))
459468

460-
def isstatic(self):
469+
def _isstatic(self):
461470
return False
462471

463472
def describing_function(self, A):

doc/classes.rst

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,3 @@ that allow for linear, nonlinear, and interconnected elements:
3030
LinearICSystem
3131
LinearIOSystem
3232
NonlinearIOSystem
33-
34-
Additional classes
35-
==================
36-
37-
.. autosummary::
38-
:toctree: generated/
39-
40-
DescribingFunctionNonlinearity
41-

doc/descfcn.rst

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
.. _descfcn-module:
2+
3+
********************
4+
Describing functions
5+
********************
6+
7+
For nonlinear systems consisting of a feedback connection between a
8+
linear system and a static nonlinearity, it is possible to obtain a
9+
generalization of Nyquist's stability criterion based on the idea of
10+
describing functions. The basic concept involves approximating the
11+
response of a static nonlinearity to an input :math:`u = A e^{\omega
12+
t}` as an output :math:`y = N(A) (A e^{\omega t})`, where :math:`N(A)
13+
\in \mathbb{C}` represents the (amplitude-dependent) gain and phase
14+
associated with the nonlinearity.
15+
16+
Stability analysis of a linear system :math:`H(s)` with a feedback
17+
nonlinearity :math:`F(x)` is done by looking for amplitudes :math:`A`
18+
and frequencies :math:`\omega` such that
19+
20+
.. math::
21+
22+
H(j\omega) N(A) = -1
23+
24+
If such an intersection exists, it indicates that there may be a limit
25+
cycle of amplitude :math:`A` with frequency :math:`\omega`.
26+
27+
Describing function analysis is a simple method, but it is approximate
28+
because it assumes that higher harmonics can be neglected.
29+
30+
Module usage
31+
============
32+
33+
The function :func:`~control.describing_function` can be used to
34+
compute the describing function of a nonlinear function::
35+
36+
N = ct.describing_function(F, A)
37+
38+
Stability analysis using describing functions is done by looking for
39+
amplitudes :math:`a` and frequencies :math`\omega` such that
40+
41+
.. math::
42+
43+
H(j\omega) = \frac{-1}{N(A)}
44+
45+
These points can be determined by generating a Nyquist plot in which the
46+
transfer function :math:`H(j\omega)` intersections the negative
47+
reciprocal of the describing function :math:`N(A)`. The
48+
:func:`~control.describing_function_plot` function generates this plot
49+
and returns the amplitude and frequency of any points of intersection::
50+
51+
ct.describing_function_plot(H, F, amp_range[, omega_range])
52+
53+
54+
Pre-defined nonlinearities
55+
==========================
56+
57+
To facilitate the use of common describing functions, the following
58+
nonlinearity constructors are predefined:
59+
60+
.. code:: python
61+
62+
backlash_nonlinearity(b) # backlash nonlinearity with width b
63+
relay_hysteresis_nonlinearity(b, c) # relay output of amplitude b with
64+
# hysteresis of half-width c
65+
saturation_nonlinearity(ub[, lb]) # saturation nonlinearity with upper
66+
# bound and (optional) lower bound
67+
68+
Calling these functions will create an object `F` that can be used for
69+
describing function analysis. For example, to create a saturation
70+
nonlinearity::
71+
72+
F = ct.saturation_nonlinearity(1)
73+
74+
These functions use the
75+
:class:`~control.DescribingFunctionNonlinearity`, which allows an
76+
analytical description of the describing function.
77+
78+
Module classes and functions
79+
============================
80+
.. autosummary::
81+
:toctree: generated/
82+
83+
~control.DescribingFunctionNonlinearity
84+
~control.backlash_nonlinearity
85+
~control.relay_hysteresis_nonlinearity
86+
~control.saturation_nonlinearity

doc/describing_functions.ipynb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../examples/describing_functions.ipynb

doc/examples.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,6 @@ using running examples in FBS2e.
4242
:maxdepth: 1
4343

4444
cruise
45+
describing_functions
4546
steering
4647
pvtol-lqr-nested

doc/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ implements basic operations for analysis and design of feedback control systems.
2929
matlab
3030
flatsys
3131
iosys
32+
descfcn
3233
examples
3334

3435
* :ref:`genindex`

examples/describing_functions.ipynb

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
"cell_type": "markdown",
55
"metadata": {},
66
"source": [
7-
"# Describing Function Analysis Using the Python Control Toolbox (python-control)\n",
8-
"### Richard M. Murray, 27 Jan 2021\n",
9-
"This Jupyter notebook shows how to use the `nltools` module of the Python Control Toolbox to perform describing function analysis of a nonlinear system. A brief introduction to describing functions can be found in [Feedback Systems](https://fbsbook.org), Section 10.5 (Generalized Notions of Gain and Phase)."
7+
"# Describing function analysis\n",
8+
"Richard M. Murray, 27 Jan 2021\n",
9+
"\n",
10+
"This Jupyter notebook shows how to use the `descfcn` module of the Python Control Toolbox to perform describing function analysis of a nonlinear system. A brief introduction to describing functions can be found in [Feedback Systems](https://fbsbook.org), Section 10.5 (Generalized Notions of Gain and Phase)."
1011
]
1112
},
1213
{
@@ -35,7 +36,7 @@
3536
"source": [
3637
"### Saturation nonlinearity\n",
3738
"\n",
38-
"A saturation nonlinearity can be obtained using the `ct.nltools.saturation_nonlinearity` function. This function takes the saturation level as an argument."
39+
"A saturation nonlinearity can be obtained using the `ct.saturation_nonlinearity` function. This function takes the saturation level as an argument."
3940
]
4041
},
4142
{
@@ -57,7 +58,7 @@
5758
}
5859
],
5960
"source": [
60-
"saturation=ct.nltools.saturation_nonlinearity(0.75)\n",
61+
"saturation=ct.saturation_nonlinearity(0.75)\n",
6162
"x = np.linspace(-2, 2, 50)\n",
6263
"plt.plot(x, saturation(x))\n",
6364
"plt.xlabel(\"Input, x\")\n",
@@ -96,7 +97,7 @@
9697
"metadata": {},
9798
"source": [
9899
"### Backlash nonlinearity\n",
99-
"A backlash nonlinearity can be obtained using the `ct.nltools.backlash_nonlinearity` function. This function takes as is argument the size of the backlash region."
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."
100101
]
101102
},
102103
{
@@ -118,7 +119,7 @@
118119
}
119120
],
120121
"source": [
121-
"backlash = ct.nltools.backlash_nonlinearity(0.5)\n",
122+
"backlash = ct.backlash_nonlinearity(0.5)\n",
122123
"theta = np.linspace(0, 2*np.pi, 50)\n",
123124
"x = np.sin(theta)\n",
124125
"plt.plot(x, backlash(x))\n",
@@ -232,7 +233,9 @@
232233
"\n",
233234
"Consider a nonlinear feedback system consisting of a third-order linear system with transfer function $H(s)$ and a saturation nonlinearity having describing function $N(a)$. Stability can be assessed by looking for points at which \n",
234235
"\n",
235-
"$$ H(j\\omega) N(a) = −1$$\n",
236+
"$$\n",
237+
"H(j\\omega) N(a) = -1",
238+
"$$\n",
236239
"\n",
237240
"The `describing_function_plot` function plots $H(j\\omega)$ and $-1/N(a)$ and prints out the the amplitudes and frequencies corresponding to intersections of these curves. "
238241
]
@@ -271,7 +274,7 @@
271274
"omega = np.logspace(-3, 3, 500)\n",
272275
"\n",
273276
"# Nonlinearity\n",
274-
"F_saturation = ct.nltools.saturation_nonlinearity(1)\n",
277+
"F_saturation = ct.saturation_nonlinearity(1)\n",
275278
"amp = np.linspace(00, 5, 50)\n",
276279
"\n",
277280
"# Describing function plot (return value = amp, freq)\n",
@@ -362,7 +365,7 @@
362365
"omega = np.logspace(-3, 3, 500)\n",
363366
"\n",
364367
"# Nonlinearity\n",
365-
"F_backlash = ct.nltools.backlash_nonlinearity(1)\n",
368+
"F_backlash = ct.backlash_nonlinearity(1)\n",
366369
"amp = np.linspace(0.6, 5, 50)\n",
367370
"\n",
368371
"# Describing function plot\n",

examples/steering.ipynb

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,12 @@
55
"metadata": {},
66
"source": [
77
"# Vehicle steering\n",
8-
"Karl J. Astrom and Richard M. Murray \n",
8+
"Karl J. Astrom and Richard M. Murray\n",
99
"23 Jul 2019\n",
1010
"\n",
1111
"This notebook contains the computations for the vehicle steering running example in *Feedback Systems*."
1212
]
1313
},
14-
{
15-
"cell_type": "markdown",
16-
"metadata": {},
17-
"source": [
18-
"RMM comments to Karl, 27 Jun 2019\n",
19-
"* I'm using this notebook to walk through all of the vehicle steering examples and make sure that all of the parameters, conditions, and maximum steering angles are consitent and reasonable.\n",
20-
"* Please feel free to send me comments on the contents as well as the bulletted notes, in whatever form is most convenient.\n",
21-
"* Once we have sorted out all of the settings we want to use, I'll copy over the changes into the MATLAB files that we use for creating the figures in the book.\n",
22-
"* These notes will be removed from the notebook once we have finalized everything."
23-
]
24-
},
2514
{
2615
"cell_type": "code",
2716
"execution_count": 1,

0 commit comments

Comments
 (0)