diff --git a/doc/cpp_common.rst b/doc/cpp_common.rst new file mode 100644 index 0000000000000000000000000000000000000000..d0e4e8b3b85c3522cdff98ff820d5546ebf9e2bc --- /dev/null +++ b/doc/cpp_common.rst @@ -0,0 +1,115 @@ +Common Types +============ + +.. cpp:namespace:: arb + +Cell Identifiers and Indexes +---------------------------- + +These types, defined in ``common_types.hpp``, are used as identifiers for +cells and members of cell-local collections. + +.. Note:: + Arbor uses ``std::unit32_t`` for :cpp:type:`cell_gid_type`, + :cpp:type:`cell_size_type`, :cpp:type:`cell_lid_type`, and + :cpp:type:`cell_local_size_type` at the time of writing, however + this could change, e.g. to handle models that cell gid that don't + fit into a 32 bit unsigned integer. + It is thus recomended that these type aliases be used whenever identifying + or counting cells and cell members. + + +.. cpp:type:: cell_gid_type + + An integer type used for identifying cells globally. + + +.. cpp:type:: cell_size_type + + An unsigned integer for sizes of collections of cells. + Unsigned type for counting :cpp:type:`cell_gid_type`. + + +.. cpp:type:: cell_lid_type + + For indexes into cell-local data. + Local indices for items within a particular cell-local collection should be + zero-based and numbered contiguously. + + +.. cpp:type:: cell_local_size_type + + An unsigned integer for for counts of cell-local data. + + +.. cpp:class:: cell_member_type + + For global identification of an item of cell local data. + Items of :cpp:type:`cell_member_type` must: + + * be associated with a unique cell, identified by the member + :cpp:member:`gid`; + * identify an item within a cell-local collection by the member + :cpp:member:`index`. + + An example is uniquely identifying a synapse in the model. + Each synapse has a post-synaptic cell (:cpp:member:`gid`), and an index + (:cpp:member:`index`) into the set of synapses on the post-synaptic cell. + + Lexographically ordered by :cpp:member:`gid`, + then :cpp:member:`index`. + + .. cpp:member:: cell_gid_type gid + + Global identifier of the cell containing/associated with the item. + + .. cpp:member:: cell_lid_type index + + The index of the item in a cell-local collection. + + +.. cpp:enum-class:: cell_kind + + Enumeration used to indentify the cell type/kind, used by the model to + group equal kinds in the same cell group. + + .. cpp:enumerator:: cable1d_neuron + + A cell with morphology described by branching 1D cable segments. + + .. cpp:enumerator:: lif_neuron + + Leaky-integrate and fire neuron. + + .. cpp:enumerator:: regular_spike_source + + Regular spiking source. + + .. cpp:enumerator:: data_spike_source + + Spike source from values inserted via description. + +Probes +------ + +.. cpp:type:: probe_tag = int + + Extra contextual information associated with a probe. + +.. cpp:class:: probe_info + + Probes are specified in the recipe objects that are used to initialize a + model; the specification of the item or value that is subjected to a + probe will be specific to a particular cell type. + + .. cpp:member:: cell_member_type id + + Cell gid, index of probe. + + .. cpp:member:: probe_tag tag + + Opaque key, returned in sample record. + + .. cpp:member:: util::any address + + Cell-type specific location info, specific to cell kind of ``id.gid``. diff --git a/doc/cpp_intro.rst b/doc/cpp_intro.rst new file mode 100644 index 0000000000000000000000000000000000000000..6fbba414d2289136a634cd3bdc77d112a6e7c543 --- /dev/null +++ b/doc/cpp_intro.rst @@ -0,0 +1,11 @@ +Overview +========= + +The C++ API for is the main interface through which application developers will +access Arbor, though it is designed to be usable for power users to +implement models. + +Arbor makes a distinction between the **description** of a model, and the +**execution** of a model. + +A :cpp:type:`arb::recipe` describes a model. diff --git a/doc/cpp_recipe.rst b/doc/cpp_recipe.rst new file mode 100644 index 0000000000000000000000000000000000000000..6edf46cbc05298577331766e169307643c1294ef --- /dev/null +++ b/doc/cpp_recipe.rst @@ -0,0 +1,230 @@ +Recipes +=============== + +An Arbor recipe is a description of a model. The recipe is queried during the model +building phase to provide cell information, such as: + + * the number of cells in the model; + * the type of a cell; + * a description of a cell; + * incoming network connections on a cell. + +The :cpp:class:`arb::recipe` class documentation is below. + +Why Recipes? +-------------- + +The interface and design of Arbor recipes was motivated by the following aims: + + * Building a simulation from a recipe description must be possible in a + distributed system efficiently with minimal communication. + * To minimise the amount of memory used in model building, to make it + possible to build and run simulations in one run. + +Recipe descriptions are cell-oriented, in order that the building phase can +be efficiently distributed and that the model can be built independently of any +runtime execution environment. + +During model building, the recipe is queried first by a load balancer, +then later when building the low-level cell groups and communication network. +The cell-centered recipe interface, whereby cell and network properties are +specified "per-cell", facilitates this. + +The steps of building a simulation from a recipe are: + +.. topic:: 1. Load balancing + + First, the cells are partitioned over MPI ranks, and each rank parses + the cells assigned to it to build a cost model. + The ranks then coordinate to redistribute cells over MPI ranks so that + each rank has a balanced workload. Finally, each rank groups its local + cells into :cpp:type:`cell_group` s that balance the work over threads (and + GPU accelerators if available). + +.. topic:: 2. Model building + + The model building phase takes the cells assigned to the local rank, and builds the + local cell groups and the part of the communication network by querying the recipe + for more information about the cells assigned to it. + +.. _recipe_best_practice: + +Best Practices +-------------- + +Here is a set of rules of tumb to keep in mind when making recipes. The first is +mandatory, and following the others as closely as possible will lead to better +performance. + +.. topic:: Stay thread safe + + The load balancing and model construction are multithreaded, that is + multiple threads query the recipe simultaneously. + Hence calls to a recipe member should not have side effects, and should use + lazy evaluation when possible (see `Be lazy <recipe_lazy_>`_). + +.. _recipe_lazy: + +.. topic:: Be lazy + + A recipe does not have to contain a complete description of the model in + memory; it should precompute as little as possible, and use + `lazy evaluation <https://en.wikipedia.org/wiki/Lazy_evaluation>`_ to generate + information only when requested. + This has multiple benefits, including: + + * thread safety; + * minimising memory footprint of recipe. + +.. topic:: Think of the cells + + When formulating a model, think cell-first, and try to formulate the model and + the associated workflow from a cell-centered perspective. If this isn't possible, + please contact the developers, because we would like to develop tools that help + make this simpler. + +.. topic:: Be reproducible + + Arbor is designed to give reproduceable results when the same model is run on a + different number of MPI ranks or threads, or on different hardware (e.g. GPUs). + This only holds when a recipe provides a reproducible model description, which + can be a challenge when a description uses random numbers, e.g. to pick incoming + connections to a cell from a random subset of a cell population. + To get a reproduceable model, use the cell `gid` (or a hash based on the `gid`) + to seed random number generators, including those for :cpp:type:`event_generator` s. + + +Class Documentation +------------------- + +.. cpp:namespace:: arb + +.. cpp:class:: recipe + + A description of a model, describing the cells and network, without any + information about how the model is to be represented or executed. + + All recipes derive from this abstract base class, defined in ``src/recipe.hpp``. + + Recipes provide a cell-centric interface for describing a model. This means that + model properties, such as connections, are queried using the global identifier + (`gid`) of a cell. In the description below, the term `gid` is used as shorthand + for "the cell with global identifier `gid`". + + + .. Warning:: + All member functions must be **thread safe**, because the recipe is used + by the multithreaded model builing stage. In practice, this means that + multiple threads should be able to call member functions of a recipe + simultaneously. Model building is multithreaded to reduce model building times, + so recipe implementations should avoid using locks and mutexes to introduce + thread safety. See `recipe best practices <recipe_best_practice_>`_ for more + information. + + **Required Member Functions** + + The following member functions must be implemented by every recipe: + + .. cpp:function:: virtual cell_size_type num_cells() const = 0 + + The number of cells in the model. + + .. cpp:function:: virtual cell_kind get_cell_kind(cell_gid_type gid) const = 0 + + The kind of `gid` (see :cpp:type:`arb::cell_kind`). + + .. cpp:function:: virtual util::unique_any get_cell_description(cell_gid_type gid) const = 0 + + A description of the cell `gid`, for example the morphology, synapses + and ion channels required to build a multi-compartment neuron. + + The type used to describe a cell depends on the kind of the cell. + The interface for querying the kind and description of a cell are + seperate to allow the the cell type to be provided without building + a full cell description, which can be very expensive. + + **Optional Member Functions** + + .. cpp:function:: virtual std::vector<cell_connection> connections_on(cell_gid_type gid) const + + Returns a list of all the **incoming** connections for `gid` . + Each connection ``con`` should have post-synaptic target ``con.dest.gid`` that matches + the argument :cpp:var:`gid`, and a valid synapse id ``con.dest.index`` on `gid`. + See :cpp:type:`cell_connection`. + + By default returns an empty list. + + .. cpp:function:: virtual std::vector<event_generator> event_generators(cell_gid_type gid) const + + Returns a list of all the event generators that are attached to `gid`. + + By default returns an empty list. + + .. cpp:function:: virtual cell_size_type num_sources(cell_gid_type gid) const + + Returns the number of spike sources on `gid`. This corresponds to the number + of spike detectors on a multi-compartment cell. Typically there is one detector + at the soma of the cell, however it is possible to attache multiple detectors + at arbitrary locations. + + By default returns 0. + + .. cpp:function:: virtual cell_size_type num_targets(cell_gid_type gid) const + + The number of post-synaptic sites on `gid`, which corresponds to the number + of synapses. + + By default returns 0. + + .. cpp:function:: virtual cell_size_type num_probes(cell_gid_type gid) const + + The number of probes attached to the cell. + + By default returns 0. + + .. cpp:function:: virtual probe_info get_probe(cell_member_type) const + + Intended for use by cell group implementations to set up sampling data + structures ahead of time and for putting in place any structures or + information in the concrete cell implementations to allow monitoring. + + By default throws :cpp:type:`std::logic_error`. If ``arb::recipe::num_probes`` + returns a non-zero value, this must also be overriden. + + .. cpp:function:: virtual util::any get_global_properties(cell_kind) const + + Global property type will be specific to given cell kind. + + By default returns an empty container. + +.. cpp:class:: cell_connection + + Describes a connection between two cells: a pre-synaptic source and a + post-synaptic destination. The source is typically a threshold detector on + a cell or a spike source. The destination is a synapse on the post-synaptic cell. + + .. cpp:type:: cell_connection_endpoint = cell_member_type + + Connection end-points are represented by pairs + (cell index, source/target index on cell). + + .. cpp:member:: cell_connection_endpoint source + + Source end point. + + .. cpp:member:: cell_connection_endpoint dest + + Destination end point. + + .. cpp:member:: float weight + + The weight delivered to the target synapse. + The weight is dimensionless, and its interpretation is + specific to the synapse type of the target. For example, + the `expsyn` synapse interprets it as a conductance + with units μS (micro-Siemens). + + .. cpp:member:: float delay + + Delay of the connection (milliseconds). + diff --git a/doc/index.rst b/doc/index.rst index b8d8e03e67ac059139481649a784cce7aa99e7a7..6e04975dd42836c5d76f1c5570cc8970a248551b 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -26,7 +26,7 @@ Arbor is designed from the ground up for **many core** architectures: Features -------- -We are actively developing Arbor, improving performance and adding features. +We are actively developing `Arbor <https://github.com/eth-cscs/arbor>`_, improving performance and adding features. Some key features include: * Optimized back ends for CUDA, KNL and AVX2 intrinsics. @@ -39,19 +39,23 @@ Some key features include: .. toctree:: :caption: Getting Stared: - :maxdepth: 1 install .. toctree:: :caption: Users: - :maxdepth: 1 users +.. toctree:: + :caption: C++ API: + + cpp_intro + cpp_common + cpp_recipe + .. toctree:: :caption: Developers: - :maxdepth: 1 library simd_api diff --git a/doc/install.rst b/doc/install.rst index 7ddce436cf8f625f90160f452b97034ba86efd3c..8cd30e7587ae56b40c82863ac1781aa443a8f66a 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -159,7 +159,8 @@ Building Arbor Once the Arbor code has been checked out, it can be built by first running CMake to configure the build, then running make. Below is a simple workflow for: **1)** getting the source; **2)** configuring the build; -**3)** building then; **4)** running tests. +**3)** building; **4)** then running tests. + For more detailed build configuration options, see the `quick start <quickstart_>`_ guide. .. code-block:: bash diff --git a/example/miniapp/miniapp.cpp b/example/miniapp/miniapp.cpp index 969b6aedf9d6f3b8527ff24504029b1d1c6a64d9..2ef4ef722edb5a335536eabe69ec5a97ec204ad1 100644 --- a/example/miniapp/miniapp.cpp +++ b/example/miniapp/miniapp.cpp @@ -107,7 +107,7 @@ int main(int argc, char** argv) { // by command line options. std::vector<sample_trace> sample_traces; for (const auto& g: decomp.groups) { - if (g.kind==cable1d_neuron) { + if (g.kind==cell_kind::cable1d_neuron) { for (auto gid: g.gids) { if (options.trace_max_gid && gid>*options.trace_max_gid) { continue; diff --git a/src/common_types.hpp b/src/common_types.hpp index 41284522a6c12286a0dbc25024f6531d0d505642..79f77232fdd67b986e723e122462c9a0fa3acdc2 100644 --- a/src/common_types.hpp +++ b/src/common_types.hpp @@ -67,7 +67,7 @@ using sample_size_type = std::int32_t; // Enumeration used to indentify the cell type/kind, used by the model to // group equal kinds in the same cell group. -enum cell_kind { +enum class cell_kind { cable1d_neuron, // Our own special mc neuron lif_neuron, // Leaky-integrate and fire neuron regular_spike_source, // Regular spiking source diff --git a/src/fvm_multicell.hpp b/src/fvm_multicell.hpp index 4d76fc83a99bb080345fcc51c8545df1a661fb57..2049c2942e969dfb0dac98f3aa4e5f1bdab1d1c3 100644 --- a/src/fvm_multicell.hpp +++ b/src/fvm_multicell.hpp @@ -630,7 +630,7 @@ void fvm_multicell<Backend>::initialize( // Handle any global parameters for these cell groups. // (Currently: just specialized mechanisms). std::map<std::string, specialized_mechanism> special_mechs; - util::any gprops = rec.get_global_properties(cable1d_neuron); + util::any gprops = rec.get_global_properties(cell_kind::cable1d_neuron); if (gprops.has_value()) { special_mechs = util::any_cast<cell_global_properties&>(gprops).special_mechs; } diff --git a/src/partition_load_balance.cpp b/src/partition_load_balance.cpp index 32f202aad8d3284c6dd743b7740bfae52106cae5..f3ec4f91dfe8e3958e739ecee76d9d4ae21eaf8b 100644 --- a/src/partition_load_balance.cpp +++ b/src/partition_load_balance.cpp @@ -2,6 +2,7 @@ #include <domain_decomposition.hpp> #include <hardware/node_info.hpp> #include <recipe.hpp> +#include <util/enumhash.hpp> namespace arb { @@ -19,7 +20,6 @@ domain_decomposition partition_load_balance(const recipe& rec, hw::node_info nd) const std::vector<cell_gid_type> gid_divisions; }; - using kind_type = std::underlying_type<cell_kind>::type; using util::make_span; unsigned num_domains = communication::global_policy::size(); @@ -40,7 +40,8 @@ domain_decomposition partition_load_balance(const recipe& rec, hw::node_info nd) // Local load balance - std::unordered_map<kind_type, std::vector<cell_gid_type>> kind_lists; + std::unordered_map<cell_kind, std::vector<cell_gid_type>, arb::util::enum_hash> + kind_lists; for (auto gid: make_span(gid_part[domain_id])) { kind_lists[rec.get_cell_kind(gid)].push_back(gid); } diff --git a/src/recipe.hpp b/src/recipe.hpp index 0a193548ea637e72e4001e2543b965099b9d7aa9..caaddaddb69a2a8c4c4154808b656bac54365a5c 100644 --- a/src/recipe.hpp +++ b/src/recipe.hpp @@ -27,13 +27,9 @@ public: /* Recipe descriptions are cell-oriented: in order that the building * phase can be done distributedly and in order that the recipe - * description can be built indepdently of any runtime execution - * environment, connection end-points are represented by pairs - * (cell index, source/target index on cell). + * description can be built indepdently of any runtime execution environment. */ -using cell_connection_endpoint = cell_member_type; - // Note: `cell_connection` and `connection` have essentially the same data // and represent the same thing conceptually. `cell_connection` objects // are notionally described in terms of external cell identifiers instead @@ -41,6 +37,10 @@ using cell_connection_endpoint = cell_member_type; // two in the current code. These two types could well be merged. struct cell_connection { + // Connection end-points are represented by pairs + // (cell index, source/target index on cell). + using cell_connection_endpoint = cell_member_type; + cell_connection_endpoint source; cell_connection_endpoint dest; diff --git a/src/util/enumhash.hpp b/src/util/enumhash.hpp new file mode 100644 index 0000000000000000000000000000000000000000..e50d7bf8a26d108fa08fa401c1903e105062ea02 --- /dev/null +++ b/src/util/enumhash.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include <functional> +#include <type_traits> + +// Work around for C++11 defect #2148: hashing enums should be supported directly by std::hash. +// Fixed in C++14. + +namespace arb { +namespace util { + +struct enum_hash { + template <typename E, typename V = typename std::underlying_type<E>::type> + std::size_t operator()(E e) const noexcept { + return std::hash<V>{}(static_cast<V>(e)); + } +}; + +} // namespace util +} // namespace arb diff --git a/tests/simple_recipes.hpp b/tests/simple_recipes.hpp index 8e7218c0570d8a5ecd6637270b22a5dd43a50617..ef6e04fca3bd43111d9d874c7f57a1b27d60335e 100644 --- a/tests/simple_recipes.hpp +++ b/tests/simple_recipes.hpp @@ -37,7 +37,7 @@ public: util::any get_global_properties(cell_kind k) const override { switch (k) { - case cable1d_neuron: + case cell_kind::cable1d_neuron: return cell_gprop; default: return util::any{};