Configuring Simulations (1)
Once you have written a first simulation, you can adapt it by modifying parameters, or you can increase the number of cells in your simulation and examine interactions between different cell types.
The Simulation class allows you to make many of those changes by simply modifying its configuration object, which will be the topic of this tutorial. The next tutorial will show you how you can make even more advanced simulations by writing custom functions — but for now we will focus on changes you can make through configuration only.
We will start from this very simple simulation from the previous tutorial:
This tutorial assumes you have completed the previous tutorial, and we will
examine the config
object of the simulation you generated there in more detail:
let config = {
// Grid settings
field_size : [100,100],
// CPM parameters and configuration
conf : {
T : 20, // CPM temperature
// Adhesion parameters:
J: [[0,20], [20,100]] ,
// VolumeConstraint parameters
LAMBDA_V : [0,50], // VolumeConstraint importance per cellkind
V : [0,500] // Target volume of each cellkind
},
// Simulation setup and configuration
simsettings : {
// Cells on the grid
NRCELLS : [1], // Number of cells to seed for all
// non-background cellkinds.
RUNTIME : 500, // Only used in node
CANVASCOLOR : "eaecef",
zoom : 4 // zoom in on canvas with this factor.
}
}
We will now see how changes to this object can alter the simulation.
Configuring the CPM itself
First, let's see how we can control the CPM model class itself. For this lesson, we recommend you start from the HTML version of the simulation in the previous tutorial. This HTML version will allow you to see the effect of your changes more quickly than running a node script would.
Changing CPM parameters
As we've seen before, the config
object has three parts: the
field_size
, the conf
, and the simsettings
.
We will now look at the conf
part, which controls the CPM model itself.
// CPM parameters and configuration
conf : {
T : 20, // CPM temperature
// Adhesion parameters:
J: [[0,20], [20,100]] ,
// VolumeConstraint parameters
LAMBDA_V : [0,50], // VolumeConstraint importance per cellkind
V : [0,500] // Target volume of each cellkind
},
The CPM shown here has only two constraints: the
Adhesion and
Volume
constraints. The T
parameter belongs to the CPM itself and controls to
what extent the model "listens" to the energy rules it gets. The higher T, the more likely
the model is to misbehave. But if T is too low, the cell might get stuck in a state it
can't get out of.
Exercise :Try some different values for T here, and see what happens with your simulation.
The other parameters here belong to the constraints. The J
values form
a matrix controlling the adhesion between cell and background. The $J_{00}$ value, which
is here 0, is never used (the adhesion constraint only counts adhesion between pixels
belonging to different cells, and since there is only one background "cell", there can
be no background-background adhesion). The $J_{01}$ and $J_{10}$ values (here 20) are
the adhesion between cell and background, and are important for this simulation.
The $J_{11}$ would be the adhesion between two different cells and is also not relevant
yet, since there is only one cell.
The V
and LAMBDA_V
parameters control the volume constraint,
see its documentation.
Exercises :
- Change the values in
J
, and try to make the cell fall apart. Do you understand why this happens?- How would you make the cell twice as big? Try it.
Finally, note that we can also add more different constraints to the model via the
conf
object. We'll do that in a later tutorial.
Setting a random seed
If you look at the simulation on the top of this page and then refresh a few times,
you should see that every simulation "run" goes a little differently. This happens because
the CPM is stochastic. If you don't want this, because you want someone else to be able
to reproduce exactly your results, you can try setting a random seed
:
// CPM parameters and configuration
conf : {
T : 20, // CPM temperature
seed : 1,
// Adhesion parameters:
J: [[0,20], [20,100]] ,
// VolumeConstraint parameters
LAMBDA_V : [0,50], // VolumeConstraint importance per cellkind
V : [0,500] // Target volume of each cellkind
},
Exercise :Set a random seed in your simulation's HTML file. Refresh the page a few times and look what happens. What is different now?
Configuring the grid
Field size
Open your file MyFirstSimulation.html
from the previous tutorial in the web
browser. You should see a black cell floating inside a square gray area, which
is the field or "grid" the simulation runs in.
The size of this grid is controlled by field_size
in the config
object. The entry
// Grid settings
field_size : [100,100],
ensures that the simulation is performed on a 100 x 100 pixel (2D) grid. We can change
the size of this grid by changing the numbers x
and y
between
the square brackets: [x,y]
, where x
controls the grid's width
and y
controls its height. For example, changing this setting to:
// Grid settings
field_size : [150,100],
makes the grid slightly broader, but equally high as before.
Exercise :How would you make a grid with a width of 100 pixels and a height of 200 pixels? Open your file
MyFirstSimulation.html
in your favourite text editor, change thefield_size
in the appropriate manner, and save the file. Now openMyFirstSimulation.html
in your web browser (or just refresh the page if you had already done so). Did it work?
Grid boundaries
Have a look at the following simulation:
When the cell leaves the grid on the right, we see that it re-enters on the left. We call this a grid with periodic boundaries. Because grid boundaries are linked, the cell feels as though it is roaming an infinite space.
In the CPM
object, the grid boundary conditions can be set through the
torus
property in the configuration object like this:
// CPM parameters and configuration
conf : {
T : 20, // CPM temperature
torus : [true,true],
// Adhesion parameters:
J: [[0,20], [20,100]] ,
// VolumeConstraint parameters
LAMBDA_V : [0,50], // VolumeConstraint importance per cellkind
V : [0,500] // Target volume of each cellkind
},
When torus=true
, this means that the grid boundaries are linked as in the
example above (this is also the default when we don't supply the torus
property in the configuration settings). If we don't want linked boundaries, we can
change this value to false:
// CPM parameters and configuration
conf : {
T : 20, // CPM temperature
torus : [false,false],
// Adhesion parameters:
J: [[0,20], [20,100]] ,
// VolumeConstraint parameters
LAMBDA_V : [0,50], // VolumeConstraint importance per cellkind
V : [0,500] // Target volume of each cellkind
},
However, what we get is not quite what we intended:
The problem here is that we can't just say that there are boundaries without telling the CPM how to deal with those boundaries. A pixel at the border of the grid will have fewer neighboring pixels to contribute to its adhesion or surface energy, which will give artefacts. In reality, any physical boundary a cell might encounter would contribute to adhesion energy.
To solve such problems, it is possible to create a layer of boundary pixels at the edges of the grid, to which the adhesion can be explicitly defined. See this example for details and implementation (once you see the simulation, right click on the page (outside of the simulation grid) and select "view page source" to see code).
Configuring the Simulation object
The Simulation object automatically takes care of many aspects of the simulation, such as seeding cells on the grid, running the simulation for a number of steps, drawing the output images and computing output statistics. We'll discuss how to tune some of these aspects here.
Have a look at the documentation for the constructor
of the simulation
class here.
All the options listed under simsettings
here are options to configure the
simulation, which we will discuss here.
Controlling the simulation
Using the option NRCELLS
, we can specify how many cells of each
CellKind we want to seed
on the grid at the beginning of the simulation. Note that this needs to be an array with
one element for each non-background cellkind on the grid. This means this array is one
element shorter than most typical CPM parameters (e.g. LAMBDA_V
) which are
specified for each cellkind including background. If the lengths of these
different arrays are not consistent with each other, the CPM will get confused about
how many cellkinds you want on the grid. It will crash.
By default, the simulation class seeds them in random positions. We'll discuss how
to seed cells in more customized locations in a later tutorial. But for now, try
modifying the NRCELLS
property of your simulation and see what happens.
The RUNTIME
property determines for how many Monte Carlo Steps (MCS) the
simulation will run. This property is actually only used by node simulations.
You can use the RUNTIME_BROWSER
property in the browser, but then you must
use that in your step()
function like this:
function step(){
sim.step()
meter.tick()
if( sim.conf["RUNTIME_BROWSER"] == "Inf" | sim.time+1 < sim.conf["RUNTIME_BROWSER"] ){
requestAnimationFrame( step )
}
}
This way, the simulation will only keep running until RUNTIME_BROWSER
is
reached. If we wouldn't do this, the browser simulation would just keep running infinitely
(or in practice, until your patience or your computer's battery runs out and you close
the browser window).
Controlling the visualization
The Simulation class contains a default method
drawCanvas()
that takes care of drawing the grid for you (if you follow the link, you can click "source"
on the far right to see the code). You can control the way this function draws
the grid via the simsettings
object as well, by setting:
CANVASCOLOR
, the color code to draw the background in. If left unspecified, this is white ("FFFFFF""
).CELLCOLOR
, the color code to draw the cells in. If left unspecified, this is black ("000000""
). If you do wish to specify it, you must provide an array with a color for each CellKind just like we did for NRCELLS above. E.g. for one black and one red cell kind, that would be["000000","FF0000"]
ACTCOLOR
, whether or not to draw the activity gradient if the cells have an ActivityConstraint. If left unspecified, this is set tofalse
for each CellKind.SHOWBORDERS
, whether or not to draw the borders of each cell as well as their area. This isfalse
by default.BORDERCOL
, the color to draw the cell borders in, once more specified for each CellKind. This is only used wheneverSHOWBORDERS
is set totrue
.zoom
the factor by which to enlarge the image (note that using a higher zoom factor may slow down your simulation, since it takes more time to draw the grid in a larger image. If a simulation is fast enough, this won't be an issue, but if you notice your simulations being too slow you might want to adjust this factor).
For all the options involving color, this online color picker may come in handy.
Exercise :Given all the information we have covered so far, can you reproduce a simulation that looks roughly like the one below?
Controlling outputs
The simulation class helps you by generating outputs automatically. This happens inside
the function createOutputs()
,
which does two things:
- Drawing things on the screen (browser version) or on an output PNG image (node version)
- Computing and logging statistics to the console (both versions)
In node, you can control the following aspects of the PNG images that are written:
SAVEIMG
, are any images saved at all? Defaults tofalse
;IMGFRAMERATE
, how often should an image be saved? Defaults to1
(save an image every simulation step), but you might set it to e.g.10
to save an image only once every 10 steps. This may significantly speed up your simulations since drawing images can be a speed bottleneck. Very often, drawing every step is not necessary to get a smooth movie in the end;SAVEPATH
, in which folder should images be stored? You have to make sure this folder exists, or you will get an error. Although this option is documented as "optional", this is only true because you don't have to give it whenSAVEIMG=false
. If you setSAVEIMG=true
, you must provide this option.EXPNAME
, a name used to generate the name of the output images. These will be stored as[SAVEPATH]/[EXPNAME]-t[STEP].png
. Defaults to"mysim"
.
Note that the options above only apply to node, since the browser version does not support
writing PNG images. If you want to make a movie from the browser version, just record your
screen. If you wish to change the IMGFRAMERATE
of your browser simulation,
you can adjust your step()
function (see also
this tutorial) to something like this:
function step(){
// Run IMGFRAMERATE simulation steps before rendering
for( let s = 0; s < config.simsettings.IMGFRAMERATE; s++ ){
sim.step()
}
requestAnimationFrame( step )
}
Note that
sim.step()
will still call
drawCanvas()
every
step, but the grid will only be rendered once every IMGFRAMERATE
steps. Since rendering is often the bottleneck, this will still speed up your simulation
most of the times.
The createOutputs()
also takes care of logging statistics to the console. By default, it logs the simulation
step, the cellID, the
CellKind, and the centroid
of every cell on the grid. To adjust this, you can overwrite this method, which we will
discuss in a later tutorial.
Two settings in the simsettings
object control the logging of statistics:
LOGSTATS
, specifying for browser and node separately whether statistics should be logged to the console. Defaults to{ browser: false, node: true }
;LOGRATE
, specifying how often statistics are computed and logged. Defaults to1
(log every simulation step), but you might set it to e.g.10
to log only once every 10 steps.