{ "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) >
"
]
},
{
"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": {
"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
}