• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

python-control / python-control / 13107996232

03 Feb 2025 06:53AM UTC coverage: 94.731% (+0.02%) from 94.709%
13107996232

push

github

web-flow
Merge pull request #1094 from murrayrm/userguide-22Dec2024

Updated user documentation (User Guide, Reference Manual)

9673 of 10211 relevant lines covered (94.73%)

8.28 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.65
control/robust.py
1
# robust.py - tools for robust control
2
#
3
# Initial authors: Steve Brunton, Kevin Chen, Lauren Padilla
4
# Creation date: 24 Dec 2010
5

6
"""Robust control synthesis algorithms."""
7

8
import warnings
9✔
9

10
# External packages and modules
11
import numpy as np
9✔
12

13
from .exception import ControlSlycot
9✔
14
from .statesp import StateSpace
9✔
15

16

17
def h2syn(P, nmeas, ncon):
9✔
18
    """H2 control synthesis for plant P.
19

20
    Parameters
21
    ----------
22
    P : `StateSpace`
23
        Partitioned LTI plant (state-space system).
24
    nmeas : int
25
        Number of measurements (input to controller).
26
    ncon : int
27
        Number of control inputs (output from controller).
28

29
    Returns
30
    -------
31
    K : `StateSpace`
32
        Controller to stabilize `P`.
33

34
    Raises
35
    ------
36
    ImportError
37
        If slycot routine sb10hd is not loaded.
38

39
    See Also
40
    --------
41
    StateSpace
42

43
    Examples
44
    --------
45
    >>> # Unstable first order SISO system
46
    >>> G = ct.tf([1], [1, -1], inputs=['u'], outputs=['y'])
47
    >>> all(G.poles() < 0)  # Is G stable?
48
    False
49

50
    >>> # Create partitioned system with trivial unity systems
51
    >>> P11 = ct.tf([0], [1], inputs=['w'], outputs=['z'])
52
    >>> P12 = ct.tf([1], [1], inputs=['u'], outputs=['z'])
53
    >>> P21 = ct.tf([1], [1], inputs=['w'], outputs=['y'])
54
    >>> P22 = G
55
    >>> P = ct.interconnect([P11, P12, P21, P22],
56
    ...                     inplist=['w', 'u'], outlist=['z', 'y'])
57

58
    >>> # Synthesize H2 optimal stabilizing controller
59
    >>> K = ct.h2syn(P, nmeas=1, ncon=1)
60
    >>> T = ct.feedback(G, K, sign=1)
61
    >>> all(T.poles() < 0)  # Is T stable?
62
    True
63

64
    """
65
    # Check for ss system object, need a utility for this?
66

67
    # TODO: Check for continous or discrete, only continuous supported right now
68

69
    try:
5✔
70
        from slycot import sb10hd
5✔
71
    except ImportError:
×
72
        raise ControlSlycot("can't find slycot subroutine sb10hd")
×
73

74
    n = np.size(P.A, 0)
5✔
75
    m = np.size(P.B, 1)
5✔
76
    np_ = np.size(P.C, 0)
5✔
77
    out = sb10hd(n, m, np_, ncon, nmeas, P.A, P.B, P.C, P.D)
5✔
78
    Ak = out[0]
5✔
79
    Bk = out[1]
5✔
80
    Ck = out[2]
5✔
81
    Dk = out[3]
5✔
82

83
    K = StateSpace(Ak, Bk, Ck, Dk)
5✔
84

85
    return K
5✔
86

87

88
def hinfsyn(P, nmeas, ncon):
9✔
89
    # TODO: document significance of rcond
90
    """H-infinity control synthesis for plant P.
91

92
    Parameters
93
    ----------
94
    P : `StateSpace`
95
        Partitioned LTI plant (state-space system).
96
    nmeas : int
97
        Number of measurements (input to controller).
98
    ncon : int
99
        Number of control inputs (output from controller).
100

101
    Returns
102
    -------
103
    K : `StateSpace`
104
        Controller to stabilize `P`.
105
    CL : `StateSpace`
106
        Closed loop system.
107
    gam : float
108
        Infinity norm of closed loop system.
109
    rcond : list
110
        4-vector, reciprocal condition estimates of:
111
            1: control transformation matrix
112
            2: measurement transformation matrix
113
            3: X-Riccati equation
114
            4: Y-Riccati equation
115

116
    Raises
117
    ------
118
    ImportError
119
        If slycot routine sb10ad is not loaded.
120

121
    See Also
122
    --------
123
    StateSpace
124

125
    Examples
126
    --------
127
    >>> # Unstable first order SISO system
128
    >>> G = ct.tf([1], [1,-1], inputs=['u'], outputs=['y'])
129
    >>> all(G.poles() < 0)
130
    False
131

132
    >>> # Create partitioned system with trivial unity systems
133
    >>> P11 = ct.tf([0], [1], inputs=['w'], outputs=['z'])
134
    >>> P12 = ct.tf([1], [1], inputs=['u'], outputs=['z'])
135
    >>> P21 = ct.tf([1], [1], inputs=['w'], outputs=['y'])
136
    >>> P22 = G
137
    >>> P = ct.interconnect([P11, P12, P21, P22], inplist=['w', 'u'], outlist=['z', 'y'])
138

139
    >>> # Synthesize Hinf optimal stabilizing controller
140
    >>> K, CL, gam, rcond = ct.hinfsyn(P, nmeas=1, ncon=1)
141
    >>> T = ct.feedback(G, K, sign=1)
142
    >>> all(T.poles() < 0)
143
    True
144

145
    """
146

147
    # Check for ss system object, need a utility for this?
148

149
    # TODO: Check for continous or discrete, only continuous supported right now
150

151
    try:
5✔
152
        from slycot import sb10ad
5✔
153
    except ImportError:
×
154
        raise ControlSlycot("can't find slycot subroutine sb10ad")
×
155

156
    n = np.size(P.A, 0)
5✔
157
    m = np.size(P.B, 1)
5✔
158
    np_ = np.size(P.C, 0)
5✔
159
    gamma = 1.e100
5✔
160
    out = sb10ad(n, m, np_, ncon, nmeas, gamma, P.A, P.B, P.C, P.D)
5✔
161
    gam = out[0]
5✔
162
    Ak = out[1]
5✔
163
    Bk = out[2]
5✔
164
    Ck = out[3]
5✔
165
    Dk = out[4]
5✔
166
    Ac = out[5]
5✔
167
    Bc = out[6]
5✔
168
    Cc = out[7]
5✔
169
    Dc = out[8]
5✔
170
    rcond = out[9]
5✔
171

172
    K = StateSpace(Ak, Bk, Ck, Dk)
5✔
173
    CL = StateSpace(Ac, Bc, Cc, Dc)
5✔
174

175
    return K, CL, gam, rcond
5✔
176

177

178
def _size_as_needed(w, wname, n):
9✔
179
    """Convert LTI object to appropriately sized StateSpace object.
180

181
    Intended for use in .robust only
182

183
    Parameters
184
    ----------
185
    w: None, 1x1 LTI object, or mxn LTI object
186
    wname: name of w, for error message
187
    n: number of inputs to w
188

189
    Returns
190
    -------
191
    w_: processed weighting function, a `StateSpace` object:
192
        - if w is None, empty `StateSpace` object
193
        - if w is scalar, w_ will be w * eye(n)
194
        - otherwise, w as `StateSpace` object
195

196
    Raises
197
    ------
198
    ValueError
199
        If w is not None or scalar, and does not have n inputs.
200

201
    See Also
202
    --------
203
    augw
204

205
    """
206
    from . import append, ss
5✔
207
    if w is not None:
5✔
208
        if not isinstance(w, StateSpace):
5✔
209
            w = ss(w)
5✔
210
        if 1 == w.ninputs and 1 == w.noutputs:
5✔
211
            w = append(*(w,) * n)
5✔
212
        else:
213
            if w.ninputs != n:
5✔
214
                msg = ("{}: weighting function has {} inputs, expected {}".
5✔
215
                       format(wname, w.ninputs, n))
216
                raise ValueError(msg)
5✔
217
    else:
218
        w = ss([], [], [], [])
5✔
219
    return w
5✔
220

221

222
def augw(g, w1=None, w2=None, w3=None):
9✔
223
    """Augment plant for mixed sensitivity problem.
224

225
    If a weighting is None, no augmentation is done for it.  At least
226
    one weighting must not be None.
227

228
    If a weighting w is scalar, it will be replaced by I*w, where I is
229
    ny-by-ny for `w1` and `w3`, and nu-by-nu for `w2`.
230

231
    Parameters
232
    ----------
233
    g : LTI object, ny-by-nu
234
        Plant.
235
    w1 : None, scalar, or k1-by-ny LTI object
236
        Weighting on S.
237
    w2 : None, scalar, or k2-by-nu LTI object
238
        Weighting on KS.
239
    w3 : None, scalar, or k3-by-ny LTI object
240
        Weighting on T.
241

242
    Returns
243
    -------
244
    p : `StateSpace`
245
        Plant augmented with weightings, suitable for submission to
246
        `hinfsyn` or `h2syn`.
247

248
    Raises
249
    ------
250
    ValueError
251
        If all weightings are None.
252

253
    See Also
254
    --------
255
    h2syn, hinfsyn, mixsyn
256

257
    """
258

259
    from . import append, connect, ss
5✔
260

261
    if w1 is None and w2 is None and w3 is None:
5✔
262
        raise ValueError("At least one weighting must not be None")
5✔
263
    ny = g.noutputs
5✔
264
    nu = g.ninputs
5✔
265

266
    w1, w2, w3 = [_size_as_needed(w, wname, n)
5✔
267
                  for w, wname, n in zip((w1, w2, w3),
268
                                         ('w1', 'w2', 'w3'),
269
                                         (ny, nu, ny))]
270

271
    if not isinstance(g, StateSpace):
5✔
272
        g = ss(g)
5✔
273

274
    #       w         u
275
    #  z1 [ w1   |   -w1*g  ]
276
    #  z2 [ 0    |    w2    ]
277
    #  z3 [ 0    |    w3*g  ]
278
    #     [------+--------- ]
279
    #  v  [ I    |    -g    ]
280

281
    # error summer: inputs are -y and r=w
282
    Ie = ss([], [], [], np.eye(ny))
5✔
283
    # control: needed to "distribute" control input
284
    Iu = ss([], [], [], np.eye(nu))
5✔
285

286
    sysall = append(w1, w2, w3, Ie, g, Iu)
5✔
287

288
    niw1 = w1.ninputs
5✔
289
    niw2 = w2.ninputs
5✔
290
    niw3 = w3.ninputs
5✔
291

292
    now1 = w1.noutputs
5✔
293
    now2 = w2.noutputs
5✔
294
    now3 = w3.noutputs
5✔
295

296
    q = np.zeros((niw1 + niw2 + niw3 + ny + nu, 2))
5✔
297
    q[:, 0] = np.arange(1, q.shape[0] + 1)
5✔
298

299
    # Ie -> w1
300
    q[:niw1, 1] = np.arange(1 + now1 + now2 + now3,
5✔
301
                            1 + now1 + now2 + now3 + niw1)
302

303
    # Iu -> w2
304
    q[niw1:niw1 + niw2, 1] = np.arange(1 + now1 + now2 + now3 + 2 * ny,
5✔
305
                                       1 + now1 + now2 + now3 + 2 * ny + niw2)
306

307
    # y -> w3
308
    q[niw1 + niw2:niw1 + niw2 + niw3, 1] = np.arange(
5✔
309
        1 + now1 + now2 + now3 + ny, 1 + now1 + now2 + now3 + ny + niw3)
310

311
    # -y -> Iy; note the leading -
312
    q[niw1 + niw2 + niw3:niw1 + niw2 + niw3 + ny, 1] = -np.arange(
5✔
313
        1 + now1 + now2 + now3 + ny, 1 + now1 + now2 + now3 + 2 * ny)
314

315
    # Iu -> G
316
    q[niw1 + niw2 + niw3 + ny:niw1 + niw2 + niw3 + ny + nu, 1] = np.arange(
5✔
317
        1 + now1 + now2 + now3 + 2 * ny,
318
        1 + now1 + now2 + now3 + 2 * ny + nu)
319

320
    # input indices: to Ie and Iu
321
    ii = np.hstack((np.arange(1 + now1 + now2 + now3,
5✔
322
                              1 + now1 + now2 + now3 + ny),
323
                    np.arange(1 + now1 + now2 + now3 + ny + nu,
324
                              1 + now1 + now2 + now3 + ny + nu + nu)))
325

326
    # output indices
327
    oi = np.arange(1, 1 + now1 + now2 + now3 + ny)
5✔
328

329
    # Filter out known warning due to use of connect
330
    with warnings.catch_warnings():
5✔
331
        warnings.filterwarnings(
5✔
332
            'ignore', message="`connect`", category=DeprecationWarning)
333

334
        p = connect(sysall, q, ii, oi)
5✔
335

336
    return p
5✔
337

338

339
def mixsyn(g, w1=None, w2=None, w3=None):
9✔
340
    """Mixed-sensitivity H-infinity synthesis.
341

342
    mixsyn(g,w1,w2,w3) -> k,cl,info
343

344
    Parameters
345
    ----------
346
    g : LTI
347
        The plant for which controller must be synthesized.
348
    w1 : None, or scalar or k1-by-ny LTI
349
        Weighting on S = (1+G*K)**-1.
350
    w2 : None, or scalar or k2-by-nu LTI
351
        Weighting on K*S.
352
    w3 : None, or scalar or k3-by-ny LTI
353
        Weighting on T = G*K*(1+G*K)**-1.
354

355
    Returns
356
    -------
357
    k : `StateSpace`
358
        Synthesized controller.
359
    cl : `StateSpace`
360
        Closed system mapping evaluation inputs to evaluation outputs.
361

362
        Let p be the augmented plant, with::
363

364
            [z] = [p11 p12] [w]
365
            [y]   [p21   g] [u]
366

367
        then cl is the system from w -> z with u = -k*y.
368
    info : tuple
369
        Two-tuple (`gamma`, `rcond`) containing additional information:
370
            - `gamma` (scalar): H-infinity norm of cl.
371
            - `rcond` (array): Estimates of reciprocal condition numbers
372
               computed during synthesis.  See hinfsyn for details.
373

374
    See Also
375
    --------
376
    hinfsyn, augw
377

378
    Notes
379
    -----
380
    If a weighting w is scalar, it will be replaced by I*w, where I is
381
    ny-by-ny for `w1` and `w3`, and nu-by-nu for `w2`.
382

383
    """
384
    nmeas = g.noutputs
5✔
385
    ncon = g.ninputs
5✔
386
    p = augw(g, w1, w2, w3)
5✔
387

388
    k, cl, gamma, rcond = hinfsyn(p, nmeas, ncon)
5✔
389
    info = gamma, rcond
5✔
390
    return k, cl, info
5✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc