Adding Python Bindings To Steppable in DeveloperZone
In the previous example we controled entire simulation from C++. This is perfectly fine and will give you optimal performance. However sometimes it may make sense to add Python bindings to your module . Especially if the functions you wil call from python will not be called many times - functions calls in Python are much slower than in C++.
In addition to this if your entire code is in C++ every change you make to the code will require compilation and installation. This is is not a big deal but takes time and is more error prone than using well designed scripting interface. However, do not feel that you need to use Python bindings for your newly created C++ modules. They are optional and it is perfectly fine to operate in C++ space.
Nevertheless we would like to show you how to add and use Python bindings if you feel it will be beneficial for your simulation.
If you remember, the first step to generate steppable code using Twedit++ is to choose whether you like to add Python bindings or not.
In this first dialog box we checked Python Wrap
option and therefore we already generated Python bindings. They are
stored in SWIG file in DeveloperZone/pyinterface/CompuCellExtraModules/CompuCellExtraModules.i
:
%module ("threads"=1) CompuCellExtraModules
%include "typemaps.i"
%include <windows.i>
%{
#include "ParseData.h"
#include "ParserStorage.h"
#include <CompuCell3D/Simulator.h>
#include <CompuCell3D/Potts3D/Potts3D.h>
#include <BasicUtils/BasicClassAccessor.h>
#include <BasicUtils/BasicClassGroup.h> //had to include it to avoid problems with template instantiation
// ********************************************* PUT YOUR PLUGIN PARSE DATA AND PLUGIN FILES HERE *************************************************
#include <SimpleVolume/SimpleVolumePlugin.h>
#include <VolumeMean/VolumeMean.h>
//AutogeneratedModules1 - DO NOT REMOVE THIS LINE IT IS USED BY TWEDIT TO LOCATE CODE INSERTION POINT
//GrowthSteppable_autogenerated
#include <GrowthSteppable/GrowthSteppable.h>
// ********************************************* END OF SECTION ********************************** ************************************************
//have to include all export definitions for modules which are arapped to avoid problems with interpreting by swig win32 specific c++ extensions...
#define SIMPLEVOLUME_EXPORT
#define VOLUMEMEAN_EXPORT
//AutogeneratedModules2 - DO NOT REMOVE THIS LINE IT IS USED BY TWEDIT TO LOCATE CODE INSERTION POINT
//GrowthSteppable_autogenerated
#define GROWTHSTEPPABLE_EXPORT
#include <iostream>
using namespace std;
using namespace CompuCell3D;
%}
// C++ std::string handling
%include "std_string.i"
// C++ std::map handling
%include "std_map.i"
// C++ std::map handling
%include "std_set.i"
// C++ std::vector handling
%include "std_vector.i"
//have to include all export definitions for modules which are arapped to avoid problems with interpreting by swig win32 specific c++ extensions...
#define SIMPLEVOLUME_EXPORT
#define VOLUMEMEAN_EXPORT
//AutogeneratedModules3 - DO NOT REMOVE THIS LINE IT IS USED BY TWEDIT TO LOCATE CODE INSERTION POINT
//GrowthSteppable_autogenerated
#define GROWTHSTEPPABLE_EXPORT
%include <BasicUtils/BasicClassAccessor.h>
%include <BasicUtils/BasicClassGroup.h> //had to include it to avoid problems with template instantiation
%include "ParseData.h"
%include "ParserStorage.h"
// ********************************************* PUT YOUR PLUGIN PARSE DATA AND PLUGIN FILES HERE *************************************************
// REMEMBER TO CHANGE #include to %include
%include <SimpleVolume/SimpleVolumePlugin.h>
// %include <SimpleVolume/SimpleVolumeParseData.h>
// THIS IS VERY IMORTANT STETEMENT WITHOUT IT SWIG will produce incorrect wrapper code which will compile but will not work
using namespace CompuCell3D;
%inline %{
SimpleVolumePlugin * reinterpretSimpleVolumePlugin(Plugin * _plugin){
return (SimpleVolumePlugin *)_plugin;
}
SimpleVolumePlugin * getSimpleVolumePlugin(){
return (SimpleVolumePlugin *)Simulator::pluginManager.get("SimpleVolume");
}
%}
%include <VolumeMean/VolumeMean.h>
%inline %{
VolumeMean * reinterpretVolumeMean(Steppable * _steppable){
return (VolumeMean *)_steppable;
}
VolumeMean * getVolumeMeanSteppable(){
return (VolumeMean *)Simulator::steppableManager.get("VolumeMean");
}
%}
//AutogeneratedModules4 - DO NOT REMOVE THIS LINE IT IS USED BY TWEDIT TO LOCATE CODE INSERTION POINT
//GrowthSteppable_autogenerated
%include <GrowthSteppable/GrowthSteppable.h>
%inline %{
GrowthSteppable * getGrowthSteppable(){
return (GrowthSteppable *)Simulator::steppableManager.get("GrowthSteppable");
}
%}
We are not going to explain how SWIG wrappers work here but if you look at the file and look for occurrences of
GrowthSteppable
you can see that adding your own steppable to the SWIG wrapper generator is fairly easy. On top
of that if you use Twedit++ it will generate wrapper code for you.
Note
At the top of the wrapper file we find %module ("threads"=1) CompuCellExtraModules
. This tells us that the Python module we develop will be called CompuCellExtraModules
.
Adding Python-Accessible Method To GrowthSteppable
If we enable compilation of CompuCellExtraModules
by uncommenting line add_subdirectory(pyinterface)
in
DeveloperZone/CMakeLists.txt
we will already get GrowthSteppable bindings that we can access from
CompuCellExtraModules
. However, they are not particularly useful because all the functions accessible via
Python are for functions that are already being used by the C++ code and, frankly it is best to leave it like that. We
need to add additional function. A reasonable choice is a function that changes growth rate for a given cell type.
First we need to add void setGrowthRate(unsigned int cellType, double growthRate); `` function
to ``GrowthSteppable
header - see below:
#ifndef GROWTHSTEPPABLESTEPPABLE_H
#define GROWTHSTEPPABLESTEPPABLE_H
#include <CompuCell3D/CC3D.h>
#include "GrowthSteppableDLLSpecifier.h"
namespace CompuCell3D {
template <class T> class Field3D;
template <class T> class WatchableField3D;
class Potts3D;
class Automaton;
class BoundaryStrategy;
class CellInventory;
class CellG;
class GROWTHSTEPPABLE_EXPORT GrowthSteppable : public Steppable {
WatchableField3D<CellG *> *cellFieldG;
Simulator * sim;
Potts3D *potts;
CC3DXMLElement *xmlData;
Automaton *automaton;
BoundaryStrategy *boundaryStrategy;
CellInventory * cellInventoryPtr;
Dim3D fieldDim;
public:
GrowthSteppable ();
virtual ~GrowthSteppable ();
std::map<unsigned int, double> growthRateMap;
// SimObject interface
virtual void init(Simulator *simulator, CC3DXMLElement *_xmlData=0);
virtual void extraInit(Simulator *simulator);
// Python wrapper functions
void setGrowthRate(unsigned int cellType, double growthRate);
//steppable interface
virtual void start();
virtual void step(const unsigned int currentStep);
virtual void finish() {}
//SteerableObject interface
virtual void update(CC3DXMLElement *_xmlData, bool _fullInitFlag=false);
virtual std::string steerableName();
virtual std::string toString();
};
};
#endif
Next, we add implementation of this function in the GrowthSteppable.cpp
:
void GrowthSteppable::setGrowthRate(unsigned int cellType, double growthRate){
cerr<<"CHANGING GROWTH RATE FOR CELL TYPE "<<cellType<<" TO "<<growthRate<<endl;
this->growthRateMap[cellType] = growthRate;
}
The implementation of this function is pretty straightforward - it is a function that takes two arguments
(unsigned int cellType
and double growthRate
and prints out message to the screen that it is about to
change growth rate for a given cell type and then assigns a growth rate to a given entry in the this->growthRateMap
.
Why does this function make sense to be implemented in Python? If you think about a simulation where you want to run many simulations that need to modify growth rate at a particular MCS but you don’t know which MCS it will be you can write a simple code where you could try many time points at this you change growth rate and see if the outcome matches your expectations. Obviously, you could do it all in c++ but then you would need to pass more parameters to the XML making XML harder and harder to understand or you could hard-code everything in C++ but then you would need to recompile DeveloperZone every time you run the simulation. You quickly realize that Python provides convenient platform for handling situations like this. This is why , it makes perfect sense to add Python bindings to your C++ modules.
At this point we can recompile the DeveloperZone but before we do it it is essential that we “touch”
DeveloperZone/pyinterface/CompuCellExtraModules/CompuCellExtraModules.i
by e.g. add or remove empty line in this
file and re-saving it.
Warning
If you add new method to header file and want this method be accessible in Python bindings you must force SWIG to re-generate bindings. One way of doing so is by “refreshing” the file but making adding (or removing) extra empty line and saving it. In the future we will write better CMake code to avoid this manual step but for now you should be aware of this limitation.
After we compiled and installed DeveloperZone
modules we can rerun the simulation. Now, however, we will add Python
code where we show you how to access new C++ steppable from Python.
Here is python Steppable code (GrowthSteppablePythonModules
):
from cc3d.core.PySteppables import *
from cc3d.cpp import CompuCellExtraModules
class GrowthSteppablePython(SteppableBasePy):
def __init__(self,frequency=1):
SteppableBasePy.__init__(self,frequency)
self.growth_steppable_cpp = None
def start(self):
self.growth_steppable_cpp = CompuCellExtraModules.getGrowthSteppable()
def step(self,mcs):
if mcs == 10:
self.growth_steppable_cpp.setGrowthRate(1, -1.2)
At the top of the file we import CompuCellExtraModules
. This is the module that SWIG generated for us
In __init__
constructor we create a variable that will hold a reference to the C++ GrowthSteppable
.
In start function we access C++ GrowthSteppable
by typing:
self.growth_steppable_cpp = CompuCellExtraModules.getGrowthSteppable()
If you look at the end of the DeveloperZone/pyinterface/CompuCellExtraModules/CompuCellExtraModules.i
you will see
getGrowthSteppable()
declared there. In other words getGrowthSteppable()
function will become a function of the
CompuCellExtraModules
and therefore we access it as CompuCellExtraModules.getGrowthSteppable()
.
Now, we can get creative, because we can access every publicly defined function of the C++ GrowthSteppable
. This is
exactly what we do in the step
function. We call our newly added function
self.growth_steppable_cpp.setGrowthRate(1, -1.2)
This call at MCS=10 changes growth of cells of type 1 into shrinking rate.
When we run the simulation at mcs==10
the text output will look as follows:
You can see there our C++ printout being triggered by calling setGrowthRate
from Python level. And the
simulation configuration at MCS 0 and 30 respectively will looks as follows:
Notice that the blue cells almost disappeared. This is the result of the negative growth rate we we set by calling
self.growth_steppable_cpp.setGrowthRate(1, -1.2)
.
The C++ code for this example can be found in DeveloperZone/GrowthSteppable
, python bindings are in
DeveloperZone/pyinterface/CompuCellExtraModules/CompuCellExtraModules.i
and the simulation example is in
CompuCell3D/DeveloperZone/Demos/GrowthSteppablePython