[HomePage] [TitleIndex] [WordIndex

DynSim Tutorial

DynSim2 is essentially a simple set of classes which allow quick simulation of analog control schemes and the process they are applied to. The framework includes a library of "block" or component classes from which we can build the simulation, but allows the user to pull it all together with his own python scripting to do all the housekeeping and analysis or to interface with other data or input/output systems.

Installing

Prerequisites

dynsim

You can find the latest version of DynSim in the Weblog at the bottom of the main DynSim page. Look for the latest (top-most) entry with the title "Uploaded new version ...". The zip file will contain all of files you need. Find your installed version of Python and navigate to "...\python\Lib\site-packages". Unzip the dynsim directory there. That's it! You should now be able to start up Python and from dynsim import control etc.

Using Pydee

Note: Pydee has been replaced with with a new version called Spyder

Pydee is a new GUI for interactive work using Python (screenshot shown below). It is fashioned along the lines of the Matlab GUI, and provides all the features that are needed for interactive work and simple script editing. It is the recommended environment to run simulations. However, it is still poorly documented, so a few pointers on its use are given here to help.

The GUI (shown below) shows:

All the tabs/windows on the left side can be rearranged to suit the user's preferences.

Tips on using Pydee:

pydee.jpg

Python Language

Look up your documentation on Python and its associated packages using the Python(x,y) helper program. It is convenient to keep it in your system tray by pressing the panel button which points to the applet area of your taskbar (right side). Python is one of the easiest languages available today. It is both an interactive scripting language as well as a serious application programming language. For example, a lot of the code running Google is Python.

See the Python page in this Wiki for more details on Python, and see the DynSim main page on the philosophy of using Python as a simulation language instead of a graphical configuration "language".

Packages

Packages contain the useful collections of Python functionality. Python has a hierarchy of container which contain library-like contents. At the highest level we have a "package". dynsim is a package. numpy and scipy are also packages. They each can contain several "modules" which are a single Python source file. A module can contain a whole library of classes and functions (very different to Matlab where each function must be in its own file!).

Specifically for DynSim we have a package called dynsim which contains four modules (control, process, mechanics, finance). In each of these modules there are many classes and functions which are available to the simulation user.

Python Classes

Objects are different to classes. The classes represent a kind of template from which you can create many instances of the class called objects. Your simulation will use a set of objects which you will have constructed from some of the classes in the overall dynsim package.

Walk-Through of Example

Here's the same example that appears on the DynSim main page. This example is of a PID controller configured in a loop with a FOPDT (First-Order-Plus-Dead-Time) dynamic representing the process to be controlled. During the simulation, the Set-Point of the controller is stepped up to allow us to see what the closed-loop response is.

Even though the simulation code already contains many comments, we'll go through this line-by-line and understand what it does. The first 3 lines is just a documentation string and is useful (but optional) to put in to describe what the script does. It is enclosed in triple-double-quotation marks so that we can use any reasonable characters and quotation marks inside the documentation without Python trying to interpret it.

"""Simple example of dynsim use for a control loop.
Simulates and plots control of a first-order plus deadtime (FOPDT) process
with a PID controller.  100 seconds at stepsize=0.1 second are simulated."""

from pctools.dynsim import control

# create the simulation objects or "blocks"
ctl = control.PID(P=1.8, I=5.0, D=0.0)  # make a PID controller
plant = control.FOPDT(gain=1.0, delay=2.0, tc=5.0) # first-order plus deadtime model
record = control.Log(name='Logged Data', legend=('PV', 'CO', 'SP'))  # set up a logger

PV = 0.0  # provide PV for first time-step (PV not yet generated by plant)
SP = 0.0   # initial value of Setpoint before step test

# simulate
for t,dt in control.timesteps(finish=100.0, timestep=0.1, plot=True):
    if t>10: SP = 40.0  # step change in setpoint
    out = ctl(SP, PV)  # run the PID controller
    PV = plant(out)  # run the process dynamics
    record([PV, out, SP])  # record a log of the main variables

Importing

The next line (in the code-box) simply imports one of the dynsim internal modules (like libraries).

from dynsim import control

The package dynsim is made up of the following modules:

The particular module being imported here is "control", but you could include more than one and just separate them with commas. However, "control" contains the base elements of dynsim as well as the control and dynamic classes. So a simulation will always import at least "control". After we've imported "control", we can use the classes in it by referring to them using the usual dot notation of python, e.g. control.PID

Technical note: It is possible to just import dynsim, but this leads to the inconvenience of the dot notation getting longer, e.g. dynsim.control.PID . Despite being longer, it is valid and makes it very clear in the code where the PID class is being used from. On the other hand, we could also from dynsim.control import * whereupon we could just use the PID class without any dot notation. However, if there were any other PID classes or functions in other imported code, there would be a clash.

Create simulation objects

Now that we've imported our "control" module, we can start using it by creating our simulation objects. Here's our code to create the objects. The first line (in green) starting with "#" is just a comment line. All parts of the code to the right of a "#" are comments, including those on the right side of the code lines (again, in green). Most Python editors and consoles will automatically detect and colour-code the comments.

# create the simulation objects or "blocks"
ctl = control.PID(P=1.8, I=5.0, D=0.0)  # make a PID controller
plant = control.FOPDT(gain=1.0, delay=2.0, tc=5.0) # first-order plus deadtime model
record = control.Log(name='Logged Data', legend=('PV', 'CO', 'SP'))  # set up a logger

The module contains "classes" from which we can create objects. We create the objects by calling the "constructor" method (a function within the class) of the class (which is what happens when we just call the class by name). The constructor method has arguments that can customise the object that we create.

In the above code, we construct a PID controller (named "ctl") by calling the class control.PID with specific values for the P (proportional) and I (integral) tuning parameters, and assigning the resulting object to the identifier "ctl" . The parameters of the constructor are typically ones that aren't expected to change during the simulation, although if needed this can easily be done. You only need to set the parameters that need to be tailored for this controller. For example, we don't bother setting the upper and lower bounds for the PID output because these are inherited from the default values in the class (set to 100.0 and 0.0 respectively). However, these are optional arguments for the PID constructor.

Technical note: Note the use of python's feature called named-arguments. Instead of putting the parameters into the argument list in the right order, it is much nicer to specify which argument you are assigning to, and it also makes the code much easier to understand. It is hard to remember the positional order of the arguments to understand which argument is which.

Similarly, we also create a FOPDT (First-Order-Plus-Dead-Time) object to represent the plant dynamics, called "process". It includes parameter values for gain, delay and tc (timeconstant) of the FOPDT. Lastly, we create a log (logger or history keeper) object, called "record". The parameters of the constructor set the label name of the new object (not to be confused with the object name record which will be used later in the code), and the legend for the data when it is plotted. The legend item order must be the same as that used when the data is recorded (this will be described later).

The three lines of code construct three objects for us to use in our simulation. Bear in mind that each of these objects remember their own parameters, can hold all of their own intermediate data for their algorithms, and hold the code to execute their functionality. So we never have to consider the original classes again in this simulation.

Technical note: Each time you call the constructor (automatically by calling the class name, e.g. control.PID), you create a new object. Each new object inherits the characteristics and functionality of the class, but obviously customised by the arguments given at construction time.

Initialisation

In a typical Process Control simulation, the connections between process dynamic elements and the control schemes generally form loops. Such loops have no beginning or end, so the simulation has to start executing the elements in a certain order. When executing the first element of a loop, it needs to know what the input from the previous element is, but since the previous element has never been executed, the input is unknown. So for the execution of the first timestep of the simulation, an initial value is required for some variables. The next couple of lines of code in the simulation are for initialisation.

# simulate
PV = 30.0
SP = 30.0   # initial value of Setpoint before step test

As before, the # indicates a comment (indicating the start of the simulation code). In this simulation, the two variables needing initialisation (using an assignment) are the PV (Plant Variable) and SP (Set-Point) inputs of the PID controller that we are going to execute early in the simulation sequence. The comment on the SP assignment is just used to remind the programmer that this value won't actually be changed by the simulation objects, but is changed by a later assignment to step the SP up during the course of the simulation.

Main Timestep Loop

Now we get to the important part of the simulation code, the iteration through all the timesteps of the simulation by repeatedly executing the objects and their "glue" code. However we need something to manage this iteration and specify how we want this done. This is done by the fairly complicated "for" loop of the next line in the example code.

for t,dt in control.timesteps(finish=100.0, timestep=0.1, plot=True):

This is a "for" loop in Python. In Python the "for" loop is of the form for iteration_variable in list_of_iteration_values:. In this case it is a little more complicated. Looking past the t,dt part for a moment, we can see that we call a function called control.timesteps(...). This is a function provided in the "control" module (that we imported earlier) which calculates a list of iteration values for the loop from the arguments of the function. The arguments specify that we want the simulation to:

There are several unused (optional) parameters available in the call to control.timesteps. One of these is initcycles=2. This tells timesteps to use two initialisation cycles through the main simulation code block (within the for loop).

The initialisation cycles are non-simulation cycles through the main code block during which the simulation objects automatically know that they need to initialise themselves. This is done using a special use of the "dt" variable. This variable is set to -1.0 for the initialisation cycles and the simulation objects automatically know what to do. You may also need to initialise variables outside of the objects, but this is seldom needed.

Back to the t,dt! In this case the "for" loop actually cycles through two iteration variables at a time, t and dt. t is just used to designate simulation time, while dt is the timestep variable. t usually just increments through the start to finish times at the specified timestep. dt is -1.0 during initialisation cycles (2 in this case) and the normal timestep value (0.1) for the rest of the simulation steps. When this loop has exhausted the list of values from control.timesteps(...), the simulation is finished.

At this point the last argument of control.timesteps comes into play. At the end of the simulation, plot=True causes control.timesteps to request each of the Log objects created in the simulation to plot a chart of its collected data. If more flexibility is required, then there are additional functions in the control module that can be used to control when and what order to plot the charts.

Object execution

Of course, the main simulation code block is the most important part of the simulation, and typically would contain the most code lines. Let's work through each line of this.

    if t>10: SP=70.0  # step change in setpoint
    out = ctl.run(dt,SP,PV,30.0,False)  # run the PID controller
    PV = process.run(dt, out, 0.0, False)  # run the process dynamics

First, note that this code is indented, indicating that this is the code block that is inside the "for" loop.

The first line is a special line to make the simulation results interesting. Essentially, it leaves the SP at its initialised value of 30.0 until t becomes 10.0, at which time it changes it to 70.0, giving us a nice step test for looking at the closed loop response.

The next line "runs" the PID controller. Recall that we called our PID controller object "ctl". ctl contains a method called "run" which executes its normal functionality. Recall that to construct a PID object we just called the class as a function. To access the other functionality (such as "run") we need to call those object methods. Here we call ctl.run(...) with arguments for the timestep (dt), the setpoint (SP), the plant variable (PV), the controller output track value (30.0) and the track flag (False). The last two arguments are a feature of this PID formulation that allows us to put the controller into a manual override state and forcing the output to track a particular value (as though either the operator or some sequencing logic required this change). In this case, the track flag is False, so the only time the track value is used is during the initialisation cycles. The output of the controller is assigned to "out".

The next line runs the "process" (FOPDT) object and follows the same format, calling the process.run(...) method with arguments of timestep (dt), input (out - note that this is the output of the controller), track value (0.0) and track flag (False). The output is assigned to "PV".

Logging the results

The last line in the main simulation code block (below), we run record.run(...) (log object) so that it can collect this each timestep's data. The call's arguments are simply the data to be logged. In this case, the data consist of three values (PV, out and SP) and they are grouped into what Python calls a tuple. Essentially it is a group of comma separated values which normal brackets around them. These three values match (in order) the three legend labels which were configured for the "record" object when it was created. Multiple Log object can be set up to collect separate groups of data so that these can be displayed in separate charts at the end of the simulation.

    record([PV, out, SP])  # record a log of the main variables

Running this example

Looking at your simulation objects after the simulation

Creating a new dynsim class (advanced user)

Why would you create a new class?

PID Example

Imports

Class Definition

new and old classes

Constructor (__init__)

run method

Also see


2015-05-14 10:33