Merge pull request #1011 from murrayrm/freqresp_improvements-16Apr2024 · python-control/python-control@feeb56a
1-# Copyright (c) 2010 by California Institute of Technology
2-# Copyright (c) 2012 by Delft University of Technology
3-# All rights reserved.
4-#
5-# Redistribution and use in source and binary forms, with or without
6-# modification, are permitted provided that the following conditions
7-# are met:
8-#
9-# 1. Redistributions of source code must retain the above copyright
10-# notice, this list of conditions and the following disclaimer.
11-#
12-# 2. Redistributions in binary form must reproduce the above copyright
13-# notice, this list of conditions and the following disclaimer in the
14-# documentation and/or other materials provided with the distribution.
15-#
16-# 3. Neither the names of the California Institute of Technology nor
17-# the Delft University of Technology nor
18-# the names of its contributors may be used to endorse or promote
19-# products derived from this software without specific prior
20-# written permission.
21-#
22-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25-# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CALTECH
26-# OR THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
28-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
29-# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
30-# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31-# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
32-# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33-# SUCH DAMAGE.
1+# frdata.py - frequency response data representation and functions
342#
353# Author: M.M. (Rene) van Paassen (using xferfcn.py as basis)
364# Date: 02 Oct 12
37538-396"""
407Frequency response data representation and functions.
418429This module contains the FRD class and also functions that operate on
4310FRD data.
4411"""
451246-# External function declarations
4713from copy import copy
4814from warnings import warn
49155016import numpy as np
51-from numpy import angle, array, empty, ones, \
52-real, imag, absolute, eye, linalg, where, sort
53-from scipy.interpolate import splprep, splev
17+from numpy import absolute, angle, array, empty, eye, imag, linalg, ones, \
18+real, sort, where
19+from scipy.interpolate import splev, splprep
542055-from .lti import LTI, _process_frequency_response
21+from . import config
5622from .exception import pandas_check
5723from .iosys import InputOutputSystem, _process_iosys_keywords, common_timebase
58-from . import config
24+from .lti import LTI, _process_frequency_response
59256026__all__ = ['FrequencyResponseData', 'FRD', 'frd']
6127@@ -100,6 +66,10 @@ class constructor, using the :func:~~control.frd` factory function
10066 dt : float, True, or None
10167 System timebase.
1026869+ See Also
70+ --------
71+ frd
72+10373 Notes
10474 -----
10575 The main data members are 'omega' and 'fresp', where 'omega' is a 1D array
@@ -120,7 +90,6 @@ class constructor, using the :func:~~control.frd` factory function
12090 for a more detailed description.
1219112292 """
123-12493#
12594# Class attributes
12695#
@@ -206,11 +175,12 @@ def __init__(self, *args, **kwargs):
206175"Needs 1 or 2 arguments; received %i." % len(args))
207176208177#
209-# Process key word arguments
178+# Process keyword arguments
210179#
211180212-# If data was generated by a system, keep track of that
213-self.sysname = kwargs.pop('sysname', None)
181+# If data was generated by a system, keep track of that (used when
182+# plotting data). Otherwise, use the system name, if given.
183+self.sysname = kwargs.pop('sysname', kwargs.get('name', None))
214184215185# Keep track of default properties for plotting
216186self.plot_phase = kwargs.pop('plot_phase', None)
@@ -280,7 +250,7 @@ def __str__(self):
280250"""String representation of the transfer function."""
281251282252mimo = self.ninputs > 1 or self.noutputs > 1
283-outstr = ['Frequency response data']
253+outstr = [f"{InputOutputSystem.__str__(self)}"]
284254285255for i in range(self.ninputs):
286256for j in range(self.noutputs):
@@ -322,7 +292,7 @@ def __add__(self, other):
322292323293# Convert the second argument to a frequency response function.
324294# or re-base the frd to the current omega (if needed)
325-other = _convert_to_FRD(other, omega=self.omega)
295+other = _convert_to_frd(other, omega=self.omega)
326296327297# Check that the input-output sizes are consistent.
328298if self.ninputs != other.ninputs:
@@ -359,7 +329,7 @@ def __mul__(self, other):
359329return FRD(self.fresp * other, self.omega,
360330smooth=(self.ifunc is not None))
361331else:
362-other = _convert_to_FRD(other, omega=self.omega)
332+other = _convert_to_frd(other, omega=self.omega)
363333364334# Check that the input-output sizes are consistent.
365335if self.ninputs != other.noutputs:
@@ -386,7 +356,7 @@ def __rmul__(self, other):
386356return FRD(self.fresp * other, self.omega,
387357smooth=(self.ifunc is not None))
388358else:
389-other = _convert_to_FRD(other, omega=self.omega)
359+other = _convert_to_frd(other, omega=self.omega)
390360391361# Check that the input-output sizes are consistent.
392362if self.noutputs != other.ninputs:
@@ -414,7 +384,7 @@ def __truediv__(self, other):
414384return FRD(self.fresp * (1/other), self.omega,
415385smooth=(self.ifunc is not None))
416386else:
417-other = _convert_to_FRD(other, omega=self.omega)
387+other = _convert_to_frd(other, omega=self.omega)
418388419389if (self.ninputs > 1 or self.noutputs > 1 or
420390other.ninputs > 1 or other.noutputs > 1):
@@ -433,7 +403,7 @@ def __rtruediv__(self, other):
433403return FRD(other / self.fresp, self.omega,
434404smooth=(self.ifunc is not None))
435405else:
436-other = _convert_to_FRD(other, omega=self.omega)
406+other = _convert_to_frd(other, omega=self.omega)
437407438408if (self.ninputs > 1 or self.noutputs > 1 or
439409other.ninputs > 1 or other.noutputs > 1):
@@ -572,8 +542,8 @@ def __call__(self, s=None, squeeze=None, return_magphase=None):
572542 ------
573543 ValueError
574544 If `s` is not purely imaginary, because
575- :class:`FrequencyDomainData` systems are only defined at imaginary
576- frequency values.
545+ :class:`FrequencyResponseData` systems are only defined at
546+ imaginary values (corresponding to real frequencies).
577547578548 """
579549if s is None:
@@ -638,7 +608,7 @@ def freqresp(self, omega):
638608def feedback(self, other=1, sign=-1):
639609"""Feedback interconnection between two FRD objects."""
640610641-other = _convert_to_FRD(other, omega=self.omega)
611+other = _convert_to_frd(other, omega=self.omega)
642612643613if (self.noutputs != other.ninputs or self.ninputs != other.noutputs):
644614raise ValueError(
@@ -710,7 +680,7 @@ def to_pandas(self):
710680FRD = FrequencyResponseData
711681712682713-def _convert_to_FRD(sys, omega, inputs=1, outputs=1):
683+def _convert_to_frd(sys, omega, inputs=1, outputs=1):
714684"""Convert a system to frequency response data form (if needed).
715685716686 If sys is already an frd, and its frequency range matches or
@@ -721,14 +691,14 @@ def _convert_to_FRD(sys, omega, inputs=1, outputs=1):
721691 manually, as in:
722692723693 >>> import numpy as np
724- >>> from control.frdata import _convert_to_FRD
694+ >>> from control.frdata import _convert_to_frd
725695726696 >>> omega = np.logspace(-1, 1)
727- >>> frd = _convert_to_FRD(3., omega) # Assumes inputs = outputs = 1
697+ >>> frd = _convert_to_frd(3., omega) # Assumes inputs = outputs = 1
728698 >>> frd.ninputs, frd.noutputs
729699 (1, 1)
730700731- >>> frd = _convert_to_FRD(1., omega, inputs=3, outputs=2)
701+ >>> frd = _convert_to_frd(1., omega, inputs=3, outputs=2)
732702 >>> frd.ninputs, frd.noutputs
733703 (3, 2)
734704@@ -777,51 +747,67 @@ def _convert_to_FRD(sys, omega, inputs=1, outputs=1):
777747sys.__class__)
778748779749780-def frd(*args):
781-"""frd(d, w)
782-783- Construct a frequency response data model.
750+def frd(*args, **kwargs):
751+"""frd(response, omega[, dt])
784752785- frd models store the (measured) frequency response of a system.
753+ Construct a frequency response data (FRD) model.
786754787- This function can be called in different ways:
755+ A frequency response data model stores the (measured) frequency response
756+ of a system. This factory function can be called in different ways:
788757789- ``frd(response, freqs)``
758+ ``frd(response, omega)``
790759 Create an frd model with the given response data, in the form of
791- complex response vector, at matching frequency freqs [in rad/s]
760+ complex response vector, at matching frequencies ``omega`` [in rad/s].
792761793- ``frd(sys, freqs)``
762+ ``frd(sys, omega)``
794763 Convert an LTI system into an frd model with data at frequencies
795- freqs.
764+ ``omega``.
796765797766 Parameters
798767 ----------
799- response: array_like, or list
800- complex vector with the system response
801- freq: array_lik or lis
802- vector with frequencies
803- sys: LTI (StateSpace or TransferFunction)
804- A linear system
768+ response : array_like or LTI system
769+ Complex vector with the system response or an LTI system that can
770+ be used to copmute the frequency response at a list of frequencies.
771+ omega : array_like
772+ Vector of frequencies at which the response is evaluated.
773+ dt : float, True, or None
774+ System timebase.
775+ smooth : bool, optional
776+ If ``True``, create an interpolation function that allows the
777+ frequency response to be computed at any frequency within the range
778+ of frequencies give in ``omega``. If ``False`` (default),
779+ frequency response can only be obtained at the frequencies
780+ specified in ``omega``.
805781806782 Returns
807783 -------
808- sys: FRD
809- New frequency response system
784+ sys : :class:`FrequencyResponseData`
785+ New frequency response data system.
786+787+ Other Parameters
788+ ----------------
789+ inputs, outputs : str, or list of str, optional
790+ List of strings that name the individual signals of the transformed
791+ system. If not given, the inputs and outputs are the same as the
792+ original system.
793+ name : string, optional
794+ System name. If unspecified, a generic name <sys[id]> is generated
795+ with a unique integer id.
810796811797 See Also
812798 --------
813- FRD, ss, tf
799+ FrequencyResponseData, frequency_response, ss, tf
814800815801 Examples
816802 --------
817803 >>> # Create from measurements
818804 >>> response = [1.0, 1.0, 0.5]
819- >>> freqs = [1, 10, 100]
820- >>> F = ct.frd(response, freqs)
805+ >>> omega = [1, 10, 100]
806+ >>> F = ct.frd(response, omega)
821807822808 >>> G = ct.tf([1], [1, 1])
823- >>> freqs = [1, 10, 100]
824- >>> F = ct.frd(G, freqs)
809+ >>> omega = [1, 10, 100]
810+ >>> F = ct.frd(G, omega)
825811826812 """
827-return FRD(*args)
813+return FrequencyResponseData(*args, **kwargs)