Skip to content

Commit 237e5ef

Browse files
committed
use NumPy printoptions in LaTeX representations
1 parent 20f4bad commit 237e5ef

File tree

5 files changed

+93
-17
lines changed

5 files changed

+93
-17
lines changed

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
@@ -13,15 +13,16 @@
1313
"""
1414

1515
import math
16+
import sys
1617
from collections.abc import Iterable
1718
from copy import deepcopy
1819
from warnings import warn
1920

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

452-
for Di in asarray(self.D):
457+
for Di in asarray(D):
453458
lines.append('&'.join(_f2s(Dij) for Dij in Di)
454459
+ '\\\\')
455460

@@ -474,19 +479,24 @@ def _latex_partitioned(self):
474479
if self.nstates == 0:
475480
return self._latex_partitioned_stateless()
476481

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

484-
for Ai, Bi in zip(asarray(self.A), asarray(self.B)):
494+
for Ai, Bi in zip(asarray(A), asarray(B)):
485495
lines.append('&'.join([_f2s(Aij) for Aij in Ai]
486496
+ [_f2s(Bij) for Bij in Bi])
487497
+ '\\\\')
488498
lines.append(r'\hline')
489-
for Ci, Di in zip(asarray(self.C), asarray(self.D)):
499+
for Ci, Di in zip(asarray(C), asarray(D)):
490500
lines.append('&'.join([_f2s(Cij) for Cij in Ci]
491501
+ [_f2s(Dij) for Dij in Di])
492502
+ '\\\\')

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
@@ -12,6 +12,7 @@
1212
1313
"""
1414

15+
import sys
1516
from collections.abc import Iterable
1617
from copy import deepcopy
1718
from itertools import chain, product
@@ -1346,9 +1347,12 @@ def _c2d_matched(sysC, Ts, **kwargs):
13461347
# Borrowed from poly1d library
13471348
def _tf_polynomial_to_string(coeffs, var='s'):
13481349
"""Convert a transfer function polynomial to a string."""
1349-
13501350
thestr = "0"
13511351

1352+
# Apply NumPy formatting
1353+
with np.printoptions(threshold=sys.maxsize):
1354+
coeffs = eval(repr(coeffs))
1355+
13521356
# Compute the number of coefficients
13531357
N = len(coeffs) - 1
13541358

@@ -1393,6 +1397,9 @@ def _tf_polynomial_to_string(coeffs, var='s'):
13931397

13941398
def _tf_factorized_polynomial_to_string(roots, gain=1, var='s'):
13951399
"""Convert a factorized polynomial to a string."""
1400+
# Apply NumPy formatting
1401+
with np.printoptions(threshold=sys.maxsize):
1402+
roots = eval(repr(roots))
13961403

13971404
if roots.size == 0:
13981405
return _float2str(gain)

0 commit comments

Comments
 (0)