Creating New Object Classes¶
This section describes how to define your own object classes for use in the simulation.
About Dynamic Modules¶
Dynamic Module (DM) is a file containing an object class, especially C++ class, which can be loaded and instantiated by the application. APP uses this mechanism to provide users a way of defining and adding new classes to appear in simulation models without recompiling the whole system. Because the classes are defined in forms of native codes, this is the most efficient way of adding a new code or object class in terms of space and speed.
In APP, subclasses of PROCESS, VARIABLE, SYSTEM and STEPPER classes can be dynamically loaded by the system.
In addition to standard DMs distributed with APP, user-defined DM files
can be created from C++ source code files (‘.cpp’ files) with the
ecell3-dmc
command. The compiled files usually take a form of shared
library (‘.so’) files.
Defining a new class¶
A new object class can be defined by writing a C++ source code file with some special usage of C++ macros.
Here is a boilarplate template of a DM file, with which you should feel
familiar if you have a C++ experience. Replace DMTYPE
,
CLASSNAME
, and BASECLASS
according to your case.
#include <libecs/libecs.hpp>
#include <libecs/.hpp>
USE_LIBECS;
LIBECS_DM_CLASS( , )
{
public:
LIBECS_DM_OBJECT( , )
{
// ( Property definition of this class comes here. )
}
() {}// A constructor without an argument
() {}// A destructor
};
LIBECS_DM_INIT( , );
DMTYPE
, CLASSNAME
and BASECLASS
¶
First of all you have to decide basic attributes of the class you are going to define; such as a DM type (PROCESS, VARIABLE, SYSTEM, or STEPPER), a class name, and a base class.
DMTYPE
DMTYPE
is one of DM base classes defined in APP PROCESS, STEPPER, VARIABLE, and SYSTEM.CLASSNAME
CLASSNAME
is a name of the object class.This must be a valid C++ class name, and should end with the
DMTYPE
name. For example, if you are going to define a new PROCESS class and want to name it Foo, the class name may look like FooProcess.BASECLASS
The class your class inherits from.
- This may or may not be the same as the ``DMTYPE
``, depending on whether it is a direct descendant of the DM base
class.
Filename¶
The name of the source file must be the same as the CLASSNAME
with a
trailing ‘.cpp’ suffix. For example, if the CLASSNAME
is FooProcess,
the file name must be FooProcess.cpp
.
The source code can be divided into header and source files (such as
FooProcess.hpp
and FooProcess.cpp
), but at least the
LIBECS_DM_INIT
macro must be placed in the source file of the class
(FooProcess.cpp
).
Include Files¶
At least the libecs header file (libecs/libecs.hpp
) and a header
file of the base class (such as libecs/.hpp
) must be included in the
head of the file.
DM Macros¶
You may notice that the template makes use of some special macros:
USE_LIBECS
, LIBECS_DM_CLASS
, LIBECS_DM_OBJECT
, and
LIBECS_DM_INIT
.
USE_LIBECS
declares use of libecs library, which is the core library
of APP, in this file after the line.
LIBECS_DM_CLASS
- ``LIBECS_DM_OBJECT( ,
- )`` should be placed on the top of the class definition part
(immediately after ‘{‘ of the class). This macro declares that this is a DM class. This macro makes it dynamically instantiable, and automatically defines getClassName() method. Note that this macro specifies public: field inside, and thus anything comes after this is placed in public. For clarity it is a good idea to always write public: explicitly after this macro.
LIBECS_DM_OBJECT( DMTYPE, CLASSNAME )
public:
- ``LIBECS_DM_INIT( ,
- )`` exports the class
CLASSNAME
as a DM class of type
DMTYPE
. This must come after the definition (not just a declaration)
of the class to be exported with a LIBECS_DM_OBJECT
call.
Constructor And Destructor¶
DM objects are always instantiated by calling the constructor with no argument. The destructor is defined virtual in the base class.
Types And Declarations¶
Basic types¶
The following four basic types are available to be used in your code if
you included libecs/libecs.hpp
header file and called the
USE_LIBECS
macro.
Real
A real number. Usually implemented as a double precision floating point number. It is a 64-bit float on Linux/IA32/gcc platform.
Integer
A signed integer number. This is a 64-bit
long int
on Linux/IA32/gcc.UnsignedInteger
An unsigned integer number. This is a 64-bit
unsigned long int
on Linux/IA32/gcc.STRING
A string equivalent to std::string class of the C++ standard library.
POLYMORPH
POLYMORPH is a sort of universal type (actually a class) which can *become* and *be made from* any of
Real
,Integer
,String
, andPolymorphVector
, which is a mixed list of these three types of objects. See the next section for details.
These types are recommended to be used over other C++ standard types
such as double
, int
and char*
.
Pointer and reference types¶
For each types, the following typedefs are available.
TYPEPtr
Pointer type. (==
TYPE*
)TYPECptr
Const pointer type. (==
const TYPE*
)TYPERef
Reference type. (==
TYPE&
)TYPECref
Const reference type. (==
const TYPE&
)
For example, RealCref
is equivalent to write const Real&
. Using
these typedefs is recommended.
To declare a new type, use DECLARE_TYPE
macro. For example,
DECLARE_TYPE( double, Real );
- is called inside the system so that
RealCref
can be used as ``const - double&``.
Similary, DECLARE_CLASS can be used to enable the typedefs for a class. Example:
DECLARE_CLASS( Process );
enables ProcessCref
ProcessPtr
etc.. Most classes defined in
libecs have these typedefs.
Limits and other attributes of types¶
To get limits and precisions of these numeric types, use
std::numeric_limits<> template class in the C++ standard library. For
instance, to get a maximum value that can be represented by the Real
type, use the template class like this:
#include <limits>
numeric_limits<Real>::max();
See the C++ standard library reference manual for more.
Polymorph class¶
A POLYMORPH object can be constructed from and converted to any of
Real
, Integer
, String
, types and POLYMORPHVECTOR class.
Construct a Polymorph¶
To construct a POLYMORPH object, simply call a constructor with a value:
Polymorph anIntegerPolymorph( 1 );
Polymorph aRealPolymorph( 3.1 );
Polymorph aStringPolymorph( "2.13e2" );
A POLYMORPH object can be constructed (or copied) from a POLYMORPH:
Polymorph aRealPolymorph2( aRealPolymorph );
Getting a value of a Polymorph¶
The value of the POLYMORPH objects can be retrieved in any type by using as<>() template method.
anIntegerPolymorph.as<Real>(); // == 1.0
aRealPolymorph.as<String>(); // == "3.1"
aStringPolymorph.as<Integer>(); // == 213
**Note**
If an overflow occurs when converting a very big ``Real`` value to
``Integer``, a ValueError exception?? is thrown. (NOT IMPLEMENTED
YET)
Examining and changing the type of Polymorph¶
getType(), changeType()
PolymorphVector¶
POLYMORPHVECTOR is a list of POLYMORPH objects.
Other C++ statements¶
The only limitation is the DM_INIT
macro, which exports a class as a
DM class, can appear only once in a compilation unit which forms a
single shared library file.
Except for that, there is no limitation as far as the C++ compiler understands it. There can be any C++ statements inside and outside of the class definition including; other class definitions, nested classes, typedefs, static functions, namespaces, and even template<>.
Be careful, however, about namespace corruptions. You may want to use private C++ namespaces and static functiont when a class or a function declared outside the DM class is needed.
PropertySlot¶
What is PropertySlot¶
PROPERTYSLOT is a pair of methods to access (get) and mutate (set) an object property, associated with the name of the property. Values of the object property can either be stored in a member variable of the object, or dynamically created when the methods are called.
All of the four DM base classes, PROCESS, VARIABLE, SYSTEM and STEPPER can have a set of PROPERTYSLOTs, or object properties. In other words, these classes inherit PROPERTYINTERFACE common base class.
What is PropertySlot for?¶
PROPERTYSLOTs can be used from model files (such as EM files) as a means of giving parameter values to each objects in the simulation model (such as ENTITY and STEPPER objects). It can also be ways of dynamic communications between objects during the simulation.
Type of PropertySlot¶
A type of a PROPERTYSLOT is any one of these four types:
Real
Integer
String
Polymorph
How to define a PropertySlot¶
To define a PROPERTYSLOT on an object class, you have to:
- Define set and/or get method(s).
- If necessary, define a member variable to store the property value.
- Register the method(s) as a PROPERTYSLOT.
Set method and get method¶
A PROPERTYSLOT is a pair of object methods, set method and get method, associated with a property name. Either one of the methods can be ommited. If there is a set method defined for a PROPERTYSLOT, the PROPERTYSLOT is said to be setable. If there is a get method, it is getable.
A set method must have the following signature to be recognized by the system.
void CLASS::* ( const T&)
And a get method must look like this:
const T CLASS::* ( void ) const
where T
is a property type and CLASS
is the object class that
the PROPERTYSLOT belongs to.
Don’t worry, you don’t need to memorize these prototypes. The following four macoros can be used to declare and define set/get methods of a specific type and a property name.
SET_METHOD( , )
Expansion:
void set( const &value )
Usage:
SET_METHOD
macro is used to declare or define a property set method, of which the property type isTYPE
and the property name isNAME
, in a class definition. The given property value is available as thevalue
argument variable.Example:
This code:
class FooProcess { SET_METHOD( Real, Flux ) { theFlux = value; } Real theFlux; };
will expand to the following C++ program.
class FooProcess { void setFlux( const Real& value ) { theFlux = value; } Real theFlux; };
In this example, the given property value is stored in the member variable
theFlux
.
GET_METHOD( , )
Expansion:
const get() const
Usage:
GET_METHOD
macro is used to declare or define a property get method, of which the property type isTYPE
and the property name isNAME
, in a class definition. Definition of the method must return the value of the property as aTYPE
object.Example:
This code:
class FooProcess { GET_METHOD( Real, Flux ) { return theFlux; } Real theFlux; };
will expand to the following C++ program.
class FooProcess { const Real getFlux() const { return theFlux; } Real theFlux; };
SET_METHOD_DEF( , , )
Expansion:
void ::set( const &value )
Usage:
SET_METHOD_DEF
macro is used to define a property set method outside class scope.Example:
SET_METHOD_DEF
macro is usually used in conjunction withSET_METHOD
macro. For instance, the following code declares a property setter method withSET_METHOD
in the class definition, and later defines the actual body of the method usingSET_METHOD_DEF
.class FooProcess { virtual SET_METHOD( Real, Flux ); Real theFlux; }; SET_METHOD_DEF( Real, Flux, FooProcess ) { theFlux = value; }
The definition part will expand to the following C++ program.
void FooProcess::setFlux( const Real& value ) { theFlux = value; }
GET_METHOD_DEF( , , )
Expansion:
const ::get() const
Usage:
GET_METHOD_DEF
macro is used to define a property get method outside class scope.Example: See the example of
SET_METHOD_DEF
above.
If the property is both setable and getable, and is simply stored in a member variable, the following macro can be used.
SIMPLE_SET_GET_METHOD( , )
This assumes there is a variable with the same name as the property name
(NAME
), and expands to a code that is equivalent to:
SET_METHOD( , )
{
= value;
}
GET_METHOD( , )
{
return ;
}
Registering PropertySlots¶
To register a PROPERTYSLOT on a class, one of these macros in the
LIBECS_DM_OBJECT
macro of the target class:
PROPERTYSLOT_SET_GET( , )
Use this if the property is both setable and getable, which means that the class defines both set method and get method.
For example, to define a property ‘Flux’ of type
Real
on the FooProcess class, write like this in the public area of the class definition:public: LIBECS_DM_OBJECT( , ) { PROPERTYSLOT_SET_GET( , ); }
This registers these methods:
void FooProcess::setFlux( const Real& );
and
const Real FooProcess::getFlux() const;
as the set and get methods of ‘Flux’ property of the class FooProcess, respectively. Signatures of the methods must match with the prototypes defined in the previous section.
LIBECS_DM_OBJECT
can have any number of properties. It can also be empty.PROPERTYSLOT_SET( , )
This is almost the same as
PROPERTYSLOT_SET_GET
, but this does not register get method. Use this if only a set method is available.PROPERTYSLOT_GET( , )
This is almost the same as
PROPERTYSLOT_SET_GET
, but this does not register set method. Use this if only a get method is available.PROPERTYSLOT( , , , )
If the name of either get or set method is different from the default format (set``NAME``() or get
NAME
()), then use this macro with explicitly specifying the pointers to the methods.For example, the following use of the macro registers setFlux2() and anotherGetMethod() methods of Flux property of the class FooProcess:
PROPERTYSLOT( Flux, Real, &FooProcess::setFlux2, &FooProcess::anotherGetMethod );
If more than one PROPERTYSLOTs with the same name are created on an object, the last is taken.
Load / save methods¶
In addition to set and get methods, load and save methods can be defined. Load methods are called when the model is loaded from the model file. Similarly, save methods are called when the state of the model is saved to a file by saveModel() method of the simulator.
Unless otherwise specified, load and save methods default to set and get methods. This default definition can be changed by using the following some macros.
PROPERTYSLOT_LOAD_SAVE( , , , , , )
This macros is the most generic way to set the property methods; all of set method, get method, load method ans save method can be specified independently. If the
LOAD_METHOD
isNOMETHOD
, it is said to be not loadable, and it is not savable ifSAVE_METHOD
isNOMETHOD
.PROPERTYSLOT_NO_LOAD_SAVE( , , , )
Usage of this macro is the same as
PROPERTYSLOT
in the previous section, but this sets bothLOAD_METHOD
andSAVE_METHOD
toNOMETHOD
.That is, this macro is equivalent to writing:
PROPERTYSLOT_SET_GET_NO_LOAD_SAVE( , , , )
PROPERTYSLOT_SET_NO_LOAD_SAVE( , , )
PROPERTYSLOT_GET_NO_LOAD_SAVE( , , )
Usage of these macros are the same as:
PROPERTYSLOT_SET_GET
,PROPERTYSLOT_SET
, andPROPERTYSLOT_GET
, except that load and save methods are not set instead of default to set and get methods.
Inheriting properties of base class¶
In most cases you may also want to use properties of base class. To
inherit the baseclass properties, use INHERIT_PROPERTIES( )
macro. This macro is usually placed before any property
definition macros (such as PROPERTY_SET_GET()
).
LIBECS_DM_OBJECT( , )
{
INHERIT_PROPERTIES( );
PROPERTYSLOT_SET_GET( , );
}
Here PROPERTY_BASECLASS
is usually the same as BASECLASS
. An
exception is when the BASECLASS
does not make use of
LIBECS_DM_OBJECT()
macro. In this case, choose the nearest baseclass
in the class hierarachy that uses LIBECS_DM_OBJECT()
for
PROPERTY_BASECLASS
.
Using PropertySlots In Simulation¶
(1) Static direct access (using native C++ method) bypassing the PROPERTYSLOT, (2) dynamically-bound access via a PROPERTYSLOT object, (3) dynamically-bound access via PROPERTYINTERFACE.
Defining a new Process class¶
To define a new PROCESS class, at least the following two methods need to be defined.
- initialize()
- fire()
initialize() is called when the simulation state needs to be reset. Note that reset can happen anytime during the session, not just at the beginning; especially when the reintegration of the state is requested. fire() is called when the reaction takes place. You have to update the VARIABLEs referred to by your PROCESS according to VARIABLEREFERENCE.
The PROCESS’s VARIABLEREFERENCEs are stored in
theVariableReferenceVector
member variable, sorted by coefficient.
Hence references that have negative coefficients are followed by those
of zero coefficients, and so by those of positive coefficients. You can
get the offset from which the “zero” or positive references start
through getZeroVariableReferenceOffset() or
getPositiveVariableReferenceOffset(). If you want to look up for a
specific VARIABLEREFERENCE by name, use getVariableReference().
#include <libecs.hpp>
#include <Process.hpp>
USE_LIBECS;
LIBECS_DM_CLASS( SimpleProcess, Process )
{
public:
LIBECS_DM_OBJECT( SimpleFluxProcess, Process )
{
PROPERTYSLOT_SET_GET( Real, k );
}
SimpleProcess(): k( 0.0 )
{
}
SIMPLE_SET_GET_METHOD( Real, k );
virtual void initialize()
{
Process::initialize();
S0 = getVariableReference( "S0" );
}
virtual void fire()
{
// concentration gets reverted to the number of molecules
// according to the volume of the System where the Process belongs.
setFlux( k * S0.getMolarConc() * getSuperSystem()->getSize() * N_A );
}
protected:
Real k;
VariableReference const& S0;
};
LIBECS_DM_INIT( SimpleProcess, Process );