Skip to content

Commit 7b2defe

Browse files
authored
Merge pull request #173 from rabraker/fresp_tb05ad
Use slycot's tb05ad when available for faster and more accurate frequency response
2 parents 7dcb892 + 62a29d9 commit 7b2defe

File tree

1 file changed

+86
-10
lines changed

1 file changed

+86
-10
lines changed

control/statesp.py

Lines changed: 86 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -387,21 +387,97 @@ def horner(self, s):
387387

388388
# Method for generating the frequency response of the system
389389
def freqresp(self, omega):
390-
"""Evaluate the system's transfer func. at a list of ang. frequencies.
390+
"""Evaluate the system's transfer func. at a list of freqs, omega.
391391
392392
mag, phase, omega = self.freqresp(omega)
393393
394-
reports the value of the magnitude, phase, and angular frequency of the
395-
system's transfer function matrix evaluated at s = i * omega, where
396-
omega is a list of angular frequencies, and is a sorted version of the
397-
input omega.
394+
Reports the frequency response of the system,
395+
396+
G(j*omega) = mag*exp(j*phase)
397+
398+
for continuous time. For discrete time systems, the response is
399+
evaluated around the unit circle such that
400+
401+
G(exp(j*omega*dt)) = mag*exp(j*phase).
402+
403+
Inputs:
404+
------
405+
omega: A list of frequencies in radians/sec at which the system
406+
should be evaluated. The list can be either a python list
407+
or a numpy array and will be sorted before evaluation.
408+
409+
Returns:
410+
-------
411+
mag: The magnitude (absolute value, not dB or log10) of the system
412+
frequency response.
413+
414+
phase: The wrapped phase in radians of the system frequency
415+
response.
416+
417+
omega: The list of sorted frequencies at which the response
418+
was evaluated.
398419
399420
"""
400-
# when evaluating at many frequencies, much faster to convert to
401-
# transfer function first and then evaluate, than to solve an
402-
# n-dimensional linear system at each frequency
403-
tf = _convertToTransferFunction(self)
404-
return tf.freqresp(omega)
421+
422+
# In case omega is passed in as a list, rather than a proper array.
423+
omega = np.asarray(omega)
424+
425+
numFreqs = len(omega)
426+
Gfrf = np.empty((self.outputs, self.inputs, numFreqs),
427+
dtype=np.complex128)
428+
429+
# Sort frequency and calculate complex frequencies on either imaginary
430+
# axis (continuous time) or unit circle (discrete time).
431+
omega.sort()
432+
if isdtime(self, strict=True):
433+
dt = timebase(self)
434+
cmplx_freqs = exp(1.j * omega * dt)
435+
if ((omega * dt).any() > pi):
436+
warn_message = ("evalfr: frequency evaluation"
437+
" above Nyquist frequency")
438+
warnings.warn(warn_message)
439+
else:
440+
cmplx_freqs = omega * 1.j
441+
442+
# Do the frequency response evaluation. Use TB05AD from Slycot
443+
# if it's available, otherwise use the built-in horners function.
444+
try:
445+
from slycot import tb05ad
446+
447+
n = np.shape(self.A)[0]
448+
m = self.inputs
449+
p = self.outputs
450+
# The first call both evalates C(sI-A)^-1 B and also returns
451+
# hessenberg transformed matrices at, bt, ct.
452+
result = tb05ad(n, m, p, cmplx_freqs[0], self.A,
453+
self.B, self.C, job='NG')
454+
# When job='NG', result = (at, bt, ct, g_i, hinvb, info)
455+
at = result[0]
456+
bt = result[1]
457+
ct = result[2]
458+
459+
# TB05AD freqency evaluation does not include direct feedthrough.
460+
Gfrf[:, :, 0] = result[3] + self.D
461+
462+
# Now, iterate through the remaining frequencies using the
463+
# transformed state matrices, at, bt, ct.
464+
465+
# Start at the second frequency, already have the first.
466+
for kk, cmplx_freqs_kk in enumerate(cmplx_freqs[1:numFreqs]):
467+
result = tb05ad(n, m, p, cmplx_freqs_kk, at,
468+
bt, ct, job='NH')
469+
# When job='NH', result = (g_i, hinvb, info)
470+
471+
# kk+1 because enumerate starts at kk = 0.
472+
# but zero-th spot is already filled.
473+
Gfrf[:, :, kk+1] = result[0] + self.D
474+
475+
except ImportError: # Slycot unavailable. Fall back to horner.
476+
for kk, cmplx_freqs_kk in enumerate(cmplx_freqs):
477+
Gfrf[:, :, kk] = self.horner(cmplx_freqs_kk)
478+
479+
# mag phase omega
480+
return np.abs(Gfrf), np.angle(Gfrf), omega
405481

406482
# Compute poles and zeros
407483
def pole(self):

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy