diff --git a/.appveyor.yml b/.appveyor.yml index 3acbf4677..c41b326a7 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -13,18 +13,11 @@ environment: - arch: x64 CMAKE_GENERATOR: Visual Studio 16 2019 CMAKE_CONFIG: RelWithDebInfo - CMAKE_ARGS: -DCMAKE_CXX_STANDARD=11 -DPython_EXECUTABLE="C:\\Python310-x64\\python.exe" -DBUILD_SHARED_LIBS=OFF -DVCPKG_TARGET_TRIPLET=x64-windows-static -DCMAKE_TOOLCHAIN_FILE=C:/Tools/vcpkg/scripts/buildsystems/vcpkg.cmake + CMAKE_ARGS: -DCMAKE_CXX_STANDARD=17 -DPython_EXECUTABLE="C:\\Python310-x64\\python.exe" -DBUILD_SHARED_LIBS=OFF -DVCPKG_TARGET_TRIPLET=x64-windows-static -DCMAKE_TOOLCHAIN_FILE=C:/Tools/vcpkg/scripts/buildsystems/vcpkg.cmake APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 VCPKG_BUILD_TYPE: "release" PY_PYTHON: 3.10 - - arch: x86 - CMAKE_GENERATOR: Visual Studio 15 2017 - CMAKE_CONFIG: MinSizeRel - CMAKE_ARGS: -DPython_EXECUTABLE="C:\\Python38\\python.exe" - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - PY_PYTHON: 3.8-32 - - arch: x64 CMAKE_GENERATOR: MSYS Makefiles COMPILER: MinGW64-gcc-8.1.0 @@ -43,7 +36,7 @@ for: build_script: - path - cmake --version - - git clone --depth=1 -b stable https://github.com/pybind/pybind11.git + - git clone --depth=1 --recursive https://github.com/wjakob/nanobind.git - cmake -G "%CMAKE_GENERATOR%" -DUSE_PYTHON=1 %CMAKE_ARGS% . - cmake --build . --config %CMAKE_CONFIG% - cmake --build . --config %CMAKE_CONFIG% --target check diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 649293731..468574244 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,10 +22,13 @@ jobs: - name: Python Tests run: | source pyenv/bin/activate - python3 -m unittest discover -v -s tests/ python3 -m pip install sphinx - cd docs - sphinx-build -M doctest . _build -n -E + # run tests twice: without and with numpy + python3 -m unittest discover -s tests/ + (cd docs && sphinx-build -M doctest . _build -n -E) + python3 -m pip install numpy + python3 -m unittest discover -v -s tests/ + (cd docs && sphinx-build -M doctest . _build -n -E) - uses: actions/upload-artifact@v4 with: name: gemmi-macos14 @@ -96,7 +99,7 @@ jobs: - name: apt-get run: | sudo apt-get update - sudo apt-get install python3-full g++ gfortran python3-numpy + sudo apt-get install python3-full g++ gfortran - name: build and test run: | g++ --version @@ -104,6 +107,7 @@ jobs: source pyenv/bin/activate export SKBUILD_CMAKE_ARGS='-DFETCH_ZLIB_NG=ON;-DCMAKE_BUILD_TYPE=None' python3 -m pip install -v . + python3 -m pip install numpy python3 -m unittest discover -v -s tests/ - name: run doctest run: | @@ -122,13 +126,9 @@ jobs: run: | sudo apt-get update sudo apt-get install libz-dev python3-pip g++ gfortran python3-numpy - - name: install pybind11 + - name: install nanobind run: | - git clone --depth=1 https://github.com/pybind/pybind11.git - #cd pybind11 - #cmake . -Wno-dev -DPYTHON_EXECUTABLE=/usr/bin/python3 -DPYBIND11_TEST=OFF - #make - #sudo make install + git clone --recursive --depth=1 https://github.com/wjakob/nanobind.git - name: build and test run: | g++ --version @@ -158,9 +158,9 @@ jobs: run: | sudo apt-get update sudo apt-get install libz-dev python3-pip g++ gfortran python3-numpy valgrind - - name: install pybind11 + - name: install nanobind run: | - python3 -m pip install pybind11 + python3 -m pip install nanobind - name: build and test run: | g++ --version @@ -181,21 +181,21 @@ jobs: - name: run tests under valgrind run: PYTHONMALLOC=malloc valgrind python3 -m unittest discover -v -s tests/ - ubuntu2004_clang6: - name: "Ubuntu 20.04 with Clang 6.0" + ubuntu2004_clang9: + name: "Ubuntu 20.04 with Clang 9" runs-on: ubuntu-20.04 if: "!contains(github.event.head_commit.message, '[skip ci]')" env: - CC: clang-6.0 - CXX: clang++-6.0 + CC: clang-9 + CXX: clang++-9 SKBUILD_CMAKE_ARGS: "-DCMAKE_CXX_STANDARD=11;-DEXTRA_WARNINGS=ON;-DSTANDALONE_PYTHON_MODULE=OFF" SKBUILD_CMAKE_TARGETS: "all;check" steps: - uses: actions/checkout@v4 - - run: sudo apt-get install clang-6.0 libz-dev python3-pip python3-numpy - - name: install pybind11 + - run: sudo apt-get install clang-9 libz-dev python3-pip python3-numpy + - name: install nanobind run: | - sudo /usr/bin/python3 -m pip install "pybind11[global]" build + sudo /usr/bin/python3 -m pip install nanobind build - name: build and test run: | $CXX --version @@ -224,11 +224,11 @@ jobs: python3.8 --version cmake --version type python3.8 - - name: install pybind11 + - name: install nanobind run: | - git clone --branch stable --depth=1 https://github.com/pybind/pybind11.git - cd pybind11 - cmake . -Wno-dev -DPYTHON_EXECUTABLE=/usr/bin/python3.8 -DPYBIND11_TEST=OFF + git clone --recursive --depth=1 https://github.com/wjakob/nanobind.git + cd nanobind + cmake . -Wno-dev -DPYTHON_EXECUTABLE=/usr/bin/python3.8 -DNB_TEST=OFF make make install - name: build and test diff --git a/.gitignore b/.gitignore index 0918f40b2..cef044c1c 100644 --- a/.gitignore +++ b/.gitignore @@ -22,8 +22,8 @@ tags /CTestTestfile.cmake /Makefile -# local pybind11 is used for testing new versions, don't add to the repo -third_party/pybind11 +# local nanobind can be used for testing new versions, don't add to the repo +/nanobind # ignore random data files in the top and source directory /*.cif diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d78c563e..45308b378 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,9 +67,8 @@ endif() set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) # Unless it's explicitly specifed, we want LTO for non-debug builds only. -# pybind11Common.cmake has its own logic to set LTO options. -# Having different options in pybind11_add_module than for gemmi_cpp -# may cause problems -- we may need to handle it at some point. +# TODO: check that it's compatible with nanobind_add_module, which has +# its own logic of setting LTO options. if (NOT DEFINED CMAKE_INTERPROCEDURAL_OPTIMIZATION) include(CheckIPOSupported) check_ipo_supported(RESULT is_ipo_supported OUTPUT ipo_error) @@ -108,10 +107,9 @@ if (DEFINED ENV{CXXFLAGS}) endif() # Set default build mode (based on CMake FAQ) -if (NOT CMAKE_BUILD_TYPE AND NOT USING_ENV_CXXFLAGS) - set(CMAKE_BUILD_TYPE Release CACHE STRING - "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel." - FORCE) +if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES AND NOT USING_ENV_CXXFLAGS) + set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") endif() message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") @@ -458,29 +456,28 @@ if (USE_PYTHON) else() find_package(Python ${PYTHON_VERSION} REQUIRED COMPONENTS Interpreter Development.Module) endif() - # pybind11 macros use PYTHON_EXECUTABLE - if (DEFINED Python_EXECUTABLE AND NOT DEFINED PYTHON_EXECUTABLE) - set(PYTHON_EXECUTABLE "${Python_EXECUTABLE}") - endif() - if (EXISTS "${CMAKE_HOME_DIRECTORY}/pybind11") - message(STATUS "Using ${CMAKE_HOME_DIRECTORY}/pybind11 (internal copy).") - add_subdirectory(pybind11) + if (EXISTS "${CMAKE_HOME_DIRECTORY}/nanobind") + message(STATUS "Using ${CMAKE_HOME_DIRECTORY}/nanobind (internal copy).") + add_subdirectory(nanobind) else() - # use pybind11-config (if available) to determine pybind11_DIR - execute_process(COMMAND pybind11-config --cmakedir OUTPUT_VARIABLE pybind11_DIR) - string(STRIP "${pybind11_DIR}" pybind11_DIR) - find_package(pybind11 2.6 CONFIG REQUIRED) - message(STATUS "Found pybind11 ${pybind11_VERSION}: ${pybind11_INCLUDE_DIRS}") + # Detect the installed nanobind package and import it into CMake + execute_process( + COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir + OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE NB_DIR) + list(APPEND CMAKE_PREFIX_PATH "${NB_DIR}") + find_package(nanobind 2.1.0 CONFIG REQUIRED) + message(STATUS "Found nanobind ${nanobind_VERSION}: ${NB_DIR}") endif() - pybind11_add_module(gemmi_py - python/mol.cpp python/gemmi.cpp python/align.cpp + nanobind_add_module(gemmi_py NOMINSIZE + python/gemmi.cpp python/align.cpp python/ccp4.cpp python/chemcomp.cpp python/cif.cpp python/custom.cpp python/elem.cpp python/grid.cpp python/hkl.cpp - python/meta.cpp python/monlib.cpp + python/meta.cpp python/mol.cpp python/monlib.cpp python/mtz.cpp python/read.cpp python/recgrid.cpp python/scaling.cpp python/search.cpp python/sf.cpp python/sym.cpp python/topo.cpp python/unitcell.cpp python/write.cpp) + if (STANDALONE_PYTHON_MODULE) target_sources(gemmi_py PRIVATE $) get_target_property(_gemmi_cpp_libs gemmi_cpp LINK_LIBRARIES) @@ -489,9 +486,25 @@ if (USE_PYTHON) target_link_libraries(gemmi_py PRIVATE gemmi_cpp) endif() set_property(TARGET gemmi_py PROPERTY OUTPUT_NAME gemmi) + + # nanobind gives warnings with -Wpedantic and -Wshadow if(CMAKE_CXX_FLAGS MATCHES "-Wshadow") + if (TARGET nanobind-static) + target_compile_options(nanobind-static PRIVATE "-Wno-shadow") + endif() target_compile_options(gemmi_py PRIVATE "-Wno-shadow") endif() + if (CMAKE_CXX_FLAGS MATCHES "-Wpedantic") + if (TARGET nanobind-static) + target_compile_options(nanobind-static PRIVATE "-Wno-pedantic") + endif() + target_compile_options(gemmi_py PRIVATE "-Wno-pedantic") + endif() + if (CMAKE_CXX_FLAGS MATCHES "-Wredundant-decls") + if (TARGET nanobind-static) + target_compile_options(nanobind-static PRIVATE "-Wno-redundant-decls") + endif() + endif() if (USE_NLOPT) message(STATUS "Using NLopt.") @@ -503,22 +516,14 @@ if (USE_PYTHON) if (GENERATE_STUBS AND CMAKE_CROSSCOMPILING) message(WARNING "Stubs cannot be generated when cross-compiling - skipping.") elseif (GENERATE_STUBS) - set(sep ":") - if (WIN32) - set(sep ";") - endif() - add_custom_command(TARGET gemmi_py POST_BUILD - COMMAND ${CMAKE_COMMAND} -E env - "PYTHONPATH=$${sep}$ENV{PYTHONPATH}" - ${Python_EXECUTABLE} -m pybind11_stubgen gemmi - --enum-class-locations=Ignore:gemmi.ContactSearch - --enum-class-locations=.+:gemmi - --numpy-array-remove-parameters - --output-dir stubs - COMMAND ${CMAKE_COMMAND} -E touch stubs/gemmi/py.typed - COMMENT "Generate type stubs with pybind11-stubgen" - BYPRODUCTS stubs/gemmi/__init__.pyi stubs/gemmi/cif.pyi stubs/gemmi/py.typed - VERBATIM) + #nanobind_add_stub( + # gemmi_py_stub + # MODULE gemmi_py + # OUTPUT stubs/gemmi/__init__.pyi + # PYTHON_PATH $ + # DEPENDS gemmi_py + # MARKER_FILE stubs/py.typed + #) endif() else() message(STATUS "Skipping Python module. Add -D USE_PYTHON=1 to build it.") @@ -579,8 +584,8 @@ if (USE_PYTHON) install(DIRECTORY examples DESTINATION "${Python_SITEARCH}/gemmi" COMPONENT py FILES_MATCHING PATTERN "*.py" PATTERN "[._]*" EXCLUDE) - if (GENERATE_STUBS AND NOT CMAKE_CROSSCOMPILING) - install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/stubs/gemmi" - DESTINATION "${Python_SITEARCH}" COMPONENT py) - endif() + #if (GENERATE_STUBS AND NOT CMAKE_CROSSCOMPILING) + # install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/stubs/gemmi" + # DESTINATION "${Python_SITEARCH}" COMPONENT py) + #endif() endif() diff --git a/docs/cif.rst b/docs/cif.rst index 43183c576..bbc00412a 100644 --- a/docs/cif.rst +++ b/docs/cif.rst @@ -225,9 +225,6 @@ element found in mmCIF: More complex examples are shown in the :ref:`cif_examples` section. -Internally, Python bindings use -`pybind11 `_. - DOM and SAX =========== diff --git a/docs/conf.py b/docs/conf.py index 773d56ef4..296fdd6e8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -54,7 +54,6 @@ doctest_global_setup = ''' import os import sys -assert sys.version_info[0] > 2, "Tests in docs are for Python 3 only" disabled_features = [] try: import numpy diff --git a/docs/grid.rst b/docs/grid.rst index 847647a90..1521247f1 100644 --- a/docs/grid.rst +++ b/docs/grid.rst @@ -504,7 +504,7 @@ The parameters of SolventMasker can be inspected and changed: .. doctest:: >>> masker.atomic_radii_set - + AtomicRadiiSet.Cctbx >>> masker.rprobe 1.1 >>> masker.rshrink diff --git a/docs/hkl.rst b/docs/hkl.rst index 1f5fad278..3e5db8aa7 100644 --- a/docs/hkl.rst +++ b/docs/hkl.rst @@ -114,7 +114,7 @@ Datasets are stored in the variable `datasets`:: .. doctest:: >>> mtz.datasets - MtzDatasets[, ] + gemmi.MtzDatasets([, ]) In the MTZ file, each dataset is identified internally by an integer "dataset ID". To get dataset with the specified ID use function:: @@ -820,7 +820,7 @@ Blocks are moved from the Document to the new list: >>> doc >>> rblocks - ReflnBlocks[] + gemmi.ReflnBlocks([]) When ReflnBlock is created some of the mmCIF tags are interpreted to initialize the following properties: @@ -1209,7 +1209,7 @@ The unit cell and the upper bin boundaries (in Å\ :sup:`--2`) are stored intern >>> binner.cell >>> binner.limits # doctest: +ELLIPSIS - [0.12224713046942721, 0.19395414269012246, 0.254107666379415..., inf] + [0.122247130469427..., 0.193954142690122..., 0.254107666379415..., inf] >>> binner.size # number of bins 4 diff --git a/docs/install.rst b/docs/install.rst index aa45896ef..b68230374 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -90,8 +90,10 @@ To install the gemmi module do:: We have binary wheels for several Python versions (for all supported CPython versions and one PyPy version), so the command usually downloads binaries. If a matching wheel is not available, -the module is compiled from source -- it takes several minutes -and requires a C++ compiler. +the module is compiled from source -- it takes a few minutes +and requires a C++ compiler that supports C++17. + +Gemmi 0.7+ supports only Python 3.8+. Other binaries ~~~~~~~~~~~~~~ @@ -136,10 +138,11 @@ for details. If gemmi is already installed, uninstall the old version first (`pip uninstall`) or add option `--upgrade`. -Alternatively, you can build a cloned project directly with CMake:: +Alternatively, you can manually install nanobind and cmake (using pip) +and build a cloned project directly with CMake:: cmake -D USE_PYTHON=1 . - make -j4 py + make -j4 gemmi_py Fortran and C bindings ---------------------- @@ -246,6 +249,8 @@ Code derived from the following projects is used in the library: Projects included under `third_party/` that are not used in the library itself, but are used in command-line utilities, python bindings or tests: +* `zpp serializer `_ -- + serialization framework. License: MIT. * `The Lean Mean C++ Option Parser `_ -- command-line option parser. License: MIT. * `doctest `_ -- testing framework. @@ -258,7 +263,7 @@ itself, but are used in command-line utilities, python bindings or tests: Not distributed with Gemmi: -* `pybind11 `_ -- used for creating +* `nanobind `_ -- used for creating Python bindings. License: 3-clause BSD. * `zlib-ng `_ -- optional, can be used instead of zlib for faster reading of gzipped files. diff --git a/docs/mol.rst b/docs/mol.rst index 6623375f2..cb7fba163 100644 --- a/docs/mol.rst +++ b/docs/mol.rst @@ -1637,9 +1637,9 @@ Properties of the Entity class are shown in this example: >>> ent.subchains ['Axp'] >>> ent.entity_type - + EntityType.Polymer >>> ent.polymer_type - + PolymerType.PeptideL >>> ent.full_sequence[:5] ['MET', 'GLU', 'GLN', 'ARG', 'ILE'] @@ -1696,7 +1696,7 @@ Each connection stores: .. doctest:: >>> st.connections[0].type - + ConnectionType.Disulf * name -- a unique name corresponding to _struct_conn.id in the mmCIF format; it is auto-generated the connections are read from the PDB format, @@ -1727,9 +1727,9 @@ Each connection stores: .. doctest:: >>> st.connections[2].asu - + Asu.Same >>> st.connections[-1].asu - + Asu.Different * and a distance read from the file. @@ -2262,9 +2262,9 @@ ResidueKind can be obtained from PolymerType: .. doctest:: >>> st.get_entity('2').polymer_type - + PolymerType.PeptideL >>> gemmi.sequence_kind(_) - + ResidueKind.AA In mmCIF `_entity_poly.pdbx_seq_one_letter_code` and in the OneDep interface, the PDB uses a hybrid sequence format: a single letter for standard @@ -3043,7 +3043,7 @@ If it's a polymer, we can ask for polymer type and sequence: .. doctest:: >>> polymer_b.check_polymer_type() - + PolymerType.PeptideL >>> polymer_b.make_one_letter_sequence() 'sAXvsAXv' @@ -3149,7 +3149,7 @@ Residue contains also a number of properties: >>> residue.label_seq 2 >>> residue.entity_type - + EntityType.Polymer >>> residue.het_flag 'A' >>> residue.flag diff --git a/include/gemmi/asudata.hpp b/include/gemmi/asudata.hpp index abdd45f5c..ccb44ee09 100644 --- a/include/gemmi/asudata.hpp +++ b/include/gemmi/asudata.hpp @@ -96,7 +96,7 @@ int count_equal_values(const std::vector& a, const std::vector& b) { template struct HklValue { - Miller hkl; + alignas(8) Miller hkl; T value; bool operator<(const Miller& m) const { return hkl < m; } diff --git a/include/gemmi/binner.hpp b/include/gemmi/binner.hpp index 333f5cc30..cc4fea431 100644 --- a/include/gemmi/binner.hpp +++ b/include/gemmi/binner.hpp @@ -148,15 +148,19 @@ struct Binner { return nums; } - std::vector get_bins_from_1_d2(const std::vector& inv_d2) const { + std::vector get_bins_from_1_d2(const double* inv_d2, size_t size) const { ensure_limits_are_set(); int hint = 0; - std::vector nums(inv_d2.size()); - for (size_t i = 0; i < inv_d2.size(); ++i) + std::vector nums(size); + for (size_t i = 0; i < size; ++i) nums[i] = get_bin_from_1_d2_hinted(inv_d2[i], hint); return nums; } + std::vector get_bins_from_1_d2(const std::vector& inv_d2) const { + return get_bins_from_1_d2(inv_d2.data(), inv_d2.size()); + } + double dmin_of_bin(int n) const { return 1. / std::sqrt(limits.at(n)); } @@ -198,7 +202,8 @@ struct HklMatch { std::vector pos; size_t hkl_size; - HklMatch(const Miller* hkl, size_t hkl_size_, const Miller* ref, size_t ref_size) + HklMatch(const Miller* hkl, size_t hkl_size_, + const Miller* ref, size_t ref_size) : pos(ref_size, -1), hkl_size(hkl_size_) { // Usually, both datasets are sorted. This make things faster. if (std::is_sorted(hkl, hkl + hkl_size) && @@ -229,14 +234,17 @@ struct HklMatch { HklMatch(const std::vector& hkl, const std::vector& ref) : HklMatch(hkl.data(), hkl.size(), ref.data(), ref.size()) {} - template std::vector aligned(const std::vector& v, T nan) { - if (v.size() != hkl_size) + template std::vector aligned_(const T* v, size_t size, T nan) { + if (size != hkl_size) fail("HklMatch.aligned(): wrong data, size differs"); std::vector result(pos.size()); for (size_t i = 0; i != pos.size(); ++i) result[i] = pos[i] >= 0 ? v[pos[i]] : nan; return result; } + template std::vector aligned(const std::vector& v, T nan) { + return aligned_(v.data(), v.size(), nan); + } }; } // namespace gemmi diff --git a/include/gemmi/grid.hpp b/include/gemmi/grid.hpp index 83eee4a24..1dc2e8113 100644 --- a/include/gemmi/grid.hpp +++ b/include/gemmi/grid.hpp @@ -211,6 +211,9 @@ struct GridMeta { size_t index_q(int u, int v, int w) const { return size_t(w * nv + v) * nu + u; } + size_t index_q(size_t u, size_t v, size_t w) const { + return (w * nv + v) * nu + u; + } /// Faster than index_s(), but works only if `-nu <= u < 2*nu`, etc. size_t index_n(int u, int v, int w) const { return index_n_ref(u, v, w); } diff --git a/pyproject.toml b/pyproject.toml index c31db5772..a0db8e8f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,5 @@ [build-system] -requires = ["scikit-build-core~=0.10.5", "pybind11>=2.6.2", - "pybind11-stubgen~=2.5.1", "numpy"] +requires = ["scikit-build-core~=0.10.5", "nanobind>=2.1"] build-backend = "scikit_build_core.build" # https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ @@ -88,6 +87,10 @@ test-command = "python -m unittest discover -v -s {project}/tests/" [tool.cibuildwheel.environment] SKBUILD_CMAKE_ARGS = '-DBUILD_GEMMI_PROGRAM=OFF;-DINSTALL_DEV_FILES=OFF;-DBUILD_SHARED_LIBS=OFF;-DFETCH_ZLIB_NG=ON' +# Needed for full C++17 support on macOS +[tool.cibuildwheel.macos.environment] +MACOSX_DEPLOYMENT_TARGET = "10.14" + # https://cibuildwheel.readthedocs.io/en/stable/faq/#macos-building-cpython-38-wheels-on-arm64 [[tool.cibuildwheel.overrides]] select = "cp38-macosx_arm64" diff --git a/python/align.cpp b/python/align.cpp index cf5ce7aad..d666fb64e 100644 --- a/python/align.cpp +++ b/python/align.cpp @@ -4,86 +4,85 @@ #include "gemmi/seqalign.hpp" // for align_string_sequences #include "common.h" -#include +#include +#include -namespace py = pybind11; using namespace gemmi; - -void add_alignment(py::module& m) { +void add_alignment(nb::module_& m) { // sequence alignment - py::class_(m, "AlignmentResult") - .def_readonly("score", &AlignmentResult::score) - .def_readonly("match_count", &AlignmentResult::match_count) - .def_readonly("match_string", &AlignmentResult::match_string) + nb::class_(m, "AlignmentResult") + .def_ro("score", &AlignmentResult::score) + .def_ro("match_count", &AlignmentResult::match_count) + .def_ro("match_string", &AlignmentResult::match_string) .def("cigar_str", &AlignmentResult::cigar_str) .def("calculate_identity", &AlignmentResult::calculate_identity, - py::arg("which")=0) - .def("add_gaps", &AlignmentResult::add_gaps, py::arg("s"), py::arg("which")) + nb::arg("which")=0) + .def("add_gaps", &AlignmentResult::add_gaps, nb::arg("s"), nb::arg("which")) .def("formatted", &AlignmentResult::formatted) ; - py::class_(m, "AlignmentScoring") - .def(py::init([](char what) { + nb::class_(m, "AlignmentScoring") + .def("__init__", [](AlignmentScoring* t, char what) { const AlignmentScoring* s = AlignmentScoring::simple(); if (what == 'p') s = AlignmentScoring::partial_model(); else if (what == 'b') s = AlignmentScoring::blosum62(); - return new AlignmentScoring(*s); - }), py::arg("what")='s') - .def_readwrite("match", &AlignmentScoring::match) - .def_readwrite("mismatch", &AlignmentScoring::mismatch) - .def_readwrite("gapo", &AlignmentScoring::gapo) - .def_readwrite("gape", &AlignmentScoring::gape) - .def_readwrite("good_gapo", &AlignmentScoring::good_gapo) - .def_readwrite("bad_gapo", &AlignmentScoring::bad_gapo) + new(t) AlignmentScoring(*s); + }, nb::arg("what")='s') + .def_rw("match", &AlignmentScoring::match) + .def_rw("mismatch", &AlignmentScoring::mismatch) + .def_rw("gapo", &AlignmentScoring::gapo) + .def_rw("gape", &AlignmentScoring::gape) + .def_rw("good_gapo", &AlignmentScoring::good_gapo) + .def_rw("bad_gapo", &AlignmentScoring::bad_gapo) ; m.def("align_string_sequences", &align_string_sequences, - py::arg("query"), py::arg("target"), py::arg("target_gapo"), - py::arg("scoring")=nullptr); + nb::arg("query"), nb::arg("target"), nb::arg("target_gapo"), + nb::arg("scoring")=nb::none()); m.def("align_sequence_to_polymer", [](const std::vector& full_seq, const ResidueSpan& polymer, PolymerType polymer_type, AlignmentScoring* scoring) { return align_sequence_to_polymer(full_seq, polymer, polymer_type, scoring); - }, py::arg("full_seq"), py::arg("polymer"), - py::arg("polymer_type"), py::arg("scoring")=nullptr); + }, nb::arg("full_seq"), nb::arg("polymer"), + nb::arg("polymer_type"), nb::arg("scoring")=nb::none()); // structure superposition - py::enum_(m, "SupSelect") + nb::enum_(m, "SupSelect") .value("CaP", SupSelect::CaP) .value("MainChain", SupSelect::MainChain) .value("All", SupSelect::All); - py::class_(m, "SupResult") - .def_readonly("rmsd", &SupResult::rmsd) - .def_readonly("count", &SupResult::count) - .def_readonly("center1", &SupResult::center1) - .def_readonly("center2", &SupResult::center2) - .def_readonly("transform", &SupResult::transform) + nb::class_(m, "SupResult") + .def_ro("rmsd", &SupResult::rmsd) + .def_ro("count", &SupResult::count) + .def_ro("center1", &SupResult::center1) + .def_ro("center2", &SupResult::center2) + .def_ro("transform", &SupResult::transform) ; m.def("calculate_current_rmsd", [](const ResidueSpan& fixed, const ResidueSpan& movable, PolymerType ptype, SupSelect sel, char altloc) { return calculate_current_rmsd(fixed, movable, ptype, sel, altloc); - }, py::arg("fixed"), py::arg("movable"), py::arg("ptype"), py::arg("sel"), - py::arg("altloc")='\0'); + }, nb::arg("fixed"), nb::arg("movable"), nb::arg("ptype"), nb::arg("sel"), + nb::arg("altloc")='\0'); m.def("calculate_superposition", [](const ResidueSpan& fixed, const ResidueSpan& movable, PolymerType ptype, SupSelect sel, int trim_cycles, double trim_cutoff, char altloc) { return calculate_superposition(fixed, movable, ptype, sel, trim_cycles, trim_cutoff, altloc); - }, py::arg("fixed"), py::arg("movable"), py::arg("ptype"), py::arg("sel"), - py::arg("trim_cycles")=0, py::arg("trim_cutoff")=2.0, - py::arg("altloc")='\0'); + }, nb::arg("fixed"), nb::arg("movable"), nb::arg("ptype"), nb::arg("sel"), + nb::arg("trim_cycles")=0, nb::arg("trim_cutoff")=2.0, + nb::arg("altloc")='\0'); m.def("calculate_superpositions_in_moving_window", [](const ResidueSpan& fixed, const ResidueSpan& movable, PolymerType ptype, double radius) { return calculate_superpositions_in_moving_window(fixed, movable, ptype, radius); - }, py::arg("fixed"), py::arg("movable"), py::arg("ptype"), py::arg("radius")=10.0); + }, nb::arg("fixed"), nb::arg("movable"), nb::arg("ptype"), nb::arg("radius")=10.0); m.def("superpose_positions", [](std::vector pos1, std::vector pos2, @@ -94,13 +93,13 @@ void add_alignment(py::module& m) { fail("superpose_positions: weights must be empty or of the same length as pos1/pos2"); return superpose_positions(pos1.data(), pos2.data(), pos1.size(), weight.empty() ? nullptr : weight.data()); - }, py::arg("pos1"), py::arg("pos2"), py::arg("weight")=std::vector{}); + }, nb::arg("pos1"), nb::arg("pos2"), nb::arg("weight")=std::vector{}); } -void add_assign_label_seq_id(py::class_& structure) { +void add_assign_label_seq_id(nb::class_& structure) { structure - .def("assign_label_seq_id", &assign_label_seq_id, py::arg("force")=false) + .def("assign_label_seq_id", &assign_label_seq_id, nb::arg("force")=false) .def("clear_sequences", &clear_sequences) - .def("assign_best_sequences", &assign_best_sequences, py::arg("fasta_sequences")) + .def("assign_best_sequences", &assign_best_sequences, nb::arg("fasta_sequences")) ; } diff --git a/python/array.h b/python/array.h new file mode 100644 index 000000000..d5b1c8564 --- /dev/null +++ b/python/array.h @@ -0,0 +1,62 @@ +#pragma once +#include +#include "common.h" +#include + +template +using cpu_array = nb::ndarray, nb::device::cpu>; +template +using cpu_c_array = nb::ndarray, nb::device::cpu, nb::c_contig>; +using cpu_miller_array = nb::ndarray, nb::device::cpu>; + +template +auto numpy_array_from_vector(std::vector&& original_vec) { + using V = std::vector; + auto v = new V(std::move(original_vec)); + nb::capsule owner(v, [](void* p) noexcept { delete static_cast(p); }); + return nb::ndarray>(v->data(), {v->size()}, owner); +} + +template +auto py_array2d_from_vector(std::vector>&& original_vec) { + using V = std::vector>; + auto v = new V(std::move(original_vec)); + nb::capsule owner(v, [](void* p) noexcept { delete static_cast(p); }); + return nb::ndarray>(v->data(), {v->size(), N}, owner); +} + +// to be used with rv_policy::reference_internal +template +auto vector_member_array(std::vector& vec, T S::*ptr) { + constexpr int64_t stride = static_cast(sizeof(S) / sizeof(T)); + static_assert(stride * sizeof(T) == sizeof(S), + "vector_member_array(): problem with stride"); + return nb::ndarray>( + &(vec.data()->*ptr), + {vec.size()}, + nb::handle(), + {stride}); +} + +template +auto make_numpy_array(std::initializer_list size, + std::initializer_list strides={}) { + size_t total_size = 1; + for (size_t i : size) + total_size *= i; + T* c_array = new T[total_size]; + nb::capsule owner(c_array, [](void* p) noexcept { delete [] static_cast(p); }); + return nb::ndarray(c_array, size, owner, strides); +} + +template +auto miller_function(const Obj& obj, Func func, cpu_miller_array hkl) { + auto h = hkl.view(); + size_t n = h.shape(0); + auto result = make_numpy_array({n}); + Ret* rptr = result.data(); + for (size_t i = 0; i < h.shape(0); ++i) + rptr[i] = (obj.*func)({h(i, 0), h(i, 1), h(i, 2)}); + return result; +} + diff --git a/python/arrvec.h b/python/arrvec.h deleted file mode 100644 index 42bdb18b1..000000000 --- a/python/arrvec.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once -#include - -template -pybind11::array_t py_array_from_vector(std::vector&& original_vec) { - auto v = new std::vector(std::move(original_vec)); - pybind11::capsule cap(v, [](void* p) { delete (std::vector*) p; }); - return pybind11::array_t(v->size(), v->data(), cap); -} diff --git a/python/ccp4.cpp b/python/ccp4.cpp index 9aa352580..3c1ab4c03 100644 --- a/python/ccp4.cpp +++ b/python/ccp4.cpp @@ -4,23 +4,23 @@ #include "gemmi/util.hpp" // for cat #include "gemmi/read_map.hpp" // for read_ccp4_map, read_ccp4_mask #include "common.h" -#include +#include +#include // for Ccp4Base::axis_positions -namespace py = pybind11; using namespace gemmi; template -py::class_, Ccp4Base> add_ccp4_common(py::module& m, const char* name) { +auto add_ccp4_common(nb::module_& m, const char* name) { using Map = Ccp4; - return py::class_(m, name) - .def(py::init<>()) - .def_readwrite("grid", &Map::grid) + return nb::class_(m, name) + .def(nb::init<>()) + .def_rw("grid", &Map::grid) .def("setup", &Map::setup, - py::arg("default_value"), py::arg("mode")=MapSetup::Full) + nb::arg("default_value"), nb::arg("mode")=MapSetup::Full) .def("update_ccp4_header", &Map::update_ccp4_header, - py::arg("mode")=-1, py::arg("update_stats")=true) + nb::arg("mode")=-1, nb::arg("update_stats")=true) .def("full_cell", &Map::full_cell) - .def("write_ccp4_map", &Map::write_ccp4_map, py::arg("filename")) + .def("write_ccp4_map", &Map::write_ccp4_map, nb::arg("filename")) .def("set_extent", &Map::set_extent) .def("__repr__", [=](const Map& self) { const SpaceGroup* sg = self.grid.spacegroup; @@ -30,16 +30,16 @@ py::class_, Ccp4Base> add_ccp4_common(py::module& m, const char* name) { }); } -void add_ccp4(py::module& m) { - py::enum_(m, "MapSetup") +void add_ccp4(nb::module_& m) { + nb::enum_(m, "MapSetup") .value("Full", MapSetup::Full) .value("NoSymmetry", MapSetup::NoSymmetry) .value("ReorderOnly", MapSetup::ReorderOnly); - py::class_(m, "Ccp4Base") + nb::class_(m, "Ccp4Base") .def("header_i32", &Ccp4Base::header_i32) .def("header_float", &Ccp4Base::header_float) - .def("header_str", &Ccp4Base::header_str, py::arg("w"), py::arg("len")=80) + .def("header_str", &Ccp4Base::header_str, nb::arg("w"), nb::arg("len")=80) .def("set_header_i32", &Ccp4Base::set_header_i32) .def("set_header_float", &Ccp4Base::set_header_float) .def("set_header_str", &Ccp4Base::set_header_str) @@ -52,9 +52,9 @@ void add_ccp4(py::module& m) { add_ccp4_common(m, "Ccp4Map"); add_ccp4_common(m, "Ccp4Mask"); m.def("read_ccp4_map", &read_ccp4_map, - py::arg("path"), py::arg("setup")=false, py::return_value_policy::move, + nb::arg("path"), nb::arg("setup")=false, nb::rv_policy::move, "Reads a CCP4 file, mode 2 (floating-point data)."); m.def("read_ccp4_mask", &read_ccp4_mask, - py::arg("path"), py::arg("setup")=false, py::return_value_policy::move, + nb::arg("path"), nb::arg("setup")=false, nb::rv_policy::move, "Reads a CCP4 file, mode 0 (int8_t data, usually 0/1 masks)."); } diff --git a/python/chemcomp.cpp b/python/chemcomp.cpp index 0f88f04cf..05d0e1d8d 100644 --- a/python/chemcomp.cpp +++ b/python/chemcomp.cpp @@ -4,38 +4,38 @@ #include "gemmi/to_chemcomp.hpp" // for add_chemcomp_to_block #include "common.h" -#include -#include +#include +#include +#include // for find_shortest_path -namespace py = pybind11; using namespace gemmi; -PYBIND11_MAKE_OPAQUE(std::vector) -PYBIND11_MAKE_OPAQUE(std::vector) -PYBIND11_MAKE_OPAQUE(std::vector) -PYBIND11_MAKE_OPAQUE(std::vector) -PYBIND11_MAKE_OPAQUE(std::vector) -PYBIND11_MAKE_OPAQUE(std::vector) +NB_MAKE_OPAQUE(std::vector) +NB_MAKE_OPAQUE(std::vector) +NB_MAKE_OPAQUE(std::vector) +NB_MAKE_OPAQUE(std::vector) +NB_MAKE_OPAQUE(std::vector) +NB_MAKE_OPAQUE(std::vector) -void add_chemcomp(py::module& m) { - py::class_ chemcomp(m, "ChemComp"); - py::class_ chemcompatom(chemcomp, "Atom"); +void add_chemcomp(nb::module_& m) { + nb::class_ chemcomp(m, "ChemComp"); + nb::class_ chemcompatom(chemcomp, "Atom"); - py::class_ restraints(m, "Restraints"); - py::class_ restraintsbond(restraints, "Bond"); - py::class_ restraintsangle(restraints, "Angle"); - py::class_ restraintstorsion(restraints, "Torsion"); - py::class_ restraintschirality(restraints, "Chirality"); - py::class_ restraintsplane(restraints, "Plane"); + nb::class_ restraints(m, "Restraints"); + nb::class_ restraintsbond(restraints, "Bond"); + nb::class_ restraintsangle(restraints, "Angle"); + nb::class_ restraintstorsion(restraints, "Torsion"); + nb::class_ restraintschirality(restraints, "Chirality"); + nb::class_ restraintsplane(restraints, "Plane"); - py::bind_vector>(m, "RestraintsBonds"); - py::bind_vector>(m, "RestraintsAngles"); - py::bind_vector>(m, "RestraintsTorsions"); - py::bind_vector>(m, "RestraintsChirs"); - py::bind_vector>(m, "RestraintsPlanes"); - py::bind_vector>(m, "ChemCompAtoms"); + nb::bind_vector, rv_ri>(m, "RestraintsBonds"); + nb::bind_vector, rv_ri>(m, "RestraintsAngles"); + nb::bind_vector, rv_ri>(m, "RestraintsTorsions"); + nb::bind_vector, rv_ri>(m, "RestraintsChirs"); + nb::bind_vector, rv_ri>(m, "RestraintsPlanes"); + nb::bind_vector, rv_ri>(m, "ChemCompAtoms"); - py::enum_(m, "BondType") + nb::enum_(m, "BondType") .value("Unspec", BondType::Unspec) .value("Single", BondType::Single) .value("Double", BondType::Double) @@ -44,99 +44,99 @@ void add_chemcomp(py::module& m) { .value("Deloc", BondType::Deloc) .value("Metal", BondType::Metal); - py::enum_(m, "ChiralityType") + nb::enum_(m, "ChiralityType") .value("Positive", ChiralityType::Positive) .value("Negative", ChiralityType::Negative) .value("Both", ChiralityType::Both); - py::enum_(restraints, "DistanceOf") + nb::enum_(restraints, "DistanceOf") .value("ElectronCloud", Restraints::DistanceOf::ElectronCloud) .value("Nucleus", Restraints::DistanceOf::Nucleus); - py::class_(restraints, "AtomId") - .def(py::init([](int comp, const std::string& atom) { - return new Restraints::AtomId{comp, atom}; - })) - .def(py::init([](const std::string& atom) { - return new Restraints::AtomId{1, atom}; - })) - .def_readwrite("comp", &Restraints::AtomId::comp) - .def_readwrite("atom", &Restraints::AtomId::atom) + nb::class_(restraints, "AtomId") + .def("__init__", [](Restraints::AtomId* p, int comp, const std::string& atom) { + new(p) Restraints::AtomId{comp, atom}; + }) + .def("__init__", [](Restraints::AtomId* p, const std::string& atom) { + new(p) Restraints::AtomId{1, atom}; + }) + .def_rw("comp", &Restraints::AtomId::comp) + .def_rw("atom", &Restraints::AtomId::atom) .def("get_from", (Atom* (Restraints::AtomId::*)(Residue&, Residue*, char, char) const) &Restraints::AtomId::get_from, - py::arg("res1"), py::arg("res2"), py::arg("altloc1"), py::arg("altloc2"), - py::return_value_policy::reference) + nb::arg("res1"), nb::arg("res2"), nb::arg("altloc1"), nb::arg("altloc2"), + nb::rv_policy::reference) .def("__repr__", [](const Restraints::AtomId& self) { return cat("'); }); restraintsbond - .def(py::init<>()) - .def_readwrite("id1", &Restraints::Bond::id1) - .def_readwrite("id2", &Restraints::Bond::id2) - .def_readwrite("type", &Restraints::Bond::type) - .def_readwrite("aromatic", &Restraints::Bond::aromatic) - .def_readwrite("value", &Restraints::Bond::value) - .def_readwrite("esd", &Restraints::Bond::esd) - .def_readwrite("value_nucleus", &Restraints::Bond::value_nucleus) - .def_readwrite("esd_nucleus", &Restraints::Bond::esd_nucleus) + .def(nb::init<>()) + .def_rw("id1", &Restraints::Bond::id1) + .def_rw("id2", &Restraints::Bond::id2) + .def_rw("type", &Restraints::Bond::type) + .def_rw("aromatic", &Restraints::Bond::aromatic) + .def_rw("value", &Restraints::Bond::value) + .def_rw("esd", &Restraints::Bond::esd) + .def_rw("value_nucleus", &Restraints::Bond::value_nucleus) + .def_rw("esd_nucleus", &Restraints::Bond::esd_nucleus) .def("lexicographic_str", &Restraints::Bond::lexicographic_str) .def("__repr__", [](const Restraints::Bond& self) { return ""; }); restraintsangle - .def(py::init<>()) - .def_readwrite("id1", &Restraints::Angle::id1) - .def_readwrite("id2", &Restraints::Angle::id2) - .def_readwrite("id3", &Restraints::Angle::id3) - .def_readwrite("value", &Restraints::Angle::value) - .def_readwrite("esd", &Restraints::Angle::esd) + .def(nb::init<>()) + .def_rw("id1", &Restraints::Angle::id1) + .def_rw("id2", &Restraints::Angle::id2) + .def_rw("id3", &Restraints::Angle::id3) + .def_rw("value", &Restraints::Angle::value) + .def_rw("esd", &Restraints::Angle::esd) .def("__repr__", [](const Restraints::Angle& self) { return ""; }); restraintstorsion - .def(py::init<>()) - .def_readwrite("label", &Restraints::Torsion::label) - .def_readwrite("id1", &Restraints::Torsion::id1) - .def_readwrite("id2", &Restraints::Torsion::id2) - .def_readwrite("id3", &Restraints::Torsion::id3) - .def_readwrite("id4", &Restraints::Torsion::id4) - .def_readwrite("value", &Restraints::Torsion::value) - .def_readwrite("esd", &Restraints::Torsion::esd) - .def_readwrite("period", &Restraints::Torsion::period) + .def(nb::init<>()) + .def_rw("label", &Restraints::Torsion::label) + .def_rw("id1", &Restraints::Torsion::id1) + .def_rw("id2", &Restraints::Torsion::id2) + .def_rw("id3", &Restraints::Torsion::id3) + .def_rw("id4", &Restraints::Torsion::id4) + .def_rw("value", &Restraints::Torsion::value) + .def_rw("esd", &Restraints::Torsion::esd) + .def_rw("period", &Restraints::Torsion::period) .def("__repr__", [](const Restraints::Torsion& self) { return ""; }); restraintschirality - .def(py::init<>()) - .def_readwrite("id_ctr", &Restraints::Chirality::id_ctr) - .def_readwrite("id1", &Restraints::Chirality::id1) - .def_readwrite("id2", &Restraints::Chirality::id2) - .def_readwrite("id3", &Restraints::Chirality::id3) - .def_readwrite("sign", &Restraints::Chirality::sign) + .def(nb::init<>()) + .def_rw("id_ctr", &Restraints::Chirality::id_ctr) + .def_rw("id1", &Restraints::Chirality::id1) + .def_rw("id2", &Restraints::Chirality::id2) + .def_rw("id3", &Restraints::Chirality::id3) + .def_rw("sign", &Restraints::Chirality::sign) .def("is_wrong", &Restraints::Chirality::is_wrong) .def("__repr__", [](const Restraints::Chirality& self) { return ""; }); restraintsplane - .def(py::init<>()) - .def_readwrite("label", &Restraints::Plane::label) - .def_readwrite("ids", &Restraints::Plane::ids) - .def_readwrite("esd", &Restraints::Plane::esd) + .def(nb::init<>()) + .def_rw("label", &Restraints::Plane::label) + .def_rw("ids", &Restraints::Plane::ids) + .def_rw("esd", &Restraints::Plane::esd) .def("__repr__", [](const Restraints::Plane& self) { return ""; }); restraints - .def(py::init<>()) - .def_readwrite("bonds", &Restraints::bonds) - .def_readwrite("angles", &Restraints::angles) - .def_readwrite("torsions", &Restraints::torsions) - .def_readwrite("chirs", &Restraints::chirs) - .def_readwrite("planes", &Restraints::planes) + .def(nb::init<>()) + .def_rw("bonds", &Restraints::bonds) + .def_rw("angles", &Restraints::angles) + .def_rw("torsions", &Restraints::torsions) + .def_rw("chirs", &Restraints::chirs) + .def_rw("planes", &Restraints::planes) .def("empty", &Restraints::empty) .def("get_bond", &Restraints::get_bond, - py::return_value_policy::reference_internal) + nb::rv_policy::reference_internal) .def("get_bond", [](Restraints& self, const std::string& a1, const std::string& a2) -> Restraints::Bond& { @@ -144,12 +144,12 @@ void add_chemcomp(py::module& m) { if (it == self.bonds.end()) fail("Bond restraint not found: " + a1 + "-" + a2); return *it; - }, py::return_value_policy::reference_internal) + }, nb::rv_policy::reference_internal) .def("find_shortest_path", &Restraints::find_shortest_path) .def("chiral_abs_volume", &Restraints::chiral_abs_volume) ; - py::enum_(chemcomp, "Group") + nb::enum_(chemcomp, "Group") .value("Peptide", ChemComp::Group::Peptide) .value("PPeptide", ChemComp::Group::PPeptide) .value("MPeptide", ChemComp::Group::MPeptide) @@ -162,21 +162,21 @@ void add_chemcomp(py::module& m) { .value("NonPolymer", ChemComp::Group::NonPolymer) .value("Null", ChemComp::Group::Null); chemcompatom - .def_readwrite("id", &ChemComp::Atom::id) - .def_readwrite("el", &ChemComp::Atom::el) - .def_readwrite("charge", &ChemComp::Atom::charge) - .def_readwrite("chem_type", &ChemComp::Atom::chem_type) + .def_rw("id", &ChemComp::Atom::id) + .def_rw("el", &ChemComp::Atom::el) + .def_rw("charge", &ChemComp::Atom::charge) + .def_rw("chem_type", &ChemComp::Atom::chem_type) .def("is_hydrogen", &ChemComp::Atom::is_hydrogen) ; - py::class_(chemcomp, "Aliasing") - .def_readonly("group", &ChemComp::Aliasing::group) + nb::class_(chemcomp, "Aliasing") + .def_ro("group", &ChemComp::Aliasing::group) .def("name_from_alias", &ChemComp::Aliasing::name_from_alias) ; chemcomp - .def_readwrite("name", &ChemComp::name) - .def_readwrite("group", &ChemComp::group) - .def_readonly("atoms", &ChemComp::atoms) - .def_readonly("rt", &ChemComp::rt) + .def_rw("name", &ChemComp::name) + .def_rw("group", &ChemComp::group) + .def_ro("atoms", &ChemComp::atoms) + .def_ro("rt", &ChemComp::rt) .def_static("read_group", &ChemComp::read_group) .def_static("group_str", &ChemComp::group_str) .def("set_group", &ChemComp::set_group) @@ -184,7 +184,7 @@ void add_chemcomp(py::module& m) { .def("find_atom", [](ChemComp& self, const std::string& atom_id) { auto it = self.find_atom(atom_id); return it != self.atoms.end() ? &*it : nullptr; - }, py::return_value_policy::reference_internal) + }, nb::rv_policy::reference_internal) .def("remove_hydrogens", &ChemComp::remove_hydrogens) ; m.def("make_chemcomp_from_block", &make_chemcomp_from_block); diff --git a/python/cif.cpp b/python/cif.cpp index 667b4cba9..f9aa89f21 100644 --- a/python/cif.cpp +++ b/python/cif.cpp @@ -9,27 +9,37 @@ #include "gemmi/read_cif.hpp" // for read_cif_gz #include "common.h" -#include +#include "make_iterator.h" +#include +#include +#include -namespace py = pybind11; using namespace gemmi::cif; -std::string pyobject_to_string(py::handle handle, bool raw) { +std::string pyobject_to_string(nb::handle handle, bool raw) { PyObject* ptr = handle.ptr(); if (ptr == Py_None) { return "?"; } else if (ptr == Py_False) { return "."; } else if (ptr == Py_True) { - throw py::value_error("unexpected value True"); + throw nb::value_error("unexpected value True"); } else if (raw || PyFloat_Check(ptr) || PyLong_Check(ptr)) { - return py::str(handle); + return nb::str(handle).c_str(); } else { - return gemmi::cif::quote(py::str(handle)); + return gemmi::cif::quote(nb::str(handle).c_str()); } } -std::vector quote_list(py::list items) { +nb::object string_to_pyobject(const std::string& s, bool raw) { + if (raw) + return nb::cast(s); + if (is_null(s)) + return nb::borrow(s[0] == '?' ? Py_None : Py_False); + return nb::cast(as_string(s)); +} + +std::vector quote_list(nb::list items) { size_t size = items.size(); std::vector ret; ret.reserve(size); @@ -43,28 +53,28 @@ T& add_to_vector(std::vector& vec, const T& new_item, int pos) { if (pos < 0) pos = (int) vec.size(); else if (pos > (int) vec.size()) - throw py::index_error(); + throw nb::index_error(); vec.insert(vec.begin() + pos, new_item); return vec[pos]; } // for delitem_slice namespace gemmi { namespace cif { -void delitem_at_index(Table& t, py::ssize_t idx) { t.remove_row((int)idx); } -void delitem_range(Table& t, py::ssize_t start, py::ssize_t end) { +void delitem_at_index(Table& t, size_t idx) { t.remove_row((int)idx); } +void delitem_range(Table& t, size_t start, size_t end) { t.remove_rows((int)start, (int)end); } } } // namespace gemmi::cif -void add_cif(py::module& cif) { - py::class_ cif_block(cif, "Block"); - py::class_ cif_item(cif, "Item"); - py::class_ cif_loop(cif, "Loop"); - py::class_ cif_column(cif, "Column"); - py::class_ cif_table(cif, "Table"); - py::class_ cif_table_row(cif_table, "Row"); +void add_cif(nb::module_& cif) { + nb::class_ cif_block(cif, "Block"); + nb::class_ cif_item(cif, "Item"); + nb::class_ cif_loop(cif, "Loop"); + nb::class_ cif_column(cif, "Column"); + nb::class_
cif_table(cif, "Table"); + nb::class_ cif_table_row(cif_table, "Row"); - py::enum_