Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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
murrayrm merged 36 commits intopython-control:masterfromsawyerbfuller:call-method
Jan 8, 2021
Merged
Show file tree
Hide file tree
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…
sawyerbfullerAug 14, 2020
b05492c
fixed remaining failing unit tests and cleanup of docfiles
sawyerbfullerAug 16, 2020
2c4ac62
minor fix following suggested change in frd str method
sawyerbfullerAug 16, 2020
6213a54
fixed a few bugs that slipped through
sawyerbfullerAug 16, 2020
8486b7a
Update control/frdata.py
sawyerbfullerAug 17, 2020
c8bf92f
Update control/frdata.py
sawyerbfullerAug 17, 2020
371986c
Update control/lti.py
sawyerbfullerAug 17, 2020
d8fbaa0
Update control/statesp.py
sawyerbfullerAug 17, 2020
17bc2a8
Apply suggestions from code review
sawyerbfullerAug 17, 2020
4ad33db
suggested changes in docstrings
sawyerbfullerAug 17, 2020
94322c8
Merge remote-tracking branch 'origin/call-method' into call-method
sawyerbfullerAug 17, 2020
60433e0
better docstrings conforming to numpydoc
sawyerbfullerAug 17, 2020
262fde6
converted statesp.horner to take care of trying to use slycot_horner …
sawyerbfullerAug 17, 2020
4c3ed0b
renamed slycot_horner to slycot_laub
sawyerbfullerAug 17, 2020
b37c16b
docs more consistent
sawyerbfullerAug 17, 2020
4e4b97f
forgot to change a variable name everywhere in a function
sawyerbfullerAug 17, 2020
5626a3a
revert doc/control.rst
bnavigatorAug 18, 2020
5caffa4
Update control/xferfcn.py
sawyerbfullerAug 19, 2020
c888b6e
numpydoc convention updates
sawyerbfullerAug 19, 2020
b64145d
small update to facilitate merge maybe
sawyerbfullerAug 19, 2020
3cf80af
small numpydoc change in trasnferfunction to resolve merge
sawyerbfullerAug 19, 2020
330e517
unit tests now test for deprecation of sys.freqresp
sawyerbfullerAug 19, 2020
8c9571d
Merge branch 'master' into call-method
sawyerbfullerAug 19, 2020
195bec3
rebase sawyerbfuller-call-method against master
murrayrmJan 6, 2021
6b82c3c
Merge commit '195bec3' into call-method-mergeinto
bnavigatorJan 6, 2021
bddc792
fix merge strategy 'errors'
bnavigatorJan 6, 2021
e9bff6a
restore test_phase_crossover_frequencies_mimo
bnavigatorJan 6, 2021
73fc6a6
reinstate second check in frd_test test_eval()
bnavigatorJan 6, 2021
7acfc77
statesp_test: rename from test_evalfr to test_call
bnavigatorJan 6, 2021
a26520d
statesp_test: remove duplicate test_pole_static and test_copy_constru…
bnavigatorJan 6, 2021
dae8d42
Update control/xferfcn.py
sawyerbfullerJan 6, 2021
3b10af0
check for warning the pytest way
bnavigatorJan 6, 2021
1bf445b
add unit tests for freqresp/evalfr warnings/errors
murrayrmJan 7, 2021
3dbaacd
test frd freqresp deprecation
bnavigatorJan 7, 2021
178bfa0
doc updates to specify frequency response outputs more precisely
sawyerbfullerJan 7, 2021
a631098
missed a couple of doc updates in xferfcn
sawyerbfullerJan 7, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletionscontrol/bench/time_freqresp.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,7 +8,7 @@
sys_tf = tf(sys)
w = logspace(-1,1,50)
ntimes = 1000
time_ss = timeit("sys.freqresp(w)", setup="from __main__ import sys, w", number=ntimes)
time_tf = timeit("sys_tf.freqresp(w)", setup="from __main__ import sys_tf, w", number=ntimes)
time_ss = timeit("sys.freqquency_response(w)", setup="from __main__ import sys, w", number=ntimes)
time_tf = timeit("sys_tf.frequency_response(w)", setup="from __main__ import sys_tf, w", number=ntimes)
print("State-space model on %d states: %f" % (nstates, time_ss))
print("Transfer-function model on %d states: %f" % (nstates, time_tf))
193 changes: 96 additions & 97 deletionscontrol/frdata.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
real, imag, absolute, eye, linalg, where, dot, sort
from scipy.interpolate import splprep, splev
from .lti import LTI

Expand DownExpand Up@@ -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 = array(args[1], dtype=float)
self.omega.sort()
self.omega = sort(np.asarray(args[1], dtype=float))
numfreq = len(self.omega)

# calculate frequency response at my points
self.fresp = empty(
(otherlti.outputs, otherlti.inputs, numfreq),
dtype=complex)
for k, w in enumerate(self.omega):
self.fresp[:, :, k] = otherlti._evalfr(w)

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)
Expand All@@ -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;receivd %i." % len(args))
raise ValueError("Needs 1 or 2 arguments;received %i." % len(args))

# create interpolation functions
if smooth:
Expand All@@ -163,17 +157,17 @@ def __str__(self):
mimo = self.inputs > 1 or self.outputs > 1
outstr = ['Frequency response data']

mt, pt, wt = self.freqresp(self.omega)
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, m, p)
for m, p, w in zip(real(self.fresp[j, i, :]),
imag(self.fresp[j, i, :]), wt)])
['%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)

Expand DownExpand Up@@ -342,110 +336,116 @@ def __pow__(self, other):
return (FRD(ones(self.fresp.shape), self.omega) / self) * \
(self**(other+1))

def evalfr(self, omega):
"""Evaluate a transfer function at a single angular frequency.

self._evalfr(omega) returns the value of the frequency response
at 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.

"""
warn("FRD.evalfr(omega) will be deprecated in a future release "
"of python-control; use sys.eval(omega) instead",
PendingDeprecationWarning) # pragma: no coverage
return self._evalfr(omega)

# 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.
def eval(self, omega):
"""Evaluate a transfer function at a single angular frequency.

self.evalfr(omega) returns the value of the frequency response
at frequency omega.
# 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``.
"""
return self._evalfr(omega)

# Internal function to evaluate the frequency responses
def _evalfr(self, omega):
"""Evaluate a transfer function at a single angular frequency."""
# Preallocate the output.
if getattr(omega, '__iter__', False):
out = empty((self.outputs, self.inputs, len(omega)), dtype=complex)
else:
out = empty((self.outputs, self.inputs), dtype=complex)
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:
try:
out = self.fresp[:, :, where(self.omega == omega)[0][0]]
except Exception:
elements = np.isin(self.omega, omega) # binary array
if sum(elements) < len(omega_array):
raise ValueError(
"Frequency %f not in frequency list, try an interpolating"
" FRD if you want additional points" % omega)
else:
if getattr(omega, '__iter__', False):
for i in range(self.outputs):
for j in range(self.inputs):
for k, w in enumerate(omega):
frraw = splev(w, self.ifunc[i, j], der=0)
out[i, j, k] = frraw[0] + 1.0j * frraw[1]
"not all frequencies omega are in frequency list of FRD "
"system. Try an interpolating FRD for additional points.")
else:
for i in range(self.outputs):
for j in range(self.inputs):
frraw = splev(omega, self.ifunc[i, j], der=0)
out[i, j] = frraw[0] + 1.0j * frraw[1]

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

# Method for generating the frequency response of the system
def freqresp(self, omega):
"""Evaluate the frequency response at a list of angular frequencies.
def __call__(self, s, squeeze=True):
"""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.

Reports the value of the magnitude, phase, and angular frequency of
the requency response evaluated at omega, where omega is a list of
angular frequencies, and is a sorted version of the input omega.
To evaluate at a frequency omega in radians per second, enter
``s = omega * 1j`` or use ``sys.eval(omega)``

Parameters
----------
omega : array_like
A list of frequencies in radians/sec at which the system should be
evaluated. The list can be either a python list or a numpy array
and will be sorted before evaluation.
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
-------
mag : (self.outputs, self.inputs, len(omega)) ndarray
The magnitude (absolute value, not dB or log10) of the system
frequency response.
phase : (self.outputs, self.inputs, len(omega)) ndarray
The wrapped phase in radians of the system frequency response.
omega : ndarray or list or tuple
The list of sorted frequencies at which the response was
evaluated.
"""
# Preallocate outputs.
numfreq = len(omega)
mag = empty((self.outputs, self.inputs, numfreq))
phase = empty((self.outputs, self.inputs, numfreq))
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``.

omega.sort()

for k, w in enumerate(omega):
fresp = self._evalfr(w)
mag[:, :, k] = abs(fresp)
phase[:, :, k] = angle(fresp)
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)

return mag, phase, omega
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."""
Expand DownExpand Up@@ -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.sort()
fresp = empty((sys.outputs, sys.inputs, len(omega)), dtype=complex)
for k, w in enumerate(omega):
fresp[:, :, k] = sys._evalfr(w)

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)):
Expand Down
24 changes: 12 additions & 12 deletionscontrol/freqplot.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -151,14 +151,14 @@ def bode_plot(syslist, omega=None,

Notes
-----
1. Alternatively, you may use the lower-levelmethod
``(mag, phase, freq) =sys.freqresp(freq)``to generate the frequency
response for a system, but it returns a MIMO response.
1. Alternatively, you may use the lower-levelmethods
:meth:`LTI.frequency_response` or ``sys(s)`` or ``sys(z)``or to
generate the frequency response for a single system.

2. If a discrete time model is given, the frequency response is plotted
along the upper branch of the unit circle, using the mapping z = exp(j
\\omega dt) where omega ranges from 0 to pi/dt anddt is the discrete
timebase. Ifnottimebaseis specified (dt =True),dt is set to 1.
along the upper branch of the unit circle, using the mapping``z = exp(1j
*omega*dt)`` where`omega` ranges from 0 to`pi/dt` and`dt` is the discrete
timebase. If timebasenot specified (``dt=True``),`dt` is set to 1.

Examples
--------
Expand DownExpand Up@@ -228,7 +228,7 @@ def bode_plot(syslist, omega=None,
nyquistfrq = None

# Get the magnitude and phase of the system
mag_tmp, phase_tmp, omega_sys = sys.freqresp(omega_sys)
mag_tmp, phase_tmp, omega_sys = sys.frequency_response(omega_sys)
mag = np.atleast_1d(np.squeeze(mag_tmp))
phase = np.atleast_1d(np.squeeze(phase_tmp))

Expand DownExpand Up@@ -588,7 +588,7 @@ def nyquist_plot(syslist, omega=None, plot=True, label_freq=0,
"Nyquist is currently only implemented for SISO systems.")
else:
# Get the magnitude and phase of the system
mag_tmp, phase_tmp, omega = sys.freqresp(omega)
mag_tmp, phase_tmp, omega = sys.frequency_response(omega)
mag = np.squeeze(mag_tmp)
phase = np.squeeze(phase_tmp)

Expand DownExpand Up@@ -718,7 +718,7 @@ def gangof4_plot(P, C, omega=None, **kwargs):
omega_plot = omega / (2. * math.pi) if Hz else omega

# TODO: Need to add in the mag = 1 lines
mag_tmp, phase_tmp, omega = S.freqresp(omega)
mag_tmp, phase_tmp, omega = S.frequency_response(omega)
mag = np.squeeze(mag_tmp)
if dB:
plot_axes['s'].semilogx(omega_plot, 20 * np.log10(mag), **kwargs)
Expand All@@ -728,7 +728,7 @@ def gangof4_plot(P, C, omega=None, **kwargs):
plot_axes['s'].tick_params(labelbottom=False)
plot_axes['s'].grid(grid, which='both')

mag_tmp, phase_tmp, omega = (P * S).freqresp(omega)
mag_tmp, phase_tmp, omega = (P * S).frequency_response(omega)
mag = np.squeeze(mag_tmp)
if dB:
plot_axes['ps'].semilogx(omega_plot, 20 * np.log10(mag), **kwargs)
Expand All@@ -738,7 +738,7 @@ def gangof4_plot(P, C, omega=None, **kwargs):
plot_axes['ps'].set_ylabel("$|PS|$" + " (dB)" if dB else "")
plot_axes['ps'].grid(grid, which='both')

mag_tmp, phase_tmp, omega = (C * S).freqresp(omega)
mag_tmp, phase_tmp, omega = (C * S).frequency_response(omega)
mag = np.squeeze(mag_tmp)
if dB:
plot_axes['cs'].semilogx(omega_plot, 20 * np.log10(mag), **kwargs)
Expand All@@ -749,7 +749,7 @@ def gangof4_plot(P, C, omega=None, **kwargs):
plot_axes['cs'].set_ylabel("$|CS|$" + " (dB)" if dB else "")
plot_axes['cs'].grid(grid, which='both')

mag_tmp, phase_tmp, omega = T.freqresp(omega)
mag_tmp, phase_tmp, omega = T.frequency_response(omega)
mag = np.squeeze(mag_tmp)
if dB:
plot_axes['t'].semilogx(omega_plot, 20 * np.log10(mag), **kwargs)
Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp