diff --git a/CMakeLists.txt b/CMakeLists.txt index ba948e02a8..3fbb6acf3a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -247,6 +247,13 @@ include("${CODING_CONV_CMAKE}/3rdparty.cmake") cpp_cc_git_submodule(Random123) cpp_cc_git_submodule(eigen) +if(NRN_ENABLE_CORENEURON) + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/external/Random123/include/Random123" + DESTINATION "${CMAKE_BINARY_DIR}/include/") + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/external/eigen/Eigen" + DESTINATION "${CMAKE_BINARY_DIR}/include/") +endif() + # ================================================================================================= # Enable sanitizer support if the NRN_SANITIZERS variable is set. Comes befores PythonHelper.cmake. # ================================================================================================= diff --git a/bin/nrnivmodl-core.in b/bin/nrnivmodl-core.in index 4668361cda..4ce31f9ab4 100755 --- a/bin/nrnivmodl-core.in +++ b/bin/nrnivmodl-core.in @@ -19,15 +19,7 @@ APP_NAME="$(basename "$0")" # directory and parent directory of this script PARENT_DIR="$(dirname "$BASH_SOURCE")/.." - -# prefer perl exe set by neuron wrappers in case of wheel -PERL_EXE="${CORENRN_PERLEXE:-@PERL_EXECUTABLE@}" -# in case of mac installer, wrapper is not used and hence -# check if binary exist. otherwise, just rely on perl being -# in default $PATH -if [ ! -f "${PERL_EXE}" ]; then PERL_EXE="$(which perl)"; fi - -ROOT_DIR="$("${PERL_EXE}" -e "use Cwd 'abs_path'; print abs_path('$PARENT_DIR')")" +ROOT_DIR="$(readlink -f "$PARENT_DIR")" # default arguments : number of parallel builds and default mod file path PARALLEL_BUILDS=4 diff --git a/bin/nrnivmodl_core_makefile.in b/bin/nrnivmodl_core_makefile.in index 86a5138d70..09f68d9fca 100644 --- a/bin/nrnivmodl_core_makefile.in +++ b/bin/nrnivmodl_core_makefile.in @@ -57,13 +57,9 @@ endif # binary used during build. If they don't exist then simply use python and # perl as the name of binaries. CORENRN_PYTHONEXE ?= @NRN_DEFAULT_PYTHON_EXECUTABLE@ -CORENRN_PERLEXE ?= @PERL_EXECUTABLE@ ifeq ($(wildcard $(CORENRN_PYTHONEXE)),) CORENRN_PYTHONEXE=python endif -ifeq ($(wildcard $(CORENRN_PERLEXE)),) - CORENRN_PERLEXE=perl -endif CXXFLAGS = @CORENRN_CXX_FLAGS@ CXX_COMPILE_CMD = $(CXX) $(CXXFLAGS) @CMAKE_CXX_COMPILE_OPTIONS_PIC@ $(INCLUDES) @@ -71,7 +67,7 @@ CXX_LINK_EXE_CMD = $(CXX) $(CXXFLAGS) @CMAKE_EXE_LINKER_FLAGS@ CXX_SHARED_LIB_CMD = $(CXX) $(CXXFLAGS) @CMAKE_SHARED_LIBRARY_CREATE_CXX_FLAGS@ @CMAKE_SHARED_LIBRARY_CXX_FLAGS@ @CMAKE_SHARED_LINKER_FLAGS@ # env variables required for mod2c or nmodl -NMODL_ENV_VAR = @CORENRN_SANITIZER_ENABLE_ENVIRONMENT_STRING@ PYTHONPATH=${PYTHONPATH}:@CORENRN_NMODL_PYTHONPATH@:${CORENRN_LIB_DIR}/python MODLUNIT=$(CORENRN_SHARE_NMODL_DIR)/nrnunits.lib +NMODL_ENV_VAR = @CORENRN_SANITIZER_ENABLE_ENVIRONMENT_STRING@ PYTHONPATH=${PYTHONPATH}:${CORENRN_LIB_DIR}/python MODLUNIT=$(CORENRN_SHARE_NMODL_DIR)/nrnunits.lib ifeq (@CORENRN_ENABLE_GPU@, ON) nmodl_arguments_c=@NMODL_ACC_BACKEND_ARGS@ @NMODL_COMMON_ARGS@ @@ -100,7 +96,6 @@ endif # Binary of NMODL depending on CMake option activated ifeq (@nmodl_FOUND@, TRUE) NMODL_BINARY_PATH = $(if $(NMODL_BINARY),$(NMODL_BINARY), @CORENRN_NMODL_BINARY@) - INCLUDES += -I@CORENRN_NMODL_INCLUDE@ else NMODL_BINARY_PATH = $(if $(NMODL_BINARY),$(NMODL_BINARY), $(CORENRN_BIN_DIR)/@nmodl_binary_name@) endif @@ -189,7 +184,7 @@ $(mod_cpp_files): $(MOD_TO_CPP_DIR)/%.cpp: $(MODS_PATH)/%.mod | $(MOD_TO_CPP_DIR # generate mod registration function. Dont overwrite if it's not changed $(MOD_FUNC_CPP): build_always | $(MOD_TO_CPP_DIR) - $(CORENRN_PERLEXE) $(CORENRN_SHARE_CORENRN_DIR)/mod_func.c.pl $(mod_files_names) > $(MOD_FUNC_CPP).tmp + bash $(CORENRN_SHARE_CORENRN_DIR)/mod_func.c.sh $(mod_files_names) > $(MOD_FUNC_CPP).tmp diff -q $(MOD_FUNC_CPP).tmp $(MOD_FUNC_CPP) || \ mv $(MOD_FUNC_CPP).tmp $(MOD_FUNC_CPP) diff --git a/ci/win_install_deps.cmd b/ci/win_install_deps.cmd index 2284b911c0..c75e977a10 100644 --- a/ci/win_install_deps.cmd +++ b/ci/win_install_deps.cmd @@ -28,7 +28,8 @@ 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:\Python312\python.exe -m pip install setuptools || goto :error +:: setuptools 70.2 leads to an error +C:\Python312\python.exe -m pip install setuptools==70.1.1 || goto :error :: install nsis nsis-3.05-setup.exe /S || goto :error diff --git a/cmake/NanoBindMinimal.cmake b/cmake/NanoBindMinimal.cmake index f661e7e473..67ba2f18d8 100644 --- a/cmake/NanoBindMinimal.cmake +++ b/cmake/NanoBindMinimal.cmake @@ -17,5 +17,4 @@ function(make_nanobind_target TARGET_NAME PYINC) endif() target_compile_features(${TARGET_NAME} PUBLIC cxx_std_17) set_property(TARGET ${TARGET_NAME} PROPERTY POSITION_INDEPENDENT_CODE True) - set_property(TARGET ${TARGET_NAME} PROPERTY INTERPROCEDURAL_OPTIMIZATION_RELEASE ON) endfunction() diff --git a/cmake/coreneuron/packages/Findnmodl.cmake b/cmake/coreneuron/packages/Findnmodl.cmake index c68ff64c7e..aa483c889e 100644 --- a/cmake/coreneuron/packages/Findnmodl.cmake +++ b/cmake/coreneuron/packages/Findnmodl.cmake @@ -17,7 +17,6 @@ # :: # set(CORENRN_NMODL_DIR "" CACHE PATH "Path to nmodl source-to-source compiler installation") # find_package(nmodl REQUIRED) -# include_directories(${nmodl_INCLUDE_DIRS}) # target_link_libraries(foo ${nmodl_LIBRARIES}) # # This module sets the following variables: @@ -25,7 +24,6 @@ # :: # # nmodl_FOUND - set to true if the library is found -# nmodl_INCLUDE - list of required include directories # nmodl_BINARY - the nmodl binary # ~~~ @@ -35,13 +33,10 @@ find_program( NAMES nmodl${CMAKE_EXECUTABLE_SUFFIX} HINTS "${CORENRN_NMODL_DIR}/bin" QUIET) -find_path(nmodl_INCLUDE "nmodl.hpp" HINTS "${CORENRN_NMODL_DIR}/include") -find_path(nmodl_PYTHONPATH "nmodl/__init__.py" HINTS "${CORENRN_NMODL_DIR}/lib") - # Checks 'REQUIRED', 'QUIET' and versions. include(FindPackageHandleStandardArgs) find_package_handle_standard_args( nmodl FOUND_VAR nmodl_FOUND - REQUIRED_VARS nmodl_BINARY nmodl_INCLUDE nmodl_PYTHONPATH) + REQUIRED_VARS nmodl_BINARY) diff --git a/external/nmodl b/external/nmodl index c5ecfea39c..7910146791 160000 --- a/external/nmodl +++ b/external/nmodl @@ -1 +1 @@ -Subproject commit c5ecfea39cbd53ffcc248b1dcf732c1e0ae1ffc4 +Subproject commit 79101467912f9690b6c40a1d3eed7315e3ea9bc4 diff --git a/packaging/python/build_requirements.txt b/packaging/python/build_requirements.txt index dcbd639f9c..c92f711148 100644 --- a/packaging/python/build_requirements.txt +++ b/packaging/python/build_requirements.txt @@ -1,2 +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.21.3;python_version=='3.9' and platform_machine=='arm64' +numpy==1.21.3;python_version=='3.10' +numpy==1.23.5;python_version=='3.11' +numpy==1.26.0;python_version=='3.12' diff --git a/packaging/python/build_wheels.bash b/packaging/python/build_wheels.bash index 8925b3e49f..4da4f01f06 100755 --- a/packaging/python/build_wheels.bash +++ b/packaging/python/build_wheels.bash @@ -25,10 +25,11 @@ fi py_ver="" -clone_install_nmodl_requirements() { +clone_nmodl_and_add_requirements() { git config --global --add safe.directory /root/nrn git submodule update --init --recursive --force --depth 1 -- external/nmodl - pip install -r external/nmodl/requirements.txt + # We only want the _build_ dependencies + sed -e '/^# runtime dependencies/,$ d' external/nmodl/requirements.txt >> my_requirements.txt } @@ -52,29 +53,6 @@ setup_venv() { } -pip_numpy_install() { - # numpy is special as we want the minimum wheel version - numpy_ver="numpy" - case "$py_ver" in - 36) numpy_ver="numpy==1.12.1" ;; - 37) numpy_ver="numpy==1.14.6" ;; - 38) numpy_ver="numpy==1.17.5" ;; - 39) numpy_ver="numpy==1.19.3" ;; - 310) numpy_ver="numpy==1.21.3" ;; - 311) numpy_ver="numpy==1.23.5" ;; - 312) numpy_ver="numpy==1.26.0" ;; - *) echo "Error: numpy version not specified for this python!" && exit 1;; - esac - - # older version for apple m1 as building from source fails - if [[ `uname -m` == 'arm64' && "$py_ver" == "39" ]]; then - numpy_ver="numpy==1.21.3" - fi - - echo " - pip install $numpy_ver" - pip install $numpy_ver -} - build_wheel_linux() { echo "[BUILD WHEEL] Building with interpreter $1" local skip= @@ -83,11 +61,7 @@ build_wheel_linux() { echo " - Installing build requirements" pip install auditwheel - pip install -r packaging/python/build_requirements.txt - pip_numpy_install - - echo " - Building..." - rm -rf dist build + cp packaging/python/build_requirements.txt my_requirements.txt CMAKE_DEFS="NRN_MPI_DYNAMIC=$3" if [ "$USE_STATIC_READLINE" == "1" ]; then @@ -96,10 +70,17 @@ build_wheel_linux() { if [ "$2" == "coreneuron" ]; then setup_args="--enable-coreneuron" - clone_install_nmodl_requirements + clone_nmodl_and_add_requirements CMAKE_DEFS="${CMAKE_DEFS},LINK_AGAINST_PYTHON=OFF" fi + cat my_requirements.txt + pip install -r my_requirements.txt + pip check + + echo " - Building..." + rm -rf dist build + # Workaround for https://github.com/pypa/manylinux/issues/1309 git config --global --add safe.directory "*" @@ -135,15 +116,11 @@ build_wheel_osx() { (( $skip )) && return 0 echo " - Installing build requirements" - pip install -U delocate -r packaging/python/build_requirements.txt - pip_numpy_install - - echo " - Building..." - rm -rf dist build + cp packaging/python/build_requirements.txt my_requirements.txt if [ "$2" == "coreneuron" ]; then setup_args="--enable-coreneuron" - clone_install_nmodl_requirements + clone_nmodl_and_add_requirements CMAKE_DEFS="${CMAKE_DEFS},LINK_AGAINST_PYTHON=OFF" fi @@ -152,6 +129,13 @@ build_wheel_osx() { 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 + + echo " - Building..." + rm -rf dist build + # We need to "fix" the platform tag if the Python installer is universal2 # See: # * https://github.com/pypa/setuptools/issues/2520 diff --git a/src/coreneuron/CMakeLists.txt b/src/coreneuron/CMakeLists.txt index 6ec5a82389..cf4fdf4903 100644 --- a/src/coreneuron/CMakeLists.txt +++ b/src/coreneuron/CMakeLists.txt @@ -147,11 +147,6 @@ configure_file(${PROJECT_SOURCE_DIR}/src/coreneuron/config/config.cpp.in # ============================================================================= include(OpenAccHelper) -# ============================================================================= -# Common dependencies -# ============================================================================= -find_package(Perl REQUIRED) - # ============================================================================= # Common build options # ============================================================================= @@ -259,10 +254,6 @@ if(NOT "${CORENRN_NMODL_DIR}" STREQUAL "" AND NOT nmodl_FOUND) endif() if(nmodl_FOUND) set(CORENRN_NMODL_BINARY ${nmodl_BINARY}) - set(CORENRN_NMODL_INCLUDE ${nmodl_INCLUDE}) - # path to python interface - set(ENV{PYTHONPATH} "${nmodl_PYTHONPATH}:$ENV{PYTHONPATH}") - set(CORENRN_NMODL_PYTHONPATH $ENV{PYTHONPATH}) else() set(NMODL_ENABLE_PYTHON_BINDINGS OFF @@ -270,10 +261,6 @@ else() nrn_add_external_project(nmodl DISABLE_ADD) add_subdirectory(${PROJECT_SOURCE_DIR}/external/nmodl ${CMAKE_BINARY_DIR}/external/nmodl) set(CORENRN_NMODL_BINARY ${CMAKE_BINARY_DIR}/bin/nmodl${CMAKE_EXECUTABLE_SUFFIX}) - set(CORENRN_NMODL_INCLUDE ${CMAKE_BINARY_DIR}/include) - set(ENV{PYTHONPATH} "$ENV{PYTHONPATH}") - set(nmodl_PYTHONPATH "${CMAKE_BINARY_DIR}/lib") - set(CORENRN_NMODL_PYTHONPATH "${nmodl_PYTHONPATH}:$ENV{PYTHONPATH}") set(NMODL_TARGET_TO_DEPEND nmodl) set(NMODL_PROJECT_BINARY_DIR ${CMAKE_BINARY_DIR}/external/nmodl) # install nrnunits.lib and libpywrapper.so from external/nmodl @@ -360,23 +347,20 @@ if(NRN_ENABLE_MPI) list(APPEND CORENEURON_CODE_FILES "mpi/core/resolve.cpp") endif() -file(COPY ${PROJECT_SOURCE_DIR}/external/Random123/include/Random123 - DESTINATION ${CMAKE_BINARY_DIR}/include) list(APPEND CORENEURON_CODE_FILES ${PROJECT_BINARY_DIR}/coreneuron/config/config.cpp) set(ENGINEMECH_CODE_FILE "mechanism/mech/enginemech.cpp") # for external mod files we need to generate modl_ref function in mod_func.c -set(MODFUNC_PERL_SCRIPT "mechanism/mech/mod_func.c.pl") - +set(MODFUNC_SHELL_SCRIPT "mechanism/mech/mod_func.c.sh") set(NMODL_UNITS_FILE "${CMAKE_BINARY_DIR}/share/mod2c/nrnunits.lib") # ============================================================================= # Copy files that are required by nrnivmodl-core to the build tree at build time. # ============================================================================= cpp_cc_build_time_copy( - INPUT "${CMAKE_CURRENT_SOURCE_DIR}/${MODFUNC_PERL_SCRIPT}" - OUTPUT "${CMAKE_BINARY_DIR}/share/coreneuron/mod_func.c.pl" + INPUT "${CMAKE_CURRENT_SOURCE_DIR}/${MODFUNC_SHELL_SCRIPT}" + OUTPUT "${CMAKE_BINARY_DIR}/share/coreneuron/mod_func.c.sh" NO_TARGET) cpp_cc_build_time_copy( INPUT "${CMAKE_CURRENT_SOURCE_DIR}/${ENGINEMECH_CODE_FILE}" @@ -387,7 +371,7 @@ cpp_cc_build_time_copy( OUTPUT "${CMAKE_BINARY_DIR}/share/coreneuron/coreneuron.cpp" NO_TARGET) set(nrnivmodl_core_dependencies - "${CMAKE_BINARY_DIR}/share/coreneuron/mod_func.c.pl" + "${CMAKE_BINARY_DIR}/share/coreneuron/mod_func.c.sh" "${CMAKE_BINARY_DIR}/share/coreneuron/enginemech.cpp" "${CMAKE_BINARY_DIR}/share/coreneuron/coreneuron.cpp") # Set up build rules that copy builtin mod files from src/coreneuron/mechanism/mech/modfile/*.mod to @@ -420,25 +404,6 @@ if(CORENRN_ENABLE_GPU) # https://forums.developer.nvidia.com/t/cannot-dynamically-load-a-shared-library-containing-both-openacc-and-cuda-code/210972 # this cannot be included in the same shared library as the rest of the OpenACC code. set(CORENEURON_CUDA_FILES ${CMAKE_CURRENT_SOURCE_DIR}/permute/cellorder.cu) - - # Eigen functions cannot be called directly from OpenACC regions, but Eigen is sort-of compatible - # with being compiled as CUDA code. Because of - # https://forums.developer.nvidia.com/t/cannot-dynamically-load-a-shared-library-containing-both-openacc-and-cuda-code/210972 - # this has to mean `nvc++ -cuda` rather than `nvcc`. We explicitly instantiate Eigen functions for - # different matrix sizes in partial_piv_lu.cpp (with CUDA attributes but without OpenACC or OpenMP - # annotations) and dispatch to these from a wrapper in partial_piv_lu.h that does have - # OpenACC/OpenMP annotations. - if(EXISTS ${CORENRN_NMODL_INCLUDE}/partial_piv_lu/partial_piv_lu.cpp) - list(APPEND CORENEURON_CODE_FILES ${CORENRN_NMODL_INCLUDE}/partial_piv_lu/partial_piv_lu.cpp) - if(CORENRN_ENABLE_GPU - AND CORENRN_HAVE_NVHPC_COMPILER - AND CMAKE_BUILD_TYPE STREQUAL "Debug") - # In this case OpenAccHelper.cmake passes -gpu=debug, which makes these Eigen functions - # extremely slow. Downgrade that to -gpu=lineinfo for this file. - set_source_files_properties(${CORENRN_NMODL_INCLUDE}/partial_piv_lu/partial_piv_lu.cpp - PROPERTIES COMPILE_FLAGS "-gpu=lineinfo,nodebug -O1") - endif() - endif() endif() # ============================================================================= @@ -706,7 +671,7 @@ install( FILES_MATCHING PATTERN "*.h*" PATTERN "*.ipp") -install(FILES ${MODFUNC_PERL_SCRIPT} ${ENGINEMECH_CODE_FILE} DESTINATION share/coreneuron) +install(FILES ${MODFUNC_SHELL_SCRIPT} ${ENGINEMECH_CODE_FILE} DESTINATION share/coreneuron) # copy nmodl for nrnivmodl-core install(PROGRAMS ${CORENRN_NMODL_BINARY} DESTINATION bin) @@ -724,9 +689,6 @@ if(CORENRN_ENABLE_SHARED) install(FILES ${corenrn_mech_library} DESTINATION lib) endif() -# install random123 and nmodl headers -install(DIRECTORY ${CMAKE_BINARY_DIR}/include/ DESTINATION include) - # install mod files install(DIRECTORY ${CMAKE_BINARY_DIR}/share/modfile DESTINATION share) diff --git a/src/coreneuron/mechanism/mech/mod_func.c.pl b/src/coreneuron/mechanism/mech/mod_func.c.pl deleted file mode 100644 index d34c83829f..0000000000 --- a/src/coreneuron/mechanism/mech/mod_func.c.pl +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/perl -# -# ============================================================================= -# Copyright (c) 2016 - 2021 Blue Brain Project/EPFL -# -# See top-level LICENSE file for details. -# ============================================================================= - -#Construct the modl_reg() function from a provided list -#of modules. - -#Usage : mod_func.c.pl[MECH1.mod MECH2.mod...] - -@mods = @ARGV; -s/\.mod$// foreach @mods; - -@mods=sort @mods; - -if(!@mods) { - print STDERR "mod_func.c.pl: No mod files provided"; - print "// No mod files provided -namespace coreneuron { - void modl_reg() {} -} -"; - exit 0; -} - -print << "__eof"; -#include -namespace coreneuron { -extern int nrnmpi_myid; -extern int nrn_nobanner_; -extern int @{[join ",\n ", map{"_${_}_reg(void)"} @mods]}; - -void modl_reg() { - if (!nrn_nobanner_ && nrnmpi_myid < 1) { - fprintf(stderr, " Additional mechanisms from files\\n"); - @{[join "\n ", - map{"fprintf(stderr, \" $_.mod\");"} @mods] } - fprintf(stderr, "\\n\\n"); - } - - @{[join "\n ", map{"_${_}_reg();"} @mods] } -} -} //namespace coreneuron -__eof diff --git a/src/coreneuron/mechanism/mech/mod_func.c.sh b/src/coreneuron/mechanism/mech/mod_func.c.sh new file mode 100755 index 0000000000..62282fbc90 --- /dev/null +++ b/src/coreneuron/mechanism/mech/mod_func.c.sh @@ -0,0 +1,55 @@ +#!/bin/bash +# +# ============================================================================= +# Copyright (c) 2016 - 2021 Blue Brain Project/EPFL +# +# See top-level LICENSE file for details. +# ============================================================================= + +#Construct the modl_reg() function from a provided list +#of modules. + +#Usage: mod_func.c.sh [MECH1.mod MECH2.mod...] + +if [[ "$#" -eq 0 ]]; then + >&2 echo "mod_func.c.sh: No mod files provided"; + cat < +namespace coreneuron { +extern int nrnmpi_myid; +extern int nrn_nobanner_; +extern int + ${decls}; + +void modl_reg() { + if (!nrn_nobanner_ && nrnmpi_myid < 1) { + fprintf(stderr, " Additional mechanisms from files\\n"); + ${prints} + fprintf(stderr, "\\n\\n"); + } + ${regs} +} +} //namespace coreneuron +EOF diff --git a/src/coreneuron/utils/memory.h b/src/coreneuron/utils/memory.h index e455c8aa87..41da2fd3f3 100644 --- a/src/coreneuron/utils/memory.h +++ b/src/coreneuron/utils/memory.h @@ -192,7 +192,8 @@ inline void alloc_memory(void*& pointer, size_t num_bytes, size_t alignment) { fill = alignment * (multiple + 1) - num_bytes; } #ifndef _WIN32 - nrn_assert((pointer = std::aligned_alloc(alignment, num_bytes + fill)) != nullptr); + pointer = aligned_alloc(alignment, num_bytes + fill); + nrn_assert(pointer != nullptr); #else // is _WIN32 // Windows has _aligned_alloc, but that must be paired with // _aligned_free diff --git a/src/nrnpython/nrnpy_nrn.cpp b/src/nrnpython/nrnpy_nrn.cpp index 5efa20c9df..a317829789 100644 --- a/src/nrnpython/nrnpy_nrn.cpp +++ b/src/nrnpython/nrnpy_nrn.cpp @@ -25,6 +25,10 @@ #include #include +#include + +namespace nb = nanobind; + extern void nrn_pt3dremove(Section* sec, int i0); extern void nrn_pt3dinsert(Section* sec, int i0, double x, double y, double z, double d); extern void nrn_pt3dchange1(Section* sec, int i, double d); @@ -561,33 +565,13 @@ static PyObject* NPyRangeVar_new_safe(PyTypeObject* type, PyObject* args, PyObje return nrn::convert_cxx_exceptions(NPyRangeVar_new, type, args, kwds); } -static int NPySegObj_init(NPySegObj* self, PyObject* args, PyObject* kwds) { - // printf("NPySegObj_init %p %p\n", self, self->pysec_); - NPySecObj* pysec; - double x; - if (!PyArg_ParseTuple(args, "O!d", psection_type, &pysec, &x)) { - return -1; - } - if (x > 1.0 && x < 1.0001) { - x = 1.0; - } - if (x < 0. || x > 1.0) { - PyErr_SetString(PyExc_ValueError, "segment position range is 0 <= x <= 1"); - return -1; - } - Py_INCREF(pysec); - if (self->pysec_) { - Py_DECREF(self->pysec_); - } - self->pysec_ = pysec; - self->x_ = x; +static int NPySegObj_init(NPySegObj* self, PyObject* /* args */, PyObject* /* kwds */) { + // PySeg objects are fully initialized in __new__ + // And strangely Python seems to never call this, as if the __new__ objs were not its instances + // So don't implement anything here return 0; } -static int NPySegObj_init_safe(NPySegObj* self, PyObject* args, PyObject* kwds) { - return nrn::convert_cxx_exceptions(NPySegObj_init, self, args, kwds); -} - static int ob_is_seg(Object* o) { if (!o || o->ctemplate->sym != nrnpy_pyobj_sym_) { return 0; @@ -607,7 +591,7 @@ static Section* o2sec(Object* o) { if (!PyObject_TypeCheck(po, psection_type)) { hoc_execerror("not a Python nrn.Section", 0); } - NPySecObj* pysec = (NPySecObj*) po; + auto* pysec = (NPySecObj*) po; return pysec->sec_; } @@ -619,7 +603,7 @@ static void o2loc(Object* o, Section** psec, double* px) { if (!PyObject_TypeCheck(po, psegment_type)) { hoc_execerror("not a Python nrn.Segment", 0); } - NPySegObj* pyseg = (NPySegObj*) po; + auto* pyseg = (NPySegObj*) po; *psec = pyseg->pysec_->sec_; if (!(*psec)->prop) { hoc_execerr_ext("nrn.Segment associated with deleted internal Section"); @@ -627,48 +611,40 @@ static void o2loc(Object* o, Section** psec, double* px) { *px = pyseg->x_; } +inline nb::object obj_get_segment(nb::object py_obj) { + // If object is list of single elem, use it + if (PyList_Check(py_obj.ptr())) { + nb::list obj_list{std::move(py_obj)}; + if (obj_list.size() != 1) { + hoc_execerror("If a list is supplied, it must be of length 1", 0); + } + py_obj = obj_list[0]; + } + + auto seg_obj = py_obj.attr("segment"); + if (!seg_obj.is_valid()) { + hoc_execerror("not a Python nrn.Segment, rxd.node, or other with a segment property", + nullptr); + } + return seg_obj; +} static void o2loc2(Object* o, Section** psec, double* px) { - bool free_po = false; if (o->ctemplate->sym != nrnpy_pyobj_sym_) { - hoc_execerror("not a Python nrn.Segment, rxd.node, or other with a segment property", 0); + hoc_execerror("not a Python nrn.Segment, rxd.node, or other with a segment property", + nullptr); } - PyObject* po = nrnpy_hoc2pyobject(o); - if (!PyObject_TypeCheck(po, psegment_type)) { - if (PyList_Check(po)) { - if (PyList_Size(po) != 1) { - hoc_execerror("If a list is supplied, it must be of length 1", 0); - } else { - PyObject* old_po = po; - Py_INCREF(old_po); - po = PyList_GetItem(po, 0); - Py_DECREF(old_po); - free_po = true; - } - } - if (!PyObject_HasAttrString(po, "segment")) { - if (free_po) { - Py_DECREF(po); - } - hoc_execerror("not a Python nrn.Segment, rxd.node, or other with a segment property", - 0); - } - PyObject* obj = po; - Py_INCREF(obj); - po = PyObject_GetAttrString(obj, "segment"); - Py_DECREF(obj); - if (free_po) { - // don't need the element from the list anymore - Py_DECREF(obj); - } - free_po = true; + + // track objects with borrow so that ref-count ends up neutral + auto py_obj_seg = nb::borrow(nrnpy_hoc2pyobject(o)); + if (!PyObject_TypeCheck(py_obj_seg.ptr(), psegment_type)) { + // Attempt to get a segment from any object. May throw + py_obj_seg = obj_get_segment(py_obj_seg); } - NPySegObj* pyseg = (NPySegObj*) po; + + auto* pyseg = (NPySegObj*) py_obj_seg.ptr(); *psec = pyseg->pysec_->sec_; *px = pyseg->x_; - if (free_po) { - Py_DECREF(po); - } if (!(*psec)->prop) { hoc_execerr_ext("nrn.Segment associated with deleted internal Section"); } diff --git a/src/nrnpython/nrnpy_nrn.h b/src/nrnpython/nrnpy_nrn.h index 64567dafba..6fd0cc81b1 100644 --- a/src/nrnpython/nrnpy_nrn.h +++ b/src/nrnpython/nrnpy_nrn.h @@ -66,7 +66,7 @@ static PyType_Slot nrnpy_SegmentType_slots[] = { {Py_tp_iter, (void*) mech_of_segment_iter_safe}, {Py_tp_methods, (void*) NPySegObj_methods}, {Py_tp_members, (void*) NPySegObj_members}, - {Py_tp_init, (void*) NPySegObj_init_safe}, + {Py_tp_init, (void*) NPySegObj_init}, {Py_tp_new, (void*) NPySegObj_new_safe}, {Py_tp_doc, (void*) "Segment objects"}, {Py_sq_contains, (void*) NPySegObj_contains_safe},