Beatbox is a program that could be compiled for sequential or parallel (MPI) use. Currently it has a small collection of cell models, including FitzHugh-Nagumo, Luo-Rudy I, Courtemanche et al. human atrial (this list will be expanding) and solves reaction diffusion equations with these cell models to simulate wave propagation in idealised and realistic cardiac tissue models. Currently the solvers use finite differencing with regular spatial grid and forward Euler in time with or without operator splitting. Some semi-implicit solvers are now in test exploitation and bidomain solvers are under development.
![]() |
A special feature of Beatbox is its flexibility in setting various
experimental protocols, without the need to recompile the package. A
simulation is set up by constructing an input script that spatial
model (1D, 2D, box, or geometry) details, and "devices", which perform
computations, input/output, and control functions. The simulation
script is typically called filename.bbs
, where the
conventional extension bbs
stands for "beatbox script",
and filename
can be anything you like. The "devices" are
chained together in a ring, with an integer counter t
,
usually corresponding to the time steps in the simulation. The concept
of the ring of devices is illustrated in Figure 1.
In this example,
k_func
k_func
at the
start of the computations, ppmout
every so often, and
stop
in the end.
k_func
diff
euler
ppmout
stop
Each device has a set of parameters, specific for it, e.g. the list of
parameters for k_func
is different from that for
diff
(but there are some "universal" and some "typical"
parameters, such as their control variables or the mesh domain on
which the device operates). A simulation run can use more than one
instance of the given device, each of which may appear anywhere in the
ring; in this example k_func
has two instances appearing
one after the other. All instances of the same device will have the same list of
parameters, but the values of parameters are set completely indepently
in different instances.
The function of the input script is to define the computational grid and to describe all the devices for the given run together with their parameters.
A simulation will thus typically involve:
In this distribution, there are some example scripts provided, which illustrate the format of the input scripts and use of the devices. A more formal description of the bbs script language and of the devices available follow below. A certain stage of Beatbox development is reflected in Ross McFarlane, High-Performance Computing for Computational Biology of the Heart, PhD Dissertation, October 2010, see Chapters 2 and 3 in particular.
The developers tried to make Beatbox self-contained and reduce the number of dependencies to an absolute minimum. You will require:
The recommended way to obtain BeatBox is to download the most recent official release from the BeatBox home page,
An alternative which may be more appropriate for beta-testers, is to obtain the most recent version of BeatBox code from its SVN repository, say by issuing the following commands:
mkdir ~/beatbox cd ~/beatbox svn co --username=anonymous --password=beatbox https://beatbox-trac.epcc.ed.ac.uk/svn/trunk/ ./
Note that this will be a read-only copy of the repository, i.e. you will not be able to check any modifications back in. If you would like to become a developer please get in touch with the project team.
Assuming that all the components mentioned above are available and in place, the following sequence of commannds
autoreconf -fi export CC=mpicc ./configure CFLAGS="-g -O0" --prefix=$HOME make make install
will compile two versions of the program, Beatbox
(parallel) and Beatbox_SEQ
(sequential), and install them
in $HOME/bin/
. A shell script
bbx_compile_local.sh
is provided in the root distribution
directory, to facilitate the
installation.
You may wish to change the install location specified in this file:
current setting --prefix=$HOME
means that the binaries
will be installed in $HOME/bin/
. This is reasonable in
the assumption that that directory exists and is in your
$PATH
; modify it as appropriate if you prefer to keep
your binaries elsewhere.
The options CFLAGS="-g -O0"
mean that the code will be
prepared for debugging and not optimized; for "production runs" this
can be omitted and replaced by CFLAGS="-O3"
.
If the script, configure
or Makefile
cannot
find the include and library files, the paths may have to be
explicitly incorporated into src/Makefile.am
and then go
through the whole procedure starting from the autoreconf
-fi
step.
HECToR (www.hector.ac.uk) is the
national UK supercomputer service. HECToR is a Cray XE6 based
system. In order to install on HECToR, we need to cross compile for
back end nodes and make correct settings,
so the process is a little bit different
(script bbx_compile_hector.sh
):
# Create the configure and install scripts autoreconf -fi # Load the PGI compiler module module load pgi # Tell the configure command what compiler we need to use. export CC=cc # Need to explicitly tell the Cray system what X libraries to use export LIBS="-lm -lX11 -lxcb -lxcb-xlib -ldl -lXau" # Run configure - note that it will install in $HOME/bin ./configure --prefix=$HOME # Now make the code make # Install the code to the directories specified by the prefix above. make install
Again, change the
prefix
flag if you would like it to install
elsewhere.
The sequential version of the program may be run by using:
Beatbox_SEQ [<options>] [--] <input_script> [<arguments> | <options> | -- ]
That is: the executable name (with the full path if necessary),
followed by the input
script and the arguments, if any, with options interpspersed anywhere after
the executable name. Similar to many Unix programs, options are those
words that start with a minus '-
', but a double minus
'--
' signals an end of all options so a word starting
with -
after that is interpreted as input script name or
argument.
Correspondingly, assuming that you have mpich2, the parallel version on a local computer may be run using
mpirun -np <num_procs> Beatbox [<options>] [--] <input_script> [<arguments> | <options> | -- ]
The parsing of the input script and the simulations are commented by printing messages to the standard output and duplicating them into a log file.
The possible options are:
-append
: append to the log file. The default is to
overwrite it-debug <filename>
: this produces a debug
file. If <filename>
is stdout
or stderr
, the debugging information is printed to
standard output or standard error rather than a disk file of that
name. The default is no debug printouts.
-log <filename>
: this set the name for the log
file. The default is the name of the input script, stripped of the
extension .bbs
if there is one, and appended
extension .log
. -mute
: no default output to stdout. The default is
to copy a (possibly shorter) version of the log file information to
the standard output.-nograph
: do not use run-time graphics. The default
is to use it if there is at least one device that has something to
show. This is only effective with the sequential version as the
run-time graphics are not yet implemented for the parallel
version.-profile
: measure timing for each device in the ring
and output it in the end. The default is not to measure and not to
output that information.-verbose
: this makes messages in the log file more
verbose. The default brief output does not include e.g. the default
values of the device parameters. CAVEAT: options -debug
and -log
gobble the next word for the name of the
debug or log file; failure to appreciate that is a common error leading to
weird behaviour. This should be addressed one day, perhaps by making
them like -debug=<filename>
instead.
The arguments can be used in the input script, where they appear as pre-defined string macros, see below. The parsing is usual for the unix shells, that is each word makes a separate argument, except when quotes are used.
This subsection gives a brief description on how to conduct simulations on HECToR or a similar HPC facility. Before starting any simulations, a certain familiarity with HECToR architecture, compilers, modules, and general terms of usage can be gained from the HECToR User Guide.
On HECToR compilation is done through a set of compilation wrappers,
cc
will always correspond to the actual compilation
suite being used. The default version is for the Cray
compilers. Other compiler suites may be used (by loading and unloading
the appropriate modules), but will still use the same wrapper. The
compilation is done for the work nodes - thus we are cross compiling.
HECToR manages production as well as (parallel and serial) debugging
runs by using the
PBSpro queuing system. A PBS script consists of a set of PBS
commands, and other generic shell commands. A sample PBS script that
can be used to run Beatbox jobs is shown below. The commands are
explained in the comments (text following a ##
on any
line) - a line that starts with #PBS
denotes a PBS
command. The following script was used to run the 3D atrium simulation
and is called Beatboxrun64.sh
. Its contents are:
#!/bin/bash --login ## A PBS command starts with #PBS. ## Start by specifying a name for the job. #PBS -N Beatbox64 ## Specify number of cores requested. #PBS -l mppwidth=64 ## Hector is composed of "nodes", each of which consist of ## two 16 cores processors - thus one has 32 cores available in a node. ## The number of cores per node controls memory available to the job. ## Use of all cores in a node (assuming mppwidth is more than 32) ## optimises the use AU charged to your account. #PBS -l mppnppn=32 ## This is the amount of time requested for the simulation. #PBS -l walltime=3:00:00 ## A project code has to be provided, otherwise the job is usually ## not accepted by a queue. #PBS -A e203 ## Assume this PBS script lies in the same directory as the executable ## file. Note to be seen by the back end nodes this must lie in one of ## the work directories. Change directories to where the job was launched. ## You must make sure that you copied the Beatbox executable, the ## humanAtrium.bbs and humanAtrium.bbg scripts to this directory for this ## to work. cd $PBS_O_WORKDIR ## Run the job ## n is the total number of processes ## N is the number of processes per node ## Launch the parallel job using aprun. The stdout is called ## Beatbox64.o_job_number, and stderr is called Beatbox64.e_job_number aprun -n 64 -N 32 ./Beatbox humanAtrium_start_crn.bbs
Before submitting any job using such a submission script, it may be
worthwhile to run a check on the script using the HECToR provided
checkScript
:
checkScript Beatbox64.sh
This will indicate if there are any errors in the script so you can fix these before you submit it to the PBS system. The production job is then submitted to a parallel queue using:
qsub Beatbox64.sh
Further options are also available, and can be seen in the user guide. Similarly to production, parallel debugging jobs must also be submitted to the queue with the following command added:
#PBS -v DISPLAY
and the appropriate debugger binary name preceding
the aprun
. For example, the TotalView
parallel debugger can be invoked by:
totalview aprun -a -b -a xt -n 64 -N 32 /work/.../myprog.x
The detailed submission script for debugging jobs can be found on the relevant HECToR pages.
The status of submitted jobs can be checked using the command:
qstat -u your_hector_user_name
and the job is deleted using:
qdel job_number
A Beatbox simulation is defined by the user in the form of an input script ("Beatbox script" or "bbs script"). A script is a plain text file that specifies the computational grid, the devices that are to be used to do the calculations and input/output, and their parameters. Any script should be able to run on any of Beatbox’s supported hardware platforms; if a sequential-only device is used in a script submitted to parallel execution, the warning message will be output but the script will run nonetheless.
This section introduces Beatbox scripts by first describing the scripting language, before discussing common applications. Unlike interpreted scripting languages such as PHP or Python, Beatbox scripts are not run during the simulation. The script is read once to build the simulation, after which the script code does no longer define the flow of execution, so can be modified or removed with no consequences for the current run.
Similar to other programming languages, Beatbox scripts allow the user to define variables and macros, include external code and make calls to the operating system. The Beatbox scripting language also provides a small library of arithmetic and logical functions. The user may also improve the legibility of their code, or disable portions of the code using comments. Each of these features is discussed below.
If you prefer to learn by example rather than go through formal definitions, you may wish to try and jump ahead to a simple working example and then only if and when necessary go back to check out those formal definitions, or go forward to further, more sophisticated examples.
Any Beatbox run operates with two sorts of data. One is the
computational grid, which is effectively a four-dimensional array of
real numbers (of the precision specified at compile time), which is
the object of operation of the computational devices. The four
dimensions are the three spatial dimensions x,y,z
and the
"component" dimension v
. Slices in the component
dimension are called "layers".
Associated with the computational grid could be the geometry array (if complex geometry option is on), which has the same shape as the main grid, and determines which points in it belong to the computational domain and which are "void", and (if anisotropy option is on) contains information about fiber directions.
The other are the arithmetic variables and string macros. The
arithmetic variables can be integer or real (with the precision
defined at compile time), their names following C convention. The
input script interpreter contains a built-in interpreter of arithmetic
experession, so parameters of a device can be specified as arithmetic
expressions (possibly depending on arithmetic variables) rather than
specific values. The script is interpreted from top to bottom, so any
arithmetic variable will exist and have a valid value of it was
defined and assigned the value above the point at which it is
used. The exceptions are the pre-defined variables, such
as integer t
containing the counter of the device ring
loop, and real pi
containing the number pi.
The string macros have the form of a string of characters
between [
and ]
.
The strings of characers making the names of the macros also have to
obey the C rules. The exception is string macros
[1]
, [2]
, [3]
..., which cannot
be defined within the script, and which contain the values of the
command line arguments (see above). There some more pre-defined string
macros, e.g. names of colours used in graphical devices. The values
of ordinary string macros are assigned at the moment of their
definition. Whenever they are used in the script, their values are
simply substituted in place of [...]
and the result is
interpreted as if it was part of the script all along. A string macro
may appear e.g. in the expression defining the value of an arithmetic
variable, which is one way to convert string macro to an arithmetic
expression.
In the parallel version, the computational grid (and the geometry array) are split between the thread: if a particular value belongs to one subdomain it is not accessible to another subdomain (with the exception of halo points, see below). On the contrary, any arithmetic variable or string macro is available in all threads; however depending on its use, they may have different values in different threads.
Need to doublecheck the actual syntatic restrictions on variable and string macro names and bring the code and this manual in correspondence with each other.
After preprocessing (see below), a Beatbox script consists of a series
of commands. A command is a sequence of characters (being
part of one line of script or spanning across several lines),
beginning with a recognised keyword and ending with a semicolon
';
'. The following code excerpt shows examples of three commands:
def int sideLength 26; state xmax=sideLength, ymax=sideLength, zmax=sideLength, vmax=3; euler v1=[iext] ode=lrd par={ht=ht IV=@24} ;
This script defines sideLength
which is the used initialise the
state of the system before the euler
device is called.
The types of command in a Beatbox script are listed below. Each command describes an action to be taken by Beatbox, with the keyword at the beginning of the command being the verb.
rem
- A ‘remark’. Marks code as a comment. This is
being processed by the interpreter as usual (so e.g. it should no
contain undefined string macros), but otherwise makes no
consequences for the simulation.if
- Allows a script command to be conditionally
read. The keyword is to be followed by an arithmetic expression
followed by a command with its own keyword. If the arithmetic
expression returns a nonzerp value, the command is interpreted,
otherwise it is ignored. This does not affect the flow of execution
as the simulation is run, but operates much like conditional
compilation.def
- Defines a variable or macro.state
- Sets the dimensions of the computational grid.screen
- Sets parameter for on-screen display for the
run-time graphics.devlist.h
.end
- Marks the end of the script. Anything after
such statement is ignored.As each line of the script is parsed, Beatbox preprocesses the code in a manner similar to the C Preprocessor [see Kernighan and Ritchie, 1988, chap. 4]. The Beatbox preprocessor handles four tasks:
Each of these are discussed below.
Comments can be added to Beatbox scripts in five ways:
/* Multi-line C style comment */
// Single-line C++ style comment
rem ’Remark’ command-style comment , which must end with a semicolon;
end
command,MAA: The last type of comment is not clear to me. An example might help.
Text in a comment is ignored by the parser, except undefined macros
will cause fatal errors within a body of a rem
or another command.
Where the relative path to a Beatbox script file is enclosed in in
angle brackets (< >), the content of the referenced file will be
read and inserted at that location. Unlike in the C programming
language, the name of the file in angle brackets is not preceded by an
#include
command. This can be used to maintain
consistency across a number of simulations, or to reduce code
redundancy. Beatbox replaces a filename in angle brackets with the
file’s entire contents. For example, given a script
called useful.bbs
:
// Here is a lot of useful code... def real apar=1; def real bpar=2; |
and a script that includes useful.bbs
as follows:
// This is my own script <useful.bbs> // Here’s some more of my own code. def real cpar=3; |
The result, after preprocessing, is as shown below:
def real apar=1; def real bpar=2; def real cpar=3; |
Beatbox replaces code in backticks (`...`
) with the
result of that code when run as a system command, via
a system()
call (stdlib.h
). For example:
`date '+%Y%m%d-%H:%M:%S'`
will be replaced with the result of the UNIX date command:
20120710-09:30:55
String macros allow the user to define reusable strings that can be pasted throughout a script. String macros are distinct from variables in that they are expanded once, prior to the script being interpreted and cannot therefore be assigned values other than when they are defined. A string macro is defined as follows:
def str <macro name> <value>;
where <macro name>
is a string using letters,
numbers, underscores (_) or hyphens (-) and <value>
is any string not including a semicolon.
Need to doublecheck the actual syntatic restrictions on variable and string macro names and bring the code and this manual in correspondence with each other.
After a macro is defined, its name, wrapped in square brackets ( [ ] ) is associated with its value. When Beatbox input script parser finds a macro’s name in square brackets, it replaces them with the value.
In the following excerpt, the variable hat
is assigned
the value porkpie
. The variable headware
is
assigned the value hat
.
def str snack porkpie; // Assigned string ’porkpie’. def str hat [snack]; // Assigned string ’porkpie’. def str headware hat; // Assigned string ’hat’, not value of hat macro.
A macro can expand to anything that could be typed in the script. For
example, a string macro can be used in place of
an int
, long
or real
:
def str ninetynine 99; // Assigned string value ’99’. def int number [ninetynine]; // Assigned integer value 99.
A number variable cannot, however, be used to define a macro:
def int number 99; // Assigned integer value 99. def str ninetynine number; // Assigned string value ’number’.
It is possible to assign several lines of code (excluding semicolons) to a string macro, so this:
def str instruction ppmout when=out file="ppm/%04d.ppm" mode="w" r=[u] r0=umin r1=umax g=[v] g0=vmin g1=vmax b=[i] b0=0 b1=255; [instruction];
is equivalent to:
ppmout when=out file="ppm/%04d.ppm" mode="w" r=[u] r0=umin r1=umax g=[v] g0=vmin g1=vmax b=[i] b0=0 b1=255;
Arithmetic variables defined in the script are distinct from the C
language variables used in Beatbox’s implementation. For clarity,
variables defined in the script, using def
may be
referred to as "k-variables".
A k-variable is defined using the def
command, like this:
def <type> <name> [=] <value>;
where <type>
is one of:
int
(integer number),long
(integer number),real
(real number),float
(real number), double
: (real number),
so k-variables and arithmetic expressions only have two data types,
real and integer, and their actual precision (int
or long int
, float
or double
)
is determined at the compile time by the settings
in k_.h
; typically
long
and double
respectively.
A <variable name>
can consist of letters,
numbers, underscores (_) or hyphens (-).
Variables defined in this way are accessible in the script
during the input script parsing, as well as at run-time by some
devices, e.g. k_func
.
The <value>
(optional) is an
expression that may be evaluated to the correct type and is assigned
as the initial value of the variable just defined. Expressions are
discussed in greater detail below. If no initial value is given, the
variable is assigned the initial value of 0 or 0.0 as appropriate.
Beatbox stores the names of macros wrapped in their square
brackets ([...]
), therefore it is possible for a script to define
macros and variables with the same name.
[0]
- string macro containing the name of the
beatbox script, without the extension .bbs
. [1], [2], ...
- string macros containing the command line
arguments passed to the script. t
- integer, the counter of the device ring loops. inf
- "infinity", a very big real number.xmax,ymax,zmax,vmax
- integer, the computational
grid sizes as defined by the state
command, even if
implicitly from the geometry.Graph
- integer, 0 if -nograph
command line option was given or the parallel version of the program
is run, and 1 otherwise.graphon
- integer, nonzero if graphical output has
actually been initiated (e.g. connection to the X server successful). XMAX,YMAX
- integer, sizes of the graphical screen
when run-time graphics is on, in pixels, as specified
by screen
command. WINX,WINY
- integer, the coordinates of the
graphical window on the screen, as specified by screen
command.
BLACK,
BLUE,
GREEN,
CYAN,
RED,
MAGENTA,
BROWN,
LIGHTGRAY,
DARKGRAY,
LIGHTBLUE,
LIGHTGREEN,
LIGHTCYAN,
LIGHTRED,
LIGHTMAGENTA,
YELLOW,
WHITE
- integer codes of the standard VGA colours to be used in the
run-time graphics.pi
- the real number pi=3.1415926...always
- the real 1.0.never
- the real 0.0.
Any numerical value in a Beatbox script, be it initial value in a
variable definition or the value of a device parameter, may be
specified as a mathematical expression. An expression may be a literal
value, such as 5.0, or the result of some computation, such
as 6*5
or count/2
.
Beatbox expressions can use the standard infix arithmetic operators: addition (+), subtraction (-), multiplication (*), division (/) and exponentiation (** or ^), as well unary plus and minus. For more complex operations Beatbox provides a collection of functions:
atan2(a, b)
- Returns the arctangent of a/b
,
using the signs of the arguments to compute the quadrant of the return
value.
hypot(a, b)
- Returns the square root of the sum of the
squares of a
and b
.
tanh(a)
- Returns the hyperbolic tangent
of a
.
erf(a)
- Returns the Gauss error function
of a
.
if(test, then, else)
- Returns then
if test
evaluates to true, else
otherwise.
ifne0(test, then, else)
- Returns then
if test
is not equal to 0, else
otherwise.
ifeq0(test, then, else)
- Returns then
if test
is equal to 0, else
otherwise.
ifgt0(test, then, else)
- Returns then
if test
is strictly greater than 0, else
otherwise.
ifge0(test, then, else)
- Returns then
if test
is greater than or equal to 0, else
otherwise.
iflt0(test, then, else)
- Returns then
if test
is strictly less than 0, else
otherwise.
ifle0(test, then, else)
- Returns then
if test
is less than or equal to 0, else
otherwise.
ifsign(test, neg, zero, pos)
- Returns neg
if test
is strictly less than 0, zero
if
test is equal to 0, pos
if test
is strictly
greater than 0.
gt(a, b)
- Returns 1.0 if a
is strictly
greater than b
.
ge(a, b)
- Returns 1.0 if a
is greater than
or equal to b
.
lt(a, b)
- Returns 1.0 if a
is strictly less
than b
.
le(a, b)
- Returns 1.0 if a
is less than or
equal to b
.
eq(a, b)
- Returns 1.0 if a
and b
are equal.
ne(a, b)
- Returns 1.0 if a
and b
are not equal.
mod(a, b)
- Returns the remainder of the integer division
of a
by b
.
max(a, b)
- Returns the greater of a
or b
.
min(a, b)
- Returns the lesser of a
or b
.
crop(test, min, max)
- Returns min
if test
is strictly less
than min
. Returns max
if test
is strictly less than max
. Returns
test
otherwise.
rnd(a, b)
- Returns a random real number greater than or
equal to a
and strictly less than b
,
uniformly distributed in between (sequential version only).
gauss(a, b)
- Returns a random real number normally
distributed with mean a
and standard
deviation b
, Box-Muller 1958 transformation (sequential version only).
u(x,y,z,v)
- Returns the value of dynamic
variable v
at point (x,y,z)
in the mesh. If
non-integer values are given for x
or y
, the
value is linearly interpolated from surrounding z
points. Non-integer values for v
are rounded down (sequential version only).
The last three functions are not thread-safe or would be too communicationally expensive to implement in the parallel mode, so are only allowed in the sequential mode.
The arithmetic operators distinguish between real and integer numbers,
so 5/2
produces 2 while 5.0/2.0
gives
2.5. The usual type casting rules apply, e.g. sum of a real and an
integer is a real, etc. All functions take real arguments and return
real values. Calling a function with a wrong number of arguments is an
error. The built-in "k-compiler" of arithmetic expressions is not
optimizing, and any intellectual work is expected to be done by the
user. E.g. calling function with an integer argument,
say 0
, will generate the conversion of the
integer 0
into the real 0.0
in the compiled
code; where as putting 0.0
as the argument will not
require that so will generate a slightly shorter and more efficient
"k-executable".
The simplified syntax of a device-defining command is
<device> [<name>=<value> [...]] ;
The <name>=<value>
pairs define the device
parameters and are separated from each other by spaces
(including '\t'
, '\b'
, '\r'
and '\n'
characters). Each device has its own set of
parameters, whose names will be checked for in the body of the
command. The pairs
<name>=<value>
with parameter names not
known to the given device, or something that is not
a <name>=<value>
pair, are silently
ignored. If there is more than
one <name>=<value>
pair with the same
name, only the first occurrence matters and the rest is/are
ignored. If no <name>=<value>
pair
corresponding to a parameter is found, the
default value is assigned, or an error message is output and parsing
terminates if there is no default value for this parameter.
The parameters could be arithmetic, string, or blocks.
For the arithmetic parameters, which could be any valid arithmetic
C type, the <value>
is taken as the string of
characters after the =
sign until the first blank
character, is intepreted as an arithmetic expression, it is evaluated
to the appropriate k-type (integer or real), cast to the required C
type, and assigned to the parameter. A parameter in a device may have
a default value and maximal and minimal allowed values. If a
calculated value goes beyond the prescribed limits, and error
message is printed and parsing is terminated.
For the string parameters, the value
(subject to
preprocessing) is intepreted as the literal value of the parameter. A
string containing blank spaces or one of the special characters
\n
,
\b
,
\f
,
\t
,
\r
,
\a
and
\0
can be used as a value by enclosing it in double
quotes "..."
. Any other character within the double
quotes, preceded by the backslash \
(including the double
quote and the backslash itself) evaluates to that character.
String parameters may have default values, but obviosuly not minimal
or maximal values.
The block parameters expect the values in the form of text
enclosed by curly brackets, {...}
. Such block text may
span several lines of the script and will usually contain its own set
of <name>=<value>
pairs. There are no
defaults for block parameters, i.e. they are always compulsory.
The first functional task of a script is to define the mesh. The
mesh is stored as a four-dimensional array, corresponding to a
three-dimensional, regular Cartesian mesh with an array of dynamic
variables at each point. Dynamic variables hold space-dependent
values, local to each point in the mesh. The majority of dynamic
variables are commonly employed to hold variables of the cell
model. The dimensions of the mesh and number of dynamic variables
(i.e. the size of the four-dimensional array) are set using
the state
command.
The syntax of the state
command is the same as that of a
device-defining command, only it does not add any devices into the
device ring.
The required set of parameters depends on whether geometry file is
used, which is determined by the string
parameter geometry
. If this parameter is assigned to a
non-empty string, then it is understood to be the name of the
geometry file. This case is described in more detail below.
If parameter geometry
is absent or is assigned to an
empty string, no geometry file is read, the computations are done in a
parallelepiped, and its sizes xmax
, ymax
(compulsory) and zmax
(optional, default=1) will be
checked for. Parameter vmax
is still required.
The integer coordinates (index) x
runs from 0 through to
xmax-1
(so the name xmax
is a bit of a
misnomer, but stuck for historical reasons).
If zmax≥3
, the calculations will be three-dimensional
(3D). Otherwise, if ymax≥3
, the calculations will be
two-dimensional (2D). Otherwise, if xmax≥3
, the
calculations will be one-dimensional (1D). Otherwise, it is
zero-dimensional case (0D), that is, no spatial extent (ODE model). At
least three points in a particular direction are needed to make that
direction "extended", because in that case the marginal values of the
coordinates need to be kept free, for technical reasons related to
implementation of boundary conditions. The cases of
xmax=2
etc are syntactically allowed but hardly ever used
in practice.
In any case, parameter vmax
is accepted. It is
optional and defaults to 2 (a tribute to the FitzHugh-Nagumo
model). It defines the number of dynamic variables per node of the
computational grid, i.e. the number of computational layers. Note
that this number may and ofter is larger that the number of
equations in the model, as extra layers may be required as working
arrays for computational purposes (more detail below).
The mesh defined in the example below has 300
points along the x-axis and 400 points along the
y-axis. Since zmax
is equal to 1, there is one point
along the z-axis, making the medium flat on the xy-plane. The
variable vmax
specifies the number of dynamic variables
stored at every point in the mesh.
state xmax=300 ymax=400 zmax=1 vmax=3; |
Beatbox assumes that the simulation medium will follow a hierarchy of axes; x before y before z, i.e. one-dimensional simulations must use the x-axis and two-dimensional simulations must use the x and y axes.
Any Beatbox input script must contain exactly one state
command, and it should precede any device-defining commands.
If you would like to use an anatomically realistic mesh for your
simulation, you can specify it in the geometry parameter of
the state
module. For
example, geometry=ffr.bbg
will select
the ffr.bbg
geometry file.
When using a geometry file, there is no need to specify the dimensions
of the mesh, in fact Beatbox will complain if you do. You do still
need to define vmax
, however, to suit your chosen RHS
module and any other dynamic variables your simulation needs. Beatbox
will also complain if the fibre directions specified in your geometry
file are not unit vectors. If you like, Beatbox will normalise the
fibre directions for you, if you set the normaliseVectors
parameter to 1. If you would like to model anisotropy, set the
ansiotropy
parameter to 1. This only works when a
geometry is specified. When the anisotropy=1
, the
behaviour and required parameters of some devices may change. In
particular, the diff
device, performing the diffusion
substep, will require two diffusion coefficients, Dpar
and Dtrans
for the diffusivity along and across the
fibres, instead of the isotropic D
.
The Beatbox geometry file will typically have the
extension .bbg
and is a plain text file, each line of
which describes a point belonging to the tissue. Any such line will
have four numbers or seven numbers: integers for x, y and z
coordinates of a node and its tissue type, and three reals for the
fibre orientation data. The de-facto limits of the x-, y- and
z-components of all points in the file will be used to define the
circumscribed box. The file does not have to describe every point in
the box; the points that are not mentioned will simply be assumed to
belong to the void. A zero tissue type designates the void, so such
lines can be omitted without any loss of information. The three reals
of the fibre orientation data for any nonzero tissue type will be the
directional cosines of the fibres. If no true fibre data are available
in the source of the geometry, and/or no anisotropy is used in
simulations, then any triple can be put as the fibre orientation data,
say 1,0,0.
Following the state
declaration, a script can add to the
ring of devices by invoking or ‘calling’ Beatbox devices. A device
call takes the form of the device name, followed by parameters as
key-value pairs, separated by spaces. The excerpt below shows
the euler
device being called with some parameters.
euler v1=[iext] ht=ht ode=lrd pars={IV=@24} name=Geoff;
Each device call will add an instance of the device to the ring of
devices. It is possible to instantiate the same device,
e.g. euler
, multiple times and the parameters of each
instance will remain independent. The different instances can be
distinguished from each other (say, in the debugging or profiling
listings) by the values of their optional name
parameter,
"Geoff" in the above example.
Device parameters are assigned following the device name as key-value
pairs with the syntax <key>=<value>
. Since spaces separate
device parameters, an str
parameter value that includes
spaces, should be enclosed in quotes (" "). Expressions assigned
to int
, long
and real
parameters will be reduced to literal values when read. An example is
shown in Listing G.5.
Some devices may request parameters formatted as a codestring or a block.
k_func
, use a block to accept script
statements. For instance, euler
uses a block to pass
parameters assignments to its RHS module.
Example uses of a codestring and a block are shown below.
Listing G.5 shows the assignment of a parameter,
bar
to an imaginary foo
device. Although the value of
hat
may change throughout the simulation, the value of
the bar
parameter will be set only once, when the device
is called. In this case, bar
will be assigned the value
40.
def real hat 10.0; foo bar = min(hat ,60)*4; |
Listing G.6 illustrate usage of block parameters
in real devices k_func
and
euler
.
device.
k_func nowhere=1 pgm={ stim = eq(t,stimTime); }; def int neqn 24; def str iext neqn; euler v0=0 v1=neqn-1 ht=ht ode=crn par={ht=ht, IV=@[iext]}; |
In the k_func
, the block parameter pgm
is
assigned the string enclosed between the {...}
brackets.
This will be compiled and result in a k-executable, which will be
stored among other internal parameters of this instance
of k_func
device for future use. Then at each time step
(but only once per thread, as defined by the nowhere=1
parameter), this k-executable will be evaluated using the then
current, run-time values of k-variables t
and stimTime
, and the result will be assigned to the
k-variable stim
.
In euler
, parameter ode
, which is a string
defining the kinetic model used by the forward Euler timestepper, is
assigned crn
which stands for the
Courtemanche-Ramirez-Nattel 1998 human atrial model. It uses layers
from 0 through to neqn-1
=23 so
k-variable neqn
must be equal to the number of dynamic
variables in the model (24), at the parsing time, otherwise program
will stop with an error message. Parameter ht
, which
designates the time step of the forward Euler scheme, will be assigned
to the current (parse-time) value of the k-variable ht
.
Parameter par
will be assigned the whole contents within
the {...}
brackets, and passed for further parse-time
processing by the ode-specific parser of the euler
device. On this occasion, the CRN model requires the time
step ht
again, since it calculates new values of some of
the variables rather than calculating their time derivatives. Note
that using a different value of ht
parameter
in euler
device and in its crn
kinetic model
would be syntactically correct, but would probably not do what you
want. This kinetic model also uses parameter IV
which
corresponds to inter-cellular current. Its value is given
as @[iext]
. In this example, string
macro [iext]
expands to 24, the value of
IV
will be different for every point, and it will be the
value taken from the layer 24 at that point (more about the layer
substitution below).
So the difference between the parse-time and run-time execution of k-code within block parameters is not syntactical, but device-specific. In other words, the only way to find out how it will be interpreted is to look into the description of the particular device.
A set of generic parameters, listed below, is applicable to all device types. All of the generic parameters listed below are optional - if no value is given for them, a default will be provided by Beatbox. Generic parameters and their defaults are described below.
(str) name
Given name for the device. If the script specifies two instances of a
device, giving one of the devices a name will disambiguate the devices
in any output messages. Also, some devices refer to another device in
the simulation using its name parameter. The default
for name
is the device name.
(real) when
The device's condition. The value given for when
must be
a real k-variable. The device will be run only on timesteps (loops of
the device ring) when the value of this variable is nonzero. A
literal value cannot be used for this parameter (think of this
parameter as a codestring, where the code is restricted to just one
real variable; allowing here a generic k-expression would be more
consistent but also more expensive). For devices that should run on
every timestep, Beatbox provides the predefined read-only
variable always
, whose value is 1,
and when=always
is the default.
These specify the points of the mesh on which the device will operate:
(int) nowhere
: if nonzero, the device is not
associated with any part of the computational grid, and its
execution does not involve any loops through x,y,z
coordinates on the grid. The other space parameters have effect only
if nowhere=0
, which is the default.
(int)x0
Lower x
bound.
Allowed values from 0 to xmax-1
,
defaults
to 1
.
(int) x1
Upper x
bound.
Allowed values from x0
to xmax-1
,
defaults
to xmax-2
.
(int) y0
Lower y
bound.
Allowed values from 0 to ymax-1
,
defaults to 0
for one-dimensional calculations and
to 1
otherwise.
(int) y1
Upper y
bound.
Allowed values from y0
to ymax-1
,
defaults
to ymax-1
for one-dimensional calculations and to
ymax-2
otherwise.
(int) z0
Lower z
bound.
Allowed values from to zmax-1
,
defaults to 1
for three-dimensional calculations and
to 0
otherwise.
(int) z1
Upper z
bound.
Allowed values from z0
to zmax-1
,
defaults
to zmax-2
for three-dimensional calculations and
zmax-1
otherwise.
(int) v0
Lower v
(layer number)
bound.
Allowed values from 0 to vmax-1
,
defaults to 0
.
(int) v1
Upper v
(layer number)
bound.
Allowed values from 0 to vmax-1
,
defaults to v0
.
In general, a device will operate on points from, e.g. x0 ≤ x
≤ x1
. In some devices, the v0,v1
parameters do
not define a range of v
coordinate, but identify separate
layers each having its own function. For example,
Beatbox’s diff*
devices use v0
to indicate
the dynamic variable containing transmembrane potential or another
diffusing field, and v1
to indicate where the Laplacian
of that field should be stored. For devices that have no effect on the
computational grid, the nowhere
parameter should be set to
1 to indicate this. The operation of some devices, in particular
k_func
is strongly affected by the nowhere
parameter.
These specify the placement and colouring of the area associated with the device within the graphical screen when run-time graphics is on.
int row0
Lower row.int row1
Upper row.int col0
Left column.int col1
Right column.int color
Colour.
All the standard devices (with the exception of ad-hoc, experimental
ones) are described in detail in
the Beatbox Device Reference below. However
the k_func
device is so important for understanding of
the Beatbox control flow that it some consideration immediately. This
device takes block parameter
pgm
which is expected to be a "k-program", that is a set
of one or more "assignment operators", separated by semicolons. An
assignment operator is a string of the form
<k-variable>=<k-expression>
. The expressions
are compiled at parse-time, but evaluated and assigned at run-time
using the then current values of k-variables.
The functioning of the k_func
device somewhat differs
depending on the value of its nowhere
parameter.
If nowhere
is nonzero, then the "k-program" is executed
exactly once per per time step, and it can only use the usual "global"
k-variables both in the right-hand sides and in the left-hand sides of
the assignments.
If, however, nowhere=0
and the device has space, then at
every time step the k-program is executed at every point of the
space. In this case, the k-programs may use the device's "local" real
variables:
x,y,z
are numerically equal to the corresponding
integer coordinates of the point at which the k-program is executed;
u0, u1, ... u<vmax-1>
- variables containing the
corresponding values of the computational grid at the point at which
the k-program is executed.
phasep, phaseu, p0, p1, ... p<vmax-1>
- variables
used for the phase-distribution method (see the next section).
All the local variables can be used in the left-hand sides as well as
the right-hand sides. The resulting values of u0, u1,
...
variables are stored back into the computational grid. The
resulting values of all other local variables will be lost after the
device instance finishes its work at the given time step. In this
mode, k_func
can be used to fill the computational grid
with values according to given formulas or other algorithms.
A common and very important application of k_func
device
in the nowhere
mode is in updating variables used for
devices’ when
parameters. For example, to run another
device at timestep 200 only, the k_func
device
called below will assign the value 1.0 to stim
when the
current timestep, t
equals stimTime
, and the
value of 0.0 otherwise:
def real stimTime 200; k_func nowhere=1 pgm={ stim = eq(t,stimTime); };
The following device call adds a line to the pgm
parameter to set k-variable print
to 1.0 on timesteps 0,
50, 100, ...
and to 0.0 otherwise:
def real stimTime 200; def real printInterval 50; k_func nowhere=1 pgm={ stim = eq(t,stimTime); print = ifeq0(mod(t,printInterval)); };
The example
below illustrates use of k_func
with a space
(nowhere=0
mode). It models stimulation of the excitable
medium by raising the transmebrane voltage (allocated in the layer
whose number is encoded by string macro [V]
), in the left
half of the box. The value of the stimulus linearly depends on the
y
coordinate and varies linearly between 0 at
y=0
and and 1.5 at y=ymax-1
:
def str V 0; k_func when=stim x0=1 x1=(xmax-1.0)/2 pgm={ u[V] = u[V] + 1.5*y/(ymax-1); };
Device k_func
implements a method that is very specific
for cardiac, and generically excitable media simulations. This is a
"phase distribution" method, particularly suitable for creating
initial conditions. The method was described e.g. in
V.N. Biktashev et al., Phil. Trans. Roy. Soc. London, ser A 347: 611-630, 1994.
The idea of the method is that the user a
scalar field, the phase, defined up to an integer multiple of 2π,
and Beatbox then automatically fills the space of the
k_func
device with corresponding values of the dynamic
variables, where what values of dynamic variables correspond to what
values of the phase is defined by a "profile", provided in an input
file. For this purpose, k_func
takes parameter
file
, which will be the name of a text file, containing
space-separated columns of real numbers. Each column corresponds to
values of one dynamic variable, and the number of rows represents the
whole circle [0,2π) of the phase.
![]() |
For example, the following data file contains a 2×10 table of values:
2.378 5.111 5.768 8.860 5.609 7.777 1.493 6.545 5.609 7.777 1.347 7.346 4.256 7.423 4.167 6.245 1.234 7.987 3.245 5.222 |
Conceptually, the values are arranged in a circle, as shown in Figure 2.
![]() |
If parameter file
is given and correponds to a valid
profile, phase distribution is initiated by assigning a value to local
variable phaseu
in the k_func
program. The
value assigned to phaseu
, taken to be in radians,
indicates a point on the circle, and consequently the line of the
input file to be read. Since phaseu
is a real number,
linear interpolation is used to calculate values from two neighbouring
rows of array, as illustrated in Figure 3. The
calculated value is then assigned to the corresponding layer
of u
: the values interpolated from the first column of
the profile to
u0
, from the second to u1
etc until
the last column available on the profile. Note that if the number of
u*
is more than the number of layers allocated to this
k_func
instance by its vmin,vmax
parameters,
then the trailing values will be lost.
An alternative version of the phase distribution method is with
assignment to phasep
local variable instead of
phaseu
. In this case, instead of calculating the values
of u0, u1, ...
local variables, the same algorithm is
used to calculate the values of local variables p0, p1,
...
. Those local variables can then be used later in the same
k-program to calculate the u0,u1,...
variables. Such
indirect assignment may be required if the columns in the profile go
in a wrong order etc.
We now consider a simple working example of a Beatbox script, minimal.bbs. It corresponds to Figure 1 above, and solves a standard initial/boundary-value problem for the FitzHugh-Nagumo model, as defined in A.T.Winfree, Chaos 1:305,1991:
∂u/∂t
=
ε-1(u-u3/3-v)
+
D∇2u,
∂v/∂t
= ε(u+β-γv),
(x,y,z) ∈ Ω
= [0,Lx]×
[0,Ly] ;
∂u/∂n = 0,
(x,y,z) ∈ ∂Ω ;
u(x,y,0)={-1.7, x≤Lx; 1.7,
otherwise},
v(x,y,0)={-0.7, y≤Ly; 0.7,
otherwise}.
It may be executed, say, in the sequential mode by using the following command:
Beatbox_SEQ minimal.bbs
state xmax=102 ymax=102 vmax=3; /* device control variables */ def real begin; def real output; def real end; /* Schedule */ k_func name=schedule nowhere=1 pgm={ begin =eq(t,0); output=eq(mod(t,100),0); end =ge(t,2000); }; /* Initial conditions */ k_func name=ic when=begin x0=1 x1=100 y0=1 y1=100 pgm={ u0=-1.7+3.4*gt(x,50); u1=-0.7+1.4*gt(y,50); }; /* Diffusion substep */ diff v0=0 v1=2 D=1.0 hx=0.5; /* Reaction substep */ euler v0=0 v1=1 ht=0.03 ode=fhncub par={ eps=0.2 bet=0.8 gam=0.5 Iu=@2 }; /* Output image files */ ppmout when=output file=%04d.ppm r=0 r0=-1.7 r1=1.7 g=1 g0=-0.7 g1=0.7 b=2 b0=0.0 b1=1.0; stop when=end; end; |
The expected results of the run are described below, and for now we consider the script itself, which is shown on the right.
The script starts with a space
command, which the 2D
computational grid of 100×100 internal nodes (zmax
defaults to 1), and three layers,
they will have numbers 0,1,2. Layers 0 and 1 will be used for
variables u and v, and layer 2 will keep the values of
the diffusion term D∇2u.
The "device control variables" begin,output,end
are
required to define which devices work when. They are defined by the
three def
commands, their values are updated by the
first k_func
device and used as values of
the when
parameters of other devices.
The first k_func
device does not have a
when
parameter, so its value defaults to
always
and it is executed at each step. It has
nowhere=1
so its k-program is not iterated over any grid
points, but only executed once. The k-program assigns value 1 to
k-variables begin
at the very first loop of the
device ring, when the loop counter t
is zero, otherwise
begin
is assigned zero. Variable
output
will be 1 at every 100th step, and 0
otherwise. And variable end
will be zero until the step
2000, from which on it will be assigned 1.
The second k_func
device calculates the initial
conditions in accordance with the formula above. It only works during
the very first loop (when=begin
), and iterates over all
internal points, as specified by x0,x1,y0,y1
. Its
k-program assigns the values to the u0
local k-variable
(which means layer 0 value, i.e. u variable of the model)
and u1
local variable (layer 1, v variable),
according to the formulas for the initial conditions above. The
k-expressions depend on local k-variables x,y
each of
which runs from 1 through to 100, and the ranges 1..50 and 51..100
represent two halves of the computational box.
Notice that the two k_func
devices use the optional
name
parameter. This is used to tell them from each other
in the output.
The diff
device calculates the diffusion term using
values in layer zero (v0=0
) and puts the results into
layer two (v1=2
). It uses the diffusion coefficient value
of 1.0, and hx=0.5
is the spatial discretization
step. The device applies every step (no when
parameter),
at all the inner points of the grid (no space parameters, so the
defaults are used).
The euler
device makes the forward Euler timestep.
It also applies at every step and at all the inner points of the
grid. This device uses layers 0 and 1, as v0=0
,
v1=1
. It uses time discretization step
ht=0.03
, and the right-hand sides of the FitzHugh-Nagumo
model are selected by ode=fhncub
. The parameters of the
model are defined by the name-value pairs within the block parameter
pgm
, namely ε=0.2, β=0.8, γ=0.5. The
parameter Iu
stands for the extra term in the right-hand
side for the u variable, which is defined here, through the
layer substitution call @2
, as the value of
layer 2 at the same point, i.e. the value of the diffusion term as
computed by the previous diff
device.
The ppmout
device make the results of calculations
usable. Every time it is active, i.e. at every 100th time
step, it outputs a file in
the ppm
format. The space for this device is not specified so it defaults to
all inner points.
The discretization of the floating point data from the computational
grid to the one-byte unsigned integers in the PPM file is defined by
the following parameters. Integer r=0
says that the
red-component will be made from the values in layer 0, i.e. values of
the u variable. Its values below r0=-1.7
will be
mapped to "0" bytes (zero intensity of the red component), the values
above r1=1.7
will be mapped to the maximal "255" bytes
(maximal intensity of the red component), with a linear interpolation
of values in between. Similarly, the (g)reen component is made out of
v values in layer 1, and the (b)lue component out of values of
the diffusion term in layer 2. Thus the head of the excitation wave is
red, its back is yellow and the refractory tail is green. The blue
component, represents the Laplacian but shows only the positive part
of it (as b0=0.0
). This shows as a dark blue
stripe ahead of the front of the excitation wave, and a cyan stripe
around its back (the images can be seen
below).
The names of the output files are encoded by the string parameter
file
.
The value of this parameter is treated as a file mask, where resulting
file names will have the form of the four
digits representing the file's ordinal number, followed by
.ppm
, so the filenames will be
0000.ppm
,
0001.ppm
,
...,
0020.ppm
.
The last device is stop
, which does what it says on
the tin. Its only one parameter when=end
means,
according to the assignment for k-variable end
above,
that this device would work at every timestep, starting from 2000 and
above. But it only works once as after that Beatbox terminates.
The script is terminated by the end
command, which is
purely syntactical, to signal that the script file is complete and the
rest of it, if any, should be ignored.
A sequential run of this script may produce standard output like this:
525 $ Beatbox_SEQ minimal.bbs Beatbox v1.0 ------------------------------------------------------------------------------------ Sequential version compiled Aug 31 2012 19:10:55 $ Execution begin at Tue Sep 25 18:24:11 2012 $ Input file minimal.bbs without additional arguments $ with options: noappend nodebug noverbose graph noprofile logname=minimal.log state /* grid 102 x 102 x 1 x 3 */ $ k_func $ k_func $ diff $ euler $ ppmout $ stop $ end $ Ring of 6 devices created: (0)schedule (1)ic (2)diff (3)euler (4)ppmout (5)stop $ STOP AT 2000[0] BEATBOX_0.1 finished at t=2000 by device 5 "stop" Tue Sep 25 18:24:14 2012 ====================================================== 526 $
and the file minimal.log
with a bit more detail, such as
values of the device parameters read, and k-variables assigned. First the standard output and
the log file show the commands being read in; so if an error happens
at parse time, it is clear which device has caused it. Then the whole
device ring is described again, now giving devices' ordinal numbers in
the ring, and using
their proper names where given. After that there would be output from
devices produced during their work, or debug information if it was
specified, but in this example the only message is from the
stop
device. The final message is a signal of normal
termination: stopping by any other device would probably happen due to
a fatal run-time error. Wall-clock times are printed before and after
the execution, so we see that this run took about 3 second.
If ppm
format is not convenient, the output files can be
converted to another using e.g. an appropriate netpbm
utility, say (in bash
):
for (( i=0 ; $i<=20 ; i=$i+1 )); do n=`printf %04d $i`; pnmtopng $n.ppm > $n.png; done
Here are the resulting figures:
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
The example we have considered is "minimal" and this sort of task is routinely done by any cardiac simulation packages. However, Beatbox's flexibility allows you to do much more than that. To discover Beatbox's capabilities, do look at further sample scripts provided.
Here we describe a few sample scripts that illustrate some typical uses of BeatBox and can be used as templates for your specific tasks. These scripts are located under:
data/scripts/sequential
for scripts illustrating the
fundamentals, which can or should be run in sequential mode, and
data/scripts/parallel_Hector/
for more specialized script
illustrating parallel work.
These scripts are designed to introduce BeatBox devices in an informal, "how-to" way; a more formal description of devices is given later in the Beatbox Device Reference.
This script generates action potentials using single cell with FitzHugh-Nagumo kinetics. See:
The APs are initiated repeatedly: a stimulating pulse is issued every
time that the two dynamic variables satisfy certain inequalities,
meaning the system comes back close enough to the resting state. The
solution corresponding to the n-th action potential, n=4
,
is output to file:
This file will be used in the next example. This example illustrates use of:
k_func
as a feedback control device: stimulating
shock is defined as a function of current cell state;sample
device to convert grid value into a
global k-variable, which is required for the k_func
;
screen
command and k_draw
device for
run-time graphics output: draw phase trajectory of the system as the
solution progresses;record
output device to write contents of the grid
(in this case, just single cell) to a file.
To run this script with run-time graphics, call it by
Beatbox_SEQ fhn0.bbs
and without graphics,
Beatbox_SEQ fhn0.bbs -nograph
The same applies to all examples with run-time graphics.
This script simulates propagation of excitation pulses in a one-dimensional cable with FitzHugh-Nagumo kinetics. See:
The pulses are initiated by non-stationary non-homogeneous Dirichlet
conditions on the left boundary, where u-variable is made
time-dependent according to the record in the fhn0.rec
(the file obtained in the previous example). It outputs a point record
during n-th front, n=5
, into file: fhn1.rec
.
This file will be used in the next example. This example illustrates
use of
⟨...⟩
to include
contents of another file;`...`
to capture output of a child process
(here just counting the number of lines in the given file); k_poincare
device to detect arrivals of wavefronts at
selected points, which is then used both to control execution and to
calculate the propagation speed;k_plot
run-time graphics device: to plot spatial
profiles of the dynamic fields at selected moments of time.
pause
device for suspending execution and keeping
the last picture on the screen until the user presses enter in the
terminal window.
This script simulates a spiral wave in a two-dimensional box with
FitzHugh-Nagumo kinetics, and periodically outputs the
solution. Essentially, this is a variant of the
minimal.bbs
script discussed above. Some new things that
this script illustrates:
k_func
as a computational device for
creating intial conditions by phase distribution method, using data
from file fhn1.rec
obtained by the previous
d_dt
and
grad2d
to compute time derivative and absolute value of
spatial gradient of a field;k_paint
to
crudely visualize distribution of dynamic fields in the plane using
16-colour VGA palette;singz
device which detects spiral wave tips
and is both computational and output;shell
output device to call arbitrary OS
command from BeatBox (here to create an output directory for the
image files);imgout
and k_imgout
output
devices to output image files; pause
device for suspending execution and keeping
the last picture on the screen for a fixed time interval.
Note that this script will create file fhn2.trj
containing the records of the detected spiral tips, and directory
fhn2.dir
which will contain several png
image files created by imgout
and k_imgout
devices.
This script simulates propagation and destruction of a critical solution in the model of INa-driven excitation front described in V.N. Biktashev, "Dissipation of the excitation wavefronts", Phys. Rev. Lett., 89(16): 168102, 2002, and reproduces figure 5 from that paper. See:
This script reproduces both panels of the figure, and it is placed in a separate subdirectory in which some post-processing is done as well. The new things that this script illustrates are:
k_print
device to write the output files in
the png
format.
The directory
../data/scripts/sequential/b02/fig5.bbs
also contains Makefile
which describes the
workflow leading to creation of the final product,
fig5a.png
and fig5b.png
files. For this to
work,
netpbm has to be installed on your
computer.
The following steps can be done to reproduce the workflow:
mkdir ~/b02-test
~/beatbox-1.2/
, this can be
done by the following commands:
cd ~/beatbox-1.2/data/scripts/sequential/b02/ cp Makefile fig5.bbs ~/b02-test/ cd ~/b02-test/
make all
make allNote that due to the
pause
device in the script, you
would need to press enter in the terminal window used to launch
BeatBox. If all works correctly, you will find files
fig5a.png
and fig5b.png
in the
~/b02-test/
directory. Compare them with the ones
obtained by the BeatBox developers, which are back in
~/beatbox-1.2/data/script/sequential/b02/
directory.
state file=name_of_your_geometry.bbg normaliseVectors=1 anisotropy=1 vmax=neqn+1;The bbs program can then work out the bounding box xmax, ymax, and zmax. If you have isotropic simulation, the normaliseVectors and anisotropy parameters should not be included in the state call. A brief description of the geometries, and asssociated bbs programs is below. The space step is usually obtained from the authors of the geometry, as is the species type (human, rabbit,...).
beatbox/data/scripts/sequential/LRD_ffr.bbs
beatbox/data/scripts/sequential/LRD_ffr_slice.bbs
beatbox/data/scripts/sequential/fhn_rabbit2011.bbs
euler v0=0 v1=neqn-1 ht=ht ode=my_cell_model_name rest=100 par={ ht=ht; Iu=@[iext]; par1=val1; par2=val2; };where neqn is the number of ODEs in the cell model, and par1 and par2 are cell model specific parameters that you may want to modify (e.g. increase gCaL). Assuming that your diffusing variable is voltage, the layer of voltage is explained in the sub-sections below. See Beatbox Cell Model Reference below for several cell model available in the package.
Devices are the primary building block of a Beatbox simulation. A
simulation consists of two or more devices that are called in turn, at
most once for each simulation timestep. When called, the device can
perform a task, with optional access to the simulation
medium, New
.
In this tutorial, we’ll build a device to output the text
“Hello, Beatbox !
” in the simulation’s standard output
file. We won’t go into much detail in this section, rather we’ll just
look at the absolute minimum one has to do to build a working device.
The template below shows the a .c
file containing the
basic outline of a minimal device.
#include <assert.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "system.h" #include "beatbox.h" #include "device.h" #include "state.h" #include "bikt.h" #include "qpp.h" typedef struct { int dummy; // Add your own device -specific storage here. } STR; RUN_HEAD(myDevice) /* Put code to perform the device ’s task here. */ RUN_TAIL(myDevice) DESTROY_HEAD(myDevice) /* Free any allocated resources here. */ DESTROY_TAIL(myDevice) CREATE_HEAD(myDevice) /* Add any initialisation code here. */ CREATE_TAIL(myDevice ,1)
There are four key parts to a device:
The first is the definition of the STR
datatype. Every
device in Beatbox defines STR
to meet its specific
storage requirements. Usually, STR
is used to define a
structure for any constants required by the device. For our
“Hello, Beatbox !
” device, we won’t need any persistent
storage, so we’ll just use the minimal implementation that’s given in
the template. We’ll see how to use STR
in more detail in
Section I.2.
The second part of interest is the function used to run our
device. Code between the RUN_HEAD
and RUN_TAIL
macros defines what the device does. This is
where whatever computation or output tasks to be performed by the
device will happen. For our “Hello, Beatbox!
” device,
this is where we want to output our message. We’ll use
the MESSAGE
macro, defined in beatbox.h
,
which will print to both
stdout
and the simulation’s .res
file.
RUN_HEAD(hello) MESSAGE("Hello , Beatbox!\n"); RUN_TAIL(hello)
We’ll also have to change the name in the parentheses of
the RUN_HEAD
and RUN_TAIL
macros
from myDevice
to hello
. hello
will be the name of our device, and we’ll need to use it wherever we
refer to the device. Device names cannot use spaces and are, by
convention, all lower-case, without punctuation. If you really need to
separate words, use an underscore (_).
The function defined by expanding the DESTROY_HEAD
and DESTROY_TAIL
macros is called only once, at the end
of a simulation, and should be used to free any allocated
resources. Since our device won’t allocate anything, we don’t have
anything to free, so we can leave this function empty and just put the
device name in the parentheses:
DESTROY_HEAD(hello) /* Nothing to do here. */ DESTROY_TAIL(hello)
The fourth and final part prepares the device to be run. The function
defined by expanding the CREATE_HEAD
and CREATE_TAIL
macros will be called only once, at the
start of the simulation and should be used to perform any
initialisation tasks required by the device. This is discussed in much
more detail in Section I.2. For our “Hello, Beatbox!
”
device, we needn’t add any code here, so just put the device name in
the parentheses.
CREATE_HEAD(hello) /* Nothing to do here. Quite boring , actually. */ CREATE_TAIL(hello ,1)
Although we’ve not put any code between the HEAD
and TAIL
macros, it’s important not to remove them
altogether, as they are all referred to elsewhere in Beatbox. Also, be
sure not to change the order of the macros,
since CREATE_TAIL
defines code dependent upon
the RUN_HEAD
and DESTROY_HEAD
macros. We
can now save our device file. By convention, the
device’s .c
file should have the same name as the device,
so ours will be hello.c
.
Now that we have a new device, we need to inform Beatbox that it’s
available for use in a simulation. We do this by listing our device’s
name in devlist.h
. The name listed here must match
exactly (case matters) the name we used in our device code.
Open devlist.h
and add the following line:
D(hello)
It’s good practice to add your device in alphabetical order. You’ll
see a few devices in there that are listed
as S(somedevice)
. These devices will only work in
sequential mode, and will be disabled in parallel. They’re naughty
devices. Since we don’t want our new device to fall in with this rough
crowd, we’re going to use D()
, ok?
Beatbox is built using the GNU Autotools. To include our new device in
the build, we must add it to Makefile.am
, which, like
your hello.c
file, should be in the src
directory. Makefile.am
contains a simple description of
what’s required to build the software. When we’re done changing it,
we’ll use automake
to generate a
new Makefile
, from which the code will actually be built.
Open Makefile.am
. Scroll down the file, past the flags
and other stuff until you find the line:
common_sources = \
followed by a long list of .c
and .h
files. In correct alphabetical order, add the following line to the
list:
hello.c\
We can now build Beatbox with our new device. First, we need to
generate a new Makefile
by invoking automake
from the beatbox
directory.
$ pwd $ /Users/rossmcf/Working/beatbox $ automake
With automake
, no news is good news. We can now compile
our new version of Beatbox containing the hello
device. We’ll run the local configure
script to set up
the build system for our machine, and then make to actually build the
code.
$ pwd $ /Users/rossmcf/Working/beatbox $ ./configure ... (Lots of stuff) $ make ... (Lots more stuff , hopefully no errors.)
This will compile the code. Assuming all is well, you can now install the binary wherever you want it.
$ make install ... (Even more stuff.)
Don’t be too worried about all the text whizzing by. If anything goes amiss, the last line usually tells you all you need to know.
To include our new device in a simulation, we use the device’s name in
the script. So, to print our “Hello, Beatbox!
” at each
timestep, we include the following line, making sure to leave a space
between the device name and the semicolon: hello ;
You
can try using the following simple script, let’s call
it basic.bbs
, to test it:
// The medium state xmax=1 ymax=1 zmax=1 vmax=4; // Schedule and display parameters def real begin; def real end; k_func nowhere=1 pgm={ begin =eq(t,0); end =ge(t,20); }; // Our new device! hello ; stop when=end; end;
Running the script with the following command:
$ <mpirun commands> Beatbox basic.bbs <-options>
should produce something like:
Beatbox ------------------------------------------------------------------------------------ BEATBOX_1.0 -- MPI version date Oct 26 2010 22:11:47 $ execution begin at Tue Oct 26 22:14:12 2010 $ Input file basic.bbs without additional arguments $ with options: noappend nodebug noverbose nograph noprofile resname=basic.res state Simulation medium (1 x 1 x 1) Domain decomposed as (001,001,001). 0 processors of 1 remain idle. $ k_func $ hello $ stop $ end of input file $ Loop of 3 devices created: (0)k_func (1)hello (2)stop $ Hello , Beatbox! Hello , Beatbox! Hello , Beatbox! Hello , Beatbox! Hello , Beatbox! Hello , Beatbox! Hello , Beatbox! Hello , Beatbox! Hello , Beatbox! Hello , Beatbox! Hello , Beatbox! Hello , Beatbox! Hello , Beatbox! Hello , Beatbox! Hello , Beatbox! Hello , Beatbox! Hello , Beatbox! Hello , Beatbox! Hello , Beatbox! Hello , Beatbox! Hello , Beatbox! TIMING INFO ------------------------- This run took 0.000266 seconds. BEATBOX_0.1 finished at t=20 by device 2 "stop"
So, now that you’ve built your first device, you might want to make one that actually does something. For that, you’ll need to read Section I.2.
This section assumes that you’ve read Section I.1 and have built a
Beatbox device using the method described therein. We’re now going to
go a little more slowly, taking the time to see how and why things
work they way they do in Beatbox. Much of the generic code required
of a device is provided as a collection of macros and type definitions
in device.h
, which must be included by all devices.
For the persistent storage of data, devices must declare a parameter
structure using the STR
datatype. STR
is a
marker type that should be defined, using typedef
, as
a struct
. Typically, the STR
struct has
fields for one or more user-specified parameters and any other
constants required by an instance of the device. A minimal
implementation, as shown in hello.c
, above, should
define STR
as a struct with at least one field.
Commonly, the parameter structure is used to hold values of parameters
read from the script. This process is described in Section I.2.5.
The Device
datatype (device.h
) represents an
instance of a device, as called in the user script.
One Device
structure is allocated for each of the device
calls in the script, such that if the same type of device
(e.g. k_func
) is called multiple times, there will be
multiple Device
structures. For the most part,
the Device structure
is only used ‘behind the
scenes’. One field of the Device
structure that you will
see a lot, however, is the Space
structure.
Your device’s space determines the points and dynamic variables of the
simulation medium on which it should operate. A device’s
implementation code should not interact directly with
the Device
structure, so we won’t look at it any
longer. Forget you even saw it. Some minimal interaction with the
Device
structure is provided in the function template
macros defined in device.h
. Using these is the best way
to get the job done without wandering into any dangerous territory.
As we saw in Section I.1, your device needs to define three functions,
referred to as Create
, Run
and Destroy
. device.h
defines three
template macros that will wrap around your function
implementations. Using these templates is strongly recommended since
they ensure reliable operation of devices, keep code concise and
reduce redundancy. Each template takes the form of a HEAD
and TAIL
macro. These open and close the function
respectively, and throw in some standard functionality for good
measure.
The head macros take at least one argument, the name of the
device. This is included into the function’s name when the macro is
expanded, so that, for example, the euler
device will
have functions
called create_euler()
, run_euler()
and
destroy_euler()
. Your chosen device name must be unique
and used consistently throughout all macros and
in devlist.h
.
You device’s Create
function performs any initialisation
required for the device. The function is run once only, before the
simulation begins. The CREATE_HEAD
macro declares and
allocates memory for the device’s parameter structure, S
,
a local variable of type STR
. You can use the body of the
function to assign values to the parameter structure.
The defined function will have the name
of create_<name>
, where
<name>
is the argument to
theCREATE_HEAD
macro. To avoid name collisions, it is
strongly recommended that <name>
correspond to the
name of the device.
The create_<name>
function has two
parameters, dev
and w
. dev
is a
pointer to the Device
structure and is used by the
CREATE_TAIL
macro to assign values to its
fields. w
is a pointer to the parameter string taken
from the device call in the user script. This can be used in
combination with the functions defined in qpp.h
to
acquire values from the user script. For more details on how to do
this, see Section I.2.5.
The CREATE_TAIL
macro assigns the address
of S
and pointers to functions
named run_<name>
and
destroy_<name>
to
the par
, p
and d
fields of the
Device
structure respectively.
Although the Create
function has access to
the Device
structure at this point, it is perilous to
tamper with the device structure, so just don’t.
Your device’s Run
function describes the task it will
perform. The function will be called run_<name>
where <name>
is the argument to the RUN_HEAD
macro
. The Run
function is pointed to by the p
field of the Device
structure, with
the s
, w
and par
fields of the
same structure passed as arguments.
s
is the device’s Space
structure, which is
discussed in Section I.2.4.
w
is the device’s Window
structure.
par
is the device’s parameter structure.
The RUN_HEAD
macro contains code that
assigns par
to a local variable, S
, of type
STR
. Fields of the parameter structure can then be
accessed using S->fieldName
. Commonly,
the Run
function begins by retrieving values from the
parameter structure to local variables. This can be done using
shortcut macros defined in device.h
. We discuss this in
more detail in Section I.2.5.
The RUN_TAIL
macro closes the function, returning 1 to
indicate success. If the Run
function of any device
returns, 0 Beatbox will stop the simulation immediately.
In your device’s Destroy
function, it should put its toys
away; it’s time for bed. Specifically, the Destroy
function should free any memory allocated for the device and close any
open files. If you use the provided template macros, there is no need
to free par
/S
. The function is pointed to by
the d
field of the Device
structure, with
the s
, w
and par
fields of the
same structure passed as arguments.
s
is the device’s Space
structure.
w
is the device’s Window
structure.
par
is the device’s parameter structure.
The DESTROY_HEAD
macro will again point local
variable S
to your device’s parameter
structure. The DESTROY_TAIL
macro frees par
and returns 1 to indicate success.
Beatbox’s simulation medium is a four-dimensional data set - multiple
dynamic variables or layers at points in three-dimensional space.
Beatbox represents the simulation medium with a one-dimensional array
called New
, defined in state.h
. In addition
to exporting New
, state.h
provides a
collection of variables and macros with which to navigate it. These
are listed in Table I.1.
Name | Description |
---|---|
t | The current time step. |
New | The simulation medium. |
vmax | Number of layers. |
xmax | Number of points in the x-axis. |
ymax | Number of points in the y-axis. |
zmax | Number of points in the z-axis. |
vmax_xmax | Number of variables in the v/x plane. |
vmax_xmax_ymax | Number of variables in the v/x/y plane. |
DX | The number of array locations between neighbouring points on the x-axis. (See Figure 2.8) |
DY | The number of array locations between neighbouring points on the y-axis. (See Figure 2.8) |
DZ | The number of array locations between neighbouring points on the z-axis. |
DV | The number of array locations between neighbouring points on the v (variable) axis. |
dim | The dimensionality of the mesh, from 0 (single cell) to 3. |
ONE | 1 if the dimensionality is greater than or equal to 1. 0 otherwise. |
TWO | 1 if the dimensionality is greater than or equal to 2. 0 otherwise. |
TRI | 1 if the dimensionality is greater than or equal to 3. 0 otherwise. |
To access a point in New
, use the ind()
function to obtain the index of the desired
location. ind()
takes 4
coordinates, {x,y,z,v}
. For a pointer, you will commonly
use New+ind(x,y,z,v)
, and for an absolute
value New[ind(x,y,z,v)]
.
When accessing New
, your device MUST only alter values at
points within its space, i.e. i0 <= i <= i1
. When
iterating over the medium, it is advised that the space structure’s
fields be used as loop bounds. Not doing so will produce inconsistent
behaviour for users.
RUN_HEAD(example) int x, y, z; real HUGE *u; for(z=s.z0;z<=s.z1;z++){ for(y=s.y0;y<=s.y1;y++){ for(x=s.x0;x<=s.x1;x++){ u=New+ind(x,y,z,s.v0); /* do something to u here */ } } } RUN_TAIL(example)
Accessing points immediately (x ± 1,y ± 1,z ± 1)
outside
of the device’s space is acceptable, provided such access is
read-only. If your device does reference neighbouring points in this
fashion, it MUST include the DEVICE_REQUIRES_SYNC
macro
in its Create
function. This will ensure that the device
continues to operate correctly in parallel.
Great care should also be exercised when writing to locations
in New
. Since New
may hold values relating
to more than one timestep, the mathematical procedure may be corrupted
if values required by other devices are overwritten. In general,
operations on New
MUST be commutative — the device MUST
NOT assume a particular order of execution. In addition, devices that
are not explicitly parallelised MUST NOT assign values derived from
New
to their parameter structure, or any other global
variable. This will stop terrible things from happening when the
device is run in parallel.
To keep users happy, layers accessed by your device should be
specified via the Space
structure. In most
cases, v0
and v1
describe a range, but some
devices use them separately, e.g. s.v0
as ‘source’
and s.v1
as ‘destination’.
If your device is interested in accessing data about the underlying
tissue geometry, you’ll find it
in Geom
. Geom
is organised in much the same
way as New
, and we use geom_ind(x,y,z,v)
to
access it. The v
variable can have one of four values:
GEOM_STATUS
,GEOM_FIBRE_1
,GEOM_FIBRE_2
,GEOM_FIBRE_3
.
To test if a point is tissue, use isTissue(x,y,z)
. The
same rules apply to Geom
as to New
, in
particular, stick to your Space
structure and don’t
reference any further away than ( x ± 1,y ± 1,z ± 1)
.
Commonly, a device will take some parameters from the user via the script. We call this process ‘accepting’ a parameter. There are two steps to this process:
qpp.h
.
Parameter accept macros provide a quick way to pull a parameter value from the script and assign it to its namesake field in the device’s parameter structure. The first thing to consider is the type of parameter you’d like to accept. There are four common types:
Integers
Requires an int
in the
parameter structure.Real Numbers
Requires a double
in the
parameter structure.Strings
Requires a char[]
array in the
parameter structure.Files
Requires two fields in the parameter structure;
a FILE *
with the same name as the parameter, and
a char[]
array of size MAXPATH
with the name
<param>name
, where <param>
is
the name of the parameter.The corresponding parameter accept macros are listed below. They each have a default argument that, if given the corresponding null value, will require that the user supplies a value, rather than merely making it optional.
ACCEPTI(name,deflt,minv,maxv);
name
Name of the parameter whose value is to be read.
deflt
Default value, in case no parameter
called name
is found. If INONE
, a script
parameter MUST be found.
minv
Minimum allowed value. If INONE
, no
minimum is prescribed.
maxv
Maximum allowed value. If INONE
, no
maximum is prescribed.
ACCEPTR(name,deflt,minv,maxv);
name
Name of the parameter whose value is to be read.
deflt
Default value, in case no parameter called
name
is found. If RNONE
, a script parameter
MUST be found.minv
Minimum allowed value. If RNONE
, no
minimum is prescribed.maxv
Maximum allowed value. If RNONE
, no
maximum is prescribed.ACCEPTS(name,deflt);
name
Name of the parameter whose value is to be
read.deflt
Default value, in case no parameter called
name
is found. If NULL
, a script parameter
MUST be found.ACCEPTF(name,mode,deflt);
name
Name of the parameter whose value is to be
read.mode
Access mode in which the file is to be
opened. Syntax is identical to a fopen()
call:
r
or rb
Open existing le for reading.w
or wb
Create le or wipe existing le
before writing.a
or ab
Append to end of existing le,
creating if necessary.rt
or rbt
or rtb
Open
existing le for updatereading and writing.wt
or wbt
or wtb
Create le
or wipe existing le before updating.at
or abt
or atb
AppendOpen
or create le for update, writing at end of le.deflt
Default filename, in case no parameter called
name
is found. If NULL
, a filename MUST be
found in the script.
To aid access to fields of the parameter
structure, device.h
provides shortcut macros to declare
local variables and initialise them with values from their namesake
fields in the parameter structure.
CONST
is used to copy values from S
to a
local variable.
VAR
copies the address of a variable in S
to
a local pointer.
ARRAY
copies the address of the first element of an array
in S
to a local pointer.
Beatbox allows simulations to be run on distributed-memory parallel machines using MPI. Many devices can be run in parallel without special adaptation, but it’s difficult to tell exactly what will and won’t work in parallel. The ‘Laws of Devices’ below describe how a device should behave in order to ensure its safety in parallel. If these rules aren’t followed, there is a very real chance that your device will crash horribly in parallel, or worse, quietly produce incorrect results. Please read and stick to them.
New
in its space,
at points in its space.New
must be commutative — the order
of operation cannot be assumed.New
, a device may
only alter the values of local variables or those in its parameter
structure.New
.Space
structure.local_xmax
).New
beyond {x ± 1 ,y ± 1 ,z ± 1}
.New
must put the DEVICE_REQUIRES_SYNC
macro in
its Create
function.Geom
beyond {x ± 1 ,y ± 1 ,z ± 1}
.
If you’re planning on making a device that needs to do something
forbidden by the laws above, it will need to be explicitly
parallelised to run in parallel. Please consult your local MPI
expert. In the meantime, you can enable the device as sequential only,
by going to devlist.h
and changing the D
before your device’s name to an S
. It’s now one of the
naughty devices.
Beatbox has the functionality to output ppm
files which
allow easy visualisation, especially of large data sets. The
visualisation can be further improved using BBXVIEW
package.
To convert ppm
to jpg/png
, use something
like this in a shell script from netpbm
and usually 0. If
voltage is being output to the ppm
files, then the:
for (( i = $start; i <= $finish; i++)) do ppmtojpeg out/ord2_${i}.ppm > jpegs/ord2_${i}.jpg done
Further, to make an animation, use ImageMagick using the convert command:
convert -delay 20 -loop 0 sphere*.gif animatespheres.gif
A shell script to handle these tasks is here:
[PUT SHELL SCRIPT].
ParaView
can be used to make animations using the
3D ppm
data. To do this, first run the
script ppm_2_paraview. This perl script
needs the inputs bgr bgg bgb channel threshold
filename.ppm
where the rgb values are from the bbs file,
or default values (255 255 255)
. The channel is again
from the bbs file threshold is usually around 127. Once the vtk files
are produced in this manner, use paraview to prepare the visualisation
interactively using the first file. An output to animation will then
produce the desired
jpeg
s / tiff
s
/ png
s. Use convert
from ImagemMagick to make
a low maintainence animated gif
that can be put into
powerpoint or web pages.
call graphs to show how the code works for developer's.
Following is a method involving minimal manipulation of CellML cell model code to incorporate it into Beatbox format. We use the OHara-Rudy 2011 model as an example to illustrate the method.
Published cardiac and other simulation models are distributed by means
of the CellML Project website, that can be found
here: CellML Project. In
particular, the cell models are listed in the
repository: CellML
Repository. The desired CellML file should be downloaded from the
repository. The file that we are looking for is usually has an
extension of .cellml
. As an example, the OHara-Rudy (ORd)
cell model file looks like this: ORd
CellML file. There are other formats in which the cell model may
be provided (C, C++, MATLAB,...) and the best location to get the
CellML file is from the provided tar.gz download all files section.
The information in the CellML file obtained as in Step 1 needs to be
converted into suitable C format. In principle, the converted codes
as delivered by the CellML website may be sufficient. However, to
obtain all the information required to construct the cell model in
Beatbox format (i.e. initial conditions and the algebraic-differential
equations for the model dynamics) is ideally obtained by means of COR
software. COR (Cellular Open Resource) is primarily a Windows
software, but may be installed on Linux and MAC-OS if the operating
system is 32 bit or if there are 32 bit emulators available. As of the
date of this document, a Linux variant is also being developed. So the
next thing to do is download and
install COR. Once it is
installed, open the CellML file that you downloaded in Step 1 and
export it to C. The exported files are a .c
and
a .h
with the same name as the CellML file. For example,
the ORd exported files
are:Ohara_Rudy_2011.c
and Ohara_Rudy_2011.h.
The header file as exported in Step 2 simply declares all constants
and variables. The format that the declarations appear in the header
file depend on the original CellML file and how the model authors
provided the original codes to CellML. In the C file
(Ohara_Rudy_2011.c
in our case), there is hopefully an
annotated section (called "Constants" or similar) that shows the model
constants and this section should be identified. It should be borne in
mind that these constants should be simply numerical assignments to
place holders, i.e. a formula should not appear on the right hand side
of these assignments. Further, due to the Beatbox format, these
modelling constants should all be real (i.e. float
or double
) numbers. All derived constants and integer
constants are handled otherwise (see below).
Take this list of constants and put them in a file called, say
ord_par.h
. The format of the list of constants should be further
manipulated such that Beatbox macro's can do what they should do. The
final ord_par.h
should look like
this: ord_par.h.
A void init(void)
(or similarly named function) is
provide in the exported C file. Contents of this function without any
alterations should be copied to another file. In the ORd example, we
choose to call this file ord_init.h
. Note that none of
the contents of the initialisation have been altered. The exported C
file probably (hopefully) uses a vector to maintain values of dynamic
variables, and that should also be retained to reduce manipulation of
original code. The prepared ord_init.c
file can be found
here: ord_init.h. Note that unlike in the
parameter file of Step 3, the code in the initialisation has not been
manipulated at all.
After having defined the constant real parameters and assigned initial
values to the dynamic variables vector, we now construct the code that
will be used for the actual cell model integration. This step starts
with identifying the function called void compute(double t) or similar
in the exported C file. The contents of this function should be
imported into another header file, say called ord_step.h. You may
notice that in this code, there are several derived variables
(e.g. currents, fluxes,...), integer constants, etc. These derived
variables are typically declared as "Computed Variables" or with
similar annotation. These declarations should be put at the top before
the model code. The final ord_step.h
look like this:
ord_step.h. Note that no integration (forward
Euler or otherwise) is included in this ord_step.h
- this
is simply the code for computing the next time step increment. A
vector called dY[] may also be found in this code. We will deal with
this in a bit.
A constant variable needs to be defined to tell several of Beatbox macros about the new cell model. For example, in case of the ORd, create a file ord.on with the following line of code:
#define ORD 1
You can see the file: ord.on.
The list of RHS of cell models needs to be updated. This list is in:
Beatbox/src/rhslist.h
of the package. Code, similar to what is shown below, should be added to this file:
#if ORD D(ord) #endif
Explanations on the meaning of the macro D, the choice of names etc. are to be found in the developers guide.
The main C wrapper that initialises, declares local placeholders, and provides the cell model code to the Euler solver is given a consistent name. For example, in case of ORd, it is simply called ord.c. This wrapper contains headers, macros, and basically includes the code from the above files.
#define N
directive.
RHS_CREATE_HEAD
macro, we initialise members of the
structure with constant parameters and initial values using the files
that we created above. In the RHS_RUN
macro, first the
parameters are declared as part of the structure S
. Since
the local vector of variables is probably called something different
from u[]
(probably Y[]
), we make that
association here.
_step.h
. If
required, add the current stimulus to the appropriate ODE variable and
then the macro can be closed.
There is a Makefile.am
in the Beatbox/src
directory. Add entries for .on
, _step.h
,
_init.h
, _par.h
, and .c
files
that you have just created. Save, do a distclean, configure, and make
again. Hopefully, it all compiles. To start using the cell model, make
a bbs
file as shown in the template below.
bbs
file template: ord0.bbs:
Output a 4D box to a file, each mode value reduced to unsigned short.
The file format is pure information, without markers.
Compresses the data in the SPACE and outputs to the file
. If file is
specified with extension (the string contains period "."), and file with this
name exist, it is not erased, but the new data are appended to its end.
Otherwise, output files created by this device will have names file.000,
file.001... The device will determine the minimal extension that such a
name does not exist, and begin with this extension.
Compressed are dynamical variables with numbers v0...v1 inclusive. Compression of data includes:
1) bringing the values to the range [u0, u1];
2) discretization of this values, by mapping [u0,u1] onto {0... 255},
making each value to occupy one byte;
3) compressing the array of bytes according to the value of compress parameter, and writing resulting sequence of bytes to the file. Nothing about compression you need to know, but for: (a) this array will be understandable by programm RECOMP which is one of satellite programs of Beatbox, and is described separately, (b) compress=0 means no compression, i.e. all the 4D array of bytes (x0..x1,y0..y1,z0..z1,v0..v1) will be stored in the file byte by byte, without any markers, (c) compress=-2 seems to be the optimal value in most cases, and may save up to 98\% of disk space occupied by the output binary files. row and col have the same meaning as for dump and load devices.
Type | Name | Description |
---|---|---|
int | v0 | starting dynamical variable number. |
int | v1 | Ending dynamical variable number. |
int | u0 | Starting value of output range. |
int | u1 | Ending value of output range. |
int | row | row (same as in dump device). |
int | col | column (same as in dump device). |
int | compress | compress = 0 is for no compression, -2 is for best available compression. |
str | file | Output file name. |
int | append | Append to existing file or not. |
beatbox/data/scripts/sequential/fhn3.bbs
Prints the current timestep to stdout
.
None.
clock
device is as follows.
The typical syntax of diff device is as follows.
beatbox/data/scripts/sequential/pd_crn0.bbs beatbox/data/scripts/sequential/pd_crn1.bbs
Saves the entire simulation state for disaster recovery, or to facilitate a branching point in long simulations.
Type | Name | Description |
---|---|---|
str | file | Control point file to be restored from / saved to. |
int | enrich | Can the restored control point define k variables not present in the script. |
Type | Name | Description |
---|---|---|
real | ht | time step |
int | vd | Dynamical variable for which derivative is sought. |
beatbox/data/sequential/fhn0.bbs
Computes the Laplacian, with implicit Neumann boundary conditions.
diff
must be used with the default
space. The v0
space parameter is used to indicate the
diffused variable, usually the transmembrane
voltage. The v1
space parameter is used to indicate the
layer in which the Laplacian should be stored.
Type | Name | Description | Isotropic or Anisotropic |
---|---|---|---|
real | hx | Space step. | Isotropic Only |
real | D | Isotropic diffusion coefficient. | Anisotropic Only |
real | Dpar | Anisotropic diffusion coefficient parallel to the fibres. | Isotropic |
real | Dtrans | Anisotropic diffusion coefficient in the transverse direction, perpendicular to the fibres. | Anisotropic |
diff
device is as follows. Assuming
that the diffusing layer is diff_layer
(a number) and the layer in which
the Laplacian is to be stored is lap_layer
, the syntax for the isotropic
diffusion is
diff v0=diff_layer v1=lap_layer hx=hx D=D;where uniform diffusion is used throughout the tissue. In case of anisotropic diffusion, the syntax changes to account for diffusion parallel to the fibres (signified by
Dpar
)
and transverse to the fibres (signified by Dtrans
) and is
diff v0=diff_layer v1=lap_layer Dpar=D Dtrans=D/4 hx=hx;
beatbox/data/scripts/sequential/fhn1.bbs beatbox/data/scripts/sequential/fhn2.bbs beatbox/data/scripts/sequential/fhn3.bbs beatbox/data/scripts/sequential/crn1.bbsand for the anisotropic case can be found in
beatbox/data/scripts/sequential/LRD_ffr.bbs
Writes the contents of the simulation medium within its space to a binary file.
Type | Name | Description |
---|---|---|
str | file | Relative path to the dump file. |
int | append | Should the file be appended to? |
dump
device is as follows. Assuming that the dynamical
layers are between ode_layer0
(a number) and the layer
ode_layer1
the syntax for the dump device is
dump when=end v0=ode_layer0 v1=ode_layer1 append=0 file=my_dump_file.dmp;
beatbox/data/scripts/sequential/fhn1.bbs beatbox/data/scripts/sequential/LRD_3d_box.bbsThe simulation state saved in such a manner may be used to continue the simulation using the
load
device.
Type | Name | Description |
---|---|---|
real | D | Diffusion constant |
real | hx | space step |
int | xl, yl, zl | x, y, z location of ECG point electrode. |
General pointwise ODE stepper.
Type | Name | Description |
---|---|---|
real | ht | Timestep duration. |
str | ode | Name of the RHS module to use. |
int | rest | Number of timesteps of the RHS module to run before starting simulation. Can be useful for finding resting initial conditions. |
euler
device is as follows. Assuming
that the ODE layers are between ode_layer0
(a number) and the layer
ode_layer1
, the ODE module (i.e. cell model) that you are using od ode_module, and
the user defined model parameters are defined in bbs parameters p1, and p2, the syntax is
euler name=name_to_device_call v0=ode_layer0 v1=ode_later1 ht=ht ode=ode_module rest=100 par={par1=p1; par2=p2;};You can given the device call a convinient name,
name_to_device_call
in this case,
to refer to your device elsewhere in the bbs script.
beatbox/data/scripts/sequential/fhn0.bbs beatbox/data/scripts/sequential/fhn1.bbs beatbox/data/scripts/sequential/fhn2.bbs beatbox/data/scripts/sequential/fhn3.bbsIndeed, all bbs scripts in the examples use the euler device.
The only parameter is the space step.
beatbox/data/sequential/fhn2.bbs
k_clock
is similar to clock
device (see below), except ...
Type | Name | Description |
---|---|---|
int | col0 | starting column of graphics. |
int | col1 | end column of graphics. |
int | row0 | starting row of graphics. |
int | col1 | end column of graphics. |
int | color | Color code of line graphics. |
def str win1 col0=col0 col1=col1 row0=row0 row1=row1 color=15*16+15; :k_draw when=always [win1] color=BLUE*16;
beatbox/data/scripts/sequential/pd_crn0.bbs
Allows the user to specify user defined functions to be executed as part of the simulation run.
Type | Name | Description |
---|---|---|
str | file | Relative path to file for phase distribution. See Beatbox Scripting Guide. |
str | debug | Relative path
to output file for debugging purposes. If stdout , will
print to screen. |
Codeblock | pgm | The
function to be executed when the device is run. Beatbox script
statements, separated by semicolons. Users should note that the
behaviour of k_func changes significantly in relation to
the nowhere parameter.When nowhere=1 , assignments can be
made to k variables only. Assignments from the simulation medium or
random number generators are disallowed. When nowhere=0 ,
assignments can be made to the simulation medium only. It is possible
to define intermediate variables in k_func programs,
using the def keyword. Variables must be initialised before they are
used, and are reinitialised on each iteration of
the k_func program. |
Type | Name | Description |
---|---|---|
int | col0 | starting column of graphics. |
int | col1 | end column of graphics. |
int | row0 | starting row of graphics. |
int | row1 | end column of graphics. |
int | color | Color code of line graphics. |
int | linewidth | Width of line. |
int | absmin | Minimum value of variable. |
int | absmax | Maximum value of variable. |
int | clean | Clean or not. |
beatbox/data/scripts/sequential/pd_crn1.bbs
Loads data from a file saved by dump
into the simulation
medium.
Type | Name | Description |
---|---|---|
str | file | Relative path to the file to be read. |
int | rewind | Should the file be rewound before reading? |
load
device is as follows. Assuming that the dynamical
layers are between ode_layer0
(a number) and the layer
ode_layer1
the syntax for the dump device is, and that the dump file (see above)
has been saved to a file called my_dump_file.dmp
, then the syntax is
load when=begin v0=ode_layer0 v1=ode_layer1 append=0 file=my_dump_file.dmp;
beatbox/data/scripts/sequential/fhn1_load.bbs
Samples a dynamic variable at a single point in the mesh, to detect a crossing in time of a specified value.
The poincare
device must be run on a single point. Upper
space parameters x1
, y1
, z1
and v1
are unused by this device. The v0
space parameter is the dynamic variable to be sampled at point
(x0,y0,z0)
.
Type | Name | Description |
---|---|---|
k variable | result | Name of the k variable into which the result should be stored — 1 if a crossing has been detected on this timestep, 0 otherwise. k variable timestep Name of the k variable into which the timestep of the last crossing should be stored. An assignment will only be made to this variable on the timestep when a, crossing is detected. |
real | cross | The crossing threshold. If previous and current values of the sampled dynamic variable straddle cross, a crossing is detected. |
int | sign | The sign of the crossings to
be detected:
|
Produces PPM images of the space. Values from specified layers of the simulation medium are mapped to colour channels by linear interpolation using specified minima and maxima. ppmout produces a new, sequentially numbered file every time it is run. In three-dimensional meshes, ppmout produces non-standard PPM files, in which multiple z-layers are stacked into a single file.
Type | Name | Description |
---|---|---|
Sequence | file | Format specifier for the sequence of files produced. |
int | r | Layer of the simulation medium from which the red colour channel should be mapped. |
real | r0 | Value of the layer assigned to r that should map to 0. |
real | r1 | Value of the layer assigned to r that should map to 255. |
int | g | Layer of the simulation medium from which the blue colour channel should be mapped. |
real | g0 | Value of the
layer assigned to g that should map to 0. |
real | g1 | Value of the
layer assigned to g that should map to 255. |
int | b | Layer of the simulation medium from which the green colour channel should be mapped. |
real | b0 | Value of the layer assigned to b that should map to 0. |
real | b1 | Value of the
layer assigned to b that should map to 255. |
Geometry Only | ||
int | bgr | Red value of the background colour, to be displayed at void points. |
int | bgg | Green value of the background colour, to be displayed at void points. |
int | bgb | Blue value of the background colour, to be displayed at void points. |
ppmout
device is as follows.
ppmout file="ppm/%04d.ppm" mode="w" r=[u] r0=umin r1=umax g=[v] g0=vmin g1=vmax b=[i] b0=0 b1=255;where the red, green, and blue channel are defined by maximum, minumum, and mid-point values of user defined dynamical variables.
beatbox/data/scripts/sequential/fhn2.bbs beatbox/data/scripts/sequential/fhn2.bbs beatbox/data/scripts/sequential/pd_crn2.bbs
record writes values from the simulation medium to file in ASCII
format. A hierarchical set of separators
— vsep
, xsep
, ysep
and zsep
— are used between
values. recordsep
is used between records, i.e. separate
runs of the record device.
Type | Name | Description |
---|---|---|
str | file | Relative path to the file to be written. |
int | append | Should the file be appended to? |
str | filehead | Data to be placed at the top of the record file. |
str | vsep | v-axis separator. Must be at most 2 characters in length. |
str | xsep | x-axis separator. Must be at most 2 characters in length. |
str | ysep | y-axis separator. Must be at most 2 characters in length. |
str | zsep | z-axis separator. Must be at most 2 characters in length. |
str | recordsep | Record separator. Must be at most 2 characters in length. |
record
device is as follows.
record when=often file=output_record_file_name.rec append=0 x0=1 x1=xmax-1 y0=1 y1=ymax-1 z0=1 z1=zmax-1 v0=ode_layer0 v1=ode_layer1;
beatbox/data/scripts/sequential/fhn0.bbs beatbox/data/scripts/sequential/fhn1.bbs beatbox/data/scripts/sequential/pd_crn0.bbs beatbox/data/scripts/sequential/pd_crn1.bbs
Reduce performs a reduction operation — sum, product, minimum or maximum — over its space. The result of the operation is assigned to a specified k variable.
Type | Name | Description |
---|---|---|
k variable | result | Name of the k variable into which the result should be stored. |
str | timestep | Name of the operation to perform. Accepted values are “ sum ”, “ product ”, “ min ” and “ max ”. |
reduce
device is as follows.
reduce name=U_sample operation=max v0=[u] result=U;
beatbox/data/scripts/sequential/fhn0.bbs beatbox/data/scripts/sequential/fhn1.bbs
Assigns a dynamic variable value from a specified point in the mesh to a k variable.
The sample device must be run on a single point. Upper space
parameters, x1
, y1
, z1
and v1
are unused by this device. The v0
space parameter is the dynamic variable to be sampled at
point (x0, y0, z0)
.
Type | Name | Description |
---|---|---|
k variable | result | Name of the k variable into which the sampled value should be stored. |
sample
device is as follows.
sample x0=x_loc y0=y_loc z0=z_loc v0=ode_layer result=name_of_place_holder;
beatbox/data/scripts/sequential/crn0.bbs beatbox/data/scripts/sequential/crn0_rest.bbs beatbox/data/scripts/sequential/crn1.bbs
Stops the simulation run.
None.
stop
device is as follows. Assuming
that the variable for end has been defined in the variable end
, the syntax is
stop when=end;
update
updates the run time visualisation.
Type | Name | Description |
---|---|---|
int | when | Frequency of updating |
beatbox/data/sequential/pd/scripts_crn0.bbs beatbox/data/sequential/pd/scripts_crn1.bbs
Type | Name | Description |
---|---|---|
int | r | Dynamical layer to output |
int | r0, r1 | minimum and maximum of the expected values in later r |
fhncubpar
and fhncub
. The example of FHN cell model AP is in
beatbox/data/scripts/sequential/fhn0_ap.bbsUsing this bbs program, you can produce an AP profile as shown in the figure below.
fk
. The example of FK cell model AP is in
beatbox/data/scripts/sequential/fk0_ap.bbsUsing this bbs program, you can produce an AP profile as shown in the figure below.
fkmod
/ The examples of the cell model are
in
beatbox/data/scripts/sequential/FKMOD/where a Makefile detailing the SAN and atrial parameters is give. Using these bbs programs, you can produce a SAN profile as shown in figure below.
lrd
. The example of LRd cell model AP is in
beatbox/data/scripts/sequential/lrd0_ap.bbsUsing this bbs program, you can produce an AP profile as shown in the figure below.
crn
. The example of CRN cell model AP is in
beatbox/data/scripts/sequential/crn0_ap.bbsUsing this bbs program, you can produce an AP profile as shown in the figure below.
ttnnp04
. This implementation is based on
author's code
retrieved 2013/02/02. It does not provide the true ODE right-hand
sides, instead it performs the timestep, using Rush-Larsen formula for
all gating variables and explicit Euler for all other variables.
Hence it requires the time step as parameter ht
.
beatbox/data/scripts/sequential/ttnnp04_ap.bbs
ttnp
. This implementation is based on CELLML
version and provides true ODE right-hand sides, unlike TTP06
implementation described below. Note that the resulting ODEs are very
stiff and require very small time steps.
The example of TTNP cell model AP is in
beatbox/data/scripts/sequential/ttp06_ap.bbsUsing this bbs program, you can produce an AP profile as shown in the figure below.
ttp06
. This implementation is based on
author's code
retrieved 2013/02/08. Unlike implementation TTNP described above, this
one does not provide the true ODE right-hand
sides, instead it performs the timestep, using Rush-Larsen formula for
all gating variables and explicit Euler for all other variables.
Hence it requires the time step as parameter ht
.
beatbox/data/scripts/sequential/ttp06.bbs
A slightly more advanced example of this model can be found in the directory:
beatbox/data/scripts/sequential/ttp06/
The benchmark.bbs
script there does the computational
job, Makefile
describes the workflow leading to the final
report and report.pdf
is the report itself.
Copyright © (2010-2012) Vadim Biktashev, Alexander
Karpov,Irina Biktasheva, Ross McFarlane;
Copyright © 2012Sanjay Kharche
This file is part of the Beatbox distribution.