pyDAE
modules can be imported in the following way:
from daetools.pyDAE import *
This will set the python sys.path
for importing the platform dependent c++
extension modules
(i.e. .../daetools/pyDAE/Windows_win32_py34
and .../daetools/solvers/Windows_win32_py34
in Windows,
.../daetools/pyDAE/Linux_x86_64_py34
and .../daetools/solvers/Linux_x86_64_py34
in GNU/Linux),
import all symbols from all pyDAE
modules: pyCore
, pyActivity
, pyDataReporting
,
pyIDAS
, pyUnits
and import some platfom independent modules: logs
,
variable_types
and dae_simulator
.
Alternatively, only the daetools
module can be imported and classes from the pyDAE
extension modules accessed using fully qualified names. For instance:
import daetools
model = daetools.pyDAE.pyCore.daeModel("name")
Once the pyDAE
module is imported, the other modules (such as third party linear solvers,
optimisation solvers etc.) can be imported in the following way:
# Import Trilinos LA solvers (Amesos, AztecOO):
from daetools.solvers.trilinos import pyTrilinos
# Import SuperLU linear solver:
from daetools.solvers.superlu import pySuperLU
# Import SuperLU_MT linear solver:
from daetools.solvers.superlu_mt import pySuperLU_MT
# Import IPOPT NLP solver:
from daetools.solvers.ipopt import pyIPOPT
# Import BONMIN MINLP solver:
from daetools.solvers.bonmin import pyBONMIN
# Import NLOPT set of optimisation solvers:
from daetools.solvers.nlopt import pyNLOPT
Since domains, parameters and variables in DAE Tools have a numerical value in terms
of a unit of measurement (quantity
) the modules containing definitions of
units and variable types must be imported. They can be imported in the following way:
from daetools.pyDAE.variable_types import length_t, area_t, volume_t
from daetools.pyDAE.pyUnits import m, kg, s, K, Pa, J, W
The complete list of units and variable types can be found in Variable types and Module pyUnits modules.
In DAE Tools models are developed by deriving a new class from the base pyCore.daeModel
class.
An empty model definition is presented below:
class myModel(daeModel):
def __init__(self, name, parent = None, description = ""):
daeModel.__init__(self, name, parent, description)
# Declaration/instantiation of domains, parameters, variables, ports, etc:
...
def DeclareEquations(self):
# Declaration of equations, state transition networks etc.:
...
The process consists of the following steps:
Calling the base class constructor:
daeModel.__init__(self, name, parent, description)
Declaring the model structure (domains, parameters, variables, ports, components etc.) in the
pyCore.daeModel.__init__()
function:
One of the fundamental ideas in DAE Tools is separation of the model specification from the activities that can be carried out on that model: this way several simulation scenarios can be developed based on a single model definition. Thus, all objects are defined in two stages:
pyCore.daeModel.__init__()
functionpyActivity.daeSimulation.SetUpParametersAndDomains()
or
pyActivity.daeSimulation.SetUpVariables()
functions.Therefore, parameters, domains and variables are only declared here, while their initialisation (setting the parameter value, setting up the domain, assigning or setting an initial condition etc.) is postponed and will be done in the simulation class
All objects must be declared as data members of the model since the base pyCore.daeModel
class keeps only week references and does not own them:
def __init__(self, name, parent = None, description = ""):
self.domain = daeDomain(...)
self.parameter = daeParameter(...)
self.variable = daeVariable(...)
... etc.
and not:
def __init__(self, name, parent = None, description = ""):
domain = daeDomain(...)
parameter = daeParameter(...)
variable = daeVariable(...)
... etc.
because at the exit from the pyCore.daeModel.__init__()
function the objects
will go out of scope and get destroyed. However, the underlying c++ model object still holds
references to them which will eventually result in the segmentation fault.
Specification of the model functionality (equations, state transition networks,
and OnEvent
and OnCondition
actions)
in the pyCore.daeModel.DeclareEquations()
function.
Initialisation of the simulation object is done in several phases. At the point when this function is called by the framework the model parameters, domains, variables etc. are fully initialised. Therefore, it is safe to obtain the values of parameters or domain points and use them to create equations at the runtime.
A simplest DAE Tools model with a description of all steps/tasks necessary to develop a model can be found in the What’s the time? (AKA: Hello world!) tutorial (whats_the_time.py).
Parameters are time invariant quantities that do not change during a simulation. Usually a good choice what should be a parameter is a physical constant, number of discretisation points in a domain etc.
There are two types of parameters in DAE Tools:
The process of defining parameters is again carried out in two phases:
pyCore.daeModel.__init__()
functionpyActivity.daeSimulation.SetUpParametersAndDomains()
functionParameters are declared in the pyCore.daeModel.__init__()
function.
An ordinary parameter can be declared in the following way:
self.myParam = daeParameter("myParam", units, parentModel, "description")
Parameters can be distributed on domains. A distributed parameter can be declared in the following way:
self.myParam = daeParameter("myParam", units, parentModel, "description")
self.myParam.DistributeOnDomain(myDomain)
# Or simply:
self.myParam = daeParameter("myParam", units, parentModel, "description", [myDomain])
Parameters are initialised in the pyActivity.daeSimulation.SetUpParametersAndDomains()
function. To set a value of an ordinary parameter the following can be used:
myParam.SetValue(value)
where value can be a floating point value or the quantity object, while to set a value of distributed parameters (onedimensional for example):
for i in range(myDomain.NumberOfPoints):
myParam.SetValue(i, value)
where the value
can be either a float
(i.e. 1.34
) or the pyUnits.quantity
object
(i.e. 1.34 * W/(m*K)
). If the simple floats are used it is assumed that they
represent values with the same units as in the parameter definition.
Nota bene
DAE Tools
(as it is the case in C/C++ and Python) use zerobased arrays
in which the initial element of a sequence is assigned the index 0
, rather than 1.
In addition, all values can be set at once using:
myParam.SetValues(values)
where values
is a numpy array of floats/quantity objects.
The most commonly used functions are:
pyCore.daeParameter.__call__()
(operator ()
)
which returns the pyCore.adouble
object that holds the parameter valuepyCore.daeParameter.array()
function which returns the pyCore.adouble_array
object that holds an array of parameter valuespyCore.daeParameter.npyValues
property which
returns the parameter values as a numpy multidimensional array (with numpy.float
data type)pyCore.daeParameter.SetValue
, pyCore.daeParameter.GetValue
,
and pyCore.daeParameter.SetValues
which get/set the parameter value(s) using the float
or the pyUnits.quantity
object(s)Notate bene
The functions pyCore.daeParameter.__call__()
and pyCore.daeParameter.array()
return pyCore.adouble
and pyCore.adouble_array
objects, respectively
and does not contain values. They are only used to specify equations’ residual expressions
which are stored in the pyCore.adouble.Node()
/ pyCore.adouble_array.Node()
attributes.
Other functions (such as pyCore.daeParameter.npyValues
and pyCore.daeParameter.GetValue
)
can be used to access the values data during the simulation.
All above stands for similar functions in pyCore.daeDomain
and pyCore.daeVariable
classes.
To get a value of the ordinary parameter the pyCore.daeParameter.__call__()
function (operator ()
) can be used. For instance, if the variable myVar
has to be
equal to the sum of the parameter myParam
and 15
:
in DAE Tools it is specified in the following acausal way:
# Notation:
#  eq is a daeEquation object created using the model.CreateEquation(...) function
#  myParam is an ordinary daeParameter object (not distributed)
#  myVar is an ordinary daeVariable (not distributed)
eq.Residual = myVar()  (myParam() + 15)
To get a value of a distributed parameter the pyCore.daeParameter.__call__()
function (operator ()
) can be used again. For instance, if the distributed
variable myVar
has to be equal to the sum of the parameter myParam
and 15
at each
point of the domain myDomain
:
in DAE Tools it is specified in the following acausal way:
# Notation:
#  myDomain is daeDomain object
#  eq is a daeEquation object distributed on the myDomain
#  i is daeDistributedEquationDomainInfo object (used to iterate through the domain points)
#  myParam is daeParameter object distributed on the myDomain
#  myVar is daeVariable object distributed on the myDomain
i = eq.DistributeOnDomain(myDomain, eClosedClosed)
eq.Residual = myVar(i)  (myParam(i) + 15)
This code translates into a set of n
algebraic equations.
Obviously, a parameter can be distributed on more than one domain. In the case of two domains:
the following can be used:
# Notation:
#  myDomain1, myDomain2 are daeDomain objects
#  eq is a daeEquation object distributed on the domains myDomain1 and myDomain2
#  i1, i2 are daeDistributedEquationDomainInfo objects (used to iterate through the domain points)
#  myParam is daeParameter object distributed on the myDomain1 and myDomain2
#  myVar is daeVariable object distributed on the myDomaina and myDomain2
i1 = eq.DistributeOnDomain(myDomain1, eClosedClosed)
i2 = eq.DistributeOnDomain(myDomain2, eClosedClosed)
eq.Residual = myVar(i1,i2)  (myParam(i1,i2) + 15)
To get an array of parameter values the function pyCore.daeParameter.array()
can be used, which returns the pyCore.adouble_array
object.
The ordinary mathematical functions can be used with the pyCore.adouble_array
objects:
pyCore.Sqrt()
, pyCore.Sin()
, pyCore.Cos()
, pyCore.Min()
, pyCore.Max()
,
pyCore.Log()
, pyCore.Log10()
, etc. In addition, some additional functions are available such as
pyCore.Sum()
and pyCore.Product()
.
For instance, if the variable myVar
has to be equal to the sum of values of the parameter
myParam
for all points in the domain myDomain
, the function pyCore.Sum()
can be used.
The pyCore.daeParameter.array()
function accepts the following arguments:
*
(to select all points from a domain)[]
(to select all points from a domain)Basically all arguments listed above are internally used to create the
pyCore.daeIndexRange
object. pyCore.daeIndexRange
constructor has
three variants:
pyCore.daeDomain
object.
In this case the returned pyCore.adouble_array
object will contain the
parameter values at all points in the specified domain.pyCore.daeDomain
object and a list
of integer that represent indexes within the specified domain.
In this case the returned pyCore.adouble_array
object will contain the
parameter values at the selected points in the specified domain.pyCore.daeDomain
object, and three
integers: startIndex
, endIndex
and step
(which is basically a slice, that is
a portion of a list of indexes: start
through end1
, by the increment step
).
More info about slices can be found in the
Python documentation.
In this case the returned pyCore.adouble_array
object will contain the
parameter values at the points in the specified domain defined by the slice object.Suppose that the variable myVar
has to be equal to the sum of values in
the array values
that holds values from the parameter myParam
at the
specified indexes in the domains myDomain1
and myDomain2
:
There are several different scenarios for creating the array values
from the parameter
myParam
distributed on two domains:
# Notation:
#  myDomain1, myDomain2 are daeDomain objects
#  n1, n2 are the number of points in the myDomain1 and myDomain2 domains
#  eq1, eq2 are daeEquation objects
#  mySum is daeVariable object
#  myParam is daeParameter object distributed on myDomain1 and myDomain2 domains
#  values is the adouble_array object
# Case 1. An array contains the following values from myParam:
#  the first point in the domain myDomain1
#  all points from the domain myDomain2
# All expressions below are equivalent:
values = myParam.array(0, '*')
values = myParam.array(0, [])
eq1.Residual = mySum()  Sum(values)
# Case 2. An array contains the following values from myParam:
#  the first three points in the domain myDomain1
#  all even points from the domain myDomain2
values = myParam.array([0,1,2], slice(0, myDomain2.NumberOfPoints, 2))
eq2.Residual = mySum()  Sum(values)
The case 1.
translates into:
where n2
is the number of points in the domain myDomain2
.
The case 2.
translates into:
More information about parameters can be found in the API reference pyCore.daeParameter
and in Tutorials.
Variable types are used in DAE Tools to describe variables and they contain the following information:
pyUnits.unit
objectDeclaration of variable types is commonly done outside of the model definition (in the module scope).
A variable type can be declared in the following way:
# Temperature type with units Kelvin, limits 1001000K, the default value 273K and the absolute tolerance 1E5
typeTemperature = daeVariableType("Temperature", K, 100, 1000, 273, 1E5)
There are two types of domains in DAE Tools:
Distributed domains can form uniform grids (the default) or nonuniform grids (userspecified). In DAE Tools many objects can be distributed on domains: parameters, variables, equations, even models and ports. Distributing a model on a domain (that is in space) can be useful for modelling of complex multiscale systems where each point in the domain have a corresponding model instance. In addition, domain points values can be obtained as a numpy onedimensional array; this way DAE Tools can be easily used in conjunction with other scientific python libraries: NumPy, SciPy and many other.
Again, the domains are defined in two phases:
Domains are declared in the pyCore.daeModel.__init__()
function:
self.myDomain = daeDomain("myDomain", parentModel, units, "description")
Domains are initialised in the pyActivity.daeSimulation.SetUpParametersAndDomains()
function.
To set up a domain as a simple array the function pyCore.daeDomain.CreateArray()
can be used:
# Array of N elements
myDomain.CreateArray(N)
while to set up a domain distributed on a structured grid the function
pyCore.daeDomain.CreateStructuredGrid()
:
# Uniform structured grid with N elements and bounds [lowerBound, upperBound]
myDomain.CreateStructuredGrid(N, lowerBound, upperBound)
where the lower and upper bounds can be simple floats or quantity objects. If the simple floats are used it is assumed that they represent values with the same units as in the domain definition. Typically, it is better to use quantities to avoid mistakes with wrong units:
# Uniform structured grid with 10 elements and bounds [0,1] in centimeters:
myDomain.CreateStructuredGrid(10, 0.0 * cm, 1.0 * cm)
Nota bene
Domains with N
elements consists of N+1
points.
It is also possible to create an unstructured grid (for use in Finite Element models). However, creation and setup of such domains is an implementation detail of corresponding modules (i.e. pyDealII).
In certain situations it is not desired to have a uniform distribution
of the points within the given interval, defined by the lower and upper bounds.
In these cases, a nonuniform structured grid can be specified using the attribute
pyCore.daeDomain.Points
which contains the list of the points and that
can be manipulated by the user:
# First create a structured grid domain
myDomain.CreateStructuredGrid(10, 0.0, 1.0)
# The original 11 points are: [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
# If the system is stiff at the beginning of the domain more points can be placed there
myDomain.Points = [0.0, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.60, 1.00]
The effect of uniform and nonuniform grids is given in Fig. 6.1 (a simple heat conduction problem from the tutorial_3 has been served as a basis for comparison). Here, there are three cases:
It can be clearly observed that in this problem the more precise results are obtained by using denser grid at the beginning of the interval.
The most commonly used functions are:
pyCore.daeDomain.__call__()
(operator ()
) and
pyCore.daeDomain.__getitem__()
(operator []
)
which return the pyCore.adouble
object that holds the value of the point
at the specified index within the domain. Both functions have the same functionality.pyCore.daeDomain.array()
function which returns the pyCore.adouble_array
object that holds an array of points valuespyCore.daeDomain.npyPoints
property which returns the points in the domain
as a numpy onedimensional array (with numpy.float
data type)Nota bene
The functions pyCore.daeDomain.__call__()
, pyCore.daeDomain.__getitem__()
and pyCore.daeDomain.array()
can only be used to build equations’ residual expressions.
On the other hand, the attributes pyCore.daeDomain.Points
and pyCore.daeDomain.npyPoints
can be used to access the domain points at any point.
The arguments of the pyCore.daeDomain.array()
function are the same as explained in Using parameters.
To get a point at the specified index within the domain the pyCore.daeDomain.__getitem__()
function (operator []
) can be used. For instance, if the variable myVar
has to be
equal to the sixth point in the domain myDomain
:
the following can be used:
# Notation:
#  eq is a daeEquation object
#  myDomain is daeDomain object
#  myVar is daeVariable object
eq.Residual = myVar()  myDomain[5]
More information about domains can be found in the API reference pyCore.daeDomain
and in Tutorials.
There are various types of variables in DAE Tools. They can be:
and:
Again, variables are defined in two phases:
Variables are declared in the pyCore.daeModel.__init__()
function.
An ordinary variable can be declared in the following way:
self.myVar = daeVariable("myVar", variableType, parentModel, "description")
Variables can also be distributed on domains. A distributed variable can be declared in the following way:
self.myVar = daeVariable("myVar", variableType, parentModel, "description")
self.myVar.DistributeOnDomain(myDomain)
# Or simply:
self.myVar = daeVariable("myVar", variableType, parentModel, "description", [myDomain])
Variables are initialised in the pyActivity.daeSimulation.SetUpVariables()
function:
To assign the variable value/fix the degrees of freedom the following can be used:
myVar.AssignValue(value)
or, if the variable is distributed:
for i in range(myDomain.NumberOfPoints):
myVar.AssignValue(i, value)
# or using a numpy array of values
myVar.AssignValues(values)
where value
can be either a float
(i.e. 1.34
) or the pyUnits.quantity
object
(i.e. 1.34 * W/(m*K)
), and values
is a numpy array of floats or pyUnits.quantity
objects.
If the simple floats are used it is assumed that they represent values with the same units as in the
variable type definition.
To set an initial condition use the following:
myVar.SetInitialCondition(value)
or, if the variable is distributed:
for i in range(myDomain.NumberOfPoints):
myVar.SetInitialCondition(i, value)
# or using a numpy array of values
myVar.SetInitialConditions(values)
where the value
can again be either a float
or the pyUnits.quantity
object,
and values
is a numpy array of floats or pyUnits.quantity
objects.
If the simple floats are used it is assumed that they represent values with the same units as in the
variable type definition.
To set an absolute tolerance the following can be used:
myVar.SetAbsoluteTolerances(1E5)
To set an initial guess use the following:
myVar.SetInitialGuess(value)
or, if the variable is distributed:
for i in range(0, myDomain.NumberOfPoints):
myVar.SetInitialGuess(i, value)
# or using a numpy array of values
myVar.SetInitialGuesses(values)
where the value
can again be either a float
or the pyUnits.quantity
object
and values
is a numpy array of floats or pyUnits.quantity
objects.
The most commonly used functions are:
pyCore.daeVariable.__call__()
(operator ()
)
which returns the pyCore.adouble
object that holds the variable valuepyCore.dt()
which returns the pyCore.adouble
object
that holds the value of a time derivative of the variablepyCore.d()
and pyCore.d2()
which return
the pyCore.adouble
object that holds the value of a partial derivative of the variable
of the first and second order, respectivelypyCore.daeVariable.array()
, pyCore.dt_array()
,
pyCore.d_array()
and pyCore.d2_array()
which return the
pyCore.adouble_array
object that holds an array of variable values, time derivatives,
partial derivative of the first order and partial derivative of the second order, respectivelypyCore.daeVariable.npyValues
property which
returns the variable values as a numpy multidimensional array (with numpy.float
data type)pyCore.daeVariable.SetValue
and pyCore.daeVariable.GetValue
/
pyCore.daeVariable.GetQuantity
which get/set the variable value as float
or the pyUnits.quantity
objectpyCore.daeVariable.ReAssignValue()
, pyCore.daeVariable.ReAssignValues()
,
pyCore.daeVariable.ReSetInitialCondition()
and pyCore.daeVariable.ReSetInitialConditions()
can be used to reassign or reinitialise
variables only during a simulation (in the function pyActivity.daeSimulation.Run()
)Nota bene
The functions pyCore.daeVariable.__call__()
, pyCore.dt()
,
pyCore.d()
, pyCore.d2()
, pyCore.daeVariable.array()
,
pyCore.dt_array()
, pyCore.d_array()
and pyCore.d2_array()
can only be used to build equations’ residual expressions.
On the other hand, the functions pyCore.daeVariable.GetValue
,
pyCore.daeVariable.SetValue
and pyCore.daeVariable.npyValues
can be used
to access the variable data at any point.
All above mentioned functions are called in the same way as explained in Using parameters. More information will be given here on getting time and partial derivatives.
To get a time derivative of the ordinary variable the function pyCore.dt()
can be used. For instance, if a time derivative of the variable myVar
has to be equal
to some constant, let’s say 1.0:
the following can be used:
# Notation:
#  eq is a daeEquation object
#  myVar is an ordinary daeVariable
eq.Residual = dt(myVar())  1.0
To get a time derivative of a distributed variable the pyCore.dt()
function can be used again.
For instance, if a time derivative of the distributed variable myVar
has to be equal to some constant
at each point of the domain myDomain
:
the following can be used:
# Notation:
#  myDomain is daeDomain object
#  n is the number of points in the myDomain
#  eq is a daeEquation object distributed on the myDomain
#  d is daeDEDI object (used to iterate through the domain points)
#  myVar is daeVariable object distributed on the myDomain
d = eq.DistributeOnDomain(myDomain, eClosedClosed)
eq.Residual = dt(myVar(d))  1.0
This code translates into a set of n
equations.
Obviously, a variable can be distributed on more than one domain. To write a similar equation for a twodimensional variable:
the following can be used:
# Notation:
#  myDomain1, myDomain2 are daeDomain objects
#  n1 is the number of points in the myDomain1
#  n2 is the number of points in the myDomain2
#  eq is a daeEquation object distributed on the domains myDomain1 and myDomain2
#  d is daeDEDI object (used to iterate through the domain points)
#  myVar is daeVariable object distributed on the myDomaina and myDomain2
d1 = eq.DistributeOnDomain(myDomain1, eClosedClosed)
d2 = eq.DistributeOnDomain(myDomain2, eClosedClosed)
eq.Residual = dt(myVar(d1,d2))  1.0
This code translates into a set of n1 * n2
equations.
To get a partial derivative of a distributed variable the functions pyCore.d()
and pyCore.d2()
can be used. For instance, if a partial derivative of
the distributed variable myVar
has to be equal to 1.0 at each point of the domain myDomain
:
we can write:
# Notation:
#  myDomain is daeDomain object
#  n is the number of points in the myDomain
#  eq is a daeEquation object distributed on the myDomain
#  d is daeDEDI object (used to iterate through the domain points)
#  myVar is daeVariable object distributed on the myDomain
d = eq.DistributeOnDomain(myDomain, eClosedClosed)
eq.Residual = d(myVar(d), myDomain, discretizationMethod=eCFDM, options={})  1.0
# since the defaults are eCFDM and an empty options dictionary the above is equivalent to:
eq.Residual = d(myVar(d), myDomain)  1.0
Again, this code translates into a set of n
equations.
The default discretisation method is center finite difference method (eCFDM
) and the default
discretisation order is 2 and can be specified in the options
dictionary: options["DiscretizationOrder"] = integer
.
At the moment, only the finite difference discretisation methods are supported by default
(but the finite volume and finite elements implementations exist through the third party libraries):
eCFDM
)eBFDM
)eFFDM
)More information about variables can be found in the API reference pyCore.daeVariable
and in Tutorials.
Ports can be used to provide an interface of a model, that is the model inputs and outputs. The models with ports can be used to create flowsheets where models are interconnected by (the same type of) ports. Ports can be defined as inlet or outlet depending on whether they represent model inputs or model outputs. Like models, ports can contain domains, parameters and variables.
In DAE Tools ports are defined by deriving a new class from the base pyCore.daePort
class.
An empty port definition is presented below:
class myPort(daePort):
def __init__(self, name, parent = None, description = ""):
daePort.__init__(self, name, type, parent, description)
# Declaration/instantiation of domains, parameters and variables
...
The process consists of the following steps:
Calling the base class constructor:
daePort.__init__(self, name, type, parent, description)
Declaring domains, parameters and variables in the
pyCore.daePort.__init__()
function
The same rules apply as described in the Developing models section.
Two ports can be connected by using the pyCore.daeModel.ConnectPorts()
function.
Ports are instantiated in the pyCore.daeModel.__init__()
function:
self.myPort = daePort("myPort", eInletPort, parentModel, "description")
Event ports are also used to connect two models; however, they allow sending of discrete messages
(events) between models. Events can be triggered manually or when a specified condition
is satisfied. The main difference between event and ordinary ports is that the former allow a discrete
communication between models while latter allow a continuous exchange of information.
Messages contain a floating point value that can be used by a recipient. Upon a reception of an event
certain actions can be executed. The actions are specified in the pyCore.daeModel.ON_EVENT()
function.
Two event ports can be connected by using the pyCore.daeModel.ConnectEventPorts()
function.
A single outlet event port can be connected to unlimited number of inlet event ports.
Event ports are instantiated in the pyCore.daeModel.__init__()
function:
self.myEventPort = daeEventPort("myEventPort", eOutletPort, parentModel, "description")
There are four types of equations in DAE Tools:
Distributed equations are equations which are distributed on one or more domains and valid on the selected points within those domains. Equations can be distributed on a whole domain, on a portion of it or even on a single point (useful for specifying boundary conditions).
Equations are declared in the pyCore.daeModel.DeclareEquations()
function.
To declare an ordinary equation the pyCore.daeModel.CreateEquation()
function can be used:
eq = model.CreateEquation("MyEquation", "description")
while to declare a distributed equation:
eq = model.CreateEquation("MyEquation")
d = eq.DistributeOnDomain(myDomain, eClosedClosed)
Equations can be distributed on a whole domain or on a portion of it. Currently there are 7 options:
where \(x_0\) stands for the LowerBound and \(x_n\) stands for the UpperBound of the domain.
An overview of various bounds is given in the table below.
Assume that we have an equation which is distributed on two domains: x
and y
.
The table shows various options while distributing an equation. Green squares
represent portions of a domain included in the distributed equation, while
white squares represent excluded portions.
Equations in DAE Tools are given in implicit (acausal) form and specified as residual expressions. For instance, to define a residual expression of an ordinary equation:
the following can be used:
# Notation:
#  V1, V3, V14 are ordinary variables
eq.Residal = dt(V14()) + V1() / (V14() + 2.5) + sin(3.14 * V3())
To define a residual expression of a distributed equation:
the following can be used:
# Notation:
#  V1 is an ordinary variable
#  V3 and V14 are variables distributed on domains x and y
eq = model.CreateEquation("MyEquation")
dx = eq.DistributeOnDomain(x, eClosedClosed)
dy = eq.DistributeOnDomain(y, eOpenOpen)
eq.Residal = dt(V14(dx,dy)) + V1() / ( V14(dx,dy) + 2.5) + sin(3.14 * V3(dx,dy) )
where dx
and dy
are pyCore.daeDEDI
(which is short for
daeDistributedEquationDomainInfo
) objects. These objects are used internally by the framework
to iterate over the domain points when generating a set of equations from a distributed equation.
If a pyCore.daeDEDI
object is used as an argument of the operator ()
, dt
,
d
, d2
, array
, dt_array
, d_array
, or d2_array
functions, it represents a
current index in the domain which is being iterated. Hence, the equation above is equivalent to writing:
# Notation:
#  V1 is an ordinary variable
#  V3 and V14 are variables distributed on domains x and y
for dx in range(0, x.NumberOfPoints): # x: [x0, xn]
for dy in range(1, y.NumberOfPoints1): # y: (y0, yn)
eq = model.CreateEquation("MyEquation_%d_%d" % (dx, dy) )
eq.Residal = dt(V14(dx,dy)) + V1() / ( V14(dx,dy) + 2.5) + sin(3.14 * V3(dx,dy) )
The second way can be used for writing equations that are different for different points within domains.
pyCore.daeDEDI
class has the pyCore.daeDEDI.__call__
(operator ()
) function defined
which returns the current index as the adouble
object. In addition, the class provides operators +
and 
which can be used to return the current index offset by the specified integer.
For instance, to define the equation below:
the following can be used:
# Notation:
#  V1 and V2 are variables distributed on the x domain
eq = model.CreateEquation("MyEquation")
dx = eq.DistributeOnDomain(x, eClosedOpen)
eq.Residal = V1(dx)  ( V2(dx) + V2(dx+1) )
DAE Tools support five basic mathematical operations (+, , *, /, **
) and the following
standard mathematical functions: sqrt, pow, log, log10, exp, min, max, floor, ceil, abs,
sin, cos, tan, asin, acos, atan, sinh, cosh, tanh, asinh, acosh, atanh, atan2, erf
).
To define conditions the following comparison operators:
<
(less than),
<=
(less than or equal),
==
(equal),
!=
(not equal),
>
(greater),
>=
(greater than or equal)
and the following logical operators:
&
(logical AND),

(logical OR),
~
(logical NOT)
can be used.
Nota bene
Since it is not allowed to overload Python’s operators and
, or
and not
they
cannot be used to define logical conditions; therefore, the custom operators &
, 
and ~
are defined
and should be used instead.
The adouble
class is designed with numpy
library in mind.
It redefines all standard mathematical functions available in numpy
(i.e. sqrt
, exp
, log
, log10
, sin
, cos
etc.) so that the numpy
functions also operate on the
adouble
objects. Therefore, the adouble
class can be used as an ordinary
data type in numpy
.
In addition, numpy
and DAE Tools mathematical functions are interchangeable.
In the example given below, the Exp()
and numpy.exp()
function calls produce identical results:
# Notation:
#  Var is an ordinary variable
#  result is an ordinary variable
eq = self.CreateEquation("...")
eq.Residual = result()  numpy.exp( Var() )
# The above is identical to:
eq.Residual = result()  Exp( Var() )
Often, it is desired to apply numpy
/scipy
numerical functions on arrays of adouble
objects.
In those cases the functions such as array()
, d_array()
,
dt_array()
, Array()
etc.
are NOT applicable since they return adouble_array
objects.
However, numpy
arrays can be created and populated with adouble
objects and numpy
functions
applied on them. In addition, an adouble_array
object can be created from resulting numpy
arrays
of adouble
objects, if necessary.
For instance, to define the equation below:
the following code can be used:
# Notation:
#  x is a continuous domain
#  V1 is a variable distributed on the x domain
#  V2 is a variable distributed on the x domain
#  sum is an ordinary variable
#  ndarr_V1 is one dimensional numpy array with dtype=object
#  ndarr_V2 is one dimensional numpy array with dtype=object
#  adarr_V1 is adouble_array object
#  Nx is the number of points in the domain x
# 1.Create empty numpy arrays as a container for daetools adouble objects
ndarr_V1 = numpy.empty(Nx, dtype=object)
ndarr_V2 = numpy.empty(Nx, dtype=object)
# 2. Fill the created numpy arrays with adouble objects
ndarr_V1[:] = [V1(x) for x in range(Nx)]
ndarr_V2[:] = [V2(x) for x in range(Nx)]
# Now, ndarr_V1 and ndarr_V2 represent arrays of Nx adouble objects each:
# ndarr_V1 := [V1(0), V1(1), V1(2), ..., V1(Nx1)]
# ndarr_V2 := [V2(0), V2(1), V2(2), ..., V2(Nx1)]
# 3. Create an equation using the common numpy/scipy functions/operators
eq = self.CreateEquation("sum")
eq.Residual = sum()  numpy.sum(ndarr_V1 + 2*ndarr_V2**2)
# If adouble_aray is needed after operations on a numpy array, the following two functions can be used:
# a) static function adouble_array.FromList(python_list)
# b) static function adouble_array.FromNumpyArray(numpy_array)
# Both return an adouble_array object.
adarr_V1 = adouble_array.FromNumpyArray(ndarr_V1)
print(adarr_V1)
To calculate a residual and its gradients (which represent a single row in the Jacobian matrix)
DAE Tools combine the
operator overloading
technique for automatic differentiation
(adopted from ADOLC library) using the concept of representing
equations as evaluation trees.
Evaluation trees consist of binary or unary nodes, each node representing a basic mathematical
operation or the standard mathematical function.
The basic mathematical operations and functions are redefined to operate on a heavily
modified ADOLC class adouble
(which has been extended to contain information about
domains/parameters/variables etc). In addition, a new adouble_array
class has been
introduced to support all abovementioned operations on arrays.
What is different here is that adouble
/adouble_array
classes
and mathematical operators/functions work in two modes; they can either buildup an evaluation tree
or calculate a value/derivative of an expression.
Once built, the evaluation trees can be used to calculate equation residuals or derivatives to fill
a Jacobian matrix necessary for a Newtontype iteration.
A typical evaluation tree is presented in the Fig. 6.2 below.
The equation F
in Fig. 6.2 is a result of the following DAE Tools equation:
eq = model.CreateEquation("F", "F description")
eq.Residal = dt(V14()) + V1() / (V14() + 2.5) + Sin(3.14 * V3())
As it has been described in the previous sections, domains, parameters, and variables contain functions
that return adouble
/adouble_array
objects used to construct the
evaluation trees. These functions include functions to get a value of
a domain/parameter/variable (operator ()
), to get a time or a partial derivative of a variable
(functions dt()
, d()
, or d2()
)
or functions to obtain an array of values, time or partial derivatives (array()
,
dt_array()
, d_array()
, and d2_array()
).
Another useful feature of DAE Tools equations is that they can be exported into MathML or Latex format and easily visualised.
Assume that a simple heat transfer needs to be modelled: heat conduction through a very thin rectangular plate. At one side (at y = 0) we have a constant temperature (500 K) while at the opposite end we have a constant flux (1E6 W/m2). The problem can be described by a single distributed equation:
# Notation:
#  T is a variable distributed on x and y domains
#  rho, k, and cp are parameters
eq = model.CreateEquation("MyEquation")
dx = eq.DistributeOnDomain(x, eClosedClosed)
dy = eq.DistributeOnDomain(y, eOpenOpen)
eq.Residual = rho() * cp() * dt(T(dx,dy))  k() * ( d2(T(dx,dy), x) + d2(T(dx,dy), y) )
The equation is defined on the y
domain open on both ends; thus, the additional equations
(boundary conditions at y = 0
and y = ny
points) need to be specified to make the system well posed:
To do so, the following equations can be used:
# "Bottom edge" boundary conditions:
bceq = model.CreateEquation("Bottom_BC")
dx = bceq.DistributeOnDomain(x, eClosedClosed)
dy = bceq.DistributeOnDomain(y, eLowerBound)
bceq.Residal = T(dx,dy)  Constant(500 * K) # Constant temperature (500 K)
# "Top edge" boundary conditions:
bceq = model.CreateEquation("Top_BC")
dx = bceq.DistributeOnDomain(x, eClosedClosed)
dy = bceq.DistributeOnDomain(y, eUpperBound)
bceq.Residal =  k() * d(T(dx,dy), y)  Constant(1E6 * W/m**2) # Constant flux (1E6 W/m2)
DAE Tools support numerical simulation of partial differential equations on unstructured grids using the Finite Elements method. Currently, DAE Tools use the deal.II library for lowlevel tasks such as mesh loading, assembly of the system stiffness and mass matrices and the system load vector, management of boundary conditions, quadrature formulas, management of degrees of freedom, and the generation of the results. After an initial assembly phase the matrices are used to generate DAE Tools equations which are solved together with the rest of the system. All details about the mesh, basis functions, quadrature rules, refinement etc. are handled by the deal.II library and can be to some extent configured by the modeller.
The unique feature of this approach is a capability to use DAE Tools variables to specify boundary conditions, time varying coefficients and nonlinear terms, and evaluate quantities such as surface/volume integrals. This way, the finite element model is fully integrated with the rest of the model and multiple FE systems can be created and coupled together. In addition, nonlinear finite element systems are automatically supported and the equation discontinuities can be handled as usual in DAE Tools.
DAE Tools provide four main classes to support the deal.II library:
dealiiFiniteElementDOF_nD
In deal.II it represents a degree of freedom distributed on a finite element domain. In DAE Tools it represents a variable distributed on a finite element domain.
dealiiFiniteElementSystem_nD
(implements daeFiniteElementObject
)
It is a wrapper around deal.II FESystem<dim>
class and handles all finite element related details.
It uses information about the mesh, quadrature and face quadrature formulas, degrees of freedom
and the FE weak formulation to assemble the system’s mass matrix (Mij), stiffness matrix (Aij)
and the load vector (Fi).
dealiiFiniteElementWeakForm_nD
Contains weak form expressions for the contribution of FE cells to the system/stiffness matrices, the load vector, boundary conditions and (optionally) surface/volume integrals (as an output).
daeModel
derived class that use system matrices/vectors from the dealiiFiniteElementSystem_nD
object to generate a system of equations in the following form:
This system is in a general case a DAE system, although it can also be a linear or a nonlinear system (if the mass matrix is zero).
A typical usecase scenario consists of the following steps:
dealiiFiniteElementSystem_nD
class
(where nD
can be 1D
, 2D
or 3D
).
That includes specification of:dealiiFiniteElementDOF_nD
objects).
Every dof has a name which will be also used to declare DAE Tools variable with the same name,
description, finite element space FE (deal.II FiniteElement<dim>
instance) and the multiplicitydaeFiniteElementModel
object (similarly to the ordinary DAE Tools model)
with the finite element system object as the last argument.phi()
: corresponds to shape_value
in deal.IIdphi()
: corresponds to shape_grad
in deal.IId2phi()
: corresponds to shape_hessian
in deal.IIphi_vector()
: corresponds to shape_value
of vector dofs in deal.IIdphi_vector()
: corresponds to shape_grad
of vector dofs in deal.IId2phi_vector()
: corresponds to shape_hessian
of vector dofs in deal.IIdiv_phi()
: corresponds to divergence in deal.IIJxW()
: corresponds to the mapped quadrature weight in deal.IIxyz()
: returns the point for the specified quadrature point in deal.IInormal()
: corresponds to the normal_vector
in deal.IIfunction_value()
: wraps Function<dim>
object that returns a valuefunction_gradient()
: wraps Function<dim>
object that returns a gradientfunction_adouble_value()
: wraps Function<dim>
object that returns adouble valuefunction_adouble_gradient()
: wraps Function<dim>
object that returns adouble gradientdof()
: returns daetools variable at the given index (adouble object)dof_approximation()
: returns FE approximation of a quantity as a daetools variable (adouble object)dof_gradient_approximation()
: returns FE gradient approximation of a quantity as a daetools variable (adouble object)dof_hessian_approximation()
: returns FE hessian approximation of a quantity as a daetools variable (adouble object)vector_dof_approximation()
: returns FE approximation of a vector quantity as a daetools variable (adouble object)vector_dof_gradient_approximation()
: returns FE approximation of a vector quantity as a daetools variable (adouble object)adouble()
: wraps any daetools expression to be used in matrix assemblytensor1()
: wraps deal.II Tensor<rank=1>
tensor2()
: wraps deal.II Tensor<rank=2>
tensor2()
: wraps deal.II Tensor<rank=3>
More information about the finite element method in DAE Tools can be found in the API reference and in Finite Element Tutorials (in particular the Tutorial deal.II 1 source code).
Discontinuous equations are equations that take different forms subject to certain conditions. For example, to model a flow through a pipe one can observe three different flow regimes:
From any of these three states the system can go to any other state.
This type of discontinuities is called a reversible discontinuity and can be described using
IF()
, ELSE_IF()
, ELSE()
and END_IF()
functions:
IF(Re() <= 2100) # (Laminar flow)
#... (equations go here)
ELSE_IF(Re() > 2100 & Re() < 10000) # (Transient flow)
#... (equations go here)
ELSE() # (Turbulent flow)
#... (equations go here)
END_IF()
The comparison operators operate on pyCore.adouble
objects and Float
values.
Units consistency is strictly checked and expressions including Float
values
are allowed only if a variable or parameter is dimensionless.
The following expressions are valid:
# Notation:
#  T is a variable with units: K
#  m is a variable with units: kg
#  p is a dimensionless parameter
# T < 0.5 K
T() < Constant(0.5 * K)
# (T >= 300 K) or (m < 1 kg)
(T() >= Constant(300 * K))  (m < Constant(0.5 * kg))
# p <= 25.3 (use of the Constant function not necessary)
p() <= 25.3
Reversible discontinuities can be symmetrical and nonsymmetrical. The above example is symmetrical. However, to model a CPU and its power dissipation one can observe three operating modes with the following state transitions:
What can be seen is that from the Normal mode the system can either go to the Power saving mode or to the Fried mode.
The same stands for the Power saving mode: the system can either go to the Normal mode or to the Fried mode.
However, once the temperature exceeds 110 degrees the CPU dies (let’s say it is heavily overclocked) and there is no return.
This type of discontinuities is called an irreversible discontinuity and can be described
using STN()
, STATE()
, END_STN()
functions:
STN("CPU")
STATE("Normal")
#... (equations go here)
ON_CONDITION( CPULoad() < 0.05, switchToStates = [ ("CPU", "PowerSaving") ] )
ON_CONDITION( T() > Constant(110*K), switchToStates = [ ("CPU", "Fried") ] )
STATE("PowerSaving")
#... (equations go here)
ON_CONDITION( CPULoad() >= 0.05, switchToStates = [ ("CPU", "Normal") ] )
ON_CONDITION( T() > Constant(110*K), switchToStates = [ ("CPU", "Fried") ] )
STATE("Fried")
#... (equations go here)
END_STN()
The function pyCore.daeModel.ON_CONDITION()
is used to define actions to be performed
when the specified condition is satisfied. In addition, the function pyCore.daeModel.ON_EVENT()
can be used to define actions to be performed when an event is trigerred on a specified event port.
Details on how to use pyCore.daeModel.ON_CONDITION()
and pyCore.daeModel.ON_EVENT()
functions can be found in the OnCondition actions and OnEvent actions sections, respectively.
More information about state transition networks can be found in pyCore.daeSTN
,
pyCore.daeIF
and in Tutorials.
The function ON_CONDITION()
can be used to define actions to be performed
when a specified condition is satisfied. The available actions include:
switchToStates
)setVariableValues
)triggerEvents
)userDefinedActions
)Nota bene
OnCondition actions can be added to models or to states in State Transition Networks
(pyCore.daeSTN
or pyCore.daeIF
):
Nota bene
switchToStates
, setVariableValues
, triggerEvents
and userDefinedActions
are empty by default. The user has to specify at least one action.
For instance, to execute some actions when the temperature becomes greater than 340 K the following can be used:
def DeclareEquations(self):
...
self.ON_CONDITION( T() > Constant(340*K), switchToStates = [ ('STN', 'State'), ... ],
setVariableValues = [ (variable, newValue), ... ],
triggerEvents = [ (eventPort, eventMessage), ... ],
userDefinedActions = [ userDefinedAction, ... ] )
where the first argument of the ON_CONDITION()
function is a condition
specifying when the actions will be executed and:
switchToStates
is a list of tuples (string ‘STN Name’, string ‘State name to become active’)setVariableValues
is a list of tuples (daeVariable
object, adouble
object)triggerEvents
is a list of tuples (daeEventPort
object, adouble
object)userDefinedActions
is a list of user defined objects derived from the base daeAction
classFor more details on how to use ON_CONDITION()
function have a look
on tutorial_13.
The function ON_EVENT()
can be used to define actions to be performed
when an event is triggered on the specified event port. The available actions are the same as
in the ON_CONDITION()
function.
Nota bene
OnEvent actions can be added to models or to states in State Transition Networks
(pyCore.daeSTN
or pyCore.daeIF
):
Nota bene
switchToStates
, setVariableValues
, triggerEvents
and userDefinedActions
are empty by default. The user has to specify at least one action.
For instance, to execute some actions when an event is triggered on an event port the following can be used:
def DeclareEquations(self):
...
self.ON_EVENT( eventPort, switchToStates = [ ('STN', 'State'), ... ],
setVariableValues = [ (variable, newValue), ... ],
triggerEvents = [ (eventPort, eventMessage), ... ],
userDefinedActions = [ userDefinedAction, ... ] )
where the first argument of the ON_EVENT()
function is the
daeEventPort
object to be monitored for events, while the rest of the arguments
is the same as in the ON_CONDITION()
function.
For more details on how to use ON_EVENT()
function have a look
on tutorial_13.
Various options used by DAE Tools objects are located in the daetools/daetools.cfg
config file (in JSON format).
The configuration file can be obtained using the global function daeGetConfig()
:
cfg = daeGetConfig()
which returns the daeConfig
object.
The configuration file is first searched in the HOME
directory, the application folder and finally in the default location.
It also can be specified manually using the function daeSetConfigFile()
.
However, this has to be done before the DAE Tools objects are created.
The current configuration file name can be retrieved using the ConfigFileName
attribute.
The options can also be programmatically changed using the Get
/Set
functions i.e.
GetBoolean()
/SetBoolean()
:
cfg = daeGetConfig()
checkUnitsConsistency = cfg.GetBoolean("daetools.core.checkUnitsConsistency")
cfg.SetBoolean("daetools.core.checkUnitsConsistency", True)
Supported data types are: Boolean
, Integer
, Float
and String
.
The whole configuration file with all options can be printed using:
cfg = daeGetConfig()
print(cfg)
The sample configuration file is given below:
{
"daetools":
{
"core":
{
"checkForInfiniteNumbers": false,
"eventTolerance": 1E7,
"logIndent": " ",
"pythonIndent": " ",
"checkUnitsConsistency": true,
"resetLAMatrixAfterDiscontinuity": true,
"printInfo": false,
"deepCopyClonedNodes": true
},
"activity":
{
"timeHorizon": 100.0,
"reportingInterval": 1.0,
"printHeader": true,
"objFunctionAbsoluteTolerance": 1E8,
"constraintsAbsoluteTolerance": 1E8,
"measuredVariableAbsoluteTolerance": 1E8
},
"datareporting":
{
"tcpipDataReceiverAddress": "127.0.0.1",
"tcpipDataReceiverPort": 50000
},
"logging":
{
"tcpipLogAddress": "127.0.0.1",
"tcpipLogPort": 51000
},
"minlpsolver":
{
"printInfo": false
},
"IDAS":
{
"relativeTolerance": 1E5,
"nextTimeAfterReinitialization": 1E7,
"printInfo": false,
"numberOfSTNRebuildsDuringInitialization": 1000,
"SensitivitySolutionMethod": "Staggered",
"SensErrCon": false,
"maxNonlinIters": 3,
"sensRelativeTolerance": 1E5,
"sensAbsoluteTolerance": 1E5,
"MaxOrd": 5,
"MaxNumSteps": 1000,
"InitStep": 0.0,
"MaxStep": 0.0,
"MaxErrTestFails": 10,
"MaxNonlinIters": 4,
"MaxConvFails": 10,
"NonlinConvCoef": 0.33,
"SuppressAlg": false,
"NoInactiveRootWarn": false,
"NonlinConvCoefIC": 0.0033,
"MaxNumStepsIC": 5,
"MaxNumJacsIC": 4,
"MaxNumItersIC": 10,
"LineSearchOffIC": false
},
"superlu":
{
"factorizationMethod": "SamePattern_SameRowPerm",
"useUserSuppliedWorkSpace": false,
"workspaceSizeMultiplier": 3.0,
"workspaceMemoryIncrement": 1.5
},
"BONMIN":
{
"IPOPT":
{
"print_level": 0,
"tol":1E5,
"linear_solver": "mumps",
"hessianApproximation": "limitedmemory",
"mu_strategy": "adaptive"
}
},
"NLOPT":
{
"printInfo": true,
"xtol_rel": 1E6,
"xtol_abs": 1E6,
"ftol_rel": 1E6,
"ftol_abs": 1E6,
"constr_tol": 1E6
}
}
}
There are three classes in the framework: base_unit
, unit
and
quantity
.
The base_unit
class handles seven SI base dimensions: length
, mass
, time
,
electric current
, temperature
, amount of substance
, and luminous intensity
(m
, kg
, s
, A
, K
, mol
, cd
).
The unit
class operates on base units defined using the base seven dimensions.
The quantity
class defines a numerical value in terms of a unit of measurement
(it contains the value and its units).
There is a large pool of base units
and units
defined (all base and derived SI units) in the
pyUnits
module:
and all above with 20 SI prefixes:
for instance: kmol (kilo mol), MW (mega Watt), ug (micro gram) etc.
New units can be defined in the following way:
rho = unit({"kg":1, "dm":3})
The constructor accepts a dictionary of base_unit : exponent
items as its argument.
The above defines a new density unit \(\frac{kg}{dm^3}\).
The unit class defines mathematical operators *
, /
and **
to allow creation of derived units.
Thus, the density unit can be also defined in the following way:
mass = unit({"kg" : 1})
volume = unit({"dm" : 3})
rho = mass / volume
Quantities are created by multiplying a value with desired units:
heat = 1.5 * J
The quantity
class defines all mathematical operators (+, , *, / and **
) and mathematical functions.
heat = 1.5 * J
time = 12 * s
power = heat / time
Unitsconsistency of equations and logical conditions is strictly enforced (although it can be switched off, if required). For instance, the operation below is not allowed:
power = heat + time
since their units are not consistent (J + s
).
There is a large number of available data reporters in DAE Tools:
daeMatlabMATFileDataReporter
)daeExcelFileDataReporter
)daeJSONFileDataReporter
)daeXMLFileDataReporter
)daeHDF5FileDataReporter
)daeVTKFileDataReporter
)daeNoOpDataReporter
)daeBlackHoleDataReporter
)daePandasDataReporter
)daePlotDataReporter
)daeDelegateDataReporter
)daeDataReporterLocal
(stores results internally; can be used for any type of processing)daeDataReporterFile
(saves the results into a file in the WriteDataToFile()
virtual member function)The best starting point in creating custom data reporters is daeDataReporterLocal
class.
It internally does all the processing and offers to users the Process
property (daeDataReceiverProcess
instance) which contains all domains, parameters and variables in the simulation.
The following functions have to be implemented (overloaded):
Connect()
:
Connects the data reporter. In the case when the local data reporter is used
it may contain a file name, for instance.Disconnect()
:
Disconnects the data reporter.IsConnected()
:
Checks if the data reporter is connected or not.All functions must return True
if successful or False
otherwise.
An empty custom data reporter is presented below:
class MyDataReporter(daeDataReporterLocal):
def __init__(self):
daeDataReporterLocal.__init__(self)
def Connect(self, ConnectString, ProcessName):
...
return True
def Disconnect(self):
...
return True
def IsConnected(self):
...
return True
To write the results into a file the daeDataReporterFile
base class can be used.
It writes the data into a file in the WriteDataToFile()
function
called in the Disconnect()
function.
The only function that needs to be overloaded is WriteDataToFile()
while the base class handles all other operations.
daeDataReceiverProcess
class contains the following properties that can be used
to obtain the results data from a data reporter:
Domains
(list of daeDataReceiverDomain
objects)Variables
(list of daeDataReceiverVariable
objects)dictDomains
(dictionary {“domain_name” : daeDataReceiverDomain
object})dictVariables
(dictionary {“variable_name” : daeDataReceiverVariable
object})dictVariableValues
(dictionary {“variable_name” : (ndarray_values, ndarray_time_points, domain_names, units)})
where ndarray_values
is numpy array with values at every time point, ndarray_time_points
is numpy array of time points,
domains
is a list of domain points for every domain on which the variable is distributed, and units
is string with the units.The example below shows how to save the results to the Matlab .mat file:
class MyDataReporter(daeDataReporterFile):
def __init__(self):
daeDataReporterFile.__init__(self)
def WriteDataToFile(self):
mdict = {}
for var in self.Process.Variables:
mdict[var.Name] = var.Values
import scipy.io
scipy.io.savemat(self.ConnectString,
mdict,
appendmat=False,
format='5',
long_field_names=False,
do_compression=False,
oned_as='row')
The filename is provided as the first argument (connectString
) of the
Connect()
function.