Skip to content

Commit d9a28e5

Browse files
committed
Merge branch 'main' of github.com:python-control/python-control
2 parents 612d19b + 0ff0452 commit d9a28e5

37 files changed

Lines changed: 3841 additions & 1046 deletions

.github/scripts/set-conda-test-matrix.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,8 @@
1010
'win': 'windows',
1111
}
1212

13-
blas_implementations = ['unset', 'Generic', 'OpenBLAS', 'Intel10_64lp']
14-
15-
combinations = {'ubuntu': blas_implementations,
16-
'macos': blas_implementations,
13+
combinations = {'ubuntu': ['unset', 'Generic', 'OpenBLAS', 'Intel10_64lp'],
14+
'macos': ['unset', 'Generic', 'OpenBLAS'],
1715
'windows': ['unset', 'Intel10_64lp'],
1816
}
1917

.github/workflows/os-blas-test-matrix.yml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ jobs:
130130
auto-update-conda: false
131131
auto-activate-base: false
132132
- name: Conda build
133-
shell: bash -l {0}
133+
shell: bash -el {0}
134134
run: |
135135
set -e
136136
conda mambabuild conda-recipe
@@ -226,7 +226,7 @@ jobs:
226226
sudo apt-get -y update
227227
case ${{ matrix.blas_lib }} in
228228
Generic ) sudo apt-get -y install libblas3 liblapack3 ;;
229-
unset | OpenBLAS ) sudo apt-get -y install libopenblas-base ;;
229+
unset | OpenBLAS ) sudo apt-get -y install libopenblas0 ;;
230230
*)
231231
echo "BLAS ${{ matrix.blas_lib }} not supported for wheels on Ubuntu"
232232
exit 1 ;;
@@ -277,7 +277,7 @@ jobs:
277277

278278
defaults:
279279
run:
280-
shell: bash -l {0}
280+
shell: bash -el {0}
281281

282282
steps:
283283
- name: Checkout Slycot
@@ -309,22 +309,22 @@ jobs:
309309
set -e
310310
case ${{ matrix.blas_lib }} in
311311
unset ) # the conda-forge default (os dependent)
312-
mamba install libblas libcblas liblapack
312+
conda install libblas libcblas liblapack
313313
;;
314314
Generic )
315-
mamba install 'libblas=*=*netlib' 'libcblas=*=*netlib' 'liblapack=*=*netlib'
315+
conda install 'libblas=*=*netlib' 'libcblas=*=*netlib' 'liblapack=*=*netlib'
316316
echo "libblas * *netlib" >> $CONDA_PREFIX/conda-meta/pinned
317317
;;
318318
OpenBLAS )
319-
mamba install 'libblas=*=*openblas' openblas
319+
conda install 'libblas=*=*openblas' openblas
320320
echo "libblas * *openblas" >> $CONDA_PREFIX/conda-meta/pinned
321321
;;
322322
Intel10_64lp )
323-
mamba install 'libblas=*=*mkl' mkl
323+
conda install 'libblas=*=*mkl' mkl
324324
echo "libblas * *mkl" >> $CONDA_PREFIX/conda-meta/pinned
325325
;;
326326
esac
327-
mamba install -c ./slycot-conda-pkgs slycot
327+
conda install -c ./slycot-conda-pkgs slycot
328328
conda list
329329
- name: Test with pytest
330330
run: JOBNAME="$JOBNAME" pytest control/tests

control/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,5 +118,14 @@
118118
except ImportError:
119119
__version__ = "dev"
120120

121+
# patch the LTI class with function aliases for convenience functions
122+
# this needs to be done after the fact since the modules that contain the functions
123+
# all heavily depend on the LTI class
124+
LTI.to_ss = ss
125+
LTI.to_tf = tf
126+
LTI.bode_plot = bode_plot
127+
LTI.nyquist_plot = nyquist_plot
128+
LTI.nichols_plot = nichols_plot
129+
121130
# Initialize default parameter values
122131
reset_defaults()

control/bdalg.py

Lines changed: 71 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ def parallel(sys1, *sysn, **kwargs):
164164
or `y`). See :class:`InputOutputSystem` for more information.
165165
states : str, or list of str, optional
166166
List of names for system states. If not given, state names will be
167-
of of the form `x[i]` for interconnections of linear systems or
167+
of the form `x[i]` for interconnections of linear systems or
168168
'<subsys_name>.<state_name>' for interconnected nonlinear systems.
169169
name : string, optional
170170
System name (used for specifying signals). If unspecified, a generic
@@ -512,7 +512,7 @@ def connect(sys, Q, inputv, outputv):
512512

513513
return Ytrim * sys * Utrim
514514

515-
def combine_tf(tf_array):
515+
def combine_tf(tf_array, **kwargs):
516516
"""Combine array-like of transfer functions into MIMO transfer function.
517517
518518
Parameters
@@ -528,6 +528,16 @@ def combine_tf(tf_array):
528528
TransferFunction
529529
Transfer matrix represented as a single MIMO TransferFunction object.
530530
531+
Other Parameters
532+
----------------
533+
inputs, outputs : str, or list of str, optional
534+
List of strings that name the individual signals. If not given,
535+
signal names will be of the form `s[i]` (where `s` is one of `u`,
536+
or `y`). See :class:`InputOutputSystem` for more information.
537+
name : string, optional
538+
System name (used for specifying signals). If unspecified, a generic
539+
name <sys[id]> is generated with a unique integer id.
540+
531541
Raises
532542
------
533543
ValueError
@@ -542,20 +552,34 @@ def combine_tf(tf_array):
542552
--------
543553
Combine two transfer functions
544554
545-
>>> s = ct.TransferFunction.s
546-
>>> ct.combine_tf([
547-
... [1 / (s + 1)],
548-
... [s / (s + 2)],
549-
... ])
550-
TransferFunction([[array([1])], [array([1, 0])]], [[array([1, 1])], [array([1, 2])]])
555+
>>> s = ct.tf('s')
556+
>>> ct.combine_tf(
557+
... [[1 / (s + 1)],
558+
... [s / (s + 2)]],
559+
... name='G'
560+
... )
561+
TransferFunction(
562+
[[array([1])],
563+
[array([1, 0])]],
564+
[[array([1, 1])],
565+
[array([1, 2])]],
566+
name='G', outputs=2, inputs=1)
551567
552568
Combine NumPy arrays with transfer functions
553569
554-
>>> ct.combine_tf([
555-
... [np.eye(2), np.zeros((2, 1))],
556-
... [np.zeros((1, 2)), ct.TransferFunction([1], [1, 0])],
557-
... ])
558-
TransferFunction([[array([1.]), array([0.]), array([0.])], [array([0.]), array([1.]), array([0.])], [array([0.]), array([0.]), array([1])]], [[array([1.]), array([1.]), array([1.])], [array([1.]), array([1.]), array([1.])], [array([1.]), array([1.]), array([1, 0])]])
570+
>>> ct.combine_tf(
571+
... [[np.eye(2), np.zeros((2, 1))],
572+
... [np.zeros((1, 2)), ct.tf([1], [1, 0])]],
573+
... name='G'
574+
... )
575+
TransferFunction(
576+
[[array([1.]), array([0.]), array([0.])],
577+
[array([0.]), array([1.]), array([0.])],
578+
[array([0.]), array([0.]), array([1])]],
579+
[[array([1.]), array([1.]), array([1.])],
580+
[array([1.]), array([1.]), array([1.])],
581+
[array([1.]), array([1.]), array([1, 0])]],
582+
name='G', outputs=3, inputs=3)
559583
"""
560584
# Find common timebase or raise error
561585
dt_list = []
@@ -595,8 +619,8 @@ def combine_tf(tf_array):
595619
f"row {row_index}."
596620
)
597621
for j_in in range(col.ninputs):
598-
num_row.append(col.num[j_out][j_in])
599-
den_row.append(col.den[j_out][j_in])
622+
num_row.append(col.num_array[j_out, j_in])
623+
den_row.append(col.den_array[j_out, j_in])
600624
num.append(num_row)
601625
den.append(den_row)
602626
for row_index, row in enumerate(num):
@@ -611,10 +635,14 @@ def combine_tf(tf_array):
611635
"Mismatched number transfer function inputs in row "
612636
f"{row_index} of denominator."
613637
)
614-
return tf.TransferFunction(num, den, dt=dt)
638+
return tf.TransferFunction(num, den, dt=dt, **kwargs)
639+
615640

616641
def split_tf(transfer_function):
617-
"""Split MIMO transfer function into NumPy array of SISO tranfer functions.
642+
"""Split MIMO transfer function into NumPy array of SISO transfer functions.
643+
644+
System and signal names for the array of SISO transfer functions are
645+
copied from the MIMO system.
618646
619647
Parameters
620648
----------
@@ -630,31 +658,42 @@ def split_tf(transfer_function):
630658
--------
631659
Split a MIMO transfer function
632660
633-
>>> G = ct.TransferFunction(
634-
... [
635-
... [[87.8], [-86.4]],
636-
... [[108.2], [-109.6]],
637-
... ],
638-
... [
639-
... [[1, 1], [1, 1]],
640-
... [[1, 1], [1, 1]],
641-
... ],
661+
>>> G = ct.tf(
662+
... [ [[87.8], [-86.4]],
663+
... [[108.2], [-109.6]] ],
664+
... [ [[1, 1], [1, 1]],
665+
... [[1, 1], [1, 1]], ],
666+
... name='G'
642667
... )
643668
>>> ct.split_tf(G)
644-
array([[TransferFunction(array([87.8]), array([1, 1])),
645-
TransferFunction(array([-86.4]), array([1, 1]))],
646-
[TransferFunction(array([108.2]), array([1, 1])),
647-
TransferFunction(array([-109.6]), array([1, 1]))]], dtype=object)
669+
array([[TransferFunction(
670+
array([87.8]),
671+
array([1, 1]),
672+
name='G', outputs=1, inputs=1), TransferFunction(
673+
array([-86.4]),
674+
array([1, 1]),
675+
name='G', outputs=1, inputs=1)],
676+
[TransferFunction(
677+
array([108.2]),
678+
array([1, 1]),
679+
name='G', outputs=1, inputs=1), TransferFunction(
680+
array([-109.6]),
681+
array([1, 1]),
682+
name='G', outputs=1, inputs=1)]],
683+
dtype=object)
648684
"""
649685
tf_split_lst = []
650686
for i_out in range(transfer_function.noutputs):
651687
row = []
652688
for i_in in range(transfer_function.ninputs):
653689
row.append(
654690
tf.TransferFunction(
655-
transfer_function.num[i_out][i_in],
656-
transfer_function.den[i_out][i_in],
691+
transfer_function.num_array[i_out, i_in],
692+
transfer_function.den_array[i_out, i_in],
657693
dt=transfer_function.dt,
694+
inputs=transfer_function.input_labels[i_in],
695+
outputs=transfer_function.output_labels[i_out],
696+
name=transfer_function.name
658697
)
659698
)
660699
tf_split_lst.append(row)

control/config.py

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,28 @@ def _check_deprecation(self, key):
7373
else:
7474
return key
7575

76+
#
77+
# Context manager functionality
78+
#
79+
80+
def __call__(self, mapping):
81+
self.saved_mapping = dict()
82+
self.temp_mapping = mapping.copy()
83+
return self
84+
85+
def __enter__(self):
86+
for key, val in self.temp_mapping.items():
87+
if not key in self:
88+
raise ValueError(f"unknown parameter '{key}'")
89+
self.saved_mapping[key] = self[key]
90+
self[key] = val
91+
return self
92+
93+
def __exit__(self, exc_type, exc_val, exc_tb):
94+
for key, val in self.saved_mapping.items():
95+
self[key] = val
96+
del self.saved_mapping, self.temp_mapping
97+
return None
7698

7799
defaults = DefaultDict(_control_defaults)
78100

@@ -266,7 +288,7 @@ def use_legacy_defaults(version):
266288
Parameters
267289
----------
268290
version : string
269-
Version number of the defaults desired. Ranges from '0.1' to '0.8.4'.
291+
Version number of the defaults desired. Ranges from '0.1' to '0.10.1'.
270292
271293
Examples
272294
--------
@@ -279,26 +301,26 @@ def use_legacy_defaults(version):
279301
(major, minor, patch) = (None, None, None) # default values
280302

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

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

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

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

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

0 commit comments

Comments
 (0)