Skip to content

Commit 223acc6

Browse files
committed
allow OperatingPoint for linearize + code and documentation tweaks
1 parent fa162ec commit 223acc6

5 files changed

Lines changed: 69 additions & 37 deletions

File tree

control/matlab/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,12 @@
8484
from ..dtime import c2d
8585
from ..sisotool import sisotool
8686
from ..stochsys import lqe, dlqe
87+
from ..nlsys import find_operating_point
8788

8889
# Functions that are renamed in MATLAB
8990
pole, zero = poles, zeros
9091
freqresp = frequency_response
92+
trim = find_operating_point
9193

9294
# Import functions specific to Matlab compatibility package
9395
from .timeresp import *

control/nlsys.py

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,7 @@ def feedback(self, other=1, sign=-1, params=None):
515515
# Return the newly created system
516516
return newsys
517517

518-
def linearize(self, x0, u0, t=0, params=None, eps=1e-6,
518+
def linearize(self, x0, u0=None, t=0, params=None, eps=1e-6,
519519
copy_names=False, **kwargs):
520520
"""Linearize an input/output system at a given state and input.
521521
@@ -526,6 +526,14 @@ def linearize(self, x0, u0, t=0, params=None, eps=1e-6,
526526
"""
527527
from .statesp import StateSpace
528528

529+
# Allow first argument to be an operating point
530+
if isinstance(x0, OperatingPoint):
531+
if u0 is None:
532+
u0 = x0.inputs
533+
x0 = x0.states
534+
elif u0 is None:
535+
u0 = 0
536+
529537
#
530538
# If the linearization is not defined by the subclass, perform a
531539
# numerical linearization use the `_rhs()` and `_out()` member
@@ -1673,19 +1681,19 @@ class OperatingPoint(object):
16731681
16741682
Attributes
16751683
----------
1676-
xop : array
1684+
states : array
16771685
State vector at the operating point.
1678-
uop : array
1686+
inputs : array
16791687
Input vector at the operating point.
16801688
result : :class:`scipy.optimize.OptimizeResult`, optional
16811689
Result from the :func:`scipy.optimize.root` function, if available.
16821690
16831691
"""
16841692
def __init__(
1685-
self, xop, uop=None, yop=None, result=None,
1693+
self, states, inputs=None, yop=None, result=None,
16861694
return_y=False, return_result=False):
1687-
self.xop = xop
1688-
self.uop = uop
1695+
self.states = states
1696+
self.inputs = inputs
16891697

16901698
if yop is None and return_y and not return_result:
16911699
raise SystemError("return_y specified by no y0 value")
@@ -1700,13 +1708,13 @@ def __init__(
17001708
# Implement iter to allow assigning to a tuple
17011709
def __iter__(self):
17021710
if self.return_y and self.return_result:
1703-
return iter((self.xop, self.uop, self.yop, self.result))
1711+
return iter((self.states, self.inputs, self.yop, self.result))
17041712
elif self.return_y:
1705-
return iter((self.xop, self.uop, self.yop))
1713+
return iter((self.states, self.inputs, self.yop))
17061714
elif self.return_result:
1707-
return iter((self.xop, self.uop, self.result))
1715+
return iter((self.states, self.inputs, self.result))
17081716
else:
1709-
return iter((self.xop, self.uop))
1717+
return iter((self.states, self.inputs))
17101718

17111719
# Implement (thin) getitem to allow access via legacy indexing
17121720
def __getitem__(self, index):
@@ -1723,10 +1731,10 @@ def find_operating_point(
17231731
root_kwargs=None, return_y=False, return_result=False):
17241732
"""Find an operating point for an input/output system.
17251733
1726-
An operating point for a nonlinear system is a state `xop` and input
1727-
`uop` around which a nonlinear system operates. This point is most
1728-
commonly an equilibrium point for the system, but in some cases a
1729-
non-equilibrium operating point can be used.
1734+
An operating point for a nonlinear system is a state and input around
1735+
which a nonlinear system operates. This point is most commonly an
1736+
equilibrium point for the system, but in some cases a non-equilibrium
1737+
operating point can be used.
17301738
17311739
This function attempts to find an operating point given a specification
17321740
for the desired inputs, outputs, states, or state updates of the system.
@@ -1802,10 +1810,10 @@ def find_operating_point(
18021810
18031811
Returns
18041812
-------
1805-
xop : array of states
1813+
states : array of states
18061814
Value of the states at the equilibrium point, or `None` if no
18071815
equilibrium point was found and `return_result` was False.
1808-
uop : array of input values
1816+
inputs : array of input values
18091817
Value of the inputs at the equilibrium point, or `None` if no
18101818
equilibrium point was found and `return_result` was False.
18111819
yop : array of output values, optional
@@ -2034,12 +2042,13 @@ def linearize(sys, xeq, ueq=None, t=0, params=None, **kw):
20342042
----------
20352043
sys : InputOutputSystem
20362044
The system to be linearized.
2037-
xeq : array
2038-
The state at which the linearization will be evaluated (does not need
2039-
to be an equilibrium state).
2040-
ueq : array
2045+
xeq : array or :class:`~control.OperatingPoint`
2046+
The state or operating point at which the linearization will be
2047+
evaluated (does not need to be an equilibrium state).
2048+
ueq : array, optional
20412049
The input at which the linearization will be evaluated (does not need
2042-
to correspond to an equlibrium state).
2050+
to correspond to an equlibrium state). Can be omitted if `xeq` is
2051+
an :class:`~control.OperatingPoint`. Defaults to 0.
20432052
t : float, optional
20442053
The time at which the linearization will be computed (for time-varying
20452054
systems).
@@ -2074,6 +2083,7 @@ def linearize(sys, xeq, ueq=None, t=0, params=None, **kw):
20742083
Description of the system outputs. Same format as `inputs`.
20752084
states : int, list of str, or None, optional
20762085
Description of the system states. Same format as `inputs`.
2086+
20772087
"""
20782088
if not isinstance(sys, InputOutputSystem):
20792089
raise TypeError("Can only linearize InputOutputSystem types")

control/tests/iosys_test.py

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2102,27 +2102,47 @@ def test_find_operating_point():
21022102
# Default version: no equilibrium solution => returns None
21032103
op_point = ct.find_operating_point(
21042104
sys, x0, u0, y0, ix=ix, iu=iu, iy=iy, dx0=dx0, idx=idx)
2105-
assert op_point.xop is None
2106-
assert op_point.uop is None
2105+
assert op_point.states is None
2106+
assert op_point.inputs is None
21072107
assert op_point.result.success is False
21082108

21092109
# Change the method to Levenberg-Marquardt (gives nearest point)
21102110
op_point = ct.find_operating_point(
21112111
sys, x0, u0, y0, ix=ix, iu=iu, iy=iy, dx0=dx0, idx=idx,
21122112
root_method='lm')
2113-
assert op_point.xop is not None
2114-
assert op_point.uop is not None
2113+
assert op_point.states is not None
2114+
assert op_point.inputs is not None
21152115
assert op_point.result.success is True
21162116

21172117
# Make sure we get a solution if we ask for the result explicitly
21182118
op_point = ct.find_operating_point(
21192119
sys, x0, u0, y0, ix=ix, iu=iu, iy=iy, dx0=dx0, idx=idx,
21202120
return_result=True)
2121-
assert op_point.xop is not None
2122-
assert op_point.uop is not None
2121+
assert op_point.states is not None
2122+
assert op_point.inputs is not None
21232123
assert op_point.result.success is False
21242124

21252125

2126+
def test_operating_point():
2127+
dt = 1
2128+
sys = ct.NonlinearIOSystem(
2129+
eqpt_rhs, eqpt_out, dt=dt, states=3, inputs=2, outputs=2)
2130+
2131+
# Find the operating point near the origin
2132+
op_point = ct.find_operating_point(sys, 0, 0)
2133+
2134+
# Linearize the old fashioned way
2135+
linsys_orig = ct.linearize(sys, op_point.states, op_point.inputs)
2136+
2137+
# Linearize around the operating point
2138+
linsys_oppt = ct.linearize(sys, op_point)
2139+
2140+
np.testing.assert_allclose(linsys_orig.A, linsys_oppt.A)
2141+
np.testing.assert_allclose(linsys_orig.B, linsys_oppt.B)
2142+
np.testing.assert_allclose(linsys_orig.C, linsys_oppt.C)
2143+
np.testing.assert_allclose(linsys_orig.D, linsys_oppt.D)
2144+
2145+
21262146
def test_iosys_sample():
21272147
csys = ct.rss(2, 1, 1)
21282148
dsys = csys.sample(0.1)

doc/control.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ Nonlinear system support
144144
:toctree: generated/
145145

146146
describing_function
147-
find_eqpt
147+
find_operating_point
148148
linearize
149149
input_output_response
150150
summing_junction

doc/iosys.rst

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@ function::
1616
resp = ct.input_output_response(io_sys, T, U, X0, params)
1717
t, y, x = resp.time, resp.outputs, resp.states
1818

19-
An input/output system can be linearized around an equilibrium point to obtain
20-
a :class:`~control.StateSpace` linear system. Use the
21-
:func:`~control.find_eqpt` function to obtain an equilibrium point and the
22-
:func:`~control.linearize` function to linearize about that equilibrium point::
19+
An input/output system can be linearized around an equilibrium point
20+
to obtain a :class:`~control.StateSpace` linear system. Use the
21+
:func:`~control.find_operating_point` function to obtain an
22+
equilibrium point and the :func:`~control.linearize` function to
23+
linearize about that equilibrium point::
2324

24-
xeq, ueq = ct.find_eqpt(io_sys, X0, U0)
25+
xeq, ueq = ct.find_operating_point(io_sys, X0, U0)
2526
ss_sys = ct.linearize(io_sys, xeq, ueq)
2627

2728
Input/output systems are automatically created for state space LTI systems
@@ -123,9 +124,8 @@ system and computing the linearization about that point.
123124

124125
.. code-block:: python
125126
126-
eqpt = ct.find_eqpt(io_predprey, X0, 0)
127-
xeq = eqpt[0] # choose the nonzero equilibrium point
128-
lin_predprey = ct.linearize(io_predprey, xeq, 0)
127+
eqpt = ct.find_operating_point(io_predprey, X0, 0)
128+
lin_predprey = ct.linearize(io_predprey, eqpt)
129129
130130
We next compute a controller that stabilizes the equilibrium point using
131131
eigenvalue placement and computing the feedforward gain using the number of
@@ -548,7 +548,7 @@ Module classes and functions
548548
.. autosummary::
549549
:toctree: generated/
550550

551-
~control.find_eqpt
551+
~control.find_operating_point
552552
~control.interconnect
553553
~control.input_output_response
554554
~control.linearize

0 commit comments

Comments
 (0)