1313import matplotlib
1414import matplotlib .pyplot as plt
1515
16- from . import frdata , freqplot , xferfcn
16+ from . import frdata , freqplot , xferfcn , statesp
1717from .exception import ControlMIMONotImplemented
1818from .iosys import issiso
19- from . import ss
2019from .ctrlutil import mag2db
2120try :
2221 from slycot import ab13md
2322except ImportError :
2423 ab13md = None
2524
26- __all__ = ['stability_margins' , 'phase_crossover_frequencies' , 'margin' , 'disk_margins' , 'disk_margin_plot' ]
25+ __all__ = ['stability_margins' , 'phase_crossover_frequencies' , 'margin' ,\
26+ 'disk_margins' , 'disk_margin_plot' ]
2727
2828# private helper functions
2929def _poly_iw (sys ):
@@ -525,12 +525,12 @@ def margin(*args):
525525 return margin [0 ], margin [1 ], margin [3 ], margin [4 ]
526526
527527def disk_margins (L , omega , skew = 0.0 , returnall = False ):
528- """Compute disk-based stability margins for SISO or MIMO LTI system .
528+ """Compute disk-based stability margins for SISO or MIMO LTI loop transfer function .
529529
530530 Parameters
531531 ----------
532532 L : SISO or MIMO LTI system
533- Loop transfer function, e.g. P*C or C*P
533+ Loop transfer function, i.e., P*C or C*P
534534 omega : ndarray
535535 1d array of (non-negative) frequencies (rad/s) at which to evaluate
536536 the disk-based stability margins
@@ -594,13 +594,21 @@ def disk_margins(L, omega, skew = 0.0, returnall = False):
594594 Control Systems Magazine, Vol. 24, Nr. 1, Feb., pp. 60-76, 2004.
595595 """
596596
597- # Check for prerequisites
597+ # First argument must be a system
598+ if not isinstance (L , (statesp .StateSpace , xferfcn .TransferFunction )):
599+ raise ValueError ("Loop gain must be state-space or transfer function object" )
600+
601+ # Loop transfer function must be square
602+ if statesp .ss (L ).B .shape [1 ] != statesp .ss (L ).C .shape [0 ]:
603+ raise ValueError ("Loop gain must be square (n_inputs = n_outputs)" )
604+
605+ # Need slycot if L is MIMO, for mu calculation
598606 if (not L .issiso ()) and (ab13md == None ):
599607 raise ControlMIMONotImplemented ("Need slycot to compute MIMO disk_margins" )
600608
601609 # Get dimensions of feedback system
602- ny , _ = ss (L ).C .shape
603- I = ss ([], [], [], np .eye (ny ))
610+ num_loops = statesp . ss (L ).C .shape [ 0 ]
611+ I = statesp . ss ([], [], [], np .eye (num_loops ))
604612
605613 # Loop sensitivity function
606614 S = I .feedback (L )
@@ -628,7 +636,8 @@ def disk_margins(L, omega, skew = 0.0, returnall = False):
628636 # For the MIMO case, the norm on (S + (skew - I)/2) assumes a
629637 # single complex uncertainty block diagonal uncertainty structure.
630638 # AB13MD provides an upper bound on this norm at the given frequency.
631- DM [ii ] = 1.0 / ab13md (ST_jw [ii ], np .array (ny * [1 ]), np .array (ny * [2 ]))[0 ]
639+ DM [ii ] = 1.0 / ab13md (ST_jw [ii ], np .array (num_loops * [1 ]),\
640+ np .array (num_loops * [2 ]))[0 ]
632641
633642 # Disk-based gain margin (dB) and phase margin (deg)
634643 with np .errstate (divide = 'ignore' , invalid = 'ignore' ):
@@ -669,20 +678,18 @@ def disk_margins(L, omega, skew = 0.0, returnall = False):
669678 (not gmidx != - 1 and float ('inf' )) or DGM [gmidx ][0 ],
670679 (not DPM .shape [0 ] and float ('inf' )) or DPM [pmidx ][0 ])
671680
672- def disk_margin_plot (alpha_max , skew = 0.0 , ax = None ):
681+ def disk_margin_plot (alpha_max , skew , ax = None ):
673682 """Plot region of allowable gain/phase variation, given worst-case disk margin.
674683
675684 Parameters
676685 ----------
677- alpha_max : float
678- worst-case disk margin(s) across all (relevant) frequencies.
679- Note that skew may be a scalar or list.
680- skew : float, optional, default = 0
686+ alpha_max : float (scalar or list)
687+ worst-case disk margin(s) across all frequencies. May be a scalar or list.
688+ skew : float (scalar or list)
681689 skew parameter(s) for disk margin calculation.
682690 skew = 0 uses the "balanced" sensitivity function 0.5*(S - T)
683691 skew = 1 uses the sensitivity function S
684692 skew = -1 uses the complementary sensitivity function T
685- Note that skew may be a scalar or list.
686693 ax : axes to plot bounding curve(s) onto
687694
688695 Returns
@@ -707,7 +714,7 @@ def disk_margin_plot(alpha_max, skew = 0.0, ax = None):
707714 >> omega = np.logspace(-1, 2, 1001)
708715 >>
709716 >> s = control.tf('s') # Laplace variable
710- >> L = 6.25*(s + 3)*(s + 5)/(s*(s + 1)**2*(s**2 + 0.18*s + 100)) # loop transfer function
717+ >> L = 6.25*(s + 3)*(s + 5)/(s*(s + 1)**2*(s**2 + 0.18*s + 100)) # loop gain
711718 >>
712719 >> DM_plot = []
713720 >> DM_plot.append(control.disk_margins(L, omega, skew = -1.0)[0]) # T-based (T)
0 commit comments