Skip to content

Commit 7da2cfe

Browse files
committed
User Guide copyedits
1 parent 3b726fd commit 7da2cfe

46 files changed

Lines changed: 928 additions & 495 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

control/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
`Feedback Systems <http://fbsbook.org>`_ by Astrom and Murray. In
1313
addition to standard techniques available for linear control systems,
1414
support for nonlinear systems (including trajectory generation, gain
15-
scheduling, phase plane diagrams, and describing functions) is also
15+
scheduling, phase plane diagrams, and describing functions) is
1616
included. A :ref:`matlab-module` is available that provides many of
1717
the common functions corresponding to commands available in the MATLAB
1818
Control Systems Toolbox.

control/freqplot.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@
3838
__all__ = ['bode_plot', 'NyquistResponseData', 'nyquist_response',
3939
'nyquist_plot', 'singular_values_response',
4040
'singular_values_plot', 'gangof4_plot', 'gangof4_response',
41-
'bode', 'nyquist', 'gangof4', 'FrequencyResponseList']
41+
'bode', 'nyquist', 'gangof4', 'FrequencyResponseList',
42+
'NyquistResponseList']
4243

4344
# Default values for module parameter variables
4445
_freqplot_defaults = {
@@ -66,6 +67,11 @@
6667

6768
class FrequencyResponseList(list):
6869
def plot(self, *args, plot_type=None, **kwargs):
70+
"""Plot a list of frequency responses.
71+
72+
See `FrequencyResponseData.plot` for details.
73+
74+
"""
6975
if plot_type == None:
7076
for response in self:
7177
if plot_type is not None and response.plot_type != plot_type:
@@ -1160,7 +1166,19 @@ def plot(self, *args, **kwargs):
11601166

11611167

11621168
class NyquistResponseList(list):
1169+
"""List of NyquistResponseData objects with plotting capability.
1170+
1171+
This class consists of a list of `NyquistResponseData` objects.
1172+
It is a subclass of the Python `list` class, with a `plot` method that
1173+
plots the individual `NyquistResponseData` objects.
1174+
1175+
"""
11631176
def plot(self, *args, **kwargs):
1177+
"""Plot a list of Nyquist responses.
1178+
1179+
See `nyquist_plot` for details.
1180+
1181+
"""
11641182
return nyquist_plot(self, *args, **kwargs)
11651183

11661184

control/iosys.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ def repr_format(self):
306306
307307
Format used in creating the representation for the system:
308308
309-
* 'info' : <IOSystemType:sysname:[inputs]->[outputs]
309+
* 'info' : <IOSystemType:sysname:[inputs] -> [outputs]>
310310
* 'eval' : system specific, loadable representation
311311
* 'latex' : HTML/LaTeX representation of the object
312312

control/nlsys.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -613,7 +613,7 @@ class InterconnectedSystem(NonlinearIOSystem):
613613
Offset to the subsystem inputs, outputs, and states in the overal
614614
system input, output, and state arrays.
615615
syslist_index : dict
616-
Index of the subsytem with key given by the name of the subsystem.
616+
Index of the subsystem with key given by the name of the subsystem.
617617
618618
See Also
619619
--------

control/pzmap.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@
3232
from .statesp import StateSpace
3333
from .xferfcn import TransferFunction
3434

35-
__all__ = ['pole_zero_map', 'pole_zero_plot', 'pzmap', 'PoleZeroData']
35+
__all__ = ['pole_zero_map', 'pole_zero_plot', 'pzmap', 'PoleZeroData',
36+
'PoleZeroList']
3637

3738

3839
# Define default parameter values for this module

control/statefbk.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def sb03md(n, C, A, U, dico, job='X',fact='N',trana='N',ldwork=None):
4141

4242

4343
__all__ = ['ctrb', 'obsv', 'gram', 'place', 'place_varga', 'lqr',
44-
'dlqr', 'acker', 'create_statefbk_iosystem']
44+
'dlqr', 'acker', 'place_acker', 'create_statefbk_iosystem']
4545

4646

4747
# Pole placement
@@ -90,7 +90,7 @@ def place(A, B, p):
9090
9191
See Also
9292
--------
93-
place_varga, acker
93+
place_acker, place_varga
9494
9595
"""
9696
from scipy.signal import place_poles
@@ -114,7 +114,7 @@ def place(A, B, p):
114114

115115

116116
def place_varga(A, B, p, dtime=False, alpha=None):
117-
"""Place closed loop eigenvalues.
117+
"""Place closed loop eigenvalues using Varga method.
118118
119119
K = place_varga(A, B, p, dtime=False, alpha=None)
120120
@@ -145,7 +145,7 @@ def place_varga(A, B, p, dtime=False, alpha=None):
145145
146146
See Also
147147
--------
148-
place, acker
148+
place, place_acker
149149
150150
Notes
151151
-----
@@ -219,11 +219,11 @@ def place_varga(A, B, p, dtime=False, alpha=None):
219219

220220

221221
# Contributed by Roberto Bucher <roberto.bucher@supsi.ch>
222-
def acker(A, B, poles):
222+
def place_acker(A, B, poles):
223223
"""Pole placement using Ackermann method.
224224
225225
Call:
226-
K = acker(A, B, poles)
226+
K = place_acker(A, B, poles)
227227
228228
Parameters
229229
----------
@@ -549,7 +549,7 @@ def dlqr(*args, **kwargs):
549549
return _ssmatrix(K), _ssmatrix(S), E
550550

551551

552-
# Function to create an I/O sytems representing a state feedback controller
552+
# Function to create an I/O systems representing a state feedback controller
553553
def create_statefbk_iosystem(
554554
sys, gain, feedfwd_gain=None, integral_action=None, estimator=None,
555555
controller_type=None, xd_labels=None, ud_labels=None, ref_labels=None,
@@ -1228,3 +1228,7 @@ def gram(sys, type):
12281228
n, m, A, Q, C.transpose(), dico, fact='N', trans=tra)
12291229
gram = X
12301230
return _ssmatrix(gram)
1231+
1232+
1233+
# Short versions of functions
1234+
acker = place_acker

control/statesp.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1980,7 +1980,7 @@ def rss(states=1, outputs=1, inputs=1, strictly_proper=False, **kwargs):
19801980
kwargs.update({'states': states, 'outputs': outputs, 'inputs': inputs})
19811981
name, inputs, outputs, states, dt = _process_iosys_keywords(kwargs)
19821982

1983-
# Figure out the size of the sytem
1983+
# Figure out the size of the system
19841984
nstates, _ = _process_signal_list(states)
19851985
ninputs, _ = _process_signal_list(inputs)
19861986
noutputs, _ = _process_signal_list(outputs)

control/stochsys.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -657,9 +657,10 @@ def correlation(T, X, Y=None, squeeze=True):
657657
658658
tau, Rtau = correlation(T, X[, Y])
659659
660-
The signal X (and Y, if present) represent a continuous time signal
661-
sampled at times T. The return value provides the correlation Rtau
662-
between X(t+tau) and X(t) at a set of time offets tau.
660+
The signal X (and Y, if present) represent a continuous or
661+
discrete time signal sampled at times T. The return value
662+
provides the correlation Rtau between X(t+tau) and X(t) at a set
663+
of time offets tau.
663664
664665
Parameters
665666
----------

control/tests/docstrings_test.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
control.NamedSignal, # internal I/O class
3838
control.TimeResponseList, # internal response class
3939
control.FrequencyResponseList, # internal response class
40+
control.NyquistResponseList, # internal response class
41+
control.PoleZeroList, # internal response class
4042
control.FrequencyResponseData, # check separately (iosys)
4143
control.InterconnectedSystem, # check separately (iosys)
4244
control.flatsys.FlatSystem, # check separately (iosys)

control/tests/flatsys_test.py

Lines changed: 103 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,48 @@
2424
atol = 1e-4
2525
rtol = 1e-4
2626

27+
# Define the kinematic car system
28+
def vehicle_flat_forward(x, u, params={}):
29+
b = params.get('wheelbase', 3.) # get parameter values
30+
zflag = [np.zeros(3), np.zeros(3)] # list for flag arrays
31+
zflag[0][0] = x[0] # flat outputs
32+
zflag[1][0] = x[1]
33+
zflag[0][1] = u[0] * np.cos(x[2]) # first derivatives
34+
zflag[1][1] = u[0] * np.sin(x[2])
35+
thdot = (u[0]/b) * np.tan(u[1]) # dtheta/dt
36+
zflag[0][2] = -u[0] * thdot * np.sin(x[2]) # second derivatives
37+
zflag[1][2] = u[0] * thdot * np.cos(x[2])
38+
return zflag
39+
40+
def vehicle_flat_reverse(zflag, params={}):
41+
b = params.get('wheelbase', 3.) # get parameter values
42+
x = np.zeros(3); u = np.zeros(2) # vectors to store x, u
43+
x[0] = zflag[0][0] # x position
44+
x[1] = zflag[1][0] # y position
45+
x[2] = np.arctan2(zflag[1][1], zflag[0][1]) # angle
46+
u[0] = zflag[0][1] * np.cos(x[2]) + zflag[1][1] * np.sin(x[2])
47+
thdot_v = zflag[1][2] * np.cos(x[2]) - zflag[0][2] * np.sin(x[2])
48+
u[1] = np.arctan2(thdot_v, u[0]**2 / b)
49+
return x, u
50+
51+
def vehicle_update(t, x, u, params):
52+
b = params.get('wheelbase', 3.) # get parameter values
53+
dx = np.array([
54+
np.cos(x[2]) * u[0],
55+
np.sin(x[2]) * u[0],
56+
(u[0]/b) * np.tan(u[1])
57+
])
58+
return dx
59+
60+
def vehicle_output(t, x, u, params): return x
61+
62+
# Create differentially flat input/output system
63+
vehicle_flat = fs.FlatSystem(
64+
vehicle_flat_forward, vehicle_flat_reverse, vehicle_update,
65+
vehicle_output, inputs=('v', 'delta'), outputs=('x', 'y', 'theta'),
66+
states=('x', 'y', 'theta'), name='vehicle_flat')
67+
68+
2769
class TestFlatSys:
2870
"""Test differential flat systems"""
2971

@@ -58,48 +100,9 @@ def test_double_integrator(self, xf, uf, Tf, basis):
58100
t, y, x = ct.forced_response(sys, T, ud, x1, return_x=True)
59101
np.testing.assert_array_almost_equal(x, xd, decimal=3)
60102

61-
@pytest.fixture
62-
def vehicle_flat(self):
63-
"""Differential flatness for a kinematic car"""
64-
def vehicle_flat_forward(x, u, params={}):
65-
b = params.get('wheelbase', 3.) # get parameter values
66-
zflag = [np.zeros(3), np.zeros(3)] # list for flag arrays
67-
zflag[0][0] = x[0] # flat outputs
68-
zflag[1][0] = x[1]
69-
zflag[0][1] = u[0] * np.cos(x[2]) # first derivatives
70-
zflag[1][1] = u[0] * np.sin(x[2])
71-
thdot = (u[0]/b) * np.tan(u[1]) # dtheta/dt
72-
zflag[0][2] = -u[0] * thdot * np.sin(x[2]) # second derivatives
73-
zflag[1][2] = u[0] * thdot * np.cos(x[2])
74-
return zflag
75-
76-
def vehicle_flat_reverse(zflag, params={}):
77-
b = params.get('wheelbase', 3.) # get parameter values
78-
x = np.zeros(3); u = np.zeros(2) # vectors to store x, u
79-
x[0] = zflag[0][0] # x position
80-
x[1] = zflag[1][0] # y position
81-
x[2] = np.arctan2(zflag[1][1], zflag[0][1]) # angle
82-
u[0] = zflag[0][1] * np.cos(x[2]) + zflag[1][1] * np.sin(x[2])
83-
thdot_v = zflag[1][2] * np.cos(x[2]) - zflag[0][2] * np.sin(x[2])
84-
u[1] = np.arctan2(thdot_v, u[0]**2 / b)
85-
return x, u
86-
87-
def vehicle_update(t, x, u, params):
88-
b = params.get('wheelbase', 3.) # get parameter values
89-
dx = np.array([
90-
np.cos(x[2]) * u[0],
91-
np.sin(x[2]) * u[0],
92-
(u[0]/b) * np.tan(u[1])
93-
])
94-
return dx
95-
96-
def vehicle_output(t, x, u, params): return x
97-
98-
# Create differentially flat input/output system
99-
return fs.FlatSystem(
100-
vehicle_flat_forward, vehicle_flat_reverse, vehicle_update,
101-
vehicle_output, inputs=('v', 'delta'), outputs=('x', 'y', 'theta'),
102-
states=('x', 'y', 'theta'))
103+
@pytest.fixture(name='vehicle_flat')
104+
def vehicle_flat_fixture(self):
105+
return vehicle_flat
103106

104107
@pytest.mark.parametrize("basis", [
105108
fs.PolyFamily(6), fs.PolyFamily(8), fs.BezierFamily(6),
@@ -811,3 +814,61 @@ def test_flatsys_factory_function(self, vehicle_flat):
811814

812815
with pytest.raises(TypeError, match="incorrect number or type"):
813816
flatsys = fs.flatsys(1, 2, 3, 4, 5)
817+
818+
819+
if __name__ == '__main__':
820+
# Generate images for User Guide
821+
import matplotlib.pyplot as plt
822+
823+
#
824+
# Point to point
825+
#
826+
# Define the endpoints of the trajectory
827+
x0 = [0., -2., 0.]; u0 = [10., 0.]
828+
xf = [100., 2., 0.]; uf = [10., 0.]
829+
Tf = 10
830+
831+
# Define a set of basis functions to use for the trajectories
832+
poly = fs.PolyFamily(6)
833+
834+
# Find a trajectory between the initial condition and the final condition
835+
traj = fs.point_to_point(vehicle_flat, Tf, x0, u0, xf, uf, basis=poly)
836+
837+
# Create the trajectory
838+
timepts = np.linspace(0, Tf, 100)
839+
xd, ud = traj.eval(timepts)
840+
resp_p2p = ct.input_output_response(vehicle_flat, timepts, ud, X0=xd[:, 0])
841+
842+
#
843+
# Solve OCP
844+
#
845+
# Define the cost along the trajectory: penalize steering angle
846+
traj_cost = ct.optimal.quadratic_cost(
847+
vehicle_flat, None, np.diag([0.1, 10]), u0=uf)
848+
849+
# Define the terminal cost: penalize distance from the end point
850+
term_cost = ct.optimal.quadratic_cost(
851+
vehicle_flat, np.diag([1e3, 1e3, 1e3]), None, x0=xf)
852+
853+
# Use a straight line as the initial guess
854+
evalpts = np.linspace(0, Tf, 10)
855+
initial_guess = np.array(
856+
[x0[i] + (xf[i] - x0[i]) * evalpts/Tf for i in (0, 1)])
857+
858+
# Solve the optimal control problem, evaluating cost at timepts
859+
bspline = fs.BSplineFamily([0, Tf/2, Tf], 4)
860+
traj = fs.solve_flat_ocp(
861+
vehicle_flat, evalpts, x0, u0, traj_cost,
862+
terminal_cost=term_cost, initial_guess=initial_guess, basis=bspline)
863+
864+
xd, ud = traj.eval(timepts)
865+
resp_ocp = ct.input_output_response(vehicle_flat, timepts, ud, X0=xd[:, 0])
866+
867+
#
868+
# Plot the results
869+
#
870+
cplt = ct.time_response_plot(
871+
ct.combine_time_responses([resp_p2p, resp_ocp]),
872+
overlay_traces=True, trace_labels=['point_to_point', 'solve_ocp'])
873+
874+
plt.savefig('flatsys-steering-compare.png')

0 commit comments

Comments
 (0)