Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
updated lectures, with fixes, full model code, + colab links
  • Loading branch information
murrayrm committed Aug 9, 2024
commit 1bcdb3a5df35d1e328e2473d2644d58aa2fcc186
10 changes: 3 additions & 7 deletions examples/Makefile

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make clean is generally useful, but make post does not seem to be, unless they configure gdrive and rclone similarly. I recommend adding a comment that other users may need to change GDRIVE for their own upload target.

Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@
# Google Colab.

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

# Clean up notebooks to remove output
clean: .ipynb-clean
Expand All @@ -23,5 +19,5 @@ clean: .ipynb-clean
touch $@

# Post Jupyter notebooks on course website
post: .ipynb-clean $(FILES)
rclone -u copy $(NOTEBOOKS) $(FILES) $(GDRIVE)
post: .ipynb-clean
rclone copy . $(GDRIVE) --include /cds110-L\*_\*.ipynb
41 changes: 21 additions & 20 deletions examples/cds110-L1_servomech-python.ipynb

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • "Moment of inertial" -> "Moment of inertia"
  • "loop dynamicsof" -> "loop dynamics of"
  • "for the system that that" -> "for the system that"
  • "the the process" -> "the process"
  • "connect everthing" -> "connect everything"
  • "for a referend trajectory" -> "for a reference trajectory"

Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@
},
"source": [
"<center>\n",
"<h4>CDS 110, Lecture 3</h4>\n",
"<h4>CDS 110, Lecture 1</h4>\n",
"<font color=blue><h1>Dynamics and Control of a Servomechanism System using Python-Control</h1></font>\n",
"<h3>Richard M. Murray, Winter 2024</h3>\n",
"</center>\n",
"<br>\n",
"\n",
"In this lecture we show how to model an input/output system and design a controller for the system (using eigenvalue placement).\n",
"[Open in Google Colab](https://colab.research.google.com/drive/1a9ECA-g4Vfi-D3LtbN21xtV9dLzA2RrF)\n",
"\n",
"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",
"\n",
"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",
"\n",
Expand Down Expand Up @@ -67,9 +68,9 @@
" + \\begin{bmatrix} 0 \\\\ 1/J \\end{bmatrix} \\tau_\\text{m}.\n",
"$$\n",
"\n",
"The system consists of a spring loaded arm that is driven by a motor, as shown below\"\n",
"The system consists of a spring loaded arm that is driven by a motor, as shown below:\n",
"\n",
"![servomech-diagram](./servomech-diagram.png)\n",
"<center><img src=\"https://www.cds.caltech.edu/~murray/courses/cds110/sp2024/servomech-diagram.png\" width=200 alt=\"servomech-diagram\"></center>\n",
"\n",
"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",
"\n",
Expand All @@ -80,7 +81,7 @@
"\\quad r = 1,\\quad l = 2,\\quad \\epsilon = 0.01.\n",
"$$\n",
"\n",
"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.)"
"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.)"
]
},
{
Expand Down Expand Up @@ -127,16 +128,15 @@
" # Return the state update law\n",
" return np.array([thetadot, dthetadot])\n",
"\n",
"# System output (full state)\n",
"# System output (tip radial position + angular velocity)\n",
"def servomech_output(t, x, u, params):\n",
" l = params['l']\n",
" return np.array([l * x[0], x[1]])\n",
"\n",
"# System dynamics\n",
"servomech = ct.nlsys(\n",
" servomech_update, servomech_output, name='servomech',\n",
" params=servomech_params,\n",
" states=['theta_', 'thdot_'],\n",
" params=servomech_params, states=['theta_', 'thdot_'],\n",
" outputs=['y', 'thdot'], inputs=['tau'])\n",
"\n",
"print(servomech)\n",
Expand Down Expand Up @@ -171,7 +171,8 @@
"\n",
"# Linearize the system about the equilibrium point\n",
"P = servomech.linearize([theta_e, 0], u_e)[0, 0]\n",
"print(\"Linearized dynamics:\", P)"
"# P.update_names(name='linservo')\n",
"print(\"Linearized dynamics:\\n\", P)"
]
},
{
Expand Down Expand Up @@ -254,7 +255,7 @@
"# Plot step response (input 0 to output 0)\n",
"plt.plot(timepts, output)\n",
"plt.xlabel(\"Time $t$ [ms]\")\n",
"plt.ylabel(\"Position $y$ [m]\")\n",
"plt.ylabel(\"Position $y$ [cm]\")\n",
"plt.title(\"Step response for the linearized, open-loop system\")\n",
"\n",
"# Compute and print properties of the step response\n",
Expand Down Expand Up @@ -314,7 +315,7 @@
"plt.plot(timepts, nl_response.outputs[0], label=\"nonlinear\")\n",
"\n",
"plt.xlabel(\"Time $t$ [ms]\")\n",
"plt.ylabel(\"Position $y$ [m]\")\n",
"plt.ylabel(\"Position $y$ [cm]\")\n",
"plt.title(\"Step response for the open-loop system\")\n",
"plt.legend()"
]
Expand All @@ -326,7 +327,7 @@
"id": "7YNmgE2XHmL3"
},
"source": [
"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."
"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."
]
},
{
Expand All @@ -338,9 +339,9 @@
"source": [
"## Feedback control design\n",
"\n",
"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",
"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",
"\n",
"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:"
"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:"
]
},
{
Expand Down Expand Up @@ -435,9 +436,9 @@
"# Compute and print properties of the step response\n",
"results = ct.step_info(clsys_resp.outputs[0], timepts)\n",
"print(\"\")\n",
"print(\"Rise time:\", results['RiseTime'])\n",
"print(\"Settling time:\", results['SettlingTime'])\n",
"print(\"Steady state error (percent):\", abs(results['SteadyStateValue'] - 1) * 100)"
"print(f\"Rise time: {results['RiseTime']:.2g} ms\")\n",
"print(f\"Settling time: {results['SettlingTime']:.2g} ms\")\n",
"print(f\"Steady state error: {abs(results['SteadyStateValue'] - 1) * 100:.2g}%\")"
]
},
{
Expand All @@ -464,7 +465,7 @@
"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",
"\n",
"$$\n",
"y(t) = M sin(\\omega t + \\phi)\n",
"y(t) = M \\sin(\\omega t + \\phi)\n",
"$$\n",
"\n",
"where the magnitude $M$ and phase $\\phi$ depend on the input frequency.\n",
Expand All @@ -487,7 +488,7 @@
"response = ct.frequency_response(G[0, 0])\n",
"out = response.plot()\n",
"axs = ct.get_plot_axes(out)\n",
"axs[1][0].set_xlabel(\"Frequency [rad/ms]\");"
"axs[1, 0].set_xlabel(\"Frequency [rad/ms]\");"
]
},
{
Expand Down
31 changes: 23 additions & 8 deletions examples/cds110-L2_invpend-dynamics.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
"source": [
"<center>\n",
"<h4>CDS 110, Lecture 2</h4>\n",
"<font color=blue><h1>Nonlinear Dynamics for an Inverted Pendulum System</h1></font>\n",
"<font color=blue><h1>Nonlinear Dynamics (and Control) of an Inverted Pendulum System</h1></font>\n",
"<h3>Richard M. Murray, Winter 2024</h3>\n",
"</center>\n",
"<br>\n",
"\n",
"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"
"[Open in Google Colab](https://colab.research.google.com/drive/1rAFZyV5XrLrqTpCJ83ow7QBJBJy8yy3u)\n",
"\n",
"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"
]
},
{
Expand All @@ -26,8 +27,12 @@
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"from math import pi\n",
"\n",
"import control as ct"
"try:\n",
" import control as ct\n",
" print(\"python-control\", ct.__version__)\n",
"except ImportError:\n",
" !pip install control\n",
" import control as ct"
]
},
{
Expand All @@ -45,6 +50,10 @@
"id": "Msad1ficHjtc"
},
"source": [
"We consider an invereted pendulum, which is a simplified version of a balance system:\n",
"\n",
"<center><img src=\"https://www.cds.caltech.edu/~murray/courses/cds110/sp2024/invpend-diagram.png\" alt=\"invpend.diagram\" width=100></center>\n",
"\n",
"The dynamics for an inverted pendulum system can be written as:\n",
"\n",
"$$\n",
Expand Down Expand Up @@ -187,6 +196,8 @@
"id": "IIiSaHNuM1u_"
},
"source": [
"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",
"\n",
"We can now linearize the closed loop system at different gains and compute the eigenvalues to check for stability:"
]
},
Expand Down Expand Up @@ -249,10 +260,11 @@
"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",
"\n",
"Some things to notice in this plot:\n",
"* 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",
"* Not all of the equilibrium points are showing up (there are two unstable equilibrium points that are missing)\n",
"* There is no detail about what is happening near the origin.\n",
"\n",
"To fix these issues, we can do a couple of things:\n",
"* 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",
"* 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",
"* 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",
"\n",
"Here's some improved code:"
Expand Down Expand Up @@ -307,6 +319,8 @@
"u = -K (x - x_\\text{d}) = -k_1 (\\theta - \\theta_d) - k_2 (\\dot\\theta - \\dot\\theta_d).\n",
"$$\n",
"\n",
"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",
"\n",
"To compute the gains, we make use of the `place` command, applied to the linearized system:"
]
},
Expand Down Expand Up @@ -394,7 +408,8 @@
"Here are some things to try with the above code:\n",
"* Try changing the locations of the closed loop eigenvalues in the `place` command\n",
"* Try resetting the limits of the control action (`umax`)\n",
"* 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?"
"* 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",
"* Plot the initial condition response of the system and see how to map time traces to phase plot traces."
]
},
{
Expand Down
44 changes: 31 additions & 13 deletions examples/cds110-L3_lti-systems.ipynb

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • The link to python-control is broken at the top of the document, due to a Markdown syntax error. It is ([python-control](https://python-control.org]) but should be ([python-control](https://python-control.org))
  • "to define and input/output" -> "to define an input/output"
  • "a response object, we can be used" -> "a response object, which can be used"

Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
"source": [
"<center>\n",
"<h4>CDS 110, Lecture 3</h4>\n",
"<font color=blue><h1>Python Tools for Analyzing Linear Systems/h1></font>\n",
"<font color=blue><h1>Python Tools for Analyzing Linear Systems</h1></font>\n",
"<h3>Richard M. Murray, Winter 2024</h3>\n",
"</center>\n",
"<br>\n",
"\n",
"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"
"[Open in Google Colab](https://colab.research.google.com/drive/1ZJUAOXFR6VXQ9xfba9KT99N4FbEraQMM)\n",
"\n",
"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"
]
},
{
Expand All @@ -23,11 +24,13 @@
"outputs": [],
"source": [
"import numpy as np\n",
"%matplotlib inline\n",
"import matplotlib.pyplot as plt\n",
"\n",
"import control as ct\n",
"print(\"python-control version:\", ct.__version__)"
"try:\n",
" import control as ct\n",
" print(\"python-control\", ct.__version__)\n",
"except ImportError:\n",
" !pip install control\n",
" import control as ct"
]
},
{
Expand All @@ -41,7 +44,7 @@
"\n",
"Consider the spring mass system below:\n",
"\n",
"<img src=\"springmass-coupled.png\" width=640>\n",
"<center><img src=\"https://www.cds.caltech.edu/~murray/courses/cds110/sp2024/springmass-coupled.png\" width=640></center>\n",
"\n",
"We wish to analyze the time and frequency response of this system using a variety of python-control functions for linear systems analysis.\n",
"\n",
Expand Down Expand Up @@ -199,7 +202,12 @@
"\n",
"for X0 in [[1, 0, 0, 0], [0, 2, 0, 0], [1, 2, 0, 0], [0, 0, 1, 0], [0, 0, 2, 0]]:\n",
" response = ct.initial_response(sys, T=20, X0=X0)\n",
" response.plot(color=next(colors), label=f\"{X0=}\")"
" try:\n",
" response.plot(color=next(colors), label=f\"{X0=}\")\n",
" except TypeError:\n",
" # Earlier versions of python-control don't allow relabeling\n",
" response.sysname = f\"{X0=}\"\n",
" response.plot(color=next(colors))"
]
},
{
Expand Down Expand Up @@ -268,6 +276,7 @@
"metadata": {},
"outputs": [],
"source": [
"# Plot the inputs on top of the outputs\n",
"out = stepresp.plot(plot_inputs='overlay')"
]
},
Expand Down Expand Up @@ -366,10 +375,16 @@
"# Manual computation of the frequency response\n",
"resp = ct.input_output_response(sys, T, np.sin(1.35 * T))\n",
"\n",
"out = resp.plot(\n",
"try:\n",
" out = resp.plot(\n",
" plot_inputs='overlay', \n",
" legend_map=np.array([['lower left'], ['lower left']]),\n",
" label=[['q1', 'u[0]'], ['q2', None]])"
" label=[['q1', 'u[0]'], ['q2', None]])\n",
"except TypeError:\n",
" # Earlier versions of python-control don't allow relabeling\n",
" out = resp.plot(\n",
" plot_inputs='overlay',\n",
" legend_map=np.array([['lower left'], ['lower left']]))"
]
},
{
Expand All @@ -393,8 +408,11 @@
"metadata": {},
"outputs": [],
"source": [
"# Create SISO transfer functions, in case we don't have slycot\n",
"G = ct.ss2tf(sys, name='u to q1')\n",
"try:\n",
" G = ct.ss2tf(sys, name='u to q1, q2')\n",
"except ct.ControlMIMONotImplemented:\n",
" # Create SISO transfer functions, in case we don't have slycot\n",
" G = ct.ss2tf(sys[0, 0], name='u to q1')\n",
"print(G)"
]
},
Expand Down
Loading