In order to understand Nanoverse, it can be helpful to get a feel for how it works. Nanoverse is a compiled language: it reads your project specification, builds up (compiles) a plan of action for simulating your model, and then simulates it. The first stage, reading the code and building an execution plan, is called compile time. The second stage, during which the simulation is run, is called run time or runtime.
Execution of a Nanoverse project is triggered by a runner (
nanoverse.runtime.control.Runner). The Runner is aware of the project as a whole, including how many instances are to be run and which one is currently running.
Each time the Runner executes an instance, it creates an integrator(
nanoverse.runtime.control.Integrator), and asks that integrator to integrate the simulation forward until it ends. The Integrator loops repeatedly through the list of user-specified processes, executing each one when specified conditions are met. This loop is halted when one of the processes signals a reason for stopping (
nanoverse.runtime.control.halt.*)) or some maximum number of integration cycles has been reached.
To execute processes, the integrator first creates a token representing the simulation state (
nanoverse.runtime.processes.StepState). It then delegates the execution of individual processes to the process manager (
nanoverse.runtime.control.ProcessManager), passing in the token in the process. The process manager identifies processes that are scheduled for execution during this integration cycle, and then executes them in order. As each process is executed, it passes the state token to the next process.
One special process, called
nanoverse.runtime.processes.discrete.TriggerProcess), causes specified agents to perform a behavior. A behavior is a name given by the user to a defined sequence of actions. Behaviors can include more
Triggers (these ones being
Trigger actions), leading to potentially complex outcomes.
To make this possible, agents have a special method in their interface called
trigger(String). Agents are called “cells” in Nanoverse v1.0.0-a1, and their code is located at
nanoverse.runtime.cells.BehaviorCell. The task of executing the triggered event is handled by a behavior dispatcher (
nanoverse.runtime.agent.control.BehaviorDispatcher), which matches the name of the behavior with its contents. From there, a number of different objects get involved, allowing the agent to execute its behavior without requiring the agent (or its neighbors) to be aware of the simulation's geometry or global state.
Nanoverse allows for two kinds of layers: agent layers and continuum layers. The latter represent a scalar field of continuous values, with one value at each coordinate. These values can be locally manipulated by agents, or globally transformed through continuum processes.
Continuum layers are coupled to a solver. There are two kinds of solvers: equilibrium and non-equilibirum. The equilibrium solver solves a transformation to steady state (assuming one exists), while the non-equilibrium solver integrates forward by a finite amount. The equilibrium solvers are much more mature than the non-equilibrium solvers, although all of these features are fairly new.
By default, changes to the continuum are applied as soon as they are requested. If a single agent alters its local continuum value, that will require a global update. Obviously that's not very efficient, so we include two meta-processes:
Release. When a
Hold event occurs, changes are queued up until a
Release event takes place. At that time, any scheduled matrix or vector operations are added up, and the resulting operation is solved all at once. The
Release can happen in the same cycle as the
Hold operation, or in a different cycle.
For example, suppose that the user specifies a
Hold process. If a
Release process then followed immediately, the continuum would not be changed at all. If the user scheduled a
Exp process (multiply every site's value by some scalar) before the
Release process, then once again the result would be the same as never having used
If, however, the user schedules a
Hold, then an
Exp, and then a
Diffuse, we start to see the power of
Release: the matrix operations associated with
Diffuse are summed, and then both of them are applied at the same time.
To be written. Note:
Release also applies all agent relationships.
A key feature of Nanoverse is the decoupling of spatial structure from spatially structured events. Actions are defined in terms of what they do to neighborhoods, and processes are defined in terms of what they do to a neighborhood relative to a given point. Neither actions nor processes know (or need to know) how many neighbors there are at a location, what happens at the boundary, or even how many dimensions exist in the simulation's concept of space.
As a result, Nanoverse allows the user to switch from a 1-D model to a 3-D model, or from absorbing boundaries to reflecting boundaries, with just one or two single-word changes. This is accomplished by using a black-box geometry (
nanoverse.runtime.geometry.Geometry), which can report relationships relative to a particular coordinate, such as neighborhood, relative distance to another coordinate, and position relative to the boundary.
The Geometry object is actually a composite of three other objects, which work together to fully specify the geometry's properties:
Boundary. See also the discussion of space in "Language concepts."
Lattice (nanoverse.runtime.geometry.lattice.*) specifies the connectivity of the space. That is, it defines how many neighbors a particular location has, and how those neighbors are situated with respect to that location. The
Lattice defines the coordinate system, and is used to specify the properties of the
Boundary. The lattice is specified at the project level: all layers have the same lattice.
nanoverse.runtime.geometry.shape.*) specifies the shape of the boundary. It is defined in terms of the
Lattice and a given
Shape might not be compatible with all coordinate systems. See the source code for more information. The shape is specified at the project level: all layers have the same shape.
nanoverse.runtime.geometry.boundary.*) specifies what happens at the end of space. Does it wrap around? Do agents (and scalar values) fall off the edge of the world? Perhaps the simulation should end when any agent crosses the boundary. Each layer can have its own boundary, which is specified at they layer's definition.
Nanoverse would not be very useful without the ability to capture output. Nanoverse's various output modes are defined through the many implementations of the Serializer class (
nanoverse.runtime.io.serialize.*). These include text and binary reports, as well as visualizations.
The capture and serialization of state is triggered by the
Record process (
nanoverse.runtime.processes.discrete.Record). This process instructs the
nanoverse.runtime.io.serialize.SerializationManager) to instruct all the other Serializers to capture the system state. The serialization manager is itself just another Serializer, so it's pretty easy to follow the propagation of this event through the codebase.
One feature that could be built fairly quickly is the ability to trigger different output reporting at different times. Email David if this would be helpful.