Skip to content
Snippets Groups Projects
Select Git revision
  • a2b39382abc272d989e36c9bc85575601e2e2878
  • master default protected
  • tut_ring_allen
  • docs_furo
  • docs_reorder_cable_cell
  • docs_graphviz
  • docs_rtd_dev
  • ebrains_mirror
  • doc_recat
  • docs_spike_source
  • docs_sim_sample_clar
  • docs_pip_warn
  • github_template_updates
  • docs_fix_link
  • cv_default_and_doc_clarification
  • docs_add_numpy_req
  • readme_zenodo_05
  • install_python_fix
  • install_require_numpy
  • typofix_propetries
  • docs_recipe_lookup
  • v0.10.0
  • v0.10.1
  • v0.10.0-rc5
  • v0.10.0-rc4
  • v0.10.0-rc3
  • v0.10.0-rc2
  • v0.10.0-rc
  • v0.9.0
  • v0.9.0-rc
  • v0.8.1
  • v0.8
  • v0.8-rc
  • v0.7
  • v0.6
  • v0.5.2
  • v0.5.1
  • v0.5
  • v0.4
  • v0.3
  • v0.2.2
41 results

dryrun.cpp

Blame
  • user avatar
    noraabiakar authored and Ben Cumming committed
    Dry-run mode: 
    * An implementation of distributed_context that is used to mimic the performance of running an MPI distributed simulation with n ranks.
    * Verifiable against an MPI run with the same parameters. 
    
    Implementation: 
    * Describe the model on a single domain (tile) and translate it to however many domains we want to mimic using arb::tile and arb::symmetric_recipe. This allows us to know the exact behavior of the entire system by only running the simulation on a single node.
    * Mimic communication between domains using arb::dry_run_context
    
    Example: 
    * dryrun in example/ is a verifiable example of using dry-run mode with mc_cells
    
    Other:
    * Documentation of dry-run mode 
    * unit test for dry_run_context 
    a2b39382
    History
    dryrun.cpp 11.34 KiB
    /*
     * A miniapp that demonstrates how to use dry_run mode
     *
     */
    
    #include <fstream>
    #include <iomanip>
    #include <iostream>
    
    #include <nlohmann/json.hpp>
    
    #include <arbor/assert_macro.hpp>
    #include <arbor/common_types.hpp>
    #include <arbor/context.hpp>
    #include <arbor/load_balance.hpp>
    #include <arbor/mc_cell.hpp>
    #include <arbor/profile/meter_manager.hpp>
    #include <arbor/profile/profiler.hpp>
    #include <arbor/simple_sampler.hpp>
    #include <arbor/simulation.hpp>
    #include <arbor/symmetric_recipe.hpp>
    #include <arbor/recipe.hpp>
    #include <arbor/version.hpp>
    
    #include <aux/ioutil.hpp>
    #include <aux/json_meter.hpp>
    
    #include "parameters.hpp"
    
    #ifdef ARB_MPI_ENABLED
    #include <mpi.h>
    #include <aux/with_mpi.hpp>
    #endif
    
    using arb::cell_gid_type;
    using arb::cell_lid_type;
    using arb::cell_size_type;
    using arb::cell_member_type;
    using arb::cell_kind;
    using arb::time_type;
    using arb::cell_probe_address;
    
    // Generate a cell.
    arb::mc_cell branch_cell(arb::cell_gid_type gid, const cell_parameters& params);
    
    class tile_desc: public arb::tile {
    public:
        tile_desc(unsigned num_cells, unsigned num_tiles, cell_parameters params, unsigned min_delay):
                num_cells_(num_cells),
                num_tiles_(num_tiles),
                cell_params_(params),
                min_delay_(min_delay)
        {}
    
        cell_size_type num_cells() const override {
            return num_cells_;
        }
    
        cell_size_type num_tiles() const override {
            return num_tiles_;
        }
    
        arb::util::unique_any get_cell_description(cell_gid_type gid) const override {
            return branch_cell(gid, cell_params_);
        }
    
        cell_kind get_cell_kind(cell_gid_type gid) const override {
            return cell_kind::cable1d_neuron;
        }
    
        // Each cell has one spike detector (at the soma).
        cell_size_type num_sources(cell_gid_type gid) const override {
            return 1;
        }
    
        // The cell has one target synapse
        cell_size_type num_targets(cell_gid_type gid) const override {
            return 1;
        }
    
        // Each cell has one incoming connection, from any cell in the network spanning all ranks:
        // src gid in {0, ..., num_cells_*num_tiles_ - 1}.
        std::vector<arb::cell_connection> connections_on(cell_gid_type gid) const override {
            std::uniform_int_distribution<cell_gid_type> source_distribution(0, num_cells_*num_tiles_ - 2);
    
            auto src_gen = std::mt19937(gid);
            auto src = source_distribution(src_gen);
            if (src>=gid) ++src;
    
            return {arb::cell_connection({src, 0}, {gid, 0}, event_weight_, min_delay_)};
        }
    
        // Return an event generator on every 20th gid. This function needs to generate events
        // for ALL cells on ALL ranks. This is because the symmetric recipe can not easily
        // translate the src gid of an event generator
        std::vector<arb::event_generator> event_generators(cell_gid_type gid) const override {
            std::vector<arb::event_generator> gens;
            if (gid%20 == 0) {
                gens.push_back(arb::explicit_generator(arb::pse_vector{{{gid, 0}, 0.1, 1.0}}));
            }
            return gens;
        }
    
        // There is one probe (for measuring voltage at the soma) on the cell.
        cell_size_type num_probes(cell_gid_type gid)  const override {
            return 1;
        }
    
        arb::probe_info get_probe(cell_member_type id) const override {
            // Get the appropriate kind for measuring voltage.
            cell_probe_address::probe_kind kind = cell_probe_address::membrane_voltage;
            // Measure at the soma.
            arb::segment_location loc(0, 0.0);
    
            return arb::probe_info{id, kind, cell_probe_address{loc, kind}};
        }
    
    private:
        cell_size_type num_cells_;
        cell_size_type num_tiles_;
        cell_parameters cell_params_;
        double min_delay_;
        float event_weight_ = 0.01;
    };
    
    struct cell_stats {
        using size_type = unsigned;
        size_type ncells = 0;
        int nranks = 1;
        size_type nsegs = 0;
        size_type ncomp = 0;
    
        cell_stats(arb::recipe& r, run_params params) {
    #ifdef ARB_MPI_ENABLED
            if(!params.dry_run) {
                int rank;
                MPI_Comm_rank(MPI_COMM_WORLD, &rank);
                MPI_Comm_size(MPI_COMM_WORLD, &nranks);
                ncells = r.num_cells();
                size_type cells_per_rank = ncells/nranks;
                size_type b = rank*cells_per_rank;
                size_type e = (rank+1)*cells_per_rank;
                size_type nsegs_tmp = 0;
                size_type ncomp_tmp = 0;
                for (size_type i=b; i<e; ++i) {
                    auto c = arb::util::any_cast<arb::mc_cell>(r.get_cell_description(i));
                    nsegs_tmp += c.num_segments();
                    ncomp_tmp += c.num_compartments();
                }
                MPI_Allreduce(&nsegs_tmp, &nsegs, 1, MPI_UNSIGNED, MPI_SUM, MPI_COMM_WORLD);
                MPI_Allreduce(&ncomp_tmp, &ncomp, 1, MPI_UNSIGNED, MPI_SUM, MPI_COMM_WORLD);
            }
    #else
            if(!params.dry_run) {
                nranks = 1;
                ncells = r.num_cells();
                for (size_type i = 0; i < ncells; ++i) {
                    auto c = arb::util::any_cast<arb::mc_cell>(r.get_cell_description(i));
                    nsegs += c.num_segments();
                    ncomp += c.num_compartments();
                }
            }
    #endif
            else {
                nranks = params.num_ranks;
                ncells = r.num_cells(); //total number of cells across all ranks
    
                for (size_type i = 0; i < params.num_cells_per_rank; ++i) {
                    auto c = arb::util::any_cast<arb::mc_cell>(r.get_cell_description(i));
                    nsegs += c.num_segments();
                    ncomp += c.num_compartments();
                }
    
                nsegs *= params.num_ranks;
                ncomp *= params.num_ranks;
            }
        }
    
        friend std::ostream& operator<<(std::ostream& o, const cell_stats& s) {
            return o << "cell stats: "
                     << s.nranks << " ranks; "
                     << s.ncells << " cells; "
                     << s.nsegs << " segments; "
                     << s.ncomp << " compartments.";
        }
    };
    
    
    int main(int argc, char** argv) {
        try {
    #ifdef ARB_MPI_ENABLED
            aux::with_mpi guard(argc, argv, false);
    #endif
            bool root = true;
            auto params = read_options(argc, argv);
    
            auto resources = arb::proc_allocation();
            auto ctx = arb::make_context(resources);
    
            if (params.dry_run) {
                ctx = arb::make_context(resources, arb::dry_run_info(params.num_ranks, params.num_cells_per_rank));
            }
    #ifdef ARB_MPI_ENABLED
            else {
                ctx = arb::make_context(resources, MPI_COMM_WORLD);
                {
                    int rank;
                    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
                    root = rank==0;
                }
            }
    #endif
            arb_assert(arb::num_ranks(ctx)==params.num_ranks);
    
    
    #ifdef ARB_PROFILE_ENABLED
            arb::profile::profiler_initialize(ctx);
    #endif
            std::cout << aux::mask_stream(root);
    
            // Print a banner with information about hardware configuration
            std::cout << "gpu:      " << (has_gpu(ctx)? "yes": "no") << "\n";
            std::cout << "threads:  " << num_threads(ctx) << "\n";
            std::cout << "mpi:      " << (has_mpi(ctx)? "yes": "no") << "\n";
            std::cout << "ranks:    " << num_ranks(ctx) << "\n" << std::endl;
    
            std::cout << "run mode: " << distribution_type(ctx) << "\n";
    
            arb::profile::meter_manager meters;
            meters.start(ctx);
    
            // Create an instance of our tile and use it to make a symmetric_recipe.
            auto tile = std::make_unique<tile_desc>(params.num_cells_per_rank,
                    params.num_ranks, params.cell, params.min_delay);
            arb::symmetric_recipe recipe(std::move(tile));
    
            cell_stats stats(recipe, params);
            std::cout << stats << "\n";
    
            auto decomp = arb::partition_load_balance(recipe, ctx);
    
            // Construct the model.
            arb::simulation sim(recipe, decomp, ctx);
    
            // Set up recording of spikes to a vector on the root process.
            std::vector<arb::spike> recorded_spikes;
            if (root) {
                sim.set_global_spike_callback(
                        [&recorded_spikes](const std::vector<arb::spike>& spikes) {
                            recorded_spikes.insert(recorded_spikes.end(), spikes.begin(), spikes.end());
                        });
            }
    
            meters.checkpoint("model-init", ctx);
    
            // Run the simulation for 100 ms, with time steps of 0.025 ms.
            sim.run(params.duration, 0.025);
    
            meters.checkpoint("model-run", ctx);
    
            auto ns = sim.num_spikes();
            std::cout << "\n" << ns << " spikes generated at rate of "
                      << params.duration/ns << " ms between spikes\n\n";
    
            // Write spikes to file
            if (root) {
                std::ofstream fid("spikes.gdf");
                if (!fid.good()) {
                    std::cerr << "Warning: unable to open file spikes.gdf for spike output\n";
                }
                else {
                    char linebuf[45];
                    for (auto spike: recorded_spikes) {
                        auto n = std::snprintf(
                                linebuf, sizeof(linebuf), "%u %.4f\n",
                                unsigned{spike.source.gid}, float(spike.time));
                        fid.write(linebuf, n);
                    }
                }
            }
    
            auto profile = arb::profile::profiler_summary();
            std::cout << profile << "\n";
    
            auto report = arb::profile::make_meter_report(meters, ctx);
            std::cout << report;
        }
        catch (std::exception& e) {
            std::cerr << "exception caught in ring miniapp:\n" << e.what() << "\n";
            return 1;
        }
    
        return 0;
    }
    
    // Helper used to interpolate in branch_cell.
    template <typename T>
    double interp(const std::array<T,2>& r, unsigned i, unsigned n) {
        double p = i * 1./(n-1);
        double r0 = r[0];
        double r1 = r[1];
        return r[0] + p*(r1-r0);
    }
    
    arb::mc_cell branch_cell(arb::cell_gid_type gid, const cell_parameters& params) {
        arb::mc_cell cell;
    
        // Add soma.
        auto soma = cell.add_soma(12.6157/2.0); // For area of 500 μm².
        soma->rL = 100;
        soma->add_mechanism("hh");
    
        std::vector<std::vector<unsigned>> levels;
        levels.push_back({0});
    
        // Standard mersenne_twister_engine seeded with gid.
        std::mt19937 gen(gid);
        std::uniform_real_distribution<double> dis(0, 1);
    
        double dend_radius = 0.5; // Diameter of 1 μm for each cable.
    
        unsigned nsec = 1;
        for (unsigned i=0; i<params.max_depth; ++i) {
            // Branch prob at this level.
            double bp = interp(params.branch_probs, i, params.max_depth);
            // Length at this level.
            double l = interp(params.lengths, i, params.max_depth);
            // Number of compartments at this level.
            unsigned nc = std::round(interp(params.compartments, i, params.max_depth));
    
            std::vector<unsigned> sec_ids;
            for (unsigned sec: levels[i]) {
                for (unsigned j=0; j<2; ++j) {
                    if (dis(gen)<bp) {
                        sec_ids.push_back(nsec++);
                        auto dend = cell.add_cable(sec, arb::section_kind::dendrite, dend_radius, dend_radius, l);
                        dend->set_compartments(nc);
                        dend->add_mechanism("pas");
                        dend->rL = 100;
                    }
                }
            }
            if (sec_ids.empty()) {
                break;
            }
            levels.push_back(sec_ids);
        }
    
        // Add spike threshold detector at the soma.
        cell.add_detector({0,0}, 10);
    
        // Add a synapse to the mid point of the first dendrite.
        cell.add_synapse({1, 0.5}, "expsyn");
    
        return cell;
    }