{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "*This notebook contains material from [cbe30338-2021](https://jckantor.github.io/cbe30338-2021);\n", "content is available [on Github](https://github.com/jckantor/cbe30338-2021.git).*\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "< [3.3 Relay Control](https://jckantor.github.io/cbe30338-2021/03.03-Relay-Control.html) | [Contents](toc.html) | [Tag Index](tag_index.html) | [3.5 Practical Proportional (P) and Proportional-Integral (PI) Control](https://jckantor.github.io/cbe30338-2021/03.05-Proportional-Integral-Control.html) >

\"Open

\"Download\"" ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 1, "link": "[3.4 Implementing Controllers in Python](https://jckantor.github.io/cbe30338-2021/03.04-Implementing-Controllers.html#3.4-Implementing-Controllers-in-Python)", "section": "3.4 Implementing Controllers in Python" } }, "source": [ "# 3.4 Implementing Controllers in Python\n", "\n", "One of the challenges we will face is implementing complex control algorithms within the real-time event loop. Up to this point we have been doing the control calculations directly inside the event loop. This can work well for simple applications, like relay control for a single control variable. But for more complex algorithms and applications, this becomes completely unworkable. Placing the code for complex control algorithms directly in the event loop makes puts an undue burden on the coder to maintain all details of controller and system operation.\n", "\n", "What we seek are coding techniques that can encapusulate control algorithms into separate, self-contained blocks of code where they can be developed, tested, and deployed. This notebook introduces three techniques for implementing controllers in testable, modular fashion:\n", "\n", "* Nested Python functions\n", "* Python generators (co-routines)\n", "* Python classes\n", "\n", "What these techniques have in common is creating blocks of code that maintain their own state, a requirement for useful control algorthms. \n", "\n", "The first of these, nested Python functions, exploits the ability of Python to return a function from another function. The 'outer' function accepts parameters specifying a desired controller and returns an 'inner' function that implements the controller. By using the Python `nonlocal`, the inner function can modify variables introduced in the outer function, and thereby accumulate and retain information. The use of nested functions builds on what most coders already know about Python.\n", "\n", "Python generators provide a more flexible framework for implementing Python co-routines. Generators use the Python `yield` statement to communication data to and from and co-routine. Generators may be a less familiar for many Python coders, but with just a few new keywords and concepts, generators provide a framework with wide range of coding applications. Python generators are a clear and concise way of implementing control algorithms.\n", "\n", "Finally, Python classes provide even more flexibility by allowing the use of object oriented programming. This is more overhead than is required from simple control algorithms, but are key to the robust implementation of more complex systems.\n", "\n", "This notebook demonstrates these three different approaches to controller implementation. A reader new to Python coding may wish to limit their attention to the first two, nested functions and generators, and leave the more involved aspects of object-oriented coding the Python classes for a later time.\n" ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 2, "link": "[3.4.1 The problem with coding controllers as simple functions](https://jckantor.github.io/cbe30338-2021/03.04-Implementing-Controllers.html#3.4.1-The-problem-with-coding-controllers-as-simple-functions)", "section": "3.4.1 The problem with coding controllers as simple functions" } }, "source": [ "## 3.4.1 The problem with coding controllers as simple functions" ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 2, "link": "[3.4.1 The problem with coding controllers as simple functions](https://jckantor.github.io/cbe30338-2021/03.04-Implementing-Controllers.html#3.4.1-The-problem-with-coding-controllers-as-simple-functions)", "section": "3.4.1 The problem with coding controllers as simple functions" } }, "source": [ "A function performing the duty of the controller does the calculations necessary to compute a new value of the manipulated variable, $MV_k$. For relay control with a deadzone, for example, the value of $MV_k$ is given \n", "\\begin{align}\n", "MV_{k} & = \\begin{cases} \n", " MV^{max} &\\text{if $PV_k \\leq SP_k$} - d\\\\\n", " MV^{min} & \\text{if $PV_k \\geq SP_k$} + d\\\\\n", " MV_{k-1} & \\text{ otherwise}\n", " \\end{cases}\n", "\\end{align}\n", "\n", "where parameters $MV^{min}$, $MV^{max}$, and $d$ define a particular instance of the control algorithm. With known values for those parameters, the current value of $MV_k$ is determined from process variable $PV_k$, setpoint $SP_k$, and the prior value $MV_{k-1}$.\n", "\n", "Here is a simple Python function implementing relay control with deadzone." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "nbpages": { "level": 2, "link": "[3.4.1 The problem with coding controllers as simple functions](https://jckantor.github.io/cbe30338-2021/03.04-Implementing-Controllers.html#3.4.1-The-problem-with-coding-controllers-as-simple-functions)", "section": "3.4.1 The problem with coding controllers as simple functions" } }, "outputs": [], "source": [ "def relay_with_deadzone(PV, SP, MV_prev, MV_min, MV_max, d):\n", " if PV <= SP - d:\n", " MV = MV_max\n", " elif PV >= SP + d:\n", " MV = MV_min\n", " else:\n", " MV = MV_prev\n", " return MV" ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 2, "link": "[3.4.1 The problem with coding controllers as simple functions](https://jckantor.github.io/cbe30338-2021/03.04-Implementing-Controllers.html#3.4.1-The-problem-with-coding-controllers-as-simple-functions)", "section": "3.4.1 The problem with coding controllers as simple functions" } }, "source": [ "Now let's put it to work." ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "nbpages": { "level": 2, "link": "[3.4.1 The problem with coding controllers as simple functions](https://jckantor.github.io/cbe30338-2021/03.04-Implementing-Controllers.html#3.4.1-The-problem-with-coding-controllers-as-simple-functions)", "section": "3.4.1 The problem with coding controllers as simple functions" } }, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "

" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "TCLab Model disconnected successfully.\n" ] }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "%matplotlib inline\n", "from tclab import TCLab, clock, Historian, Plotter, setup\n", "\n", "TCLab = setup(connected=False, speedup=20)\n", "\n", "# control parameters\n", "U_min = 0\n", "U_max = 100\n", "T_SP = 40\n", "d = 0.5\n", "\n", "# time horizon and time step\n", "t_final = 250\n", "\n", "# perform experiment\n", "with TCLab() as lab:\n", " lab.P1 = 200\n", " h = Historian(lab.sources)\n", " p = Plotter(h, t_final)\n", " U1 = U_min\n", " for t in clock(t_final):\n", " T1 = lab.T1\n", " U1 = relay_with_deadzone(T1, T_SP, U1, U_min, U_max, d)\n", " lab.Q1(U1)\n", " p.update(t) " ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 2, "link": "[3.4.1 The problem with coding controllers as simple functions](https://jckantor.github.io/cbe30338-2021/03.04-Implementing-Controllers.html#3.4.1-The-problem-with-coding-controllers-as-simple-functions)", "section": "3.4.1 The problem with coding controllers as simple functions" } }, "source": [ "Let's consider how this implementation would be extended to complex process applications with more controllers.\n", "\n", "* If multiple controllers are required, the parameter values for every controller must be available in the event loop.\n", "* Because the state of Python functions are lost after a `return` statement, any information required by a controller for the next update must be managed in the event loop. The information that needs to be retained depends on the details of the selected controllers.\n", "* Any change to a controller implementation requires revision of the event loop, including possible changes to the information retained between updates.\n", "\n", "This tight couplineg of the controller implementation, information management, and process operation would present very difficult challenges for the design and maintenance of control systems." ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 2, "link": "[3.4.2 What we seek in a controller implementation](https://jckantor.github.io/cbe30338-2021/03.04-Implementing-Controllers.html#3.4.2-What-we-seek-in-a-controller-implementation)", "section": "3.4.2 What we seek in a controller implementation" } }, "source": [ "## 3.4.2 What we seek in a controller implementation\n", "\n", "What we seek in controller implementations are features facilitating the coding and maintenance of complex control systems and algorithms. Ideally, the code would \"encapulate\" the controllers such that:\n", "\n", "* Multiple instances of generic control algorithms can be created, each with unique parameter values.\n", "* Controller instances maintain the required process information. \n", "* Controllers algorithms can be changed without required modification of the event loop.\n", "\n", "By separating controller implementations from the event loop, we create a more flexible, maintainable, and testable framework for process control." ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 2, "link": "[3.4.3 Implementating relay control as nested function](https://jckantor.github.io/cbe30338-2021/03.04-Implementing-Controllers.html#3.4.3-Implementating-relay-control-as-nested-function)", "section": "3.4.3 Implementating relay control as nested function" } }, "source": [ "## 3.4.3 Implementating relay control as nested function" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "nbpages": { "level": 2, "link": "[3.4.3 Implementating relay control as nested function](https://jckantor.github.io/cbe30338-2021/03.04-Implementing-Controllers.html#3.4.3-Implementating-relay-control-as-nested-function)", "section": "3.4.3 Implementating relay control as nested function" } }, "outputs": [], "source": [ "def relay_with_deadzone(MV_min, MV_max, d):\n", " MV = MV_min\n", " def update(SP, PV):\n", " nonlocal MV\n", " if PV <= SP - d:\n", " MV = MV_max\n", " elif PV >= SP + d:\n", " MV = MV_min\n", " return MV\n", " return update" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "nbpages": { "level": 2, "link": "[3.4.3 Implementating relay control as nested function](https://jckantor.github.io/cbe30338-2021/03.04-Implementing-Controllers.html#3.4.3-Implementating-relay-control-as-nested-function)", "section": "3.4.3 Implementating relay control as nested function" } }, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "TCLab Model disconnected successfully.\n" ] }, { "ename": "KeyboardInterrupt", "evalue": "", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 16\u001b[0m \u001b[0mh\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mHistorian\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mlab\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msources\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 17\u001b[0m \u001b[0mp\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mPlotter\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mh\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mt_final\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 18\u001b[0;31m \u001b[0;32mfor\u001b[0m \u001b[0mt\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mclock\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mt_final\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 19\u001b[0m \u001b[0mT1\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mlab\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mT1\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 20\u001b[0m \u001b[0mU1\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mcontroller\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mT_SP\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mT1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m~/opt/anaconda3/lib/python3.8/site-packages/tclab/labtime.py\u001b[0m in \u001b[0;36mclock\u001b[0;34m(period, step, tol, adaptive)\u001b[0m\n\u001b[1;32m 107\u001b[0m '({:.2f} too long). Consider increasing step.')\n\u001b[1;32m 108\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mRuntimeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmessage\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mformat\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstep\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0melapsed\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0melapsed\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0mstep\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 109\u001b[0;31m \u001b[0mlabtime\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msleep\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstep\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mlabtime\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtime\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0mstart\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0mstep\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 110\u001b[0m \u001b[0mnow\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mlabtime\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtime\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0mstart\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m~/opt/anaconda3/lib/python3.8/site-packages/tclab/labtime.py\u001b[0m in \u001b[0;36msleep\u001b[0;34m(self, delay)\u001b[0m\n\u001b[1;32m 39\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlastsleep\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mdelay\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 40\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_running\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 41\u001b[0;31m \u001b[0mtime\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msleep\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdelay\u001b[0m \u001b[0;34m/\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_rate\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 42\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 43\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mRuntimeWarning\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"sleep is not valid when labtime is stopped.\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mKeyboardInterrupt\u001b[0m: " ] }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "%matplotlib inline\n", "from tclab import TCLab, clock, Historian, Plotter, setup\n", "\n", "TCLab = setup(connected=False, speedup=20)\n", "\n", "# control parameters\n", "controller = relay_with_deadzone(MV_min=0, MV_max=100, d=1)\n", "T_SP = 40\n", "\n", "# time horizon and time step\n", "t_final = 250\n", "\n", "# perform experiment\n", "with TCLab() as lab:\n", " lab.P1 = 200\n", " h = Historian(lab.sources)\n", " p = Plotter(h, t_final)\n", " for t in clock(t_final):\n", " T1 = lab.T1\n", " U1 = controller(T_SP, T1)\n", " lab.Q1(U1)\n", " p.update(t) " ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 2, "link": "[3.4.4 Implementing relay control as a Python generator](https://jckantor.github.io/cbe30338-2021/03.04-Implementing-Controllers.html#3.4.4-Implementing-relay-control-as-a-Python-generator)", "section": "3.4.4 Implementing relay control as a Python generator" } }, "source": [ "## 3.4.4 Implementing relay control as a Python generator" ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 3, "link": "[3.4.4.1 What is a Python generator?](https://jckantor.github.io/cbe30338-2021/03.04-Implementing-Controllers.html#3.4.4.1-What-is-a-Python-generator?)", "section": "3.4.4.1 What is a Python generator?" } }, "source": [ "### 3.4.4.1 What is a Python generator?\n", "\n", "Values are returned from Python functions using the `return` statement. Once the return statement is encountered, the function is over and all local information is lost." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "nbpages": { "level": 3, "link": "[3.4.4.1 What is a Python generator?](https://jckantor.github.io/cbe30338-2021/03.04-Implementing-Controllers.html#3.4.4.1-What-is-a-Python-generator?)", "section": "3.4.4.1 What is a Python generator?" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello, World\n" ] } ], "source": [ "def my_function():\n", " return \"Hello, World\"\n", "\n", "print(my_function())" ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 3, "link": "[3.4.4.1 What is a Python generator?](https://jckantor.github.io/cbe30338-2021/03.04-Implementing-Controllers.html#3.4.4.1-What-is-a-Python-generator?)", "section": "3.4.4.1 What is a Python generator?" } }, "source": [ "Values are returned from generators with a `yield` statement. But what makes a generator different is that operation can be restarted to produce values from more `yield` statements. (Functions are 'one and done', generators come back for another season.) \n", "\n", "Because generators can be called multiple times, there are some extra details involved with their use:\n", "\n", "* An 'instance' of the generator must be created before it can be used,\n", "* The Python function `next()` gets the value returned by the next yeild statement." ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "nbpages": { "level": 3, "link": "[3.4.4.1 What is a Python generator?](https://jckantor.github.io/cbe30338-2021/03.04-Implementing-Controllers.html#3.4.4.1-What-is-a-Python-generator?)", "section": "3.4.4.1 What is a Python generator?" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello, World\n", "Hello, again\n", "And one last thing ...\n" ] } ], "source": [ "def my_generator():\n", " yield \"Hello, World\"\n", " yield \"Hello, again\"\n", " yield \"And one last thing ...\"\n", "\n", "# create an instance of the generator\n", "f = my_generator()\n", "\n", "# get values from the generator\n", "print(next(f))\n", "print(next(f))\n", "print(next(f))" ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 3, "link": "[3.4.4.1 What is a Python generator?](https://jckantor.github.io/cbe30338-2021/03.04-Implementing-Controllers.html#3.4.4.1-What-is-a-Python-generator?)", "section": "3.4.4.1 What is a Python generator?" } }, "source": [ "You can create multiple instances of generators, each maintaining its own state. " ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "nbpages": { "level": 3, "link": "[3.4.4.1 What is a Python generator?](https://jckantor.github.io/cbe30338-2021/03.04-Implementing-Controllers.html#3.4.4.1-What-is-a-Python-generator?)", "section": "3.4.4.1 What is a Python generator?" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello Spot\n", "Hello Rover\n", "Go fetch Rover\n", "Go fetch Spot\n" ] } ], "source": [ "def my_dog(dog):\n", " yield f\"Hello {dog}\"\n", " yield f\"Go fetch {dog}\"\n", " yield f\"Come back {dog}\"\n", "\n", "f = my_dog(\"Spot\")\n", "g = my_dog(\"Rover\")\n", "\n", "print(next(f))\n", "print(next(g))\n", "print(next(g))\n", "print(next(f))" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "nbpages": { "level": 3, "link": "[3.4.4.1 What is a Python generator?](https://jckantor.github.io/cbe30338-2021/03.04-Implementing-Controllers.html#3.4.4.1-What-is-a-Python-generator?)", "section": "3.4.4.1 What is a Python generator?" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello ['Rover']\n", "Hello ['Rover', 'Spot']\n" ] } ], "source": [ "def my_gen():\n", " msgs = []\n", " while True:\n", " msg = yield f\"Hello {msgs}\"\n", " msgs.append(msg)\n", "\n", "f = my_gen()\n", "next(f)\n", "\n", "msg = f.send(\"Rover\")\n", "print(msg)\n", "\n", "msg = f.send(\"Spot\")\n", "print(msg)" ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 3, "link": "[3.4.4.1 What is a Python generator?](https://jckantor.github.io/cbe30338-2021/03.04-Implementing-Controllers.html#3.4.4.1-What-is-a-Python-generator?)", "section": "3.4.4.1 What is a Python generator?" } }, "source": [ "This behavior has a number very useful implications. The first thing is that we can create multiple instances of a generator, each with it's own parameters." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "nbpages": { "level": 3, "link": "[3.4.4.1 What is a Python generator?](https://jckantor.github.io/cbe30338-2021/03.04-Implementing-Controllers.html#3.4.4.1-What-is-a-Python-generator?)", "section": "3.4.4.1 What is a Python generator?" } }, "outputs": [ { "ename": "TypeError", "evalue": "my_generator() takes 0 positional arguments but 1 was given", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mg10\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmy_generator\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m10\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0mg20\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmy_generator\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m20\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnext\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mg10\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnext\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mg10\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mTypeError\u001b[0m: my_generator() takes 0 positional arguments but 1 was given" ] } ], "source": [ "g10 = my_generator(10)\n", "g20 = my_generator(20)\n", "\n", "print(next(g10))\n", "print(next(g10))\n", "print(next(g20))\n", "print(next(g10))\n", "print(next(g20))\n", "print(next(g20))" ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 3, "link": "[3.4.4.1 What is a Python generator?](https://jckantor.github.io/cbe30338-2021/03.04-Implementing-Controllers.html#3.4.4.1-What-is-a-Python-generator?)", "section": "3.4.4.1 What is a Python generator?" } }, "source": [ "The second important implication is that each instance of a generator can maintain its own state in the form of local variables." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "nbpages": { "level": 3, "link": "[3.4.4.1 What is a Python generator?](https://jckantor.github.io/cbe30338-2021/03.04-Implementing-Controllers.html#3.4.4.1-What-is-a-Python-generator?)", "section": "3.4.4.1 What is a Python generator?" } }, "outputs": [], "source": [ "def infinite_counter():\n", " i = 0\n", " while True:\n", " yield i\n", " i = i + 1\n", " \n", "f = infinite_counter()\n", "print(next(f))\n", "print(next(f))\n", "print(next(f))\n", "print(next(f))" ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 3, "link": "[3.4.4.1 What is a Python generator?](https://jckantor.github.io/cbe30338-2021/03.04-Implementing-Controllers.html#3.4.4.1-What-is-a-Python-generator?)", "section": "3.4.4.1 What is a Python generator?" } }, "source": [ "We can also send information to a generator. " ] }, { "cell_type": "code", "execution_count": null, "metadata": { "nbpages": { "level": 3, "link": "[3.4.4.1 What is a Python generator?](https://jckantor.github.io/cbe30338-2021/03.04-Implementing-Controllers.html#3.4.4.1-What-is-a-Python-generator?)", "section": "3.4.4.1 What is a Python generator?" } }, "outputs": [], "source": [ "def make_power(n):\n", " k = 0\n", " while True:\n", " val = yield k**n\n", " k = val\n", " \n", "cube = make_power(3)\n", "print(cube.send(None))\n", "print(cube.send(2))\n", "print(cube.send(12))" ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 3, "link": "[3.4.4.2 Coding a relay controller as a Python generator](https://jckantor.github.io/cbe30338-2021/03.04-Implementing-Controllers.html#3.4.4.2-Coding-a-relay-controller-as-a-Python-generator)", "section": "3.4.4.2 Coding a relay controller as a Python generator" } }, "source": [ "### 3.4.4.2 Coding a relay controller as a Python generator" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "nbpages": { "level": 3, "link": "[3.4.4.2 Coding a relay controller as a Python generator](https://jckantor.github.io/cbe30338-2021/03.04-Implementing-Controllers.html#3.4.4.2-Coding-a-relay-controller-as-a-Python-generator)", "section": "3.4.4.2 Coding a relay controller as a Python generator" } }, "outputs": [], "source": [ "def Relay(MV_min=0, MV_max=100, d=0):\n", " MV = MV_min\n", " while True:\n", " SP, PV = yield MV\n", " if PV <= SP - d:\n", " MV = MV_max\n", " if PV >= SP + d:\n", " MV = MV_min" ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 3, "link": "[3.4.4.2 Coding a relay controller as a Python generator](https://jckantor.github.io/cbe30338-2021/03.04-Implementing-Controllers.html#3.4.4.2-Coding-a-relay-controller-as-a-Python-generator)", "section": "3.4.4.2 Coding a relay controller as a Python generator" } }, "source": [ "By coding the relay controller as a Python generator, we've eliminated the need to store the controller's parameters as global variable for our main program. This reduces the complexity of the code, and makes it possible. We have decoupled details of the control algorithm from the event loop. This is a big step forward to writing more modular, testable, and ultimately more reliable codes." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "nbpages": { "level": 3, "link": "[3.4.4.2 Coding a relay controller as a Python generator](https://jckantor.github.io/cbe30338-2021/03.04-Implementing-Controllers.html#3.4.4.2-Coding-a-relay-controller-as-a-Python-generator)", "section": "3.4.4.2 Coding a relay controller as a Python generator" } }, "outputs": [], "source": [ "%matplotlib inline\n", "from tclab import TCLab, clock, Historian, Plotter, setup\n", "\n", "TCLab = setup(connected=False, speedup=20)\n", "\n", "# control parameters\n", "controller = Relay(MV_min=0, MV_max=100, d=0.5)\n", "next(controller)\n", "T_SP = 40\n", "\n", "# time horizon and time step\n", "t_final = 250\n", "\n", "# perform experiment\n", "with TCLab() as lab:\n", " lab.P1 = 200\n", " h = Historian(lab.sources)\n", " p = Plotter(h, t_final)\n", " for t in clock(t_final):\n", " T1 = lab.T1\n", " U1 = controller.send((T_SP, T1))\n", " lab.Q1(U1)\n", " p.update(t) " ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 2, "link": "[3.4.5 Implementing relay control as a Python class](https://jckantor.github.io/cbe30338-2021/03.04-Implementing-Controllers.html#3.4.5-Implementing-relay-control-as-a-Python-class)", "section": "3.4.5 Implementing relay control as a Python class" } }, "source": [ "## 3.4.5 Implementing relay control as a Python class" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "nbpages": { "level": 2, "link": "[3.4.5 Implementing relay control as a Python class](https://jckantor.github.io/cbe30338-2021/03.04-Implementing-Controllers.html#3.4.5-Implementing-relay-control-as-a-Python-class)", "section": "3.4.5 Implementing relay control as a Python class" } }, "outputs": [], "source": [ "class Relay():\n", " def __init__(self, MV_min=0, MV_max=100, d=0):\n", " self.d = d\n", " self.MV_min = MV_min\n", " self.MV_max = MV_max\n", " self.d = d\n", " self.MV = self.MV_min\n", " \n", " def update(self, SP, PV):\n", " if PV <= SP - self.d:\n", " self.MV = self.MV_max\n", " if PV >= SP + self.d:\n", " self.MV = self.MV_min\n", " return self.MV" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "nbpages": { "level": 2, "link": "[3.4.5 Implementing relay control as a Python class](https://jckantor.github.io/cbe30338-2021/03.04-Implementing-Controllers.html#3.4.5-Implementing-relay-control-as-a-Python-class)", "section": "3.4.5 Implementing relay control as a Python class" } }, "outputs": [], "source": [ "%matplotlib inline\n", "from tclab import TCLab, clock, Historian, Plotter, setup\n", "\n", "TCLab = setup(connected=False, speedup=20)\n", "\n", "# control parameters\n", "controller = Relay(MV_min=0, MV_max=100, d=0.5)\n", "T_SP = 40\n", "\n", "# time horizon and time step\n", "t_final = 250\n", "\n", "# perform experiment\n", "with TCLab() as lab:\n", " lab.P1 = 200\n", " h = Historian(lab.sources)\n", " p = Plotter(h, t_final)\n", " for t in clock(t_final):\n", " T1 = lab.T1\n", " U1 = controller.update(T_SP, T1)\n", " lab.Q1(U1)\n", " p.update(t) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "< [3.3 Relay Control](https://jckantor.github.io/cbe30338-2021/03.03-Relay-Control.html) | [Contents](toc.html) | [Tag Index](tag_index.html) | [3.5 Practical Proportional (P) and Proportional-Integral (PI) Control](https://jckantor.github.io/cbe30338-2021/03.05-Proportional-Integral-Control.html) >

\"Open

\"Download\"" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.5" } }, "nbformat": 4, "nbformat_minor": 4 }