User Tools

Site Tools


Exponential growth: exp_growth.nano

This page is accurate as of Nanoverse v1.0.0-a6. If you are using a later version, some aspects may have changed.

The sample file exp_growth.nano in the ./samples directory creates a single agent in a random location. That agent then divides ten times, at which point the simulation ends. Let's walk through the various parts of this script.

Describing a project

In an imperative programming language like Python, R or even NetLogo, the user creates a file that issues a series of instructions, which are resolved in order.

By contrast, a .nano file is essentially a parameters file: it describes the properties of the simulation, including descriptions of agents and of space.

This makes Nanoverse a declarative programming language. One consequence of that is that Nanoverse doesn't create an agent because you issue an instruction to do so. Rather, you describe what kinds of agents you want, and when you want them to appear, and Nanoverse designs a simulation plan that will satisfy your description.

Defining the agents

The agents in this simulation need to be able to reproduce. They don't need to be able to do anything else. This is our agent description: something that can replicate itself and place the replica nearby. Agents in Nanoverse can associate specific actions like these with names. These named actions then become behaviors, which can be called either through a process or an action. Nanoverse happens to have an action that consists of exactly what we want, and it's called Expand. So we define the behavior reproduce to consist of the action Expand:

reproduce: Expand;

We could also have said that reproduce involved more steps, like triggering a celebrate behavior in all its neighbors

reproduce {
  Trigger {
    target: OccupiedNeighbors;
    behavior: "celebrate";

If we did that, we would have to define the behavior celebrate as well.

Now that we have our definition of reproduce, we want to define an agent that can perform it. Agents have a property called behaviors, which maps names to behaviors as we have done. This is the only property that we will specify for our agent. In the end, then, the agent looks like this:

Agent: behaviors {
  reproduce: Expand;

We chose to use the block syntax here. Since there is only one behavior in the map, though, we also could have used the single assignment syntax:

Agent: behaviors: reproduce: Expand;

And since Nanoverse doesn't care about whitespace, we could break that up onto as many lines as we like.

Placing the first agent

Now that we have an agent definition, let's schedule a process that will put an agent into the system.

There are two main processes for this in Nanoverse 1.0.0-a1: Fill and Scatter. Scatter randomly places a fixed number of agents within a defined region; Fill completely fills the region. We want to place exactly one agent, and to begin with we don't care where. So we define a Scatter process that places exactly one agent.

Scatter { 
  maxTargets: 1;
  description: Agent: behaviors {
    reproduce: Expand;

Our agent is the description of what we want to scatter, and maxTargets is the maximum number of individuals to be affected. (The argument maxTargets is common to all processes that affect agents.)

As it is, this process will occur once during each pass through the main loop, i.e., once per integration cycle. This would add a linear component to our growth process, which is supposed to be only exponential. To stop this, we set the period argument to zero.

period: 0;

(With apologies to mathematicians, period: 0 in Nanoverse means that something should only happen once.)

What if we wanted to specify that the agent should be right in the center? We can do this using by restricting the activeSites of the process. Like maxTargets, the argument activeSites is available for all processes that affect agents. activeSites expects a set of coordinates. For initial conditions, the most useful coordinate set is often a point or disc at the center of the system. DiscSet is the command for this, and its location defaults to dead-center. (To change this, one can specify an offset from the center). Thus, we could specify that our one-time Scatter affect only the center point like so:

Scatter { 
  maxTargets: 1;
  period: 0;
  activeSites {
    discSet: radius: 0;
  description: Agent: behaviors {
    reproduce: Expand;

Driving reproduction

Now that we have an agent with a reproduce behavior, we need to trigger that behavior. We also want to trigger the behavior in this and all other cells during every cycle in the simulation. To do this, we create a trigger process:

Trigger {
  behavior: "reproduce";

No other arguments are needed, because there's nothing else to say. In the absence of restrictions on maximum agent count, area of effect, or timing, this event will happen to every agent at every cycle. That's just what we want.


The simulation's logical time advances by one unit with each pass through the simulation. However, from the agents' standpoints–and the standpoint of reports and visualizations–everything is happening at once. To advance the simulation time, we cause the simulation clock to Tick forward by one unit every cycle. Tick is a process that does exactly what it sounds like: advance the clock, nothing more and nothing less.

Capturing output

The simulation runs whether you capture its state or not. If you do not, however, you don't have much to show for all that computational effort. Reporting and output is accomplished through the output property of the project description.


The main thing we want from this simulation is a visualization. This seems simple enough to arrange; we include a VisualizationSerializer in the output, and if we're happy with the default, we can leave it at that. Right?

Well, almost. VisualizationSerializer acts after the simulation has run, and has several dependencies which must be captured during the simulation itself. Without these, there is not enough information for the visualization package to generate the desired images.

Note: We plan to add logic that will automatically supply VisualizationSerializer's dependencies when it is specified, but these are not ready as of 1.0.0-a1.

To use VisualizationSerializer, you must also specify the following output options:

  • CoordinateIndexer: Captures information relating representations of the system's spatial state to the coordinate system of the current geometry.
  • CellStateWriter: Captures agent state.
  • TimeWriter: Provides a mapping between logical time integration cycle and simulation time.

Interactive output

The visualization accounts for four of five arguments in the output list. The fifth one, ProgressReporter, provides additional logging detail to console, and can be useful for making sure Nanoverse is running as expected.

General parameters

The parameters section is for providing Nanoverse with system-level instructions. The project argument specifies the name of the time-stamped directory into which to put results, and the date argument specifies that that directory should be time-stamped. maxStep indicates how long the simulation should run before it automatically terminates, and is included as a system-level parameter as a failsafe.

There is one important argument, path, which is broken in v1.0.0-a6. This argument allows you to specify the target root directory for output. Right now, the output is stored in the working directory for the Nanoverse instance.

exp_growth.txt · Last modified: 2015/11/17 11:06 by david