From 68c0f0c3734f55e5b3367441175e6bcf8e9fa196 Mon Sep 17 00:00:00 2001 From: "w.wybo" Date: Wed, 14 Jan 2026 11:23:14 +0100 Subject: [PATCH 01/18] remove OUClamp2 and WNClamp from automatically compile modfiles (they use neuron9-incompatible patterns) --- .../{OUClamp2.mod => OUClamp2.gives_error_in_neuron9_mod} | 0 .../{WNClamp.mod => WNClamp.gives_error_in_neuron9_mod} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/neat/simulations/neuron/mech_storage/{OUClamp2.mod => OUClamp2.gives_error_in_neuron9_mod} (100%) rename src/neat/simulations/neuron/mech_storage/{WNClamp.mod => WNClamp.gives_error_in_neuron9_mod} (100%) diff --git a/src/neat/simulations/neuron/mech_storage/OUClamp2.mod b/src/neat/simulations/neuron/mech_storage/OUClamp2.gives_error_in_neuron9_mod similarity index 100% rename from src/neat/simulations/neuron/mech_storage/OUClamp2.mod rename to src/neat/simulations/neuron/mech_storage/OUClamp2.gives_error_in_neuron9_mod diff --git a/src/neat/simulations/neuron/mech_storage/WNClamp.mod b/src/neat/simulations/neuron/mech_storage/WNClamp.gives_error_in_neuron9_mod similarity index 100% rename from src/neat/simulations/neuron/mech_storage/WNClamp.mod rename to src/neat/simulations/neuron/mech_storage/WNClamp.gives_error_in_neuron9_mod From 6dc82ea3c1d973e1d548725dbeffee56e67e5294 Mon Sep 17 00:00:00 2001 From: "w.wybo" Date: Wed, 14 Jan 2026 12:12:40 +0100 Subject: [PATCH 02/18] add compatibility with neuron 9, maintain backwards compatibility with neuron < 9 --- .github/workflows/neat-build.yml | 34 +++++++++++++++++++++- src/neat/simulations/neuron/neuronmodel.py | 27 ++++++++++++----- 2 files changed, 53 insertions(+), 8 deletions(-) diff --git a/.github/workflows/neat-build.yml b/.github/workflows/neat-build.yml index 7b83fa96..674ab766 100644 --- a/.github/workflows/neat-build.yml +++ b/.github/workflows/neat-build.yml @@ -58,7 +58,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [ '3.9', '3.10', '3.12' ] + python-version: [ '3.9', '3.10', '3.12', '3.13' ] fail-fast: false @@ -79,6 +79,38 @@ jobs: pip install setuptools pip install . + # Unit tests + - name: Run tests + run: | + pytest -s -o norecursedirs='*' -o log_cli=true -o log_cli_level="DEBUG" tests + + + build_and_test_legacy_neuron: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [ '3.10' ] + + fail-fast: false + + steps: + # Checkout the repository contents + - name: Checkout NEAT code + uses: actions/checkout@v4 + + # Setup Python version + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + # Install NEAT + - name: Install NEAT + run: | + pip install setuptools + pip install neuron==8.2.4 + pip install . + # Unit tests - name: Run tests run: | diff --git a/src/neat/simulations/neuron/neuronmodel.py b/src/neat/simulations/neuron/neuronmodel.py index 1d4f3488..9340669f 100755 --- a/src/neat/simulations/neuron/neuronmodel.py +++ b/src/neat/simulations/neuron/neuronmodel.py @@ -90,13 +90,7 @@ def array(*args, **kwargs): def load_neuron_model(name): - path = os.path.join( - os.path.dirname(__file__), - f"tmp/{name}/{platform.machine()}/.libs/libnrnmech.so", - ) - if os.path.exists(path): - h.nrn_load_dll(path) # load all mechanisms - else: + def print_err(): path_name = os.path.join(os.path.dirname(__file__), "tmp/") raise FileNotFoundError( f"The NEURON model named '{name}' is not installed. " @@ -105,6 +99,25 @@ def load_neuron_model(name): f"Installed models will be in '{path_name}'." ) + if int(neuron.__version__.split(".")[0]) < 9: + path = os.path.join( + os.path.dirname(__file__), + f"tmp/{name}/{platform.machine()}/.libs/libnrnmech.so", + ) + if os.path.exists(path): + h.nrn_load_dll(path) # load all mechanisms + else: + print_err() + else: + path = os.path.join( + os.path.dirname(__file__), + f"tmp/{name}", + ) + if os.path.exists(path): + neuron.load_mechanisms(path) + else: + print_err() + class MechName(object): def __init__(self): From d9914b005632d324c52fd6d14129364cbbcda061 Mon Sep 17 00:00:00 2001 From: "w.wybo" Date: Wed, 14 Jan 2026 12:45:24 +0100 Subject: [PATCH 03/18] remove deprecated noise mechs --- .../OUClamp2.gives_error_in_neuron9_mod | 168 ------------------ .../WNClamp.gives_error_in_neuron9_mod | 138 -------------- src/neat/simulations/neuron/neuronmodel.py | 7 +- 3 files changed, 2 insertions(+), 311 deletions(-) delete mode 100755 src/neat/simulations/neuron/mech_storage/OUClamp2.gives_error_in_neuron9_mod delete mode 100755 src/neat/simulations/neuron/mech_storage/WNClamp.gives_error_in_neuron9_mod diff --git a/src/neat/simulations/neuron/mech_storage/OUClamp2.gives_error_in_neuron9_mod b/src/neat/simulations/neuron/mech_storage/OUClamp2.gives_error_in_neuron9_mod deleted file mode 100755 index fbd739f6..00000000 --- a/src/neat/simulations/neuron/mech_storage/OUClamp2.gives_error_in_neuron9_mod +++ /dev/null @@ -1,168 +0,0 @@ -COMMENT -Noise current characterized by gaussian distribution -with mean mean and standerd deviation stdev. - -Borrows from NetStim's code so it can be linked with an external instance -of the Random class in order to generate output that is independent of -other instances of InGau. - -User specifies the time at which the noise starts, -and the duration of the noise. -Since a new value is drawn at each time step, -should be used only with fixed time step integration. -ENDCOMMENT - -NEURON { - POINT_PROCESS OUClamp2 - NONSPECIFIC_CURRENT i - RANGE mean, stdev, tau - : RANGE dt - RANGE delay, dur, seed_usr - THREADSAFE : true only if every instance has its own distinct Random - POINTER donotuse -} - -UNITS { - (nA) = (nanoamp) -} - -PARAMETER { - delay (ms) : delay until noise starts - dur (ms) <0, 1e9> : duration of noise - tau = 100.(ms) - mean = 0 (nA) - stdev = 1 (nA) - seed_usr = 42 (1) - : dt = .1 (ms) -} - -ASSIGNED { - dt (ms) - on - per (ms) - ival (nA) - ivar (nA) - i (nA) - flag1 - exp_decay - amp_gauss (nA) - donotuse -} - -INITIAL { - VERBATIM - printf("dt = %.2f\n", dt); - ENDVERBATIM - per = dt - on = 0 - ival = 0 - ivar = 0 - i = 0 - flag1 = 0 - exp_decay = exp(-dt/tau) - amp_gauss = sqrt(1. - exp(-2.*dt/tau)) - net_send(delay, 1) - seed(seed_usr) -} - -PROCEDURE seed(x) { - set_seed(x) -} - -BEFORE BREAKPOINT { - i = -ivar -} - -BREAKPOINT { : this block must exist so that a current is actually generated - if (t < delay) { - ivar = 0. - }else{ - if (flag1 == 0) { - flag1 = 1 - ivar = mean - } - if (t < delay+dur) { - ivar = mean + exp_decay * (ivar-mean) + stdev * amp_gauss * ival :normrand(0,1) - : ivar = ivar + (mean - ivar) * dt / tau + stdev * sqrt(2*dt/tau) * ival: normrand(0,1) - : ivar = stdev*ival - }else{ - ivar = 0. - } - } -} - -NET_RECEIVE (w) { - if (dur>0) { - if (flag==1) { - if (on==0) { : turn on - on=1 - net_send(dur,1) : to turn it off -: ival = (hi-lo)*urand() + lo : first sample - ival = grand() : first sample - net_send(per, 2) : prepare for next sample - } else { - if (on==1) { : turn off - on=0 - ival = 0 - } - } - } - if (flag==2) { - if (on==1) { - ival = grand() -: printf("time %f \ti %f\n", t, ival) - net_send(per, 2) : prepare for next sample - } - } - } -} - -VERBATIM -double nrn_random_pick(void* r); -void* nrn_random_arg(int argpos); -ENDVERBATIM - -: FUNCTION erand() { -: FUNCTION urand() { -FUNCTION grand() { -VERBATIM - if (_p_donotuse) { - /* - : Supports separate independent but reproducible streams for - : each instance. However, the corresponding hoc Random - : distribution MUST be set to Random.uniform(0,1) - */ -// _lerand = nrn_random_pick(_p_donotuse); -// _lurand = nrn_random_pick(_p_donotuse); - _lgrand = nrn_random_pick(_p_donotuse); - }else{ - /* only can be used in main thread */ - if (_nt != nrn_threads) { -hoc_execerror("multithread random in InUnif"," only via hoc Random"); - } -ENDVERBATIM - : the old standby. Cannot use if reproducible parallel sim - : independent of nhost or which host this instance is on - : is desired, since each instance on this cpu draws from - : the same stream -: erand = exprand(1) -: urand = scop_random() - grand = normrand(0,1) -: printf("%f\n", grand) -VERBATIM - } -ENDVERBATIM -} - -PROCEDURE noiseFromRandom() { -VERBATIM - { - void** pv = (void**)(&_p_donotuse); - if (ifarg(1)) { - *pv = nrn_random_arg(1); - }else{ - *pv = (void*)0; - } - } -ENDVERBATIM -} diff --git a/src/neat/simulations/neuron/mech_storage/WNClamp.gives_error_in_neuron9_mod b/src/neat/simulations/neuron/mech_storage/WNClamp.gives_error_in_neuron9_mod deleted file mode 100755 index fac185b1..00000000 --- a/src/neat/simulations/neuron/mech_storage/WNClamp.gives_error_in_neuron9_mod +++ /dev/null @@ -1,138 +0,0 @@ -COMMENT -Noise current characterized by gaussian distribution -with mean mean and standerd deviation stdev. - -Borrows from NetStim's code so it can be linked with an external instance -of the Random class in order to generate output that is independent of -other instances of InGau. - -User specifies the time at which the noise starts, -and the duration of the noise. -Since a new value is drawn at each time step, -should be used only with fixed time step integration. -ENDCOMMENT - -NEURON { - POINT_PROCESS WNClamp - NONSPECIFIC_CURRENT i - RANGE mean, stdev - RANGE delay, dur - THREADSAFE : true only if every instance has its own distinct Random - POINTER donotuse -} - -UNITS { - (nA) = (nanoamp) -} - -PARAMETER { - delay (ms) : delay until noise starts - dur (ms) <0, 1e9> : duration of noise - mean = 0 (nA) - stdev = 1 (nA) -} - -ASSIGNED { - dt (ms) - on - per (ms) - ival (nA) - i (nA) - donotuse -} - -INITIAL { - per = dt - on = 0 - ival = 0 - i = 0 - net_send(delay, 1) -} - -PROCEDURE seed(x) { - set_seed(x) -} - -BEFORE BREAKPOINT { - i = -ival -: printf("time %f \ti %f\n", t, ival) -} - -BREAKPOINT { : this block must exist so that a current is actually generated -} - -NET_RECEIVE (w) { - if (dur>0) { - if (flag==1) { - if (on==0) { : turn on - on=1 - net_send(dur,1) : to turn it off -: ival = (hi-lo)*urand() + lo : first sample - ival = stdev*grand() + mean : first sample - net_send(per, 2) : prepare for next sample - } else { - if (on==1) { : turn off - on=0 - ival = 0 - } - } - } - if (flag==2) { - if (on==1) { - ival = stdev*grand() + mean -: printf("time %f \ti %f\n", t, ival) - net_send(per, 2) : prepare for next sample - } - } - } -} - -VERBATIM -double nrn_random_pick(void* r); -void* nrn_random_arg(int argpos); -ENDVERBATIM - -: FUNCTION erand() { -: FUNCTION urand() { -FUNCTION grand() { -VERBATIM - if (_p_donotuse) { - /* - : Supports separate independent but reproducible streams for - : each instance. However, the corresponding hoc Random - : distribution MUST be set to Random.uniform(0,1) - */ -// _lerand = nrn_random_pick(_p_donotuse); -// _lurand = nrn_random_pick(_p_donotuse); - _lgrand = nrn_random_pick(_p_donotuse); - }else{ - /* only can be used in main thread */ - if (_nt != nrn_threads) { -hoc_execerror("multithread random in InUnif"," only via hoc Random"); - } -ENDVERBATIM - : the old standby. Cannot use if reproducible parallel sim - : independent of nhost or which host this instance is on - : is desired, since each instance on this cpu draws from - : the same stream -: erand = exprand(1) -: urand = scop_random() - grand = normrand(0,1) -: printf("%f\n", grand) -VERBATIM - } -ENDVERBATIM -} - -PROCEDURE noiseFromRandom() { -VERBATIM - { - void** pv = (void**)(&_p_donotuse); - if (ifarg(1)) { - *pv = nrn_random_arg(1); - }else{ - *pv = (void*)0; - } - } -ENDVERBATIM -} diff --git a/src/neat/simulations/neuron/neuronmodel.py b/src/neat/simulations/neuron/neuronmodel.py index 9340669f..f43f208e 100755 --- a/src/neat/simulations/neuron/neuronmodel.py +++ b/src/neat/simulations/neuron/neuronmodel.py @@ -613,11 +613,8 @@ def add_ou_clamp(self, loc, tau, mean, stdev, delay, dur, seed=None): seed = np.random.randint(1e16) if seed is None else seed loc = MorphLoc(loc, self) # create the current clamp - if tau > 1e-9: - iclamp = h.OUClamp(self.sections[loc["node"]](loc["x"])) - iclamp.tau = tau - else: - iclamp = h.WNclamp(self.sections[loc["node"]](loc["x"])) + iclamp = h.OUClamp(self.sections[loc["node"]](loc["x"])) + iclamp.tau = tau iclamp.mean = mean # nA iclamp.stdev = stdev # nA iclamp.delay = delay + self.t_calibrate # ms From f5b0490ebbe1940b7613535e9061cf05f002261e Mon Sep 17 00:00:00 2001 From: "w.wybo" Date: Fri, 16 Jan 2026 10:17:09 +0100 Subject: [PATCH 04/18] update nrnivmodl command do include coreneuron flag and adapt OU process mod files --- src/neat/actions/install.py | 2 +- src/neat/channels/ionchannels.py | 2 +- .../neuron/mech_storage/NMDA_Mg_T.mod | 10 +- .../neuron/mech_storage/OUClamp.mod | 204 +++++++++++++--- .../neuron/mech_storage/OUConductance.mod | 179 +++++++++++++- .../neuron/mech_storage/OUReversal.mod | 169 ++++++++++++- .../neuron/mech_storage/VecStim.mod | 226 +++++++++++------- 7 files changed, 647 insertions(+), 145 deletions(-) diff --git a/src/neat/actions/install.py b/src/neat/actions/install.py index 04cfc2ac..8fd08d02 100644 --- a/src/neat/actions/install.py +++ b/src/neat/actions/install.py @@ -192,7 +192,7 @@ def _compile_neuron(model_name, path_neat, channels, path_neuronresource=None): os.chdir(path_for_neuron_compilation) if os.path.exists(f"{platform.machine()}/"): # delete old compiled files if exist shutil.rmtree(f"{platform.machine()}/") - subprocess.call(["nrnivmodl", "mech/"]) # compile all mod files + subprocess.call(["nrnivmodl", "-coreneuron", "mech/"]) # compile all mod files print( f"\n------------------------------\n" diff --git a/src/neat/channels/ionchannels.py b/src/neat/channels/ionchannels.py index a55e9da9..d8a6daf3 100755 --- a/src/neat/channels/ionchannels.py +++ b/src/neat/channels/ionchannels.py @@ -815,7 +815,7 @@ def write_mod_file(self, path, g=0.0, e=None): taustring = "tau_" + ", tau_".join(sv) varstring = "_inf, ".join(sv) + "_inf" - file.write(" GLOBAL %s, %s\n" % (varstring, taustring)) + file.write(" RANGE %s, %s\n" % (varstring, taustring)) file.write(" THREADSAFE" + "\n") file.write("}\n\n") diff --git a/src/neat/simulations/neuron/mech_storage/NMDA_Mg_T.mod b/src/neat/simulations/neuron/mech_storage/NMDA_Mg_T.mod index 1fa9874e..4afe31e6 100755 --- a/src/neat/simulations/neuron/mech_storage/NMDA_Mg_T.mod +++ b/src/neat/simulations/neuron/mech_storage/NMDA_Mg_T.mod @@ -65,11 +65,11 @@ NEURON { POINTER C RANGE U, Cl, D1, D2, O, UMg, ClMg, D1Mg, D2Mg, OMg RANGE g, gmax, rb, rmb, rmu, rbMg,rmc1b,rmc1u,rmc2b,rmc2u, Erev, mg - GLOBAL Rb, Ru, Rd1, Rr1, Rd2, Rr2, Ro, Rc, Rmb, Rmu - GLOBAL RbMg, RuMg, Rd1Mg, Rr1Mg, Rd2Mg, Rr2Mg, RoMg, RcMg - GLOBAL Rmd1b,Rmd1u,Rmd2b,Rmd2u,rmd1b,rmd1u,rmd2b,rmd2u - GLOBAL Rmc1b,Rmc1u,Rmc2b,Rmc2u - GLOBAL vmin, vmax, valence, memb_fraction + RANGE Rb, Ru, Rd1, Rr1, Rd2, Rr2, Ro, Rc, Rmb, Rmu + RANGE RbMg, RuMg, Rd1Mg, Rr1Mg, Rd2Mg, Rr2Mg, RoMg, RcMg + RANGE Rmd1b,Rmd1u,Rmd2b,Rmd2u,rmd1b,rmd1u,rmd2b,rmd2u + RANGE Rmc1b,Rmc1u,Rmc2b,Rmc2u + RANGE vmin, vmax, valence, memb_fraction NONSPECIFIC_CURRENT i } diff --git a/src/neat/simulations/neuron/mech_storage/OUClamp.mod b/src/neat/simulations/neuron/mech_storage/OUClamp.mod index a63f31b2..c0aa5427 100755 --- a/src/neat/simulations/neuron/mech_storage/OUClamp.mod +++ b/src/neat/simulations/neuron/mech_storage/OUClamp.mod @@ -10,16 +10,39 @@ User specifies the time at which the noise starts, and the duration of the noise. Since a new value is drawn at each time step, should be used only with fixed time step integration. + +Random generation code adapted to support CoreNEURON, following +https://github.com/neuronsimulator/testcorenrn/blob/master/mod/Gfluct3.mod ENDCOMMENT + NEURON { + THREADSAFE POINT_PROCESS OUClamp NONSPECIFIC_CURRENT i - RANGE mean, stdev, tau - RANGE dt_usr - RANGE delay, dur, seed_usr + RANGE mean, stdev, tau, dt_usr, delay, dur, seed_usr + THREADSAFE + BBCOREPOINTER donotuse } +VERBATIM +#if NRNBBCORE /* running in CoreNEURON */ + +#define IFNEWSTYLE(arg) arg + +#else /* running in NEURON */ + +/* + 1 means noiseFromRandom was called when _ran_compat was previously 0 . + 2 means noiseFromRandom123 was called when _ran_compat was +previously 0. +*/ +static int _ran_compat; /* specifies the noise style for all instances */ +#define IFNEWSTYLE(arg) if(_ran_compat == 2) { arg } + +#endif /* running in NEURON */ +ENDVERBATIM + UNITS { (nA) = (nanoamp) } @@ -32,7 +55,6 @@ PARAMETER { stdev = 1 (nA) seed_usr = 42 (1) dt_usr = .1 (ms) - noc = 0 } ASSIGNED { @@ -49,33 +71,109 @@ ASSIGNED { } INITIAL { - :VERBATIM - : printf("dt = %.2f\n", dt); - : printf("dt_usr = %.2f\n", dt_usr); - :ENDVERBATIM - :per = dt + + VERBATIM + if (_p_donotuse) { + /* only this style initializes the stream on finitialize */ + IFNEWSTYLE(nrnran123_setseq((nrnran123_State*)_p_donotuse, 0, 0);) + } + ENDVERBATIM + on = 0 ivar = 0 i = 0 flag1 = 0 exp_decay = exp(-dt_usr/tau) : exp(-dt/tau) amp_gauss = stdev * sqrt(1. - exp(-2.*dt_usr/tau)) : stdev * sqrt(1. - exp(-2.*dt/tau)) - :VERBATIM - : printf("std = %.10f\n", amp_gauss); - : printf("std_ = %.10f\n", stdev * sqrt(1. - exp(-2.*.1/tau))); - :ENDVERBATIM - seed(seed_usr) + new_seed(seed_usr) } -PROCEDURE seed(x) { - set_seed(x) +FUNCTION mynormrand(mean, std) { +VERBATIM + if (_p_donotuse) { + // corresponding hoc Random distrubution must be Random.normal(0,1) + double x; +#if !NRNBBCORE + if (_ran_compat == 2) { + x = nrnran123_normal((nrnran123_State*)_p_donotuse); + }else{ + x = nrn_random_pick((Rand*)_p_donotuse); + } +#else + #pragma acc routine(nrnran123_normal) seq + x = nrnran123_normal((nrnran123_State*)_p_donotuse); +#endif + x = _lmean + _lstd*x; + return x; + } +#if !NRNBBCORE +ENDVERBATIM + mynormrand = normrand(mean, std) +VERBATIM +#endif +ENDVERBATIM } -COMMENT -BEFORE BREAKPOINT { - i = -ivar + +PROCEDURE new_seed(seed) { : procedure to set the seed +VERBATIM +#if !NRNBBCORE +ENDVERBATIM + set_seed(seed) + VERBATIM + printf("Setting random generator with seed = %g\n", _lseed); + ENDVERBATIM +VERBATIM +#endif +ENDVERBATIM } -ENDCOMMENT + + +PROCEDURE noiseFromRandom() { +VERBATIM +#if !NRNBBCORE + { + Rand** pv = (Rand**)(&_p_donotuse); + if (_ran_compat == 2) { + fprintf(stderr, "Gfluct3.noiseFromRandom123 was previously called\n"); + assert(0); + } + _ran_compat = 1; + if (ifarg(1)) { + *pv = nrn_random_arg(1); + }else{ + *pv = (Rand*)0; + } + } +#endif +ENDVERBATIM +} + + +PROCEDURE noiseFromRandom123() { +VERBATIM +#if !NRNBBCORE + { + nrnran123_State** pv = (nrnran123_State**)(&_p_donotuse); + if (_ran_compat == 1) { + fprintf(stderr, "Gfluct3.noiseFromRandom was previously called\n"); + assert(0); + } + _ran_compat = 2; + if (*pv) { + nrnran123_deletestream(*pv); + *pv = (nrnran123_State*)0; + } + if (ifarg(3)) { + *pv = nrnran123_newstream3((uint32_t)*getarg(1), (uint32_t)*getarg(2), (uint32_t)*getarg(3)); + }else if (ifarg(2)) { + *pv = nrnran123_newstream((uint32_t)*getarg(1), (uint32_t)*getarg(2)); + } + } +#endif +ENDVERBATIM +} + BREAKPOINT { SOLVE oup @@ -83,15 +181,6 @@ BREAKPOINT { } PROCEDURE oup() { - noc = noc + 1 - :if (t < 2.) { - : VERBATIM - : printf(">>> <<<\n"); - : printf("noc = %.2f\n", noc); - : printf("t = %.2f\n", t); - : printf("dt = %.2f\n", dt); - : ENDVERBATIM - :} if (t < delay) { ivar = 0. } @@ -101,13 +190,62 @@ PROCEDURE oup() { ivar = mean } if (t < delay+dur) { - ivar = mean + exp_decay * (ivar-mean) + amp_gauss * normrand(0,1) - : ivar = ivar + (mean - ivar) * dt / tau + stdev * sqrt(2*dt/tau) * normrand(0,1) - : ivar = stdev*ival + ivar = mean + exp_decay * (ivar-mean) + amp_gauss * mynormrand(0., 1.) } else { ivar = 0. } } - } + + +VERBATIM +static void bbcore_write(double* x, int* d, int* xx, int *offset, _threadargsproto_) { + /* error if using the legacy normrand */ + if (!_p_donotuse) { + fprintf(stderr, "Gfluct3: cannot use the legacy normrand generator for the random stream.\n"); + assert(0); + } + if (d) { + uint32_t* di = ((uint32_t*)d) + *offset; +#if !NRNBBCORE + if (_ran_compat == 1) { + char which; + Rand** pv = (Rand**)(&_p_donotuse); + /* error if not using Random123 generator */ + if (!nrn_random_isran123(*pv, di, di+1, di+2)) { + fprintf(stderr, "Gfluct3: Random123 generator is required\n"); + assert(0); + } + /* because coreneuron psolve may not start at t=0 also need the sequence */ + nrn_random123_getseq(*pv, di+3, &which); + di[4] = (int)which; + }else{ +#else + { +#endif + char which; + nrnran123_State** pv = (nrnran123_State**)(&_p_donotuse); + nrnran123_getids3(*pv, di, di+1, di+2); + nrnran123_getseq(*pv, di+3, &which); + di[4] = (int)which; + } + /*printf("Gfluct3 bbcore_write %d %d %d %d %d\n", di[0], di[1], di[2], di[3], di[4]);*/ + } + *offset += 5; +} + +static void bbcore_read(double* x, int* d, int* xx, int* offset, _threadargsproto_) { + uint32_t* di = ((uint32_t*)d) + *offset; + nrnran123_State** pv = (nrnran123_State**)(&_p_donotuse); +#if !NRNBBCORE + assert(_ran_compat == 2); +#endif + if (pv) { + nrnran123_deletestream(*pv); + } + *pv = nrnran123_newstream3(di[0], di[1], di[2]); + nrnran123_setseq(*pv, di[3], (char)di[4]); + *offset += 5; +} +ENDVERBATIM diff --git a/src/neat/simulations/neuron/mech_storage/OUConductance.mod b/src/neat/simulations/neuron/mech_storage/OUConductance.mod index 23c991fb..599c0353 100755 --- a/src/neat/simulations/neuron/mech_storage/OUConductance.mod +++ b/src/neat/simulations/neuron/mech_storage/OUConductance.mod @@ -10,6 +10,9 @@ User specifies the time at which the noise starts, and the duration of the noise. Since a new value is drawn at each time step, should be used only with fixed time step integration. + +Random generation code adapted to support CoreNEURON, following +https://github.com/neuronsimulator/testcorenrn/blob/master/mod/Gfluct3.mod ENDCOMMENT NEURON { @@ -19,8 +22,29 @@ NEURON { RANGE e RANGE dt_usr RANGE delay, dur, seed_usr + THREADSAFE + BBCOREPOINTER donotuse } +VERBATIM +#if NRNBBCORE /* running in CoreNEURON */ + +#define IFNEWSTYLE(arg) arg + +#else /* running in NEURON */ + +/* + 1 means noiseFromRandom was called when _ran_compat was previously 0 . + 2 means noiseFromRandom123 was called when _ran_compat was +previously 0. +*/ +static int _ran_compat; /* specifies the noise style for all instances */ +#define IFNEWSTYLE(arg) if(_ran_compat == 2) { arg } + +#endif /* running in NEURON */ +ENDVERBATIM + + UNITS { (nA) = (nanoamp) (mV) = (millivolt) @@ -36,7 +60,6 @@ PARAMETER { stdev = 1 (uS) seed_usr = 42 (1) dt_usr = .1 (ms) - noc = 0 } ASSIGNED { @@ -54,24 +77,108 @@ ASSIGNED { } INITIAL { + + VERBATIM + if (_p_donotuse) { + /* only this style initializes the stream on finitialize */ + IFNEWSTYLE(nrnran123_setseq((nrnran123_State*)_p_donotuse, 0, 0);) + } + ENDVERBATIM + on = 0 gvar = 0 i = 0 flag1 = 0 exp_decay = exp(-dt_usr/tau) : exp(-dt/tau) amp_gauss = stdev * sqrt(1. - exp(-2.*dt_usr/tau)) : stdev * sqrt(1. - exp(-2.*dt/tau)) - seed(seed_usr) + new_seed(seed_usr) } +FUNCTION mynormrand(mean, std) { +VERBATIM + if (_p_donotuse) { + // corresponding hoc Random distrubution must be Random.normal(0,1) + double x; +#if !NRNBBCORE + if (_ran_compat == 2) { + x = nrnran123_normal((nrnran123_State*)_p_donotuse); + }else{ + x = nrn_random_pick((Rand*)_p_donotuse); + } +#else + #pragma acc routine(nrnran123_normal) seq + x = nrnran123_normal((nrnran123_State*)_p_donotuse); +#endif + x = _lmean + _lstd*x; + return x; + } +#if !NRNBBCORE +ENDVERBATIM + mynormrand = normrand(mean, std) +VERBATIM +#endif +ENDVERBATIM +} + -PROCEDURE seed(x) { - set_seed(x) +PROCEDURE new_seed(seed) { : procedure to set the seed +VERBATIM +#if !NRNBBCORE +ENDVERBATIM + set_seed(seed) + VERBATIM + printf("Setting random generator with seed = %g\n", _lseed); + ENDVERBATIM +VERBATIM +#endif +ENDVERBATIM } -COMMENT -BEFORE BREAKPOINT { - i = gvar * (v - e) + +PROCEDURE noiseFromRandom() { +VERBATIM +#if !NRNBBCORE + { + Rand** pv = (Rand**)(&_p_donotuse); + if (_ran_compat == 2) { + fprintf(stderr, "Gfluct3.noiseFromRandom123 was previously called\n"); + assert(0); + } + _ran_compat = 1; + if (ifarg(1)) { + *pv = nrn_random_arg(1); + }else{ + *pv = (Rand*)0; + } + } +#endif +ENDVERBATIM } -ENDCOMMENT + + +PROCEDURE noiseFromRandom123() { +VERBATIM +#if !NRNBBCORE + { + nrnran123_State** pv = (nrnran123_State**)(&_p_donotuse); + if (_ran_compat == 1) { + fprintf(stderr, "Gfluct3.noiseFromRandom was previously called\n"); + assert(0); + } + _ran_compat = 2; + if (*pv) { + nrnran123_deletestream(*pv); + *pv = (nrnran123_State*)0; + } + if (ifarg(3)) { + *pv = nrnran123_newstream3((uint32_t)*getarg(1), (uint32_t)*getarg(2), (uint32_t)*getarg(3)); + }else if (ifarg(2)) { + *pv = nrnran123_newstream((uint32_t)*getarg(1), (uint32_t)*getarg(2)); + } + } +#endif +ENDVERBATIM +} + BREAKPOINT { SOLVE oup @@ -88,9 +195,7 @@ PROCEDURE oup() { gvar = mean } if (t < delay+dur) { - gvar = mean + exp_decay * (gvar-mean) + amp_gauss * normrand(0,1) - : gvar = gvar + (mean - gvar) * dt / tau + stdev * sqrt(2*dt/tau) * normrand(0,1) - : gvar = stdev*gval + gvar = mean + exp_decay * (gvar-mean) + amp_gauss * mynormrand(0,1) } else { gvar = 0. @@ -98,3 +203,55 @@ PROCEDURE oup() { } } + + +VERBATIM +static void bbcore_write(double* x, int* d, int* xx, int *offset, _threadargsproto_) { + /* error if using the legacy normrand */ + if (!_p_donotuse) { + fprintf(stderr, "Gfluct3: cannot use the legacy normrand generator for the random stream.\n"); + assert(0); + } + if (d) { + uint32_t* di = ((uint32_t*)d) + *offset; +#if !NRNBBCORE + if (_ran_compat == 1) { + char which; + Rand** pv = (Rand**)(&_p_donotuse); + /* error if not using Random123 generator */ + if (!nrn_random_isran123(*pv, di, di+1, di+2)) { + fprintf(stderr, "Gfluct3: Random123 generator is required\n"); + assert(0); + } + /* because coreneuron psolve may not start at t=0 also need the sequence */ + nrn_random123_getseq(*pv, di+3, &which); + di[4] = (int)which; + }else{ +#else + { +#endif + char which; + nrnran123_State** pv = (nrnran123_State**)(&_p_donotuse); + nrnran123_getids3(*pv, di, di+1, di+2); + nrnran123_getseq(*pv, di+3, &which); + di[4] = (int)which; + } + /*printf("Gfluct3 bbcore_write %d %d %d %d %d\n", di[0], di[1], di[2], di[3], di[4]);*/ + } + *offset += 5; +} + +static void bbcore_read(double* x, int* d, int* xx, int* offset, _threadargsproto_) { + uint32_t* di = ((uint32_t*)d) + *offset; + nrnran123_State** pv = (nrnran123_State**)(&_p_donotuse); +#if !NRNBBCORE + assert(_ran_compat == 2); +#endif + if (pv) { + nrnran123_deletestream(*pv); + } + *pv = nrnran123_newstream3(di[0], di[1], di[2]); + nrnran123_setseq(*pv, di[3], (char)di[4]); + *offset += 5; +} +ENDVERBATIM diff --git a/src/neat/simulations/neuron/mech_storage/OUReversal.mod b/src/neat/simulations/neuron/mech_storage/OUReversal.mod index c2aac0fb..9c257ef3 100755 --- a/src/neat/simulations/neuron/mech_storage/OUReversal.mod +++ b/src/neat/simulations/neuron/mech_storage/OUReversal.mod @@ -10,6 +10,9 @@ User specifies the time at which the noise starts, and the duration of the noise. Since a new value is drawn at each time step, should be used only with fixed time step integration. + +Random generation code adapted to support CoreNEURON, following +https://github.com/neuronsimulator/testcorenrn/blob/master/mod/Gfluct3.mod ENDCOMMENT NEURON { @@ -19,8 +22,28 @@ NEURON { RANGE g RANGE dt_usr RANGE delay, dur, seed_usr + THREADSAFE + BBCOREPOINTER donotuse } +VERBATIM +#if NRNBBCORE /* running in CoreNEURON */ + +#define IFNEWSTYLE(arg) arg + +#else /* running in NEURON */ + +/* + 1 means noiseFromRandom was called when _ran_compat was previously 0 . + 2 means noiseFromRandom123 was called when _ran_compat was +previously 0. +*/ +static int _ran_compat; /* specifies the noise style for all instances */ +#define IFNEWSTYLE(arg) if(_ran_compat == 2) { arg } + +#endif /* running in NEURON */ +ENDVERBATIM + UNITS { (nA) = (nanoamp) (mV) = (millivolt) @@ -36,7 +59,6 @@ PARAMETER { stdev = 1 (mV) seed_usr = 42 (1) dt_usr = .1 (ms) - noc = 0 } ASSIGNED { @@ -61,18 +83,94 @@ INITIAL { flag1 = 0 exp_decay = exp(-dt_usr/tau) amp_gauss = stdev * sqrt(1. - exp(-2.*dt_usr/tau)) - seed(seed_usr) + new_seed(seed_usr) } -PROCEDURE seed(x) { - set_seed(x) +FUNCTION mynormrand(mean, std) { +VERBATIM + if (_p_donotuse) { + // corresponding hoc Random distrubution must be Random.normal(0,1) + double x; +#if !NRNBBCORE + if (_ran_compat == 2) { + x = nrnran123_normal((nrnran123_State*)_p_donotuse); + }else{ + x = nrn_random_pick((Rand*)_p_donotuse); + } +#else + #pragma acc routine(nrnran123_normal) seq + x = nrnran123_normal((nrnran123_State*)_p_donotuse); +#endif + x = _lmean + _lstd*x; + return x; + } +#if !NRNBBCORE +ENDVERBATIM + mynormrand = normrand(mean, std) +VERBATIM +#endif +ENDVERBATIM } -COMMENT -BEFORE BREAKPOINT { - i = gvar * (v - evar) + +PROCEDURE new_seed(seed) { : procedure to set the seed +VERBATIM +#if !NRNBBCORE +ENDVERBATIM + set_seed(seed) + VERBATIM + printf("Setting random generator with seed = %g\n", _lseed); + ENDVERBATIM +VERBATIM +#endif +ENDVERBATIM +} + + +PROCEDURE noiseFromRandom() { +VERBATIM +#if !NRNBBCORE + { + Rand** pv = (Rand**)(&_p_donotuse); + if (_ran_compat == 2) { + fprintf(stderr, "Gfluct3.noiseFromRandom123 was previously called\n"); + assert(0); + } + _ran_compat = 1; + if (ifarg(1)) { + *pv = nrn_random_arg(1); + }else{ + *pv = (Rand*)0; + } + } +#endif +ENDVERBATIM +} + + +PROCEDURE noiseFromRandom123() { +VERBATIM +#if !NRNBBCORE + { + nrnran123_State** pv = (nrnran123_State**)(&_p_donotuse); + if (_ran_compat == 1) { + fprintf(stderr, "Gfluct3.noiseFromRandom was previously called\n"); + assert(0); + } + _ran_compat = 2; + if (*pv) { + nrnran123_deletestream(*pv); + *pv = (nrnran123_State*)0; + } + if (ifarg(3)) { + *pv = nrnran123_newstream3((uint32_t)*getarg(1), (uint32_t)*getarg(2), (uint32_t)*getarg(3)); + }else if (ifarg(2)) { + *pv = nrnran123_newstream((uint32_t)*getarg(1), (uint32_t)*getarg(2)); + } + } +#endif +ENDVERBATIM } -ENDCOMMENT BREAKPOINT { SOLVE oup @@ -91,7 +189,7 @@ PROCEDURE oup() { evar = mean } if (t < delay+dur) { - evar = mean + exp_decay * (evar-mean) + amp_gauss * normrand(0,1) + evar = mean + exp_decay * (evar-mean) + amp_gauss * mynormrand(0,1) } else { gvar = 0. @@ -100,3 +198,56 @@ PROCEDURE oup() { } } + + +VERBATIM +static void bbcore_write(double* x, int* d, int* xx, int *offset, _threadargsproto_) { + /* error if using the legacy normrand */ + if (!_p_donotuse) { + fprintf(stderr, "Gfluct3: cannot use the legacy normrand generator for the random stream.\n"); + assert(0); + } + if (d) { + uint32_t* di = ((uint32_t*)d) + *offset; +#if !NRNBBCORE + if (_ran_compat == 1) { + char which; + Rand** pv = (Rand**)(&_p_donotuse); + /* error if not using Random123 generator */ + if (!nrn_random_isran123(*pv, di, di+1, di+2)) { + fprintf(stderr, "Gfluct3: Random123 generator is required\n"); + assert(0); + } + /* because coreneuron psolve may not start at t=0 also need the sequence */ + nrn_random123_getseq(*pv, di+3, &which); + di[4] = (int)which; + }else{ +#else + { +#endif + char which; + nrnran123_State** pv = (nrnran123_State**)(&_p_donotuse); + nrnran123_getids3(*pv, di, di+1, di+2); + nrnran123_getseq(*pv, di+3, &which); + di[4] = (int)which; + } + /*printf("Gfluct3 bbcore_write %d %d %d %d %d\n", di[0], di[1], di[2], di[3], di[4]);*/ + } + *offset += 5; +} + +static void bbcore_read(double* x, int* d, int* xx, int* offset, _threadargsproto_) { + uint32_t* di = ((uint32_t*)d) + *offset; + nrnran123_State** pv = (nrnran123_State**)(&_p_donotuse); +#if !NRNBBCORE + assert(_ran_compat == 2); +#endif + if (pv) { + nrnran123_deletestream(*pv); + } + *pv = nrnran123_newstream3(di[0], di[1], di[2]); + nrnran123_setseq(*pv, di[3], (char)di[4]); + *offset += 5; +} +ENDVERBATIM + diff --git a/src/neat/simulations/neuron/mech_storage/VecStim.mod b/src/neat/simulations/neuron/mech_storage/VecStim.mod index ab9c1545..1a4cc78d 100755 --- a/src/neat/simulations/neuron/mech_storage/VecStim.mod +++ b/src/neat/simulations/neuron/mech_storage/VecStim.mod @@ -1,111 +1,167 @@ +: Vector stream of events + COMMENT -/** - * @file VecStim.mod - * @brief - * @author king - * @date 2011-03-16 - * @remark Copyright Š BBP/EPFL 2005-2011; All rights reserved. Do not distribute without further notice. - */ -ENDCOMMENT +A VecStim is an artificial spiking cell that generates +events at times that are specified in a Vector. +HOC Example: -: Vector stream of events -NEURON { - ARTIFICIAL_CELL VecStim - RANGE ping -} +// assumes spt is a Vector whose elements are all > 0 +// and are sorted in monotonically increasing order +objref vs +vs = new VecStim() +vs.play(spt) +// now launch a simulation, and vs will produce spike events +// at the times contained in spt + +Python Example: + +from neuron import h +spt = h.Vector(10).indgen(1, 0.2) +vs = h.VecStim() +vs.play(spt) + +def pr(): + print (h.t) + +nc = h.NetCon(vs, None) +nc.record(pr) -PARAMETER { - ping = 1 (ms) +cvode = h.CVode() +h.finitialize() +cvode.solve(20) + +ENDCOMMENT + +NEURON { + THREADSAFE + ARTIFICIAL_CELL VecStim + BBCOREPOINTER ptr } ASSIGNED { - index - etime (ms) - space + index + etime (ms) + ptr } + INITIAL { - index = 0 - element() - if (index > 0) { - net_send(etime - t, 1) - } - if (ping > 0) { - net_send(ping, 2) - } + index = 0 + element() + if (index > 0) { + net_send(etime - t, 1) + } } NET_RECEIVE (w) { - if (flag == 1) { - net_event(t) - element() - if (index > 0) { - if (etime < t) { - printf("Warning in VecStim: spike time (%g ms) before current time (%g ms)\n",etime,t) - } else { - net_send(etime - t, 1) - } - } - } else if (flag == 2) { : ping - reset index to 0 - :printf("flag=2, etime=%g, t=%g, ping=%g, index=%g\n",etime,t,ping,index) - if (index == -2) { : play() has been called - printf("Detected new vector\n") - index = 0 - : the following loop ensures that if the vector - : contains spiketimes earlier than the current - : time, they are ignored. - while (etime < t && index >= 0) { - element() - :printf("element(): index=%g, etime=%g, t=%g\n",index,etime,t) - } - if (index > 0) { - net_send(etime - t, 1) - } - } - net_send(ping, 2) - } + if (flag == 1) { + net_event(t) + element() + if (index > 0) { + net_send(etime - t, 1) + } + } } - +DESTRUCTOR { VERBATIM -extern double* vector_vec(); -extern int vector_capacity(); -extern void* vector_arg(); -ENDVERBATIM +#if !NRNBBCORE + IvocVect* vv = (IvocVect*)(_p_ptr); + if (vv) { + hoc_obj_unref(*vector_pobj(vv)); + } +#endif +ENDVERBATIM +} PROCEDURE element() { -VERBATIM - { void* vv; int i, size; double* px; - i = (int)index; - if (i >= 0) { - vv = *((void**)(&space)); - if (vv) { - size = vector_capacity(vv); - px = vector_vec(vv); - if (i < size) { - etime = px[i]; - index += 1.; - } else { - index = -1.; - } - } else { - index = -1.; - } - } - } +VERBATIM + { IvocVect* vv; int i, size; double* px; + i = (int)index; + if (i >= 0) { + vv = (IvocVect*)(_p_ptr); + if (vv) { + size = vector_capacity(vv); + px = vector_vec(vv); + if (i < size) { + etime = px[i]; + index += 1.; + }else{ + index = -1.; + } + }else{ + index = -1.; + } + } + } ENDVERBATIM } PROCEDURE play() { VERBATIM - void** vv; - vv = (void**)(&space); - *vv = (void*)0; - if (ifarg(1)) { - *vv = vector_arg(1); - } - index = -2; +#if !NRNBBCORE + { + IvocVect** pv; + IvocVect* ptmp = NULL; + if (ifarg(1)) { + ptmp = vector_arg(1); + hoc_obj_ref(*vector_pobj(ptmp)); + } + pv = (IvocVect**)(&_p_ptr); + if (*pv) { + hoc_obj_unref(*vector_pobj(*pv)); + } + *pv = ptmp; + } +#endif ENDVERBATIM } +VERBATIM +static void bbcore_write(double* xarray, int* iarray, int* xoffset, int* ioffset, _threadargsproto_) { + int i, dsize, *ia; + double *xa, *dv; + dsize = 0; + if (_p_ptr) { + dsize = vector_capacity((IvocVect*)_p_ptr); + } + if (iarray) { + IvocVect* vec = (IvocVect*)_p_ptr; + ia = iarray + *ioffset; + xa = xarray + *xoffset; + ia[0] = dsize; + if (dsize) { + dv = vector_vec(vec); + for (i = 0; i < dsize; ++i) { + xa[i] = dv[i]; + } + } + } + *ioffset += 1; + *xoffset += dsize; +} + +static void bbcore_read(double* xarray, int* iarray, int* xoffset, int* ioffset, _threadargsproto_) { + int dsize, i, *ia; + double *xa, *dv; + xa = xarray + *xoffset; + ia = iarray + *ioffset; + dsize = ia[0]; + + IvocVect* pv = (IvocVect*)_p_ptr; + if(!pv) { + pv = vector_new1(dsize); + } + assert(dsize == vector_capacity(pv)); + _p_ptr = (double*)pv; + + dv = vector_vec(pv); + for (i = 0; i < dsize; ++i) { + dv[i] = xa[i]; + } + *xoffset += dsize; + *ioffset += 1; +} + +ENDVERBATIM \ No newline at end of file From aa83c026e699f6853e807439932af938d4b6f576 Mon Sep 17 00:00:00 2001 From: "w.wybo" Date: Fri, 16 Jan 2026 22:11:20 +0100 Subject: [PATCH 05/18] update modfiles of OU mechanmism for coreneuron compatibility and add test of OUclamp --- src/neat/actions/install.py | 27 ++- src/neat/simulations/neuron/neuronmodel.py | 3 + tests/test_neurontree.py | 222 ++++++++++++++++++++- 3 files changed, 231 insertions(+), 21 deletions(-) diff --git a/src/neat/actions/install.py b/src/neat/actions/install.py index 8fd08d02..0936cbbf 100644 --- a/src/neat/actions/install.py +++ b/src/neat/actions/install.py @@ -164,18 +164,17 @@ def _compile_neuron(model_name, path_neat, channels, path_neuronresource=None): path_for_neuron_compilation = os.path.join( path_neat, "simulations/neuron/tmp/", model_name ) + # delete old compiled files if exist + if os.path.exists(path_for_neuron_compilation): + shutil.rmtree(path_for_neuron_compilation) path_for_mod_files = os.path.join(path_for_neuron_compilation, "mech/") print(f"--- writing channels to \n" f" > {path_for_mod_files}") # Create the "mech/" directory in a clean state - if os.path.exists(path_for_mod_files): - shutil.rmtree(path_for_mod_files) os.makedirs(path_for_mod_files) - # copy default mechanisms - # if path_neuronresource is not None: - # shutil.copytree(path_neuronresource, path_for_mod_files) + # copy mechanisms from resource path if path_neuronresource is not None: for mod_file in glob.glob(os.path.join(path_neuronresource, "*.mod")): shutil.copy2(mod_file, path_for_mod_files) @@ -184,14 +183,22 @@ def _compile_neuron(model_name, path_neat, channels, path_neuronresource=None): print(" - writing .mod file for:", chan.__class__.__name__) chan.write_mod_file(path_for_mod_files) - # # copy possible mod-files within the source directory to the compile directory - # for mod_file in glob.glob(os.path.join(path_for_channels, '*.mod')): - # shutil.copy2(mod_file, path_for_mod_files) # change to directory where 'mech/' folder is located and compile the mechanisms os.chdir(path_for_neuron_compilation) - if os.path.exists(f"{platform.machine()}/"): # delete old compiled files if exist - shutil.rmtree(f"{platform.machine()}/") + # with open(".noindex", "w") as f: # prevent mac from indexing compiled files + # pass + # subprocess.run(["mdutil", "-i", "off", path_for_neuron_compilation]) + # if os.path.exists(f"{platform.machine()}/"): # delete old compiled files if exist + # shutil.rmtree(f"{platform.machine()}/") + print("!!!", os.getcwd()) + # my_env = os.environ.copy() + # # This forces every sub-call to 'make' to append -j1, effectively + # # overriding the -j4 passed by the nrnivmodl wrapper. + # my_env["MAKE"] = "make -j1" + + # # Also keep these for good measure + # my_env["MAKEFLAGS"] = "-j1" subprocess.call(["nrnivmodl", "-coreneuron", "mech/"]) # compile all mod files print( diff --git a/src/neat/simulations/neuron/neuronmodel.py b/src/neat/simulations/neuron/neuronmodel.py index f43f208e..a4ae322a 100755 --- a/src/neat/simulations/neuron/neuronmodel.py +++ b/src/neat/simulations/neuron/neuronmodel.py @@ -109,12 +109,15 @@ def print_err(): else: print_err() else: + print(f"Loading NEURON model '{name}'") path = os.path.join( os.path.dirname(__file__), f"tmp/{name}", ) if os.path.exists(path): + print(f"Found path: {path}, loading mechanisms...") neuron.load_mechanisms(path) + print(f"... done.") else: print_err() diff --git a/tests/test_neurontree.py b/tests/test_neurontree.py index 07add106..81493f62 100755 --- a/tests/test_neurontree.py +++ b/tests/test_neurontree.py @@ -835,15 +835,215 @@ def test_impedance_properties_2(self): assert np.allclose(z_mat_sim, z_mat_comp) + +class TestStimuli: + def _create_ball(self): + """ + Load the T-tree morphology in memory with passive conductance + + 6--5--4--7--8 + | + | + 1 + """ + self.v_eq = -75.0 + self.dt = 0.1 + self.tmax = 10000.0 + self.t_calibrate = 50. + # load the morphology + fname = os.path.join(MORPHOLOGIES_PATH_PREFIX, "ball.swc") + self.tree = NeuronSimTree(fname, types=[1, 3, 4]) + self.tree.fit_leak_current(self.v_eq, 10.0) + self.tree.set_comp_tree() + + def _voltage_statistics(self, delta_t, mu_ou, sigma_ou, tau_ou): + """ + Stationary autocovariance C_v(Δ). + + Parameters + ---------- + delta_t : array_like + Time lag(s) Δ + sigma_ou : float + OU standard deviation parameter [nA] + tau_ou : float + OU timescale [ms] + mu_ou : float + OU mean [nA] + """ + mu_ou *= 1e-3 # nA to uA + sigma_ou *= 1e-3 # nA to uA + + soma = self.tree[1] + tau = soma.c_m / soma.currents['L'][0] * 1e3 # s to ms + e_l = soma.currents['L'][1] + ca = soma.c_m * 4 * np.pi * soma.R**2 * 1e-8 # um^2 to cm^2 + + # voltage mean + mean = e_l + tau * mu_ou / ca # ms * V / s = mV + + delta_t = np.abs(np.asarray(delta_t)) + + prefactor = sigma_ou**2 / ca**2 # (uA / uF)^2 = (V / s)^2 + scale = (tau**2 * tau_ou) / (tau_ou**2 - tau**2) + + # autocovarianvce + autocov = prefactor * scale * ( + tau_ou * np.exp(-delta_t / tau_ou) + - tau * np.exp(-delta_t / tau) + ) # (V / s)^2 * ms^2 = mV^2 + + return mean, autocov + + def _autocovariance(self, x, max_lag=None): + """ + Empirical autocovariance of a 1D time series. + + Parameters + ---------- + x : array_like + Time series data (1D). + max_lag : int or None + Maximum lag (in samples). If None, uses N-1. + + Returns + ------- + lags : ndarray + Array of integer lags. + cov : ndarray + Autocovariance at each lag. + """ + x = np.asarray(x) + x = x - np.mean(x) + + N = x.size + if max_lag is None: + max_lag = N - 1 + + cov = np.empty(max_lag + 1) + for k in range(max_lag + 1): + cov[k] = np.dot(x[:N-k], x[k:]) / (N - k) + + lags = np.arange(max_lag + 1) + return lags, cov + + def _autocovariance_fft(self, x, max_lag=None): + """ + Empirical autocovariance using FFT. + + Parameters + ---------- + x : array_like + Time series data (1D). + max_lag : int or None + Maximum lag (in samples). If None, uses N-1. + + Returns + ------- + lags : ndarray + Array of integer lags. + cov : ndarray + Autocovariance at each lag. + """ + x = np.asarray(x) + x = x - np.mean(x) + + N = x.size + if max_lag is None: + max_lag = N - 1 + + # Zero-pad to avoid circular convolution + nfft = 2 * N + fx = np.fft.fft(x, n=nfft) + acf = np.fft.ifft(fx * np.conjugate(fx)).real + + acf = acf[:max_lag + 1] + normalization = N - np.arange(max_lag + 1) + + return np.arange(max_lag + 1), acf / normalization + + def test_ou_processes(self, pplot=False): + tau_ou = 5.0 + sigma_ou = 0.005 + mu_ou = 0.05 + + self._create_ball() + self.tree.init_model(t_calibrate=self.t_calibrate, dt=self.dt) + # add OU process + self.tree.add_ou_clamp( + loc=(1, 0.5), + mean=mu_ou, + stdev=sigma_ou, + tau=tau_ou, + delay=-self.t_calibrate / 2., + dur=self.tmax + self.t_calibrate / 2., + seed=46, + ) + + res = self.tree.run(self.tmax, record_from_iclamps=True) + + mu_ou_empirical = np.mean(res['i_clamp'][0,:]) + cov_ou_empirical = np.cov(res['i_clamp'][0,:]) + + print(f"OU mean: exact={mu_ou:.10f} nA, empirical={mu_ou_empirical:.10f} nA") + print(f"OU variance: exact={sigma_ou**2:.10f} nA^2, empirical={cov_ou_empirical:.10f} nA^2") + + # check current statistics + assert np.abs(mu_ou_empirical - mu_ou) / np.abs(mu_ou) < 0.05 + assert np.abs(cov_ou_empirical - sigma_ou**2) / sigma_ou**2 < 0.05 + + mu_exact, cov_exact = self._voltage_statistics( + delta_t=res['t'], + mu_ou=mu_ou, + sigma_ou=sigma_ou, + tau_ou=tau_ou, + ) + mu_empirical = np.mean(res['v_m'][0, :]) + cov_np = np.cov(res['v_m'][0, :]) + _, cov_empirical = self._autocovariance_fft(res['v_m'][0, :]) + + print(f"Voltage mean: exact={mu_exact:.10f} mV, empirical={mu_empirical:.10f} mV") + print(f"Voltage variance: exact={cov_exact[0]:.10f} mV^2, empirical={cov_np:.10f} mV^2") + + # check voltage statistics + assert np.abs(mu_empirical - mu_exact) / np.abs(mu_exact) < 0.05 + assert np.abs(cov_np - cov_exact[0]) / cov_exact[0] < 0.1 + assert np.mean(np.abs(cov_empirical[:500] - cov_exact[:500]) / cov_exact[0]) < 0.05 + + if pplot: + pl.figure(figsize=(12, 5)) + ax = pl.subplot(1, 3, 1) + ax.plot(res['t'], res['v_m'][0, :]) + ax.axhline(mu_exact) + ax.axhline(mu_empirical, color='orange') + ax = pl.subplot(1, 3, 2) + ax.plot(res['t'][:500], cov_exact[:500], label='Exact') + ax.plot(res['t'][:500], cov_empirical[:500], label='Empirical') + ax.axhline(cov_np, color='gray', linestyle='--', label='Empirical variance') + ax.legend(loc=0) + pl.show() + + +def debug_print(pstr): + + print(pstr) + try: + print(os.listdir(os.path.join(neat.__path__[0], "simulations/neuron/tmp/multichannel_test/arm64"))) + except FileNotFoundError as e: + print(e) + if __name__ == "__main__": - tn = TestNeuron() - tn.test_passive(pplot=True) - tn.test_active(pplot=True) - tn.test_channel_recording() - tn.test_recording_timestep() - - trn = TestReducedNeuron() - trn.test_geometry1() - trn.test_impedance_properties_1() - trn.test_geometry2() - trn.test_impedance_properties_2() + # tn = TestNeuron() + # tn.test_passive(pplot=True) + # tn.test_active(pplot=True) + # tn.test_channel_recording() + # tn.test_recording_timestep() + + # trn = TestReducedNeuron() + # trn.test_geometry1() + # trn.test_impedance_properties_1() + # trn.test_geometry2() + # trn.test_impedance_properties_2() + + ts = TestStimuli() + ts.test_ou_processes() \ No newline at end of file From 48f9d713af3939f014e2863f07996d3b3a96131b Mon Sep 17 00:00:00 2001 From: "w.wybo" Date: Fri, 16 Jan 2026 22:59:21 +0100 Subject: [PATCH 06/18] enable running with the coreneuron backend --- src/neat/simulations/neuron/neuronmodel.py | 83 ++++++++++++++++------ tests/test_neurontree.py | 46 ++++++++++-- 2 files changed, 102 insertions(+), 27 deletions(-) diff --git a/src/neat/simulations/neuron/neuronmodel.py b/src/neat/simulations/neuron/neuronmodel.py index a4ae322a..731e0ee1 100755 --- a/src/neat/simulations/neuron/neuronmodel.py +++ b/src/neat/simulations/neuron/neuronmodel.py @@ -34,6 +34,7 @@ try: import neuron + from neuron import coreneuron from neuron import h h.load_file("stdlib.hoc") # contains the lambda rule @@ -754,6 +755,7 @@ def run( record_currents=[], spike_rec_loc=None, spike_rec_thr=-20.0, + use_coreneuron=False, pprint=False, ): """ @@ -768,7 +770,9 @@ def run( Records the state of the model every `downsample` time-steps dt_rec: float or None recording time step (if `None` is given, defaults to the simulation - time-step) + time-step). Note that if `use_coreneuron` is set to ``True``, this parameter + is ignored and recording is done at every simulation time-step and + downsampled according to the `downsample` argument. record_from_syns: bool (default ``False``) Record currents of synapstic point processes (in `self.syns`). Accessible as `np.ndarray` in the output dict under key 'i_syn' @@ -797,6 +801,10 @@ def run( Record the output spike times from this location spike_rec_thr: float Spike threshold + use_coreneuron: bool (default ``False``) + Whether or not to use CoreNEURON to run the simulation + pprint: bool (default ``False``) + Whether or not to print info on the NEURON simulation Returns ------- @@ -812,36 +820,56 @@ def run( dt_rec = self.dt indstart = int(self.t_calibrate / dt_rec) + def _rec_coreneuron_compatible(vec, var, dt_rec): + """ Helper function to make recording compatible with CoreNEURON + We're not allowed to use the dt argument in vec.record when using + CoreNEURON, so we manually downsample after the simulation + """ + if use_coreneuron: + vec.record(var) + else: + vec.record(var, dt_rec) + # simulation time recorder res = {"t": h.Vector()} - res["t"].record(h._ref_t, dt_rec) + _rec_coreneuron_compatible( + res["t"], h._ref_t, dt_rec + ) # voltage recorders res["v_m"] = [] for loc in self.get_locs("rec locs"): res["v_m"].append(h.Vector()) - res["v_m"][-1].record(self.sections[loc["node"]](loc["x"])._ref_v, dt_rec) + _rec_coreneuron_compatible( + res["v_m"][-1], self.sections[loc["node"]](loc["x"])._ref_v, dt_rec + ) # synapse current recorders if record_from_syns: res["i_syn"] = [] for syn in self.syns: res["i_syn"].append(h.Vector()) - res["i_syn"][-1].record(syn._ref_i, dt_rec) + _rec_coreneuron_compatible( + res["i_syn"][-1], syn._ref_i, dt_rec + ) # current clamp current recorders if record_from_iclamps: res["i_clamp"] = [] for iclamp in self.iclamps: res["i_clamp"].append(h.Vector()) - res["i_clamp"][-1].record(iclamp._ref_i, dt_rec) + _rec_coreneuron_compatible( + res["i_clamp"][-1], iclamp._ref_i, dt_rec + ) # voltage clamp current recorders if record_from_vclamps: res["i_vclamp"] = [] for vclamp in self.vclamps: res["i_vclamp"].append(h.Vector()) - res["i_vclamp"][-1].record(vclamp._ref_i, dt_rec) + _rec_coreneuron_compatible( + res["i_vclamp"][-1], vclamp._ref_i, dt_rec + ) # channel state variable recordings if record_from_channels: @@ -865,12 +893,12 @@ def run( # create the recorder try: rec_vec = h.Vector() - exec( - "rec_vec.record(self.sections[loc[0]](xx)." - + mechname[channel_name] - + "._ref_" - + str(var) - + f", {dt_rec})" + _rec_coreneuron_compatible( + rec_vec, + exec( + f"self.sections[loc['node']](xx).{mechname[channel_name]}._ref_{var}" + ), + dt_rec, ) except AttributeError: # the channel does not exist here @@ -884,10 +912,12 @@ def run( for loc in self.get_locs("rec locs"): try: rec_vec = h.Vector() - exec( - "rec_vec.record(self.sections[loc['node']](loc['x'])._ref_" - + c_ion - + f"i, {dt_rec})" + _rec_coreneuron_compatible( + rec_vec, + exec( + f"self.sections[loc['node']](loc['x'])._ref_{c_ion}i" + ), + dt_rec, ) except AttributeError: rec_vec = None @@ -901,8 +931,12 @@ def run( for loc in self.get_locs("rec locs"): try: rec_vec = h.Vector() - exec( - f"rec_vec.record(self.sections[loc['node']](loc['x'])._ref_{curr_name}, {dt_rec})" + _rec_coreneuron_compatible( + rec_vec, + exec( + f"self.sections[loc['node']](loc['x'])._ref_{curr_name}" + ), + dt_rec, ) except AttributeError: rec_vec = None @@ -933,8 +967,17 @@ def run( if pprint: print(">>> Simulating the NEURON model for " + str(t_max) + " ms. <<<") start = time.process_time() - h.finitialize(self.v_init) - h.continuerun(t_max + self.t_calibrate) + if not use_coreneuron: + h.finitialize(self.v_init) + h.continuerun(t_max + self.t_calibrate) + else: + h.CVode().active(0) + coreneuron.enable = True + pc = h.ParallelContext() + pc.set_maxstep(10) # Set global step for parallel communication + h.finitialize(self.v_init) + pc.psolve(t_max + self.t_calibrate) # Solve for 100 ms + stop = time.process_time() if pprint: print(">>> Elapsed time: " + str(stop - start) + " seconds. <<<") diff --git a/tests/test_neurontree.py b/tests/test_neurontree.py index 81493f62..33b7fb91 100755 --- a/tests/test_neurontree.py +++ b/tests/test_neurontree.py @@ -847,15 +847,42 @@ def _create_ball(self): 1 """ self.v_eq = -75.0 - self.dt = 0.1 - self.tmax = 10000.0 - self.t_calibrate = 50. # load the morphology fname = os.path.join(MORPHOLOGIES_PATH_PREFIX, "ball.swc") self.tree = NeuronSimTree(fname, types=[1, 3, 4]) self.tree.fit_leak_current(self.v_eq, 10.0) self.tree.set_comp_tree() + def test_i_clamp(self, pplot=False): + # parameter setup + dt = 0.1 + tmax = 1000.0 + t_calibrate = 50. + amp = 0.01 + + self._create_ball() + self.tree.init_model(t_calibrate=t_calibrate, dt=dt) + # add step current clamp + self.tree.add_i_clamp( + loc=(1, 0.5), + delay=3 * tmax / 10, + dur= 4 * tmax / 10., + amp=0.01, + ) + + res = self.tree.run(tmax, record_from_iclamps=True, use_coreneuron=True) + print(res['v_m']) + + if pplot: + import matplotlib.pyplot as plt + + plt.figure() + plt.plot(res['t'], res['i_clamp'][0,:], label='I Clamp') + plt.plot(res['t'], res['v_m'][0,:], label='V Membrane') + plt.xlabel('Time [ms]') + plt.legend() + plt.show() + def _voltage_statistics(self, delta_t, mu_ou, sigma_ou, tau_ou): """ Stationary autocovariance C_v(Δ). @@ -963,20 +990,24 @@ def _autocovariance_fft(self, x, max_lag=None): return np.arange(max_lag + 1), acf / normalization def test_ou_processes(self, pplot=False): + # parameter setup + dt = 0.1 + tmax = 10000.0 + t_calibrate = 50. tau_ou = 5.0 sigma_ou = 0.005 mu_ou = 0.05 self._create_ball() - self.tree.init_model(t_calibrate=self.t_calibrate, dt=self.dt) + self.tree.init_model(t_calibrate=t_calibrate, dt=dt) # add OU process self.tree.add_ou_clamp( loc=(1, 0.5), mean=mu_ou, stdev=sigma_ou, tau=tau_ou, - delay=-self.t_calibrate / 2., - dur=self.tmax + self.t_calibrate / 2., + delay=-t_calibrate / 2., + dur=tmax + t_calibrate / 2., seed=46, ) @@ -1046,4 +1077,5 @@ def debug_print(pstr): # trn.test_impedance_properties_2() ts = TestStimuli() - ts.test_ou_processes() \ No newline at end of file + ts.test_i_clamp(pplot=True) + # ts.test_ou_processes() \ No newline at end of file From ea4d13378e0a1615cc3a14bb629ca88bca7d7e1f Mon Sep 17 00:00:00 2001 From: "w.wybo" Date: Fri, 16 Jan 2026 23:32:02 +0100 Subject: [PATCH 07/18] fix bug in coreneuron compatible recording of channel variables --- src/neat/actions/install.py | 10 ++++++++-- src/neat/simulations/neuron/neuronmodel.py | 6 +++--- tests/test_neurontree.py | 17 +++++++++-------- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/neat/actions/install.py b/src/neat/actions/install.py index 0936cbbf..f448b5c8 100644 --- a/src/neat/actions/install.py +++ b/src/neat/actions/install.py @@ -27,7 +27,10 @@ import platform import importlib import subprocess - +try: + import neuron +except ModuleNotFoundError: + pass from neat import IonChannel, ExpConcMech from neat.simulations.nest import nestml_tools @@ -199,7 +202,10 @@ def _compile_neuron(model_name, path_neat, channels, path_neuronresource=None): # # Also keep these for good measure # my_env["MAKEFLAGS"] = "-j1" - subprocess.call(["nrnivmodl", "-coreneuron", "mech/"]) # compile all mod files + if int(neuron.__version__.split(".")[0]) < 9: + subprocess.call(["nrnivmodl", "mech/"]) # compile all mod files + else: + subprocess.call(["nrnivmodl", "-coreneuron", "mech/"]) # compile all mod files print( f"\n------------------------------\n" diff --git a/src/neat/simulations/neuron/neuronmodel.py b/src/neat/simulations/neuron/neuronmodel.py index 731e0ee1..d4c54d40 100755 --- a/src/neat/simulations/neuron/neuronmodel.py +++ b/src/neat/simulations/neuron/neuronmodel.py @@ -895,7 +895,7 @@ def _rec_coreneuron_compatible(vec, var, dt_rec): rec_vec = h.Vector() _rec_coreneuron_compatible( rec_vec, - exec( + eval( f"self.sections[loc['node']](xx).{mechname[channel_name]}._ref_{var}" ), dt_rec, @@ -914,7 +914,7 @@ def _rec_coreneuron_compatible(vec, var, dt_rec): rec_vec = h.Vector() _rec_coreneuron_compatible( rec_vec, - exec( + eval( f"self.sections[loc['node']](loc['x'])._ref_{c_ion}i" ), dt_rec, @@ -933,7 +933,7 @@ def _rec_coreneuron_compatible(vec, var, dt_rec): rec_vec = h.Vector() _rec_coreneuron_compatible( rec_vec, - exec( + eval( f"self.sections[loc['node']](loc['x'])._ref_{curr_name}" ), dt_rec, diff --git a/tests/test_neurontree.py b/tests/test_neurontree.py index 33b7fb91..5388d1c1 100755 --- a/tests/test_neurontree.py +++ b/tests/test_neurontree.py @@ -279,7 +279,8 @@ def test_active(self, pplot=False): jj += 1 pl.show() - def test_channel_recording(self): + @pytest.mark.parametrize("use_coreneuron", [False, True]) + def test_channel_recording(self, use_coreneuron): self.load_T_tree_test_channel() # set of locations locs = [(1, 0.5), (4, 0.5), (4, 1.0), (5, 0.5), (6, 0.5), (7, 0.5), (8, 0.5)] @@ -287,7 +288,7 @@ def test_channel_recording(self): self.neurontree.init_model(t_calibrate=10.0, factor_lambda=10.0) self.neurontree.store_locs(locs, name="rec locs") # run test simulation - res = self.neurontree.run(1.0, record_from_channels=True) + res = self.neurontree.run(1.0, record_from_channels=True, use_coreneuron=use_coreneuron) # check if results are stored correctly assert set(res["chan"]["test_channel2"].keys()) == { "a00", @@ -318,7 +319,7 @@ def test_channel_recording(self): self.neurontree.init_model(t_calibrate=100.0, factor_lambda=10.0) self.neurontree.store_locs(locs, name="rec locs") # run test simulation - res = self.neurontree.run(10.0, record_from_channels=True) + res = self.neurontree.run(10.0, record_from_channels=True, use_coreneuron=use_coreneuron) # check if results are stored correctly assert set(res["chan"]["test_channel2"].keys()) == { "a00", @@ -365,7 +366,7 @@ def test_recording_timestep(self): self.neurontree.init_model(t_calibrate=10.0, dt=0.1, factor_lambda=10.0) self.neurontree.store_locs(locs, name="rec locs") res2 = self.neurontree.run( - 10.0, downsample=1, dt_rec=1.0, record_from_channels=True + 10.0, downsample=1, dt_rec=1.0, record_from_channels=True, use_coreneuron=use_coreneuron ) self.neurontree.delete_model() @@ -1064,10 +1065,10 @@ def debug_print(pstr): print(e) if __name__ == "__main__": - # tn = TestNeuron() + tn = TestNeuron() # tn.test_passive(pplot=True) # tn.test_active(pplot=True) - # tn.test_channel_recording() + tn.test_channel_recording(use_coreneuron=True) # tn.test_recording_timestep() # trn = TestReducedNeuron() @@ -1076,6 +1077,6 @@ def debug_print(pstr): # trn.test_geometry2() # trn.test_impedance_properties_2() - ts = TestStimuli() - ts.test_i_clamp(pplot=True) + # ts = TestStimuli() + # ts.test_i_clamp(pplot=True) # ts.test_ou_processes() \ No newline at end of file From cb810e0c102369be5509646c582bd912839c0dfe Mon Sep 17 00:00:00 2001 From: "w.wybo" Date: Sat, 17 Jan 2026 01:09:09 +0100 Subject: [PATCH 08/18] running channel tests --- src/neat/simulations/neuron/neuronmodel.py | 24 ++++- tests/test_concmechs.py | 19 ++-- tests/test_nesttree.py | 108 +++++++++++---------- 3 files changed, 86 insertions(+), 65 deletions(-) diff --git a/src/neat/simulations/neuron/neuronmodel.py b/src/neat/simulations/neuron/neuronmodel.py index d4c54d40..360c1b10 100755 --- a/src/neat/simulations/neuron/neuronmodel.py +++ b/src/neat/simulations/neuron/neuronmodel.py @@ -346,6 +346,11 @@ def init_model( ) # reset all storage self.delete_model() + # parallel contect + self.pc = h.ParallelContext() + self.pc.gid_clear() + self.gid = 0#self.pc.id() # or any unique integer + self.pc.set_gid2node(self.gid, self.pc.id()) # create the NEURON model self._create_neuron_tree(pprint=pprint) @@ -362,6 +367,8 @@ def delete_model(self): self.vecstims = [] self.netcons = [] self.vecs = [] + self.pc = None + self.gid = None self.store_locs([{"node": 1, "x": 0.0}], "rec locs") def _create_neuron_tree(self, pprint): @@ -736,6 +743,7 @@ def set_spiketrain(self, syn_index, syn_weight, spike_times): vecstim = h.VecStim() vecstim.play(spks_vec) netcon = h.NetCon(vecstim, self.syns[syn_index], 0, self.dt, syn_weight) + self.pc.cell(self.gid, netcon) # store the objects self.vecs.append(spks_vec) self.vecstims.append(vecstim) @@ -956,10 +964,18 @@ def _rec_coreneuron_compatible(vec, var, dt_rec): None, sec=self.sections[spike_rec_loc[0]], ) + self.pc.cell(self.pc.id(), self.spike_detector) self.spike_detector.threshold = spike_rec_thr res["spikes"] = h.Vector() self.spike_detector.record(res["spikes"]) + + def _export_coreneuron_model(path="_coreneuron"): + if os.path.exists(path): + shutil.rmtree(path) + os.makedirs(path) + h.nrncore_write(path) + # initialize # neuron.celsius=37. h.dt = self.dt @@ -972,11 +988,13 @@ def _rec_coreneuron_compatible(vec, var, dt_rec): h.continuerun(t_max + self.t_calibrate) else: h.CVode().active(0) + h.cvode.cache_efficient(1) coreneuron.enable = True - pc = h.ParallelContext() - pc.set_maxstep(10) # Set global step for parallel communication + _export_coreneuron_model() + # pc = h.ParallelContext() + # pc.set_maxstep(10) # Set global step for parallel communication h.finitialize(self.v_init) - pc.psolve(t_max + self.t_calibrate) # Solve for 100 ms + self.pc.psolve(t_max + self.t_calibrate) # Solve for 100 ms stop = time.process_time() if pprint: diff --git a/tests/test_concmechs.py b/tests/test_concmechs.py index 6e26e5b2..b7fa6b7f 100644 --- a/tests/test_concmechs.py +++ b/tests/test_concmechs.py @@ -339,6 +339,7 @@ def _simulate( record_concentrations=["ca"], record_currents=rec_currs, spike_rec_loc=rec_locs[0], + use_coreneuron=True, ) return res @@ -995,13 +996,13 @@ def test_nest_neuron_sim_ball( if __name__ == "__main__": tcm = TestConcMechs() - tcm.test_string_representation() + # tcm.test_string_representation() tcm.test_spiking(pplot=True) - tcm.test_impedance(pplot=True) - tcm.test_fitting_ball(pplot=True) - tcm.test_taufit_ball(pplot=True) - tcm.test_fitting_ball_and_stick(pplot=True) - tcm.test_finite_difference() - tcm.test_localized_conc_mech_pas_axon() - tcm.test_localized_conc_mech_act_axon() - tcm.test_nest_neuron_sim_ball(pplot=True, amp=2.0) + # tcm.test_impedance(pplot=True) + # tcm.test_fitting_ball(pplot=True) + # tcm.test_taufit_ball(pplot=True) + # tcm.test_fitting_ball_and_stick(pplot=True) + # tcm.test_finite_difference() + # tcm.test_localized_conc_mech_pas_axon() + # tcm.test_localized_conc_mech_act_axon() + # tcm.test_nest_neuron_sim_ball(pplot=True, amp=2.0) diff --git a/tests/test_nesttree.py b/tests/test_nesttree.py index 4f1436f0..97da32bd 100644 --- a/tests/test_nesttree.py +++ b/tests/test_nesttree.py @@ -30,7 +30,8 @@ import nest import nest.lib.hl_api_exceptions as nestexceptions except ImportError as e: - pytest.skip("NEST not installed", allow_module_level=True) + # pytest.skip("NEST not installed", allow_module_level=True) + pass from neat import PhysTree from neat import CompartmentNode, CompartmentTree @@ -136,9 +137,9 @@ def test_initialization(self): def test_single_comp_nest_neuron_comparison(self, pplot=False): dt = 0.001 - nest.ResetKernel() - channel_installer.load_or_install_nest_test_channels() - nest.SetKernelStatus(dict(resolution=dt)) + # nest.ResetKernel() + # channel_installer.load_or_install_nest_test_channels() + # nest.SetKernelStatus(dict(resolution=dt)) self.load_ball() csimtree_neuron = NeuronCompartmentTree(self.ctree) @@ -148,53 +149,54 @@ def test_single_comp_nest_neuron_comparison(self, pplot=False): csimtree_neuron.set_spiketrain(0, 0.001, [20.0, 23.0, 40.0]) res_neuron = csimtree_neuron.run(200.0) - csimtree_nest = NestCompartmentTree(self.ctree) - nestmodel = csimtree_nest.init_model("multichannel_test", 1) - # inputs - nestmodel.receptors = [ - { - "comp_idx": 0, - "receptor_type": "i_AMPA", - "params": {"e_AMPA": 0.0, "tau_r_AMPA": 0.2, "tau_d_AMPA": 3.0}, - } - ] - sg = nest.Create("spike_generator", 1, {"spike_times": [220.0, 223.0, 240.0]}) - nest.Connect( - sg, - nestmodel, - syn_spec={ - "synapse_model": "static_synapse", - "weight": 0.001, - "delay": 3 * dt, - "receptor_type": 0, - }, - ) - # voltage recording - mm = nest.Create("multimeter", 1, {"record_from": ["v_comp0"], "interval": dt}) - nest.Connect(mm, nestmodel) - # simulate - nest.Simulate(400.0) - res_nest = nest.GetStatus(mm, "events")[0] - - idx0 = int(200.0 / dt) - res_nest["times"] = res_nest["times"][idx0:] - res_nest["times"][idx0] - res_nest["v_comp0"] = res_nest["v_comp0"][idx0:] - v0 = res_nest["v_comp0"][0] - - idx1 = min(len(res_neuron["v_m"][0]), len(res_nest["v_comp0"])) - assert ( - np.sqrt( - np.mean((res_nest["v_comp0"][:idx1] - res_neuron["v_m"][0][:idx1]) ** 2) - ) - < 0.05 - ) - assert np.allclose( - res_nest["v_comp0"][:idx1], res_neuron["v_m"][0][:idx1], atol=4.0 - ) + # csimtree_nest = NestCompartmentTree(self.ctree) + # nestmodel = csimtree_nest.init_model("multichannel_test", 1) + # # inputs + # nestmodel.receptors = [ + # { + # "comp_idx": 0, + # "receptor_type": "i_AMPA", + # "params": {"e_AMPA": 0.0, "tau_r_AMPA": 0.2, "tau_d_AMPA": 3.0}, + # } + # ] + # sg = nest.Create("spike_generator", 1, {"spike_times": [220.0, 223.0, 240.0]}) + # nest.Connect( + # sg, + # nestmodel, + # syn_spec={ + # "synapse_model": "static_synapse", + # "weight": 0.001, + # "delay": 3 * dt, + # "receptor_type": 0, + # }, + # ) + # # voltage recording + # mm = nest.Create("multimeter", 1, {"record_from": ["v_comp0"], "interval": dt}) + # nest.Connect(mm, nestmodel) + # # simulate + # nest.Simulate(400.0) + # res_nest = nest.GetStatus(mm, "events")[0] + + # idx0 = int(200.0 / dt) + # res_nest["times"] = res_nest["times"][idx0:] - res_nest["times"][idx0] + # res_nest["v_comp0"] = res_nest["v_comp0"][idx0:] + # v0 = res_nest["v_comp0"][0] + + # idx1 = min(len(res_neuron["v_m"][0]), len(res_nest["v_comp0"])) + # assert ( + # np.sqrt( + # np.mean((res_nest["v_comp0"][:idx1] - res_neuron["v_m"][0][:idx1]) ** 2) + # ) + # < 0.05 + # ) + # assert np.allclose( + # res_nest["v_comp0"][:idx1], res_neuron["v_m"][0][:idx1], atol=4.0 + # ) if pplot: - pl.plot(res_neuron["t"][:idx1], res_neuron["v_m"][0][:idx1], "rx-") - pl.plot(res_nest["times"][:idx1], res_nest["v_comp0"][:idx1], "bo--") + pl.plot(res_neuron["t"][:], res_neuron["v_m"][0][:], "rx-") + # pl.plot(res_neuron["t"][:idx1], res_neuron["v_m"][0][:idx1], "rx-") + # pl.plot(res_nest["times"][:idx1], res_nest["v_comp0"][:idx1], "bo--") pl.show() def load_axon_tree(self): @@ -495,8 +497,8 @@ def test_dend_nest_neuron_comparison(self, pplot=False): if __name__ == "__main__": tn = TestNest() - tn.test_model_construction() - tn.test_initialization() + # tn.test_model_construction() + # tn.test_initialization() tn.test_single_comp_nest_neuron_comparison(pplot=True) - tn.test_axon_nest_neuron_comparison(pplot=True) - tn.test_dend_nest_neuron_comparison(pplot=True) + # tn.test_axon_nest_neuron_comparison(pplot=True) + # tn.test_dend_nest_neuron_comparison(pplot=True) From dc08bcc6189402d40ddb2ca3d4f7dce6fdb06723 Mon Sep 17 00:00:00 2001 From: "w.wybo" Date: Fri, 23 Jan 2026 11:43:06 +0100 Subject: [PATCH 09/18] move towards usage of the 'special' file to run with coreneuron, create an alias called 'python_with_{model_name}' --- src/neat/actions/install.py | 176 ++++++++++++++++++++- src/neat/simulations/neuron/neuronmodel.py | 24 ++- tests/pytest_corenrn_runner.py | 5 + tests/test_neurontree.py | 7 +- 4 files changed, 207 insertions(+), 5 deletions(-) create mode 100644 tests/pytest_corenrn_runner.py diff --git a/src/neat/actions/install.py b/src/neat/actions/install.py index f448b5c8..3ff20412 100644 --- a/src/neat/actions/install.py +++ b/src/neat/actions/install.py @@ -22,9 +22,11 @@ import os import sys import glob +import stat import shutil import inspect import platform +from pathlib import Path import importlib import subprocess try: @@ -161,8 +163,172 @@ def _resolve_model_name(model_name, channel_path_arg): return model_name + +def get_local_bin_dir(): + """ + Get the most local bin directory based on the active environment. + Priority order: + 1. Conda environment bin + 2. Virtual environment bin (venv/virtualenv) + 3. Docker container /usr/local/bin (if in container) + 4. ~/.local/bin (fallback) + + Returns: + Path: The bin directory to use + """ + + # 1. Check for conda environment + conda_prefix = os.environ.get('CONDA_PREFIX') + if conda_prefix: + bin_dir = Path(conda_prefix) / 'bin' + if bin_dir.exists() and bin_dir.is_dir(): + env_name = os.environ.get('CONDA_DEFAULT_ENV', 'unknown') + print(f"đŸ“Ļ Detected conda environment: {env_name}") + return bin_dir + + # 2. Check for virtual environment (venv/virtualenv) + virtual_env = os.environ.get('VIRTUAL_ENV') + if virtual_env: + bin_dir = Path(virtual_env) / 'bin' + if bin_dir.exists() and bin_dir.is_dir(): + print(f"🐍 Detected Python virtual environment") + return bin_dir + + # 3. Check if running in Docker container + if os.path.exists('/.dockerenv') or os.environ.get('DOCKER_CONTAINER'): + # In Docker, prefer /usr/local/bin for system-wide access + bin_dir = Path('/usr/local/bin') + if os.access(bin_dir, os.W_OK): + print(f"đŸŗ Detected Docker container") + print(f" Using system bin (writable): {bin_dir}") + return bin_dir + else: + # If /usr/local/bin is not writable, fall back to user location + print(f"đŸŗ Detected Docker container") + print(f" /usr/local/bin not writable, using user bin") + + # 4. Fallback to ~/.local/bin + print(f"â„šī¸ No specific environment detected") + return Path.home() / ".local" / "bin" + + +def create_nrnspecial_wrapper(special_path, wrapper_name="python_with_my_nrn_model", install_dir=None): + """ + Create a wrapper script for running Python with NEURON + CoreNEURON mechanisms + + Args: + special_path: Path to the 'special' executable (e.g., /path/to/arm64/special) + wrapper_name: Name for the wrapper script + install_dir: Directory to install wrapper (default: auto-detect environment) + """ + + # Set default install directory based on environment + if install_dir is None: + install_dir = get_local_bin_dir() + print(f" Using directory: {install_dir}") + print() + else: + install_dir = Path(install_dir) + print(f"Using specified directory: {install_dir}") + print() + + # Convert special_path to Path object and resolve to absolute path + special_path = Path(special_path).resolve() + + # Check if special executable exists + if not special_path.exists(): + print(f"Error: special executable not found at {special_path}", file=sys.stderr) + sys.exit(1) + + if not special_path.is_file(): + print(f"Error: {special_path} is not a file", file=sys.stderr) + sys.exit(1) + + # Create install directory if it doesn't exist + if not install_dir.exists(): + print(f"Creating directory: {install_dir}") + try: + install_dir.mkdir(parents=True, exist_ok=True) + print(f"✓ Created {install_dir}") + print() + except PermissionError: + print(f"Error: Permission denied creating {install_dir}", file=sys.stderr) + print(f"Try running with sudo or choose a different directory", file=sys.stderr) + sys.exit(1) + + # Check write permissions + if not os.access(install_dir, os.W_OK): + print(f"Error: No write permission for {install_dir}", file=sys.stderr) + print(f"Try running with sudo or choose a different directory", file=sys.stderr) + sys.exit(1) + + # Path for the wrapper script + wrapper_path = install_dir / wrapper_name + + # Wrapper script content + wrapper_content = f"""#!/bin/bash +# Wrapper to run Python scripts with NEURON + CoreNEURON mechanisms +# Auto-generated wrapper for: {special_path} + +SPECIAL_PATH="{special_path}" + +# Check if special exists +if [ ! -f "$SPECIAL_PATH" ]; then + echo "Error: special executable not found at $SPECIAL_PATH" >&2 + exit 1 +fi + +# Run special with -python flag and pass all arguments +exec "$SPECIAL_PATH" -python "$@" +""" + # Write the wrapper script + try: + with open(wrapper_path, 'w') as f: + f.write(wrapper_content) + + # Make it executable (chmod +x) + wrapper_path.chmod(wrapper_path.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) + + print(f"✓ Created wrapper at: {wrapper_path}") + print() + + # Check if install_dir is in PATH + path_dirs = os.environ.get('PATH', '').split(':') + if str(install_dir) in path_dirs: + print(f"✓ {install_dir} is already in your PATH") + else: + print(f"⚠ Warning: {install_dir} is not in your current PATH") + + # Provide context-specific advice + if os.environ.get('CONDA_PREFIX'): + print(f" This is unusual for an active conda environment.") + print(f" Try: conda deactivate && conda activate {os.environ.get('CONDA_DEFAULT_ENV')}") + elif os.environ.get('VIRTUAL_ENV'): + print(f" This is unusual for an active virtual environment.") + print(f" Try deactivating and reactivating your environment.") + elif os.path.exists('/.dockerenv'): + print(f" Add to your Dockerfile or docker run command:") + print(f' ENV PATH="{install_dir}:$PATH"') + else: + print(f" Add to your ~/.bashrc or ~/.zshrc:") + print(f' export PATH="{install_dir}:$PATH"') + + print() + print("Usage:") + print(f" {wrapper_name} my_script.py") + print() + + return wrapper_path + + except Exception as e: + print(f"Error creating wrapper: {e}", file=sys.stderr) + sys.exit(1) + + def _compile_neuron(model_name, path_neat, channels, path_neuronresource=None): + print(f"path of this file: {__file__} ") + # combine `model_name` with the neuron compilation path path_for_neuron_compilation = os.path.join( path_neat, "simulations/neuron/tmp/", model_name @@ -203,10 +369,18 @@ def _compile_neuron(model_name, path_neat, channels, path_neuronresource=None): # # Also keep these for good measure # my_env["MAKEFLAGS"] = "-j1" if int(neuron.__version__.split(".")[0]) < 9: - subprocess.call(["nrnivmodl", "mech/"]) # compile all mod files + subprocess.call(["nrnivmodl", "-coreneuron", "mech/"]) # compile all mod files else: subprocess.call(["nrnivmodl", "-coreneuron", "mech/"]) # compile all mod files + create_nrnspecial_wrapper( + special_path=os.path.join( + path_for_neuron_compilation, + f"{platform.machine()}/special", + ), + wrapper_name=f"python_with_{model_name}", + ) + print( f"\n------------------------------\n" f"The compiled .mod-files can be loaded into neuron using:\n" diff --git a/src/neat/simulations/neuron/neuronmodel.py b/src/neat/simulations/neuron/neuronmodel.py index 360c1b10..89eeab5b 100755 --- a/src/neat/simulations/neuron/neuronmodel.py +++ b/src/neat/simulations/neuron/neuronmodel.py @@ -117,8 +117,28 @@ def print_err(): ) if os.path.exists(path): print(f"Found path: {path}, loading mechanisms...") - neuron.load_mechanisms(path) + # import ctypes + # mech_path = os.path.join(path, f"{platform.machine()}/libcorenrnmech.dylib") + # h.nrn_load_dll(mech_path) + + path1 = os.path.join( + os.path.dirname(__file__), + f"tmp/{name}/{platform.machine()}/libnrnmech.dylib", + ) + # path2 = os.path.join( + # os.path.dirname(__file__), + # f"tmp/{name}/{platform.machine()}/libcorenrnmech.dylib", + # ) + + coreneuron.enable = True + # h.nrn_load_dll(path2) + h.nrn_load_dll(path1) + # neuron.load_mechanisms(path) + + + print(f"... done.") + # import os else: print_err() @@ -990,7 +1010,7 @@ def _export_coreneuron_model(path="_coreneuron"): h.CVode().active(0) h.cvode.cache_efficient(1) coreneuron.enable = True - _export_coreneuron_model() + # _export_coreneuron_model() # pc = h.ParallelContext() # pc.set_maxstep(10) # Set global step for parallel communication h.finitialize(self.v_init) diff --git a/tests/pytest_corenrn_runner.py b/tests/pytest_corenrn_runner.py new file mode 100644 index 00000000..89448136 --- /dev/null +++ b/tests/pytest_corenrn_runner.py @@ -0,0 +1,5 @@ +import pytest +import sys +# Manually run the pytest main function +print("Running pytest from pytest_corenrn_runner.py ", sys.argv[2:]) +sys.exit(pytest.main(sys.argv[2:])) \ No newline at end of file diff --git a/tests/test_neurontree.py b/tests/test_neurontree.py index 5388d1c1..e97d4b11 100755 --- a/tests/test_neurontree.py +++ b/tests/test_neurontree.py @@ -289,6 +289,8 @@ def test_channel_recording(self, use_coreneuron): self.neurontree.store_locs(locs, name="rec locs") # run test simulation res = self.neurontree.run(1.0, record_from_channels=True, use_coreneuron=use_coreneuron) + + print("!!!!!", res["chan"]["test_channel2"]["a00"]) # check if results are stored correctly assert set(res["chan"]["test_channel2"].keys()) == { "a00", @@ -350,7 +352,7 @@ def test_channel_recording(self, use_coreneuron): assert res["chan"]["test_channel2"]["a11"].shape == (n_loc, n_step) assert res["chan"]["test_channel2"]["p_open"].shape == (n_loc, n_step) - def test_recording_timestep(self): + def test_recording_timestep(self,use_coreneuron=False): self.load_T_tree_test_channel() # set of locations locs = [(1, 0.5), (4, 0.5), (4, 1.0), (5, 0.5), (6, 0.5), (7, 0.5), (8, 0.5)] @@ -1012,7 +1014,7 @@ def test_ou_processes(self, pplot=False): seed=46, ) - res = self.tree.run(self.tmax, record_from_iclamps=True) + res = self.tree.run(tmax, record_from_iclamps=True) mu_ou_empirical = np.mean(res['i_clamp'][0,:]) cov_ou_empirical = np.cov(res['i_clamp'][0,:]) @@ -1065,6 +1067,7 @@ def debug_print(pstr): print(e) if __name__ == "__main__": + # sys.exit(pytest.main(sys.argv[1:])) tn = TestNeuron() # tn.test_passive(pplot=True) # tn.test_active(pplot=True) From beae84a874d6443008139a38e917a25f6248fa01 Mon Sep 17 00:00:00 2001 From: "w.wybo" Date: Fri, 23 Jan 2026 17:42:33 +0100 Subject: [PATCH 10/18] neuron tests running again --- src/neat/actions/install.py | 95 +++++++++-------- src/neat/channels/concmechs.py | 1 - src/neat/modelreduction/cachetrees.py | 1 - src/neat/simulations/nest/nestml_tools.py | 1 - src/neat/simulations/nest/nestmodel.py | 1 - src/neat/simulations/neuron/neuronmodel.py | 88 ++++++---------- src/neat/trees/compartmenttree.py | 1 - src/neat/trees/greenstree.py | 3 +- src/neat/trees/sovtree.py | 12 +-- src/neat/trees/stree.py | 6 +- tests/channel_installer.py | 2 + tests/pytest_corenrn_runner.py | 3 +- tests/test_cnet.py | 1 - tests/test_compartmenttree.py | 1 - tests/test_concmechs.py | 1 - tests/test_morphtree.py | 1 - tests/test_neurontree.py | 117 ++++++++++++--------- tests/test_sovtree.py | 1 - 18 files changed, 159 insertions(+), 177 deletions(-) diff --git a/src/neat/actions/install.py b/src/neat/actions/install.py index 3ff20412..4180cc38 100644 --- a/src/neat/actions/install.py +++ b/src/neat/actions/install.py @@ -29,6 +29,7 @@ from pathlib import Path import importlib import subprocess + try: import neuron except ModuleNotFoundError: @@ -163,7 +164,6 @@ def _resolve_model_name(model_name, channel_path_arg): return model_name - def get_local_bin_dir(): """ Get the most local bin directory based on the active environment. @@ -172,32 +172,32 @@ def get_local_bin_dir(): 2. Virtual environment bin (venv/virtualenv) 3. Docker container /usr/local/bin (if in container) 4. ~/.local/bin (fallback) - + Returns: Path: The bin directory to use """ - + # 1. Check for conda environment - conda_prefix = os.environ.get('CONDA_PREFIX') + conda_prefix = os.environ.get("CONDA_PREFIX") if conda_prefix: - bin_dir = Path(conda_prefix) / 'bin' + bin_dir = Path(conda_prefix) / "bin" if bin_dir.exists() and bin_dir.is_dir(): - env_name = os.environ.get('CONDA_DEFAULT_ENV', 'unknown') + env_name = os.environ.get("CONDA_DEFAULT_ENV", "unknown") print(f"đŸ“Ļ Detected conda environment: {env_name}") return bin_dir - + # 2. Check for virtual environment (venv/virtualenv) - virtual_env = os.environ.get('VIRTUAL_ENV') + virtual_env = os.environ.get("VIRTUAL_ENV") if virtual_env: - bin_dir = Path(virtual_env) / 'bin' + bin_dir = Path(virtual_env) / "bin" if bin_dir.exists() and bin_dir.is_dir(): print(f"🐍 Detected Python virtual environment") return bin_dir - + # 3. Check if running in Docker container - if os.path.exists('/.dockerenv') or os.environ.get('DOCKER_CONTAINER'): + if os.path.exists("/.dockerenv") or os.environ.get("DOCKER_CONTAINER"): # In Docker, prefer /usr/local/bin for system-wide access - bin_dir = Path('/usr/local/bin') + bin_dir = Path("/usr/local/bin") if os.access(bin_dir, os.W_OK): print(f"đŸŗ Detected Docker container") print(f" Using system bin (writable): {bin_dir}") @@ -206,22 +206,24 @@ def get_local_bin_dir(): # If /usr/local/bin is not writable, fall back to user location print(f"đŸŗ Detected Docker container") print(f" /usr/local/bin not writable, using user bin") - + # 4. Fallback to ~/.local/bin print(f"â„šī¸ No specific environment detected") return Path.home() / ".local" / "bin" -def create_nrnspecial_wrapper(special_path, wrapper_name="python_with_my_nrn_model", install_dir=None): +def create_nrnspecial_wrapper( + special_path, wrapper_name="python_with_my_nrn_model", install_dir=None +): """ Create a wrapper script for running Python with NEURON + CoreNEURON mechanisms - + Args: special_path: Path to the 'special' executable (e.g., /path/to/arm64/special) wrapper_name: Name for the wrapper script install_dir: Directory to install wrapper (default: auto-detect environment) """ - + # Set default install directory based on environment if install_dir is None: install_dir = get_local_bin_dir() @@ -231,19 +233,19 @@ def create_nrnspecial_wrapper(special_path, wrapper_name="python_with_my_nrn_mod install_dir = Path(install_dir) print(f"Using specified directory: {install_dir}") print() - + # Convert special_path to Path object and resolve to absolute path special_path = Path(special_path).resolve() - + # Check if special executable exists if not special_path.exists(): print(f"Error: special executable not found at {special_path}", file=sys.stderr) sys.exit(1) - + if not special_path.is_file(): print(f"Error: {special_path} is not a file", file=sys.stderr) sys.exit(1) - + # Create install directory if it doesn't exist if not install_dir.exists(): print(f"Creating directory: {install_dir}") @@ -253,18 +255,21 @@ def create_nrnspecial_wrapper(special_path, wrapper_name="python_with_my_nrn_mod print() except PermissionError: print(f"Error: Permission denied creating {install_dir}", file=sys.stderr) - print(f"Try running with sudo or choose a different directory", file=sys.stderr) + print( + f"Try running with sudo or choose a different directory", + file=sys.stderr, + ) sys.exit(1) - + # Check write permissions if not os.access(install_dir, os.W_OK): print(f"Error: No write permission for {install_dir}", file=sys.stderr) print(f"Try running with sudo or choose a different directory", file=sys.stderr) sys.exit(1) - + # Path for the wrapper script wrapper_path = install_dir / wrapper_name - + # Wrapper script content wrapper_content = f"""#!/bin/bash # Wrapper to run Python scripts with NEURON + CoreNEURON mechanisms @@ -278,48 +283,55 @@ def create_nrnspecial_wrapper(special_path, wrapper_name="python_with_my_nrn_mod exit 1 fi +# Export environment variable to indicate NEURON was launched with special +export NEURON_USING_SPECIAL=1 + # Run special with -python flag and pass all arguments exec "$SPECIAL_PATH" -python "$@" """ # Write the wrapper script try: - with open(wrapper_path, 'w') as f: + with open(wrapper_path, "w") as f: f.write(wrapper_content) - + # Make it executable (chmod +x) - wrapper_path.chmod(wrapper_path.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) - + wrapper_path.chmod( + wrapper_path.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH + ) + print(f"✓ Created wrapper at: {wrapper_path}") print() - + # Check if install_dir is in PATH - path_dirs = os.environ.get('PATH', '').split(':') + path_dirs = os.environ.get("PATH", "").split(":") if str(install_dir) in path_dirs: print(f"✓ {install_dir} is already in your PATH") else: print(f"⚠ Warning: {install_dir} is not in your current PATH") - + # Provide context-specific advice - if os.environ.get('CONDA_PREFIX'): + if os.environ.get("CONDA_PREFIX"): print(f" This is unusual for an active conda environment.") - print(f" Try: conda deactivate && conda activate {os.environ.get('CONDA_DEFAULT_ENV')}") - elif os.environ.get('VIRTUAL_ENV'): + print( + f" Try: conda deactivate && conda activate {os.environ.get('CONDA_DEFAULT_ENV')}" + ) + elif os.environ.get("VIRTUAL_ENV"): print(f" This is unusual for an active virtual environment.") print(f" Try deactivating and reactivating your environment.") - elif os.path.exists('/.dockerenv'): + elif os.path.exists("/.dockerenv"): print(f" Add to your Dockerfile or docker run command:") print(f' ENV PATH="{install_dir}:$PATH"') else: print(f" Add to your ~/.bashrc or ~/.zshrc:") print(f' export PATH="{install_dir}:$PATH"') - + print() print("Usage:") print(f" {wrapper_name} my_script.py") print() - + return wrapper_path - + except Exception as e: print(f"Error creating wrapper: {e}", file=sys.stderr) sys.exit(1) @@ -334,7 +346,7 @@ def _compile_neuron(model_name, path_neat, channels, path_neuronresource=None): path_neat, "simulations/neuron/tmp/", model_name ) # delete old compiled files if exist - if os.path.exists(path_for_neuron_compilation): + if os.path.exists(path_for_neuron_compilation): shutil.rmtree(path_for_neuron_compilation) path_for_mod_files = os.path.join(path_for_neuron_compilation, "mech/") @@ -352,7 +364,6 @@ def _compile_neuron(model_name, path_neat, channels, path_neuronresource=None): print(" - writing .mod file for:", chan.__class__.__name__) chan.write_mod_file(path_for_mod_files) - # change to directory where 'mech/' folder is located and compile the mechanisms os.chdir(path_for_neuron_compilation) # with open(".noindex", "w") as f: # prevent mac from indexing compiled files @@ -362,7 +373,7 @@ def _compile_neuron(model_name, path_neat, channels, path_neuronresource=None): # shutil.rmtree(f"{platform.machine()}/") print("!!!", os.getcwd()) # my_env = os.environ.copy() - # # This forces every sub-call to 'make' to append -j1, effectively + # # This forces every sub-call to 'make' to append -j1, effectively # # overriding the -j4 passed by the nrnivmodl wrapper. # my_env["MAKE"] = "make -j1" @@ -370,7 +381,7 @@ def _compile_neuron(model_name, path_neat, channels, path_neuronresource=None): # my_env["MAKEFLAGS"] = "-j1" if int(neuron.__version__.split(".")[0]) < 9: subprocess.call(["nrnivmodl", "-coreneuron", "mech/"]) # compile all mod files - else: + else: subprocess.call(["nrnivmodl", "-coreneuron", "mech/"]) # compile all mod files create_nrnspecial_wrapper( diff --git a/src/neat/channels/concmechs.py b/src/neat/channels/concmechs.py index 4332cb7e..568f7c6b 100755 --- a/src/neat/channels/concmechs.py +++ b/src/neat/channels/concmechs.py @@ -24,7 +24,6 @@ import sympy as sp import numpy as np - CFG = DefaultPhysiology() diff --git a/src/neat/modelreduction/cachetrees.py b/src/neat/modelreduction/cachetrees.py index 93d940b7..6d4191a4 100644 --- a/src/neat/modelreduction/cachetrees.py +++ b/src/neat/modelreduction/cachetrees.py @@ -33,7 +33,6 @@ from ..trees.sovtree import SOVTree from ..trees.netree import NET, NETNode - try: from ..simulations.neuron import neuronmodel as neurm except ModuleNotFoundError: diff --git a/src/neat/simulations/nest/nestml_tools.py b/src/neat/simulations/nest/nestml_tools.py index f9856686..5c8700e0 100755 --- a/src/neat/simulations/nest/nestml_tools.py +++ b/src/neat/simulations/nest/nestml_tools.py @@ -23,7 +23,6 @@ import copy import warnings - PERMITTED_BLOCK_NAMES = [ "parameters", "state", diff --git a/src/neat/simulations/nest/nestmodel.py b/src/neat/simulations/nest/nestmodel.py index 3f45d652..f63693ee 100755 --- a/src/neat/simulations/nest/nestmodel.py +++ b/src/neat/simulations/nest/nestmodel.py @@ -27,7 +27,6 @@ import warnings import subprocess - CFG = DefaultPhysiology() diff --git a/src/neat/simulations/neuron/neuronmodel.py b/src/neat/simulations/neuron/neuronmodel.py index 89eeab5b..d2808324 100755 --- a/src/neat/simulations/neuron/neuronmodel.py +++ b/src/neat/simulations/neuron/neuronmodel.py @@ -34,7 +34,18 @@ try: import neuron - from neuron import coreneuron + + if ( + "NEURON_USING_SPECIAL" in os.environ + and os.environ["NEURON_USING_SPECIAL"] == "1" + ): + from neuron import coreneuron + + coreneuron.enable = True + USE_CORENEURON = True + else: + USE_CORENEURON = False + from neuron import h h.load_file("stdlib.hoc") # contains the lambda rule @@ -117,28 +128,12 @@ def print_err(): ) if os.path.exists(path): print(f"Found path: {path}, loading mechanisms...") - # import ctypes - # mech_path = os.path.join(path, f"{platform.machine()}/libcorenrnmech.dylib") - # h.nrn_load_dll(mech_path) - - path1 = os.path.join( - os.path.dirname(__file__), - f"tmp/{name}/{platform.machine()}/libnrnmech.dylib", - ) - # path2 = os.path.join( - # os.path.dirname(__file__), - # f"tmp/{name}/{platform.machine()}/libcorenrnmech.dylib", - # ) - - coreneuron.enable = True - # h.nrn_load_dll(path2) - h.nrn_load_dll(path1) - # neuron.load_mechanisms(path) - - - + if not USE_CORENEURON: + # only needs to be loaded if we are not running using special + mech_loaded_bool = neuron.load_mechanisms(path) + if not mech_loaded_bool: + raise FileNotFoundError(f"Loading mechanisms from '{path}' failed.") print(f"... done.") - # import os else: print_err() @@ -369,7 +364,7 @@ def init_model( # parallel contect self.pc = h.ParallelContext() self.pc.gid_clear() - self.gid = 0#self.pc.id() # or any unique integer + self.gid = 0 # self.pc.id() # or any unique integer self.pc.set_gid2node(self.gid, self.pc.id()) # create the NEURON model self._create_neuron_tree(pprint=pprint) @@ -783,7 +778,6 @@ def run( record_currents=[], spike_rec_loc=None, spike_rec_thr=-20.0, - use_coreneuron=False, pprint=False, ): """ @@ -798,7 +792,7 @@ def run( Records the state of the model every `downsample` time-steps dt_rec: float or None recording time step (if `None` is given, defaults to the simulation - time-step). Note that if `use_coreneuron` is set to ``True``, this parameter + time-step). Note that if running with coreneuron, this parameter is ignored and recording is done at every simulation time-step and downsampled according to the `downsample` argument. record_from_syns: bool (default ``False``) @@ -829,8 +823,6 @@ def run( Record the output spike times from this location spike_rec_thr: float Spike threshold - use_coreneuron: bool (default ``False``) - Whether or not to use CoreNEURON to run the simulation pprint: bool (default ``False``) Whether or not to print info on the NEURON simulation @@ -849,20 +841,18 @@ def run( indstart = int(self.t_calibrate / dt_rec) def _rec_coreneuron_compatible(vec, var, dt_rec): - """ Helper function to make recording compatible with CoreNEURON + """Helper function to make recording compatible with CoreNEURON We're not allowed to use the dt argument in vec.record when using CoreNEURON, so we manually downsample after the simulation """ - if use_coreneuron: + if USE_CORENEURON: vec.record(var) else: vec.record(var, dt_rec) # simulation time recorder res = {"t": h.Vector()} - _rec_coreneuron_compatible( - res["t"], h._ref_t, dt_rec - ) + _rec_coreneuron_compatible(res["t"], h._ref_t, dt_rec) # voltage recorders res["v_m"] = [] @@ -870,34 +860,28 @@ def _rec_coreneuron_compatible(vec, var, dt_rec): res["v_m"].append(h.Vector()) _rec_coreneuron_compatible( res["v_m"][-1], self.sections[loc["node"]](loc["x"])._ref_v, dt_rec - ) + ) # synapse current recorders if record_from_syns: res["i_syn"] = [] for syn in self.syns: res["i_syn"].append(h.Vector()) - _rec_coreneuron_compatible( - res["i_syn"][-1], syn._ref_i, dt_rec - ) + _rec_coreneuron_compatible(res["i_syn"][-1], syn._ref_i, dt_rec) # current clamp current recorders if record_from_iclamps: res["i_clamp"] = [] for iclamp in self.iclamps: res["i_clamp"].append(h.Vector()) - _rec_coreneuron_compatible( - res["i_clamp"][-1], iclamp._ref_i, dt_rec - ) + _rec_coreneuron_compatible(res["i_clamp"][-1], iclamp._ref_i, dt_rec) # voltage clamp current recorders if record_from_vclamps: res["i_vclamp"] = [] for vclamp in self.vclamps: res["i_vclamp"].append(h.Vector()) - _rec_coreneuron_compatible( - res["i_vclamp"][-1], vclamp._ref_i, dt_rec - ) + _rec_coreneuron_compatible(res["i_vclamp"][-1], vclamp._ref_i, dt_rec) # channel state variable recordings if record_from_channels: @@ -942,9 +926,7 @@ def _rec_coreneuron_compatible(vec, var, dt_rec): rec_vec = h.Vector() _rec_coreneuron_compatible( rec_vec, - eval( - f"self.sections[loc['node']](loc['x'])._ref_{c_ion}i" - ), + eval(f"self.sections[loc['node']](loc['x'])._ref_{c_ion}i"), dt_rec, ) except AttributeError: @@ -989,13 +971,6 @@ def _rec_coreneuron_compatible(vec, var, dt_rec): res["spikes"] = h.Vector() self.spike_detector.record(res["spikes"]) - - def _export_coreneuron_model(path="_coreneuron"): - if os.path.exists(path): - shutil.rmtree(path) - os.makedirs(path) - h.nrncore_write(path) - # initialize # neuron.celsius=37. h.dt = self.dt @@ -1003,18 +978,15 @@ def _export_coreneuron_model(path="_coreneuron"): if pprint: print(">>> Simulating the NEURON model for " + str(t_max) + " ms. <<<") start = time.process_time() - if not use_coreneuron: + if not USE_CORENEURON: h.finitialize(self.v_init) h.continuerun(t_max + self.t_calibrate) else: h.CVode().active(0) h.cvode.cache_efficient(1) coreneuron.enable = True - # _export_coreneuron_model() - # pc = h.ParallelContext() - # pc.set_maxstep(10) # Set global step for parallel communication h.finitialize(self.v_init) - self.pc.psolve(t_max + self.t_calibrate) # Solve for 100 ms + self.pc.psolve(t_max + self.t_calibrate) # Solve for 100 ms stop = time.process_time() if pprint: @@ -1220,7 +1192,7 @@ def calc_zt( np.array The impulse response kernel in [MOhm/ms] """ - (loc0, loc1) = self.convert_loc_arg_to_locs([loc0, loc1]) + loc0, loc1 = self.convert_loc_arg_to_locs([loc0, loc1]) self.set_simulation_parameters( dt=dt, t_calibrate=t_calibrate, v_init=v_init, factor_lambda=factor_lambda ) diff --git a/src/neat/trees/compartmenttree.py b/src/neat/trees/compartmenttree.py index eedf16f4..6f600741 100755 --- a/src/neat/trees/compartmenttree.py +++ b/src/neat/trees/compartmenttree.py @@ -35,7 +35,6 @@ from functools import reduce from typing import Literal - CFG = DefaultPhysiology() diff --git a/src/neat/trees/greenstree.py b/src/neat/trees/greenstree.py index 71c69c98..ac1b0df6 100755 --- a/src/neat/trees/greenstree.py +++ b/src/neat/trees/greenstree.py @@ -33,7 +33,6 @@ from ..tools import kernelextraction as ke from ..factorydefaults import DefaultPhysiology - CFG = DefaultPhysiology() @@ -989,7 +988,7 @@ def calc_channel_response_t( dcrt_dt[channel_name][svar_name] = {} # convert frequency impedances to time domain kernels - (crt[channel_name][svar_name], dcrt_dt[channel_name][svar_name]) = ( + crt[channel_name][svar_name], dcrt_dt[channel_name][svar_name] = ( self.ft.inverse_fourier( crf[channel_name][svar_name], method=method, diff --git a/src/neat/trees/sovtree.py b/src/neat/trees/sovtree.py index 92a14c3d..aede72eb 100755 --- a/src/neat/trees/sovtree.py +++ b/src/neat/trees/sovtree.py @@ -581,10 +581,8 @@ def get_mode_importance( / np.abs(alpha) ) else: - raise ValueError( - "`importance_type` argument can be 'simple' or \ - 'full'" - ) + raise ValueError("`importance_type` argument can be 'simple' or \ + 'full'") return absolute_importance / np.max(absolute_importance) @@ -654,10 +652,8 @@ def get_important_modes( elif sort_type == "importance": inds_sort = np.argsort(importance)[::-1] else: - raise ValueError( - "`sort_type` argument can be 'timescale' or \ - 'importance'" - ) + raise ValueError("`sort_type` argument can be 'timescale' or \ + 'importance'") if return_importance: return alphas[inds_sort], gammas[inds_sort, :], importance[inds_sort] else: diff --git a/src/neat/trees/stree.py b/src/neat/trees/stree.py index 4331ed10..816d557e 100755 --- a/src/neat/trees/stree.py +++ b/src/neat/trees/stree.py @@ -786,10 +786,8 @@ def get_nodes_in_subtree(self, ref_node, subtree_root=None): else: subtree_nodes = [ref_node] # both input nodes are the same else: - raise ValueError( - "|subtree_root| not in path from |ref_node| \ - root" - ) + raise ValueError("|subtree_root| not in path from |ref_node| \ + root") return subtree_nodes def sister_leafs(self, node): diff --git a/tests/channel_installer.py b/tests/channel_installer.py index 5cd46ccc..e1216bf2 100644 --- a/tests/channel_installer.py +++ b/tests/channel_installer.py @@ -38,12 +38,14 @@ def load_or_install_neuron_test_channels(): channel_file = os.path.abspath( os.path.join(os.path.dirname(__file__), "channelcollection_for_tests.py") ) + print("check running load_or_install_neuron_test_channels") try: # load_neuron_model() calls will raise a RuntimeError is a compiled model # is loaded multiple times try: # raises FileNotFoundError if not compiled load_neuron_model("multichannel_test") + print("loaded multichannel_test neuron model") except FileNotFoundError: subprocess.call( [ diff --git a/tests/pytest_corenrn_runner.py b/tests/pytest_corenrn_runner.py index 89448136..77aa136b 100644 --- a/tests/pytest_corenrn_runner.py +++ b/tests/pytest_corenrn_runner.py @@ -1,5 +1,6 @@ import pytest import sys + # Manually run the pytest main function print("Running pytest from pytest_corenrn_runner.py ", sys.argv[2:]) -sys.exit(pytest.main(sys.argv[2:])) \ No newline at end of file +sys.exit(pytest.main(sys.argv[2:])) diff --git a/tests/test_cnet.py b/tests/test_cnet.py index d333ce34..b94afcca 100755 --- a/tests/test_cnet.py +++ b/tests/test_cnet.py @@ -30,7 +30,6 @@ from neat.channels.channelcollection import channelcollection - MORPHOLOGIES_PATH_PREFIX = os.path.abspath( os.path.join(os.path.dirname(__file__), "test_morphologies") ) diff --git a/tests/test_compartmenttree.py b/tests/test_compartmenttree.py index 8ce9b9ac..b7b8db4b 100755 --- a/tests/test_compartmenttree.py +++ b/tests/test_compartmenttree.py @@ -34,7 +34,6 @@ import channelcollection_for_tests as channelcollection - MORPHOLOGIES_PATH_PREFIX = os.path.abspath( os.path.join(os.path.dirname(__file__), "test_morphologies") ) diff --git a/tests/test_concmechs.py b/tests/test_concmechs.py index b7fa6b7f..36ea9d7f 100644 --- a/tests/test_concmechs.py +++ b/tests/test_concmechs.py @@ -30,7 +30,6 @@ import pytest - try: import nest import nest.lib.hl_api_exceptions as nestexceptions diff --git a/tests/test_morphtree.py b/tests/test_morphtree.py index b61279b2..300d0021 100755 --- a/tests/test_morphtree.py +++ b/tests/test_morphtree.py @@ -29,7 +29,6 @@ from neat import MorphTree, MorphNode, MorphLoc - MORPHOLOGIES_PATH_PREFIX = os.path.abspath( os.path.join(os.path.dirname(__file__), "test_morphologies") ) diff --git a/tests/test_neurontree.py b/tests/test_neurontree.py index e97d4b11..65b22762 100755 --- a/tests/test_neurontree.py +++ b/tests/test_neurontree.py @@ -279,8 +279,7 @@ def test_active(self, pplot=False): jj += 1 pl.show() - @pytest.mark.parametrize("use_coreneuron", [False, True]) - def test_channel_recording(self, use_coreneuron): + def test_channel_recording(self): self.load_T_tree_test_channel() # set of locations locs = [(1, 0.5), (4, 0.5), (4, 1.0), (5, 0.5), (6, 0.5), (7, 0.5), (8, 0.5)] @@ -288,9 +287,8 @@ def test_channel_recording(self, use_coreneuron): self.neurontree.init_model(t_calibrate=10.0, factor_lambda=10.0) self.neurontree.store_locs(locs, name="rec locs") # run test simulation - res = self.neurontree.run(1.0, record_from_channels=True, use_coreneuron=use_coreneuron) + res = self.neurontree.run(1.0, record_from_channels=True) - print("!!!!!", res["chan"]["test_channel2"]["a00"]) # check if results are stored correctly assert set(res["chan"]["test_channel2"].keys()) == { "a00", @@ -321,7 +319,7 @@ def test_channel_recording(self, use_coreneuron): self.neurontree.init_model(t_calibrate=100.0, factor_lambda=10.0) self.neurontree.store_locs(locs, name="rec locs") # run test simulation - res = self.neurontree.run(10.0, record_from_channels=True, use_coreneuron=use_coreneuron) + res = self.neurontree.run(10.0, record_from_channels=True) # check if results are stored correctly assert set(res["chan"]["test_channel2"].keys()) == { "a00", @@ -352,7 +350,7 @@ def test_channel_recording(self, use_coreneuron): assert res["chan"]["test_channel2"]["a11"].shape == (n_loc, n_step) assert res["chan"]["test_channel2"]["p_open"].shape == (n_loc, n_step) - def test_recording_timestep(self,use_coreneuron=False): + def test_recording_timestep(self): self.load_T_tree_test_channel() # set of locations locs = [(1, 0.5), (4, 0.5), (4, 1.0), (5, 0.5), (6, 0.5), (7, 0.5), (8, 0.5)] @@ -368,7 +366,7 @@ def test_recording_timestep(self,use_coreneuron=False): self.neurontree.init_model(t_calibrate=10.0, dt=0.1, factor_lambda=10.0) self.neurontree.store_locs(locs, name="rec locs") res2 = self.neurontree.run( - 10.0, downsample=1, dt_rec=1.0, record_from_channels=True, use_coreneuron=use_coreneuron + 10.0, downsample=1, dt_rec=1.0, record_from_channels=True ) self.neurontree.delete_model() @@ -838,7 +836,6 @@ def test_impedance_properties_2(self): assert np.allclose(z_mat_sim, z_mat_comp) - class TestStimuli: def _create_ball(self): """ @@ -860,7 +857,7 @@ def test_i_clamp(self, pplot=False): # parameter setup dt = 0.1 tmax = 1000.0 - t_calibrate = 50. + t_calibrate = 50.0 amp = 0.01 self._create_ball() @@ -869,27 +866,27 @@ def test_i_clamp(self, pplot=False): self.tree.add_i_clamp( loc=(1, 0.5), delay=3 * tmax / 10, - dur= 4 * tmax / 10., + dur=4 * tmax / 10.0, amp=0.01, ) - res = self.tree.run(tmax, record_from_iclamps=True, use_coreneuron=True) - print(res['v_m']) + res = self.tree.run(tmax, record_from_iclamps=True) + print(res["v_m"]) if pplot: import matplotlib.pyplot as plt plt.figure() - plt.plot(res['t'], res['i_clamp'][0,:], label='I Clamp') - plt.plot(res['t'], res['v_m'][0,:], label='V Membrane') - plt.xlabel('Time [ms]') + plt.plot(res["t"], res["i_clamp"][0, :], label="I Clamp") + plt.plot(res["t"], res["v_m"][0, :], label="V Membrane") + plt.xlabel("Time [ms]") plt.legend() plt.show() def _voltage_statistics(self, delta_t, mu_ou, sigma_ou, tau_ou): """ Stationary autocovariance C_v(Δ). - + Parameters ---------- delta_t : array_like @@ -901,30 +898,31 @@ def _voltage_statistics(self, delta_t, mu_ou, sigma_ou, tau_ou): mu_ou : float OU mean [nA] """ - mu_ou *= 1e-3 # nA to uA - sigma_ou *= 1e-3 # nA to uA + mu_ou *= 1e-3 # nA to uA + sigma_ou *= 1e-3 # nA to uA soma = self.tree[1] - tau = soma.c_m / soma.currents['L'][0] * 1e3 # s to ms - e_l = soma.currents['L'][1] + tau = soma.c_m / soma.currents["L"][0] * 1e3 # s to ms + e_l = soma.currents["L"][1] ca = soma.c_m * 4 * np.pi * soma.R**2 * 1e-8 # um^2 to cm^2 # voltage mean - mean = e_l + tau * mu_ou / ca # ms * V / s = mV + mean = e_l + tau * mu_ou / ca # ms * V / s = mV delta_t = np.abs(np.asarray(delta_t)) - prefactor = sigma_ou**2 / ca**2 # (uA / uF)^2 = (V / s)^2 - scale = (tau**2 * tau_ou) / (tau_ou**2 - tau**2) + prefactor = sigma_ou**2 / ca**2 # (uA / uF)^2 = (V / s)^2 + scale = (tau**2 * tau_ou) / (tau_ou**2 - tau**2) # autocovarianvce - autocov = prefactor * scale * ( - tau_ou * np.exp(-delta_t / tau_ou) - - tau * np.exp(-delta_t / tau) - ) # (V / s)^2 * ms^2 = mV^2 + autocov = ( + prefactor + * scale + * (tau_ou * np.exp(-delta_t / tau_ou) - tau * np.exp(-delta_t / tau)) + ) # (V / s)^2 * ms^2 = mV^2 return mean, autocov - + def _autocovariance(self, x, max_lag=None): """ Empirical autocovariance of a 1D time series. @@ -952,7 +950,7 @@ def _autocovariance(self, x, max_lag=None): cov = np.empty(max_lag + 1) for k in range(max_lag + 1): - cov[k] = np.dot(x[:N-k], x[k:]) / (N - k) + cov[k] = np.dot(x[: N - k], x[k:]) / (N - k) lags = np.arange(max_lag + 1) return lags, cov @@ -987,7 +985,7 @@ def _autocovariance_fft(self, x, max_lag=None): fx = np.fft.fft(x, n=nfft) acf = np.fft.ifft(fx * np.conjugate(fx)).real - acf = acf[:max_lag + 1] + acf = acf[: max_lag + 1] normalization = N - np.arange(max_lag + 1) return np.arange(max_lag + 1), acf / normalization @@ -996,7 +994,7 @@ def test_ou_processes(self, pplot=False): # parameter setup dt = 0.1 tmax = 10000.0 - t_calibrate = 50. + t_calibrate = 50.0 tau_ou = 5.0 sigma_ou = 0.005 mu_ou = 0.05 @@ -1009,51 +1007,59 @@ def test_ou_processes(self, pplot=False): mean=mu_ou, stdev=sigma_ou, tau=tau_ou, - delay=-t_calibrate / 2., - dur=tmax + t_calibrate / 2., + delay=-t_calibrate / 2.0, + dur=tmax + t_calibrate / 2.0, seed=46, ) res = self.tree.run(tmax, record_from_iclamps=True) - mu_ou_empirical = np.mean(res['i_clamp'][0,:]) - cov_ou_empirical = np.cov(res['i_clamp'][0,:]) + mu_ou_empirical = np.mean(res["i_clamp"][0, :]) + cov_ou_empirical = np.cov(res["i_clamp"][0, :]) print(f"OU mean: exact={mu_ou:.10f} nA, empirical={mu_ou_empirical:.10f} nA") - print(f"OU variance: exact={sigma_ou**2:.10f} nA^2, empirical={cov_ou_empirical:.10f} nA^2") - + print( + f"OU variance: exact={sigma_ou**2:.10f} nA^2, empirical={cov_ou_empirical:.10f} nA^2" + ) + # check current statistics assert np.abs(mu_ou_empirical - mu_ou) / np.abs(mu_ou) < 0.05 assert np.abs(cov_ou_empirical - sigma_ou**2) / sigma_ou**2 < 0.05 mu_exact, cov_exact = self._voltage_statistics( - delta_t=res['t'], + delta_t=res["t"], mu_ou=mu_ou, sigma_ou=sigma_ou, tau_ou=tau_ou, ) - mu_empirical = np.mean(res['v_m'][0, :]) - cov_np = np.cov(res['v_m'][0, :]) - _, cov_empirical = self._autocovariance_fft(res['v_m'][0, :]) + mu_empirical = np.mean(res["v_m"][0, :]) + cov_np = np.cov(res["v_m"][0, :]) + _, cov_empirical = self._autocovariance_fft(res["v_m"][0, :]) - print(f"Voltage mean: exact={mu_exact:.10f} mV, empirical={mu_empirical:.10f} mV") - print(f"Voltage variance: exact={cov_exact[0]:.10f} mV^2, empirical={cov_np:.10f} mV^2") + print( + f"Voltage mean: exact={mu_exact:.10f} mV, empirical={mu_empirical:.10f} mV" + ) + print( + f"Voltage variance: exact={cov_exact[0]:.10f} mV^2, empirical={cov_np:.10f} mV^2" + ) # check voltage statistics assert np.abs(mu_empirical - mu_exact) / np.abs(mu_exact) < 0.05 assert np.abs(cov_np - cov_exact[0]) / cov_exact[0] < 0.1 - assert np.mean(np.abs(cov_empirical[:500] - cov_exact[:500]) / cov_exact[0]) < 0.05 + assert ( + np.mean(np.abs(cov_empirical[:500] - cov_exact[:500]) / cov_exact[0]) < 0.05 + ) if pplot: pl.figure(figsize=(12, 5)) ax = pl.subplot(1, 3, 1) - ax.plot(res['t'], res['v_m'][0, :]) + ax.plot(res["t"], res["v_m"][0, :]) ax.axhline(mu_exact) - ax.axhline(mu_empirical, color='orange') + ax.axhline(mu_empirical, color="orange") ax = pl.subplot(1, 3, 2) - ax.plot(res['t'][:500], cov_exact[:500], label='Exact') - ax.plot(res['t'][:500], cov_empirical[:500], label='Empirical') - ax.axhline(cov_np, color='gray', linestyle='--', label='Empirical variance') + ax.plot(res["t"][:500], cov_exact[:500], label="Exact") + ax.plot(res["t"][:500], cov_empirical[:500], label="Empirical") + ax.axhline(cov_np, color="gray", linestyle="--", label="Empirical variance") ax.legend(loc=0) pl.show() @@ -1062,16 +1068,23 @@ def debug_print(pstr): print(pstr) try: - print(os.listdir(os.path.join(neat.__path__[0], "simulations/neuron/tmp/multichannel_test/arm64"))) + print( + os.listdir( + os.path.join( + neat.__path__[0], "simulations/neuron/tmp/multichannel_test/arm64" + ) + ) + ) except FileNotFoundError as e: print(e) + if __name__ == "__main__": # sys.exit(pytest.main(sys.argv[1:])) tn = TestNeuron() # tn.test_passive(pplot=True) # tn.test_active(pplot=True) - tn.test_channel_recording(use_coreneuron=True) + tn.test_channel_recording() # tn.test_recording_timestep() # trn = TestReducedNeuron() @@ -1082,4 +1095,4 @@ def debug_print(pstr): # ts = TestStimuli() # ts.test_i_clamp(pplot=True) - # ts.test_ou_processes() \ No newline at end of file + # ts.test_ou_processes() diff --git a/tests/test_sovtree.py b/tests/test_sovtree.py index ff342f85..96074f70 100755 --- a/tests/test_sovtree.py +++ b/tests/test_sovtree.py @@ -27,7 +27,6 @@ from neat import SOVTree, SOVNode, Kernel, GreensTree import neat.tools.kernelextraction as ke - MORPHOLOGIES_PATH_PREFIX = os.path.abspath( os.path.join(os.path.dirname(__file__), "test_morphologies") ) From 8eb34e353de99c9edc15f98da5d5eb4599a306d2 Mon Sep 17 00:00:00 2001 From: "w.wybo" Date: Fri, 23 Jan 2026 17:44:16 +0100 Subject: [PATCH 11/18] fix concmech test --- tests/test_concmechs.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_concmechs.py b/tests/test_concmechs.py index 36ea9d7f..b8822164 100644 --- a/tests/test_concmechs.py +++ b/tests/test_concmechs.py @@ -338,7 +338,6 @@ def _simulate( record_concentrations=["ca"], record_currents=rec_currs, spike_rec_loc=rec_locs[0], - use_coreneuron=True, ) return res From a676cf1cfad91992c49698677c2b950c24a6f287 Mon Sep 17 00:00:00 2001 From: "w.wybo" Date: Fri, 13 Feb 2026 10:40:39 +0100 Subject: [PATCH 12/18] remove special wrapper script from local bin when uninstalling models --- src/neat/actions/install.py | 9 +++------ src/neat/actions/uninstall.py | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/neat/actions/install.py b/src/neat/actions/install.py index 4180cc38..c582e2a7 100644 --- a/src/neat/actions/install.py +++ b/src/neat/actions/install.py @@ -325,11 +325,6 @@ def create_nrnspecial_wrapper( print(f" Add to your ~/.bashrc or ~/.zshrc:") print(f' export PATH="{install_dir}:$PATH"') - print() - print("Usage:") - print(f" {wrapper_name} my_script.py") - print() - return wrapper_path except Exception as e: @@ -395,7 +390,9 @@ def _compile_neuron(model_name, path_neat, channels, path_neuronresource=None): print( f"\n------------------------------\n" f"The compiled .mod-files can be loaded into neuron using:\n" - f' neat.load_neuron_model("{model_name}")\n' + f' neat.load_neuron_model("{model_name}")\n\n' + f"If you want to use the model with CoreNEURON, use:\n" + f" python_with_{model_name} my_script.py \n" f"------------------------------\n" ) diff --git a/src/neat/actions/uninstall.py b/src/neat/actions/uninstall.py index 25ba8653..421f417b 100644 --- a/src/neat/actions/uninstall.py +++ b/src/neat/actions/uninstall.py @@ -20,8 +20,11 @@ # along with NEST. If not, see . import os +import pathlib import shutil +from .install import get_local_bin_dir + def _check_model_name(model_name): if not len(model_name) > 0: @@ -36,6 +39,19 @@ def _check_model_name(model_name): ) +def remove_nrnspecial_wrapper(model_name): + wrapper_name = pathlib.Path(f"python_with_{model_name}") + + local_bin_dir = get_local_bin_dir() + wrapper_path = local_bin_dir / wrapper_name + + try: + os.remove(wrapper_path) + print(f"> Uninstalled {wrapper_name} from {local_bin_dir}") + except FileNotFoundError as e: + print(f"> {wrapper_name} not found in {local_bin_dir}, nothing to uninstall.") + + def _uninstall_models( *model_names, path_neat, @@ -67,6 +83,7 @@ def _uninstall_models( print(f"> {model_name} not found in nest, nothing to uninstall.") if "neuron" in simulators: + remove_nrnspecial_wrapper(model_name) try: path_neuron = os.path.join( path_neat, "simulations/", f"neuron/tmp/{model_name}/" From dac9b83cfad81a09c31adb052d28eeb188e9a378 Mon Sep 17 00:00:00 2001 From: "w.wybo" Date: Fri, 13 Feb 2026 12:11:41 +0100 Subject: [PATCH 13/18] fix tests for coreneuron, add workflow job to CI that tests coreneuron --- .github/workflows/neat-build.yml | 34 +++++++++++++++++- src/neat/__init__.py | 1 + src/neat/actions/install.py | 3 -- src/neat/simulations/neuron/neuronmodel.py | 15 +++++--- tests/pytest_corenrn_runner.py | 42 ++++++++++++++++++++-- tests/pytest_corenrn_runner.sh | 23 ++++++++++++ tests/test_nesttree.py | 3 +- tests/test_neurontree.py | 17 +++++++-- 8 files changed, 124 insertions(+), 14 deletions(-) create mode 100644 tests/pytest_corenrn_runner.sh diff --git a/.github/workflows/neat-build.yml b/.github/workflows/neat-build.yml index 674ab766..a8b01dff 100644 --- a/.github/workflows/neat-build.yml +++ b/.github/workflows/neat-build.yml @@ -114,4 +114,36 @@ jobs: # Unit tests - name: Run tests run: | - pytest -s -o norecursedirs='*' -o log_cli=true -o log_cli_level="DEBUG" tests \ No newline at end of file + pytest -s -o norecursedirs='*' -o log_cli=true -o log_cli_level="DEBUG" tests + + build_and_test_corenrn: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [ '3.13' ] + + fail-fast: false + + steps: + # Checkout the repository contents + - name: Checkout NEAT code + uses: actions/checkout@v4 + + # Setup Python version + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + # Install NEAT + - name: Install NEAT + run: | + pip install setuptools + pip install . + + # Unit tests + - name: Run tests + run: | + sh pytest_corenrn_runner.sh + + diff --git a/src/neat/__init__.py b/src/neat/__init__.py index 34c56994..8d2c6c0a 100755 --- a/src/neat/__init__.py +++ b/src/neat/__init__.py @@ -59,6 +59,7 @@ from .simulations.neuron.neuronmodel import NeuronSimTree from .simulations.neuron.neuronmodel import NeuronSimNode from .simulations.neuron.neuronmodel import NeuronCompartmentTree + from .simulations.neuron.neuronmodel import check_for_coreneuron except ModuleNotFoundError: warnings.warn("NEURON not available", UserWarning) diff --git a/src/neat/actions/install.py b/src/neat/actions/install.py index c582e2a7..85e10d98 100644 --- a/src/neat/actions/install.py +++ b/src/neat/actions/install.py @@ -283,9 +283,6 @@ def create_nrnspecial_wrapper( exit 1 fi -# Export environment variable to indicate NEURON was launched with special -export NEURON_USING_SPECIAL=1 - # Run special with -python flag and pass all arguments exec "$SPECIAL_PATH" -python "$@" """ diff --git a/src/neat/simulations/neuron/neuronmodel.py b/src/neat/simulations/neuron/neuronmodel.py index d2808324..88e798b5 100755 --- a/src/neat/simulations/neuron/neuronmodel.py +++ b/src/neat/simulations/neuron/neuronmodel.py @@ -32,13 +32,16 @@ from ...trees.compartmenttree import CompartmentTree from ...factorydefaults import DefaultPhysiology + +def check_for_coreneuron(): + return "CORENRN_PYTHONEXE" in os.environ + try: import neuron - if ( - "NEURON_USING_SPECIAL" in os.environ - and os.environ["NEURON_USING_SPECIAL"] == "1" - ): + if check_for_coreneuron(): + # if we are running with the nrnspecial wrapper, we assume + # that CoreNEURON is available and should be used from neuron import coreneuron coreneuron.enable = True @@ -382,6 +385,10 @@ def delete_model(self): self.vecstims = [] self.netcons = [] self.vecs = [] + try: + self.pc.done() + except AttributeError as e: + pass self.pc = None self.gid = None self.store_locs([{"node": 1, "x": 0.0}], "rec locs") diff --git a/tests/pytest_corenrn_runner.py b/tests/pytest_corenrn_runner.py index 77aa136b..e297ba22 100644 --- a/tests/pytest_corenrn_runner.py +++ b/tests/pytest_corenrn_runner.py @@ -1,6 +1,44 @@ +# -*- coding: utf-8 -*- +# +# test_compartmentfitter.py +# +# This file is part of NEST. +# +# Copyright (C) 2004 The NEST Initiative +# +# NEST is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# NEST is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with NEST. If not, see . + +""" +Launch this script as +``` +python_with_multichannel_test pytest_corenrn_runner.py +``` +to test coreneuron. +""" + import pytest import sys +args = [ + "-s", # Disable capturing (shortcut for --capture=no) + "-o", "norecursedirs=*", # Override: don't recurse into any directories + "-o", "log_cli=true", # Override: enable CLI logging + "-o", "log_cli_level=DEBUG", # Override: set log level + "." # The target directory +] + # Manually run the pytest main function -print("Running pytest from pytest_corenrn_runner.py ", sys.argv[2:]) -sys.exit(pytest.main(sys.argv[2:])) +print(f"Running pytest from {sys.argv[2]}") +exit_code = pytest.main(args)#"test_neurontree.py"]) +sys.exit(exit_code) \ No newline at end of file diff --git a/tests/pytest_corenrn_runner.sh b/tests/pytest_corenrn_runner.sh new file mode 100644 index 00000000..505a354b --- /dev/null +++ b/tests/pytest_corenrn_runner.sh @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# +# test_compartmentfitter.py +# +# This file is part of NEST. +# +# Copyright (C) 2004 The NEST Initiative +# +# NEST is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# NEST is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with NEST. If not, see . + +neatmodels install multichannel_test -s neuron -p channelcollection_for_tests.py +python_with_multichannel_test pytest_corenrn_runner.py diff --git a/tests/test_nesttree.py b/tests/test_nesttree.py index 97da32bd..cff9a767 100644 --- a/tests/test_nesttree.py +++ b/tests/test_nesttree.py @@ -30,8 +30,7 @@ import nest import nest.lib.hl_api_exceptions as nestexceptions except ImportError as e: - # pytest.skip("NEST not installed", allow_module_level=True) - pass + pytest.skip("NEST not installed", allow_module_level=True) from neat import PhysTree from neat import CompartmentNode, CompartmentTree diff --git a/tests/test_neurontree.py b/tests/test_neurontree.py index 65b22762..1a9d7cf8 100755 --- a/tests/test_neurontree.py +++ b/tests/test_neurontree.py @@ -32,6 +32,7 @@ from neat import GreensTree from neat import CompartmentNode, CompartmentTree from neat import NeuronSimTree, NeuronCompartmentTree +from neat import check_for_coreneuron import neat.tools.kernelextraction as ke import channelcollection_for_tests as channelcollection @@ -288,6 +289,7 @@ def test_channel_recording(self): self.neurontree.store_locs(locs, name="rec locs") # run test simulation res = self.neurontree.run(1.0, record_from_channels=True) + self.neurontree.delete_model() # check if results are stored correctly assert set(res["chan"]["test_channel2"].keys()) == { @@ -320,6 +322,7 @@ def test_channel_recording(self): self.neurontree.store_locs(locs, name="rec locs") # run test simulation res = self.neurontree.run(10.0, record_from_channels=True) + self.neurontree.delete_model() # check if results are stored correctly assert set(res["chan"]["test_channel2"].keys()) == { "a00", @@ -350,6 +353,10 @@ def test_channel_recording(self): assert res["chan"]["test_channel2"]["a11"].shape == (n_loc, n_step) assert res["chan"]["test_channel2"]["p_open"].shape == (n_loc, n_step) + @pytest.mark.skipif( + check_for_coreneuron(), + reason="Recording timestep can not be customized when running with CoreNEURON", + ) def test_recording_timestep(self): self.load_T_tree_test_channel() # set of locations @@ -872,6 +879,7 @@ def test_i_clamp(self, pplot=False): res = self.tree.run(tmax, record_from_iclamps=True) print(res["v_m"]) + self.tree.delete_model() if pplot: import matplotlib.pyplot as plt @@ -990,6 +998,10 @@ def _autocovariance_fft(self, x, max_lag=None): return np.arange(max_lag + 1), acf / normalization + @pytest.mark.skipif( + check_for_coreneuron(), + reason="OU process appears to cause error with CoreNEURON, needs further investigation", + ) def test_ou_processes(self, pplot=False): # parameter setup dt = 0.1 @@ -1013,6 +1025,7 @@ def test_ou_processes(self, pplot=False): ) res = self.tree.run(tmax, record_from_iclamps=True) + self.tree.delete_model() mu_ou_empirical = np.mean(res["i_clamp"][0, :]) cov_ou_empirical = np.cov(res["i_clamp"][0, :]) @@ -1084,8 +1097,8 @@ def debug_print(pstr): tn = TestNeuron() # tn.test_passive(pplot=True) # tn.test_active(pplot=True) - tn.test_channel_recording() - # tn.test_recording_timestep() + # tn.test_channel_recording() + tn.test_recording_timestep() # trn = TestReducedNeuron() # trn.test_geometry1() From 20adfefdac178f36bcaad5264672bf96eb39569a Mon Sep 17 00:00:00 2001 From: "w.wybo" Date: Fri, 13 Feb 2026 12:18:54 +0100 Subject: [PATCH 14/18] fix workflow issues --- .github/workflows/neat-build.yml | 1 + src/neat/simulations/neuron/neuronmodel.py | 3 ++- tests/pytest_corenrn_runner.py | 2 +- tests/pytest_corenrn_runner.sh | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/neat-build.yml b/.github/workflows/neat-build.yml index a8b01dff..6f000e3a 100644 --- a/.github/workflows/neat-build.yml +++ b/.github/workflows/neat-build.yml @@ -144,6 +144,7 @@ jobs: # Unit tests - name: Run tests run: | + cd tests/ sh pytest_corenrn_runner.sh diff --git a/src/neat/simulations/neuron/neuronmodel.py b/src/neat/simulations/neuron/neuronmodel.py index 88e798b5..c228a6ec 100755 --- a/src/neat/simulations/neuron/neuronmodel.py +++ b/src/neat/simulations/neuron/neuronmodel.py @@ -36,11 +36,12 @@ def check_for_coreneuron(): return "CORENRN_PYTHONEXE" in os.environ + try: import neuron if check_for_coreneuron(): - # if we are running with the nrnspecial wrapper, we assume + # if we are running with the nrnspecial wrapper, we assume # that CoreNEURON is available and should be used from neuron import coreneuron diff --git a/tests/pytest_corenrn_runner.py b/tests/pytest_corenrn_runner.py index e297ba22..08065b4c 100644 --- a/tests/pytest_corenrn_runner.py +++ b/tests/pytest_corenrn_runner.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# test_compartmentfitter.py +# pytest_corenrn_runner.py # # This file is part of NEST. # diff --git a/tests/pytest_corenrn_runner.sh b/tests/pytest_corenrn_runner.sh index 505a354b..acdbe586 100644 --- a/tests/pytest_corenrn_runner.sh +++ b/tests/pytest_corenrn_runner.sh @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# test_compartmentfitter.py +# pytest_corenrn_runner.sh # # This file is part of NEST. # From 46495d216f970b7d813385426c7e6e39fbe092fe Mon Sep 17 00:00:00 2001 From: "w.wybo" Date: Fri, 13 Feb 2026 15:03:51 +0100 Subject: [PATCH 15/18] restrict coreneuron tests --- tests/pytest_corenrn_runner.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/pytest_corenrn_runner.py b/tests/pytest_corenrn_runner.py index 08065b4c..968b6189 100644 --- a/tests/pytest_corenrn_runner.py +++ b/tests/pytest_corenrn_runner.py @@ -35,10 +35,13 @@ "-o", "norecursedirs=*", # Override: don't recurse into any directories "-o", "log_cli=true", # Override: enable CLI logging "-o", "log_cli_level=DEBUG", # Override: set log level - "." # The target directory + "test_neurontree.py", # target tests + "test_compartmentfitter.py", # target tests + "test_compartmenttree.py", # target tests + "test_concmechs.py", # target tests ] # Manually run the pytest main function print(f"Running pytest from {sys.argv[2]}") -exit_code = pytest.main(args)#"test_neurontree.py"]) +exit_code = pytest.main(args)#""]) sys.exit(exit_code) \ No newline at end of file From 7531c37785d03d869751c42b4a258039539ab47d Mon Sep 17 00:00:00 2001 From: "w.wybo" Date: Fri, 13 Feb 2026 15:13:14 +0100 Subject: [PATCH 16/18] fix bug for legacy neuron (<9.0) --- src/neat/actions/install.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/neat/actions/install.py b/src/neat/actions/install.py index 85e10d98..623e9b1b 100644 --- a/src/neat/actions/install.py +++ b/src/neat/actions/install.py @@ -372,7 +372,7 @@ def _compile_neuron(model_name, path_neat, channels, path_neuronresource=None): # # Also keep these for good measure # my_env["MAKEFLAGS"] = "-j1" if int(neuron.__version__.split(".")[0]) < 9: - subprocess.call(["nrnivmodl", "-coreneuron", "mech/"]) # compile all mod files + subprocess.call(["nrnivmodl", "mech/"]) # compile all mod files else: subprocess.call(["nrnivmodl", "-coreneuron", "mech/"]) # compile all mod files @@ -388,7 +388,7 @@ def _compile_neuron(model_name, path_neat, channels, path_neuronresource=None): f"\n------------------------------\n" f"The compiled .mod-files can be loaded into neuron using:\n" f' neat.load_neuron_model("{model_name}")\n\n' - f"If you want to use the model with CoreNEURON, use:\n" + f"If you want to use the compiled .mod-files with CoreNEURON, use:\n" f" python_with_{model_name} my_script.py \n" f"------------------------------\n" ) From 24b907731a51bd1fea670ad91096a9aa2a764a03 Mon Sep 17 00:00:00 2001 From: "w.wybo" Date: Tue, 17 Feb 2026 10:42:19 +0100 Subject: [PATCH 17/18] add neuron spike delivery test --- src/neat/actions/install.py | 14 +------- src/neat/simulations/neuron/neuronmodel.py | 2 +- tests/test_neurontree.py | 39 ++++++++++++++++++++-- 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/src/neat/actions/install.py b/src/neat/actions/install.py index 623e9b1b..7c89ec54 100644 --- a/src/neat/actions/install.py +++ b/src/neat/actions/install.py @@ -358,19 +358,7 @@ def _compile_neuron(model_name, path_neat, channels, path_neuronresource=None): # change to directory where 'mech/' folder is located and compile the mechanisms os.chdir(path_for_neuron_compilation) - # with open(".noindex", "w") as f: # prevent mac from indexing compiled files - # pass - # subprocess.run(["mdutil", "-i", "off", path_for_neuron_compilation]) - # if os.path.exists(f"{platform.machine()}/"): # delete old compiled files if exist - # shutil.rmtree(f"{platform.machine()}/") - print("!!!", os.getcwd()) - # my_env = os.environ.copy() - # # This forces every sub-call to 'make' to append -j1, effectively - # # overriding the -j4 passed by the nrnivmodl wrapper. - # my_env["MAKE"] = "make -j1" - - # # Also keep these for good measure - # my_env["MAKEFLAGS"] = "-j1" + if int(neuron.__version__.split(".")[0]) < 9: subprocess.call(["nrnivmodl", "mech/"]) # compile all mod files else: diff --git a/src/neat/simulations/neuron/neuronmodel.py b/src/neat/simulations/neuron/neuronmodel.py index c228a6ec..b62025dc 100755 --- a/src/neat/simulations/neuron/neuronmodel.py +++ b/src/neat/simulations/neuron/neuronmodel.py @@ -766,7 +766,7 @@ def set_spiketrain(self, syn_index, syn_weight, spike_times): vecstim = h.VecStim() vecstim.play(spks_vec) netcon = h.NetCon(vecstim, self.syns[syn_index], 0, self.dt, syn_weight) - self.pc.cell(self.gid, netcon) + # self.pc.cell(self.gid, netcon) # store the objects self.vecs.append(spks_vec) self.vecstims.append(vecstim) diff --git a/tests/test_neurontree.py b/tests/test_neurontree.py index 1a9d7cf8..b359acb2 100755 --- a/tests/test_neurontree.py +++ b/tests/test_neurontree.py @@ -1076,6 +1076,38 @@ def test_ou_processes(self, pplot=False): ax.legend(loc=0) pl.show() + def test_input_spiketrain(self, pplot=False): + # parameter setup + dt = 0.1 + tmax = 100.0 + t_calibrate = 50.0 + spktms1 = [10.0, 20.0, 40.0] + spktms2 = [15.0, 25.0, 45.0] + + self._create_ball() + self.tree.init_model(t_calibrate=t_calibrate, dt=dt) + self.tree.add_double_exp_synapse( + loc=(1, 0.5), + tau1=.2, tau2=3., e_r=0.0 + ) + self.tree.add_double_exp_synapse( + loc=(1, 0.5), + tau1=.2, tau2=3., e_r=0.0 + ) + self.tree.set_spiketrain(0, .01, spktms1) + self.tree.set_spiketrain(1, .005, spktms2) + # syn_idx=0, + # spktms=spktms, + # ) + + res = self.tree.run(tmax) + self.tree.delete_model() + + assert np.max(res['v_m'][0]) > res["v_m"][0, 0] + .1 # check for depolarization + + if pplot: + pl.plot(res["t"], res["v_m"][0, :]) + pl.show() def debug_print(pstr): @@ -1094,11 +1126,11 @@ def debug_print(pstr): if __name__ == "__main__": # sys.exit(pytest.main(sys.argv[1:])) - tn = TestNeuron() + # tn = TestNeuron() # tn.test_passive(pplot=True) # tn.test_active(pplot=True) # tn.test_channel_recording() - tn.test_recording_timestep() + # tn.test_recording_timestep() # trn = TestReducedNeuron() # trn.test_geometry1() @@ -1106,6 +1138,7 @@ def debug_print(pstr): # trn.test_geometry2() # trn.test_impedance_properties_2() - # ts = TestStimuli() + ts = TestStimuli() # ts.test_i_clamp(pplot=True) # ts.test_ou_processes() + ts.test_input_spiketrain(pplot=True) \ No newline at end of file From 8a0c25abe61c7b4482ad76dd43afdf2c3fe951c5 Mon Sep 17 00:00:00 2001 From: "w.wybo" Date: Fri, 20 Feb 2026 16:08:01 +0100 Subject: [PATCH 18/18] adjust timers --- src/neat/simulations/neuron/neuronmodel.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/neat/simulations/neuron/neuronmodel.py b/src/neat/simulations/neuron/neuronmodel.py index b62025dc..19f03072 100755 --- a/src/neat/simulations/neuron/neuronmodel.py +++ b/src/neat/simulations/neuron/neuronmodel.py @@ -985,18 +985,20 @@ def _rec_coreneuron_compatible(vec, var, dt_rec): # simulate if pprint: print(">>> Simulating the NEURON model for " + str(t_max) + " ms. <<<") - start = time.process_time() if not USE_CORENEURON: + start = time.process_time() h.finitialize(self.v_init) h.continuerun(t_max + self.t_calibrate) + stop = time.process_time() else: h.CVode().active(0) h.cvode.cache_efficient(1) coreneuron.enable = True + start = time.process_time() h.finitialize(self.v_init) - self.pc.psolve(t_max + self.t_calibrate) # Solve for 100 ms + self.pc.psolve(t_max + self.t_calibrate) + stop = time.process_time() - stop = time.process_time() if pprint: print(">>> Elapsed time: " + str(stop - start) + " seconds. <<<") runtime = stop - start