|
73 | 73 | 'statesp.use_numpy_matrix': True,
|
74 | 74 | 'statesp.default_dt': None,
|
75 | 75 | 'statesp.remove_useless_states': True,
|
| 76 | + 'statesp.latex_num_format': '.3g', |
| 77 | + 'statesp.latex_repr_type': 'partitioned', |
76 | 78 | }
|
77 | 79 |
|
78 | 80 |
|
@@ -122,6 +124,34 @@ def _ssmatrix(data, axis=1):
|
122 | 124 | return arr.reshape(shape)
|
123 | 125 |
|
124 | 126 |
|
| 127 | +def _f2s(f): |
| 128 | + """Format floating point number f for StateSpace._repr_latex_. |
| 129 | +
|
| 130 | + Numbers are converted to strings with statesp.latex_num_format. |
| 131 | +
|
| 132 | + Inserts column separators, etc., as needed. |
| 133 | + """ |
| 134 | + fmt = "{:" + config.defaults['statesp.latex_num_format'] + "}" |
| 135 | + sraw = fmt.format(f) |
| 136 | + # significand-exponent |
| 137 | + se = sraw.lower().split('e') |
| 138 | + # whole-fraction |
| 139 | + wf = se[0].split('.') |
| 140 | + s = wf[0] |
| 141 | + if wf[1:]: |
| 142 | + s += r'.&\hspace{{-1em}}{frac}'.format(frac=wf[1]) |
| 143 | + else: |
| 144 | + s += r'\phantom{.}&\hspace{-1em}' |
| 145 | + |
| 146 | + if se[1:]: |
| 147 | + s += r'&\hspace{{-1em}}\cdot10^{{{:d}}}'.format(int(se[1])) |
| 148 | + else: |
| 149 | + s += r'&\hspace{-1em}\phantom{\cdot}' |
| 150 | + |
| 151 | + return s |
| 152 | + |
| 153 | + |
| 154 | + |
125 | 155 | class StateSpace(LTI):
|
126 | 156 | """StateSpace(A, B, C, D[, dt])
|
127 | 157 |
|
@@ -152,6 +182,24 @@ class StateSpace(LTI):
|
152 | 182 | time. The default value of 'dt' is None and can be changed by changing the
|
153 | 183 | value of ``control.config.defaults['statesp.default_dt']``.
|
154 | 184 |
|
| 185 | + StateSpace instances have support for IPython LaTeX output, |
| 186 | + intended for pretty-printing in Jupyter notebooks. The LaTeX |
| 187 | + output can be configured using |
| 188 | + `control.config.defaults['latex_num_format']` and |
| 189 | + `control.config.defaults['latex_repr_type']`. The LaTeX output is |
| 190 | + tailored for MathJax, as used in Jupyter, and may look odd when |
| 191 | + typeset by non-MathJax LaTeX systems. |
| 192 | +
|
| 193 | + `control.config.defaults['latex_num_format']` is a format string |
| 194 | + fragment, specifically the part of the format string after `'{:'` |
| 195 | + used to convert floating-point numbers to strings. By default it |
| 196 | + is `'.3g'`. |
| 197 | +
|
| 198 | + `control.config.defaults['latex_repr_type']` must either be |
| 199 | + `'partioned'` or `'separate'`. If `'partitioned'`, the A, B, C, D |
| 200 | + matrices are shown as a single, partitioned matrix; if |
| 201 | + `'separate'`, the matrices are shown separately. |
| 202 | +
|
155 | 203 | """
|
156 | 204 |
|
157 | 205 | # Allow ndarray * StateSpace to give StateSpace._rmul_() priority
|
@@ -292,6 +340,136 @@ def __repr__(self):
|
292 | 340 | C=asarray(self.C).__repr__(), D=asarray(self.D).__repr__(),
|
293 | 341 | dt=(isdtime(self, strict=True) and ", {}".format(self.dt)) or '')
|
294 | 342 |
|
| 343 | + def _latex_partitioned_stateless(self): |
| 344 | + """`Partitioned` matrix LaTeX representation for stateless systems |
| 345 | +
|
| 346 | + Model is presented as a matrix, D. No partition lines are shown. |
| 347 | +
|
| 348 | + Returns |
| 349 | + ------- |
| 350 | + s : string with LaTeX representation of model |
| 351 | + """ |
| 352 | + lines = [ |
| 353 | + r'\[', |
| 354 | + r'\left(', |
| 355 | + (r'\begin{array}' |
| 356 | + + r'{' + 'rll' * self.inputs + '}') |
| 357 | + ] |
| 358 | + |
| 359 | + for Di in asarray(self.D): |
| 360 | + lines.append('&'.join(_f2s(Dij) for Dij in Di) |
| 361 | + + '\\\\') |
| 362 | + |
| 363 | + lines.extend([ |
| 364 | + r'\end{array}' |
| 365 | + r'\right)', |
| 366 | + r'\]']) |
| 367 | + |
| 368 | + return '\n'.join(lines) |
| 369 | + |
| 370 | + def _latex_partitioned(self): |
| 371 | + """Partitioned matrix LaTeX representation of state-space model |
| 372 | +
|
| 373 | + Model is presented as a matrix partitioned into A, B, C, and D |
| 374 | + parts. |
| 375 | +
|
| 376 | + Returns |
| 377 | + ------- |
| 378 | + s : string with LaTeX representation of model |
| 379 | + """ |
| 380 | + if self.states == 0: |
| 381 | + return self._latex_partitioned_stateless() |
| 382 | + |
| 383 | + lines = [ |
| 384 | + r'\[', |
| 385 | + r'\left(', |
| 386 | + (r'\begin{array}' |
| 387 | + + r'{' + 'rll' * self.states + '|' + 'rll' * self.inputs + '}') |
| 388 | + ] |
| 389 | + |
| 390 | + for Ai, Bi in zip(asarray(self.A), asarray(self.B)): |
| 391 | + lines.append('&'.join([_f2s(Aij) for Aij in Ai] |
| 392 | + + [_f2s(Bij) for Bij in Bi]) |
| 393 | + + '\\\\') |
| 394 | + lines.append(r'\hline') |
| 395 | + for Ci, Di in zip(asarray(self.C), asarray(self.D)): |
| 396 | + lines.append('&'.join([_f2s(Cij) for Cij in Ci] |
| 397 | + + [_f2s(Dij) for Dij in Di]) |
| 398 | + + '\\\\') |
| 399 | + |
| 400 | + lines.extend([ |
| 401 | + r'\end{array}' |
| 402 | + r'\right)', |
| 403 | + r'\]']) |
| 404 | + |
| 405 | + return '\n'.join(lines) |
| 406 | + |
| 407 | + def _latex_separate(self): |
| 408 | + """Separate matrices LaTeX representation of state-space model |
| 409 | +
|
| 410 | + Model is presented as separate, named, A, B, C, and D matrices. |
| 411 | +
|
| 412 | + Returns |
| 413 | + ------- |
| 414 | + s : string with LaTeX representation of model |
| 415 | + """ |
| 416 | + lines = [ |
| 417 | + r'\[', |
| 418 | + r'\begin{array}{ll}', |
| 419 | + ] |
| 420 | + |
| 421 | + def fmt_matrix(matrix, name): |
| 422 | + matlines = [name |
| 423 | + + r' = \left(\begin{array}{' |
| 424 | + + 'rll' * matrix.shape[1] |
| 425 | + + '}'] |
| 426 | + for row in asarray(matrix): |
| 427 | + matlines.append('&'.join(_f2s(entry) for entry in row) |
| 428 | + + '\\\\') |
| 429 | + matlines.extend([ |
| 430 | + r'\end{array}' |
| 431 | + r'\right)']) |
| 432 | + return matlines |
| 433 | + |
| 434 | + if self.states > 0: |
| 435 | + lines.extend(fmt_matrix(self.A, 'A')) |
| 436 | + lines.append('&') |
| 437 | + lines.extend(fmt_matrix(self.B, 'B')) |
| 438 | + lines.append('\\\\') |
| 439 | + |
| 440 | + lines.extend(fmt_matrix(self.C, 'C')) |
| 441 | + lines.append('&') |
| 442 | + lines.extend(fmt_matrix(self.D, 'D')) |
| 443 | + |
| 444 | + lines.extend([ |
| 445 | + r'\end{array}', |
| 446 | + r'\]']) |
| 447 | + |
| 448 | + return '\n'.join(lines) |
| 449 | + |
| 450 | + def _repr_latex_(self): |
| 451 | + """LaTeX representation of state-space model |
| 452 | +
|
| 453 | + Output is controlled by config options statesp.latex_repr_type |
| 454 | + and statesp.latex_num_format. |
| 455 | +
|
| 456 | + The output is primarily intended for Jupyter notebooks, which |
| 457 | + use MathJax to render the LaTeX, and the results may look odd |
| 458 | + when processed by a 'conventional' LaTeX system. |
| 459 | +
|
| 460 | + Returns |
| 461 | + ------- |
| 462 | + s : string with LaTeX representation of model |
| 463 | +
|
| 464 | + """ |
| 465 | + if config.defaults['statesp.latex_repr_type'] == 'partitioned': |
| 466 | + return self._latex_partitioned() |
| 467 | + elif config.defaults['statesp.latex_repr_type'] == 'separate': |
| 468 | + return self._latex_separate() |
| 469 | + else: |
| 470 | + cfg = config.defaults['statesp.latex_repr_type'] |
| 471 | + raise ValueError("Unknown statesp.latex_repr_type '{cfg}'".format(cfg=cfg)) |
| 472 | + |
295 | 473 | # Negation of a system
|
296 | 474 | def __neg__(self):
|
297 | 475 | """Negate a state space system."""
|
|
0 commit comments