From dbec67de129f4e8173c69cb60279947173399a38 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Tue, 16 Jul 2024 14:57:22 +0200 Subject: [PATCH 01/29] Support negative number for limits (#2992) --- src/nmodl/parse1.ypp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nmodl/parse1.ypp b/src/nmodl/parse1.ypp index 37076da3bd..443825a1ea 100755 --- a/src/nmodl/parse1.ypp +++ b/src/nmodl/parse1.ypp @@ -227,7 +227,7 @@ uniton: UNITSON {replacstr($1, "");} ; limits: /*nothing*/ {$$ = stralloc("", (char*)0);} - | LT real ',' real GT + | LT number ',' number GT { Sprintf(buf, "%s %s", STR($2), STR($4)); $$ = stralloc(buf, (char*)0); From f8f9cc649a755545ba2966354ae35e26b2c1811a Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Tue, 16 Jul 2024 15:01:31 +0200 Subject: [PATCH 02/29] Docs: Using LIKWID with NEURON for performance analysis (#2986) Added instructions about how to build NEURON with LIKWID and how to use it with low overhead (see #2983) Co-authored-by: Luc Grosheintz --- docs/install/debug.md | 215 +++++++++++++++++- .../nrn_likwid_presoa_master_comparison.png | Bin 0 -> 55218 bytes 2 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 docs/install/images/nrn_likwid_presoa_master_comparison.png diff --git a/docs/install/debug.md b/docs/install/debug.md index 2ce7ef51fb..725f692576 100644 --- a/docs/install/debug.md +++ b/docs/install/debug.md @@ -579,4 +579,217 @@ It's important to note that higher latencies do not necessarily indicate false s instance, indirect-memory accesses with a strided pattern could lead to increased latencies. One should examine how the memory object is used within a function to determine if false sharing is a potential issue. Additionally, comparing access latencies with scenarios like single-threaded -execution or versions without such issues can provide further insights. \ No newline at end of file +execution or versions without such issues can provide further insights. + +#### Using LIKWID With NEURON + +As described in the previous section, Intel VTune is a powerful tool for node-level performance analysis. +However, we might need alternative options like LIKWID profiling tools in scenarios such as: +1) VTune is not available due to non-Intel CPUs or lack of necessary permissions. +2) We prefer to precisely mark the code regions of interest rather than using a sampling-based approach. +3) The raw events/performance counters shown by VTune are overwhelming, and we want high-level metrics that typically used in HPC/Scientific Computing. +4) Or simply we want to cross-check VTune's results with another tool like LIKWID to ensure there are no false positives. + +LIKWID offers a comprehensive set of tools for performance measurement on HPC platforms. It supports Intel, AMD, and ARM CPUs. +However, as it is not vendor-specific, it may lack support for specific CPUs (e.g., Intel Alder Lake). Despite this, LIKWID is still a valuable tool. +Let’s quickly see how we can use LIKWID with NEURON. + +We assume LIKWID is installed with the necessary permissions (via `accessDaemon` or Linux `perf` mode). +Usage of LIKWID is covered in multiple other tutorials like [this](https://github.com/RRZE-HPC/likwid/wiki/TutorialStart) and [this](https://hpc.fau.de/research/tools/likwid/). +Here, we focus on its usage with NEURON. + +LIKWID can measure performance counters on any binary like NEURON. For example, in the execution below, +we measure metrics from `FLOPS_DP` LIKWID group, i.e., double precision floating point related metrics: + +```console +$ likwid-perfctr -C 0 -g FLOPS_DP ./x86_64/special -python test.py +-------------------------------------------------------------------------------- +CPU name: Intel(R) Xeon(R) Gold 6140 CPU @ 2.30GHz +... +-------------------------------------------------------------------------------- +Group 1: FLOPS_DP ++------------------------------------------+---------+------------+ +| Event | Counter | HWThread 0 | ++------------------------------------------+---------+------------+ +| INSTR_RETIRED_ANY | FIXC0 | 8229299612 | +| CPU_CLK_UNHALTED_CORE | FIXC1 | 3491693048 | +| CPU_CLK_UNHALTED_REF | FIXC2 | 2690601456 | +| FP_ARITH_INST_RETIRED_128B_PACKED_DOUBLE | PMC0 | 1663849 | +| FP_ARITH_INST_RETIRED_SCALAR_DOUBLE | PMC1 | 740794589 | +| FP_ARITH_INST_RETIRED_256B_PACKED_DOUBLE | PMC2 | 0 | +| FP_ARITH_INST_RETIRED_512B_PACKED_DOUBLE | PMC3 | - | ++------------------------------------------+---------+------------+ + ++----------------------+------------+ +| Metric | HWThread 0 | ++----------------------+------------+ +| Runtime (RDTSC) [s] | 1.4446 | +| Runtime unhalted [s] | 1.5181 | +| Clock [MHz] | 2984.7862 | +| CPI | 0.4243 | +| DP [MFLOP/s] | 515.0941 | +| AVX DP [MFLOP/s] | 0 | +| AVX512 DP [MFLOP/s] | 0 | +| Packed [MUOPS/s] | 1.1517 | +| Scalar [MUOPS/s] | 512.7906 | +| Vectorization ratio | 0.2241 | ++----------------------+------------+ +``` + +In this execution, we see information like the total number of instructions executed and contributions from SSE, AVX2, and AVX-512 instructions. + +But, in the context of NEURON model execution, this is not sufficient because the above measurements summarize the full execution, +including model building and simulation phases. For detailed performance analysis, we need granular information. For example: +1) We want to compare hardware counters for phases like current update (`BREAKPOINT`) vs. state update (`DERIVATIVE`) due to their different properties (memory-bound vs. compute-bound). +2) We might want to analyze the performance of a specific MOD file and it's kernels. + +This is where [LIKWID's Marker API](https://github.com/RRZE-HPC/likwid/wiki/TutorialMarkerC) comes into play. +Currently, Caliper doesn't integrate LIKWID as a service, but NEURON's profiler interface supports enabling LIKWID markers via the same +[Instrumentor API](https://github.com/neuronsimulator/nrn/blob/master/src/coreneuron/utils/profile/profiler_interface.h). + +###### Building NEURON With LIKWID Support + +If LIKWID is already installed correctly with the necessary permissions, enabling LIKWID support into NEURON is not difficult: + +```console +cmake .. \ + -DNRN_ENABLE_PROFILING=ON \ + -DNRN_PROFILER=likwid \ + -DCMAKE_PREFIX_PATH=/share/likwid \ + -DCMAKE_INSTALL_PREFIX=$(pwd)/install \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo +make -j && make install +``` + +Once built this way, LIKWID markers are added for the various simulation phases similar to those shown in Caliper and Vtune section. + +###### Measurement with LIKWID + +Measuring different metrics with LIKWID is easy, as seen earlier. By building LIKWID support via CMake, we now have enabled LIKWID markers +that help us see the performance counters for different phases of simulations. In the example below, we added the `-m` CLI option to enable markers: + +```console +$ likwid-perfctr -C 0 -m -g FLOPS_DP ./x86_64/special -python test.py +... +... +Region psolve, Group 1: FLOPS_DP ++-------------------+------------+ +| Region Info | HWThread 0 | ++-------------------+------------+ +| RDTSC Runtime [s] | 10.688310 | +| call count | 1 | ++-------------------+------------+ + ++------------------------------------------+---------+------------+ +| Event | Counter | HWThread 0 | ++------------------------------------------+---------+------------+ +| INSTR_RETIRED_ANY | FIXC0 | 5496569000 | +| CPU_CLK_UNHALTED_CORE | FIXC1 | 2753500000 | +| CPU_CLK_UNHALTED_REF | FIXC2 | 2111229000 | +| FP_ARITH_INST_RETIRED_128B_PACKED_DOUBLE | PMC0 | 489202 | +| FP_ARITH_INST_RETIRED_SCALAR_DOUBLE | PMC1 | 730000000 | +| FP_ARITH_INST_RETIRED_256B_PACKED_DOUBLE | PMC2 | 0 | +| FP_ARITH_INST_RETIRED_512B_PACKED_DOUBLE | PMC3 | - | ++------------------------------------------+---------+------------+ +... +... +Region state-cdp5StCmod, Group 1: FLOPS_DP ++-------------------+------------+ +| Region Info | HWThread 0 | ++-------------------+------------+ +| RDTSC Runtime [s] | 0.353965 | +| call count | 400 | ++-------------------+------------+ + ++------------------------------------------+---------+------------+ +| Event | Counter | HWThread 0 | ++------------------------------------------+---------+------------+ +| INSTR_RETIRED_ANY | FIXC0 | 2875111000 | +| CPU_CLK_UNHALTED_CORE | FIXC1 | 1057608000 | +| CPU_CLK_UNHALTED_REF | FIXC2 | 810826000 | +| FP_ARITH_INST_RETIRED_128B_PACKED_DOUBLE | PMC0 | 380402 | +| FP_ARITH_INST_RETIRED_SCALAR_DOUBLE | PMC1 | 358722700 | +| FP_ARITH_INST_RETIRED_256B_PACKED_DOUBLE | PMC2 | 0 | +| FP_ARITH_INST_RETIRED_512B_PACKED_DOUBLE | PMC3 | - | ++------------------------------------------+---------+------------+ +... +Region state-Cav2_3, Group 1: FLOPS_DP ++-------------------+------------+ +| Region Info | HWThread 0 | ++-------------------+------------+ +| RDTSC Runtime [s] | 0.002266 | +| call count | 400 | ++-------------------+------------+ +... +... +``` + +Here, we see performance counters for the `psolve` region, which includes the full simulation loop, +and a breakdown into channel-specific kernels like `state-cdp5StCmod` and `state-Cav2_3`. + +###### Avoiding Measurement Overhead with `NRN_PROFILE_REGIONS` + +When profiling with Caliper, careful instrumentation can ensure that measuring execution does not incur significant overhead. +It's important to avoid instrumenting very small code regions to minimize performance impact. +However, with LIKWID, starting and stopping measurement using high-level API like `LIKWID_MARKER_START()` and +`LIKWID_MARKER_STOP()` can introduce relatively high overhead, especially when instrumentation covers small code regions. +This is the case in NEURON as we instrument all simulation phases and individual mechanisms' state and current update kernels. +In small models, such overhead could slow down execution by 10x. + +To avoid this, NEURON provides an environment variable `NRN_PROFILE_REGIONS` to enable profiling for specific code regions. +For example, let's assume we want to understand hardware performance counters for two phases: +- `psolve`: entire simulation phase +- `state-hh`: one specific state update phase where we call `DERIVATIVE` block of the `hh.mod` file + +These names are the same as those instrumented in the code using `Instrumentor::phase` API (see previous step). +We can specify these regions to profile as a *comma-separated list* via the `NRN_PROFILE_REGIONS` environment variable: + +```console +$ export NRN_PROFILE_REGIONS=psolve,state-hh +$ likwid-perfctr -C 0 -m -g FLOPS_DP ./x86_64/special -python test.py +``` + +Now, we should get the performance counter report only for these two regions with relatively small execution overhead: + +```console +Region psolve, Group 1: FLOPS_DP ++-------------------+------------+ +| Region Info | HWThread 0 | ++-------------------+------------+ +| RDTSC Runtime [s] | 10.688310 | +| call count | 1 | ++-------------------+------------+ + ++------------------------------------------+---------+------------+ +| Event | Counter | HWThread 0 | ++------------------------------------------+---------+------------+ +| INSTR_RETIRED_ANY | FIXC0 | 5496569000 | +| CPU_CLK_UNHALTED_CORE | FIXC1 | 2753500000 | +... +... +Region state-hh, Group 1: FLOPS_DP ++-------------------+------------+ +| Region Info | HWThread 0 | ++-------------------+------------+ +| RDTSC Runtime [s] | 0.180081 | +| call count | 400 | ++-------------------+------------+ + ++------------------------------------------+---------+------------+ +| Event | Counter | HWThread 0 | ++------------------------------------------+---------+------------+ +| INSTR_RETIRED_ANY | FIXC0 | 1341553000 | +| CPU_CLK_UNHALTED_CORE | FIXC1 | 540962900 | +... +``` + +> NOTE: Currently, NEURON uses marker APIs `LIKWID_MARKER_START()` and `LIKWID_MARKER_STOP()` from LIKWID. +> We should consider using `LIKWID_MARKER_REGISTER()` API to reduce overhead and prevent incorrect report counts for tiny code regions. + +### Comparing LIKWID Profiles + +Unlike VTune, LIKWID doesn't support direct comparison of profile data. However, the powerful CLI allows us to select specific metrics +for code regions, and since the profile results are provided as text output, comparing results from two runs is straightforward. +For instance, the screenshot below shows FLOPS instructions side by side between two runs: + +![VTune Comparison](images/nrn_likwid_presoa_master_comparison.png) \ No newline at end of file diff --git a/docs/install/images/nrn_likwid_presoa_master_comparison.png b/docs/install/images/nrn_likwid_presoa_master_comparison.png new file mode 100644 index 0000000000000000000000000000000000000000..5f5a45f4e7c2a25368a2527e4e3cc9275d13d6d8 GIT binary patch literal 55218 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 W@|Ccp4>1h*Cnu#W`AOU`;C}&~;)1UL literal 0 HcmV?d00001 From 7bc5789fc9f0701733787ae54697b14f75c979dd Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Tue, 16 Jul 2024 22:29:42 +0200 Subject: [PATCH 03/29] Add ubuntu-24.04 CI (#2995) --- .github/workflows/neuron-ci.yml | 4 ++-- docs/install/install_instructions.md | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/neuron-ci.yml b/.github/workflows/neuron-ci.yml index dec6235955..a355ca18cf 100644 --- a/.github/workflows/neuron-ci.yml +++ b/.github/workflows/neuron-ci.yml @@ -70,7 +70,7 @@ jobs: # TODO: address-leak is the dream, but there are many problems, # including external ones from the MPI implementations sanitizer: address - - os: ubuntu-22.04 + - os: ubuntu-24.04 config: build_mode: cmake # Cannot use a non-instrumented OpenMP with TSan, and we don't @@ -141,7 +141,7 @@ jobs: run: | sudo apt-get install build-essential ccache libopenmpi-dev \ libmpich-dev libx11-dev libxcomposite-dev mpich ninja-build \ - openmpi-bin flex libfl-dev bison + openmpi-bin flex libfl-dev bison libreadline-dev # The sanitizer builds use ubuntu 22.04 if [[ "${{matrix.os}}" == "ubuntu-20.04" ]]; then sudo apt-get install g++-7 g++-8 diff --git a/docs/install/install_instructions.md b/docs/install/install_instructions.md index 57949a8781..209509b371 100644 --- a/docs/install/install_instructions.md +++ b/docs/install/install_instructions.md @@ -290,7 +290,8 @@ install dependencies. For example, on Ubuntu: sudo apt-get update sudo apt-get install -y bison cmake flex git \ libncurses-dev libopenmpi-dev libx11-dev \ - libxcomposite-dev openmpi-bin python3-dev + libxcomposite-dev openmpi-bin python3-dev \ + libreadline-dev # for python dependencies pip install -r nrn_requirements.txt ``` From 0b8b96f634a3b3ed38b9968b70e999f9102932bb Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 17 Jul 2024 13:03:06 +0200 Subject: [PATCH 04/29] Improve comments near nrn_promote. (#2967) --- src/nrnoc/eion.cpp | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/nrnoc/eion.cpp b/src/nrnoc/eion.cpp index 908ff06683..7933fabe29 100644 --- a/src/nrnoc/eion.cpp +++ b/src/nrnoc/eion.cpp @@ -392,7 +392,13 @@ double nrn_nernst_coef(int type) { } /* -It is generally an error for two models to WRITE the same concentration +It is generally an error for two models to WRITE the same concentration. + +This functions checks that there's no write conflict; and warns if it detects +one. It also sets respective write flag in the style of the ion. + +The argument `i` specifies which concentration is being written to. It's 0 for +exterior; and 1 for interior. */ void nrn_check_conc_write(Prop* p_ok, Prop* pion, int i) { static long *chk_conc_, *ion_bit_, size_; @@ -546,13 +552,13 @@ void nrn_promote(Prop* p, int conc, int rev) { } /*the bitmap is -03 concentration unused, nrnocCONST, DEP, STATE -04 initialize concentrations -030 reversal potential unused, nrnocCONST, DEP, STATE -040 initialize reversal potential -0100 calc reversal during fadvance -0200 ci being written by a model -0400 co being written by a model + 03 concentration unused, nrnocCONST, DEP, STATE + 04 initialize concentrations + 030 reversal potential unused, nrnocCONST, DEP, STATE + 040 initialize reversal potential +0100 calc reversal during fadvance +0200 ci being written by a model +0400 co being written by a model */ /* Must be called prior to any channels which update the currents */ From 2c90971c15b3e55c1e091c7271ed6b3df5eecb03 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Wed, 17 Jul 2024 22:55:26 +0200 Subject: [PATCH 05/29] Add -h / --help option for nrnivmodl (#2996) --- bin/nrnivmodl.in | 13 ++++++++++++- src/mswin/bin/nrnivmodl.bat | 13 +++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/bin/nrnivmodl.in b/bin/nrnivmodl.in index 6ba80d444e..2dc1d5aaf4 100755 --- a/bin/nrnivmodl.in +++ b/bin/nrnivmodl.in @@ -81,8 +81,19 @@ while [ "$1" ] ; do UserNMODLFLAGS="$UserNMODLFLAGS $2" shift shift;; + -h|--help) + echo "Usage: nrnivmodl [options] [mod files or directories with mod files]" + echo "Options:" + echo " -coreneuron Compile MOD files for CoreNEURON using nrnivmodl-core." + echo " -incflags \"include flags\" Extra include flags and paths when MOD (C++) files are compiled." + echo " -loadflags \"link flags\" Extra link flags, paths, and libraries when MOD (C++) files are linked." + echo " -nmodl /path/to/nmodl Path to the new NMODL transpiler for MOD files (pre-alpha, development use only)." + echo " -nmodlflags \"CLI flags\" Additional CLI flags for new NMODL transpiler" + echo " -h, --help Show this help message and exit." + echo "If no MOD files or directories provided then MOD files from current directory are used." + exit 0;; -*) - echo "$1 unrecognized" + echo "$1 unrecognized, check available CLI options with --help" exit 1;; *) break;; diff --git a/src/mswin/bin/nrnivmodl.bat b/src/mswin/bin/nrnivmodl.bat index 21433df11a..60d83bb40c 100644 --- a/src/mswin/bin/nrnivmodl.bat +++ b/src/mswin/bin/nrnivmodl.bat @@ -3,6 +3,18 @@ setlocal set nrnhome=/cygdrive/%~dp0%/.. set "nrnhome=%nrnhome:\=/%" set "nrnhome=%nrnhome::=%" +if /I not "%~1" == "-h" ( + if /I not "%~1" == "--help" ( + goto :afterhelp + ) +) + +echo Usage: nrnivmodl [options] [mod files or directories with mod files] +echo Options: +echo -h, --help Show this help message and exit. +echo If no MOD files or directories provided then MOD files from current directory are used. +goto :done +:afterhelp if not "%~1"=="" ( pushd %~1 %~dp0/../mingw/usr/bin/sh %~dp0/../lib/mknrndll.sh %nrnhome% "--OUTPUTDIR=%CD%" @@ -10,3 +22,4 @@ if not "%~1"=="" ( ) else ( %~dp0/../mingw/usr/bin/sh %~dp0/../lib/mknrndll.sh %nrnhome% --OUTPUTDIR=. ) +:done From b9259997df5923d5d89fbf13d3fe31d77a079ed0 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Thu, 18 Jul 2024 13:53:48 +0200 Subject: [PATCH 06/29] Pin setuptools to a known working version (#3007) --- nrn_requirements.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nrn_requirements.txt b/nrn_requirements.txt index 8eaec90ee4..c4f58663da 100644 --- a/nrn_requirements.txt +++ b/nrn_requirements.txt @@ -1,6 +1,6 @@ wheel -setuptools setuptools_scm +setuptools<=70.3.0 scikit-build matplotlib # bokeh 3 seems to break docs notebooks diff --git a/setup.py b/setup.py index 96382a2e5e..3d944d1332 100644 --- a/setup.py +++ b/setup.py @@ -513,7 +513,7 @@ def setup_package(): "numpy>=1.9.3,<2", "packaging", "find_libpython", - "setuptools", + "setuptools<=70.3.0", ] + ( [ From c48d7d5bd099d8f1917702e8022af9bb725bb8be Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Thu, 18 Jul 2024 16:45:23 +0200 Subject: [PATCH 07/29] Support negative defined values (#3001) --- src/nmodl/parse1.ypp | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/nmodl/parse1.ypp b/src/nmodl/parse1.ypp index 443825a1ea..2d94253d41 100755 --- a/src/nmodl/parse1.ypp +++ b/src/nmodl/parse1.ypp @@ -172,14 +172,26 @@ model: MODEL line line: {$$ = inputline();} ; define1: DEFINE1 NAME INTEGER - /* all subsequent occurences of NAME will be replaced - by integer during parseing. See 'integer:' */ - { Symbol *sp = SYM($2); - if (sp->subtype) - diag(sp->name, " used before DEFINEed"); - sp->u.str = STR($3); - sp->type = DEFINEDVAR; - deltokens($1, $3);} + /* all subsequent occurences of NAME will be replaced + by integer during parseing. See 'integer:' */ + { + Symbol *sp = SYM($2); + if (sp->subtype) + diag(sp->name, " used before DEFINEed"); + sp->u.str = STR($3); + sp->type = DEFINEDVAR; + deltokens($1, $3); + } + | DEFINE1 NAME '-' INTEGER + { + Symbol *sp = SYM($2); + if (sp->subtype) + diag(sp->name, " used before DEFINEed"); + Sprintf(buf, "-%s", STR($4)); + sp->u.str = stralloc(buf, nullptr); + sp->type = DEFINEDVAR; + deltokens($1, $4); + } | DEFINE1 error {myerr("syntax: DEFINE name integer");} ; Name: NAME From 6459c2d368951a6a8cd57776bae511d72e7da957 Mon Sep 17 00:00:00 2001 From: Erik Heeren Date: Thu, 25 Jul 2024 17:06:00 +0200 Subject: [PATCH 08/29] 8.2.6 changelog (#3013) * Changelog for 8.2.6 (#3008) * Changelog for 8.2.6 * Update docs/changelog.md Co-authored-by: JCGoran * Update docs/changelog.md Co-authored-by: JCGoran --------- Co-authored-by: JCGoran * Update artefact links in documentation for 8.2.6 (#3009) * Update artefact links in documentation for 8.2.6 * Fix links --------- Co-authored-by: JCGoran --- docs/changelog.md | 16 ++++++++++++++++ docs/index.rst | 4 ++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 0185999a9d..803c8cb372 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,21 @@ # NEURON 8.2 +## 8.2.6 +_Release Date_ : 24-07-2024 + +This release pins numpy to <2 and includes backports for several fixes. + +### Bug Fixes +- Informative error when cannot import hoc module +- ParallelContext: hoc_ac_ encodes global id of submitted process. +- Python 3.12 compatibility with Windows installer (#2963) +- Windows 11 fix for nrniv -python (#2946) +- Fix for dynamic ECS diffusion characteristics. +- python38 is back. For testing can use rx3doptlevel=0 bash bldnrnmacpkgcmake.sh +- Fix cvode.use_fast_imem(1) error with electrode time varying conductance. +- Apple M1 cmake failure in cloning subrepository. See #2326. +- Update iv + ## 8.2.4 _Release Date_ : 08-02-2024 diff --git a/docs/index.rst b/docs/index.rst index 7db6fdcc5e..f56c3fdcdd 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -98,7 +98,7 @@ Installation pip3 install neuron - Alternatively, you can use the `PKG installer `_. + Alternatively, you can use the `PKG installer `_. For troubleshooting, see the `detailed installation instructions `_. @@ -115,7 +115,7 @@ Installation .. tab-item:: Windows - `Download the Windows Installer `_. + `Download the Windows Installer `_. You can also install the Linux wheel via the Windows Subsystem for Linux (WSL). See `instructions `_. From 455766202c6e07c97ecc1be6f88ea638da7f19a4 Mon Sep 17 00:00:00 2001 From: Erik Heeren Date: Thu, 25 Jul 2024 17:28:08 +0200 Subject: [PATCH 09/29] Update docs for building aarch64 wheels (#3014) --- docs/install/python_wheels.md | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/docs/install/python_wheels.md b/docs/install/python_wheels.md index 7b6d6b1cf8..42387bfad8 100644 --- a/docs/install/python_wheels.md +++ b/docs/install/python_wheels.md @@ -264,24 +264,39 @@ $ git checkout 8.1a -b release/8.1a-aarch64 # manually updated `.circleci/config.yml` $ git diff -@@ -15,6 +15,10 @@ jobs: +@@ -14,6 +14,11 @@ jobs: + machine: image: ubuntu-2004:202101-01 + environment: -+ SETUPTOOLS_SCM_PRETEND_VERSION: 8.1a ++ SETUPTOOLS_SCM_PRETEND_VERSION: 8.2.6 + NEURON_NIGHTLY_TAG: "" + NRN_NIGHTLY_UPLOAD: false + NRN_RELEASE_UPLOAD: false -@@ -89,7 +95,7 @@ workflows: - - manylinux2014-aarch64: + resource_class: arm.medium + +@@ -54,6 +59,7 @@ jobs: + 39) pyenv_py_ver="3.9.1" ;; + 310) pyenv_py_ver="3.10.1" ;; + 311) pyenv_py_ver="3.11.0" ;; ++ 312) pyenv_py_ver="3.12.2" ;; + *) echo "Error: pyenv python version not specified!" && exit 1;; + esac + +@@ -95,7 +101,7 @@ workflows: + - /circleci\/.*/ matrix: parameters: - NRN_PYTHON_VERSION: ["311"] -+ NRN_PYTHON_VERSION: ["38", "39", "310", "311"] ++ NRN_PYTHON_VERSION: ["38", "39", "310", "311", "312"] + NRN_NIGHTLY_UPLOAD: ["false"] + + nightly: ``` The reason we are setting `SETUPTOOLS_SCM_PRETEND_VERSION` to a desired version `8.1a` because `setup.py` uses `git describe` and it will give different version name as we are now on a new branch! +`NEURON_WHEEL_VERSION` will also stop your wheels from getting extra numbers on the version ## Nightly wheels From fec8ce65938d01bbb5ccefe2d34b34895e9b2789 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Tue, 30 Jul 2024 08:34:44 +0200 Subject: [PATCH 10/29] Fix various CI issues (#3017) - NumPy 1.21.3 has the wrong tag on MacOS with Python 3.10, so `pip check` started failing - NumPy was not being installed on Python 3.9 due to missing arch spec - mpi4py version 4 does not appear to be compatible with MUSIC, so we pin it to <4 everywhere --- .github/workflows/coverage.yml | 2 +- .github/workflows/neuron-ci.yml | 2 +- nrn_requirements.txt | 2 +- packaging/python/build_requirements.txt | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 54de0c6157..cde6c2c8a0 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -58,7 +58,7 @@ jobs: run: | python3 -m venv music-venv source music-venv/bin/activate - python3 -m pip install mpi4py "cython<3" 'numpy<2' setuptools + python3 -m pip install 'mpi4py<4' "cython<3" 'numpy<2' setuptools sudo mkdir -p $MUSIC_INSTALL_DIR sudo chown -R $USER $MUSIC_INSTALL_DIR curl -L -o MUSIC.zip https://github.com/INCF/MUSIC/archive/refs/tags/${MUSIC_VERSION}.zip diff --git a/.github/workflows/neuron-ci.yml b/.github/workflows/neuron-ci.yml index a355ca18cf..6f3076290f 100644 --- a/.github/workflows/neuron-ci.yml +++ b/.github/workflows/neuron-ci.yml @@ -207,7 +207,7 @@ jobs: run: | python3 -m venv music-venv source music-venv/bin/activate - python3 -m pip install mpi4py "cython<3" 'numpy<2' setuptools + python3 -m pip install 'mpi4py<4' "cython<3" 'numpy<2' setuptools sudo mkdir -p $MUSIC_INSTALL_DIR sudo chown -R $USER $MUSIC_INSTALL_DIR curl -L -o MUSIC.zip https://github.com/INCF/MUSIC/archive/refs/tags/${MUSIC_VERSION}.zip diff --git a/nrn_requirements.txt b/nrn_requirements.txt index c4f58663da..d78453146a 100644 --- a/nrn_requirements.txt +++ b/nrn_requirements.txt @@ -10,6 +10,6 @@ cython<3 packaging pytest<=8.1.1 # potential bug from 8.2.0 due to parallelism? pytest-cov -mpi4py +mpi4py<4 # MUSIC not compatible with MPI 4 numpy<2 find_libpython diff --git a/packaging/python/build_requirements.txt b/packaging/python/build_requirements.txt index c92f711148..5fe1763173 100644 --- a/packaging/python/build_requirements.txt +++ b/packaging/python/build_requirements.txt @@ -1,8 +1,8 @@ cython<3 packaging numpy==1.17.5;python_version=='3.8' -numpy==1.19.3;python_version=='3.9' and platform_machine=='x86_64' +numpy==1.19.3;python_version=='3.9' and platform_machine!='arm64' numpy==1.21.3;python_version=='3.9' and platform_machine=='arm64' -numpy==1.21.3;python_version=='3.10' +numpy==1.21.4;python_version=='3.10' numpy==1.23.5;python_version=='3.11' numpy==1.26.0;python_version=='3.12' From 88b95db11a63a14b5439acff5119f14c287bb83a Mon Sep 17 00:00:00 2001 From: Fernando Pereira Date: Wed, 31 Jul 2024 07:29:28 +0100 Subject: [PATCH 11/29] Use nanobind for refcounting in seg/sec code. (#3018) This commit replaces manual reference counting with the equivalent `nanobind` code for a couple of functions related to getting and setting attributes for sections and segments. --- src/nrnpython/nrnpy_nrn.cpp | 116 ++++++++++++++---------------------- 1 file changed, 44 insertions(+), 72 deletions(-) diff --git a/src/nrnpython/nrnpy_nrn.cpp b/src/nrnpython/nrnpy_nrn.cpp index ea46a6247f..9bf01bc2b3 100644 --- a/src/nrnpython/nrnpy_nrn.cpp +++ b/src/nrnpython/nrnpy_nrn.cpp @@ -192,17 +192,12 @@ static Object* pysec_cell(Section* sec) { static int NpySObj_contains(PyObject* s, PyObject* obj, const char* string) { /* Checks is provided PyObject* s contains obj */ - PyObject* obj_seg; - int result; if (!PyObject_HasAttrString(obj, string)) { return 0; } - Py_INCREF(obj); - obj_seg = PyObject_GetAttrString(obj, string); - Py_DECREF(obj); - result = PyObject_RichCompareBool(s, obj_seg, Py_EQ); - Py_XDECREF(obj_seg); - return (result); + auto _pyobj = nb::borrow(obj); // keep refcount+1 during use + auto obj_seg = nb::steal(PyObject_GetAttrString(obj, string)); + return PyObject_RichCompareBool(s, obj_seg.ptr(), Py_EQ); } static int NPySecObj_contains(PyObject* sec, PyObject* obj) { @@ -1015,10 +1010,8 @@ static PyObject* nrnpy_set_psection_safe(PyObject* self, PyObject* args) { static PyObject* NPySecObj_psection(NPySecObj* self) { CHECK_SEC_INVALID(self->sec_); if (nrnpy_psection) { - PyObject* arglist = Py_BuildValue("(O)", self); - PyObject* result = PyObject_CallObject(nrnpy_psection, arglist); - Py_DECREF(arglist); - return result; + auto arglist = nb::steal(Py_BuildValue("(O)", self)); + return PyObject_CallObject(nrnpy_psection, arglist.ptr()); } Py_RETURN_NONE; } @@ -1128,14 +1121,13 @@ static PyObject* pysec_orientation_safe(NPySecObj* self) { } static bool lappendsec(PyObject* const sl, Section* const s) { - PyObject* item = (PyObject*) newpysechelp(s); - if (!item) { + auto item = nb::steal((PyObject*) newpysechelp(s)); + if (!item.is_valid()) { return false; } - if (PyList_Append(sl, item) != 0) { + if (PyList_Append(sl, item.ptr()) != 0) { return false; } - Py_XDECREF(item); return true; } @@ -1705,10 +1697,9 @@ static PyObject* seg_point_processes(NPySegObj* self) { for (Prop* p = nd->prop; p; p = p->next) { if (memb_func[p->_type].is_point) { auto* pp = p->dparam[1].get(); - PyObject* item = nrnpy_ho2po(pp->ob); - int err = PyList_Append(result, item); + auto item = nb::steal(nrnpy_ho2po(pp->ob)); + int err = PyList_Append(result, item.ptr()); assert(err == 0); - Py_XDECREF(item); } } return result; @@ -1901,8 +1892,8 @@ static PyObject* mech_of_segment_iter_safe(NPySegObj* self) { } static Object* seg_from_sec_x(Section* sec, double x) { - PyObject* pyseg = (PyObject*) PyObject_New(NPySegObj, psegment_type); - NPySegObj* pseg = (NPySegObj*) pyseg; + auto pyseg = nb::steal((PyObject*) PyObject_New(NPySegObj, psegment_type)); + auto* pseg = (NPySegObj*) pyseg.ptr(); auto* pysec = static_cast(sec->prop->dparam[PROP_PY_INDEX].get()); if (pysec) { pseg->pysec_ = pysec; @@ -1916,9 +1907,7 @@ static Object* seg_from_sec_x(Section* sec, double x) { pseg->pysec_ = pysec; } pseg->x_ = x; - Object* ho = nrnpy_pyobject_in_obj(pyseg); - Py_DECREF(pyseg); - return ho; + return nrnpy_pyobject_in_obj(pyseg.ptr()); } static Object** pp_get_segment(void* vptr) { @@ -1990,16 +1979,15 @@ static PyObject* section_getattro(NPySecObj* self, PyObject* pyname) { Section* sec = self->sec_; CHECK_SEC_INVALID(sec); PyObject* rv; - Py_INCREF(pyname); + auto _pyname_tracker = nb::borrow(pyname); // keep refcount+1 during use Py2NRNString name(pyname); char* n = name.c_str(); if (name.err()) { name.set_pyerr(PyExc_TypeError, "attribute name must be a string"); - Py_DECREF(pyname); - return NULL; + return nullptr; } // printf("section_getattr %s\n", n); - PyObject* result = 0; + PyObject* result = nullptr; if (strcmp(n, "L") == 0) { result = Py_BuildValue("d", section_length(sec)); } else if (strcmp(n, "Ra") == 0) { @@ -2016,7 +2004,7 @@ static PyObject* section_getattro(NPySecObj* self, PyObject* pyname) { auto const d = nrnpy_rangepointer(sec, sym, 0.5, &err, 0 /* idx */); if (d.is_invalid_handle()) { rv_noexist(sec, n, 0.5, err); - result = nullptr; + return nullptr; } else { if (sec->recalc_area_ && sym->u.rng.type == MORPHOLOGY) { nrn_area_ri(sec); @@ -2039,7 +2027,6 @@ static PyObject* section_getattro(NPySecObj* self, PyObject* pyname) { } else { result = PyObject_GenericGetAttr((PyObject*) self, pyname); } - Py_DECREF(pyname); return result; } @@ -2055,12 +2042,11 @@ static int section_setattro(NPySecObj* self, PyObject* pyname, PyObject* value) } PyObject* rv; int err = 0; - Py_INCREF(pyname); + auto _pyname_tracker = nb::borrow(pyname); // keep refcount+1 during use Py2NRNString name(pyname); char* n = name.c_str(); if (name.err()) { name.set_pyerr(PyExc_TypeError, "attribute name must be a string"); - Py_DECREF(pyname); return -1; } // printf("section_setattro %s\n", n); @@ -2075,7 +2061,7 @@ static int section_setattro(NPySecObj* self, PyObject* pyname, PyObject* value) } } else { PyErr_SetString(PyExc_ValueError, "L must be > 0."); - err = -1; + return -1; } } else if (strcmp(n, "Ra") == 0) { double x; @@ -2085,7 +2071,7 @@ static int section_setattro(NPySecObj* self, PyObject* pyname, PyObject* value) sec->recalc_area_ = 1; } else { PyErr_SetString(PyExc_ValueError, "Ra must be > 0."); - err = -1; + return -1; } } else if (strcmp(n, "nseg") == 0) { int nseg; @@ -2093,7 +2079,7 @@ static int section_setattro(NPySecObj* self, PyObject* pyname, PyObject* value) nrn_change_nseg(sec, nseg); } else { PyErr_SetString(PyExc_ValueError, "nseg must be an integer in range 1 to 32767"); - err = -1; + return -1; } // printf("section_setattro err=%d nseg=%d nnode\n", err, nseg, // sec->nnode); @@ -2101,19 +2087,19 @@ static int section_setattro(NPySecObj* self, PyObject* pyname, PyObject* value) Symbol* sym = ((NPyRangeVar*) rv)->sym_; if (is_array(*sym)) { PyErr_SetString(PyExc_IndexError, "missing index"); - err = -1; + return -1; } else { int errp; auto d = nrnpy_rangepointer(sec, sym, 0.5, &errp, 0 /* idx */); if (d.is_invalid_handle()) { rv_noexist(sec, n, 0.5, errp); - err = -1; + return -1; } else if (!d.holds()) { PyErr_SetString(PyExc_ValueError, "can't assign value to opaque pointer"); - err = -1; + return -1; } else if (!PyArg_Parse(value, "d", d.get())) { PyErr_SetString(PyExc_ValueError, "bad value"); - err = -1; + return -1; } else { // only need to do following if nseg > 1, VINDEX, or EXTRACELL nrn_rangeconst(sec, sym, neuron::container::data_handle(d), 0); @@ -2127,12 +2113,11 @@ static int section_setattro(NPySecObj* self, PyObject* pyname, PyObject* value) sec->recalc_area_ = 1; } else { PyErr_SetString(PyExc_ValueError, "rallbranch must be > 0"); - err = -1; + return -1; } } else { err = PyObject_GenericSetAttr((PyObject*) self, pyname, value); } - Py_DECREF(pyname); return err; } @@ -2210,16 +2195,15 @@ static PyObject* segment_getattro(NPySegObj* self, PyObject* pyname) { CHECK_SEC_INVALID(sec) Symbol* sym; - Py_INCREF(pyname); + auto _pyname_tracker = nb::borrow(pyname); // keep refcount+1 during use Py2NRNString name(pyname); char* n = name.c_str(); if (name.err()) { name.set_pyerr(PyExc_TypeError, "attribute name must be a string"); - Py_DECREF(pyname); - return NULL; + return nullptr; } // printf("segment_getattr %s\n", n); - PyObject* result = NULL; + PyObject* result = nullptr; PyObject* otype = NULL; PyObject* rv = NULL; if (strcmp(n, "v") == 0) { @@ -2232,7 +2216,7 @@ static PyObject* segment_getattro(NPySegObj* self, PyObject* pyname) { Prop* p = nrn_mechanism(type, nd); if (!p) { rv_noexist(sec, n, self->x_, 1); - result = NULL; + return nullptr; } else { result = (PyObject*) new_pymechobj(self, p); } @@ -2258,7 +2242,7 @@ static PyObject* segment_getattro(NPySegObj* self, PyObject* pyname) { auto const d = nrnpy_rangepointer(sec, sym, self->x_, &err, 0 /* idx */); if (d.is_invalid_handle()) { rv_noexist(sec, n, self->x_, err); - result = NULL; + return nullptr; } else { if (sec->recalc_area_ && sym->u.rng.type == MORPHOLOGY) { nrn_area_ri(sec); @@ -2286,7 +2270,7 @@ static PyObject* segment_getattro(NPySegObj* self, PyObject* pyname) { auto const d = nrnpy_rangepointer(sec, sym, self->x_, &err, 0 /* idx */); if (d.is_invalid_handle()) { rv_noexist(sec, n + 5, self->x_, err); - result = NULL; + return nullptr; } else { if (d.holds()) { result = nrn_hocobj_handle(neuron::container::data_handle(d)); @@ -2297,7 +2281,7 @@ static PyObject* segment_getattro(NPySegObj* self, PyObject* pyname) { } } else { rv_noexist(sec, n, self->x_, 2); - result = NULL; + return nullptr; } } else if (strcmp(n, "__dict__") == 0) { Node* nd = node_exact(sec, self->x_); @@ -2318,7 +2302,6 @@ static PyObject* segment_getattro(NPySegObj* self, PyObject* pyname) { } else { result = PyObject_GenericGetAttr((PyObject*) self, pyname); } - Py_DECREF(pyname); return result; } @@ -2357,12 +2340,11 @@ static int segment_setattro(NPySegObj* self, PyObject* pyname, PyObject* value) PyObject* rv; Symbol* sym; int err = 0; - Py_INCREF(pyname); + auto _pyname_tracker = nb::borrow(pyname); // keep refcount+1 during use Py2NRNString name(pyname); char* n = name.c_str(); if (name.err()) { name.set_pyerr(PyExc_TypeError, "attribute name must be a string"); - Py_DECREF(pyname); return -1; } // printf("segment_setattro %s\n", n); @@ -2379,7 +2361,7 @@ static int segment_setattro(NPySegObj* self, PyObject* pyname, PyObject* value) } } else { PyErr_SetString(PyExc_ValueError, "x must be in range 0. to 1."); - err = -1; + return -1; } } else if ((rv = PyDict_GetItemString(rangevars_, n)) != NULL) { sym = ((NPyRangeVar*) rv)->sym_; @@ -2387,19 +2369,17 @@ static int segment_setattro(NPySegObj* self, PyObject* pyname, PyObject* value) char s[200]; Sprintf(s, "%s needs an index for assignment", sym->name); PyErr_SetString(PyExc_IndexError, s); - err = -1; + return -1; } else { int errp; auto d = nrnpy_rangepointer(sec, sym, self->x_, &errp, 0 /* idx */); if (d.is_invalid_handle()) { rv_noexist(sec, n, self->x_, errp); - Py_DECREF(pyname); return -1; } if (d.holds()) { if (!PyArg_Parse(value, "d", d.get())) { PyErr_SetString(PyExc_ValueError, "bad value"); - Py_DECREF(pyname); return -1; } else if (sym->u.rng.type == MORPHOLOGY) { diam_changed = 1; @@ -2411,7 +2391,6 @@ static int segment_setattro(NPySegObj* self, PyObject* pyname, PyObject* value) } } else { PyErr_SetString(PyExc_ValueError, "can't assign value to opaque pointer"); - Py_DECREF(pyname); return -1; } } @@ -2429,7 +2408,6 @@ static int segment_setattro(NPySegObj* self, PyObject* pyname, PyObject* value) } else { err = PyObject_GenericSetAttr((PyObject*) self, pyname, value); } - Py_DECREF(pyname); return err; } @@ -2512,13 +2490,12 @@ static PyObject* mech_getattro(NPyMechObj* self, PyObject* pyname) { Section* sec = self->pyseg_->pysec_->sec_; CHECK_SEC_INVALID(sec) CHECK_PROP_INVALID(self->prop_id_); - Py_INCREF(pyname); + auto _pyname_tracker = nb::borrow(pyname); // keep refcount+1 during use Py2NRNString name(pyname); char* n = name.c_str(); if (!n) { name.set_pyerr(PyExc_TypeError, "attribute name must be a string"); - Py_DECREF(pyname); - return NULL; + return nullptr; } // printf("mech_getattro %s\n", n); PyObject* result = NULL; @@ -2592,7 +2569,6 @@ static PyObject* mech_getattro(NPyMechObj* self, PyObject* pyname) { result = PyObject_GenericGetAttr((PyObject*) self, pyname); } } - Py_DECREF(pyname); delete[] buf; return result; } @@ -2609,12 +2585,11 @@ static int mech_setattro(NPyMechObj* self, PyObject* pyname, PyObject* value) { } int err = 0; - Py_INCREF(pyname); + auto _pyname_tracker = nb::borrow(pyname); // keep refcount+1 during use Py2NRNString name(pyname); char* n = name.c_str(); if (name.err()) { name.set_pyerr(PyExc_TypeError, "attribute name must be a string"); - Py_DECREF(pyname); return -1; } // printf("mech_setattro %s\n", n); @@ -2638,19 +2613,18 @@ static int mech_setattro(NPyMechObj* self, PyObject* pyname, PyObject* value) { auto pd = get_rangevar(self, sym, 0); if (pd.is_invalid_handle()) { rv_noexist(sec, sym->name, self->pyseg_->x_, 2); - err = -1; + return -1; } else if (!pd.holds()) { PyErr_SetString(PyExc_ValueError, "can't assign value to opaque pointer"); - err = -1; + return -1; } else if (!PyArg_Parse(value, "d", pd.get())) { PyErr_SetString(PyExc_ValueError, "must be a double"); - err = -1; + return -1; } } } else { err = PyObject_GenericSetAttr((PyObject*) self, pyname, value); } - Py_DECREF(pyname); return err; } @@ -2682,10 +2656,8 @@ static PyObject* NPySecObj_call(NPySecObj* self, PyObject* args) { CHECK_SEC_INVALID(self->sec_); double x = 0.5; PyArg_ParseTuple(args, "|d", &x); - PyObject* segargs = Py_BuildValue("(O,d)", self, x); - PyObject* seg = NPySegObj_new(psegment_type, segargs, 0); - Py_DECREF(segargs); - return seg; + auto segargs = nb::steal(Py_BuildValue("(O,d)", self, x)); + return NPySegObj_new(psegment_type, segargs.ptr(), nullptr); } static PyObject* NPySecObj_call_safe(NPySecObj* self, PyObject* args) { From 72f9ec4e4b8d931372aec99a7a0ee5fb2efe48f2 Mon Sep 17 00:00:00 2001 From: Fernando Pereira Date: Wed, 31 Jul 2024 10:21:33 +0100 Subject: [PATCH 12/29] Use `nb::dict` instead of the C Python API. (#3019) This also simplifies dealing with `None`. In certain versions of Python one's requires to increase the reference count of `None`. While later versions make it "eternal" and don't require bumping reference count. --- src/nrnpython/nrnpy_nrn.cpp | 39 +++++++++++++++---------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/src/nrnpython/nrnpy_nrn.cpp b/src/nrnpython/nrnpy_nrn.cpp index 9bf01bc2b3..dd9687f100 100644 --- a/src/nrnpython/nrnpy_nrn.cpp +++ b/src/nrnpython/nrnpy_nrn.cpp @@ -2015,15 +2015,12 @@ static PyObject* section_getattro(NPySecObj* self, PyObject* pyname) { } else if (strcmp(n, "rallbranch") == 0) { result = Py_BuildValue("d", sec->prop->dparam[4].get()); } else if (strcmp(n, "__dict__") == 0) { - result = PyDict_New(); - int err = PyDict_SetItemString(result, "L", Py_None); - assert(err == 0); - err = PyDict_SetItemString(result, "Ra", Py_None); - assert(err == 0); - err = PyDict_SetItemString(result, "nseg", Py_None); - assert(err == 0); - err = PyDict_SetItemString(result, "rallbranch", Py_None); - assert(err == 0); + nb::dict out_dict{}; + out_dict["L"] = nb::none(); + out_dict["Ra"] = nb::none(); + out_dict["nseg"] = nb::none(); + out_dict["rallbranch"] = nb::none(); + result = out_dict.release().ptr(); } else { result = PyObject_GenericGetAttr((PyObject*) self, pyname); } @@ -2285,20 +2282,17 @@ static PyObject* segment_getattro(NPySegObj* self, PyObject* pyname) { } } else if (strcmp(n, "__dict__") == 0) { Node* nd = node_exact(sec, self->x_); - result = PyDict_New(); - int err = PyDict_SetItemString(result, "v", Py_None); - assert(err == 0); - PyDict_SetItemString(result, "diam", Py_None); - assert(err == 0); - PyDict_SetItemString(result, "cm", Py_None); - assert(err == 0); + nb::dict out_dict{}; + out_dict["v"] = nb::none(); + out_dict["diam"] = nb::none(); + out_dict["cm"] = nb::none(); for (Prop* p = nd->prop; p; p = p->next) { if (p->_type > CAP && !memb_func[p->_type].is_point) { char* pn = memb_func[p->_type].sym->name; - err = PyDict_SetItemString(result, pn, Py_None); - assert(err == 0); + out_dict[pn] = nb::none(); } } + result = out_dict.release().ptr(); } else { result = PyObject_GenericGetAttr((PyObject*) self, pyname); } @@ -2536,21 +2530,20 @@ static PyObject* mech_getattro(NPyMechObj* self, PyObject* pyname) { Object* ob = nrn_nmodlrandom_wrap(self->prop_, sym); result = nrnpy_ho2po(ob); } else if (strcmp(n, "__dict__") == 0) { - result = PyDict_New(); + nb::dict out_dict{}; int cnt = mechsym->s_varn; for (int i = 0; i < cnt; ++i) { Symbol* s = mechsym->u.ppsym[i]; if (!striptrail(buf, bufsz, s->name, mname)) { strcpy(buf, s->name); } - int err = PyDict_SetItemString(result, buf, Py_None); - assert(err == 0); + out_dict[buf] = nb::none(); } // FUNCTION and PROCEDURE for (auto& it: nrn_mech2funcs_map[self->prop_->_type]) { - int err = PyDict_SetItemString(result, it.first.c_str(), Py_None); - assert(err == 0); + out_dict[it.first.c_str()] = nb::none(); } + result = out_dict.release().ptr(); } else { bool found_func{false}; if (self->prop_) { From 35823e6063dba70c51297962f1a393570bc1c031 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Mon, 5 Aug 2024 13:06:16 +0200 Subject: [PATCH 13/29] type of mechanism should not be < 0 (#3026) --- src/neuron/model_data.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/neuron/model_data.hpp b/src/neuron/model_data.hpp index 9d71d80074..8909aa01cc 100644 --- a/src/neuron/model_data.hpp +++ b/src/neuron/model_data.hpp @@ -124,7 +124,7 @@ struct Model { private: container::Mechanism::storage& mechanism_data_impl(int type) const { - if (0 <= type && type >= m_mech_data.size()) { + if (type < 0 || type >= m_mech_data.size()) { throw std::runtime_error("mechanism_data(" + std::to_string(type) + "): type out of range"); } From bd5400b1100ff07ce4c11b5ff121077e28d102d0 Mon Sep 17 00:00:00 2001 From: MikeG Date: Tue, 6 Aug 2024 14:17:49 +0200 Subject: [PATCH 14/29] Cleanup python __init__ (#3027) * remove duplicate `os` and `sys` imports. * remove `hoc_execute` and `hoc_comment` since both must be unused since they relied on `logging` being imported, but it wasn't. --- share/lib/python/neuron/__init__.py | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/share/lib/python/neuron/__init__.py b/share/lib/python/neuron/__init__.py index c48fdd56ef..36bca6d407 100644 --- a/share/lib/python/neuron/__init__.py +++ b/share/lib/python/neuron/__init__.py @@ -104,7 +104,7 @@ import warnings import weakref -embedded = True if "hoc" in sys.modules else False +embedded = "hoc" in sys.modules # First, check that the compiled extension (neuron.hoc) was built for this version of # Python. If not, fail early and helpfully. @@ -291,7 +291,6 @@ def test_rxd(exitOnError=True): # using the idiom self.basemethod = self.baseattr('methodname') # ------------------------------------------------------------------------------ -import sys, types from neuron.hclass3 import HocBaseObject, hclass # global list of paths already loaded by load_mechanisms @@ -341,8 +340,6 @@ def load_mechanisms(path, warn_if_already_loaded=True): return False -import os, sys - if "NRN_NMODL_PATH" in os.environ: nrn_nmodl_path = os.environ["NRN_NMODL_PATH"].split(":") print("Auto-loading mechanisms:") @@ -478,21 +475,6 @@ def quit(*args, **kwargs): return h.quit(*args, **kwargs) -def hoc_execute(hoc_commands, comment=None): - assert isinstance(hoc_commands, list) - if comment: - logging.debug(comment) - for cmd in hoc_commands: - logging.debug(cmd) - success = hoc.execute(cmd) - if not success: - raise HocError('Error produced by hoc command "%s"' % cmd) - - -def hoc_comment(comment): - logging.debug(comment) - - def psection(section): """ function psection(section): @@ -650,7 +632,6 @@ def nrn_dll_sym(name, type=None): """ # TODO: this won't work under Windows; will need to search through until # can find the right dll (should we cache the results of the search?) - import os if os.name == "nt": return nrn_dll_sym_nt(name, type) @@ -671,7 +652,6 @@ def nrn_dll_sym_nt(name, type): """ global nt_dlls import ctypes - import os if len(nt_dlls) == 0: b = "bin" @@ -708,8 +688,6 @@ def nrn_dll(printpath=False): """ import ctypes import glob - import os - import sys try: # extended? if there is a __file__, then use that From 514efd6c7fb184a15bbdf946a3cf24b2c3e91eb2 Mon Sep 17 00:00:00 2001 From: Jorge Blanco Alonso <41900536+jorblancoa@users.noreply.github.com> Date: Wed, 7 Aug 2024 10:16:43 +0200 Subject: [PATCH 15/29] Add Git version check for --depth option. (#3032) On certain old OSes (commonly found on clusters), the installed Git version doesn't support `--depth`. --- cmake/ExternalProjectHelper.cmake | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmake/ExternalProjectHelper.cmake b/cmake/ExternalProjectHelper.cmake index d1227d056c..c7f5f2299e 100644 --- a/cmake/ExternalProjectHelper.cmake +++ b/cmake/ExternalProjectHelper.cmake @@ -29,7 +29,11 @@ function(nrn_initialize_submodule path) list(APPEND UPDATE_OPTIONS --recursive) endif() if(opt_SHALLOW) - list(APPEND UPDATE_OPTIONS --depth 1) + # RHEL7-family distributions ship with an old git that does not support the --depth argument to + # git submodule update + if(GIT_VERSION_STRING VERSION_GREATER_EQUAL "1.8.4") + list(APPEND UPDATE_OPTIONS --depth 1) + endif() endif() if(NOT ${GIT_FOUND}) message( From 94ee309d21044fef3759881f0ce3f300aabad34e Mon Sep 17 00:00:00 2001 From: Fernando Pereira Date: Wed, 7 Aug 2024 10:41:37 +0100 Subject: [PATCH 16/29] Simplify Python bindings internally. (#3024) Use nanobind to simplify the implementation of: * `NpySObj_contains`, * `NPySecObj_insert`, * `seg_point_processes`. --- src/nrnpython/nrnpy_nrn.cpp | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/src/nrnpython/nrnpy_nrn.cpp b/src/nrnpython/nrnpy_nrn.cpp index dd9687f100..17ff995ffe 100644 --- a/src/nrnpython/nrnpy_nrn.cpp +++ b/src/nrnpython/nrnpy_nrn.cpp @@ -191,13 +191,13 @@ static Object* pysec_cell(Section* sec) { } static int NpySObj_contains(PyObject* s, PyObject* obj, const char* string) { - /* Checks is provided PyObject* s contains obj */ - if (!PyObject_HasAttrString(obj, string)) { + /* Checks is provided PyObject* s matches obj. */ + auto pyobj = nb::borrow(obj); // keep refcount+1 during use + if (!nb::hasattr(pyobj, string)) { return 0; } - auto _pyobj = nb::borrow(obj); // keep refcount+1 during use - auto obj_seg = nb::steal(PyObject_GetAttrString(obj, string)); - return PyObject_RichCompareBool(s, obj_seg.ptr(), Py_EQ); + auto obj_seg = pyobj.attr(string); + return nb::handle{s}.equal(obj_seg); } static int NPySecObj_contains(PyObject* sec, PyObject* obj) { @@ -1493,29 +1493,27 @@ static PyObject* NPySecObj_connect_safe(NPySecObj* self, PyObject* args) { static PyObject* NPySecObj_insert(NPySecObj* self, PyObject* args) { CHECK_SEC_INVALID(self->sec_); char* tname; - PyObject *tpyobj, *tpyobj2; if (!PyArg_ParseTuple(args, "s", &tname)) { PyErr_Clear(); // if called with an object that has an insert method, use that + PyObject* tpyobj; if (PyArg_ParseTuple(args, "O", &tpyobj)) { - Py_INCREF(tpyobj); - Py_INCREF((PyObject*) self); - tpyobj2 = PyObject_CallMethod(tpyobj, "insert", "O", (PyObject*) self); - Py_DECREF(tpyobj); - if (tpyobj2 == NULL) { - Py_DECREF((PyObject*) self); + auto _tpyobj_tracker = nb::borrow(tpyobj); + // Returned object to be discarded + auto out_o = nb::steal(PyObject_CallMethod(tpyobj, "insert", "O", (PyObject*) self)); + if (!out_o.is_valid()) { PyErr_Clear(); PyErr_SetString( PyExc_TypeError, "insert argument must be either a string or an object with an insert method"); - return NULL; + return nullptr; } - Py_DECREF(tpyobj2); + Py_INCREF(self); return (PyObject*) self; } PyErr_Clear(); PyErr_SetString(PyExc_TypeError, "insert takes a single positional argument"); - return NULL; + return nullptr; } PyObject* otype = PyDict_GetItemString(pmech_types, tname); if (!otype) { @@ -1693,16 +1691,14 @@ static PyObject* seg_point_processes(NPySegObj* self) { Section* sec = self->pysec_->sec_; CHECK_SEC_INVALID(sec); Node* nd = node_exact(sec, self->x_); - PyObject* result = PyList_New(0); + nb::list result{}; for (Prop* p = nd->prop; p; p = p->next) { if (memb_func[p->_type].is_point) { auto* pp = p->dparam[1].get(); - auto item = nb::steal(nrnpy_ho2po(pp->ob)); - int err = PyList_Append(result, item.ptr()); - assert(err == 0); + result.append(nb::steal(nrnpy_ho2po(pp->ob))); } } - return result; + return result.release().ptr(); } static PyObject* seg_point_processes_safe(NPySegObj* self) { From d81f60a93539fdb95c7b9cda2784c4ff37413b09 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Fri, 9 Aug 2024 12:59:31 +0200 Subject: [PATCH 17/29] Remove unused NEMO simulator compatibility (#3035) --- cmake/NeuronFileLists.cmake | 1 - docs/hoc/compilationoptions.rst | 8 - docs/python/compilationoptions.rst | 8 - src/nrnoc/neuron.h | 4 - src/nrnoc/nrnnemo.cpp | 231 ----------------------------- src/oc/code.cpp | 20 --- src/oc/hocdec.h | 6 +- src/oc/mk_hocusr_h.py | 4 +- 8 files changed, 3 insertions(+), 279 deletions(-) delete mode 100644 src/nrnoc/nrnnemo.cpp diff --git a/cmake/NeuronFileLists.cmake b/cmake/NeuronFileLists.cmake index ad70963033..59b8e6c2de 100644 --- a/cmake/NeuronFileLists.cmake +++ b/cmake/NeuronFileLists.cmake @@ -126,7 +126,6 @@ set(NRNOC_FILE_LIST ldifus.cpp membfunc.cpp memblist.cpp - nrnnemo.cpp nrntimeout.cpp nrnversion.cpp passive0.cpp diff --git a/docs/hoc/compilationoptions.rst b/docs/hoc/compilationoptions.rst index fd40c2b306..321290c63a 100644 --- a/docs/hoc/compilationoptions.rst +++ b/docs/hoc/compilationoptions.rst @@ -44,12 +44,4 @@ and obscure to benefit most users. insufficiently tested and the correctness must be established for each simulation. #endif - - #if NEMO - neuron2nemo("filename") Beginning of translator between John Millers - nemosys program and NEURON. Probably out of date. - nemo2neuron("filename") - #endif - - diff --git a/docs/python/compilationoptions.rst b/docs/python/compilationoptions.rst index fd40c2b306..73fb3805dc 100755 --- a/docs/python/compilationoptions.rst +++ b/docs/python/compilationoptions.rst @@ -45,11 +45,3 @@ and obscure to benefit most users. each simulation. #endif - #if NEMO - neuron2nemo("filename") Beginning of translator between John Millers - nemosys program and NEURON. Probably out of date. - nemo2neuron("filename") - #endif - - - diff --git a/src/nrnoc/neuron.h b/src/nrnoc/neuron.h index e42274b308..665ebe11d1 100755 --- a/src/nrnoc/neuron.h +++ b/src/nrnoc/neuron.h @@ -1,9 +1,5 @@ #include "options.h" -#if NEMO -extern int neuron2nemo(), nemo2neuron(); -#endif - extern void node_data(), disconnect(); extern void batch_run(), batch_save(); extern void pt3dadd(), n3d(), x3d(), y3d(), z3d(), arc3d(), diam3d(); diff --git a/src/nrnoc/nrnnemo.cpp b/src/nrnoc/nrnnemo.cpp deleted file mode 100644 index c0a2137bdb..0000000000 --- a/src/nrnoc/nrnnemo.cpp +++ /dev/null @@ -1,231 +0,0 @@ -#include <../../nrnconf.h> - -#include "section.h" - - -#define OBSOLETE 1 - -#if !OBSOLETE -#include "membfunc.h" -#include "hocassrt.h" - -/* basic loop taken from topology() in solve.cpp */ -static FILE *fin, *fmark, *fdat; -static int inode; -static dashes(), file_func(), dat_head(); -#define MAXMARKS 32 -static long* marksec; -static printline(); - -extern int section_count; -extern Section** secorder; -#endif - -void nemo2neuron(void) { - hoc_retpushx(1.0); -} - -void neuron2nemo(void) { -#if OBSOLETE - hoc_execerror("neuron2nemo:", "implementation is obsolete"); -#else - short i, isec, imark; - char name[50]; - - if (tree_changed) { - setup_topology(); - } - Sprintf(name, "in/%s", gargstr(1)); - if ((fin = fopen(name, "w")) == (FILE*) 0) { - hoc_execerror("Can't write to ", name); - } - Sprintf(name, "mark/%s", gargstr(1)); - if ((fmark = fopen(name, "w")) == (FILE*) 0) { - hoc_execerror("Can't write to ", name); - } - Sprintf(name, "dat/%s", gargstr(1)); - if ((fdat = fopen(name, "w")) == (FILE*) 0) { - hoc_execerror("Can't write to ", name); - } - dat_head(); - - /* set up the mark for every section */ - imark = 1; - marksec = (long*) ecalloc(section_count, sizeof(long)); - for (i = 1; i < section_count; i++) { - isec = i; - if (marksec[isec]) { - marksec[i] = marksec[isec]; - } else { - if (imark < MAXMARKS) { - marksec[i] = 1L << (imark++); - file_func(secorder[isec]); - } - } - } - Fprintf(fin, "/* created by NEURON */\n"); - inode = 0; - for (i = 0; i < rootnodecount; i++) { - printline(v_node[i]->child, 0, 0., (double) (i * 100)); - dashes(v_node[i]->child, 0., (double) (i * 100), 0., 1); - } - free((char*) marksec); - fclose(fin); - fclose(fmark); -#endif - hoc_retpushx(1.0); -} - -#if !OBSOLETE -static dashes(Section* sec, double x, double y, double theta, int leftend) { - Section* ch; - int i, nrall, irall; - double cos(), sin(), xx, yy, ttheta, dx; - - dx = section_length(sec) / ((double) sec->nnode - 1); - nrall = (int) sec->prop->dparam[4].val; - for (irall = 0; irall < nrall; irall++) { - xx = x; - yy = y; - ttheta = theta + (double) (nrall - 2 * irall - 1) / (double) nrall; - for (i = 0; i < sec->nnode - 1; i++) { - xx += dx * cos(ttheta); - yy += dx * sin(ttheta); - printline(sec, i, xx, yy); - } - for (i = sec->nnode - 1; i >= 0; i--) { - if ((ch = sec->pnode[i]->child) != (Section*) 0) { - if (i == sec->nnode - 1) { - dashes(ch, xx, yy, ttheta, 0); - } else { - dashes(ch, xx, yy, ttheta + .5, 0); - } - } - if (i < sec->nnode - 1) { - xx -= dx * cos(ttheta); - yy -= dx * sin(ttheta); - } - } - if (leftend) { - ttheta = 3.14159 - .5; - } - if ((ch = sec->sibling) != (Section*) 0) { - dashes(ch, x, y, ttheta + .5, 0); - } - } -} - -static double diamval(Node* nd) { - Prop* p; - - for (p = nd->prop; p; p = p->next) { - if (p->_type == MORPHOLOGY) { - break; - } - } - assert(p); - return p->param[0]; -} - -static void printline(Section* sec, int i, double x, double y) { - char type; - int nb; - Section* csec; - Node* nd; - double d; - - ++inode; - assert(sec->nnode > 1); - type = 'C'; - nd = sec->pnode[i]; - d = diamval(nd); - nb = 0; - for (csec = nd->child; csec; csec = csec->sibling) { - nb += (int) csec->prop->dparam[4].val; - } - if (i == sec->nnode - 2) { - ++i; - nd = sec->pnode[i]; - for (csec = nd->child; csec; csec = csec->sibling) { - nb += (int) csec->prop->dparam[4].val; - } - } - if (i < sec->nnode - 2) { - nb++; - } - if (nb > 2) { /*many branches*/ - type = 'B'; - x -= .001; - while (nb > 2) { - fwrite((char*) &marksec[sec->order], sizeof(long), 1, fmark); - Fprintf(fin, "%d\t%c\t%g\t%g\t0\t%g\n", inode++, type, (x += .001), y, d); - --nb; - } - } else if (nb == 2) { /*one branch*/ - type = 'B'; - } else if (nb == 0) { - type = 'T'; - } - fwrite((char*) &marksec[sec->order], sizeof(long), 1, fmark); - Fprintf(fin, "%d\t%c\t%g\t%g\t0\t%g\n", inode, type, x, y, d); -} - -/* modified from nemo.cpp */ -static void file_func(Section* sec) { - int active; - Node* nd; - Prop* p; - Symbol *s, *hoc_lookup(); - static int hhtype = 0; - - if (!hhtype) { - s = hoc_lookup("HH"); - hhtype = s->subtype; - } - nd = sec->pnode[0]; - - active = 0; - for (p = nd->prop; p; p = p->next) { - if (p->_type == hhtype) { - active = 1; - } - } - - assert(sec->prop->dparam[0].sym); - fprintf(fdat, "%s\n", sec->prop->dparam[0].sym->name); - fprintf(fdat, "^area %g\n", 1.); - fprintf(fdat, "^Rm %g\n", 1.); - fprintf(fdat, "^active %d\n", active); - fprintf(fdat, "^synapse %d\n", 0); - fprintf(fdat, "^Erev %g\n", 0.); - fprintf(fdat, "^gback %g\n", 0.); - fprintf(fdat, "^gsyn %g\n", 0.); - fprintf(fdat, "^tstart %g\n", 0.); - fprintf(fdat, "^twidth %g\n", 0.); -} - -static void dat_head(void) { - double Ra = 35.4; - - fprintf(fdat, "freq\n"); - fprintf(fdat, "%d %g %g\n", 4, 1., 1000.); - - fprintf(fdat, "tran\n"); - fprintf(fdat, "%g %g\n", .1, 10.); - - fprintf(fdat, "elec\n"); - fprintf(fdat, "%g %g %g\n", 1000., 1.e-6, Ra); - - fprintf(fdat, "syn\n"); - fprintf(fdat, "%g %g\n", 30., -15.); - - fprintf(fdat, "hh\n"); - fprintf(fdat, "%g %g %g %g %g %g\n", 1., 1., 1., 1., 1., 1.); - - fprintf(fdat, "batt\n"); - fprintf(fdat, "%g %g\n", 115., -12.); - - fprintf(fdat, "%10d\n", 9999); -} - -#endif diff --git a/src/oc/code.cpp b/src/oc/code.cpp index 817d4ba656..e0a7c9794c 100644 --- a/src/oc/code.cpp +++ b/src/oc/code.cpp @@ -1877,14 +1877,6 @@ void eval(void) /* evaluate variable on stack */ case USERFLOAT: d = (sym->u.pvalfloat)[araypt(sym, SYMBOL)]; break; -#if NEMO - case NEMONODE: - hoc_eval_nemonode(sym, hoc_xpop(), &d); - break; - case NEMOAREA: - hoc_eval_nemoarea(sym, hoc_xpop(), &d); - break; -#endif /*NEMO*/ default: d = (OPVAL(sym))[araypt(sym, OBJECTVAR)]; break; @@ -1951,10 +1943,6 @@ void hoc_evalpointer() { break; case USERINT: case USERFLOAT: -#if NEMO - case NEMONODE: - case NEMOAREA: -#endif /*NEMO*/ execerror("can use pointer only to doubles", sym->name); break; default: @@ -2244,14 +2232,6 @@ void hoc_assign() { } (sym->u.pvalfloat)[ind] = (float) d2; break; -#if NEMO - case NEMONODE: - hoc_asgn_nemonode(sym, hoc_xpop(), &d2, op); - break; - case NEMOAREA: - hoc_asgn_nemoarea(sym, hoc_xpop(), &d2, op); - break; -#endif /*NEMO*/ default: ind = araypt(sym, OBJECTVAR); if (op) { diff --git a/src/oc/hocdec.h b/src/oc/hocdec.h index d1a95eb200..ab4d553bed 100644 --- a/src/oc/hocdec.h +++ b/src/oc/hocdec.h @@ -83,11 +83,7 @@ typedef char* Upoint; #define USERINT 1 /* For subtype */ #define USERDOUBLE 2 #define USERPROPERTY 3 /* for newcable non-range variables */ -#define USERFLOAT 4 /* John Miller's NEMO uses floats */ -#if NEMO -#define NEMONODE 5 /* looks syntactically like vector */ -#define NEMOAREA 6 /* looks like vector */ -#endif +#define USERFLOAT 4 #define SYMBOL 7 /* for stack type */ #define OBJECTTMP 8 /* temporary object on stack */ #define STKOBJ_UNREF 9 /* already unreffed temporary object on stack */ diff --git a/src/oc/mk_hocusr_h.py b/src/oc/mk_hocusr_h.py index 596634c51a..85bed1b1cf 100644 --- a/src/oc/mk_hocusr_h.py +++ b/src/oc/mk_hocusr_h.py @@ -49,11 +49,11 @@ def process(type, names): # the pgcc 18.4 compiler prepends with a multiline typedef and several # extern void lines that need to be skipped. Our first relevant line -# contains spatial_method or neuron2nemo or node_data +# contains spatial_method or node_data skip = 1 for line in text.splitlines(): - if "spatial_method" in line or "neuron2nemo" in line or "node_data" in line: + if "spatial_method" in line or "node_data" in line: skip = 0 names = line.replace(",", " ").replace(";", " ").split() if not skip and len(names) > 2: From 1f6f497eb4e41568c07e9d461c7278c88a66ec26 Mon Sep 17 00:00:00 2001 From: nrnhines Date: Mon, 12 Aug 2024 06:09:05 -0400 Subject: [PATCH 18/29] remove mention of spatial_method(), node_data(), NODE_DATA, METHOD3 (#3037) --- docs/guide/hoc_chapter_11_old_reference.rst | 2 +- docs/hoc/compilationoptions.rst | 21 +--- docs/python/compilationoptions.rst | 21 +--- src/nrnoc/neuron.h | 2 +- src/nrnoc/treeset.cpp | 101 -------------------- src/oc/mk_hocusr_h.py | 4 +- 6 files changed, 6 insertions(+), 145 deletions(-) diff --git a/docs/guide/hoc_chapter_11_old_reference.rst b/docs/guide/hoc_chapter_11_old_reference.rst index c09930d940..aeeb70bbf0 100644 --- a/docs/guide/hoc_chapter_11_old_reference.rst +++ b/docs/guide/hoc_chapter_11_old_reference.rst @@ -338,7 +338,7 @@ Names introduced by nrnoc .. code:: c++ - node_data disconnect batch_run batch_save + disconnect batch_run batch_save pt3dclear pt3dadd n3d x3d y3d z3d diam3d arc3d define_shape p3dconst spine3d setSpineArea getSpineArea area ri initnrn topology fadvance distance diff --git a/docs/hoc/compilationoptions.rst b/docs/hoc/compilationoptions.rst index 321290c63a..e06db2fceb 100644 --- a/docs/hoc/compilationoptions.rst +++ b/docs/hoc/compilationoptions.rst @@ -3,8 +3,7 @@ Rarely Used Compilation Options The following definitions are found in nrnoc/SRC/options.h and add extra functionality which not everyone may need. The extras come at the cost -of larger memory requirements for node and section structures. METHOD3 is too large -and obscure to benefit most users. +of larger memory requirements for node and section structures. .. code-block:: none @@ -27,21 +26,3 @@ and obscure to benefit most users. * of spine. setSpineArea() tells how much * area/spine to add to the segment. */ #endif - - #define METHOD3 1 /* third order spatially correct method */ - /* testing only, not completely implemented */ - /* not working at this time */ - - #if METHOD3 - spatial_method(i) - no arg, returns current method - i=0 The standard NEURON method with zero area nodes at the ends - of sections. - i=1 conventional method with 1/2 area end nodes - i=2 modified second order method - i=3 third order correct spatial method - Note: i=1-3 don't work under all circumstances. They have been - insufficiently tested and the correctness must be established for - each simulation. - #endif - diff --git a/docs/python/compilationoptions.rst b/docs/python/compilationoptions.rst index 73fb3805dc..e06db2fceb 100755 --- a/docs/python/compilationoptions.rst +++ b/docs/python/compilationoptions.rst @@ -3,8 +3,7 @@ Rarely Used Compilation Options The following definitions are found in nrnoc/SRC/options.h and add extra functionality which not everyone may need. The extras come at the cost -of larger memory requirements for node and section structures. METHOD3 is too large -and obscure to benefit most users. +of larger memory requirements for node and section structures. .. code-block:: none @@ -27,21 +26,3 @@ and obscure to benefit most users. * of spine. setSpineArea() tells how much * area/spine to add to the segment. */ #endif - - #define METHOD3 1 /* third order spatially correct method */ - /* testing only, not completely implemented */ - /* not working at this time */ - - #if METHOD3 - spatial_method(i) - no arg, returns current method - i=0 The standard NEURON method with zero area nodes at the ends - of sections. - i=1 conventional method with 1/2 area end nodes - i=2 modified second order method - i=3 third order correct spatial method - Note: i=1-3 don't work under all circumstances. They have been - insufficiently tested and the correctness must be established for - each simulation. - #endif - diff --git a/src/nrnoc/neuron.h b/src/nrnoc/neuron.h index 665ebe11d1..1fac159bb5 100755 --- a/src/nrnoc/neuron.h +++ b/src/nrnoc/neuron.h @@ -1,6 +1,6 @@ #include "options.h" -extern void node_data(), disconnect(); +extern void disconnect(); extern void batch_run(), batch_save(); extern void pt3dadd(), n3d(), x3d(), y3d(), z3d(), arc3d(), diam3d(); extern void pt3dclear(), pt3dinsert(), pt3dremove(), pt3dchange(); diff --git a/src/nrnoc/treeset.cpp b/src/nrnoc/treeset.cpp index ff8ca8283b..6ca37a4d1d 100644 --- a/src/nrnoc/treeset.cpp +++ b/src/nrnoc/treeset.cpp @@ -1782,107 +1782,6 @@ void v_setup_vectors(void) { } -#define NODE_DATA 0 -#if NODE_DATA -static FILE* fnd; - -#undef P -#undef Pn -#undef Pd -#undef Pg - -#define P fprintf(fnd, -#define Pn P "\n") -#define Pd(arg) P "%d\n", arg) -#define Pg(arg) P "%g\n", arg) - -void node_data_scaffolding(void) { - int i; - Pd(n_memb_func); - /* P "Mechanism names (first two are nullptr) beginning with memb_func[2]\n");*/ - for (i = 2; i < n_memb_func; ++i) { - P "%s", memb_func[i].sym->name); - Pn; - } -} - -void node_data_structure(void) { - int i, j; - nrn_thread_error("node_data_structure"); - Pd(v_node_count); - - Pd(nrn_global_ncell); - /* P "Indices of node parents\n");*/ - for (i = 0; i < v_node_count; ++i) { - Pd(v_parent[i]->v_node_index); - } - /* P "node lists for the membrane mechanisms\n");*/ - for (i = 2; i < n_memb_func; ++i) { - /* P "count, node list for mechanism %s\n", memb_func[i].sym->name);*/ - Pd(memb_list[i].nodecount); - for (j = 0; j < memb_list[i].nodecount; ++j) { - Pd(memb_list[i].nodelist[j]->v_node_index); - } - } -} - -void node_data_values(void) { - int i, j, k; - /* P "data for nodes then for all mechanisms in order of the above structure\n"); */ - for (i = 0; i < v_node_count; ++i) { - Pg(NODEV(v_node[i])); - Pg(NODEA(v_node[i])); - Pg(NODEB(v_node[i])); - Pg(NODEAREA(v_node[i])); - } - for (i = 2; i < n_memb_func; ++i) { - Prop* prop; - int cnt; - double* pd; - if (memb_list[i].nodecount) { - assert(!memb_func[i].hoc_mech); - prop = nrn_mechanism(i, memb_list[i].nodelist[0]); - cnt = prop->param_size; - Pd(cnt); - } - for (j = 0; j < memb_list[i].nodecount; ++j) { - pd = memb_list[i]._data[j]; - for (k = 0; k < cnt; ++k) { - Pg(pd[k]); - } - } - } -} - -void node_data(void) { - fnd = fopen(gargstr(1), "w"); - if (!fnd) { - hoc_execerror("node_data: can't open", gargstr(1)); - } - if (tree_changed) { - setup_topology(); - } - if (v_structure_change) { - v_setup_vectors(); - } - if (diam_changed) { - recalc_diam(); - } - node_data_scaffolding(); - node_data_structure(); - node_data_values(); - fclose(fnd); - hoc_retpushx(1.); -} - -#else -void node_data(void) { - Printf("recalc_diam=%d nrn_area_ri=%d\n", recalc_diam_count_, nrn_area_ri_count_); - hoc_retpushx(0.); -} - -#endif - void nrn_matrix_node_free() { NrnThread* nt; FOR_THREADS(nt) { diff --git a/src/oc/mk_hocusr_h.py b/src/oc/mk_hocusr_h.py index 85bed1b1cf..56f60ff688 100644 --- a/src/oc/mk_hocusr_h.py +++ b/src/oc/mk_hocusr_h.py @@ -49,11 +49,11 @@ def process(type, names): # the pgcc 18.4 compiler prepends with a multiline typedef and several # extern void lines that need to be skipped. Our first relevant line -# contains spatial_method or node_data +# contains disconnect skip = 1 for line in text.splitlines(): - if "spatial_method" in line or "node_data" in line: + if "disconnect" in line: skip = 0 names = line.replace(",", " ").replace(";", " ").split() if not skip and len(names) > 2: From cdabc3cfdbb7ab3aca18804fab4641d91f239104 Mon Sep 17 00:00:00 2001 From: Fernando Pereira Date: Tue, 13 Aug 2024 08:28:52 +0100 Subject: [PATCH 19/29] Add dev docs: py-package build+test (+ note for mac) (#3003) * Document `setup.py` options. * Document pitfall specific to Python 3.13 and Macs. --- docs/dev/setuptools/setuptools.md | 56 +++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/docs/dev/setuptools/setuptools.md b/docs/dev/setuptools/setuptools.md index 56cc37f5e2..ae449e4f7e 100644 --- a/docs/dev/setuptools/setuptools.md +++ b/docs/dev/setuptools/setuptools.md @@ -15,20 +15,64 @@ We use [setup.py](../../../setup.py) in two operational modes : 1) **wheel building** - It boils down to + It boils down to setup.py build_ext bdist_wheel - + We configure the HOC extension along with a CMake configure, build all extensios and collect them for the wheel. This is called via [build_wheels.bash](../../../packaging/python/build_wheels.bash). 2) **CMake build** It boils down to - - setup.py build_ext build - We provide the cmake build folder, in this mode we do not run CMake configure, we build all extensions and make sure they are integrated into the CMake build and install. This is called via CMake in [src/nrnpython/CMakeLists.txt](../../../src/nrnpython/CMakeLists.txt) by passing **--cmake-build-dir** (the folder where we configured NEURON with CMake), along with other CMake options. + setup.py build_ext build + + We provide the cmake build folder, in this mode we do not run CMake configure, we build all extensions and make sure they are integrated into the CMake build and install. This is called via CMake in [src/nrnpython/CMakeLists.txt](../../../src/nrnpython/CMakeLists.txt) by passing **--cmake-build-dir** (the folder where we configured NEURON with CMake), along with other CMake options. ## **Activity Diagram** -![](images/setup-py.png) \ No newline at end of file +![](images/setup-py.png) + + +## Creating a Development Python Package + +`setup.py` can be lanched manually as well to create a dev build package, which can be tested +immediately. It supports several arguments to that the build can be tuned: + +``` + --disable-rx3d Disables Rx3d. Implies CMake -DNRN_ENABLE_RX3D=OFF + --disable-iv Disables IV. Implies CMake -DNRN_ENABLE_INTERVIEWS=OFF + --disable-mpi Disables MPI. Implies -DNRN_ENABLE_MPI=OFF and disabling of neuronmusic + --enable-music Enables neuronmusic + --enable-coreneuron Enables experimental CorenNeuron support + --rx3d-opt-level Sets the rx3d optimization level. Default: 0 (-O0) + --cmake-build-dir Declares one wants to use a specic NEURON build (with CMake), instead + of creating one behind the scenes. Only builds extensions and package. +``` + +A quick build for testing a change to a core component could therefore be: +``` +python setup.py build --disable-rx3d --disable-iv --disable-mpi +``` + +--- +**Mac Note** + +Since the introduction of Mac with Apple silicon, with an Official Python distribution, extension are simultaneously built for arm64 and x86_64. Besides not required for a dev build, it may fail +on older SDKs with `Unsupported architecture` errors. + +To skip that you may set the ARCHFLAGS environment var. Set it to "" (empty) for default architecture: `ARCHFLAGS='' python setup.py ...` + +--- + +### Testing + +Once built, the package may be imported and used normally. You might, however, need to set up +PYTHONPATH accordingly for the import to work: + +``` +export PYTHONPATH="/build/lib.macosx-10.9-x86_64-3.9/:$PYTHONPATH" + +# Run Neuron base tests +python -c "import neuron; neuron.test()" +``` From 61dfd7c00b2ec2617f76e7946b6ac0ebd24b0877 Mon Sep 17 00:00:00 2001 From: Koen van Walstijn <38299796+kbvw@users.noreply.github.com> Date: Tue, 13 Aug 2024 11:58:44 +0200 Subject: [PATCH 20/29] Support `cython>=3` and remove `cython<3` pin. (#3028) * Change Cython language setting to C instead of C++ except for MUSIC --- .github/workflows/coverage.yml | 2 +- .github/workflows/neuron-ci.yml | 2 +- CMakeLists.txt | 6 ------ ci/win_build_cmake.sh | 2 +- ci/win_install_deps.cmd | 10 +++++----- docs/conda_environment.yml | 2 +- docs/install/install_instructions.md | 2 +- nrn_requirements.txt | 2 +- packaging/python/build_requirements.txt | 2 +- setup.py | 4 ++-- share/lib/python/neuron/rxd/geometry3d/surfaces.pyx | 4 +--- 11 files changed, 15 insertions(+), 23 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index cde6c2c8a0..96ee062936 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -58,7 +58,7 @@ jobs: run: | python3 -m venv music-venv source music-venv/bin/activate - python3 -m pip install 'mpi4py<4' "cython<3" 'numpy<2' setuptools + python3 -m pip install 'mpi4py<4' "cython" 'numpy<2' setuptools sudo mkdir -p $MUSIC_INSTALL_DIR sudo chown -R $USER $MUSIC_INSTALL_DIR curl -L -o MUSIC.zip https://github.com/INCF/MUSIC/archive/refs/tags/${MUSIC_VERSION}.zip diff --git a/.github/workflows/neuron-ci.yml b/.github/workflows/neuron-ci.yml index 6f3076290f..abd42d2873 100644 --- a/.github/workflows/neuron-ci.yml +++ b/.github/workflows/neuron-ci.yml @@ -207,7 +207,7 @@ jobs: run: | python3 -m venv music-venv source music-venv/bin/activate - python3 -m pip install 'mpi4py<4' "cython<3" 'numpy<2' setuptools + python3 -m pip install 'mpi4py<4' "cython" 'numpy<2' setuptools sudo mkdir -p $MUSIC_INSTALL_DIR sudo chown -R $USER $MUSIC_INSTALL_DIR curl -L -o MUSIC.zip https://github.com/INCF/MUSIC/archive/refs/tags/${MUSIC_VERSION}.zip diff --git a/CMakeLists.txt b/CMakeLists.txt index c5dee70d64..1b74dbc9a4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -325,9 +325,6 @@ if(NRN_ENABLE_RX3D) message(SEND_ERROR "NRN_ENABLE_RX3D requires NRN_ENABLE_PYTHON feature.") else() find_package(Cython REQUIRED) - if(CYTHON_VERSION VERSION_GREATER_EQUAL 3) - message(FATAL_ERROR "Cython 3+ is not supported") - endif() endif() endif() if(MINGW) @@ -382,9 +379,6 @@ if(NRN_ENABLE_MUSIC) message(FATAL "MUSIC requires -DNRN_ENABLE_PYTHON=ON") endif() find_package(Cython REQUIRED) - if(CYTHON_VERSION VERSION_GREATER_EQUAL 3) - message(FATAL_ERROR "Cython 3+ is not supported") - endif() find_package(MUSIC REQUIRED) set(NRN_MUSIC 1) endif() diff --git a/ci/win_build_cmake.sh b/ci/win_build_cmake.sh index c3c84ad37e..fcf5fe419c 100755 --- a/ci/win_build_cmake.sh +++ b/ci/win_build_cmake.sh @@ -10,7 +10,7 @@ export MSYSTEM_PREFIX=/mingw64 export PATH=/mingw64/bin:$PATH # have compatible cython3 -python3 -m pip install "cython<3" +python3 -m pip install "cython" # if BUILD_SOURCESDIRECTORY not available, use te root of the repo if [ -z "$BUILD_SOURCESDIRECTORY" ]; then diff --git a/ci/win_install_deps.cmd b/ci/win_install_deps.cmd index c75e977a10..b27cfba3af 100644 --- a/ci/win_install_deps.cmd +++ b/ci/win_install_deps.cmd @@ -23,11 +23,11 @@ pwsh -command "(Get-Content C:\Python310\Lib\distutils\cygwinccompiler.py) -repl pwsh -command "(Get-Content C:\Python311\Lib\distutils\cygwinccompiler.py) -replace 'msvcr100', 'msvcrt' | Out-File C:\Python311\Lib\distutils\cygwinccompiler.py" :: install numpy -C:\Python38\python.exe -m pip install numpy==1.17.5 "cython < 3" || goto :error -C:\Python39\python.exe -m pip install numpy==1.19.3 "cython < 3" || goto :error -C:\Python310\python.exe -m pip install numpy==1.21.3 "cython < 3" || goto :error -C:\Python311\python.exe -m pip install numpy==1.23.5 "cython < 3" || goto :error -C:\Python312\python.exe -m pip install numpy==1.26.3 "cython < 3" || goto :error +C:\Python38\python.exe -m pip install numpy==1.17.5 "cython" || goto :error +C:\Python39\python.exe -m pip install numpy==1.19.3 "cython" || goto :error +C:\Python310\python.exe -m pip install numpy==1.21.3 "cython" || goto :error +C:\Python311\python.exe -m pip install numpy==1.23.5 "cython" || goto :error +C:\Python312\python.exe -m pip install numpy==1.26.3 "cython" || goto :error :: setuptools 70.2 leads to an error C:\Python312\python.exe -m pip install setuptools==70.1.1 || goto :error diff --git a/docs/conda_environment.yml b/docs/conda_environment.yml index d13bdc8775..6abc60862b 100644 --- a/docs/conda_environment.yml +++ b/docs/conda_environment.yml @@ -9,7 +9,7 @@ dependencies: - cmake - xorg-libxcomposite - ffmpeg - - cython<3 + - cython - pandoc - mpich - pip diff --git a/docs/install/install_instructions.md b/docs/install/install_instructions.md index 209509b371..af7a86583f 100644 --- a/docs/install/install_instructions.md +++ b/docs/install/install_instructions.md @@ -207,7 +207,7 @@ In order to build NEURON from source, the following packages must be available: The following packages are optional (see build options): - Python >=3.8 (for Python interface) -- Cython < 3 (for RXD) +- Cython (for RXD) - MPI (for parallel) - X11 (Linux) or XQuartz (MacOS) (for GUI) diff --git a/nrn_requirements.txt b/nrn_requirements.txt index d78453146a..c28bc995bb 100644 --- a/nrn_requirements.txt +++ b/nrn_requirements.txt @@ -6,7 +6,7 @@ matplotlib # bokeh 3 seems to break docs notebooks bokeh<3 ipython -cython<3 +cython packaging pytest<=8.1.1 # potential bug from 8.2.0 due to parallelism? pytest-cov diff --git a/packaging/python/build_requirements.txt b/packaging/python/build_requirements.txt index 5fe1763173..29d2c1b080 100644 --- a/packaging/python/build_requirements.txt +++ b/packaging/python/build_requirements.txt @@ -1,4 +1,4 @@ -cython<3 +cython packaging numpy==1.17.5;python_version=='3.8' numpy==1.19.3;python_version=='3.9' and platform_machine!='arm64' diff --git a/setup.py b/setup.py index 3d944d1332..d27d995c11 100644 --- a/setup.py +++ b/setup.py @@ -354,7 +354,7 @@ def setup_package(): NRN_COLLECT_DIRS = ["bin", "lib", "include", "share"] docs_require = [] # sphinx, themes, etc - maybe_rxd_reqs = ["numpy<2", "Cython<3"] if Components.RX3D else [] + maybe_rxd_reqs = ["numpy<2", "Cython"] if Components.RX3D else [] maybe_docs = docs_require if "docs" in sys.argv else [] maybe_test_runner = ["pytest-runner"] if "test" in sys.argv else [] @@ -379,7 +379,6 @@ def setup_package(): list, library_dirs=[os.path.join(cmake_build_dir, "lib")], libraries=ext_common_libraries, - language="c++", ) logging.info("Extension common compile flags %s" % str(extension_common_params)) @@ -436,6 +435,7 @@ def setup_package(): "neuronmusic", ["src/neuronmusic/neuronmusic.pyx"], include_dirs=["src/nrnpython", "src/nrnmusic"], + language="c++", **extension_common_params, ) ] diff --git a/share/lib/python/neuron/rxd/geometry3d/surfaces.pyx b/share/lib/python/neuron/rxd/geometry3d/surfaces.pyx index 141adf7899..bf3aac2eb1 100644 --- a/share/lib/python/neuron/rxd/geometry3d/surfaces.pyx +++ b/share/lib/python/neuron/rxd/geometry3d/surfaces.pyx @@ -1,13 +1,11 @@ import os import numpy -import graphicsPrimitives -import neuron -import numpy cimport numpy import itertools import bisect cimport cython +from neuron.rxd.geometry3d import graphicsPrimitives """ The surfaces module From 09d04901bb0b4101ebea2f64ffea7616abf735ad Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 14 Aug 2024 11:01:09 +0200 Subject: [PATCH 21/29] Use nanobind for python BBS implementation (#3034) --- src/nrnpython/nrnpy_p2h.cpp | 61 +++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/src/nrnpython/nrnpy_p2h.cpp b/src/nrnpython/nrnpy_p2h.cpp index 0d765bb6a5..fddf59ebbe 100644 --- a/src/nrnpython/nrnpy_p2h.cpp +++ b/src/nrnpython/nrnpy_p2h.cpp @@ -1,6 +1,7 @@ #include <../../nrnconf.h> #include + #include #include #include @@ -11,6 +12,10 @@ #include "oc_ansi.h" #include "parse.hpp" + +#include +namespace nb = nanobind; + static void nrnpy_decref_defer(PyObject*); static char* nrnpyerr_str(); static PyObject* nrnpy_pyCallObject(PyObject*, PyObject*); @@ -607,32 +612,31 @@ static int guigetstr(Object* ho, char** cpp) { return 1; } -static PyObject* loads; -static PyObject* dumps; +static nb::callable loads; +static nb::callable dumps; static void setpickle() { if (dumps) { return; } - PyObject* pickle = PyImport_ImportModule("pickle"); - if (pickle) { - Py_INCREF(pickle); - dumps = PyObject_GetAttrString(pickle, "dumps"); - loads = PyObject_GetAttrString(pickle, "loads"); - if (dumps) { - Py_INCREF(dumps); - Py_INCREF(loads); - } - } + nb::module_ pickle = nb::module_::import_("pickle"); + dumps = pickle.attr("dumps"); + loads = pickle.attr("loads"); if (!dumps || !loads) { hoc_execerror("Neither Python cPickle nor pickle are available", 0); } + // We intentionally leak these, because if we don't + // we observe SEGFAULTS during application shutdown. + // Likely because "~nb::callable" runs after the Python + // library cleaned up. + dumps.inc_ref(); + loads.inc_ref(); } // note that *size includes the null terminating character if it exists static std::vector pickle(PyObject* p) { PyObject* arg = PyTuple_Pack(1, p); - PyObject* r = nrnpy_pyCallObject(dumps, arg); + PyObject* r = nrnpy_pyCallObject(dumps.ptr(), arg); Py_XDECREF(arg); if (!r && PyErr_Occurred()) { PyErr_Print(); @@ -656,25 +660,21 @@ static std::vector po2pickle(Object* ho) { } } -static PyObject* unpickle(const char* s, std::size_t len) { - PyObject* ps = PyBytes_FromStringAndSize(s, len); - PyObject* arg = PyTuple_Pack(1, ps); - PyObject* po = nrnpy_pyCallObject(loads, arg); - assert(po); - Py_XDECREF(arg); - Py_XDECREF(ps); - return po; +static nb::object unpickle(const char* s, std::size_t len) { + nb::bytes string(s, len); + nb::list args; + args.append(string); + return loads(*args); } -static PyObject* unpickle(const std::vector& s) { +static nb::object unpickle(const std::vector& s) { return unpickle(s.data(), s.size()); } static Object* pickle2po(const std::vector& s) { setpickle(); - PyObject* po = unpickle(s); - Object* ho = nrnpy_pyobject_in_obj(po); - Py_XDECREF(po); + nb::object po = unpickle(s); + Object* ho = nrnpy_pyobject_in_obj(po.ptr()); return ho; } @@ -750,7 +750,7 @@ std::vector call_picklef(const std::vector& fname, int narg) { setpickle(); PyObject* ps = PyBytes_FromStringAndSize(fname.data(), fname.size()); args = PyTuple_Pack(1, ps); - callable = nrnpy_pyCallObject(loads, args); + callable = nrnpy_pyCallObject(loads.ptr(), args); assert(callable); Py_XDECREF(args); Py_XDECREF(ps); @@ -801,7 +801,8 @@ static PyObject* char2pylist(char* buf, int np, int* cnt, int* displ) { Py_INCREF(Py_None); // 'Fatal Python error: deallocating None' eventually PyList_SetItem(plist, i, Py_None); } else { - PyObject* p = unpickle(buf + displ[i], cnt[i]); + nb::object po = unpickle(buf + displ[i], cnt[i]); + PyObject* p = po.release().ptr(); PyList_SetItem(plist, i, p); } } @@ -874,7 +875,8 @@ static PyObject* py_broadcast(PyObject* psrc, int root) { nrnmpi_char_broadcast(buf.data(), cnt, root); PyObject* pdest = psrc; if (root != nrnmpi_myid) { - pdest = unpickle(buf); + nb::object po = unpickle(buf); + pdest = po.release().ptr(); } else { Py_INCREF(pdest); } @@ -1082,7 +1084,8 @@ static Object* py_alltoall_type(int size, int type) { delete[] sdispl; if (rcnt[0]) { - pdest = unpickle(r); + nb::object po = unpickle(r); + pdest = po.release().ptr(); } else { pdest = Py_None; Py_INCREF(pdest); From e72152896028cc88402ee335da96e8df95597d4f Mon Sep 17 00:00:00 2001 From: MikeG Date: Thu, 15 Aug 2024 11:57:06 +0200 Subject: [PATCH 22/29] Cleanup `PyMemberDef` (#3031) --- src/nrnpython/nrnpy_nrn.cpp | 27 +++------------------------ 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/src/nrnpython/nrnpy_nrn.cpp b/src/nrnpython/nrnpy_nrn.cpp index 17ff995ffe..5edf14cec7 100644 --- a/src/nrnpython/nrnpy_nrn.cpp +++ b/src/nrnpython/nrnpy_nrn.cpp @@ -699,7 +699,7 @@ static PyObject* NPySecObj_n3d_safe(NPySecObj* self) { static PyObject* NPySecObj_pt3dremove(NPySecObj* self, PyObject* args) { Section* sec = self->sec_; CHECK_SEC_INVALID(sec); - int i0, n; + int i0; if (!PyArg_ParseTuple(args, "i", &i0)) { return NULL; } @@ -1232,8 +1232,6 @@ static long pyseg_hash_safe(PyObject* self) { } static PyObject* pyseg_richcmp(NPySegObj* self, PyObject* other, int op) { - PyObject* pysec; - bool result = false; NPySegObj* seg = (NPySegObj*) self; void* self_ptr = (void*) node_exact(seg->pysec_->sec_, seg->x_); void* other_ptr = (void*) other; @@ -1250,8 +1248,6 @@ static PyObject* pyseg_richcmp_safe(NPySegObj* self, PyObject* other, int op) { static PyObject* pysec_richcmp(NPySecObj* self, PyObject* other, int op) { - PyObject* pysec; - bool result = false; void* self_ptr = (void*) (self->sec_); void* other_ptr = (void*) other; if (PyObject_TypeCheck(other, psection_type)) { @@ -1321,7 +1317,6 @@ static PyObject* NPyMechFunc_name_safe(NPyMechFunc* self) { static PyObject* NPyMechFunc_call(NPyMechFunc* self, PyObject* args) { CHECK_PROP_INVALID(self->pymech_->prop_id_); PyObject* result = NULL; - auto pyseg = self->pymech_->pyseg_; auto& f = self->f_->func; // patterning after fcall @@ -2339,7 +2334,6 @@ static int segment_setattro(NPySegObj* self, PyObject* pyname, PyObject* value) } // printf("segment_setattro %s\n", n); if (strcmp(n, "x") == 0) { - int nseg; double x; if (PyArg_Parse(value, "d", &x) == 1 && x > 0. && x <= 1.) { if (x < 1e-9) { @@ -2924,23 +2918,9 @@ static PyMethodDef NPySegObj_methods[] = { {"volume", (PyCFunction) seg_volume_safe, METH_NOARGS, "Segment volume (um3)"}, {NULL}}; -// I'm guessing Python should change their typedef to get rid of the -// four "deprecated conversion from string constant to 'char*'" warnings. -// Could avoid by casting each to (char*) but probably better to keep the -// warnings. For now we get rid of the warnings by copying the string to -// char array. -static char* cpstr(const char* s) { - char* s2 = new char[strlen(s) + 1]; - strcpy(s2, s); - return s2; -} static PyMemberDef NPySegObj_members[] = { - {cpstr("x"), - T_DOUBLE, - offsetof(NPySegObj, x_), - 0, - cpstr("location in the section (segment containing x)")}, - {cpstr("sec"), T_OBJECT_EX, offsetof(NPySegObj, pysec_), 0, cpstr("Section")}, + {"x", T_DOUBLE, offsetof(NPySegObj, x_), 0, "location in the section (segment containing x)"}, + {"sec", T_OBJECT_EX, offsetof(NPySegObj, pysec_), 0, "Section"}, {NULL}}; static PyMethodDef NPyMechObj_methods[] = { @@ -3014,7 +2994,6 @@ static void rangevars_add(Symbol* sym) { } PyObject* nrnpy_nrn(void) { - int i; PyObject* m; int err = 0; From c918f6b9ff9be32828e882ed51106d8449e7cb6c Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Thu, 15 Aug 2024 14:07:01 +0200 Subject: [PATCH 23/29] nanobind: use loads and dumps as callable (#3043) --- src/nrnpython/nrnpy_p2h.cpp | 47 ++++++++++--------------------------- 1 file changed, 12 insertions(+), 35 deletions(-) diff --git a/src/nrnpython/nrnpy_p2h.cpp b/src/nrnpython/nrnpy_p2h.cpp index fddf59ebbe..0164c2b667 100644 --- a/src/nrnpython/nrnpy_p2h.cpp +++ b/src/nrnpython/nrnpy_p2h.cpp @@ -635,19 +635,12 @@ static void setpickle() { // note that *size includes the null terminating character if it exists static std::vector pickle(PyObject* p) { - PyObject* arg = PyTuple_Pack(1, p); - PyObject* r = nrnpy_pyCallObject(dumps.ptr(), arg); - Py_XDECREF(arg); + auto r = nb::borrow(dumps(nb::borrow(p))); if (!r && PyErr_Occurred()) { PyErr_Print(); } assert(r); - assert(PyBytes_Check(r)); - std::size_t size = PyBytes_Size(r); - char* buf = PyBytes_AsString(r); - std::vector ret(buf, buf + size); - Py_XDECREF(r); - return ret; + return std::vector(r.c_str(), r.c_str() + r.size()); } static std::vector po2pickle(Object* ho) { @@ -661,10 +654,7 @@ static std::vector po2pickle(Object* ho) { } static nb::object unpickle(const char* s, std::size_t len) { - nb::bytes string(s, len); - nb::list args; - args.append(string); - return loads(*args); + return loads(nb::bytes(s, len)); } static nb::object unpickle(const std::vector& s) { @@ -743,33 +733,22 @@ std::vector call_picklef(const std::vector& fname, int narg) { // fname is a pickled callable, narg is the number of args on the // hoc stack with types double, char*, hoc Vector, and PythonObject // callable return must be pickleable. - PyObject* args = 0; - PyObject* result = 0; - PyObject* callable; - setpickle(); - PyObject* ps = PyBytes_FromStringAndSize(fname.data(), fname.size()); - args = PyTuple_Pack(1, ps); - callable = nrnpy_pyCallObject(loads.ptr(), args); + nb::bytes ps(fname.data(), fname.size()); + + auto callable = nb::borrow(loads(ps)); assert(callable); - Py_XDECREF(args); - Py_XDECREF(ps); - args = PyTuple_New(narg); + nb::list args{}; for (int i = 0; i < narg; ++i) { - PyObject* arg = nrnpy_hoc_pop("call_picklef"); - if (PyTuple_SetItem(args, narg - 1 - i, arg)) { - assert(0); - } - // Py_XDECREF(arg); + nb::object arg = nb::steal(nrnpy_hoc_pop("call_picklef")); + args.append(arg); } - result = nrnpy_pyCallObject(callable, args); - Py_DECREF(callable); - Py_DECREF(args); + nb::object result = callable(*args); if (!result) { char* mes = nrnpyerr_str(); if (mes) { - Fprintf(stderr, "%s\n", mes); + std::cerr << mes << std::endl; free(mes); hoc_execerror("PyObject method call failed:", NULL); } @@ -777,9 +756,7 @@ std::vector call_picklef(const std::vector& fname, int narg) { PyErr_Print(); } } - auto rs = pickle(result); - Py_XDECREF(result); - return rs; + return pickle(result.ptr()); } #include "nrnmpi.h" From 48440295517b7989f876c54529d46db9d89978f8 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Mon, 26 Aug 2024 11:37:27 +0200 Subject: [PATCH 24/29] Improve wheel-building experience on multi-arch MacOS (#3038) The build_static_readline_osx.bash script is a bit inflexible and cumbersome to use if trying to perform NEURON builds on both arm64 and x86_64; for instance, it installs everything to /opt/nrnwheel, which means that it doesn't really differentiate between arches, so if one wants to build a wheel, one has to remember to (re)install the readline library for the correct arch, otherwise we end up with many warnings about incompatible archs, and eventually the linker complaining about missing symbols. This PR basically improves this by moving the install location to /opt/nrnwheel/[ARCH], where [ARCH] is detected the usual way. This means we can use build_static_readline_osx.bash on either arch, and it won't clobber any existing install of the other arch. One can also specify a custom install location by passing a single argument to build_static_readline_osx.bash. Note that Linux builds should not be affected by any of these changes. --- azure-pipelines.yml | 4 +-- .../python/build_static_readline_osx.bash | 30 +++++++++---------- packaging/python/build_wheels.bash | 2 +- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b7dd958f4a..238988a536 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -148,8 +148,8 @@ stages: export PATH=/usr/local/opt/flex/bin:/usr/local/opt/bison/bin:$PATH export SDKROOT=$(xcrun --sdk macosx --show-sdk-path) export NRN_BUILD_FOR_UPLOAD=1 - sudo mkdir /opt/nrnwheel - sudo tar -zxf $(readlineSF.secureFilePath) --directory /opt/nrnwheel/ + sudo mkdir -p /opt/nrnwheel/$(uname -m) + sudo tar -zxf $(readlineSF.secureFilePath) --directory /opt/nrnwheel/$(uname -m) packaging/python/build_wheels.bash osx $(python.version) coreneuron displayName: 'Build MacOS Wheel' diff --git a/packaging/python/build_static_readline_osx.bash b/packaging/python/build_static_readline_osx.bash index efe0bf09d5..26919f8958 100644 --- a/packaging/python/build_static_readline_osx.bash +++ b/packaging/python/build_static_readline_osx.bash @@ -1,57 +1,55 @@ #!/usr/bin/env bash -set -xe +set -eux # A script to build a static readline library for osx # # PREREQUESITES: # - curl -# - wget # - C/C++ compiler -# - /opt/nrnwheel folder created with access rights (this specific path is kept for consistency wrt `build_wheels.bash`) +# - /opt/nrnwheel/[ARCH] folder created with access rights (this specific path is kept for consistency wrt `build_wheels.bash`) -set -e - -if [[ `uname -s` != 'Darwin' ]]; then +if [[ "$(uname -s)" != 'Darwin' ]]; then echo "Error: this script is for macOS only. readline is already built statically in the linux Docker images" exit 1 fi -NRNWHEEL_DIR=/opt/nrnwheel +ARCH="$(uname -m)" + +NRNWHEEL_DIR="${1:-/opt/nrnwheel/${ARCH}}" if [[ ! -d "$NRNWHEEL_DIR" || ! -x "$NRNWHEEL_DIR" ]]; then - echo "Error: /opt/nrnwheel must exist and be accessible, i.e: sudo mkdir -p /opt/nrnwheel && sudo chown -R $USER /opt/nrnwheel" + echo "Error: ${NRNWHEEL_DIR} must exist and be accessible, i.e: sudo mkdir -p ${NRNWHEEL_DIR} && sudo chown -R ${USER} ${NRNWHEEL_DIR}" exit 1 fi # Set MACOSX_DEPLOYMENT_TARGET based on wheel arch. # For upcoming `universal2` wheels we will consider leveling everything to 11.0. -if [[ `uname -m` == 'arm64' ]]; then +if [[ "${ARCH}" == 'arm64' ]]; then export MACOSX_DEPLOYMENT_TARGET=11.0 # for arm64 we need 11.0 else export MACOSX_DEPLOYMENT_TARGET=10.9 # for x86_64 fi -(wget http://ftpmirror.gnu.org/ncurses/ncurses-6.4.tar.gz \ +(curl -L -o ncurses-6.4.tar.gz http://ftpmirror.gnu.org/ncurses/ncurses-6.4.tar.gz \ && tar -xvzf ncurses-6.4.tar.gz \ && cd ncurses-6.4 \ - && ./configure --prefix=/opt/nrnwheel/ncurses --without-shared CFLAGS="-fPIC" \ + && ./configure --prefix="${NRNWHEEL_DIR}/ncurses" --without-shared CFLAGS="-fPIC" \ && make -j install) (curl -L -o readline-7.0.tar.gz https://ftp.gnu.org/gnu/readline/readline-7.0.tar.gz \ && tar -xvzf readline-7.0.tar.gz \ && cd readline-7.0 \ - && ./configure --prefix=/opt/nrnwheel/readline --disable-shared CFLAGS="-fPIC" \ + && ./configure --prefix="${NRNWHEEL_DIR}/readline" --disable-shared CFLAGS="-fPIC" \ && make -j install) -(cd /opt/nrnwheel/readline/lib \ +(cd "${NRNWHEEL_DIR}/readline/lib" \ && ar -x libreadline.a \ && ar -x ../../ncurses/lib/libncurses.a \ && ar cq libreadline.a *.o \ && rm *.o) -RDL_MINOS=`otool -l /opt/nrnwheel/readline/lib/libreadline.a | grep -e "minos \|version " | uniq | awk '{print $2}'` +RDL_MINOS="$(otool -l "${NRNWHEEL_DIR}/readline/lib/libreadline.a" | grep -e "minos \|version " | uniq | awk '{print $2}')" if [ "$RDL_MINOS" != "$MACOSX_DEPLOYMENT_TARGET" ]; then - echo "Error: /opt/nrnwheel/readline/lib/libreadline.a doesn't match MACOSX_DEPLOYMENT_TARGET ($MACOSX_DEPLOYMENT_TARGET)" + echo "Error: ${NRNWHEEL_DIR}/readline/lib/libreadline.a doesn't match MACOSX_DEPLOYMENT_TARGET ($MACOSX_DEPLOYMENT_TARGET)" exit 1 fi - echo "Done." diff --git a/packaging/python/build_wheels.bash b/packaging/python/build_wheels.bash index 4da4f01f06..9f4eb34ba0 100755 --- a/packaging/python/build_wheels.bash +++ b/packaging/python/build_wheels.bash @@ -160,7 +160,7 @@ build_wheel_osx() { fi fi - python setup.py build_ext --cmake-prefix="/opt/nrnwheel/ncurses;/opt/nrnwheel/readline;/usr/x11" --cmake-defs="$CMAKE_DEFS" $setup_args bdist_wheel + python setup.py build_ext --cmake-prefix="/opt/nrnwheel/$(uname -m)/ncurses;/opt/nrnwheel/$(uname -m)/readline;/usr/x11" --cmake-defs="$CMAKE_DEFS" $setup_args bdist_wheel echo " - Calling delocate-listdeps" delocate-listdeps dist/*.whl From 45a7e6cf5eacf7190f21c070899da75f2db567b5 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Tue, 27 Aug 2024 15:04:04 +0200 Subject: [PATCH 25/29] Remove `ERRCHK` macro (#3056) --- src/oc/fileio.cpp | 12 +++++++----- src/oc/hocdec.h | 2 -- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/oc/fileio.cpp b/src/oc/fileio.cpp index 455d0220a1..8b59284e79 100644 --- a/src/oc/fileio.cpp +++ b/src/oc/fileio.cpp @@ -91,14 +91,16 @@ void wopen(void) /* open file for writing */ else fname = ""; d = 1.; - if (fout != stdout) - ERRCHK(IGNORE(fclose(fout));) + if (fout != stdout) { + IGNORE(fclose(fout)); + } fout = stdout; - if (fname[0] != 0) - ERRCHK(if ((fout = fopen(expand_env_var(fname), "w")) == (FILE*) 0) { + if (fname[0] != 0) { + if ((fout = fopen(expand_env_var(fname), "w")) == nullptr) { d = 0.; fout = stdout; - }) + } + } errno = 0; ret(); pushx(d); diff --git a/src/oc/hocdec.h b/src/oc/hocdec.h index ab4d553bed..a8f5b31826 100644 --- a/src/oc/hocdec.h +++ b/src/oc/hocdec.h @@ -276,8 +276,6 @@ int ilint; #endif using neuron::Sprintf; -#define ERRCHK(c1) c1 - // No longer used because of clang format difficulty // #define IFGUI if (hoc_usegui) { // #define ENDGUI } From 94e41b638652adee0dcbf4506540db8152ca3525 Mon Sep 17 00:00:00 2001 From: Matthias Wolf Date: Thu, 29 Aug 2024 10:24:18 +0200 Subject: [PATCH 26/29] Make OpenMP handling more canonical. (#3050) Future depend on the appropriate OpenMP target, and add the compiler flags separately (so that we can extract them and add them into the mechanism generating Makefile). ADDITIONAL_THREADSAFE_FLAGS has no other reference - a web search shows no usage, either. --- cmake/coreneuron/MakefileBuildOptions.cmake | 8 +++++++- packaging/python/build_wheels.bash | 12 ++++++------ setup.py | 1 - src/coreneuron/CMakeLists.txt | 16 ++++++++++------ 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/cmake/coreneuron/MakefileBuildOptions.cmake b/cmake/coreneuron/MakefileBuildOptions.cmake index 1ab2de8e92..5f7119dddf 100644 --- a/cmake/coreneuron/MakefileBuildOptions.cmake +++ b/cmake/coreneuron/MakefileBuildOptions.cmake @@ -73,13 +73,19 @@ function(coreneuron_process_target target) if(target_imported) # In this case we can extract the full path to the library get_target_property(target_location ${target} LOCATION) - coreneuron_process_library_path(${target_location}) + if(target_location) + coreneuron_process_library_path(${target_location}) + endif() else() # This is probably another of our libraries, like -lcoreneuron-cuda. We might need to add -L # and an RPATH later. set_property(GLOBAL APPEND_STRING PROPERTY CORENRN_LIB_LINK_DEP_FLAGS " -l${target}") endif() endif() + get_target_property(target_flags ${target} INTERFACE_COMPILE_OPTIONS) + if(target_flags) + set_property(GLOBAL APPEND_STRING PROPERTY CORENRN_EXTRA_COMPILE_FLAGS " ${target_flags}") + endif() get_target_property(target_libraries ${target} LINK_LIBRARIES) if(target_libraries) foreach(child_target ${target_libraries}) diff --git a/packaging/python/build_wheels.bash b/packaging/python/build_wheels.bash index 9f4eb34ba0..eeebf32f21 100755 --- a/packaging/python/build_wheels.bash +++ b/packaging/python/build_wheels.bash @@ -71,7 +71,7 @@ build_wheel_linux() { if [ "$2" == "coreneuron" ]; then setup_args="--enable-coreneuron" clone_nmodl_and_add_requirements - CMAKE_DEFS="${CMAKE_DEFS},LINK_AGAINST_PYTHON=OFF" + CMAKE_DEFS="${CMAKE_DEFS},LINK_AGAINST_PYTHON=OFF,CORENRN_ENABLE_OPENMP=ON" fi cat my_requirements.txt @@ -118,17 +118,17 @@ build_wheel_osx() { echo " - Installing build requirements" cp packaging/python/build_requirements.txt my_requirements.txt + CMAKE_DEFS="NRN_MPI_DYNAMIC=$3" + if [ "$USE_STATIC_READLINE" == "1" ]; then + CMAKE_DEFS="$CMAKE_DEFS,NRN_BINARY_DIST_BUILD=ON,NRN_WHEEL_STATIC_READLINE=ON" + fi + if [ "$2" == "coreneuron" ]; then setup_args="--enable-coreneuron" clone_nmodl_and_add_requirements CMAKE_DEFS="${CMAKE_DEFS},LINK_AGAINST_PYTHON=OFF" fi - CMAKE_DEFS="NRN_MPI_DYNAMIC=$3" - if [ "$USE_STATIC_READLINE" == "1" ]; then - CMAKE_DEFS="$CMAKE_DEFS,NRN_BINARY_DIST_BUILD=ON,NRN_WHEEL_STATIC_READLINE=ON" - fi - cat my_requirements.txt pip install -U delocate -r my_requirements.txt pip check diff --git a/setup.py b/setup.py index d27d995c11..bca41a0c2e 100644 --- a/setup.py +++ b/setup.py @@ -406,7 +406,6 @@ def setup_package(): ] + ( [ - "-DCORENRN_ENABLE_OPENMP=ON", # TODO: manylinux portability questions "-DNMODL_ENABLE_PYTHON_BINDINGS=ON", ] if Components.CORENRN diff --git a/src/coreneuron/CMakeLists.txt b/src/coreneuron/CMakeLists.txt index cf4fdf4903..1cf452d23c 100644 --- a/src/coreneuron/CMakeLists.txt +++ b/src/coreneuron/CMakeLists.txt @@ -29,7 +29,7 @@ include(${CODING_CONV_CMAKE}/build-time-copy.cmake) # ============================================================================= # Build options # ============================================================================= -option(CORENRN_ENABLE_OPENMP "Build the CORE NEURON with OpenMP implementation" ON) +option(CORENRN_ENABLE_OPENMP "Build the CORE NEURON with OpenMP implementation" OFF) option(CORENRN_ENABLE_OPENMP_OFFLOAD "Prefer OpenMP target offload to OpenACC" ON) option(CORENRN_ENABLE_TIMEOUT "Enable nrn_timeout implementation" ON) option(CORENRN_ENABLE_REPORTING "Enable use of libsonata for soma reports" OFF) @@ -197,11 +197,7 @@ if(NRN_ENABLE_MPI_DYNAMIC) endif() if(CORENRN_ENABLE_OPENMP) - find_package(OpenMP QUIET) - if(OPENMP_FOUND) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS} ${ADDITIONAL_THREADSAFE_FLAGS}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS} ${ADDITIONAL_THREADSAFE_FLAGS}") - endif() + find_package(OpenMP REQUIRED) endif() list(APPEND CORENRN_COMPILE_DEFS LAYOUT=0) @@ -444,6 +440,14 @@ endif() # https://forums.developer.nvidia.com/t/cannot-dynamically-load-a-shared-library-containing-both-openacc-and-cuda-code/210972 add_library(coreneuron-core STATIC ${CORENEURON_CODE_FILES} ${CORENRN_MPI_OBJ}) add_dependencies(coreneuron-core coreneuron-copy-nrnivmodl-core-dependencies) +if(CORENRN_ENABLE_OPENMP) + # target_link_libraries(coreneuron-core PUBLIC OpenMP::OpenMP_CXX) + target_compile_options(coreneuron-core PUBLIC ${OpenMP_CXX_FLAGS}) + target_link_libraries(coreneuron-core PUBLIC ${OpenMP_CXX_LIB_NAMES}) + # The first line uses a generator expression to arrive at -fopenmp (or equivalent) - this will not + # work with our Makefile generation. Thus specify the flag manually. Enable the first line and + # remove the last two lines below once we get rid of the mechanism building Makefile. +endif() if(CORENRN_ENABLE_GPU) set(coreneuron_cuda_target coreneuron-cuda) add_library(coreneuron-cuda ${COMPILE_LIBRARY_TYPE} ${CORENEURON_CUDA_FILES}) From 29960069f09bc7288137ed2c315812a875268320 Mon Sep 17 00:00:00 2001 From: Koen van Walstijn <38299796+kbvw@users.noreply.github.com> Date: Thu, 29 Aug 2024 10:38:50 +0200 Subject: [PATCH 27/29] Support `numpy>=2` and remove `numpy<2` pin (#3040) * Build wheels with latest numpy and test them with oldest supported numpy * Update oldest supported Python and Numpy versions * Pin `numpy<2` for documentation only The documentation requires an old version of bokeh which isn't compatible with `numpy>=2`. * Cast to `c_long` in RxD on Python side before passing to C++ With Numpy2 there was an inconsistency in the integer types, i.e. the C++ assumed a smaller type than the Python side, on Windows only. Causing SEGFAULT. This fixes the issue by using consistent integer types. --- .github/workflows/coverage.yml | 2 +- .github/workflows/neuron-ci.yml | 2 +- ci/win_build_cmake.sh | 2 +- ci/win_install_deps.cmd | 10 ++++----- ci/win_test_installer.cmd | 19 ++++++++++++++-- docs/install/install_instructions.md | 2 +- nrn_requirements.txt | 2 +- packaging/python/build_requirements.txt | 7 +----- .../python/oldest_numpy_requirements.txt | 5 +++++ packaging/python/test_requirements.txt | 2 ++ packaging/python/test_wheels.sh | 22 ++++++++++++++----- setup.py | 4 ++-- share/lib/python/neuron/rxd/rxd.py | 10 ++++----- share/lib/python/neuron/rxd/species.py | 12 +++++----- test/rxd/conftest.py | 3 ++- 15 files changed, 66 insertions(+), 38 deletions(-) create mode 100644 packaging/python/oldest_numpy_requirements.txt create mode 100644 packaging/python/test_requirements.txt diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 96ee062936..ce0bb21bae 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -58,7 +58,7 @@ jobs: run: | python3 -m venv music-venv source music-venv/bin/activate - python3 -m pip install 'mpi4py<4' "cython" 'numpy<2' setuptools + python3 -m pip install 'mpi4py<4' cython numpy setuptools sudo mkdir -p $MUSIC_INSTALL_DIR sudo chown -R $USER $MUSIC_INSTALL_DIR curl -L -o MUSIC.zip https://github.com/INCF/MUSIC/archive/refs/tags/${MUSIC_VERSION}.zip diff --git a/.github/workflows/neuron-ci.yml b/.github/workflows/neuron-ci.yml index abd42d2873..e3ca977eb4 100644 --- a/.github/workflows/neuron-ci.yml +++ b/.github/workflows/neuron-ci.yml @@ -207,7 +207,7 @@ jobs: run: | python3 -m venv music-venv source music-venv/bin/activate - python3 -m pip install 'mpi4py<4' "cython" 'numpy<2' setuptools + python3 -m pip install 'mpi4py<4' cython numpy setuptools sudo mkdir -p $MUSIC_INSTALL_DIR sudo chown -R $USER $MUSIC_INSTALL_DIR curl -L -o MUSIC.zip https://github.com/INCF/MUSIC/archive/refs/tags/${MUSIC_VERSION}.zip diff --git a/ci/win_build_cmake.sh b/ci/win_build_cmake.sh index fcf5fe419c..ca610acea3 100755 --- a/ci/win_build_cmake.sh +++ b/ci/win_build_cmake.sh @@ -30,7 +30,7 @@ cd $BUILD_SOURCESDIRECTORY/build -DNRN_ENABLE_RX3D=ON \ -DNRN_RX3D_OPT_LEVEL=2 \ -DNRN_BINARY_DIST_BUILD=ON \ - -DPYTHON_EXECUTABLE=/c/Python38/python.exe \ + -DPYTHON_EXECUTABLE=/c/Python39/python.exe \ -DNRN_ENABLE_PYTHON_DYNAMIC=ON \ -DNRN_PYTHON_DYNAMIC='c:/Python38/python.exe;c:/Python39/python.exe;c:/Python310/python.exe;c:/Python311/python.exe;c:/Python312/python.exe' \ -DCMAKE_INSTALL_PREFIX='/c/nrn-install' \ diff --git a/ci/win_install_deps.cmd b/ci/win_install_deps.cmd index b27cfba3af..b30c41c733 100644 --- a/ci/win_install_deps.cmd +++ b/ci/win_install_deps.cmd @@ -23,11 +23,11 @@ pwsh -command "(Get-Content C:\Python310\Lib\distutils\cygwinccompiler.py) -repl pwsh -command "(Get-Content C:\Python311\Lib\distutils\cygwinccompiler.py) -replace 'msvcr100', 'msvcrt' | Out-File C:\Python311\Lib\distutils\cygwinccompiler.py" :: install numpy -C:\Python38\python.exe -m pip install numpy==1.17.5 "cython" || goto :error -C:\Python39\python.exe -m pip install numpy==1.19.3 "cython" || goto :error -C:\Python310\python.exe -m pip install numpy==1.21.3 "cython" || goto :error -C:\Python311\python.exe -m pip install numpy==1.23.5 "cython" || goto :error -C:\Python312\python.exe -m pip install numpy==1.26.3 "cython" || goto :error +C:\Python38\python.exe -m pip install numpy cython || goto :error +C:\Python39\python.exe -m pip install numpy cython || goto :error +C:\Python310\python.exe -m pip install numpy cython || goto :error +C:\Python311\python.exe -m pip install numpy cython || goto :error +C:\Python312\python.exe -m pip install numpy cython || goto :error :: setuptools 70.2 leads to an error C:\Python312\python.exe -m pip install setuptools==70.1.1 || goto :error diff --git a/ci/win_test_installer.cmd b/ci/win_test_installer.cmd index 85255af463..0bf68842ab 100644 --- a/ci/win_test_installer.cmd +++ b/ci/win_test_installer.cmd @@ -21,9 +21,24 @@ C:\Python38\python -c "import neuron; neuron.test(); quit()" || set "errorfound= C:\Python39\python -c "import neuron; neuron.test(); quit()" || set "errorfound=y" C:\Python310\python -c "import neuron; neuron.test(); quit()" || set "errorfound=y" C:\Python311\python -c "import neuron; neuron.test(); quit()" || set "errorfound=y" -:: install numpy dependency -python -m pip install "numpy<2" +C:\Python312\python -c "import neuron; neuron.test(); quit()" || set "errorfound=y" + +:: install oldest supported numpy +C:\Python38\python.exe -m pip install -r packaging/python/oldest_numpy_requirements.txt || goto :error +C:\Python39\python.exe -m pip install -r packaging/python/oldest_numpy_requirements.txt || goto :error +C:\Python310\python.exe -m pip install -r packaging/python/oldest_numpy_requirements.txt || goto :error +C:\Python311\python.exe -m pip install -r packaging/python/oldest_numpy_requirements.txt || goto :error +C:\Python312\python.exe -m pip install -r packaging/python/oldest_numpy_requirements.txt || goto :error + +:: test all pythons again +C:\Python38\python -c "import neuron; neuron.test(); quit()" || set "errorfound=y" +C:\Python39\python -c "import neuron; neuron.test(); quit()" || set "errorfound=y" +C:\Python310\python -c "import neuron; neuron.test(); quit()" || set "errorfound=y" +C:\Python311\python -c "import neuron; neuron.test(); quit()" || set "errorfound=y" +C:\Python312\python -c "import neuron; neuron.test(); quit()" || set "errorfound=y" + :: run also using whatever is system python +python -m pip install numpy python --version python -c "import neuron; neuron.test(); quit()" || set "errorfound=y" diff --git a/docs/install/install_instructions.md b/docs/install/install_instructions.md index af7a86583f..dcce802840 100644 --- a/docs/install/install_instructions.md +++ b/docs/install/install_instructions.md @@ -482,7 +482,7 @@ share/lib/python/neuron/rxd/geometry3d/surfaces.cpp:14605:41: error: no member n ``` often there's something related to NumPy nearby, e.g. `npy`. -The issue is that certain versions of NEURON (9.0 and earlier) are not +The issue is that certain versions of NEURON (below 9.0) are not compatible with `numpy>=2`. Check the numpy version, e.g., ``` python -c "import numpy; print(numpy.__version__)" diff --git a/nrn_requirements.txt b/nrn_requirements.txt index c28bc995bb..f049602b4e 100644 --- a/nrn_requirements.txt +++ b/nrn_requirements.txt @@ -11,5 +11,5 @@ packaging pytest<=8.1.1 # potential bug from 8.2.0 due to parallelism? pytest-cov mpi4py<4 # MUSIC not compatible with MPI 4 -numpy<2 +numpy find_libpython diff --git a/packaging/python/build_requirements.txt b/packaging/python/build_requirements.txt index 29d2c1b080..71fe06794e 100644 --- a/packaging/python/build_requirements.txt +++ b/packaging/python/build_requirements.txt @@ -1,8 +1,3 @@ cython packaging -numpy==1.17.5;python_version=='3.8' -numpy==1.19.3;python_version=='3.9' and platform_machine!='arm64' -numpy==1.21.3;python_version=='3.9' and platform_machine=='arm64' -numpy==1.21.4;python_version=='3.10' -numpy==1.23.5;python_version=='3.11' -numpy==1.26.0;python_version=='3.12' +numpy diff --git a/packaging/python/oldest_numpy_requirements.txt b/packaging/python/oldest_numpy_requirements.txt new file mode 100644 index 0000000000..4a77e64c2b --- /dev/null +++ b/packaging/python/oldest_numpy_requirements.txt @@ -0,0 +1,5 @@ +numpy==1.20.3;python_version=='3.9' and platform_machine!='arm64' +numpy==1.21.6;python_version=='3.9' and platform_machine=='arm64' +numpy==1.21.6;python_version=='3.10' +numpy==1.23.5;python_version=='3.11' +numpy==1.26.4;python_version=='3.12' diff --git a/packaging/python/test_requirements.txt b/packaging/python/test_requirements.txt new file mode 100644 index 0000000000..6dbabce060 --- /dev/null +++ b/packaging/python/test_requirements.txt @@ -0,0 +1,2 @@ +pytest +setuptools;python_version>='3.12' # From 3.12, no longer installed by default diff --git a/packaging/python/test_wheels.sh b/packaging/python/test_wheels.sh index 1234d57c2d..853d7cbd7e 100755 --- a/packaging/python/test_wheels.sh +++ b/packaging/python/test_wheels.sh @@ -237,6 +237,12 @@ test_wheel () { } +test_wheel_basic_python () { + echo "=========== BASIC PYTHON TESTS ===========" + $python_exe -c "import neuron; neuron.test(); neuron.test_rxd()" +} + + echo "== Testing $python_wheel using $python_exe ($python_ver) ==" @@ -257,10 +263,8 @@ fi $python_exe -m pip install --upgrade pip -# install numpy, pytest and neuron -# we install setuptools because since python 3.12 it is no more installed -# by default -$python_exe -m pip install "numpy<2" pytest setuptools +# install test requirements +$python_exe -m pip install -r packaging/python/test_requirements.txt $python_exe -m pip install $python_wheel $python_exe -m pip show neuron || $python_exe -m pip show neuron-nightly @@ -271,8 +275,14 @@ if echo $compile_options | grep "NRN_ENABLE_CORENEURON=ON" > /dev/null ; then has_coreneuron=true fi -# run tests -test_wheel "${python_exe}" +# run tests with latest NumPy +echo " == Running tests with latest NumPy == " +test_wheel + +# run basic python tests with oldest supported NumPy +echo " == Running basic python tests with oldest supported NumPy == " +$python_exe -m pip install -r packaging/python/oldest_numpy_requirements.txt +test_wheel_basic_python # cleanup if [[ "$use_venv" != "false" ]]; then diff --git a/setup.py b/setup.py index bca41a0c2e..e15b6e5ac0 100644 --- a/setup.py +++ b/setup.py @@ -354,7 +354,7 @@ def setup_package(): NRN_COLLECT_DIRS = ["bin", "lib", "include", "share"] docs_require = [] # sphinx, themes, etc - maybe_rxd_reqs = ["numpy<2", "Cython"] if Components.RX3D else [] + maybe_rxd_reqs = ["numpy", "Cython"] if Components.RX3D else [] maybe_docs = docs_require if "docs" in sys.argv else [] maybe_test_runner = ["pytest-runner"] if "test" in sys.argv else [] @@ -509,7 +509,7 @@ def setup_package(): }, cmdclass=dict(build_ext=CMakeAugmentedBuilder, docs=Docs), install_requires=[ - "numpy>=1.9.3,<2", + "numpy>=1.9.3", "packaging", "find_libpython", "setuptools<=70.3.0", diff --git a/share/lib/python/neuron/rxd/rxd.py b/share/lib/python/neuron/rxd/rxd.py index 0ac5249684..3c0fd18a8e 100644 --- a/share/lib/python/neuron/rxd/rxd.py +++ b/share/lib/python/neuron/rxd/rxd.py @@ -61,7 +61,7 @@ setup_solver.argtypes = [ ndpointer(ctypes.c_double), ctypes.c_int, - numpy.ctypeslib.ndpointer(numpy.int_, flags="contiguous"), + numpy.ctypeslib.ndpointer(ctypes.c_long, flags="contiguous"), ctypes.c_int, ] @@ -630,8 +630,8 @@ def _matrix_to_rxd_sparse(m): return ( n, len(nonzero_i), - numpy.ascontiguousarray(nonzero_i, dtype=numpy.int_), - numpy.ascontiguousarray(nonzero_j, dtype=numpy.int_), + numpy.ascontiguousarray(nonzero_i, dtype=ctypes.c_long), + numpy.ascontiguousarray(nonzero_j, dtype=ctypes.c_long), nonzero_values, ) @@ -709,7 +709,7 @@ def _setup_matrices(): n = len(_node_get_states()) volumes = node._get_data()[0] - zero_volume_indices = (numpy.where(volumes == 0)[0]).astype(numpy.int_) + zero_volume_indices = (numpy.where(volumes == 0)[0]).astype(ctypes.c_long) if species._has_1d: # TODO: initialization is slow. track down why for sr in _species_get_all_species(): @@ -1896,7 +1896,7 @@ def _init(): _setup_matrices() # if species._has_1d and species._1d_submatrix_n(): # volumes = node._get_data()[0] - # zero_volume_indices = (numpy.where(volumes == 0)[0]).astype(numpy.int_) + # zero_volume_indices = (numpy.where(volumes == 0)[0]).astype(ctypes.c_long) # setup_solver(_node_get_states(), len(_node_get_states()), zero_volume_indices, len(zero_volume_indices), h._ref_t, h._ref_dt) clear_rates() _setup_memb_currents() diff --git a/share/lib/python/neuron/rxd/species.py b/share/lib/python/neuron/rxd/species.py index de8a3095b2..bd6e59f610 100644 --- a/share/lib/python/neuron/rxd/species.py +++ b/share/lib/python/neuron/rxd/species.py @@ -60,12 +60,12 @@ ctypes.c_int, ctypes.py_object, ctypes.c_long, - numpy.ctypeslib.ndpointer(dtype=int), - numpy.ctypeslib.ndpointer(dtype=int), + numpy.ctypeslib.ndpointer(dtype=ctypes.c_long), + numpy.ctypeslib.ndpointer(dtype=ctypes.c_long), ctypes.c_long, - numpy.ctypeslib.ndpointer(dtype=int), + numpy.ctypeslib.ndpointer(dtype=ctypes.c_long), ctypes.c_long, - numpy.ctypeslib.ndpointer(dtype=int), + numpy.ctypeslib.ndpointer(dtype=ctypes.c_long), ctypes.c_long, numpy.ctypeslib.ndpointer(dtype=float), ctypes.c_double, @@ -842,7 +842,7 @@ def line_defs(self, nodes, direction, nodes_length): # sort list for parallelization line_defs.sort(key=lambda x: x[1], reverse=True) - line_defs = numpy.asarray(line_defs, dtype=int) + line_defs = numpy.asarray(line_defs, dtype=ctypes.c_long) line_defs = line_defs.reshape(2 * len(line_defs)) return line_defs @@ -862,7 +862,7 @@ def ordered_nodes(self, p_line_defs, direction, neighbors): def create_neighbors_array(self, nodes, nodes_length): self._isalive() - my_array = numpy.zeros((nodes_length, 3), dtype=int) + my_array = numpy.zeros((nodes_length, 3), dtype=ctypes.c_long) for n in nodes: for i, ele in enumerate(n.neighbors[::2]): my_array[n._index, i] = ele if ele is not None else -1 diff --git a/test/rxd/conftest.py b/test/rxd/conftest.py index 1fbe8ee0cc..2a98c9d3f9 100644 --- a/test/rxd/conftest.py +++ b/test/rxd/conftest.py @@ -1,5 +1,6 @@ import os.path as osp import numpy +import ctypes import pytest import gc @@ -81,7 +82,7 @@ def neuron_nosave_instance(neuron_import): rxd.rxd.rxd_include_node_flux1D(0, None, None, None) rxd.species._has_1d = False rxd.species._has_3d = False - rxd.rxd._zero_volume_indices = numpy.ndarray(0, dtype=numpy.int_) + rxd.rxd._zero_volume_indices = numpy.ndarray(0, dtype=ctypes.c_long) rxd.set_solve_type(dimension=1) From 7d518dd8e739435c2668320d234d1814d067a84e Mon Sep 17 00:00:00 2001 From: Fernando Pereira Date: Thu, 29 Aug 2024 12:31:56 +0100 Subject: [PATCH 28/29] Style improvement of INCREF usage. (#3023) This commit makes INCREF usage more consistent. The rules are to INCREF before assignment and as late as possible. --- src/nrnpython/nrnpy_nrn.cpp | 66 +++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 35 deletions(-) diff --git a/src/nrnpython/nrnpy_nrn.cpp b/src/nrnpython/nrnpy_nrn.cpp index 5edf14cec7..79b2b89011 100644 --- a/src/nrnpython/nrnpy_nrn.cpp +++ b/src/nrnpython/nrnpy_nrn.cpp @@ -316,6 +316,8 @@ static NPyMechObj* new_pymechobj() { // it as "null". So later `a = b` might segfault because copy constructor decrements the // refcount of `a`s nonsense memory. new (&m->prop_id_) neuron::container::non_owning_identifier_without_container; + m->pyseg_ = nullptr; + m->prop_ = nullptr; } return m; @@ -327,8 +329,8 @@ static NPyMechObj* new_pymechobj(NPySegObj* pyseg, Prop* p) { if (!m) { return NULL; } + Py_INCREF(pyseg); m->pyseg_ = pyseg; - Py_INCREF(m->pyseg_); m->prop_ = p; m->prop_id_ = p->id(); m->type_ = p->_type; @@ -441,8 +443,8 @@ static int NPyAllSegOfSecIter_init(NPyAllSegOfSecIter* self, PyObject* args, PyO return -1; } self->allseg_iter_ = 0; - self->pysec_ = pysec; Py_INCREF(pysec); + self->pysec_ = pysec; } return 0; } @@ -510,9 +512,9 @@ static PyObject* NPySegObj_new(PyTypeObject* type, PyObject* args, PyObject* /* self = (NPySegObj*) type->tp_alloc(type, 0); // printf("NPySegObj_new %p\n", self); if (self != NULL) { + Py_INCREF(pysec); self->pysec_ = pysec; self->x_ = x; - Py_INCREF(self->pysec_); } return (PyObject*) self; } @@ -532,8 +534,8 @@ static PyObject* NPyMechObj_new(PyTypeObject* type, PyObject* args, PyObject* /* // ((PyObject*)self)->ob_type->tp_name); if (self != NULL) { new (self) NPyMechObj; + Py_INCREF(pyseg); self->pyseg_ = pyseg; - Py_INCREF(self->pyseg_); } return (PyObject*) self; } @@ -1200,7 +1202,7 @@ static PyObject* pysec_wholetree_safe(NPySecObj* const self) { static PyObject* pysec2cell(NPySecObj* self) { PyObject* result; if (self->cell_weakref_) { - result = PyWeakref_GET_OBJECT(self->cell_weakref_); + result = PyWeakref_GetObject(self->cell_weakref_); Py_INCREF(result); } else if (auto* o = self->sec_->prop->dparam[6].get(); self->sec_->prop && o) { result = nrnpy_ho2po(o); @@ -1357,12 +1359,8 @@ static PyObject* NPyMechObj_is_ion_safe(NPyMechObj* self) { static PyObject* NPyMechObj_segment(NPyMechObj* self) { CHECK_PROP_INVALID(self->prop_id_); - PyObject* result = NULL; - if (self->pyseg_) { - result = (PyObject*) (self->pyseg_); - Py_INCREF(result); - } - return result; + Py_XINCREF(self->pyseg_); + return (PyObject*) self->pyseg_; } static PyObject* NPyMechObj_segment_safe(NPyMechObj* self) { @@ -1370,13 +1368,12 @@ static PyObject* NPyMechObj_segment_safe(NPyMechObj* self) { } static PyObject* NPyMechFunc_mech(NPyMechFunc* self) { - PyObject* result = NULL; - if (self->pymech_) { - CHECK_PROP_INVALID(self->pymech_->prop_id_); - result = (PyObject*) (self->pymech_); - Py_INCREF(result); + auto* pymech = self->pymech_; + if (pymech) { + CHECK_PROP_INVALID(pymech->prop_id_); + Py_INCREF(pymech); } - return result; + return (PyObject*) pymech; } static PyObject* NPyMechFunc_mech_safe(NPyMechFunc* self) { @@ -1423,13 +1420,12 @@ static PyObject* NPyRangeVar_name_safe(NPyRangeVar* self) { } static PyObject* NPyRangeVar_mech(NPyRangeVar* self) { - CHECK_SEC_INVALID(self->pymech_->pyseg_->pysec_->sec_); - PyObject* result = NULL; - if (self->pymech_) { - result = (PyObject*) self->pymech_; - Py_INCREF(result); + auto* pymech = self->pymech_; + if (pymech) { + CHECK_SEC_INVALID(pymech->pyseg_->pysec_->sec_); + Py_INCREF(pymech); } - return result; + return (PyObject*) pymech; } static PyObject* NPyRangeVar_mech_safe(NPyRangeVar* self) { @@ -1472,12 +1468,12 @@ static PyObject* NPySecObj_connect(NPySecObj* self, PyObject* args) { PyErr_SetString(PyExc_ValueError, "child connection end must be 0 or 1"); return NULL; } - Py_INCREF(self); hoc_pushx(childend); hoc_pushx(parentx); nrn_pushsec(self->sec_); nrn_pushsec(parent->sec_); simpleconnectsection(); + Py_INCREF(self); return (PyObject*) self; } @@ -1610,8 +1606,8 @@ static PyObject* allseg(NPySecObj* self) { CHECK_SEC_INVALID(self->sec_); // printf("allseg\n"); NPyAllSegOfSecIter* ai = PyObject_New(NPyAllSegOfSecIter, pallseg_of_sec_iter_type); - ai->pysec_ = self; Py_INCREF(self); + ai->pysec_ = self; ai->allseg_iter_ = -1; return (PyObject*) ai; } @@ -1621,8 +1617,8 @@ static PyObject* allseg_safe(NPySecObj* self) { } static PyObject* allseg_of_sec_iter(NPyAllSegOfSecIter* self) { - Py_INCREF(self); self->allseg_iter_ = -1; + Py_INCREF(self); return (PyObject*) self; } @@ -1642,8 +1638,8 @@ static PyObject* allseg_of_sec_next(NPyAllSegOfSecIter* self) { // error return NULL; } - seg->pysec_ = self->pysec_; Py_INCREF(self->pysec_); + seg->pysec_ = self->pysec_; if (self->allseg_iter_ == -1) { seg->x_ = 0.; } else if (self->allseg_iter_ == n1) { @@ -1671,8 +1667,8 @@ static PyObject* seg_of_sec_next(NPySegOfSecIter* self) { // error return NULL; } - seg->pysec_ = self->pysec_; Py_INCREF(self->pysec_); + seg->pysec_ = self->pysec_; seg->x_ = (double(self->seg_iter_) + 0.5) / ((double) n1); ++self->seg_iter_; return (PyObject*) seg; @@ -1937,8 +1933,8 @@ static NPyRangeVar* rvnew(Symbol* sym, NPySecObj* sec, double x) { } r->pymech_ = new_pymechobj(); r->pymech_->pyseg_ = PyObject_New(NPySegObj, psegment_type); - r->pymech_->pyseg_->pysec_ = sec; Py_INCREF(sec); + r->pymech_->pyseg_->pysec_ = sec; r->pymech_->pyseg_->x_ = 0.5; r->sym_ = sym; r->isptr_ = 0; @@ -2147,8 +2143,8 @@ static PyObject* var_of_mech_iter(NPyMechObj* self) { if (!self->prop_) { return NULL; } + Py_INCREF(self); vmi->pymech_ = self; - Py_INCREF(vmi->pymech_); vmi->msym_ = memb_func[self->prop_->_type].sym; vmi->i_ = 0; return (PyObject*) vmi; @@ -2166,8 +2162,8 @@ static PyObject* var_of_mech_next(NPyVarOfMechIter* self) { Symbol* sym = self->msym_->u.ppsym[self->i_]; self->i_++; NPyRangeVar* r = (NPyRangeVar*) PyObject_New(NPyRangeVar, range_type); + Py_INCREF(self->pymech_); r->pymech_ = self->pymech_; - Py_INCREF(r->pymech_); r->sym_ = sym; r->isptr_ = 0; r->attr_from_sec_ = 0; @@ -2219,8 +2215,8 @@ static PyObject* segment_getattro(NPySegObj* self, PyObject* pyname) { } else if (is_array(*sym)) { NPyRangeVar* r = PyObject_New(NPyRangeVar, range_type); r->pymech_ = new_pymechobj(); + Py_INCREF(self); r->pymech_->pyseg_ = self; - Py_INCREF(r->pymech_->pyseg_); r->sym_ = sym; r->isptr_ = 0; r->attr_from_sec_ = 0; @@ -2247,8 +2243,8 @@ static PyObject* segment_getattro(NPySegObj* self, PyObject* pyname) { if (is_array(*sym)) { NPyRangeVar* r = PyObject_New(NPyRangeVar, range_type); r->pymech_ = new_pymechobj(); - r->pymech_->pyseg_ = self; Py_INCREF(self); + r->pymech_->pyseg_ = self; r->sym_ = sym; r->isptr_ = 1; r->attr_from_sec_ = 0; @@ -2499,8 +2495,8 @@ static PyObject* mech_getattro(NPyMechObj* self, PyObject* pyname) { // printf("mech_getattro sym %s\n", sym->name); if (is_array(*sym)) { NPyRangeVar* r = PyObject_New(NPyRangeVar, range_type); - r->pymech_ = self; Py_INCREF(self); + r->pymech_ = self; r->sym_ = sym; r->isptr_ = isptr; r->attr_from_sec_ = 0; @@ -2542,8 +2538,8 @@ static PyObject* mech_getattro(NPyMechObj* self, PyObject* pyname) { found_func = true; auto& f = funcs[n]; NPyMechFunc* pymf = PyObject_New(NPyMechFunc, pmechfunc_generic_type); - pymf->pymech_ = self; Py_INCREF(self); + pymf->pymech_ = self; pymf->f_ = f; result = (PyObject*) pymf; } From b985a12c0ae0904bbbb43f806e89c67be0529f99 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Thu, 29 Aug 2024 13:34:08 +0200 Subject: [PATCH 29/29] Remove dead code related to Python strings. (#3048) At some point Python strings where kept alive and put into a `dlist` for deferred deletion. This was changed in 15b3b3f5. This commit removes leftover dead code. --- src/nrnpython/nrnpy_p2h.cpp | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/src/nrnpython/nrnpy_p2h.cpp b/src/nrnpython/nrnpy_p2h.cpp index 0164c2b667..931fa0a453 100644 --- a/src/nrnpython/nrnpy_p2h.cpp +++ b/src/nrnpython/nrnpy_p2h.cpp @@ -16,12 +16,10 @@ #include namespace nb = nanobind; -static void nrnpy_decref_defer(PyObject*); static char* nrnpyerr_str(); static PyObject* nrnpy_pyCallObject(PyObject*, PyObject*); static PyObject* main_module; static PyObject* main_namespace; -static hoc_List* dlist; struct Py2Nrn final { ~Py2Nrn() { @@ -254,15 +252,12 @@ static void py2n_component(Object* ob, Symbol* sym, int nindex, int isfunc) { PyObject* pn = PyNumber_Float(result); hoc_pushx(PyFloat_AsDouble(pn)); Py_XDECREF(pn); - Py_XDECREF(result); } else if (is_python_string(result)) { char** ts = hoc_temp_charptr(); Py2NRNString str(result, true); *ts = str.c_str(); hoc_pop_defer(); hoc_pushstr(ts); - // how can we defer the result unref til the string is popped - nrnpy_decref_defer(result); } else { // PyObject_Print(result, stdout, 0); on = nrnpy_po2ho(result); @@ -271,8 +266,8 @@ static void py2n_component(Object* ob, Symbol* sym, int nindex, int isfunc) { if (on) { on->refcount--; } - Py_XDECREF(result); } + Py_XDECREF(result); Py_XDECREF(head); Py_DECREF(tail); } @@ -334,17 +329,6 @@ static void hpoasgn(Object* o, int type) { } } -static void nrnpy_decref_defer(PyObject* po) { - if (po) { -#if 0 - PyObject* ps = PyObject_Str(po); - printf("defer %s\n", PyString_AsString(ps)); - Py_DECREF(ps); -#endif - hoc_l_lappendvoid(dlist, (void*) po); - } -} - static PyObject* hoccommand_exec_help1(PyObject* po) { PyObject* r; // PyObject_Print(po, stdout, 0); @@ -1129,5 +1113,4 @@ extern "C" NRN_EXPORT void nrnpython_reg_real(neuron::python::impl_ptrs* ptrs) { nrnpython_reg_real_nrnpython_cpp(ptrs); // call a function in nrnpy_hoc.cpp to register the functions defined there nrnpython_reg_real_nrnpy_hoc_cpp(ptrs); - dlist = hoc_l_newlist(); }