@@ -387,21 +387,97 @@ def horner(self, s):
387
387
388
388
# Method for generating the frequency response of the system
389
389
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 .
391
391
392
392
mag, phase, omega = self.freqresp(omega)
393
393
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.
398
419
399
420
"""
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
405
481
406
482
# Compute poles and zeros
407
483
def pole (self ):
0 commit comments