Skip to content

Commit eb2247c

Browse files
committed
User Guide copyedits
1 parent a69c014 commit eb2247c

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
@@ -37,7 +37,8 @@
3737
__all__ = ['bode_plot', 'NyquistResponseData', 'nyquist_response',
3838
'nyquist_plot', 'singular_values_response',
3939
'singular_values_plot', 'gangof4_plot', 'gangof4_response',
40-
'bode', 'nyquist', 'gangof4', 'FrequencyResponseList']
40+
'bode', 'nyquist', 'gangof4', 'FrequencyResponseList',
41+
'NyquistResponseList']
4142

4243
# Default values for module parameter variables
4344
_freqplot_defaults = {
@@ -65,6 +66,11 @@
6566

6667
class FrequencyResponseList(list):
6768
def plot(self, *args, plot_type=None, **kwargs):
69+
"""Plot a list of frequency responses.
70+
71+
See `FrequencyResponseData.plot` for details.
72+
73+
"""
6874
if plot_type == None:
6975
for response in self:
7076
if plot_type is not None and response.plot_type != plot_type:
@@ -1157,7 +1163,19 @@ def plot(self, *args, **kwargs):
11571163

11581164

11591165
class NyquistResponseList(list):
1166+
"""List of NyquistResponseData objects with plotting capability.
1167+
1168+
This class consists of a list of `NyquistResponseData` objects.
1169+
It is a subclass of the Python `list` class, with a `plot` method that
1170+
plots the individual `NyquistResponseData` objects.
1171+
1172+
"""
11601173
def plot(self, *args, **kwargs):
1174+
"""Plot a list of Nyquist responses.
1175+
1176+
See `nyquist_plot` for details.
1177+
1178+
"""
11611179
return nyquist_plot(self, *args, **kwargs)
11621180

11631181

control/iosys.py

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

control/nlsys.py

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

control/pzmap.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@
2929
from .statesp import StateSpace
3030
from .xferfcn import TransferFunction
3131

32-
__all__ = ['pole_zero_map', 'pole_zero_plot', 'pzmap', 'PoleZeroData']
32+
__all__ = ['pole_zero_map', 'pole_zero_plot', 'pzmap', 'PoleZeroData',
33+
'PoleZeroList']
3334

3435

3536
# Define default parameter values for this module

control/statefbk.py

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

4141

4242
__all__ = ['ctrb', 'obsv', 'gram', 'place', 'place_varga', 'lqr',
43-
'dlqr', 'acker', 'create_statefbk_iosystem']
43+
'dlqr', 'acker', 'place_acker', 'create_statefbk_iosystem']
4444

4545

4646
# Pole placement
@@ -89,7 +89,7 @@ def place(A, B, p):
8989
9090
See Also
9191
--------
92-
place_varga, acker
92+
place_acker, place_varga
9393
9494
"""
9595
from scipy.signal import place_poles
@@ -107,7 +107,7 @@ def place(A, B, p):
107107

108108

109109
def place_varga(A, B, p, dtime=False, alpha=None):
110-
"""Place closed loop eigenvalues.
110+
"""Place closed loop eigenvalues using Varga method.
111111
112112
K = place_varga(A, B, p, dtime=False, alpha=None)
113113
@@ -138,7 +138,7 @@ def place_varga(A, B, p, dtime=False, alpha=None):
138138
139139
See Also
140140
--------
141-
place, acker
141+
place, place_acker
142142
143143
Notes
144144
-----
@@ -210,11 +210,11 @@ def place_varga(A, B, p, dtime=False, alpha=None):
210210

211211

212212
# Contributed by Roberto Bucher <roberto.bucher@supsi.ch>
213-
def acker(A, B, poles):
213+
def place_acker(A, B, poles):
214214
"""Pole placement using Ackermann method.
215215
216216
Call:
217-
K = acker(A, B, poles)
217+
K = place_acker(A, B, poles)
218218
219219
Parameters
220220
----------
@@ -540,7 +540,7 @@ def dlqr(*args, **kwargs):
540540
return K, S, E
541541

542542

543-
# Function to create an I/O sytems representing a state feedback controller
543+
# Function to create an I/O systems representing a state feedback controller
544544
def create_statefbk_iosystem(
545545
sys, gain, feedfwd_gain=None, integral_action=None, estimator=None,
546546
controller_type=None, xd_labels=None, ud_labels=None, ref_labels=None,
@@ -1220,3 +1220,7 @@ def gram(sys, type):
12201220
n, m, A, Q, C.transpose(), dico, fact='N', trans=tra)
12211221
gram = X
12221222
return gram
1223+
1224+
1225+
# Short versions of functions
1226+
acker = place_acker

control/statesp.py

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

2038-
# Figure out the size of the sytem
2038+
# Figure out the size of the system
20392039
nstates, _ = _process_signal_list(states)
20402040
ninputs, _ = _process_signal_list(inputs)
20412041
noutputs, _ = _process_signal_list(outputs)

control/stochsys.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -660,9 +660,10 @@ def correlation(T, X, Y=None, squeeze=True):
660660
661661
tau, Rtau = correlation(T, X[, Y])
662662
663-
The signal X (and Y, if present) represent a continuous time signal
664-
sampled at times T. The return value provides the correlation Rtau
665-
between X(t+tau) and X(t) at a set of time offets tau.
663+
The signal X (and Y, if present) represent a continuous or
664+
discrete time signal sampled at times T. The return value
665+
provides the correlation Rtau between X(t+tau) and X(t) at a set
666+
of time offets tau.
666667
667668
Parameters
668669
----------

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),
@@ -839,3 +842,61 @@ def test_flatsys_factory_function(self, vehicle_flat):
839842

840843
with pytest.raises(TypeError, match="incorrect number or type"):
841844
flatsys = fs.flatsys(1, 2, 3, 4, 5)
845+
846+
847+
if __name__ == '__main__':
848+
# Generate images for User Guide
849+
import matplotlib.pyplot as plt
850+
851+
#
852+
# Point to point
853+
#
854+
# Define the endpoints of the trajectory
855+
x0 = [0., -2., 0.]; u0 = [10., 0.]
856+
xf = [100., 2., 0.]; uf = [10., 0.]
857+
Tf = 10
858+
859+
# Define a set of basis functions to use for the trajectories
860+
poly = fs.PolyFamily(6)
861+
862+
# Find a trajectory between the initial condition and the final condition
863+
traj = fs.point_to_point(vehicle_flat, Tf, x0, u0, xf, uf, basis=poly)
864+
865+
# Create the trajectory
866+
timepts = np.linspace(0, Tf, 100)
867+
xd, ud = traj.eval(timepts)
868+
resp_p2p = ct.input_output_response(vehicle_flat, timepts, ud, X0=xd[:, 0])
869+
870+
#
871+
# Solve OCP
872+
#
873+
# Define the cost along the trajectory: penalize steering angle
874+
traj_cost = ct.optimal.quadratic_cost(
875+
vehicle_flat, None, np.diag([0.1, 10]), u0=uf)
876+
877+
# Define the terminal cost: penalize distance from the end point
878+
term_cost = ct.optimal.quadratic_cost(
879+
vehicle_flat, np.diag([1e3, 1e3, 1e3]), None, x0=xf)
880+
881+
# Use a straight line as the initial guess
882+
evalpts = np.linspace(0, Tf, 10)
883+
initial_guess = np.array(
884+
[x0[i] + (xf[i] - x0[i]) * evalpts/Tf for i in (0, 1)])
885+
886+
# Solve the optimal control problem, evaluating cost at timepts
887+
bspline = fs.BSplineFamily([0, Tf/2, Tf], 4)
888+
traj = fs.solve_flat_ocp(
889+
vehicle_flat, evalpts, x0, u0, traj_cost,
890+
terminal_cost=term_cost, initial_guess=initial_guess, basis=bspline)
891+
892+
xd, ud = traj.eval(timepts)
893+
resp_ocp = ct.input_output_response(vehicle_flat, timepts, ud, X0=xd[:, 0])
894+
895+
#
896+
# Plot the results
897+
#
898+
cplt = ct.time_response_plot(
899+
ct.combine_time_responses([resp_p2p, resp_ocp]),
900+
overlay_traces=True, trace_labels=['point_to_point', 'solve_ocp'])
901+
902+
plt.savefig('flatsys-steering-compare.png')

0 commit comments

Comments
 (0)