From 95cc05b38b96e256f95af583fea4124777161843 Mon Sep 17 00:00:00 2001 From: nrnhines Date: Mon, 30 Sep 2024 17:54:39 -0400 Subject: [PATCH] Test for PR 3055 (#3081) # fix a few problems with PR 3055. * For bitset single bit, first reset, then set the bit. * Add another mod file to neurondemo that WRITE cai. * Replace // LCOV_EXCL_END with // LCOV_EXCL_STOP --------- Co-authored-by: wthun Co-authored-by: Luc Grosheintz --- share/demo/release/capmpr.mod | 90 ++++++++++++++++++++ src/ivoc/apwindow.cpp | 2 +- src/ivoc/pwman.cpp | 2 +- src/nrnoc/eion.cpp | 7 +- src/sundials/cvodes/cvodes.c | 2 +- test/hoctests/tests/test_eion_cover.py | 62 ++++++++++++++ test/hoctests/tests/test_neurondemo.py | 113 +++++++++++++++++++++++++ 7 files changed, 272 insertions(+), 6 deletions(-) create mode 100644 share/demo/release/capmpr.mod create mode 100644 test/hoctests/tests/test_eion_cover.py diff --git a/share/demo/release/capmpr.mod b/share/demo/release/capmpr.mod new file mode 100644 index 0000000000..40abd43d13 --- /dev/null +++ b/share/demo/release/capmpr.mod @@ -0,0 +1,90 @@ +: capump.mod plus a "reservoir" used to initialize cai to desired concentrations + +UNITS { + (mM) = (milli/liter) + (mA) = (milliamp) + (um) = (micron) + (mol) = (1) + PI = (pi) (1) + FARADAY = (faraday) (coulomb) +} + +NEURON { + SUFFIX capmpr + USEION ca READ cao, cai WRITE cai, ica + GLOBAL k1, k2, k3, k4 + GLOBAL car, tau +} + +STATE { + pump (mol/cm2) + pumpca (mol/cm2) + cai (mM) +} + +PARAMETER { + car = 5e-5 (mM) : ca in reservoir, used to initialize cai to desired concentrations + tau = 1e9 (ms) : rate of equilibration between cai and car + + k1 = 5e8 (/mM-s) + k2 = .25e6 (/s) + k3 = .5e3 (/s) + k4 = 5e0 (/mM-s) + + pumpdens = 3e-14 (mol/cm2) +} + +CONSTANT { + volo = 1 (liter) +} + +ASSIGNED { + diam (um) + cao (mM) + + ica (mA/cm2) + ipump (mA/cm2) + ipump_last (mA/cm2) + voli (um3) + area1 (um2) + c1 (1+8 um5/ms) + c2 (1-10 um2/ms) + c3 (1-10 um2/ms) + c4 (1+8 um5/ms) +} + +BREAKPOINT { + SOLVE pmp METHOD sparse + ipump_last = ipump + ica = ipump +} + +KINETIC pmp { + COMPARTMENT voli {cai} + COMPARTMENT (1e10)*area1 {pump pumpca} + COMPARTMENT volo*(1e15) {cao car} + + ~ car <-> cai (1(um3)/tau,1(um3)/tau) + ~ cai + pump <-> pumpca (c1,c2) + ~ pumpca <-> pump + cao (c3,c4) + + : note that forward flux here is the outward flux + ipump = (1e-4)*2*FARADAY*(f_flux - b_flux)/area1 + + : ipump_last vs ipump needed because of STEADYSTATE calculation + ~ cai << (-(ica - ipump_last)*area1/(2*FARADAY)*(1e4)) + + CONSERVE pump + pumpca = (1e10)*area1*pumpdens +} + +INITIAL { + :cylindrical coordinates; actually vol and area1/unit length + voli = PI*(diam/2)^2 * 1(um) + area1 = 2*PI*(diam/2) * 1(um) + c1 = (1e7)*area1 * k1 + c2 = (1e7)*area1 * k2 + c3 = (1e7)*area1 * k3 + c4 = (1e7)*area1 * k4 + + SOLVE pmp STEADYSTATE sparse +} diff --git a/src/ivoc/apwindow.cpp b/src/ivoc/apwindow.cpp index 71c07bcf36..e5afea2fc2 100644 --- a/src/ivoc/apwindow.cpp +++ b/src/ivoc/apwindow.cpp @@ -415,7 +415,7 @@ bool PrintableWindow::receive(const Event& e) { reconfigured(); notify(); break; - // LCOV_EXCL_END + // LCOV_EXCL_STOP case MapNotify: if (xplace_) { if (xtop() != xtop_ || xleft() != xleft_) { diff --git a/src/ivoc/pwman.cpp b/src/ivoc/pwman.cpp index 29335af77f..d0ec316a47 100644 --- a/src/ivoc/pwman.cpp +++ b/src/ivoc/pwman.cpp @@ -1477,7 +1477,7 @@ void PrintableWindow::reconfigured() { xmove(x, y); } } -// LCOV_EXCL_END +// LCOV_EXCL_STOP void ViewWindow::reconfigured() { if (!pixres) { diff --git a/src/nrnoc/eion.cpp b/src/nrnoc/eion.cpp index 4c841f4c2c..e8e7657b7e 100644 --- a/src/nrnoc/eion.cpp +++ b/src/nrnoc/eion.cpp @@ -430,9 +430,10 @@ void nrn_check_conc_write(Prop* p_ok, Prop* pion, int i) { } for (k = 0, j = 0; j < n_memb_func; ++j) { if (nrn_is_ion(j)) { - ion_bit_[j] = (1 << k); - ++k; assert(k < max_ions); + ion_bit_[j].reset(); + ion_bit_[j].set(k); + ++k; } } @@ -640,7 +641,7 @@ void second_order_cur(NrnThread* nt) { extern int secondorder; NrnThreadMembList* tml; Memb_list* ml; - int j, i, i2; + int i, i2; constexpr auto c = 3; constexpr auto dc = 4; if (secondorder == 2) { diff --git a/src/sundials/cvodes/cvodes.c b/src/sundials/cvodes/cvodes.c index 8fee9feabb..48a96db636 100755 --- a/src/sundials/cvodes/cvodes.c +++ b/src/sundials/cvodes/cvodes.c @@ -3285,7 +3285,7 @@ static int CVStep(CVodeMem cv_mem) dsm = CVStgrUpdateDsm(cv_mem, dsm, dsmS); } } - // LCOV_EXCL_END + // LCOV_EXCL_STOP /* Everything went fine; exit loop */ break; diff --git a/test/hoctests/tests/test_eion_cover.py b/test/hoctests/tests/test_eion_cover.py new file mode 100644 index 0000000000..73e96aa35c --- /dev/null +++ b/test/hoctests/tests/test_eion_cover.py @@ -0,0 +1,62 @@ +from neuron import h +from neuron.expect_hocerr import expect_err, set_quiet + +set_quiet(0) +from math import isclose + + +def test_nernst(): + assert h.nernst(1, 1, 0) == 0.0 + assert h.nernst(-1, 1, 1) == 1e6 + assert h.nernst(1, -1, 1) == -1e6 + + s = h.Section() + s.insert("hh") + assert isclose(h.nernst("ena", sec=s), 63.55150321937486) + assert isclose(h.nernst("ena", 0.5, sec=s), 63.55150321937486) + assert isclose(h.nernst("nai", sec=s), 17.554820246530547) + assert isclose(h.nernst("nao", sec=s), 79.7501757545304) + expect_err('h.nernst("ina", sec=s)') + + del s + locals() + + +def test_ghk(): + assert isclose(h.ghk(-10, 0.1, 10, 2), -2828.3285716150644) + assert isclose(h.ghk(1e-6, 0.1, 10, 2), -1910.40949510667) + + +def test_ion_style(): + expect_err('h.ion_style("foo")') + + +def test_second_order_cur(): + s = h.Section() + s.insert("hh") + h.secondorder = 2 + h.finitialize(-65) + h.fadvance() + assert isclose(s(0.5).ina, -0.001220053188847315) + h.secondorder = 0 + h.finitialize(-65) + h.fadvance() + assert isclose(s(0.5).ina, -0.0012200571764654333) + + +def test_ion_charge(): + assert h.ion_charge("na_ion") == 1 + expect_err('h.ion_charge("na") == 1') + + +def test_eion_cover(): + test_nernst() + test_ghk() + test_ion_style() + test_second_order_cur() + test_ion_charge() + + +if __name__ == "__main__": + test_eion_cover() + h.topology() diff --git a/test/hoctests/tests/test_neurondemo.py b/test/hoctests/tests/test_neurondemo.py index b3c8d6bcbf..78f88f861e 100644 --- a/test/hoctests/tests/test_neurondemo.py +++ b/test/hoctests/tests/test_neurondemo.py @@ -13,6 +13,7 @@ dir_path = os.path.dirname(os.path.realpath(__file__)) chk = Chk(os.path.join(dir_path, "test_neurondemo.json")) + # Run a command with input into stdin def run(cmd, input): p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, text=True) @@ -47,6 +48,7 @@ def run(cmd, input): quit() """ + # run neurondemo and return the stdout lines between ZZZbegin and ZZZend def neurondemo(extra, input): input = extra + input @@ -172,3 +174,114 @@ def neurondemo(extra, input): chk(key, rich_data) chk.save() + +################################################ +# test_many_ions.py copied below and removed to fix a CI coverage test failure: +# libgcov profiling error:/home/...demo/release/x86_64/mod_func.gcda:overwriting +# an existing profile data with a different timestamp. +# I was unable to reproduce the coverage test failure on my linux desktop by +# experimenting with parallel runs of the distinct tests. Nevertheless, +# CI coverage is successful when the use of the neurondemo shared library is +# serialized into this file. +# I'd rather keep the file and run from here via something like +# run("python test_many_ions.py", "") +# but cmake installs each hoctest file in a separate folder and "python" may not +# be the name of the program we need to run. +########################### +# Test when large number of ion mechanisms exist. + +# Strategy is to create a large number of ion mechanisms before loading +# the neurondemo mod file shared library which will create the ca ion +# along with several mechanisms that write to cai. + +from neuron import h, load_mechanisms +from platform import machine +import sys +import io + + +# return True if name exists in h +def exists(name): + try: + exec("h.%s" % name) + except: + return False + return True + + +# use up a large number of mechanism indices for ions. And want to test beyond +# 1000 which requires change to max_ions in nrn/src/nrnoc/eion.cpp +nion = 250 +ion_indices = [int(h.ion_register("ca%d" % i, 2)) for i in range(1, nion + 1)] +# gain some confidence they exist +assert exists("ca%d_ion" % nion) +assert (ion_indices[-1] - ion_indices[0]) == (nion - 1) +mt = h.MechanismType(0) +assert mt.count() > nion + +# this test depends on the ca ion not existing at this point +assert exists("ca_ion") is False + +# load neurondemo mod files. That will create ca_ion and provide two +# mod files that write cai +# Following Aborts prior to PR#3055 with +# eion.cpp:431: void nrn_check_conc_write(Prop*, Prop*, int): Assertion `k < sizeof(long) * 8' failed. +nrnmechlibpath = "%s/demo/release" % h.neuronhome() +print(nrnmechlibpath) +assert load_mechanisms(nrnmechlibpath) + +# ca_ion now exists and has a mechanism index > nion +assert exists("ca_ion") +mt = h.MechanismType(0) # new mechanisms do not appear in old MechanismType +mt.select("ca_ion") +assert mt.selected() > nion + + +# return stderr output resulting from sec.insert(mechname) +def mech_insert(sec, mechname): + # capture stderr + old_stderr = sys.stderr + sys.stderr = mystderr = io.StringIO() + + sec.insert(mechname) + + sys.stderr = old_stderr + return mystderr.getvalue() + + +# test if can use the high mechanism index cadifpmp (with USEION ... WRITE cai) along with the high mechanims index ca_ion +s = h.Section() +s.insert("cadifpmp") +h.finitialize(-65) +# The ion_style tells whether cai or cao is being written +istyle = int(h.ion_style("ca_ion", sec=s)) +assert istyle & 0o200 # writing cai +assert (istyle & 0o400) == 0 # not writing ca0 +# unfortunately, at present, uninserting does not turn off that bit +s.uninsert("cadifpmp") +assert s.has_membrane("cadifpmp") is False +assert int(h.ion_style("ca_ion", sec=s)) & 0o200 +# nevertheless, on reinsertion, there is no warning, so can't test the +# warning without a second mechanism that WRITE cai +assert mech_insert(s, "cadifpmp") == "" +assert s.has_membrane("cadifpmp") +# uninsert again and insert another mechanism that writes. +s.uninsert("cadifpmp") +assert mech_insert(s, "capmpr") == "" # no warning +assert mech_insert(s, "cadifpmp") != "" # now there is a warning. + +# more serious test +# several sections with alternating cadifpmp and capmpr +del s +secs = [h.Section() for i in range(5)] +for i, s in enumerate(secs): + if i % 2: + assert mech_insert(s, "capmpr") == "" + else: + assert mech_insert(s, "cadifpmp") == "" + +# warnings due to multiple mechanisms at same location that WRITE cai +assert mech_insert(secs[2], "capmpr") != "" +assert mech_insert(secs[3], "cadifpmp") != "" + +############################################