Skip to content

Commit 033b236

Browse files
committed
rename method _rhs in iosys to dynamics
1 parent f701b75 commit 033b236

File tree

5 files changed

+97
-56
lines changed

5 files changed

+97
-56
lines changed

control/iosys.py

Lines changed: 66 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ class for a set of subclasses that are used to implement specific
111111
The :class:`~control.InputOuputSystem` class (and its subclasses) makes
112112
use of two special methods for implementing much of the work of the class:
113113
114-
* _rhs(t, x, u): compute the right hand side of the differential or
114+
* dynamics(t, x, u): compute the right hand side of the differential or
115115
difference equation for the system. This must be specified by the
116116
subclass for the system.
117117
@@ -353,28 +353,69 @@ def _process_signal_list(self, signals, prefix='s'):
353353
# Find a signal by name
354354
def _find_signal(self, name, sigdict): return sigdict.get(name, None)
355355

356-
# Update parameters used for _rhs, _out (used by subclasses)
356+
# Update parameters used for dynamics, _out (used by subclasses)
357357
def _update_params(self, params, warning=False):
358358
if (warning):
359359
warn("Parameters passed to InputOutputSystem ignored.")
360360

361-
def _rhs(self, t, x, u):
362-
"""Evaluate right hand side of a differential or difference equation.
361+
def dynamics(self, t, x, u):
362+
"""Compute the dynamics of a differential or difference equation.
363363
364-
Private function used to compute the right hand side of an
365-
input/output system model.
364+
Given time `t`, input `u` and state `x`, returns the dynamics of the
365+
system. If the system is continuous, returns the time derivative
366366
367+
``dx/dt = f(t, x, u)``
368+
369+
where `f` is the system's (possibly nonlinear) dynamics function.
370+
If the system is discrete-time, returns the next value of `x`:
371+
372+
``x[t+dt] = f(t, x[t], u[t])``
373+
374+
Where `t` is a scalar.
375+
376+
The inputs `x` and `u` must be of the correct length.
377+
378+
Parameters
379+
----------
380+
t : float
381+
the time at which to evaluate
382+
x : array_like
383+
current state
384+
u : array_like
385+
input
386+
387+
Returns
388+
-------
389+
`dx/dt` or `x[t+dt]` : ndarray
367390
"""
368-
NotImplemented("Evaluation not implemented for system of type ",
391+
392+
NotImplemented("Dynamics not implemented for system of type ",
369393
type(self))
370394

371395
def _out(self, t, x, u, params={}):
372-
"""Evaluate the output of a system at a given state, input, and time
396+
"""Compute the output of the system
397+
398+
Given time `t`, input `u` and state `x`, returns the output of the
399+
system:
400+
401+
``y = g(t, x, u)``
373402
374-
Private function used to compute the output of of an input/output
375-
system model given the state, input, parameters, and time.
403+
The inputs `x` and `u` must be of the correct length.
376404
405+
Parameters
406+
----------
407+
t : float
408+
the time at which to evaluate
409+
x : array_like
410+
current state
411+
u : array_like
412+
input
413+
414+
Returns
415+
-------
416+
y : ndarray
377417
"""
418+
378419
# If no output function was defined in subclass, return state
379420
return x
380421

@@ -533,7 +574,7 @@ def linearize(self, x0, u0, t=0, params={}, eps=1e-6,
533574
"""
534575
#
535576
# If the linearization is not defined by the subclass, perform a
536-
# numerical linearization use the `_rhs()` and `_out()` member
577+
# numerical linearization use the `dynamics()` and `_out()` member
537578
# functions.
538579
#
539580

@@ -554,7 +595,7 @@ def linearize(self, x0, u0, t=0, params={}, eps=1e-6,
554595
self._update_params(params)
555596

556597
# Compute the nominal value of the update law and output
557-
F0 = self._rhs(t, x0, u0)
598+
F0 = self.dynamics(t, x0, u0)
558599
H0 = self._out(t, x0, u0)
559600

560601
# Create empty matrices that we can fill up with linearizations
@@ -567,14 +608,14 @@ def linearize(self, x0, u0, t=0, params={}, eps=1e-6,
567608
for i in range(nstates):
568609
dx = np.zeros((nstates,))
569610
dx[i] = eps
570-
A[:, i] = (self._rhs(t, x0 + dx, u0) - F0) / eps
611+
A[:, i] = (self.dynamics(t, x0 + dx, u0) - F0) / eps
571612
C[:, i] = (self._out(t, x0 + dx, u0) - H0) / eps
572613

573614
# Perturb each of the input variables and compute linearization
574615
for i in range(ninputs):
575616
du = np.zeros((ninputs,))
576617
du[i] = eps
577-
B[:, i] = (self._rhs(t, x0, u0 + du) - F0) / eps
618+
B[:, i] = (self.dynamics(t, x0, u0 + du) - F0) / eps
578619
D[:, i] = (self._out(t, x0, u0 + du) - H0) / eps
579620

580621
# Create the state space system
@@ -694,7 +735,7 @@ def _update_params(self, params={}, warning=True):
694735
if params and warning:
695736
warn("Parameters passed to LinearIOSystems are ignored.")
696737

697-
def _rhs(self, t, x, u):
738+
def dynamics(self, t, x, u):
698739
# Convert input to column vector and then change output to 1D array
699740
xdot = np.dot(self.A, np.reshape(x, (-1, 1))) \
700741
+ np.dot(self.B, np.reshape(u, (-1, 1)))
@@ -863,7 +904,7 @@ def _update_params(self, params, warning=False):
863904
self._current_params = self.params.copy()
864905
self._current_params.update(params)
865906

866-
def _rhs(self, t, x, u):
907+
def dynamics(self, t, x, u):
867908
xdot = self.updfcn(t, x, u, self._current_params) \
868909
if self.updfcn is not None else []
869910
return np.array(xdot).reshape((-1,))
@@ -1033,7 +1074,7 @@ def _update_params(self, params, warning=False):
10331074
local.update(params) # update with locally passed parameters
10341075
sys._update_params(local, warning=warning)
10351076

1036-
def _rhs(self, t, x, u):
1077+
def dynamics(self, t, x, u):
10371078
# Make sure state and input are vectors
10381079
x = np.array(x, ndmin=1)
10391080
u = np.array(u, ndmin=1)
@@ -1047,7 +1088,7 @@ def _rhs(self, t, x, u):
10471088
for sys in self.syslist:
10481089
# Update the right hand side for this subsystem
10491090
if sys.nstates != 0:
1050-
xdot[state_index:state_index + sys.nstates] = sys._rhs(
1091+
xdot[state_index:state_index + sys.nstates] = sys.dynamics(
10511092
t, x[state_index:state_index + sys.nstates],
10521093
ulist[input_index:input_index + sys.ninputs])
10531094

@@ -1497,14 +1538,14 @@ def input_output_response(sys, T, U=0., X0=0, params={}, method='RK45',
14971538

14981539
# Create a lambda function for the right hand side
14991540
u = sp.interpolate.interp1d(T, U, fill_value="extrapolate")
1500-
def ivp_rhs(t, x): return sys._rhs(t, x, u(t))
1541+
def ivp_dynamics(t, x): return sys.dynamics(t, x, u(t))
15011542

15021543
# Perform the simulation
15031544
if isctime(sys):
15041545
if not hasattr(sp.integrate, 'solve_ivp'):
15051546
raise NameError("scipy.integrate.solve_ivp not found; "
15061547
"use SciPy 1.0 or greater")
1507-
soln = sp.integrate.solve_ivp(ivp_rhs, (T0, Tf), X0, t_eval=T,
1548+
soln = sp.integrate.solve_ivp(ivp_dynamics, (T0, Tf), X0, t_eval=T,
15081549
method=method, vectorized=False)
15091550

15101551
# Compute the output associated with the state (and use sys.out to
@@ -1549,7 +1590,7 @@ def ivp_rhs(t, x): return sys._rhs(t, x, u(t))
15491590
y.append(sys._out(T[i], x, u(T[i])))
15501591

15511592
# Update the state for the next iteration
1552-
x = sys._rhs(T[i], x, u(T[i]))
1593+
x = sys.dynamics(T[i], x, u(T[i]))
15531594

15541595
# Convert output to numpy arrays
15551596
soln.y = np.transpose(np.array(soln.y))
@@ -1670,8 +1711,8 @@ def find_eqpt(sys, x0, u0=[], y0=None, t=0, params={},
16701711
if y0 is None:
16711712
# Take u0 as fixed and minimize over x
16721713
# TODO: update to allow discrete time systems
1673-
def ode_rhs(z): return sys._rhs(t, z, u0)
1674-
result = root(ode_rhs, x0, **kw)
1714+
def ode_dynamics(z): return sys.dynamics(t, z, u0)
1715+
result = root(ode_dynamics, x0, **kw)
16751716
z = (result.x, u0, sys._out(t, result.x, u0))
16761717
else:
16771718
# Take y0 as fixed and minimize over x and u
@@ -1680,7 +1721,7 @@ def rootfun(z):
16801721
x, u = np.split(z, [nstates])
16811722
# TODO: update to allow discrete time systems
16821723
return np.concatenate(
1683-
(sys._rhs(t, x, u), sys._out(t, x, u) - y0), axis=0)
1724+
(sys.dynamics(t, x, u), sys._out(t, x, u) - y0), axis=0)
16841725
z0 = np.concatenate((x0, u0), axis=0) # Put variables together
16851726
result = root(rootfun, z0, **kw) # Find the eq point
16861727
x, u = np.split(result.x, [nstates]) # Split result back in two
@@ -1782,7 +1823,7 @@ def rootfun(z):
17821823
u[input_vars] = z[nstate_vars:]
17831824

17841825
# Compute the update and output maps
1785-
dx = sys._rhs(t, x, u) - dx0
1826+
dx = sys.dynamics(t, x, u) - dx0
17861827
if dtime:
17871828
dx -= x # TODO: check
17881829
dy = sys._out(t, x, u) - y0

control/sisotool.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def sisotool(sys, kvect=None, xlim_rlocus=None, ylim_rlocus=None,
101101

102102
# First time call to setup the bode and step response plots
103103
_SisotoolUpdate(sys, fig,
104-
1 if kvect is None else kvect[0], bode_plot_params)
104+
1 if kvect is None else kvect[0], bode_plot_params, tvect=tvect)
105105

106106
# Setup the root-locus plot window
107107
root_locus(sys, kvect=kvect, xlim=xlim_rlocus,

control/tests/iosys_test.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def test_linear_iosys(self, tsys):
5656
# Make sure that the right hand side matches linear system
5757
for x, u in (([0, 0], 0), ([1, 0], 0), ([0, 1], 0), ([0, 0], 1)):
5858
np.testing.assert_array_almost_equal(
59-
np.reshape(iosys._rhs(0, x, u), (-1, 1)),
59+
np.reshape(iosys.dynamics(0, x, u), (-1, 1)),
6060
np.dot(linsys.A, np.reshape(x, (-1, 1))) + np.dot(linsys.B, u))
6161

6262
# Make sure that simulations also line up
@@ -687,7 +687,7 @@ def test_find_eqpts(self, tsys):
687687
assert result.success
688688
np.testing.assert_array_almost_equal(xeq, [1.64705879, 1.17923874])
689689
np.testing.assert_array_almost_equal(
690-
nlsys._rhs(0, xeq, ueq), np.zeros((2,)))
690+
nlsys.dynamics(0, xeq, ueq), np.zeros((2,)))
691691

692692
# Ducted fan dynamics with output = velocity
693693
nlsys = ios.NonlinearIOSystem(pvtol, lambda t, x, u, params: x[0:2])
@@ -697,15 +697,15 @@ def test_find_eqpts(self, tsys):
697697
nlsys, [0, 0, 0, 0], [0, 4*9.8], return_result=True)
698698
assert result.success
699699
np.testing.assert_array_almost_equal(
700-
nlsys._rhs(0, xeq, ueq), np.zeros((4,)))
700+
nlsys.dynamics(0, xeq, ueq), np.zeros((4,)))
701701
np.testing.assert_array_almost_equal(xeq, [0, 0, 0, 0])
702702

703703
# Use a small lateral force to cause motion
704704
xeq, ueq, result = ios.find_eqpt(
705705
nlsys, [0, 0, 0, 0], [0.01, 4*9.8], return_result=True)
706706
assert result.success
707707
np.testing.assert_array_almost_equal(
708-
nlsys._rhs(0, xeq, ueq), np.zeros((4,)), decimal=5)
708+
nlsys.dynamics(0, xeq, ueq), np.zeros((4,)), decimal=5)
709709

710710
# Equilibrium point with fixed output
711711
xeq, ueq, result = ios.find_eqpt(
@@ -715,7 +715,7 @@ def test_find_eqpts(self, tsys):
715715
np.testing.assert_array_almost_equal(
716716
nlsys._out(0, xeq, ueq), [0.1, 0.1], decimal=5)
717717
np.testing.assert_array_almost_equal(
718-
nlsys._rhs(0, xeq, ueq), np.zeros((4,)), decimal=5)
718+
nlsys.dynamics(0, xeq, ueq), np.zeros((4,)), decimal=5)
719719

720720
# Specify outputs to constrain (replicate previous)
721721
xeq, ueq, result = ios.find_eqpt(
@@ -725,15 +725,15 @@ def test_find_eqpts(self, tsys):
725725
np.testing.assert_array_almost_equal(
726726
nlsys._out(0, xeq, ueq), [0.1, 0.1], decimal=5)
727727
np.testing.assert_array_almost_equal(
728-
nlsys._rhs(0, xeq, ueq), np.zeros((4,)), decimal=5)
728+
nlsys.dynamics(0, xeq, ueq), np.zeros((4,)), decimal=5)
729729

730730
# Specify inputs to constrain (replicate previous), w/ no result
731731
xeq, ueq = ios.find_eqpt(
732732
nlsys, [0, 0, 0, 0], [0.01, 4*9.8], y0=[0.1, 0.1], iu = [])
733733
np.testing.assert_array_almost_equal(
734734
nlsys._out(0, xeq, ueq), [0.1, 0.1], decimal=5)
735735
np.testing.assert_array_almost_equal(
736-
nlsys._rhs(0, xeq, ueq), np.zeros((4,)), decimal=5)
736+
nlsys.dynamics(0, xeq, ueq), np.zeros((4,)), decimal=5)
737737

738738
# Now solve the problem with the original PVTOL variables
739739
# Constrain the output angle and x velocity
@@ -746,7 +746,7 @@ def test_find_eqpts(self, tsys):
746746
np.testing.assert_array_almost_equal(
747747
nlsys_full._out(0, xeq, ueq)[[2, 3]], [0.1, 0.1], decimal=5)
748748
np.testing.assert_array_almost_equal(
749-
nlsys_full._rhs(0, xeq, ueq)[-4:], np.zeros((4,)), decimal=5)
749+
nlsys_full.dynamics(0, xeq, ueq)[-4:], np.zeros((4,)), decimal=5)
750750

751751
# Fix one input and vary the other
752752
nlsys_full = ios.NonlinearIOSystem(pvtol_full, None)
@@ -759,7 +759,7 @@ def test_find_eqpts(self, tsys):
759759
np.testing.assert_array_almost_equal(
760760
nlsys_full._out(0, xeq, ueq)[[3]], [0.1], decimal=5)
761761
np.testing.assert_array_almost_equal(
762-
nlsys_full._rhs(0, xeq, ueq)[-4:], np.zeros((4,)), decimal=5)
762+
nlsys_full.dynamics(0, xeq, ueq)[-4:], np.zeros((4,)), decimal=5)
763763

764764
# PVTOL with output = y velocity
765765
xeq, ueq, result = ios.find_eqpt(
@@ -771,7 +771,7 @@ def test_find_eqpts(self, tsys):
771771
np.testing.assert_array_almost_equal(
772772
nlsys_full._out(0, xeq, ueq)[-3:], [0.1, 0, 0], decimal=5)
773773
np.testing.assert_array_almost_equal(
774-
nlsys_full._rhs(0, xeq, ueq)[-5:], np.zeros((5,)), decimal=5)
774+
nlsys_full.dynamics(0, xeq, ueq)[-5:], np.zeros((5,)), decimal=5)
775775

776776
# Unobservable system
777777
linsys = ct.StateSpace(

control/tests/type_conversion_test.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def sys_dict():
1919
sdict['frd'] = ct.frd([10+0j, 9 + 1j, 8 + 2j], [1,2,3])
2020
sdict['lio'] = ct.LinearIOSystem(ct.ss([[-1]], [[5]], [[5]], [[0]]))
2121
sdict['ios'] = ct.NonlinearIOSystem(
22-
sdict['lio']._rhs, sdict['lio']._out, 1, 1, 1)
22+
sdict['lio'].dynamics, sdict['lio']._out, 1, 1, 1)
2323
sdict['arr'] = np.array([[2.0]])
2424
sdict['flt'] = 3.
2525
return sdict
@@ -66,7 +66,7 @@ def sys_dict():
6666
('add', 'ios', ['xos', 'xos', 'E', 'ios', 'ios', 'xos', 'xos']),
6767
('add', 'arr', ['ss', 'tf', 'xrd', 'xio', 'xos', 'arr', 'arr']),
6868
('add', 'flt', ['ss', 'tf', 'xrd', 'xio', 'xos', 'arr', 'flt']),
69-
69+
7070
# op left ss tf frd lio ios arr flt
7171
('sub', 'ss', ['ss', 'ss', 'xrd', 'ss', 'xos', 'ss', 'ss' ]),
7272
('sub', 'tf', ['tf', 'tf', 'xrd', 'tf', 'xos', 'tf', 'tf' ]),
@@ -75,7 +75,7 @@ def sys_dict():
7575
('sub', 'ios', ['xos', 'xio', 'E', 'ios', 'xos' 'xos', 'xos']),
7676
('sub', 'arr', ['ss', 'tf', 'xrd', 'xio', 'xos', 'arr', 'arr']),
7777
('sub', 'flt', ['ss', 'tf', 'xrd', 'xio', 'xos', 'arr', 'flt']),
78-
78+
7979
# op left ss tf frd lio ios arr flt
8080
('mul', 'ss', ['ss', 'ss', 'xrd', 'ss', 'xos', 'ss', 'ss' ]),
8181
('mul', 'tf', ['tf', 'tf', 'xrd', 'tf', 'xos', 'tf', 'tf' ]),
@@ -84,7 +84,7 @@ def sys_dict():
8484
('mul', 'ios', ['xos', 'xos', 'E', 'ios', 'ios', 'xos', 'xos']),
8585
('mul', 'arr', ['ss', 'tf', 'xrd', 'xio', 'xos', 'arr', 'arr']),
8686
('mul', 'flt', ['ss', 'tf', 'frd', 'xio', 'xos', 'arr', 'flt']),
87-
87+
8888
# op left ss tf frd lio ios arr flt
8989
('truediv', 'ss', ['xs', 'tf', 'xrd', 'xio', 'xos', 'xs', 'xs' ]),
9090
('truediv', 'tf', ['tf', 'tf', 'xrd', 'tf', 'xos', 'tf', 'tf' ]),
@@ -100,7 +100,7 @@ def sys_dict():
100100
for rtype, expected in zip(rtype_list, expected_list):
101101
# Add this to the list of tests to run
102102
test_matrix.append([opname, ltype, rtype, expected])
103-
103+
104104
@pytest.mark.parametrize("opname, ltype, rtype, expected", test_matrix)
105105
def test_operator_type_conversion(opname, ltype, rtype, expected, sys_dict):
106106
op = getattr(operator, opname)
@@ -110,7 +110,7 @@ def test_operator_type_conversion(opname, ltype, rtype, expected, sys_dict):
110110
# Get rid of warnings for InputOutputSystem objects by making a copy
111111
if isinstance(leftsys, ct.InputOutputSystem) and leftsys == rightsys:
112112
rightsys = leftsys.copy()
113-
113+
114114
# Make sure we get the right result
115115
if expected == 'E' or expected[0] == 'x':
116116
# Exception expected
@@ -119,7 +119,7 @@ def test_operator_type_conversion(opname, ltype, rtype, expected, sys_dict):
119119
else:
120120
# Operation should work and return the given type
121121
result = op(leftsys, rightsys)
122-
122+
123123
# Print out what we are testing in case something goes wrong
124124
assert isinstance(result, type_dict[expected])
125125

@@ -138,7 +138,7 @@ def test_operator_type_conversion(opname, ltype, rtype, expected, sys_dict):
138138
#
139139
# * For IOS/LTI, convert to IOS. In the case of a linear I/O system (LIO),
140140
# this will preserve the linear structure since the LTI system will
141-
# be converted to state space.
141+
# be converted to state space.
142142
#
143143
# * When combining state space or transfer with linear I/O systems, the
144144
# * output should be of type Linear IO system, since that maintains the
@@ -149,7 +149,7 @@ def test_operator_type_conversion(opname, ltype, rtype, expected, sys_dict):
149149

150150
type_list = ['ss', 'tf', 'tfx', 'frd', 'lio', 'ios', 'arr', 'flt']
151151
conversion_table = [
152-
# L \ R ['ss', 'tf', 'tfx', 'frd', 'lio', 'ios', 'arr', 'flt']
152+
# L \ R ['ss', 'tf', 'tfx', 'frd', 'lio', 'ios', 'arr', 'flt']
153153
('ss', ['ss', 'ss', 'tf' 'frd', 'lio', 'ios', 'ss', 'ss' ]),
154154
('tf', ['tf', 'tf', 'tf' 'frd', 'lio', 'ios', 'tf', 'tf' ]),
155155
('tfx', ['tf', 'tf', 'tf', 'frd', 'E', 'E', 'tf', 'tf' ]),

0 commit comments

Comments
 (0)