HSolve Overview

Introduction

This document gives an overview of the Hines' solver (HSolve) implementation in MOOSE. At present it talks more about the interaction between HSolve and the rest of MOOSE, and does not talk about HSolve's internals (that is, the numerical implementation). Hence, it will be useful for someone who is implementing a new numerical scheme in MOOSE.

When a neuronal model is read into MOOSE (from a NeuroML file, for example), it is represented inside MOOSE by biophysical objects (of type Compartment, HHChannel, etc.) linked up by messages. Representing the model in terms of objects and messages is nice because it provides a natural interface that the user and the rest of the system can use.

These objects have fields, using which the user can specify model parameters, and monitor state variables during a simulation. The objects also have some ODE solving code (usually Exponential Euler, or EE) which allows them to advance their own state. The messages allow the objects to talk to each other. In addition, the message connectivity depicts the model's structure.

In absence of HSolve, these objects do the following things:

This method of doing calculations is good because it is simple to implement, and also provides a fallback method. However, it is very slow for the following reasons:

The Hines' solver, in addition to being a higher-order integration method, also increases speed by doing all the calculations in one place, and storing all the data in arrays. This eliminates messaging overheads, and improves data locality.

At the same time, one will like to retain the original objects-and-messages representation of the model, so that the user can easily inspect and manipulate it. In MOOSE, this is accomplished by replacing the original objects with "zombie" objects, whenever a solver like the HSolve is created. The clients of the original objects remain unaware of this switch, and to them, the zombie objects look just like the originals. The zombie objects have the same fields as the original objects, and the message connectivity is also retained. The illusion is made complete by letting the zombie objects forward any field queries and incoming messages to the HSolve. More detail on zombie objects is in the "Zombies" section below.

Conceptual Overview

MOOSE allows you to keep your main numerical code very loosely coupled with the rest of the MOOSE system. HSolve makes good use of this, and keeps the numerical code as independent of MOOSE-specific concepts/classes as possible. The points of interaction between HSolve and the rest of MOOSE are neatly contained in a few classes/files.

Note: At present, a single HSolve object handles calculations for a single neuron. Soon, HSolve will also handle calculations for arrays of identical neurons.

Here is an overview of how things proceed chronologically in a simulation:

  1. The user loads in a model, from, say a NeuroML file. The model is represented inside MOOSE as a bunch of objects, connected by messages. The objects are of type Compartment, HHChannel, etc. The connections between these objects capture the structure of the model. Each of the objects have fields (e.g.: "Vm" for a Compartment, "Gk" for an HHChannel). The user can use these fields to read/modify the parameters and state of the model.
  2. The objects are capable of doing their own calculations at simulation time, using the Exponential Euler method. Usually, the user "schedules" all the objects constituting the model. This means hooking up the objects to a clock, which will invoke the objects at regular intervals to do their calculations. However, since we want HSolve to the calculations instead of the original objects, this scheduling step is not necessary.
  3. The user connects this single-neuron model with other, external things. For example, a Table object may be connected to a Compartment object for the purpose of monitoring its Vm, later during the simulation. Other examples are:
    • a Table providing time-varying current-injection to a compartment.
    • synaptic connections between compartments belonging to different neurons.
  4. The user creates an HSolve object.
  5. The user "schedules" the HSolve object so that it can do its calculations.
  6. The user sets the "dt" field of the HSolve object.
  7. The user points the HSolve object to the model. This is done by setting the HSolve's "target" field to the location of model inside MOOSE.

(Note: MOOSE, arranges objects in a tree, just like directories and files are arranged in a tree by filesystems. Hence, the location of a model is simply the "path" to an object which contains all of the model's objects).

Setting the "target" field causes HSolve to do the following:

  1. Traverse the model, and build internal data structures based on the model's structure, parameters and state.
  2. "Deschedule" all the original objects, so that they are not longer invoked by the clock to do their calculations.
  3. Create "zombie" objects. More on this in the "Zombies" section below.

The user runs the simulation. As mentioned above, only the HSolve is invoked every time-step to do its calculations. Further, the rest of the system continues to interact with the individual zombified biophysical objects, not knowing that HSolve is doing all the thinking in the background.

Note that at present, the user is responsible for carrying out all the above steps. In the future, a "solver manager" will be implemented which will take over most of the above responsibilities from the user. The user will mainly need to specify the choice of solver: EE, HSolve, or any other, if present.

Zombies

When an HSolve object is created, it takes over all the above functions from the original objects. At the same time, each of the original objects is replaced by a corresponding "zombie" object. For example, a Compartment object is replaced with a ZombieCompartment object. The user (or the rest of the system) continues to interact with the zombie objects, unaware of the switch. The role of the zombies is to act as fully-functional stand-ins, while letting the HSolve do all the thinking. Hence, a Table object can continue requesting for Vm from the compartment it was connected to, not knowing that the compartment has now been replaced by a zombie. Simliarly, another Table object can continue feeding current inject values to a compartment, not knowing that they are being fed into HSolve. All of this is accomplished in the following way:

For further details about zombies, see the Programmer's Guide.

C++ code: classes and files

Now we look at the different C++ classes that make up HSolve, and at the role they play in the processes described above.

At setup time, most of the information flow is in the MOOSE --> HSolve direction. Here, the HSolveUtils class is of particular interest.

At simulation time, most of the information flow is in the HSolve --> MOOSE direction. Here, the HSolve class and the Zombie classes capture most of the interactions.

The numerical implementation is contained in the 3 classes HSolveActive, HSolvePassive, and HinesMatrix.

Further details below:

  1. HSolveUtils: This is a little library of convenience functions built on top of more basic MOOSE API calls. This library is meant for someone implementing a numerical scheme, and wishing to read in the model. A typical call looks like: "For a given compartment, give me all its neighbouring compartments", or, "For a given compartment, give me all the HHChannels that it has".
  2. HSolve: The user and the rest of MOOSE interact with this class, and the Zombie classes. HSolve does the following:
    1. Inherits numerical code and data structures from the HSolveActive class.
    2. It provides an interface for looking up and modifying the parameters and state of the model. This is implemented as a host of set/get functions, written in HSolveInterface.cpp.
    3. Elevates its own status from regular C++ class to a MOOSE class. It does so by registering itself as a class with the MOOSE system. Here it also tells MOOSE that it has fields called "target" and "dt" (as mentioned earlier). It also specifies that it has a field called 'process', which allows it to be connected to a clock from the MOOSE scheduling system. All of this is done in HSolve::initCinfo().
    4. When the "target" field is set, it sets up its internal data structures using code inherited from HSolveActive. At this point, it also converts all the original objects into zombies.
  3. HSolveActive: At setup time, when the "target" field of HSolve is set, it triggers the HSolveActive::setup() function. This function is encoded in HSolveActiveSetup.cpp. It traverses the model using the HSolveUtils API, interrogates the model's structure, parameter and state, and sets up all the internal data-structures accordingly. At simulation time, HSolveActive does the full-fledged calculations for a neuronal model with ion channels, calcium, synapses, etc.The entry point for these calculations is HSolveActive::step().
  4. HSolvePassive: This class does the compartmental calculations for passive neurons. Derives from HinesMatrix.
  5. HinesMatrix: This class stores the HinesMatrix.
  6. Zombie*: These are the zombie classes.

Generated on 1 Jul 2015 for MOOSE by  doxygen 1.6.1