Skip to content

Commit 9897ab2

Browse files
committed
use NumPy printoptions in LaTeX representations
1 parent 85772f5 commit 9897ab2

5 files changed

Lines changed: 93 additions & 17 deletions

File tree

control/config.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ def use_legacy_defaults(version):
266266
Parameters
267267
----------
268268
version : string
269-
Version number of the defaults desired. Ranges from '0.1' to '0.8.4'.
269+
Version number of the defaults desired. Ranges from '0.1' to '0.10.1'.
270270
271271
Examples
272272
--------
@@ -279,26 +279,26 @@ def use_legacy_defaults(version):
279279
(major, minor, patch) = (None, None, None) # default values
280280

281281
# Early release tag format: REL-0.N
282-
match = re.match("REL-0.([12])", version)
282+
match = re.match(r"^REL-0.([12])$", version)
283283
if match: (major, minor, patch) = (0, int(match.group(1)), 0)
284284

285285
# Early release tag format: control-0.Np
286-
match = re.match("control-0.([3-6])([a-d])", version)
286+
match = re.match(r"^control-0.([3-6])([a-d])$", version)
287287
if match: (major, minor, patch) = \
288288
(0, int(match.group(1)), ord(match.group(2)) - ord('a') + 1)
289289

290290
# Early release tag format: v0.Np
291-
match = re.match("[vV]?0.([3-6])([a-d])", version)
291+
match = re.match(r"^[vV]?0\.([3-6])([a-d])$", version)
292292
if match: (major, minor, patch) = \
293293
(0, int(match.group(1)), ord(match.group(2)) - ord('a') + 1)
294294

295295
# Abbreviated version format: vM.N or M.N
296-
match = re.match("([vV]?[0-9]).([0-9])", version)
296+
match = re.match(r"^[vV]?([0-9]*)\.([0-9]*)$", version)
297297
if match: (major, minor, patch) = \
298298
(int(match.group(1)), int(match.group(2)), 0)
299299

300300
# Standard version format: vM.N.P or M.N.P
301-
match = re.match("[vV]?([0-9]).([0-9]).([0-9])", version)
301+
match = re.match(r"^[vV]?([0-9]*)\.([0-9]*)\.([0-9]*)$", version)
302302
if match: (major, minor, patch) = \
303303
(int(match.group(1)), int(match.group(2)), int(match.group(3)))
304304

@@ -311,6 +311,10 @@ def use_legacy_defaults(version):
311311
#
312312
reset_defaults() # start from a clean slate
313313

314+
# Verions 0.10.2
315+
if major == 0 and minor <= 10 and patch < 2:
316+
set_defaults('iosys', repr_format='loadable')
317+
314318
# Version 0.9.2:
315319
if major == 0 and minor < 9 or (minor == 9 and patch < 2):
316320
from math import inf

control/statesp.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,16 @@
1414
"""
1515

1616
import math
17+
import sys
1718
from collections.abc import Iterable
1819
from copy import deepcopy
1920
from warnings import warn
2021

2122
import numpy as np
2223
import scipy as sp
2324
import scipy.linalg
24-
from numpy import any, asarray, concatenate, cos, delete, empty, exp, eye, \
25-
isinf, ones, pad, sin, squeeze, zeros
25+
from numpy import any, array, asarray, concatenate, cos, delete, empty, \
26+
exp, eye, isinf, ones, pad, sin, squeeze, zeros
2627
from numpy.linalg import LinAlgError, eigvals, matrix_rank, solve
2728
from numpy.random import rand, randn
2829
from scipy.signal import StateSpace as signalStateSpace
@@ -444,14 +445,18 @@ def _latex_partitioned_stateless(self):
444445
-------
445446
s : string with LaTeX representation of model
446447
"""
448+
# Apply NumPy formatting
449+
with np.printoptions(threshold=sys.maxsize):
450+
D = eval(repr(self.D))
451+
447452
lines = [
448453
r'$$',
449454
(r'\left('
450455
+ r'\begin{array}'
451456
+ r'{' + 'rll' * self.ninputs + '}')
452457
]
453458

454-
for Di in asarray(self.D):
459+
for Di in asarray(D):
455460
lines.append('&'.join(_f2s(Dij) for Dij in Di)
456461
+ '\\\\')
457462

@@ -476,19 +481,24 @@ def _latex_partitioned(self):
476481
if self.nstates == 0:
477482
return self._latex_partitioned_stateless()
478483

484+
# Apply NumPy formatting
485+
with np.printoptions(threshold=sys.maxsize):
486+
A, B, C, D = (
487+
eval(repr(getattr(self, M))) for M in ['A', 'B', 'C', 'D'])
488+
479489
lines = [
480490
r'$$',
481491
(r'\left('
482492
+ r'\begin{array}'
483493
+ r'{' + 'rll' * self.nstates + '|' + 'rll' * self.ninputs + '}')
484494
]
485495

486-
for Ai, Bi in zip(asarray(self.A), asarray(self.B)):
496+
for Ai, Bi in zip(asarray(A), asarray(B)):
487497
lines.append('&'.join([_f2s(Aij) for Aij in Ai]
488498
+ [_f2s(Bij) for Bij in Bi])
489499
+ '\\\\')
490500
lines.append(r'\hline')
491-
for Ci, Di in zip(asarray(self.C), asarray(self.D)):
501+
for Ci, Di in zip(asarray(C), asarray(D)):
492502
lines.append('&'.join([_f2s(Cij) for Cij in Ci]
493503
+ [_f2s(Dij) for Dij in Di])
494504
+ '\\\\')

control/tests/config_test.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,3 +319,16 @@ def test_system_indexing(self):
319319
indexed_system_name_suffix='POST')
320320
sys2 = sys[1:, 1:]
321321
assert sys2.name == 'PRE' + sys.name + 'POST'
322+
323+
def test_legacy_repr_format(self):
324+
from ..statesp import StateSpace
325+
from numpy import array
326+
327+
sys = ct.ss([[1]], [[1]], [[1]], [[0]])
328+
with pytest.raises(SyntaxError, match="invalid syntax"):
329+
new = eval(repr(sys)) # iosys is default
330+
331+
ct.use_legacy_defaults('0.10.1') # loadable is default
332+
new = eval(repr(sys))
333+
for attr in ['A', 'B', 'C', 'D']:
334+
assert getattr(sys, attr) == getattr(sys, attr)

control/tests/lti_test.py

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
"""lti_test.py"""
22

3+
import re
4+
35
import numpy as np
46
import pytest
5-
from .conftest import editsdefaults
67

78
import control as ct
8-
from control import c2d, tf, ss, tf2ss, NonlinearIOSystem
9-
from control.lti import LTI, evalfr, damp, dcgain, zeros, poles, bandwidth
10-
from control import common_timebase, isctime, isdtime, issiso
11-
from control.tests.conftest import slycotonly
9+
from control import NonlinearIOSystem, c2d, common_timebase, isctime, \
10+
isdtime, issiso, ss, tf, tf2ss
1211
from control.exception import slycot_check
12+
from control.lti import LTI, bandwidth, damp, dcgain, evalfr, poles, zeros
13+
from control.tests.conftest import slycotonly
14+
15+
from .conftest import editsdefaults
16+
1317

1418
class TestLTI:
1519
@pytest.mark.parametrize("fun, args", [
@@ -368,3 +372,41 @@ def test_scalar_algebra(op, fcn):
368372

369373
scaled = getattr(sys, op)(2)
370374
np.testing.assert_almost_equal(getattr(sys(1j), op)(2), scaled(1j))
375+
376+
377+
@pytest.mark.parametrize(
378+
"fcn, args, kwargs, suppress, " +
379+
"repr_expected, str_expected, latex_expected", [
380+
(ct.ss, (-1e-12, 1, 2, 3), {}, False,
381+
r"StateSpace\([\s]*array\(\[\[-1.e-12\]\]\).*",
382+
None, # standard Numpy formatting
383+
r"10\^\{-12\}"),
384+
(ct.ss, (-1e-12, 1, 3, 3), {}, True,
385+
r"StateSpace\([\s]*array\(\[\[-0\.\]\]\).*",
386+
None, # standard Numpy formatting
387+
r"-0"),
388+
(ct.tf, ([1, 1e-12, 1], [1, 2, 1]), {}, False,
389+
r"\[1\.e\+00, 1\.e-12, 1.e\+00\]",
390+
r"s\^2 \+ 1e-12 s \+ 1",
391+
r"1 \\times 10\^\{-12\}"),
392+
(ct.tf, ([1, 1e-12, 1], [1, 2, 1]), {}, True,
393+
r"\[1\., 0., 1.\]",
394+
r"s\^2 \+ 1",
395+
r"\{s\^2 \+ 1\}"),
396+
])
397+
@pytest.mark.usefixtures("editsdefaults")
398+
def test_printoptions(
399+
fcn, args, kwargs, suppress,
400+
repr_expected, str_expected, latex_expected):
401+
sys = fcn(*args, **kwargs)
402+
403+
with np.printoptions(suppress=suppress):
404+
# Test loadable representation
405+
assert re.search(repr_expected, sys.iosys_repr('loadable')) is not None
406+
407+
# Test string representation
408+
if str_expected is not None:
409+
assert re.search(str_expected, str(sys)) is not None
410+
411+
# Test LaTeX representation
412+
assert re.search(latex_expected, sys._repr_latex_()) is not None

control/xferfcn.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
1414
"""
1515

16+
import sys
1617
from collections.abc import Iterable
1718
from copy import deepcopy
1819
from itertools import chain, product
@@ -1349,9 +1350,12 @@ def _c2d_matched(sysC, Ts, **kwargs):
13491350
# Borrowed from poly1d library
13501351
def _tf_polynomial_to_string(coeffs, var='s'):
13511352
"""Convert a transfer function polynomial to a string."""
1352-
13531353
thestr = "0"
13541354

1355+
# Apply NumPy formatting
1356+
with np.printoptions(threshold=sys.maxsize):
1357+
coeffs = eval(repr(coeffs))
1358+
13551359
# Compute the number of coefficients
13561360
N = len(coeffs) - 1
13571361

@@ -1396,6 +1400,9 @@ def _tf_polynomial_to_string(coeffs, var='s'):
13961400

13971401
def _tf_factorized_polynomial_to_string(roots, gain=1, var='s'):
13981402
"""Convert a factorized polynomial to a string."""
1403+
# Apply NumPy formatting
1404+
with np.printoptions(threshold=sys.maxsize):
1405+
roots = eval(repr(roots))
13991406

14001407
if roots.size == 0:
14011408
return _float2str(gain)

0 commit comments

Comments
 (0)