33
44Functions for computing system norms.
55
6- Routines in this module:
6+ Routine in this module:
77
8- norm()
8+ norm
99
1010Created on Thu Dec 21 08:06:12 2023
1111Author: Henrik Sandberg
1616
1717import control as ct
1818
19+ __all__ = ['norm' ]
20+
1921#------------------------------------------------------------------------------
2022
21- def norm (system , p = 2 , tol = 1e-6 ):
23+ def norm (system , p = 2 , tol = 1e-6 , print_warning = True ):
2224 """Computes norm of system.
2325
2426 Parameters
@@ -30,11 +32,13 @@ def norm(system, p=2, tol=1e-6):
3032 tol : float
3133 Relative tolerance for accuracy of L_infinity norm computation. Ignored
3234 unless p='inf'.
35+ print_warning : bool
36+ Print warning message in case norm value may be uncertain.
3337
3438 Returns
3539 -------
36- norm : float
37- Norm of system
40+ norm_value : float or NoneType
41+ Norm value of system (float) or None if computation could not be completed.
3842
3943 Notes
4044 -----
@@ -54,52 +58,114 @@ def norm(system, p=2, tol=1e-6):
5458 C = G .C
5559 D = G .D
5660
57- if p == 2 : # H_2-norm
61+ #
62+ # H_2-norm computation
63+ #
64+ if p == 2 :
65+ # Continuous time case
5866 if G .isctime ():
59- if (D != 0 ).any () or any (G .poles ().real >= 0 ):
67+ poles_real_part = G .poles ().real
68+ if any (np .isclose (poles_real_part , 0.0 )):
69+ if print_warning :
70+ print ("Warning: Poles close to, or on, the imaginary axis. Norm value may be uncertain." )
71+ return float ('inf' )
72+ elif (D != 0 ).any () or any (poles_real_part > 0.0 ): # System unstable or has direct feedthrough?
6073 return float ('inf' )
6174 else :
62- P = ct .lyap (A , B @B .T )
63- return np .sqrt (np .trace (C @P @C .T ))
75+ try :
76+ P = ct .lyap (A , B @B .T )
77+ except Exception as e :
78+ print (f"An error occurred solving the continuous time Lyapunov equation: { e } " )
79+ return None
80+
81+ # System is stable to reach this point, and P should be positive semi-definite.
82+ # Test next is a precaution in case the Lyapunov equation is ill conditioned.
83+ if any (la .eigvals (P ) < 0.0 ):
84+ if print_warning :
85+ print ("Warning: There appears to be poles close to the imaginary axis. Norm value may be uncertain." )
86+ return float ('inf' )
87+ else :
88+ norm_value = np .sqrt (np .trace (C @P @C .T )) # Argument in sqrt should be non-negative
89+ if np .isnan (norm_value ):
90+ print ("Unknown error. Norm computation resulted in NaN." )
91+ return None
92+ else :
93+ return norm_value
94+
95+ # Discrete time case
6496 elif G .isdtime ():
65- if any (abs (G .poles ()) >= 1 ):
97+ poles_abs = abs (G .poles ())
98+ if any (np .isclose (poles_abs , 1.0 )):
99+ if print_warning :
100+ print ("Warning: Poles close to, or on, the complex unit circle. Norm value may be uncertain." )
101+ return float ('inf' )
102+ elif any (poles_abs > 1.0 ): # System unstable?
66103 return float ('inf' )
67104 else :
68- P = ct .dlyap (A , B @B .T )
69- return np .sqrt (np .trace (C @P @C .T + D @D .T ))
70-
71- elif p == "inf" : # L_infinity-norm
105+ try :
106+ P = ct .dlyap (A , B @B .T )
107+ except Exception as e :
108+ print (f"An error occurred solving the discrete time Lyapunov equation: { e } " )
109+ return None
110+
111+ # System is stable to reach this point, and P should be positive semi-definite.
112+ # Test next is a precaution in case the Lyapunov equation is ill conditioned.
113+ if any (la .eigvals (P ) < 0.0 ):
114+ if print_warning :
115+ print ("Warning: There appears to be poles close to the complex unit circle. Norm value may be uncertain." )
116+ return float ('inf' )
117+ else :
118+ norm_value = np .sqrt (np .trace (C @P @C .T + D @D .T )) # Argument in sqrt should be non-negative
119+ if np .isnan (norm_value ):
120+ print ("Unknown error. Norm computation resulted in NaN." )
121+ return None
122+ else :
123+ return norm_value
124+ #
125+ # L_infinity-norm computation
126+ #
127+ elif p == "inf" :
72128 def _Hamilton_matrix (gamma ):
73- """Constructs Hamiltonian matrix."""
129+ """Constructs Hamiltonian matrix. For internal use. """
74130 R = Ip * gamma ** 2 - D .T @D
75131 invR = la .inv (R )
76132 return np .block ([[A + B @invR @D .T @C , B @invR @B .T ], [- C .T @(Ip + D @invR @D .T )@C , - (A + B @invR @D .T @C ).T ]])
77133
78- if G .isdtime (): # Bilinear transformation of discrete time system to s-plane if no poles at |z|=1 or z=0
134+ # Discrete time case
135+ # Use inverse bilinear transformation of discrete time system to s-plane if no poles on |z|=1 or z=0.
136+ # Allows us to use test for continuous time systems next.
137+ if G .isdtime ():
79138 Ad = A
80139 Bd = B
81140 Cd = C
82141 Dd = D
83142 if any (np .isclose (abs (la .eigvals (Ad )), 1.0 )):
143+ if print_warning :
144+ print ("Warning: Poles close to, or on, the complex unit circle. Norm value may be uncertain." )
84145 return float ('inf' )
85146 elif any (np .isclose (la .eigvals (Ad ), 0.0 )):
86- print ("L_infinity norm computation for discrete time system with pole(s) at z = 0 currently not supported." )
147+ print ("L_infinity norm computation for discrete time system with pole(s) at z= 0 currently not supported." )
87148 return None
149+
150+ # Inverse bilinear transformation
88151 In = np .eye (len (Ad ))
89152 Adinv = la .inv (Ad + In )
90153 A = 2 * (Ad - In )@Adinv
91154 B = 2 * Adinv @Bd
92155 C = 2 * Cd @Adinv
93156 D = Dd - Cd @Adinv @Bd
94-
157+
158+ # Continus time case
95159 if any (np .isclose (la .eigvals (A ).real , 0.0 )):
160+ if print_warning :
161+ print ("Warning: Poles close to, or on, imaginary axis. Norm value may be uncertain." )
96162 return float ('inf' )
97163
98- gaml = la .norm (D ,ord = 2 ) # Lower bound
99- gamu = max (1.0 , 2.0 * gaml ) # Candidate upper bound
164+ gaml = la .norm (D ,ord = 2 ) # Lower bound
165+ gamu = max (1.0 , 2.0 * gaml ) # Candidate upper bound
100166 Ip = np .eye (len (D ))
101167
102- while any (np .isclose (la .eigvals (_Hamilton_matrix (gamu )).real , 0.0 )): # Find an upper bound
168+ while any (np .isclose (la .eigvals (_Hamilton_matrix (gamu )).real , 0.0 )): # Find actual upper bound
103169 gamu *= 2.0
104170
105171 while (gamu - gaml )/ gamu > tol :
@@ -110,5 +176,5 @@ def _Hamilton_matrix(gamma):
110176 gamu = gam
111177 return gam
112178 else :
113- print ("Norm computation for p =" , p , " currently not supported." )
179+ print (f "Norm computation for p= { p } currently not supported." )
114180 return None
0 commit comments