diff --git a/arbor/fvm_layout.cpp b/arbor/fvm_layout.cpp index dc6364325399b3f7a604a5d95a0317e4334bc241..5fbfafbe20e42f52e83b4ebc87adaa713d12bd43 100644 --- a/arbor/fvm_layout.cpp +++ b/arbor/fvm_layout.cpp @@ -592,7 +592,12 @@ fvm_mechanism_data fvm_build_mechanism_data(const cable_cell_global_properties& assign(param_names, util::keys(info.parameters)); sort(param_names); - for (auto& p: param_names) { + std::size_t n_param = param_names.size(); + param_dflt.reserve(n_param); + config.param_values.reserve(n_param); + + for (std::size_t i = 0; i<n_param; ++i) { + const auto& p = param_names[i]; config.param_values.emplace_back(p, std::vector<value_type>{}); param_dflt.push_back(info.parameters.at(p).default_value); } @@ -600,23 +605,21 @@ fvm_mechanism_data fvm_build_mechanism_data(const cable_cell_global_properties& mcable_map<double> support; std::vector<mcable_map<double>> param_maps; - { - std::unordered_map<std::string, mcable_map<double>> keyed_param_maps; - for (auto& on_cable: entry.second) { - verify_mechanism(info, on_cable.second); - mcable cable = on_cable.first; - - support.insert(cable, 1.); - for (auto param_assign: on_cable.second.values()) { - keyed_param_maps[param_assign.first].insert(cable, param_assign.second); - } - } - for (auto& p: param_names) { - param_maps.push_back(std::move(keyed_param_maps[p])); + param_maps.resize(n_param); + + for (auto& on_cable: entry.second) { + verify_mechanism(info, on_cable.second); + mcable cable = on_cable.first; + const auto& set_params = on_cable.second.values(); + + support.insert(cable, 1.); + for (std::size_t i = 0; i<n_param; ++i) { + double value = value_by_key(set_params, param_names[i]).value_or(param_dflt[i]); + param_maps[i].insert(cable, value); } } - std::vector<double> param_on_cv(config.param_values.size()); + std::vector<double> param_on_cv(n_param); for (auto cv: D.geometry.cell_cvs(cell_idx)) { double area = 0; @@ -627,17 +630,18 @@ fvm_mechanism_data fvm_build_mechanism_data(const cable_cell_global_properties& if (!area_on_cable) continue; area += area_on_cable; - for (auto i: count_along(param_on_cv)) { - param_on_cv[i] += embedding.integrate_area(c.branch, pw_over_cable(param_maps[i], c, param_dflt[i])); + for (std::size_t i = 0; i<n_param; ++i) { + param_on_cv[i] += embedding.integrate_area(c.branch, pw_over_cable(param_maps[i], c, 0.)); } } if (area>0) { - double oo_cv_area = 1./D.cv_area[cv]; config.cv.push_back(cv); - config.norm_area.push_back(area*oo_cv_area); + config.norm_area.push_back(area/D.cv_area[cv]); + + double oo_area = 1./area; for (auto i: count_along(param_on_cv)) { - config.param_values[i].second.push_back(param_on_cv[i]*oo_cv_area); + config.param_values[i].second.push_back(param_on_cv[i]*oo_area); } } } diff --git a/test/unit/test_fvm_layout.cpp b/test/unit/test_fvm_layout.cpp index 2b6fda52e5be9b475dd139522ed3f0d5b186f6f3..bbdc25fd98ff962bb85476bc3a7b68b36e7f8463 100644 --- a/test/unit/test_fvm_layout.cpp +++ b/test/unit/test_fvm_layout.cpp @@ -36,27 +36,27 @@ namespace { // Bulk resistivity: 90 Ω·cm // capacitance: // soma: 0.01 F/m² [default] - // segment 1: 0.017 F/m² - // segment 2: 0.013 F/m² - // segment 3: 0.018 F/m² + // branch 1: 0.017 F/m² + // branch 2: 0.013 F/m² + // branch 3: 0.018 F/m² // // Soma diameter: 14 µm // Some mechanisms: HH (default params) // - // Segment 1 diameter: 1 µm - // Segment 1 length: 200 µm + // Branch 1 diameter: 1 µm + // Branch 1 length: 200 µm // - // Segment 2 diameter: 0.8 µm - // Segment 2 length: 300 µm + // Branch 2 diameter: 0.8 µm + // Branch 2 length: 300 µm // - // Segment 3 diameter: 0.7 µm - // Segment 3 length: 180 µm + // Branch 3 diameter: 0.7 µm + // Branch 3 length: 180 µm // // Dendrite mechanisms: passive (default params). - // Stimulus at end of segment 2, amplitude 0.45. - // Stimulus at end of segment 3, amplitude -0.2. + // Stimulus at end of branch 2, amplitude 0.45. + // Stimulus at end of branch 3, amplitude -0.2. // - // All dendrite segments with 4 compartments. + // All dendrite branches with 4 compartments. soma_cell_builder builder(7.); auto b1 = builder.add_branch(0, 200, 0.5, 0.5, 4, "dend"); @@ -117,13 +117,13 @@ TEST(fvm_layout, mech_index) { EXPECT_EQ(mechanismKind::density, hh_config.kind); EXPECT_EQ(ivec({0,6}), hh_config.cv); - // Three expsyn synapses, two 0.4 along segment 1, and one 0.4 along segment 5. + // Three expsyn synapses, two 0.4 along branch 1, and one 0.4 along branch 5. // These two synapses can be coalesced into 1 synapse - // 0.4 along => second (non-parent) CV for segment. + // 0.4 along => second (non-parent) CV for branch. EXPECT_EQ(ivec({3, 17}), expsyn_config.cv); - // One exp2syn synapse, 0.4 along segment 4. + // One exp2syn synapse, 0.4 along branch 4. EXPECT_EQ(ivec({13}), exp2syn_config.cv); @@ -459,28 +459,26 @@ namespace { TEST(fvm_layout, density_norm_area) { // Test area-weighted linear combination of density mechanism parameters. - // Create a cell with 4 segments: + // Create a cell with 4 branches: // - Soma (branch 0) plus three dendrites (1, 2, 3) meeting at a branch point. - // - HH mechanism on all segments. + // - HH mechanism on all branches. // - Discretize with 3 CVs per non-soma branch, centred on forks. // // The CV corresponding to the branch point should comprise the terminal - // 1/6 of segment 1 and the initial 1/6 of segments 2 and 3. + // 1/6 of branch 1 and the initial 1/6 of branches 2 and 3. // // The HH mechanism current density parameters ('gnabar', 'gkbar' and 'gl') are set - // differently for each segment: + // differently for each branch: // // soma: all default values (gnabar = 0.12, gkbar = .036, gl = .0003) - // segment 1: gl = .0002 - // segment 2: gkbar = .05 - // segment 3: gkbar = .07, gl = .0004 + // branch 1: gl = .0002 + // branch 2: gkbar = .05 + // branch 3: gkbar = .07, gl = .0004 // // Geometry: - // segment 1: 100 µm long, 1 µm diameter cylinder. - // segment 2: 200 µm long, diameter linear taper from 1 µm to 0.2 µm. - // segment 3: 150 µm long, 0.8 µm diameter cylinder. - // - // Use divided compartment view on segments to compute area contributions. + // branch 1: 100 µm long, 1 µm diameter cylinder. + // branch 2: 200 µm long, diameter linear taper from 1 µm to 0.2 µm. + // branch 3: 150 µm long, 0.8 µm diameter cylinder. soma_cell_builder builder(12.6157/2.0); @@ -521,31 +519,31 @@ TEST(fvm_layout, density_norm_area) { std::vector<double> expected_gkbar(ncv, dflt_gkbar); std::vector<double> expected_gl(ncv, dflt_gl); - // Last 1/6 of segment 1 + // Last 1/6 of branch 1 double seg1_area_right = cells[0].embedding().integrate_area(mcable{1, 5./6., 1.}); - // First 1/6 of segment 2 + // First 1/6 of branch 2 double seg2_area_left = cells[0].embedding().integrate_area(mcable{2, 0., 1./6.}); - // First 1/6 of segment 3 + // First 1/6 of branch 3 double seg3_area_left = cells[0].embedding().integrate_area(mcable{3, 0., 1./6.}); // CV 0: soma - // CV1: left of segment 1 + // CV1: left of branch 1 expected_gl[0] = dflt_gl; expected_gl[1] = seg1_gl; expected_gl[2] = seg1_gl; expected_gl[3] = seg1_gl; - // CV 4: mix of right of segment 1 and left of segments 2 and 3. + // CV 4: mix of right of branch 1 and left of branches 2 and 3. expected_gkbar[4] = wmean(seg1_area_right, dflt_gkbar, seg2_area_left, seg2_gkbar, seg3_area_left, seg3_gkbar); expected_gl[4] = wmean(seg1_area_right, seg1_gl, seg2_area_left, dflt_gl, seg3_area_left, seg3_gl); - // CV 5-7: just segment 2 + // CV 5-7: just branch 2 expected_gkbar[5] = seg2_gkbar; expected_gkbar[6] = seg2_gkbar; expected_gkbar[7] = seg2_gkbar; - // CV 8-10: just segment 3 + // CV 8-10: just branch 3 expected_gkbar[8] = seg3_gkbar; expected_gkbar[9] = seg3_gkbar; expected_gkbar[10] = seg3_gkbar; @@ -572,6 +570,91 @@ TEST(fvm_layout, density_norm_area) { EXPECT_TRUE(testing::seq_almost_eq<double>(expected_gl, gl)); } +TEST(fvm_layout, density_norm_area_partial) { + // Test area-weighted linear combination of density mechanism parameters, + // when mechanism covers only part of CV. + + // Create a cell with 2 unbranched cables: + // - Soma (branch 0) plus one constant-diameter dendrite. + // - HH mechanism on part of the dendrite. + // - Discretize with 1 CV per branch. + // + // The HH mechanism is applied to the first 30% and last 60% of the dendrite: + // + // first 30%: all default values (gnabar = 0.12, gkbar = .036, gl = .0003) + // last 60%: gl = .0002, gkbar = .05 + // + // Geometry: + // dendrite: 200 µm long, diameter linear taper from 1 µm to 0.2 µm. + + soma_cell_builder builder(12.6157/2.0); + + // p len r1 r2 ncomp tag + builder.add_branch(0, 200, 0.5, 0.1, 1, "dend"); + + double dflt_gnabar = .12; + double dflt_gkbar = .036; + double dflt_gl = 0.0003; + + double end_gl = .0002; + double end_gkbar = .05; + + auto hh_begin = mechanism_desc("hh"); + + auto hh_end = mechanism_desc("hh"); + hh_end["gl"] = end_gl; + hh_end["gkbar"] = end_gkbar; + + auto cell = builder.make_cell(); + cell.default_parameters.discretization = cv_policy_fixed_per_branch(1); + + cell.paint(mcable{1, 0., 0.3}, hh_begin); + cell.paint(mcable{1, 0.4, 1.}, hh_end); + + std::vector<cable_cell> cells{std::move(cell)}; + + // First 30% of branch 1. + double b1_area_begin = cells[0].embedding().integrate_area(mcable{1, 0., 0.3}); + // Last 60% of branch 1. + double b1_area_end = cells[0].embedding().integrate_area(mcable{1, 0.4, 1.}); + // All of branch 1. + double b1_area = cells[0].embedding().integrate_area(mcable{1, 0., 1.}); + + double expected_norm_area = (b1_area_begin+b1_area_end)/b1_area; + double expected_gnabar = dflt_gnabar; + double expected_gkbar = (dflt_gkbar*b1_area_begin + end_gkbar*b1_area_end)/(b1_area_begin + b1_area_end); + double expected_gl = (dflt_gl*b1_area_begin + end_gl*b1_area_end)/(b1_area_begin + b1_area_end); + + cable_cell_global_properties gprop; + gprop.default_parameters = neuron_parameter_defaults; + + fvm_cv_discretization D = fvm_cv_discretize(cells, gprop.default_parameters); + fvm_mechanism_data M = fvm_build_mechanism_data(gprop, cells, D); + + // Grab the HH parameters from the mechanism. + + EXPECT_EQ(1u, M.mechanisms.size()); + ASSERT_EQ(1u, M.mechanisms.count("hh")); + + auto& norm_area = M.mechanisms.at("hh").norm_area; + ASSERT_EQ(1u, norm_area.size()); + EXPECT_DOUBLE_EQ(expected_norm_area, norm_area[0]); + + auto& hh_params = M.mechanisms.at("hh").param_values; + + auto& gkbar = value_by_key(hh_params, "gkbar"s).value(); + auto& gnabar = value_by_key(hh_params, "gnabar"s).value(); + auto& gl = value_by_key(hh_params, "gl"s).value(); + + ASSERT_EQ(1u, gkbar.size()); + ASSERT_EQ(1u, gnabar.size()); + ASSERT_EQ(1u, gl.size()); + + EXPECT_DOUBLE_EQ(expected_gkbar, gkbar[0]); + EXPECT_DOUBLE_EQ(expected_gnabar, gnabar[0]); + EXPECT_DOUBLE_EQ(expected_gl, gl[0]); +} + TEST(fvm_layout, valence_verify) { auto cell = soma_cell_builder(6).make_cell(); cell.paint("soma", "test_cl_valence"); @@ -599,9 +682,9 @@ TEST(fvm_layout, valence_verify) { } TEST(fvm_layout, ion_weights) { - // Create a cell with 4 segments: - // - Soma (segment 0) plus three dendrites (1, 2, 3) meeting at a branch point. - // - Dendritic segments are given 1 compartments each. + // Create a cell with 4 branches: + // - Soma (branch 0) plus three dendrites (1, 2, 3) meeting at a branch point. + // - Dendritic branches are given 1 compartments each. // // / // d2 @@ -611,7 +694,7 @@ TEST(fvm_layout, ion_weights) { // d3 // // The CV corresponding to the branch point should comprise the terminal - // 1/2 of segment 1 and the initial 1/2 of segments 2 and 3. + // 1/2 of branch 1 and the initial 1/2 of branches 2 and 3. // // Geometry: // soma 0: radius 5 µm