How the Cable Cell is made

This is a short tour through the inner workings of a cable cell intended for new Arbor developers. Cable cells are not the sole cell type supported in Arbor, but both the most common and most complex kind.

This is the introduction from which more detailed descriptions will branch out. As such, we will start with a simple cable cell simulation and how the user input is turned into an executable object.

Terminology

In Arbor’s codebase some prefixes are used as a low-key namespacing

  • arb_:: Mech ABI types, in general use through out Arbor, eg arb_mechanism_type.

  • fvm_:: Concerning use by the Finite Volume Method (FVM), eg fvm_lowered_cell.

  • mc_:: Related to Multi-Compartment (Cells), identical to cable cells the difference is purely historical, eg mc_cell_group.

Setting up a Cable Cell simulation

Arbor constructs a runnable simulation from three ingredients:

  • recipe:: a queryable structure that collects cells, connections, gap junctions, etc.

  • context:: a hardware configuration, summarising threads, GPU, and MPI resources to be used.

  • domain_decomposition:: Distribution of cells over the context, made from context and recipe.

The interesting part here is the recipe, which is used to lazily produce the data required by the simulation and domain_decomposition. A simple example might be this model of a single cell

 struct recipe: public arb::recipe {
     recipe(const arb::morphology& m, const arb::decor& d): cell{m, d} {}

     arb::cell_size_type num_cells() const override { return 1; }

     std::vector<arb::probe_info> get_probes(arb::cell_gid_type) const override { return {}; }

     arb::cell_kind get_cell_kind(arb::cell_gid_type) const override { return arb::cell_kind::cable; }

     std::any get_global_properties(arb::cell_kind) const override { return gprop; }

     arb::util::unique_any get_cell_description(arb::cell_gid_type) const override { return cell; }

     arb::cable_cell cell
     arb::cable_cell_global_properties gprop;
};

As you can see, data is produced on request by feeding the recipe a cell_gid_type. Finally, we need to have a morphology and a decor to generate a cable_cell. Please refer to the documentation on how to construct these objects. For now, it is sufficient to note that a cable_cell is a description of a cell, consisting of a cell geometry and a mapping of sub-geometries to properties, rather an object that can be simulated. At this point ion channels are described by a structure (name, [(parameter, value)]).

Lowered Cells, Shared State, and the Discretisation

To obtain a simulation we need to turn the cable_cell description object into a fvm_lowered_cell. However, multiple cells are collected into a mc_cell_group and fvm_lowered_cell is the lowered representation of a full cell group. The fvm_lowered_cell is responsible for holding the backend-specific data of a cell group, managing sampling and stimuli, facilitate event processing, and driving time integration.

Discretisation splits the segments described by the morphology into control volumes (CV; sometimes called compartments) according to a cv_policy. This allows us to construct a system of linear equations, the Hines matrix, to describe the evolution of the CV voltages according to the cable equation. Refer to Discretisation and Cable equation.

Backend-dependent data is stored in shared_state as per-compartment data and indices into these arrays. Upon fvm_lowered_cell::initialize these are populated using the concrete discretisation and the cable_cell description. Also, mechanisms are concretised using the provided mechanism_catalogue and their internal data is set up in shared_state. See Shared state for more details

Main integration loop

Now we are in a state to simulate a cell group by calling simulation::run(t_end, dt).

The integration in Arbor proceeds in epochs with a length less than half a time constant \(T_{min}\), which is the maximum time over which cell groups can evolve independently. The length \(T_{min}\) is derived as the minimum over all axonal/synaptic delays. This works since we know that an event at time \(t\) can have an effect at time \(t + T_{min}\) at the soonest. The factor of one half stems from double-buffering to overlap communication and computation. So, Arbor collects all events in an epoch and transmits them in bulk, see Communication for details.

Integration in Arbor is then split into three parts:

  1. apply effect of events to mechanisms Event Handling

  2. evolve mechanisms and apply currents Mechanisms

  3. solve voltage equations, see Solver

Integration proceeds as far as possible without needing to process an event, but at most with the given time step dt.