Skip to content

Commit 0d6fc8f

Browse files
committed
updated docstrings, userdocs + fixes along the way
1 parent 39a976f commit 0d6fc8f

22 files changed

+475
-186
lines changed

control/descfcn.py

Lines changed: 76 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@
2222
from . import config
2323

2424
__all__ = ['describing_function', 'describing_function_plot',
25-
'describing_function_response', 'DescribingFunctionNonlinearity',
26-
'friction_backlash_nonlinearity', 'relay_hysteresis_nonlinearity',
27-
'saturation_nonlinearity']
25+
'describing_function_response', 'DescribingFunctionResponse',
26+
'DescribingFunctionNonlinearity', 'friction_backlash_nonlinearity',
27+
'relay_hysteresis_nonlinearity', 'saturation_nonlinearity']
2828

2929
# Class for nonlinearities with a built-in describing function
3030
class DescribingFunctionNonlinearity():
@@ -213,13 +213,46 @@ def describing_function(
213213

214214
# Simple class to store the describing function response
215215
class DescribingFunctionResponse:
216+
"""Results of describing function analysis.
217+
218+
Describing functions allow analysis of a linear I/O systems with a
219+
static nonlinear feedback function. The DescribingFunctionResponse
220+
class is used by the :func:`~control.describing_function_response`
221+
function to return the results of a describing function analysis. The
222+
response object can be used to obtain information about the describing
223+
function analysis or generate a Nyquist plot showing the frequency
224+
response of the linear systems and the describing function for the
225+
nonlinear element.
226+
227+
Attributes
228+
----------
229+
response : :class:`~control.FrequencyResponseData`
230+
Frequency response of the linear system component of the system.
231+
intersections : 1D array of 2-tuples or None
232+
A list of all amplitudes and frequencies in which
233+
:math:`H(j\\omega) N(a) = -1`, where :math:`N(a)` is the describing
234+
function associated with `F`, or `None` if there are no such
235+
points. Each pair represents a potential limit cycle for the
236+
closed loop system with amplitude given by the first value of the
237+
tuple and frequency given by the second value.
238+
N_vals : complex array
239+
Complex value of the describing function.
240+
positions : list of complex
241+
Location of the intersections in the complex plane.
242+
243+
"""
216244
def __init__(self, response, N_vals, positions, intersections):
245+
"""Create a describing function response data object."""
217246
self.response = response
218247
self.N_vals = N_vals
219248
self.positions = positions
220249
self.intersections = intersections
221250

222251
def plot(self, **kwargs):
252+
"""Plot the results of a describing function analysis.
253+
254+
See :func:`~control.describing_function_plot` for details.
255+
"""
223256
return describing_function_plot(self, **kwargs)
224257

225258
# Implement iter, getitem, len to allow recovering the intersections
@@ -262,21 +295,27 @@ def describing_function_response(
262295
263296
Returns
264297
-------
265-
intersections : 1D array of 2-tuples or None
266-
A list of all amplitudes and frequencies in which :math:`H(j\\omega)
267-
N(a) = -1`, where :math:`N(a)` is the describing function associated
268-
with `F`, or `None` if there are no such points. Each pair represents
269-
a potential limit cycle for the closed loop system with amplitude
270-
given by the first value of the tuple and frequency given by the
271-
second value.
298+
response : :class:`~control.DescribingFunctionResponse` object
299+
Response object that contains the result of the describing function
300+
analysis. The following information can be retrieved from this
301+
object:
302+
response.intersections : 1D array of 2-tuples or None
303+
A list of all amplitudes and frequencies in which
304+
:math:`H(j\\omega) N(a) = -1`, where :math:`N(a)` is the describing
305+
function associated with `F`, or `None` if there are no such
306+
points. Each pair represents a potential limit cycle for the
307+
closed loop system with amplitude given by the first value of the
308+
tuple and frequency given by the second value.
272309
273310
Examples
274311
--------
275312
>>> H_simple = ct.tf([8], [1, 2, 2, 1])
276313
>>> F_saturation = ct.saturation_nonlinearity(1)
277314
>>> amp = np.linspace(1, 4, 10)
278-
>>> ct.describing_function_response(H_simple, F_saturation, amp) # doctest: +SKIP
315+
>>> response = ct.describing_function_response(H_simple, F_saturation, amp)
316+
>>> response.intersections # doctest: +SKIP
279317
[(3.343844998258643, 1.4142293090899216)]
318+
>>> lines = response.plot()
280319
281320
"""
282321
# Decide whether to turn on warnings or not
@@ -340,14 +379,29 @@ def _cost(x):
340379

341380
def describing_function_plot(
342381
*sysdata, label="%5.2g @ %-5.2g", **kwargs):
343-
"""Plot a Nyquist plot with a describing function for a nonlinear system.
382+
"""describing_function_plot(data, *args, **kwargs)
383+
384+
Plot a Nyquist plot with a describing function for a nonlinear system.
344385
345386
This function generates a Nyquist plot for a closed loop system
346387
consisting of a linear system with a static nonlinear function in the
347388
feedback path.
348389
390+
The function may be called in one of two forms:
391+
392+
describing_function_plot(response[, options])
393+
394+
describing_function_plot(H, F, A[, omega[, options]])
395+
396+
In the first form, the response should be generated using the
397+
:func:`~control.describing_function_response` function. In the second
398+
form, that function is called internally, with the listed arguments.
399+
349400
Parameters
350401
----------
402+
data : :class:`~control.DescribingFunctionData`
403+
A describing function response data object created by
404+
:func:`~control.describing_function_response`.
351405
H : LTI system
352406
Linear time-invariant (LTI) system (state space, transfer function, or
353407
FRD)
@@ -357,7 +411,9 @@ def describing_function_plot(
357411
A : list
358412
List of amplitudes to be used for the describing function plot.
359413
omega : list, optional
360-
List of frequencies to be used for the linear system Nyquist curve.
414+
List of frequencies to be used for the linear system Nyquist
415+
curve. If not specified (or None), frequencies are computed
416+
automatically based on the properties of the linear system.
361417
refine : bool, optional
362418
If True (default), refine the location of the intersection of the
363419
Nyquist curve for the linear system and the describing function to
@@ -368,21 +424,19 @@ def describing_function_plot(
368424
369425
Returns
370426
-------
371-
intersections : 1D array of 2-tuples or None
372-
A list of all amplitudes and frequencies in which :math:`H(j\\omega)
373-
N(a) = -1`, where :math:`N(a)` is the describing function associated
374-
with `F`, or `None` if there are no such points. Each pair represents
375-
a potential limit cycle for the closed loop system with amplitude
376-
given by the first value of the tuple and frequency given by the
377-
second value.
427+
lines : 1D array of Line2D
428+
Arrray of Line2D objects for each line in the plot. The first
429+
element of the array is a list of lines (typically only one) for
430+
the Nyquist plot of the linear I/O styem. The second element of
431+
the array is a list of lines (typically only one) for the
432+
describing function curve.
378433
379434
Examples
380435
--------
381436
>>> H_simple = ct.tf([8], [1, 2, 2, 1])
382437
>>> F_saturation = ct.saturation_nonlinearity(1)
383438
>>> amp = np.linspace(1, 4, 10)
384-
>>> ct.describing_function_plot(H_simple, F_saturation, amp) # doctest: +SKIP
385-
[(3.343844998258643, 1.4142293090899216)]
439+
>>> lines = ct.describing_function_plot(H_simple, F_saturation, amp)
386440
387441
"""
388442
# Process keywords

control/frdata.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ class FrequencyResponseData(LTI):
6666
A class for models defined by frequency response data (FRD).
6767
6868
The FrequencyResponseData (FRD) class is used to represent systems in
69-
frequency response data form.
69+
frequency response data form. It can be created manually using the
70+
class constructor, using the :func:~~control.frd` factory function
71+
(preferred), or via the :func:`~control.frequency_response` function.
7072
7173
Parameters
7274
----------
@@ -654,12 +656,27 @@ def feedback(self, other=1, sign=-1):
654656
return FRD(fresp, other.omega, smooth=(self.ifunc is not None))
655657

656658
# Plotting interface
657-
def plot(self, *args, **kwargs):
658-
from .freqplot import bode_plot
659+
def plot(self, plot_type=None, *args, **kwargs):
660+
"""Plot the frequency response using a Bode plot.
659661
660-
# For now, only support Bode plots
661-
# TODO: add 'kind' keyword and Nyquist plots (?)
662-
return bode_plot(self, *args, **kwargs)
662+
Plot the frequency response using either a standard Bode plot
663+
(default) or using a singular values plot (by setting `plot_type`
664+
to 'svplot'). See :func:`~control.bode_plot` and
665+
:func:`~control.singular_values_plot` for more detailed
666+
descriptions.
667+
668+
"""
669+
from .freqplot import bode_plot, singular_values_plot
670+
671+
if plot_type is None:
672+
plot_type = self.plot_type
673+
674+
if plot_type == 'bode':
675+
return bode_plot(self, *args, **kwargs)
676+
elif plot_type == 'svplot':
677+
return singular_values_plot(self, *args, **kwargs)
678+
else:
679+
raise ValueError(f"unknown plot type '{plot_type}'")
663680

664681
# Convert to pandas
665682
def to_pandas(self):

0 commit comments

Comments
 (0)