13
13
import matplotlib
14
14
import matplotlib .pyplot as plt
15
15
16
- from . import frdata , freqplot , xferfcn
16
+ from . import frdata , freqplot , xferfcn , statesp
17
17
from .exception import ControlMIMONotImplemented
18
18
from .iosys import issiso
19
- from . import ss
20
19
from .ctrlutil import mag2db
21
20
try :
22
21
from slycot import ab13md
23
22
except ImportError :
24
23
ab13md = None
25
24
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' ]
27
27
28
28
# private helper functions
29
29
def _poly_iw (sys ):
@@ -525,12 +525,12 @@ def margin(*args):
525
525
return margin [0 ], margin [1 ], margin [3 ], margin [4 ]
526
526
527
527
def 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 .
529
529
530
530
Parameters
531
531
----------
532
532
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
534
534
omega : ndarray
535
535
1d array of (non-negative) frequencies (rad/s) at which to evaluate
536
536
the disk-based stability margins
@@ -594,13 +594,21 @@ def disk_margins(L, omega, skew = 0.0, returnall = False):
594
594
Control Systems Magazine, Vol. 24, Nr. 1, Feb., pp. 60-76, 2004.
595
595
"""
596
596
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
598
606
if (not L .issiso ()) and (ab13md == None ):
599
607
raise ControlMIMONotImplemented ("Need slycot to compute MIMO disk_margins" )
600
608
601
609
# 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 ))
604
612
605
613
# Loop sensitivity function
606
614
S = I .feedback (L )
@@ -628,7 +636,8 @@ def disk_margins(L, omega, skew = 0.0, returnall = False):
628
636
# For the MIMO case, the norm on (S + (skew - I)/2) assumes a
629
637
# single complex uncertainty block diagonal uncertainty structure.
630
638
# 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 ]
632
641
633
642
# Disk-based gain margin (dB) and phase margin (deg)
634
643
with np .errstate (divide = 'ignore' , invalid = 'ignore' ):
@@ -669,20 +678,18 @@ def disk_margins(L, omega, skew = 0.0, returnall = False):
669
678
(not gmidx != - 1 and float ('inf' )) or DGM [gmidx ][0 ],
670
679
(not DPM .shape [0 ] and float ('inf' )) or DPM [pmidx ][0 ])
671
680
672
- def disk_margin_plot (alpha_max , skew = 0.0 , ax = None ):
681
+ def disk_margin_plot (alpha_max , skew , ax = None ):
673
682
"""Plot region of allowable gain/phase variation, given worst-case disk margin.
674
683
675
684
Parameters
676
685
----------
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)
681
689
skew parameter(s) for disk margin calculation.
682
690
skew = 0 uses the "balanced" sensitivity function 0.5*(S - T)
683
691
skew = 1 uses the sensitivity function S
684
692
skew = -1 uses the complementary sensitivity function T
685
- Note that skew may be a scalar or list.
686
693
ax : axes to plot bounding curve(s) onto
687
694
688
695
Returns
@@ -707,7 +714,7 @@ def disk_margin_plot(alpha_max, skew = 0.0, ax = None):
707
714
>> omega = np.logspace(-1, 2, 1001)
708
715
>>
709
716
>> 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
711
718
>>
712
719
>> DM_plot = []
713
720
>> DM_plot.append(control.disk_margins(L, omega, skew = -1.0)[0]) # T-based (T)
0 commit comments