diff --git a/data/hh.mod b/data/hh.mod new file mode 100755 index 0000000000000000000000000000000000000000..6020be0c1f6d99ab90a3639916eb32cdb1fab155 --- /dev/null +++ b/data/hh.mod @@ -0,0 +1,120 @@ +TITLE hh.mod squid sodium, potassium, and leak channels + +COMMENT + This is the original Hodgkin-Huxley treatment for the set of sodium, + potassium, and leakage channels found in the squid giant axon membrane. + ("A quantitative description of membrane current and its application + conduction and excitation in nerve" J.Physiol. (Lond.) 117:500-544 (1952).) + Membrane voltage is in absolute mV and has been reversed in polarity + from the original HH convention and shifted to reflect a resting potential + of -65 mV. + Remember to set celsius=6.3 (or whatever) in your HOC file. + See squid.hoc for an example of a simulation using this model. + SW Jaslove 6 March, 1992 +ENDCOMMENT + +UNITS { + (mA) = (milliamp) + (mV) = (millivolt) + (S) = (siemens) +} + +NEURON { + SUFFIX hh + USEION na READ ena WRITE ina + USEION k READ ek WRITE ik + NONSPECIFIC_CURRENT il + RANGE gnabar, gkbar, gl, el, gna, gk + GLOBAL minf, hinf, ninf, mtau, htau, ntau + THREADSAFE : assigned GLOBALs will be per thread +} + +PARAMETER { + gnabar = .12 (S/cm2) <0,1e9> + gkbar = .036 (S/cm2) <0,1e9> + gl = .0003 (S/cm2) <0,1e9> + el = -54.3 (mV) +} + +STATE { + m h n +} + +ASSIGNED { + v (mV) + celsius (degC) + ena (mV) + ek (mV) + + gna (S/cm2) + gk (S/cm2) + ina (mA/cm2) + ik (mA/cm2) + il (mA/cm2) + minf hinf ninf + mtau (ms) htau (ms) ntau (ms) +} + +? currents +BREAKPOINT { + SOLVE states METHOD cnexp + gna = gnabar*m*m*m*h + ina = gna*(v - ena) + gk = gkbar*n*n*n*n + ik = gk*(v - ek) + il = gl*(v - el) +} + +INITIAL { + rates(v) + m = minf + h = hinf + n = ninf +} + +DERIVATIVE states +{ + rates(v) + m' = (minf-m)/mtau + h' = (hinf-h)/htau + n' = (ninf-n)/ntau +} + +PROCEDURE rates(v(mV)) +{ + :Computes rate and other constants at current v. + :Call once from HOC to initialize inf at resting v. + LOCAL alpha, beta, sum, q10 + + q10 = 3^((celsius - 6.3)/10) + + :"m" sodium activation system + alpha = .1 * vtrap(-(v+40),10) + beta = 4 * exp(-(v+65)/18) + sum = alpha + beta + mtau = 1/(q10*sum) + minf = alpha/sum + + :"h" sodium inactivation system + alpha = .07 * exp(-(v+65)/20) + beta = 1 / (exp(-(v+35)/10) + 1) + sum = alpha + beta + htau = 1/(q10*sum) + hinf = alpha/sum + + :"n" potassium activation system + alpha = .01*vtrap(-(v+55),10) + beta = .125*exp(-(v+65)/80) + sum = alpha + beta + ntau = 1/(q10*sum) + ninf = alpha/sum +} + +FUNCTION vtrap(x,y) { :Traps for 0 in denominator of rate eqns. + if (fabs(x/y) < 1e-6) { + vtrap = y*(1 - x/y/2) + }else{ + vtrap = x/(exp(x/y) - 1) + } +} + diff --git a/src/cell.cpp b/src/cell.cpp index 5d9cec1b22e43d265f89ec3998f3c33e566f6ebc..c0d51bc32741c17b73da8215d7076b5325b21cb9 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -2,11 +2,22 @@ namespace nestmc { +cell::cell() +{ + // insert a placeholder segment for the soma + segments_.push_back(make_segment<placeholder_segment>()); + parents_.push_back(0); +} + int cell::num_segments() const { return segments_.size(); } +// +// 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) { if(has_soma()) { @@ -15,24 +26,15 @@ void cell::add_soma(value_type radius, point_type center) ); } - // soma has intself as its own parent - soma_ = num_segments(); - parents_.push_back(num_segments()); - // add segment for the soma if(center.is_set()) { - segments_.push_back( - make_segment<soma_segment>(radius, center) - ); + segments_[0] = make_segment<soma_segment>(radius, center); } else { - segments_.push_back( - make_segment<soma_segment>(radius) - ); + segments_[0] = make_segment<soma_segment>(radius); } } -// add a cable that is provided by the user as a segment_ptr void cell::add_cable(cell::index_type parent, segment_ptr&& cable) { // check for a valid parent id @@ -52,16 +54,44 @@ void cell::add_cable(cell::index_type parent, segment_ptr&& cable) parents_.push_back(parent); } +segment* cell::segment(int index) +{ + if(index<0 || index>=num_segments()) { + throw std::out_of_range( + "attempt to access a segment with invalid index" + ); + } + return segments_[index].get(); +} + +segment const* cell::segment(int index) const +{ + if(index<0 || index>=num_segments()) { + throw std::out_of_range( + "attempt to access a segment with invalid index" + ); + } + return segments_[index].get(); +} + bool cell::has_soma() const { - return soma_ > -1; + return !segment(0)->is_placeholder(); } soma_segment* cell::soma() { if(has_soma()) { - return segments_[soma_].get()->as_soma(); + return segment(0)->as_soma(); + } + return nullptr; +} + +cable_segment* cell::cable(int index) +{ + if(index>0 && index<num_segments()) { + return segment(index)->as_cable(); } return nullptr; } @@ -95,7 +125,7 @@ std::vector<segment_ptr> const& cell::segments() const return segments_; } -void cell::construct() +void cell::construct() const { if(num_segments()) { tree_ = cell_tree(parents_); @@ -104,6 +134,7 @@ void cell::construct() cell_tree const& cell::graph() const { + construct(); return tree_; } diff --git a/src/cell.hpp b/src/cell.hpp index bc3d49e6289099b59e24e813c1a91e6b386c16c5..214a5b3c9ba090a2c38e007b50f1594512d53268 100644 --- a/src/cell.hpp +++ b/src/cell.hpp @@ -1,7 +1,9 @@ #pragma once -#include <vector> +#include <mutex> #include <stdexcept> +#include <thread> +#include <vector> #include "segment.hpp" #include "cell_tree.hpp" @@ -17,6 +19,9 @@ namespace nestmc { using value_type = double; using point_type = point<value_type>; + // constructor + cell(); + /// add a soma to the cell /// radius must be specified void add_soma(value_type radius, point_type center=point_type()); @@ -37,9 +42,18 @@ namespace nestmc { bool has_soma() const; + class segment* segment(int index); + class segment const* segment(int index) const; + /// access pointer to the soma + /// returns nullptr if the cell has no soma soma_segment* soma(); + /// access pointer to a cable segment + /// will throw an std::out_of_range exception if + /// the cable index is not valid + cable_segment* cable(int index); + /// the volume of the cell value_type volume() const; @@ -48,10 +62,6 @@ namespace nestmc { std::vector<segment_ptr> const& segments() const; - /// generate the internal representation of the connectivity - /// graph for the cell segments - void construct(); - /// the connectivity graph for the cell segments cell_tree const& graph() const; @@ -61,6 +71,11 @@ namespace nestmc { private: + /// generate the internal representation of the connectivity + /// graph for the cell segments + void construct() const; + + // // the local description of the cell which can be modified by the user // in a ad-hoc manner (adding segments, modifying segments, etc) @@ -70,15 +85,15 @@ namespace nestmc { std::vector<index_type> parents_; // the segments std::vector<segment_ptr> segments_; - // index of the soma - int soma_ = -1; // // fixed cell description, which is computed from the layout description - // above + // this computed whenever a call to the graph() method is made + // the graph method is const, so tree_ is mutable // + // note: this isn't remotely thread safe! - cell_tree tree_; + mutable cell_tree tree_; }; // create a cable by forwarding cable construction parameters provided by the user diff --git a/src/segment.hpp b/src/segment.hpp index 7710ed9714aa112dea731cf27bfd648027bcca16..a2429032f28d97aa61f87eb31f79b9783eca4246 100644 --- a/src/segment.hpp +++ b/src/segment.hpp @@ -20,7 +20,8 @@ struct segment_properties { enum class segmentKind { soma, dendrite, - axon + axon, + none }; // forward declarations of segment specializations @@ -68,6 +69,11 @@ class segment { return nullptr; } + virtual bool is_placeholder() const + { + return false; + } + segment_properties<value_type> properties; protected: @@ -75,6 +81,35 @@ class segment { segmentKind kind_; }; +class placeholder_segment : public segment +{ + public: + + using base = segment; + using base::kind_; + using base::value_type; + + placeholder_segment() + { + kind_ = segmentKind::none; + } + + value_type volume() const override + { + return std::numeric_limits<value_type>::quiet_NaN(); + } + + value_type area() const override + { + return std::numeric_limits<value_type>::quiet_NaN(); + } + + bool is_placeholder() const override + { + return true; + } +}; + class soma_segment : public segment { public : diff --git a/src/stimulus.hpp b/src/stimulus.hpp new file mode 100644 index 0000000000000000000000000000000000000000..94a4dddfddf1cab80272de74bd3a94f9130989fd --- /dev/null +++ b/src/stimulus.hpp @@ -0,0 +1,52 @@ +#pragma once + +namespace nestmc { + +class i_clamp { + public: + + using value_type = double; + + i_clamp(value_type del, value_type dur, value_type amp) + : delay_(del), + duration_(dur), + amplitude_(amp) + {} + + value_type delay() const { + return delay_; + } + value_type duration() const { + return duration_; + } + value_type amplitude() const { + return amplitude_; + } + + void set_delay(value_type d) { + delay_ = d; + } + void set_duration(value_type d) { + duration_ = d; + } + void set_amplitude(value_type a) { + amplitude_ = a; + } + + // current is set to amplitude for time in the half open interval: + // t \in [delay, delay+duration) + value_type amplitude(double t) { + if(t>=delay_ && t<(delay_+duration_)) { + return amplitude_; + } + return 0; + } + + private: + + value_type delay_ = 0; + value_type duration_ = 0; + value_type amplitude_ = 0; +}; + +} // namespace nestmc diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3bfe22c59526798ea1108ad1862a7e418a734182..53db3ff016a2765cecef53d807b20d09f5c8eaa1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -16,6 +16,7 @@ set(TEST_SOURCES test_cell.cpp test_point.cpp test_segment.cpp + test_stimulus.cpp test_swcio.cpp test_tree.cpp test_run.cpp diff --git a/tests/test_cell.cpp b/tests/test_cell.cpp index d9cb96b8e816d1942438d98ff13a5ee3e1bba940..68748512f594702d84a68d89c3eb72ec8010af25 100644 --- a/tests/test_cell.cpp +++ b/tests/test_cell.cpp @@ -145,7 +145,6 @@ TEST(cell_type, multiple_cables) EXPECT_EQ(c.area(), 8. + math::area_sphere(soma_radius)); // construct the graph - c.construct(); auto const& con = c.graph(); EXPECT_EQ(con.num_segments(), 5u); diff --git a/tests/test_run.cpp b/tests/test_run.cpp index 9ac528f7a5768c08b8043d019f9207b7339506e7..f80cb41f612d2fd53db9162ec6b8400634cf91bc 100644 --- a/tests/test_run.cpp +++ b/tests/test_run.cpp @@ -8,10 +8,32 @@ TEST(run, init) nestmc::cell cell; - cell.add_soma(18.8); - auto& props = cell.soma()->properties; + cell.add_soma(12.6157/2.0); + //auto& props = cell.soma()->properties; - cell.construct(); + cell.add_cable(0, segmentKind::dendrite, 0.5, 0.5, 200); - EXPECT_EQ(cell.graph().num_segments(), 1u); + EXPECT_EQ(cell.graph().num_segments(), 2u); + + for(auto &s : cell.segments()) { + std::cout << "volume : " << s->volume() + << " area : " << s->area() + << " ratio : " << s->volume()/s->area() << std::endl; + } + +#ifdef example_1 + + // in this context (i.e. attached to a segment on a high-level cell) + // a mechanism is essentially a set of parameters + // - the only "state" is that used to define parameters + cell.soma()->add_mechanism("hh"); + + auto& soma_hh = cell.soma()->mechanisms("hh"); + soma_hh.set("gnabar", 0.12); + soma_hh.set("gkbar", 0.036); + soma_hh.set("gl", 0.0003); + soma_hh.set("el", -54.3); + + fvm_cell fv(cell); +#endif } diff --git a/tests/test_stimulus.cpp b/tests/test_stimulus.cpp new file mode 100644 index 0000000000000000000000000000000000000000..43be56f158e07e409c94b21cca7c293026b8d830 --- /dev/null +++ b/tests/test_stimulus.cpp @@ -0,0 +1,33 @@ +#include "gtest.h" + +#include "../src/stimulus.hpp" + +TEST(stimulus, i_clamp) +{ + using namespace nestmc; + + // stimulus with delay 2, duration 0.5, amplitude 6.0 + i_clamp stim(2.0, 0.5, 6.0); + + EXPECT_EQ(stim.delay(), 2.0); + EXPECT_EQ(stim.duration(), 0.5); + EXPECT_EQ(stim.amplitude(), 6.0); + + // test that current only turned on in the half open interval + // t \in [2, 2.5) + EXPECT_EQ(stim.amplitude(0.0), 0.0); + EXPECT_EQ(stim.amplitude(1.0), 0.0); + EXPECT_EQ(stim.amplitude(2.0), 6.0); + EXPECT_EQ(stim.amplitude(2.4999), 6.0); + EXPECT_EQ(stim.amplitude(2.5), 0.0); + + // update: delay 1.0, duration 1.5, amplitude 3.0 + stim.set_delay(1.0); + stim.set_duration(1.5); + stim.set_amplitude(3.0); + + EXPECT_EQ(stim.delay(), 1.0); + EXPECT_EQ(stim.duration(), 1.5); + EXPECT_EQ(stim.amplitude(), 3.0); +} +