Skip to content

Commit 7273e11

Browse files
committed
iosys.py cleanup plus related subclass, docstring mods
1 parent 31f6574 commit 7273e11

9 files changed

Lines changed: 57 additions & 141 deletions

File tree

control/config.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,6 @@ def reset_defaults():
132132
from .statesp import _statesp_defaults
133133
defaults.update(_statesp_defaults)
134134

135-
from .nlsys import _iosys_defaults
136-
defaults.update(_iosys_defaults)
137-
138135
from .optimal import _optimal_defaults
139136
defaults.update(_optimal_defaults)
140137

control/frdata.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,6 @@ class FrequencyResponseData(LTI):
115115
116116
"""
117117

118-
# Allow NDarray * StateSpace to give StateSpace._rmul_() priority
119-
# https://docs.scipy.org/doc/numpy/reference/arrays.classes.html
120-
__array_priority__ = 13 # override ndarray, StateSpace, I/O sys
121-
122118
#
123119
# Class attributes
124120
#

control/iosys.py

Lines changed: 17 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
# iosys.py - I/O system class and helper functions
22
# RMM, 13 Mar 2022
33
#
4-
# This file implements the InputOutputSystem class, which is used as a parent
5-
# class for FrequencyResponseData, InputOutputSystem, LTI, TimeResponseData,
6-
# and other similar classes to allow naming of signals.
4+
# This file implements the InputOutputSystem class, which is used as a
5+
# parent class for StateSpace, TransferFunction, NonlinearIOSystem, LTI,
6+
# FrequencyResponseData, InterconnectedSystem and other similar classes
7+
# that allow naming of signals.
78

89
import numpy as np
910
from copy import deepcopy
@@ -12,7 +13,7 @@
1213
from . import config
1314

1415
__all__ = ['InputOutputSystem', 'issiso', 'timebase', 'common_timebase',
15-
'timebaseEqual', 'isdtime', 'isctime']
16+
'isdtime', 'isctime']
1617

1718
# Define module default parameter values
1819
_iosys_defaults = {
@@ -38,6 +39,15 @@ class InputOutputSystem(object):
3839
a set of subclasses that are used to implement specific structures and
3940
operations for different types of input/output dynamical systems.
4041
42+
The timebase for the system, dt, is used to specify whether the system
43+
is operating in continuous or discrete time. It can have the following
44+
values:
45+
46+
* dt = None No timebase specified
47+
* dt = 0 Continuous time system
48+
* dt > 0 Discrete time system with sampling time dt
49+
* dt = True Discrete time system with unspecified sampling time
50+
4151
Parameters
4252
----------
4353
inputs : int, list of str, or None
@@ -93,22 +103,10 @@ class InputOutputSystem(object):
93103
state_prefix : string, optional
94104
Set the prefix for state signals. Default = 'x'.
95105
96-
Notes
97-
-----
98-
The :class:`~control.InputOuputSystem` class (and its subclasses) makes
99-
use of two special methods for implementing much of the work of the class:
100-
101-
* _rhs(t, x, u): compute the right hand side of the differential or
102-
difference equation for the system. This must be specified by the
103-
subclass for the system.
104-
105-
* _out(t, x, u): compute the output for the current state of the system.
106-
The default is to return the entire system state.
107-
108106
"""
109-
110-
# Allow ndarray * InputOutputSystem to give IOSystem._rmul_() priority
111-
__array_priority__ = 13 # override ndarray, SS, TF types
107+
# Allow NDarray * IOSystem to give IOSystem._rmul_() priority
108+
# https://docs.scipy.org/doc/numpy/reference/arrays.classes.html
109+
__array_priority__ = 20
112110

113111
def __init__(
114112
self, name=None, inputs=None, outputs=None, states=None,
@@ -503,32 +501,6 @@ def common_timebase(dt1, dt2):
503501
else:
504502
raise ValueError("Systems have incompatible timebases")
505503

506-
# Check to see if two timebases are equal
507-
def timebaseEqual(sys1, sys2):
508-
"""
509-
Check to see if two systems have the same timebase
510-
511-
timebaseEqual(sys1, sys2)
512-
513-
returns True if the timebases for the two systems are compatible. By
514-
default, systems with timebase 'None' are compatible with either
515-
discrete or continuous timebase systems. If two systems have a discrete
516-
timebase (dt > 0) then their timebases must be equal.
517-
"""
518-
warn("timebaseEqual will be deprecated in a future release of "
519-
"python-control; use :func:`common_timebase` instead",
520-
PendingDeprecationWarning)
521-
522-
if (type(sys1.dt) == bool or type(sys2.dt) == bool):
523-
# Make sure both are unspecified discrete timebases
524-
return type(sys1.dt) == type(sys2.dt) and sys1.dt == sys2.dt
525-
elif (sys1.dt is None or sys2.dt is None):
526-
# One or the other is unspecified => the other can be anything
527-
return True
528-
else:
529-
return sys1.dt == sys2.dt
530-
531-
532504
# Check to see if a system is a discrete time system
533505
def isdtime(sys, strict=False):
534506
"""

control/lti.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,13 @@ class LTI(InputOutputSystem):
2222
contains the number of inputs and outputs, and the timebase (dt) for the
2323
system. This function is not generally called directly by the user.
2424
25-
The timebase for the system, dt, is used to specify whether the system
26-
is operating in continuous or discrete time. It can have the following
27-
values:
28-
29-
* dt = None No timebase specified
30-
* dt = 0 Continuous time system
31-
* dt > 0 Discrete time system with sampling time dt
32-
* dt = True Discrete time system with unspecified sampling time
33-
3425
When two LTI systems are combined, their timebases much match. A system
3526
with timebase None can be combined with a system having a specified
3627
timebase, and the result will have the timebase of the latter system.
3728
3829
Note: dt processing has been moved to the InputOutputSystem class.
3930
4031
"""
41-
4232
def __init__(self, inputs=1, outputs=1, states=None, name=None, **kwargs):
4333
"""Assign the LTI object's numbers of inputs and ouputs."""
4434
super().__init__(

control/nlsys.py

Lines changed: 36 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
# nlsys.py - input/output system module
2-
#
32
# RMM, 28 April 2019
43
#
54
# Additional features to add
@@ -19,13 +18,6 @@
1918
2019
"""
2120

22-
__author__ = "Richard Murray"
23-
__copyright__ = "Copyright 2019, California Institute of Technology"
24-
__credits__ = ["Richard Murray"]
25-
__license__ = "BSD"
26-
__maintainer__ = "Richard Murray"
27-
__email__ = "murray@cds.caltech.edu"
28-
2921
import numpy as np
3022
import scipy as sp
3123
import copy
@@ -41,9 +33,6 @@
4133
'input_output_response', 'find_eqpt', 'linearize',
4234
'interconnect']
4335

44-
# Define module default parameter values
45-
_iosys_defaults = {}
46-
4736

4837
class NonlinearIOSystem(InputOutputSystem):
4938
"""Nonlinear I/O system.
@@ -110,10 +99,19 @@ class NonlinearIOSystem(InputOutputSystem):
11099
--------
111100
InputOutputSystem : Input/output system class.
112101
113-
"""
114-
# Set priority for operators
115-
__array_priority__ = 13 # override ndarray, SS and TF types
102+
Notes
103+
-----
104+
The :class:`~control.InputOuputSystem` class (and its subclasses) makes
105+
use of two special methods for implementing much of the work of the class:
116106
107+
* _rhs(t, x, u): compute the right hand side of the differential or
108+
difference equation for the system. If not specified, the system
109+
has no state.
110+
111+
* _out(t, x, u): compute the output for the current state of the system.
112+
The default is to return the entire system state.
113+
114+
"""
117115
def __init__(self, updfcn, outfcn=None, params=None, **kwargs):
118116
"""Create a nonlinear I/O system given update and output functions."""
119117
# Process keyword arguments
@@ -135,8 +133,9 @@ def __init__(self, updfcn, outfcn=None, params=None, **kwargs):
135133
if self.nstates is None:
136134
self.nstates = 0
137135
else:
138-
raise ValueError("States specified but no update function "
139-
"given.")
136+
raise ValueError(
137+
"states specified but no update function given.")
138+
140139
if outfcn is None:
141140
# No output function specified => outputs = states
142141
if self.noutputs is None and self.nstates is not None:
@@ -145,7 +144,7 @@ def __init__(self, updfcn, outfcn=None, params=None, **kwargs):
145144
# Number of outputs = number of states => all is OK
146145
pass
147146
elif self.noutputs is not None and self.noutputs != 0:
148-
raise ValueError("Outputs specified but no output function "
147+
raise ValueError("outputs specified but no output function "
149148
"(and nstates not known).")
150149

151150
# Initialize current parameters to default parameters
@@ -266,7 +265,7 @@ def __add__(self, other):
266265
# Make sure number of input and outputs match
267266
if self.ninputs != other.ninputs or self.noutputs != other.noutputs:
268267
raise ValueError("Can't add systems with incompatible numbers of "
269-
"inputs or outputs.")
268+
"inputs or outputs")
270269

271270
# Create a new system to handle the composition
272271
inplist = [[(0, i), (1, i)] for i in range(self.ninputs)]
@@ -286,8 +285,8 @@ def __radd__(self, other):
286285

287286
# Make sure number of input and outputs match
288287
if self.ninputs != other.ninputs or self.noutputs != other.noutputs:
289-
raise ValueError("Can't add systems with incompatible numbers of "
290-
"inputs or outputs.")
288+
raise ValueError("can't add systems with incompatible numbers of "
289+
"inputs or outputs")
291290

292291
# Create a new system to handle the composition
293292
inplist = [[(0, i), (1, i)] for i in range(other.ninputs)]
@@ -308,8 +307,8 @@ def __sub__(self, other):
308307
# Make sure number of input and outputs match
309308
if self.ninputs != other.ninputs or self.noutputs != other.noutputs:
310309
raise ValueError(
311-
"Can't substract systems with incompatible numbers of "
312-
"inputs or outputs.")
310+
"can't substract systems with incompatible numbers of "
311+
"inputs or outputs")
313312
ninputs = self.ninputs
314313
noutputs = self.noutputs
315314

@@ -613,7 +612,7 @@ def __init__(self, syslist, connections=None, inplist=None, outlist=None,
613612
name, inputs, outputs, states, _ = _process_iosys_keywords(kwargs)
614613

615614
# Initialize the system list and index
616-
self.syslist = list(syslist) # insure modifications can be made
615+
self.syslist = list(syslist) # ensure modifications can be made
617616
self.syslist_index = {}
618617

619618
# Initialize the input, output, and state counts, indices
@@ -638,7 +637,7 @@ def __init__(self, syslist, connections=None, inplist=None, outlist=None,
638637
# Make sure number of inputs, outputs, states is given
639638
if sys.ninputs is None or sys.noutputs is None or \
640639
sys.nstates is None:
641-
raise TypeError("System '%s' must define number of inputs, "
640+
raise TypeError("system '%s' must define number of inputs, "
642641
"outputs, states in order to be connected" %
643642
sys.name)
644643

@@ -734,7 +733,7 @@ def outfcn(t, x, u, params):
734733
input_indices, output_indices):
735734
if self.connect_map[input_index, output_index] != 0:
736735
warn("multiple connections given for input %d" %
737-
input_index + ". Combining with previous entries.")
736+
input_index + "; combining with previous entries")
738737
self.connect_map[input_index, output_index] += gain
739738

740739
# Convert the input list to a matrix: maps system to subsystems
@@ -744,13 +743,13 @@ def outfcn(t, x, u, params):
744743
inpspec = [inpspec]
745744
if not isinstance(inpspec, list):
746745
raise ValueError("specifications in inplist must be of type "
747-
"int, str, tuple or list.")
746+
"int, str, tuple or list")
748747
for spec in inpspec:
749748
ulist_indices = self._parse_input_spec(spec)
750749
for j, ulist_index in enumerate(ulist_indices):
751750
if self.input_map[ulist_index, index] != 0:
752751
warn("multiple connections given for input %d" %
753-
index + ". Combining with previous entries.")
752+
index + "; combining with previous entries.")
754753
self.input_map[ulist_index, index + j] += 1
755754

756755
# Convert the output list to a matrix: maps subsystems to system
@@ -760,13 +759,13 @@ def outfcn(t, x, u, params):
760759
outspec = [outspec]
761760
if not isinstance(outspec, list):
762761
raise ValueError("specifications in outlist must be of type "
763-
"int, str, tuple or list.")
762+
"int, str, tuple or list")
764763
for spec in outspec:
765764
ylist_indices, gain = self._parse_output_spec(spec)
766765
for j, ylist_index in enumerate(ylist_indices):
767766
if self.output_map[index, ylist_index] != 0:
768767
warn("multiple connections given for output %d" %
769-
index + ". Combining with previous entries.")
768+
index + "; combining with previous entries")
770769
self.output_map[index + j, ylist_index] += gain
771770

772771
def _update_params(self, params, warning=False):
@@ -866,7 +865,7 @@ def _compute_static_io(self, t, x, u):
866865

867866
# Make sure that we stopped before detecting an algebraic loop
868867
if cycle_count == 0:
869-
raise RuntimeError("Algebraic loop detected.")
868+
raise RuntimeError("algebraic loop detected")
870869

871870
return ulist, ylist
872871

@@ -876,7 +875,7 @@ def _parse_input_spec(self, spec):
876875
subsys_index, input_indices, gain = _parse_spec(
877876
self.syslist, spec, 'input')
878877
if gain != 1:
879-
raise ValueError("gain not allowed in spec '%s'." % str(spec))
878+
raise ValueError("gain not allowed in spec '%s'" % str(spec))
880879

881880
# Return the indices into the input vector list (ylist)
882881
return [self.input_offset[subsys_index] + i for i in input_indices]
@@ -1431,8 +1430,8 @@ def ivp_rhs(t, x):
14311430
# Make sure the time vector is uniformly spaced
14321431
dt = t_eval[1] - t_eval[0]
14331432
if not np.allclose(t_eval[1:] - t_eval[:-1], dt):
1434-
raise ValueError("Parameter ``t_eval``: time values must be "
1435-
"equally spaced.")
1433+
raise ValueError("parameter ``t_eval``: time values must be "
1434+
"equally spaced")
14361435

14371436
# Make sure the sample time matches the given time
14381437
if sys.dt is not True:
@@ -1579,7 +1578,7 @@ def find_eqpt(sys, x0, u0=None, y0=None, t=0, params=None,
15791578
(u0 is not None and len(u0) != ninputs) or \
15801579
(y0 is not None and len(y0) != noutputs) or \
15811580
(dx0 is not None and len(dx0) != nstates):
1582-
raise ValueError("Length of input arguments does not match system.")
1581+
raise ValueError("length of input arguments does not match system")
15831582

15841583
# Update the parameter values
15851584
sys._update_params(params)
@@ -1691,8 +1690,8 @@ def rootfun(z):
16911690
num_freedoms = len(state_vars) + len(input_vars)
16921691
num_constraints = len(output_vars) + len(deriv_vars)
16931692
if num_constraints != num_freedoms:
1694-
warn("Number of constraints (%d) does not match number of degrees "
1695-
"of freedom (%d). Results may be meaningless." %
1693+
warn("number of constraints (%d) does not match number of degrees "
1694+
"of freedom (%d); results may be meaningless" %
16961695
(num_constraints, num_freedoms))
16971696

16981697
# Make copies of the state and input variables to avoid overwriting
@@ -1823,7 +1822,7 @@ def _find_size(sysval, vecval):
18231822
elif sysval == 1:
18241823
# (1, scalar) is also a valid combination from legacy code
18251824
return 1
1826-
raise ValueError("Can't determine size of system component.")
1825+
raise ValueError("can't determine size of system component")
18271826

18281827

18291828
# Function to create an interconnected system

control/statesp.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,6 @@ class StateSpace(NonlinearIOSystem, LTI):
147147
The default value of dt can be changed by changing the value of
148148
``control.config.defaults['control.default_dt']``.
149149
150-
Note: timebase processing has moved to iosys.
151-
152150
A state space system is callable and returns the value of the transfer
153151
function evaluated at a point in the complex plane. See
154152
:meth:`~control.StateSpace.__call__` for a more detailed description.
@@ -172,10 +170,6 @@ class StateSpace(NonlinearIOSystem, LTI):
172170
`'separate'`, the matrices are shown separately.
173171
174172
"""
175-
176-
# Allow ndarray * StateSpace to give StateSpace._rmul_() priority
177-
__array_priority__ = 12 # override ndarray and TF types
178-
179173
def __init__(self, *args, **kwargs):
180174
"""StateSpace(A, B, C, D[, dt])
181175
@@ -278,9 +272,8 @@ def __init__(self, *args, **kwargs):
278272
updfcn, outfcn,
279273
name=name, inputs=inputs, outputs=outputs,
280274
states=states, dt=dt, **kwargs)
281-
self.params = {}
282275

283-
# Reset shapes (may not be needed once np.matrix support is removed)
276+
# Reset shapes if the system is static
284277
if self._isstatic():
285278
A.shape = (0, 0)
286279
B.shape = (0, self.ninputs)

0 commit comments

Comments
 (0)