Skip to content

Commit 4f53d26

Browse files
authored
neuronapi: recompute 3d-derived diam in nrn_segment_diam_get (#3814)
When a section's geometry is defined by 3d points, NEURON rewrites the segment diam from those points lazily, gated per-section on recalc_area_. nrn_segment_diam_get returned the range-variable pointer without checking that flag, so a diam read after pt3dadd (before define_shape/finitialize) returned a stale default rather than the 3d-derived value. Mirror the range-variable read path (nrnpy_nrn.cpp): trigger the pending per-section recompute via nrn_area_ri before reading. Uses the per-section recalc_area_ flag and nrn_area_ri (not the global diam_changed/recalc_diam, which rebuilds the whole solver matrix and asserts !tree_changed). Adds test/api/segment_diam.cpp: a uniform 3d section reads diam 10 both before and after finitialize. Documents the behavior in capi.rst.
1 parent 4a478b1 commit 4f53d26

4 files changed

Lines changed: 73 additions & 3 deletions

File tree

docs/capi.rst

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -526,10 +526,15 @@ Segments
526526
:param x: Normalized position along Section (0.0 to 1.0).
527527
:returns: Diameter in microns at the specified position.
528528
529+
When the section geometry is defined by 3d points (:func:`pt3dadd`), the
530+
segment diameter is derived from those points. This getter triggers that
531+
recompute if it is pending, so the value is correct even before an explicit
532+
geometry pass such as :func:`define_shape` or :func:`finitialize`.
533+
529534
**C Usage:**
530-
535+
531536
.. code-block:: c
532-
537+
533538
double diameter = nrn_segment_diam_get(soma, 0.5); // Get diameter at middle
534539
535540
**Python Equivalent:**

src/nrniv/neuronapi.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "ocfunc.h"
1010
#include "ocjump.h"
1111
#include "parse.hpp"
12+
#include "nrn_ansi.h"
1213
#include "section.h"
1314
#include "shapeplt.h"
1415
#include <cstring>
@@ -195,6 +196,12 @@ void nrn_segment_diam_set(Section* const sec, const double x, const double diam)
195196
}
196197

197198
double nrn_segment_diam_get(Section* const sec, const double x) {
199+
// Geometry from 3d points writes diam lazily, gated per-section on
200+
// recalc_area_. Mirror the range-variable read path (nrnpy_nrn.cpp) so a
201+
// diam read after pt3dadd returns the 3d-derived value, not a stale default.
202+
if (sec && sec->recalc_area_) {
203+
nrn_area_ri(sec);
204+
}
198205
Node* const node = node_exact(sec, x);
199206
for (auto prop = node->prop; prop; prop = prop->next) {
200207
if (prop->_type == MORPHOLOGY) {

test/api/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
foreach(api_test_file hh_sim.cpp netcon.cpp node_index.cpp sections.cpp vclamp.cpp)
1+
foreach(api_test_file hh_sim.cpp netcon.cpp node_index.cpp segment_diam.cpp sections.cpp vclamp.cpp)
22
string(REPLACE "." "_" api_test_name "${api_test_file}")
33
add_executable(${api_test_name} ${api_test_file})
44
cpp_cc_configure_sanitizers(TARGET ${api_test_name})

test/api/segment_diam.cpp

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// NOTE: this assumes neuronapi.h is on your CPLUS_INCLUDE_PATH
2+
// Exercises nrn_segment_diam_get's lazy recompute of 3d-derived diameter.
3+
// When a section's geometry comes from pt3dadd, NEURON rewrites the segment
4+
// diam lazily (gated per-section on recalc_area_). nrn_segment_diam_get must
5+
// trigger that recompute so a read before any geometry pass returns the
6+
// 3d-derived value, matching what Python's seg.diam already does.
7+
#include <array>
8+
#include <cmath>
9+
#include <iostream>
10+
#include "neuronapi.h"
11+
12+
using std::cerr;
13+
using std::endl;
14+
15+
extern "C" void modl_reg(){/* No modl_reg */};
16+
17+
static bool close_to(double got, double want, const char* msg) {
18+
if (std::fabs(got - want) > 1e-9) {
19+
cerr << "FAIL: " << msg << " — got " << got << ", want " << want << endl;
20+
return false;
21+
}
22+
return true;
23+
}
24+
25+
static void pt3dadd(Section* sec, double xx, double yy, double zz, double diam) {
26+
nrn_section_push(sec);
27+
nrn_double_push(xx);
28+
nrn_double_push(yy);
29+
nrn_double_push(zz);
30+
nrn_double_push(diam);
31+
nrn_function_call(nrn_symbol("pt3dadd"), 4);
32+
nrn_double_pop(); // pt3dadd returns a number
33+
nrn_section_pop();
34+
}
35+
36+
int main(void) {
37+
static std::array<const char*, 4> argv = {"segment_diam", "-nogui", "-nopython", nullptr};
38+
nrn_init(3, argv.data());
39+
40+
// Uniform 3d geometry: two points, both diam 10, so diam is 10 everywhere.
41+
Section* s = nrn_section_new("s");
42+
pt3dadd(s, 0, 0, 0, 10);
43+
pt3dadd(s, 10, 0, 0, 10);
44+
45+
bool ok = true;
46+
47+
// Read BEFORE any geometry pass. Without the recompute this returns the
48+
// 500.0 default; with it, the 3d-derived 10.0.
49+
ok &= close_to(nrn_segment_diam_get(s, 0.5), 10.0, "diam read before geometry pass");
50+
51+
// A geometry pass must not change the already-correct value.
52+
nrn_double_push(-65);
53+
nrn_function_call(nrn_symbol("finitialize"), 1);
54+
nrn_double_pop();
55+
ok &= close_to(nrn_segment_diam_get(s, 0.5), 10.0, "diam read after finitialize");
56+
57+
return ok ? 0 : 1;
58+
}

0 commit comments

Comments
 (0)