Skip to content

Commit 695cdfc

Browse files
committed
DOC: fix LTI system attribute docstrings, add __call__, PEP8 cleanup
1 parent d89dfd8 commit 695cdfc

12 files changed

Lines changed: 303 additions & 72 deletions

File tree

control/frdata.py

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,35 @@ class FrequencyResponseData(LTI):
7777
above, i.e. the rows represent the outputs and the columns
7878
represent the inputs.
7979
80+
A frequency response data object is callable and returns the value of the
81+
transfer function evaluated at a point in the complex plane (must be on
82+
the imaginary access). See :meth:`~control.FrequencyResponseData.__call__`
83+
for a more detailed description.
84+
8085
"""
8186

8287
# Allow NDarray * StateSpace to give StateSpace._rmul_() priority
8388
# https://docs.scipy.org/doc/numpy/reference/arrays.classes.html
8489
__array_priority__ = 11 # override ndarray and matrix types
8590

86-
epsw = 1e-8
91+
#
92+
# Class attributes
93+
#
94+
# These attributes are defined as class attributes so that they are
95+
# documented properly. They are "overwritten" in __init__.
96+
#
97+
98+
#: Number of system inputs.
99+
#:
100+
#: :meta hide-value:
101+
ninputs = 1
102+
103+
#: Number of system outputs.
104+
#:
105+
#: :meta hide-value:
106+
noutputs = 1
107+
108+
_epsw = 1e-8 #: Bound for exact frequency match
87109

88110
def __init__(self, *args, **kwargs):
89111
"""Construct an FRD object.
@@ -141,7 +163,8 @@ def __init__(self, *args, **kwargs):
141163
self.omega = args[0].omega
142164
self.fresp = args[0].fresp
143165
else:
144-
raise ValueError("Needs 1 or 2 arguments; received %i." % len(args))
166+
raise ValueError(
167+
"Needs 1 or 2 arguments; received %i." % len(args))
145168

146169
# create interpolation functions
147170
if smooth:
@@ -378,7 +401,7 @@ def eval(self, omega, squeeze=None):
378401
then single-dimensional axes are removed.
379402
380403
"""
381-
omega_array = np.array(omega, ndmin=1) # array-like version of omega
404+
omega_array = np.array(omega, ndmin=1) # array-like version of omega
382405

383406
# Make sure that we are operating on a simple list
384407
if len(omega_array.shape) > 1:
@@ -389,7 +412,7 @@ def eval(self, omega, squeeze=None):
389412
raise ValueError("FRD.eval can only accept real-valued omega")
390413

391414
if self.ifunc is None:
392-
elements = np.isin(self.omega, omega) # binary array
415+
elements = np.isin(self.omega, omega) # binary array
393416
if sum(elements) < len(omega_array):
394417
raise ValueError(
395418
"not all frequencies omega are in frequency list of FRD "
@@ -398,7 +421,7 @@ def eval(self, omega, squeeze=None):
398421
out = self.fresp[:, :, elements]
399422
else:
400423
out = empty((self.noutputs, self.ninputs, len(omega_array)),
401-
dtype=complex)
424+
dtype=complex)
402425
for i in range(self.noutputs):
403426
for j in range(self.ninputs):
404427
for k, w in enumerate(omega_array):
@@ -417,6 +440,9 @@ def __call__(self, s, squeeze=None):
417440
To evaluate at a frequency omega in radians per second, enter
418441
``s = omega * 1j`` or use ``sys.eval(omega)``
419442
443+
For a frequency response data object, the argument must be an
444+
imaginary number (since only the frequency response is defined).
445+
420446
Parameters
421447
----------
422448
s : complex scalar or 1D array_like
@@ -444,14 +470,15 @@ def __call__(self, s, squeeze=None):
444470
If `s` is not purely imaginary, because
445471
:class:`FrequencyDomainData` systems are only defined at imaginary
446472
frequency values.
473+
447474
"""
448475
# Make sure that we are operating on a simple list
449476
if len(np.atleast_1d(s).shape) > 1:
450477
raise ValueError("input list must be 1D")
451478

452479
if any(abs(np.atleast_1d(s).real) > 0):
453480
raise ValueError("__call__: FRD systems can only accept "
454-
"purely imaginary frequencies")
481+
"purely imaginary frequencies")
455482

456483
# need to preserve array or scalar status
457484
if hasattr(s, '__len__'):
@@ -510,6 +537,7 @@ def feedback(self, other=1, sign=-1):
510537
# fixes this problem.
511538
#
512539

540+
513541
FRD = FrequencyResponseData
514542

515543

@@ -534,7 +562,7 @@ def _convert_to_FRD(sys, omega, inputs=1, outputs=1):
534562
if isinstance(sys, FRD):
535563
omega.sort()
536564
if len(omega) == len(sys.omega) and \
537-
(abs(omega - sys.omega) < FRD.epsw).all():
565+
(abs(omega - sys.omega) < FRD._epsw).all():
538566
# frequencies match, and system was already frd; simply use
539567
return sys
540568

control/iosys.py

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,10 @@ class for a set of subclasses that are used to implement specific
9595
Dictionary of signal names for the inputs, outputs and states and the
9696
index of the corresponding array
9797
dt : None, True or float
98-
System timebase. 0 (default) indicates continuous
99-
time, True indicates discrete time with unspecified sampling
100-
time, positive number is discrete time with specified
101-
sampling time, None indicates unspecified timebase (either
102-
continuous or discrete time).
98+
System timebase. 0 (default) indicates continuous time, True indicates
99+
discrete time with unspecified sampling time, positive number is
100+
discrete time with specified sampling time, None indicates unspecified
101+
timebase (either continuous or discrete time).
103102
params : dict, optional
104103
Parameter values for the systems. Passed to the evaluation functions
105104
for the system as default values, overriding internal defaults.
@@ -120,12 +119,12 @@ class for a set of subclasses that are used to implement specific
120119
121120
"""
122121

123-
idCounter = 0
122+
_idCounter = 0
124123

125124
def name_or_default(self, name=None):
126125
if name is None:
127-
name = "sys[{}]".format(InputOutputSystem.idCounter)
128-
InputOutputSystem.idCounter += 1
126+
name = "sys[{}]".format(InputOutputSystem._idCounter)
127+
InputOutputSystem._idCounter += 1
129128
return name
130129

131130
def __init__(self, inputs=None, outputs=None, states=None, params={},
@@ -187,6 +186,28 @@ def __init__(self, inputs=None, outputs=None, states=None, params={},
187186
self.set_outputs(outputs)
188187
self.set_states(states)
189188

189+
#
190+
# Class attributes
191+
#
192+
# These attributes are defined as class attributes so that they are
193+
# documented properly. They are "overwritten" in __init__.
194+
#
195+
196+
#: Number of system inputs.
197+
#:
198+
#: :meta hide-value:
199+
ninputs = 0
200+
201+
#: Number of system outputs.
202+
#:
203+
#: :meta hide-value:
204+
noutputs = 0
205+
206+
#: Number of system states.
207+
#:
208+
#: :meta hide-value:
209+
nstates = 0
210+
190211
def __repr__(self):
191212
return self.name if self.name is not None else str(type(self))
192213

@@ -751,6 +772,17 @@ def __init__(self, linsys, inputs=None, outputs=None, states=None,
751772
if nstates is not None and linsys.nstates != nstates:
752773
raise ValueError("Wrong number/type of states given.")
753774

775+
# The following text needs to be replicated from StateSpace in order for
776+
# this entry to show up properly in sphinx doccumentation (not sure why,
777+
# but it was the only way to get it to work).
778+
#
779+
#: Deprecated attribute; use :attr:`nstates` instead.
780+
#:
781+
#: The ``state`` attribute was used to store the number of states for : a
782+
#: state space system. It is no longer used. If you need to access the
783+
#: number of states, use :attr:`nstates`.
784+
states = property(StateSpace._get_states, StateSpace._set_states)
785+
754786
def _update_params(self, params={}, warning=True):
755787
# Parameters not supported; issue a warning
756788
if params and warning:
@@ -1473,6 +1505,17 @@ def __init__(self, io_sys, ss_sys=None):
14731505
else:
14741506
raise TypeError("Second argument must be a state space system.")
14751507

1508+
# The following text needs to be replicated from StateSpace in order for
1509+
# this entry to show up properly in sphinx doccumentation (not sure why,
1510+
# but it was the only way to get it to work).
1511+
#
1512+
#: Deprecated attribute; use :attr:`nstates` instead.
1513+
#:
1514+
#: The ``state`` attribute was used to store the number of states for : a
1515+
#: state space system. It is no longer used. If you need to access the
1516+
#: number of states, use :attr:`nstates`.
1517+
states = property(StateSpace._get_states, StateSpace._set_states)
1518+
14761519

14771520
def input_output_response(
14781521
sys, T, U=0., X0=0, params={},

control/lti.py

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,34 +59,52 @@ def __init__(self, inputs=1, outputs=1, dt=None):
5959
# future warning, so that users will see it.
6060
#
6161

62-
@property
63-
def inputs(self):
62+
def _get_inputs(self):
6463
warn("The LTI `inputs` attribute will be deprecated in a future "
6564
"release. Use `ninputs` instead.",
6665
DeprecationWarning, stacklevel=2)
6766
return self.ninputs
6867

69-
@inputs.setter
70-
def inputs(self, value):
68+
def _set_inputs(self, value):
7169
warn("The LTI `inputs` attribute will be deprecated in a future "
7270
"release. Use `ninputs` instead.",
7371
DeprecationWarning, stacklevel=2)
7472
self.ninputs = value
7573

76-
@property
77-
def outputs(self):
74+
#: Deprecated
75+
inputs = property(
76+
_get_inputs, _set_inputs, doc=
77+
"""
78+
Deprecated attribute; use :attr:`ninputs` instead.
79+
80+
The ``input`` attribute was used to store the number of system inputs.
81+
It is no longer used. If you need access to the number of inputs for
82+
an LTI system, use :attr:`ninputs`.
83+
""")
84+
85+
def _get_outputs(self):
7886
warn("The LTI `outputs` attribute will be deprecated in a future "
7987
"release. Use `noutputs` instead.",
8088
DeprecationWarning, stacklevel=2)
8189
return self.noutputs
8290

83-
@outputs.setter
84-
def outputs(self, value):
91+
def _set_outputs(self, value):
8592
warn("The LTI `outputs` attribute will be deprecated in a future "
8693
"release. Use `noutputs` instead.",
8794
DeprecationWarning, stacklevel=2)
8895
self.noutputs = value
8996

97+
#: Deprecated
98+
outputs = property(
99+
_get_outputs, _set_outputs, doc=
100+
"""
101+
Deprecated attribute; use :attr:`noutputs` instead.
102+
103+
The ``output`` attribute was used to store the number of system
104+
outputs. It is no longer used. If you need access to the number of
105+
outputs for an LTI system, use :attr:`noutputs`.
106+
""")
107+
90108
def isdtime(self, strict=False):
91109
"""
92110
Check to see if a system is a discrete-time system

0 commit comments

Comments
 (0)