Skip to content

Commit 1bcdb3a

Browse files
committed
updated lectures, with fixes, full model code, + colab links
1 parent 546a4f1 commit 1bcdb3a

15 files changed

Lines changed: 603 additions & 237 deletions

examples/Makefile

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,7 @@
55
# Google Colab.
66

77
NOTEBOOKS = cds110-L*_*.ipynb
8-
FILES = satellite-dish.png disk-drive.png robotic-arm.png \
9-
springmass-coupled.png pvtol.py kincar.py estimation.png ssctrl.png \
10-
zn-step-response.png zn-step-table.png zn-step-improved.png \
11-
pid-aw-diagram.png
12-
GDRIVE= gdrive:python-control/examples
8+
GDRIVE= gdrive:projects/python-control/public/notebooks
139

1410
# Clean up notebooks to remove output
1511
clean: .ipynb-clean
@@ -23,5 +19,5 @@ clean: .ipynb-clean
2319
touch $@
2420

2521
# Post Jupyter notebooks on course website
26-
post: .ipynb-clean $(FILES)
27-
rclone -u copy $(NOTEBOOKS) $(FILES) $(GDRIVE)
22+
post: .ipynb-clean
23+
rclone copy . $(GDRIVE) --include /cds110-L\*_\*.ipynb

examples/cds110-L1_servomech-python.ipynb

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88
},
99
"source": [
1010
"<center>\n",
11-
"<h4>CDS 110, Lecture 3</h4>\n",
11+
"<h4>CDS 110, Lecture 1</h4>\n",
1212
"<font color=blue><h1>Dynamics and Control of a Servomechanism System using Python-Control</h1></font>\n",
1313
"<h3>Richard M. Murray, Winter 2024</h3>\n",
1414
"</center>\n",
15-
"<br>\n",
1615
"\n",
17-
"In this lecture we show how to model an input/output system and design a controller for the system (using eigenvalue placement).\n",
16+
"[Open in Google Colab](https://colab.research.google.com/drive/1a9ECA-g4Vfi-D3LtbN21xtV9dLzA2RrF)\n",
17+
"\n",
18+
"In this lecture we show how to model an input/output system and design a controller for the system (using eigenvalue placement). This main intent of this lecture is to introduction the Python Control Systems Toolbox ([python-control](https://python-control.org)) and how it can be used to design a control system.\n",
1819
"\n",
1920
"We consider a class of control systems know as *servomechanisms*. Servermechanisms are mechanical systems that use feedback to provide high precision control of position and velocity. Some examples of servomechanisms are shown below:\n",
2021
"\n",
@@ -67,9 +68,9 @@
6768
" + \\begin{bmatrix} 0 \\\\ 1/J \\end{bmatrix} \\tau_\\text{m}.\n",
6869
"$$\n",
6970
"\n",
70-
"The system consists of a spring loaded arm that is driven by a motor, as shown below\"\n",
71+
"The system consists of a spring loaded arm that is driven by a motor, as shown below:\n",
7172
"\n",
72-
"![servomech-diagram](./servomech-diagram.png)\n",
73+
"<center><img src=\"https://www.cds.caltech.edu/~murray/courses/cds110/sp2024/servomech-diagram.png\" width=200 alt=\"servomech-diagram\"></center>\n",
7374
"\n",
7475
"The motor applies a torque that twists the arm against a linear spring and moves the end of the arm across a rotating platter. The input to the system is the motor torque $\\tau_\\text{m}$. The force exerted by the spring is a nonlinear function of the head position due to the way it is attached.\n",
7576
"\n",
@@ -80,7 +81,7 @@
8081
"\\quad r = 1,\\quad l = 2,\\quad \\epsilon = 0.01.\n",
8182
"$$\n",
8283
"\n",
83-
"and we assume that time is measured in msec and distance in cm. (The constants here are made up and don't necessarily reflect a real disk drive, though the units and time constants are motivated by computer disk drives.)"
84+
"and we assume that time is measured in milliseconds (ms) and distance in centimeters (cm). (The constants here are made up and don't necessarily reflect a real disk drive, though the units and time constants are motivated by computer disk drives.)"
8485
]
8586
},
8687
{
@@ -127,16 +128,15 @@
127128
" # Return the state update law\n",
128129
" return np.array([thetadot, dthetadot])\n",
129130
"\n",
130-
"# System output (full state)\n",
131+
"# System output (tip radial position + angular velocity)\n",
131132
"def servomech_output(t, x, u, params):\n",
132133
" l = params['l']\n",
133134
" return np.array([l * x[0], x[1]])\n",
134135
"\n",
135136
"# System dynamics\n",
136137
"servomech = ct.nlsys(\n",
137138
" servomech_update, servomech_output, name='servomech',\n",
138-
" params=servomech_params,\n",
139-
" states=['theta_', 'thdot_'],\n",
139+
" params=servomech_params, states=['theta_', 'thdot_'],\n",
140140
" outputs=['y', 'thdot'], inputs=['tau'])\n",
141141
"\n",
142142
"print(servomech)\n",
@@ -171,7 +171,8 @@
171171
"\n",
172172
"# Linearize the system about the equilibrium point\n",
173173
"P = servomech.linearize([theta_e, 0], u_e)[0, 0]\n",
174-
"print(\"Linearized dynamics:\", P)"
174+
"# P.update_names(name='linservo')\n",
175+
"print(\"Linearized dynamics:\\n\", P)"
175176
]
176177
},
177178
{
@@ -254,7 +255,7 @@
254255
"# Plot step response (input 0 to output 0)\n",
255256
"plt.plot(timepts, output)\n",
256257
"plt.xlabel(\"Time $t$ [ms]\")\n",
257-
"plt.ylabel(\"Position $y$ [m]\")\n",
258+
"plt.ylabel(\"Position $y$ [cm]\")\n",
258259
"plt.title(\"Step response for the linearized, open-loop system\")\n",
259260
"\n",
260261
"# Compute and print properties of the step response\n",
@@ -314,7 +315,7 @@
314315
"plt.plot(timepts, nl_response.outputs[0], label=\"nonlinear\")\n",
315316
"\n",
316317
"plt.xlabel(\"Time $t$ [ms]\")\n",
317-
"plt.ylabel(\"Position $y$ [m]\")\n",
318+
"plt.ylabel(\"Position $y$ [cm]\")\n",
318319
"plt.title(\"Step response for the open-loop system\")\n",
319320
"plt.legend()"
320321
]
@@ -326,7 +327,7 @@
326327
"id": "7YNmgE2XHmL3"
327328
},
328329
"source": [
329-
"We see that the nonlinear system responses differently. This is because of the fact that the force exerted by the spring is nonlinear due to the kinematics of the mechanism design."
330+
"We see that the nonlinear system responds differently. This is because the force exerted by the spring is nonlinear due to the kinematics of the mechanism design."
330331
]
331332
},
332333
{
@@ -338,9 +339,9 @@
338339
"source": [
339340
"## Feedback control design\n",
340341
"\n",
341-
"We next design a feedback controller for the system that that allows the system to track a desired position $y_\\text{d}$ and sets the closed loop eigenvalues of the linearized system to $\\lambda_{1,2} = −10 \\pm 10 i$.\n",
342+
"We next design a feedback controller for the system that that allows the system to track a desired position $y_\\text{d}$ and sets the closed loop eigenvalues of the linearized system to $\\lambda_{1,2} = −10 \\pm 10 i$. We will learn how to do this more formally in later lectures, so if you aren't familiar with these techniques, that's OK.\n",
342343
"\n",
343-
"To do this, we make use of full state feedback of the form $u = -K(x - x_\\text{d})$ where $x_\\text{d}$ is the desired state of the system. The python-control `place` command can be used to compute the state feedback gains $K$ that set the closed loop poles at a desired location:"
344+
"We make use of full state feedback of the form $u = -K(x - x_\\text{d})$ where $x_\\text{d}$ is the desired state of the system. The python-control `place` command can be used to compute the state feedback gains $K$ that set the closed loop poles at a desired location:"
344345
]
345346
},
346347
{
@@ -435,9 +436,9 @@
435436
"# Compute and print properties of the step response\n",
436437
"results = ct.step_info(clsys_resp.outputs[0], timepts)\n",
437438
"print(\"\")\n",
438-
"print(\"Rise time:\", results['RiseTime'])\n",
439-
"print(\"Settling time:\", results['SettlingTime'])\n",
440-
"print(\"Steady state error (percent):\", abs(results['SteadyStateValue'] - 1) * 100)"
439+
"print(f\"Rise time: {results['RiseTime']:.2g} ms\")\n",
440+
"print(f\"Settling time: {results['SettlingTime']:.2g} ms\")\n",
441+
"print(f\"Steady state error: {abs(results['SteadyStateValue'] - 1) * 100:.2g}%\")"
441442
]
442443
},
443444
{
@@ -464,7 +465,7 @@
464465
"Roughly speaking, we set the input of the system to be of the form $u(t) = \\sin(\\omega t)$ and then look at the output signal $y(t)$. For a *linear* system, we can show that the output signal will have the form\n",
465466
"\n",
466467
"$$\n",
467-
"y(t) = M sin(\\omega t + \\phi)\n",
468+
"y(t) = M \\sin(\\omega t + \\phi)\n",
468469
"$$\n",
469470
"\n",
470471
"where the magnitude $M$ and phase $\\phi$ depend on the input frequency.\n",
@@ -487,7 +488,7 @@
487488
"response = ct.frequency_response(G[0, 0])\n",
488489
"out = response.plot()\n",
489490
"axs = ct.get_plot_axes(out)\n",
490-
"axs[1][0].set_xlabel(\"Frequency [rad/ms]\");"
491+
"axs[1, 0].set_xlabel(\"Frequency [rad/ms]\");"
491492
]
492493
},
493494
{

examples/cds110-L2_invpend-dynamics.ipynb

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@
88
"source": [
99
"<center>\n",
1010
"<h4>CDS 110, Lecture 2</h4>\n",
11-
"<font color=blue><h1>Nonlinear Dynamics for an Inverted Pendulum System</h1></font>\n",
11+
"<font color=blue><h1>Nonlinear Dynamics (and Control) of an Inverted Pendulum System</h1></font>\n",
1212
"<h3>Richard M. Murray, Winter 2024</h3>\n",
1313
"</center>\n",
14-
"<br>\n",
1514
"\n",
16-
"In this lecture we investigate the nonlinear dynamics of an inverted pendulum system. More information on this example can be found in [FBS2e](https://fbswiki.org/wiki/index.php?title=FBS), Examples 3.3 and 5.4.\n"
15+
"[Open in Google Colab](https://colab.research.google.com/drive/1rAFZyV5XrLrqTpCJ83ow7QBJBJy8yy3u)\n",
16+
"\n",
17+
"In this lecture we investigate the nonlinear dynamics of an inverted pendulum system. More information on this example can be found in [FBS2e](https://fbswiki.org/wiki/index.php?title=FBS), Examples 3.3 and 5.4. This lecture demonstrates how to use [python-control](https://python-control.org) to analyze nonlinear systems, including creating phase plane plots.\n"
1718
]
1819
},
1920
{
@@ -26,8 +27,12 @@
2627
"import numpy as np\n",
2728
"import matplotlib.pyplot as plt\n",
2829
"from math import pi\n",
29-
"\n",
30-
"import control as ct"
30+
"try:\n",
31+
" import control as ct\n",
32+
" print(\"python-control\", ct.__version__)\n",
33+
"except ImportError:\n",
34+
" !pip install control\n",
35+
" import control as ct"
3136
]
3237
},
3338
{
@@ -45,6 +50,10 @@
4550
"id": "Msad1ficHjtc"
4651
},
4752
"source": [
53+
"We consider an invereted pendulum, which is a simplified version of a balance system:\n",
54+
"\n",
55+
"<center><img src=\"https://www.cds.caltech.edu/~murray/courses/cds110/sp2024/invpend-diagram.png\" alt=\"invpend.diagram\" width=100></center>\n",
56+
"\n",
4857
"The dynamics for an inverted pendulum system can be written as:\n",
4958
"\n",
5059
"$$\n",
@@ -187,6 +196,8 @@
187196
"id": "IIiSaHNuM1u_"
188197
},
189198
"source": [
199+
"Note: you will see a warning when you run this command, because the output $\\dot\\theta$ (`thdot`) is not connected to anything. You can ignore this here, but as you get to more complicated examples, you should pay attention to warnings of this sort and make sure they are OK.\n",
200+
"\n",
190201
"We can now linearize the closed loop system at different gains and compute the eigenvalues to check for stability:"
191202
]
192203
},
@@ -249,10 +260,11 @@
249260
"This plot is not very useful and has several errors. It shows the limitations of the default parameter values for the `phase_plane_plot` command.\n",
250261
"\n",
251262
"Some things to notice in this plot:\n",
252-
"* The equilibrium point at $\\theta = 0$ is not showing up. This happens because the grid spacing is such that we don't find that point.\n",
263+
"* Not all of the equilibrium points are showing up (there are two unstable equilibrium points that are missing)\n",
264+
"* There is no detail about what is happening near the origin.\n",
253265
"\n",
254266
"To fix these issues, we can do a couple of things:\n",
255-
"* Restrict the range of the plot from $-\\pi$ to $\\pi$, which means that grid used to calculate the equilibrium point is a bit finer.\n",
267+
"* Restrict the range of the plot from $-3\\pi/2$ to $3\\pi/2$, which means that grid used to calculate the equilibrium point is a bit finer.\n",
256268
"* Reset the grid spacing, so that we have more initial conditions around the edge of the plot and a finer search for equilibrium points.\n",
257269
"\n",
258270
"Here's some improved code:"
@@ -307,6 +319,8 @@
307319
"u = -K (x - x_\\text{d}) = -k_1 (\\theta - \\theta_d) - k_2 (\\dot\\theta - \\dot\\theta_d).\n",
308320
"$$\n",
309321
"\n",
322+
"We will learn morea bout how to design these controllers later, so if you aren't familiar with the idea of eigenvalue placement, just take this as a bit of \"control theory magic\" for now.\n",
323+
"\n",
310324
"To compute the gains, we make use of the `place` command, applied to the linearized system:"
311325
]
312326
},
@@ -394,7 +408,8 @@
394408
"Here are some things to try with the above code:\n",
395409
"* Try changing the locations of the closed loop eigenvalues in the `place` command\n",
396410
"* Try resetting the limits of the control action (`umax`)\n",
397-
"* Try leaving the state space controller fixed but changing the parameters of the system dynamics ($m$, $l$, $b$). Does the controller still stabilize the system?"
411+
"* Try leaving the state space controller fixed but changing the parameters of the system dynamics ($m$, $l$, $b$). Does the controller still stabilize the system?\n",
412+
"* Plot the initial condition response of the system and see how to map time traces to phase plot traces."
398413
]
399414
},
400415
{

examples/cds110-L3_lti-systems.ipynb

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@
88
"source": [
99
"<center>\n",
1010
"<h4>CDS 110, Lecture 3</h4>\n",
11-
"<font color=blue><h1>Python Tools for Analyzing Linear Systems/h1></font>\n",
11+
"<font color=blue><h1>Python Tools for Analyzing Linear Systems</h1></font>\n",
1212
"<h3>Richard M. Murray, Winter 2024</h3>\n",
1313
"</center>\n",
14-
"<br>\n",
1514
"\n",
16-
"In this lecture we describe tools in the Python Control Systems Toolbox (python-control) that can be used to analyze linear systems, including some of the options available to present the information in different ways.\n"
15+
"[Open in Google Colab](https://colab.research.google.com/drive/1ZJUAOXFR6VXQ9xfba9KT99N4FbEraQMM)\n",
16+
"\n",
17+
"In this lecture we describe tools in the Python Control Systems Toolbox ([python-control](https://python-control.org]) that can be used to analyze linear systems, including some of the options available to present the information in different ways.\n"
1718
]
1819
},
1920
{
@@ -23,11 +24,13 @@
2324
"outputs": [],
2425
"source": [
2526
"import numpy as np\n",
26-
"%matplotlib inline\n",
2727
"import matplotlib.pyplot as plt\n",
28-
"\n",
29-
"import control as ct\n",
30-
"print(\"python-control version:\", ct.__version__)"
28+
"try:\n",
29+
" import control as ct\n",
30+
" print(\"python-control\", ct.__version__)\n",
31+
"except ImportError:\n",
32+
" !pip install control\n",
33+
" import control as ct"
3134
]
3235
},
3336
{
@@ -41,7 +44,7 @@
4144
"\n",
4245
"Consider the spring mass system below:\n",
4346
"\n",
44-
"<img src=\"springmass-coupled.png\" width=640>\n",
47+
"<center><img src=\"https://www.cds.caltech.edu/~murray/courses/cds110/sp2024/springmass-coupled.png\" width=640></center>\n",
4548
"\n",
4649
"We wish to analyze the time and frequency response of this system using a variety of python-control functions for linear systems analysis.\n",
4750
"\n",
@@ -199,7 +202,12 @@
199202
"\n",
200203
"for X0 in [[1, 0, 0, 0], [0, 2, 0, 0], [1, 2, 0, 0], [0, 0, 1, 0], [0, 0, 2, 0]]:\n",
201204
" response = ct.initial_response(sys, T=20, X0=X0)\n",
202-
" response.plot(color=next(colors), label=f\"{X0=}\")"
205+
" try:\n",
206+
" response.plot(color=next(colors), label=f\"{X0=}\")\n",
207+
" except TypeError:\n",
208+
" # Earlier versions of python-control don't allow relabeling\n",
209+
" response.sysname = f\"{X0=}\"\n",
210+
" response.plot(color=next(colors))"
203211
]
204212
},
205213
{
@@ -268,6 +276,7 @@
268276
"metadata": {},
269277
"outputs": [],
270278
"source": [
279+
"# Plot the inputs on top of the outputs\n",
271280
"out = stepresp.plot(plot_inputs='overlay')"
272281
]
273282
},
@@ -366,10 +375,16 @@
366375
"# Manual computation of the frequency response\n",
367376
"resp = ct.input_output_response(sys, T, np.sin(1.35 * T))\n",
368377
"\n",
369-
"out = resp.plot(\n",
378+
"try:\n",
379+
" out = resp.plot(\n",
370380
" plot_inputs='overlay', \n",
371381
" legend_map=np.array([['lower left'], ['lower left']]),\n",
372-
" label=[['q1', 'u[0]'], ['q2', None]])"
382+
" label=[['q1', 'u[0]'], ['q2', None]])\n",
383+
"except TypeError:\n",
384+
" # Earlier versions of python-control don't allow relabeling\n",
385+
" out = resp.plot(\n",
386+
" plot_inputs='overlay',\n",
387+
" legend_map=np.array([['lower left'], ['lower left']]))"
373388
]
374389
},
375390
{
@@ -393,8 +408,11 @@
393408
"metadata": {},
394409
"outputs": [],
395410
"source": [
396-
"# Create SISO transfer functions, in case we don't have slycot\n",
397-
"G = ct.ss2tf(sys, name='u to q1')\n",
411+
"try:\n",
412+
" G = ct.ss2tf(sys, name='u to q1, q2')\n",
413+
"except ct.ControlMIMONotImplemented:\n",
414+
" # Create SISO transfer functions, in case we don't have slycot\n",
415+
" G = ct.ss2tf(sys[0, 0], name='u to q1')\n",
398416
"print(G)"
399417
]
400418
},

0 commit comments

Comments
 (0)