Skip to content
Snippets Groups Projects
Select Git revision
  • b01aa58bf949ab0ba0e3b88e26636506b74e8c3d
  • 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

single_cell_model.cpp

Blame
  • user avatar
    Thorsten Hater authored and GitHub committed
    # Changes
    
    - `cable_cell_global_properties` now holds a catalogue directy
    - fixes for tests, examples, and python bindings accordingly
    
    # Linked Issues
    Fixes #1446 
    b01aa58b
    History
    single_cell_model.cpp 9.33 KiB
    #include <any>
    #include <memory>
    #include <string>
    #include <utility>
    #include <vector>
    
    #include <pybind11/pybind11.h>
    #include <pybind11/stl.h>
    
    #include <arborio/label_parse.hpp>
    
    #include <arbor/cable_cell.hpp>
    #include <arbor/load_balance.hpp>
    #include <arbor/recipe.hpp>
    #include <arbor/simulation.hpp>
    #include <arbor/util/any_cast.hpp>
    
    #include "error.hpp"
    #include "strprintf.hpp"
    
    using arb::util::any_cast;
    
    namespace pyarb {
    
    //
    // Implementation of sampling infrastructure for Python single cell models.
    //
    // Note that only voltage sampling is currently supported, which simplifies the
    // code somewhat.
    //
    
    // Stores the location and sampling frequency for a probe in a single cell model.
    struct probe_site {
        arb::mlocation site;  // Location of sample on morphology.
        double frequency;     // Sampling frequency [kHz].
    };
    
    // Stores a single trace, which can be queried and viewed by the user at the end
    // of a simulation run.
    struct trace {
        std::string variable;           // Name of the variable being recorded.
        arb::mlocation loc;             // Sample site on morphology.
        std::vector<arb::time_type> t;  // Sample times [ms].
        std::vector<double> v;          // Sample values [units specific to sample variable].
    };
    
    // Callback provided to sampling API that records into a trace variable.
    struct trace_callback {
        trace& trace_;
    
        trace_callback(trace& t): trace_(t) {}
    
        void operator()(arb::probe_metadata, std::size_t n, const arb::sample_record* recs) {
            // Push each (time, value) pair from the last epoch into trace_.
            for (std::size_t i=0; i<n; ++i) {
                if (auto p = any_cast<const double*>(recs[i].data)) {
                    trace_.t.push_back(recs[i].time);
                    trace_.v.push_back(*p);
                }
                else {
                    throw std::runtime_error("unexpected sample type");
                }
            }
        }
    };
    
    // Used internally by the single cell model to expose model information to the
    // arb::simulation API when a model is instantiated.
    // Model descriptors, i.e. the cable_cell and probes, are instantiated
    // in the single_cell_model by user calls. The recipe is generated lazily, just
    // before simulation construction, so the recipe can use const references to all
    // of the model descriptors.
    struct single_cell_recipe: arb::recipe {
        const arb::cable_cell& cell_;
        const std::vector<probe_site>& probes_;
        const arb::cable_cell_global_properties& gprop_;
    
        single_cell_recipe(
                const arb::cable_cell& c,
                const std::vector<probe_site>& probes,
                const arb::cable_cell_global_properties& props):
            cell_(c), probes_(probes), gprop_(props)
        {}
    
        virtual arb::cell_size_type num_cells() const override {
            return 1;
        }
    
        virtual arb::util::unique_any get_cell_description(arb::cell_gid_type gid) const override {
            return cell_;
        }
    
        virtual arb::cell_kind get_cell_kind(arb::cell_gid_type) const override {
            return arb::cell_kind::cable;
        }
    
        // connections and event generators
    
        virtual std::vector<arb::cell_connection> connections_on(arb::cell_gid_type) const override {
            return {}; // no connections on a single cell model
        }
    
        virtual std::vector<arb::event_generator> event_generators(arb::cell_gid_type) const override {
            return {};
        }
    
        // probes
    
        virtual std::vector<arb::probe_info> get_probes(arb::cell_gid_type gid) const override {
            // For now only voltage can be selected for measurement.
            std::vector<arb::probe_info> pinfo;
            for (auto& p: probes_) {
                pinfo.push_back(arb::cable_probe_membrane_voltage{p.site});
            }
            return pinfo;
        }
    
        // gap junctions
    
        virtual std::vector<arb::gap_junction_connection> gap_junctions_on(arb::cell_gid_type) const override {
            return {}; // No gap junctions on a single cell model.
        }
    
        virtual std::any get_global_properties(arb::cell_kind) const override {
            return gprop_;
        }
    };
    
    class single_cell_model {
        arb::cable_cell cell_;
        arb::context ctx_;
        bool run_ = false;
    
        std::vector<probe_site> probes_;
        std::unique_ptr<arb::simulation> sim_;
        std::vector<double> spike_times_;
        // Create one trace for each probe.
        std::vector<trace> traces_;
    
    public:
        arb::cable_cell_global_properties gprop;
    
        single_cell_model(arb::cable_cell c):
            cell_(std::move(c)), ctx_(arb::make_context())
        {
            gprop.default_parameters = arb::neuron_parameter_defaults;
            gprop.catalogue = arb::global_default_catalogue();
        }
    
        // example use:
        //      m.probe('voltage', arbor.location(2,0.5))
        //      m.probe('voltage', '(location 2 0.5)')
        //      m.probe('voltage', 'term')
    
        void probe(const std::string& what, const arb::locset& where, double frequency) {
            if (what != "voltage") {
                throw pyarb_error(
                    util::pprintf("{} does not name a valid variable to trace (currently only 'voltage' is supported)", what));
            }
            if (frequency<=0) {
                throw pyarb_error(
                    util::pprintf("sampling frequency is not greater than zero", what));
            }
            for (auto& l: cell_.concrete_locset(where)) {
                probes_.push_back({l, frequency});
            }
        }
    
        void run(double tfinal, double dt) {
            single_cell_recipe rec(cell_, probes_, gprop);
    
            auto domdec = arb::partition_load_balance(rec, ctx_);
    
            sim_ = std::make_unique<arb::simulation>(rec, domdec, ctx_);
    
            // Create one trace for each probe.
            traces_.reserve(probes_.size());
    
            // Add probes
            for (arb::cell_lid_type i=0; i<probes_.size(); ++i) {
                const auto& p = probes_[i];
    
                traces_.push_back({"voltage", p.site, {}, {}});
    
                auto sched = arb::regular_schedule(1.0/p.frequency);
    
                // Now attach the sampler at probe site, with sampling schedule sched, writing to voltage
                sim_->add_sampler(arb::one_probe({0,i}), sched, trace_callback(traces_[i]));
            }
    
            // Set callback that records spike times.
            sim_->set_global_spike_callback(
                [this](const std::vector<arb::spike>& spikes) {
                    for (auto& s: spikes) {
                        spike_times_.push_back(s.time);
                    }
                });
    
            sim_->run(tfinal, dt);
    
            run_ = true;
        }
    
        const std::vector<double>& spike_times() const {
            return spike_times_;
        }
    
        const std::vector<trace>& traces() const {
            return traces_;
        }
    };
    
    void register_single_cell(pybind11::module& m) {
        using namespace pybind11::literals;
        pybind11::class_<trace> tr(m, "trace", "Values and meta-data for a sample-trace on a single cell model.");
        tr
            .def_readonly("variable", &trace::variable, "Name of the variable being recorded.")
            .def_readonly("location", &trace::loc, "Location on cell morphology.")
            .def_readonly("time",    &trace::t, "Time stamps of samples [ms].")
            .def_readonly("value",   &trace::v, "Sample values.")
            .def("__str__", [](const trace& tr) {return util::pprintf("(trace \"{}\" {})", tr.variable, tr.loc);})
            .def("__repr__", [](const trace& tr) {return util::pprintf("(trace \"{}\" {})", tr.variable, tr.loc);});
    
        pybind11::class_<single_cell_model> model(m, "single_cell_model",
            "Wrapper for simplified description, and execution, of single cell models.");
    
        model
            .def(pybind11::init<arb::cable_cell>(),
                "cell"_a, "Initialise a single cell model for a cable cell.")
            .def("run",
                 &single_cell_model::run,
                 "tfinal"_a,
                 "dt"_a = 0.025,
                 "Run model from t=0 to t=tfinal ms.")
            .def("probe",
                [](single_cell_model& m, const char* what, const char* where, double frequency) {
                    m.probe(what, arborio::parse_locset_expression(where).unwrap(), frequency);},
                "what"_a, "where"_a, "frequency"_a,
                "Sample a variable on the cell.\n"
                " what:      Name of the variable to record (currently only 'voltage').\n"
                " where:     Location on cell morphology at which to sample the variable.\n"
                " frequency: The target frequency at which to sample [kHz].")
            .def("probe",
                [](single_cell_model& m, const char* what, const arb::mlocation& where, double frequency) {
                    m.probe(what, where, frequency);},
                "what"_a, "where"_a, "frequency"_a,
                "Sample a variable on the cell.\n"
                " what:      Name of the variable to record (currently only 'voltage').\n"
                " where:     Location on cell morphology at which to sample the variable.\n"
                " frequency: The target frequency at which to sample [kHz].")
            .def_property_readonly("spikes",
                [](const single_cell_model& m) {
                    return m.spike_times();}, "Holds spike times [ms] after a call to run().")
            .def_property_readonly("traces",
                [](const single_cell_model& m) {
                    return m.traces();},
                "Holds sample traces after a call to run().")
            .def_readwrite("properties", &single_cell_model::gprop, "Global properties.")
            .def("__repr__", [](const single_cell_model&){return "<arbor.single_cell_model>";})
            .def("__str__",  [](const single_cell_model&){return "<arbor.single_cell_model>";});
    }
    
    } // namespace pyarb