8000 Merge pull request #1117 from murrayrm/update_isstatic-01Feb2025 · python-control/python-control@cf77f99 · GitHub
[go: up one dir, main page]

Skip to content

Commit cf77f99

Browse files
authored
Merge pull request #1117 from murrayrm/update_isstatic-01Feb2025
Update use/computation of sys._isstatic()
2 parents f73e893 + ce77e6b commit cf77f99

File tree

7 files changed

+64
-47
lines changed

7 files changed

+64
-47
lines changed

control/descfcn.py

Lines changed: 13 additions & 14 deletions
< 6D47 /tr>
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ def describing_function(
152152
#
153153
# The describing function of a nonlinear function F() can be computed by
154154
# evaluating the nonlinearity over a sinusoid. The Fourier series for a
155-
# static nonlinear function evaluated on a sinusoid can be written as
155+
# nonlinear function evaluated on a sinusoid can be written as
156156
#
157157
# F(A\sin\omega t) = \sum_{k=1}^\infty M_k(A) \sin(k\omega t + \phi_k(A))
158158
#
@@ -226,10 +226,10 @@ class DescribingFunctionResponse:
226226
"""Results of describing function analysis.
227227
228228
Describing functions allow analysis of a linear I/O systems with a
229-
static nonlinear feedback function. The DescribingFunctionResponse
230-
class is used by the `describing_function_response`
231-
function to return the results of a describing function analysis. The
232-
response object can be used to obtain information about the describing
229+
nonlinear feedback function. The DescribingFunctionResponse class
230+
is used by the `describing_function_response` function to return
231+
the results of a describing function analysis. The response
232+
object can be used to obtain information about the describing
233233
function analysis or generate a Nyquist plot showing the frequency
234234
response of the linear systems and the describing function for the
235235
nonlinear element.
@@ -283,16 +283,16 @@ def describing_function_response(
283283
"""Compute the describing function response of a system.
284284
285285
This function uses describing function analysis to analyze a closed
286-
loop system consisting of a linear system with a static nonlinear
287-
function in the feedback path.
286+
loop system consisting of a linear system with a nonlinear function in
287+
the feedback path.
288288
289289
Parameters
290290
----------
291291
H : LTI system
292292
Linear time-invariant (LTI) system (state space, transfer function,
293293
or FRD).
294-
F : static nonlinear function
295-
A static nonlinearity, either a scalar function or a single-input,
294+
F : nonlinear function
295+
Feedback nonlinearity, either a scalar function or a single-input,
296296
single-output, static input/output system.
297297
A : list
298298
List of amplitudes to be used for the describing function plot.
@@ -405,8 +405,7 @@ def describing_function_plot(
405405
Nyquist plot with describing function for a nonlinear system.
406406
407407
This function generates a Nyquist plot for a closed loop system
408-
consisting of a linear system with a static nonlinear function in the
409-
feedback path.
408+
consisting of a linear system with a nonlinearity in the feedback path.
410409
411410
The function may be called in one of two forms:
412411
@@ -426,9 +425,9 @@ def describing_function_plot(
426425
H : LTI system
427426
Linear time-invariant (LTI) system (state space, transfer function,
428427
or FRD).
429-
F : static nonlinear function
430-
A static nonlinearity, either a scalar function or a single-input,
431-
single-output, static input/output system.
428+
F : nonlinear function
429+
Nonlinearity in the feedback path, either a scalar function or a
430+
single-input, single-output, static input/output system.
432431
A : list
433432
List of amplitudes to be used for the describing function plot.
434433
omega : list, optional

control/iosys.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -757,10 +757,6 @@ def issiso(self):
757757
"""Check to see if a system is single input, single output."""
758758
return self.ninputs == 1 and self.noutputs == 1
759759

760-
def _isstatic(self):
761-
"""Check to see if a system is a static system (no states)"""
762-
return self.nstates == 0
763-
764760

765761
# Test to see if a system is SISO
766762
def issiso(sys, strict=False):

control/nlsys.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ def __call__(sys, u, params=None, squeeze=None):
181181
182182
"""
183183
# Make sure the call makes sense
184-
if not sys._isstatic():
184+
if sys.nstates != 0:
185185
raise TypeError(
186186
"function evaluation is only supported for static "
187187
"input/output systems")
@@ -199,7 +199,7 @@ def __call__(sys, u, params=None, squeeze=None):
199199
def __mul__(self, other):
200200
"""Multiply two input/output systems (series interconnection)"""
201201
# Convert 'other' to an I/O system if needed
202-
other = _convert_static_iosystem(other)
202+
other = _convert_to_iosystem(other)
203203
if not isinstance(other, InputOutputSystem):
204204
return NotImplemented
205205

@@ -231,7 +231,7 @@ def __mul__(self, other):
231231
def __rmul__(self, other):
232232
"""Pre-multiply an input/output systems by a scalar/matrix"""
233233
# Convert other to an I/O system if needed
234-
other = _convert_static_iosystem(other)
234+
other = _convert_to_iosystem(other)
235235
if not isinstance(other, InputOutputSystem):
236236
return NotImplemented
237237

@@ -263,7 +263,7 @@ def __rmul__(self, other):
263263
def __add__(self, other):
264264
"""Add two input/output systems (parallel interconnection)"""
265265
# Convert other to an I/O system if needed
266-
other = _convert_static_iosystem(other)
266+
other = _convert_to_iosystem(other)
267267
if not isinstance(other, InputOutputSystem):
268268
return NotImplemented
269269

@@ -284,7 +284,7 @@ def __add__(self, other):
284284
def __radd__(self, other):
285285
"""Parallel addition of input/output system to a compatible object."""
286286
# Convert other to an I/O system if needed
287-
other = _convert_static_iosystem(other)
287+
other = _convert_to_iosystem(other)
288288
if not isinstance(other, InputOutputSystem):
289289
return NotImplemented
290290

@@ -305,7 +305,7 @@ def __radd__(self, other):
305305
def __sub__(self, other):
306306
"""Subtract two input/output systems (parallel interconnection)"""
307307
# Convert other to an I/O system if needed
308-
other = _convert_static_iosystem(other)
308+
other = _convert_to_iosystem(other)
309309
if not isinstance(other, InputOutputSystem):
310310
return NotImplemented
311311

@@ -329,7 +329,7 @@ def __sub__(self, other):
329329
def __rsub__(self, other):
330330
"""Parallel subtraction of I/O system to a compatible object."""
331331
# Convert other to an I/O system if needed
332-
other = _convert_static_iosystem(other)
332+
other = _convert_to_iosystem(other)
333333
if not isinstance(other, InputOutputSystem):
334334
return NotImplemented
335335
return other - self
@@ -355,6 +355,10 @@ def __truediv__(self, other):
355355
else:
356356
return NotImplemented
357357

358+
# Determine if a system is static (memoryless)
359+
def _isstatic(self):
360+
return self.nstates == 0
361+
358362
def _update_params(self, params):
359363
# Update the current parameter values
360364
self._current_params = self.params.copy()
@@ -484,7 +488,7 @@ def feedback(self, other=1, sign=-1, params=None):
484488
485489
"""
486490
# Convert sys2 to an I/O system if needed
487-
other = _convert_static_iosystem(other)
491+
other = _convert_to_iosystem(other)
488492

489493
# Make sure systems can be interconnected
490494
if self.noutputs != other.ninputs or other.noutputs != self.ninputs:
@@ -932,6 +936,7 @@ def _out(self, t, x, u):
932936
# Make the full set of subsystem outputs to system output
933937
return self.output_map @ ylist
934938

939+
# Find steady state (static) inputs and outputs
935940
def _compute_static_io(self, t, x, u):
936941
# Figure out the total number of inputs and outputs
937942
(ninputs, noutputs) = self.connect_map.shape
@@ -1711,8 +1716,8 @@ def ufun(t):
17111716
dt = (t - T[idx-1]) / (T[idx] - T[idx-1])
17121717
return U[..., idx-1] * (1. - dt) + U[..., idx] * dt
17131718

1714-
# Check to make sure this is not a static function
1715-
if nstates == 0: # No states => map input to output
1719+
# Check to make sure see if this is a static function
1720+
if sys.nstates == 0:
17161721
# Make sure the user gave a time vector for evaluation (or 'T')
17171722
if t_eval is None:
17181723
# User overrode t_eval with None, but didn't give us the times...
@@ -2924,8 +2929,8 @@ def _process_vector_argument(arg, name, size):
29242929
return val, nelem
29252930

29262931

2927-
# Utility function to create an I/O system from a static gain
2928-
def _convert_static_iosystem(sys):
2932+
# Utility function to create an I/O system (from number or array)
2933+
def _convert_to_iosystem(sys):
29292934
# If we were given an I/O system, do nothing
29302935
if isinstance(sys, InputOutputSystem):
29312936
return sys

control/statesp.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,9 @@ def __init__(self, *args, **kwargs):
229229
self.C = C
230230
self.D = D
231231

232+
# Determine if the system is static (memoryless)
233+
static = (A.size == 0)
234+
232235
#
233236
# Process keyword arguments
234237
#
@@ -242,7 +245,7 @@ def __init__(self, *args, **kwargs):
242245
{'inputs': B.shape[1], 'outputs': C.shape[0],
243246
'states': A.shape[0]}
244247
name, inputs, outputs, states, dt = _process_iosys_keywords(
245-
kwargs, defaults, static=(A.size == 0))
248+
kwargs, defaults, static=static)
246249

247250
# Create updfcn and outfcn
248251
updfcn = lambda t, x, u, params: \
@@ -257,7 +260,7 @@ def __init__(self, *args, **kwargs):
257260
states=states, dt=dt, **kwargs)
258261

259262
# Reset shapes if the system is static
260-
if self._isstatic():
263+
if static:
261264
A.shape = (0, 0)
262265
B.shape = (0, self.ninputs)
263266
C.shape = (self.noutputs, 0)

control/xferfcn.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -219,15 +219,22 @@ def __init__(self, *args, **kwargs):
219219
raise ValueError("display_format must be 'poly' or 'zpk',"
220220
" got '%s'" % self.display_format)
221221

222-
# Determine if the transfer function is static (needed for dt)
222+
#
223+
# Determine if the transfer function is static (memoryless)
224+
#
225+
# True if and only if all of the numerator and denominator
226+
# polynomials of the (MIMO) transfer function are zeroth order.
227+
#
223228
static = True
224229
for arr in [num, den]:
230+
# Iterate using refs_OK since num and den are ndarrays of ndarrays
225231
for poly_ in np.nditer(arr, flags=['refs_ok']):
226232
if poly_.item().size > 1:
227233
static = False
228234
break
229235
if not static:
230236
break
237+
self._static = static # retain for later usage
231238

232239
defaults = args[0] if len(args) == 1 else \
233240
{'inputs': num.shape[1], 'outputs': num.shape[0]}
@@ -1283,16 +1290,9 @@ def dcgain(self, warn_infinite=False):
12831290
"""
12841291
return self._dcgain(warn_infinite)
12851292

1293+
# Determine if a system is static (memoryless)
12861294
def _isstatic(self):
1287-
"""returns True if and only if all of the numerator and denominator
1288-
polynomials of the (possibly MIMO) transfer function are zeroth order,
1289-
that is, if the system has no dynamics. """
1290-
for list_of_polys in self.num, self.den:
1291-
for row in list_of_polys:
1292-
for poly_ in row:
1293-
if len(poly_) > 1:
1294-
return False
1295-
return True
1295+
return self._static # Check done at initialization
12961296

12971297
# Attributes for differentiation and delay
12981298
#

doc/descfcn.rst

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,21 @@ Describing Functions
66
====================
77

88
For nonlinear systems consisting of a feedback connection between a
9-
linear system and a static nonlinearity, it is possible to obtain a
9+
linear system and a nonlinearity, it is possible to obtain a
1010
generalization of Nyquist's stability criterion based on the idea of
1111
describing functions. The basic concept involves approximating the
12-
response of a static nonlinearity to an input :math:`u = A e^{j \omega
13-
t}` as an output :math:`y = N(A) (A e^{j \omega t})`, where :math:`N(A)
14-
\in \mathbb{C}` represents the (amplitude-dependent) gain and phase
12+
response of a nonlinearity to an input :math:`u = A e^{j \omega t}` as
13+
an output :math:`y = N(A) (A e^{j \omega t})`, where :math:`N(A) \in
14+
\mathbb{C}` represents the (amplitude-dependent) gain and phase
1515
associated with the nonlinearity.
1616

17+
In the most common case, the nonlinearity will be a static,
18+
time-invariant nonlinear function :math:`y = h(u)`. However,
19+
describing functions can be defined for nonlinear input/output systems
20+
that have some internal memory, such as hysteresis or backlash. For
21+
simplicity, we take the nonlinearity to be static (memoryless) in the
22+
description below, unless otherwise specified.
23+
1724
Stability analysis of a linear system :math:`H(s)` with a feedback
1825
nonlinearity :math:`F(x)` is done by looking for amplitudes :math:`A`
1926
and frequencies :math:`\omega` such that

doc/nlsys.rst

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ Discrete time systems are also supported and have dynamics of the form
2323
x[t+1] &= f(t, x[t], u[t], \theta), \\
2424
y[t] &= h(t, x[t], u[t], \theta).
2525
26+
A nonlinear input/output model is said to be "static" if the output
27+
:math:`y(t)` at any given time :math:`t` depends only on the input
28+
:math:`u(t)` at that same time :math:`t` and not on past or future
29+
values of :math:`u`.
30+
2631

2732
.. _sec-nonlinear-models:
2833

@@ -47,7 +52,9 @@ dynamics of the system can be in continuous or discrete time (use the
4752
The output function `outfcn` is used to specify the outputs of the
4853
system and has the same calling signature as `updfcn`. If it is not
4954
specified, then the output of the system is set equal to the system
50-
state. Otherwise, it should return an array of shape (p,).
55+
state. Otherwise, it should return an array of shape (p,). If a
56+
input/output system is static, the state `x` should still be passed to
57+
the output function, but the state is ignored.
5158

5259
Note that the number of states, inputs, and outputs should generally
5360
be explicitly specified, although some operations can infer the

0 commit comments

Comments
 (0)
0