None Notebook

This notebook contains material from cbe30338-2021; content is available on Github.

< 3.3 Relay Control | Contents | Tag Index | 3.5 Practical Proportional (P) and Proportional-Integral (PI) Control >

Open in Colab

Download

3.4 Implementing Controllers in Python

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.

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:

What these techniques have in common is creating blocks of code that maintain their own state, a requirement for useful control algorthms.

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.

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.

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.

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.

3.4.1 The problem with coding controllers as simple functions

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 \begin{align} MV_{k} & = \begin{cases} MV^{max} &\text{if $PV_k \leq SP_k$} - d\\ MV^{min} & \text{if $PV_k \geq SP_k$} + d\\ MV_{k-1} & \text{ otherwise} \end{cases} \end{align}

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}$.

Here is a simple Python function implementing relay control with deadzone.

Now let's put it to work.

Let's consider how this implementation would be extended to complex process applications with more controllers.

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.

3.4.2 What we seek in a controller implementation

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:

By separating controller implementations from the event loop, we create a more flexible, maintainable, and testable framework for process control.

3.4.3 Implementating relay control as nested function

3.4.4 Implementing relay control as a Python generator

3.4.4.1 What is a Python generator?

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.

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.)

Because generators can be called multiple times, there are some extra details involved with their use:

You can create multiple instances of generators, each maintaining its own state.

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.

The second important implication is that each instance of a generator can maintain its own state in the form of local variables.

We can also send information to a generator.

3.4.4.2 Coding a relay controller as a Python generator

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.

3.4.5 Implementing relay control as a Python class

< 3.3 Relay Control | Contents | Tag Index | 3.5 Practical Proportional (P) and Proportional-Integral (PI) Control >

Open in Colab

Download