diff --git a/.gitignore b/.gitignore index cd1f0915e298883234777e008bb6d127fa9be697..f60f1b41e5cdbbb2ac445207ed0d2e41176e0104 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ *.dot *.pdf *.jpg +*.dat # latex output *.aux diff --git a/mechanisms/mod/hh.mod b/mechanisms/mod/hh.mod index 975d601d09491f6b6418200b795368dbd9b14f7e..c96280fd490ce70fbfbeb2a66f64fc42761187a5 100644 --- a/mechanisms/mod/hh.mod +++ b/mechanisms/mod/hh.mod @@ -29,11 +29,13 @@ NEURON { } PARAMETER { + : neuron uses S/cm2, i have to scale by a factor of 10 gnabar = .12 (S/cm2) : <0,1e9> gkbar = .036 (S/cm2) : <0,1e9> gl = .0003 (S/cm2) : <0,1e9> el = -54.3 (mV) - celsius = 37 (degC) + celsius = 6.3 (degC) + :celsius = 37 (degC) } STATE { diff --git a/mechanisms/mod/pas.mod b/mechanisms/mod/pas.mod index 413bd250c8e9a077287c97b82cb9dd2cd44927cb..b7dc2ad6d2a343bb2333c5b8ecfec17060150865 100644 --- a/mechanisms/mod/pas.mod +++ b/mechanisms/mod/pas.mod @@ -16,7 +16,7 @@ INITIAL {} PARAMETER { g = .001 (S/cm2) :<0,1e9> - e = -70 (mV) + e = -65 (mV) : we use -65 for the ball and stick model, instead of Neuron default of -70 } ASSIGNED { diff --git a/src/cell.cpp b/src/cell.cpp index ab01007a206ea241befea21fbed83c531298a36d..3c867c83fcd622001d0f24c3ac8ab22fd20f72ab 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -20,7 +20,7 @@ int cell::num_segments() const // note: I think that we have to enforce that the soma is the first // segment that is added // -void cell::add_soma(value_type radius, point_type center) +soma_segment* cell::add_soma(value_type radius, point_type center) { if(has_soma()) { throw std::domain_error( @@ -35,9 +35,11 @@ void cell::add_soma(value_type radius, point_type center) else { segments_[0] = make_segment<soma_segment>(radius); } + + return segments_[0]->as_soma(); } -void cell::add_cable(cell::index_type parent, segment_ptr&& cable) +cable_segment* cell::add_cable(cell::index_type parent, segment_ptr&& cable) { // check for a valid parent id if(cable->is_soma()) { @@ -54,6 +56,8 @@ void cell::add_cable(cell::index_type parent, segment_ptr&& cable) } segments_.push_back(std::move(cable)); parents_.push_back(parent); + + return segments_.back()->as_cable(); } segment* cell::segment(int index) diff --git a/src/cell.hpp b/src/cell.hpp index 68bc4c5a21f99237b18557f690226c0441e580c6..1c1a43c45e9f929e7aa2b2c141bb5584d3172f6c 100644 --- a/src/cell.hpp +++ b/src/cell.hpp @@ -33,18 +33,18 @@ class cell { /// add a soma to the cell /// radius must be specified - void add_soma(value_type radius, point_type center=point_type()); + soma_segment* add_soma(value_type radius, point_type center=point_type()); /// add a cable /// parent is the index of the parent segment for the cable section /// cable is the segment that will be moved into the cell - void add_cable(index_type parent, segment_ptr&& cable); + cable_segment* add_cable(index_type parent, segment_ptr&& cable); /// add a cable by constructing it in place /// parent is the index of the parent segment for the cable section /// args are the arguments to be used to consruct the new cable template <typename... Args> - void add_cable(index_type parent, Args ...args); + cable_segment* add_cable(index_type parent, Args ...args); /// the number of segments in the cell int num_segments() const; @@ -93,7 +93,7 @@ class cell { // create a cable by forwarding cable construction parameters provided by the user template <typename... Args> -void cell::add_cable(cell::index_type parent, Args ...args) +cable_segment* cell::add_cable(cell::index_type parent, Args ...args) { // check for a valid parent id if(parent>=num_segments()) { @@ -103,6 +103,8 @@ void cell::add_cable(cell::index_type parent, Args ...args) } segments_.push_back(make_segment<cable_segment>(std::forward<Args>(args)...)); parents_.push_back(parent); + + return segments_.back()->as_cable(); } } // namespace mc diff --git a/src/fvm.hpp b/src/fvm.hpp index 674c7d0e181c23997f83c1cf8a884bbdfd514081..d680edc8d290ffce11a2b000d3edcf287713d8ed 100644 --- a/src/fvm.hpp +++ b/src/fvm.hpp @@ -79,6 +79,11 @@ class fvm_cell { return cv_capacitance_; } + /// return the voltage in each CV + vector_view voltage() { + return voltage_; + } + std::size_t size() const { return matrix_.size(); } @@ -121,8 +126,17 @@ class fvm_cell { return ions_[mechanisms::ionKind::k]; } + /// make a time step + void advance(value_type dt); + + /// set initial states + void initialize(); + private: + /// current time + value_type t_ = value_type{0}; + /// the linear system for implicit time stepping of cell state matrix_type matrix_; @@ -241,6 +255,8 @@ fvm_cell<T, I>::fvm_cell(nest::mc::cell const& cell) cv_capacitance_[i] /= cv_areas_[i]; } + std::cout << "capacitance " << cv_capacitance_ << "\n"; + ///////////////////////////////////////////// // create mechanisms ///////////////////////////////////////////// @@ -290,12 +306,13 @@ fvm_cell<T, I>::fvm_cell(nest::mc::cell const& cell) } // instantiate the mechanism + index_view node_index(compartment_index.data(), compartment_index.size()); mechanisms_.push_back( - helper->new_mechanism( - &matrix_, - index_view(compartment_index.data(), compartment_index.size()) - ) + helper->new_mechanism(voltage_, current_, node_index) ); + std::cout << "created mech " << mech.first + << " with size " << mechanisms_.back()->size() + << " and indexes " << node_index << "\n"; } ///////////////////////////////////////////// @@ -340,20 +357,23 @@ fvm_cell<T, I>::fvm_cell(nest::mc::cell const& cell) // FIXME: Hard code parameters for now. // Take defaults for reversal potential of sodium and potassium from // the default values in Neuron. - // We don't use the other parameters for the HH model, so I leave the NaN. - // To set them I would have to go spelunking in the Neuron source. + // Neuron's defaults are defined in the file + // nrn/src/nrnoc/membdef.h using memory::all; - ion_ca().reversal_potential()(all) = std::numeric_limits<value_type>::quiet_NaN(); - ion_ca().internal_concentration()(all) = std::numeric_limits<value_type>::quiet_NaN(); - ion_ca().external_concentration()(all) = std::numeric_limits<value_type>::quiet_NaN(); - ion_na().reversal_potential()(all) = -50.0; - ion_na().internal_concentration()(all) = std::numeric_limits<value_type>::quiet_NaN(); - ion_na().external_concentration()(all) = std::numeric_limits<value_type>::quiet_NaN(); + constexpr value_type DEF_vrest = -65.0; // same name as #define in Neuron + + ion_na().reversal_potential()(all) = 115+DEF_vrest; // mV + ion_na().internal_concentration()(all) = 10.0; // mM + ion_na().external_concentration()(all) = 140.0; // mM + + ion_k().reversal_potential()(all) = -12.0+DEF_vrest;// mV + ion_k().internal_concentration()(all) = 54.4; // mM + ion_k().external_concentration()(all) = 2.5; // mM - ion_k().reversal_potential()(all) = -77.0; - ion_k().internal_concentration()(all) = std::numeric_limits<value_type>::quiet_NaN(); - ion_k().external_concentration()(all) = std::numeric_limits<value_type>::quiet_NaN(); + ion_ca().reversal_potential()(all) = 12.5 * std::log(2.0/5e-5);// mV + ion_ca().internal_concentration()(all) = 5e-5; // mM + ion_ca().external_concentration()(all) = 2.0; // mM } template <typename T, typename I> @@ -399,6 +419,53 @@ void fvm_cell<T, I>::setup_matrix(T dt) } } +template <typename T, typename I> +void fvm_cell<T, I>::initialize() +{ + // initialize mechanism states + for(auto& m : mechanisms_) { + m->nrn_init(); + } +} + +template <typename T, typename I> +void fvm_cell<T, I>::advance(T dt) +{ + using memory::all; + + current_(all) = 0.; + + // update currents + for(auto& m : mechanisms_) { + m->set_params(t_, dt); + m->nrn_current(); + } + + //if(t_>=10.) { + current_[0] -= 0.1; + //} + //std::cout << "t " << t_ << " current " << current_; + + // set matrix diagonals and rhs + setup_matrix(dt); + + //std::cout << " rhs " << matrix_.rhs() << " d " << matrix_.d(); + + // solve the linear system + matrix_.solve(); + + voltage_(all) = matrix_.rhs(); + + //std::cout << " v " << voltage_ << "\n"; + + // update states + for(auto& m : mechanisms_) { + m->nrn_state(); + } + + t_ += dt; +} + } // namespace fvm } // namespace mc } // namespace nest diff --git a/src/matrix.hpp b/src/matrix.hpp index e6d406ee3b5c0809af7c7c8dc02528a06b5d9381..db9d2df7f56e8bfffb73051e996acb13d9fa0369 100644 --- a/src/matrix.hpp +++ b/src/matrix.hpp @@ -19,10 +19,10 @@ class matrix { using size_type = I; // define storage types - using vector_type = memory::HostVector<value_type>; - using view_type = typename vector_type::view_type; - using index_type = memory::HostVector<size_type>; - using index_view = typename index_type::view_type; + using vector_type = memory::HostVector<value_type>; + using vector_view_type = typename vector_type::view_type; + using index_type = memory::HostVector<size_type>; + using index_view_type = typename index_type::view_type; matrix() = default; @@ -81,42 +81,44 @@ class matrix { } /// FIXME : make modparser use the correct accessors (l,d,u,rhs) instead of these - view_type vec_rhs() { return rhs_; } - view_type vec_d() { return d_; } - view_type vec_v() { return v_; } + vector_view_type vec_rhs() { return rhs(); } + vector_view_type vec_d() { return d(); } + vector_view_type vec_v() { return v(); } /// the vector holding the lower part of the matrix - view_type l() + vector_view_type l() { return l_; } /// the vector holding the diagonal of the matrix - view_type d() + vector_view_type d() { return d_; } /// the vector holding the upper part of the matrix - view_type u() + vector_view_type u() { return u_; } /// the vector holding the right hand side of the linear equation system - view_type rhs() + vector_view_type rhs() { return rhs_; } /// the vector holding the solution (voltage) - view_type v() + vector_view_type v() { + EXPECTS(has_voltage_); + return v_; } /// the vector holding the parent index - index_view p() + index_view_type p() { return parent_index_; } @@ -126,7 +128,16 @@ class matrix { /// and can be accessed via rhs() void solve() { - index_view const& p = parent_index_; + /* + std::cout << "solving matrix :\n"; + std::cout << " l " << l_ << "\n"; + std::cout << " d " << d_ << "\n"; + std::cout << " u " << u_ << "\n"; + std::cout << " rhs " << rhs_ << "\n"; + */ + + // FIXME make a const view + index_view_type const& p = parent_index_; auto const ncells = num_cells(); // loop over submatrices @@ -148,6 +159,17 @@ class matrix { rhs_[i] /= d_[i]; } } + //std::cout << " v " << rhs_ << "\n"; + } + + void add_voltage(vector_view_type v_ext) + { + EXPECTS(v_ext.size()==size()); + + std::cout << "============ adding voltage" << std::endl; + + v_ = v_ext; + has_voltage_ = true; } private: @@ -163,7 +185,6 @@ class matrix { d_ = vector_type(n, default_value); u_ = vector_type(n, default_value); rhs_ = vector_type(n, default_value); - v_ = vector_type(n, default_value); } /// the parent indice that describe matrix structure @@ -179,7 +200,9 @@ class matrix { /// after calling solve, the solution is stored in rhs_ vector_type rhs_; - vector_type v_; // should this be in the matrix? not so sure... + vector_view_type v_; + + bool has_voltage_=false; }; } // namespace nest diff --git a/src/mechanism.hpp b/src/mechanism.hpp index f43abc1683a4ab6f8814a65f911112f85316f47d..ea69c292e1f5c50efe0fd0e6e7dbf092c32b16fb 100644 --- a/src/mechanism.hpp +++ b/src/mechanism.hpp @@ -6,7 +6,6 @@ #include <string> #include "indexed_view.hpp" -#include "matrix.hpp" #include "parameter_list.hpp" #include "util.hpp" #include "ion.hpp" @@ -32,11 +31,11 @@ public: using index_view = typename index_type::view_type; using indexed_view_type = indexed_view<value_type, size_type>; - using matrix_type = matrix<value_type, size_type>; using ion_type = ion<value_type, size_type>; - mechanism(matrix_type *matrix, index_view node_index) - : matrix_(matrix) + mechanism(view_type vec_v, view_type vec_i, index_view node_index) + : vec_v_(vec_v) + , vec_i_(vec_i) , node_index_(node_index) {} @@ -50,6 +49,16 @@ public: return node_index_; } + value_type voltage(size_type i) const + { + return vec_v_[node_index_[i]]; + } + + value_type current(size_type i) const + { + return vec_i_[node_index_[i]]; + } + virtual void set_params(value_type t_, value_type dt_) = 0; virtual std::string name() const = 0; virtual std::size_t memory() const = 0; @@ -61,7 +70,8 @@ public: virtual mechanismKind kind() const = 0; - matrix_type* matrix_; + view_type vec_v_; + view_type vec_i_; index_type node_index_; }; @@ -71,10 +81,11 @@ using mechanism_ptr = std::unique_ptr<mechanism<T,I>>; template <typename M> mechanism_ptr<typename M::value_type, typename M::size_type> make_mechanism( - typename M::matrix_type* matrix, - typename M::index_view node_indices + typename M::view_type vec_v, + typename M::view_type vec_i, + typename M::index_view node_indices ) { - return util::make_unique<M>(matrix, node_indices); + return util::make_unique<M>(vec_v, vec_i, node_indices); } } // namespace mechanisms diff --git a/src/mechanism_interface.hpp b/src/mechanism_interface.hpp index 33b6e7f2f8b106b91be56e45917dde663429dd04..c59caaad6c655d6a69c5206b956b1cb42959cfdf 100644 --- a/src/mechanism_interface.hpp +++ b/src/mechanism_interface.hpp @@ -3,7 +3,6 @@ #include <map> #include <string> -#include "matrix.hpp" #include "mechanism.hpp" #include "parameter_list.hpp" @@ -23,11 +22,11 @@ struct mechanism_helper { using index_type = memory::HostVector<I>; using index_view = typename index_type::view_type; using mechanism_ptr_type = mechanism_ptr<T, I>; - using matrix_type = typename mechanism<T,I>::matrix_type; + using view_type = typename mechanism<T,I>::view_type; virtual std::string name() const = 0; - virtual mechanism_ptr<T,I> new_mechanism(matrix_type*, index_view) const = 0; + virtual mechanism_ptr<T,I> new_mechanism(view_type, view_type, index_view) const = 0; virtual void set_parameters(mechanism_ptr_type&, parameter_list const&) const = 0; }; diff --git a/src/parameter_list.hpp b/src/parameter_list.hpp index 1080ac493d3378c3bed4540bd6871d547a1d69bb..0c06988756a077fdbb860fc8f42043b178b1b7e7 100644 --- a/src/parameter_list.hpp +++ b/src/parameter_list.hpp @@ -146,8 +146,15 @@ namespace mc { membrane_parameters() : base("membrane") { - base::add_parameter({"r_L", 0.01, {0., 1e9}}); // typically 10 nF/mm^2 == 0.01 F/m2 - base::add_parameter({"c_m", 180.00, {0., 1e9}}); // Ohm.cm + // from nrn/src/nrnoc/membdef.h + //#define DEF_cm 1. // uF/cm^2 + //#define DEF_Ra 35.4 // ohm-cm + //#define DEF_celsius 6.3 // deg-C + //#define DEF_vrest -65. // mV + // r_L is called Ra in Neuron + //base::add_parameter({"c_m", 10e-6, {0., 1e9}}); // typically 10 nF/mm^2 == 0.01 F/m^2 == 10^-6 F/cm^2 + base::add_parameter({"c_m", 0.01, {0., 1e9}}); // typically 10 nF/mm^2 == 0.01 F/m^2 == 10^-6 F/cm^2 + base::add_parameter({"r_L", 180.00, {0., 1e9}}); // equivalent to Ra in Neuron : Ohm.cm } }; @@ -169,10 +176,10 @@ namespace mc { hh_parameters() : base("hh") { - base::add_parameter({"gnabar", 0.12, {0, 1e9}}); - base::add_parameter({"gkbar", 0.036, {0, 1e9}}); - base::add_parameter({"gl", 0.0003,{0, 1e9}}); - base::add_parameter({"el", -54.3}); + base::add_parameter({"gnabar", 0.12, {0, 1e9}}); + base::add_parameter({"gkbar", 0.036, {0, 1e9}}); + base::add_parameter({"gl", 0.0003,{0, 1e9}}); + base::add_parameter({"el", -54.3}); } }; diff --git a/tests/test_mechanisms.cpp b/tests/test_mechanisms.cpp index f4e5a4d23b1a364b2b366d4a8d3c1458345ac966..d61c89d20b40136044dfd7556157b68947d8e56f 100644 --- a/tests/test_mechanisms.cpp +++ b/tests/test_mechanisms.cpp @@ -22,13 +22,16 @@ TEST(mechanisms, helpers) { //0 1 2 3 4 5 6 7 8 9 std::vector<int> parent_index = {0,0,1,2,3,4,0,6,7,8}; memory::HostVector<int> node_indices = std::vector<int>{0,6,7,8,9}; + auto n = node_indices.size(); - nest::mc::matrix<double, int> matrix(parent_index); + //nest::mc::matrix<double, int> matrix(parent_index); + memory::HostVector<double> vec_i(n, 0.); + memory::HostVector<double> vec_v(n, 0.); auto& helper = nest::mc::mechanisms::get_mechanism_helper("hh"); - auto mech = helper->new_mechanism(&matrix, node_indices); + auto mech = helper->new_mechanism(vec_v, vec_i, node_indices); EXPECT_EQ(mech->name(), "hh"); EXPECT_EQ(mech->size(), 5u); - EXPECT_EQ(mech->matrix_, &matrix); + //EXPECT_EQ(mech->matrix_, &matrix); } diff --git a/tests/test_run.cpp b/tests/test_run.cpp index 55fb0d0f457ef66ac837518ea4b44add41e0fa70..0dc0b4951f3cc79b2f2af21db694a0fc35bbf72c 100644 --- a/tests/test_run.cpp +++ b/tests/test_run.cpp @@ -3,6 +3,94 @@ #include "../src/cell.hpp" #include "../src/fvm.hpp" +// based on hh/Neuron/steps_B.py +TEST(run, single_compartment) +{ + using namespace nest::mc; + + nest::mc::cell cell; + + // setup global state for the mechanisms + nest::mc::mechanisms::setup_mechanism_helpers(); + + // Soma with diameter 18.8um -> 1.88e-3 cm and HH channel + auto soma = cell.add_soma(1.88e-3/2.0); + soma->add_mechanism(hh_parameters()); + + std::cout << soma->mechanism("membrane"); + + // make the lowered finite volume cell + fvm::fvm_cell<double, int> model(cell); + + std::cout << "CV areas " << model.cv_areas() << "\n"; + + std::cout << "-----------------------------\n"; + + // set initial conditions + using memory::all; + model.voltage()(all) = -65.; + model.initialize(); // have to do this _after_ initial conditions are set + + std::cout << "-----------------------------\n"; + + // run the simulation + //auto dt = 0.02 / 1000.; // convert ms to s + //auto tfinal = 100./1000.; + auto dt = 0.02; // ms + auto tfinal = 5; // ms + int nt = tfinal/dt; + std::vector<double> result; + result.push_back(model.voltage()[0]); + for(auto i=0; i<nt; ++i) { + model.advance(dt); + result.push_back(model.voltage()[0]); + } + std::cout << "took " << nt << " time steps" << std::endl; + + { + std::ofstream fid("v.dat"); + auto t = 0.; + for(auto v:result) { + fid << t << " " << v << "\n"; + t += dt; + } + } +} + +TEST(run, ball_and_stick) +{ + using namespace nest::mc; + + nest::mc::cell cell; + + // setup global state for the mechanisms + nest::mc::mechanisms::setup_mechanism_helpers(); + + // Soma with diameter 12.6157 and HH channel + auto soma = cell.add_soma(12.6157/2.0); + soma->add_mechanism(hh_parameters()); + + // add dendrite with passive channel and 10 compartments + auto dendrite = cell.add_cable(0, segmentKind::dendrite, 0.5, 0.5, 200); + dendrite->set_compartments(5); + dendrite->add_mechanism(pas_parameters()); + + // make the lowered finite volume cell + fvm::fvm_cell<double, int> model(cell); + + std::cout << "CV areas " << model.cv_areas() << "\n"; + + // set initial conditions + using memory::all; + model.voltage()(all) = -65.; + + // run the simulation + auto dt = 0.02; // ms + model.advance(dt); + + std::cout << model.voltage() << "\n"; +} + TEST(run, cable) { using namespace nest::mc; diff --git a/tests/vis.py b/tests/vis.py new file mode 100644 index 0000000000000000000000000000000000000000..751c1fd26e915cf9c725c2146098311a8420e3c4 --- /dev/null +++ b/tests/vis.py @@ -0,0 +1,13 @@ +from matplotlib import pyplot +import numpy as np + +data = np.loadtxt('v.dat') +t = data[:,0] +v = data[:,1] + + +pyplot.plot(t, v) +pyplot.xlabel('time (ms)') +pyplot.ylabel('mV') +pyplot.show() +