3
3
4
4
Functions for computing system norms.
5
5
6
- Routines in this module:
6
+ Routine in this module:
7
7
8
- norm()
8
+ norm
9
9
10
10
Created on Thu Dec 21 08:06:12 2023
11
11
Author: Henrik Sandberg
16
16
17
17
import control as ct
18
18
19
+ __all__ = ['norm' ]
20
+
19
21
#------------------------------------------------------------------------------
20
22
21
- def norm (system , p = 2 , tol = 1e-6 ):
23
+ def norm (system , p = 2 , tol = 1e-6 , print_warning = True ):
22
24
"""Computes norm of system.
23
25
24
26
Parameters
@@ -30,11 +32,13 @@ def norm(system, p=2, tol=1e-6):
30
32
tol : float
31
33
Relative tolerance for accuracy of L_infinity norm computation. Ignored
32
34
unless p='inf'.
35
+ print_warning : bool
36
+ Print warning message in case norm value may be uncertain.
33
37
34
38
Returns
35
39
-------
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.
38
42
39
43
Notes
40
44
-----
@@ -54,52 +58,114 @@ def norm(system, p=2, tol=1e-6):
54
58
C = G .C
55
59
D = G .D
56
60
57
- if p == 2 : # H_2-norm
61
+ #
62
+ # H_2-norm computation
63
+ #
64
+ if p == 2 :
65
+ # Continuous time case
58
66
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?
60
73
return float ('inf' )
61
74
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
64
96
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?
66
103
return float ('inf' )
67
104
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" :
72
128
def _Hamilton_matrix (gamma ):
73
- """Constructs Hamiltonian matrix."""
129
+ """Constructs Hamiltonian matrix. For internal use. """
74
130
R = Ip * gamma ** 2 - D .T @D
75
131
invR = la .inv (R )
76
132
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 ]])
77
133
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 ():
79
138
Ad = A
80
139
Bd = B
81
140
Cd = C
82
141
Dd = D
83
142
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." )
84
145
return float ('inf' )
85
146
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." )
87
148
return None
149
+
150
+ # Inverse bilinear transformation
88
151
In = np .eye (len (Ad ))
89
152
Adinv = la .inv (Ad + In )
90
153
A = 2 * (Ad - In )@Adinv
91
154
B = 2 * Adinv @Bd
92
155
C = 2 * Cd @Adinv
93
156
D = Dd - Cd @Adinv @Bd
94
-
157
+
158
+ # Continus time case
95
159
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." )
96
162
return float ('inf' )
97
163
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
100
166
Ip = np .eye (len (D ))
101
167
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
103
169
gamu *= 2.0
104
170
105
171
while (gamu - gaml )/ gamu > tol :
@@ -110,5 +176,5 @@ def _Hamilton_matrix(gamma):
110
176
gamu = gam
111
177
return gam
112
178
else :
113
- print ("Norm computation for p =" , p , " currently not supported." )
179
+ print (f "Norm computation for p= { p } currently not supported." )
114
180
return None
0 commit comments