Whether you are baking a cake or trying to get a rocket in to space, the application of control theory is being used. Most people are unaware of its presence, but to take the baking example: how does your oven know what temperature it is currently at? How does it know when an appropriate temperature has been reached? What is the correct thing to do when that temperature has been reached?
Even if this is the first time you are hearing about the concept of control theory, you can probably answer at least a few of the above questions: your oven has a thermometer, and it turns itself off when the desired temperature is reached. But this is indeed the foundation of what is ultimately called feedback controls:
- An input in to a system is given. Often a voltage, but in the case of the oven it is an ignited gas.
- A goal is set. A desired voltage, a certain elevation, some maximum speed, or a temperature is to be achieved.
- Some output is given. The oven outputs heat, the rocket shoots out propellant, the applied voltage causes the motor to spin.
- A sensor observes the output. What is the resultant temperature?
- A controller modifies the input based on how much error there exists between the sensor readings and the target.
To expand on part 5: an oven reading 75 °F (room temperature) when the target is 450 °F means there is a large error, so on the next “cycle”, the input will be modified to spit out as much gas as possible to get the heat flowing. Some time later, the thermometer now reads 465 °F. Not only has the desired target been hit, it has been surpassed. The next thing to do is to shut off the gas completely (in a “dumb” system that is your oven) until heat is bled off such that the target temperature is met.
For an oven, this is all the control you need. If the system goes above the desired temperature, no big deal; your dessert will not be harmed if the gas is constantly being turned on and off, oscillating between 435 °F and 465 °F. It is slow, has a relatively high inaccuracy, and is not smart, but it is extremely cheap. When speed, precision, and accuracy are required, control gets much more complicated (and also more expensive). The next evolution is what is referred to as Proportional, Integral, and Derivative Control, or PID Control for short. Each component still does the same thing: reads the error between the current sensor(s) reading of the output, and the desired output. How this error is interpreted is where they differ:
- Proportional control by itself is the simplest control, and the oven is an example of this kind of control. It basically asks: what is the difference between 450 °F (target) and 75 °F? If there is error, the input turns on. If there is no error (target and current are equal), proportional control does nothing. As a result, there is always going to be some error between the desired output and the real output. For this reason, the other two types of control are used in conjunction.
- Integral control takes a history of past performance. Integral is where calculus comes in to play, and if you understand integration, then this control is observing the area under the curve that represents the error. For those that have not learned calculus, it asks the question: what has happened to the error as timed passed? Has it gotten smaller or larger, and if so, by how much? Using a combination of proportional and integral control, error can be eliminated almost entirely.
- Derivative control looks to future performance. Again with calculus: when taking the derivative of a function, you are finding the slope of the curve at some instance in time. Derivative control asks: where is the error going? In the next iteration, is it going to be higher or lower than the current iteration?
The combination of these three types of control yields a system that achieves a desired output quickly, accurately, and with high precision. Derivative control has the added bonus of providing system stability. An issue with controlled systems is that, if proper precautions are not taken, the system will spiral wildly out of control, and lead to disastrous and even lethal results (ie, if your oven is unstable, it would not stop at 450 °F, but will keep heating up until it catches fire).
With all this in mind about control theory, the missing piece of the puzzle is the project. The goal of the Chicago Python Organization Mentorship Program is to learn how to use Python better than we knew going in. There are several high-level goals to be accomplished with this project:
- Become more fluent in Python
- Put my skills with Python, high-level math, and problem-solving skills on display
- Present my project in a manner that could be easily understood by people with a non-technical background
Point 1 will be accomplished as I do points 2 and 3. The reason for this is several necessary tools (mostly Python libraries) that I have never used before will need to be learned and accessed in a meaningful way. I will be using the following:
- Python (of course)
- SciPy – An open source library for scientific manipulation and analysis. I have tried to keep this blog post as non-technical as possible, but to properly implement controls requires an understanding of linear algebra and differential equations.
- NumPy – Sister to SciPy, used for arrays and matrices, which are essential to some mathematical operations. Arrays and matrices in the Numpy library have more functionality than Python’s list object.
- matplotlib – For quickly plotting graphs. Can be used to quickly determine aspects of a system and its controller.
- Pygame – A way to graphically display the behavior of the system and how it behaves with a controller.
- Git/Github – For quickly and efficiently handling version control, while also making it available to my mentor (and the public) for feedback.
This list may not be final.
The project is: Using the concepts of control theory and the aforementioned tools, I will build a simulation where a car follows a road. The car will have sensors, and it will read information to provide feedback to the controller, telling the car if it is in an optimal location or not as it travels along this road. There will (hopefully) be hazards such as pedestrians and potholes for the car to avoid.
First Week Progress
This first blog comes 10 days after the ChiPy Mentorship welcome dinner (Monday, February 5th). My first meeting with my mentor, Brian Krasnopolsky, happened the following Wednesday. This first meeting was primarily brainstorming and getting each other on the same page. Brian got me on to the idea of using Pygame for a graphical way of showing off my project (my original intent was simply to use matplotlib).
Modeling System Behavior
Some of the work done in the first couple of days was simply review. The class that I took that got me interested in the field of controls finished over a year ago, and I needed some time to get myself refreshed on the material.
Figure 1: Step response of a proportional controller
Scipy in combination of matplotlib has the ability to quickly model the behavior of a controlled system, as seen above in figure 1. This is more abstract than a kitchen oven, but is one of the ways I am familiar representing a control system. In short: the horizontal axis represents time, and the vertical line represents the system response. Figure 1 is an example of proportional control, and as I stated earlier: there is always error between the output of the system and the target. The target for the system in figure 1 is set to 1, and even if extended the graph to an hour (instead of .35 seconds), the output would never pass .5.
Figure 2: Adding integral control (left) and full PID control (right)
For integral and derivative control, they can be added or excluded (although proportional-derivative control is relatively uncommon, so it is excluded here). On the left you can see that the system has some initial instability, oscillating above and below the target until achieving equilibrium about 30 seconds in. As mentioned, derivative control increases the stability of the system, so on the right you can see that it reaches the target relatively quickly (about 3-4 seconds), and barely oscillates. This is the ideal situation.
These functions are very quick and easy to get going in Python:
import scipy.signal as tf
import matplotlib as plt
nums = 
dens = [1, 2.3, 2]
sys = tf.TransferFunction(nums, dens)
t, y = tf.step(sys)
This code turns two arrays which represent numerators and denominators of the transfer function, which is defined as the output of the system divided by the input. This is the equation that describes the behavior of the system. It is then put in graph form using matplotlib.
Getting used to a graphical interface was difficult. There was the initial shock of how to think about games (which I play a lot): the screen is rewritten from top to bottom as you move in the game world. This happens so quickly (your frames per second hopefully being above 30) that the appearance of motion is achieved. The first thing I set out to do was to generate a road on the fly. A hard-coded seed for what was and was not road was made, and then an implementation of the numpy randint function was used to make the road move up or down as you “traveled” to the right. There were some issues:
Figure 3: My first ‘roadblock’
Road generation started out fine, but as soon as it reached the bottom of the screen, part of the road got stuck there, and the rest branched off. When the branch reached the top, that also got stuck. The solution: I was originally using array.insert(value, location = -1), which was changing the value of the last element. I switched to array.append(value) and a new element location was added, and the value placed in this new, empty element.
Figure 4: Working random road generation
As far as fixes go, this one was relatively simple. But actually finding the root of the problem felt like hunting for that missing semicolon in other languages.
The following blog is due a month from now. Brainstorming is all but finished; Brian and I have both tempered ourselves from the desire to add more and more features.
The car driving is going to be the main goal of this month. That white square in figures 3 and 4 is what represents the car in this graphical interface. The road generation is random and happens without input from the car. But a car drives, so its motion should determine when new road should appear on the edge of the screen (but not where, otherwise the point of this project is lost). Furthermore: I want the car to drive in a total of 8 directions (cardinal and ordinal).
You can find all the files related to this project on GitHub