- Notifications
You must be signed in to change notification settings - Fork441
use __call__ instead of evalfr in lti system classes#449
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Merged
Uh oh!
There was an error while loading.Please reload this page.
Merged
Changes fromall commits
Commits
Show all changes
36 commits Select commitHold shift + click to select a range
1a6d806
eliminate _evalfr in lti system classes, replaced with __call__, whic…
sawyerbfullerb05492c
fixed remaining failing unit tests and cleanup of docfiles
sawyerbfuller2c4ac62
minor fix following suggested change in frd str method
sawyerbfuller6213a54
fixed a few bugs that slipped through
sawyerbfuller8486b7a
Update control/frdata.py
sawyerbfullerc8bf92f
Update control/frdata.py
sawyerbfuller371986c
Update control/lti.py
sawyerbfullerd8fbaa0
Update control/statesp.py
sawyerbfuller17bc2a8
Apply suggestions from code review
sawyerbfuller4ad33db
suggested changes in docstrings
sawyerbfuller94322c8
Merge remote-tracking branch 'origin/call-method' into call-method
sawyerbfuller60433e0
better docstrings conforming to numpydoc
sawyerbfuller262fde6
converted statesp.horner to take care of trying to use slycot_horner …
sawyerbfuller4c3ed0b
renamed slycot_horner to slycot_laub
sawyerbfullerb37c16b
docs more consistent
sawyerbfuller4e4b97f
forgot to change a variable name everywhere in a function
sawyerbfuller5626a3a
revert doc/control.rst
bnavigator5caffa4
Update control/xferfcn.py
sawyerbfullerc888b6e
numpydoc convention updates
sawyerbfullerb64145d
small update to facilitate merge maybe
sawyerbfuller3cf80af
small numpydoc change in trasnferfunction to resolve merge
sawyerbfuller330e517
unit tests now test for deprecation of sys.freqresp
sawyerbfuller8c9571d
Merge branch 'master' into call-method
sawyerbfuller195bec3
rebase sawyerbfuller-call-method against master
murrayrm6b82c3c
Merge commit '195bec3' into call-method-mergeinto
bnavigatorbddc792
fix merge strategy 'errors'
bnavigatore9bff6a
restore test_phase_crossover_frequencies_mimo
bnavigator73fc6a6
reinstate second check in frd_test test_eval()
bnavigator7acfc77
statesp_test: rename from test_evalfr to test_call
bnavigatora26520d
statesp_test: remove duplicate test_pole_static and test_copy_constru…
bnavigatordae8d42
Update control/xferfcn.py
sawyerbfuller3b10af0
check for warning the pytest way
bnavigator1bf445b
add unit tests for freqresp/evalfr warnings/errors
murrayrm3dbaacd
test frd freqresp deprecation
bnavigator178bfa0
doc updates to specify frequency response outputs more precisely
sawyerbfullera631098
missed a couple of doc updates in xferfcn
sawyerbfullerFile filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
4 changes: 2 additions & 2 deletionscontrol/bench/time_freqresp.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
193 changes: 96 additions & 97 deletionscontrol/frdata.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -48,7 +48,7 @@ | ||
from warnings import warn | ||
import numpy as np | ||
from numpy import angle, array, empty, ones, \ | ||
real, imag, absolute, eye, linalg, where, dot, sort | ||
from scipy.interpolate import splprep, splev | ||
from .lti import LTI | ||
@@ -100,24 +100,18 @@ def __init__(self, *args, **kwargs): | ||
object, other than an FRD, call FRD(sys, omega) | ||
""" | ||
# TODO: discrete-time FRD systems? | ||
smooth = kwargs.get('smooth', False) | ||
if len(args) == 2: | ||
if not isinstance(args[0], FRD) and isinstance(args[0], LTI): | ||
# not an FRD, but still a system, second argument should be | ||
# the frequency range | ||
otherlti = args[0] | ||
self.omega = sort(np.asarray(args[1], dtype=float)) | ||
numfreq = len(self.omega) | ||
# calculate frequency response at my points | ||
self.fresp = otherlti(1j * self.omega, squeeze=False) | ||
else: | ||
# The user provided a response and a freq vector | ||
self.fresp = array(args[0], dtype=complex) | ||
@@ -141,7 +135,7 @@ def __init__(self, *args, **kwargs): | ||
self.omega = args[0].omega | ||
self.fresp = args[0].fresp | ||
else: | ||
raise ValueError("Needs 1 or 2 arguments;received %i." % len(args)) | ||
# create interpolation functions | ||
if smooth: | ||
@@ -163,17 +157,17 @@ def __str__(self): | ||
mimo = self.inputs > 1 or self.outputs > 1 | ||
outstr = ['Frequency response data'] | ||
for i in range(self.inputs): | ||
for j in range(self.outputs): | ||
if mimo: | ||
outstr.append("Input %i to output %i:" % (i + 1, j + 1)) | ||
outstr.append('Freq [rad/s] Response') | ||
outstr.append('------------ ---------------------') | ||
outstr.extend( | ||
['%12.3f %10.4g%+10.4gj' % (w, re, im) | ||
for w, re, im in zip(self.omega, | ||
real(self.fresp[j, i, :]), | ||
imag(self.fresp[j, i, :]))]) | ||
return '\n'.join(outstr) | ||
@@ -342,110 +336,116 @@ def __pow__(self, other): | ||
return (FRD(ones(self.fresp.shape), self.omega) / self) * \ | ||
(self**(other+1)) | ||
# Define the `eval` function to evaluate an FRD at a given (real) | ||
# frequency. Note that we choose to use `eval` instead of `evalfr` to | ||
# avoid confusion with :func:`evalfr`, which takes a complex number as its | ||
# argument. Similarly, we don't use `__call__` to avoid confusion between | ||
# G(s) for a transfer function and G(omega) for an FRD object. | ||
# update Sawyer B. Fuller 2020.08.14: __call__ added to provide a uniform | ||
# interface to systems in general and the lti.frequency_response method | ||
def eval(self, omega, squeeze=True): | ||
"""Evaluate a transfer function at angular frequency omega. | ||
Note that a "normal" FRD only returns values for which there is an | ||
entry in the omega vector. An interpolating FRD can return | ||
intermediate values. | ||
Parameters | ||
---------- | ||
omega : float or array_like | ||
Frequencies in radians per second | ||
squeeze : bool, optional (default=True) | ||
If True and `sys` is single input single output (SISO), returns a | ||
1D array rather than a 3D array. | ||
Returns | ||
------- | ||
fresp : (self.outputs, self.inputs, len(x)) or (len(x), ) complex ndarray | ||
The frequency response of the system. Array is ``len(x)`` if and only | ||
if system is SISO and ``squeeze=True``. | ||
""" | ||
omega_array = np.array(omega, ndmin=1) # array-like version of omega | ||
if any(omega_array.imag > 0): | ||
raise ValueError("FRD.eval can only accept real-valued omega") | ||
if self.ifunc is None: | ||
elements = np.isin(self.omega, omega) # binary array | ||
if sum(elements) < len(omega_array): | ||
raise ValueError( | ||
"not all frequencies omega are in frequency list of FRD " | ||
"system. Try an interpolating FRD for additional points.") | ||
else: | ||
out = self.fresp[:, :, elements] | ||
else: | ||
out = empty((self.outputs, self.inputs, len(omega_array)), | ||
dtype=complex) | ||
for i in range(self.outputs): | ||
for j in range(self.inputs): | ||
for k, w in enumerate(omega_array): | ||
frraw = splev(w, self.ifunc[i, j], der=0) | ||
out[i, j, k] = frraw[0] + 1.0j * frraw[1] | ||
if not hasattr(omega, '__len__'): | ||
# omega is a scalar, squeeze down array along last dim | ||
out = np.squeeze(out, axis=2) | ||
if squeeze and self.issiso(): | ||
out = out[0][0] | ||
return out | ||
def __call__(self, s, squeeze=True): | ||
sawyerbfuller marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
"""Evaluate system's transfer function at complex frequencies. | ||
Returns the complex frequency response `sys(s)` of system `sys` with | ||
`m = sys.inputs` number of inputs and `p = sys.outputs` number of | ||
outputs. | ||
To evaluate at a frequency omega in radians per second, enter | ||
``s = omega * 1j`` or use ``sys.eval(omega)`` | ||
Parameters | ||
---------- | ||
s : complex scalar or array_like | ||
Complex frequencies | ||
squeeze : bool, optional (default=True) | ||
If True and `sys` is single input single output (SISO), i.e. `m=1`, | ||
`p=1`, return a 1D array rather than a 3D array. | ||
Returns | ||
------- | ||
fresp : (p, m, len(s)) complex ndarray or (len(s),) complex ndarray | ||
The frequency response of the system. Array is ``(len(s), )`` if | ||
and only if system is SISO and ``squeeze=True``. | ||
Raises | ||
------ | ||
ValueError | ||
If `s` is not purely imaginary, because | ||
:class:`FrequencyDomainData` systems are only defined at imaginary | ||
frequency values. | ||
""" | ||
if any(abs(np.array(s, ndmin=1).real) > 0): | ||
raise ValueError("__call__: FRD systems can only accept " | ||
"purely imaginary frequencies") | ||
# need to preserve array or scalar status | ||
if hasattr(s, '__len__'): | ||
return self.eval(np.asarray(s).imag, squeeze=squeeze) | ||
else: | ||
return self.eval(complex(s).imag, squeeze=squeeze) | ||
def freqresp(self, omega): | ||
"""(deprecated) Evaluate transfer function at complex frequencies. | ||
.. deprecated::0.9.0 | ||
Method has been given the more pythonic name | ||
:meth:`FrequencyResponseData.frequency_response`. Or use | ||
:func:`freqresp` in the MATLAB compatibility module. | ||
""" | ||
warn("FrequencyResponseData.freqresp(omega) will be removed in a " | ||
"future release of python-control; use " | ||
"FrequencyResponseData.frequency_response(omega), or " | ||
"freqresp(sys, omega) in the MATLAB compatibility module " | ||
"instead", DeprecationWarning) | ||
return self.frequency_response(omega) | ||
def feedback(self, other=1, sign=-1): | ||
"""Feedback interconnection between two FRD objects.""" | ||
@@ -515,11 +515,10 @@ def _convertToFRD(sys, omega, inputs=1, outputs=1): | ||
"Frequency ranges of FRD do not match, conversion not implemented") | ||
elif isinstance(sys, LTI): | ||
omega = np.sort(omega) | ||
fresp = sys(1j * omega) | ||
if len(fresp.shape) == 1: | ||
fresp = fresp[np.newaxis, np.newaxis, :] | ||
return FRD(fresp, omega, smooth=True) | ||
elif isinstance(sys, (int, float, complex, np.number)): | ||
24 changes: 12 additions & 12 deletionscontrol/freqplot.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
Oops, something went wrong.
Uh oh!
There was an error while loading.Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.