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 :
  1. Change the values in J, and try to make the cell fall apart. Do you understand why this happens?
  2. 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 the field_size in the appropriate manner, and save the file. Now open MyFirstSimulation.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 to false for each CellKind.
  • SHOWBORDERS, whether or not to draw the borders of each cell as well as their area. This is false by default.
  • BORDERCOL, the color to draw the cell borders in, once more specified for each CellKind. This is only used whenever SHOWBORDERS is set to true.
  • zoomthe 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:

  1. Drawing things on the screen (browser version) or on an output PNG image (node version)
  2. 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 to false;
  • IMGFRAMERATE, how often should an image be saved? Defaults to 1 (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 when SAVEIMG=false. If you set SAVEIMG=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 to 1 (log every simulation step), but you might set it to e.g. 10 to log only once every 10 steps.